Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Source/Commands/Command.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public enum Ids : byte
CondWrite32 = 36,
CondWrite16 = 37,
CondWrite8 = 38,
WriteRange = 39,

Branch = 64,
BranchLink = 65,
Expand Down
111 changes: 111 additions & 0 deletions Source/Commands/WriteRangeCommand.cs
Original file line number Diff line number Diff line change
@@ -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<string> PackForRiivolution()
{
Address.Value.AssertAbsolute();
AssertValue();

return Util.PackLargeWriteForRiivolution(Address.Value, Value);
}

public override IEnumerable<string> PackForDolphin()
{
Address.Value.AssertAbsolute();
AssertValue();

return Util.PackLargeWriteForDolphin(Address.Value, Value);
}

public override IEnumerable<ulong> 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<ulong> 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");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace Kamek.Commands
{
class WriteCommand : Command
class WriteWordCommand : Command
{
public enum Type
{
Expand Down Expand Up @@ -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;
Expand Down
8 changes: 4 additions & 4 deletions Source/Hooks/WriteHook.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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));
}
}
}
103 changes: 86 additions & 17 deletions Source/KamekFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<InjectedCodeBlob> _injectedCodeBlobs;
private long _bssSize;
private long _ctorStart;
private long _ctorEnd;

public Word BaseAddress { get { return _baseAddress; } }
public byte[] CodeBlob { get { return _codeBlob; } }
public IReadOnlyList<InjectedCodeBlob> 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));
}

Expand All @@ -58,7 +97,11 @@ public uint QuerySymbolSize(Word addr)
}
#endregion

private Dictionary<Word, Commands.Command> _commands;
private Dictionary<Word, Commands.Command> _injectionCommands;
private Dictionary<Word, Commands.Command> _otherCommands;
// Injection commands have to come first, since relocations are applied on top of them
public IEnumerable<KeyValuePair<Word, Commands.Command>> _commands { get { return _injectionCommands.Concat(_otherCommands); } }

private List<Hooks.Hook> _hooks;
private Dictionary<Word, uint> _symbolSizes;
private AddressMapper _mapper;
Expand All @@ -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<InjectedCodeBlob>();

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<Hooks.Hook>();
_commands = new Dictionary<Word, Commands.Command>();
_injectionCommands = new Dictionary<Word, Commands.Command>();
_otherCommands = new Dictionary<Word, Commands.Command>();

_symbolSizes = new Dictionary<Word, uint>();
foreach (var pair in linker.SymbolSizes)
Expand All @@ -91,19 +144,21 @@ public void LoadFromLinker(Linker linker)
foreach (var cmd in linker.Hooks)
ApplyHook(cmd);
ApplyStaticCommands();

AddInjectionsAsCommands();
}


private void AddRelocsAsCommands(IReadOnlyList<Linker.Fixup> 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;
}
}

Expand All @@ -115,39 +170,53 @@ 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);
}


private void ApplyStaticCommands()
{
// leave _commands containing just the ones we couldn't apply here
var original = _commands;
_commands = new Dictionary<Word, Commands.Command>();
// leave _otherCommands containing just the ones we couldn't apply here
var original = _otherCommands;
_otherCommands = new Dictionary<Word, Commands.Command>();

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()
{
using (var ms = new MemoryStream())
{
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);
Expand Down
Loading