File formats, scenes and palettes

Technical appendix · Home · Français

File Size (B) Format Payloads
G1.CON 295,406 .CON 60 (20 records)
G2.CON 70,513 .CON 15 (5 records)
G3.CON 114,229 .CON 18 (4 records)
G4.CON 180,769 .CON 53 (5 records)
G5.CON 54,978 .CON 93 (2 records)
G6.CON 63,860 .CON 16 (4 records)
MUS.HUF 31,782 stream file 29
SND.HUF 4,478 stream file 28
SAUP.MAU 2,467 raw 1
DEMOFR.MAU 895 raw 1
HNF.HUF 4,717 not game data see section 4

That is 40 .CON records, 255 .CON payloads and 57 .HUF streams (312 together, 952,709 B), plus the two .MAU files.

Files named by maup.exe: G1-G6.CON, SAUP.MAU, DEMOFR.MAU, MUS.HUF, SND.HUF, SAUVEMAU.00n, and conditionally G7.CON (below).

1. .CON

Read path: Con_ReadRecord EA 0x13F9F (verified)

Arguments, pushed first to last: record, sub-index, destination segment, destination offset.

  1. Calls Con_OpenArchive EA 0x140EC.
  2. Walks wConArchiveSentinels EA 0x1AE64: six 0xFFFF-terminated groups of record numbers. The group number selects ConOfs_Disk1-6 (EA 0x1ADAC, 0x1AE00, 0x1AE18, 0x1AE2C, 0x1AE44, 0x1AE50); the position in the group selects a dword in that table.
  3. Seeks to base + 4*sub, then reads 8 bytes into ConRecIndexBuf EA 0x1AEC0.
  4. Seeks to base + start, and reads next - start bytes (low words) to the caller's buffer.
  5. Closes the file only if the length was non-zero.

The groups are: 0,1,2,4,5,6,8,9,B,C,D,E,12,13,14,17,18,1A,1C,1E / 7,A,10,15,1B / 27,28,29,2A / 21,22,23,2B,2C / 20,1F / 3,F,19,1D.

Archive selection: Con_OpenArchive (verified)

  • The name comes from wConRecordToArchive[record] EA 0x1AD52: slot (n-1)*7 of aConNames EA 0x1AEC8. The open is AX=3D80h.
  • bConDiskLayoutVariant EA 0x1AA92 is set by Sys_DetectMedia EA 0x10842 from INT 21h AH=1Bh: media byte F9h gives 1, F8h (hard disk) gives 2, anything else gives 0.
  • Variant 2 skips disk checks. Otherwise the wanted disk comes from bConRecordDiskMap0/1 EA 0x1ACF8 / 0x1AD25; on a mismatch Con_PromptDisk EA 0x128F7 runs with AX = 8, 0x306 or 6.
  • G7.CON: with variant 0, record 0x20, current disk 2 and a sub-index outside 4..9, the digit of the G5.CON name is patched to 7 at 0x14185. No G7.CON is in this folder. It presumably belongs to a low-density floppy set (inferred), and a hard-disk install never takes this path.
  • Open failure clears the disk byte and jumps back to the start (0x141CF).

Payload slicing

4-byte entries, N = first entry / 4, payload[k] = blob[base + start[k] : base + start[k+1]]. All archives tile exactly (selftest); 39 payloads are empty, always the last slot of a record.

Known record contents

Record Contents Evidence
0x27 sub 2 full-screen picture verified: renders as a sailboat at sunset
0x27 subs 9, 10 codec streams, music-like verified codec; loaded by Huf_LoadRec27Music EA 0x1781C
0x27 subs 0, 1, 3-8 frame parts and interface sprites, such as the title and the magnifying glass; exact roles not established verified load
0x28 sub 0 1,589 big-endian u16 string sizes, in 7 banks verified
0x29 sub 0 5-bit packed game text, in 7 banks verified: see Text and fonts
0x2A sub 0 516-entry table of game-logic data, below verified structure
0x00-0x1E subs 0, 1 rooms: sub 0 is the background, below verified for sub 0
0x22 conversation background and the nine character portraits, loaded by Con_LoadRec22Images verified
0x2B sub 0 speech samples: 644 big-endian offsets, then 4-bit delta audio. See Speech verified
0x2C sub 0 speech phrases: 460 big-endian offsets (450 used), then 16-bit phoneme words. See Speech verified

