using System; using System.IO; using System.Security.Cryptography; using Ashfall.Core; using UnityEngine; namespace Ashfall.Runtime { public sealed class UnityWorldCodec : IWorldCodec { public string Encode(WorldState world) => JsonUtility.ToJson(world); public WorldState Decode(string json) => JsonUtility.FromJson(json); } [Serializable] public sealed class JoinIdentity { public string playerId, token, name; public Species species; public const string Protocol = "ashfall-prototype-1"; public string protocol = Protocol; } [Serializable] public sealed class WorldPacket { public bool full; public WorldState world; public string recipient; } [Serializable] public sealed class CommandReply { public long sequence; public bool success; public string message; } public static class LocalProfile { public static JoinIdentity Load(string profile) { string safe = ""; foreach (char c in profile) if (char.IsLetterOrDigit(c) || c == '-') safe += c; if (safe == "") safe = "default"; string directory = Path.Combine(Application.persistentDataPath, "Profiles"); Directory.CreateDirectory(directory); string path = Path.Combine(directory, safe + ".json"); if (File.Exists(path)) { var identity = JsonUtility.FromJson(File.ReadAllText(path)); if (identity != null && Guid.TryParse(identity.playerId, out _) && identity.token?.Length == 64) return identity; throw new InvalidDataException("本地身份文件无效;请备份 Profiles 目录后使用新的 --profile 名称。"); } byte[] secret = new byte[32]; using (var rng = RandomNumberGenerator.Create()) rng.GetBytes(secret); var value = new JoinIdentity { playerId = Guid.NewGuid().ToString("N"), token = BitConverter.ToString(secret).Replace("-", ""), name = "幸存者" }; File.WriteAllText(path, JsonUtility.ToJson(value)); return value; } public static string TokenHash(string token) { using (var hash = SHA256.Create()) return Convert.ToBase64String(hash.ComputeHash(System.Text.Encoding.UTF8.GetBytes(token ?? ""))); } } }