Daniel Seiller
7afdfb5869
- Add notes folder with MDBook documentation (the NOTES.md file was getting kind of large) - Add rz_analyze.py, does the same a r2_analyze.py just with Rizin instead of radare2 so the project can be loaded in Cutter (*and* it's faster) - Add Scrap.rzdb, Rizin database for the Scrap.exe executable - Add Scrapper_rs, Rust version of .packed extractor and repacker - replace helplib.txt with helplib.md - add Py_Docs folder which contains generated documentation for the binary python modules built into Scrap.exe
372 lines
No EOL
11 KiB
Markdown
372 lines
No EOL
11 KiB
Markdown
# Infos
|
|
|
|
- Engine: ScrapEngine/Mercury Engine
|
|
- Ingame Scripting Language: Python 1.5.2
|
|
- Interesting memory locations and functions are noted in `config.yml`
|
|
|
|
# Launch options:
|
|
|
|
- `-console`: open external console window on start
|
|
- `-wideWindow`: start game in widescreen mode
|
|
- `-dedicated`: start in mutliplayer dedicated server mode (needs to be used with `-server`)
|
|
- `-server`: start in multiplayer server mode
|
|
|
|
|
|
## Ingame-Console (Ctrl+\^ or right click on titlebar and select "switch console") (Handler@`0x402190`):
|
|
|
|
* `<Command>`: Try to evaluate Command as Python expression
|
|
* `:<Var>`: Get Game Engine Global Variable
|
|
* `:<Var> <Val>`: Set Game Engine Global Variable
|
|
* `?`: Show all Global Variable
|
|
* `?<String>`: Show all Global Variable matching `<String>`
|
|
* `/<command>`: Run Command defined in `QuickConsole.py`: `import quickconsole;quickconsole.%s()`
|
|
* `/<command> <arg>,<arg>`: Run function in `QuickConsole.py` with argument(s) `import quickconsole;quickconsole.%s(%s)`
|
|
|
|
## External Console (Scenegraph Debugging?) (Handler @ `0x5f9520`):
|
|
|
|
* `listar luces` List lights in scene
|
|
* `listar` list models in scene
|
|
* `arbol <model_name>` show details for model
|
|
* `mem` (doesn't do anything?)
|
|
* `ver uniones`
|
|
* Easter Eggs:
|
|
* `imbecil`
|
|
* `idiota`
|
|
* `capullo`
|
|
|
|
|
|
## Other interesting Memory Addresses
|
|
|
|
- `0x852914`: D3D8-Device pointer
|
|
- `0x7FCC00`: number of opened `.packed` files
|
|
- `0x84cb64`: pointer to console command handler
|
|
- `0x7fac84`: pointer to C++ callback list structure
|
|
- `0x80b2cc`: pointer to ActionClassList (???)
|
|
- `0x807a20`: pointer to SScorer (ingame GUI/Menu/Text system) structure (???)
|
|
- `0x80a398`: pointer to SoundSystem (???)
|
|
- `0x8b18f0`: pointer to Models Data (can be dumped using scenegraph debugging console)
|
|
- `0x8b18f4`: pointer to Scenes Data (can be dumped using scenegraph debugging console)
|
|
- `0x8b18f8`: pointer to active Models Data (can be dumped using scenegraph debugging console)
|
|
|
|
## Hash-function used in Hash-Tables
|
|
|
|
```c
|
|
unsigned long hash(const unsigned char *s)
|
|
{
|
|
unsigned long h = 0, high;
|
|
while ( *s )
|
|
{
|
|
h = ( h << 4 ) + *s++;
|
|
if ( high = h & 0xF0000000 )
|
|
h ^= high >> 24;
|
|
h &= ~high;
|
|
}
|
|
return h;
|
|
}
|
|
```
|
|
|
|
|
|
## Other Functions:
|
|
|
|
Check `config.yml` for full list
|
|
|
|
## File Index struct @ `0x7fcbec`
|
|
|
|
```cpp
|
|
struct FileEntry {
|
|
uint32_t offset;
|
|
uint32_t size;
|
|
uint32_t unk; // seems to always be 0xBADFOO1
|
|
unsigned char* path;
|
|
FileEntry* next; // next entry in hashtable chain
|
|
}
|
|
|
|
struct FileIDX {
|
|
uint32_t size;
|
|
FileEntry** entries;
|
|
};
|
|
```
|
|
|
|
## Packed Index struct (array of 0x80 entries @ `0x7fc1b0`)
|
|
```cpp
|
|
struct PackedIDX {
|
|
void** VMT;
|
|
unsigned char* filename;
|
|
uint32_t locked; // not sure
|
|
void* data;
|
|
uint32_t seek;
|
|
}
|
|
```
|
|
|
|
## C(++)-Callbacks @ `0x7fac84`
|
|
|
|
Structure:
|
|
|
|
```cpp
|
|
struct CPP_Callback {
|
|
const char* name;
|
|
void* func;
|
|
CPP_Callback* left;
|
|
CPP_Callback* right;
|
|
}
|
|
```
|
|
|
|
|
|
## Game engine Variables Hashtable @ `0x7fbe50`
|
|
|
|
## Game engine Variables @ `0x7fbe4c`
|
|
|
|
Structure:
|
|
|
|
```cpp
|
|
struct GameVar {
|
|
GameVar* next;
|
|
const char* name;
|
|
const char* desc;
|
|
uint8_t subtype;
|
|
uint8_t type;
|
|
uint16_t unk;
|
|
void* value;
|
|
void* def_value;
|
|
}
|
|
```
|
|
|
|
Types
|
|
|
|
Value | Type
|
|
----- | ---------------
|
|
`0x1` | const char*
|
|
`0x2` | int32_t
|
|
`0x3` | List of Defines
|
|
`0x4` | float
|
|
`0x5` | function
|
|
`0x6` | Script function
|
|
|
|
## Game World/State Pointer @ `0x7fe944`
|
|
|
|
Points to World struct
|
|
|
|
Offset | Type | Description
|
|
------ | ------------------------ | --------------------------------------
|
|
0x0000 | `void**` | Virtual Method Table
|
|
0x0004 | `uint32_t` | Size of Entity Hashtable
|
|
0x0008 | `void**` | Pointer to Entity Hashtable
|
|
0x00B0 | `??` | Pointer to Ground Object (?)
|
|
0x0288 | `pyEntity*` | UsrEntity[0]
|
|
0x028C | `pyEntity*` | UsrEntity[1]
|
|
0x0290 | `pyEntity*` | UsrEntity[2]
|
|
0x0294 | `pyEntity*` | UsrEntity[3]
|
|
0x02B8 | `uint32_t` | Number of entity lists
|
|
0x02BC | `void**` | Pointer to entity list Hashtable
|
|
0x0330 | `float[3]` | Time (why 3 times?)
|
|
0x1C6C | `float` | Alarm level
|
|
0x1C68 | `float` | Alarm Grow Level
|
|
0x2158 | `float` | Used in `World_Init`
|
|
0x2170 | `???` | Used in `World_Init`
|
|
0x2180 | `float` | Used in `World_Init`
|
|
0x2188 | `void*` | Used in `World_Init`
|
|
0x218C | `void*` | Used in `World_Init`
|
|
0x2190 | `float` | Used in `World_Init`
|
|
0x2198 | `void*` | Used in `World_Init`
|
|
0x219C | `void*` | Used in `World_Init`
|
|
0x21A0 | `void**` | Used in `World_Init` (VTable pointer?)
|
|
0x21B4 | `void**` | Used in `World_Init` (VTable pointer?)
|
|
0x21C8 | `???` | Used in `World_Init`
|
|
0x2204 | `uint32_t` or `uint16_t` | Used in `World_Init`
|
|
0x2230 | `float` | Used in `World_Init`
|
|
0x2238 | `???` | Used in `World_Init`
|
|
0x2254 | `float` | Used in `World_Init`
|
|
|
|
## cPyEntity structure
|
|
|
|
Offset | Type | Description
|
|
------ | -------- | --------------------
|
|
0x0000 | `void**` | Virtual Method Table
|
|
0x0004 | `char*` | Name
|
|
0x0008 | `void*` | ???
|
|
|
|
|
|
## Entity Hash Table
|
|
|
|
Hash-function used: [PJW](https://en.wikipedia.org/wiki/PJW_hash_function) (Same parameters as the example implementation)
|
|
|
|
Entry format:
|
|
|
|
```cpp
|
|
struct HT_Entry {
|
|
void* data;
|
|
const char* key;
|
|
HT_Entry* next;
|
|
}
|
|
```
|
|
|
|
Data format:
|
|
|
|
Offset | Type | Description
|
|
------ | ------------- | ------------------------
|
|
0x0 | `void**` | Virtual Method Table (?)
|
|
0x4 | `const char*` | name as string
|
|
0x14 | `void*` | pointer to self (why?)
|
|
0x28 | `float[3]` | Position in Game World
|
|
|
|
## EntityList Hash Table
|
|
|
|
Attributes:
|
|
- `Near`
|
|
- `First`
|
|
- `Num`
|
|
- `OnDeath`
|
|
- `OnDamage`
|
|
|
|
# Netplay protocol (WIP)
|
|
|
|
Game Info Packet
|
|
|
|
```
|
|
Server 'B':FZ (0/10) Ver 1.0 at 192.168.99.1:28086
|
|
[0-3] header/ID?
|
|
[4-5] port (16-bit)
|
|
[6-7] max_players (16-bit)
|
|
[8-9] curr_player (16-bit)
|
|
[10-x] server name (char*)
|
|
|
|
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
|
|
0019fdc0 ba ce 00 01 b6 6d 0a 00 00 00 42 00 30 fe 19 00 .....m....B.0...
|
|
0019fdd0 ff ff ff ff 27 2b b3 9b c7 3e bb 00 9c af 29 00 ....'+...>....).
|
|
0019fde0 db 69 00 00 00 00 00 00 00 00 44 65 61 74 68 4d .i........DeathM
|
|
0019fdf0 61 74 63 68 00 00 00 00 ff ff 46 5a 00 4a 91 f0 atch......FZ.J..
|
|
0019fe00 92 8b 57 4e 7f 00 00 00 10 21 fe 38 0d ae 00 00 ..WN.....!.8....
|
|
0019fe10 f0 ce f3 36 a0 e8 0b 77 a0 e8 ...6...w..
|
|
```
|
|
|
|
|
|
Player Join Packet
|
|
|
|
```
|
|
[0-3] header/ID?
|
|
[6-x] Player name
|
|
|
|
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
|
|
09c9dfe8 7f 47 00 00 00 0e 55 6e 6e 61 6d 65 64 20 50 6c .G....Unnamed Pl
|
|
09c9dff8 61 79 65 72 06 53 42 6f 73 73 31 b9 00 07 50 5f ayer.SBoss1...P_
|
|
09c9e008 42 65 74 74 79 06 4d 42 4f 53 53 31 06 4d 42 4f Betty.MBOSS1.MBO
|
|
09c9e018 53 53 31 00 00 10 30 2c 31 35 2c 30 2c 30 2c 31 SS1...0,15,0,0,1
|
|
09c9e028 35 2c 31 35 2c 31 02 00 00 00 5,15,1....
|
|
```
|
|
|
|
Message | Description
|
|
---------------------------------------- | -----------------------------------------------------------------
|
|
`5c68625c32383230395c73637261706c616e64` | "Scrapland Server" announcement broadcast (`\hb\28209\scrapland`)
|
|
`7f01000007` | Retrieve Game info
|
|
`48423d35323932322c3235363a323830383600` | Connection Information (`HB=52922,256:28086`)
|
|
|
|
# [Notes](NOTES.md)
|
|
|
|
## File Formats
|
|
|
|
File Extension | Description | Chunked
|
|
-------------- | ------------------------ | -------
|
|
.packed | Game Data Archive | n
|
|
.cm3 | Animation file | y
|
|
.sm3 | 3d model file | y
|
|
.dum | Dummy (map object) file | y
|
|
.pth | AI Path | n
|
|
.emi | Emission maps/Materials? | y
|
|
.amc | Collision Data | y
|
|
.ini | Configuration | n
|
|
.txa | Texture Animation Config | n
|
|
|
|
- [Chunked](file_formats/chunked.md)
|
|
- [Packed](file_formats/packed.md)
|
|
- [AI Pathfinding Graph](file_formats/ai_path.md)
|
|
|
|
|
|
# Virtual Method Tables:
|
|
|
|
check `r2_analyze.py` for full list
|
|
|
|
## Loading Custom Content (not really working)
|
|
|
|
1. Create a folder `mods`
|
|
2. Drop a `*.packed` file into it
|
|
3. Change `Scrap.cfg` as follows
|
|
1. Add `ModPathName = mods`
|
|
2. Add `ModFileName = <filename>`
|
|
|
|
## Interesting file inside `Data.packed`
|
|
|
|
* `m3d.ini`: Rendering Engine Configuration
|
|
* `scripts/`: Game Engine Scripts
|
|
|
|
|
|
# How to enable External Console:
|
|
|
|
1. Right click on the title bar (in windowed mode) and click "Switch Console"
|
|
|
|
# How to enable Scenegraph debugging console
|
|
|
|
1. extract `Data.packed`
|
|
2. in m3d.ini uncomment (remove `;`) `ConsolaWnd` (GUI Console) and/or `ConsolaTxt` (Text Console) and set the value to `SI`
|
|
3. repack `Data.packed`
|
|
|
|
# Misc. Interesting things
|
|
|
|
- `sys.path` contains "./lib" so you can import your own Python Modules
|
|
- Games crashes when starting a multiplayer server and feeding it random UDP data
|
|
|
|
# Code Snippets
|
|
|
|
## [Kaitai Struct](http://kaitai.io/) Parser for .packed files
|
|
|
|
```yaml
|
|
meta:
|
|
id: packed
|
|
application: Scrapland
|
|
file-extension: packed
|
|
endian: le
|
|
xref: http://wiki.xentax.com/index.php/Scrapland_PACKED
|
|
license: MIT
|
|
encoding: UTF-8
|
|
seq:
|
|
- id: magic
|
|
contents: BFPK
|
|
doc: File Magic
|
|
- id: version
|
|
contents: [0,0,0,0]
|
|
doc: File Version
|
|
- id: num_files
|
|
type: u4
|
|
doc: Number of files
|
|
- id: files
|
|
type: file_entry
|
|
repeat: expr
|
|
repeat-expr: num_files
|
|
doc: Entry for each file
|
|
types:
|
|
file_entry:
|
|
seq:
|
|
- id: path_len
|
|
type: u4
|
|
doc: Length of file path
|
|
- id: path
|
|
type: str
|
|
size: path_len
|
|
doc: File path
|
|
- id: size
|
|
type: u4
|
|
doc: File size
|
|
- id: offset
|
|
type: u4
|
|
doc: Absoulte File offset
|
|
instances:
|
|
data:
|
|
pos: offset
|
|
size: size
|
|
```
|
|
|
|
# TODO:
|
|
|
|
- Figure out how C++ Callbacks work
|
|
- Figure out SM3 (Models), CM3 (Animations) file formats
|
|
- Figure out rest of World structure
|
|
- Figure out rest of Entity structure |