Images: Img_Decode EA 0x14930 (verified structure)

Far arguments: source segment, source offset, destination segment, x, y.

u8 kind; u8 tileCount
if kind == 0: 16 big-endian palette words, 0RGB with 3 bits per channel; no transparency
else:         u8 transparent colour; u8 unused
each tile:    be16 lenA, lenB, x, y, flags, w-1, h-1, then its data
  • flags high byte is the scan mode, dispatched at 0x14B91 + 3*mode, 0..18.
  • Mode 0 stores raw nibbles. Other modes build a nibble value list (lenA) and a run-length nibble list (lenB) in a work area at destination segment +0xFA0, just past the 64,000-byte page.
  • flags bits 0 and 1 mark those lists as themselves run-length packed.
  • Scan orders include serpentine rows and columns and several diagonal patterns.

rec27_sub02 has a 16-colour palette and four tiles that cover 320×200 exactly:

The intro picture, record 0x27 sub 2

tools/decode_image.py ports all 19 modes. Run in an emulator, the game's own routine produces the same page for all 439 images in the game (tools/verify_image.py).

Other image records:

Record Contents
0x1F object icons, in the object list's order
0x20 timed scene overlays (below)
0x21 book, newspaper, calendar and scroll pages, with their backgrounds; subs 6 and 7 are the two halves of the pirate inscription
0x22 conversation background and character portraits; the palette appears to be the one in record 0x23 (inferred)
0x23 island map and small portraits
0x27 title, frame parts and interface sprites

Rooms: service 0x18 Con_LoadRoom EA 0x12640 (verified)

The record number is the room number minus 1: MAUPITI1 passes DS:166 - 1, and the screenshots confirm it (room 26, the cabin, is record 0x19). Sub 0 holds two 16-word palettes at +0 and +0x20, then a normal image at +0x40 with its own palette at +0x42, drawn at (0,0). Header sizes tile exactly: for record 0x1A, 21 tiles fill the remaining 11,418 bytes. Six rooms by day:

Six rooms by day

A scene is drawn in layers by Room_Enter MAUPITI1 EA 0x141CB; tools/render_scene.py ports it, and scenes/ has every room. On all four screenshots, only the mouse pointer differs in the picture area (49-61 pixels each). On the left, the screenshots; on the right, the renderer:

Screenshots beside the renderer's output

  1. Background: sub 0.
  2. Zones: tables 0x129 (count per room) and 0x12A (zone). State DS:92F + 11 * room + i picks the open image (sizes in 0x12B) or the closed one (0x12C). A zone is skipped if its flag in DS:7B5 is clear, unless Zone_Alias EA 0x1079F maps it to another zone.
  3. Objects: tables 0xC0 and 0xC1. State DS:8AE + 2 * index picks the image: zero uses table 0x132, otherwise 0x133. Room 30 pairs its bénitiers, and also draws entries of size 0.
  4. Timed overlays, Scene_DrawTimedOverlays EA 0x10479: tables 0x11C (count per room), 0x11D and 0x11E (first and last time), 0x11F (sub of record 0x20). These draw the bassin's tides and reflection, the bodies on the beach and the jetty, and the changes to rooms 6 and 8.

Sub 1 holds those images in order, each a normal image: sizes from 0x12B, 0x12C, 0x132, 0x133, 0x134, then 51 bytes used only by the non-VGA paths. This adds up exactly on the rooms checked (for example 3,146 + 51 = 3,197 for record 0x19). Table 0x134 holds images used when an object is taken.

Record 0x1E, which the ending loads as "room 31", is a sunset over the island with no room data of its own.

Palettes (VGA mode)

Palette words are 0RGB, 3 bits per channel. They never reach the VGA directly. tools/decode_image.py implements this, and it reproduces the 16 colours in screenshots/ exactly, on all four.

Step 1: which palette. EA 0x145D4 stores three in DS:79E, 7BE, 7DE, and Pal_TimeOfDayB EA 0x147C3 picks by hour = (time / 2) mod 24:

