Tachibana

Documentation › SEDORIC

SEDORIC disk format & tooling

Primary reference: the SEDORIC 3.0 manual (sedna3_0.pdf), cross-checked with A. Chéramy's “SEDORIC 3.0 à NU” and the SCUMM-Oric / scoop-oric project tooling. Nothing is invented: the offsets below are those of the manual, verified byte-exact against the descriptor dump the manual itself gives as an example.

1. MFM_DISK container

256-byte header: "MFM_DISK" + sides (u32 LE) + tracks (u32 LE) + geometry (u32 LE). Then a 6400-byte track block per (side, track), in SIDE-MAJOR order: all tracks of side 0, then all of side 1 (block = side * tracks + track).

Each track block: initial gap 60×$4E, then per sector 12×$00 | A1 A1 A1 FE [trk side sec size] CRC | 22×$4E | 12×$00 | A1 A1 A1 FB [256 data] CRC | 38×$4E, $4E padding up to 6400 bytes. CRC-16/CCITT (poly $1021, init $FFFF) over the 4 marker bytes + the data.

A RAW image (256-byte sectors concatenated, same side-major order, no framing) is accepted by sedoric-info and produced by oric1-emu --disk-create / tools/sedoric_inject.py. tools/dsk_raw2mfm.py converts RAW → MFM_DISK.

2. System sectors (track 20)

