using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Text; using System.Threading.Tasks; using System.Web.Script.Serialization; namespace CapyLauncher { public class SubStat { public int Id; public int Count = 1; public int Step; } public class BuildFile { static readonly int[] PromotionCaps = { 20, 30, 40, 50, 60, 70 }; readonly JavaScriptSerializer serializer = new JavaScriptSerializer { MaxJsonLength = int.MaxValue, RecursionLimit = 256 }; Dictionary root = new Dictionary(); public string Path { get; } public DateTime LoadedStamp { get; private set; } public bool Dirty { get; set; } public BuildFile(string path) { Path = path; } public void Load() { root = File.Exists(Path) ? serializer.DeserializeObject(File.ReadAllText(Path)) as Dictionary ?? new Dictionary() : new Dictionary(); if (root.TryGetValue("data", out object wrapped) && wrapped is Dictionary inner && inner.ContainsKey("avatars")) root = inner; Dict(root, "avatars"); List(root, "relics"); List(root, "lightcones"); LoadedStamp = File.Exists(Path) ? File.GetLastWriteTimeUtc(Path) : DateTime.MinValue; Dirty = false; } // ---- json tree helpers ---- public static Dictionary Dict(Dictionary parent, string key) { if (parent.TryGetValue(key, out object value) && value is Dictionary dict) return dict; var fresh = new Dictionary(); parent[key] = fresh; return fresh; } public static List List(Dictionary parent, string key) { if (parent.TryGetValue(key, out object value) && value is List list) return list; var fresh = value is IEnumerable items && !(value is string) ? items.Cast().ToList() : new List(); parent[key] = fresh; return fresh; } public static int Int(Dictionary d, string key, int fallback = 0) => d != null && d.TryGetValue(key, out object v) && v != null && !(v is string) && !(v is IEnumerable) ? Convert.ToInt32(v, CultureInfo.InvariantCulture) : fallback; public static string Str(Dictionary d, string key, string fallback = "") => d != null && d.TryGetValue(key, out object v) && v is string s ? s : fallback; public static int PromotionFor(int level) => PromotionCaps.Count(cap => level > cap); int NextUid(List items) => items.OfType>().Select(i => Int(i, "internal_uid")).DefaultIfEmpty(0).Max() + 1; // ---- avatars ---- public Dictionary Avatars => Dict(root, "avatars"); public Dictionary Avatar(int id) => Avatars.TryGetValue(id.ToString(), out object value) ? value as Dictionary : null; public Dictionary AddAvatar(AvatarInfo info) { var avatar = new Dictionary { ["avatar_id"] = info.Id, ["data"] = new Dictionary { ["rank"] = 0, ["skills"] = new Dictionary(), ["skills_by_anchor_type"] = new Dictionary(), }, ["level"] = 80, ["promotion"] = 6, ["sp_max"] = info.Sp, ["sp_value"] = info.Sp / 2, ["techniques"] = new List { info.Id * 100 + 1 }, ["enhanced_id"] = 0, }; Avatars[info.Id.ToString()] = avatar; foreach (Anchor anchor in info.Anchors) SetAnchor(avatar, anchor, anchor.Max); Dirty = true; return avatar; } public void RemoveAvatar(int id) { Avatars.Remove(id.ToString()); foreach (var item in Relics.Concat(Lightcones).OfType>()) { if (Int(item, "equip_avatar") == id) item["equip_avatar"] = 0; } Dirty = true; } public static int Rank(Dictionary avatar) => Int(Dict(avatar, "data"), "rank"); public void SetRank(Dictionary avatar, int rank) { Dict(avatar, "data")["rank"] = rank; Dirty = true; } public void SetLevel(Dictionary avatar, int level) { avatar["level"] = level; avatar["promotion"] = PromotionFor(level); Dirty = true; } public static int AnchorLevel(Dictionary avatar, Anchor anchor) => Int(Dict(Dict(avatar, "data"), "skills_by_anchor_type"), anchor.Number.ToString()); public void SetAnchor(Dictionary avatar, Anchor anchor, int level) { var data = Dict(avatar, "data"); Dict(data, "skills_by_anchor_type")[anchor.Number.ToString()] = level; var skills = Dict(data, "skills"); foreach (int point in anchor.Points) skills[point.ToString()] = level; Dirty = true; } public void MaxAvatar(Dictionary avatar, AvatarInfo info, bool eidolons) { SetLevel(avatar, 80); if (eidolons) SetRank(avatar, info.MaxRank); foreach (Anchor anchor in info.Anchors) SetAnchor(avatar, anchor, anchor.Max); } // ---- light cones ---- public List Lightcones => List(root, "lightcones"); public Dictionary LightconeOf(int avatarId) => Lightcones.OfType>().FirstOrDefault(l => Int(l, "equip_avatar") == avatarId); public Dictionary EquipLightcone(int avatarId, int itemId) { var cone = LightconeOf(avatarId); if (cone == null) { cone = new Dictionary { ["equip_avatar"] = avatarId, ["internal_uid"] = NextUid(Lightcones), ["item_id"] = itemId, ["level"] = 80, ["promotion"] = 6, ["rank"] = 1, }; Lightcones.Add(cone); } cone["item_id"] = itemId; Dirty = true; return cone; } public void UnequipLightcone(int avatarId) { var cone = LightconeOf(avatarId); if (cone != null) Lightcones.Remove(cone); Dirty = true; } public void SetLightconeLevel(Dictionary cone, int level) { cone["level"] = level; cone["promotion"] = PromotionFor(level); Dirty = true; } // ---- relics ---- public List Relics => List(root, "relics"); public static int Slot(Dictionary relic) => Int(relic, "relic_id") % 10; public Dictionary RelicOf(int avatarId, int slot) => Relics.OfType>().FirstOrDefault(r => Int(r, "equip_avatar") == avatarId && Slot(r) == slot); public Dictionary PutRelic(int avatarId, int slot, RelicSetInfo set, int mainAffix) { var relic = RelicOf(avatarId, slot); if (relic == null) { relic = new Dictionary { ["equip_avatar"] = avatarId, ["internal_uid"] = NextUid(Relics), ["level"] = 15, ["sub_affixes"] = new List(), }; Relics.Add(relic); } relic["relic_id"] = set.Pieces[slot]; relic["relic_set_id"] = set.Id; relic["main_affix_id"] = mainAffix; Dirty = true; return relic; } public void RemoveRelic(int avatarId, int slot) { var relic = RelicOf(avatarId, slot); if (relic != null) Relics.Remove(relic); Dirty = true; } public static List Subs(Dictionary relic) => List(relic, "sub_affixes").OfType>() .Select(s => new SubStat { Id = Int(s, "sub_affix_id"), Count = Int(s, "count", 1), Step = Int(s, "step") }).ToList(); public void SetSubs(Dictionary relic, IEnumerable subs) { relic["sub_affixes"] = subs.Where(s => s.Id != 0).Select(s => (object)new Dictionary { ["sub_affix_id"] = s.Id, ["count"] = s.Count, ["step"] = s.Step, }).ToList(); Dirty = true; } public void Touch() => Dirty = true; public void Restamp() => LoadedStamp = File.Exists(Path) ? File.GetLastWriteTimeUtc(Path) : DateTime.MinValue; // ---- battle ---- public Dictionary Battle => Dict(root, "battle_config"); // ---- saving ---- public string ToJson() { var text = new StringBuilder(); Write(text, root, 0); text.Append('\n'); return text.ToString(); } static void Write(StringBuilder text, object value, int depth) { string pad = new string(' ', (depth + 1) * 2), close = new string(' ', depth * 2); switch (value) { case null: text.Append("null"); break; case string s: text.Append(new JavaScriptSerializer().Serialize(s)); break; case bool b: text.Append(b ? "true" : "false"); break; case Dictionary dict: if (dict.Count == 0) { text.Append("{}"); break; } text.Append("{\n"); int i = 0; foreach (var pair in dict) { text.Append(pad).Append(new JavaScriptSerializer().Serialize(pair.Key)).Append(": "); Write(text, pair.Value, depth + 1); text.Append(++i < dict.Count ? ",\n" : "\n"); } text.Append(close).Append('}'); break; case IEnumerable list: var items = list.Cast().ToList(); if (items.Count == 0) { text.Append("[]"); break; } bool flat = items.All(x => !(x is Dictionary) && (x is string || !(x is IEnumerable))); if (flat) { text.Append('['); for (int j = 0; j < items.Count; j++) { if (j > 0) text.Append(", "); Write(text, items[j], depth + 1); } text.Append(']'); break; } text.Append("[\n"); for (int j = 0; j < items.Count; j++) { text.Append(pad); Write(text, items[j], depth + 1); text.Append(j + 1 < items.Count ? ",\n" : "\n"); } text.Append(close).Append(']'); break; case double d: text.Append(d.ToString("R", CultureInfo.InvariantCulture)); break; case float f: text.Append(f.ToString("R", CultureInfo.InvariantCulture)); break; case IFormattable number: text.Append(number.ToString(null, CultureInfo.InvariantCulture)); break; default: text.Append(new JavaScriptSerializer().Serialize(value)); break; } } public void WriteToDisk() { string temp = Path + ".tmp"; File.WriteAllText(temp, ToJson(), new UTF8Encoding(false)); if (File.Exists(Path)) { string backup = Path + ".bak"; File.Replace(temp, Path, backup, true); } else { File.Move(temp, Path); } LoadedStamp = File.GetLastWriteTimeUtc(Path); Dirty = false; } public async Task SendToServer(string dispatch) { string body = ToJson(); var request = (HttpWebRequest)WebRequest.Create(dispatch.TrimEnd('/') + "/srtools"); request.Method = "POST"; request.ContentType = "application/json"; request.Timeout = 10000; byte[] bytes = Encoding.UTF8.GetBytes(body); using (Stream stream = await request.GetRequestStreamAsync()) await stream.WriteAsync(bytes, 0, bytes.Length); using (var response = (HttpWebResponse)await request.GetResponseAsync()) using (var reader = new StreamReader(response.GetResponseStream())) { var reply = serializer.DeserializeObject(await reader.ReadToEndAsync()) as Dictionary; string message = Str(reply, "message", "no reply"); if (message == "OK") { await Task.Delay(150); LoadedStamp = File.Exists(Path) ? File.GetLastWriteTimeUtc(Path) : DateTime.MinValue; Dirty = false; } return message; } } } }