Мутаторы и UnrealScript

Декомпиляция пакетов 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

  1. Откройте исходный .u в hex-редакторе и замените первый байт C2 на C1. UTPT не знает Killing Floor и после этого принимает файл за пакет Unreal Tournament 2004. Сохраните.
  2. Откройте исправленный файл в UTPT. Во вкладке Export Tree щёлкните правой кнопкой по любому классу и выберите Full Collapse.
  3. В File → Options включите Add line offsets to decompiled code. Смещения понадобятся, чтобы расставить метки.
  4. Для каждого класса: правая кнопка → Decompile, затем Save в правой панели. Сохраняйте в <Пакет>\Classes в корне игры, например Mut\Classes.
  5. Откройте каждый сохранённый .uc и замените defaultproperties блоком из папки Default.
  6. Почистите объявление класса. 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:

ЗначениеNetModeRoleClientState (оружие)ClientGrenadeState
0NM_StandaloneROLE_NoneWS_NoneGN_None
1NM_DedicatedServerROLE_DumbProxyWS_HiddenGN_TempDown
2NM_ListenServerROLE_SimulatedProxyWS_BringUpGN_BringUp
3NM_ClientROLE_AutonomousProxyWS_PutDown
4ROLE_AuthorityWS_ReadyToFire
ЗначениеPhysicsЗначениеPhysics
0PHYS_None9PHYS_Spider
1PHYS_Walking10PHYS_Trailer
2PHYS_Falling11PHYS_Ladder
3PHYS_Swimming12PHYS_RootMotion
4PHYS_Flying13PHYS_Karma
5PHYS_Rotating14PHYS_KarmaRagDoll
6PHYS_Projectile15PHYS_Hovering
7PHYS_Interpolating16PHYS_CinMotion
8PHYS_MovingBrush
ЗначениеСлот звука (PlayOwnedSound)DrawType
0SLOT_NoneDT_None
1SLOT_MiscDT_Sprite
2SLOT_PainDT_Mesh
3SLOT_InteractDT_Brush
4SLOT_AmbientDT_RopeSprite
5SLOT_TalkDT_VerticalSprite
6SLOT_InterfaceDT_Terraform
7DT_SpriteAnimOnce
8DT_StaticMesh
9DT_DrawType
10DT_Particle
11DT_AntiPortal
12DT_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 не даст удалить меш, на который ссылается класс из того же пакета.