SectorRoleKey fields
s1 Systemdisk name + autoexec+9..+29 disk name (21 b) · +0x1E..+0x59 INIST (60 b: ASCII boot commands, separated by :, terminated by #00)
s2 VTOC/bitmapcounters+2,+3 free sectors (LE) · +4,+5 file count (LE)
s4 Directorycatalogue+0,+1 link to next dir sector (0=end) · +2 high-water mark · 16-byte entries from +16

Directory entry (16 b): name[9] ext[3] track sector nsec status (V4 valid file = $40; bit 7 = deleted).

Note (seen on Master disks): the VTOC free/files counter can be an 80-track template stamped onto a smaller physical image — a template, not a recomputed state. sedoric-info therefore reports both the VTOC counter and the number of files actually walked in the catalogue.

3. File descriptor sector

Pointed to by the directory entry (track,sector). Real dump from the manual (descriptor of the BANQUE #7 system file, p.16):

 0 1 2 3 4 5 6 7 8 9 A B C D E F
00 00 FF 40 00 C4 FF C7 00 00 04 00 05 0B 05 0C 05 0D 05 0E 00 00
OffsetFieldExample
+0,+1link to next descriptor (00 00 = none)00 00
+2first-descriptor markerFF
+3type: b0=AUTO, b6=data block, b7=BASIC → $40=ML, $41=AUTO ML40
+4,+5load address (LE)00 C4 = $C400
+6,+7end address (LE)FF C7 = $C7FF
+8,+9execution address if AUTO (LE)00 00
+0xA,+0xBnumber of data sectors (LE)04 00 = 4
+0xC..data sector map (track,sector)×n, terminated 00 0005 0B 05 0C 05 0D 05 0E 00 00

This dump fixes the old tap2sedoric implementation, which left +3=0 (no type) and wrote the sector count at +9,+10 big-endian (overlapping the execution address). Fixed since v1.64.0.

Chained descriptors (files > 122 sectors) — validated

A descriptor sector holds only part of the map: the 1st descriptor carries the header (12 b) then the map from +0x0C (122 pairs max, 0x0C..0xFE); each subsequent descriptor carries only the +0,+1 link then the map from +0x02 (127 pairs max). The +0,+1 link points to the next descriptor (00 00 = last). +0xA,+0xB (1st descriptor) = total number of data sectors. Validated in-situ: a 150-sector file (2 descriptors) fully loaded by LOAD. tap2sedoric and sedoric_inject.py chain since v1.65.0.

4. Tools

ToolRole
tap2sedoric <in.tap> -o out.dsk -b base.dsk [-n NAME.EXT] [-a] [-e EXEC] [-i "INIST"]injects a CSAVE .tap into an MFM Sedoric disk (file + conforming descriptor + dir entry + VTOC; -a/-e AUTO, -i boot autoexec)
sedoric-info <disk.dsk> [--check FREE:FILES]inspects VTOC, disk name, INIST, catalogue and decoded descriptors; --check = regression guard on the counters
tools/sedoric_inject.py …RAW injection (direct offsets)
tools/dsk_raw2mfm.py <raw> <out.dsk> …RAW → MFM_DISK (side-major blocks)

Typical RAW chain: oric1-emu --disk-create base.raw (then INIT at boot for a real VTOC) → sedoric_inject.pydsk_raw2mfm.pyoric1-emu --disk-rom microdis.rom -d.

5. Multi-file injection (limit lifted)

Historically, both tap2sedoric and sedoric_inject.py always allocated from track 21 sector 1 without consulting already-occupied sectors: a second injection onto the same disk overwrote the first.

Since v1.64.0, both tools walk the catalogue (full chain) and the sector maps of existing descriptors — including the chained catalogue sectors themselves — to mark occupied sectors before allocating. Verified by make test-sedoric-tools.

Directory-sector chaining (implemented)

When the current catalogue sector is full (15 entries), the tools walk the chain via the +0,+1 link; if the whole chain is full, they allocate a free sector, initialise it as a blank catalogue and chain it in. Catalogue sectors being located by track/sector link, a chained catalogue can live on any free sector. Validated in-situ.

6. Running a machine-code file under Sedoric (in-situ-validated recipe)

Loading/execution verified in the emulator (bare boot + LOAD). Pitfalls encountered and resolved:

Command / caseResult
typing PROBE (bare name) at Ready?SYNTAX ERROR — a bare name only runs an AUTO BASIC
LOADM"PROBE"?TYPE MISMATCHLOADM is the cassette ROM command
CLOAD"PROBE"no error but loads nothing (BASIC files)
LOAD"PROBE" on .BIN?FILE NOT FOUND.COM is the default extension
LOAD"PROBE",J on AUTOBREAK ON BYTE #5000,J conflicts with the AUTO flag
LOAD"PROBE" on .COM AUTO (type $41)loads AND runs

Recipe: inject as .COM AUTO (tap2sedoric … -n NAME.COM -a -e EXEC), then at Ready: LOAD"NAME". The Sedoric command is LOAD (auto-detects BASIC vs binary via the type byte +3), with options ,A (address) and ,J (jump).

Bare bootable Sedoric master (deterministic)

To validate a home-made .COM, you need a disk that boots to Ready with no competing application. Robust, timing-free method:

tools/sedoric_mkbare.py disks/SEDO40u.DSK bare.dsk        # neutralises the INIST
tools/sedoric_mkbare.py disks/SEDO40u.DSK auto.dsk 'LOAD"PROBE"'   # or auto-launch

It neutralises (or replaces) the INIST (track 20 sector 1, +0x1E..+0x59, MFM sector CRC recomputed; RAW handled too) → the disk drops to a bare SEDORIC V4.0 / Ready while remaining bootable.

Loading a file from machine code (in-situ-validated recipe)

From a running ML program (launched by LOAD), the Sedoric RAM overlay is not mapped: calling the DOS routines directly ($DB2D search, $E0EA read) crashes. The robust path goes through the “!” vector $0467 (in low RAM, always mapped), which switches to the overlay, runs the SEDORIC interpreter ($D3AE) on the line pointed to by TXTPTR ($00E9/$00EA), then switches back and does RTS.

        LDA #<CMD : STA $E9        ; TXTPTR = address of the command line
        LDA #>CMD : STA $EA
        JSR $0467                  ; "!" vector: run the command (overlay handled)
        ; ... file loaded; normal continuation ...
        RTS
CMD:    .byte "LOAD\"SCDATA\"", 0  ; SEDORIC line terminated by $00

Pitfall: the line must be terminated by $00, never by CR ($0D). With CR the $D3AE interpreter does not complete and does not RTS. TXTPTR points at the first character (the L).

Sedoric RAM overlay ↔ BASIC ROM switch (validated)

From an ML program launched by LOAD, the BASIC ROM is mapped at $C000-$FFFF: system variables and routines are in the overlay → switch before any $C000+ access. Simplest: JSR $0477 (Sedoric stub in low RAM) — a ROM↔overlay toggle that preserves drive/side/IRQ via the $04FB shadow:

PHP:PHA:SEI : LDA $04FB : EOR #$02 : STA $04FB : STA $0314 : PLA:PLP:RTS

$0314 values: bit 1 (ROMDIS, $02) = 0 → RAM overlay $C000-$DFFF, = 1 → BASIC ROM; bit 7 (EPROM, $80) = 1 → RAM overlay $E000-$FFFF. Since $0314 is write-only, go through the $04FB shadow or $0477.

Reading a file by sector at an offset (random access) — validated

To read N bytes at an offset without loading the whole file. Overlay ON ($0477) required.

  1. BUFNOM filled + JSR $DB2D (SEARCH) → X = POSNMX, catalogue sector in BUF3.
  2. LDA $C30C,X : LDY $C30D,X : JSR $DA5Ddescriptor loaded into BUF1 ($C100).
  3. BUF1 layout: $C100/01 next-descriptor link · $C102=FF · $C103 type · $C104-05 load · $C106-07 end · $C108-09 exec · $C10A-0B sector count · map from $C10C.
  4. sect_index = off/256, byte_in_sect = off & 255. Set DRIVE $C000, TRACK $C001, SECTOR $C002, RWBUF $C003/$C004.
  5. JSR $DA73 (XPRSEC). Copy N bytes from buffer + byte_in_sect.

XRWTS/XPRSEC interface (manual, byte-exact): DRIVE $C000, TRACK $C001 (b7=side B), SECTOR $C002, RWBUF $C003/$C004. $C009 = DRVDEF, $C00A = DRVSYS.

Reproduced from docs/SEDORIC.md (authoritative, up-to-date version on GitHub, with the full recipes).

← Documentation · LOCI →