refactor: split into gameboy-rust workspace with core, demos, and desktop crates

Move the emulator into crates/gba-core, the original demo programs into
crates/gba-demos, and the minifb frontend into crates/desktop. The binary
is now gameboy-rust. Imports change from gba_rust to gba_core/gba_demos;
no behavior changes.

gba-core has no dependencies. Its integration tests use gba-demos as a
development dependency.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
0xrsydn 2026-10-05 16:07:32 +07:00
commit f8a0676748
113 changed files with 304 additions and 282 deletions

18
Cargo.lock generated
View file

@ -141,12 +141,28 @@ dependencies = [
] ]
[[package]] [[package]]
name = "gba-rust" name = "gameboy-rust-desktop"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"gba-core",
"gba-demos",
"minifb", "minifb",
] ]
[[package]]
name = "gba-core"
version = "0.1.0"
dependencies = [
"gba-demos",
]
[[package]]
name = "gba-demos"
version = "0.1.0"
dependencies = [
"gba-core",
]
[[package]] [[package]]
name = "instant" name = "instant"
version = "0.1.13" version = "0.1.13"

View file

@ -1,18 +1,8 @@
[package] [workspace]
name = "gba-rust" resolver = "2"
members = ["crates/gba-core", "crates/gba-demos", "crates/desktop"]
[workspace.package]
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
publish = false publish = false
[features]
default = ["desktop"]
desktop = ["dep:minifb"]
[dependencies]
# Pinned because src/desktop.rs corrects a macOS focus bug in this version.
minifb = { version = "=0.28.0", optional = true, default-features = false, features = ["x11"] }
[[bin]]
name = "gba-rust"
path = "src/main.rs"
required-features = ["desktop"]

242
README.md
View file

