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/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)); } } } 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/Source/Linker.cs b/Source/Linker.cs index 3746f8d..b0a154a 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; } } @@ -74,8 +73,109 @@ 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(); private void ImportSections(ref List blobs, ref Word location, string prefix) { @@ -104,6 +204,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 +253,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 +261,9 @@ 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(); + ImportInjectedSections(); } #endregion @@ -194,6 +282,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 +292,7 @@ private void BuildSymbolTables() { _globalSymbols = new Dictionary(); _localSymbols = new Dictionary>(); + _hookSymbols = new Dictionary, uint>(); _symbolTableContents = new Dictionary(); _symbolSizes = new Dictionary(); @@ -292,9 +382,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 +524,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 +549,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 +574,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 }); } } } 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 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 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];