The site is a small Next.js app on the same private Amiga host as the play page, at /library, behind the same login. SQLite holds one row per disk: name, extension, size, the folder it lives in, and, when the file was inside something else, the container and the path inside it. Rebuild is a button. It does not merge. It replaces the index, then fills it again, so the count can dip and then climb while a large image is being read. The status line names the file open at that moment.
The web app cannot see W:. It runs in a container. The share is a Windows drive letter on the host next to it. A broker on that host accepts “reindex” and “extract this inner path,” and asks PowerShell to do the reading. Downloads never trust a path from the browser. They take a numeric id, the broker copies or extracts that row into a cache, and the app streams the cache.
A qualifying disk is an ADF, ADZ, DMS, IPF, HDF, HDZ, a gzip-compressed ADF, or a WHDLoad floppy named Disk.1, Disk.2, and so on. Anything under 1024 bytes is not a disk.
Three passes, one button
Rebuild scans only W:\Amiga. Recycle bins, system-volume folders, and reparse points are skipped. The walk is a stack, not a recursive pipeline: PowerShell flattens a list of hashtables when it crosses the pipeline, and the first version of this scanner wrote rows with blank names because of that.
| Pass | What it opens | What it writes |
|---|---|---|
| 1 — The tree | Every directory under W:\Amiga | Loose disks immediately. .img and .iso paths are queued. |
| 2 — Disk images | Each .img, largest ones included | The image itself, then whatever qualifying disks are inside it. |
| 3 — CDs | Each .iso | Qualifying files inside the CD. |
Pass 2 tries the CD reader first. If that finds no inner disks, it tries ext4. Raspberry Pi images are not CDs. They are an MBR, a small FAT partition, and a Linux filesystem. An image over 900 MB is skipped only when its path does not look like Amiga media at all; under W:\Amiga they are read. A CD's inner disks keep the folder that holds the ISO, and store the container and the inner path as their own columns. An ext4 image nests the folder: container path plus the directory of the inner file, so the library can drill from the share, into the image, into Games_ADF.
How an ISO is walked
ISO 9660 puts the volume descriptor at sector 16. We do not assume a 2048-byte sector. Three layouts are tried, and the first one whose sector 16 starts with type byte 1 and the id CD001 wins:
| Stride | Data starts at | What that file usually is |
|---|---|---|
| 2048 | 0 | A plain ISO |
| 2352 | 16 | A raw CD sector, header in front of the data |
| 2448 | 16 | Raw sector plus subchannel |
From there we read descriptors until the terminator (type 255). A Joliet supplementary descriptor (type 2, with the %/ escape at bytes 88–89) is preferred, because it keeps real filenames in UTF-16. Otherwise we use the primary descriptor and, on each directory record, the Rock Ridge NM name when one is present. The ;1 version suffix ISO loves is stripped.
Directory records point at an extent and a length. On a 2048-byte image that extent is a straight offset. On a raw CD image we reassemble the file 2048 bytes at a time, skipping the sector header on every stride. The walk is breadth-first from the root directory in the volume descriptor. A name that matches a disk type is a row. The rest of the CD is ignored.
How an ext4 image is walked
The first two bytes decide the container. 1F 8B is gzip. Those images are expanded to a temp file, capped at 12 GB, and reused on the next rebuild if the expanded file is already there. One of the files we cared about was a gzip of a stock RetroPie image, not a disk full of games. The magic number said so before we spent an hour mounting it.
Otherwise we look for an MBR (55 AA at the end of the first 512 bytes) and take the largest partition of type 0x83. That is the Linux filesystem. Raspberry Pi images we have seen put it at sector 124928, after a FAT boot partition. If there is no MBR, the superblock is allowed to sit at byte 1024 of the file itself. The superblock is accepted only when the magic at offset 0x38 is 0xEF53. Block size, inodes per group, inode size, and the group-descriptor size come from that same block. A 64-bit “incompat” flag means the descriptors are larger than the classic 32 bytes.
The catalog starts at inode 2, the root directory. Each directory inode is either an extent tree (header magic 0xF30A in the inode) or the older direct-and-single-indirect map. Extent index nodes are followed until a leaf gives a physical block. A directory with the htree flag (0x1000) is not a flat list: after . and .. sits a dx root, and the block numbers in it are logical, so they go back through the extent map. Directories without that flag are linear dirents. File type 2 is a directory and is queued. Type 1 is a file. Type 7 is a symlink and is not followed. proc, sys, dev, and run are skipped on sight.
A file is emitted only when its name is a disk type, or Disk. plus digits, and its inode size is at least 1024 bytes. Disk.N is stored as adf. Each hit is written as it is found, so a rebuild of a 100 GB image does not stay at zero until the end. The same reader extracts one inner path later, when someone downloads that row.
The images we thought were full of games
Three files in the Pi images folder were the ones we kept opening. None of them were the library.
- HyperPie is a Pi OS. The roms/amiga tree is there. The games are not. A full walk produced thousands of directories and zero qualifying disks.
- HP2-Full Media is about as large as a disk image gets. The roms tree is box art and video: tens of thousands of PNG and MP4 files, and no ADF or HDF.
- The 731 MB .img is gzip. The original name inside the gzip is a stock RetroPie 4.5.1 image.
The disks were in a different image under the RetroPie content tree, the same shape as HP2 (MBR, ext4 at sector 124928) and the same size class. After the 1024-byte filter that image contributed about 8,300 real disks: on the order of 4,300 ADF (including Disk.N), about 4,000 HDF, and a couple of IPF. WHDLoad installs show up as a folder under Games_WHDLoad, Games_WHDLoad_AGA, and the CD32, CDTV, and demo siblings, each holding Disk.1 and its neighbours. The .slave sits beside them in the image. It is not its own row, because a slave is not a disk image. Search still finds the install, because the folder name is on the row.
That is the lookup the WHDLoad notes use now. Drop the spaces (alienbreed, not “alien breed”), query the library for a path containing whdload, and treat Disk.N in that folder as the WHDLoad-enabled copy. If the query is empty, the index has a loose ADF or it has nothing, and only then do we grep the flat NAS listing.
What a rebuild still does not open
- Zip and 7-Zip sitting next to the images. They are archives, not disks, and they are not containers in this scan.
- .uae launcher files. There are thousands of them in that Pi image. They point at a disk. They are not one.
- A CD image nested inside the ext4 image. The inner file is recorded if its name is .iso, and we do not open it for a second level of floppies.
- UDF-only discs. No CD001 means the ISO reader returns nothing, and if it is not ext4 either, the image stays a single row.
The point of the button is that the next image does not need a special case. Drop it under W:\Amiga, rebuild, and the same two readers run: CD001, and if that fails, 0xEF53.
Where it lives
Private library on the Amiga host, path /library. Scanner: scripts/Index-AmigaNas.ps1 in amiga-eyesense (Index, Copy, Extract). The host broker shells out to that script; the Next app only reads SQLite and streams the cache. Related: the WHDLoad media index, a second drive, the hardfile.