@ -1,8 +1,11 @@
# gba-rust # gameboy-rust
A small Game Boy Advance (GBA) emulator foundation written in Rust. Game Boy family emulation in Rust. The Game Boy Advance (GBA) is the first and currently only system.
The core has no external Rust dependencies when built with `--no-default-features`. This is a small GBA emulator foundation, organized as a Cargo workspace:
The default `desktop` feature adds minifb for a native window.
- `crates/gba-core`: the platform-independent emulator, with no external Rust dependencies.
- `crates/gba-demos`: original test programs that drive the core.
- `crates/desktop`: the native window frontend, built on minifb. It provides the `gameboy-rust` executable.
**This cannot run Pokémon Emerald or other games yet.** **This cannot run Pokémon Emerald or other games yet.**
The CPU-driven demos run original ARM code through emulated video RAM. The CPU-driven demos run original ARM code through emulated video RAM.
@ -279,7 +282,7 @@ From this project directory:
direnv allow direnv allow
cargo run cargo run
cargo test cargo test
cargo fmt --check cargo fmt --all --check
cargo clippy --all-targets -- -D warnings cargo clippy --all-targets -- -D warnings
``` ```
@ -303,7 +306,7 @@ The flake sets Darwin-only `CFLAGS` to correct two minifb 0.28 native-build issu
Build through direnv or `nix develop` so these settings apply. Build through direnv or `nix develop` so these settings apply.
The window runs on the main thread, as required by macOS AppKit. The window runs on the main thread, as required by macOS AppKit.
`src/desktop.rs` also corrects the inverted macOS focus result in minifb 0.28.0. `crates/desktop/src/desktop.rs` also corrects the inverted macOS focus result in minifb 0.28.0.
The dependency is pinned to that exact version. Review this workaround before upgrading minifb. The dependency is pinned to that exact version. Review this workaround before upgrading minifb.
Intel macOS and Linux desktop builds are not verified. Intel macOS and Linux desktop builds are not verified.
@ -477,7 +480,7 @@ Neither constructor provides Nintendo BIOS services or initializes BIOS-managed
### Optional original BIOS replacement ### Optional original BIOS replacement
`src/bios.rs` builds a deterministic 16 KiB image from original ARM instructions and mathematically generated data. `crates/gba-core/src/bios.rs` builds a deterministic 16 KiB image from original ARM instructions and mathematically generated data.
No Nintendo firmware bytes are included. No Nintendo firmware bytes are included.
A compile-time integer calculation generates the 512-byte sine table at `0x3e00..0x3fff`. A compile-time integer calculation generates the 512-byte sine table at `0x3e00..0x3fff`.
The image builder checks that code and literal pools do not overlap this table. The image builder checks that code and literal pools do not overlap this table.
@ -1632,100 +1635,102 @@ Important timing limits:
| --- | --- | | --- | --- |
| `flake.nix`, `flake.lock` | Pinned development environment | | `flake.nix`, `flake.lock` | Pinned development environment |
| `.envrc` | Automatic environment loading through direnv | | `.envrc` | Automatic environment loading through direnv |
| `src/cpu.rs` | Registers, flags, instruction-set state, and stepping | | `crates/gba-core/src/cpu.rs` | Registers, flags, instruction-set state, and stepping |
| `src/cpu/arm.rs` | ARM decoding, data processing, transfers, branches, and multiply | | `crates/gba-core/src/cpu/arm.rs` | ARM decoding, data processing, transfers, branches, and multiply |
| `src/cpu/alu.rs` | Shared arithmetic and barrel shifter | | `crates/gba-core/src/cpu/alu.rs` | Shared arithmetic and barrel shifter |
| `src/cpu/transfer.rs` | Shared ARM/Thumb block transfers and ARM swaps | | `crates/gba-core/src/cpu/transfer.rs` | Shared ARM/Thumb block transfers and ARM swaps |
| `src/cpu/thumb.rs` | Thumb decoding and execution | | `crates/gba-core/src/cpu/thumb.rs` | Thumb decoding and execution |
| `src/cpu/status.rs` | Processor modes, register banks, and status transfers | | `crates/gba-core/src/cpu/status.rs` | Processor modes, register banks, and status transfers |
| `src/cpu/exception.rs` | Reset state, exception entry, and interrupt sampling | | `crates/gba-core/src/cpu/exception.rs` | Reset state, exception entry, and interrupt sampling |
| `src/cpu/status_tests.rs`, `src/cpu/exception_tests.rs` | Status, banking, exception, and return tests | | `crates/gba-core/src/cpu/status_tests.rs`, `crates/gba-core/src/cpu/exception_tests.rs` | Status, banking, exception, and return tests |
| `src/cpu/thumb_tests.rs` | Thumb instructions, state switching, and decoder checks | | `crates/gba-core/src/cpu/thumb_tests.rs` | Thumb instructions, state switching, and decoder checks |
| `src/cpu/transfer_tests.rs` | Stack, addressing, overlap, empty-list, and swap tests | | `crates/gba-core/src/cpu/transfer_tests.rs` | Stack, addressing, overlap, empty-list, and swap tests |
| `src/cpu/tests.rs` | Condition truth tables and immediate arithmetic tests | | `crates/gba-core/src/cpu/tests.rs` | Condition truth tables and immediate arithmetic tests |
| `src/cpu/instruction_tests.rs` | Register operations, transfers, branches, and edge cases | | `crates/gba-core/src/cpu/instruction_tests.rs` | Register operations, transfers, branches, and edge cases |
| `src/memory.rs` | Memory mapping, I/O routing, write validation, and device clock | | `crates/gba-core/src/memory.rs` | Memory mapping, I/O routing, write validation, and device clock |
| `src/io.rs` | I/O registers, timers, HALT wake-up, next-event bounds, and IRQ latches | | `crates/gba-core/src/io.rs` | I/O registers, timers, HALT wake-up, next-event bounds, and IRQ latches |
| `src/display.rs` | Display clock, scanline status, comparison edges, and display IRQ events | | `crates/gba-core/src/display.rs` | Display clock, scanline status, comparison edges, and display IRQ events |
| `src/dma.rs` | DMA registers, internal pointers, trigger state, priority, and completion IRQs | | `crates/gba-core/src/dma.rs` | DMA registers, internal pointers, trigger state, priority, and completion IRQs |
| `src/machine.rs` | Timed CPU/DMA/device stepping and IRQ delivery | | `crates/gba-core/src/machine.rs` | Timed CPU/DMA/device stepping and IRQ delivery |
| `src/timing.rs` | Bus widths, wait-state costs, and timing breakdowns | | `crates/gba-core/src/timing.rs` | Bus widths, wait-state costs, and timing breakdowns |
| `src/cpu/timing.rs` | ARM/Thumb cycle summaries and data-access accounting | | `crates/gba-core/src/cpu/timing.rs` | ARM/Thumb cycle summaries and data-access accounting |
| `src/cpu/timing_tests.rs` | Instruction timing and semantic-equivalence checks | | `crates/gba-core/src/cpu/timing_tests.rs` | Instruction timing and semantic-equivalence checks |
| `src/timer_demo.rs` | Original timer-configuration program and IRQ handler | | `crates/gba-demos/src/timer_demo.rs` | Original timer-configuration program and IRQ handler |
| `src/lib.rs` | Core modules and original demo bytes | | `Cargo.toml` | Workspace members and shared package settings |
| `src/bios.rs` | Original ARM BIOS image builder, minimal boot, IRQ dispatch, waits, and memory services | | `crates/gba-core/src/lib.rs` | Core modules |
| `src/bios/reset.rs` | Emitted ARM SoftReset and selective RegisterRamReset; CPU banks, RAM clearing, and supported device-register resets | | `crates/gba-demos/src/lib.rs` | Demo modules and original instruction/exception demo bytes |
| `src/bios/arithmetic.rs` | Emitted ARM division and integer-square-root routines | | `crates/gba-core/src/bios.rs` | Original ARM BIOS image builder, minimal boot, IRQ dispatch, waits, and memory services |
| `src/bios/affine.rs` | Emitted ARM background/sprite matrix services, range checks, and generated sine table | | `crates/gba-core/src/bios/reset.rs` | Emitted ARM SoftReset and selective RegisterRamReset; CPU banks, RAM clearing, and supported device-register resets |
| `src/bios/angles.rs` | Emitted ARM ArcTan polynomial, ArcTan2 ratio/quadrant handling, and input validation | | `crates/gba-core/src/bios/arithmetic.rs` | Emitted ARM division and integer-square-root routines |
| `src/bios/lz77.rs` | Emitted ARM LZ77 decoder with byte and halfword output | | `crates/gba-core/src/bios/affine.rs` | Emitted ARM background/sprite matrix services, range checks, and generated sine table |
| `src/bios/run_length.rs` | Emitted ARM run-length decoder, header validation, and block bounds | | `crates/gba-core/src/bios/angles.rs` | Emitted ARM ArcTan polynomial, ArcTan2 ratio/quadrant handling, and input validation |
| `src/bios/bit_unpack.rs` | Emitted ARM packed-unit expansion, offsets, word stores, and validation | | `crates/gba-core/src/bios/lz77.rs` | Emitted ARM LZ77 decoder with byte and halfword output |
| `src/bios/decompression.rs` | Shared emitted byte/halfword output routine for decompression and byte differential filters | | `crates/gba-core/src/bios/run_length.rs` | Emitted ARM run-length decoder, header validation, and block bounds |
| `src/bios/differential.rs` | Emitted ARM byte/halfword differential filters, modular accumulation, and validation | | `crates/gba-core/src/bios/bit_unpack.rs` | Emitted ARM packed-unit expansion, offsets, word stores, and validation |
| `src/bios/huffman.rs` | Emitted ARM Huffman tree traversal, symbol packing, word output, and validation | | `crates/gba-core/src/bios/decompression.rs` | Shared emitted byte/halfword output routine for decompression and byte differential filters |
| `src/video.rs` | Presentation buffer, RGB555 conversion, and Mode 0–5 composition | | `crates/gba-core/src/bios/differential.rs` | Emitted ARM byte/halfword differential filters, modular accumulation, and validation |
| `src/video/affine.rs` | Programmed registers, internal scanline origins, and tiled/bitmap sampling | | `crates/gba-core/src/bios/huffman.rs` | Emitted ARM Huffman tree traversal, symbol packing, word output, and validation |
| `src/video/windows.rs` | Persistent vertical window edge flags and constant-time clock advancement | | `crates/gba-core/src/video.rs` | Presentation buffer, RGB555 conversion, and Mode 0–5 composition |
| `src/video/windows/horizontal.rs` | Horizontal comparator flags, per-column history, and bounded bulk advancement | | `crates/gba-core/src/video/affine.rs` | Programmed registers, internal scanline origins, and tiled/bitmap sampling |
| `src/video/mosaic.rs` | Live vertical mosaic phase and constant-time counter transitions | | `crates/gba-core/src/video/windows.rs` | Persistent vertical window edge flags and constant-time clock advancement |
| `src/video/capture.rs` | Drawing/completed frame buffers, row completeness, and deferred render diagnostics | | `crates/gba-core/src/video/windows/horizontal.rs` | Horizontal comparator flags, per-column history, and bounded bulk advancement |
| `src/video/sprites.rs` | Sprite index preparation, priority metadata, mosaic, late palette lookup, and composition | | `crates/gba-core/src/video/mosaic.rs` | Live vertical mosaic phase and constant-time counter transitions |
| `src/video/sprites/pipeline.rs` | Two prepared row buffers, cycle40 events, and bounded capture-disabled advancement | | `crates/gba-core/src/video/capture.rs` | Drawing/completed frame buffers, row completeness, and deferred render diagnostics |
| `src/video/sprites/budget.rs` | Nominal per-row work allowance, partial canvas prefixes, and independent arithmetic tests | | `crates/gba-core/src/video/sprites.rs` | Sprite index preparation, priority metadata, mosaic, late palette lookup, and composition |
| `src/input.rs` | Platform-independent GBA button state | | `crates/gba-core/src/video/sprites/pipeline.rs` | Two prepared row buffers, cycle40 events, and bounded capture-disabled advancement |
| `src/graphics_demo.rs` | Original ARM bitmap program and shared bounded frame runner | | `crates/gba-core/src/video/sprites/budget.rs` | Nominal per-row work allowance, partial canvas prefixes, and independent arithmetic tests |
| `src/tile_demo.rs` | Original ARM tile/sprite program, palettes, tiles, maps, and OAM image | | `crates/gba-core/src/input.rs` | Platform-independent GBA button state |
| `src/affine_demo.rs` | Shared ARM background program builder and Mode 2 assets | | `crates/gba-demos/src/graphics_demo.rs` | Original ARM bitmap program and shared bounded frame runner |
| `src/affine_raster_demo.rs` | CPU-driven HBlank DMA affine distortion demo | | `crates/gba-demos/src/tile_demo.rs` | Original ARM tile/sprite program, palettes, tiles, maps, and OAM image |
| `src/bitmap_demo.rs` | Mode 4/5 demo runners, palettes, and original two-page images | | `crates/gba-demos/src/affine_demo.rs` | Shared ARM background program builder and Mode 2 assets |
| `src/effects_demo.rs` | CPU-driven window and color-effects demo runner | | `crates/gba-demos/src/affine_raster_demo.rs` | CPU-driven HBlank DMA affine distortion demo |
| `src/mosaic_demo.rs` | CPU-driven background/sprite mosaic demo runner | | `crates/gba-demos/src/bitmap_demo.rs` | Mode 4/5 demo runners, palettes, and original two-page images |
| `src/raster_demo.rs` | Original ARM HBlank DMA raster program and color table | | `crates/gba-demos/src/effects_demo.rs` | CPU-driven window and color-effects demo runner |
| `src/video/effects.rs` | Region masks and RGB555 alpha/brightness arithmetic | | `crates/gba-demos/src/mosaic_demo.rs` | CPU-driven background/sprite mosaic demo runner |
| `src/desktop.rs` | Native window, display test, and keyboard controls | | `crates/gba-demos/src/raster_demo.rs` | Original ARM HBlank DMA raster program and color table |
| `src/desktop/affine.rs` | Affine background window and smoke-test pixel checks | | `crates/gba-core/src/video/effects.rs` | Region masks and RGB555 alpha/brightness arithmetic |
| `src/desktop/affine_raster.rs` | Affine raster window and every-pixel smoke checks | | `crates/desktop/src/desktop.rs` | Native window, display test, and keyboard controls |
| `src/desktop/bitmap.rs` | Mode 4/5 windows and page-flipping smoke-test pixel checks | | `crates/desktop/src/desktop/affine.rs` | Affine background window and smoke-test pixel checks |
| `src/desktop/effects.rs` | Window/effects demo and independent smoke-test pixel checks | | `crates/desktop/src/desktop/affine_raster.rs` | Affine raster window and every-pixel smoke checks |
| `src/desktop/mosaic.rs` | Mosaic window and 128-frame independent pixel checks | | `crates/desktop/src/desktop/bitmap.rs` | Mode 4/5 windows and page-flipping smoke-test pixel checks |
| `src/desktop/raster.rs` | Raster window and scanline color-band smoke test | | `crates/desktop/src/desktop/effects.rs` | Window/effects demo and independent smoke-test pixel checks |
| `src/main.rs` | Command-line modes and fixed-length CPU demo | | `crates/desktop/src/desktop/mosaic.rs` | Mosaic window and 128-frame independent pixel checks |
| `tests/core.rs` | CPU integration tests and the complete demo | | `crates/desktop/src/desktop/raster.rs` | Raster window and scanline color-band smoke test |
| `tests/memory.rs` | Memory widths, alignment, errors, and mirrors | | `crates/desktop/src/main.rs` | Command-line modes and fixed-length CPU demo |
| `tests/exceptions.rs` | Exception demo, BIOS mapping, and reset-vector execution | | `crates/gba-core/tests/core.rs` | CPU integration tests and the complete demo |
| `tests/timers.rs` | Timer rules, interrupt registers, and a cycle-by-cycle reference | | `crates/gba-core/tests/memory.rs` | Memory widths, alignment, errors, and mirrors |
| `tests/machine.rs` | Device IRQ entry/return, clock policy, and I/O failure atomicity | | `crates/gba-core/tests/exceptions.rs` | Exception demo, BIOS mapping, and reset-vector execution |
| `tests/timing.rs` | WAITCNT fields, bus costs, and timer/IRQ timing integration | | `crates/gba-core/tests/timers.rs` | Timer rules, interrupt registers, and a cycle-by-cycle reference |
| `tests/graphics.rs` | Video memory, rendering, KEYINPUT, and CPU-driven graphics integration | | `crates/gba-core/tests/machine.rs` | Device IRQ entry/return, clock policy, and I/O failure atomicity |
| `tests/tiles.rs` | Mode 0 registers, maps, palettes, composition, diagnostics, and CPU tile/sprite demo | | `crates/gba-core/tests/timing.rs` | WAITCNT fields, bus costs, and timer/IRQ timing integration |
| `tests/affine_backgrounds.rs` | Affine registers, maps, transforms, bitmap sampling, composition, and CPU demo | | `crates/gba-core/tests/graphics.rs` | Video memory, rendering, KEYINPUT, and CPU-driven graphics integration |
| `tests/affine_tracking.rs` | Internal origins, mid-frame writes, reloads, mosaic, DMA, and affine raster demo | | `crates/gba-core/tests/tiles.rs` | Mode 0 registers, maps, palettes, composition, diagnostics, and CPU tile/sprite demo |
| `tests/bitmap_modes.rs` | Mode 4/5 pages, formats, transforms, transparency, sprites, and CPU demos | | `crates/gba-core/tests/affine_backgrounds.rs` | Affine registers, maps, transforms, bitmap sampling, composition, and CPU demo |
| `tests/effects.rs`, `tests/effects/` | Window masks, color arithmetic, target selection, OBJ effects, DMA, and CPU demo | | `crates/gba-core/tests/affine_tracking.rs` | Internal origins, mid-frame writes, reloads, mosaic, DMA, and affine raster demo |
| `tests/window_tracking.rs`, `tests/window_tracking/` | Vertical/horizontal edges, mid-line writes, hidden positions, DMA, masks, and capture independence | | `crates/gba-core/tests/bitmap_modes.rs` | Mode 4/5 pages, formats, transforms, transparency, sprites, and CPU demos |
| `tests/mosaic.rs`, `tests/mosaic/` | Mosaic sizes, sampling, transparent metadata, windows, DMA, and CPU demo | | `crates/gba-core/tests/effects.rs`, `crates/gba-core/tests/effects/` | Window masks, color arithmetic, target selection, OBJ effects, DMA, and CPU demo |
| `tests/mosaic_tracking.rs` | Size changes, counter wrap, text/affine/OBJ sampling, DMA, and frame resets | | `crates/gba-core/tests/window_tracking.rs`, `crates/gba-core/tests/window_tracking/` | Vertical/horizontal edges, mid-line writes, hidden positions, DMA, masks, and capture independence |
| `tests/scanlines.rs`, `tests/scanlines/` | Capture boundaries, mid-frame changes, DMA ordering, diagnostics, and raster program | | `crates/gba-core/tests/mosaic.rs`, `crates/gba-core/tests/mosaic/` | Mosaic sizes, sampling, transparent metadata, windows, DMA, and CPU demo |
| `tests/sprites.rs` | OAM, DMA, sprite sizes, mapping, palettes, flips, clipping, priorities, and diagnostics | | `crates/gba-core/tests/mosaic_tracking.rs` | Size changes, counter wrap, text/affine/OBJ sampling, DMA, and frame resets |
| `tests/sprite_pipeline.rs` | Preparation boundaries, OAM/VRAM history, late palette/effects, DMA, row0, and diagnostics | | `crates/gba-core/tests/scanlines.rs`, `crates/gba-core/tests/scanlines/` | Capture boundaries, mid-frame changes, DMA ordering, diagnostics, and raster program |
| `tests/sprite_budget.rs` | Regular/affine costs, clipping policy, truncation, inspection, bit5 sampling, DMA, and diagnostics | | `crates/gba-core/tests/sprites.rs` | OAM, DMA, sprite sizes, mapping, palettes, flips, clipping, priorities, and diagnostics |
| `tests/sprites/affine.rs` | Affine matrices, drawing areas, signed sampling, shared groups, and floating-point reference tests | | `crates/gba-core/tests/sprite_pipeline.rs` | Preparation boundaries, OAM/VRAM history, late palette/effects, DMA, row0, and diagnostics |
| `tests/display.rs` | Display boundaries, register masks, IRQ handlers, and VBlank frame execution | | `crates/gba-core/tests/sprite_budget.rs` | Regular/affine costs, clipping policy, truncation, inspection, bit5 sampling, DMA, and diagnostics |
| `tests/dma.rs` | DMA widths, latches, priority, triggers, IRQs, timing, errors, and CPU integration | | `crates/gba-core/tests/sprites/affine.rs` | Affine matrices, drawing areas, signed sampling, shared groups, and floating-point reference tests |
| `tests/halt.rs` | HALT wake masks, idle timing, BIOS-only writes, DMA progress, and STOP diagnostics | | `crates/gba-core/tests/display.rs` | Display boundaries, register masks, IRQ handlers, and VBlank frame execution |
| `tests/bios.rs` | ARM/Thumb service calls, copy/fill boundaries, wait races, callback contracts, and boot | | `crates/gba-core/tests/dma.rs` | DMA widths, latches, priority, triggers, IRQs, timing, errors, and CPU integration |
| `tests/bios/ram_reset.rs` | Selective RAM boundaries, flag combinations, I/O reset, display rendering, DMA/timer latches, and diagnostics | | `crates/gba-core/tests/halt.rs` | HALT wake masks, idle timing, BIOS-only writes, DMA progress, and STOP diagnostics |
| `tests/bios/reset.rs` | All restart flags, exact RAM boundaries, CPU banks, restart execution, device continuity, and IRQ masking | | `crates/gba-core/tests/bios.rs` | ARM/Thumb service calls, copy/fill boundaries, wait races, callback contracts, and boot |
| `tests/bios/arithmetic.rs` | Arithmetic boundaries, wide-integer references, status restoration, and zero-division diagnostics | | `crates/gba-core/tests/bios/ram_reset.rs` | Selective RAM boundaries, flag combinations, I/O reset, display rendering, DMA/timer latches, and diagnostics |
| `tests/bios/affine.rs` | Matrix/origin references, all angle phases, strides, live rendering, register preservation, and diagnostics | | `crates/gba-core/tests/bios/reset.rs` | All restart flags, exact RAM boundaries, CPU banks, restart execution, device continuity, and IRQ masking |
| `tests/bios/angles.rs` | Fixed-point references, axes/quadrants, rounding, caller flags, stack bounds, DMA, and diagnostics | | `crates/gba-core/tests/bios/arithmetic.rs` | Arithmetic boundaries, wide-integer references, status restoration, and zero-division diagnostics |
| `tests/bios/lz77.rs` | Token-reference tests, overlaps, output widths, malformed streams, and partial failures | | `crates/gba-core/tests/bios/affine.rs` | Matrix/origin references, all angle phases, strides, live rendering, register preservation, and diagnostics |
| `tests/bios/run_length.rs` | All block controls, mixed streams, output widths, status, diagnostics, DMA, and IRQ masking | | `crates/gba-core/tests/bios/angles.rs` | Fixed-point references, axes/quadrants, rounding, caller flags, stack bounds, DMA, and diagnostics |
| `tests/bios/bit_unpack.rs` | Width pairs, bit references, offsets, maximum length, memory boundaries, status, DMA, and diagnostics | | `crates/gba-core/tests/bios/lz77.rs` | Token-reference tests, overlaps, output widths, malformed streams, and partial failures |
| `tests/bios/differential.rs` | Round trips, wraparound, output widths, large lengths, partial failures, status, DMA, and diagnostics | | `crates/gba-core/tests/bios/run_length.rs` | All block controls, mixed streams, output widths, status, diagnostics, DMA, and IRQ masking |
| `tests/bios/huffman.rs` | Independent path encoding, tree bounds, packing, large lengths, partial failures, status, and DMA | | `crates/gba-core/tests/bios/bit_unpack.rs` | Width pairs, bit references, offsets, maximum length, memory boundaries, status, DMA, and diagnostics |
| `crates/gba-core/tests/bios/differential.rs` | Round trips, wraparound, output widths, large lengths, partial failures, status, DMA, and diagnostics |
| `crates/gba-core/tests/bios/huffman.rs` | Independent path encoding, tree bounds, packing, large lengths, partial failures, status, and DMA |
## Deliberate limits ## Deliberate limits
@ -1765,35 +1770,36 @@ Writes to cartridge addresses return errors instead of modeling cartridge hardwa
Run native macOS validation from the project directory: Run native macOS validation from the project directory:
```sh ```sh
direnv exec . cargo fmt --check direnv exec . cargo fmt --all --check
direnv exec . cargo clippy --locked --all-targets -- -D warnings direnv exec . cargo clippy --locked --all-targets -- -D warnings
direnv exec . cargo test --locked direnv exec . cargo test --locked
direnv exec . cargo test --locked --release direnv exec . cargo test --locked --release
direnv exec . cargo build --locked --release direnv exec . cargo build --locked --release
file target/release/gba-rust file target/release/gameboy-rust
./target/release/gba-rust --smoke-test ./target/release/gameboy-rust --smoke-test
./target/release/gba-rust --graphics-smoke-test ./target/release/gameboy-rust --graphics-smoke-test
./target/release/gba-rust --tile-smoke-test ./target/release/gameboy-rust --tile-smoke-test
./target/release/gba-rust --affine-smoke-test ./target/release/gameboy-rust --affine-smoke-test
./target/release/gba-rust --affine-raster-smoke-test ./target/release/gameboy-rust --affine-raster-smoke-test
./target/release/gba-rust --bitmap4-smoke-test ./target/release/gameboy-rust --bitmap4-smoke-test
./target/release/gba-rust --bitmap5-smoke-test ./target/release/gameboy-rust --bitmap5-smoke-test
./target/release/gba-rust --effects-smoke-test ./target/release/gameboy-rust --effects-smoke-test
./target/release/gba-rust --mosaic-smoke-test ./target/release/gameboy-rust --mosaic-smoke-test
./target/release/gba-rust --raster-smoke-test ./target/release/gameboy-rust --raster-smoke-test
./target/release/gba-rust --cpu-demo ./target/release/gameboy-rust --cpu-demo
./target/release/gba-rust --timer-demo ./target/release/gameboy-rust --timer-demo
``` ```
All ten window smoke tests need an active desktop session. Ordinary tests do not open windows. All ten window smoke tests need an active desktop session. Ordinary tests do not open windows.
CPU-driven smoke tests supply scripted buttons; they do not validate physical keyboard events. CPU-driven smoke tests supply scripted buttons; they do not validate physical keyboard events.
To test the core without building the window dependency: To test the core and demo programs without building the window dependency:
```sh ```sh
direnv exec . cargo test --locked --no-default-features direnv exec . cargo test --locked -p gba-core -p gba-demos
``` ```
This runs 709 core and integration tests; the eleven desktop and command-line tests are excluded. This runs 709 core, demo, and integration tests; the eleven desktop and command-line tests are excluded.
The core's integration tests use `gba-demos` as a development dependency.
On Apple Silicon, `file` must report a Mach-O `arm64` executable. On Apple Silicon, `file` must report a Mach-O `arm64` executable.
Do not set a Linux cross-compilation target for this validation. Do not set a Linux cross-compilation target for this validation.

