diff --git a/Source/Commands/BranchCommand.cs b/Source/Commands/BranchCommand.cs index 0ac6789..4ed765a 100644 --- a/Source/Commands/BranchCommand.cs +++ b/Source/Commands/BranchCommand.cs @@ -10,61 +10,64 @@ namespace Kamek.Commands class BranchCommand : Command { public readonly Word Target; + public readonly Word? Original; - public BranchCommand(Word source, Word target, bool isLink) - : base(isLink ? Ids.BranchLink : Ids.Branch, source) + private static Ids IdFromFlags(bool isLink, bool isConditional) + { + if (isConditional) + return isLink ? Ids.CondBranchLink : Ids.CondBranch; + else + return isLink ? Ids.BranchLink : Ids.Branch; + + throw new NotImplementedException(); + } + + public BranchCommand(Word source, Word target, Word? original, bool isLink) + : base(IdFromFlags(isLink, original.HasValue), source) { Target = target; + Original = original; } public override void WriteArguments(BinaryWriter bw) { Target.AssertNotAmbiguous(); bw.WriteBE(Target.Value); + + if (Original.HasValue) + { + Original.Value.AssertNotRelative(); + bw.WriteBE(Original.Value.Value); + } } public override IEnumerable PackForRiivolution() { - Address.Value.AssertAbsolute(); - Target.AssertAbsolute(); - - return new string[] { string.Format("", Address.Value.Value, GenerateInstruction()) }; + return GenerateWriteCommand().PackForRiivolution(); } public override IEnumerable PackForDolphin() { - Address.Value.AssertAbsolute(); - Target.AssertAbsolute(); - - return new string[] { string.Format("0x{0:X8}:dword:0x{1:X8}", Address.Value.Value, GenerateInstruction()) }; + return GenerateWriteCommand().PackForDolphin(); } public override IEnumerable PackGeckoCodes() { - Address.Value.AssertAbsolute(); - Target.AssertAbsolute(); - - ulong code = ((ulong)(Address.Value.Value & 0x1FFFFFF) << 32) | GenerateInstruction(); - code |= 0x4000000UL << 32; - - return new ulong[1] { code }; + return GenerateWriteCommand().PackGeckoCodes(); } public override IEnumerable PackActionReplayCodes() { - Address.Value.AssertAbsolute(); - Target.AssertAbsolute(); - - ulong code = ((ulong)(Address.Value.Value & 0x1FFFFFF) << 32) | GenerateInstruction(); - code |= 0x4000000UL << 32; - - return new ulong[1] { code }; + return GenerateWriteCommand().PackActionReplayCodes(); } public override bool Apply(KamekFile file) { if (file.Contains(Address.Value) && Address.Value.Type == Target.Type) { + if (Original.HasValue && file.ReadUInt32(Address.Value) != Original.Value.Value) + return true; + file.WriteUInt32(Address.Value, GenerateInstruction()); return true; } @@ -74,19 +77,31 @@ public override bool Apply(KamekFile file) public override void ApplyToCodeFile(CodeFiles.CodeFile file) { - Address.Value.AssertAbsolute(); - Target.AssertAbsolute(); - - file.WriteUInt32(Address.Value.Value, GenerateInstruction()); + GenerateWriteCommand().ApplyToCodeFile(file); } + private WriteCommand GenerateWriteCommand() + { + Target.AssertAbsolute(); + return new WriteCommand( + Address.Value, + new Word(WordType.Value, GenerateInstruction()), + WriteCommand.Type.Value32, + Original); + } + private uint GenerateInstruction() { long delta = Target - Address.Value; - uint insn = (Id == Ids.BranchLink) ? 0x48000001U : 0x48000000U; + uint insn = IsLink() ? 0x48000001U : 0x48000000U; insn |= ((uint)delta & 0x3FFFFFC); return insn; } + + private bool IsLink() + { + return Id == Ids.BranchLink || Id == Ids.CondBranchLink; + } } } diff --git a/Source/Commands/Command.cs b/Source/Commands/Command.cs index ace2d42..aefa91f 100644 --- a/Source/Commands/Command.cs +++ b/Source/Commands/Command.cs @@ -32,6 +32,8 @@ public enum Ids : byte Branch = 64, BranchLink = 65, + CondBranch = 66, + CondBranchLink = 67, } public readonly Ids Id; diff --git a/Source/Hooks/BranchHook.cs b/Source/Hooks/BranchHook.cs index 64f3d59..5f880c0 100644 --- a/Source/Hooks/BranchHook.cs +++ b/Source/Hooks/BranchHook.cs @@ -9,18 +9,20 @@ namespace Kamek.Hooks { class BranchHook : Hook { - public BranchHook(bool isLink, Word[] args, AddressMapper mapper) + public BranchHook(bool isLink, bool isConditional, Word[] args, AddressMapper mapper) { - if (args.Length != 2) + if (args.Length != (isConditional ? 3 : 2)) throw new InvalidDataException("wrong arg count for BranchCommand"); // expected args: - // source : pointer to game code - // dest : pointer to game code or to Kamek code + // source : pointer to game code + // dest : pointer to game code or to Kamek code + // original : value (an encoded PPC instruction, probably) var source = GetAbsoluteArg(args[0], mapper); var dest = GetAnyPointerArg(args[1], mapper); + Word? original = isConditional ? GetValueArg(args[2]) : null; - Commands.Add(new Commands.BranchCommand(source, dest, isLink)); + Commands.Add(new Commands.BranchCommand(source, dest, original, isLink)); } } } diff --git a/Source/Hooks/Hook.cs b/Source/Hooks/Hook.cs index b13d28c..b6ee5dd 100644 --- a/Source/Hooks/Hook.cs +++ b/Source/Hooks/Hook.cs @@ -12,7 +12,9 @@ public enum HookType : uint { kctConditionalWrite = 2, kctInjectBranch = 3, kctInjectCall = 4, - kctPatchExit = 5 + kctPatchExit = 5, + kctConditionalInjectBranch = 6, + kctConditionalInjectCall = 7 } abstract class Hook @@ -26,9 +28,13 @@ public static Hook Create(Linker.HookData data, AddressMapper mapper) case (uint)HookType.kctConditionalWrite: return new WriteHook(true, data.args, mapper); case (uint)HookType.kctInjectBranch: - return new BranchHook(false, data.args, mapper); + return new BranchHook(false, false, data.args, mapper); case (uint)HookType.kctInjectCall: - return new BranchHook(true, data.args, mapper); + return new BranchHook(true, false, data.args, mapper); + case (uint)HookType.kctConditionalInjectBranch: + return new BranchHook(false, true, data.args, mapper); + case (uint)HookType.kctConditionalInjectCall: + return new BranchHook(true, true, data.args, mapper); case (uint)HookType.kctPatchExit: return new PatchExitHook(data.args, mapper); default: diff --git a/Source/KamekFile.cs b/Source/KamekFile.cs index fd1daea..22c744a 100644 --- a/Source/KamekFile.cs +++ b/Source/KamekFile.cs @@ -146,8 +146,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/k_stdlib/kamek.h b/k_stdlib/kamek.h index 618eef7..3c6ed96 100644 --- a/k_stdlib/kamek.h +++ b/k_stdlib/kamek.h @@ -20,6 +20,8 @@ #define kctInjectBranch 3 #define kctInjectCall 4 #define kctPatchExit 5 +#define kctInjectConditionalBranch 6 +#define kctInjectConditionalCall 7 #define kmIdentifier(key, counter) \ @@ -80,6 +82,11 @@ struct _kmHook_4ui_2f_t { unsigned int a; unsigned int b; unsigned int c; unsign #define kmBranch(addr, ptr) kmHook2(kctInjectBranch, (addr), (ptr)) #define kmCall(addr, ptr) kmHook2(kctInjectCall, (addr), (ptr)) +// kmCondBranch, kmCondCall +// Set up a branch from a specific instruction to a specific address, conditionally +#define kmCondBranch(addr, original, ptr) kmHook3(kctInjectConditionalBranch, (addr), (ptr), (original)) +#define kmCondCall(addr, original, ptr) kmHook3(kctInjectConditionalCall, (addr), (ptr), (original)) + // kmBranchDefCpp, kmBranchDefAsm // Set up a branch (b) from a specific instruction to a function defined // directly underneath. If exitPoint is not NULL, the function will diff --git a/k_stdlib/kamek_asm.S b/k_stdlib/kamek_asm.S index 8c0452b..32d63b7 100755 --- a/k_stdlib/kamek_asm.S +++ b/k_stdlib/kamek_asm.S @@ -3,6 +3,8 @@ kctConditionalWrite .equ 2 kctInjectBranch .equ 3 kctInjectCall .equ 4 kctPatchExit .equ 5 +kctInjectConditionalBranch .equ 6 +kctInjectConditionalCall .equ 7 // general hook definition macros kmHook0: .macro type @@ -116,6 +118,15 @@ kmCall: .macro addr, ptr kmHook2 kctInjectCall, addr, ptr .endm +// kmCondBranch, kmCondCall +// Set up a branch from a specific instruction to a specific address, conditionally +kmCondBranch: .macro addr, original, ptr + kmHook3 kctInjectConditionalBranch, addr, ptr, original + .endm +kmCondCall: .macro addr, original, ptr + kmHook3 kctInjectConditionalCall, addr, ptr, original + .endm + // kmBranchDef // Set up a branch (b) from a specific instruction to a function defined // directly underneath. diff --git a/kamekfile.bt b/kamekfile.bt index 86b6365..f499687 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; @@ -59,6 +61,8 @@ enum CommandType { kCondWrite8 = 38, kBranch = 64, kBranchLink = 65, + kCondBranch = 66, // added in version >= 3 + kCondBranchLink = 67, // added in version >= 3 }; @@ -217,6 +221,20 @@ typedef struct { valueStr = Str("kmCall(%s, %s)", readDestAddress(dest), readSrcAddress(src, kAddr32)); break; + case kCondBranch: + SrcAddress src ; + uint32 original ; + // intentionally using kAddr32 instead of kRel24 below + valueStr = Str("kmCondBranch(%s, %s, %s)", + readDestAddress(dest), formatHex(original), readSrcAddress(src, kAddr32)); + break; + case kCondBranchLink: + SrcAddress src ; + uint32 original ; + // intentionally using kAddr32 instead of kRel24 below + valueStr = Str("kmCondCall(%s, %s, %s)", + readDestAddress(dest), formatHex(original), readSrcAddress(src, kAddr32)); + break; default: break; } diff --git a/loader/kamekLoader.cpp b/loader/kamekLoader.cpp index d25691e..dd9fa5d 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) @@ -39,6 +39,8 @@ struct DVDHandle #define kCondWrite8 38 #define kBranch 64 #define kBranchLink 65 +#define kCondBranch 66 +#define kCondBranchLink 67 void kamekError(const loaderFunctions *funcs, const char *str) @@ -141,6 +143,18 @@ kCommandHandler(BranchLink) { *(u32 *)address = 0x48000001; return kHandleRel24(input, text, address); } +kCommandHandler(CondBranch) { + u32 original = ((const u32 *)input)[1]; + if (*(u32 *)address == original) + kHandleBranch(input, text, address); + return input + 8; +} +kCommandHandler(CondBranchLink) { + u32 original = ((const u32 *)input)[1]; + if (*(u32 *)address == original) + kHandleBranchLink(input, text, address); + return input + 8; +} inline void cacheInvalidateAddress(u32 address) { @@ -220,6 +234,8 @@ void loadKamekBinary(const loaderFunctions *funcs, const void *binary, u32 binar kDispatchCommand(CondWrite8); kDispatchCommand(Branch); kDispatchCommand(BranchLink); + kDispatchCommand(CondBranch); + kDispatchCommand(CondBranchLink); default: funcs->OSReport("Unknown command: %d\n", cmd); }