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)`
+ 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.
+ A: Immediate offset to use from memory region base.
+ 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.
#### Encoding
`1TMC00AA AAAAAAAA VVVVVVVV (VVVVVVVV)`
`1TMCXrAA AAAAAAAA VVVVVVVV (VVVVVVVV)`
+ 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).
+ T: Width of memory read (1, 2, 4, or 8 bytes).
+ M: Memory region to read from (0 = Main NSO, 1 = Heap, 2 = Alias, 3 = Aslr, 4 = non-relative).
+ 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.
+ V: Value to compare to.
@ -78,6 +80,9 @@ If the condition is not met, all instructions until the appropriate End or Else
+ 5: ==
+ 6: !=
#### Operand Type
+ 0: Memory Base + Relative Offset
+ 1: Memory Base + Offset Register + Relative Offset
---
### 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`
+ 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.
+ 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).
+ 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
@ -250,7 +271,10 @@ Code type 0x9 allows performing arithmetic on registers.
+ 7: Logical Not (discards right-hand operand)
+ 8: Logical Xor
+ 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
@ -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 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("Mem Type: %x\n", opcode->begin_cond.mem_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("Value: %lx\n", opcode->begin_cond.value.bit64);
break;
@ -158,6 +160,11 @@ namespace ams::dmnt::cheat::impl {
this->LogToDebugFile("Opcode: Begin Keypress Conditional\n");
this->LogToDebugFile("Key Mask: %x\n", opcode->begin_keypress_cond.key_mask);
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:
this->LogToDebugFile("Opcode: Perform Register Arithmetic\n");
this->LogToDebugFile("Bit Width: %x\n", opcode->perform_math_reg.bit_width);
@ -358,6 +365,7 @@ namespace ams::dmnt::cheat::impl {
switch (opcode.opcode) {
case CheatVmOpcodeType_BeginConditionalBlock:
case CheatVmOpcodeType_BeginKeypressConditionalBlock:
case CheatVmOpcodeType_BeginExtendedKeypressConditionalBlock:
case CheatVmOpcodeType_BeginRegisterConditionalBlock:
opcode.begin_conditional_block = true;
break;
@ -387,6 +395,8 @@ namespace ams::dmnt::cheat::impl {
opcode.begin_cond.bit_width = (first_dword >> 24) & 0xF;
opcode.begin_cond.mem_type = (MemoryAccessType)((first_dword >> 20) & 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.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.mem_type = (MemoryAccessType)((first_dword >> 20) & 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);
}
break;
@ -460,6 +471,14 @@ namespace ams::dmnt::cheat::impl {
opcode.begin_keypress_cond.key_mask = first_dword & 0x0FFFFFFF;
}
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:
{
/* 9TCRSIs0 (VVVVVVVV (VVVVVVVV)) */
@ -734,6 +753,8 @@ namespace ams::dmnt::cheat::impl {
return metadata->alias_extents.base + rel_address;
case MemoryAccessType_Aslr:
return metadata->aslr_extents.base + rel_address;
case MemoryAccessType_NonRelative:
return rel_address;
}
}
@ -769,6 +790,7 @@ namespace ams::dmnt::cheat::impl {
return true;
}
static u64 s_keyold = 0;
void CheatVirtualMachine::Execute(const CheatProcessMetadata *metadata) {
CheatVmOpcode cur_opcode;
u64 kHeld = 0;
@ -824,7 +846,7 @@ namespace ams::dmnt::cheat::impl {
case CheatVmOpcodeType_BeginConditionalBlock:
{
/* 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;
switch (cur_opcode.store_static.bit_width) {
case 1:
@ -896,8 +918,12 @@ namespace ams::dmnt::cheat::impl {
{
/* Choose source 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;
} 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 {
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);
}
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:
{
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:
res_val = operand_1_value;
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;
}
}
s_keyold = kHeld;
}
}

View file

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