diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b30c4da3e..0f58a358e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,9 @@ - [Project structure](#project-structure) - [Decompiling](#decompiling) - [Code style](#code-style) -- [Creating new `.c`/`.cpp` files](#creating-new-ccpp-files) + - [Naming new things](#naming-new-things) + - [Creating a class](#creating-a-class) + ## Project structure - `build/`: Build output @@ -33,3 +35,60 @@ See [/docs/decompiling.md](/docs/decompiling.md). ## Code style This project has a `.clang-format` file and all C/C++ files in this project should follow it. We recommend using an editor compatible with `clang-format` to format the code as you save. + +As a rule of thumb, try to mimick the style that can be observed in already decompiled files. Please write hexadecimal numbers in upper case (`0x9ABCDEF` instead of `0x9abcdef`). + +### Naming new things + +You may have to create new classes, structs, member attributes or functions, etc. Here is described how to name them according to what they are. + +Once you find out what something does, it helps to give it a meaningfull name (eg. `ModelRender` class, `Actor::isAlive()` function or `Actor.mPrevPos` member attribute). + +If you don't know yet what a piece of code does, try to follow this rough format: `{type}_ov{num}_{address}`. +- `type` is the kind of code you're naming, `UnkStruct` for a struct, `mUnk` for a member attribute, `Unk{O}System{X}` for a class or group of functions. In the last case, `X` would then be an arbitrary, unique identifier. Likely a number that would increase for every new `System` to name. `O` is optional and aimed to give more information about the context in which the system is used (eg. `File` or `Actor`). +- `num` is the id of the overlay the code is part of. +- `address` is the address of the data you're naming. This may not always be applicable, in which case you can ignore it (and remove the trailing `_` of the format given above). + +### Creating a class +If you are to create a new class, try to follow this structure: +```cpp +class Foo { +public: + /* 00 */ int mBar; + /* 04 */ + + Foo(); + ~Foo(); + + /* 00 */ virtual void vfunc_00(); + /* 04 */ virtual void vfunc_04(); + /* 08 */ + + // itcm + bool func_01fff1e0(); + + // overlay 0 + void func_ov000_0208a318(unk32 param1, unk32 param2, unk32 param3); + void func_ov000_0208bbd4(unk32 param1, VecFx32 *param2, u16 param3); + + static UnkStruct_027e0ce0_34 *func_ov000_0205c904(); + + // overlay 1 + void func_ov001_020bc5f8(); + void func_ov001_020bc524(bool param1); + + static Foo *Create(); + static void Destroy(); + + // overlay 17 + void func_ov017_020bd69c(); +}; +``` + +In order, the parts are: +- Member attributes. +- Constructor (ctor) and destructor (dtor). +- Virtual functions. +- Other methods, grouped by overlay with a comment indicating which one. + +Using `private` may sometime be required to enable some inlining, so feel free to when use it you feel like you should. diff --git a/INSTALL.md b/INSTALL.md index 84af70851..a4c84ba9e 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -5,7 +5,7 @@ Contents: - [Prerequisites](#prerequisites) - [Build the ROM](#build-the-rom) - [Matching the base ROM](#matching-the-base-rom) - - [Building with non-matching code](#building-with-non-matching-code) + - [[Optional] LSP setup](#lsp-setup) ## Prerequisites @@ -40,6 +40,8 @@ Now you can run `ninja` to build a ROM for the chosen version. > [!NOTE] > For Linux users: Wibo is used by default. If you want to use Wine instead, run `configure.py` with `-w `. +## Build the ROM + ### Matching the base ROM **This is optional!** You only need to follow these steps if you want a matching ROM. @@ -58,9 +60,9 @@ ARM7 BIOS in the root directory of this repository, and verify that your dumped The repository contains a [`CMakeLists.txt`](CMakeLists.txt) that allows generating a compilation database. For now, the `CMakeLists.txt` can only be used to generate `compile_commands.json` and similar files, not compiling the project. To generate the compilation database, run `cmake -S . -G "Unix Makefiles" -B cmake` from the root directory of the project. This will create a `cmake/` directory that contains the `compile_commands.json`. -Once the file is generated, you can dynamically link it to the root directory and let your LSP detect it (make sure not to `git add` it though), or edit your `.clangd` as follows for it to recognize the compilation database: +Once the file is generated, you can dynamically link it to the root directory and let your LSP detect it (make sure not to `git add` it though, even though the project's `.gitignore` should prevent it), or edit your `.clangd` as follows for it to recognize the compilation database: ```clangd CompileFlags: - CompilationDatabase: "cmake" + CompilationDatabase: "cmake" # path to the compilation database ``` This setup is adapted from a [tutorial by Strus](https://gist.github.com/Strus/042a92a00070a943053006bf46912ae9), refer to his post for further details. diff --git a/README.md b/README.md index c1b9e410b..8cef65b85 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,7 @@ The Legend of Zelda: Spirit Tracks [Discord Badge]: https://img.shields.io/discord/688807550715560050?color=%237289DA&logo=discord&logoColor=%23FFFFFF [discord]: https://discord.com/invite/DqwyCBYKqf/ -**Work in progress!** This project aims to recreate source code for ***The Legend of Zelda: Spirit Tracks*** by decompiling its code by hand. **The repository does not contain assets or assembly code.** To build the ROM, you must own an existing -copy of the game to extract assets from. +**Work in progress!** This project aims to recreate source code for ***The Legend of Zelda: Spirit Tracks*** by decompiling its code by hand. **The repository does not contain assets or assembly code.** To build the ROM, you must own an existing copy of the game to extract assets from. > [!NOTE] > The project targets the European and Japanese versions, and other versions might be supported later. @@ -19,7 +18,7 @@ copy of the game to extract assets from. See [INSTALL.md](INSTALL.md) for instructions on how to install the project. ## Contribution -A work in progress, but [CONTRIBUTING.md](CONTRIBUTING.md) has guidelines for how to contribute to the project. +A work in progress, but [CONTRIBUTING.md](CONTRIBUTING.md) has guidelines for how to contribute to the project. Make sure to follow instructions on [installation](#how-to-install) first. ## Documentation See [/docs](/docs) for documentation about the game. diff --git a/docs/decompiling.md b/docs/decompiling.md new file mode 100644 index 000000000..bc56d0a97 --- /dev/null +++ b/docs/decompiling.md @@ -0,0 +1,112 @@ +# Decompiling +This document describes how you can start decompiling code and contribute to the project. Feel free to ask for help if you get +stuck or need assistance. +- [Pick a source file](#pick-a-source-file) +- [Decompiling a source file](#decompiling-a-source-file) +- [Decompiling a function](#decompiling-a-function) +- [Decompiling `.init` functions](#decompiling-init-functions) +- [The Ghidra project](#the-ghidra-project) + +## Pick a source file +For actors and map objects, a reservation sheet exists for a list of delinked source files that are ready to be decompiled. This list grows as more source files are delinked from the rest of the base ROM. You can request access to the sheet in the ZeldaRET discord [channels for ST](https://discord.com/channels/688807550715560050/1453177153502969977) (you can join the server with [this invite link](https://discord.gg/6tjntnU8hC)). + +You can claim a source file (called an "actor") by changing its state to "reserved" in the "Reserved by" column. Once you started decompilation, create a PR on the ST repository for the actor you're decompiling. The decomp-dev bot will follow your PR and give information about the decompilation progress of your code. + +If you want to unclaim the file, leave a comment your the PR and mark the actor as "available" on the reservation sheet so we can be certain that the source file is available to be claimed again. +Remember to make a pull request of any progress you made on the source file, whether it is just header files or partially decompiled code. + +> [!NOTE] +> If you want to decompile a non-actor file, instead of filling an entry in the spreadsheet, open [an issue](https://github.com/zeldaret/st/issues) for it on top of the PR and mark it with appropriate labels ("decomp", "reserved", etc). You can find a detailed list of the labels [on github](https://github.com/zeldaret/st/labels). + +## Decompiling a source file +We use the object diffing tool [`objdiff`](https://github.com/encounter/objdiff) to track differences between our decompiled C++ code and the base ROM's code. +1. [Download the latest release.](https://github.com/encounter/objdiff/releases/latest) +1. Run `configure.py [--version|-v ]` and `ninja` to generate `objdiff.json` in the repository root (don't forget to follow the instructions in [INSTALL.md](../INSTALL.md) first). Note: if `--version` isn't passed the project will be configured to use all supported versions (meaning all versions will be showed on objdiff. +1. In `objdiff`, set the project directory to the repository root (it should load `objdiff.json` itself). + - [WSL only] If you're using WSL (which is possible to do, although a few things may not work), navigate to the project directory with window's directory picker tool and select it. Do not set the path manually unless you know what you're doing, `objdiff` may use different path format over time. +1. Select your source file in the left sidebar: +![List of objects in objdiff](images/objdiff_objects.png) +1. See the list of functions and data to decompile: +![List of symbols in objdiff](images/objdiff_symbols.png) + +The following sections explain how to decompile the different parts you see in `objdiff`. + +> [!NOTE] +> If a source file is missing in `objdiff`, or `objdiff` fails to build a file, first rerun `ninja` to update `objdiff.json`. +> You can see more details on a `objdiff` error by looking for a context window called "Jobs" at the top of the window (hoverring on the red text should show a full description of the run command and the error). +> If the problem persists, feel free to ask for help. + +## Decompiling a function +Once you've opened a source file in `objdiff`, you can choose to decompile the functions in any order. We recommend starting +with a small function if you're unfamiliar with decompilation. Here's an example: + +![Function in objdiff](images/objdiff_function.png) + +As a starting point, we look at the decompiler output in Ghidra. You can request access to our shared Ghidra project [in this section](#the-ghidra-project). + +![Decompiler in Ghidra](images/ghidra_decomp.png) + +Looking at this output, we might try writing something like this: +```cpp +bool Actor::Drop(Vec3p *vel) { + if (mGrabbed) { + mVel = *vel; + mGrabbed = false; + return true; + } + return false; +} +``` + +Now we can go back to `objdiff` and look at the result: + +![Matching function in objdiff](images/objdiff_match.png) + +Success! Note that this was a simple example and that you'll sometimes get stuck on a function. In that case, try the +following: +- Decompile a different function and come back later. +- Export to [decomp.me](https://decomp.me/): + 1. Press the `decomp.me` button in `objdiff`. + 1. Paste your code into the "Source code" tab. The whole file may be needed to access defined globals. + 1. On `decomp.me`, switch to the `objdiff` tab, you can check that you see what was expected from your local diff. + 1. Share the link with us! (Reminder [link to the ZeldaRET discord server](https://discord.gg/6tjntnU8hC).) + +> [!Note] +> If the function is using THUMB mode you can use `THUMB_BEGIN` and `THUMB_END` before and after the function to create a THUMB region, anything outside of the region will use ARM. +> If you have inlines in a header and `#include` the header outside of the region it will use ARM. But if you include it inside the thumb region it will use thumb. + +## Decompiling `.init` functions +> [!NOTE] +> This section will be updated as we learn more about global objects. Feel free to contribute or provide us with more information! + +Functions in the `.init` section are static initializers. Their purpose is to call C++ constructors on global objects, and to +register destructors so the global objects can be destroyed when their overlay unloads. + +Static initializers are generated implicitly and do not require us to write any code ourselves. So, to generate one, you must +define a global variable by using a constructor. + +If the static initializer calls `__register_global_object`, that means the global object has a destructor. This means you'll +have to declare a destructor if it doesn't exist already. + +Another consequence of having a destructor is that a `DestructorChain` object will be added to the `.bss` section. This struct +is 12 (`0xc`) bytes long and is also implicit, so we don't need to define it ourselves. + +> [!IMPORTANT] +> An important thing to keep in mind is that a static initializer can construct multiple global objects. + +## Decompiling data +> [!NOTE] +> Under construction! It's not fully clear how data is decompiled, as the compiler is strict on how it orders global variables. +> Feel free to contribute to this section or provide us with more information! + +Other than `.text` and `.init` which contain code, there are the following sections for data: +- `.rodata`: Global or static constants (requires `const`) +- `.data`: Global or static variables (requires not using `const` except if it's used in a static initializer, in which case all of the data will be set to zero) +- `.bss`: Global or static uninitialized variables + +You can see examples of these data sections in the [compilation section in `build_system.md`](/docs/build_system.md#compiling-code). + +## The Ghidra project +We use a shared Ghidra project to analyze the game and decompile functions. To gain access to the project, install +[Ghidra version 11.2.1](https://github.com/NationalSecurityAgency/ghidra/releases/tag/Ghidra_11.2.1_build) and request access +from @aetias on Discord. \ No newline at end of file diff --git a/docs/images/ghidra_decomp.png b/docs/images/ghidra_decomp.png new file mode 100644 index 000000000..07e54d734 Binary files /dev/null and b/docs/images/ghidra_decomp.png differ diff --git a/docs/images/objdiff_function.png b/docs/images/objdiff_function.png new file mode 100644 index 000000000..26247156c Binary files /dev/null and b/docs/images/objdiff_function.png differ diff --git a/docs/images/objdiff_match.png b/docs/images/objdiff_match.png new file mode 100644 index 000000000..06dec2ff4 Binary files /dev/null and b/docs/images/objdiff_match.png differ diff --git a/docs/images/objdiff_objects.png b/docs/images/objdiff_objects.png new file mode 100644 index 000000000..0bde5aaa1 Binary files /dev/null and b/docs/images/objdiff_objects.png differ diff --git a/docs/images/objdiff_symbols.png b/docs/images/objdiff_symbols.png new file mode 100644 index 000000000..d1af868b5 Binary files /dev/null and b/docs/images/objdiff_symbols.png differ