oaknut.discimage

The sector-level layer beneath every disc-backed filesystem. It has no notion of catalogues or files; it models the physical shape of a disc image and hands out byte-addressable views of its sectors.

The pieces fit together bottom-up: a DiscImage wraps a byte buffer and one or more Surface sides, each described by a SurfaceSpec geometry. A range of sectors is returned as a SectorsView that reads and writes straight through to the buffer, and UnifiedDisc flattens several surfaces into the single linear address space ADFS expects. A DiscFormat, built from the surface-spec helpers, names a complete geometry plus its catalogue type; filesystem packages compose these into concrete format constants of their own.

Every name documented here is importable directly from oaknut.discimage.

The disc image

class oaknut.discimage.DiscImage(buffer, surface_specs)

A disc image with one or more surfaces.

Parameters:
property buffer: memoryview

The underlying buffer containing the disc image data.

surface(surface_index)

Get the Surface object for the given surface index.

Parameters:

surface_index (int)

Return type:

Surface

sector_views(surface_index, sector_numbers)

Get memoryviews for sectors on a surface, optimized to merge contiguous sectors.

This method encapsulates all knowledge of physical sector layout. When multiple sectors are physically adjacent in the buffer, they are merged into a single memoryview for efficiency.

Parameters:
  • surface_index (int) – Which surface (0-based)

  • sector_numbers (list[int]) – List of logical sector numbers within that surface (0-based)

Returns:

List of memoryview slices (may be fewer than len(sector_numbers) if sectors are physically contiguous)

Raises:
  • IndexError – If surface_index is out of range

  • ValueError – If any sector_number is out of range for that surface

Return type:

list[memoryview]

class oaknut.discimage.Surface(disc_image, spec, index)

A single disc surface.

A single-sided disc has one surface. A double-sided disc has two surfaces. Surface instances are created by DiscImage and reference their parent DiscImage.

Parameters:
sector_range(start_sector, num_sectors)

Create a Sectors view for a range of sectors.

Parameters:
  • start_sector (int) – First sector number (0-based)

  • num_sectors (int) – Number of sectors to include

Returns:

Sectors view wrapping the sector range

Raises:

ValueError – If range is invalid or out of bounds

Return type:

SectorsView

class oaknut.discimage.SurfaceSpec(num_tracks, sectors_per_track, bytes_per_sector, track_zero_offset_bytes, track_stride_bytes)

Specification of how a surface maps to a disc image.

Parameters:
  • num_tracks (int)

  • sectors_per_track (int)

  • bytes_per_sector (int)

  • track_zero_offset_bytes (int)

  • track_stride_bytes (int)

Sector access

class oaknut.discimage.SectorsView(views)

Presents multiple disk sectors as a single logical buffer.

Supports reads via __getitem__ and writes via __setitem__. For reads, data is materialized from the underlying sector views on demand. For writes, changes are written back to the underlying sectors immediately.

This class does not distinguish between physically contiguous and non-contiguous sectors - it uses the same code path for both cases.

Parameters:

views (list[memoryview])

None
Type:

implementation details are private

Example

# Single sector view = SectorsView([memoryview(buffer)[0:256]]) data = view[:100] # Read first 100 bytes

# Multiple sectors views = [memoryview(buffer)[0:256], memoryview(buffer)[256:512]] view = SectorsView(views) view[10:20] = b”new data” # Writes back to underlying buffers

tobytes()

Convert to bytes.

Materializes all sectors into a single bytes object.

Returns:

bytes containing all data

Return type:

bytes

class oaknut.discimage.UnifiedDisc(disc_image)

Presents multiple surfaces as a single linear sector address space.

Parameters:

disc_image (DiscImage)

property num_sectors: int

Total sectors across all surfaces.

property num_bytes: int

Total bytes across all surfaces.

sector_range(start_sector, num_sectors)

Read/write sectors from the unified address space.

Handles ranges that span the boundary between surfaces by combining SectorsViews from each surface.

Parameters:
  • start_sector (int) – First unified sector number (0-based).

  • num_sectors (int) – Number of sectors to include.

Returns:

SectorsView wrapping the requested sector range.

Raises:

ValueError – If range is invalid or out of bounds.

Return type:

SectorsView

Disc formats

A DiscFormat is a list of surface specs plus the catalogue type that lives on them. The helpers build the SurfaceSpec lists for the three common physical layouts, so a filesystem package rarely constructs a SurfaceSpec by hand.

class oaknut.discimage.DiscFormat(surface_specs, catalogue_name)

Complete disk format specification including all surfaces and catalogue type.

Parameters:
property image_size: int

Total number of bytes required for a full disc image in this format.

oaknut.discimage.single_sided_spec(num_tracks, sectors_per_track, bytes_per_sector=256)

Create SurfaceSpec for a single-sided disc image.

Parameters:
  • num_tracks (int)

  • sectors_per_track (int)

  • bytes_per_sector (int)

Return type:

SurfaceSpec

oaknut.discimage.interleaved_double_sided_specs(num_tracks, sectors_per_track, bytes_per_sector=256)

Create SurfaceSpecs for an interleaved double-sided disc image.

The physical layout alternates sides per track (side 0 track 0, side 1 track 0, side 0 track 1, …).

Parameters:
  • num_tracks (int)

  • sectors_per_track (int)

  • bytes_per_sector (int)

Return type:

list[SurfaceSpec]

oaknut.discimage.sequential_double_sided_specs(num_tracks, sectors_per_track, bytes_per_sector=256)

Create SurfaceSpecs for a sequential double-sided disc image.

The physical layout is all of side 0 first, then all of side 1.

Parameters:
  • num_tracks (int)

  • sectors_per_track (int)

  • bytes_per_sector (int)

Return type:

list[SurfaceSpec]

oaknut.discimage.BYTES_PER_SECTOR = 256

int([x]) -> integer int(x, base=10) -> integer

Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.__int__(). For floating-point numbers, this truncates towards zero.

If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by ‘+’ or ‘-’ and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer literal. >>> int(‘0b100’, base=0) 4

Opening an image file

oaknut.discimage.open_image_mmap(filepath)

Open filepath as an mmap, writable when the host allows it.

Tries r+b first; on PermissionError falls back to rb. The yielded tuple is (mm, writable) so callers know whether to mm.flush() on clean exit.

Parameters:

filepath (Path) – Path to an existing disc-image file.

Yields:

(mmap, writable) — the mmap is sized to the whole file and held open for the duration of the with block.

Return type:

Iterator[tuple[mmap, bool]]