Add extensions to dmnt cheat virtual machine (#2479)

* dmnt_extension

* update type 8 extension

* clearify that bit 27 does not correspond to a button

* update cheat.md with new code type 0xC4

* implement code type 0xC4

* Add type 1 extension

* remove C0Tcr6Ma aaaaaaaa VVVVVVVV (VVVVVVVV)

* Type 9 extension for floating point math

* updated according to review
This commit is contained in:
tomvita 2025-01-10 10:38:59 +08:00 committed by GitHub
parent 9f8d17b9e6
commit 4e99a5e08d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 171 additions and 10 deletions

View File

@ -49,7 +49,7 @@ Code type 0x0 allows writing a static value to a memory address.
`0TMR00AA AAAAAAAA VVVVVVVV (VVVVVVVV)` `0TMR00AA AAAAAAAA VVVVVVVV (VVVVVVVV)`
+ T: Width of memory write (1, 2, 4, or 8 bytes). + T: Width of memory write (1, 2, 4, or 8 bytes).
+ M: Memory region to write to (0 = Main NSO, 1 = Heap, 2 = Alias, 3 = Aslr). + M: Memory region to write to (0 = Main NSO, 1 = Heap, 2 = Alias, 3 = Aslr, 4 = non-relative).
+ R: Register to use as an offset from memory region base. + R: Register to use as an offset from memory region base.
+ A: Immediate offset to use from memory region base. + A: Immediate offset to use from memory region base.
+ V: Value to write. + V: Value to write.
@ -62,11 +62,13 @@ Code type 0x1 performs a comparison of the contents of memory to a static value.
If the condition is not met, all instructions until the appropriate End or Else conditional block terminator are skipped. If the condition is not met, all instructions until the appropriate End or Else conditional block terminator are skipped.
#### Encoding #### Encoding
`1TMC00AA AAAAAAAA VVVVVVVV (VVVVVVVV)` `1TMCXrAA AAAAAAAA VVVVVVVV (VVVVVVVV)`
+ T: Width of memory write (1, 2, 4, or 8 bytes). + T: Width of memory read (1, 2, 4, or 8 bytes).
+ M: Memory region to write to (0 = Main NSO, 1 = Heap, 2 = Alias, 3 = Aslr). + M: Memory region to read from (0 = Main NSO, 1 = Heap, 2 = Alias, 3 = Aslr, 4 = non-relative).
+ C: Condition to use, see below. + C: Condition to use, see below.
+ X: Operand Type, see below.
+ r: Offset Register (operand types 1).
+ A: Immediate offset to use from memory region base. + A: Immediate offset to use from memory region base.
+ V: Value to compare to. + V: Value to compare to.
@ -78,6 +80,9 @@ If the condition is not met, all instructions until the appropriate End or Else
+ 5: == + 5: ==
+ 6: != + 6: !=
#### Operand Type
+ 0: Memory Base + Relative Offset
+ 1: Memory Base + Offset Register + Relative Offset
--- ---
### Code Type 0x2: End Conditional Block ### Code Type 0x2: End Conditional Block
@ -126,7 +131,7 @@ Code type 0x5 allows loading a value from memory into a register, either using a
`5TMR00AA AAAAAAAA` `5TMR00AA AAAAAAAA`
+ T: Width of memory read (1, 2, 4, or 8 bytes). + T: Width of memory read (1, 2, 4, or 8 bytes).
+ M: Memory region to write to (0 = Main NSO, 1 = Heap, 2 = Alias, 3 = Aslr). + M: Memory region to write to (0 = Main NSO, 1 = Heap, 2 = Alias, 3 = Aslr, 4 = non-relative).
+ R: Register to load value into. + R: Register to load value into.
+ A: Immediate offset to use from memory region base. + A: Immediate offset to use from memory region base.
@ -137,6 +142,22 @@ Code type 0x5 allows loading a value from memory into a register, either using a
+ R: Register to load value into. (This register is also used as the base memory address). + R: Register to load value into. (This register is also used as the base memory address).
+ A: Immediate offset to use from register R. + A: Immediate offset to use from register R.
#### Load from Register Address Encoding
`5T0R2SAA AAAAAAAA`
+ T: Width of memory read (1, 2, 4, or 8 bytes).
+ R: Register to load value into.
+ S: Register to use as the base memory address.
+ A: Immediate offset to use from register R.
#### Load From Fixed Address Encoding with offset register
`5TMR3SAA AAAAAAAA`
+ T: Width of memory read (1, 2, 4, or 8 bytes).
+ M: Memory region to write to (0 = Main NSO, 1 = Heap, 2 = Alias, 3 = Aslr, 4 = non-relative).
+ R: Register to load value into.
+ S: Register to use as offset register.
+ A: Immediate offset to use from memory region base.
--- ---
### Code Type 0x6: Store Static Value to Register Memory Address ### Code Type 0x6: Store Static Value to Register Memory Address
@ -250,7 +271,10 @@ Code type 0x9 allows performing arithmetic on registers.
+ 7: Logical Not (discards right-hand operand) + 7: Logical Not (discards right-hand operand)
+ 8: Logical Xor + 8: Logical Xor
+ 9: None/Move (discards right-hand operand) + 9: None/Move (discards right-hand operand)
+ 10: Float Addition, T==4 single T==8 double
+ 11: Float Subtraction, T==4 single T==8 double
+ 12: Float Multiplication, T==4 single T==8 double
+ 13: Float Division, T==4 single T==8 double
--- ---
### Code Type 0xA: Store Register to Memory Address ### Code Type 0xA: Store Register to Memory Address
@ -380,6 +404,61 @@ Code type 0xC3 reads or writes a static register with a given register.
--- ---
### Code Type 0xC4: Begin Extended Keypress Conditional Block
Code type 0xC4 enters or skips a conditional block based on whether a key combination is pressed.
#### Encoding
`C4r00000 kkkkkkkk kkkkkkkk`
+ r: Auto-repeat, see below.
+ kkkkkkkkkk: Keypad mask to check against output of `hidKeysDown()`.
Note that for multiple button combinations, the bitmasks should be OR'd together.
#### Auto-repeat
+ 0: The conditional block executes only once when the keypad mask matches. The mask must stop matching to reset for the next trigger.
+ 1: The conditional block executes as long as the keypad mask matches.
#### Keypad Values
Note: This is the direct output of `hidKeysDown()`.
+ 000000001: A
+ 000000002: B
+ 000000004: X
+ 000000008: Y
+ 000000010: Left Stick Pressed
+ 000000020: Right Stick Pressed
+ 000000040: L
+ 000000080: R
+ 000000100: ZL
+ 000000200: ZR
+ 000000400: Plus
+ 000000800: Minus
+ 000001000: Left
+ 000002000: Up
+ 000004000: Right
+ 000008000: Down
+ 000010000: Left Stick Left
+ 000020000: Left Stick Up
+ 000040000: Left Stick Right
+ 000080000: Left Stick Down
+ 000100000: Right Stick Left
+ 000200000: Right Stick Up
+ 000400000: Right Stick Right
+ 000800000: Right Stick Down
+ 001000000: SL Left Joy-Con
+ 002000000: SR Left Joy-Con
+ 004000000: SL Right Joy-Con
+ 008000000: SR Right Joy-Con
+ 010000000: Top button on Poké Ball Plus (Palma) controller
+ 020000000: Verification
+ 040000000: B button on Left NES/HVC controller in Handheld mode
+ 080000000: Left C button in N64 controller
+ 100000000: Up C button in N64 controller
+ 200000000: Right C button in N64 controller
+ 400000000: Down C button in N64 controller
### Code Type 0xF0: Double Extended-Width Instruction ### Code Type 0xF0: Double Extended-Width Instruction
Code Type 0xF0 signals to the VM to treat the upper three nybbles of the first dword as instruction type, instead of just the upper nybble. Code Type 0xF0 signals to the VM to treat the upper three nybbles of the first dword as instruction type, instead of just the upper nybble.

View File

@ -108,6 +108,8 @@ namespace ams::dmnt::cheat::impl {
this->LogToDebugFile("Bit Width: %x\n", opcode->begin_cond.bit_width); this->LogToDebugFile("Bit Width: %x\n", opcode->begin_cond.bit_width);
this->LogToDebugFile("Mem Type: %x\n", opcode->begin_cond.mem_type); this->LogToDebugFile("Mem Type: %x\n", opcode->begin_cond.mem_type);
this->LogToDebugFile("Cond Type: %x\n", opcode->begin_cond.cond_type); this->LogToDebugFile("Cond Type: %x\n", opcode->begin_cond.cond_type);
this->LogToDebugFile("Inc Ofs reg: %d\n", opcode->begin_cond.include_ofs_reg);
this->LogToDebugFile("Ofs Reg Idx: %x\n", opcode->begin_cond.ofs_reg_index);
this->LogToDebugFile("Rel Addr: %lx\n", opcode->begin_cond.rel_address); this->LogToDebugFile("Rel Addr: %lx\n", opcode->begin_cond.rel_address);
this->LogToDebugFile("Value: %lx\n", opcode->begin_cond.value.bit64); this->LogToDebugFile("Value: %lx\n", opcode->begin_cond.value.bit64);
break; break;
@ -158,6 +160,11 @@ namespace ams::dmnt::cheat::impl {
this->LogToDebugFile("Opcode: Begin Keypress Conditional\n"); this->LogToDebugFile("Opcode: Begin Keypress Conditional\n");
this->LogToDebugFile("Key Mask: %x\n", opcode->begin_keypress_cond.key_mask); this->LogToDebugFile("Key Mask: %x\n", opcode->begin_keypress_cond.key_mask);
break; break;
case CheatVmOpcodeType_BeginExtendedKeypressConditionalBlock:
this->LogToDebugFile("Opcode: Begin Extended Keypress Conditional\n");
this->LogToDebugFile("Key Mask: %x\n", opcode->begin_ext_keypress_cond.key_mask);
this->LogToDebugFile("Auto Repeat: %d\n", opcode->begin_ext_keypress_cond.auto_repeat);
break;
case CheatVmOpcodeType_PerformArithmeticRegister: case CheatVmOpcodeType_PerformArithmeticRegister:
this->LogToDebugFile("Opcode: Perform Register Arithmetic\n"); this->LogToDebugFile("Opcode: Perform Register Arithmetic\n");
this->LogToDebugFile("Bit Width: %x\n", opcode->perform_math_reg.bit_width); this->LogToDebugFile("Bit Width: %x\n", opcode->perform_math_reg.bit_width);
@ -358,6 +365,7 @@ namespace ams::dmnt::cheat::impl {
switch (opcode.opcode) { switch (opcode.opcode) {
case CheatVmOpcodeType_BeginConditionalBlock: case CheatVmOpcodeType_BeginConditionalBlock:
case CheatVmOpcodeType_BeginKeypressConditionalBlock: case CheatVmOpcodeType_BeginKeypressConditionalBlock:
case CheatVmOpcodeType_BeginExtendedKeypressConditionalBlock:
case CheatVmOpcodeType_BeginRegisterConditionalBlock: case CheatVmOpcodeType_BeginRegisterConditionalBlock:
opcode.begin_conditional_block = true; opcode.begin_conditional_block = true;
break; break;
@ -387,6 +395,8 @@ namespace ams::dmnt::cheat::impl {
opcode.begin_cond.bit_width = (first_dword >> 24) & 0xF; opcode.begin_cond.bit_width = (first_dword >> 24) & 0xF;
opcode.begin_cond.mem_type = (MemoryAccessType)((first_dword >> 20) & 0xF); opcode.begin_cond.mem_type = (MemoryAccessType)((first_dword >> 20) & 0xF);
opcode.begin_cond.cond_type = (ConditionalComparisonType)((first_dword >> 16) & 0xF); opcode.begin_cond.cond_type = (ConditionalComparisonType)((first_dword >> 16) & 0xF);
opcode.begin_cond.include_ofs_reg = ((first_dword >> 12) & 0xF) != 0;
opcode.begin_cond.ofs_reg_index = ((first_dword >> 8) & 0xF);
opcode.begin_cond.rel_address = ((u64)(first_dword & 0xFF) << 32ul) | ((u64)second_dword); opcode.begin_cond.rel_address = ((u64)(first_dword & 0xFF) << 32ul) | ((u64)second_dword);
opcode.begin_cond.value = GetNextVmInt(opcode.begin_cond.bit_width); opcode.begin_cond.value = GetNextVmInt(opcode.begin_cond.bit_width);
} }
@ -427,7 +437,8 @@ namespace ams::dmnt::cheat::impl {
opcode.ldr_memory.bit_width = (first_dword >> 24) & 0xF; opcode.ldr_memory.bit_width = (first_dword >> 24) & 0xF;
opcode.ldr_memory.mem_type = (MemoryAccessType)((first_dword >> 20) & 0xF); opcode.ldr_memory.mem_type = (MemoryAccessType)((first_dword >> 20) & 0xF);
opcode.ldr_memory.reg_index = ((first_dword >> 16) & 0xF); opcode.ldr_memory.reg_index = ((first_dword >> 16) & 0xF);
opcode.ldr_memory.load_from_reg = ((first_dword >> 12) & 0xF) != 0; opcode.ldr_memory.load_from_reg = ((first_dword >> 12) & 0xF);
opcode.ldr_memory.offset_register = ((first_dword >> 8) & 0xF);
opcode.ldr_memory.rel_address = ((u64)(first_dword & 0xFF) << 32ul) | ((u64)second_dword); opcode.ldr_memory.rel_address = ((u64)(first_dword & 0xFF) << 32ul) | ((u64)second_dword);
} }
break; break;
@ -460,6 +471,14 @@ namespace ams::dmnt::cheat::impl {
opcode.begin_keypress_cond.key_mask = first_dword & 0x0FFFFFFF; opcode.begin_keypress_cond.key_mask = first_dword & 0x0FFFFFFF;
} }
break; break;
case CheatVmOpcodeType_BeginExtendedKeypressConditionalBlock:
{
/* C4r00000 kkkkkkkk kkkkkkkk */
/* Read additional words. */
opcode.begin_ext_keypress_cond.key_mask = (u64)GetNextDword() << 32ul | (u64)GetNextDword();
opcode.begin_ext_keypress_cond.auto_repeat = ((first_dword >> 20) & 0xF) != 0;
}
break;
case CheatVmOpcodeType_PerformArithmeticRegister: case CheatVmOpcodeType_PerformArithmeticRegister:
{ {
/* 9TCRSIs0 (VVVVVVVV (VVVVVVVV)) */ /* 9TCRSIs0 (VVVVVVVV (VVVVVVVV)) */
@ -734,6 +753,8 @@ namespace ams::dmnt::cheat::impl {
return metadata->alias_extents.base + rel_address; return metadata->alias_extents.base + rel_address;
case MemoryAccessType_Aslr: case MemoryAccessType_Aslr:
return metadata->aslr_extents.base + rel_address; return metadata->aslr_extents.base + rel_address;
case MemoryAccessType_NonRelative:
return rel_address;
} }
} }
@ -769,6 +790,7 @@ namespace ams::dmnt::cheat::impl {
return true; return true;
} }
static u64 s_keyold = 0;
void CheatVirtualMachine::Execute(const CheatProcessMetadata *metadata) { void CheatVirtualMachine::Execute(const CheatProcessMetadata *metadata) {
CheatVmOpcode cur_opcode; CheatVmOpcode cur_opcode;
u64 kHeld = 0; u64 kHeld = 0;
@ -824,7 +846,7 @@ namespace ams::dmnt::cheat::impl {
case CheatVmOpcodeType_BeginConditionalBlock: case CheatVmOpcodeType_BeginConditionalBlock:
{ {
/* Read value from memory. */ /* Read value from memory. */
u64 src_address = GetCheatProcessAddress(metadata, cur_opcode.begin_cond.mem_type, cur_opcode.begin_cond.rel_address); u64 src_address = GetCheatProcessAddress(metadata, cur_opcode.begin_cond.mem_type, (cur_opcode.begin_cond.include_ofs_reg) ? m_registers[cur_opcode.begin_cond.ofs_reg_index] + cur_opcode.begin_cond.rel_address : cur_opcode.begin_cond.rel_address);
u64 src_value = 0; u64 src_value = 0;
switch (cur_opcode.store_static.bit_width) { switch (cur_opcode.store_static.bit_width) {
case 1: case 1:
@ -896,8 +918,12 @@ namespace ams::dmnt::cheat::impl {
{ {
/* Choose source address. */ /* Choose source address. */
u64 src_address; u64 src_address;
if (cur_opcode.ldr_memory.load_from_reg) { if (cur_opcode.ldr_memory.load_from_reg == 1) {
src_address = m_registers[cur_opcode.ldr_memory.reg_index] + cur_opcode.ldr_memory.rel_address; src_address = m_registers[cur_opcode.ldr_memory.reg_index] + cur_opcode.ldr_memory.rel_address;
} else if (cur_opcode.ldr_memory.load_from_reg == 2) {
src_address = m_registers[cur_opcode.ldr_memory.offset_register] + cur_opcode.ldr_memory.rel_address;
} else if (cur_opcode.ldr_memory.load_from_reg == 3) {
src_address = GetCheatProcessAddress(metadata, cur_opcode.ldr_memory.mem_type, m_registers[cur_opcode.ldr_memory.offset_register] + cur_opcode.ldr_memory.rel_address);
} else { } else {
src_address = GetCheatProcessAddress(metadata, cur_opcode.ldr_memory.mem_type, cur_opcode.ldr_memory.rel_address); src_address = GetCheatProcessAddress(metadata, cur_opcode.ldr_memory.mem_type, cur_opcode.ldr_memory.rel_address);
} }
@ -982,6 +1008,18 @@ namespace ams::dmnt::cheat::impl {
this->SkipConditionalBlock(true); this->SkipConditionalBlock(true);
} }
break; break;
case CheatVmOpcodeType_BeginExtendedKeypressConditionalBlock:
/* Check for keypress. */
if (!cur_opcode.begin_ext_keypress_cond.auto_repeat) {
if ((cur_opcode.begin_ext_keypress_cond.key_mask & kHeld) != (cur_opcode.begin_ext_keypress_cond.key_mask) || (cur_opcode.begin_ext_keypress_cond.key_mask & s_keyold) == (cur_opcode.begin_ext_keypress_cond.key_mask)) {
/* Keys not pressed. Skip conditional block. */
this->SkipConditionalBlock(true);
}
} else if ((cur_opcode.begin_ext_keypress_cond.key_mask & kHeld) != cur_opcode.begin_ext_keypress_cond.key_mask) {
/* Keys not pressed. Skip conditional block. */
this->SkipConditionalBlock(true);
}
break;
case CheatVmOpcodeType_PerformArithmeticRegister: case CheatVmOpcodeType_PerformArithmeticRegister:
{ {
const u64 operand_1_value = m_registers[cur_opcode.perform_math_reg.src_reg_1_index]; const u64 operand_1_value = m_registers[cur_opcode.perform_math_reg.src_reg_1_index];
@ -1022,6 +1060,34 @@ namespace ams::dmnt::cheat::impl {
case RegisterArithmeticType_None: case RegisterArithmeticType_None:
res_val = operand_1_value; res_val = operand_1_value;
break; break;
case RegisterArithmeticType_FloatAddition:
if (cur_opcode.perform_math_reg.bit_width == 4) {
res_val = std::bit_cast<std::uint32_t>(std::bit_cast<float>(static_cast<uint32_t>(operand_1_value)) + std::bit_cast<float>(static_cast<uint32_t>(operand_2_value)));
} else if (cur_opcode.perform_math_reg.bit_width == 8) {
res_val = std::bit_cast<std::uint64_t>(std::bit_cast<double>(operand_1_value) + std::bit_cast<double>(operand_2_value));
}
break;
case RegisterArithmeticType_FloatSubtraction:
if (cur_opcode.perform_math_reg.bit_width == 4) {
res_val = std::bit_cast<std::uint32_t>(std::bit_cast<float>(static_cast<uint32_t>(operand_1_value)) - std::bit_cast<float>(static_cast<uint32_t>(operand_2_value)));
} else if (cur_opcode.perform_math_reg.bit_width == 8) {
res_val = std::bit_cast<std::uint64_t>(std::bit_cast<double>(operand_1_value) - std::bit_cast<double>(operand_2_value));
}
break;
case RegisterArithmeticType_FloatMultiplication:
if (cur_opcode.perform_math_reg.bit_width == 4) {
res_val = std::bit_cast<std::uint32_t>(std::bit_cast<float>(static_cast<uint32_t>(operand_1_value)) * std::bit_cast<float>(static_cast<uint32_t>(operand_2_value)));
} else if (cur_opcode.perform_math_reg.bit_width == 8) {
res_val = std::bit_cast<std::uint64_t>(std::bit_cast<double>(operand_1_value) * std::bit_cast<double>(operand_2_value));
}
break;
case RegisterArithmeticType_FloatDivision:
if (cur_opcode.perform_math_reg.bit_width == 4) {
res_val = std::bit_cast<std::uint32_t>(std::bit_cast<float>(static_cast<uint32_t>(operand_1_value)) / std::bit_cast<float>(static_cast<uint32_t>(operand_2_value)));
} else if (cur_opcode.perform_math_reg.bit_width == 8) {
res_val = std::bit_cast<std::uint64_t>(std::bit_cast<double>(operand_1_value) / std::bit_cast<double>(operand_2_value));
}
break;
} }
@ -1304,6 +1370,7 @@ namespace ams::dmnt::cheat::impl {
break; break;
} }
} }
s_keyold = kHeld;
} }
} }

View File

@ -43,6 +43,7 @@ namespace ams::dmnt::cheat::impl {
CheatVmOpcodeType_SaveRestoreRegister = 0xC1, CheatVmOpcodeType_SaveRestoreRegister = 0xC1,
CheatVmOpcodeType_SaveRestoreRegisterMask = 0xC2, CheatVmOpcodeType_SaveRestoreRegisterMask = 0xC2,
CheatVmOpcodeType_ReadWriteStaticRegister = 0xC3, CheatVmOpcodeType_ReadWriteStaticRegister = 0xC3,
CheatVmOpcodeType_BeginExtendedKeypressConditionalBlock = 0xC4,
/* This is a meta entry, and not a real opcode. */ /* This is a meta entry, and not a real opcode. */
/* This is to facilitate multi-nybble instruction decoding. */ /* This is to facilitate multi-nybble instruction decoding. */
@ -59,6 +60,7 @@ namespace ams::dmnt::cheat::impl {
MemoryAccessType_Heap = 1, MemoryAccessType_Heap = 1,
MemoryAccessType_Alias = 2, MemoryAccessType_Alias = 2,
MemoryAccessType_Aslr = 3, MemoryAccessType_Aslr = 3,
MemoryAccessType_NonRelative = 4,
}; };
enum ConditionalComparisonType : u32 { enum ConditionalComparisonType : u32 {
@ -84,6 +86,10 @@ namespace ams::dmnt::cheat::impl {
RegisterArithmeticType_LogicalXor = 8, RegisterArithmeticType_LogicalXor = 8,
RegisterArithmeticType_None = 9, RegisterArithmeticType_None = 9,
RegisterArithmeticType_FloatAddition = 10,
RegisterArithmeticType_FloatSubtraction = 11,
RegisterArithmeticType_FloatMultiplication = 12,
RegisterArithmeticType_FloatDivision = 13,
}; };
enum StoreRegisterOffsetType : u32 { enum StoreRegisterOffsetType : u32 {
@ -138,6 +144,8 @@ namespace ams::dmnt::cheat::impl {
u32 bit_width; u32 bit_width;
MemoryAccessType mem_type; MemoryAccessType mem_type;
ConditionalComparisonType cond_type; ConditionalComparisonType cond_type;
bool include_ofs_reg;
u32 ofs_reg_index;
u64 rel_address; u64 rel_address;
VmInt value; VmInt value;
}; };
@ -161,7 +169,8 @@ namespace ams::dmnt::cheat::impl {
u32 bit_width; u32 bit_width;
MemoryAccessType mem_type; MemoryAccessType mem_type;
u32 reg_index; u32 reg_index;
bool load_from_reg; u8 load_from_reg;
u8 offset_register;
u64 rel_address; u64 rel_address;
}; };
@ -185,6 +194,11 @@ namespace ams::dmnt::cheat::impl {
u32 key_mask; u32 key_mask;
}; };
struct BeginExtendedKeypressConditionalOpcode {
u64 key_mask;
bool auto_repeat;
};
struct PerformArithmeticRegisterOpcode { struct PerformArithmeticRegisterOpcode {
u32 bit_width; u32 bit_width;
RegisterArithmeticType math_type; RegisterArithmeticType math_type;
@ -259,6 +273,7 @@ namespace ams::dmnt::cheat::impl {
StoreStaticToAddressOpcode str_static; StoreStaticToAddressOpcode str_static;
PerformArithmeticStaticOpcode perform_math_static; PerformArithmeticStaticOpcode perform_math_static;
BeginKeypressConditionalOpcode begin_keypress_cond; BeginKeypressConditionalOpcode begin_keypress_cond;
BeginExtendedKeypressConditionalOpcode begin_ext_keypress_cond;
PerformArithmeticRegisterOpcode perform_math_reg; PerformArithmeticRegisterOpcode perform_math_reg;
StoreRegisterToAddressOpcode str_register; StoreRegisterToAddressOpcode str_register;
BeginRegisterConditionalOpcode begin_reg_cond; BeginRegisterConditionalOpcode begin_reg_cond;