- Introduced a new command 'annotate-usecode' to import USECODE IR JSON annotation hints as Ghidra comments on compiled anchors. - Added argument parsing for multiple IR JSON files, comment type selection, and a dry-run option. - Implemented logic to read annotation records from the provided IR files and set comments on the corresponding addresses in Ghidra. - Enhanced JSON schema to include response structure for the new command.
34 KiB
Raw 000e: Parser & RIFF/Animation Clusters
Content extracted from crusader_decompilation_notes.md. Covers the 000e: segment parser helper cluster and the RIFF/AVI animation streaming subsystem.
Raw 000e Parser Helper Cluster
A small helper cluster in the raw 000e: area implements a fixed-size CRLF record parser/table builder, likely used by startup/config or script-ish text data.
Newly renamed helpers
| Address | Name |
|---|---|
000e:345e |
record_table_init |
000e:34cc |
record_table_destroy |
000e:35c6 |
record_table_release_buffer |
000e:35ef |
record_table_next_slot |
000e:3639 |
record_table_parse_buffer |
000e:3798 |
record_parser_read_line |
000e:38a0 |
record_parser_seek_next_marker |
000e:38f8 |
record_parser_find_marker |
000e:39cc |
record_parser_dispatch_at_directive |
Behavior notes
record_table_initclears the table header and zeroes 300 words of inline storage.record_table_parse_bufferwalks a CRLF-separated text buffer, captures each line, splits around a marker helper path, and stores parsed entry state into0x0c-byte records.record_parser_read_lineadvances to the next CRLF-delimited line, rejects lines that start with@or with non-identifier punctuation, and terminates the line in-place with0.record_parser_seek_next_markerupdates the parser's current marker cursor at+0x18/+0x1aby callingrecord_parser_find_marker; returns1if another marker was found,0at end-of-data.record_parser_find_markerscans forward until an@marker or end-of-data; optionally consumes the remaining length from the parser state.record_parser_dispatch_at_directivereturns0unless the current substring begins with@; in the@case, it advances by 7 bytes and dispatches through a FAR thunk (0000:ffff).
EUSECODE.FLX extraction notes
USECODE/EUSECODE.FLXdoes not look like a loadable code image or plain text script. It is now validated as an indexed binary container.- Current table model:
- entry count at file offset
0x54 - entry table at
0x80 - 8-byte records:
<u32 data_offset, u32 declared_size> entry_count = 3074table_end = 0x6090, which matches the first non-zero payload offset403non-zero entries in the current file
- entry count at file offset
tools/extract_eusecode_flx.pynow parses the full validated table and emits all403non-zero entries underUSECODE/EUSECODE_extracted/, includingentry_index.tsv,descriptor_index.tsv,descriptor_neighborhoods.tsv,summary.json, per-chunk.bin, and.strings.txtsidecars.- The extractor now also carries the conservative owner-loaded class rule directly into machine-readable outputs:
class_layout_index.tsvrecordsobject_index,class_id, the raw bytes-8..11field, derivedcode_base_minus_one, andconservative_event_count, whileclass_event_index.tsvexpands parsed classes into raw 6-byte event rows with slot numbers, ScummVM event-name hints for0x00..0x1f, unresolved leading words, raw code-offset dwords, derived body-window columns, and conservative repeated-template status tags for the verified repeated families. - The extractor now emits one concrete generated per-class decompile artifact for the cleanest repeated lane too:
boot_family_decompile.md/.tsvrender the five_BOOTclasses slot-by-slot with raw row bytes, derived body windows, repeated-template status, and stable body digests. - The generated reports now expose lightweight descriptor summaries (
primary_label,field_names,field_tags) so the object lane can be searched by field grammar instead of only by raw names. - The extracted data now separates into at least two lanes:
- text-heavy records that fit the
000e:CRLF parser model, such asDATALINKmission/objective text andTEXTFIL1message banks - binary object/behavior descriptors whose sidecars expose object names and field names, such as
EVENT,NPCTRIG,CRUZTRIG,TRIGPAD,JELYHACK,JELYH2,SPECIAL,SURCAMNS, andSURCAMEW
- text-heavy records that fit the
- The descriptor lane also shows a repeatable tagged field trailer rather than raw trailing strings only. Current spot-checks show patterns like
69 xx 00 <name>and24 xx 02 <name>immediately before field names inNPCTRIG,CRUZTRIG,TRIGPAD,SPECIAL, andSFXTRIG. This is strong evidence that the field names belong to compact per-field metadata records, not accidental string leakage. - The strongest currently stable tag readings are:
69:0000 -> referent69:0A00 -> eventon event-capable classes such asEVENT,NPCTRIG,COR_BOOT,REE_BOOT,SFXTRIG,FLAMEBOX,NOSTRIL,VAR_BOOT, andSTEAMBOX24:FE02/24:FC02/24:FA02on object-reference-like fields such asitem,elev,door,source,dest,monster1,deadGuy, and related referent-style links24:0A02 -> eventTriggeronSURCAMNS/SURCAMEW
- The tag report is not a full type system yet, but it is already enough to separate scalar/event slots from pointer-like object links in many descriptor classes.
- Confirmed descriptor examples from the full index:
EVENT:referent,event,item,source,dest,door,counter,counter2,link,time,post1,post2,floor,flicManNPCTRIG:referent,event,item,item2,typeNpcCRUZTRIG:referent,item,elevTRIGPAD:referent,item,elevJELYHACK:referentJELYH2:referentSURCAMNS/SURCAMEW:referent,textFile,monit,valueBox,passcode,link,code,screen,cameraEgg,trueRef,therma,eventTrigger,foundGun
- Current immortality-lane status inside EUSECODE:
- the trigger/object namespace now clearly includes
JELYHACK,NPCTRIG,CRUZTRIG, andTRIGPAD JELYHACK/JELYH2sit in a local extraction neighborhood besideSPECIAL,TRIGPAD,DATALINK,HOFFMAN,REE_BOOT,SURCAMEW, andSFXTRIG, which looks more like a map/object grouping than random table order- that neighborhood does not make
JELYHACKitself event-bearing, but it does place it immediately beside multiple event-capable or trigger-adjacent classes (REE_BOOT,SFXTRIG,SURCAMEW.eventTrigger) - the requested descriptor-family sweep now sharpens the nearby callable-body picture too:
NPCTRIGis the only requested family here that is both explicitly event-bearing and non-empty inclass_event_index.tsv(equipat slot0x0a, plus anonymous slot0x20), whileSPECIAL,TRIGPAD, andREB_PADhave callable bodies but still look like state/controller or referent-neighbor records rather than direct event carriers - the new generated
immortality_target_body_scan.md/.tsvreport now scansEVENT,NPCTRIG,COR_BOOT,REE_BOOT,SFXTRIG,SPECIAL, andTRIGPADbody windows directly for inline little-endian0x0410, dword0x00000410, and byte-swapped0x1004 - that scan found zero literal hits in every currently targeted body, so no extracted target body is yet tied directly to event
0x410by immediate-value evidence - the
TELEPADslot-0x20row withraw_code_offset = 0x00000410inclass_event_index.tsvis now closed as an offset collision, not proof thatTELEPADemits gameplay event0x410 - the new body scan also narrows the frontier structurally:
EVENTremains one monolithic slot-0x0abody (8150bytes),NPCTRIGremains the strongest compact trigger frontier with slot0x0a(373bytes) plus slot0x20(345bytes), and_BOOTslot pairs (COR_BOOT/REE_BOOT) stay near-template bodies rather than unique immortality emitters SPECIALandTRIGPADare now stronger negative controls too: both still have callable bodies, but the new literal scan found no inline0x410evidence there either- the practical blocker is now narrower: the extractor no longer stops at body offsets only, but it still does not decode emitted payload values or bytecode operands inside the surviving
EVENTslot-0x0aandNPCTRIGslot-0x0a/0x20frontier bodies - one exact
0x410collision in compiled code is now explained away:000e:0953pushes0x410into importedASYLUM.27from the animation audio-subframe path immediately after setting the local audio-completion byte at+0xef1. SinceASYLUM.DLLis theASS_*audio/media library, treat this as a media ordinal/value collision rather than a second gameplay or USECODE event source. - the present best reading is that
0x410is likely carried by data relationships between generic event-capable descriptors (EVENT,NPCTRIG,SFXTRIG, etc.) and map/object references rather than by a plain-text script line
- the trigger/object namespace now clearly includes
- The
000e:record parser helpers still matter, but they now appear to cover only the text-oriented subset rather than the entire FLX payload. The strongest concrete caller so far is the raw window at000e:1b9f..1d49, whererecord_table_parse_bufferis invoked after setup of fields that match the known animation object layout (+0x117/+0x11b/+0x11f/+0x123,+0xeaf/+0xeb1,+0x10f/+0x111). That makes the currently verified000e:3639consumer part of the animation-object lane, not a clean standalone EUSECODE loader. - This shifts the current working model: treat
record_table_parse_bufferas a text/metadata helper used by at least one animation/resource object, while the EUSECODE binary descriptor lane is more likely consumed by the000dVM/object interpreter path. - That
000dpath is now materially less anonymous:- the global runtime object at
0x6611is now namedentity_vm_runtime_create/entity_vm_runtime_init_slots/entity_vm_runtime_release_slots/entity_vm_runtime_destroy - it owns the 0x80-entry slot table and a retained owner/resource object at
+0x1315/+0x1317 entity_vm_slot_index_from_entityandentity_vm_context_try_create_masked_for_entityshow that gameplay entities are filtered through one owner-side slot-mask table before a context is createdentity_vm_context_try_create_masked_for_entityis now better constrained too: after the owner-side mask check succeeds, an immediate-flagged context result clears the caller output word while an object-backed result returns the created object's low wordentity_vm_context_create_from_slot_indexthen seeds one0x6714context fromentity_vm_slot_load_value_plus_offset, while the large callers at000d:208band000d:21edcontinue by reading bytecode-like data from the seeded+0xd6/+0xd8lane
- the global runtime object at
- The context lane now also has a separate referent-registry subsystem:
entity_vm_set_field_da_to_globalwrites the current referent id to0x8c94from context field+0xdaand then enters the still-misaligned000c:3350bodyentity_vm_referent_registry_init/entity_vm_referent_registry_destroy/entity_vm_referent_registry_alloc/entity_vm_referent_registry_release_by_id/entity_vm_referent_registry_free_nodeshow that0x8c8c/0x8c8e/0x8c90/0x8c94implement one free-list-backed registry keyed by that current referent id- this is the first solid runtime mechanism showing how referent-only descriptors can still drive script state even when the actual event field lives in a separate neighboring descriptor
- the registry now also has a named chain container layer:
entity_vm_referent_chain_copy,entity_vm_referent_chain_append_unique_from,entity_vm_referent_chain_contains_entry,entity_vm_referent_chain_get_entry_data_at, andentity_vm_referent_chain_get_indirect_datashow that one referent can own copied/deduplicated payload chains with either inline fixed-size payloads or indirect string-like payloads
- That chain layer is now less one-sided than before:
entity_vm_referent_chain_remove_matching_from(000d:6a9a) removes entries from one chain when they match a second chain, using either inline compare or indirect string compare depending on the chain type byteentity_vm_referent_chain_set_entry_data_at(000d:6cf6) updates the payload of the Nth chain entry in place, freeing old indirect payload storage first when neededentity_vm_opcode_finish(000d:3350) is now identified as the common opcode epilogue that writes0x8c94from the current frame result and unwinds the temporary slot-array state before returning the opcode result
- That makes the emerging human-readable script model less ad hoc. A plausible future IR is now:
referent anchor -> payload chain(s) -> event-bearing attachment(s)rather than a flat list of isolated descriptor rows. - The opcode side now reinforces that IR too: at least one handler family around
000d:0988can either append unique payload entries or remove matching ones before returning through the same epilogue, which is a better fit for a graph-editing/object-attachment VM than for a pure linear trigger list. - That
000d:0988family is now classified more tightly at the opcode-id level:- opcode
0x19= append unique indirect/string-like payload entries into the referent chain - opcode
0x1a= remove matching indirect/string-like payload entries from the referent chain - opcode
0x1b= remove matching inline/fixed-size payload entries from the referent chain - the same helper body also implies the missing sibling
0x18as the inline/fixed-size append-unique form, because only0x19/0x1aset the indirect compare flag while only0x1a/0x1btake the removal path
- opcode
- The first concrete
000cto000dbridge inside that lane remainsentity_vm_set_value_from_slot_plus_offsetat000c:f95f: it callsentity_vm_slot_load_value_plus_offset, stores its return pair into object fields+0xd6/+0xd8, and sits immediately beside otherentity_vm_*helpers in the000c:f6b8..f9d9mini-VM cluster. On the000dside,entity_vm_slot_load_value_plus_offsetwrapsentity_vm_slot_load_value, but the oldPUSH 0x410suspicion at000d:5290is now rejected: that site reaches the seg091 fatal-report helper family at000a:44fd, not live gameplay dispatch. - The two main
000dcaller blocks beneath that bridge now have a first stable byte/value reading too:- internal block
000d:208bis the simple materialize-or-forward path: it creates one VM context from the caller's stream state, checks the returned object flags, and either writes the returned value pair straight to the caller output slot or forwards the created object's low word through the shared opcode epilogue - internal block
000d:21edis the inline-payload path: it creates the same VM context, prepends the caller-owned blob into the backward-growing context buffer at+0x102, then consumes two bytes from the seeded+0xd6/+0xd8lane as small shape/count metadata before building anentity_linkclosure matrix from the following caller-stream words and pushing back the non-0x0400results - that is the first concrete evidence that the
+0xd6/+0xd8lane is not only carrying immediate event/value ids; it also carries compact metadata bytes that parameterize larger inline payloads copied from the caller stream
- internal block
- Current JELYHACK implication: because
JELYHACKandJELYH2still expose onlyreferent, the most defensible model is now that they provide map/object identity into the referent-registry lane, while one adjacent event-capable record (REE_BOOT,SURCAMEW.eventTrigger,SFXTRIG.event, or another nearby genericEVENT/NPCTRIG) carries the actual event semantics that can eventually reach0x410. - The immediate runtime-owner writer is now pinned down one step further too.
entity_vm_runtime_create(000d:4c99) is the only verified writer of runtime+0x1315/+0x1317, and it does so by calling newly recoveredentity_vm_runtime_owner_resource_create(000d:7000). That helper does not simply copy a caller-supplied owner table: it constructs one embedded seg069/070 helper object, queries the needed table size through vtable+0x04, allocates child+0x10/+0x12, then fills the0x0d-stride per-slot producer records through vtable+0x0c. The paired release path isentity_vm_runtime_owner_resource_destroy(000d:70fd). - That narrows the owner/resource classification safely but still stops short of speculative source-format naming. The embedded helper goes through the same seg069/070 object lifecycle used by other file/resource-style helpers (
0009:1c00init,0009:1800destroy), so the most defensible current description is stillruntime owner/resource helperrather thanUSECODE file loaderor a descriptor-specific name. - The first gameplay-side mask families around
entity_vm_context_try_create_masked_for_entityare also now explicit from instruction evidence:- local wrapper
0004:f033passes slot mask0x8000:0007 FUN_0004_f05cpasses slot mask0x2000:0015and is reached from0004:f2b3after overlap/proximity checks plus entity byte+0x32state togglingFUN_0005_27a4passes slot mask0x0001:0000and is reached from the000c:a09eentity+0x5bbit-0x0004branch
- local wrapper
- Those masks are enough to prove that the runtime is exposing multiple gameplay-side materialization lanes into the same owner/resource table, but they are not yet enough to tie one lane specifically to the
JELYHACK/JELYH2anchor pair instead of the neighboring event-bearing descriptors (REE_BOOT,SURCAMEW,SFXTRIG, or another local trigger record). - The extractor now emits a first graph-oriented view of that claim too:
referent_anchor_event_graph.tsvgroups referent-bearing rows with nearby event-bearing neighbors, andjelyhack_island_graph.mdrenders theJELYHACK/JELYH2island as edges to local descriptors. On the current data, the strongest event-bearing neighbors in that island areREE_BOOT(event),SURCAMEW(eventTrigger), andSFXTRIG(event). - The new focused comparison report (
jelyhack_descriptor_compare.tsv) makes one more structural point explicit:JELYHACKandJELYH2have identical first 16 header words and the same lonereferentfield tag, while differing only in the label string and one small trailingwx[...]literal. That strengthens the reading that they are sibling referent-anchor classes rather than separate event-bearing behavior records. - The same comparison also helps separate anchor classes from event-bearing neighbors:
REE_BOOT,SURCAMEW, andSFXTRIGall carry materially richer header/state patterns thanJELYHACK/JELYH2, which is consistent with them holding actual trigger or attachment semantics beside the anchor-only classes. - The
000d:21edcallee chain is now tighter too. The nested call at0008:7d27isentity_link, which appends one entity id into another entity's word-list and, unless bit0x0400is set, also updates the reciprocal pair-link slots. So the22bc..2433opcode block is best understood as building a bidirectional entity-link closure matrix from streamed entity ids, not merely copying opaque words around. - Ghidra now carries that interpretation as a conservative disassembly comment at
000d:22bc, but not yet as a symbol rename, because the surrounding000d:208b/21ed/22bcregion is still mis-split into artificial function bodies. - The new
EVENT-focused reports (event_island_graph.md,event_descriptor_compare.tsv) broaden the descriptor-side picture beyond the JELYHACK anchor case. The strongest second island is the compact local cluster at indices186..195, whereCOR_BOOT,EVENT, andNPCTRIGall expose explicit69:0A00 -> eventtags whileROLL_NS,CRUZTRIG,NPC_ONLY, andVMAILstay on the referent/link/text side. - That cluster looks structurally different from JELYHACK in a useful way:
EVENTis the large hub payload (0x20AA) carryingsource,dest,door,link,time,counter,post1,post2,floor, andflicMan, whileCOR_BOOTandNPCTRIGare smaller event-bearing satellites and the surrounding records (ROLL_NS,CRUZTRIG,NPC_ONLY,VMAIL) look like attached state/trigger/object descriptors rather than alternate event cores. - The first compare pass on that island is already informative.
COR_BOOT,EVENT,CRUZTRIG,NPC_ONLY, andVMAILshare the same leading0x00000000dword class shape,NPCTRIGmoves to a nearby0x00000001shape, andROLL_NSis the obvious outlier with first dword0x00000002plus rider/time/cargo fields. So the present best reading is one three-node event-bearing core embedded inside a wider referent-neighbor island, not one flat run of equivalent trigger records. - The extractor now also emits a global event-family pass (
event_family_index.tsv,event_family_summary.md), which turns the local island findings into a wider descriptor taxonomy. Current validated families are:event-hub:EVENTboot-event-core:AND_BOOT,BRO_BOOT,COR_BOOT,VAR_BOOT,REE_BOOTnpc-trigger:NPCTRIGminimal-event-core:SFXTRIGenvironmental-event:FLAMEBOX,NOSTRIL,STEAMBOXcallback-eventtrigger:SURCAMNS,SURCAMEW
- That split matters because it is the first extractor-backed distinction between active event carriers and callback-only trigger holders. The
69:0A00 -> eventclasses now look like the active event-bearing core of the descriptor system, while the surveillance classes with24:0A02 -> eventTriggerare better treated as callback/attachment endpoints rather than peer event hubs. - The extractor now emits a stronger script-facing bridge artifact too:
runtime_descriptor_family_rankings.md/.tsvrank those descriptor families against the verified runtime lanes instead of only listing neighborhoods. Current best fit isEVENTas the strongest active-event payload lane,_BOOTcores andNPCTRIGas strong satellites,SFXTRIG/ environmental classes as moderate active-event fits,JELYHACK/JELYH2as the dedicated referent-anchor lane, andSURCAM*as structurally distinct callback/attachment holders. - That ranking is anchored by the current owner-loader evidence as well as the descriptor grammar:
000d:44df -> 000d:4c99 -> 000d:7000supplies the slot-backed source, and raw seg070 windows0009:67b6/0009:6916now show the embedded helper walking object+0x10/+0x18tables, formatting per-entry paths, and open/read/close-loading files before the0x0d-stride owner records are materialized. - The next focused pass tightened the
_BOOTlane too.boot_family_compare.tsvnow shows that all five_BOOTevent cores (AND_BOOT,BRO_BOOT,COR_BOOT,VAR_BOOT,REE_BOOT) share the same header skeleton and the same compact field shape (referent,event,counter,item). The meaningful differences are payload size and local neighborhood, not descriptor schema. - The new
boot_frontier_graph.mdmakes the best early_BOOTfrontier explicit:AND_BOOTandBRO_BOOTsit in one compact referent-heavy neighborhood (OFFWORK,GUARD,GDOOR_N,GDOOR_E,BIGCAN,CRUMORPH,GUARDSQ,CARD_NS,CARD_EW,EWALLEW/EWALLNS) and also point directly at each other as adjacent event-bearing siblings. So the present best reading is a reusable boot-event core template instantiated in several different local object islands, not a set of unrelated boot scripts. - The environmental hazard lane is now similarly constrained.
environmental_family_compare.tsvshows thatFLAMEBOXandSTEAMBOXare close structural siblings with the same active-event backbone (referent,event,<hazard>,<hazard2>,direction,count) and matching24:0A02 / 24:FC02 / 24:FE02object-link pattern, whileNOSTRILis a smaller fire-specific variant that keeps the activeeventplus dual fire references and count fields but drops the direction/newType side. - Their neighborhoods are different enough to matter:
environmental_event_graph.mdshowsFLAMEBOXembedded among vent/door/bridge/copy records,NOSTRILamong flame/pad/desk/blaster/keypad records, andSTEAMBOXamong bounce/hover/fade/steam/flame box records. So this looks like one hazard-event descriptor family reused across distinct local object islands rather than one single environmental mega-cluster. - The callback lane is tighter too.
callback_trigger_compare.tsvconfirms thatSURCAMNSandSURCAMEWare effectively the same callback-trigger template: identical field set (referent,textFile,monit,valueBox,passcode,link,code,screen,cameraEgg,trueRef,therma,eventTrigger,foundGun) and identical tag grammar except for thethermaslot offset (24:F102vs24:F602). That keeps theeventTriggersplit credible as a true callback/attachment lane rather than only a spelling variation on activeeventcarriers. - Mining the new
class_layout_index.tsv/class_event_index.tsvoutputs now gives a first small safe set of repeated non-zero slot patterns:JELYHACKandJELYH2are exact referent-anchor twins at the event-table level too: both have only slot0x01non-zero, with the same row0x002A / 0x00000001.- The five
_BOOTevent cores (AND_BOOT,BRO_BOOT,COR_BOOT,REE_BOOT,VAR_BOOT) all share the same three-slot pattern0x0A / 0x0F / 0x10. The clearest exact repeated row is slot0x10, where all five useraw_event_entry_word = 0x003Bwith class-specific code offsets. SURCAMNSandSURCAMEWshare one exact five-slot callback pattern0x01 / 0x0A / 0x20 / 0x21 / 0x22, including the same0x0A = 0x00D1 / 0x00000001anchor row and the same0x22event-table word0x01A3.FLAMEBOX,NOSTRIL, andSTEAMBOXshare one environmental-event pattern0x0A / 0x20 / 0x21, which is enough to treat the higher slots as real repeated structure even though the exact row values differ by class.EVENTandSFXTRIGboth participate in the wide0x0Alane, but that lane is broad enough that the slot number is currently more trustworthy than the ScummVM label attached to it.
- The next body-window pass now confirms that repeated slot rows are usually near-templates rather than clones. Using
body_start = code_base_minus_one + raw_code_offsetand the next non-zero slot offset or chunk EOF as the body end:JELYHACKandJELYH2slot0x01are both42bytes long with a shared10-byte prefix and17-byte suffix, but are not byte-identical._BOOTslot0x10is a clean short-template lane: all five bodies are exactly59bytes long, share the same first5bytes and last17bytes, but each has a distinct digest._BOOTslots0x0Aand0x0Fare larger variants of the same pattern: shared suffix-heavy structure, class-local middles, no exact clones.SURCAMNSandSURCAMEWslots0x20and0x22are same-length near-templates (698and419bytes respectively), while slot0x21diverges more strongly (1801vs1621bytes) even though it still keeps a common tail.
- That makes the current best human-readable script model more precise: preserve repeated-family status and exact row bytes, but record byte-identity as a separate property so “same slot template” does not get mistaken for “same compiled body.”
- That pattern pass materially improves what a decompiled USECODE script can look like right now. The honest current form is not a pretty source language; it is a reversible descriptor-plus-event-table rendering with raw slot ids, raw event-entry words, raw code offsets, and optional ScummVM labels marked as hints only. The concrete examples now live in
docs/usecode-roundtrip-ir.mdand are grounded inreadable_script_ir.md,readable_descriptor_templates.md, andruntime_descriptor_family_rankings.md. - The first runtime-side follow-through on those descriptor gains is now a little tighter too. Instruction search around
000d:ebe3confirms one fixed sequenced VM/opcode driver body, not just a vague constructor helper: it calls000d:177c,000d:1acb,000d:0988, the internal000d:22bclink-matrix block, then000d:1d4aand000d:2104in order. The key negative result is just as useful:000d:ec31is only the internalCALL 000d:22bcsite inside that body, not a standalone function entry. - Ghidra now carries that as a conservative disassembly comment at
000d:ebe3. That is still short of a safe rename, but it does promote the lane from “suspected constructor chain” to “verified ordered opcode/handler sequence,” which is the clearest current bridge from the descriptor-side event families back into the000dVM/object runtime.
Raw 000e RIFF/Animation Cluster
The 000e: segment contains a RIFF/AVI streaming animation subsystem.
Animation object field map
Field offsets relative to the object base pointer:
| Offset | Field |
|---|---|
+0xb0 |
active/valid flag |
+0xb4–+0xc2 |
constructor-initialized flags |
+0xd4 |
alive sentinel (must be -1 for "alive") |
+0xe4 |
paused flag (0 = running) |
+0xeaf/+0xeb1 |
far pointer to current RIFF chunk |
+0xedb |
animation frame stack depth counter (max 9) |
+0xee1 |
frame data from current chunk +4 |
+0xeef |
current subframe index |
+0x1b3 |
subframe count |
+0xef1 |
audio completion flag |
+0x11b |
ring buffer write pointer |
+0x11f |
ring buffer read pointer |
+0x117 |
ring buffer base |
+0x123 |
ring buffer end (capacity boundary) |
+0x102 |
resource pointer |
+0xde |
entry index (multiplied by 0x30 to reach per-entry data at +0x1c7) |
RIFF format notes
The game uses standard RIFF/IFF:
- LIST magic:
0x5453494c="LIST" - RIFF magic:
0x46464952="RIFF" "movi"FourCC subchunk for animation frames- Audio frames tagged
"01wb"(0x62773130) - Video frames handled through a separate path
Newly renamed functions
| Address | Name | Evidence |
|---|---|---|
000e:2a28 |
riff_find_chunk_by_type |
Walks RIFF LIST/RIFF chunk list; compares each node's FourCC at +8 vs param_2; returns pointer to matching chunk or NULL |
000e:2104 |
animation_start |
Finds "movi" chunk via riff_find_chunk_by_type, inits ring buffer ptrs at +0x11b from +0x117 + duration, calls animation_advance_frame, loops anim_load_audio_frame and a second frame-loader thunk path per subframe |
000e:12f4 |
animation_advance_frame |
Fixed-point 0x1000 timer arithmetic; checks +0xe4 (paused), advances ring buffer +0x11b/+0x11f/+0x117/+0x123; calls advance thunk |
000e:103f |
animation_tick |
Guard wrapper: checks param_1+0xd4 != -1, then calls animation_advance_frame(param_1, 0) |
000e:06f7 |
anim_load_audio_frame |
Checks chunk tag == 0x62773130 ("01wb" = audio stream 1); computes ring buffer free space; copies chunk payload via 0x0000:ffff thunk; increments subframe index at +0xeef; resets at subframe count +0x1b3 |
000e:053d |
anim_load_video_frame_wrapper |
Called once per subframe in animation_start immediately after anim_load_audio_frame; thin wrapper that forwards to 000e:ffb0 |
Unresolved callee
000e:ffb0remains unresolved (decompiles garbled due to overlapping instructions at000f:0085/000f:0086). Current evidence from theanimation_startloop suggests this path is the video-side subframe loader paired withanim_load_audio_frame.- The caller-side proof is now explicit enough to preserve that note in Ghidra too:
animation_startinvokesanim_load_video_frame_wrapperonce per active subframe immediately afteranim_load_audio_frame, andanim_load_video_frame_wrapperis only a thin forwarder to000e:ffb0. Until the overlap is repaired, the safest label remainsunresolved video-side subframe loader paired with the resolved audio-frame path. - A second caller pass tightens the local model without forcing a repair.
search_instructionsnow showsanim_load_video_frame_wrapperis also called at000e:11afand000e:1245, not only from the startup prime loop at000e:220c. In both of those additional windows the return value is checked as a success/failure result, which makes000e:ffb0look like an active chunk-consume/decode step rather than a passive notifier. - The strongest new evidence is the neighboring tag gate at
000e:121d..1234: afteranim_load_audio_frameruns, the same lane checks the current RIFF chunk tag against0x62643030/0x63643030("00db"/"00dc") before clearing the local busy flag and continuing. That is the first concrete caller-side clue that000e:ffb0is consuming AVI video-frame chunk types rather than some unrelated animation-side bookkeeping path. - Boundary analysis still reports one overlapped function object
FUN_000e_ffb0 @ 000e:ffb0 body 000e:ffb0 - 000f:00e0, so the function remains comment-only for now. The useful gain is semantic: the unresolved body is now best described asvideo-side subframe loader/decoder for the 00db/00dc chunk lane, paired with anim_load_audio_frame.
Constructor pattern
All three constructor variants (000e:2777, 000e:2860, 000e:2969) follow the same layout:
- Call
FUN_000e_e935(allocator — produces garbled 11KB decompile, not renamed) - Set fields
+0xb4through+0xc2on the result - Call near target
000e:ebe3directly (confirmed CALL sites at000e:283e,000e:2931,000e:29e4; this is a separate mis-split000eregion, notFUN_000d_ebe3) - Call
assert_alive_sentinel(assertion: checks+0xd4 != -1) - Call
func_0x000eec83
The old assumption that these constructor calls fed the 000d VM sequencer is now retired. Raw instruction search shows the direct near calls land on 000e:ebe3, whose current body is still mis-split/garbled and cannot yet be tied to the 000d:177c / 000d:1acb / 000d:0988 / 000d:22bc / 000d:2104 chain.
The constructor-side field setup before that sequencer is now slightly tighter too:
- variants A and B both set
+0xc0 = 1before the direct000e:ebe3call and derive+0xc2fromDS:0x604e - variant C instead sets
+0xc0 = 0,+0xc2 = 1, and+0x4c = 0x000dbefore the same near-call lane - this remains useful for the animation subsystem, but it no longer counts as upstream xref evidence for
FUN_000d_ebe3; the true selector/write path into the000ddispatcher is still unresolved
Constructor variant renames
| Address | Name |
|---|---|
000e:223d |
assert_alive_sentinel |
000e:2777 |
animation_ctor_variant_a |
000e:2860 |
animation_ctor_variant_b |
000e:2969 |
animation_ctor_variant_c |