Web64 logo Web64 Documentation Web64 Hardware Loader Runtime

Web64 Hardware Loader Runtime

Version: 2026-09-07

The hardware loader is an opt-in, synchronous native 6502 runtime for named-file reads from a 1541-family drive. It can receive raw PRGs, install raw payloads, and validate, decompress and transactionally install Web64 Packed data targets. It owns transport, validation and drive-code lifecycle. Your application owns memory, IRQs, music, display, file meanings, cache contents and transition policy.

Include <web64/loader.h> in C or web64/loader.inc in native assembly. Merely including declarations emits no loader. Calls exact-link their dependencies; raw reading does not link the packed decoder, and unpacking an existing memory container does not link disk transport. Pure assembly needs no C startup.

For a complete starting project, choose New > Template > Hardware Loader + Disk Mastering. The template provides native resident/raw/packed ASM targets, an editable disk set, memory reservations, IRQ-driven loading feedback and retry. Use Disk/Media > Build Dependencies > Run Disk; inspect per-target results under Code > Build Output. Its included DISK-GUIDE.md describes how to create more banks entirely in the IDE.

Hardware and scheduling contract

The supported protocol is the one-bit, long-filename Covert Bitops 1541 path on C64. Independent PAL and NTSC fixtures run against VICE true-drive 1541-II emulation, with repeated reads, errors, abort/retry and an actual SAVE/read-back. This is not a claim of physical-hardware testing. Other IEC devices, SD2IEC, 1581, CMD drives, cartridges and accelerated CPUs are not supported by this contract. Unsupported detected drive types return an error.

There is no automatic KERNAL fallback. Existing <web64/disk.h> operations remain available as an explicitly chosen, separate compatible path; they do not inherit the fast loader's presentation or transactional guarantees.

All loading calls block the mainline until success or failure. There is no start/poll/finish API and no hidden background task. A caller-owned IRQ can continue music and lightweight presentation during fast reads. That does not make a blocked mainline menu responsive: do not start title-screen prefetch if its input or display updates depend on that mainline returning every frame.

Initialization and reinstallation use normal KERNAL IEC communication and may stall presentation. Ordinary SAVE is also non-fluid. Silence or deliberately hold the music and provide a static busy state for those phases if necessary. The fast-read fixtures preserve a one-frame presentation cadence; this is a measured fixture result, not a promise for arbitrary heavy IRQ handlers.

Create the complete disk inside Web64

  1. Create a resident C, ASM or hybrid boot target in Build Targets. Reserve resident code, C stack if used, music, display and loader workspaces.
  2. Author data with native asset editors and ordinary virtual source files. Assemble each bank at its intended installed origin. SID Tracker output should be included using its generated C64 payload offset/size, not the PSID container header. See the IDE manual's SID payload-bank instructions.
  3. Keep the bank a normal PRG target for raw loading, or choose Packed data (Exomizer). Choose its staging address, maximum packed size and, optionally, fixed-size independent blocks. This uses the browser-native encoder, not an external Exomizer executable. The native editor source remains authoritative.
  4. In Disk/Media, add target outputs as logical PRG files. Set banks to Raw data, the boot program to Runnable (SYS), and place them on a D64. Packed targets reject SYS wrapping. Give each bank the exact DOS name used by the application; filenames are 1–16 nonzero PETSCII bytes.
  5. Use Build Dependencies, then Run Disk. Native source/asset edits and included dependencies invalidate packed targets too. Rebuild and remount after edits; the already mounted image cannot change itself.

No generated source needs to be hand-edited, no project JSON tailoring is required, and no prepacked source artifact or host filesystem tool is involved. Disk placement controls are optional. Directory order does not schedule loads.

API and results

Every status-returning function returns uint8_t: zero means success. Except for a rejected busy/reentrant call, web64_loader_status() reports the last operation's result. A busy call returns BUSY without mutating the active call.

CallPurpose
web64_loader_init(device, sector, ticks)Detect a supported drive, upload its program and configure caller storage. Call again after a drive power-cycle or uncertain configuration.
web64_loader_read(name, length, stage, capacity)Read a PRG's body into bounded staging, consuming its two-byte load address separately.
web64_loader_last_size() / web64_loader_last_origin()Received body byte count and original PRG load address; use after a successful read.
web64_loader_load_raw(&request)Stage a complete PRG, check origin and size, then copy to the allowed destination.
web64_loader_load_packed(&request)Stage a complete W64X PRG and perform the checked packed transaction.
web64_loader_unpack(&request)Validate/install an already staged W64X body; does not require a drive.
web64_loader_abort()IRQ-safe cancellation request during a fallible read/decode phase. Idle/commit requests are ignored.
web64_loader_shutdown()Detach drive code and invalidate fast reads before normal KERNAL I/O.
web64_loader_reinitialize_after_save()Reinstall using the last valid device/sector/clock configuration.

