Skip to content

Commit 62a8f1f

Browse files
Move documentation to new docs folder, remake Ghidra scripts with Ghidrathon (#30)
* Remove TODO that was completed * Move building and contributing instructions into new `docs` folder * Added WIP instructions for Ghidra * Add link for decompiler setup to `BUILDING.md`, improve formatting * Add Ghidra `export_functions_as_csv.py` port * Rename scripts to remove `_in_ida` suffix * Fix export functions script * ignore automatically named functions * fix formatting of address (prepend `0x`) * `rename_functions` ported to ghidra * Make docs say to change image base before opening * Fix docs numbering * Fix Ghidra function name prefixes * Fix link markdown syntax * fix rename functions to use correct memory * port rename data function * Add descriptions to Ghidra scripts * Port export_data_as_csv script to ghidra * Remove `export_names_as_syms.py` (not for LCE decomp) * fix function addrs being lower case * fix quality always being `U` * Remove print * Fix config function call * Update instructions for Ghidra Switch Loader * Fix rename function script to use block and not address space * Add multichunk deletion back * overwrite default functions * add IDA instructions
1 parent d96f6c2 commit 62a8f1f

11 files changed

Lines changed: 295 additions & 89 deletions

README.md

Lines changed: 3 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -33,70 +33,12 @@ The Nintendo Switch Edition release only lasted for a year, before being replace
3333
While the latest version of Nintendo Switch Edition is v1.0.17, that version only matches up with Wii U Edition's Patch 35 (v560). Wii U Edition would get a further 8 updates (up until v688) before LCE was abandoned for good.
3434

3535
## Building
36-
The decomp toolchain was created for Linux & MacOS users. While it isn't a hard requirement, if you're running Windows its advised that you [setup WSL](https://learn.microsoft.com/en-us/windows/wsl/install) with an Ubuntu-like distro for the easiest setup.
37-
38-
The instructions below assume you're running a form of Linux (WSL or native).
39-
40-
### 1. Set up dependencies
41-
42-
* Python 3.6 or newer with [pip](https://pip.pypa.io/en/stable/installation/)
43-
* Ninja
44-
* CMake 3.13+
45-
* If you are on Ubuntu 18.04, you must first [update CMake by using the official CMake APT repository](https://apt.kitware.com/).
46-
* ccache (to speed up builds)
47-
* xdelta3
48-
* clang (for compiling Rust tools)
49-
50-
Ubuntu users can install those dependencies by running:
51-
52-
```shell
53-
sudo apt install python3 ninja-build cmake ccache xdelta3 clang libssl-dev libncurses5
54-
```
55-
56-
Additionally, you'll also need:
57-
58-
* A Rust toolchain ([follow the instructions here](https://www.rust-lang.org/tools/install))
59-
* The following Python modules: `capstone colorama cxxfilt pyelftools ansiwrap watchdog python-Levenshtein toml` (install them with `pip install ...`)
60-
61-
### 2. Set up the project
62-
63-
1. Clone this repository. If you are using WSL, please clone the repo *inside* WSL, *not* on the Windows side (for performance reasons).
64-
65-
2. Run `git submodule update --init --recursive`
66-
67-
Next, you'll need to acquire the **original v1.0.17 `main` NSO executable**.
68-
69-
3. Run `tools/setup.py [path to the NSO]`
70-
* This will:
71-
* install tools/check to check for differences in decompiled code
72-
* convert the executable if necessary
73-
* set up [Clang 4.0.1](https://releases.llvm.org/download.html#4.0.1) by downloading it from the official LLVM website
74-
* create a build directory in `build/`
75-
* If something goes wrong, follow the instructions given to you by the script.
76-
* If you wish to use a CMake generator that isn't Ninja, use `--cmake_backend` to specify it.
77-
78-
### 3. Build
79-
80-
To start the build, just run
81-
82-
```shell
83-
ninja -C build
84-
```
85-
86-
By default, a multithreaded build is performed.
87-
88-
To check whether everything built correctly, just run `tools/check` after the build completes.
36+
See [BUILDING.md](docs/BUILDING.md).
8937

9038
## Contributing
91-
As this project is in its very early stages, its hard to put guidelines on something that will evolve over time as contributors gain a better understanding of the game's internals.
92-
93-
While almost all source paths aren't known, a few .cpp file names do appear in the Wii U Edition's global static constructors, along with a couple asserts giving file paths. Follow these file names wherever possible.
94-
95-
Another point of reference is the Switch Edition, showing us the file path to `Minecraft.World/Calendar.cpp` and some other files in that folder. World generation should be stored in this folder based on `MinecraftWorld_RunStaticCtors` routing all worldgen static constructors inside of the folder.
96-
97-
For another point of reference, you could also look at [the Minecraft: Pocket Edition decompilation](https://github.com/MCPE-RE/0.1.3j-core) for more inspiration. As a matter of fact, a lot of classes from that decompilation project share function names with this game's symbols. Both editions originated from Notch's messy Minecraft beta code, so it's no wonder that a lot of names were probably copied from Mojang's official mappings at the time.
39+
See [CONTRIBUTING.md](docs/CONTRIBUTING.md).
9840

99-
##
41+
---
10042

10143
Minecraft LCE Decompilation logo by [@break-core](https://github.com/break-core?tab=repositories)
10244

docs/BUILDING.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Building
2+
The decomp toolchain was created for Linux & MacOS users. While it isn't a hard requirement, if you're running Windows its advised that you [setup WSL](https://learn.microsoft.com/en-us/windows/wsl/install) with an Ubuntu-like distro for the easiest setup.
3+
4+
The instructions below assume you're running a form of Linux (WSL or native).
5+
6+
## 1. Set up dependencies
7+
8+
* Python 3.6 or newer with [pip](https://pip.pypa.io/en/stable/installation/)
9+
* Ninja
10+
* CMake 3.13+
11+
* If you are on Ubuntu 18.04, you must first [update CMake by using the official CMake APT repository](https://apt.kitware.com/).
12+
* ccache (to speed up builds)
13+
* xdelta3
14+
* clang (for compiling Rust tools)
15+
16+
Ubuntu users can install those dependencies by running:
17+
18+
```shell
19+
sudo apt install python3 ninja-build cmake ccache xdelta3 clang libssl-dev libncurses5
20+
```
21+
22+
Additionally, you'll also need:
23+
24+
* A Rust toolchain ([follow the instructions here](https://www.rust-lang.org/tools/install))
25+
* The following Python modules: `capstone colorama cxxfilt pyelftools ansiwrap watchdog python-Levenshtein toml` (install them with `pip install ...`)
26+
27+
## 2. Set up the project
28+
29+
1. Clone this repository. If you are using WSL, please clone the repo *inside* WSL, *not* on the Windows side (for performance reasons).
30+
31+
2. Run `git submodule update --init --recursive`
32+
33+
Next, you'll need to acquire the **original v1.0.17 `main` NSO executable**.
34+
35+
3. Run `tools/setup.py [path to the NSO]`
36+
* This will:
37+
* install tools/check to check for differences in decompiled code
38+
* convert the executable if necessary
39+
* set up [Clang 4.0.1](https://releases.llvm.org/download.html#4.0.1) by downloading it from the official LLVM website
40+
* create a build directory in `build/`
41+
* If something goes wrong, follow the instructions given to you by the script.
42+
* If you wish to use a CMake generator that isn't Ninja, use `--cmake_backend` to specify it.
43+
44+
## 3. Build
45+
46+
To start the build, just run
47+
48+
```shell
49+
ninja -C build
50+
```
51+
52+
By default, a multithreaded build is performed.
53+
54+
To check whether everything built correctly, just run `tools/check` after the build completes.
55+
56+
## 4. Decompilers
57+
58+
For instructions on setting up a decompiler, see [DECOMPILERS.md](DECOMPILERS.md).

docs/CONTRIBUTING.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Contributing
2+
As this project is in its very early stages, its hard to put guidelines on something that will evolve over time as contributors gain a better understanding of the game's internals.
3+
4+
While almost all source paths aren't known, a few .cpp file names do appear in the Wii U Edition's global static constructors, along with a couple asserts giving file paths. Follow these file names wherever possible.
5+
6+
Another point of reference is the Switch Edition, showing us the file path to `Minecraft.World/Calendar.cpp` and some other files in that folder. World generation should be stored in this folder based on `MinecraftWorld_RunStaticCtors` routing all worldgen static constructors inside of the folder.
7+
8+
For another point of reference, you could also look at [the Minecraft: Pocket Edition decompilation](https://github.com/MCPE-RE/0.1.3j-core) for more inspiration. As a matter of fact, a lot of classes from that decompilation project share function names with this game's symbols. Both editions originated from Notch's messy Minecraft beta code, so it's no wonder that a lot of names were probably copied from Mojang's official mappings at the time.

docs/DECOMPILERS.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Decompilers
2+
3+
## IDA Pro
4+
1. Install `nxo64.py` from `reswitched/loaders`. Instructions are available [here](https://github.com/reswitched/loaders?tab=readme-ov-file#installation)
5+
2. Open `ida64.exe` and disassemble `main.nso`.
6+
7+
## Ghidra
8+
### Setup Instructions
9+
10+
1. Setup `Ghidrathon`. Instructions are available [here](https://github.com/mandiant/Ghidrathon#installing-ghidrathon). Make sure `Ghidrathon` has access to the `toml` package.
11+
2. Setup `Ghidra Switch Loader`. Instructions are available [here](https://github.com/Adubbz/Ghidra-Switch-Loader).
12+
3. Create a new project. When importing `main.nso`, click `Options`. Then set `Base Address` to `0x7100000000`.
13+
4. Open the `Script Manager` (green play icon) from Ghidra's toolbar.
14+
5. Click on Manage Script Directories (bullet points icon) inside the Script Manager, and add `tools/ghidra`. Do not add `tools/common/ghidra_scripts`.
15+
6. Select the `NX-Switch` category, and use the scripts provided. Information and instructions for each individual script are shown inside the Script Manager.

tools/ghidra/export_data_as_csv.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Exports the .bss data to a CSV
2+
#@author Boreal
3+
#@category NX-Switch
4+
5+
import csv
6+
import os
7+
8+
from ghidra.app.script import GhidraScript
9+
from ghidra.program.model.symbol import SymbolType
10+
from ghidra.program.model.address import AddressSet
11+
12+
# this is java.io.file
13+
OutputFilePathObject = askFile("Save CSV file", "OK")
14+
output_file_path = OutputFilePathObject.getAbsolutePath()
15+
16+
def is_valid_name(name: str) -> bool:
17+
return not name.startswith(("DAT_"))
18+
19+
listing = currentProgram().getListing()
20+
21+
def get_data_heads(block):
22+
start = block.getStart()
23+
end = block.getEnd()
24+
25+
addr = start
26+
27+
print(start)
28+
print(end)
29+
30+
if output_file_path:
31+
with open(output_file_path, 'w', newline='') as csvfile:
32+
fieldnames = ['Address', 'Name']
33+
csvwriter = csv.DictWriter(csvfile, fieldnames=fieldnames)
34+
35+
bss = currentProgram().getMemory().getBlock(".bss")
36+
symbols = currentProgram().getSymbolTable().getSymbols(AddressSet(bss.getAddressRange()), SymbolType.LABEL, True)
37+
38+
for data in symbols:
39+
csvwriter.writerow({
40+
'Address': f'0x{data.getAddress().getOffset():016x}',
41+
'Name': data.getName()
42+
})
43+
44+
print(f"CSV file '{output_file_path}' has been generated.")
45+
else:
46+
print("Operation cancelled by the user.")
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Export functions to a CSV (CURRENTLY NOT MATCHING IDA!!!)
2+
#@author Boreal
3+
#@category NX-Switch
4+
5+
import csv
6+
import os
7+
8+
from ghidra.app.script import GhidraScript
9+
10+
# this is java.io.file
11+
OutputFilePathObject = askFile("Select the CSV file to update, checked functions will be preserved", "OK")
12+
output_file_path = OutputFilePathObject.getAbsolutePath()
13+
14+
def is_valid_name(name: str) -> bool:
15+
return not name.startswith(("sub_", "nullsub_", "j_", "FUN_", "thunk_FUN_"))
16+
17+
# Load the existing csv if it exists
18+
existing_data = {}
19+
if output_file_path and os.path.exists(output_file_path):
20+
with open(output_file_path, 'r', newline='') as csvfile:
21+
reader = csv.DictReader(csvfile)
22+
for row in reader:
23+
address = row['Address']
24+
existing_data[address] = row
25+
26+
if output_file_path:
27+
with open(output_file_path, 'w', newline='') as csvfile:
28+
fieldnames = ['Address', 'Quality', 'Size', 'Name']
29+
csvwriter = csv.DictWriter(csvfile, fieldnames=fieldnames)
30+
csvwriter.writeheader()
31+
32+
for func in currentProgram().getListing().getFunctions(True):
33+
func_name = func.getName()
34+
func_size = func.getBody().getNumAddresses()
35+
address_raw = func.getEntryPoint().getOffset()
36+
address = f"0x{address_raw:016x}"
37+
38+
quality = existing_data.get(address, {}).get('Quality', 'U')
39+
40+
csvwriter.writerow({
41+
'Address': address,
42+
'Quality': quality,
43+
'Size': str(func_size).zfill(6),
44+
'Name': func_name if is_valid_name(func_name) else ''
45+
})
46+
47+
print(f"CSV file '{output_file_path}' has been updated.")
48+
else:
49+
print("Operation cancelled by the user.")
50+

tools/ghidra/rename_data.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Renames data to those from the CSV
2+
#@author Boreal
3+
#@category NX-Switch
4+
5+
import csv
6+
import os
7+
8+
from ghidra.app.script import GhidraScript
9+
from ghidra.program.model.symbol import SourceType
10+
11+
# this is java.io.file
12+
InputFilePathObject = askFile("Import from CSV file", "OK")
13+
input_file_path = InputFilePathObject.getAbsolutePath()
14+
15+
if input_file_path:
16+
with open(input_file_path, 'r', newline='') as csvfile:
17+
csvreader = csv.reader(csvfile)
18+
19+
print(csvfile)
20+
21+
for row in csvreader:
22+
if len(row) < 2:
23+
continue
24+
25+
address_str, name = row[0], row[1]
26+
27+
if not address_str or not name:
28+
continue
29+
30+
try:
31+
address = toAddr(int(address_str, 16))
32+
except ValueError:
33+
print(f"Invalid address format: {address_str}")
34+
continue
35+
36+
data = getDataAt(address)
37+
38+
if data is not None:
39+
data.getPrimarySymbol().setName(name, SourceType.USER_DEFINED)

tools/ghidra/rename_functions.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Renames functions to those from the CSV
2+
#@author Boreal
3+
#@category NX-Switch
4+
5+
import csv
6+
import os
7+
8+
from ghidra.app.script import GhidraScript
9+
from ghidra.program.model.symbol import SourceType
10+
from ghidra.program.model.address import AddressSet
11+
12+
common_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "common"))
13+
14+
# util collides with java.util so this has to be done
15+
import importlib
16+
config_path = os.path.join(common_path, "util/config.py")
17+
spec = importlib.util.spec_from_file_location("config", config_path)
18+
config = importlib.util.module_from_spec(spec)
19+
spec.loader.exec_module(config)
20+
21+
adf = currentProgram().getAddressFactory()
22+
mem = currentProgram().getMemory()
23+
text_block = mem.getBlock(".text")
24+
25+
csv_path = config.get_functions_csv_path()
26+
27+
function_manager = currentProgram().getFunctionManager()
28+
29+
30+
def delete_multichunk_funcs():
31+
for func in function_manager.getFunctions(True):
32+
if func.getBody().getNumAddressRanges() > 1:
33+
function_manager.removeFunction(func.getEntryPoint())
34+
print(f"deleted multichunk function {func.getEntryPoint().getOffset():016x}")
35+
36+
delete_multichunk_funcs()
37+
38+
def can_overwrite_name(new_name: str):
39+
if new_name == "":
40+
return False
41+
42+
return True # we have to allow wii u symbols
43+
44+
with open(csv_path, "r") as f:
45+
reader = csv.reader(f)
46+
next(reader)
47+
48+
prev_func = None
49+
50+
for fn in reader:
51+
raw_addr = int(fn[0], 16)
52+
size = int(fn[2])
53+
name = fn[3]
54+
55+
addr = text_block.getStart().getNewAddress(raw_addr)
56+
func = function_manager.getFunctionAt(addr)
57+
58+
if func is None:
59+
print(f"Creating function at {addr} {name}")
60+
try:
61+
func = function_manager.createFunction(None, addr, AddressSet(addr, addr.add(size - 1)), SourceType.USER_DEFINED)
62+
except:
63+
print(f"Creating function at {addr} failed")
64+
continue
65+
66+
elif func.getEntryPoint() != addr:
67+
print(f"Fixing function at {addr} with name {name}")
68+
prev_func.setBody(AddressSet(prev_func.getEntryPoint(), func.getBody().getMaxAddress().add(1)))
69+
func = function_manager.createFunction(None, addr, AddressSet(addr, addr.add(size - 1)), SourceType.USER_DEFINED)
70+
71+
if can_overwrite_name(name):
72+
print(f"Renaming {addr} to {name}")
73+
func.setName(name, SourceType.USER_DEFINED)
74+
75+
prev_func = func
76+
print("Renaming from functions completed.")

0 commit comments

Comments
 (0)