Skip to content

Instantly share code, notes, and snippets.

@CyberShadow
Last active October 14, 2020 12:47
Show Gist options
  • Save CyberShadow/02da7df8322400840f8de83d95d6f2ed to your computer and use it in GitHub Desktop.
Save CyberShadow/02da7df8322400840f8de83d95d6f2ed to your computer and use it in GitHub Desktop.
Leged of Zelda Twilight Princess inventory hotkey macro / Gecko code
/ar_tpkey
/gen_profile
/gecko2ar
module ar_gen;
import std.bitmanip;
import std.typecons;
void instr(uint addr, uint data)
{
import std.stdio;
writefln("%08X %08X", addr, data);
}
void zeroCode(uint data) { instr(0, data); }
enum ZeroCode : ubyte { end = 0x00, norm = 0x02, row = 0x03, area = 0x04 }
void zeroCode(ZeroCode kind, uint data)
{
assert((data & ~((1 << 29) - 1)) == 0, "Zero code data too large");
zeroCode((kind << 29) | data);
}
enum DataType : ubyte
{
dt8bit = 0x00,
dt16bit = 0x01,
dt32bit = 0x02,
dt32float = 0x03,
}
template dataTypeOf(T)
{
static if (is(T == ubyte))
enum dataTypeOf = DataType.dt8bit;
else
static if (is(T == ushort))
enum dataTypeOf = DataType.dt16bit;
else
static if (is(T == uint))
enum dataTypeOf = DataType.dt32bit;
else
static if (is(T == float))
enum dataTypeOf = DataType.dt32float;
else
static assert(false, "Bad type: " ~ T.stringof);
}
enum ConditionalType : ubyte
{
normal = 0x00,
equal = 0x01,
notEqual = 0x02,
lessThanSigned = 0x03,
greaterThanSigned = 0x04,
lessThanUnsigned = 0x05,
greaterThanUnsigned = 0x06,
and = 0x07, // bitwise AND
}
enum NormalSubType : ubyte
{
ramWrite = 0x00,
writePointr = 0x01,
addCode = 0x02,
masterCode = 0x03,
}
enum ConditionSkip : ubyte
{
oneLine = 0x00,
twoLines = 0x01,
allLinesUntil = 0x02,
allLines = 0x03,
}
uint mkaddr(uint gcAddr, DataType size, ubyte type = 0, ubyte subtype = 0)
{
static union Address
{
uint address;
struct
{
mixin(bitfields!(
uint, "gcAddr" , 25,
uint, "size" , 2,
uint, "type" , 3,
uint, "subtype" , 2,
));
}
}
Address address;
address.gcAddr = gcAddr & 0x01FFFFFF;
address.size = size;
address.type = type;
address.subtype = subtype;
return address.address;
}
/// Write value to address.
void put(DataType type, uint addr, uint value) { instr(mkaddr(addr, type, 0, 0), value); }
void put(T)(uint addr, uint value) { put(dataTypeOf!T, addr, value); }
/// Add to value in memory.
void add(DataType type, uint addr, uint value) { instr(mkaddr(addr, type, ConditionalType.normal, NormalSubType.addCode), value); }
void add(T)(uint addr, uint value) { add(dataTypeOf!T, addr, value); }
/// Copy bytes from srcAddr to dstAddr.
/// If dereference is true, the addresses are pointers which indicate
/// the source and target.
void copy(uint srcAddr, uint dstAddr, ushort numBytes, Flag!"dereference" dereference)
{
zeroCode(ZeroCode.area, mkaddr(dstAddr, cast(DataType)3, 0, 0));
instr(srcAddr, numBytes | (dereference << 24));
}
void copy(T)(uint srcAddr, uint dstAddr, Flag!"dereference" dereference) { copy(srcAddr, dstAddr, T.sizeof, dereference); }
/// Ensure address points to real GC address space.
uint gcAddr(uint addr) { return addr | 0x80000000; }
void conditional(DataType type, uint address, ConditionalType cond, uint value, ConditionSkip action) { instr(mkaddr(address, type, cond, action), value); }
void conditional(T)(uint address, ConditionalType cond, uint value, ConditionSkip action) { conditional(dataTypeOf!T, address, cond, value, action); }
module ar_tpkey; // Twilight Princess instant key selection
import std.typecons;
import ar_gen;
void main()
{
enum GameAddress
{
key1 = 0x4343FC,
key2 = 0x4343FD,
globals = 0x4061C0,
equipment = globals + 0x5E6C,
xItem = equipment + 0,
yItem = equipment + 1,
inventory = globals + 0x9C,
equipSlotNumber = globals + 0xB,
comboSlotNumber = globals + 0xF,
temp1 = 0x1844,
temp2 = temp1 + 4,
temp3 = temp2 + 4,
}
enum Key2 : ubyte
{
Z = 0x10,
RT = 0x20,
}
enum maxItems = 24;
// Exit if Z isn't pressed
conditional!ubyte(GameAddress.key2, ConditionalType.and, Key2.Z, ConditionSkip.allLines);
// Filter out out-of-bounds values (>24)
conditional!ubyte(GameAddress.key1, ConditionalType.lessThanUnsigned, maxItems, ConditionSkip.allLines);
// Prepare source address
put!uint(GameAddress.temp1, 0);
copy!ubyte(GameAddress.key1, GameAddress.temp1+3, No.dereference);
add!uint(GameAddress.temp1, GameAddress.inventory.gcAddr);
// Prepare target address
put!uint(GameAddress.temp2, GameAddress.xItem.gcAddr);
conditional!ubyte(GameAddress.key2, ConditionalType.and, 0x20, ConditionSkip.oneLine); // Select to Y if RT is pressed
add!uint(GameAddress.temp2, 1);
copy!ubyte(GameAddress.temp1, GameAddress.temp2, Yes.dereference);
// Copy the slot number, too
put!uint(GameAddress.temp1, GameAddress.key1.gcAddr);
put!uint(GameAddress.temp2, GameAddress.equipSlotNumber.gcAddr);
conditional!ubyte(GameAddress.key2, ConditionalType.and, 0x20, ConditionSkip.oneLine); // Select to Y if RT is pressed
add!uint(GameAddress.temp2, 1);
copy!ubyte(GameAddress.temp1, GameAddress.temp2, Yes.dereference);
// Clear the combo slot number
put!ubyte(GameAddress.temp3, 0xFF);
put!uint(GameAddress.temp1, GameAddress.temp3.gcAddr);
put!uint(GameAddress.temp2, GameAddress.comboSlotNumber.gcAddr);
conditional!ubyte(GameAddress.key2, ConditionalType.and, 0x20, ConditionSkip.oneLine); // Select to Y if RT is pressed
add!uint(GameAddress.temp2, 1);
copy!ubyte(GameAddress.temp1, GameAddress.temp2, Yes.dereference);
}
import core.bitop;
import std.algorithm.iteration;
import std.array;
import std.bitmanip;
import std.exception;
import std.format;
import std.math;
import std.stdio;
static import ar_gen;
enum GeckoCommand : ubyte
{
write32 = 0x04,
insertAsm = 0xC2,
}
enum baseAddress = 0x80002000; // needs to be different for every code...
void main()
{
uint[2][] lines;
while (!stdin.eof)
{
uint[2] line;
if (readf("%x %x\n", line[0], line[1]) == 2)
lines ~= line;
}
auto numTotalLines = lines.length;
uint currentAddress = baseAddress;
while (lines.length)
{
scope(failure) stderr.writefln("Error at line %d:", numTotalLines - lines.length + 1);
auto cmd = lines[0][0] >> 24;
switch (cmd)
{
case GeckoCommand.write32:
{
// Coincidentally these have the same syntax
auto address = (lines[0][0] & 0xFFFFFF) | 0x80000000;
ar_gen.put!uint(address, lines[0][1]);
lines = lines[1..$];
break;
}
case GeckoCommand.insertAsm:
{
static void comeFrom(uint source, uint target)
{
int delta = target - source;
enforce(abs(delta) < ((1 << (26 - 1)) - 1), "Source and target are too far");
ar_gen.put!uint(source, /*bswap*/(0x48000000 | (delta & ((1 << 26) - 1))));
}
auto address = (lines[0][0] & 0xFFFFFF) | 0x80000000;
comeFrom(address, currentAddress);
auto numLines = lines[0][1];
enforce(numLines && 1 + numLines <= lines.length, "Invalid line count for insertAsm command");
auto code = lines[1 .. 1+numLines].map!((ref s) => s[]).join();
foreach (uint word; code[0..$-1])
{
ar_gen.put!uint(currentAddress, word);
currentAddress += 4;
}
enforce(code[$-1] == 0, "Last insertAsm word is non-zero");
comeFrom(currentAddress, address + 4);
currentAddress += 4;
lines = lines[1+numLines..$];
break;
}
default:
throw new Exception("Unknown Gecko command: %02X".format(cmd));
}
}
}
import std.array;
import std.stdio;
immutable string[] bitButtons =
[
"Buttons/A",
"Buttons/B",
"Buttons/X",
"Buttons/Y",
"Buttons/Start",
];
enum selectButton = "Buttons/Z";
enum shiftButton = "Triggers/R";
enum numKeys = 24;
version (CyberShadow)
version = Dvorak;
version (Dvorak)
{
string[numKeys] keys =
[
"`1`",
"`2`",
"`3`",
"`4`",
"`5`",
"`6`",
"`7`",
"`8`",
"`9`",
"`0`",
"bracketleft",
"bracketright",
"apostrophe",
"comma",
"period",
"P",
"Y",
"F",
"G",
"C",
"R",
"L",
"minus",
"equal",
];
}
else
{
string[numKeys] keys =
[
"`1`",
"`2`",
"`3`",
"`4`",
"`5`",
"`6`",
"`7`",
"`8`",
"`9`",
"`0`",
"minus",
"equal",
"Q",
"W",
"E",
"R",
"T",
"Y",
"U",
"I",
"O",
"P",
"apostrophe",
"bracketright",
];
}
enum shiftKey = `Shift_R`;
enum deviceName = `XInput2/0/Virtual core pointer`;
void main()
{
writeln("[Profile]");
writeln("Device = ", deviceName);
writeln(shiftButton, " = ", shiftKey);
string[][bitButtons.length] buttonKeys;
foreach (n; 0..numKeys)
foreach (bit; 0..bitButtons.length)
if (n & (1 << bit))
buttonKeys[bit] ~= keys[n];
foreach (idx, name; bitButtons)
writeln(name, " = ", buttonKeys[idx].join(" | "));
writeln(selectButton, " = ", keys[].join(" | "));
}

