From 5b8fbc6cd5f96ee77ec4c4369e492371656f279b Mon Sep 17 00:00:00 2001 From: Luke Street Date: Wed, 4 Oct 2023 23:26:02 -0400 Subject: [PATCH] Initial commit --- .gitattributes | 13 + .gitignore | 11 + .vscode.example/c_cpp_properties.json | 22 + .vscode.example/settings.json | 26 + LICENSE | 21 + README.example.md | 114 +++ README.md | 65 ++ assets/dolphin-extract.png | Bin 0 -> 11843 bytes assets/objdiff.png | Bin 0 -> 66571 bytes config/GAMEID/build.sha1 | 2 + config/GAMEID/config.example.yml | 100 +++ config/GAMEID/config.yml | 12 + config/GAMEID/splits.txt | 1 + config/GAMEID/symbols.txt | 1 + configure.py | 233 ++++++ docs/comment_section.md | 106 +++ docs/common_bss.md | 69 ++ docs/dependencies.md | 33 + docs/getting_started.md | 124 ++++ docs/splits.md | 42 ++ docs/symbols.md | 35 + orig/GAMEID/.gitkeep | 0 tools/__init__.py | 0 tools/decompctx.py | 87 +++ tools/download_tool.py | 93 +++ tools/ninja_syntax.py | 223 ++++++ tools/project.py | 982 ++++++++++++++++++++++++++ tools/transform_dep.py | 84 +++ tools/upload_progress.py | 76 ++ 29 files changed, 2575 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 .vscode.example/c_cpp_properties.json create mode 100644 .vscode.example/settings.json create mode 100644 LICENSE create mode 100644 README.example.md create mode 100644 README.md create mode 100644 assets/dolphin-extract.png create mode 100644 assets/objdiff.png create mode 100644 config/GAMEID/build.sha1 create mode 100644 config/GAMEID/config.example.yml create mode 100644 config/GAMEID/config.yml create mode 100644 config/GAMEID/splits.txt create mode 100644 config/GAMEID/symbols.txt create mode 100644 configure.py create mode 100644 docs/comment_section.md create mode 100644 docs/common_bss.md create mode 100644 docs/dependencies.md create mode 100644 docs/getting_started.md create mode 100644 docs/splits.md create mode 100644 docs/symbols.md create mode 100644 orig/GAMEID/.gitkeep create mode 100644 tools/__init__.py create mode 100644 tools/decompctx.py create mode 100644 tools/download_tool.py create mode 100644 tools/ninja_syntax.py create mode 100644 tools/project.py create mode 100644 tools/transform_dep.py create mode 100644 tools/upload_progress.py diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..9545db5 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,13 @@ +# Auto detect text files and perform LF normalization +* text=auto + +# Explicitly declare text files +*.py text + +# Enforce platform-specific encodings +*.bat text eol=crlf +*.sh text eol=lf +*.sha1 text eol=lf + +# DTK keeps these files with LF +config/**/*.txt text eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d37c144 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +__pycache__ +.idea +.vscode +.ninja_* +*.exe +build +build.ninja +objdiff.json +orig/*/* +!orig/*/.gitkeep +/*.txt diff --git a/.vscode.example/c_cpp_properties.json b/.vscode.example/c_cpp_properties.json new file mode 100644 index 0000000..60e51b8 --- /dev/null +++ b/.vscode.example/c_cpp_properties.json @@ -0,0 +1,22 @@ +{ + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${workspaceFolder}/include/**" + ], + "cStandard": "c99", + "cppStandard": "c++98", + "intelliSenseMode": "linux-clang-x86", + "compilerPath": "", + "configurationProvider": "ms-vscode.makefile-tools", + "browse": { + "path": [ + "${workspaceFolder}/include" + ], + "limitSymbolsToIncludedHeaders": true + } + } + ], + "version": 4 +} diff --git a/.vscode.example/settings.json b/.vscode.example/settings.json new file mode 100644 index 0000000..60e7e27 --- /dev/null +++ b/.vscode.example/settings.json @@ -0,0 +1,26 @@ +{ + "[c]": { + "files.encoding": "utf8", + "editor.defaultFormatter": "xaver.clang-format" + }, + "[cpp]": { + "files.encoding": "utf8", + "editor.defaultFormatter": "xaver.clang-format" + }, + "[python]": { + "editor.defaultFormatter": "ms-python.black-formatter" + }, + "files.insertFinalNewline": true, + "files.trimFinalNewlines": true, + "files.associations": { + "*.inc": "cpp" + }, + "search.useIgnoreFiles": false, + "search.exclude": { + "build/*/config.json": true, + "build/**/*.MAP": true, + "build.ninja": true, + ".ninja_*": true, + "objdiff.json": true + } +} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..af8690a --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Luke Street + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.example.md b/README.example.md new file mode 100644 index 0000000..af8f9a1 --- /dev/null +++ b/README.example.md @@ -0,0 +1,114 @@ +Some Game +[![Build Status]][actions] ![Progress] ![DOL Progress] ![RELs Progress] [![Discord Badge]][discord] +============= + + +[Build Status]: https://github.com/zeldaret/tww/actions/workflows/build.yml/badge.svg +[actions]: https://github.com/zeldaret/tww/actions/workflows/build.yml + +[Progress]: https://img.shields.io/endpoint?label=Code&url=https%3A%2F%2Fprogress.decomp.club%2Fdata%2Ftww%2FGZLE01%2Fall%2F%3Fmode%3Dshield%26measure%3Dcode + +[DOL Progress]: https://img.shields.io/endpoint?label=DOL&url=https%3A%2F%2Fprogress.decomp.club%2Fdata%2Ftww%2FGZLE01%2Fdol%2F%3Fmode%3Dshield%26measure%3Dcode + +[RELs Progress]: https://img.shields.io/endpoint?label=RELs&url=https%3A%2F%2Fprogress.decomp.club%2Fdata%2Ftww%2FGZLE01%2Fmodules%2F%3Fmode%3Dshield%26measure%3Dcode + +[Discord Badge]: https://img.shields.io/discord/727908905392275526?color=%237289DA&logo=discord&logoColor=%23FFFFFF +[discord]: https://discord.gg/hKx3FJJgrV + +A work-in-progress decompilation of Some Game. + +This repository does **not** contain any game assets or assembly whatsoever. An existing copy of the game is required. + +Supported versions: + +- `GAMEID`: Rev 0 (USA) + +Dependencies +============ + +Windows: +-------- + +On Windows, it's **highly recommended** to use native tooling. WSL or msys2 are **not** required. +When running under WSL, [objdiff](#diffing) is unable to get filesystem notifications for automatic rebuilds. + +- Install [Python](https://www.python.org/downloads/) and add it to `%PATH%`. + - Also available from the [Windows Store](https://apps.microsoft.com/store/detail/python-311/9NRWMJP3717K). +- Download [ninja](https://github.com/ninja-build/ninja/releases) and add it to `%PATH%`. + - Quick install via pip: `pip install ninja` + +macOS: +------ +- Install [ninja](https://github.com/ninja-build/ninja/wiki/Pre-built-Ninja-packages): + ``` + brew install ninja + ``` +- Install [wine-crossover](https://github.com/Gcenx/homebrew-wine): + ``` + brew install --cask --no-quarantine gcenx/wine/wine-crossover + ``` + +After OS upgrades, if macOS complains about `Wine Crossover.app` being unverified, you can unquarantine it using: +```sh +sudo xattr -rd com.apple.quarantine '/Applications/Wine Crossover.app' +``` + +Linux: +------ +- Install [ninja](https://github.com/ninja-build/ninja/wiki/Pre-built-Ninja-packages). +- For non-x86(_64) platforms: Install wine from your package manager. + - For x86(_64), [WiBo](https://github.com/decompals/WiBo), a minimal 32-bit Windows binary wrapper, will be automatically downloaded and used. + +Building +======== + +- Clone the repository: + ``` + git clone https://github.com/my/repo.git + ``` +- Using [Dolphin Emulator](https://dolphin-emu.org/), extract your game to `orig/GAMEID`. +![](assets/dolphin-extract.png) + - To save space, the only necessary files are the following. Any others can be deleted. + - `sys/main.dol` + - `files/rels/*.rel` +- Configure: + ``` + python configure.py + ``` + To use a version other than `GAMEID` (USA), specify it with `--version`. +- Build: + ``` + ninja + ``` + +Visual Studio Code +================== + +If desired, use the recommended Visual Studio Code settings by renaming the `.vscode.example` directory to `.vscode`. + +Diffing +======= + +Once the initial build succeeds, an `objdiff.json` should exist in the project root. + +Download the latest release from [encounter/objdiff](https://github.com/encounter/objdiff). Under project settings, set `Project directory`. The configuration should be loaded automatically. + +Select an object from the left sidebar to begin diffing. Changes to the project will rebuild automatically: changes to source files, headers, `configure.py`, `splits.txt` or `symbols.txt`. + +![](assets/objdiff.png) diff --git a/README.md b/README.md new file mode 100644 index 0000000..6d77cac --- /dev/null +++ b/README.md @@ -0,0 +1,65 @@ +decomp-toolkit Project Template +=============================== + +If starting a new GameCube / Wii decompilation project, this repository can be used as a scaffold. + +See [decomp-toolkit](https://github.com/encounter/decomp-toolkit) for background on the concept and more information on the tooling used. + +Documentation +------------- + +- [Dependencies](docs/dependencies.md) +- [Getting Started](docs/getting_started.md) +- [`symbols.txt`](docs/symbols.md) +- [`splits.txt`](docs/splits.md) + +General: +- [Common BSS](docs/common_bss.md) +- [`.comment` section](docs/comment_section.md) + +References +-------- + +- [Discord: GC/Wii Decompilation](https://discord.gg/hKx3FJJgrV) (Come to `#dtk` for help!) +- [objdiff](https://github.com/encounter/objdiff) (Local diffing tool) +- [decomp.me](https://decomp.me) (Collaborate on matches) +- [frogress](https://github.com/decompals/frogress) (Decompilation progress API) +- [wibo](https://github.com/decompals/wibo) (Minimal Win32 wrapper for Linux) +- [sjiswrap](https://github.com/encounter/sjiswrap) (UTF-8 to Shift JIS wrapper) + +Projects using this structure: +- [zeldaret/tww](https://github.com/zeldaret/tww) +- [PrimeDecomp/echoes](https://github.com/PrimeDecomp/echoes) +- [DarkRTA/rb3](https://github.com/DarkRTA/rb3) +- [InputEvelution/wp](https://github.com/InputEvelution/wp) +- [Rainchus/ttyd_dtk](https://github.com/Rainchus/ttyd_dtk) + +Features +-------- +- Few external dependencies: Just `python` for the generator and `ninja` for the build system. See [Dependencies](docs/dependencies.md). +- Simple configuration: Everything lives in `config.yml`, `symbols.txt`, and `splits.txt`. +- Multi-version support: Separate configurations for each game version, and a `configure.py --version` flag to switch between them. +- Feature-rich analyzer: Many time-consuming tasks are automated, allowing you to focus on the decompilation itself. See [Analyzer features](https://github.com/encounter/decomp-toolkit#analyzer-features). +- REL support: RELs each have their own `symbols.txt` and `splits.txt`, and will automatically be built and linked against the main binary. +- No manual assembly: decomp-toolkit handles splitting the DOL into relocatable objects based on the configuration. No game assets are committed to the repository. +- Progress calculation and upload script for [frogress](https://github.com/decompals/frogress). +- Integration with [objdiff](https://github.com/encounter/objdiff) for a diffing workflow. +- (TODO) CI workflow template for GitHub Actions. + +Project structure +----------------- + +- `configure.py` - Project configuration and generator script. +- `config/[GAMEID]` - Configuration files for each game version. +- `config/[GAMEID]/build.sha1` - SHA-1 hashes for each built artifact, for final verification. +- `build/` - Build artifacts generated by the the build process. Ignored by `.gitignore`. +- `orig/[GAMEID]` - Original game files, extracted from the disc. Ignored by `.gitignore`. +- `orig/[GAMEID]/.gitkeep` - Empty checked-in file to ensure the directory is created on clone. +- `src/` - C/C++ source files. +- `include/` - C/C++ header files. +- `tools/` - Scripts shared between projects. + +Temporary, delete when done: +- `config/GAMEID/config.example.yml` - Example configuration file and documentation. +- `docs/` - Documentation for decomp-toolkit configuration. +- `README.md` - This file, replace with your own. For a template, see [`README.example.md`](README.example.md). \ No newline at end of file diff --git a/assets/dolphin-extract.png b/assets/dolphin-extract.png new file mode 100644 index 0000000000000000000000000000000000000000..67a3aa3bc1b5716ab472e24c3807daa1b426a18c GIT binary patch literal 11843 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 Cs1TR{ literal 0 HcmV?d00001 diff --git a/assets/objdiff.png b/assets/objdiff.png new file mode 100644 index 0000000000000000000000000000000000000000..080e335ba0921822df1a5425753bd26670f9cdea GIT binary patch literal 66571 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