using Il2Cpp; using Il2CppAlmanacData; using Il2CppInterop.Runtime.InteropTypes.Arrays; using JetBrains.Annotations; using MelonLoader; using MelonLoader.TinyJSON; using Newtonsoft.Json; using PvZ_Fusion_Translator.AssetStore; using PvZ_Fusion_Translator.Patches.GameObjects; using PvZ_Fusion_Translator.Patches.GameObjects.MinorObjects; using PvZ_Fusion_Translator.Patches.Modes.Odyssey; using PvZ_Fusion_Translator.Patches.OtherManagers; using System.CodeDom.Compiler; using System.Text; using System.Text.Encodings.Web; using System.Text.Json; using UnityEngine; namespace PvZ_Fusion_Translator { internal static class FileLoader { internal enum AssetType { Textures, Strings, Dumps, Almanac, Sprites } public static string GetAssetDir(AssetType assetType, Utils.LanguageEnum? language = null) { string languagePath = language.HasValue ? ("Localization" + Path.DirectorySeparatorChar + language.ToString()) : string.Empty; return Path.Combine(Core.Instance.modsDirectory, languagePath, assetType.ToString()); } // private static string GetAssetDir(AssetType assetType) => Path.Combine(Core.Instance.modsDirectory, assetType.ToString()); internal static void LoadStrings() => LoadStrings(Utils.Language); internal static void LoadStrings(Utils.LanguageEnum language) { string stringDir = GetAssetDir(AssetType.Strings, Utils.Language); if (!Directory.Exists(stringDir)) { Directory.CreateDirectory(stringDir); } if(!Utils.useLocal) { // load translationStrings string translationStringsContent = Utils.GetDataFromWeb($"https://raw.githubusercontent.com/Teyliu/PVZF-Translation/refs/heads/main/PvZ_Fusion_Translator/Localization/{Utils.Language.ToString()}/Strings/translation_strings.json").Result; if(translationStringsContent != null) { LoadTranslationStrings(translationStringsContent); } else { string translationStringsPath = Path.Combine(GetAssetDir(AssetType.Strings, Utils.Language), "translation_strings.json"); if(File.Exists(translationStringsPath)) { LoadTranslationStrings(File.ReadAllText(translationStringsPath)); } } // load translationRegexs string translationRegexsContent = Utils.GetDataFromWeb($"https://raw.githubusercontent.com/Teyliu/PVZF-Translation/refs/heads/main/PvZ_Fusion_Translator/Localization/{Utils.Language.ToString()}/Strings/translation_regexs.json").Result; if(translationRegexsContent != null) { LoadTranslationRegexs(translationRegexsContent); } else { string translationRegexsPath = Path.Combine(GetAssetDir(AssetType.Strings, Utils.Language), "translation_regexs.json"); if(File.Exists(translationRegexsPath)) { LoadTranslationRegexs(File.ReadAllText(translationRegexsPath)); } } // load changelog LoadChangelogText(); // load tips string izTipsContent = Utils.GetDataFromWeb($"https://raw.githubusercontent.com/Teyliu/PVZF-Translation/refs/heads/main/PvZ_Fusion_Translator/Localization/{Utils.Language.ToString()}/Strings/tips_iz.json").Result; if(izTipsContent != null) { LoadIZStrings(izTipsContent); } else { string izTipsPath = Path.Combine(GetAssetDir(AssetType.Strings, Utils.Language), "tips_iz.json"); if(File.Exists(izTipsPath)) { LoadIZStrings(File.ReadAllText(izTipsPath)); } } string fsTipsContent = Utils.GetDataFromWeb($"https://raw.githubusercontent.com/Teyliu/PVZF-Translation/refs/heads/main/PvZ_Fusion_Translator/Localization/{Utils.Language.ToString()}/Strings/tips_fs.json").Result; if(fsTipsContent != null) { LoadFSStrings(fsTipsContent); } else { string fsTipsPath = Path.Combine(GetAssetDir(AssetType.Strings, Utils.Language), "tips_fs.json"); if(File.Exists(fsTipsPath)) { LoadFSStrings(File.ReadAllText(fsTipsPath)); } } // load almanacs string almanacDir = GetAssetDir(AssetType.Almanac, Utils.Language); if(!Directory.Exists(almanacDir)) { Directory.CreateDirectory(almanacDir); } string plantAlmanacContent = Utils.GetDataFromWeb($"https://raw.githubusercontent.com/Teyliu/PVZF-Translation/refs/heads/main/PvZ_Fusion_Translator/Localization/{Utils.Language.ToString()}/Almanac/LawnStringsTranslate.json").Result; if(plantAlmanacContent != null) { AlmanacPlantMenu_Patch.almanacJson = plantAlmanacContent; } else { string path = Path.Combine(almanacDir, "LawnStringsTranslate.json"); if(File.Exists(path)) { AlmanacPlantMenu_Patch.almanacJson = File.ReadAllText(path); } } string zombieAlmanacContent = Utils.GetDataFromWeb($"https://raw.githubusercontent.com/Teyliu/PVZF-Translation/refs/heads/main/PvZ_Fusion_Translator/Localization/{Utils.Language.ToString()}/Almanac/ZombieStringsTranslate.json").Result; if(zombieAlmanacContent != null) { AlmanacZombieMenu_Patch.almanacJson = zombieAlmanacContent; } else { string path = Path.Combine(almanacDir, "ZombieStringsTranslate.json"); if(File.Exists(path)) { AlmanacZombieMenu_Patch.almanacJson = File.ReadAllText(path); } } string detailStringsContent = Utils.GetDataFromWeb($"https://raw.githubusercontent.com/Teyliu/PVZF-Translation/refs/heads/3.6/PvZ_Fusion_Translator/Localization/{Utils.Language.ToString()}/Almanac/DetailStrings.json").Result; if(detailStringsContent != null) { LoadDetailStrings(detailStringsContent); } else { string path = Path.Combine(almanacDir, "DetailStringsTranslate.json"); if(File.Exists(path)) { LoadDetailStrings(File.ReadAllText(path)); } } // load abyss buffs Patches.Modes.Abyss.AbyssBuffMenu_Patch.LoadAbyssBuffData(); SaveStrings(); DumpJson(); } else { if (!Directory.Exists(stringDir)) { Directory.CreateDirectory(stringDir); } try { foreach (string filepath in Directory.EnumerateFiles(stringDir, "*.json", SearchOption.AllDirectories)) { #if OBFUSCATE if (CheckSumStore.IsModified(filepath)) { Log.LogError("File {0} was modified!" , filepath); continue; } #endif string fileName = Path.GetFileNameWithoutExtension(filepath); StringStore.stringsDict[fileName] = filepath; #if DEBUG Log.LogInfo("Loading File : " + filepath); #endif string jsonString = File.ReadAllText(filepath); if (fileName.EndsWith("_strings")) { LoadTranslationStrings(jsonString); } else if (fileName.EndsWith("_regexs")) { LoadTranslationRegexs(jsonString); } else if(fileName.EndsWith("tips_iz")) { LoadIZStrings(jsonString); } else if(fileName.EndsWith("tips_fs")) { LoadFSStrings(jsonString); } else if(fileName.EndsWith("abyss_buffs")) { Patches.Modes.Abyss.AbyssBuffMenu_Patch.LoadAbyssBuffData(); } } string almanacDir = GetAssetDir(AssetType.Almanac, Utils.Language); if(!Directory.Exists(almanacDir)) { Directory.CreateDirectory(almanacDir); } string plantAlmanacPath = Path.Combine(almanacDir, "LawnStringsTranslate.json"); if(File.Exists(plantAlmanacPath)) { AlmanacPlantMenu_Patch.almanacJson = File.ReadAllText(plantAlmanacPath); } string zombieAlmanacPath = Path.Combine(almanacDir, "ZombieStringsTranslate.json"); if(File.Exists(zombieAlmanacPath)) { AlmanacZombieMenu_Patch.almanacJson = File.ReadAllText(zombieAlmanacPath); } string detailStringsPath = Path.Combine(almanacDir, "DetailStringsTranslate.json"); if(File.Exists(detailStringsPath)) { LoadDetailStrings(File.ReadAllText(detailStringsPath)); } SaveStrings(); DumpJson(); } catch (Exception e) { Log.LogError("Error loading string."); Log.LogError(e.GetType() + " " + e.Message); } } Log.LogInfo("Strings loaded successfully."); } internal static void LoadTranslationStrings(string content) { Dictionary dictionary = System.Text.Json.JsonSerializer.Deserialize>(content); foreach (var (key, value) in dictionary) { StringStore.translationString[key] = value; } } internal static void LoadTranslationRegexs(string content) { Dictionary dictionary = System.Text.Json.JsonSerializer.Deserialize>(content); foreach (var (key, value) in dictionary) { StringStore.translationStringRegex[key] = value; } Zombie_Patch.LoadHPStrings(); } internal static void LoadIZStrings(string content) { var izDump = DumpIZStrings(); var izLevelData = izDump.Item1; var izLevelDataDump = izDump.Item2; var izTranslatedTips = System.Text.Json.JsonSerializer.Deserialize>(content); foreach (var level in izLevelData) { string data = level.text; LevelData levelData = JsonUtility.FromJson(data); if(levelData.tips != null) { if(izTranslatedTips.ContainsKey(level.name) && !StringStore.izTipCollectionString.ContainsKey(levelData.tips)) { StringStore.izLevelTipDictionary.Add(level.name, izTranslatedTips[level.name]); StringStore.izTipCollectionString.Add(levelData.tips, izTranslatedTips[level.name]); } } } } internal static void LoadFSStrings(string content) { var fusionShowcaseDump = DumpFSStrings(); var fusionShowcaseData = fusionShowcaseDump.Item1; var fusionShowcaseDataDump = fusionShowcaseDump.Item2; var fsTranslatedTips = System.Text.Json.JsonSerializer.Deserialize>(content); foreach (var level in fusionShowcaseData) { string data = level.text; LevelData levelData = JsonUtility.FromJson(data); if (levelData.tips != null) { if (fsTranslatedTips.ContainsKey(level.name) && !StringStore.fsTipCollectionString.ContainsKey(levelData.tips)) { StringStore.fsLevelTipDictionary.Add(level.name, fsTranslatedTips[level.name]); StringStore.fsTipCollectionString.Add(levelData.tips, fsTranslatedTips[level.name]); } } } } internal static void LoadDetailStrings(string content) { var detailStringsDump = DumpDetailStrings(); var detailStringsData = detailStringsDump.Item1; var detailStrings = detailStringsDump.Item2; var translatedDetailStrings = System.Text.Json.JsonSerializer.Deserialize>(content); AlmanacSelectMenu_Patch.detailStrings = translatedDetailStrings; foreach (var detailString in detailStringsData.details) { if (translatedDetailStrings.ContainsKey(detailString.title)) { if(!AlmanacSelectMenu_Patch.detailTranslateStrings.ContainsKey(detailString.text)) { AlmanacSelectMenu_Patch.detailTranslateStrings.Add(detailString.text, translatedDetailStrings[detailString.title]); } else { AlmanacSelectMenu_Patch.detailTranslateStrings[detailString.text] = translatedDetailStrings[detailString.title]; } } } } internal static void LoadChangelogText() { string stringDir = FileLoader.GetAssetDir(FileLoader.AssetType.Strings, Utils.Language); string changelogDir = Path.Combine(stringDir, "changelog.txt"); string changelogText = ""; if (!File.Exists(changelogDir)) { File.WriteAllText(changelogDir, ""); } changelogText = File.ReadAllText(changelogDir); if(!Utils.useLocal) { string changelogContent = Utils.GetDataFromWeb($"https://raw.githubusercontent.com/Teyliu/PVZF-Translation/refs/heads/main/PvZ_Fusion_Translator/Localization/{Utils.Language.ToString()}/Strings/changelog.txt").Result; if(changelogContent != null) { changelogText = changelogContent; } } NoticePauseMenu_Patch.changelogText = changelogText; } internal static void LoadTravelBuffs() { if (!Utils.useLocal) { string travelBuffsContent = Utils.GetDataFromWeb($"https://raw.githubusercontent.com/Teyliu/PVZF-Translation/refs/heads/main/PvZ_Fusion_Translator/Localization/{Utils.Language.ToString()}/Strings/travel_buffs.json").Result; if (travelBuffsContent != null) { TravelMgr_Patch.translatedTravelBuffs = TravelMgr_Patch.GenerateTranslatedTravelBuffs(travelBuffsContent); } else { string travelBuffsPath = Path.Combine(GetAssetDir(AssetType.Strings, Utils.Language), "travel_buffs.json"); if (File.Exists(travelBuffsPath)) { TravelMgr_Patch.translatedTravelBuffs = TravelMgr_Patch.GenerateTranslatedTravelBuffs(File.ReadAllText(travelBuffsPath)); } } } else { string stringDir = FileLoader.GetAssetDir(FileLoader.AssetType.Strings, Utils.Language); foreach (string filepath in Directory.EnumerateFiles(stringDir, "*.json", SearchOption.AllDirectories)) { string fileName = Path.GetFileNameWithoutExtension(filepath); string jsonString = File.ReadAllText(filepath); if (fileName.EndsWith("travel_buffs")) { TravelMgr_Patch.translatedTravelBuffs = TravelMgr_Patch.GenerateTranslatedTravelBuffs(jsonString); } } } } public static bool loadDefaultTextures = MelonPreferences.GetEntryValue("PvZ_Fusion_Translator", "DefaultTextures"); internal static void LoadTextures() { // Default or Custom Texturse -> English Textures -> Localized Textures try { if (Utils.customTextures) { LoadCustomTextures(); } } catch (Exception e) { Log.LogError("Error loading texture setting."); Log.LogError($"{e.GetType()} {e.Message}"); } #if MULTI_LANGUAGE LoadLocalizedTextures(Utils.Language); LoadLocalizedSprites(Utils.Language); #else LoadLocalizedTextures(); #endif Log.LogInfo("Textures loaded successfully."); } internal static void LoadLocalizedTextures(Utils.LanguageEnum? language = null) { string textureDir = GetAssetDir(AssetType.Textures, language); if (!Directory.Exists(textureDir)) { Directory.CreateDirectory(textureDir); } try { Dictionary localTOC = new Dictionary(); string localTOCDir = Path.Combine(textureDir, "Texture_TOC.json"); if (!File.Exists(localTOCDir)) { File.WriteAllText(localTOCDir, "{}"); } string localTOCContent = File.ReadAllText(localTOCDir); localTOC = System.Text.Json.JsonSerializer.Deserialize>(localTOCContent); string basePath = Core.Instance.modsDirectory; if (!Utils.useLocal) { string textureTOCRequest = Utils.GetDataFromWeb($"https://raw.githubusercontent.com/Teyliu/PVZF-Translation/refs/heads/main/PvZ_Fusion_Translator/Localization/{Utils.Language.ToString()}/Textures/Texture_TOC.json").Result; if (textureTOCRequest != null) { Dictionary textureTOC = JsonConvert.DeserializeObject>(textureTOCRequest); List toDownload = new(); foreach(var pair in textureTOC) { if(!localTOC.ContainsKey(pair.Key)) { toDownload.Add(pair.Key); continue; } if(pair.Value != localTOC[pair.Key]) { toDownload.Add(pair.Key); } if(!File.Exists(Path.Combine(basePath, pair.Key))) { toDownload.Add(pair.Key); continue; } } string toWriteText = SerializeWithIndentation(textureTOC); File.WriteAllText(localTOCDir, toWriteText); foreach(string path in toDownload) { string downloadPath = Path.Combine(basePath, path); string baseUrl = $"https://raw.githubusercontent.com/Teyliu/PVZF-Translation/refs/heads/main/PvZ_Fusion_Translator/"; string url = baseUrl + path; byte[] imageData = Utils.GetByteDataFromWeb(url).Result; if(imageData != null) { string fileName = Path.GetFileName(downloadPath); Texture2D texture2D = Utils.LoadImage(imageData); TextureStore.textureDict[fileName] = imageData; if(!Directory.Exists(Path.GetDirectoryName(downloadPath))) { Directory.CreateDirectory(Path.GetDirectoryName(downloadPath)); } File.WriteAllBytes(downloadPath, imageData); } } } } foreach (string filepath in Directory.EnumerateFiles(textureDir, "*.png", SearchOption.AllDirectories)) { if (filepath.Contains("[Custom Textures]", StringComparison.OrdinalIgnoreCase) && loadDefaultTextures) { continue; } if (!localTOC.ContainsKey(filepath.Replace(basePath + "\\", "").Replace("\\", "/")) && (SerializeWithIndentation(localTOC) != "{}")) continue; #if OBFUSCATE if (CheckSumStore.IsModified(filepath)) { Log.LogError("File {0} was modified!" , filepath); continue; } #endif #if DEBUG Log.LogDebug("Loading File : " + filepath); #endif byte[] imageData = File.ReadAllBytes(filepath); Texture2D texture2D = Utils.LoadImage(imageData); TextureStore.textureDict[Path.GetFileNameWithoutExtension(filepath)] = imageData; } } catch (Exception e) { Log.LogError("Error loading Texture."); Log.LogError(e.GetType() + " " + e.Message); } } internal static void LoadLocalizedSprites(Utils.LanguageEnum? language = null) { string textureDir = GetAssetDir(AssetType.Sprites, language); if (!Directory.Exists(textureDir)) { Directory.CreateDirectory(textureDir); } try { string trueTextureDir = GetAssetDir(AssetType.Textures, language); Dictionary localTOC = new Dictionary(); string localTOCDir = Path.Combine(trueTextureDir, "Texture_TOC.json"); if (!File.Exists(localTOCDir)) { File.WriteAllText(localTOCDir, "{}"); } string localTOCContent = File.ReadAllText(localTOCDir); localTOC = System.Text.Json.JsonSerializer.Deserialize>(localTOCContent); string basePath = Core.Instance.modsDirectory; if(!Utils.useLocal) { string textureTOCRequest = Utils.GetDataFromWeb($"https://raw.githubusercontent.com/Teyliu/PVZF-Translation/refs/heads/main/PvZ_Fusion_Translator/Localization/{Utils.Language.ToString()}/Textures/Texture_TOC.json").Result; if (textureTOCRequest != null) { Dictionary textureTOC = JsonConvert.DeserializeObject>(textureTOCRequest); List toDownload = new(); foreach(var pair in textureTOC) { if(!localTOC.ContainsKey(pair.Key)) { toDownload.Add(pair.Key); continue; } if(pair.Value != localTOC[pair.Key]) { toDownload.Add(pair.Key); } if(!File.Exists(Path.Combine(basePath, pair.Key))) { toDownload.Add(pair.Key); continue; } } string toWriteText = SerializeWithIndentation(textureTOC); File.WriteAllText(localTOCDir, toWriteText); foreach(string path in toDownload) { string downloadPath = Path.Combine(basePath, path); string baseUrl = $"https://raw.githubusercontent.com/Teyliu/PVZF-Translation/refs/heads/main/PvZ_Fusion_Translator/"; string url = baseUrl + path; byte[] imageData = Utils.GetByteDataFromWeb(url).Result; if(imageData != null) { string fileName = Path.GetFileName(downloadPath); Texture2D texture2D = Utils.LoadImage(imageData); TextureStore.spriteDict[fileName] = imageData; if(!Directory.Exists(Path.GetDirectoryName(downloadPath))) { Directory.CreateDirectory(Path.GetDirectoryName(downloadPath)); } File.WriteAllBytes(downloadPath, imageData); } } } } foreach (string filepath in Directory.EnumerateFiles(textureDir, "*.png", SearchOption.AllDirectories)) { if (filepath.Contains("[Custom Textures]", StringComparison.OrdinalIgnoreCase) && loadDefaultTextures) { continue; } if (!localTOC.ContainsKey(filepath.Replace(basePath + "\\", "").Replace("\\", "/")) && (SerializeWithIndentation(localTOC) != "{}")) continue; #if OBFUSCATE if (CheckSumStore.IsModified(filepath)) { Log.LogError("File {0} was modified!" , filepath); continue; } #endif #if DEBUG Log.LogDebug("Loading File (Sprite): " + filepath); #endif byte[] imageData = File.ReadAllBytes(filepath); Texture2D texture2D = Utils.LoadImage(imageData); TextureStore.spriteDict[Path.GetFileNameWithoutExtension(filepath)] = imageData; } } catch (Exception e) { Log.LogError("Error loading Texture."); Log.LogError(e.GetType() + " " + e.Message); } } internal static void LoadDefaultTextures() { if(Utils.customTextures == false) { string textureDefaultDir = Path.Combine(Core.Instance.modsDirectory, AssetType.Dumps.ToString(), "Default Textures [Do Not Remove]"); if (!Directory.Exists(textureDefaultDir)) { Directory.CreateDirectory(textureDefaultDir); } try { string trueTextureDir = Path.Combine(Core.Instance.modsDirectory, "[Custom Textures]"); Dictionary localTOC = new Dictionary(); string localTOCDir = Path.Combine(trueTextureDir, "Default_TOC.json"); if (!File.Exists(localTOCDir)) { File.WriteAllText(localTOCDir, "{}"); } string localTOCContent = File.ReadAllText(localTOCDir); localTOC = System.Text.Json.JsonSerializer.Deserialize>(localTOCContent); string basePath = Core.Instance.modsDirectory; if (!Utils.useLocal) { string textureTOCRequest = Utils.GetDataFromWeb($"https://raw.githubusercontent.com/Teyliu/PVZF-Translation/refs/heads/main/PvZ_Fusion_Translator/{Uri.EscapeDataString("[Custom Textures]")}/Default_TOC.json").Result; if (textureTOCRequest != null) { Dictionary textureTOC = JsonConvert.DeserializeObject>(textureTOCRequest); List toDownload = new(); foreach (var pair in textureTOC) { if (!localTOC.ContainsKey(pair.Key)) { toDownload.Add(pair.Key); continue; } if (pair.Value != localTOC[pair.Key]) { toDownload.Add(pair.Key); } if(!File.Exists(Path.Combine(basePath, pair.Key))) { toDownload.Add(pair.Key); continue; } } string toWriteText = SerializeWithIndentation(textureTOC); File.WriteAllText(localTOCDir, toWriteText); foreach (string path in toDownload) { string downloadPath = Path.Combine(basePath, path); string baseUrl = $"https://raw.githubusercontent.com/Teyliu/PVZF-Translation/refs/heads/main/PvZ_Fusion_Translator/"; string url = baseUrl + path; byte[] imageData = Utils.GetByteDataFromWeb(url).Result; if (imageData != null) { string fileName = Path.GetFileName(downloadPath); Texture2D texture2D = Utils.LoadImage(imageData); TextureStore.textureDict[fileName] = imageData; if(!Directory.Exists(Path.GetDirectoryName(downloadPath))) { Directory.CreateDirectory(Path.GetDirectoryName(downloadPath)); } File.WriteAllBytes(downloadPath, imageData); } } } } foreach (string filepath in Directory.EnumerateFiles(textureDefaultDir, "*.png", SearchOption.AllDirectories)) { if (!localTOC.ContainsKey(filepath.Replace(basePath + "\\", "").Replace("\\", "/")) && (SerializeWithIndentation(localTOC) != "{}")) continue; #if OBFUSCATE if (CheckSumStore.IsModified(filepath)) { Log.LogError("File {0} was modified!" , filepath); continue; } #endif byte[] imageData = File.ReadAllBytes(filepath); Texture2D texture2D = Utils.LoadImage(imageData); TextureStore.textureDict[Path.GetFileNameWithoutExtension(filepath)] = imageData; } } catch (Exception e) { Log.LogError("Error loading Texture."); Log.LogError(e.GetType() + " " + e.Message); } } } internal static void LoadCustomTextures() { string texturePackDir = Path.Combine(Core.Instance.modsDirectory, "[Custom Textures]"); if (!Directory.Exists(texturePackDir)) { Directory.CreateDirectory(texturePackDir); } try { string trueTextureDir = Path.Combine(Core.Instance.modsDirectory, "[Custom Textures]"); Dictionary localTOC = new Dictionary(); string localTOCDir = Path.Combine(trueTextureDir, "Custom_TOC.json"); if (!File.Exists(localTOCDir)) { File.WriteAllText(localTOCDir, "{}"); } string localTOCContent = File.ReadAllText(localTOCDir); localTOC = System.Text.Json.JsonSerializer.Deserialize>(localTOCContent); string basePath = Core.Instance.modsDirectory; if (!Utils.useLocal) { string textureTOCRequest = Utils.GetDataFromWeb($"https://raw.githubusercontent.com/Teyliu/PVZF-Translation/refs/heads/main/PvZ_Fusion_Translator/{Uri.EscapeDataString("[Custom Textures]")}/Custom_TOC.json").Result; if (textureTOCRequest != null) { Dictionary textureTOC = JsonConvert.DeserializeObject>(textureTOCRequest); List toDownload = new(); foreach (var pair in textureTOC) { if (!localTOC.ContainsKey(pair.Key)) { toDownload.Add(pair.Key); continue; } if (pair.Value != localTOC[pair.Key]) { toDownload.Add(pair.Key); continue; } if(!File.Exists(Path.Combine(basePath, pair.Key))) { toDownload.Add(pair.Key); continue; } } string toWriteText = SerializeWithIndentation(textureTOC); File.WriteAllText(localTOCDir, toWriteText); foreach (string path in toDownload) { string downloadPath = Path.Combine(basePath, path); string baseUrl = $"https://raw.githubusercontent.com/Teyliu/PVZF-Translation/refs/heads/main/PvZ_Fusion_Translator/"; string url = baseUrl + path; byte[] imageData = Utils.GetByteDataFromWeb(url).Result; if (imageData != null) { string fileName = Path.GetFileName(downloadPath); Texture2D texture2D = Utils.LoadImage(imageData); TextureStore.textureDict[fileName] = imageData; if(!Directory.Exists(Path.GetDirectoryName(downloadPath))) { Directory.CreateDirectory(Path.GetDirectoryName(downloadPath)); } File.WriteAllBytes(downloadPath, imageData); } } } } foreach (string filepath in Directory.EnumerateFiles(texturePackDir, "*.png", SearchOption.AllDirectories)) { #if OBFUSCATE if (CheckSumStore.IsModified(filepath)) { Log.LogError("File {0} was modified!" , filepath); continue; } #endif if (!localTOC.ContainsKey(filepath.Replace(basePath + "\\", "").Replace("\\", "/")) && (SerializeWithIndentation(localTOC) != "{}")) continue; byte[] imageData = File.ReadAllBytes(filepath); Texture2D texture2D = Utils.LoadImage(imageData); TextureStore.textureDict[Path.GetFileNameWithoutExtension(filepath)] = imageData; } } catch (Exception e) { Log.LogError("Error loading Texture."); Log.LogError(e.GetType() + " " + e.Message); } } internal static void SaveStrings() { #if MULTI_LANGUAGE string stringDir = GetAssetDir(AssetType.Strings, Utils.Language); #else string stringDir = GetAssetDir(AssetType.Strings); #endif if (!Directory.Exists(stringDir)) { Directory.CreateDirectory(stringDir); } // save regexs string translationStringRegex = SerializeWithIndentation(StringStore.translationStringRegex, 2); File.WriteAllText(Path.Combine(stringDir, "translation_regexs.json"), translationStringRegex); // save strings string translationString = SerializeWithIndentation(StringStore.translationString, 2); File.WriteAllText(Path.Combine(stringDir, "translation_strings.json"), translationString); // save iz tips string izTipCollectionString = SerializeWithIndentation(StringStore.izLevelTipDictionary, 1, ' '); File.WriteAllText(Path.Combine(stringDir, "tips_iz.json"), izTipCollectionString); // save fs tips string fsLevelTipDictionary = SerializeWithIndentation(StringStore.fsLevelTipDictionary); File.WriteAllText(Path.Combine(stringDir, "tips_fs.json"), fsLevelTipDictionary); // save changelog if(NoticePauseMenu_Patch.changelogText != "") { File.WriteAllText(Path.Combine(stringDir, "changelog.txt"), NoticePauseMenu_Patch.changelogText); } // save abyss buffs if(Patches.Modes.Abyss.AbyssBuffMenu_Patch.abyssBuffData != new Dictionary()) { string abyssBuffData = SerializeWithIndentation(Patches.Modes.Abyss.AbyssBuffMenu_Patch.abyssBuffData, 2); File.WriteAllText(Path.Combine(stringDir, "abyss_buffs.json"), abyssBuffData); } // save almanacs string almanacDir = GetAssetDir(AssetType.Almanac, Utils.Language); if(AlmanacPlantMenu_Patch.almanacJson != "") { string plantAlmanac = SerializeWithIndentation(AlmanacPlantMenu_Patch.almanacJson); File.WriteAllText(Path.Combine(almanacDir, "LawnStringsTranslate.json"), AlmanacPlantMenu_Patch.almanacJson); } if(AlmanacZombieMenu_Patch.almanacJson != "") { string zombieAlmanac = SerializeWithIndentation(AlmanacZombieMenu_Patch.almanacJson); File.WriteAllText(Path.Combine(almanacDir, "ZombieStringsTranslate.json"), AlmanacZombieMenu_Patch.almanacJson); } if(AlmanacSelectMenu_Patch.detailStrings != new Dictionary()) { string detailStrings = SerializeWithIndentation(AlmanacSelectMenu_Patch.detailStrings); File.WriteAllText(Path.Combine(almanacDir, "DetailStringsTranslate.json"), detailStrings); } } public static string SerializeWithIndentation(T value, int size = 4, char indentChar = ' ') { var stringBuilder = new StringBuilder(); var stringWriter = new StringWriter(); using (JsonTextWriter writer = new JsonTextWriter(stringWriter)) { writer.Formatting = Formatting.Indented; writer.Indentation = size; writer.IndentChar = indentChar; Newtonsoft.Json.JsonSerializer serializer = new Newtonsoft.Json.JsonSerializer(); serializer.Serialize(writer, value, typeof(T)); } return stringWriter.ToString(); } public static void DumpJson() { string dumpDir = GetAssetDir(AssetType.Dumps); if (!Directory.Exists(dumpDir)) { Directory.CreateDirectory(dumpDir); } string LawnStrings = Resources.Load("LawnStrings").text; string ZombieStrings = Resources.Load("ZombieStrings").text; string AbyssBuffData = Resources.Load("AbyssBuffData").text; File.WriteAllText(Path.Combine(dumpDir, "LawnStrings.json"), LawnStrings); File.WriteAllText(Path.Combine(dumpDir, "ZombieStrings.json"), ZombieStrings); File.WriteAllText(Path.Combine(dumpDir, "AbyssBuffData.json"), AbyssBuffData); // dump iz tips var izDump = DumpIZStrings(); var izLevelData = izDump.Item1; var izLevelDataDump = izDump.Item2; File.WriteAllText(Path.Combine(dumpDir, "tips_iz.json"), System.Text.Json.JsonSerializer.Serialize(izLevelDataDump, new JsonSerializerOptions { WriteIndented = true, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping })); // dump fusion showcase tips var fusionShowcaseDump = DumpFSStrings(); var fusionShowcaseData = fusionShowcaseDump.Item1; var fusionShowcaseDataDump = fusionShowcaseDump.Item2; File.WriteAllText(Path.Combine(dumpDir, "tips_fs.json"), System.Text.Json.JsonSerializer.Serialize(fusionShowcaseDataDump, new JsonSerializerOptions { WriteIndented = true, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping })); // dump almanac mechanics var detailStringsDump = DumpDetailStrings(); var detailStringsData = detailStringsDump.Item1; var detailStrings = detailStringsDump.Item2; File.WriteAllText(Path.Combine(dumpDir, "DetailStrings.json"), System.Text.Json.JsonSerializer.Serialize(detailStrings, new JsonSerializerOptions { WriteIndented = true, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping })); /*Dictionary achievementsList = new Dictionary(); foreach (Il2CppSystem.Collections.Generic.KeyValuePair> entry in AchievementClip.achievementsText) { achievementsList.Add(entry.Key, new AchievementObject(entry.Key, entry.Value.Item1, entry.Value.Item2)); } File.WriteAllText(Path.Combine(dumpDir, "AchievementsText.json"), System.Text.Json.JsonSerializer.Serialize(achievementsList, new JsonSerializerOptions { WriteIndented = true, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }));*/ } public static ValueTuple, Dictionary> DumpIZStrings() { var izLevelData = Resources.LoadAll("izleveldata"); Dictionary izLevelDataDump = new Dictionary(); foreach (var level in izLevelData) { string data = level.text; LevelData levelData = JsonUtility.FromJson(data); if(levelData.tips != null) { izLevelDataDump.Add(level.name, levelData.tips); } } return new ValueTuple, Dictionary>(izLevelData, izLevelDataDump); } public static ValueTuple, Dictionary> DumpFSStrings() { var fusionShowcaseData = Resources.LoadAll("leveldata/explore"); Dictionary fusionShowcaseDataDump = new Dictionary(); foreach (var level in fusionShowcaseData) { string data = level.text; LevelData levelData = JsonUtility.FromJson(data); if (levelData.tips != null) { fusionShowcaseDataDump.Add(level.name, levelData.tips); } } return new ValueTuple, Dictionary>(fusionShowcaseData, fusionShowcaseDataDump); } public static ValueTuple> DumpDetailStrings() { string detailStringsData = Resources.Load("detailstrings").text; Il2CppAlmanacData.AlmanacData detailStrings = JsonUtility.FromJson(detailStringsData); Dictionary detailStringsDump = new Dictionary(); foreach (var detailString in detailStrings.details) { if(detailStringsDump.ContainsKey(detailString.title)) { detailStringsDump[detailString.title] = detailString.text; } else { detailStringsDump.Add(detailString.title, detailString.text); } } return new ValueTuple>(detailStrings, detailStringsDump); } #if DEBUG public static void DumpUntranslatedStrings(string text) { string dumpDir = GetAssetDir(AssetType.Dumps); string jsonFile = Path.Combine(dumpDir, "UntranslatedStrings.json"); if (!Directory.Exists(dumpDir)) { Directory.CreateDirectory(dumpDir); } if (!File.Exists(jsonFile)) { File.WriteAllText(jsonFile, "{}"); // Initialize empty JSON object } string json = File.ReadAllText(jsonFile); var untranslatedStrings = System.Text.Json.JsonSerializer.Deserialize>(json); if (!untranslatedStrings.ContainsKey(text)) { untranslatedStrings[text] = text; var options = new JsonSerializerOptions { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, WriteIndented = true }; File.WriteAllText(jsonFile, System.Text.Json.JsonSerializer.Serialize(untranslatedStrings, options)); } } #endif #if MULTI_LANGUAGE internal static void LoadLanguage() { try { // Load the language preference as a string and parse it into the enum string languageName = MelonPreferences.GetEntryValue("PvZ_Fusion_Translator", "Language"); if (Enum.TryParse(languageName, out Utils.LanguageEnum loadedLanguage)) { Utils.Language = loadedLanguage; } else { Log.LogWarning($"Invalid language '{languageName}' found in preferences. Falling back to English."); Utils.Language = Utils.LanguageEnum.English; // Default fallback } Log.LogWarning($"Loaded language {languageName}"); } catch (Exception e) { Log.LogError("Error loading language setting."); Log.LogError($"{e.GetType()} {e.Message}"); } Log.LogInfo($"Language has been loaded: {Utils.Language}"); } internal static void SaveLanguage() { try { // Save the current language as a string MelonPreferences.SetEntryValue("PvZ_Fusion_Translator", "Language", Utils.Language.ToString()); // Ensure changes are written to the config file // MelonPreferences.Save(); } catch (Exception e) { Log.LogError("Error saving language setting."); Log.LogError($"{e.GetType()} {e.Message}"); } // Log.LogInfo($"Language has been saved: {Utils.Language}"); } #endif } }