All status names have the prefix WEB64_LOADER_ in C and ASM:

ValueNameMeaning / response
0OKRequested data installed or operation completed.
1NOT_FOUNDNamed file absent; check mounted media and exact DOS name.
2TRANSPORTSerial/read failure or timeout; initialize before retrying.
3UNAVAILABLEDevice/media unavailable; restore it and initialize.
4HEADERBad W64X magic/version or incompatible metadata/trailer.
5INTEGRITYContainer or decoded CRC mismatch; reject the bank.
6PACKED_SIZEStaging too small, truncated body or inconsistent packed layout.
7UNPACKED_SIZEInvalid/overflowing decoded record size.
8RANGEInvalid, overflowing or forbidden address span.
9WORKSPACEOverlapping storage or insufficient decode workspace.
10DECOMPRESSIONMalformed/unsupported bounded stream or wrong decoded extent.
11ABORTEDAccepted cancellation; initialize/reinstall before another read.
12NOT_INITIALIZEDDrive code is not valid, or no saved configuration exists.
13INVALID_ARGUMENTBad filename, device, sector, clock, banking, interrupt state or block selector.
14UNSUPPORTED_DRIVEDetected drive is outside the implemented protocol.
15BUSYA mainline loader operation is already active.

Check success before using getters, entering an overlay, starting a new SID player or displaying a new bank. Do not interpret a nonzero status as EOF.

Staging, packed transactions and caches

web64_loader_read receives into the exact supplied staging address regardless of the PRG's origin. Its size excludes the two-byte PRG prefix. Staging can be partially modified on a failed receive. It must never alias live data.

For whole-file raw installation, the PRG origin must equal destination and the received body must fit capacity. Workspace fields are unused. Destination is unchanged until the complete receive succeeds. For whole-file packed loading, the PRG prefix must equal stage, matching the Packed data target's staging setting. Embedded W64X destinations must lie inside the allowed destination span. They are restore-at-origin records, not implicit relocation requests.

The packed transaction checks format, complete directory, lengths, stream trailers, address bounds and the outer CRC. Selected blocks decode into separate workspace, with input/output/history bounds and decoded CRC checks. All selected blocks pass before the first live destination write. Even malformed compressed bytes with a recomputed outer CRC cannot partially replace a bank. Integrity checks detect corruption; CRC is not cryptographic authentication.

Choose a zero-based block, or WEB64_LOADER_ALL_BLOCKS (255). All directory records are validated; only selected streams are decoded and installed. ALL requires scratch equal to the sum of selected decoded lengths. An independent block target has multiple records sharing one installed address: normally select one. ALL installs them in directory order, so the last record wins at that address.

Retain an immutable container in caller-owned cache RAM and call unpack with the selected record whenever needed. There is no runtime cache policy or hidden allocation. Do not overwrite staging, descriptors or workspace from an IRQ during a call. The final commit closes the cancellation window atomically, then copies with the caller's interrupt state restored. Multi-block success is not an atomic display-page flip: readers of a replaced bank must remain paused until the call returns. The IRQ may use other resident banks throughout.

C request layouts

The SDK supplies packed, exact-width struct declarations under both Web64-C ABIs. Pointers and uint16_t fields are little-endian two-byte words; byte fields have no padding. Use these types instead of guessing offsets.

TypeFields in byte orderSize
web64_loader_requestname pointer 0, name_length byte 2, stage pointer 3, stage_capacity word 5, destination pointer 7, capacity word 9, workspace pointer 11, workspace_capacity word 13, block byte 1516
web64_loader_packed_requeststage pointer 0, length word 2, destination pointer 4, capacity word 6, workspace pointer 8, workspace_capacity word 10, block byte 1213

For example, after the application has installed its IRQ and reserved these disjoint addresses (they are choices for this snippet, not runtime reservations):

#include <web64/loader.h>
extern volatile uint16_t disk_ticks; /* incremented by the application's IRQ */
web64_loader_request bank;

uint8_t load_bank(void) {
    uint8_t status;
    status = web64_loader_init(8, (uint8_t*)0xbf00, &disk_ticks);
    if (status != WEB64_LOADER_OK) return status;
    bank.name = "DATA";
    bank.name_length = 4;
    bank.stage = (uint8_t*)0x6000;
    bank.stage_capacity = 2048;
    bank.destination = (uint8_t*)0x9000;
    bank.capacity = 2048;
    bank.workspace = (uint8_t*)0x7000;
    bank.workspace_capacity = 2048;
    bank.block = WEB64_LOADER_ALL_BLOCKS;
    return web64_loader_load_packed(&bank);
}

Initialize once in normal use, not before every successful read. Keep the previous bank active on error and offer a retry when media is available.

