Skip to content

Glacier: How to read decompiled PRP

DronCode edited this page Jan 22, 2022 · 1 revision

About this document

This document describes how to read decompiled PRP file as JSON.

Decompiler tool is here.

JSON specification is here.

Notice:

1. Make backups
2. Be careful and do not remove any data! 
3. Don't change any instruction types if you don't know what you doing. 
4. Be careful when you add a script or change its name. It could crash the game in some cases.
5. Make backups
6. Do not re-create the whole ZIP file. Use windows explorer and replace PRP files when you want to check your changes.
7. MAKE BACKUPS!!!11111

About PRP

In Glacier (gen. 1) the scene was divided by few files:

  • GMS - game scene definition
  • BUF - static buffer (data trashcan/unstructured)
  • PRM - primitives (models/meshes)
  • PRP - properties

So, the PRP file is a classic 'execution machine' pattern. There are 4 sections:

  • Header (general information about the file)
  • Token table (strings)
  • ZDefines (common properties of level)
  • Properties list

ZDefines

Each scene contains at least 2 scene definitions:

  1. ScriptCModule: this is the name of DLL which contains all in-game scripts
  2. PostFilterPalettes: array of int32. Unknown what it means.

But PRP can contain much more definitions. And you may add/change your own definitions, it will not crash the game if you do it properly.

Properties list

This is the main part of the PRP file. It contains instruction bytes and each instruction can be explained as 1 byte with op-code and a few next bytes as its own data. More details about PRP is here

Be aware of instructions order, it's very important! Don't change it!

Decompiled PRP

When you understand what is PRP, ZDefines & properties list you can work with PRP file in depth.

First of all, you need to decompile your first PRP. Let it be Hideout/Loader_Sequence.ZIP/Scenes/Loader_Sequence.PRP.

Use prptool.py to decompile it: python prptool.py Loader_Sequence.PRP Loader_Sequence.JSON decompile.

Now, you are ready to open this file in your editor (Visual Studio Code, notepad, as you like).

Here you can see 4 global sections:

  • is_raw - flag from the original PRP. Do not change it, it must be false always (let me know if it doesn't)
  • flags - mask of original PRP file. It must be 13 (0xD). Let me know if it doesn't.
  • definitions - first useful section of PRP. Here are located ZDefines from PRP.
  • properties - second useful section of PRP. Here are located instructions of PRP.

So, now you are ready to go deeper into the Glacier properties system.

Glacier properties system

Objects & world

So, we are talking about 'PRP', 'properties', 'Z-things', but what is it? Why 'Z'? Why binary files? What are types? Let's talk about that.

Glacier, like other engines, is working with OOP objects. Everything on the scene would be represented as separated objects/group of objects.

In glacier-specific talks, we can say that everything is ZGEOM. Z - is a common prefix in the glacier code style guide. There are 3 prefixes:

  • Z - Class or struct
  • C - Class
  • E - Enumeration

In most cases, we can see the 'Z' prefix.

So, we know that everything is ZGEOM. What's next?

Serialization

So, we have a world of objects. Generally, we can represent any object as a group of properties. First of all - type of object.

In managed languages (like C#, Python, YOUR_BEST_LANGUAGE) compiler/interpreter knows what type of object has and which properties exist for it. But Glacier was written in C++ and here we have RTTI but that's not enough to make your world serializable.

So, we may write our own serialization code, write bindings but it's boring. So, Glacier makes its own RTTI compiled inside executable (via defines). And uses that to deserialize everything. Our property instructions are the result of serialization. But as we can see our PRP->JSON doesn't contain any type ids, what's wrong? Yeah, this is the first hard place: type id is stored inside the GMS file.

But we know that the first object in PRP is always ZROOM, let's talk about that.

PRP declaration rules

First of all, we should remember that everything is ZGEOM. Therefore every object definition starts from PRPOpCode.BeginObject. You may say: "What the problem? Just find every BeginObject opcode and you will have all objects on the scene" and you will not be right. Let's talk about ZGEOM structure

ZGEOM structure

Every ZGEOM contain three sections:

  1. Properties - specific for own type properties (instructions) list
  2. ZEvents container - container of ZEventBase inner things
  3. Children container - container of children ZGEOM's

ZEvents

Every ZGEOM could be expanded in two ways

  1. Inherit new class of ZGEOM and add your own logic (old way)
  2. Create new ZEventBase class and add it as 'ZEvent' to any ZGEOM (new way)

The second option is more preferred and it looks like ECS (ZEventBase = Component + System)

Children container

It's just a bunch of child geoms. Nothing interesting.

Also, some types contain their own objects, and to recognize where starts new ZGEOM you need visit every instruction and save flags into your own context to know what's happening right now.

Read your first ZGEOM

We know that the first object is always ZROOM. Let's read it!

We will use this document as help.

The first instruction PRPOpCode.BeginObject is a marker to interpret that new objects begin.

Instruction PRPOpCode.StringOrArray_E with op_data = "BOUNDING_Static" means that our ZGEOM has EBoundingBox::BOUNDING_Static value.

Now we can see PRPOpCode.Array. It means that we have an array of N elements, where N is declared in the op_data section at 'length'.

This is a 'Matrix' (not a film) of the ZGEOM object. It represents the transformation of objects in the graphics world.

And next 9 op-codes will represents all entries of your array (float32[9]).

The next array represents 'Position' data (Vector3).

The next bool flag means 'Inactive' flag of ZGEOM. And the last ZGEOM's op-code is Int32 means primitive ID (index of primitive in PRM file).

So, our ZGEOM properties ended. Now, we reading op-codes from ZGROUP. Then of ZTreeGroup and the last instructions set represents ZROOM itself.

Result:

Property Type Namespace Value
BoundingBox EBoundingBox ZGEOM BOUNDING_Static
Matrix float[9] ZGEOM (0, 0, 1, 0, 1, 0, 1, 0, 0)
Position float[3] ZGEOM 0,0,0
Inactive bool ZGEOM false
PrimId int ZGEOM 0
property_40 float ZGROUP 1.0
bLightShinesIn bool ZGROUP true
bLightShinesOut bool ZGROUP true
bIsDynamicContainer bool ZTreeGroup true
bIsPrivate bool ZTreeGroup true
bIsPrivate bool ZTreeGroup false
bIsStaticContainer bool ZTreeGroup true
property_6C uint ZROOM 0
property_84 int8 ZROOM 0
property_A4 ZAUDIOREF (int) ZROOM 0
NoiseLevel ENoiseLevel ZROOM eNormal
Location ELocation ZROOM eUNDEFINED
property_12C RawData ZROOM Container { length=0 }
NotInRoomData bool ZROOM false
DisableAudiEvt bool ZROOM false
ExitOffsets int ZROOM 0
NeighborsOffset int ZROOM 0
EnvironmentLight ZGEOMREF (string) ZROOM "" (empty string)
property_134 float[3] ZROOM [0.0, 0.0, 0.0]

ZEvents: [] (no extra ZEventBase entries)

Children: [] (no children geoms)

How to make mods?

Now I'm working on a GUI tool to edit PRP & GMS but before it, you may create your own mods.

The base method is to find the thing that you want to change by primitive ID or by position (via ReHitman). If you would like to find an entity by the position you should look by an integral part of the float number (because ending may change at runtime).

Remember about actors: it's implemented via 2 geoms: parent and child. Parent geom contains position but not primitive id. The child geom contains all other data.

How to compile my mod back?

python prptool.py MyPrpFile.JSON MyPrpFile.PRP compile