docs(amiga): complete AmigaOS 3.1/3.2 developer reference — 172 files across 17 sections

Comprehensive technical documentation covering:
- Hardware: OCS/ECS/AGA custom chip registers, Copper & Blitter deep dives
- Boot sequence: cold boot through startup-sequence
- Binary format: HUNK executable spec, relocation, debug info
- Linking & ABI: .fd files, LVO tables, register calling conventions
- Exec kernel: tasks, interrupts, memory, signals, semaphores
- AmigaDOS: file I/O, FFS/OFS layout, CLI/Shell scripting
- Graphics: planar bitmaps, Copper programming, HAM/EHB modes
- Intuition: screens, windows, IDCMP, BOOPSI
- Devices: trackdisk, SCSI, serial, timer, audio, keyboard
- Libraries: utility, expansion, IFFParse, locale, ARexx
- Networking: bsdsocket API, SANA-II, TCP/IP stack comparison
- Toolchain: GCC, vasm/vlink, SAS/C, NDK, debugging
- Reverse engineering: IDA/Ghidra setup, compiler fingerprints, case studies
- CPU & MMU: 68040/060 emulation libs, PMMU, cache management
- Driver development: SANA-II, Picasso96/RTG, AHI audio

All files include breadcrumb navigation. No local paths or proprietary content.
This commit is contained in:
Ilia Sharin 2026-04-23 12:16:52 -04:00
parent f07a368bf1
commit 21751c0025
172 changed files with 19701 additions and 0 deletions

View file

@ -0,0 +1,46 @@
[← Home](../README.md) · [Overview](README.md)
# Amiga Hardware Models Reference
## Model Specification Table
| Model | Year | CPU | MHz | Chipset | Chip RAM | ROM | Expansion |
|---|---|---|---|---|---|---|---|
| A1000 | 1985 | 68000 | 7.14 | OCS | 256 KB | 256 KB | Sidecar |
| A500 | 1987 | 68000 | 7.09 | OCS | 512 KB | 256 KB | Edge connector |
| A2000 | 1987 | 68000 | 7.14 | OCS/ECS | 512 KB1 MB | 256/512 KB | Zorro II, ISA, CPU slot |
| A500+ | 1991 | 68000 | 7.09 | ECS | 1 MB | 512 KB | Edge connector |
| A600 | 1992 | 68000 | 7.09 | ECS | 1 MB | 512 KB | PCMCIA, IDE, trapdoor |
| A3000 | 1990 | 68030 | 16/25 | ECS | 1 MB | 512 KB | Zorro III, ISA, SCSI |
| A1200 | 1992 | 68020 | 14.18 | AGA | 2 MB | 512 KB | PCMCIA, IDE, trapdoor |
| A4000 | 1992 | 68030/040 | 25 | AGA | 2 MB | 512 KB | Zorro III, IDE |
| A4000T | 1994 | 68040/060 | 25 | AGA | 2 MB | 512 KB | Zorro III, SCSI |
| CD32 | 1993 | 68020 | 14.18 | AGA | 2 MB | 512 KB | SX-1, CD-ROM |
## CPU Feature Matrix
| CPU | Bus | Address | I-Cache | D-Cache | MMU | FPU |
|---|---|---|---|---|---|---|
| 68000 | 16-bit | 24-bit | — | — | — | External 68881 |
| 68020 | 32-bit | 32-bit | 256 B direct | — | External 68851 | External 68881/2 |
| 68030 | 32-bit | 32-bit | 256 B | 256 B | On-chip | External 68882 |
| 68040 | 32-bit | 32-bit | 4 KB 4-way | 4 KB 4-way | On-chip | On-chip (partial) |
| 68060 | 32-bit | 32-bit | 8 KB 4-way | 8 KB 4-way | On-chip | On-chip (partial) |
> [!NOTE]
> 68040 and 68060 have on-chip FPUs that omit transcendental instructions. AmigaOS provides `68040.library` and `68060.library` to trap the missing opcodes via Line-F emulation.
## Kickstart ROM Sizes
| OS Version | ROM Size | Part | Models |
|---|---|---|---|
| 1.2 / 1.3 | 256 KB | Single | A500, A2000 |
| 2.04 | 512 KB | Single | A500+, A600, A3000 |
| 3.0 / 3.1 | 512 KB | Single | A1200, A4000 |
| 3.1 | 512 KB + 512 KB Ext | Pair | A4000 (with ext ROM) |
## References
- Commodore *A1200 Technical Reference Manual*`Documentation/A1200/` local archive
- Commodore *A4000 Technical Reference Manual*`Documentation/A4000/` local archive
- ADCD 2.1 Hardware Manual: http://amigadev.elowar.com/read/ADCD_2.1/Hardware_Manual_guide/node0000.html

114
00_overview/history.md Normal file
View file

@ -0,0 +1,114 @@
[← Home](../README.md) · [Overview](README.md)
# Amiga History & Chipset Generations
## Origins (19821985)
The Amiga was designed by Jay Miner's team at Amiga Corporation (originally Hi-Toro), beginning in 1982 under the codename **Lorraine**. The primary design goal was a low-cost personal computer with dedicated custom silicon handling graphics, audio, and DMA — freeing the CPU for application code. Commodore Business Machines acquired Amiga Corporation in 1984, incorporating the technology into what would ship as the **Commodore Amiga 1000** in July 1985.
The core insight was the **coprocessor paradigm**: three custom chips (Agnus, Denise, Paula) operate concurrently with the M68000, driven by a shared DMA bus arbitrated by Agnus. This allowed the Amiga to demonstrate colour animation, digitised speech, and multitasking simultaneously — capabilities competitors would not match for years.
---
## Chipset Generations
### OCS — Original Chip Set (19851990)
| Component | Part Numbers | Role |
|---|---|---|
| **Agnus** | MOS 8361 (PAL), 8367 (NTSC) | DMA controller, Copper, Blitter, address gen |
| **Denise** | MOS 8362 | Display: sprites, bitplanes, colour decode |
| **Paula** | MOS 8364 | Audio DMA (4 channels), disk I/O, serial I/O |
Key characteristics:
- **1 MB Chip RAM** maximum (512 KB in early A1000/A500 configs)
- 6 bitplanes → 64 colours (EHB mode) or 4096 (HAM)
- 8 hardware sprites (16px wide, 2bpp)
- Copper coprocessor: 2 registers, WAIT/SKIP/MOVE instructions
- Blitter: 3 source channels + destination, minterm logic, line mode
Machines using OCS:
- A1000 (1985) — first production Amiga
- A500 (1987) — high-volume consumer model
- A2000 (1987) — big-box, Zorro II expansion
---
### ECS — Enhanced Chip Set (19901992)
| Component | Part Numbers | Role |
|---|---|---|
| **Super Agnus** | MOS 8372A | Agnus + 2 MB chip RAM addressing, BEAMCON0 |
| **ECS Denise** | MOS 8373 | Denise + productivity modes, BPLCON3 |
| **Paula** | MOS 8364 (unchanged) | Same as OCS |
Key enhancements over OCS:
- **2 MB Chip RAM** with Super Agnus (1 MB or 2 MB Agnus variants exist)
- Productivity/multiscan display modes (VGA-compatible timing)
- `BEAMCON0` register for programmable sync signals
- `BPLCON3` for border blank, sprite control extensions
- Super Agnus: larger copper/bitplane DMA window
- Gary chip on A3000: bus controller, DMA, auto-config
- **Gayle** chip on A600: IDE, PCMCIA interface, interrupt routing
Machines using ECS:
- A3000 (1990) — 68030, SCSI, ECS, Zorro III
- A500+ (1991) — enhanced A500, 1 MB chip, ECS
- A600 (1992) — compact, IDE disk, PCMCIA, Gayle
---
### AGA — Advanced Graphics Architecture (19921996)
| Component | Part Numbers | Role |
|---|---|---|
| **Alice** | MOS 8374 | Super Agnus successor: 64-bit bus, FMODE |
| **Lisa** | — | Denise successor: 8-bit palettes, chunky assist |
| **Paula** | MOS 8364 (unchanged) | Same as OCS/ECS |
Key enhancements over ECS:
- **32-bit colour registers**: 24-bit palette (256 colours, HAM8)
- **256 colour registers** (COLOR00COLOR255)
- HAM8 mode: 262,144 simultaneous colours
- **64-bit blitter bus** via `FMODE` register (1x/2x/4x word transfers)
- **BPLCON3 / BPLCON4**: sprite palette bank, bitplane bank select
- **DIWHIGH**: extended display window for overscan
- `FMODE`: configures DMA fetch width for blitter and bitplanes
- **68030/040** CPUs with MMU and FPU
- **Gayle** chip on A1200: IDE + PCMCIA (different pinout from A600)
- **Ramsey** chip on A4000: 32-bit SIMM controller
Machines using AGA:
- A1200 (1992) — budget AGA: 68020, Gayle, PCMCIA
- A4000 (1992) — premium AGA: 68030/040, IDE, Zorro III
- A4000T (1994) — tower, SCSI, Zorro III
- CD32 (1993) — game console, AGA, CD-ROM
---
## AmigaOS Version Timeline
```mermaid
timeline
title AmigaOS Kickstart Timeline
1985 : Kickstart 1.0 (A1000)
1986 : Kickstart 1.1
1987 : Kickstart 1.2 (33.180)
1988 : Kickstart 1.3 (34.5) — most cloned
1990 : Kickstart 2.0 (36.x) — new Shell, ASL, ReAction preview
1991 : Kickstart 2.04 (37.175) — A500+ standard
1992 : Kickstart 3.0 (39.x) — AGA support
1993 : Kickstart 3.1 (40.x) — final Commodore release
2002 : Kickstart 3.9 (Hyperion/Haage&Partner)
2021 : Kickstart 3.2 (47.x) — Hyperion new-generation
```
---
## Key References
- **ADCD 2.1** — Amiga Developer CD, version 2.1 (OS 3.5 era): http://amigadev.elowar.com/read/ADCD_2.1/
- **Hardware Reference Manual** (3rd ed.): `Hardware_Manual_guide/` on ADCD
- **AmigaMail Vol. 2**: `AmigaMail_Vol2_guide/` on ADCD — developer newsletter with deep hardware/OS articles
- Haynie, Dave — *Amiga Hardware Reference Manual* (Addison-Wesley, 1991, ISBN 0-201-56776-8)
- Dewar, R. & Smosna, M. — *The Amiga User Interface Style Guide* (Addison-Wesley, 1992)

148
00_overview/os_versions.md Normal file
View file

@ -0,0 +1,148 @@
[← Home](../README.md) · [Overview](README.md)
# AmigaOS Version Matrix — 3.1 vs 3.2
> **Scope of this documentation:** OS 3.1 (Kickstart 40.x, Workbench 40.x) and OS 3.2 (Kickstart 47.x, Workbench 47.x).
## Kickstart Version Numbers
| OS Version | Kickstart Ver | Exec Ver | Release | Primary Platform |
|---|---|---|---|---|
| OS 3.0 | 39.106 | 39.x | 1992 | A1200/A4000 launch |
| **OS 3.1** | **40.068** | **40.x** | **1993** | **All AGA, final Commodore** |
| OS 3.5 | 45.x | 45.x | 1999 | Hyperion/H&P, WB-only |
| OS 3.9 | 45.x | 45.x | 2000 | Hyperion/H&P, WB-only |
| **OS 3.2** | **47.96+** | **47.x** | **2021** | **Hyperion, all 68k** |
| OS 3.2.2 | 47.102 | 47.x | 2022 | Hyperion |
---
## OS 3.1 Feature Summary (Kickstart 40.x)
This is the final OS release by Commodore before their 1994 bankruptcy. It ships with all production AGA machines (A1200, A4000) and upgrade ROMs for A500/A2000/A3000.
**Exec (40.x):**
- Stable SysBase layout (unchanged since 2.x in most respects)
- `PoolAllocMem` / `PoolFreeMem` — memory pools
- `NewMinList()` macro
- `GetCC()` — condition codes from last instruction
**dos.library (40.x):**
- `ReadArgs()` / `FreeArgs()` — template argument parsing
- `CreateNewProc()` — new process creation API
- `DosGetLocalVar()` / `DosSetLocalVar()` — local environment variables
- `ChangeMode()` — shared/exclusive lock upgrade
- `SameLock()` — lock identity comparison
- `ExAll()` — extended directory examination
**graphics.library (40.x):**
- `AllocBitMap()` / `FreeBitMap()` — dynamic bitmap allocation
- `GetBitMapAttr()` — bitmap attribute query
- `ObtainBestPenA()` — closest-match colour allocation
- `SetRPAttrsA()` / `GetRPAttrsA()` — RastPort attribute tags
- AGA full colour support: `LoadRGB32()`, 256 colour tables
- RTG stubs present but not functional (RTG lives in 3.5+)
**intuition.library (40.x):**
- BOOPSI completely stable
- `OpenWindowTagList()` / `OpenScreenTagList()` — tag-based open calls
- `GetScreenDrawInfo()` / `FreeScreenDrawInfo()`
- `LockPubScreen()` / `UnlockPubScreen()`
- `NewModifyProp()` — proportional gadget update
**locale.library (40.x):**
- Complete locale/catalog system
- `OpenCatalogA()` / `GetCatalogStr()`
**New in 3.1 vs 3.0:**
- `datatypes.library` — class-based data-type system
- Multiview application (uses datatypes)
- `asl.library` — enhanced requesters vs `req.library`
- PCMCIA card resource (`cardres`)
---
## OS 3.2 Delta — What Changed (Kickstart 47.x)
OS 3.2 is a Hyperion-developed modernisation of the 3.1 codebase, first released in 2021.
### Exec (47.x)
- `AllocSysObject()` / `FreeSysObject()` — unified object allocator
- Replaces manual `AllocMem` + init for ports, tasks, IORequests, semaphores
- `NewCreateTask()` — extended task creation with tags
- `IExec->` interface style (for future AOS4 parity — not mandatory on 68k)
- Improved memory tracking and debug support
### dos.library (47.x)
- `BPTR`-free variants of many path functions
- Enhanced `ReadArgs()` template syntax
- `Examine()` extended: `ExamineTags()` returning `ExamineData` structs
- DOS path handling improvements (up to 1024-char paths)
- `AddDosEntry()` / `RemDosEntry()` refined
### graphics.library (47.x)
- Compositing system (alpha blending between screens)
- `CompositeTags()` — hardware-assisted screen composition
- Improved RTG (RastPort to VRAM) support via `cybergraphics.library` integration points
- `AllocSpriteDataA()` extended for RTG sprites
- Palette handling improvements
### intuition.library (47.x)
- New Intuition prefs (IPrefs 47.x)
- Better pen sharing and colour management
- `intuition.library` opens before `graphics.library` in new boot sequence
- Visual prefs: themes, scaled UI elements
### New Libraries in 3.2
| Library | Purpose |
|---|---|
| `disksonar.library` | Fast disk scanning |
| `application.library` | Application registration, task-bar |
| `timezone.library` | Timezone database |
| `identify.library` | Hardware identification |
### AHI Changes (3.2)
- AHI (Audio Hardware Interface) integrated as standard component
- `ahi.device` replaces direct `audio.device` use in 3.2 applications
---
## Cross-Version API Compatibility Table
| Function | 3.1 (40) | 3.2 (47) | Notes |
|---|---|---|---|
| `AllocMem` | ✓ | ✓ | Unchanged |
| `AllocSysObject` | ✗ | ✓ | New in 3.2 |
| `OpenLibrary` | ✓ | ✓ | Unchanged |
| `AllocBitMap` | ✓ | ✓ | Extended in 3.2 |
| `CompositeTags` | ✗ | ✓ | New in 3.2 |
| `ExamineTags` | ✗ | ✓ | New in 3.2 |
| `CreateNewProc` | ✓ | ✓ | Extended |
| `NewCreateTask` | ✗ | ✓ | New in 3.2 |
| `OpenCatalogA` | ✓ | ✓ | Unchanged |
---
## Version Detection in Code
```c
/* Check for OS 3.2+ at runtime */
struct Library *SysBase = *((struct Library **)4);
if (SysBase->lib_Version >= 47) {
/* OS 3.2 features available */
}
/* Check for OS 3.1+ */
if (SysBase->lib_Version >= 40) {
/* AllocBitMap, datatypes etc available */
}
```
---
## References
- NDK 3.9: `NDK39.lha` — headers define version-gated macros
- ADCD 2.1: `AmigaMail_Vol2_guide/` — release notes per OS version
- Hyperion OS 3.2 SDK: https://www.hyperion-entertainment.com/
- `exec/execbase.h``AttnFlags` bits for CPU detection

57
01_hardware/README.md Normal file
View file

@ -0,0 +1,57 @@
[← Home](../README.md)
# Hardware — Chipset Generation Overview
## Generation Comparison
| Feature | OCS | ECS | AGA |
|---|---|---|---|
| Agnus | 8361/8367 | 8372A (Super Agnus) | Alice (8374) |
| Denise | 8362 | 8373 (ECS Denise) | Lisa |
| Paula | 8364 | 8364 (unchanged) | 8364 (unchanged) |
| Max Chip RAM | 512 KB1 MB | 12 MB | 2 MB |
| Colours (max normal) | 32 | 32 | 256 |
| HAM | 12-bit HAM (6bpp) | 12-bit HAM | 24-bit HAM8 (8bpp) |
| Sprites | 8 × 16px | 8 × 16px | 8 × 64px, 256 colours |
| Blitter bus | 16-bit | 16-bit | 64-bit (FMODE) |
| Display modes | NTSC/PAL | +Productivity, VGA | +Doublescan, 31kHz |
| Machines | A500/A1000/A2000 | A600/A3000/A500+ | A1200/A4000/CD32 |
---
## Custom Register Address Space
All chipset generations share the base address **$00DFF000** for custom registers. Registers are memory-mapped, mostly 16-bit wide.
```
$DFF000 BLTDDAT Blitter destination early read
$DFF002 DMACONR DMA control (read)
$DFF004 VPOSR Vertical position (read, high)
...
$DFF180 COLOR00 Colour register 0
...
$DFF1FE (last OCS/ECS register)
$DFF1FC BEAMCON0 (ECS+) beam control
$DFF1FC+ (AGA extensions)
```
See [custom_registers_full.md](../references/custom_registers_full.md) for the complete table across all chipsets.
---
## Navigation
| Subfolder | Content |
|---|---|
| [common/](common/) | M68k CPU, address space layout, CIA chips, Zorro bus |
| [ocs_a500/](ocs_a500/) | OCS chipset — A500, A1000, A2000 |
| [ecs_a600_a3000/](ecs_a600_a3000/) | ECS chipset — A600, A3000, A500+ |
| [aga_a1200_a4000/](aga_a1200_a4000/) | AGA chipset — A1200, A4000, CD32 |
---
## References
- *Amiga Hardware Reference Manual* 3rd ed. — `Hardware_Manual_guide/` on ADCD 2.1
- ADCD 2.1: http://amigadev.elowar.com/read/ADCD_2.1/Hardware_Manual_guide/node0000.html
- AmigaMail Vol.2 — chipset programming articles

View file

@ -0,0 +1,69 @@
[← Home](../../README.md) · [Hardware](../README.md)
# AGA Chipset — A1200 / A4000 / CD32
## Overview
The **Advanced Graphics Architecture** (AGA) is the final custom chipset developed by Commodore, shipping from 1992. It dramatically expands colour depth, palette size, sprite capabilities, and blitter bandwidth while retaining full OCS/ECS backward compatibility.
## Chip Summary
| Chip | Name | Changes from ECS |
|---|---|---|
| **Alice** | MOS 8374 | Super Agnus successor: 64-bit bus, FMODE register |
| **Lisa** | (unnamed MOS) | ECS Denise successor: 8-bit palette, 256 colours |
| **Paula** | MOS 8364 | Unchanged from OCS/ECS |
## Contents
| File | Topic |
|---|---|
| [chipset_aga.md](chipset_aga.md) | Alice and Lisa internals, AGA architecture |
| [aga_registers_delta.md](aga_registers_delta.md) | New/changed registers vs ECS |
| [aga_palette.md](aga_palette.md) | 24-bit colour system, 256 registers |
| [aga_display_modes.md](aga_display_modes.md) | HAM8, 256-colour, doublescan, VGA |
| [aga_blitter.md](aga_blitter.md) | 64-bit blitter bus, FMODE |
| [cpu_030_040.md](cpu_030_040.md) | 68030/040 on A3000/A4000: cache, MMU, FPU |
| [gayle_ide_a1200.md](gayle_ide_a1200.md) | A1200 Gayle: IDE and PCMCIA specifics |
## AGA vs ECS — Key Differences
| Feature | ECS | AGA |
|---|---|---|
| Colour registers | 32 (12-bit) | **256 (24-bit)** |
| Max simultaneous colours | 64 EHB / HAM | **256** (or HAM8: 262,144) |
| Blitter bus | 16-bit | **64-bit** (FMODE) |
| Sprite width | 16 px | **64 px** |
| Sprite colours | 3+transparent | **15+transparent** (64-colour attached) |
| Bitplane depth | 6 planes max | **8 planes** |
| Palette select | 1 bank | **4 bitplane banks, 4 sprite banks** |
## Identifying AGA at Runtime
```c
#include <graphics/gfxbase.h>
extern struct GfxBase *GfxBase;
BOOL is_aga = (GfxBase->ChipRevBits0 & (1 << GFXB_AA_ALICE)) != 0;
```
| ChipRevBits0 bit | Flag | Meaning |
|---|---|---|
| 4 | `GFXB_AA_ALICE` | AGA Alice present |
| 5 | `GFXB_AA_LISA` | AGA Lisa present |
## AGA Machines
| Model | CPU | Notes |
|---|---|---|
| A1200 | 68020 14 MHz | Budget AGA; Gayle IDE; PCMCIA; 2 MB Chip |
| A4000 | 68030/040 25 MHz | High-end; Zorro III; IDE; 2 MB Chip + Fast |
| A4000T | 68040/060 | Tower variant; SCSI |
| CD32 | 68020 14 MHz | Game console; CD-ROM; SX-1 expansion |
## References
- ADCD 2.1 Hardware Manual — AGA chapters
- NDK39: `graphics/gfxbase.h`, `hardware/custom.h`
- Commodore A1200/A4000 Technical Reference Manuals (local archive)

View file

@ -0,0 +1,167 @@
[← Home](../../README.md) · [Hardware](../README.md) · [AGA](README.md)
# AGA Blitter — What It Is, What It Does, and 64-bit Bus Mode
## What Is the Blitter?
The **Blitter** (Block Image Transfer) is a dedicated DMA hardware engine inside every Amiga, designed to move and transform rectangular blocks of bitmap data **without CPU intervention**. While the CPU handles game logic, AI, physics, and sound setup, the Blitter does the heavy graphical lifting in the background.
Think of it as a **hardware GPU for 2D raster operations** — years before PC graphics cards existed.
### What the Blitter Can Do
| Capability | Description | Typical Use |
|---|---|---|
| **Block copy** | Move rectangular bitmap regions | Scrolling backgrounds, restoring screen areas |
| **Cookie-cut blit** | Stamp a shape onto a background using a mask | Drawing game sprites (BOBs) with transparency |
| **Area fill** | Fill arbitrary shapes | Flood-filling polygons, UI element backgrounds |
| **Line drawing** | Draw lines using hardware Bresenham | Vector graphics, wireframe 3D |
| **Clear/set memory** | Zero or fill large memory blocks | Screen clearing between frames |
| **Logic operations** | Combine 3 inputs with any Boolean function | XOR cursors, shadow effects, masking |
| **Shifting** | Shift source data 015 pixels | Sub-word-aligned sprite positioning |
### What the Blitter Cannot Do
| Limitation | Detail |
|---|---|
| No scaling | Cannot resize images — fixed 1:1 pixel mapping |
| No rotation | Cannot rotate — must be pre-rendered in software |
| No 3D | No perspective, texture mapping, or Z-buffer |
| No chunky pixels | Operates on **planar** bitplanes only (1 plane at a time) |
| No colour blending | Pure Boolean logic — no alpha, no transparency gradients |
| Word-aligned width | Minimum operation width is 16 pixels (1 word) |
### How Software Uses It
**Games** — The blitter draws BOBs (Blitter Objects = software sprites) by cookie-cutting character graphics onto the playfield. A typical game frame:
1. Blitter restores background behind old sprite positions
2. Game logic updates positions
3. Blitter draws sprites at new positions using cookie-cut
4. Copper switches display to the newly drawn buffer (double-buffering)
**Demos** — Demoscene coders exploit every blitter trick: interleaved bitplane blits, fill-mode for filled vector polygons, line-mode for wireframes, and careful DMA scheduling to run blitter and CPU in parallel.
**Applications** — Workbench uses `BltBitMap()` for window dragging, scrolling text, and refreshing damaged screen areas. The `layers.library` damage repair system depends entirely on blitter operations.
---
## AGA Enhancements
AGA's Alice chip extends the blitter with a **64-bit data bus mode** controlled by the `FMODE` register. This allows the blitter to fetch 2 or 4 words per DMA cycle, dramatically increasing fill and copy throughput for large bitmap operations.
## FMODE Configuration
`FMODE` ($DFF1FC) controls blitter, bitplane, and sprite DMA fetch widths independently:
```
bits 9-8: BLT_FMODE — blitter fetch mode
00 = 16-bit (OCS compatible)
01 = 32-bit
10 = 64-bit
11 = reserved
bits 13-12: BPL_FMODE — bitplane fetch mode (same encoding)
bits 15-14: SPR_FMODE — sprite fetch mode (same encoding)
```
Setting 64-bit blitter mode:
```asm
move.w #$0300, $DFF1FC ; BLT_FMODE = 10 (64-bit)
```
> [!IMPORTANT]
> FMODE must be set **before** loading blitter registers and starting the blit. Changing FMODE mid-blit causes undefined behaviour.
## Width Calculation with FMODE
When using wider fetch modes, **BLTSIZE widths must be adjusted**:
| FMODE | Width unit | Width formula |
|---|---|---|
| 1× (16-bit) | 1 word = 16 bits | `width_in_words` |
| 2× (32-bit) | 2 words = 32 bits | `(width_in_words + 1) / 2` |
| 4× (64-bit) | 4 words = 64 bits | `(width_in_words + 3) / 4` |
Example: blitting 320 pixels wide (20 words):
- 1× mode: BLTSIZE width = 20
- 2× mode: BLTSIZE width = 10
- 4× mode: BLTSIZE width = 5
**Modulo values are still in bytes** regardless of FMODE.
## Performance Comparison
For a typical 320×200 blit (simple copy, 1 bitplane):
| Mode | FMODE | Approx time (7 MHz) |
|---|---|---|
| OCS/ECS | 1× | ~1.8 ms |
| AGA 2× | 2× | ~0.9 ms |
| AGA 4× | 4× | ~0.45 ms |
## Alignment Requirements
Wider fetch modes impose alignment constraints on source/destination pointers:
| FMODE | Required alignment |
|---|---|
| 1× | 2 bytes (word) |
| 2× | 4 bytes (long) |
| 4× | 8 bytes (quadword) |
Misaligned pointers with 2× or 4× FMODE produce incorrect blit results.
## Using graphics.library
The OS `BltBitMap()` / `BltBitMapRastPort()` calls are FMODE-aware in OS 3.1+ on AGA hardware:
```c
BltBitMap(src_bm, srcx, srcy, dst_bm, dstx, dsty, width, height,
0xC0, 0xFF, NULL); /* minterm $C0 = copy, all planes */
```
`graphics.library` automatically selects the optimal FMODE for the current hardware.
## BLTCON0 / BLTCON1 — Unchanged in AGA
The minterm logic (`BLTCON0`) and fill/line mode (`BLTCON1`) registers are functionally identical to OCS/ECS. AGA only adds bandwidth, not new logical capabilities.
## Direct AGA Blitter Example (64-bit clear)
```asm
; Clear 320×256 bitmap at address $100000 (20 words wide, 256 lines)
; Using AGA 4× FMODE (5 quads wide)
move.w #$0300, $DFF1FC ; FMODE: BLT_FMODE = 4×
move.w #$0100, BLTCON0+custom ; USE D only, minterm $00 (fill with zero)
move.w #$0000, BLTCON1+custom
move.w #$FFFF, BLTAFWM+custom ; first word mask
move.w #$FFFF, BLTALWM+custom ; last word mask
move.l #$00100000, BLTDPTH+custom+$00 ; dest high word
; (split into high/low)
move.w #$0010, BLTDPTH+custom
move.w #$0000, BLTDPTL+custom
move.w #0, BLTDMOD+custom ; modulo = 0 (contiguous)
; BLTSIZE = (256 lines << 6) | 5 quads width = (256*64)|5 = $4005
move.w #((256<<6)|5), BLTSIZE+custom ; start blit
; wait for completion
WaitBlit:
btst #6, DMACONR+custom+1
bne.s WaitBlit
; Restore FMODE to 1× for safety
move.w #$0000, $DFF1FC
```
## References
- ADCD 2.1 Hardware Manual — AGA blitter section
- NDK39: `hardware/blit.h`, `hardware/custom.h`
- Commodore A1200/A4000 Technical Reference Manuals — Alice section
- graphics.library Autodocs: BltBitMap, BltClear

View file

@ -0,0 +1,371 @@
[← Home](../../README.md) · [Hardware](../README.md) · [AGA](README.md)
# AGA Copper — What It Is, How It Works, and Programming Guide
## What Is the Copper?
The **Copper** (Co-Processor) is one of the most distinctive pieces of hardware in any computer ever built. It is a tiny, ultra-simple programmable DMA engine that executes a program — called a **copper list** — in perfect synchronisation with the video beam as it sweeps across the screen.
The Copper watches the beam position and can **write any value to any custom chip register at any specific screen position**. This single capability enables an astonishing range of visual effects.
### Why Does It Matter?
On a conventional computer, changing display parameters (colours, scroll positions, resolutions) requires the CPU to execute code at precisely the right moment. This is fragile, wastes CPU time, and is limited by interrupt latency.
The Copper does this **automatically, for free, with perfect timing** — every single frame, without any CPU involvement at all.
---
## Copper in the System Architecture
### Block Diagram
```
┌─────────────────────────────────────────────────────────────────────┐
│ CHIP RAM (up to 2MB) │
│ ┌──────────────┐ ┌──────────────┐ ┌───────────────────────────┐ │
│ │ Copper List │ │ Bitplane Data│ │ Sprite Data │ │
│ │ (MOVE/WAIT/ │ │ (screen │ │ (hardware sprite │ │
│ │ SKIP words) │ │ pixels) │ │ images) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────────┬────────────────┘ │
└─────────┼──────────────────┼─────────────────────┼──────────────────┘
│ DMA read │ DMA read │ DMA read
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────────┐
│ AGNUS / ALICE (DMA Controller) │
│ │
│ ┌────────────┐ ┌──────────────┐ ┌───────────────────────┐ │
│ │ COPPER │ │ Bitplane │ │ Sprite DMA │ │
│ │ Engine │ │ DMA Engine │ │ Engine │ │
│ │ │ │ │ │ │ │
│ │ • Fetches │ │ Fetches 1-8 │ │ Fetches sprite │ │
│ │ copper │ │ planes per │ │ data for 8 sprites │ │
│ │ list via │ │ line from │ │ per line │ │
│ │ DMA │ │ BPLxPT │ │ │ │
│ │ • Compares │ │ pointers │ │ │ │
│ │ beam pos │ │ │ │ │ │
│ │ • Writes │ │ │ │ │ │
│ │ to regs │ │ │ │ │ │
│ └─────┬──────┘ └──────┬───────┘ └───────────┬───────────┘ │
│ │ │ │ │
│ ┌─────┴──────────────────┴────────────────────────┴─────────┐ │
│ │ BEAM COUNTER (V count, H count) │ │
│ │ Increments every colour clock, resets each frame │ │
│ │ PAL: 312 lines × 227 clocks NTSC: 262 × 227 │ │
│ └───────────────────────────────────────────────────────────┘ │
└──────────┬─────────────────────────────────────────────────────────┘
│ register writes ($DFF000$DFF1FE)
┌─────────────────────────────────────────────────────────────────────┐
│ DENISE / LISA (Video Encoder) │
│ │
│ Receives bitplane data + sprite data + colour register values │
│ Composites them into a final pixel stream: │
│ │
│ ┌────────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │ Bitplane │ │ Sprite │ │ Colour │ │ Playfield │ │
│ │ Decode │→ │ Priority │→ │ Palette │→ │ Priority & │ │
│ │ (planar→ │ │ Merge │ │ Lookup │ │ Genlock Control │ │
│ │ index) │ │ │ │ (32/256) │ │ │ │
│ └────────────┘ └──────────┘ └──────────┘ └────────┬─────────┘ │
└───────────────────────────────────────────────────────┬─────────────┘
┌────────────┐
│ RGB / DAC │
│ → Monitor │
└────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ M68K CPU │
│ │
│ • Can read/write the SAME custom registers as the Copper │
│ • Can modify the copper list in Chip RAM at any time │
│ • Shares the DMA bus with Copper (Agnus arbitrates) │
│ • CPU is STALLED when DMA bus is busy (Chip RAM access only) │
│ • Fast RAM access is NOT affected by DMA contention │
└─────────────────────────────────────────────────────────────────────┘
```
### Component Interactions
**Copper ↔ Chip RAM:**
The Copper fetches its program (the copper list) from Chip RAM via DMA. It reads one instruction (2 words = 4 bytes) every 4 colour clocks. The copper list **must** reside in Chip RAM — it cannot be in Fast RAM because only Chip RAM is DMA-accessible.
**Copper ↔ Custom Registers:**
When the Copper executes a MOVE instruction, it writes directly to a custom chip register (`$DFF000``$DFF1FE`). This is the exact same register space the CPU writes to. The Copper can set colours, bitplane pointers, sprite pointers, display window, scroll offsets, DMA control, and audio parameters.
**Copper ↔ Beam Counter:**
The Copper continuously compares the current beam position (V count, H count) against WAIT instructions. When the beam reaches or passes the specified position, execution continues. This is a hardware comparator — no polling loop, no interrupt latency.
**Copper ↔ CPU:**
- They share the DMA bus. Agnus arbitrates: DMA engines get priority, CPU gets remaining cycles
- The CPU can modify copper list words in Chip RAM; changes take effect on the next frame (or immediately if the Copper hasn't read that instruction yet)
- The CPU can set `COP1LC`/`COP2LC` to change which copper list runs
- The CPU can strobe `COPJMP1`/`COPJMP2` to restart the Copper mid-frame
- The CPU is **not interrupted** by Copper activity — they are fully independent
**Copper ↔ Video Output:**
The Copper doesn't produce video directly. It modifies the registers that Denise/Lisa uses to produce video. By changing registers at specific beam positions, the Copper indirectly controls what appears on every scanline.
**Copper ↔ Blitter:**
A WAIT instruction with bit 15 of the mask word cleared becomes a "blitter-finished WAIT" — the Copper pauses until both the beam position is reached AND the blitter is idle. This coordinates copper list execution with blitter operations.
---
## What the Copper Can Do
| Effect | How | Used In |
|---|---|---|
| **Per-line colour changes** | WAIT for line, MOVE colour register | Gradient skies, rainbow bars |
| **Split screens** | Change bitplane pointers mid-frame | Status bar + scrolling playfield |
| **Parallax scrolling** | Change BPLCON1 (scroll offset) at different lines | Multi-layer side-scrollers |
| **Resolution changes** | Change BPLCON0 mid-frame | HiRes menu + LoRes game area |
| **Sprite multiplexing** | Repoint sprite DMA pointers after sprite finishes | More than 8 sprites per frame |
| **Palette animation** | Modify colour registers each frame | Cycling colours, water shimmer |
| **Display window tricks** | Change DIWSTRT/DIWSTOP | Overscan, letterbox |
| **Interlace tricks** | Toggle LOF bit | Custom interlace effects |
### What the Copper Cannot Do
| Limitation | Detail |
|---|---|
| No computation | Cannot add, subtract, compare, branch, or loop |
| No memory read | Can only WRITE to registers, never read |
| Write-only to custom regs | Cannot write to CPU memory, CIA, or Fast RAM |
| Limited register set | Protected registers ($000$03E) need `COPCON` unlock |
| No sub-pixel timing | Horizontal resolution is 4 colour clocks (~8 low-res pixels) |
| Vertical wrapping | V counter wraps at 255; PAL lines 256+ need two WAITs |
---
## Instruction Set
The Copper has exactly **3 instructions**. Each is 32 bits (two 16-bit words):
### MOVE — Write Value to Register
```
Word 1: 0RRRRRRR RR000000 R = register offset ($040$1FE, even)
Word 2: DDDDDDDD DDDDDDDD D = 16-bit data to write
Example: Set COLOR00 ($180) to bright red ($0F00):
dc.w $0180, $0F00
```
The register address in word 1 is the offset from `$DFF000`. Bit 0 of word 1 is always 0 (this distinguishes MOVE from WAIT/SKIP).
### WAIT — Wait for Beam Position
```
Word 1: VVVVVVVV HHHHHHHH V = vertical beam (8 bits), H = horizontal
Bit 0 = 1 (marks this as WAIT, not MOVE)
Word 2: vvvvvvvv hhhhhhhm v,h = mask bits, m = 0 for WAIT
(bit 0 of word 2 = 0 distinguishes from SKIP)
Example: Wait for line 100 ($64), any horizontal position:
dc.w $6401, $FFFE
```
The mask controls which beam position bits are compared. `$FFFE` means "match all bits" (standard). You can mask horizontal bits to wait for a specific column.
### SKIP — Conditional Skip
```
Same format as WAIT, but bit 0 of word 2 = 1.
If beam ≥ specified position, skip the next instruction.
Example: Skip next if beam is past line 200:
dc.w $C801, $FFFF ; SKIP if V ≥ $C8
dc.w $0180, $0F00 ; this MOVE is skipped if condition met
```
### End of List
```
dc.w $FFFF, $FFFE ; WAIT for impossible position (V=$FF, H=$FF)
; Copper halts until next frame
```
---
## Your First Copper List
Here's the simplest possible copper list — it changes the background colour at line 128:
```asm
SECTION copperlist,DATA_C ; *** MUST be in Chip RAM! ***
MyCopperList:
; Top half: blue background
dc.w $0180, $005F ; MOVE COLOR00 = blue
; Wait for middle of screen
dc.w $8001, $FFFE ; WAIT line 128
; Bottom half: red background
dc.w $0180, $0F00 ; MOVE COLOR00 = red
; End of list
dc.w $FFFF, $FFFE ; WAIT forever
```
To activate it:
```asm
lea $DFF000,a5
move.l #MyCopperList,$080(a5) ; COP1LCH = pointer to our list
move.w d0,$088(a5) ; COPJMP1 strobe = restart copper
move.w #$8280,$096(a5) ; DMACON: enable Copper + Master DMA
```
---
## Rainbow Gradient (Colour Per Scanline)
```asm
RainbowCopper:
dc.w $2C01,$FFFE ; WAIT line 44 (first visible PAL line)
dc.w $0180,$0F00 ; red
dc.w $2D01,$FFFE ; line 45
dc.w $0180,$0E10
dc.w $2E01,$FFFE ; line 46
dc.w $0180,$0D20
dc.w $2F01,$FFFE ; line 47
dc.w $0180,$0C30
dc.w $3001,$FFFE ; line 48
dc.w $0180,$0B40
dc.w $3101,$FFFE ; line 49
dc.w $0180,$0A50
dc.w $3201,$FFFE ; line 50
dc.w $0180,$0960
; ... continue for each line ...
dc.w $FFFF,$FFFE
```
This produces a smooth colour gradient down the screen — **zero CPU cost**.
---
## Parallax Scrolling (Per-Layer Scroll Speed)
```asm
ParallaxCopper:
; Sky layer (no scroll)
dc.w $0102,$0000 ; BPLCON1 = 0 (no shift)
; Wait for horizon
dc.w $6001,$FFFE ; WAIT line 96
; Hills layer (scroll slow)
dc.w $0102,$0022 ; BPLCON1 = shift 2 pixels
; Wait for ground
dc.w $A001,$FFFE ; WAIT line 160
; Ground layer (scroll fast)
dc.w $0102,$0066 ; BPLCON1 = shift 6 pixels
dc.w $FFFF,$FFFE
```
Each frame, a VBlank interrupt updates the scroll values in the copper list. The CPU just modifies 3 words — the Copper handles all the per-line register changes.
---
## Sprite Multiplexing
The Amiga has 8 hardware sprites, but the Copper can repoint them mid-frame for more:
```asm
; Sprite 0 shows character A at Y=50
dc.w $3001,$FFFE ; WAIT before sprite starts
dc.w $0120,SprDataA>>16 ; SPR0PTH
dc.w $0122,SprDataA ; SPR0PTL
; After sprite A finishes (Y=66), reuse for character B at Y=120
dc.w $7801,$FFFE ; WAIT line 120
dc.w $0120,SprDataB>>16 ; SPR0PTH = new sprite data
dc.w $0122,SprDataB ; SPR0PTL
; After character B finishes, reuse for character C...
dc.w $A001,$FFFE
dc.w $0120,SprDataC>>16
dc.w $0122,SprDataC
```
This gives you **24+ sprites** on screen (8 physical × 3+ reuses per frame).
---
## System-Friendly Copper (OS API)
If your program coexists with Workbench, use `graphics.library`:
```c
struct UCopList *ucl = AllocMem(sizeof(struct UCopList), MEMF_CLEAR);
CINIT(ucl, 50); /* init, max 50 instructions */
CWAIT(ucl, 0, 0); /* wait top of screen */
CMOVE(ucl, custom.color[0], 0x005F); /* COLOR00 = blue */
CWAIT(ucl, 128, 0); /* wait line 128 */
CMOVE(ucl, custom.color[0], 0x0F00); /* COLOR00 = red */
CEND(ucl); /* end */
viewport->UCopIns = ucl;
RethinkDisplay(); /* merge into system copper list */
```
The OS inserts your instructions into its own copper list, interleaved with its own display management.
---
## AGA Copper Enhancements
AGA (Alice chip) keeps the same 3-instruction Copper but gains access to the **extended AGA registers**:
| AGA Feature | Copper Can Set |
|---|---|
| 256-colour palette | `COLOR00COLOR255` via BPLCON3 bank select |
| Extended sprites | 64-colour sprites via palette banks |
| FMODE | DMA fetch width (but careful — affects in-progress DMA) |
| BPLCON3/BPLCON4 | AGA-specific bitplane/sprite control |
### AGA Palette via Copper
AGA has 256 colours but still only 32 colour registers visible at a time. To load all 256 colours, the Copper uses BPLCON3 to select palette banks:
```asm
; Load colours 031 (bank 0)
dc.w $0106,$0000 ; BPLCON3: bank 0
dc.w $0180,$0000 ; COLOR00
dc.w $0182,$0111 ; COLOR01
; ... all 32 colours ...
; Switch to bank 1 (colours 3263)
dc.w $0106,$2000 ; BPLCON3: bank 1
dc.w $0180,$0222 ; COLOR32
dc.w $0182,$0333 ; COLOR33
; ... etc for all 8 banks ...
```
---
## Copper Timing
| Parameter | Value |
|---|---|
| Instruction time | 4 colour clocks (= 8 lo-res pixels = ~1.12 µs) |
| Max instructions per line | ~112 (NTSC) / ~114 (PAL) |
| Horizontal resolution | 4 colour clocks (~8 lo-res pixels) |
| Vertical range | 0255 (wraps; use double-WAIT for PAL lines 256+) |
| PAL visible lines | 44300 (256 visible) |
| NTSC visible lines | 44244 (200 visible) |
---
## References
- HRM: *Copper* chapter — authoritative register descriptions
- `08_graphics/copper.md` — graphics.library UCopList API
- `08_graphics/copper_programming.md` — additional examples
- `01_hardware/ocs_a500/copper.md` — OCS-level register reference

View file

@ -0,0 +1,122 @@
[← Home](../../README.md) · [Hardware](../README.md) · [AGA](README.md)
# AGA Display Modes
## Overview
AGA introduces several new display modes and extends existing ones, enabled via the OS **screenmode** system (`graphics/modeid.h`) and `OpenScreenTags()`.
## Mode ID System
AmigaOS uses 32-bit **ModeIDs** to identify display modes. The format is:
```
[Monitor ID (16 bits)] | [Mode flags (16 bits)]
```
Key AGA monitor IDs:
```c
#define PAL_MONITOR_ID 0x00000000
#define NTSC_MONITOR_ID 0x00010000
#define A2024_MONITOR_ID 0x00020000
#define VGA_MONITOR_ID 0x00031000 /* ECS productivity */
#define A2024_10HZ_ID 0x00040000
#define DBLPAL_MONITOR_ID 0x00420000 /* AGA double PAL */
#define DBLNTSC_MONITOR_ID 0x00400000 /* AGA double NTSC */
#define SUPER72_MONITOR_ID 0x00080000
```
Mode flags (lower 16 bits):
```c
#define LORES_KEY 0x0000 /* 320 wide (PAL) */
#define HIRES_KEY 0x8000 /* 640 wide */
#define HAM_KEY 0x0800 /* HAM mode */
#define EXTRAHALFBRITE_KEY 0x0080 /* EHB */
#define LACE_KEY 0x0004 /* interlace */
```
## Standard AGA Modes
| Mode ID | Resolution | Colours | H rate |
|---|---|---|---|
| `PAL_MONITOR_ID \| LORES_KEY` | 320×256 | 256 | 15.6 kHz |
| `PAL_MONITOR_ID \| HIRES_KEY` | 640×256 | 256 | 15.6 kHz |
| `PAL_MONITOR_ID \| HIRES_KEY \| LACE_KEY` | 640×512 | 256 | 15.6 kHz (interlace) |
| `DBLPAL_MONITOR_ID \| LORES_KEY` | 320×512 | 256 | 31.25 kHz |
| `DBLPAL_MONITOR_ID \| HIRES_KEY` | 640×512 | 256 | 31.25 kHz |
| `PAL_MONITOR_ID \| LORES_KEY \| HAM_KEY` | 320×256 | 262,144 (HAM8) | 15.6 kHz |
| `SUPER72_MONITOR_ID \| HIRES_KEY` | 800×600 (approx) | 256 | 28+ kHz |
## Opening an AGA Screen (OS 3.1)
```c
#include <intuition/screens.h>
#include <graphics/modeid.h>
#include <proto/intuition.h>
struct Screen *scr;
/* 256-colour AGA screen, PAL, 320×256 */
scr = OpenScreenTags(NULL,
SA_DisplayID, PAL_MONITOR_ID | LORES_KEY,
SA_Width, 320,
SA_Height, 256,
SA_Depth, 8, /* 8 bitplanes = 256 colours */
SA_Colors32, (ULONG)colour_table, /* LoadRGB32 format */
SA_Title, (ULONG)"My AGA Screen",
SA_Quiet, TRUE,
TAG_DONE);
```
## HAM8 Screen
```c
scr = OpenScreenTags(NULL,
SA_DisplayID, PAL_MONITOR_ID | LORES_KEY | HAM_KEY,
SA_Width, 320,
SA_Height, 256,
SA_Depth, 8,
TAG_DONE);
```
> [!NOTE]
> HAM8 screens require 8 bitplanes. The display system automatically programmes BPLCON0 with HAM=1. The first 64 colour registers are used as the HAM8 index palette.
## BestModeID() — Querying Available Modes
```c
#include <proto/graphics.h>
ULONG modeid = BestModeID(
BIDTAG_NominalWidth, 640,
BIDTAG_NominalHeight, 512,
BIDTAG_Depth, 8,
BIDTAG_MonitorID, DBLPAL_MONITOR_ID,
TAG_DONE);
if (modeid == INVALID_ID) {
/* Requested mode not available */
}
```
## Double Scan Modes (DblPAL / DblNTSC)
`DBLPAL` and `DBLNTSC` modes use AGA's scan doubler to produce non-interlaced 31 kHz output from PAL/NTSC timing:
- DblPAL: 320×512 or 640×512, 31.25 kHz — compatible with VGA monitors
- DblNTSC: 320×400 or 640×400, 31.47 kHz
These require a multisync monitor and AGA chipset. The A1200 can drive a 1084S in scan-doubled mode.
## Super72 Mode
Super72 provides approximately 800×600 resolution at ~28 kHz horizontal:
- Used by some Workbench productivity configurations
- Requires multisync monitor
- Available via `SUPER72_MONITOR_ID`
## References
- NDK39: `graphics/modeid.h` — all monitor and mode ID definitions
- ADCD 2.1: `Libraries_Manual_guide/` — graphics.library OpenScreen
- Autodocs: `BestModeID`, `OpenScreenTags`
- AmigaMail Vol. 2 — AGA display mode programming

View file

@ -0,0 +1,134 @@
[← Home](../../README.md) · [Hardware](../README.md) · [AGA](README.md)
# AGA Palette & Colour System
## Overview
AGA provides **256 colour registers** (COLOR00COLOR255), each **24-bit RGB** (8 bits per channel). This replaces OCS/ECS's 32 registers with 12-bit colour.
## Colour Register Layout
```
ADDRESS: $DFF180 + (n × 2) for register n (0255)
```
AGA extends the register space:
```
$DFF180$DFF1BE COLOR00COLOR31 (same addresses as OCS/ECS)
$DFF180$DFF3BE COLOR00COLOR255 (full AGA range — needs BPLCON4 bank select)
```
The 256 colour registers are accessed in 64-register **banks** selected by `BPLCON4`.
## Writing 24-bit Colours
Each colour register holds 12 bits directly (OCS/ECS compatible). The upper 12 bits (the "low nibble") are written via a second access with `LOCT` set in BPLCON3.
### Manual 24-bit write sequence:
```asm
; Write color $FF8040 (R=$FF, G=$80, B=$40) to COLOR00
; Step 1: Write high nibble ($F84 = top 4 bits of R,G,B)
move.w #$0F84, COLOR00+custom ; $0RGB high nibble
; Step 2: Set LOCT bit in BPLCON3 to enable low nibble write
bset #9, BPLCON3+custom+1 ; bit 9
; Step 3: Write low nibble ($F04 = low 4 bits of R,G,B → $0F, $08, $04 → $F84 again!)
; Actually: low nibble of FF = F, low nibble of 80 = 0, low nibble of 40 = 0
move.w #$0F00, COLOR00+custom ; low nibble
; Step 4: Clear LOCT
bclr #9, BPLCON3+custom+1
```
### Using LoadRGB32()
The preferred OS call:
```c
#include <graphics/view.h>
#include <proto/graphics.h>
/* Table: count, index, then 0x00RRGGBB values, terminated by ~0 */
ULONG table[] = {
4, 0, /* 4 colours starting at index 0 */
0x00FF0000, /* COLOR00: red */
0x0000FF00, /* COLOR01: green */
0x000000FF, /* COLOR02: blue */
0x00FFFFFF, /* COLOR03: white */
~0UL /* terminator */
};
LoadRGB32(viewport, table);
```
`LoadRGB32()` (graphics.library LVO -$192) is the AGA-correct way to set colours. It handles the two-write LOCT protocol internally and is available from OS 3.0+.
### Using LoadRGB4() — OCS/ECS compatible (12-bit)
```c
UWORD colours[32] = { 0x000, 0xF00, 0x0F0, ... };
LoadRGB4(viewport, colours, 32); /* sets 32 colours from 12-bit table */
```
`LoadRGB4()` is safe on all chipsets but only provides 12-bit precision on AGA.
## HAM8 Mode (Hold-And-Modify, 8-bit)
HAM8 is the AGA extension of OCS's HAM6. It uses 8 bitplanes:
- **Bits 7-6** of each pixel: mode select
- `00` = index mode (look up COLOR00COLOR63)
- `01` = modify blue channel
- `10` = modify red channel
- `11` = modify green channel
- **Bits 5-0**: 6-bit value for the selected channel (or 6-bit colour index)
Result: 2^18 = **262,144 simultaneous colours** from adjacent-pixel modification.
Enabling HAM8:
```asm
; BPLCON0: 8 planes (BPU=8, BPU3=1, BPU2-0=000), HAM=1, ECSENA=1
move.w #$9811, BPLCON0+custom
```
## Colour Modes Summary
| Mode | Planes | Colours | Method |
|---|---|---|---|
| Standard | 18 | 2256 | Direct palette lookup |
| EHB | 6 | 64 | Extra Half-Brite (OCS/ECS compat) |
| HAM6 | 6 | 4096 | Hold-and-modify 4-bit channels |
| HAM8 | 8 | 262,144 | Hold-and-modify 6-bit channels |
| Dual Playfield | 3+3 | 8+8 | Two independent 8-colour layers |
## Colour Bank Selection (BPLCON4)
The 256 colour registers are split into 4 banks of 64:
| BPLAM | Bank | Registers |
|---|---|---|
| $00 | 0 | COLOR00COLOR63 |
| $40 | 1 | COLOR64COLOR127 |
| $80 | 2 | COLOR128COLOR191 |
| $C0 | 3 | COLOR192COLOR255 |
Dual playfield can use BPLCON4 to give each playfield a different 64-colour bank.
## OS Colour Management
`graphics.library` manages palette via the `ColorMap` structure attached to a `ViewPort`:
```c
struct ColorMap *cm = GetColorMap(256); /* allocate 256-entry AGA map */
SetRGB32(vp, n, r, g, b); /* set one colour (24-bit each) */
LoadRGB32(vp, table); /* bulk load */
FreeColorMap(cm);
```
## References
- NDK39: `graphics/view.h` — ColorMap, LoadRGB32
- ADCD 2.1 Autodocs: graphics — LoadRGB32, SetRGB32
- http://amigadev.elowar.com/read/ADCD_2.1/Libraries_Manual_guide/node02B4.html
- AmigaMail Vol. 2 — AGA colour system articles

View file

@ -0,0 +1,97 @@
[← Home](../../README.md) · [Hardware](../README.md) · [AGA](README.md)
# AGA Register Deltas vs ECS
## New Registers
### FMODE — $DFF1FC (AGA only)
DMA fetch mode — see `chipset_aga.md` for full description.
### BPLCON4 — $DFF10C (AGA only)
Bitplane and sprite colour bank selection:
```
bits 15-8: BPLAM7-0 — Bitplane XOR pattern (AGA colour bank XOR)
bits 7-4: ESPRM7-4 — Even sprite colour bank (bits 7:4 of colour reg index)
bits 3-0: OSPRM7-4 — Odd sprite colour bank
```
**Bitplane bank select via BPLAM:**
- BPLAM provides an XOR mask applied to the 8-bit colour index before palette lookup
- BPLAM = $00 → use COLOR00COLOR63 (bank 0)
- BPLAM = $40 → use COLOR64COLOR127 (bank 1)
- BPLAM = $80 → use COLOR128COLOR191 (bank 2)
- BPLAM = $C0 → use COLOR192COLOR255 (bank 3)
### COLOR00COLOR255 — $DFF180$DFF3BE (AGA)
AGA extends the colour table from 32 registers (OCS/ECS) to **256 registers**.
Each AGA colour register is 32 bits (accessed as two word writes via BPLCON3 latch):
```asm
; Write 24-bit colour to COLOR00:
; First write sets high nibble, second sets low nibble
move.w #$0000, BPLCON3+custom ; set LACE=0, select low colour word
move.w #$0FFF, COLOR00+custom ; write $RGB (high 12 bits)
bset #9, BPLCON3_shadow ; set LOCT (low nibble enable)
move.w #$0FFF, COLOR00+custom ; write low nibble of each channel
```
The standard `LoadRGB32()` and `LoadRGB4()` graphics library calls manage this transparently.
## Changed Registers
### BPLCON2 — $DFF104 (AGA extended)
```
bits 14-9: KILLEHB — kill EHB mode (AGA replaces EHB with 256 colour)
bit 6: RDRAM — read bitplane data from RAM (not registered in Lisa)
bits 5-3: PF2PRI — playfield 2 priority
bits 2-0: PF1PRI — playfield 1 priority + sprite priority
```
### BPLCON3 — $DFF106 (AGA extended from ECS)
Additional AGA bits:
```
bit 9: LOCT — low colour write enable (for 24-bit colour access)
bit 3: BRDSPRT — sprites visible in border
```
## AGA-Specific BPLCON0 Bits
See `chipset_aga.md` — bit 4 is the MSB of the bitplane count for 7/8-plane modes.
## Colour Register Access — Low Nibble Protocol
Writing 24-bit colour to AGA registers requires two steps per colour:
1. **Write high nibble** (standard): `COLOR00 = $0RGB` (bits [11:0] = R[3:0], G[3:0], B[3:0])
2. **Set LOCT** in BPLCON3 (bit 9)
3. **Write low nibble**: `COLOR00 = $0rgb` (bits [11:0] = R[3:0], G[3:0], B[3:0], these are the low 4 bits)
This two-write sequence gives 8 bits per channel (R[7:0], G[7:0], B[7:0]) = 24-bit colour.
`LoadRGB32()` does this automatically:
```c
/* AGA 32-bit colour table format:
Count, then pairs: [colour_index, 0x00RRGGBB] */
ULONG colour_table[] = {
32, 0, /* 32 colours starting at index 0 */
0x00FF0000, /* COLOR00 = red */
0x0000FF00, /* COLOR01 = green */
/* ... */
~0UL /* terminator */
};
LoadRGB32(vp, colour_table);
```
## References
- ADCD 2.1 Hardware Manual — AGA register appendix
- NDK39: `hardware/custom.h`, `graphics/view.h`
- Commodore A1200/A4000 Technical Reference Manuals
- AmigaMail Vol. 2 — AGA colour programming

View file

@ -0,0 +1,120 @@
[← Home](../../README.md) · [Hardware](../README.md) · [AGA](README.md)
# AGA Chipset Internals — Alice & Lisa
## Alice (MOS 8374) — AGA Agnus
Alice is the successor to Super Agnus and is the DMA controller and Copper/Blitter engine for AGA machines.
### Key Enhancements over Super Agnus
**64-bit DMA fetch bus (FMODE):**
Alice can fetch 2 or 4 words per DMA cycle via the `FMODE` register ($DFF1FC). This dramatically increases the bandwidth available to the blitter and bitplane DMA.
**Extended bitplane depth:**
Alice supports up to **8 bitplanes** (256 colours), compared to OCS/ECS's 6-plane limit.
**BPLCON4:**
Alice adds `BPLCON4` to control bitplane bank selection — which 64-entry block of the 256-entry colour table is used by the bitplanes.
### ALICE_ID
Alice can be identified via `VPOSR`:
```asm
move.w $DFF004, d0 ; VPOSR
lsr.w #8, d0
```
| VPOSR[15:8] | Chip |
|---|---|
| $22 | Alice AGA (standard) |
| $23 | Alice AGA (some A4000 revisions) |
---
## Lisa (AGA Denise)
Lisa is the display chip successor to ECS Denise, providing 8-bit colour output (256 colour registers) and extended sprite capabilities.
### Key Enhancements over ECS Denise
**256 colour registers:**
Lisa provides COLOR00COLOR255, each 24-bit (32-bit register with low byte unused).
**4 colour banks for bitplanes:**
`BPLCON4` selects which 64-register bank (03) the bitplanes use for lookup. This allows dual-playfield each using a different 64-colour palette.
**Sprite bank selection:**
`BPLCON3` bits select which colour bank sprite pairs use.
**Extended sprite width:**
Sprites can be 16 or 64 pixels wide in AGA mode.
**Lisa ID:**
Readable from `$DFF07C` (DENISEID):
```asm
move.w $DFF07C, d0 ; DENISEID = $00F8 for AGA Lisa
```
---
## FMODE — DMA Fetch Width Register ($DFF1FC)
The most critical AGA-specific register. Controls the data bus width for blitter and bitplane DMA:
```
bits 15-14: SPR_FMODE — sprite fetch mode
bits 13-12: BPL_FMODE — bitplane fetch mode
bits 9-8: BLT_FMODE — blitter fetch mode
00 = 1× (16-bit, OCS/ECS compatible)
01 = 2× (32-bit)
10 = 4× (64-bit)
11 = reserved
```
**Setting full 64-bit blitter mode:**
```asm
move.w #$00C0, $DFF1FC ; FMODE: BLT_FMODE=11 (64-bit)
; Also set BPL and SPR modes as needed
```
> [!CAUTION]
> Writing FMODE on OCS/ECS machines writes to the `LISAID` location (read-only) — no hardware damage, but the read-back value is incorrect. Always verify AGA presence before writing FMODE.
---
## DMA Bandwidth with FMODE
| FMODE | Bus width | Blitter speed | Bitplane DMA |
|---|---|---|---|
| 1× (OCS compat) | 16-bit | 1× | 1× |
| 2× | 32-bit | 2× | 2× |
| 4× | 64-bit | 4× | 4× |
At 4× mode, the AGA blitter can fill/copy at ~70 MB/s theoretical on a 7 MHz bus.
---
## BPLCON0 Extended Bits (AGA)
In AGA mode, `BPLCON0` bit 4 (`ECSENA`) must be **1** to enable AGA features. Additional BPU bit (bit 4 of the count) allows 7 and 8 planes:
```
bits 14-12: BPU2-0 — lower 3 bits of bitplane count
bit 4: BPU3 — MSB of bitplane count (AGA: allows 7, 8 planes)
```
To use 8 bitplanes (256 colours):
```asm
move.w #$9411, BPLCON0+custom ; HIRES=1 (if needed), BPU=8 (BPU3=1, BPU2-0=000), ECSENA=1
```
---
## References
- ADCD 2.1 Hardware Manual — AGA chapter
- NDK39: `hardware/custom.h` — struct Custom (with AGA extensions)
- Commodore A1200 Technical Reference Manual — Alice/Lisa section
- AmigaMail Vol. 2 — AGA programming articles

View file

@ -0,0 +1,139 @@
[← Home](../../README.md) · [Hardware](../README.md) · [AGA](README.md)
# 68030/040 on the Amiga (A3000/A4000)
## Overview
The A3000 ships with a **Motorola 68030** at 16 or 25 MHz. The A4000 ships with either a 68030 or **68040** at 25 MHz. Later accelerator cards bring the 68060. This document covers CPU-specific concerns for AmigaOS 3.1/3.2 on these platforms.
## 68030 (A3000)
### On-Chip Caches
- **Instruction cache**: 256 bytes, direct-mapped
- **Data cache**: 256 bytes, direct-mapped
- Both enabled by default on AmigaOS 3.1 (`CacheControl()` call)
### On-Chip PMMU
- Full 68030 PMMU: ATC (Address Translation Cache), TT registers
- AmigaOS does **not** use the MMU by default on A3000
- Third-party tools (e.g., VMM, mmu.library) use it for virtual memory
### Cache Control (exec)
```c
#include <proto/exec.h>
/* Enable instruction and data caches */
CacheControl(CACRF_EnableI | CACRF_EnableD, CACRF_EnableI | CACRF_EnableD);
/* Flush caches (required before self-modifying code) */
CacheClearU(); /* clear all caches */
CacheClearE(addr, len, CACRF_ClearI | CACRF_ClearD); /* targeted flush */
```
### Cache Coherency with DMA
The 68030 data cache is **not snooped** by the Amiga chip bus. If the CPU writes to a buffer that the DMA engine (Blitter, audio, custom chips) will read, the cache must be flushed first:
```c
/* Before handing a buffer to DMA: */
CacheClearE(buf, size, CACRF_ClearD);
```
Similarly, after DMA writes to a buffer that the CPU will read:
```c
CacheClearE(buf, size, CACRF_ClearD);
```
This is a common source of bugs in audio/video programming on 68030+ Amigas.
---
## 68040 (A4000 / Accelerators)
### On-Chip FPU — Partial Implementation
The 68040 has an on-chip FPU but omits many instructions present in the 68881/68882:
**Missing from 68040 FPU:**
- `FSIN`, `FCOS`, `FTAN`, `FASIN`, `FACOS`, `FATAN`
- `FETOX`, `FETOXM1`, `FLOGN`, `FLOG10`
- `FSINH`, `FCOSH`, `FTANH`
- `FATANH`, `FASINH`, `FACOSH`
- `FSCALE`, `FREMX`, `FREMX`, `FINTRZ`
AmigaOS provides `68040.library` which installs Line-F exception handlers to emulate the missing instructions in software.
```c
/* 68040.library is opened automatically by exec at boot */
/* Software should check that it is open before using FP */
struct Library *MathLib = OpenLibrary("68040.library", 0);
```
> [!WARNING]
> If `68040.library` is not installed and software uses a missing 68040 FPU instruction, the system will crash with a Line-F exception. Always ensure `68040.library` is present on A4000.
### 4 KB Instruction + Data Caches
- 68040 has **4 KB instruction cache** and **4 KB data cache**, both 4-way set-associative
- Cache coherency is more complex: DMA writes may not be visible to the CPU without invalidation
- `CacheClearE()` / `CacheClearU()` remain the correct API
### 68040 Memory Model
```
CACR (Cache Control Register) accessed via MOVEC:
bit 15: EDC — enable data cache
bit 14: NAD — no allocate data (streaming mode)
bit 13: ESB — enable store buffer
bit 10: DPI — disable push-inhibit
bit 7: EIC — enable instruction cache
bit 3: CINV — cache invalidate
```
### Bus Error Stack Frame (68040)
The 68040 generates a **different bus error stack frame** from the 68000:
- 68000: 14-byte frame
- 68040: 104-byte frame with pipeline state
This matters for exception handlers and debuggers targeting both platforms.
---
## 68060 (Accelerator Cards)
Available via Blizzard 060, CyberStorm 060, etc. Key differences:
- **Superscalar**: two integer pipelines (in-order, no OOO)
- **Branch prediction**: static and dynamic
- **No MOVE16 snooping** on some Amiga implementations
- On-chip FPU: missing same transcendentals as 68040 → needs `68060.library`
- Separate `68060.library` for the additional missing instructions vs 68040
```c
struct Library *Lib060 = OpenLibrary("68060.library", 0);
```
---
## AmigaOS exec CacheControl() API
```c
#include <exec/execbase.h>
#include <proto/exec.h>
/* SysBase->CacheFlags reflect current cache state */
ULONG flags = CacheControl(0, 0); /* query without changing */
/* Flag bits: */
#define CACRF_EnableD (1L<<3) /* data cache enable */
#define CACRF_FreezeD (1L<<4) /* data cache freeze */
#define CACRF_ClearD (1L<<5) /* clear data cache */
#define CACRF_EnableI (1L<<8) /* instruction cache enable */
#define CACRF_FreezeI (1L<<9) /* instruction cache freeze */
#define CACRF_ClearI (1L<<10) /* clear instruction cache */
#define CACRF_CopyBack (1L<<31) /* data cache write-back mode */
```
## References
- Motorola 68030 User's Manual (M68030UM/AD)
- Motorola 68040 User's Manual (M68040UM/AD)
- NDK39: `exec/execbase.h`, `proto/exec.h` — CacheControl, CacheClearE
- ADCD 2.1: `Libraries_Manual_guide/` — exec CacheControl autodoc
- Commodore A3000/A4000 Technical Reference Manuals

View file

@ -0,0 +1,97 @@
[← Home](../../README.md) · [Hardware](../README.md) · [AGA](README.md)
# Gayle — A1200 IDE & PCMCIA
## Overview
The A1200 uses a different revision of **Gayle** than the A600. The A1200 Gayle integrates:
- **ATA/IDE interface** (for one hard drive + optional CD-ROM)
- **PCMCIA Type II** slot (for modems, network cards, RAM cards)
- **Interrupt routing** for both IDE and PCMCIA events
The A1200 Gayle is at a different base address layout than the A600 Gayle, and the byte-lane mapping differs from the A4000 IDE interface.
## Gayle ID
Read the Gayle ID by toggling read access to the ID register:
```c
#define GAYLE_ID_A1200 0xDA8000 /* read 8 bits, shifts on each access */
volatile UBYTE *gayle_id = (UBYTE *)0xDA8000;
UBYTE id_byte = *gayle_id; /* returns $D0 (A600) or $D1 (A1200) */
```
## IDE Register Map (A1200)
The A1200 IDE registers are at `$DA0000`, but the byte lanes are **swapped** relative to standard AT/ATA convention — the 8-bit registers appear at odd byte offsets within each 4-byte window:
| A1200 Address | ATA Register | RW |
|---|---|---|
| $DA0000 | Data (16-bit) | RW |
| $DA0005 | Error (R) / Features (W) | RW |
| $DA0009 | Sector Count | RW |
| $DA000D | Sector Number (LBA 7:0) | RW |
| $DA0011 | Cylinder Low (LBA 15:8) | RW |
| $DA0015 | Cylinder High (LBA 23:16) | RW |
| $DA0019 | Drive/Head select (LBA 27:24) | RW |
| $DA001D | Status (R) / Command (W) | RW |
| $DA101D | Alternate Status (R) / Device Control (W) | RW |
> [!NOTE]
> The odd byte offset is because Gayle maps ATA registers on the **odd byte lane** of the 16-bit Amiga bus. Accessing `$DA0000+1` is the first register, not `$DA0000`. Many IDE drivers compensate with an offset of +1 or use a byte-swapped struct.
## Gayle Interrupt Register
```
$DA9000 GAYLE_INT_STATUS (read/write)
$DA9004 GAYLE_INT_ENABLE
```
```c
#define GAYLE_IRQ_IDE (1<<7) /* IDE interrupt pending */
#define GAYLE_IRQ_CARD (1<<6) /* PCMCIA interrupt */
#define GAYLE_IRQ_BVD1 (1<<5)
#define GAYLE_IRQ_BVD2 (1<<4)
#define GAYLE_IRQ_WP (1<<3) /* PCMCIA write protect */
#define GAYLE_IRQ_CD (1<<2) /* PCMCIA card detect */
```
Gayle routes its interrupt to **CIA-A /FLG** pin → CIAA ICR `CIAICRF_FLG` → CPU IPL 6.
Interrupt service routine must:
1. Check `GAYLE_INT_STATUS` to identify source (IDE or PCMCIA)
2. Clear the relevant bit by writing 0 to it
3. If IDE: read the ATA status register to clear the IDE INTRQ
## PCMCIA Interface (A1200)
The A1200 PCMCIA slot is at:
| Address | Content |
|---|---|
| $600000$9FFFFF | PCMCIA attribute memory (card CIS) |
| $A00000$A3FFFF | PCMCIA common memory (data) |
**Card detect sequence:**
1. A card insertion triggers `GAYLE_IRQ_CD` (bit 2)
2. Software reads CIS from attribute memory at $600000 to identify card type
3. For ATA cards: configure card mode via PCMCIA CIS `CONFIG` tuple
4. For network/modem cards: use the card's documented I/O mapping
## AmigaOS IDE Access
AmigaOS 3.1 includes `ata.device` (sometimes called `ide.device`) which drives the A1200 Gayle IDE internally. Applications never access Gayle registers directly — they go through dos.library → filesystem handler → ata.device.
```c
/* Standard path — no direct Gayle access needed: */
BPTR fh = Open("DH0:myfile", MODE_NEWFILE);
Write(fh, data, length);
Close(fh);
```
## References
- Commodore A1200 Technical Reference Manual — Gayle chapter (local archive)
- NDK39: (no official Gayle header — community documented)
- Amiga Hardware Reference (community supplement) — Gayle register map
- `scsi.device` / `ata.device` Autodocs on ADCD 2.1

View file

@ -0,0 +1,109 @@
[← Home](../../README.md) · [Hardware](../README.md)
# Amiga Address Space
## Overview
The Amiga uses a **24-bit physical address bus** on OCS/ECS machines (68000/68020 effective), giving 16 MB of addressable space. AGA machines with 68030/040 and 32-bit-clean software can address the full 4 GB, but Chip RAM and custom registers remain in the lower 16 MB.
## Memory Map — 24-bit (OCS/ECS, A500/A600/A3000)
```
$000000$1FFFFF Chip RAM (max 2 MB on ECS, 512 KB on OCS A500)
$200000$9FFFFF Fast RAM (expansion via Zorro II autoconfig)
$A00000$BEFFFF Zorro II I/O space
$BFD000$BFDFFF CIA-B (8520, keyboard, floppy motor, disk side)
$BFE001$BFE1FF CIA-A (8520, parallel port, serial flags, timer)
$C00000$C7FFFF Slow RAM ("Ranger", DMA-visible but not fast)
$C80000$CFFFFF Zorro II expansion I/O (boards)
$D00000$D7FFFF Zorro II expansion I/O
$D80000$DBFFFF Reserved / board-specific
$DC0000$DCFFFF Real-Time Clock (MSM6242B / RF5C01A)
$DD0000$DEFFFF Reserved
$DF0000$DFFFFF Custom chip registers ($DFF000$DFF1FE)
$E00000$E7FFFF Kick memory (WCS / Ranger slow RAM mirror)
$E80000$EFFFFF Autoconfig space (Zorro II probe)
$F00000$F7FFFF Extended Kickstart ROM (OS 3.1: second 256 KB)
$F80000$FFFFFF Kickstart ROM (512 KB mirror at top of 16 MB)
```
## Memory Map — 32-bit (AGA, A1200/A4000)
```
$000000$1FFFFF 2 MB Chip RAM
$200000$07FFFFFF Fast RAM (on-board: 416 MB via Ramsey on A4000)
Trapdoor/PCMCIA on A1200
$A00000$BEFFFF Zorro II I/O
$BFD000 CIA-B
$BFE001 CIA-A
$C00000$CFFFFF Slow RAM / board I/O
$D80000$D8FFFF IDE / Gayle (A1200/A4000)
$DA0000$DA3FFF PCMCIA attribute memory (A1200)
$DC0000 RTC
$DFF000$DFFFFF Custom registers
$E00000$E7FFFF Kick mirror / WCS
$F00000$F7FFFF Extended ROM
$F80000$FFFFFF Kickstart ROM (512 KB)
$01000000+ Zorro III expansion (32-bit, A3000/A4000 only)
```
## Memory Type Classification
AmigaOS classifies memory by access flags used in `AllocMem()`:
| MEMF Flag | Value | Description |
|---|---|---|
| `MEMF_ANY` | 0 | No constraint |
| `MEMF_PUBLIC` | 1<<0 | Accessible to all tasks and DMA |
| `MEMF_CHIP` | 1<<1 | Chip RAM accessible to custom chips (DMA) |
| `MEMF_FAST` | 1<<2 | Fast RAM CPU-only, no DMA, faster |
| `MEMF_LOCAL` | 1<<8 | Not mapped out (always present) |
| `MEMF_24BITDMA` | 1<<9 | Addressable within 24-bit space |
| `MEMF_CLEAR` | 1<<16 | Zero-fill before returning |
| `MEMF_REVERSE` | 1<<17 | Allocate from top of memory |
| `MEMF_LARGEST` | 1<<18 | Return size of largest free block |
| `MEMF_TOTAL` | 1<<19 | Return total memory of type |
### Chip RAM Requirement
Custom chip DMA can only access **Chip RAM** (`MEMF_CHIP`). This means:
- Graphics bitmaps rendered by Blitter/Copper must be in Chip RAM
- Audio sample data must be in Chip RAM
- Copper lists must be in Chip RAM
- Sprite data must be in Chip RAM
Fast RAM is **CPU-only** — generally used for code, non-DMA data structures, and stacks.
## Diagram
```mermaid
block-beta
columns 1
block:chip["Chip RAM\n$000000$1FFFFF\n(DMA accessible)"]
block:fast["Fast RAM\n$200000$9FFFFF\n(CPU only, faster)"]
block:zio["Zorro II I/O\n$A00000$BEFFFF"]
block:cia["CIA-A/B\n$BFD000/$BFE001"]
block:slow["Slow/Ranger RAM\n$C00000$C7FFFF"]
block:rtc["RTC $DC0000"]
block:custom["Custom Registers\n$DFF000$DFFFFF"]
block:rom["Kickstart ROM\n$F80000$FFFFFF"]
```
## Key Chip RAM Addresses
| Address | Content |
|---|---|
| $000000$000400 | Exception vector table (copied from ROM) |
| $000004 | `SysBase` pointer (exec library base) |
| $000100 | Copper list scratch area (boot) |
| $000400$001000 | Reserved by OS |
| $001000+ | Free Chip RAM (AvailMem result) |
> [!WARNING]
> Writing to $000000$000400 corrupts the exception table. Writing to $000004 corrupts `SysBase`. These addresses must never be allocated by user code; exec reserves them.
## References
- NDK39: `exec/memory.h` — MEMF_ flag definitions
- ADCD 2.1 Hardware Manual: memory map chapter
- Commodore A1200/A4000 Technical Reference Manuals (local archive)

View file

@ -0,0 +1,152 @@
[← Home](../../README.md) · [Hardware](../README.md)
# CIA Chips — 8520 MOS Technology
## Overview
The Amiga uses **two MOS 8520 CIA** (Complex Interface Adapter) chips, providing timers, parallel/serial I/O ports, a time-of-day clock, and interrupt generation. They are the primary source of hardware timing outside the vertical blank and audio DMA.
- **CIA-A** at `$BFE001` (even byte addresses)
- **CIA-B** at `$BFD000` (odd byte addresses)
Both CIAs are accessed via byte reads/writes; the 68000 byte-lane placement means CIA-A uses even offsets and CIA-B uses odd offsets on the 16-bit bus.
## Register Map
Each CIA has 16 registers, spaced 256 bytes apart in the Amiga address space:
| Offset | Register | CIA-A Function | CIA-B Function |
|---|---|---|---|
| $000 | PRA | Parallel port data (input) | Disk control outputs |
| $100 | PRB | Parallel port data (output) | Disk status inputs |
| $200 | DDRA | Port A direction (1=output) | Port A direction |
| $300 | DDRB | Port B direction | Port B direction |
| $400 | TALO | Timer A low byte | Timer A low byte |
| $500 | TAHI | Timer A high byte | Timer A high byte |
| $600 | TBLO | Timer B low byte | Timer B low byte |
| $700 | TBHI | Timer B high byte | Timer B high byte |
| $800 | TODLO | TOD clock low (1/60 s) | Disk position (latched) |
| $900 | TODMID | TOD clock mid | |
| $A00 | TODHI | TOD clock high | |
| $B00 | (unused) | — | — |
| $C00 | SDR | Serial data register | Serial data register |
| $D00 | ICR | Interrupt control | Interrupt control |
| $E00 | CRA | Control register A | Control register A |
| $F00 | CRB | Control register B | Control register B |
## CIA-A: $BFE001
CIA-A handles:
| Bit | Port A (PRA, read $BFE001) |
|---|---|
| 7 | `/FIR1` — joystick port 1 button |
| 6 | `/FIR0` — joystick port 0 button |
| 5 | `/RDY` — floppy ready |
| 4 | `/TK0` — track 0 sensor |
| 3 | `/WPRO` — write-protect |
| 2 | `/CHNG` — disk change |
| 1 | `/LED` — power LED (write: 0=bright) |
| 0 | `/OVL` — Chip RAM overlay (write during boot) |
Port B (PRB, $BFE101): Parallel port data lines D0D7.
**CIA-A interrupts** appear on **CPU IPL level 2** via INTENA bit `INTB_EXTER` — actually CIA is on IPL 6.
## CIA-B: $BFD000
CIA-B handles floppy drive motor/selection and disk DMA sync:
| Bit | Port A (PRA, $BFD000) |
|---|---|
| 7 | `/MTR` — motor on/off |
| 6 | `/SEL3` — drive 3 select |
| 5 | `/SEL2` — drive 2 select |
| 4 | `/SEL1` — drive 1 select |
| 3 | `/SEL0` — drive 0 select |
| 2 | `/SIDE` — head side (0=upper) |
| 1 | `/DIR` — step direction |
| 0 | `/STEP` — step pulse |
Port B (PRB, $BFD100): Parallel port shadow (less commonly used on B).
**CIA-B interrupts** appear on **CPU IPL level 6**.
## Timers
Each CIA has two 16-bit countdown timers (Timer A and Timer B):
- Count from a loaded latch value down to zero
- Can be one-shot or continuous
- Clock sources: system clock (709 kHz PAL / 715 kHz NTSC), or Timer A output (for Timer B)
- Timer A can generate `SDR` baud rate for serial output
**Control Register A (CRA) bits:**
```
bit 0: START — 1 = timer running
bit 1: PBON — 1 = timer output on Port B bit 6
bit 2: OUTMODE — 0=pulse, 1=toggle
bit 3: RUNMODE — 0=continuous, 1=one-shot
bit 4: LOAD — 1 = force load latch into counter
bit 5: INMODE — 0=clock, 1=count rising edges on CNT pin
bit 6: SPMODE — 0=SDR input, 1=SDR output
bit 7: TODIN — 0=60 Hz TOD, 1=50 Hz TOD (PAL)
```
## Time-of-Day (TOD) Clock
24-bit counter, clocked at 60 Hz (NTSC) or 50 Hz (PAL):
- CIA-A TOD: used by OS as software clock
- TOD registers latch on read of TODHI — must read TODHI first, then TODMID, then TODLO
- `ciaa.ciatodhi``ciaa.ciatodmid``ciaa.ciatodlo`
- Set by writing TODHI → TODMID → TODLO (halts during write)
## Interrupt Control Register (ICR)
Write to enable interrupts, read to see which fired:
```c
/* Enable Timer A interrupt */
ciaa.ciaicr = CIAICRF_SETCLR | CIAICRF_TA;
/* On read: bits indicate which sources fired */
UBYTE icr = ciaa.ciaicr;
if (icr & CIAICRF_TA) { /* Timer A fired */ }
if (icr & CIAICRF_TB) { /* Timer B fired */ }
if (icr & CIAICRF_ALRM) { /* TOD alarm fired */ }
if (icr & CIAICRF_SP) { /* Serial register full/empty */ }
if (icr & CIAICRF_FLG) { /* /FLAG pin (index pulse on CIA-B) */ }
```
Write bit 7 (`CIAICRF_SETCLR`): 1 = set enable bits, 0 = clear enable bits.
## AmigaOS Timer Device Integration
AmigaOS's `timer.device` uses CIA timers internally:
- `UNIT_MICROHZ` — uses CIA-A Timer A for microsecond delays
- `UNIT_VBLANK` — uses vertical blank interrupt (not CIA)
- `UNIT_ECLOCK` — uses the E clock (709/715 kHz, same as CIA clock)
Direct CIA programming should be done with `ciaa`/`ciab` resource claims via `OpenResource("ciaa.resource")` — not by poking CIA registers directly.
## C Access via NDK Headers
```c
#include <hardware/cia.h>
#include <resources/cia.h>
/* CIA-A is at fixed address */
#define ciaa (*((volatile struct CIA *)0xBFE001))
#define ciab (*((volatile struct CIA *)0xBFD000))
/* struct CIA fields (hardware/cia.h): */
/* ciaa.ciapra, ciaa.ciaprb, ciaa.ciaicr, ciaa.ciacra, ... */
```
## References
- MOS Technology 6526/8520 datasheet
- ADCD 2.1 Hardware Manual — CIA chapter: http://amigadev.elowar.com/read/ADCD_2.1/Hardware_Manual_guide/
- NDK39: `hardware/cia.h`, `resources/cia.h`
- Autodocs: `cia` resource — http://amigadev.elowar.com/read/ADCD_2.1/Includes_and_Autodocs_3._guide/node00C7.html

View file

@ -0,0 +1,141 @@
[← Home](../../README.md) · [Hardware](../README.md)
# M68k CPU on the Amiga
## Overview
AmigaOS runs on the Motorola 68000 family exclusively. The CPU operates in two privilege modes and interacts with the custom chips via the shared 16/32-bit bus arbitrated by Agnus/Alice.
## Privilege Modes
| Mode | SR[13] (S-bit) | Stack Pointer | Access |
|---|---|---|---|
| **Supervisor** | 1 | SSP (A7') | Full hardware access |
| **User** | 0 | USP (A7) | Restricted — no privileged instructions |
AmigaOS runs entirely in **supervisor mode**. User-mode tasks switch to supervisor via `Supervisor()` or trap instructions. The OS does not enforce user-mode isolation — all tasks share one address space.
## Register Set
```
D0D7 Data registers (32-bit)
A0A6 Address registers (32-bit)
A7 Stack pointer (USP in user mode, SSP in supervisor mode)
PC Program counter
SR Status register (CCR + supervisor bits)
```
**CCR (Condition Code Register) — lower byte of SR:**
```
bit 4: X (extend)
bit 3: N (negative)
bit 2: Z (zero)
bit 1: V (overflow)
bit 0: C (carry)
```
**SR upper byte (supervisor only):**
```
bit 15: T1 (trace mode — single-step)
bit 14: T0
bit 13: S (supervisor state)
bit 10-8: I2-I0 (interrupt mask level, 07)
```
## Exception Vector Table
Located at **$000000** (physical), shadowed from Kick ROM into Chip RAM on boot.
| Offset | Vector | Description |
|---|---|---|
| $000 | Reset SSP | Initial supervisor stack pointer |
| $004 | Reset PC | Initial program counter (ROM entry) |
| $008 | Bus Error | Access fault |
| $00C | Address Error | Odd-address word/long access |
| $010 | Illegal Instruction | Undefined opcode |
| $014 | Divide by Zero | DIVS/DIVU by zero |
| $018 | CHK | CHK instruction bound check fail |
| $01C | TRAPV / cpTRAPcc | Overflow trap |
| $020 | Privilege Violation | User-mode privileged instruction |
| $024 | Trace | Single-step trap |
| $028 | Line 1010 (A-line) | Opcode $Axxx — OS trap dispatch |
| $02C | Line 1111 (F-line) | Opcode $Fxxx — FPU / emulation |
| $060 | Spurious Interrupt | No response to interrupt acknowledge |
| $064 | Level 1 Autovector | CIA-B timer / serial / disk |
| $068 | Level 2 Autovector | CIA-A, software |
| $06C | Level 3 Autovector | VBL, copper, blitter |
| $070 | Level 4 Autovector | Audio, disk DMA |
| $074 | Level 5 Autovector | Serial port |
| $078 | Level 6 Autovector | CIA / ext interrupt |
| $07C | Level 7 Autovector | NMI (not maskable) |
| $080$0BC | TRAP #0#15 | Software traps |
## AmigaOS Use of Exception Vectors
- **A-Line ($028):** AmigaOS uses this for its internal library call dispatch on some early titles; modern code uses direct JSR via library base.
- **Level 3 ($06C):** Vertical Blank interrupt — exec's `AddIntServer(INTB_VERTB, ...)` chains here.
- **Level 6 ($078):** CIA interrupts chain here; exec dispatches to `INTB_EXTER` servers.
- **TRAP #0 ($080):** Used by `Supervisor()` to enter supervisor mode from user code.
## Interrupt Priority Levels
AmigaOS maps hardware interrupt levels to internal interrupt bits (`INTENA`/`INTREQ`):
| IPL | Source | INTENA bits |
|---|---|---|
| 1 | Serial TX, disk block | `INTB_TBE`, `INTB_DSKBLK` |
| 2 | Software (PostIntServer) | `INTB_SOFTINT` |
| 3 | VBlank, copper, blitter | `INTB_VERTB`, `INTB_COPPER`, `INTB_BLIT` |
| 4 | Audio ch 03, disk DMA | `INTB_AUD0``INTB_AUD3`, `INTB_DSKSYNC` |
| 5 | Serial RX | `INTB_RBF` |
| 6 | External / CIA | `INTB_EXTER` |
| 7 | NMI (rarely used) | — |
## CPU Detection at Runtime
AmigaOS stores CPU capability flags in `ExecBase->AttnFlags` (offset $128):
```c
#include <exec/execbase.h>
struct ExecBase *SysBase = *((struct ExecBase **)4);
UWORD attn = SysBase->AttnFlags;
if (attn & AFF_68020) { /* 68020+ present */ }
if (attn & AFF_68030) { /* 68030+ present */ }
if (attn & AFF_68040) { /* 68040+ present */ }
if (attn & AFF_68060) { /* 68060 present */ }
if (attn & AFF_FPU) { /* FPU present */ }
if (attn & AFF_68881) { /* 68881/68882 */ }
```
**AttnFlags bit definitions** (`exec/execbase.h`):
```c
#define AFF_68010 (1<<0)
#define AFF_68020 (1<<1)
#define AFF_68030 (1<<2)
#define AFF_68040 (1<<3)
#define AFF_68881 (1<<4) /* on-chip FPU or external 68881 */
#define AFF_FPU (1<<4)
#define AFF_68882 (1<<5)
#define AFF_FPU40 (1<<6) /* 040/060 on-chip FPU */
#define AFF_68060 (1<<7)
```
## Key Instruction Subsets by CPU
| Instruction | 68000 | 68020 | 68030 | 68040 |
|---|---|---|---|---|
| MULS/MULU 16×16 | ✓ | ✓ | ✓ | ✓ |
| MULS/MULU 32×32 | ✗ | ✓ | ✓ | ✓ |
| Bit-field (BFEXTU etc.) | ✗ | ✓ | ✓ | ✓ |
| CALLM/RTM | ✗ | ✓ | ✗ | ✗ |
| CAS/CAS2 | ✗ | ✓ | ✓ | ✓ |
| LPSTOP | ✗ | ✗ | ✓ | ✓ |
| CINV/CPUSH | ✗ | ✗ | ✓ | ✓ |
## References
- Motorola *M68000 Family Programmer's Reference Manual* (M68000PM/AD)
- ADCD 2.1 Hardware Manual: http://amigadev.elowar.com/read/ADCD_2.1/Hardware_Manual_guide/node0000.html
- NDK39: `exec/execbase.h``AttnFlags` definitions

View file

@ -0,0 +1,139 @@
[← Home](../../README.md) · [Hardware](../README.md)
# Zorro Bus — Expansion Architecture
## Overview
The Amiga uses the **Zorro** expansion bus for add-on cards. There are two generations:
- **Zorro II** — 16-bit, 24-bit addressing, 7 MHz, compatible with A2000/A3000/A4000
- **Zorro III** — 32-bit, 32-bit addressing, up to 33 MHz burst, A3000/A4000 only
Zorro uses **AutoConfig** — a standardised plug-and-play configuration protocol that predates PCI by several years.
## Zorro II
| Parameter | Value |
|---|---|
| Data bus | 16-bit |
| Address bus | 24-bit |
| Clock | 7.14 MHz (bus cycle ≈ 280 ns) |
| Max transfer | ~5 MB/s (DMA) |
| Address space | $A00000$EFFFFF (I/O), $200000$9FFFFF (RAM) |
| Slots | 5 (A2000), 3 (A3000) |
Zorro II cards appear in the 16 MB address space. RAM cards are configured into $200000$9FFFFF. I/O cards use $A00000$DEFFFF.
## Zorro III
| Parameter | Value |
|---|---|
| Data bus | 32-bit |
| Address bus | 32-bit |
| Clock | Up to 33 MHz burst |
| Max transfer | ~40 MB/s (DMA) |
| Address space | $01000000 and above |
| Slots | 4 (A3000), 5 (A4000) |
Zorro III extends into the 32-bit address space, allowing large RAM cards (32128 MB) and fast peripherals. Requires a 32-bit CPU (68030+) and OS support.
## AutoConfig Protocol
AutoConfig allows the OS to discover and configure cards without jumpers:
```mermaid
sequenceDiagram
participant OS as AmigaOS (expansion.library)
participant Card as Zorro Card
OS->>Card: Read $E80000 (config space)
Card-->>OS: Manufacturer ID (16-bit)
OS->>Card: Read $E80002
Card-->>OS: Product ID, flags
OS->>Card: Read board size, type
OS->>OS: AllocAbs() / ConfigBoard()
OS->>Card: Write base address
Card-->>OS: Card configured, moves off $E80000
```
**Key AutoConfig registers** (read from $E80000$E8007F before configuration):
| Offset | Content |
|---|---|
| $00 | er_Type (board type: RAM/IO, Zorro II/III) |
| $02 | er_Product (product ID) |
| $04 | er_Flags |
| $06 | er_Reserved03 |
| $08$0A | er_Manufacturer (16-bit) |
| $0C$0F | er_SerialNumber |
| $10$11 | er_InitDiagVec (diagnostic ROM vector) |
**Board types** (`er_Type` bits):
```c
#define ERT_TYPEMASK 0xC0
#define ERT_ZORROII 0xC0 /* Zorro II card */
#define ERT_ZORROIII 0x80 /* Zorro III card */
#define ERTB_MEMLIST 5 /* board is RAM, add to free list */
#define ERTB_DIAGVALID 4 /* DiagArea ROM is valid */
#define ERTB_CHAINEDCONFIG 3 /* more boards to configure */
```
## expansion.library
AmigaOS provides `expansion.library` to manage Zorro configuration:
```c
#include <libraries/expansion.h>
#include <clib/expansion_protos.h>
/* Find a configured board by manufacturer/product */
struct ConfigDev *cd = NULL;
while ((cd = FindConfigDev(cd, MANUF_ID, PROD_ID)) != NULL) {
APTR base = cd->cd_BoardAddr;
ULONG size = cd->cd_BoardSize;
/* use board at base */
}
```
**Key structures:**
```c
struct ConfigDev {
struct Node cd_Node;
UBYTE cd_Flags;
UBYTE cd_Pad;
struct ExpansionRom cd_Rom; /* copy of autoconfig ROM area */
APTR cd_BoardAddr; /* configured base address */
ULONG cd_BoardSize;
UWORD cd_SlotAddr;
UWORD cd_SlotSize;
APTR cd_Driver;
struct ConfigDev *cd_NextCD;
ULONG cd_Unused[4];
};
```
## DiagArea — Card ROM
Cards with `ERTB_DIAGVALID` have a small ROM (DiagArea) that the OS calls during boot:
```c
struct DiagArea {
UBYTE da_Config; /* flags */
UBYTE da_Flags;
UWORD da_Size;
UWORD da_DiagPoint; /* offset to diagnostic code */
UWORD da_BootPoint; /* offset to boot code */
UWORD da_Name; /* offset to name string */
UWORD da_Reserved01;
UWORD da_Reserved02;
};
```
The boot vector is called by `ConfigChain()` during the early boot sequence — this is how SCSI controllers install their filesystem handlers.
## References
- NDK39: `libraries/expansion.h`, `libraries/configregs.h`, `libraries/configvars.h`
- ADCD 2.1 Autodocs: `expansion` — http://amigadev.elowar.com/read/ADCD_2.1/Includes_and_Autodocs_3._guide/node025B.html
- *Amiga Hardware Reference Manual* 3rd ed. — AutoConfig chapter
- Dave Haynie's Zorro III specification documents

View file

@ -0,0 +1,67 @@
[← Home](../../README.md) · [Hardware](../README.md)
# ECS Chipset — A600 / A3000 / A500+
## Overview
The **Enhanced Chip Set** (ECS) is a significant revision of OCS, shipping from 1990 onward. It adds 2 MB Chip RAM addressing, programmable display timing, and extended registers while maintaining full backward compatibility with OCS software.
## Chip Summary
| Chip | Part | Changes from OCS |
|---|---|---|
| **Super Agnus** | MOS 8372A | Addresses 2 MB Chip RAM, extended DMA window |
| **ECS Denise** | MOS 8373 | BPLCON3, border blank, programmable sync |
| **Paula** | MOS 8364 | Unchanged |
## Contents
| File | Topic |
|---|---|
| [chipset_ecs.md](chipset_ecs.md) | Super Agnus and ECS Denise internals |
| [ecs_registers_delta.md](ecs_registers_delta.md) | New/changed registers vs OCS |
| [productivity_modes.md](productivity_modes.md) | Multiscan/productivity display modes |
| [gary_gayle.md](gary_gayle.md) | Gary (A3000) and Gayle (A600) chips: IDE, PCMCIA |
| [chip_ram_expansion.md](chip_ram_expansion.md) | 2 MB Chip RAM with Super Agnus |
## ECS vs OCS — Key Differences
| Feature | OCS | ECS |
|---|---|---|
| Max Chip RAM | 1 MB (Fat Agnus) | **2 MB** (Super Agnus) |
| Display sync | Fixed NTSC/PAL | **BEAMCON0** — programmable |
| Bitplane scroll | 4-bit (BPLCON1) | Extended (ECS Denise) |
| Border blank | No | **BPLCON3** border control |
| Hires sprites | No | ECS Denise extended sprite control |
| DMA window | Smaller | Extended: wider bitplane fetch |
## Identifying ECS at Runtime
```c
#include <exec/execbase.h>
struct ExecBase *SysBase = *((struct ExecBase **)4);
/* AttnFlags does not directly identify chipset */
/* Use graphics.library GfxBase->ChipRevBits0 */
#include <graphics/gfxbase.h>
struct GfxBase *GfxBase = (struct GfxBase *)OpenLibrary("graphics.library", 0);
if (GfxBase->ChipRevBits0 & GFXB_BIG_BLITTER) { /* ECS+ */ }
if (GfxBase->ChipRevBits0 & GFXB_HR_AGNUS) { /* Super Agnus */ }
if (GfxBase->ChipRevBits0 & GFXB_HR_DENISE) { /* ECS Denise */ }
```
## Machines Using ECS
| Model | Notes |
|---|---|
| A3000 | Super Agnus + ECS Denise; 68030; SCSI; Zorro III |
| A500+ | Super Agnus (1 MB variant); ECS Denise; no IDE |
| A600 | Super Agnus (1 MB variant); ECS Denise; Gayle; IDE; PCMCIA |
| A2000 (late) | Some late rev boards shipped with ECS chips |
## References
- ADCD 2.1 Hardware Manual — ECS extension chapters
- NDK39: `graphics/gfxbase.h` — ChipRevBits0 flags
- *Amiga Hardware Reference Manual* 3rd ed. — ECS appendix

View file

@ -0,0 +1,76 @@
[← Home](../../README.md) · [Hardware](../README.md) · [ECS](README.md)
# 2 MB Chip RAM with Super Agnus
## Overview
The original Agnus (8361/8367) could address only **512 KB** of Chip RAM. The Fat Agnus (later OCS revision) addressed **1 MB**. Super Agnus (8372A) extends the DMA address bus to **21 bits**, allowing **2 MB** of Chip RAM to be addressed by all DMA channels.
## Requirements for 2 MB Chip RAM
All of the following must be present:
1. **Super Agnus 8372A** — 2 MB variant (not all 8372A chips support 2 MB; check marking)
2. **2 MB of Chip RAM physically installed** — requires modified A3000 or a third-party board
3. **OS 2.0 or later** — earlier OS does not manage the extended Chip RAM
On the A3000, 2 MB Chip RAM is the standard configuration. On A500/A2000 with Super Agnus, it requires a RAM expansion that adds 1 MB in the Chip RAM window.
## Address Space Layout with 2 MB Chip RAM
```
$000000$1FFFFF 2 MB Chip RAM (DMA accessible by all channels)
$200000+ Fast RAM (CPU only)
```
The Chip RAM extends from $000000 to $1FFFFF. Previously, $100000$1FFFFF was "ranger" slow RAM, not DMA-accessible.
## OS Detection and Use
AmigaOS automatically discovers Chip RAM size via the exec memory list:
```c
/* Check available Chip RAM */
ULONG chip_free = AvailMem(MEMF_CHIP);
ULONG chip_total = AvailMem(MEMF_CHIP | MEMF_TOTAL);
```
The exec memory list is built at boot time from the chip RAM size detected by the ROM initialisation code, which queries Agnus's internal address counter.
## AmigaOS ROM Initialisation (Exec init)
During cold boot, the Kickstart ROM probes Chip RAM size:
1. Write a test pattern to $100000 (top of 1 MB range)
2. Read back — if the value matches, 2 MB Chip RAM is present
3. The exec `MemHeader` for Chip RAM is extended to $1FFFFF
This is performed in the `RomBoot()``InitCode()` sequence before the exec memory system is fully initialised.
## Implications for Programming
- **Bitplane pointers** can address any location in the 2 MB range
- **Copper lists, sprite data, audio samples** can all use the upper 1 MB
- `AllocMem(size, MEMF_CHIP)` will draw from the full 2 MB pool
- **MEMF_24BITDMA** is set on Chip RAM to indicate DMA accessibility within the 24-bit space
## Common Pitfall: 1 MB vs 2 MB Super Agnus
Some Super Agnus chips (8372A rev 1) are hardware-limited to 1 MB despite the ECS part number. Identifying the 2 MB variant:
```asm
; Read the Agnus chip ID from VPOSR
move.w $DFF004, d0 ; VPOSR
and.w #$7F00, d0 ; mask to chip ID bits
cmp.w #$2300, d0 ; 8372A 2MB = ID $23?
beq .is_2mb_agnus
```
Software should not assume 2 MB Chip RAM — always use `AvailMem()` to determine the actual size.
## References
- Commodore A3000 Technical Reference Manual — memory section
- AmigaMail Vol. 2 — Chip RAM expansion articles
- NDK39: `exec/memory.h` — MEMF flags
- ADCD 2.1 Hardware Manual — memory map section

View file

@ -0,0 +1,119 @@
[← Home](../../README.md) · [Hardware](../README.md) · [ECS](README.md)
# ECS Chipset Internals — Super Agnus & ECS Denise
## Super Agnus (MOS 8372A)
### Chip RAM Addressing
OCS Agnus could only generate 19-bit DMA addresses (512 KB) or 20-bit (1 MB with Fat Agnus). Super Agnus extends this to **21 bits**, addressing 2 MB of Chip RAM.
The revision of Super Agnus present determines the Chip RAM limit:
| Part | Chip RAM Max | Marking |
|---|---|---|
| 8372A rev 1 | 1 MB | AGNUS 8372A |
| 8372A rev 4+ | 2 MB | AGNUS 8372A (2MB) |
> [!NOTE]
> Software cannot assume 2 MB Chip RAM is available just because Super Agnus is present. The actual installed RAM amount must be checked via `AvailMem(MEMF_CHIP)`.
### Extended DMA Window
Super Agnus extends the bitplane DMA fetch window, allowing:
- Full overscan displays without copper tricks
- Access to the full 2 MB address range for all DMA channels
### AGNUS ID Register
Super Agnus provides an ID register readable via the `VPOSR` / `DIWSTRT` path. The chip revision can be read:
```asm
move.w VPOSR+custom, d0 ; read VPOSR
lsr.w #8, d0 ; shift to get Agnus ID in low byte
```
| VPOSR[15:8] | Chip | Notes |
|---|---|---|
| $00 | OCS Agnus 8367/8361 | Original |
| $10 | OCS Fat Agnus 8371 | 1 MB PAL |
| $20 | Super Agnus 8372A | ECS, 1 or 2 MB |
| $30 | Super Agnus 8372B | Some ECS |
---
## ECS Denise (MOS 8373)
### New Capabilities
ECS Denise adds to OCS Denise (8362):
1. **BPLCON3** — new control register for border colour, sprite bank
2. **Sub-pixel scrolling** — additional scroll control bits
3. **Genlock extensions** — improved external sync handling
4. **Border blank** — BPLCON3 can blank the border area to colour 0
### DENISEID — Revision Register
ECS Denise provides a self-identification register at `$DFF07C` (read only on ECS+):
```asm
move.w $DFF07C, d0 ; read DENISEID
```
| Value | Chip |
|---|---|
| $FFFF | OCS Denise 8362 (register not present) |
| $00FC | ECS Denise 8373 |
| $00F8 | AGA Lisa |
---
## BPLCON3 — ECS Denise Extension
New register at `$DFF106` (ECS only, must not be written on OCS):
```
bit 15-13: BANK2-0 — sprite colour bank (AGA: upper 4 bits of colour reg)
bit 12-10: PF2OF2-0 — playfield 2 colour offset (for dual playfield)
bit 9: LOCT — low colour enable (AGA HAM8 mode)
bit 6: BRDRBLNK — border blank: forces border area to colour 0
bit 5: BRDNTRAN — border not-transparent (disable border transparency)
bit 4: ZDCLKEN — horizontal/vertical count display
bit 3: BRDSPRT — sprites in border area enable
bit 2: EXTBLKEN — external blank signal
```
**Border blank use:**
```asm
move.w #$0020, $DFF106 ; set BRDRBLNK — blank border area
```
---
## GfxBase ChipRevBits0 Flags
```c
/* graphics/gfxbase.h */
#define GFXB_BIG_BLITTER 0 /* ECS big blitter present */
#define GFXB_BLITTER_DMA 1
#define GFXB_HR_AGNUS 2 /* Super Agnus */
#define GFXB_HR_DENISE 3 /* ECS Denise */
#define GFXB_AA_ALICE 4 /* AGA Alice */
#define GFXB_AA_LISA 5 /* AGA Lisa */
```
Reading chipset type in C:
```c
UBYTE rev = GfxBase->ChipRevBits0;
BOOL is_ecs_agnus = (rev & (1 << GFXB_HR_AGNUS)) != 0;
BOOL is_ecs_denise = (rev & (1 << GFXB_HR_DENISE)) != 0;
BOOL is_aga = (rev & (1 << GFXB_AA_ALICE)) != 0;
```
## References
- ADCD 2.1 Hardware Manual — ECS registers, Super Agnus chapter
- NDK39: `graphics/gfxbase.h`
- AmigaMail Vol. 2 — ECS chipset programming articles
- *Amiga Hardware Reference Manual* 3rd ed. — Appendix F (ECS)

View file

@ -0,0 +1,114 @@
[← Home](../../README.md) · [Hardware](../README.md) · [ECS](README.md)
# ECS Register Deltas vs OCS
This file documents registers that are **new or changed in ECS** versus OCS. OCS registers not listed here are unchanged.
## New Registers
### BEAMCON0 — $DFF1DC (ECS only)
The most significant new ECS register. Controls display sync generation and timing mode.
```
bit 15: HARDDIS — disable hard limits on display window
bit 14: LPENDIS — disable light pen latch
bit 13: VARVBEN — enable variable VBlank
bit 12: LOLDIS — disable long line sync
bit 11: CSCBEN — composite sync on colour burst
bit 10: VARVSYEN — variable vertical sync
bit 9: VARHSYEN — variable horizontal sync
bit 8: VARBEAMEN— variable beam enable
bit 7: DUAL — dual sync (separate composite + RGB)
bit 6: PAL — 1 = PAL timing, 0 = NTSC timing
bit 5: VARCSYEN — variable composite sync
bit 4: BLANKEN — enable blanking signal
bit 3: CSYTRUE — composite sync polarity
bit 2: VSYTRUE — vertical sync polarity
bit 1: HSYTRUE — horizontal sync polarity
bit 0: MONCSYEN — monochrome composite sync enable
```
**Default OCS behaviour** is replicated by writing $0000 to BEAMCON0 on ECS.
**PAL/NTSC software switch:**
```asm
move.w #$0020, $DFF1DC ; BEAMCON0 = PAL mode
move.w #$0000, $DFF1DC ; BEAMCON0 = NTSC mode
```
**Productivity mode (31 kHz):**
```asm
move.w #$0A00, $DFF1DC ; VARBEAMEN + VARVSYEN (31 kHz VGA-like)
```
### BPLCON3 — $DFF106 (ECS Denise only)
New bitplane/sprite control register — see `chipset_ecs.md` for full bit definition.
### DENISEID — $DFF07C (read only, ECS+)
Chip identification — see `chipset_ecs.md`.
## Changed / Extended Registers
### BPLCON0 — $DFF100
OCS BPLCON0 bit 0 (`ECSENA`) is reserved (must be 0) on OCS. On ECS:
```
bit 0: ECSENA — 1 = enable ECS features (required to use BPLCON3 etc.)
```
Must set `ECSENA=1` before programming ECS-specific display modes.
### DIWSTRT / DIWSTOP — $DFF08E / $DFF090
OCS: 8-bit vertical and 8-bit horizontal (limited range).
ECS: `DIWHIGH` ($DFF1E4) extends these to full 12-bit resolution:
### DIWHIGH — $DFF1E4 (ECS only)
```
bit 15: FLOP1 — playfield 1 window high bit
bit 7: FLOP0 — playfield 0 window high bit
bit 13-8: HB7-2 — horizontal window stop bits [7:2]
bit 5-0: HS7-2 — horizontal window start bits [7:2]
```
Allows the display window to be positioned anywhere in the extended beam range, enabling full overscan without copper tricks.
### FMODE — $DFF1FC (ECS read, AGA write)
On ECS, `$DFF1FC` is reserved. On AGA it becomes the `FMODE` register (see AGA section).
## ECS-Only DMA Extended Mode
Super Agnus extends the chip bus to allow DMA access across the full 2 MB range. No register change is needed — the chip detects the extended address automatically based on the installed RAM size and its internal revision.
## Programming Notes
> [!WARNING]
> **OCS compatibility:** Never write to `BEAMCON0`, `BPLCON3`, or `DIWHIGH` on OCS hardware — the addresses are not decoded on OCS and writes may corrupt adjacent chip state or have undefined effects. Always check `GfxBase->ChipRevBits0` before writing ECS registers.
Safe ECS register programming pattern:
```c
#include <graphics/gfxbase.h>
extern struct GfxBase *GfxBase;
void set_pal_mode(void) {
if (GfxBase->ChipRevBits0 & (1 << GFXB_HR_AGNUS)) {
/* Safe to write BEAMCON0 */
volatile UWORD *beamcon0 = (UWORD *)0xDFF1DC;
*beamcon0 = 0x0020; /* PAL */
}
}
```
## References
- ADCD 2.1 Hardware Manual — ECS register descriptions
- NDK39: `hardware/custom.h` (note: some ECS registers not in OCS struct)
- AmigaMail Vol. 2 — ECS programming tutorials
- *Amiga Hardware Reference Manual* 3rd ed. — Appendix F

View file

@ -0,0 +1,114 @@
[← Home](../../README.md) · [Hardware](../README.md) · [ECS](README.md)
# Gary & Gayle — System Controller Chips
## Gary (A3000)
**Gary** is the custom system controller chip in the A3000, combining functions that are discrete ICs on the A2000:
- **Bus controller**: Manages the interaction between 68030/68882, chip bus, and Zorro III
- **Auto-config controller**: Runs the Zorro expansion enumeration
- **DMA arbitration**: Between 68030, custom chips, and Zorro III DMA
- **SCSI interface glue**: Works with the A3000's built-in WD33C93 SCSI controller
- **ROM decode**: Maps Kickstart ROM into the address space
Gary is not directly programmable by user software; its configuration is set by hardware strapping and the ROM initialisation sequence.
## Gayle (A600 / A1200)
**Gayle** is the custom chip providing **IDE** and **PCMCIA** interface on the A600 and A1200. The A600 and A1200 use different Gayle revisions with different PCMCIA pinouts.
### Gayle Identification
```
A600 Gayle revision ID: read from $DA8000
A1200 Gayle revision ID: read from $DA8000
```
```asm
move.b $DA8000, d0 ; Read Gayle ID byte
```
| Byte | Machine |
|---|---|
| $D0 | A600 Gayle |
| $D1 | A1200 Gayle (revision 1) |
### Gayle Register Map (A600/A1200)
| Address | Register | Description |
|---|---|---|
| $DA8000 | GAYLE_ID | Chip ID (read shifts bits) |
| $DA9000 | GAYLE_INT_STATUS | Interrupt status |
| $DA9004 | GAYLE_INT_ENABLE | Interrupt enable |
| $DA9008 | GAYLE_CONTROL | Control register |
### IDE Interface
The IDE interface via Gayle is at `$DA0000` (A1200) or `$DA0000` (A600):
| Offset | Register | Description |
|---|---|---|
| $DA0000 | DATA | IDE data register (16-bit) |
| $DA0004 | ERROR/FEATURE | Error (read) / Feature (write) |
| $DA0008 | SECTOR_COUNT | Sector count |
| $DA000C | SECTOR_NUMBER | Sector number (LBA 7:0) |
| $DA0010 | CYLINDER_LOW | Cylinder low (LBA 15:8) |
| $DA0014 | CYLINDER_HIGH | Cylinder high (LBA 23:16) |
| $DA0018 | DRIVE_HEAD | Drive/Head/LBA (LBA 27:24) |
| $DA001C | STATUS/COMMAND | Status (read) / Command (write) |
| $DA101C | ALT_STATUS | Alternate status (no interrupt clear) |
| $DA101C | DEVICE_CONTROL | Device control (write) |
> [!NOTE]
> On the A1200, IDE registers are byte-wide on odd addresses in a 16-bit window. The data register is 16-bit. This differs from standard PC IDE — byte lanes are swapped relative to x86 convention.
### PCMCIA Interface (A600/A1200)
The A600 and A1200 support a Type II PCMCIA (PC Card) slot:
| Address Range | Type | Description |
|---|---|---|
| $600000$9FFFFF | Attribute memory | Card configuration (CIS access) |
| $A00000$A3FFFF | Common memory | Modem/network card data |
| $A40000$A7FFFF | Common memory (cont.) | |
| $600000 (Gayle) | Gayle attribute | Gayle own config space |
PCMCIA interrupt routing: Card interrupt → Gayle → CIA-A (`/FLG` pin) → CPU IPL 6.
### Gayle Interrupt Bits
```c
/* DA9000 GAYLE_INT_STATUS */
#define GAYLE_IRQ_IDE (1<<6) /* IDE drive interrupt */
#define GAYLE_IRQ_CARD (1<<5) /* PCMCIA card interrupt */
#define GAYLE_IRQ_BVD1 (1<<4) /* PCMCIA battery voltage 1 */
#define GAYLE_IRQ_BVD2 (1<<3) /* PCMCIA battery voltage 2 */
#define GAYLE_IRQ_WP (1<<2) /* PCMCIA write protect */
#define GAYLE_IRQ_CD (1<<1) /* PCMCIA card detect */
```
### Gayle Power Control
Gayle controls PCMCIA card power (5V / 3.3V on A1200 rev 1D+):
```c
/* GAYLE_CONTROL bits */
#define GAYLE_POW (1<<7) /* PCMCIA power on */
#define GAYLE_WS (1<<6) /* wait states for PCMCIA */
```
## AmigaOS IDE Access
AmigaOS accesses the Gayle IDE through the `scsi.device` or dedicated `ata.device` driver provided with OS 3.1+. Direct IDE programming is done in the filesystem handler (`trackdisk.device` replacement).
The standard path:
```
Application → dos.library → File System Handler → scsi.device → Gayle IDE
```
## References
- Commodore A600 Technical Reference Manual — Gayle chapter
- Commodore A1200 Technical Reference Manual — Gayle chapter
- ADCD 2.1 — `Devices_Manual_guide/` scsi.device
- NDK39: `hardware/gayle.h` (if present), community-documented Gayle registers

View file

@ -0,0 +1,97 @@
[← Home](../../README.md) · [Hardware](../README.md) · [ECS](README.md)
# ECS Productivity & Multiscan Display Modes
## Overview
ECS introduces **BEAMCON0** which allows the Amiga to produce non-standard display timings. The most useful are **productivity mode** (~28 kHz / 31 kHz horizontal) and **multiscan mode**, which provide flicker-free, high-resolution displays compatible with standard SVGA monitors.
These modes are available on A3000 and some A2000/A600 configurations with a multisync monitor.
## Standard OCS Timings (for reference)
| Mode | H rate | V rate | Resolution |
|---|---|---|---|
| PAL LORES | 15.625 kHz | 50 Hz | 320×256 |
| PAL HIRES | 15.625 kHz | 50 Hz | 640×256 |
| NTSC LORES | 15.720 kHz | 60 Hz | 320×200 |
| NTSC HIRES | 15.720 kHz | 60 Hz | 640×200 |
| PAL interlace | 15.625 kHz | 50 Hz (25 Hz/field) | 320×512 / 640×512 |
## ECS Multiscan Modes
| Mode | H rate | V rate | Resolution | BEAMCON0 |
|---|---|---|---|---|
| Productivity | ~28.6 kHz | 57 Hz | 640×480 | $0A00 |
| Super72 | ~28.6 kHz | 72 Hz | 800×600 (approx) | varies |
| DblPAL | 31.25 kHz | 50 Hz | 640×512 | custom |
| DblNTSC | 31.47 kHz | 60 Hz | 640×400 | custom |
| VGA-like | 31.47 kHz | 60 Hz | 640×480 | custom |
## Programming Productivity Mode
Productivity mode on the A3000 (PAL, 640×480):
```asm
; Set BEAMCON0 for variable beam timing
move.w #$0A00, $DFF1DC ; VARBEAMEN | VARVSYEN
; Program horizontal total, sync, blank (custom timing)
move.w #$71, $DFF1C0 ; HTOTAL (horizontal total - 1)
move.w #$0F, $DFF1C4 ; HSSTRT (H sync start)
move.w #$19, $DFF1C6 ; HSSTOP (H sync stop)
move.w #$09, $DFF1C8 ; HBSTRT (H blank start)
move.w #$71, $DFF1CA ; HBSTOP (H blank stop)
; Vertical timing
move.w #$0242, $DFF1E0 ; VTOTAL
move.w #$0015, $DFF1E6 ; VSSTRT
move.w #$001D, $DFF1E8 ; VSSTOP
```
> [!NOTE]
> The exact register values depend on the target monitor's sync requirements. The A3000's monitor (1084S or Commodore 1950) has a specific timing window. Always consult the monitor's datasheet.
## OS Support: ScreenModes
AmigaOS 3.1 integrates ECS productivity modes through the **screenmode** system:
```c
#include <graphics/modeid.h>
#include <libraries/asl.h>
/* Open a 640×480 productivity screen */
struct Screen *scr = OpenScreenTags(NULL,
SA_DisplayID, PRODDBL_MONITOR_ID | HIRES_KEY,
SA_Width, 640,
SA_Height, 480,
SA_Depth, 4,
TAG_DONE);
```
**Key mode IDs for ECS:**
```c
#define MULTISCAN_MONITOR_ID 0x00041000 /* multiscan / productivity */
#define SUPER72_MONITOR_ID 0x00081000
#define DBLNTSC_MONITOR_ID 0x00401000
#define DBLPAL_MONITOR_ID 0x00421000
```
These IDs are returned by `BestModeID()` and accepted by `OpenScreen()` / `OpenScreenTags()`.
## Hardware Requirements
- **ECS chipset** (Super Agnus + ECS Denise) — required for BEAMCON0
- **Multisync monitor** — standard 15 kHz PAL/NTSC monitors do not support 31 kHz
- **A3000** — has built-in multiscan support; A2000 requires a separate scan doubler card
## Flicker Fixer (A2000/A500)
Some Zorro II cards (e.g., Flicker Fixer by MicroWay, Indivision ECS) scan-double the 15 kHz signal to 31 kHz for use with VGA monitors. These operate transparently — no BEAMCON0 programming needed.
## References
- ADCD 2.1 Hardware Manual — ECS display modes
- AmigaMail Vol. 2 — ECS multiscan articles
- NDK39: `graphics/modeid.h` — monitor mode IDs
- A3000 Technical Reference Manual — display timing chapter

View file

@ -0,0 +1,41 @@
[← Home](../../README.md) · [Hardware](../README.md)
# OCS Chipset — A500 / A1000 / A2000
## Overview
The **Original Chip Set** (OCS) ships in the Amiga 1000 (1985), A500 (1987), and early A2000 boards. It consists of three custom chips: **Agnus**, **Denise**, and **Paula**, supported by the MOS 8520 CIA pair.
## Chip Summary
| Chip | MOS Part | Primary Responsibilities |
|---|---|---|
| **Agnus** | 8361 (PAL), 8367 (NTSC) | DMA controller, Copper coprocessor, Blitter, address generation |
| **Denise** | 8362 | Display: bitplane fetch decode, sprite decode, colour output |
| **Paula** | 8364 | Audio DMA (4 channels), floppy disk I/O, serial port, interrupts |
## Contents
| File | Topic |
|---|---|
| [chipset_ocs.md](chipset_ocs.md) | Chip internals, DMA priorities, bus arbitration |
| [custom_registers.md](custom_registers.md) | Full OCS register map $DFF000$DFF1FE |
| [copper.md](copper.md) | Copper coprocessor: WAIT/SKIP/MOVE, copperlist format |
| [blitter.md](blitter.md) | Blitter: channels A/B/C/D, minterms, line mode, fill |
| [paula_audio.md](paula_audio.md) | Audio DMA: AUDxLCH/LCL/LEN/PER/VOL, interrupt |
| [paula_serial.md](paula_serial.md) | Serial port: SERPER/SERDATR, baud rate |
| [sprites.md](sprites.md) | Hardware sprites: SPRxPTH, control words, attach mode |
## OCS Limitations vs ECS/AGA
- Max **512 KB Chip RAM** on A500 rev 5 and earlier (Agnus 8361/8367 addresses 512 KB only)
- A500 rev 6+ allows 1 MB with Fat Agnus (part of later OCS run)
- No productivity display modes (ECS adds BEAMCON0)
- 32 colours max (or 64 EHB, or HAM 12-bit) in standard bitplane modes
- Blitter is 16-bit; no 64-bit fetch (AGA adds FMODE)
- No ECS Denise border features
## References
- ADCD 2.1 Hardware Manual: http://amigadev.elowar.com/read/ADCD_2.1/Hardware_Manual_guide/node0000.html
- *Amiga Hardware Reference Manual* 3rd ed., Chapter 58

View file

@ -0,0 +1,154 @@
[← Home](../../README.md) · [Hardware](../README.md) · [OCS](README.md)
# Blitter
## Overview
The **Blitter** (Block Image Transferrer) is a DMA-driven coprocessor inside Agnus. It performs block copy, fill, and line-draw operations directly on Chip RAM without CPU involvement, at DMA bus speed.
## Channels
The Blitter has four channels:
| Channel | Role | Register Pointer |
|---|---|---|
| **A** | Source (with mask) | BLTAPTH/BLTAPTL |
| **B** | Source (with shift) | BLTBPTH/BLTBPTL |
| **C** | Source (destination content) | BLTCPTH/BLTCPTL |
| **D** | Destination | BLTDPTH/BLTDPTL |
Any channel can be disabled per operation. The result written to D is computed as a **minterm** (boolean function) of A, B, C.
## BLTCON0 — Control Register 0
```
bit 15-8: USEA, USEB, USEC, USED (enable channels A/B/C/D)
bit 7-0: LF (Logic Function / minterm) — 8-bit truth table
Bit = result bit for combination of A,B,C
Bit 7: !A!B!C
Bit 6: A!B!C
Bit 5: !AB!C
Bit 4: AB!C
Bit 3: !A!BC
Bit 2: A!BC
Bit 1: !ABC
Bit 0: ABC
```
### Common Minterm Values
| Minterm | Hex | Operation |
|---|---|---|
| D = A AND B | $C0 | Mask copy |
| D = A | $F0 | Simple copy (A only) |
| D = A OR C | $FC | Overlay (A onto C) |
| D = A XOR C | $3C | XOR blit |
| D = NOT A | $0F | Invert A |
| D = (A AND B) OR (!A AND C) | $CA | Cookie-cut (transparency) |
### Cookie-Cut Example
Cookie-cut (transparent sprite blit):
```asm
; A = mask (1=opaque, 0=transparent)
; B = sprite data
; C = background
; D = result: background where mask=0, sprite where mask=1
; Minterm: D = (A AND B) OR (!A AND C) = $CA
move.w #$09F0, BLTCON0 ; USE A,B,C,D; minterm=$CA
move.w #$0000, BLTCON1
```
## BLTCON1 — Control Register 1
```
bit 15: DOFF — disable D write (dry run for fill detection)
bit 4: IFE — inclusive fill enable
bit 3: EFE — exclusive fill enable
bit 2: FCI — fill carry in (start state)
bit 1: DESC — descending mode (blit from bottom-right)
bit 0: LINE — line draw mode
```
## BLTSIZE — Start Blitter
```
BLTSIZE = (height << 6) | width_in_words
```
Writing to BLTSIZE starts the blit operation immediately. The blitter holds the bus until complete.
Example — blit 16×16 pixels (1 word wide, 16 lines):
```asm
move.w #((16<<6)|1), BLTSIZE
```
Example — blit 320×256 (20 words wide, 256 lines):
```asm
move.w #((256<<6)|20), BLTSIZE
```
## Modulo Registers
Modulo is the number of **bytes** to skip at the end of each row (to move to the start of the next row in a larger bitmap):
```
BLTxMOD = (bytes_per_row_in_bitmap) - (width_of_blit_in_bytes)
```
For a 320-pixel wide (40-byte row) bitmap, blitting a 32-pixel (4-byte) wide section:
```
Modulo = 40 - 4 = 36
```
## First/Last Word Masks
`BLTAFWM` (first word mask) and `BLTALWM` (last word mask) mask the A channel for the first and last word of each blit row, allowing sub-word-aligned blitting.
For a fully aligned blit with no partial words:
```asm
move.w #$FFFF, BLTAFWM
move.w #$FFFF, BLTALWM
```
## Line Draw Mode (BLTCON1 bit 0)
In line mode, the blitter draws a line between two points using the Bresenham algorithm:
- A channel provides the single pixel pattern (usually $8000 for MSB)
- D channel is the destination bitmap
- BLTSIZE specifies the line length (height=octant length, width=2)
- BLTCON1 encodes octant, sign flags, and texture data
Line mode is used by `graphics.library` `Draw()` calls internally.
## Fill Mode (BLTCON1 IFE/EFE)
**Exclusive fill (EFE):** Each set bit toggles the fill state — produces XOR fill (like polygon rasterisation).
**Inclusive fill (IFE):** Set bit turns fill on, stays on until end of row — used for solid polygon fill.
Fill operates in D channel only (no source channels active). BLTCON1 `DESC` bit = 1 when filling bottom-up.
## Waiting for Blitter Completion
```asm
; Busy-wait on BLTCON0 busy bit
WaitBlit:
btst #6, DMACONR+1 ; test BBUSY bit (bit 14 of DMACONR, byte=bit6)
bne.s WaitBlit
```
Or via exec (preferred):
```c
WaitBlit(); /* graphics.library — waits and resets to safe state */
```
> [!CAUTION]
> Never start a blit while the blitter is busy. Always call `WaitBlit()` or poll `DMACONR[BBUSY]` before setting up new blit registers.
## References
- ADCD 2.1 Hardware Manual — Blitter chapter
- NDK39: `hardware/blit.h`, `graphics/blitattr.h`
- graphics.library Autodocs: `BltBitMap`, `BltTemplate`, `BltClear`
- http://amigadev.elowar.com/read/ADCD_2.1/Hardware_Manual_guide/node006D.html

View file

@ -0,0 +1,107 @@
[← Home](../../README.md) · [Hardware](../README.md) · [OCS](README.md)
# OCS Chipset Internals
## Architecture
The three OCS chips share a common **chip bus** (also called the chip bus or DMA bus). Agnus is the bus master — it arbitrates between the CPU, Copper, Blitter, and DMA channels.
```mermaid
block-beta
columns 3
CPU["68000 CPU"] Agnus["Agnus\n(DMA Master)"] ChipRAM["Chip RAM\n512 KB"]
space Paula["Paula\n(Audio/Disk/Serial)"] space
space Denise["Denise\n(Display)"] space
CPU-- "bus request" -->Agnus
Agnus-- "DMA cycles" -->ChipRAM
Paula-- "DMA req" -->Agnus
Denise-- "bitplane/sprite data" -->Agnus
```
## DMA Channels and Priorities
Agnus schedules DMA cycles across a fixed priority scheme within each horizontal raster line (228 colour clocks per line, PAL):
| Priority | DMA Channel | Register Bits |
|---|---|---|
| 1 (highest) | Disk | `DMACONR[4]` DSKEN |
| 2 | Sprite | `DMACONR[2]` SPREN |
| 3 | Bitplane | `DMACONR[8]` BPLEN |
| 4 | Copper | `DMACONR[7]` COPEN |
| 5 | Blitter | `DMACONR[6]` BLTEN |
| 6 | Audio ch 03 | `DMACONR[0..3]` AUD0AUD3 |
| 7 (lowest) | CPU | Remaining cycles |
The CPU only gets cycles not consumed by DMA — this is why heavy DMA usage (e.g., full-screen bitplanes + sprites + audio) can starve the CPU noticeably on OCS.
## DMACON Register
Write `$DFF096` (DMACON), read `$DFF002` (DMACONR):
```
bit 15: SET/CLR — write: 1=set bits, 0=clear bits (read: always 0)
bit 14: BBUSY — blitter busy (read only)
bit 13: BZERO — blitter zero flag (read only)
bit 10: (reserved)
bit 9: MASTER — master DMA enable (must be set for any DMA)
bit 8: BPLEN — bitplane DMA
bit 7: COPEN — Copper DMA
bit 6: BLTEN — Blitter DMA
bit 5: SPREN — Sprite DMA
bit 4: DSKEN — Disk DMA
bit 3: AUD3EN — Audio channel 3 DMA
bit 2: AUD2EN — Audio channel 2 DMA
bit 1: AUD1EN — Audio channel 1 DMA
bit 0: AUD0EN — Audio channel 0 DMA
```
Enable all standard DMA:
```asm
move.w #$8380,DMACON ; SET + MASTER + BPLEN + COPEN
move.w #$800F,DMACON ; SET + AUD0-3
```
## INTENA / INTREQ — Interrupt System
Write `$DFF09A` (INTENA), read `$DFF01C` (INTENAR):
Write `$DFF09C` (INTREQ), read `$DFF01E` (INTREQR):
```
bit 14: INTEN — global interrupt enable (INTENA only)
bit 13: EXTER — external/CIA interrupt (IPL6)
bit 12: DSKSYNC — disk sync (IPL5)
bit 11: RBF — serial receive buffer full (IPL5)
bit 10: AUD3 — audio channel 3 (IPL4)
bit 9: AUD2 — audio channel 2 (IPL4)
bit 8: AUD1 — audio channel 1 (IPL4)
bit 7: AUD0 — audio channel 0 (IPL4)
bit 6: BLIT — blitter finished (IPL3)
bit 5: VERTB — vertical blank (IPL3)
bit 4: COPPER — copper interrupt (IPL3)
bit 3: PORTS — CIA-A port interrupts (IPL2)
bit 2: SOFT — software interrupt (IPL1)
bit 1: DSKBLK — disk block finished (IPL1)
bit 0: TBE — serial transmit buffer empty (IPL1)
```
To enable vertical blank interrupt:
```asm
move.w #$C020,INTENA ; SET + INTEN + VERTB
```
## Agnus: Bitplane DMA Timing
During each scan line, Agnus fetches bitplane data for the current line. For a 320-pixel wide, 4-bitplane display, Agnus takes 40 DMA cycles per line for bitplane fetch. On a standard PAL line with 227 clock cycles, this leaves ~187 cycles for CPU + other DMA.
**Bitplane DMA pointers** (set at start of each frame or via Copper):
```
BPL1PTH/BPL1PTL $DFF0E0/$DFF0E2 Bitplane 1 pointer (high/low word)
BPL2PTH/BPL2PTL $DFF0E4/$DFF0E6
... up to BPL6 for OCS (6 bitplanes max)
```
## References
- ADCD 2.1 Hardware Manual — Agnus/DMA chapters
- NDK39: `hardware/dmabits.h`, `hardware/intbits.h`, `hardware/custom.h`
- *Amiga Hardware Reference Manual* 3rd ed.

View file

@ -0,0 +1,128 @@
[← Home](../../README.md) · [Hardware](../README.md) · [OCS](README.md)
# Copper Coprocessor
## Overview
The **Copper** (co-processor) is built into Agnus. It executes a simple instruction list (the **copperlist**) in sync with the video beam, allowing precise per-scanline changes to any writable custom register — without CPU intervention.
The Copper can only write to custom registers (it cannot access Chip RAM directly), but it can change bitplane pointers, colours, BPLCON0, sprite pointers, and any other `$DFF0xx` register on a cycle-accurate basis.
## Copper Instruction Set
The Copper has exactly **three instructions**, each exactly **32 bits** (two 16-bit words):
### 1. MOVE — Write a Register
```
Word 1: [RRR RRRR RRR0] Destination register address (must be even, bit 0 = 0)
Word 2: [DDDD DDDD DDDD] Data to write
```
Example — set COLOR00 to red at scanline 100:
```
DC.W $0180, $0F00 ; MOVE COLOR00, $0F00 (red)
```
### 2. WAIT — Wait for Beam Position
```
Word 1: [VVVV VVVH HHHH HH1] Vertical pos[8:1], Horizontal pos[8:3], bit0=1
Word 2: [EVVV VVVH HHHH HHx0] Enable, VP mask, HP mask, bit0=0
```
The Copper stalls until the beam reaches `(V,H) AND (Vmask,Hmask)`.
Standard full-precision WAIT:
```
DC.W $6401, $FF00 ; WAIT for line $64 (100), any H — full V mask
```
WAIT for end of frame (copper stop):
```
DC.W $FFFF, $FFFE ; WAIT $FF,$FF — impossible position → stop copper
```
### 3. SKIP — Conditional Skip
```
Word 1: [VVVV VVVH HHHH HH1] Same format as WAIT
Word 2: [EVVV VVVH HHHH HHx1] Same as WAIT but bit 0 = 1 (SKIP flag)
```
If beam has passed the position, skip the next instruction. Used for double-buffered copper switching.
## Copperlist Format
A copperlist is an array of 32-bit instruction pairs in **Chip RAM**, terminated by:
```
DC.W $FFFF, $FFFE
```
Example — colour cycle on vertical blank:
```asm
Copperlist:
DC.W $0180, $0000 ; COLOR00 = black
DC.W $4401, $FF00 ; WAIT line 68 (display area start)
DC.W $0180, $0F00 ; COLOR00 = red
DC.W $6401, $FF00 ; WAIT line 100
DC.W $0180, $00F0 ; COLOR00 = green
DC.W $FFFF, $FFFE ; END
```
## Copper Pointers and Control
| Register | Offset | Description |
|---|---|---|
| COP1LCH | $080 | Copper list 1 pointer high word |
| COP1LCL | $082 | Copper list 1 pointer low word |
| COP2LCH | $084 | Copper list 2 pointer high word |
| COP2LCL | $086 | Copper list 2 pointer low word |
| COPJMP1 | $088 | Strobe: restart copper from list 1 (any write) |
| COPJMP2 | $08A | Strobe: restart copper from list 2 |
| COPCON | $02E | Copper danger bit (CDANG) |
**COPCON** bit 1 (`CDANG`): When set, Copper is allowed to write to registers `$40``$7F` (blitter registers). Should be 0 in normal use to prevent runaway copperlists from corrupting the blitter.
## Activating the Copper
```asm
; Load copperlist address into copper list 1
move.l #Copperlist, d0
move.w d0, COP1LCL+custom ; low word
swap d0
move.w d0, COP1LCH+custom ; high word
; Restart copper (triggers on next VBlank)
move.w d0, COPJMP1+custom ; value irrelevant, strobe only
; Enable copper DMA
move.w #$8280, DMACON+custom ; SET + MASTER + COPEN
```
## Dual-Playfield and HAM via Copper
Common copper techniques:
**Split-screen different palettes:** Change `COLOR00``COLOR31` registers mid-screen via a WAIT at the split scanline.
**Mid-screen bitplane pointer change:** Redirect `BPL1PTH/BPL1PTL` to a different bitmap half-way through the display — used for large vertical scrolling without double-buffering the full screen.
**BPLCON0 mid-screen:** Switch between `HIRES` and `LORES`, or between 6-plane and 4-plane modes, on different lines.
**Raster bars:** Write a different colour to COLOR00 on every scanline using sequential WAIT+MOVE pairs.
## Graphics Library vs Direct Copper
AmigaOS's `graphics.library` manages the Copper list internally:
- `MrgCop()` merges system and user copper lists
- `LoadView()` installs a View structure's copper list
- `WaitTOF()` waits for the top-of-frame (VBlank) before the copper restarts
Direct copper access should be done via `GetColorMap()`, `SetRGB4()` and official graphics calls, or through a custom `View`/`ViewPort`/`ColorMap` structure passed to `LoadView()`.
## References
- ADCD 2.1 Hardware Manual — Copper chapter
- NDK39: `hardware/custom.h`, `graphics/copper.h`
- *Amiga Hardware Reference Manual* 3rd ed., Chapter 6

View file

@ -0,0 +1,183 @@
[← Home](../../README.md) · [Hardware](../README.md) · [OCS](README.md)
# OCS Custom Register Map
Base address: `$00DFF000`. All registers are 16-bit (word) wide. Byte access is valid for the appropriate byte lane.
Legend: **R** = read, **W** = write, **RW** = read-write, **S** = strobe (write triggers action)
## DMA and Interrupt Control
| Offset | Name | Dir | Description |
|---|---|---|---|
| $002 | DMACONR | R | DMA control register (read) |
| $01C | INTENAR | R | Interrupt enable (read) |
| $01E | INTREQR | R | Interrupt request (read) |
| $096 | DMACON | W | DMA control (write: bit15=SET/CLR) |
| $09A | INTENA | W | Interrupt enable (write: bit15=SET/CLR) |
| $09C | INTREQ | W | Interrupt request — ack / force |
## Beam Position (Read Only)
| Offset | Name | Dir | Description |
|---|---|---|---|
| $004 | VPOSR | R | Vertical position high, LOF bit |
| $006 | VHPOSR | R | Vertical + horizontal beam position |
| $007 | VHPOS | R | Horizontal beam position (byte) |
## Copper
| Offset | Name | Dir | Description |
|---|---|---|---|
| $02E | COPCON | W | Copper control (CDANG bit) |
| $080 | COP1LCH | W | Copper list 1 pointer high |
| $082 | COP1LCL | W | Copper list 1 pointer low |
| $084 | COP2LCH | W | Copper list 2 pointer high |
| $086 | COP2LCL | W | Copper list 2 pointer low |
| $088 | COPJMP1 | S | Restart Copper from list 1 |
| $08A | COPJMP2 | S | Restart Copper from list 2 |
| $08C | COPINS | W | Copper instruction (direct write) |
| $000 | BLTDDAT | R | Blitter dest early read (Copper use) |
## Blitter
| Offset | Name | Dir | Description |
|---|---|---|---|
| $040 | BLTCON0 | W | Blitter control 0 (minterm, channels) |
| $042 | BLTCON1 | W | Blitter control 1 (line mode, fill) |
| $044 | BLTAFWM | W | First word mask, channel A |
| $046 | BLTALWM | W | Last word mask, channel A |
| $048 | BLTCPTH | W | Channel C pointer high |
| $04A | BLTCPTL | W | Channel C pointer low |
| $04C | BLTBPTH | W | Channel B pointer high |
| $04E | BLTBPTL | W | Channel B pointer low |
| $050 | BLTAPTH | W | Channel A pointer high |
| $052 | BLTAPTL | W | Channel A pointer low |
| $054 | BLTDPTH | W | Destination pointer high |
| $056 | BLTDPTL | W | Destination pointer low |
| $058 | BLTSIZE | W | Blitter size + start (height×64 + width) |
| $060 | BLTCMOD | W | Channel C modulo |
| $062 | BLTBMOD | W | Channel B modulo |
| $064 | BLTAMOD | W | Channel A modulo |
| $066 | BLTDMOD | W | Destination modulo |
| $070 | BLTCDAT | W | Channel C data register |
| $072 | BLTBDAT | W | Channel B data register |
| $074 | BLTADAT | W | Channel A data register |
## Bitplane Pointers
| Offset | Name | Dir | Description |
|---|---|---|---|
| $0E0 | BPL1PTH | W | Bitplane 1 pointer high |
| $0E2 | BPL1PTL | W | Bitplane 1 pointer low |
| $0E4 | BPL2PTH | W | Bitplane 2 pointer high |
| $0E6 | BPL2PTL | W | Bitplane 2 pointer low |
| $0E8 | BPL3PTH | W | Bitplane 3 pointer high |
| $0EA | BPL3PTL | W | Bitplane 3 pointer low |
| $0EC | BPL4PTH | W | Bitplane 4 pointer high |
| $0EE | BPL4PTL | W | Bitplane 4 pointer low |
| $0F0 | BPL5PTH | W | Bitplane 5 pointer high |
| $0F2 | BPL5PTL | W | Bitplane 5 pointer low |
| $0F4 | BPL6PTH | W | Bitplane 6 pointer high |
| $0F6 | BPL6PTL | W | Bitplane 6 pointer low |
## Bitplane Control
| Offset | Name | Dir | Description |
|---|---|---|---|
| $100 | BPLCON0 | W | Bitplane control 0 (depth, HAM, HIRES, LACE) |
| $102 | BPLCON1 | W | Bitplane scroll (fine scroll values) |
| $104 | BPLCON2 | W | Sprite vs bitplane priority |
| $108 | BPL1MOD | W | Bitplane modulo (odd planes) |
| $10A | BPL2MOD | W | Bitplane modulo (even planes) |
| $110 | BPL1DAT | W | Bitplane 1 data register |
| $112 | BPL2DAT | W | Bitplane 2 data register |
| $114 | BPL3DAT | W | Bitplane 3 |
| $116 | BPL4DAT | W | Bitplane 4 |
| $118 | BPL5DAT | W | Bitplane 5 |
| $11A | BPL6DAT | W | Bitplane 6 |
**BPLCON0 bit layout:**
```
bit 15: HIRES (1 = 640 pixel wide)
bit 14-12: BPU2-0 (number of bitplanes: 06)
bit 11: HAM (1 = Hold-And-Modify mode)
bit 10: DPF (dual playfield)
bit 9: COLOR (0 = monochrome, 1 = colour)
bit 8: GAUD (genlock audio)
bit 7-4: (various, OCS = 0)
bit 1: ERSY (external sync)
bit 0: ECSENA (ECS enable — must be 0 on OCS)
```
## Display Window and Fetch
| Offset | Name | Dir | Description |
|---|---|---|---|
| $08E | DIWSTRT | W | Display window start (V and H start) |
| $090 | DIWSTOP | W | Display window stop |
| $092 | DDFSTRT | W | Display data fetch start |
| $094 | DDFSTOP | W | Display data fetch stop |
## Sprite Pointers and Data
| Offset | Name | Dir | Description |
|---|---|---|---|
| $120 | SPR0PTH | W | Sprite 0 pointer high |
| $122 | SPR0PTL | W | Sprite 0 pointer low |
| ... | ... | | Sprites 17 follow at +4 each |
| $13E | SPR7PTL | W | Sprite 7 pointer low |
| $140 | SPR0POS | W | Sprite 0 position |
| $142 | SPR0CTL | W | Sprite 0 control |
| $144 | SPR0DATA | W | Sprite 0 image data word A |
| $146 | SPR0DATB | W | Sprite 0 image data word B |
| ... | | | Sprites 17 follow |
| $178 | SPR7DATB | W | Sprite 7 image data word B |
## Audio Registers
| Offset | Name | Dir | Description |
|---|---|---|---|
| $0A0 | AUD0LCH | W | Audio ch 0 pointer high |
| $0A2 | AUD0LCL | W | Audio ch 0 pointer low |
| $0A4 | AUD0LEN | W | Audio ch 0 length (words) |
| $0A6 | AUD0PER | W | Audio ch 0 period (clock divider) |
| $0A8 | AUD0VOL | W | Audio ch 0 volume (064) |
| $0AA | AUD0DAT | W | Audio ch 0 data (direct, non-DMA) |
| ... | | | Channels 13 follow at +$10 |
## Serial Port
| Offset | Name | Dir | Description |
|---|---|---|---|
| $030 | SERDAT | W | Serial data and stop bits |
| $018 | SERDATR | R | Serial data receive and status |
| $032 | SERPER | W | Serial period and word length |
## Disk DMA
| Offset | Name | Dir | Description |
|---|---|---|---|
| $020 | DSKPTH | W | Disk pointer high |
| $022 | DSKPTL | W | Disk pointer low |
| $024 | DSKLEN | W | Disk length and write flag |
| $010 | ADKCONR | R | Audio / disk control (read) |
| $09E | ADKCON | W | Audio / disk control (write) |
| $07C | DSKSYNC | W | Disk sync word |
## Colour Registers
| Offset | Name | Dir | Description |
|---|---|---|---|
| $180 | COLOR00 | W | Background / colour 0 |
| $182 | COLOR01 | W | Colour 1 |
| ... | | | |
| $1BE | COLOR31 | W | Colour 31 |
OCS colours: 12-bit RGB (4 bits per component, $0RGB format).
## References
- *Amiga Hardware Reference Manual* 3rd ed. — Appendix B: Register Summary
- NDK39: `hardware/custom.h` — struct Custom definition
- http://amigadev.elowar.com/read/ADCD_2.1/Hardware_Manual_guide/node0000.html

View file

@ -0,0 +1,135 @@
[← Home](../../README.md) · [Hardware](../README.md) · [OCS](README.md)
# Paula — Audio DMA
## Overview
**Paula** (MOS 8364) provides four independent audio DMA channels (03), corresponding to the four hardware voice outputs:
| Channel | Stereo Output |
|---|---|
| 0 | Left |
| 1 | Right |
| 2 | Right |
| 3 | Left |
Each channel has its own DMA pointer, length, period (pitch), and volume registers. Paula fetches sample data from **Chip RAM** automatically at the rate set by the period register.
## Audio Registers Per Channel
Channel `n` (n = 03) registers at `$DFF0A0 + n×$10`:
| Offset | Name | Description |
|---|---|---|
| +$00/$01 | AUDnLCH/AUDnLCL | Sample pointer high/low (Chip RAM address) |
| +$04 | AUDnLEN | Sample length in words (not bytes) |
| +$06 | AUDnPER | Period — clock divider for playback rate |
| +$08 | AUDnVOL | Volume: 064 (0 = silent, 64 = full) |
| +$0A | AUDnDAT | Current sample word (direct, for non-DMA mode) |
## Sample Playback Rate
Paula derives playback rate from the system clock divided by the period register:
```
Sample rate (Hz) = System clock / Period
```
| System | Clock | Period for 8363 Hz | Period for 22050 Hz |
|---|---|---|---|
| PAL | 3,546,895 Hz | 428 | ~161 |
| NTSC | 3,579,545 Hz | 428 | ~162 |
> **8363 Hz** is the standard MOD tracker reference — period 428 on PAL plays middle-A at exactly 8363 Hz.
Period range: 12465535 (minimum period = maximum frequency ≈ 28 kHz PAL).
## Starting Audio DMA
```asm
; Load channel 0 with sample at SampleData, length 256 words
move.l #SampleData, d0
move.w d0, AUD0LCL+custom
swap d0
move.w d0, AUD0LCH+custom
move.w #256, AUD0LEN+custom ; 256 words = 512 bytes
move.w #428, AUD0PER+custom ; period 428 = 8287 Hz PAL
move.w #64, AUD0VOL+custom ; full volume
; Enable audio DMA for channel 0
move.w #$8201, DMACON+custom ; SET + MASTER + AUD0EN
; Enable audio interrupt (optional)
move.w #$A080, INTENA+custom ; SET + INTEN + AUD0
```
## ADKCON — Audio/Disk Control
| Bit | Name | Description |
|---|---|---|
| 15 | SET/CLR | Write: 1=set, 0=clear bits |
| 11 | PRECOMP1 | Disk precompensation |
| 10 | PRECOMP0 | Disk precompensation |
| 9 | MFMPREC | MFM precompensation |
| 7 | MSBSYNC | Audio sync from MSB |
| 6 | WORDSYNC | Disk word sync enable |
| 4 | ATPER | Channel 3 period from channel 2 data |
| 3 | ATVOL | Channel 3 volume from channel 2 data |
| 2 | ATPER2 | Channel 1 period from channel 0 data |
| 1 | ATVOL2 | Channel 1 volume from channel 0 data |
| 0 | (unused) | |
`ATPER`/`ATVOL` bits enable **audio modulation** — channel N's period/volume is modulated by the sample data of channel N-1. Used for AM synthesis effects (e.g., in many MOD players for 8-channel soft mixing).
## Interrupt Handling
Audio channels raise interrupts when their DMA pointer wraps to the beginning of the next buffer:
```
INTENA/INTREQ bits:
AUD0 = bit 7 (IPL 4)
AUD1 = bit 8 (IPL 4)
AUD2 = bit 9 (IPL 4)
AUD3 = bit 10 (IPL 4)
```
The interrupt fires when the channel has consumed its buffer and reloaded the pointer from the DMA registers. At this point, software can update AUDnLCH/L and AUDnLEN with the next buffer.
## Double-Buffering Pattern
```c
/* In interrupt handler (IPL 4 server for AUD0): */
void audio_interrupt(void)
{
/* Swap buffers */
UWORD *next = (active_buf == buf_a) ? buf_b : buf_a;
active_buf = next;
/* Load next buffer address */
custom.aud[0].ac_ptr = (UWORD *)next;
custom.aud[0].ac_len = BUF_WORDS;
}
```
## Direct (Non-DMA) Audio
For simple sound effects without DMA overhead, write directly to `AUDnDAT`:
```asm
move.w #$0080, AUD0VOL+custom ; volume 64 (approx)
move.w #$7FFF, AUD0DAT+custom ; max positive sample
```
This only produces a single sample word — not practical for continuous audio but useful for one-shot clicks/beeps.
## audio.device
AmigaOS provides `audio.device` for arbitrated, multi-application audio access:
- Allocates channels (bitmask: `{1,2,4,8}`)
- Creates `IOAudio` request, uses standard device I/O (`BeginIO`/`WaitIO`)
- Handles period, volume, sample pointer, cycle count
- See [10_devices/audio_device.md](../../10_devices/audio_device.md) for full API reference
## References
- ADCD 2.1 Hardware Manual — Paula audio chapter
- NDK39: `hardware/custom.h` (struct AudChannel), `devices/audio.h`
- Autodocs: audio.device — http://amigadev.elowar.com/read/ADCD_2.1/Includes_and_Autodocs_3._guide/node0081.html

View file

@ -0,0 +1,123 @@
[← Home](../../README.md) · [Hardware](../README.md) · [OCS](README.md)
# Paula — Serial Port
## Overview
**Paula** contains a hardware UART (Universal Asynchronous Receiver/Transmitter) for the Amiga's serial port. It is a simple, non-buffered serial interface — no FIFO, single transmit and single receive register.
The serial port operates at **RS-232 voltage levels** (via external level shifter on the A500/A1000). The A2000 and A3000 route through a MAX232 equivalent.
## Registers
| Register | Offset | Dir | Description |
|---|---|---|---|
| SERPER | $DFF032 | W | Serial period and word length |
| SERDAT | $DFF030 | W | Serial data transmit |
| SERDATR | $DFF018 | R | Serial data receive + status |
## SERPER — Baud Rate Configuration
```
bit 15: LONG — 0 = 8-bit words, 1 = 9-bit words
bit 14-0: Period — clock divider for baud rate
```
**Baud rate formula:**
```
Baud = System clock / (Period + 1)
```
| Baud Rate | PAL Period | NTSC Period |
|---|---|---|
| 300 | 11811 | 11929 |
| 1200 | 2952 | 2981 |
| 2400 | 1476 | 1490 |
| 4800 | 737 | 745 |
| 9600 | 368 | 372 |
| 19200 | 183 | 186 |
| 38400 | 91 | 92 |
| 115200 | 30 | 30 |
## SERDAT — Transmit
```
bit 15-11: Must be 1 (stop bits framing)
bit 10: Stop bit
bit 9-0: Data word (8 or 9 bits, MSB first relative to wire)
```
To transmit a byte (8-bit mode):
```asm
; Wait for TBE (transmit buffer empty) interrupt or poll
WaitTBE:
btst #0, SERDATR+1 ; TBE = bit 0 of SERDATR high byte...
; Actually check INTREQR bit TBE
beq.s WaitTBE
; Send byte $41 ('A'), 8-bit framing: $3C01 prefix + data
move.w #($3FC0 | 0x41), SERDAT+custom
```
Correct framing for 8-bit word:
```
SERDAT = $3C00 | byte_value ; bits[15:10] = %111111 (stop + start framing)
```
## SERDATR — Receive
```
bit 15: OVRUN — overrun error (data was not read before next byte arrived)
bit 14: RBF — receive buffer full (data ready to read)
bit 13: TBE — transmit buffer empty
bit 12: TSRE — transmit shift register empty
bit 11: RXD — current state of RXD pin
bit 9: STP — stop bit of received word
bit 8-0: Data — received byte (8 or 9 bits)
```
Read a byte:
```c
/* Wait for RBF */
while (!(custom.serdatr & SERDATF_RBF))
;
UBYTE ch = custom.serdatr & 0xFF;
/* Acknowledge interrupt */
custom.intreq = INTF_RBF;
```
## Interrupt Sources
| Event | INTENA/INTREQ bit | IPL |
|---|---|---|
| TBE (transmit buffer empty) | bit 0 `INTF_TBE` | 1 |
| RBF (receive buffer full) | bit 11 `INTF_RBF` | 5 |
RBF fires at IPL 5 — relatively high priority, since the receive register has no FIFO and an overrun loses data.
## serial.device
AmigaOS provides `serial.device` for managed serial access:
- Supports baud rates, parity, stop bits, word length
- Provides buffered read/write via `IOExtSer` structure
- Handles `CMD_READ`, `CMD_WRITE`, `SDCMD_SETPARAMS`, `SDCMD_QUERY`
- Multiple opens are not supported — one opener at a time
```c
#include <devices/serial.h>
struct IOExtSer *ser_req; /* allocated IOExtSer */
OpenDevice("serial.device", 0, (struct IORequest *)ser_req, 0);
ser_req->io_Baud = 9600;
ser_req->io_RBufLen = 4096;
ser_req->IOSer.io_Command = SDCMD_SETPARAMS;
DoIO((struct IORequest *)ser_req);
```
## References
- ADCD 2.1 Hardware Manual — Paula serial section
- NDK39: `hardware/custom.h`, `devices/serial.h`
- Autodocs: serial.device — http://amigadev.elowar.com/read/ADCD_2.1/Includes_and_Autodocs_3._guide/node013E.html

View file

@ -0,0 +1,136 @@
[← Home](../../README.md) · [Hardware](../README.md) · [OCS](README.md)
# Hardware Sprites
## Overview
The OCS/ECS chipset provides **8 hardware sprites**, each 16 pixels wide and arbitrarily tall. They are managed by Agnus's DMA and rendered by Denise independently of the bitplane display. Sprites are commonly used for the mouse pointer, weapons, overlays, and animated objects that must not disturb the background.
## Sprite Properties (OCS)
| Property | OCS Value |
|---|---|
| Count | 8 sprites |
| Width | 16 pixels fixed |
| Height | Programmable (any number of lines) |
| Colours | 3 (+1 transparent) per sprite |
| Colour source | Sprite colour registers (COLOR16COLOR31) |
| Attach mode | Pairs 0/1, 2/3, 4/5, 6/7 → 15 colours |
## Sprite Registers
Each sprite `n` (07) has four registers at `$DFF120 + n×$08`:
| Offset | Name | Description |
|---|---|---|
| +$00/$01 | SPRnPTH/SPRnPTL | Sprite DMA pointer (high/low) |
| +$04 | SPRnPOS | Vertical start, horizontal position |
| +$06 | SPRnCTL | Vertical stop, attach flag, H LSB |
| +$08 | SPRnDATA | Image data word A (current line) |
| +$0A | SPRnDATB | Image data word B (current line) |
## SPRnPOS — Position Register
```
bit 15-8: VSTART[8:1] Vertical start position (high 8 bits)
bit 7-0: HSTART[8:1] Horizontal position (bits 8..1, shifted right)
```
## SPRnCTL — Control Register
```
bit 15-8: VSTOP[8:1] Vertical stop position
bit 7: ATT Attach (pair this sprite with next)
bit 2: HSTART[0] Horizontal position LSB
bit 1: VSTOP[0] Vertical stop LSB
bit 0: VSTART[0] Vertical start LSB
```
## Horizontal Position Formula
```
H pixel = (HSTART[8:0]) - 1
```
Standard screen left edge is approximately H=128 (HSTART=$80).
## Sprite DMA Data Format
Each line of the sprite consists of two 16-bit words (DATA and DATB) fetched from Chip RAM:
```
For each scanline of sprite:
Word 1 (DATA): bit 15..0 → pixel bit 1 (colour bit 1)
Word 2 (DATB): bit 15..0 → pixel bit 0 (colour bit 0)
Pixel colour:
DATA[bit] = 0, DATB[bit] = 0 → transparent
DATA[bit] = 0, DATB[bit] = 1 → colour 1 (COLOR17 for sprite 0)
DATA[bit] = 1, DATB[bit] = 0 → colour 2 (COLOR18)
DATA[bit] = 1, DATB[bit] = 1 → colour 3 (COLOR19)
```
## Sprite Data in Memory
Agnus DMA reads the sprite from a memory block structured as:
```
Word: SPRnPOS value (copied to register at DMA start)
Word: SPRnCTL value (copied to register at DMA start)
[Repeat for each line:]
Word: SPRnDATA (image word A)
Word: SPRnDATB (image word B)
[End of sprite:]
Word: $0000 (SPRnPOS = 0 → null position signals end)
Word: $0000 (SPRnCTL = 0)
```
## Colour Mapping
Sprites share colour registers with bitplanes:
| Sprites | Colour Registers |
|---|---|
| 0 and 1 | COLOR16COLOR19 |
| 2 and 3 | COLOR20COLOR23 |
| 4 and 5 | COLOR24COLOR27 |
| 6 and 7 | COLOR28COLOR31 |
COLOR16 (the first colour of sprite pair 0/1) is always transparent — the sprite background. Only COLOR17COLOR19 are visible for sprites 0/1.
## Attached Sprites (15 Colours)
Pairing two sprites (`ATT` bit in SPRnCTL of the even sprite) combines their DATA/DATB bits to produce a 4-bit colour index (16 colours, one transparent):
```
4-bit colour = {SPR_even.DATA[bit], SPR_even.DATB[bit],
SPR_odd.DATA[bit], SPR_odd.DATB[bit]}
```
This gives 15 visible colours per pair, using COLOR16COLOR31 for pair 0/1.
## BPLCON2 — Sprite Priority
`BPLCON2` ($DFF104) controls the display priority of sprites vs bitplanes:
```
bit 5-3: PF2PRI, PF2P2-0 — Playfield 2 priority
bit 2-0: PF1PRI, PF1P2-0 — Playfield 1 and sprite priority
```
Default: sprites appear in front of all bitplanes.
## OS Mouse Pointer
AmigaOS's Intuition uses sprite 0 (and 1 in attached mode for colour pointer) for the mouse pointer. Intuition calls `SetPointer()` / `ClearPointer()` on a Window to install custom pointer sprites.
```c
SetPointer(window, pointer_data, height, width, x_offset, y_offset);
ClearPointer(window); /* restore system default */
```
## References
- ADCD 2.1 Hardware Manual — Sprites chapter
- NDK39: `hardware/sprite.h`, `intuition/intuition.h` (SetPointer)
- http://amigadev.elowar.com/read/ADCD_2.1/Hardware_Manual_guide/node00D7.html

View file

@ -0,0 +1,12 @@
[← Home](../README.md)
# Boot Sequence — Overview
## Section Index
| File | Description |
|---|---|
| [cold_boot.md](cold_boot.md) | Power-on to Kickstart: hardware init, ROM checksum |
| [kickstart_init.md](kickstart_init.md) | ROM scan, resident modules, ExecBase creation |
| [dos_boot.md](dos_boot.md) | BootStrap, eb_MountList, startup-sequence |
| [early_startup.md](early_startup.md) | Early Startup Control (ESC menu), boot priority |

View file

@ -0,0 +1,101 @@
[← Home](../README.md) · [Boot Sequence](README.md)
# Cold Boot — Power-On to Kickstart
## Overview
When the Amiga powers on or is reset (Ctrl-Amiga-Amiga), the 68000 CPU begins execution from the ROM. The boot process progresses from raw hardware init through to a fully running AmigaOS desktop in approximately 38 seconds.
---
## Hardware Initialisation (Pre-ROM)
### 1. CPU Reset Vector
The 68000 reads two longwords from address `$000000`:
```
$000000: Initial SSP (Supervisor Stack Pointer)
$000004: Initial PC (Program Counter) → ROM entry point
```
On Amiga, these locations map to Kickstart ROM at `$FC0000` (256 KB) or `$F80000` (512 KB). The ROM image contains:
- Word 01: SSP value (typically `$000400`)
- Word 23: PC value → ROM entry point
### 2. ROM Checksum
First code executed: compute checksum of entire ROM. If it fails → **red screen of death** (solid red background, no further boot).
```
Checksum: simple 32-bit additive checksum of all ROM longwords.
Result must equal $FFFFFFFF (complement to zero).
The last longword of the ROM is the complement value.
```
### 3. Chip Register Reset
```
- Write $7FFF to INTENA ($DFF09A) — disable all interrupts
- Write $7FFF to INTREQ ($DFF09C) — clear all pending interrupts
- Write $7FFF to DMACON ($DFF096) — disable all DMA
- CIA chips: reset timer, serial, port registers
```
### 4. Memory Detection
The ROM probes for available memory:
```
1. Test Chip RAM at $000000 by writing test patterns
2. Size Chip RAM: 256 KB, 512 KB, 1 MB, or 2 MB
3. Probe for Fast RAM at $C00000 (Ranger), $200000 (Slow/Ranger)
4. Probe for Zorro II auto-config space at $E80000
5. Probe for 32-bit fast RAM at $07000000+ (Zorro III)
```
### 5. Display Diagnostic Colours
During boot, the background colour indicates progress:
| Colour | Stage |
|---|---|
| Dark grey | ROM checksum passed |
| Light grey | Chip RAM sized |
| White | ExecBase initialised |
| Green flash | DOS boot starting |
| **Red** | ROM checksum fail |
| **Yellow** | Chip RAM test fail |
| **Blue** | Alert (Guru Meditation) |
---
## ROM Layout
### 256 KB ROM (Kickstart 1.x)
```
$FC0000$FFFFFF (256 KB)
$FC0000: Reset vectors (SSP + PC)
$FC0008: ROM header (version, checksum)
...
$FC0020: First resident module (exec.library)
...
$FFFFFC: Checksum complement word
```
### 512 KB ROM (Kickstart 2.0+)
```
$F80000$FFFFFF (512 KB)
$F80000: Reset vectors
$F80008: ROM header
...
$F80020: exec.library resident tag
...
$FFFFFC: Checksum complement
```
---
## References
- RKRM: *Hardware Reference Manual* — reset chapter

View file

@ -0,0 +1,121 @@
[← Home](../README.md) · [Boot Sequence](README.md)
# DOS Boot — Bootstrap, Mount List, Startup-Sequence
## Overview
After the Kickstart ROM initialises the kernel and resident modules, `dos.library` takes over to mount filesystems and run the user's startup scripts.
---
## Boot Sequence Flow
```
Kickstart ROM init complete
dos.library creates initial CLI process
BootStrap module (strap) reads boot block from boot device
Boot block executed (OFS/FFS bootblock code)
Mount configured devices from MountList (DEVS:DOSDrivers/)
Execute S:Startup-Sequence
Execute S:User-Startup
LoadWB (start Workbench)
```
---
## Boot Block Format
The first 2 sectors (1024 bytes) of a bootable disk:
```
Offset Size Description
$000 4 Boot block ID: "DOS\0" (OFS), "DOS\1" (FFS), etc.
$004 4 Checksum (complement to zero)
$008 4 Root block pointer (usually 880)
$00C ... Boot code (up to 1012 bytes of 68000 code)
```
### Boot Block Types
| ID | Hex | Type |
|---|---|---|
| `DOS\0` | `444F5300` | OFS (Old File System) |
| `DOS\1` | `444F5301` | FFS (Fast File System) |
| `DOS\2` | `444F5302` | OFS + International |
| `DOS\3` | `444F5303` | FFS + International |
| `DOS\4` | `444F5304` | OFS + Dir Cache |
| `DOS\5` | `444F5305` | FFS + Dir Cache |
| `DOS\6` | `444F5306` | OFS + Long Filenames (OS 3.2) |
| `DOS\7` | `444F5307` | FFS + Long Filenames (OS 3.2) |
---
## Boot Priority
Devices are booted in priority order. Highest priority device boots first:
| Device | Default Priority | Description |
|---|---|---|
| DF0: | 5 | First floppy drive |
| DH0: | 0 | First hard disk partition |
| DH1: | 5 | Second partition |
| DF1: | 10 | Second floppy |
Set in mountlist: `BootPri = 0` or via HDToolBox.
---
## Startup-Sequence
Standard `S:Startup-Sequence` for OS 3.1:
```
C:SetPatch QUIET ; apply ROM patches
C:AddBuffers DH0: 50 ; filesystem buffers
C:Version >NIL:
FailAt 21
MakeDir RAM:T RAM:Clipboards RAM:ENV RAM:ENV/Sys
Copy >NIL: ENVARC: RAM:ENV ALL NOREQ
Assign >NIL: T: RAM:T
Assign >NIL: CLIPS: RAM:Clipboards
Assign >NIL: REXX: S:
Assign >NIL: PRINTERS: DEVS:Printers
Assign >NIL: KEYMAPS: DEVS:Keymaps
Assign >NIL: LOCALE: SYS:Locale
Assign >NIL: LIBS: SYS:Classes ADD
Assign >NIL: HELP: LOCALE:Help DEFER
C:IPrefs
ConClip >NIL:
; Launch Workbench:
LoadWB
EndCLI >NIL:
```
---
## Early Startup Control (Boot Menu)
Hold **both mouse buttons** during boot to access the Early Startup Control menu:
| Option | Description |
|---|---|
| Boot With No Startup-Sequence | Skip S:Startup-Sequence |
| Boot Standard | Normal boot |
| Boot With: [device list] | Select boot device |
| Display Mode: [NTSC/PAL] | Override display standard |
---
## References
- RKRM: Boot chapter
- NDK39: `dos/dosextens.h` — BootStrap

View file

@ -0,0 +1,55 @@
[← Home](../README.md) · [Boot Sequence](README.md)
# Early Startup Control — Boot Options
## Overview
The **Early Startup Control** (ESC) menu appears when both mouse buttons are held during power-on or reset. It provides boot device selection, display mode override, and emergency recovery options.
---
## Activation
- **Both mouse buttons held** before the boot screen hand animation completes
- On A1200: hold both buttons during "floppy click" phase
- Requires Kickstart 2.0+ (not available on 1.3)
---
## Menu Options (OS 3.1)
```
┌──────────────────────────────────┐
│ EARLY STARTUP CONTROL │
│ │
│ Boot With No Startup-Sequence │
│ Boot Standard │
│ │
│ Boot Device: │
│ DF0: (pri 5) │
│ DH0: (pri 0) │
│ │
│ Display Mode: │
│ ● PAL ○ NTSC │
│ │
│ [BOOT] │
└──────────────────────────────────┘
```
---
## Recovery Scenarios
| Scenario | Fix |
|---|---|
| Corrupted Startup-Sequence | Boot with No Startup-Sequence, then `ED S:Startup-Sequence` |
| Wrong display mode (no image) | Hold both buttons → change PAL/NTSC |
| Boot from floppy instead of HD | Select DF0: as boot device |
| Assign loops / infinite boot | No Startup-Sequence → fix assigns manually |
---
## References
- RKRM: Early Startup chapter
- OS 3.1 AmigaGuide: `System/BootMenu`

View file

@ -0,0 +1,104 @@
[← Home](../README.md) · [Boot Sequence](README.md)
# Kickstart Initialisation — ROM Scan, ExecBase, Resident Modules
## Overview
After hardware init, the Kickstart ROM creates `ExecBase` and scans for **resident modules** — the OS components compiled into ROM. This process builds the entire OS kernel from tagged structures embedded in the ROM image.
---
## ExecBase Creation
1. Allocate ExecBase structure at a known address (end of Chip RAM)
2. Initialise memory lists, library list, device list, resource list
3. Store ExecBase pointer at **address `$000004`** — the global `SysBase`
4. Initialise the Supervisor stack, interrupt vectors, trap vectors
```
After this step:
*(ULONG *)4 == ExecBase pointer
SysBase->LibNode.lib_Version == Kickstart version
```
---
## Resident Module Scan
The ROM scanner searches for **RomTag** markers (`$4AFC`) throughout the ROM address range:
```c
struct Resident {
UWORD rt_MatchWord; /* $4AFC — magic marker */
struct Resident *rt_MatchTag; /* pointer to self (verification) */
APTR rt_EndSkip; /* skip past this address */
UBYTE rt_Flags; /* RTF_COLDSTART, RTF_SINGLETASK, etc. */
UBYTE rt_Version; /* version number */
UBYTE rt_Type; /* NT_LIBRARY, NT_DEVICE, NT_RESOURCE */
BYTE rt_Pri; /* init priority (higher = earlier) */
char *rt_Name; /* module name */
char *rt_IdString; /* version string */
APTR rt_Init; /* init function or auto-init table */
};
```
### Scan Algorithm
```
for addr in ROM_START to ROM_END step 2:
if *(UWORD *)addr == $4AFC:
tag = (struct Resident *)addr
if tag->rt_MatchTag == tag: /* self-pointer validates */
add to resident list
addr = tag->rt_EndSkip /* skip past module */
```
---
## Initialisation Phases
Residents are initialised in **priority order** within each phase:
### Phase 1: RTF_COLDSTART (flags bit 0)
Called during cold boot, single-tasking (no scheduler yet):
| Priority | Module | Description |
|---|---|---|
| 126 | `exec.library` | Core kernel |
| 120 | `expansion.library` | AutoConfig Zorro boards |
| 105 | `68040.library` | CPU trap handlers (if present) |
| 100 | `utility.library` | Tag/hook support |
| 70 | `graphics.library` | Display init |
| 50 | `layers.library` | Clipping layers |
| 50 | `intuition.library` | GUI subsystem |
| 40 | `timer.device` | Timing services |
| 30 | `keyboard.device` | Keyboard |
| 20 | `input.device` | Input event merging |
| 10 | `trackdisk.device` | Floppy |
| 50 | `dos.library` | AmigaDOS file system |
| 120 | `ramlib` | Disk-based library/device loader |
### Phase 2: RTF_SINGLETASK
Runs after all COLDSTART modules but before multitasking begins. Used by strap (bootstrap) module.
### Phase 3: RTF_AFTERDOS
Runs after DOS is available. External disk-based modules.
---
## After Resident Init
1. Multitasking enabled (scheduler starts)
2. `dos.library` creates initial CLI process
3. Boot task launches `strap` → reads boot block from DF0: or boot device
4. Control passes to `startup-sequence`
---
## References
- NDK39: `exec/resident.h`
- RKRM: Resident modules chapter

View file

@ -0,0 +1,51 @@
[← Home](../README.md)
# Executable Loader & HUNK Format
## Overview
This section covers the complete lifecycle of an AmigaOS executable:
1. **HUNK file format** — the binary container for all AmigaOS executables, libraries, and object files
2. **Loader pipeline** — how `dos.library` loads and relocates an executable into memory
3. **Object files** — how compilers produce relocatable object files for the linker
4. **Overlays** — how programs larger than available memory use the overlay system
## Contents
| File | Topic |
|---|---|
| [hunk_format.md](hunk_format.md) | Complete HUNK binary specification |
| [hunk_ext_deep_dive.md](hunk_ext_deep_dive.md) | HUNK_EXT: exports, imports, commons |
| [hunk_relocation.md](hunk_relocation.md) | HUNK_RELOC32/16/8 mechanics |
| [hunk_debug_info.md](hunk_debug_info.md) | HUNK_SYMBOL, HUNK_DEBUG (stabs) |
| [exe_load_pipeline.md](exe_load_pipeline.md) | LoadSeg → Process creation |
| [object_file_format.md](object_file_format.md) | Compiler object files (HUNK_UNIT) |
| [overlay_system.md](overlay_system.md) | HUNK_OVERLAY memory segmentation |
## Why HUNK?
HUNK is the native AmigaOS executable format, used from AmigaOS 1.0 through 3.x. It predates ELF/COFF and has these key properties:
- **Segmented**: separate code, data, and BSS hunks with independent memory allocation
- **Relocatable**: all absolute references are patched at load time (no ASLR; base address changes each run)
- **Typed memory**: each hunk can request `CHIP` or `FAST` memory independently
- **Symbol-complete**: optional HUNK_SYMBOL and HUNK_DEBUG hunks carry debugging information
## Key Concepts
| Term | Meaning |
|---|---|
| **Hunk** | One contiguous block in the binary (code, data, BSS, etc.) |
| **Segment** | A loaded hunk at runtime — a `BPTR`-linked list |
| **Segment list** | Chain of loaded hunks returned by `LoadSeg()` |
| **BPTR** | Amiga byte pointer — 32-bit value right-shifted by 2 (`ptr >> 2`) |
| **Relocation** | Patching absolute addresses based on actual load address |
| **LVO** | Library Vector Offset — negative offset from library base |
## References
- ADCD 2.1: `Includes_and_Autodocs_3._guide/` — dos.library LoadSeg autodoc
- NDK39: `dos/dos.h` — BPTR, segment handling macros
- *Amiga ROM Kernel Reference Manual: Libraries* — AmigaDOS chapter
- http://amigadev.elowar.com/read/ADCD_2.1/Libraries_Manual_guide/node0150.html

View file

@ -0,0 +1,276 @@
[← Home](../README.md) · [Loader & HUNK Format](README.md)
# Executable Load Pipeline
## Overview
This document traces the complete path from user request to a running process: `LoadSeg()` → memory allocation → relocation → `CreateProc()` → execution.
---
## Entry Points
| Function | Library | Description |
|---|---|---|
| `LoadSeg(name)` | dos.library | Load named file, return segment list |
| `InternalLoadSeg(fh, table, funcarray, stack)` | dos.library | Low-level load from open file handle |
| `NewLoadSeg(name, tags)` | dos.library (3.1+) | Tagged version of LoadSeg |
| `UnLoadSeg(seglist)` | dos.library | Free segment list |
| `CreateNewProc(tags)` | dos.library | Create process from segment list |
| `RunCommand(seg, stack, args, len)` | dos.library | Run segment in current process context |
---
## Phase 1: Parsing HUNK_HEADER
`InternalLoadSeg` opens the file and reads the header:
```c
1. Read magic word — must be $000003F3 (HUNK_HEADER)
2. Read resident library list (always 0 for standard executables)
3. Read num_hunks, first_hunk, last_hunk
4. Read size table: num_hunks longwords
Each longword: bits[31:30] = memory type, bits[29:0] = size in longs
```
The loader allocates one memory block per hunk using `AllocMem()`:
- Memory type from size longword bits → `MEMF_CHIP`, `MEMF_FAST`, or `MEMF_ANY`
- Size = longword_count × 4 bytes
---
## Phase 2: Memory Allocation
For each hunk (from first_hunk to last_hunk):
```c
ULONG size_longs = size_table[i] & ~0xC0000000;
ULONG mem_type = (size_table[i] >> 30) & 3;
ULONG memf;
switch (mem_type) {
case 0: memf = MEMF_PUBLIC; break;
case 1: memf = MEMF_CHIP; break;
case 2: memf = MEMF_FAST; break;
case 3: /* extended: read additional longword for MEMF_ flags */
}
APTR seg_mem = AllocMem(size_longs * 4 + sizeof(BPTR), memf | MEMF_CLEAR);
```
Each allocation is **4 bytes larger** than the hunk data to hold the BPTR link to the next segment.
---
## Phase 3: Loading Hunk Data
For each hunk, the loader reads hunks sequentially from the file:
```
while not HUNK_END:
switch (hunk_type):
HUNK_CODE, HUNK_DATA:
read num_longs × 4 bytes into segment memory
HUNK_BSS:
already zero-filled by AllocMem(MEMF_CLEAR)
HUNK_RELOC32:
store for Phase 4 (apply after all hunks loaded)
HUNK_SYMBOL, HUNK_DEBUG:
read and discard (or pass to debugger hook)
HUNK_END:
advance to next hunk
```
---
## Phase 4: Relocation Pass
After all hunks are loaded and their base addresses are known:
```
for each HUNK_RELOC32 in hunk H:
for each (target_hunk, offsets[]):
base = segment_base[target_hunk]
for each offset:
patch = (ULONG *)(segment_base[H] + offset)
*patch += base /* add actual load address */
```
This two-pass approach (load all, then relocate) is required because `HUNK_RELOC32` entries may reference any hunk, including ones not yet loaded when the reloc entry is encountered.
---
## Phase 5: Segment List Construction
The segments are chained as a **BPTR list**:
```
Segment 0 memory:
[BPTR → seg1] (4 bytes)
[code data...]
Segment 1 memory:
[BPTR → 0] NULL = end of list
[data...]
```
`LoadSeg()` returns `MKBADDR(seg0_mem)` — a BPTR to the first segment (the memory address right-shifted by 2, as required by the BCPL pointer convention).
Converting BPTR to real address:
```c
APTR addr = BADDR(seglist); /* = seglist << 2 */
```
---
## Phase 6: Process Creation
`CreateNewProc()` (or the old `CreateProc()`) takes the segment list and creates a new AmigaOS process:
```c
struct Process *proc = CreateNewProcTags(
NP_Seglist, seglist,
NP_Name, "MyProgram",
NP_StackSize, 8192,
NP_Priority, 0,
NP_CommandName, cmd_string,
TAG_DONE);
```
Internally this calls `exec.library MakeNode()` and initialises:
- `Process->pr_SegList` — the segment list BPTR
- Stack: allocated via `AllocMem(NP_StackSize, MEMF_PUBLIC)`, stored in `pr_Stack`
- `tc_SPLower` / `tc_SPUpper` — stack bounds
- `tc_SPReg` — initial stack pointer (top of stack)
- `pr_GlobVec` — global vector (BCPL compat, not used by C programs)
- `pr_CLI` — CLI structure if launched from Shell
---
## Phase 7: Entry Point
The process starts executing at the **first word of hunk 0** (the first loaded segment). This is not `main()` — it is the startup code (`_start` / `c.o`):
```asm
; c.o (SAS/C startup):
_start:
MOVE.L 4.W, A6 ; SysBase
MOVE.L A0, _CommandStr ; raw command line from dos.library
BSR __main ; C runtime init
...
MOVE.L _ExitCode, D0 ; return value
RTS
```
---
## CLI vs Workbench Launch
| Parameter | CLI Launch | WBStartup |
|---|---|---|
| `pr_CLI` | Non-NULL, points to CLI struct | NULL |
| `pr_WBenchMsg` | NULL | Pointer to WBStartup message |
| A0 at entry | Command string pointer | NULL |
| A1 at entry | NULL | Pointer to WBStartup message |
| Return | `dos.library` handles exit | Must `Forbid(); ReplyMsg(wb_msg)` |
Startup code detects the launch type:
```c
if (pr->pr_CLI) {
/* CLI launch: use command string */
} else {
/* WB launch: wait for and reply to WBenchMsg */
WaitPort(&pr->pr_MsgPort);
WBMsg = (struct WBStartup *)GetMsg(&pr->pr_MsgPort);
}
```
---
## State Machine Diagram
```mermaid
stateDiagram-v2
[*] --> ParseHeader : LoadSeg(name)
ParseHeader --> AllocHunks : HUNK_HEADER valid
AllocHunks --> LoadData : AllocMem() per hunk
LoadData --> Relocate : all hunks read
Relocate --> BuildSegList : all patches applied
BuildSegList --> CreateProc : BPTR chain complete
CreateProc --> Running : AddTask() + Dispatch
Running --> [*] : process exits, UnLoadSeg()
```
---
## UnLoadSeg — Freeing Memory
```c
UnLoadSeg(seglist);
/* Walks the BPTR chain, FreeMem() each segment block */
```
The 4-byte BPTR header in each segment block records the size (stored by the loader before the data):
```
seg_mem - 4 : size of this allocation in bytes
seg_mem + 0 : BPTR to next segment
seg_mem + 4 : hunk data
```
---
## Hunk Types Reference
Complete enumeration of all hunk type codes as defined in `NDK39: dos/doshunks.h`. Types are listed in numeric order. The **Context** column indicates where each hunk may legally appear: **Exec** = loadable executable, **Obj** = relocatable object file (HUNK_UNIT stream), **Both** = either.
| Hex | Dec | Name | Context | Purpose | Typical Content | Notes / Limits |
|---|---|---|---|---|---|---|
| `$3E7` | 999 | `HUNK_UNIT` | Obj | Marks the start of a relocatable object file unit | 1 longword = name length in longs, then the unit name string (padded to longword boundary) | Must be the first record in every `.o` file. Not present in final executables. |
| `$3E8` | 1000 | `HUNK_NAME` | Obj | Names a section within a HUNK_UNIT | 1 longword = name length, then the section name string | Optional; precedes the code/data/BSS hunk it names. Linker uses for diagnostics. |
| `$3E9` | 1001 | `HUNK_CODE` | Both | Machine-code section | 1 longword = size in 32-bit longs; then *size×4* bytes of 68k opcodes | Loaded into RAM. Bits 3029 of the type word select memory type (see HUNKF_* flags). Max size: ~1 GB (29-bit field = 512 M longs). |
| `$3EA` | 1002 | `HUNK_DATA` | Both | Initialized read/write data section | 1 longword = size in longs; then *size×4* bytes of raw data | Same memory-type flags as HUNK_CODE. Pointer tables here require HUNK_RELOC32 fixups. |
| `$3EB` | 1003 | `HUNK_BSS` | Both | Uninitialized (zero-filled) data section | 1 longword = size in longs | No data follows — the loader's `AllocMem(MEMF_CLEAR)` zeroes the block. Bitmap/audio buffers use this. |
| `$3EC` | 1004 | `HUNK_RELOC32` / `HUNK_ABSRELOC32` | Both | Absolute 32-bit address fixup table | Pairs of `(num_offsets, target_hunk)` followed by *num_offsets* longword byte-offsets; terminated by `num_offsets=0` | Each patch site: `*(ULONG*)(hunk_base+offset) += target_base`. Offsets must be longword-aligned. Unlimited entries. Most common reloc type. |
| `$3ED` | 1005 | `HUNK_RELOC16` / `HUNK_RELRELOC16` | Obj | PC-relative or absolute 16-bit fixup | Same structure as HUNK_RELOC32 but patches a UWORD | Rarely generated. The 68k only supports 16-bit displacements in `Bcc`/`BSR`; linkers prefer PC-relative code instead. |
| `$3EE` | 1006 | `HUNK_RELOC8` / `HUNK_RELRELOC8` | Obj | 8-bit fixup | Same structure, patches a UBYTE | Extremely rare. Only useful for short-branch offsets inside a single hunk. |
| `$3EF` | 1007 | `HUNK_EXT` | Obj | External symbol table (imports + exports) | Sequence of `(type_namelen, name, value/refs)` entries; terminated by longword `$00000000` | **Not present in executables** — linker resolves all externals into HUNK_RELOC32 at link time. See EXT_DEF, EXT_REF32, EXT_COMMON sub-types in `hunk_ext_deep_dive.md`. |
| `$3F0` | 1008 | `HUNK_SYMBOL` | Both | Local (non-exported) symbol table for debugging | Pairs of `(name_len, name, value)`; terminated by `name_len=0` | Ignored by the OS loader; used only by debuggers (MonAm, wack, IDA). Strip with `slink NODBG` or `m68k-amigaos-strip --strip-debug`. No limit on entry count. |
| `$3F1` | 1009 | `HUNK_DEBUG` | Both | Arbitrary debugger data block | 1 longword = size in longs; then *size×4* bytes of opaque data | First longword is often a format tag: `$3D415053` = SAS/C stabs, `$3D474343` = GCC stabs. Ignored by loader. Can hold DWARF, stabs, or proprietary data. |
| `$3F2` | 1010 | `HUNK_END` | Both | Marks the end of one logical hunk | No data — bare type longword only | **Required** after every code/data/BSS + its reloc/symbol records. Loader advances to the next segment slot when this is seen. |
| `$3F3` | 1011 | `HUNK_HEADER` | Exec | Executable file header — magic number + segment size table | Resident-lib list (always 0), num_hunks, first_hunk, last_hunk, then one size longword per hunk | **Must be the very first record** in a loadable executable. Object files use HUNK_UNIT instead. Each size longword: bits 3130 = memory type, bits 290 = size in longs. |
| `$3F5` | 1013 | `HUNK_OVERLAY` | Exec | Overlay table — describes on-demand swap-in groups | 1 longword = overlay data size; then the overlay tree structure (node count, per-node hunk counts and sizes) | Follows the resident hunks in the file. Obsolete in modern Amiga software; replaced by `OpenLibrary()`. AmigaOS `InternalLoadSeg` supports it but it is rarely seen after 1990. |
| `$3F6` | 1014 | `HUNK_BREAK` | Exec | End-of-overlay-tree sentinel | No data | Immediately follows the overlay tree data. Required for `InternalLoadSeg` to know where the overlay definition ends. |
| `$3F7` | 1015 | `HUNK_DREL32` | Both | Compact 32-bit base-relative relocation (word-sized fields) | Pairs of `(num_offsets:WORD, target_hunk:WORD)` followed by *num_offsets* WORDs; terminated by WORD `0` | Used by BLink and some third-party linkers. More compact than HUNK_RELOC32 for small programs (all offsets fit in 16 bits, hunks < 64 KB). Supported by `InternalLoadSeg`. |
| `$3F8` | 1016 | `HUNK_DREL16` | Obj | Compact 16-bit base-relative relocation | Same word-field structure as HUNK_DREL32, patches UWORD | Very rare; primarily in object files from BLink-family toolchains. |
| `$3F9` | 1017 | `HUNK_DREL8` | Obj | Compact 8-bit base-relative relocation | Same word-field structure, patches UBYTE | Essentially unused in practice. |
| `$3FA` | 1018 | `HUNK_LIB` | Obj | Static library archive container | Sequence of embedded HUNK_UNIT blocks (each a full `.o`) preceded by their individual sizes | Output of `ar`-equivalent tools (`ar68k`, AmigaOS `join`). The linker extracts individual units from this container as needed. Not executed directly. |
| `$3FB` | 1019 | `HUNK_INDEX` | Obj | Symbol index for a HUNK_LIB archive | String table + per-unit symbol-name / hunk-number mappings | Allows the linker to locate a specific symbol without scanning all units. Immediately follows HUNK_LIB. |
| `$3FC` | 1020 | `HUNK_RELOC32SHORT` | Both | Compact 32-bit absolute relocation (word-sized offsets) | Same compact word-field structure as HUNK_DREL32 but semantically identical to HUNK_RELOC32 | Added in AmigaOS 3.x era. Reduces reloc-table file size when all patch offsets fit in 16 bits. Some linkers (e.g., vasm/vlink) emit this by default. |
| `$3FD` | 1021 | `HUNK_RELRELOC32` | Both | PC-relative 32-bit relocation | Same longword-field structure as HUNK_RELOC32; patches a 32-bit displacement rather than an absolute address | Used by GCC `-fPIC` output and shared-library position-independent code. Patch: `*(LONG*)(base+offset) += target_base (base+offset+4)`. |
| `$3FE` | 1022 | `HUNK_ABSRELOC16` | Both | Absolute 16-bit relocation | Same longword-field structure as HUNK_RELOC32, patches a UWORD with the lower 16 bits of the target address | Required when code uses `MOVE.W #abs_addr, Dn` with a truncated 16-bit address constant. Rare in well-structured programs. |
### Memory-Type Flags (Bits 3029 of Hunk Type Word)
These flags may be ORed into the type longword of `HUNK_CODE`, `HUNK_DATA`, and `HUNK_BSS` to control memory placement:
| Bit 30 | Bit 29 | Meaning | AllocMem flag |
|---|---|---|---|
| 0 | 0 | Any memory (default) | `MEMF_PUBLIC` |
| 1 | 0 | Chip RAM required | `MEMF_CHIP` |
| 0 | 1 | Fast RAM preferred | `MEMF_FAST` |
| 1 | 1 | Extended — next longword specifies full `MEMF_*` flags | See note |
Example: `HUNK_CODE` forced into Chip RAM = `$C00003E9` (`HUNK_CODE | HUNKF_CHIP`).
### HUNKB_ADVISORY (Bit 29 of the *type word itself*)
When **bit 29** is set in an otherwise-unknown hunk type, AmigaOS `InternalLoadSeg` treats it like `HUNK_DEBUG` (reads and discards the block) instead of failing with an error. This allows future hunk types to be added without breaking older loaders.
---
## References
- NDK39: `dos/dos.h`, `dos/dosextens.h` — Process, CLI structures
- ADCD 2.1 Autodocs: `LoadSeg`, `InternalLoadSeg`, `CreateNewProc`
- http://amigadev.elowar.com/read/ADCD_2.1/Libraries_Manual_guide/node0150.html
- *Amiga ROM Kernel Reference Manual: Libraries* — AmigaDOS chapter

View file

@ -0,0 +1,163 @@
[← Home](../README.md) · [Loader & HUNK Format](README.md)
# HUNK Debug Information
## Overview
Two optional hunk types carry debug information in AmigaOS executables:
- **HUNK_SYMBOL** ($3F0) — a simple name→offset symbol table
- **HUNK_DEBUG** ($3F1) — arbitrary debug data (most commonly stabs records)
Both are ignored by the loader and only used by debuggers.
---
## HUNK_SYMBOL
The simplest debug hunk. Contains a list of (name, offset) pairs for the current hunk:
```
HUNK_SYMBOL ($000003F0)
[Repeat:]
<name_len> Length of name in longwords (0 terminates)
<name...> Symbol name padded to longword boundary
<value> Symbol value (offset within current hunk)
<0> Terminator: name_len = 0
HUNK_END
```
### Example
Two symbols in a code hunk:
```
$000003F0 HUNK_SYMBOL
$00000001 name = 1 long (4 chars)
"_foo" symbol name
$00000000 at offset 0
$00000002 name = 2 longs (8 chars)
"_bar\0\0\0\0"
$00000040 at offset $40
$00000000 terminator
```
### Use in Debuggers
MonAm, wack, and IDA Pro all parse `HUNK_SYMBOL` to provide named labels in the disassembly. IDA's Amiga loader maps these directly to function/data names.
---
## HUNK_DEBUG
`HUNK_DEBUG` carries arbitrary debug data. The most common format used by AmigaOS compilers is **stabs** records (as produced by SAS/C 6.x and GCC).
```
HUNK_DEBUG ($000003F1)
<size_in_longs> Total size of the debug data in longwords
<data...> Compiler-specific debug data
HUNK_END
```
### SAS/C Stabs Format
SAS/C 6.x emits stabs-format debug info. The first longword in the debug data is a tag identifying the format:
```
$3D415053 Tag = "=APS" — SAS/C stabs
```
Following the tag: standard BSD/UNIX stabs records:
```c
struct stab_entry {
ULONG n_strx; /* offset into string table */
UBYTE n_type; /* stab type code */
UBYTE n_other;
UWORD n_desc; /* line number or misc */
ULONG n_value; /* symbol value */
};
```
**Common stab type codes:**
| Code | Name | Meaning |
|---|---|---|
| $24 | `N_FUN` | Function start |
| $44 | `N_SLINE` | Source line number |
| $64 | `N_SO` | Source file name |
| $84 | `N_LSYM` | Local symbol / type |
| $A0 | `N_GSYM` | Global symbol |
| $C0 | `N_RSYM` | Register variable |
### GCC Stabs Format
GCC (`m68k-amigaos-gcc`) emits similar stabs, usually with tag `$3D474343` ("=GCC") or no tag at all.
### Line Number Information
Stabs records with `N_SLINE` provide source-to-address mapping, enabling source-level debugging in tools like wack:
```
N_SLINE: n_desc = source_line_number
n_value = offset in current code hunk
```
---
## Reading Debug Info in IDA Pro
IDA Pro's Amiga HUNK loader (standard IDA plugin) parses:
- `HUNK_SYMBOL` → applies as function/data names automatically
- `HUNK_DEBUG` → partially parsed; stab `N_FUN` entries become function names
To see IDA's parsed symbols after loading:
- `View → Open Subviews → Names` — all named locations including HUNK_SYMBOL entries
- `View → Open Subviews → Segments` — hunk-to-segment mapping
---
## Stripping Debug Info
To produce a smaller executable without debug info:
**SAS/C:**
```
slink lib/c.o + myobj.o TO myexe NODBG
```
**GCC:**
```
m68k-amigaos-strip --strip-debug myexe
```
This removes HUNK_SYMBOL and HUNK_DEBUG records, reducing file size.
---
## Worked Hex Example (HUNK_SYMBOL)
Binary fragment from a real executable:
```
Offset Hex Bytes Decoded
$1A00: 00 00 03 F0 HUNK_SYMBOL
$1A04: 00 00 00 01 name_len = 1 (4 chars)
$1A08: 5F 6D 61 69 "_mai"
$1A0C: 6E 00 00 00 "n\0\0\0"
$1A10: 00 00 00 00 value = 0 (entry at start of code hunk)
$1A14: 00 00 00 02 name_len = 2 (8 chars)
$1A18: 5F 70 72 6F "_pro"
$1A1C: 63 65 73 73 "cess"
$1A20: 00 00 00 78 value = $78
$1A24: 00 00 00 00 terminator
$1A28: 00 00 03 F2 HUNK_END
```
---
## References
- NDK39: `dos/doshunks.h` — HUNK_SYMBOL, HUNK_DEBUG constants
- SAS/C 6.x Programmer's Guide — debug output format
- GCC internals — stabs format documentation
- IDA Pro Amiga loader source (community) — stabs parsing

View file

@ -0,0 +1,164 @@
[← Home](../README.md) · [Loader & HUNK Format](README.md)
# HUNK_EXT — Exports and Imports
## Overview
`HUNK_EXT` ($000003EF) is the external symbol record used in **object files** (HUNK_UNIT format). It carries both **exported symbols** (definitions visible to the linker) and **imported symbols** (references to symbols defined in other object files or libraries).
`HUNK_EXT` does **not** appear in final executables — the linker resolves all externals during the link step and emits `HUNK_RELOC32` instead.
---
## Record Format
```
HUNK_EXT ($000003EF)
[Repeat until terminator:]
<type_and_namelen> Longword: bits[31:24] = EXT type, bits[23:0] = name length in longs
<name...> Symbol name, padded to longword boundary
[type-specific data]
<0x00000000> Terminator (name length = 0)
HUNK_END
```
---
## EXT Type Codes
| Value | Name | Direction | Description |
|---|---|---|---|
| 0 | `EXT_SYMB` | export | Absolute symbol (no relocation) |
| 1 | `EXT_DEF` | export | Defined symbol at offset in current hunk |
| 2 | `EXT_ABS` | export | Absolute value symbol |
| 3 | `EXT_RES` | export | Resident library symbol |
| 129 | `EXT_REF32` | import | 32-bit reference (absolute) |
| 130 | `EXT_COMMON` | import/def | Common BSS block |
| 131 | `EXT_REF16` | import | 16-bit reference |
| 132 | `EXT_REF8` | import | 8-bit reference |
| 133 | `EXT_DEXT32` | import | 32-bit data-relative reference |
| 134 | `EXT_DEXT16` | import | 16-bit data-relative reference |
| 135 | `EXT_DEXT8` | import | 8-bit data-relative reference |
---
## Export Records
### EXT_DEF — Defined Symbol
The most common export — a function or data label at a fixed offset within the current hunk:
```
bits[31:24] = 0x01 EXT_DEF
bits[23:0] = name_longs name length in longwords
<name...> symbol name (padded)
<offset> offset within current hunk (longword)
```
Example: exporting `_init` at offset 0x10 in the code hunk:
```
$01000005 ; EXT_DEF, name = 5 longs (20 chars)
"_ini"
"t "
" " ; (padded to 5 longs)
$00000010 ; offset $10
```
### EXT_ABS — Absolute Symbol
Symbol has an absolute value (not relative to any hunk):
```
bits[31:24] = 0x02
<name...>
<absolute_value>
```
---
## Import Records
### EXT_REF32 — 32-bit Reference
Used when the current object references an external symbol. The linker patches these after symbol resolution.
```
bits[31:24] = 0x81 EXT_REF32
bits[23:0] = name_longs
<name...> symbol name being referenced
<num_refs> number of reference sites in current hunk
<ref_offset_0> byte offset within current hunk
<ref_offset_1>
...
```
Example: `_start` calls `_DOSBase` (external):
```
$81000004 ; EXT_REF32, name = 4 longs
"_DOS"
"Base"
$00000003 ; 3 reference sites
$0000001C ; offset $1C
$00000034 ; offset $34
$00000048 ; offset $48
```
### EXT_COMMON — Common Block (BSS)
Uninitialized data shared across multiple object files (like C `extern int x;`). The linker allocates one block, all references point to it:
```
bits[31:24] = 0x82 EXT_COMMON
<name...>
<size> size in bytes (the common block size)
<num_refs> reference sites
<offsets...>
```
---
## Worked Binary Example
Object file exporting `_foo` (at offset 0) and importing `_puts`:
```
Offset Hex Meaning
$00: 00 00 03 E7 HUNK_UNIT
$04: 00 00 00 01 name length = 1 long
$08: "foo\0" unit name = "foo"
$0C: 00 00 03 E9 HUNK_CODE
$10: 00 00 00 08 8 longwords = 32 bytes of code
$14: [32 bytes of code...]
$34: 00 00 03 EF HUNK_EXT
$38: 01 00 00 01 EXT_DEF, name = 1 long
$3C: "_foo" symbol name "_foo"
$40: 00 00 00 00 at offset 0 in code hunk
$44: 81 00 00 01 EXT_REF32, name = 1 long
$48: "_put" (truncated)
$4C: "s\0\0\0"
$50: 00 00 00 01 1 reference
$54: 00 00 00 08 at offset $08 in code
$58: 00 00 00 00 HUNK_EXT terminator
$5C: 00 00 03 F2 HUNK_END
```
---
## Linker Resolution
When the linker processes multiple object files:
1. Build a **global symbol table** from all `EXT_DEF` records
2. For each `EXT_REF32`, find the defining object and record the target hunk + offset
3. Emit `HUNK_RELOC32` in the output executable to patch the reference sites at load time
4. `EXT_COMMON` blocks are allocated once; all references redirected to that allocation
---
## References
- NDK39: `dos/doshunks.h` — EXT_ type constants
- vlink documentation: http://sun.hasenbraten.de/vlink/release/vlink.pdf
- ADE (Amiga Developer Environment) linker source code
- SAS/C 6.x reference manual — object file format appendix

View file

@ -0,0 +1,536 @@
[← Home](../README.md) · [Loader & HUNK Format](README.md)
# HUNK Binary Format — Complete Specification
## Overview
The **HUNK** format is the binary container format used throughout AmigaOS. It is **not** a single file type — it covers two very different kinds of file that happen to share the same record structure:
| File kind | Extension | First longword | Can be executed? |
|---|---|---|---|
| **Executable** — program, shared library, device driver | *(none)*, `.library`, `.device` | `$000003F3` (`HUNK_HEADER`) | ✅ Yes — loaded directly by `dos.library LoadSeg()` |
| **Object file** — compiler/assembler output, needs linking | `.o` | `$000003E7` (`HUNK_UNIT`) | ❌ No — must be linked first to produce an executable |
| **Static library archive** — collection of object files | `.lib` | `$000003FA` (`HUNK_LIB`) | ❌ No — linker input only |
An object file (`.o`) is **intermediate output** from a compiler. It contains relocatable code and unresolved external references. A linker (`slink`, `vlink`) combines one or more `.o` files with library archives into a final executable.
The format is a linear stream of **hunk records**, each identified by a 32-bit type word followed by type-specific data.
---
## Magic Number — All Valid First Longword Values
Tools and the OS identify a HUNK file by reading its **first 32-bit longword**. There are exactly three valid opening values:
| First longword | Hex | Dec | Constant | File type | Who reads it |
|---|---|---|---|---|---|
| `$000003F3` | `0x3F3` | 1011 | `HUNK_HEADER` | **Loadable executable** — program, `.library`, `.device` | `dos.library InternalLoadSeg()` |
| `$000003E7` | `0x3E7` | 999 | `HUNK_UNIT` | **Relocatable object file** (`.o`) — compiler/assembler output | Linker (`slink`, `vlink`) |
| `$000003FA` | `0x3FA` | 1018 | `HUNK_LIB` | **Static library archive** (`.lib`) — collection of `.o` files | Linker only |
Any other first longword means the file is **not a valid HUNK file**. `InternalLoadSeg` will return an error.
> [!NOTE]
> Only `HUNK_HEADER` files can be passed to `LoadSeg()`. Passing a `.o` object file or a `.lib` archive to `LoadSeg()` will fail — those are consumed exclusively by the linker at build time, never at runtime.
### What `$000003F3` means exactly
The value `$000003F3` = decimal 1011 = the constant `HUNK_HEADER`. Nothing about this value is arbitrary — it is the hunk type code for the header record, used as the magic number because the header is always the first hunk in an executable.
### What `$000003E7` means exactly
The value `$000003E7` = decimal 999 = `HUNK_UNIT`. This marks the start of one relocatable compilation unit. A `.o` file may contain multiple `HUNK_UNIT` records, one per independently-compiled module (though most compilers emit exactly one per file).
### Checking the magic yourself
```bash
# Check file type from the command line:
python3 -c "
import struct, sys
data = open(sys.argv[1], 'rb').read(4)
tag = struct.unpack('>I', data)[0]
names = {0x3F3:'HUNK_HEADER (executable)', 0x3E7:'HUNK_UNIT (object file)', 0x3FA:'HUNK_LIB (library archive)'}
print(f'{sys.argv[1]}: {names.get(tag, f\"UNKNOWN ({tag:#010x})\")}')
" mybinary
```
---
## Hunk Type Codes
> Source header: **`dos/doshunks.h`** (NDK 3.9). Every hunk record starts with one of these 32-bit tag values. The file is a linear stream — the loader reads tag → payload → next tag, until the file ends.
---
### Terminology
| Term | Meaning |
|---|---|
| **longword** | 32-bit (4-byte) value — the native word size of the 68000 |
| **BPTR** | BCPL pointer — byte address **right-shifted by 2** (always longword-aligned). Dereference: `real_addr = bptr_value << 2` |
| **ULONG** | Unsigned 32-bit integer |
| **UBYTE** | Unsigned 8-bit byte |
| **size in longs** | Content length as a count of 4-byte longwords. Bytes = longs × 4 |
| **Exec** | Appears in loadable executables only (starts with `HUNK_HEADER`) |
| **Obj** | Appears in relocatable object files only (starts with `HUNK_UNIT`) |
| **Both** | Valid in either context |
---
### Group 1 — Object File Framing
> These two tags appear **only in `.o` files**. Never in a final linked executable.
| Hex | Dec | Constant | Wire format | Description |
|---|---|---|---|---|
| `$3E7` | 999 | `HUNK_UNIT` | `[tag] [name_len_longs] [name_bytes…]` | **Start of a relocatable object unit.** Always the very first record in a `.o` file — the object-file equivalent of `HUNK_HEADER`. The name field names the compilation unit (e.g. `"main.o"`). A single `.o` file may contain multiple `HUNK_UNIT` records. |
| `$3E8` | 1000 | `HUNK_NAME` | `[tag] [name_len_longs] [name_bytes…]` | **Section name label.** Optional; assigns a human-readable name to the following section. The linker uses it for map files and diagnostics. |
---
### Group 2 — Content Sections
> Carry actual program data. Valid in **both** executables and object files. The type longword may have `HUNKF_CHIP` / `HUNKF_FAST` ORed into its upper bits — see [Memory Placement Flags](#memory-placement-flags).
| Hex | Dec | Constant | Payload | Description |
|---|---|---|---|---|
| `$3E9` | 1001 | `HUNK_CODE` | `[tag] [size_longs] [code_bytes × size×4]` | **Machine-code section.** The loader allocates RAM, copies the bytes, then applies any `HUNK_RELOC32` that follows. Holds 68k instructions — never data. |
| `$3EA` | 1002 | `HUNK_DATA` | `[tag] [size_longs] [data_bytes × size×4]` | **Initialized read/write data.** Global variables with non-zero values, string literals, jump tables, etc. Any embedded pointers to other hunks require `HUNK_RELOC32` fixups. |
| `$3EB` | 1003 | `HUNK_BSS` | `[tag] [size_longs]` *(no data bytes)* | **Uninitialized data (zero-fill).** Only the size is stored — no bytes in the file. The loader calls `AllocMem(..., MEMF_CLEAR)`. A 64 KB zero array costs 4 bytes on disk. |
---
### Group 3 — Relocation Records
> Tell the loader which longwords inside the current hunk need to be patched with the actual load address of another hunk. Without relocation, all cross-hunk pointers would point to wrong addresses after the OS places code at a non-zero address.
| Hex | Dec | Constant | Alias | Field width | Description |
|---|---|---|---|---|---|
| `$3EC` | 1004 | `HUNK_RELOC32` | `HUNK_ABSRELOC32` | LONG (32-bit) | **Absolute 32-bit fixup — the most common type.** Wire format: `[tag] { [count] [hunk_idx] [offset_0] … [offset_n] } … [0]`. Each offset points to a longword in the current hunk; `*(ULONG*)(base+offset) += target_hunk_base`. Terminated by `count=0`. |
| `$3ED` | 1005 | `HUNK_RELOC16` | `HUNK_RELRELOC16` | LONG (32-bit) | **16-bit absolute fixup.** Same format as above but patches a UWORD. Rare — 68k branch displacements are PC-relative and need no reloc. |
| `$3EE` | 1006 | `HUNK_RELOC8` | `HUNK_RELRELOC8` | LONG (32-bit) | **8-bit fixup.** Patches a UBYTE. Essentially unused — no 68k instruction has an 8-bit absolute address field. |
| `$3F7` | 1015 | `HUNK_DREL32` | — | WORD (16-bit) | **Compact 32-bit reloc.** Same semantics as `HUNK_RELOC32` but count, hunk index, and offsets are stored as 16-bit WORDs, halving the table size. Valid only when all hunk offsets fit in 16 bits (hunk < 64 KB). Generated by BLink. |
| `$3F8` | 1016 | `HUNK_DREL16` | — | WORD (16-bit) | Compact 16-bit reloc with WORD-sized fields. Very rare. |
| `$3F9` | 1017 | `HUNK_DREL8` | — | WORD (16-bit) | Compact 8-bit reloc with WORD-sized fields. Essentially unused. |
| `$3FC` | 1020 | `HUNK_RELOC32SHORT` | — | WORD (16-bit) | **Compact absolute 32-bit reloc with WORD offsets.** Semantically identical to `HUNK_RELOC32` with WORD fields. Default output of vasm/vlink when all offsets fit in 16 bits. Preferred over `HUNK_DREL32` in OS 3.x-era tools. |
| `$3FD` | 1021 | `HUNK_RELRELOC32` | — | LONG (32-bit) | **PC-relative 32-bit reloc.** Patch: `*(LONG*)(base+off) += target_base (base+off+4)`. Used by GCC `-fPIC` and PIC shared libraries. |
| `$3FE` | 1022 | `HUNK_ABSRELOC16` | — | LONG (32-bit) | **Absolute 16-bit fixup.** Patches a UWORD with the low 16 bits of the target's absolute address. Required for `MOVE.W #abs_addr,Dn` patterns. Rare. |
---
### Group 4 — External Symbol Table
> Object files only — **never present in a linked executable**.
| Hex | Dec | Constant | Description |
|---|---|---|---|
| `$3EF` | 1007 | `HUNK_EXT` | **Import + export symbol table for a compilation unit.** A single stream encodes both sides: *exports* declare symbols defined in this hunk (type `EXT_DEF`, `EXT_ABS`, `EXT_RES`); *imports* list unresolved references the linker must satisfy from other objects (type `EXT_REF32`, `EXT_REF16`, `EXT_REF8`, `EXT_COMMON`). The linker resolves all imports and emits `HUNK_RELOC32` records in the output executable. Wire format: `[tag] { [type_and_namelen] [name_bytes…] [value_or_refcount] [ref_offsets…] } … [0]`. See [`hunk_ext_deep_dive.md`](hunk_ext_deep_dive.md) for sub-type encoding. |
---
### Group 5 — Debug and Metadata
> **Completely ignored by the OS loader.** Strip with `slink NODBG` or `m68k-amigaos-strip --strip-debug` to reduce file size.
| Hex | Dec | Constant | Payload | Description |
|---|---|---|---|---|
| `$3F0` | 1008 | `HUNK_SYMBOL` | `[tag] { [namelen_longs] [name_bytes…] [value] } … [0]` | **Local symbol table.** Maps label names → offsets within this hunk. Consumed by MonAm, wack, IDA Pro. Terminated by `namelen=0`. |
| `$3F1` | 1009 | `HUNK_DEBUG` | `[tag] [size_longs] [format_tag] [data_bytes…]` | **Opaque debug block.** The leading `format_tag` longword identifies the format: `$3D415053` = SAS/C stabs; `$3D474343` = GCC stabs; `$3D574152` = Warp/Storm C. See [`hunk_debug_info.md`](hunk_debug_info.md). |
---
### Group 6 — Structural Records
| Hex | Dec | Constant | Payload | Description |
|---|---|---|---|---|
| `$3F2` | 1010 | `HUNK_END` | `[tag]` only — **no payload** | **Required end-of-hunk marker.** Every code/data/BSS hunk (and all reloc/symbol records that follow it) must close with `HUNK_END`. The loader advances to the next segment slot on reading it. |
| `$3F3` | 1011 | `HUNK_HEADER` | `[tag] [0] [num_hunks] [first_hunk] [last_hunk] [size_longs × n]` | **Executable magic number and segment size table.** Must be the very first longword in a loadable executable. The zero longword is the resident-library list (always 0 in practice). `num_hunks` = total hunks; `first_hunk`/`last_hunk` = inclusive range; followed by one size-in-longs per hunk. |
| `$3F5` | 1013 | `HUNK_OVERLAY` | `[tag] [size_longs] [overlay_table_data…]` | **Overlay descriptor table.** Follows the resident hunks; describes groups of code swapped in from disk on demand. Allows programs larger than available RAM. Obsolete — prefer `OpenLibrary()`. |
| `$3F6` | 1014 | `HUNK_BREAK` | `[tag]` only — **no payload** | **End of overlay tree sentinel.** `InternalLoadSeg` needs this to know where the overlay descriptor ends and the per-overlay hunk data begins. |
> [!NOTE]
> Value `$3F4` (decimal 1012) is **unused** — the numbering skips it intentionally.
---
### Group 7 — Static Library Archive
> Linker input only. Never loaded by `LoadSeg()` at runtime.
| Hex | Dec | Constant | Description |
|---|---|---|---|
| `$3FA` | 1018 | `HUNK_LIB` | **Static library archive container.** A sequence of embedded `HUNK_UNIT` object files, each preceded by its size in longwords. Produced by `ar68k` or the AmigaOS `join` command. The linker extracts only the units needed to resolve outstanding `HUNK_EXT` imports. |
| `$3FB` | 1019 | `HUNK_INDEX` | **Symbol index for `HUNK_LIB`.** A packed string table plus a per-unit map of exported symbol names → unit byte offsets. Lets the linker locate a function without scanning every object in the archive. Always immediately follows the `HUNK_LIB` it describes. |
### Memory Placement Flags
The type longword for these three hunks can encode a **memory placement request** in its upper bits. The loader passes the corresponding `MEMF_*` flags to `AllocMem`.
```
Bit layout of the type longword:
31 30 29 28 ............. 0
┌───┐ ┌────┐ ┌────┐ ┌─────────────────┐
│ 0 │ │CHIP│ │FAST│ │ Hunk type code │
└───┘ └────┘ └────┘ └─────────────────┘
```
| Bit | Constant | Value | Meaning |
|---|---|---|---|
| 30 | `HUNKF_CHIP` | `1L<<30` | Hunk **must** be in Chip RAM — required for anything the custom chips DMA from (bitmaps, audio, copper lists, sprites) |
| 29 | `HUNKF_FAST` | `1L<<29` | Hunk **prefers** Fast RAM — use for pure CPU data where DMA is not needed; avoids Chip RAM bus contention |
| 30+29 both set | *(extended)* | `0x60000000` | Next longword in the file contains full `MEMF_*` flags for `AllocMem` — allows any combination |
| neither | *(default)* | `0` | `MEMF_PUBLIC` — any available memory |
Additional helper constants:
| Constant | Value | Meaning |
|---|---|---|
| `HUNKB_CHIP` | `30` | Bit **number** (use with `bset`/`btst`) |
| `HUNKB_FAST` | `29` | Bit **number** |
**`MEMF_*` flags** used in extended mode (from `exec/memory.h`):
| Constant | Value | Meaning |
|---|---|---|
| `MEMF_ANY` | `0` | No preference — any accessible memory |
| `MEMF_PUBLIC` | `1<<0` | Must be accessible by all tasks and hardware |
| `MEMF_CHIP` | `1<<1` | Chip RAM — reachable by DMA controllers |
| `MEMF_FAST` | `1<<2` | Fast RAM — CPU-only, no chip DMA contention |
| `MEMF_CLEAR` | `1<<16` | Zero-fill on allocation |
| `MEMF_LARGEST` | `1<<17` | Return the single largest contiguous free block |
| `MEMF_REVERSE` | `1<<18` | Allocate from the top of the region (high addresses first) |
| `MEMF_TOTAL` | `1<<19` | `AvailMem`: report total installed rather than current free |
**Example:** force a code hunk into Chip RAM:
```
type longword = HUNK_CODE | HUNKF_CHIP
= 0x000003E9 | 0x40000000
= 0xC00003E9
```
**Why would code go in Chip RAM?** Rare, but needed on an A500 with no Fast RAM — everything including code must fit in the 512 KB Chip RAM.
---
### Quick Reference Table
| Hex | Dec | Constant | Alias | Context | Purpose |
|---|---|---|---|---|---|
| `$3E7` | 999 | `HUNK_UNIT` | — | Obj | Start of relocatable object unit |
| `$3E8` | 1000 | `HUNK_NAME` | — | Obj | Name label for the following section |
| `$3E9` | 1001 | `HUNK_CODE` | — | Both | Machine-code section |
| `$3EA` | 1002 | `HUNK_DATA` | — | Both | Initialized read/write data |
| `$3EB` | 1003 | `HUNK_BSS` | — | Both | Uninitialized data (size only, no bytes) |
| `$3EC` | 1004 | `HUNK_RELOC32` | `HUNK_ABSRELOC32` | Both | Absolute 32-bit address fixup list |
| `$3ED` | 1005 | `HUNK_RELOC16` | `HUNK_RELRELOC16` | Obj | 16-bit address fixup list |
| `$3EE` | 1006 | `HUNK_RELOC8` | `HUNK_RELRELOC8` | Obj | 8-bit fixup list |
| `$3EF` | 1007 | `HUNK_EXT` | — | Obj | Import + export symbol table |
| `$3F0` | 1008 | `HUNK_SYMBOL` | — | Both | Local debug symbol table |
| `$3F1` | 1009 | `HUNK_DEBUG` | — | Both | Opaque debug data (stabs / DWARF) |
| `$3F2` | 1010 | `HUNK_END` | — | Both | End-of-hunk marker — **required** |
| `$3F3` | 1011 | `HUNK_HEADER` | — | Exec | Executable magic number + size table |
| *(none)* | *1012* | *(unused)* | — | — | Gap in the numbering |
| `$3F5` | 1013 | `HUNK_OVERLAY` | — | Exec | Overlay group descriptor |
| `$3F6` | 1014 | `HUNK_BREAK` | — | Exec | End of overlay tree |
| `$3F7` | 1015 | `HUNK_DREL32` | — | Both | Compact 32-bit reloc (WORD-width fields) |
| `$3F8` | 1016 | `HUNK_DREL16` | — | Obj | Compact 16-bit reloc |
| `$3F9` | 1017 | `HUNK_DREL8` | — | Obj | Compact 8-bit reloc |
| `$3FA` | 1018 | `HUNK_LIB` | — | Obj | Static library archive |
| `$3FB` | 1019 | `HUNK_INDEX` | — | Obj | Symbol index for HUNK_LIB |
| `$3FC` | 1020 | `HUNK_RELOC32SHORT` | — | Both | Compact abs 32-bit reloc (WORD offsets) |
| `$3FD` | 1021 | `HUNK_RELRELOC32` | — | Both | PC-relative 32-bit reloc |
| `$3FE` | 1022 | `HUNK_ABSRELOC16` | — | Both | Absolute 16-bit address patch |
**Context key:** `Exec` = loadable executable · `Obj` = object file (HUNK_UNIT stream) · `Both` = either
```c
/* dos/doshunks.h — NDK 3.9 */
#define HUNK_UNIT 999 /* 0x3E7 — start of relocatable object (.o) */
#define HUNK_NAME 1000 /* 0x3E8 — name of this object unit/section */
#define HUNK_CODE 1001 /* 0x3E9 — machine code section */
#define HUNK_DATA 1002 /* 0x3EA — initialized data section */
#define HUNK_BSS 1003 /* 0x3EB — uninitialized data (zero-fill) */
#define HUNK_RELOC32 1004 /* 0x3EC — 32-bit absolute relocation table */
#define HUNK_ABSRELOC32 HUNK_RELOC32 /* alias */
#define HUNK_RELOC16 1005 /* 0x3ED — 16-bit relocation table */
#define HUNK_RELRELOC16 HUNK_RELOC16 /* alias */
#define HUNK_RELOC8 1006 /* 0x3EE — 8-bit relocation table */
#define HUNK_RELRELOC8 HUNK_RELOC8 /* alias */
#define HUNK_EXT 1007 /* 0x3EF — external symbol table (obj only) */
#define HUNK_SYMBOL 1008 /* 0x3F0 — local debug symbol table */
#define HUNK_DEBUG 1009 /* 0x3F1 — arbitrary debug data (stabs etc.) */
#define HUNK_END 1010 /* 0x3F2 — end-of-hunk marker */
#define HUNK_HEADER 1011 /* 0x3F3 — executable file header (magic) */
/* 0x3F4 — unused */
#define HUNK_OVERLAY 1013 /* 0x3F5 — overlay tree descriptor */
#define HUNK_BREAK 1014 /* 0x3F6 — end of overlay tree */
#define HUNK_DREL32 1015 /* 0x3F7 — compact 32-bit reloc (WORD fields) */
#define HUNK_DREL16 1016 /* 0x3F8 — compact 16-bit reloc */
#define HUNK_DREL8 1017 /* 0x3F9 — compact 8-bit reloc */
#define HUNK_LIB 1018 /* 0x3FA — static library archive */
#define HUNK_INDEX 1019 /* 0x3FB — symbol index for HUNK_LIB */
#define HUNK_RELOC32SHORT 1020 /* 0x3FC — compact 32-bit absolute reloc */
#define HUNK_RELRELOC32 1021 /* 0x3FD — PC-relative 32-bit reloc */
#define HUNK_ABSRELOC16 1022 /* 0x3FE — absolute 16-bit reloc */
/* Memory placement flags — OR'd into HUNK_CODE/DATA/BSS type longword */
#define HUNKB_FAST 29 /* bit number for Fast RAM flag */
#define HUNKB_CHIP 30 /* bit number for Chip RAM flag */
#define HUNKF_FAST (1L<<29) /* request Fast RAM for this hunk */
#define HUNKF_CHIP (1L<<30) /* request Chip RAM for this hunk */
```
### Terminology used in this document
- **longword** — a 32-bit (4-byte) value; the natural word size of the 68k
- **BPTR** — a BCPL pointer: the byte address right-shifted by 2 (always longword-aligned). Convert: `byte_addr = BPTR_value << 2`
- **ULONG** — unsigned 32-bit integer (`unsigned long` on the 68k)
- **UBYTE** — unsigned 8-bit byte
- **size in longs** — the hunk content length expressed as a count of 4-byte longwords (multiply by 4 to get bytes)
### Quick Reference Table
| Hex | Dec | Name | Alias | Context | Purpose |
|---|---|---|---|---|---|
| `$3E7` | 999 | `HUNK_UNIT` | — | Obj | Start of relocatable object unit |
| `$3E8` | 1000 | `HUNK_NAME` | — | Obj | Name string for this unit/section |
| `$3E9` | 1001 | `HUNK_CODE` | — | Both | Executable machine code section |
| `$3EA` | 1002 | `HUNK_DATA` | — | Both | Initialized read/write data section |
| `$3EB` | 1003 | `HUNK_BSS` | — | Both | Uninitialized data — size only, no bytes |
| `$3EC` | 1004 | `HUNK_RELOC32` | `HUNK_ABSRELOC32` | Both | Absolute 32-bit address patch list |
| `$3ED` | 1005 | `HUNK_RELOC16` | `HUNK_RELRELOC16` | Obj | 16-bit address patch list |
| `$3EE` | 1006 | `HUNK_RELOC8` | `HUNK_RELRELOC8` | Obj | 8-bit patch list |
| `$3EF` | 1007 | `HUNK_EXT` | — | Obj | Import + export symbol table |
| `$3F0` | 1008 | `HUNK_SYMBOL` | — | Both | Local debug symbol table |
| `$3F1` | 1009 | `HUNK_DEBUG` | — | Both | Opaque debug data (stabs, DWARF) |
| `$3F2` | 1010 | `HUNK_END` | — | Both | End of this hunk — required |
| `$3F3` | 1011 | `HUNK_HEADER` | — | Exec | Executable magic + size table |
| `$3F5` | 1013 | `HUNK_OVERLAY` | — | Exec | Overlay group descriptor |
| `$3F6` | 1014 | `HUNK_BREAK` | — | Exec | End of overlay tree |
| `$3F7` | 1015 | `HUNK_DREL32` | — | Both | Compact 32-bit reloc (WORD-sized fields) |
| `$3F8` | 1016 | `HUNK_DREL16` | — | Obj | Compact 16-bit reloc |
| `$3F9` | 1017 | `HUNK_DREL8` | — | Obj | Compact 8-bit reloc |
| `$3FA` | 1018 | `HUNK_LIB` | — | Obj | Static library archive (.lib) |
| `$3FB` | 1019 | `HUNK_INDEX` | — | Obj | Symbol index for HUNK_LIB |
| `$3FC` | 1020 | `HUNK_RELOC32SHORT` | — | Both | Compact abs 32-bit reloc (WORD offsets) |
| `$3FD` | 1021 | `HUNK_RELRELOC32` | — | Both | PC-relative 32-bit reloc |
| `$3FE` | 1022 | `HUNK_ABSRELOC16` | — | Both | Absolute 16-bit address patch |
**Context key:** `Exec` = loadable executable only · `Obj` = object file (HUNK_UNIT stream) only · `Both` = valid in either
### Memory Type Flags on HUNK_CODE / HUNK_DATA / HUNK_BSS
The type longword for code, data, and BSS hunks can carry memory placement hints in its upper two bits:
```
Bits 31..0 of the type longword:
bit 31: unused (always 0)
bit 30: HUNKF_CHIP — loader must use AllocMem(..., MEMF_CHIP)
bit 29: HUNKF_FAST — loader prefers AllocMem(..., MEMF_FAST)
bits 28..0: the hunk type constant (e.g. 0x3E9 for HUNK_CODE)
```
| Bit 30 | Bit 29 | Meaning | `AllocMem` flags used |
|---|---|---|---|
| 0 | 0 | Any memory (default) | `MEMF_PUBLIC` |
| 1 | 0 | Chip RAM required | `MEMF_CHIP` |
| 0 | 1 | Fast RAM preferred | `MEMF_FAST` |
| 1 | 1 | **Extended** — next longword holds full `MEMF_*` flags | caller reads extra longword |
**`MEMF_*` flag constants** (from `exec/memory.h`, used in extended mode):
```c
/* exec/memory.h — NDK39 */
#define MEMF_PUBLIC (1L<<0) /* any accessible memory */
#define MEMF_CHIP (1L<<1) /* Chip RAM (DMA-reachable by custom chips) */
#define MEMF_FAST (1L<<2) /* Fast RAM (CPU-only; faster than Chip) */
#define MEMF_VIRTUAL (1L<<3) /* not used on classic AmigaOS */
#define MEMF_CLEAR (1L<<16) /* zero-fill the allocation */
#define MEMF_LARGEST (1L<<17) /* return the single largest free block */
#define MEMF_REVERSE (1L<<18) /* allocate from top of list (high address) */
#define MEMF_TOTAL (1L<<19) /* AvailMem: return total, not largest */
#define MEMF_ANY 0L /* no preference — equivalent to MEMF_PUBLIC */
```
Example: `HUNK_CODE` forced into Chip RAM:
```
type longword = HUNK_CODE | HUNKF_CHIP
= 0x000003E9 | 0x40000000
= 0xC00003E9
```
---
## HUNK_HEADER — Executable Header
Appears at the very start of an executable file:
```
$000003F3 HUNK_HEADER magic
$00000000 Resident library list (always 0 for loadable executables)
<num_hunks> Number of hunks in the executable (longword)
<first_hunk> Index of first loadable hunk (usually 0)
<last_hunk> Index of last loadable hunk (= num_hunks - 1)
<size_0> Size of hunk 0 in longwords
<size_1> Size of hunk 1 in longwords
...
<size_n> Size of last hunk in longwords
```
**Size longword bit encoding:**
```
bits 31-30: Memory type (00=ANY, 10=CHIP, 01=FAST, 11=extended)
bits 29-0: Size in 32-bit longwords
```
**Example header (2 hunks: code + data):**
```
Offset Bytes Meaning
$00: 00 00 03 F3 HUNK_HEADER
$04: 00 00 00 00 no resident library list
$08: 00 00 00 02 2 hunks
$0C: 00 00 00 00 first hunk index = 0
$10: 00 00 00 01 last hunk index = 1
$14: 00 00 00 50 hunk 0: 0x50 longwords = 0x140 bytes (code)
$18: 00 00 00 10 hunk 1: 0x10 longwords = 0x40 bytes (data)
```
---
## HUNK_CODE / HUNK_DATA
```
<type> HUNK_CODE ($3E9) or HUNK_DATA ($3EA)
<num_longs> Size in longwords
<data...> Raw code or data bytes (num_longs × 4 bytes)
HUNK_END ($3F2) End of this hunk
```
---
## HUNK_BSS
```
<type> HUNK_BSS ($3EB)
<num_longs> Size in longwords (allocate this many bytes, zero-filled)
HUNK_END ($3F2) End of this hunk
```
No data follows — BSS is zero-initialised by the loader.
---
## HUNK_RELOC32
32-bit absolute relocation records. These patch addresses in the code/data that reference other hunks.
```
<HUNK_RELOC32> $000003EC
<num_offsets> Number of offsets to patch for the next target hunk
<target_hunk> Index of the hunk whose base address is added
<offset_0> Byte offset within current hunk to patch (longword)
<offset_1>
...
<num_offsets=0> Terminator — end of relocation table
HUNK_END ($3F2)
```
**Example:** Code hunk references data hunk. Two addresses need patching:
```
$000003EC HUNK_RELOC32
$00000002 2 offsets to patch
$00000001 target = hunk 1 (data hunk)
$00000010 patch at offset $10 in code hunk
$00000024 patch at offset $24 in code hunk
$00000000 end of reloc list
$000003F2 HUNK_END
```
At load time: `*(ULONG *)(code_base + 0x10) += data_base`
---
## HUNK_SYMBOL
Optional local symbol table for debugging:
```
<HUNK_SYMBOL> $000003F0
<name_len> String length in longwords (1N)
<name...> Symbol name (padded to longword boundary)
<value> Symbol value (offset within hunk)
...
<0> Zero name_len terminates
HUNK_END ($3F2)
```
---
## Complete Executable Structure (Annotated)
```
[File start]
HUNK_HEADER
num_hunks = 3 ; code, data, BSS
sizes: [0x200, 0x80, 0x100]
--- Hunk 0: Code ---
HUNK_CODE
0x200 bytes of machine code
HUNK_RELOC32
Patches in code referencing hunk 1 (data)
Patches in code referencing hunk 2 (BSS)
HUNK_SYMBOL (optional)
_main at offset 0
_foo at offset 0x40
HUNK_END
--- Hunk 1: Data ---
HUNK_DATA
0x80 bytes of initialized data
HUNK_RELOC32
Patches in data (e.g., pointer tables) referencing code hunk
HUNK_END
--- Hunk 2: BSS ---
HUNK_BSS
0x100 longwords = 1024 bytes (zero-filled at load time)
HUNK_END
[File end]
```
---
## File Format Diagram
```mermaid
block-beta
columns 1
header["HUNK_HEADER\n(sizes + memory types)"]
code["HUNK_CODE\n(machine code bytes)"]
reloc0["HUNK_RELOC32\n(patch list for code)"]
sym["HUNK_SYMBOL\n(optional debug names)"]
end0["HUNK_END"]
data["HUNK_DATA\n(initialized data)"]
reloc1["HUNK_RELOC32\n(patch list for data)"]
end1["HUNK_END"]
bss["HUNK_BSS\n(zero-fill size)"]
end2["HUNK_END"]
```
---
## References
- *Amiga ROM Kernel Reference Manual: Libraries* — AmigaDOS executable format chapter
- ADCD 2.1: `Libraries_Manual_guide/` — LoadSeg, InternalLoadSeg
- NDK39: `dos/doshunks.h` — hunk type constants
- http://amigadev.elowar.com/read/ADCD_2.1/Libraries_Manual_guide/node01E0.html
- Community reference: http://sun.hasenbraten.de/vlink/release/vlink.pdf (HUNK format appendix)

View file

@ -0,0 +1,160 @@
[← Home](../README.md) · [Loader & HUNK Format](README.md)
# HUNK Relocation Mechanics
## Overview
Relocation is the process of **patching absolute addresses** in a loaded executable to reflect its actual memory location. Since AmigaOS allocates memory dynamically, a program cannot know its load address at compile time — all inter-hunk references must be fixed up at runtime.
---
## Why Relocation Is Necessary
An Amiga executable contains references like:
```asm
LEA DataTable(PC), A0 ; PC-relative — no relocation needed
MOVE.L #DataTable, A0 ; Absolute — MUST be relocated
```
The linker places `DataTable` at some hunk-relative offset (e.g., offset 0 in the data hunk). The absolute address is only known at load time. The relocation table tells the loader which longwords in the code contain these absolute values.
---
## HUNK_RELOC32 Format
```
HUNK_RELOC32 ($000003EC)
[Repeat until terminator:]
<num_offsets> Number of longword addresses to patch for this target hunk
<target_hunk> Index of the hunk whose base address is added
<offset_0> Byte offset within the current hunk to patch
<offset_1>
...
<0> num_offsets = 0 terminates the reloc list
HUNK_END ($000003F2)
```
### Patching Algorithm
For each entry in HUNK_RELOC32 of hunk `H`:
```
foreach (target_hunk, offsets[]):
base = segment_base_address[target_hunk]
foreach offset in offsets:
*(ULONG *)(H_base + offset) += base
```
The value at `H_base + offset` already contains the **hunk-relative** address written by the linker. Adding the actual base produces the final absolute address.
### Example
Code hunk references data hunk at two sites:
```
Before load (raw file values):
code[0x18] = $00000000 ; linker placed "data offset 0" here
code[0x2C] = $00000010 ; linker placed "data offset 0x10" here
HUNK_RELOC32:
num_offsets = 2
target_hunk = 1 ; data hunk
offsets = [0x18, 0x2C]
After load (data hunk loaded at $20000):
code[0x18] = $00000000 + $20000 = $00020000
code[0x2C] = $00000010 + $20000 = $00020010
```
---
## HUNK_RELOC16 and HUNK_RELOC8
Same format as HUNK_RELOC32 but patch **16-bit** or **8-bit** values:
- `HUNK_RELOC16` ($3ED): patches UWORD at offset
- `HUNK_RELOC8` ($3EE): patches UBYTE at offset
These are rare in practice — the 68000 requires even-aligned word accesses and only supports 16-bit displacement in most addressing modes.
---
## HUNK_DREL32 — Short Relocation (32-bit)
`HUNK_DREL32` ($3F7) is an alternative relocation format used by some linkers (e.g., BLink) for smaller reloc tables:
```
HUNK_DREL32
[Repeat:]
<num_offsets> (WORD, not LONGWORD)
<target_hunk> (WORD)
<offset_0> (WORD)
...
<0> terminator
```
By using 16-bit values, this format is more compact for programs with many relocations and small hunk sizes (<64 KB). AmigaOS `InternalLoadSeg` supports both formats.
---
## PC-Relative References (No Relocation Needed)
The 68020+ supports **PC-relative addressing** with 32-bit displacements:
```asm
LEA symbol(PC), A0 ; PC-relative load effective address
MOVE.L data(PC), D0 ; PC-relative data read
```
PC-relative references do not require relocation — the offset is relative to the instruction, so it is valid regardless of where the code is loaded. **GCC for 68k** generates PC-relative code by default (`-fpic`), significantly reducing the size of relocation tables.
SAS/C generates absolute references by default and relies heavily on `HUNK_RELOC32`.
---
## Relocation at Runtime — Segment Chain
The loader tracks loaded segments as a **BPTR chain** (singly-linked list). The segment list head is returned by `LoadSeg()`:
```
Segment 0 (code):
BPTR → Segment 1
[code data]
Segment 1 (data):
BPTR → 0 (NULL)
[data]
```
Each segment begins with a 4-byte BPTR to the next segment. Hunk index `n` corresponds to segment `n` in this chain.
---
## Viewing Relocations with Tools
### IDA Pro
After loading a HUNK file with the Amiga plugin, IDA resolves relocations automatically. The fixup table is visible in `View → Open Subviews → Fixups`.
### hexdump + manual
Locate HUNK_RELOC32 ($3EC) in raw hex:
```bash
xxd mybinary | grep "0003 ec"
```
Then read num_offsets and target_hunk longwords that follow.
### hunkinfo (custom tool)
```bash
hunkinfo mybinary # shows all hunks, sizes, reloc counts
```
---
## References
- NDK39: `dos/doshunks.h`
- *Amiga ROM Kernel Reference Manual: Libraries* — AmigaDOS chapter, `InternalLoadSeg`
- vlink linker documentation — relocation section
- http://amigadev.elowar.com/read/ADCD_2.1/Libraries_Manual_guide/node01E0.html

View file

@ -0,0 +1,167 @@
[← Home](../README.md) · [Loader & HUNK Format](README.md)
# Object File Format (HUNK_UNIT)
## Overview
Compiler output (`.o` files) uses `HUNK_UNIT` format — a variation of the HUNK executable format for **relocatable, unlinked code**. Multiple object files are combined by the linker to produce a final executable.
---
## Object File vs Executable
| Feature | Object file | Executable |
|---|---|---|
| Magic word | `HUNK_UNIT` ($3E7) | `HUNK_HEADER` ($3F3) |
| Unit name | Yes (HUNK_NAME) | No |
| External refs | `HUNK_EXT` (imports+exports) | None (resolved by linker) |
| Relocation | `HUNK_RELOC32` (partial) | `HUNK_RELOC32` (complete) |
| BSS | `HUNK_BSS` | `HUNK_BSS` |
| Loaded directly | No | Yes |
---
## Object File Structure
```
HUNK_UNIT ($000003E7) — identifies this as an object file
HUNK_NAME ($000003E8) — optional: source/module name
<num_longs>
<name string>
--- For each code/data/bss section: ---
HUNK_CODE / HUNK_DATA / HUNK_BSS
<data>
HUNK_RELOC32 — intra-object relocations (if any)
HUNK_EXT — external symbol definitions + references
EXT_DEF _myfunc = offset X (export)
EXT_REF32 _printf [offsets] (import)
HUNK_SYMBOL — optional local symbols
HUNK_END
--- Repeat for additional sections ---
```
---
## Multi-Section Object Files
A single `.o` file can contain multiple code/data sections. Each section is a separate hunk terminated by `HUNK_END`:
```
HUNK_UNIT
HUNK_NAME "mymodule.c"
HUNK_CODE ; section 0: main code
[code...]
HUNK_EXT ; exports/imports for section 0
HUNK_END
HUNK_CODE ; section 1: __initdata (constructor table)
[init code...]
HUNK_END
HUNK_DATA ; section 2: initialized data
[data...]
HUNK_RELOC32 ; internal relocs for section 2
HUNK_END
HUNK_BSS ; section 3: uninitialized data
HUNK_END
```
---
## Compiler Section Naming Convention
Different compilers use different conventions for code/data section organization:
### SAS/C 6.x
| Section | Type | Contents |
|---|---|---|
| `.text` | HUNK_CODE | Compiled functions |
| `.data` | HUNK_DATA | Initialized globals |
| `.bss` | HUNK_BSS | Uninitialized globals |
| `__CSEG` | HUNK_CODE | (alternate) code |
| `__DSEG` | HUNK_DATA | (alternate) data |
SAS/C 6.x uses HUNK_NAME to embed section names (matching HUNK_NAME format).
### GCC (m68k-amigaos)
GCC emits more sections:
```
.text — code
.data — initialized data
.bss — BSS
.rodata — read-only data (string literals, const)
.ctors — C++ constructor table
.dtors — C++ destructor table
```
VBCC follows a similar scheme to GCC.
---
## Library Archives (.lib)
A `.lib` file is an **archive of object files**, using `HUNK_LIB` ($3FA) and `HUNK_INDEX` ($3FB):
```
HUNK_LIB ($000003FA)
<total_size> size of all following library data in longs
[HUNK_UNIT blocks for each member ...]
HUNK_INDEX ($000003FB)
<size_in_longs> size of index data
[index entries...]
```
The index maps symbol names to their containing `HUNK_UNIT` within the library, allowing the linker to extract only the needed object files.
Libraries shipped with SAS/C, GCC, and VBCC use this format.
---
## Linker Operation (Overview)
The linker (`slink`, `blink`, `ld`) processes object files:
1. **Collect all HUNK_EXT exports** from every `.o` into a global symbol table
2. **Resolve HUNK_EXT imports** — for each `EXT_REF32`, find the defining object
3. **Pull in library members** — if an imported symbol lives in a `.lib`, add that object file
4. **Merge sections** — combine all `.text` hunks into one code hunk, all `.data` into one data hunk, etc.
5. **Emit HUNK_RELOC32** — for each resolved external reference, emit a relocation entry
6. **Write HUNK_HEADER** — calculate final hunk sizes and write executable header
---
## Inspecting Object Files
### hexdump
```bash
xxd myfile.o | head -40 # look for $000003E7 (HUNK_UNIT) at start
```
### hunkinfo (community tool)
```bash
hunkinfo myfile.o # lists all hunks, sizes, symbols, externals
```
### IDA Pro
IDA can load `.o` files directly using the Amiga HUNK loader plugin — useful for reversing library object files without full executable context.
---
## References
- NDK39: `dos/doshunks.h`
- SAS/C 6.x Programmer's Guide — object file chapter
- VBCC documentation — object file format
- vlink linker manual (covers HUNK_LIB/HUNK_INDEX): http://sun.hasenbraten.de/vlink/
- GCC m68k-amigaos port documentation

View file

@ -0,0 +1,83 @@
[← Home](../README.md) · [Loader & HUNK Format](README.md)
# HUNK_OVERLAY — Overlay System
## Overview
The **overlay system** allows programs larger than available RAM to run by dividing code into **segments loaded on demand**. Only one overlay section is present in memory at any time; others are swapped in from disk when needed.
This predates virtual memory and was commonly used in A500-era applications with limited Fast RAM.
---
## When Overlays Are Used
- Application code exceeds available RAM
- Rarely-used code paths (setup, error handling) should not occupy memory permanently
- The game/app has a fixed resident core and multiple interchangeable level/module overlays
---
## HUNK_OVERLAY Structure
```
HUNK_HEADER
(normal header for resident hunks)
[Normal hunks: code, data, BSS]
HUNK_OVERLAY ($000003F5)
<size_in_longs> total size of overlay table data
<overlay_tree...> tree of overlay nodes
HUNK_BREAK ($000003F6) marks end of overlay tree
```
### Overlay Tree Format
The overlay tree describes groups of overlays and their dependencies:
```
<num_overlay_nodes>
For each node:
<num_hunks> number of hunks in this overlay
<hunk_sizes...> size of each hunk in longwords
<hunk_memory_types...> memory requirements
```
The resident (non-overlay) hunks are hunk 0 through N. The overlay hunks are numbered starting at N+1.
---
## Runtime Overlay Support — overlaylibrary
AmigaOS provides `dos.library` support for overlays via `InternalLoadSeg` with an overlay table. The application calls `ObtainSemaphore()` + `OverlayLoad()` to swap overlays.
In practice, the overlay system is complex and rarely documented precisely. Most real Amiga applications avoid overlays in favour of:
- Dynamic library loading (`OpenLibrary`)
- Splitting functionality into separate executables run via `Execute`
- AmigaOS shared library mechanism
---
## Practical Alternative: Dynamic Linking
Modern Amiga development (and OS 3.1+ best practices) uses `OpenLibrary()` instead of overlays:
```c
struct MyLib *MyBase = (struct MyLib *)OpenLibrary("mycode.library", 0);
if (MyBase) {
MyBase->myFunction(arg1, arg2);
CloseLibrary((struct Library *)MyBase);
}
```
This is functionally equivalent to overlay loading but uses the OS resource tracking system and allows multiple users.
---
## References
- *Amiga ROM Kernel Reference Manual: Libraries* — AmigaDOS overlay section
- NDK39: `dos/doshunks.h` — HUNK_OVERLAY, HUNK_BREAK
- Paul Tuma's Amiga HUNK format notes (community)

View file

@ -0,0 +1,44 @@
[← Home](../README.md)
# Linking & Library Integration
## Overview
This section documents how AmigaOS shared libraries work at the binary level — how compilers produce library call stubs, how the linker wires them up, and how to reconstruct this mechanism during reverse engineering.
## Contents
| File | Topic |
|---|---|
| [library_structure.md](library_structure.md) | Library node, LVO table, OpenLibrary mechanics |
| [fd_files.md](fd_files.md) | Function Definition files — the library ABI source |
| [lvo_table.md](lvo_table.md) | JMP table layout and reconstruction |
| [compiler_stubs.md](compiler_stubs.md) | How SAS/C, GCC, VBCC call libraries |
| [setfunction.md](setfunction.md) | Runtime function patching with SetFunction |
| [startup_code.md](startup_code.md) | c.o / gcrt0.S — startup and exit sequences |
## The Library ABI Model
Every AmigaOS shared library exposes its functions through a **negative-offset JMP table** relative to the library base pointer:
```
Library base: LIB+0 → Library node (struct Library)
LIB-6 → JMP _funcN (last function)
LIB-12 → JMP _funcN-1
...
LIB-6 → JMP _func1 (first user function)
```
A C call like `OpenLibrary("graphics.library", 0)` compiles to:
```asm
MOVE.L 4.W, A6 ; A6 = SysBase (exec)
JSR -552(A6) ; LVO for OpenLibrary = -552
```
The negative offset (`-552`) is the **Library Vector Offset (LVO)** — a fixed ABI value defined in the library's `.fd` file and `proto/` include.
## References
- NDK39: `fd/` directory, `proto/` includes, `inline/` inline stubs
- ADCD 2.1: Library writing guide, ROM Kernel Manual
- http://amigadev.elowar.com/read/ADCD_2.1/Libraries_Manual_guide/node0000.html

View file

@ -0,0 +1,188 @@
[← Home](../README.md) · [Linking & Libraries](README.md)
# Compiler Library Call Stubs
## Overview
This document explains how each major AmigaOS compiler generates library calls and what the resulting machine code looks like — essential for recognising compiler signatures during reverse engineering.
---
## The AmigaOS Calling Convention
All library calls use the same register-based convention:
- **A6** = library base pointer
- Arguments in **A0A3**, **D0D7** as specified by the `.fd` file
- Return value in **D0** (or D0+D1 for 64-bit returns)
- **Callee** preserves: D2D7, A2A6
- **Caller** may destroy: D0, D1, A0, A1
The code generated by all compilers follows this — the difference is how they **set up A6** before the `JSR`.
---
## SAS/C 6.x
SAS/C uses **global library base variables** (`_DOSBase`, `_SysBase`, etc.) and generates direct `JSR LVO(A6)` calls. The standard pattern:
```asm
; Calling Write(file, buffer, length):
; D1=file, D2=buffer, D3=length, A6=_DOSBase
MOVE.L _DOSBase, A6 ; load library base from global
MOVE.L file, D1 ; arg 1
MOVE.L buffer, D2 ; arg 2
MOVE.L length, D3 ; arg 3
JSR -48(A6) ; Write LVO = -48
```
### SAS/C Inline Pragmas
SAS/C uses `#pragma libcall` to define inline stubs:
```c
#pragma libcall DOSBase Write 30 32103
/* 30 = bias/6 = 8th function (LVO -48)
32103 = register encoding: D3,D2,D1 → args 3,2,1 */
```
The pragma-generated stub wraps the JSR into a C-callable inline function.
### SAS/C Library Base Globals
SAS/C's `clib/dos_protos.h` + startup code declares:
```c
extern struct DosLibrary *DOSBase;
extern struct ExecBase *SysBase;
```
These are initialised in `c.o` (the startup stub):
```asm
_c_start:
MOVE.L 4.W, A6 ; SysBase from exception vector
MOVE.L A6, _SysBase ; store in global
LEA _dosname, A1 ; "dos.library"
MOVEQ #0, D0 ; version 0
JSR -552(A6) ; OpenLibrary
MOVE.L D0, _DOSBase
```
---
## GCC (m68k-amigaos / bebbo GCC)
GCC uses a different stub mechanism — **inline functions** from `inline/` headers or the `libnix` library:
```c
/* inline/exec.h (generated from exec_lib.fd) */
static __inline APTR
AllocMem(ULONG byteSize, ULONG requirements)
{
register APTR _res __asm("d0");
register struct ExecBase *const _SysBase __asm("a6") = SysBase;
register ULONG _byteSize __asm("d0") = byteSize;
register ULONG _requirements __asm("d1") = requirements;
__asm volatile ("jsr a6@(-198:W)"
: "=r"(_res)
: "r"(_SysBase), "r"(_byteSize), "r"(_requirements)
: "a0", "a1", "fp0", "fp1", "cc", "memory");
return _res;
}
```
The `jsr a6@(-198:W)` emits a 4-byte instruction: `JSR -198(A6)` using 16-bit word displacement.
### GCC Code Pattern
```asm
; GCC calling AllocMem(1024, MEMF_CHIP):
MOVEA.L _SysBase, A6 ; GCC uses same global SysBase
MOVEQ #$01, D1 ; MEMF_CHIP = 2... actually:
MOVE.L #$00000400, D0 ; byteSize = 1024
MOVE.L #$00000002, D1 ; MEMF_CHIP
JSR -$C6(A6) ; -198 decimal = AllocMem
MOVE.L D0, _mybuf ; save return value
```
### PC-Relative Addressing (GCC Default)
GCC with `-fpic` or `-fbaserel` generates **PC-relative** accesses:
```asm
LEA _SysBase(PC), A0 ; load SysBase address PC-relative
MOVEA.L (A0), A6 ; dereference to get SysBase value
JSR -$228(A6) ; OpenLibrary LVO = -552
```
This reduces relocation entries and is the default for GCC on AmigaOS.
---
## VBCC (vbcc m68k-amigaos)
VBCC uses `inline.h` style stubs similar to GCC. The calling pattern is identical at the assembly level:
```asm
; VBCC compiled library call — indistinguishable from GCC at asm level
MOVEA.L (_SysBase), A6
MOVE.L D2, -(SP) ; VBCC may push/pop D2 as scratch
MOVE.L arg, D0
JSR -$C6(A6)
MOVE.L (SP)+, D2
```
VBCC is slightly more aggressive about not clobbering D2-D7, matching the calling convention exactly.
---
## Recognising Compiler Signatures During RE
### SAS/C Signature
```asm
; Entry prologue:
LINK A5, #-N ; frame setup (A5 = frame pointer)
MOVEM.L D2-D7/A2-A4, -(SP) ; save callee-saved regs
; ...
MOVEM.L (SP)+, D2-D7/A2-A4 ; restore
UNLK A5 ; teardown
RTS
```
SAS/C uses `A5` as frame pointer and `LINK/UNLK` for stack frames.
### GCC Signature
```asm
; Entry prologue (GCC without frame pointer):
MOVEM.L D2/A2, -(SP) ; only saves what it uses
; ...
MOVEM.L (SP)+, D2/A2
RTS
```
GCC typically does **not** use LINK/UNLK unless required. It uses `A5` as an additional scratch register and often generates `JSR` through `A0` for indirect calls.
### VBCC Signature
VBCC generates very clean, minimal code — similar to GCC but with more consistent callee-save patterns and no GCC-specific idioms (e.g., no `__builtin_` calls).
---
## Library Call Pattern Summary
| Pattern | Compiler | Key Signature |
|---|---|---|
| `LINK A5, #-N` + `MOVEM D2-D7/A2-A4` | SAS/C | Frame pointer A5, full reglist |
| `JSR LVO(A6)` with global `_LibBase` | SAS/C / GCC | Both use global |
| `jsr a6@(-LVO:W)` (GAS syntax) | GCC inline | 16-bit short displacement |
| PC-relative `LEA _SysBase(PC)` | GCC -fpic | No absolute refs |
| No LINK, minimal MOVEM | GCC / VBCC | No frame pointer |
---
## References
- NDK39: `fd/`, `proto/`, `inline/`, `clib/` directories
- SAS/C 6.x Programmer's Guide — pragma libcall chapter
- GCC m68k-amigaos port documentation (bebbo/m68k-amigaos-toolchain)
- VBCC documentation — register calling convention
- *Amiga ROM Kernel Reference Manual: Libraries* — calling conventions appendix

View file

@ -0,0 +1,172 @@
[← Home](../README.md) · [Linking & Libraries](README.md)
# Function Definition (.fd) Files
## Overview
**Function Definition files** (`.fd`) are the authoritative source of truth for AmigaOS library ABIs. They define:
- The **name** of every library function
- The **LVO** (Library Vector Offset) — the negative offset used to call it
- The **register parameters** — which M68k registers carry each argument
- The **bias** (starting offset, always negative)
Every AmigaOS library call ultimately traces back to an `.fd` file.
---
## File Location
In the NDK39:
```
NDK39/fd/
dos_lib.fd
exec_lib.fd
graphics_lib.fd
intuition_lib.fd
icon_lib.fd
...
```
Also part of the AmigaOS developer CD (adjust to your NDK path):
```
NDK39/fd/
```
---
## .fd File Format
```
##base _DOSBase ; library base variable name
##bias 30 ; first LVO = -30 (4 mandatory: -6,-12,-18,-24)
##public
Open(name,accessMode)(D1,D2) ; LVO -30
Close(file)(D1) ; LVO -36
Read(file,buffer,length)(D1,D2,D3); LVO -42
Write(file,buffer,length)(D1,D2,D3); LVO -48
...
##private
_InternalOpen(...) ; private function, not in public ABI
```
### Grammar Rules
| Directive | Meaning |
|---|---|
| `##base _LibBase` | Name of the library base global variable |
| `##bias N` | Starting LVO magnitude; first public function is at `-N` |
| `##public` | Following functions are part of the public ABI |
| `##private` | Following functions are internal/private |
| `FuncName(args)(regs)` | Function with argument names and register assignments |
The LVO of function `n` (0-indexed from first public) is:
```
LVO = -(bias + n × 6)
```
---
## Register Notation
Register assignments are listed in order matching the argument list, using Amiga register names:
```
Open(name, accessMode)(D1, D2)
```
Means:
- `name` → D1
- `accessMode` → D2
- Caller must put library base in A6 (by convention)
Special register names:
- `A6` — always the library base (implicit, not listed)
- `A0``A3`, `D0``D7` — data and address registers
- `D0` — return value (by convention)
---
## LVO Calculation Example
For `dos_lib.fd` with `##bias 30`:
| LVO | Function |
|---|---|
| -6 | `Open` (mandatory) |
| -12 | `Close` (mandatory) |
| -18 | `Expunge` (mandatory) |
| -24 | `Reserved` (mandatory) |
| -30 | first public function |
| -36 | second public function |
| ... | |
| -144 | `SetComment` |
| -150 | `SetProtection` |
The actual LVOs match `dos/dos.h` defines:
```c
#define DOS_Open (-30)
#define DOS_Close (-36)
#define DOS_Read (-42)
```
---
## Proto Includes (Generated from .fd)
The `proto/` directory contains compiler-specific call wrappers generated from `.fd` files:
```c
/* proto/dos.h — generated automatically from dos_lib.fd */
#ifndef PROTO_DOS_H
#include <clib/dos_protos.h> /* function prototypes */
#include <inline/dos.h> /* inline register call stubs */
#endif
```
`inline/dos.h` contains:
```c
#define Open(name,accessMode) \
({ ULONG _r; \
__asm volatile("movea.l %1,a6; jsr -30(a6)" : "=r"(_r) : "r"(_DOSBase) : "a6"); \
_r; })
```
---
## .fd Files and Reverse Engineering
When reversing an Amiga binary, `.fd` files give you the **complete function name and parameter register map** for every library call. If you see:
```asm
MOVEA.L $00BEEF04, A6 ; graphics.library base
JSR -$114(A6) ; LVO -276
```
Look up `-276` in `graphics_lib.fd`:
```
-276 ÷ 6 = 46th function from start of table
```
Or directly look at `graphics/graphics.h` or `proto/graphics.h` for the constant:
```c
#define GFX_BltBitMap (-276)
/* → JSR -276(A6) = BltBitMap() */
```
---
## Full LVO Table Reconstruction
See [lvo_table.md](lvo_table.md) for the complete process of reconstructing a library's JMP table from scratch during reverse engineering.
---
## References
- NDK39: `fd/` directory — all `.fd` files
- NDK39: `proto/` directory — generated call wrappers
- NDK39: `clib/` directory — raw C prototypes
- ADCD 2.1: library chapters reference LVOs
- Community: `https://wiki.amigaos.net/wiki/Fd_Files`

View file

@ -0,0 +1,198 @@
[← Home](../README.md) · [Linking & Libraries](README.md)
# Compiler Inline Stubs
## Overview
AmigaOS library functions are not called via the C standard ABI. Every call goes through a **negative-offset JMP table** relative to a library base pointer held in an address register (conventionally A6). Compiler vendors each solve this differently:
- **SAS/C**`#pragma` headers and `inline/` headers
- **GCC (m68k-amigaos)** — inline-assembly `__attribute__((regparm))` stubs
- **VBCC**`__reg()` storage class and module pragmas
All approaches ultimately compile to the same machine code:
```asm
MOVEA.L _DOSBase, A6
JSR -LVO(A6) ; e.g. JSR -138(A6) for dos.library Output()
```
---
## SAS/C: Pragma-Based Stubs
### Pragma Syntax
SAS/C uses `#pragma` directives to declare register assignments:
```c
#pragma amicall(DOSBase, 0x008A, Open(D1, D2))
```
- `DOSBase` — global holding the library base (placed in A6 automatically)
- `0x008A` — LVO offset (hex)
- `Open(D1, D2)` — function name and register allocation for arguments
### Include Structure (NDK39)
```
NDK39/
include/
pragmas/
dos_pragmas.h ← #pragma amicall directives for dos.library
exec_pragmas.h ← exec.library pragmas
graphics_pragmas.h
...
inline/
dos.h ← Alternative: inline-macro stubs (SAS/C 6.x)
```
Usage:
```c
#include <clib/dos_protos.h> /* ANSI prototypes */
#include <pragmas/dos_pragmas.h> /* register pragmas */
extern struct DosLibrary *DOSBase;
BPTR fh = Open("foo", MODE_OLDFILE); /* expands to JSR -30(A6) */
```
### Pragma-Generated Code
```asm
; Open("foo", MODE_OLDFILE)
MOVE.L #MODE_OLDFILE, D2
MOVE.L #_str_foo, D1
MOVEA.L _DOSBase, A6
JSR -30(A6)
; Return value in D0
```
No stack frame involved — pure register passing.
---
## GCC (m68k-amigaos): Inline Assembly Stubs
### Modern Approach (GCC 6.x+ / bebbo cross-compiler)
The NDK provides `<proto/dos.h>` which pulls in compiler-specific stubs. Under bebbo GCC:
```c
/* auto-generated stub in clib2 / libnix */
static inline BPTR __attribute__((always_inline))
Open(CONST_STRPTR name, LONG accessMode)
{
register BPTR __ret __asm("d0");
register struct DosLibrary *const __DOSBase __asm("a6") = DOSBase;
register CONST_STRPTR __name __asm("d1") = name;
register LONG __accessMode __asm("d2") = accessMode;
__asm volatile ("jsr %%a6@(-30:W)"
: "=r"(__ret)
: "r"(__DOSBase), "r"(__name), "r"(__accessMode)
: "d1", "a0", "a1", "fp0", "fp1", "cc", "memory");
return __ret;
}
```
Key points:
- `__asm("a6")` forces the library base into A6
- `__asm("d1")`, `__asm("d2")` — per-function register assignments from `.fd` file
- `"jsr %%a6@(-30:W)"` — 16-bit signed displacement form (most efficient)
- Clobbers declared explicitly to prevent register allocation conflicts
### Older GCC (< 6.x): `__OLDSTYLE__` macros
```c
#define Open(name, mode) \
({ BPTR _r; \
__asm volatile ("movea.l %2,a6; jsr -30(a6)" : "=d"(_r) : \
"d"(name), "d"(mode), "m"(DOSBase) : "a6"); \
_r; })
```
Less elegant — explicitly moves DOSBase to A6 inside the macro.
---
## VBCC: `__reg()` Storage Class
VBCC uses a compiler extension to place variables in specific registers:
```c
/* vbcc style */
BPTR __reg("d0") Open(__reg("d1") CONST_STRPTR name,
__reg("d2") LONG mode);
#pragma amicall(DOSBase, 0x1E, Open(d1,d2))
```
VBCC automatically inserts `MOVEA.L DOSBase,A6` and `JSR -30(A6)`.
Module pragma file (`dos.fd`-derived):
```
##base DOSBase
##bias 30
Open(name,accessMode)(d1,d2)
##bias 36
Close(file)(d1)
...
```
---
## Generated Machine Code (All Compilers)
For `Write(fh, buf, len)` at LVO 48 (`$30`):
```asm
MOVEA.L _DOSBase, A6 ; load library base into A6
MOVE.L fh_var, D1 ; BPTR filehandle → D1
MOVE.L buf_ptr, D2 ; buffer address → D2
MOVE.L len_val, D3 ; byte count → D3
JSR -48(A6) ; call Write()
; D0 = bytes actually written, or -1 on error
```
---
## Register Allocation Convention
All AmigaOS library functions follow this invariant:
| Register | Role |
|---|---|
| A6 | Library base (always) |
| D0 | Return value (32-bit) |
| D0+D1 | 64-bit return (rare: `DivideU`) |
| D1D7, A0A3 | Arguments (per `.fd` file) |
| A4, A5 | Scratch in OS (do not rely on preservation) |
| D2D7, A2A3 | **Preserved** by OS calls (callee-saved) |
A compiler stub must:
1. Load arguments into exact registers from the `.fd` specification
2. Load the library base into A6
3. Execute `JSR -LVO(A6)`
4. Collect the return value from D0
---
## Stub Generation Tools
| Tool | Input | Output |
|---|---|---|
| `fd2pragma` (SAS) | `.fd` file | `#pragma amicall` header |
| `fd2inline` | `.fd` file | GCC inline-asm header |
| `fd2sfd` | `.fd` file | SFD (Amiga DevKit) format |
| `cvinclude.pl` | `.fd` + `.sfd` | VBCC pragma headers |
NDK39 ships pre-generated `pragmas/` and `inline/` directories — you only need to run these tools when writing a new library.
---
## References
- NDK39: `include/pragmas/`, `include/inline/`, `fd/`
- SAS/C 6.x Programmer's Reference Manual — chapter on pragmas
- GCC for Amiga (bebbo): `m68k-amigaos-gcc` repo, `libnix` stubs
- VBCC manual: http://www.compilers.de/vbcc.html — register specification chapter
- vlink documentation: http://sun.hasenbraten.de/vlink/

View file

@ -0,0 +1,142 @@
[← Home](../README.md) · [Linking & Libraries](README.md)
# AmigaOS Library Structure
## The Library Node
Every AmigaOS library (and device, resource) begins with a `struct Library` at its base address:
```c
/* exec/libraries.h */
struct Library {
struct Node lib_Node; /* +$00: linked list node */
UBYTE lib_Flags; /* +$0E: LIBF_ flags */
UBYTE lib_pad; /* +$0F: (reserved) */
UWORD lib_NegSize; /* +$10: bytes of function table (negative area) */
UWORD lib_PosSize; /* +$12: bytes of struct Library + private data */
UWORD lib_Version; /* +$14: major version */
UWORD lib_Revision; /* +$16: minor revision */
APTR lib_IdString; /* +$18: pointer to ID string */
ULONG lib_Sum; /* +$1C: checksum of the function table */
UWORD lib_OpenCnt; /* +$20: number of current openers */
};
```
`lib_NegSize` is the total byte size of the JMP table (the negative-address area preceding the struct). It equals `num_functions × 6` bytes per JMP instruction.
---
## JMP Table (Negative Offset Area)
The function table is constructed **below** the library base address. Each entry is a 6-byte JMP instruction:
```
Library base - 6: 4E F9 xx xx xx xx JMP AbsAddress (Open)
Library base - 12: 4E F9 xx xx xx xx JMP AbsAddress (Close)
Library base - 18: 4E F9 xx xx xx xx JMP AbsAddress (Expunge)
Library base - 24: 4E F9 xx xx xx xx JMP AbsAddress (Reserved)
Library base - 30: 4E F9 xx xx xx xx JMP AbsAddress (first user function)
Library base - 36: ...
```
### Standard Functions (Fixed LVOs for All Libraries)
| LVO (offset) | Function |
|---|---|
| -6 | `Open` |
| -12 | `Close` |
| -18 | `Expunge` |
| -24 | `Reserved` (must return 0) |
These four are mandatory for every library. User functions start at LVO -30.
### JMP Encoding
`4E F9 AAAA AAAA` = `JMP.L absolute_address`
To call function at LVO -30:
```asm
MOVEA.L LibBase, A6
JSR -30(A6) ; call through JMP table
```
The `JSR -30(A6)` does **not** jump directly to the function — it jumps to the JMP slot, which then jumps to the real function. This indirection is essential for `SetFunction()` patching.
---
## MakeLibrary() — Constructing the Table
`exec.library MakeLibrary()` builds a library:
```c
struct Library *MakeLibrary(
APTR funcArray, /* array of function pointers (APTR) or LONG offsets */
APTR structInit, /* structure initialiser table (or NULL) */
ULONG (*initFunc)(), /* init function (or NULL) */
ULONG dataSize, /* size of library data area */
BPTR segList /* segment list of the library code */
);
```
`funcArray` is a NULL-terminated list of function addresses. `MakeLibrary` allocates the combined negative+positive area and fills in the JMP table.
---
## Library Initialisation
At `MakeNode()` / `MakeLibrary()` time:
1. `AllocMem(lib_NegSize + lib_PosSize, MEMF_PUBLIC | MEMF_CLEAR)`
2. Fill JMP table at negative offsets
3. Initialise `struct Library` fields at positive offsets
4. Set `lib_Sum` to the checksum of the JMP table
At `AddLibrary()`:
1. Library is added to `SysBase->LibList`
2. Future `OpenLibrary()` calls find it by name via `FindName()`
---
## OpenLibrary() Path
```c
struct Library *base = OpenLibrary("mylib.library", MIN_VERSION);
```
Internally:
1. `exec` searches `SysBase->LibList` for a node with `ln_Name == "mylib.library"`
2. If found and version sufficient: calls `LVO_Open` (offset -6) on the library
3. If not found: attempts to load `LIBS:mylib.library` from disk via `LoadSeg()` + `InitResident()`
4. Returns library base pointer, or NULL on failure
---
## ROM Libraries (Kickstart)
Some libraries are **resident** — embedded directly in the Kickstart ROM:
- `exec.library` — always in ROM; base at `$4` in exec exception vector
- `graphics.library`, `intuition.library`, `dos.library` — loaded from ROM on boot
ROM-resident libraries are listed in the **Resident module** list. During boot, `exec` calls `InitResident()` for each module marked as auto-init.
---
## Library Flags
```c
/* lib_Flags bits: */
#define LIBF_SUMMING (1<<0) /* currently computing checksum */
#define LIBF_CHANGED (1<<1) /* function table was patched (SetFunction) */
#define LIBF_SUMUSED (1<<2) /* checksum is valid */
#define LIBF_DELEXP (1<<3) /* delayed expunge requested */
```
`LIBF_CHANGED` is set by `SetFunction()` to signal that the checksum is no longer valid — tools like `ShowConfig` use this to detect patched libraries.
---
## References
- NDK39: `exec/libraries.h`, `exec/nodes.h`
- ADCD 2.1 Autodocs: exec — OpenLibrary, MakeLibrary, AddLibrary, SetFunction
- *Amiga ROM Kernel Reference Manual: Libraries* — library architecture chapter
- http://amigadev.elowar.com/read/ADCD_2.1/Libraries_Manual_guide/node0002.html

View file

@ -0,0 +1,172 @@
[← Home](../README.md) · [Linking & Libraries](README.md)
# AmigaLib Static Linking
## Overview
AmigaOS programs are linked against a set of **static libraries** that provide startup code, C runtime stubs, and glue for OS entry points. The linker resolves all external symbols at link time and produces a self-contained HUNK-format executable.
---
## Key Static Libraries (NDK39 / SAS/C)
| Library | Purpose |
|---|---|
| `amiga.lib` | OS stub functions — `OpenLibrary`, `AllocMem`, etc. for non-inline linking |
| `sc.lib` / `c.lib` | SAS/C runtime: `printf`, `malloc`, `strlen`, standard I/O |
| `auto.lib` | Auto-open libraries (`DOSBase`, `SysBase` acquisition) |
| `debug.lib` | `kprintf`, `dprintf` serial debugging stubs |
| `m.lib` | Math library (software floating point) |
| `scm68020.lib` | SAS/C math for 68020 (co-processor stubs) |
For GCC:
| Library | Purpose |
|---|---|
| `libnix` | C runtime for `m68k-amigaos-gcc` — replaces `sc.lib` |
| `libamiga` | OS glue for GCC (NDK-based) |
| `libm` | Soft-float math |
| `libgcc` | GCC internal helpers (division, etc.) |
---
## Startup Object: `c.o` / `_start`
Every AmigaOS C program begins execution at the **first word of segment 0** — not at `main()`. The startup object `c.o` (SAS/C) or `crt0.o` (GCC/libnix) is always linked first and provides:
```asm
;; SAS/C c.o skeleton (simplified)
_start:
MOVE.L 4.W, A6 ; SysBase from absolute location $4
MOVE.L A0, _CommandStr ; raw CLI argument string (from dos.library)
MOVE.L A1, _WBenchMsg ; WBStartup message (if WB launch, else NULL)
JSR __main ; C runtime init → eventually calls main()
; D0 = exit code from main()
MOVE.L D0, _rc ; save return code
JSR __exit ; C runtime cleanup
RTS ; return to dos.library
```
### What `__main` Does
1. Opens `DOSBase` via `OpenLibrary("dos.library", 0)` using SysBase in A6
2. Sets up `stdin`/`stdout`/`stderr` file handles (wraps `dos.library` I/O)
3. Allocates C heap (if any static `malloc`/`new` usage)
4. Initializes `errno`, `_timezone`, `__ProgramName`
5. Calls any registered `__constructor` functions (C++ static init)
6. Calls `main(argc, argv)` — argument string is parsed from `_CommandStr`
7. Calls `exit(return_code)` → runs `atexit()` handlers → `CloseLibrary(DOSBase)`
---
## WBStartup Glue
When launched from Workbench (double-click), `A0 = NULL`, `A1 = WBStartup msg ptr`. The startup code must:
```c
/* standard WB detection in __main / startup */
if (_WBenchMsg) {
/* We are a WB launch. argc=0, argv=NULL passed to main() */
/* Must not return until cleanup */
}
```
On exit from a WB-launched program:
```c
Forbid(); /* prevent task switching during cleanup */
ReplyMsg((struct Message *)_WBenchMsg); /* unblock Workbench */
/* now safe to RTS / remove task */
```
This pattern is critical: **failing to `ReplyMsg` a WB launch will hang Workbench**.
---
## Stack Cookie
SAS/C startup checks for a stack size cookie in the executable:
```c
/* In your source — sets minimum stack to 8 KB */
LONG __stack = 8192;
```
The linker includes this symbol at a known offset; the OS shell reads it and allocates at least that much stack before launching. IDA Pro will often highlight `_stack` as a data symbol.
---
## CTRL-C Checking
SAS/C runtime polls for CTRL-C via `CheckSignal(SIGBREAKF_CTRL_C)` inside `printf`, `fgets`, and other stdio functions. Programs that do long computation loops should call:
```c
if (SetSignal(0, 0) & SIGBREAKF_CTRL_C)
cleanup_and_exit();
```
Or use `SAS/C`'s `__chkabort()` hook.
---
## Typical Link Command (SAS/C)
```
slink FROM lib/c.o myobj.o LIB lib/sc.lib lib/amiga.lib TO myprogram
```
Order matters:
1. `lib/c.o`**must be first** (entry point at start of segment 0)
2. Object files (`myobj.o`, ...)
3. `sc.lib` — C runtime (resolves `printf`, etc.)
4. `amiga.lib` — OS stubs (resolves any non-inlined OS calls)
### Typical GCC Link (bebbo)
```bash
m68k-amigaos-gcc -o myprogram myobj.o -lamiga -lnix -lgcc \
-Wl,-Map,myprogram.map
```
Linker script places `crt0.o` automatically via `-lnix` startup group.
---
## Library Archive Format
Static libraries (`.lib`) are HUNK_LIB archives — sequences of embedded HUNK_UNIT object files with a HUNK_INDEX for fast symbol lookup:
```
HUNK_LIB
[HUNK_UNIT: AllocMem.o]
HUNK_CODE
HUNK_EXT (export: _AllocMem)
HUNK_END
[HUNK_UNIT: FreeMem.o]
...
HUNK_INDEX
[symbol table: _AllocMem → unit 0, _FreeMem → unit 1, ...]
```
The linker only pulls in units whose exported symbols are referenced — unused code from libraries is **not** linked into the executable (link-time dead-stripping).
---
## Segment Layout in Final Executable
With a typical 3-object, 2-lib link:
```
Segment 0 (code): c.o startup + all CODE sections merged
Segment 1 (data): all DATA sections merged
Segment 2 (BSS): all BSS sections (zero-filled)
```
Most linkers merge same-type sections by default. `slink` supports explicit placement control via `CHIP` / `FAST` keywords.
---
## References
- NDK39: `lib/``amiga.lib`, `auto.lib`, `debug.lib`, `c.o`
- SAS/C 6.x Programmer's Reference Manual — linking chapter
- libnix source: https://github.com/bebbo/libnix
- *Amiga ROM Kernel Reference Manual: Libraries* — process startup appendix

View file

@ -0,0 +1,194 @@
[← Home](../README.md) · [Linking & Libraries](README.md)
# LVO Table Layout & Reconstruction
## Overview
The **Library Vector Offset (LVO) table** is the core mechanism of the AmigaOS ABI. Understanding and reconstructing it is essential for reversing any Amiga binary that calls system libraries.
---
## Memory Layout of a Loaded Library
```
Low addresses
←──────────────────────────────────────────────────────────→
High addresses
[JMP table (negative area)] [struct Library + private data]
... -36 -30 -24 -18 -12 -6 base+0 base+2 ...
↑ ↑
Last user function Library base pointer
(e.g., -576 for graphics) (returned by OpenLibrary)
```
Each slot is **6 bytes**:
```
4E F9 AA AA AA AA JMP.L AAAAAAAA
```
---
## Calculating LVOs
Given `##bias N` in the `.fd` file, the LVO of the `k`th public function (1-indexed) is:
```
LVO_k = -(N + (k-1) × 6)
```
The four mandatory functions before public functions (Open, Close, Expunge, Reserved) are at:
- `LVO_Open = -6`
- `LVO_Close = -12`
- `LVO_Expunge = -18`
- `LVO_Reserved = -24`
For a library with `##bias 30`, the first public function is at `-30`.
---
## Key Library LVO Tables
### exec.library (##bias 30)
| LVO | Function | Registers |
|---|---|---|
| -30 | `Supervisor` | A5=function |
| -36 | `ExitIntr` | (internal) |
| -42 | `Schedule` | (internal) |
| -48 | `Reschedule` | (internal) |
| -54 | `Switch` | (internal) |
| -60 | `Dispatch` | (internal) |
| -66 | `Exception` | (internal) |
| -72 | `InitCode` | D0=startClass, D1=version |
| -78 | `InitStruct` | A1=mem, A2=table, D0=size |
| -84 | `MakeLibrary` | A0=vectors, A1=struct, A2=init, D0=dataSize, D1=segList |
| -90 | `MakeFunctions` | A0=target, A1=funcArray, A2=funcDispBase |
| -96 | `FindResident` | A1=name |
| -102 | `InitResident` | A1=resident, D1=segList |
| -108 | `Alert` | D7=alertNum |
| -114 | `Debug` | D0=flags |
| -120 | `Disable` | |
| -126 | `Enable` | |
| -132 | `Forbid` | |
| -138 | `Permit` | |
| -144 | `SetSR` | D0=newSR, D1=mask |
| -150 | `SuperState` | |
| -156 | `UserState` | D0=savedSR |
| -162 | `SetIntVector` | D0=intNum, A1=interrupt |
| -168 | `AddIntServer` | D0=intNum, A1=interrupt |
| -174 | `RemIntServer` | D0=intNum, A1=interrupt |
| -180 | `Cause` | A1=interrupt |
| -186 | `Allocate` | A0=memHeader, D0=size |
| -192 | `Deallocate` | A0=memHeader, A1=memBlock, D0=size |
| -198 | `AllocMem` | D0=byteSize, D1=requirements |
| -204 | `AllocAbs` | D0=byteSize, A1=location |
| -210 | `FreeMem` | A1=memBlock, D0=byteSize |
| -216 | `AvailMem` | D1=requirements |
| -222 | `AllocEntry` | A0=entry |
| -228 | `FreeEntry` | A0=entry |
| -234 | `Insert` | A0=list, A1=node, A2=listNode |
| -240 | `AddHead` | A0=list, A1=node |
| -246 | `AddTail` | A0=list, A1=node |
| -252 | `Remove` | A1=node |
| -258 | `RemHead` | A0=list |
| -264 | `RemTail` | A0=list |
| -270 | `Enqueue` | A0=list, A1=node |
| -276 | `FindName` | A0=list, A1=name |
| -282 | `AddTask` | A1=task, A2=initialPC, A3=finalPC |
| -288 | `RemTask` | A1=task |
| -294 | `FindTask` | A1=name |
| -300 | `SetTaskPri` | A1=task, D0=priority |
| -306 | `SetSignal` | D0=newSignals, D1=signalSet |
| -312 | `SetExcept` | D0=newSignals, D1=signalSet |
| -318 | `Wait` | D0=signalSet |
| -324 | `Signal` | A1=task, D0=signals |
| -330 | `AllocSignal` | D0=signalNum |
| -336 | `FreeSignal` | D0=signalNum |
| -342 | `AllocTrap` | D0=trapNum |
| -348 | `FreeTrap` | D0=trapNum |
| -354 | `AddPort` | A1=port |
| -360 | `RemPort` | A1=port |
| -366 | `PutMsg` | A0=port, A1=message |
| -372 | `GetMsg` | A0=port |
| -378 | `ReplyMsg` | A1=message |
| -384 | `WaitPort` | A0=port |
| -390 | `FindPort` | A1=name |
| -396 | `AddLibrary` | A1=library |
| -402 | `RemLibrary` | A1=library |
| -408 | `OldOpenLibrary` | A1=libName |
| -414 | `CloseLibrary` | A1=library |
| -420 | `SetFunction` | A1=library, A0=funcOffset, D0=newFunc |
| -426 | `SumLibrary` | A1=library |
| -432 | `AddDevice` | A1=device |
| -438 | `RemDevice` | A1=device |
| -444 | `OpenDevice` | A0=devName, D0=unit, A1=ioReq, D1=flags |
| -450 | `CloseDevice` | A1=ioReq |
| -456 | `DoIO` | A1=ioReq |
| -462 | `SendIO` | A1=ioReq |
| -468 | `CheckIO` | A1=ioReq |
| -474 | `WaitIO` | A1=ioReq |
| -480 | `AbortIO` | A1=ioReq |
| -486 | `AddResource` | A1=resource |
| -492 | `RemResource` | A1=resource |
| -498 | `OpenResource` | A1=resName |
| -552 | `OpenLibrary` | A1=libName, D0=version |
| -558 | `InitSemaphore` | A0=semaphore |
| -564 | `ObtainSemaphore` | A0=semaphore |
| -570 | `ReleaseSemaphore` | A0=semaphore |
| -576 | `AttemptSemaphore` | A0=semaphore |
| -582 | `ObtainSemaphoreList` | A0=sigSemList |
| -588 | `ReleaseSemaphoreList` | A0=sigSemList |
| -594 | `FindSemaphore` | A1=sigSem |
| -600 | `AddSemaphore` | A1=sigSem |
| -606 | `RemSemaphore` | A1=sigSem |
| -612 | `SumKickData` | |
| -618 | `AddMemList` | D0=size, D1=attr, D2=pri, A0=base, A1=name |
| -624 | `CopyMem` | A0=source, A1=dest, D0=size |
| -630 | `CopyMemQuick` | A0=source, A1=dest, D0=size |
| -636 | `CacheClearU` | |
| -642 | `CacheClearE` | A0=addr, D0=len, D1=caches |
| -648 | `CacheControl` | D0=cacheBits, D1=cacheMask |
| -654 | `CreateIORequest` | A0=port, D0=size |
| -660 | `DeleteIORequest` | A0=ioreq |
| -666 | `CreateMsgPort` | |
| -672 | `DeleteMsgPort` | A0=port |
| -678 | `ObtainSemaphoreShared` | A0=sigSem |
| -684 | `AllocVec` | D0=size, D1=attr |
| -690 | `FreeVec` | A1=mem |
| -726 | `NewAddTask` | A1=task, A2=initialPC, A3=finalPC, A4=?? |
---
## Reconstructing the JMP Table During Reverse Engineering
When analysing a patched library (e.g., `bsdsocket.library`):
1. **Find the library base** — usually pointed to by a global variable or passed in A6
2. **Read `lib_NegSize`** at `base+0x10` — this gives the total JMP table byte count
3. **Scan the JMP table** — from `base - lib_NegSize` to `base - 6` in 6-byte steps
4. **Decode each JMP**`4E F9 AA AA AA AA` → target at `AAAAAAAA`
5. **Match to .fd file** — entry at offset `(-6 × n)` corresponds to function `n`
IDA Pro script to dump JMP table:
```python
import idc, idaapi
def dump_jmp_table(lib_base, num_funcs, fd_names):
for i, name in enumerate(fd_names):
slot = lib_base - 6 * (i + 1)
opcode = idc.get_wide_word(slot)
if opcode == 0x4EF9:
target = idc.get_wide_dword(slot + 2)
idc.set_name(slot, f"lvo_{name}", idc.SN_NOWARN)
idc.set_name(target, name, idc.SN_NOWARN)
print(f"LVO -{6*(i+1):4d} {name:40s} → {target:#010x}")
```
---
## References
- NDK39: `fd/exec_lib.fd`, `fd/dos_lib.fd`, `fd/graphics_lib.fd`
- NDK39: `include/exec/execbase.h` — SysBase layout
- ADCD 2.1 Autodocs: all library function descriptions

View file

@ -0,0 +1,124 @@
[← Home](../README.md) · [Linking & Libraries](README.md)
# M68k Register Calling Conventions on Amiga
## Overview
AmigaOS uses a **pure register-based calling convention** for all OS API calls. There is no stack-based C ABI for library functions. Every argument is passed in a specific CPU register defined by the `.fd` file for that library.
---
## The AmigaOS Register Convention
All OS library calls follow this scheme:
| Register | Role |
|---|---|
| **A6** | Library base pointer (always) |
| **D0** | Return value (32-bit integer or BOOL) |
| **D0+D1** | 64-bit return (rare; e.g., `DivideU`) |
| D1D7, A0A3 | Arguments — exact registers per `.fd` |
| D2D7, A2A3 | **Callee-preserved** (OS will not trash these) |
| D0, D1, A0, A1 | **Scratch** (may be destroyed by any OS call) |
| A4 | Global data pointer (VBCC; not used by OS) |
| A5 | Frame pointer (some compilers; not used by OS) |
| A6 | Library base — **always trashed to point to lib** |
| A7 | Stack pointer |
### Key rules:
- **A6 is always destroyed** — it holds the target library base after every OS call
- **D0, D1, A0, A1** are volatile — save them if needed across OS calls
- **FP0, FP1** are scratch if the FPU is present
---
## Example: `dos.library Write()`
From `fd/dos_lib.fd`:
```
Write(file,buffer,length)(d1,d2,d3)
```
```c
/* C call: */
LONG n = Write(fh, buf, 512);
/* Compiles to: */
MOVEA.L _DOSBase, A6
MOVE.L fh, D1
MOVE.L buf, D2
MOVE.L #512, D3
JSR -48(A6)
; D0 = bytes written (1 = error)
```
---
## Preserved vs. Scratch Register Summary
```
Scratch (caller must save if needed):
D0 D1 A0 A1 A6 FP0 FP1
Preserved (callee saves/restores):
D2 D3 D4 D5 D6 D7
A2 A3 A4 A5
FP2 FP3 FP4 FP5 FP6 FP7
```
This matches the Motorola 68000 family software convention, but AmigaOS does **not** use A5 as a frame pointer (unlike the standard System V m68k ABI).
---
## Inter-Library Calls
When one library function calls another library internally, it must:
```asm
; save A6 (current lib base), load new lib base
MOVEM.L A6, -(SP)
MOVEA.L _GfxBase, A6
JSR -102(A6) ; graphics.library BltClear()
MOVEM.L (SP)+, A6
```
Failure to save/restore A6 is a common bug in hand-written assembly library code.
---
## C Compiler Differences
### SAS/C 6.x
- Generates standard `MOVEA.L libbase,A6; JSR -lvo(A6)` via `#pragma amicall`
- Uses A5 as a frame pointer in non-leaf functions
- Stack frame: `LINK A5,#-N` on entry, `UNLK A5` on exit
### GCC (bebbo m68k-amigaos)
- Generates inline-asm stubs with explicit register constraints
- No frame pointer by default (`-fomit-frame-pointer`)
- D2D7/A2A3 saved on stack per function (ABI-compatible)
### VBCC
- Uses `__reg()` storage class for explicit register placement
- No frame pointer — tighter code than SAS/C for register-intensive functions
---
## Detecting the Calling Convention in IDA Pro
Pattern to identify an OS API call in disassembly:
```asm
MOVEA.L (_DOSBase).L, A6 ; load library base
JSR (-138,A6) ; call at LVO 138
```
Cross-reference the LVO against the `.fd` file to identify the function. IDA's Amiga loader applies LVO names automatically when library definitions are present.
---
## References
- NDK39: `fd/*.fd` — register assignments per function
- *Amiga ROM Kernel Reference Manual: Libraries* — register conventions appendix
- SAS/C 6.x Programmer's Guide — calling convention chapter
- GCC m68k-amigaos (bebbo) — `libnix` inline headers

View file

@ -0,0 +1,158 @@
[← Home](../README.md) · [Linking & Libraries](README.md)
# SetFunction — Runtime Library Patching
## Overview
`exec.library SetFunction()` replaces a single function in a library's JMP table with a different function pointer. It is the standard AmigaOS mechanism for hooking, patching, and replacing library functions at runtime.
```c
/* exec/exec.h */
APTR SetFunction(
struct Library *library, /* A1: library to patch */
LONG funcOffset, /* A0: LVO (e.g., -30) — must be negative */
ULONG (*newFunc)() /* D0: new function address */
);
/* Returns: D0 = old function address (to call through in the replacement) */
```
---
## How SetFunction Works
SetFunction modifies the JMP table in the library's negative-offset area:
```
Before:
lib_base - 30: 4E F9 00 01 23 45 JMP $00012345 (original function)
After SetFunction(lib, -30, MyNewFunc):
lib_base - 30: 4E F9 00 FF 00 10 JMP $00FF0010 (replacement)
```
It also:
1. Sets `lib_Flags |= LIBF_CHANGED` — signals that the library was patched
2. Invalidates `lib_Sum` — checksum no longer matches
3. Returns the **old** function address so the replacement can chain to the original
---
## Canonical Patching Pattern
```c
/* Store the old function pointer for chaining */
static APTR OldOpen = NULL;
/* Replacement function — must match the library's calling convention */
BPTR __saveds MyOpen(struct DosLibrary *base __asm("a6"),
BSTR name __asm("d1"),
LONG accessMode __asm("d2"))
{
/* Pre-processing */
kprintf("Open called: %s mode %ld\n", BADDR(name), accessMode);
/* Chain to original */
return ((BPTR(*)(struct DosLibrary *, BSTR, LONG))OldOpen)(base, name, accessMode);
}
/* Installation */
void install_patch(void)
{
Forbid(); /* atomic with respect to task switching */
OldOpen = (APTR)SetFunction((struct Library *)DOSBase,
-30, /* Open LVO */
(ULONG(*)())MyOpen);
Permit();
}
```
> [!WARNING]
> `Forbid()` / `Permit()` must surround the `SetFunction` call to prevent race conditions where another task calls the function between the time you read the old pointer and the time the new pointer is installed.
---
## Calling the Original (Chaining)
The replacement function **must** call the original to maintain correct library behaviour. The old function address returned by `SetFunction` is a raw pointer to the original function body (not the JMP slot):
```c
/* Jump to original using register convention */
static APTR OldAllocMem;
APTR __saveds MyAllocMem(ULONG size __asm("d0"), ULONG flags __asm("d1"),
struct ExecBase *base __asm("a6"))
{
APTR result = ((APTR(*)(ULONG, ULONG, struct ExecBase *))OldAllocMem)(size, flags, base);
if (result == NULL && (flags & MEMF_CHIP)) {
kprintf("Chip RAM alloc failed: %lu bytes\n", size);
}
return result;
}
```
---
## Removing a Patch
To remove a patch cleanly, restore the original:
```c
void remove_patch(void)
{
Forbid();
SetFunction((struct Library *)DOSBase, -30, (ULONG(*)())OldOpen);
Permit();
}
```
This **must** be done before unloading the library that contains the replacement function — otherwise the JMP table points to freed memory.
---
## SetFunction in Reverse Engineering Context
`SetFunction` creates trampoline patterns recognisable in disassembly:
**JMP table after patching:**
```asm
; Library JMP slot (lib_base - 30):
JMP.L $00FF1234 ; replacement function (different segment)
```
**Replacement function preamble:**
```asm
00FF1234:
MOVEM.L D0-D7/A0-A6, -(SP) ; save all regs (wrapper)
; ... logging/modification ...
MOVEM.L (SP)+, D0-D7/A0-A6
JMP.L OldOpen ; chain to original
```
**Detection heuristics:**
- JMP slot points outside the library's code segment
- `lib_Flags & LIBF_CHANGED` is set
- The `lib_Sum` no longer matches a fresh calculation
- The pointed-to function immediately chains to another address
---
## SetFunction vs Manual Patching
Some protections directly write to the JMP table without using `SetFunction`:
```asm
; Direct JMP table write (bypasses SetFunction protocol):
MOVE.L #MyFunc, -30+2(A0) ; overwrite address part of JMP instruction
```
This does not set `LIBF_CHANGED` and is harder to detect. Look for direct writes to `LIB_BASE - N + 2` (the address word of a JMP.L instruction).
---
## References
- NDK39: `exec/execbase.h`, `exec/libraries.h`
- ADCD 2.1 Autodocs: exec — `SetFunction`
- http://amigadev.elowar.com/read/ADCD_2.1/Libraries_Manual_guide/node01A8.html
- *Amiga ROM Kernel Reference Manual: Libraries* — library patching chapter

View file

@ -0,0 +1,116 @@
[← Home](../README.md) · [Linking & Libraries](README.md)
# Shared Library Runtime Mechanics
## Overview
AmigaOS shared libraries are **resident in memory** — once opened, the same code is shared by all tasks. The OS tracks open counts, handles version negotiation, and defers unloading until all users have closed the library.
---
## Library Discovery: `OpenLibrary()`
```c
struct Library *OpenLibrary(CONST_STRPTR libName, ULONG version);
```
`exec.library` searches for the library in this order:
1. `exec.library` LibList (already-open libraries in RAM)
2. Resident module list (ROM-resident: exec, graphics, etc.)
3. DOS path search — `LIBS:` assign — scan for `libName`
4. If found on disk: `LoadSeg()` + `InitLib` → add to LibList
5. Increment `lib_OpenCnt`
6. Call library's `Open()` vector
7. Return library base pointer (NULL on failure or version mismatch)
### Version Checking
```c
DOSBase = (struct DosLibrary *)OpenLibrary("dos.library", 36);
if (!DOSBase) { /* OS older than 2.0 — handle gracefully */ }
```
The `version` argument is the **minimum** acceptable `lib_Version`. Version 0 accepts any.
| Library | Version | OS |
|---|---|---|
| `exec.library` | 33 | OS 1.2 |
| `exec.library` | 36 | OS 2.0 |
| `exec.library` | 39 | OS 3.0 |
| `exec.library` | 40 | OS 3.1 |
| `exec.library` | 44 | OS 3.2 |
---
## Library Base Structure
```c
/* exec/libraries.h */
struct Library {
struct Node lib_Node; /* ln_Type = NT_LIBRARY */
UBYTE lib_Flags; /* LIBF_SUMUSED, LIBF_DELEXP */
UBYTE lib_Pad;
UWORD lib_NegSize; /* bytes of JMP table preceding base */
UWORD lib_PosSize; /* sizeof(Library) + private fields */
UWORD lib_Version;
UWORD lib_Revision;
APTR lib_IdString; /* "dos.library 40.1 (16.7.93)" */
ULONG lib_Sum; /* JMP table checksum */
UWORD lib_OpenCnt; /* reference count */
};
```
The pointer returned by `OpenLibrary` points to this structure. The JMP table is **below** the base at negative offsets.
---
## Standard Library Vectors
| Offset | Function | Description |
|---|---|---|
| 6 | `Open` | Increment open count, return base |
| 12 | `Close` | Decrement count, optionally expunge |
| 18 | `Expunge` | Free library if open count == 0 |
| 24 | `Reserved` | Always NULL |
| 30 and below | Library-specific | Per `.fd` file |
---
## Open Count and Expunge Deferral
```c
/* exec.library CloseLibrary() pseudo-code */
lib->lib_OpenCnt--;
if (lib->lib_OpenCnt == 0) {
BPTR seg = CallVector(lib, CLOSE_VEC);
if (seg) UnLoadSeg(seg);
}
```
A library sets `LIBF_DELEXP` when it cannot unload (low memory). On the next close that drops the count to zero, expunge runs.
---
## Open/Close Lifecycle Diagram
```mermaid
stateDiagram-v2
[*] --> Unloaded
Unloaded --> Initialised : LoadSeg + InitLib
Initialised --> Open : lib_OpenCnt++ (OpenLibrary)
Open --> Open : additional opens
Open --> Initialised : CloseLibrary, count > 0
Initialised --> Expunging : CloseLibrary, count == 0
Expunging --> Unloaded : Expunge OK — FreeMem + UnLoadSeg
Expunging --> Initialised : LIBF_DELEXP — deferred
```
---
## References
- NDK39: `exec/libraries.h`, `exec/nodes.h`
- ADCD 2.1 Autodocs: `OpenLibrary`, `CloseLibrary`, `MakeLibrary`
- *Amiga ROM Kernel Reference Manual: Libraries* — library creation chapter
- http://amigadev.elowar.com/read/ADCD_2.1/Libraries_Manual_guide/node0124.html

View file

@ -0,0 +1,209 @@
[← Home](../README.md) · [Linking & Libraries](README.md)
# Startup Code — c.o / gcrt0.S
## Overview
The AmigaOS **startup code** is the first code that runs when an executable is launched. It bridges the OS loader (which jumps to hunk 0 offset 0) and the C `main()` function. Understanding it is critical for reverse engineering — it reveals the initialisation sequence, library opens, argument parsing, and the Workbench vs CLI detection path.
---
## Entry Point Contract
When `CreateProc()` dispatches the new task, the CPU jumps to the first longword of hunk 0 with:
| Register | Value |
|---|---|
| D0 | Length of CLI argument string |
| A0 | Pointer to CLI argument string (or NULL for Workbench) |
| A1 | Pointer to WBStartup message (Workbench) or NULL (CLI) |
| A6 | (caller's A6 — not reliable) |
| SP | Top of the allocated stack |
---
## SAS/C Startup (c.o)
The SAS/C `c.o` module is the canonical AmigaOS C startup:
```asm
_start:
; 1. Get SysBase from absolute address 4
MOVE.L 4.W, A6
MOVE.L A6, _SysBase
; 2. Detect CLI vs Workbench launch
MOVE.L D0, _RawCommandLen ; save CLI arg length
MOVE.L A0, _RawCommandStr ; save CLI arg pointer
TST.L A1 ; A1=NULL means CLI, non-NULL = Workbench
BEQ.S .cli_launch
; Workbench path:
MOVE.L A1, _WBenchMsg ; save WBStartup message
JSR _OpenLibraries ; open DOS, graphics etc.
BSR _main ; call C main()
BRA.S .exit
.cli_launch:
JSR _OpenLibraries
BSR _main
; fall through to exit
.exit:
MOVE.L D0, _ReturnCode ; main() return value
JSR _CloseLibraries
; return D0 to AmigaDOS
RTS
```
### _OpenLibraries (SAS/C)
```asm
_OpenLibraries:
MOVEA.L _SysBase, A6
; Open dos.library
LEA _dosname(PC), A1 ; "dos.library"
MOVEQ #0, D0
JSR -552(A6) ; OpenLibrary
MOVE.L D0, _DOSBase
; (other libraries as needed by pragmas)
RTS
_dosname: DC.B "dos.library", 0
```
### Workbench Message Handling (SAS/C)
```asm
; After main() returns, if Workbench launch:
MOVEA.L _SysBase, A6
JSR -132(A6) ; Forbid()
MOVEA.L _WBenchMsg, A1
JSR -378(A6) ; ReplyMsg(_WBenchMsg)
JSR -138(A6) ; Permit() — never actually reached
RTS
```
> [!IMPORTANT]
> For Workbench launches, `Forbid()` must be called **before** `ReplyMsg()` on the WBStartup message. The Workbench process waits for this reply; if the app exits without replying, the Workbench can crash or hang.
---
## GCC Startup (gcrt0.S / libnix)
GCC's AmigaOS startup is provided by `libnix` (or newer `clib2`):
```asm
/* gcrt0.S — GCC startup */
.text
.globl _start
_start:
move.l 4.w, a6 /* SysBase */
jsr ___startup_SysBase /* store SysBase, init libnix */
/* Open dos.library */
lea _doslib(pc), a1
moveq #0, d0
jsr -552(a6) /* OpenLibrary */
move.l d0, _DOSBase
/* Parse args, set up __argv/__argc */
jsr ___parse_args
/* Call main() */
jsr _main
/* Exit cleanup */
move.l d0, -(sp) /* return value */
jsr ___exit
_doslib: .asciz "dos.library"
```
`libnix` provides a more complete C runtime than the minimal SAS/C `c.o`.
---
## Argument Parsing
### CLI Arguments
CLI arguments arrive as a **raw byte string** pointed to by A0, with D0 holding the length. The startup code must:
1. Copy the string (it lives in the caller's stack)
2. Tokenise it into `argc`/`argv` (standard) or pass raw via `RawArg()`
SAS/C standard `argc`/`argv`:
```c
/* _main.c in c.o */
int main(int argc, char *argv[]);
/* startup converts:
RawCommandStr = "myarg1 myarg2\n"
→ argc = 3, argv = ["progname", "myarg1", "myarg2"] */
```
### Workbench Arguments
For Workbench launches, the WBStartup message carries an array of `WBArg` structures:
```c
struct WBStartup {
struct Message sm_Message;
struct MsgPort *sm_Process; /* WB process port */
BPTR sm_Segment; /* loaded segment */
LONG sm_NumArgs; /* number of args */
char *sm_ToolWindow;
struct WBArg *sm_ArgList; /* array of WBArg */
};
struct WBArg {
BPTR wa_Lock; /* directory containing the icon */
STRPTR wa_Name; /* filename of the icon */
};
```
---
## Stack Setup
The stack size is specified at link time (SAS/C) or in the Tooltype/CLI:
```
; SAS/C: specify in linker command:
slink lib/c.o myobj.o TO myexe STACKSIZE 8192
; At runtime, AmigaDOS CreateProc() passes NP_StackSize
```
The startup code does **not** set up the stack — that is done by `CreateProc()` / the OS task dispatch. The startup can optionally check for stack overflow via `GetCC()` / `CheckStack()`.
---
## Recognising Startup Code in IDA Pro
The startup stub is always at the start of hunk 0. Look for:
```asm
; SAS/C signature:
MOVE.L $00000004, A6 ; or MOVEA.L 4.W, A6
; followed immediately by MOVE.L A6, _SysBase
```
```asm
; GCC signature (bebbo):
MOVE.L 4.W, A6
JSR some_init_stub ; libnix internal
```
After identifying startup, `main()` is the first function BSR/JSR'd after the library opens.
---
## References
- NDK39: `lib/` directory — `c.o`, `c_sm.o` (SAS/C startup variants)
- libnix source: https://github.com/bebbo/libnix
- clib2 source: https://github.com/adozenlines/clib2
- SAS/C 6.x Programmer's Guide — startup code chapter
- *Amiga ROM Kernel Reference Manual: Libraries* — process creation, Workbench startup

48
05_reversing/README.md Normal file
View file

@ -0,0 +1,48 @@
[← Home](../README.md)
# Reverse Engineering AmigaOS Binaries
## Overview
This section provides a systematic methodology for reverse engineering AmigaOS executables and shared libraries using IDA Pro (or Ghidra with the Amiga plugin), with focus on:
- Reconstructing the library JMP table
- Identifying compiler-specific code patterns
- Understanding the exec/dos calling convention at the assembly level
- Tracing library patches (SetFunction)
- Case studies from real Amiga software
## Contents
| File | Topic |
|---|---|
| [methodology.md](methodology.md) | Step-by-step RE workflow for Amiga HUNK binaries |
| [ida_setup.md](ida_setup.md) | IDA Pro configuration for 68k/Amiga analysis |
| [compiler_fingerprints.md](compiler_fingerprints.md) | Compiler identification by code patterns |
| [library_reconstruction.md](library_reconstruction.md) | Reconstructing unknown library JMP tables |
| [patching_techniques.md](patching_techniques.md) | Surgical binary patching methods |
| [case_studies/](case_studies/) | Real-world RE walkthroughs |
| [case_studies/ramdrive_device.md](case_studies/ramdrive_device.md) | ramdrive.device RE walkthrough |
## Core Principles
1. **Know the ABI first** — All library calls are `JSR LVO(A6)`. Before reversing any function, identify which library A6 holds using the `lib_Node.ln_Name` string at `base+$00`.
2. **Use .fd files** — The NDK39 `.fd` files give you every function name and parameter mapping for free.
3. **Relocations are your friend** — HUNK_RELOC32 entries tell you exactly which longwords are inter-hunk references, making it easy to distinguish code from data.
4. **Compiler signatures reduce work** — SAS/C vs GCC produces distinct prologues. Identifying the compiler narrows the pattern space dramatically.
## Tool Setup
| Tool | Purpose |
|---|---|
| IDA Pro 7.x | Primary disassembler and decompiler (Hex-Rays) |
| IDA Amiga plugin | HUNK loader, HUNK_SYMBOL import |
| `hunkinfo` | Quick hunk/symbol/reloc dump |
| Ghidra + AmigaOS plugin | Free alternative to IDA |
| wack / MonAm | On-device debugger |
## References
- NDK39: `fd/`, `include/`
- ADCD 2.1: complete library autodocs
- *Amiga ROM Kernel Reference Manual: Libraries* and *Devices*

View file

@ -0,0 +1,129 @@
[← Home](../../README.md) · [Reverse Engineering](../README.md)
# Case Study — ramdrive.device Structure Analysis
## Overview
`ramdrive.device` is the Amiga's built-in RAM disk device. It ships in Kickstart ROM and implements the `trackdisk.device`-compatible interface on top of allocated Chip/Fast RAM. Analysing it teaches exec device architecture, IORequest handling, and the device-as-library pattern.
---
## Locating ramdrive.device in ROM
```bash
# Find the resident tag in Kickstart ROM dump:
python3 - <<'EOF'
import struct, sys
rom = open("kick31.rom", "rb").read()
for i in range(0, len(rom)-4, 2):
tag = struct.unpack_from(">H", rom, i)[0]
if tag == 0x4AFC: # RomTag magic
rt_matchword = struct.unpack_from(">H", rom, i)[0]
rt_matchtag = struct.unpack_from(">I", rom, i+2)[0]
rt_name = struct.unpack_from(">I", rom, i+14)[0]
# print offset and map rt_name to string
print(f"RomTag @ ROM+{i:#x}")
EOF
```
The RomTag for `ramdrive.device` has `RT_TYPE=NT_DEVICE` and `RT_NAME="ramdrive.device"`.
---
## Device Structure Layout
`ramdrive.device` extends `struct Device` (which extends `struct Library`):
```c
struct RAMDriveBase {
struct Device rd_Device; /* standard device base */
/* private fields follow */
APTR rd_RAMStart; /* pointer to allocated RAM block */
ULONG rd_RAMSize; /* total size */
ULONG rd_BlockSize; /* always 512 */
ULONG rd_NumBlocks; /* RAMSize / BlockSize */
struct MinList rd_Units; /* list of open units */
};
```
---
## Standard Device Vectors (LVO)
| Offset | Vector | Description |
|---|---|---|
| 6 | `Open` | Open a unit (unit number in io_Unit) |
| 12 | `Close` | Close unit, decrement open count |
| 18 | `Expunge` | Unload if no users |
| 24 | `Reserved` | NULL |
| 30 | `BeginIO` | Queue or execute an IORequest |
| 36 | `AbortIO` | Cancel pending IORequest |
`BeginIO` is the heart of any device driver — it dispatches on `io_Command`.
---
## IORequest Command Handling
```c
void BeginIO(struct IORequest *ior) {
struct IOStdReq *io = (struct IOStdReq *)ior;
switch (io->io_Command) {
case CMD_READ: rd_Read(io); break;
case CMD_WRITE: rd_Write(io); break;
case CMD_CLEAR: rd_Clear(io); break;
case TD_FORMAT: rd_Format(io); break;
case TD_GETGEOMETRY: rd_Geometry(io); break;
default:
io->io_Error = IOERR_NOCMD;
ReplyMsg(&io->io_Message);
}
}
```
### `CMD_READ` Implementation
```c
void rd_Read(struct IOStdReq *io) {
UBYTE *src = rdbase->rd_RAMStart + io->io_Offset;
CopyMem(src, io->io_Data, io->io_Length);
io->io_Actual = io->io_Length;
io->io_Error = 0;
ReplyMsg(&io->io_Message);
}
```
---
## Memory Allocation Strategy
On initialisation, `ramdrive.device` uses `AllocMem`:
```c
rdbase->rd_RAMStart = AllocMem(rdbase->rd_RAMSize,
MEMF_PUBLIC | MEMF_CLEAR);
```
Later requests can pass `MEMF_CHIP` to force chip RAM allocation (useful for audio/graphics DMA sources).
---
## Disassembly Landmarks in IDA
After loading Kickstart ROM in IDA with M68k + HUNK/ROM loader:
1. Search for string `"ramdrive.device"` → find RomTag
2. `RT_INIT` pointer → initialization function
3. `RT_INIT` calls `MakeLibrary` then `AddDevice`
4. The device base is stored — follow to find `BeginIO` function
5. `BeginIO` switch table → individual command handlers
---
## References
- NDK39: `exec/devices.h`, `exec/io.h`, `devices/trackdisk.h`
- `06_exec_os/io_requests.md` — IORequest structure and dispatch
- `10_devices/trackdisk_device.md` — TD_* command codes
- Kickstart 3.1 ROM dump (required for disassembly)

View file

@ -0,0 +1,183 @@
[← Home](../README.md) · [Reverse Engineering](README.md)
# Compiler Fingerprints — Identifying the Toolchain
## Overview
Knowing which compiler produced an Amiga binary dramatically reduces reverse engineering effort. Each compiler has distinctive code generation patterns at the function prologue, epilogue, calling sequence, and global variable access levels.
---
## SAS/C 6.x Fingerprints
### Function Prologue
```asm
; Classic SAS/C stack frame with A5 as frame pointer:
LINK A5, #-N ; allocate N bytes on stack
MOVEM.L D2-D7/A2-A4, -(SP) ; save callee-saved registers
```
Alternatively (small functions):
```asm
LINK A5, #0 ; minimal frame (no locals)
MOVE.L D2, -(SP) ; save only used regs
```
### Function Epilogue
```asm
MOVEM.L (SP)+, D2-D7/A2-A4 ; restore registers (reverse order)
UNLK A5 ; deallocate stack frame
RTS ; return
```
### Global Variable Access
```asm
; SAS/C: absolute addressing (with HUNK_RELOC32):
MOVE.L $0000BEEF, D0 ; absolute address — gets relocated
MOVEA.L _DOSBase, A6 ; load from global via absolute ref
```
### Library Calls
```asm
MOVEA.L _DOSBase, A6 ; always load from named global
JSR -48(A6) ; Write LVO
```
### Identifying Strings
Look for:
- `"dos.library"` string in DATA hunk — opened by startup
- `"SAS/C"` or `"SAS C"` in the ID string of any custom library written with SAS/C
- The startup `_ReturnCode` variable name in HUNK_SYMBOL
---
## GCC (m68k-amigaos / bebbo) Fingerprints
### Function Prologue
```asm
; GCC: no frame pointer (default -fomit-frame-pointer)
MOVEM.L D2/D3/A2, -(SP) ; save only actually-used regs
; (no LINK instruction)
```
Or with frame pointer (`-fno-omit-frame-pointer`):
```asm
LINK A6, #-N ; GCC uses A6 as frame pointer (not A5!)
MOVEM.L D2/A2, -(SP)
```
> [!NOTE]
> GCC uses **A6** as frame pointer when frame pointers are enabled. SAS/C uses **A5**. This is the primary disambiguation between the two.
### Function Epilogue
```asm
MOVEM.L (SP)+, D2/D3/A2 ; restore
RTS
; (no UNLK — GCC prefers to adjust SP directly)
```
### Global Variable Access (PC-relative)
```asm
; GCC -fpic: PC-relative access to globals:
LEA _SysBase(PC), A0 ; PC-relative address of global
MOVEA.L (A0), A6 ; dereference to get library base
; Alternative (without PIC):
MOVEA.L (_DOSBase).L, A6 ; absolute with reloc (similar to SAS/C)
```
### Library Calls
```asm
; GCC inline stubs emit 16-bit short JSR:
JSR -198(A6) ; same visual result as SAS/C
```
But the surrounding code differs:
```asm
; GCC: tighter register use, less stack traffic
MOVEA.L (_SysBase).L, A6
MOVE.L #$400, D0 ; byteSize
MOVE.L #2, D1 ; MEMF_CHIP
JSR -198(A6) ; AllocMem
```
### Identifying Strings
- `"libnix"` or `"clib2"` version strings in DATA hunk
- GCC version string in `.comment` section (if present): `"GCC 6.5.0b ..."`
- `__main`, `__exit`, `__parse_args` as function names from HUNK_SYMBOL
---
## VBCC Fingerprints
VBCC is the most common modern AmigaOS compiler and produces very clean, standards-compliant code.
### Function Prologue
```asm
; VBCC: highly similar to GCC, no LINK by default
MOVEM.L D2-D5/A2-A3, -(SP) ; exact set of used regs
```
### Distinguishing VBCC from GCC
| Pattern | GCC | VBCC |
|---|---|---|
| `__saveds` keyword | Yes (some stubs) | Yes |
| Tail calls via JMP | Rare | Common |
| Stack checking | Optional (`-stack-check`) | Optional |
| `move.l #imm, An` | `movea.l #imm, An` | Same |
| BRA to epilogue | Sometimes | Common |
| register int a0 | Supported | Supported |
VBCC often generates more BRA→epilogue patterns where GCC inlines the epilogue code.
### Identifying Strings
- `"vbcc"` in any metadata strings
- VBCC version string: `"vbcc (c) in 1995-2020 by Volker Barthelmann"`
---
## Aztec C / Manx C
Rare but present in 1.x/2.x era software. Distinctive:
```asm
; Aztec C: uses A4 as small-data register (AmigaOS __far model)
MOVEA.L _DOSBase, A6 ; absolute refs
MOVE.L A4, -(SP) ; A4 preserved (small-data base)
```
Aztec C often uses a different calling convention for internal functions — examine carefully before assuming standard lib-call convention.
---
## Assembler-Only Code
Some core library routines and demos are pure assembly. Identifying features:
- No compiler prologue pattern
- `MOVEM.L` register lists tend to be maximally specified
- Copper/blitter setup code appears directly
- May use `SECTION` macros instead of implicit hunk ordering
---
## Quick Fingerprint Checklist
```
□ Does function prologue use LINK A5? → SAS/C
□ Does function prologue use LINK A6? → GCC with -fno-omit-frame-pointer
□ No LINK at all, just MOVEM.L? → GCC/VBCC (check other patterns)
□ PC-relative globals (LEA x(PC))? → GCC -fpic or VBCC
□ Absolute globals + HUNK_RELOC32? → SAS/C or GCC without -fpic
□ HUNK_SYMBOL has __main, __exit? → GCC/libnix
□ HUNK_SYMBOL has _c_start, _main? → SAS/C
```
---
## References
- SAS/C 6.x Programmer's Guide — code generation appendix
- GCC m68k-amigaos port (bebbo): https://github.com/bebbo/amiga-gcc
- VBCC manual: http://www.ibaug.de/vbcc/doc/vbcc.html
- Aztec C 68k manual (archive.org)

View file

@ -0,0 +1,110 @@
[← Home](../../README.md) · [Reverse Engineering](../README.md)
# Enforcer and MungWall — Memory Violation Tracing
## Overview
**Enforcer** (by Michael Sinz) and **MungWall** are the two canonical Amiga memory debugging tools. They catch illegal memory accesses and heap corruption at runtime, providing the equivalent of AddressSanitizer for AmigaOS.
---
## Enforcer
Enforcer uses the 68020+ MMU (or software patching on 68000) to trap accesses to:
- Address `$0000$07FF` (lower 2 KB — reserved vectors and exec structures)
- Odd-addressed word/longword reads
- Accesses above the installed RAM
- Writes to ROM addresses
### Installation
```
; AmigaOS Shell:
run enforcer
; or for logging:
run enforcer QUIET LOG enforcer.log
```
Enforcer patches the `BusError` exception vector (`$8`). Any illegal access causes a bus error, which Enforcer catches, logs, and (usually) continues.
### Output Format
```
ENFORCER HIT: by Unknown (Task: "DPaint" at $001234AB)
Program Counter: $0023AB12
Address Accessed: $0000012C (read longword)
Stack Dump: $001234C0 $0001A2B4 ...
```
- **Program Counter** — instruction that caused the hit
- **Address Accessed** — illegal address
- Cross-reference PC against `HUNK_SYMBOL` names or IDA disassembly
### Common Causes
| Hit Pattern | Likely Cause |
|---|---|
| Access to `$0$3FF` | NULL pointer dereference |
| Access to `$4` (SysBase) without read | Null exec base |
| Odd address read (word/long) | Misaligned pointer |
| Access to `$B80000$BFFFFF` | CIA access without correct alignment |
| Write to ROM `$F80000+` | Write to Kickstart ROM |
---
## MungWall
MungWall fills `AllocMem()` allocations with a known pattern (`$ABADCAFE`) and adds guard longwords before and after each block (`$DEADBEEF`). On `FreeMem()`, it verifies the guards.
### What It Catches
- **Heap underrun** — write before the allocated block (guard before = corrupted)
- **Heap overrun** — write past the end of block (guard after = corrupted)
- **Use after free** — block is filled with `$DEADBEEF` on free; reads from it will fail if Enforcer is also running
### Installation
```
run mungwall
```
### Output on Corruption
```
MUNGWALL: Block $001A2000 (size 128) has been overwritten!
Header guard: OK
Trailer guard: CORRUPTED at +132
Caller: $0023BC44 (FreeMem called from here)
```
---
## Combined Workflow
1. `run mungwall` first — patches AllocMem/FreeMem
2. `run enforcer` — adds MMU-level illegal access detection
3. Launch the suspect program
4. Any crash produces Enforcer + MungWall output on the serial port / `enforcer.log`
5. Cross-reference the PC value with `HUNK_SYMBOL` or IDA to find the exact line
---
## Serial Port Logging
Both tools output via `kprintf` to serial port (115200 8N1). Capture on host:
```bash
# macOS / Linux:
screen /dev/cu.usbserial-XXXX 115200
# or
minicom -D /dev/cu.usbserial-XXXX -b 115200
```
---
## References
- Enforcer: Michael Sinz — available on Aminet (`util/misc/Enforcer.lha`)
- MungWall: original CBM debug tool, available on Aminet
- `dynamic/serial_debug.md` — serial output setup
- *Amiga ROM Kernel Reference Manual: Libraries* — exec memory management

View file

@ -0,0 +1,132 @@
[← Home](../../README.md) · [Reverse Engineering](../README.md)
# Live Memory Probing
## Overview
Live memory probing on a running Amiga means directly reading exec structures — `SysBase`, `LibList`, `TaskReady`, `MemList` — to observe system state without a traditional debugger.
---
## SysBase: The Root of Everything
`SysBase` is always at absolute address `$4` (a pointer to the `ExecBase` structure):
```c
struct ExecBase *SysBase = *((struct ExecBase **)4);
printf("exec version: %d.%d\n",
SysBase->LibNode.lib_Version,
SysBase->LibNode.lib_Revision);
```
In assembly:
```asm
MOVEA.L 4.W, A6 ; A6 = SysBase (exec.library base)
MOVE.W ($16,A6), D0 ; lib_Version
MOVE.W ($18,A6), D1 ; lib_Revision
```
---
## Walking the Library List
```c
struct Node *n = SysBase->LibList.lh_Head;
while (n->ln_Succ != NULL) {
struct Library *lib = (struct Library *)n;
printf("%-30s v%d.%d opens=%d\n",
lib->lib_Node.ln_Name,
lib->lib_Version, lib->lib_Revision,
lib->lib_OpenCnt);
n = n->ln_Succ;
}
```
This enumerates all currently loaded libraries. Useful for:
- Finding if a target library is loaded
- Reading `lib_OpenCnt` to detect if your hook is installed
- Checking `lib_Flags & LIBF_DELEXP` (expunge pending)
---
## Reading `lib_OpenCnt` Live
```c
/* Check if bsdsocket.library is loaded and its open count */
struct Library *base = FindName(&SysBase->LibList, "bsdsocket.library");
if (base) {
printf("bsdsocket: OpenCnt=%d, Version=%d\n",
base->lib_OpenCnt, base->lib_Version);
}
```
`FindName` scans `ln_Name` in a linked list — it is an exec function at LVO 276.
---
## Memory Region Map
`SysBase->MemList` lists all memory regions:
```c
struct MemHeader *mh = (struct MemHeader *)SysBase->MemList.lh_Head;
while (mh->mh_Node.ln_Succ) {
printf("Region: %s %08lx%08lx free=%ld\n",
mh->mh_Node.ln_Name,
(ULONG)mh->mh_Lower,
(ULONG)mh->mh_Upper,
mh->mh_Free);
mh = (struct MemHeader *)mh->mh_Node.ln_Succ;
}
```
Output example:
```
Region: chip memory $000000$1FFFFF free=524288
Region: fast memory $200000$9FFFFF free=6291456
```
---
## Task List Inspection
```c
/* Running tasks: */
Forbid();
struct Task *t = (struct Task *)SysBase->TaskReady.lh_Head;
while (t->tc_Node.ln_Succ) {
printf("Task: %-20s pri=%d state=%d\n",
t->tc_Node.ln_Name,
t->tc_Node.ln_Pri,
t->tc_State);
t = (struct Task *)t->tc_Node.ln_Succ;
}
Permit();
```
`Forbid()` / `Permit()` are mandatory — the task list must not change while walking it.
---
## Patching Memory Live (Surgical Writes)
For RE/patching: direct longword write to an OS structure:
```c
/* Example: force a library's version to 99 */
Forbid();
target_lib->lib_Version = 99;
Permit();
```
> [!CAUTION]
> Direct memory writes to OS structures bypass all synchronization. Always use `Forbid()` at minimum; use `Disable()` if modifying interrupt-visible data.
---
## References
- NDK39: `exec/execbase.h`, `exec/memory.h`, `exec/tasks.h`
- `06_exec_os/exec_base.md` — full ExecBase offset table
- `06_exec_os/memory_management.md` — MemHeader structure
- `05_reversing/dynamic/setfunction_patching.md` — Forbid/Permit patterns

View file

@ -0,0 +1,118 @@
[← Home](../../README.md) · [Reverse Engineering](../README.md)
# Serial Debugging — kprintf and Serial Output
## Overview
The Amiga's built-in serial port is the primary low-level debugging channel. `kprintf()` (kernel printf) and `RawPutChar()` write directly to the serial hardware, bypassing `dos.library` and working even from interrupt context or before OS initialization.
---
## `kprintf()` — Kernel Printf
`kprintf()` is a ROM debug function present in Kickstart 1.3 and later debug ROMs. It formats a string and outputs each character via `RawPutChar`.
```c
/* Prototype (exec internal, not in NDK — declare manually): */
void kprintf(const char *fmt, ...);
/* Arguments in: D1=fmt, stack args (unlike standard AmigaOS register ABI) */
```
### Calling `kprintf` from Assembly
```asm
MOVEA.L 4.W, A6 ; SysBase
LEA _fmt_str(PC), A0 ; format string
MOVE.L A0, -(SP) ; push as stack argument
MOVE.L A0, D1 ; some implementations use D1
JSR (-$F0,A6) ; RawDoFmt or debug rom entry
; OR for ROM debug builds:
JSR _kprintf
```
> [!NOTE]
> `kprintf` is **not available** in standard Kickstart 3.1 release ROMs. Use `debug.lib` stubs (`dprintf`) or `RawDoFmt + RawPutChar` instead.
---
## `RawDoFmt` + `RawPutChar` — Universal Approach
This works on **all** Kickstart versions (1.2+):
```c
/* Format into a buffer and output via RawPutChar */
static void serial_putchar(UBYTE c, APTR dummy) {
/* write directly to serial data register */
volatile UWORD *SERDATR = (UWORD *)0xDFF018;
volatile UWORD *SERDATW = (UWORD *)0xDFF030;
volatile UWORD *SERDATSTAT;
/* Wait for TBE (transmit buffer empty) */
while (!(*SERDATR & 0x2000));
*SERDATW = 0x0100 | c; /* 8 bits + start bit */
}
void dbg_printf(const char *fmt, ...) {
UBYTE buf[256];
va_list args;
va_start(args, fmt);
/* RawDoFmt(fmt, args, putChar, buf) */
RawDoFmt((STRPTR)fmt, &args,
(VOID (*)())serial_putchar, buf);
va_end(args);
}
```
Or simpler — write to the serial hardware directly:
```c
static void SerPutChar(UBYTE c) {
while (!(*((volatile UWORD *)0xDFF018) & 0x2000)); /* wait TBE */
*((volatile UWORD *)0xDFF030) = 0x0100 | c;
}
```
---
## `debug.lib` (SAS/C)
SAS/C ships `debug.lib` providing `dprintf`:
```c
#include <debug.h>
dprintf("mylib: Open called, name=%s\n", name);
```
Output goes to the serial port at the rate set by SERPER (default 9600 baud on startup, 115200 if set).
---
## Setting Baud Rate
```c
/* Set serial to 115200 baud (PAL, 3.546895 MHz clock): */
/* SERPER = (clock / (16 * baud)) - 1 */
/* = (3546895 / (16 * 115200)) - 1 = 0 */
volatile UWORD *SERPER = (UWORD *)0xDFF032;
*SERPER = 0x0000; /* 115200 on PAL */
```
---
## Host-Side Capture
```bash
# macOS (USB-serial adapter):
screen /dev/cu.usbserial-XXXX 115200
# or:
stty -f /dev/cu.usbserial-XXXX 115200 raw && cat /dev/cu.usbserial-XXXX
```
MiSTer FPGA: the UART bridge is exposed on the MiSTer IO board or via the DE10-Nano UART.
---
## References
- NDK39: `exec/execbase.h``RawDoFmt`, `RawPutChar` LVOs
- `01_hardware/ocs_a500/paula_serial.md` — SERPER, SERDATR, SERDATW register details
- Aminet: `debug/misc/dprintf.lha`

View file

@ -0,0 +1,130 @@
[← Home](../../README.md) · [Reverse Engineering](../README.md)
# SetFunction — Hooking Library Vectors at Runtime
## Overview
`SetFunction()` is the official AmigaOS mechanism for **patching a library's JMP table** at runtime. It installs a custom function at a given LVO, replacing the original, and returns the old function pointer so a trampoline can be constructed.
---
## `SetFunction()` API
```c
/* exec/execbase.h */
APTR SetFunction(struct Library *library, LONG funcOffset, APTR newFunction);
/* Returns: pointer to OLD function */
```
- `library` — target library base (e.g., `DOSBase`)
- `funcOffset` — negative LVO offset (e.g., `-30` for `dos.library Open`)
- `newFunction` — your replacement function
---
## Installing a Hook
```asm
; Example: hook dos.library Write() at LVO -48
MOVEA.L _SysBase, A6
JSR (-120,A6) ; Forbid() — prevent preemption during patch
MOVEA.L _DOSBase, A1
MOVE.L #-48, A0 ; LVO for Write
LEA _my_write(PC), A2
JSR (-420,A6) ; SetFunction(DOSBase, -48, &my_write)
MOVE.L D0, _orig_write ; save original function pointer
JSR (-126,A6) ; Permit()
```
### C equivalent:
```c
static APTR orig_write;
void install_hook(void) {
Forbid();
orig_write = SetFunction((struct Library *)DOSBase, -48,
(APTR)my_write_hook);
Permit();
}
```
---
## Writing a Trampoline
The hook function must:
1. Perform its instrumentation
2. Call the original via the saved pointer
3. Return with the original return value in D0
```asm
_my_write:
; D1 = file handle, D2 = buffer, D3 = length (Write args)
MOVEM.L D0-D7/A0-A6, -(SP) ; save all (we may corrupt anything)
; ... instrumentation: log args, patch buffer, etc. ...
MOVEM.L (SP)+, D0-D7/A0-A6
MOVEA.L _orig_write, A0
JMP (A0) ; jump to original — not JSR; let original RTS
```
In C (with `__asm` constraints):
```c
LONG __asm my_write_hook(register __d1 BPTR fh,
register __d2 APTR buf,
register __d3 LONG len) {
/* instrumentation */
return ((LONG (*)(BPTR,APTR,LONG))orig_write)(fh, buf, len);
}
```
---
## Restoring on Exit
**Critical:** Always restore the original function before the program exits. Failure leaves a dangling pointer in the library JMP table, causing crashes for any subsequent users of the library.
```c
void remove_hook(void) {
Forbid();
SetFunction((struct Library *)DOSBase, -48, orig_write);
Permit();
}
/* Register with atexit: */
atexit(remove_hook);
```
---
## Thread Safety Considerations
- `Forbid()` / `Permit()` disable task switching — keep the window minimal
- If the hook itself calls OS functions, use `Disable()` / `Enable()` instead only when interrupts must be excluded
- Hooks are system-global — all tasks using the library will go through your hook
---
## Common Use Cases in RE
| Use | Hook | LVO |
|---|---|---|
| Trace file access | `dos.library Open` | 30 |
| Intercept writes | `dos.library Write` | 48 |
| Monitor memory allocation | `exec.library AllocMem` | 198 |
| Log task creation | `exec.library AddTask` | 282 |
| Spy on library opens | `exec.library OpenLibrary` | 552 |
---
## References
- NDK39: `exec/execbase.h`
- ADCD 2.1: `SetFunction` autodoc
- `05_reversing/dynamic/live_memory_probing.md` — SysBase structure access
- *Amiga ROM Kernel Reference Manual: Libraries* — SetFunction chapter

190
05_reversing/ida_setup.md Normal file
View file

@ -0,0 +1,190 @@
[← Home](../README.md) · [Reverse Engineering](README.md)
# IDA Pro Setup for Amiga 68k Binaries
## Requirements
| Component | Version / Notes |
|---|---|
| IDA Pro | 7.0+ (7.5+ recommended for Hex-Rays decompiler quality) |
| Processor module | M68k — included in IDA standard install |
| HUNK loader | Included in some IDA builds; community plugin if absent |
| Hex-Rays decompiler | 68k decompiler license required for pseudocode |
---
## Step 1: Install the HUNK Loader Plugin
IDA does not ship with an Amiga HUNK loader in all versions. The community loader is available at:
- https://github.com/wiemerc/ida-amiga (Python-based, IDA 7.x)
- Alternative: load as raw binary at base address 0, then manually define hunk segments
### Installing the plugin:
```bash
cp amiga_hunk.py ~/.idapro/plugins/ # macOS/Linux
cp amiga_hunk.py %APPDATA%\Hex-Rays\IDA Pro\plugins\ # Windows
```
Restart IDA. The loader appears in the format list when opening `.library`, `.device`, or executables with `$3F3` magic.
---
## Step 2: Processor Configuration
When loading, select:
- **Processor**: Motorola 680x0
- **Variant**: 68020 for A1200/A4000 targets, 68000 for A500 targets
- **Endianness**: Big-endian (automatic for 68k)
Changing processor after load:
`Edit → Plugins → Change processor type`
---
## Step 3: Segment Setup (Manual Load Fallback)
If not using the HUNK plugin, define segments manually after loading as raw binary:
```python
# IDA Python: create segments for a 2-hunk binary
# (code at 0, data at 0x1234)
import idc, idaapi
CODE_BASE = 0x00000000
DATA_BASE = 0x00001234
idc.add_segm(0, CODE_BASE, CODE_BASE + 0x1230, "CODE", "CODE")
idc.add_segm(0, DATA_BASE, DATA_BASE + 0x200, "DATA", "DATA")
idc.set_segm_class(idc.get_segm_attr(CODE_BASE, idc.SEGATTR_SEL), "CODE")
```
---
## Step 4: Define SysBase and Library Bases
Tell IDA about global library pointers so it can track A6:
```python
# Mark $00000004 as SysBase (exec pointer)
idc.create_dword(4)
idc.set_name(4, "SysBase", idc.SN_NOWARN)
idc.set_cmt(4, "exec.library base pointer (absolute address 4)", 0)
```
For each library base found during analysis:
```python
idc.set_name(ea_of_libbase_var, "_DOSBase", idc.SN_NOWARN)
```
---
## Step 5: Apply FLIRT Signatures
FLIRT (Fast Library Identification and Recognition Technology) signatures identify known library startup and runtime functions. Amiga-specific signature files:
- `m68k_amiga_sasc6.sig` — SAS/C 6.x standard library
- `m68k_amiga_gcc_libnix.sig` — GCC libnix
- `m68k_amiga_vbcc.sig` — VBCC
Apply via: `File → Load file → FLIRT signature file`
If no prebuilt sigs are available, create them with IDA's PELF tool from known `.lib` files.
---
## Step 6: Import AmigaOS Types
Apply AmigaOS structure definitions:
### Option A: Type Library (.til)
If an AmigaOS `.til` is available:
```
View → Open Subviews → Type Libraries → Insert → select amigaos.til
```
### Option B: Parse Headers Directly
```
File → Load file → Parse C header file
```
Load NDK39 headers (adjust path to your NDK location):
```
NDK39/include/exec/execbase.h
NDK39/include/dos/dosextens.h
NDK39/include/graphics/gfxbase.h
```
Set pre-processor defines:
```c
#define __AMIGA__
#define __mc68000__
```
---
## Step 7: Annotate JMP Table Calls
Run the LVO annotation script:
```python
import idautils, idc, idaapi, re
# Build LVO→name dict from NDK fd files (partial)
EXEC_LVO = {
-198: "AllocMem", -210: "FreeMem",
-282: "AddTask", -288: "RemTask",
-366: "PutMsg", -372: "GetMsg",
-552: "OpenLibrary",-414: "CloseLibrary",
-420: "SetFunction",-624: "CopyMem",
# ... extend from lvo_table.md
}
def annotate_lvos():
for seg_ea in idautils.Segments():
for func_ea in idautils.Functions(seg_ea, idc.get_segm_end(seg_ea)):
for ea in idautils.FuncItems(func_ea):
if idc.print_insn_mnem(ea).lower() == 'jsr':
op = idc.print_operand(ea, 0)
m = re.match(r'(-\d+)\(A6\)', op, re.IGNORECASE)
if m:
lvo = int(m.group(1))
name = EXEC_LVO.get(lvo)
if name:
idc.set_cmt(ea, f"exec: {name}", 0)
annotate_lvos()
```
---
## Step 8: Hex-Rays Decompiler Tips for 68k
The Hex-Rays 68k decompiler needs type information to produce clean pseudocode:
1. **Set function types** — mark return type and argument registers for library call wrappers
2. **Suppress spurious variables** — many D-register temps appear; use `Collapse variable` or retype
3. **Add `__asm` register hints** for known argument registers
Example — marking a library function prototype:
```c
// In IDA Local Types:
APTR __cdecl AllocMem_wrap(ULONG byteSize, ULONG requirements);
```
Then apply to call sites via `Y` (set type) on the JSR instruction.
---
## References
- IDA Pro 7.x documentation — processor modules, FLIRT
- ida-amiga plugin: https://github.com/wiemerc/ida-amiga
- Ghidra Amiga plugin: https://github.com/lab313ru/ghidra_amiga_ldr
- Ghidra m68k fixer: https://github.com/lab313ru/m68k_fixer
- BartmanAbyss Ghidra Amiga: https://github.com/BartmanAbyss/ghidra-amiga — Amiga HUNK loader + helpers for Ghidra
- IDA Pro m68k extensions: https://github.com/LucienMP/idapro_m68k — GDB step-over, type info
- NDK39: header files for type import

217
05_reversing/methodology.md Normal file
View file

@ -0,0 +1,217 @@
[← Home](../README.md) · [Reverse Engineering](README.md)
# RE Methodology — AmigaOS HUNK Binaries
## Phase 1: Initial Triage
### 1.1 Identify the File Type
```bash
xxd target.library | head -4
```
| First 4 bytes | Type |
|---|---|
| `00 00 03 F3` | Executable (HUNK_HEADER) |
| `00 00 03 E7` | Object file (HUNK_UNIT) |
| `70 FF 60 FA` | Resident tag (RomTag) |
### 1.2 Dump HUNK Structure
```bash
hunkinfo target.library
```
Expected output:
```
Hunk 0: CODE size=$1234 offset=$00012000
Hunk 1: DATA size=$0200 offset=$00013240
Hunk 2: BSS size=$0400
Symbols: 45
Relocs: 127
```
### 1.3 Verify Library Header
For a `.library` file, hunk 0 should start with a `RomTag` (resident module tag):
```
$4AFC ; RT_MATCHWORD (hex: MOVEQ #0, D0 in context)
```
Real `RomTag` starts at word `$4AFC`:
```asm
_romtag:
DC.W RTC_MATCHWORD ; $4AFC
DC.L _romtag ; RT_MATCHTAG (self-pointer)
DC.L _endskip ; RT_ENDSKIP
DC.B RTF_AUTOINIT ; RT_FLAGS
DC.B VERSION ; RT_VERSION
DC.B NT_LIBRARY ; RT_TYPE
DC.B PRIORITY ; RT_PRI
DC.L _libname ; RT_NAME
DC.L _idstring ; RT_IDSTRING
DC.L _inittable ; RT_INIT (or function)
```
---
## Phase 2: Load in IDA Pro
### 2.1 Load the File
File → New → select the binary → select "Amiga HUNK" format (or m68k raw if plugin not available).
If using the HUNK plugin:
- Hunks are mapped to segments: `CODE0`, `DATA0`, `BSS0`, etc.
- HUNK_SYMBOL entries become IDA names automatically
- HUNK_RELOC32 become IDA fixups
### 2.2 Set Processor
`Options → General → Processor type = Motorola 680x0`
For A1200/A4000 targets, enable `68020` or `68040` instruction sets.
### 2.3 Apply Library Type Information
Load the AmigaOS TIL (Type Information Library) if available:
- `File → Load file → FLIRT signature file` — use Amiga-specific FLIRT sigs
- Or manually: `View → Open Subviews → Type Libraries` → load `amigaos.til`
---
## Phase 3: Locate the Library Base
For a `.library` file, the library base is created by `MakeLibrary()` at runtime. In the static binary, look for:
```asm
; InitTable for the library:
DC.L _libsize ; LIB_POSSIZE (struct size)
DC.L _funcTable ; function array pointer
DC.L _dataTable ; data init table (or NULL)
DC.L _initCode ; init function
```
The function table is an array of absolute function pointers terminated by `-1`:
```asm
_funcTable:
DC.L _Open
DC.L _Close
DC.L _Expunge
DC.L _Reserved
DC.L _MyFunc1
DC.L _MyFunc2
...
DC.L -1 ; terminator
```
This is the easiest way to find all library functions from the static binary.
---
## Phase 4: Identify Calling Conventions
### 4.1 Find Library Calls
Search for the pattern `JSR -N(A6)` or `JSR -N(A5)` (some code uses A5):
In IDA, search for instruction text: `jsr -`
Each hit is a library call. The register `A6` (or A5) holds the library base at that point.
### 4.2 Trace Library Base
Trace backwards from the JSR to find where A6 was loaded:
```asm
MOVEA.L _DOSBase, A6 ; most common: global variable
JSR -48(A6) ; Write
MOVEA.L D0, A6 ; from return value of OpenLibrary
JSR -30(A6) ; Open
MOVEA.L 4.W, A6 ; SysBase directly
JSR -198(A6) ; AllocMem
```
### 4.3 Resolve the LVO
```python
# IDA Python: resolve a JSR -N(A6) to a function name
def resolve_lvo(ea):
insn = idc.print_insn_mnem(ea)
if insn not in ('jsr', 'JSR'):
return
op = idc.print_operand(ea, 0)
# op looks like "-48(A6)" or "-552(a6)"
import re
m = re.match(r'(-?\d+)\(A6\)', op, re.IGNORECASE)
if m:
lvo = int(m.group(1))
# Look up in your LVO table
name = LVO_TABLE.get(lvo, f"unknown_lvo_{-lvo}")
idc.set_cmt(ea, f"→ {name} LVO={lvo}", 0)
```
---
## Phase 5: Annotate and Name
### 5.1 Apply Library Function Names
Using the LVO tables from [lvo_table.md](../04_linking_and_libraries/lvo_table.md), annotate each JSR:
```python
# IDA script: annotate all JSR -N(A6) calls in exec context
import idautils, idc, idaapi
EXEC_LVOS = {
-198: "AllocMem",
-210: "FreeMem",
-282: "AddTask",
-552: "OpenLibrary",
# ... (full table from lvo_table.md)
}
for ea in idautils.CodeRefsTo(idc.get_name_ea_simple("_SysBase"), 0):
# For each reference to SysBase load, find subsequent JSR
pass # implement full trace
```
### 5.2 Name Functions
After resolving all library calls in a function, it becomes clear what the function does. Apply a meaningful name:
```
IDA: Press N on a function → rename to descriptive identifier
```
### 5.3 Define Structures
Apply AmigaOS structure types:
- `View → Open Subviews → Local Types` → import from AmigaOS headers
- Or manually define: `ExecBase`, `DosLibrary`, `MsgPort`, etc.
---
## Phase 6: Patch Analysis
Look for evidence of `SetFunction` patching:
1. JMP table entries pointing outside the library code segment
2. `LIBF_CHANGED` bit set in `lib_Flags`
3. Functions that immediately JSR to a stored old-function address
Look for timer/protection mechanisms:
1. Calls to `dos.library CurrentTime()` or `timer.device`
2. CIA timer reads (`$BFDE00` area)
3. Comparison of tick counts with a threshold
---
## References
- [ida_setup.md](ida_setup.md) — IDA configuration details
- [compiler_fingerprints.md](compiler_fingerprints.md) — compiler identification
- [lvo_table.md](../04_linking_and_libraries/lvo_table.md) — complete LVO tables
- NDK39: all `.fd` and `include/` files

View file

@ -0,0 +1,228 @@
[← Home](../README.md) · [Reverse Engineering](README.md)
# Patching Techniques for Amiga Binaries
## Overview
This document covers the methods used to surgically patch AmigaOS HUNK executables and libraries — without access to source code. All techniques operate on the binary file directly.
---
## Method 1: NOP Patching
Replace one or more instructions with `NOP` ($4E71) to eliminate a code path:
```python
# Python: NOP out 6 bytes at offset $1234
import struct
with open("target.library", "r+b") as f:
f.seek(0x1234)
f.write(b'\x4e\x71' * 3) # 3× NOP = 6 bytes
```
**Use case:** Disable a conditional check:
```asm
; Before: jumps to expiry code if timer > limit
CMPI.L #$12345678, D3
BHI.S .expired
; After: NOP the BHI (2 bytes)
CMPI.L #$12345678, D3
NOP
NOP
```
---
## Method 2: Branch Inversion
Flip the condition of a branch instruction to always take / never take a path:
| Original | Patched | Effect |
|---|---|---|
| `BEQ $4E43` | `BRA $4E43` | Always branch (was: branch if equal) |
| `BNE $4E43` | `BRA $4E43` | Always branch (was: branch if not equal) |
| `BEQ $4E43` | `NOP`×2 | Never branch (was: branch if equal) |
| `BHI $4E43` | `BLS $4E43` | Invert condition |
Branch instruction bytes:
```
67 xx BEQ.S (offset xx)
66 xx BNE.S
6E xx BGT.S
6F xx BLE.S
60 xx BRA.S
```
Change `BEQ.S` to `BRA.S`: replace first byte `$67` with `$60`.
---
## Method 3: Return Value Forcing
Force a function to always return a specific value:
```asm
; Original: complex check, returns 0 on failure
_CheckTimer:
LINK A5, #-4
... ; timer logic
UNLK A5
RTS
; Patched: always return 1 (success/valid)
_CheckTimer:
MOVEQ #1, D0 ; $7001 — MOVEQ #1, D0 (2 bytes)
RTS ; $4E75 (2 bytes)
```
`MOVEQ #1, D0` = `$70 $01` (2 bytes)
`RTS` = `$4E $75` (2 bytes)
Write 4 bytes at the function entry point.
---
## Method 4: JMP Redirect
Redirect a function call to a completely different address:
```asm
; Original call:
JSR _CheckAuth ; $4EB9 XXXXXXXX
; Patched to call our stub instead:
JSR _AlwaysTrue ; $4EB9 YYYYYYYY
```
Requires updating the relocation table if the new address is in a different hunk — simpler if both are in the same hunk (no reloc change needed).
---
## Method 5: Constant Replacement
Replace a comparison constant (timer limit, version check value, etc.):
```asm
; Original: 10 retry limit
CMPI.L #$0000000A, D3 ; $0A = 10
; Patched: effectively unlimited retries
CMPI.L #$7FFFFFFF, D3 ; max positive longword
```
Find the constant in the binary: `xxd target.library | grep "00 00 00 0a"`
Replace with new value at that offset.
---
## Method 6: Library JMP Table Patch (Runtime)
For patching a library's JMP table at runtime (not file-level):
```asm
; Install patch at LVO -30 of DOSBase:
MOVEA.L _DOSBase, A0 ; library base
MOVE.L #_MyOpen, -28(A0) ; overwrite address bytes of JMP.L at -30
; JMP.L = 4E F9 [AAAAAAAA]
; address is at offset -30+2 = -28
```
Note: this does not use `SetFunction()` — no `LIBF_CHANGED` flag. Used when you need silent patching.
---
## Updating Relocations After Patching
If a patched longword is in the HUNK_RELOC32 list, the loader will overwrite your patch at load time by adding the hunk base to it. You must either:
1. **Remove the reloc entry** for that offset from `HUNK_RELOC32`
2. **Adjust the stored value** so that after relocation it becomes the desired value
**Finding reloc entries to remove:**
```python
def remove_reloc_entry(data, hunk_offset, target_offset):
"""Remove a specific offset from HUNK_RELOC32 records."""
# Parse the file, find HUNK_RELOC32, remove the entry
# This requires a full HUNK parser — see hunk_parser.py
pass
```
---
## Automated Patcher Template (Python)
```python
#!/usr/bin/env python3
"""
Amiga HUNK Binary Patcher
"""
import struct
import shutil
import sys
class AmigaPatcher:
def __init__(self, path):
with open(path, 'rb') as f:
self.data = bytearray(f.read())
self.path = path
def find_pattern(self, pattern: bytes) -> list:
"""Find all occurrences of a byte pattern."""
results = []
idx = 0
while True:
idx = self.data.find(pattern, idx)
if idx == -1:
break
results.append(idx)
idx += 1
return results
def patch_bytes(self, offset: int, new_bytes: bytes, comment: str = ""):
old = self.data[offset:offset+len(new_bytes)]
print(f"[PATCH] {comment}")
print(f" Offset: {offset:#010x}")
print(f" Old: {old.hex()}")
print(f" New: {new_bytes.hex()}")
self.data[offset:offset+len(new_bytes)] = new_bytes
def nop(self, offset: int, count: int, comment: str = ""):
self.patch_bytes(offset, b'\x4e\x71' * count, f"NOP×{count}: {comment}")
def save(self, out_path: str):
with open(out_path, 'wb') as f:
f.write(self.data)
print(f"[SAVE] Written to {out_path}")
# Example usage:
if __name__ == "__main__":
p = AmigaPatcher("target.library")
# Patch 1: NOP out redundant range check
p.nop(0x1234, 3, "skip bounds validation BHI branch")
# Patch 2: Force version check to pass
p.patch_bytes(0x5678, bytes([0x70, 0x01, 0x4E, 0x75]),
"always return 1 from CheckVersion")
p.save("target_patched.library")
```
---
## Verification After Patching
1. **Checksum update**: Some libraries check `SumLibrary()` at init — may need to disable that check too
2. **Test on hardware**: Use the MiSTer or UAE emulator to verify the patched binary
3. **Regression test**: Ensure patched functions that are chained still work correctly
---
## References
- [setfunction.md](../04_linking_and_libraries/setfunction.md) — runtime patching
- [hunk_relocation.md](../03_loader_and_exec_format/hunk_relocation.md) — relocation interaction
- [case_studies/ramdrive_device.md](case_studies/ramdrive_device.md) — complete real-world example

View file

@ -0,0 +1,296 @@
[← Home](../../README.md) · [Reverse Engineering](../README.md)
# Identifying OS API Calls in Disassembly
## Background: How AmigaOS Library Calls Work
Before diving into identification techniques, it helps to understand the mechanics from first principles.
### What is a Shared Library?
On AmigaOS, a **shared library** is a block of code loaded into RAM once and shared by every program that needs it. Programs don't link the OS code into their own executable — they call it indirectly at runtime. This keeps executables small and allows the OS to be upgraded without relinking every application.
Examples: `dos.library`, `graphics.library`, `intuition.library`.
### What is a Library Base?
When you open a library, exec returns a pointer to the **library base** — a `struct Library` that lives in RAM. Immediately *before* this pointer (at negative offsets) sits the **JMP table**: a sequence of `JMP <address>` instructions, one per library function.
```
Memory layout:
lib_base - 30: JMP Open_impl ← first user function
lib_base - 24: JMP Reserved
lib_base - 18: JMP Expunge
lib_base - 12: JMP Close
lib_base - 6: JMP Open (standard)
lib_base + 0: struct Library ← pointer returned by OpenLibrary()
lib_base + N: private library data
```
Every program that wants to call `dos.library Open()` stores the library base somewhere and calls `JSR -30(A6)`, where A6 holds the library base.
---
## What is an LVO?
**LVO** stands for **Library Vector Offset**. It is the negative byte offset from the library base to a specific function's JMP table slot.
The formula is:
```
LVO = 6 × (slot_index + 1)
slot 0 (Open standard): 6
slot 1 (Close standard): 12
slot 2 (Expunge): 18
slot 3 (Reserved): 24
slot 4 (first user fn): 30 ← dos.library Open()
slot 5: 36 ← dos.library Close()
...
```
So `JSR -30(A6)` means "call the function at LVO 30 in the library whose base is in A6." Every unique LVO in every library maps to exactly one function.
### Why Negative Offsets?
The JMP table grows **downward** in memory from the library base. Using negative offsets means programs only need to store a single pointer (the library base) and derive all function entry points from it with a constant displacement. This is the same trick used by C++ vtables.
---
## What is an .fd File?
**`.fd` files** (Function Descriptor files) are part of the Amiga NDK (Native Developer Kit). They are simple text files that declare every public function in a library: its name, argument registers, and LVO (called the **bias** in `.fd` terminology).
### Example: `dos_lib.fd` (excerpt)
```
##base _DOSBase
##bias 30
##public
Open(name,accessMode)(d1,d2)
##bias 36
Close(file)(d1)
##bias 42
Read(file,buffer,length)(d1,d2,d3)
##bias 48
Write(file,buffer,length)(d1,d2,d3)
##bias 54
Input()(-)
##bias 60
Output()(-)
##bias 138
Delay(timeout)(d1)
```
Reading this:
- `##base _DOSBase` — the global variable that holds the library base
- `##bias 30` — the **positive** bias; the actual call offset is `30`
- `Open(name,accessMode)(d1,d2)` — function name, argument names, and the registers each argument goes in
So `##bias 30` means LVO `30`. When you see `JSR (-30,A6)` in disassembly and A6 holds `DOSBase`, that is `dos.library Open()`.
### Where are .fd files?
In the NDK39 distribution at:
```
NDK39/
fd/
dos_lib.fd
exec_lib.fd
graphics_lib.fd
intuition_lib.fd
...
```
They are plain text — open any with a text editor.
---
## The Canonical Call Pattern
Every AmigaOS library call in disassembly looks like this:
```asm
MOVEA.L (_DOSBase).L, A6 ; (1) load the library base into A6
JSR (-30,A6) ; (2) call function at LVO -30 = Open()
; D0 now contains the return value
```
Sometimes the base is loaded once and reused:
```asm
MOVEA.L (_DOSBase).L, A6
JSR (-30,A6) ; Open
...
; A6 still holds DOSBase — no reload needed
JSR (-48,A6) ; Write
```
And for `exec.library`, programs often use the fixed address `$4` directly:
```asm
MOVEA.L 4.W, A6 ; exec.library base is always at $4
MOVEQ #40, D0 ; minimum version
LEA _str_dos(PC), A1 ; "dos.library"
JSR (-552,A6) ; exec.library OpenLibrary(A1,D0)
MOVE.L D0, _DOSBase ; save result for later
```
---
## Step-by-Step: Tracing OS Calls in IDA Pro
### Step 1 — Find OpenLibrary calls at startup
Search for `JSR (-552,A6)` — that is always `exec.library OpenLibrary`. The instruction immediately before it loads A1 with a library name string.
```asm
LEA (_str_dos).L, A1 ; → xref this to see "dos.library"
MOVEQ #40, D0
MOVEA.L 4.W, A6
JSR (-552,A6) ; OpenLibrary("dos.library", 40)
MOVE.L D0, (_DOSBase).L ; ← label this global "_DOSBase"
```
Press `N` in IDA on the `_DOSBase` write to name the variable.
### Step 2 — Find all reads of that library base
Press `X` on `_DOSBase` to show all cross-references. Each xref is either a write (the open) or a read (before a JSR).
### Step 3 — Resolve each JSR to a function name
For each `JSR (-N,A6)` where A6 holds `_DOSBase`:
1. Look up `N` in `dos_lib.fd` under `##bias N`
2. Read the function name
3. Press `N` in IDA on the JSR instruction's displacement to annotate it
After annotation:
```asm
MOVEA.L (_DOSBase).L, A6
JSR (Open,A6) ; was: JSR (-30,A6)
```
### Step 4 — Note argument registers
From `dos_lib.fd`:
```
Open(name,accessMode)(d1,d2)
```
So immediately before the JSR:
- `D1` is loaded with the filename pointer
- `D2` is loaded with the access mode (`MODE_OLDFILE` = 1005, `MODE_NEWFILE` = 1006)
---
## Quick LVO Reference: dos.library
| LVO | Bias | Function | Args | Return |
|---|---|---|---|---|
| 30 | 30 | `Open` | D1=name, D2=mode | D0=BPTR handle (0=fail) |
| 36 | 36 | `Close` | D1=handle | — |
| 42 | 42 | `Read` | D1=handle, D2=buf, D3=len | D0=actual (1=fail) |
| 48 | 48 | `Write` | D1=handle, D2=buf, D3=len | D0=actual |
| 54 | 54 | `Input` | — | D0=stdin handle |
| 60 | 60 | `Output` | — | D0=stdout handle |
| 66 | 66 | `IoErr` | — | D0=last error code |
| 78 | 78 | `CreateDir` | D1=name | D0=lock |
| 84 | 84 | `CurrentDir` | D1=lock | D0=old lock |
| 90 | 90 | `Lock` | D1=name, D2=mode | D0=lock |
| 96 | 96 | `UnLock` | D1=lock | — |
| 102 | 102 | `DupLock` | D1=lock | D0=new lock |
| 108 | 108 | `Examine` | D1=lock, D2=fib | D0=bool |
| 120 | 120 | `ExNext` | D1=lock, D2=fib | D0=bool |
| 126 | 126 | `Info` | D1=lock, D2=infoblock | D0=bool |
| 132 | 132 | `Execute` | D1=string, D2=input, D3=output | D0=bool |
| 138 | 138 | `Delay` | D1=ticks | — |
| 144 | 144 | `DateStamp` | D1=datestamp | D0=datestamp |
| 150 | 150 | `Exit` | D1=returnCode | — |
| 156 | 156 | `LoadSeg` | D1=name | D0=seglist |
| 162 | 162 | `UnLoadSeg` | D1=seglist | — |
## Quick LVO Reference: exec.library (selected)
| LVO | Bias | Function | Args | Return |
|---|---|---|---|---|
| 6 | 6 | `Supervisor` | A5=func | — |
| 120 | 120 | `Forbid` | — | — |
| 126 | 126 | `Permit` | — | — |
| 132 | 132 | `Disable` | — | — |
| 138 | 138 | `Enable` | — | — |
| 168 | 168 | `FindTask` | A1=name | D0=task |
| 174 | 174 | `SetTaskPri` | A1=task, D0=pri | D0=old |
| 192 | 192 | `Signal` | A1=task, D0=signals | — |
| 198 | 198 | `AllocMem` | D0=size, D1=attrs | D0=ptr |
| 210 | 210 | `FreeMem` | A1=ptr, D0=size | — |
| 234 | 234 | `Wait` | D0=signals | D0=set |
| 270 | 270 | `AddPort` | A1=port | — |
| 276 | 276 | `FindName` | A0=list, A1=name | D0=node |
| 378 | 378 | `PutMsg` | A0=port, A1=msg | — |
| 384 | 384 | `GetMsg` | A0=port | D0=msg |
| 408 | 408 | `WaitPort` | A0=port | D0=msg |
| 420 | 420 | `SetFunction` | A1=lib, A0=lvo, D0=func | D0=old |
| 552 | 552 | `OpenLibrary` | A1=name, D0=ver | D0=base |
| 558 | 558 | `CloseLibrary` | A1=lib | — |
Full tables: [`04_linking_and_libraries/lvo_table.md`](../../../04_linking_and_libraries/lvo_table.md)
---
## Automated IDA Script
```python
# apply_dos_lvos.py — run from IDA's File → Script command
import idaapi, idc, idautils
DOS_LVO = {
-30: "Open", -36: "Close", -42: "Read", -48: "Write",
-54: "Input", -60: "Output", -66: "IoErr", -132: "Execute",
-138: "Delay", -156: "LoadSeg",-162: "UnLoadSeg",
}
EXEC_LVO = {
-120: "Forbid", -126: "Permit", -132: "Disable", -138: "Enable",
-198: "AllocMem", -210: "FreeMem",-234: "Wait",
-378: "PutMsg", -384: "GetMsg", -408: "WaitPort",
-420: "SetFunction", -552: "OpenLibrary", -558: "CloseLibrary",
}
def apply_lvos(lib_global_name, lvo_map):
ea = idc.get_name_ea_simple(lib_global_name)
if ea == idc.BADADDR:
print(f"Global {lib_global_name} not found")
return
lib_ptr = idc.get_wide_dword(ea)
for lvo, name in lvo_map.items():
jmp_ea = lib_ptr + lvo
# JMP ABS.L opcode: 4EF9, target at +2
target = idc.get_wide_dword(jmp_ea + 2)
if target != 0xFFFFFFFF:
idc.set_name(target, f"{lib_global_name[1:]}_{name}",
idaapi.SN_NOWARN)
print(f" {lvo:+5d} → {name} @ {target:#010x}")
apply_lvos("_DOSBase", DOS_LVO)
apply_lvos("_SysBase", EXEC_LVO)
```
---
## Identifying Unknown Library Calls
If you encounter `JSR (-N,A6)` and don't know which library A6 holds:
1. Trace A6 backward in IDA (`View → Register tracking`) to its last write
2. The write is `MOVEA.L (some_global).L, A6` — name that global
3. Trace *that* global backward to its `MOVE.L D0, ...` after an `OpenLibrary` call
4. The string argument to OpenLibrary names the library
5. Look up LVO `N` in the matching `.fd` file
---
## References
- NDK39: `fd/` directory — all library `.fd` files (plain text, open in any editor)
- `04_linking_and_libraries/lvo_table.md` — formatted LVO tables
- `static/library_jmp_table.md` — JMP table layout and IDA scripting
- `04_linking_and_libraries/fd_files.md``.fd` file format specification
- ADCD 2.1 Autodocs online: http://amigadev.elowar.com/read/ADCD_2.1/

View file

@ -0,0 +1,123 @@
[← Home](../../README.md) · [Reverse Engineering](../README.md)
# Static Analysis — HUNK Reconstruction
## Overview
Manually parsing a HUNK binary from a hex dump is a foundational Amiga RE skill. It reveals segment boundaries, symbol tables, and relocation data before any tool processing.
---
## Step 1 — Identify Magic and Header
```bash
xxd mybinary | head -8
```
```
00000000: 0000 03f3 ← HUNK_HEADER magic
00000004: 0000 0000 ← resident library list (always 0)
00000008: 0000 0003 ← num_hunks = 3
0000000c: 0000 0000 ← first_hunk = 0
00000010: 0000 0002 ← last_hunk = 2
00000014: 0000 0200 ← hunk 0: 0x200 longs = 0x800 bytes (code)
00000018: 0000 0020 ← hunk 1: 0x20 longs = 0x80 bytes (data)
0000001c: 0000 0010 ← hunk 2: 0x10 longs = 0x40 bytes (BSS)
```
Each size longword: **bits 3130** = memory type flag, **bits 290** = size in longs.
---
## Step 2 — Walk the Hunk Stream
After the header, scan longword-by-longword:
```
$000003E9 → HUNK_CODE: read next longword = size, then size*4 bytes
$000003EA → HUNK_DATA: same
$000003EB → HUNK_BSS: read size longword only (no data)
$000003EC → HUNK_RELOC32: read pairs until terminator 0
$000003F0 → HUNK_SYMBOL: read (name_len, name, value) until name_len=0
$000003F1 → HUNK_DEBUG: read size longword, skip size*4 bytes
$000003F2 → HUNK_END: advance to next hunk
```
### Grep for hunk boundaries
```bash
xxd mybinary | grep -E "0003 (e9|ea|eb|ec|f0|f1|f2|f3)"
```
---
## Step 3 — Extract HUNK_SYMBOL Table
```bash
# find HUNK_SYMBOL ($3F0)
python3 - <<'EOF'
import struct, sys
data = open("mybinary", "rb").read()
i = 0
while i < len(data) - 4:
tag = struct.unpack_from(">I", data, i)[0]
if tag == 0x3F0: # HUNK_SYMBOL
print(f"HUNK_SYMBOL at offset {i:#x}")
i += 4
while True:
nlen = struct.unpack_from(">I", data, i)[0]
if nlen == 0: break
name = data[i+4 : i+4+nlen*4].rstrip(b"\x00").decode("ascii","replace")
val = struct.unpack_from(">I", data, i+4+nlen*4)[0]
print(f" {name} = {val:#x}")
i += 4 + nlen*4 + 4
else:
i += 4
EOF
```
---
## Step 4 — Resolve HUNK_EXT Imports/Exports
In object files (HUNK_UNIT), `HUNK_EXT` carries import/export tables:
```python
# Simplified HUNK_EXT parser
elif tag == 0x3EF: # HUNK_EXT
i += 4
while True:
word = struct.unpack_from(">I", data, i)[0]
if word == 0: break
ext_type = (word >> 24) & 0xFF
nlen = word & 0x00FFFFFF
name = data[i+4 : i+4+nlen*4].rstrip(b"\x00").decode("ascii","replace")
i += 4 + nlen * 4
if ext_type in (1, 2): # EXT_DEF / EXT_ABS
val = struct.unpack_from(">I", data, i)[0]; i += 4
print(f" EXPORT {name} = {val:#x}")
elif ext_type == 0x81: # EXT_REF32
nrefs = struct.unpack_from(">I", data, i)[0]; i += 4
refs = struct.unpack_from(f">{nrefs}I", data, i); i += nrefs*4
print(f" IMPORT {name} @ {[hex(r) for r in refs]}")
```
---
## Step 5 — Annotating Reloc Patches in IDA
After loading the HUNK file in IDA:
1. `View → Open Subviews → Fixups` — lists all HUNK_RELOC32 patch sites
2. Press `F5` on a relocated longword to see the computed address
3. Use `Edit → Operand type → Offset (data segment)` to annotate as a pointer
IDA's Amiga loader applies relocations automatically, so all cross-hunk pointers show their final resolved addresses.
---
## References
- NDK39: `dos/doshunks.h`
- `hunk_format.md` — hunk type code reference
- `hunk_relocation.md` — HUNK_RELOC32 mechanics
- vlink documentation (HUNK appendix): http://sun.hasenbraten.de/vlink/

View file

@ -0,0 +1,142 @@
[← Home](../../README.md) · [Reverse Engineering](../README.md)
# Reconstructing Library JMP Tables
## Overview
Every AmigaOS library has a **JMP table** at negative offsets from its base pointer. Reconstructing this table maps LVOs to function names and is essential for identifying all OS calls made by a binary under analysis.
---
## JMP Table Layout
```
lib_base - N*6: JFF xxxx xxxx ; JMP to function N (6 bytes)
...
lib_base - 24: JMP Reserved()
lib_base - 18: JMP Expunge()
lib_base - 12: JMP Close()
lib_base - 6: JMP Open()
lib_base + 0: struct Library ; lib_Node, lib_Version, ...
```
Each entry is a 68k `JMP (abs.l)` — opcode `4EF9` followed by a 4-byte absolute address, totalling 6 bytes. Hence LVO = `6 × slot_index`.
---
## Finding the Library Base
### From SysBase LibList
The `exec.library` maintains a doubly-linked list at `SysBase→LibList`:
```c
struct ExecBase {
...
struct List LibList; /* offset +378 — list of open libraries */
...
};
/* Walk the list: */
struct Node *n = SysBase->LibList.lh_Head;
while (n->ln_Succ) {
struct Library *lib = (struct Library *)n;
printf("%s v%d\n", lib->lib_Node.ln_Name, lib->lib_Version);
n = n->ln_Succ;
}
```
### In IDA Pro
After loading, `SysBase` is at `$4`. Use `Edit → Segments → Create Segment` pointed at `$4` with type `WORD` to follow the pointer to `ExecBase`. Then navigate to `LibList` at offset `+0x17A` and walk the linked list.
---
## Reading the JMP Table in IDA
1. Know the library base address (e.g., `DOSBase` from the `OpenLibrary` result)
2. Navigate to `lib_base - 6` — first user function slot
3. IDA shows `JMP sub_XXXXXX` — the target is the actual function implementation
4. Rename each `sub_` with the function name from the LVO table
### Automated Script: `apply_lvo_names.py`
```python
import idaapi, idc
LVO_DOS = {
-30: "Open", # LVO -30 = Open(name, mode) d1/d2
-36: "Close",
-42: "Read",
-48: "Write",
-54: "Input",
-60: "Output",
-126: "WaitForChar",
-138: "Delay",
# ... extend from dos_lib.fd
}
DOS_BASE = idc.get_name_ea_simple("_DOSBase")
dos_ptr = idc.get_wide_dword(DOS_BASE)
for lvo, name in LVO_DOS.items():
jmp_entry = dos_ptr + lvo
# read the JMP target: opcode at jmp_entry is 4EF9, target at +2
target = idc.get_wide_dword(jmp_entry + 2)
idc.set_name(target, f"dos_{name}", idaapi.SN_NOWARN)
print(f"LVO {lvo:+d}: {name} → {target:#010x}")
```
---
## Mapping LVO → Function via `.fd` Files
NDK39 `.fd` files define the exact register assignments and bias (LVO offset):
```
## NDK39/fd/dos_lib.fd (excerpt)
##base _DOSBase
##bias 30
##public
Open(name,accessMode)(d1,d2)
##bias 36
Close(file)(d1)
##bias 42
Read(file,buffer,length)(d1,d2,d3)
##bias 48
Write(file,buffer,length)(d1,d2,d3)
```
The `##bias` value **is** the positive LVO — the actual call offset is `bias`.
---
## JSR LVO(A6) Pattern in Disassembly
```asm
; Typical OS call site in disassembly:
MOVEA.L (_DOSBase).L, A6
JSR (-30,A6) ; Open(d1=name, d2=mode)
; D0 = file handle (BPTR) or 0 on error
```
In IDA, this appears as `jsr ($fffffffe2,a6)` with displacement `-30` (`$FFFFFFE2` in two's complement 16-bit). Applying LVO names makes this `jsr (Open,a6)`.
---
## Common Library Bases and LVO Tables
See [`../../../04_linking_and_libraries/lvo_table.md`](../../../04_linking_and_libraries/lvo_table.md) for complete LVO offset tables for:
- `exec.library`
- `dos.library`
- `graphics.library`
- `intuition.library`
---
## References
- NDK39: `fd/` directory — all library `.fd` files
- `04_linking_and_libraries/lvo_table.md`
- ADCD 2.1: `Libraries_Manual_guide/`
- IDA Pro scripting: `idc.py` reference

View file

@ -0,0 +1,156 @@
[← Home](../../README.md) · [Reverse Engineering](../README.md)
# Compiler-Specific Code Generation Patterns
## Overview
Different Amiga compilers produce distinct code signatures. Recognising these helps quickly identify compiler origin, locate `main()`, and distinguish OS glue from application logic.
---
## SAS/C 6.x Patterns
### Function Prologue / Epilogue
```asm
; Non-leaf function with local vars:
LINK A5, #-N ; allocate N bytes of locals on stack
MOVEM.L D2-D7/A2-A3, -(SP) ; save preserved registers
...
MOVEM.L (SP)+, D2-D7/A2-A3
UNLK A5
RTS
; Leaf function (no locals, no preserved regs):
; — no LINK, pure computation, ends in RTS
```
### D0 Save Pattern
SAS/C saves D0 at the start of functions that need it later:
```asm
MOVE.L D0, -(SP) ; save return value from previous call
JSR another_func
MOVE.L (SP)+, D0 ; restore
```
### Register Argument Passing
SAS/C passes OS call args via `#pragma amicall` register placement. Inside application functions, SAS/C uses a **stack-based C ABI** (unlike OS calls):
```asm
; C function call in SAS/C: push args right-to-left
MOVE.L arg3, -(SP)
MOVE.L arg2, -(SP)
MOVE.L arg1, -(SP)
JSR _myfunction
ADDQ.L #12, SP ; clean args (caller cleanup)
```
### String Constants
SAS/C places string literals in the **data hunk**, referenced via absolute addresses requiring `HUNK_RELOC32`:
```asm
MOVE.L #_str_hello, D1 ; absolute address → RELOC32 entry
MOVEA.L _DOSBase, A6
JSR (-48,A6) ; Write(stdout, "hello", ...)
```
---
## GCC (m68k-amigaos / bebbo) Patterns
### PC-Relative String Access
GCC uses PC-relative addressing by default, eliminating most HUNK_RELOC32 entries:
```asm
LEA _str_hello(PC), A0 ; PC-relative — no reloc needed
```
### No Frame Pointer (Default)
```asm
; GCC -O2 leaf function:
MOVEM.L D2/A2, -(SP) ; only save what's used
...
MOVEM.L (SP)+, D2/A2
RTS
; No LINK/UNLK — pure register allocation
```
### GCC Function Prologues
```asm
; Non-leaf with GCC -fno-omit-frame-pointer:
LINK A6, #-N ; note: GCC uses A6 as frame pointer here
; (conflicts with OS library base usage — rare)
; More common with -O2:
SUBQ.L #N, SP ; allocate locals without frame pointer
```
### Integer Division / Modulo
GCC emits calls to `__divsi3`, `__modsi3` from `libgcc`:
```asm
JSR ___divsi3 ; 32-bit signed divide (libgcc helper)
; operands in D0:D1, result in D0
```
SAS/C uses the 68k `DIVS.L` instruction directly (available on 020+) or `DIVS.W`.
---
## VBCC Patterns
VBCC generates very tight code with minimal function overhead:
```asm
; VBCC typical function (no frame pointer, minimal saves):
MOVEM.L D2-D4, -(SP)
...
MOVEM.L (SP)+, D2-D4
RTS
```
VBCC's OS call inline expansion looks identical to GCC's inline-asm stubs.
---
## Distinguishing Compiler Artefacts from Logic
| Pattern | Compiler | Meaning |
|---|---|---|
| `LINK A5, #-N` | SAS/C | Function with locals |
| `LINK A6, #-N` | GCC (rare) | Frame pointer mode |
| `JSR ___divsi3` | GCC | Software 32-bit division |
| `DIVS.L D1, D0` | SAS/C (020+) | Hardware divide |
| `MULS.L D1, D0` | SAS/C (020+) | Hardware multiply |
| `LEA str(PC), A0` | GCC | PC-relative string ref |
| `MOVE.L #_str, D1` | SAS/C | Absolute string ref (reloc'd) |
| `JSR _main` | Startup | C main() entry point |
| `MOVE.L 4.W, A6` | Startup | SysBase load |
| `JSR -552(A6)` | Any | exec.library OpenLibrary |
---
## Locating `main()` via Startup Skip
After identifying the startup stub (`MOVE.L 4.W, A6``JSR _OpenLibraries`):
1. Find the first `JSR` or `BSR` after library opens
2. That target is `__main` or directly `_main`
3. If `__main`: follow its internal `JSR _main` call
4. Label the target `main` in IDA
---
## References
- SAS/C 6.x manual — code generation chapter
- GCC for m68k: https://github.com/bebbo/amiga-gcc
- VBCC manual: http://www.compilers.de/vbcc.html
- *Amiga ROM Kernel Reference Manual: Libraries* — register conventions

View file

@ -0,0 +1,123 @@
[← Home](../../README.md) · [Reverse Engineering](../README.md)
# String Cross-Reference Analysis
## Overview
String references are the fastest entry point into a disassembled Amiga binary. Library name strings, error messages, and format strings immediately reveal program intent and identify OS API usage patterns.
---
## Finding Library Name Strings
Every `OpenLibrary` call is preceded by a string reference. Search for `".library"`:
```bash
# Host: grep for library name strings in binary
strings mybinary | grep -i library
# → "dos.library", "graphics.library", "intuition.library", ...
```
In IDA:
1. `View → Open Subviews → Strings` (Shift+F12)
2. Search for `.library`
3. Press `X` on any result to see all cross-references
4. Each xref leads to a `LEA str(PC), A1` or `MOVE.L #str, A1` before a `JSR -552(A6)` (OpenLibrary)
---
## Tracing OpenLibrary Calls to Their Targets
```asm
; Pattern to find:
LEA (_str_dos).L, A1 ; "dos.library"
MOVEQ #36, D0 ; min version
MOVEA.L 4.W, A6 ; exec.library
JSR (-552,A6) ; OpenLibrary → D0 = DOSBase
MOVE.L D0, (_DOSBase).L ; store for later use
```
Xref `_str_dos` → find this block → identify the stored library base variable → label it `_DOSBase`.
---
## Using HUNK_SYMBOL Names as Seed Labels
If `HUNK_SYMBOL` is present (debug build), IDA auto-applies names. These seed labels help bootstrap analysis:
1. `View → Open Subviews → Names` → look for any `_` prefixed symbols
2. Named functions often call unnamed helpers nearby — work outward
3. String xrefs from named functions propagate names further
---
## Error Message Strings
Error/diagnostic strings reveal program flow:
```asm
; Common pattern:
LEA _err_nolib(PC), A0 ; "Can't open dos.library"
MOVEA.L _DOSBase, A6
JSR (-60,A6) ; Output() → D0 = stdout
MOVE.L D0, D1
LEA _err_nolib(PC), A2
MOVE.L A2, D2
MOVEQ #_err_nolib_end - _err_nolib, D3
JSR (-48,A6) ; Write(stdout, msg, len)
```
The error string tells you exactly what this code path handles.
---
## Format String Xref Analysis (printf)
SAS/C `printf` style calls via `dos.library VPrintf`:
```asm
MOVEA.L _DOSBase, A6
LEA _fmt_str(PC), A0 ; "Error: %ld\n"
MOVE.L A0, D1
MOVE.L A1, D2 ; varargs array
JSR (-954,A6) ; VPrintf()
```
Format strings like `"Error: %ld\n"` or `"Processing: %s"` reveal parameter types and function purpose.
---
## Workbench Title Strings
```asm
; Typical NewScreen/OpenScreen call sequence:
LEA _screen_title(PC), A0 ; "MyApp v1.0"
MOVE.L A0, (NewScreen+ns_Title)
```
Screen/window title strings appear in `intuition.library` `OpenScreen` / `OpenWindow` calls and give the product name.
---
## Automated String Map
Build a complete string inventory:
```python
# IDA script: map all string xrefs
for s in idautils.Strings():
text = str(idc.get_strlit_contents(s.ea, s.length, s.strtype))
refs = list(idautils.XrefsTo(s.ea))
if refs:
for ref in refs:
func = idc.get_func_name(ref.frm)
print(f"{s.ea:#x} [{text!r:40s}] ← {func or 'unknown'} @ {ref.frm:#x}")
```
---
## References
- IDA Pro: Strings subview (Shift+F12), Xrefs (X key)
- `static/api_call_identification.md` — resolving library base from string xrefs
- NDK39: `dos/dos.h``VPrintf`, `FPrintf`, error code strings

View file

@ -0,0 +1,137 @@
[← Home](../../README.md) · [Reverse Engineering](../README.md)
# Recovering Data Structures
## Overview
Amiga executables use OS structures extensively — `ExecBase`, `Node`, `Process`, `IORequest`, etc. This document describes how to recover and annotate these structures in disassembly by matching field access patterns against NDK39 header offsets.
---
## The MOVE.L offset(An),Dm Pattern
Structure field accesses appear as:
```asm
MOVEA.L _DOSBase, A6
MOVE.L ($17A,A6), A0 ; SysBase->LibList at offset +0x17A
MOVE.L (A0), A1 ; lh_Head
```
The key is the **base register** and **constant offset**. Match the offset against known structure definitions.
---
## Common Structures and Key Offsets
### `struct ExecBase` (at absolute address `$4`)
| Offset | Field | Type |
|---|---|---|
| +0 | `LibNode` | `struct Library` |
| +0x128 | `TaskReady` | `struct List` |
| +0x132 | `TaskWait` | `struct List` |
| +0x17A | `LibList` | `struct List` |
| +0x182 | `DeviceList` | `struct List` |
| +0x21E | `ChipRevBits0` | `UWORD` |
| +0x280 | `MemList` | `struct List` |
### `struct Node` (8 bytes)
| Offset | Field |
|---|---|
| +0 | `ln_Succ` (next node) |
| +4 | `ln_Pred` (prev node) |
| +8 | `ln_Type` (UBYTE) |
| +9 | `ln_Pri` (BYTE priority) |
| +10 | `ln_Name` (STRPTR) |
List traversal:
```asm
MOVEA.L lh_Head, A0 ; first node
.loop:
TST.L (A0) ; ln_Succ == NULL?
BEQ.S .done
; process node at A0
MOVEA.L (A0), A0 ; A0 = ln_Succ
BRA.S .loop
```
### `struct Process` (extends `struct Task`)
| Offset | Field |
|---|---|
| +0 | `pr_Task` (struct Task) |
| +92 | `pr_MsgPort` |
| +128 | `pr_CLI` (BPTR, non-NULL if CLI) |
| +172 | `pr_SegList` (BPTR to segment list) |
Detection in disassembly:
```asm
MOVE.L ($80,A4), D0 ; pr_CLI at offset +0x80
BEQ.S .wb_launch ; NULL = Workbench
```
### `struct IORequest` / `struct IOStdReq`
| Offset | Field |
|---|---|
| +0 | `io_Message` (struct Message) |
| +20 | `io_Device` |
| +24 | `io_Unit` |
| +28 | `io_Command` (UWORD) |
| +30 | `io_Flags` (UBYTE) |
| +32 | `io_Error` (BYTE) |
| +36 | `io_Length` (ULONG) |
| +40 | `io_Actual` (ULONG) |
| +44 | `io_Data` (APTR) |
| +48 | `io_Offset` (ULONG) |
---
## Annotating Structures in IDA Pro
### Define a structure type:
1. `View → Open Subviews → Local Types``Insert` → paste C struct definition
2. IDA parses the struct and creates a type entry
3. Navigate to the base register in disassembly
4. Press `T` (structure offset) on any `offset(An)` operand
5. Select the struct type → all accesses auto-annotated
### Import NDK39 headers:
Use `File → Load file → Parse C header file` → select `exec/execbase.h`, `exec/tasks.h`, etc. from NDK39.
---
## Exec Node Traversal Loops
A recurring pattern: walking the `LibList` or `DeviceList`:
```asm
; Annotated after struct recovery:
MOVEA.L SysBase, A6
LEA (LibList,A6), A0 ; &SysBase->LibList
MOVEA.L (lh_Head,A0), A1 ; first lib node
.scan:
MOVEA.L (ln_Succ,A1), A2 ; peek next
TST.L A2
BEQ.S .not_found
; compare ln_Name string
MOVEA.L (ln_Name,A1), A0
JSR ___strcmp
TST.L D0
BEQ.S .found
MOVEA.L A2, A1
BRA.S .scan
```
---
## References
- NDK39: `exec/execbase.h`, `exec/tasks.h`, `exec/nodes.h`, `exec/io.h`
- `06_exec_os/exec_base.md` — full ExecBase field listing
- `06_exec_os/lists_nodes.md` — MinList/List traversal
- IDA Pro: Structure subview, Local Types, T hotkey for struct offset

20
06_exec_os/README.md Normal file
View file

@ -0,0 +1,20 @@
[← Home](../README.md)
# exec.library — Kernel Overview
## Section Index
| File | Description |
|---|---|
| [exec_base.md](exec_base.md) | ExecBase structure — full field listing |
| [library_system.md](library_system.md) | Library node, OpenLibrary lifecycle |
| [library_vectors.md](library_vectors.md) | JMP table, LVO offsets, MakeFunctions |
| [tasks_processes.md](tasks_processes.md) | Task/Process structs, scheduling |
| [interrupts.md](interrupts.md) | Interrupt levels, INTENA, AddIntServer |
| [memory_management.md](memory_management.md) | AllocMem, FreeMem, MemHeader |
| [message_ports.md](message_ports.md) | MsgPort, PutMsg, GetMsg, WaitPort |
| [signals.md](signals.md) | AllocSignal, SetSignal, Wait |
| [semaphores.md](semaphores.md) | SignalSemaphore, ObtainSemaphore |
| [io_requests.md](io_requests.md) | IORequest, DoIO, SendIO, AbortIO |
| [lists_nodes.md](lists_nodes.md) | MinList/List/Node traversal |
| [resident_modules.md](resident_modules.md) | RomTag, RTF_AUTOINIT, FindResident |

View file

@ -0,0 +1,106 @@
[← Home](../README.md) · [Exec Kernel](README.md)
# Exception and Trap Handling — M68k on AmigaOS
## Overview
The M68k CPU provides a **256-entry exception vector table** starting at address `$000000`. AmigaOS manages these vectors through `exec.library`, allowing both the OS and user code to install handlers for hardware interrupts, bus errors, and software traps.
---
## Exception Vector Table
| Vector | Address | Exception | AmigaOS Handler |
|---|---|---|---|
| 0 | `$000` | Reset: Initial SSP | (boot value) |
| 1 | `$004` | Reset: Initial PC | ROM entry point |
| 2 | `$008` | Bus Error | Guru Meditation / Enforcer |
| 3 | `$00C` | Address Error | Guru Meditation |
| 4 | `$010` | Illegal Instruction | Guru Meditation |
| 5 | `$014` | Zero Divide | Alert |
| 6 | `$018` | CHK Instruction | Alert |
| 7 | `$01C` | TRAPV | Alert |
| 8 | `$020` | Privilege Violation | Alert |
| 9 | `$024` | Trace | Debug (wack/BareFoot) |
| 10 | `$028` | Line-A Emulator | Unused (soft trap space) |
| 11 | `$02C` | Line-F Emulator | 68040/060.library FPU emulation |
| 1214 | `$030$038` | Reserved | — |
| 15 | `$03C` | Uninitialised Interrupt | Alert |
| 24 | `$060` | Spurious Interrupt | — |
| 2531 | `$064$07C` | Auto-vector interrupts 17 | Exec interrupt dispatcher |
| 3247 | `$080$0BC` | TRAP #0#15 | User-installable traps |
| 4863 | `$0C0$0FC` | Reserved (FPU) | 68881/68882 exception handlers |
| 64255 | `$100$3FC` | User-defined vectors | User |
---
## TRAP Instructions — Software Interrupts
`TRAP #n` (n = 015) generates a software exception. AmigaOS uses:
| TRAP | User |
|---|---|
| `TRAP #0` | exec.library `Supervisor()` — switch to supervisor mode |
| `TRAP #1#14` | Available for user programs |
| `TRAP #15` | Remote debugger breakpoint (BareFoot/wack) |
---
## Installing an Exception Handler
```c
/* Using exec.library SetExcept/SetTrapHandler (not recommended): */
/* Direct vector patching in supervisor mode: */
APTR OldVector;
__asm void MyTrapHandler(void)
{
/* Save registers, examine stack frame */
/* ... handle trap ... */
rte
}
/* Install: */
Supervisor(function() {
OldVector = *(APTR *)0x0B0; /* TRAP #12 vector */
*(APTR *)0x0B0 = MyTrapHandler;
});
```
---
## Guru Meditation
When a fatal exception occurs (Bus Error, Address Error), exec.library displays:
```
Software Failure. Press left mouse button to continue.
Guru Meditation #XXYYYYYY.ZZZZZZZZ
```
| Field | Meaning |
|---|---|
| `XX` | Alert type: $00=recovery possible, $80=dead-end |
| `YYYYYY` | Error code (subsystem + specific error) |
| `ZZZZZZZZ` | Address where error occurred |
### Common Guru Codes
| Code | Meaning |
|---|---|
| `$80000003` | Address Error (dead-end) |
| `$80000004` | Illegal instruction (dead-end) |
| `$81000005` | exec: No memory |
| `$82000005` | graphics: No memory |
| `$87000007` | trackdisk: No disk |
| `$04000001` | exec: Recoverable alert |
| `$00000001` | No memory (recoverable) |
---
## References
- Motorola: *MC68000 Family Reference Manual* — exception processing
- NDK39: `exec/alerts.h` — alert code definitions
- RKRM: Exception chapter

110
06_exec_os/exec_base.md Normal file
View file

@ -0,0 +1,110 @@
[← Home](../README.md) · [Exec Kernel](README.md)
# ExecBase — Full Structure Reference
## Overview
`ExecBase` is the root structure of AmigaOS, located at absolute address `$4`. It is a `struct Library` extended with all exec kernel state: memory lists, task queues, interrupt vectors, library lists, and hardware abstraction fields.
---
## Locating ExecBase
```c
struct ExecBase *SysBase = *((struct ExecBase **)4);
```
In assembly:
```asm
MOVEA.L 4.W, A6 ; A6 = SysBase
```
---
## Key Field Groups
### Library Header (offset 0)
```c
struct Library LibNode; /* +0 — ln_Name = "exec.library" */
/* +20 — lib_Version (40 = OS3.1, 44 = OS3.2) */
```
### Interrupts (offset +84)
```c
UWORD AttnFlags; /* +0x128 — processor capability flags */
UWORD AttnResched; /* +0x12A — reschedule attention flag */
```
### Task Scheduling
| Offset | Field | Description |
|---|---|---|
| +0x128 | `TaskReady` | `struct List` — tasks ready to run |
| +0x132 | `TaskWait` | `struct List` — tasks waiting on signals |
| +0x126 | `IDNestCnt` | Interrupt disable nesting count |
| +0x127 | `TDNestCnt` | Task disable nesting count |
### Memory
| Offset | Field | Description |
|---|---|---|
| +0x130 | `MemList` | `struct List` of `MemHeader` regions |
| +0x134 | `ResourceList` | Resources list |
### Library and Device Lists
| Offset | Field | Description |
|---|---|---|
| +0x17A | `LibList` | `struct List` — loaded libraries |
| +0x182 | `DeviceList` | `struct List` — loaded devices |
| +0x18A | `IntrList` | Interrupt server list |
| +0x192 | `PortList` | Public message ports |
| +0x19A | `TaskList` | All tasks (not just ready/waiting) |
### Vectors and ROM
| Offset | Field | Description |
|---|---|---|
| +0x26 | `SoftVer` | Kickstart software revision |
| +0x10 | `ChkBase` | Checksum of library header |
| +0x222 | `PowerSupplyFrequency` | 50 or 60 Hz |
| +0x21E | `ChipRevBits0` | Chip revision detection flags |
### Chip Revision Flags (`ChipRevBits0`)
| Bit | Constant | Chip |
|---|---|---|
| 4 | `ATNF_68010` | 68010 or better |
| 5 | `ATNF_68020` | 68020 or better |
| 6 | `ATNF_68030` | 68030 |
| 7 | `ATNF_68040` | 68040 |
| 10 | `ATNF_FPU40` | 68040 internal FPU |
---
## Detecting CPU and Chipset
```c
/* CPU: */
if (SysBase->AttnFlags & AFF_68020) { /* 020+ */ }
if (SysBase->AttnFlags & AFF_68040) { /* 040 */ }
/* Chipset (via graphics.library): */
struct GfxBase *gfx = (struct GfxBase *)OpenLibrary("graphics.library", 36);
if (gfx->ChipRevBits0 & GFXB_AA_ALICE) { /* AGA Alice chip */ }
```
---
## ExecBase in IDA Pro
After loading Kickstart ROM:
1. Create a segment at `$4` containing a pointer
2. Follow the pointer to the ExecBase (in ROM)
3. Apply `struct ExecBase` type (from NDK39 headers parsed via `File → Parse C header`)
4. All `N(A6)` offsets auto-annotate as field names
---
## References
- NDK39: `exec/execbase.h` — authoritative field definitions
- ADCD 2.1: exec.library autodoc
- *Amiga ROM Kernel Reference Manual: Exec* — ExecBase chapter
- http://amigadev.elowar.com/read/ADCD_2.1/Libraries_Manual_guide/node0072.html

126
06_exec_os/interrupts.md Normal file
View file

@ -0,0 +1,126 @@
[← Home](../README.md) · [Exec Kernel](README.md)
# Interrupts — Levels, INTENA, AddIntServer, CIA Interrupts
## Overview
AmigaOS supports 7 hardware interrupt levels (68k IPL0IPL6) plus a software interrupt mechanism. Custom chip interrupts are filtered through the `INTENA` / `INTREQ` registers; CIA-generated interrupts arrive on level 2 (CIA-A) and level 6 (CIA-B).
---
## Interrupt Priority Levels
| IPL | Source | AmigaOS Use |
|---|---|---|
| 1 | TBE, DSKBLK, SOFTINT | Software interrupts (`SoftInt`) |
| 2 | PORTS (CIA-A) | Keyboard, timer, parallel, floppy motor |
| 3 | COPER, VERTB, BLIT | Copper, vertical blank, blitter |
| 4 | AUD0AUD3 | Audio DMA completion |
| 5 | RBF, DSKSYNC | Serial receive, disk sync |
| 6 | EXTER (CIA-B) | External interrupts, CIA-B timers, TOD |
| 7 | NMI | Non-maskable (unused on stock Amiga) |
---
## Custom Chip Interrupt Registers
| Register | Address | Description |
|---|---|---|
| `INTENAR` | `$DFF01C` | Interrupt enable status (read) |
| `INTENA` | `$DFF09A` | Interrupt enable set/clear (write) |
| `INTREQR` | `$DFF01E` | Interrupt request status (read) |
| `INTREQ` | `$DFF09C` | Interrupt request clear/set (write) |
### INTENA / INTREQ Bit Map
| Bit | Constant | Source |
|---|---|---|
| 0 | `INTF_TBE` | Serial transmit buffer empty |
| 1 | `INTF_DSKBLK` | Disk DMA block complete |
| 2 | `INTF_SOFTINT` | Software interrupt |
| 3 | `INTF_PORTS` | CIA-A interrupt (level 2) |
| 4 | `INTF_COPER` | Copper interrupt |
| 5 | `INTF_VERTB` | Vertical blank |
| 6 | `INTF_BLIT` | Blitter interrupt |
| 7 | `INTF_AUD0` | Audio channel 0 |
| 8 | `INTF_AUD1` | Audio channel 1 |
| 9 | `INTF_AUD2` | Audio channel 2 |
| 10 | `INTF_AUD3` | Audio channel 3 |
| 11 | `INTF_RBF` | Serial receive buffer full |
| 12 | `INTF_DSKSYNC` | Disk sync word match |
| 13 | `INTF_EXTER` | CIA-B / external interrupt |
| 14 | `INTF_INTEN` | Master interrupt enable bit |
To enable vertical blank: write `$C005` to `INTENA` (bit 14 set = enable, bit 5 = VERTB).
To clear vertical blank request: write `$0020` to `INTREQ`.
---
## Adding an Interrupt Server
```c
struct Interrupt myVBL = {
{ NULL, NULL, NT_INTERRUPT, 0, "My VBL" },
NULL, /* is_Data (passed to handler) */
myVBLHandler /* is_Code */
};
AddIntServer(INTB_VERTB, &myVBL); /* INTB_VERTB = 5 */
/* ... run ... */
RemIntServer(INTB_VERTB, &myVBL); /* always remove before exit */
```
### Interrupt Handler Rules
- Handler is called at interrupt level — **no OS calls that Wait()**
- D0D1, A0A1 may be trashed; all others preserved
- Return D0 = 0 if you did not handle it (pass to next server)
- Return D0 ≠ 0 if you handled it (stop server chain)
Handler signature:
```asm
myVBLHandler:
; A1 = is_Data pointer (from struct Interrupt)
; do fast work only
MOVEQ #1, D0 ; handled — stop chain
RTS
```
---
## CIA Interrupts
CIA-A (at `$BFEC01`) generates level 2 interrupts. CIA-B (at `$BFD000`) generates level 6. Each CIA has an ICR (Interrupt Control Register) with 5 sources:
| Bit | Source |
|---|---|
| 0 | Timer A underflow |
| 1 | Timer B underflow |
| 2 | TOD alarm |
| 3 | Serial register full |
| 4 | Flag pin / FLG |
CIA interrupts are serviced via AddIntServer on `INTB_PORTS` (level 2, CIA-A) or `INTB_EXTER` (level 6, CIA-B).
---
## Disable / Enable vs Forbid / Permit
| Function | Effect | Scope |
|---|---|---|
| `Forbid()` | Disables task switching | Task-level (interrupts still run) |
| `Permit()` | Re-enables task switching | Reverses `Forbid()` |
| `Disable()` | Masks all hardware interrupts | Hardware + task switching |
| `Enable()` | Unmasks hardware interrupts | Reverses `Disable()` |
> [!CAUTION]
> `Disable()` / `Enable()` can be held for only a few microseconds — never do I/O or complex operations inside a `Disable()` section.
---
## References
- NDK39: `hardware/intbits.h`, `hardware/cia.h`
- ADCD 2.1: `AddIntServer`, `RemIntServer`, `SetIntVector`, `Disable`, `Forbid`
- `01_hardware/common/cia_chips.md` — CIA timer and ICR details
- `01_hardware/ocs_a500/custom_registers.md` — INTENA/INTREQ register listing

153
06_exec_os/io_requests.md Normal file
View file

@ -0,0 +1,153 @@
[← Home](../README.md) · [Exec Kernel](README.md)
# IO Requests — IORequest, DoIO, SendIO, CheckIO, AbortIO
## Overview
AmigaOS device I/O uses a **message-based** asynchronous protocol. Every device operation is described by an `IORequest` structure sent to a device's command port. The device processes it (synchronously or in the background) and replies when done.
---
## Structures
```c
/* exec/io.h — NDK39 */
struct IORequest {
struct Message io_Message; /* embedded Message (has MsgPort reply port) */
struct Device *io_Device; /* filled by OpenDevice */
struct Unit *io_Unit; /* filled by OpenDevice */
UWORD io_Command; /* CMD_READ, CMD_WRITE, TD_FORMAT, ... */
UBYTE io_Flags; /* IOF_QUICK = attempt synchronous fast path */
BYTE io_Error; /* result: 0 = success, negative = error code */
};
struct IOStdReq { /* extended version with data fields */
struct IORequest io_Request;
ULONG io_Actual; /* actual bytes transferred */
ULONG io_Length; /* requested byte count */
APTR io_Data; /* data buffer pointer */
ULONG io_Offset; /* byte offset (for random-access devices) */
};
```
---
## Standard Command Codes
```c
/* exec/io.h */
#define CMD_INVALID 0 /* not a valid command */
#define CMD_RESET 1 /* reset the device/unit to initial state */
#define CMD_READ 2 /* read io_Length bytes into io_Data from io_Offset */
#define CMD_WRITE 3 /* write io_Length bytes from io_Data at io_Offset */
#define CMD_UPDATE 4 /* flush write cache to media */
#define CMD_CLEAR 5 /* discard device read buffers */
#define CMD_STOP 6 /* suspend device operation */
#define CMD_START 7 /* resume device operation */
#define CMD_FLUSH 8 /* abort all pending requests */
#define CMD_NONSTD 9 /* first device-specific command number */
```
Device-specific commands start at `CMD_NONSTD` (9). Example: trackdisk uses `TD_FORMAT` (10), `TD_MOTOR` (11), `TD_SEEK` (12).
---
## Error Codes (`io_Error`)
```c
/* exec/errors.h — NDK39 */
#define IOERR_OPENFAIL -1 /* device/unit could not be opened */
#define IOERR_ABORTED -2 /* request was aborted via AbortIO */
#define IOERR_NOCMD -3 /* unknown command */
#define IOERR_BADLENGTH -4 /* io_Length invalid for this command */
#define IOERR_BADADDRESS -5 /* io_Data not aligned or accessible */
#define IOERR_UNITBUSY -6 /* unit in use, cannot complete */
#define IOERR_SELFTEST -7 /* hardware self-test failed */
```
---
## Opening a Device
```c
struct IOStdReq *ior = CreateStdIO(reply_port); /* alloc + fill reply port */
if (OpenDevice("trackdisk.device", unit, (struct IORequest *)ior, 0) != 0) {
/* open failed — ior->io_Error set */
}
```
Or manually:
```c
struct IOStdReq *ior = AllocMem(sizeof(struct IOStdReq), MEMF_PUBLIC|MEMF_CLEAR);
ior->io_Message.mn_ReplyPort = my_reply_port;
ior->io_Message.mn_Length = sizeof(struct IOStdReq);
OpenDevice("audio.device", 0, (struct IORequest *)ior, 0);
```
---
## Synchronous I/O: `DoIO`
Blocks the calling task until the device completes the request:
```c
ior->io_Command = CMD_READ;
ior->io_Data = buffer;
ior->io_Length = 512;
ior->io_Offset = 0;
LONG err = DoIO((struct IORequest *)ior);
/* io_Actual = bytes actually read; io_Error = error code */
```
---
## Asynchronous I/O: `SendIO` + `WaitIO`
```c
/* Queue the request — returns immediately: */
SendIO((struct IORequest *)ior);
/* Do other work while device operates... */
/* Block until this specific request completes: */
WaitIO((struct IORequest *)ior);
err = ior->io_Error;
```
### Poll without blocking: `CheckIO`
```c
/* Returns non-NULL if request is done (removed from device queue): */
if (CheckIO((struct IORequest *)ior)) {
WaitIO((struct IORequest *)ior); /* must still call WaitIO to dequeue reply */
}
```
---
## Aborting a Request: `AbortIO`
```c
AbortIO((struct IORequest *)ior); /* ask device to cancel */
WaitIO((struct IORequest *)ior); /* wait for confirmation */
/* io_Error will be IOERR_ABORTED (-2) */
```
---
## Closing a Device
```c
CloseDevice((struct IORequest *)ior);
DeleteStdIO(ior); /* or FreeMem */
```
---
## References
- NDK39: `exec/io.h`, `exec/errors.h`
- ADCD 2.1: `OpenDevice`, `CloseDevice`, `DoIO`, `SendIO`, `WaitIO`, `CheckIO`, `AbortIO`
- `10_devices/` — per-device command codes and structures
- *Amiga ROM Kernel Reference Manual: Exec* — I/O requests chapter

View file

@ -0,0 +1,97 @@
[← Home](../README.md) · [Exec Kernel](README.md)
# Library System — OpenLibrary Lifecycle
## Overview
The AmigaOS library system provides **versioned, shared code** via a standardised interface. Libraries are identified by name, opened with a version check, and reference-counted for safe unloading.
---
## Library Node
Every library is an `NT_LIBRARY` node on `SysBase->LibList`:
```c
struct Library {
struct Node lib_Node; /* ln_Name = "dos.library" */
UBYTE lib_Flags; /* LIBF_SUMUSED | LIBF_DELEXP */
UBYTE lib_Pad;
UWORD lib_NegSize; /* size of JMP table in bytes */
UWORD lib_PosSize; /* size of library base struct */
UWORD lib_Version; /* major version */
UWORD lib_Revision; /* minor revision */
APTR lib_IdString; /* "dos.library 40.1 (16.7.93)" */
ULONG lib_Sum; /* JMP table checksum */
UWORD lib_OpenCnt; /* reference count */
};
```
---
## OpenLibrary / CloseLibrary
```c
/* Open — get a reference: */
struct DosLibrary *DOSBase =
(struct DosLibrary *)OpenLibrary("dos.library", 40);
/* Use the library ... */
/* Close — release reference: */
CloseLibrary((struct Library *)DOSBase);
```
Internally:
1. `exec` scans `LibList` for `ln_Name == "dos.library"`
2. If not found, searches resident list and `LIBS:` path
3. If found on disk: `LoadSeg` + call `InitLib`
4. Check `lib_Version >= requested_version`
5. Call library's `Open()` vector → `lib_OpenCnt++`
6. Return library base
---
## Library Flags
| Flag | Value | Meaning |
|---|---|---|
| `LIBF_SUMUSED` | 0x01 | Checksum is maintained |
| `LIBF_CHANGED` | 0x02 | Checksum needs recalculation |
| `LIBF_DELEXP` | 0x04 | Expunge deferred (opened while expunge pending) |
---
## Version Numbering Convention
`lib_Version.lib_Revision`:
- `40.1` = OS 3.1 release
- `40.x` = OS 3.1 (various revisions)
- `44.x` = OS 3.2
Increment rules:
- `lib_Revision` — minor bugfix, compatible
- `lib_Version` — API change or major update (requestors check this)
---
## Finding a Library Without Opening
```c
/* Read-only peek — no open count increment */
Forbid();
struct Library *lib = FindName(&SysBase->LibList, "graphics.library");
Permit();
if (lib) printf("Found v%d\n", lib->lib_Version);
```
> [!CAUTION]
> Using `FindName` without `Forbid()` is a race condition — the library could be expunged between finding it and using it.
---
## References
- NDK39: `exec/libraries.h`
- ADCD 2.1: `OpenLibrary`, `CloseLibrary`, `FindName`
- `04_linking_and_libraries/shared_libraries_runtime.md` — expunge lifecycle

View file

@ -0,0 +1,125 @@
[← Home](../README.md) · [Exec Kernel](README.md)
# Library Vectors — JMP Table, LVOs, MakeFunctions, SetFunction
## Overview
Every AmigaOS library exposes its functions via a **JMP table** at negative offsets from the library base. This document covers the structure of the table, how LVOs are assigned, and how to create or patch one programmatically.
---
## JMP Table Structure
```
Address Content Description
lib_base - N×6: 4EF9 XXXXXXXX JMP <absolute address> ← function N
...
lib_base - 24: 4EF9 XXXXXXXX JMP Reserved
lib_base - 18: 4EF9 XXXXXXXX JMP Expunge
lib_base - 12: 4EF9 XXXXXXXX JMP Close
lib_base - 6: 4EF9 XXXXXXXX JMP Open
lib_base + 0: struct Library ← pointer returned by OpenLibrary
```
Each slot is exactly **6 bytes**: opcode `$4EF9` (JMP abs.l) + 4-byte target address.
---
## LVO Formula
```
LVO = 6 × slot_index
where slot_index counts from 1 (Open) upward:
Open = slot 1 → LVO = 6
Close = slot 2 → LVO = 12
Expunge = slot 3 → LVO = 18
Reserved = slot 4 → LVO = 24
First user fn = slot 5 → LVO = 30
Second user fn = slot 6 → LVO = 36
...
```
The `.fd` file `##bias` value is the positive LVO: `bias 30` → LVO `30`.
---
## MakeFunctions — Building a JMP Table
`exec.library MakeFunctions()` fills in the JMP table from a function pointer array:
```c
ULONG MakeFunctions(APTR targetLib, APTR funcArray, APTR funcDispBase);
```
Typical usage in library `InitLib`:
```asm
; funcArray: table of function pointers, terminated by -1
_LibFuncTable:
dc.l _LibOpen
dc.l _LibClose
dc.l _LibExpunge
dc.l _LibNull ; Reserved — returns NULL
dc.l _MyFunc1
dc.l _MyFunc2
dc.l -1 ; terminator
LibInit:
LEA _LibFuncTable(PC), A0
MOVEA.L A6, A1 ; library base (passed in A6 by exec)
MOVEQ #0, D0 ; funcDispBase = 0 (absolute addresses)
MOVEA.L 4.W, A6
JSR (-420,A6) ; MakeFunctions(A1, A0, D0)
```
`MakeFunctions` writes `JMP <ptr>` for each entry, filling the table downward from `lib_base 6`.
---
## SetFunction — Patching a Single Slot
```c
APTR SetFunction(struct Library *library, LONG funcOffset, APTR newFunction);
```
- `funcOffset` is the negative LVO (e.g., `30` for the first user function)
- Returns the old function pointer
```c
/* Hook dos.library Write() */
old_write = SetFunction((struct Library *)DOSBase, -48, my_write_hook);
```
See `05_reversing/dynamic/setfunction_patching.md` for trampoline patterns.
---
## Checksum Maintenance
After `MakeFunctions` or `SetFunction`, exec updates `lib_Sum` via `SumLibrary`:
```c
SumLibrary((struct Library *)myLib);
```
If `LIBF_SUMUSED` is set, exec verifies the checksum at `CloseLibrary` time. Patching the JMP table without calling `SumLibrary` will trigger a checksum failure (alert box or guru).
---
## Viewing Vectors in IDA Pro
1. Navigate to `lib_base 6` (first standard vector)
2. Each 6-byte group: opcode `4EF9` + 4-byte address
3. Press `C` to disassemble if not auto-detected
4. The 4-byte value is the actual function address — press `G` (Go to) to navigate
5. Name each function with the `.fd` file as reference
---
## References
- NDK39: `exec/execbase.h`, `exec/libraries.h`
- ADCD 2.1: `MakeFunctions`, `MakeLibrary`, `SetFunction`, `SumLibrary`
- `05_reversing/static/library_jmp_table.md` — reconstruction workflow
- `04_linking_and_libraries/lvo_table.md` — complete LVO reference tables

158
06_exec_os/lists_nodes.md Normal file
View file

@ -0,0 +1,158 @@
[← Home](../README.md) · [Exec Kernel](README.md)
# Lists and Nodes — MinList, List, Node, MinNode
## Overview
AmigaOS uses **intrusive doubly-linked lists** throughout exec: the task list, library list, device list, memory list, port list, and more all use the same `List`/`Node` structures defined in `exec/lists.h`.
---
## Structures
```c
/* exec/nodes.h — NDK39 */
struct Node {
struct Node *ln_Succ; /* pointer to next node (NULL at tail sentinel) */
struct Node *ln_Pred; /* pointer to prev node (NULL at head sentinel) */
UBYTE ln_Type; /* node type — NT_TASK, NT_LIBRARY, NT_MEMORY... */
BYTE ln_Pri; /* scheduling priority (used by Enqueue) */
char *ln_Name; /* optional name string (NULL = anonymous) */
};
struct MinNode {
struct MinNode *mln_Succ;
struct MinNode *mln_Pred;
/* no type, priority, or name — minimal overhead */
};
```
```c
/* exec/lists.h — NDK39 */
struct List {
struct Node *lh_Head; /* first node (or tail sentinel if empty) */
struct Node *lh_Tail; /* always NULL — marks end of list */
struct Node *lh_TailPred; /* last node (or head sentinel if empty) */
UBYTE lh_Type; /* list type */
UBYTE lh_pad;
};
struct MinList {
struct MinNode *mlh_Head;
struct MinNode *mlh_Tail; /* always NULL */
struct MinNode *mlh_TailPred;
};
```
### Node Type Constants
```c
/* exec/nodes.h */
#define NT_UNKNOWN 0
#define NT_TASK 1 /* exec Task */
#define NT_INTERRUPT 2 /* Interrupt server */
#define NT_DEVICE 3 /* Device */
#define NT_MSGPORT 4 /* MsgPort */
#define NT_MESSAGE 5 /* Message */
#define NT_FREEMSG 6
#define NT_REPLYMSG 7
#define NT_RESOURCE 8
#define NT_LIBRARY 9 /* Library */
#define NT_MEMORY 10 /* MemHeader */
#define NT_SOFTINT 11
#define NT_FONT 12
#define NT_PROCESS 13 /* dos.library Process */
#define NT_SEMAPHORE 14
#define NT_SIGNALSEM 15 /* SignalSemaphore */
#define NT_BOOTNODE 16
#define NT_KICKMEM 17
#define NT_GRAPHICS 18
#define NT_DEATHMESSAGE 19
```
---
## Initialising a List
```c
/* Stack-allocated list: */
struct List myList;
NewList(&myList); /* sets up sentinel pointers — mandatory */
/* Or use NEWLIST() macro: */
NEWLIST(&myList);
```
---
## Adding and Removing Nodes
```c
/* Add at head (highest LRU position): */
AddHead(&myList, &myNode); /* LVO -240 */
/* Add at tail: */
AddTail(&myList, &myNode); /* LVO -246 */
/* Remove from wherever it is (no list pointer needed): */
Remove(&myNode); /* LVO -252 */
/* Priority-ordered insert (by ln_Pri, high first): */
Enqueue(&myList, &myNode); /* LVO -270 */
```
---
## Walking a List
```c
struct Node *node, *next;
for (node = myList.lh_Head; node->ln_Succ != NULL; node = node->ln_Succ) {
/* process node */
}
```
Safe removal while iterating (save next before removing):
```c
for (node = myList.lh_Head; (next = node->ln_Succ) != NULL; node = next) {
if (should_remove(node)) Remove(node);
}
```
---
## Finding a Node by Name
```c
struct Node *found = FindName(&SysBase->LibList, "dos.library");
/* Returns NULL if not found */
/* Always call under Forbid() if the list may change */
```
---
## How the Sentinel Works
The AmigaOS list design uses a **3-pointer layout** that avoids special-casing empty lists and end-of-list checks:
```
lh_Head ──→ [ Node A ]──→ [ Node B ]──→ [ tail sentinel ]
lh_Tail = NULL (always)
lh_TailPred ──────────────────────────→ [ Node B ]
Empty list:
lh_Head ──→ [ tail sentinel ]
lh_TailPred ──→ [ head sentinel ]
```
Walking stops when `ln_Succ == NULL` — that is the tail sentinel's `lh_Tail` field.
---
## References
- NDK39: `exec/nodes.h`, `exec/lists.h`
- ADCD 2.1: `AddHead`, `AddTail`, `Remove`, `Enqueue`, `FindName`, `NewList`
- *Amiga ROM Kernel Reference Manual: Exec* — lists chapter

View file

@ -0,0 +1,151 @@
[← Home](../README.md) · [Exec Kernel](README.md)
# Memory Management — AllocMem, FreeMem, MemHeader
## Overview
AmigaOS memory management is built directly into `exec.library`. There is no `malloc`/`free` in the OS itself — applications call `AllocMem` and `FreeMem` which operate on a linked list of `MemHeader` regions representing physical RAM.
---
## MemHeader — Memory Region Descriptor
```c
/* exec/memory.h — NDK39 */
struct MemHeader {
struct Node mh_Node; /* ln_Type=NT_MEMORY, ln_Pri=region priority */
/* ln_Name = e.g. "chip memory" */
UWORD mh_Attributes; /* MEMF_* flags describing this region */
struct MemChunk *mh_First; /* pointer to first free chunk in this region */
APTR mh_Lower; /* lowest byte address of region */
APTR mh_Upper; /* highest byte address + 1 */
ULONG mh_Free; /* total free bytes currently */
};
struct MemChunk {
struct MemChunk *mc_Next; /* next free chunk (NULL = end of list) */
ULONG mc_Bytes; /* size of this free chunk in bytes */
};
```
The OS maintains a doubly-linked list of `MemHeader` regions at `SysBase→MemList`. On a stock A1200:
- `"chip memory"` covering `$000000$1FFFFF` (2 MB Chip RAM)
- `"fast memory"` covering `$200000$9FFFFF` (up to 8 MB Fast RAM if fitted)
---
## MEMF_ Flag Constants
```c
/* exec/memory.h — NDK39 */
#define MEMF_ANY 0L /* no placement preference */
#define MEMF_PUBLIC (1L<<0) /* accessible by all hardware/software */
#define MEMF_CHIP (1L<<1) /* must be in Chip RAM (DMA-reachable) */
#define MEMF_FAST (1L<<2) /* prefer Fast RAM (CPU-only, faster) */
#define MEMF_CLEAR (1L<<16) /* zero-fill the allocation */
#define MEMF_LARGEST (1L<<17) /* return single largest free block */
#define MEMF_REVERSE (1L<<18) /* allocate from top of list */
#define MEMF_TOTAL (1L<<19) /* AvailMem: report total, not largest free */
```
**Chip RAM** is required for anything the custom chips DMA from — bitmaps, audio samples, Copper lists, blitter sources/destinations, sprite data. The custom chip DMA controllers cannot reach Fast RAM.
**Fast RAM** has no DMA contention with the custom chips, making it faster for pure CPU use.
---
## AllocMem / FreeMem
```c
/* exec/execbase.h — LVO -198 */
APTR AllocMem(ULONG byteSize, ULONG requirements);
/* Returns: pointer to allocated block, or NULL on failure */
/* LVO -210 */
void FreeMem(APTR memoryBlock, ULONG byteSize);
```
### Usage
```c
/* Allocate 512 bytes of Chip RAM, zero-filled: */
UBYTE *buf = AllocMem(512, MEMF_CHIP | MEMF_CLEAR);
if (!buf) { /* handle out-of-memory */ }
/* Free it: */
FreeMem(buf, 512);
```
> [!IMPORTANT]
> `FreeMem` requires the **exact same size** as `AllocMem`. The OS does not store the size internally — you must track it yourself.
---
## AllocVec / FreeVec (OS 2.0+)
```c
/* LVO -684 (exec.library 36+) */
APTR AllocVec(ULONG byteSize, ULONG requirements);
void FreeVec(APTR memoryBlock); /* LVO -690 */
```
`AllocVec` stores the size in the 4 bytes immediately before the returned pointer, allowing `FreeVec` to work without a size argument. Prefer this in new code.
---
## AvailMem — Query Free Memory
```c
/* LVO -216 */
ULONG AvailMem(ULONG requirements);
```
```c
ULONG chip_free = AvailMem(MEMF_CHIP);
ULONG fast_free = AvailMem(MEMF_FAST);
ULONG total_chip = AvailMem(MEMF_CHIP | MEMF_TOTAL);
```
---
## Pool Allocator (OS 3.0+)
For many small allocations, use the pool API which reduces fragmentation:
```c
/* LVO -696 */
APTR pool = CreatePool(MEMF_ANY, 4096, 1024);
/* puddleSize=4096, threshSize=1024 */
APTR p1 = AllocPooled(pool, 32); /* LVO -702 */
FreePooled(pool, p1, 32); /* LVO -708 */
DeletePool(pool); /* LVO -714 */
```
---
## Memory Map (A1200 Example)
| Range | Type | Used for |
|---|---|---|
| `$000000$000400` | Chip | 68k exception vectors |
| `$000400$000BFF` | Chip | exec library, SysBase |
| `$000C00$1FFFFF` | Chip | Application allocations, DMA buffers |
| `$200000$9FFFFF` | Fast | Fast RAM expansion (if present) |
| `$A00000$BFFFFF` | Slow/Ranger | A500 slow RAM (not on A1200) |
| `$BFD000$BFDFFF` | CIA | CIA-B registers |
| `$BFE001$BFEFFF` | CIA | CIA-A registers |
| `$C00000$D7FFFF` | Slow | A500 slow RAM expansion |
| `$D80000$DFFFFF` | Custom | Custom chip registers ($DFF000) |
| `$E00000$E7FFFF` | ROM mirror | (A500) |
| `$F80000$FFFFFF` | ROM | Kickstart 3.1 (512 KB) |
---
## References
- NDK39: `exec/memory.h`, `exec/execbase.h`
- ADCD 2.1: `AllocMem`, `FreeMem`, `AllocVec`, `FreeVec`, `CreatePool`
- `01_hardware/common/address_space.md` — full address map
- *Amiga ROM Kernel Reference Manual: Exec* — memory management chapter

142
06_exec_os/message_ports.md Normal file
View file

@ -0,0 +1,142 @@
[← Home](../README.md) · [Exec Kernel](README.md)
# Message Ports — MsgPort, Message, PutMsg, GetMsg, WaitPort
## Overview
AmigaOS inter-task communication uses a **message passing** system. Tasks send `Message` structures to `MsgPort` queues. The receiving task either polls (`GetMsg`) or blocks (`WaitPort`) for incoming messages. No shared memory is touched without the message handshake.
---
## Core Structures
```c
/* exec/ports.h — NDK39 */
struct MsgPort {
struct Node mp_Node; /* ln_Name = port name (for public ports) */
UBYTE mp_Flags; /* PA_SIGNAL, PA_SOFTINT, PA_IGNORE */
UBYTE mp_SigBit; /* signal bit used for PA_SIGNAL ports */
APTR mp_SigTask; /* task to signal on message arrival */
struct List mp_MsgList; /* queue of pending messages */
};
struct Message {
struct Node mn_Node; /* ln_Type = NT_MESSAGE */
struct MsgPort *mn_ReplyPort; /* port to send reply to (or NULL) */
UWORD mn_Length; /* total size of message including header */
};
```
`mp_Flags` values:
| Value | Constant | Meaning |
|---|---|---|
| 0 | `PA_SIGNAL` | Signal `mp_SigTask` when message arrives |
| 1 | `PA_SOFTINT` | Trigger software interrupt |
| 2 | `PA_IGNORE` | Do not wake the task (polling only) |
---
## Creating a Message Port
```c
struct MsgPort *port = CreateMsgPort(); /* exec.library LVO -732 (OS 2.0+) */
/* or manually for OS 1.x compatibility: */
struct MsgPort *port = AllocMem(sizeof(struct MsgPort), MEMF_PUBLIC|MEMF_CLEAR);
port->mp_Node.ln_Type = NT_MSGPORT;
port->mp_Flags = PA_SIGNAL;
port->mp_SigBit = AllocSignal(-1); /* any free signal bit */
port->mp_SigTask = FindTask(NULL); /* signal current task */
NewList(&port->mp_MsgList);
```
---
## Sending a Message
```c
/* PutMsg: add message to queue, signal receiver */
PutMsg(target_port, (struct Message *)my_msg);
/* Non-blocking — returns immediately */
```
PutMsg can be called from interrupt context.
---
## Receiving Messages
```c
/* Block until at least one message arrives: */
WaitPort(my_port);
/* Then drain the queue: */
struct MyMsg *msg;
while ((msg = (struct MyMsg *)GetMsg(my_port)) != NULL) {
/* process msg */
ReplyMsg((struct Message *)msg); /* send reply if mn_ReplyPort != NULL */
}
```
### GetMsg (non-blocking poll)
```c
struct Message *msg = GetMsg(my_port);
/* Returns NULL if queue is empty */
```
---
## Public Named Ports
```c
/* Register a port so others can find it by name: */
port->mp_Node.ln_Name = "myapp.port";
Forbid();
AddPort(port);
Permit();
/* From another task: */
Forbid();
struct MsgPort *remote = FindPort("myapp.port");
Permit();
if (remote) PutMsg(remote, my_msg);
/* Cleanup: */
Forbid();
RemPort(port);
Permit();
```
`Forbid()` is required around `FindPort`/`AddPort`/`RemPort` to prevent the task list from changing mid-operation.
---
## Reply Pattern
The standard request-reply idiom:
```c
/* Sender: */
my_msg->mn_ReplyPort = reply_port;
PutMsg(server_port, &my_msg->mn_Message);
WaitPort(reply_port);
struct MyMsg *reply = (struct MyMsg *)GetMsg(reply_port);
/* reply now contains the server's response */
/* Server: */
WaitPort(server_port);
struct MyMsg *req = (struct MyMsg *)GetMsg(server_port);
/* process req... */
req->result = 42;
ReplyMsg(&req->mn_Message); /* sends back to req->mn_ReplyPort */
```
---
## References
- NDK39: `exec/ports.h`, `exec/messages.h`
- ADCD 2.1: `CreateMsgPort`, `PutMsg`, `GetMsg`, `WaitPort`, `ReplyMsg`
- *Amiga ROM Kernel Reference Manual: Exec* — messages and ports chapter

View file

@ -0,0 +1,107 @@
[← Home](../README.md) · [Exec Kernel](README.md)
# Resident Modules — RomTag, RTF_AUTOINIT, FindResident
## Overview
AmigaOS ROM and disk-resident modules (libraries, devices, resources) identify themselves via a **RomTag** structure. At boot, exec scans the ROM and loaded segments for RomTags and initialises every module it finds.
---
## struct Resident (RomTag)
```c
/* exec/resident.h — NDK39 */
struct Resident {
UWORD rt_MatchWord; /* always $4AFC — magic identifier */
struct Resident *rt_MatchTag; /* pointer back to this struct (self-ref) */
APTR rt_EndSkip; /* pointer past end of this module's code */
UBYTE rt_Flags; /* RTF_* flags */
UBYTE rt_Version; /* module version number */
UBYTE rt_Type; /* NT_LIBRARY, NT_DEVICE, NT_RESOURCE, ... */
BYTE rt_Pri; /* initialisation priority (higher = earlier) */
char *rt_Name; /* module name string, e.g. "dos.library" */
char *rt_IdString; /* human-readable ID, e.g. "dos.library 40.1" */
APTR rt_Init; /* init function or InitTable pointer */
};
```
### Magic Word
`rt_MatchWord = $4AFC` is the 68k opcode for `ILLEGAL` — a deliberate trap instruction chosen so that an accidental execution of a RomTag causes an immediate CPU exception rather than silent corruption.
### RTF_ Flags
```c
#define RTF_AUTOINIT (1<<7) /* use rt_Init as pointer to InitTable */
#define RTF_SINGLETASK (1<<1) /* init runs in single-task context */
#define RTF_COLDSTART (1<<0) /* init on cold boot only */
```
---
## RTF_AUTOINIT — Automatic Initialisation
When `RTF_AUTOINIT` is set, `rt_Init` points to an **InitTable** rather than a bare function:
```c
struct InitTable {
ULONG it_DataSize; /* size of library instance struct */
APTR *it_FuncTable; /* pointer to function pointer table */
APTR it_DataTable; /* pointer to INITBYTE/INITWORD/INITLONG table */
APTR it_InitRoutine;/* pointer to actual LibInit() function */
};
```
exec uses `MakeLibrary()` to allocate the library, install the JMP table, and initialise the data, then calls `it_InitRoutine`. For most libraries, the author only needs to provide `it_FuncTable` and `it_DataTable` and `RTF_AUTOINIT` handles the rest automatically.
---
## Finding a Resident by Name
```c
struct Resident *res = FindResident("dos.library"); /* LVO -60 */
if (res) {
printf("Found: %s v%d\n", res->rt_Name, res->rt_Version);
}
```
`FindResident` scans `SysBase->ResModules` — the list of all RomTag pointers collected at boot.
---
## ROM Scan at Boot
During exec initialisation, the ROM scanner walks from `$F80000` (Kickstart base) upward looking for the `$4AFC` magic word. For each match it verifies `rt_MatchTag == &rt` (self-referential pointer), confirms `rt_EndSkip` is beyond the RomTag, and adds valid entries to `ResModules`.
The same scan is applied to any loaded segment when `AddResidentModule` is called.
---
## Writing a Minimal RomTag (Assembly)
```asm
; Minimal ROM tag for a library:
dc.w $4AFC ; rt_MatchWord
dc.l _RomTag ; rt_MatchTag (self-ref)
dc.l _EndTag ; rt_EndSkip
dc.b RTF_AUTOINIT ; rt_Flags
dc.b 1 ; rt_Version
dc.b NT_LIBRARY ; rt_Type
dc.b 0 ; rt_Pri
dc.l _Name ; rt_Name
dc.l _IdString ; rt_IdString
dc.l _InitTable ; rt_Init (InitTable when AUTOINIT)
_Name: dc.b "mylib.library", 0
_IdString: dc.b "mylib.library 1.0 (23.4.2026)", 13, 10, 0
even
_EndTag:
```
---
## References
- NDK39: `exec/resident.h`, `exec/execbase.h`
- ADCD 2.1: `FindResident`, `InitResident`, `AddResidentModule`
- *Amiga ROM Kernel Reference Manual: Exec* — resident modules chapter

113
06_exec_os/semaphores.md Normal file
View file

@ -0,0 +1,113 @@
[← Home](../README.md) · [Exec Kernel](README.md)
# Semaphores — SignalSemaphore, ObtainSemaphore, Shared/Exclusive
## Overview
Semaphores are the AmigaOS mechanism for **mutual exclusion and shared-read access** to resources. Unlike `Forbid()` (which blocks all scheduling), semaphores allow other tasks to run while waiting — the waiting task simply sleeps until the resource is available.
---
## struct SignalSemaphore
```c
/* exec/semaphores.h — NDK39 */
struct SignalSemaphore {
struct Node ss_Link; /* ln_Type = NT_SIGNALSEM */
/* ln_Name = semaphore name (public) */
WORD ss_NestCount; /* how many times THIS task has obtained it */
struct MinList ss_WaitQueue;/* tasks waiting for exclusive access */
struct SemaphoreRequest ss_MultipleLink; /* shared-reader slot */
struct Task *ss_Owner; /* task holding exclusive lock (or NULL) */
WORD ss_QueueCount; /* number of waiters */
};
```
---
## Initialising a Semaphore
```c
/* Stack or AllocMem — always initialise before use: */
struct SignalSemaphore sem;
InitSemaphore(&sem); /* LVO -558 */
/* Public (named) semaphore — so other tasks can find it: */
sem.ss_Link.ln_Name = "myapp.lock";
AddSemaphore(&sem); /* LVO -564 */
/* Later: */
RemSemaphore(&sem); /* LVO -570 */
```
---
## Exclusive (Write) Lock
```c
/* Block until this task holds the semaphore exclusively: */
ObtainSemaphore(&sem); /* LVO -534 */
/* --- critical section: only one task in here at a time --- */
ReleaseSemaphore(&sem); /* LVO -546 */
```
### Non-Blocking Try
```c
/* Returns TRUE if obtained, FALSE if someone else holds it: */
if (AttemptSemaphore(&sem)) { /* LVO -540 */
/* got it */
ReleaseSemaphore(&sem);
} else {
/* resource busy — do something else */
}
```
---
## Shared (Read) Lock
Multiple tasks may hold a shared lock simultaneously. An exclusive lock blocks until all shared holders release.
```c
ObtainSemaphoreShared(&sem); /* LVO -768 */
/* --- read-only access: multiple tasks may be here at once --- */
ReleaseSemaphore(&sem); /* same release for both modes */
```
---
## Nesting
Semaphores are **reentrant** — the same task can call `ObtainSemaphore` multiple times. The `ss_NestCount` tracks how many times the current owner has obtained it. `ReleaseSemaphore` must be called the same number of times.
```c
ObtainSemaphore(&sem); /* NestCount = 1 */
ObtainSemaphore(&sem); /* NestCount = 2 — safe, same task */
ReleaseSemaphore(&sem); /* NestCount = 1 */
ReleaseSemaphore(&sem); /* NestCount = 0 — fully released, waiters wake */
```
---
## Semaphore vs Forbid/Disable
| Mechanism | Blocks | Other tasks run while waiting? | Interrupt safe? |
|---|---|---|---|
| `Forbid()` | All task switching | ❌ No | ✅ (interrupts still run) |
| `Disable()` | All task switching + interrupts | ❌ No | ✅ |
| `ObtainSemaphore()` | Only contending tasks | ✅ Yes | ❌ Not from interrupt context |
Use semaphores for anything that may take more than a few microseconds. Use `Forbid()` only for very short list manipulations.
---
## References
- NDK39: `exec/semaphores.h`
- ADCD 2.1: `InitSemaphore`, `ObtainSemaphore`, `ObtainSemaphoreShared`, `ReleaseSemaphore`, `AttemptSemaphore`
- *Amiga ROM Kernel Reference Manual: Exec* — semaphores chapter

132
06_exec_os/signals.md Normal file
View file

@ -0,0 +1,132 @@
[← Home](../README.md) · [Exec Kernel](README.md)
# Signals — AllocSignal, SetSignal, Wait
## Overview
Signals are the lightest AmigaOS synchronization primitive. Each task has 32 signal bits (`tc_SigAlloc`). A task blocks on `Wait(mask)` until any of the specified bits are set by another task or interrupt handler calling `Signal()`.
---
## Signal Bit Constants
```c
/* exec/tasks.h — NDK39 */
/* Bits 015: application-allocated via AllocSignal() */
/* Bits 1631: reserved by exec */
#define SIGB_ABORT 0 /* bit 0: break signal */
#define SIGB_CHILD 1 /* bit 1: child task signal */
#define SIGB_BLIT 4 /* bit 4: blitter done (exec internal) */
#define SIGB_SINGLE 4 /* alias */
#define SIGB_INTUITION 5 /* bit 5: Intuition events (exec internal) */
#define SIGB_DOS 8 /* bit 8: DOS signal */
/* Workbench/DOS break signals (bits 1215): */
#define SIGBREAKB_CTRL_C 12
#define SIGBREAKB_CTRL_D 13
#define SIGBREAKB_CTRL_E 14
#define SIGBREAKB_CTRL_F 15
#define SIGBREAKF_CTRL_C (1L<<SIGBREAKB_CTRL_C) /* $1000 */
#define SIGBREAKF_CTRL_D (1L<<SIGBREAKB_CTRL_D) /* $2000 */
#define SIGBREAKF_CTRL_E (1L<<SIGBREAKB_CTRL_E) /* $4000 */
#define SIGBREAKF_CTRL_F (1L<<SIGBREAKB_CTRL_F) /* $8000 */
```
---
## Allocating and Freeing Signals
```c
/* Allocate an unused signal bit (-1 = any free bit): */
LONG sigBit = AllocSignal(-1); /* LVO -246 */
if (sigBit < 0) { /* all 16 user bits in use */ }
ULONG sigMask = (1L << sigBit);
/* Free when done: */
FreeSignal(sigBit); /* LVO -252 */
```
---
## Waiting for Signals
```c
/* Block until any of the listed signals arrive: */
ULONG received = Wait(sigMask | SIGBREAKF_CTRL_C); /* LVO -318 */
if (received & SIGBREAKF_CTRL_C) {
/* user pressed CTRL-C */
cleanup_and_exit();
}
if (received & sigMask) {
/* our custom event occurred */
}
```
`Wait()` returns only after at least one bit in the mask is set. It is equivalent to sleeping — the task is moved to `TaskWait` and no CPU is consumed.
---
## Sending Signals
```c
/* Signal a task from another task or interrupt handler: */
Signal(target_task, sigMask); /* LVO -324 */
```
`Signal()` is safe from interrupt context.
---
## SetSignal — Read and Clear
```c
/* Read and clear specific signal bits atomically: */
ULONG old = SetSignal(new_bits, change_mask); /* LVO -306 */
/* old = previous state of all 32 signal bits */
/* new value = (old & ~change_mask) | (new_bits & change_mask) */
/* Check CTRL-C without blocking: */
if (SetSignal(0, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C) {
/* CTRL-C was pending — now cleared */
}
```
---
## Typical Usage Pattern: Event Loop
```c
struct MsgPort *port = CreateMsgPort();
ULONG portSig = (1L << port->mp_SigBit);
ULONG waitMask = portSig | SIGBREAKF_CTRL_C;
BOOL running = TRUE;
while (running) {
ULONG sigs = Wait(waitMask);
if (sigs & SIGBREAKF_CTRL_C) {
running = FALSE;
}
if (sigs & portSig) {
struct Message *msg;
while ((msg = GetMsg(port)) != NULL) {
/* handle message */
ReplyMsg(msg);
}
}
}
DeleteMsgPort(port);
```
---
## References
- NDK39: `exec/tasks.h`, `exec/execbase.h`
- ADCD 2.1: `AllocSignal`, `FreeSignal`, `Signal`, `Wait`, `SetSignal`
- `06_exec_os/tasks_processes.md` — tc_SigAlloc, tc_SigRecvd fields
- *Amiga ROM Kernel Reference Manual: Exec* — signals chapter

View file

@ -0,0 +1,157 @@
[← Home](../README.md) · [Exec Kernel](README.md)
# Tasks and Processes — Structures, States, Scheduling
## Overview
AmigaOS uses **cooperative/preemptive** scheduling. Tasks are the fundamental unit of execution; Processes are Tasks with an additional DOS environment (message port, CLI, segment list). The scheduler runs at each quantum (50 Hz VBL interrupt) and after any `Signal()` or `Wait()` call.
---
## struct Task
```c
/* exec/tasks.h */
struct Task {
struct Node tc_Node; /* ln_Type=NT_TASK or NT_PROCESS */
/* ln_Pri = scheduling priority */
/* ln_Name = task name string */
UBYTE tc_Flags; /* TF_LAUNCH, TF_STRIKE, TF_EXCEPT */
UBYTE tc_State; /* TS_RUN, TS_READY, TS_WAIT, TS_EXCEPT */
BYTE tc_IDNestCnt; /* interrupt disable nesting */
BYTE tc_TDNestCnt; /* task disable (Forbid) nesting */
ULONG tc_SigAlloc; /* allocated signal bits mask */
ULONG tc_SigWait; /* signals task is waiting for */
ULONG tc_SigRecvd; /* signals received */
ULONG tc_SigExcept; /* exception signals */
/* ... stack bounds, context, exception handler ... */
APTR tc_SPLower; /* lowest valid stack address */
APTR tc_SPUpper; /* highest valid stack address + 2 */
APTR tc_SPReg; /* saved stack pointer (when not running) */
};
```
---
## struct Process (extends Task)
```c
/* dos/dosextens.h */
struct Process {
struct Task pr_Task; /* embedded Task */
struct MsgPort pr_MsgPort; /* I/O message port */
UWORD pr_Pad;
BPTR pr_SegList; /* BPTR to segment list */
LONG pr_StackSize;
APTR pr_GlobVec; /* BCPL global vector */
LONG pr_TaskNum; /* CLI task number */
BPTR pr_StackBase; /* base of stack (BPTR) */
LONG pr_Result2; /* secondary result */
BPTR pr_CurrentDir; /* current directory lock */
BPTR pr_CIS; /* current input stream */
BPTR pr_COS; /* current output stream */
APTR pr_ConsoleTask;
APTR pr_FileSystemTask;
BPTR pr_CLI; /* CLI structure (NULL if WB) */
...
struct MsgPort *pr_ReturnAddr; /* return address for CLI tasks */
APTR pr_PktWait;
struct SaveMsg pr_ExitData;
UBYTE *pr_Arguments; /* argument string */
struct MinList pr_LocalVars; /* local shell variables */
ULONG pr_ShellPrivate;
BPTR pr_CES; /* current error stream */
};
```
---
## Task States
| State | Value | Meaning |
|---|---|---|
| `TS_INVALID` | 0 | Not a valid task |
| `TS_ADDED` | 1 | Just added, not yet scheduled |
| `TS_RUN` | 2 | Currently running (only one task) |
| `TS_READY` | 3 | On the `TaskReady` list, waiting for CPU |
| `TS_WAIT` | 4 | Blocked on `Wait()` — on `TaskWait` list |
| `TS_EXCEPT` | 5 | Handling an exception |
| `TS_REMOVED` | 6 | Removed from scheduling |
---
## Scheduling: Priority-Based Round Robin
The scheduler (`exec.library` internal) picks the highest-priority task from `SysBase→TaskReady`. Among equal-priority tasks, they get equal time slices (round-robin).
- Default priority: 0
- Range: 128 to +127 (higher = more CPU)
- OS tasks run at priority 1020
- Input handler: priority 20
- Disk tasks: priority 10
```c
SetTaskPri(FindTask(NULL), 5); /* raise current task to priority 5 */
```
---
## Creating Tasks and Processes
```c
/* Simple task (exec level): */
struct Task *t = AllocMem(sizeof(struct Task), MEMF_PUBLIC|MEMF_CLEAR);
t->tc_Node.ln_Name = "MyTask";
t->tc_Node.ln_Pri = 0;
t->tc_SPLower = stack;
t->tc_SPUpper = stack + stacksize;
t->tc_SPReg = (APTR)((ULONG)stack + stacksize);
AddTask(t, myTaskFunc, NULL);
/* DOS Process (with message port, filesystem access): */
struct Process *p = CreateNewProcTags(
NP_Entry, myFunc,
NP_Name, "MyProcess",
NP_StackSize, 8192,
NP_Priority, 0,
TAG_DONE);
```
---
## Task State Machine
```mermaid
stateDiagram-v2
[*] --> READY : AddTask()
READY --> RUN : Scheduler picks task
RUN --> READY : Quantum expired or higher-priority task
RUN --> WAIT : Wait(signal_mask)
WAIT --> READY : Signal() delivers awaited signals
RUN --> EXCEPT : Exception signal received
EXCEPT --> RUN : Exception handler returns
RUN --> [*] : Task function returns / RemTask()
```
---
## FindTask and Task Identity
```c
struct Task *me = FindTask(NULL); /* NULL = current task */
printf("Running as: %s\n", me->tc_Node.ln_Name);
/* Check if we are a Process (vs plain Task): */
if (me->tc_Node.ln_Type == NT_PROCESS) {
struct Process *pr = (struct Process *)me;
/* access pr_CLI, pr_MsgPort, etc. */
}
```
---
## References
- NDK39: `exec/tasks.h`, `dos/dosextens.h`
- ADCD 2.1: `AddTask`, `RemTask`, `FindTask`, `SetTaskPri`, `CreateNewProc`
- *Amiga ROM Kernel Reference Manual: Exec* — tasks and scheduling chapter

18
07_dos/README.md Normal file
View file

@ -0,0 +1,18 @@
[← Home](../README.md)
# dos.library — AmigaDOS Overview
## Section Index
| File | Description |
|---|---|
| [dos_base.md](dos_base.md) | DosLibrary structure and global state |
| [file_io.md](file_io.md) | Open, Close, Read, Write, Seek |
| [locks_examine.md](locks_examine.md) | Lock, UnLock, Examine, ExNext, ExAll |
| [pattern_matching.md](pattern_matching.md) | ParsePattern, MatchPattern, wildcards |
| [process_management.md](process_management.md) | CreateNewProc, SystemTagList, Execute |
| [packet_system.md](packet_system.md) | DosPacket, ACTION_* codes, handler protocol |
| [filesystem.md](filesystem.md) | FFS/OFS layout, block structure |
| [environment.md](environment.md) | GetVar/SetVar, local/global env variables |
| [error_handling.md](error_handling.md) | IoErr, Fault, PrintFault, error codes |
| [cli_shell.md](cli_shell.md) | CLI/Shell: pipes, redirection, scripts, ReadArgs |

176
07_dos/cli_shell.md Normal file
View file

@ -0,0 +1,176 @@
[← Home](../README.md) · [AmigaDOS](README.md)
# CLI and Shell — Command Interpreter
## Overview
The AmigaDOS **Shell** (called CLI in OS 1.x) is the command-line interface. It processes scripts, handles I/O redirection, pipes, and environment variable expansion. Understanding Shell internals is essential for writing scripts and understanding process creation.
---
## Shell vs CLI
| Feature | CLI (OS 1.x) | Shell (OS 2.0+) |
|---|---|---|
| Command history | No | Yes |
| Line editing | Minimal | Full (cursor keys, delete) |
| Pipes | No | Yes (`|` and `|&`) |
| Wildcards | Manual `#?` | Automatic in `Dir`, `List`, etc. |
| Resident commands | No | Yes (`Resident` command) |
| Background execution | `Run` only | `Run` + `&` suffix |
---
## I/O Redirection
```
; Redirect output to file:
Dir >RAM:listing.txt SYS:
; Redirect input from file:
Type <RAM:data.txt
; Append output:
Echo "log entry" >>RAM:log.txt
; Discard output:
Copy >NIL: file1 file2
; Redirect stderr (error output) — OS 2.0+:
command *>RAM:errors.txt
```
---
## Pipes
```
; Pipe output of one command to input of another:
List SYS:C | Sort | More
; Pipe both stdout and stderr:
command |& Filter
```
Pipes create temporary files in `T:` (assigned to `RAM:T`). Not true Unix-style byte streams.
---
## Script Execution
Scripts are text files executed line by line. Use `.` or `Execute`:
```
; Run a script:
Execute S:MyScript
; Or make executable and run directly (with Execute bit set):
Protect S:MyScript +s
S:MyScript
```
### Script Control Structures
```
; Conditional:
IF EXISTS SYS:Libs/68040.library
Echo "68040 detected"
ELSE
Echo "No 68040"
ENDIF
; Loop:
LAB loop
Echo "iteration"
SKIP loop
; Fail handling:
FailAt 21
Copy SYS:Missing RAM:
IF WARN
Echo "Copy had warnings"
ENDIF
```
---
## ReadArgs — Argument Parsing
AmigaDOS provides `ReadArgs()` for standardised argument parsing:
```c
/* Template syntax: KEYWORD/A = required, /S = switch, /K = keyword,
/N = numeric, /M = multi, /F = rest-of-line */
LONG args[4] = {0};
struct RDArgs *rd = ReadArgs("FROM/A,TO/A,ALL/S,BUFFER/K/N", args, NULL);
if (rd) {
char *from = (char *)args[0];
char *to = (char *)args[1];
BOOL all = (BOOL)args[2];
LONG *bufsize = (LONG *)args[3];
FreeArgs(rd);
}
```
### Template Format
| Qualifier | Meaning | Example |
|---|---|---|
| `/A` | Required argument | `FROM/A` |
| `/K` | Keyword (must use keyword=value) | `PUBSCREEN/K` |
| `/S` | Switch (boolean flag) | `ALL/S` |
| `/N` | Numeric value | `BUF/N` |
| `/M` | Multiple values (array) | `FILES/M` |
| `/F` | Rest of line (to end) | `CMD/F` |
| `=` | Alias | `FILE=FROM/A` |
---
## Resident Commands
Frequently used commands can be made **resident** (kept in RAM):
```
; Make a command resident:
Resident C:Dir PURE
Resident C:List PURE
Resident C:Copy PURE
; List resident commands:
Resident
; Remove:
Resident C:Dir REMOVE
```
Requires the binary to be compiled as `PURE` (position-independent, no writeable globals in code section).
---
## Built-in Shell Commands
| Command | Description |
|---|---|
| `CD` | Change directory |
| `Echo` | Print text |
| `If/Else/EndIf` | Conditional |
| `Skip/Lab` | Loop |
| `FailAt` | Set error threshold |
| `Set/Unset` | Local variables |
| `SetEnv/GetEnv` | Global (ENV:) variables |
| `Alias` | Command aliases |
| `Path` | Manage command search path |
| `Prompt` | Set shell prompt |
| `Protect` | Set file protection bits |
| `Run` | Execute command in background |
| `Execute` | Run script file |
| `EndCLI` | Close this shell |
| `NewCLI`/`NewShell` | Open new shell |
---
## References
- NDK39: `dos/rdargs.h`
- RKRM: Shell chapter
- ADCD 2.1: AmigaDOS Guide

77
07_dos/dos_base.md Normal file
View file

@ -0,0 +1,77 @@
[← Home](../README.md) · [AmigaDOS](README.md)
# DosLibrary — Structure and Global State
## Overview
`dos.library` is opened by nearly every Amiga program. The library base `DOSBase` is a `struct DosLibrary` that extends `struct Library` with process-level state, the root node, and handler management.
---
## struct DosLibrary
```c
/* dos/dosextens.h — NDK39 */
struct DosLibrary {
struct Library dl_lib; /* standard Library header */
struct RootNode *dl_Root; /* pointer to the system root node */
APTR dl_GV; /* BCPL global vector */
LONG dl_A2; /* BCPL scratch */
LONG dl_A5; /* BCPL scratch */
LONG dl_A6; /* BCPL scratch */
struct ErrorString *dl_Errors; /* error string table */
struct timerequest *dl_TimeReq; /* timer.device request */
struct Library *dl_UtilityBase; /* cached UtilityBase */
struct Library *dl_IntuitionBase;/* cached IntuitionBase */
};
```
---
## struct RootNode
```c
struct RootNode {
BPTR rn_TaskArray; /* BPTR to CLI task array */
BPTR rn_ConsoleSegment; /* console handler segment */
struct DateStamp rn_Time; /* current system time */
LONG rn_RestartSeg; /* restart segment */
BPTR rn_Info; /* BPTR to DosInfo */
BPTR rn_FileHandlerSegment;
struct MinList rn_CliList; /* list of CLI processes */
/* ... OS 3.x additions ... */
ULONG rn_BootFlags;
APTR rn_BootProc;
};
```
---
## Opening dos.library
```c
struct DosLibrary *DOSBase;
DOSBase = (struct DosLibrary *)OpenLibrary("dos.library", 40);
if (!DOSBase) { /* OS 3.1 or later required */ }
```
---
## BCPL Heritage
AmigaDOS was originally written in BCPL. Artifacts remain:
- **BPTR** — pointers right-shifted by 2 (`real = bptr << 2`)
- `dl_GV` — BCPL global vector (used internally by filesystem handlers)
- File handles and locks are BPTRs, not real pointers
```c
/* Convert BPTR to C pointer: */
#define BADDR(bptr) ((APTR)((ULONG)(bptr) << 2))
```
---
## References
- NDK39: `dos/dosextens.h`, `dos/dos.h`
- ADCD 2.1: dos.library autodocs

67
07_dos/environment.md Normal file
View file

@ -0,0 +1,67 @@
[← Home](../README.md) · [AmigaDOS](README.md)
# Environment Variables — GetVar, SetVar
## Overview
AmigaDOS supports both **local** (per-process) and **global** (system-wide) environment variables. Local variables are stored in the process's `pr_LocalVars` MinList; global variables are stored as files in `ENV:` (RAM-backed) and `ENVARC:` (persistent on disk).
---
## API
| LVO | Function | Description |
|---|---|---|
| 900 | `GetVar(name, buf, size, flags)` | Read a variable |
| 906 | `SetVar(name, buf, size, flags)` | Set or create a variable |
| 912 | `DeleteVar(name, flags)` | Remove a variable |
| 918 | `FindVar(name, type)` | Find a LocalVar node |
### Flags
```c
/* dos/var.h — NDK39 */
#define GVF_GLOBAL_ONLY 0x100 /* search only ENV: */
#define GVF_LOCAL_ONLY 0x200 /* search only pr_LocalVars */
#define GVF_BINARY_VAR 0x10 /* treat value as binary data */
#define GVF_DONT_NULL_TERM 0x20 /* don't null-terminate result */
#define LV_VAR 0 /* standard variable */
#define LV_ALIAS 1 /* shell alias */
```
---
## Usage
```c
/* Set a global variable: */
SetVar("EDITOR", "ed", -1, GVF_GLOBAL_ONLY);
/* Read it back: */
char buf[256];
if (GetVar("EDITOR", buf, sizeof(buf), 0) >= 0) {
Printf("EDITOR = %s\n", buf);
}
/* Delete: */
DeleteVar("EDITOR", GVF_GLOBAL_ONLY);
```
---
## Storage Locations
| Scope | Storage | Persistent? |
|---|---|---|
| Local | `pr_LocalVars` MinList (in-memory) | No — dies with process |
| Global (volatile) | `ENV:` — assign to `RAM:Env/` | No — lost on reboot |
| Global (persistent) | `ENVARC:` — assign to `SYS:Prefs/Env-Archive/` | Yes — survives reboot |
The startup-sequence copies `ENVARC:` to `ENV:` at boot.
---
## References
- NDK39: `dos/var.h`
- ADCD 2.1: `GetVar`, `SetVar`, `DeleteVar`

89
07_dos/error_handling.md Normal file
View file

@ -0,0 +1,89 @@
[← Home](../README.md) · [AmigaDOS](README.md)
# Error Handling — IoErr, Fault, PrintFault, Error Codes
## Overview
Every DOS function that can fail sets an error code retrievable via `IoErr()`. The error code is stored in `pr_Result2` of the calling process.
---
## API
| LVO | Function | Description |
|---|---|---|
| 132 | `IoErr()` | Return last error code |
| 138 | `SetIoErr(code)` | Set error code manually |
| 468 | `Fault(code, header, buf, len)` | Format error message into buffer |
| 474 | `PrintFault(code, header)` | Print error message to stderr |
---
## Complete Error Code Table
| Code | Constant | Meaning |
|---|---|---|
| 103 | `ERROR_NO_FREE_STORE` | Out of memory |
| 104 | `ERROR_TASK_TABLE_FULL` | Process table full |
| 114 | `ERROR_BAD_TEMPLATE` | Bad template for ReadArgs |
| 115 | `ERROR_BAD_NUMBER` | Bad number in argument |
| 116 | `ERROR_REQUIRED_ARG_MISSING` | Required argument missing |
| 117 | `ERROR_KEY_NEEDS_ARG` | Keyword requires an argument |
| 118 | `ERROR_TOO_MANY_ARGS` | Too many arguments |
| 119 | `ERROR_UNMATCHED_QUOTES` | Unmatched quotes |
| 120 | `ERROR_LINE_TOO_LONG` | Argument line too long |
| 121 | `ERROR_FILE_NOT_OBJECT` | Not a valid executable |
| 122 | `ERROR_INVALID_RESIDENT_LIBRARY` | Invalid resident library |
| 202 | `ERROR_OBJECT_IN_USE` | Object is in use |
| 203 | `ERROR_OBJECT_EXISTS` | Object already exists |
| 204 | `ERROR_DIR_NOT_FOUND` | Directory not found |
| 205 | `ERROR_OBJECT_NOT_FOUND` | Object not found |
| 206 | `ERROR_BAD_STREAM_NAME` | Invalid stream name |
| 207 | `ERROR_OBJECT_TOO_LARGE` | Object too large |
| 209 | `ERROR_ACTION_NOT_KNOWN` | Action not known (by handler) |
| 210 | `ERROR_INVALID_COMPONENT_NAME` | Invalid filename component |
| 211 | `ERROR_INVALID_LOCK` | Invalid lock |
| 212 | `ERROR_OBJECT_WRONG_TYPE` | Object wrong type |
| 213 | `ERROR_DISK_NOT_VALIDATED` | Disk not validated |
| 214 | `ERROR_DISK_WRITE_PROTECTED` | Disk is write-protected |
| 215 | `ERROR_RENAME_ACROSS_DEVICES` | Rename across devices |
| 216 | `ERROR_DIRECTORY_NOT_EMPTY` | Directory not empty |
| 217 | `ERROR_TOO_MANY_LEVELS` | Too many directory levels |
| 218 | `ERROR_DEVICE_NOT_MOUNTED` | Device not mounted |
| 219 | `ERROR_SEEK_ERROR` | Seek error |
| 220 | `ERROR_COMMENT_TOO_BIG` | Comment too big |
| 221 | `ERROR_DISK_FULL` | Disk full |
| 222 | `ERROR_DELETE_PROTECTED` | Delete protected |
| 223 | `ERROR_WRITE_PROTECTED` | Write protected |
| 224 | `ERROR_READ_PROTECTED` | Read protected |
| 225 | `ERROR_NOT_A_DOS_DISK` | Not a DOS disk |
| 226 | `ERROR_NO_DISK` | No disk in drive |
| 232 | `ERROR_NO_MORE_ENTRIES` | No more directory entries |
| 233 | `ERROR_IS_SOFT_LINK` | Object is a soft link |
| 234 | `ERROR_OBJECT_LINKED` | Object is hard-linked |
| 235 | `ERROR_BAD_HUNK` | Bad hunk in executable |
| 236 | `ERROR_NOT_IMPLEMENTED` | Function not implemented |
| 240 | `ERROR_RECORD_NOT_LOCKED` | Record not locked |
| 241 | `ERROR_LOCK_COLLISION` | Record lock collision |
| 242 | `ERROR_LOCK_TIMEOUT` | Record lock timeout |
| 243 | `ERROR_UNLOCK_ERROR` | Unlock error |
---
## Usage Pattern
```c
BPTR fh = Open("nonexistent", MODE_OLDFILE);
if (!fh) {
LONG err = IoErr();
PrintFault(err, "myapp");
/* prints: "myapp: Object not found" */
}
```
---
## References
- NDK39: `dos/dos.h`, `dos/dosasl.h`
- ADCD 2.1: `IoErr`, `Fault`, `PrintFault`

107
07_dos/file_io.md Normal file
View file

@ -0,0 +1,107 @@
[← Home](../README.md) · [AmigaDOS](README.md)
# File I/O — Open, Close, Read, Write, Seek
## Overview
AmigaDOS file I/O is synchronous from the caller's perspective. All functions use `BPTR` file handles and communicate errors via `IoErr()`.
---
## Core Functions
| LVO | Function | Registers | Returns |
|---|---|---|---|
| 30 | `Open(name, mode)` | D1=name, D2=mode | D0=BPTR handle (0=fail) |
| 36 | `Close(fh)` | D1=handle | D0=BOOL |
| 42 | `Read(fh, buf, len)` | D1=handle, D2=buf, D3=len | D0=actual bytes (1=error) |
| 48 | `Write(fh, buf, len)` | D1=handle, D2=buf, D3=len | D0=actual bytes (1=error) |
| 66 | `Seek(fh, pos, mode)` | D1=handle, D2=pos, D3=mode | D0=old position (1=error) |
---
## Access Modes
```c
/* dos/dos.h — NDK39 */
#define MODE_READWRITE 1004 /* open existing, read+write */
#define MODE_OLDFILE 1005 /* open existing, read only */
#define MODE_NEWFILE 1006 /* create new (truncate if exists) */
```
---
## Seek Modes
```c
#define OFFSET_BEGINNING -1 /* from start of file */
#define OFFSET_CURRENT 0 /* from current position */
#define OFFSET_END 1 /* from end of file */
```
---
## File Handle (BPTR)
The returned handle is a BPTR to a `struct FileHandle`:
```c
struct FileHandle {
struct Message *fh_Link;
struct MsgPort *fh_Interactive; /* non-NULL if console */
struct MsgPort *fh_Type; /* handler process port */
BPTR fh_Buf; /* I/O buffer (BPTR) */
LONG fh_Pos; /* current position in buffer */
LONG fh_End; /* end of valid data in buffer */
LONG fh_Funcs; /* unused */
LONG fh_Func2; /* unused */
LONG fh_Func3; /* unused */
LONG fh_Args; /* packet args */
BPTR fh_Arg2;
};
```
To access as a C pointer: `struct FileHandle *fh = BADDR(handle);`
---
## Usage Example
```c
BPTR fh = Open("RAM:test.txt", MODE_NEWFILE);
if (fh) {
Write(fh, "Hello Amiga\n", 12);
Close(fh);
}
fh = Open("RAM:test.txt", MODE_OLDFILE);
if (fh) {
UBYTE buf[64];
LONG n = Read(fh, buf, sizeof(buf));
if (n > 0) Write(Output(), buf, n); /* echo to stdout */
Close(fh);
} else {
PrintFault(IoErr(), "Open failed");
}
```
---
## Error Checking
```c
LONG err = IoErr(); /* LVO 66 — returns last DOS error code */
/* Common codes: */
#define ERROR_OBJECT_NOT_FOUND 205
#define ERROR_OBJECT_EXISTS 203
#define ERROR_DISK_FULL 221
#define ERROR_SEEK_ERROR 219
```
---
## References
- NDK39: `dos/dos.h`, `dos/dosextens.h`
- `07_dos/error_handling.md` — full error code list
- ADCD 2.1: `Open`, `Close`, `Read`, `Write`, `Seek`

90
07_dos/filesystem.md Normal file
View file

@ -0,0 +1,90 @@
[← Home](../README.md) · [AmigaDOS](README.md)
# Filesystem — FFS/OFS Block Structure
## Overview
AmigaOS supports two native filesystem types: **OFS** (Old File System, OS 1.x) and **FFS** (Fast File System, OS 2.0+). Both use a block-based layout with 512-byte blocks. FFS differs by storing data blocks without headers, improving throughput.
---
## Block Types
| Block | Type ID | Description |
|---|---|---|
| Boot block | `"DOS\0"` / `"DOS\1"` | Blocks 01; OFS=`DOS\0`, FFS=`DOS\1` |
| Root block | `T_HEADER` (2) | Always at middle of partition; directory root |
| File header | `T_HEADER` (2) | Metadata for one file |
| Directory header | `T_HEADER` (2) | Metadata for one directory |
| Data block | `T_DATA` (8) | OFS: header + data; FFS: pure data |
| File extension | `T_LIST` (16) | Overflow pointer table for large files |
| Hash chain | — | Root/dir blocks have a 72-entry hash table |
---
## Root Block Layout (Simplified)
| Offset | Field | Description |
|---|---|---|
| 0 | `type` | Always 2 (`T_HEADER`) |
| 4 | `header_key` | Own block number |
| 8 | `high_seq` | Number of data blocks in hash table |
| 12 | `ht_size` | Hash table size (usually 72) |
| 16 | `first_data` | Unused |
| 20 | `checksum` | Block checksum |
| 24312 | `ht[72]` | Hash table: block pointers for directory entries |
| 420 | `bm_flag` | Bitmap valid flag (`-1` = valid) |
| 424472 | `bm_pages[25]` | Pointers to bitmap blocks |
| 484 | `last_altered_days` | Modification date |
| 504 | `disk_name` | BSTR: volume name |
---
## Hash Function
File/directory names are hashed into the 72-slot table:
```c
ULONG hash_name(const char *name, int table_size) {
ULONG hash = strlen(name);
for (int i = 0; name[i]; i++) {
hash = hash * 13 + toupper(name[i]);
hash &= 0x7FF;
}
return hash % table_size;
}
```
Collisions are resolved by chaining: each file/dir header has a `hash_chain` pointer to the next entry in the same slot.
---
## OFS vs FFS
| Feature | OFS (`DOS\0`) | FFS (`DOS\1`) |
|---|---|---|
| Data blocks | 24-byte header + 488 bytes data | Pure 512 bytes data |
| Max filename | 30 chars | 30 chars |
| International | No | `DOS\2` (INTL OFS), `DOS\3` (INTL FFS) |
| Dir cache | No | `DOS\4` (FFS + dir cache) |
| Throughput | ~488/512 = 95% efficiency | 100% efficiency |
---
## Checksum Algorithm
```c
LONG compute_checksum(ULONG *block, int longs) {
LONG sum = 0;
for (int i = 0; i < longs; i++) sum += block[i];
return -sum; /* stored in the checksum field to make total = 0 */
}
```
---
## References
- NDK39: `dos/filehandler.h`
- Ralph Babel: *The AmigaDOS Manual* (3rd edition) — definitive FFS reference
- Laurent Clevy: *The Amiga Filesystem* — http://lclevy.free.fr/adflib/

126
07_dos/locks_examine.md Normal file
View file

@ -0,0 +1,126 @@
[← Home](../README.md) · [AmigaDOS](README.md)
# Locks and Examine — Lock, UnLock, Examine, ExNext, ExAll
## Overview
AmigaDOS uses **locks** to reference files and directories. A lock is a BPTR to a `FileLock` structure. Locks provide exclusive or shared access and are used for directory scanning, attribute reading, and path resolution.
---
## Lock Types
```c
/* dos/dos.h */
#define SHARED_LOCK -2 /* read-only; multiple readers allowed */
#define ACCESS_READ SHARED_LOCK
#define EXCLUSIVE_LOCK -1 /* read/write; only one holder */
#define ACCESS_WRITE EXCLUSIVE_LOCK
```
---
## Core Functions
| LVO | Function | Registers | Returns |
|---|---|---|---|
| 84 | `Lock(name, mode)` | D1=name, D2=mode | D0=lock BPTR (0=fail) |
| 90 | `UnLock(lock)` | D1=lock | — |
| 96 | `DupLock(lock)` | D1=lock | D0=new lock |
| 102 | `Examine(lock, fib)` | D1=lock, D2=fib | D0=BOOL |
| 108 | `ExNext(lock, fib)` | D1=lock, D2=fib | D0=BOOL |
| 78 | `CurrentDir(lock)` | D1=lock | D0=old lock |
| 654 | `ExAll(lock, buf, size, type, ctrl)` | D1D5 | D0=BOOL |
---
## struct FileInfoBlock
```c
/* dos/dos.h — NDK39 */
struct FileInfoBlock {
LONG fib_DiskKey; /* handler-private key */
LONG fib_DirEntryType; /* >0 = directory, <0 = file */
char fib_FileName[108]; /* null-terminated name */
LONG fib_Protection; /* rwed bits */
LONG fib_EntryType; /* same as DirEntryType */
LONG fib_Size; /* file size in bytes */
LONG fib_NumBlocks; /* blocks used */
struct DateStamp fib_Date; /* modification date */
char fib_Comment[80]; /* file comment string */
UWORD fib_OwnerUID;
UWORD fib_OwnerGID;
char fib_Reserved[32];
};
```
> [!IMPORTANT]
> `FileInfoBlock` must be longword-aligned. Use `AllocDosObject(DOS_FIB, NULL)` on OS 2.0+ or `AllocMem(sizeof(struct FileInfoBlock), MEMF_PUBLIC)`.
---
## Protection Bits
```c
/* dos/dos.h */
#define FIBF_SCRIPT (1<<6) /* s script (executable script) */
#define FIBF_PURE (1<<5) /* p pure (re-entrant) */
#define FIBF_ARCHIVE (1<<4) /* a archived */
#define FIBF_READ (1<<3) /* r readable (0=allowed, 1=denied!) */
#define FIBF_WRITE (1<<2) /* w writable */
#define FIBF_EXECUTE (1<<1) /* e executable */
#define FIBF_DELETE (1<<0) /* d deletable */
```
> [!WARNING]
> Amiga protection bits are **inverted** from Unix: bit SET means access is **denied**.
---
## Directory Scanning
```c
BPTR lock = Lock("SYS:", SHARED_LOCK);
struct FileInfoBlock *fib = AllocDosObject(DOS_FIB, NULL);
if (Examine(lock, fib)) { /* read dir's own info */
while (ExNext(lock, fib)) { /* iterate entries */
Printf("%-30s %8ld %s\n",
fib->fib_FileName,
fib->fib_Size,
fib->fib_DirEntryType > 0 ? "(dir)" : "");
}
/* ExNext returns FALSE when done; IoErr() == ERROR_NO_MORE_ENTRIES */
}
FreeDosObject(DOS_FIB, fib);
UnLock(lock);
```
---
## ExAll (OS 2.0+) — Bulk Scan
```c
struct ExAllControl *eac = AllocDosObject(DOS_EXALLCONTROL, NULL);
UBYTE buf[4096];
BOOL more;
eac->eac_LastKey = 0;
do {
more = ExAll(lock, buf, sizeof(buf), ED_NAME, eac);
struct ExAllData *ead = (struct ExAllData *)buf;
while (ead) {
Printf("%s\n", ead->ed_Name);
ead = ead->ed_Next;
}
} while (more);
FreeDosObject(DOS_EXALLCONTROL, eac);
```
---
## References
- NDK39: `dos/dos.h`, `dos/dosextens.h`, `dos/exall.h`
- ADCD 2.1: `Lock`, `UnLock`, `Examine`, `ExNext`, `ExAll`

93
07_dos/packet_system.md Normal file
View file

@ -0,0 +1,93 @@
[← Home](../README.md) · [AmigaDOS](README.md)
# Packet System — DosPacket, ACTION_* Codes
## Overview
AmigaDOS filesystem handlers communicate via **DosPackets** — messages sent to the handler's `MsgPort`. Every file operation (`Open`, `Read`, `Lock`, etc.) is internally translated into an `ACTION_*` packet. Understanding packets is essential for writing custom handlers or intercepting filesystem calls.
---
## struct DosPacket
```c
/* dos/dosextens.h — NDK39 */
struct DosPacket {
struct Message *dp_Link; /* exec message (backlink) */
struct MsgPort *dp_Port; /* reply port */
LONG dp_Type; /* ACTION_* code */
LONG dp_Res1; /* primary result */
LONG dp_Res2; /* secondary result (error code) */
LONG dp_Arg1; /* argument 1 — type depends on dp_Type */
LONG dp_Arg2;
LONG dp_Arg3;
LONG dp_Arg4;
LONG dp_Arg5;
LONG dp_Arg6;
LONG dp_Arg7;
};
```
---
## Common ACTION_* Codes
| Code | Dec | Action | Args |
|---|---|---|---|
| `ACTION_FINDINPUT` | 1005 | Open for reading | Arg1=FileHandle, Arg2=Lock, Arg3=name(BSTR) |
| `ACTION_FINDOUTPUT` | 1006 | Open for writing (create) | same |
| `ACTION_FINDUPDATE` | 1004 | Open for r/w | same |
| `ACTION_READ` | 82 | Read bytes | Arg1=FH_Arg1, Arg2=buf, Arg3=len |
| `ACTION_WRITE` | 87 | Write bytes | Arg1=FH_Arg1, Arg2=buf, Arg3=len |
| `ACTION_SEEK` | 1008 | Seek | Arg1=FH_Arg1, Arg2=pos, Arg3=mode |
| `ACTION_END` | 1007 | Close file | Arg1=FH_Arg1 |
| `ACTION_LOCATE_OBJECT` | 8 | Lock (obtain) | Arg1=dirLock, Arg2=name(BSTR), Arg3=mode |
| `ACTION_FREE_LOCK` | 15 | UnLock | Arg1=lock |
| `ACTION_EXAMINE_OBJECT` | 23 | Examine (stat) | Arg1=lock, Arg2=FIB(BPTR) |
| `ACTION_EXAMINE_NEXT` | 24 | ExNext | Arg1=lock, Arg2=FIB(BPTR) |
| `ACTION_PARENT` | 29 | ParentDir | Arg1=lock |
| `ACTION_DELETE_OBJECT` | 16 | Delete | Arg1=lock, Arg2=name(BSTR) |
| `ACTION_RENAME_OBJECT` | 17 | Rename | Arg1=fromLock, Arg2=fromName, Arg3=toLock, Arg4=toName |
| `ACTION_CREATE_DIR` | 22 | CreateDir | Arg1=lock, Arg2=name(BSTR) |
| `ACTION_SET_PROTECT` | 21 | SetProtection | Arg1=0, Arg2=lock, Arg3=name(BSTR), Arg4=bits |
| `ACTION_DISK_INFO` | 25 | Info | Arg1=InfoData(BPTR) |
| `ACTION_IS_FILESYSTEM` | 1027 | Query | (none) → Res1=DOSTRUE if filesystem |
---
## Sending a Packet Manually
```c
struct MsgPort *handler = ((struct FileLock *)BADDR(lock))->fl_Task;
struct StandardPacket sp;
sp.sp_Msg.mn_Node.ln_Name = (char *)&sp.sp_Pkt;
sp.sp_Pkt.dp_Link = &sp.sp_Msg;
sp.sp_Pkt.dp_Port = CreateMsgPort();
sp.sp_Pkt.dp_Type = ACTION_DISK_INFO;
sp.sp_Pkt.dp_Arg1 = MKBADDR(infodata);
PutMsg(handler, &sp.sp_Msg);
WaitPort(sp.sp_Pkt.dp_Port);
GetMsg(sp.sp_Pkt.dp_Port);
/* sp.sp_Pkt.dp_Res1 = result */
DeleteMsgPort(sp.sp_Pkt.dp_Port);
```
---
## BSTR — BCPL Strings
Handler packets use **BSTR** for filenames: a BPTR to a length-prefixed string:
```
[len_byte][char_0][char_1]...[char_n]
```
- `len_byte` = string length (max 255)
- No null terminator
- Convert: `UBYTE *bstr = (UBYTE *)BADDR(bstr_bptr); int len = bstr[0]; char *name = &bstr[1];`
---
## References
- NDK39: `dos/dosextens.h`, `dos/dos.h`
- ADCD 2.1: `DoPkt`, packet system
- *Amiga ROM Kernel Reference Manual: Devices* — filesystem handler chapter

View file

@ -0,0 +1,77 @@
[← Home](../README.md) · [AmigaDOS](README.md)
# Pattern Matching — ParsePattern, MatchPattern
## Overview
AmigaDOS provides built-in wildcard/pattern matching for file operations. Patterns are compiled into token streams via `ParsePattern` and matched via `MatchPattern`.
---
## Wildcard Syntax
| Pattern | Meaning | Example |
|---|---|---|
| `?` | Match exactly one character | `file?.txt` matches `file1.txt` |
| `#` | Match zero or more of the following | `#?.info` matches anything ending in `.info` |
| `#?` | Match any string (equivalent to `*`) | `#?` matches everything |
| `(a\|b)` | Alternation — match a or b | `(read\|write)` |
| `~` | Negation — match if NOT | `~(#?.info)` matches non-info files |
| `[abc]` | Character class | `[abc]` matches a, b, or c |
| `[a-z]` | Character range | `[0-9]` matches digits |
| `'` | Quote next character literally | `'#` matches literal `#` |
---
## API
```c
/* dos/dos.h — NDK39 */
/* Compile a pattern into tokenised form: */
LONG ParsePattern(STRPTR pat, STRPTR buf, LONG buflen);
/* Returns: 1 = pattern has wildcards, 0 = plain string, -1 = error */
/* Test a name against a compiled pattern: */
BOOL MatchPattern(STRPTR pat_compiled, STRPTR name);
/* Case-insensitive variants: */
LONG ParsePatternNoCase(STRPTR pat, STRPTR buf, LONG buflen);
BOOL MatchPatternNoCase(STRPTR pat_compiled, STRPTR name);
```
---
## Usage Example
```c
char pat[256], buf[256];
LONG is_wild;
is_wild = ParsePatternNoCase("#?.txt", pat, sizeof(pat));
if (is_wild >= 0) {
if (MatchPatternNoCase(pat, "readme.txt"))
Printf("Match!\n");
if (!MatchPatternNoCase(pat, "readme.doc"))
Printf("No match\n");
}
```
---
## Common Patterns
| Pattern | Matches |
|---|---|
| `#?` | Everything (wildcard all) |
| `#?.info` | All `.info` icon files |
| `~(#?.info)` | Everything except `.info` files |
| `(#?.c\|#?.h)` | All C source and header files |
| `file[0-9]` | `file0` through `file9` |
---
## References
- NDK39: `dos/dos.h`
- ADCD 2.1: `ParsePattern`, `MatchPattern`

View file

@ -0,0 +1,89 @@
[← Home](../README.md) · [AmigaDOS](README.md)
# Process Management — CreateNewProc, SystemTagList, Execute
## Overview
AmigaDOS provides several ways to launch child processes, ranging from the low-level `CreateNewProc` to the shell-level `Execute` and `SystemTagList`.
---
## CreateNewProcTags (OS 2.0+)
```c
/* dos/dostags.h — NDK39 */
struct Process *proc = CreateNewProcTags(
NP_Entry, myFunction, /* function pointer */
NP_Name, "Worker", /* task name */
NP_StackSize, 8192, /* stack size in bytes */
NP_Priority, 0, /* scheduling priority */
NP_Input, Open("NIL:", MODE_OLDFILE),
NP_Output, Open("NIL:", MODE_NEWFILE),
NP_CloseInput, TRUE, /* close input on exit */
NP_CloseOutput, TRUE,
NP_CurrentDir, DupLock(currentDir),
TAG_DONE);
```
### Tag Constants
| Tag | Value | Meaning |
|---|---|---|
| `NP_Entry` | — | Function to run as the new process |
| `NP_Seglist` | — | Alternative: run from a loaded segment list |
| `NP_Name` | — | Process name (appears in task list) |
| `NP_StackSize` | — | Stack size in bytes (default 4096) |
| `NP_Priority` | — | Task priority (128 to +127) |
| `NP_Input` | — | BPTR stdin handle |
| `NP_Output` | — | BPTR stdout handle |
| `NP_Error` | — | BPTR stderr handle (OS 3.0+) |
| `NP_CurrentDir` | — | Lock for current directory |
| `NP_HomeDir` | — | Lock for PROGDIR: |
| `NP_CopyVars` | — | Copy parent's local vars to child |
---
## SystemTagList — Run a Shell Command
```c
/* Execute a command string as if typed in a shell: */
LONG rc = SystemTagList("dir SYS: ALL", NULL);
/* rc = return code from the command */
/* With custom I/O: */
LONG rc = SystemTagList("list RAM:", (struct TagItem[]){
{ SYS_Input, Open("NIL:", MODE_OLDFILE) },
{ SYS_Output, Open("RAM:output.txt", MODE_NEWFILE) },
{ TAG_DONE, 0 }
});
```
---
## Execute — Legacy Command Execution
```c
/* dos.library LVO 132 */
BOOL Execute(STRPTR command, BPTR input, BPTR output);
```
- `command` — shell command string
- `input` — BPTR to additional input (0 = none)
- `output` — BPTR to output handle (0 = current)
---
## WaitForChild / Process Exit
Child processes are independent tasks. To synchronize:
1. Use a shared `MsgPort` — child sends a death message
2. Check `pr_Result2` after the child task exits
3. Use `SYS_Asynch` tag with `SystemTagList` for fire-and-forget
---
## References
- NDK39: `dos/dostags.h`, `dos/dosextens.h`
- ADCD 2.1: `CreateNewProc`, `SystemTagList`, `Execute`
- `06_exec_os/tasks_processes.md` — Task/Process structures

21
08_graphics/README.md Normal file
View file

@ -0,0 +1,21 @@
[← Home](../README.md)
# Graphics Subsystem — Overview
## Section Index
| File | Description |
|---|---|
| [gfx_base.md](gfx_base.md) | GfxBase structure and global graphics state |
| [bitmap.md](bitmap.md) | BitMap structure, planar layout, allocation |
| [copper.md](copper.md) | Copper coprocessor, instruction format, UCopList |
| [blitter.md](blitter.md) | Blitter DMA engine, minterms, BltBitMap |
| [sprites.md](sprites.md) | Hardware sprites, SimpleSprite, MoveSprite |
| [rastport.md](rastport.md) | RastPort, drawing primitives, layers |
| [views.md](views.md) | View, ViewPort, MakeVPort, display construction |
| [text_fonts.md](text_fonts.md) | TextFont, TextAttr, OpenFont, Text rendering |
| [display_modes.md](display_modes.md) | Display database, ModeID, monitor specs |
| [ham_ehb_modes.md](ham_ehb_modes.md) | HAM6, HAM8, and EHB special display modes |
| [animation.md](animation.md) | AnimOb, BOB, VSprite, GEL system |
| [copper_programming.md](copper_programming.md) | Copper deep dive: architecture, examples, system API |
| [blitter_programming.md](blitter_programming.md) | Blitter deep dive: minterms, cookie-cut, line draw |

110
08_graphics/animation.md Normal file
View file

@ -0,0 +1,110 @@
[← Home](../README.md) · [Graphics](README.md)
# Animation — GEL System: BOBs, VSprites, AnimObs
## Overview
The **GEL (Graphics ELement)** system provides high-level animated sprite and bitmap overlay support. It manages VSprites (virtual sprites that can use hardware or software rendering), BOBs (Blitter OBjects — arbitrary-sized bitmaps overlaid on a playfield), and AnimObs (animation objects with sequencing).
---
## VSprite
```c
/* graphics/gels.h — NDK39 */
struct VSprite {
struct VSprite *NextVSprite;
struct VSprite *PrevVSprite;
struct VSprite *DrawPath;
struct VSprite *ClearPath;
WORD OldY, OldX; /* previous position */
WORD Flags; /* VSPRITE, SAVEBACK, OVERLAY, MUSTDRAW */
WORD Y, X; /* current position */
WORD Height;
WORD Width; /* width in words */
WORD Depth;
WORD MeMask; /* collision mask */
WORD HitMask;
WORD *ImageData; /* sprite image */
WORD *BorderLine; /* collision border */
WORD *CollMask; /* collision mask data */
WORD *SprColors; /* colour table */
struct Bob *VSBob; /* if this VSprite backs a BOB */
BYTE PlanePick;
BYTE PlaneOnOff;
/* ... */
};
```
---
## BOB (Blitter Object)
```c
struct Bob {
WORD Flags;
WORD *SaveBuffer; /* background save buffer */
WORD *ImageShadow; /* shadow mask for cookie-cut */
struct Bob *Before;
struct Bob *After;
struct VSprite *BobVSprite; /* associated VSprite */
struct AnimComp *BobComp; /* if part of animation */
struct DBufPacket *DBuffer; /* double-buffer packet */
};
```
---
## Usage Pattern
```c
struct GelsInfo gi;
struct VSprite headVS, tailVS;
/* Initialise GEL system: */
InitGels(&headVS, &tailVS, &gi);
rp->GelsInfo = &gi;
/* Add a VSprite/BOB: */
AddVSprite(&myVSprite, rp);
/* or */
AddBob(&myBob, rp);
/* Each frame: */
SortGList(rp); /* sort by Y position */
DrawGList(rp, &vp); /* render all GELs */
WaitTOF(); /* sync to vertical blank */
/* Cleanup: */
RemVSprite(&myVSprite);
/* or */
RemBob(&myBob);
```
---
## AnimOb — Animation Sequences
```c
struct AnimOb {
struct AnimOb *NextOb;
struct AnimOb *PrevOb;
LONG Clock; /* frame counter */
WORD AnOldY, AnOldX;
WORD AnY, AnX; /* current position */
WORD YVel, XVel; /* velocity */
WORD YAccel, XAccel; /* acceleration */
WORD RingYTrans; /* ring buffer Y translation */
WORD RingXTrans;
struct AnimComp *HeadComp; /* component chain */
/* ... */
};
```
---
## References
- NDK39: `graphics/gels.h`, `graphics/gelsinternal.h`
- ADCD 2.1: `InitGels`, `AddVSprite`, `AddBob`, `SortGList`, `DrawGList`
- *Amiga ROM Kernel Reference Manual: Libraries* — GELs chapter

117
08_graphics/bitmap.md Normal file
View file

@ -0,0 +1,117 @@
[← Home](../README.md) · [Graphics](README.md)
# BitMap — Planar Bitmap Structure and Layout
## Overview
Amiga display memory uses **planar** layout: each bitplane is a separate contiguous memory region. A pixel's colour index is formed by reading one bit from each plane at the same x,y position. This is fundamentally different from chunky (packed-pixel) or interleaved formats.
---
## struct BitMap
```c
/* graphics/gfx.h — NDK39 */
struct BitMap {
UWORD BytesPerRow; /* bytes per row per plane (must be even) */
UWORD Rows; /* height in pixels */
UBYTE Flags; /* BMF_* flags */
UBYTE Depth; /* number of bitplanes (18) */
UWORD pad;
PLANEPTR Planes[8]; /* pointers to each bitplane buffer */
};
```
---
## BMF_ Flags
```c
#define BMF_CLEAR (1<<0) /* clear planes on allocation */
#define BMF_DISPLAYABLE (1<<1) /* allocated in displayable (Chip) RAM */
#define BMF_INTERLEAVED (1<<2) /* planes are interleaved in memory */
#define BMF_STANDARD (1<<3) /* use standard allocation */
#define BMF_MINPLANES (1<<4) /* minimum number of planes */
```
---
## Planar Memory Layout
For a 320×256×4 display (16 colours):
```
BytesPerRow = 320/8 = 40 bytes
Rows = 256
Depth = 4
Plane 0: 40 × 256 = 10,240 bytes (bit 0 of colour index)
Plane 1: 40 × 256 = 10,240 bytes (bit 1)
Plane 2: 40 × 256 = 10,240 bytes (bit 2)
Plane 3: 40 × 256 = 10,240 bytes (bit 3)
Total = 4 × 10,240 = 40,960 bytes
```
Pixel colour at (x, y):
```
bit0 = (Planes[0][y * BytesPerRow + x/8] >> (7 - x%8)) & 1
bit1 = (Planes[1][y * BytesPerRow + x/8] >> (7 - x%8)) & 1
bit2 = (Planes[2][y * BytesPerRow + x/8] >> (7 - x%8)) & 1
bit3 = (Planes[3][y * BytesPerRow + x/8] >> (7 - x%8)) & 1
colour_index = (bit3 << 3) | (bit2 << 2) | (bit1 << 1) | bit0
```
---
## Allocation
```c
/* OS 3.0+ — AllocBitMap: */
struct BitMap *bm = AllocBitMap(320, 256, 4,
BMF_CLEAR | BMF_DISPLAYABLE, NULL);
/* Always in Chip RAM when BMF_DISPLAYABLE */
/* Manual allocation (OS 1.x compatible): */
struct BitMap bm;
InitBitMap(&bm, 4, 320, 256);
for (int i = 0; i < 4; i++)
bm.Planes[i] = AllocRaster(320, 256); /* MEMF_CHIP */
/* Free: */
FreeBitMap(bm); /* or FreeRaster per plane */
```
---
## Interleaved BitMaps
With `BMF_INTERLEAVED`, all planes are stored sequentially row by row:
```
Row 0, Plane 0: 40 bytes
Row 0, Plane 1: 40 bytes
Row 0, Plane 2: 40 bytes
Row 0, Plane 3: 40 bytes
Row 1, Plane 0: 40 bytes
...
```
BytesPerRow becomes `40 × Depth = 160`, and each `Planes[i]` pointer is offset by `i * 40` from the base. This layout is more cache-friendly and allows single-pass blits.
---
## AGA 8-Bit Bitmaps
AGA (A1200/A4000) supports up to 8 bitplanes = 256 colours:
```c
struct BitMap *bm = AllocBitMap(320, 256, 8, BMF_CLEAR | BMF_DISPLAYABLE, NULL);
/* 8 planes × 10,240 = 81,920 bytes of Chip RAM */
```
---
## References
- NDK39: `graphics/gfx.h`
- ADCD 2.1: `AllocBitMap`, `FreeBitMap`, `InitBitMap`
- HRM: *Amiga Hardware Reference Manual* — bitplane DMA chapter

109
08_graphics/blitter.md Normal file
View file

@ -0,0 +1,109 @@
[← Home](../README.md) · [Graphics](README.md)
# Blitter — DMA Engine, Minterms, BltBitMap
## Overview
The **Blitter** is a DMA engine in the custom chips that performs bulk memory operations: block copies, line drawing, area fills, and arbitrary boolean combinations of up to three source bitmaps. It operates independently of the CPU, freeing the 68k for other work.
---
## Channels
The blitter has four DMA channels:
| Channel | Name | Direction | Description |
|---|---|---|---|
| A | Source A | Read | First source bitmap |
| B | Source B | Read | Second source (often mask/pattern) |
| C | Source C | Read | Third source (typically destination for read-modify-write) |
| D | Destination | Write | Output |
Each channel has: pointer register, modulo register, shift register (A/B only), and first/last word masks (A only).
---
## Minterm Logic
The blitter combines A, B, C inputs using an 8-bit **minterm** value. Each bit selects whether the output is 1 for a specific combination:
| Bit | A | B | C | Common Use |
|---|---|---|---|---|
| 7 | 1 | 1 | 1 | — |
| 6 | 1 | 1 | 0 | — |
| 5 | 1 | 0 | 1 | — |
| 4 | 1 | 0 | 0 | — |
| 3 | 0 | 1 | 1 | — |
| 2 | 0 | 1 | 0 | — |
| 1 | 0 | 0 | 1 | — |
| 0 | 0 | 0 | 0 | — |
Common minterm values:
| Minterm | Hex | Operation |
|---|---|---|
| `$F0` | `A` | Copy A to D (straight copy) |
| `$CA` | `AB + ~AC` | Cookie-cut: A=mask, B=source, C=background |
| `$3C` | `A XOR C` | XOR blit (sprite toggle) |
| `$0F` | `NOT A` | Invert source |
| `$00` | `0` | Clear destination |
| `$FF` | `1` | Fill destination with 1s |
---
## Register Map
| Address | Name | Description |
|---|---|---|
| `$DFF040` | `BLTCON0` | Control: use-channels + minterm + shift-A |
| `$DFF042` | `BLTCON1` | Control: direction, fill mode, line mode |
| `$DFF044` | `BLTAFWM` | First word mask for channel A |
| `$DFF046` | `BLTALWM` | Last word mask for channel A |
| `$DFF048` | `BLTCPT` | Channel C pointer (high word) |
| `$DFF04A` | `BLTCPT` | Channel C pointer (low word) |
| `$DFF04C` | `BLTBPT` | Channel B pointer |
| `$DFF050` | `BLTAPT` | Channel A pointer |
| `$DFF054` | `BLTDPT` | Channel D pointer (destination) |
| `$DFF058` | `BLTSIZE` | Size and start: `(height << 6) | width_words` |
| `$DFF064` | `BLTCMOD` | Channel C modulo |
| `$DFF062` | `BLTBMOD` | Channel B modulo |
| `$DFF060` | `BLTAMOD` | Channel A modulo |
| `$DFF066` | `BLTDMOD` | Channel D modulo |
---
## OS-Level Blitter Functions
```c
/* graphics.library */
/* Copy rectangular region between bitmaps: */
LONG BltBitMap(
struct BitMap *srcBM, WORD srcX, WORD srcY,
struct BitMap *dstBM, WORD dstX, WORD dstY,
WORD sizeX, WORD sizeY,
UBYTE minterm, /* usually $C0 = copy */
UBYTE mask, /* plane mask */
APTR tempA /* temp buffer or NULL */
);
/* Blit into RastPort (clips to layer): */
void BltBitMapRastPort(struct BitMap *src, WORD srcX, WORD srcY,
struct RastPort *rp, WORD dstX, WORD dstY,
WORD sizeX, WORD sizeY, UBYTE minterm);
/* Wait for blitter completion: */
void WaitBlit(void); /* must call before freeing blit buffers */
/* Gain exclusive blitter access: */
void OwnBlitter(void);
void DisownBlitter(void);
```
---
## References
- HRM: *Amiga Hardware Reference Manual* — Blitter chapter
- NDK39: `hardware/blit.h`, `graphics/gfx.h`
- ADCD 2.1: `BltBitMap`, `BltBitMapRastPort`, `OwnBlitter`

Some files were not shown because too many files have changed in this diff Show more