diff --git a/CompressSave/CompressSave.cs b/CompressSave/CompressSave.cs index 6a778e1..bf622ed 100644 --- a/CompressSave/CompressSave.cs +++ b/CompressSave/CompressSave.cs @@ -20,7 +20,7 @@ public enum CompressionType public class CompressSave : BaseUnityPlugin { private Harmony _patchSave, _patchUISave, _patchUILoad; - private static string StringFromCompresstionType(CompressionType type) + public static string StringFromCompresstionType(CompressionType type) { return type switch { @@ -46,25 +46,22 @@ public class CompressSave : BaseUnityPlugin SaveUtil.Logger = Logger; if (LZ4API.Avaliable && ZstdAPI.Avaliable) { - PatchSave.CompressionTypeForSaves = CompressionTypeFromString( - Config.Bind("Compression", "Type", StringFromCompresstionType(PatchSave.CompressionTypeForSaves), + PatchSave.CompressionTypeForSavesConfig = Config.Bind("Compression", "Type", StringFromCompresstionType(PatchSave.CompressionTypeForSaves), new ConfigDescription("Set default compression type for manual saves.", - new AcceptableValueList("lz4", "zstd", "none"), new { })) - .Value); - PatchSave.CompressionTypeForAutoSaves = CompressionTypeFromString( - Config.Bind("Compression", "TypeForAuto", StringFromCompresstionType(PatchSave.CompressionTypeForSaves), + new AcceptableValueList("lz4", "zstd", "none"), new { })); + PatchSave.CompressionTypeForSaves = CompressionTypeFromString(PatchSave.CompressionTypeForSavesConfig.Value); + PatchSave.CompressionTypeForAutoSavesConfig = Config.Bind("Compression", "TypeForAuto", StringFromCompresstionType(PatchSave.CompressionTypeForAutoSaves), new ConfigDescription("Set default compression type for auto saves and last-exit save.", - new AcceptableValueList("lz4", "zstd", "none"), new { })) - .Value); + new AcceptableValueList("lz4", "zstd", "none"), new { })); + PatchSave.CompressionTypeForAutoSaves = CompressionTypeFromString(PatchSave.CompressionTypeForAutoSavesConfig.Value); PatchSave.CompressionLevelForSaves = Config.Bind("Compression", "Level", PatchSave.CompressionLevelForSaves, "Set default compression level for manual saves.\n0 for default level.\n3 ~ 12 for lz4, -5 ~ 22 for zstd.\nSmaller level leads to faster speed and less compression ratio.") .Value; PatchSave.CompressionLevelForAutoSaves = Config.Bind("Compression", "LevelForAuto", PatchSave.CompressionLevelForAutoSaves, "Set default compression level for auto saves and last-exit save.\n0 for default level.\n3 ~ 12 for lz4, -5 ~ 22 for zstd.\nSmaller level leads to faster speed and less compression ratio.") .Value; - PatchSave.EnableForAutoSaves = Config.Bind("Compression", "EnableForAutoSaves", PatchSave.EnableForAutoSaves, - "Enable the feature for auto saves and last-exit save.") - .Value; + PatchSave.EnableForAutoSaves = Config.Bind("Compression", "EnableForAutoSaves", true, + "Enable the feature for auto saves and last-exit save."); PatchSave.CreateCompressBuffer(); if (GameConfig.gameVersion != SaveUtil.VerifiedVersion) { @@ -78,7 +75,14 @@ public class CompressSave : BaseUnityPlugin _patchUILoad = Harmony.CreateAndPatchAll(typeof(PatchUILoadGame)); } else - SaveUtil.Logger.LogWarning("Either lz4warp.dll or zstdwrap.dll is not avaliable."); + SaveUtil.Logger.LogWarning("Either nonewrap.dll, lz4warp.dll or zstdwrap.dll is not avaliable."); + I18N.Init(); + I18N.Add("Store", "Store (No Compression)", "存储(不压缩)"); + I18N.Add("Decompress", "Decompress", "解压存档"); + I18N.Add("Save with Compression", "Save (Compress)", "压缩保存"); + I18N.Add("Compression for auto saves", "Compression for auto saves", "  自动存档压缩方式"); + I18N.Add("Compression for manual saves", "Compression for manual saves", "  手动存档压缩方式"); + I18N.Apply(); } public void OnDestroy() @@ -108,9 +112,11 @@ public class PatchSave private static int _compressionLevelForSaving; public static CompressionType CompressionTypeForSaves = CompressionType.Zstd; public static CompressionType CompressionTypeForAutoSaves = CompressionType.Zstd; + public static ConfigEntry CompressionTypeForSavesConfig; + public static ConfigEntry CompressionTypeForAutoSavesConfig; public static int CompressionLevelForSaves; public static int CompressionLevelForAutoSaves; - public static bool EnableForAutoSaves = true; + public static ConfigEntry EnableForAutoSaves; private static Stream _compressionStream; public static bool EnableCompress; @@ -151,7 +157,7 @@ public class PatchSave [HarmonyPatch(typeof(GameSave), "SaveAsLastExit")] private static void BeforeAutoSave() { - UseCompressSave = EnableForAutoSaves && EnableCompress; + UseCompressSave = EnableForAutoSaves.Value && EnableCompress; if (!UseCompressSave) return; _compressionTypeForSaving = CompressionTypeForAutoSaves; _compressionLevelForSaving = CompressionLevelForAutoSaves; diff --git a/CompressSave/CompressSave.csproj b/CompressSave/CompressSave.csproj index 44338be..8e5c97c 100644 --- a/CompressSave/CompressSave.csproj +++ b/CompressSave/CompressSave.csproj @@ -5,7 +5,7 @@ CompressSave org.soardev.compresssave DSP MOD - CompressSave - 1.3.1 + 1.3.2 true latest net472 diff --git a/CompressSave/I18N.cs b/CompressSave/I18N.cs new file mode 100644 index 0000000..0a1816b --- /dev/null +++ b/CompressSave/I18N.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using HarmonyLib; + +namespace CompressSave; + +public static class I18N +{ + private static bool _initialized; + + public static Action OnInitialized; + + public static void Init() + { + Harmony.CreateAndPatchAll(typeof(I18N)); + } + + public static bool Initialized() => _initialized; + private static int _nextID = 1; + private static readonly List StringsToAdd = new(); + public static void Add(string key, string enus, string zhcn = null, string frfr = null) + { + var strings = LDB._strings; + var strProto = new StringProto + { + Name = key, + SID = "", + ENUS = enus, + ZHCN = string.IsNullOrEmpty(zhcn) ? enus : zhcn, + FRFR = string.IsNullOrEmpty(frfr) ? enus : frfr + }; + StringsToAdd.Add(strProto); + } + + public static void Apply() + { + if (!_initialized) return; + var strings = LDB._strings; + var index = strings.dataArray.Length; + strings.dataArray = strings.dataArray.Concat(StringsToAdd).ToArray(); + StringsToAdd.Clear(); + var newIndex = strings.dataArray.Length; + for (; index < newIndex; index++) + { + var strProto = strings.dataArray[index]; + strProto.ID = GetNextID(); + strings.dataIndices[strProto.ID] = index; + strings.nameIndices[strings.dataArray[index].Name] = index; + } + } + + [HarmonyPostfix, HarmonyPriority(Priority.Last), HarmonyPatch(typeof(VFPreload), "InvokeOnLoadWorkEnded")] + private static void VFPreload_InvokeOnLoadWorkEnded_Postfix() + { + if (_initialized) return; + _initialized = true; + if (StringsToAdd.Count == 0) + { + OnInitialized?.Invoke(); + return; + } + + Apply(); + OnInitialized?.Invoke(); + } + + private static int GetNextID() + { + var strings = LDB._strings; + while (_nextID <= 12000) + { + if (!strings.dataIndices.ContainsKey(_nextID)) + { + break; + } + + _nextID++; + } + + var result = _nextID; + _nextID++; + return result; + } +} \ No newline at end of file diff --git a/CompressSave/PatchUILoadGame.cs b/CompressSave/PatchUILoadGame.cs index aa6874e..4888367 100644 --- a/CompressSave/PatchUILoadGame.cs +++ b/CompressSave/PatchUILoadGame.cs @@ -32,32 +32,44 @@ class PatchUILoadGame if (_decompressButton) return; var loadButton = __instance.loadButton; + var created = false; var gameObj = __instance.transform.Find("button-decompress")?.gameObject; if (gameObj == null) + { gameObj = Object.Instantiate(loadButton.gameObject, loadButton.transform.parent); + created = true; + } + _decompressButton = gameObj.GetComponent(); - __instance.loadSandboxGroup.transform.Translate(new Vector3(-2.5f, 0, 0)); - _decompressButton.gameObject.name = "button-decompress"; - _decompressButton.transform.Translate(new Vector3(-2.0f, 0, 0)); - _decompressButton.button.image.color = new Color32(0, 0xf4, 0x92, 0x77); - var localizer = _decompressButton.transform.Find("button-text")?.GetComponent(); - var text = _decompressButton.transform.Find("button-text")?.GetComponent(); - - if (localizer) + if (created) { - localizer.stringKey = "Decompress"; - localizer.translation = "Decompress".Translate(); - } - if (text) + var rtrans = (RectTransform)__instance.loadSandboxGroup.transform; + var pos = rtrans.anchoredPosition3D; + rtrans.anchoredPosition3D = new Vector3(pos.x - 230, pos.y, pos.z); + _decompressButton.gameObject.name = "button-decompress"; + rtrans = (RectTransform)_decompressButton.transform; + pos = rtrans.anchoredPosition3D; + rtrans.anchoredPosition3D = new Vector3(pos.x - 180, pos.y, pos.z); + _decompressButton.button.image.color = new Color32(0, 0xf4, 0x92, 0x77); + var textTrans = _decompressButton.transform.Find("button-text"); + var text = textTrans.GetComponent(); text.text = "Decompress".Translate(); + var localizer = textTrans.GetComponent(); + if (localizer) + { + localizer.stringKey = "Decompress"; + localizer.translation = "Decompress".Translate(); + } + + _decompressButton.onClick += _ => + { + if (!SaveUtil.DecompressSave(__instance.selected.saveName, out var newfileName)) return; + __instance.RefreshList(); + __instance.selected = __instance.entries.First(e => e.saveName == newfileName); + }; + } - _decompressButton.onClick += _ => - { - if (!SaveUtil.DecompressSave(__instance.selected.saveName, out var newfileName)) return; - __instance.RefreshList(); - __instance.selected = __instance.entries.First(e => e.saveName == newfileName); - }; _decompressButton.button.interactable = false; _decompressButton.gameObject.SetActive(false); } diff --git a/CompressSave/PatchUISaveGame.cs b/CompressSave/PatchUISaveGame.cs index db66471..49ba771 100644 --- a/CompressSave/PatchUISaveGame.cs +++ b/CompressSave/PatchUISaveGame.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using HarmonyLib; using UnityEngine; using UnityEngine.UI; @@ -19,7 +20,7 @@ static class PatchUISaveGame } [HarmonyPatch(typeof(UISaveGameWindow), "OnSelectedChange"), HarmonyPostfix] - static void OnSelectedChange(UISaveGameWindow __instance) + private static void OnSelectedChange(UISaveGameWindow __instance) { var selected = __instance.selected; var compressedType = SaveUtil.SaveGetCompressType(selected == null ? null : selected.saveName); @@ -46,20 +47,16 @@ static class PatchUISaveGame private static void CheckAndSetSaveButtonEnable(UISaveGameWindow __instance) { _OnOpen(__instance); - if (_context.SaveButtonText && _context.SaveButton) - SetButtonState(_context.SaveButtonText.text, _context.SaveButton.button.interactable); - } - - private static void SetButtonState(string text, bool interactable) - { - _context.ButtonCompress.button.interactable = interactable; - _context.ButtonCompressText.text = text; + if (_context.SaveButton) + _context.ButtonCompress.button.interactable = _context.SaveButton.button.interactable; } private class UIContext { public UIButton ButtonCompress; public UIButton SaveButton; + public GameObject ManualSaveTypeComboBox; + public GameObject AutoSaveTypeComboBox; public Text ButtonCompressText; public Text SaveButtonText; public UISaveGameWindow Window; @@ -77,23 +74,134 @@ static class PatchUISaveGame private static void _OnOpen(UISaveGameWindow __instance) { if (_context.ButtonCompress) return; + RectTransform rtrans; + Vector3 pos; _context.SaveButton = __instance.saveButton; _context.SaveButtonText = __instance.saveButtonText; - _context.Window = __instance; var gameObj = __instance.transform.Find("button-compress")?.gameObject; + var created = false; if (gameObj == null) + { gameObj = Object.Instantiate(__instance.saveButton.gameObject, __instance.saveButton.transform.parent); + created = true; + } _context.ButtonCompress = gameObj.GetComponent(); + if (created) + { + _context.ButtonCompress.gameObject.name = "button-compress"; + rtrans = (RectTransform)_context.ButtonCompress.transform; + pos = rtrans.anchoredPosition3D; + rtrans.anchoredPosition3D = new Vector3(pos.x - 180, pos.y, pos.z); + _context.ButtonCompress.button.image.color = new Color32(0xfc, 0x6f, 00, 0x77); + var textTrans = _context.ButtonCompress.transform.Find("button-text"); + _context.ButtonCompressText = textTrans.GetComponent(); + _context.ButtonCompress.onClick += __instance.OnSaveClick; + _context.SaveButton.onClick -= __instance.OnSaveClick; + _context.SaveButton.onClick += WrapClick; + _context.ButtonCompressText.text = "Save with Compression".Translate(); + var localizer = textTrans.GetComponent(); + if (localizer) + { + localizer.stringKey = "Save with Compression"; + localizer.translation = "Save with Compression".Translate(); + } + } - _context.ButtonCompress.gameObject.name = "button-compress"; - _context.ButtonCompress.transform.Translate(new Vector3(-2.0f, 0, 0)); - _context.ButtonCompress.button.image.color = new Color32(0xfc, 0x6f, 00, 0x77); - _context.ButtonCompressText = _context.ButtonCompress.transform.Find("button-text")?.GetComponent(); + created = false; + gameObj = __instance.transform.Find("manual-save-type-combobox")?.gameObject; + if (gameObj == null) + { + gameObj = Object.Instantiate(UIRoot.instance.optionWindow.resolutionComp.transform.parent.gameObject, __instance.saveButton.transform.parent); + created = true; + } + _context.ManualSaveTypeComboBox = gameObj; + if (created) + { + _context.ManualSaveTypeComboBox.name = "manual-save-type-combobox"; + rtrans = (RectTransform)_context.ManualSaveTypeComboBox.transform; + var rtrans2 = (RectTransform)_context.ButtonCompress.transform; + pos = rtrans2.anchoredPosition3D; + rtrans.anchorMin = rtrans2.anchorMin; + rtrans.anchorMax = rtrans2.anchorMax; + rtrans.pivot = rtrans2.pivot; + rtrans.anchoredPosition3D = new Vector3(pos.x + 100, pos.y + 45, pos.z); + var cb = rtrans.transform.Find("ComboBox").GetComponent(); + cb.onSubmit.RemoveAllListeners(); + cb.onItemIndexChange.RemoveAllListeners(); + cb.Items = new List { "Store".Translate(), "LZ4", "Zstd" }; + cb.itemIndex = (int)PatchSave.CompressionTypeForSaves; + cb.onItemIndexChange.AddListener(()=> + { + PatchSave.CompressionTypeForSaves = (CompressionType)cb.itemIndex; + PatchSave.CompressionTypeForSavesConfig.Value = CompressSave.StringFromCompresstionType(PatchSave.CompressionTypeForSaves); + }); + rtrans = (RectTransform)cb.transform; + pos = rtrans.anchoredPosition3D; + rtrans.anchoredPosition3D = new Vector3(pos.x - 50, pos.y, pos.z); + var size = rtrans.sizeDelta; + rtrans.sizeDelta = new Vector2(150f, size.y); + var txt = _context.ManualSaveTypeComboBox.GetComponent(); + txt.text = "Compression for manual saves".Translate(); + var localizer = _context.ManualSaveTypeComboBox.GetComponent(); + if (localizer != null) + { + localizer.stringKey = "Compression for manual saves"; + localizer.translation = "Compression for manual saves".Translate(); + } + } - _context.ButtonCompress.onClick += __instance.OnSaveClick; - _context.SaveButton.onClick -= __instance.OnSaveClick; - _context.SaveButton.onClick += WrapClick; + created = false; + gameObj = __instance.transform.Find("auto-save-type-combobox")?.gameObject; + if (gameObj == null) + { + gameObj = Object.Instantiate(UIRoot.instance.optionWindow.resolutionComp.transform.parent.gameObject, __instance.saveButton.transform.parent); + created = true; + } + _context.AutoSaveTypeComboBox = gameObj; + if (created) + { + _context.AutoSaveTypeComboBox.name = "auto-save-type-combobox"; + rtrans = (RectTransform)_context.AutoSaveTypeComboBox.transform; + var rtrans2 = (RectTransform)_context.ButtonCompress.transform; + pos = rtrans2.anchoredPosition3D; + rtrans.anchorMin = rtrans2.anchorMin; + rtrans.anchorMax = rtrans2.anchorMax; + rtrans.pivot = rtrans2.pivot; + rtrans.anchoredPosition3D = new Vector3(pos.x + 510, pos.y + 45, pos.z); + var cb = rtrans.transform.Find("ComboBox").GetComponent(); + cb.onSubmit.RemoveAllListeners(); + cb.onItemIndexChange.RemoveAllListeners(); + cb.Items = new List { "已停用".Translate(), "Store".Translate(), "LZ4", "Zstd" }; + cb.itemIndex = PatchSave.EnableForAutoSaves.Value ? (int)PatchSave.CompressionTypeForAutoSaves + 1 : 0; + cb.onItemIndexChange.AddListener(() => + { + var idx = cb.itemIndex; + if (idx == 0) + { + PatchSave.EnableForAutoSaves.Value = false; + } + else + { + PatchSave.EnableForAutoSaves.Value = true; + PatchSave.CompressionTypeForAutoSaves = (CompressionType)idx - 1; + PatchSave.CompressionTypeForAutoSavesConfig.Value = CompressSave.StringFromCompresstionType(PatchSave.CompressionTypeForAutoSaves); + } + }); + rtrans = (RectTransform)cb.transform; + pos = rtrans.anchoredPosition3D; + rtrans.anchoredPosition3D = new Vector3(pos.x - 50, pos.y, pos.z); + var size = rtrans.sizeDelta; + rtrans.sizeDelta = new Vector2(150f, size.y); + var txt = _context.AutoSaveTypeComboBox.GetComponent(); + txt.text = "Compression for auto saves".Translate(); + var localizer = _context.AutoSaveTypeComboBox.GetComponent(); + if (localizer != null) + { + localizer.stringKey = "Compression for auto saves"; + localizer.translation = "Compression for auto saves".Translate(); + } + } } private static void WrapClick(int data) @@ -101,5 +209,4 @@ static class PatchUISaveGame PatchSave.UseCompressSave = false; _context.Window.OSaveGameAs(data); } - -} \ No newline at end of file +} diff --git a/CompressSave/README.md b/CompressSave/README.md index 65a73c7..bb0a63e 100644 --- a/CompressSave/README.md +++ b/CompressSave/README.md @@ -5,7 +5,11 @@ #### 压缩游戏存档以降低空间使用并提升保存速度 #### 原作者 [@bluedoom](https://github.com/bluedoom/DSP_Mod)(直到1.1.11) 和 [@starfi5h](https://github.com/starfi5h/DSP_CompressSave)(1.1.12),本人继续更新以支持最新游戏版本。 -## Updates +## Changelog + +### 1.3.2 +* Add config UI on Save Game dialog, to set compression types. +* Change button text to `Save (Compress)` for better understanding. ### 1.3.1 * Add config to disable feature for auto saves. @@ -14,7 +18,7 @@ ### 1.3.0 * Separate config entries for manual save and auto save. * Now you can still get speed benefit while setting compression type to `None` for auto saves, and for manual saves if using the new `Save` button. - * Adds a `nonewrap.dll` for this function. + + Adds a `nonewrap.dll` for this function. * Update `LZ4` and `Zstd` library to latest version. * `lz4wrap.dll` and `zstdwrap.dll` are compiled using `-O3` instead of `-Os`, expect to be slightly faster but larger. @@ -112,7 +116,7 @@ ## Introduction -* Reduce archive size by 30% / save time by 75% (On HDD + i7-4790K@4.4G + DDR3 2400MHz) +* Reduce archive size by 30% / save time by 75% (Compressed by LZ4, on HDD + i7-4790K@4.4G + DDR3 2400MHz) | Before | After | | - | - | @@ -128,9 +132,38 @@ * You can decompress saves on load panel. * Remember to backup your save(use original save button) before updating game to avoid loading failure. +## 更新日志 + +### 1.3.2 +* 在保存面板上增加设置压缩方式的UI。 +* 将按钮文本改为`压缩保存`以区分功能。 + +### 1.3.1 +* 增加在自动存档中禁用压缩的设置项。 +* 修复一个导致游戏开始后第一次保存总是使用Zstd压缩的bug。 + +### 1.3.0 +* 分离手动存档和自动存档的设置项。 +* 现在在自动存档设置压缩类型为`存储`也可以获得速度提升,手动存档也可以在使用新的`保存`按钮后获得速度提升。 + + 为此增加了`nonewrap.dll`。 +* 更新`LZ4`和`Zstd`库到最新版本。 +* `lz4wrap.dll`和`zstdwrap.dll`使用`-O3`编译而不是`-Os`,速度略有提升但体积变大。 + +### 1.2.2 +* 修复 #4,一个导致非ASCII UTF-8字符导致的bug。 +* 移除使用Harmony.UnpatchAll()以避免在BepInEx日志中出现警告。 + +### 1.2.1 +* 简化代码以在存档读取面板上显示压缩类型和`解压`按钮,使得CompressSave与其他MOD(如GalacticScale)兼容,因为它们都覆盖了`UILoadGameWindow::OnSelectedChange()`。 +* 为zstd添加了压缩等级-5到-1,现在它比lz4(实际上是lz4frame)表现更好了: + * -5比lz4更快,但压缩比略有提升。 + * -1和lz4几乎一样快,但压缩比更高。 + * 由于r2modman UI的bug,压缩等级的设置项不再限制范围。 +* 将本地的wrapper DLL移动到`x64` + ## 介绍 -* 减少存档容量30% / 存档用时75% (测试环境:机械硬盘 + i7-4790K@4.4G + DDR3 2400MHz) +* 减少存档容量30% / 存档用时75% (LZ4压缩,测试环境:机械硬盘 + i7-4790K@4.4G + DDR3 2400MHz) | 原存档 | 压缩后 | | - | - | diff --git a/CompressSave/package/manifest.json b/CompressSave/package/manifest.json index aea2564..5a3b343 100644 --- a/CompressSave/package/manifest.json +++ b/CompressSave/package/manifest.json @@ -1,6 +1,6 @@ { "name": "CompressSave", - "version_number": "1.3.1", + "version_number": "1.3.2", "website_url": "https://github.com/soarqin/DSP_Mods/tree/master/CompressSave", "description": "Compress game saves to reduce space use and boost save speed / 压缩游戏存档以降低空间使用并提升保存速度", "dependencies": ["xiaoye97-BepInEx-5.4.17"]