Items

  • 00 - ? Rupees (black)
  • 01 - 1 Rupee (green)
  • 02 - 5 Rupees (blue)
  • 06 - 100 Rupees (orange)
  • 07 - 200 Rupees (silver)
  • 10 - ? arrows
  • 20 - Small Key
  • 30 - Magic Armor
  • 3E - Hawkeye
  • 40 - Gale Boomerang
  • 41 - Spinner
  • 42 - Ball and Chain
  • 43 - Hero’s Bow
  • 45 - Iron Boots
  • 48 - Lantern
  • 4B - Slingshot
  • 59 - Bow + Bomb combo (A1XXXX value)
  • 5A - Bow + Hawkeye combo (A1XXXX value)
  • 64 - Milk
  • 6C - Fairy
  • 70 - Bombs
  • FF - nothing?

My inventory

  1. 40 - Gale Boomerang
  2. 48 - Lantern
  3. FF
  4. 45 - Iron Boots

Addresses

  • 40C02C - X item (also A1AE5A?)
  • 40C02D - Y item (also A1AE5C?)
  • 40625C - start of array of inventory items (24 items total?)
  • 4343FC, 434452, 44C7F8 - gamecube 2 input
    • 0x0001 - A
    • 0x0002 - B
    • 0x0004 - X
    • 0x0008 - Y
    • 0x0010 - Start
    • 0x0100 - D-Pad Left
    • 0x0200 - D-Pad Right
    • 0x0400 - D-Pad Down
    • 0x0800 - D-Pad Up
    • 0x1000 - Z
    • 0x2000 - R trigger
    • 0x4000 - L trigger

  • Rolled version:
    1. If Z isn’t pressed (4343FD is not 0x10), abort program
    2. Copy 32-bit 0 to temporary address
    3. Copy controller state byte (4343FC) to temporary address
    4. Add inventory address to 32-bit variable at temporary address
    5. (need indirect read, no such AR feature / need to use broken memory-copy-with-pointers)
  • Unrolled version:
    1. If Z isn’t pressed (4343FD is not 0x10), abort program
    2. For each key (loop for X in 0 to 24):
      1. If controller state byte (4343FC) has value X:
        1. Copy byte from 40625C+X to 40C02C
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment