From 03abdf57470842eaebc0c5c8578d21000128ef93 Mon Sep 17 00:00:00 2001 From: RoadrunnerWMC Date: Wed, 11 Mar 2026 03:20:42 -0400 Subject: [PATCH 1/5] Don't collect .kamek sections into guest address space The previous implementation temporarily placed the .kamek (hooks) section into the simulated Wii memory just after the .bss section. Any relocations that applied to this address range were assumed to apply to the hook data, which was fine because there was no way to violate that assumption at the time. However, with the new code-injection feature, it's now possible to inject code into arbitrary static address ranges. If building in -static mode, that means that .kamek is given a fixed, absolute address range, which can potentially conflict with one or more injected code sections. It's a rare edge case, but it can happen. When it does, Kamek can no longer distinguish between relocations meant to apply to the injected section(s) and ones meant to apply to the hook data, which can lead to exceptions or incorrect behavior. To fix this, this commit makes Kamek keep track of the .kamek section data *separately*, outside of the Wii address space entirely. This somewhat complicates the logic, unfortunately, but it fixes the root cause of the issue. --- Source/Linker.cs | 103 ++++++++++++++++++++++++----------------------- 1 file changed, 52 insertions(+), 51 deletions(-) diff --git a/Source/Linker.cs b/Source/Linker.cs index 3746f8d..ab453a4 100644 --- a/Source/Linker.cs +++ b/Source/Linker.cs @@ -62,7 +62,6 @@ private void DoLink(Dictionary externalSymbols) private Word _dataStart, _dataEnd; private Word _outputStart, _outputEnd; private Word _bssStart, _bssEnd; - private Word _kamekStart, _kamekEnd; private byte[] _memory = null; public Word BaseAddress { get { return _baseAddress; } } @@ -76,6 +75,7 @@ private void DoLink(Dictionary externalSymbols) #region Collecting Sections private Dictionary _sectionBases = new Dictionary(); + private List _hookSections = new List(); private void ImportSections(ref List blobs, ref Word location, string prefix) { @@ -104,6 +104,15 @@ where s.name.StartsWith(prefix) } } + private void ImportHookSections() + { + foreach (var elf in _modules) + foreach (var s in (from s in elf.Sections + where s.name.StartsWith(".kamek") + select s)) + _hookSections.Add(s); + } + private void CollectSections() { List blobs = new List(); @@ -144,10 +153,6 @@ private void CollectSections() ImportSections(ref blobs, ref location, ".bss"); _bssEnd = location; - _kamekStart = location; - ImportSections(ref blobs, ref location, ".kamek"); - _kamekEnd = location; - // Create one big blob from this _memory = new byte[location - _baseAddress]; int position = 0; @@ -156,26 +161,8 @@ private void CollectSections() Array.Copy(blob, 0, _memory, position, blob.Length); position += blob.Length; } - } - #endregion - - #region Result Binary Manipulation - private ushort ReadUInt16(Word addr) - { - return Util.ExtractUInt16(_memory, addr - _baseAddress); - } - private uint ReadUInt32(Word addr) - { - return Util.ExtractUInt32(_memory, addr - _baseAddress); - } - private void WriteUInt16(Word addr, ushort value) - { - Util.InjectUInt16(_memory, addr - _baseAddress, value); - } - private void WriteUInt32(Word addr, uint value) - { - Util.InjectUInt32(_memory, addr - _baseAddress, value); + ImportHookSections(); } #endregion @@ -194,6 +181,7 @@ private struct SymbolName } private Dictionary _globalSymbols = null; private Dictionary> _localSymbols = null; + private Dictionary, uint> _hookSymbols = null; private Dictionary _symbolTableContents = null; private Dictionary _externalSymbols = null; private Dictionary _symbolSizes = null; @@ -203,6 +191,7 @@ private void BuildSymbolTables() { _globalSymbols = new Dictionary(); _localSymbols = new Dictionary>(); + _hookSymbols = new Dictionary, uint>(); _symbolTableContents = new Dictionary(); _symbolSizes = new Dictionary(); @@ -292,9 +281,15 @@ private SymbolName[] ParseSymbolTable(Elf elf, Elf.ElfSection symtab, Elf.ElfSec { // Part of a section var section = elf.Sections[st_shndx]; - if (!_sectionBases.ContainsKey(section)) + if (_sectionBases.ContainsKey(section)) + addr = _sectionBases[section] + st_value; + else if (_hookSections.Contains(section)) + { + _hookSymbols[new Tuple(section, name)] = st_value; + continue; + } + else continue; // skips past symbols we don't care about, like DWARF junk - addr = _sectionBases[section] + st_value; } else throw new NotImplementedException("unknown section index found in symbol table"); @@ -428,19 +423,24 @@ private void ProcessRelaSection(Elf elf, Elf.ElfSection relocs, Elf.ElfSection s if (symIndex == 0) throw new InvalidDataException("linking to undefined symbol"); - if (!_sectionBases.ContainsKey(section)) + + Word source; + if (_sectionBases.ContainsKey(section)) + source = _sectionBases[section] + r_offset; + else if (_hookSections.Contains(section)) + source = new Word(WordType.Value, r_offset); + else continue; // we don't care about this SymbolName symbol = _symbolTableContents[symtab][symIndex]; string symName = symbol.name; //Console.WriteLine("{0,-30} {1}", symName, reloc); - Word source = _sectionBases[section] + r_offset; Word dest = (String.IsNullOrEmpty(symName) ? _sectionBases[elf.Sections[symbol.shndx]] : ResolveSymbol(elf, symName).address) + r_addend; //Console.WriteLine("Linking from 0x{0:X8} to 0x{1:X8}", source.Value, dest.Value); - if (!KamekUseReloc(reloc, source, dest)) + if (!KamekUseReloc(section, reloc, source.Value, dest)) _fixups.Add(new Fixup { type = reloc, source = source, dest = dest }); } } @@ -448,16 +448,16 @@ private void ProcessRelaSection(Elf elf, Elf.ElfSection relocs, Elf.ElfSection s #region Kamek Hooks - private Dictionary _kamekRelocations = new Dictionary(); + private Dictionary, Word> _hookRelocations = new Dictionary, Word>(); - private bool KamekUseReloc(Elf.Reloc type, Word source, Word dest) + private bool KamekUseReloc(Elf.ElfSection section, Elf.Reloc type, uint source, Word dest) { - if (source < _kamekStart || source >= _kamekEnd) + if (!_hookSections.Contains(section)) return false; if (type != Elf.Reloc.R_PPC_ADDR32) throw new InvalidOperationException($"Unsupported relocation type {type} in the Kamek hook data section"); - _kamekRelocations[source] = dest; + _hookRelocations[new Tuple(section, source)] = dest; return true; } @@ -473,29 +473,30 @@ public struct HookData private void ProcessHooks() { - foreach (var elf in _modules) + foreach (var pair in _hookSymbols) { - foreach (var pair in _localSymbols[elf]) + Elf.ElfSection section = pair.Key.Item1; + string name = pair.Key.Item2; + + if (name.StartsWith("_kHook")) { - if (pair.Key.StartsWith("_kHook")) - { - var cmdAddr = pair.Value.address; + uint cmdOffs = pair.Value; - var argCount = ReadUInt32(cmdAddr); - var type = ReadUInt32(cmdAddr + 4); - var args = new Word[argCount]; + var argCount = Util.ExtractUInt32(section.data, cmdOffs); + var type = Util.ExtractUInt32(section.data, cmdOffs + 4); + var args = new Word[argCount]; - for (int i = 0; i < argCount; i++) - { - var argAddr = cmdAddr + (8 + (i * 4)); - if (_kamekRelocations.ContainsKey(argAddr)) - args[i] = _kamekRelocations[argAddr]; - else - args[i] = new Word(WordType.Value, ReadUInt32(argAddr)); - } - - _hooks.Add(new HookData { type = type, args = args }); + for (uint i = 0; i < argCount; i++) + { + var argOffs = cmdOffs + (8 + (i * 4)); + var tuple = new Tuple(section, argOffs); + if (_hookRelocations.ContainsKey(tuple)) + args[i] = _hookRelocations[tuple]; + else + args[i] = new Word(WordType.Value, Util.ExtractUInt32(section.data, argOffs)); } + + _hooks.Add(new HookData { type = type, args = args }); } } } From c9ed2156b3ce47eb735b922673bb3ab12b2f28e7 Mon Sep 17 00:00:00 2001 From: RoadrunnerWMC Date: Wed, 11 Mar 2026 03:20:44 -0400 Subject: [PATCH 2/5] Add injection support to kamek.h, kamek_asm.S, and Linker --- Source/Linker.cs | 103 ++++++++++++++++++++++++++++++++++++++++++- k_stdlib/kamek.h | 73 +++++++++++++++++++++++++++++- k_stdlib/kamek_asm.S | 57 ++++++++++++++++++++++-- 3 files changed, 228 insertions(+), 5 deletions(-) diff --git a/Source/Linker.cs b/Source/Linker.cs index ab453a4..b0a154a 100644 --- a/Source/Linker.cs +++ b/Source/Linker.cs @@ -73,7 +73,107 @@ private void DoLink(Dictionary externalSymbols) public long BssSize { get { return _bssEnd - _bssStart; } } - #region Collecting Sections + #region Collecting Injection Sections + + [Flags] + public enum InjectionFlags : uint + { + KM_INJECT_STRIP_BLR_PAST = 1, + KM_INJECT_ADD_PADDING = 2 + } + + public struct InjectedSection + { + public uint Address; + public byte[] Data; + } + + private List _injectedSections = new List(); + public IReadOnlyList InjectedSections { get { return _injectedSections; } } + + private void ImportInjectedSections() + { + foreach (var elf in _modules) + { + foreach (var injectionSection in (from s in elf.Sections + where s.name.StartsWith(".km_inject_") && !s.name.EndsWith("_meta") + select s)) + { + Elf.ElfSection metaSection = (from s in elf.Sections + where s.name == $"{injectionSection.name}_meta" + select s).Single(); + + var numValues = Util.ExtractUInt32(metaSection.data, 0); + if (numValues < 4) + continue; + + var startAddr = Util.ExtractUInt32(metaSection.data, 4); + var endAddr = Util.ExtractUInt32(metaSection.data, 8); + var flags = (InjectionFlags)Util.ExtractUInt32(metaSection.data, 12); + var pad = Util.ExtractUInt32(metaSection.data, 16); + + ProcessSectionInjection(elf, injectionSection, startAddr, endAddr, flags, pad); + } + } + } + + private void ProcessSectionInjection(Elf elf, Elf.ElfSection sec, uint start, uint end, InjectionFlags flags, uint pad) + { + uint startPreMap = start; + uint sizePreMap = end - start; + + start = Mapper.Remap(start); + end = Mapper.Remap(end); + + uint sizePostMap = end - start; + if (sizePreMap != sizePostMap) + throw new InvalidDataException($"Injected code range at 0x{startPreMap:x} changes size when remapped (0x{sizePreMap:x} -> 0x{sizePostMap:x})"); + + EnforceSectionInjectionSize(sec, start, end - start + 4, flags, pad); + + _injectedSections.Add(new InjectedSection { Address=start, Data=sec.data }); + _sectionBases[sec] = new Word(WordType.AbsoluteAddr, start); + } + + private void EnforceSectionInjectionSize(Elf.ElfSection sec, uint start, uint requestedSize, InjectionFlags flags, uint pad) + { + if (sec.sh_size < requestedSize) + { + // Section data is too short + + if ((flags & InjectionFlags.KM_INJECT_ADD_PADDING) != 0) + { + // Pad it with the user-provided pad value + Array.Resize(ref sec.data, (int)requestedSize); + for (uint offs = sec.sh_size; offs < requestedSize; offs += 4) + Util.InjectUInt32(sec.data, offs, pad); + sec.sh_size = requestedSize; + } + } + else if (sec.sh_size > requestedSize) + { + // Section data is too long + + if ((flags & InjectionFlags.KM_INJECT_STRIP_BLR_PAST) != 0 + && sec.sh_size == requestedSize + 4 + && Util.ExtractUInt32(sec.data, requestedSize) == 0x4e800020) + { + // Section data is too long, but by exactly one "blr" instruction. Instead of + // erroring, make an exception and just trim the blr instead. (This way, users + // don't need to put a "nofralloc" in every single kmWriteDefAsm call.) + Array.Resize(ref sec.data, (int)requestedSize); + sec.sh_size = requestedSize; + } + else + { + throw new InvalidDataException($"Injected code at 0x{start:x} doesn't fit (0x{sec.sh_size:x} > 0x{requestedSize:x})"); + } + } + } + #endregion + + + #region Collecting Other Sections private Dictionary _sectionBases = new Dictionary(); private List _hookSections = new List(); @@ -163,6 +263,7 @@ private void CollectSections() } ImportHookSections(); + ImportInjectedSections(); } #endregion diff --git a/k_stdlib/kamek.h b/k_stdlib/kamek.h index 618eef7..bd0610a 100644 --- a/k_stdlib/kamek.h +++ b/k_stdlib/kamek.h @@ -21,6 +21,17 @@ #define kctInjectCall 4 #define kctPatchExit 5 +// KM_INJECT_STRIP_BLR_PAST (kmWriteDefAsm flag) +// If the specified address range has size N, and the compiled code has size +// exactly N+4, and the last four bytes are a blr instruction, delete the +// instruction instead of raising a link-time error. +#define KM_INJECT_STRIP_BLR_PAST 0x1 +// KM_INJECT_ADD_PADDING (kmWriteDefAsm flag) +// If the specified address range has size N, and the compiled code has size +// < N, pad the remaining space with the provided pad value. +// (If this flag isn't set, the remaining space is left untouched.) +#define KM_INJECT_ADD_PADDING 0x2 + #define kmIdentifier(key, counter) \ _k##key##counter @@ -68,7 +79,6 @@ struct _kmHook_4ui_2f_t { unsigned int a; unsigned int b; unsigned int c; unsign #define kmWrite16(addr, value) kmHook3(kctWrite, 3, (addr), (value)) #define kmWrite8(addr, value) kmHook3(kctWrite, 4, (addr), (value)) #define kmWriteFloat(addr, value) kmHook_2ui_1f(kctWrite, 2, (addr), (value)) -#define kmWriteNop(addr) kmWrite32((addr), 0x60000000) // kmPatchExitPoint // Force the end of a Kamek function to always jump to a specific address @@ -108,4 +118,65 @@ struct _kmHook_4ui_2f_t { unsigned int a; unsigned int b; unsigned int c; unsign #define kmCallDefAsm(addr) \ kmCallDefInt(__COUNTER__, addr, asm void, ) +#define _kmWriteDefHelper0(pragmaString) _Pragma(#pragmaString) +#define _kmWriteDefHelper1(secName) \ + _kmWriteDefHelper0(section data_type sdata_type #secName #secName) +#define _kmWriteDefHelper2(secName) \ + _kmWriteDefHelper0(section code_type #secName) +#define _kmWriteDefHelper3(secName) \ + __declspec (section #secName) + +// kmWriteDefAsm(startAddr[, endAddr[, flags[, pad]]]) +// Inject assembly code directly into the target executable, overwriting +// whatever's there. startAddr and endAddr are the addresses of the first and +// last instructions to replace. +// - If endAddr is omitted, it defaults to startAddr. +// - flags specifies options. Default is `KM_INJECT_STRIP_BLR_PAST | KM_INJECT_ADD_PADDING`. +// - pad is the value to fill extra space with if KM_INJECT_ADD_PADDING is set. +// Default is nop (0x60000000). +#define _kmWriteDefAsm3(counter, startAddr, endAddr, flags, pad) \ + _Pragma("push") \ + _kmWriteDefHelper1(.km_inject_##counter##_meta) \ + _kmWriteDefHelper2(.km_inject_##counter) \ + _kmWriteDefHelper3(.km_inject_##counter##_meta) static const unsigned int kmIdentifier(InjectionMeta, counter) [5] = { 4, (startAddr), (endAddr), (flags), (pad) }; \ + _kmWriteDefHelper3(.km_inject_##counter) static void kmIdentifier(UserFunc, counter) (); \ + _Pragma("pop") \ + static asm void kmIdentifier(UserFunc, counter) () +#define _get1stArg(_1, ...) _1 +#define _get2ndArg(_1, _2, ...) _2 +#define _get3rdArg(_1, _2, _3, ...) _3 +#define _get4thArg(_1, _2, _3, _4, ...) _4 +#define _kmWriteDefAsm2(counter, ...) \ + _kmWriteDefAsm3( \ + counter, \ + _get1stArg(__VA_ARGS__, 0), \ + _get2ndArg(__VA_ARGS__, _get1stArg(__VA_ARGS__, 0), 0), \ + _get3rdArg(__VA_ARGS__, KM_INJECT_STRIP_BLR_PAST|KM_INJECT_ADD_PADDING, KM_INJECT_STRIP_BLR_PAST|KM_INJECT_ADD_PADDING, 0), \ + _get4thArg(__VA_ARGS__, 0x60000000, 0x60000000, 0x60000000, 0) \ + ) +#define kmWriteDefAsm(...) \ + _kmWriteDefAsm2(__COUNTER__, __VA_ARGS__) + +// kmWriteDefCpp +// Inject a function defined directly underneath into the target executable, +// overwriting whatever function is already there. startAddr and endAddr are +// the addresses of the first instruction to replace and the last instruction +// that *can* be replaced, respectively. +#define _kmWriteDefCpp2(counter, startAddr, endAddr, returnType, ...) \ + _Pragma("push") \ + _kmWriteDefHelper1(.km_inject_##counter##_meta) \ + _kmWriteDefHelper2(.km_inject_##counter) \ + _kmWriteDefHelper3(.km_inject_##counter##_meta) static const unsigned int kmIdentifier(InjectionMeta, counter) [5] = { 4, (startAddr), (endAddr), 0, 0 }; \ + _kmWriteDefHelper3(.km_inject_##counter) static returnType kmIdentifier(UserFunc, counter) (__VA_ARGS__); \ + _Pragma("pop") \ + static returnType kmIdentifier(UserFunc, counter) (__VA_ARGS__) +#define kmWriteDefCpp(startAddr, endAddr, returnType, ...) \ + _kmWriteDefCpp2(__COUNTER__, startAddr, endAddr, returnType, __VA_ARGS__) + +// kmWriteNop, kmWriteNops +// Write one or more nop instructions to an address or address range +#define kmWriteNop(addr) kmWrite32((addr), 0x60000000) +#define kmWriteNops(startAddr, endAddr) \ + kmWriteDefAsm((startAddr), (endAddr)) { nofralloc; nop } + #endif diff --git a/k_stdlib/kamek_asm.S b/k_stdlib/kamek_asm.S index 8c0452b..05447c8 100755 --- a/k_stdlib/kamek_asm.S +++ b/k_stdlib/kamek_asm.S @@ -4,6 +4,10 @@ kctInjectBranch .equ 3 kctInjectCall .equ 4 kctPatchExit .equ 5 +KM_INJECT_STRIP_BLR_PAST .equ 0x1 +KM_INJECT_ADD_PADDING .equ 0x2 + + // general hook definition macros kmHook0: .macro type .section .kamek @@ -103,9 +107,6 @@ kmWrite8: .macro addr, value kmWriteFloat: .macro addr, value kmHook_2ui_1f kctWrite, 2, addr, value .endm -kmWriteNop: .macro addr - kmWrite32 addr, 0x60000000 - .endm // kmBranch, kmCall // Set up a branch from a specific instruction to a specific address @@ -132,6 +133,56 @@ kmCallDef: .macro addr __kUserFuncCall\@: .endm +// kmWriteDefStart startAddr[, endAddr[, flags[, pad]]] +// kmWriteDefEnd +// Inject assembly code directly into the target executable, overwriting +// whatever's there. startAddr and endAddr are the addresses of the first and +// last instructions to replace. +// - If endAddr is omitted, it defaults to startAddr. +// - flags specifies options. Default is `KM_INJECT_STRIP_BLR_PAST | KM_INJECT_ADD_PADDING`. +// - pad is the value to fill extra space with if KM_INJECT_ADD_PADDING is set. +// Default is nop (0x60000000). +kmWriteDefStart: .macro startAddr, endAddr, flags, pad + .section .km_inject_\@_meta + _kInjectionMeta\@: .long 4, startAddr + .if narg >= 2 + .long endAddr + .else + .long startAddr + .endif + .if narg >= 3 + .long flags + .else + .long KM_INJECT_STRIP_BLR_PAST | KM_INJECT_ADD_PADDING + .endif + .if narg >= 4 + .long pad + .else + .long 0x60000000 + .endif + .previous + .section .discard + li r0, (_kInjectionMeta\@)@l // reference to prevent CodeWarrior from deleting the symbol + li r0, (__kUserFuncInject\@)@l // ditto + .previous + .section .km_inject_\@ + __kUserFuncInject\@: + .endm +kmWriteDefEnd: .macro + .previous + .endm + +// kmWriteNop, kmWriteNops +// Write one or more nop instructions to an address or address range +kmWriteNop: .macro addr + kmWrite32 addr, 0x60000000 + .endm +kmWriteNops: .macro startAddr, endAddr + kmWriteDefStart startAddr, endAddr + nop + kmWriteDefEnd + .endm + // kamek_b, kamek_bl // Branch to or call a direct code address from the original game executable. // This allows Kamek's address mapping functionality to be used without From 9d060a3fd3001d0c882b7c2c1d976cae3412bbb4 Mon Sep 17 00:00:00 2001 From: RoadrunnerWMC Date: Wed, 11 Mar 2026 03:20:45 -0400 Subject: [PATCH 3/5] Rename WriteCommand to WriteWordCommand --- Source/Commands/{WriteCommand.cs => WriteWordCommand.cs} | 4 ++-- Source/Hooks/WriteHook.cs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) rename Source/Commands/{WriteCommand.cs => WriteWordCommand.cs} (98%) diff --git a/Source/Commands/WriteCommand.cs b/Source/Commands/WriteWordCommand.cs similarity index 98% rename from Source/Commands/WriteCommand.cs rename to Source/Commands/WriteWordCommand.cs index 8947875..26a19b4 100644 --- a/Source/Commands/WriteCommand.cs +++ b/Source/Commands/WriteWordCommand.cs @@ -7,7 +7,7 @@ namespace Kamek.Commands { - class WriteCommand : Command + class WriteWordCommand : Command { public enum Type { @@ -49,7 +49,7 @@ private static Ids IdFromType(Type type, bool isConditional) public readonly Word Value; public readonly Word? Original; - public WriteCommand(Word address, Word value, Type valueType, Word? original) + public WriteWordCommand(Word address, Word value, Type valueType, Word? original) : base(IdFromType(valueType, original.HasValue), address) { Value = value; diff --git a/Source/Hooks/WriteHook.cs b/Source/Hooks/WriteHook.cs index 47852cd..cc0b17b 100644 --- a/Source/Hooks/WriteHook.cs +++ b/Source/Hooks/WriteHook.cs @@ -13,18 +13,18 @@ class WriteHook : Hook public WriteHook(bool isConditional, Word[] args, AddressMapper mapper) { if (args.Length != (isConditional ? 4 : 3)) - throw new InvalidDataException("wrong arg count for WriteCommand"); + throw new InvalidDataException("wrong arg count for WriteWordCommand"); // expected args: // address : pointer to game code // value : value, OR pointer to game code or to Kamek code // original : value, OR pointer to game code or to Kamek code - var type = (WriteCommand.Type)GetValueArg(args[0]).Value; + var type = (WriteWordCommand.Type)GetValueArg(args[0]).Value; Word address, value; Word? original = null; address = GetAbsoluteArg(args[1], mapper); - if (type == WriteCommand.Type.Pointer) + if (type == WriteWordCommand.Type.Pointer) { value = GetAnyPointerArg(args[2], mapper); if (isConditional) @@ -37,7 +37,7 @@ public WriteHook(bool isConditional, Word[] args, AddressMapper mapper) original = GetValueArg(args[3]); } - Commands.Add(new WriteCommand(address, value, type, original)); + Commands.Add(new WriteWordCommand(address, value, type, original)); } } } From b4c6aa287731cfb1b4b43072b8539564f87d1c05 Mon Sep 17 00:00:00 2001 From: RoadrunnerWMC Date: Wed, 11 Mar 2026 03:20:48 -0400 Subject: [PATCH 4/5] Add WriteRange command, and use it for injections (no loader support yet) --- Source/Commands/Command.cs | 1 + Source/Commands/WriteRangeCommand.cs | 111 +++++++++++++++++++++++++++ Source/KamekFile.cs | 103 +++++++++++++++++++++---- kamekfile.bt | 21 ++++- 4 files changed, 217 insertions(+), 19 deletions(-) create mode 100644 Source/Commands/WriteRangeCommand.cs diff --git a/Source/Commands/Command.cs b/Source/Commands/Command.cs index ace2d42..655cac2 100644 --- a/Source/Commands/Command.cs +++ b/Source/Commands/Command.cs @@ -29,6 +29,7 @@ public enum Ids : byte CondWrite32 = 36, CondWrite16 = 37, CondWrite8 = 38, + WriteRange = 39, Branch = 64, BranchLink = 65, diff --git a/Source/Commands/WriteRangeCommand.cs b/Source/Commands/WriteRangeCommand.cs new file mode 100644 index 0000000..a4d88d7 --- /dev/null +++ b/Source/Commands/WriteRangeCommand.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Kamek.Commands +{ + class WriteRangeCommand : Command + { + public readonly byte[] Value; + + public WriteRangeCommand(Word address, byte[] value) + : base(Command.Ids.WriteRange, address) + { + Value = value; + } + + public override void WriteArguments(BinaryWriter bw) + { + AssertValue(); + + // Format (after the address): + // - u32 length + // - data, aligned so that you can do efficient 32-bit copies + // - Example: dest. addr 0x80800000 -> no padding between "length" and data blob + // - Example: dest. addr 0x80800003 -> data blob will be prefixed by 3 null pad bytes, + // so you can copy that single byte and then do 32-bit copies from 0x80800004 onward + // - Additional pad bytes, if needed to align the next command to 4 + + bw.WriteBE((uint)Value.Length); + + // Align to 4 (start) + if (Address.Value.Value % 4 == 1) + bw.Write(new byte[] {0}); + else if (Address.Value.Value % 4 == 2) + bw.Write(new byte[] {0, 0}); + else if (Address.Value.Value % 4 == 3) + bw.Write(new byte[] {0, 0, 0}); + + bw.Write(Value); + + // Align to 4 (end) + if ((Address.Value.Value + Value.Length) % 4 == 1) + bw.Write(new byte[] {0, 0, 0}); + else if ((Address.Value.Value + Value.Length) % 4 == 2) + bw.Write(new byte[] {0, 0}); + else if ((Address.Value.Value + Value.Length) % 4 == 3) + bw.Write(new byte[] {0}); + } + + public override IEnumerable PackForRiivolution() + { + Address.Value.AssertAbsolute(); + AssertValue(); + + return Util.PackLargeWriteForRiivolution(Address.Value, Value); + } + + public override IEnumerable PackForDolphin() + { + Address.Value.AssertAbsolute(); + AssertValue(); + + return Util.PackLargeWriteForDolphin(Address.Value, Value); + } + + public override IEnumerable PackGeckoCodes() + { + Address.Value.AssertAbsolute(); + AssertValue(); + + if (Address.Value.Value >= 0x90000000) + throw new NotImplementedException("MEM2 writes not yet supported for gecko"); + + return Util.PackLargeWriteForGeckoCodes(Address.Value, Value); + } + + public override IEnumerable PackActionReplayCodes() + { + Address.Value.AssertAbsolute(); + AssertValue(); + + if (Address.Value.Value >= 0x90000000) + throw new NotImplementedException("MEM2 writes not yet supported for action replay"); + + return Util.PackLargeWriteForActionReplayCodes(Address.Value, Value); + } + + public override void ApplyToCodeFile(CodeFiles.CodeFile file) + { + Address.Value.AssertAbsolute(); + AssertValue(); + + for (uint offs = 0; offs < Value.Length; offs++) + file.WriteByte(Address.Value.Value + offs, Value[offs]); + } + + public override bool Apply(KamekFile file) + { + return false; + } + + private void AssertValue() + { + if (Value.Length == 0) + throw new InvalidOperationException("WriteRangeCommand has no data to write"); + } + } +} diff --git a/Source/KamekFile.cs b/Source/KamekFile.cs index fd1daea..3be1530 100644 --- a/Source/KamekFile.cs +++ b/Source/KamekFile.cs @@ -18,37 +18,76 @@ public static byte[] PackFrom(Linker linker) + public struct InjectedCodeBlob + { + public uint Address; + public byte[] Data; + } + private Word _baseAddress; private byte[] _codeBlob; + private List _injectedCodeBlobs; private long _bssSize; private long _ctorStart; private long _ctorEnd; public Word BaseAddress { get { return _baseAddress; } } public byte[] CodeBlob { get { return _codeBlob; } } + public IReadOnlyList InjectedCodeBlobs { get { return _injectedCodeBlobs; } } #region Result Binary Manipulation + private InjectedCodeBlob? FindInjectedBlobForAddr(Word addr, uint accessSize) + { + if (addr.Type == WordType.AbsoluteAddr) + foreach (var blob in _injectedCodeBlobs) + if (blob.Address <= addr.Value && addr.Value + accessSize <= blob.Address + blob.Data.Length) + return blob; + return null; + } + public ushort ReadUInt16(Word addr) { - return Util.ExtractUInt16(_codeBlob, addr - _baseAddress); + InjectedCodeBlob? blob = FindInjectedBlobForAddr(addr, 2); + if (blob != null) + return Util.ExtractUInt16(blob.Value.Data, addr.Value - blob.Value.Address); + else + return Util.ExtractUInt16(_codeBlob, addr - _baseAddress); } public uint ReadUInt32(Word addr) { - return Util.ExtractUInt32(_codeBlob, addr - _baseAddress); + InjectedCodeBlob? blob = FindInjectedBlobForAddr(addr, 4); + if (blob != null) + return Util.ExtractUInt32(blob.Value.Data, addr.Value - blob.Value.Address); + else + return Util.ExtractUInt32(_codeBlob, addr - _baseAddress); } public void WriteUInt16(Word addr, ushort value) { - Util.InjectUInt16(_codeBlob, addr - _baseAddress, value); + InjectedCodeBlob? blob = FindInjectedBlobForAddr(addr, 2); + if (blob != null) + Util.InjectUInt16(blob.Value.Data, addr.Value - blob.Value.Address, value); + else + Util.InjectUInt16(_codeBlob, addr - _baseAddress, value); } public void WriteUInt32(Word addr, uint value) { - Util.InjectUInt32(_codeBlob, addr - _baseAddress, value); + InjectedCodeBlob? blob = FindInjectedBlobForAddr(addr, 4); + if (blob != null) + Util.InjectUInt32(blob.Value.Data, addr.Value - blob.Value.Address, value); + else + Util.InjectUInt32(_codeBlob, addr - _baseAddress, value); } public bool Contains(Word addr) { + if (addr.Type == WordType.AbsoluteAddr) + foreach (var blob in _injectedCodeBlobs) + if (blob.Address <= addr.Value && addr.Value < blob.Address + blob.Data.Length) + return true; + if (addr.Type != _baseAddress.Type) return false; + return (addr >= _baseAddress && addr < (_baseAddress + _codeBlob.Length)); } @@ -58,7 +97,11 @@ public uint QuerySymbolSize(Word addr) } #endregion - private Dictionary _commands; + private Dictionary _injectionCommands; + private Dictionary _otherCommands; + // Injection commands have to come first, since relocations are applied on top of them + public IEnumerable> _commands { get { return _injectionCommands.Concat(_otherCommands); } } + private List _hooks; private Dictionary _symbolSizes; private AddressMapper _mapper; @@ -74,13 +117,23 @@ public void LoadFromLinker(Linker linker) _codeBlob = new byte[linker.OutputEnd - linker.OutputStart]; Array.Copy(linker.Memory, linker.OutputStart - linker.BaseAddress, _codeBlob, 0, _codeBlob.Length); + _injectedCodeBlobs = new List(); + + foreach (var injection in linker.InjectedSections) + { + byte[] data = new byte[injection.Data.Length]; + Array.Copy(injection.Data, 0, data, 0, injection.Data.Length); + _injectedCodeBlobs.Add(new InjectedCodeBlob { Address=injection.Address, Data=data }); + } + _baseAddress = linker.BaseAddress; _bssSize = linker.BssSize; _ctorStart = linker.CtorStart - linker.OutputStart; _ctorEnd = linker.CtorEnd - linker.OutputStart; _hooks = new List(); - _commands = new Dictionary(); + _injectionCommands = new Dictionary(); + _otherCommands = new Dictionary(); _symbolSizes = new Dictionary(); foreach (var pair in linker.SymbolSizes) @@ -91,6 +144,8 @@ public void LoadFromLinker(Linker linker) foreach (var cmd in linker.Hooks) ApplyHook(cmd); ApplyStaticCommands(); + + AddInjectionsAsCommands(); } @@ -98,12 +153,12 @@ private void AddRelocsAsCommands(IReadOnlyList relocs) { foreach (var rel in relocs) { - if (_commands.ContainsKey(rel.source)) + if (_otherCommands.ContainsKey(rel.source)) throw new InvalidOperationException(string.Format("duplicate commands for address {0}", rel.source)); Commands.Command cmd = new Commands.RelocCommand(rel.source, rel.dest, rel.type); cmd.CalculateAddress(this); cmd.AssertAddressNonNull(); - _commands[rel.source] = cmd; + _otherCommands[rel.source] = cmd; } } @@ -115,9 +170,9 @@ private void ApplyHook(Linker.HookData hookData) { cmd.CalculateAddress(this); cmd.AssertAddressNonNull(); - if (_commands.ContainsKey(cmd.Address.Value)) + if (_otherCommands.ContainsKey(cmd.Address.Value)) throw new InvalidOperationException(string.Format("duplicate commands for address {0}", cmd.Address.Value)); - _commands[cmd.Address.Value] = cmd; + _otherCommands[cmd.Address.Value] = cmd; } _hooks.Add(hook); } @@ -125,20 +180,34 @@ private void ApplyHook(Linker.HookData hookData) private void ApplyStaticCommands() { - // leave _commands containing just the ones we couldn't apply here - var original = _commands; - _commands = new Dictionary(); + // leave _otherCommands containing just the ones we couldn't apply here + var original = _otherCommands; + _otherCommands = new Dictionary(); foreach (var cmd in original.Values) { - if (!cmd.Apply(this)) { + if (!cmd.Apply(this)) + { cmd.AssertAddressNonNull(); - _commands[cmd.Address.Value] = cmd; + _otherCommands[cmd.Address.Value] = cmd; } } } + private void AddInjectionsAsCommands() + { + foreach (var blob in _injectedCodeBlobs) + { + var addr = new Word(WordType.AbsoluteAddr, blob.Address); + Commands.WriteRangeCommand cmd = new Commands.WriteRangeCommand(addr, blob.Data); + cmd.CalculateAddress(this); + cmd.AssertAddressNonNull(); + _injectionCommands[cmd.Address.Value] = cmd; + } + } + + public byte[] Pack() { @@ -146,8 +215,8 @@ public byte[] Pack() { using (var bw = new BinaryWriter(ms)) { - bw.WriteBE((uint)0x4B616D65); // 'Kamek\0\0\2' - bw.WriteBE((uint)0x6B000002); + bw.WriteBE((uint)0x4B616D65); // 'Kamek\0\0\3' + bw.WriteBE((uint)0x6B000003); bw.WriteBE((uint)_bssSize); bw.WriteBE((uint)_codeBlob.Length); bw.WriteBE((uint)_ctorStart); diff --git a/kamekfile.bt b/kamekfile.bt index 86b6365..04fa8f5 100644 --- a/kamekfile.bt +++ b/kamekfile.bt @@ -37,6 +37,8 @@ if (first8 == 0x4b616d656b000001) { // "Kamek", version 1 version = 1; } else if (first8 == 0x4b616d656b000002) { // "Kamek", version 2 version = 2; +} else if (first8 == 0x4b616d656b000003) { // "Kamek", version 3 + version = 3; } else { Warning("Not a Kamekfile."); return; @@ -57,6 +59,7 @@ enum CommandType { kCondWrite32 = 36, kCondWrite16 = 37, kCondWrite8 = 38, + kWriteRange = 39, // added in version >= 3 kBranch = 64, kBranchLink = 65, }; @@ -94,11 +97,15 @@ typedef struct { } } DestAddress ; +string formatDestAddress(int isAbsolute, uint32 addr) { + return Str(isAbsolute ? "0x%x" : "", addr); +} + string readDestAddress(DestAddress &value) { if (value.isAbsolute) { - return Str("0x%x", value.absAddr); + return formatDestAddress(true, value.absAddr); } else { - return Str("", readUint24(value.relAddr)); + return formatDestAddress(false, readUint24(value.relAddr)); } } @@ -205,6 +212,16 @@ typedef struct { valueStr = Str("kmCondWrite8(%s, %s, %s)", readDestAddress(dest), formatHex(original), formatHex(value)); break; + case kWriteRange: + uint32 size ; + local uint32 startAddr = dest.isAbsolute ? dest.absAddr : dest.relAddr; + local uint32 endAddr = startAddr + size; + FSkip(startAddr & 3); + byte data[size] ; + FSkip((4 - endAddr) & 3); + valueStr = Str("kmWriteRange(%s, %s) {...}", + readDestAddress(dest), formatDestAddress(dest.isAbsolute, endAddr - 1)); + break; case kBranch: SrcAddress src ; // intentionally using kAddr32 instead of kRel24 below From 30cbc0a916325f2ced1c89c0c3a20d03dbe98d28 Mon Sep 17 00:00:00 2001 From: RoadrunnerWMC Date: Mon, 16 Mar 2026 07:16:52 -0400 Subject: [PATCH 5/5] Support Kamekfile v3 (WriteRange command) in loader --- loader/kamekLoader.cpp | 21 ++++++++++++++++----- loader/kamekLoader.h | 2 ++ loader/nsmbw.cpp | 17 ++++++++--------- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/loader/kamekLoader.cpp b/loader/kamekLoader.cpp index d25691e..aa5c044 100644 --- a/loader/kamekLoader.cpp +++ b/loader/kamekLoader.cpp @@ -1,6 +1,6 @@ #include "kamekLoader.h" -#define KM_FILE_VERSION 2 +#define KM_FILE_VERSION 3 #define STRINGIFY_(x) #x #define STRINGIFY(x) STRINGIFY_(x) @@ -37,6 +37,7 @@ struct DVDHandle #define kCondWrite32 36 #define kCondWrite16 37 #define kCondWrite8 38 +#define kWriteRange 39 #define kBranch 64 #define kBranchLink 65 @@ -57,9 +58,9 @@ static inline u32 resolveAddress(u32 text, u32 address) { #define kCommandHandler(name) \ - static inline const u8 *kHandle##name(const u8 *input, u32 text, u32 address) + static inline const u8 *kHandle##name(const u8 *input, u32 text, u32 address, const loaderFunctions *funcs) #define kDispatchCommand(name) \ - case k##name: input = kHandle##name(input, text, address); break + case k##name: input = kHandle##name(input, text, address, funcs); break kCommandHandler(Addr32) { u32 target = resolveAddress(text, *(const u32 *)input); @@ -133,13 +134,22 @@ kCommandHandler(CondWrite8) { *(u8 *)address = value & 0xFF; return input + 8; } +kCommandHandler(WriteRange) { + u32 size = *(const u32 *)input; + // skip 1, 2, or 3 bytes to align to 4 + u32 startOfBuffer = (u32)input + 4 + (address & 3); + funcs->memcpy((void *)address, (const void *)(startOfBuffer), (size_t)size); + u32 endOfBuffer = startOfBuffer + size; + // skip 1, 2, or 3 bytes to align to 4 + return (const u8 *)((endOfBuffer + 3) & ~3); +} kCommandHandler(Branch) { *(u32 *)address = 0x48000000; - return kHandleRel24(input, text, address); + return kHandleRel24(input, text, address, funcs); } kCommandHandler(BranchLink) { *(u32 *)address = 0x48000001; - return kHandleRel24(input, text, address); + return kHandleRel24(input, text, address, funcs); } @@ -218,6 +228,7 @@ void loadKamekBinary(const loaderFunctions *funcs, const void *binary, u32 binar kDispatchCommand(CondWrite32); kDispatchCommand(CondWrite16); kDispatchCommand(CondWrite8); + kDispatchCommand(WriteRange); kDispatchCommand(Branch); kDispatchCommand(BranchLink); default: diff --git a/loader/kamekLoader.h b/loader/kamekLoader.h index 2381dfd..32ffe19 100644 --- a/loader/kamekLoader.h +++ b/loader/kamekLoader.h @@ -12,6 +12,7 @@ typedef bool (*DVDFastOpen_t) (int entrynum, DVDHandle *handle); typedef int (*DVDReadPrio_t) (DVDHandle *handle, void *buffer, int length, int offset, int unk); typedef bool (*DVDClose_t) (DVDHandle *handle); typedef int (*sprintf_t) (char *str, const char *format, ...); +typedef void *(*memcpy_t) (void *dest, const void *src, size_t count); typedef void *(*KamekAlloc_t) (u32 size, bool isForCode, const loaderFunctions *funcs); typedef void (*KamekFree_t) (void *buffer, bool isForCode, const loaderFunctions *funcs); @@ -24,6 +25,7 @@ struct loaderFunctions { DVDReadPrio_t DVDReadPrio; DVDClose_t DVDClose; sprintf_t sprintf; + memcpy_t memcpy; KamekAlloc_t kamekAlloc; KamekFree_t kamekFree; }; diff --git a/loader/nsmbw.cpp b/loader/nsmbw.cpp index 24dc8e4..2296328 100644 --- a/loader/nsmbw.cpp +++ b/loader/nsmbw.cpp @@ -7,7 +7,6 @@ typedef void *(*EGG_Heap_alloc_t) (u32 size, s32 align, void *heap); typedef void (*EGG_Heap_free_t) (void *buffer, void *heap); -typedef void *(*memcpy_t) (void *dest, const void *src, size_t count); typedef void (*flush_cache_t) (void *buffer, size_t size); struct loaderFunctionsEx { @@ -48,11 +47,11 @@ const loaderFunctionsEx functions_P = { (DVDReadPrio_t) 0x801CAC60, (DVDClose_t) 0x801CAB40, (sprintf_t) 0x802E1ACC, + (memcpy_t) 0x80004364, allocAdapter, freeAdapter}, (EGG_Heap_alloc_t) 0x802B8E00, (EGG_Heap_free_t) 0x802B90B0, - (memcpy_t) 0x80004364, (flush_cache_t) 0x80004330, (void **) 0x80377F48, (void **) 0x8042A72C, @@ -67,11 +66,11 @@ const loaderFunctionsEx functions_E = { (DVDReadPrio_t) 0x801CAB20, (DVDClose_t) 0x801CAA00, (sprintf_t) 0x802E17DC, + (memcpy_t) 0x80004364, allocAdapter, freeAdapter}, (EGG_Heap_alloc_t) 0x802B8CC0, (EGG_Heap_free_t) 0x802B8F70, - (memcpy_t) 0x80004364, (flush_cache_t) 0x80004330, (void **) 0x80377C48, (void **) 0x8042A44C, @@ -87,11 +86,11 @@ const loaderFunctionsEx functions_J = { (DVDReadPrio_t) 0x801CA930, (DVDClose_t) 0x801CA810, (sprintf_t) 0x802E15EC, + (memcpy_t) 0x80004364, allocAdapter, freeAdapter}, (EGG_Heap_alloc_t) 0x802B8AD0, (EGG_Heap_free_t) 0x802B8D80, - (memcpy_t) 0x80004364, (flush_cache_t) 0x80004330, (void **) 0x803779C8, (void **) 0x8042A16C, @@ -106,11 +105,11 @@ const loaderFunctionsEx functions_K = { (DVDReadPrio_t) 0x801CB060, (DVDClose_t) 0x801CAF40, (sprintf_t) 0x802E1D1C, + (memcpy_t) 0x80004364, allocAdapter, freeAdapter}, (EGG_Heap_alloc_t) 0x802B9200, (EGG_Heap_free_t) 0x802B94B0, - (memcpy_t) 0x80004364, (flush_cache_t) 0x80004330, (void **) 0x80384948, (void **) 0x804370EC, @@ -125,11 +124,11 @@ const loaderFunctionsEx functions_W = { (DVDReadPrio_t) 0x801CB060, (DVDClose_t) 0x801CAF40, (sprintf_t) 0x802E1D1C, + (memcpy_t) 0x80004364, allocAdapter, freeAdapter}, (EGG_Heap_alloc_t) 0x802B9200, (EGG_Heap_free_t) 0x802B94B0, - (memcpy_t) 0x80004364, (flush_cache_t) 0x80004330, (void **) 0x80382D48, (void **) 0x804354EC, @@ -145,11 +144,11 @@ const loaderFunctionsEx functions_C = { (DVDReadPrio_t) 0x801CCE80, (DVDClose_t) 0x801CCD60, (sprintf_t) 0x802E4DF8, + (memcpy_t) 0x80004364, allocAdapter, freeAdapter}, (EGG_Heap_alloc_t) 0x802BB360, (EGG_Heap_free_t) 0x802BB610, - (memcpy_t) 0x80004364, (flush_cache_t) 0x80004330, (void **) 0x8037D4C8, (void **) 0x8042FCCC, @@ -250,7 +249,7 @@ void loadIntoNSMBW() { // modify myBackGround_PhaseMethod to load rels earlier & load the kamek binary u32 temp[20]; - sFuncs->memcpy(&temp, sFuncs->myBackGround_PhaseMethod, 0x50); + sFuncs->base.memcpy(&temp, sFuncs->myBackGround_PhaseMethod, 0x50); // set rel loading functions as the first entries in the table sFuncs->myBackGround_PhaseMethod[0] = temp[15]; @@ -261,7 +260,7 @@ void loadIntoNSMBW() { sFuncs->myBackGround_PhaseMethod[3] = (u32)&loadBinary; // set all the other functions - sFuncs->memcpy(&sFuncs->myBackGround_PhaseMethod[4], &temp, 0x3C); + sFuncs->base.memcpy(&sFuncs->myBackGround_PhaseMethod[4], &temp, 0x3C); sFuncs->myBackGround_PhaseMethod[19] = temp[18]; sFuncs->myBackGround_PhaseMethod[20] = temp[19];