Декомпиляция пакетов Killing Floor через UT Package Tool: ошибки и исправления
Декомпилируйте, чтобы учиться, чинить заброшенные моды или с разрешения автора. В любой публикации указывайте автора оригинала.
Это адаптация Steam-руководства kazachidla «Декомпиляция заскриптованного кода unreal engine 2.5 (Killing floor)». В нём используется UT Package Tool 2.0 beta 5 (UTPT), который восстанавливает код из пакетов с вырезанным исходным текстом. Ручной работы с UTPT гораздо больше, чем с UE Explorer, зато, по опыту автора, он надёжнее со скобками и границами блоков. Вторая половина страницы — список ошибок компиляции, которые оставляет UTPT, и как исправить каждую.
Инструменты
- UT Package Tool 2.0 beta 5 — декомпилятор.
- Hex-редактор (в руководстве HexEdit) — чтобы UTPT принял пакет KF.
- UnCodeX (по желанию) — просмотр и поиск по исходникам скриптов KF, которые ставятся вместе с Killing Floor SDK. Пригодится, чтобы найти правильные имена типов.
Шаг 1. Экспорт defaultproperties через ucc
UTPT декомпилирует значения по умолчанию с ошибками, поэтому сначала возьмите их у штатного экспортёра игры. Создайте .bat в System:
ucc batchexport Mut.u class uc ..\Mut\Classes
pause
Вместо Mut — имя пакета. Запустите и скопируйте полученные .uc в отдельную папку, например Default. Из них понадобятся только блоки defaultproperties.
Шаг 2. Декомпиляция в UTPT
- Откройте исходный
.uв hex-редакторе и замените первый байтC2наC1. UTPT не знает Killing Floor и после этого принимает файл за пакет Unreal Tournament 2004. Сохраните. - Откройте исправленный файл в UTPT. Во вкладке Export Tree щёлкните правой кнопкой по любому классу и выберите Full Collapse.
- В File → Options включите Add line offsets to decompiled code. Смещения понадобятся, чтобы расставить метки.
- Для каждого класса: правая кнопка → Decompile, затем Save в правой панели. Сохраняйте в
<Пакет>\Classesв корне игры, напримерMut\Classes. - Откройте каждый сохранённый
.ucи заменитеdefaultpropertiesблоком из папкиDefault. - Почистите объявление класса. UTPT добавляет спецификаторы, которые компилятор не принимает:
class AntiBlocker extends Mutator
Localized
HideCategories(Movement,Collision,Lighting,LightColor,Karma,Force);
превращается в
class AntiBlocker extends Mutator;
Затем компилируйте через ucc make и разбирайте ошибки ниже.
Ошибки компиляции после UTPT
Unrecognized type 'FPerksListType'
UTPT объявляет переменную раньше структуры, которую она использует:
var array<FPerksListType> CachePerks;
struct FPerksListType
{
var Class<KFpubVet> PerkClass;
var byte CurrentLevel;
};
Перенесите все struct в начало файла, сразу после объявления класса, до переменных, которые их используют.
Missing '>' in 'class limitor'
local array<Class<KFpubVet>> ca;
Парсер UE2 читает >> как оператор сдвига. Добавьте пробел:
local array<Class<KFpubVet> > ca;
Unrecognized type 'ServerResponseLine'
UTPT теряет внешний класс у типов, объявленных в других классах:
function GetServerDetails (out ServerResponseLine ServerState)
Укажите его:
function GetServerDetails (out GameInfo.ServerResponseLine ServerState)
Ищите тип в UnCodeX, чтобы найти, где он объявлен. Частые случаи: PlayerRecord → xUtil.PlayerRecord, ServerFavorite → ExtendedConsole.ServerFavorite, AllowWeaponInTrader → Static.AllowWeaponInTrader. Недостающий Static. у статических вызовов придётся дописывать часто.
'Extends' not allowed here: state overrides version in parent class
auto state PendingMatch extends PendingMatch
Имя состояния продублировалось. Уберите extends:
auto state PendingMatch
Specified type modifiers not allowed here
function bool ButtonClicked (export editinlineuse GUIComponent Sender)
local export editinlineuse GUIList List;
Удалите export editinlineuse из параметров и локальных переменных:
function bool ButtonClicked (GUIComponent Sender)
local GUIList List;
Type mismatch in 'new'
C = new (None,Class'KFpubLC');
Правильный синтаксис:
C = new (None) Class'KFpubLC';
Label 'JL0021' not found in this block of code
UTPT превращает все циклы в goto, но почти никогда не ставит метки, а если ставит, то обычно неправильно. Тут нужны смещения, включённые на шаге 2: имя метки — это смещение цели, так что goto JL0021 прыгает на строку, у которой смещение заканчивается на 0x0021.
i = PC.Player.LocalInteractions.Length - 1; // 0x00000019 : 0x0000
JL0021: if ( i >= 0 ) // 0x00000032 : 0x0021
{
if ( PC.Player.LocalInteractions[i].Class == Default.Class )
return;
--i;
goto JL0021; // 0x00000066 : 0x0065
}
Метка должна быть в той же функции, что и goto. Ещё лучше переписать это в исходный цикл (while (i >= 0)).
Missing 'Reliable' or 'Unreliable'
un?reliable if ( (Role == 4) && bNetOwner )
UTPT не смог определить, что здесь было. reliable гарантирует доставку, unreliable может потеряться при плохом соединении. В модах условия репликации чаще всего reliable. Если у стандартного родительского класса есть такой же блок, возьмите слово оттуда.
'If' is not allowed here (числа вместо имён enum)
if ( Level.NetMode == 3 )
UTPT заменяет имена enum их числами. Иногда компилятор это принимает, но не везде. Верните имена: Level.NetMode == NM_Client. Таблицы самых частых enum:
| Значение | NetMode | Role | ClientState (оружие) | ClientGrenadeState |
|---|---|---|---|---|
| 0 | NM_Standalone | ROLE_None | WS_None | GN_None |
| 1 | NM_DedicatedServer | ROLE_DumbProxy | WS_Hidden | GN_TempDown |
| 2 | NM_ListenServer | ROLE_SimulatedProxy | WS_BringUp | GN_BringUp |
| 3 | NM_Client | ROLE_AutonomousProxy | WS_PutDown | |
| 4 | ROLE_Authority | WS_ReadyToFire |
| Значение | Physics | Значение | Physics |
|---|---|---|---|
| 0 | PHYS_None | 9 | PHYS_Spider |
| 1 | PHYS_Walking | 10 | PHYS_Trailer |
| 2 | PHYS_Falling | 11 | PHYS_Ladder |
| 3 | PHYS_Swimming | 12 | PHYS_RootMotion |
| 4 | PHYS_Flying | 13 | PHYS_Karma |
| 5 | PHYS_Rotating | 14 | PHYS_KarmaRagDoll |
| 6 | PHYS_Projectile | 15 | PHYS_Hovering |
| 7 | PHYS_Interpolating | 16 | PHYS_CinMotion |
| 8 | PHYS_MovingBrush |
| Значение | Слот звука (PlayOwnedSound) | DrawType |
|---|---|---|
| 0 | SLOT_None | DT_None |
| 1 | SLOT_Misc | DT_Sprite |
| 2 | SLOT_Pain | DT_Mesh |
| 3 | SLOT_Interact | DT_Brush |
| 4 | SLOT_Ambient | DT_RopeSprite |
| 5 | SLOT_Talk | DT_VerticalSprite |
| 6 | SLOT_Interface | DT_Terraform |
| 7 | DT_SpriteAnimOnce | |
| 8 | DT_StaticMesh | |
| 9 | DT_DrawType | |
| 10 | DT_Particle | |
| 11 | DT_AntiPortal | |
| 12 | DT_FluidSurface |
Большинство из них объявлено в Actor.uc; если сомневаетесь, смотрите исходники.
'If' is not allowed here (нет метки в состоянии)
У той же ошибки есть вторая причина. UTPT не ставит метки в коде состояний:
auto state RepSetup
{
if ( Level.NetMode == NM_Client )
{
stop
}
Коду состояния нужна метка Begin:, а после stop нужна точка с запятой:
auto state RepSetup
{
Begin:
if ( Level.NetMode == NM_Client )
stop;
Проверьте каждое состояние на пропущенные метки. Если этого не сделать, пакет может даже скомпилироваться без предупреждений, но код состояния будет работать не так, как задумано. В руководстве из-за этого мобы «тупили»: ИИ мобов во многом держится на коде состояний. Большинство состояний в модах — переписанные стандартные, так что сверяйтесь со стандартным классом.
goto в коде состояний
goto на числовую метку в коде состояний не работает, даже если метки расставлены. Перепишите переходы в while и for. Декомпилировано (не компилируется):
SendIndex = 0;
if ( SendIndex < ShopInventory.Length )
{
ClientReceiveWeapon(SendIndex, ShopInventory[SendIndex].PC, ShopInventory[SendIndex].CatNum);
Sleep(0.1);
++SendIndex;
goto JL007C;
}
Переписано:
for( SendIndex=0; SendIndex<ShopInventory.Length; ++SendIndex )
{
ClientReceiveWeapon(SendIndex, ShopInventory[SendIndex].PC, ShopInventory[SendIndex].CatNum);
Sleep(0.1f);
}
В полном примере руководства так восстанавливается ClientPerkRepLink.RepSetup из ServerPerks. Если декомпилированный код похож на стандартный или известный класс, используйте оригинал как образец.
Сколько это занимает
Отдельное оружие, моба или скин можно сделать быстро. Целый серверный пакет может занять от нескольких дней до недели внимательной работы. В стандартных исходниках есть почти все переменные и функции, которые придётся искать.
Анимации — отдельная проблема. Если автор упаковал меши в большой .ukx с вырезанной информацией о мешах, распаковать его нельзя, и автор руководства способа не знал. Можно поставлять весь пак целиком или попробовать удалить лишнее в KF SDK, но SDK не даст удалить меш, на который ссылается класс из того же пакета.