Lots of changes, expand to read

- 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
This commit is contained in:
Daniel S. 2021-01-20 23:53:14 +01:00
parent 43c01e81d2
commit 7afdfb5869
50 changed files with 483086 additions and 1709 deletions

6
.gitignore vendored
View file

@ -261,9 +261,13 @@ __pycache__/
*.pyc
.history
**/.history
ScrapHacks/build/*
ScrapHacks/src/D3D8_VMT.hpp
.vscode/c_cpp_properties.json
tools/*.log
frida/*.mp
frida/*.mp
frida/dump.mp.xz
terms.py
hist_restore.py

121
NOTES.md
View file

@ -11,7 +11,6 @@
- `-dedicated`: start in mutliplayer dedicated server mode (needs to be used with `-server`)
- `-server`: start in multiplayer server mode
# Functions identified:
## Ingame-Console (Ctrl+\^ or right click on titlebar and select "switch console") (Handler@`0x402190`):
@ -134,56 +133,57 @@ struct GameVar {
Types
| Value | Type |
|-------|-----------------|
| `0x1` | const char* |
| `0x2` | int32_t |
| `0x3` | List of Defines |
| `0x4` | float |
| `0x5` | function |
| `0x6` | Script function |
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 |
| 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` |
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*` | ??? |
Offset | Type | Description
------ | -------- | --------------------
0x0000 | `void**` | Virtual Method Table
0x0004 | `char*` | Name
0x0008 | `void*` | ???
## Entity Hash Table
@ -202,12 +202,12 @@ struct HT_Entry {
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 |
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
@ -254,16 +254,28 @@ Player Join Packet
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`) |
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)
@ -290,7 +302,6 @@ check `r2_analyze.py` for full list
# How to enable External Console:
1. Right click on the title bar (in windowed mode) and click "Switch Console"
2. or Use a custom Content Pack (**untested!**)
# How to enable Scenegraph debugging console

118
Py_Docs/SAI.md Normal file
View file

@ -0,0 +1,118 @@
# SAI
- *GetStateChar*: `<built-in function GetStateChar>`
```
GetStateChar(string nameEntity) : Devuelve el estado de la IA del personaje.
```
- *AddVehicleRace*: `<built-in function AddVehicleRace>`
```
bool AddVehicleRace(cWithLifeEntity *entidad) : Asigna entidad como perteneciente a carrera.
```
- *SetStateVehicle*: `<built-in function SetStateVehicle>`
```
SetStateVehicle(0, string nameAgent) : Estado sin movimiento ni disparo.
SetStateVehicle(1, float posObjX, float posObjY, float posObjZ, radiusObj, string nameAgent) : Estado alcanzar posición.
SetStateVehicle(2, string nameObjectiveDin, string nameAgent) : Estado persecución enemigo.
SetStateVehicle(3, string nameAgent) : Estado en ruta.
SetStateVehicle(4, string nameAgent) : Estado tráfico.
SetStateVehicle(5, string nameAgent, float distStop) : Estado huída con parada.
SetStateVehicle(6, float posObjX, float posObjY, float posObjZ, radiusObj, string nameAgent) : Estado alcanzar meta en carrera.
SetStateVehicle(7, string nameObjectiveDin, string nameAgent) : Estado persecución.
SetStateVehicle(8, string nameObjectiveDin, string nameAgent) : Estado persecución enemigo con uso de hook.
```
- *EnableAIChar*: `<built-in function EnableAIChar>`
```
EnableAIChar(string nameEntity, int enable, int stupidPathfinding) : Habilita la IA del personaje indicando características asociadas al movimiento).
```
- *BuildGraph*: `<built-in function BuildGraph>`
```
BuildGraph(int numNodesRadius, float sizeNodeX, float sizeNodeY, float sizeNodeZ) : Crea el Grafo asociado al Pathfinding
```
- *AnalizeMap*: `<built-in function AnalizeMap>`
```
AnalizeMap(float sizeNode) : Analiza características mapa.
```
- *SetStateChar*: `<built-in function SetStateChar>`
```
SetStateChar(0, string nameAgent) : Estado sin movimiento ni disparo.
SetStateChar(1, string nameAgent, float vel, int withStopTemp) : Estado en ruta.
SetStateChar(2, string nameAgent, string nameObjective, float vel) : Estado en persecución objetivo con acción.
SetStateChar(3, string nameAgent, string nameObjective, float vel) : Estado en persecución objetivo sin acción.
SetStateChar(4, string nameAgent, float posObjX, float posObjY, float posObjZ, float orientX, float orientY, float orientZ, float radiusObj, float velObj) : Estado ir a un punto con orientación final.
SetStateChar(5, string nameAgent, string nameObjective, float vel) : Estado huída de otro personaje.
SetStateChar(6, string nameAgent, float centerPatrolZone.x, float centerPatrolZone.y, float centerPatrolZone.z, float radiusPatrolZone, float vel, int withStopTemp) : Estado patrulla de zona.
SetStateChar(7, string nameAgent, string nameObjective, float vel) : Estado en persecución objetivo con acción.
```
- *AnalizeGraph2D*: `<built-in function AnalizeGraph2D>`
```
AnalizeGraph2D() : Analiza características grafo 2D.
```
- *GetNextRacePoint*: `<built-in function GetNextRacePoint>`
```
(point) GetNextRacePoint(initialPoint, minDist, maxDist) : A partir de un punto inicial 'initialPoint', una distancia mínima 'minDist' y una distancia máxima 'maxDist', devuelve un punto aleatorio a partir del grafo 3D de la IA en el interior actual.
```
- *GetOD*: `<built-in function GetOD>`
- *SetRotStaticObj*: `<built-in function SetRotStaticObj>`
```
bool SetRotStaticObj(float maxVelRot, float limIncVelRot) : Asigna rotaciones para movimiento hacia objetivo estático.
```
- *IniAI*: `<built-in function IniAI>`
```
IniAI(levelPath) : Inicializa AI para un nivel.
```
- *GetStateVehicle*: `<built-in function GetStateVehicle>`
```
GetStateVehicle(string nameEntity) : Devuelve el estado de la IA del vehículo.
0 : Estado sin movimiento ni disparo.
1 : Estado alcanzar posición.
2 : Estado persecución enemigo.
3 : Estado en ruta.
4 : Estado tráfico.
5 : Estado huída con parada.
6 : Estado alcanzar meta en carrera.
7 : Estado persecución.
8 : Estado persecución enemigo con uso de hook.
9 : Estado sin movimiento ni disparo por estar objetivo en posición inválida.
```
- *BuildGraph2D*: `<built-in function BuildGraph2D>`
```
BuildGraph2D(int numNodesRadius, float sizeNodeX, float sizeNodeY, float sizeNodeZ) : Crea el Grafo asociado al Pathfinding 2D
```
- *AnalizeTraffic*: `<built-in function AnalizeTraffic>`
```
AnalizeTraffic() : Analiza características tráfico.
```
- *SetInertia*: `<built-in function SetInertia>`
```
void SetInertia(bool inertia) : Indica si la nave tiene inercia.
```
- *InitVehicleRace*: `<built-in function InitVehicleRace>`
```
InitVehicleRace() : Inicializa carrera de vehículos.
```
- *GetNearestItemLife*: `<built-in function GetNearestItemLife>`
```
(itemName) GetNearestItemLife(vehicleName)) : Devuelve el item de vida más cercano a una nave dada.
```
- *EnableAIVehicle*: `<built-in function EnableAIVehicle>`
```
EnableAIVehicle(string nameEntity, int enable, int controlStrafe, int controlBrake, int stupidPathfinding) : Habilita la IA del vehículo indicando características asociadas al movimiento).
```
- *AnalizeGraph*: `<built-in function AnalizeGraph>`
```
AnalizeGraph() : Analiza características grafo.
```
- *GetRandomVisibilityPoint*: `<built-in function GetRandomVisibilityPoint>`
```
(point) GetRandomVisibilityPoint() : Devuelve un punto aleatorio del grafo de puntos de visibilidad.
```
- *GetReposCharPos*: `<built-in function GetReposCharPos>`
```
(x,y,z) GetReposCharPos((x,y,z) ,EntityClass,[,EntityName]) : Obtiene un punto de reposicion de personaje (si entidad, se asigna).
Retorna (None) si falla
```

40
Py_Docs/SAct.md Normal file
View file

@ -0,0 +1,40 @@
# SAct
- *CreateClass*: `<built-in function CreateClass>`
```
CreateClass(classname) : Crea una clase de objeto animado
```
- *CreateAction*: `<built-in function CreateAction>`
```
CreateAction(varname) : Crea una accion en la clase actual.
```
- *DelClass*: `<built-in function DelClass>`
```
DelClass(classname) : Crea una clase de objeto animado
```
- *SetCls*: `<built-in function SetCls>`
```
SetCls(varname,value) : Modifica el valor de una variable de una clase objeto animado
```
- *GetCls*: `<built-in function GetCls>`
```
GetCls(varname) : Obtiene el valor de una variable de una clase objeto animado
```
- *GetClass*: `<built-in function GetClass>`
```
GetClass(classname) : Activa una clase de objeto animado
```
- *SetAct*: `<built-in function SetAct>`
```
SetAct(varname,value) : Modifica el valor de una variable de la accion de una clase objeto animado
```
- *GetAct*: `<built-in function GetAct>`
```
GetAct(varname) : Obtiene el valor de una variable de la accion de una clase objeto animado
```
- *GetAction*: `<built-in function GetAction>`
```
GetAction(varname) : Obtiene una accion de la clase actual.
```

28
Py_Docs/SFX.md Normal file
View file

@ -0,0 +1,28 @@
# SFX
- *CharacterConversor*: `<built-in function CharacterConversor>`
```
FXCharacterConversor(CharacterName, ConversorName, phase) : Conversor de Personajes.
```
- *BishopSellLife*: `<built-in function BishopSellLife>`
```
FXBishopSellLife(AttackerName, AttackedName) : Efecto de dar vida (de Attacker [Bishop ó NULL] a Attacked[Usuario]).
```
- *CharacterConversion*: `<built-in function CharacterConversion>`
```
FXCharacterConversion(AttackerName, AttackedName, time) : Conversión de Personajes.
```
- *EmbeddedSet*: `<built-in function EmbeddedSet>`
```
FXEmbeddedSet(EntityName, FXType) : Asigna el controlador de efectos embedidos.
```
- *ShipExplosion*: `<built-in function ShipExplosion>`
```
ShipExplosion(V3D Pos, V3D Vel, float scale, float time, float nflames, bool bcolision) : Explosión de la nave.
```
- *MoneyTransfer*: `<built-in function MoneyTransfer>`
```
FXMoneyTransfer(EntityFrom, EntityTo, Time) : Transferencia de dinero
```

84
Py_Docs/SInput.md Normal file
View file

@ -0,0 +1,84 @@
# SInput
- *Rumble*: `<built-in function Rumble>`
```
Rumble(iPlayer,Left,Right,Time) : Inicializa el rumble de un pad.
```
- *GetActionSet*: `<built-in function GetActionSet>`
```
string GetActionSet() : Obtiene el set de acciones Actual...
```
- *SetInputFunc*: `<built-in function SetInputFunc>`
```
SetInputFunc(iPlayer,modfunc) : agrega la funcion callback de entrada modfunc(iPlayer,string)
```
- *GetCursorChar*: `<built-in function GetCursorChar>`
```
GetCursorChar(iPlayer) : Obtiene el (x,y,caracter) que indican el estado del cursor.
```
- *GetDefinedEntry*: `<built-in function GetDefinedEntry>`
```
value GetDefinedEntry(iPlayer,ActionSet,Action,Device) : obtiene una cadena con la primera definicion del control que encuentre
```
- *AbortListenToDefine*: `<built-in function AbortListenToDefine>`
```
AbortListenToDefine() : Aborta la redefinicion en curso
```
- *ResetToDefault*: `<built-in function ResetToDefault>`
```
ResetToDefault(iPlayer,ActionSet,Action) : Pone todas las entradas de los controles a valores por defecto.
```
- *ResetToSplit*: `<built-in function ResetToSplit>`
```
ResetToSplit() : Resetea el sistema de entrada de datos para iniciar el modo split screen.
```
- *GetDefinedList*: `<built-in function GetDefinedList>`
```
GetDefinedList(iPlayer,ActionSet,Action) : obtiene una cadena con la definicion de controles
```
- *AssingEntry*: `<built-in function AssingEntry>`
```
int AssingEntry(Device,Entry,Player,ActionSet,Action) : Asigna una entrada... retorna 0 o el Nro de parametro erroneo
```
- *CheckPadButton*: `<built-in function CheckPadButton>`
```
CheckPadButton() : Chequea el estado de un determinado botón del pad.
```
- *ListenToDefine*: `<built-in function ListenToDefine>`
```
ListenToDefine(iPlayer,ActionSet,Action,LaFunction) : Espera a que el usuario mueva un control y lo redefine
```
- *SetString*: `<built-in function SetString>`
```
SetString(iPlayer,String) : Modifica la cadena de entrada de texto.
```
- *Bind*: `<built-in function Bind>`
```
Bind(iPlayer,ActionSet,Action) : obtiene una cadena con la definicion de controles
```
- *GetEntry*: `<built-in function GetEntry>`
```
(Player,Action) GetEntry(Device,Entry,ActionSet) : Obtiene una entrada, (0,) si vacia
```
- *GetVirtualKeyboard*: `<built-in function GetVirtualKeyboard>`
```
GetVirtualKeyboard() : Obtiene el (W,H,Board) que son datos del keyboard virtual.
```
- *GetString*: `<built-in function GetString>`
```
GetString(iPlayer) : Obtiene la cadena de entrada de texto.
```
- *SetVirtualKeyboard*: `<built-in function SetVirtualKeyboard>`
```
SetVirtualKeyboard(tipo de teclado) : Cambia el teclado virtual
```
- *SetActionSet*: `<built-in function SetActionSet>`
```
SetActionSet(string name) : Pone el set de acciones requerido...
```
- *ClearDefinedList*: `<built-in function ClearDefinedList>`
```
ClearDefinedList(iPlayer,ActionSet,Action) : Elimina todas las entradas de un control.
```

52
Py_Docs/SLogic.md Normal file
View file

@ -0,0 +1,52 @@
# SLogic
- *IsEnemyActive*: `<built-in function IsEnemyActive>`
```
int IsEnemyActive() : Indica si hay un enemigo activo, mirando el contenido de la lista de enemigos y en el tráfico.
```
- *ChangeZoneState*: `<built-in function ChangeZoneState>`
```
void ChangeZoneState(zoneId, state): Pone el estado de una zona de dominación
```
- *SetOnFloor*: `<built-in function SetOnFloor>`
```
void SetOnFloor(Entity) : Pone una entidad en el suelo
```
- *ChangeBatonState*: `<built-in function ChangeBatonState>`
```
void ChangeBatonState(pos, state): Actualiza el estado del testigo en el modo dominación
```
- *SetShipToRegenerate*: `<built-in function SetShipToRegenerate>`
```
void SetShipToRegenerate(shipName, regSpeed,regEndSpeed): Pone una nave a regenerarse
```
- *SetDominationZones*: `<built-in function SetDominationZones>`
```
void SetDominationZones(zonesList): Establece la lista de posiciones de las zonas de dominación en la super apuesta de dominación
```
- *Flash*: `<built-in function Flash>`
```
void Flash((x,y,z),radius) : Pone una entidad en el suelo
```
- *GetNearestShip*: `<built-in function GetNearestShip>`
```
Name GetNearestShip(pos) : Cicla por varias listas y devuelve la nave más cercana a la posición indicada
```
- *SendSentinelToWatch*: `<built-in function SendSentinelToWatch>`
```
void SendSentinelToWatch(Pos) : Envia a un centinela a inspeccionar una posicion
```
- *UpdateTauntEndTime*: `<built-in function UpdateTauntEndTime>`
```
void UpdateTauntEndTime(time): Tiempo final del taunt
```
- *GearAttack*: `<built-in function GearAttack>`
```
void GearAttack(Entity) : Envia a un Gear a atacar a una entidad
```
- *SetCharState*: `<built-in function SetCharState>`
```
void SetCharState(entityName, state, entityTargetName) : Pone a una entidad en un estado de logica y un target determinados
```

94
Py_Docs/SNet.md Normal file
View file

@ -0,0 +1,94 @@
# SNet
- *SendUsrString*: `<built-in function SendUsrString>`
```
SendUsrString(id,string) : -1 significa a todo el mundo en modo servidor, para cliente id se ignora
```
- *InitServer*: `<built-in function InitServer>`
```
InitServer(LevelPath,MaxPlayers,ipport) : Intenta inicializar el servidor.
```
- *GetBotName*: `<built-in function GetBotName>`
```
GetBotName() : Obtiene un nombre valido de entidad jugador manejada por el servidor (bot o jugador local)
```
- *CloseServer*: `<built-in function CloseServer>`
```
CloseServer(LevelPath) : Acaba el servidor y carga un nivel.
```
- *IsClient*: `<built-in function IsClient>`
```
IsClient() : 1 si esta activado el sistema cliente
NOTA: Scrap.GetNetFlags() tiene el flag cliente activado
si la coneccion se hizo efectiva
```
- *ServerChangeLevel*: `<built-in function ServerChangeLevel>`
```
ServerChangeLevel(resource name) : carga el siguiente nivel.
```
- *DoneBrowser*: `<built-in function DoneBrowser>`
```
DoneBrowser() : Cierra el browser de red local.
```
- *InitClient*: `<built-in function InitClient>`
```
InitClient(ipAddress,ipport) : Inicia el proceso de coneccion con el servidor.
```
- *CloseClient*: `<built-in function CloseClient>`
```
CloseClient(LevelPath) : Acaba el cliente y carga un nivel.
```
- *IsServer*: `<built-in function IsServer>`
```
IsServer() : 1 si esta activado el sistema servidor
NOTA: es para depuracion, mejor use Scrap.GetNetFlags()
```
- *GetObjName*: `<built-in function GetObjName>`
```
GetObjName() : Obtiene un nombre valido de Objeto cualesquiera.
```
- *GetMyClientShip*: `<built-in function GetMyClientShip>`
```
GetMyClientShip() : retorna el nombre de su nave.
```
- *ModifyUsrData*: `<built-in function ModifyUsrData>`
```
ModifyUsrData(ClientId) :
modifica desde el servidor los datos locales.
```
- *AddResource*: `<built-in function AddResource>`
```
resourceid AddResource(resource name) : intenta agregar un recurso si este no existe. -1 si el pool esta lleno
```
- *SendChatString*: `<built-in function SendChatString>`
```
SendChatString(id,string) : -1 significa a todo el mundo en modo servidor, para cliente id se ignora
```
- *GetClientData*: `<built-in function GetClientData>`
```
GetClientData() : Obtiene la tupla (ipaddress,ipport)
```
- *InitBrowser*: `<built-in function InitBrowser>`
```
InitBrowser(port) : Inicializa el browser de red local.
```
- *SendMasterString*: `<built-in function SendMasterString>`
```
SendMasterString(string) : envia una cadena al master. Si retorna cero no hay master.
```
- *PingInetSvrs*: `<built-in function PingInetSvrs>`
```
PingInetSvrs() : 1 exitoso. revisa el estado de los servidores en internet. Se realiza despues de browse.
```
- *IsMaster*: `<built-in function IsMaster>`
```
IsMaster() : 1 si esta activado el sistema cliente
NOTA: Scrap.GetNetFlags() tiene el flag cliente activado
si la coneccion se hizo efectiva
```
- *GetServerData*: `<built-in function GetServerData>`
```
GetServerData() : Obtiene la tupla (Hostname,ipaddress,ipport)
```

132
Py_Docs/SScorer.md Normal file
View file

@ -0,0 +1,132 @@
# SScorer
- *SetCursor*: `<built-in function SetCursor>`
```
SetCursor(playernumber,CursorName) : pone un item del scorer como cursor
```
- *Show*: `<built-in function Show>`
```
Show(playernumber) : Activa el scorer.
```
- *GetTextArea*: `<built-in function GetTextArea>`
```
(width, height) GetTextArea(fontType, text) : obtiene la anchura y la altura de un texto dado el texto y el tipo de fuente
```
- *AddModel*: `<built-in function AddModel>`
```
AddModel(playernumber,Name,ModelFile,numanim,Radius) : Pre carga un modelo para usarse en el scorer.
```
- *SetOnPrev*: `<built-in function SetOnPrev>`
```
SetOnPrev(playernumber,CancelEvent) : Define la funcion que se ejecutara en el caso del pagina abajo.
```
- *AddChatMsg*: `<built-in function AddChatMsg>`
```
AddChatMsg(string,red,gree,blue) : Agrega un mensaje a la lista de chat.
```
- *GetOnCancel*: `<built-in function GetOnCancel>`
```
(CancelEvent)GetOnCancel(playernumber) : Devuelve la funcion que se ejecutara en el caso del escape.
```
- *GetDefault*: `<built-in function GetDefault>`
```
DefaultItemName GetDefault(playernumber) Obtiene el nombre del item por defecto
```
- *SetOnSpecialHint*: `<built-in function SetOnSpecialHint>`
```
SetOnSpecialHint(playernumber,SpecialHintTestFunc,SpecialHint) : Define la funcion que decide si se muestra un hint especial
```
- *SetConsole*: `<built-in function SetConsole>`
```
SetConsole(show) : oculta/ muestra la consola de pantalla completa
```
- *Get2DPos*: `<built-in function Get2DPos>`
```
(x,y) Get2DPos(vector3d) : obtiene en cordenadas de pantalla una posicion del escenario
```
- *GetActual*: `<built-in function GetActual>`
```
ActualItem GetActual(playernumber) Obtiene el item actual
```
- *SetOnNext*: `<built-in function SetOnNext>`
```
SetOnNext(playernumber,CancelEvent) : Define la funcion que se ejecutara en el caso del pagina abajo.
```
- *SetMPFunc*: `<built-in function SetMPFunc>`
```
SetMPFunc(playernumber,Callback) : Callback(id,showmpscorer) (muestra oculta el scorer multiplayer)
```
- *Set*: `<built-in function Set>`
```
Set(playernumber,itemname,varname,value) : Modifica el valor de una variable de un item
```
- *SetSpeechCallback*: `<built-in function SetSpeechCallback>`
```
SetSpeechCallback(Callback) : Especifica la funcion callback que será llamada
```
- *Get*: `<built-in function Get>`
```
Get(playernumber,itemname,varname) : Obtiene el valor de una variable de un item
```
- *GetMenuAccept*: `<built-in function GetMenuAccept>`
```
GetMenuAccept(id) : Devuelve el estado de la acción de menu aceptar.
```
- *SetDefault*: `<built-in function SetDefault>`
```
SetDefault(playernumber,DefaultItemName) : pone un item del scorer 'por defecto'
```
- *CancelSpeech*: `<built-in function CancelSpeech>`
```
CancelSpeech(time) : cancela un mensaje remoto
```
- *SetHeadMonitor*: `<built-in function SetHeadMonitor>`
```
SetHeadMonitor(Head,Msg,anm) : Especifica la cabeza que será usada en el monitor y el mensaje.
```
- *Add*: `<built-in function Add>`
```
Add(playernumber,itemname,itemtype,AtEnd) : Agrega un item al scorer.
```
- *SetMarkerSprite*: `<built-in function SetMarkerSprite>`
```
SetMarkerSprite(id,SpriteName) : pone un sprite como marcador
```
- *PreloadTexture*: `<built-in function PreloadTexture>`
```
PreloadTexture(filename) : Precarga una textura
```
- *SetMsgText*: `<built-in function SetMsgText>`
```
SetMsgText(text,time) : Muestra un mensaje de sistema y lo desactiva en el tiempo de mundo time
```
- *Fade*: `<built-in function Fade>`
```
Fade(id,(r,g,b,a),(r,g,b,a),time) : Realiza un fade de rgba a rgba.
```
- *Clear*: `<built-in function Clear>`
```
Clear(playernumber) : Limpia el scorer completamente.
```
- *SetCinema*: `<built-in function SetCinema>`
```
SetCinema(id,status,time) : Activa el modo escena de cine.
```
- *SetSpeechText*: `<built-in function SetSpeechText>`
```
SetSpeechText(text,time,r,g,b) : activa el texto de una conversacion que durará un tiempo espeficico
```
- *SetOnCancel*: `<built-in function SetOnCancel>`
```
SetOnCancel(playernumber,CancelEvent) : Define la funcion que se ejecutara en el caso del escape.
```
- *Hide*: `<built-in function Hide>`
```
Hide(playernumber) : Desactiva el scorer.
```
- *SetLabelText*: `<built-in function SetLabelText>`
```
SetLabelText(text,time) : Muestra un rotulo de sistema y lo desactiva en el tiempo de mundo time
```

72
Py_Docs/SSound.md Normal file
View file

@ -0,0 +1,72 @@
# SSound
- *StopVoice*: `<built-in function StopVoice>`
```
StopVoice(channel) : stop a voice on the specified channel.
```
- *Play*: `<built-in function Play>`
```
Play('soundname'[,vol,pan]) : ejecuta un sonido con el volumen deseado.
```
- *PlaySound*: `<built-in function PlaySound>`
```
PlaySound((x,y,z),'soundname',vol[,AttenIni,AttenEnd,Doppler]) : ejecuta un sonido en la posicion dada con el volumen deseado.
```
- *SetMusic*: `<built-in function SetMusic>`
```
SetMusic('soundname'[,vol]) : set the file of the background music.
```
- *LoadSound*: `<built-in function LoadSound>`
```
LoadSound(soundfile) : carga un sonido desde un archivo empaquetado.
```
- *OpenVoice*: `<built-in function OpenVoice>`
```
OpenVoice(name,channel) : Preload a voice on a channel.
```
- *StopAllSounds*: `<built-in function StopAllSounds>`
```
SCRAP_StopAllSounds() : para todos los sonidos.
```
- *SetMusicVolume*: `<built-in function SetMusicVolume>`
```
SetMusicVolume(vel) : set the volume of the background music.
```
- *UsePS*: `<built-in function UsePS>`
```
UsePS(Name) : Crea un nuevo sonido posicional
```
- *SetVoiceString*: `<built-in function SetVoiceString>`
```
SetVoiceString(channel,volume) : set string user data of a voice.
```
- *VoiceRemain*: `<built-in function VoiceRemain>`
```
VoiceRemain(channel,time) : return the remaining time of a voice.
```
- *CreatePS*: `<built-in function CreatePS>`
```
CreatePS(Name) : Crea un nuevo sonido posicional
```
- *SetPS*: `<built-in function SetPS>`
```
SetPS(name,varname,value) : Modifica el valor de una variable de un sonido posicional
```
- *PlayVoice*: `<built-in function PlayVoice>`
```
PlayVoice(channel) : play a voice loaded on the specified channel.
```
- *DeleteAllSounds*: `<built-in function DeleteAllSounds>`
```
SCRAP_DeleteAllSounds() : elimina todos los sonidos.
```
- *GetPS*: `<built-in function GetPS>`
```
GetPS(name,varname) : Obtiene el valor de una variable de un sonido posicional
```
- *SetVoiceVolume*: `<built-in function SetVoiceVolume>`
```
SetVoiceVolume(channel,volume) : set the volume of a voice.
```

52
Py_Docs/SVec.md Normal file
View file

@ -0,0 +1,52 @@
# SVec
- *Mod*: `<built-in function Mod>`
```
res Mod(v1) : Devuelve el modulo de un vector
```
- *Norm*: `<built-in function Norm>`
```
res Norm(v1) : Normaliza un vector
```
- *Prod*: `<built-in function Prod>`
```
res Prod(v1,f) : multiplica un vector por un numero
```
- *ModSqr*: `<built-in function ModSqr>`
```
res ModSqr(v1) : Devuelve el modulo (al cuadrado) de un vector
```
- *DProd*: `<built-in function DProd>`
```
res DProd(v1,v2) : Calcula el producto escalar de dos vectores
```
- *Add*: `<built-in function Add>`
```
res Add(v1,v2) : suma dos vectores 3D
```
- *CProd*: `<built-in function CProd>`
```
vRes CProd(v1,v2) : Calcula el producto vectorial de dos vectores
```
- *NormAng*: `<built-in function NormAng>`
```
rAng NormAng(Ang) : Normaliza un angulo
```
- *Sub*: `<built-in function Sub>`
```
res Sub(v1,v2) : Resta dos vectores 3D
```
- *GetRotAng*: `<built-in function GetRotAng>`
```
AngX,AngY GetRotAng(vec) : Obtiene la rotacion de un vector
```
- *Rotate3D*: `<built-in function Rotate3D>`
```
res Rotate3D(src,rot) : Rota un vector
```
- *GetAngle*: `<built-in function GetAngle>`
```
Ang GetAngle(vec) : Obtiene el ángulo entre dos vectores
```

64
Py_Docs/SWeap.md Normal file
View file

@ -0,0 +1,64 @@
# SWeap
- *SetSWeap*: `<built-in function SetSWeap>`
```
SetSWeap(numammo,svar,svalue) : Pone un valor cadena de las municiones
```
- *FillPriority*: `<built-in function FillPriority>`
```
FillPriority() : Inizializa las prioridades de las armas
```
- *GetSAmmo*: `<built-in function GetSAmmo>`
```
GetSAmmo(numammo,svar) : Obtiene un valor cadena de las municiones
```
- *SetFAmmo*: `<built-in function SetFAmmo>`
```
GetFAmmo(numammo,svar,fvalue) : Pone un valor numerico de las municiones
```
- *InitWeap*: `<built-in function InitWeap>`
```
InitWeap(num) : Inicializa las armas num es el numero de armas
```
- *GetNWeap*: `<built-in function GetNWeap>`
```
GetNWeap() : Obtiene el numero de armas en el juego
```
- *GetFAmmo*: `<built-in function GetFAmmo>`
```
GetFAmmo(numammo,svar) : Obtiene un valor numerico de las municiones
```
- *GetFirstiWeap*: `<built-in function GetFirstiWeap>`
```
GetFirstiWeap() : Obtiene el arma primera y por defecto
```
- *GetSWeap*: `<built-in function GetSWeap>`
```
GetSWeap(numWeap,svar) : Obtiene un valor cadena de las municiones
```
- *GetFWeap*: `<built-in function GetFWeap>`
```
GetFWeap(numWeap,svar) : Obtiene un valor numerico de las municiones
```
- *GetNAmmo*: `<built-in function GetNAmmo>`
```
GetNAmmo() : Obtiene el numero de municiones en el juego
```
- *NetExec*: `<built-in function NetExec>`
```
NetExec() : Rutinas de red de los misiles
```
- *SetFWeap*: `<built-in function SetFWeap>`
```
GetFWeap(numWeap,svar,fvalue) : Pone un valor numerico de las municiones
```
- *InitAmmo*: `<built-in function InitAmmo>`
```
InitAmmo(num) : Inicializa las municiones del juego
```
- *SetSAmmo*: `<built-in function SetSAmmo>`
```
SetSAmmo(numammo,svar,svalue) : Pone un valor cadena de las municiones
```

425
Py_Docs/Scrap.md Normal file
View file

@ -0,0 +1,425 @@
# Scrap
- *DropDebris*: `<built-in function DropDebris>`
```
DropDebris(name,size) : Lanza un objeto que cae y rebota hasta que desaparece
```
- *ShowGVars*: `<built-in function ShowGVars>`
```
ShowGVars() : Muestra una lista de todas las variables globales
```
- *GetNewLevelPath*: `<built-in function GetNewLevelPath>`
```
levelpath GetNewLevelPath() : Obtiene el path del próximo nivel
```
- *SetTime*: `<built-in function SetTime>`
```
SetTime(Time) : Cambia el tiempo del mundo en segundos
```
- *SetCam*: `<built-in function SetCam>`
```
SetCam(i,name) : Activa una camara
```
- *PreloadAnm*: `<built-in function PreloadAnm>`
```
PreloadAnm(ObjFilename,AnmFilename) : Precarga una animacion
```
- *GetCam*: `<built-in function GetCam>`
```
name GetCam(i) : Obtiene el nombre de una entidad camara
```
- *GetLockAlarm*: `<built-in function GetLockAlarm>`
```
Scrap.GetLockAlarm() : verdadero si la alarma esta bloqueada
```
- *AddItem*: `<built-in function AddItem>`
```
Scrap.AddItem(Life, string name, int life) : Add Item Life.
Scrap.AddItem(Ammo, string name, int typeAmmo, int ammo) : Add Item Ammo.
```
- *Verbose*: `<built-in function Verbose>`
```
Verbose(string name) : Muestra un mensaje de parloteo por la consola
```
- *StartDummySearch*: `<built-in function StartDummySearch>`
```
StartDummySearch(name,usewildcards) : Inicia la busqueda de dummies en el mapa
```
- *OpenPack*: `<built-in function OpenPack>`
```
OpenPack(string PackPath) : Abre un archivo *.packed
```
- *LaunchDashboard*: `<built-in function LaunchDashboard>`
```
int Scrap.LaunchDashboard() : sale del juego y ejecuta el dashboard.
```
- *SaveGameVars*: `<built-in function SaveGameVars>`
```
Scrap.SaveGameVars(str, str) : Salva un juego en un archivo, con un nombre opcional
```
- *SphereCall*: `<built-in function SphereCall>`
```
Scrap.SphereCall(x,y,z,radius,´strmask´,´callback´,[IgnoreGeometry=1]) : testea una esfera y llama a ´callback´ por cada entidad que colisiona
```
- *SetTimeSpeed*: `<built-in function SetTimeSpeed>`
```
SetTimeSpeed(TimeSpeed) : Cambia la velocidad del tiempo del mundo.
```
- *Preload3DObject*: `<built-in function Preload3DObject>`
```
Preload3DObject(filename,scalex,scaley,scalez) : Precarga un objeto 3D
```
- *TestLine*: `<built-in function TestLine>`
```
((x,y,z),s) TestLine((x,y,z),(fz,fy,fz),'strmask') : testea una linea y devuelve el nombre de la entidad y punto de colision o '' si es el mapa o None
```
- *GetFreeBlocks*: `<built-in function GetFreeBlocks>`
```
int Scrap.GetFreeBlocks() : obtiene los bloques libres en el disco duro.
```
- *IncSaveVar*: `<built-in function IncSaveVar>`
```
int Scrap.IncSaveVar(str[,value]) : incrementa un contador de estadisticas
```
- *DeselectProfile*: `<built-in function DeselectProfile>`
```
Scrap.DeselectProfile() : deselecciona el profile actual.
```
- *GetMoney*: `<built-in function GetMoney>`
```
money Scrap.GetMoney() : Devuelve el liquido disponible.
```
- *CreateElements*: `<built-in function CreateElements>`
```
CreateElements() : Crea una lista de elementos estaticos que pertenecen al mapa.
```
- *NextDummySearch*: `<built-in function NextDummySearch>`
```
(s(ddd)(dd)i) NextDummySearch() : Obtiene el proximo dummy, sino None
```
- *CreateEntity*: `<built-in function CreateEntity>`
```
CreateEntity(name,x,y,z,type) : Crea una una entidad
```
- *LoadLevel*: `<built-in function LoadLevel>`
```
LoadLevel(string name) : Carga un nivel en el siguiente frame
```
- *DebugInput*: `<built-in function DebugInput>`
```
string DebugInput() : Detiene todo para iniciar la depuracion
```
- *GetLangStr*: `<built-in function GetLangStr>`
```
str Scrap.GetLangStr(Name) : Obtiene una cadena de lenguaje. '' si no existe.
```
- *Get*: `<built-in function Get>`
```
Get('GlobalVar') : Obtiene el valor de una variable global
```
- *SetAlarmChar*: `<built-in function SetAlarmChar>`
```
Scrap.SetAlarmChar(El_que_se_busca) : Modifica el personaje que se busca.
```
- *GetEntity*: `<built-in function GetEntity>`
```
GetEntity(string name) : Retorna una entidad
```
- *GetLanguage*: `<built-in function GetLanguage>`
```
lang Scrap.GetLanguage() : Obtiene la lengua actual. None si no fue inicializada.
```
- *Print*: `<built-in function Print>`
```
Print(string name) : Muestra un mensaje por la consola siempre
```
- *SwitchMissionArrows*: `<built-in function SwitchMissionArrows>`
```
SwitchMissionArrows(MainMissionFile,TargetMissionFile) : Cambia las flechas de mision primaria y secundaria (Modelos)
```
- *GetFarestParked*: `<built-in function GetFarestParked>`
```
Entity,Pos = Scrap.GetFarestParked([isparked[,fromPos]]) : Obtiene la nave aparcada mas lejana.
```
- *CreateSaveProfile*: `<built-in function CreateSaveProfile>`
```
int Scrap.CreateSaveProfile(str) : Crea un Save profile para X-Box y pone como actual.
```
- *ExtractPack*: `<built-in function ExtractPack>`
```
ExtractPack(string PackPath) : Extrae un archivo *.packed
```
- *SetLockAlarm*: `<built-in function SetLockAlarm>`
```
Scrap.SetLockAlarm(1/0) : Bloquea/desbloquea la alarma
```
- *SetSaveVar*: `<built-in function SetSaveVar>`
```
bool Scrap.SetSaveVar(Name,DefValue) : Modifica una variable (si puede)
```
- *ListModels*: `<built-in function ListModels>`
```
ListModels() : Muestra una lista de modelos y escenas
```
- *ConsoleOut*: `<built-in function ConsoleOut>`
```
ConsoleOut(string name) : Muestra un mensaje pyton por la consola
```
- *ScreenShot*: `<built-in function ScreenShot>`
```
ScreenShot(filename) : Screenshot of the current frame
```
- *SetMoney*: `<built-in function SetMoney>`
```
Scrap.SetMoney(money) : Determina el liquido disponible
```
- *GetFirst*: `<built-in function GetFirst>`
```
GetFirst() : Retorna la primera entuidad de la lista o none
```
- *LoadGameVars*: `<built-in function LoadGameVars>`
```
Scrap.LoadGameVars(str) : Carga un juego en un archivo
```
- *GetTime*: `<built-in function GetTime>`
```
GetTime() : Obtiene el tiempo del mundo en segundos
```
- *GetAlarmChars*: `<built-in function GetAlarmChars>`
```
(Actual, El_que_se_busca) Scrap.GetAlarmChars() : Obtiene los tipos de personaje que se buscan.
```
- *SetVideoCurrentMode*: `<built-in function SetVideoCurrentMode>`
```
SetVideoCurrentMode() : Set Video Current Mode Index
```
- *AddScheduledFunc*: `<built-in function AddScheduledFunc>`
```
AddScheduledFunc(time,func,params[,name]) : Ejecuta un codigo en python en un instante de tiempo.
```
- *ClosePack*: `<built-in function ClosePack>`
```
ClosePack() : Abre un archivo *.packed
```
- *CreateEntityList*: `<built-in function CreateEntityList>`
```
CreateEntityList(listName) : Crea una lista de entidades
```
- *ProcessDVF*: `<built-in function ProcessDVF>`
```
ProcessDVF(filename,command) : send a dvf command
```
- *SetAlarm*: `<built-in function SetAlarm>`
```
Scrap.SetAlarm(dialpos) : pone la alarma en una posicion (0 desactiva, 1 activa).
```
- *GetNearestParked*: `<built-in function GetNearestParked>`
```
Entity,Pos = Scrap.GetNearestParked([isparked[,fromPos]]) : Obtiene la nave aparcada mas cercana.
```
- *Set*: `<built-in function Set>`
```
Set('GlobalVar',val) : Modifica el valor de una variable global
```
- *PythonCompileAll*: `<built-in function PythonCompileAll>`
```
PythonCompileAll() : compila recursivamente los archivos .py
```
- *Rand*: `<built-in function Rand>`
```
Rand(min,max) : Obtiene un numero seudo-aleatorio entre (min,max)
```
- *Def*: `<built-in function Def>`
```
Def('GlobalVar') : Obtiene el valor por defecto de una variable global
```
- *Round*: `<built-in function Round>`
```
Round(num) : Redondea un numero real al entero más cercano.
```
- *ModelInfo*: `<built-in function ModelInfo>`
```
ModelInfo(name) : Muestra información sobre la jerarquía de nodos del modelo
```
- *UsrEntity*: `<built-in function UsrEntity>`
```
UsrEntity(ictr) : Retorna una entidad controlada por usario (personaje o nave)
```
- *GetAlarm*: `<built-in function GetAlarm>`
```
(active,dialpos,growcode) Scrap.GetAlarm() : Obtiene informacion de la alarma.
```
- *SetDebrisValue*: `<built-in function SetDebrisValue>`
```
SetDebrisValue(id,x,y,z) : id = 0:Posicion, 1:Angulos, 2:Velocidad, 3:Velocidad de rotacion
```
- *MusicPlayer*: `<built-in function MusicPlayer>`
```
MusicPlayer(filename[,command,param1]) : play music file
```
- *Des*: `<built-in function Des>`
```
Des('GlobalVar') : Obtiene el descriptor de una variable global
```
- *SetCallFunc*: `<built-in function SetCallFunc>`
```
Scrap.SetCallFunc('!funcname') : Especifica la funcion callback (c++) a llamar. (1 si existe)
```
- *SetDebrisSys*: `<built-in function SetDebrisSys>`
```
SetDebrisSys(name,initialnumber,MaxDist,Friction,Scale,Grav,bounce) crea/modifica un sistema de debris
```
- *Execute*: `<built-in function Execute>`
```
Scrap.Execute() : Ejecuta una funcion callback (c++).
```
- *AddParamf*: `<built-in function AddParamf>`
```
Scrap.AddParamf(Float) : Incluye un parametro a una funcion callback (c++).
```
- *PreloadLibrary*: `<built-in function PreloadLibrary>`
```
PreloadLibrary(LibraryName,CompiledFile) : Precarga una libreria de un archivo empaquetado
```
- *GetVideoCurrentMode*: `<built-in function GetVideoCurrentMode>`
```
GetVideoCurrentMode() : Get Video Current Mode Index
```
- *InitLoading*: `<built-in function InitLoading>`
```
InitLoading() Inicia la pantalla de carga rapida.
```
- *CallElements*: `<built-in function CallElements>`
```
CallElements('Function') : llama una funcion Function(Name,x,y,z,angx,angy)
```
- *GetTimeSpeed*: `<built-in function GetTimeSpeed>`
```
GetTimeSpeed() : Obtiene la velocidad del tiempo del mundo.
```
- *CreateSaveVar*: `<built-in function CreateSaveVar>`
```
bool Scrap.CreateSaveVar(Name,DefValue) : Crea una nueva variable (si puede)
```
- *EntityListGet*: `<built-in function EntityListGet>`
```
(value)Scrap.EntityListGet(listName, varName) : Obtiene el valor de una variable de una lista de entidades
```
- *DeleteProfile*: `<built-in function DeleteProfile>`
```
int Scrap.DeleteProfile(str) : Borra un profile de X-Box.
```
- *AddParami*: `<built-in function AddParami>`
```
Scrap.AddParami(int) : Incluye un parametro a una funcion callback (c++).
```
- *VideoPlayer*: `<built-in function VideoPlayer>`
```
VideoPlayer(filename[,command]) : play video file
```
- *SetLanguage*: `<built-in function SetLanguage>`
```
res Scrap.SetLanguage(Name[,forcetoreload]) : Modifica la lengua actual. false si ya es la lengua la selecionada
```
- *AddParams*: `<built-in function AddParams>`
```
Scrap.AddParams(string) : Incluye un parametro a una funcion callback (c++).
```
- *GetVideoModes*: `<built-in function GetVideoModes>`
```
GetVideoModes(NumMode) : Get Video Mode Info
```
- *GetSaveGamesList*: `<built-in function GetSaveGamesList>`
```
[(fecha, slot)] Scrap.GetSaveGamesList() : Devuelve una lista de tuplas donde para cada tupla el primer elemento indica el nombre (fecha) del archivo y el segundo elemento indica el slot del que se leerá la partida. La lista está ordenada por la fecha, de la más reciente a la más antigua.
```
- *GetLevelPath*: `<built-in function GetLevelPath>`
```
levelpath GetLevelPath() : Obtiene el path del nivel actual
```
- *BuildFont*: `<built-in function BuildFont>`
```
BuildFont(name,size) : Construye un archivo de fuente de letras a partir de un .TGA dado
```
- *DeleteScheduledFuncs*: `<built-in function DeleteScheduledFuncs>`
```
DeleteScheduledFuncs(name) : Borra las scheduled funcs creadas con un nombre dado.
```
- *GetNetFlags*: `<built-in function GetNetFlags>`
```
GetNetFlags() : Obtiene la tupla de flags : client, server, dedicated
```
- *GetSaveVar*: `<built-in function GetSaveVar>`
```
value Scrap.GetSaveVar(Name) : obtiene una variable o devuelve none
```
- *GetLangEntries*: `<built-in function GetLangEntries>`
```
[str] Scrap.GetLangEntries(Name) : Obtiene un subconjunto de entradas de la tabla de lenguaje que empiecen por 'Name'.
```
- *FileExist*: `<built-in function FileExist>`
```
FileExist() check for file existence.
```
- *GetMinCamDist*: `<built-in function GetMinCamDist>`
```
GetMinCamDist(x,y,z) : Obtiene la distancia de un punto a la camara
```
- *EntityListSet*: `<built-in function EntityListSet>`
```
EntityListSet(listName, varName, value) : Modifica el valor de una variable de una lista de entidades
```
- *SetAlarmGrow*: `<built-in function SetAlarmGrow>`
```
Scrap.SetAlarmGrow(growcode) : -1 baja, 0 se mantiene, 1 sube.
```
- *Exit*: `<built-in function Exit>`
```
Exit() sale del juego.
```
- *DelSaveVars*: `<built-in function DelSaveVars>`
```
Scrap.DelSaveVars(str) : Elimina las <str>* variables
```
- *SaveConfig*: `<built-in function SaveConfig>`
```
res Scrap.SaveConfig() : Guarda el fichero de configuración
```
- *SetSaveProfile*: `<built-in function SetSaveProfile>`
```
int Scrap.SetSaveProfile(path,name) : Carga un profile de jugador.
```
- *GetSavedProfilesList*: `<built-in function GetSavedProfilesList>`
```
[(Directorio, nombre)] Scrap.GetSavedProfilesList() : Devuelve una lista de tuplas donde para cada tupla el primer elemento indica el directorio del profile y el segundo elemento indica el nombre del profile. La lista está ordenada por el criterio de microsoft
```
- *SetGrid*: `<built-in function SetGrid>`
```
SetGrid(x,y,z) : Modifica las dimensiones del grid de colision
```
- *TestSphere*: `<built-in function TestSphere>`
```
Scrap.TestSphere(x,y,z,radius,´strmask´) : testea una esfera y devuelve el nombre de la entidad o '' si es el mapa o None
```
- *EndLoading*: `<built-in function EndLoading>`
```
EndLoading() Inicia la pantalla de carga rapida.
```
- *Ver*: `<built-in function Ver>`
```
Ver() Obtiene la version del juego.
```
- *ConsoleError*: `<built-in function ConsoleError>`
```
ConsoleError(string name) : Muestra un mensaje de error por la consola
```
- *GetFloor*: `<built-in function GetFloor>`
```
i GetFloor(y) : Obtiene el numero de piso en el que esta.
```
- *AddParkingZone*: `<built-in function AddParkingZone>`
```
Scrap.AddParkingZone(occupied,pos,name) : Agrega un sitio de parking.
```

475700
Scrap.rzdb Normal file

File diff suppressed because one or more lines are too long

22
ScrapHacks/.gitignore vendored Normal file
View file

@ -0,0 +1,22 @@
# Created by https://www.toptal.com/developers/gitignore/api/cmake
# Edit at https://www.toptal.com/developers/gitignore?templates=cmake
### CMake ###
CMakeLists.txt.user
CMakeCache.txt
CMakeFiles
CMakeScripts
Testing
Makefile
cmake_install.cmake
install_manifest.txt
compile_commands.json
CTestTestfile.cmake
_deps
CMakeUserPresets.json
### CMake Patch ###
# External projects
*-prefix/
# End of https://www.toptal.com/developers/gitignore/api/cmake

View file

@ -5,24 +5,30 @@ project(ScrapHacks
DESCRIPTION "Scrapland memory hacking library"
LANGUAGES CXX)
message(STATUS "SCRAPLAND_DIR=${SCRAPLAND_DIR}")
message(STATUS "Fetching Scrapland installation folder")
get_filename_component(SCRAPLAND_DIR "[HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432Node\\MercurySteam Entertainment\\Scrapland;DIRECTORY]" ABSOLUTE CACHE)
get_filename_component(DEFAULT_SCRAPLAND_DIR "[HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432Node\\MercurySteam Entertainment\\Scrapland;DIRECTORY]" ABSOLUTE CACHE)
if(NOT IS_ABSOLUTE "${SCRAPLAND_DIR}" OR NOT EXISTS "${SCRAPLAND_DIR}")
message(FATAL_ERROR "Scrapland installation folder not found!")
if(NOT IS_ABSOLUTE "${DEFAULT_SCRAPLAND_DIR}" OR NOT EXISTS "${DEFAULT_SCRAPLAND_DIR}")
set(DEFAULT_SCRAPLAND_DIR "")
endif()
set(SCRAPLAND_DIR "${DEFAULT_SCRAPLAND_DIR}" CACHE STRING "Scrapland installation path")
message(STATUS "Scrapland found at ${SCRAPLAND_DIR}")
message(STATUS "Checking Scrap.exe hash")
message(CHECK_START "Checking Scrap.exe hash")
file(SHA1 "${SCRAPLAND_DIR}/Bin/Scrap.exe" SCRAP_EXE_HASH)
if(NOT ${SCRAP_EXE_HASH} STREQUAL "d2dde960e8eca69d60c2e39a439088b75f0c89fa")
message(FATAL_ERROR "Scrap.exe hash miss match!")
message(CHECK_FAIL "Scrap.exe hash miss match ScrapHacks will probably crash your game!")
else()
message(CHECK_PASS "Hash matches!")
endif()
# ==============================
# "${SCRAPLAND_DIR}/Bin/Scrap.exe"
add_custom_target(
run
COMMAND "Scrap.exe"
@ -30,6 +36,7 @@ add_custom_target(
VERBATIM
)
add_dependencies(run install)
# ==============================
set(COMPONENT "ScrapHacks")
@ -40,7 +47,7 @@ set(ASMJIT_EMBED ON CACHE INTERNAL "")
set(ASMTK_EMBED ON CACHE INTERNAL "")
set(ZYDIS_BUILD_TOOLS OFF CACHE INTERNAL "")
set(ZYDIS_BUILD_EXAMPLES OFF CACHE INTERNAL "")
set(toml11_BUILD_TEST OFF CACHE INTERNAL "")
set(toml11_BUILD_TEST OFF CACHE INTERNAL "")
if(WIN32)
if(MSVC)
@ -73,28 +80,28 @@ FetchContent_Declare(
)
FetchContent_Declare(
ASM_JIT
PREFIX ${CMAKE_CURRENT_BINARY_DIR}
GIT_REPOSITORY git@github.com:asmjit/asmjit.git
GIT_SHALLOW true
INSTALL_COMMAND ""
CONFIGURE_COMMAND ""
ASM_JIT
PREFIX ${CMAKE_CURRENT_BINARY_DIR}
GIT_REPOSITORY git@github.com:asmjit/asmjit.git
GIT_SHALLOW true
INSTALL_COMMAND ""
CONFIGURE_COMMAND ""
)
FetchContent_Declare(
ASM_TK
PREFIX ${CMAKE_CURRENT_BINARY_DIR}
GIT_REPOSITORY git@github.com:asmjit/asmtk.git
GIT_SHALLOW true
INSTALL_COMMAND ""
ASM_TK
PREFIX ${CMAKE_CURRENT_BINARY_DIR}
GIT_REPOSITORY git@github.com:asmjit/asmtk.git
GIT_SHALLOW true
INSTALL_COMMAND ""
)
FetchContent_Declare(
Zydis
PREFIX ${CMAKE_CURRENT_BINARY_DIR}
GIT_REPOSITORY git@github.com:zyantific/zydis.git
GIT_SHALLOW true
INSTALL_COMMAND ""
Zydis
PREFIX ${CMAKE_CURRENT_BINARY_DIR}
GIT_REPOSITORY git@github.com:zyantific/zydis.git
GIT_SHALLOW true
INSTALL_COMMAND ""
)
FetchContent_MakeAvailable(Python DirectX ASM_JIT ASM_TK Zydis)
@ -125,6 +132,7 @@ add_custom_command(
COMMENT "Generating D3D8_VMT.hpp from d3d8.h"
VERBATIM
)
add_custom_target(D3D8_VMT ALL
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/D3D8_VMT.hpp")

View file

@ -31,7 +31,7 @@ This will find the Games's installation folder, verify that the version you have
- type `import ScrapHack`
- type `$help`
## Config file keys
## Config file keys (not yet implemented?)
- patches.asm: map of address->list of assembly instructions
- patches.hex: map of address->hex bytes
@ -61,4 +61,5 @@ Example:
- Zydis disassembler
- asmJIT/asmTK assembler
- nlohmann/json JSON-parser
- nlohmann/json JSON-parser
- LIEF/lief

View file

@ -1,5 +1,6 @@
@echo off
setlocal
set SCRAPLAND_DIR=%1
if "%VSINSTALLDIR%"=="" (
for /f "usebackq tokens=*" %%i in (`vswhere -latest -find **\vcvarsall.bat`) do (
call "%%i" x86
@ -7,13 +8,14 @@ if "%VSINSTALLDIR%"=="" (
)
if "%VSINSTALLDIR%"=="" (
echo "VSINSTALLDIR" not set something is wrong!
exit
) else (
if not exist build cmake -G"NMake Makefiles" -B build
if "%1"=="--run" (
if "%2"=="--run" (
cmake --build build --target run
) else (
cmake --build build --target install
)
)
:END
endlocal

24
ScrapHacks/patcher.py Normal file
View file

@ -0,0 +1,24 @@
import lief
import sys
exit("WIP, not really useful yet")
Scrap = lief.PE.parse(sys.argv[1])
data = []
section_data = lief.PE.Section(".hdata")
section_data.content = data
section_data.virtual_address = 0x8000
section_data.characteristics = (
lief.PE.SECTION_CHARACTERISTICS.CNT_INITIALIZED_DATA
| lief.PE.SECTION_CHARACTERISTICS.MEM_READ
)
sh = Scrap.add_library("_ScrapHack.pyd")
sh.add_entry("Init")
builder = lief.PE.Builder(Scrap)
builder.build_imports(True).patch_imports(True).build()
builder.write("Scrap_mod_sh.exe")

4
Scrapper_rs/.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
target
ext
.history
Cargo.lock

12
Scrapper_rs/Cargo.toml Normal file
View file

@ -0,0 +1,12 @@
[package]
name = "scrapper"
version = "0.1.0"
authors = ["Daniel Seiller <earthnuker@gmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
byteorder = "1.4.2"
encoding = "0.2.33"
structopt = {version="0.3.21",features = [ "paw" ]}

202
Scrapper_rs/src/main.rs Normal file
View file

@ -0,0 +1,202 @@
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use encoding::{all::WINDOWS_1252, EncoderTrap};
use encoding::{DecoderTrap, Encoding};
use std::{
collections::VecDeque,
convert::TryInto,
fs::{self, File},
io::Seek,
path::PathBuf,
};
use std::{fs::create_dir_all, io::SeekFrom};
use structopt::{StructOpt, paw};
use std::{
io::{BufReader, BufWriter, Read, Write},
path::Path,
};
#[derive(StructOpt)]
#[structopt(about = "Scrapland .packed packer and unpacker")]
enum Args {
/// Unpack .packed file
Unpack {
destination_folder: PathBuf,
packed_files: Vec<PathBuf>,
},
/// Repack .packed file
Repack {
input_folder: PathBuf,
destination_folder: PathBuf
}
}
#[derive(Debug, PartialEq, Eq)]
struct FileEntry {
path: String,
offset: u32,
size: u32,
}
#[derive(Debug, PartialEq, Eq)]
struct Packed {
path: PathBuf,
ext_path: Option<PathBuf>,
magic: [u8; 4],
version: [u8; 4],
files: Vec<FileEntry>,
}
impl Packed {
fn from_file(filename: &PathBuf) -> std::io::Result<Packed> {
let mut fh = BufReader::new(File::open(filename)?);
let magic: [u8; 4] = [fh.read_u8()?, fh.read_u8()?, fh.read_u8()?, fh.read_u8()?];
let version: [u8; 4] = [fh.read_u8()?, fh.read_u8()?, fh.read_u8()?, fh.read_u8()?];
println!("Magic: {:?}", magic);
println!("Version: {:?}", version);
let num_files = fh.read_u32::<LittleEndian>()?;
println!("{} files", num_files);
let mut files: Vec<FileEntry> = Vec::new();
for _ in 0..num_files {
let name_len = fh.read_u32::<LittleEndian>()?;
let mut name = vec![0; name_len as usize];
fh.read_exact(&mut name)?;
let size = fh.read_u32::<LittleEndian>()?;
let offset = fh.read_u32::<LittleEndian>()?;
let path = WINDOWS_1252.decode(&name, DecoderTrap::Strict).unwrap();
files.push(FileEntry { path, offset, size });
}
Ok(Packed {
path: filename.to_owned(),
magic,
version,
files,
ext_path: None,
})
}
fn size(&self) -> (u64, u64) {
let mut header_size: u64 = 4 * 3; // Magic+Version+Num files
let mut data_size: u64 = 0;
for entry in &self.files {
header_size += 4 * 3; // Name length, Offset, Size
let path = WINDOWS_1252
.encode(&entry.path, EncoderTrap::Strict)
.unwrap();
let hs: u64 = path.len().try_into().unwrap();
header_size += hs;
data_size += entry.size as u64;
}
(header_size, data_size)
}
fn from_folder(folder: PathBuf) -> std::io::Result<Self> {
let mut queue = VecDeque::new();
queue.push_back(folder.clone());
let mut header = Packed {
path: PathBuf::new(),
magic: [0x42, 0x46, 0x50, 0x4B], // BFPK
version: [0x00, 0x00, 0x00, 0x00],
files: vec![],
ext_path: Some(folder.clone()),
};
while let Some(dir) = queue.pop_front() {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
queue.push_back(path);
} else {
let size = path.metadata().unwrap().len().try_into().unwrap();
header.files.push(FileEntry {
path: path
.strip_prefix(folder.clone())
.unwrap()
.to_string_lossy()
.replace("\\", "/"),
offset: 0,
size,
});
}
}
}
let mut offset = header.size().0.try_into().unwrap();
for entry in header.files.iter_mut() {
entry.offset = offset;
offset += entry.size;
}
Ok(header)
}
fn write(&self, out_path: &PathBuf) -> std::io::Result<()> {
let base_path = self.ext_path.clone().unwrap();
let mut outfile = BufWriter::new(File::create(out_path)?);
outfile.write_all(&self.magic)?;
outfile.write_all(&self.version)?;
outfile.write_u32::<LittleEndian>(self.files.len() as u32)?;
println!("Building header");
for entry in &self.files {
let path = WINDOWS_1252
.encode(&entry.path, EncoderTrap::Strict)
.unwrap();
outfile.write_u32::<LittleEndian>(path.len().try_into().unwrap())?;
outfile.write_all(&path)?;
outfile.write_u32::<LittleEndian>(entry.size)?;
outfile.write_u32::<LittleEndian>(entry.offset)?;
}
let total = self.files.len();
for (n, entry) in self.files.iter().enumerate() {
println!(
"[{}/{}] Writing: {} (offset: {}, size: {})",
n + 1,
total,
entry.path,
entry.offset,
entry.size
);
let mut fh = BufReader::new(File::open(base_path.join(&entry.path))?);
std::io::copy(&mut fh, &mut outfile)?;
}
Ok(())
}
fn extract(&mut self, ext_folder: &PathBuf) -> std::io::Result<()> {
let total = self.files.len();
let ext_folder = ext_folder.join(&Path::new(&self.path).file_name().unwrap());
println!("Extracting to {}", ext_folder.to_string_lossy());
let mut fh = File::open(self.path.clone())?;
for (n, entry) in self.files.iter().enumerate() {
println!(
"[{}/{}] Extracting: {} (offset: {}, size: {})",
n + 1,
total,
entry.path,
entry.offset,
entry.size
);
fh.seek(SeekFrom::Start(entry.offset.try_into().unwrap()))?;
let mut data = vec![0; entry.size.try_into().unwrap()];
let path: PathBuf = PathBuf::from(&entry.path);
let file = Path::new(&ext_folder).join(path);
fh.read_exact(&mut data)?;
create_dir_all(file.parent().unwrap())?;
let mut fh = BufWriter::new(File::create(file)?);
fh.write_all(&data)?;
}
self.ext_path = Some(ext_folder);
Ok(())
}
}
#[paw::main]
fn main(args: Args) -> std::io::Result<()> {
match args {
Args::Unpack { packed_files, destination_folder } => {
for packed_file in &packed_files {
let mut pkd = Packed::from_file(&packed_file)?;
pkd.extract(&destination_folder)?;
}
}
Args::Repack { input_folder, destination_folder } => {
Packed::from_folder(input_folder)?.write(&destination_folder)?;
}
}
Ok(())
}

View file

@ -64,6 +64,9 @@ flags:
0x8c6140: P_module_filename
0x853954: P_D3DApp
0x853091: N_Uniones
0x7faa4c: server_state
0x8039a8: client_data
0x8038a0: client_ship
# 0x7fbe24:
# 0x7fa778:
@ -101,6 +104,48 @@ types:
- "struct HashTable { uint32_t size; struct HashTableEntry** data; };"
- "struct va_list { unsigned int gp_offset; unsigned int fp_offset; void *overflow_arg_area; void *reg_save_area; };"
functions:
0x5dadc0:
name: read_MAT_block
0x5daf10:
name: read_MAP_block
0x60d710:
name: read_INI_block
0x6155b0:
name: read_DUM_block
0x628760:
name: read_EMI_block
0x62c070:
name: read_AMC_block
0x62c580:
name: read_SUEL_block
0x631570:
name: read_CMSH_block
0x631940:
name: read_QUAD_block
0x64d380:
name: read_LUZ_block
0x650f80:
name: read_SCN_block
0x652480:
name: read_LFVF_block
0x658770:
name: read_CAM_block
0x6665a0:
name: read_CM3_block
0x666900:
name: read_SM3_block
0x6715e0:
name: read_PORT_block
0x675c90:
name: read_MD3D_block
0x6776d0:
name: read_NAM_block
0x6787a0:
name: read_ANI_block
0x6869b0:
name: read_SPR3_block
0x6875d0:
name: read_EVA_block
0x6283a0:
name: load_emi
0x4fa9f0:
@ -371,15 +416,9 @@ functions:
name: list_models
0x63a2f0:
name: gdi_draw_line
0x650f80:
name: load_sm3
0x6597d0:
name: read_ini_entry
signature: bool read_ini_entry(void* dest,const char* key, const char* section);
0x6665a0:
name: load_m3d_1
0x666900:
name: load_m3d_2
0x666c60:
name: read_m3d
0x6b1c70:

76
frida/utils.js Normal file
View file

@ -0,0 +1,76 @@
function dasm(addr, size) {
var size = size || 8;
var offset = 0;
var ret = [];
while (ret.length != size) {
var inst = Instruction.parse(ptr(addr).add(offset));
ret.push(("[" + inst.address + "] " + inst.mnemonic + " " + inst.opStr).trim());
offset += inst.size;
}
return ret;
}
function r(addr, options) {
var options = options || {}
var max_depth = options.max_depth || 4;
var num = options.num || 4;
var ret = {};
var vals = [
"S8",
"U8",
"S16",
"U16",
"S32",
"U32",
"Float",
"Double",
"Pointer",
"CString",
"Utf8String",
"Utf16String",
"AnsiString"
];
vals.forEach(function (k) {
try {
ret[k] = ptr(addr)['read' + k]()
} catch (e) {
ret[k] = undefined;
}
})
try {
ret["code"] = dasm(addr, 8);
} catch (e) {
ret["code"] = undefined;
}
if (max_depth > 1) {
var p = {};
var read_ptr = false;
for (var i = 0; i < num; ++i) {
if (ret["Pointer"] === undefined) {
continue;
}
p[i * Process.pointerSize] = r(ret["Pointer"].add(i * Process.pointerSize), {
max_depth: max_depth - 1,
num
});
read_ptr = true;
}
if (read_ptr) {
ret["p"] = p;
}
}
return ret;
}
// function test() {
// for (var p = 0; p < 4; ++p) {
// var player = ptr(0x7FE944).readPointer().add(0x288 + p * 4).readPointer();
// if (!player.isNull()) {
// console.log("Player " + (p+1) + ":", player);
// console.log(JSON.stringify(r(player),null,4));
// }
// }
// }

2367
helplib.md Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

9
notes/.gitignore vendored Normal file
View file

@ -0,0 +1,9 @@
# Created by https://www.toptal.com/developers/gitignore/api/mdbook
# Edit at https://www.toptal.com/developers/gitignore?templates=mdbook
### MdBook ###
book
# End of https://www.toptal.com/developers/gitignore/api/mdbook

25
notes/book.toml Normal file
View file

@ -0,0 +1,25 @@
[book]
authors = ["Daniel Seiller"]
language = "en"
multilingual = false
src = "src"
title = "Scrapland Reverse Engineering Notes"
[output.html]
preferred-dark-theme = "ayu"
mathjax-support = true
# [preprocessor.graphviz]
# command = "mdbook-graphviz"
[preprocessor.svgbob]
text_width = 8.0
text_height = 16.0
class = "bob"
font_family = "arial"
font_size = 14.0
stroke_width = 2.0
# there's using css-variables from theme:
stroke_color = "var(--fg)" # see default theme / variables.css
background_color = "transparent" # also useful `var(--bg)`
# all properties are optional.

140
notes/src/Chunked.md Normal file
View file

@ -0,0 +1,140 @@
# Chunked Formats
# General Block format
```cpp
struct Block {
unsigned char block_id[4],
uint32_t size,
unsigned char data[size],
}
template<typename T>
struct Block {
unsigned char block_id[4],
uint32_t size,
T data,
}
```
# Block IDs
File ID | Chunk IDs
------- | ------------------------------------------------------------------------------
AMC | AMC, CMSH, QUAD
CM3 | ANI, CM3, EVA, NAE, NAM, SCN
DUM | DUM, INI
EMI | EMI, LFVF, MAP, MAT, TRI
SM3 | ANI, CAM, INI, LFVF, LUZ, MAP, MAT, MD3D, NAE, NAM, PORT, SCN, SM3, SPR3, SUEL
Read types:
- `i`: 4-byte unsigned integer
- `s`: 4-byte signed integer
- `p`: length prefixed string
- `f`: 4-byte float
- `3f`: array of 3 4-byte floats
- `3i`: array of 3 4-byte unsigned integers
Chunk ID | Description | Reads
-------- | --------------------------- | ------------------------
AMC | Collision Data |
ANI | Animation data? |
CAM | Camera info? |
CMSH | Collision Mesh Data |
DUM | Dummy (map object) data |
INI | INI-Configuration data |
LFVF | FVF Vertex Data |
LUZ | Lighting information |
MAP | UV Map? |
MAT | Material information |
NAE | Animation Data? |
NAM | Animation Data? |
PORT | Map portals? | i==1, i, i, 4, 4
QUAD | Mesh data? |
SCN | Scene data? |
SUEL | Ground plane? | 0x18, 0xc, 4, 4, 4, 0x18
TRI | Triangle strip definitions? |
MD3D | 3D Model definition? |
# Format of Specific chunks
## INI
Configuration Data
```cpp
struct INI {
uint32_t num_sections;
struct {
uint32_t num_lines;
struct {
uint32_t num_chars;
char line[num_chars]
} lines[num_lines];
} sections[num_sections];
};
```
## LFVF
DirectX Flexible Vertex Format Data
```cpp
struct Vertex { // fields according to flags
float position[3]; // D3DFVF_XYZ | D3DFVF_XYZRHW | D3DFVF_XYZB*
float rhw; // D3DFVF_XYZRHW
float weights[3]; // D3DFVF_XYZB*
float normal[3]; // D3DFVF_NORMAL
float point_size; // D3DFVF_PSIZE
uint32_t diffuse; // D3DFVF_DIFFUSE, RGBA
uint32_t specular; //D3DFVF_SPECULAR, RGBA
float tex_coords[D3DFVF_TEXTUREFORMAT][D3DFVF_TEX]; // D3DFVF_TEX* and D3DFVF_TEXTUREFORMAT*
};
struct LFVF {
uint32_t unk;
uint32_t num_entries;
struct {
uint32_t FVF; // FVF vertex data configuration
uint32_t vert_size; //?,
uint32_t num_verts;
Vertex vertices[num_vers];
} entry[num_entries];
};
```
## DUM
Map object data
```cpp
struct DUM {
uint32_t unk_1;
uint32_t num_dummies;
uint32_t unk_2;
struct {
uint32_t name_length;
char name[name_length];
float position[3];
float rotation[3];
uint32_t has_ini;
if (has_ini) {
Block<INI> ini;
};
uint32_t unk_1; // has_next?
} sections[num_sections];
};
```
## MAP
```cpp
struct MAP {
uint32_t version;
uint32_t tex_name_len;
char tex_name[tex_name_len];
// TODO: rest
}
```

1
notes/src/Entities.md Normal file
View file

@ -0,0 +1 @@
# Entities

18
notes/src/File.md Normal file
View file

@ -0,0 +1,18 @@
# 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](Chunked.md)
- [Packed](Packed.md)
- [AI Pathfinding Graph](Nodegraph.md)

View file

@ -0,0 +1 @@
# File Formats

41
notes/src/Netplay.md Normal file
View file

@ -0,0 +1,41 @@
# Netplay
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`)

26
notes/src/Nodegraph.md Normal file
View file

@ -0,0 +1,26 @@
# AI Nodegraph
Used for pathfinding using the A*-Algorithm.
```cpp
// n= number of dimension (3 or 2)
template<size_t n>
struct Node {
float pos[n];
};
template<size_t n>
struct Edge {
uint32_t num_edge_nodes;
Node<n> nodes[];
};
template<size_t n>
struct Graph {
uint32_t num_nodes;
Node<n> nodes[];
uint32_t num_edges;
Edge<n> edges[];
};
```

1
notes/src/Overview.md Normal file
View file

@ -0,0 +1 @@
# Overview

15
notes/src/Packed.md Normal file
View file

@ -0,0 +1,15 @@
# Packed
```cpp
struct Header {
unsigned char magic[4]; // always BFPK
uint32_t version;
uint32_t number_of_files;
struct File {
uint32_t path_length;
char path[]; // latin1 encoding
uint32_t data_size;
uint32_t data_offset; // offset includes header size so it can be used directly in a seek() call
} files[];
};
```

2369
notes/src/Python.md Normal file

File diff suppressed because one or more lines are too long

1
notes/src/Python_API.md Normal file
View file

@ -0,0 +1 @@
# Python API

13
notes/src/SUMMARY.md Normal file
View file

@ -0,0 +1,13 @@
# Summary
- [Overview](./Overview.md)
- [ScrapEngine](./ScrapEngine.md)
- [World](./World.md)
- [Entities](./Entities.md)
- [Netplay](./Netplay.md)
- [Python API](./Python_API.md)
- [File Formats](./File_Formats.md)
- [Chunked Formats](./Chunked.md)
- [Packed](./Packed.md)
- [AI Nodegraph](./Nodegraph.md)
- [ScrapHacks](./ScrapHacks.md)

54
notes/src/ScrapEngine.md Normal file
View file

@ -0,0 +1,54 @@
# ScrapEngine
- 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 multiplayer dedicated server mode (needs to be used with `-server`)
- `-server`: start in multiplayer server mode
- `-build`: Rebuild `Data.packed` (needs a `filelist.2Bpack`)
## Ingame-Console
(Ctrl+\^ or right click on window title bar and select "switch console") (Handler @ `0x402190`)
* `<Code>`: Evaluate Python code
* `:<Var>`: Get Game Engine Variable
* `:<Var> <Val>`: Set Game Engine Variable
* `?`: Show all Game Engine Variables
* `?<String>`: Show all Game Engine Variables 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
(Scene graph 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 scene graph debugging console)
- `0x8b18f4`: pointer to Scenes Data (can be dumped using scene graph debugging console)
- `0x8b18f8`: pointer to active Models Data (can be dumped using scene graph debugging console)

1
notes/src/ScrapHacks.md Normal file
View file

@ -0,0 +1 @@
# ScrapHacks

80
notes/src/World.md Normal file
View file

@ -0,0 +1,80 @@
# World
## Game World/State Pointer @ `0x7fe944`
Points to World struct
Offset | Type | Description
------ | ------------------------ | --------------------------------------
0x0000 | `void**` | Virtual Method Table
0x0004 | `uint32_t` | Slots in 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]
0x0298 | `uint32_t` | Slots in Model Hashtable
0x029C | `void**` | Pointer to Model Hashtable
0x02B8 | `uint32_t` | Slots in Entity lists Hashtable
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`
- ...

21
parse_save.py Normal file
View file

@ -0,0 +1,21 @@
import sys
from construct import *
from pprint import pprint
ScrapSaveVar = Struct(
"name" / PascalString(Int32ul, encoding="utf-8"),
"data" / PascalString(Int32ul, encoding="utf-8"),
)
ScrapSave = "ScarpSaveGame" / Struct(
"title" / PascalString(Int32ul, encoding="utf-8"),
"id" / PascalString(Int32ul, encoding="utf-8"),
"data" / PrefixedArray(Int32ul, ScrapSaveVar),
Terminated,
)
with open(sys.argv[1], "rb") as sav_file:
save = ScrapSave.parse_stream(sav_file)
print("ID:", save.id)
print("Title:", save.title)
for var in save.data:
print(" {}: {}".format(var.name, var.data))

View file

@ -113,7 +113,6 @@ for addr, func in config.functions.items():
r2_cmd(f"afr fcn.{name} {hex(addr)}")
r2_cmd(f"afn fcn.{name} {hex(addr)}")
if sig:
sig = sig.replace(name, "fcn." + name)
r2_cmd(f'"afs {sig}" @{hex(addr)}')
@ -305,7 +304,7 @@ print("[+] Wrote scrap_dissect.x32dbg.txt")
with open(r2_script_path, "w") as of:
wcmds = []
for cmd in r2cmds:
if cmd == "avj":
if cmd=="avj":
continue
record = True
for start in ["p", "/", "s"]:
@ -317,7 +316,6 @@ with open(r2_script_path, "w") as of:
print("[+] Wrote scrap_dissect.r2")
def start_program(cmdl, **kwargs):
if os.name == "nt":
return SP.Popen(["cmd", "/c", "start"] + cmdl, **kwargs)

343
rz_analyze.py Normal file
View file

@ -0,0 +1,343 @@
import rzpipe
import os
import json
from datetime import datetime
import subprocess as SP
from tqdm import tqdm
import sys
import yaml
tqdm_ascii = False
rzcmds = []
x64_dbg_script = []
script_path = os.path.dirname(os.path.abspath(__file__))
scrap_exe = os.path.abspath(sys.argv[1])
scrapland_folder = os.path.abspath(os.path.dirname(scrap_exe))
rz_script_path = os.path.join(scrapland_folder, "scrap_dissect.rz")
x64_dbg_script_path = os.path.join(scrapland_folder, "scrap_dissect.x32dbg.txt")
json_path = os.path.join(scrapland_folder, "scrap_dissect.json")
assert os.path.isfile(scrap_exe), "File not found!"
rz = rzpipe.open(scrap_exe)
file_hashes = rz.cmdj("itj")
target_hashes = {
"sha1": "d2dde960e8eca69d60c2e39a439088b75f0c89fa",
"md5": "a934c85dca5ab1c32f05c0977f62e186",
"sha256": "24ef449322f28f87b702834f1a1aac003f885db6d68757ff29fad3ddba6c7b88",
}
assert file_hashes == target_hashes, "Hash mismatch"
def x64_dbg_label(addr, name, prefix=None):
global x64_dbg_script
if isinstance(addr, int):
addr = hex(addr)
if prefix:
x64_dbg_script.append(f'lbl {addr},"{prefix}.{name}"')
else:
x64_dbg_script.append(f'lbl {addr},"{name}"')
def rz_cmd(cmd):
global rz, rzcmds
rzcmds.append(cmd)
return rz.cmd(cmd)
def rz_cmdj(cmd):
global rz, rzcmds
rzcmds.append(cmd)
return rz.cmdj(cmd)
def rz_cmdJ(cmd):
global rz, rzcmds
rzcmds.append(cmd)
return rz.cmdJ(cmd)
t_start = datetime.today()
def analysis(full=False):
print("[*] Running analysis")
steps = []
if full:
steps = [
"e anal.dataref = true",
# "e anal.esil = true",
"e anal.jmp.after = true",
"e anal.jmp.indir = true",
"e anal.loads = true",
"e anal.pushret = true",
"e anal.refstr = true",
"e anal.strings = true",
"e anal.vinfun = true",
"e asm.anal = true",
]
if full:
steps += ["aaaa"]
else:
steps += ["aaa"]
for ac in steps:
print(f"[*] Running '{ac}'")
rz_cmd(f"{ac} 2>NUL")
with open(os.path.join(script_path, "config.yml")) as cfg:
print("[*] Loading config")
config = type("Config", (object,), yaml.load(cfg, Loader=yaml.SafeLoader))
for line in config.script.strip().splitlines():
rz_cmd(line)
analysis(False)
for addr, comment in config.comments.items():
rz_cmd(f"CC {comment} @ {hex(addr)}")
for t in config.types:
rz_cmd(f'"td {t}"')
for addr, name in config.flags.items():
x64_dbg_label(addr, name, "loc")
rz_cmd(f"f loc.{name} 4 {hex(addr)}")
for addr, func in config.functions.items():
name, sig = func.get("name"), func.get("signature")
if name:
x64_dbg_label(addr, name, "fcn")
rz_cmd(f"afr fcn.{name} {hex(addr)}")
rz_cmd(f"afn fcn.{name} {hex(addr)}")
if sig:
sig = sig.replace(name, "fcn." + name)
rz_cmd(f'"afs {sig}" @{hex(addr)}')
def vtables():
ret = {}
print("[*] Analyzing VTables")
vtables = rz_cmdJ("avj")
for c in tqdm(vtables, ascii=tqdm_ascii):
methods = []
name = config.VMTs.get(c.offset, f"{c.offset:08x}")
x64_dbg_label(c.offset, name, "vmt")
rz_cmd(f"f vmt.{name} 4 {hex(c.offset)}")
for idx, m in enumerate(tqdm(c.methods, ascii=tqdm_ascii, leave=False)):
methods.append(hex(m.offset))
x64_dbg_label(m.offset, f"{name}.{idx}", "fcn.vmt")
rz_cmd(f"afr fcn.vmt.{name}.{idx} {hex(m.offset)} 2>NUL")
ret[hex(c.offset)] = methods
return ret
def c_callbacks():
print("[*] Parsing C Callbacks")
funcs = {}
res = rz_cmd("/r fcn.register_c_callback ~CALL[1]").splitlines()
for addr in tqdm(res, ascii=tqdm_ascii):
rz_cmd(f"s {addr}")
rz_cmd(f"so -3")
func, name = rz_cmdJ(f"pdj 2")
func = func.refs[0].addr
name = rz_cmd(f"psz @{hex(name.refs[0].addr)}").strip()
rz_cmd(f"afr fcn.callbacks.{name} {hex(func)} 2>NUL")
x64_dbg_label(func, f"{name}", "fcn.callbacks")
funcs[name] = hex(func)
return funcs
def assertions():
assertions = {}
for (n_args, a_addr) in [
(3, "fcn.throw_assertion_1"),
(4, "fcn.throw_assertion_2"),
]:
print(f"[*] Parsing C assertions for {a_addr}")
res = rz_cmd(f"/r {a_addr} ~CALL[1]").splitlines()
print()
for line in tqdm(res, ascii=tqdm_ascii):
addr = line.strip()
rz_cmd(f"s {addr}")
rz_cmd(f"so -{n_args}")
dis = rz_cmdJ(f"pij {n_args}")
if n_args == 4:
line, date, file, msg = dis
elif n_args == 3:
date = None
line, file, msg = dis
try:
file = rz_cmd(f"psz @{file.refs[0].addr}").strip()
msg = rz_cmd(f"psz @{msg.refs[0].addr}").strip()
if date:
date = rz_cmd(f"psz @{date.refs[0].addr}").strip()
line = line.val
file = file.replace("\\\\", "\\")
assertions.setdefault(file, [])
assertions[file].append(
{"line": line, "date": date, "addr": addr, "msg": msg}
)
except:
pass
for path in assertions:
assertions[path].sort(key=lambda v: v["line"])
return assertions
def bb_refs(addr):
ret = {}
res = rz_cmd(f"/r {addr} ~fcn[0,1]").splitlines()
print()
for ent in tqdm(res, ascii=tqdm_ascii):
func, hit = ent.split()
ret[hit] = {"asm": [], "func": func}
for ins in rz_cmdJ(f"pdbj @{hit}"):
ret[hit]["asm"].append(ins.disasm)
return ret
def world():
print("[*] Parsing World offsets")
return bb_refs("loc.P_World")
def render():
print("[*] Parsing D3D_Device offsets")
return bb_refs("loc.P_D3D8_Dev")
def py_mods():
print("[*] Parsing Python modules")
res = rz_cmd("/r fcn.Py_InitModule ~CALL[1]").splitlines()
print()
py_mods = {}
for call_loc in tqdm(res, ascii=tqdm_ascii):
rz_cmd(f"s {call_loc}")
rz_cmd(f"so -3")
args = rz_cmdJ("pdj 3")
refs = []
if not all(arg.type == "push" for arg in args):
continue
for arg in args:
refs.append(hex(arg.val))
doc, methods, name = refs
doc = rz_cmd(f"psz @{doc}").strip()
name = rz_cmd(f"psz @{name}").strip()
rz_cmd(f"s {methods}")
rz_cmd(f"f py.{name} 4 {methods}")
x64_dbg_label(methods, f"{name}", "py")
py_mods[name] = {"methods_addr": methods, "doc": doc, "methods": {}}
while True:
m_name, m_func, _, m_doc = [v.value for v in rz_cmdJ(f"pfj xxxx")]
if m_name == 0:
break
m_name, m_func, m_doc = map(hex, (m_name, m_func, m_doc))
m_name = rz_cmd(f"psz @{m_name}").strip()
rz_cmd(f"f py.{name}.{m_name}.__doc__ 4 {m_doc}")
if int(m_doc, 16) != 0:
x64_dbg_label(m_doc, f"{name}.{m_name}.__doc__", "py")
m_doc = rz_cmd(f"psz @{m_doc}").strip()
else:
m_doc = None
py_mods[name]["methods"][m_name] = {"addr": m_func, "doc": m_doc}
rz_cmd(f"afr py.{name}.{m_name} {m_func} 2>NUL")
x64_dbg_label(m_func, f"{name}.{m_name}", "fcn.py")
rz_cmd("s +16")
return py_mods
def game_vars():
ret = {}
print("[*] Parsing Game variables")
res = rz_cmd("/r fcn.setup_game_vars ~CALL[1]").splitlines()
print()
for line in tqdm(res, ascii=tqdm_ascii):
addr = line.strip()
rz_cmd(f"s {addr}")
args = rz_cmd("pdj -5") # seek and print disassembly
if not args:
continue
args = json.loads(args)
args_a = []
push_cnt = 0
for arg in args[::-1]:
if arg["type"] not in ["push", "mov"]:
continue
if arg["type"] == "push":
push_cnt += 1
args_a.append(arg)
if push_cnt == 3:
break
if len(args_a) != 4:
continue
if not all("val" in v for v in args_a):
continue
addr, name, _, desc = [v["val"] for v in args_a]
name = rz_cmd(f"psz @{hex(name)}").strip()
desc = rz_cmd(f"psz @{hex(desc)}").strip()
addr = hex(addr)
rz_cmd(f"f loc.gvar.{name} 4 {addr}")
x64_dbg_label(addr, f"{name}", "loc.gvar")
ret[addr] = {"name": name, "desc": desc}
return ret
ret = {}
# world, render
for func in ["game_vars", "c_callbacks", "py_mods", "assertions", "vtables"]:
ret[func] = globals()[func]()
analysis(True)
with open(json_path, "w") as of:
json.dump(ret, of, indent=4)
print("[+] Wrote scrap_dissect.json")
with open(x64_dbg_script_path, "w") as of:
of.write("\n".join(x64_dbg_script))
print("[+] Wrote scrap_dissect.x32dbg.txt")
with open(rz_script_path, "w") as of:
wcmds = []
for cmd in rzcmds:
if cmd == "avj":
continue
record = True
for start in ["p", "/", "s"]:
if cmd.strip('"').startswith(start):
record = False
if record:
wcmds.append(cmd)
of.write("\n".join(wcmds))
print("[+] Wrote scrap_dissect.rz")
def start_program(cmdl, **kwargs):
if os.name == "nt":
return SP.Popen(["cmd", "/c", "start"] + cmdl, **kwargs)
else:
return SP.Popen(cmdl, **kwargs)
print("[+] Analysis took:", datetime.today() - t_start)
rz.cmd("Ps Scrap.rzdb")
exit()
print("[+] Executing Cutter")
try:
start_program(
["cutter", "-A", "0", "-i", rz_script_path, scrap_exe],
cwd=scrapland_folder,
shell=False,
)
except FileNotFoundError:
print("[-] cutter not installed, falling back to rz")
start_program(
["rz", "-i", rz_script_path, scrap_exe], cwd=scrapland_folder, shell=False
)

132
scrapper.py Normal file
View file

@ -0,0 +1,132 @@
import argparse
from collections import OrderedDict
import glob
import os
import shutil
from construct import *
from tqdm import tqdm
setglobalstringencoding(None)
ScrapFile = Struct(
"path" / PascalString(Int32ul),
"size" / Int32ul,
"offset" / Int32ul,
"data" / OnDemandPointer(this.offset, Bytes(this.size)),
)
DummyFile = Struct("path" / PascalString(Int32ul), "size" / Int32ul, "offset" / Int32ul)
PackedHeader = Struct(
Const(b"BFPK"), Const(b"\0\0\0\0"), "files" / PrefixedArray(Int32ul, ScrapFile)
)
DummyHeader = Struct(
Const(b"BFPK"), Const(b"\0\0\0\0"), "files" / PrefixedArray(Int32ul, DummyFile)
)
parser = argparse.ArgumentParser(description="Unpack and Repack .packed files")
parser.add_argument(
"-u", "--unpack", action="store_true", help="unpack file to 'extracted' directory"
)
parser.add_argument(
"-r", "--repack", action="store_true", help="repack file from 'extracted' directory"
)
parser.add_argument(
"--reset", action="store_true", default=False, help="restore backup"
)
parser.add_argument(
"scrap_dir",
metavar="Scrapland Directory",
type=str,
default=".",
help="Scrapland installation directory",
)
options = parser.parse_args()
scrap_dir = os.path.abspath(options.scrap_dir)
if options.reset:
print("Restoring Backups and removing extracted folder...")
for packed_file in glob.glob(os.path.join(scrap_dir, "*.packed.bak")):
outfile = os.path.basename(packed_file)
orig_filename = outfile[:-4]
if os.path.isfile(outfile):
print("deleting", orig_filename)
os.remove(orig_filename)
print("moving", outfile, "->", orig_filename)
shutil.move(outfile, orig_filename)
target_folder = os.path.join("extracted", os.path.basename(orig_filename))
print("deleting", target_folder)
shutil.rmtree(target_folder)
if os.path.isdir("extracted"):
input("Press enter to remove rest of extracted folder")
shutil.rmtree("extracted")
exit("Done!")
if not (options.unpack or options.repack):
parser.print_help()
exit()
pstatus = ""
if options.unpack:
if os.path.isdir("extracted"):
print("Removing extracted folder")
shutil.rmtree("extracted")
for packed_file in glob.glob(os.path.join(scrap_dir, "*.packed")):
os.chdir(scrap_dir)
BN = os.path.basename(packed_file)
target_folder = os.path.join("extracted", os.path.basename(packed_file))
os.makedirs(target_folder, exist_ok=True)
os.chdir(target_folder)
print("Unpacking {}".format(os.path.basename(packed_file)))
with open(packed_file, "rb") as pkfile:
data = PackedHeader.parse_stream(pkfile)
print("Offset:", hex(pkfile.tell()))
for file in tqdm(data.files, ascii=True):
folder, filename = os.path.split(file.path)
if folder:
os.makedirs(folder, exist_ok=True)
with open(file.path, "wb") as outfile:
outfile.write(file.data())
print("\r" + " " * len(pstatus) + "\r", end="", flush=True)
os.chdir(scrap_dir)
if options.unpack and options.repack:
input(
"Press enter to rebuild *.packed files from folders in 'extracted' dir..."
) # noqa
pass
def file_gen(files, offset=0):
for real_path, size, path in files:
file = dict(path=path, offset=offset, size=size)
yield file
offset += file["size"]
def make_header(files, offset=0):
files_list = list(file_gen(files, offset))
return DummyHeader.build(dict(files=files_list))
if options.repack:
for folder in glob.glob(os.path.join(scrap_dir, "extracted", "*.packed")):
data = []
filename = os.path.join(scrap_dir, os.path.basename(folder))
for root, folders, files in os.walk(folder):
for file in sorted(files):
file = os.path.join(root, file)
rel_path = bytes(
file.replace(folder, "").replace("\\", "/").lstrip("/"),
"windows-1252",
)
size = os.stat(file).st_size
data.append((file, size, rel_path))
print("Found {} files for {}".format(len(data), filename))
offset = len(make_header(data))
print("Writing", filename)
header = make_header(data, offset)
with open(filename, "wb") as outfile:
outfile.write(header)
for file, size, rel_path in tqdm(data, ascii=True):
outfile.write(open(file, "rb").read())
print("Done!")

View file

@ -8,10 +8,13 @@ import string
import re
from binascii import hexlify
def gen():
with open(sys.argv[1], "rb") as fh:
size = os.stat(sys.argv[1]).st_size
progbar = tqdm(total=size, unit="bytes", unit_scale=True, unit_divisor=1024)
progbar = tqdm(
total=size, unit="bytes", unit_scale=True, unit_divisor=1024, leave=False
)
pos = 0
for entry in mp.Unpacker(fh, raw=True):
progbar.update(fh.tell() - pos)
@ -27,14 +30,10 @@ def gen():
yield entry
def strdump(data):
printable_chars = set(bytes(string.printable, "ascii")) - set(b"\n\r\t\x0b\x0c")
return "".join(chr(c) if c in printable_chars else "." for c in data)
def tohex(data):
return str(hexlify(data), "utf8").upper()
# best=sorted(tqdm(gen(),ascii=True),key=lambda v:len(v['data']),reverse=True)
@ -53,16 +52,24 @@ def tohex(data):
# entry['infos'][fmt]=[v[0] for v in struct.iter_unpack(fmt,data)]
# return entry
filters=[re.compile(s) for s in sys.argv[2:]]
filters = sys.argv[2:]
with open("all.log", "w") as of:
for entry in gen():
fm=[(f.match(entry['filename']) is not None) for f in filters]
if filters and not any(fm):
fm = any(
all(s.lower() in entry["filename"].lower() for s in f.split("|"))
for f in filters
)
if filters and not fm:
continue
is_magic = bytes(entry["block_id"], "utf8").ljust(4, b"\0") == entry["data"]
entry["data_len"] = len(entry["data"])
entry["str"] = strdump(entry["data"])
entry["data"] = tohex(entry["data"])
entry["data"] = entry["data"].hex().upper()
if is_magic:
print("#" * 50, file=of)
print(
"{timestamp} {block_id} {filename} {data_len:08X} {data} {str}".format(**entry), file=of
"{timestamp} {block_id} {filename} {data_len:08X} {data} {str}".format(
**entry
),
file=of,
)

View file

@ -38,12 +38,12 @@ def seek_to(fh, offset, pos=None):
yield
fh.seek(pos)
def read_array(s,fh):
ret=[]
def read_array(s, fh):
ret = []
count = read_struct("<I", fh)[0]
size = struct.calcsize(s)
for _ in range(count):
ret.append(read_struct(s,fh))
ret.append(read_struct(s, fh))
return ret
@ -93,7 +93,10 @@ class Parser:
print("{}[{}] {} bytes".format(" " * self.depth, magic, len(data)))
self.depth += 1
fh = BytesIO(data)
ret = getattr(self, magic, lambda fh: self._default(magic, fh))(fh)
if len(data) != 0:
ret = getattr(self, magic, lambda fh: self._default(magic, fh))(fh)
else:
ret = None
pos = fh.tell()
leftover = len(fh.read())
fh.seek(pos)
@ -198,17 +201,27 @@ class Parser:
dum = {}
dum["name"] = read_str(fh)
dum["pos"] = read_struct("<fff", fh)
dum["rot"] = read_struct("<fff", fh)
dum["ang"] = read_struct("<fff", fh)
dum["has_ini"] = read_struct("<I", fh)[0]
if dum["has_ini"]:
dum['ini']=self.parse_block(fh)
dum["has_next"] = read_struct("<I", fh)[0]
dum["ini"] = self.parse_block(fh)
dum["unk_1"] = read_struct("<I", fh)[0]
ret["dummies"].append(dum)
assert fh.read() == b"", "Leftover Data"
return ret
# def AMC(self, fh):
# return len(fh.read())
def AMC(self, fh):
ret = {}
ret["unk_1"] = read_struct("<I", fh)[0]
ret["unk_2"] = read_struct("<I", fh)[0]
ret["floats"] = read_struct("<16f", fh) # ???
ret["cmsh_1"] = [self.parse_block(fh) for _ in range(2)]
ret["num_submeshes"] = read_struct("<I", fh)[0]
ret["cmsh_2"] = [self.parse_block(fh) for _ in range(ret["num_submeshes"])]
return ret
def CMSH(self, fh):
return {"ret": len(fh.read())}
# def EMI(self, fh):
# return len(fh.read())
@ -218,30 +231,14 @@ class Parser:
basedir = r"D:/Games/Deep Silver/Scrapland/extracted/Data.packed"
files = [
r"Models/Chars/Dtritus/Dtritus.sm3",
r"Models/Elements/AnilloEstructuraA/AnilloEstructuraA.SM3",
r"models/elements/antenaa/antenaa.lod1.sm3",
# r"models/elements/abshield/anm/loop.cm3",
# r"levels/fake/map/map3d.amc",
# r"levels/shipedit/map/map3d.dum",
# r"levels/menu/map/map3d.emi",
r"Models/Skies/Menu/Sky.SM3",
r"Levels/Menu/Map/Map3D.SM3",
r"Models/Elements/AnilloEstructuraD/AnilloEstructuraD.LOD1.SM3",
# r"levels/menu/map/map3d.amc",
# r"levels/menu/map/map3d.dum",
# r"levels/menu/map/scenecamera/anm/loop.cm3",
r"models/chars/boss/boss.sm3",
# r"models/chars/boss/anm/boss_walk.cm3",
]
filt = [s.lower() for s in sys.argv[1:]]
for root, folders, files in os.walk(basedir):
for root, _, files in os.walk(basedir):
for file in files:
path = os.path.join(root, file).replace("\\","/")
if not path.lower().endswith(".dum".lower()):
path = os.path.join(root, file).replace("\\", "/")
if not path.lower().endswith(".amc".lower()):
continue
if "menu" not in path.lower():
continue
print("Parsing", path)
p = Parser(debug=True)
@ -252,4 +249,3 @@ for root, folders, files in os.walk(basedir):
break
pprint(parsed, compact=False, indent=4)
print("#" * 50)

21
tools/parse_save.py Normal file
View file

@ -0,0 +1,21 @@
import sys
from construct import *
from pprint import pprint
ScrapSaveVar = Struct(
"name" / PascalString(Int32ul, encoding="utf-8"),
"data" / PascalString(Int32ul, encoding="utf-8"),
)
ScrapSave = "ScarpSaveGame" / Struct(
"title" / PascalString(Int32ul, encoding="utf-8"),
"id" / PascalString(Int32ul, encoding="utf-8"),
"data" / PrefixedArray(Int32ul, ScrapSaveVar),
Terminated,
)
with open(sys.argv[1], "rb") as sav_file:
save = ScrapSave.parse_stream(sav_file)
print("ID:", save.id)
print("Title:", save.title)
for var in save.data:
print(" {}: {}".format(var.name, var.data))

View file

@ -3,29 +3,18 @@ from collections import OrderedDict
import glob
import os
import shutil
from construct import (
Struct,
PascalString,
Int32ul,
Lazy,
Pointer,
Bytes,
this,
PrefixedArray,
Const,
Debugger
)
from construct import *
from tqdm import tqdm
setglobalstringencoding(None)
ScrapFile = Struct(
"path" / PascalString(Int32ul, encoding="ascii"),
"path" / PascalString(Int32ul),
"size" / Int32ul,
"offset" / Int32ul,
"data" / Lazy(Pointer(this.offset, Bytes(this.size))),
)
DummyFile = Struct(
"path" / PascalString(Int32ul, encoding="u8"), "size" / Int32ul, "offset" / Int32ul
"data" / OnDemandPointer(this.offset, Bytes(this.size)),
)
DummyFile = Struct("path" / PascalString(Int32ul), "size" / Int32ul, "offset" / Int32ul)
PackedHeader = Struct(
Const(b"BFPK"), Const(b"\0\0\0\0"), "files" / PrefixedArray(Int32ul, ScrapFile)