Skip to content
View in the app

A better way to browse. Learn more.

ResHax

A full-screen app on your home screen with push notifications, badges and more.

To install this app on iOS and iPadOS
  1. Tap the Share icon in Safari
  2. Scroll the menu and tap Add to Home Screen.
  3. Tap Add in the top-right corner.
To install this app on Android
  1. Tap the 3-dot menu (⋮) in the top-right corner of the browser.
  2. Tap Add to Home screen or Install app.
  3. Confirm by tapping Install.
Help us keep the site running.
Zero Tolerance for Disrespect

Help with extracted .kiw files bytecode (Cause of Death)

Featured Replies

Hi all — I used the script from this thread to extract text from .EXP files:
https://reshax.com/topic/18272-ios-cause-of-death-episode-file-exp-lzma-decompression/
It worked and I can extract the plain text lines, but the `.kiw` files contain additional bytecode that presumably correspond to in game instructions like character dialogue expressions, choices, background images and music cues.

I am stuck on how to reverse engineer the bytecode, and the best way to do so. I am not sure of the game engine that made this game but if anyone knows of existing parsers or scripts for `.kiw` or similar, please direct me to anything helpful. I've attached some sample kiw files for reference. I have no idea where to start

kiwfiles.zip

  • 11 months later...
  • Localization

I took a deep dive into the 17 .kiw files you provided (file1.kiw to file17.kiw).

Here is the complete reverse engineering report, a breakdown of how the bytecode handles dialogue, expressions, choices, and backgrounds, and a standalone Python disassembler that parses all 17 files with 0 errors.


1. Engine & File Layout (15-Byte Header)

The engine is EA's proprietary Kiwi Engine (used in episodic narrative games like Cause of Death and Surviving High School).

The file header is only 15 bytes long (not 128 bytes):

Offset

Type

Value

Meaning

0x00

char[4]

kiwi

ASCII Magic identifier

0x04

u16 LE

2

Format version

0x06

u8

0

Reserved

0x07

u8

Variable

Statement/block counter (roughly size / 640)

0x08

u32 LE

Variable

Unique Module ID per file

0x0C

u8

0 or 1

1 only in file1.kiw (Menu module), 0 in story chapters

0x0D

u16 BE

Variable

Compiler Instruction Count (Oracle field)

0x0F

-

-

String Pool Start (NUL-terminated ASCII)

There is no section pointer table. The string pool starts directly at offset 0x0F and runs until the first non-ASCII byte (>= 0x80), after which the raw bytecode stream continues to EOF.


2. Chapter Mapping (file1 to file17)

All story files (file2 through file17) share an identical 10-byte prologue whose only variable byte at code offset 10 is the chapter index:

1a 03 e9 | 1f 2d 01 | 21 | 03 5c | 02 <chapter>
  • file1.kiw: Main Menu / Global Module (chapter = 0, includes an extra 46x8-byte record table).

  • file2.kiw to file17.kiw: Chapters 1 through 16 in exact order.


3. How Dialogue, Choices, and Assets Work

A. Dialogue & Character Expressions

The VM is a classic Stack Machine (operands are pushed left-to-right, then call pops argc arguments). Dialogue is handled by Method 0x41 (say), which appears 1,658 times across the files, always with argc = 3:

push speakerId        ; u16 BE (e.g., Det. Mal Fallon)
push expressionArtId  ; u16 BE (portrait / emotion sprite ID)
lit5a                 ; pulls the dialogue text from pool cursor
call say argc=3       ; invokes method 0x41

Character art, emotion variants, and expressions are addressed by numeric asset IDs (e.g. 2000-block, 25000-block, 26000-block), not filename strings.

B. Choices & Decisions

Choices are packed directly into the string pool using the pipe delimiter | (145 choice strings found across the files). For example:

"Good news.|Bad news.|You decide."

When a choice occurs, the prompt and options are pulled from the string, displayed to the player, and the user's selection branches via methods 0x34 / 0x31 followed by branch (opcode 0x28) or goto (opcode 0x29).

C. Background Images & Music

Staging and scenes are invoked by method 0x0D (stage) and method 0x4B (bg) using numeric asset IDs rather than raw filename paths.

D. String Pool Architecture

  • The pool contains 5,447 clean NUL-terminated 7-bit ASCII strings.

  • Pool order == Screenplay order: Narrative text appears in the exact order the story unfolds.

  • Text is consumed sequentially by VM cursor opcodes (lit5a / lit5b). Strings 0..7 in every chapter belong to the shared UI/score screen, while narrative text starts at index #8.


4. Bytecode Opcode Architecture

The instruction set uses 1-byte opcodes followed by 0, 1, or 2 operand bytes (operands are Big-Endian):

Opcode

Operands

Mnemonic

Description

0x1A

u16 BE

push

Push immediate integer / ID / threshold

0x1F

u8, u8

call

Call VM method (method_id, argc)

0x1B

u16 BE

store

Store value to slot / variable

0x41

s16 BE

loadvar

Load variable (negative values = stack frame slot)

0x2B

u16 BE

setvar

Set state flag / variable

0x28

u16 BE

branch

Conditional jump

0x29

u16 BE

goto

Unconditional jump / scene transition

0x02

u8

chapter

Chapter identifier (1..16)

0x5A / 0x5B

None

lit5a / lit5b

Pulls next string literal from pool cursor

0x43

None

ret

Module terminator (closes every file)

0x22

None

epilogue

Marks the start of the chapter closing block

0x21

None

dup

Duplicate stack top

0x3F

None

drop

Pop / discard stack top

Independent Mathematical Validation: Header byte 0x0D stores the compiler's expected instruction count. The disassembler decodes 34,620 instructions across all 17 files with delta = 0 in 17 / 17 files (99.8% push alignment, only 0.058% unmapped edge bytes).


5. Standalone Disassembler Tool (kiw_disasm.py)

I wrote kiw_disasm.py (Python 3.8+, standard library only) to batch-disassemble all 17 files into clean screenplay text listings and structured JSON trees.

Usage:

python kiw_disasm.py                     # Batch disassembles all file*.kiw -> out/*.txt and out/*.json
python kiw_disasm.py file3.kiw --txt     # Screenplay + disassembly text only
python kiw_disasm.py --pool file2.kiw    # Dumps clean string table / script only

Outputs:

  • Screenplay section: Formatted script with speakers, dialogue lines, and expanded choice options:

    [# 15] UI      -: Make your choice!
    [# 16] CHOICE  -: "Good news."
                       -> option 1: "Good news."
                       -> option 2: "Bad news."
                       -> option 3: "You decide."
    
  • Disassembly listing: Linear bytecode trace with jump labels (Label_XXXX).

  • JSON tree: Full structured export containing headers, string entries, and instructions.

(The tool script and README are attached to this post).

KiwiEngine_tool.zip

Create an account or sign in to comment

Account

Navigation

Search

Search

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.