Direct assembly ABI

The native implementation is authoritative. C _web64_rt calls and assembly macros write the same exact-width named windows before JSR:

EntryArgument window bytes, in order
_web64_loader_initdevice 1, sector 2, ticks 2
_web64_loader_readname 2, length 1, stage 2, capacity 2
_web64_loader_load_raw, _web64_loader_load_packed, _web64_loader_unpackrequest 2
Other public entriesNo argument window

Labels are _web64_loader_read__arg_stage, for example; window size, field width and offset symbols are generated by the same ABI registry. web64_rt_prepare_* macros set windows; web64_rt_call_* also call the routine. Macros take immediate values/addresses. For changing runtime values, write the named window explicitly. WEB64_LOADER_REQUEST_* and WEB64_LOADER_PACKED_REQUEST_* provide descriptor offsets, including STAGE_CAPACITY, WORKSPACE_CAPACITY and SIZE as applicable.

.include "web64/loader.inc"
; Application has configured its clock/IRQ, $01=$35 and enabled interrupts.
web64_rt_call_loader_init 8, $bf00, disk_ticks
cmp #WEB64_LOADER_OK
bne failed
web64_rt_call_loader_load_packed bank_request
cmp #WEB64_LOADER_OK
bne failed
; The complete selected data is now safe to use.
rts
failed:
rts
bank_name: .text "DATA"
bank_request:
    .word bank_name
    .byte 4
    .word $6000,2048,$9000,2048,$7000,2048
    .byte WEB64_LOADER_ALL_BLOCKS

Status returns in A. Sixteen-bit getters return low byte in A and high byte in X. A/X/Y and arithmetic flags are caller-clobbered; compare A explicitly, not carry. The entry may clear decimal mode. Normal calls balance the hardware stack. abort clobbers A/flags, makes no IEC calls and is the only interrupt-callable mutation. The other operations are single-instance, mainline-only, non-reentrant.

RAM, IRQ, SID and machine ownership

Optional caller-placed runtime images

Normal calls let the exact linker place code beside the application. For a tightly banked ASM or hybrid program, read-only SDK includes can emit boot-copy images of the same implementation, without a second private port:

web64/loader-transport-runtime.inc, loader-state-runtime.inc, loader-packed-runtime.inc, loader-decoder-runtime.inc, loader-raw-runtime.inc (all under web64/).

For each selected image define WEB64_LOADER_<NAME>_ADDRESS, include it once, and copy web64_loader_<name>_image_size bytes from web64_loader_<name>_image to that address before calling it. The compiler checks logical execution spans against other placed images, assembled data and the configured C stack. Transport must be page-aligned in RAM below $d000 because it executes while KERNAL is visible. Other kernels require writable RAM; they use self-modifying operands. Keep their boot image alive until copied, and never overwrite their logical code/state with a loaded bank. Advanced includes intentionally emit whole module images; the normal linker can trim unused entries more tightly. Ordinary loader.inc still emits no data.

SAVE and error recovery

Use this explicit sequence; no reliable automatic detection of arbitrary external drive writes exists:

  1. Finish the current loader call. Call web64_loader_shutdown() and check OK.
  2. Map KERNAL and perform the application's normal SAVE. Preserve its result separately; loading status and save status are different operations.
  3. Call web64_loader_reinitialize_after_save() before the next fast read.
  4. Restore the application's normal mapping, IRQ and presentation policy. If reinstall failed, leave fast loading unavailable and offer a retry using init once the drive/media is restored.

Reads after shutdown are rejected with NOT_INITIALIZED. Do not call ordinary SAVE while pretending the fast-loader state is still valid. A drive power-cycle, hard error or accepted abort may also require fresh initialization. The runtime never silently retries forever; the application decides when another attempt is appropriate. Do not execute a bank merely because a previous load succeeded.

Format and measured costs

W64X v1's single shared IDE/runtime contract is backward Exomizer P39/M255, 8-byte header, 10-byte records and CRC16/XMODEM. See the IDE manual for byte offsets. Only that bounded profile is supported. This runtime does not fix or expand the separate unrestricted c64lib Exomizer decoder.

Code size depends on called entries, origin and alignment. The source includes independent native closure and CPU-cycle fixtures, plus real PAL/NTSC true-drive fixtures; decoded bytes are compared byte-for-byte, not accepted by appearance. Transport body is about 1.6 KiB, packed validation/CRC about 2.4 KiB, and bounded decoder about 0.7 KiB. Whole-file wrappers add their checked staging policy. Raw-only code does not pay the decoder cost. Sector RAM is 256 bytes; staging is caller-sized; transactional scratch is the sum of selected decoded lengths.

The altered upstream implementations and redistribution conditions are recorded in Hardware loader notices. Retain those notices when distributing substantial loader source or binaries.

Troubleshooting