Add generic hook module (x86dis + auto-sizing detour), framecrash updates, marker assets

Hook lib: port HDE32 length disassembler to Zig (x86dis.zig), add GenericHook
with automatic prologue sizing and relative instruction relocation, add
Detour(FnType) comptime-generic type-safe hook wrapper. Update build.zig
to expose x86dis, generic_hook, hook as separate modules.

Framecrash: expand vtable hook research and implementation.
Markers: updated raid marker M2 assets.
This commit is contained in:
MarcelineVQ
2026-02-28 17:46:03 -08:00
parent 2cca8f4b53
commit 608e1c43d5
11 changed files with 1713 additions and 53 deletions
+17
View File
@@ -15,4 +15,21 @@ pub fn build(b: *std.Build) void {
.optimize = optimize,
});
hook_mod.addOptions("config", options);
// x86 length disassembler — standalone, no dependencies
const x86dis_mod = b.addModule("x86dis", .{
.root_source_file = b.path("src/x86dis.zig"),
.target = target,
.optimize = optimize,
});
// Generic hook — uses x86dis + hook for auto-sizing trampolines
const generic_hook_mod = b.addModule("generic_hook", .{
.root_source_file = b.path("src/generic_hook.zig"),
.target = target,
.optimize = optimize,
});
generic_hook_mod.addImport("x86dis", x86dis_mod);
generic_hook_mod.addImport("hook.zig", hook_mod);
generic_hook_mod.addOptions("config", options);
}
+279
View File
@@ -0,0 +1,279 @@
//! Generic x86 inline hook — no manual prologue size or fixup lists needed.
//!
//! Uses the x86 length disassembler (HDE32 port) to automatically determine
//! how many prologue bytes to steal, and relocates all relative instructions.
//!
//! Combines MinHook's compact disassembler with HadesMem's type-safe approach:
//! declare the function signature once at comptime, get a correctly-typed
//! trampoline and detour with zero manual casting.
//!
//! ## Low-level API (GenericHook)
//!
//! ```zig
//! var my_hook: GenericHook = .{};
//! if (my_hook.install(0x401000, @intFromPtr(&myDetour)) == .ok) {
//! const orig = my_hook.getTrampoline(OrigFnType);
//! _ = orig();
//! }
//! my_hook.remove();
//! ```
//!
//! ## Type-safe API (Detour) — HadesMem-inspired
//!
//! ```zig
//! const MyHook = Detour(fn (u32, u32) callconv(.{ .x86_stdcall = .{} }) u32);
//! var hook: MyHook = .{};
//! hook.attach(0x401000, myDetour);
//! // Inside detour: hook.callOriginal(.{arg1, arg2});
//! ```
const std = @import("std");
const x86dis = @import("x86dis");
const hook_base = @import("hook.zig");
const VirtualAlloc = hook_base.VirtualAlloc;
const VirtualFree = hook_base.VirtualFree;
const PAGE_EXECUTE_READWRITE = hook_base.PAGE_EXECUTE_READWRITE;
const MEM_COMMIT = hook_base.MEM_COMMIT;
const MEM_RELEASE = hook_base.MEM_RELEASE;
const writeProtected = hook_base.writeProtected;
const JMP_SIZE: usize = 5; // E9 + rel32
const MAX_STOLEN: usize = 32;
const TRAMPOLINE_BUF: usize = 64;
// ═══════════════════════════════════════════════════════════════════════
// GenericHook — low-level auto-sizing hook
// ═══════════════════════════════════════════════════════════════════════
pub const GenericHook = struct {
mem: ?[*]u8 = null,
trampoline: usize = 0,
target: usize = 0,
stolen_size: usize = 0,
saved_bytes: [MAX_STOLEN]u8 = undefined,
pub const Error = enum {
ok,
alloc_failed,
disasm_error,
prologue_too_short,
unsupported_relocation,
};
/// Analyse target, build trampoline, patch target → detour. One call.
pub fn install(self: *GenericHook, target: usize, detour_addr: usize) Error {
const err = self.prepare(target);
if (err != .ok) return err;
self.activate(detour_addr);
return .ok;
}
/// Phase 1: disassemble prologue, allocate trampoline, copy + relocate.
pub fn prepare(self: *GenericHook, target: usize) Error {
if (self.mem != null) return .ok;
const src: [*]const u8 = @ptrFromInt(target);
// ── determine how many bytes to steal ──
var stolen: usize = 0;
while (stolen < JMP_SIZE) {
const insn = x86dis.decode(src + stolen);
if (insn.flags & x86dis.F_ERROR != 0) return .disasm_error;
if (insn.len == 0) return .disasm_error;
stolen += insn.len;
if (stolen > MAX_STOLEN) return .prologue_too_short;
}
// ── allocate ──
const mem = VirtualAlloc(null, TRAMPOLINE_BUF, MEM_COMMIT, PAGE_EXECUTE_READWRITE) orelse return .alloc_failed;
self.mem = mem;
self.target = target;
self.stolen_size = stolen;
self.trampoline = @intFromPtr(mem);
@memcpy(self.saved_bytes[0..stolen], src[0..stolen]);
// ── check if already hooked (E9 at target) — chain through ──
if (src[0] == 0xE9) {
const other_detour = hook_base.rel32Target(target);
mem[0] = 0xE9;
hook_base.writeRel32(mem + 1, self.trampoline + 1, other_detour);
return .ok;
}
// ── build trampoline: copy + relocate ──
var t_pos: usize = 0;
var s_pos: usize = 0;
while (s_pos < stolen) {
const insn = x86dis.decode(src + s_pos);
const op = insn.opcode;
const src_addr = target + s_pos;
const dst_addr = self.trampoline + t_pos;
if (insn.flags & x86dis.F_RELATIVE != 0) {
if (op == 0xE8 or op == 0xE9) {
// CALL/JMP rel32
const abs = hook_base.rel32Target(src_addr);
mem[t_pos] = op;
hook_base.writeRel32(mem + t_pos + 1, dst_addr + 1, abs);
t_pos += 5;
} else if (op == 0x0F and insn.opcode2 >= 0x80 and insn.opcode2 <= 0x8F) {
// Jcc rel32 (0F 80-8F)
const abs = jcc32Target(src_addr);
mem[t_pos] = 0x0F;
mem[t_pos + 1] = insn.opcode2;
hook_base.writeRel32(mem + t_pos + 2, dst_addr + 2, abs);
t_pos += 6;
} else if (op >= 0x70 and op <= 0x7F) {
// Short Jcc → expand to near Jcc (0F 8x)
const offset = @as(i8, @bitCast(src[s_pos + 1]));
const abs: usize = @bitCast(@as(isize, @intCast(src_addr + 2)) + offset);
mem[t_pos] = 0x0F;
mem[t_pos + 1] = op + 0x10;
hook_base.writeRel32(mem + t_pos + 2, dst_addr + 2, abs);
t_pos += 6;
} else if (op == 0xEB) {
// Short JMP → expand to near JMP (E9)
const offset = @as(i8, @bitCast(src[s_pos + 1]));
const abs: usize = @bitCast(@as(isize, @intCast(src_addr + 2)) + offset);
mem[t_pos] = 0xE9;
hook_base.writeRel32(mem + t_pos + 1, dst_addr + 1, abs);
t_pos += 5;
} else {
// LOOP/JECXZ or unknown — cannot trivially expand
self.cleanup();
return .unsupported_relocation;
}
} else {
// Non-relative — copy verbatim
@memcpy(mem[t_pos .. t_pos + insn.len], src[s_pos .. s_pos + insn.len]);
t_pos += insn.len;
}
s_pos += insn.len;
}
// ── JMP back to original code after stolen bytes ──
mem[t_pos] = 0xE9;
hook_base.writeRel32(
mem + t_pos + 1,
self.trampoline + t_pos + 1,
target + stolen,
);
return .ok;
}
/// Phase 2: write the E9 JMP patch at the target.
pub fn activate(self: *GenericHook, detour_addr: usize) void {
var patch: [MAX_STOLEN]u8 = .{0x90} ** MAX_STOLEN;
patch[0] = 0xE9;
hook_base.writeRel32(patch[1..5], self.target + 1, detour_addr);
writeProtected(self.target, patch[0..self.stolen_size]);
}
/// Restore original bytes and free trampoline memory.
pub fn remove(self: *GenericHook) void {
if (self.mem == null) return;
writeProtected(self.target, self.saved_bytes[0..self.stolen_size]);
_ = VirtualFree(@ptrFromInt(@intFromPtr(self.mem.?)), 0, MEM_RELEASE);
self.mem = null;
}
/// Get trampoline as a typed function pointer.
pub fn getTrampoline(self: *const GenericHook, comptime T: type) T {
return @ptrFromInt(self.trampoline);
}
fn cleanup(self: *GenericHook) void {
if (self.mem) |m| {
_ = VirtualFree(@ptrFromInt(@intFromPtr(m)), 0, MEM_RELEASE);
self.mem = null;
}
}
};
// ═══════════════════════════════════════════════════════════════════════
// Detour(FnType) — HadesMem-style type-safe generic hook
// ═══════════════════════════════════════════════════════════════════════
/// Comptime-generic typed detour. Declare the target function's type once;
/// get type-checked attach/callOriginal with no manual pointer casts.
///
/// ```zig
/// const StdcallU32x2 = fn (u32, u32) callconv(.{ .x86_stdcall = .{} }) u32;
/// const MyHook = generic_hook.Detour(StdcallU32x2);
/// var hook: MyHook = .{};
/// hook.attach(0x401000, &myDetour);
/// // in detour: hook.callOriginal(.{ a, b });
/// hook.detach();
/// ```
pub fn Detour(comptime FnType: type) type {
const FnInfo = @typeInfo(FnType).@"fn";
const FnPtr = *const FnType;
const ReturnType = FnInfo.return_type orelse void;
const ParamTypes = FnInfo.params;
return struct {
inner: GenericHook = .{},
const Self = @This();
/// Hook the function at `target` to redirect to `detour`.
pub fn attach(self: *Self, target: usize, detour: FnPtr) GenericHook.Error {
return self.inner.install(target, @intFromPtr(detour));
}
/// Call the original (pre-hook) function through the trampoline.
pub fn callOriginal(self: *const Self, args: anytype) ReturnType {
const orig: FnPtr = @ptrFromInt(self.inner.trampoline);
return @call(.auto, orig, coerceArgs(ParamTypes, args));
}
/// Unhook: restore original bytes, free trampoline.
pub fn detach(self: *Self) void {
self.inner.remove();
}
/// Get the trampoline as the correctly-typed function pointer.
pub fn original(self: *const Self) FnPtr {
return @ptrFromInt(self.inner.trampoline);
}
};
}
/// Coerce a tuple of args into the exact parameter types expected.
fn coerceArgs(comptime params: anytype, args: anytype) CoercedTuple(params) {
var result: CoercedTuple(params) = undefined;
inline for (0..params.len) |idx| {
@field(result, std.fmt.comptimePrint("{d}", .{idx})) = args[idx];
}
return result;
}
fn CoercedTuple(comptime params: anytype) type {
var fields: [params.len]std.builtin.Type.StructField = undefined;
inline for (0..params.len) |idx| {
fields[idx] = .{
.name = std.fmt.comptimePrint("{d}", .{idx}),
.type = params[idx].type.?,
.default_value_ptr = null,
.is_comptime = false,
.alignment = 0,
};
}
return @Type(.{ .@"struct" = .{
.layout = .auto,
.fields = &fields,
.decls = &.{},
.is_tuple = true,
} });
}
/// Resolve absolute target of a Jcc rel32 (0F 8x xx xx xx xx) — 6 byte insn.
fn jcc32Target(addr: usize) usize {
const disp: u32 = @bitCast(@as(*align(1) const i32, @ptrFromInt(addr + 2)).*);
return (addr + 6) +% disp;
}
+402
View File
@@ -0,0 +1,402 @@
//! Minimal x86 (32-bit) length disassembler.
//!
//! Faithful port of Vyacheslav Patkov's Hacker Disassembler Engine 32 (HDE32),
//! used by MinHook. Only computes instruction length + flags needed for
//! relocation (F_RELATIVE). ~470 bytes of table data, compiles to ~1-2 KB.
const std = @import("std");
// ── public flags ───────────────────────────────────────────────────────
pub const F_MODRM: u32 = 0x00000001;
pub const F_SIB: u32 = 0x00000002;
pub const F_IMM8: u32 = 0x00000004;
pub const F_IMM16: u32 = 0x00000008;
pub const F_IMM32: u32 = 0x00000010;
pub const F_DISP8: u32 = 0x00000020;
pub const F_DISP16: u32 = 0x00000040;
pub const F_DISP32: u32 = 0x00000080;
pub const F_RELATIVE: u32 = 0x00000100;
pub const F_ERROR: u32 = 0x00001000;
// ── internal cflags ────────────────────────────────────────────────────
const C_MODRM: u8 = 0x01;
const C_IMM8: u8 = 0x02;
const C_IMM16: u8 = 0x04;
const C_IMM_P66: u8 = 0x10;
const C_REL8: u8 = 0x20;
const C_REL32: u8 = 0x40;
const C_GROUP: u8 = 0x80;
const C_ERROR: u8 = 0xff;
const PRE_NONE: u8 = 0x01;
const PRE_66: u8 = 0x08;
const PRE_67: u8 = 0x10;
const DELTA_OPCODES: usize = 0x4a;
// HDE32 opcode table — verbatim from table32.h
const hde32_table = [_]u8{
0xa3, 0xa8, 0xa3, 0xa8, 0xa3, 0xa8, 0xa3, 0xa8, 0xa3, 0xa8, 0xa3, 0xa8, 0xa3, 0xa8, 0xa3,
0xa8, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xac, 0xaa, 0xb2, 0xaa, 0x9f, 0x9f,
0x9f, 0x9f, 0xb5, 0xa3, 0xa3, 0xa4, 0xaa, 0xaa, 0xba, 0xaa, 0x96, 0xaa, 0xa8, 0xaa, 0xc3,
0xc3, 0x96, 0x96, 0xb7, 0xae, 0xd6, 0xbd, 0xa3, 0xc5, 0xa3, 0xa3, 0x9f, 0xc3, 0x9c, 0xaa,
0xaa, 0xac, 0xaa, 0xbf, 0x03, 0x7f, 0x11, 0x7f, 0x01, 0x7f, 0x01, 0x3f, 0x01, 0x01, 0x90,
0x82, 0x7d, 0x97, 0x59, 0x59, 0x59, 0x59, 0x59, 0x7f, 0x59, 0x59, 0x60, 0x7d, 0x7f, 0x7f,
0x59, 0x59, 0x59, 0x59, 0x59, 0x59, 0x59, 0x59, 0x59, 0x59, 0x59, 0x59, 0x9a, 0x88, 0x7d,
0x59, 0x50, 0x50, 0x50, 0x50, 0x59, 0x59, 0x59, 0x59, 0x61, 0x94, 0x61, 0x9e, 0x59, 0x59,
0x85, 0x59, 0x92, 0xa3, 0x60, 0x60, 0x59, 0x59, 0x59, 0x59, 0x59, 0x59, 0x59, 0x59, 0x59,
0x59, 0x59, 0x9f, 0x01, 0x03, 0x01, 0x04, 0x03, 0xd5, 0x03, 0xcc, 0x01, 0xbc, 0x03, 0xf0,
0x10, 0x10, 0x10, 0x10, 0x50, 0x50, 0x50, 0x50, 0x14, 0x20, 0x20, 0x20, 0x20, 0x01, 0x01,
0x01, 0x01, 0xc4, 0x02, 0x10, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0xc0, 0xc2, 0x10, 0x11,
0x02, 0x03, 0x11, 0x03, 0x03, 0x04, 0x00, 0x00, 0x14, 0x00, 0x02, 0x00, 0x00, 0xc6, 0xc8,
0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xca,
0x01, 0x01, 0x01, 0x00, 0x06, 0x00, 0x04, 0x00, 0xc0, 0xc2, 0x01, 0x01, 0x03, 0x01, 0xff,
0xff, 0x01, 0x00, 0x03, 0xc4, 0xc4, 0xc6, 0x03, 0x01, 0x01, 0x01, 0xff, 0x03, 0x03, 0x03,
0xc8, 0x40, 0x00, 0x0a, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x33, 0x01, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xbf, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00,
0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x7f, 0x00, 0x00, 0xff, 0x4a, 0x4a, 0x4a, 0x4a, 0x4b, 0x52, 0x4a, 0x4a, 0x4a, 0x4a, 0x4f,
0x4c, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x55, 0x45, 0x40, 0x4a, 0x4a, 0x4a,
0x45, 0x59, 0x4d, 0x46, 0x4a, 0x5d, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a,
0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x61, 0x63, 0x67, 0x4e, 0x4a, 0x4a, 0x6b, 0x6d, 0x4a, 0x4a,
0x45, 0x6d, 0x4a, 0x4a, 0x44, 0x45, 0x4a, 0x4a, 0x00, 0x00, 0x00, 0x02, 0x0d, 0x06, 0x06,
0x06, 0x06, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x06, 0x06, 0x06, 0x00, 0x06, 0x06, 0x02, 0x06,
0x00, 0x0a, 0x0a, 0x07, 0x07, 0x06, 0x02, 0x05, 0x05, 0x02, 0x02, 0x00, 0x00, 0x04, 0x04,
0x04, 0x04, 0x00, 0x00, 0x00, 0x0e, 0x05, 0x06, 0x06, 0x06, 0x01, 0x06, 0x00, 0x00, 0x08,
0x00, 0x10, 0x00, 0x18, 0x00, 0x20, 0x00, 0x28, 0x00, 0x30, 0x00, 0x80, 0x01, 0x82, 0x01,
0x86, 0x00, 0xf6, 0xcf, 0xfe, 0x3f, 0xab, 0x00, 0xb0, 0x00, 0xb1, 0x00, 0xb3, 0x00, 0xba,
0xf8, 0xbb, 0x00, 0xc0, 0x00, 0xc1, 0x00, 0xc7, 0xbf, 0x62, 0xff, 0x00, 0x8d, 0xff, 0x00,
0xc4, 0xff, 0x00, 0xc5, 0xff, 0x00,
};
pub const Insn = struct {
len: u8,
flags: u32,
opcode: u8,
opcode2: u8,
};
/// Decode the instruction at `code`, returning its length and flags.
pub fn decode(code: [*]const u8) Insn {
var result = Insn{ .len = 0, .flags = 0, .opcode = 0, .opcode2 = 0 };
var p: usize = 0;
var pref: u8 = 0;
var disp_size: u8 = 0;
// ── prefixes ──
var prefix_count: u8 = 16;
prefix_loop: while (prefix_count > 0) : (prefix_count -= 1) {
switch (code[p]) {
0xf3, 0xf2 => pref |= if (code[p] == 0xf3) 0x04 else 0x02,
0xf0 => pref |= 0x20, // PRE_LOCK
0x26, 0x2e, 0x36, 0x3e, 0x64, 0x65 => pref |= 0x40, // PRE_SEG
0x66 => pref |= PRE_66,
0x67 => pref |= PRE_67,
else => break :prefix_loop,
}
p += 1;
}
result.flags = @as(u32, pref) << 23;
if (pref == 0) pref |= PRE_NONE;
// ── opcode ──
var ht_base: usize = 0;
var c = code[p];
p += 1;
result.opcode = c;
if (c == 0x0f) {
// two-byte opcode
result.opcode2 = code[p];
c = code[p];
p += 1;
ht_base = DELTA_OPCODES;
} else if (c >= 0xa0 and c <= 0xa3) {
// MOV moffs — address-size prefix swaps operand-size behavior
if (pref & PRE_67 != 0)
pref |= PRE_66
else
pref &= ~PRE_66;
}
const opcode = c;
// ── two-level table lookup: ht[ht[opcode/4] + (opcode%4)] ──
var cflags: u8 = blk: {
const idx1 = ht_base + @as(usize, opcode / 4);
if (idx1 >= hde32_table.len) break :blk C_ERROR;
const idx2 = ht_base + @as(usize, hde32_table[idx1]) + @as(usize, opcode % 4);
if (idx2 >= hde32_table.len) break :blk C_ERROR;
break :blk hde32_table[idx2];
};
if (cflags == C_ERROR) {
result.flags |= F_ERROR;
cflags = 0;
if ((opcode & 0xfd) == 0x24) // (opcode & -3) == 0x24
cflags +%= 1;
}
// ── group resolution ──
var x: u8 = 0;
if (cflags & C_GROUP != 0) {
const group_idx = ht_base + @as(usize, cflags & 0x7f);
if (group_idx + 1 < hde32_table.len) {
const t = std.mem.readInt(u16, hde32_table[group_idx..][0..2], .little);
cflags = @truncate(t);
x = @truncate(t >> 8);
}
}
// ── modrm ──
if (cflags & C_MODRM != 0) {
result.flags |= F_MODRM;
const modrm = code[p];
p += 1;
const m_mod = modrm >> 6;
const m_rm: u8 = modrm & 7;
const m_reg: u3 = @truncate((modrm & 0x3f) >> 3);
// F6 TEST imm8 / F7 TEST imm16/32
if (m_reg <= 1) {
if (opcode == 0xf6)
cflags |= C_IMM8;
if (opcode == 0xf7)
cflags |= C_IMM_P66;
}
// displacement
switch (m_mod) {
0 => {
if (pref & PRE_67 != 0) {
if (m_rm == 6) disp_size = 2;
} else {
if (m_rm == 5) disp_size = 4;
}
},
1 => disp_size = 1,
2 => {
disp_size = 2;
if (pref & PRE_67 == 0)
disp_size = 4;
},
else => {},
}
// SIB byte
if (m_mod != 3 and m_rm == 4 and (pref & PRE_67 == 0)) {
result.flags |= F_SIB;
const sib = code[p];
p += 1;
if ((sib & 7) == 5 and (m_mod & 1) == 0)
disp_size = 4;
}
// displacement bytes
switch (disp_size) {
1 => {
result.flags |= F_DISP8;
p += 1;
},
2 => {
result.flags |= F_DISP16;
p += 2;
},
4 => {
result.flags |= F_DISP32;
p += 4;
},
else => {},
}
}
// ── immediates ──
if (cflags & C_IMM_P66 != 0) {
if (cflags & C_REL32 != 0) {
if (pref & PRE_66 != 0) {
result.flags |= F_IMM16 | F_RELATIVE;
p += 2;
// disasm_done — skip remaining immediate checks
result.len = @intCast(p);
if (result.len > 15) {
result.flags |= F_ERROR;
result.len = 15;
}
return result;
}
// fall through to rel32_ok below
} else {
if (pref & PRE_66 != 0) {
result.flags |= F_IMM16;
p += 2;
} else {
result.flags |= F_IMM32;
p += 4;
}
}
}
if (cflags & C_IMM16 != 0) {
if (result.flags & F_IMM32 != 0) {
result.flags |= F_IMM16;
} else if (result.flags & F_IMM16 != 0) {
// F_2IMM16
} else {
result.flags |= F_IMM16;
}
p += 2;
}
if (cflags & C_IMM8 != 0) {
result.flags |= F_IMM8;
p += 1;
}
if (cflags & C_REL32 != 0) {
result.flags |= F_IMM32 | F_RELATIVE;
p += 4;
} else if (cflags & C_REL8 != 0) {
result.flags |= F_IMM8 | F_RELATIVE;
p += 1;
}
result.len = @intCast(p);
if (result.len > 15) {
result.flags |= F_ERROR;
result.len = 15;
}
return result;
}
// ── tests ──────────────────────────────────────────────────────────────
test "push ebp" {
const d = decode(&[_]u8{ 0x55, 0xCC });
try std.testing.expectEqual(@as(u8, 1), d.len);
}
test "mov ebp, esp" {
// 8B EC (or 89 E5)
const d = decode(&[_]u8{ 0x8B, 0xEC });
try std.testing.expectEqual(@as(u8, 2), d.len);
try std.testing.expect(d.flags & F_MODRM != 0);
}
test "call rel32" {
const d = decode(&[_]u8{ 0xE8, 0x78, 0x56, 0x34, 0x12 });
try std.testing.expectEqual(@as(u8, 5), d.len);
try std.testing.expect(d.flags & F_RELATIVE != 0);
try std.testing.expect(d.flags & F_IMM32 != 0);
}
test "jmp rel32" {
const d = decode(&[_]u8{ 0xE9, 0x00, 0x00, 0x00, 0x00 });
try std.testing.expectEqual(@as(u8, 5), d.len);
try std.testing.expect(d.flags & F_RELATIVE != 0);
}
test "sub esp, imm8" {
// 83 EC 10
const d = decode(&[_]u8{ 0x83, 0xEC, 0x10 });
try std.testing.expectEqual(@as(u8, 3), d.len);
try std.testing.expect(d.flags & F_MODRM != 0);
try std.testing.expect(d.flags & F_IMM8 != 0);
}
test "mov eax, [ebp+8]" {
// 8B 45 08
const d = decode(&[_]u8{ 0x8B, 0x45, 0x08 });
try std.testing.expectEqual(@as(u8, 3), d.len);
try std.testing.expect(d.flags & F_MODRM != 0);
try std.testing.expect(d.flags & F_DISP8 != 0);
}
test "jz rel32 (0F 84)" {
const d = decode(&[_]u8{ 0x0F, 0x84, 0x10, 0x00, 0x00, 0x00 });
try std.testing.expectEqual(@as(u8, 6), d.len);
try std.testing.expect(d.flags & F_RELATIVE != 0);
}
test "nop" {
const d = decode(&[_]u8{0x90});
try std.testing.expectEqual(@as(u8, 1), d.len);
}
test "ret" {
const d = decode(&[_]u8{0xC3});
try std.testing.expectEqual(@as(u8, 1), d.len);
}
test "short jmp EB" {
const d = decode(&[_]u8{ 0xEB, 0x05 });
try std.testing.expectEqual(@as(u8, 2), d.len);
try std.testing.expect(d.flags & F_RELATIVE != 0);
try std.testing.expect(d.flags & F_IMM8 != 0);
}
test "short jcc 74 (jz rel8)" {
const d = decode(&[_]u8{ 0x74, 0x0A });
try std.testing.expectEqual(@as(u8, 2), d.len);
try std.testing.expect(d.flags & F_RELATIVE != 0);
}
test "mov eax, imm32" {
const d = decode(&[_]u8{ 0xB8, 0x44, 0x33, 0x22, 0x11 });
try std.testing.expectEqual(@as(u8, 5), d.len);
}
test "push imm32" {
const d = decode(&[_]u8{ 0x68, 0x44, 0x33, 0x22, 0x11 });
try std.testing.expectEqual(@as(u8, 5), d.len);
}
test "push imm8" {
// 6A 01
const d = decode(&[_]u8{ 0x6A, 0x01 });
try std.testing.expectEqual(@as(u8, 2), d.len);
}
test "mov [ebp-4], eax" {
// 89 45 FC
const d = decode(&[_]u8{ 0x89, 0x45, 0xFC });
try std.testing.expectEqual(@as(u8, 3), d.len);
try std.testing.expect(d.flags & F_MODRM != 0);
try std.testing.expect(d.flags & F_DISP8 != 0);
}
test "lea eax, [ecx+edx*4+8]" {
// 8D 44 91 08
const d = decode(&[_]u8{ 0x8D, 0x44, 0x91, 0x08 });
try std.testing.expectEqual(@as(u8, 4), d.len);
try std.testing.expect(d.flags & F_MODRM != 0);
try std.testing.expect(d.flags & F_SIB != 0);
try std.testing.expect(d.flags & F_DISP8 != 0);
}
test "mov [disp32], eax" {
// A3 xx xx xx xx
const d = decode(&[_]u8{ 0xA3, 0x00, 0x10, 0x40, 0x00 });
try std.testing.expectEqual(@as(u8, 5), d.len);
}
test "sub esp, imm32" {
// 81 EC 00 01 00 00
const d = decode(&[_]u8{ 0x81, 0xEC, 0x00, 0x01, 0x00, 0x00 });
try std.testing.expectEqual(@as(u8, 6), d.len);
try std.testing.expect(d.flags & F_MODRM != 0);
}
test "test eax, imm32 (F7 C0)" {
// F7 C0 FF 00 00 00 = test eax, 0xFF
const d = decode(&[_]u8{ 0xF7, 0xC0, 0xFF, 0x00, 0x00, 0x00 });
try std.testing.expectEqual(@as(u8, 6), d.len);
}
test "ret imm16" {
// C2 04 00
const d = decode(&[_]u8{ 0xC2, 0x04, 0x00 });
try std.testing.expectEqual(@as(u8, 3), d.len);
}