1.4.2: проверка версии игры и хранилище, которое не стирается
Повод: 1.4.1 на игре 3.3.0 experimental не смогла прочитать Пространственное хранилище (Bag переписан) и при сохранении записала пустой маркер - вещи пропали навсегда. GameVersionGuard: версия из Constants.cVersionInformation (не из const cVersionMinor - та вшивается в DLL), поддерживается Major 3 и Minor/10 == 2, то есть любая 3.2.x. На другой версии в главном меню окно «мод несовместим, обновите» с единственной кнопкой «Выйти из игры» (решение пользователя), а вместо PatchAll ставятся по отдельности только окно и четыре патча сохранения хранилища. Всё, чем пользуется защита, сверено с декомпиляцией 3.3. Хранилище: непрочитанные байты (чужая версия или упавший разбор) проходят Read -> ToPlayer -> FromPlayer -> Write нетронутыми, ключ - entityId, сброс при открытии главного меню (последнее сохранение игрока делается раньше, внутри SaveAndCleanupWorld - проверено по коду игры). Пока байты не прочитаны, хранилище не открывается и не пополняется, с подсказкой. Проверено в игре на 3.2 через отладочные файлы FORCE_VAULT_UNREAD и FORCE_VERSION_GUARD (в .gitignore, в архив не попадают): 603 байта хранилища прошли сохранение и прочитались обратно совпадающими с исходными (26 ячеек, 24 предмета); окно появляется, Esc его не закрывает, кнопка закрывает игру. На настоящей 3.3 не проверено. Localization: 4 новых ключа на 13 языках, всего 165; README - статус и число ключей (было 149). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
77b56be9ae
commit
f4470b784f
@@ -0,0 +1,151 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using HarmonyLib;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NecromancerTome
|
||||
{
|
||||
/// <summary>
|
||||
/// ПРОВЕРКА ВЕРСИИ ИГРЫ, 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.2.x и не пропускает 3.3 и всё, что будет после.
|
||||
///
|
||||
/// ВСЁ, ЧЕМ ПОЛЬЗУЕТСЯ ЭТОТ ФАЙЛ, СВЕРЕНО С ДЕКОМПИЛЯЦИЕЙ 3.3.0 (build 25485021): Constants,
|
||||
/// VersionInformation, XUiC_MainMenu.OnOpen, XUiC_MessageBoxWindowGroup (класс совпадает целиком),
|
||||
/// Mod.VersionString. Иначе сборка под 3.2 упала бы на 3.3 раньше, чем показала окно.
|
||||
///
|
||||
/// ОТЛАДКА НА 3.2: если в папке мода лежит файл FORCE_VERSION_GUARD, мод ведёт себя так, будто
|
||||
/// версия несовместима. В релизный архив он не попадает - состав архива задан явным списком.
|
||||
/// </summary>
|
||||
public static class GameVersionGuard
|
||||
{
|
||||
/// <summary>Под какую версию собран мод: Major и десяток Minor (3.2.x = 3 и 2).</summary>
|
||||
public const int SupportedMajor = 3;
|
||||
public const int SupportedMinorTens = 2;
|
||||
public const string SupportedText = "3.2";
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
/// <summary>Отладка сохранения хранилища байт в байт на 3.2: при файле FORCE_VAULT_UNREAD
|
||||
/// в папке мода хранилище из сейва не разбирается, а проходит всю цепочку сохранения
|
||||
/// нетронутым - ровно как на несовместимой версии, но без окна с выходом, так что в мир
|
||||
/// можно зайти и сохраниться.</summary>
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Главное меню: здесь показывается окно, и здесь же сбрасываются непрочитанные байты
|
||||
/// хранилища - возврат в меню значит, что прежний мир закрыт, и его данные не должны перейти к
|
||||
/// персонажу следующего.</summary>
|
||||
[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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user