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
@@ -89,6 +89,37 @@ namespace NecromancerTome
|
||||
public class VaultSlot
|
||||
{
|
||||
public Bag Bag;
|
||||
|
||||
/// <summary>Байты хранилища, которые эта сборка не прочитала: версия игры не та, под
|
||||
/// которую собран мод, или разбор упал. Пишутся обратно в файл байт в байт.</summary>
|
||||
public byte[] RawPayload;
|
||||
}
|
||||
|
||||
/// <summary>ХРАНИЛИЩЕ НЕ ДОЛЖНО ТЕРЯТЬСЯ НА ЧУЖОЙ ВЕРСИИ ИГРЫ, 1.4.2 (2026-09-24). В 1.4.1 на
|
||||
/// игре 3.3 разбор хранилища падал, и при сохранении мод писал вместо него ПУСТОЙ маркер -
|
||||
/// вещи пропадали навсегда. Теперь непрочитанные байты проходят всю цепочку сохранения
|
||||
/// нетронутыми: Read кладёт их в слот файла, ToPlayer переносит к игроку (сюда), FromPlayer
|
||||
/// отдаёт новому файлу (игра на каждом сохранении создаёт НОВЫЙ PlayerDataFile), Write
|
||||
/// пишет их как есть. Сборка под нужную версию потом прочтёт их обычным путём.
|
||||
///
|
||||
/// Ключ - entityId. Очищается при каждом открытии главного меню (GameVersionGuard.cs), чтобы
|
||||
/// байты одного мира не перешли к персонажу другого.
|
||||
///
|
||||
/// Пока байты здесь, хранилище не открывается и не пополняется (SpatialVaultPatch.OpenVault,
|
||||
/// SpatialVaultPickup.Begin): вещи, положенные в новое пустое хранилище, всё равно не
|
||||
/// сохранились бы - запись отдаёт приоритет нетронутым байтам.</summary>
|
||||
public static readonly System.Collections.Generic.Dictionary<int, byte[]> UnreadableByEntity =
|
||||
new System.Collections.Generic.Dictionary<int, byte[]>();
|
||||
|
||||
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 });
|
||||
}
|
||||
|
||||
/// <summary>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);
|
||||
|
||||
Reference in New Issue
Block a user