Hours Palette Record offset DS:
8-17 day +0x42 79E
6-7, 18-19 dawn and dusk +0 7BE
20-5 night +0x20 7DE

Step 2: channel tables. EA 0x143F0 converts each channel through a table at DS:676 to a 6-bit DAC value. The tables differ per channel, and 6 and 7 are the same:

Value 0 1 2 3 4 5 6 7
Red, DS:676 0 3 17 31 44 58 63 63
Green, DS:67E 0 7 18 30 43 54 63 63
Blue, DS:686 0 4 13 29 41 60 63 63

Step 3: register order. Palette entry k is written to DAC register DS:68E[k], which reverses the four bits: 0 8 4 12 2 10 6 14 1 9 5 13 3 11 7 15. So pixel value i shows palette word reversed(i).

Step 4: fade. Colours are written at one site, EA 0x1453E. Each step blends (from * a + to * b) >> 6 per channel, with a falling from 63 and b rising from 1, over 63 steps. "From" or "to" can be cleared to black (DS:647 = 1 or 2).

Part Checked by
Day palette, channel tables, register order All four screenshots, exact
Dawn, dusk and night palettes Code only: the screenshots are all at 10:00
Fade Code only
Other video modes (DS:492 not 5) Not used by this path; not studied

DOSBox shows a 6-bit value v as (v << 2) | (v >> 4). That's in screenshots/' colours, and isn't part of the game.

Record 0x2A: the game data table (verified structure)

Table_GetEntry EA 0x15354 (service 0x0F) treats the record as big-endian sizes. The first word, 1,032, is the table's own size, so 516 entries. On first use, EA 0x153BD makes them cumulative in place. The sizes total 41,895, 10 bytes more than the record, so the final entry runs past its end.

Entries are small arrays of bytes and big-endian words, read with the Mem_Peek services. Almost none are images. Entries 199 and 200 are 32-byte palettes, and requesting them has a side effect at 0x153E4/0x15405 that also copies palette data. rec2a-entries.tsv lists every entry, with its callers where the index is a constant (115 entries; others are computed). Game logic explains the tables behind dialogue, actions, objects and the ending; others are still unidentified.

2. .HUF

HufStreamTab EA 0x1CC60 holds the cumulative u32 offsets; there is no directory in the files.

MUS: 30 boundaries, 29 streams
SND: 29 boundaries, 28 streams

Huf_LoadFile EA 0x1761D opens the files with AX=3D80h. When bSoundCard (DS:F6, EA 0x1A516) is zero, the selector is first replaced by HufStreamRemap[selector] (word table at EA 0x1CD5A): identity up to 0x14, then 0x15→0x17, 0x16→0x14, 0x17→0x17.

Each stream starts with a u32 length and uses the codec in The compression codec. All decode byte-exactly.

  • SND: {u16 ver; u16 count; u16 dataOfs = 6+9*count; count × 9-byte name; count × 56-byte record}; 25 distinct instrument names.
  • MUS: 28 event scripts with a 70-byte header (u16 0x04F0 at +0x24, u32 body length at +0x2A); stream 28 is an Amiga IFF 8SVX sample.

3. .MAU: Mau_LoadFiles EA 0x141D9 (verified)

  • SAUP.MAU: reads 0x9A3 bytes to the caller's buffer. The last 8 bytes are copied to DS:2835. It is the starting game state: see The two programs, and Game logic for the fields in use.
  • DEMOFR.MAU: reads 0x37F bytes to DS:AE6 (EA 0x1AF06).

DEMOFR.MAU is u8 + 149 six-byte records. Demo_Init EA 0x148BC multiplies byte 0 by 18; the consumer at 0x137FC steps by 6. The records hold mouse-style data read near 0x135BB (x and y words at +2 and +4). The reading as a mouse path is inferred.

4. HNF.HUF: not game data

maup.exe never names it. Its header is ROT-13 text:

Jeu telecharge sur : The X Web
Webmaster : Fred
E-mail du webmaster : fred@lankhor.net
URL : http://www.lankhor.net

The rest resembles SND.HUF but isn't a copy: the shared suffix is 216 B. This looks like release-group metadata, like the trailer on maup.exe.

Previous: Game logic · Next: The compression codec · Technical appendix · Home