Раскопки сейвов Stronghold Crusader

2026-09-27 - 2026-09-28

Details

Исходники выложены в GitLab.

Отчасти интерес к Stronghold уже привёл к созданию Каменного Венца — игры про строительство крепости прямо на шахматной доске.

С чего началось

Мы с другом много играли в Stronghold Crusader: Definitive Edition. Сейвы там постоянно ломаются, игра работает нестабильно, а после вылета нередко делает вид, что сохранения вообще нет.

Стало интересно, из чего состоит файл сохранения: список игроков, их состояние, ресурсы. В перспективе — ещё и чинить сейвы (это уже совсем другая история). Начать решил с простых замен чисел: поменять ресурсы выглядело самым понятным первым экспериментом. Дальше уже пошли замены юнитов и зданий, стены, странные объекты.

В итоге получилась небольшая утилита shctool на Go. Она читает .sav и .msv, показывает игроков, золото, популярность, живых юнитов и здания, умеет менять золото, здоровье юнитов, их владельца, владельца обычных зданий и заменять одни типы юнитов или зданий на другие.

Например, можно посмотреть армию игрока, узнать внутренний ID Лучника и превратить всех Лучников в Шутов. Практической пользы почти нет, но игра позволяет.

Что оказалось внутри

Сейв состоит не из одной большой структуры "вот состояние игры", а из множества отдельных секций. Часть сжата. Игроки лежат отдельно, юниты отдельно, здания отдельно, карта вообще живёт своей жизнью в нескольких сетках. Я ожидал каши из совсем бинарного мусора, но файл оказался относительно структурированным и подходящим для разбора.

У юнита есть запись фиксированного размера, где среди прочего лежат тип, владелец, цвет игрока, здоровье и признак того, что он жив (убитые юниты ещё как бы в игре, но их скоро удалит). У зданий похожая история.

Можно не только менять тип сущности, но и передавать юнитов и обычные здания другому игроку; у юнитов вместе с управлением меняется и цвет. Да, владелец и цвет юнита - не одно и то же. Это хорошо видно в одиночной песочнице: есть событие, когда под управление красного игрока приходят зелёные лучники.

В сейве хранится их расстановка игроков, кто ими управляет, союзы. Поэтому вместо условного "Игрок 4" shctool может показать Саладина, Крысу, Кахину или другого AI. Полный вариант имени вроде "Эмир Мудрый" берётся из архивов игры, в самом сохранении его нет. Имена живых игроков тоже не хранятся. Видимо, партию могут продолжить любая пара игроков, если у них есть одинаковый файл сейва.

Если у бота больше нет живого Лорда, в общем списке появляется пометка "убит". У одного такого игрока при этом остаются три живые курицы, находящиеся неизвестно где.

Золота в сейве чуть меньше

Первым нашлось золото, но сразу выяснилось, что отображаемые в игре ресурсы состоят из нескольких частей. В конкретном сейве значение в основной переменной оказалось на 46 меньше числа на экране: если записать туда 10000, игра покажет 10046.

Игра делает вид, что от такого редактирования защищается. Нужные переменные лежат внутри блока, сжатого не очень стандартным форматом BLAST, а рядом хранится CRC-сумма — короткая проверка, что данные не испорчены. Просто найти байты числа и заменить их не выйдет: секцию нужно распаковать, после изменения снова сжать и пересчитать контрольную сумму.

Я пробовал так же искать камень и дерево как самые часто используемые ресурсы. Похоже, с ними такая же история, но их значения обычно небольшие, в отличие от золота, поэтому точно найти нужные поля не получилось. Золота пока хватает.

Лордов может быть много

При проверке замены юнитов я решил, что гулять так гулять, и поменял Лучников сразу на Лордов. Замена на другой обычный боевой юнит наверняка работала бы нормально и ничего интересного не показала. Игра не запретила Лордов, и на карте их стало много.

Лорды занимают население. Повторные Лорды почти неуправляемые: однажды получилось отправить их в атаку, пока они ещё не успели зайти на главную башню. Потом они слепились в несколько стаков и перестали нормально ходить. Некоторые сами в замок не пошли, но зашли туда по клику и больше не спускаются.

Гибель дублированного Лорда вызывает обычную реплику "наш лорд погиб", но партия не заканчивается. Смерть сущности типа "Лорд" и поражение игрока явно не одно и то же событие. И у первого Лорда явно есть другой флаг, делающий его "тем самым", на котором всё держится.

То же самое происходит при загрузке мультиплеерного сохранения .msv, переименованного в одиночное .sav: не загружаются мультиплеерные скрипты, не подключается AI ботов, а при победе над Лордом, хоть и есть реплика "мы взяли замок", реально замок не падает, ресурсы не захватываются, войска продолжают сражаться.

Баллиста, которая вроде есть

Попытка заменить Ассасина на башенную Баллисту, ещё и стоящую на земле, дала ещё более странный результат. Баллисты появились как игровые сущности, их можно выделить рамкой. Инженеры видят их и идут занимать машину, но спрайтов нет, а инженеры в итоге внутрь не садятся.

Запись существует, часть логики узнаёт в ней осадную машину, но для полноценной Баллисты не хватает дополнительного состояния.

У Мангонели и Баллисты есть ID и среди юнитов, и среди зданий. Это не конфликт, а несовместимые наборы структур для разных типов. Простая замена обычного здания на "Баллисту-здание" закончилась падением игры. Такие объекты, видимо, собираются из нескольких частей состояния, поэтому одного правильного ID недостаточно.

