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)
| Sector | Role | Key fields |
|---|---|---|
| s1 System | disk name + autoexec | +9..+29 disk name (21 b) · +0x1E..+0x59 INIST (60 b: ASCII boot commands, separated by :, terminated by #00) |
| s2 VTOC/bitmap | counters | +2,+3 free sectors (LE) · +4,+5 file count (LE) |
| s4 Directory | catalogue | +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
| Offset | Field | Example |
|---|---|---|
+0,+1 | link to next descriptor (00 00 = none) | 00 00 |
+2 | first-descriptor marker | FF |
+3 | type: b0=AUTO, b6=data block, b7=BASIC → $40=ML, $41=AUTO ML | 40 |
+4,+5 | load address (LE) | 00 C4 = $C400 |
+6,+7 | end address (LE) | FF C7 = $C7FF |
+8,+9 | execution address if AUTO (LE) | 00 00 |
+0xA,+0xB | number of data sectors (LE) | 04 00 = 4 |
+0xC.. | data sector map (track,sector)×n, terminated 00 00 | 05 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
| Tool | Role |
|---|---|
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.py → dsk_raw2mfm.py → oric1-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 / case | Result |
|---|---|
typing PROBE (bare name) at Ready | ?SYNTAX ERROR — a bare name only runs an AUTO BASIC |
LOADM"PROBE" | ?TYPE MISMATCH — LOADM 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 AUTO | BREAK 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.
BUFNOMfilled +JSR $DB2D(SEARCH) →X = POSNMX, catalogue sector in BUF3.LDA $C30C,X : LDY $C30D,X : JSR $DA5D→ descriptor loaded into BUF1 ($C100).- BUF1 layout:
$C100/01next-descriptor link ·$C102=FF·$C103type ·$C104-05load ·$C106-07end ·$C108-09exec ·$C10A-0Bsector count · map from$C10C. sect_index = off/256,byte_in_sect = off & 255. Set DRIVE$C000, TRACK$C001, SECTOR$C002, RWBUF$C003/$C004.JSR $DA73(XPRSEC). CopyNbytes frombuffer + 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).