oaknut-adfs
A Python library for reading, writing, and creating Acorn ADFS (Advanced Disc Filing System) disc images — the hierarchical filing system used by the BBC Master, Acorn Archimedes, and RISC OS.
oaknut-adfs opens ADFS floppy and hard-disc images to browse the directory
tree, read and write files and directories, inspect and edit RISC OS
metadata (filetypes, datestamps, access), and create new formatted discs —
from Python, with a pathlib-inspired API.
Looking for DFS? The flat-catalogue Disc Filing System of the BBC Micro is a separate filing system in the sibling
oaknut-dfspackage (from oaknut.dfs import DFS). For a unified command-line tool across DFS, ADFS, AFS, ROMFS and ZIP, seeoaknut-disc.
Part of the oaknut monorepo.
Supported formats
ADFS evolved across three machine generations. oaknut-adfs reads, writes, and creates every standard floppy format and the FileCore hard-disc layout:
| Format | Map | Directory | Capacity | Sector | Zones | Notes |
|---|---|---|---|---|---|---|
| S | Old | Old | 160 KB | 256 B | — | 40-track single-sided; ADFS on the BBC B / Electron |
| M | Old | Old | 320 KB | 256 B | — | 80-track single-sided |
| L | Old | Old | 640 KB | 256 B | — | 80-track double-sided |
| D | Old | New | 800 KB | 1024 B | — | Arthur / the BBC Master 512 |
| E | New | New | 800 KB | 1024 B | 1 | double density; RISC OS |
| E+ | New | Big | 800 KB | 1024 B | 1 | RISC OS 4 — long filenames |
| F | New | New | 1600 KB | 1024 B | 4 | high density; RISC OS |
| F+ | New | Big | 1600 KB | 1024 B | 4 | RISC OS 4 — long filenames |
| G | New | New | 3200 KB | 1024 B | 8 | extra-high density; rarely used |
| G+ | New | Big | 3200 KB | 1024 B | 8 | RISC OS 4 — long filenames; rarely used |
| Hard disc | New | New / Big | multi-megabyte | 256 / 512 B | many | Archimedes / RISC OS FileCore |
The map and directory are independent axes; the format letter is shorthand for a particular pairing.
Allocation maps
- Old map — a free-space map in sectors 0–1, listing free fragments as
(start, length)pairs, guarded by two additive checksums. Files occupy contiguous sectors. Used by S, M, L and D. - New map (FileCore) — a fragmented allocation scheme. A disc record
at offset
0x04describes the geometry; a zoned allocation bitmap tracks variable-length fragments addressed by a fragment id plus a sector offset. This is what lifts the size ceiling of the old map and allows the multi-zone F and G formats and large hard discs. Used by E, F, G and their+variants.
Directory formats
- Old directory (
Hugo) — a fixed 1280-byte block, up to 47 entries, 10-character names. Used by S, M and L. - New directory (
Hugo/Nick) — a fixed 2048-byte block, 10-character names, carrying the 32-bit RISC OS load/exec fields. Used by D, E, F and G. - Big directory (
SBPr/oven) — a variable-size block with a packed name heap, supporting filenames of up to 255 characters. Used by the+formats (E+, F+, G+).
Hard-disc images
New-map hard discs are read, written, and created in both common on-disc layouts:
.hdf— RPCEmu / Arculator IDE images, where FileCore numbers sectors fromlow_sector, placing disc address 0 at a0x200offset. Detected and handled by content..dat/.dsc— a raw image with a 22-byte SCSI geometry sidecar; the New-map geometry is read from the disc record, so the sidecar is optional.
Metadata
RISC OS is a 32-bit system, and its per-file metadata lives in the load and execution address fields:
- Filetypes and datestamps. When the top 12 bits of the load address are
&FFF, the field encodes a 12-bit filetype and a 40-bit datestamp (centiseconds since 1900) rather than genuine addresses. oaknut-adfs stores both the raw fields and, through theoaknut.filesystemcapability layer, the decoded filetype and datestamp. - Access bits. Owner and public read/write, plus the lock (
L) bit, via the sharedoaknut-fileAccesstype.
Content-based identification (via oaknut.filesystem) recognises every map
and directory combination above, so a disc need not be labelled by extension.
Installation
uv add oaknut-adfs # or: pip install oaknut-adfs
oaknut-adfs works with any PEP 517 build
front-end and package manager; the examples use
uv.
Usage
Opening and reading
The format is auto-detected from the image content (and, where content is ambiguous, the file extension). Pass a format only to override detection.
from oaknut.adfs import ADFS
with ADFS.from_file("RISCOS.adf") as adfs:
print(adfs.title)
# Navigate with a pathlib-inspired API; "$" is the root.
for entry in (adfs.root / "$").iterdir():
s = entry.stat()
print(f"{entry.name:12s} {s.length:7d} load={s.load_address:08X}")
data = (adfs.root / "$" / "!Boot").read_bytes()
from_file opens the image read/write when host permissions allow; pass
read_only=True to guarantee a shared image cannot be modified.
Creating a disc
Every floppy format has a module constant. Pass it to create_file:
from oaknut.adfs import ADFS, ADFS_E, ADFS_F_PLUS
# An 800 KB New-map E disc.
with ADFS.create_file("disc.adf", ADFS_E, title="RISCOS") as adfs:
(adfs.root / "$.ReadMe").write_bytes(
b"oaknut-adfs demo", load_address=0xFFFFFF00, exec_address=0x00000000
)
(adfs.root / "$.Apps").mkdir()
# A 1600 KB F+ disc, whose Big directories allow long filenames.
with ADFS.create_file("big.adf", ADFS_F_PLUS, title="Extended") as adfs:
(adfs.root / "$.AVeryLongFileNameIndeed").write_bytes(b"...")
Hard-disc images
from oaknut.adfs import ADFS
# Size a New-map hard disc from a capacity; big_directories=True selects
# the Big directory (E+/F+) layout.
adfs = ADFS.create_new_map_hard_disc("40MB", title="IDEDisc", big_directories=True)
Validating structure
validate() walks the whole disc — map, zone checks, and directory tree —
and returns a list of structural errors (empty when the disc is sound):
with ADFS.from_file("disc.adf") as adfs:
errors = adfs.validate()
assert errors == []
Development
The package is developed in the oaknut workspace. From the repository root:
uv sync # install all workspace members editable
uv run pytest packages/oaknut-adfs/tests # this package's tests
uv run ruff check # lint
Architecture
A layered design, dependencies flowing strictly downward:
ADFS (adfs.py) user-facing ADFS / ADFSPath / ADFSStat
↓
directory.py ←→ free_space_map.py ←→ new_map.py
(Old/New/Big dirs) (old map) (FileCore new map)
↓
UnifiedDisc / Surface / SectorsView (from oaknut-discimage)
adfs.py— the user-facingADFS,ADFSPath,ADFSStat; format detection, the format constants, and disc creation.directory.py— the Old, New, and Big directory formats (parse, serialise, and their respective check-byte algorithms).free_space_map.py— the old-map free-space map and its checksums.new_map.py— the FileCore disc record, zoned allocation bitmap, fragment allocation and sharing, and blank-image formatting.filesystem.py— theoaknut.filesystemadapter: content identification, the mount, and the Filetyped / Datestamped capabilities.
Sector-level access (Surface, SectorsView, UnifiedDisc) lives in
oaknut-discimage;
metadata and the acorn codec in
oaknut-file;
BBC BASIC (de)tokenisation in
oaknut-basic.
oaknut-adfs and oaknut-dfs are independent siblings: from oaknut.dfs import ADFS does not work — ADFS lives in oaknut.adfs.
Further reading
- Advanced Disc Filing System — Wikipedia overview of ADFS and its formats.
- FileCore — the RISC OS Open source for the New-map filing core (Apache 2.0).
- Guide to Acorn Disc Images — Gerald Holdsworth's specification of the ADFS map and directory formats.
- INF file format — the
.infsidecar metadata format. - Stardot INF format specification —
Tom Seddon's formal grammar for
.inffiles, which oaknut follows. - Storing Acorn/BBC metadata on other systems — J.G. Harston's INF and ZIP metadata rules, used where the Stardot specification leaves room.
License
MIT — see LICENSE.
Metadata
Release files for oaknut-adfs 13.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| oaknut_adfs-13.1.2.tar.gz | 133.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| oaknut_adfs-13.1.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 216.7 kB
Release files / oaknut_adfs-13.1.2.tar.gz
| Download URL | oaknut_adfs-13.1.2.tar.gz |
|---|---|
| Size | 133.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
794879f208f27be7a0a165c2811a6eca7516bff7b434df134fc56f9c70c68c35
|
|
BLAKE2b-256 checksum How to use checksums |
d3699957d9c7ee9e39eefd8c53656f34b6806f484a66afde2cf5442f59f9b26a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.21 {"installer":{"name":"uv","version":"0.12.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / oaknut_adfs-13.1.2-py3-none-any.whl
| Download URL | oaknut_adfs-13.1.2-py3-none-any.whl |
|---|---|
| Size | 83.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f137e37821f4e7b74310d91d566499b8c87cf2f48187c2588a2db790f2ac911e
|
|
BLAKE2b-256 checksum How to use checksums |
979183bd3dbadbca832bc200bf41e18c21768e9e1c35084880c9de92f4a94913
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.21 {"installer":{"name":"uv","version":"0.12.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|