From 201f68749bdde6c8b32cedd77d12a6ef285b2492 Mon Sep 17 00:00:00 2001 From: DeathHound Date: Fri, 6 Sep 2024 17:09:21 +0100 Subject: [PATCH 1/3] Add PPC Runtime basis --- CONTRIBUTING.md | 1 + README.md | 5 + config/SALP4Q/splits.txt | 5 +- config/SALP4Q/symbols.txt | 8 +- configure.py | 46 ++--- include/Runtime/Gecko_ExceptionPPC.h | 35 ++++ include/Runtime/__init_cpp_exceptions.h | 16 ++ include/Runtime/__ppc_eabi_linker.h | 13 ++ include/Runtime/global_destructor_chain.h | 12 ++ include/compiler_macros.h | 30 +++ include/global.h | 26 +++ include/macros.h | 30 +++ include/macros.inc | 232 ++++++++++++++++++++++ include/types.h | 47 +++++ src/Runtime/Gecko_ExceptionPPC.cpp | 64 ++++++ src/Runtime/__init_cpp_exceptions.cpp | 35 ++++ src/Runtime/global_destructor_chain.c | 12 ++ 17 files changed, 580 insertions(+), 37 deletions(-) create mode 100644 include/Runtime/Gecko_ExceptionPPC.h create mode 100644 include/Runtime/__init_cpp_exceptions.h create mode 100644 include/Runtime/__ppc_eabi_linker.h create mode 100644 include/Runtime/global_destructor_chain.h create mode 100644 include/compiler_macros.h create mode 100644 include/global.h create mode 100644 include/macros.h create mode 100644 include/macros.inc create mode 100644 include/types.h create mode 100644 src/Runtime/Gecko_ExceptionPPC.cpp create mode 100644 src/Runtime/__init_cpp_exceptions.cpp create mode 100644 src/Runtime/global_destructor_chain.c diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b057f4f..965fff7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,6 +22,7 @@ This will also generate `.s` (assembly) files inside the `build/[GAME ID]/asm` f - Place the function contents into a new scratch in [decomp.me](https://decomp.me) - Write C++ code that creates matching assembly code - Use the following string as the build flags + - Compiler `4.3 build 188 (Wii MW 1.5)` - `-O2,p -lang=c++ -RTTI off -enum int -fp hard -enc SJIS -fp_contract on -str reuse -inline auto -use_lmw_stmw on` - When placing the function or variable into this repository, mark in a comment the address of the symbol - For example `// symbol 0x80395304` diff --git a/README.md b/README.md index 8e14471..21c693b 100644 --- a/README.md +++ b/README.md @@ -105,3 +105,8 @@ Download the latest release from [encounter/objdiff](https://github.com/encounte 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) + +## References +- [Decomp Toolkit](https://github.com/encounter/dtk-template) (repo template) +- [Dolphin 2001 SDK](https://github.com/doldecomp/dolsdk2001) +- [MSL Runtime](https://github.com/projectPiki/pikmin2/blob/main/src/Dolphin/Runtime) (From Pikmin 2 Decomp) diff --git a/config/SALP4Q/splits.txt b/config/SALP4Q/splits.txt index fdfc840..7f19dc6 100644 --- a/config/SALP4Q/splits.txt +++ b/config/SALP4Q/splits.txt @@ -13,9 +13,12 @@ Sections: .sdata2 type:rodata align:16 .sbss2 type:bss align:32 -Runtime.PPCEABI.H/__init_cpp_exceptions.cpp: +Runtime/__init_cpp_exceptions.cpp: .text start:0x80395304 end:0x80395374 .ctors start:0x804D3540 end:0x804D3544 rename:.ctors$10 .dtors start:0x804D41E0 end:0x804D41E4 rename:.dtors$10 .dtors start:0x804D41E4 end:0x804D41E8 rename:.dtors$15 .sdata start:0x805F3828 end:0x805F3830 + +Runtime/Gecko_ExceptionPPC.cpp: + .text start:0x80395374 end:0x80395480 diff --git a/config/SALP4Q/symbols.txt b/config/SALP4Q/symbols.txt index f6598e3..71d3d79 100644 --- a/config/SALP4Q/symbols.txt +++ b/config/SALP4Q/symbols.txt @@ -37233,7 +37233,7 @@ fn_80394874 = .text:0x80394874; // type:function size:0x25C fn_80394AD0 = .text:0x80394AD0; // type:function size:0x40 fn_80394B10 = .text:0x80394B10; // type:function size:0xC fn_80394B1C = .text:0x80394B1C; // type:function size:0x5C -fn_80394B78 = .text:0x80394B78; // type:function size:0x4C +__save_fpr = .text:0x80394B78; // type:function size:0x4C scope:global _savefpr_14 = .text:0x80394B78; // type:label scope:global _savefpr_15 = .text:0x80394B7C; // type:label scope:global _savefpr_16 = .text:0x80394B80; // type:label scope:global @@ -37252,7 +37252,7 @@ _savefpr_28 = .text:0x80394BB0; // type:label scope:global _savefpr_29 = .text:0x80394BB4; // type:label scope:global _savefpr_30 = .text:0x80394BB8; // type:label scope:global _savefpr_31 = .text:0x80394BBC; // type:label scope:global -fn_80394BC4 = .text:0x80394BC4; // type:function size:0x4C +__restore_fpr = .text:0x80394BC4; // type:function size:0x4C scope:global _restfpr_14 = .text:0x80394BC4; // type:label scope:global _restfpr_15 = .text:0x80394BC8; // type:label scope:global _restfpr_16 = .text:0x80394BCC; // type:label scope:global @@ -37271,7 +37271,7 @@ _restfpr_28 = .text:0x80394BFC; // type:label scope:global _restfpr_29 = .text:0x80394C00; // type:label scope:global _restfpr_30 = .text:0x80394C04; // type:label scope:global _restfpr_31 = .text:0x80394C08; // type:label scope:global -fn_80394C10 = .text:0x80394C10; // type:function size:0x4C +__save_gpr = .text:0x80394C10; // type:function size:0x4C scope:global _savegpr_14 = .text:0x80394C10; // type:label scope:global _savegpr_15 = .text:0x80394C14; // type:label scope:global _savegpr_16 = .text:0x80394C18; // type:label scope:global @@ -37290,7 +37290,7 @@ _savegpr_28 = .text:0x80394C48; // type:label scope:global _savegpr_29 = .text:0x80394C4C; // type:label scope:global _savegpr_30 = .text:0x80394C50; // type:label scope:global _savegpr_31 = .text:0x80394C54; // type:label scope:global -fn_80394C5C = .text:0x80394C5C; // type:function size:0x4C +__restore_gpr = .text:0x80394C5C; // type:function size:0x4C scope:global _restgpr_14 = .text:0x80394C5C; // type:label scope:global _restgpr_15 = .text:0x80394C60; // type:label scope:global _restgpr_16 = .text:0x80394C64; // type:label scope:global diff --git a/configure.py b/configure.py index 1ae85b0..bfb7906 100644 --- a/configure.py +++ b/configure.py @@ -173,7 +173,7 @@ "-fp hardware", "-Cpp_exceptions off", # "-W all", - "-O2,p", + "-O4,p", "-inline auto", '-pragma "cats off"', '-pragma "warn_notinlined off"', @@ -204,36 +204,18 @@ "-inline auto", ] -# REL flags -# cflags_rel = [ -# *cflags_base, -# "-sdata 0", -# "-sdata2 0", -# ] - -config.linker_version = "Wii/1.3" +config.linker_version = "Wii/1.5" # Helper function for Dolphin libraries -# def DolphinLib(lib_name: str, objects: List[Object]) -> Dict[str, Any]: -# return { -# "lib": lib_name, -# "mw_version": "GC/1.2.5n", -# "cflags": cflags_base, -# "host": False, -# "objects": objects, -# } - - -# Helper function for REL script objects -# def Rel(lib_name: str, objects: List[Object]) -> Dict[str, Any]: -# return { -# "lib": lib_name, -# "mw_version": "GC/1.3.2", -# "cflags": cflags_rel, -# "host": True, -# "objects": objects, -# } +def DolphinLib(lib_name: str, objects: List[Object]) -> Dict[str, Any]: + return { + "lib": lib_name, + "mw_version": config.linker_version, + "cflags": cflags_base, + "host": False, + "objects": objects, + } Matching = True # Object matches and should be linked @@ -244,15 +226,15 @@ config.warn_missing_source = False config.libs = [ { - "lib": "Runtime.PPCEABI.H", + "lib": "Runtime", "mw_version": config.linker_version, "cflags": cflags_runtime, "host": False, "objects": [ - Object(NonMatching, "Runtime.PPCEABI.H/global_destructor_chain.c"), - Object(NonMatching, "Runtime.PPCEABI.H/__init_cpp_exceptions.cpp"), + Object(NonMatching, "Runtime/__init_cpp_exceptions.cpp"), + Object(NonMatching, "Runtime/Gecko_ExceptionPPC.cpp") ], - }, + } ] if args.mode == "configure": diff --git a/include/Runtime/Gecko_ExceptionPPC.h b/include/Runtime/Gecko_ExceptionPPC.h new file mode 100644 index 0000000..6ab4ae4 --- /dev/null +++ b/include/Runtime/Gecko_ExceptionPPC.h @@ -0,0 +1,35 @@ +#ifndef RUNTIME_GECKO_EXCEPTIONPPC_H + #define RUNTIME_GECKO_EXCEPTIONPPC_H + + #include "types.h" + #include "Runtime/__ppc_eabi_linker.h" + #define MAXFRAGMENTS 1 + + typedef struct ExceptionTableIndex { + u32 functionoffset; + u32 eti_field; + u32 exceptionoffset; + } ExceptionTableIndex; + + typedef struct FragmentInfo { + ExceptionTableIndex* exception_start; + ExceptionTableIndex* exception_end; + char* code_start; + char* code_end; + char* data_start; + char* data_end; + char* TOC; + s32 active; + } FragmentInfo; + + typedef struct ProcessInfo { + __eti_init_info* exception_info; + char* TOC; + s32 active; + } ProcessInfo; + + s32 __register_fragment(struct __eti_init_info*, char*); + void __unregister_fragment(s32); + s32 ExPPC_FindExceptionFragment(char*, FragmentInfo*); + +#endif diff --git a/include/Runtime/__init_cpp_exceptions.h b/include/Runtime/__init_cpp_exceptions.h new file mode 100644 index 0000000..8180435 --- /dev/null +++ b/include/Runtime/__init_cpp_exceptions.h @@ -0,0 +1,16 @@ +#ifndef __INIT_CPP_EXCEPTIONS_H + #define __INIT_CPP_EXCEPTIONS_H + + #ifdef __cplusplus + extern "C" { + #endif + + extern void __init_cpp_exceptions(void); + extern void __fini_cpp_exceptions(void); + + #ifdef __cplusplus + } + #endif + + +#endif diff --git a/include/Runtime/__ppc_eabi_linker.h b/include/Runtime/__ppc_eabi_linker.h new file mode 100644 index 0000000..8661cdd --- /dev/null +++ b/include/Runtime/__ppc_eabi_linker.h @@ -0,0 +1,13 @@ +#ifndef __PPC_EAB_LINKER_H + #define __PPC_EAB_LINKER_H + + typedef struct __eti_init_info { + void* eti_start; + void* eti_end; + void* code_start; + u32 code_size; + } __eti_init_info; + + SECTION_INIT extern __eti_init_info _eti_init_info[]; + +#endif diff --git a/include/Runtime/global_destructor_chain.h b/include/Runtime/global_destructor_chain.h new file mode 100644 index 0000000..35672a1 --- /dev/null +++ b/include/Runtime/global_destructor_chain.h @@ -0,0 +1,12 @@ +#ifndef GLOBAL_DESTRUCTOR_CHAIN_H + #define GLOBAL_DESTRUCTOR_CHAIN_H + + typedef struct DestructorChain { + struct DestructorChain* next; + void* destructor; + void* object; + } DestructorChain; + + void __destroy_global_chain(void); + +#endif diff --git a/include/compiler_macros.h b/include/compiler_macros.h new file mode 100644 index 0000000..0fd563a --- /dev/null +++ b/include/compiler_macros.h @@ -0,0 +1,30 @@ +#ifndef COMPILER_MACROS_H + #define COMPILER_MACROS_H + + #ifndef __MWERKS__ + #define __option(x) + #define __declspec(x) + #define __attribute__(x) + #define ASM_DECL asm + #define ASM_BLOCK asm + #else + #define ASM_DECL + #define ASM_BLOCK(...) + #endif + + #define ALIGN(x) __attribute__((aligned(x))) + #define DONT_INLINE __declspec(noinline) // use for regular functions + #define DONT_INLINE_CLASS __attribute__((never_inline)) // use for class methods + + #define DECL_SECTION(x) __declspec(section x) + #define DECL_WEAK __declspec(weak) + + #ifdef VERSION_SZBE69_B8 + #define RETAIL_DONT_INLINE_FUNC inline + #define RETAIL_DONT_INLINE_CLASS inline + #else + #define RETAIL_DONT_INLINE_FUNC DONT_INLINE + #define RETAIL_DONT_INLINE_CLASS DONT_INLINE_CLASS + #endif + +#endif diff --git a/include/global.h b/include/global.h new file mode 100644 index 0000000..a10eda1 --- /dev/null +++ b/include/global.h @@ -0,0 +1,26 @@ +#ifndef GLOABL_H + #define GLOBAL_H + + #include "types.h" + + #ifndef __INTELLISENSE__ + #define ALIGN_DECL(ALIGNMENT) __attribute__((aligned(ALIGNMENT))) + #define SECTION_DATA __declspec(section ".data") + #define SECTION_INIT __declspec(section ".init") + #else + #define STATIC_ASSERT(...) + #define ALIGN_DECL(...) + #define SECTION_DATA + #define SECTION_INIT + #endif + + #define VERSION_NTSC 0 + #define VERSION_PAL 1 + + #if VERSION == VERSION_NTSC + #define VERSION_SELECT(NTSC, PAL) (NTSC) + #elif VERSION == VERSION_PAL + #define VERSION_SELECT(NTSC, PAL) (PAL) + #endif + +#endif diff --git a/include/macros.h b/include/macros.h new file mode 100644 index 0000000..7fcf28a --- /dev/null +++ b/include/macros.h @@ -0,0 +1,30 @@ +#ifndef MACROS_H + #define MACROS_H + + #include "compiler_macros.h" + + #define MAX(x, y) ((x) > (y) ? (x) : (y)) + #define MIN(x, y) ((x) < (y) ? (x) : (y)) + #define MINEQ(x, y) ((x) <= (y) ? (x) : (y)) + + #define ABS(x) ((x) > 0 ? (x) : -(x)) + + #define CLAMP(low, high, x) ((x) > (high) ? (high) : ((x) < (low) ? (low) : (x))) + + #define ROUND_UP(x, align) (((x) + (align)-1) & (-(align))) + #define ROUND_UP_PTR(x, align) ((void *)((((u32)(x)) + (align)-1) & (~((align)-1)))) + + #define ROUND_DOWN(x, align) ((x) & (-(align))) + #define ROUND_DOWN_PTR(x, align) ((void *)(((u32)(x)) & (~((align)-1)))) + + #define ROTATE_LEFT(x, i) (((x) << (i)) | ((x) >> ((sizeof((x)) * 8) - (i)))) + #define ROTATE_RIGHT(x, i) (((x) >> (i)) | ((x) << ((sizeof((x)) * 8) - (i)))) + + #define ARRAY_LENGTH(x) (sizeof((x)) / sizeof((x)[0])) + + #define null 0 + + #define RELEASE(x) { delete x; x = null; } + #define RELEASEARRAY(x) { delete[] (ubyte*)x; x = null; } + +#endif diff --git a/include/macros.inc b/include/macros.inc new file mode 100644 index 0000000..ac20a94 --- /dev/null +++ b/include/macros.inc @@ -0,0 +1,232 @@ +# General Purpose Registers (GPRs) +.set r0, 0 +.set r1, 1 +.set r2, 2 +.set r3, 3 +.set r4, 4 +.set r5, 5 +.set r6, 6 +.set r7, 7 +.set r8, 8 +.set r9, 9 +.set r10, 10 +.set r11, 11 +.set r12, 12 +.set r13, 13 +.set r14, 14 +.set r15, 15 +.set r16, 16 +.set r17, 17 +.set r18, 18 +.set r19, 19 +.set r20, 20 +.set r21, 21 +.set r22, 22 +.set r23, 23 +.set r24, 24 +.set r25, 25 +.set r26, 26 +.set r27, 27 +.set r28, 28 +.set r29, 29 +.set r30, 30 +.set r31, 31 + +# Floating Point Registers (FPRs) +.set f0, 0 +.set f1, 1 +.set f2, 2 +.set f3, 3 +.set f4, 4 +.set f5, 5 +.set f6, 6 +.set f7, 7 +.set f8, 8 +.set f9, 9 +.set f10, 10 +.set f11, 11 +.set f12, 12 +.set f13, 13 +.set f14, 14 +.set f15, 15 +.set f16, 16 +.set f17, 17 +.set f18, 18 +.set f19, 19 +.set f20, 20 +.set f21, 21 +.set f22, 22 +.set f23, 23 +.set f24, 24 +.set f25, 25 +.set f26, 26 +.set f27, 27 +.set f28, 28 +.set f29, 29 +.set f30, 30 +.set f31, 31 + +# Graphics Quantization Registers (GQRs) +.set qr0, 0 +.set qr1, 1 +.set qr2, 2 +.set qr3, 3 +.set qr4, 4 +.set qr5, 5 +.set qr6, 6 +.set qr7, 7 + +# Special Purpose Registers (SPRs) +.set XER, 1 +.set LR, 8 +.set CTR, 9 +.set DSISR, 18 +.set DAR, 19 +.set DEC, 22 +.set SDR1, 25 +.set SRR0, 26 +.set SRR1, 27 +.set SPRG0, 272 +.set SPRG1, 273 +.set SPRG2, 274 +.set SPRG3, 275 +.set EAR, 282 +.set PVR, 287 +.set IBAT0U, 528 +.set IBAT0L, 529 +.set IBAT1U, 530 +.set IBAT1L, 531 +.set IBAT2U, 532 +.set IBAT2L, 533 +.set IBAT3U, 534 +.set IBAT3L, 535 +.set DBAT0U, 536 +.set DBAT0L, 537 +.set DBAT1U, 538 +.set DBAT1L, 539 +.set DBAT2U, 540 +.set DBAT2L, 541 +.set DBAT3U, 542 +.set DBAT3L, 543 +.set GQR0, 912 +.set GQR1, 913 +.set GQR2, 914 +.set GQR3, 915 +.set GQR4, 916 +.set GQR5, 917 +.set GQR6, 918 +.set GQR7, 919 +.set HID2, 920 +.set WPAR, 921 +.set DMA_U, 922 +.set DMA_L, 923 +.set UMMCR0, 936 +.set UPMC1, 937 +.set UPMC2, 938 +.set USIA, 939 +.set UMMCR1, 940 +.set UPMC3, 941 +.set UPMC4, 942 +.set USDA, 943 +.set MMCR0, 952 +.set PMC1, 953 +.set PMC2, 954 +.set SIA, 955 +.set MMCR1, 956 +.set PMC3, 957 +.set PMC4, 958 +.set SDA, 959 +.set HID0, 1008 +.set HID1, 1009 +.set IABR, 1010 +.set DABR, 1013 +.set L2CR, 1017 +.set ICTC, 1019 +.set THRM1, 1020 +.set THRM2, 1021 +.set THRM3, 1022 + +# Condition Register (CR) bits +.set cr0lt, 0 +.set cr0gt, 1 +.set cr0eq, 2 +.set cr0un, 3 +.set cr1lt, 4 +.set cr1gt, 5 +.set cr1eq, 6 +.set cr1un, 7 +.set cr2lt, 8 +.set cr2gt, 9 +.set cr2eq, 10 +.set cr2un, 11 +.set cr3lt, 12 +.set cr3gt, 13 +.set cr3eq, 14 +.set cr3un, 15 +.set cr4lt, 16 +.set cr4gt, 17 +.set cr4eq, 18 +.set cr4un, 19 +.set cr5lt, 20 +.set cr5gt, 21 +.set cr5eq, 22 +.set cr5un, 23 +.set cr6lt, 24 +.set cr6gt, 25 +.set cr6eq, 26 +.set cr6un, 27 +.set cr7lt, 28 +.set cr7gt, 29 +.set cr7eq, 30 +.set cr7un, 31 + +# Defines a sized symbol with function type. +# Usage: +# .fn my_function, local +# /* asm here */ +# .endfn my_function +.macro .fn name, visibility=global +.\visibility "\name" +.type "\name", @function +"\name": +.endm + +.macro .endfn name +.size "\name", . - "\name" +.endm + +# Defines a sized symbol with object type. +# Usage: +# .obj my_object, local +# /* data here */ +# .endobj my_object +.macro .obj name, visibility=global +.\visibility "\name" +.type "\name", @object +"\name": +.endm + +.macro .endobj name +.size "\name", . - "\name" +.endm + +# Defines a sized symbol without a type. +# Usage: +# .sym my_sym, local +# /* anything here */ +# .endsym my_sym +.macro .sym name, visibility=global +.\visibility "\name" +"\name": +.endm + +.macro .endsym name +.size "\name", . - "\name" +.endm + +# Generates a relative relocation against a symbol. +# Usage: +# .rel my_function, .L_label +.macro .rel name, label +.4byte "\name" + ("\label" - "\name") +.endm diff --git a/include/types.h b/include/types.h new file mode 100644 index 0000000..8a69573 --- /dev/null +++ b/include/types.h @@ -0,0 +1,47 @@ +#ifndef ALICE_TYPES_H + #define ALICE_TYPES_H + + #include "macros.h" + + typedef int BOOL; + + typedef signed char s8; + typedef signed short s16; + typedef signed long int s32; + typedef signed long long int s64; + typedef unsigned char u8; + typedef unsigned short u16; + typedef unsigned int uint; + typedef unsigned long int u32; + typedef unsigned long long int u64; + + typedef volatile u8 vu8; + typedef volatile u16 vu16; + typedef volatile u32 vu32; + typedef volatile u64 vu64; + typedef volatile s8 vs8; + typedef volatile s16 vs16; + typedef volatile s32 vs32; + typedef volatile s64 vs64; + + typedef float f32; + typedef double f64; + typedef volatile f32 vf32; + typedef volatile f64 vf64; + + #ifdef NULL + #undef NULL + #endif + #define NULL 0 + + #ifdef TRUE + #undef TRUE + #endif + #define TRUE 1 + + #ifdef FALSE + #undef FALSE + #endif + #define FALSE 0 + +#endif diff --git a/src/Runtime/Gecko_ExceptionPPC.cpp b/src/Runtime/Gecko_ExceptionPPC.cpp new file mode 100644 index 0000000..b348ec0 --- /dev/null +++ b/src/Runtime/Gecko_ExceptionPPC.cpp @@ -0,0 +1,64 @@ +#include "global.h" +#include "Runtime/Gecko_ExceptionPPC.h" + +extern ProcessInfo fragment_info[MAXFRAGMENTS]; + +s32 __register_fragment(struct __eti_init_info* info, char* TOC) +{ + + ProcessInfo* f = fragment_info; + s32 i; + + for (i = 0; i < MAXFRAGMENTS; i++, f++) { + if (f->active == 0) { + f->exception_info = info; + f->TOC = TOC; + f->active = 1; + return i; + } + } + + return -1; +} + +void __unregister_fragment(s32 fragmentID) +{ + ProcessInfo* f; + if (fragmentID >= 0 && fragmentID < MAXFRAGMENTS) { + f = &fragment_info[fragmentID]; + f->exception_info = 0; + f->TOC = 0; + f->active = 0; + } +} + +s32 ExPPC_FindExceptionFragment(char* returnaddr, FragmentInfo* frag) +{ + ProcessInfo* f; + s32 i; + __eti_init_info* eti_info; + + for (i = 0, f = fragment_info; i < MAXFRAGMENTS; ++i, ++f) { + if (f->active) { + eti_info = f->exception_info; + while (1) { + if (eti_info->code_size == 0) + break; + if (returnaddr >= eti_info->code_start && returnaddr < (char*)eti_info->code_start + eti_info->code_size) { + frag->exception_start = (ExceptionTableIndex*)eti_info->eti_start; + frag->exception_end = (ExceptionTableIndex*)eti_info->eti_end; + frag->code_start = 0; + frag->code_end = 0; + frag->data_start = 0; + frag->data_end = 0; + frag->TOC = f->TOC; + frag->active = f->active; + return 1; + } + eti_info++; + } + } + } + + return 0; +} diff --git a/src/Runtime/__init_cpp_exceptions.cpp b/src/Runtime/__init_cpp_exceptions.cpp new file mode 100644 index 0000000..e03b469 --- /dev/null +++ b/src/Runtime/__init_cpp_exceptions.cpp @@ -0,0 +1,35 @@ +#include "global.h" +#include "Runtime/__init_cpp_exceptions.h" +#include "Runtime/__ppc_eabi_linker.h" +#include "Runtime/Gecko_ExceptionPPC.h" +#include "Runtime/global_destructor_chain.h" + +static s32 fragmentID = -2; + +void __init_cpp_exceptions(void) +{ + if (fragmentID == -2) + { + register char* R2; + #ifndef __INTELLISENSE__ + asm { mr R2, r2 } + #endif + fragmentID = __register_fragment(_eti_init_info, R2); + } +} + +void __fini_cpp_exceptions(void) +{ + if (fragmentID != -2) { + __unregister_fragment(fragmentID); + fragmentID = -2; + } +} + +#pragma section ".ctors$10" +#pragma section ".dtors$10" +#pragma section ".dtors$15" + +DECL_SECTION(".ctors$10") void* __init_cpp_exceptions_reference = __init_cpp_exceptions; +DECL_SECTION(".dtors$10") void* __destroy_global_chain_reference = __destroy_global_chain; +DECL_SECTION(".dtors$15") void* __fini_cpp_exceptions_reference = __fini_cpp_exceptions; diff --git a/src/Runtime/global_destructor_chain.c b/src/Runtime/global_destructor_chain.c new file mode 100644 index 0000000..8c975a4 --- /dev/null +++ b/src/Runtime/global_destructor_chain.c @@ -0,0 +1,12 @@ +#include "global.h" +#include "Runtime.PPCEABI.H/global_destructor_chain.h" + +void __destroy_global_chain(void) +{ + +} + +/* clang-format off */ +DECL_SECTION(".dtors") +static void* const __destroy_global_chain_reference = __destroy_global_chain; +/* clang-format on */ From 4beca3308765eed544458d54898b1713790d9a41 Mon Sep 17 00:00:00 2001 From: DeathHound Date: Thu, 24 Oct 2024 09:22:32 +0100 Subject: [PATCH 2/3] Update dtk conf. Start PPC Runtime --- .gitignore | 3 +- config/SALP4Q/splits.txt | 43 +- configure.py | 64 +- .../MSL/MSL_C/MSL_Common/restrict_def.h | 10 + .../Runtime/Gecko_ExceptionPPC.h | 2 +- .../Runtime/__init_cpp_exceptions.h | 0 .../Runtime/__ppc_eabi_linker.h | 0 .../Runtime/global_destructor_chain.h | 13 + include/Runtime/global_destructor_chain.h | 12 - include/types.h | 20 +- .../Runtime/Gecko_ExceptionPPC.cpp | 7 +- .../Runtime/__init_cpp_exceptions.cpp | 8 +- .../Runtime/global_destructor_chain.c | 30 + src/Revolution/OS/__start.c | 0 src/Runtime/global_destructor_chain.c | 12 - tools/decompctx.py | 2 +- tools/download_tool.py | 1 + tools/project.py | 1022 +++++++++++------ 18 files changed, 860 insertions(+), 389 deletions(-) create mode 100644 include/PowerPC_EABI_Support/MSL/MSL_C/MSL_Common/restrict_def.h rename include/{ => PowerPC_EABI_Support}/Runtime/Gecko_ExceptionPPC.h (93%) rename include/{ => PowerPC_EABI_Support}/Runtime/__init_cpp_exceptions.h (100%) rename include/{ => PowerPC_EABI_Support}/Runtime/__ppc_eabi_linker.h (100%) create mode 100644 include/PowerPC_EABI_Support/Runtime/global_destructor_chain.h delete mode 100644 include/Runtime/global_destructor_chain.h rename src/{ => PowerPC_EABI_Support}/Runtime/Gecko_ExceptionPPC.cpp (93%) rename src/{ => PowerPC_EABI_Support}/Runtime/__init_cpp_exceptions.cpp (75%) create mode 100644 src/PowerPC_EABI_Support/Runtime/global_destructor_chain.c create mode 100644 src/Revolution/OS/__start.c delete mode 100644 src/Runtime/global_destructor_chain.c diff --git a/.gitignore b/.gitignore index 287a6f7..61bc259 100644 --- a/.gitignore +++ b/.gitignore @@ -7,9 +7,8 @@ __pycache__ build build.ninja objdiff.json +compile_commands.json orig/*/**/*.dol /*.txt ctx.c !requirements*.txt -powerpc* -split diff --git a/config/SALP4Q/splits.txt b/config/SALP4Q/splits.txt index 7f19dc6..d0f99f8 100644 --- a/config/SALP4Q/splits.txt +++ b/config/SALP4Q/splits.txt @@ -13,12 +13,51 @@ Sections: .sdata2 type:rodata align:16 .sbss2 type:bss align:32 -Runtime/__init_cpp_exceptions.cpp: +PowerPC_EABI_Support/Runtime/global_destructor_chain.c: + .text start:0x80368C58 end:0x80394124 + +PowerPC_EABI_Support/Runtime/__init_cpp_exceptions.cpp: .text start:0x80395304 end:0x80395374 .ctors start:0x804D3540 end:0x804D3544 rename:.ctors$10 .dtors start:0x804D41E0 end:0x804D41E4 rename:.dtors$10 .dtors start:0x804D41E4 end:0x804D41E8 rename:.dtors$15 .sdata start:0x805F3828 end:0x805F3830 -Runtime/Gecko_ExceptionPPC.cpp: +PowerPC_EABI_Support/Runtime/Gecko_ExceptionPPC.cpp: .text start:0x80395374 end:0x80395480 + +Alice/Objects/0Managers/CKSoundManager.cpp: + .text start:0x802BA0DC end:0x802BAEA0 + +Alice/Objects/1Services/CKSrvTrigger.cpp: + .text start:0x8022CE90 end:0x8022D744 + +Alice/Objects/2Hooks/CKHkAliceHero.cpp: + .text start:0x8001D894 end:0x8001E9E8 + +Alice/Objects/4Groups/CKGrpAliceHero.cpp: + .text start:0x8000DBF0 end:0x8000E264 + +Alice/Objects/6Components/CKAliceHeroConfig.cpp: + .text start:0x800539D4 end:0x80055528 + +Alice/Objects/7Camera/CKCameraFixTrack.cpp: + .text start:0x80238574 end:0x802388B4 + +Alice/Objects/8Cinematics/CKStartDoor.cpp: + .text start:0x80266000 end:0x802662AC + +Alice/Objects/9Dictionaries/CKSoundDictionary.cpp: + .text start:0x802B9360 end:0x802B9A70 + +Alice/Objects/10Geometries/CSkinGeometry.cpp: + .text start:0x801DB1A8 end:0x801DB6D8 + +Alice/Objects/11Nodes/CSpawnNode.cpp: + .text start:0x80211C1C end:0x80212060 + +Alice/Objects/12Logic/CKAliceGameSpawnPoint.cpp: + .text start:0x800EEFBC end:0x800EF814 + +Alice/Objects/13Graphics/CLightManager.cpp: + .text start:0x801E3D88 end:0x801E76B4 diff --git a/configure.py b/configure.py index bfb7906..ca27779 100644 --- a/configure.py +++ b/configure.py @@ -19,6 +19,7 @@ from tools.project import ( Object, + ProgressCategory, ProjectConfig, calculate_progress, generate_build, @@ -113,6 +114,12 @@ action="store_true", help="builds equivalent (but non-matching) or modded objects", ) +parser.add_argument( + "--no-progress", + dest="progress", + action="store_false", + help="disable progress calculation", +) args = parser.parse_args() config = ProjectConfig() @@ -125,10 +132,10 @@ config.objdiff_path = args.objdiff config.binutils_path = args.binutils config.compilers_path = args.compilers -config.debug = args.debug config.generate_map = args.map config.non_matching = args.non_matching config.sjiswrap_path = args.sjiswrap +config.progress = args.progress if not is_windows(): config.wrapper = args.wrapper # Don't build asm unless we're --non-matching @@ -138,8 +145,8 @@ # Tool versions config.binutils_tag = "2.42-1" config.compilers_tag = "20240706" -config.dtk_tag = "v0.9.4" -config.objdiff_tag = "v2.0.0-beta.4" +config.dtk_tag = "v1.1.2" +config.objdiff_tag = "v2.3.2" config.sjiswrap_tag = "v1.1.1" config.wibo_tag = "0.6.11" @@ -157,9 +164,13 @@ config.ldflags = [ "-fp hardware", "-nodefaults", - # "-warn off", - "-listclosure", # Uncomment for Wii linkers ] +if args.debug: + config.ldflags.append("-gdwarf-2") # Or -gdwarf-2 for Wii linkers +if args.map: + config.ldflags.append("-mapunused") + config.ldflags.append("-listclosure") # For Wii linkers + # Use for any additional files that should cause a re-configure when modified config.reconfig_deps = [] @@ -189,7 +200,7 @@ ] # Debug flags -if config.debug: +if args.debug: cflags_base.extend(["-sym on", "-DDEBUG=1"]) else: cflags_base.append("-DNDEBUG=1") @@ -214,6 +225,7 @@ def DolphinLib(lib_name: str, objects: List[Object]) -> Dict[str, Any]: "mw_version": config.linker_version, "cflags": cflags_base, "host": False, + "progress_category": "sdk", "objects": objects, } @@ -222,21 +234,55 @@ def DolphinLib(lib_name: str, objects: List[Object]) -> Dict[str, Any]: NonMatching = False # Object does not match and should not be linked Equivalent = config.non_matching # Object should be linked when configured with --non-matching + +# Object is only matching for specific versions +def MatchingFor(*versions): + return config.version in versions + + config.warn_missing_config = True config.warn_missing_source = False config.libs = [ { - "lib": "Runtime", + "lib": "PowerPC_EABI_Support", + "mw_version": config.linker_version, + "cflags": cflags_runtime, + "host": False, + "progress_category": "sdk", # str | List[str] + "objects": [ + Object(MatchingFor(), "PowerPC_EABI_Support/Runtime/__init_cpp_exceptions.cpp"), + Object(MatchingFor(), "PowerPC_EABI_Support/Runtime/Gecko_ExceptionPPC.cpp"), + Object(MatchingFor(), "PowerPC_EABI_Support/Runtime/global_destructor_chain.c") + ], + }, + { + "lib": "Revolution", "mw_version": config.linker_version, "cflags": cflags_runtime, "host": False, + "progress_category": "sdk", # str | List[str] "objects": [ - Object(NonMatching, "Runtime/__init_cpp_exceptions.cpp"), - Object(NonMatching, "Runtime/Gecko_ExceptionPPC.cpp") + Object(MatchingFor(), "Revolution/OS/__start.c") ], + }, + { + "lib": "Alice", + "mw_version": config.linker_version, + "cflags": cflags_runtime, + "host": False, + "progress_category": "game", # str | List[str] + "objects": [], } ] +# Optional extra categories for progress tracking +# Adjust as desired for your project +config.progress_categories = [ + ProgressCategory("game", "Game Code"), + ProgressCategory("sdk", "SDK Code"), +] +config.progress_each_module = args.verbose + if args.mode == "configure": # Write build.ninja and objdiff.json generate_build(config) diff --git a/include/PowerPC_EABI_Support/MSL/MSL_C/MSL_Common/restrict_def.h b/include/PowerPC_EABI_Support/MSL/MSL_C/MSL_Common/restrict_def.h new file mode 100644 index 0000000..2a19e8b --- /dev/null +++ b/include/PowerPC_EABI_Support/MSL/MSL_C/MSL_Common/restrict_def.h @@ -0,0 +1,10 @@ +#ifndef _MSL_RESTRICT_DEF_H + #define _MSL_RESTRICT_DEF_H + + #if !defined(__cplusplus) && __STDC_VERSION__ >= 199901L + #define RESTRICT restrict + #else + #define RESTRICT + #endif + +#endif diff --git a/include/Runtime/Gecko_ExceptionPPC.h b/include/PowerPC_EABI_Support/Runtime/Gecko_ExceptionPPC.h similarity index 93% rename from include/Runtime/Gecko_ExceptionPPC.h rename to include/PowerPC_EABI_Support/Runtime/Gecko_ExceptionPPC.h index 6ab4ae4..0cf3b41 100644 --- a/include/Runtime/Gecko_ExceptionPPC.h +++ b/include/PowerPC_EABI_Support/Runtime/Gecko_ExceptionPPC.h @@ -2,7 +2,7 @@ #define RUNTIME_GECKO_EXCEPTIONPPC_H #include "types.h" - #include "Runtime/__ppc_eabi_linker.h" + #include "PowerPC_EABI_Support/Runtime/__ppc_eabi_linker.h" #define MAXFRAGMENTS 1 typedef struct ExceptionTableIndex { diff --git a/include/Runtime/__init_cpp_exceptions.h b/include/PowerPC_EABI_Support/Runtime/__init_cpp_exceptions.h similarity index 100% rename from include/Runtime/__init_cpp_exceptions.h rename to include/PowerPC_EABI_Support/Runtime/__init_cpp_exceptions.h diff --git a/include/Runtime/__ppc_eabi_linker.h b/include/PowerPC_EABI_Support/Runtime/__ppc_eabi_linker.h similarity index 100% rename from include/Runtime/__ppc_eabi_linker.h rename to include/PowerPC_EABI_Support/Runtime/__ppc_eabi_linker.h diff --git a/include/PowerPC_EABI_Support/Runtime/global_destructor_chain.h b/include/PowerPC_EABI_Support/Runtime/global_destructor_chain.h new file mode 100644 index 0000000..e778e77 --- /dev/null +++ b/include/PowerPC_EABI_Support/Runtime/global_destructor_chain.h @@ -0,0 +1,13 @@ +#ifndef RUNTIME_GLOBAL_DESTRUCTOR_CHAIN_H + #define RUNTIME_GLOBAL_DESTRUCTOR_CHAIN_H + #include + #ifdef __cplusplus + extern "C" { + #endif + + void __destroy_global_chain(void); + + #ifdef __cplusplus + } + #endif +#endif diff --git a/include/Runtime/global_destructor_chain.h b/include/Runtime/global_destructor_chain.h deleted file mode 100644 index 35672a1..0000000 --- a/include/Runtime/global_destructor_chain.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef GLOBAL_DESTRUCTOR_CHAIN_H - #define GLOBAL_DESTRUCTOR_CHAIN_H - - typedef struct DestructorChain { - struct DestructorChain* next; - void* destructor; - void* object; - } DestructorChain; - - void __destroy_global_chain(void); - -#endif diff --git a/include/types.h b/include/types.h index 8a69573..91fef11 100644 --- a/include/types.h +++ b/include/types.h @@ -15,19 +15,19 @@ typedef unsigned long int u32; typedef unsigned long long int u64; - typedef volatile u8 vu8; - typedef volatile u16 vu16; - typedef volatile u32 vu32; - typedef volatile u64 vu64; - typedef volatile s8 vs8; - typedef volatile s16 vs16; - typedef volatile s32 vs32; - typedef volatile s64 vs64; + typedef volatile unsigned char vu8; + typedef volatile unsigned short vu16; + typedef volatile unsigned long int vu32; + typedef volatile unsigned long long int vu64; + typedef volatile signed char vs8; + typedef volatile signed short vs16; + typedef volatile signed long int vs32; + typedef volatile signed long long int vs64; typedef float f32; typedef double f64; - typedef volatile f32 vf32; - typedef volatile f64 vf64; + typedef volatile float vf32; + typedef volatile double vf64; #ifdef NULL #undef NULL diff --git a/src/Runtime/Gecko_ExceptionPPC.cpp b/src/PowerPC_EABI_Support/Runtime/Gecko_ExceptionPPC.cpp similarity index 93% rename from src/Runtime/Gecko_ExceptionPPC.cpp rename to src/PowerPC_EABI_Support/Runtime/Gecko_ExceptionPPC.cpp index b348ec0..0457e9b 100644 --- a/src/Runtime/Gecko_ExceptionPPC.cpp +++ b/src/PowerPC_EABI_Support/Runtime/Gecko_ExceptionPPC.cpp @@ -1,19 +1,18 @@ #include "global.h" -#include "Runtime/Gecko_ExceptionPPC.h" +#include "PowerPC_EABI_Support/Runtime/Gecko_ExceptionPPC.h" extern ProcessInfo fragment_info[MAXFRAGMENTS]; s32 __register_fragment(struct __eti_init_info* info, char* TOC) { - ProcessInfo* f = fragment_info; s32 i; for (i = 0; i < MAXFRAGMENTS; i++, f++) { if (f->active == 0) { f->exception_info = info; - f->TOC = TOC; - f->active = 1; + f->TOC = TOC; + f->active = 1; return i; } } diff --git a/src/Runtime/__init_cpp_exceptions.cpp b/src/PowerPC_EABI_Support/Runtime/__init_cpp_exceptions.cpp similarity index 75% rename from src/Runtime/__init_cpp_exceptions.cpp rename to src/PowerPC_EABI_Support/Runtime/__init_cpp_exceptions.cpp index e03b469..ac48bb9 100644 --- a/src/Runtime/__init_cpp_exceptions.cpp +++ b/src/PowerPC_EABI_Support/Runtime/__init_cpp_exceptions.cpp @@ -1,8 +1,8 @@ #include "global.h" -#include "Runtime/__init_cpp_exceptions.h" -#include "Runtime/__ppc_eabi_linker.h" -#include "Runtime/Gecko_ExceptionPPC.h" -#include "Runtime/global_destructor_chain.h" +#include "PowerPC_EABI_Support/Runtime/__init_cpp_exceptions.h" +#include "PowerPC_EABI_Support/Runtime/__ppc_eabi_linker.h" +#include "PowerPC_EABI_Support/Runtime/Gecko_ExceptionPPC.h" +#include "PowerPC_EABI_Support/Runtime/global_destructor_chain.h" static s32 fragmentID = -2; diff --git a/src/PowerPC_EABI_Support/Runtime/global_destructor_chain.c b/src/PowerPC_EABI_Support/Runtime/global_destructor_chain.c new file mode 100644 index 0000000..bdfc297 --- /dev/null +++ b/src/PowerPC_EABI_Support/Runtime/global_destructor_chain.c @@ -0,0 +1,30 @@ +#include "types.h" +#include "PowerPC_EABI_Support/Runtime/global_destructor_chain.h" + +typedef void (*DtorFunction)(void* obj, s16 method); + +typedef struct DtorLink { + struct DtorLink* next; // at 0x0 + DtorFunction dtor; // at 0x4 + void* object; // at 0x8 +} DtorLink; + +DtorLink* __global_destructor_chain = NULL; + +void __register_global_object(void* object, DtorFunction dtor, DtorLink* link) { + link->next = __global_destructor_chain; + link->dtor = dtor; + link->object = object; + __global_destructor_chain = link; +} + +void __destroy_global_chain(void) { + DtorLink* link; + + while (link = __global_destructor_chain) { + // Pop destructor + __global_destructor_chain = link->next; + // Destroy object (-1 to destroy all bases) + link->dtor(link->object, -1); + } +} diff --git a/src/Revolution/OS/__start.c b/src/Revolution/OS/__start.c new file mode 100644 index 0000000..e69de29 diff --git a/src/Runtime/global_destructor_chain.c b/src/Runtime/global_destructor_chain.c deleted file mode 100644 index 8c975a4..0000000 --- a/src/Runtime/global_destructor_chain.c +++ /dev/null @@ -1,12 +0,0 @@ -#include "global.h" -#include "Runtime.PPCEABI.H/global_destructor_chain.h" - -void __destroy_global_chain(void) -{ - -} - -/* clang-format off */ -DECL_SECTION(".dtors") -static void* const __destroy_global_chain_reference = __destroy_global_chain; -/* clang-format on */ diff --git a/tools/decompctx.py b/tools/decompctx.py index 290946f..87cfb7e 100644 --- a/tools/decompctx.py +++ b/tools/decompctx.py @@ -23,7 +23,7 @@ # Add additional include directories here ] -include_pattern = re.compile(r'^#\s*include\s*[<"](.+?)[>"]$') +include_pattern = re.compile(r'^#\s*include\s*[<"](.+?)[>"]') guard_pattern = re.compile(r"^#\s*ifndef\s+(.*)$") defines = set() diff --git a/tools/download_tool.py b/tools/download_tool.py index 69ef96a..f4512d0 100644 --- a/tools/download_tool.py +++ b/tools/download_tool.py @@ -55,6 +55,7 @@ def dtk_url(tag: str) -> str: repo = "https://github.com/encounter/decomp-toolkit" return f"{repo}/releases/download/{tag}/dtk-{system}-{arch}{suffix}" + def objdiff_cli_url(tag: str) -> str: uname = platform.uname() suffix = "" diff --git a/tools/project.py b/tools/project.py index 373285c..fd387ef 100644 --- a/tools/project.py +++ b/tools/project.py @@ -17,7 +17,7 @@ import platform import sys from pathlib import Path -from typing import Any, Dict, List, Optional, Set, Tuple, Union +from typing import IO, Any, Dict, Iterable, List, Optional, Set, Tuple, Union, cast from . import ninja_syntax from .ninja_syntax import serialize_path @@ -29,24 +29,93 @@ f"\n(Current path: {sys.executable})" ) +Library = Dict[str, Any] + class Object: def __init__(self, completed: bool, name: str, **options: Any) -> None: self.name = name - self.base_name = Path(name).with_suffix("") self.completed = completed self.options: Dict[str, Any] = { - "add_to_all": True, + "add_to_all": None, "asflags": None, - "extra_asflags": None, + "asm_dir": None, "cflags": None, - "extra_cflags": None, + "extra_asflags": [], + "extra_cflags": [], + "extra_clang_flags": [], + "host": None, + "lib": None, "mw_version": None, + "progress_category": None, "shift_jis": None, "source": name, + "src_dir": None, } self.options.update(options) + # Internal + self.src_path: Optional[Path] = None + self.asm_path: Optional[Path] = None + self.src_obj_path: Optional[Path] = None + self.asm_obj_path: Optional[Path] = None + self.host_obj_path: Optional[Path] = None + self.ctx_path: Optional[Path] = None + + def resolve(self, config: "ProjectConfig", lib: Library) -> "Object": + # Use object options, then library options + obj = Object(self.completed, self.name, **lib) + for key, value in self.options.items(): + if value is not None or key not in obj.options: + obj.options[key] = value + + # Use default options from config + def set_default(key: str, value: Any) -> None: + if obj.options[key] is None: + obj.options[key] = value + + set_default("add_to_all", True) + set_default("asflags", config.asflags) + set_default("asm_dir", config.asm_dir) + set_default("host", False) + set_default("mw_version", config.linker_version) + set_default("shift_jis", config.shift_jis) + set_default("src_dir", config.src_dir) + + # Validate progress categories + def check_category(category: str): + if not any(category == c.id for c in config.progress_categories): + sys.exit( + f"Progress category '{category}' missing from config.progress_categories" + ) + + progress_category = obj.options["progress_category"] + if isinstance(progress_category, list): + for category in progress_category: + check_category(category) + elif progress_category is not None: + check_category(progress_category) + + # Resolve paths + build_dir = config.out_path() + obj.src_path = Path(obj.options["src_dir"]) / obj.options["source"] + if obj.options["asm_dir"] is not None: + obj.asm_path = ( + Path(obj.options["asm_dir"]) / obj.options["source"] + ).with_suffix(".s") + base_name = Path(self.name).with_suffix("") + obj.src_obj_path = build_dir / "src" / f"{base_name}.o" + obj.asm_obj_path = build_dir / "mod" / f"{base_name}.o" + obj.host_obj_path = build_dir / "host" / f"{base_name}.o" + obj.ctx_path = build_dir / "src" / f"{base_name}.ctx" + return obj + + +class ProgressCategory: + def __init__(self, id: str, name: str) -> None: + self.id = id + self.name = name + class ProjectConfig: def __init__(self) -> None: @@ -77,11 +146,10 @@ def __init__(self) -> None: self.build_rels: bool = True # Build REL files self.check_sha_path: Optional[Path] = None # Path to version.sha1 self.config_path: Optional[Path] = None # Path to config.yml - self.debug: bool = False # Build with debug info self.generate_map: bool = False # Generate map file(s) self.asflags: Optional[List[str]] = None # Assembler flags self.ldflags: Optional[List[str]] = None # Linker flags - self.libs: Optional[List[Dict[str, Any]]] = None # List of libraries + self.libs: Optional[List[Library]] = None # List of libraries self.linker_version: Optional[str] = None # mwld version self.version: Optional[str] = None # Version name self.warn_missing_config: bool = False # Warn on missing unit configuration @@ -102,12 +170,21 @@ def __init__(self) -> None: self.custom_build_steps: Optional[Dict[str, List[Dict[str, Any]]]] = ( None # Custom build steps, types are ["pre-compile", "post-compile", "post-link", "post-build"] ) + self.generate_compile_commands: bool = ( + True # Generate compile_commands.json for clangd + ) + self.extra_clang_flags: List[str] = [] # Extra flags for clangd - # Progress output and progress.json config + # Progress output, progress.json and report.json config + self.progress = True # Enable report.json generation and CLI progress output self.progress_all: bool = True # Include combined "all" category self.progress_modules: bool = True # Include combined "modules" category self.progress_each_module: bool = ( - True # Include individual modules, disable for large numbers of modules + False # Include individual modules, disable for large numbers of modules + ) + self.progress_categories: List[ProgressCategory] = [] # Additional categories + self.print_progress_categories: Union[bool, List[str]] = ( + True # Print additional progress categories in the CLI progress output ) # Progress fancy printing @@ -133,16 +210,52 @@ def validate(self) -> None: if getattr(self, attr) is None: sys.exit(f"ProjectConfig.{attr} missing") - def find_object(self, name: str) -> Optional[Tuple[Dict[str, Any], Object]]: + # Creates a map of object names to Object instances + # Options are fully resolved from the library and object + def objects(self) -> Dict[str, Object]: + out = {} for lib in self.libs or {}: - for obj in lib["objects"]: - if obj.name == name: - return lib, obj - return None - + objects: List[Object] = lib["objects"] + for obj in objects: + if obj.name in out: + sys.exit(f"Duplicate object name {obj.name}") + out[obj.name] = obj.resolve(self, lib) + return out + + # Gets the output path for build-related files. def out_path(self) -> Path: return self.build_dir / str(self.version) + # Gets the path to the compilers directory. + # Exits the program if neither `compilers_path` nor `compilers_tag` is provided. + def compilers(self) -> Path: + if self.compilers_path: + return self.compilers_path + elif self.compilers_tag: + return self.build_dir / "compilers" + else: + sys.exit("ProjectConfig.compilers_tag missing") + + # Gets the wrapper to use for compiler commands, if set. + def compiler_wrapper(self) -> Optional[Path]: + wrapper = self.wrapper + + if self.use_wibo(): + wrapper = self.build_dir / "tools" / "wibo" + if not is_windows() and wrapper is None: + wrapper = Path("wine") + + return wrapper + + # Determines whether or not to use wibo as the compiler wrapper. + def use_wibo(self) -> bool: + return ( + self.wibo_tag is not None + and sys.platform == "linux" + and platform.machine() in ("i386", "x86_64") + and self.wrapper is None + ) + def is_windows() -> bool: return os.name == "nt" @@ -154,11 +267,26 @@ def is_windows() -> bool: EXE = ".exe" if is_windows() else "" -def make_flags_str(cflags: Union[str, List[str]]) -> str: - if isinstance(cflags, list): - return " ".join(cflags) - else: - return cflags +def file_is_asm(path: Path) -> bool: + return path.suffix.lower() == ".s" + + +def file_is_c(path: Path) -> bool: + return path.suffix.lower() == ".c" + + +def file_is_cpp(path: Path) -> bool: + return path.suffix.lower() in (".cc", ".cp", ".cpp", ".cxx") + + +def file_is_c_cpp(path: Path) -> bool: + return file_is_c(path) or file_is_cpp(path) + + +def make_flags_str(flags: Optional[List[str]]) -> str: + if flags is None: + return "" + return " ".join(flags) # Load decomp-toolkit generated config.json @@ -174,15 +302,15 @@ def versiontuple(v: str) -> Tuple[int, ...]: f = open(build_config_path, "r", encoding="utf-8") build_config: Dict[str, Any] = json.load(f) config_version = build_config.get("version") - if not config_version: - # Invalid config.json + if config_version is None: + print("Invalid config.json, regenerating...") f.close() os.remove(build_config_path) return None dtk_version = str(config.dtk_tag)[1:] # Strip v if versiontuple(config_version) < versiontuple(dtk_version): - # Outdated config.json + print("Outdated config.json, regenerating...") f.close() os.remove(build_config_path) return None @@ -191,19 +319,22 @@ def versiontuple(v: str) -> Tuple[int, ...]: return build_config -# Generate build.ninja and objdiff.json +# Generate build.ninja, objdiff.json and compile_commands.json def generate_build(config: ProjectConfig) -> None: + config.validate() + objects = config.objects() build_config = load_build_config(config, config.out_path() / "config.json") - generate_build_ninja(config, build_config) - generate_objdiff_config(config, build_config) + generate_build_ninja(config, objects, build_config) + generate_objdiff_config(config, objects, build_config) + generate_compile_commands(config, objects, build_config) # Generate build.ninja def generate_build_ninja( - config: ProjectConfig, build_config: Optional[Dict[str, Any]] + config: ProjectConfig, + objects: Dict[str, Object], + build_config: Optional[Dict[str, Any]], ) -> None: - config.validate() - out = io.StringIO() n = ninja_syntax.Writer(out) n.variable("ninja_required_version", "1.3") @@ -221,13 +352,8 @@ def generate_build_ninja( # Variables ### n.comment("Variables") - ldflags = " ".join(config.ldflags or []) - if config.generate_map: - ldflags += " -mapunused" - if config.debug: - ldflags += " -g" - n.variable("ldflags", ldflags) - if not config.linker_version: + n.variable("ldflags", make_flags_str(config.ldflags)) + if config.linker_version is None: sys.exit("ProjectConfig.linker_version missing") n.variable("mw_version", Path(config.linker_version)) n.newline() @@ -347,16 +473,10 @@ def write_cargo_rule(): else: sys.exit("ProjectConfig.sjiswrap_tag missing") + wrapper = config.compiler_wrapper() # Only add an implicit dependency on wibo if we download it - wrapper = config.wrapper wrapper_implicit: Optional[Path] = None - if ( - config.wibo_tag is not None - and sys.platform == "linux" - and platform.machine() in ("i386", "x86_64") - and config.wrapper is None - ): - wrapper = build_tools_path / "wibo" + if wrapper is not None and config.use_wibo(): wrapper_implicit = wrapper n.build( outputs=wrapper, @@ -367,15 +487,11 @@ def write_cargo_rule(): "tag": config.wibo_tag, }, ) - if not is_windows() and wrapper is None: - wrapper = Path("wine") wrapper_cmd = f"{wrapper} " if wrapper else "" + compilers = config.compilers() compilers_implicit: Optional[Path] = None - if config.compilers_path: - compilers = config.compilers_path - elif config.compilers_tag: - compilers = config.build_dir / "compilers" + if config.compilers_path is None and config.compilers_tag is not None: compilers_implicit = compilers n.build( outputs=compilers, @@ -386,8 +502,6 @@ def write_cargo_rule(): "tag": config.compilers_tag, }, ) - else: - sys.exit("ProjectConfig.compilers_tag missing") binutils_implicit = None if config.binutils_path: @@ -506,8 +620,8 @@ def write_cargo_rule(): n.comment("Custom project build rules (pre/post-processing)") for rule in config.custom_build_rules or {}: n.rule( - name=rule.get("name"), - command=rule.get("command"), + name=cast(str, rule.get("name")), + command=cast(str, rule.get("command")), description=rule.get("description", None), depfile=rule.get("depfile", None), generator=rule.get("generator", False), @@ -519,12 +633,12 @@ def write_cargo_rule(): ) n.newline() - def write_custom_step(step: str) -> List[str]: - implicit = [] + def write_custom_step(step: str) -> List[str | Path]: + implicit: List[str | Path] = [] if config.custom_build_steps and step in config.custom_build_steps: n.comment(f"Custom build steps ({step})") for custom_step in config.custom_build_steps[step]: - outputs = custom_step.get("outputs") + outputs = cast(List[str | Path], custom_step.get("outputs")) if isinstance(outputs, list): implicit.extend(outputs) @@ -533,7 +647,7 @@ def write_custom_step(step: str) -> List[str]: n.build( outputs=outputs, - rule=custom_step.get("rule"), + rule=cast(str, custom_step.get("rule")), inputs=custom_step.get("inputs", None), implicit=custom_step.get("implicit", None), order_only=custom_step.get("order_only", None), @@ -570,10 +684,6 @@ def write_custom_step(step: str) -> List[str]: # Source files ### n.comment("Source files") - build_asm_path = build_path / "mod" - build_src_path = build_path / "src" - build_host_path = build_path / "host" - build_config_path = build_path / "config.json" def map_path(path: Path) -> Path: return path.parent / (path.name + ".MAP") @@ -605,7 +715,6 @@ def write(self, n: ninja_syntax.Writer) -> None: n.comment(f"Link {self.name}") if self.module_id == 0: elf_path = build_path / f"{self.name}.elf" - dol_path = build_path / f"{self.name}.dol" elf_ldflags = f"$ldflags -lcf {serialize_path(self.ldscript)}" if config.generate_map: elf_map = map_path(elf_path) @@ -669,99 +778,101 @@ def write(self, n: ninja_syntax.Writer) -> None: host_source_inputs: List[Path] = [] source_added: Set[Path] = set() - def c_build( - obj: Object, options: Dict[str, Any], lib_name: str, src_path: Path - ) -> Optional[Path]: - cflags_str = make_flags_str(options["cflags"]) - if options["extra_cflags"] is not None: - extra_cflags_str = make_flags_str(options["extra_cflags"]) - cflags_str += " " + extra_cflags_str - used_compiler_versions.add(options["mw_version"]) - - src_obj_path = build_src_path / f"{obj.base_name}.o" - src_base_path = build_src_path / obj.base_name - + def c_build(obj: Object, src_path: Path) -> Optional[Path]: # Avoid creating duplicate build rules - if src_obj_path in source_added: - return src_obj_path - source_added.add(src_obj_path) + if obj.src_obj_path is None or obj.src_obj_path in source_added: + return obj.src_obj_path + source_added.add(obj.src_obj_path) + + cflags = obj.options["cflags"] + extra_cflags = obj.options["extra_cflags"] + + # Add appropriate language flag if it doesn't exist already + # Added directly to the source so it flows to other generation tasks + if not any(flag.startswith("-lang") for flag in cflags) and not any( + flag.startswith("-lang") for flag in extra_cflags + ): + # Ensure extra_cflags is a unique instance, + # and insert into there to avoid modifying shared sets of flags + extra_cflags = obj.options["extra_cflags"] = list(extra_cflags) + if file_is_cpp(src_path): + extra_cflags.insert(0, "-lang=c++") + else: + extra_cflags.insert(0, "-lang=c") - shift_jis = options["shift_jis"] - if shift_jis is None: - shift_jis = config.shift_jis + cflags_str = make_flags_str(cflags) + if len(extra_cflags) > 0: + extra_cflags_str = make_flags_str(extra_cflags) + cflags_str += " " + extra_cflags_str + used_compiler_versions.add(obj.options["mw_version"]) # Add MWCC build rule + lib_name = obj.options["lib"] n.comment(f"{obj.name}: {lib_name} (linked {obj.completed})") n.build( - outputs=src_obj_path, - rule="mwcc_sjis" if shift_jis else "mwcc", + outputs=obj.src_obj_path, + rule="mwcc_sjis" if obj.options["shift_jis"] else "mwcc", inputs=src_path, variables={ - "mw_version": Path(options["mw_version"]), + "mw_version": Path(obj.options["mw_version"]), "cflags": cflags_str, - "basedir": os.path.dirname(src_base_path), - "basefile": src_base_path, + "basedir": os.path.dirname(obj.src_obj_path), + "basefile": obj.src_obj_path.with_suffix(""), }, - implicit=mwcc_sjis_implicit if shift_jis else mwcc_implicit, + implicit=( + mwcc_sjis_implicit if obj.options["shift_jis"] else mwcc_implicit + ), ) # Add ctx build rule - ctx_path = build_src_path / f"{obj.base_name}.ctx" - n.build( - outputs=ctx_path, - rule="decompctx", - inputs=src_path, - implicit=decompctx, - ) + if obj.ctx_path is not None: + n.build( + outputs=obj.ctx_path, + rule="decompctx", + inputs=src_path, + implicit=decompctx, + ) # Add host build rule - if options.get("host", False): - host_obj_path = build_host_path / f"{obj.base_name}.o" - host_base_path = build_host_path / obj.base_name + if obj.options["host"] and obj.host_obj_path is not None: n.build( - outputs=host_obj_path, - rule="host_cc" if src_path.suffix == ".c" else "host_cpp", + outputs=obj.host_obj_path, + rule="host_cc" if file_is_c(src_path) else "host_cpp", inputs=src_path, variables={ - "basedir": os.path.dirname(host_base_path), - "basefile": host_base_path, + "basedir": os.path.dirname(obj.host_obj_path), + "basefile": obj.host_obj_path.with_suffix(""), }, ) - if options["add_to_all"]: - host_source_inputs.append(host_obj_path) + if obj.options["add_to_all"]: + host_source_inputs.append(obj.host_obj_path) n.newline() - if options["add_to_all"]: - source_inputs.append(src_obj_path) + if obj.options["add_to_all"]: + source_inputs.append(obj.src_obj_path) - return src_obj_path + return obj.src_obj_path def asm_build( - obj: Object, - options: Dict[str, Any], - lib_name: str, - src_path: Path, - build_path: Path, + obj: Object, src_path: Path, obj_path: Optional[Path] ) -> Optional[Path]: - asflags = options["asflags"] or config.asflags - if asflags is None: + if obj.options["asflags"] is None: sys.exit("ProjectConfig.asflags missing") - asflags_str = make_flags_str(asflags) - if options["extra_asflags"] is not None: - extra_asflags_str = make_flags_str(options["extra_asflags"]) + asflags_str = make_flags_str(obj.options["asflags"]) + if len(obj.options["extra_asflags"]) > 0: + extra_asflags_str = make_flags_str(obj.options["extra_asflags"]) asflags_str += " " + extra_asflags_str - asm_obj_path = build_path / f"{obj.base_name}.o" - # Avoid creating duplicate build rules - if asm_obj_path in source_added: - return asm_obj_path - source_added.add(asm_obj_path) + if obj_path is None or obj_path in source_added: + return obj_path + source_added.add(obj_path) # Add assembler build rule + lib_name = obj.options["lib"] n.comment(f"{obj.name}: {lib_name} (linked {obj.completed})") n.build( - outputs=asm_obj_path, + outputs=obj_path, rule="as", inputs=src_path, variables={"asflags": asflags_str}, @@ -769,61 +880,40 @@ def asm_build( ) n.newline() - if options["add_to_all"]: - source_inputs.append(asm_obj_path) + if obj.options["add_to_all"]: + source_inputs.append(obj_path) - return asm_obj_path + return obj_path def add_unit(build_obj, link_step: LinkStep): obj_path, obj_name = build_obj["object"], build_obj["name"] - result = config.find_object(obj_name) - if not result: + obj = objects.get(obj_name) + if obj is None: if config.warn_missing_config and not build_obj["autogenerated"]: print(f"Missing configuration for {obj_name}") link_step.add(obj_path) return - lib, obj = result - lib_name = lib["lib"] - - # Use object options, then library options - options = lib.copy() - for key, value in obj.options.items(): - if value is not None or key not in options: - options[key] = value - - unit_src_path = Path(lib.get("src_dir", config.src_dir)) / options["source"] - - unit_asm_path: Optional[Path] = None - if config.asm_dir is not None: - unit_asm_path = ( - Path(lib.get("asm_dir", config.asm_dir)) / options["source"] - ).with_suffix(".s") - link_built_obj = obj.completed built_obj_path: Optional[Path] = None - if unit_src_path.exists(): - if unit_src_path.suffix in (".c", ".cp", ".cpp"): + if obj.src_path is not None and obj.src_path.exists(): + if file_is_c_cpp(obj.src_path): # Add MWCC & host build rules - built_obj_path = c_build(obj, options, lib_name, unit_src_path) - elif unit_src_path.suffix == ".s": + built_obj_path = c_build(obj, obj.src_path) + elif file_is_asm(obj.src_path): # Add assembler build rule - built_obj_path = asm_build( - obj, options, lib_name, unit_src_path, build_src_path - ) + built_obj_path = asm_build(obj, obj.src_path, obj.src_obj_path) else: - sys.exit(f"Unknown source file type {unit_src_path}") + sys.exit(f"Unknown source file type {obj.src_path}") else: if config.warn_missing_source or obj.completed: - print(f"Missing source file {unit_src_path}") + print(f"Missing source file {obj.src_path}") link_built_obj = False # Assembly overrides - if unit_asm_path is not None and unit_asm_path.exists(): + if obj.asm_path is not None and obj.asm_path.exists(): link_built_obj = True - built_obj_path = asm_build( - obj, options, lib_name, unit_asm_path, build_asm_path - ) + built_obj_path = asm_build(obj, obj.asm_path, obj.asm_obj_path) if link_built_obj and built_obj_path is not None: # Use the source-built object @@ -832,7 +922,10 @@ def add_unit(build_obj, link_step: LinkStep): # Use the original (extracted) object link_step.add(obj_path) else: - sys.exit(f"Missing object for {obj_name}: {unit_src_path} {lib} {obj}") + lib_name = obj.options["lib"] + sys.exit( + f"Missing object for {obj_name}: {obj.src_path} {lib_name} {obj}" + ) # Add DOL link step link_step = LinkStep(build_config) @@ -848,7 +941,7 @@ def add_unit(build_obj, link_step: LinkStep): add_unit(unit, module_link_step) # Add empty object to empty RELs if len(module_link_step.inputs) == 0: - if not config.rel_empty_file: + if config.rel_empty_file is None: sys.exit("ProjectConfig.rel_empty_file missing") add_unit( { @@ -910,7 +1003,7 @@ def add_unit(build_obj, link_step: LinkStep): rspfile="$rspfile", rspfile_content="$in_newline", ) - generated_rels = [] + generated_rels: List[str] = [] for idx, link in enumerate(build_config["links"]): # Map module names to link steps link_steps_local = list( @@ -1014,7 +1107,12 @@ def add_unit(build_obj, link_step: LinkStep): n.build( outputs=progress_path, rule="progress", - implicit=[ok_path, configure_script, python_lib, config.config_path], + implicit=[ + ok_path, + configure_script, + python_lib, + report_path, + ], ) ### @@ -1026,10 +1124,11 @@ def add_unit(build_obj, link_step: LinkStep): command=f"{objdiff} report generate -o $out", description="REPORT", ) + report_implicit: List[str | Path] = [objdiff, "all_source"] n.build( outputs=report_path, rule="report", - implicit=[objdiff, "all_source"], + implicit=report_implicit, ) ### @@ -1078,6 +1177,7 @@ def add_unit(build_obj, link_step: LinkStep): ### # Split DOL ### + build_config_path = build_path / "config.json" n.comment("Split DOL into relocatable objects") n.rule( name="split", @@ -1125,8 +1225,10 @@ def add_unit(build_obj, link_step: LinkStep): if build_config: if config.non_matching: n.default(link_outputs) - else: + elif config.progress: n.default(progress_path) + else: + n.default(ok_path) else: n.default(build_config_path) @@ -1138,13 +1240,22 @@ def add_unit(build_obj, link_step: LinkStep): # Generate objdiff.json def generate_objdiff_config( - config: ProjectConfig, build_config: Optional[Dict[str, Any]] + config: ProjectConfig, + objects: Dict[str, Object], + build_config: Optional[Dict[str, Any]], ) -> None: - if not build_config: + if build_config is None: return + # Load existing objdiff.json + existing_units = {} + if Path("objdiff.json").is_file(): + with open("objdiff.json", "r", encoding="utf-8") as r: + existing_config = json.load(r) + existing_units = {unit["name"]: unit for unit in existing_config["units"]} + objdiff_config: Dict[str, Any] = { - "min_version": "1.0.0", + "min_version": "2.0.0-beta.5", "custom_make": "ninja", "build_target": False, "watch_patterns": [ @@ -1160,16 +1271,17 @@ def generate_objdiff_config( "*.json", ], "units": [], + "progress_categories": [], } # decomp.me compiler name mapping - # Commented out versions have not been added to decomp.me yet COMPILER_MAP = { "GC/1.0": "mwcc_233_144", "GC/1.1": "mwcc_233_159", "GC/1.2.5": "mwcc_233_163", "GC/1.2.5e": "mwcc_233_163e", "GC/1.2.5n": "mwcc_233_163n", + "GC/1.3": "mwcc_242_53", "GC/1.3.2": "mwcc_242_81", "GC/1.3.2r": "mwcc_242_81r", "GC/2.0": "mwcc_247_92", @@ -1194,95 +1306,138 @@ def generate_objdiff_config( "Wii/1.7": "mwcc_43_213", } - build_path = config.out_path() - - def add_unit(build_obj: Dict[str, Any], module_name: str) -> None: - if build_obj["autogenerated"]: - # Skip autogenerated objects - return - + def add_unit( + build_obj: Dict[str, Any], module_name: str, progress_categories: List[str] + ) -> None: obj_path, obj_name = build_obj["object"], build_obj["name"] base_object = Path(obj_name).with_suffix("") + name = str(Path(module_name) / base_object).replace(os.sep, "/") unit_config: Dict[str, Any] = { - "name": Path(module_name) / base_object, + "name": name, "target_path": obj_path, + "base_path": None, + "scratch": None, + "metadata": { + "complete": None, + "reverse_fn_order": None, + "source_path": None, + "progress_categories": progress_categories, + "auto_generated": build_obj["autogenerated"], + }, + "symbol_mappings": None, } - result = config.find_object(obj_name) - if not result: - objdiff_config["units"].append(unit_config) - return + # Preserve existing symbol mappings + existing_unit = existing_units.get(name) + if existing_unit is not None: + unit_config["symbol_mappings"] = existing_unit.get("symbol_mappings") - lib, obj = result - src_dir = Path(lib.get("src_dir", config.src_dir)) - - # Use object options, then library options - options = lib.copy() - for key, value in obj.options.items(): - if value is not None or key not in options: - options[key] = value - - unit_src_path = src_dir / str(options["source"]) - - if not unit_src_path.exists(): + obj = objects.get(obj_name) + if obj is None: objdiff_config["units"].append(unit_config) return - cflags = options["cflags"] - src_obj_path = build_path / "src" / f"{obj.base_name}.o" - src_ctx_path = build_path / "src" / f"{obj.base_name}.ctx" + src_exists = obj.src_path is not None and obj.src_path.exists() + if src_exists: + unit_config["base_path"] = obj.src_obj_path + unit_config["metadata"]["source_path"] = obj.src_path + cflags = obj.options["cflags"] reverse_fn_order = False - if type(cflags) is list: - for flag in cflags: - if not flag.startswith("-inline "): - continue - for value in flag.split(" ")[1].split(","): - if value == "deferred": - reverse_fn_order = True - elif value == "nodeferred": - reverse_fn_order = False - - # Filter out include directories - def keep_flag(flag): - return not flag.startswith("-i ") and not flag.startswith("-I ") + for flag in cflags: + if not flag.startswith("-inline "): + continue + for value in flag.split(" ")[1].split(","): + if value == "deferred": + reverse_fn_order = True + elif value == "nodeferred": + reverse_fn_order = False - cflags = list(filter(keep_flag, cflags)) + # Filter out include directories + def keep_flag(flag): + return not flag.startswith("-i ") and not flag.startswith("-I ") - # Add appropriate lang flag - if unit_src_path.suffix in (".cp", ".cpp"): - cflags.insert(0, "-lang=c++") - else: - cflags.insert(0, "-lang=c") + cflags = list(filter(keep_flag, cflags)) - unit_config["base_path"] = src_obj_path - unit_config["reverse_fn_order"] = reverse_fn_order - unit_config["complete"] = obj.completed - compiler_version = COMPILER_MAP.get(options["mw_version"]) + compiler_version = COMPILER_MAP.get(obj.options["mw_version"]) if compiler_version is None: - print(f"Missing scratch compiler mapping for {options['mw_version']}") + print(f"Missing scratch compiler mapping for {obj.options['mw_version']}") else: cflags_str = make_flags_str(cflags) - if options["extra_cflags"] is not None: - extra_cflags_str = make_flags_str(options["extra_cflags"]) + if len(obj.options["extra_cflags"]) > 0: + extra_cflags_str = make_flags_str(obj.options["extra_cflags"]) cflags_str += " " + extra_cflags_str unit_config["scratch"] = { "platform": "gc_wii", "compiler": compiler_version, "c_flags": cflags_str, - "ctx_path": src_ctx_path, - "build_ctx": True, } + if src_exists: + unit_config["scratch"].update( + { + "ctx_path": obj.ctx_path, + "build_ctx": True, + } + ) + category_opt: List[str] | str = obj.options["progress_category"] + if isinstance(category_opt, list): + progress_categories.extend(category_opt) + elif category_opt is not None: + progress_categories.append(category_opt) + unit_config["metadata"].update( + { + "complete": obj.completed if src_exists else None, + "reverse_fn_order": reverse_fn_order, + "progress_categories": progress_categories, + } + ) objdiff_config["units"].append(unit_config) # Add DOL units for unit in build_config["units"]: - add_unit(unit, build_config["name"]) + progress_categories = [] + # Only include a "dol" category if there are any modules + # Otherwise it's redundant with the global report measures + if len(build_config["modules"]) > 0: + progress_categories.append("dol") + add_unit(unit, build_config["name"], progress_categories) # Add REL units for module in build_config["modules"]: for unit in module["units"]: - add_unit(unit, module["name"]) + progress_categories = [] + if config.progress_modules: + progress_categories.append("modules") + if config.progress_each_module: + progress_categories.append(module["name"]) + add_unit(unit, module["name"], progress_categories) + + # Add progress categories + def add_category(id: str, name: str): + objdiff_config["progress_categories"].append( + { + "id": id, + "name": name, + } + ) + + if len(build_config["modules"]) > 0: + add_category("dol", "DOL") + if config.progress_modules: + add_category("modules", "Modules") + if config.progress_each_module: + for module in build_config["modules"]: + add_category(module["name"], module["name"]) + for category in config.progress_categories: + add_category(category.id, category.name) + + def cleandict(d): + if isinstance(d, dict): + return {k: cleandict(v) for k, v in d.items() if v is not None} + elif isinstance(d, list): + return [cleandict(v) for v in d] + else: + return d # Write objdiff.json with open("objdiff.json", "w", encoding="utf-8") as w: @@ -1290,140 +1445,343 @@ def keep_flag(flag): def unix_path(input: Any) -> str: return str(input).replace(os.sep, "/") if input else "" - json.dump(objdiff_config, w, indent=4, default=unix_path) + json.dump(cleandict(objdiff_config), w, indent=2, default=unix_path) -# Calculate, print and write progress to progress.json -def calculate_progress(config: ProjectConfig) -> None: - out_path = config.out_path() - build_config = load_build_config(config, out_path / "config.json") - if not build_config: +def generate_compile_commands( + config: ProjectConfig, + objects: Dict[str, Object], + build_config: Optional[Dict[str, Any]], +) -> None: + if build_config is None or not config.generate_compile_commands: return - class ProgressUnit: - def __init__(self, name: str) -> None: - self.name: str = name - self.code_total: int = 0 - self.code_fancy_frac: int = config.progress_code_fancy_frac - self.code_fancy_item: str = config.progress_code_fancy_item - self.code_progress: int = 0 - self.data_total: int = 0 - self.data_fancy_frac: int = config.progress_data_fancy_frac - self.data_fancy_item: str = config.progress_data_fancy_item - self.data_progress: int = 0 - self.objects_progress: int = 0 - self.objects_total: int = 0 - self.objects: Set[Object] = set() - - def add(self, build_obj: Dict[str, Any]) -> None: - self.code_total += build_obj["code_size"] - self.data_total += build_obj["data_size"] - - # Avoid counting the same object in different modules twice - include_object = build_obj["name"] not in self.objects - if include_object: - self.objects.add(build_obj["name"]) - self.objects_total += 1 - - if build_obj["autogenerated"]: - # Skip autogenerated objects - return + # The following code attempts to convert mwcc flags to clang flags + # for use with clangd. - result = config.find_object(build_obj["name"]) - if not result: - return + # Flags to ignore explicitly + CFLAG_IGNORE: Set[str] = { + # Search order modifier + # Has a different meaning to Clang, and would otherwise + # be picked up by the include passthrough prefix + "-I-", + "-i-", + } + CFLAG_IGNORE_PREFIX: Tuple[str, ...] = ( + # Recursive includes are not supported by modern compilers + "-ir ", + ) - _, obj = result - if not obj.completed: - return + # Flags to replace + CFLAG_REPLACE: Dict[str, str] = {} + CFLAG_REPLACE_PREFIX: Tuple[Tuple[str, str], ...] = ( + # Includes + ("-i ", "-I"), + ("-I ", "-I"), + ("-I+", "-I"), + # Defines + ("-d ", "-D"), + ("-D ", "-D"), + ("-D+", "-D"), + ) + + # Flags with a finite set of options + CFLAG_REPLACE_OPTIONS: Tuple[Tuple[str, Dict[str, Tuple[str, ...]]], ...] = ( + # Exceptions + ( + "-Cpp_exceptions", + { + "off": ("-fno-cxx-exceptions",), + "on": ("-fcxx-exceptions",), + }, + ), + # RTTI + ( + "-RTTI", + { + "off": ("-fno-rtti",), + "on": ("-frtti",), + }, + ), + # Language configuration + ( + "-lang", + { + "c": ("--language=c", "--std=c99"), + "c99": ("--language=c", "--std=c99"), + "c++": ("--language=c++", "--std=c++98"), + "cplus": ("--language=c++", "--std=c++98"), + }, + ), + # Enum size + ( + "-enum", + { + "min": ("-fshort-enums",), + "int": ("-fno-short-enums",), + }, + ), + # Common BSS + ( + "-common", + { + "off": ("-fno-common",), + "on": ("-fcommon",), + }, + ), + ) + + # Flags to pass through + CFLAG_PASSTHROUGH: Set[str] = set() + CFLAG_PASSTHROUGH_PREFIX: Tuple[str, ...] = ( + "-I", # includes + "-D", # defines + ) + + clangd_config = [] + + def add_unit(build_obj: Dict[str, Any]) -> None: + obj = objects.get(build_obj["name"]) + if obj is None: + return + + # Skip unresolved objects + if ( + obj.src_path is None + or obj.src_obj_path is None + or not file_is_c_cpp(obj.src_path) + ): + return + + # Gather cflags for source file + cflags: list[str] = [] + + def append_cflags(flags: Iterable[str]) -> None: + # Match a flag against either a set of concrete flags, or a set of prefixes. + def flag_match( + flag: str, concrete: Set[str], prefixes: Tuple[str, ...] + ) -> bool: + if flag in concrete: + return True + + for prefix in prefixes: + if flag.startswith(prefix): + return True - self.code_progress += build_obj["code_size"] - self.data_progress += build_obj["data_size"] - if include_object: - self.objects_progress += 1 + return False - def code_frac(self) -> float: - return self.code_progress / self.code_total + # Determine whether a flag should be ignored. + def should_ignore(flag: str) -> bool: + return flag_match(flag, CFLAG_IGNORE, CFLAG_IGNORE_PREFIX) - def data_frac(self) -> float: - return self.data_progress / self.data_total + # Determine whether a flag should be passed through. + def should_passthrough(flag: str) -> bool: + return flag_match(flag, CFLAG_PASSTHROUGH, CFLAG_PASSTHROUGH_PREFIX) + + # Attempts replacement for the given flag. + def try_replace(flag: str) -> bool: + replacement = CFLAG_REPLACE.get(flag) + if replacement is not None: + cflags.append(replacement) + return True + + for prefix, replacement in CFLAG_REPLACE_PREFIX: + if flag.startswith(prefix): + cflags.append(flag.replace(prefix, replacement, 1)) + return True + + for prefix, options in CFLAG_REPLACE_OPTIONS: + if not flag.startswith(prefix): + continue + + # "-lang c99" and "-lang=c99" are both generally valid option forms + option = flag.removeprefix(prefix).removeprefix("=").lstrip() + replacements = options.get(option) + if replacements is not None: + cflags.extend(replacements) + + return True + + return False + + for flag in flags: + # Ignore flags first + if should_ignore(flag): + continue + + # Then find replacements + if try_replace(flag): + continue + + # Pass flags through last + if should_passthrough(flag): + cflags.append(flag) + continue + + append_cflags(obj.options["cflags"]) + append_cflags(obj.options["extra_cflags"]) + cflags.extend(config.extra_clang_flags) + cflags.extend(obj.options["extra_clang_flags"]) + + unit_config = { + "directory": Path.cwd(), + "file": obj.src_path, + "output": obj.src_obj_path, + "arguments": [ + "clang", + "-nostdinc", + "-fno-builtin", + "--target=powerpc-eabi", + *cflags, + "-c", + obj.src_path, + "-o", + obj.src_obj_path, + ], + } + clangd_config.append(unit_config) # Add DOL units - all_progress = ProgressUnit("All") if config.progress_all else None - dol_progress = ProgressUnit("DOL") for unit in build_config["units"]: - if all_progress: - all_progress.add(unit) - dol_progress.add(unit) + add_unit(unit) # Add REL units - rels_progress = ProgressUnit("Modules") if config.progress_modules else None - modules_progress: List[ProgressUnit] = [] for module in build_config["modules"]: - progress = ProgressUnit(module["name"]) - modules_progress.append(progress) for unit in module["units"]: - if all_progress: - all_progress.add(unit) - if rels_progress: - rels_progress.add(unit) - progress.add(unit) + add_unit(unit) - # Print human-readable progress - print("Progress:") + # Write compile_commands.json + with open("compile_commands.json", "w", encoding="utf-8") as w: + + def default_format(o): + if isinstance(o, Path): + return o.resolve().as_posix() + return str(o) + + json.dump(clangd_config, w, indent=2, default=default_format) - def print_category(unit: Optional[ProgressUnit]) -> None: - if unit is None: - return - code_frac = unit.code_frac() - data_frac = unit.data_frac() - print( - f" {unit.name}: {code_frac:.2%} code, {data_frac:.2%} data ({unit.objects_progress} / {unit.objects_total} files)" +# Calculate, print and write progress to progress.json +def calculate_progress(config: ProjectConfig) -> None: + config.validate() + out_path = config.out_path() + report_path = out_path / "report.json" + if not report_path.is_file(): + sys.exit(f"Report file {report_path} does not exist") + + report_data: Dict[str, Any] = {} + with open(report_path, "r", encoding="utf-8") as f: + report_data = json.load(f) + + # Convert string numbers (u64) to int + def convert_numbers(data: Dict[str, Any]) -> None: + for key, value in data.items(): + if isinstance(value, str) and value.isdigit(): + data[key] = int(value) + + convert_numbers(report_data["measures"]) + for category in report_data.get("categories", []): + convert_numbers(category["measures"]) + + # Output to GitHub Actions job summary, if available + summary_path = os.getenv("GITHUB_STEP_SUMMARY") + summary_file: Optional[IO[str]] = None + if summary_path: + summary_file = open(summary_path, "a", encoding="utf-8") + summary_file.write("```\n") + + def progress_print(s: str) -> None: + print(s) + if summary_file: + summary_file.write(s + "\n") + + # Print human-readable progress + progress_print("Progress:") + + def print_category(name: str, measures: Dict[str, Any]) -> None: + total_code = measures.get("total_code", 0) + matched_code = measures.get("matched_code", 0) + matched_code_percent = measures.get("matched_code_percent", 0) + total_data = measures.get("total_data", 0) + matched_data = measures.get("matched_data", 0) + matched_data_percent = measures.get("matched_data_percent", 0) + total_functions = measures.get("total_functions", 0) + matched_functions = measures.get("matched_functions", 0) + complete_code_percent = measures.get("complete_code_percent", 0) + total_units = measures.get("total_units", 0) + complete_units = measures.get("complete_units", 0) + + progress_print( + f" {name}: {matched_code_percent:.2f}% matched, {complete_code_percent:.2f}% linked ({complete_units} / {total_units} files)" ) - print(f" Code: {unit.code_progress} / {unit.code_total} bytes") - print(f" Data: {unit.data_progress} / {unit.data_total} bytes") - if config.progress_use_fancy: - print( - "\nYou have {} out of {} {} and {} out of {} {}.".format( - math.floor(code_frac * unit.code_fancy_frac), - unit.code_fancy_frac, - unit.code_fancy_item, - math.floor(data_frac * unit.data_fancy_frac), - unit.data_fancy_frac, - unit.data_fancy_item, - ) + progress_print( + f" Code: {matched_code} / {total_code} bytes ({matched_functions} / {total_functions} functions)" + ) + progress_print( + f" Data: {matched_data} / {total_data} bytes ({matched_data_percent:.2f}%)" + ) + + print_category("All", report_data["measures"]) + for category in report_data.get("categories", []): + if config.print_progress_categories is True or ( + isinstance(config.print_progress_categories, list) + and category["id"] in config.print_progress_categories + ): + print_category(category["name"], category["measures"]) + + if config.progress_use_fancy: + measures = report_data["measures"] + total_code = measures.get("total_code", 0) + total_data = measures.get("total_data", 0) + if total_code == 0 or total_data == 0: + return + code_frac = measures.get("complete_code", 0) / total_code + data_frac = measures.get("complete_data", 0) / total_data + + progress_print( + "\nYou have {} out of {} {} and {} out of {} {}.".format( + math.floor(code_frac * config.progress_code_fancy_frac), + config.progress_code_fancy_frac, + config.progress_code_fancy_item, + math.floor(data_frac * config.progress_data_fancy_frac), + config.progress_data_fancy_frac, + config.progress_data_fancy_item, ) + ) - if all_progress: - print_category(all_progress) - print_category(dol_progress) - module_count = len(build_config["modules"]) - if module_count > 0: - print_category(rels_progress) - if config.progress_each_module: - for progress in modules_progress: - print_category(progress) + # Finalize GitHub Actions job summary + if summary_file: + summary_file.write("```\n") + summary_file.close() # Generate and write progress.json progress_json: Dict[str, Any] = {} - def add_category(category: str, unit: ProgressUnit) -> None: - progress_json[category] = { - "code": unit.code_progress, - "code/total": unit.code_total, - "data": unit.data_progress, - "data/total": unit.data_total, + def add_category(id: str, measures: Dict[str, Any]) -> None: + progress_json[id] = { + "code": measures.get("complete_code", 0), + "code/total": measures.get("total_code", 0), + "data": measures.get("complete_data", 0), + "data/total": measures.get("total_data", 0), + "matched_code": measures.get("matched_code", 0), + "matched_code/total": measures.get("total_code", 0), + "matched_data": measures.get("matched_data", 0), + "matched_data/total": measures.get("total_data", 0), + "matched_functions": measures.get("matched_functions", 0), + "matched_functions/total": measures.get("total_functions", 0), + "fuzzy_match": int(measures.get("fuzzy_match_percent", 0) * 100), + "fuzzy_match/total": 10000, + "units": measures.get("complete_units", 0), + "units/total": measures.get("total_units", 0), } - if all_progress: - add_category("all", all_progress) - add_category("dol", dol_progress) - if len(build_config["modules"]) > 0: - if rels_progress: - add_category("modules", rels_progress) - if config.progress_each_module: - for progress in modules_progress: - add_category(progress.name, progress) + if config.progress_all: + add_category("all", report_data["measures"]) + else: + # Support for old behavior where "dol" was the main category + add_category("dol", report_data["measures"]) + for category in report_data.get("categories", []): + add_category(category["id"], category["measures"]) + with open(out_path / "progress.json", "w", encoding="utf-8") as w: - json.dump(progress_json, w, indent=4) + json.dump(progress_json, w, indent=2) From 9d9cabfc307d262ec5120c21f07b35902de38e78 Mon Sep 17 00:00:00 2001 From: DeathHound Date: Thu, 31 Oct 2024 09:22:53 +0000 Subject: [PATCH 3/3] Match Gecko_ExceptionPPC --- CONTRIBUTING.md | 2 +- README.md | 2 +- config/SALP4Q/splits.txt | 71 ++-- configure.py | 37 +- .../Runtime/Gecko_ExceptionPPC.h | 8 +- include/Revolution/OS/__ppc_eabi_init.h | 89 +++++ .../Runtime/Gecko_ExceptionPPC.cpp | 16 +- src/Revolution/OS/__start.c | 346 ++++++++++++++++++ 8 files changed, 518 insertions(+), 53 deletions(-) create mode 100644 include/Revolution/OS/__ppc_eabi_init.h diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 965fff7..ed9ff57 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,7 +23,7 @@ This will also generate `.s` (assembly) files inside the `build/[GAME ID]/asm` f - Write C++ code that creates matching assembly code - Use the following string as the build flags - Compiler `4.3 build 188 (Wii MW 1.5)` - - `-O2,p -lang=c++ -RTTI off -enum int -fp hard -enc SJIS -fp_contract on -str reuse -inline auto -use_lmw_stmw on` + - `-O4,s -lang=c++ -RTTI off -enum int -fp hard -enc SJIS -fp_contract on -str reuse -inline auto -use_lmw_stmw on` - When placing the function or variable into this repository, mark in a comment the address of the symbol - For example `// symbol 0x80395304` diff --git a/README.md b/README.md index 21c693b..682fb2b 100644 --- a/README.md +++ b/README.md @@ -109,4 +109,4 @@ Select an object from the left sidebar to begin diffing. Changes to the project ## References - [Decomp Toolkit](https://github.com/encounter/dtk-template) (repo template) - [Dolphin 2001 SDK](https://github.com/doldecomp/dolsdk2001) -- [MSL Runtime](https://github.com/projectPiki/pikmin2/blob/main/src/Dolphin/Runtime) (From Pikmin 2 Decomp) + - Alice uses Revolution SDK of a currently unknown version or release, however, dolsdk can be used as a basis diff --git a/config/SALP4Q/splits.txt b/config/SALP4Q/splits.txt index d0f99f8..b66afda 100644 --- a/config/SALP4Q/splits.txt +++ b/config/SALP4Q/splits.txt @@ -13,51 +13,54 @@ Sections: .sdata2 type:rodata align:16 .sbss2 type:bss align:32 -PowerPC_EABI_Support/Runtime/global_destructor_chain.c: - .text start:0x80368C58 end:0x80394124 +Revolution/OS/__start.c: + .init start:0x80004000 end:0x80006618 -PowerPC_EABI_Support/Runtime/__init_cpp_exceptions.cpp: - .text start:0x80395304 end:0x80395374 - .ctors start:0x804D3540 end:0x804D3544 rename:.ctors$10 - .dtors start:0x804D41E0 end:0x804D41E4 rename:.dtors$10 - .dtors start:0x804D41E4 end:0x804D41E8 rename:.dtors$15 - .sdata start:0x805F3828 end:0x805F3830 +Alice/Objects/Groups/CKGrpAliceHero.cpp: + .text start:0x8000DBF0 end:0x8000E264 -PowerPC_EABI_Support/Runtime/Gecko_ExceptionPPC.cpp: - .text start:0x80395374 end:0x80395480 +Alice/Objects/Hooks/CKHkAliceHero.cpp: + .text start:0x8001D894 end:0x8001E9E8 -Alice/Objects/0Managers/CKSoundManager.cpp: - .text start:0x802BA0DC end:0x802BAEA0 +Alice/Objects/Components/CKAliceHeroConfig.cpp: + .text start:0x800539D4 end:0x80055528 -Alice/Objects/1Services/CKSrvTrigger.cpp: - .text start:0x8022CE90 end:0x8022D744 +Alice/Objects/Logic/CKAliceGameSpawnPoint.cpp: + .text start:0x800EEFBC end:0x800EF814 -Alice/Objects/2Hooks/CKHkAliceHero.cpp: - .text start:0x8001D894 end:0x8001E9E8 +Alice/Objects/Geometries/CSkinGeometry.cpp: + .text start:0x801DB1A8 end:0x801DB6D8 -Alice/Objects/4Groups/CKGrpAliceHero.cpp: - .text start:0x8000DBF0 end:0x8000E264 +Alice/Objects/Graphics/CLightManager.cpp: + .text start:0x801E3D88 end:0x801E76B4 -Alice/Objects/6Components/CKAliceHeroConfig.cpp: - .text start:0x800539D4 end:0x80055528 +Alice/Objects/Nodes/CSpawnNode.cpp: + .text start:0x80211C1C end:0x80212060 -Alice/Objects/7Camera/CKCameraFixTrack.cpp: - .text start:0x80238574 end:0x802388B4 +Alice/Objects/Services/CKSrvTrigger.cpp: + .text start:0x8022CE90 end:0x8022D744 -Alice/Objects/8Cinematics/CKStartDoor.cpp: - .text start:0x80266000 end:0x802662AC +Alice/Objects/Camera/CKCameraFixTrack.cpp: + .text start:0x80238574 end:0x802388B4 -Alice/Objects/9Dictionaries/CKSoundDictionary.cpp: - .text start:0x802B9360 end:0x802B9A70 +Alice/Objects/Cinematics/CKStartDoor.cpp: + .text start:0x80266000 end:0x802662AC -Alice/Objects/10Geometries/CSkinGeometry.cpp: - .text start:0x801DB1A8 end:0x801DB6D8 +Alice/Objects/Dictionaries/CKSoundDictionary.cpp: + .text start:0x802B9360 end:0x802B9A70 -Alice/Objects/11Nodes/CSpawnNode.cpp: - .text start:0x80211C1C end:0x80212060 +Alice/Objects/Managers/CKSoundManager.cpp: + .text start:0x802BA0DC end:0x802BAEA0 -Alice/Objects/12Logic/CKAliceGameSpawnPoint.cpp: - .text start:0x800EEFBC end:0x800EF814 +PowerPC_EABI_Support/Runtime/global_destructor_chain.c: + .text start:0x80368C58 end:0x80394124 + +PowerPC_EABI_Support/Runtime/__init_cpp_exceptions.cpp: + .text start:0x80395304 end:0x80395374 + .ctors start:0x804D3540 end:0x804D3544 rename:.ctors$10 + .dtors start:0x804D41E0 end:0x804D41E4 rename:.dtors$10 + .dtors start:0x804D41E4 end:0x804D41E8 rename:.dtors$15 + .sdata start:0x805F3828 end:0x805F3830 -Alice/Objects/13Graphics/CLightManager.cpp: - .text start:0x801E3D88 end:0x801E76B4 +PowerPC_EABI_Support/Runtime/Gecko_ExceptionPPC.cpp: + .text start:0x80395374 end:0x80395480 diff --git a/configure.py b/configure.py index ca27779..5b2c228 100644 --- a/configure.py +++ b/configure.py @@ -184,7 +184,7 @@ "-fp hardware", "-Cpp_exceptions off", # "-W all", - "-O4,p", + "-O4,s", "-inline auto", '-pragma "cats off"', '-pragma "warn_notinlined off"', @@ -244,7 +244,7 @@ def MatchingFor(*versions): config.warn_missing_source = False config.libs = [ { - "lib": "PowerPC_EABI_Support", + "lib": "PowerPC_EABI_Support/Runtime", "mw_version": config.linker_version, "cflags": cflags_runtime, "host": False, @@ -255,6 +255,22 @@ def MatchingFor(*versions): Object(MatchingFor(), "PowerPC_EABI_Support/Runtime/global_destructor_chain.c") ], }, + { + "lib": "PowerPC_EABI_Support/MSL", + "mw_version": config.linker_version, + "cflags": cflags_runtime, + "host": False, + "progress_category": "sdk", # str | List[str] + "objects": [], + }, + { + "lib": "PowerPC_EABI_Support/MetroTRK", + "mw_version": config.linker_version, + "cflags": cflags_runtime, + "host": False, + "progress_category": "sdk", # str | List[str] + "objects": [], + }, { "lib": "Revolution", "mw_version": config.linker_version, @@ -268,10 +284,23 @@ def MatchingFor(*versions): { "lib": "Alice", "mw_version": config.linker_version, - "cflags": cflags_runtime, + "cflags": cflags_base, "host": False, "progress_category": "game", # str | List[str] - "objects": [], + "objects": [ + Object(MatchingFor(), "Alice/Objects/Managers/CKSoundManager.cpp"), + Object(MatchingFor(), "Alice/Objects/Services/CKSrvTrigger.cpp"), + Object(MatchingFor(), "Alice/Objects/Hooks/CKHkAliceHero.cpp"), + Object(MatchingFor(), "Alice/Objects/Groups/CKGrpAliceHero.cpp"), + Object(MatchingFor(), "Alice/Objects/Components/CKAliceHeroConfig.cpp"), + Object(MatchingFor(), "Alice/Objects/Camera/CKCameraFixTrack.cpp"), + Object(MatchingFor(), "Alice/Objects/Cinematics/CKStartDoor.cpp"), + Object(MatchingFor(), "Alice/Objects/Dictionaries/CKSoundDictionary.cpp"), + Object(MatchingFor(), "Alice/Objects/Geometries/CSkinGeometry.cpp"), + Object(MatchingFor(), "Alice/Objects/Nodes/CSpawnNode.cpp"), + Object(MatchingFor(), "Alice/Objects/Logic/CKAliceGameSpawnPoint.cpp"), + Object(MatchingFor(), "Alice/Objects/Graphics/CLightManager.cpp") + ], } ] diff --git a/include/PowerPC_EABI_Support/Runtime/Gecko_ExceptionPPC.h b/include/PowerPC_EABI_Support/Runtime/Gecko_ExceptionPPC.h index 0cf3b41..adb1733 100644 --- a/include/PowerPC_EABI_Support/Runtime/Gecko_ExceptionPPC.h +++ b/include/PowerPC_EABI_Support/Runtime/Gecko_ExceptionPPC.h @@ -3,7 +3,7 @@ #include "types.h" #include "PowerPC_EABI_Support/Runtime/__ppc_eabi_linker.h" - #define MAXFRAGMENTS 1 + #define MAXFRAGMENTS 0x1F typedef struct ExceptionTableIndex { u32 functionoffset; @@ -25,11 +25,11 @@ typedef struct ProcessInfo { __eti_init_info* exception_info; char* TOC; - s32 active; + u32 active; } ProcessInfo; - s32 __register_fragment(struct __eti_init_info*, char*); - void __unregister_fragment(s32); + u32 __register_fragment(struct __eti_init_info*, char*); + void __unregister_fragment(u32); s32 ExPPC_FindExceptionFragment(char*, FragmentInfo*); #endif diff --git a/include/Revolution/OS/__ppc_eabi_init.h b/include/Revolution/OS/__ppc_eabi_init.h new file mode 100644 index 0000000..723ca62 --- /dev/null +++ b/include/Revolution/OS/__ppc_eabi_init.h @@ -0,0 +1,89 @@ +#ifndef RVL_SDK_OS_PPC_EABI_INIT_H + #define RVL_SDK_OS_PPC_EABI_INIT_H + #include "global.h" + #ifdef __cplusplus + extern "C" { + #endif + + #pragma section ".init" + DECL_SECTION(".init") void __init_hardware(void); + // DECL_SECTION(".init") void __flush_cache(void*, size_t); + + s32 main(s32 argc, char** argv); + void __init_user(void); + void __init_cpp(void); + void __fini_cpp(void); + DECL_WEAK void exit(void); + void _ExitProcess(void); + + // Declare linker symbols for a section in the ROM + #define DECL_ROM_SECTION(x) \ + extern u8 _f##x[]; \ + extern u8 _f##x##_rom[]; \ + extern u8 _e##x[]; + + // Declare linker symbols for a BSS section + #define DECL_BSS_SECTION(x) \ + extern u8 _f##x[]; \ + extern u8 _e##x[]; + + // Debugger stack + extern u8 _db_stack_addr[]; + extern u8 _db_stack_end[]; + + // Program arena + extern u8 __ArenaLo[]; + extern u8 __ArenaHi[]; + + // Program stack + extern u8 _stack_addr[]; + extern u8 _stack_end[]; + + // Small data bases + extern u8 _SDA_BASE_[]; + extern u8 _SDA2_BASE_[]; + + // ROM sections + DECL_ROM_SECTION(_init); + DECL_ROM_SECTION(extab); + DECL_ROM_SECTION(extabindex); + DECL_ROM_SECTION(_text); + DECL_ROM_SECTION(_ctors); + DECL_ROM_SECTION(_dtors); + DECL_ROM_SECTION(_rodata); + DECL_ROM_SECTION(_data); + DECL_ROM_SECTION(_sdata); + DECL_ROM_SECTION(_sdata2); + DECL_ROM_SECTION(_stack); + + // BSS sections + DECL_BSS_SECTION(_bss); + DECL_BSS_SECTION(_sbss); + DECL_BSS_SECTION(_sbss2); + + // typedef struct RomSection { + // void* phys; // at 0x4 + // void* virt; // at 0x0 + // size_t size; // at 0x8 + // } RomSection; + + // typedef struct BssSection { + // void* virt; // at 0x0 + // size_t size; // at 0x8 + // } BssSection; + + typedef struct ExtabIndexInfo { + void* section; // at 0x0 + struct ExtabIndexInfo* extab; // at 0x4 + void* codeStart; // at 0x8 + u32 codeSize; // at 0xC + } ExtabIndexInfo; + + // DECL_SECTION(".init") extern const RomSection _rom_copy_info[]; + // DECL_SECTION(".init") extern const BssSection _bss_init_info[]; + DECL_SECTION(".init") extern const ExtabIndexInfo _eti_init_info[]; + + #ifdef __cplusplus + } + #endif +#endif diff --git a/src/PowerPC_EABI_Support/Runtime/Gecko_ExceptionPPC.cpp b/src/PowerPC_EABI_Support/Runtime/Gecko_ExceptionPPC.cpp index 0457e9b..46e09fb 100644 --- a/src/PowerPC_EABI_Support/Runtime/Gecko_ExceptionPPC.cpp +++ b/src/PowerPC_EABI_Support/Runtime/Gecko_ExceptionPPC.cpp @@ -3,12 +3,11 @@ extern ProcessInfo fragment_info[MAXFRAGMENTS]; -s32 __register_fragment(struct __eti_init_info* info, char* TOC) +#pragma schedule once +u32 __register_fragment(struct __eti_init_info* info, char* TOC) { ProcessInfo* f = fragment_info; - s32 i; - - for (i = 0; i < MAXFRAGMENTS; i++, f++) { + for (u32 i = 0; i < MAXFRAGMENTS+1; i++, f++) { if (f->active == 0) { f->exception_info = info; f->TOC = TOC; @@ -16,14 +15,14 @@ s32 __register_fragment(struct __eti_init_info* info, char* TOC) return i; } } - return -1; } +#pragma schedule twice -void __unregister_fragment(s32 fragmentID) +void __unregister_fragment(u32 fragmentID) { ProcessInfo* f; - if (fragmentID >= 0 && fragmentID < MAXFRAGMENTS) { + if (fragmentID >= 0 && fragmentID <= MAXFRAGMENTS) { f = &fragment_info[fragmentID]; f->exception_info = 0; f->TOC = 0; @@ -37,7 +36,7 @@ s32 ExPPC_FindExceptionFragment(char* returnaddr, FragmentInfo* frag) s32 i; __eti_init_info* eti_info; - for (i = 0, f = fragment_info; i < MAXFRAGMENTS; ++i, ++f) { + for (i = 0, f = fragment_info; i < MAXFRAGMENTS+1; ++i, ++f) { if (f->active) { eti_info = f->exception_info; while (1) { @@ -58,6 +57,5 @@ s32 ExPPC_FindExceptionFragment(char* returnaddr, FragmentInfo* frag) } } } - return 0; } diff --git a/src/Revolution/OS/__start.c b/src/Revolution/OS/__start.c index e69de29..e5b5965 100644 --- a/src/Revolution/OS/__start.c +++ b/src/Revolution/OS/__start.c @@ -0,0 +1,346 @@ +#include "global.h" +// #include "Revolution/OS/__ppc_eabi_init.h" + +static u8 Debug_BBA; + +// SECTION_INIT static void __check_pad3(void) { +// if ((OS_GC_PAD_3_BTN & 0x0EEF) == 0x0EEF) { +// OSResetSystem(0, 0, 0); +// } +// } + +SECTION_INIT static void __set_debug_bba(void) { + Debug_BBA = TRUE; +} + +SECTION_INIT static BOOL __get_debug_bba(void) { + return Debug_BBA; +} + +// SECTION_INIT DECL_WEAK asm void __start(void) { +// // clang-format off +// nofralloc + +// // Setup hardware +// bl __init_registers +// bl __init_hardware + +// // Create first stack frame +// li r0, -1 +// // Parameter save +// stwu r1, -8(r1) +// // LR save (-1 to signify first frame) +// stw r0, 4(r1) +// // Back chain (-1 to signify first frame) +// stw r0, 0(r1) + +// // Setup ROM/BSS +// bl __init_data + +// // Clear debugger exception mask +// li r0, 0 +// lis r6, (OS_DEBUG_INTERFACE + OSDebugInterface.exceptionMask)@ha +// addi r6, r6, (OS_DEBUG_INTERFACE + OSDebugInterface.exceptionMask)@l +// stw r0, 0(r6) + +// /** +// * Check the BI2 debug flag to determine how to call InitMetroTRK. +// * +// * This can be done by either checking the value from the DVD +// * (OSBI2.debugFlag), or by checking the global OS_BI2_DEBUG_FLAG. +// * +// * If the DVD BI2 is available, it is prioritized over the global value. +// */ +// lis r6, OS_DVD_BI2@ha +// addi r6, r6, OS_DVD_BI2@l +// lwz r6, 0(r6) +// cmplwi r6, 0 +// beq _no_dvd_bi2 // <- NULL BI2, try the OS global + +// // Use the DVD's flag +// lwz r7, OSBI2.debugFlag(r6) +// b _handle_bi2_debug_flag + +// /** +// * At this point, we do one last check to decide whether we want to +// * setup the TRK debugger. +// * +// * If the OS boot info specifies an arena hi, we grab the BI2 debug +// * flag using the global OS_BI2_DEBUG_FLAG. +// * +// * (This must be some heuristic, but I don't understand it) +// */ +// _no_dvd_bi2: +// lis r5, (OS_BOOT_INFO + OSBootInfo.arenaHi)@ha +// addi r5, r5, (OS_BOOT_INFO + OSBootInfo.arenaHi)@l +// lwz r5, 0(r5) +// cmplwi r5, 0 +// beq _check_for_exec_args // <- NULL arena hi, don't setup the debugger + +// // Grab the global BI2 debug flag +// lis r7, OS_BI2_DEBUG_FLAG@ha +// addi r7, r7, OS_BI2_DEBUG_FLAG@l +// lwz r7, 0(r7) + +// /** +// * The BI2 debug flag/level decides how to set up TRK. +// * +// * Most importantly, it decides how InitMetroTRKCommTable is called, by +// * specifying a type of hardware. +// * +// * ID 1: NDEV hardware +// * ID 2: BBA hardware +// * Anything else: "Unknown" to TRK, defaults to GDEV hardware +// * +// * BI2 debug level maps to TRK comm hardware ID as follows: +// * - BI2 Level 2 -> ID 0 (GDEV) +// * - BI2 Level 3 -> ID 1 (NDEV) +// * - BI2 Level 4 -> ID 2 (BBA) +// */ +// _handle_bi2_debug_flag: +// // BI2 Debug Level 3: Init TRK as GDEV hardware +// li r5, 0 +// cmplwi r7, 2 +// beq _call_init_metro_trk + +// // BI2 Debug Level 2: Init TRK as NDEV hardware +// cmplwi r7, 3 +// li r5, 1 +// beq _call_init_metro_trk + +// // BI2 Debug Level 4: Init TRK as BBA hardware +// // *Any other debug level is ignored* +// cmplwi r7, 4 +// bne _check_for_exec_args // <- Ignore debug level, goto next step +// /** +// * I think at one point this used to call InitMetroTRK as they set +// * r5 here; however, in this version it goes unused. +// * +// * What currently happens is __set_debug_bba sets Debug_BBA, which +// * will result in a call to InitMetroTRK_BBA after the OS is +// * initialized. +// * +// * Maybe this is a leftover from how things worked before +// * InitMetroTRK_BBA was written? +// */ +// li r5, 2 +// bl __set_debug_bba +// b _check_for_exec_args + +// /** +// * Call InitMetroTRK +// * The MetroTRK hardware ID is specified in r5 +// */ +// _call_init_metro_trk: +// lis r6, InitMetroTRK@ha +// addi r6, r6, InitMetroTRK@l +// mtlr r6 +// blrl + +// /** +// * After setting up the hardware and the debugger, we next setup +// * the program arguments. This label checks whether any arguments +// * exist. +// * +// * BI2 contains an offset from itself to the argument data, which +// * is formatted as follows: +// * +// * typedef struct BI2Args { +// * int argc; +// * union { +// * char* argument; +// * u32 offset; +// * } argv[]; +// * } BI2Args; +// */ +// _check_for_exec_args: +// lis r6, OS_DVD_BI2@ha +// addi r6, r6, OS_DVD_BI2@l +// lwz r5, 0(r6) +// cmplwi r5, 0 +// beq+ _no_args // <- No BI2 to get args from + +// lwz r6, OSBI2.argumentOfs(r5) +// cmplwi r6, 0 +// beq+ _no_args // <- Invalid argument offset in BI2 + +// // Calculate pointer to argument data +// add r6, r5, r6 +// // First integer value is the argument count +// lwz r14, 0(r6) +// cmplwi r14, 0 +// beq _no_args // <- Argument count is zero + +// // BI2 args + 0x8, used to adjust MEM1 later +// addi r15, r6, 4 +// // Move argc to the counter to prepare the loop +// mtctr r14 + +// /** +// * This loop unpacks the arguments by converting them from offsets +// * to pointers, in-place. +// * +// * The offsets are relative to the start of the BI2, so we just +// * add the offset to the BI2 pointer and write it back. +// */ +// _unpack_args_loop: +// // Skip over argc +// addi r6, r6, 4 +// // Load argument offset +// lwz r7, 0(r6) +// // Convert offset to pointer +// add r7, r7, r5 +// // Store pointer +// stw r7, 0(r6) +// bdnz _unpack_args_loop + +// /** +// * Both the MEM1 arena hi and the MEM1 limit are adjusted to +// * preserve the BI2 args. +// * +// * They are set to eight bytes into the BI2 arguments, +// * rounded down to the nearest 32 bytes. +// */ +// lis r5, (OS_BOOT_INFO + OSBootInfo.arenaHi)@ha +// addi r5, r5, (OS_BOOT_INFO + OSBootInfo.arenaHi)@l +// clrrwi r7, r15, 5 +// stw r7, 0(r5) + +// lis r5, OS_USABLE_MEM1_END@ha +// addi r5, r5, OS_USABLE_MEM1_END@l +// clrrwi r7, r15, 5 +// stw r7, 0(r5) +// b _init_os + +// /** +// * Either there was no BI2 available, or it did not contain any arguments +// */ +// _no_args: +// li r14, 0 // argc = 0 +// li r15, 0 // argv = NULL + +// /** +// * Here, the OS and its debug monitor are initialized, and +// * then we check if we should call __check_pad3. +// * +// * __check_pad3 is called before future initialization if the disk +// * drive device code is 0x0001, or if the OS' inquiry fails (emulation +// * or some debug hardware?) +// * +// * The apploader reads the button state of the fourth GCN controller +// * and writes it to GC_PAD_3_BTN (zero-indexed), which is used in +// * __check_pad3. WiiBrew states that this is for GameCube NR disc +// * support, so that could explain the unusual DVD device code address. +// */ +// _init_os: +// // Initialize the OS and its debug monitor +// bl DBInit +// bl OSInit + +// // Load DVD device code address +// lis r4, OS_DVD_DEVICE_CODE@ha +// addi r4, r4, OS_DVD_DEVICE_CODE@l +// lhz r3, 0(r4) +// // Check whether OS inquiry failed +// andi. r5, r3, DVD_DEVICE_CODE_READ +// beq _call_check_pad3 // <- Bit 0 is NOT set (fail) +// // 0x0001 may be a real ID or a failsafe (see OS.c:InquiryCallback) +// andi. r3, r3, (~DVD_DEVICE_CODE_READ) & 0xFFFF +// cmplwi r3, 0x0001 +// bne _check_debug_bba // <- NOT 0x0001 device code address +// _call_check_pad3: +// bl __check_pad3 + +// /** +// * If the BI2 debug level from earlier was set to four, we need to +// * initialize the debugger for BBA hardware. +// */ +// _check_debug_bba: +// bl __get_debug_bba +// cmplwi r3, 1 +// bne _after_init_metro_trk_bba // <- Debug_BBA == false +// bl InitMetroTRK_BBA + +// /** +// * 1. Initialize C++ runtime +// * 2. Call main(argc, argv) +// * 3. Teardown C++ runtime +// */ +// _after_init_metro_trk_bba: +// bl __init_user +// mr r3, r14 +// mr r4, r15 +// bl main +// b exit // <- Will halt CPU +// // clang-format on +// } + +// DECL_SECTION(".init") static asm void __init_registers(void) { +// // clang-format off +// nofralloc + +// li r0, 0 +// li r3, 0 +// li r4, 0 +// li r5, 0 +// li r6, 0 +// li r7, 0 +// li r8, 0 +// li r9, 0 +// li r10, 0 +// li r11, 0 +// li r12, 0 +// li r14, 0 +// li r15, 0 +// li r16, 0 +// li r17, 0 +// li r18, 0 +// li r19, 0 +// li r20, 0 +// li r21, 0 +// li r22, 0 +// li r23, 0 +// li r24, 0 +// li r25, 0 +// li r26, 0 +// li r27, 0 +// li r28, 0 +// li r29, 0 +// li r30, 0 +// li r31, 0 + +// lis r1, _stack_addr@h +// ori r1, r1, _stack_addr@l +// lis r2, _SDA2_BASE_@h +// ori r2, r2, _SDA2_BASE_@l +// lis r13, _SDA_BASE_@h +// ori r13, r13, _SDA_BASE_@l + +// blr +// // clang-format on +// } + +// SECTION_INIT static void __init_data(void) { +// const RomSection* rs; +// const BssSection* bs; + +// rs = _rom_copy_info; +// while (TRUE) { +// if (rs->size == 0) { +// break; +// } + +// __copy_rom_section(rs->virt, rs->phys, rs->size); +// rs++; +// } + +// bs = _bss_init_info; +// while (TRUE) { +// if (bs->size == 0) { +// break; +// } + +// __init_bss_section(bs->virt, bs->size); +// bs++; +// } +// }