PVZFusionTranslation/Source/Utils.cs

708 lines
22 KiB
C#

using PvZ_Fusion_Translator.AssetStore;
using PvZ_Fusion_Translator.Patches.GameObjects;
using PvZ_Fusion_Translator.Patches.GameObjects.ButtonObjects;
using PvZ_Fusion_Translator.Patches.GameObjects.MinorObjects;
using System.Diagnostics;
using System.Reflection;
using System.Text.RegularExpressions;
using UnityEngine.Experimental.Rendering;
using UnityEngine.UI;
namespace PvZ_Fusion_Translator;
public static class Utils
{
internal static bool TryReplaceTexture2D(Texture2D ogTexture)
{
if (ogTexture == null)
return false;
if (!TextureStore.TextureDict.TryGetValue(ogTexture.name, out var textureData))
return false;
try
{
ImageConversion.LoadImage(ogTexture, textureData);
Log.LogDebug("OK! Replaced Texture " + ogTexture.name);
ogTexture.name = "replaced_" + ogTexture.name;
return true;
}
catch (Exception ex)
{
Log.LogError("Failed to replace texture: " + ogTexture.name);
Log.LogError(ex.ToString());
}
return false;
}
internal static Texture2D LoadImage(string path)
{
if (!File.Exists(path))
throw new FileNotFoundException($"The image file at path '{path}' does not exist.");
var array = File.ReadAllBytes(path);
Texture2D texture2D = new(
2,
2,
GraphicsFormat.R8G8B8A8_UNorm,
TextureCreationFlags.None,
1,
IntPtr.Zero,
null
);
ImageConversion.LoadImage(texture2D, array);
return texture2D;
}
internal static Texture2D LoadImage(byte[] bytes)
{
if (bytes == null)
throw new FileNotFoundException("Byte array was null.");
Texture2D texture2D = new(
2,
2,
GraphicsFormat.R8G8B8A8_UNorm,
TextureCreationFlags.None,
1,
IntPtr.Zero,
null
);
ImageConversion.LoadImage(texture2D, bytes);
return texture2D;
}
public static Color HexToColor(string hex)
{
var r = int.Parse(hex.TrimStart("#")[..2], System.Globalization.NumberStyles.HexNumber) / 255f;
var g = int.Parse(hex.TrimStart("#")[2..4], System.Globalization.NumberStyles.HexNumber) / 255f;
var b = int.Parse(hex.TrimStart("#")[4..6], System.Globalization.NumberStyles.HexNumber) / 255f;
return new(r, g, b);
}
public static void OpenSaveDirectory()
{
var saveDirectory = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
"AppData",
"LocalLow",
"LanPiaoPiao",
"PlantsVsZombiesRH"
);
Process.Start("explorer.exe", saveDirectory);
}
public static void OpenOnlineAlmanac()
{
const string Website = "https://carroti4ch.itch.io/almanac";
Process.Start(new ProcessStartInfo(Website) { UseShellExecute = true });
}
public static string RemoveColorTags(string text)
{
if (string.IsNullOrEmpty(text))
return text ?? string.Empty;
// Remove opening color tags like <color=#FF0000>
var withoutOpenTags = Regex.Replace(text, "<color=[^>]+>", string.Empty, RegexOptions.IgnoreCase);
// Remove closing color tags like </color>
var withoutCloseTags = Regex.Replace(withoutOpenTags, "</color>", string.Empty, RegexOptions.IgnoreCase);
return withoutCloseTags;
}
public static string RemoveSizeTags(string text)
{
if (string.IsNullOrEmpty(text))
return text ?? string.Empty;
// Remove opening size tags like <color=#FF0000>
var withoutOpenTags = Regex.Replace(text, "<size=[^>]+>", string.Empty, RegexOptions.IgnoreCase);
// Remove closing size tags like </color>
var withoutCloseTags = Regex.Replace(withoutOpenTags, "</size>", string.Empty, RegexOptions.IgnoreCase);
return withoutCloseTags;
}
public static string RemoveFormatTags(string text)
{
if (string.IsNullOrEmpty(text))
return text ?? string.Empty;
var withoutUnderlineTags = Regex.Replace(text, "<u>", string.Empty, RegexOptions.IgnoreCase);
var withoutUnderlineCloseTags = Regex.Replace(
withoutUnderlineTags,
"</u>",
string.Empty,
RegexOptions.IgnoreCase
);
var withoutItalicTags = Regex.Replace(
withoutUnderlineCloseTags,
"<i>",
string.Empty,
RegexOptions.IgnoreCase
);
var withoutItalicCloseTags = Regex.Replace(
withoutItalicTags,
"</i>",
string.Empty,
RegexOptions.IgnoreCase
);
return withoutItalicCloseTags;
}
public static string RemoveAllTags(string text)
{
if (string.IsNullOrEmpty(text))
return text ?? string.Empty;
return RemoveColorTags(RemoveSizeTags(RemoveFormatTags(text)));
}
public static GameObject ConvertToTextMeshProUGUI(GameObject originalText, Transform parent, string name)
{
GameObject newObj = new(name)
{
transform =
{
position = originalText.transform.position,
},
};
newObj.AddComponent<CanvasRenderer>();
newObj.AddComponent<RectTransform>();
newObj.AddComponent<TextMeshProUGUI>();
newObj.transform.SetParent(parent);
newObj.transform.localScale = Vector3.one;
UnityEngine.Object.Destroy(originalText);
return newObj;
}
public static void ConvertButtonText(Transform original, string name)
{
var fontAsset = FontStore.LoadTMPFont(Language.ToString());
var transform = original.FindChild(name);
var textTransform = transform.FindChild("text");
var text = textTransform.GetComponent<Text>().text;
var color = textTransform.GetComponent<Text>().color;
var newGoBackText = ConvertToTextMeshProUGUI(textTransform.gameObject, transform, "text2")
.GetComponent<TextMeshProUGUI>();
newGoBackText.autoSizeTextContainer = true;
newGoBackText.text = StringStore.TranslateText(text, false);
newGoBackText.font = fontAsset;
newGoBackText.color = color;
}
public static string GetPlantNameFromAlmanac(PlantType thePlantType, bool removeTags = false, bool log = false)
{
var foundPlantName = false;
var thePlantName = "";
if (PlantIndices.ContainsKey((int)thePlantType))
{
foundPlantName = true;
thePlantName = PlantIndices[(int)thePlantType].Value;
}
if (!foundPlantName)
{
if (log)
Log.LogInfo("Couldn't find plant name!");
thePlantName = "";
}
thePlantName = removeTags ? RemoveAllTags(thePlantName) : thePlantName;
return thePlantName;
}
public static string GetPlantNameFromAlmanac(
string theOriginalPlantName,
bool removeTags = false,
bool log = false
)
{
var foundPlantName = false;
var thePlantName = "";
if (PlantIndiceString.TryGetValue(theOriginalPlantName, out var value))
{
foundPlantName = true;
thePlantName = value;
}
if (!foundPlantName)
{
if (log)
Log.LogInfo("Couldn't find plant name!");
thePlantName = "";
}
thePlantName = removeTags ? RemoveAllTags(thePlantName) : thePlantName;
return thePlantName;
}
public static string GetZombieNameFromAlmanac(ZombieType theZombieType, bool removeTags = false)
{
var theZombieName = "";
var currentLanguage = Language.ToString();
var json = AlmanacZombieMenu_Patch.AlmanacJson;
var zombieData = JsonUtility.FromJson<Il2CppAlmanacDataClass>(json);
foreach (var zombieInfo in zombieData.zombies)
if ((int)zombieInfo.theZombieType == (int)theZombieType)
theZombieName = zombieInfo.name;
theZombieName = removeTags ? RemoveAllTags(theZombieName) : theZombieName;
return theZombieName;
}
public static Dictionary<int, KeyValuePair<int, string>> PlantIndices = new();
public static Dictionary<string, string> PlantIndiceString = new();
public static void RegisterPlantIndices()
{
PlantIndices = new();
PlantIndiceString = new();
string originalJson;
string translatedJson;
var currentLanguage = Language.ToString();
var almanacDir = FileLoader.GetAssetDir(FileLoader.AssetType.Almanac, Language);
var dumpDir = FileLoader.GetAssetDir(FileLoader.AssetType.Dumps);
var originalPath = Path.Combine(dumpDir, "LawnStrings.json");
var path = Path.Combine(almanacDir, "LawnStringsTranslate.json");
if (!File.Exists(originalPath))
Log.LogError($"LawnStrings.json file not found at path: {originalPath}");
else
try
{
originalJson = File.ReadAllText(originalPath);
translatedJson = AlmanacPlantMenu_Patch.AlmanacJson;
var originalPlantData = JsonUtility.FromJson<Il2CppAlmanacDataClass>(originalJson);
var translatedPlantData = JsonUtility.FromJson<Il2CppAlmanacDataClass>(translatedJson);
foreach (var originalPlantInfo in originalPlantData.plants)
{
if (!PlantIndiceString.ContainsKey(originalPlantInfo.name))
PlantIndiceString.Add(originalPlantInfo.name, originalPlantInfo.name);
Il2CppPlantInfo translatedPlantInfo = null;
foreach (var info in translatedPlantData.plants)
if (info.seedType == originalPlantInfo.seedType)
translatedPlantInfo = info;
if (translatedPlantInfo != null)
if (translatedPlantInfo.name != null)
{
var temp = new KeyValuePair<int, string>(
translatedPlantInfo.seedType,
translatedPlantInfo.name
);
PlantIndices.Add(originalPlantInfo.seedType, temp);
PlantIndiceString[originalPlantInfo.name] = translatedPlantInfo.name;
}
}
}
catch (Exception e)
{
Log.LogError(e);
}
}
public static void TryAdd<TA, TB>(Dictionary<TA, TB> dict, TA key, TB val)
{
if (dict.ContainsKey(key))
dict[key] = val;
else
dict.Add(key, val);
}
public static void TryAdd<TA, TB>(SortedDictionary<TA, TB> dict, TA key, TB val)
{
if (dict.ContainsKey(key))
dict[key] = val;
else
dict.Add(key, val);
}
public static AssetBundle GetAssetBundle(Assembly assembly, string assetBundlePath, bool log = false)
{
try
{
if (assembly != null)
{
var manifestResourceStream =
assembly.GetManifestResourceStream($"PvZ_Fusion_Translator.Resources.{assetBundlePath}");
using (manifestResourceStream)
{
if (manifestResourceStream == null)
throw new("Stream was null.");
var array = new byte[manifestResourceStream.Length];
_ = manifestResourceStream.Read(array, 0, array.Length);
var assetBundle = AssetBundle.LoadFromMemory(array);
ArgumentNullException.ThrowIfNull(assetBundle, "assetBundle");
if (log)
Log.LogDebug($"AssetBundle {assetBundlePath} has successfully been loaded.");
return assetBundle;
}
}
throw new($"AssetBundle {assetBundlePath} was null.");
}
catch (Exception value)
{
throw new ArgumentException($"Failed to load {assetBundlePath}. \n{value}");
}
}
public static AssetBundle GetAssetBundle(string path)
{
var assetBundle = AssetBundle.LoadFromFile(path);
return assetBundle;
}
/*public static Dictionary<int, List<int>> recipeLinks = new Dictionary<int, List<int>>();
public static void RegisterRecipeLinks()
{
recipeLinks.Clear();
Il2CppSystem.Array mixData = MixData.data.Cast<Il2CppSystem.Array>();
int rows = mixData.GetLength(0);
int columns = mixData.GetLength(1);
foreach (PlantType plantType in Enum.GetValues(typeof(PlantType)))
{
int seedType = (int)plantType;
recipeLinks.Add(seedType, new());
if (seedType > 0)
{
foreach (PlantType checkType in Enum.GetValues(typeof(PlantType)))
{
if (checkType > 0)
{
int[] checks = [mixData.GetValue(seedType, (int)checkType).Unbox<int>(), mixData.GetValue((int)checkType, seedType).Unbox<int>()];
if (checks[0] != 0)
{
recipeLinks[seedType].Add(checks[0]);
}
if (checks[1] != 0)
{
recipeLinks[seedType].Add(checks[1]);
}
}
}
}
if (recipeLinks[seedType].Count > 0)
{
Log.LogDebug($"Found {recipeLinks[seedType].Count} fusions for {seedType}!");
}
}
}*/
public static readonly Regex UntranslatedRegex = new("\\p{IsCJKUnifiedIdeographs}+");
public static bool CheckForUntranslatedText(string text)
{
try
{
var match = UntranslatedRegex.Match(text);
return match.Success;
}
catch (Exception e)
{
Log.LogError(e);
}
return false;
}
public static void WarnLocalData()
{
var newMenu = GameAPP.UIManager.Push(UIType.ConfirmMenu, GameAPP.canvasUp);
var confirmMenu = newMenu.gameObject.GetComponent<ConfirmMenu>();
var modeTo = Core.Instance.ConfigUseLocal.Value ? "online mode" : "local mode";
confirmMenu.SetTitle(
$"<size=40%>WARNING: This button is meant for translator testing and can overwrite/interfere with the translation files saved in your installation. Proceed with caution. <color=red>You are switching to {modeTo}.</color>\nOnline Mode: Default, strings will auto-sync with the latest version when opening the game\nLocal Mode: Strings are only loaded from your installaion and do not update (meant for translators)"
);
confirmMenu.transform.Find("window/text").GetComponent<TextMeshProUGUI>().text = RemoveColorTags(
confirmMenu.transform.Find("window/text").GetComponent<TextMeshProUGUI>().text
);
var confirmButton = confirmMenu.transform.Find("Image").GetComponent<UIButton>();
_ = confirmMenu.transform.Find("Image2").GetComponent<UIButton>();
confirmButton.clickEvent = new();
confirmButton.clickEvent.AddListener(new Action(SwapLocalData));
confirmButton.clickEvent.AddListener(new Action(CursorChange.SetDefaultCursor));
confirmButton.clickEvent.AddListener(new Action(() => GameAPP.UIManager.Pop()));
}
public static void SwapLocalData()
{
Core.Instance.ConfigUseLocal.Value = !Core.Instance.ConfigUseLocal.Value;
ChangeLanguage(Language.ToString());
var sourceMsg = $"<size=10>Translation Source:\n{(Core.Instance.ConfigUseLocal.Value ? "Local" : "Online")}";
OptLanguageBtn_Patch.FlashMessage(OptLanguageBtn_Patch.ToggleSlots[2], sourceMsg, 0.1f, false);
CursorChange.SetDefaultCursor();
}
public static async Task<string> GetDataFromWeb(string url, bool isLog = true)
{
if (isLog)
Log.LogInfo($"Attempting to read {url}");
var client = new HttpClient();
try
{
var dataRequest = await client.GetAsync(url);
if (dataRequest.StatusCode == System.Net.HttpStatusCode.OK)
{
if (isLog)
Log.LogInfo($"Successfully loaded data from {url}!");
var content = await dataRequest.Content.ReadAsStringAsync();
return content;
}
if (isLog)
{
Log.LogError($"Failed to load data from {url}! Falling back...");
Log.LogError($"Status code: {dataRequest.StatusCode}");
}
new CancellationTokenSource().Cancel();
return null;
}
catch (Exception ex)
{
Log.LogError($"Failed to load data from {url}! Falling back...");
Log.LogError(ex);
new CancellationTokenSource().Cancel();
return null;
}
}
public static async Task<byte[]> GetByteDataFromWeb(string url, bool isLog = true)
{
if (isLog)
Log.LogInfo($"Attempting to read {url}");
var client = new HttpClient();
try
{
var dataRequest = await client.GetAsync(url);
if (dataRequest.StatusCode == System.Net.HttpStatusCode.OK)
{
if (isLog)
Log.LogInfo($"Successfully loaded data from {url}!");
var content = await dataRequest.Content.ReadAsByteArrayAsync();
return content;
}
if (isLog)
{
Log.LogError($"Failed to load data from {url}! Falling back...");
Log.LogError($"Status code: {dataRequest.StatusCode}");
}
new CancellationTokenSource().Cancel();
return null;
}
catch (Exception ex)
{
Log.LogError($"Failed to load data from {url}! Falling back...");
Log.LogError(ex);
new CancellationTokenSource().Cancel();
return null;
}
}
public static async Task<string> GetDataFromWebWithInput(string url, bool isLog = true)
{
if (isLog)
Log.LogInfo($"Attempting to read {url}");
var client = new HttpClient();
using var request = new HttpRequestMessage
{
Method = HttpMethod.Get,
RequestUri = new(url),
};
request.Headers.Add("Accept", "*/*");
request.Headers.Add("Accept-Encoding", "gzip, deflate, br");
request.Headers.Add("User-Agent", "TranslatorMod");
var dataRequest = await client.SendAsync(request);
if (dataRequest.StatusCode == System.Net.HttpStatusCode.OK)
{
if (isLog)
Log.LogInfo($"Successfully loaded data from {url}!");
var content = await dataRequest.Content.ReadAsStringAsync();
return content;
}
if (isLog)
{
Log.LogError($"Failed to load data from {url}! Falling back...");
Log.LogError($"Status code: {dataRequest.StatusCode}");
}
new CancellationTokenSource().Cancel();
return null;
}
public static int CalculateGameVersion(string gameVersion)
{
var res = 0;
var gameVersionParts = gameVersion.Split('.');
var startingPower = (int)Math.Max(gameVersionParts.Length, 3);
for (var i = 0; i < startingPower; i++)
{
var factor = 0;
if (i < gameVersionParts.Length)
factor = int.Parse(gameVersionParts[i]);
var power = startingPower - i;
res += factor * (int)Math.Pow(10, power - 1);
}
return res;
}
internal static void ChangeLanguage(string language)
{
UnityEngine.Debug.Assert(ModFeatures.IsMultiLanguage, nameof(ModFeatures.IsMultiLanguage));
OldLanguage = Language;
if (Enum.TryParse(language, out LanguageEnum lang))
Language = lang;
else
// Handle invalid language string
Log.LogError($"Invalid language string: {language}");
//WarningStore.isWarningMessageLoaded = false;
FontStore.Reload();
StringStore.Reload();
TextureStore.Reload();
FileLoader.SaveLanguage();
RegisterPlantIndices();
Zombie_Patch.LoadHpStrings();
}
public static LanguageEnum OldLanguage;
public static LanguageEnum Language;
public enum LanguageEnum
{
// first column
English,
ChineseCn,
French,
//Italian,
German,
Spanish,
// second column
//Portuguese,
//Javanese,
//Filipino,
Vietnamese,
Indonesian,
//Russian, //NOTE: legacy language
Japanese,
// third column
Korean,
Ukrainian,
// Slovak,
//Polish,
//Turkish,
Arabic,
Romanian,
LangEnd,
}
public static Dictionary<LanguageEnum, string> LanguageNames = new()
{
{ LanguageEnum.English, "English" },
{ LanguageEnum.ChineseCn, "简体中文" },
{ LanguageEnum.French, "Français" },
{ LanguageEnum.German, "Deutsch" },
{ LanguageEnum.Spanish, "Español" },
{ LanguageEnum.Vietnamese, "Tiếng Việt" },
{ LanguageEnum.Indonesian, "Bahasa Indonesia" },
{ LanguageEnum.Japanese, "日本語" },
{ LanguageEnum.Korean, "한국어" },
{ LanguageEnum.Ukrainian, "українська" },
{ LanguageEnum.Arabic, "ﺔﻴﺑﺮﻌﻟا" },
{ LanguageEnum.Romanian, "Română" },
};
public static Dictionary<ToggleEnum, string> ToggleNames = new()
{
{ ToggleEnum.Textures, "<size=85%>Change Texture\nSource" },
{ ToggleEnum.Audio, "<size=85%>Change Audio\nSource" },
{ ToggleEnum.SwapLocal, "<size=85%>Change Translation\nSource" },
};
public enum ToggleEnum
{
Textures,
Audio,
SwapLocal,
ToggleEnd,
}
}