The cooking minigame uses its own little ecosystem of files in
data/recipe/. Three files, three different inner formats β
but all wearing the same outer wrapper. Two carry text; one is pure
binary lookup. All three formats are now fully reverse-engineered
and round-trip-verified: recipedata.lzs (step-294)
and recipemsg.lzs (step-297) each shipped a dedicated writer
during v2.4 prep, and mgcinfo.lzs has no text to translate.
This page is the file-format spec. The actual list of dish names lives on
the UCC food recipe slots page
β note that the UCC food slots are a different file
(data/ucc/names.ofs), even though the player-facing
feature overlaps.
The three files
| File | Packed β decompressed | Inner format | Contains |
|---|---|---|---|
recipe/recipedata.lzs | 1,536 β 2,272 | RESC v3.11, 48-byte entry stride | 34 records: 10 category labels, 11 recipe names, 13 "Mysterious Soup" placeholder slots |
recipe/recipemsg.lzs | 1,576 β 3,658 | Custom 34-record table (stride 56) + pointer-deduplicated UTF-16-LE blob | 87 unique cooking-procedure narration strings, heavily reused |
recipe/mgcinfo.lzs | 272 β 520 | Pure binary item_id lookup | No text |
The outer wrapper (all three files)
Every file in recipe/ uses the same Konami outer envelope: a
16-byte SZL header followed by an
LZ11 stream.
The header layout:
offset size meaning
------ ---- ---------------------------------------------------
0x00 4 "1.00" version string, ASCII
0x04 12 Γ 12 padding (always zero)
0x10 4 file-specific tag distinguishes which inner format follows
The 4-byte tag at SZL +0x0C..+0x10 is the only part that
varies between the three files, and it is load-bearing
β the engine uses it to pick the right inner parser. On re-emit, the tag
must be preserved byte-for-byte (using the default b'sfo'
from lzs.compress() will corrupt the file):
| File | Tag bytes (hex) | Reads as |
|---|---|---|
recipedata.lzs | 00 73 65 72 | \x00ser |
recipemsg.lzs | 73 6D 63 72 | smcr |
mgcinfo.lzs | 00 6E 69 62 | \x00nib |
recipedata.lzs β RESC v3.11
Once the SZL + LZ11 layers are stripped, this file is an
RESC container
with magic RESC (not the more common RESO),
version "3.11", 48-byte entry stride. The entries hold:
- 10 category labels β top-level menu categories.
- 11 recipe names β the actual named dishes (Love Cake, Mood Cookie, Surprise Pie, and 8 others, translated in step-294).
- 13 "Mysterious Soup" placeholder slots β generic fallbacks the engine fills in when the player makes an unrecognised combination. All 13 deduplicate to one shared string in the blob.
Ingredient references inside recipedata.lzs are stored
as item_id integers, not as text. To know that
"egg" is at slot N you have to cross-reference the item table; you
cannot translate this file in isolation from items.
recipemsg.lzs β pointer-deduplicated narration table
This file does not reuse RESC. Its own bespoke layout, fully documented
at notes/format_recipemsg_lzs.md:
- 34 records of 56 bytes each (1,904 bytes total).
- Each record carries 4 control words (16 bytes) plus 10 pointer slots (40 bytes). Slots 0..1 are zero on normal records and carry raw u32 control words on the 13 spell-soup records; slots 2..9 are absolute file-offset pointers into the string blob.
- The blob holds 87 unique cooking-procedure narration strings β the descriptive lines the engine speaks while a dish is being made ("Mix it all together", "Bake it -- done!", "Mysterious Soup is done!", etc.).
- The record-to-string mapping is many-to-many via pointer dedup: 34 records Γ 10 slots = 340 nominal references, but only 87 unique strings exist. A single fragment like
γ¨(offset 0x0774) is referenced by 19 different records.
A naive "translate one fragment per slot" approach would corrupt every
record that shares a fragment. The pointer-deduplicated writer
(src/translator/_recipemsg_lzs_writer.py) builds a
{source_offset: en_string} map (one entry per unique JP
source), writes each unique EN string once into the new blob, then
rewrites all pointer-table entries to point at the new offsets. Records
that shared a fragment in JP keep sharing the translated fragment in EN.
The narration assembles at runtime by concatenating each non-null,
non-empty fragment in slot order, with engine-injected item names
spliced between the fragments. The fragments themselves do not
contain [ITEM:N] opcodes β the substitution happens in the
engine's narration loop, which reads the recipe's ingredient list from
mgcinfo.lzs and weaves them into the slot sequence.
The per-fragment cap is empirically untested; the v2.4 translation targeted 24 EN chars per fragment as a conservative default. JP fragments range from 1 to 14 chars; EN equivalents at the 24-char cap leave room for engine-spliced ingredient names without overflowing the popup textbox.
mgcinfo.lzs β binary item_id lookup
After SZL + LZ11 strip-down this file is 520 bytes of pure binary
item_id data. There are no strings, no header, just
fixed-width integer slots used by the cooking minigame to map game
state to specific items. Nothing here needs translating.
Re-emit constraints
- Preserve the 4-byte SZL tag at
+0x10per file. recipedata.lzsentries must stay 48 bytes (RESC v3.11 stride); entry-1 bytes must pass through unchanged; the entry-0 string-blob can grow as long as the file is repadded to 0x10 alignment.recipemsg.lzsrecords must stay 56 bytes; slots 0..1 on the 13 spell-soup records hold raw control words and must NOT be rewritten as pointers; pointer dedup must be preserved so shared fragments stay shared.mgcinfo.lzsis read by offset, so its layout must not move β copy bytes verbatim.
Related
- RESC containers β the inner format of
recipedata.lzs. - LZ11 compression β what sits under the SZL wrapper.
- UCC food recipe slots β different file, but the player-facing feature overlaps.
Source
notes/format_resc_v311.mdβ RESC v3.11 spec (step-294).notes/format_recipemsg_lzs.mdβ recipemsg layout + pointer-dedup writer notes (step-297).src/archive/_resc_v311_writer.pyβ recipedata writer.src/translator/_recipemsg_lzs_writer.pyβ recipemsg writer.