加入用户最近100场战绩记录并实现展示
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -58,9 +58,11 @@ public class ChartFileEntry
|
||||
[CreateAssetMenu(fileName = "NewSongData", menuName = "EaseOut/SongData")]
|
||||
public class SongData : ScriptableObject
|
||||
{
|
||||
private const string SecureSaveCategory = "song_runtime";
|
||||
public string usernameID = "user_TestUser@FloatGaming";
|
||||
public string songName;
|
||||
public int songID;
|
||||
public bool isUnlocked;
|
||||
public int songLength_second;
|
||||
public string artistName;
|
||||
public string painter;
|
||||
@@ -289,7 +291,7 @@ public class SongData : ScriptableObject
|
||||
if (chartFileMap == null) chartFileMap = new Dictionary<int, TextAsset>();
|
||||
if (chartFiles == null) chartFiles = new List<ChartFileEntry>();
|
||||
|
||||
// 尝试从持久化存储加载数据
|
||||
// 灏濊瘯浠庢寔涔呭寲瀛樺偍鍔犺浇鏁版嵁
|
||||
LoadPersistent();
|
||||
|
||||
SyncRecordsFromChartFiles();
|
||||
@@ -317,8 +319,7 @@ public class SongData : ScriptableObject
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将数据保存到持久化存储,并附加 MD5 校验和防止篡改。
|
||||
/// </summary>
|
||||
/// 灏嗘暟鎹繚瀛樺埌鎸佷箙鍖栧瓨鍌紝骞堕檮鍔?MD5 鏍¢獙鍜岄槻姝㈢鏀广€? /// </summary>
|
||||
public void SavePersistent()
|
||||
{
|
||||
if (songID == 0)
|
||||
@@ -328,28 +329,23 @@ public class SongData : ScriptableObject
|
||||
|
||||
try
|
||||
{
|
||||
// 在保存前确保 chartFiles 已同步,这样在编辑器中查看 SO 时数据也是最新的
|
||||
// 鍦ㄤ繚瀛樺墠纭繚 chartFiles 宸插悓姝ワ紝杩欐牱鍦ㄧ紪杈戝櫒涓煡鐪?SO 鏃舵暟鎹篃鏄渶鏂扮殑
|
||||
SyncEntriesFromMaps();
|
||||
|
||||
string path = System.IO.Path.Combine(Application.persistentDataPath, $"SongData_{songID}.json");
|
||||
SongDataSerializable serializable = new SongDataSerializable(this);
|
||||
|
||||
// 1. 序列化基础数据
|
||||
// 1. 搴忓垪鍖栧熀纭€鏁版嵁
|
||||
string jsonWithoutHash = JsonUtility.ToJson(serializable);
|
||||
|
||||
// 2. 计算 MD5 (使用自定义盐值提高安全性)
|
||||
// 2. 璁$畻 MD5 (浣跨敤鑷畾涔夌洂鍊兼彁楂樺畨鍏ㄦ€?
|
||||
serializable.dataHash = CalculateMD5(jsonWithoutHash + "bansonic2026@fudongyouxi");
|
||||
|
||||
// 3. 再次序列化包含 Hash 的最终数据
|
||||
string finalJson = JsonUtility.ToJson(serializable, true);
|
||||
|
||||
// 确保目录存在
|
||||
string dir = System.IO.Path.GetDirectoryName(path);
|
||||
if (!System.IO.Directory.Exists(dir)) System.IO.Directory.CreateDirectory(dir);
|
||||
SecureSaveVault.SaveRawJson(SecureSaveCategory, songID.ToString(), finalJson, GetLegacySavePath());
|
||||
|
||||
System.IO.File.WriteAllText(path, finalJson);
|
||||
|
||||
Debug.Log($"[SongData] {songName} (ID:{songID}) saved to {path}");
|
||||
Debug.Log($"[SongData] {songName} (ID:{songID}) saved to secure vault.");
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
@@ -365,21 +361,32 @@ public class SongData : ScriptableObject
|
||||
}
|
||||
}
|
||||
|
||||
public void SetUnlocked(bool value)
|
||||
{
|
||||
if (isUnlocked == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
isUnlocked = value;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
UnityEditor.EditorUtility.SetDirty(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从持久化 JSON 恢复数据。
|
||||
/// 校验逻辑已迁移至 DataCheck 脚本,此处仅执行基础加载。
|
||||
/// </summary>
|
||||
/// 浠庢寔涔呭寲 JSON 鎭㈠鏁版嵁銆? /// 鏍¢獙閫昏緫宸茶縼绉昏嚦 DataCheck 鑴氭湰锛屾澶勪粎鎵ц鍩虹鍔犺浇銆? /// </summary>
|
||||
public void LoadPersistent()
|
||||
{
|
||||
if (_isPersistentDataLoaded) return;
|
||||
|
||||
string path = System.IO.Path.Combine(Application.persistentDataPath, $"SongData_{songID}.json");
|
||||
if (System.IO.File.Exists(path))
|
||||
string json;
|
||||
if (SecureSaveVault.TryLoadRawJson(SecureSaveCategory, songID.ToString(), out json, GetLegacySavePath()))
|
||||
{
|
||||
try
|
||||
{
|
||||
string json = System.IO.File.ReadAllText(path);
|
||||
// 尝试解析数据
|
||||
// 灏濊瘯瑙f瀽鏁版嵁
|
||||
SongDataSerializable serializable = JsonUtility.FromJson<SongDataSerializable>(json);
|
||||
if (serializable != null)
|
||||
{
|
||||
@@ -391,7 +398,7 @@ public class SongData : ScriptableObject
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Debug.LogWarning($"[SongData] {songName} 基础加载失败: {e.Message}");
|
||||
Debug.LogWarning($"[SongData] {songName} 鍩虹鍔犺浇澶辫触: {e.Message}");
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -456,11 +463,7 @@ public class SongData : ScriptableObject
|
||||
|
||||
UpdateOverallRecordsFromEntries();
|
||||
|
||||
string path = System.IO.Path.Combine(Application.persistentDataPath, $"SongData_{songID}.json");
|
||||
if (System.IO.File.Exists(path))
|
||||
{
|
||||
try { System.IO.File.Delete(path); } catch { }
|
||||
}
|
||||
SecureSaveVault.Delete(SecureSaveCategory, songID.ToString(), GetLegacySavePath());
|
||||
|
||||
SavePersistent();
|
||||
|
||||
@@ -470,8 +473,12 @@ public class SongData : ScriptableObject
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当检测到篡改或文件损坏时,重置所有动态记录并立即重新保存文件以恢复合法性。
|
||||
/// </summary>
|
||||
/// 褰撴娴嬪埌绡℃敼鎴栨枃浠舵崯鍧忔椂锛岄噸缃墍鏈夊姩鎬佽褰曞苟绔嬪嵆閲嶆柊淇濆瓨鏂囦欢浠ユ仮澶嶅悎娉曟€с€? /// </summary>
|
||||
private string GetLegacySavePath()
|
||||
{
|
||||
return System.IO.Path.Combine(Application.persistentDataPath, $"SongData_{songID}.json");
|
||||
}
|
||||
|
||||
private void ResetToZeroAndSave()
|
||||
{
|
||||
// 1. 重置所有动态数值字段
|
||||
@@ -498,15 +505,14 @@ public class SongData : ScriptableObject
|
||||
if (levelProgressMap == null) levelProgressMap = new Dictionary<int, float>();
|
||||
else levelProgressMap.Clear();
|
||||
|
||||
// 3. 立即调用保存方法,生成一个新的、带有正确哈希值的合法文件
|
||||
// 3. 绔嬪嵆璋冪敤淇濆瓨鏂规硶锛岀敓鎴愪竴涓柊鐨勩€佸甫鏈夋纭搱甯屽€肩殑鍚堟硶鏂囦欢
|
||||
SavePersistent();
|
||||
|
||||
Debug.LogWarning($"[SongData] {songName} has been fully reset to zero due to data integrity failure.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 静态校验方法,可在异步线程调用,判断 JSON 内容是否合法。
|
||||
/// </summary>
|
||||
/// 闈欐€佹牎楠屾柟娉曪紝鍙湪寮傛绾跨▼璋冪敤锛屽垽鏂?JSON 鍐呭鏄惁鍚堟硶銆? /// </summary>
|
||||
public static bool VerifyJsonIntegrity(string json, string salt, out SongDataSerializable result)
|
||||
{
|
||||
result = null;
|
||||
@@ -519,7 +525,7 @@ public class SongData : ScriptableObject
|
||||
serializable.dataHash = "";
|
||||
string currentJson = JsonUtility.ToJson(serializable);
|
||||
|
||||
// 使用静态 MD5 计算
|
||||
// 浣跨敤闈欐€?MD5 璁$畻
|
||||
string calculatedHash = StaticCalculateMD5(currentJson + salt);
|
||||
|
||||
result = serializable;
|
||||
@@ -620,8 +626,7 @@ public class SongData : ScriptableObject
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 Dictionary 中的数据同步回 ChartFileEntry 列表,确保 SO 内存数据与持久化数据一致。
|
||||
/// </summary>
|
||||
/// 灏?Dictionary 涓殑鏁版嵁鍚屾鍥?ChartFileEntry 鍒楄〃锛岀‘淇?SO 鍐呭瓨鏁版嵁涓庢寔涔呭寲鏁版嵁涓€鑷淬€? /// </summary>
|
||||
public void SyncEntriesFromMaps()
|
||||
{
|
||||
if (chartFiles == null) return;
|
||||
@@ -898,3 +903,4 @@ public class SongData : ScriptableObject
|
||||
CalculateHighestPersonalRecord();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
using System.Collections;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
|
||||
public class SongDataLoader : MonoBehaviour
|
||||
{
|
||||
private const string SecureCategory = "song_export";
|
||||
private string jsonDirectory;
|
||||
public List<SongDataSerializable> loadedSongs = new List<SongDataSerializable>();
|
||||
[SerializeField] private int _yieldEveryNFiles = 5;
|
||||
@@ -17,26 +18,27 @@ public class SongDataLoader : MonoBehaviour
|
||||
|
||||
IEnumerator LoadAllSongsFromJsonRoutine()
|
||||
{
|
||||
if (!Directory.Exists(jsonDirectory))
|
||||
List<string> loadedJsonList = SecureSaveVault.LoadAllRawJson(SecureCategory, jsonDirectory);
|
||||
if (loadedJsonList == null || loadedJsonList.Count == 0)
|
||||
{
|
||||
Debug.LogError("JSON �ļ��в����ڣ�");
|
||||
Debug.LogError("JSON 持久化数据不存在");
|
||||
yield break;
|
||||
}
|
||||
|
||||
loadedSongs.Clear();
|
||||
IEnumerable<string> jsonFiles = Directory.EnumerateFiles(jsonDirectory, "*.json");
|
||||
int counter = 0;
|
||||
|
||||
foreach (string file in jsonFiles)
|
||||
for (int i = 0; i < loadedJsonList.Count; i++)
|
||||
{
|
||||
string json = File.ReadAllText(file);
|
||||
string json = loadedJsonList[i];
|
||||
SongDataSerializable jsonData = JsonUtility.FromJson<SongDataSerializable>(json);
|
||||
loadedSongs.Add(jsonData);
|
||||
// 由于 SongDataSerializable 现在只存储动态分数,不再包含 songName
|
||||
Debug.Log($"Loaded persistent song data from: {Path.GetFileName(file)}");
|
||||
Debug.Log("Loaded persistent song data from secure vault.");
|
||||
counter++;
|
||||
if (_yieldEveryNFiles > 0 && counter % _yieldEveryNFiles == 0)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using System.IO;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
|
||||
public class SongDataManager : MonoBehaviour
|
||||
{
|
||||
private const string SecureCategory = "song_export";
|
||||
private string jsonDirectory;
|
||||
|
||||
[Header("Settings")]
|
||||
@@ -12,12 +13,6 @@ public class SongDataManager : MonoBehaviour
|
||||
{
|
||||
jsonDirectory = Path.Combine(Application.persistentDataPath, "song_json_export");
|
||||
|
||||
if (!Directory.Exists(jsonDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(jsonDirectory);
|
||||
Debug.Log("Created song JSON directory: " + jsonDirectory);
|
||||
}
|
||||
|
||||
if (autoSaveOnStart)
|
||||
{
|
||||
StartCoroutine(SaveAllSongDataToJsonRoutine());
|
||||
@@ -39,7 +34,7 @@ public class SongDataManager : MonoBehaviour
|
||||
yield break;
|
||||
}
|
||||
|
||||
Debug.Log($"Start exporting {allSongData.Length} songs to JSON...");
|
||||
Debug.Log($"Start exporting {allSongData.Length} songs to secure vault...");
|
||||
|
||||
for (int i = 0; i < allSongData.Length; i++)
|
||||
{
|
||||
@@ -47,14 +42,13 @@ public class SongDataManager : MonoBehaviour
|
||||
if (song == null) continue;
|
||||
|
||||
SongDataSerializable jsonData = new SongDataSerializable(song);
|
||||
string json = JsonUtility.ToJson(jsonData, true);
|
||||
|
||||
string filePath = Path.Combine(jsonDirectory, song.songID + ".json");
|
||||
File.WriteAllText(filePath, json);
|
||||
string json = JsonUtility.ToJson(jsonData, false);
|
||||
string legacyPath = Path.Combine(jsonDirectory, song.songID + ".json");
|
||||
SecureSaveVault.SaveRawJson(SecureCategory, song.songID.ToString(), json, legacyPath);
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
Debug.Log("All song JSON files exported.");
|
||||
|
||||
Debug.Log("All song JSON files exported to secure vault.");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user