diff --git a/.gitignore b/.gitignore
index 55a72b7..0e73657 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,5 @@ TEPersistenceSrc/bin/
*.user
BACKLOG.md
FINAL_TEXT.md
+FORCE_VERSION_GUARD
+FORCE_VAULT_UNREAD
diff --git a/Config/Localization.csv b/Config/Localization.csv
index d6da77c..32248a3 100644
--- a/Config/Localization.csv
+++ b/Config/Localization.csv
@@ -160,3 +160,7 @@ buffNecroRatMarkName,buffs,Buff,,,,Spirit Mark,,Geistermal,Marca del espíritu,M
buffNecroRatMarkDesc,buffs,Buff,,,,"Marked by a rat spirit: visible on its master’s compass and map, and rotting - every wound cuts deeper than it should.",,"Von einem Rattengeist gezeichnet: auf Kompass und Karte seines Herrn sichtbar - und faulend, sodass jede Wunde tiefer geht, als sie sollte.","Marcado por un espíritu de rata: visible en la brújula y el mapa de su amo, y pudriéndose - cada herida cala más hondo de lo que debería.","Marqué par un esprit de rat : visible sur la boussole et la carte de son maître, et en putréfaction - chaque blessure creuse plus profond qu’elle ne devrait.","Marchiato da uno spirito di ratto: visibile sulla bussola e sulla mappa del suo padrone, e in putrefazione - ogni ferita affonda più del dovuto.",ネズミの霊に印をつけられた。その主のコンパスと地図に映り、腐ってゆく - どんな傷も本来より深く刻まれる。,"쥐의 영혼에게 표식이 찍혔습니다. 그 주인의 나침반과 지도에 드러나며, 썩어들어 모든 상처가 본래보다 깊게 패입니다.","Naznaczony przez ducha szczura: widoczny na kompasie i mapie jego pana, i gnijący - każda rana sięga głębiej, niż powinna.","Marcado por um espírito de rato: visível na bússola e no mapa de seu mestre, e apodrecendo - cada ferida penetra mais fundo do que deveria.","Помечен духом крысы: виден на компасе и карте его хозяина и гниёт — каждая рана заходит глубже, чем должна.",Bir sıçan ruhu tarafından işaretlendi: efendisinin pusulasında ve haritasında görünür ve çürür - her yara olması gerekenden derine iner.,被鼠灵标记:显现在其主人的罗盘与地图上,并且开始腐烂——任何伤口都比应有的更深。,被鼠靈標記:顯現在其主人的羅盤與地圖上,並且開始腐爛——任何傷口都比應有的更深。
buffNecroRatBleedName,buffs,Buff,,,,Torn Open,,Aufgerissen,Desgarrado,Déchiré,Squarciato,裂傷,찢긴 상처,Rozszarpany,Dilacerado,Рваная рана,Parçalanmış,撕裂伤,撕裂傷
buffNecroRatBleedDesc,buffs,Buff,,,,Teeth found a vein. The wound will not close on its own while they keep worrying at it.,,"Zähne haben eine Ader gefunden. Die Wunde schließt sich nicht von selbst, solange sie daran weiterzerren.",Unos dientes dieron con una vena. La herida no se cerrará sola mientras sigan hurgando en ella.,Des dents ont trouvé une veine. La plaie ne se refermera pas d’elle-même tant qu’elles s’y acharneront.,I denti hanno trovato una vena. La ferita non si chiuderà da sola finché continuano ad accanirsi.,歯が血管を探り当てた。噛みつづけられているかぎり、傷はひとりでに塞がらない。,이빨이 혈관을 찾아냈습니다. 계속 물어뜯기는 동안 상처는 저절로 아물지 않습니다.,"Zęby trafiły na żyłę. Rana nie zamknie się sama, dopóki będą się w nią wgryzać.",Os dentes acharam uma veia. A ferida não vai fechar sozinha enquanto continuarem a remexer nela.,"Зубы нашли жилу. Пока в ней продолжают копаться, рана сама не затянется.",Dişler bir damar buldu. Onu kurcalamayı sürdürdükleri sürece yara kendiliğinden kapanmaz.,牙齿咬中了血管。只要还在撕扯,伤口就不会自行闭合。,牙齒咬中了血管。只要還在撕扯,傷口就不會自行閉合。
+necroVersionGuardTitle,UI,XUi MainMenu,x,,,Necromancer's Tome: incompatible game version,,Necromancer's Tome: inkompatible Spielversion,Necromancer's Tome: versión del juego incompatible,Necromancer's Tome : version du jeu incompatible,Necromancer's Tome: versione del gioco incompatibile,Necromancer's Tome: 互換性のないゲームバージョン,Necromancer's Tome: 호환되지 않는 게임 버전,Necromancer's Tome: niezgodna wersja gry,Necromancer's Tome: versão do jogo incompatível,Книга некроманта: несовместимая версия игры,Necromancer's Tome: uyumsuz oyun sürümü,Necromancer's Tome:游戏版本不兼容,Necromancer's Tome:遊戲版本不相容
+necroVersionGuardText,UI,XUi MainMenu,x,,,"Necromancer's Tome {0} is made for 7 Days to Die {1} and is not compatible with your game version ({2}). Playing with it would damage your saves. Please update the mod to the version made for your game, or remove it. The game will now close.",,Necromancer's Tome {0} ist für 7 Days to Die {1} gemacht und nicht mit deiner Spielversion ({2}) kompatibel. Damit zu spielen würde deine Spielstände beschädigen. Bitte aktualisiere die Mod auf die Version für dein Spiel oder entferne sie. Das Spiel wird jetzt beendet.,Necromancer's Tome {0} está hecho para 7 Days to Die {1} y no es compatible con tu versión del juego ({2}). Jugar con él dañaría tus partidas guardadas. Actualiza el mod a la versión para tu juego o elimínalo. El juego se cerrará ahora.,"Necromancer's Tome {0} est conçu pour 7 Days to Die {1} et n'est pas compatible avec ta version du jeu ({2}). Jouer avec endommagerait tes sauvegardes. Mets le mod à jour vers la version prévue pour ton jeu, ou retire-le. Le jeu va maintenant se fermer.",Necromancer's Tome {0} è fatto per 7 Days to Die {1} e non è compatibile con la tua versione del gioco ({2}). Giocarci danneggerebbe i tuoi salvataggi. Aggiorna la mod alla versione per il tuo gioco oppure rimuovila. Il gioco ora verrà chiuso.,Necromancer's Tome {0} は 7 Days to Die {1} 用に作られており、お使いのゲームバージョン({2})とは互換性がありません。このままプレイするとセーブデータが損傷します。お使いのゲーム用のバージョンにMODを更新するか、MODを削除してください。ゲームを終了します。,Necromancer's Tome {0}은(는) 7 Days to Die {1}용으로 만들어졌으며 현재 게임 버전({2})과 호환되지 않습니다. 이대로 플레이하면 저장 파일이 손상됩니다. 게임에 맞는 버전으로 모드를 업데이트하거나 모드를 제거하세요. 이제 게임이 종료됩니다.,Necromancer's Tome {0} jest przeznaczony dla 7 Days to Die {1} i nie jest zgodny z twoją wersją gry ({2}). Gra z nim uszkodziłaby twoje zapisy. Zaktualizuj moda do wersji dla twojej gry albo go usuń. Gra zostanie teraz zamknięta.,Necromancer's Tome {0} foi feito para 7 Days to Die {1} e não é compatível com a sua versão do jogo ({2}). Jogar com ele danificaria seus saves. Atualize o mod para a versão feita para o seu jogo ou remova-o. O jogo será fechado agora.,"Мод «Книга некроманта» {0} сделан для 7 Days to Die {1} и несовместим с вашей версией игры ({2}). Игра с ним повредит ваши сохранения. Пожалуйста, обновите мод до версии для вашей игры или удалите его. Сейчас игра закроется.","Necromancer's Tome {0}, 7 Days to Die {1} için yapıldı ve oyun sürümünüzle ({2}) uyumlu değil. Onunla oynamak kayıtlarınıza zarar verir. Lütfen modu oyununuza uygun sürüme güncelleyin veya kaldırın. Oyun şimdi kapanacak.",Necromancer's Tome {0} 为 7 Days to Die {1} 制作,与你的游戏版本({2})不兼容。继续使用会损坏你的存档。请将模组更新到适合你游戏的版本,或将其移除。游戏现在将关闭。,Necromancer's Tome {0} 為 7 Days to Die {1} 製作,與你的遊戲版本({2})不相容。繼續使用會損壞你的存檔。請將模組更新到適合你遊戲的版本,或將其移除。遊戲現在將關閉。
+necroVersionGuardQuit,UI,XUi MainMenu,x,,,Quit game,,Spiel beenden,Salir del juego,Quitter le jeu,Esci dal gioco,ゲームを終了,게임 종료,Wyjdź z gry,Sair do jogo,Выйти из игры,Oyundan çık,退出游戏,退出遊戲
+braceletSpatialVaultUnreadable,items,Item,,,,This storage rift was saved by another version of the mod. It is kept untouched until you install the mod made for your game version.,,"Dieser Lagerriss wurde von einer anderen Version der Mod gespeichert. Er bleibt unangetastet, bis du die Mod für deine Spielversion installierst.",Esta grieta de almacenamiento se guardó con otra versión del mod. Se conserva intacta hasta que instales el mod para tu versión del juego.,Cette faille de stockage a été enregistrée par une autre version du mod. Elle reste intacte jusqu'à ce que tu installes le mod prévu pour ta version du jeu.,Questa fenditura di deposito è stata salvata da un'altra versione della mod. Resta intatta finché non installi la mod per la tua versione del gioco.,この収納の裂け目は別のバージョンのMODで保存されました。お使いのゲームバージョン用のMODを入れるまで、手を付けずに保持されます。,이 보관 균열은 다른 버전의 모드로 저장되었습니다. 게임 버전에 맞는 모드를 설치할 때까지 그대로 보존됩니다.,"Ta szczelina magazynowa została zapisana przez inną wersję moda. Pozostaje nietknięta, dopóki nie zainstalujesz moda dla twojej wersji gry.",Esta fenda de armazenamento foi salva por outra versão do mod. Ela fica intacta até você instalar o mod feito para a sua versão do jogo.,"Этот разлом хранилища сохранён другой версией мода. Он останется нетронутым, пока вы не поставите мод для вашей версии игры.",Bu depolama yarığı modun başka bir sürümüyle kaydedildi. Oyun sürümünüze uygun modu kurana kadar dokunulmadan saklanır.,这个储物裂隙由另一个版本的模组保存。在你安装适合你游戏版本的模组之前,它会原样保留。,這個儲物裂隙由另一個版本的模組儲存。在你安裝適合你遊戲版本的模組之前,它會原樣保留。
diff --git a/HarmonySrc/GameVersionGuard.cs b/HarmonySrc/GameVersionGuard.cs
new file mode 100644
index 0000000..58d3d5f
--- /dev/null
+++ b/HarmonySrc/GameVersionGuard.cs
@@ -0,0 +1,151 @@
+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.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, мод ведёт себя так, будто
+ /// версия несовместима. В релизный архив он не попадает - состав архива задан явным списком.
+ ///
+ public static class GameVersionGuard
+ {
+ /// Под какую версию собран мод: Major и десяток Minor (3.2.x = 3 и 2).
+ 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");
+ }
+
+ /// Отладка сохранения хранилища байт в байт на 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);
+ }
+ }
+ }
+}
diff --git a/HarmonySrc/ModEntry.cs b/HarmonySrc/ModEntry.cs
index 357881f..f850c40 100644
--- a/HarmonySrc/ModEntry.cs
+++ b/HarmonySrc/ModEntry.cs
@@ -19,6 +19,36 @@ namespace NecromancerTome
{
Instance = _modInstance;
var harmony = new Harmony("necromancertome.harmony");
+
+ // НЕСОВМЕСТИМАЯ ВЕРСИЯ ИГРЫ (1.4.2, см. GameVersionGuard.cs): ставятся только окно в
+ // главном меню и сохранение хранилища байт в байт - каждое отдельно, чтобы отказ одного
+ // не помешал другим. Остальной код мода, написанный под другую версию, не запускается
+ // вовсе: игра всё равно закроется из главного меню.
+ if (!GameVersionGuard.IsSupported)
+ {
+ System.Type[] safetyPatches =
+ {
+ typeof(Patch_XUiC_MainMenu_OnOpen_VersionGuard),
+ typeof(Patch_PlayerDataFile_Read_SpatialVault),
+ typeof(Patch_PlayerDataFile_ToPlayer_SpatialVault),
+ typeof(Patch_PlayerDataFile_FromPlayer_SpatialVault),
+ typeof(Patch_PlayerDataFile_Write_SpatialVault),
+ };
+ foreach (System.Type patch in safetyPatches)
+ {
+ try
+ {
+ harmony.CreateClassProcessor(patch).Patch();
+ Debug.Log("[NecromancerTome] ModEntry: unsupported game version - safety patch " + patch.Name + " applied");
+ }
+ catch (System.Exception e)
+ {
+ Debug.LogError("[NecromancerTome] ModEntry: safety patch " + patch.Name + " failed: " + e);
+ }
+ }
+ return;
+ }
+
harmony.PatchAll(Assembly.GetExecutingAssembly());
PetFollowPatch.Init();
// SpatialVaultPersistence needs NO Init(): it is four Harmony postfixes that PatchAll
diff --git a/HarmonySrc/SpatialVaultPatch.cs b/HarmonySrc/SpatialVaultPatch.cs
index db1e9e1..fd0ba89 100644
--- a/HarmonySrc/SpatialVaultPatch.cs
+++ b/HarmonySrc/SpatialVaultPatch.cs
@@ -116,6 +116,13 @@ namespace NecromancerTome
public static void OpenVault(EntityPlayerLocal player)
{
+ // Хранилище из сейва не прочитано - открывать нечего, а пустое новое затёрло бы его при
+ // сохранении. См. SpatialVaultPersistence.UnreadableByEntity.
+ if (SpatialVaultPersistence.IsLocked(player))
+ {
+ GameManager.ShowTooltip(player, "braceletSpatialVaultUnreadable");
+ return;
+ }
ProgressionValue progressionValue = player.Progression?.GetProgressionValue(NecromancySkillName);
int level = progressionValue != null ? progressionValue.Level : 0;
int slotCount = Mathf.RoundToInt(level / 10f);
diff --git a/HarmonySrc/SpatialVaultPersistence.cs b/HarmonySrc/SpatialVaultPersistence.cs
index ac6b1e6..02e999e 100644
--- a/HarmonySrc/SpatialVaultPersistence.cs
+++ b/HarmonySrc/SpatialVaultPersistence.cs
@@ -89,6 +89,37 @@ namespace NecromancerTome
public class VaultSlot
{
public Bag Bag;
+
+ /// Байты хранилища, которые эта сборка не прочитала: версия игры не та, под
+ /// которую собран мод, или разбор упал. Пишутся обратно в файл байт в байт.
+ public byte[] RawPayload;
+ }
+
+ /// ХРАНИЛИЩЕ НЕ ДОЛЖНО ТЕРЯТЬСЯ НА ЧУЖОЙ ВЕРСИИ ИГРЫ, 1.4.2 (2026-09-24). В 1.4.1 на
+ /// игре 3.3 разбор хранилища падал, и при сохранении мод писал вместо него ПУСТОЙ маркер -
+ /// вещи пропадали навсегда. Теперь непрочитанные байты проходят всю цепочку сохранения
+ /// нетронутыми: Read кладёт их в слот файла, ToPlayer переносит к игроку (сюда), FromPlayer
+ /// отдаёт новому файлу (игра на каждом сохранении создаёт НОВЫЙ PlayerDataFile), Write
+ /// пишет их как есть. Сборка под нужную версию потом прочтёт их обычным путём.
+ ///
+ /// Ключ - entityId. Очищается при каждом открытии главного меню (GameVersionGuard.cs), чтобы
+ /// байты одного мира не перешли к персонажу другого.
+ ///
+ /// Пока байты здесь, хранилище не открывается и не пополняется (SpatialVaultPatch.OpenVault,
+ /// SpatialVaultPickup.Begin): вещи, положенные в новое пустое хранилище, всё равно не
+ /// сохранились бы - запись отдаёт приоритет нетронутым байтам.
+ public static readonly System.Collections.Generic.Dictionary UnreadableByEntity =
+ new System.Collections.Generic.Dictionary();
+
+ public static bool IsLocked(EntityPlayer _player)
+ {
+ return _player != null && UnreadableByEntity.ContainsKey(_player.entityId);
+ }
+
+ public static void AttachRaw(PlayerDataFile _file, byte[] _raw)
+ {
+ AttachedVaults.Remove(_file);
+ AttachedVaults.Add(_file, new VaultSlot { RawPayload = _raw });
}
/// Vault attached to a PlayerDataFile while it is being written, read or
@@ -215,6 +246,17 @@ namespace NecromancerTome
{
return;
}
+ if (SpatialVaultPersistence.UnreadableByEntity.TryGetValue(_player.entityId, out byte[] raw))
+ {
+ SpatialVaultPersistence.AttachRaw(__instance, raw);
+ string rawLine = "FromPlayer entity " + _player.entityId + " - carrying " + raw.Length + " unread byte(s) unchanged";
+ if (rawLine != SpatialVaultPersistence.lastSaveLogged)
+ {
+ SpatialVaultPersistence.lastSaveLogged = rawLine;
+ Debug.Log("[NecromancerTome] SpatialVaultPersistence: " + rawLine);
+ }
+ return;
+ }
Patch_ItemActionEat_ExecuteAction_SpatialVault.PlayerVaults.TryGetValue(_player.entityId, out Bag bag);
string source = bag != null ? "session cache" : null;
if (bag == null && _player is EntityPlayerLocal && SpatialVaultPersistence.LastLoadedVault != null)
@@ -260,6 +302,21 @@ namespace NecromancerTome
string note;
Bag bag;
+ if (SpatialVaultPersistence.AttachedVaults.TryGetValue(__instance, out SpatialVaultPersistence.VaultSlot rawSlot) &&
+ rawSlot.RawPayload != null)
+ {
+ SpatialVaultPersistence.UnreadableByEntity[_player.entityId] = rawSlot.RawPayload;
+ Patch_ItemActionEat_ExecuteAction_SpatialVault.PlayerVaults.Remove(_player.entityId);
+ if (isLocal)
+ {
+ SpatialVaultPersistence.LastLoadedVault = null;
+ }
+ Debug.LogWarning("[NecromancerTome] SpatialVaultPersistence: ToPlayer entity " + _player.entityId +
+ " - vault kept as " + rawSlot.RawPayload.Length + " unread byte(s); it stays locked and is saved unchanged");
+ return;
+ }
+ SpatialVaultPersistence.UnreadableByEntity.Remove(_player.entityId);
+
if (SpatialVaultPersistence.AttachedVaults.TryGetValue(__instance, out SpatialVaultPersistence.VaultSlot slot))
{
bag = slot.Bag;
@@ -309,6 +366,11 @@ namespace NecromancerTome
try
{
SpatialVaultPersistence.AttachedVaults.TryGetValue(__instance, out SpatialVaultPersistence.VaultSlot slot);
+ if (slot != null && slot.RawPayload != null)
+ {
+ SpatialVaultBlobIO.Write(_bw, slot.RawPayload);
+ return;
+ }
Bag bag = slot != null ? slot.Bag : null;
SpatialVaultBlobIO.Write(_bw, SpatialVaultPersistence.BuildPayload(bag));
if (bag == null)
@@ -346,7 +408,27 @@ namespace NecromancerTome
return;
}
- Bag bag = SpatialVaultPersistence.ParsePayload(payload);
+ if (!GameVersionGuard.IsSupported || GameVersionGuard.ForceVaultUnread)
+ {
+ SpatialVaultPersistence.AttachRaw(__instance, payload);
+ Debug.LogWarning("[NecromancerTome] SpatialVaultPersistence: Read - game version not supported, " +
+ payload.Length + " byte(s) kept unread and will be saved unchanged");
+ return;
+ }
+ Bag bag;
+ try
+ {
+ bag = SpatialVaultPersistence.ParsePayload(payload);
+ }
+ catch (Exception parseError)
+ {
+ // Упал сам разбор (на 3.3 это был MissingMethodException на Bag.Read). Байты не
+ // выбрасываем: они уйдут в файл как были.
+ SpatialVaultPersistence.AttachRaw(__instance, payload);
+ Debug.LogError("[NecromancerTome] SpatialVaultPersistence: Read - could not parse the vault, " +
+ payload.Length + " byte(s) kept unread and will be saved unchanged: " + parseError);
+ return;
+ }
// Attached even when null: a blob that says "no vault" IS an answer, and ToPlayer
// uses it to clear a stale cache when a different save is loaded.
SpatialVaultPersistence.Attach(__instance, bag);
diff --git a/HarmonySrc/SpatialVaultPickupPatch.cs b/HarmonySrc/SpatialVaultPickupPatch.cs
index 3ce220a..6182e14 100644
--- a/HarmonySrc/SpatialVaultPickupPatch.cs
+++ b/HarmonySrc/SpatialVaultPickupPatch.cs
@@ -599,6 +599,11 @@ namespace NecromancerTome
/// the same way in both places.
public static Bag GetVault(EntityPlayerLocal _player)
{
+ if (SpatialVaultPersistence.IsLocked(_player))
+ {
+ Deny(_player, "braceletSpatialVaultUnreadable");
+ return null;
+ }
ProgressionValue progressionValue = _player.Progression?.GetProgressionValue(
Patch_ItemActionEat_ExecuteAction_SpatialVault.NecromancySkillName);
int level = progressionValue != null ? progressionValue.Level : 0;
diff --git a/ModInfo.xml b/ModInfo.xml
index 479c85e..9f6a970 100644
--- a/ModInfo.xml
+++ b/ModInfo.xml
@@ -4,6 +4,6 @@
-
+
diff --git a/NEXUS_CHANGELOG_1.4.2.txt b/NEXUS_CHANGELOG_1.4.2.txt
new file mode 100644
index 0000000..9f54c0f
--- /dev/null
+++ b/NEXUS_CHANGELOG_1.4.2.txt
@@ -0,0 +1,4 @@
+NEW: the mod now checks the game version at start. This build is for 7 Days to Die 3.2.x; on any other version (3.3 experimental included) the main menu shows a window saying the mod is incompatible and asking you to update it, with a single Quit game button. This is to stop what happened with 1.4.1 on 3.3: the Spatial Vault could not be read there and was saved empty, so its items were lost.
+NEW: the Spatial Vault can no longer be wiped by a version it cannot read. If its saved contents cannot be read, they are kept untouched and written back byte for byte on every save, and the vault stays closed with a message instead of opening empty. Installing the right build of the mod brings the items back.
+The new window and the vault message are translated into all 13 languages of the mod.
+Drop-in over 1.4.1, saves carry over. For game version 3.2 only; a 3.3 version will come separately. Single-player. The mod ships Harmony libraries, so EasyAntiCheat must be off.
diff --git a/NEXUS_FILE_SUMMARY_1.4.2.txt b/NEXUS_FILE_SUMMARY_1.4.2.txt
new file mode 100644
index 0000000..ca83f55
--- /dev/null
+++ b/NEXUS_FILE_SUMMARY_1.4.2.txt
@@ -0,0 +1 @@
+Safety update: on an incompatible game version (e.g. 3.3 experimental) the mod asks you to update it and quits, and the Spatial Vault is never wiped - unreadable contents are kept as they are. For game 3.2. Drop-in over 1.4.1.
diff --git a/NecromancerHarmony.dll b/NecromancerHarmony.dll
index e4cd997..c2e2a06 100644
Binary files a/NecromancerHarmony.dll and b/NecromancerHarmony.dll differ
diff --git a/NecromancerHarmony.pdb b/NecromancerHarmony.pdb
index 0da2eef..aa1279e 100644
Binary files a/NecromancerHarmony.pdb and b/NecromancerHarmony.pdb differ
diff --git a/README.md b/README.md
index c8cabf7..f31cd2e 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
*English version below — scroll past the Russian half.*
-**Версия 1.4.1** — для 7 Days to Die 3.2 (с 3.3 experimental не совместима). Автор: Alex Cube.
+**Версия 1.4.2** — для 7 Days to Die 3.2 (с 3.3 experimental не совместима и сама об этом скажет). Автор: Alex Cube.
- Страница мода: https://www.alexcube.ru/7-days-to-die-moi-mody/kniga-nekromanta-necromancer-s-tome/
- Nexus Mods: https://www.nexusmods.com/7daystodie/mods/12547
@@ -150,7 +150,7 @@ HUD за этим следят два индикатора: череп в ста
**13 языков полностью:** русский, английский, немецкий, испанский, французский, итальянский,
японский, корейский, польский, португальский (Бразилия), турецкий, китайский упрощённый и
-традиционный. Все 149 ключей `Config/Localization.csv` заполнены, пустых ячеек нет.
+традиционный. Все 165 ключей `Config/Localization.csv` заполнены, пустых ячеек нет.
## Установка
@@ -162,6 +162,8 @@ HUD за этим следят два индикатора: череп в ста
## Статус
+Версия 1.4.2 — **защита от несовместимой версии игры**. Если мод запущен не на той версии игры, под которую собран (эта сборка — для 3.2.x), в главном меню появляется окно «мод несовместим, обновите» с единственной кнопкой «Выйти из игры». Если игрок всё же откроет мир, сохранённое Пространственное хранилище не разбирается и записывается обратно байт в байт — вещи дождутся правильной сборки мода.
+
Версия 1.4.1 — **исправлено падение FPS**. Красный труп «Жертвы» оставлял в памяти свои текстуры и материалы и не отдавал их до перезапуска игры: за пару часов набегали гигабайты видеопамяти, и FPS падал в разы. Теперь всё убирается вместе с трупом, а сам труп лежит 5 минут, а не час. Урон ножа пересчитывается раз в секунду, а не 10 раз.
**Мод рассчитан на игру 3.2 и НЕ совместим с 3.3 experimental**: там перестаёт сохраняться содержимое Пространственного хранилища и ломается Пирамида Ереси. Версия под 3.3 выйдет отдельно.
@@ -248,7 +250,7 @@ HUD за этим следят два индикатора: череп в ста
# Necromancer's Tome (English)
-**Version 1.4.1** - for 7 Days to Die 3.2 (not compatible with 3.3 experimental). By Alex Cube.
+**Version 1.4.2** - for 7 Days to Die 3.2 (not compatible with 3.3 experimental, and says so itself). By Alex Cube.
- Mod page: https://www.alexcube.ru/7-days-to-die-moi-mody/kniga-nekromanta-necromancer-s-tome/
- Nexus Mods: https://www.nexusmods.com/7daystodie/mods/12547
@@ -396,7 +398,7 @@ deliberately not retold here: a README gets read before the playthrough.
## Localization
**13 languages, complete:** Russian, English, German, Spanish, French, Italian, Japanese, Korean,
-Polish, Brazilian Portuguese, Turkish, Simplified and Traditional Chinese. All 149 keys in
+Polish, Brazilian Portuguese, Turkish, Simplified and Traditional Chinese. All 165 keys in
`Config/Localization.csv` are filled in, with no empty cells.
## Installation
@@ -410,6 +412,8 @@ story scenes play without stopping time.
## Status
+Version 1.4.2 - **protection against an incompatible game version**. If the mod runs on a game version it was not built for (this build is for 3.2.x), the main menu shows a "mod is incompatible, please update" window with a single "Quit game" button. And if a world does get opened, the saved Spatial Vault is not parsed but written back byte for byte - its items wait for the right build of the mod.
+
Version 1.4.1 - **the FPS drop is fixed**. A red Victim corpse left its textures and materials in memory and never gave them back until the game was restarted: within a couple of hours that was gigabytes of video memory, and the frame rate fell several times over. Everything is now cleaned up with the corpse, and the corpse itself stays for 5 minutes instead of an hour. The knife's damage is refreshed once a second instead of 10 times.
**The mod is built for game version 3.2 and is NOT compatible with 3.3 experimental**: there the Spatial Vault stops saving its contents and the Pyramid of Heresy breaks. A 3.3 version will come separately.