2 Commits
Author SHA1 Message Date
pancake d4a5fbfc7f Add gameboy pattern file 2026-10-29 19:48:09 +01:00
pancake d7635ca49e Add address-bound trees, flag colors and hexpat flag cleanup 2026-10-13 18:48:21 +02:00
16 changed files with 1383 additions and 30 deletions

No files matched your search

+2 -2
View File
@@ -24,13 +24,13 @@ all: $(OUT)
$(OUT): r2/plugin.ts r2/r2.d.ts $(LIB)
$(BUNDLE) r2/plugin.ts
tests/unit.js: tests/unit.ts tests/search_unit.ts tests/magic_unit.ts $(LIB)
tests/unit.js: tests/unit.ts tests/search_unit.ts tests/magic_unit.ts tests/tree_unit.ts $(LIB)
$(BUNDLE) tests/unit.ts
tests/testlib.js: tests/testlib.ts tests/upstream_hooks.ts $(LIB)
$(BUNDLE) tests/testlib.ts
tests/upstream.r2.js: tests/upstream_r2.ts tests/upstream_hooks.ts tests/search_unit.ts tests/magic_unit.ts $(LIB)
tests/upstream.r2.js: tests/upstream_r2.ts tests/upstream_hooks.ts tests/search_unit.ts tests/magic_unit.ts tests/tree_unit.ts $(LIB)
$(BUNDLE) tests/upstream_r2.ts
user-install: $(OUT)
+49 -1
View File
@@ -31,11 +31,13 @@ Commands (all output goes through r2's console, so `~grep` and `|` work):
| `hexpat- [n]` | unload all loaded files (or the nth) |
| `hexpat-v` | show the r2hexpat version |
| `hexpatj [file]` | patterns as JSON (`hexpatj~{}` to indent) |
| `hexpat* [file]` | patterns as r2 commands: flags in the `hexpat` flagspace, `Cd`/`Cs` data hints and value comments (`.hexpat*` applies them) |
| `hexpat* [file]` | patterns as r2 commands: flags in the `hexpat` flagspace, flag colors/comments, `Cd`/`Cs` data hints and value comments (`.hexpat*` applies them) |
| `hexpat-*` | r2 commands to remove generated flags (`.hexpat-*` applies them) |
| `hexpatl[j]` | list loaded files and their top-level patterns |
| `hexpate expr` | evaluate an expression or assignment in the last loaded file (`hexpate sizeof(header)`, `hexpate header.value = 3`) |
| `hexpatp[?+=-ijl] ...` | build partial instances and masked search patterns (`hexpatp?` for help) |
| `hexpatm [options] Type` | print a libmagic header decoder (`hexpatm?` for help) |
| `hexpatt[j*] [options] [Type [address]\|pattern]` | view a type at an address or a loaded pattern as a tree, JSON or r2 commands (`hexpatt?` for help) |
| `hexpats [file]` | one-line summary (types with their field count, enums, functions, patterns, size) or the error, meant for status bars |
| `hexpat?` | help |
@@ -57,6 +59,46 @@ the transform has no inverse.
Quote the whole r2 command when it contains operators that r2 handles itself,
for example `"hexpate header.value |= 8"` or `"hexpate header.value >= 3"`.
## Address-bound trees and flag colors
`hexpatt` displays a type's fields as a tree with addresses, values, sizes
and evaluated attributes, including `color`, `comment` and `name`:
```text
hexpatt -f types.hexpat Header @ 0x100
hexpat -q types.hexpat
hexpatt Header 0x100
hexpatt header
hexpatt header.inner
hexpatt
```
A type uses the current r2 seek when no address is given; an explicit address
can be an r2 expression or flag name. `-f file` supplies types without loading
the file's placements, executing its top-level statements or calling `main()`.
It accepts `base64:` sources and `-I dir` for includes. Without `-f`, types come
from the last loaded file. Type decoding follows ordinary hexpat declarations,
including data-dependent arrays and pointers, and honors the binary base
address or `#pragma base_address`. These one-shot views preserve loaded files
and search instances. A loaded pattern name or nested path shows its existing
tree; no arguments shows all loaded patterns.
Rows display their color attributes even with `e scr.color=0`; enabling colors
also colors the rows. Enclosing aggregate colors apply to members that have no
explicit color of their own. `hexpattj` and `hexpatj` expose `color`, `comment`,
`displayName` and evaluated `attributes` in JSON.
`hexpat*` and `hexpatt*` emit `fc flag=rgb:RRGGBB` for colored flags and `fC`
for pattern comments. The runtime's `std::core::set_pattern_color` is supported
too. Apply a type's flags directly with `.hexpatt* -f types.hexpat Header @ 0x100`.
`hexpat-*` prints a removal script; `.hexpat-*` applies it. This removes flags
matching `hexpat.*` in the `hexpat` flagspace, including their flag metadata,
and restores the previous flagspace. It works after the patterns are unloaded
and preserves other flags, loaded files and search instances. Address comments
and data hints remain. `hexpat-` (or `hexpat- *`, with a space) still unloads
the pattern files.
## Search patterns
`hexpatp` instantiates a type without a backing address. All fields start
@@ -189,6 +231,12 @@ evaluates an expression or assignment in the global scope of an evaluated patter
functions, types, pragma handlers and `in` variables, like the C++ runtime's
`addFunction`/`addType`/`addPragma`.
`decodeType(program, "Header", host, offset)` decodes one type at a host-relative
offset after preparing types and constants only. Its root pattern is named
`value` (with underscores added if a constant already uses that name).
`dumpTree(inst, print, { base, color, maxEntries })` renders a tree
with metadata; `patterns` can select specific roots.
Hosts can implement `write(addr, bytes)` for interactive field assignments;
read-only hosts reject those writes. Assignments while evaluating a pattern
file retain ImHex's value overrides and do not write host data.
+30 -10
View File
@@ -158,6 +158,24 @@ export class PatternInstance {
this.execBlock(constants(this.program.body), this.global);
}
/** Decode a type against the input at an offset, without running top-level code. */
placeType(type: string, offset = 0, name?: string): Pattern {
if (!this.global) this.prepareTypes();
if (!Number.isSafeInteger(offset) || offset < 0 || offset > this.data.size())
this.error("type offset is outside the data");
this.frames = [this.global];
this.cursor = offset;
if (name === undefined) {
name = "value";
while (this.global.scope.vars.has(name)) name += "_";
}
const p = this.create(this.resolveType(parseType(type, this.types.keys())), name);
this.declare(name, { v: p }, this.global.scope, this.loc);
this.patterns.push(p);
this.variables.set(name, p);
return p;
}
/** Build a fixed layout at offset zero, without reading any instance data. */
instantiateType(type: string, options: LayoutOptions = {}): Pattern {
const alignment = options.alignment === undefined ? 1 : options.alignment;
@@ -1836,19 +1854,14 @@ export class PatternInstance {
private dumpPattern(p: Pattern, indent: number, print: (s: string) => void, name: string): void {
if (p.hidden) return;
if (p.displayName) name = p.displayName;
const pad = "".padStart(indent * 2, " ");
const addr = p.local ? "local" : "0x" + (this.dumpBase + p.offset).toString(16).padStart(8, "0");
const type = p.kind === "bitfield_field" ? p.typeName + ":" + p.bits : p.typeName;
let line = pad + name + " (" + type + ") @ " + addr;
print(pad + this.describePattern(p, this.dumpBase, name));
if (p.displayName) name = p.displayName;
if (p.kind === "pointer") {
const v = this.value(p) as bigint;
print(line + " = " + fmtInt(v));
if (p.pointee) this.dumpPattern(p.pointee, indent + 1, print, p.pointee.name || "*" + name);
return;
}
if (p.isComposite() && !p.formatFn && !p.transformFn) {
print(line + " size=" + p.size);
if (p.sealed) return;
if (p.kind === "array") {
const n = p.entryCount();
@@ -1856,9 +1869,16 @@ export class PatternInstance {
} else for (const c of p.children || []) this.dumpPattern(c, indent + 1, print, c.name);
return;
}
if (p.kind === "padding") { print(line + " size=" + p.size); return; }
line += " = " + this.dumpValue(p);
print(line);
}
/** One pattern row, shared by the indented dump and the metadata tree. */
describePattern(p: Pattern, base = 0, name = p.name): string {
const addr = p.local ? "local" : "0x" + (base + p.offset).toString(16).padStart(8, "0");
const type = p.kind === "bitfield_field" ? p.typeName + ":" + p.bits : p.typeName;
const line = (p.displayName || name) + " (" + type + ") @ " + addr;
if (p.kind === "pointer") return line + " = " + fmtInt(this.value(p) as bigint);
if ((p.isComposite() && !p.formatFn && !p.transformFn) || p.kind === "padding") return line + " size=" + p.size;
return line + " = " + this.dumpValue(p);
}
private dumpValue(p: Pattern): string {
+9 -2
View File
@@ -12,8 +12,8 @@ import * as A from "./ast";
export { parse, PatternInstance, PatternError, BufferHost };
export type { HexpatHost, HostHooks };
export { Pattern } from "./patterns";
export { toJson, patternToJson, walkPatterns, jsonValue } from "./output";
export type { PatternJson, JsonOptions } from "./output";
export { toJson, patternToJson, walkPatterns, jsonValue, dumpTree, patternColor } from "./output";
export type { PatternJson, JsonOptions, TreeOptions } from "./output";
export { SearchPattern } from "./search";
export type { SearchOptions, SearchField, SearchEncoding, FieldState } from "./search";
@@ -35,6 +35,13 @@ export function evaluate(program: A.Program, host: HexpatHost, hooks?: HostHooks
return inst;
}
/** Decode one type at a host offset, preparing types and constants only. */
export function decodeType(program: A.Program, type: string, host: HexpatHost, offset = 0, hooks?: HostHooks): PatternInstance {
const inst = new PatternInstance(program, new HostMemory(host), hooks, host.print ? (s) => host.print!(s) : undefined);
inst.placeType(type, offset);
return inst;
}
/** Parse and evaluate a hexpat source. Synchronous. */
export function runHexpat(src: string, host: HexpatHost, opts: RunOptions = {}): PatternInstance {
return evaluate(compile(src, host, opts.name, opts.defines), host, opts.hooks);
+60 -2
View File
@@ -8,6 +8,11 @@ export interface JsonOptions {
maxEntries?: number; // cap array entries (default: all)
}
export interface TreeOptions extends JsonOptions {
color?: boolean; // color rows with ANSI truecolor escapes (default: off)
patterns?: Pattern[]; // selected roots (default: all placed patterns)
}
export interface PatternJson {
name: string;
type: string;
@@ -15,6 +20,10 @@ export interface PatternJson {
addr: number;
offset: number;
size: number;
displayName?: string;
color?: string;
comment?: string;
attributes?: { name: string; args: any[] }[];
local?: boolean;
section?: number;
bits?: number;
@@ -46,6 +55,10 @@ export function patternToJson(inst: PatternInstance, p: Pattern, opts: JsonOptio
size: p.size,
};
if (p.local) j.local = true;
if (p.displayName !== undefined) j.displayName = p.displayName;
if (p.getColor() !== undefined) j.color = p.getColor();
if (p.comment !== undefined) j.comment = p.comment;
if (p.attrs && p.attrs.length) j.attributes = attributes(inst, p);
if (p.section !== MAIN_SECTION && !p.local) j.section = p.section;
if (p.kind === "bitfield_field") { j.bits = p.bits; j.bitOffset = p.bitPos % 8; }
if (p.kind === "array") {
@@ -74,12 +87,57 @@ export function toJson(inst: PatternInstance, opts: JsonOptions = {}): PatternJs
return inst.patterns.filter((p) => !p.hidden).map((p) => patternToJson(inst, p, opts));
}
/** Evaluated, JSON-safe attribute arguments, including pattern-valued arguments. */
function attributes(inst: PatternInstance, p: Pattern): { name: string; args: any[] }[] {
return (p.attrs || []).map((a) => ({ name: a.name, args: a.args.map((v) => {
const value = inst.decay(v);
return value instanceof Pattern ? inst.toStr(value) : jsonValue(value);
}) }));
}
/** Hexpat's RRGGBB colors in a form usable by ANSI and r2's rgb: syntax. */
export function patternColor(p: Pattern): string | undefined {
const m = (p.getColor() || "").match(/^(?:#|0x|rgb:)?([0-9a-f]{6})$/i);
return m ? m[1].toLowerCase() : undefined;
}
/** Draw a tree with values, addresses and evaluated presentation attributes. */
export function dumpTree(inst: PatternInstance, print: (s: string) => void, opts: TreeOptions = {}): void {
const walk = (p: Pattern, prefix: string, branch: string): void => {
const attrs = attributes(inst, p).filter((a) => !["color", "comment", "name"].includes(a.name));
const presentation: [string, string | undefined][] = [["color", p.getColor()], ["comment", p.comment], ["name", p.displayName]];
for (const [name, value] of presentation) {
if (value !== undefined) attrs.push({ name, args: [value] });
}
let line = inst.describePattern(p, opts.base || 0);
if (attrs.length) line += " [[" + attrs.map((a) => a.name + (a.args.length ? "(" +
a.args.map((v) => JSON.stringify(v) || "null").join(", ") + ")" : "")).join(", ") + "]]";
const rgb = opts.color ? patternColor(p) : undefined;
if (rgb) line = "\x1b[38;2;" + [0, 2, 4].map((i) => parseInt(rgb.substring(i, i + 2), 16)).join(";") + "m" + line + "\x1b[0m";
print(prefix + branch + line);
if (p.sealed) return;
let children: Pattern[] = [];
let omitted = 0;
if (p.kind === "pointer") children = p.pointee ? [p.pointee] : [];
else if (p.kind === "array") {
const n = p.entryCount(), max = opts.maxEntries === undefined ? n : Math.min(n, opts.maxEntries);
for (let i = 0; i < max; i++) children.push(p.entry(i)!);
omitted = n - max;
} else if (p.isComposite()) children = p.children || [];
children = children.filter((c) => !c.hidden);
const next = prefix + (branch ? branch === "`- " ? " " : "| " : "");
children.forEach((c, i) => walk(c, next, i === children.length - 1 && !omitted ? "`- " : "|- "));
if (omitted) print(next + "`- ... (" + omitted + " more entries)");
};
for (const p of opts.patterns || inst.patterns) if (!p.hidden) walk(p, "", "");
}
/**
* Walk the placed patterns depth first. `path` holds the member names from
* the top-level pattern down (array entries use their index).
* Return false from `visit` to skip the children of a pattern.
*/
export function walkPatterns(inst: PatternInstance, visit: (p: Pattern, path: string[]) => boolean | void): void {
export function walkPatterns(inst: PatternInstance, visit: (p: Pattern, path: string[]) => boolean | void, patterns = inst.patterns): void {
const walk = (p: Pattern, path: string[]) => {
if (p.hidden) return;
if (visit(p, path) === false) return;
@@ -92,5 +150,5 @@ export function walkPatterns(inst: PatternInstance, visit: (p: Pattern, path: st
for (const c of p.children || []) walk(c, path.concat(c.name));
}
};
for (const p of inst.patterns) walk(p, [p.name]);
for (const p of patterns) walk(p, [p.name]);
}
+5
View File
@@ -67,6 +67,11 @@ export class Pattern {
return this.kind === "struct" || this.kind === "union" || this.kind === "array" || this.kind === "bitfield";
}
/** Explicit member colors override the enclosing aggregate's color. */
getColor(): string | undefined {
return this.color === undefined ? this.parent?.getColor() : this.color;
}
attr(name: string): { name: string; args: any[] } | undefined {
if (this.attrs) for (const a of this.attrs) if (a.name === name) return a;
return undefined;
+7 -1
View File
@@ -230,7 +230,13 @@ const BUILTINS: Record<string, Fn> = {
"std::core::get_attribute_value": (ev, a) => BUILTINS["std::core::get_attribute_argument"](ev, a, ev.loc),
"std::core::set_display_name": (ev, a) => { if (a[0] instanceof Pattern) a[0].name = ev.toStr(ev.decay(a[1])); },
"std::core::set_pattern_comment": (ev, a) => { if (a[0] instanceof Pattern) a[0].comment = ev.toStr(ev.decay(a[1])); },
"std::core::set_pattern_color": () => undefined,
"std::core::set_pattern_color": (ev, a) => {
if (a[0] instanceof Pattern) {
// The runtime's integer colors store red in the least significant byte.
const color = uintN(32, ev.toInt(a[1]));
a[0].color = [0n, 8n, 16n].map((shift) => Number((color >> shift) & 0xffn).toString(16).padStart(2, "0")).join("");
}
},
"std::core::set_endian": (ev, a) => { const e = ev.toInt(a[0]); ev.bigEndian = e === 1n; },
"std::core::get_endian": (ev) => (ev.bigEndian ? 1n : 2n),
"std::core::array_index": (ev) => ev.arrayIndex,
+861
View File
@@ -0,0 +1,861 @@
// Game Boy / Game Boy Color cartridge and CPU memory layouts.
// Reference: https://gbdev.io/pandocs/ (CC0 hardware documentation).
// Header: https://gbdev.io/pandocs/The_Cartridge_Header.html
// I/O: https://gbdev.io/pandocs/Hardware_Reg_List.html
// Memory: https://gbdev.io/pandocs/Memory_Map.html
// Mappers: https://gbdev.io/pandocs/MBCs.html
// Plugin: https://github.com/radareorg/r2hexpat
// ROM: r2 -q -c 'hexpat gameboy.hexpat' game.gb
// Apply flags, data hints and comments: .hexpat* gameboy.hexpat
// JSON: hexpatj gameboy.hexpat
// Set GB_CPU_MEMORY to 1 ONLY for a flat 64 KiB CPU address-space dump.
// A ROM file's offsets above 0x7FFF are more ROM banks, not CPU RAM or I/O.
// CPU dumps expose only the currently selected ROM/VRAM/WRAM/cartridge bank.
// This maps data; it does not emulate devices or reconstruct bank state.
#pragma endian little
#pragma base_address 0
#define GB_CPU_MEMORY 0
// Bitfields below run from least significant bit to most significant bit.
// CGB flag bit 7 enables color mode; 0xC0 conventionally means CGB-only.
// 0x00 requests DMG compatibility. 0xCE and 0xC3 are NOT DMG/CGB markers.
enum CgbFlag : u8 {
DmgCompatible = 0x00 ... 0x7F,
CgbEnhanced = 0x80,
CgbOnly = 0xC0
};
enum SgbFlag : u8 {
NoSgb = 0x00,
SgbSupported = 0x03
};
enum CartridgeType : u8 {
RomOnly = 0x00,
Mbc1 = 0x01,
Mbc1Ram = 0x02,
Mbc1RamBattery = 0x03,
Mbc2 = 0x05,
Mbc2Battery = 0x06,
RomRam = 0x08,
RomRamBattery = 0x09,
Mmm01 = 0x0B,
Mmm01Ram = 0x0C,
Mmm01RamBattery = 0x0D,
Mbc3TimerBattery = 0x0F,
Mbc3TimerRamBattery = 0x10,
Mbc3 = 0x11,
Mbc3Ram = 0x12,
Mbc3RamBattery = 0x13,
Mbc5 = 0x19,
Mbc5Ram = 0x1A,
Mbc5RamBattery = 0x1B,
Mbc5Rumble = 0x1C,
Mbc5RumbleRam = 0x1D,
Mbc5RumbleRamBattery = 0x1E,
Mbc6 = 0x20,
Mbc7SensorRumbleRamBattery = 0x22,
PocketCamera = 0xFC,
BandaiTama5 = 0xFD,
HuC3 = 0xFE,
HuC1RamBattery = 0xFF
};
enum RomSize : u8 {
KiB32_Banks2 = 0x00,
KiB64_Banks4 = 0x01,
KiB128_Banks8 = 0x02,
KiB256_Banks16 = 0x03,
KiB512_Banks32 = 0x04,
MiB1_Banks64 = 0x05,
MiB2_Banks128 = 0x06,
MiB4_Banks256 = 0x07,
MiB8_Banks512 = 0x08,
Unofficial_Banks72 = 0x52,
Unofficial_Banks80 = 0x53,
Unofficial_Banks96 = 0x54
};
// 0x01 is a legacy unofficial 2 KiB code, unused by licensed cartridges.
// MBC2 has 512 four-bit RAM cells despite its header RAM size being zero.
enum RamSize : u8 {
None = 0x00,
Unused_LegacyKiB2 = 0x01,
KiB8_Banks1 = 0x02,
KiB32_Banks4 = 0x03,
KiB128_Banks16 = 0x04,
KiB64_Banks8 = 0x05
};
enum Destination : u8 {
Japan = 0x00,
Overseas = 0x01
};
// New codes are two ASCII bytes; read this enum big-endian.
enum NewLicenseeCode : u16 {
None_00 = 0x3030,
Nintendo_Research_Development_1_01 = 0x3031,
Capcom_08 = 0x3038,
EA_Electronic_Arts_13 = 0x3133,
Hudson_Soft_18 = 0x3138,
B_AI_19 = 0x3139,
KSS_20 = 0x3230,
Planning_Office_WADA_22 = 0x3232,
PCM_Complete_24 = 0x3234,
San_X_25 = 0x3235,
Kemco_28 = 0x3238,
SETA_Corporation_29 = 0x3239,
Viacom_30 = 0x3330,
Nintendo_31 = 0x3331,
Bandai_32 = 0x3332,
Ocean_Software_Acclaim_Entertainment_33 = 0x3333,
Konami_34 = 0x3334,
HectorSoft_35 = 0x3335,
Taito_37 = 0x3337,
Hudson_Soft_38 = 0x3338,
Banpresto_39 = 0x3339,
Ubi_Soft_41 = 0x3431,
Atlus_42 = 0x3432,
Malibu_Interactive_44 = 0x3434,
Angel_46 = 0x3436,
Bullet_Proof_Software_47 = 0x3437,
Irem_49 = 0x3439,
Absolute_50 = 0x3530,
Acclaim_Entertainment_51 = 0x3531,
Activision_52 = 0x3532,
Sammy_USA_Corporation_53 = 0x3533,
Konami_54 = 0x3534,
Hi_Tech_Expressions_55 = 0x3535,
LJN_56 = 0x3536,
Matchbox_57 = 0x3537,
Mattel_58 = 0x3538,
Milton_Bradley_Company_59 = 0x3539,
Titus_Interactive_60 = 0x3630,
Virgin_Games_Ltd_61 = 0x3631,
Lucasfilm_Games_64 = 0x3634,
Ocean_Software_67 = 0x3637,
EA_Electronic_Arts_69 = 0x3639,
Infogrames_70 = 0x3730,
Interplay_Entertainment_71 = 0x3731,
Broderbund_72 = 0x3732,
Sculptured_Software_73 = 0x3733,
The_Sales_Curve_Limited_75 = 0x3735,
THQ_78 = 0x3738,
Accolade_79 = 0x3739,
Misawa_Entertainment_80 = 0x3830,
LOZC_G_83 = 0x3833,
Tokuma_Shoten_86 = 0x3836,
Tsukuda_Original_87 = 0x3837,
Chunsoft_Co_91 = 0x3931,
Video_System_92 = 0x3932,
Ocean_Software_Acclaim_Entertainment_93 = 0x3933,
Varie_95 = 0x3935,
Yonezawa_S_Pal_96 = 0x3936,
Kaneko_97 = 0x3937,
Pack_In_Video_99 = 0x3939,
Bottom_Up_9H = 0x3948,
Konami_Yu_Gi_Oh_A4 = 0x4134,
MTO_BL = 0x424C,
Kodansha_DK = 0x444B
};
enum OldLicenseeCode : u8 {
None_00 = 0x00,
Nintendo_01 = 0x01,
Capcom_08 = 0x08,
HOT_B_09 = 0x09,
Jaleco_0A = 0x0A,
Coconuts_Japan_0B = 0x0B,
Elite_Systems_0C = 0x0C,
EA_Electronic_Arts_13 = 0x13,
Hudson_Soft_18 = 0x18,
ITC_Entertainment_19 = 0x19,
Yanoman_1A = 0x1A,
Japan_Clary_1D = 0x1D,
Virgin_Games_Ltd_1F = 0x1F,
PCM_Complete_24 = 0x24,
San_X_25 = 0x25,
Kemco_28 = 0x28,
SETA_Corporation_29 = 0x29,
Infogrames_30 = 0x30,
Nintendo_31 = 0x31,
Bandai_32 = 0x32,
UseNewLicensee_33 = 0x33,
Konami_34 = 0x34,
HectorSoft_35 = 0x35,
Capcom_38 = 0x38,
Banpresto_39 = 0x39,
Entertainment_Interactive_3C = 0x3C,
Gremlin_3E = 0x3E,
Ubi_Soft_41 = 0x41,
Atlus_42 = 0x42,
Malibu_Interactive_44 = 0x44,
Angel_46 = 0x46,
Spectrum_HoloByte_47 = 0x47,
Irem_49 = 0x49,
Virgin_Games_Ltd_4A = 0x4A,
Malibu_Interactive_4D = 0x4D,
U_S_Gold_4F = 0x4F,
Absolute_50 = 0x50,
Acclaim_Entertainment_51 = 0x51,
Activision_52 = 0x52,
Sammy_USA_Corporation_53 = 0x53,
GameTek_54 = 0x54,
Park_Place_55 = 0x55,
LJN_56 = 0x56,
Matchbox_57 = 0x57,
Milton_Bradley_Company_59 = 0x59,
Mindscape_5A = 0x5A,
Romstar_5B = 0x5B,
Naxat_Soft_5C = 0x5C,
Tradewest_5D = 0x5D,
Titus_Interactive_60 = 0x60,
Virgin_Games_Ltd_61 = 0x61,
Ocean_Software_67 = 0x67,
EA_Electronic_Arts_69 = 0x69,
Elite_Systems_6E = 0x6E,
Electro_Brain_6F = 0x6F,
Infogrames_70 = 0x70,
Interplay_Entertainment_71 = 0x71,
Broderbund_72 = 0x72,
Sculptured_Software_73 = 0x73,
The_Sales_Curve_Limited_75 = 0x75,
THQ_78 = 0x78,
Accolade_79 = 0x79,
Triffix_Entertainment_7A = 0x7A,
MicroProse_7C = 0x7C,
Kemco_7F = 0x7F,
Misawa_Entertainment_80 = 0x80,
LOZC_G_83 = 0x83,
Tokuma_Shoten_86 = 0x86,
Bullet_Proof_Software_8B = 0x8B,
Vic_Tokai_Corp_8C = 0x8C,
Ape_Inc_8E = 0x8E,
I_Max_8F = 0x8F,
Chunsoft_Co_91 = 0x91,
Video_System_92 = 0x92,
Tsubaraya_Productions_93 = 0x93,
Varie_95 = 0x95,
Yonezawa_S_Pal_96 = 0x96,
Kemco_97 = 0x97,
Arc_99 = 0x99,
Nihon_Bussan_9A = 0x9A,
Tecmo_9B = 0x9B,
Imagineer_9C = 0x9C,
Banpresto_9D = 0x9D,
Nova_9F = 0x9F,
Hori_Electric_A1 = 0xA1,
Bandai_A2 = 0xA2,
Konami_A4 = 0xA4,
Kawada_A6 = 0xA6,
Takara_A7 = 0xA7,
Technos_Japan_A9 = 0xA9,
Broderbund_AA = 0xAA,
Toei_Animation_AC = 0xAC,
Toho_AD = 0xAD,
Namco_AF = 0xAF,
Acclaim_Entertainment_B0 = 0xB0,
ASCII_Corporation_or_Nexsoft_B1 = 0xB1,
Bandai_B2 = 0xB2,
Square_Enix_B4 = 0xB4,
HAL_Laboratory_B6 = 0xB6,
SNK_B7 = 0xB7,
Pony_Canyon_B9 = 0xB9,
Culture_Brain_BA = 0xBA,
Sunsoft_BB = 0xBB,
Sony_Imagesoft_BD = 0xBD,
Sammy_Corporation_BF = 0xBF,
Taito_C0 = 0xC0,
Kemco_C2 = 0xC2,
Square_C3 = 0xC3,
Tokuma_Shoten_C4 = 0xC4,
Data_East_C5 = 0xC5,
Tonkin_House_C6 = 0xC6,
Koei_C8 = 0xC8,
UFL_C9 = 0xC9,
Ultra_Games_CA = 0xCA,
VAP_Inc_CB = 0xCB,
Use_Corporation_CC = 0xCC,
Meldac_CD = 0xCD,
Pony_Canyon_CE = 0xCE,
Angel_CF = 0xCF,
Taito_D0 = 0xD0,
SOFEL_Software_Engineering_Lab_D1 = 0xD1,
Quest_D2 = 0xD2,
Sigma_Enterprises_D3 = 0xD3,
ASK_Kodansha_Co_D4 = 0xD4,
Naxat_Soft_D6 = 0xD6,
Copya_System_D7 = 0xD7,
Banpresto_D9 = 0xD9,
Tomy_DA = 0xDA,
LJN_DB = 0xDB,
Nippon_Computer_Systems_DD = 0xDD,
Human_Ent_DE = 0xDE,
Altron_DF = 0xDF,
Jaleco_E0 = 0xE0,
Towa_Chiki_E1 = 0xE1,
Yutaka_E2 = 0xE2,
Varie_E3 = 0xE3,
Epoch_E5 = 0xE5,
Athena_E7 = 0xE7,
Asmik_Ace_Entertainment_E8 = 0xE8,
Natsume_E9 = 0xE9,
King_Records_EA = 0xEA,
Atlus_EB = 0xEB,
Epic_Sony_Records_EC = 0xEC,
IGS_EE = 0xEE,
A_Wave_F0 = 0xF0,
Extreme_Entertainment_F3 = 0xF3
};
// All three title layouts occupy the same 16 bytes; no reliable auto-detection.
struct CgbTitle {
char text[15];
CgbFlag cgb_flag;
};
struct ManufacturerTitle {
char text[11];
char manufacturer_code[4];
CgbFlag cgb_flag;
};
union CartridgeTitle {
char dmg_text[16];
CgbTitle cgb;
ManufacturerTitle manufacturer;
};
union NewLicensee {
char ascii[2];
be NewLicenseeCode publisher;
};
struct GameBoyHeader {
u8 entry_point_code[4]; // 0100: instructions, not an integer pointer.
u8 nintendo_logo[48]; // 0104: boot logo, ends at 0133.
CartridgeTitle title; // 0134: overlapping DMG/CGB title layouts.
NewLicensee new_licensee; // 0144: used only when old_licensee == 0x33.
SgbFlag sgb_flag; // 0146: SGB also requires old_licensee 0x33.
CartridgeType cartridge_type; // 0147.
RomSize rom_size; // 0148.
RamSize ram_size; // 0149.
Destination destination; // 014A.
OldLicenseeCode old_licensee; // 014B.
u8 rom_version; // 014C.
u8 header_checksum; // 014D: subtract each byte 0134..014C plus 1.
be u16 global_checksum; // 014E: sum ROM bytes excluding 014E..014F.
};
// These slots contain code, not address pointers; unused vectors may hold data.
struct GameBoyVectors {
u8 rst_00[8];
u8 rst_08[8];
u8 rst_10[8];
u8 rst_18[8];
u8 rst_20[8];
u8 rst_28[8];
u8 rst_30[8];
u8 rst_38[8];
u8 interrupt_vblank_40[8];
u8 interrupt_lcd_stat_48[8];
u8 interrupt_timer_50[8];
u8 interrupt_serial_58[8];
u8 interrupt_joypad_60[8];
};
// Register layouts describe bit positions, not read/write side effects.
// Unused bits often read as 1; write-only bits cannot be recovered from a dump.
enum TimerClock : u8 {
Hz4096 = 0,
Hz262144 = 1,
Hz65536 = 2,
Hz16384 = 3
};
enum PpuMode : u8 {
HBlank = 0,
VBlank = 1,
OamScan = 2,
Drawing = 3
};
enum DmgShade : u8 {
White = 0,
LightGray = 1,
DarkGray = 2,
Black = 3
};
enum DutyCycle : u8 {
Percent12_5 = 0,
Percent25 = 1,
Percent50 = 2,
Percent75 = 3
};
enum WaveLevel : u8 {
Mute = 0,
Full = 1,
Half = 2,
Quarter = 3
};
bitfield Joypad {
a_or_right_released : 1;
b_or_left_released : 1;
select_or_up_released : 1;
start_or_down_released : 1;
dpad_deselected : 1; // 0 selects directions.
buttons_deselected : 1; // 0 selects buttons; all inputs active-low.
padding : 2;
};
bitfield SerialControl {
internal_clock : 1;
cgb_fast_clock : 1;
padding : 5;
transfer_requested : 1;
};
bitfield TimerControl {
TimerClock clock : 2; // Normal-speed rates; double on CGB double speed.
enabled : 1;
padding : 5;
};
bitfield Interrupts {
vblank : 1;
lcd_stat : 1;
timer : 1;
serial : 1;
joypad : 1;
padding : 3;
};
bitfield AudioSweep {
shift : 3;
subtract : 1;
pace : 3;
padding : 1;
};
bitfield AudioDutyLength {
initial_length_timer : 6; // Length = 64 - value; write-only portion.
DutyCycle duty : 2;
};
bitfield AudioEnvelope {
pace : 3;
increase : 1;
initial_volume : 4;
};
bitfield AudioPeriodHigh {
period_high : 3;
padding : 3;
length_enabled : 1;
trigger : 1; // Write 1 to trigger; not a readable state bit.
};
bitfield WaveDac {
padding : 7;
enabled : 1;
};
bitfield WaveOutputLevel {
padding : 5;
WaveLevel level : 2;
padding : 1;
};
bitfield NoiseLength {
initial_length_timer : 6;
padding : 2;
};
bitfield NoisePolynomial {
clock_divider : 3; // Divisors: 8,16,32,48,64,80,96,112.
short_lfsr : 1; // 1: 7-bit; 0: 15-bit.
clock_shift : 4;
};
bitfield NoiseControl {
padding : 6;
length_enabled : 1;
trigger : 1;
};
bitfield AudioVolume {
right_volume : 3;
vin_right : 1;
left_volume : 3;
vin_left : 1;
};
bitfield AudioPanning {
channel1_right : 1;
channel2_right : 1;
channel3_right : 1;
channel4_right : 1;
channel1_left : 1;
channel2_left : 1;
channel3_left : 1;
channel4_left : 1;
};
bitfield AudioMaster {
channel1_active : 1; // Bits 0..3 read-only.
channel2_active : 1;
channel3_active : 1;
channel4_active : 1;
padding : 3;
audio_enabled : 1;
};
bitfield WaveSamples {
second_sample : 4;
first_sample : 4;
};
bitfield LcdControl {
bg_window_enable_or_cgb_priority : 1;
objects_enabled : 1;
objects_8x16 : 1;
bg_map_9c00 : 1;
tile_data_8000_unsigned : 1; // 0: signed IDs relative to 9000.
window_enabled : 1;
window_map_9c00 : 1;
lcd_enabled : 1;
};
bitfield LcdStatus {
PpuMode mode : 2; // Read-only.
ly_equals_lyc : 1; // Read-only.
hblank_interrupt : 1;
vblank_interrupt : 1;
oam_interrupt : 1;
lyc_interrupt : 1;
padding : 1;
};
bitfield DmgPalette {
DmgShade color0 : 2; // Ignored for OBP0/OBP1: OBJ index 0 is transparent.
DmgShade color1 : 2;
DmgShade color2 : 2;
DmgShade color3 : 2;
};
bitfield CpuMode {
padding : 2;
dmg_compatibility : 1; // Boot-ROM-only; locked when boot ROM is unmapped.
unknown : 5;
};
bitfield SpeedSwitch {
switch_armed : 1; // Arm, then execute STOP.
padding : 6;
double_speed : 1; // Read-only.
};
bitfield VramBank {
bank : 1;
padding : 7;
};
bitfield VramDmaControl {
blocks_minus_one : 7; // Transfer length = (value + 1) * 16 bytes.
hblank_on_write_inactive_on_read : 1;
};
bitfield Infrared {
led_on : 1;
no_signal : 1; // Read-only, 0 means receiving.
padding : 4;
read_enable : 2; // 3 enables receiver; 0 disables it.
};
bitfield PaletteIndex {
byte_index : 6;
padding : 1;
auto_increment_on_write : 1;
};
bitfield ObjectPriority {
dmg_x_priority : 1; // 0: CGB OAM order; 1: DMG x-coordinate order.
padding : 7;
};
bitfield WramBank {
bank : 3; // 0 aliases bank 1.
padding : 5;
};
bitfield Undocumented75 {
padding : 4;
storage : 3;
padding : 1;
};
bitfield Pcm12 {
channel1 : 4;
channel2 : 4;
};
bitfield Pcm34 {
channel3 : 4;
channel4 : 4;
};
// Offsets below are relative to FF00; gaps preserve exact register addresses.
// CGB-only fields are retained in DMG dumps for address consistency.
struct GameBoyIo {
Joypad JOYP; // FF00: joypad / SGB packet transport.
u8 SB; // FF01: serial data.
SerialControl SC; // FF02.
u8 unused_ff03;
u8 DIV; // FF04: any write resets divider.
u8 TIMA; // FF05: timer counter.
u8 TMA; // FF06: timer reload value.
TimerControl TAC; // FF07.
u8 unused_ff08_ff0e[7];
Interrupts IF; // FF0F: requested interrupts.
AudioSweep NR10; // FF10: channel 1.
AudioDutyLength NR11; // FF11.
AudioEnvelope NR12; // FF12.
u8 NR13; // FF13: period low, write-only.
AudioPeriodHigh NR14; // FF14.
u8 unused_ff15;
AudioDutyLength NR21; // FF16: channel 2.
AudioEnvelope NR22; // FF17.
u8 NR23; // FF18: period low, write-only.
AudioPeriodHigh NR24; // FF19.
WaveDac NR30; // FF1A: channel 3.
u8 NR31; // FF1B: length = 256 - value, write-only.
WaveOutputLevel NR32; // FF1C.
u8 NR33; // FF1D: period low, write-only.
AudioPeriodHigh NR34; // FF1E.
u8 unused_ff1f;
NoiseLength NR41; // FF20: channel 4.
AudioEnvelope NR42; // FF21.
NoisePolynomial NR43; // FF22.
NoiseControl NR44; // FF23.
AudioVolume NR50; // FF24.
AudioPanning NR51; // FF25.
AudioMaster NR52; // FF26.
u8 unused_ff27_ff2f[9];
WaveSamples wave_ram[16]; // FF30..FF3F: 32 four-bit samples.
LcdControl LCDC; // FF40.
LcdStatus STAT; // FF41.
u8 SCY; // FF42: scroll y.
u8 SCX; // FF43: scroll x.
u8 LY; // FF44: scanline, read-only.
u8 LYC; // FF45: scanline compare.
u8 DMA; // FF46: write source high byte; copies 160 bytes to OAM.
DmgPalette BGP; // FF47: non-CGB background palette.
DmgPalette OBP0; // FF48: non-CGB object palette 0.
DmgPalette OBP1; // FF49: non-CGB object palette 1.
u8 WY; // FF4A: window y.
u8 WX; // FF4B: window x + 7.
CpuMode KEY0; // FF4C: CGB.
SpeedSwitch KEY1; // FF4D: CGB.
u8 unused_ff4e;
VramBank VBK; // FF4F: CGB.
u8 BOOT; // FF50: boot ROM unmap, write-only.
u8 HDMA1; // FF51: CGB DMA source high, write-only.
u8 HDMA2; // FF52: source low, low nibble ignored, write-only.
u8 HDMA3; // FF53: destination bits 12..8, write-only.
u8 HDMA4; // FF54: destination bits 7..4, write-only.
VramDmaControl HDMA5; // FF55: CGB.
Infrared RP; // FF56: CGB infrared.
u8 unused_ff57_ff67[17];
PaletteIndex BCPS; // FF68: CGB BG palette index (BGPI).
u8 BCPD; // FF69: CGB BG palette byte (BGPD).
PaletteIndex OCPS; // FF6A: CGB OBJ palette index (OBPI).
u8 OCPD; // FF6B: CGB OBJ palette byte (OBPD).
ObjectPriority OPRI; // FF6C: CGB.
u8 unused_ff6d_ff6f[3];
WramBank SVBK; // FF70: CGB.
u8 unused_ff71;
u8 undocumented_ff72; // CGB storage, R/W.
u8 undocumented_ff73; // CGB storage, R/W.
u8 undocumented_ff74; // CGB-mode storage, R/W.
Undocumented75 undocumented_ff75;
Pcm12 PCM12; // FF76: CGB channel 1/2 output, read-only.
Pcm34 PCM34; // FF77: CGB channel 3/4 output, read-only.
u8 unused_ff78_ff7f[8];
};
// Graphics structures can also be placed manually over known ROM asset offsets.
struct TileRow2bpp {
u8 low_plane; // Bit 7 is the leftmost pixel.
u8 high_plane; // Pixel index = low bit | (high bit << 1).
};
struct Tile2bpp {
TileRow2bpp rows[8];
};
bitfield BgMapAttributes {
cgb_palette : 3;
tile_vram_bank : 1;
padding : 1;
x_flip : 1;
y_flip : 1;
bg_priority : 1;
};
union TileMap {
u8 tile_ids[1024]; // VRAM bank 0: 32 rows of 32 tile IDs.
BgMapAttributes attributes[1024]; // VRAM bank 1: corresponding CGB attributes.
};
struct GameBoyVram {
Tile2bpp tiles[384]; // 8000..97FF.
TileMap map_9800;
TileMap map_9c00;
};
bitfield ObjectAttributes {
cgb_palette : 3;
cgb_tile_bank : 1;
dmg_palette_1 : 1;
x_flip : 1;
y_flip : 1;
behind_bg_colors_1_to_3 : 1;
};
struct OamObject {
u8 y_plus_16;
u8 x_plus_8;
u8 tile_index; // In 8x16 mode, bit 0 is ignored.
ObjectAttributes attributes;
};
bitfield Rgb555 {
red : 5;
green : 5;
blue : 5;
padding : 1;
};
struct CgbPalette {
Rgb555 colors[4];
};
// Not directly CPU-mapped: dump separately through BGPI/BGPD or OBPI/OBPD.
// Each palette RAM bank contains eight palettes (64 bytes).
struct CgbPaletteRam {
CgbPalette palettes[8];
};
// MBC control registers are WRITE destinations, not readable ROM structures.
// These enums are reference constants; no control state is overlaid onto ROM.
// Other cartridge types above need their own mapper-specific device protocols.
enum Mbc1WriteAddress : u16 {
RamEnable_0000_1fff = 0x0000, // Low nibble 0xA enables; anything else disables.
RomBankLow5_2000_3fff = 0x2000, // 0 aliases 1 before masking to ROM size.
BankHigh2_4000_5fff = 0x4000, // Upper ROM bits or RAM bank, depends on wiring.
BankingMode_6000_7fff = 0x6000 // 0: simple; 1: advanced RAM/ROM banking.
};
enum Mbc1Mode : u8 {
Simple = 0,
Advanced = 1
};
enum RamEnableValue : u8 {
Disable = 0x00,
Enable = 0x0A
};
// MBC2 decodes address bit 8 throughout 0000..3FFF, not two contiguous ranges.
enum Mbc2WriteAddress : u16 {
RamEnable_AddressBit8Clear = 0x0000,
RomBankLow4_AddressBit8Set = 0x2100 // 0 aliases 1; RAM cells at A000..A1FF, mirrored.
};
enum Mbc3WriteAddress : u16 {
RamRtcEnable_0000_1fff = 0x0000,
RomBank7_2000_3fff = 0x2000, // 0 aliases 1 (MBC30 uses 8 bits).
RamRtcSelect_4000_5fff = 0x4000,
LatchClock_6000_7fff = 0x6000 // Write 0, then 1, to latch RTC.
};
enum Mbc3RamRtcSelect : u8 {
RamBank0 = 0x00,
RamBank1 = 0x01,
RamBank2 = 0x02,
RamBank3 = 0x03,
Mbc30RamBank4 = 0x04,
Mbc30RamBank5 = 0x05,
Mbc30RamBank6 = 0x06,
Mbc30RamBank7 = 0x07,
RtcSeconds = 0x08,
RtcMinutes = 0x09,
RtcHours = 0x0A,
RtcDayLow = 0x0B,
RtcDayHigh = 0x0C
};
bitfield RtcDayHigh {
day_bit8 : 1;
padding : 5;
halted : 1;
day_overflow : 1;
};
// A convenient five-byte export layout, NOT five adjacent CPU registers.
// Only the selected RTC register appears at A000..BFFF, repeated at every byte.
struct Mbc3RtcSnapshot {
u8 seconds;
u8 minutes;
u8 hours;
u8 day_low;
RtcDayHigh day_high;
};
enum Mbc5WriteAddress : u16 {
RamEnable_0000_1fff = 0x0000,
RomBankLow8_2000_2fff = 0x2000, // Bank 0 is allowed.
RomBankBit8_3000_3fff = 0x3000,
RamBank4_4000_5fff = 0x4000 // Rumble carts: bit 3 motor, bits 0..2 RAM bank.
};
bitfield Mbc5RumbleRamBank {
ram_bank : 3;
rumble_on : 1;
padding : 4;
};
// A raw ROM bank N starts at file offset N * 0x4000.
// Its usual CPU address is 0x4000 + (file_offset % 0x4000) for N > 0.
struct RomBank {
u8 bytes[0x4000];
};
if (GB_CPU_MEMORY) {
std::assert (std::mem::size () >= 0x10000, "CPU memory mode requires a 64 KiB dump");
GameBoyVectors vectors @ 0x0000;
GameBoyHeader header @ 0x0100;
u8 rom0_before_header[0x98] @ 0x0068;
u8 rom0_after_header[0x3EB0] @ 0x0150;
RomBank selected_rom_bank @ 0x4000;
GameBoyVram vram @ 0x8000;
u8 cartridge_ram_or_device[0x2000] @ 0xA000;
u8 wram_bank0[0x1000] @ 0xC000;
u8 selected_wram_bank[0x1000] @ 0xD000;
u8 echo_c000_ddff[0x1E00] @ 0xE000;
OamObject oam[40] @ 0xFE00;
u8 unusable_fea0_feff[0x60] @ 0xFEA0;
GameBoyIo io @ 0xFF00;
u8 hram[0x7F] @ 0xFF80;
Interrupts IE @ 0xFFFF;
} else {
std::assert (std::mem::size () >= 0x150, "ROM is too short for a Game Boy header");
GameBoyVectors vectors @ 0x0000;
GameBoyHeader header @ 0x0100;
}
+92 -11
View File
@@ -3,7 +3,8 @@
* plugin.ts - radare2 core plugin (r2js) running the hexpat library.
* R2Host implements the library's HexpatHost interface on top of r2 commands.
*/
import { compile, evaluate, pragma, toJson, walkPatterns, HexpatHost, PatternError, PatternInstance, Pattern, SearchPattern } from "../lib/index";
import { compile, evaluate, decodeType, pragma, toJson, patternToJson, dumpTree, patternColor, walkPatterns,
HexpatHost, PatternError, PatternInstance, Pattern, SearchPattern } from "../lib/index";
import * as A from "../lib/ast";
/** Host accessing r2 I/O (`p8`/`wx`), translated by a base address. */
@@ -73,7 +74,7 @@ export class R2Host implements HexpatHost {
const VERSION = "0.3.0";
const HELP = [
"Usage: hexpat[?+-jl*espm] [args] # ImHex pattern language",
"Usage: hexpat[?+-jl*espmt] [args] # ImHex pattern language",
"| hexpat [-q] [-I dir] [file] evaluate file (replacing the loaded ones) and show its patterns",
"| hexpat show the patterns of the loaded files",
"| hexpat+ [-I dir] file evaluate file and add it to the loaded ones",
@@ -81,11 +82,13 @@ const HELP = [
"| hexpat-v show the r2hexpat version",
"| hexpatj [file] show the patterns as JSON",
"| hexpat* [file] show the patterns as r2 commands (flags, comments, data hints)",
"| hexpat-* show r2 commands to remove generated flags (.hexpat-* applies them)",
"| hexpatl[j] list the loaded files and their top-level patterns",
"| hexpats [file] one-line summary (types, fields, patterns) or the error, for status bars",
"| hexpate expr evaluate an expression or assignment in the last loaded file (writes r2 I/O)",
"| hexpatp[?+=-ijl] ... create partial instances and masked search patterns (hexpatp? for help)",
"| hexpatm [options] Type print a libmagic decoder for a fixed type (hexpatm? for help)",
"| hexpatt[j*] [options] [Type [address]|pattern] view a type at an address or a loaded pattern as a tree",
"| hexpat? show this help",
"| options: -q (evaluate only), -I dir (add #include / import search path)",
"| file can be 'base64:<data>' to evaluate the encoded source instead of reading a file",
@@ -126,6 +129,19 @@ const MAGIC_HELP = [
"| decode: pm /tmp/macho.magic @ 0 (increase block size with b if the header is larger)",
];
const TREE_HELP = [
"Usage: hexpatt[j*] [options] [Type [address]|pattern] # address-bound pattern tree",
"| hexpatt tree of loaded patterns, with values, colors and attributes",
"| hexpatt pattern tree of a loaded pattern (nested paths also work)",
"| hexpatt [-f file] [-I dir] Type [address] decode a type at an r2 address (default: current seek)",
"| hexpattj ... JSON including color, comment, displayName and attributes",
"| hexpatt* ... r2 commands for flags, flag colors/comments and data hints",
"| types: last loaded file or -f file; only types and constants are loaded, not placements or main()",
"| layouts: hexpat declarations, including data-dependent arrays; addresses honor base_address",
"| examples: hexpatt -f demo/player.hexpat Player @ 0x100; hexpatt header",
"| apply/remove flags: .hexpatt* Player @ 0x100; .hexpat-*",
];
/** Collect output lines and print them through r_cons at once (so ~grep and | work). */
class Out {
lines: string[] = [];
@@ -148,11 +164,12 @@ function word(src: string): { value: string; rest: string } {
return { value, rest: "" };
}
function patternOptions(src: string): { file: string; includeDirs: string[]; alignment?: number; rest: string } {
function patternOptions(src: string, allowAlignment = true): { file: string; includeDirs: string[]; alignment?: number; rest: string } {
const opts = { file: "", includeDirs: [] as string[], alignment: undefined as number | undefined, rest: src.trim() };
while (opts.rest.startsWith("-")) {
const flag = word(opts.rest), arg = word(flag.rest);
if (!["-f", "--file", "-I", "-a", "--align"].includes(flag.value)) throw new Error("unknown layout option '" + flag.value + "'");
const allowed = allowAlignment ? ["-f", "--file", "-I", "-a", "--align"] : ["-f", "--file", "-I"];
if (!allowed.includes(flag.value)) throw new Error("unknown layout option '" + flag.value + "'");
if (!arg.value) throw new Error("missing value for " + flag.value);
if (flag.value === "-f" || flag.value === "--file") opts.file = arg.value;
else if (flag.value === "-I") opts.includeDirs.push(arg.value);
@@ -249,6 +266,57 @@ function magicCommand(rest: string, out: Out): void {
out.push(p.toMagic());
}
function treeCommand(tail: string, out: Out): void {
const mode = tail[0] && "j*?".includes(tail[0]) ? tail[0] : "";
const rest = (mode ? tail.substring(1) : tail).trim();
if (mode === "?" || rest === "-h" || rest === "--help") {
for (const line of TREE_HELP) out.push(line);
return;
}
const opts = patternOptions(rest, false), arg = word(opts.rest);
let targets: { session: Session; patterns: Pattern[] }[];
if (!arg.value) {
if (opts.file || opts.includeDirs.length) throw new Error("missing type; see hexpatt?");
targets = sessions.map((session) => ({ session, patterns: session.instance.patterns }));
} else {
const last = sessions[sessions.length - 1];
let p: Pattern | undefined;
if (!opts.file && !arg.rest && last) {
p = last.instance.get(arg.value);
if (!p && /^[a-zA-Z_]\w*[.\[]/.test(arg.value)) {
const v = last.instance.evaluateExpression(arg.value);
if (!(v instanceof Pattern)) throw new Error("tree requires a pattern");
p = v;
}
}
if (p) {
if (opts.includeDirs.length) throw new Error("-I requires -f when compiling types");
targets = [{ session: last, patterns: [p] }];
} else {
const program = searchProgram(opts.file, opts.includeDirs);
const host = new R2Host(r2, programBase(program));
host.quiet = true;
const number = r2.cmdj("?j " + (arg.rest || "$$"));
const address = Number(number ? number.uint64 : undefined);
if (!Number.isSafeInteger(address) || address < 0) throw new Error("invalid address");
const instance = decodeType(program, arg.value, host, address - host.baseAddress);
targets = [{ session: { file: opts.file || last.file, program, instance, host }, patterns: instance.patterns }];
}
}
if (mode === "j") {
out.push(JSON.stringify(targets.flatMap(({ session: s, patterns }) =>
patterns.filter((p) => !p.hidden).map((p) => patternToJson(s.instance, p, { base: s.host.baseAddress })))));
} else if (mode === "*") {
out.push("fs+hexpat");
for (const { session, patterns } of targets) r2script(session, out, patterns);
out.push("fs-");
} else {
const color = Number(r2.cmd("e scr.color").trim()) > 0;
for (const { session: s, patterns } of targets) dumpTree(s.instance, out.push, { base: s.host.baseAddress, color, patterns });
if (!targets.length) out.push("no patterns loaded, run 'hexpatt?' for help");
}
}
function parseArgs(args: string[]): { quiet: boolean; includeDirs: string[]; file: string } {
const r = { quiet: false, includeDirs: [] as string[], file: "" };
for (let i = 0; i < args.length; i++) {
@@ -260,6 +328,11 @@ function parseArgs(args: string[]): { quiet: boolean; includeDirs: string[]; fil
return r;
}
function programBase(program: A.Program): number {
const ba = (pragma(program, "base_address") || "").match(/0x[0-9a-fA-F]+|\d+/);
return ba ? parseInt(ba[0]) : R2Host.binBaseAddress(r2);
}
function load(file: string, includeDirs: string[], quiet = false): Session {
const host = new R2Host(r2);
const inline = file.startsWith(B64);
@@ -269,8 +342,7 @@ function load(file: string, includeDirs: string[], quiet = false): Session {
host.includeDirs = includeDirs;
host.quiet = quiet;
const program = compile(src || "", host, file);
const ba = (pragma(program, "base_address") || "").match(/0x[0-9a-fA-F]+|\d+/);
host.baseAddress = ba ? parseInt(ba[0]) : R2Host.binBaseAddress(r2);
host.baseAddress = programBase(program);
return { file, program, instance: evaluate(program, host), host };
}
@@ -306,13 +378,16 @@ function commentValue(inst: PatternInstance, p: Pattern): string {
return inst.formatPattern(p, true);
}
function r2script(s: Session, out: Out): void {
function r2script(s: Session, out: Out, patterns = s.instance.patterns): void {
const inst = s.instance;
const base = s.host.baseAddress;
walkPatterns(inst, (p: Pattern, path: string[]) => {
if (p.local || p.section !== 0) return false;
const addr = "0x" + (base + p.offset).toString(16);
out.push("f " + flagName(path) + " " + Math.max(1, p.size) + " @ " + addr);
const name = flagName(path), color = patternColor(p);
out.push("f " + name + " " + Math.max(1, p.size) + " @ " + addr);
if (color) out.push("fc " + name + "=rgb:" + color);
if (p.comment) out.push("fC " + name + " " + sanitize(printable(p.comment, 256)));
if (p.kind === "string" || p.kind === "wstring") {
out.push("Cs " + p.size + " @ " + addr);
out.push("CCu " + sanitize(p.typeName + " " + p.name + " = " + printable(String(inst.value(p)))) + " @ " + addr);
@@ -332,7 +407,7 @@ function r2script(s: Session, out: Out): void {
out.push("CCu " + sanitize((t ? t + " " : "") + p.name + " = " + commentValue(inst, p)) + " @ " + addr);
}
return true;
});
}, patterns);
}
function list(out: Out, json: boolean): void {
@@ -410,7 +485,12 @@ function hexpatCommand(cmd: string): void {
break;
}
case "-": {
if (rest === "v") out.push(VERSION);
if (cmd.charAt(7) === "*") {
if (rest !== "*") throw new Error("Usage: hexpat-*");
out.push("fs+hexpat");
out.push("f-hexpat.*");
out.push("fs-");
} else if (rest === "v") out.push(VERSION);
else if (rest === "" || rest === "*") sessions.length = 0;
else {
const n = parseInt(rest, 10);
@@ -456,6 +536,7 @@ function hexpatCommand(cmd: string): void {
}
case "p": patternCommand(cmd.substring(7), out); break;
case "m": magicCommand(rest, out); break;
case "t": treeCommand(cmd.substring(7), out); break;
default:
for (const l of HELP) out.push(l);
}
@@ -479,7 +560,7 @@ function hexpatCommand(cmd: string): void {
call: function (cmd: string) {
if (!cmd.startsWith("hexpat")) return false;
const c = cmd.charAt(6);
if (c !== "" && " ?h+-j*lespm".indexOf(c) < 0) return false;
if (c !== "" && " ?h+-j*lespmt".indexOf(c) < 0) return false;
hexpatCommand(cmd);
return true;
},
+3 -1
View File
@@ -1,5 +1,5 @@
-- help
Usage: hexpat[?+-jl*espm] [args] # ImHex pattern language
Usage: hexpat[?+-jl*espmt] [args] # ImHex pattern language
| hexpat [-q] [-I dir] [file] evaluate file (replacing the loaded ones) and show its patterns
| hexpat show the patterns of the loaded files
| hexpat+ [-I dir] file evaluate file and add it to the loaded ones
@@ -7,11 +7,13 @@ Usage: hexpat[?+-jl*espm] [args] # ImHex pattern language
| hexpat-v show the r2hexpat version
| hexpatj [file] show the patterns as JSON
| hexpat* [file] show the patterns as r2 commands (flags, comments, data hints)
| hexpat-* show r2 commands to remove generated flags (.hexpat-* applies them)
| hexpatl[j] list the loaded files and their top-level patterns
| hexpats [file] one-line summary (types, fields, patterns) or the error, for status bars
| hexpate expr evaluate an expression or assignment in the last loaded file (writes r2 I/O)
| hexpatp[?+=-ijl] ... create partial instances and masked search patterns (hexpatp? for help)
| hexpatm [options] Type print a libmagic decoder for a fixed type (hexpatm? for help)
| hexpatt[j*] [options] [Type [address]|pattern] view a type at an address or a loaded pattern as a tree
| hexpat? show this help
| options: -q (evaluate only), -I dir (add #include / import search path)
| file can be 'base64:<data>' to evaluate the encoded source instead of reading a file
+60
View File
@@ -0,0 +1,60 @@
?e -- decode types without loading a session
hexpatt -f tests/tree_types.pat Header @ 0
hexpattj -f tests/tree_types.pat Header 8
hexpatlj
?e -- current seek and address expressions
s 8
hexpatt -f tests/tree_types.pat Header
f tree.address 1 @ 8
hexpatt -f tests/tree_types.pat Header tree.address
?e -- base address, inline sources and include directories
omv 1 0x1000
hexpattj -f base64:I3ByYWdtYSBiYXNlX2FkZHJlc3MgMHgxMDAwCiNpbmNsdWRlIDx0cmVlX3R5cGVzLnBhdD4K -I tests Header 0x1008
omv 1 0
?e -- loaded pattern trees and one-shot types preserve the session
hexpat -q tests/syntax_structs.hexpat
hexpatt file.head
hexpatt file.data[0]
hexpattj file.head.version
hexpatt Header 4
hexpatl
?e -- generated flag colors and comments
hexpatt* -f tests/tree_types.pat Header 0
fs other
f other.keep 1 @ 0
.hexpatt* -f tests/tree_types.pat Header 0
fs
fs hexpat
fc*
fC hexpat.value.count
?e -- regular flag export carries colors too
hexpat* base64:c3RydWN0IENvbG9yIHsgdTggdGFnIFtbY29tbWVudCgiY29sb3JlZCBmaWVsZCIpXV07IH0gW1tjb2xvcigiQUJDREVGIildXTsgQ29sb3IgY29sb3JlZCBAIDA7
.hexpat*
fc*
hexpate std::core::set_pattern_color(colored, 0x00332211)
hexpatt colored
.hexpat*
fc*
?e -- cleanup preview and application preserve sessions and unrelated flags
hexpat-*
f keep 1 @ 8
fs other
f hexpat.outside 1 @ 0
.hexpat-*
f
fs hexpat
f
hexpatl
?e -- cleanup also works after unloading
hexpat-
.hexpatt* -f tests/tree_types.pat Header 8
hexpat-
.hexpat-*
f
?e -- errors
hexpatt -f tests/tree_types.pat
hexpatt -f tests/tree_types.pat Missing 0
hexpatt -f tests/tree_types.pat Header -1
hexpatt -f tests/tree_types.pat Header 0x100000
hexpatt -f tests/tree_types.pat Header no.such.address
hexpatt Header
+134
View File
@@ -0,0 +1,134 @@
-- decode types without loading a session
value (Header) @ 0x00000000 size=4 [[color("1144AA")]]
|- count (u8) @ 0x00000000 = 124 (0x7c) [[color("FF0000"), comment("entry count")]]
|- Details (Inner) @ 0x00000001 size=2 [[color("00FF00"), name("Details")]]
| `- word (u16) @ 0x00000001 = 5415 (0x1527) [[color("00FF00")]]
`- data (u8[1]) @ 0x00000003 size=1 [[color("1144AA")]]
`- [0] (u8) @ 0x00000003 = 25 (0x19) [[color("1144AA")]]
[{"name":"value","type":"Header","kind":"struct","addr":8,"offset":8,"size":5,"color":"1144AA","attributes":[{"name":"color","args":["1144AA"]}],"children":[{"name":"count","type":"u8","kind":"unsigned","addr":8,"offset":8,"size":1,"color":"FF0000","comment":"entry count","attributes":[{"name":"color","args":["FF0000"]},{"name":"comment","args":["entry count"]}],"value":131},{"name":"inner","type":"Inner","kind":"struct","addr":9,"offset":9,"size":2,"displayName":"Details","color":"00FF00","attributes":[{"name":"color","args":["00FF00"]},{"name":"name","args":["Details"]}],"children":[{"name":"word","type":"u16","kind":"unsigned","addr":9,"offset":9,"size":2,"color":"00FF00","value":48272}]},{"name":"data","type":"u8[2]","kind":"array","addr":11,"offset":11,"size":2,"color":"1144AA","count":2,"entries":[{"name":"[0]","type":"u8","kind":"unsigned","addr":11,"offset":11,"size":1,"color":"1144AA","value":164},{"name":"[1]","type":"u8","kind":"unsigned","addr":12,"offset":12,"size":1,"color":"1144AA","value":66}]}]}]
[]
-- current seek and address expressions
value (Header) @ 0x00000008 size=5 [[color("1144AA")]]
|- count (u8) @ 0x00000008 = 131 (0x83) [[color("FF0000"), comment("entry count")]]
|- Details (Inner) @ 0x00000009 size=2 [[color("00FF00"), name("Details")]]
| `- word (u16) @ 0x00000009 = 48272 (0xbc90) [[color("00FF00")]]
`- data (u8[2]) @ 0x0000000b size=2 [[color("1144AA")]]
|- [0] (u8) @ 0x0000000b = 164 (0xa4) [[color("1144AA")]]
`- [1] (u8) @ 0x0000000c = 66 (0x42) [[color("1144AA")]]
value (Header) @ 0x00000008 size=5 [[color("1144AA")]]
|- count (u8) @ 0x00000008 = 131 (0x83) [[color("FF0000"), comment("entry count")]]
|- Details (Inner) @ 0x00000009 size=2 [[color("00FF00"), name("Details")]]
| `- word (u16) @ 0x00000009 = 48272 (0xbc90) [[color("00FF00")]]
`- data (u8[2]) @ 0x0000000b size=2 [[color("1144AA")]]
|- [0] (u8) @ 0x0000000b = 164 (0xa4) [[color("1144AA")]]
`- [1] (u8) @ 0x0000000c = 66 (0x42) [[color("1144AA")]]
-- base address, inline sources and include directories
[{"name":"value","type":"Header","kind":"struct","addr":4104,"offset":8,"size":5,"color":"1144AA","attributes":[{"name":"color","args":["1144AA"]}],"children":[{"name":"count","type":"u8","kind":"unsigned","addr":4104,"offset":8,"size":1,"color":"FF0000","comment":"entry count","attributes":[{"name":"color","args":["FF0000"]},{"name":"comment","args":["entry count"]}],"value":131},{"name":"inner","type":"Inner","kind":"struct","addr":4105,"offset":9,"size":2,"displayName":"Details","color":"00FF00","attributes":[{"name":"color","args":["00FF00"]},{"name":"name","args":["Details"]}],"children":[{"name":"word","type":"u16","kind":"unsigned","addr":4105,"offset":9,"size":2,"color":"00FF00","value":48272}]},{"name":"data","type":"u8[2]","kind":"array","addr":4107,"offset":11,"size":2,"color":"1144AA","count":2,"entries":[{"name":"[0]","type":"u8","kind":"unsigned","addr":4107,"offset":11,"size":1,"color":"1144AA","value":164},{"name":"[1]","type":"u8","kind":"unsigned","addr":4108,"offset":12,"size":1,"color":"1144AA","value":66}]}]}]
-- loaded pattern trees and one-shot types preserve the session
head (Header) @ 0x00000000 size=16
|- magic (u32) @ 0x00000000 = 421991804 (0x1927157c)
|- version (u16) @ 0x00000004 = 62935 (0xf5d7)
|- flags (u16) @ 0x00000006 = 51282 (0xc852)
`- timestamp (u64) @ 0x00000008 = 13032463231746096259 (0xb4dc9c42a490bc83)
[0] (u8) @ 0x00000015 = 126 (0x7e)
[{"name":"version","type":"u16","kind":"unsigned","addr":4,"offset":4,"size":2,"value":62935}]
value (Header) @ 0x00000004 size=16
|- magic (u32) @ 0x00000004 = 3360880087 (0xc852f5d7)
|- version (u16) @ 0x00000008 = 48259 (0xbc83)
|- flags (u16) @ 0x0000000a = 42128 (0xa490)
`- timestamp (u64) @ 0x0000000c = 1165859946296548418 (0x102df771b4dc9c42)
0 tests/syntax_structs.hexpat (2 patterns)
0x00000000 22 file (File)
0x00000016 4 footer (u32)
-- generated flag colors and comments
fs+hexpat
f hexpat.value 4 @ 0x0
fc hexpat.value=rgb:1144aa
f hexpat.value.count 1 @ 0x0
fc hexpat.value.count=rgb:ff0000
fC hexpat.value.count entry count
Cd 1 @ 0x0
CCu u8 count = 0x7c @ 0x0
f hexpat.value.inner 2 @ 0x1
fc hexpat.value.inner=rgb:00ff00
f hexpat.value.inner.word 2 @ 0x1
fc hexpat.value.inner.word=rgb:00ff00
Cd 2 @ 0x1
CCu u16 word = 0x1527 @ 0x1
f hexpat.value.data 1 @ 0x3
fc hexpat.value.data=rgb:1144aa
Cd 1 1 @ 0x3
fs-
rgb:1144aa
rgb:ff0000
rgb:00ff00
rgb:00ff00
rgb:1144aa
0 . classes
0 . format
5 . hexpat
1 * other
0 . sections
0 . segments
0 . symbols
0 . trycatch
'fc hexpat.value=rgb:1144aa
'fc hexpat.value.count=rgb:ff0000
'fc hexpat.value.inner=rgb:00ff00
'fc hexpat.value.inner.word=rgb:00ff00
'fc hexpat.value.data=rgb:1144aa
entry count
-- regular flag export carries colors too
fs+hexpat
f hexpat.colored 1 @ 0x0
fc hexpat.colored=rgb:abcdef
f hexpat.colored.tag 1 @ 0x0
fc hexpat.colored.tag=rgb:abcdef
fC hexpat.colored.tag colored field
Cd 1 @ 0x0
CCu u8 tag = 0x7c @ 0x0
fs-
rgb:abcdef
rgb:abcdef
'fc hexpat.value=rgb:1144aa
'fc hexpat.value.count=rgb:ff0000
'fc hexpat.colored=rgb:abcdef
'fc hexpat.colored.tag=rgb:abcdef
'fc hexpat.value.inner=rgb:00ff00
'fc hexpat.value.inner.word=rgb:00ff00
'fc hexpat.value.data=rgb:1144aa
colored (Color) @ 0x00000000 size=1 [[color("112233")]]
`- tag (u8) @ 0x00000000 = 124 (0x7c) [[color("112233"), comment("colored field")]]
rgb:112233
rgb:112233
'fc hexpat.value=rgb:1144aa
'fc hexpat.value.count=rgb:ff0000
'fc hexpat.colored=rgb:112233
'fc hexpat.colored.tag=rgb:112233
'fc hexpat.value.inner=rgb:00ff00
'fc hexpat.value.inner.word=rgb:00ff00
'fc hexpat.value.data=rgb:1144aa
-- cleanup preview and application preserve sessions and unrelated flags
fs+hexpat
f-hexpat.*
fs-
0x00000000 1 other.keep
0x00000000 1 hexpat.outside
0x00000008 1 keep
0 base64 (1 patterns)
0x00000000 1 colored (Color)
-- cleanup also works after unloading
rgb:1144aa
rgb:ff0000
rgb:00ff00
rgb:00ff00
rgb:1144aa
0x00000008 1 keep
-- errors
ERROR: unknown symbol
hexpat: missing type; see hexpatt?
hexpat: error: unknown type 'Missing' (<expr>:1:1)
hexpat: invalid address
hexpat: error: type offset is outside the data (line 0:0)
hexpat: invalid address
hexpat: no types loaded: use -f file or load a pattern with hexpat
+8
View File
@@ -0,0 +1,8 @@
struct Inner { be u16 word; } [[color("00FF00")]];
struct Header {
u8 count [[color("FF0000"), comment("entry count")]];
Inner inner [[name("Details")]];
u8 data[count % 3];
} [[color("1144AA")]];
std::assert(false, "tree commands must not execute top-level statements");
fn main() { std::assert(false, "tree commands must not run main()"); };
+59
View File
@@ -0,0 +1,59 @@
import { compile, decodeType, BufferHost, dumpTree, toJson, patternColor } from "../lib/index";
export function testTree(): void {
const check = (ok: boolean, msg: string): void => { if (!ok) throw new Error("tree: " + msg); };
const program = compile(`
const u8 N = 2;
struct Inner { u16 word; } [[color("00FF00")]];
struct Header {
u8 count [[color("FF0000"), comment("entry count")]];
Inner inner [[name("Details"), tag(count)]];
u8 data[count];
padding[1];
u8 secret [[hidden]];
Inner closed [[sealed]];
} [[color("1144AA")]];
struct Array<T, auto Count> { T entries[Count]; };
Header unrelated @ 10000;
std::assert(false, "type decoder must not execute top-level statements");
fn main() { std::assert(false, "type decoder must not call main"); };
`);
const data = new Uint8Array([99, 99, 2, 0x34, 0x12, 1, 2, 0, 0x78, 0x56, 3, 0]);
const inst = decodeType(program, "Header", new BufferHost(data), 2);
const root = inst.get("value")!;
check(inst.patterns.length === 1 && root.offset === 2 && root.size === 9, "placed layout");
check(inst.evaluateExpression("value.inner.word + 0") === 0x1234n, "decoded value");
check(root.member("data")!.entryCount() === 2, "data-dependent array length");
check(patternColor(root.member("data")!.entry(0)!) === "1144aa", "array color inheritance");
check(patternColor(root.member("inner")!.member("word")!) === "00ff00", "member type color override");
const json = toJson(inst, { base: 0x1000 })[0];
check(json.addr === 0x1002 && json.color === "1144AA", "JSON address/color");
check(json.children![0].comment === "entry count" && json.children![1].displayName === "Details", "JSON presentation metadata");
check(json.children![1].attributes!.find((a) => a.name === "tag")!.args[0] === 2, "pattern-valued attribute serialization");
check(json.children![2].entries![1].color === "1144AA", "JSON array entry color");
const lines: string[] = [];
dumpTree(inst, (line) => lines.push(line), { base: 0x1000 });
const tree = lines.join("\n");
check(tree.includes('value (Header) @ 0x00001002 size=9 [[color("1144AA")]]'), "root row");
check(tree.includes('|- Details (Inner)') && tree.includes('| `- word (u16) @ 0x00001003 = 4660 (0x1234)'), "nested tree");
check(tree.includes('tag(2)') && tree.includes('comment("entry count")'), "tree attributes");
check(!tree.includes("secret") && !tree.includes("word (u16) @ 0x00001009"), "hidden/sealed patterns");
check(!tree.includes("\x1b"), "plain output contains ANSI escapes");
const limited: string[] = [];
dumpTree(inst, (line) => limited.push(line), { patterns: [root.member("data")!], maxEntries: 1, color: true });
check(limited[0].includes("\x1b[38;2;17;68;170m") && limited[2] === "`- ... (1 more entries)", "colored/limited tree");
inst.evaluateExpression("std::core::set_pattern_color(value.data, 0x00efcdab)");
check(patternColor(root.member("data")!.entry(0)!) === "abcdef", "runtime ABGR color");
check(toJson(inst)[0].children![2].color === "abcdef", "runtime color serialization");
check(decodeType(program, "Array<u16, N>", new BufferHost(data)).patterns[0].size === 4, "template constants");
const conflict = decodeType(compile("const u8 value = 2; struct H { u8 bytes[value]; };"), "H", new BufferHost(data));
check(conflict.patterns[0].name === "value_" && conflict.patterns[0].size === 2, "root collides with a constant");
for (const offset of [-1, 1.5, data.length + 1, Number.MAX_SAFE_INTEGER + 1]) {
let failed = false;
try { decodeType(program, "Header", new BufferHost(data), offset); } catch (e) {
failed = e instanceof Error && e.message.includes("outside the data");
}
check(failed, "invalid offset " + offset);
}
console.log("[test] Address-bound trees OK");
}
+2
View File
@@ -2,6 +2,7 @@
import { runHexpat, BufferHost } from "../lib/index";
import { testSearchPatterns } from "./search_unit";
import { testMagic } from "./magic_unit";
import { testTree } from "./tree_unit";
const src = `
struct Header {
@@ -158,3 +159,4 @@ check(editData[0] === 11, "rejected field write modified host bytes");
console.log("[test] Interactive writes OK");
testSearchPatterns();
testMagic();
testTree();
+2
View File
@@ -8,6 +8,7 @@ import { runHexpat, BufferHost } from "../lib/index";
import { VIRTUAL_SOURCES, HOOKS } from "./upstream_hooks";
import { testSearchPatterns } from "./search_unit";
import { testMagic } from "./magic_unit";
import { testTree } from "./tree_unit";
const size = JSON.parse(r2.cmd("ij")).core.size;
const hex = r2.cmd("p8 " + size + " @ 0").replace(/[^0-9a-fA-F]/g, "");
@@ -39,3 +40,4 @@ console.log("==================================");
console.log("r2: " + passed + "/" + files.length + " passed" + (unexpected.length ? ", unexpected failures: " + unexpected.join(" ") : ""));
try { testSearchPatterns(); } catch (e) { console.log("FAIL partial search patterns: " + String(e)); }
try { testMagic(); } catch (e) { console.log("FAIL magic export: " + String(e)); }
try { testTree(); } catch (e) { console.log("FAIL address-bound trees: " + String(e)); }