using System; using System.IO; using HarmonyLib; using UnityEngine; namespace NecromancerTome { /// /// ПРОВЕРКА ВЕРСИИ ИГРЫ, 1.4.2 (2026-09-24). Повод: 1.4.1 на игре 3.3.0 experimental молча /// потеряла содержимое Пространственного хранилища - разбор в BACKLOG.md, «Игра 3.3.0 /// experimental». Решение пользователя: при несовместимой версии мод показывает в главном меню /// окно «мод несовместим, обновите» с ЕДИНСТВЕННОЙ кнопкой «Выйти из игры» - без «играть всё /// равно». /// /// ПОЧЕМУ ЧИТАЕТСЯ cVersionInformation, А НЕ cVersionMinor. cVersionMinor - const, компилятор /// вписывает её значение (20) прямо в нашу DLL, и на 3.3 она всё равно «видела» бы 20. /// cVersionInformation - static readonly, её значение берётся из игры при запуске. /// /// ПОЧЕМУ Minor / 10. Игра хранит версию двузначным числом: 3.2.0 = 20, 3.2.1 = 21, 3.3.0 = 30 /// (VersionInformation печатает его как {Minor/10}.{Minor%10}). Проверка по десяткам пропускает /// любые хотфиксы 3.3.x и не пропускает ни 3.2, ни 3.4 и всё, что будет после. /// /// ВСЁ, ЧЕМ ПОЛЬЗУЕТСЯ ЭТОТ ФАЙЛ, СВЕРЕНО С ДЕКОМПИЛЯЦИЕЙ 3.3.0 (build 25485021): Constants, /// VersionInformation, XUiC_MainMenu.OnOpen, XUiC_MessageBoxWindowGroup (класс совпадает целиком), /// Mod.VersionString. Иначе сборка под 3.2 упала бы на 3.3 раньше, чем показала окно. /// /// ОТЛАДКА НА 3.2: если в папке мода лежит файл FORCE_VERSION_GUARD, мод ведёт себя так, будто /// версия несовместима. В релизный архив он не попадает - состав архива задан явным списком. /// public static class GameVersionGuard { /// Под какую версию собран мод: Major и десяток Minor (3.3.x = 3 и 3; сборка 1.4.x была под 3.2). public const int SupportedMajor = 3; public const int SupportedMinorTens = 3; public const string SupportedText = "3.3"; private static bool? supported; public static bool IsSupported { get { if (supported == null) { supported = Check(); } return supported.Value; } } public static string GameVersionText { get { VersionInformation v = Constants.cVersionInformation; return v != null ? v.LongStringNoBuild : "?"; } } private static bool Check() { if (ForcedByFile()) { Debug.LogWarning("[NecromancerTome] GameVersionGuard: FORCE_VERSION_GUARD file present - acting as if the game version were unsupported"); return false; } VersionInformation v = Constants.cVersionInformation; bool ok = v != null && v.Major == SupportedMajor && v.Minor / 10 == SupportedMinorTens; Debug.Log("[NecromancerTome] GameVersionGuard: game " + GameVersionText + " (Major " + (v != null ? v.Major : -1) + ", Minor " + (v != null ? v.Minor : -1) + "), mod built for " + SupportedText + " -> " + (ok ? "supported" : "NOT SUPPORTED")); return ok; } private static bool ForcedByFile() { return DebugFileExists("FORCE_VERSION_GUARD"); } /// Отладка сохранения хранилища байт в байт на 3.2: при файле FORCE_VAULT_UNREAD /// в папке мода хранилище из сейва не разбирается, а проходит всю цепочку сохранения /// нетронутым - ровно как на несовместимой версии, но без окна с выходом, так что в мир /// можно зайти и сохраниться. public static bool ForceVaultUnread { get { return DebugFileExists("FORCE_VAULT_UNREAD"); } } private static bool DebugFileExists(string _name) { try { string folder = ModEntry.Instance != null ? ModEntry.Instance.Path : null; return folder != null && File.Exists(Path.Combine(folder, _name)); } catch (Exception) { return false; } } public static void ShowIncompatibleWindow(XUi _xui) { string modVersion = ModEntry.Instance != null ? ModEntry.Instance.VersionString : "?"; string text = string.Format(Localization.Get("necroVersionGuardText"), modVersion, SupportedText, GameVersionText); XUiC_MessageBoxWindowGroup.ShowCustom(_xui, Localization.Get("necroVersionGuardTitle"), text, "", delegate(XUiC_MessageBoxWindowGroup _box) { _box.buttons[0].DefaultConfirm("necroVersionGuardQuit", delegate { Debug.Log("[NecromancerTome] GameVersionGuard: player pressed Quit"); Application.Quit(); }); }, _openMainMenuOnClose: false, _modal: true, _buttonOnOutsideClick: -1, // Проверено в игре 2026-09-25: Esc модальное окно не закрывает - оно остаётся на // экране, и уйти можно только кнопкой. Это и нужно. _buttonOnExternalClose: 0 - на // случай, если игра всё же закроет окно извне: тогда это тоже выход. _buttonOnExternalClose: 0); Debug.LogWarning("[NecromancerTome] GameVersionGuard: incompatible game version, quit window shown"); } } /// Главное меню: здесь показывается окно, и здесь же сбрасываются непрочитанные байты /// хранилища - возврат в меню значит, что прежний мир закрыт, и его данные не должны перейти к /// персонажу следующего. [HarmonyPatch(typeof(XUiC_MainMenu), "OnOpen")] public static class Patch_XUiC_MainMenu_OnOpen_VersionGuard { public static void Postfix(XUiC_MainMenu __instance) { try { SpatialVaultPersistence.UnreadableByEntity.Clear(); if (!GameVersionGuard.IsSupported) { GameVersionGuard.ShowIncompatibleWindow(__instance.xui); } } catch (Exception e) { Debug.LogError("[NecromancerTome] GameVersionGuard: main menu postfix failed: " + e); } } } }