15
crates/desktop/Cargo.toml Normal file
View file

@ -0,0 +1,15 @@
[package]
name = "gameboy-rust-desktop"
version.workspace = true
edition.workspace = true
publish.workspace = true
[[bin]]
name = "gameboy-rust"
path = "src/main.rs"
[dependencies]
gba-core = { path = "../gba-core" }
gba-demos = { path = "../gba-demos" }
# Pinned because src/desktop.rs corrects a macOS focus bug in this version.
minifb = { version = "=0.28.0", default-features = false, features = ["x11"] }

View file

@ -19,13 +19,15 @@ use std::{
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use gba_rust::{ use gba_core::{
display::{CPU_HZ, CYCLES_PER_FRAME, VISIBLE_LINES}, display::{CPU_HZ, CYCLES_PER_FRAME, VISIBLE_LINES},
graphics_demo::{GraphicsDemo, BACKGROUND, DEMO_STATE, SQUARE_SIZE},
input::{Button, Buttons}, input::{Button, Buttons},
tile_demo::{TileDemo, TILE_STATE},
video::{rgb555_to_rgb888, Framebuffer, HEIGHT, WIDTH}, video::{rgb555_to_rgb888, Framebuffer, HEIGHT, WIDTH},
}; };
use gba_demos::{
graphics_demo::{GraphicsDemo, BACKGROUND, DEMO_STATE, SQUARE_SIZE},
tile_demo::{TileDemo, TILE_STATE},
};
use minifb::{Key, KeyRepeat, Scale, Window, WindowOptions}; use minifb::{Key, KeyRepeat, Scale, Window, WindowOptions};
const TITLE: &str = const TITLE: &str =
@ -381,14 +383,14 @@ fn validate_tile_smoke(
if memory.read32(TILE_STATE)? != index as u32 + 1 if memory.read32(TILE_STATE)? != index as u32 + 1
|| memory.read32(TILE_STATE + 4)? != sx as u32 || memory.read32(TILE_STATE + 4)? != sx as u32
|| memory.read32(TILE_STATE + 8)? != sy as u32 || memory.read32(TILE_STATE + 8)? != sy as u32
|| memory.read16(gba_rust::memory::OAM_START)? != if affine { 0x340 } else { 72 } || memory.read16(gba_core::memory::OAM_START)? != if affine { 0x340 } else { 72 }
|| memory.read16(gba_rust::memory::OAM_START + 2)? || memory.read16(gba_core::memory::OAM_START + 2)?
!= if affine { != if affine {
0x4068 0x4068
} else { } else {
0x4070 | if flipped { 0x1000 } else { 0 } 0x4070 | if flipped { 0x1000 } else { 0 }
} }
|| memory.read16(gba_rust::memory::OAM_START + 4)? != if behind { 0x400 } else { 0 } || memory.read16(gba_core::memory::OAM_START + 4)? != if behind { 0x400 } else { 0 }
|| u32::from(position.scanline) != VISIBLE_LINES || u32::from(position.scanline) != VISIBLE_LINES
|| u32::from(position.line_cycle) >= demo.machine().last_timing().total() || u32::from(position.line_cycle) >= demo.machine().last_timing().total()
{ {
@ -397,7 +399,7 @@ fn validate_tile_smoke(
); );
} }
for (i, value) in matrix.into_iter().enumerate() { for (i, value) in matrix.into_iter().enumerate() {
if memory.read16(gba_rust::memory::OAM_START + 6 + i as u32 * 8)? != value as u16 { if memory.read16(gba_core::memory::OAM_START + 6 + i as u32 * 8)? != value as u16 {
return Err(io::Error::other("tile smoke test: unexpected affine matrix").into()); return Err(io::Error::other("tile smoke test: unexpected affine matrix").into());
} }
} }

View file

@ -1,10 +1,10 @@
use super::{create_window, read_buttons}; use super::{create_window, read_buttons};
use gba_rust::{ use gba_core::{
affine_demo::{AffineDemo, AFFINE_STATE},
display::{CPU_HZ, CYCLES_PER_FRAME, VISIBLE_LINES}, display::{CPU_HZ, CYCLES_PER_FRAME, VISIBLE_LINES},
input::{Button, Buttons}, input::{Button, Buttons},
video::{rgb555_to_rgb888, Framebuffer, HEIGHT, WIDTH}, video::{rgb555_to_rgb888, Framebuffer, HEIGHT, WIDTH},
}; };
use gba_demos::affine_demo::{AffineDemo, AFFINE_STATE};
use minifb::Key; use minifb::Key;
use std::{ use std::{
error::Error, error::Error,
@ -75,7 +75,7 @@ fn validate(demo: &AffineDemo, frame: &Framebuffer, index: usize) -> Result<(),
if m.read32(AFFINE_STATE)? != index as u32 + 1 if m.read32(AFFINE_STATE)? != index as u32 + 1
|| m.read32(AFFINE_STATE + 4)? != pan_x as u32 || m.read32(AFFINE_STATE + 4)? != pan_x as u32
|| m.read32(AFFINE_STATE + 8)? != pan_y as u32 || m.read32(AFFINE_STATE + 8)? != pan_y as u32
|| m.read16(gba_rust::io::BG2CNT)? != if clipped { 0x5000 } else { 0x7000 } || m.read16(gba_core::io::BG2CNT)? != if clipped { 0x5000 } else { 0x7000 }
|| u32::from(position.scanline) != VISIBLE_LINES || u32::from(position.scanline) != VISIBLE_LINES
|| u32::from(position.line_cycle) >= demo.machine().last_timing().total() || u32::from(position.line_cycle) >= demo.machine().last_timing().total()
{ {

View file

@ -1,10 +1,10 @@
use super::{create_window, read_buttons}; use super::{create_window, read_buttons};
use gba_rust::{ use gba_core::{
affine_raster_demo::{AffineRasterDemo, AFFINE_RASTER_STATE},
display::{CPU_HZ, CYCLES_PER_FRAME, VISIBLE_LINES}, display::{CPU_HZ, CYCLES_PER_FRAME, VISIBLE_LINES},
input::{Button, Buttons}, input::{Button, Buttons},
video::{rgb555_to_rgb888, Framebuffer, HEIGHT, WIDTH}, video::{rgb555_to_rgb888, Framebuffer, HEIGHT, WIDTH},
}; };
use gba_demos::affine_raster_demo::{AffineRasterDemo, AFFINE_RASTER_STATE};
use minifb::Key; use minifb::Key;
use std::{ use std::{
error::Error, error::Error,
@ -79,7 +79,7 @@ fn validate(
if m.read32(AFFINE_RASTER_STATE)? != index as u32 + 1 if m.read32(AFFINE_RASTER_STATE)? != index as u32 + 1
|| m.read32(AFFINE_RASTER_STATE + 4)? != pan_x as u32 || m.read32(AFFINE_RASTER_STATE + 4)? != pan_x as u32
|| m.read32(AFFINE_RASTER_STATE + 8)? != pan_y as u32 || m.read32(AFFINE_RASTER_STATE + 8)? != pan_y as u32
|| m.read16(gba_rust::io::BG2CNT)? != 0x7000 || m.read16(gba_core::io::BG2CNT)? != 0x7000
|| u32::from(position.scanline) != VISIBLE_LINES || u32::from(position.scanline) != VISIBLE_LINES
|| u32::from(position.line_cycle) >= demo.machine().last_timing().total() || u32::from(position.line_cycle) >= demo.machine().last_timing().total()
{ {

View file

@ -1,11 +1,11 @@
use super::{create_window, read_buttons}; use super::{create_window, read_buttons};
use gba_rust::{ use gba_core::{
bitmap_demo::{BitmapDemo, BitmapMode, BITMAP_STATE},
display::{CPU_HZ, CYCLES_PER_FRAME, VISIBLE_LINES}, display::{CPU_HZ, CYCLES_PER_FRAME, VISIBLE_LINES},
input::{Button, Buttons}, input::{Button, Buttons},
io::DISPCNT, io::DISPCNT,
video::{rgb555_to_rgb888, Framebuffer, HEIGHT, WIDTH}, video::{rgb555_to_rgb888, Framebuffer, HEIGHT, WIDTH},
}; };
use gba_demos::bitmap_demo::{BitmapDemo, BitmapMode, BITMAP_STATE};
use minifb::Key; use minifb::Key;
use std::{ use std::{
error::Error, error::Error,

View file

@ -1,11 +1,11 @@
use super::{create_window, read_buttons}; use super::{create_window, read_buttons};
use gba_rust::{ use gba_core::{
display::{CPU_HZ, CYCLES_PER_FRAME, VISIBLE_LINES}, display::{CPU_HZ, CYCLES_PER_FRAME, VISIBLE_LINES},
effects_demo::{EffectsDemo, EFFECTS_STATE},
input::{Button, Buttons}, input::{Button, Buttons},
io::{BLDALPHA, BLDCNT, DISPCNT, WININ, WINOUT}, io::{BLDALPHA, BLDCNT, DISPCNT, WININ, WINOUT},
video::{rgb555_to_rgb888, Framebuffer, HEIGHT, WIDTH}, video::{rgb555_to_rgb888, Framebuffer, HEIGHT, WIDTH},
}; };
use gba_demos::effects_demo::{EffectsDemo, EFFECTS_STATE};
use minifb::Key; use minifb::Key;
use std::{ use std::{
error::Error, error::Error,

View file

@ -1,12 +1,12 @@
use super::{create_window, read_buttons}; use super::{create_window, read_buttons};
use gba_rust::{ use gba_core::{
display::{CPU_HZ, CYCLES_PER_FRAME, VISIBLE_LINES}, display::{CPU_HZ, CYCLES_PER_FRAME, VISIBLE_LINES},
input::{Button, Buttons}, input::{Button, Buttons},
io::{BG0CNT, BG1CNT, DISPCNT, MOSAIC}, io::{BG0CNT, BG1CNT, DISPCNT, MOSAIC},
memory::OAM_START, memory::OAM_START,
mosaic_demo::{MosaicDemo, MOSAIC_STATE},
video::{rgb555_to_rgb888, Framebuffer, HEIGHT, WIDTH}, video::{rgb555_to_rgb888, Framebuffer, HEIGHT, WIDTH},
}; };
use gba_demos::mosaic_demo::{MosaicDemo, MOSAIC_STATE};
use minifb::Key; use minifb::Key;
use std::{ use std::{
error::Error, error::Error,

View file

@ -1,11 +1,11 @@
use super::{create_window, read_buttons}; use super::{create_window, read_buttons};
use gba_rust::{ use gba_core::{
display::{CPU_HZ, CYCLES_PER_FRAME}, display::{CPU_HZ, CYCLES_PER_FRAME},
dma::DMA_BASE, dma::DMA_BASE,
input::{Button, Buttons}, input::{Button, Buttons},
raster_demo::{RasterDemo, RASTER_STATE},
video::{rgb555_to_rgb888, Framebuffer, HEIGHT, WIDTH}, video::{rgb555_to_rgb888, Framebuffer, HEIGHT, WIDTH},
}; };
use gba_demos::raster_demo::{RasterDemo, RASTER_STATE};
use minifb::Key; use minifb::Key;
use std::{ use std::{
error::Error, error::Error,

View file

@ -2,12 +2,14 @@ mod desktop;
use std::{error::Error, ffi::OsString, io}; use std::{error::Error, ffi::OsString, io};
use gba_rust::{ use gba_core::{
bitmap_demo::BitmapMode,
cpu::Cpu, cpu::Cpu,
demo_bios, demo_program, exception_demo_program,
io::{IF, TIMER_BASE}, io::{IF, TIMER_BASE},
memory::{Memory, ROM_START}, memory::{Memory, ROM_START},
};
use gba_demos::{
bitmap_demo::BitmapMode,
demo_bios, demo_program, exception_demo_program,
timer_demo::{timer_demo, TIMER_DEMO_STEPS}, timer_demo::{timer_demo, TIMER_DEMO_STEPS},
DEMO_STEPS, EXCEPTION_DEMO_STEPS, DEMO_STEPS, EXCEPTION_DEMO_STEPS,
}; };
@ -64,7 +66,7 @@ fn parse_args(args: &[OsString]) -> Result<RunMode, io::Error> {
[arg] if arg == "--help" || arg == "-h" => Ok(RunMode::Help), [arg] if arg == "--help" || arg == "-h" => Ok(RunMode::Help),
_ => Err(io::Error::new( _ => Err(io::Error::new(
io::ErrorKind::InvalidInput, io::ErrorKind::InvalidInput,
"usage: gba-rust [--cpu-demo | --timer-demo | --graphics-demo | --smoke-test | --graphics-smoke-test | --raster-demo | --raster-smoke-test | --mosaic-demo | --mosaic-smoke-test | --effects-demo | --effects-smoke-test | --tile-demo | --tile-smoke-test | --affine-demo | --affine-smoke-test | --affine-raster-demo | --affine-raster-smoke-test | --bitmap4-demo | --bitmap5-demo | --bitmap4-smoke-test | --bitmap5-smoke-test | --help]; ROM loading is not supported yet", "usage: gameboy-rust [--cpu-demo | --timer-demo | --graphics-demo | --smoke-test | --graphics-smoke-test | --raster-demo | --raster-smoke-test | --mosaic-demo | --mosaic-smoke-test | --effects-demo | --effects-smoke-test | --tile-demo | --tile-smoke-test | --affine-demo | --affine-smoke-test | --affine-raster-demo | --affine-raster-smoke-test | --bitmap4-demo | --bitmap5-demo | --bitmap4-smoke-test | --bitmap5-smoke-test | --help]; ROM loading is not supported yet",
)), )),
} }
} }

View file

@ -0,0 +1,9 @@
[package]
name = "gba-core"
version.workspace = true
edition.workspace = true
publish.workspace = true
[dev-dependencies]
# Integration tests exercise the core through the original demo programs.
gba-demos = { path = "../gba-demos" }

View file

@ -0,0 +1,12 @@
//! An educational GBA starting point, not a game-compatible emulator.
pub mod bios;
pub mod cpu;
pub mod display;
pub mod dma;
pub mod input;
pub mod io;
pub mod machine;
pub mod memory;
pub mod timing;
pub mod video;

View file

@ -1,4 +1,4 @@
use gba_rust::{ use gba_core::{
io::*, io::*,
memory::{Memory, MemoryError, OAM_START as OAM, PALETTE_START as PAL, VRAM_START as VRAM}, memory::{Memory, MemoryError, OAM_START as OAM, PALETTE_START as PAL, VRAM_START as VRAM},
video::{rgb555_to_rgb888, Framebuffer, VideoError, WIDTH}, video::{rgb555_to_rgb888, Framebuffer, VideoError, WIDTH},
@ -281,10 +281,8 @@ fn diagnostics_leave_output_unchanged_and_disabled_layers_skip_validation() {
#[test] #[test]
fn cpu_demo_pans_rotates_zooms_clips_cancels_opposites_and_resets() { fn cpu_demo_pans_rotates_zooms_clips_cancels_opposites_and_resets() {
use gba_rust::{ use gba_core::input::{Button, Buttons};
affine_demo::{AffineDemo, AFFINE_STATE}, use gba_demos::affine_demo::{AffineDemo, AFFINE_STATE};
input::{Button, Buttons},
};
let mut demo = AffineDemo::new().unwrap(); let mut demo = AffineDemo::new().unwrap();
let mut f = Framebuffer::default(); let mut f = Framebuffer::default();
assert_eq!(demo.machine().memory().read16(DISPCNT).unwrap(), 0); assert_eq!(demo.machine().memory().read16(DISPCNT).unwrap(), 0);

View file

@ -1,5 +1,4 @@
use gba_rust::{ use gba_core::{
affine_raster_demo::{AffineRasterDemo, AFFINE_RASTER_STATE, AFFINE_RASTER_TABLE},
cpu::Cpu, cpu::Cpu,
display::{CYCLES_PER_FRAME, CYCLES_PER_LINE as LINE, HBLANK_START as HB, VBLANK_START as VB}, display::{CYCLES_PER_FRAME, CYCLES_PER_LINE as LINE, HBLANK_START as HB, VBLANK_START as VB},
dma::DMA_BASE, dma::DMA_BASE,
@ -9,6 +8,7 @@ use gba_rust::{
memory::{Memory, PALETTE_START as PAL, ROM_START, VRAM_START as VRAM}, memory::{Memory, PALETTE_START as PAL, ROM_START, VRAM_START as VRAM},
video::{rgb555_to_rgb888 as rgb, Framebuffer, HEIGHT, WIDTH}, video::{rgb555_to_rgb888 as rgb, Framebuffer, HEIGHT, WIDTH},
}; };
use gba_demos::affine_raster_demo::{AffineRasterDemo, AFFINE_RASTER_STATE, AFFINE_RASTER_TABLE};
fn color(x: u32, y: u32) -> u16 { fn color(x: u32, y: u32) -> u16 {
((x & 31) | ((y & 31) << 5) | (((x + y) & 31) << 10)) as u16 ((x & 31) | ((y & 31) << 5) | (((x + y) & 31) << 10)) as u16

View file

@ -1,4 +1,4 @@
use gba_rust::{ use gba_core::{
bios::{self, IRQ_FLAGS, IRQ_HANDLER, SYSTEM_STACK, UNSUPPORTED_TRAP}, bios::{self, IRQ_FLAGS, IRQ_HANDLER, SYSTEM_STACK, UNSUPPORTED_TRAP},
cpu::{CpuError, InstructionSet, Mode}, cpu::{CpuError, InstructionSet, Mode},
display::VBLANK_START, display::VBLANK_START,

View file

@ -1,5 +1,5 @@
use super::*; use super::*;
use gba_rust::{ use gba_core::{
bios::INVALID_ARGUMENT_TRAP, bios::INVALID_ARGUMENT_TRAP,
dma::DMA_BASE, dma::DMA_BASE,
io::{BG2PA, DISPCNT}, io::{BG2PA, DISPCNT},

View file

@ -1,5 +1,5 @@
use super::*; use super::*;
use gba_rust::{bios::INVALID_ARGUMENT_TRAP, dma::DMA_BASE}; use gba_core::{bios::INVALID_ARGUMENT_TRAP, dma::DMA_BASE};
/// Wide-integer reference for signed low-word multiplication and floor division. /// Wide-integer reference for signed low-word multiplication and floor division.
/// This avoids both host signed overflow and the emitted instruction sequence. /// This avoids both host signed overflow and the emitted instruction sequence.

View file

@ -1,5 +1,5 @@
use super::*; use super::*;
use gba_rust::bios::INVALID_ARGUMENT_TRAP; use gba_core::bios::INVALID_ARGUMENT_TRAP;
fn divide(number: i32, denominator: i32, thumb: bool, service: u8) { fn divide(number: i32, denominator: i32, thumb: bool, service: u8) {
let inputs = if service == 6 { let inputs = if service == 6 {

View file

@ -1,5 +1,5 @@
use super::*; use super::*;
use gba_rust::{ use gba_core::{
bios::INVALID_ARGUMENT_TRAP, bios::INVALID_ARGUMENT_TRAP,
dma::DMA_BASE, dma::DMA_BASE,
memory::{OAM_START, PALETTE_START}, memory::{OAM_START, PALETTE_START},

View file

@ -1,5 +1,5 @@
use super::*; use super::*;
use gba_rust::{bios::INVALID_ARGUMENT_TRAP, dma::DMA_BASE, memory::OAM_START}; use gba_core::{bios::INVALID_ARGUMENT_TRAP, dma::DMA_BASE, memory::OAM_START};
fn filtered(service: u8, samples: &[u16]) -> (Vec<u8>, Vec<u8>) { fn filtered(service: u8, samples: &[u16]) -> (Vec<u8>, Vec<u8>) {
let halfword = service == 0x18; let halfword = service == 0x18;

View file

@ -1,5 +1,5 @@
use super::*; use super::*;
use gba_rust::{ use gba_core::{
bios::INVALID_ARGUMENT_TRAP, bios::INVALID_ARGUMENT_TRAP,
dma::DMA_BASE, dma::DMA_BASE,
memory::{OAM_START, PALETTE_START}, memory::{OAM_START, PALETTE_START},

View file

@ -1,5 +1,5 @@
use super::*; use super::*;
use gba_rust::bios::INVALID_ARGUMENT_TRAP; use gba_core::bios::INVALID_ARGUMENT_TRAP;
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
enum Token { enum Token {

View file

@ -1,5 +1,5 @@
use super::*; use super::*;
use gba_rust::{ use gba_core::{
bios::INVALID_ARGUMENT_TRAP, bios::INVALID_ARGUMENT_TRAP,
dma::{DmaError, DMA_BASE, DMA_STRIDE}, dma::{DmaError, DMA_BASE, DMA_STRIDE},
input::Buttons, input::Buttons,

View file

@ -1,5 +1,5 @@
use super::*; use super::*;
use gba_rust::{ use gba_core::{
dma::DMA_BASE, dma::DMA_BASE,
io::{BG0CNT, DISPCNT, WAITCNT}, io::{BG0CNT, DISPCNT, WAITCNT},
memory::{OAM_START, PALETTE_START}, memory::{OAM_START, PALETTE_START},

View file

@ -1,5 +1,5 @@
use super::*; use super::*;
use gba_rust::{bios::INVALID_ARGUMENT_TRAP, dma::DMA_BASE, memory::OAM_START}; use gba_core::{bios::INVALID_ARGUMENT_TRAP, dma::DMA_BASE, memory::OAM_START};
#[derive(Clone)] #[derive(Clone)]
enum Block { enum Block {

View file

@ -1,4 +1,4 @@
use gba_rust::{ use gba_core::{
io::{BG2CNT, BG2PA, BG2X, BG2Y, DISPCNT, GREENSWAP}, io::{BG2CNT, BG2PA, BG2X, BG2Y, DISPCNT, GREENSWAP},
memory::{Memory, OAM_START as OAM, PALETTE_START as PAL, VRAM_START as VRAM}, memory::{Memory, OAM_START as OAM, PALETTE_START as PAL, VRAM_START as VRAM},
video::{rgb555_to_rgb888, Framebuffer, VideoError, WIDTH}, video::{rgb555_to_rgb888, Framebuffer, VideoError, WIDTH},
@ -293,13 +293,13 @@ fn bitmap_green_swap_disabled_bg_and_forced_blank_remain_consistent() {
fn assert_demo_frame( fn assert_demo_frame(
f: &Framebuffer, f: &Framebuffer,
mode: gba_rust::bitmap_demo::BitmapMode, mode: gba_demos::bitmap_demo::BitmapMode,
pan: (i32, i32), pan: (i32, i32),
rotated: bool, rotated: bool,
zoomed: bool, zoomed: bool,
page: usize, page: usize,
) { ) {
use gba_rust::bitmap_demo::BitmapMode; use gba_demos::bitmap_demo::BitmapMode;
let (w, h) = if mode == BitmapMode::Mode4 { let (w, h) = if mode == BitmapMode::Mode4 {
(240, 160) (240, 160)
} else { } else {
@ -346,10 +346,8 @@ fn assert_demo_frame(
#[test] #[test]
fn cpu_demos_copy_both_pages_and_flip_during_vblank_every_32_updates() { fn cpu_demos_copy_both_pages_and_flip_during_vblank_every_32_updates() {
use gba_rust::{ use gba_core::input::Buttons;
bitmap_demo::{BitmapDemo, BitmapMode, BITMAP_STATE}, use gba_demos::bitmap_demo::{BitmapDemo, BitmapMode, BITMAP_STATE};
input::Buttons,
};
for mode in [BitmapMode::Mode4, BitmapMode::Mode5] { for mode in [BitmapMode::Mode4, BitmapMode::Mode5] {
let mut demo = BitmapDemo::new(mode).unwrap(); let mut demo = BitmapDemo::new(mode).unwrap();
let mut f = Framebuffer::default(); let mut f = Framebuffer::default();
@ -376,10 +374,8 @@ fn cpu_demos_copy_both_pages_and_flip_during_vblank_every_32_updates() {
#[test] #[test]
fn cpu_bitmap_controls_pan_rotate_zoom_force_page_and_reset() { fn cpu_bitmap_controls_pan_rotate_zoom_force_page_and_reset() {
use gba_rust::{ use gba_core::input::{Button, Buttons};
bitmap_demo::{BitmapDemo, BitmapMode, BITMAP_STATE}, use gba_demos::bitmap_demo::{BitmapDemo, BitmapMode, BITMAP_STATE};
input::{Button, Buttons},
};
for mode in [BitmapMode::Mode4, BitmapMode::Mode5] { for mode in [BitmapMode::Mode4, BitmapMode::Mode5] {
let mut demo = BitmapDemo::new(mode).unwrap(); let mut demo = BitmapDemo::new(mode).unwrap();
let mut f = Framebuffer::default(); let mut f = Framebuffer::default();

View file

@ -1,9 +1,8 @@
use gba_rust::{ use gba_core::{
cpu::{Cpu, CpuError, InstructionSet}, cpu::{Cpu, CpuError, InstructionSet},
demo_program,
memory::{Memory, MemoryError, ROM_START}, memory::{Memory, MemoryError, ROM_START},
DEMO_STEPS,
}; };
use gba_demos::{demo_program, DEMO_STEPS};
fn program(words: &[u32]) -> Memory { fn program(words: &[u32]) -> Memory {
Memory::new(words.iter().flat_map(|word| word.to_le_bytes()).collect()).unwrap() Memory::new(words.iter().flat_map(|word| word.to_le_bytes()).collect()).unwrap()

View file

@ -1,4 +1,4 @@
use gba_rust::{ use gba_core::{
cpu::{Cpu, Mode}, cpu::{Cpu, Mode},
display::{CYCLES_PER_FRAME, CYCLES_PER_LINE, HBLANK_START, LINES_PER_FRAME, VBLANK_START}, display::{CYCLES_PER_FRAME, CYCLES_PER_LINE, HBLANK_START, LINES_PER_FRAME, VBLANK_START},
io::{DISPCNT, DISPSTAT, IE, IF, IME, TIMER_BASE, VCOUNT}, io::{DISPCNT, DISPSTAT, IE, IF, IME, TIMER_BASE, VCOUNT},
@ -397,7 +397,7 @@ fn failed_block_store_does_not_change_window_controls_or_advance_display() {
assert!(machine.step().is_err()); assert!(machine.step().is_err());
assert_eq!(machine.cpu(), &before); assert_eq!(machine.cpu(), &before);
assert_eq!(machine.memory().display_position(), position); assert_eq!(machine.memory().display_position(), position);
assert_eq!(machine.memory().read32(gba_rust::io::WININ).unwrap(), 0); assert_eq!(machine.memory().read32(gba_core::io::WININ).unwrap(), 0);
assert_eq!(machine.memory().read16(DISPSTAT).unwrap(), 0); assert_eq!(machine.memory().read16(DISPSTAT).unwrap(), 0);
assert_eq!(machine.memory().read16(IF).unwrap(), 0); assert_eq!(machine.memory().read16(IF).unwrap(), 0);
} }

View file

@ -1,4 +1,4 @@
use gba_rust::{ use gba_core::{
cpu::{Cpu, Mode}, cpu::{Cpu, Mode},
display::{CYCLES_PER_FRAME, CYCLES_PER_LINE, HBLANK_START, VBLANK_START}, display::{CYCLES_PER_FRAME, CYCLES_PER_LINE, HBLANK_START, VBLANK_START},
dma::{DmaError, DMA_BASE, DMA_STRIDE}, dma::{DmaError, DMA_BASE, DMA_STRIDE},

View file

@ -1,4 +1,4 @@
use gba_rust::{ use gba_core::{
io::*, io::*,
memory::{Memory, MemoryError, OAM_START as OAM, PALETTE_START as PAL, VRAM_START as VRAM}, memory::{Memory, MemoryError, OAM_START as OAM, PALETTE_START as PAL, VRAM_START as VRAM},
video::{rgb555_to_rgb888 as rgb, Framebuffer, HEIGHT, WIDTH}, video::{rgb555_to_rgb888 as rgb, Framebuffer, HEIGHT, WIDTH},

View file

@ -1,7 +1,7 @@
use super::*; use super::*;
use gba_rust::{ use gba_core::input::{Button, Buttons};
use gba_demos::{
effects_demo::{EffectsDemo, EFFECTS_STATE}, effects_demo::{EffectsDemo, EFFECTS_STATE},
input::{Button, Buttons},
tile_demo::TileDemo, tile_demo::TileDemo,
}; };
@ -149,7 +149,7 @@ fn cpu_window_demo_retains_active_region_when_bottom_moves_beyond_vcount_range()
#[test] #[test]
fn dma_word_write_updates_selection_and_coefficients_together() { fn dma_word_write_updates_selection_and_coefficients_together() {
use gba_rust::{ use gba_core::{
cpu::Cpu, cpu::Cpu,
dma::DMA_BASE, dma::DMA_BASE,
machine::{Machine, StepKind}, machine::{Machine, StepKind},

View file

@ -1,9 +1,8 @@
use gba_rust::{ use gba_core::{
cpu::{Cpu, InstructionSet, Mode}, cpu::{Cpu, InstructionSet, Mode},
demo_bios, exception_demo_program,
memory::{Memory, MemoryError, BIOS_SIZE, ROM_START}, memory::{Memory, MemoryError, BIOS_SIZE, ROM_START},
EXCEPTION_DEMO_STEPS,
}; };
use gba_demos::{demo_bios, exception_demo_program, EXCEPTION_DEMO_STEPS};
#[test] #[test]
fn original_demo_enters_supervisor_and_returns_to_arm_and_thumb() { fn original_demo_enters_supervisor_and_returns_to_arm_and_thumb() {

View file

@ -1,11 +1,11 @@
use gba_rust::{ use gba_core::{
graphics_demo::{GraphicsDemo, BACKGROUND, DEMO_STATE, SQUARE_SIZE},
input::{Button, Buttons}, input::{Button, Buttons},
io::{BG2PA, BG2PD, DISPCNT, GREENSWAP, KEYINPUT}, io::{BG2PA, BG2PD, DISPCNT, GREENSWAP, KEYINPUT},
memory::{Memory, MemoryError, PALETTE_START, VRAM_START}, memory::{Memory, MemoryError, PALETTE_START, VRAM_START},
timing::{bus_cycles, AccessKind, AccessWidth}, timing::{bus_cycles, AccessKind, AccessWidth},
video::{rgb555_to_rgb888, Framebuffer, VideoError, HEIGHT, WIDTH}, video::{rgb555_to_rgb888, Framebuffer, VideoError, HEIGHT, WIDTH},
}; };
use gba_demos::graphics_demo::{GraphicsDemo, BACKGROUND, DEMO_STATE, SQUARE_SIZE};
fn mode3() -> Memory { fn mode3() -> Memory {
let mut memory = Memory::new(vec![]).unwrap(); let mut memory = Memory::new(vec![]).unwrap();
@ -65,7 +65,7 @@ fn cpu_program_draws_mode3_and_moves_from_keyinput_without_host_vram_writes() {
assert_eq!(position.frames, previous.frames + 1); assert_eq!(position.frames, previous.frames + 1);
assert_eq!( assert_eq!(
demo.machine().cycles() - previous_cycles, demo.machine().cycles() - previous_cycles,
u64::from(gba_rust::display::CYCLES_PER_FRAME) + u64::from(position.line_cycle) u64::from(gba_core::display::CYCLES_PER_FRAME) + u64::from(position.line_cycle)
- u64::from(previous.line_cycle) - u64::from(previous.line_cycle)
); );
assert_demo_frame(&demo, &frame, 114, 70, 0x7fff); assert_demo_frame(&demo, &frame, 114, 70, 0x7fff);

View file

@ -1,4 +1,4 @@
use gba_rust::{ use gba_core::{
cpu::{Cpu, InstructionSet, Mode}, cpu::{Cpu, InstructionSet, Mode},
display::{CYCLES_PER_FRAME, CYCLES_PER_LINE, HBLANK_START, VBLANK_START}, display::{CYCLES_PER_FRAME, CYCLES_PER_LINE, HBLANK_START, VBLANK_START},
dma::{DmaError, DMA_BASE}, dma::{DmaError, DMA_BASE},

View file

@ -1,10 +1,10 @@
use gba_rust::{ use gba_core::{
cpu::{Cpu, CpuError, InstructionSet, Mode}, cpu::{Cpu, CpuError, InstructionSet, Mode},
io::{IE, IF, IME, TIMER_BASE, WAITCNT}, io::{IE, IF, IME, TIMER_BASE, WAITCNT},
machine::{Machine, MachineError, StepKind}, machine::{Machine, MachineError, StepKind},
memory::{Memory, MemoryError, BIOS_SIZE, ROM_START}, memory::{Memory, MemoryError, BIOS_SIZE, ROM_START},
timer_demo::{timer_demo, TIMER_DEMO_STEPS},
}; };
use gba_demos::timer_demo::{timer_demo, TIMER_DEMO_STEPS};
fn words(values: &[u32]) -> Vec<u8> { fn words(values: &[u32]) -> Vec<u8> {
values values

View file

@ -1,4 +1,4 @@
use gba_rust::memory::{Memory, MemoryError, ROM_START}; use gba_core::memory::{Memory, MemoryError, ROM_START};
#[test] #[test]
fn halfword_and_word_writes_are_little_endian() { fn halfword_and_word_writes_are_little_endian() {

View file

@ -1,4 +1,4 @@
use gba_rust::{ use gba_core::{
io::*, io::*,
memory::{Memory, MemoryError, OAM_START as OAM, PALETTE_START as PAL, VRAM_START as VRAM}, memory::{Memory, MemoryError, OAM_START as OAM, PALETTE_START as PAL, VRAM_START as VRAM},
video::{rgb555_to_rgb888 as rgb, Framebuffer, HEIGHT, WIDTH}, video::{rgb555_to_rgb888 as rgb, Framebuffer, HEIGHT, WIDTH},

View file

@ -1,6 +1,6 @@
use super::*; use super::*;
use gba_rust::{ use gba_core::input::{Button, Buttons};
input::{Button, Buttons}, use gba_demos::{
mosaic_demo::{MosaicDemo, MOSAIC_STATE}, mosaic_demo::{MosaicDemo, MOSAIC_STATE},
tile_demo::TileDemo, tile_demo::TileDemo,
}; };
@ -72,7 +72,7 @@ fn both_bypasses_restore_original_scene_with_scroll_rotation_zoom_and_reset() {
#[test] #[test]
fn dma_word_write_sets_bg_and_obj_fields_while_unused_halfword_ignores_writes() { fn dma_word_write_sets_bg_and_obj_fields_while_unused_halfword_ignores_writes() {
use gba_rust::{ use gba_core::{
cpu::Cpu, cpu::Cpu,
dma::DMA_BASE, dma::DMA_BASE,
machine::{Machine, StepKind}, machine::{Machine, StepKind},

View file

@ -1,4 +1,4 @@
use gba_rust::{ use gba_core::{
cpu::Cpu, cpu::Cpu,
display::{ display::{
CYCLES_PER_FRAME as FRAME, CYCLES_PER_LINE as LINE, HBLANK_START as HB, VBLANK_START as VB, CYCLES_PER_FRAME as FRAME, CYCLES_PER_LINE as LINE, HBLANK_START as HB, VBLANK_START as VB,

View file

@ -1,4 +1,4 @@
use gba_rust::{ use gba_core::{
cpu::Cpu, cpu::Cpu,
display::{CYCLES_PER_FRAME, CYCLES_PER_LINE, HBLANK_START, VBLANK_START}, display::{CYCLES_PER_FRAME, CYCLES_PER_LINE, HBLANK_START, VBLANK_START},
dma::DMA_BASE, dma::DMA_BASE,
@ -6,9 +6,9 @@ use gba_rust::{
io::*, io::*,
machine::{Machine, StepKind}, machine::{Machine, StepKind},
memory::{Memory, OAM_START as OAM, PALETTE_START as PAL, ROM_START, VRAM_START as VRAM}, memory::{Memory, OAM_START as OAM, PALETTE_START as PAL, ROM_START, VRAM_START as VRAM},
raster_demo::{RasterDemo, RASTER_STATE, RASTER_TABLE},
video::{rgb555_to_rgb888 as rgb, Framebuffer, VideoError, HEIGHT, WIDTH}, video::{rgb555_to_rgb888 as rgb, Framebuffer, VideoError, HEIGHT, WIDTH},
}; };
use gba_demos::raster_demo::{RasterDemo, RASTER_STATE, RASTER_TABLE};
#[path = "scanlines/capture.rs"] #[path = "scanlines/capture.rs"]
mod capture; mod capture;

View file

@ -1,5 +1,5 @@
//! Regression tests for the nominal allowance, not hardware fetch cutoffs. //! Regression tests for the nominal allowance, not hardware fetch cutoffs.
use gba_rust::{ use gba_core::{
cpu::Cpu, cpu::Cpu,
display::{CYCLES_PER_LINE as LINE, VBLANK_START as VB}, display::{CYCLES_PER_LINE as LINE, VBLANK_START as VB},
dma::DMA_BASE, dma::DMA_BASE,

View file

@ -1,4 +1,4 @@
use gba_rust::{ use gba_core::{
cpu::Cpu, cpu::Cpu,
display::{CYCLES_PER_FRAME as FRAME, CYCLES_PER_LINE as LINE, VBLANK_START as VB}, display::{CYCLES_PER_FRAME as FRAME, CYCLES_PER_LINE as LINE, VBLANK_START as VB},
dma::DMA_BASE, dma::DMA_BASE,

View file

@ -1,4 +1,4 @@
use gba_rust::{ use gba_core::{
io::{BG0CNT, BG2CNT, DISPCNT, GREENSWAP}, io::{BG0CNT, BG2CNT, DISPCNT, GREENSWAP},
memory::{Memory, MemoryError, OAM_START as OAM, PALETTE_START as PAL, VRAM_START as VRAM}, memory::{Memory, MemoryError, OAM_START as OAM, PALETTE_START as PAL, VRAM_START as VRAM},
video::{rgb555_to_rgb888, Framebuffer, VideoError, WIDTH}, video::{rgb555_to_rgb888, Framebuffer, VideoError, WIDTH},
@ -339,7 +339,7 @@ fn green_swap_applies_after_sprite_composition() {
#[test] #[test]
fn oam_bus_uses_one_cycle_for_every_width() { fn oam_bus_uses_one_cycle_for_every_width() {
use gba_rust::timing::{bus_cycles, AccessKind, AccessWidth}; use gba_core::timing::{bus_cycles, AccessKind, AccessWidth};
for kind in [AccessKind::Sequential, AccessKind::NonSequential] { for kind in [AccessKind::Sequential, AccessKind::NonSequential] {
for width in [AccessWidth::Byte, AccessWidth::Halfword, AccessWidth::Word] { for width in [AccessWidth::Byte, AccessWidth::Halfword, AccessWidth::Word] {
assert_eq!(bus_cycles(0, OAM, width, kind), 1); assert_eq!(bus_cycles(0, OAM, width, kind), 1);
@ -349,7 +349,7 @@ fn oam_bus_uses_one_cycle_for_every_width() {
#[test] #[test]
fn dma_copies_words_to_oam_and_reads_them_back() { fn dma_copies_words_to_oam_and_reads_them_back() {
use gba_rust::{ use gba_core::{
cpu::Cpu, cpu::Cpu,
dma::DMA_BASE, dma::DMA_BASE,
machine::{Machine, StepKind}, machine::{Machine, StepKind},

View file

@ -1,4 +1,4 @@
use gba_rust::{ use gba_core::{
io::*, io::*,
memory::{Memory, MemoryError, PALETTE_START as PAL, VRAM_START as VRAM}, memory::{Memory, MemoryError, PALETTE_START as PAL, VRAM_START as VRAM},
video::{rgb555_to_rgb888, Framebuffer, VideoError, WIDTH}, video::{rgb555_to_rgb888, Framebuffer, VideoError, WIDTH},
@ -308,10 +308,8 @@ fn assert_scene(
#[test] #[test]
fn cpu_tile_demo_copies_assets_scrolls_wraps_cancels_opposites_and_resets() { fn cpu_tile_demo_copies_assets_scrolls_wraps_cancels_opposites_and_resets() {
use gba_rust::{ use gba_core::input::{Button, Buttons};
input::{Button, Buttons}, use gba_demos::tile_demo::{TileDemo, TILE_STATE};
tile_demo::{TileDemo, TILE_STATE},
};
let mut demo = TileDemo::new().unwrap(); let mut demo = TileDemo::new().unwrap();
let mut frame = Framebuffer::default(); let mut frame = Framebuffer::default();
assert_eq!(demo.machine().memory().read16(VRAM).unwrap(), 0); assert_eq!(demo.machine().memory().read16(VRAM).unwrap(), 0);
@ -367,11 +365,11 @@ fn cpu_tile_demo_copies_assets_scrolls_wraps_cancels_opposites_and_resets() {
#[test] #[test]
fn cpu_sprite_controls_flip_and_change_priority_through_oam() { fn cpu_sprite_controls_flip_and_change_priority_through_oam() {
use gba_rust::{ use gba_core::{
input::{Button, Buttons}, input::{Button, Buttons},
memory::OAM_START, memory::OAM_START,
tile_demo::TileDemo,
}; };
use gba_demos::tile_demo::TileDemo;
let mut demo = TileDemo::new().unwrap(); let mut demo = TileDemo::new().unwrap();
let mut frame = Framebuffer::default(); let mut frame = Framebuffer::default();
for _ in 0..16 { for _ in 0..16 {
@ -411,11 +409,11 @@ fn cpu_sprite_controls_flip_and_change_priority_through_oam() {
#[test] #[test]
fn cpu_affine_controls_write_matrices_preserve_center_and_return_to_regular_mode() { fn cpu_affine_controls_write_matrices_preserve_center_and_return_to_regular_mode() {
use gba_rust::{ use gba_core::{
input::{Button, Buttons}, input::{Button, Buttons},
memory::OAM_START, memory::OAM_START,
tile_demo::TileDemo,
}; };
use gba_demos::tile_demo::TileDemo;
let mut demo = TileDemo::new().unwrap(); let mut demo = TileDemo::new().unwrap();
let mut frame = Framebuffer::default(); let mut frame = Framebuffer::default();
// Put foreground crosses over the sprite so X also tests affine composition. // Put foreground crosses over the sprite so X also tests affine composition.

View file

@ -1,4 +1,4 @@
use gba_rust::{ use gba_core::{
io::{IE, IF, IME, TIMER_BASE}, io::{IE, IF, IME, TIMER_BASE},
memory::{Memory, MemoryError}, memory::{Memory, MemoryError},
}; };

View file

@ -1,4 +1,4 @@
use gba_rust::{ use gba_core::{
cpu::Cpu, cpu::Cpu,
io::{IE, IF, IME, TIMER_BASE, WAITCNT}, io::{IE, IF, IME, TIMER_BASE, WAITCNT},
machine::{Machine, StepKind}, machine::{Machine, StepKind},

View file

@ -1,4 +1,4 @@
use gba_rust::{ use gba_core::{
cpu::Cpu, cpu::Cpu,
display::{ display::{
CYCLES_PER_FRAME as FRAME, CYCLES_PER_LINE as LINE, HBLANK_START as HB, VBLANK_START as VB, CYCLES_PER_FRAME as FRAME, CYCLES_PER_LINE as LINE, HBLANK_START as HB, VBLANK_START as VB,

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