DOL C-Kit is a toolkit for compiling C/C++ code (or assembly) using DevkitPPC to inject into a GameCube/Wii *.dol executable. It has been written in such a way that it can be adapted to many different games. You will need Python 3 and DevKitPPC installed to use it. As well, DOL C-Kit is dependent on pyelftools, JoshuaMK's fork of dolreader, and geckocode-libs.
Optionally, one may use MetroWerks CodeWarrior instead of DevkitPPC for compiling and assembling code. DevkitPPC is still a dependency even if CodeWarrior tools are used, since a GCC linker is needed.
Credit to Yoshi2 for creating the original GC C-Kit. DOL C-Kit couldn't exist without it.
DOL C-Kit is a Python module. To install on Windows, run INSTALL.bat as administrator. To install on Linux, run INSTALL.sh as superuser.
from dol_c_kit import Project, Compiler, Assembler, Linker
The Project class automates the tedious parts of compiling, linking, and injecting custom code into a *.dol executable.
By shifting forward the stack, db_stack, and OSArenaLo, space for new data can be allocated. To do this, a patching function modifying a given game's "__init_registers", "OSInit", and "__OSThreadInit" functions must be written. The project's save_dol function passes two parameters to this patching function: a DolFile class, and the base_addr of your project.
Project(self, base_addr=None, verbose=False, compiler=Compiler.DevkitPPC, assembler=Assembler.DevkitPPC, linker=Linker.DevkitPPC)
base_addr
Sets its respective class member.verbose
Sets its respective class member.compiler
Enumerated value determining which compiler is used for C/C++. Currently, Compiler.DevkitPPC and Compiler.CodeWarrior are available.assembler
Enumerated value determining which assembler is used. Currently, Assembler.DevkitPPC and Assembler.CodeWarrior are available.linker
Enumerated value determining which linker is used. Currently, only Linker.DevkitPPC is available.
The Project class has many member variables that may be directly modified:
src_dir
Path to C code (or assembly) source files. Default is "".obj_dir
Path to output *.o files and other files generated by DOL C-Kit to. Default is "".devkitppc_path
Change this if DevKitPPC is not installed at its default location.- For Windows, this is "C:/devkitPro/devkitPPC/bin/".
- For other platforms, this is "/opt/devkitpro/devkitPPC/bin/".
codewarrior_path
Change this if MetroWerks CodeWarrior is not installed at its default location.- For Windows, this is "C:/Program Files (x86)/Metrowerks/CodeWarrior/PowerPC_EABI_Tools/Command_Line_Tools/".
- For other platforms, this is "/".
c_flags
List of non-crucial flags passed to the compiler for C.- For DevkitPPC, the default is ["-w", "-std=c99", "-O1", "-fno-asynchronous-unwind-tables",].
- For CodeWarrior, the default is ["-proc", "gekko", "-Cpp_exceptions", "off", "-use_lmw_stmw", "on", "-fp", "fmadd", "-schedule", "on",].
cpp_flags
List of non-crucial flags passed to the compiler for C++.- For DevkitPPC, the default is ["-w", "-std=c++98", "-O1", "-fno-asynchronous-unwind-tables", "-fno-rtti",].
- For CodeWarrior, the default is ["-proc", "gekko", "-Cpp_exceptions", "off", "-use_lmw_stmw", "on", "-fp", "fmadd", "-schedule", "on",].
asm_flags
List of non-crucial flags passed to the assembler.- For DevkitPPC, the default is ["-w",].
- For CodeWarrior, the default is ["-proc", "gekko",].
linker_flags
List of non-crucial flags passed to the linker.- For DevkitPPC, the default is [].
project_name
Name used for certain files generated by DOL C-Kit. Default is "project".base_addr
The location new data will be put at. This is set by the constructor, but may be modified directly as well.sda_base
The value used for the _SDA_BASE_ symbol. This is set by the set_sda_bases method, but may be modified directly as well.sda2_base
The value used for the _SDA2_BASE_ symbol. This is set by the set_sda_bases method, but may be modified directly as well.verbose
Flag for additional information printing. This is set by the constructor, but may be modified directly as well.
-
add_c_file(filepath, flags=(), use_global_flags=True)
Add a C source file to the project. Two optional arguments may be given: flags is a tuple of strings passed to the compiler for C as flags, and use_global_flags determines if the c_flags member of the Project class are used for this source file. -
add_cpp_file(filepath, flags=(), use_global_flags=True)
Add a C++ source file to the project. Two optional arguments may be given: flags is a tuple of strings passed to the compiler for C++ as flags, and use_global_flags determines if the cpp_flags member of the Project class are used for this source file. -
add_asm_file(filepath, flags=(), use_global_flags=True)
Add an assembly source file to the project. Two optional arguments may be given: flags is a tuple of strings passed to the assembler as flags, and use_global_flags determines if the asm_flags member of the Project class are used for this source file. -
add_obj_file(filepath, do_cleanup=False)
Add an unlinked object file to the project. This object file must be in the obj_dir, not the src_dir. The cleanup method WILL DELETE FILES added by the add_obj_file method if the optional do_cleanup argument is True. -
add_linker_script_file(filepath)
Add a linker script file to the project. This is useful for defining symbols. -
add_gecko_txt_file(filepath)
Add a textual Gecko Code List to the project. When build_dol is used, codetypes 00, 02, 04, 06, 08, C6, C2, and F2 are permanently patched into the DOL. When build_gecko is used, all given Gecko Codes are copied into a new Gecko Code List. -
add_gecko_gct_file(filepath)
Add a binary Gecko Code Table to the project. When build_dol is used, codetypes 00, 02, 04, 06, 08, C6, C2, and F2 are permanently patched into the DOL. When build_gecko is used, all given Gecko Codes are copied into a new Gecko Code List. -
hook_branch(addr, sym_name, LK=False)
Declare a branch to a symbol to be written at a given address. Optionally, pass LK=True to declare a branchlink. -
hook_branchlink(addr, sym_name)
Declare a branchlink to a symbol to be written at a given address. -
hook_pointer(addr, sym_name)
Declare a pointer to a symbol to be written at a given address. -
hook_string(addr, string, encoding = "ascii", max_strlen = None)
Declare a string to be written at a given address. Optionally, an encoding and maximum size (in bytes) can be specified. -
hook_file(addr, filepath, start=0, end=None, max_size = None)
Declare a file to be written at a given address. Optionally, you may provide a start and end offset to only include a portion of the file, and a maximum size (in bytes) can be specified. Use a negative end offset if you want the offset to be relative to the end of the file. If the file cannot be opened, nothing is written at the given address. This is useful for editing files embedded in the DOL. -
hook_immediate16(addr, sym_name, modifier)
Declare a 16-bit immediate to be written at a given address. This is useful for modifying the SIMM, UIMM, and d fields of certain instructions. Valid modifiers include "@h", "@l", "@ha", "@sda", and "@sda2". Make sure to use the set_sda_base method before trying to use the "@sda" or "sda2" modifiers. -
hook_immediate12(addr, w, i, sym_name, modifier)
Same thing as add_immediate_16, but for the 12-bit immediate field of Paired-Singles load/store instructions. The w and i fields of the original instruction must also be provided. -
set_osarena_patcher(function)
Give your project a game-specific patching function to use to allocate space for new data. -
set_sda_bases(sda_base, sda2_base)
Set the _SDA_BASE_ and _SDA2_BASE_ symbols. These values get passed to the linker. They are also important for the @sda and @sda2 modifiers for Immediate16Hooks.
-
build_dol(in_dol_path, out_dol_path)
Compile, assemble, and link all source files, hooks, and supported Gecko Codes into a *.dol executable. If no base_addr is specified, the ROM end will automatically be detected and used. A new text section will be allocated to contain the new data. If no text sections are available, a data section will be allocated instead.
Note: Automatic ROM end detection does not work for DOLs that allocate space for .sbss2. -
build_gecko(gecko_path)
Compile, assemble, and link all source files, hooks, and Gecko Codes into a large Gecko Code List. OSArenaLo patchers are not used, and likely never will be worth implementing be due to timing limitations of Gecko Codes. Instead, existing data must be overwritten. -
save_map(map_path)
Generate a CodeWarrior-like symbol map from the project. Run this after building but before cleanup. -
cleanup()
Delete unimportant files created by DOL C-Kit. This includes unlinked *.o files, and <project_name>.o, <project_name>.bin, and <project_name>.map.
In C++, there is the concept of mangled symbol names. For example, the function signature int foo::bar(MyClass arg1)
becomes the symbol _ZN3foo3barE7MyClass
. DOL C-Kit provides faculties to make working with mangled symbols easy.
from dol_c_kit import LDPlusPlus, ABI
Just like a usual linker script to give dol-side symbols a value for C and ASM, one is needed for C++ as well. LDPlusPlus makes this easier by mangling function signatures in bulk for you.
LDPlusPlus(abi)
abi
The desired ABI (an enum value). At the moment, ABI.Itanium and ABI.Macintosh are available.
-
assign(prototype, value)
Assign a value to a symbol in the linker script. -
provide(prototype, value)
Provide a value for a symbol in the linker script. -
save(filepath)
Save the linker script to a given filepath.
from dol_c_kit import mangle, ABI, itanium_mangle, macintosh_mangle
To mangle an individual signature, use the mangle function (or itanium_mangle/macintosh_mangle function). This is useful for hooks which take a symbol name as an argument.
-
mangle(prototype, abi)
Returns a mangled symbol from a given signature prototype in a given ABI (an enum value). At the moment, ABI.Itanium and ABI.Macintosh are available. -
itanium_mangle(prototype)
Returns a mangled symbol from a given signature prototype in the Itanium ABI. -
macintosh_mangle(prototype)
Returns a mangled symbol from a given signature prototype in the Classic Macintosh ABI.
Writing a C++ mangler that has no concept of user defined types, or really any context at all, was a struggle that came with a few compromises.
- Function pointer types are not supported.
- Certain kinds of template instancing are not supported.
- Typedefs are not supported (yet).
- Implicit integral long and long long is not supported. Always type out long int and long long int, respectively.
- To mangle certain edge cases, special tokens starting with "$$" are used. These examples are in the Itanium ABI:
$$vtable
is used for vtable signatures. e.g.ClassA::$$vtable
will mangle to_ZTV6ClassA
.$$rtti
is used for typeinfo signatures. e.g.ClassA::$$rtti
will mangle to_ZTI6ClassA
.$$vtt_structure
is used for vtable structure signatures. e.g.ClassA::$$vtt_structure
will mangle to_ZTT6ClassA
.$$rtti_name
is used for typeinfo names. e.g.ClassA::$$rtti_name
will mangle to_ZTS6ClassA
.$$ctor
is used for (the assumed default) object constructors. e.g.ClassA::$$ctor
will mangle to_ZN6ClassAC1Ev
.$$ctor1
is used for complete object constructors. e.g.ClassA::$$ctor1
will mangle to_ZN6ClassAC1Ev
.$$ctor2
is used for base object constructors. e.g.ClassA::$$ctor2
will mangle to_ZN6ClassAC2Ev
.$$ctor3
is used for complete object allocating. e.g.ClassA::$$ctor3
will mangle to_ZN6ClassAC3Ev
.$$dtor
is used for (the assumed default) object destructors. e.g.ClassA::$$dtor
will mangle to_ZN6ClassAD1Ev
.$$dtor0
is used for deleting destructors. e.g.ClassA::$$dtor0
will mangle to_ZN6ClassAD0Ev
.$$dtor1
is used for complete object destructors. e.g.ClassA::$$dtor1
will mangle to_ZN6ClassAD1Ev
.$$dtor2
is used for base object destructors. e.g.ClassA::$$dtor2
will mangle to_ZN6ClassAD2Ev
.$$unary
is used for operator overloads that are ambigious without context.operator+ $$unary
will produce the "positive" operator override rather than the "add" operator override.operator- $$unary
will produce the "negative" operator override rather than the "subtract" operator override.operator& $$unary
will produce the "reference" operator override rather than the "bitwise AND" operator override.operator* $$unary
will produce the "dereference" operator override rather than the "multiply" operator override.
There are likely other cases that the mangler won't cover, but for 99% of signatures, it should work.