Если на башне то, что не должно

Всадники и переносные щиты могут оказаться на башне, только после этого не передвигаются. Катапульты не хотят залазить ещё сильнее: исчезают вообще. При этом в файле остаются, просто игра их не отображает.

Это тоже казалось очевидной заменой: если юнит уже оказался наверху, почему бы ему не ходить по стенам. В старой версии был баг, где переносной щит в одной группе с пехотинцем мог зайти на стену по осадной башне. Нет, исправили основательно: "неправильным" юнитам запретили ходить, даже если они как-то оказались на стене или башне.

Редактирование сейва постепенно становится проверкой того, насколько хорошо игра переживает состояния, которые штатно никогда не создаёт.

Хижина не содержит людей

Я ожидал, что где-то у Хижины будет связано население или хотя бы значение, из которого игра пересчитывает лимит. Думал, что хотя бы по триггеру строительства или разрушения Хижины игра пересчитает всё, что есть на карте, и скорректирует лимит населения. На деле она просто прибавляет или отнимает число жителей дома. Поэтому замена Хижины в сейве на другое здание или наоборот не меняет лимит. Немного необычно, потому что текущее население игра постоянно пересчитывает в моменте.

Стало понятно, что население можно редактировать отдельно, но этим уже заниматься не стал.

В игре много гражданских юнитов: мирных и неуправляемых. Они не считаются незанятыми крестьянами и не входят в население. Похоже, это сущности, связанные с Хижинами, хотя как именно — пока неясно.

Алкаши тоже логически связаны с Трактиром, но не привязаны к нему состоянием игры. Будто Трактир производит их, а дальше они самостоятельно гуляют и исчезают. Есть и другие постройки с привязанным юнитом, но для него обычно нет отдельного ID.

Здания можно разделять

Амбары и Склады можно строить только рядом. Но разделённые искусственно Амбары продолжают работать. Судя по всему, Склады тоже. Сам Склад оказался сразу четырьмя разными складами: на карте видно одно большое здание, а во внутренних структурах оно представлено несколькими частями.

Похоже, "главного" Амбара или Склада нет: дальше все они (почти) равны. Это как раз понятно, потому что и обычным способом можно построить цепочку Амбаров или Складов, снести середину и дальше достраивать независимые хранилища. Единственно: при выборе места для нового Амбара игра центрует карту на первый построенный, потом второй, если первого уже нет, и т. д.

Также не проверял, но наверняка игра переварит Амбары сверх лимита в девять штук.

Стены вообще не здания

До особенностей стен я добрался практическим путём: в таблицах игры сохранился ID деревянной стены из первого Stronghold. В Crusader её штатно не построить, поэтому возник план заменить в сейве каменную стену на деревянную и посмотреть, что получится.

Сначала хотел посчитать, сколько у игрока стен. В списке зданий стен не оказалось. Не получилось даже найти "здание каменная стена", которое можно было бы заменить. Это выглядело как неверно найденный ID или... отдельный тип, не здание, о чём уже давно догадывался.

После постройки стены становятся частью карты и лежат в тайловых сетках вместе с данными о занятой клетке, владельце, лестницах и другой геометрии. С точки зрения сейва готовая стена ближе к рельефу, чем к Хижине или Казарме.

Поэтому посчитать стены в итоге удалось, но обычная команда замены зданий к ним неприменима. Сохранённый ID деревянной стены всё ещё интересен: код и часть старой механики явно не вычищены полностью. Для восстановления деревянных стен надо уже разбираться с представлением самой карты.

Что умеет shctool

Сейчас это небольшой консольный инструмент, а не универсальный редактор карты. Он умеет:

shctool -l game.sav
Номер  Имя         Золото  Популярность
1 (1)  Игрок 1     4545    100
2 (1)  Игрок 2     2628    100
3 (2)  Львица      55      65
4 (2)  Аббат       20489   59
5 (2)  Кахина (x)  -17     84
6 (2)  Эмир        1198    75
shctool -p 1 -h 50 game.sav
shctool -p 1 -uo 22:2 game.sav
shctool -p 1 -bo 77:2 game.sav

shctool -p 1 -bs 19:116 auto*
Замена зданий: Хижина (1) → Амбар (19): 7

Полный список команд и сборка лежат в README репозитория.

Зачем это вообще

Золото можно выдать себе любым трейнером. Хотя трейнер наверняка не пропишет его корректно в сохранении, и такое изменение не получится использовать в мультиплеере. Небольшой практический смысл: докинуть тыщёнку стартового золота для совсем адских карт кампании, требующих десятка перезапусков, чтобы просто выжить дольше пяти минут. Постараемся не пользоваться даже этим и проходить как задумано.

Интереснее смотреть, как игра принимает состояния, невозможные обычным способом. Например, размещение щита на башне казалось логичным, но "нельзя". Посмотрел, что будет, если всё же запихнуть щит через замену Лучников, уже сидящих на башне. Старые стратегии хорошо подходят для таких раскопок. В них десятилетиями копятся старые ID, исключения, решения разработчиков, необходимые им в моменте, но непонятные через 20 лет таким археологам, как я.

shctool пока даёт удобный вход в эти данные — простой инструмент типа лопаты. Сильно дальше пока разбирать не планировал, но всё может быть, если появятся необычные и потенциально рабочие идеи для проверки.

Image