Major changes to setup. Now installable by mere mortals on Debian, Fedora, and WSL.
This commit is contained in:
parent
07f18d9ef5
commit
8b9590ddb2
116 changed files with 5632 additions and 4003 deletions
7
.gitignore
vendored
7
.gitignore
vendored
|
|
@ -11,8 +11,11 @@ build/
|
||||||
*.gs
|
*.gs
|
||||||
*.bin
|
*.bin
|
||||||
|
|
||||||
# Toolchain installations (everything fetched/built by install.sh)
|
# Everything ./install.sh downloads, builds or generates. Nothing in it is
|
||||||
toolchains/*
|
# tracked, so the folder can be deleted at any time and re-created.
|
||||||
|
toolchains/
|
||||||
|
# A project's private-bundle URL (installer/bundle.conf) is the owner's
|
||||||
|
# choice to commit or not; it is not ignored.
|
||||||
|
|
||||||
# Disk images
|
# Disk images
|
||||||
*.adf
|
*.adf
|
||||||
|
|
|
||||||
134
LICENSES.md
134
LICENSES.md
|
|
@ -22,7 +22,7 @@ JoeyLib. License compatibility for these matters most.
|
||||||
- **License:** MIT
|
- **License:** MIT
|
||||||
- **Role:** ProTracker / Amiga MOD replayer + SMIX SFX overlay. Linked
|
- **Role:** ProTracker / Amiga MOD replayer + SMIX SFX overlay. Linked
|
||||||
into AUDIO binaries on the chunky-display platforms.
|
into AUDIO binaries on the chunky-display platforms.
|
||||||
- **Vendored at:** `toolchains/audio/libxmp-lite/`
|
- **Installed at:** `toolchains/opt/libxmp/` (installer component `libxmp`)
|
||||||
|
|
||||||
### NinjaTrackerPlus (NTP)
|
### NinjaTrackerPlus (NTP)
|
||||||
|
|
||||||
|
|
@ -33,10 +33,9 @@ JoeyLib. License compatibility for these matters most.
|
||||||
by Jesse Blue (NinjaForce). Treated here as free for use; consult
|
by Jesse Blue (NinjaForce). Treated here as free for use; consult
|
||||||
upstream before commercial redistribution.
|
upstream before commercial redistribution.
|
||||||
- **Role:** Apple IIgs Ensoniq DOC `.NTP` module replayer + SFX
|
- **Role:** Apple IIgs Ensoniq DOC `.NTP` module replayer + SFX
|
||||||
streamer. Merlin32-assembled and **linked** into the AUDIO binary as
|
streamer. Merlin32-assembled into `ntpplayer`, which ships on the disk
|
||||||
bytes (see `make/iigs.mk` `$(NTP_HEADER)` rule).
|
next to each IIgs program and is loaded at run time.
|
||||||
- **Vendored at:** `toolchains/iigs/ntp/ninjatrackerplus.s` (source);
|
- **Installed at:** `toolchains/opt/ntp/` (source; installer component `ntp`).
|
||||||
build product baked into `examples/audio` AUDIO binary.
|
|
||||||
|
|
||||||
### PTPlayer
|
### PTPlayer
|
||||||
|
|
||||||
|
|
@ -46,7 +45,7 @@ JoeyLib. License compatibility for these matters most.
|
||||||
- **License:** Public Domain
|
- **License:** Public Domain
|
||||||
- **Role:** ProTracker MOD replayer + `mt_playfx` SFX channel for the
|
- **Role:** ProTracker MOD replayer + `mt_playfx` SFX channel for the
|
||||||
Paula audio HAL.
|
Paula audio HAL.
|
||||||
- **Vendored at:** `toolchains/amiga/ptplayer/`
|
- **Installed at:** `toolchains/opt/ptplayer/` (installer component `ptplayer`)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -64,10 +63,10 @@ produced game binaries.
|
||||||
LLVM license).
|
LLVM license).
|
||||||
- **Role:** clang C compiler targeting the 65816, plus llvm-mc for
|
- **Role:** clang C compiler targeting the 65816, plus llvm-mc for
|
||||||
assembling hand-written `.s` files and link816 for producing OMF
|
assembling hand-written `.s` files and link816 for producing OMF
|
||||||
S16 executables. Driven by `toolchains/iigs/clang-build.sh`; the
|
S16 executables. Driven by `make/support/iigs/clang-build.sh`; the
|
||||||
toolchain root is located via the `LLVM816_ROOT` environment
|
toolchain root is located via the `LLVM816_ROOT` environment
|
||||||
variable.
|
variable.
|
||||||
- **Vendored at:** `toolchains/iigs/llvm-mos/`
|
- **Installed at:** `toolchains/opt/llvm816/` (installer component `llvm816`)
|
||||||
|
|
||||||
### Merlin32
|
### Merlin32
|
||||||
|
|
||||||
|
|
@ -75,12 +74,12 @@ produced game binaries.
|
||||||
- **URL:** <https://brutaldeluxe.fr/products/crossdevtools/merlin/>
|
- **URL:** <https://brutaldeluxe.fr/products/crossdevtools/merlin/>
|
||||||
- **License:** Freely distributed by Brutal Deluxe Software
|
- **License:** Freely distributed by Brutal Deluxe Software
|
||||||
(Olivier Zardini, Antoine Vignau). Source published with no
|
(Olivier Zardini, Antoine Vignau). Source published with no
|
||||||
formal open-source license header — Brutal Deluxe's "open source
|
formal open-source license header -- Brutal Deluxe's "open source
|
||||||
philosophy" but copyright is retained. Treat as "free to use,
|
philosophy" but copyright is retained. Treat as "free to use,
|
||||||
redistribute as part of a build chain; ask before forking
|
redistribute as part of a build chain; ask before forking
|
||||||
commercially."
|
commercially."
|
||||||
- **Role:** 65816 cross-assembler. Builds NinjaTrackerPlus.
|
- **Role:** 65816 cross-assembler. Builds NinjaTrackerPlus.
|
||||||
- **Vendored at:** `toolchains/iigs/merlin32/`
|
- **Installed at:** `toolchains/opt/merlin32/` (installer component `merlin32`)
|
||||||
|
|
||||||
### Bebbo m68k-amigaos GCC toolchain
|
### Bebbo m68k-amigaos GCC toolchain
|
||||||
|
|
||||||
|
|
@ -92,13 +91,13 @@ produced game binaries.
|
||||||
domain, ira freeware, etc.).
|
domain, ira freeware, etc.).
|
||||||
- **Role:** AmigaOS HUNK-format gcc + binutils + libnix used to
|
- **Role:** AmigaOS HUNK-format gcc + binutils + libnix used to
|
||||||
compile the Amiga port and link against PTPlayer.
|
compile the Amiga port and link against PTPlayer.
|
||||||
- **Vendored at:** `toolchains/amiga/gcc/`
|
- **Installed at:** `toolchains/opt/amigaGcc/` (installer component `amigaGcc`)
|
||||||
|
|
||||||
### vasm
|
### vasm
|
||||||
|
|
||||||
- **Used on:** Amiga (PTPlayer only)
|
- **Used on:** Amiga (PTPlayer only)
|
||||||
- **URL:** <http://sun.hasenbraten.de/vasm/>
|
- **URL:** <http://sun.hasenbraten.de/vasm/>
|
||||||
- **License:** vasm custom license — © Volker Barthelmann; free for
|
- **License:** vasm custom license -- (c) Volker Barthelmann; free for
|
||||||
non-commercial use, plus an explicit **commercial-use exception
|
non-commercial use, plus an explicit **commercial-use exception
|
||||||
for M68k + AmigaOS targets** (see vasm distribution README). Other
|
for M68k + AmigaOS targets** (see vasm distribution README). Other
|
||||||
commercial use requires written permission. JoeyLib's only
|
commercial use requires written permission. JoeyLib's only
|
||||||
|
|
@ -107,7 +106,8 @@ produced game binaries.
|
||||||
- **Role:** Assemble third-party `ptplayer.asm` (Frank Wille's
|
- **Role:** Assemble third-party `ptplayer.asm` (Frank Wille's
|
||||||
Devpac/vasm-syntax source). Our own `.s` files are GAS syntax and
|
Devpac/vasm-syntax source). Our own `.s` files are GAS syntax and
|
||||||
go through `m68k-amigaos-as` (binutils, GPL) instead.
|
go through `m68k-amigaos-as` (binutils, GPL) instead.
|
||||||
- **Vendored at:** `toolchains/amiga/vasm/`
|
- **Installed at:** built with the Amiga GCC toolchain (AmigaPorts `vasm`
|
||||||
|
sub-project), `toolchains/opt/amigaGcc/bin/vasmm68k_mot`
|
||||||
|
|
||||||
### m68k-atari-mint GCC toolchain (Thorsten Otto builds)
|
### m68k-atari-mint GCC toolchain (Thorsten Otto builds)
|
||||||
|
|
||||||
|
|
@ -118,7 +118,7 @@ produced game binaries.
|
||||||
and permissive (per-file headers).
|
and permissive (per-file headers).
|
||||||
- **Role:** GEMDOS-native gcc + binutils + mintlib used to compile
|
- **Role:** GEMDOS-native gcc + binutils + mintlib used to compile
|
||||||
the Atari ST port.
|
the Atari ST port.
|
||||||
- **Vendored at:** `toolchains/atarist/gcc/`
|
- **Installed at:** `toolchains/opt/mintGcc/` (installer component `mintGcc`)
|
||||||
|
|
||||||
### DJGPP (build-djgpp packaging)
|
### DJGPP (build-djgpp packaging)
|
||||||
|
|
||||||
|
|
@ -127,23 +127,23 @@ produced game binaries.
|
||||||
(DJGPP upstream: <http://www.delorie.com/djgpp/>)
|
(DJGPP upstream: <http://www.delorie.com/djgpp/>)
|
||||||
- **License:** Build script GNU GPL v3; bundled gcc/binutils GNU
|
- **License:** Build script GNU GPL v3; bundled gcc/binutils GNU
|
||||||
GPL v2; DJGPP runtime libraries (libc) are mostly public-domain or
|
GPL v2; DJGPP runtime libraries (libc) are mostly public-domain or
|
||||||
permissive — see DJGPP `readme.1st`.
|
permissive -- see DJGPP `readme.1st`.
|
||||||
- **Role:** 32-bit protected-mode gcc cross-compiler producing
|
- **Role:** 32-bit protected-mode gcc cross-compiler producing
|
||||||
COFF binaries that run on 386+ DOS via DPMI.
|
COFF binaries that run on 386+ DOS via DPMI.
|
||||||
- **Vendored at:** `toolchains/dos/djgpp/`
|
- **Installed at:** `toolchains/opt/djgpp/` (installer component `djgpp`)
|
||||||
|
|
||||||
### CWSDPMI
|
### CWSDPMI
|
||||||
|
|
||||||
- **Used on:** DOS
|
- **Used on:** DOS
|
||||||
- **URL:** <https://sandmann.dotster.com/cwsdpmi/>
|
- **URL:** <https://sandmann.dotster.com/cwsdpmi/>
|
||||||
- **License:** Custom redistribution terms (Charles W. Sandmann,
|
- **License:** Custom redistribution terms (Charles W. Sandmann,
|
||||||
© 1995–2010). Binary may be redistributed without source provided
|
(c) 1995-2010). Binary may be redistributed without source provided
|
||||||
the executables are unmodified and end users are notified of
|
the executables are unmodified and end users are notified of
|
||||||
source availability — full terms in `cwsdpmi.doc`. Source is
|
source availability -- full terms in `cwsdpmi.doc`. Source is
|
||||||
GPL-licensed if distributed.
|
GPL-licensed if distributed.
|
||||||
- **Role:** Stubbed-into-binary DPMI server for DJGPP-compiled DOS
|
- **Role:** Stubbed-into-binary DPMI server for DJGPP-compiled DOS
|
||||||
executables (via `embed-dpmi.sh`).
|
executables (via `embed-dpmi.sh`).
|
||||||
- **Vendored at:** `toolchains/dos/cwsdpmi/`
|
- **Installed at:** `toolchains/opt/cwsdpmi/` (installer component `cwsdpmi`)
|
||||||
|
|
||||||
### NASM
|
### NASM
|
||||||
|
|
||||||
|
|
@ -152,93 +152,95 @@ produced game binaries.
|
||||||
- **License:** 2-clause BSD
|
- **License:** 2-clause BSD
|
||||||
- **Role:** Assembler for x86 inline-asm support files in the DOS
|
- **Role:** Assembler for x86 inline-asm support files in the DOS
|
||||||
port.
|
port.
|
||||||
- **Vendored at:** `toolchains/dos/nasm/`
|
- **Installed from:** the Linux distribution's `nasm` package
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Test-time emulators (not shipped, not linked)
|
## Test-time emulators (not shipped, not linked)
|
||||||
|
|
||||||
Used by `scripts/run-*.sh` to run the produced binaries on the
|
Used by `joey-run` and the test gates to run the produced binaries on the
|
||||||
developer's machine. Not part of any redistributed artifact.
|
developer's machine. Not part of any redistributed artifact. Installed by
|
||||||
|
`./install.sh install emulators`.
|
||||||
### GSplus (KEGS fork)
|
|
||||||
|
|
||||||
- **Used on:** Apple IIgs
|
|
||||||
- **URL:** <https://github.com/digarok/gsplus>
|
|
||||||
- **License:** GNU GPL v3
|
|
||||||
- **Vendored at:** `toolchains/emulators/gsplus/`
|
|
||||||
|
|
||||||
### MAME
|
### MAME
|
||||||
|
|
||||||
- **Used on:** Apple IIgs (apple2gs driver, optional)
|
- **Used on:** Apple IIgs (apple2gs driver) and Sharp X68000 (x68k driver)
|
||||||
- **URL:** <https://www.mamedev.org/>
|
- **URL:** <https://www.mamedev.org/>
|
||||||
- **License:** GNU GPL v2+ (GPL-2.0-or-later) plus a 3-clause BSD
|
- **License:** GNU GPL v2+ (GPL-2.0-or-later) plus a 3-clause BSD
|
||||||
alternative for some components.
|
alternative for some components.
|
||||||
|
- **Installed at:** `toolchains/opt/mame/`, built from the 0.264 release with
|
||||||
|
the patches in `installer/patches/mame/` (installer component `mame`).
|
||||||
|
|
||||||
### Hatari
|
### Hatari
|
||||||
|
|
||||||
- **Used on:** Atari ST
|
- **Used on:** Atari ST
|
||||||
- **URL:** <https://www.hatari-emu.org/>
|
- **URL:** <https://www.hatari-emu.org/>
|
||||||
- **License:** GNU GPL v2
|
- **License:** GNU GPL v2
|
||||||
|
- **Installed at:** the distribution package on Debian and Ubuntu; on Fedora,
|
||||||
|
`toolchains/opt/hatari/`, built from the 2.4.1 source (installer component
|
||||||
|
`hatari`)
|
||||||
|
|
||||||
### DOSBox
|
### DOSBox Staging
|
||||||
|
|
||||||
- **Used on:** DOS
|
- **Used on:** DOS
|
||||||
- **URL:** <https://www.dosbox.com/>
|
- **URL:** <https://www.dosbox-staging.org/>
|
||||||
- **License:** GNU GPL v2
|
- **License:** GNU GPL v2+
|
||||||
|
- **Installed at:** `toolchains/opt/dosbox/` (the project's Linux release;
|
||||||
|
installer component `dosbox`)
|
||||||
|
|
||||||
### FS-UAE
|
### FS-UAE
|
||||||
|
|
||||||
- **Used on:** Amiga
|
- **Used on:** Amiga
|
||||||
- **URL:** <https://fs-uae.net/>
|
- **URL:** <https://fs-uae.net/>
|
||||||
- **License:** GNU GPL v2 (UAE codebase)
|
- **License:** GNU GPL v2 (UAE codebase)
|
||||||
|
- **Installed at:** the distribution package on Debian and Ubuntu; on Fedora,
|
||||||
|
`toolchains/opt/fsuae/`, built from the 3.1.66 release (installer component
|
||||||
|
`fsuae`)
|
||||||
|
|
||||||
### EmuTOS
|
### EmuTOS
|
||||||
|
|
||||||
- **Used on:** Atari ST (TOS replacement for Hatari)
|
- **Used on:** Atari ST (TOS replacement for Hatari)
|
||||||
- **URL:** <https://emutos.sourceforge.io/>
|
- **URL:** <https://emutos.sourceforge.io/>
|
||||||
- **License:** GNU GPL v2
|
- **License:** GNU GPL v2
|
||||||
- **Vendored at:** `toolchains/emulators/support/emutos-512k.img`
|
- **Installed at:** `toolchains/firmware/atarist/emutos-512k.img` (installer
|
||||||
|
component `emutos`)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## System ROMs and disk images (used for testing only)
|
## System ROMs and disk images (used for testing only)
|
||||||
|
|
||||||
These are **not** part of JoeyLib distribution and **not**
|
These are **not** part of JoeyLib distribution. `./install.sh`
|
||||||
redistributed. They are downloaded to the developer's local cache by
|
places them in `toolchains/firmware/` for emulator-side testing only; see
|
||||||
`toolchains/install.sh` for emulator-side testing only. License terms
|
the source of each before shipping anything that depends on them.
|
||||||
prohibit redistribution; please consult the source if you intend to
|
|
||||||
ship anything that depends on them.
|
|
||||||
|
|
||||||
### Apple IIgs ROM 03
|
### Downloaded by the installer
|
||||||
|
|
||||||
- **Used on:** Apple IIgs (GSplus, MAME)
|
- **Apple IIgs MAME ROM sets** (`apple2gs`, ROM 03, and `apple2gsr1`,
|
||||||
- **URL:** Various preservation archives, e.g.
|
ROM 01): Apple Inc. proprietary firmware, from the archive.org MAME 0.240
|
||||||
<https://mirrors.apple2.org.za/ftp.apple.asimov.net/emulators/rom_images/>
|
split set. Component `iigsFirmware`.
|
||||||
- **License:** Apple Inc. proprietary firmware. Provided here for
|
- **Apple IIgs System 6.0.4** (`System.Disk`): Apple Inc. proprietary OS, from
|
||||||
developer testing only; **do not redistribute** as part of any
|
the Apple II archive (<https://mirrors.apple2.org.za/>). Component
|
||||||
derivative project.
|
`iigsFirmware`.
|
||||||
|
- **CFFA2 card firmware** (`cffa20eec02.bin`): freely redistributed by
|
||||||
|
dreher.net (<https://dreher.net/>). Component `iigsFirmware`.
|
||||||
|
- **X68000 IPL-ROM and Human68k 3.02 system disk**: from Sharp's own free
|
||||||
|
release (2000), published by the X68000 LIBRARY
|
||||||
|
(<http://retropc.net/x68000/software/sharp/>). Component `x68kFirmware`.
|
||||||
|
|
||||||
### Apple IIgs System 6.0.4
|
### Never downloaded (supplied by the user or the project's own bundle)
|
||||||
|
|
||||||
- **Used on:** Apple IIgs (GSplus boot disk)
|
Installed with `./install.sh import PATH`, or from the bundle a
|
||||||
- **URL:** <https://mirrors.apple2.org.za/ftp.apple.asimov.net/images/gs/os/gsos/>
|
project hosts (`JOEY_BUNDLE_URL`); matched by the checksums in
|
||||||
- **License:** Apple Inc. proprietary OS. Same caveat as the ROM.
|
`installer/files.sha1`.
|
||||||
|
|
||||||
### Apple IIgs apple2gs MAME ROM set
|
- **Amiga Kickstart 3.1 ROM**: Cloanto / Amiga Corp. proprietary, sold today
|
||||||
|
(Amiga Forever). Required by the Amiga test gates; `joey-run` falls back to
|
||||||
- **Used on:** Apple IIgs (MAME testing path)
|
FS-UAE's AROS without it. Component `kickstart`.
|
||||||
- **URL:** Same preservation archives as above.
|
- **X68000 CG (font) ROM**: not part of Sharp's free release. MAME's x68000
|
||||||
- **License:** Apple Inc. proprietary firmware. Required for MAME's
|
driver needs it. Component `x68kCgrom`.
|
||||||
apple2gs driver but not redistributed.
|
- **Space Taxi C64 memory image** (`stuff/spacetaxi/raw.bin`): the original
|
||||||
|
game's data, from which the example's levels are extracted. Optional.
|
||||||
### Amiga Kickstart ROM (optional)
|
Component `staxiData`.
|
||||||
|
|
||||||
- **Used on:** Amiga (FS-UAE, optional — falls back to AROS)
|
|
||||||
- **URL:** Cloanto / Amiga Forever (commercial), or original media.
|
|
||||||
- **License:** Cloanto / Amiga Corp. proprietary. JoeyLib does **not**
|
|
||||||
bundle Kickstart; users supply their own ROM at
|
|
||||||
`toolchains/emulators/support/kickstart.rom` if they have one.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -249,7 +251,7 @@ When adding a new third-party dependency:
|
||||||
1. Append a row in the appropriate section above.
|
1. Append a row in the appropriate section above.
|
||||||
2. Record platform(s), upstream URL, license name, and a one-line
|
2. Record platform(s), upstream URL, license name, and a one-line
|
||||||
role description.
|
role description.
|
||||||
3. If the dependency is vendored, list the path under
|
3. List where the installer puts it (`toolchains/opt/<component>/` or
|
||||||
`toolchains/`.
|
`toolchains/firmware/`).
|
||||||
4. If license terms restrict redistribution, call that out
|
4. If license terms restrict redistribution, call that out
|
||||||
explicitly (as we do for Apple ROMs).
|
explicitly (as we do for Apple ROMs).
|
||||||
|
|
|
||||||
40
Makefile
40
Makefile
|
|
@ -1,27 +1,30 @@
|
||||||
# JoeyLib top-level Makefile.
|
# JoeyLib top-level Makefile.
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# source toolchains/env.sh (first, every shell session)
|
# make builds every target whose toolchain is installed
|
||||||
# make builds every target whose toolchain is present
|
|
||||||
# make iigs builds Apple IIgs only
|
# make iigs builds Apple IIgs only
|
||||||
# make amiga builds Commodore Amiga only
|
# make amiga builds Commodore Amiga only
|
||||||
# make atarist builds Atari ST only
|
# make atarist builds Atari ST only
|
||||||
# make dos builds MS-DOS only
|
# make dos builds MS-DOS only
|
||||||
|
# make x68000 builds Sharp X68000 only
|
||||||
# make docs renders the manual to build/docs (HTML + PDF)
|
# make docs renders the manual to build/docs (HTML + PDF)
|
||||||
# make verify-audio runs every port's audio gate (needs emulators)
|
# make verify-audio runs every port's audio gate (needs emulators)
|
||||||
# make clean removes all build outputs
|
# make clean removes all build outputs
|
||||||
#
|
#
|
||||||
# The build references cross tools exclusively from toolchains/, set up
|
# Cross tools come from ./install.sh, which records their paths in
|
||||||
# by sourcing toolchains/env.sh. Do not invoke make without sourcing
|
# toolchains/config.mk; every makefile reads that file itself, so no
|
||||||
# env.sh first or builds will fail with missing tool errors.
|
# environment setup is needed first.
|
||||||
|
|
||||||
REPO_DIR := $(abspath $(dir $(firstword $(MAKEFILE_LIST))))
|
REPO_DIR := $(abspath $(dir $(firstword $(MAKEFILE_LIST))))
|
||||||
|
|
||||||
# Detect which toolchains are actually available.
|
-include $(REPO_DIR)/toolchains/config.mk
|
||||||
HAVE_IIGS := $(shell [ -x "$(IIGS_CC)" ] && echo 1)
|
|
||||||
HAVE_AMIGA := $(shell [ -x "$(AMIGA_CC)" ] && echo 1)
|
# A port is available when its real compiler binary is installed.
|
||||||
HAVE_ATARIST := $(shell [ -x "$(ST_CC)" ] && echo 1)
|
HAVE_IIGS := $(if $(wildcard $(LLVM816_ROOT)/tools/llvm-mos-build/bin/clang),1)
|
||||||
HAVE_DOS := $(shell [ -x "$(DOS_CC)" ] && echo 1)
|
HAVE_AMIGA := $(if $(wildcard $(AMIGA_CC)),1)
|
||||||
|
HAVE_ATARIST := $(if $(wildcard $(ST_CC)),1)
|
||||||
|
HAVE_DOS := $(if $(wildcard $(DOS_CC)),1)
|
||||||
|
HAVE_X68000 := $(if $(wildcard $(X68K_ROOT)/bin/m68k-xelf-gcc),1)
|
||||||
|
|
||||||
ALL_TARGETS :=
|
ALL_TARGETS :=
|
||||||
ifeq ($(HAVE_IIGS),1)
|
ifeq ($(HAVE_IIGS),1)
|
||||||
|
|
@ -36,12 +39,15 @@ endif
|
||||||
ifeq ($(HAVE_DOS),1)
|
ifeq ($(HAVE_DOS),1)
|
||||||
ALL_TARGETS += dos
|
ALL_TARGETS += dos
|
||||||
endif
|
endif
|
||||||
|
ifeq ($(HAVE_X68000),1)
|
||||||
|
ALL_TARGETS += x68000
|
||||||
|
endif
|
||||||
|
|
||||||
.PHONY: all iigs iigs-disk iigs-verify amiga atarist dos blank-check tools clean help status verify-audio docs
|
.PHONY: all iigs iigs-disk iigs-verify amiga atarist dos x68000 blank-check tools clean help status verify-audio docs
|
||||||
|
|
||||||
all: $(ALL_TARGETS) tools
|
all: $(ALL_TARGETS) tools
|
||||||
ifeq ($(ALL_TARGETS),)
|
ifeq ($(ALL_TARGETS),)
|
||||||
@echo "No toolchains detected. Run ./toolchains/install.sh and source toolchains/env.sh."
|
@echo "No toolchains detected. Run: ./install.sh install toolchains"
|
||||||
@exit 1
|
@exit 1
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
|
@ -66,6 +72,9 @@ atarist:
|
||||||
dos:
|
dos:
|
||||||
@$(MAKE) -f $(REPO_DIR)/make/dos.mk
|
@$(MAKE) -f $(REPO_DIR)/make/dos.mk
|
||||||
|
|
||||||
|
x68000:
|
||||||
|
@$(MAKE) -f $(REPO_DIR)/make/x68000.mk
|
||||||
|
|
||||||
# Compile + link-check the new-port template (src/blank/blank.c) with the host
|
# Compile + link-check the new-port template (src/blank/blank.c) with the host
|
||||||
# cc. No toolchain needed; keeps the template from bit-rotting as the lib grows.
|
# cc. No toolchain needed; keeps the template from bit-rotting as the lib grows.
|
||||||
blank-check:
|
blank-check:
|
||||||
|
|
@ -94,11 +103,12 @@ status:
|
||||||
@echo " amiga: $(if $(HAVE_AMIGA),yes,NO)"
|
@echo " amiga: $(if $(HAVE_AMIGA),yes,NO)"
|
||||||
@echo " atarist: $(if $(HAVE_ATARIST),yes,NO)"
|
@echo " atarist: $(if $(HAVE_ATARIST),yes,NO)"
|
||||||
@echo " dos: $(if $(HAVE_DOS),yes,NO)"
|
@echo " dos: $(if $(HAVE_DOS),yes,NO)"
|
||||||
|
@echo " x68000: $(if $(HAVE_X68000),yes,NO)"
|
||||||
|
|
||||||
help:
|
help:
|
||||||
@echo "Targets: iigs amiga atarist dos all clean status help"
|
@echo "Targets: iigs amiga atarist dos x68000 all clean status help"
|
||||||
@echo " iigs-disk iigs-verify (IIgs MAME visual check)"
|
@echo " iigs-disk iigs-verify (IIgs MAME visual check)"
|
||||||
@echo " blank-check (host-cc smoke test of the new-port template)"
|
@echo " blank-check (host-cc smoke test of the new-port template + gnu89 lint)"
|
||||||
@echo " verify-audio (all 5 audio gates; PORTS=\"atarist amiga\" for a subset)"
|
@echo " verify-audio (all 5 audio gates; PORTS=\"atarist amiga\" for a subset)"
|
||||||
@echo " docs (manual to build/docs; FORMAT=html for HTML only)"
|
@echo " docs (manual to build/docs; FORMAT=html for HTML only)"
|
||||||
@echo "Source toolchains/env.sh first (blank-check needs no toolchain)."
|
@echo "Install toolchains first: ./install.sh install toolchains"
|
||||||
|
|
|
||||||
81
README.md
81
README.md
|
|
@ -17,42 +17,68 @@ See `claudeDocs/DESIGN.md` for the full 1.0 design.
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
|
JoeyLib builds on Linux x86-64, and on Windows inside WSL2. One script
|
||||||
|
installs every cross toolchain, pinned and checksum-verified, into
|
||||||
|
`toolchains/`; outside that directory it only installs ordinary packages
|
||||||
|
from your distribution (apt or dnf), and only when asked.
|
||||||
|
|
||||||
```
|
```
|
||||||
git clone <repo> joeylib
|
git clone https://forge.duensing.digital/AI_Slop/joeylib2.git joeylib
|
||||||
cd joeylib
|
cd joeylib
|
||||||
./toolchains/install.sh
|
./install.sh deps toolchains --install # distribution packages (sudo)
|
||||||
# follow any non-free-tool placement instructions the script prints
|
./install.sh install toolchains # every cross toolchain
|
||||||
source toolchains/env.sh
|
|
||||||
make
|
make
|
||||||
```
|
```
|
||||||
|
|
||||||
This builds `libjoey.a` for every target whose toolchain is installed,
|
The first `install` takes about an hour, almost all of it compiling the IIgs
|
||||||
plus the example programs (`hello`, `pattern`, `draw`, `keys`, `serial`,
|
and Amiga compilers. `./install.sh install toolchain:dos` (or any
|
||||||
|
other port) installs just one. `./install.sh list` shows every
|
||||||
|
component and whether it is current, and `./install.sh doctor`
|
||||||
|
smoke-tests what is installed. The manual's Getting Started chapter
|
||||||
|
(`docs/GettingStarted.adoc`) has the full installer reference.
|
||||||
|
|
||||||
|
No environment setup is needed: `make` reads the tool paths the installer
|
||||||
|
records in `toolchains/config.mk`. For running a cross compiler by
|
||||||
|
hand, `source toolchains/env.sh` sets the same variables.
|
||||||
|
|
||||||
|
`make` builds `libjoey.a` for every port whose toolchain is installed, plus
|
||||||
|
the example programs (`hello`, `pattern`, `draw`, `keys`, `serial`,
|
||||||
`sertest`, `save`, `joy`, `sprite`, `audio`, `uber`, `adventure`,
|
`sertest`, `save`, `joy`, `sprite`, `audio`, `uber`, `adventure`,
|
||||||
`adventure2`, `agi`, `spacetaxi`) for each. The IIgs is the exception:
|
`adventure2`, `agi`, `spacetaxi`) for each. The IIgs is the exception:
|
||||||
`make iigs` compile-checks the library and runs the clang smoke test, and
|
`make iigs` builds the library, and `make iigs-disk` builds its examples and
|
||||||
`make -f make/iigs.mk iigs-examples` builds its example binaries. The
|
packages them onto a disk image.
|
||||||
Sharp X68000 is not covered at all -- see below.
|
|
||||||
|
Space Taxi's levels come from the original Commodore 64 game, which is not in
|
||||||
|
the repository. Without `stuff/spacetaxi/raw.bin` the build compiles the
|
||||||
|
STAXI program, skips only its data, and prints a note saying so.
|
||||||
|
|
||||||
|
|
||||||
|
## Running programs
|
||||||
|
|
||||||
|
`joey-run` starts a built program in the right emulator; the installer sets up
|
||||||
|
the emulators and their firmware:
|
||||||
|
|
||||||
|
```
|
||||||
|
./install.sh install emulators # or emulator:dos, emulator:iigs, ...
|
||||||
|
./joey-run build/dos/bin/DRAW.EXE
|
||||||
|
./joey-run --port amiga draw
|
||||||
|
./joey-run --headless --seconds 30 --snapshot draw.png --port atarist draw
|
||||||
|
```
|
||||||
|
|
||||||
|
Three files are never downloaded -- the Amiga Kickstart, the X68000 font ROM
|
||||||
|
and Space Taxi's C64 memory image -- so supply your copies once with
|
||||||
|
`./install.sh import PATH`. The manual's Getting Started chapter
|
||||||
|
covers `joey-run`'s options and the private-file bundle.
|
||||||
|
|
||||||
|
|
||||||
## Building for a single target
|
## Building for a single target
|
||||||
|
|
||||||
```
|
```
|
||||||
source toolchains/env.sh
|
|
||||||
make iigs
|
make iigs
|
||||||
make amiga
|
make amiga
|
||||||
make atarist
|
make atarist
|
||||||
make dos
|
make dos
|
||||||
```
|
make x68000
|
||||||
|
|
||||||
The Sharp X68000 has no top-level target: `toolchains/install.sh` does not
|
|
||||||
fetch elf2x68k and the root `Makefile` has no `x68000` rule. Stage the
|
|
||||||
elf2x68k tarball under `toolchains/x68000/m68k-xelf` and drive the fragment
|
|
||||||
directly -- it resolves its own compiler relative to the repo, so this one
|
|
||||||
does not need `env.sh`:
|
|
||||||
|
|
||||||
```
|
|
||||||
make -f make/x68000.mk # libjoey.a plus SERIAL.X, UBER.X, AUDIO.X
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Every fragment also answers to a bare `clean`, and every fragment that builds
|
Every fragment also answers to a bare `clean`, and every fragment that builds
|
||||||
|
|
@ -79,8 +105,8 @@ make -f make/x68000.mk x68000-verify-golden # ~70 min under the patched MAME
|
||||||
make -f make/x68000.mk x68000-verify-serial # both-directions RS-232C, ~1 min
|
make -f make/x68000.mk x68000-verify-serial # both-directions RS-232C, ~1 min
|
||||||
```
|
```
|
||||||
|
|
||||||
Both X68000 gates want `X68K_SCRATCH` pointing at a work dir containing
|
Both X68000 gates run under the patched MAME and the X68000 firmware that
|
||||||
`x68mame/`, and a MAME built with the patches in `patches/`.
|
`./install.sh install emulator:x68000` sets up.
|
||||||
|
|
||||||
|
|
||||||
## Repository layout
|
## Repository layout
|
||||||
|
|
@ -104,11 +130,14 @@ tools/songbake/ chip-tracker text notation -> JYM1 stream
|
||||||
tools/xdftool.py Human68k FAT12 .XDF disk image reader / writer
|
tools/xdftool.py Human68k FAT12 .XDF disk image reader / writer
|
||||||
tools/diff-uber-hashes UBER golden-hash comparator
|
tools/diff-uber-hashes UBER golden-hash comparator
|
||||||
examples/ example programs
|
examples/ example programs
|
||||||
scripts/ run / bench / verification scripts
|
joey-run run a built program in its emulator
|
||||||
patches/ MAME patches the X68000 gates need
|
scripts/ bench / verification scripts (scripts/lib: shared helpers)
|
||||||
tests/goldens/uber/ frozen UBER conformance hashes
|
tests/goldens/uber/ frozen UBER conformance hashes
|
||||||
toolchains/ self-contained cross-build tools
|
install.sh installs toolchains, emulators and firmware
|
||||||
make/ per-target Makefile fragments
|
installer/ the installer's component recipes, patches, lists
|
||||||
|
toolchains/ everything install.sh downloads and builds (not in
|
||||||
|
git; delete it any time and re-run install.sh)
|
||||||
|
make/ per-target Makefile fragments (make/support: build helpers)
|
||||||
build/<plat>/ per-target build outputs
|
build/<plat>/ per-target build outputs
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -133,8 +133,10 @@
|
||||||
|===
|
|===
|
||||||
|Symptom |Likely cause
|
|Symptom |Likely cause
|
||||||
|
|
||||||
|Build produces stale or missing binaries, `Error 127 (ignored)`
|
|`make` stops with "toolchain:PORT is not installed"
|
||||||
|`toolchains/env.sh` was not sourced in this shell.
|
|That port's cross toolchain is missing or incomplete. Run
|
||||||
|
`./install.sh install toolchain:PORT`; `./install.sh doctor`
|
||||||
|
smoke-tests what is installed.
|
||||||
|
|
||||||
|Sprites draw but the game crawls
|
|Sprites draw but the game crawls
|
||||||
|The sprites were never compiled. Call `jlSpritePrewarm` during loading. Check
|
|The sprites were never compiled. Call `jlSpritePrewarm` during loading. Check
|
||||||
|
|
|
||||||
|
|
@ -212,8 +212,10 @@ The output extension selects the conversion:
|
||||||
Amiga replayer, PTPlayer, has no native song-end signal, so without this
|
Amiga replayer, PTPlayer, has no native song-end signal, so without this
|
||||||
marker `jlAudioPlayMod(..., loop=false)` loops anyway. Other replayers ignore
|
marker `jlAudioPlayMod(..., loop=false)` loops anyway. Other replayers ignore
|
||||||
`E8FF`, so an `.amod` is harmless if it reaches the wrong port.
|
`E8FF`, so an `.amod` is harmless if it reaches the wrong port.
|
||||||
* *`.ntp`* -- NinjaTrackerPlus format for the IIgs, produced by the staged
|
* *`.ntp`* -- NinjaTrackerPlus format for the IIgs, produced by the
|
||||||
converter under `toolchains/iigs/ntp/`.
|
NinjaTrackerPlus converter the installer's `ntp` component provides (part of
|
||||||
|
`toolchain:iigs`; it needs the PHP command-line interpreter, which the
|
||||||
|
installer's host packages include).
|
||||||
|
|
||||||
Keep modules modest. A four-channel mix is real CPU work on DOS and the ST, and
|
Keep modules modest. A four-channel mix is real CPU work on DOS and the ST, and
|
||||||
the sample data has to fit in memory alongside your game.
|
the sample data has to fit in memory alongside your game.
|
||||||
|
|
|
||||||
|
|
@ -112,28 +112,28 @@ can hide hundreds of kilobytes in subdirectories that a directory listing does
|
||||||
not show; rebuild from empty rather than deleting files if space seems to
|
not show; rebuild from empty rather than deleting files if space seems to
|
||||||
vanish.
|
vanish.
|
||||||
|
|
||||||
*Amiga.* The runner writes an `s/startup-sequence` that invokes your binary.
|
*Amiga.* `joey-run` writes an `s/startup-sequence` that invokes your binary.
|
||||||
|
|
||||||
=== Verifying It Actually Runs
|
=== Verifying It Actually Runs
|
||||||
|
|
||||||
Build success is weak evidence on retro targets. Run it:
|
Build success is weak evidence on retro targets. Run it with `joey-run` (see
|
||||||
|
<<Running What You Built>>), on every machine:
|
||||||
|
|
||||||
[source,console]
|
[source,console]
|
||||||
----
|
----
|
||||||
$ scripts/run-iigs.sh MYGAME
|
$ ./joey-run --port iigs mygame
|
||||||
$ scripts/run-amiga.sh MyGame
|
$ ./joey-run --port amiga mygame
|
||||||
$ scripts/run-atarist.sh MYGAME
|
$ ./joey-run --port atarist mygame
|
||||||
$ scripts/run-dos.sh MYGAME
|
$ ./joey-run --port dos mygame
|
||||||
$ scripts/run-x68000.sh MYGAME
|
$ ./joey-run --port x68000 mygame
|
||||||
----
|
----
|
||||||
|
|
||||||
There are also headless runners that capture a screenshot and the program's log
|
Without a window, which is what you want while iterating, run it headless and
|
||||||
without opening a window, which is what you want when iterating:
|
look at the snapshot; the program's log lands next to it as usual:
|
||||||
|
|
||||||
[source,console]
|
[source,console]
|
||||||
----
|
----
|
||||||
$ scripts/run-atarist-headless.sh MYGAME 90
|
$ ./joey-run --headless --seconds 90 --snapshot mygame.png --port atarist mygame
|
||||||
$ scripts/run-amiga-headless.sh MyGame 90
|
|
||||||
----
|
----
|
||||||
|
|
||||||
=== The Gates
|
=== The Gates
|
||||||
|
|
|
||||||
|
|
@ -2,29 +2,47 @@
|
||||||
|
|
||||||
=== Setting Up the Toolchains
|
=== Setting Up the Toolchains
|
||||||
|
|
||||||
JoeyLib cross-compiles for five machines, and every cross toolchain lives under
|
JoeyLib cross-compiles for five machines. It builds on Linux x86-64, and on
|
||||||
`toolchains/` in the repository. They are fetched and built once:
|
Windows inside WSL2. One script installs every cross toolchain, pinned to a
|
||||||
|
known-good version and checked against a published checksum, into
|
||||||
|
`toolchains/` in the repository. The only things it installs anywhere else
|
||||||
|
are ordinary packages from your distribution (apt on Debian and Ubuntu, dnf
|
||||||
|
on Fedora), and only when you ask it to.
|
||||||
|
|
||||||
|
Two commands set up a new machine:
|
||||||
|
|
||||||
[source,console]
|
[source,console]
|
||||||
----
|
----
|
||||||
$ ./toolchains/install.sh
|
$ ./install.sh deps toolchains --install
|
||||||
|
$ ./install.sh install toolchains
|
||||||
----
|
----
|
||||||
|
|
||||||
Then, *in every shell session before you build anything*:
|
The first installs the distribution packages the toolchains need; it runs
|
||||||
|
`sudo` once. The second downloads or builds every cross toolchain. The first
|
||||||
|
run takes about an hour, almost all of it compiling the Apple IIgs and Amiga
|
||||||
|
compilers. To start working sooner, install only the ports you need:
|
||||||
|
|
||||||
[source,console]
|
[source,console]
|
||||||
----
|
----
|
||||||
$ source toolchains/env.sh
|
$ ./install.sh install toolchain:dos toolchain:atarist
|
||||||
----
|
----
|
||||||
|
|
||||||
[WARNING]
|
Running `install` again is cheap. Components that are already current are
|
||||||
====
|
skipped, and a component is rebuilt only when its pinned version changes.
|
||||||
Forgetting `source toolchains/env.sh` is the single most common build problem.
|
|
||||||
The makefiles reference the cross compilers only through the variables that
|
No environment setup is needed afterwards. `make` reads the tool paths the
|
||||||
script sets, so without it a build can appear to succeed while producing stale
|
installer records in `toolchains/config.mk`, and the scripts in
|
||||||
or missing binaries, and errors are reported as `Error 127 (ignored)`. If a
|
`scripts/` load the same settings themselves. To use a cross compiler by hand
|
||||||
build behaves strangely, source the script and try again.
|
in a shell, `source toolchains/env.sh` sets the same variables.
|
||||||
====
|
|
||||||
|
If a port's toolchain is missing, its build stops at once and says what to
|
||||||
|
install:
|
||||||
|
|
||||||
|
[source,console]
|
||||||
|
----
|
||||||
|
$ make amiga
|
||||||
|
make/amiga.mk:5: *** toolchain:amiga is not installed; run: ./install.sh install toolchain:amiga. Stop.
|
||||||
|
----
|
||||||
|
|
||||||
Check what the build can see:
|
Check what the build can see:
|
||||||
|
|
||||||
|
|
@ -36,27 +54,143 @@ Toolchain availability:
|
||||||
amiga: yes
|
amiga: yes
|
||||||
atarist: yes
|
atarist: yes
|
||||||
dos: yes
|
dos: yes
|
||||||
|
x68000: yes
|
||||||
----
|
----
|
||||||
|
|
||||||
|
==== Installer reference
|
||||||
|
|
||||||
|
`./install.sh [options] COMMAND [TARGETS...]`, run from the top of the
|
||||||
|
repository.
|
||||||
|
|
||||||
|
[cols="1,3"]
|
||||||
|
|===
|
||||||
|
| Command | What it does
|
||||||
|
|
||||||
|
| `list`
|
||||||
|
| Every component, the groups it belongs to, and whether it is current,
|
||||||
|
outdated or missing.
|
||||||
|
|
||||||
|
| `install TARGETS`
|
||||||
|
| Installs or updates the targets and everything they depend on, and
|
||||||
|
smoke-tests each one it installs. A failure does not stop the rest; the
|
||||||
|
failures are listed at the end.
|
||||||
|
|
||||||
|
| `deps TARGETS`
|
||||||
|
| Lists the distribution packages the targets need and which are missing.
|
||||||
|
With `--install`, installs the missing ones.
|
||||||
|
|
||||||
|
| `doctor`
|
||||||
|
| Checks the host, then smoke-tests every installed component (each cross
|
||||||
|
compiler compiles a file, and every prebuilt program must find its shared
|
||||||
|
libraries, for example). It reports anything out of date, any component
|
||||||
|
whose files have gone, and any part of a port you installed only partly,
|
||||||
|
such as FS-UAE without a Kickstart.
|
||||||
|
|
||||||
|
| `import PATH...`
|
||||||
|
| Installs the files that are not downloaded (see <<Firmware that is not
|
||||||
|
downloaded>>) from files, folders or `.zip` archives, matched by checksum.
|
||||||
|
|
||||||
|
| `bundle FILE`
|
||||||
|
| Packs those files, as present on this machine, into `FILE` (a `.tar.gz`)
|
||||||
|
for a project to host.
|
||||||
|
|===
|
||||||
|
|
||||||
|
A target is a component name from `list`, or a group:
|
||||||
|
|
||||||
|
[cols="1,3"]
|
||||||
|
|===
|
||||||
|
| Target | Means
|
||||||
|
|
||||||
|
| `toolchain:PORT`
|
||||||
|
| Everything needed to build for one port: `iigs`, `amiga`, `atarist`, `dos`
|
||||||
|
or `x68000`.
|
||||||
|
|
||||||
|
| `emulator:PORT`
|
||||||
|
| Everything needed to run that port's programs: the emulator and its
|
||||||
|
firmware.
|
||||||
|
|
||||||
|
| `PORT`
|
||||||
|
| Both of the above.
|
||||||
|
|
||||||
|
| `toolchains`
|
||||||
|
| Every port's toolchain.
|
||||||
|
|
||||||
|
| `emulators`
|
||||||
|
| Every port's emulator.
|
||||||
|
|
||||||
|
| `all`
|
||||||
|
| Everything except optional components (such as `staxiData`, Space Taxi's
|
||||||
|
level source), which are installed by name.
|
||||||
|
|===
|
||||||
|
|
||||||
|
[cols="1,3"]
|
||||||
|
|===
|
||||||
|
| Option | Effect
|
||||||
|
|
||||||
|
| `-y`, `--yes`
|
||||||
|
| Install missing distribution packages without stopping to ask.
|
||||||
|
|
||||||
|
| `--install`
|
||||||
|
| With `deps`: install the missing packages.
|
||||||
|
|
||||||
|
| `--force`
|
||||||
|
| Reinstall components even when they are current.
|
||||||
|
|
||||||
|
| `--jobs N`
|
||||||
|
| Parallel build jobs. The default is every core.
|
||||||
|
|
||||||
|
| `--keep-build`
|
||||||
|
| Keep the scratch build trees (normally deleted after a successful install).
|
||||||
|
|
||||||
|
| `--llvm816-dir PATH`
|
||||||
|
| Use an existing checkout of llvm816, the IIgs compiler, instead of cloning
|
||||||
|
it. For working on the compiler and JoeyLib together. The setting is
|
||||||
|
remembered; `--llvm816-dir clone` goes back to a managed clone.
|
||||||
|
|===
|
||||||
|
|
||||||
|
Everything the installer creates stays inside `toolchains/`:
|
||||||
|
|
||||||
|
[cols="1,3"]
|
||||||
|
|===
|
||||||
|
| Path | Contents
|
||||||
|
|
||||||
|
| `toolchains/opt/`
|
||||||
|
| The installed components, one directory each.
|
||||||
|
|
||||||
|
| `toolchains/firmware/`
|
||||||
|
| ROM and system-disk images for the emulators.
|
||||||
|
|
||||||
|
| `toolchains/cache/`
|
||||||
|
| Downloads, kept and re-verified by checksum on reuse, and scratch build
|
||||||
|
trees.
|
||||||
|
|
||||||
|
| `toolchains/state/`
|
||||||
|
| Which version of each component is installed, and settings such as
|
||||||
|
`--llvm816-dir`.
|
||||||
|
|
||||||
|
| `toolchains/config.mk`, `toolchains/env.sh`
|
||||||
|
| The tool paths, for `make` and for shells.
|
||||||
|
|===
|
||||||
|
|
||||||
|
Nothing in `toolchains/` is part of the repository: delete the whole folder
|
||||||
|
to return the machine to a clean state, and `./install.sh` re-creates it. The
|
||||||
|
installer itself lives in `install.sh` and `installer/` (its component
|
||||||
|
recipes, patches and package lists), and the build helpers the makefiles call
|
||||||
|
live in `make/support/`.
|
||||||
|
|
||||||
=== Building
|
=== Building
|
||||||
|
|
||||||
[source,console]
|
[source,console]
|
||||||
----
|
----
|
||||||
$ make # every target whose toolchain is present
|
$ make # every target whose toolchain is installed
|
||||||
$ make iigs # Apple IIgs only
|
$ make iigs # Apple IIgs only
|
||||||
$ make amiga # Commodore Amiga only
|
$ make amiga # Commodore Amiga only
|
||||||
$ make atarist # Atari ST only
|
$ make atarist # Atari ST only
|
||||||
$ make dos # MS-DOS only
|
$ make dos # MS-DOS only
|
||||||
|
$ make x68000 # Sharp X68000 only
|
||||||
$ make clean # remove all build outputs
|
$ make clean # remove all build outputs
|
||||||
----
|
----
|
||||||
|
|
||||||
The X68000 port has its own makefile:
|
|
||||||
|
|
||||||
[source,console]
|
|
||||||
----
|
|
||||||
$ make -f make/x68000.mk
|
|
||||||
----
|
|
||||||
|
|
||||||
Built programs land under `build/<platform>/bin/`. The IIgs additionally
|
Built programs land under `build/<platform>/bin/`. The IIgs additionally
|
||||||
produces a bootable disk image:
|
produces a bootable disk image:
|
||||||
|
|
||||||
|
|
@ -65,6 +199,15 @@ produces a bootable disk image:
|
||||||
$ make iigs-disk # build/iigs/bin/joey.2mg
|
$ make iigs-disk # build/iigs/bin/joey.2mg
|
||||||
----
|
----
|
||||||
|
|
||||||
|
Space Taxi's levels come from the original Commodore 64 game, which is not
|
||||||
|
part of the repository. Without `stuff/spacetaxi/raw.bin` the build still
|
||||||
|
compiles the STAXI program, skips only its data files, and says so:
|
||||||
|
|
||||||
|
[source,console]
|
||||||
|
----
|
||||||
|
note: Space Taxi data skipped: stuff/spacetaxi/raw.bin is not present
|
||||||
|
----
|
||||||
|
|
||||||
[TIP]
|
[TIP]
|
||||||
====
|
====
|
||||||
Never judge a retro build by its exit status alone. Check that the file you
|
Never judge a retro build by its exit status alone. Check that the file you
|
||||||
|
|
@ -165,19 +308,107 @@ back buffer called the *stage*; nothing reaches the display until you present.
|
||||||
|
|
||||||
=== Running What You Built
|
=== Running What You Built
|
||||||
|
|
||||||
Helper scripts boot each target in an emulator:
|
`joey-run`, at the top of the repository, starts a built program in the right
|
||||||
|
emulator. The emulators, and the firmware they boot, come from the installer:
|
||||||
|
|
||||||
[source,console]
|
[source,console]
|
||||||
----
|
----
|
||||||
$ scripts/run-iigs.sh DRAW
|
$ ./install.sh install emulators # every machine
|
||||||
$ scripts/run-amiga.sh Draw
|
$ ./install.sh install emulator:dos # or just one
|
||||||
$ scripts/run-atarist.sh DRAW
|
|
||||||
$ scripts/run-dos.sh DRAW
|
|
||||||
$ scripts/run-x68000.sh DRAW
|
|
||||||
----
|
----
|
||||||
|
|
||||||
Note the capitalisation differences: the IIgs, ST and DOS use uppercase names
|
MAME (for the IIgs and X68000) is always built from source, because the test
|
||||||
(ProDOS, GEMDOS and DOS 8.3 conventions), while the Amiga keeps mixed case.
|
gates need JoeyLib's patches to it. DOSBox Staging is upstream's own Linux
|
||||||
|
release. Hatari and FS-UAE are distribution packages on Debian and Ubuntu;
|
||||||
|
Fedora does not ship the versions the gates expect, so there the installer
|
||||||
|
builds both from source.
|
||||||
|
|
||||||
|
Point `joey-run` at a program, or name an example together with `--port`:
|
||||||
|
|
||||||
|
[source,console]
|
||||||
|
----
|
||||||
|
$ ./joey-run build/dos/bin/DRAW.EXE
|
||||||
|
$ ./joey-run build/amiga/bin/Draw
|
||||||
|
$ ./joey-run --port atarist draw
|
||||||
|
$ ./joey-run build/iigs/bin/STAXI.2mg
|
||||||
|
----
|
||||||
|
|
||||||
|
It recognises the machine from the file itself. Interactive runs use the
|
||||||
|
program's own folder or disk image, so saves and logs persist from one run to
|
||||||
|
the next. Nothing is written to your home directory.
|
||||||
|
|
||||||
|
[cols="1,3"]
|
||||||
|
|===
|
||||||
|
| Option | Effect
|
||||||
|
|
||||||
|
| `--port PORT`
|
||||||
|
| The machine, when naming an example rather than a file.
|
||||||
|
|
||||||
|
| `--data DIR`
|
||||||
|
| Copy `DIR`'s files into the program's `DATA` folder for this run, for
|
||||||
|
example an AGI game's files.
|
||||||
|
|
||||||
|
| `--headless`
|
||||||
|
| No window and no sound, for scripts and continuous integration. Stops after
|
||||||
|
60 seconds unless `--seconds` says otherwise.
|
||||||
|
|
||||||
|
| `--seconds N`
|
||||||
|
| Stop after `N` seconds.
|
||||||
|
|
||||||
|
| `--snapshot FILE`
|
||||||
|
| Save a PNG of the screen when the run stops.
|
||||||
|
|
||||||
|
| `--machine rom01`
|
||||||
|
| Apple IIgs: the ROM 01 machine instead of ROM 03.
|
||||||
|
|
||||||
|
| `--ram SIZE`
|
||||||
|
| Apple IIgs: MAME's memory size. The default is `4M`; AGI wants `8M`.
|
||||||
|
|
||||||
|
| `--debug`
|
||||||
|
| Apple IIgs and X68000: start MAME's debugger.
|
||||||
|
|
||||||
|
| `-- ARGS`
|
||||||
|
| Pass `ARGS` to the program. DOS only: the other machines' autostart cannot
|
||||||
|
pass arguments.
|
||||||
|
|===
|
||||||
|
|
||||||
|
A headless run with a snapshot is the quickest way to check a change:
|
||||||
|
|
||||||
|
[source,console]
|
||||||
|
----
|
||||||
|
$ ./joey-run --headless --seconds 30 --snapshot draw.png --port dos draw
|
||||||
|
----
|
||||||
|
|
||||||
|
==== The Apple IIgs
|
||||||
|
|
||||||
|
The IIgs runs programs from disk images. `make -f make/iigs.mk iigs-game-disk
|
||||||
|
GAME=DRAW` builds a bootable one, `build/iigs/bin/DRAW.2mg`, which is also how
|
||||||
|
a game ships. Given a data disk instead, such as `joey.2mg`, `joey-run` boots
|
||||||
|
GS/OS beside it and you open the program in the Finder. Given a bare program,
|
||||||
|
`build/iigs/bin/DRAW`, it builds both disks itself, which needs the IIgs
|
||||||
|
toolchain.
|
||||||
|
|
||||||
|
==== Firmware that is not downloaded
|
||||||
|
|
||||||
|
Almost everything the emulators need is downloaded: EmuTOS for the Atari ST,
|
||||||
|
the Apple IIgs ROMs, GS/OS 6.0.4 and the CFFA2 card's firmware, and Sharp's own
|
||||||
|
free release of the X68000 IPL-ROM and Human68k 3.02. Three files are not,
|
||||||
|
because they were never released for free: the Amiga Kickstart 3.1 ROM, the
|
||||||
|
X68000 CG (font) ROM, and the memory image of the original Space Taxi that its
|
||||||
|
levels are extracted from. Supply your copies once:
|
||||||
|
|
||||||
|
[source,console]
|
||||||
|
----
|
||||||
|
$ ./install.sh import ~/my-roms
|
||||||
|
----
|
||||||
|
|
||||||
|
`import` takes files, folders and `.zip` archives, identifies each file by its
|
||||||
|
checksum (the list is `installer/files.sha1`) and puts it in place.
|
||||||
|
|
||||||
|
A project that hands its setup to testers can host these files instead:
|
||||||
|
`./install.sh bundle joeylib-private.tar.gz` packs the ones present on
|
||||||
|
a machine that has them, and setting `JOEY_BUNDLE_URL` to where it is hosted,
|
||||||
|
in `installer/bundle.conf`, makes every `install` fetch it automatically.
|
||||||
|
|
||||||
=== The Example Programs
|
=== The Example Programs
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ Same loop every time, so it stays cheap to iterate:
|
||||||
`stLevel.c`, `spacetaxi.c`).
|
`stLevel.c`, `spacetaxi.c`).
|
||||||
6. **Build + ship** -- `make -f make/dos.mk EXAMPLE=spacetaxi`
|
6. **Build + ship** -- `make -f make/dos.mk EXAMPLE=spacetaxi`
|
||||||
first, then once stable also amiga / atarist / iigs.
|
first, then once stable also amiga / atarist / iigs.
|
||||||
7. **User test** -- run `scripts/run-dos.sh staxi`, report
|
7. **User test** -- run `./joey-run --port dos staxi`, report
|
||||||
behavior. Do not start the next phase before this.
|
behavior. Do not start the next phase before this.
|
||||||
|
|
||||||
## Phase 0 -- foundation (DONE)
|
## Phase 0 -- foundation (DONE)
|
||||||
|
|
|
||||||
|
|
@ -82,9 +82,9 @@ plus the inline scripts run during verification sessions.
|
||||||
## TRACE INFRASTRUCTURE
|
## TRACE INFRASTRUCTURE
|
||||||
|
|
||||||
- `stuff/spacetaxi/trace.py`: instrumented wrapper around `cpu6502.Cpu6502`. `Tracer.from_dump('raw.bin').call(addr)` runs a subroutine and snapshots memory + JSR chain.
|
- `stuff/spacetaxi/trace.py`: instrumented wrapper around `cpu6502.Cpu6502`. `Tracer.from_dump('raw.bin').call(addr)` runs a subroutine and snapshots memory + JSR chain.
|
||||||
- `stuff/spacetaxi/cpu6502.py`: minimal 6502 emulator. Sufficient for all routines tested above; throws `NotImplementedError` for unsupported opcodes (none hit so far in gameplay paths).
|
- `examples/spacetaxi/assets/cpu6502.py`: minimal 6502 emulator. Sufficient for all routines tested above; throws `NotImplementedError` for unsupported opcodes (none hit so far in gameplay paths).
|
||||||
- `stuff/spacetaxi/dumpScenes.py` + `dumpAllLevels.py`: pre-existing scene-decompress drivers.
|
- `examples/spacetaxi/assets/dumpScenes.py` + `stuff/spacetaxi/dumpAllLevels.py`: pre-existing scene-decompress drivers.
|
||||||
- `stuff/spacetaxi/romToLevel.py`: emits the `.dat` files from raw.bin via the same emulator.
|
- `examples/spacetaxi/assets/romToLevel.py`: emits the `.dat` files from raw.bin via the same emulator.
|
||||||
|
|
||||||
## METHODOLOGY (for future verification sessions)
|
## METHODOLOGY (for future verification sessions)
|
||||||
|
|
||||||
|
|
|
||||||
560
examples/spacetaxi/assets/cpu6502.py
Normal file
560
examples/spacetaxi/assets/cpu6502.py
Normal file
|
|
@ -0,0 +1,560 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# Minimal 6502 CPU emulator -- just enough to run the Space Taxi
|
||||||
|
# level decompressor at $6283 against a captured 64KB RAM dump.
|
||||||
|
#
|
||||||
|
# Scope: implements the opcodes used by $6283 / $9656 / $964A / $96A3
|
||||||
|
# and their helpers. Not a general-purpose 6502 emulator; it has no
|
||||||
|
# decimal mode, no BRK handling, no NMI/IRQ -- the routine runs with
|
||||||
|
# SEI in effect and never returns control.
|
||||||
|
#
|
||||||
|
# Flags tracked: N Z C. V (overflow) is computed for ADC/SBC since
|
||||||
|
# those instructions set it, even if no branch tests it. D (decimal)
|
||||||
|
# is ignored.
|
||||||
|
|
||||||
|
class Cpu6502:
|
||||||
|
def __init__(self, ram):
|
||||||
|
# ram is a bytearray of 65536 bytes. Caller pre-loaded the dump.
|
||||||
|
self.ram = ram
|
||||||
|
self.a = 0
|
||||||
|
self.x = 0
|
||||||
|
self.y = 0
|
||||||
|
self.sp = 0xFF
|
||||||
|
self.pc = 0
|
||||||
|
self.n = self.z = self.c = self.v = False
|
||||||
|
# Stop tracking when RTS unwinds to this depth (set by run()).
|
||||||
|
self._depth_floor = 0
|
||||||
|
|
||||||
|
# ---- memory ----
|
||||||
|
def read8(self, addr):
|
||||||
|
return self.ram[addr & 0xFFFF]
|
||||||
|
|
||||||
|
def write8(self, addr, val):
|
||||||
|
self.ram[addr & 0xFFFF] = val & 0xFF
|
||||||
|
|
||||||
|
def read16(self, addr):
|
||||||
|
return self.ram[addr & 0xFFFF] | (self.ram[(addr + 1) & 0xFFFF] << 8)
|
||||||
|
|
||||||
|
# ---- stack ----
|
||||||
|
def push8(self, val):
|
||||||
|
self.ram[0x100 + self.sp] = val & 0xFF
|
||||||
|
self.sp = (self.sp - 1) & 0xFF
|
||||||
|
|
||||||
|
def pop8(self):
|
||||||
|
self.sp = (self.sp + 1) & 0xFF
|
||||||
|
return self.ram[0x100 + self.sp]
|
||||||
|
|
||||||
|
def push16(self, val):
|
||||||
|
self.push8((val >> 8) & 0xFF)
|
||||||
|
self.push8(val & 0xFF)
|
||||||
|
|
||||||
|
def pop16(self):
|
||||||
|
lo = self.pop8()
|
||||||
|
hi = self.pop8()
|
||||||
|
return (hi << 8) | lo
|
||||||
|
|
||||||
|
# ---- flag helpers ----
|
||||||
|
def set_nz(self, val):
|
||||||
|
val &= 0xFF
|
||||||
|
self.z = (val == 0)
|
||||||
|
self.n = (val & 0x80) != 0
|
||||||
|
return val
|
||||||
|
|
||||||
|
def pack_p(self):
|
||||||
|
# B and U are usually set when pushed by PHP/BRK.
|
||||||
|
p = 0x20
|
||||||
|
if self.n: p |= 0x80
|
||||||
|
if self.v: p |= 0x40
|
||||||
|
if self.z: p |= 0x02
|
||||||
|
if self.c: p |= 0x01
|
||||||
|
return p | 0x10 # B = 1 when pushed by PHP
|
||||||
|
|
||||||
|
def unpack_p(self, p):
|
||||||
|
self.n = (p & 0x80) != 0
|
||||||
|
self.v = (p & 0x40) != 0
|
||||||
|
self.z = (p & 0x02) != 0
|
||||||
|
self.c = (p & 0x01) != 0
|
||||||
|
|
||||||
|
# ---- addressing helpers (return effective address) ----
|
||||||
|
def addr_imm(self):
|
||||||
|
a = self.pc; self.pc = (self.pc + 1) & 0xFFFF; return a
|
||||||
|
|
||||||
|
def addr_zp(self):
|
||||||
|
a = self.read8(self.pc); self.pc = (self.pc + 1) & 0xFFFF; return a
|
||||||
|
|
||||||
|
def addr_zpx(self):
|
||||||
|
a = (self.read8(self.pc) + self.x) & 0xFF; self.pc = (self.pc + 1) & 0xFFFF; return a
|
||||||
|
|
||||||
|
def addr_abs(self):
|
||||||
|
a = self.read16(self.pc); self.pc = (self.pc + 2) & 0xFFFF; return a
|
||||||
|
|
||||||
|
def addr_absx(self):
|
||||||
|
a = (self.read16(self.pc) + self.x) & 0xFFFF; self.pc = (self.pc + 2) & 0xFFFF; return a
|
||||||
|
|
||||||
|
def addr_absy(self):
|
||||||
|
a = (self.read16(self.pc) + self.y) & 0xFFFF; self.pc = (self.pc + 2) & 0xFFFF; return a
|
||||||
|
|
||||||
|
def addr_indy(self):
|
||||||
|
zp = self.read8(self.pc); self.pc = (self.pc + 1) & 0xFFFF
|
||||||
|
base = self.ram[zp] | (self.ram[(zp + 1) & 0xFF] << 8)
|
||||||
|
return (base + self.y) & 0xFFFF
|
||||||
|
|
||||||
|
# ---- branch helper ----
|
||||||
|
def branch(self, cond):
|
||||||
|
off = self.read8(self.pc); self.pc = (self.pc + 1) & 0xFFFF
|
||||||
|
if off >= 0x80: off -= 0x100
|
||||||
|
if cond:
|
||||||
|
self.pc = (self.pc + off) & 0xFFFF
|
||||||
|
|
||||||
|
# ---- step ----
|
||||||
|
def step(self):
|
||||||
|
op = self.read8(self.pc)
|
||||||
|
self.pc = (self.pc + 1) & 0xFFFF
|
||||||
|
f = _DISPATCH.get(op)
|
||||||
|
if f is None:
|
||||||
|
raise NotImplementedError(f"opcode ${op:02X} at PC ${(self.pc-1)&0xFFFF:04X}")
|
||||||
|
f(self)
|
||||||
|
|
||||||
|
def call(self, target, max_steps=10_000_000):
|
||||||
|
"""Call subroutine at `target` as if via JSR. Returns when the
|
||||||
|
matching RTS pops the stack back below the original SP. Hard
|
||||||
|
cap on steps to avoid runaway."""
|
||||||
|
# Push a sentinel return address. When RTS pops to this, stop.
|
||||||
|
# Use $FFFF as sentinel: subsequent step would attempt to fetch
|
||||||
|
# from $FFFF; we detect via depth floor instead.
|
||||||
|
original_sp = self.sp
|
||||||
|
self.push16(0xFFFE) # RTS will pop this, then step will land at $FFFF
|
||||||
|
self.pc = target & 0xFFFF
|
||||||
|
for _ in range(max_steps):
|
||||||
|
if self.pc == 0xFFFF:
|
||||||
|
return
|
||||||
|
self.step()
|
||||||
|
raise RuntimeError(f"6502 call exceeded {max_steps} steps; PC=${self.pc:04X}")
|
||||||
|
|
||||||
|
|
||||||
|
# ---- opcode implementations ----
|
||||||
|
# Each takes the cpu and reads remaining operand bytes via cpu.addr_*.
|
||||||
|
|
||||||
|
def _NOP(cpu): pass
|
||||||
|
def _SEI(cpu): pass # we don't model interrupts
|
||||||
|
def _CLD(cpu): pass # no decimal mode
|
||||||
|
def _SEC(cpu): cpu.c = True
|
||||||
|
def _CLC(cpu): cpu.c = False
|
||||||
|
def _PHP(cpu): cpu.push8(cpu.pack_p())
|
||||||
|
def _PLP(cpu): cpu.unpack_p(cpu.pop8())
|
||||||
|
def _PHA(cpu): cpu.push8(cpu.a)
|
||||||
|
def _PLA(cpu): cpu.a = cpu.set_nz(cpu.pop8())
|
||||||
|
def _TXA(cpu): cpu.a = cpu.set_nz(cpu.x)
|
||||||
|
def _TAX(cpu): cpu.x = cpu.set_nz(cpu.a)
|
||||||
|
def _TYA(cpu): cpu.a = cpu.set_nz(cpu.y)
|
||||||
|
def _TAY(cpu): cpu.y = cpu.set_nz(cpu.a)
|
||||||
|
def _INX(cpu): cpu.x = cpu.set_nz((cpu.x + 1) & 0xFF)
|
||||||
|
def _INY(cpu): cpu.y = cpu.set_nz((cpu.y + 1) & 0xFF)
|
||||||
|
def _DEX(cpu): cpu.x = cpu.set_nz((cpu.x - 1) & 0xFF)
|
||||||
|
def _DEY(cpu): cpu.y = cpu.set_nz((cpu.y - 1) & 0xFF)
|
||||||
|
|
||||||
|
def _LDA_imm(cpu): cpu.a = cpu.set_nz(cpu.read8(cpu.addr_imm()))
|
||||||
|
def _LDA_zp(cpu): cpu.a = cpu.set_nz(cpu.read8(cpu.addr_zp()))
|
||||||
|
def _LDA_zpx(cpu): cpu.a = cpu.set_nz(cpu.read8(cpu.addr_zpx()))
|
||||||
|
def _LDA_abs(cpu): cpu.a = cpu.set_nz(cpu.read8(cpu.addr_abs()))
|
||||||
|
def _LDA_absx(cpu): cpu.a = cpu.set_nz(cpu.read8(cpu.addr_absx()))
|
||||||
|
def _LDA_absy(cpu): cpu.a = cpu.set_nz(cpu.read8(cpu.addr_absy()))
|
||||||
|
def _LDA_indy(cpu): cpu.a = cpu.set_nz(cpu.read8(cpu.addr_indy()))
|
||||||
|
|
||||||
|
def _LDX_imm(cpu): cpu.x = cpu.set_nz(cpu.read8(cpu.addr_imm()))
|
||||||
|
def _LDX_abs(cpu): cpu.x = cpu.set_nz(cpu.read8(cpu.addr_abs()))
|
||||||
|
def _LDX_absy(cpu): cpu.x = cpu.set_nz(cpu.read8(cpu.addr_absy()))
|
||||||
|
def _LDX_zp(cpu): cpu.x = cpu.set_nz(cpu.read8(cpu.addr_zp()))
|
||||||
|
|
||||||
|
def _LDY_imm(cpu): cpu.y = cpu.set_nz(cpu.read8(cpu.addr_imm()))
|
||||||
|
def _LDY_abs(cpu): cpu.y = cpu.set_nz(cpu.read8(cpu.addr_abs()))
|
||||||
|
def _LDY_absx(cpu): cpu.y = cpu.set_nz(cpu.read8(cpu.addr_absx()))
|
||||||
|
def _LDY_zp(cpu): cpu.y = cpu.set_nz(cpu.read8(cpu.addr_zp()))
|
||||||
|
|
||||||
|
def _STA_zp(cpu): cpu.write8(cpu.addr_zp(), cpu.a)
|
||||||
|
def _STA_zpx(cpu): cpu.write8(cpu.addr_zpx(), cpu.a)
|
||||||
|
def _STA_abs(cpu): cpu.write8(cpu.addr_abs(), cpu.a)
|
||||||
|
def _STA_absx(cpu): cpu.write8(cpu.addr_absx(), cpu.a)
|
||||||
|
def _STA_absy(cpu): cpu.write8(cpu.addr_absy(), cpu.a)
|
||||||
|
def _STA_indy(cpu): cpu.write8(cpu.addr_indy(), cpu.a)
|
||||||
|
|
||||||
|
def _STX_zp(cpu): cpu.write8(cpu.addr_zp(), cpu.x)
|
||||||
|
def _STX_abs(cpu): cpu.write8(cpu.addr_abs(), cpu.x)
|
||||||
|
|
||||||
|
def _STY_zp(cpu): cpu.write8(cpu.addr_zp(), cpu.y)
|
||||||
|
def _STY_abs(cpu): cpu.write8(cpu.addr_abs(), cpu.y)
|
||||||
|
|
||||||
|
def _AND_imm(cpu): cpu.a = cpu.set_nz(cpu.a & cpu.read8(cpu.addr_imm()))
|
||||||
|
def _AND_zp(cpu): cpu.a = cpu.set_nz(cpu.a & cpu.read8(cpu.addr_zp()))
|
||||||
|
def _AND_abs(cpu): cpu.a = cpu.set_nz(cpu.a & cpu.read8(cpu.addr_abs()))
|
||||||
|
def _ORA_imm(cpu): cpu.a = cpu.set_nz(cpu.a | cpu.read8(cpu.addr_imm()))
|
||||||
|
def _ORA_zp(cpu): cpu.a = cpu.set_nz(cpu.a | cpu.read8(cpu.addr_zp()))
|
||||||
|
def _EOR_imm(cpu): cpu.a = cpu.set_nz(cpu.a ^ cpu.read8(cpu.addr_imm()))
|
||||||
|
def _EOR_abs(cpu): cpu.a = cpu.set_nz(cpu.a ^ cpu.read8(cpu.addr_abs()))
|
||||||
|
|
||||||
|
def _adc(cpu, m):
|
||||||
|
r = cpu.a + m + (1 if cpu.c else 0)
|
||||||
|
cpu.c = r > 0xFF
|
||||||
|
cpu.v = ((cpu.a ^ r) & (m ^ r) & 0x80) != 0
|
||||||
|
cpu.a = cpu.set_nz(r & 0xFF)
|
||||||
|
|
||||||
|
def _ADC_imm(cpu): _adc(cpu, cpu.read8(cpu.addr_imm()))
|
||||||
|
def _ADC_zp(cpu): _adc(cpu, cpu.read8(cpu.addr_zp()))
|
||||||
|
def _ADC_abs(cpu): _adc(cpu, cpu.read8(cpu.addr_abs()))
|
||||||
|
def _ADC_absy(cpu): _adc(cpu, cpu.read8(cpu.addr_absy()))
|
||||||
|
|
||||||
|
def _sbc(cpu, m):
|
||||||
|
_adc(cpu, m ^ 0xFF)
|
||||||
|
|
||||||
|
def _SBC_imm(cpu): _sbc(cpu, cpu.read8(cpu.addr_imm()))
|
||||||
|
def _SBC_zp(cpu): _sbc(cpu, cpu.read8(cpu.addr_zp()))
|
||||||
|
def _SBC_abs(cpu): _sbc(cpu, cpu.read8(cpu.addr_abs()))
|
||||||
|
def _SBC_absx(cpu): _sbc(cpu, cpu.read8(cpu.addr_absx()))
|
||||||
|
def _SBC_absy(cpu): _sbc(cpu, cpu.read8(cpu.addr_absy()))
|
||||||
|
|
||||||
|
def _AND_absx(cpu): cpu.a = cpu.set_nz(cpu.a & cpu.read8(cpu.addr_absx()))
|
||||||
|
def _AND_absy(cpu): cpu.a = cpu.set_nz(cpu.a & cpu.read8(cpu.addr_absy()))
|
||||||
|
def _AND_indy(cpu): cpu.a = cpu.set_nz(cpu.a & cpu.read8(cpu.addr_indy()))
|
||||||
|
def _ORA_absx(cpu): cpu.a = cpu.set_nz(cpu.a | cpu.read8(cpu.addr_absx()))
|
||||||
|
def _ORA_abs(cpu): cpu.a = cpu.set_nz(cpu.a | cpu.read8(cpu.addr_abs()))
|
||||||
|
def _ORA_zpx(cpu): cpu.a = cpu.set_nz(cpu.a | cpu.read8(cpu.addr_zpx()))
|
||||||
|
def _ORA_indy(cpu): cpu.a = cpu.set_nz(cpu.a | cpu.read8(cpu.addr_indy()))
|
||||||
|
def _EOR_zp(cpu): cpu.a = cpu.set_nz(cpu.a ^ cpu.read8(cpu.addr_zp()))
|
||||||
|
def _EOR_zpx(cpu): cpu.a = cpu.set_nz(cpu.a ^ cpu.read8(cpu.addr_zpx()))
|
||||||
|
def _EOR_absx(cpu): cpu.a = cpu.set_nz(cpu.a ^ cpu.read8(cpu.addr_absx()))
|
||||||
|
def _EOR_absy(cpu): cpu.a = cpu.set_nz(cpu.a ^ cpu.read8(cpu.addr_absy()))
|
||||||
|
def _ADC_zpx(cpu): _adc(cpu, cpu.read8(cpu.addr_zpx()))
|
||||||
|
def _ADC_absx(cpu): _adc(cpu, cpu.read8(cpu.addr_absx()))
|
||||||
|
def _CMP_absx(cpu): _cmp_with(cpu, cpu.a, cpu.read8(cpu.addr_absx()))
|
||||||
|
def _CMP_absy(cpu): _cmp_with(cpu, cpu.a, cpu.read8(cpu.addr_absy()))
|
||||||
|
def _CMP_zpx(cpu): _cmp_with(cpu, cpu.a, cpu.read8(cpu.addr_zpx()))
|
||||||
|
|
||||||
|
def _JMP_ind(cpu):
|
||||||
|
addr = cpu.addr_abs()
|
||||||
|
cpu.pc = cpu.read8(addr) | (cpu.read8((addr & 0xFF00) | ((addr + 1) & 0xFF)) << 8)
|
||||||
|
|
||||||
|
def _BIT_zp(cpu):
|
||||||
|
v = cpu.read8(cpu.addr_zp())
|
||||||
|
cpu.z = (cpu.a & v) == 0
|
||||||
|
cpu.n = (v & 0x80) != 0
|
||||||
|
cpu.v = (v & 0x40) != 0
|
||||||
|
|
||||||
|
def _BIT_abs(cpu):
|
||||||
|
v = cpu.read8(cpu.addr_abs())
|
||||||
|
cpu.z = (cpu.a & v) == 0
|
||||||
|
cpu.n = (v & 0x80) != 0
|
||||||
|
cpu.v = (v & 0x40) != 0
|
||||||
|
|
||||||
|
def _ASL_zp(cpu):
|
||||||
|
addr = cpu.addr_zp()
|
||||||
|
v = cpu.read8(addr)
|
||||||
|
cpu.c = (v & 0x80) != 0
|
||||||
|
cpu.write8(addr, cpu.set_nz((v << 1) & 0xFF))
|
||||||
|
|
||||||
|
def _ASL_abs(cpu):
|
||||||
|
addr = cpu.addr_abs()
|
||||||
|
v = cpu.read8(addr)
|
||||||
|
cpu.c = (v & 0x80) != 0
|
||||||
|
cpu.write8(addr, cpu.set_nz((v << 1) & 0xFF))
|
||||||
|
|
||||||
|
def _LSR_abs(cpu):
|
||||||
|
addr = cpu.addr_abs()
|
||||||
|
v = cpu.read8(addr)
|
||||||
|
cpu.c = (v & 0x01) != 0
|
||||||
|
cpu.write8(addr, cpu.set_nz(v >> 1))
|
||||||
|
|
||||||
|
def _ROL_abs(cpu):
|
||||||
|
addr = cpu.addr_abs()
|
||||||
|
v = cpu.read8(addr)
|
||||||
|
new_c = (v & 0x80) != 0
|
||||||
|
v = ((v << 1) | (1 if cpu.c else 0)) & 0xFF
|
||||||
|
cpu.write8(addr, cpu.set_nz(v))
|
||||||
|
cpu.c = new_c
|
||||||
|
|
||||||
|
def _ROR_abs(cpu):
|
||||||
|
addr = cpu.addr_abs()
|
||||||
|
v = cpu.read8(addr)
|
||||||
|
new_c = (v & 0x01) != 0
|
||||||
|
v = ((v >> 1) | (0x80 if cpu.c else 0)) & 0xFF
|
||||||
|
cpu.write8(addr, cpu.set_nz(v))
|
||||||
|
cpu.c = new_c
|
||||||
|
|
||||||
|
def _ROR_zp(cpu):
|
||||||
|
addr = cpu.addr_zp()
|
||||||
|
v = cpu.read8(addr)
|
||||||
|
new_c = (v & 0x01) != 0
|
||||||
|
v = ((v >> 1) | (0x80 if cpu.c else 0)) & 0xFF
|
||||||
|
cpu.write8(addr, cpu.set_nz(v))
|
||||||
|
cpu.c = new_c
|
||||||
|
|
||||||
|
def _STA_zpx(cpu_): cpu_.write8(cpu_.addr_zpx(), cpu_.a)
|
||||||
|
def _TSX(cpu): cpu.x = cpu.set_nz(cpu.sp)
|
||||||
|
def _TXS(cpu): cpu.sp = cpu.x
|
||||||
|
def _CLI(cpu): pass
|
||||||
|
def _CLV(cpu): cpu.v = False
|
||||||
|
def _SED(cpu): pass # decimal mode ignored
|
||||||
|
def _STX_zpy(cpu):
|
||||||
|
a = (cpu.read8(cpu.pc) + cpu.y) & 0xFF; cpu.pc = (cpu.pc + 1) & 0xFFFF
|
||||||
|
cpu.write8(a, cpu.x)
|
||||||
|
def _STY_zpx(cpu): cpu.write8(cpu.addr_zpx(), cpu.y)
|
||||||
|
def _LDX_zpy(cpu):
|
||||||
|
a = (cpu.read8(cpu.pc) + cpu.y) & 0xFF; cpu.pc = (cpu.pc + 1) & 0xFFFF
|
||||||
|
cpu.x = cpu.set_nz(cpu.read8(a))
|
||||||
|
def _LDY_zpx(cpu): cpu.y = cpu.set_nz(cpu.read8(cpu.addr_zpx()))
|
||||||
|
|
||||||
|
def _cmp_with(cpu, lhs, m):
|
||||||
|
r = (lhs - m) & 0x1FF
|
||||||
|
cpu.c = lhs >= m
|
||||||
|
cpu.set_nz(r & 0xFF)
|
||||||
|
|
||||||
|
def _CMP_imm(cpu): _cmp_with(cpu, cpu.a, cpu.read8(cpu.addr_imm()))
|
||||||
|
def _CMP_zp(cpu): _cmp_with(cpu, cpu.a, cpu.read8(cpu.addr_zp()))
|
||||||
|
def _CMP_abs(cpu): _cmp_with(cpu, cpu.a, cpu.read8(cpu.addr_abs()))
|
||||||
|
def _CPX_imm(cpu): _cmp_with(cpu, cpu.x, cpu.read8(cpu.addr_imm()))
|
||||||
|
def _CPX_abs(cpu): _cmp_with(cpu, cpu.x, cpu.read8(cpu.addr_abs()))
|
||||||
|
def _CPY_imm(cpu): _cmp_with(cpu, cpu.y, cpu.read8(cpu.addr_imm()))
|
||||||
|
|
||||||
|
def _ASL_a(cpu):
|
||||||
|
cpu.c = (cpu.a & 0x80) != 0
|
||||||
|
cpu.a = cpu.set_nz((cpu.a << 1) & 0xFF)
|
||||||
|
|
||||||
|
def _LSR_a(cpu):
|
||||||
|
cpu.c = (cpu.a & 0x01) != 0
|
||||||
|
cpu.a = cpu.set_nz(cpu.a >> 1)
|
||||||
|
|
||||||
|
def _LSR_zp(cpu):
|
||||||
|
addr = cpu.addr_zp()
|
||||||
|
v = cpu.read8(addr)
|
||||||
|
cpu.c = (v & 0x01) != 0
|
||||||
|
cpu.write8(addr, cpu.set_nz(v >> 1))
|
||||||
|
|
||||||
|
def _ROL_a(cpu):
|
||||||
|
new_c = (cpu.a & 0x80) != 0
|
||||||
|
cpu.a = cpu.set_nz(((cpu.a << 1) | (1 if cpu.c else 0)) & 0xFF)
|
||||||
|
cpu.c = new_c
|
||||||
|
|
||||||
|
def _ROL_zp(cpu):
|
||||||
|
addr = cpu.addr_zp()
|
||||||
|
v = cpu.read8(addr)
|
||||||
|
new_c = (v & 0x80) != 0
|
||||||
|
v = ((v << 1) | (1 if cpu.c else 0)) & 0xFF
|
||||||
|
cpu.write8(addr, cpu.set_nz(v))
|
||||||
|
cpu.c = new_c
|
||||||
|
|
||||||
|
def _ROR_a(cpu):
|
||||||
|
new_c = (cpu.a & 0x01) != 0
|
||||||
|
cpu.a = cpu.set_nz(((cpu.a >> 1) | (0x80 if cpu.c else 0)) & 0xFF)
|
||||||
|
cpu.c = new_c
|
||||||
|
|
||||||
|
def _INC_abs(cpu):
|
||||||
|
addr = cpu.addr_abs()
|
||||||
|
cpu.write8(addr, cpu.set_nz((cpu.read8(addr) + 1) & 0xFF))
|
||||||
|
|
||||||
|
def _INC_absx(cpu):
|
||||||
|
addr = cpu.addr_absx()
|
||||||
|
cpu.write8(addr, cpu.set_nz((cpu.read8(addr) + 1) & 0xFF))
|
||||||
|
|
||||||
|
def _INC_zp(cpu):
|
||||||
|
addr = cpu.addr_zp()
|
||||||
|
cpu.write8(addr, cpu.set_nz((cpu.read8(addr) + 1) & 0xFF))
|
||||||
|
|
||||||
|
def _INC_zpx(cpu):
|
||||||
|
addr = cpu.addr_zpx()
|
||||||
|
cpu.write8(addr, cpu.set_nz((cpu.read8(addr) + 1) & 0xFF))
|
||||||
|
|
||||||
|
def _DEC_abs(cpu):
|
||||||
|
addr = cpu.addr_abs()
|
||||||
|
cpu.write8(addr, cpu.set_nz((cpu.read8(addr) - 1) & 0xFF))
|
||||||
|
|
||||||
|
def _DEC_absx(cpu):
|
||||||
|
addr = cpu.addr_absx()
|
||||||
|
cpu.write8(addr, cpu.set_nz((cpu.read8(addr) - 1) & 0xFF))
|
||||||
|
|
||||||
|
def _DEC_zp(cpu):
|
||||||
|
addr = cpu.addr_zp()
|
||||||
|
cpu.write8(addr, cpu.set_nz((cpu.read8(addr) - 1) & 0xFF))
|
||||||
|
|
||||||
|
def _DEC_zpx(cpu):
|
||||||
|
addr = cpu.addr_zpx()
|
||||||
|
cpu.write8(addr, cpu.set_nz((cpu.read8(addr) - 1) & 0xFF))
|
||||||
|
|
||||||
|
def _ASL_zpx(cpu):
|
||||||
|
addr = cpu.addr_zpx()
|
||||||
|
v = cpu.read8(addr)
|
||||||
|
cpu.c = (v & 0x80) != 0
|
||||||
|
cpu.write8(addr, cpu.set_nz((v << 1) & 0xFF))
|
||||||
|
|
||||||
|
def _ASL_absx(cpu):
|
||||||
|
addr = cpu.addr_absx()
|
||||||
|
v = cpu.read8(addr)
|
||||||
|
cpu.c = (v & 0x80) != 0
|
||||||
|
cpu.write8(addr, cpu.set_nz((v << 1) & 0xFF))
|
||||||
|
|
||||||
|
def _LSR_zpx(cpu):
|
||||||
|
addr = cpu.addr_zpx()
|
||||||
|
v = cpu.read8(addr)
|
||||||
|
cpu.c = (v & 0x01) != 0
|
||||||
|
cpu.write8(addr, cpu.set_nz(v >> 1))
|
||||||
|
|
||||||
|
def _LSR_absx(cpu):
|
||||||
|
addr = cpu.addr_absx()
|
||||||
|
v = cpu.read8(addr)
|
||||||
|
cpu.c = (v & 0x01) != 0
|
||||||
|
cpu.write8(addr, cpu.set_nz(v >> 1))
|
||||||
|
|
||||||
|
def _ROL_zpx(cpu):
|
||||||
|
addr = cpu.addr_zpx()
|
||||||
|
v = cpu.read8(addr)
|
||||||
|
new_c = (v & 0x80) != 0
|
||||||
|
v = ((v << 1) | (1 if cpu.c else 0)) & 0xFF
|
||||||
|
cpu.write8(addr, cpu.set_nz(v))
|
||||||
|
cpu.c = new_c
|
||||||
|
|
||||||
|
def _ROL_absx(cpu):
|
||||||
|
addr = cpu.addr_absx()
|
||||||
|
v = cpu.read8(addr)
|
||||||
|
new_c = (v & 0x80) != 0
|
||||||
|
v = ((v << 1) | (1 if cpu.c else 0)) & 0xFF
|
||||||
|
cpu.write8(addr, cpu.set_nz(v))
|
||||||
|
cpu.c = new_c
|
||||||
|
|
||||||
|
def _ROR_zpx(cpu):
|
||||||
|
addr = cpu.addr_zpx()
|
||||||
|
v = cpu.read8(addr)
|
||||||
|
new_c = (v & 0x01) != 0
|
||||||
|
v = ((v >> 1) | (0x80 if cpu.c else 0)) & 0xFF
|
||||||
|
cpu.write8(addr, cpu.set_nz(v))
|
||||||
|
cpu.c = new_c
|
||||||
|
|
||||||
|
def _ROR_absx(cpu):
|
||||||
|
addr = cpu.addr_absx()
|
||||||
|
v = cpu.read8(addr)
|
||||||
|
new_c = (v & 0x01) != 0
|
||||||
|
v = ((v >> 1) | (0x80 if cpu.c else 0)) & 0xFF
|
||||||
|
cpu.write8(addr, cpu.set_nz(v))
|
||||||
|
cpu.c = new_c
|
||||||
|
|
||||||
|
def _LDA_indx(cpu):
|
||||||
|
zp = cpu.read8(cpu.pc); cpu.pc = (cpu.pc + 1) & 0xFFFF
|
||||||
|
addr_ptr = (zp + cpu.x) & 0xFF
|
||||||
|
addr = cpu.ram[addr_ptr] | (cpu.ram[(addr_ptr + 1) & 0xFF] << 8)
|
||||||
|
cpu.a = cpu.set_nz(cpu.read8(addr))
|
||||||
|
|
||||||
|
def _STA_indx(cpu):
|
||||||
|
zp = cpu.read8(cpu.pc); cpu.pc = (cpu.pc + 1) & 0xFFFF
|
||||||
|
addr_ptr = (zp + cpu.x) & 0xFF
|
||||||
|
addr = cpu.ram[addr_ptr] | (cpu.ram[(addr_ptr + 1) & 0xFF] << 8)
|
||||||
|
cpu.write8(addr, cpu.a)
|
||||||
|
|
||||||
|
def _AND_indx(cpu):
|
||||||
|
zp = cpu.read8(cpu.pc); cpu.pc = (cpu.pc + 1) & 0xFFFF
|
||||||
|
addr_ptr = (zp + cpu.x) & 0xFF
|
||||||
|
addr = cpu.ram[addr_ptr] | (cpu.ram[(addr_ptr + 1) & 0xFF] << 8)
|
||||||
|
cpu.a = cpu.set_nz(cpu.a & cpu.read8(addr))
|
||||||
|
|
||||||
|
def _ORA_indx(cpu):
|
||||||
|
zp = cpu.read8(cpu.pc); cpu.pc = (cpu.pc + 1) & 0xFFFF
|
||||||
|
addr_ptr = (zp + cpu.x) & 0xFF
|
||||||
|
addr = cpu.ram[addr_ptr] | (cpu.ram[(addr_ptr + 1) & 0xFF] << 8)
|
||||||
|
cpu.a = cpu.set_nz(cpu.a | cpu.read8(addr))
|
||||||
|
|
||||||
|
def _EOR_indx(cpu):
|
||||||
|
zp = cpu.read8(cpu.pc); cpu.pc = (cpu.pc + 1) & 0xFFFF
|
||||||
|
addr_ptr = (zp + cpu.x) & 0xFF
|
||||||
|
addr = cpu.ram[addr_ptr] | (cpu.ram[(addr_ptr + 1) & 0xFF] << 8)
|
||||||
|
cpu.a = cpu.set_nz(cpu.a ^ cpu.read8(addr))
|
||||||
|
|
||||||
|
def _ADC_indx(cpu):
|
||||||
|
zp = cpu.read8(cpu.pc); cpu.pc = (cpu.pc + 1) & 0xFFFF
|
||||||
|
addr_ptr = (zp + cpu.x) & 0xFF
|
||||||
|
addr = cpu.ram[addr_ptr] | (cpu.ram[(addr_ptr + 1) & 0xFF] << 8)
|
||||||
|
_adc(cpu, cpu.read8(addr))
|
||||||
|
|
||||||
|
def _SBC_indx(cpu):
|
||||||
|
zp = cpu.read8(cpu.pc); cpu.pc = (cpu.pc + 1) & 0xFFFF
|
||||||
|
addr_ptr = (zp + cpu.x) & 0xFF
|
||||||
|
addr = cpu.ram[addr_ptr] | (cpu.ram[(addr_ptr + 1) & 0xFF] << 8)
|
||||||
|
_sbc(cpu, cpu.read8(addr))
|
||||||
|
|
||||||
|
def _CMP_indx(cpu):
|
||||||
|
zp = cpu.read8(cpu.pc); cpu.pc = (cpu.pc + 1) & 0xFFFF
|
||||||
|
addr_ptr = (zp + cpu.x) & 0xFF
|
||||||
|
addr = cpu.ram[addr_ptr] | (cpu.ram[(addr_ptr + 1) & 0xFF] << 8)
|
||||||
|
_cmp_with(cpu, cpu.a, cpu.read8(addr))
|
||||||
|
|
||||||
|
def _CPX_zp(cpu):
|
||||||
|
_cmp_with(cpu, cpu.x, cpu.read8(cpu.addr_zp()))
|
||||||
|
|
||||||
|
def _CPY_zp(cpu):
|
||||||
|
_cmp_with(cpu, cpu.y, cpu.read8(cpu.addr_zp()))
|
||||||
|
|
||||||
|
def _BPL(cpu): cpu.branch(not cpu.n)
|
||||||
|
def _BMI(cpu): cpu.branch(cpu.n)
|
||||||
|
def _BNE(cpu): cpu.branch(not cpu.z)
|
||||||
|
def _BEQ(cpu): cpu.branch(cpu.z)
|
||||||
|
def _BCC(cpu): cpu.branch(not cpu.c)
|
||||||
|
def _BCS(cpu): cpu.branch(cpu.c)
|
||||||
|
def _BVC(cpu): cpu.branch(not cpu.v)
|
||||||
|
def _BVS(cpu): cpu.branch(cpu.v)
|
||||||
|
|
||||||
|
def _JMP_abs(cpu): cpu.pc = cpu.addr_abs()
|
||||||
|
def _JSR(cpu):
|
||||||
|
target = cpu.addr_abs()
|
||||||
|
cpu.push16((cpu.pc - 1) & 0xFFFF)
|
||||||
|
cpu.pc = target
|
||||||
|
def _RTS(cpu):
|
||||||
|
cpu.pc = (cpu.pop16() + 1) & 0xFFFF
|
||||||
|
|
||||||
|
|
||||||
|
# Build dispatch table
|
||||||
|
_DISPATCH = {
|
||||||
|
0x00: _NOP, # treat BRK as NOP (we never expect it)
|
||||||
|
0x18: _CLC, 0x38: _SEC, 0x58: _NOP, 0x78: _SEI, 0xD8: _CLD,
|
||||||
|
0x08: _PHP, 0x28: _PLP, 0x48: _PHA, 0x68: _PLA,
|
||||||
|
0x8A: _TXA, 0xAA: _TAX, 0x98: _TYA, 0xA8: _TAY,
|
||||||
|
0xE8: _INX, 0xC8: _INY, 0xCA: _DEX, 0x88: _DEY,
|
||||||
|
0xA9: _LDA_imm, 0xA5: _LDA_zp, 0xB5: _LDA_zpx, 0xAD: _LDA_abs,
|
||||||
|
0xBD: _LDA_absx, 0xB9: _LDA_absy, 0xB1: _LDA_indy,
|
||||||
|
0xA2: _LDX_imm, 0xAE: _LDX_abs, 0xBE: _LDX_absy, 0xA6: _LDX_zp,
|
||||||
|
0xA0: _LDY_imm, 0xAC: _LDY_abs, 0xBC: _LDY_absx, 0xA4: _LDY_zp,
|
||||||
|
0x85: _STA_zp, 0x95: _STA_zpx, 0x8D: _STA_abs,
|
||||||
|
0x9D: _STA_absx, 0x99: _STA_absy, 0x91: _STA_indy,
|
||||||
|
0x86: _STX_zp, 0x8E: _STX_abs,
|
||||||
|
0x84: _STY_zp, 0x8C: _STY_abs,
|
||||||
|
0x29: _AND_imm, 0x25: _AND_zp, 0x2D: _AND_abs,
|
||||||
|
0x3D: _AND_absx, 0x39: _AND_absy, 0x31: _AND_indy,
|
||||||
|
0x09: _ORA_imm, 0x05: _ORA_zp, 0x0D: _ORA_abs, 0x15: _ORA_zpx,
|
||||||
|
0x1D: _ORA_absx, 0x11: _ORA_indy,
|
||||||
|
0x49: _EOR_imm, 0x4D: _EOR_abs, 0x45: _EOR_zp, 0x55: _EOR_zpx,
|
||||||
|
0x5D: _EOR_absx, 0x59: _EOR_absy,
|
||||||
|
0x69: _ADC_imm, 0x65: _ADC_zp, 0x6D: _ADC_abs, 0x79: _ADC_absy,
|
||||||
|
0x75: _ADC_zpx, 0x7D: _ADC_absx,
|
||||||
|
0xE9: _SBC_imm, 0xE5: _SBC_zp, 0xED: _SBC_abs, 0xFD: _SBC_absx, 0xF9: _SBC_absy,
|
||||||
|
0xC9: _CMP_imm, 0xC5: _CMP_zp, 0xCD: _CMP_abs, 0xD5: _CMP_zpx,
|
||||||
|
0xDD: _CMP_absx, 0xD9: _CMP_absy,
|
||||||
|
0xE0: _CPX_imm, 0xEC: _CPX_abs,
|
||||||
|
0xC0: _CPY_imm,
|
||||||
|
0x24: _BIT_zp, 0x2C: _BIT_abs,
|
||||||
|
0x0A: _ASL_a, 0x06: _ASL_zp, 0x0E: _ASL_abs,
|
||||||
|
0x4A: _LSR_a, 0x46: _LSR_zp, 0x4E: _LSR_abs,
|
||||||
|
0x2A: _ROL_a, 0x26: _ROL_zp, 0x2E: _ROL_abs,
|
||||||
|
0x6A: _ROR_a, 0x66: _ROR_zp, 0x6E: _ROR_abs,
|
||||||
|
0xEE: _INC_abs, 0xFE: _INC_absx, 0xE6: _INC_zp, 0xF6: _INC_zpx,
|
||||||
|
0xCE: _DEC_abs, 0xDE: _DEC_absx, 0xC6: _DEC_zp, 0xD6: _DEC_zpx,
|
||||||
|
0x16: _ASL_zpx, 0x1E: _ASL_absx,
|
||||||
|
0x56: _LSR_zpx, 0x5E: _LSR_absx,
|
||||||
|
0x36: _ROL_zpx, 0x3E: _ROL_absx,
|
||||||
|
0x76: _ROR_zpx, 0x7E: _ROR_absx,
|
||||||
|
0xA1: _LDA_indx, 0x81: _STA_indx, 0x21: _AND_indx,
|
||||||
|
0x01: _ORA_indx, 0x41: _EOR_indx,
|
||||||
|
0x61: _ADC_indx, 0xE1: _SBC_indx, 0xC1: _CMP_indx,
|
||||||
|
0xE4: _CPX_zp, 0xC4: _CPY_zp,
|
||||||
|
0x6C: _JMP_ind,
|
||||||
|
0x9A: _TXS, 0xBA: _TSX,
|
||||||
|
0x58: _CLI, 0xB8: _CLV, 0xF8: _SED,
|
||||||
|
0x96: _STX_zpy, 0x94: _STY_zpx,
|
||||||
|
0xB6: _LDX_zpy, 0xB4: _LDY_zpx,
|
||||||
|
0x10: _BPL, 0x30: _BMI, 0xD0: _BNE, 0xF0: _BEQ,
|
||||||
|
0x90: _BCC, 0xB0: _BCS, 0x50: _BVC, 0x70: _BVS,
|
||||||
|
0x4C: _JMP_abs, 0x20: _JSR, 0x60: _RTS,
|
||||||
|
0xEA: _NOP,
|
||||||
|
}
|
||||||
|
|
@ -8,7 +8,7 @@ no good either -- it captures the banked-IN view, so $A000-$BFFF comes
|
||||||
back as BASIC ROM and 13 of the 15 utterances are missing. Every
|
back as BASIC ROM and 13 of the 15 utterances are missing. Every
|
||||||
mem0000*.bin in this tree has that problem.
|
mem0000*.bin in this tree has that problem.
|
||||||
|
|
||||||
The way out needs no emulator install: stuff/spacetaxi/cpu6502.py is a
|
The way out needs no emulator install: cpu6502.py (next to this script) is a
|
||||||
plain 6502 core over a flat 64K bytearray with no banking and no I/O,
|
plain 6502 core over a flat 64K bytearray with no banking and no I/O,
|
||||||
which is all a depacker needs. Load the .prg, run from the BASIC SYS
|
which is all a depacker needs. Load the .prg, run from the BASIC SYS
|
||||||
entry, and the depacked image IS the RAM -- $A000-$BFFF included.
|
entry, and the depacked image IS the RAM -- $A000-$BFFF included.
|
||||||
|
|
@ -41,11 +41,8 @@ def main():
|
||||||
prgPath = sys.argv[1] if len(sys.argv) > 1 else os.path.join(STUFF, "extract", "SPACE TAXI+.prg")
|
prgPath = sys.argv[1] if len(sys.argv) > 1 else os.path.join(STUFF, "extract", "SPACE TAXI+.prg")
|
||||||
outPath = sys.argv[2] if len(sys.argv) > 2 else os.path.join(STUFF, "staxi-ram.bin")
|
outPath = sys.argv[2] if len(sys.argv) > 2 else os.path.join(STUFF, "staxi-ram.bin")
|
||||||
|
|
||||||
sys.path.insert(0, STUFF)
|
sys.path.insert(0, HERE)
|
||||||
try:
|
from cpu6502 import Cpu6502
|
||||||
from cpu6502 import Cpu6502
|
|
||||||
except ImportError:
|
|
||||||
sys.exit("depackSpeechRam: need cpu6502.py in %s" % STUFF)
|
|
||||||
|
|
||||||
prg = open(prgPath, "rb").read()
|
prg = open(prgPath, "rb").read()
|
||||||
load = prg[0] | (prg[1] << 8)
|
load = prg[0] | (prg[1] << 8)
|
||||||
|
|
|
||||||
115
examples/spacetaxi/assets/dumpScenes.py
Normal file
115
examples/spacetaxi/assets/dumpScenes.py
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# Drive the captured Space Taxi RAM through the 6502 emulator to
|
||||||
|
# decompress every scene the game knows how to load.
|
||||||
|
#
|
||||||
|
# Approach:
|
||||||
|
# 1. Load a VICE memory dump as flat 64KB RAM. The dump captured at
|
||||||
|
# runtime contains the decompressor at $9656, the scene pointer
|
||||||
|
# table at $9600, and (we hope) the compressed data the pointers
|
||||||
|
# reach into.
|
||||||
|
# 2. Sanity check: run scene ID 1 (the captured live level) through
|
||||||
|
# $6283 and compare the resulting screen buffer at $7530-$7929
|
||||||
|
# with the live screen RAM at $0400-$07E7 already in the dump.
|
||||||
|
# If they match, the emulator + dump together can reproduce any
|
||||||
|
# scene. If not, we know something's missing (likely hidden RAM
|
||||||
|
# under ROMs that VICE didn't capture).
|
||||||
|
# 3. If sanity passes: iterate scene IDs 0..23 (lettered levels)
|
||||||
|
# plus known special IDs (title=$24, level-complete=$19) and
|
||||||
|
# dump each scene's screen + color buffer.
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
from cpu6502 import Cpu6502
|
||||||
|
|
||||||
|
|
||||||
|
def load_dump(path):
|
||||||
|
with open(path, 'rb') as f:
|
||||||
|
raw = f.read()
|
||||||
|
if len(raw) == 0x10000:
|
||||||
|
return bytearray(raw)
|
||||||
|
if len(raw) == 0x10002:
|
||||||
|
# 2-byte LE load-addr header
|
||||||
|
return bytearray(raw[2:])
|
||||||
|
raise SystemExit(f'{path}: unexpected dump size {len(raw)}')
|
||||||
|
|
||||||
|
|
||||||
|
# Memory map landmarks (from MECHANICS.md):
|
||||||
|
ADDR_DECOMP_ENTRY = 0x6283 # bank-switch wrapper around $9656
|
||||||
|
SCREEN_BUF_BASE = 0x7530 # decompressed screen RAM target
|
||||||
|
SCREEN_BUF_SIZE = 0x400 # 4 pages = 1024 bytes
|
||||||
|
COLOR_BUF_BASE = 0x7918 # decompressed color RAM target
|
||||||
|
COLOR_BUF_SIZE = 0x400
|
||||||
|
|
||||||
|
|
||||||
|
def run_scene(ram, scene_id):
|
||||||
|
"""Run $6283(X=scene_id) on a fresh copy of `ram`. Returns a tuple
|
||||||
|
(screen_buf, color_buf, final_ram) so the caller can compare or
|
||||||
|
examine state."""
|
||||||
|
work = bytearray(ram) # copy so callers can keep the original
|
||||||
|
cpu = Cpu6502(work)
|
||||||
|
cpu.a = scene_id
|
||||||
|
cpu.x = scene_id
|
||||||
|
cpu.y = 0
|
||||||
|
cpu.call(ADDR_DECOMP_ENTRY)
|
||||||
|
screen = bytes(work[SCREEN_BUF_BASE:SCREEN_BUF_BASE + SCREEN_BUF_SIZE])
|
||||||
|
color = bytes(work[COLOR_BUF_BASE:COLOR_BUF_BASE + COLOR_BUF_SIZE])
|
||||||
|
return screen, color, work
|
||||||
|
|
||||||
|
|
||||||
|
def compare_screen(decomp_screen, live_screen, label):
|
||||||
|
"""Compare 1024-byte decompressed screen vs live screen RAM.
|
||||||
|
Only the first 1000 bytes are visible (40x25). Bytes 1000..1023
|
||||||
|
are unused tail / overlap with color RAM, so we ignore them."""
|
||||||
|
a = decomp_screen[:1000]
|
||||||
|
b = live_screen[:1000]
|
||||||
|
if a == b:
|
||||||
|
print(f' {label}: MATCH')
|
||||||
|
return True
|
||||||
|
# Show how many cells differ and a sample
|
||||||
|
diffs = [(i, a[i], b[i]) for i in range(1000) if a[i] != b[i]]
|
||||||
|
print(f' {label}: {len(diffs)} / 1000 cells differ')
|
||||||
|
for i, av, bv in diffs[:6]:
|
||||||
|
row, col = divmod(i, 40)
|
||||||
|
print(f' cell ({col:2d},{row:2d}): decomp=${av:02X} live=${bv:02X}')
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print('usage: dumpScenes.py <dump-path> [scene-id]', file=sys.stderr)
|
||||||
|
sys.exit(2)
|
||||||
|
ram = load_dump(sys.argv[1])
|
||||||
|
|
||||||
|
# Figure out what the captured state is, then pick the scene to
|
||||||
|
# test against. $7215 = current level number (0..23 = A..X),
|
||||||
|
# but the title screen / specials don't update it -- the
|
||||||
|
# most-recent JSR $6283 used scene $24 (title) or $19 (other
|
||||||
|
# special). Default to the level-number scene, but allow override.
|
||||||
|
state_level = ram[0x7215]
|
||||||
|
if len(sys.argv) >= 3:
|
||||||
|
scene = int(sys.argv[2], 0)
|
||||||
|
else:
|
||||||
|
# Heuristic: if state sentinel $07A6 says we're on the title,
|
||||||
|
# the live screen matches scene 36 ($24). Otherwise we're
|
||||||
|
# mid-gameplay and the live screen matches scene = level.
|
||||||
|
# Title state in this game: top-left tile is the inner frame
|
||||||
|
# corner $5D / $5C. For now just try scene 36 then scene N.
|
||||||
|
scene = 36
|
||||||
|
print(f'=== Sanity test: scene {scene} (${scene:02X}) vs live screen RAM ===')
|
||||||
|
print(f' captured state: $7215={state_level} $07A6=${ram[0x07A6]:02X}')
|
||||||
|
try:
|
||||||
|
scr, col, _ = run_scene(ram, scene)
|
||||||
|
except Exception as e:
|
||||||
|
print(f' emulator raised: {type(e).__name__}: {e}')
|
||||||
|
return
|
||||||
|
live_scr = bytes(ram[0x0400:0x0400 + 1024])
|
||||||
|
live_col = bytes(b & 0x0F for b in ram[0xD800:0xD800 + 1024])
|
||||||
|
compare_screen(scr, live_scr, 'screen')
|
||||||
|
masked_col = bytes(b & 0x0F for b in col)
|
||||||
|
compare_screen(masked_col, live_col, 'color ')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
|
@ -82,7 +82,7 @@ without changes.
|
||||||
3. Author each level layout in a text editor or grid tool, save as a
|
3. Author each level layout in a text editor or grid tool, save as a
|
||||||
.dat per this spec. The helper `examples/spacetaxi/mkstlevel`
|
.dat per this spec. The helper `examples/spacetaxi/mkstlevel`
|
||||||
converts from a human-readable text grid to .dat. The 24 canonical
|
converts from a human-readable text grid to .dat. The 24 canonical
|
||||||
C64 levels are emitted by `stuff/spacetaxi/romToLevel.py` directly
|
C64 levels are emitted by `examples/spacetaxi/assets/romToLevel.py` directly
|
||||||
from a raw VICE dump of the original .prg.
|
from a raw VICE dump of the original .prg.
|
||||||
|
|
||||||
## Naming
|
## Naming
|
||||||
|
|
|
||||||
205
examples/spacetaxi/assets/romToLevel.py
Normal file
205
examples/spacetaxi/assets/romToLevel.py
Normal file
|
|
@ -0,0 +1,205 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# Convert ROM-extracted Space Taxi scenes directly into the runtime
|
||||||
|
# STL1 binary format (see examples/spacetaxi/stLevel.c). Uses the
|
||||||
|
# 6502 emulator + raw RAM dump to decompress each scene, then writes
|
||||||
|
# screen + color RAM bytes straight into the .dat layout the JoeyLib
|
||||||
|
# port loads at runtime.
|
||||||
|
#
|
||||||
|
# Replaces the .txt -> mkstlevel pipeline for canonical levels. The C
|
||||||
|
# mkstlevel tool stays in tree for hand-authored .txt levels.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# romToLevel.py <raw-dump> <out-dir> # all 24 levels
|
||||||
|
# romToLevel.py <raw-dump> <out-dir> <scene_id> # one specific
|
||||||
|
#
|
||||||
|
# Writes:
|
||||||
|
# <out-dir>/level01.dat ... level24.dat (scene 0 = "A" = level01)
|
||||||
|
# <out-dir>/tbank0.png (charset extracted from $2800)
|
||||||
|
|
||||||
|
import os
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
from cpu6502 import Cpu6502
|
||||||
|
from dumpScenes import load_dump, run_scene
|
||||||
|
|
||||||
|
|
||||||
|
# STL2 binary layout (see examples/spacetaxi/stLevel.c):
|
||||||
|
# "STL2"
|
||||||
|
# nameLen + name
|
||||||
|
# tileBank, music, bgColor, borderColor, taxiSpawnX, taxiSpawnY
|
||||||
|
# xAccel, yAccel, xGrav (int8), yGrav (int8)
|
||||||
|
# bgColor1, bgColor2, bgColor3, spriteMc0, spriteMc1, sprite0Color, sprite1Color
|
||||||
|
# padCount + padCount * (letter, tileX, tileY, tileW)
|
||||||
|
# fareCount + fareCount * (spawnPad, destPad)
|
||||||
|
# tilemap[40 * 25]
|
||||||
|
# colormap[40 * 25]
|
||||||
|
|
||||||
|
ST_TILEMAP_W = 40
|
||||||
|
ST_PLAYFIELD_ROWS = 25
|
||||||
|
ST_TILE_PX = 8
|
||||||
|
|
||||||
|
# VIC-II sprite-register origins: sprite-X $18 (24) is display column 0,
|
||||||
|
# sprite-Y $32 (50) is the first visible raster line. All in-game pad/taxi
|
||||||
|
# positions are stored in this sprite-register space, so tile conversion
|
||||||
|
# subtracts these origins. PAD_SURFACE_ORIGIN folds in the ~14px landing-
|
||||||
|
# gear offset (50-14=36) so a pad's row byte maps to its contact-surface
|
||||||
|
# tile row rather than the sprite-top row.
|
||||||
|
VIC_X_ORIGIN = 24
|
||||||
|
VIC_Y_ORIGIN = 50
|
||||||
|
PAD_SURFACE_ORIGIN = 36
|
||||||
|
LEVEL_LETTERS = [chr(ord('A') + i) for i in range(24)]
|
||||||
|
|
||||||
|
|
||||||
|
def write_level(path, level_index, after, screen, colormap, sprite_blocks):
|
||||||
|
"""Write an STL4 level .dat straight from post-decompression RAM.
|
||||||
|
|
||||||
|
Everything stays in the C64's own units so the runtime can mirror the
|
||||||
|
original routines byte for byte:
|
||||||
|
name[22] $7D78-$7D8D level name (screen codes, ASCII-compatible)
|
||||||
|
header[9] $7D00-$7D08 VIC colours: border, bg, bg1-3, mc0, mc1,
|
||||||
|
sprite-0, sprite-1
|
||||||
|
padCount $7D5A pads in the table
|
||||||
|
specialPad $7D09 fare pads (== padCount, or padCount-1 when
|
||||||
|
the last pad is the fuel station)
|
||||||
|
fuelRate $7D60 ticks of flight per half fuel cell
|
||||||
|
spawn[5] $7D5B-$7D5F X frac, X lo, X hi, Y frac, Y hi (sprite coords)
|
||||||
|
templates[8] $7D8F-$7D96 Y accel, X accel, Y gravity, X gravity
|
||||||
|
(16-bit little-endian each)
|
||||||
|
pads[10][8] $7D0A-$7D59 raw slots: x1 hi/lo, x2 hi/lo, row,
|
||||||
|
passenger X frac, passenger X col, unused
|
||||||
|
tilemap[1000] screen RAM (all 25 rows, the HUD rows included)
|
||||||
|
colormap[1000] colour RAM low nibbles
|
||||||
|
levelIndex 0..23 = A..X, 24 = title
|
||||||
|
spriteCount N, then N x (ptr, 63 bytes): the per-level sprite
|
||||||
|
bitmaps the level's extra scene loads at $2000+
|
||||||
|
hookData[616] $7D98-$7FFF: the level's hook code and tables
|
||||||
|
"""
|
||||||
|
cells = ST_TILEMAP_W * ST_PLAYFIELD_ROWS
|
||||||
|
assert len(screen) == cells, f'screen is {len(screen)}, need {cells}'
|
||||||
|
assert len(colormap) == cells, f'colormap is {len(colormap)}, need {cells}'
|
||||||
|
with open(path, 'wb') as fp:
|
||||||
|
fp.write(b'STL4')
|
||||||
|
fp.write(bytes(after[0x7D78:0x7D78 + 22]))
|
||||||
|
fp.write(bytes(after[0x7D00:0x7D09]))
|
||||||
|
fp.write(bytes([after[0x7D5A], after[0x7D09], after[0x7D60]]))
|
||||||
|
fp.write(bytes(after[0x7D5B:0x7D60]))
|
||||||
|
fp.write(bytes(after[0x7D8F:0x7D97]))
|
||||||
|
pad_count = min(after[0x7D5A], 10)
|
||||||
|
for pad_idx in range(10):
|
||||||
|
if pad_idx < pad_count:
|
||||||
|
off = 0x7D0A + pad_idx * 8
|
||||||
|
fp.write(bytes(after[off:off + 8]))
|
||||||
|
else:
|
||||||
|
fp.write(bytes(8))
|
||||||
|
fp.write(bytes(screen))
|
||||||
|
fp.write(bytes(colormap))
|
||||||
|
fp.write(bytes([level_index]))
|
||||||
|
fp.write(bytes([len(sprite_blocks)]))
|
||||||
|
for ptr, data in sprite_blocks:
|
||||||
|
fp.write(bytes([ptr]))
|
||||||
|
fp.write(bytes(data))
|
||||||
|
fp.write(bytes(after[0x7D98:0x8000]))
|
||||||
|
|
||||||
|
|
||||||
|
# Levels whose data blob flags an extra scene ($7D97 != 0) load a second
|
||||||
|
# scene from this table ($6F55, indexed by level) right after the level
|
||||||
|
# itself. Those extra scenes only write sprite bitmaps into $2000-$27FF
|
||||||
|
# (block pointers $80-$9F) for the level's hook code to animate.
|
||||||
|
_EXTRA_SCENE = [0, 0, 0, 0, 0, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0, 0x1F, 0, 0, 0,
|
||||||
|
0x20, 0x21, 0, 0, 0, 0x22, 0, 0, 0, 0x23]
|
||||||
|
|
||||||
|
|
||||||
|
def extract_one_level(ram, scene_id, out_dir):
|
||||||
|
"""Decompress `scene_id` and write out_dir/level<NN>.dat (NN = scene+1)."""
|
||||||
|
letter = LEVEL_LETTERS[scene_id]
|
||||||
|
screen_buf, color_buf, after = run_scene(ram, scene_id)
|
||||||
|
sprite_blocks = []
|
||||||
|
if after[0x7D97] != 0 and _EXTRA_SCENE[scene_id] != 0:
|
||||||
|
# Start the extra scene from a zeroed sprite window so the blocks
|
||||||
|
# it writes are exactly the ones the level ships with.
|
||||||
|
work = bytearray(after)
|
||||||
|
for i in range(0x2000, 0x2800):
|
||||||
|
work[i] = 0
|
||||||
|
_s, _c, after2 = run_scene(bytes(work), _EXTRA_SCENE[scene_id])
|
||||||
|
for ptr in range(0x80, 0xA0):
|
||||||
|
block = bytes(after2[ptr * 64:ptr * 64 + 63])
|
||||||
|
if any(block):
|
||||||
|
sprite_blocks.append((ptr, block))
|
||||||
|
# The level data itself (pads, hooks) is untouched by the extra
|
||||||
|
# scene; keep reading it from the level's own image.
|
||||||
|
cells = ST_TILEMAP_W * ST_PLAYFIELD_ROWS
|
||||||
|
screen = list(screen_buf[:cells])
|
||||||
|
colormap = [(b & 0x0F) for b in color_buf[:cells]]
|
||||||
|
level_num = scene_id + 1
|
||||||
|
out_path = os.path.join(out_dir, f'level{level_num:02d}.dat')
|
||||||
|
write_level(out_path, scene_id, after, screen, colormap, sprite_blocks)
|
||||||
|
return out_path, after[0x7D5A], len(sprite_blocks)
|
||||||
|
|
||||||
|
|
||||||
|
def extract_screen_y(ram, out_dir):
|
||||||
|
"""Screen Y, the 25th screen ("MYSTERY SCREEN !!!"), is scene 24.
|
||||||
|
|
||||||
|
$5010 dispatches on the screens-completed counter: at 24 it jumps to
|
||||||
|
$5167, which loads #$59 ('Y') as the pending screen; the shift only
|
||||||
|
ends when the counter reaches 25. So a full 24-hour shift plays A..X
|
||||||
|
and then Y -- the 25 screens the intro advertises.
|
||||||
|
"""
|
||||||
|
screen_buf, color_buf, after = run_scene(ram, 24)
|
||||||
|
cells = ST_TILEMAP_W * ST_PLAYFIELD_ROWS
|
||||||
|
screen = list(screen_buf[:cells])
|
||||||
|
colormap = [(b & 0x0F) for b in color_buf[:cells]]
|
||||||
|
out_path = os.path.join(out_dir, 'level25.dat')
|
||||||
|
write_level(out_path, 24, after, screen, colormap, [])
|
||||||
|
return out_path
|
||||||
|
|
||||||
|
|
||||||
|
def extract_title(ram, out_dir):
|
||||||
|
"""The title screen is scene 25 (what $477A decompresses).
|
||||||
|
Pads, spawn and templates are dummies."""
|
||||||
|
screen_buf, color_buf, after = run_scene(ram, 25)
|
||||||
|
cells = ST_TILEMAP_W * ST_PLAYFIELD_ROWS
|
||||||
|
screen = list(screen_buf[:cells])
|
||||||
|
colormap = [(b & 0x0F) for b in color_buf[:cells]]
|
||||||
|
out_path = os.path.join(out_dir, 'title.dat')
|
||||||
|
# The title scene writes no level name, so $7D78 still holds whatever
|
||||||
|
# the previously decompressed scene left there (PUZZLER, level H).
|
||||||
|
# The title never draws the name; ship it blank rather than stale.
|
||||||
|
work = bytearray(after)
|
||||||
|
work[0x7D78:0x7D78 + 22] = b' ' * 22
|
||||||
|
write_level(out_path, 25, work, screen, colormap, [])
|
||||||
|
return out_path
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) < 3:
|
||||||
|
print('usage: romToLevel.py <raw-dump> <out-dir> [scene_id]', file=sys.stderr)
|
||||||
|
sys.exit(2)
|
||||||
|
ram = load_dump(sys.argv[1])
|
||||||
|
out_dir = sys.argv[2]
|
||||||
|
os.makedirs(out_dir, exist_ok=True)
|
||||||
|
|
||||||
|
if len(sys.argv) >= 4:
|
||||||
|
scene_ids = [int(sys.argv[3], 0)]
|
||||||
|
else:
|
||||||
|
# Levels A..X = scenes 0..23. The title comes from screen RAM
|
||||||
|
# at $0400 (the dump was taken on the title screen), not from
|
||||||
|
# a decompressed scene.
|
||||||
|
scene_ids = list(range(24))
|
||||||
|
|
||||||
|
for sid in scene_ids:
|
||||||
|
path, pad_count, nspr = extract_one_level(ram, sid, out_dir)
|
||||||
|
size = os.path.getsize(path)
|
||||||
|
print(f' scene {sid:2d} (level {LEVEL_LETTERS[sid]}): pads={pad_count} sprites={nspr} size={size} -> {path}')
|
||||||
|
|
||||||
|
# Screen Y and the title are not part of the A..X sweep.
|
||||||
|
if len(sys.argv) < 4:
|
||||||
|
y_path = extract_screen_y(ram, out_dir)
|
||||||
|
print(f' scene 24 (screen Y) : -> {y_path} ({os.path.getsize(y_path)} bytes)')
|
||||||
|
title_path = extract_title(ram, out_dir)
|
||||||
|
print(f' title (screen RAM $0400) : -> {title_path} ({os.path.getsize(title_path)} bytes)')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
|
@ -13,8 +13,8 @@ extracted the speech:
|
||||||
A song is over when all three voice stream pointers' high bytes are zero
|
A song is over when all three voice stream pointers' high bytes are zero
|
||||||
(that is exactly what the game's play-and-wait loop at $44D5 spins on).
|
(that is exactly what the game's play-and-wait loop at $44D5 spins on).
|
||||||
"""
|
"""
|
||||||
import json, sys
|
import json, os, sys
|
||||||
sys.path.insert(0, "/home/scott/claude/joeylib/stuff/spacetaxi")
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
from cpu6502 import Cpu6502
|
from cpu6502 import Cpu6502
|
||||||
|
|
||||||
RAM = bytearray(open(sys.argv[1], "rb").read()[:65536])
|
RAM = bytearray(open(sys.argv[1], "rb").read()[:65536])
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// Space Taxi -- STL4 level file parser (plain stdio, no JoeyLib), shared
|
// Space Taxi -- STL4 level file parser (plain stdio, no JoeyLib), shared
|
||||||
// by the game and the host-side simulation harness.
|
// by the game and the host-side simulation harness.
|
||||||
//
|
//
|
||||||
// Layout (all bytes, written by stuff/spacetaxi/romToLevel.py):
|
// Layout (all bytes, written by examples/spacetaxi/assets/romToLevel.py):
|
||||||
// "STL4"
|
// "STL4"
|
||||||
// name[22] $7D78 level name in screen codes
|
// name[22] $7D78 level name in screen codes
|
||||||
// header[9] $7D00 VIC colour block
|
// header[9] $7D00 VIC colour block
|
||||||
|
|
|
||||||
447
install.sh
Executable file
447
install.sh
Executable file
|
|
@ -0,0 +1,447 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# JoeyLib installer: everything needed to build (and, with the emulator
|
||||||
|
# components, run and test) JoeyLib programs, pinned and checksummed.
|
||||||
|
# Host packages come from the Linux distribution (apt or dnf); everything
|
||||||
|
# else is downloaded or built under toolchains/ and nowhere else, so that
|
||||||
|
# folder can be deleted at any time and re-created by running this again.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./install.sh [options] <command> [targets...]
|
||||||
|
#
|
||||||
|
# Commands:
|
||||||
|
# list every component, its groups and whether it is current
|
||||||
|
# install TARGETS... install (or update) components and groups
|
||||||
|
# deps TARGETS... show the host packages TARGETS need (--install: install them)
|
||||||
|
# doctor check the host and smoke-test every installed component
|
||||||
|
# import PATH... take private files (Kickstart, ...) from files, folders
|
||||||
|
# or .zip archives, matched by checksum
|
||||||
|
# (installer/files.sha1)
|
||||||
|
# bundle FILE.tar.gz pack the private files present here, for the owner
|
||||||
|
# to host (JOEY_BUNDLE_URL in installer/bundle.conf)
|
||||||
|
# help this text
|
||||||
|
#
|
||||||
|
# Targets are component names (see `list`) or groups:
|
||||||
|
# toolchain:PORT emulator:PORT PORT toolchains emulators all
|
||||||
|
# where PORT is iigs, amiga, atarist, dos or x68000. `all` leaves out
|
||||||
|
# optional components (such as staxiData); name those explicitly.
|
||||||
|
#
|
||||||
|
# Options:
|
||||||
|
# -y, --yes install missing host packages without asking
|
||||||
|
# --install with `deps`: install the missing host packages
|
||||||
|
# --force reinstall even when a component is current
|
||||||
|
# --jobs N parallel build jobs (default: every core)
|
||||||
|
# --keep-build keep the scratch build trees in cache/build
|
||||||
|
# --llvm816-dir PATH use an existing llvm816 checkout for the IIgs
|
||||||
|
# compiler (remembered; "--llvm816-dir clone" undoes it)
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# ./install.sh install toolchains every cross compiler
|
||||||
|
# ./install.sh install emulator:dos what a tester needs for DOS
|
||||||
|
# ./install.sh install amiga Amiga compiler + emulator
|
||||||
|
# ./install.sh deps all --install just the host packages
|
||||||
|
#
|
||||||
|
# make reads toolchains/config.mk by itself. For interactive shells,
|
||||||
|
# `source toolchains/env.sh` sets the same variables.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
# shellcheck source=installer/lib/common.sh
|
||||||
|
source "$(dirname "${BASH_SOURCE[0]}")/installer/lib/common.sh"
|
||||||
|
# shellcheck source=installer/lib/distro.sh
|
||||||
|
source "$JOEY_INSTALLER/lib/distro.sh"
|
||||||
|
# shellcheck source=installer/lib/config.sh
|
||||||
|
source "$JOEY_INSTALLER/lib/config.sh"
|
||||||
|
# shellcheck source=installer/lib/bundle.sh
|
||||||
|
source "$JOEY_INSTALLER/lib/bundle.sh"
|
||||||
|
|
||||||
|
JOEY_PORTS="iigs amiga atarist dos x68000"
|
||||||
|
depsInstall=0
|
||||||
|
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
sed -n '2,/^$/p' "$0" | sed 's/^# \{0,1\}//'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# groupMembers GROUP -- print the components in GROUP.
|
||||||
|
groupMembers() {
|
||||||
|
local group="$1"
|
||||||
|
local id
|
||||||
|
local groups
|
||||||
|
for id in $(componentIds); do
|
||||||
|
groups=" $(compQuery "$id" compGroups) "
|
||||||
|
case "$group" in
|
||||||
|
all)
|
||||||
|
if [[ "$groups" != *" optional "* ]]; then
|
||||||
|
printf '%s\n' "$id"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
toolchains)
|
||||||
|
if [[ "$groups" == *" toolchain:"* ]]; then
|
||||||
|
printf '%s\n' "$id"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
emulators)
|
||||||
|
if [[ "$groups" == *" emulator:"* ]]; then
|
||||||
|
printf '%s\n' "$id"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
iigs|amiga|atarist|dos|x68000)
|
||||||
|
if [[ "$groups" == *" toolchain:$group "* || "$groups" == *" emulator:$group "* ]]; then
|
||||||
|
printf '%s\n' "$id"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
if [[ "$groups" == *" $group "* ]]; then
|
||||||
|
printf '%s\n' "$id"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# resolveTargets TARGET... -- print component ids, each after the components
|
||||||
|
# it requires.
|
||||||
|
resolveTargets() {
|
||||||
|
local target
|
||||||
|
local id
|
||||||
|
local members
|
||||||
|
declare -A seen=()
|
||||||
|
local ordered=()
|
||||||
|
|
||||||
|
addWithRequires() {
|
||||||
|
local req
|
||||||
|
[ -z "${seen[$1]:-}" ] || return 0
|
||||||
|
seen[$1]=1
|
||||||
|
for req in $(compQuery "$1" compRequires); do
|
||||||
|
addWithRequires "$req"
|
||||||
|
done
|
||||||
|
ordered+=("$1")
|
||||||
|
}
|
||||||
|
|
||||||
|
[ $# -gt 0 ] || die "no targets given (try: toolchains, emulators, all, or a port name)"
|
||||||
|
for target in "$@"; do
|
||||||
|
if [ -f "$JOEY_COMPONENTS/$target.sh" ]; then
|
||||||
|
addWithRequires "$target"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
members="$(groupMembers "$target")"
|
||||||
|
[ -n "$members" ] || die "unknown target: $target (see: ./install.sh list)"
|
||||||
|
for id in $members; do
|
||||||
|
addWithRequires "$id"
|
||||||
|
done
|
||||||
|
done
|
||||||
|
printf '%s\n' "${ordered[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# hostPackagesFor ID... -- the distribution packages the components need.
|
||||||
|
hostPackagesFor() {
|
||||||
|
local id
|
||||||
|
local logical=()
|
||||||
|
for id in "$@"; do
|
||||||
|
# shellcheck disable=SC2207
|
||||||
|
logical+=($(compQuery "$id" compHostDeps))
|
||||||
|
done
|
||||||
|
[ ${#logical[@]} -gt 0 ] || return 0
|
||||||
|
distroPackageNames $(printf '%s\n' "${logical[@]}" | sort -u) | sort -u
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
cmdDeps() {
|
||||||
|
local ids
|
||||||
|
local pkgs
|
||||||
|
local missing
|
||||||
|
# Command substitution (not < <(...)) so a failure -- an unknown target,
|
||||||
|
# an unsupported distribution -- stops the script.
|
||||||
|
ids="$(resolveTargets "$@")"
|
||||||
|
pkgs="$(hostPackagesFor $ids)"
|
||||||
|
missing="$(distroMissing $pkgs)"
|
||||||
|
info "host packages for $*: $(printf '%s ' $pkgs)"
|
||||||
|
if [ -z "$missing" ]; then
|
||||||
|
ok "all present"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
warn "missing: $(printf '%s ' $missing)"
|
||||||
|
if [ "$depsInstall" = "1" ]; then
|
||||||
|
distroInstall $missing
|
||||||
|
else
|
||||||
|
info "install them with: ./install.sh deps $* --install"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
cmdInstall() {
|
||||||
|
local ids
|
||||||
|
local pkgs
|
||||||
|
local missing
|
||||||
|
local id
|
||||||
|
local installed=0
|
||||||
|
local idsText
|
||||||
|
idsText="$(resolveTargets "$@")"
|
||||||
|
mapfile -t ids <<< "$idsText"
|
||||||
|
pkgs="$(hostPackagesFor "${ids[@]}")"
|
||||||
|
missing="$(distroMissing $pkgs)"
|
||||||
|
if [ -n "$missing" ]; then
|
||||||
|
if [ "$JOEY_ASSUME_YES" = "1" ]; then
|
||||||
|
distroInstall $missing
|
||||||
|
else
|
||||||
|
die "missing host packages: $(printf '%s ' $missing)
|
||||||
|
Install them with: ./install.sh deps $* --install
|
||||||
|
or re-run with --yes to install them as part of this command."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# A failed component does not stop the others (a tester without the
|
||||||
|
# private bundle still gets every other emulator); anything that
|
||||||
|
# requires a failed component is skipped. All of it is reported at the
|
||||||
|
# end, and the exit status is non-zero.
|
||||||
|
local failed=()
|
||||||
|
local req
|
||||||
|
local blocked
|
||||||
|
for id in "${ids[@]}"; do
|
||||||
|
blocked=""
|
||||||
|
for req in $(compQuery "$id" compRequires); do
|
||||||
|
if [[ " ${failed[*]:-} " == *" $req "* ]]; then
|
||||||
|
blocked="$req"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ -n "$blocked" ]; then
|
||||||
|
warn "$id skipped: it needs $blocked, which failed"
|
||||||
|
failed+=("$id")
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if [ "$JOEY_FORCE" != "1" ] && compIsCurrent "$id"; then
|
||||||
|
ok "$id is current"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
header "$id: $(compQuery "$id" compDescription)"
|
||||||
|
stampClear "$id"
|
||||||
|
if ! compRun "$id" compInstall; then
|
||||||
|
warn "$id: install failed"
|
||||||
|
failed+=("$id")
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if ! compRun "$id" compInstalled; then
|
||||||
|
warn "$id: install finished but the result is missing"
|
||||||
|
failed+=("$id")
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if ! compRun "$id" compVerify >/dev/null; then
|
||||||
|
warn "$id: installed, but its smoke test failed"
|
||||||
|
failed+=("$id")
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
stampWrite "$id" "$(compQuery "$id" compPin)"
|
||||||
|
ok "$id installed"
|
||||||
|
installed=$((installed + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
writeConfig
|
||||||
|
if [ ${#failed[@]} -gt 0 ]; then
|
||||||
|
die "$(( ${#ids[@]} - ${#failed[@]} )) of ${#ids[@]} components current; FAILED: ${failed[*]}"
|
||||||
|
fi
|
||||||
|
ok "${#ids[@]} components current ($installed installed now)"
|
||||||
|
info "make picks the tools up by itself; for a shell: source toolchains/env.sh"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
cmdImport() {
|
||||||
|
local installedText
|
||||||
|
local id
|
||||||
|
[ $# -gt 0 ] || die "import needs at least one file, folder or .zip archive"
|
||||||
|
installedText="$(importPaths "$@")"
|
||||||
|
if [ -z "$installedText" ]; then
|
||||||
|
warn "nothing matched: none of those files is in installer/files.sha1"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
printf '%s\n' "$installedText" | sort -u | while read -r dest; do
|
||||||
|
ok "imported $dest"
|
||||||
|
done
|
||||||
|
# Mark the private components whose file is now in place as installed.
|
||||||
|
for id in $(componentIds); do
|
||||||
|
if [ -n "$(compQuery "$id" compPrivateFile)" ] && compRun "$id" compInstalled; then
|
||||||
|
stampWrite "$id" "$(compQuery "$id" compPin)"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
writeConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
cmdList() {
|
||||||
|
local id
|
||||||
|
local status
|
||||||
|
local stamp
|
||||||
|
printf '%-13s %-9s %-46s %s\n' "COMPONENT" "STATUS" "GROUPS" "DESCRIPTION"
|
||||||
|
for id in $(componentIds); do
|
||||||
|
stamp="$(stampRead "$id")"
|
||||||
|
if [ -z "$stamp" ] || ! compRun "$id" compInstalled >/dev/null 2>&1; then
|
||||||
|
status="missing"
|
||||||
|
elif [ "$stamp" = "$(compQuery "$id" compPin)" ]; then
|
||||||
|
status="current"
|
||||||
|
else
|
||||||
|
status="outdated"
|
||||||
|
fi
|
||||||
|
printf '%-13s %-9s %-46s %s\n' "$id" "$status" "$(compQuery "$id" compGroups)" "$(compQuery "$id" compDescription)"
|
||||||
|
done
|
||||||
|
printf '\ngroups: toolchains emulators all %s, and toolchain:PORT / emulator:PORT\n' "$JOEY_PORTS"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# portGroups ID -- ID's toolchain:PORT and emulator:PORT groups.
|
||||||
|
portGroups() {
|
||||||
|
local group
|
||||||
|
for group in $(compQuery "$1" compGroups); do
|
||||||
|
case "$group" in
|
||||||
|
toolchain:*|emulator:*) printf '%s\n' "$group" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# wantedGroups -- the port groups the user chose: those holding an installed
|
||||||
|
# component that belongs to that group alone. Shared members (headless,
|
||||||
|
# libxmp, mame, cadius) do not count, or installing one port would make
|
||||||
|
# every port that shares them look half-installed.
|
||||||
|
wantedGroups() {
|
||||||
|
local id
|
||||||
|
local groups
|
||||||
|
for id in $(componentIds); do
|
||||||
|
[ -n "$(stampRead "$id")" ] || continue
|
||||||
|
groups="$(portGroups "$id")"
|
||||||
|
[ -n "$groups" ] && [ "$groups" = "${groups%%$'\n'*}" ] && printf '%s\n' "$groups"
|
||||||
|
done
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# firstGroupIn ID " GROUP... " -- print ID's first port group in the list.
|
||||||
|
firstGroupIn() {
|
||||||
|
local group
|
||||||
|
for group in $(portGroups "$1"); do
|
||||||
|
if [[ "$2" == *" $group "* ]]; then
|
||||||
|
printf '%s\n' "$group"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
cmdDoctor() {
|
||||||
|
local id
|
||||||
|
local failures=0
|
||||||
|
local family
|
||||||
|
local wanted
|
||||||
|
local group
|
||||||
|
header "host"
|
||||||
|
family="$(distroFamily)"
|
||||||
|
if [ "$family" = "unknown" ]; then
|
||||||
|
warn "unrecognised distribution: host packages must be installed by hand"
|
||||||
|
failures=$((failures + 1))
|
||||||
|
else
|
||||||
|
ok "package manager: $family"
|
||||||
|
fi
|
||||||
|
if [ "${BASH_VERSINFO[0]}" -lt 4 ]; then
|
||||||
|
warn "bash ${BASH_VERSION} is older than 4"
|
||||||
|
failures=$((failures + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
header "components"
|
||||||
|
wanted=" $(printf '%s ' $(wantedGroups))"
|
||||||
|
for id in $(componentIds); do
|
||||||
|
if [ -z "$(stampRead "$id")" ]; then
|
||||||
|
group="$(firstGroupIn "$id" "$wanted")"
|
||||||
|
if [ -n "$group" ]; then
|
||||||
|
warn "$id: not installed, but the rest of $group is (run: ./install.sh install $id)"
|
||||||
|
failures=$((failures + 1))
|
||||||
|
fi
|
||||||
|
elif ! compRun "$id" compInstalled >/dev/null 2>&1; then
|
||||||
|
if [ -n "$(compQuery "$id" compPrivateFile)" ]; then
|
||||||
|
warn "$id: $(compQuery "$id" compPrivateFile) is gone; supply it with ./install.sh import PATH (or JOEY_BUNDLE_URL)"
|
||||||
|
else
|
||||||
|
warn "$id: its files are gone (run: ./install.sh install $id)"
|
||||||
|
fi
|
||||||
|
failures=$((failures + 1))
|
||||||
|
elif ! compIsCurrent "$id"; then
|
||||||
|
warn "$id: out of date, its pin changed (run: ./install.sh install $id)"
|
||||||
|
failures=$((failures + 1))
|
||||||
|
elif compRun "$id" compVerify >/dev/null; then
|
||||||
|
ok "$id"
|
||||||
|
else
|
||||||
|
warn "$id: smoke test failed"
|
||||||
|
failures=$((failures + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
writeConfig
|
||||||
|
if [ "$failures" -gt 0 ]; then
|
||||||
|
die "$failures problem(s) found"
|
||||||
|
fi
|
||||||
|
ok "everything installed checks out"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ---- argument parsing -----------------------------------------------------
|
||||||
|
|
||||||
|
command=""
|
||||||
|
targets=()
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
-y|--yes)
|
||||||
|
JOEY_ASSUME_YES=1
|
||||||
|
;;
|
||||||
|
--install)
|
||||||
|
depsInstall=1
|
||||||
|
;;
|
||||||
|
--force)
|
||||||
|
JOEY_FORCE=1
|
||||||
|
;;
|
||||||
|
--keep-build)
|
||||||
|
JOEY_KEEP_BUILD=1
|
||||||
|
;;
|
||||||
|
--jobs)
|
||||||
|
[ $# -ge 2 ] || die "--jobs needs a number"
|
||||||
|
JOEY_JOBS="$2"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--llvm816-dir)
|
||||||
|
[ $# -ge 2 ] || die "--llvm816-dir needs a path (or 'clone')"
|
||||||
|
mkdir -p "$JOEY_STATE"
|
||||||
|
if [ "$2" = "clone" ]; then
|
||||||
|
rm -f "$JOEY_STATE/llvm816Dir"
|
||||||
|
else
|
||||||
|
[ -f "$2/setup.sh" ] || die "$2 does not look like an llvm816 checkout (no setup.sh)"
|
||||||
|
(cd "$2" && pwd) > "$JOEY_STATE/llvm816Dir"
|
||||||
|
fi
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h|--help|help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
-*)
|
||||||
|
die "unknown option: $1 (see: ./install.sh help)"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
if [ -z "$command" ]; then
|
||||||
|
command="$1"
|
||||||
|
else
|
||||||
|
targets+=("$1")
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
case "$command" in
|
||||||
|
install) cmdInstall "${targets[@]}" ;;
|
||||||
|
deps) cmdDeps "${targets[@]}" ;;
|
||||||
|
import) cmdImport "${targets[@]}" ;;
|
||||||
|
bundle) [ ${#targets[@]} -eq 1 ] || die "bundle needs one output file name"; bundleCreate "${targets[0]}" ;;
|
||||||
|
list) cmdList ;;
|
||||||
|
doctor) cmdDoctor ;;
|
||||||
|
"") usage; exit 2 ;;
|
||||||
|
*) die "unknown command: $command (see: ./install.sh help)" ;;
|
||||||
|
esac
|
||||||
96
installer/components/amigaGcc.sh
Normal file
96
installer/components/amigaGcc.sh
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
# Amiga C toolchain: Bebbo's m68k-amigaos-gcc 6.5 (AmigaPorts), with
|
||||||
|
# binutils, libnix, clib2, the NDK 3.2 headers and vasm. Built from source
|
||||||
|
# (~15-40 minutes).
|
||||||
|
#
|
||||||
|
# The build clones its sub-projects by BRANCH name, which would make the
|
||||||
|
# result drift; every sub-project is pre-cloned here at a pinned commit so
|
||||||
|
# the Makefile finds it in place and skips its own clone. It also wget's
|
||||||
|
# the NDK and ixemul SDK on every build; those are checked afterwards.
|
||||||
|
|
||||||
|
compDescription="Amiga compiler: m68k-amigaos-gcc 6.5 + NDK 3.2 + vasm (source build)"
|
||||||
|
compGroups="toolchain:amiga"
|
||||||
|
compRequires="host"
|
||||||
|
compHostDeps="build-tools git which bison flex m4 texinfo autoconf automake gettext rsync wget lhasa gmp-dev mpfr-dev mpc-dev ncurses-dev readline-dev"
|
||||||
|
compUrl="https://github.com/AmigaPorts/m68k-amigaos-gcc.git"
|
||||||
|
compCommit="7d3e35f58e61bdf325d84b912b139f973d9b7452"
|
||||||
|
compPin="$compCommit"
|
||||||
|
|
||||||
|
# name commit (the URL comes from the checkout's default-repos file)
|
||||||
|
subProjects=(
|
||||||
|
"amiga-netinclude 7d155ca3a3090afe3466f819dd178f2af6f5c176"
|
||||||
|
"aros-stuff 86711c70569ffc6b70ee1fb99895866c1d9f30e4"
|
||||||
|
"binutils 45a7a53c6d42a38e544ea30116e03eb6b2b37f8b"
|
||||||
|
"clib2 694064c4000b335247164773dd2d231e809a8e8e"
|
||||||
|
"fd2pragma e98f0901340b8989308ca31e53c02a6e61f8339e"
|
||||||
|
"fd2sfd 403714de37f1b9851475ab455964af355bcd6523"
|
||||||
|
"gcc 87dab3c2f2524682287f5e841d75caa60ba504c2"
|
||||||
|
"ira bb7c11384a32c409341fdb0d16d715b7959a0290"
|
||||||
|
"libdebug 42d9d3a7a0d26fa4679384ffc78cf79fbce394d8"
|
||||||
|
"libnix e1ba6cfae3d39c560f975bc2e44d9e8e99b42868"
|
||||||
|
"newlib-cygwin 4c8cbd88df31cd06c89b258585ea5cd86cc9dc45"
|
||||||
|
"sfdc 0e9fa28272aab30f72f34b2d71c178d6ca88d9dd"
|
||||||
|
"vasm 291f6f08226c8843711e1319864526e6cd57ce06"
|
||||||
|
)
|
||||||
|
|
||||||
|
# file sha256 -- what the build is expected to have downloaded
|
||||||
|
buildDownloads=(
|
||||||
|
"NDK3.2.lha 96cabd4ad683dced632e147bf86dee0f50dcb1254386216c25c362916a6409bb"
|
||||||
|
"ixemul-sdk.lha 00b4054283b99d22f6b353194fa8c75917ec0cc08a3a64395a891da446db07fc"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
compInstall() {
|
||||||
|
local src
|
||||||
|
local entry
|
||||||
|
local name
|
||||||
|
local sha
|
||||||
|
local url
|
||||||
|
local dir
|
||||||
|
src="$(freshBuildDir "$compId")"
|
||||||
|
gitFetchCommit "$compUrl" "$compCommit" "$src"
|
||||||
|
for entry in "${subProjects[@]}"; do
|
||||||
|
read -r name sha <<< "$entry"
|
||||||
|
url="$(awk -v n="$name" '$1 == n { print $2 }' "$src/default-repos")"
|
||||||
|
[ -n "$url" ] || die "amiga-gcc: no URL for sub-project $name in default-repos"
|
||||||
|
gitFetchCommit "$url" "$sha" "$src/projects/$name"
|
||||||
|
done
|
||||||
|
rm -rf "$compDir"
|
||||||
|
make -C "$src" all PREFIX="$compDir" -j"$JOEY_JOBS"
|
||||||
|
# Anything cloned by the build itself is unpinned; say so.
|
||||||
|
for dir in "$src"/projects/*/; do
|
||||||
|
name="$(basename "$dir")"
|
||||||
|
[ -d "$dir/.git" ] || continue
|
||||||
|
if [[ " ${subProjects[*]} " != *" $name "* ]]; then
|
||||||
|
warn "amiga-gcc: the build cloned unpinned sub-project '$name'; pin it in $compId.sh"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
for entry in "${buildDownloads[@]}"; do
|
||||||
|
read -r name sha <<< "$entry"
|
||||||
|
[ "$(sha256Of "$src/download/$name")" = "$sha" ] ||
|
||||||
|
die "amiga-gcc: $name downloaded by the build does not match its pin; upstream changed it"
|
||||||
|
done
|
||||||
|
dropBuildDir "$compId"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compInstalled() {
|
||||||
|
[ -x "$compDir/bin/m68k-amigaos-gcc" ] && [ -x "$compDir/bin/vasmm68k_mot" ] && [ -d "$compDir/m68k-amigaos/sys-include" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compVerify() {
|
||||||
|
verifyCompiles "$compDir/bin/m68k-amigaos-gcc" -m68000 -O2
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compEnv() {
|
||||||
|
envVar AMIGA_PREFIX "$compDir"
|
||||||
|
envVar AMIGA_CC "$compDir/bin/m68k-amigaos-gcc"
|
||||||
|
envVar AMIGA_AR "$compDir/bin/m68k-amigaos-ar"
|
||||||
|
envVar AMIGA_LD "$compDir/bin/m68k-amigaos-ld"
|
||||||
|
# vasm assembles only ptplayer.asm (Devpac syntax); JoeyLib's own .s
|
||||||
|
# files are GAS syntax and go through the gcc driver.
|
||||||
|
envVar AMIGA_AS "$compDir/bin/vasmm68k_mot"
|
||||||
|
envVar AMIGA_NDK "$compDir/m68k-amigaos/sys-include"
|
||||||
|
envPath "$compDir/bin"
|
||||||
|
}
|
||||||
44
installer/components/cadius.sh
Normal file
44
installer/components/cadius.sh
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
# cadius: ProDOS disk-image tool used by every IIgs disk script. The
|
||||||
|
# scripts parse its CATALOG output, so it stays pinned to the mach-kernel
|
||||||
|
# fork commit they were written against (v1.4.6).
|
||||||
|
|
||||||
|
compDescription="IIgs disk images: cadius 1.4.6 (ProDOS image tool)"
|
||||||
|
compGroups="toolchain:iigs emulator:iigs"
|
||||||
|
compRequires="host"
|
||||||
|
compHostDeps="build-tools git"
|
||||||
|
compUrl="https://github.com/mach-kernel/cadius.git"
|
||||||
|
compCommit="75c4aa684f6aa0de0c2879b729be465196a348ba"
|
||||||
|
# Built with _GNU_SOURCE: its strptime() call has no prototype otherwise,
|
||||||
|
# which gcc 14 (Fedora 40) rejects. The flag is in the pin so existing
|
||||||
|
# builds redo it.
|
||||||
|
cflags="-D_GNU_SOURCE"
|
||||||
|
compPin="$compCommit $cflags"
|
||||||
|
|
||||||
|
|
||||||
|
compInstall() {
|
||||||
|
local src
|
||||||
|
src="$(freshBuildDir "$compId")"
|
||||||
|
gitFetchCommit "$compUrl" "$compCommit" "$src"
|
||||||
|
# Through the environment: its Makefile appends its own flags to it.
|
||||||
|
CFLAGS="$cflags" make -C "$src" -j"$JOEY_JOBS"
|
||||||
|
install -D -m 755 "$src/bin/release/cadius" "$compDir/bin/cadius"
|
||||||
|
dropBuildDir "$compId"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compInstalled() {
|
||||||
|
[ -x "$compDir/bin/cadius" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compVerify() {
|
||||||
|
local banner
|
||||||
|
# Run with no arguments it prints its banner and exits non-zero.
|
||||||
|
banner="$("$compDir/bin/cadius" 2>&1 || true)"
|
||||||
|
[[ "$banner" == *"v 1.4.6"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compEnv() {
|
||||||
|
envVar CADIUS "$compDir/bin/cadius"
|
||||||
|
}
|
||||||
36
installer/components/cwsdpmi.sh
Normal file
36
installer/components/cwsdpmi.sh
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
# CWSDPMI r7: the DPMI host embedded into every DOS executable
|
||||||
|
# (make/support/dos/embed-dpmi.sh prepends CWSDSTUB.EXE), so programs run
|
||||||
|
# without a separate CWSDPMI.EXE.
|
||||||
|
|
||||||
|
compDescription="DOS DPMI host: CWSDPMI r7 (embedded into each .EXE)"
|
||||||
|
compGroups="toolchain:dos"
|
||||||
|
compRequires="host"
|
||||||
|
compHostDeps="unzip"
|
||||||
|
compUrl="https://sandmann.dotster.com/cwsdpmi/csdpmi7b.zip"
|
||||||
|
compSha256="deacda0488e1cdd7c4a9f32fab45662b34c0ed6b2d7d4d13bc07041b62004a8c"
|
||||||
|
compPin="$compSha256"
|
||||||
|
|
||||||
|
|
||||||
|
compInstall() {
|
||||||
|
local zip
|
||||||
|
zip="$(fetchFile "$compUrl" "$compSha256")"
|
||||||
|
rm -rf "$compDir"
|
||||||
|
# The zip holds bin/ and src/ trees; bin/ is what the build uses.
|
||||||
|
extractArchive "$zip" "$compDir"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compInstalled() {
|
||||||
|
[ -f "$compDir/bin/CWSDSTUB.EXE" ] && [ -f "$compDir/bin/CWSDPMI.EXE" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compVerify() {
|
||||||
|
[ "$(head -c 2 "$compDir/bin/CWSDSTUB.EXE")" = "MZ" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compEnv() {
|
||||||
|
envVar DOS_CWSDPMI "$compDir/bin"
|
||||||
|
envVar DOS_CWSDSTUB "$compDir/bin/CWSDSTUB.EXE"
|
||||||
|
}
|
||||||
44
installer/components/djgpp.sh
Normal file
44
installer/components/djgpp.sh
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
# DOS C toolchain: DJGPP with gcc 12.2.0, prebuilt for Linux x86-64 by
|
||||||
|
# andrewwutw/build-djgpp (release v3.4).
|
||||||
|
|
||||||
|
compDescription="DOS compiler: DJGPP gcc 12.2.0 (build-djgpp v3.4)"
|
||||||
|
compGroups="toolchain:dos"
|
||||||
|
compRequires="host"
|
||||||
|
compHostDeps="bzip2 libfl"
|
||||||
|
compUrl="https://github.com/andrewwutw/build-djgpp/releases/download/v3.4/djgpp-linux64-gcc1220.tar.bz2"
|
||||||
|
compSha256="8464f17017d6ab1b2bb2df4ed82357b5bf692e6e2b7fee37e315638f3d505f00"
|
||||||
|
compPin="$compSha256"
|
||||||
|
|
||||||
|
|
||||||
|
compInstall() {
|
||||||
|
local archive
|
||||||
|
archive="$(fetchFile "$compUrl" "$compSha256")"
|
||||||
|
rm -rf "$compDir"
|
||||||
|
extractArchive "$archive" "$compDir" 1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compInstalled() {
|
||||||
|
[ -x "$compDir/bin/i586-pc-msdosdjgpp-gcc" ] && [ -x "$compDir/i586-pc-msdosdjgpp/bin/exe2coff" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compVerify() {
|
||||||
|
verifyLinked "$compDir/bin" "$compDir/i586-pc-msdosdjgpp/bin" "$compDir"/libexec/gcc/*/*
|
||||||
|
verifyCompiles "$compDir/bin/i586-pc-msdosdjgpp-gcc" -march=i386 -O2
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compEnv() {
|
||||||
|
envVar DOS_PREFIX "$compDir"
|
||||||
|
envVar DOS_CC "$compDir/bin/i586-pc-msdosdjgpp-gcc"
|
||||||
|
envVar DOS_AR "$compDir/bin/i586-pc-msdosdjgpp-ar"
|
||||||
|
envVar DOS_LD "$compDir/bin/i586-pc-msdosdjgpp-ld"
|
||||||
|
# The DOS-target utilities live in the target sysroot's bin, not the
|
||||||
|
# host bin that holds the cross gcc.
|
||||||
|
envVar DOS_STUBEDIT "$compDir/i586-pc-msdosdjgpp/bin/stubedit"
|
||||||
|
envVar DOS_STUBIFY "$compDir/i586-pc-msdosdjgpp/bin/stubify"
|
||||||
|
envVar DOS_EXE2COFF "$compDir/i586-pc-msdosdjgpp/bin/exe2coff"
|
||||||
|
envVar DOS_EMBED_DPMI "$JOEY_BUILD_SUPPORT/dos/embed-dpmi.sh"
|
||||||
|
envPath "$compDir/bin"
|
||||||
|
}
|
||||||
41
installer/components/dosbox.sh
Normal file
41
installer/components/dosbox.sh
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
# DOSBox Staging 0.83.0: the DOS emulator, as the project's own prebuilt
|
||||||
|
# Linux release (bundled libraries), because Debian/Ubuntu only package the
|
||||||
|
# old 0.74 and the gates are written for Staging.
|
||||||
|
|
||||||
|
compDescription="emulator: DOSBox Staging 0.83.0 (MS-DOS; upstream Linux release)"
|
||||||
|
compGroups="emulator:dos"
|
||||||
|
compRequires="host"
|
||||||
|
compHostDeps="xz"
|
||||||
|
compUrl="https://github.com/dosbox-staging/dosbox-staging/releases/download/v0.83.0/dosbox-staging-linux-x86_64-v0.83.0.tar.xz"
|
||||||
|
compSha256="d3a94f7f1c3e68a47ec88d61145506c7904452adb0c9c5928cb8cfe2331d6c5c"
|
||||||
|
compPin="$compSha256"
|
||||||
|
|
||||||
|
|
||||||
|
compInstall() {
|
||||||
|
local archive
|
||||||
|
archive="$(fetchFile "$compUrl" "$compSha256")"
|
||||||
|
rm -rf "$compDir"
|
||||||
|
extractArchive "$archive" "$compDir" 1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compInstalled() {
|
||||||
|
[ -x "$compDir/dosbox" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compVerify() {
|
||||||
|
local xdg
|
||||||
|
local version
|
||||||
|
verifyLinked "$compDir"
|
||||||
|
# Isolated: even --version creates ~/.config/dosbox otherwise.
|
||||||
|
xdg="$(mktemp -d)"
|
||||||
|
version="$(XDG_CONFIG_HOME="$xdg" "$compDir/dosbox" --noprimaryconf --nolocalconf --version 2>&1 || true)"
|
||||||
|
rm -rf "$xdg"
|
||||||
|
[[ "$version" == *"version 0.83.0"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compEnv() {
|
||||||
|
envVar DOSBOX "$compDir/dosbox"
|
||||||
|
}
|
||||||
36
installer/components/elf2x68k.sh
Normal file
36
installer/components/elf2x68k.sh
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
# X68000 C toolchain: yunkya2's elf2x68k (m68k-xelf gcc 13.4.0, binutils
|
||||||
|
# 2.44, newlib 4.5.0), prebuilt for Linux x86-64. m68k-xelf-gcc emits ELF;
|
||||||
|
# its linker script runs elf2x68k.py (python3) to produce Human68k .X files.
|
||||||
|
|
||||||
|
compDescription="X68000 compiler: elf2x68k 20260602 (m68k-xelf gcc 13.4.0)"
|
||||||
|
compGroups="toolchain:x68000"
|
||||||
|
compRequires="host"
|
||||||
|
compHostDeps="bzip2 python3 gconv-cp932"
|
||||||
|
compUrl="https://github.com/yunkya2/elf2x68k/releases/download/20260602/elf2x68k-Linux-20260602.tar.bz2"
|
||||||
|
compSha256="625968297d140b5b5a47edbc67d69a0b505b0891adb152b45adeee5329fd9832"
|
||||||
|
compPin="$compSha256"
|
||||||
|
|
||||||
|
|
||||||
|
compInstall() {
|
||||||
|
local archive
|
||||||
|
archive="$(fetchFile "$compUrl" "$compSha256")"
|
||||||
|
rm -rf "$compDir"
|
||||||
|
extractArchive "$archive" "$compDir" 1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compInstalled() {
|
||||||
|
[ -x "$compDir/bin/m68k-xelf-gcc" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compVerify() {
|
||||||
|
verifyLinked "$compDir/bin" "$compDir/m68k-elf/bin" "$compDir"/libexec/gcc/*/*
|
||||||
|
verifyCompiles "$compDir/bin/m68k-xelf-gcc" -m68000 -O2
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compEnv() {
|
||||||
|
envVar X68K_ROOT "$compDir"
|
||||||
|
envPath "$compDir/bin"
|
||||||
|
}
|
||||||
36
installer/components/emutos.sh
Normal file
36
installer/components/emutos.sh
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
# EmuTOS 1.4 (GPL): a free TOS replacement for the Atari ST emulator, the
|
||||||
|
# 512 KB US-English image. The gates and joey-run boot it instead of
|
||||||
|
# Atari's copyrighted TOS.
|
||||||
|
|
||||||
|
compDescription="Atari ST firmware: EmuTOS 1.4 (free TOS replacement)"
|
||||||
|
compGroups="emulator:atarist"
|
||||||
|
compRequires="host"
|
||||||
|
compHostDeps="unzip"
|
||||||
|
compUrl="https://sourceforge.net/projects/emutos/files/emutos/1.4/emutos-512k-1.4.zip/download"
|
||||||
|
compSha256="1ef7bd25f61bcfc66d19debc2f0ebb0d5e5ba811ccd7f0fddfcb105bb72d1663"
|
||||||
|
compPin="$compSha256"
|
||||||
|
imgPath="$JOEY_FIRMWARE/atarist/emutos-512k.img"
|
||||||
|
|
||||||
|
|
||||||
|
compInstall() {
|
||||||
|
local tmp
|
||||||
|
tmp="$(freshBuildDir "$compId")"
|
||||||
|
unzip -oq "$(fetchFile "$compUrl" "$compSha256" emutos-512k-1.4.zip)" 'emutos-512k-1.4/etos512us.img' -d "$tmp"
|
||||||
|
install -D -m 644 "$tmp/emutos-512k-1.4/etos512us.img" "$imgPath"
|
||||||
|
dropBuildDir "$compId"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compInstalled() {
|
||||||
|
[ -f "$imgPath" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compVerify() {
|
||||||
|
[ "$(sha1sum "$imgPath" | cut -d' ' -f1)" = "7d28ee58f00854954073198983572e6921b87a21" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compEnv() {
|
||||||
|
envVar EMUTOS_IMG "$imgPath"
|
||||||
|
}
|
||||||
58
installer/components/fsuae.sh
Normal file
58
installer/components/fsuae.sh
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
# FS-UAE 3.1.66: the Amiga emulator. Debian and Ubuntu package exactly this
|
||||||
|
# version; Fedora does not package FS-UAE at all, so on dnf systems it is
|
||||||
|
# built from the upstream release tarball into toolchains/opt/fsuae.
|
||||||
|
|
||||||
|
compDescription="emulator: FS-UAE 3.1.66 (Amiga; distribution package, source build on dnf)"
|
||||||
|
compGroups="emulator:amiga"
|
||||||
|
compRequires="host"
|
||||||
|
compUrl="https://fs-uae.net/files/FS-UAE/Stable/3.1.66/fs-uae-3.1.66.tar.xz"
|
||||||
|
compSha256="606e1868b500413d69bd33bb469f8fd08d6c08988801f17b7dd022f3fbe23832"
|
||||||
|
if [ "$(distroFamily)" = "dnf" ]; then
|
||||||
|
fsuaeBin="$compDir/bin/fs-uae"
|
||||||
|
compHostDeps="build-tools make pkg-config gettext zip glib-dev openal-dev png-dev sdl2-dev zlib-dev gl-dev x11-dev xi-dev"
|
||||||
|
compPin="$compSha256"
|
||||||
|
else
|
||||||
|
fsuaeBin="$(command -v fs-uae || true)"
|
||||||
|
compHostDeps="fs-uae"
|
||||||
|
compPin="distro"
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
compInstall() {
|
||||||
|
local src
|
||||||
|
local archive
|
||||||
|
if [ "$compPin" = "distro" ]; then
|
||||||
|
[ -n "$fsuaeBin" ] || die "fs-uae is not on PATH after installing host packages"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
src="$(freshBuildDir "$compId")"
|
||||||
|
archive="$(fetchFile "$compUrl" "$compSha256")"
|
||||||
|
extractArchive "$archive" "$src" 1
|
||||||
|
# libmpeg2 only plays CD32 FMV; nothing here needs it.
|
||||||
|
(cd "$src" && ./configure --prefix="$compDir" --without-libmpeg2)
|
||||||
|
make -C "$src" -j"$JOEY_JOBS"
|
||||||
|
rm -rf "$compDir"
|
||||||
|
make -C "$src" install
|
||||||
|
dropBuildDir "$compId"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compInstalled() {
|
||||||
|
[ -n "$fsuaeBin" ] && [ -x "$fsuaeBin" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compVerify() {
|
||||||
|
local version
|
||||||
|
version="$("$fsuaeBin" --version 2>&1)" || return 1
|
||||||
|
version="${version%%$'\n'*}"
|
||||||
|
# Another version still runs programs; only the gates may disagree.
|
||||||
|
if [ "$version" != "3.1.66" ]; then
|
||||||
|
warn "found FS-UAE '$version'; the Amiga gates were written against 3.1.66"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compEnv() {
|
||||||
|
envVar FSUAE "$fsuaeBin"
|
||||||
|
}
|
||||||
58
installer/components/hatari.sh
Normal file
58
installer/components/hatari.sh
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
# Hatari 2.4.1: the Atari ST emulator. The ST gates parse its trace and
|
||||||
|
# debugger output as Hatari 2.4 prints them. Debian and Ubuntu package
|
||||||
|
# 2.4.1; Fedora ships a different version, so on dnf systems 2.4.1 is built
|
||||||
|
# from source into toolchains/opt/hatari.
|
||||||
|
|
||||||
|
compDescription="emulator: Hatari 2.4.1 (Atari ST; distribution package, source build on dnf)"
|
||||||
|
compGroups="emulator:atarist"
|
||||||
|
compRequires="host"
|
||||||
|
compUrl="https://framagit.org/hatari/hatari.git"
|
||||||
|
compCommit="4371dcd647fc85d31c0629400adaeaa4212040d9"
|
||||||
|
if [ "$(distroFamily)" = "dnf" ]; then
|
||||||
|
hatariBin="$compDir/bin/hatari"
|
||||||
|
compHostDeps="build-tools make cmake git sdl2-dev zlib-dev png-dev readline-dev"
|
||||||
|
compPin="$compCommit"
|
||||||
|
else
|
||||||
|
hatariBin="$(command -v hatari || true)"
|
||||||
|
compHostDeps="hatari"
|
||||||
|
compPin="distro"
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
compInstall() {
|
||||||
|
local src
|
||||||
|
if [ "$compPin" = "distro" ]; then
|
||||||
|
[ -n "$hatariBin" ] || die "hatari is not on PATH after installing host packages"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
src="$(freshBuildDir "$compId")"
|
||||||
|
gitFetchCommit "$compUrl" "$compCommit" "$src"
|
||||||
|
cmake -S "$src" -B "$src/build" -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="$compDir"
|
||||||
|
cmake --build "$src/build" -j"$JOEY_JOBS"
|
||||||
|
rm -rf "$compDir"
|
||||||
|
cmake --install "$src/build"
|
||||||
|
dropBuildDir "$compId"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compInstalled() {
|
||||||
|
[ -n "$hatariBin" ] && [ -x "$hatariBin" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compVerify() {
|
||||||
|
local version
|
||||||
|
# It prints a blank line first and exits 1, so match anywhere.
|
||||||
|
version="$("$hatariBin" --version 2>&1 || true)"
|
||||||
|
[[ "$version" == *"Hatari v"* ]] || return 1
|
||||||
|
# Another version still runs programs; only the gates may disagree.
|
||||||
|
if [[ "$version" != *"Hatari v2.4"* ]]; then
|
||||||
|
version="${version#$'\n'}"
|
||||||
|
warn "found '${version%%$'\n'*}'; the ST gates were written against Hatari 2.4.x"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compEnv() {
|
||||||
|
envVar HATARI "$hatariBin"
|
||||||
|
}
|
||||||
29
installer/components/headless.sh
Normal file
29
installer/components/headless.sh
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
# What the test gates need to run the SDL/X11 emulators without a screen:
|
||||||
|
# a virtual X server, key injection, screenshots, and PulseAudio's
|
||||||
|
# capture tools for the audio gates.
|
||||||
|
|
||||||
|
compDescription="test gates: Xvfb, xdotool, ImageMagick, pactl/parec (headless runs)"
|
||||||
|
compGroups="emulator:amiga emulator:atarist emulator:dos"
|
||||||
|
compRequires="host"
|
||||||
|
compHostDeps="xvfb xdotool imagemagick pulse-utils"
|
||||||
|
compPin="distro"
|
||||||
|
|
||||||
|
|
||||||
|
compInstall() {
|
||||||
|
:
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compInstalled() {
|
||||||
|
command -v Xvfb >/dev/null && command -v xdotool >/dev/null && command -v import >/dev/null && command -v parec >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compVerify() {
|
||||||
|
compInstalled
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compEnv() {
|
||||||
|
:
|
||||||
|
}
|
||||||
67
installer/components/host.sh
Normal file
67
installer/components/host.sh
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
# Host tools every JoeyLib setup needs: a native compiler for the in-repo
|
||||||
|
# tools, GNU make, git with LFS (the sample assets live in LFS), the
|
||||||
|
# archive tools the other components unpack with, and Python 3 with
|
||||||
|
# Pillow for the asset converters.
|
||||||
|
|
||||||
|
compDescription="host tools: make, host compiler, git-lfs, python3 + Pillow, archivers"
|
||||||
|
# Not in any port group: every other component requires it.
|
||||||
|
compGroups="host"
|
||||||
|
compRequires=""
|
||||||
|
compHostDeps="build-tools make git git-lfs curl tar xz bzip2 unzip lhasa python3 python3-pil procps diffutils"
|
||||||
|
compPin="1"
|
||||||
|
|
||||||
|
|
||||||
|
# Print the LFS-tracked files that are still pointer stubs.
|
||||||
|
lfsPointerFiles() {
|
||||||
|
local file
|
||||||
|
local names
|
||||||
|
local start
|
||||||
|
names="$(git -C "$JOEY_REPO" lfs ls-files --name-only 2>/dev/null || true)"
|
||||||
|
while IFS= read -r file; do
|
||||||
|
[ -n "$file" ] || continue
|
||||||
|
# A pointer stub starts with this 23-byte line; read just that much
|
||||||
|
# (binary assets hold NUL bytes a command substitution would warn about).
|
||||||
|
start=""
|
||||||
|
IFS= read -r -N 23 start < "$JOEY_REPO/$file" 2>/dev/null || true
|
||||||
|
if [[ "$start" == "version https://git-lfs"* ]]; then
|
||||||
|
printf '%s\n' "$file"
|
||||||
|
fi
|
||||||
|
done <<< "$names"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compInstall() {
|
||||||
|
mkdir -p "$compDir"
|
||||||
|
# A clone made without git-lfs holds pointer stubs instead of the
|
||||||
|
# sample assets; fetch the real files only in that case.
|
||||||
|
if [ -d "$JOEY_REPO/.git" ] && [ -n "$(lfsPointerFiles)" ]; then
|
||||||
|
info "fetching Git LFS assets"
|
||||||
|
git -C "$JOEY_REPO" lfs install --local
|
||||||
|
git -C "$JOEY_REPO" lfs pull
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compInstalled() {
|
||||||
|
command -v make >/dev/null && command -v cc >/dev/null && command -v python3 >/dev/null && command -v git-lfs >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compVerify() {
|
||||||
|
local makeVersion
|
||||||
|
local stubs
|
||||||
|
# No pipes into head here: under pipefail an early-exiting reader makes
|
||||||
|
# the writer die of SIGPIPE, and the check fails at random.
|
||||||
|
makeVersion="$(make --version 2>/dev/null || true)"
|
||||||
|
[[ "$makeVersion" =~ ([0-9]+\.[0-9]+) ]] && makeVersion="${BASH_REMATCH[1]}"
|
||||||
|
# Grouped targets (&:) in the port makefiles need GNU make 4.3.
|
||||||
|
printf '%s\n4.3\n' "$makeVersion" | sort -V -C -r || { warn "GNU make $makeVersion is older than 4.3"; return 1; }
|
||||||
|
python3 -c 'import PIL' || { warn "python3 cannot import PIL (Pillow)"; return 1; }
|
||||||
|
stubs="$(lfsPointerFiles | wc -l)"
|
||||||
|
[ "$stubs" = "0" ] || { warn "$stubs Git LFS files are still pointer stubs"; return 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compEnv() {
|
||||||
|
:
|
||||||
|
}
|
||||||
61
installer/components/iigsFirmware.sh
Normal file
61
installer/components/iigsFirmware.sh
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
# Apple IIgs firmware and system software for MAME:
|
||||||
|
# - the MAME apple2gs (ROM 03) and apple2gsr1 (ROM 01) ROM sets, from the
|
||||||
|
# archive.org MAME 0.240 split set (the source llvm816 also uses)
|
||||||
|
# - the CFFA2 card firmware, freely redistributed by dreher.net, needed
|
||||||
|
# to boot images larger than an 800 KB floppy
|
||||||
|
# - the GS/OS 6.0.4 System.Disk, from the Apple II archive
|
||||||
|
# Everything lands in toolchains/firmware/iigs.
|
||||||
|
|
||||||
|
compDescription="IIgs firmware: MAME ROM sets (ROM 01 + 03), CFFA2 firmware, GS/OS 6.0.4"
|
||||||
|
compGroups="emulator:iigs"
|
||||||
|
compRequires="host"
|
||||||
|
compHostDeps="unzip"
|
||||||
|
romBase="https://archive.org/download/mame-0.240-roms-split_202201/MAME%200.240%20ROMs%20(split)"
|
||||||
|
cffaUrl="https://dreher.net/projects/CFforAppleII/downloads/Run6_CDROM.zip"
|
||||||
|
gsosUrl="https://mirrors.apple2.org.za/ftp.apple.asimov.net/images/gs/os/gsos/Apple_IIGS_System_6.0.4.zip"
|
||||||
|
fwDir="$JOEY_FIRMWARE/iigs"
|
||||||
|
|
||||||
|
# file sha256
|
||||||
|
romSets=(
|
||||||
|
"apple2gs.zip eddce4a9f9f6e73ffed06c5aede6fbcdaa01dda5568b81f90d865858a3d7d9bc"
|
||||||
|
"apple2gsr1.zip 1c16fec7b9e785c4118909e409e61dcc38d0921f774b8708bae176bc5eb27307"
|
||||||
|
)
|
||||||
|
cffaSha256="48242b9e2e87d87bfef1f1b1d64d1091bbfca182d745c1d031690fac0f80c0fe"
|
||||||
|
gsosSha256="e42a4f1f50c1aae0cc17dc555415a2fbf21dc0b7b6b3e33e0d73bf22ef97506a"
|
||||||
|
compPin="${romSets[*]} $cffaSha256 $gsosSha256"
|
||||||
|
|
||||||
|
|
||||||
|
compInstall() {
|
||||||
|
local entry
|
||||||
|
local name
|
||||||
|
local sha
|
||||||
|
local tmp
|
||||||
|
for entry in "${romSets[@]}"; do
|
||||||
|
read -r name sha <<< "$entry"
|
||||||
|
install -D -m 644 "$(fetchFile "$romBase/$name" "$sha")" "$fwDir/roms/$name"
|
||||||
|
done
|
||||||
|
tmp="$(freshBuildDir "$compId")"
|
||||||
|
unzip -oq "$(fetchFile "$cffaUrl" "$cffaSha256")" 'Firmware/V2.0/CFFA20EEC02.BIN' -d "$tmp"
|
||||||
|
install -D -m 644 "$tmp/Firmware/V2.0/CFFA20EEC02.BIN" "$fwDir/roms/a2cffa2/cffa20eec02.bin"
|
||||||
|
unzip -oq "$(fetchFile "$gsosUrl" "$gsosSha256")" 'Apple_IIGS_System_6.0.4/PO Disk Images/System.Disk.po' -d "$tmp"
|
||||||
|
install -D -m 644 "$tmp/Apple_IIGS_System_6.0.4/PO Disk Images/System.Disk.po" "$fwDir/gsos-system.po"
|
||||||
|
dropBuildDir "$compId"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compInstalled() {
|
||||||
|
[ -f "$fwDir/roms/apple2gs.zip" ] && [ -f "$fwDir/roms/apple2gsr1.zip" ] &&
|
||||||
|
[ -f "$fwDir/roms/a2cffa2/cffa20eec02.bin" ] && [ -f "$fwDir/gsos-system.po" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compVerify() {
|
||||||
|
[ "$(sha1sum "$fwDir/roms/a2cffa2/cffa20eec02.bin" | cut -d' ' -f1)" = "080ff88f19de22328e162954ee2b51ee65f9d5cd" ] &&
|
||||||
|
[ "$(sha1sum "$fwDir/gsos-system.po" | cut -d' ' -f1)" = "a57940a0b724fc7dc545236a4f9d4c6dd68f3e81" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compEnv() {
|
||||||
|
envVar IIGS_ROMPATH "$fwDir/roms"
|
||||||
|
envVar GSOS_SYSTEM_DISK "$fwDir/gsos-system.po"
|
||||||
|
}
|
||||||
31
installer/components/kickstart.sh
Normal file
31
installer/components/kickstart.sh
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
# Amiga Kickstart 3.1 ROM. Cloanto still sells it (Amiga Forever), so it is
|
||||||
|
# never downloaded from a public source: it comes from the private bundle
|
||||||
|
# or `install.sh import`. The Amiga gates need it; AROS, FS-UAE's free
|
||||||
|
# fallback, is not reliable enough for them.
|
||||||
|
|
||||||
|
compDescription="Amiga firmware: Kickstart 3.1 (private: bundle or import)"
|
||||||
|
compGroups="emulator:amiga"
|
||||||
|
compRequires=""
|
||||||
|
compHostDeps=""
|
||||||
|
compPrivateFile="toolchains/firmware/amiga/kickstart.rom"
|
||||||
|
compPin="$(privateSha1For "$compPrivateFile")"
|
||||||
|
|
||||||
|
|
||||||
|
compInstall() {
|
||||||
|
privateEnsure "$compPrivateFile"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compInstalled() {
|
||||||
|
privatePresent "$compPrivateFile"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compVerify() {
|
||||||
|
privatePresent "$compPrivateFile"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compEnv() {
|
||||||
|
envVar KICKSTART_ROM "$JOEY_REPO/$compPrivateFile"
|
||||||
|
}
|
||||||
33
installer/components/libxmp.sh
Normal file
33
installer/components/libxmp.sh
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
# libxmp-lite 4.7.0: the MOD decoder the DOS, Atari ST and X68000 audio
|
||||||
|
# HALs compile in. Each port's makefile builds it from this source tree.
|
||||||
|
|
||||||
|
compDescription="audio: libxmp-lite 4.7.0 source (DOS, Atari ST, X68000)"
|
||||||
|
compGroups="toolchain:dos toolchain:atarist toolchain:x68000"
|
||||||
|
compRequires="host"
|
||||||
|
compHostDeps="tar"
|
||||||
|
compUrl="https://github.com/libxmp/libxmp/releases/download/libxmp-4.7.0/libxmp-lite-4.7.0.tar.gz"
|
||||||
|
compSha256="69967a7802d2d2f938347f6f9cd95232bf45a4623cbfcc196e37cdaefadc3974"
|
||||||
|
compPin="$compSha256"
|
||||||
|
|
||||||
|
|
||||||
|
compInstall() {
|
||||||
|
local archive
|
||||||
|
archive="$(fetchFile "$compUrl" "$compSha256")"
|
||||||
|
rm -rf "$compDir"
|
||||||
|
extractArchive "$archive" "$compDir" 1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compInstalled() {
|
||||||
|
[ -f "$compDir/include/libxmp-lite/xmp.h" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compVerify() {
|
||||||
|
[ -d "$compDir/src" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compEnv() {
|
||||||
|
envVar LIBXMP_DIR "$compDir"
|
||||||
|
}
|
||||||
68
installer/components/llvm816.sh
Normal file
68
installer/components/llvm816.sh
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
# Apple IIgs C toolchain: llvm816, a W65816 backend for llvm-mos, plus its
|
||||||
|
# linker (link816 + omfEmit) and runtime. Cloned at a pinned commit and
|
||||||
|
# built by the repo's own setup.sh; the first build is ~30-60 minutes.
|
||||||
|
#
|
||||||
|
# `install.sh --llvm816-dir PATH` links an existing checkout instead (for
|
||||||
|
# working on llvm816 and JoeyLib together). A linked checkout is managed
|
||||||
|
# by its owner: the installer only checks it is built.
|
||||||
|
|
||||||
|
compDescription="IIgs compiler: llvm816 (clang/llc/llvm-mc for w65816, link816, runtime)"
|
||||||
|
compGroups="toolchain:iigs"
|
||||||
|
compRequires="host"
|
||||||
|
compUrl="https://forge.duensing.digital/AI_Slop/65816-llvm-mos.git"
|
||||||
|
compCommit="9ca7f1d0de6e23282bed4546441ed9bcced30a5d"
|
||||||
|
|
||||||
|
linkedDir="$(cat "$JOEY_STATE/llvm816Dir" 2>/dev/null || true)"
|
||||||
|
# A linked checkout is built by its owner, so it needs no build packages.
|
||||||
|
if [ -n "$linkedDir" ]; then
|
||||||
|
compPin="linked:$linkedDir"
|
||||||
|
compHostDeps=""
|
||||||
|
else
|
||||||
|
compPin="$compCommit"
|
||||||
|
compHostDeps="build-tools cmake ninja lld git python3 zlib-dev zstd-dev libedit-dev libxml2-dev ncurses-dev"
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
compInstall() {
|
||||||
|
if [ -n "$linkedDir" ]; then
|
||||||
|
[ -x "$linkedDir/tools/llvm-mos-build/bin/clang" ] || die "$linkedDir has no built clang; run its setup.sh first"
|
||||||
|
rm -rf "$compDir"
|
||||||
|
ln -s "$linkedDir" "$compDir"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [ -L "$compDir" ]; then
|
||||||
|
rm -f "$compDir"
|
||||||
|
fi
|
||||||
|
gitFetchCommit "$compUrl" "$compCommit" "$compDir"
|
||||||
|
# Host packages come from install.sh (--skip-deps); JoeyLib brings its
|
||||||
|
# own MAME and does not use Calypsi or GNO/ME.
|
||||||
|
(cd "$compDir" && bash setup.sh --skip-deps --skip-mame --skip-calypsi --skip-gno --skip-verify)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compInstalled() {
|
||||||
|
[ -x "$compDir/tools/llvm-mos-build/bin/clang" ] &&
|
||||||
|
[ -x "$compDir/tools/llvm-mos-build/bin/llvm-ar" ] &&
|
||||||
|
[ -x "$compDir/tools/link816" ] &&
|
||||||
|
[ -x "$compDir/tools/omfEmit" ] &&
|
||||||
|
[ -f "$compDir/runtime/crt0Gsos.o" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compVerify() {
|
||||||
|
local tmp
|
||||||
|
local status=1
|
||||||
|
tmp="$(mktemp -d)"
|
||||||
|
printf 'int answer(void) { return 42; }\n' > "$tmp/t.c"
|
||||||
|
if "$compDir/tools/llvm-mos-build/bin/clang" --target=w65816 -O2 -c "$tmp/t.c" -o "$tmp/t.o"; then
|
||||||
|
[[ "$("$compDir/tools/llvm-mos-build/bin/llvm-objdump" --triple=w65816 -d "$tmp/t.o" 2>/dev/null)" == *lda* ]] && status=0
|
||||||
|
fi
|
||||||
|
rm -rf "$tmp"
|
||||||
|
return $status
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compEnv() {
|
||||||
|
envVar LLVM816_ROOT "$compDir"
|
||||||
|
envVar IIGS_CLANG_BUILD "$JOEY_BUILD_SUPPORT/iigs/clang-build.sh"
|
||||||
|
}
|
||||||
49
installer/components/mame.sh
Normal file
49
installer/components/mame.sh
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
# MAME 0.264 with JoeyLib's patches (installer/patches/mame), built from
|
||||||
|
# source as one binary holding only the two drivers the ports need: apple2gs
|
||||||
|
# (Apple IIgs) and x68k (Sharp X68000). Always built, never taken from the
|
||||||
|
# distribution: the test gates need the patches. ~20-40 minutes.
|
||||||
|
|
||||||
|
compDescription="emulator: MAME 0.264 + JoeyLib patches (apple2gs and x68k drivers)"
|
||||||
|
compGroups="emulator:iigs emulator:x68000"
|
||||||
|
compRequires="host"
|
||||||
|
compHostDeps="build-tools make python3 patch sdl2-dev sdl2-ttf-dev fontconfig-dev pulse-dev asound-dev x11-dev xi-dev xext-dev xinerama-dev gl-dev"
|
||||||
|
compUrl="https://github.com/mamedev/mame/archive/refs/tags/mame0264.tar.gz"
|
||||||
|
compSha256="3d9f69ed3ef7c1628d5714c8ae2695ea77b1a652a93347b2703f7c862299376e"
|
||||||
|
patchDir="$JOEY_PATCHES/mame"
|
||||||
|
# Editing a patch rebuilds MAME, so the patches are part of the pin.
|
||||||
|
compPin="$compSha256 $(cat "$patchDir"/*.patch | sha256sum | cut -d' ' -f1)"
|
||||||
|
|
||||||
|
|
||||||
|
compInstall() {
|
||||||
|
local src
|
||||||
|
local archive
|
||||||
|
local p
|
||||||
|
src="$(freshBuildDir "$compId")"
|
||||||
|
archive="$(fetchFile "$compUrl" "$compSha256" mame0264.tar.gz)"
|
||||||
|
extractArchive "$archive" "$src" 1
|
||||||
|
for p in "$patchDir"/*.patch; do
|
||||||
|
info "applying $(basename "$p")"
|
||||||
|
patch -d "$src" -p1 -s -F0 --no-backup-if-mismatch < "$p"
|
||||||
|
done
|
||||||
|
make -C "$src" SUBTARGET=joey SOURCES=src/mame/apple/apple2gs.cpp,src/mame/sharp/x68k.cpp \
|
||||||
|
REGENIE=1 USE_QTDEBUG=0 NOWERROR=1 TOOLS=0 -j"$JOEY_JOBS"
|
||||||
|
install -D -m 755 "$src/joey" "$compDir/bin/mame"
|
||||||
|
dropBuildDir "$compId"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compInstalled() {
|
||||||
|
[ -x "$compDir/bin/mame" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compVerify() {
|
||||||
|
local drivers
|
||||||
|
drivers="$("$compDir/bin/mame" -listfull apple2gs x68000 2>/dev/null)"
|
||||||
|
[[ "$drivers" == *apple2gs* && "$drivers" == *x68000* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compEnv() {
|
||||||
|
envVar MAME "$compDir/bin/mame"
|
||||||
|
}
|
||||||
41
installer/components/merlin32.sh
Normal file
41
installer/components/merlin32.sh
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
# Merlin32 1.1: 65816 assembler, used only for the NinjaTrackerPlus audio
|
||||||
|
# replayer (third-party Merlin source). The release zip carries a prebuilt
|
||||||
|
# x86-64 Linux binary.
|
||||||
|
|
||||||
|
compDescription="IIgs assembler: Merlin32 1.1 (for the NinjaTrackerPlus replayer)"
|
||||||
|
compGroups="toolchain:iigs"
|
||||||
|
compRequires="host"
|
||||||
|
compHostDeps="unzip"
|
||||||
|
compUrl="https://brutaldeluxe.fr/products/crossdevtools/merlin/Merlin32_v1.1.zip"
|
||||||
|
compSha256="6a4ebd09ed8800a3874b15e35b5d16469a8ad08dd477b9f84cbd5ce8832dc108"
|
||||||
|
compPin="$compSha256"
|
||||||
|
|
||||||
|
|
||||||
|
compInstall() {
|
||||||
|
local zip
|
||||||
|
local src
|
||||||
|
zip="$(fetchFile "$compUrl" "$compSha256")"
|
||||||
|
src="$(freshBuildDir "$compId")"
|
||||||
|
extractArchive "$zip" "$src"
|
||||||
|
install -D -m 755 "$src/Merlin32_v1.1/Linux/Merlin32" "$compDir/bin/merlin32"
|
||||||
|
dropBuildDir "$compId"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compInstalled() {
|
||||||
|
[ -x "$compDir/bin/merlin32" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compVerify() {
|
||||||
|
local banner
|
||||||
|
verifyLinked "$compDir/bin"
|
||||||
|
# Run with no arguments it prints its banner and exits non-zero.
|
||||||
|
banner="$("$compDir/bin/merlin32" 2>&1 || true)"
|
||||||
|
[[ "$banner" == *"v 1.1,"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compEnv() {
|
||||||
|
envVar IIGS_AS "$compDir/bin/merlin32"
|
||||||
|
}
|
||||||
52
installer/components/mintGcc.sh
Normal file
52
installer/components/mintGcc.sh
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
# Atari ST C toolchain: Vincent Riviere's m68k-atari-mint gcc 15.2 +
|
||||||
|
# binutils 2.45, with MiNTLib 0.60.1, as prebuilt Linux x86-64 tarballs
|
||||||
|
# from Thorsten Otto's mirror. Each tarball is rooted at usr/, so stripping
|
||||||
|
# one component lays all three into one relocatable prefix.
|
||||||
|
|
||||||
|
compDescription="Atari ST compiler: m68k-atari-mint gcc 15.2 + binutils 2.45 + MiNTLib 0.60.1"
|
||||||
|
compGroups="toolchain:atarist"
|
||||||
|
compRequires="host"
|
||||||
|
compHostDeps="xz"
|
||||||
|
compBase="https://tho-otto.de/download/mint"
|
||||||
|
|
||||||
|
# file sha256
|
||||||
|
tarballs=(
|
||||||
|
"gcc-15.2.0-mint-20250810-bin-linux64.tar.xz 82cce07b1ce3c9f176854e3c5f75ca64ae67d5924da82fc55390d0e59014ae84"
|
||||||
|
"binutils-2.45-mint-20250812-bin-linux64.tar.xz 0f70905ccec202cb6fa7809ce9749c3fa7bda08a80b06e4efed7e5cfd5f58a5e"
|
||||||
|
"mintlib-0.60.1-mint-20240718-dev.tar.xz 60e5205cf11d94eaa024a4f11a39107833add073abdddbe1cb8bf46fc3cd0548"
|
||||||
|
)
|
||||||
|
compPin="${tarballs[*]}"
|
||||||
|
|
||||||
|
|
||||||
|
compInstall() {
|
||||||
|
local entry
|
||||||
|
local name
|
||||||
|
local sha
|
||||||
|
local archive
|
||||||
|
rm -rf "$compDir"
|
||||||
|
for entry in "${tarballs[@]}"; do
|
||||||
|
read -r name sha <<< "$entry"
|
||||||
|
archive="$(fetchFile "$compBase/$name" "$sha")"
|
||||||
|
extractArchive "$archive" "$compDir" 1
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compInstalled() {
|
||||||
|
[ -x "$compDir/bin/m68k-atari-mint-gcc" ] && [ -d "$compDir/m68k-atari-mint/sys-root" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compVerify() {
|
||||||
|
verifyLinked "$compDir/bin" "$compDir/m68k-atari-mint/bin" "$compDir"/lib64/gcc/*/*
|
||||||
|
verifyCompiles "$compDir/bin/m68k-atari-mint-gcc" -m68000 -O2
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compEnv() {
|
||||||
|
envVar ST_PREFIX "$compDir"
|
||||||
|
envVar ST_CC "$compDir/bin/m68k-atari-mint-gcc"
|
||||||
|
envVar ST_AR "$compDir/bin/m68k-atari-mint-ar"
|
||||||
|
envVar ST_LD "$compDir/bin/m68k-atari-mint-ld"
|
||||||
|
envPath "$compDir/bin"
|
||||||
|
}
|
||||||
36
installer/components/nasm.sh
Normal file
36
installer/components/nasm.sh
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
# NASM: assembler for the DOS port's x86 sources. Any 2.x release works,
|
||||||
|
# so it comes from the distribution.
|
||||||
|
|
||||||
|
compDescription="DOS assembler: NASM (distribution package)"
|
||||||
|
compGroups="toolchain:dos"
|
||||||
|
compRequires="host"
|
||||||
|
compHostDeps="nasm"
|
||||||
|
compPin="distro"
|
||||||
|
|
||||||
|
|
||||||
|
compInstall() {
|
||||||
|
command -v nasm >/dev/null || die "nasm is not on PATH after installing host packages"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compInstalled() {
|
||||||
|
command -v nasm >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compVerify() {
|
||||||
|
local tmp
|
||||||
|
local status=1
|
||||||
|
tmp="$(mktemp -d)"
|
||||||
|
printf 'bits 32\nret\n' > "$tmp/t.asm"
|
||||||
|
if nasm -f coff "$tmp/t.asm" -o "$tmp/t.o" && [ -s "$tmp/t.o" ]; then
|
||||||
|
status=0
|
||||||
|
fi
|
||||||
|
rm -rf "$tmp"
|
||||||
|
return $status
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compEnv() {
|
||||||
|
envVar DOS_AS "$(command -v nasm)"
|
||||||
|
}
|
||||||
41
installer/components/ntp.sh
Normal file
41
installer/components/ntp.sh
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
# NinjaTrackerPlus: Ninjaforce's IIgs MOD replayer, shipped as Merlin
|
||||||
|
# source plus the PHP converter tools/joeymod uses for .MOD -> .NTP.
|
||||||
|
# Redistribution is granted by the copyright holder. The URL is not
|
||||||
|
# versioned, so the pin is the archive's checksum.
|
||||||
|
|
||||||
|
compDescription="IIgs audio: NinjaTrackerPlus replayer source + ntpconverter.php"
|
||||||
|
compGroups="toolchain:iigs"
|
||||||
|
compRequires="host"
|
||||||
|
compHostDeps="unzip php-cli"
|
||||||
|
compUrl="https://www.ninjaforce.com/downloads/ntpsources.zip"
|
||||||
|
compSha256="461260e35dc0b10176c05865d01c4e31694f704c0c3a9e5760fc093623dc52fb"
|
||||||
|
compPin="$compSha256"
|
||||||
|
|
||||||
|
|
||||||
|
compInstall() {
|
||||||
|
local zip
|
||||||
|
local s
|
||||||
|
zip="$(fetchFile "$compUrl" "$compSha256")"
|
||||||
|
rm -rf "$compDir"
|
||||||
|
extractArchive "$zip" "$compDir"
|
||||||
|
# The sources ship with CRLF line endings, which Merlin32 rejects as
|
||||||
|
# "Unknown line".
|
||||||
|
for s in "$compDir"/*.s; do
|
||||||
|
sed -i 's/\r$//' "$s"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compInstalled() {
|
||||||
|
[ -f "$compDir/ninjatrackerplus.s" ] && [ -f "$compDir/ntpconverter.php" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compVerify() {
|
||||||
|
! grep -q $'\r' "$compDir/ninjatrackerplus.s"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compEnv() {
|
||||||
|
envVar IIGS_NTP_DIR "$compDir"
|
||||||
|
}
|
||||||
34
installer/components/ptplayer.sh
Normal file
34
installer/components/ptplayer.sh
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
# PTPlayer 6.4: Frank Wille's ProTracker replayer for the Amiga, used by
|
||||||
|
# the Amiga audio HAL. Aminet's URL is not versioned, so the pin is the
|
||||||
|
# archive's checksum.
|
||||||
|
|
||||||
|
compDescription="Amiga audio: PTPlayer 6.4 (ProTracker replayer)"
|
||||||
|
compGroups="toolchain:amiga"
|
||||||
|
compRequires="host"
|
||||||
|
compHostDeps="lhasa"
|
||||||
|
compUrl="http://aminet.net/mus/play/ptplayer.lha"
|
||||||
|
compSha256="5ba1285e91413f6b6cb72768c0c31cff90029af6920870d9f589689aa2e2161a"
|
||||||
|
compPin="$compSha256"
|
||||||
|
|
||||||
|
|
||||||
|
compInstall() {
|
||||||
|
local lha
|
||||||
|
lha="$(fetchFile "$compUrl" "$compSha256")"
|
||||||
|
rm -rf "$compDir"
|
||||||
|
extractArchive "$lha" "$compDir"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compInstalled() {
|
||||||
|
[ -f "$compDir/ptplayer.asm" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compVerify() {
|
||||||
|
grep -qi 'ptplayer' "$compDir/ptplayer.asm"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compEnv() {
|
||||||
|
envVar AMIGA_PTPLAYER "$compDir"
|
||||||
|
}
|
||||||
30
installer/components/staxiData.sh
Normal file
30
installer/components/staxiData.sh
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
# Space Taxi's level source: a memory image of the original Commodore 64
|
||||||
|
# game. Optional -- without it every port still builds, skipping only the
|
||||||
|
# Space Taxi data -- so it is not part of `all`; install it by name.
|
||||||
|
|
||||||
|
compDescription="Space Taxi C64 memory image (private, optional: bundle or import)"
|
||||||
|
compGroups="staxi optional"
|
||||||
|
compRequires=""
|
||||||
|
compHostDeps=""
|
||||||
|
compPrivateFile="stuff/spacetaxi/raw.bin"
|
||||||
|
compPin="$(privateSha1For "$compPrivateFile")"
|
||||||
|
|
||||||
|
|
||||||
|
compInstall() {
|
||||||
|
privateEnsure "$compPrivateFile"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compInstalled() {
|
||||||
|
privatePresent "$compPrivateFile"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compVerify() {
|
||||||
|
privatePresent "$compPrivateFile"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compEnv() {
|
||||||
|
:
|
||||||
|
}
|
||||||
31
installer/components/x68kCgrom.sh
Normal file
31
installer/components/x68kCgrom.sh
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
# X68000 character-generator (font) ROM. Not part of Sharp's free release,
|
||||||
|
# so it comes from the private bundle or `install.sh import`. MAME's x68000
|
||||||
|
# driver will not start without it (it warns about the checksum of this
|
||||||
|
# dump, which is expected).
|
||||||
|
|
||||||
|
compDescription="X68000 firmware: CG (font) ROM (private: bundle or import)"
|
||||||
|
compGroups="emulator:x68000"
|
||||||
|
compRequires=""
|
||||||
|
compHostDeps=""
|
||||||
|
compPrivateFile="toolchains/firmware/x68000/roms/x68000/cgrom.dat"
|
||||||
|
compPin="$(privateSha1For "$compPrivateFile")"
|
||||||
|
|
||||||
|
|
||||||
|
compInstall() {
|
||||||
|
privateEnsure "$compPrivateFile"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compInstalled() {
|
||||||
|
privatePresent "$compPrivateFile"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compVerify() {
|
||||||
|
privatePresent "$compPrivateFile"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compEnv() {
|
||||||
|
:
|
||||||
|
}
|
||||||
42
installer/components/x68kFirmware.sh
Normal file
42
installer/components/x68kFirmware.sh
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
# Sharp X68000 firmware from Sharp's own free release (2000, published by
|
||||||
|
# the X68000 LIBRARY): the original IPL-ROM (all `-bios ipl10` needs) and
|
||||||
|
# the Human68k 3.02 system disk. The CG (font) ROM is not part of that
|
||||||
|
# release; it is the private component x68kCgrom.
|
||||||
|
|
||||||
|
compDescription="X68000 firmware: Sharp's free IPL-ROM + Human68k 3.02 system disk"
|
||||||
|
compGroups="emulator:x68000"
|
||||||
|
compRequires="host"
|
||||||
|
compHostDeps="lhasa"
|
||||||
|
compBase="http://retropc.net/x68000/software/sharp"
|
||||||
|
biosSha256="286564f316e1615c46a23b25536ebd403ab60979f6c4eee21d2287f005a3b309"
|
||||||
|
humanSha256="da68be5c5cbea871ecb5588010a4d3fe52f26c8c686401027cecc0d1a27dca1a"
|
||||||
|
compPin="$biosSha256 $humanSha256"
|
||||||
|
fwDir="$JOEY_FIRMWARE/x68000"
|
||||||
|
|
||||||
|
|
||||||
|
compInstall() {
|
||||||
|
local tmp
|
||||||
|
tmp="$(freshBuildDir "$compId")"
|
||||||
|
extractArchive "$(fetchFile "$compBase/x68bios/X68BIOSE.LZH" "$biosSha256")" "$tmp/bios"
|
||||||
|
extractArchive "$(fetchFile "$compBase/human302/HUMN302I.LZH" "$humanSha256")" "$tmp/human"
|
||||||
|
# Name case depends on the extractor (lhasa lowercases MS-DOS archives).
|
||||||
|
install -D -m 644 "$(find "$tmp/bios" -maxdepth 1 -iname iplrom.dat)" "$fwDir/roms/x68000/iplrom.dat"
|
||||||
|
install -D -m 644 "$(find "$tmp/human" -maxdepth 1 -iname human302.xdf)" "$fwDir/HUMAN302.XDF"
|
||||||
|
dropBuildDir "$compId"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compInstalled() {
|
||||||
|
[ -f "$fwDir/roms/x68000/iplrom.dat" ] && [ -f "$fwDir/HUMAN302.XDF" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compVerify() {
|
||||||
|
[ "$(sha1sum "$fwDir/roms/x68000/iplrom.dat" | cut -d' ' -f1)" = "0ed038ed2133b9f78c6e37256807424e0d927560" ] &&
|
||||||
|
[ "$(sha1sum "$fwDir/HUMAN302.XDF" | cut -d' ' -f1)" = "bdee51e80a9ef28d3c336d79ac80e9749f475608" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compEnv() {
|
||||||
|
envVar X68K_DIR "$fwDir"
|
||||||
|
}
|
||||||
89
installer/distroPackages.txt
Normal file
89
installer/distroPackages.txt
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
# Host packages the components need, by logical name.
|
||||||
|
#
|
||||||
|
# Columns: logical-name apt-packages dnf-packages
|
||||||
|
# Several packages are comma-separated; "-" means the family has no package
|
||||||
|
# for it (the component must then build it itself or cannot be installed).
|
||||||
|
# Components list logical names in compHostDeps; `install.sh deps` maps
|
||||||
|
# them through this table.
|
||||||
|
|
||||||
|
# ---- basics every setup needs
|
||||||
|
build-tools build-essential gcc,gcc-c++,make
|
||||||
|
make make make
|
||||||
|
git git git
|
||||||
|
git-lfs git-lfs git-lfs
|
||||||
|
curl curl,ca-certificates curl,ca-certificates
|
||||||
|
tar tar tar
|
||||||
|
xz xz-utils xz
|
||||||
|
bzip2 bzip2 bzip2
|
||||||
|
unzip unzip unzip
|
||||||
|
wget wget wget
|
||||||
|
lhasa lhasa lhasa
|
||||||
|
python3 python3 python3
|
||||||
|
python3-pil python3-pil python3-pillow
|
||||||
|
php-cli php-cli php-cli
|
||||||
|
patch patch patch
|
||||||
|
|
||||||
|
# ---- runtime libraries the prebuilt toolchains link against
|
||||||
|
libfl libfl2 libfl2
|
||||||
|
# elf2x68k's gcc converts strings to cp932 (Shift-JIS) through iconv;
|
||||||
|
# Debian ships every converter in libc6, Fedora splits most of them off.
|
||||||
|
gconv-cp932 libc6 glibc-gconv-extra
|
||||||
|
|
||||||
|
# ---- GNU toolchain builds (amiga-gcc)
|
||||||
|
bison bison bison
|
||||||
|
flex flex flex
|
||||||
|
m4 m4 m4
|
||||||
|
texinfo texinfo texinfo
|
||||||
|
autoconf autoconf autoconf
|
||||||
|
automake automake automake
|
||||||
|
gettext gettext gettext
|
||||||
|
rsync rsync rsync
|
||||||
|
procps procps procps-ng
|
||||||
|
which debianutils which
|
||||||
|
diffutils diffutils diffutils
|
||||||
|
gmp-dev libgmp-dev gmp-devel
|
||||||
|
mpfr-dev libmpfr-dev mpfr-devel
|
||||||
|
mpc-dev libmpc-dev libmpc-devel
|
||||||
|
ncurses-dev libncurses-dev ncurses-devel
|
||||||
|
readline-dev libreadline-dev readline-devel
|
||||||
|
|
||||||
|
# ---- LLVM builds (llvm816)
|
||||||
|
cmake cmake cmake
|
||||||
|
ninja ninja-build ninja-build
|
||||||
|
lld lld lld
|
||||||
|
zlib-dev zlib1g-dev zlib-devel
|
||||||
|
zstd-dev libzstd-dev libzstd-devel
|
||||||
|
libedit-dev libedit-dev libedit-devel
|
||||||
|
libxml2-dev libxml2-dev libxml2-devel
|
||||||
|
|
||||||
|
# ---- assemblers
|
||||||
|
nasm nasm nasm
|
||||||
|
|
||||||
|
# ---- MAME build
|
||||||
|
sdl2-dev libsdl2-dev SDL2-devel
|
||||||
|
sdl2-ttf-dev libsdl2-ttf-dev SDL2_ttf-devel
|
||||||
|
fontconfig-dev libfontconfig-dev fontconfig-devel
|
||||||
|
pulse-dev libpulse-dev pulseaudio-libs-devel
|
||||||
|
asound-dev libasound2-dev alsa-lib-devel
|
||||||
|
x11-dev libx11-dev libX11-devel
|
||||||
|
xi-dev libxi-dev libXi-devel
|
||||||
|
xext-dev libxext-dev libXext-devel
|
||||||
|
xinerama-dev libxinerama-dev libXinerama-devel
|
||||||
|
gl-dev libgl-dev mesa-libGL-devel
|
||||||
|
|
||||||
|
# ---- emulators packaged by the distribution
|
||||||
|
hatari hatari hatari
|
||||||
|
fs-uae fs-uae -
|
||||||
|
|
||||||
|
# ---- running emulators headless (the test gates)
|
||||||
|
xvfb xvfb xorg-x11-server-Xvfb
|
||||||
|
xdotool xdotool xdotool
|
||||||
|
imagemagick imagemagick ImageMagick
|
||||||
|
pulse-utils pulseaudio-utils pulseaudio-utils
|
||||||
|
|
||||||
|
# ---- FS-UAE source build (dnf systems, which do not package it)
|
||||||
|
pkg-config pkg-config pkgconf-pkg-config
|
||||||
|
zip zip zip
|
||||||
|
glib-dev libglib2.0-dev glib2-devel
|
||||||
|
openal-dev libopenal-dev openal-soft-devel
|
||||||
|
png-dev libpng-dev libpng-devel
|
||||||
9
installer/files.sha1
Normal file
9
installer/files.sha1
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
# Files JoeyLib needs that are not downloaded from any public source. They
|
||||||
|
# come from the project owner's private bundle (JOEY_BUNDLE_URL, set in
|
||||||
|
# installer/bundle.conf) or from `./install.sh import PATH...`,
|
||||||
|
# which finds them by checksum in any files or .zip archives you point it at.
|
||||||
|
#
|
||||||
|
# sha1 destination (repository-relative) what it is
|
||||||
|
3b7f1493b27e212830f989f26ca76c02049f09ca toolchains/firmware/amiga/kickstart.rom Amiga Kickstart 3.1 r40.63 (A500/A600/A2000)
|
||||||
|
8d72c5b4d63bb14c5dbdac495244d659aa1498b6 toolchains/firmware/x68000/roms/x68000/cgrom.dat X68000 character-generator (font) ROM
|
||||||
|
5bfa4ba70431135575dab610a73dde02b8095953 stuff/spacetaxi/raw.bin Space Taxi C64 memory image (level source)
|
||||||
140
installer/lib/bundle.sh
Normal file
140
installer/lib/bundle.sh
Normal file
|
|
@ -0,0 +1,140 @@
|
||||||
|
# Private files: the ones in files.sha1, which are never fetched from a
|
||||||
|
# public source. They arrive from the owner's bundle (JOEY_BUNDLE_URL) or
|
||||||
|
# from `install.sh import`. Sourced after lib/common.sh.
|
||||||
|
|
||||||
|
JOEY_FILES_TABLE="$JOEY_INSTALLER/files.sha1"
|
||||||
|
JOEY_BUNDLE_CONF="$JOEY_INSTALLER/bundle.conf"
|
||||||
|
|
||||||
|
|
||||||
|
# privateTable -- print "sha1 destination" for every private file.
|
||||||
|
privateTable() {
|
||||||
|
awk '!/^#/ && NF >= 2 { print $1, $2 }' "$JOEY_FILES_TABLE"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# privateSha1For DEST -- the checksum files.sha1 expects at DEST.
|
||||||
|
privateSha1For() {
|
||||||
|
awk -v d="$1" '!/^#/ && $2 == d { print $1 }' "$JOEY_FILES_TABLE"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# privatePresent DEST -- true when DEST holds the expected file.
|
||||||
|
privatePresent() {
|
||||||
|
local want
|
||||||
|
want="$(privateSha1For "$1")"
|
||||||
|
[ -n "$want" ] || die "no files.sha1 entry for $1"
|
||||||
|
[ -f "$JOEY_REPO/$1" ] && [ "$(sha1sum "$JOEY_REPO/$1" | cut -d' ' -f1)" = "$want" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# importOneFile FILE -- install FILE if its checksum is a private file's.
|
||||||
|
# Prints the destination it was installed to.
|
||||||
|
importOneFile() {
|
||||||
|
local sum
|
||||||
|
local dest
|
||||||
|
sum="$(sha1sum "$1" | cut -d' ' -f1)"
|
||||||
|
dest="$(privateTable | awk -v s="$sum" '$1 == s { print $2 }')"
|
||||||
|
[ -n "$dest" ] || return 0
|
||||||
|
# Importing a file that is already in place is a no-op, not an error.
|
||||||
|
if [ "$(realpath "$1")" != "$(realpath -m "$JOEY_REPO/$dest")" ]; then
|
||||||
|
install -D -m 644 "$1" "$JOEY_REPO/$dest"
|
||||||
|
fi
|
||||||
|
printf '%s\n' "$dest"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# importPaths PATH... -- scan files, directories and .zip archives and
|
||||||
|
# install every private file found. Prints what was installed.
|
||||||
|
importPaths() {
|
||||||
|
local path
|
||||||
|
local file
|
||||||
|
local tmp
|
||||||
|
tmp="$(mktemp -d)"
|
||||||
|
for path in "$@"; do
|
||||||
|
[ -e "$path" ] || die "no such file or directory: $path"
|
||||||
|
while IFS= read -r -d '' file; do
|
||||||
|
case "$file" in
|
||||||
|
*.zip|*.ZIP)
|
||||||
|
rm -rf "$tmp/zip"
|
||||||
|
mkdir -p "$tmp/zip"
|
||||||
|
if unzip -qq -o "$file" -d "$tmp/zip" 2>/dev/null; then
|
||||||
|
find "$tmp/zip" -type f -print0 | while IFS= read -r -d '' inner; do
|
||||||
|
importOneFile "$inner"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
importOneFile "$file"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done < <(find "$path" -type f -print0)
|
||||||
|
done
|
||||||
|
rm -rf "$tmp"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# bundleUrl -- the configured bundle URL, or nothing.
|
||||||
|
bundleUrl() {
|
||||||
|
if [ -n "${JOEY_BUNDLE_URL:-}" ]; then
|
||||||
|
printf '%s\n' "$JOEY_BUNDLE_URL"
|
||||||
|
elif [ -f "$JOEY_BUNDLE_CONF" ]; then
|
||||||
|
(. "$JOEY_BUNDLE_CONF" && printf '%s\n' "${JOEY_BUNDLE_URL:-}")
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# bundleFetch -- download the owner's bundle (if one is configured) and
|
||||||
|
# import what it contains. Its checksum is not pinned -- the owner updates
|
||||||
|
# it -- but every file taken from it is checked against files.sha1.
|
||||||
|
bundleFetch() {
|
||||||
|
local url
|
||||||
|
local tmp
|
||||||
|
url="$(bundleUrl)"
|
||||||
|
[ -n "$url" ] || return 1
|
||||||
|
tmp="$(mktemp -d)"
|
||||||
|
info "fetching the private bundle"
|
||||||
|
curl -fL --retry 3 --connect-timeout 30 --progress-bar -o "$tmp/bundle.tar.gz" "$url" || { rm -rf "$tmp"; die "could not download the private bundle from $url"; }
|
||||||
|
mkdir -p "$tmp/x"
|
||||||
|
tar -xzf "$tmp/bundle.tar.gz" -C "$tmp/x" || { rm -rf "$tmp"; die "the private bundle is not a .tar.gz"; }
|
||||||
|
importPaths "$tmp/x" >/dev/null
|
||||||
|
rm -rf "$tmp"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# privateEnsure DEST -- make DEST present, from the bundle if configured,
|
||||||
|
# or stop and explain how to supply it.
|
||||||
|
privateEnsure() {
|
||||||
|
local what
|
||||||
|
privatePresent "$1" && return 0
|
||||||
|
bundleFetch || true
|
||||||
|
privatePresent "$1" && return 0
|
||||||
|
what="$(awk -v d="$1" '!/^#/ && $2 == d { $1 = ""; $2 = ""; sub(/^ +/, ""); print }' "$JOEY_FILES_TABLE")"
|
||||||
|
die "$1 is missing: $what (sha1 $(privateSha1For "$1")).
|
||||||
|
This file is not downloaded from public sources. Supply your copy with
|
||||||
|
./install.sh import PATH (files, folders or .zip archives)
|
||||||
|
or set JOEY_BUNDLE_URL in installer/bundle.conf to your private bundle."
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# bundleCreate OUTFILE -- pack every private file present into OUTFILE
|
||||||
|
# (a .tar.gz) for the owner to host.
|
||||||
|
bundleCreate() {
|
||||||
|
local out="$1"
|
||||||
|
local dest
|
||||||
|
local sum
|
||||||
|
local tmp
|
||||||
|
local count=0
|
||||||
|
tmp="$(mktemp -d)"
|
||||||
|
while read -r sum dest; do
|
||||||
|
if privatePresent "$dest"; then
|
||||||
|
install -D -m 644 "$JOEY_REPO/$dest" "$tmp/$dest"
|
||||||
|
count=$((count + 1))
|
||||||
|
else
|
||||||
|
warn "not bundled (not present here): $dest"
|
||||||
|
fi
|
||||||
|
done < <(privateTable)
|
||||||
|
[ "$count" -gt 0 ] || { rm -rf "$tmp"; die "none of the private files are present to bundle"; }
|
||||||
|
tar -czf "$out" -C "$tmp" .
|
||||||
|
rm -rf "$tmp"
|
||||||
|
ok "bundled $count private files into $out"
|
||||||
|
}
|
||||||
339
installer/lib/common.sh
Normal file
339
installer/lib/common.sh
Normal file
|
|
@ -0,0 +1,339 @@
|
||||||
|
# Shared helpers for install.sh and the component files in
|
||||||
|
# installer/components/. Sourced, not executed.
|
||||||
|
#
|
||||||
|
# Tracked (the recipes):
|
||||||
|
# install.sh the command line
|
||||||
|
# installer/lib/ these helpers
|
||||||
|
# installer/components/<id> one recipe per component
|
||||||
|
# installer/patches/ patches applied to sources the installer builds
|
||||||
|
# make/support/ build helper scripts the makefiles call
|
||||||
|
# Generated (everything under toolchains/, which can be deleted at any time
|
||||||
|
# and re-created by install.sh):
|
||||||
|
# toolchains/cache/downloads/ sha256-verified downloads
|
||||||
|
# toolchains/cache/build/<id>/ scratch build trees, deleted after install
|
||||||
|
# toolchains/opt/<id>/ installed components
|
||||||
|
# toolchains/firmware/ ROM / OS images for the emulators
|
||||||
|
# toolchains/state/ stamps and settings
|
||||||
|
# toolchains/env.sh, config.mk the tool paths, for shells and for make
|
||||||
|
|
||||||
|
JOEY_REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
JOEY_INSTALLER="$JOEY_REPO/installer"
|
||||||
|
JOEY_COMPONENTS="$JOEY_INSTALLER/components"
|
||||||
|
JOEY_PATCHES="$JOEY_INSTALLER/patches"
|
||||||
|
JOEY_BUILD_SUPPORT="$JOEY_REPO/make/support"
|
||||||
|
JOEY_TOOLCHAINS="$JOEY_REPO/toolchains"
|
||||||
|
JOEY_CACHE="$JOEY_TOOLCHAINS/cache/downloads"
|
||||||
|
JOEY_BUILD_ROOT="$JOEY_TOOLCHAINS/cache/build"
|
||||||
|
JOEY_OPT="$JOEY_TOOLCHAINS/opt"
|
||||||
|
JOEY_FIRMWARE="$JOEY_TOOLCHAINS/firmware"
|
||||||
|
JOEY_STATE="$JOEY_TOOLCHAINS/state"
|
||||||
|
JOEY_STAMPS="$JOEY_STATE/stamps"
|
||||||
|
|
||||||
|
JOEY_JOBS="${JOEY_JOBS:-$(nproc 2>/dev/null || echo 2)}"
|
||||||
|
JOEY_KEEP_BUILD="${JOEY_KEEP_BUILD:-0}"
|
||||||
|
JOEY_ASSUME_YES="${JOEY_ASSUME_YES:-0}"
|
||||||
|
JOEY_FORCE="${JOEY_FORCE:-0}"
|
||||||
|
|
||||||
|
# Never prompt for credentials: a missing or private repo must fail fast
|
||||||
|
# with a clear message instead of hanging on a username prompt.
|
||||||
|
export GIT_TERMINAL_PROMPT=0
|
||||||
|
|
||||||
|
if [ -t 1 ]; then
|
||||||
|
JOEY_C_RESET=$'\033[0m'
|
||||||
|
JOEY_C_BOLD=$'\033[1m'
|
||||||
|
JOEY_C_RED=$'\033[31m'
|
||||||
|
JOEY_C_GREEN=$'\033[32m'
|
||||||
|
JOEY_C_YELLOW=$'\033[33m'
|
||||||
|
JOEY_C_CYAN=$'\033[36m'
|
||||||
|
else
|
||||||
|
JOEY_C_RESET=""
|
||||||
|
JOEY_C_BOLD=""
|
||||||
|
JOEY_C_RED=""
|
||||||
|
JOEY_C_GREEN=""
|
||||||
|
JOEY_C_YELLOW=""
|
||||||
|
JOEY_C_CYAN=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
info() {
|
||||||
|
printf '%s[*]%s %s\n' "$JOEY_C_CYAN" "$JOEY_C_RESET" "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ok() {
|
||||||
|
printf '%s[ok]%s %s\n' "$JOEY_C_GREEN" "$JOEY_C_RESET" "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
warn() {
|
||||||
|
printf '%s[!!]%s %s\n' "$JOEY_C_YELLOW" "$JOEY_C_RESET" "$*" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
die() {
|
||||||
|
printf '%s[xx]%s %s\n' "$JOEY_C_RED" "$JOEY_C_RESET" "$*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
header() {
|
||||||
|
printf '\n%s== %s ==%s\n' "$JOEY_C_BOLD" "$*" "$JOEY_C_RESET"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sha256Of() {
|
||||||
|
sha256sum "$1" | cut -d' ' -f1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# fetchFile URL SHA256 [NAME] -- download URL into the cache as NAME
|
||||||
|
# (default: the URL's basename), verify it, and print its path. A cached
|
||||||
|
# file is reused only if its checksum still matches; downloads go to a
|
||||||
|
# .part file first, so an interrupted fetch is never mistaken for a
|
||||||
|
# complete one.
|
||||||
|
fetchFile() {
|
||||||
|
local url="$1"
|
||||||
|
local want="$2"
|
||||||
|
local name="${3:-$(basename "$url")}"
|
||||||
|
local dest="$JOEY_CACHE/$name"
|
||||||
|
local got
|
||||||
|
mkdir -p "$JOEY_CACHE"
|
||||||
|
if [ -f "$dest" ]; then
|
||||||
|
got="$(sha256Of "$dest")"
|
||||||
|
if [ "$got" = "$want" ]; then
|
||||||
|
printf '%s\n' "$dest"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
warn "cached $name has the wrong checksum; downloading again"
|
||||||
|
rm -f "$dest"
|
||||||
|
fi
|
||||||
|
info "downloading $name" >&2
|
||||||
|
curl -fL --retry 3 --connect-timeout 30 --progress-bar -o "$dest.part" "$url" \
|
||||||
|
|| die "download failed: $url"
|
||||||
|
got="$(sha256Of "$dest.part")"
|
||||||
|
if [ "$got" != "$want" ]; then
|
||||||
|
rm -f "$dest.part"
|
||||||
|
die "checksum mismatch for $url
|
||||||
|
expected $want
|
||||||
|
got $got
|
||||||
|
The upstream file changed. Check it, then update the pin in the component file."
|
||||||
|
fi
|
||||||
|
mv "$dest.part" "$dest"
|
||||||
|
printf '%s\n' "$dest"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# gitFetchCommit URL SHA DIR -- make DIR a shallow checkout of exactly SHA.
|
||||||
|
# Reuses an existing clone when it can.
|
||||||
|
gitFetchCommit() {
|
||||||
|
local url="$1"
|
||||||
|
local sha="$2"
|
||||||
|
local dir="$3"
|
||||||
|
if [ -d "$dir/.git" ] && [ "$(git -C "$dir" rev-parse HEAD 2>/dev/null)" = "$sha" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [ ! -d "$dir/.git" ]; then
|
||||||
|
rm -rf "$dir"
|
||||||
|
mkdir -p "$dir"
|
||||||
|
git -C "$dir" init -q
|
||||||
|
git -C "$dir" remote add origin "$url"
|
||||||
|
else
|
||||||
|
git -C "$dir" remote set-url origin "$url"
|
||||||
|
fi
|
||||||
|
git -C "$dir" fetch -q --depth=1 origin "$sha" || die "could not fetch $sha from $url"
|
||||||
|
git -C "$dir" checkout -q -f --detach FETCH_HEAD
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# lhaCommand -- the LHA extractor: the lhasa package installs it as lhasa
|
||||||
|
# on Debian and Ubuntu but as lha on Fedora.
|
||||||
|
lhaCommand() {
|
||||||
|
command -v lhasa || command -v lha || die "no lhasa or lha command (install the lhasa package)"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# extractArchive ARCHIVE DEST [STRIP] -- unpack ARCHIVE into DEST,
|
||||||
|
# dropping STRIP leading path components (tar formats only).
|
||||||
|
extractArchive() {
|
||||||
|
local archive="$1"
|
||||||
|
local dest="$2"
|
||||||
|
local strip="${3:-0}"
|
||||||
|
mkdir -p "$dest"
|
||||||
|
case "$archive" in
|
||||||
|
*.tar.gz|*.tgz) tar -xzf "$archive" -C "$dest" --strip-components="$strip" ;;
|
||||||
|
*.tar.bz2) tar -xjf "$archive" -C "$dest" --strip-components="$strip" ;;
|
||||||
|
*.tar.xz) tar -xJf "$archive" -C "$dest" --strip-components="$strip" ;;
|
||||||
|
*.zip) unzip -oq "$archive" -d "$dest" ;;
|
||||||
|
*.lha|*.lzh|*.LZH) (cd "$dest" && "$(lhaCommand)" xq "$archive") ;;
|
||||||
|
*) die "don't know how to extract $archive" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# freshBuildDir ID -- print an empty scratch directory for building ID.
|
||||||
|
freshBuildDir() {
|
||||||
|
local dir="$JOEY_BUILD_ROOT/$1"
|
||||||
|
rm -rf "$dir"
|
||||||
|
mkdir -p "$dir"
|
||||||
|
printf '%s\n' "$dir"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# dropBuildDir ID -- delete ID's scratch tree unless --keep-build.
|
||||||
|
dropBuildDir() {
|
||||||
|
if [ "$JOEY_KEEP_BUILD" = "1" ]; then
|
||||||
|
info "keeping build tree $JOEY_BUILD_ROOT/$1"
|
||||||
|
else
|
||||||
|
rm -rf "${JOEY_BUILD_ROOT:?}/$1"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# verifyCompiles CC [FLAGS...] -- compile a trivial C file to an object;
|
||||||
|
# the smoke test the cross-compiler components share.
|
||||||
|
verifyCompiles() {
|
||||||
|
local tmp
|
||||||
|
local status=1
|
||||||
|
tmp="$(mktemp -d)"
|
||||||
|
printf 'int answer(int x) { return x * 42; }\n' > "$tmp/t.c"
|
||||||
|
if "$@" -c "$tmp/t.c" -o "$tmp/t.o" && [ -s "$tmp/t.o" ]; then
|
||||||
|
status=0
|
||||||
|
fi
|
||||||
|
rm -rf "$tmp"
|
||||||
|
return $status
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# verifyLinked DIR... -- every dynamically linked executable directly in
|
||||||
|
# the DIRs finds its shared libraries. A prebuilt toolchain can need one the
|
||||||
|
# host lacks (djgpp's ar wants the flex runtime, libfl.so.2), and a
|
||||||
|
# compile-only smoke test never runs ar.
|
||||||
|
verifyLinked() {
|
||||||
|
local dir
|
||||||
|
local f
|
||||||
|
local out
|
||||||
|
local line
|
||||||
|
local status=0
|
||||||
|
for dir in "$@"; do
|
||||||
|
for f in "$dir"/*; do
|
||||||
|
[ -f "$f" ] && [ -x "$f" ] || continue
|
||||||
|
# Scripts and static binaries are not dynamic executables.
|
||||||
|
out="$(ldd "$f" 2>/dev/null)" || continue
|
||||||
|
while IFS= read -r line; do
|
||||||
|
if [[ "$line" == *"not found"* ]]; then
|
||||||
|
warn "${f#"$JOEY_REPO"/} needs ${line#"${line%%[![:space:]]*}"}"
|
||||||
|
status=1
|
||||||
|
fi
|
||||||
|
done <<< "$out"
|
||||||
|
done
|
||||||
|
done
|
||||||
|
return $status
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ---- stamps ---------------------------------------------------------------
|
||||||
|
# A stamp records the pin a component was installed from, so moving the pin
|
||||||
|
# in the component file makes the component out of date.
|
||||||
|
|
||||||
|
stampRead() {
|
||||||
|
cat "$JOEY_STAMPS/$1" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
stampWrite() {
|
||||||
|
mkdir -p "$JOEY_STAMPS"
|
||||||
|
printf '%s\n' "$2" > "$JOEY_STAMPS/$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
stampClear() {
|
||||||
|
rm -f "$JOEY_STAMPS/$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ---- components -------------------------------------------------------------
|
||||||
|
# Every component file defines:
|
||||||
|
# compDescription one line for `list`
|
||||||
|
# compGroups groups it belongs to, e.g. "toolchain:amiga"
|
||||||
|
# compRequires components that must be installed first
|
||||||
|
# compHostDeps logical host package names (see distroPackages.txt)
|
||||||
|
# compPin anything that changes when the component must be rebuilt
|
||||||
|
# compInstall installs into $compDir
|
||||||
|
# compInstalled cheap check that the install is present
|
||||||
|
# compVerify smoke test for `doctor`
|
||||||
|
# compEnv emits the variables it provides (envVar / envPath)
|
||||||
|
# and a private-file component (lib/bundle.sh) also sets compPrivateFile.
|
||||||
|
# The file is always sourced in a subshell, so components can use the same
|
||||||
|
# function names without colliding.
|
||||||
|
|
||||||
|
componentIds() {
|
||||||
|
local f
|
||||||
|
for f in "$JOEY_COMPONENTS"/*.sh; do
|
||||||
|
basename "$f" .sh
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
componentFile() {
|
||||||
|
local file="$JOEY_COMPONENTS/$1.sh"
|
||||||
|
[ -f "$file" ] || die "unknown component: $1"
|
||||||
|
printf '%s\n' "$file"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# compQuery ID VAR -- print a component file's metadata variable.
|
||||||
|
compQuery() {
|
||||||
|
local file
|
||||||
|
file="$(componentFile "$1")"
|
||||||
|
(
|
||||||
|
compId="$1"
|
||||||
|
compDir="$JOEY_OPT/$1"
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$file"
|
||||||
|
printf '%s\n' "${!2:-}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# compRun ID FUNCTION [ARGS...] -- run one of a component's functions.
|
||||||
|
# It runs in a separate bash PROCESS, not a subshell: callers check the
|
||||||
|
# result (compRun ... || die), and bash disables set -e inside anything
|
||||||
|
# whose status is being checked, subshells included -- a failing step in
|
||||||
|
# the middle of compInstall would then be skipped silently.
|
||||||
|
compRun() {
|
||||||
|
local file
|
||||||
|
file="$(componentFile "$1")"
|
||||||
|
JOEY_JOBS="$JOEY_JOBS" JOEY_KEEP_BUILD="$JOEY_KEEP_BUILD" \
|
||||||
|
JOEY_ASSUME_YES="$JOEY_ASSUME_YES" JOEY_FORCE="$JOEY_FORCE" \
|
||||||
|
bash -c '
|
||||||
|
set -euo pipefail
|
||||||
|
source "$1/common.sh"
|
||||||
|
source "$1/distro.sh"
|
||||||
|
source "$1/bundle.sh"
|
||||||
|
compId="$2"
|
||||||
|
compDir="$JOEY_OPT/$2"
|
||||||
|
mkdir -p "$JOEY_OPT"
|
||||||
|
source "$3"
|
||||||
|
shift 3
|
||||||
|
"$@"
|
||||||
|
' compRun "$JOEY_INSTALLER/lib" "$1" "$file" "${@:2}"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# compIsCurrent ID -- installed from the current pin and still present.
|
||||||
|
compIsCurrent() {
|
||||||
|
[ -n "$(stampRead "$1")" ] || return 1
|
||||||
|
[ "$(stampRead "$1")" = "$(compQuery "$1" compPin)" ] || return 1
|
||||||
|
compRun "$1" compInstalled >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Used inside compEnv to publish a variable / a PATH directory.
|
||||||
|
envVar() {
|
||||||
|
printf 'var\t%s\t%s\n' "$1" "$2"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
envPath() {
|
||||||
|
printf 'path\t%s\n' "$1"
|
||||||
|
}
|
||||||
61
installer/lib/config.sh
Normal file
61
installer/lib/config.sh
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
# Generates toolchains/env.sh (for shells and scripts) and
|
||||||
|
# toolchains/config.mk (for make) from the compEnv output of every installed
|
||||||
|
# component, so both see exactly the same tool paths. Sourced after
|
||||||
|
# lib/common.sh.
|
||||||
|
|
||||||
|
|
||||||
|
# writeConfig -- regenerate both files from the installed components.
|
||||||
|
writeConfig() {
|
||||||
|
local id
|
||||||
|
local kind
|
||||||
|
local name
|
||||||
|
local value
|
||||||
|
local vars=()
|
||||||
|
local paths=()
|
||||||
|
local envTmp
|
||||||
|
local mkTmp
|
||||||
|
|
||||||
|
case "$JOEY_REPO" in
|
||||||
|
*" "*) die "the repository path contains a space ($JOEY_REPO); make cannot build from there" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
for id in $(componentIds); do
|
||||||
|
[ -n "$(stampRead "$id")" ] || continue
|
||||||
|
while IFS=$'\t' read -r kind name value; do
|
||||||
|
case "$kind" in
|
||||||
|
var) vars+=("$name=$value") ;;
|
||||||
|
path) paths+=("$name") ;;
|
||||||
|
esac
|
||||||
|
done < <(compRun "$id" compEnv)
|
||||||
|
done
|
||||||
|
|
||||||
|
mkdir -p "$JOEY_TOOLCHAINS"
|
||||||
|
envTmp="$(mktemp "$JOEY_TOOLCHAINS/env.sh.XXXXXX")"
|
||||||
|
mkTmp="$(mktemp "$JOEY_TOOLCHAINS/config.mk.XXXXXX")"
|
||||||
|
{
|
||||||
|
printf '# Generated by install.sh -- do not edit. For a shell:\n'
|
||||||
|
printf '# source toolchains/env.sh\n'
|
||||||
|
printf "export JOEY_TOOLCHAINS='%s'\n" "$JOEY_TOOLCHAINS"
|
||||||
|
for value in "${vars[@]}"; do
|
||||||
|
printf "export %s='%s'\n" "${value%%=*}" "${value#*=}"
|
||||||
|
done
|
||||||
|
# Prepend each directory once, even when this file is sourced again.
|
||||||
|
for value in "${paths[@]}"; do
|
||||||
|
printf 'case ":$PATH:" in *":%s:"*) ;; *) PATH='"'"'%s'"'"':"$PATH" ;; esac\n' "$value" "$value"
|
||||||
|
done
|
||||||
|
printf 'export PATH\n'
|
||||||
|
} > "$envTmp"
|
||||||
|
{
|
||||||
|
printf '# Generated by install.sh -- do not edit. make reads it by itself.\n'
|
||||||
|
printf 'export JOEY_TOOLCHAINS := %s\n' "$JOEY_TOOLCHAINS"
|
||||||
|
for value in "${vars[@]}"; do
|
||||||
|
printf 'export %s := %s\n' "${value%%=*}" "${value#*=}"
|
||||||
|
done
|
||||||
|
if [ ${#paths[@]} -gt 0 ]; then
|
||||||
|
printf 'export PATH := %s:$(PATH)\n' "$(IFS=:; printf '%s' "${paths[*]}")"
|
||||||
|
fi
|
||||||
|
} > "$mkTmp"
|
||||||
|
chmod 644 "$envTmp" "$mkTmp"
|
||||||
|
mv "$envTmp" "$JOEY_TOOLCHAINS/env.sh"
|
||||||
|
mv "$mkTmp" "$JOEY_TOOLCHAINS/config.mk"
|
||||||
|
}
|
||||||
103
installer/lib/distro.sh
Normal file
103
installer/lib/distro.sh
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
# Host package handling: maps the logical names in distroPackages.txt to
|
||||||
|
# apt or dnf packages, reports which are missing, and installs them.
|
||||||
|
# Sourced after lib/common.sh.
|
||||||
|
|
||||||
|
JOEY_PACKAGE_MAP="$JOEY_INSTALLER/distroPackages.txt"
|
||||||
|
|
||||||
|
|
||||||
|
# distroFamily -- print apt, dnf or unknown.
|
||||||
|
distroFamily() {
|
||||||
|
local id=""
|
||||||
|
local like=""
|
||||||
|
if [ -r /etc/os-release ]; then
|
||||||
|
id="$(. /etc/os-release && printf '%s' "${ID:-}")"
|
||||||
|
like="$(. /etc/os-release && printf '%s' "${ID_LIKE:-}")"
|
||||||
|
fi
|
||||||
|
case " $id $like " in
|
||||||
|
*" debian "*|*" ubuntu "*) printf 'apt\n' ;;
|
||||||
|
*" fedora "*|*" rhel "*|*" centos "*|*" rocky "*) printf 'dnf\n' ;;
|
||||||
|
*) printf 'unknown\n' ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# distroPackageNames LOGICAL... -- print the real package names, one per line.
|
||||||
|
distroPackageNames() {
|
||||||
|
local family
|
||||||
|
local column
|
||||||
|
local name
|
||||||
|
local row
|
||||||
|
family="$(distroFamily)"
|
||||||
|
case "$family" in
|
||||||
|
apt) column=2 ;;
|
||||||
|
dnf) column=3 ;;
|
||||||
|
*) die "unsupported Linux distribution (need apt or dnf); install host packages by hand: $*" ;;
|
||||||
|
esac
|
||||||
|
for name in "$@"; do
|
||||||
|
row="$(awk -v n="$name" -v c="$column" '$1 == n { print $c }' "$JOEY_PACKAGE_MAP")"
|
||||||
|
[ -n "$row" ] || die "no entry for host dependency '$name' in distroPackages.txt"
|
||||||
|
[ "$row" != "-" ] || die "host dependency '$name' has no $family package"
|
||||||
|
printf '%s\n' "${row//,/$'\n'}"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# distroMissing PACKAGE... -- print the packages that are not installed.
|
||||||
|
distroMissing() {
|
||||||
|
local family
|
||||||
|
local pkg
|
||||||
|
local native=""
|
||||||
|
family="$(distroFamily)"
|
||||||
|
[ "$family" = "apt" ] && native="$(dpkg --print-architecture)"
|
||||||
|
for pkg in "$@"; do
|
||||||
|
case "$family" in
|
||||||
|
apt)
|
||||||
|
aptInstalled "$pkg" "$native" || printf '%s\n' "$pkg"
|
||||||
|
;;
|
||||||
|
dnf)
|
||||||
|
rpm -q "$pkg" >/dev/null 2>&1 || printf '%s\n' "$pkg"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# aptInstalled PACKAGE NATIVE-ARCH -- PACKAGE is installed for this machine.
|
||||||
|
# dpkg-query prints one line per architecture present (libc6 on a system
|
||||||
|
# with i386 multiarch has two), so look for a native or arch-all line.
|
||||||
|
aptInstalled() {
|
||||||
|
local arch
|
||||||
|
local status
|
||||||
|
while read -r arch status; do
|
||||||
|
if [ "$status" = "installed" ] && { [ "$arch" = "$2" ] || [ "$arch" = "all" ]; }; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done <<< "$(dpkg-query -W -f='${Architecture} ${db:Status-Status}\n' "$1" 2>/dev/null || true)"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# distroInstall PACKAGE... -- install with one sudo call (none when already
|
||||||
|
# root, as in containers and CI, which often have no sudo at all).
|
||||||
|
distroInstall() {
|
||||||
|
local family
|
||||||
|
local yes=()
|
||||||
|
local asRoot=()
|
||||||
|
[ $# -gt 0 ] || return 0
|
||||||
|
family="$(distroFamily)"
|
||||||
|
[ "$JOEY_ASSUME_YES" = "1" ] && yes=(-y)
|
||||||
|
[ "$(id -u)" = "0" ] || asRoot=(sudo)
|
||||||
|
info "installing host packages: $*"
|
||||||
|
case "$family" in
|
||||||
|
apt)
|
||||||
|
"${asRoot[@]}" apt-get update
|
||||||
|
"${asRoot[@]}" env DEBIAN_FRONTEND=noninteractive apt-get install "${yes[@]}" --no-install-recommends "$@"
|
||||||
|
;;
|
||||||
|
dnf)
|
||||||
|
"${asRoot[@]}" dnf install "${yes[@]}" "$@"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
die "unsupported Linux distribution; install by hand: $*"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
--- /tmp/apple2gs.cpp.orig 2026-08-13 15:22:16.445448309 -0500
|
--- a/src/mame/apple/apple2gs.cpp
|
||||||
+++ toolchains/cache/mame-mame0264/src/mame/apple/apple2gs.cpp 2026-08-13 15:22:16.497448131 -0500
|
+++ b/src/mame/apple/apple2gs.cpp
|
||||||
@@ -3839,8 +3839,13 @@
|
@@ -3839,8 +3839,13 @@
|
||||||
ADDRESS_MAP_BANK(config, A2GS_C300_TAG).set_map(&apple2gs_state::c300bank_map).set_options(ENDIANNESS_LITTLE, 8, 32, 0x100);
|
ADDRESS_MAP_BANK(config, A2GS_C300_TAG).set_map(&apple2gs_state::c300bank_map).set_options(ENDIANNESS_LITTLE, 8, 32, 0x100);
|
||||||
|
|
||||||
26
installer/patches/mame/0002-z80scc-rx-fifo.patch
Normal file
26
installer/patches/mame/0002-z80scc-rx-fifo.patch
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
--- a/src/devices/machine/z80scc.cpp
|
||||||
|
+++ b/src/devices/machine/z80scc.cpp
|
||||||
|
@@ -1045,7 +1045,7 @@
|
||||||
|
|
||||||
|
m_uart->m_wr0_ptrbits = 0;
|
||||||
|
|
||||||
|
- m_rx_fifo_sz = (m_uart->m_variant & z80scc_device::SET_ESCC) ? 8 : 3;
|
||||||
|
+ m_rx_fifo_sz = 512; // RetroNet patch: see z80scc.h - emulation-side depth for polled receivers
|
||||||
|
|
||||||
|
m_tx_fifo_sz = (m_uart->m_variant & z80scc_device::SET_ESCC) ? 4 : 1;
|
||||||
|
|
||||||
|
--- a/src/devices/machine/z80scc.h
|
||||||
|
+++ b/src/devices/machine/z80scc.h
|
||||||
|
@@ -256,8 +256,10 @@
|
||||||
|
unsigned int m_delayed_tx_brg_change;
|
||||||
|
|
||||||
|
// receiver state
|
||||||
|
- uint8_t m_rx_data_fifo[8]; // receive data FIFO
|
||||||
|
- uint8_t m_rx_error_fifo[8]; // receive error FIFO
|
||||||
|
+ // RetroNet patch: widened from 8 so a POLLED receiver (no RX interrupt) survives its deaf
|
||||||
|
+ // windows under emulation; real silicon depth is 3/8 and real clients need interrupt RX.
|
||||||
|
+ uint8_t m_rx_data_fifo[512]; // receive data FIFO
|
||||||
|
+ uint8_t m_rx_error_fifo[512]; // receive error FIFO
|
||||||
|
uint8_t m_rx_error; // current receive error
|
||||||
|
//int m_rx_fifo // receive FIFO pointer
|
||||||
|
int m_rx_fifo_rp; // receive FIFO read pointer
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
--- a/src/mame/sharp/x68k_crtc.h 2026-08-04 16:07:13.182675756 -0500
|
--- a/src/mame/sharp/x68k_crtc.h
|
||||||
+++ b/src/mame/sharp/x68k_crtc.h 2026-08-04 16:32:58.889623301 -0500
|
+++ b/src/mame/sharp/x68k_crtc.h
|
||||||
@@ -61,7 +61,7 @@
|
@@ -61,7 +61,7 @@
|
||||||
private:
|
private:
|
||||||
// internal helpers
|
// internal helpers
|
||||||
|
|
@ -30,8 +30,8 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
class vinas_device : public x68k_crtc_device
|
class vinas_device : public x68k_crtc_device
|
||||||
--- a/src/mame/sharp/x68k_crtc.cpp 2026-08-04 16:07:13.181675761 -0500
|
--- a/src/mame/sharp/x68k_crtc.cpp
|
||||||
+++ b/src/mame/sharp/x68k_crtc.cpp 2026-08-04 16:32:58.889623301 -0500
|
+++ b/src/mame/sharp/x68k_crtc.cpp
|
||||||
@@ -25,6 +25,8 @@
|
@@ -25,6 +25,8 @@
|
||||||
, m_clock_69m(0)
|
, m_clock_69m(0)
|
||||||
, m_clock_50m(0)
|
, m_clock_50m(0)
|
||||||
|
|
@ -1,19 +1,108 @@
|
||||||
From ab6b6fc9e7971b367ec54d565cf10f04e71109ec Mon Sep 17 00:00:00 2001
|
--- a/src/mame/sharp/x68k.h
|
||||||
From: Patrick Mackinlay <pmackinlay@hotmail.com>
|
+++ b/src/mame/sharp/x68k.h
|
||||||
Date: Tue, 14 Jan 2025 13:53:33 +0700
|
@@ -20,7 +20,7 @@
|
||||||
Subject: [PATCH] x68k: add serial mouse
|
#include "cpu/m68000/m68000.h"
|
||||||
|
#include "cpu/m68000/m68030.h"
|
||||||
---
|
#include "imagedev/floppy.h"
|
||||||
src/mame/sharp/x68k.cpp | 169 ++++------------------------------
|
-#include "machine/8530scc.h"
|
||||||
src/mame/sharp/x68k.h | 24 +----
|
+#include "machine/z80scc.h"
|
||||||
src/mame/sharp/x68k_mouse.cpp | 103 +++++++++++++++++++++
|
#include "machine/hd63450.h"
|
||||||
src/mame/sharp/x68k_mouse.h | 36 ++++++++
|
#include "machine/i8255.h"
|
||||||
4 files changed, 158 insertions(+), 174 deletions(-)
|
#include "machine/mb87030.h"
|
||||||
create mode 100644 src/mame/sharp/x68k_mouse.cpp
|
@@ -64,9 +64,6 @@
|
||||||
create mode 100644 src/mame/sharp/x68k_mouse.h
|
, m_expansion(*this, "exp%u", 1U)
|
||||||
|
, m_adpcm_out(*this, {"adpcm_outl", "adpcm_outr"})
|
||||||
diff --git a/src/mame/sharp/x68k.cpp b/src/mame/sharp/x68k.cpp
|
, m_options(*this, "options")
|
||||||
index 8e5def0a501a6..c6787eeef393d 100644
|
- , m_mouse1(*this, "mouse1")
|
||||||
|
- , m_mouse2(*this, "mouse2")
|
||||||
|
- , m_mouse3(*this, "mouse3")
|
||||||
|
, m_eject_drv_out(*this, "eject_drv%u", 0U)
|
||||||
|
, m_ctrl_drv_out(*this, "ctrl_drv%u", 0U)
|
||||||
|
, m_access_drv_out(*this, "access_drv%u", 0U)
|
||||||
|
@@ -100,7 +97,7 @@
|
||||||
|
required_device<palette_device> m_pcgpalette;
|
||||||
|
required_device<mc68901_device> m_mfpdev;
|
||||||
|
required_device<rp5c15_device> m_rtc;
|
||||||
|
- required_device<scc8530_legacy_device> m_scc;
|
||||||
|
+ required_device<scc8530_device> m_scc;
|
||||||
|
required_device<ym2151_device> m_ym2151;
|
||||||
|
required_device<i8255_device> m_ppi;
|
||||||
|
required_device<screen_device> m_screen;
|
||||||
|
@@ -111,9 +108,6 @@
|
||||||
|
required_device_array<filter_volume_device, 2> m_adpcm_out;
|
||||||
|
|
||||||
|
required_ioport m_options;
|
||||||
|
- required_ioport m_mouse1;
|
||||||
|
- required_ioport m_mouse2;
|
||||||
|
- required_ioport m_mouse3;
|
||||||
|
|
||||||
|
output_finder<4> m_eject_drv_out;
|
||||||
|
output_finder<4> m_ctrl_drv_out;
|
||||||
|
@@ -178,24 +172,21 @@
|
||||||
|
uint8_t hdcvector = 0;
|
||||||
|
uint8_t prnvector = 0;
|
||||||
|
} m_ioc;
|
||||||
|
- struct
|
||||||
|
- {
|
||||||
|
- int inputtype = 0; // determines which input is to be received
|
||||||
|
- bool irqactive = false; // true if IRQ is being serviced
|
||||||
|
- uint8_t irqvector = 0;
|
||||||
|
- char last_mouse_x = 0; // previous mouse x-axis value
|
||||||
|
- char last_mouse_y = 0; // previous mouse y-axis value
|
||||||
|
- int bufferempty = 0; // non-zero if buffer is empty
|
||||||
|
- } m_mouse;
|
||||||
|
uint8_t m_ppi_portc = 0;
|
||||||
|
bool m_dmac_int = false;
|
||||||
|
bool m_mfp_int = false;
|
||||||
|
+ bool m_scc_int = false;
|
||||||
|
bool m_exp_irq2[2]{};
|
||||||
|
bool m_exp_irq4[2]{};
|
||||||
|
bool m_exp_nmi[2]{};
|
||||||
|
uint8_t m_current_ipl = 0;
|
||||||
|
int m_led_state = 0;
|
||||||
|
- emu_timer* m_mouse_timer = nullptr;
|
||||||
|
+ // Extra cycles charged per VRAM access, over and above a main-memory
|
||||||
|
+ // access, in 16.16 fixed point. Set from measured values in machine_start
|
||||||
|
+ // for the machines those measurements cover; 0 (unmodelled) otherwise.
|
||||||
|
+ uint32_t m_gvram_wait = 0;
|
||||||
|
+ uint32_t m_tvram_wait = 0;
|
||||||
|
+ uint32_t m_vram_wait_accum = 0; // 16.16 carry between accesses
|
||||||
|
emu_timer* m_led_timer = nullptr;
|
||||||
|
unsigned char m_scc_prev = 0;
|
||||||
|
emu_timer* m_fdc_tc = nullptr;
|
||||||
|
@@ -233,7 +224,6 @@
|
||||||
|
void dma_irq(int state);
|
||||||
|
void dma_end(offs_t offset, uint8_t data);
|
||||||
|
|
||||||
|
- int read_mouse();
|
||||||
|
void set_adpcm();
|
||||||
|
|
||||||
|
void fm_irq(int state);
|
||||||
|
@@ -241,8 +231,6 @@
|
||||||
|
template <int N> void irq4_line(int state);
|
||||||
|
template <int N> void nmi_line(int state);
|
||||||
|
|
||||||
|
- void scc_w(offs_t offset, uint16_t data);
|
||||||
|
- uint16_t scc_r(offs_t offset);
|
||||||
|
void fdc_w(offs_t offset, uint16_t data);
|
||||||
|
uint16_t fdc_r(offs_t offset);
|
||||||
|
void ioc_w(offs_t offset, uint16_t data);
|
||||||
|
@@ -265,6 +253,15 @@
|
||||||
|
uint16_t exp_r(offs_t offset, uint16_t mem_mask = ~0);
|
||||||
|
void exp_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0);
|
||||||
|
|
||||||
|
+ // VRAM wait states. The X68000's video RAM is slower than main memory;
|
||||||
|
+ // MAME charged nothing for it, so VRAM-bound code ran optimistically
|
||||||
|
+ // fast. These trampolines charge the difference and forward to the CRTC.
|
||||||
|
+ void charge_vram_wait(uint32_t increment);
|
||||||
|
+ uint16_t gvram_wait_r(offs_t offset);
|
||||||
|
+ void gvram_wait_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0);
|
||||||
|
+ uint16_t tvram_wait_r(offs_t offset);
|
||||||
|
+ void tvram_wait_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0);
|
||||||
|
+
|
||||||
|
uint16_t spritereg_r(offs_t offset);
|
||||||
|
void spritereg_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0);
|
||||||
|
uint16_t spriteram_r(offs_t offset);
|
||||||
--- a/src/mame/sharp/x68k.cpp
|
--- a/src/mame/sharp/x68k.cpp
|
||||||
+++ b/src/mame/sharp/x68k.cpp
|
+++ b/src/mame/sharp/x68k.cpp
|
||||||
@@ -119,6 +119,7 @@
|
@@ -119,6 +119,7 @@
|
||||||
|
|
@ -24,7 +113,7 @@ index 8e5def0a501a6..c6787eeef393d 100644
|
||||||
|
|
||||||
#include "machine/nvram.h"
|
#include "machine/nvram.h"
|
||||||
|
|
||||||
@@ -174,127 +175,6 @@ TIMER_CALLBACK_MEMBER(x68k_state::led_callback)
|
@@ -174,127 +175,6 @@
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -152,7 +241,7 @@ index 8e5def0a501a6..c6787eeef393d 100644
|
||||||
void x68k_state::set_adpcm()
|
void x68k_state::set_adpcm()
|
||||||
{
|
{
|
||||||
uint32_t rate = adpcm_div[m_adpcm.rate];
|
uint32_t rate = adpcm_div[m_adpcm.rate];
|
||||||
@@ -833,7 +713,7 @@ void x68k_state::update_ipl()
|
@@ -837,7 +717,7 @@
|
||||||
new_ipl = 7;
|
new_ipl = 7;
|
||||||
else if (m_mfp_int)
|
else if (m_mfp_int)
|
||||||
new_ipl = 6;
|
new_ipl = 6;
|
||||||
|
|
@ -161,7 +250,7 @@ index 8e5def0a501a6..c6787eeef393d 100644
|
||||||
new_ipl = 5;
|
new_ipl = 5;
|
||||||
else if (m_exp_irq4[0] || m_exp_irq4[1])
|
else if (m_exp_irq4[0] || m_exp_irq4[1])
|
||||||
new_ipl = 4;
|
new_ipl = 4;
|
||||||
@@ -946,18 +826,6 @@ uint8_t x68k_state::iack4()
|
@@ -950,18 +830,6 @@
|
||||||
return 0x18; // spurious interrupt
|
return 0x18; // spurious interrupt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -180,7 +269,7 @@ index 8e5def0a501a6..c6787eeef393d 100644
|
||||||
void x68k_state::cpu_space_map(address_map &map)
|
void x68k_state::cpu_space_map(address_map &map)
|
||||||
{
|
{
|
||||||
map.global_mask(0xffffff);
|
map.global_mask(0xffffff);
|
||||||
@@ -965,7 +833,7 @@ void x68k_state::cpu_space_map(address_map &map)
|
@@ -969,7 +837,7 @@
|
||||||
map(0xfffff5, 0xfffff5).r(FUNC(x68k_state::iack2));
|
map(0xfffff5, 0xfffff5).r(FUNC(x68k_state::iack2));
|
||||||
map(0xfffff7, 0xfffff7).r(m_hd63450, FUNC(hd63450_device::iack));
|
map(0xfffff7, 0xfffff7).r(m_hd63450, FUNC(hd63450_device::iack));
|
||||||
map(0xfffff9, 0xfffff9).r(FUNC(x68k_state::iack4));
|
map(0xfffff9, 0xfffff9).r(FUNC(x68k_state::iack4));
|
||||||
|
|
@ -189,7 +278,90 @@ index 8e5def0a501a6..c6787eeef393d 100644
|
||||||
map(0xfffffd, 0xfffffd).r(m_mfpdev, FUNC(mc68901_device::get_vector));
|
map(0xfffffd, 0xfffffd).r(m_mfpdev, FUNC(mc68901_device::get_vector));
|
||||||
map(0xffffff, 0xffffff).lr8(NAME([] () { return m68000_base_device::autovector(7); }));
|
map(0xffffff, 0xffffff).lr8(NAME([] () { return m68000_base_device::autovector(7); }));
|
||||||
}
|
}
|
||||||
@@ -1005,7 +873,7 @@ void x68k_state::x68k_base_map(address_map &map)
|
@@ -992,12 +860,80 @@
|
||||||
|
// Is this an undocumented MB89352 feature, an ASIC register, an original code bug or a bad dump?
|
||||||
|
}
|
||||||
|
|
||||||
|
+// VRAM wait states.
|
||||||
|
+//
|
||||||
|
+// The 68000 in an X68000 does not reach video RAM as fast as it reaches main
|
||||||
|
+// memory, and MAME modelled no wait at all -- so any VRAM-bound inner loop ran
|
||||||
|
+// optimistically fast, which is exactly the code a graphics library cares
|
||||||
|
+// about. The figures used here are Makoto Kamada's real-hardware measurements
|
||||||
|
+// distributed with XEiJ (credited there to uchopon, tnb and ita), taken by
|
||||||
|
+// timing tst.w against each region on five 10 MHz X68000 variants:
|
||||||
|
+//
|
||||||
|
+// region tst.w time (us) derived wait (cycles)
|
||||||
|
+// main memory 1.642 .. 1.663 0.123
|
||||||
|
+// GVRAM 1.734 .. 1.770 1.145
|
||||||
|
+// TVRAM 1.829 .. 1.850 2.046
|
||||||
|
+//
|
||||||
|
+// MAME charges nothing for main memory, so what is added here is the wait
|
||||||
|
+// RELATIVE to main memory: 1.145 - 0.123 = 1.022 cycles for GVRAM and
|
||||||
|
+// 2.046 - 0.123 = 1.923 for TVRAM. Cross-check against the raw measurements:
|
||||||
|
+// GVRAM - main memory averages ~1.03 cycles and TVRAM - main memory ~1.92
|
||||||
|
+// across the five machines, which agrees.
|
||||||
|
+//
|
||||||
|
+// Main memory's own 0.123 cycles (DRAM refresh) is deliberately NOT modelled:
|
||||||
|
+// it would mean interposing on the hottest path in the driver to recover ~0.1
|
||||||
|
+// cycles per access, and it is a uniform offset that does not change the
|
||||||
|
+// relative cost of VRAM versus RAM, which is the thing being measured.
|
||||||
|
+//
|
||||||
|
+// The wait is fractional, so it accumulates in 16.16 fixed point and whole
|
||||||
|
+// cycles are charged as they carry out -- the same technique the apple2gs
|
||||||
|
+// driver uses for its 1 MHz cycles (see slow_cycle() there).
|
||||||
|
+#define X68K_GVRAM_WAIT 0x000105a2 // 1.022 cycles, 16.16
|
||||||
|
+#define X68K_TVRAM_WAIT 0x0001ec4a // 1.923 cycles, 16.16
|
||||||
|
+
|
||||||
|
+void x68k_state::charge_vram_wait(uint32_t increment)
|
||||||
|
+{
|
||||||
|
+ // side_effects_disabled() covers debugger and Lua reads, which must not
|
||||||
|
+ // perturb the timing they are measuring.
|
||||||
|
+ if (increment == 0 || machine().side_effects_disabled())
|
||||||
|
+ return;
|
||||||
|
+ m_vram_wait_accum += increment;
|
||||||
|
+ int const cycles = m_vram_wait_accum >> 16;
|
||||||
|
+ m_vram_wait_accum &= 0xffff;
|
||||||
|
+ if (cycles != 0)
|
||||||
|
+ m_maincpu->adjust_icount(-cycles);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+uint16_t x68k_state::gvram_wait_r(offs_t offset)
|
||||||
|
+{
|
||||||
|
+ charge_vram_wait(m_gvram_wait);
|
||||||
|
+ return m_crtc->gvram_r(offset);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+void x68k_state::gvram_wait_w(offs_t offset, uint16_t data, uint16_t mem_mask)
|
||||||
|
+{
|
||||||
|
+ charge_vram_wait(m_gvram_wait);
|
||||||
|
+ m_crtc->gvram_w(offset, data, mem_mask);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+uint16_t x68k_state::tvram_wait_r(offs_t offset)
|
||||||
|
+{
|
||||||
|
+ charge_vram_wait(m_tvram_wait);
|
||||||
|
+ return m_crtc->tvram_r(offset);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+void x68k_state::tvram_wait_w(offs_t offset, uint16_t data, uint16_t mem_mask)
|
||||||
|
+{
|
||||||
|
+ charge_vram_wait(m_tvram_wait);
|
||||||
|
+ m_crtc->tvram_w(offset, data, mem_mask);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
void x68k_state::x68k_base_map(address_map &map)
|
||||||
|
{
|
||||||
|
map(0x000000, 0xbffffb).rw(FUNC(x68k_state::emptyram_r), FUNC(x68k_state::emptyram_w));
|
||||||
|
map(0xbffffc, 0xbfffff).rw(FUNC(x68k_state::rom0_r), FUNC(x68k_state::rom0_w));
|
||||||
|
- map(0xc00000, 0xdfffff).rw(m_crtc, FUNC(x68k_crtc_device::gvram_r), FUNC(x68k_crtc_device::gvram_w));
|
||||||
|
- map(0xe00000, 0xe7ffff).rw(m_crtc, FUNC(x68k_crtc_device::tvram_r), FUNC(x68k_crtc_device::tvram_w));
|
||||||
|
+ map(0xc00000, 0xdfffff).rw(FUNC(x68k_state::gvram_wait_r), FUNC(x68k_state::gvram_wait_w));
|
||||||
|
+ map(0xe00000, 0xe7ffff).rw(FUNC(x68k_state::tvram_wait_r), FUNC(x68k_state::tvram_wait_w));
|
||||||
|
map(0xe80000, 0xe81fff).rw(m_crtc, FUNC(x68k_crtc_device::crtc_r), FUNC(x68k_crtc_device::crtc_w));
|
||||||
|
map(0xe82400, 0xe83fff).rw(FUNC(x68k_state::vid_r), FUNC(x68k_state::vid_w));
|
||||||
|
map(0xe84000, 0xe85fff).rw(m_hd63450, FUNC(hd63450_device::read), FUNC(hd63450_device::write));
|
||||||
|
@@ -1009,7 +945,7 @@
|
||||||
map(0xe90000, 0xe91fff).rw(m_ym2151, FUNC(ym2151_device::read), FUNC(ym2151_device::write)).umask16(0x00ff);
|
map(0xe90000, 0xe91fff).rw(m_ym2151, FUNC(ym2151_device::read), FUNC(ym2151_device::write)).umask16(0x00ff);
|
||||||
map(0xe94000, 0xe94003).m(m_upd72065, FUNC(upd72065_device::map)).umask16(0x00ff);
|
map(0xe94000, 0xe94003).m(m_upd72065, FUNC(upd72065_device::map)).umask16(0x00ff);
|
||||||
map(0xe94004, 0xe94007).rw(FUNC(x68k_state::fdc_r), FUNC(x68k_state::fdc_w));
|
map(0xe94004, 0xe94007).rw(FUNC(x68k_state::fdc_r), FUNC(x68k_state::fdc_w));
|
||||||
|
|
@ -198,7 +370,7 @@ index 8e5def0a501a6..c6787eeef393d 100644
|
||||||
map(0xe9a000, 0xe9bfff).rw(FUNC(x68k_state::ppi_r), FUNC(x68k_state::ppi_w));
|
map(0xe9a000, 0xe9bfff).rw(FUNC(x68k_state::ppi_r), FUNC(x68k_state::ppi_w));
|
||||||
map(0xe9c000, 0xe9dfff).rw(FUNC(x68k_state::ioc_r), FUNC(x68k_state::ioc_w));
|
map(0xe9c000, 0xe9dfff).rw(FUNC(x68k_state::ioc_r), FUNC(x68k_state::ioc_w));
|
||||||
map(0xe9e000, 0xe9e3ff).rw(FUNC(x68k_state::exp_r), FUNC(x68k_state::exp_w)); // FPU (Optional)
|
map(0xe9e000, 0xe9e3ff).rw(FUNC(x68k_state::exp_r), FUNC(x68k_state::exp_w)); // FPU (Optional)
|
||||||
@@ -1068,16 +936,6 @@ static INPUT_PORTS_START( x68000 )
|
@@ -1072,16 +1008,6 @@
|
||||||
PORT_CONFNAME( 0x02, 0x02, "Enable fake bus errors")
|
PORT_CONFNAME( 0x02, 0x02, "Enable fake bus errors")
|
||||||
PORT_CONFSETTING( 0x00, DEF_STR( Off ))
|
PORT_CONFSETTING( 0x00, DEF_STR( Off ))
|
||||||
PORT_CONFSETTING( 0x02, DEF_STR( On ))
|
PORT_CONFSETTING( 0x02, DEF_STR( On ))
|
||||||
|
|
@ -215,18 +387,34 @@ index 8e5def0a501a6..c6787eeef393d 100644
|
||||||
INPUT_PORTS_END
|
INPUT_PORTS_END
|
||||||
|
|
||||||
void x68k_state::floppy_load_unload(bool load, floppy_image_device *dev)
|
void x68k_state::floppy_load_unload(bool load, floppy_image_device *dev)
|
||||||
@@ -1151,10 +1009,6 @@ void x68k_state::machine_start()
|
@@ -1155,9 +1081,23 @@
|
||||||
m_spriteram = (uint16_t*)(memregion("user1")->base());
|
m_spriteram = (uint16_t*)(memregion("user1")->base());
|
||||||
space.install_ram(0x000000,m_ram->size()-1,m_ram->pointer());
|
space.install_ram(0x000000,m_ram->size()-1,m_ram->pointer());
|
||||||
|
|
||||||
- // start mouse timer
|
- // start mouse timer
|
||||||
- m_mouse_timer->adjust(attotime::zero, 0, attotime::from_msec(1)); // a guess for now
|
- m_mouse_timer->adjust(attotime::zero, 0, attotime::from_msec(1)); // a guess for now
|
||||||
- m_mouse.inputtype = 0;
|
- m_mouse.inputtype = 0;
|
||||||
-
|
+ // VRAM wait states, applied only to the machine the measurements cover.
|
||||||
|
+ // The published figures are for a 10 MHz 68000 X68000; XEiJ's table also
|
||||||
|
+ // lists a 25 MHz X68030 (GVRAM 6.377, TVRAM 6.623) but those machines are
|
||||||
|
+ // MACHINE_NOT_WORKING here and cannot be validated, so they are left
|
||||||
|
+ // unmodelled rather than given invented numbers.
|
||||||
|
+ if (m_maincpu->clock() == 10'000'000)
|
||||||
|
+ {
|
||||||
|
+ m_gvram_wait = X68K_GVRAM_WAIT;
|
||||||
|
+ m_tvram_wait = X68K_TVRAM_WAIT;
|
||||||
|
+ }
|
||||||
|
+ else
|
||||||
|
+ {
|
||||||
|
+ m_gvram_wait = 0;
|
||||||
|
+ m_tvram_wait = 0;
|
||||||
|
+ }
|
||||||
|
+ m_vram_wait_accum = 0;
|
||||||
|
+ save_item(NAME(m_vram_wait_accum));
|
||||||
|
|
||||||
// start LED timer
|
// start LED timer
|
||||||
m_led_timer->adjust(attotime::zero, 0, attotime::from_msec(400));
|
m_led_timer->adjust(attotime::zero, 0, attotime::from_msec(400));
|
||||||
|
@@ -1177,11 +1117,11 @@
|
||||||
@@ -1173,11 +1027,11 @@ void x68k_state::machine_start()
|
|
||||||
|
|
||||||
m_dmac_int = false;
|
m_dmac_int = false;
|
||||||
m_mfp_int = false;
|
m_mfp_int = false;
|
||||||
|
|
@ -239,7 +427,7 @@ index 8e5def0a501a6..c6787eeef393d 100644
|
||||||
m_current_ipl = 0;
|
m_current_ipl = 0;
|
||||||
m_adpcm.rate = 0;
|
m_adpcm.rate = 0;
|
||||||
m_adpcm.clock = 0;
|
m_adpcm.clock = 0;
|
||||||
@@ -1204,7 +1058,6 @@ void x68k_state::driver_start()
|
@@ -1208,7 +1148,6 @@
|
||||||
// copy last half of BIOS to a user region, to use for initial startup
|
// copy last half of BIOS to a user region, to use for initial startup
|
||||||
memcpy(user2,(rom+0xff0000),0x10000);
|
memcpy(user2,(rom+0xff0000),0x10000);
|
||||||
|
|
||||||
|
|
@ -247,7 +435,7 @@ index 8e5def0a501a6..c6787eeef393d 100644
|
||||||
m_led_timer = timer_alloc(FUNC(x68ksupr_state::led_callback), this);
|
m_led_timer = timer_alloc(FUNC(x68ksupr_state::led_callback), this);
|
||||||
m_fdc_tc = timer_alloc(FUNC(x68ksupr_state::floppy_tc_tick), this);
|
m_fdc_tc = timer_alloc(FUNC(x68ksupr_state::floppy_tc_tick), this);
|
||||||
m_adpcm_timer = timer_alloc(FUNC(x68ksupr_state::adpcm_drq_tick), this);
|
m_adpcm_timer = timer_alloc(FUNC(x68ksupr_state::adpcm_drq_tick), this);
|
||||||
@@ -1249,6 +1102,11 @@ static void keyboard_devices(device_slot_interface &device)
|
@@ -1253,6 +1192,11 @@
|
||||||
device.option_add("x68k", X68K_KEYBOARD);
|
device.option_add("x68k", X68K_KEYBOARD);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -259,7 +447,7 @@ index 8e5def0a501a6..c6787eeef393d 100644
|
||||||
void x68k_state::x68000_base(machine_config &config)
|
void x68k_state::x68000_base(machine_config &config)
|
||||||
{
|
{
|
||||||
config.set_maximum_quantum(attotime::from_hz(60));
|
config.set_maximum_quantum(attotime::from_hz(60));
|
||||||
@@ -1281,7 +1139,12 @@ void x68k_state::x68000_base(machine_config &config)
|
@@ -1285,7 +1229,26 @@
|
||||||
m_hd63450->dma_read<0>().set("upd72065", FUNC(upd72065_device::dma_r));
|
m_hd63450->dma_read<0>().set("upd72065", FUNC(upd72065_device::dma_r));
|
||||||
m_hd63450->dma_write<0>().set("upd72065", FUNC(upd72065_device::dma_w));
|
m_hd63450->dma_write<0>().set("upd72065", FUNC(upd72065_device::dma_w));
|
||||||
|
|
||||||
|
|
@ -270,97 +458,62 @@ index 8e5def0a501a6..c6787eeef393d 100644
|
||||||
+ rs232_port_device &mouse(RS232_PORT(config, "mouse_port", mouse_devices, "x68k"));
|
+ rs232_port_device &mouse(RS232_PORT(config, "mouse_port", mouse_devices, "x68k"));
|
||||||
+ mouse.rxd_handler().set(m_scc, FUNC(scc8530_device::rxb_w));
|
+ mouse.rxd_handler().set(m_scc, FUNC(scc8530_device::rxb_w));
|
||||||
+ m_scc->out_rtsb_callback().set(mouse, FUNC(rs232_port_device::write_rts));
|
+ m_scc->out_rtsb_callback().set(mouse, FUNC(rs232_port_device::write_rts));
|
||||||
|
+
|
||||||
|
+ // RS-232C -- SCC channel A. Only channel B (the mouse) was ever wired, so
|
||||||
|
+ // the machine's actual serial port had no connection at all and could not
|
||||||
|
+ // be exercised. Exposing it as a standard rs232 slot makes
|
||||||
|
+ // `-rs232c null_modem` (TCP socket) and `-rs232c pty` available, which is
|
||||||
|
+ // what an automated both-directions serial test needs.
|
||||||
|
+ m_scc->out_txda_callback().set("rs232c", FUNC(rs232_port_device::write_txd));
|
||||||
|
+ m_scc->out_dtra_callback().set("rs232c", FUNC(rs232_port_device::write_dtr));
|
||||||
|
+ m_scc->out_rtsa_callback().set("rs232c", FUNC(rs232_port_device::write_rts));
|
||||||
|
+
|
||||||
|
+ rs232_port_device &rs232c(RS232_PORT(config, "rs232c", default_rs232_devices, nullptr));
|
||||||
|
+ rs232c.rxd_handler().set(m_scc, FUNC(scc8530_device::rxa_w));
|
||||||
|
+ rs232c.dcd_handler().set(m_scc, FUNC(scc8530_device::dcda_w));
|
||||||
|
+ rs232c.cts_handler().set(m_scc, FUNC(scc8530_device::ctsa_w));
|
||||||
|
|
||||||
RP5C15(config, m_rtc, 32.768_kHz_XTAL);
|
RP5C15(config, m_rtc, 32.768_kHz_XTAL);
|
||||||
m_rtc->alarm().set(m_mfpdev, FUNC(mc68901_device::i0_w));
|
m_rtc->alarm().set(m_mfpdev, FUNC(mc68901_device::i0_w));
|
||||||
diff --git a/src/mame/sharp/x68k.h b/src/mame/sharp/x68k.h
|
--- /dev/null
|
||||||
index 2c1dc8552af67..b5c40b213b6bb 100644
|
+++ b/src/mame/sharp/x68k_mouse.h
|
||||||
--- a/src/mame/sharp/x68k.h
|
@@ -0,0 +1,36 @@
|
||||||
+++ b/src/mame/sharp/x68k.h
|
+// license:BSD-3-Clause
|
||||||
@@ -20,7 +20,7 @@
|
+// copyright-holders:Patrick Mackinlay
|
||||||
#include "cpu/m68000/m68000.h"
|
+
|
||||||
#include "cpu/m68000/m68030.h"
|
+#ifndef MAME_SHARP_X68K_MOUSE_H
|
||||||
#include "imagedev/floppy.h"
|
+#define MAME_SHARP_X68K_MOUSE_H
|
||||||
-#include "machine/8530scc.h"
|
+
|
||||||
+#include "machine/z80scc.h"
|
+#pragma once
|
||||||
#include "machine/hd63450.h"
|
+
|
||||||
#include "machine/i8255.h"
|
+#include "bus/rs232/rs232.h"
|
||||||
#include "machine/mb87030.h"
|
+
|
||||||
@@ -64,9 +64,6 @@ class x68k_state : public driver_device
|
+class x68k_mouse_device : public buffered_rs232_device<3>
|
||||||
, m_expansion(*this, "exp%u", 1U)
|
+{
|
||||||
, m_adpcm_out(*this, {"adpcm_outl", "adpcm_outr"})
|
+public:
|
||||||
, m_options(*this, "options")
|
+ x68k_mouse_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock = 0);
|
||||||
- , m_mouse1(*this, "mouse1")
|
+
|
||||||
- , m_mouse2(*this, "mouse2")
|
+ virtual void input_rts(int state) override; // MSCTRL (active low)
|
||||||
- , m_mouse3(*this, "mouse3")
|
+
|
||||||
, m_eject_drv_out(*this, "eject_drv%u", 0U)
|
+protected:
|
||||||
, m_ctrl_drv_out(*this, "ctrl_drv%u", 0U)
|
+ virtual ioport_constructor device_input_ports() const override ATTR_COLD;
|
||||||
, m_access_drv_out(*this, "access_drv%u", 0U)
|
+ virtual void device_start() override ATTR_COLD;
|
||||||
@@ -100,7 +97,7 @@ class x68k_state : public driver_device
|
+
|
||||||
required_device<palette_device> m_pcgpalette;
|
+ virtual void received_byte(u8 byte) override {}
|
||||||
required_device<mc68901_device> m_mfpdev;
|
+
|
||||||
required_device<rp5c15_device> m_rtc;
|
+private:
|
||||||
- required_device<scc8530_legacy_device> m_scc;
|
+ required_ioport m_buttons;
|
||||||
+ required_device<scc8530_device> m_scc;
|
+ required_ioport m_x_axis;
|
||||||
required_device<ym2151_device> m_ym2151;
|
+ required_ioport m_y_axis;
|
||||||
required_device<i8255_device> m_ppi;
|
+
|
||||||
required_device<screen_device> m_screen;
|
+ u8 m_b;
|
||||||
@@ -111,9 +108,6 @@ class x68k_state : public driver_device
|
+ u16 m_x;
|
||||||
required_device_array<filter_volume_device, 2> m_adpcm_out;
|
+ u16 m_y;
|
||||||
|
+};
|
||||||
required_ioport m_options;
|
+
|
||||||
- required_ioport m_mouse1;
|
+DECLARE_DEVICE_TYPE(X68K_MOUSE, x68k_mouse_device)
|
||||||
- required_ioport m_mouse2;
|
+
|
||||||
- required_ioport m_mouse3;
|
+#endif // MAME_SHARP_X68K_MOUSE_H
|
||||||
|
|
||||||
output_finder<4> m_eject_drv_out;
|
|
||||||
output_finder<4> m_ctrl_drv_out;
|
|
||||||
@@ -178,24 +172,15 @@ class x68k_state : public driver_device
|
|
||||||
uint8_t hdcvector = 0;
|
|
||||||
uint8_t prnvector = 0;
|
|
||||||
} m_ioc;
|
|
||||||
- struct
|
|
||||||
- {
|
|
||||||
- int inputtype = 0; // determines which input is to be received
|
|
||||||
- bool irqactive = false; // true if IRQ is being serviced
|
|
||||||
- uint8_t irqvector = 0;
|
|
||||||
- char last_mouse_x = 0; // previous mouse x-axis value
|
|
||||||
- char last_mouse_y = 0; // previous mouse y-axis value
|
|
||||||
- int bufferempty = 0; // non-zero if buffer is empty
|
|
||||||
- } m_mouse;
|
|
||||||
uint8_t m_ppi_portc = 0;
|
|
||||||
bool m_dmac_int = false;
|
|
||||||
bool m_mfp_int = false;
|
|
||||||
+ bool m_scc_int = false;
|
|
||||||
bool m_exp_irq2[2]{};
|
|
||||||
bool m_exp_irq4[2]{};
|
|
||||||
bool m_exp_nmi[2]{};
|
|
||||||
uint8_t m_current_ipl = 0;
|
|
||||||
int m_led_state = 0;
|
|
||||||
- emu_timer* m_mouse_timer = nullptr;
|
|
||||||
emu_timer* m_led_timer = nullptr;
|
|
||||||
unsigned char m_scc_prev = 0;
|
|
||||||
emu_timer* m_fdc_tc = nullptr;
|
|
||||||
@@ -233,7 +218,6 @@ class x68k_state : public driver_device
|
|
||||||
void dma_irq(int state);
|
|
||||||
void dma_end(offs_t offset, uint8_t data);
|
|
||||||
|
|
||||||
- int read_mouse();
|
|
||||||
void set_adpcm();
|
|
||||||
|
|
||||||
void fm_irq(int state);
|
|
||||||
@@ -241,8 +225,6 @@ class x68k_state : public driver_device
|
|
||||||
template <int N> void irq4_line(int state);
|
|
||||||
template <int N> void nmi_line(int state);
|
|
||||||
|
|
||||||
- void scc_w(offs_t offset, uint16_t data);
|
|
||||||
- uint16_t scc_r(offs_t offset);
|
|
||||||
void fdc_w(offs_t offset, uint16_t data);
|
|
||||||
uint16_t fdc_r(offs_t offset);
|
|
||||||
void ioc_w(offs_t offset, uint16_t data);
|
|
||||||
diff --git a/src/mame/sharp/x68k_mouse.cpp b/src/mame/sharp/x68k_mouse.cpp
|
|
||||||
new file mode 100644
|
|
||||||
index 0000000000000..f30cfc7d25d54
|
|
||||||
--- /dev/null
|
--- /dev/null
|
||||||
+++ b/src/mame/sharp/x68k_mouse.cpp
|
+++ b/src/mame/sharp/x68k_mouse.cpp
|
||||||
@@ -0,0 +1,103 @@
|
@@ -0,0 +1,103 @@
|
||||||
|
|
@ -467,45 +620,3 @@ index 0000000000000..f30cfc7d25d54
|
||||||
+{
|
+{
|
||||||
+ return INPUT_PORTS_NAME(x68k);
|
+ return INPUT_PORTS_NAME(x68k);
|
||||||
+}
|
+}
|
||||||
diff --git a/src/mame/sharp/x68k_mouse.h b/src/mame/sharp/x68k_mouse.h
|
|
||||||
new file mode 100644
|
|
||||||
index 0000000000000..9028c831fe47e
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/src/mame/sharp/x68k_mouse.h
|
|
||||||
@@ -0,0 +1,36 @@
|
|
||||||
+// license:BSD-3-Clause
|
|
||||||
+// copyright-holders:Patrick Mackinlay
|
|
||||||
+
|
|
||||||
+#ifndef MAME_SHARP_X68K_MOUSE_H
|
|
||||||
+#define MAME_SHARP_X68K_MOUSE_H
|
|
||||||
+
|
|
||||||
+#pragma once
|
|
||||||
+
|
|
||||||
+#include "bus/rs232/rs232.h"
|
|
||||||
+
|
|
||||||
+class x68k_mouse_device : public buffered_rs232_device<3>
|
|
||||||
+{
|
|
||||||
+public:
|
|
||||||
+ x68k_mouse_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock = 0);
|
|
||||||
+
|
|
||||||
+ virtual void input_rts(int state) override; // MSCTRL (active low)
|
|
||||||
+
|
|
||||||
+protected:
|
|
||||||
+ virtual ioport_constructor device_input_ports() const override ATTR_COLD;
|
|
||||||
+ virtual void device_start() override ATTR_COLD;
|
|
||||||
+
|
|
||||||
+ virtual void received_byte(u8 byte) override {}
|
|
||||||
+
|
|
||||||
+private:
|
|
||||||
+ required_ioport m_buttons;
|
|
||||||
+ required_ioport m_x_axis;
|
|
||||||
+ required_ioport m_y_axis;
|
|
||||||
+
|
|
||||||
+ u8 m_b;
|
|
||||||
+ u16 m_x;
|
|
||||||
+ u16 m_y;
|
|
||||||
+};
|
|
||||||
+
|
|
||||||
+DECLARE_DEVICE_TYPE(X68K_MOUSE, x68k_mouse_device)
|
|
||||||
+
|
|
||||||
+#endif // MAME_SHARP_X68K_MOUSE_H
|
|
||||||
27
installer/patches/mame/README.md
Normal file
27
installer/patches/mame/README.md
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
# MAME 0.264 patches
|
||||||
|
|
||||||
|
install.sh (component `mame`) applies these to the MAME 0.264
|
||||||
|
release in filename order, then builds one binary with the apple2gs and x68k
|
||||||
|
drivers. The gates depend on every one of them.
|
||||||
|
|
||||||
|
| Patch | What it fixes | Needed by |
|
||||||
|
|---|---|---|
|
||||||
|
| 0001-apple2gs-scc-clock | The IIgs SCC clock, so serial RX works at rates other than 230400 baud | `verify-iigs-serial.sh` |
|
||||||
|
| 0002-z80scc-rx-fifo | Widens the Z8530 receive FIFO from 8 to 512 bytes, so a POLLED receiver (no RX interrupt) survives the emulator's deaf windows. Real silicon holds 3 bytes; real clients need interrupt-driven RX. Originally written for RetroNet. | IIgs serial gates |
|
||||||
|
| 0003-x68k-crtc-timing | The X68000 CRTC: the graphic high-speed clear's timing (the main defect), and the text raster copy firing on both hsync edges instead of only the front porch | X68000 gates and timing |
|
||||||
|
| 0004-x68k-waitstates-rs232-mouse | Three X68000 changes that edit the same files: VRAM wait states (MAME modelled none), the RS-232C port wired to a slot so `-rs232c null_modem` works, and a backport of upstream commit ab6b6fc9e797 (Patrick Mackinlay, "x68k: add serial mouse"), which provides the `:mouse_port:x68k:X/Y/BTN` inputs | `verify-x68000-serial.sh`, `verify-x68000-mouse.sh`, X68000 timing |
|
||||||
|
|
||||||
|
## Why the X68000 changes are one patch
|
||||||
|
|
||||||
|
They were first kept as three separate patches, but the upstream mouse
|
||||||
|
backport no longer applied cleanly on top of the other two in any order, and
|
||||||
|
the sources the working MAME was built from had been fixed up by hand. These
|
||||||
|
four files were regenerated on 2026-09-27 by diffing that working tree
|
||||||
|
against the pristine 0.264 release, so they reproduce it exactly (verified:
|
||||||
|
every file applies with zero fuzz and matches byte for byte).
|
||||||
|
|
||||||
|
## Probes
|
||||||
|
|
||||||
|
`lua/` holds the MAME Lua probes used while developing the X68000 patches
|
||||||
|
(boot check, CRTC timing, wait states). Run one with
|
||||||
|
`mame x68000 -bios ipl10 -autoboot_script lua/<probe>.lua`.
|
||||||
547
joey-run
Executable file
547
joey-run
Executable file
|
|
@ -0,0 +1,547 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# joey-run: run a built JoeyLib program in the right emulator.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./joey-run [options] PROGRAM [-- ARGS...]
|
||||||
|
#
|
||||||
|
# PROGRAM is any of:
|
||||||
|
# a program file build/dos/bin/DRAW.EXE, build/atarist/bin/DRAW.PRG,
|
||||||
|
# build/amiga/bin/Draw, build/x68000/bin/DRAW.X,
|
||||||
|
# build/iigs/bin/DRAW
|
||||||
|
# an app folder build/amiga/bin/Taxi (the program plus its DATA)
|
||||||
|
# an IIgs disk image build/iigs/bin/STAXI.2mg (make iigs-game-disk), or
|
||||||
|
# a data disk such as joey.2mg (boots GS/OS with it)
|
||||||
|
# an example name draw, staxi, uber, ... together with --port
|
||||||
|
# The machine is recognised from the file itself.
|
||||||
|
#
|
||||||
|
# Options:
|
||||||
|
# --port PORT iigs, amiga, atarist, dos or x68000
|
||||||
|
# --data DIR copy DIR's files into the program's DATA folder for
|
||||||
|
# this run (for example an AGI game's files)
|
||||||
|
# --headless no window and no sound, for scripts and CI; stops
|
||||||
|
# after 60 seconds unless --seconds says otherwise
|
||||||
|
# --seconds N stop after N seconds
|
||||||
|
# --snapshot FILE save a PNG of the screen when the run stops
|
||||||
|
# (needs --seconds or --headless)
|
||||||
|
# --machine rom01 IIgs: the ROM 01 machine instead of ROM 03
|
||||||
|
# --ram SIZE IIgs: memory size for MAME (default 4M; AGI wants 8M)
|
||||||
|
# --debug IIgs and X68000: start MAME's debugger
|
||||||
|
# -h, --help this text
|
||||||
|
#
|
||||||
|
# Arguments after -- are passed to the program (DOS only: the other
|
||||||
|
# machines' autostart cannot pass arguments).
|
||||||
|
#
|
||||||
|
# The emulators and firmware come from ./install.sh
|
||||||
|
# (install emulator:PORT). Interactive runs use the program's own folder
|
||||||
|
# or disk image, so saves and logs persist; --headless and --data runs
|
||||||
|
# work on a private copy. Nothing is written to your home directory.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
repo="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# shellcheck source=scripts/lib/joeyEnv.sh
|
||||||
|
source "$repo/scripts/lib/joeyEnv.sh"
|
||||||
|
|
||||||
|
port=""
|
||||||
|
dataDir=""
|
||||||
|
headless=0
|
||||||
|
seconds=""
|
||||||
|
snapshot=""
|
||||||
|
machine="apple2gs"
|
||||||
|
ramSize="4M"
|
||||||
|
debug=0
|
||||||
|
program=""
|
||||||
|
progArgs=()
|
||||||
|
emuPid=""
|
||||||
|
xvfbPid=""
|
||||||
|
work=""
|
||||||
|
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
sed -n '2,/^$/p' "$0" | sed 's/^# \{0,1\}//'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo "joey-run: $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
joeyStop "$emuPid"
|
||||||
|
if [ -n "$xvfbPid" ]; then
|
||||||
|
kill "$xvfbPid" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
if [ -n "$work" ]; then
|
||||||
|
rm -rf "$work"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ---- finding the program ----------------------------------------------------
|
||||||
|
|
||||||
|
# magicPort FILE -- print the machine FILE was built for, or nothing.
|
||||||
|
magicPort() {
|
||||||
|
local file="$1"
|
||||||
|
local magic
|
||||||
|
case "$file" in
|
||||||
|
*.2mg|*.2MG|*.po|*.PO|*.hdv|*.HDV)
|
||||||
|
printf 'iigs\n'
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
magic="$(head -c 16 "$file" | od -An -tx1 | tr -d ' \n')"
|
||||||
|
case "$magic" in
|
||||||
|
000003f3*) printf 'amiga\n' ;;
|
||||||
|
601a*) printf 'atarist\n' ;;
|
||||||
|
4d5a*) printf 'dos\n' ;;
|
||||||
|
4855*) printf 'x68000\n' ;;
|
||||||
|
*)
|
||||||
|
# Apple IIgs OMF: segment header byte 14 (NUMLEN) is 4 and
|
||||||
|
# byte 15 (VERSION) is 2.
|
||||||
|
if [ "${magic:28:4}" = "0402" ]; then
|
||||||
|
printf 'iigs\n'
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# exampleProgram NAME PORT -- the built file for an example name.
|
||||||
|
exampleProgram() {
|
||||||
|
local name="$1"
|
||||||
|
local bin="$repo/build/$2/bin"
|
||||||
|
local up="${name^^}"
|
||||||
|
local cap
|
||||||
|
local candidate
|
||||||
|
case "$2" in
|
||||||
|
iigs)
|
||||||
|
printf '%s\n' "$bin/$up"
|
||||||
|
;;
|
||||||
|
amiga)
|
||||||
|
cap="${name,,}"
|
||||||
|
case "$cap" in
|
||||||
|
staxi|spacetaxi) cap=taxi ;;
|
||||||
|
esac
|
||||||
|
cap="${cap^}"
|
||||||
|
for candidate in "$bin/$cap/$cap" "$bin/$cap"; do
|
||||||
|
if [ -f "$candidate" ]; then
|
||||||
|
printf '%s\n' "$candidate"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
printf '%s\n' "$bin/$cap"
|
||||||
|
;;
|
||||||
|
atarist|dos)
|
||||||
|
[ "$up" = "SPACETAXI" ] && up=STAXI
|
||||||
|
local ext=".PRG"
|
||||||
|
[ "$2" = "dos" ] && ext=".EXE"
|
||||||
|
for candidate in "$bin/$up/$up$ext" "$bin/$up$ext"; do
|
||||||
|
if [ -f "$candidate" ]; then
|
||||||
|
printf '%s\n' "$candidate"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
printf '%s\n' "$bin/$up$ext"
|
||||||
|
;;
|
||||||
|
x68000)
|
||||||
|
printf '%s\n' "$bin/$up.X"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
fail "unknown port '$2' (iigs, amiga, atarist, dos or x68000)"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# folderProgram DIR -- the one program file inside an app folder.
|
||||||
|
folderProgram() {
|
||||||
|
local file
|
||||||
|
local found=""
|
||||||
|
while IFS= read -r -d '' file; do
|
||||||
|
if [ -n "$(magicPort "$file")" ]; then
|
||||||
|
[ -z "$found" ] || fail "$1 holds more than one program; name the file"
|
||||||
|
found="$file"
|
||||||
|
fi
|
||||||
|
done < <(find "$1" -maxdepth 1 -type f -print0)
|
||||||
|
[ -n "$found" ] || fail "no program found in $1"
|
||||||
|
printf '%s\n' "$found"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ---- shared run plumbing ------------------------------------------------------
|
||||||
|
|
||||||
|
# stageFolder DEST -- copy the program's folder (its files and DATA tree)
|
||||||
|
# plus --data into DEST, for runs that must not touch the build output.
|
||||||
|
stageFolder() {
|
||||||
|
local dest="$1"
|
||||||
|
local progDir
|
||||||
|
progDir="$(dirname "$program")"
|
||||||
|
mkdir -p "$dest"
|
||||||
|
find "$progDir" -maxdepth 1 -type f -exec cp -t "$dest" {} +
|
||||||
|
if [ -d "$progDir/DATA" ]; then
|
||||||
|
cp -r "$progDir/DATA" "$dest/"
|
||||||
|
fi
|
||||||
|
if [ -n "$dataDir" ]; then
|
||||||
|
mkdir -p "$dest/DATA"
|
||||||
|
cp -r "$dataDir/." "$dest/DATA/"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# startDisplay -- a virtual X display for --headless SDL emulators.
|
||||||
|
startDisplay() {
|
||||||
|
local display
|
||||||
|
display=":$((300 + RANDOM % 500))"
|
||||||
|
Xvfb "$display" -screen 0 1024x768x24 -nolisten tcp >/dev/null 2>&1 &
|
||||||
|
xvfbPid=$!
|
||||||
|
sleep 1
|
||||||
|
export DISPLAY="$display"
|
||||||
|
export SDL_AUDIODRIVER=dummy
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# waitOrStop -- wait for the emulator, or stop it after --seconds (taking
|
||||||
|
# the --snapshot first).
|
||||||
|
waitOrStop() {
|
||||||
|
local waited=0
|
||||||
|
local win
|
||||||
|
if [ -z "$seconds" ]; then
|
||||||
|
wait "$emuPid" || true
|
||||||
|
emuPid=""
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
while [ "$waited" -lt "$seconds" ] && kill -0 "$emuPid" 2>/dev/null; do
|
||||||
|
sleep 1
|
||||||
|
waited=$((waited + 1))
|
||||||
|
done
|
||||||
|
if [ -n "$snapshot" ]; then
|
||||||
|
if [ "$headless" = "1" ]; then
|
||||||
|
import -window root "$snapshot"
|
||||||
|
elif win="$(joeyFindWindow "$DISPLAY" "$emuPid")"; then
|
||||||
|
import -window "$win" "$snapshot"
|
||||||
|
else
|
||||||
|
echo "joey-run: no emulator window to snapshot" >&2
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# mameStopScript FILE -- MAME Lua that snapshots (if asked) and exits after
|
||||||
|
# --seconds of emulated time.
|
||||||
|
mameStopScript() {
|
||||||
|
cat > "$1" <<LUA
|
||||||
|
local done = false
|
||||||
|
emu.register_frame_done(function()
|
||||||
|
if not done and manager.machine.time:as_double() >= $seconds then
|
||||||
|
done = true
|
||||||
|
if $([ -n "$snapshot" ] && echo true || echo false) then
|
||||||
|
manager.machine.video:snapshot()
|
||||||
|
end
|
||||||
|
manager.machine:exit()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
LUA
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# runMame DRIVER ARGS... -- start MAME with the per-run directories, the
|
||||||
|
# headless/debug/stop options, then collect the snapshot.
|
||||||
|
runMame() {
|
||||||
|
local driver="$1"
|
||||||
|
shift
|
||||||
|
local args=(-skip_gameinfo -cfg_directory "$work/cfg" -nvram_directory "$work/nvram"
|
||||||
|
-snapshot_directory "$work/snap" -snapname "shot")
|
||||||
|
if [ "$headless" = "1" ]; then
|
||||||
|
args+=(-video none -sound none -nothrottle)
|
||||||
|
export SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy QT_QPA_PLATFORM=offscreen
|
||||||
|
else
|
||||||
|
args+=(-window)
|
||||||
|
fi
|
||||||
|
if [ "$debug" = "1" ]; then
|
||||||
|
args+=(-debug)
|
||||||
|
fi
|
||||||
|
if [ -n "$seconds" ]; then
|
||||||
|
mameStopScript "$work/stop.lua"
|
||||||
|
args+=(-autoboot_script "$work/stop.lua")
|
||||||
|
fi
|
||||||
|
(cd "$work" && exec "$MAME" "$driver" "$@" "${args[@]}") &
|
||||||
|
emuPid=$!
|
||||||
|
wait "$emuPid" || true
|
||||||
|
emuPid=""
|
||||||
|
if [ -n "$snapshot" ]; then
|
||||||
|
if [ -f "$work/snap/shot.png" ]; then
|
||||||
|
cp "$work/snap/shot.png" "$snapshot"
|
||||||
|
else
|
||||||
|
echo "joey-run: MAME saved no snapshot" >&2
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ---- one function per machine -------------------------------------------------
|
||||||
|
|
||||||
|
runIigs() {
|
||||||
|
local name
|
||||||
|
local img
|
||||||
|
local bytes
|
||||||
|
local media=()
|
||||||
|
joeyNeed MAME mame
|
||||||
|
joeyNeed IIGS_ROMPATH iigsFirmware
|
||||||
|
[ -z "$dataDir" ] || fail "--data is not supported on the IIgs; build a game disk (make -f make/iigs.mk iigs-game-disk-agi for AGI)"
|
||||||
|
case "$program" in
|
||||||
|
*.2mg|*.2MG|*.po|*.PO|*.hdv|*.HDV)
|
||||||
|
joeyNeed CADIUS cadius
|
||||||
|
img="$program"
|
||||||
|
if [ "$headless" = "1" ]; then
|
||||||
|
img="$work/$(basename "$program")"
|
||||||
|
cp "$program" "$img"
|
||||||
|
fi
|
||||||
|
bytes="$(stat -c %s "$img")"
|
||||||
|
if "$CADIUS" CATALOG "$img" 2>/dev/null | grep -qE '^ ProDOS +SYS'; then
|
||||||
|
# A bootable game disk: it launches its program by itself.
|
||||||
|
if [ "$bytes" -le 900000 ]; then
|
||||||
|
media=(-flop3 "$img")
|
||||||
|
else
|
||||||
|
media=(-sl7 cffa2 -hard1 "$img")
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# A data disk: boot GS/OS beside it and pick in the Finder.
|
||||||
|
joeyNeed GSOS_SYSTEM_DISK iigsFirmware
|
||||||
|
cp "$GSOS_SYSTEM_DISK" "$work/boot.po"
|
||||||
|
if [ "$bytes" -le 900000 ]; then
|
||||||
|
media=(-flop3 "$work/boot.po" -flop4 "$img")
|
||||||
|
else
|
||||||
|
media=(-flop3 "$work/boot.po" -sl7 cffa2 -hard1 "$img")
|
||||||
|
fi
|
||||||
|
echo "joey-run: $(basename "$img") is not bootable; GS/OS boots beside it -- open it in the Finder."
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# A bare program: pack it onto a data disk and boot a GS/OS
|
||||||
|
# whose startup program launches it (the developer path; needs
|
||||||
|
# the IIgs toolchain).
|
||||||
|
joeyNeed LLVM816_ROOT toolchain:iigs
|
||||||
|
joeyNeed CADIUS cadius
|
||||||
|
joeyNeed IIGS_CLANG_BUILD toolchain:iigs
|
||||||
|
joeyNeed GSOS_SYSTEM_DISK iigsFirmware
|
||||||
|
name="$(basename "$program")"
|
||||||
|
BINDIR="$(dirname "$program")" JOEY_DISK_EXAMPLES="$name" \
|
||||||
|
"$repo/scripts/make-iigs-disk.sh" "$work/data.2mg" >/dev/null
|
||||||
|
"$repo/scripts/make-iigs-boot-disk.sh" "$work/boot.po" "$name" >/dev/null
|
||||||
|
media=(-flop3 "$work/boot.po" -flop4 "$work/data.2mg")
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
echo "joey-run: booting the IIgs ($machine); the program comes up in about a minute. Close MAME to stop."
|
||||||
|
runMame "$machine" -ramsize "$ramSize" -rompath "$IIGS_ROMPATH" "${media[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
runAmiga() {
|
||||||
|
local name
|
||||||
|
local hd="$work/hd"
|
||||||
|
local args=()
|
||||||
|
joeyNeed FSUAE fsuae
|
||||||
|
name="$(basename "$program")"
|
||||||
|
# FS-UAE boots the folder as a hard drive and writes to it, so it is
|
||||||
|
# always staged. s/startup-sequence runs the program.
|
||||||
|
stageFolder "$hd"
|
||||||
|
mkdir -p "$hd/s"
|
||||||
|
printf ':%s\n' "$name" > "$hd/s/startup-sequence"
|
||||||
|
args=(--amiga_model=A500 --fast_memory=2048
|
||||||
|
--hard_drive_0="$hd" --hard_drive_0_label=JOEYLIB
|
||||||
|
--automatic_input_grab=0 --middle_click_ungrab=1 --mouse_integration=1
|
||||||
|
--floppy_drive_speed=800 --base_dir="$work/fs-uae")
|
||||||
|
if [ -n "${KICKSTART_ROM:-}" ] && [ -f "$KICKSTART_ROM" ]; then
|
||||||
|
args+=(--kickstart_file="$KICKSTART_ROM")
|
||||||
|
else
|
||||||
|
echo "joey-run: no Kickstart installed (./install.sh install kickstart); booting FS-UAE's AROS, which may not run every program" >&2
|
||||||
|
fi
|
||||||
|
if [ "$headless" = "1" ]; then
|
||||||
|
startDisplay
|
||||||
|
# A real gamepad crashes FS-UAE when there is no screen to grab.
|
||||||
|
args+=(--joystick_port_0=none --joystick_port_1=none)
|
||||||
|
export ALSOFT_DRIVERS=null
|
||||||
|
fi
|
||||||
|
echo "joey-run: booting the Amiga; $name starts from the JOEYLIB hard drive. Close FS-UAE to stop."
|
||||||
|
"$FSUAE" "${args[@]}" >"$work/fs-uae.log" 2>&1 &
|
||||||
|
emuPid=$!
|
||||||
|
waitOrStop
|
||||||
|
# Keep the program's log next to it, as a direct run would.
|
||||||
|
find "$hd" -maxdepth 1 -iname 'joeylog.txt' -exec cp {} "$(dirname "$program")/" \; 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
runAtarist() {
|
||||||
|
local dir
|
||||||
|
local args=()
|
||||||
|
joeyNeed HATARI hatari
|
||||||
|
joeyNeed EMUTOS_IMG emutos
|
||||||
|
dir="$(dirname "$program")"
|
||||||
|
if [ "$headless" = "1" ] || [ -n "$dataDir" ]; then
|
||||||
|
stageFolder "$work/hd"
|
||||||
|
dir="$work/hd"
|
||||||
|
fi
|
||||||
|
args=(--tos "$EMUTOS_IMG" --harddrive "$dir" --gemdos-drive C --auto "C:\\$(basename "$program")")
|
||||||
|
if [ "$headless" = "1" ]; then
|
||||||
|
startDisplay
|
||||||
|
args+=(--sound off)
|
||||||
|
fi
|
||||||
|
echo "joey-run: booting the Atari ST (EmuTOS); $(basename "$program") starts from drive C. Close Hatari to stop."
|
||||||
|
"$HATARI" "${args[@]}" >"$work/hatari.log" 2>&1 &
|
||||||
|
emuPid=$!
|
||||||
|
waitOrStop
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
runDos() {
|
||||||
|
local dir
|
||||||
|
local command
|
||||||
|
local args=()
|
||||||
|
joeyNeed DOSBOX dosbox
|
||||||
|
dir="$(dirname "$program")"
|
||||||
|
if [ "$headless" = "1" ] || [ -n "$dataDir" ]; then
|
||||||
|
stageFolder "$work/hd"
|
||||||
|
dir="$work/hd"
|
||||||
|
fi
|
||||||
|
command="$(basename "$program")"
|
||||||
|
if [ ${#progArgs[@]} -gt 0 ]; then
|
||||||
|
command="$command ${progArgs[*]}"
|
||||||
|
fi
|
||||||
|
args=("${JOEY_DOSBOX_ISOLATE[@]}" --conf "$repo/scripts/dosbox-386sx16.conf"
|
||||||
|
--set "mouse_capture=seamless" -c "MOUNT C \"$dir\"" -c "C:" -c "$command")
|
||||||
|
if [ "$headless" = "1" ]; then
|
||||||
|
startDisplay
|
||||||
|
else
|
||||||
|
# Leave the program's last screen up until a key is pressed.
|
||||||
|
args+=(-c "pause")
|
||||||
|
fi
|
||||||
|
args+=(--exit)
|
||||||
|
echo "joey-run: booting DOS (386SX-16); $command runs from drive C. Close DOSBox to stop."
|
||||||
|
XDG_CONFIG_HOME="$work/xdg" "$DOSBOX" "${args[@]}" >"$work/dosbox.log" 2>&1 &
|
||||||
|
emuPid=$!
|
||||||
|
waitOrStop
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
runX68000() {
|
||||||
|
local name
|
||||||
|
local xdf="$repo/tools/xdftool.py"
|
||||||
|
local stage="$work/files"
|
||||||
|
local f
|
||||||
|
local rel
|
||||||
|
joeyNeed MAME mame
|
||||||
|
joeyNeed X68K_DIR x68kFirmware
|
||||||
|
joeyNeedFile "$X68K_DIR/roms/x68000/cgrom.dat" x68kCgrom
|
||||||
|
name="$(basename "$program")"
|
||||||
|
# Human68k boots from a trimmed copy of the system disk whose
|
||||||
|
# AUTOEXEC.BAT runs the program from the data disk in drive B.
|
||||||
|
printf 'B:\r\nB:\\%s\r\n\x1a' "$name" > "$work/AUTOEXEC.BAT"
|
||||||
|
cp "$X68K_DIR/HUMAN302.XDF" "$work/boot.xdf"
|
||||||
|
cp "$X68K_DIR/HUMAN302.XDF" "$work/data.xdf"
|
||||||
|
python3 "$xdf" empty "$work/data.xdf" >/dev/null
|
||||||
|
for f in USKCG.SYS BEEP.SYS KEY.SYS STARTUP.ENV; do
|
||||||
|
python3 "$xdf" delete "$work/boot.xdf" "$f" >/dev/null 2>&1 || true
|
||||||
|
done
|
||||||
|
for f in SYS HIS BIN BASIC2 ASK ETC; do
|
||||||
|
python3 "$xdf" deltree "$work/boot.xdf" "$f" >/dev/null 2>&1 || true
|
||||||
|
done
|
||||||
|
python3 "$xdf" add "$work/boot.xdf" "$work/AUTOEXEC.BAT" AUTOEXEC.BAT >/dev/null
|
||||||
|
python3 "$xdf" add "$work/data.xdf" "$program" "$name" >/dev/null
|
||||||
|
rm -rf "$stage"
|
||||||
|
mkdir -p "$stage"
|
||||||
|
if [ -d "$(dirname "$program")/DATA" ]; then
|
||||||
|
cp -r "$(dirname "$program")/DATA" "$stage/"
|
||||||
|
fi
|
||||||
|
if [ -n "$dataDir" ]; then
|
||||||
|
mkdir -p "$stage/DATA"
|
||||||
|
cp -r "$dataDir/." "$stage/DATA/"
|
||||||
|
fi
|
||||||
|
if [ -d "$stage/DATA" ]; then
|
||||||
|
while IFS= read -r f; do
|
||||||
|
rel="${f#"$stage"/}"
|
||||||
|
python3 "$xdf" add "$work/data.xdf" "$f" "${rel^^}" >/dev/null
|
||||||
|
done < <(find "$stage/DATA" -type f | sort)
|
||||||
|
fi
|
||||||
|
echo "joey-run: booting the X68000 (Human68k 3.02); $name runs from drive B. Close MAME to stop."
|
||||||
|
runMame x68000 -bios ipl10 -rompath "$X68K_DIR/roms" -flop1 "$work/boot.xdf" -flop2 "$work/data.xdf"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ---- main ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--port) port="${2:?--port needs iigs, amiga, atarist, dos or x68000}"; shift ;;
|
||||||
|
--data) dataDir="${2:?--data needs a folder}"; shift ;;
|
||||||
|
--headless) headless=1 ;;
|
||||||
|
--seconds) seconds="${2:?--seconds needs a number}"; shift ;;
|
||||||
|
--snapshot) snapshot="${2:?--snapshot needs a file name}"; shift ;;
|
||||||
|
--machine)
|
||||||
|
case "${2:-}" in
|
||||||
|
rom01) machine="apple2gsr1" ;;
|
||||||
|
rom03) machine="apple2gs" ;;
|
||||||
|
*) fail "--machine takes rom01 or rom03" ;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--ram) ramSize="${2:?--ram needs a size such as 4M}"; shift ;;
|
||||||
|
--debug) debug=1 ;;
|
||||||
|
-h|--help) usage; exit 0 ;;
|
||||||
|
--) shift; progArgs=("$@"); break ;;
|
||||||
|
-*) fail "unknown option $1 (see: joey-run --help)" ;;
|
||||||
|
*)
|
||||||
|
[ -z "$program" ] || fail "one program at a time (got '$program' and '$1')"
|
||||||
|
program="$1"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
[ -n "$program" ] || { usage; exit 2; }
|
||||||
|
[[ "$seconds" =~ ^[0-9]*$ ]] || fail "--seconds takes a whole number"
|
||||||
|
if [ "$headless" = "1" ] && [ -z "$seconds" ]; then
|
||||||
|
seconds=60
|
||||||
|
fi
|
||||||
|
if [ -n "$snapshot" ]; then
|
||||||
|
[ -n "$seconds" ] || fail "--snapshot needs --seconds (or --headless)"
|
||||||
|
snapshot="$(realpath -m "$snapshot")"
|
||||||
|
fi
|
||||||
|
if [ -n "$dataDir" ]; then
|
||||||
|
[ -d "$dataDir" ] || fail "--data: no such folder $dataDir"
|
||||||
|
dataDir="$(cd "$dataDir" && pwd)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Resolve PROGRAM to a file and its machine.
|
||||||
|
if [ -d "$program" ]; then
|
||||||
|
program="$(folderProgram "$program")"
|
||||||
|
elif [ ! -e "$program" ]; then
|
||||||
|
[ -n "$port" ] || fail "$program is not a file; for an example name add --port"
|
||||||
|
program="$(exampleProgram "$program" "$port")"
|
||||||
|
[ -e "$program" ] || fail "$program is not built (make $port)"
|
||||||
|
fi
|
||||||
|
program="$(cd "$(dirname "$program")" && pwd)/$(basename "$program")"
|
||||||
|
detected="$(magicPort "$program")"
|
||||||
|
[ -n "$detected" ] || fail "cannot tell which machine $program is for"
|
||||||
|
if [ -n "$port" ] && [ "$port" != "$detected" ]; then
|
||||||
|
fail "$program is a $detected program, not $port"
|
||||||
|
fi
|
||||||
|
if [ ${#progArgs[@]} -gt 0 ] && [ "$detected" != "dos" ]; then
|
||||||
|
fail "program arguments only work on DOS"
|
||||||
|
fi
|
||||||
|
|
||||||
|
work="$(mktemp -d -t joey-run.XXXXXX)"
|
||||||
|
trap cleanup EXIT INT TERM
|
||||||
|
|
||||||
|
case "$detected" in
|
||||||
|
iigs) runIigs ;;
|
||||||
|
amiga) runAmiga ;;
|
||||||
|
atarist) runAtarist ;;
|
||||||
|
dos) runDos ;;
|
||||||
|
x68000) runX68000 ;;
|
||||||
|
esac
|
||||||
|
if [ -n "$snapshot" ] && [ -f "$snapshot" ]; then
|
||||||
|
echo "joey-run: snapshot saved to $snapshot"
|
||||||
|
fi
|
||||||
|
|
@ -2,17 +2,21 @@
|
||||||
|
|
||||||
include $(dir $(lastword $(MAKEFILE_LIST)))/common.mk
|
include $(dir $(lastword $(MAKEFILE_LIST)))/common.mk
|
||||||
|
|
||||||
|
$(call requireTool,$(AMIGA_CC),toolchain:amiga)
|
||||||
|
$(call requireTool,$(AMIGA_AS),toolchain:amiga)
|
||||||
|
$(call requireTool,$(AMIGA_PTPLAYER)/ptplayer.asm,toolchain:amiga)
|
||||||
|
|
||||||
PLATFORM := amiga
|
PLATFORM := amiga
|
||||||
BUILD := $(REPO_DIR)/build/$(PLATFORM)
|
BUILD := $(REPO_DIR)/build/$(PLATFORM)
|
||||||
LIBDIR := $(BUILD)/lib
|
LIBDIR := $(BUILD)/lib
|
||||||
BINDIR := $(BUILD)/bin
|
BINDIR := $(BUILD)/bin
|
||||||
|
|
||||||
# PTPlayer is staged by toolchains/install.sh into PTPLAYER_DIR; we
|
# PTPlayer is installed by ./install.sh (AMIGA_PTPLAYER); we
|
||||||
# reference its ptplayer.asm + header from there rather than copying
|
# reference its ptplayer.asm + header from there rather than copying
|
||||||
# them into the source tree, so install.sh can refresh / version it
|
# them into the source tree, so the installer can refresh / version it
|
||||||
# independently. -I on $(SRC_DIR)/amiga lets ptplayer.h resolve
|
# independently. -I on $(SRC_DIR)/amiga lets ptplayer.h resolve
|
||||||
# <SDI_compiler.h> from the machine-local shim alongside our code.
|
# <SDI_compiler.h> from the machine-local shim alongside our code.
|
||||||
PTPLAYER_DIR := $(REPO_DIR)/toolchains/amiga/ptplayer
|
PTPLAYER_DIR := $(AMIGA_PTPLAYER)
|
||||||
CFLAGS := $(COMMON_CFLAGS) -m68000 -fomit-frame-pointer -noixemul -D__OSCOMPAT -I$(SRC_DIR)/amiga -I$(SRC_68K) -I$(PTPLAYER_DIR) -MMD -MP $(CFLAGS_EXTRA)
|
CFLAGS := $(COMMON_CFLAGS) -m68000 -fomit-frame-pointer -noixemul -D__OSCOMPAT -I$(SRC_DIR)/amiga -I$(SRC_68K) -I$(PTPLAYER_DIR) -MMD -MP $(CFLAGS_EXTRA)
|
||||||
# OSCOMPAT=1 selects PTPlayer's audio.device-friendly variant (uses
|
# OSCOMPAT=1 selects PTPlayer's audio.device-friendly variant (uses
|
||||||
# CIA-B + audio.device interrupts via the OS rather than taking over
|
# CIA-B + audio.device interrupts via the OS rather than taking over
|
||||||
|
|
@ -101,10 +105,7 @@ STAXI_GEN_DIR := $(EXAMPLES)/spacetaxi/generated/amiga
|
||||||
STAXI_RUN_DIR := $(STAXI_INSTALL_DIR)/DATA
|
STAXI_RUN_DIR := $(STAXI_INSTALL_DIR)/DATA
|
||||||
# Level data is extracted from the C64 ROM (see dos.mk for the long
|
# Level data is extracted from the C64 ROM (see dos.mk for the long
|
||||||
# explanation): 24 lettered levels A..X from scenes 0..23 plus the
|
# explanation): 24 lettered levels A..X from scenes 0..23 plus the
|
||||||
# title from scene 24, via stuff/spacetaxi/romToLevel.py.
|
# title from scene 24, via examples/spacetaxi/assets/romToLevel.py.
|
||||||
STAXI_RAW_DUMP := $(REPO_DIR)/stuff/spacetaxi/raw.bin
|
|
||||||
ROMTOLEVEL_BIN := $(REPO_DIR)/stuff/spacetaxi/romToLevel.py
|
|
||||||
STAXI_LEVEL_NUMS := 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
|
||||||
STAXI_ROM_LEVEL_GEN := $(foreach n,$(STAXI_LEVEL_NUMS),$(STAXI_GEN_DIR)/levels/level$(n).dat) \
|
STAXI_ROM_LEVEL_GEN := $(foreach n,$(STAXI_LEVEL_NUMS),$(STAXI_GEN_DIR)/levels/level$(n).dat) \
|
||||||
$(STAXI_GEN_DIR)/levels/title.dat
|
$(STAXI_GEN_DIR)/levels/title.dat
|
||||||
STAXI_LEVEL_GEN := $(STAXI_ROM_LEVEL_GEN)
|
STAXI_LEVEL_GEN := $(STAXI_ROM_LEVEL_GEN)
|
||||||
|
|
@ -165,13 +166,13 @@ ASSETBAKE_BIN := $(REPO_DIR)/tools/assetbake/assetbake.py
|
||||||
ASSETBAKE_TARGET := amiga
|
ASSETBAKE_TARGET := amiga
|
||||||
|
|
||||||
# Game data lives under bin/DATA/, ready to be copied into the
|
# Game data lives under bin/DATA/, ready to be copied into the
|
||||||
# scratch JOEYLIB hard-drive dir staged by scripts/run-amiga.sh.
|
# scratch JOEYLIB hard-drive dir staged by joey-run.
|
||||||
# audio.c fopens "DATA/test.mod" etc. relative to the boot volume.
|
# audio.c fopens "DATA/test.mod" etc. relative to the boot volume.
|
||||||
DATA_DIR := $(BINDIR)/DATA
|
DATA_DIR := $(BINDIR)/DATA
|
||||||
DATA_FILES := $(DATA_DIR)/test.mod $(DATA_DIR)/test.sfx
|
DATA_FILES := $(DATA_DIR)/test.mod $(DATA_DIR)/test.sfx
|
||||||
|
|
||||||
.PHONY: all amiga clean-amiga clean
|
.PHONY: all amiga clean-amiga clean
|
||||||
all amiga: $(LIB) $(HELLO_BIN) $(PATTERN_BIN) $(DRAW_BIN) $(KEYS_BIN) $(SERIAL_BIN) $(SERTEST_BIN) $(SAVE_BIN) $(JOY_BIN) $(SPRITE_BIN) $(AUDIO_BIN) $(UBER_BIN) $(ADV_BIN) $(ADV2_BIN) $(AGI_BIN) $(STAXI_BIN) $(DATA_FILES) $(STAXI_ASSET_DSTS)
|
all amiga: $(LIB) $(HELLO_BIN) $(PATTERN_BIN) $(DRAW_BIN) $(KEYS_BIN) $(SERIAL_BIN) $(SERTEST_BIN) $(SAVE_BIN) $(JOY_BIN) $(SPRITE_BIN) $(AUDIO_BIN) $(UBER_BIN) $(ADV_BIN) $(ADV2_BIN) $(AGI_BIN) $(STAXI_BIN) $(DATA_FILES) $(call staxiData,$(STAXI_ASSET_DSTS))
|
||||||
|
|
||||||
$(BUILD)/obj/core/%.o: $(SRC_CORE)/%.c
|
$(BUILD)/obj/core/%.o: $(SRC_CORE)/%.c
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,24 @@
|
||||||
|
|
||||||
include $(dir $(lastword $(MAKEFILE_LIST)))/common.mk
|
include $(dir $(lastword $(MAKEFILE_LIST)))/common.mk
|
||||||
|
|
||||||
|
$(call requireTool,$(ST_CC),toolchain:atarist)
|
||||||
|
$(call requireTool,$(LIBXMP_DIR)/include/libxmp-lite/xmp.h,toolchain:atarist)
|
||||||
|
|
||||||
PLATFORM := atarist
|
PLATFORM := atarist
|
||||||
|
ST_SHIM := $(REPO_DIR)/make/support/atarist/include-shim
|
||||||
BUILD := $(REPO_DIR)/build/$(PLATFORM)
|
BUILD := $(REPO_DIR)/build/$(PLATFORM)
|
||||||
LIBDIR := $(BUILD)/lib
|
LIBDIR := $(BUILD)/lib
|
||||||
BINDIR := $(BUILD)/bin
|
BINDIR := $(BUILD)/bin
|
||||||
|
|
||||||
# CFLAGS_EXTRA: command-line A/B hook (e.g. make CFLAGS_EXTRA=-DX)
|
# CFLAGS_EXTRA: command-line A/B hook (e.g. make CFLAGS_EXTRA=-DX)
|
||||||
# so flag experiments never edit a tracked file (perf-hunt-2 rank 17).
|
# so flag experiments never edit a tracked file (perf-hunt-2 rank 17).
|
||||||
CFLAGS := $(COMMON_CFLAGS) -m68000 -fomit-frame-pointer -I$(REPO_DIR)/toolchains/audio/libxmp-lite/include -I$(REPO_DIR)/toolchains/atarist/include-shim -I$(SRC_DIR)/atarist -I$(SRC_68K) -MMD -MP $(CFLAGS_EXTRA)
|
CFLAGS := $(COMMON_CFLAGS) -m68000 -fomit-frame-pointer -I$(LIBXMP_DIR)/include -I$(ST_SHIM) -I$(SRC_DIR)/atarist -I$(SRC_68K) -MMD -MP $(CFLAGS_EXTRA)
|
||||||
LDFLAGS :=
|
LDFLAGS :=
|
||||||
|
|
||||||
# libxmp-lite shared with the DOS port. Built as a static archive that
|
# libxmp-lite shared with the DOS port. Built as a static archive that
|
||||||
# provides the Protracker pattern decoder for the ST audio HAL; the
|
# provides the Protracker pattern decoder for the ST audio HAL; the
|
||||||
# 68k 4-channel mixer + YM2149 PWM output stage layer on top.
|
# 68k 4-channel mixer + YM2149 PWM output stage layer on top.
|
||||||
LIBXMP_DIR := $(REPO_DIR)/toolchains/audio/libxmp-lite
|
# LIBXMP_DIR comes from the installer's config.mk.
|
||||||
LIBXMP_SRC := $(filter-out %/win32.c, $(wildcard $(LIBXMP_DIR)/src/*.c) $(wildcard $(LIBXMP_DIR)/src/loaders/*.c))
|
LIBXMP_SRC := $(filter-out %/win32.c, $(wildcard $(LIBXMP_DIR)/src/*.c) $(wildcard $(LIBXMP_DIR)/src/loaders/*.c))
|
||||||
LIBXMP_OBJDIR := $(BUILD)/obj/libxmp-lite
|
LIBXMP_OBJDIR := $(BUILD)/obj/libxmp-lite
|
||||||
LIBXMP_OBJS := $(patsubst $(LIBXMP_DIR)/src/%.c,$(LIBXMP_OBJDIR)/%.o,$(LIBXMP_SRC)) $(LIBXMP_OBJDIR)/math_shim.o
|
LIBXMP_OBJS := $(patsubst $(LIBXMP_DIR)/src/%.c,$(LIBXMP_OBJDIR)/%.o,$(LIBXMP_SRC)) $(LIBXMP_OBJDIR)/math_shim.o
|
||||||
|
|
@ -85,10 +89,7 @@ STAXI_GEN_DIR := $(EXAMPLES)/spacetaxi/generated/atarist
|
||||||
STAXI_RUN_DIR := $(STAXI_INSTALL_DIR)/DATA
|
STAXI_RUN_DIR := $(STAXI_INSTALL_DIR)/DATA
|
||||||
# Level data is extracted from the C64 ROM (see dos.mk for the long
|
# Level data is extracted from the C64 ROM (see dos.mk for the long
|
||||||
# explanation): 24 lettered levels A..X from scenes 0..23 plus the
|
# explanation): 24 lettered levels A..X from scenes 0..23 plus the
|
||||||
# title from scene 24, via stuff/spacetaxi/romToLevel.py.
|
# title from scene 24, via examples/spacetaxi/assets/romToLevel.py.
|
||||||
STAXI_RAW_DUMP := $(REPO_DIR)/stuff/spacetaxi/raw.bin
|
|
||||||
ROMTOLEVEL_BIN := $(REPO_DIR)/stuff/spacetaxi/romToLevel.py
|
|
||||||
STAXI_LEVEL_NUMS := 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
|
||||||
STAXI_ROM_LEVEL_GEN := $(foreach n,$(STAXI_LEVEL_NUMS),$(STAXI_GEN_DIR)/levels/level$(n).dat) \
|
STAXI_ROM_LEVEL_GEN := $(foreach n,$(STAXI_LEVEL_NUMS),$(STAXI_GEN_DIR)/levels/level$(n).dat) \
|
||||||
$(STAXI_GEN_DIR)/levels/title.dat
|
$(STAXI_GEN_DIR)/levels/title.dat
|
||||||
STAXI_LEVEL_GEN := $(STAXI_ROM_LEVEL_GEN)
|
STAXI_LEVEL_GEN := $(STAXI_ROM_LEVEL_GEN)
|
||||||
|
|
@ -151,7 +152,7 @@ DATA_DIR := $(BINDIR)/DATA
|
||||||
DATA_FILES := $(DATA_DIR)/test.mod $(DATA_DIR)/test.sfx
|
DATA_FILES := $(DATA_DIR)/test.mod $(DATA_DIR)/test.sfx
|
||||||
|
|
||||||
.PHONY: all atarist clean-atarist clean
|
.PHONY: all atarist clean-atarist clean
|
||||||
all atarist: $(LIB) $(LIBXMP_AR) $(HELLO_BIN) $(PATTERN_BIN) $(DRAW_BIN) $(KEYS_BIN) $(SERIAL_BIN) $(SERTEST_BIN) $(SAVE_BIN) $(JOY_BIN) $(SPRITE_BIN) $(AUDIO_BIN) $(UBER_BIN) $(ADV_BIN) $(ADV2_BIN) $(AGI_BIN) $(STAXI_BIN) $(DATA_FILES) $(STAXI_ASSET_DSTS)
|
all atarist: $(LIB) $(LIBXMP_AR) $(HELLO_BIN) $(PATTERN_BIN) $(DRAW_BIN) $(KEYS_BIN) $(SERIAL_BIN) $(SERTEST_BIN) $(SAVE_BIN) $(JOY_BIN) $(SPRITE_BIN) $(AUDIO_BIN) $(UBER_BIN) $(ADV_BIN) $(ADV2_BIN) $(AGI_BIN) $(STAXI_BIN) $(DATA_FILES) $(call staxiData,$(STAXI_ASSET_DSTS))
|
||||||
|
|
||||||
$(BUILD)/obj/core/%.o: $(SRC_CORE)/%.c
|
$(BUILD)/obj/core/%.o: $(SRC_CORE)/%.c
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
|
|
@ -194,7 +195,7 @@ $(LIBXMP_OBJDIR)/%.o: $(LIBXMP_DIR)/src/%.c
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
$(ST_CC) $(CFLAGS) $(LIBXMP_CFLAGS) -c $< -o $@
|
$(ST_CC) $(CFLAGS) $(LIBXMP_CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
$(LIBXMP_OBJDIR)/math_shim.o: $(REPO_DIR)/toolchains/atarist/include-shim/math_shim.c
|
$(LIBXMP_OBJDIR)/math_shim.o: $(ST_SHIM)/math_shim.c
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
$(ST_CC) $(CFLAGS) -c $< -o $@
|
$(ST_CC) $(CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,30 @@
|
||||||
# Source this only via the per-target make file (make/iigs.mk etc).
|
# Source this only via the per-target make file (make/iigs.mk etc).
|
||||||
|
|
||||||
REPO_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))/..)
|
REPO_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))/..)
|
||||||
|
|
||||||
|
# Tool paths (AMIGA_CC, LLVM816_ROOT, LIBXMP_DIR, ...) come from the file
|
||||||
|
# install.sh generates; it is the only place they are defined.
|
||||||
|
-include $(REPO_DIR)/toolchains/config.mk
|
||||||
|
|
||||||
|
# $(call requireTool,FILE,INSTALL-TARGET): stop with a clear message when a
|
||||||
|
# port's compiler is missing, instead of letting recipes run an empty
|
||||||
|
# command. Goals that only clean need no toolchain. The missing path is not
|
||||||
|
# printed: with nothing installed it is empty or a bare suffix, and
|
||||||
|
# `./install.sh doctor` names exactly what is wrong.
|
||||||
|
requireTool = $(if $(filter-out clean%,$(or $(MAKECMDGOALS),all)),$(if $(wildcard $(1)),,$(error $(2) is not installed; run: ./install.sh install $(2))))
|
||||||
|
|
||||||
|
# Space Taxi's levels are extracted from the original C64 game image, which
|
||||||
|
# is not part of the repository (the owner's private bundle provides it; see
|
||||||
|
# ./install.sh import). Without it every port still builds the
|
||||||
|
# STAXI binary and skips only the data derived from the image.
|
||||||
|
STAXI_RAW_DUMP := $(REPO_DIR)/stuff/spacetaxi/raw.bin
|
||||||
|
ROMTOLEVEL_BIN := $(REPO_DIR)/examples/spacetaxi/assets/romToLevel.py
|
||||||
|
STAXI_LEVEL_NUMS := 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
||||||
|
HAVE_STAXI_DATA := $(if $(wildcard $(STAXI_RAW_DUMP)),1)
|
||||||
|
# $(call staxiData,TARGETS): TARGETS, or nothing (with a note, once per make
|
||||||
|
# process) without the image.
|
||||||
|
staxiData = $(if $(HAVE_STAXI_DATA),$(1),$(staxiNote))
|
||||||
|
staxiNote = $(if $(staxiNoted),,$(eval staxiNoted := 1)$(info note: Space Taxi data skipped: stuff/spacetaxi/raw.bin is not present))
|
||||||
INCLUDE_DIR := $(REPO_DIR)/include
|
INCLUDE_DIR := $(REPO_DIR)/include
|
||||||
# Machine-specific code lives in src/<machine>/ (iigs, amiga, atarist, dos);
|
# Machine-specific code lives in src/<machine>/ (iigs, amiga, atarist, dos);
|
||||||
# code shared across machines of one CPU lives in a processor folder (src/m68k).
|
# code shared across machines of one CPU lives in a processor folder (src/m68k).
|
||||||
|
|
|
||||||
21
make/dos.mk
21
make/dos.mk
|
|
@ -2,6 +2,11 @@
|
||||||
|
|
||||||
include $(dir $(lastword $(MAKEFILE_LIST)))/common.mk
|
include $(dir $(lastword $(MAKEFILE_LIST)))/common.mk
|
||||||
|
|
||||||
|
$(call requireTool,$(DOS_CC),toolchain:dos)
|
||||||
|
$(call requireTool,$(DOS_AS),toolchain:dos)
|
||||||
|
$(call requireTool,$(DOS_CWSDSTUB),toolchain:dos)
|
||||||
|
$(call requireTool,$(LIBXMP_DIR)/include/libxmp-lite/xmp.h,toolchain:dos)
|
||||||
|
|
||||||
PLATFORM := dos
|
PLATFORM := dos
|
||||||
BUILD := $(REPO_DIR)/build/$(PLATFORM)
|
BUILD := $(REPO_DIR)/build/$(PLATFORM)
|
||||||
LIBDIR := $(BUILD)/lib
|
LIBDIR := $(BUILD)/lib
|
||||||
|
|
@ -12,15 +17,14 @@ BINDIR := $(BUILD)/bin
|
||||||
# DOSBox's per-instruction-charged core (perf-hunt-2 rank 2).
|
# DOSBox's per-instruction-charged core (perf-hunt-2 rank 2).
|
||||||
# CFLAGS_EXTRA: command-line A/B hook (e.g. make CFLAGS_EXTRA=-DX)
|
# CFLAGS_EXTRA: command-line A/B hook (e.g. make CFLAGS_EXTRA=-DX)
|
||||||
# so flag experiments never edit a tracked file (perf-hunt-2 rank 17).
|
# so flag experiments never edit a tracked file (perf-hunt-2 rank 17).
|
||||||
CFLAGS := $(COMMON_CFLAGS) -march=i386 -m32 -fomit-frame-pointer -I$(SRC_DIR)/dos -I$(REPO_DIR)/toolchains/audio/libxmp-lite/include -MMD -MP $(CFLAGS_EXTRA)
|
CFLAGS := $(COMMON_CFLAGS) -march=i386 -m32 -fomit-frame-pointer -I$(SRC_DIR)/dos -I$(LIBXMP_DIR)/include -MMD -MP $(CFLAGS_EXTRA)
|
||||||
ASFLAGS := -f coff
|
ASFLAGS := -f coff
|
||||||
LDFLAGS :=
|
LDFLAGS :=
|
||||||
|
|
||||||
# libxmp-lite (MIT, libxmp.org) -- 4-channel-Protracker-and-friends
|
# libxmp-lite (MIT, libxmp.org) -- 4-channel-Protracker-and-friends
|
||||||
# replayer + SMIX SFX overlay. Staged at toolchains/audio/libxmp-lite/
|
# replayer + SMIX SFX overlay. Installed by ./install.sh
|
||||||
# (currently checked in; install.sh wiring is a follow-up). Built as
|
# (LIBXMP_DIR, from config.mk). Built as a static archive that the DOS
|
||||||
# a static archive that the DOS audio HAL links against.
|
# audio HAL links against.
|
||||||
LIBXMP_DIR := $(REPO_DIR)/toolchains/audio/libxmp-lite
|
|
||||||
LIBXMP_SRC := $(filter-out %/win32.c, $(wildcard $(LIBXMP_DIR)/src/*.c) $(wildcard $(LIBXMP_DIR)/src/loaders/*.c))
|
LIBXMP_SRC := $(filter-out %/win32.c, $(wildcard $(LIBXMP_DIR)/src/*.c) $(wildcard $(LIBXMP_DIR)/src/loaders/*.c))
|
||||||
LIBXMP_OBJDIR := $(BUILD)/obj/libxmp-lite
|
LIBXMP_OBJDIR := $(BUILD)/obj/libxmp-lite
|
||||||
LIBXMP_OBJS := $(patsubst $(LIBXMP_DIR)/src/%.c,$(LIBXMP_OBJDIR)/%.o,$(LIBXMP_SRC))
|
LIBXMP_OBJS := $(patsubst $(LIBXMP_DIR)/src/%.c,$(LIBXMP_OBJDIR)/%.o,$(LIBXMP_SRC))
|
||||||
|
|
@ -94,16 +98,13 @@ STAXI_GEN_DIR := $(EXAMPLES)/spacetaxi/generated/dos
|
||||||
STAXI_RUN_DIR := $(STAXI_INSTALL_DIR)/DATA
|
STAXI_RUN_DIR := $(STAXI_INSTALL_DIR)/DATA
|
||||||
|
|
||||||
# Level data is extracted from the C64 ROM by
|
# Level data is extracted from the C64 ROM by
|
||||||
# stuff/spacetaxi/romToLevel.py: 24 lettered levels (A..X) from scenes
|
# examples/spacetaxi/assets/romToLevel.py: 24 lettered levels (A..X) from scenes
|
||||||
# 0..23 plus the title from scene 24. The script runs the captured
|
# 0..23 plus the title from scene 24. The script runs the captured
|
||||||
# decompressor through a Python 6502 emulator and writes STL2 .dat
|
# decompressor through a Python 6502 emulator and writes STL2 .dat
|
||||||
# files directly. Input is the raw RAM dump. mkstlevel + title.txt
|
# files directly. Input is the raw RAM dump. mkstlevel + title.txt
|
||||||
# is no longer used: the C64 title encodes its screen with the game's
|
# is no longer used: the C64 title encodes its screen with the game's
|
||||||
# custom charset (extracted into tbank0.png), so a hand-authored tile
|
# custom charset (extracted into tbank0.png), so a hand-authored tile
|
||||||
# layout with a different glyph mapping renders as garbage.
|
# layout with a different glyph mapping renders as garbage.
|
||||||
STAXI_RAW_DUMP := $(REPO_DIR)/stuff/spacetaxi/raw.bin
|
|
||||||
ROMTOLEVEL_BIN := $(REPO_DIR)/stuff/spacetaxi/romToLevel.py
|
|
||||||
STAXI_LEVEL_NUMS := 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
|
||||||
STAXI_ROM_LEVEL_GEN := $(foreach n,$(STAXI_LEVEL_NUMS),$(STAXI_GEN_DIR)/levels/level$(n).dat) \
|
STAXI_ROM_LEVEL_GEN := $(foreach n,$(STAXI_LEVEL_NUMS),$(STAXI_GEN_DIR)/levels/level$(n).dat) \
|
||||||
$(STAXI_GEN_DIR)/levels/title.dat
|
$(STAXI_GEN_DIR)/levels/title.dat
|
||||||
STAXI_LEVEL_GEN := $(STAXI_ROM_LEVEL_GEN)
|
STAXI_LEVEL_GEN := $(STAXI_ROM_LEVEL_GEN)
|
||||||
|
|
@ -166,7 +167,7 @@ STAXI_SPC_RUN := $(patsubst $(STAXI_GEN_DIR)/%,$(STAXI_RUN_DIR)/%,$(STAXI_SP
|
||||||
MKSTLEVEL_BIN := $(REPO_DIR)/build/tools/mkstlevel
|
MKSTLEVEL_BIN := $(REPO_DIR)/build/tools/mkstlevel
|
||||||
|
|
||||||
.PHONY: all dos clean-dos clean
|
.PHONY: all dos clean-dos clean
|
||||||
all dos: $(LIB) $(LIBXMP_AR) $(HELLO_BIN) $(PATTERN_BIN) $(DRAW_BIN) $(KEYS_BIN) $(SERIAL_BIN) $(SERTEST_BIN) $(SAVE_BIN) $(JOY_BIN) $(SPRITE_BIN) $(AUDIO_BIN) $(UBER_BIN) $(ADV_BIN) $(ADV2_BIN) $(AGI_BIN) $(STAXI_BIN) $(DATA_FILES) $(STAXI_ASSET_DSTS)
|
all dos: $(LIB) $(LIBXMP_AR) $(HELLO_BIN) $(PATTERN_BIN) $(DRAW_BIN) $(KEYS_BIN) $(SERIAL_BIN) $(SERTEST_BIN) $(SAVE_BIN) $(JOY_BIN) $(SPRITE_BIN) $(AUDIO_BIN) $(UBER_BIN) $(ADV_BIN) $(ADV2_BIN) $(AGI_BIN) $(STAXI_BIN) $(DATA_FILES) $(call staxiData,$(STAXI_ASSET_DSTS))
|
||||||
|
|
||||||
$(BUILD)/obj/core/%.o: $(SRC_CORE)/%.c
|
$(BUILD)/obj/core/%.o: $(SRC_CORE)/%.c
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
|
|
|
||||||
56
make/iigs.mk
56
make/iigs.mk
|
|
@ -1,6 +1,6 @@
|
||||||
# Apple IIgs build rules (llvm-mos w65816: clang + llvm-mc + link816).
|
# Apple IIgs build rules (llvm-mos w65816: clang + llvm-mc + link816).
|
||||||
#
|
#
|
||||||
# toolchains/iigs/clang-build.sh drives compile (clang) / assemble
|
# make/support/iigs/clang-build.sh drives compile (clang) / assemble
|
||||||
# (llvm-mc) / link (link816) against the prebuilt w65816 runtime.
|
# (llvm-mc) / link (link816) against the prebuilt w65816 runtime.
|
||||||
# Hand-rolled asm lives in src/iigs/*.s (llvm-mc GAS syntax);
|
# Hand-rolled asm lives in src/iigs/*.s (llvm-mc GAS syntax);
|
||||||
# bank placement is handled by -ffunction-sections + link816
|
# bank placement is handled by -ffunction-sections + link816
|
||||||
|
|
@ -9,8 +9,8 @@
|
||||||
# STATUS: the launchable-binary pipeline (multi-segment OMF emission +
|
# STATUS: the launchable-binary pipeline (multi-segment OMF emission +
|
||||||
# disk packaging + GS/OS launch) is still being brought up. `iigs-lib`
|
# disk packaging + GS/OS launch) is still being brought up. `iigs-lib`
|
||||||
# compile-checks every library + example translation unit, and
|
# compile-checks every library + example translation unit, and
|
||||||
# `iigs-clang-smoke` validates the full clang -> llvm-mc -> link816 ->
|
# `iigs-clang-smoke` (not part of the default goal: it needs MAME)
|
||||||
# MAME path end to end. Per-example binary targets build via
|
# validates the full clang -> llvm-mc -> link816 -> MAME path end to end. Per-example binary targets build via
|
||||||
# clang-build.sh; large examples need the multi-segment OMF wiring still
|
# clang-build.sh; large examples need the multi-segment OMF wiring still
|
||||||
# in progress.
|
# in progress.
|
||||||
|
|
||||||
|
|
@ -22,8 +22,11 @@ BINDIR := $(BUILD)/bin
|
||||||
OBJDIR := $(BUILD)/obj
|
OBJDIR := $(BUILD)/obj
|
||||||
|
|
||||||
CLANG := $(LLVM816_ROOT)/tools/llvm-mos-build/bin/clang
|
CLANG := $(LLVM816_ROOT)/tools/llvm-mos-build/bin/clang
|
||||||
|
$(call requireTool,$(CLANG),toolchain:iigs)
|
||||||
|
$(call requireTool,$(IIGS_AS),toolchain:iigs)
|
||||||
|
$(call requireTool,$(IIGS_NTP_DIR)/ninjatrackerplus.s,toolchain:iigs)
|
||||||
LLVM_MC := $(LLVM816_ROOT)/tools/llvm-mos-build/bin/llvm-mc
|
LLVM_MC := $(LLVM816_ROOT)/tools/llvm-mos-build/bin/llvm-mc
|
||||||
AR := $(LLVM816_ROOT)/tools/llvm-mos-sdk/bin/llvm-ar
|
AR := $(LLVM816_ROOT)/tools/llvm-mos-build/bin/llvm-ar
|
||||||
CCWRAP := $(LLVM816_ROOT)/scripts/ccRegallocFallback.sh
|
CCWRAP := $(LLVM816_ROOT)/scripts/ccRegallocFallback.sh
|
||||||
RT_INC := $(LLVM816_ROOT)/runtime/include
|
RT_INC := $(LLVM816_ROOT)/runtime/include
|
||||||
CFLAGS := --target=w65816 -O2 -ffreestanding -ffunction-sections $(EXTRA_CFLAGS)
|
CFLAGS := --target=w65816 -O2 -ffreestanding -ffunction-sections $(EXTRA_CFLAGS)
|
||||||
|
|
@ -70,19 +73,17 @@ STAXI_SRCS := $(EXAMPLES)/spacetaxi/stC64Data.c $(EXAMPLES)/spacetaxi/stCels.c
|
||||||
AUDIO_SRC := $(EXAMPLES)/audio/audio.c
|
AUDIO_SRC := $(EXAMPLES)/audio/audio.c
|
||||||
|
|
||||||
# NinjaTrackerPlus replayer: Merlin32-assemble ninjatrackerplus.s to a 34KB
|
# NinjaTrackerPlus replayer: Merlin32-assemble ninjatrackerplus.s to a 34KB
|
||||||
# binary, then bake it into a dead-strippable GAS data object (gNtpPlayerBytes
|
# binary that ships next to the program; jlpAudioInit loads "ntpplayer" from
|
||||||
# / _len, which audio_full.c externs). bin-to-s.sh is the clang-era replacement
|
# disk at run time (see the $(BINDIR)/AUDIO rule).
|
||||||
# for the deleted bin-to-asm.sh. Only the AUDIO binary pulls it in; link816
|
NTP_SRC := $(IIGS_NTP_DIR)/ninjatrackerplus.s
|
||||||
# dead-strips it everywhere else.
|
|
||||||
NTP_SRC := $(REPO_DIR)/toolchains/iigs/ntp/ninjatrackerplus.s
|
|
||||||
NTP_BIN := $(BUILD)/audio/ntpplayer.bin
|
NTP_BIN := $(BUILD)/audio/ntpplayer.bin
|
||||||
NTP_ASM := $(BUILD)/audio/ntpdata.s
|
IIGS_MERLIN := $(IIGS_AS)
|
||||||
IIGS_MERLIN := $(REPO_DIR)/toolchains/iigs/merlin32/bin/merlin32
|
|
||||||
|
|
||||||
.PHONY: all iigs iigs-lib iigs-clang-smoke iigs-examples iigs-disk iigs-disk-all iigs-game-disk iigs-game-disks iigs-game-disk-agi iigs-verify-game-disk iigs-verify iigs-verify-all iigs-verify-save iigs-verify-serial iigs-verify-shrtail clean-iigs clean
|
.PHONY: all iigs iigs-lib iigs-clang-smoke iigs-examples iigs-disk iigs-disk-all iigs-game-disk iigs-game-disks iigs-game-disk-agi iigs-verify-game-disk iigs-verify iigs-verify-all iigs-verify-save iigs-verify-serial iigs-verify-shrtail clean-iigs clean
|
||||||
|
|
||||||
# Default: compile-check the library + run the end-to-end smoke test.
|
# Default: build (compile-check) the library. The MAME smoke test is
|
||||||
all iigs: iigs-lib iigs-clang-smoke
|
# `iigs-clang-smoke`; disks and examples are `iigs-disk`.
|
||||||
|
all iigs: iigs-lib
|
||||||
|
|
||||||
# Per-example launchable binaries. STAXI and AUDIO are INCLUDED: the
|
# Per-example launchable binaries. STAXI and AUDIO are INCLUDED: the
|
||||||
# old exclusion comment ("blocked on deeper toolchain features") went
|
# old exclusion comment ("blocked on deeper toolchain features") went
|
||||||
|
|
@ -95,8 +96,8 @@ iigs-examples: $(BINDIR)/PATTERN $(BINDIR)/DRAW $(BINDIR)/KEYS $(BINDIR)/SERIAL
|
||||||
$(BINDIR)/SPRITE $(BINDIR)/UBER $(BINDIR)/ADV $(BINDIR)/ADV2 \
|
$(BINDIR)/SPRITE $(BINDIR)/UBER $(BINDIR)/ADV $(BINDIR)/ADV2 \
|
||||||
$(BINDIR)/AGI $(BINDIR)/STAXI $(BINDIR)/AUDIO
|
$(BINDIR)/AGI $(BINDIR)/STAXI $(BINDIR)/AUDIO
|
||||||
|
|
||||||
# Build the JOEYLIB data disk (build/iigs/bin/joey.2mg) that run-iigs-mame.sh /
|
# Build the JOEYLIB data disk (build/iigs/bin/joey.2mg) that the gates, and
|
||||||
# run-iigs.sh mount on flop4. Packs the launchable examples as ProDOS S16 apps
|
# `joey-run build/iigs/bin/joey.2mg`, mount beside GS/OS. Packs the launchable examples as ProDOS S16 apps
|
||||||
# (an 800KB floppy can't hold every example; see make-iigs-disk.sh).
|
# (an 800KB floppy can't hold every example; see make-iigs-disk.sh).
|
||||||
# NTP_BIN rides along so jlpAudioInit's runtime load of "ntpplayer" finds it
|
# NTP_BIN rides along so jlpAudioInit's runtime load of "ntpplayer" finds it
|
||||||
# on the volume -- without it every example logs "audioInit failed" (Phase 1
|
# on the volume -- without it every example logs "audioInit failed" (Phase 1
|
||||||
|
|
@ -120,10 +121,18 @@ STAXI_IIGS_SPC := $(STAXICELS_SPR:.spr=.spc)
|
||||||
STAXI_CEL_LEVELS := 07 08 09 10 12 16 17 21
|
STAXI_CEL_LEVELS := 07 08 09 10 12 16 17 21
|
||||||
STAXILEVEL_IIGS_SPC := $(foreach n,$(STAXI_CEL_LEVELS),$(EXAMPLES)/spacetaxi/generated/iigs/sprites/level$(n).spc)
|
STAXILEVEL_IIGS_SPC := $(foreach n,$(STAXI_CEL_LEVELS),$(EXAMPLES)/spacetaxi/generated/iigs/sprites/level$(n).spc)
|
||||||
# Speech baked to 1-bit PCM per passenger pitch (assets/genSpeechPcm.py);
|
# Speech baked to 1-bit PCM per passenger pitch (assets/genSpeechPcm.py);
|
||||||
# make-iigs-disk.sh picks it up from the generated tree like font.tbk.
|
# make-iigs-disk.sh picks it up from the generated tree.
|
||||||
STAXI_IIGS_SPEECH := $(EXAMPLES)/spacetaxi/generated/iigs/speech.bin
|
STAXI_IIGS_SPEECH := $(EXAMPLES)/spacetaxi/generated/iigs/speech.bin
|
||||||
$(STAXI_IIGS_SPEECH): $(EXAMPLES)/spacetaxi/stSpeechData.c $(EXAMPLES)/spacetaxi/assets/genSpeechPcm.py
|
$(STAXI_IIGS_SPEECH): $(EXAMPLES)/spacetaxi/stSpeechData.c $(EXAMPLES)/spacetaxi/assets/genSpeechPcm.py
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
python3 $(EXAMPLES)/spacetaxi/assets/genSpeechPcm.py $< $@ --biased
|
python3 $(EXAMPLES)/spacetaxi/assets/genSpeechPcm.py $< $@ --biased
|
||||||
|
# Level files: extracted from the C64 game image exactly as on the other
|
||||||
|
# ports (see make/dos.mk), then found by make-iigs-disk.sh in the tree.
|
||||||
|
STAXI_IIGS_LEVELS := $(foreach n,$(STAXI_LEVEL_NUMS),$(EXAMPLES)/spacetaxi/generated/iigs/levels/level$(n).dat) \
|
||||||
|
$(EXAMPLES)/spacetaxi/generated/iigs/levels/title.dat
|
||||||
|
$(STAXI_IIGS_LEVELS) &: $(STAXI_RAW_DUMP) $(ROMTOLEVEL_BIN)
|
||||||
|
@mkdir -p $(EXAMPLES)/spacetaxi/generated/iigs/levels
|
||||||
|
python3 $(ROMTOLEVEL_BIN) $(STAXI_RAW_DUMP) $(EXAMPLES)/spacetaxi/generated/iigs/levels
|
||||||
SPRITEBAKE_IIGS := $(REPO_DIR)/build/tools/spritebake-iigs
|
SPRITEBAKE_IIGS := $(REPO_DIR)/build/tools/spritebake-iigs
|
||||||
STAXIBAKE := $(REPO_DIR)/build/tools/staxibake
|
STAXIBAKE := $(REPO_DIR)/build/tools/staxibake
|
||||||
|
|
||||||
|
|
@ -144,7 +153,7 @@ $(EXAMPLES)/spacetaxi/generated/iigs/sprites/level%.spr: $(EXAMPLES)/spacetaxi/g
|
||||||
$(EXAMPLES)/spacetaxi/generated/iigs/sprites/%.spc: $(EXAMPLES)/spacetaxi/generated/iigs/sprites/%.spr $(SPRITEBAKE_IIGS)
|
$(EXAMPLES)/spacetaxi/generated/iigs/sprites/%.spc: $(EXAMPLES)/spacetaxi/generated/iigs/sprites/%.spr $(SPRITEBAKE_IIGS)
|
||||||
$(SPRITEBAKE_IIGS) $< $@
|
$(SPRITEBAKE_IIGS) $< $@
|
||||||
|
|
||||||
iigs-disk: iigs-examples $(NTP_BIN) $(STAXI_IIGS_SPC) $(STAXILEVEL_IIGS_SPC) $(STAXI_IIGS_SPEECH)
|
iigs-disk: iigs-examples $(NTP_BIN) $(STAXI_IIGS_SPC) $(call staxiData,$(STAXI_IIGS_LEVELS) $(STAXILEVEL_IIGS_SPC)) $(STAXI_IIGS_SPEECH)
|
||||||
BINDIR="$(BINDIR)" NTP_BIN="$(NTP_BIN)" $(REPO_DIR)/scripts/make-iigs-disk.sh $(BINDIR)/joey.2mg
|
BINDIR="$(BINDIR)" NTP_BIN="$(NTP_BIN)" $(REPO_DIR)/scripts/make-iigs-disk.sh $(BINDIR)/joey.2mg
|
||||||
|
|
||||||
# ONE self-booting volume per game: a GS/OS system stripped to what JoeyLib
|
# ONE self-booting volume per game: a GS/OS system stripped to what JoeyLib
|
||||||
|
|
@ -162,7 +171,7 @@ iigs-disk: iigs-examples $(NTP_BIN) $(STAXI_IIGS_SPC) $(STAXILEVEL_IIGS_SPC) $(S
|
||||||
# (see make-iigs-disk.sh's JOEY_DISK_EXAMPLES), and the gates depend on
|
# (see make-iigs-disk.sh's JOEY_DISK_EXAMPLES), and the gates depend on
|
||||||
# joey.2mg's exact contents, so this must not rewrite it.
|
# joey.2mg's exact contents, so this must not rewrite it.
|
||||||
GAME ?= STAXI
|
GAME ?= STAXI
|
||||||
iigs-game-disk: iigs-examples $(NTP_BIN) $(STAXI_IIGS_SPC) $(STAXILEVEL_IIGS_SPC) $(STAXI_IIGS_SPEECH)
|
iigs-game-disk: iigs-examples $(NTP_BIN) $(STAXI_IIGS_SPC) $(call staxiData,$(STAXI_IIGS_LEVELS) $(STAXILEVEL_IIGS_SPC)) $(STAXI_IIGS_SPEECH)
|
||||||
BINDIR="$(BINDIR)" NTP_BIN="$(NTP_BIN)" JOEY_DISK_EXAMPLES="$(GAME)" \
|
BINDIR="$(BINDIR)" NTP_BIN="$(NTP_BIN)" JOEY_DISK_EXAMPLES="$(GAME)" \
|
||||||
$(REPO_DIR)/scripts/make-iigs-disk.sh $(BINDIR)/$(GAME)-data.2mg
|
$(REPO_DIR)/scripts/make-iigs-disk.sh $(BINDIR)/$(GAME)-data.2mg
|
||||||
JOEY_GAME_DISK_SIZE="$(or $(GAME_DISK_SIZE),auto)" \
|
JOEY_GAME_DISK_SIZE="$(or $(GAME_DISK_SIZE),auto)" \
|
||||||
|
|
@ -195,7 +204,7 @@ iigs-verify-game-disk: iigs-game-disk
|
||||||
# ProDOS hard-disk image (joey-all.2mg): boot it in MAME with
|
# ProDOS hard-disk image (joey-all.2mg): boot it in MAME with
|
||||||
# `-sl7 cffa2 -hard1 <GS/OS> -hard2 build/iigs/bin/joey-all.2mg` and launch
|
# `-sl7 cffa2 -hard1 <GS/OS> -hard2 build/iigs/bin/joey-all.2mg` and launch
|
||||||
# any example from the JOEYLIB volume in the Finder.
|
# any example from the JOEYLIB volume in the Finder.
|
||||||
iigs-disk-all: iigs-examples $(NTP_BIN) $(STAXI_IIGS_SPC) $(STAXILEVEL_IIGS_SPC) $(STAXI_IIGS_SPEECH)
|
iigs-disk-all: iigs-examples $(NTP_BIN) $(STAXI_IIGS_SPC) $(call staxiData,$(STAXI_IIGS_LEVELS) $(STAXILEVEL_IIGS_SPC)) $(STAXI_IIGS_SPEECH)
|
||||||
BINDIR="$(BINDIR)" NTP_BIN="$(NTP_BIN)" \
|
BINDIR="$(BINDIR)" NTP_BIN="$(NTP_BIN)" \
|
||||||
JOEY_DISK_SIZE="16MB" \
|
JOEY_DISK_SIZE="16MB" \
|
||||||
JOEY_DISK_EXAMPLES="DRAW PATTERN KEYS JOY SPRITE SERIAL SERTEST SAVE AUDIO UBER ADV2 ADV AGI STAXI" \
|
JOEY_DISK_EXAMPLES="DRAW PATTERN KEYS JOY SPRITE SERIAL SERTEST SAVE AUDIO UBER ADV2 ADV AGI STAXI" \
|
||||||
|
|
@ -256,9 +265,6 @@ $(NTP_BIN): $(NTP_SRC) $(IIGS_MERLIN)
|
||||||
cd $(BUILD)/audio && $(IIGS_MERLIN) . ninjatrackerplus.s
|
cd $(BUILD)/audio && $(IIGS_MERLIN) . ninjatrackerplus.s
|
||||||
mv $(BUILD)/audio/ntpplayer $@
|
mv $(BUILD)/audio/ntpplayer $@
|
||||||
|
|
||||||
$(NTP_ASM): $(NTP_BIN) $(REPO_DIR)/toolchains/iigs/bin-to-s.sh
|
|
||||||
$(REPO_DIR)/toolchains/iigs/bin-to-s.sh $(NTP_BIN) $@ gNtpPlayerBytes gNtpPlayerBytes_len
|
|
||||||
|
|
||||||
# Compile-check every library and example translation unit with clang,
|
# Compile-check every library and example translation unit with clang,
|
||||||
# and assemble every .s with llvm-mc. This is the canonical "does the
|
# and assemble every .s with llvm-mc. This is the canonical "does the
|
||||||
# IIgs source still build" gate while the launchable pipeline is wired.
|
# IIgs source still build" gate while the launchable pipeline is wired.
|
||||||
|
|
@ -291,7 +297,7 @@ iigs-lib: $(LIB)
|
||||||
# End-to-end smoke test: clang -> llvm-mc -> link816 -> MAME, validating
|
# End-to-end smoke test: clang -> llvm-mc -> link816 -> MAME, validating
|
||||||
# the ported asm computes correctly on emulated hardware.
|
# the ported asm computes correctly on emulated hardware.
|
||||||
iigs-clang-smoke:
|
iigs-clang-smoke:
|
||||||
$(REPO_DIR)/toolchains/iigs/clang-smoke.sh
|
$(REPO_DIR)/make/support/iigs/clang-smoke.sh
|
||||||
|
|
||||||
# Per-example binaries via clang-build.sh. clang-build.sh mirrors the
|
# Per-example binaries via clang-build.sh. clang-build.sh mirrors the
|
||||||
# old driver's CLI (-I / -o / -M; -s and -b are accepted no-ops). These
|
# old driver's CLI (-I / -o / -M; -s and -b are accepted no-ops). These
|
||||||
|
|
@ -376,8 +382,8 @@ $(BINDIR)/STAXI: $(STAXI_SRCS) $(LIB) $(IIGS_CLANG_BUILD)
|
||||||
# AUDIO loads the NTP replayer from disk at runtime: audio_full.c reads
|
# AUDIO loads the NTP replayer from disk at runtime: audio_full.c reads
|
||||||
# ntpplayer.bin via NTP_PATH instead of baking the 34KB replayer as rodata
|
# ntpplayer.bin via NTP_PATH instead of baking the 34KB replayer as rodata
|
||||||
# (which overflowed bank 0, and clang's IMM16+$BE near form can't reach a far
|
# (which overflowed bank 0, and clang's IMM16+$BE near form can't reach a far
|
||||||
# rodata segment anyway). The binary no longer links $(NTP_ASM); $(NTP_BIN) is
|
# rodata segment anyway). $(NTP_BIN) is shipped on the application disk next
|
||||||
# still built so it can be shipped on the application disk next to the binary.
|
# to the binary.
|
||||||
$(BINDIR)/AUDIO: $(AUDIO_SRC) $(LIB) $(NTP_BIN) $(IIGS_CLANG_BUILD)
|
$(BINDIR)/AUDIO: $(AUDIO_SRC) $(LIB) $(NTP_BIN) $(IIGS_CLANG_BUILD)
|
||||||
@mkdir -p $(dir $@) $(DEP_DIR)
|
@mkdir -p $(dir $@) $(DEP_DIR)
|
||||||
$(IIGS_CLANG_BUILD) -M $(DEP_DIR)/AUDIO.d $(INCLUDES) -o $@ $(AUDIO_SRC) $(LIB)
|
$(IIGS_CLANG_BUILD) -M $(DEP_DIR)/AUDIO.d $(INCLUDES) -o $@ $(AUDIO_SRC) $(LIB)
|
||||||
|
|
|
||||||
25
make/support/atarist/include-shim/math.h
Normal file
25
make/support/atarist/include-shim/math.h
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
// Minimal math.h shim for the m68k-atari-mint toolchain (mintlib
|
||||||
|
// doesn't ship <math.h> or libm). Declares only what libxmp-lite
|
||||||
|
// reaches for; the implementations live in math_shim.c next to this
|
||||||
|
// header. Accuracy is sufficient for libxmp-lite's note-period
|
||||||
|
// conversion, not for general-purpose use.
|
||||||
|
|
||||||
|
#ifndef JOEY_ATARIST_MATH_SHIM_H
|
||||||
|
#define JOEY_ATARIST_MATH_SHIM_H
|
||||||
|
|
||||||
|
#define M_LN2 0.69314718055994530942
|
||||||
|
#define M_PI 3.14159265358979323846
|
||||||
|
|
||||||
|
double ceil(double x);
|
||||||
|
double floor(double x);
|
||||||
|
double log(double x);
|
||||||
|
double pow(double base, double exp);
|
||||||
|
double trunc(double x);
|
||||||
|
|
||||||
|
float ceilf(float x);
|
||||||
|
float floorf(float x);
|
||||||
|
float logf(float x);
|
||||||
|
float powf(float base, float exp);
|
||||||
|
float truncf(float x);
|
||||||
|
|
||||||
|
#endif
|
||||||
100
make/support/atarist/include-shim/math_shim.c
Normal file
100
make/support/atarist/include-shim/math_shim.c
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
// Tiny libm replacement used by libxmp-lite on the m68k-atari-mint
|
||||||
|
// toolchain. Only the functions libxmp-lite calls are implemented.
|
||||||
|
// Accuracy targets: a few hundred parts per million, plenty for
|
||||||
|
// note-frequency math.
|
||||||
|
|
||||||
|
#include "math.h"
|
||||||
|
|
||||||
|
double ceil(double x) {
|
||||||
|
long i = (long)x;
|
||||||
|
if ((double)i < x) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return (double)i;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
double floor(double x) {
|
||||||
|
long i = (long)x;
|
||||||
|
if ((double)i > x) {
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
return (double)i;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Natural log via 2*atanh((x-1)/(x+1)) after a power-of-two range
|
||||||
|
// reduction into [1, 2). The atanh series converges fast on that
|
||||||
|
// interval.
|
||||||
|
static double logSeries(double m) {
|
||||||
|
double y;
|
||||||
|
double y2;
|
||||||
|
|
||||||
|
y = (m - 1.0) / (m + 1.0);
|
||||||
|
y2 = y * y;
|
||||||
|
return 2.0 * y * (1.0
|
||||||
|
+ y2 * (1.0 / 3.0
|
||||||
|
+ y2 * (1.0 / 5.0
|
||||||
|
+ y2 * (1.0 / 7.0
|
||||||
|
+ y2 * (1.0 / 9.0)))));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
double log(double x) {
|
||||||
|
int k;
|
||||||
|
|
||||||
|
if (x <= 0.0) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
k = 0;
|
||||||
|
while (x >= 2.0) { x *= 0.5; k++; }
|
||||||
|
while (x < 1.0) { x *= 2.0; k--; }
|
||||||
|
return (double)k * M_LN2 + logSeries(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 2^f for f in [0, 1) -- 5-term polynomial good to ~1e-6.
|
||||||
|
static double exp2Frac(double f) {
|
||||||
|
return 1.0 + f * (0.69314718
|
||||||
|
+ f * (0.24022650
|
||||||
|
+ f * (0.05550410
|
||||||
|
+ f * (0.00961813
|
||||||
|
+ f * 0.00133335))));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
double pow(double base, double exp) {
|
||||||
|
double k;
|
||||||
|
long ki;
|
||||||
|
double frac;
|
||||||
|
double result;
|
||||||
|
|
||||||
|
if (base <= 0.0) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
if (exp == 0.0) {
|
||||||
|
return 1.0;
|
||||||
|
}
|
||||||
|
k = exp * (log(base) / M_LN2);
|
||||||
|
ki = (long)k;
|
||||||
|
if (k < 0.0 && (double)ki != k) {
|
||||||
|
ki--;
|
||||||
|
}
|
||||||
|
frac = k - (double)ki;
|
||||||
|
result = exp2Frac(frac);
|
||||||
|
while (ki > 0) { result *= 2.0; ki--; }
|
||||||
|
while (ki < 0) { result *= 0.5; ki++; }
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
double trunc(double x) {
|
||||||
|
return (double)(long)x;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
float ceilf (float x) { return (float)ceil ((double)x); }
|
||||||
|
float floorf(float x) { return (float)floor((double)x); }
|
||||||
|
float logf (float x) { return (float)log ((double)x); }
|
||||||
|
float powf (float b, float e) { return (float)pow ((double)b, (double)e); }
|
||||||
|
float truncf(float x) { return (float)(long)x; }
|
||||||
69
make/support/dos/embed-dpmi.sh
Executable file
69
make/support/dos/embed-dpmi.sh
Executable file
|
|
@ -0,0 +1,69 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Embed the CWSDPMI DPMI server into a DJGPP-built DOS executable so
|
||||||
|
# the resulting .EXE is self-contained and does not require a separate
|
||||||
|
# CWSDPMI.EXE on the target machine. Run as a post-link step from the
|
||||||
|
# DOS Makefile.
|
||||||
|
#
|
||||||
|
# Usage: embed-dpmi.sh program.EXE
|
||||||
|
#
|
||||||
|
# Mechanism: DJGPP .EXE files are COFF binaries prefixed with a small
|
||||||
|
# MZ stub. Stripping the stub via exe2coff leaves a pure COFF image.
|
||||||
|
# CWSDSTUB.EXE is a larger MZ stub that contains a full DPMI host;
|
||||||
|
# concatenating CWSDSTUB.EXE with the COFF image produces a new .EXE
|
||||||
|
# with the DPMI server built in.
|
||||||
|
#
|
||||||
|
# Requires $DOS_EXE2COFF and $DOS_CWSDSTUB (exported by make from the
|
||||||
|
# installer's toolchains/config.mk; toolchains/env.sh for a shell).
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [[ $# -ne 1 ]]; then
|
||||||
|
echo "usage: $0 program.EXE" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
exe=$1
|
||||||
|
|
||||||
|
for v in DOS_EXE2COFF DOS_CWSDSTUB; do
|
||||||
|
if [[ -z "${!v:-}" ]]; then
|
||||||
|
echo "$v not set; run ./install.sh install toolchain:dos" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ ! -x $DOS_EXE2COFF ]]; then
|
||||||
|
echo "exe2coff not executable: $DOS_EXE2COFF" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ ! -f $DOS_CWSDSTUB ]]; then
|
||||||
|
echo "CWSDSTUB.EXE not found: $DOS_CWSDSTUB" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ ! -f $exe ]]; then
|
||||||
|
echo "executable not found: $exe" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
coff=${exe%.*}
|
||||||
|
coff_tmp=${coff}.coff.tmp
|
||||||
|
trap 'rm -f "$coff_tmp" "$coff"' EXIT
|
||||||
|
|
||||||
|
# djgpp-gcc on a case-sensitive host writes its output to both case
|
||||||
|
# variants (e.g. PATTERN.EXE and PATTERN.exe) as distinct files.
|
||||||
|
# Remove the lowercase sibling so DOSBox's case-folded lookup cannot
|
||||||
|
# pick up the pre-embed copy after we rewrite the uppercase one.
|
||||||
|
lower_exe=${coff}.exe
|
||||||
|
if [[ $exe != "$lower_exe" && -f $lower_exe ]]; then
|
||||||
|
rm -f "$lower_exe"
|
||||||
|
fi
|
||||||
|
|
||||||
|
"$DOS_EXE2COFF" "$exe"
|
||||||
|
if [[ ! -f $coff ]]; then
|
||||||
|
echo "exe2coff did not produce expected output: $coff" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat "$DOS_CWSDSTUB" "$coff" > "$coff_tmp"
|
||||||
|
mv "$coff_tmp" "$exe"
|
||||||
|
trap - EXIT
|
||||||
|
rm -f "$coff"
|
||||||
342
make/support/iigs/clang-build.sh
Executable file
342
make/support/iigs/clang-build.sh
Executable file
|
|
@ -0,0 +1,342 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# clang-build.sh - Build an Apple IIgs binary with the llvm-mos w65816
|
||||||
|
# toolchain (clang + llvm-mc + link816).
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# clang-build.sh [-I path]... [-Dname[=val]]... [-s stacksize] [-b] \
|
||||||
|
# [-M depfile] -o OUTPUT MAIN.c [EXTRA.c|.s ...]
|
||||||
|
#
|
||||||
|
# -I path extra include dir (repeatable)
|
||||||
|
# -Dname preprocessor define, passed to clang (probe builds)
|
||||||
|
# -s SIZE accepted for compatibility; llvm-mos sizes the stack in
|
||||||
|
# crt0, so this is a no-op here.
|
||||||
|
# -b accepted for compatibility; no-op (clang pointers are
|
||||||
|
# already 32-bit).
|
||||||
|
# -M depfile emit a combined header-dependency file (clang -MM) so make
|
||||||
|
# rebuilds the binary when a shared header changes.
|
||||||
|
# -o OUTPUT output path for the GS/OS S16 OMF (ExpressLoad-wrapped,
|
||||||
|
# relocatable) that the System Loader / Finder can launch.
|
||||||
|
#
|
||||||
|
# C sources are compiled with clang; .s / .asm sources are assembled with
|
||||||
|
# llvm-mc; everything links against the prebuilt w65816 runtime (with the
|
||||||
|
# GS/OS-aware crt0Gsos). link816 dead-strips unreferenced sections, then
|
||||||
|
# omfEmit wraps the relocatable image as an S16 OMF so the Loader can place
|
||||||
|
# it at any Memory-Manager-chosen address.
|
||||||
|
#
|
||||||
|
# Requires LLVM816_ROOT (toolchain root) with a built runtime
|
||||||
|
# ($LLVM816_ROOT/runtime/*.o). make exports it; from a shell, source
|
||||||
|
# toolchains/env.sh.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
: "${LLVM816_ROOT:?clang-build.sh: LLVM816_ROOT unset; run ./install.sh install toolchain:iigs, then use make or source toolchains/env.sh}"
|
||||||
|
|
||||||
|
CLANG="$LLVM816_ROOT/tools/llvm-mos-build/bin/clang"
|
||||||
|
LLVM_MC="$LLVM816_ROOT/tools/llvm-mos-build/bin/llvm-mc"
|
||||||
|
LINK="$LLVM816_ROOT/tools/link816"
|
||||||
|
OMFEMIT="$LLVM816_ROOT/tools/omfEmit"
|
||||||
|
RT="$LLVM816_ROOT/runtime"
|
||||||
|
RTINC="$RT/include"
|
||||||
|
|
||||||
|
for t in "$CLANG" "$LLVM_MC" "$LINK" "$OMFEMIT"; do
|
||||||
|
[ -x "$t" ] || { echo "clang-build.sh: missing tool: $t" >&2; exit 1; }
|
||||||
|
done
|
||||||
|
[ -f "$RT/crt0Gsos.o" ] || { echo "clang-build.sh: runtime not built ($RT/crt0Gsos.o missing); run runtime/build.sh" >&2; exit 1; }
|
||||||
|
|
||||||
|
includes=()
|
||||||
|
output=""
|
||||||
|
depfile=""
|
||||||
|
srcs=()
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
-I) includes+=(-I "$2"); shift 2 ;;
|
||||||
|
-I*) includes+=("$1"); shift ;; # combined -Ipath form
|
||||||
|
-D*) includes+=("$1"); shift ;; # preprocessor define (probe builds)
|
||||||
|
-o) output="$2"; shift 2 ;;
|
||||||
|
-s) shift 2 ;; # stack size: no-op (crt0 handles it)
|
||||||
|
-b) shift ;; # accepted for compatibility: no-op
|
||||||
|
-M) depfile="$2"; shift 2 ;;
|
||||||
|
-*) echo "clang-build.sh: unknown flag $1" >&2; exit 2 ;;
|
||||||
|
*) srcs+=("$1"); shift ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[ -n "$output" ] || { echo "clang-build.sh: -o OUTPUT required" >&2; exit 2; }
|
||||||
|
[ ${#srcs[@]} -gt 0 ] || { echo "clang-build.sh: no source files" >&2; exit 2; }
|
||||||
|
|
||||||
|
tmpdir="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "$tmpdir"' EXIT
|
||||||
|
|
||||||
|
CFLAGS=(--target=w65816 -O2 -ffreestanding -ffunction-sections -I"$RTINC" "${includes[@]}")
|
||||||
|
|
||||||
|
# Curated runtime object set for GS/OS-launched OMFs. crt0Gsos.o (the
|
||||||
|
# System-Loader-aware crt) MUST be first so __start lands at --text-base.
|
||||||
|
# link816 dead-strips whatever a given binary doesn't use, so offering the
|
||||||
|
# whole runtime (libc helpers, soft-float, toolbox, GS/OS) is free.
|
||||||
|
runtimeObjs=(
|
||||||
|
"$RT/crt0Gsos.o" "$RT/libgcc.o" "$RT/libc.o"
|
||||||
|
"$RT/strtol.o" "$RT/snprintf.o" "$RT/sscanf.o" "$RT/qsort.o"
|
||||||
|
"$RT/strtok.o" "$RT/extras.o" "$RT/timeExt.o" "$RT/math.o"
|
||||||
|
"$RT/softFloat.o" "$RT/softDouble.o"
|
||||||
|
"$RT/iigsToolbox.o" "$RT/iigsToolboxExtra.o" "$RT/iigsGsos.o"
|
||||||
|
)
|
||||||
|
|
||||||
|
objs=()
|
||||||
|
depsrcs=()
|
||||||
|
objIdx=0
|
||||||
|
for s in "${srcs[@]}"; do
|
||||||
|
# Unique object name per source. A plain basename collides when two
|
||||||
|
# sources share a name (e.g. src/core/input.c and src/iigs/
|
||||||
|
# input.c both -> input.o), silently clobbering one and dropping its
|
||||||
|
# symbols. Index-prefix to guarantee uniqueness.
|
||||||
|
b="$(basename "$s")"
|
||||||
|
o="$tmpdir/${objIdx}_${b%.*}.o"
|
||||||
|
objIdx=$((objIdx + 1))
|
||||||
|
case "$s" in
|
||||||
|
*.c)
|
||||||
|
# Route through the regalloc fallback so a register-heavy TU
|
||||||
|
# (greedy "ran out of registers" on the single-accumulator
|
||||||
|
# 65816 -- e.g. AGI's main()) auto-retries with -regalloc=basic
|
||||||
|
# instead of breaking the build, exactly like runtime/build.sh.
|
||||||
|
"$LLVM816_ROOT/scripts/ccRegallocFallback.sh" "$CLANG" "${CFLAGS[@]}" -c "$s" -o "$o"
|
||||||
|
depsrcs+=("$s")
|
||||||
|
;;
|
||||||
|
*.s|*.asm)
|
||||||
|
"$LLVM_MC" -arch=w65816 -filetype=obj "$s" -o "$o"
|
||||||
|
;;
|
||||||
|
*.o|*.a)
|
||||||
|
# Prebuilt object or static library (e.g. libjoey.a): link816 reads
|
||||||
|
# both directly and --gc-sections selects what's referenced. Pass
|
||||||
|
# the real path through untouched.
|
||||||
|
objs+=("$s")
|
||||||
|
continue
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "clang-build.sh: skipping unrecognized source: $s" >&2
|
||||||
|
continue
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
objs+=("$o")
|
||||||
|
done
|
||||||
|
|
||||||
|
# Object order is no longer layout-sensitive. The multi-segment crash that
|
||||||
|
# made it matter was the OMF segment header carrying ALIGN=0, so the System
|
||||||
|
# Loader placed overflow segments at a non-bank-aligned address (e.g.
|
||||||
|
# $07:BB11). Code uses 16-bit self-references (computed-goto `rts` tables,
|
||||||
|
# intra-segment branches) whose operands equal the *link* offset, which only
|
||||||
|
# resolve when the segment sits at bank:0000. omfEmit now emits ALIGN=0x10000
|
||||||
|
# so every code segment is bank-aligned and runtime_offset == link_offset in
|
||||||
|
# ANY split. Objects therefore build in compile order (deterministic from the
|
||||||
|
# source list); no sort needed. (Root-caused via the MAME debugger: seg2 main
|
||||||
|
# landed at $07:BB11, jlInit at +0x1E02=$07:D913, computed-gotos jumped wild.)
|
||||||
|
|
||||||
|
# Relocatable GS/OS OMF build. The System Loader places the image at a
|
||||||
|
# Memory-Manager-chosen address, so the OMF carries relocation records
|
||||||
|
# (--reloc-out -> --relocs). --text-base 0 (NOT 0x1000): the Loader
|
||||||
|
# bank-aligns KIND=0x1000 segments, so link-time IMM16 operands already
|
||||||
|
# equal their runtime segment offsets and link816 skips the IMM16 cRELOC
|
||||||
|
# entries (50-90% fewer relocs -> proportionally faster Loader boot).
|
||||||
|
# --bss-base 0xA000 keeps BSS above the SHR shadow ($2000-$9FFF in bank 0)
|
||||||
|
# so global writes don't scribble the screen. crt0Gsos.o (first object)
|
||||||
|
# puts __start at the segment base.
|
||||||
|
linkBin="$tmpdir/link.bin"
|
||||||
|
linkMap="$tmpdir/link.map"
|
||||||
|
linkReloc="$tmpdir/link.reloc"
|
||||||
|
linkManifest="$tmpdir/link.json"
|
||||||
|
linkErr="$tmpdir/link.err"
|
||||||
|
|
||||||
|
# The GS/OS load name is the output basename, uppercased to <=8 alnum chars.
|
||||||
|
omfName="$(basename "$output" | tr '[:lower:]' '[:upper:]' | tr -cd 'A-Z0-9' | cut -c1-8)"
|
||||||
|
[ -n "$omfName" ] || omfName="JOEYAPP"
|
||||||
|
|
||||||
|
# link816 emits an ExpressLoad S16 OMF. Two distinct overflows force the
|
||||||
|
# --full-bank: a GS/OS OMF image is loaded into a Memory-Manager-chosen
|
||||||
|
# bank, not bank 0, so there is no IO window or language card to dodge and
|
||||||
|
# all 64K is usable. link816 defaults to the bank-0-safe layout because its
|
||||||
|
# other consumers (clang-smoke.sh and the bench/probe scripts) really do
|
||||||
|
# raw-load into bank 0 -- see the fullBank comment in link816.cpp.
|
||||||
|
#
|
||||||
|
# MULTI-segment path: (a) text overrunning the entry bank, or
|
||||||
|
# (b) BSS overflowing the entry bank's leftover space. crt0Gsos sets
|
||||||
|
# DBR = the entry segment's bank and C globals are reached DBR-relative, so
|
||||||
|
# BSS can NOT be moved to its own bank -- instead we push TEXT into overflow
|
||||||
|
# banks (a low --segment-cap) to free the entry bank for rodata + BSS, and
|
||||||
|
# raise --bss-base so BSS sits at the top of the entry bank. The GS/OS
|
||||||
|
# Loader places each bank-aligned segment + applies the per-seg relocations.
|
||||||
|
emitMultiSeg() { # $1 = segment-cap, $2 = bss-base
|
||||||
|
"$LINK" -o "$linkBin" --text-base 0 --bss-base "$2" --segment-cap "$1" \
|
||||||
|
--overflow-cap "$JOEY_OVERFLOW_CAP" --full-bank \
|
||||||
|
--manifest "$linkManifest" --map "$linkMap" --reloc-out "$linkReloc" \
|
||||||
|
--multi-seg-expressload "${runtimeObjs[@]}" "${objs[@]}" 2>"$linkErr" || return 1
|
||||||
|
"$OMFEMIT" --manifest "$linkManifest" --expressload --relocs "$linkReloc" \
|
||||||
|
--output "$output"
|
||||||
|
}
|
||||||
|
|
||||||
|
# The C heap only has to be LARGE ENOUGH. It used to also have to sit below
|
||||||
|
# "the bank-0 IO window ($C000)", which was wrong: the program does not run
|
||||||
|
# in bank 0. Measured under MAME, joeylib's examples execute in banks
|
||||||
|
# $08-$0B, where there is no IO window and no language card, and link816's
|
||||||
|
# map is bank-relative (--text-base 0), so that test was comparing a bank
|
||||||
|
# OFFSET against a bank-0 address. link816's __heap_start comment has the
|
||||||
|
# full reasoning; the heap now runs to the top of the entry bank.
|
||||||
|
#
|
||||||
|
# This used to test only that the heap STARTED below $C000, which let the
|
||||||
|
# descending-cap search stop at the first layout that merely cleared the
|
||||||
|
# window -- even when almost nothing was left under it. A program that
|
||||||
|
# REDUCED its bss could therefore get a worse image: shrinking bss moves
|
||||||
|
# __heap_start down, so a layout that previously failed the test now
|
||||||
|
# passes it with a few hundred bytes of heap, and the search stops there.
|
||||||
|
# Space Taxi hit exactly this, linking a 778-byte heap (vs 4802 before);
|
||||||
|
# it drew its first screen and then froze, because every allocation after
|
||||||
|
# that failed. The big buffers (codegen arena, stage pixels, audio) come
|
||||||
|
# from the Memory Manager, but stdio FILE buffers and codegen arena slots
|
||||||
|
# come from this heap, so "a few hundred bytes" is never enough.
|
||||||
|
#
|
||||||
|
# If no cap reaches the minimum the caller still falls back to the highest
|
||||||
|
# cap that linked, so this can only reject layouts that were broken.
|
||||||
|
# Minimum size for the link-time heap WINDOW, in bytes. DEFAULT 0, and
|
||||||
|
# no target needs to raise it any more.
|
||||||
|
#
|
||||||
|
# What this window is: the free space above bss in the ENTRY BANK. In a
|
||||||
|
# program that links libc it is NOT the heap -- __heapInitMM asks the
|
||||||
|
# Memory Manager for a handle (which carries no attrBank, so it can land
|
||||||
|
# in any bank) and malloc runs in that. The window is only addressed
|
||||||
|
# directly if that allocation fails.
|
||||||
|
#
|
||||||
|
# The old measurements below were read as "each program needs its own
|
||||||
|
# minimum heap", with the odd result that MORE heap could break a program:
|
||||||
|
#
|
||||||
|
# STAXI 778 B -> froze on its first allocation
|
||||||
|
# STAXI 2426 B -> still froze
|
||||||
|
# AGI 3337 B -> runs KQ3 (19 segments)
|
||||||
|
# STAXI 4802 B -> runs (25 segments)
|
||||||
|
# AGI 7689 B -> BROKE: launched, then did nothing
|
||||||
|
#
|
||||||
|
# That last row is not mysterious: libc used to pass the window straight
|
||||||
|
# to NewHandle when it fell in [0x2000, 0xC000] and substitute a 32 KB
|
||||||
|
# default otherwise. 3337 is below 0x2000, so AGI got 32 KB and ran; 7689
|
||||||
|
# is inside the range, so AGI asked for 7689 -- less than it needs -- and
|
||||||
|
# drew nothing. The freezes are the same story with the Memory Manager
|
||||||
|
# exhausted by a many-segment image, so NewHandle failed and malloc fell
|
||||||
|
# back to the tiny window. Reproduced exactly while fixing the IO-window
|
||||||
|
# assumption, and fixed in libc.c: the window is now a floor to beat, not
|
||||||
|
# the request, so a bigger window can never mean a smaller heap.
|
||||||
|
JOEY_MIN_HEAP="${JOEY_MIN_HEAP:-0}"
|
||||||
|
|
||||||
|
# How much text an OVERFLOW segment may hold. --segment-cap bounds the
|
||||||
|
# ENTRY bank, which also carries rodata, bss and the heap -- for Space
|
||||||
|
# Taxi only ~12K of its 64K is left for text. An
|
||||||
|
# overflow bank carries text ONLY, so capping it the same way wasted ~50K
|
||||||
|
# per bank: 177K of code needed 17 bank-aligned segments instead of 4.
|
||||||
|
# Each segment must still fit one bank (16-bit intra-segment addressing),
|
||||||
|
# so this stays below 0x10000.
|
||||||
|
#
|
||||||
|
# DEFAULT 0 = overflow segments use --segment-cap (the historical
|
||||||
|
# behaviour); each target opts in from make/iigs.mk. AGI links 5 segments
|
||||||
|
# instead of 19, STAXI 4 instead of 26.
|
||||||
|
#
|
||||||
|
# Raising this on STAXI used to produce a dead image, which looked like a
|
||||||
|
# segment-size limit. It was not -- AGI ships 28798-byte segments and runs.
|
||||||
|
# The real chain: 26 bank-aligned segments starved the Memory Manager, so
|
||||||
|
# NTP's 34K NewHandle failed, gNTPReady stayed 0 and every SFX call
|
||||||
|
# short-circuited. IIgs builds were simply SILENT. Freeing that memory let
|
||||||
|
# audio initialise, and a latent bug fired the first time NTPstreamsound
|
||||||
|
# was ever reached (fixed in src/iigs/audio_full.c: SFX now requires a
|
||||||
|
# running replayer, as jlpAudioStopSfx already did).
|
||||||
|
#
|
||||||
|
# Each segment must still fit one bank (16-bit intra-segment addressing),
|
||||||
|
# so this stays below 0x10000.
|
||||||
|
JOEY_OVERFLOW_CAP="${JOEY_OVERFLOW_CAP:-0}"
|
||||||
|
heapUsable() {
|
||||||
|
local hs he
|
||||||
|
hs=$(grep -wE '__heap_start' "$linkMap" 2>/dev/null \
|
||||||
|
| grep -oE '0x[0-9a-fA-F]+' | head -1)
|
||||||
|
he=$(grep -wE '__heap_end' "$linkMap" 2>/dev/null \
|
||||||
|
| grep -oE '0x[0-9a-fA-F]+' | head -1)
|
||||||
|
[ -n "$hs" ] && [ -n "$he" ] || return 1
|
||||||
|
# No IO-window test: the heap is not in bank 0. See the __heap_start
|
||||||
|
# comment in link816.cpp -- GS/OS loads bank-aligned segments into
|
||||||
|
# free banks ($08-$0B here), where the whole 64K is plain RAM, and in
|
||||||
|
# a program that links libc this window is only a size request to the
|
||||||
|
# Memory Manager anyway.
|
||||||
|
[ $(( he - hs )) -ge $(( JOEY_MIN_HEAP )) ]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Multi-seg, choosing the HIGHEST --segment-cap that BOTH links AND leaves a
|
||||||
|
# usable C heap. A lower cap pushes more TEXT into overflow banks, freeing
|
||||||
|
# entry-bank space for rodata + BSS + the heap; BSS packs
|
||||||
|
# tight (--bss-base 0x2000 -- safe, a relocatable multi-seg image loads high,
|
||||||
|
# never bank 0/1). The highest qualifying cap is preferred: fewest segments,
|
||||||
|
# fastest Loader boot.
|
||||||
|
#
|
||||||
|
# Do NOT "improve" this by falling back to whichever cap yields the BIGGEST
|
||||||
|
# heap. Tried 2026-09-19: for Space Taxi that selects cap 0x1000, and the
|
||||||
|
# resulting 49-segment image renders garbage (SCB byte reads back $DD). Low
|
||||||
|
# caps break for reasons this script cannot see, so the search stays anchored
|
||||||
|
# to the highest cap that qualifies, and the fallback stays the highest cap
|
||||||
|
# that merely linked.
|
||||||
|
# Any of these means seg 1 is too full for bank 0 and a smaller cap may
|
||||||
|
# fit: BSS, the trampoline table, or rodata + init_array (which follow
|
||||||
|
# seg 1's text) ran past the ceiling.
|
||||||
|
ENTRY_BANK_OVERFLOW='bss .*exceeds bank-0 ceiling|trampoline table pushes seg 1 over|rodata .*exceeds bank-0'
|
||||||
|
emitMultiSegFit() {
|
||||||
|
local cap
|
||||||
|
local fallback=""
|
||||||
|
for cap in 0xB000 0xA000 0x9000 0x8000 0x7000 0x6000 0x5000 0x4000 0x3000 0x2000 0x1000; do
|
||||||
|
if emitMultiSeg "$cap" 0x2000; then
|
||||||
|
[ -z "$fallback" ] && fallback="$cap"
|
||||||
|
heapUsable && return 0
|
||||||
|
else
|
||||||
|
# Keep shrinking only on a fixable entry-bank overflow; bail on
|
||||||
|
# anything else (unsplittable text, an unresolved symbol, ...).
|
||||||
|
grep -qE "$ENTRY_BANK_OVERFLOW" "$linkErr" || break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
# Nothing reached JOEY_MIN_HEAP; re-link the highest that linked and say so,
|
||||||
|
# because the image may well fail at run time for want of heap.
|
||||||
|
if [ -n "$fallback" ]; then
|
||||||
|
echo "clang-build: WARNING no segment cap reached a ${JOEY_MIN_HEAP}-byte" \
|
||||||
|
"heap; using $fallback -- the image may fail to allocate" >&2
|
||||||
|
emitMultiSeg "$fallback" 0x2000
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
# Nothing linked at all. This used to fall off the end and return the
|
||||||
|
# status of the false `if` above -- 0 -- so the caller never printed
|
||||||
|
# the linker error and the build "succeeded" with no output file.
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if "$LINK" -o "$linkBin" --text-base 0 --bss-base 0xA000 --full-bank \
|
||||||
|
--map "$linkMap" --reloc-out "$linkReloc" \
|
||||||
|
"${runtimeObjs[@]}" "${objs[@]}" 2>"$linkErr" && heapUsable; then
|
||||||
|
# Single-segment AND the heap is big enough: wrap as-is.
|
||||||
|
"$OMFEMIT" --input "$linkBin" --map "$linkMap" --base 0 --entry __start \
|
||||||
|
--output "$output" --name "$omfName" --expressload --relocs "$linkReloc"
|
||||||
|
else
|
||||||
|
# Single-seg overflowed (text/BSS/trampoline) OR it linked with too
|
||||||
|
# small a heap -- go multi-seg with a heap-aware descending cap.
|
||||||
|
emitMultiSegFit || { cat "$linkErr" >&2; exit 1; }
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The link map is the only record of where a symbol landed, and the OMF
|
||||||
|
# carries no symbols at all, so anything that inspects a running program
|
||||||
|
# (the PC profiler, the MAME state gates) needs it kept. Off by default.
|
||||||
|
if [ -n "${JOEY_LINK_MAP:-}" ] && [ -f "$linkMap" ]; then
|
||||||
|
mkdir -p "$(dirname "$JOEY_LINK_MAP")"
|
||||||
|
cp "$linkMap" "$JOEY_LINK_MAP"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Combined header-dependency file: union of clang -MM over the C sources,
|
||||||
|
# rewritten to a single target ($output) so make rebuilds the binary when
|
||||||
|
# any transitively-included header changes.
|
||||||
|
if [ -n "$depfile" ] && [ ${#depsrcs[@]} -gt 0 ]; then
|
||||||
|
mkdir -p "$(dirname "$depfile")"
|
||||||
|
{
|
||||||
|
printf '%s:' "$output"
|
||||||
|
for s in "${depsrcs[@]}"; do
|
||||||
|
"$CLANG" "${CFLAGS[@]}" -MM "$s" 2>/dev/null \
|
||||||
|
| sed -e 's/^[^:]*://' -e 's/\\$//' | tr -d '\n'
|
||||||
|
done
|
||||||
|
printf '\n'
|
||||||
|
} > "$depfile"
|
||||||
|
fi
|
||||||
65
make/support/iigs/clang-smoke.sh
Executable file
65
make/support/iigs/clang-smoke.sh
Executable file
|
|
@ -0,0 +1,65 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# clang-smoke.sh - End-to-end smoke test of the llvm-mos w65816 IIgs
|
||||||
|
# build path. Compiles a tiny C main that calls JoeyLib's ported asm
|
||||||
|
# (iigsInitRowLut), links it with clang-build.sh, runs it under MAME's
|
||||||
|
# apple2gs, and verifies the LUT the asm computed.
|
||||||
|
#
|
||||||
|
# This proves the whole toolchain end to end: clang -> llvm-mc ->
|
||||||
|
# link816 -> runnable IIgs image -> correct runtime result. Exit 0 on
|
||||||
|
# success. Needs the toolchain:iigs install (make exports its paths).
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
: "${LLVM816_ROOT:?LLVM816_ROOT unset: run ./install.sh install toolchain:iigs, then use make or source toolchains/env.sh}"
|
||||||
|
: "${MAME:?MAME unset: run ./install.sh install emulator:iigs}"
|
||||||
|
: "${IIGS_ROMPATH:?IIGS_ROMPATH unset: run ./install.sh install emulator:iigs}"
|
||||||
|
|
||||||
|
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
REPO="$(cd "$HERE/../../.." && pwd)"
|
||||||
|
RUN_IN_MAME="$LLVM816_ROOT/scripts/runInMame.sh"
|
||||||
|
|
||||||
|
[ -x "$RUN_IN_MAME" ] || { echo "clang-smoke: $RUN_IN_MAME missing" >&2; exit 1; }
|
||||||
|
|
||||||
|
# runInMame.sh takes MAME and MAME_ROMPATH from the environment, so it
|
||||||
|
# runs the installer's patched MAME against the installer's ROM set.
|
||||||
|
export MAME
|
||||||
|
export MAME_ROMPATH="$IIGS_ROMPATH"
|
||||||
|
|
||||||
|
tmp="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "$tmp"' EXIT
|
||||||
|
|
||||||
|
# gRowOffsetLut[5] = 5 * 160 = 800 = 0x0320. Publish it to a 24-bit
|
||||||
|
# address MAME can read back (bank 2 is clear of IIgs ROM scribbling).
|
||||||
|
cat > "$tmp/smoke.c" <<'EOF'
|
||||||
|
#include <stdint.h>
|
||||||
|
extern void iigsInitRowLut(void);
|
||||||
|
extern uint16_t gRowOffsetLut[];
|
||||||
|
int main(void) {
|
||||||
|
iigsInitRowLut();
|
||||||
|
*(volatile uint16_t *)0x025000 = gRowOffsetLut[5];
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# runInMame.sh raw-loads the image at $00:1000 and jumps there, so the
|
||||||
|
# smoke needs a BARE binary -- NOT the ExpressLoad GS/OS OMF (crt0Gsos.o,
|
||||||
|
# Loader-relocated) that clang-build.sh now emits. Build it directly with
|
||||||
|
# the bare crt0.o at --text-base 0x1000. link816 dead-strips the unused
|
||||||
|
# runtime, so offering libgcc/libc/extras is free.
|
||||||
|
echo "[clang-smoke] building bare smoke.bin (crt0.o, --text-base 0x1000)"
|
||||||
|
RT="$LLVM816_ROOT/runtime"
|
||||||
|
CLANG="$LLVM816_ROOT/tools/llvm-mos-build/bin/clang"
|
||||||
|
LLVM_MC="$LLVM816_ROOT/tools/llvm-mos-build/bin/llvm-mc"
|
||||||
|
LINK="$LLVM816_ROOT/tools/link816"
|
||||||
|
"$CLANG" --target=w65816 -O2 -ffreestanding -ffunction-sections \
|
||||||
|
-I"$RT/include" -c "$tmp/smoke.c" -o "$tmp/smoke.o"
|
||||||
|
"$LLVM_MC" -arch=w65816 -filetype=obj \
|
||||||
|
"$REPO/src/iigs/joeyDraw.s" -o "$tmp/joeyDraw.o"
|
||||||
|
"$LINK" -o "$tmp/smoke.bin" --text-base 0x1000 \
|
||||||
|
"$RT/crt0.o" "$RT/libgcc.o" "$RT/libc.o" "$RT/extras.o" \
|
||||||
|
"$tmp/smoke.o" "$tmp/joeyDraw.o"
|
||||||
|
|
||||||
|
echo "[clang-smoke] running under MAME apple2gs, checking gRowOffsetLut[5] == 0x0320"
|
||||||
|
bash "$RUN_IN_MAME" "$tmp/smoke.bin" --check 0x025000=0320
|
||||||
|
|
||||||
|
echo "[clang-smoke] PASS"
|
||||||
|
|
@ -4,8 +4,8 @@
|
||||||
# generic backend -- no native primitives, no audio, no sprite codegen yet.
|
# generic backend -- no native primitives, no audio, no sprite codegen yet.
|
||||||
# See claudeDocs/x68000_port.md for what is and is not real.
|
# See claudeDocs/x68000_port.md for what is and is not real.
|
||||||
#
|
#
|
||||||
# The toolchain is elf2x68k (BSD-3, gcc 13.4.0 / binutils / newlib), staged into
|
# The toolchain is elf2x68k (BSD-3, gcc 13.4.0 / binutils / newlib), installed
|
||||||
# toolchains/x68000 by the tarball. m68k-xelf-gcc emits ELF; the driver's
|
# by ./install.sh (X68K_ROOT). m68k-xelf-gcc emits ELF; the driver's
|
||||||
# -Wl,-elf2x68k pass converts it to a Human68k .X.
|
# -Wl,-elf2x68k pass converts it to a Human68k .X.
|
||||||
|
|
||||||
include $(dir $(lastword $(MAKEFILE_LIST)))/common.mk
|
include $(dir $(lastword $(MAKEFILE_LIST)))/common.mk
|
||||||
|
|
@ -15,12 +15,14 @@ include $(dir $(lastword $(MAKEFILE_LIST)))/common.mk
|
||||||
# make/x68000.mk` reports only "libxmplite.a is up to date" and builds nothing).
|
# make/x68000.mk` reports only "libxmplite.a is up to date" and builds nothing).
|
||||||
.DEFAULT_GOAL := all
|
.DEFAULT_GOAL := all
|
||||||
|
|
||||||
|
$(call requireTool,$(X68K_ROOT)/bin/m68k-xelf-gcc,toolchain:x68000)
|
||||||
|
$(call requireTool,$(LIBXMP_DIR)/include/libxmp-lite/xmp.h,toolchain:x68000)
|
||||||
|
|
||||||
PLATFORM := x68000
|
PLATFORM := x68000
|
||||||
BUILD := $(REPO_DIR)/build/$(PLATFORM)
|
BUILD := $(REPO_DIR)/build/$(PLATFORM)
|
||||||
LIBDIR := $(BUILD)/lib
|
LIBDIR := $(BUILD)/lib
|
||||||
BINDIR := $(BUILD)/bin
|
BINDIR := $(BUILD)/bin
|
||||||
|
|
||||||
X68K_ROOT := $(REPO_DIR)/toolchains/x68000/m68k-xelf
|
|
||||||
X68K_CC := $(X68K_ROOT)/bin/m68k-xelf-gcc
|
X68K_CC := $(X68K_ROOT)/bin/m68k-xelf-gcc
|
||||||
X68K_AR := $(X68K_ROOT)/bin/m68k-xelf-ar
|
X68K_AR := $(X68K_ROOT)/bin/m68k-xelf-ar
|
||||||
|
|
||||||
|
|
@ -32,7 +34,7 @@ export PATH := $(X68K_ROOT)/bin:$(PATH)
|
||||||
|
|
||||||
CFLAGS := $(COMMON_CFLAGS) -m68000 -fomit-frame-pointer \
|
CFLAGS := $(COMMON_CFLAGS) -m68000 -fomit-frame-pointer \
|
||||||
-DJOEYLIB_PLATFORM_X68000 \
|
-DJOEYLIB_PLATFORM_X68000 \
|
||||||
-I$(REPO_DIR)/toolchains/audio/libxmp-lite/include \
|
-I$(LIBXMP_DIR)/include \
|
||||||
-I$(SRC_DIR)/x68000 -I$(SRC_68K) -I$(REPO_DIR)/src/codegen -MMD -MP $(CFLAGS_EXTRA)
|
-I$(SRC_DIR)/x68000 -I$(SRC_68K) -I$(REPO_DIR)/src/codegen -MMD -MP $(CFLAGS_EXTRA)
|
||||||
LDFLAGS := -lm
|
LDFLAGS := -lm
|
||||||
|
|
||||||
|
|
@ -73,7 +75,7 @@ LIB_OBJS := \
|
||||||
# libxmp-lite: the Protracker decoder shared with the DOS and ST ports. Same
|
# libxmp-lite: the Protracker decoder shared with the DOS and ST ports. Same
|
||||||
# build recipe as make/atarist.mk -- LIBXMP_CORE_DISABLE_IT keeps it off math.h,
|
# build recipe as make/atarist.mk -- LIBXMP_CORE_DISABLE_IT keeps it off math.h,
|
||||||
# which this newlib does not usefully provide for a 68000 target.
|
# which this newlib does not usefully provide for a 68000 target.
|
||||||
LIBXMP_DIR := $(REPO_DIR)/toolchains/audio/libxmp-lite
|
# LIBXMP_DIR comes from the installer's config.mk.
|
||||||
LIBXMP_SRC := $(filter-out %/win32.c, $(wildcard $(LIBXMP_DIR)/src/*.c) $(wildcard $(LIBXMP_DIR)/src/loaders/*.c))
|
LIBXMP_SRC := $(filter-out %/win32.c, $(wildcard $(LIBXMP_DIR)/src/*.c) $(wildcard $(LIBXMP_DIR)/src/loaders/*.c))
|
||||||
LIBXMP_OBJDIR := $(BUILD)/obj/libxmp-lite
|
LIBXMP_OBJDIR := $(BUILD)/obj/libxmp-lite
|
||||||
LIBXMP_OBJS := $(patsubst $(LIBXMP_DIR)/src/%.c,$(LIBXMP_OBJDIR)/%.o,$(LIBXMP_SRC))
|
LIBXMP_OBJS := $(patsubst $(LIBXMP_DIR)/src/%.c,$(LIBXMP_OBJDIR)/%.o,$(LIBXMP_SRC))
|
||||||
|
|
@ -173,9 +175,6 @@ $(BINDIR)/KEYS.X: $(KEYS_SRC) $(LIB) $(LIBXMP_AR)
|
||||||
# expands prerequisites when it reads the rule, so these come first.
|
# expands prerequisites when it reads the rule, so these come first.
|
||||||
STAXI_GEN_DIR := $(EXAMPLES)/spacetaxi/generated/x68000
|
STAXI_GEN_DIR := $(EXAMPLES)/spacetaxi/generated/x68000
|
||||||
STAXI_RUN_DIR := $(BINDIR)/DATA
|
STAXI_RUN_DIR := $(BINDIR)/DATA
|
||||||
STAXI_RAW_DUMP := $(REPO_DIR)/stuff/spacetaxi/raw.bin
|
|
||||||
ROMTOLEVEL_BIN := $(REPO_DIR)/stuff/spacetaxi/romToLevel.py
|
|
||||||
STAXI_LEVEL_NUMS := 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
|
||||||
STAXI_LEVEL_GEN := $(foreach n,$(STAXI_LEVEL_NUMS),$(STAXI_GEN_DIR)/levels/level$(n).dat) \
|
STAXI_LEVEL_GEN := $(foreach n,$(STAXI_LEVEL_NUMS),$(STAXI_GEN_DIR)/levels/level$(n).dat) \
|
||||||
$(STAXI_GEN_DIR)/levels/title.dat
|
$(STAXI_GEN_DIR)/levels/title.dat
|
||||||
STAXI_LEVEL_RUN := $(patsubst $(STAXI_GEN_DIR)/%,$(STAXI_RUN_DIR)/%,$(STAXI_LEVEL_GEN))
|
STAXI_LEVEL_RUN := $(patsubst $(STAXI_GEN_DIR)/%,$(STAXI_RUN_DIR)/%,$(STAXI_LEVEL_GEN))
|
||||||
|
|
@ -227,8 +226,8 @@ $(STAXI_SPEECH_RUN): $(STAXI_SPEECH_GEN)
|
||||||
# Space Taxi. -s (strip) because the image it ships on is a 1232 KB
|
# Space Taxi. -s (strip) because the image it ships on is a 1232 KB
|
||||||
# Human68k floppy: the unstripped binary is most of the volume on its
|
# Human68k floppy: the unstripped binary is most of the volume on its
|
||||||
# own. The DATA tree next to it (levels + the precompiled cel bank) is
|
# own. The DATA tree next to it (levels + the precompiled cel bank) is
|
||||||
# what scripts/run-x68000.sh copies onto the data floppy.
|
# what joey-run copies onto the data floppy.
|
||||||
$(BINDIR)/STAXI.X: $(STAXI_SRCS) $(LIB) $(LIBXMP_AR) $(STAXI_ASSET_DSTS)
|
$(BINDIR)/STAXI.X: $(STAXI_SRCS) $(LIB) $(LIBXMP_AR) $(call staxiData,$(STAXI_ASSET_DSTS))
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
$(X68K_CC) $(CFLAGS) -s -I$(EXAMPLES)/spacetaxi $(STAXI_SRCS) $(LIB) $(LIBXMP_AR) $(LDFLAGS) -o $@
|
$(X68K_CC) $(CFLAGS) -s -I$(EXAMPLES)/spacetaxi $(STAXI_SRCS) $(LIB) $(LIBXMP_AR) $(LDFLAGS) -o $@
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
--- a/src/mame/sharp/x68k.h 2026-08-04 18:08:43.712316105 -0500
|
|
||||||
+++ b/src/mame/sharp/x68k.h 2026-08-04 18:06:29.498097848 -0500
|
|
||||||
@@ -172,18 +172,10 @@
|
|
||||||
uint8_t hdcvector = 0;
|
|
||||||
uint8_t prnvector = 0;
|
|
||||||
} m_ioc;
|
|
||||||
- struct
|
|
||||||
- {
|
|
||||||
- int inputtype = 0; // determines which input is to be received
|
|
||||||
- bool irqactive = false; // true if IRQ is being serviced
|
|
||||||
- uint8_t irqvector = 0;
|
|
||||||
- char last_mouse_x = 0; // previous mouse x-axis value
|
|
||||||
- char last_mouse_y = 0; // previous mouse y-axis value
|
|
||||||
- int bufferempty = 0; // non-zero if buffer is empty
|
|
||||||
- } m_mouse;
|
|
||||||
uint8_t m_ppi_portc = 0;
|
|
||||||
bool m_dmac_int = false;
|
|
||||||
bool m_mfp_int = false;
|
|
||||||
+ bool m_scc_int = false;
|
|
||||||
bool m_exp_irq2[2]{};
|
|
||||||
bool m_exp_irq4[2]{};
|
|
||||||
bool m_exp_nmi[2]{};
|
|
||||||
@@ -195,7 +187,6 @@
|
|
||||||
uint32_t m_gvram_wait = 0;
|
|
||||||
uint32_t m_tvram_wait = 0;
|
|
||||||
uint32_t m_vram_wait_accum = 0; // 16.16 carry between accesses
|
|
||||||
- emu_timer* m_mouse_timer = nullptr;
|
|
||||||
emu_timer* m_led_timer = nullptr;
|
|
||||||
unsigned char m_scc_prev = 0;
|
|
||||||
emu_timer* m_fdc_tc = nullptr;
|
|
||||||
--- a/src/mame/sharp/x68k.cpp 2026-08-04 18:08:43.712316105 -0500
|
|
||||||
+++ b/src/mame/sharp/x68k.cpp 2026-08-04 18:06:57.240157174 -0500
|
|
||||||
@@ -1236,6 +1236,20 @@
|
|
||||||
mouse.rxd_handler().set(m_scc, FUNC(scc8530_device::rxb_w));
|
|
||||||
m_scc->out_rtsb_callback().set(mouse, FUNC(rs232_port_device::write_rts));
|
|
||||||
|
|
||||||
+ // RS-232C -- SCC channel A. Only channel B (the mouse) was ever wired, so
|
|
||||||
+ // the machine's actual serial port had no connection at all and could not
|
|
||||||
+ // be exercised. Exposing it as a standard rs232 slot makes
|
|
||||||
+ // `-rs232c null_modem` (TCP socket) and `-rs232c pty` available, which is
|
|
||||||
+ // what an automated both-directions serial test needs.
|
|
||||||
+ m_scc->out_txda_callback().set("rs232c", FUNC(rs232_port_device::write_txd));
|
|
||||||
+ m_scc->out_dtra_callback().set("rs232c", FUNC(rs232_port_device::write_dtr));
|
|
||||||
+ m_scc->out_rtsa_callback().set("rs232c", FUNC(rs232_port_device::write_rts));
|
|
||||||
+
|
|
||||||
+ rs232_port_device &rs232c(RS232_PORT(config, "rs232c", default_rs232_devices, nullptr));
|
|
||||||
+ rs232c.rxd_handler().set(m_scc, FUNC(scc8530_device::rxa_w));
|
|
||||||
+ rs232c.dcd_handler().set(m_scc, FUNC(scc8530_device::dcda_w));
|
|
||||||
+ rs232c.cts_handler().set(m_scc, FUNC(scc8530_device::ctsa_w));
|
|
||||||
+
|
|
||||||
RP5C15(config, m_rtc, 32.768_kHz_XTAL);
|
|
||||||
m_rtc->alarm().set(m_mfpdev, FUNC(mc68901_device::i0_w));
|
|
||||||
m_rtc->set_year_offset(20);
|
|
||||||
|
|
@ -1,141 +0,0 @@
|
||||||
--- a/src/mame/sharp/x68k.h 2026-08-04 16:54:53.872522860 -0500
|
|
||||||
+++ b/src/mame/sharp/x68k.h 2026-08-04 16:54:53.876522842 -0500
|
|
||||||
@@ -195,6 +195,12 @@
|
|
||||||
bool m_exp_nmi[2]{};
|
|
||||||
uint8_t m_current_ipl = 0;
|
|
||||||
int m_led_state = 0;
|
|
||||||
+ // Extra cycles charged per VRAM access, over and above a main-memory
|
|
||||||
+ // access, in 16.16 fixed point. Set from measured values in machine_start
|
|
||||||
+ // for the machines those measurements cover; 0 (unmodelled) otherwise.
|
|
||||||
+ uint32_t m_gvram_wait = 0;
|
|
||||||
+ uint32_t m_tvram_wait = 0;
|
|
||||||
+ uint32_t m_vram_wait_accum = 0; // 16.16 carry between accesses
|
|
||||||
emu_timer* m_mouse_timer = nullptr;
|
|
||||||
emu_timer* m_led_timer = nullptr;
|
|
||||||
unsigned char m_scc_prev = 0;
|
|
||||||
@@ -265,6 +271,15 @@
|
|
||||||
uint16_t exp_r(offs_t offset, uint16_t mem_mask = ~0);
|
|
||||||
void exp_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0);
|
|
||||||
|
|
||||||
+ // VRAM wait states. The X68000's video RAM is slower than main memory;
|
|
||||||
+ // MAME charged nothing for it, so VRAM-bound code ran optimistically
|
|
||||||
+ // fast. These trampolines charge the difference and forward to the CRTC.
|
|
||||||
+ void charge_vram_wait(uint32_t increment);
|
|
||||||
+ uint16_t gvram_wait_r(offs_t offset);
|
|
||||||
+ void gvram_wait_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0);
|
|
||||||
+ uint16_t tvram_wait_r(offs_t offset);
|
|
||||||
+ void tvram_wait_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0);
|
|
||||||
+
|
|
||||||
uint16_t spritereg_r(offs_t offset);
|
|
||||||
void spritereg_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0);
|
|
||||||
uint16_t spriteram_r(offs_t offset);
|
|
||||||
--- a/src/mame/sharp/x68k.cpp 2026-08-04 16:54:53.871522864 -0500
|
|
||||||
+++ b/src/mame/sharp/x68k.cpp 2026-08-04 16:54:53.876522842 -0500
|
|
||||||
@@ -992,12 +992,80 @@
|
|
||||||
// Is this an undocumented MB89352 feature, an ASIC register, an original code bug or a bad dump?
|
|
||||||
}
|
|
||||||
|
|
||||||
+// VRAM wait states.
|
|
||||||
+//
|
|
||||||
+// The 68000 in an X68000 does not reach video RAM as fast as it reaches main
|
|
||||||
+// memory, and MAME modelled no wait at all -- so any VRAM-bound inner loop ran
|
|
||||||
+// optimistically fast, which is exactly the code a graphics library cares
|
|
||||||
+// about. The figures used here are Makoto Kamada's real-hardware measurements
|
|
||||||
+// distributed with XEiJ (credited there to uchopon, tnb and ita), taken by
|
|
||||||
+// timing tst.w against each region on five 10 MHz X68000 variants:
|
|
||||||
+//
|
|
||||||
+// region tst.w time (us) derived wait (cycles)
|
|
||||||
+// main memory 1.642 .. 1.663 0.123
|
|
||||||
+// GVRAM 1.734 .. 1.770 1.145
|
|
||||||
+// TVRAM 1.829 .. 1.850 2.046
|
|
||||||
+//
|
|
||||||
+// MAME charges nothing for main memory, so what is added here is the wait
|
|
||||||
+// RELATIVE to main memory: 1.145 - 0.123 = 1.022 cycles for GVRAM and
|
|
||||||
+// 2.046 - 0.123 = 1.923 for TVRAM. Cross-check against the raw measurements:
|
|
||||||
+// GVRAM - main memory averages ~1.03 cycles and TVRAM - main memory ~1.92
|
|
||||||
+// across the five machines, which agrees.
|
|
||||||
+//
|
|
||||||
+// Main memory's own 0.123 cycles (DRAM refresh) is deliberately NOT modelled:
|
|
||||||
+// it would mean interposing on the hottest path in the driver to recover ~0.1
|
|
||||||
+// cycles per access, and it is a uniform offset that does not change the
|
|
||||||
+// relative cost of VRAM versus RAM, which is the thing being measured.
|
|
||||||
+//
|
|
||||||
+// The wait is fractional, so it accumulates in 16.16 fixed point and whole
|
|
||||||
+// cycles are charged as they carry out -- the same technique the apple2gs
|
|
||||||
+// driver uses for its 1 MHz cycles (see slow_cycle() there).
|
|
||||||
+#define X68K_GVRAM_WAIT 0x000105a2 // 1.022 cycles, 16.16
|
|
||||||
+#define X68K_TVRAM_WAIT 0x0001ec4a // 1.923 cycles, 16.16
|
|
||||||
+
|
|
||||||
+void x68k_state::charge_vram_wait(uint32_t increment)
|
|
||||||
+{
|
|
||||||
+ // side_effects_disabled() covers debugger and Lua reads, which must not
|
|
||||||
+ // perturb the timing they are measuring.
|
|
||||||
+ if (increment == 0 || machine().side_effects_disabled())
|
|
||||||
+ return;
|
|
||||||
+ m_vram_wait_accum += increment;
|
|
||||||
+ int const cycles = m_vram_wait_accum >> 16;
|
|
||||||
+ m_vram_wait_accum &= 0xffff;
|
|
||||||
+ if (cycles != 0)
|
|
||||||
+ m_maincpu->adjust_icount(-cycles);
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+uint16_t x68k_state::gvram_wait_r(offs_t offset)
|
|
||||||
+{
|
|
||||||
+ charge_vram_wait(m_gvram_wait);
|
|
||||||
+ return m_crtc->gvram_r(offset);
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+void x68k_state::gvram_wait_w(offs_t offset, uint16_t data, uint16_t mem_mask)
|
|
||||||
+{
|
|
||||||
+ charge_vram_wait(m_gvram_wait);
|
|
||||||
+ m_crtc->gvram_w(offset, data, mem_mask);
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+uint16_t x68k_state::tvram_wait_r(offs_t offset)
|
|
||||||
+{
|
|
||||||
+ charge_vram_wait(m_tvram_wait);
|
|
||||||
+ return m_crtc->tvram_r(offset);
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+void x68k_state::tvram_wait_w(offs_t offset, uint16_t data, uint16_t mem_mask)
|
|
||||||
+{
|
|
||||||
+ charge_vram_wait(m_tvram_wait);
|
|
||||||
+ m_crtc->tvram_w(offset, data, mem_mask);
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
void x68k_state::x68k_base_map(address_map &map)
|
|
||||||
{
|
|
||||||
map(0x000000, 0xbffffb).rw(FUNC(x68k_state::emptyram_r), FUNC(x68k_state::emptyram_w));
|
|
||||||
map(0xbffffc, 0xbfffff).rw(FUNC(x68k_state::rom0_r), FUNC(x68k_state::rom0_w));
|
|
||||||
- map(0xc00000, 0xdfffff).rw(m_crtc, FUNC(x68k_crtc_device::gvram_r), FUNC(x68k_crtc_device::gvram_w));
|
|
||||||
- map(0xe00000, 0xe7ffff).rw(m_crtc, FUNC(x68k_crtc_device::tvram_r), FUNC(x68k_crtc_device::tvram_w));
|
|
||||||
+ map(0xc00000, 0xdfffff).rw(FUNC(x68k_state::gvram_wait_r), FUNC(x68k_state::gvram_wait_w));
|
|
||||||
+ map(0xe00000, 0xe7ffff).rw(FUNC(x68k_state::tvram_wait_r), FUNC(x68k_state::tvram_wait_w));
|
|
||||||
map(0xe80000, 0xe81fff).rw(m_crtc, FUNC(x68k_crtc_device::crtc_r), FUNC(x68k_crtc_device::crtc_w));
|
|
||||||
map(0xe82400, 0xe83fff).rw(FUNC(x68k_state::vid_r), FUNC(x68k_state::vid_w));
|
|
||||||
map(0xe84000, 0xe85fff).rw(m_hd63450, FUNC(hd63450_device::read), FUNC(hd63450_device::write));
|
|
||||||
@@ -1155,6 +1223,24 @@
|
|
||||||
m_spriteram = (uint16_t*)(memregion("user1")->base());
|
|
||||||
space.install_ram(0x000000,m_ram->size()-1,m_ram->pointer());
|
|
||||||
|
|
||||||
+ // VRAM wait states, applied only to the machine the measurements cover.
|
|
||||||
+ // The published figures are for a 10 MHz 68000 X68000; XEiJ's table also
|
|
||||||
+ // lists a 25 MHz X68030 (GVRAM 6.377, TVRAM 6.623) but those machines are
|
|
||||||
+ // MACHINE_NOT_WORKING here and cannot be validated, so they are left
|
|
||||||
+ // unmodelled rather than given invented numbers.
|
|
||||||
+ if (m_maincpu->clock() == 10'000'000)
|
|
||||||
+ {
|
|
||||||
+ m_gvram_wait = X68K_GVRAM_WAIT;
|
|
||||||
+ m_tvram_wait = X68K_TVRAM_WAIT;
|
|
||||||
+ }
|
|
||||||
+ else
|
|
||||||
+ {
|
|
||||||
+ m_gvram_wait = 0;
|
|
||||||
+ m_tvram_wait = 0;
|
|
||||||
+ }
|
|
||||||
+ m_vram_wait_accum = 0;
|
|
||||||
+ save_item(NAME(m_vram_wait_accum));
|
|
||||||
+
|
|
||||||
// start mouse timer
|
|
||||||
m_mouse_timer->adjust(attotime::zero, 0, attotime::from_msec(1)); // a guess for now
|
|
||||||
m_mouse.inputtype = 0;
|
|
||||||
|
|
@ -11,25 +11,23 @@
|
||||||
# scripts/bench-amiga.sh # default 150s timeout
|
# scripts/bench-amiga.sh # default 150s timeout
|
||||||
# TIMEOUT=240 scripts/bench-amiga.sh # override
|
# TIMEOUT=240 scripts/bench-amiga.sh # override
|
||||||
#
|
#
|
||||||
# Real Kickstart (toolchains/emulators/support/kickstart.rom) is
|
# Real Kickstart (installer component `kickstart`) is
|
||||||
# required -- the HAL drives OCS directly and AROS is unreliable for it.
|
# required -- the HAL drives OCS directly and AROS is unreliable for it.
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
source "$repo/scripts/lib/joeyEnv.sh"
|
||||||
|
joeyNeed FSUAE fsuae
|
||||||
|
joeyNeed KICKSTART_ROM kickstart
|
||||||
bin_dir=$repo/build/amiga/bin
|
bin_dir=$repo/build/amiga/bin
|
||||||
support=$repo/toolchains/emulators/support
|
kickstart=$KICKSTART_ROM
|
||||||
kickstart=$support/kickstart.rom
|
|
||||||
timeout_s=${TIMEOUT:-150}
|
timeout_s=${TIMEOUT:-150}
|
||||||
|
|
||||||
if [[ ! -f $bin_dir/Uber ]]; then
|
if [[ ! -f $bin_dir/Uber ]]; then
|
||||||
echo "Uber not built. Run 'make amiga' first." >&2
|
echo "Uber not built. Run 'make amiga' first." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
if [[ ! -f $kickstart ]]; then
|
|
||||||
echo "Kickstart ROM missing: $kickstart" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
work=$(mktemp -d -t joeylib-amiga-bench.XXXXXX)
|
work=$(mktemp -d -t joeylib-amiga-bench.XXXXXX)
|
||||||
mkdir -p "$work/s"
|
mkdir -p "$work/s"
|
||||||
|
|
@ -50,7 +48,7 @@ Xvfb "$display" -screen 0 800x600x24 -nolisten tcp >/dev/null 2>&1 &
|
||||||
xvfb_pid=$!
|
xvfb_pid=$!
|
||||||
sleep 0.4
|
sleep 0.4
|
||||||
cleanup() {
|
cleanup() {
|
||||||
kill "$fs_pid" 2>/dev/null || true
|
joeyStop "$fs_pid"
|
||||||
kill "$xvfb_pid" 2>/dev/null || true
|
kill "$xvfb_pid" 2>/dev/null || true
|
||||||
wait 2>/dev/null || true
|
wait 2>/dev/null || true
|
||||||
rm -rf "$work"
|
rm -rf "$work"
|
||||||
|
|
@ -58,7 +56,7 @@ cleanup() {
|
||||||
trap cleanup EXIT
|
trap cleanup EXIT
|
||||||
|
|
||||||
echo "bench-amiga: running UBER headless (timeout ${timeout_s}s)..." >&2
|
echo "bench-amiga: running UBER headless (timeout ${timeout_s}s)..." >&2
|
||||||
DISPLAY=$display fs-uae \
|
DISPLAY=$display "$FSUAE" \
|
||||||
--amiga_model=A500 \
|
--amiga_model=A500 \
|
||||||
--fast_memory=2048 \
|
--fast_memory=2048 \
|
||||||
--hard_drive_0="$work" \
|
--hard_drive_0="$work" \
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,11 @@
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
source "$repo/scripts/lib/joeyEnv.sh"
|
||||||
|
joeyNeed HATARI hatari
|
||||||
|
joeyNeed EMUTOS_IMG emutos
|
||||||
bin_dir=$repo/build/atarist/bin
|
bin_dir=$repo/build/atarist/bin
|
||||||
tos=$repo/toolchains/emulators/support/emutos-512k.img
|
tos=$EMUTOS_IMG
|
||||||
log=$bin_dir/joeylog.txt
|
log=$bin_dir/joeylog.txt
|
||||||
timeout_s=${TIMEOUT:-90}
|
timeout_s=${TIMEOUT:-90}
|
||||||
|
|
||||||
|
|
@ -41,14 +44,14 @@ Xvfb "$display" -screen 0 800x600x24 -nolisten tcp >/dev/null 2>&1 &
|
||||||
xvfb_pid=$!
|
xvfb_pid=$!
|
||||||
sleep 0.4
|
sleep 0.4
|
||||||
cleanup() {
|
cleanup() {
|
||||||
kill "$hat_pid" 2>/dev/null || true
|
joeyStop "$hat_pid"
|
||||||
kill "$xvfb_pid" 2>/dev/null || true
|
kill "$xvfb_pid" 2>/dev/null || true
|
||||||
wait 2>/dev/null || true
|
wait 2>/dev/null || true
|
||||||
}
|
}
|
||||||
trap cleanup EXIT
|
trap cleanup EXIT
|
||||||
|
|
||||||
echo "bench-atarist: running UBER headless (timeout ${timeout_s}s)..." >&2
|
echo "bench-atarist: running UBER headless (timeout ${timeout_s}s)..." >&2
|
||||||
DISPLAY=$display hatari \
|
DISPLAY=$display "$HATARI" \
|
||||||
--tos "$tos" \
|
--tos "$tos" \
|
||||||
--harddrive "$bin_dir" \
|
--harddrive "$bin_dir" \
|
||||||
--gemdos-drive C \
|
--gemdos-drive C \
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
# that log for UBER's "press any key to exit" completion marker, then
|
# that log for UBER's "press any key to exit" completion marker, then
|
||||||
# kills DOSBox. tools/uber-perf-table reads build/dos/bin/JOEYLOG.TXT.
|
# kills DOSBox. tools/uber-perf-table reads build/dos/bin/JOEYLOG.TXT.
|
||||||
#
|
#
|
||||||
# Uses the same dosbox-386sx16.conf as run-dos.sh so the cycle budget
|
# Uses the same dosbox-386sx16.conf as joey-run so the cycle budget
|
||||||
# matches the claudeDocs/PERF.md reference machine.
|
# matches the claudeDocs/PERF.md reference machine.
|
||||||
#
|
#
|
||||||
# scripts/bench-dos.sh # default 90s timeout
|
# scripts/bench-dos.sh # default 90s timeout
|
||||||
|
|
@ -15,6 +15,8 @@
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
source "$repo/scripts/lib/joeyEnv.sh"
|
||||||
|
joeyNeed DOSBOX dosbox
|
||||||
bin_dir=$repo/build/dos/bin
|
bin_dir=$repo/build/dos/bin
|
||||||
conf=$repo/scripts/dosbox-386sx16.conf
|
conf=$repo/scripts/dosbox-386sx16.conf
|
||||||
log=$bin_dir/JOEYLOG.TXT
|
log=$bin_dir/JOEYLOG.TXT
|
||||||
|
|
@ -26,15 +28,17 @@ if [[ ! -f $bin_dir/UBER.EXE ]]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
rm -f "$log" "$bin_dir/joeylog.txt"
|
rm -f "$log" "$bin_dir/joeylog.txt"
|
||||||
|
work=$(mktemp -d -t joeylib-bench-dos.XXXXXX)
|
||||||
|
|
||||||
display=:$((90 + RANDOM % 9))
|
display=:$((90 + RANDOM % 9))
|
||||||
Xvfb "$display" -screen 0 800x600x24 -nolisten tcp >/dev/null 2>&1 &
|
Xvfb "$display" -screen 0 800x600x24 -nolisten tcp >/dev/null 2>&1 &
|
||||||
xvfb_pid=$!
|
xvfb_pid=$!
|
||||||
sleep 0.4
|
sleep 0.4
|
||||||
cleanup() {
|
cleanup() {
|
||||||
kill "$dosbox_pid" 2>/dev/null || true
|
joeyStop "$dosbox_pid"
|
||||||
kill "$xvfb_pid" 2>/dev/null || true
|
kill "$xvfb_pid" 2>/dev/null || true
|
||||||
wait 2>/dev/null || true
|
wait 2>/dev/null || true
|
||||||
|
rm -rf "$work"
|
||||||
}
|
}
|
||||||
trap cleanup EXIT
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
|
@ -44,12 +48,12 @@ echo "bench-dos: running UBER headless (timeout ${timeout_s}s)..." >&2
|
||||||
# "UBER.EXE" ran against a drive that did not exist yet ("Drive C does
|
# "UBER.EXE" ran against a drive that did not exist yet ("Drive C does
|
||||||
# not exist!" / "Illegal command") and the run sat at Z:\> until the
|
# not exist!" / "Illegal command") and the run sat at Z:\> until the
|
||||||
# timeout with no JOEYLOG.TXT.
|
# timeout with no JOEYLOG.TXT.
|
||||||
DISPLAY=$display dosbox \
|
XDG_CONFIG_HOME="$work/xdg" DISPLAY=$display "$DOSBOX" "${JOEY_DOSBOX_ISOLATE[@]}" \
|
||||||
-conf "$conf" \
|
--conf "$conf" \
|
||||||
-c "MOUNT C \"$bin_dir\"" \
|
-c "MOUNT C \"$bin_dir\"" \
|
||||||
-c "C:" \
|
-c "C:" \
|
||||||
-c "UBER.EXE" \
|
-c "UBER.EXE" \
|
||||||
>/tmp/bench-dos-dosbox.log 2>&1 &
|
>"$work/dosbox.log" 2>&1 &
|
||||||
dosbox_pid=$!
|
dosbox_pid=$!
|
||||||
|
|
||||||
waited=0
|
waited=0
|
||||||
|
|
|
||||||
|
|
@ -15,23 +15,27 @@
|
||||||
# MAME_ROMPATH override ROM dir (default ~/.mame/roms)
|
# MAME_ROMPATH override ROM dir (default ~/.mame/roms)
|
||||||
# BENCH_MAX_FRAMES hard frame cap before giving up (default 36000)
|
# BENCH_MAX_FRAMES hard frame cap before giving up (default 36000)
|
||||||
#
|
#
|
||||||
# Requires: toolchains/env.sh sourced; build/iigs/bin/joey.2mg with UBER on
|
# Requires: ./install.sh install iigs; build/iigs/bin/joey.2mg with UBER on
|
||||||
# it (run 'make iigs-disk', ensure UBER was not skipped for space).
|
# it (run 'make iigs-disk', ensure UBER was not skipped for space).
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
|
||||||
: "${LLVM816_ROOT:?bench-iigs.sh: source toolchains/env.sh first}"
|
source "$repo/scripts/lib/joeyEnv.sh"
|
||||||
CADIUS="${CADIUS:-$LLVM816_ROOT/tools/cadius/cadius}"
|
joeyNeed LLVM816_ROOT toolchain:iigs
|
||||||
|
joeyNeed CADIUS cadius
|
||||||
|
joeyNeed MAME mame
|
||||||
|
joeyNeed IIGS_ROMPATH iigsFirmware
|
||||||
|
joeyNeed GSOS_SYSTEM_DISK iigsFirmware
|
||||||
|
|
||||||
sys_disk=$repo/toolchains/emulators/support/gsos-system.po
|
sys_disk=$GSOS_SYSTEM_DISK
|
||||||
# Overridable so a diagnostic build (e.g. the -DUBER_PROBE binary built by
|
# Overridable so a diagnostic build (e.g. the -DUBER_PROBE binary built by
|
||||||
# `make iigs-verify-shrtail`) can be run off its own single-app disk into its
|
# `make iigs-verify-shrtail`) can be run off its own single-app disk into its
|
||||||
# own log without disturbing the goldened joey.2mg / joeylog.txt pair. Both
|
# own log without disturbing the goldened joey.2mg / joeylog.txt pair. Both
|
||||||
# default to the standard locations, so normal bench runs are unchanged.
|
# default to the standard locations, so normal bench runs are unchanged.
|
||||||
data_disk="${BENCH_DATA_DISK:-$repo/build/iigs/bin/joey.2mg}"
|
data_disk="${BENCH_DATA_DISK:-$repo/build/iigs/bin/joey.2mg}"
|
||||||
out_log="${BENCH_OUT_LOG:-$repo/build/iigs/bin/joeylog.txt}"
|
out_log="${BENCH_OUT_LOG:-$repo/build/iigs/bin/joeylog.txt}"
|
||||||
rompath="${MAME_ROMPATH:-$HOME/.mame/roms}"
|
rompath="$IIGS_ROMPATH"
|
||||||
maxFrames="${BENCH_MAX_FRAMES:-36000}"
|
maxFrames="${BENCH_MAX_FRAMES:-36000}"
|
||||||
|
|
||||||
[ -x "$CADIUS" ] || { echo "bench-iigs: cadius not found at $CADIUS" >&2; exit 2; }
|
[ -x "$CADIUS" ] || { echo "bench-iigs: cadius not found at $CADIUS" >&2; exit 2; }
|
||||||
|
|
@ -212,7 +216,7 @@ LUA
|
||||||
echo "bench-iigs: running UBER headless under MAME (cap $maxFrames frames)..." >&2
|
echo "bench-iigs: running UBER headless under MAME (cap $maxFrames frames)..." >&2
|
||||||
cd "$work"
|
cd "$work"
|
||||||
out=$(QT_QPA_PLATFORM=offscreen SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \
|
out=$(QT_QPA_PLATFORM=offscreen SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \
|
||||||
timeout 1200 mame apple2gs \
|
timeout 1200 "$MAME" apple2gs \
|
||||||
-rompath "$rompath" \
|
-rompath "$rompath" \
|
||||||
-flop3 "$work/boot.po" -flop4 "$work/joey.2mg" \
|
-flop3 "$work/boot.po" -flop4 "$work/joey.2mg" \
|
||||||
-video none -sound none -nothrottle \
|
-video none -sound none -nothrottle \
|
||||||
|
|
|
||||||
|
|
@ -25,12 +25,16 @@
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
: "${LLVM816_ROOT:?bench-staxi-title.sh: source toolchains/env.sh first}"
|
source "$repo/scripts/lib/joeyEnv.sh"
|
||||||
CADIUS="${CADIUS:-$LLVM816_ROOT/tools/cadius/cadius}"
|
joeyNeed LLVM816_ROOT toolchain:iigs
|
||||||
|
joeyNeed CADIUS cadius
|
||||||
|
joeyNeed MAME mame
|
||||||
|
joeyNeed IIGS_ROMPATH iigsFirmware
|
||||||
|
joeyNeed GSOS_SYSTEM_DISK iigsFirmware
|
||||||
|
|
||||||
sys_disk=$repo/toolchains/emulators/support/gsos-system.po
|
sys_disk=$GSOS_SYSTEM_DISK
|
||||||
data_disk=$repo/build/iigs/bin/joey.2mg
|
data_disk=$repo/build/iigs/bin/joey.2mg
|
||||||
rompath="${MAME_ROMPATH:-$HOME/.mame/roms}"
|
rompath="$IIGS_ROMPATH"
|
||||||
maxFrames="${STAXI_MAX_FRAMES:-15000}"
|
maxFrames="${STAXI_MAX_FRAMES:-15000}"
|
||||||
maxChanges="${STAXI_MAX_CHANGES:-80}"
|
maxChanges="${STAXI_MAX_CHANGES:-80}"
|
||||||
label="${1:-run}"
|
label="${1:-run}"
|
||||||
|
|
@ -148,7 +152,7 @@ LUA
|
||||||
echo "bench-staxi-title[$label]: running STAXI headless under MAME (cap $maxFrames frames, $maxChanges changes)..." >&2
|
echo "bench-staxi-title[$label]: running STAXI headless under MAME (cap $maxFrames frames, $maxChanges changes)..." >&2
|
||||||
cd "$work"
|
cd "$work"
|
||||||
out=$(QT_QPA_PLATFORM=offscreen SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \
|
out=$(QT_QPA_PLATFORM=offscreen SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \
|
||||||
timeout "${STAXI_WALL_TIMEOUT:-900}" mame apple2gs \
|
timeout "${STAXI_WALL_TIMEOUT:-900}" "$MAME" apple2gs \
|
||||||
-rompath "$rompath" \
|
-rompath "$rompath" \
|
||||||
-flop3 "$work/boot.po" -flop4 "$work/joey.2mg" \
|
-flop3 "$work/boot.po" -flop4 "$work/joey.2mg" \
|
||||||
-video none -sound none -nothrottle \
|
-video none -sound none -nothrottle \
|
||||||
|
|
|
||||||
|
|
@ -22,13 +22,15 @@
|
||||||
# larger than the gate's because a real measurement window runs each operation
|
# larger than the gate's because a real measurement window runs each operation
|
||||||
# for hundreds of frames rather than one.
|
# for hundreds of frames rather than one.
|
||||||
#
|
#
|
||||||
# X68K_SCRATCH=<dir with x68mame/> bash scripts/bench-x68000.sh
|
# bash scripts/bench-x68000.sh
|
||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
|
|
||||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
SP="${X68K_SCRATCH:?set X68K_SCRATCH to a work dir containing x68mame/}"
|
source "$repo/scripts/lib/joeyEnv.sh"
|
||||||
MAME="$repo/toolchains/cache/mame-mame0264/x68k"
|
joeyNeed MAME mame
|
||||||
TEMPLATE="$SP/x68mame/HUMAN302.XDF"
|
joeyNeed X68K_DIR x68kFirmware
|
||||||
|
joeyNeedFile "$X68K_DIR/roms/x68000/cgrom.dat" x68kCgrom
|
||||||
|
TEMPLATE="$X68K_DIR/HUMAN302.XDF"
|
||||||
FRAMES="${X68K_BENCH_FRAMES:-120000}"
|
FRAMES="${X68K_BENCH_FRAMES:-120000}"
|
||||||
WALL="${X68K_BENCH_WALL:-7200}"
|
WALL="${X68K_BENCH_WALL:-7200}"
|
||||||
# Which plane the built port presents into: must match X68K_PRESENT_TEXT in
|
# Which plane the built port presents into: must match X68K_PRESENT_TEXT in
|
||||||
|
|
@ -36,7 +38,9 @@ WALL="${X68K_BENCH_WALL:-7200}"
|
||||||
# the hashes are of the surface in RAM and are the same either way.
|
# the hashes are of the surface in RAM and are the same either way.
|
||||||
PRESENT_PLANE="${X68K_PRESENT_PLANE:-text}"
|
PRESENT_PLANE="${X68K_PRESENT_PLANE:-text}"
|
||||||
|
|
||||||
export PATH="$repo/toolchains/x68000/m68k-xelf/bin:$PATH"
|
joeyNeed X68K_ROOT toolchain:x68000
|
||||||
|
joeyNeed LIBXMP_DIR libxmp
|
||||||
|
export PATH="$X68K_ROOT/bin:$PATH"
|
||||||
work=$(mktemp -d -t joey-x68bench.XXXXXX)
|
work=$(mktemp -d -t joey-x68bench.XXXXXX)
|
||||||
trap 'rm -rf "$work"' EXIT
|
trap 'rm -rf "$work"' EXIT
|
||||||
|
|
||||||
|
|
@ -48,7 +52,7 @@ trap 'rm -rf "$work"' EXIT
|
||||||
m68k-xelf-gcc -s -O2 -m68000 -fomit-frame-pointer \
|
m68k-xelf-gcc -s -O2 -m68000 -fomit-frame-pointer \
|
||||||
-DJOEYLIB_PLATFORM_X68000 \
|
-DJOEYLIB_PLATFORM_X68000 \
|
||||||
-I"$repo/include" -I"$repo/src/core" -I"$repo/src/x68000" \
|
-I"$repo/include" -I"$repo/src/core" -I"$repo/src/x68000" \
|
||||||
-I"$repo/toolchains/audio/libxmp-lite/include" \
|
-I"$LIBXMP_DIR/include" \
|
||||||
"$repo/examples/uber/uber.c" \
|
"$repo/examples/uber/uber.c" \
|
||||||
"$repo/build/x68000/lib/libjoey.a" "$repo/build/x68000/lib/libxmplite.a" -lm \
|
"$repo/build/x68000/lib/libjoey.a" "$repo/build/x68000/lib/libxmplite.a" -lm \
|
||||||
-o "$work/UBER.X" || exit 1
|
-o "$work/UBER.X" || exit 1
|
||||||
|
|
@ -168,7 +172,7 @@ end)
|
||||||
LUA
|
LUA
|
||||||
|
|
||||||
runLog=$(timeout -s KILL "$WALL" "$MAME" x68000 -bios ipl10 \
|
runLog=$(timeout -s KILL "$WALL" "$MAME" x68000 -bios ipl10 \
|
||||||
-rompath "$SP/x68mame/roms" -flop1 "$work/boot.xdf" -flop2 "$work/data.xdf" \
|
-rompath "$X68K_DIR/roms" -flop1 "$work/boot.xdf" -flop2 "$work/data.xdf" \
|
||||||
-video none -sound none -nothrottle \
|
-video none -sound none -nothrottle \
|
||||||
-autoboot_script "$work/bench.lua" </dev/null 2>&1 | grep '^BENCH')
|
-autoboot_script "$work/bench.lua" </dev/null 2>&1 | grep '^BENCH')
|
||||||
echo "$runLog"
|
echo "$runLog"
|
||||||
|
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# captureDos.sh -- run a built DOS example in headless DOSBox via Xvfb
|
|
||||||
# and snap a PNG of the simulated screen. Lets us verify the runtime
|
|
||||||
# output without an interactive emulator session.
|
|
||||||
#
|
|
||||||
# scripts/captureDos.sh staxi /tmp/staxi.png # default 5s wait
|
|
||||||
# scripts/captureDos.sh staxi /tmp/staxi.png 12 # 12s wait
|
|
||||||
#
|
|
||||||
# The wait time gives the program enough cycles to boot, draw, and
|
|
||||||
# settle on a steady frame. Increase for slow boots / cold caches.
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
if [[ $# -lt 2 || $# -gt 3 ]]; then
|
|
||||||
echo "usage: $0 <example-name> <out.png> [wait-seconds]" >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
prog=$1
|
|
||||||
out=$2
|
|
||||||
wait_s=${3:-5}
|
|
||||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
|
||||||
bin_dir=$repo/build/dos/bin
|
|
||||||
conf=$repo/scripts/dosbox-386sx16.conf
|
|
||||||
file=${prog^^}.EXE
|
|
||||||
appdir=${prog^^}
|
|
||||||
|
|
||||||
if [[ -f "$bin_dir/$appdir/$file" ]]; then
|
|
||||||
mount_dir="$bin_dir/$appdir"
|
|
||||||
elif [[ -f "$bin_dir/$file" ]]; then
|
|
||||||
mount_dir="$bin_dir"
|
|
||||||
else
|
|
||||||
echo "captureDos: $file not found in $bin_dir" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
display=:$((90 + RANDOM % 9))
|
|
||||||
|
|
||||||
Xvfb "$display" -screen 0 800x600x24 -nolisten tcp &
|
|
||||||
xvfb_pid=$!
|
|
||||||
sleep 0.3
|
|
||||||
trap 'kill $xvfb_pid 2>/dev/null || true; kill $dosbox_pid 2>/dev/null || true' EXIT
|
|
||||||
|
|
||||||
# Mount with an explicit -c, not by handing DOSBox the directory as a
|
|
||||||
# positional argument: 0.74-3 ignored it here and dropped the program at
|
|
||||||
# "Drive C does not exist", which looks exactly like a program that
|
|
||||||
# failed to start. The cycles override has to be one argv entry too --
|
|
||||||
# unquoted it split into three words and ate the path.
|
|
||||||
dosboxArgs=(-conf "$conf" -set "mouse_capture=seamless")
|
|
||||||
if [[ -n ${CYCLES:-} ]]; then
|
|
||||||
dosboxArgs+=(-set "cpu cycles=${CYCLES}")
|
|
||||||
fi
|
|
||||||
dosboxArgs+=(-c "mount c \"$mount_dir\"" -c "C:" -c "$file")
|
|
||||||
|
|
||||||
DISPLAY=$display SDL_VIDEO_X11_VISUALID="" \
|
|
||||||
dosbox "${dosboxArgs[@]}" >/tmp/captureDos-dosbox.log 2>&1 &
|
|
||||||
dosbox_pid=$!
|
|
||||||
|
|
||||||
# Wait for DOSBox to boot the program and settle.
|
|
||||||
sleep "$wait_s"
|
|
||||||
|
|
||||||
DISPLAY=$display import -window root -screen "$out" 2>/dev/null
|
|
||||||
echo "captureDos: wrote $out"
|
|
||||||
|
|
||||||
# DOSBox is still running -- the trap will clean it up.
|
|
||||||
|
|
@ -2,13 +2,17 @@
|
||||||
# check-blank.sh - Smoke-test the new-port template (src/blank/blank.c).
|
# check-blank.sh - Smoke-test the new-port template (src/blank/blank.c).
|
||||||
#
|
#
|
||||||
# A blank port has no toolchain, so we compile it with the HOST cc just to
|
# A blank port has no toolchain, so we compile it with the HOST cc just to
|
||||||
# prove two things stay true as the library evolves:
|
# prove three things stay true as the library evolves:
|
||||||
# 1. The template + the JOEYLIB_PLATFORM_BLANK blocks in platform.h still
|
# 1. The template + the JOEYLIB_PLATFORM_BLANK blocks in platform.h still
|
||||||
# compile (catches a new platform-#if dispatch point that forgot a BLANK
|
# compile (catches a new platform-#if dispatch point that forgot a BLANK
|
||||||
# case -- e.g. assetLoad.c's tile target, the sprite emitter selector).
|
# case -- e.g. assetLoad.c's tile target, the sprite emitter selector).
|
||||||
# 2. blank.c + the generics LINK with no unresolved jlp* ops -- i.e. a new
|
# 2. blank.c + the generics LINK with no unresolved jlp* ops -- i.e. a new
|
||||||
# chunky port really does get everything except the platform services for
|
# chunky port really does get everything except the platform services for
|
||||||
# free from src/generic/.
|
# free from src/generic/.
|
||||||
|
# 3. The same sources stay gnu89-clean: no C99-only construct except `//`
|
||||||
|
# comments. BeOS R5 builds with gcc 2.95 (x86) and Metrowerks mwcc (PPC),
|
||||||
|
# and neither speaks C99. This pass is a lint, so it always uses gcc
|
||||||
|
# (-Wc90-c99-compat is gcc-only) whatever CC says.
|
||||||
#
|
#
|
||||||
# Source set mirrors a real blank-port build: core + ALL of src/generic
|
# Source set mirrors a real blank-port build: core + ALL of src/generic
|
||||||
# (including the stub sprite emitter) + blank.c; NOT src/codegen/spriteCompile.c
|
# (including the stub sprite emitter) + blank.c; NOT src/codegen/spriteCompile.c
|
||||||
|
|
@ -22,9 +26,10 @@ trap 'rm -rf "$work"' EXIT
|
||||||
|
|
||||||
inc=(-Iinclude -Iinclude/joey -Isrc/core -Isrc/codegen)
|
inc=(-Iinclude -Iinclude/joey -Isrc/core -Isrc/codegen)
|
||||||
cd "$repo"
|
cd "$repo"
|
||||||
|
srcs=(src/core/*.c src/generic/*.c src/blank/blank.c)
|
||||||
|
|
||||||
fail=0
|
fail=0
|
||||||
for f in src/core/*.c src/generic/*.c src/blank/blank.c; do
|
for f in "${srcs[@]}"; do
|
||||||
o="$work/$(echo "$f" | tr / _).o"
|
o="$work/$(echo "$f" | tr / _).o"
|
||||||
if ! "$cc" -DJOEYLIB_PLATFORM_BLANK "${inc[@]}" -Wall -c "$f" -o "$o" 2>"$work/err"; then
|
if ! "$cc" -DJOEYLIB_PLATFORM_BLANK "${inc[@]}" -Wall -c "$f" -o "$o" 2>"$work/err"; then
|
||||||
echo "check-blank: COMPILE FAIL: $f" >&2
|
echo "check-blank: COMPILE FAIL: $f" >&2
|
||||||
|
|
@ -54,4 +59,26 @@ if [ -n "$missing" ]; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "check-blank: PASS (blank template compiles + links; all ops resolve via blank.c + generics)"
|
# gnu89 dialect lint. gcc also reports `//` comments under -Wc90-c99-compat
|
||||||
|
# (once per file); gcc 2.95 and mwcc both accept them, so those are dropped.
|
||||||
|
# Any other warning, or a hard error such as a for-loop declaration, fails.
|
||||||
|
# LC_ALL=C: a translated gcc says "Warnung:", which the grep would miss --
|
||||||
|
# the lint would pass having checked nothing.
|
||||||
|
command -v gcc >/dev/null || { echo "check-blank: FAIL - gcc not found (the gnu89 lint needs it)" >&2; exit 1; }
|
||||||
|
lint=""
|
||||||
|
for f in "${srcs[@]}"; do
|
||||||
|
status=0
|
||||||
|
LC_ALL=C gcc -std=gnu89 -Wc90-c99-compat -Wdeclaration-after-statement -fno-diagnostics-show-caret -DJOEYLIB_PLATFORM_BLANK "${inc[@]}" -fsyntax-only "$f" 2>"$work/err" || status=$?
|
||||||
|
hits=$(grep -E 'warning:|error:' "$work/err" | grep -v 'C++ style comments are incompatible with C90' || true)
|
||||||
|
if [ "$status" != 0 ] && [ -z "$hits" ]; then
|
||||||
|
hits=$(cat "$work/err")
|
||||||
|
fi
|
||||||
|
[ -z "$hits" ] || lint+="$hits"$'\n'
|
||||||
|
done
|
||||||
|
if [ -n "$lint" ]; then
|
||||||
|
echo "check-blank: FAIL - shared code is not gnu89-clean (BeOS R5 needs gcc 2.95 / mwcc):" >&2
|
||||||
|
printf '%s' "$lint" | sort -u | sed 's/^/ /' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "check-blank: PASS (blank template compiles + links; all ops resolve via blank.c + generics; shared code is gnu89-clean)"
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,16 @@
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
: "${LLVM816_ROOT:?diag-staxi-screen.sh: source toolchains/env.sh first}"
|
source "$repo/scripts/lib/joeyEnv.sh"
|
||||||
CADIUS="${CADIUS:-$LLVM816_ROOT/tools/cadius/cadius}"
|
joeyNeed LLVM816_ROOT toolchain:iigs
|
||||||
|
joeyNeed CADIUS cadius
|
||||||
|
joeyNeed MAME mame
|
||||||
|
joeyNeed IIGS_ROMPATH iigsFirmware
|
||||||
|
joeyNeed GSOS_SYSTEM_DISK iigsFirmware
|
||||||
|
|
||||||
sys_disk=$repo/toolchains/emulators/support/gsos-system.po
|
sys_disk=$GSOS_SYSTEM_DISK
|
||||||
data_disk=$repo/build/iigs/bin/joey.2mg
|
data_disk=$repo/build/iigs/bin/joey.2mg
|
||||||
rompath="${MAME_ROMPATH:-$HOME/.mame/roms}"
|
rompath="$IIGS_ROMPATH"
|
||||||
dumpFrame="${STAXI_DUMP_FRAME:-9000}"
|
dumpFrame="${STAXI_DUMP_FRAME:-9000}"
|
||||||
|
|
||||||
work=$(mktemp -d -t joeylib-staxidiag.XXXXXX)
|
work=$(mktemp -d -t joeylib-staxidiag.XXXXXX)
|
||||||
|
|
@ -120,7 +124,7 @@ LUA
|
||||||
echo "diag-staxi: dumping SHR at frame $dumpFrame..." >&2
|
echo "diag-staxi: dumping SHR at frame $dumpFrame..." >&2
|
||||||
cd "$work"
|
cd "$work"
|
||||||
out=$(QT_QPA_PLATFORM=offscreen SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \
|
out=$(QT_QPA_PLATFORM=offscreen SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \
|
||||||
timeout 900 mame apple2gs \
|
timeout 900 "$MAME" apple2gs \
|
||||||
-rompath "$rompath" \
|
-rompath "$rompath" \
|
||||||
-flop3 "$work/boot.po" -flop4 "$work/joey.2mg" \
|
-flop3 "$work/boot.po" -flop4 "$work/joey.2mg" \
|
||||||
-video none -sound none -nothrottle \
|
-video none -sound none -nothrottle \
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# DOSBox config: simulate an Intel 386SX-16 (1988), the slowest 386
|
# DOSBox Staging config: simulate an Intel 386SX-16 (1988), the slowest
|
||||||
# desktop CPU JoeyLib could realistically be run on. Use this floor
|
# 386 desktop CPU JoeyLib could realistically be run on. Use this floor
|
||||||
# to verify the DOS port still hits its frame budget on the bottom of
|
# to verify the DOS port still hits its frame budget on the bottom of
|
||||||
# the 386 stack rather than coasting on host CPU.
|
# the 386 stack rather than coasting on host CPU.
|
||||||
#
|
#
|
||||||
|
|
@ -14,15 +14,19 @@
|
||||||
# defeat slow-CPU simulation).
|
# defeat slow-CPU simulation).
|
||||||
# cputype = 386 386 instruction set (no 486 BSWAP /
|
# cputype = 386 386 instruction set (no 486 BSWAP /
|
||||||
# CMPXCHG, no Pentium MMX).
|
# CMPXCHG, no Pentium MMX).
|
||||||
# cycles = fixed 2200 community-standard approximation for
|
# cpu_cycles, cpu_cycles_protected = 2200
|
||||||
# 386SX-16 throughput in DOSBox.
|
# community-standard approximation for
|
||||||
# DOSBox-Staging deprecates this in favor
|
# 386SX-16 throughput. Staging splits real
|
||||||
# of cpu_cycles, but still accepts it.
|
# and protected mode; JoeyLib's DJGPP
|
||||||
# Vanilla DOSBox and DOSBox-X only know
|
# programs run in protected mode, so both
|
||||||
# the old key, so 'cycles' stays for
|
# are pinned. The window title shows the
|
||||||
# cross-fork portability.
|
# rate in force ("<PROG> - 2200 cycles/ms").
|
||||||
|
#
|
||||||
|
# Launch with --noprimaryconf --nolocalconf so nothing from the user's
|
||||||
|
# own DOSBox configuration leaks into a run.
|
||||||
|
|
||||||
[cpu]
|
[cpu]
|
||||||
core = normal
|
core = normal
|
||||||
cputype = 386
|
cputype = 386
|
||||||
cycles = fixed 2200
|
cpu_cycles = 2200
|
||||||
|
cpu_cycles_protected = 2200
|
||||||
|
|
|
||||||
83
scripts/lib/joeyEnv.sh
Normal file
83
scripts/lib/joeyEnv.sh
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
# Sourced by the scripts in scripts/ (and tools/) so every one of them finds
|
||||||
|
# toolchains the same way: from the environment ./install.sh generates. A script calls joeyNeed for each tool it uses, and gets a clear
|
||||||
|
# "install this" message (exit 2, which the gate runners treat as SKIP)
|
||||||
|
# instead of failing halfway through with a missing binary.
|
||||||
|
|
||||||
|
JOEY_REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
|
||||||
|
if [ -f "$JOEY_REPO/toolchains/env.sh" ]; then
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
source "$JOEY_REPO/toolchains/env.sh"
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# joeyNeed VAR INSTALL-TARGET -- exit 2 unless VAR names an existing path.
|
||||||
|
joeyNeed() {
|
||||||
|
local var="$1"
|
||||||
|
local target="$2"
|
||||||
|
if [ -z "${!var:-}" ] || [ ! -e "${!var}" ]; then
|
||||||
|
echo "$(basename "$0"): $var is not available; run: ./install.sh install $target" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# joeyNeedFile PATH INSTALL-TARGET -- exit 2 unless PATH exists.
|
||||||
|
joeyNeedFile() {
|
||||||
|
if [ ! -e "$1" ]; then
|
||||||
|
echo "$(basename "$0"): $1 is missing; run: ./install.sh install $2" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# DOSBox Staging must never read or write the user's own DOSBox setup, even
|
||||||
|
# ~/.config/dosbox (it creates that tree even with --noprimaryconf). Launch
|
||||||
|
# it as
|
||||||
|
# XDG_CONFIG_HOME="$work/xdg" "$DOSBOX" "${JOEY_DOSBOX_ISOLATE[@]}" ...
|
||||||
|
JOEY_DOSBOX_ISOLATE=(--noprimaryconf --nolocalconf)
|
||||||
|
|
||||||
|
|
||||||
|
# joeyFindWindow DISPLAY PID -- print the X window owned by process PID.
|
||||||
|
# More robust than matching titles, which differ between emulator builds.
|
||||||
|
joeyFindWindow() {
|
||||||
|
local win
|
||||||
|
for win in $(DISPLAY="$1" xdotool search --name -- "." 2>/dev/null); do
|
||||||
|
if [ "$(DISPLAY="$1" xdotool getwindowpid "$win" 2>/dev/null)" = "$2" ]; then
|
||||||
|
printf '%s\n' "$win"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# joeyAlive PID -- PID is running. A zombie (exited, not yet reaped) is not,
|
||||||
|
# although `kill -0` still succeeds on it.
|
||||||
|
joeyAlive() {
|
||||||
|
local stat
|
||||||
|
stat="$(ps -o stat= -p "$1" 2>/dev/null)" || return 1
|
||||||
|
[[ "$stat" != Z* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# joeyStop PID... -- stop emulator processes and make sure they are gone.
|
||||||
|
# Hatari, MAME and DOSBox Staging ignore or are slow to act on SIGTERM; a
|
||||||
|
# plain `kill` left them running (and burning CPU) after their script had
|
||||||
|
# finished, so escalate to SIGKILL. Only ever pass PIDs this script started.
|
||||||
|
joeyStop() {
|
||||||
|
local pid
|
||||||
|
local tick
|
||||||
|
for pid in "$@"; do
|
||||||
|
[ -n "$pid" ] || continue
|
||||||
|
kill "$pid" 2>/dev/null || continue
|
||||||
|
for tick in 1 2 3 4 5 6 7 8 9 10; do
|
||||||
|
joeyAlive "$pid" || break
|
||||||
|
sleep 0.2
|
||||||
|
done
|
||||||
|
kill -9 "$pid" 2>/dev/null || true
|
||||||
|
# Reap it when it is this shell's child (a no-op otherwise), so no
|
||||||
|
# zombie outlives the script where nothing else would reap it.
|
||||||
|
wait "$pid" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# make-agi-iigs-disk.sh - Build the 2MB ProDOS data volume that holds the
|
# make-agi-iigs-disk.sh - Build the 2MB ProDOS data volume that holds the
|
||||||
# IIgs AGI interpreter plus one AGI v2 game's resources. run-iigs.sh mounts
|
# IIgs AGI interpreter plus one AGI v2 game's resources. The
|
||||||
# it as the CFFA2 card's second partition (-hard2); GS/OS boots from the
|
# iigs-game-disk-agi make target merges it into the bootable AGI.2mg that
|
||||||
# first partition and auto-mounts this one.
|
# joey-run starts (--ram 8M).
|
||||||
#
|
#
|
||||||
# A single 800KB 3.5" floppy can't hold the ~240KB AGI binary plus KQ3's
|
# A single 800KB 3.5" floppy can't hold the ~240KB AGI binary plus KQ3's
|
||||||
# ~644KB of required resources (878KB total), and MAME's IIgs 3.5 drive is
|
# ~644KB of required resources (878KB total), and MAME's IIgs 3.5 drive is
|
||||||
|
|
@ -11,12 +11,13 @@
|
||||||
#
|
#
|
||||||
# Usage: scripts/make-agi-iigs-disk.sh <output.2mg> [game-name]
|
# Usage: scripts/make-agi-iigs-disk.sh <output.2mg> [game-name]
|
||||||
# game-name : subdirectory under examples/agi/gamedata/ (default kq3)
|
# game-name : subdirectory under examples/agi/gamedata/ (default kq3)
|
||||||
# Requires: toolchains/env.sh sourced (LLVM816_ROOT), built build/iigs/bin/AGI.
|
# Requires: ./install.sh install toolchain:iigs; built build/iigs/bin/AGI.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
: "${LLVM816_ROOT:?make-agi-iigs-disk.sh: source toolchains/env.sh first}"
|
source "$repo/scripts/lib/joeyEnv.sh"
|
||||||
CADIUS="${CADIUS:-$LLVM816_ROOT/tools/cadius/cadius}"
|
joeyNeed LLVM816_ROOT toolchain:iigs
|
||||||
|
joeyNeed CADIUS cadius
|
||||||
BINDIR="${BINDIR:-$repo/build/iigs/bin}"
|
BINDIR="${BINDIR:-$repo/build/iigs/bin}"
|
||||||
|
|
||||||
out=${1:?usage: make-agi-iigs-disk.sh <output.2mg> [game]}
|
out=${1:?usage: make-agi-iigs-disk.sh <output.2mg> [game]}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
# Usage: scripts/make-iigs-boot-disk.sh <out.po> <EXAMPLE> [volume]
|
# Usage: scripts/make-iigs-boot-disk.sh <out.po> <EXAMPLE> [volume]
|
||||||
# EXAMPLE uppercase binary name as it appears on the data disk
|
# EXAMPLE uppercase binary name as it appears on the data disk
|
||||||
# volume data volume name, default JOEYLIB
|
# volume data volume name, default JOEYLIB
|
||||||
# Requires: toolchains/env.sh sourced.
|
# Requires: ./install.sh install iigs (the toolchain and the GS/OS disk).
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
out=${1:?usage: make-iigs-boot-disk.sh <out.po> <EXAMPLE> [volume]}
|
out=${1:?usage: make-iigs-boot-disk.sh <out.po> <EXAMPLE> [volume]}
|
||||||
|
|
@ -27,9 +27,12 @@ prog=${2:?usage: make-iigs-boot-disk.sh <out.po> <EXAMPLE> [volume]}
|
||||||
vol=${3:-JOEYLIB}
|
vol=${3:-JOEYLIB}
|
||||||
|
|
||||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
: "${LLVM816_ROOT:?make-iigs-boot-disk.sh: source toolchains/env.sh first}"
|
source "$repo/scripts/lib/joeyEnv.sh"
|
||||||
CADIUS="${CADIUS:-$LLVM816_ROOT/tools/cadius/cadius}"
|
joeyNeed LLVM816_ROOT toolchain:iigs
|
||||||
sysDisk="$repo/toolchains/emulators/support/gsos-system.po"
|
joeyNeed CADIUS cadius
|
||||||
|
joeyNeed IIGS_CLANG_BUILD toolchain:iigs
|
||||||
|
joeyNeed GSOS_SYSTEM_DISK iigsFirmware
|
||||||
|
sysDisk=$GSOS_SYSTEM_DISK
|
||||||
|
|
||||||
[ -x "$CADIUS" ] || { echo "make-iigs-boot-disk.sh: cadius not found at $CADIUS" >&2; exit 2; }
|
[ -x "$CADIUS" ] || { echo "make-iigs-boot-disk.sh: cadius not found at $CADIUS" >&2; exit 2; }
|
||||||
[ -f "$sysDisk" ] || { echo "make-iigs-boot-disk.sh: missing $sysDisk" >&2; exit 2; }
|
[ -f "$sysDisk" ] || { echo "make-iigs-boot-disk.sh: missing $sysDisk" >&2; exit 2; }
|
||||||
|
|
@ -51,7 +54,7 @@ cp "$sysDisk" "$out"
|
||||||
# makes room: the stock disk has under 50KB free.
|
# makes room: the stock disk has under 50KB free.
|
||||||
"$CADIUS" DELETEFILE "$out" "/$sysVol/System/Start" >/dev/null
|
"$CADIUS" DELETEFILE "$out" "/$sysVol/System/Start" >/dev/null
|
||||||
|
|
||||||
"$repo/toolchains/iigs/clang-build.sh" \
|
"$IIGS_CLANG_BUILD" \
|
||||||
-DJOEY_START_PATH="\"/$vol/$prog\"" \
|
-DJOEY_START_PATH="\"/$vol/$prog\"" \
|
||||||
-o "$work/Start#B30000" \
|
-o "$work/Start#B30000" \
|
||||||
"$repo/tools/iigsStart/iigsStart.c" \
|
"$repo/tools/iigsStart/iigsStart.c" \
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# make-iigs-disk.sh - Build the JOEYLIB ProDOS disk image (joey.2mg) that
|
# make-iigs-disk.sh - Build the JOEYLIB ProDOS disk image (joey.2mg) that
|
||||||
# run-iigs-mame.sh and run-iigs.sh mount as the data disk (flop4). It holds
|
# the gates and joey-run mount as the data disk beside GS/OS. It holds
|
||||||
# the launchable example binaries as ProDOS type $B3 (S16 / GS-OS apps) in a
|
# the launchable example binaries as ProDOS type $B3 (S16 / GS-OS apps) in a
|
||||||
# volume named JOEYLIB, so GS/OS Finder lists and launches them.
|
# volume named JOEYLIB, so GS/OS Finder lists and launches them.
|
||||||
#
|
#
|
||||||
|
|
@ -10,12 +10,13 @@
|
||||||
# the image. Override the set with JOEY_DISK_EXAMPLES="DRAW SPRITE ...".
|
# the image. Override the set with JOEY_DISK_EXAMPLES="DRAW SPRITE ...".
|
||||||
#
|
#
|
||||||
# Usage: scripts/make-iigs-disk.sh [output.2mg]
|
# Usage: scripts/make-iigs-disk.sh [output.2mg]
|
||||||
# Requires: toolchains/env.sh sourced (LLVM816_ROOT), built IIgs examples.
|
# Requires: ./install.sh install toolchain:iigs; built IIgs examples.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
: "${LLVM816_ROOT:?make-iigs-disk.sh: source toolchains/env.sh first}"
|
source "$repo/scripts/lib/joeyEnv.sh"
|
||||||
CADIUS="${CADIUS:-$LLVM816_ROOT/tools/cadius/cadius}"
|
joeyNeed LLVM816_ROOT toolchain:iigs
|
||||||
|
joeyNeed CADIUS cadius
|
||||||
BINDIR="${BINDIR:-$repo/build/iigs/bin}"
|
BINDIR="${BINDIR:-$repo/build/iigs/bin}"
|
||||||
# The NTP replayer. DEFAULTED, not optional: without it on the volume every
|
# The NTP replayer. DEFAULTED, not optional: without it on the volume every
|
||||||
# IIgs example is silent except the ROM boot beep, because jlpAudioInit
|
# IIgs example is silent except the ROM boot beep, because jlpAudioInit
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@
|
||||||
# drive is 800KB GCR and REJECTS a 1440KB image, so anything
|
# drive is 800KB GCR and REJECTS a 1440KB image, so anything
|
||||||
# larger has to boot from a CFFA2 card:
|
# larger has to boot from a CFFA2 card:
|
||||||
# -sl7 cffa2 -hard1 <image>.
|
# -sl7 cffa2 -hard1 <image>.
|
||||||
# Requires: toolchains/env.sh sourced.
|
# Requires: ./install.sh install iigs (the toolchain and the GS/OS disk).
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
out=${1:?usage: make-iigs-game-disk.sh <out> <PROG> [volume] [data.2mg]}
|
out=${1:?usage: make-iigs-game-disk.sh <out> <PROG> [volume] [data.2mg]}
|
||||||
|
|
@ -48,10 +48,13 @@ prog=${2:?usage: make-iigs-game-disk.sh <out> <PROG> [volume] [data.2mg]}
|
||||||
vol=${3:-$prog}
|
vol=${3:-$prog}
|
||||||
|
|
||||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
: "${LLVM816_ROOT:?make-iigs-game-disk.sh: source toolchains/env.sh first}"
|
source "$repo/scripts/lib/joeyEnv.sh"
|
||||||
CADIUS="${CADIUS:-$LLVM816_ROOT/tools/cadius/cadius}"
|
joeyNeed LLVM816_ROOT toolchain:iigs
|
||||||
|
joeyNeed CADIUS cadius
|
||||||
|
joeyNeed IIGS_CLANG_BUILD toolchain:iigs
|
||||||
|
joeyNeed GSOS_SYSTEM_DISK iigsFirmware
|
||||||
data=${4:-$repo/build/iigs/bin/joey.2mg}
|
data=${4:-$repo/build/iigs/bin/joey.2mg}
|
||||||
sysDisk="$repo/toolchains/emulators/support/gsos-system.po"
|
sysDisk=$GSOS_SYSTEM_DISK
|
||||||
size=${JOEY_GAME_DISK_SIZE:-auto}
|
size=${JOEY_GAME_DISK_SIZE:-auto}
|
||||||
|
|
||||||
[ -x "$CADIUS" ] || { echo "make-iigs-game-disk.sh: cadius not found at $CADIUS" >&2; exit 2; }
|
[ -x "$CADIUS" ] || { echo "make-iigs-game-disk.sh: cadius not found at $CADIUS" >&2; exit 2; }
|
||||||
|
|
@ -165,7 +168,7 @@ dd if="$staged" of="$work/boot.blk" bs=1 skip=$srcOff count=1024 status=none
|
||||||
dd if="$work/boot.blk" of="$out" bs=1 seek=$dstOff count=1024 conv=notrunc status=none
|
dd if="$work/boot.blk" of="$out" bs=1 seek=$dstOff count=1024 conv=notrunc status=none
|
||||||
|
|
||||||
# ----- the launcher -----
|
# ----- the launcher -----
|
||||||
"$repo/toolchains/iigs/clang-build.sh" \
|
"$IIGS_CLANG_BUILD" \
|
||||||
-DJOEY_START_PATH="\"/$vol/$prog/$prog\"" \
|
-DJOEY_START_PATH="\"/$vol/$prog/$prog\"" \
|
||||||
-o "$work/Start#B30000" \
|
-o "$work/Start#B30000" \
|
||||||
"$repo/tools/iigsStart/iigsStart.c" \
|
"$repo/tools/iigsStart/iigsStart.c" \
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
# example .X base name in build/x68000/bin, default staxi
|
# example .X base name in build/x68000/bin, default staxi
|
||||||
# seconds emulated seconds to run, default 240
|
# seconds emulated seconds to run, default 240
|
||||||
# skipSeconds ignore samples before this, default 130 (boot + menu)
|
# skipSeconds ignore samples before this, default 130 (boot + menu)
|
||||||
# Env: X68K_DIR dir holding HUMAN302.XDF + roms/ (default toolchains/x68mame)
|
# Env: X68K_DIR dir holding HUMAN302.XDF + roms/ (default: the installer's toolchains/firmware/x68000)
|
||||||
# Requires: the example built WITH symbols (build/x68000/bin/<NAME>.X.elf).
|
# Requires: the example built WITH symbols (build/x68000/bin/<NAME>.X.elf).
|
||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
|
|
||||||
|
|
@ -33,15 +33,17 @@ skip=${3:-130}
|
||||||
PROG=$(echo "$prog" | tr '[:lower:]' '[:upper:]')
|
PROG=$(echo "$prog" | tr '[:lower:]' '[:upper:]')
|
||||||
|
|
||||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
source "$repo/scripts/lib/joeyEnv.sh"
|
||||||
|
joeyNeed MAME mame
|
||||||
|
joeyNeed X68K_DIR x68kFirmware
|
||||||
|
joeyNeedFile "$X68K_DIR/roms/x68000/cgrom.dat" x68kCgrom
|
||||||
bin=$repo/build/x68000/bin/$PROG.X
|
bin=$repo/build/x68000/bin/$PROG.X
|
||||||
elf=$bin.elf
|
elf=$bin.elf
|
||||||
x68=${X68K_DIR:-$repo/toolchains/x68mame}
|
x68=$X68K_DIR
|
||||||
template=$x68/HUMAN302.XDF
|
template=$x68/HUMAN302.XDF
|
||||||
export PATH="$repo/toolchains/x68000/m68k-xelf/bin:$PATH"
|
joeyNeed X68K_ROOT toolchain:x68000
|
||||||
|
export PATH="$X68K_ROOT/bin:$PATH"
|
||||||
|
|
||||||
MAME="${MAME:-$repo/toolchains/cache/mame-mame0264/x68k}"
|
|
||||||
[[ -x $MAME ]] || MAME="$(command -v mame || true)"
|
|
||||||
[[ -n ${MAME:-} && -x $MAME ]] || { echo "profile-x68000: no MAME (set MAME=...)" >&2; exit 2; }
|
|
||||||
[[ -f $bin ]] || { echo "profile-x68000: $bin not built" >&2; exit 2; }
|
[[ -f $bin ]] || { echo "profile-x68000: $bin not built" >&2; exit 2; }
|
||||||
[[ -f $elf ]] || { echo "profile-x68000: $elf missing (need symbols)" >&2; exit 2; }
|
[[ -f $elf ]] || { echo "profile-x68000: $elf missing (need symbols)" >&2; exit 2; }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,277 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# Launch the AGI interpreter in the named platform's emulator with a
|
|
||||||
# staged AGI v2 game directory. The game's data files are copied flat
|
|
||||||
# alongside the binary so AGI's CWD-relative default ("./LOGDIR" etc.)
|
|
||||||
# works on platforms whose autostart can't pass argv.
|
|
||||||
#
|
|
||||||
# scripts/run-agi.sh <platform> [game] [room]
|
|
||||||
# platform : dos | amiga | atarist | iigs
|
|
||||||
# game : subdirectory under examples/agi/gamedata/ (default kq3)
|
|
||||||
# room : starting room number 1..255. Skips logic.0's hardcoded
|
|
||||||
# title-room target and jumps straight to the named room.
|
|
||||||
# Only forwarded to DOS today -- the Atari ST / Amiga
|
|
||||||
# autostart paths can't pass argv to the binary; their
|
|
||||||
# AGI starts at the title screen until we wire a stage-
|
|
||||||
# time config-file fallback or implement enough title
|
|
||||||
# opcodes for the title to advance on its own.
|
|
||||||
#
|
|
||||||
# Required files in the game directory (others are ignored):
|
|
||||||
# LOGDIR PICDIR VIEWDIR SNDDIR VOL.0 [VOL.1 ...] [OBJECT] [WORDS.TOK]
|
|
||||||
#
|
|
||||||
# IIgs delegates to run-iigs.sh's AGI mode, which packs AGI + the game
|
|
||||||
# onto a 2MB CFFA2 hard-disk volume (an 800KB floppy can't hold both) and
|
|
||||||
# boots GS/OS from a second CFFA2 partition. See make-agi-iigs-disk.sh.
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
if [[ $# -lt 1 || $# -gt 3 ]]; then
|
|
||||||
echo "usage: $0 <dos|amiga|atarist|iigs> [game-name] [starting-room]" >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
platform=$1
|
|
||||||
game=${2:-kq3}
|
|
||||||
room=${3:-0}
|
|
||||||
|
|
||||||
if ! [[ $room =~ ^[0-9]+$ ]] || (( room > 255 )); then
|
|
||||||
echo "starting-room must be 0..255 (got '$room')" >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
|
||||||
gamedata=$repo/examples/agi/gamedata/$game
|
|
||||||
support=$repo/toolchains/emulators/support
|
|
||||||
|
|
||||||
if [[ ! -d $gamedata ]]; then
|
|
||||||
echo "$gamedata: not an AGI v2 game directory" >&2
|
|
||||||
if [[ -d $repo/examples/agi/gamedata ]]; then
|
|
||||||
echo "available games:" >&2
|
|
||||||
find "$repo/examples/agi/gamedata" -maxdepth 1 -mindepth 1 -type d -printf ' %f\n' >&2
|
|
||||||
fi
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if [[ ! -f $gamedata/LOGDIR ]]; then
|
|
||||||
echo "$gamedata: no LOGDIR -- not an AGI v2 game directory" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
# stageGameData WORKDIR
|
|
||||||
# Copies the subset of files AGI v2 reads (DIR files, VOL.x, OBJECT,
|
|
||||||
# WORDS.TOK) into WORKDIR. Leaves Sierra's original AGI binary, OVL
|
|
||||||
# graphics drivers, and platform-specific launchers behind so a flat
|
|
||||||
# stage doesn't clash with our binary's name on case-insensitive
|
|
||||||
# filesystems (e.g. Amiga FFS: "AGI" vs our "Agi").
|
|
||||||
stageGameData() {
|
|
||||||
local destDir=$1
|
|
||||||
local dataDir=$destDir/DATA
|
|
||||||
local f
|
|
||||||
|
|
||||||
# JoeyLib forces every data-file open under DATA/ (jlDataOpen), so the
|
|
||||||
# AGI resources are staged into a DATA subdirectory of the mount root.
|
|
||||||
mkdir -p "$dataDir"
|
|
||||||
for f in LOGDIR PICDIR VIEWDIR SNDDIR OBJECT WORDS.TOK; do
|
|
||||||
if [[ -f $gamedata/$f ]]; then
|
|
||||||
cp "$gamedata/$f" "$dataDir/$f"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
for f in "$gamedata"/VOL.*; do
|
|
||||||
[[ -f $f ]] && cp "$f" "$dataDir/$(basename "$f")"
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
runDos() {
|
|
||||||
local bin=$repo/build/dos/bin/AGI.EXE
|
|
||||||
local conf=$repo/scripts/dosbox-386sx16.conf
|
|
||||||
local work
|
|
||||||
|
|
||||||
if [[ ! -f $bin ]]; then
|
|
||||||
echo "$bin not built. Run 'make -f make/dos.mk $bin' first." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Sweep any leftover staging dirs from previous runs that
|
|
||||||
# didn't reach their trap (DOSBox window close, SIGKILL, etc.).
|
|
||||||
# We're about to make a new one anyway; freshly leftover dirs
|
|
||||||
# serve no purpose and just leak ~1 MB each.
|
|
||||||
rm -rf /tmp/joeylib-agi-dos.* 2>/dev/null || true
|
|
||||||
rm -f /tmp/joeylib-agi-dos-last.log 2>/dev/null || true
|
|
||||||
|
|
||||||
work=$(mktemp -d -t joeylib-agi-dos.XXXXXX)
|
|
||||||
# Cleanup trap: copy DJGPP's diagnostic log out (DOS uppercases
|
|
||||||
# so the file lands as JOEYLOG.TXT, not joeylog.txt), then nuke
|
|
||||||
# the staging dir. Hook EXIT plus the common interrupt signals
|
|
||||||
# so the cleanup fires even when the user closes the DOSBox
|
|
||||||
# window or Ctrl-Cs the terminal -- both bypass plain EXIT in
|
|
||||||
# some bash configurations.
|
|
||||||
cleanupDos() {
|
|
||||||
local log
|
|
||||||
for log in "$work/JOEYLOG.TXT" "$work/joeylog.txt"; do
|
|
||||||
if [[ -f $log ]]; then
|
|
||||||
cp "$log" /tmp/joeylib-agi-dos-last.log
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
rm -rf "$work"
|
|
||||||
}
|
|
||||||
trap cleanupDos EXIT INT TERM HUP
|
|
||||||
|
|
||||||
cp "$bin" "$work/AGI.EXE"
|
|
||||||
stageGameData "$work"
|
|
||||||
|
|
||||||
# AGI.EXE reads game data from DATA/ under its CWD (we mount $work as C:
|
|
||||||
# and cd there first), so no game-directory argument is needed; the
|
|
||||||
# starting-room override is argv[1]. Skip it when room=0 so the binary
|
|
||||||
# exercises its default code path.
|
|
||||||
local agi_cmd="AGI.EXE"
|
|
||||||
if (( room != 0 )); then
|
|
||||||
agi_cmd="AGI.EXE $room"
|
|
||||||
fi
|
|
||||||
|
|
||||||
cat <<EOF
|
|
||||||
DOSBox: mounting $work as C:, running '$agi_cmd' for $game.
|
|
||||||
ESC quits the demo, then 'pause' waits for a keypress in the shell.
|
|
||||||
EOF
|
|
||||||
|
|
||||||
local dosboxArgs=(
|
|
||||||
-conf "$conf"
|
|
||||||
-set "mouse_capture=seamless"
|
|
||||||
)
|
|
||||||
# CYCLES=max (or any other DOSBox cycles value) overrides the conf's
|
|
||||||
# period-correct 386SX-16 cap. Use this to A/B test whether perf
|
|
||||||
# ceilings are coming from the simulated CPU or the code itself.
|
|
||||||
if [[ -n "${CYCLES:-}" ]]; then
|
|
||||||
dosboxArgs+=(-set "cpu cycles=$CYCLES")
|
|
||||||
fi
|
|
||||||
dosboxArgs+=(
|
|
||||||
-c "C:"
|
|
||||||
-c "$agi_cmd"
|
|
||||||
-c "pause"
|
|
||||||
--exit "$work"
|
|
||||||
)
|
|
||||||
# Avoid `exec`: we need bash to stay alive so the EXIT trap above
|
|
||||||
# actually runs and removes the staged $work dir. With `exec`,
|
|
||||||
# dosbox replaces the shell and the trap never fires, leaking
|
|
||||||
# ~1 MB of game data into /tmp on every launch.
|
|
||||||
dosbox "${dosboxArgs[@]}"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
runAtariST() {
|
|
||||||
local bin=$repo/build/atarist/bin/AGI.PRG
|
|
||||||
local tos=$support/emutos-512k.img
|
|
||||||
local work
|
|
||||||
|
|
||||||
if [[ ! -f $bin ]]; then
|
|
||||||
echo "$bin not built. Run 'make -f make/atarist.mk $bin' first." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if [[ ! -f $tos ]]; then
|
|
||||||
echo "TOS ROM missing: $tos" >&2
|
|
||||||
echo "Run ./toolchains/install.sh (EmuTOS should have been staged)." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
rm -rf /tmp/joeylib-agi-atarist.* 2>/dev/null || true
|
|
||||||
work=$(mktemp -d -t joeylib-agi-atarist.XXXXXX)
|
|
||||||
trap 'rm -rf "$work"' EXIT INT TERM HUP
|
|
||||||
|
|
||||||
cp "$bin" "$work/AGI.PRG"
|
|
||||||
stageGameData "$work"
|
|
||||||
|
|
||||||
cat <<EOF
|
|
||||||
Hatari: mounting $work as GEMDOS C:, autostarting AGI.PRG for $game.
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# Run hatari as a child so the EXIT trap removes the staged
|
|
||||||
# game data dir; `exec` would have hatari replace bash and
|
|
||||||
# leak $work into /tmp on every launch.
|
|
||||||
hatari \
|
|
||||||
--tos "$tos" \
|
|
||||||
--harddrive "$work" \
|
|
||||||
--gemdos-drive C \
|
|
||||||
--auto "C:\\AGI.PRG"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
runAmiga() {
|
|
||||||
local bin=$repo/build/amiga/bin/Agi
|
|
||||||
local kickstart=$support/kickstart.rom
|
|
||||||
local workbench=$support/workbench.adf
|
|
||||||
local work
|
|
||||||
|
|
||||||
if [[ ! -f $bin ]]; then
|
|
||||||
echo "$bin not built. Run 'make -f make/amiga.mk $bin' first." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
rm -rf /tmp/joeylib-agi-amiga.* 2>/dev/null || true
|
|
||||||
work=$(mktemp -d -t joeylib-agi-amiga.XXXXXX)
|
|
||||||
trap 'rm -rf "$work"' EXIT INT TERM HUP
|
|
||||||
|
|
||||||
mkdir -p "$work/s"
|
|
||||||
cp "$bin" "$work/Agi"
|
|
||||||
stageGameData "$work"
|
|
||||||
# ':' prefix anchors to volume root so AmigaDOS doesn't search C:.
|
|
||||||
echo ":Agi" > "$work/s/startup-sequence"
|
|
||||||
|
|
||||||
local fsargs=(
|
|
||||||
--amiga_model=A500
|
|
||||||
--fast_memory=2048
|
|
||||||
--hard_drive_0="$work"
|
|
||||||
--hard_drive_0_label=JOEYLIB
|
|
||||||
--automatic_input_grab=0
|
|
||||||
--middle_click_ungrab=1
|
|
||||||
--mouse_integration=1
|
|
||||||
--floppy_drive_speed=800
|
|
||||||
)
|
|
||||||
if [[ -f $kickstart ]]; then
|
|
||||||
fsargs+=(--kickstart_file="$kickstart")
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -f $workbench ]]; then
|
|
||||||
# If a Workbench floppy is staged it boots from DF0: instead of
|
|
||||||
# the HD, so the startup-sequence never fires. Skip Workbench
|
|
||||||
# for AGI to keep the launch one-shot; user can still launch
|
|
||||||
# manually with run-amiga.sh if they want the GUI.
|
|
||||||
echo "(skipping workbench.adf so the JOEYLIB HD boots Agi directly)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
cat <<EOF
|
|
||||||
FS-UAE booting JOEYLIB hard drive ($work) and auto-running Agi for $game.
|
|
||||||
EOF
|
|
||||||
|
|
||||||
fs-uae "${fsargs[@]}"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
runIigs() {
|
|
||||||
# IIgs uses a CFFA2 hard disk (the 800KB floppy can't hold AGI + KQ3),
|
|
||||||
# driven entirely by run-iigs.sh's AGI mode. Delegate to it: it builds
|
|
||||||
# the two-partition CFFA image via make-agi-iigs-disk.sh, boots GS/OS,
|
|
||||||
# and launches AGI. The starting-room override is DOS-only (the IIgs
|
|
||||||
# autostart path can't pass argv), so $room is not forwarded here.
|
|
||||||
if (( room != 0 )); then
|
|
||||||
echo "note: starting-room override is DOS-only; ignored on IIgs" >&2
|
|
||||||
fi
|
|
||||||
# The IIgs build target is the binary path, not EXAMPLE=agi.
|
|
||||||
if [[ ! -f "$repo/build/iigs/bin/AGI" ]]; then
|
|
||||||
echo "$repo/build/iigs/bin/AGI not built." >&2
|
|
||||||
echo "Build it with: make -f make/iigs.mk $repo/build/iigs/bin/AGI" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
JOEY_AGI_GAME="$game" exec "$repo/scripts/run-iigs.sh" agi
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
case "$platform" in
|
|
||||||
dos) runDos ;;
|
|
||||||
atarist) runAtariST ;;
|
|
||||||
amiga) runAmiga ;;
|
|
||||||
iigs) runIigs ;;
|
|
||||||
*)
|
|
||||||
echo "$platform: unknown platform (expected dos, amiga, atarist, or iigs)" >&2
|
|
||||||
exit 2
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
@ -1,107 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# run-amiga-headless.sh - Run one built Amiga example under FS-UAE with no
|
|
||||||
# window, wait, then hand back the example's joeylog.txt and a screenshot.
|
|
||||||
#
|
|
||||||
# This is the smoke test bench-amiga.sh cannot be: that script runs UBER
|
|
||||||
# and waits for its "press any key to exit" line, but a game never exits.
|
|
||||||
# Here the run is simply given N seconds and whatever it logged and drew
|
|
||||||
# is reported.
|
|
||||||
#
|
|
||||||
# The Amiga's joeylog.txt lands in the directory mounted as the hard
|
|
||||||
# drive, so it can be read straight off the host while the machine runs.
|
|
||||||
#
|
|
||||||
# Usage: scripts/run-amiga-headless.sh [example] [seconds] [fastKB]
|
|
||||||
# example binary name in build/amiga/bin, default Taxi
|
|
||||||
# seconds how long to let it run, default 90
|
|
||||||
# fastKB fast memory in KB, default 2048. Pass 0 for a
|
|
||||||
# genuinely bare 512 KB-chip A500 (slow memory off
|
|
||||||
# as well), which is the tight case.
|
|
||||||
# Requires: fs-uae, Xvfb, toolchains/emulators/support/kickstart.rom.
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
prog=${1:-Taxi}
|
|
||||||
seconds=${2:-90}
|
|
||||||
fastKb=${3:-2048}
|
|
||||||
|
|
||||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
|
||||||
binDir=$repo/build/amiga/bin
|
|
||||||
support=$repo/toolchains/emulators/support
|
|
||||||
kickstart=$support/kickstart.rom
|
|
||||||
outDir=${AMIGA_OUT_DIR:-$repo/build/amiga/headless}
|
|
||||||
|
|
||||||
# Most examples are a bare binary in bin/; Space Taxi ships as its own
|
|
||||||
# drawer holding the binary and its DATA tree, so accept either shape.
|
|
||||||
if [ -d "$binDir/$prog" ]; then
|
|
||||||
progDir=$binDir/$prog
|
|
||||||
[ -f "$progDir/$prog" ] || { echo "run-amiga-headless: $progDir/$prog not built" >&2; exit 2; }
|
|
||||||
elif [ -f "$binDir/$prog" ]; then
|
|
||||||
progDir=""
|
|
||||||
else
|
|
||||||
echo "run-amiga-headless: $binDir/$prog not built" >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
[ -f "$kickstart" ] || { echo "run-amiga-headless: missing $kickstart" >&2; exit 2; }
|
|
||||||
|
|
||||||
work=$(mktemp -d -t joeylib-amiga-run.XXXXXX)
|
|
||||||
mkdir -p "$work/s" "$outDir"
|
|
||||||
if [ -n "$progDir" ]; then
|
|
||||||
cp -r "$progDir/." "$work/"
|
|
||||||
else
|
|
||||||
find "$binDir" -maxdepth 1 -type f -executable -exec cp -t "$work/" {} +
|
|
||||||
[ -d "$binDir/DATA" ] && cp -r "$binDir/DATA" "$work/"
|
|
||||||
fi
|
|
||||||
printf 'stack 32768\n:%s\n' "$prog" > "$work/s/startup-sequence"
|
|
||||||
|
|
||||||
display=:$((90 + RANDOM % 9))
|
|
||||||
Xvfb "$display" -screen 0 800x600x24 -nolisten tcp >/dev/null 2>&1 &
|
|
||||||
xvfbPid=$!
|
|
||||||
sleep 1
|
|
||||||
|
|
||||||
fsArgs=(
|
|
||||||
--amiga_model=A500
|
|
||||||
--hard_drive_0="$work"
|
|
||||||
--hard_drive_0_label=JOEYLIB
|
|
||||||
--kickstart_file="$kickstart"
|
|
||||||
--automatic_input_grab=0
|
|
||||||
--fullscreen=0
|
|
||||||
--fade_out_duration=0
|
|
||||||
)
|
|
||||||
# Always pass the memory explicitly. Leaving --fast_memory off does NOT
|
|
||||||
# give a bare machine: FS-UAE's A500 default still fits the 512 KB
|
|
||||||
# trapdoor expansion, which AvailMem reports as ~300 KB of FAST. A real
|
|
||||||
# unexpanded A500 is 512 KB of CHIP and nothing else, so fastKB=0 has to
|
|
||||||
# turn the slow memory off too.
|
|
||||||
fsArgs+=(--fast_memory="$fastKb")
|
|
||||||
[ "$fastKb" = "0" ] && fsArgs+=(--slow_memory=0)
|
|
||||||
|
|
||||||
DISPLAY=$display fs-uae "${fsArgs[@]}" > "$work/fsuae.log" 2>&1 &
|
|
||||||
fsPid=$!
|
|
||||||
cleanup() {
|
|
||||||
kill "$fsPid" 2>/dev/null || true
|
|
||||||
kill "$xvfbPid" 2>/dev/null || true
|
|
||||||
wait 2>/dev/null || true
|
|
||||||
}
|
|
||||||
trap cleanup EXIT
|
|
||||||
|
|
||||||
echo "run-amiga-headless: $prog for ${seconds}s on an A500 with ${fastKb} KB fast..." >&2
|
|
||||||
elapsed=0
|
|
||||||
while [ "$elapsed" -lt "$seconds" ]; do
|
|
||||||
sleep 5
|
|
||||||
elapsed=$((elapsed + 5))
|
|
||||||
kill -0 "$fsPid" 2>/dev/null || { echo "run-amiga-headless: FS-UAE exited early" >&2; break; }
|
|
||||||
done
|
|
||||||
|
|
||||||
DISPLAY=$display import -window root "$outDir/$prog.png" 2>/dev/null || true
|
|
||||||
cp "$work/joeylog.txt" "$outDir/$prog-joeylog.txt" 2>/dev/null || true
|
|
||||||
cp "$work/fsuae.log" "$outDir/$prog-fsuae.log" 2>/dev/null || true
|
|
||||||
cleanup
|
|
||||||
trap - EXIT
|
|
||||||
rm -rf "$work"
|
|
||||||
|
|
||||||
echo "run-amiga-headless: wrote $outDir/$prog.png"
|
|
||||||
if [ -f "$outDir/$prog-joeylog.txt" ]; then
|
|
||||||
echo "--- joeylog.txt (last 25 lines)"
|
|
||||||
tail -25 "$outDir/$prog-joeylog.txt"
|
|
||||||
else
|
|
||||||
echo "run-amiga-headless: NO joeylog.txt -- the example never logged anything" >&2
|
|
||||||
fi
|
|
||||||
|
|
@ -1,159 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# Launch the built Amiga example in FS-UAE. Defaults to PATTERN.
|
|
||||||
#
|
|
||||||
# Kickstart and Workbench:
|
|
||||||
# - If toolchains/emulators/support/kickstart.rom is present, it is
|
|
||||||
# used as the Kickstart ROM. Otherwise FS-UAE's built-in AROS ROM
|
|
||||||
# is used (less compatible with OCS Intuition but requires no
|
|
||||||
# licensed files).
|
|
||||||
# - If toolchains/emulators/support/workbench.adf is present, it is
|
|
||||||
# inserted into DF0: and the Amiga boots Workbench. The user
|
|
||||||
# launches the example from the JOEYLIB drawer on the desktop.
|
|
||||||
# - If Workbench is not present, AmigaDOS boots directly from DH0:,
|
|
||||||
# executes s/startup-sequence, and auto-runs the requested example.
|
|
||||||
#
|
|
||||||
# Always on A500 hardware (OCS chipset). Our HAL only uses OCS
|
|
||||||
# features so this matches real-hardware behavior.
|
|
||||||
#
|
|
||||||
# scripts/run-amiga.sh # runs Pattern
|
|
||||||
# scripts/run-amiga.sh hello # runs Hello
|
|
||||||
# scripts/run-amiga.sh draw # runs Draw
|
|
||||||
# scripts/run-amiga.sh staxi # runs Taxi (Space Taxi)
|
|
||||||
#
|
|
||||||
# Argument is any built example name (case-insensitive); the script
|
|
||||||
# normalizes it to PascalCase (first letter upper, rest lower) which
|
|
||||||
# is the JoeyLib convention for Amiga binary filenames. Special
|
|
||||||
# aliases: "staxi" / "spacetaxi" -> "Taxi" (the Amiga binary name).
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
if [[ $# -gt 1 ]]; then
|
|
||||||
echo "usage: $0 [example-name]" >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
prog=${1:-pattern}
|
|
||||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
|
||||||
bin_dir=$repo/build/amiga/bin
|
|
||||||
support=$repo/toolchains/emulators/support
|
|
||||||
|
|
||||||
prog_lower=${prog,,}
|
|
||||||
# Project-name aliases (input -> Amiga binary name). On Amiga the
|
|
||||||
# Space Taxi binary is "Taxi", not "Staxi" or "Spacetaxi".
|
|
||||||
case "$prog_lower" in
|
|
||||||
staxi|spacetaxi) prog_lower=taxi ;;
|
|
||||||
esac
|
|
||||||
file=${prog_lower^}
|
|
||||||
|
|
||||||
# An example may live either directly in bin_dir (Pattern, Hello, etc.)
|
|
||||||
# or inside its own subdir alongside a per-example DATA/ tree (Taxi).
|
|
||||||
# Prefer the subdir layout if present.
|
|
||||||
if [[ -f "$bin_dir/$file/$file" ]]; then
|
|
||||||
bin_path=$bin_dir/$file/$file
|
|
||||||
bin_extra_dir=$bin_dir/$file
|
|
||||||
elif [[ -f "$bin_dir/$file" ]]; then
|
|
||||||
bin_path=$bin_dir/$file
|
|
||||||
bin_extra_dir=
|
|
||||||
else
|
|
||||||
echo "$file not built. Run 'make amiga' first." >&2
|
|
||||||
echo " looked for:" >&2
|
|
||||||
echo " $bin_dir/$file/$file" >&2
|
|
||||||
echo " $bin_dir/$file" >&2
|
|
||||||
echo "available examples in $bin_dir:" >&2
|
|
||||||
find "$bin_dir" -maxdepth 1 -type f -executable -printf '%f\n' >&2 2>/dev/null || true
|
|
||||||
find "$bin_dir" -maxdepth 2 -type f -executable -printf '%P\n' >&2 2>/dev/null \
|
|
||||||
| grep '/' >&2 || true
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
kickstart=$support/kickstart.rom
|
|
||||||
workbench=$support/workbench.adf
|
|
||||||
|
|
||||||
# Stage a fresh hard-drive directory containing both example binaries
|
|
||||||
# plus an s/startup-sequence that auto-invokes the chosen one. The
|
|
||||||
# startup-sequence only runs when booting directly from the hard
|
|
||||||
# drive (i.e. no Workbench floppy); Workbench takes over boot if
|
|
||||||
# present and the user launches manually.
|
|
||||||
work=$(mktemp -d -t joeylib-amiga.XXXXXX)
|
|
||||||
# Preserve any diagnostic dumps Pattern writes to the virtual HD
|
|
||||||
# (copper.txt, etc.) before the temp dir is removed on script exit.
|
|
||||||
# Also stage UBER's joeylog.txt back to build/amiga/bin/ so the
|
|
||||||
# tools/uber-perf-table picks it up at the standard path.
|
|
||||||
dump_keep=/tmp/joeylib-amiga-dump
|
|
||||||
trap 'mkdir -p "$dump_keep"; cp "$work"/*.txt "$dump_keep"/ 2>/dev/null; cp "$work"/joeylog.txt "$bin_dir/joeylog.txt" 2>/dev/null; rm -rf "$work"' EXIT
|
|
||||||
|
|
||||||
mkdir -p "$work/s"
|
|
||||||
# Stage every built top-level binary (executable file at top of bin_dir,
|
|
||||||
# no extension on Amiga). DATA/ is copied separately below.
|
|
||||||
find "$bin_dir" -maxdepth 1 -type f -executable -exec cp -t "$work/" {} +
|
|
||||||
# Stage the top-level DATA folder (test.mod, test.sfx) the audio demo
|
|
||||||
# loads from the boot volume at runtime.
|
|
||||||
if [[ -d "$bin_dir/DATA" ]]; then
|
|
||||||
cp -r "$bin_dir/DATA" "$work/"
|
|
||||||
fi
|
|
||||||
# If the chosen example lives in its own subdir (Taxi/), stage the
|
|
||||||
# binary AND its sibling DATA tree -- the subdir's DATA replaces the
|
|
||||||
# top-level one if there's a conflict. We rsync-merge so existing
|
|
||||||
# entries (e.g. test.mod) survive alongside Taxi's font.jas, levels/,
|
|
||||||
# sprites/, tiles/.
|
|
||||||
if [[ -n "$bin_extra_dir" ]]; then
|
|
||||||
cp "$bin_path" "$work/$file"
|
|
||||||
if [[ -d "$bin_extra_dir/DATA" ]]; then
|
|
||||||
mkdir -p "$work/DATA"
|
|
||||||
cp -r "$bin_extra_dir/DATA/." "$work/DATA/"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
# ':' prefix anchors to the root of the current volume; otherwise
|
|
||||||
# AmigaDOS looks in C: and the command is not found.
|
|
||||||
echo ":$file" > "$work/s/startup-sequence"
|
|
||||||
|
|
||||||
fsargs=(
|
|
||||||
--amiga_model=A500
|
|
||||||
--fast_memory=2048
|
|
||||||
--hard_drive_0="$work"
|
|
||||||
--hard_drive_0_label=JOEYLIB
|
|
||||||
# Mouse-handling overrides for VM hosts where FS-UAE's default
|
|
||||||
# relative-mode grab fights with the hypervisor's own pointer
|
|
||||||
# integration. automatic_input_grab=0 stops FS-UAE from grabbing
|
|
||||||
# the pointer on focus; middle_click_ungrab=1 is a fallback so a
|
|
||||||
# middle-click releases any grab that does occur; mouse_integration=1
|
|
||||||
# asks FS-UAE to track host pointer position directly instead of
|
|
||||||
# sampling relative deltas.
|
|
||||||
--automatic_input_grab=0
|
|
||||||
--middle_click_ungrab=1
|
|
||||||
--mouse_integration=1
|
|
||||||
# Speed overrides: floppy_drive_speed=800 runs the floppy at 8x so
|
|
||||||
# the Workbench boot finishes in a few seconds instead of a minute.
|
|
||||||
# accuracy=0 and fast_copper=1 are deliberately NOT set -- they
|
|
||||||
# compromise copper timing, which our per-scanline palette swaps
|
|
||||||
# depend on.
|
|
||||||
--floppy_drive_speed=800
|
|
||||||
)
|
|
||||||
|
|
||||||
if [[ -f $kickstart ]]; then
|
|
||||||
fsargs+=(--kickstart_file="$kickstart")
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -f $workbench ]]; then
|
|
||||||
fsargs+=(--floppy_drive_0="$workbench")
|
|
||||||
cat <<EOF
|
|
||||||
FS-UAE booting Workbench from DF0:. Once the desktop appears:
|
|
||||||
1. Double-click the JOEYLIB disk icon.
|
|
||||||
2. From the Window menu, pick "Show > All Files" (Workbench hides
|
|
||||||
files that have no .info icon by default).
|
|
||||||
3. Double-click $file to run it.
|
|
||||||
Alternatively open a Shell (Workbench > Tools > Shell), type 'cd
|
|
||||||
JOEYLIB:' then '$file'.
|
|
||||||
EOF
|
|
||||||
else
|
|
||||||
cat <<EOF
|
|
||||||
FS-UAE booting directly from the JOEYLIB hard drive. $file will
|
|
||||||
auto-run via s/startup-sequence. Drop a Workbench 3.1 ADF at
|
|
||||||
$workbench to boot the GUI instead.
|
|
||||||
EOF
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Intentionally no exec: we need the bash EXIT trap to fire so the
|
|
||||||
# scratch dir gets cleaned up (and any diagnostic dumps preserved).
|
|
||||||
# exec would replace the shell and skip trap execution.
|
|
||||||
fs-uae "${fsargs[@]}"
|
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# run-atarist-headless.sh - Run one built Atari ST example under Hatari
|
|
||||||
# with no window, wait, then hand back its joeylog.txt and a screenshot.
|
|
||||||
#
|
|
||||||
# The ST twin of run-amiga-headless.sh, and for the same reason:
|
|
||||||
# bench-atarist.sh runs UBER and waits for its "press any key to exit"
|
|
||||||
# line, which a game never prints.
|
|
||||||
#
|
|
||||||
# Hatari mounts the program's directory as GEMDOS drive C, so the app's
|
|
||||||
# joeylog.txt lands there and can be read off the host while it runs.
|
|
||||||
#
|
|
||||||
# Usage: scripts/run-atarist-headless.sh [example] [seconds]
|
|
||||||
# example PRG base name in build/atarist/bin, default STAXI
|
|
||||||
# seconds how long to let it run, default 90
|
|
||||||
# Env: ST_SOUND=on keep the YM running (default off, it is faster)
|
|
||||||
# ST_MEMSIZE=n machine RAM in MB, passed to Hatari. Default is
|
|
||||||
# Hatari's own; 1 is a stock 1040 ST.
|
|
||||||
# Requires: hatari, Xvfb, toolchains/emulators/support/emutos-512k.img
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
prog=${1:-STAXI}
|
|
||||||
seconds=${2:-90}
|
|
||||||
|
|
||||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
|
||||||
binDir=$repo/build/atarist/bin
|
|
||||||
tos=$repo/toolchains/emulators/support/emutos-512k.img
|
|
||||||
outDir=${ST_OUT_DIR:-$repo/build/atarist/headless}
|
|
||||||
|
|
||||||
# Most examples are a bare PRG in bin/; Space Taxi ships as its own
|
|
||||||
# folder holding the PRG and its DATA tree.
|
|
||||||
if [ -d "$binDir/$prog" ]; then
|
|
||||||
driveDir=$binDir/$prog
|
|
||||||
else
|
|
||||||
driveDir=$binDir
|
|
||||||
fi
|
|
||||||
[ -f "$driveDir/$prog.PRG" ] || { echo "run-atarist-headless: $driveDir/$prog.PRG not built" >&2; exit 2; }
|
|
||||||
[ -f "$tos" ] || { echo "run-atarist-headless: missing $tos" >&2; exit 2; }
|
|
||||||
|
|
||||||
mkdir -p "$outDir"
|
|
||||||
rm -f "$driveDir/joeylog.txt"
|
|
||||||
|
|
||||||
display=:$((90 + RANDOM % 9))
|
|
||||||
Xvfb "$display" -screen 0 800x600x24 -nolisten tcp >/dev/null 2>&1 &
|
|
||||||
xvfbPid=$!
|
|
||||||
sleep 1
|
|
||||||
|
|
||||||
DISPLAY=$display hatari \
|
|
||||||
--tos "$tos" \
|
|
||||||
--harddrive "$driveDir" \
|
|
||||||
--gemdos-drive C \
|
|
||||||
--drive-a off --drive-b off \
|
|
||||||
--sound "${ST_SOUND:-off}" \
|
|
||||||
${ST_MEMSIZE:+--memsize "$ST_MEMSIZE"} \
|
|
||||||
--fast-forward on \
|
|
||||||
--auto "C:\\$prog.PRG" > "$outDir/$prog-hatari.log" 2>&1 &
|
|
||||||
hatPid=$!
|
|
||||||
cleanup() {
|
|
||||||
kill "$hatPid" 2>/dev/null || true
|
|
||||||
kill "$xvfbPid" 2>/dev/null || true
|
|
||||||
wait 2>/dev/null || true
|
|
||||||
}
|
|
||||||
trap cleanup EXIT
|
|
||||||
|
|
||||||
echo "run-atarist-headless: $prog for ${seconds}s..." >&2
|
|
||||||
elapsed=0
|
|
||||||
while [ "$elapsed" -lt "$seconds" ]; do
|
|
||||||
sleep 5
|
|
||||||
elapsed=$((elapsed + 5))
|
|
||||||
kill -0 "$hatPid" 2>/dev/null || { echo "run-atarist-headless: Hatari exited early" >&2; break; }
|
|
||||||
done
|
|
||||||
|
|
||||||
DISPLAY=$display import -window root "$outDir/$prog.png" 2>/dev/null || true
|
|
||||||
cp "$driveDir/joeylog.txt" "$outDir/$prog-joeylog.txt" 2>/dev/null || true
|
|
||||||
cleanup
|
|
||||||
trap - EXIT
|
|
||||||
|
|
||||||
echo "run-atarist-headless: wrote $outDir/$prog.png"
|
|
||||||
if [ -f "$outDir/$prog-joeylog.txt" ]; then
|
|
||||||
echo "--- joeylog.txt (last 25 lines)"
|
|
||||||
tail -25 "$outDir/$prog-joeylog.txt"
|
|
||||||
else
|
|
||||||
echo "run-atarist-headless: NO joeylog.txt -- the example never logged anything" >&2
|
|
||||||
fi
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# Launch the built Atari ST example in Hatari. Defaults to PATTERN.
|
|
||||||
# Hatari autostarts the .PRG via the --auto flag; EmuTOS
|
|
||||||
# (toolchains/emulators/support/emutos-512k.img) provides the TOS ROM
|
|
||||||
# since the Ubuntu hatari package does not bundle one.
|
|
||||||
#
|
|
||||||
# scripts/run-atarist.sh # runs PATTERN.PRG
|
|
||||||
# scripts/run-atarist.sh hello # runs HELLO.PRG
|
|
||||||
# scripts/run-atarist.sh draw # runs DRAW.PRG
|
|
||||||
# scripts/run-atarist.sh staxi # runs STAXI/STAXI.PRG (Space Taxi)
|
|
||||||
#
|
|
||||||
# Argument is any built example name (case-insensitive); the script
|
|
||||||
# upper-cases it and appends .PRG, then checks the file exists.
|
|
||||||
# Aliases: "spacetaxi" -> "STAXI" (the canonical install dir name).
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
if [[ $# -gt 1 ]]; then
|
|
||||||
echo "usage: $0 [example-name]" >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
prog=${1:-pattern}
|
|
||||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
|
||||||
bin_dir=$repo/build/atarist/bin
|
|
||||||
|
|
||||||
# Name aliases (input -> install/binary base name). On Atari ST the
|
|
||||||
# Space Taxi install dir + binary are "STAXI".
|
|
||||||
prog_upper=${prog^^}
|
|
||||||
case "$prog_upper" in
|
|
||||||
SPACETAXI) prog_upper=STAXI ;;
|
|
||||||
esac
|
|
||||||
file=$prog_upper.PRG
|
|
||||||
|
|
||||||
tos=$repo/toolchains/emulators/support/emutos-512k.img
|
|
||||||
|
|
||||||
# Examples may live directly in bin_dir (PATTERN.PRG, HELLO.PRG, etc.)
|
|
||||||
# or in their own subdir alongside a per-example DATA/ tree
|
|
||||||
# (STAXI/STAXI.PRG). Prefer the subdir layout if present so STAXI
|
|
||||||
# auto-loads from its own asset bundle.
|
|
||||||
if [[ -f "$bin_dir/$prog_upper/$file" ]]; then
|
|
||||||
auto_path="C:\\$prog_upper\\$file"
|
|
||||||
elif [[ -f "$bin_dir/$file" ]]; then
|
|
||||||
auto_path="C:\\$file"
|
|
||||||
else
|
|
||||||
echo "$file not built. Run 'make atarist' first." >&2
|
|
||||||
echo " looked for:" >&2
|
|
||||||
echo " $bin_dir/$prog_upper/$file" >&2
|
|
||||||
echo " $bin_dir/$file" >&2
|
|
||||||
if compgen -G "$bin_dir/*.PRG" > /dev/null; then
|
|
||||||
echo "available examples in $bin_dir:" >&2
|
|
||||||
ls "$bin_dir"/*.PRG | xargs -n1 basename >&2
|
|
||||||
fi
|
|
||||||
find "$bin_dir" -maxdepth 2 -name '*.PRG' -printf ' %P\n' >&2 2>/dev/null \
|
|
||||||
| grep '/' >&2 || true
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if [[ ! -f $tos ]]; then
|
|
||||||
echo "TOS ROM missing: $tos" >&2
|
|
||||||
echo "run ./toolchains/install.sh (EmuTOS should have been staged)." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Hatari's --auto needs the target on a mounted GEMDOS drive.
|
|
||||||
exec hatari \
|
|
||||||
--tos "$tos" \
|
|
||||||
--harddrive "$bin_dir" \
|
|
||||||
--gemdos-drive C \
|
|
||||||
--auto "$auto_path"
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# Launch the built DOS example in DOSBox. Defaults to PATTERN.
|
|
||||||
#
|
|
||||||
# scripts/run-dos.sh # runs PATTERN
|
|
||||||
# scripts/run-dos.sh hello # runs HELLO
|
|
||||||
# scripts/run-dos.sh draw # runs DRAW
|
|
||||||
#
|
|
||||||
# Argument is any built example name (case-insensitive); the script
|
|
||||||
# upper-cases it and appends .EXE, then checks the file exists.
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
if [[ $# -gt 1 ]]; then
|
|
||||||
echo "usage: $0 [example-name]" >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
prog=${1:-pattern}
|
|
||||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
|
||||||
bin_dir=$repo/build/dos/bin
|
|
||||||
conf=$repo/scripts/dosbox-386sx16.conf
|
|
||||||
file=${prog^^}.EXE
|
|
||||||
appdir=${prog^^}
|
|
||||||
|
|
||||||
# Two layouts supported:
|
|
||||||
# 1. New: bin/<APP>/<APP>.EXE (with DATA/ alongside it)
|
|
||||||
# DOSBox mounts bin/<APP> as C: so DATA/foo.bin is reachable
|
|
||||||
# via the relative path the binary opens.
|
|
||||||
# 2. Old: bin/<APP>.EXE (no DATA, or DATA at bin/DATA)
|
|
||||||
# DOSBox mounts bin/ as C: as before.
|
|
||||||
if [[ -f "$bin_dir/$appdir/$file" ]]; then
|
|
||||||
mount_dir="$bin_dir/$appdir"
|
|
||||||
elif [[ -f "$bin_dir/$file" ]]; then
|
|
||||||
mount_dir="$bin_dir"
|
|
||||||
else
|
|
||||||
echo "Neither $bin_dir/$appdir/$file nor $bin_dir/$file built." >&2
|
|
||||||
echo "Run 'make dos' first." >&2
|
|
||||||
if compgen -G "$bin_dir/*.EXE" > /dev/null; then
|
|
||||||
echo "available top-level binaries:" >&2
|
|
||||||
ls "$bin_dir"/*.EXE 2>/dev/null | xargs -n1 basename >&2
|
|
||||||
fi
|
|
||||||
if compgen -G "$bin_dir/*/*.EXE" > /dev/null; then
|
|
||||||
echo "available app-dir binaries:" >&2
|
|
||||||
ls "$bin_dir"/*/*.EXE 2>/dev/null | xargs -n1 -I{} sh -c 'dirname {} | xargs basename'
|
|
||||||
fi
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# mouse_capture=seamless is required for DOSBox-Staging when running
|
|
||||||
# inside a VM whose host already manages the pointer; without it the
|
|
||||||
# default capture-on-click behavior fights the VM's grab and mouse
|
|
||||||
# input is unusable. On plain DOSBox this -set flag is unknown and is
|
|
||||||
# logged once as a warning, then ignored -- harmless either way.
|
|
||||||
#
|
|
||||||
# -conf $conf locks the CPU to a simulated 386SX-16 (the slowest
|
|
||||||
# realistic 386 desktop). DOSBox layers configs: anything not set in
|
|
||||||
# our file falls back to the user's main dosbox.conf.
|
|
||||||
dosboxArgs=(
|
|
||||||
-conf "$conf"
|
|
||||||
-set "mouse_capture=seamless"
|
|
||||||
)
|
|
||||||
# CYCLES=max (or any other DOSBox cycles value, e.g. CYCLES=50000)
|
|
||||||
# overrides the conf's period-correct 386SX-16 cap. Use this to
|
|
||||||
# A/B test whether perf ceilings are coming from the simulated CPU
|
|
||||||
# or the code itself. Same pattern as run-agi.sh.
|
|
||||||
if [[ -n "${CYCLES:-}" ]]; then
|
|
||||||
dosboxArgs+=(-set "cpu cycles=$CYCLES")
|
|
||||||
fi
|
|
||||||
dosboxArgs+=(
|
|
||||||
-c "C:"
|
|
||||||
-c "$file"
|
|
||||||
-c "pause"
|
|
||||||
--exit "$mount_dir"
|
|
||||||
)
|
|
||||||
exec dosbox "${dosboxArgs[@]}"
|
|
||||||
|
|
@ -1,302 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# Launch the IIgs sprite demo (or other example) under MAME's apple2gs
|
|
||||||
# driver instead of GSplus. We get:
|
|
||||||
# - The MAME debugger window (always visible with -debug)
|
|
||||||
# - debug.log file written to the working directory
|
|
||||||
# - A Lua hook that, when the CPU halts (e.g., BRK), dumps registers
|
|
||||||
# and the surrounding code bytes to /tmp/mame-iigs/crash.txt
|
|
||||||
#
|
|
||||||
# The boot path is the same as run-iigs.sh: gsos-system.po as flop3, our
|
|
||||||
# joey.2mg as flop4. Once Finder is up, navigate to JOEYLIB and double-
|
|
||||||
# click the example.
|
|
||||||
#
|
|
||||||
# Outputs of a run land in /tmp/mame-iigs/:
|
|
||||||
# - debug.log MAME's debugger console output (-debuglog)
|
|
||||||
# - crash.txt Lua-hook crash dump (if anything halts the CPU)
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
if [[ $# -gt 1 ]]; then
|
|
||||||
echo "usage: $0 [example-name]" >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
prog=${1:-pattern}
|
|
||||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
|
||||||
|
|
||||||
bin_dir=$repo/build/iigs/bin
|
|
||||||
target=${prog^^}
|
|
||||||
if [[ ! -f "$bin_dir/$target" ]]; then
|
|
||||||
echo "$bin_dir/$target not built. Run 'make iigs' first." >&2
|
|
||||||
if compgen -G "$bin_dir/*" > /dev/null; then
|
|
||||||
echo "available examples in $bin_dir:" >&2
|
|
||||||
find "$bin_dir" -maxdepth 1 -type f -printf '%f\n' \
|
|
||||||
| grep -vE '\.2mg$|\.txt$' >&2 || true
|
|
||||||
fi
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
sys_disk=$repo/toolchains/emulators/support/gsos-system.po
|
|
||||||
data_disk=$bin_dir/joey.2mg
|
|
||||||
|
|
||||||
for f in "$sys_disk" "$data_disk"; do
|
|
||||||
if [[ ! -f $f ]]; then
|
|
||||||
echo "missing: $f" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
work=$(mktemp -d -t joeylib-mame.XXXXXX)
|
|
||||||
out=/tmp/mame-iigs
|
|
||||||
mkdir -p "$out"
|
|
||||||
|
|
||||||
cp "$sys_disk" "$work/boot.po"
|
|
||||||
cp "$data_disk" "$work/joey.2mg"
|
|
||||||
|
|
||||||
# Lua script: drives Finder via natural keyboard + macadb key fields
|
|
||||||
# to launch the requested example, dumps register state on any halt
|
|
||||||
# (BRK, breakpoint, watchpoint, manual stop). Field names for keys
|
|
||||||
# come from MAME's apple2gs macadb input definitions:
|
|
||||||
# :macadb:KEY0 -> "d D"
|
|
||||||
# :macadb:KEY1 -> "o O"
|
|
||||||
# :macadb:KEY2 -> "j J", "p P"
|
|
||||||
# :macadb:KEY3 -> "Command / Open Apple"
|
|
||||||
# Letters not on KEY0..2 fall back to natkeyboard:post() (which
|
|
||||||
# handles modifier-less character entry only).
|
|
||||||
# Type the FULL program name to disambiguate (DRAW vs DATA which both
|
|
||||||
# start with D and live in the same JOEYLIB volume).
|
|
||||||
prog_select_str=${target}
|
|
||||||
cat > "$work/crash-hook.lua" <<LUA
|
|
||||||
-- Crash diagnostics + Finder driver for IIgs demos. Auto-resumes
|
|
||||||
-- the initial debug pause; at calibrated frame counts taps "J" to
|
|
||||||
-- select JOEYLIB, Cmd-O to open it, the program first letter to
|
|
||||||
-- select the binary, Cmd-O to launch it. On any halt (BRK trap,
|
|
||||||
-- watchpoint, breakpoint) dumps registers + bytes around PC to
|
|
||||||
-- crash.txt.
|
|
||||||
local cpu = manager.machine.devices[":maincpu"]
|
|
||||||
local prog = cpu.spaces["program"]
|
|
||||||
local outpath = "/tmp/mame-iigs/crash.txt"
|
|
||||||
|
|
||||||
local function dump(label)
|
|
||||||
local f = io.open(outpath, "a")
|
|
||||||
if f == nil then return end
|
|
||||||
f:write(string.format("=== %s @ %s ===\n", label, os.date("%H:%M:%S")))
|
|
||||||
for k, v in pairs(cpu.state) do
|
|
||||||
f:write(string.format(" %s = %X\n", k, v.value))
|
|
||||||
end
|
|
||||||
local pc = cpu.state["CURPC"].value
|
|
||||||
local lo = (pc - 16) & 0xFFFFFF
|
|
||||||
f:write(string.format(" bytes %06X..%06X:", lo, lo + 32))
|
|
||||||
for i = 0, 32 do
|
|
||||||
local b = prog:read_u8(lo + i)
|
|
||||||
f:write(string.format(" %02X", b))
|
|
||||||
end
|
|
||||||
f:write("\n")
|
|
||||||
-- Probe addresses from joeyDraw.asm
|
|
||||||
f:write(string.format(" probe 012050=%02X 011F80=%02X 023000=%02X\n",
|
|
||||||
prog:read_u8(0x012050),
|
|
||||||
prog:read_u8(0x011F80),
|
|
||||||
prog:read_u8(0x023000)))
|
|
||||||
f:close()
|
|
||||||
end
|
|
||||||
|
|
||||||
local done_marker = "/tmp/mame-iigs/.done"
|
|
||||||
local started = false
|
|
||||||
local crashed = false
|
|
||||||
local boot_frames = 0
|
|
||||||
|
|
||||||
local function signal_done(reason)
|
|
||||||
local f = io.open(done_marker, "w")
|
|
||||||
if f ~= nil then
|
|
||||||
f:write(reason)
|
|
||||||
f:close()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local nat = manager.machine.natkeyboard
|
|
||||||
|
|
||||||
local function get_field(port, field_name)
|
|
||||||
local p = manager.machine.ioport.ports[port]
|
|
||||||
if p == nil then return nil end
|
|
||||||
return p.fields[field_name]
|
|
||||||
end
|
|
||||||
|
|
||||||
local key_cmd = get_field(":macadb:KEY3", "Command / Open Apple")
|
|
||||||
|
|
||||||
local function press(f) if f then f:set_value(1) end end
|
|
||||||
local function release(f) if f then f:set_value(0) end end
|
|
||||||
|
|
||||||
local function log_step(label)
|
|
||||||
local f = io.open("/tmp/mame-iigs/steps.txt", "a")
|
|
||||||
if f ~= nil then
|
|
||||||
f:write(string.format("frame=%d %s\n", boot_frames, label))
|
|
||||||
f:close()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function snap(label)
|
|
||||||
log_step("snap " .. label)
|
|
||||||
manager.machine.video:snapshot()
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Finder navigation: natkeyboard handles per-letter scancode/timing
|
|
||||||
-- correctly; the Cmd modifier is forced via the macadb field for the
|
|
||||||
-- duration of the post() call. Mirrors the proven approach in the
|
|
||||||
-- pre-existing /tmp/auto_run.lua.
|
|
||||||
local steps = {
|
|
||||||
{ 2700, function() snap("preboot") end },
|
|
||||||
{ 3000, function() snap("at_3000_finder") log_step("post J") nat:post("J") end },
|
|
||||||
{ 3120, function() snap("after_J") log_step("Cmd hold") press(key_cmd) end },
|
|
||||||
{ 3126, function() log_step("post o under Cmd") nat:post("o") end },
|
|
||||||
{ 3180, function() log_step("Cmd release") release(key_cmd) end },
|
|
||||||
{ 3300, function() snap("after_Cmd_O") end },
|
|
||||||
{ 3540, function() log_step("post name") nat:post("${prog_select_str}") end },
|
|
||||||
{ 3600, function() snap("after_first") end },
|
|
||||||
{ 3660, function() log_step("Cmd hold #2") press(key_cmd) end },
|
|
||||||
{ 3666, function() log_step("post o #2") nat:post("o") end },
|
|
||||||
{ 3720, function() log_step("Cmd release #2") release(key_cmd) end },
|
|
||||||
{ 3900, function() snap("after_launch") end },
|
|
||||||
{ 6000, function() snap("running") end },
|
|
||||||
{ 9000, function() snap("running_2") end },
|
|
||||||
{ 12000, function() snap("running_3") end },
|
|
||||||
{ 13500, function() snap("running_4") end },
|
|
||||||
{ 14500, function() snap("running_5") end },
|
|
||||||
{ 15500, function() snap("running_6") end },
|
|
||||||
{ 16500, function() snap("running_7") end },
|
|
||||||
}
|
|
||||||
local step_idx = 1
|
|
||||||
|
|
||||||
-- Probe-address poll. Watch 3 addresses and log every change, so we
|
|
||||||
-- can verify which (if any) is reachable by our long-mode STAs.
|
|
||||||
local last1, last2, last3, last4, last5, last6, last7 = -1, -1, -1, -1, -1, -1, -1
|
|
||||||
local function check_ckpt()
|
|
||||||
local v1 = prog:read_u8(0x012050)
|
|
||||||
local v2 = prog:read_u8(0x011F80)
|
|
||||||
local v3 = prog:read_u8(0x023000)
|
|
||||||
local v4 = prog:read_u8(0xE12050) -- SHR pixel byte (mirror of $012050 once blitted)
|
|
||||||
local v5 = prog:read_u8(0xE19D00) -- SCB row 0 (0 = 320 mode)
|
|
||||||
local v6 = prog:read_u8(0xE15000) -- SHR pixel byte mid-screen (row ~38, col ~64)
|
|
||||||
local v7 = prog:read_u8(0xE19E00) -- palette[0][0] low byte (color 0 lo)
|
|
||||||
if v1 ~= last1 or v2 ~= last2 or v3 ~= last3 or v4 ~= last4 or v5 ~= last5 or v6 ~= last6 or v7 ~= last7 then
|
|
||||||
local f = io.open("/tmp/mame-iigs/ckpt-trace.txt", "a")
|
|
||||||
if f ~= nil then
|
|
||||||
f:write(string.format("frame=%d 012050=%02X 011F80=%02X 023000=%02X E12050=%02X E19D00=%02X E15000=%02X E19E00=%02X\n",
|
|
||||||
boot_frames, v1, v2, v3, v4, v5, v6, v7))
|
|
||||||
f:close()
|
|
||||||
end
|
|
||||||
last1, last2, last3, last4, last5, last6, last7 = v1, v2, v3, v4, v5, v6, v7
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
emu.register_periodic(function()
|
|
||||||
local dbg = manager.machine.debugger
|
|
||||||
if dbg == nil then return end
|
|
||||||
if dbg.execution_state == "stop" then
|
|
||||||
if not started then
|
|
||||||
started = true
|
|
||||||
dbg.execution_state = "run"
|
|
||||||
return
|
|
||||||
end
|
|
||||||
if not crashed then
|
|
||||||
crashed = true
|
|
||||||
dump("halt")
|
|
||||||
signal_done("halt")
|
|
||||||
end
|
|
||||||
else
|
|
||||||
boot_frames = boot_frames + 1
|
|
||||||
check_ckpt()
|
|
||||||
while step_idx <= #steps and boot_frames >= steps[step_idx][1] do
|
|
||||||
steps[step_idx][2]()
|
|
||||||
step_idx = step_idx + 1
|
|
||||||
end
|
|
||||||
if boot_frames > 18000 and not crashed then
|
|
||||||
crashed = true
|
|
||||||
dump("watchdog")
|
|
||||||
signal_done("watchdog")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
LUA
|
|
||||||
|
|
||||||
# Wipe prior crash.txt so we don't confuse runs.
|
|
||||||
: > "$out/crash.txt"
|
|
||||||
|
|
||||||
cat <<EOF
|
|
||||||
MAME apple2gs (auto-launch ${prog^^}):
|
|
||||||
Boot disk: $work/boot.po (flop3)
|
|
||||||
|
|
||||||
Boots GS/OS, waits ~50s for Finder, then drives the keyboard via Lua
|
|
||||||
to select the JOEYLIB volume and double-launch ${prog^^}.
|
|
||||||
On crash the MAME debugger halts and Lua dumps state to:
|
|
||||||
$out/crash.txt
|
|
||||||
|
|
||||||
After exit:
|
|
||||||
$out/debug.log MAME debugger console output
|
|
||||||
|
|
||||||
EOF
|
|
||||||
|
|
||||||
cleanup() {
|
|
||||||
# Always rescue debug.log first -- it's written by MAME relative to
|
|
||||||
# cwd ($work) and lost when the dir is removed.
|
|
||||||
if [[ -f $work/debug.log ]]; then
|
|
||||||
mv -f "$work/debug.log" "$out/debug.log"
|
|
||||||
fi
|
|
||||||
# Snapshots that the Lua hook captured for visual debugging.
|
|
||||||
if [[ -d $work/snap ]]; then
|
|
||||||
rm -rf "$out/snap"
|
|
||||||
mv "$work/snap" "$out/snap"
|
|
||||||
fi
|
|
||||||
rm -rf "$work"
|
|
||||||
}
|
|
||||||
trap cleanup EXIT
|
|
||||||
|
|
||||||
# Visible by default. Set MAME_HEADLESS=1 to suppress the video window
|
|
||||||
# (CI / batch runs that only care about crash.txt / snapshots). The crash
|
|
||||||
# hook drives MAME's debugger, which needs a Qt platform; headless boxes
|
|
||||||
# have no X display, so force Qt's offscreen plugin and SDL's dummy
|
|
||||||
# video/audio drivers so MAME never tries to open a window.
|
|
||||||
video_arg="-window"
|
|
||||||
if [[ "${MAME_HEADLESS:-0}" = "1" ]]; then
|
|
||||||
# Use the software renderer (not "-video none"): MAME's video:snapshot()
|
|
||||||
# needs a real render target, so "-video none" silently drops snapshots.
|
|
||||||
# "-video soft" renders to an offscreen surface that snapshot() can read.
|
|
||||||
video_arg="-video soft"
|
|
||||||
export QT_QPA_PLATFORM=offscreen
|
|
||||||
export SDL_VIDEODRIVER=dummy
|
|
||||||
export SDL_AUDIODRIVER=dummy
|
|
||||||
fi
|
|
||||||
|
|
||||||
# apple2gs ROM set. Default to the user's MAME roms; override via MAME_ROMPATH.
|
|
||||||
# (A bare `mame apple2gs` only finds ROMs if rompath is already configured;
|
|
||||||
# passing it explicitly keeps this script self-contained.)
|
|
||||||
rompath="${MAME_ROMPATH:-$HOME/.mame/roms}"
|
|
||||||
|
|
||||||
# Clear the done-marker the Lua hook uses to signal shutdown.
|
|
||||||
rm -f "$out/.done"
|
|
||||||
|
|
||||||
cd "$work"
|
|
||||||
mame apple2gs \
|
|
||||||
-rompath "$rompath" \
|
|
||||||
-flop3 "$work/boot.po" \
|
|
||||||
-flop4 "$work/joey.2mg" \
|
|
||||||
$video_arg -sound none \
|
|
||||||
-nothrottle \
|
|
||||||
-debug -debuglog \
|
|
||||||
-autoboot_script "$work/crash-hook.lua" &
|
|
||||||
mame_pid=$!
|
|
||||||
|
|
||||||
# Poll for the done-marker. Kill MAME once Lua signals it. Cap total
|
|
||||||
# wall-clock at 6 min so we don't outlive the Lua-side watchdog (5 min).
|
|
||||||
deadline=$((SECONDS + 360))
|
|
||||||
while kill -0 "$mame_pid" 2>/dev/null; do
|
|
||||||
if [[ -f $out/.done ]]; then
|
|
||||||
kill "$mame_pid" 2>/dev/null
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
if (( SECONDS > deadline )); then
|
|
||||||
kill "$mame_pid" 2>/dev/null
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
sleep 0.5
|
|
||||||
done
|
|
||||||
wait "$mame_pid" 2>/dev/null || true
|
|
||||||
|
|
@ -1,610 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# Launch the built Apple IIgs examples.
|
|
||||||
#
|
|
||||||
# scripts/run-iigs.sh GSplus + Finder: boots GS/OS with
|
|
||||||
# joey.2mg mounted; pick the demo by
|
|
||||||
# double-clicking it in Finder.
|
|
||||||
# scripts/run-iigs.sh staxi MAME auto-launch: builds a
|
|
||||||
# single-example run disk
|
|
||||||
# (joey-run.2mg -- joey.2mg is left
|
|
||||||
# alone so benches stay honest) plus a
|
|
||||||
# Finder-less boot disk whose
|
|
||||||
# /System/Start IS the launcher, so
|
|
||||||
# the example is on screen in ~50 s
|
|
||||||
# with no Finder and no keystroke
|
|
||||||
# driving. No clicking needed.
|
|
||||||
# scripts/run-iigs.sh --all MAME + every example on one 16 MB
|
|
||||||
# CFFA2 hard disk (the full set does
|
|
||||||
# not fit an 800KB floppy): boots
|
|
||||||
# GS/OS with the JOEYLIB volume so you
|
|
||||||
# pick a demo in Finder.
|
|
||||||
# scripts/run-iigs.sh --all staxi same disk, auto-launches the named
|
|
||||||
# example via the Lua Finder driver.
|
|
||||||
#
|
|
||||||
# The MAME path needs the apple2gs ROM set (default ~/.mame/roms,
|
|
||||||
# override with MAME_ROMPATH). MAME_HEADLESS=1 runs it windowless with
|
|
||||||
# periodic PNG snapshots (batch verification). --all also needs the
|
|
||||||
# CFFA2 firmware (a2cffa2/cffa20eec02.bin), like the AGI path.
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
|
||||||
bin_dir=$repo/build/iigs/bin
|
|
||||||
sys_disk=$repo/toolchains/emulators/support/gsos-system.po
|
|
||||||
|
|
||||||
# MAME: an installed one wins, otherwise the apple2gs build this repo
|
|
||||||
# already carries in toolchains/cache. Resolving it here means a shell
|
|
||||||
# whose PATH predates ~/.local/bin (a symlink created after login) does
|
|
||||||
# not fail with a bare "mame: command not found" three hundred lines in.
|
|
||||||
MAME="${MAME:-$(command -v mame || true)}"
|
|
||||||
if [[ -z $MAME ]]; then
|
|
||||||
MAME=$repo/toolchains/cache/mame-mame0264/apple2gs
|
|
||||||
fi
|
|
||||||
if [[ ! -x $MAME ]]; then
|
|
||||||
echo "run-iigs: no MAME found (set MAME=/path/to/mame, or apt install mame)" >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
# --all boots the full example set off a CFFA2 hard-disk image; an
|
|
||||||
# optional example name after it auto-launches that one.
|
|
||||||
all_mode=0
|
|
||||||
if [[ "${1:-}" == "--all" ]]; then
|
|
||||||
all_mode=1
|
|
||||||
shift
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ $# -gt 1 ]]; then
|
|
||||||
echo "usage: $0 [--all] [example-name]" >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ------------------------------------------------------------ --all (CFFA2)
|
|
||||||
if [[ $all_mode -eq 1 ]]; then
|
|
||||||
all_disk=$bin_dir/joey-all.2mg
|
|
||||||
target=""
|
|
||||||
if [[ $# -eq 1 ]]; then
|
|
||||||
target=${1^^}
|
|
||||||
if [[ ! -f "$bin_dir/$target" ]]; then
|
|
||||||
echo "$bin_dir/$target not built. Run 'make iigs' first." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
if [[ ! -f $sys_disk ]]; then
|
|
||||||
echo "missing: $sys_disk (run ./toolchains/install.sh)" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# make-iigs-disk.sh needs the toolchain env (cadius path).
|
|
||||||
if [[ -z "${LLVM816_ROOT:-}" ]]; then
|
|
||||||
# shellcheck disable=SC1091
|
|
||||||
source "$repo/toolchains/env.sh" >/dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
# (Re)build the all-examples image when it is missing or older than any
|
|
||||||
# example binary. The full launchable set, sized to a 16 MB ProDOS
|
|
||||||
# volume (an 800KB floppy can't hold it).
|
|
||||||
all_examples="DRAW PATTERN KEYS JOY SPRITE SERIAL SERTEST SAVE AUDIO UBER ADV2 ADV AGI STAXI"
|
|
||||||
need_disk=0
|
|
||||||
if [[ ! -f $all_disk ]]; then
|
|
||||||
need_disk=1
|
|
||||||
else
|
|
||||||
for name in $all_examples; do
|
|
||||||
if [[ -f "$bin_dir/$name" && "$bin_dir/$name" -nt $all_disk ]]; then
|
|
||||||
need_disk=1
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
if [[ $need_disk -eq 1 ]]; then
|
|
||||||
echo "run-iigs: building all-examples disk ($all_disk)..."
|
|
||||||
BINDIR="$bin_dir" \
|
|
||||||
JOEY_DISK_SIZE="16MB" JOEY_DISK_EXAMPLES="$all_examples" \
|
|
||||||
"$repo/scripts/make-iigs-disk.sh" "$all_disk"
|
|
||||||
else
|
|
||||||
echo "run-iigs: all-examples disk is current ($all_disk)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# CFFA2 firmware (same resolution as the AGI path): prefer the repo-
|
|
||||||
# staged copy, fall back to the user's rompath.
|
|
||||||
staged_roms=$repo/toolchains/emulators/support/mame-roms
|
|
||||||
user_roms="${MAME_ROMPATH:-$HOME/.mame/roms}"
|
|
||||||
rompath="$user_roms"
|
|
||||||
if [[ -f "$staged_roms/a2cffa2/cffa20eec02.bin" ]]; then
|
|
||||||
rompath="$user_roms;$staged_roms"
|
|
||||||
elif [[ ! -f "$user_roms/a2cffa2/cffa20eec02.bin" ]]; then
|
|
||||||
cat >&2 <<EOF
|
|
||||||
missing CFFA2 firmware: a2cffa2/cffa20eec02.bin
|
|
||||||
Needed to mount the all-examples hard disk. It ships in dreher.net's
|
|
||||||
Run6_CDROM.zip as Firmware/V2.0/CFFA20EEC02.BIN. Drop it in either
|
|
||||||
$staged_roms/a2cffa2/ or $user_roms/a2cffa2/
|
|
||||||
EOF
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
headless=${MAME_HEADLESS:-0}
|
|
||||||
work=$(mktemp -d -t joeylib-run-all.XXXXXX)
|
|
||||||
mame_pid=""
|
|
||||||
cleanup() {
|
|
||||||
if [[ -n $mame_pid ]] && kill -0 "$mame_pid" 2>/dev/null; then
|
|
||||||
kill "$mame_pid" 2>/dev/null || true
|
|
||||||
sleep 1
|
|
||||||
kill -9 "$mame_pid" 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
rm -rf "$work"
|
|
||||||
}
|
|
||||||
trap cleanup EXIT INT TERM
|
|
||||||
|
|
||||||
# hard1: writable bootable GS/OS. hard2: the all-examples volume.
|
|
||||||
cp "$sys_disk" "$work/boot.po"
|
|
||||||
cp "$all_disk" "$work/joey-all.2mg"
|
|
||||||
|
|
||||||
# Finder driver: only when a target is named (else the user picks a
|
|
||||||
# demo). Type J (select the JOEYLIB volume), Cmd-O (open), the full
|
|
||||||
# example name, Cmd-O (launch) -- the same steps the floppy path uses.
|
|
||||||
cat > "$work/launch.lua" <<LUA
|
|
||||||
local nat = manager.machine.natkeyboard
|
|
||||||
local function get_field(port, field_name)
|
|
||||||
local p = manager.machine.ioport.ports[port]
|
|
||||||
if p == nil then return nil end
|
|
||||||
return p.fields[field_name]
|
|
||||||
end
|
|
||||||
local key_cmd = get_field(":macadb:KEY3", "Command / Open Apple")
|
|
||||||
local function press(f) if f then f:set_value(1) end end
|
|
||||||
local function release(f) if f then f:set_value(0) end end
|
|
||||||
local headless = ($headless == 1)
|
|
||||||
local target = "$target"
|
|
||||||
local frames = 0
|
|
||||||
local steps = {}
|
|
||||||
if target ~= "" then
|
|
||||||
steps = {
|
|
||||||
{ 3000, function() nat:post("J") end },
|
|
||||||
{ 3120, function() press(key_cmd) end },
|
|
||||||
{ 3126, function() nat:post("o") end },
|
|
||||||
{ 3180, function() release(key_cmd) end },
|
|
||||||
{ 3540, function() nat:post(target) end },
|
|
||||||
{ 3660, function() press(key_cmd) end },
|
|
||||||
{ 3666, function() nat:post("o") end },
|
|
||||||
{ 3720, function() release(key_cmd) end },
|
|
||||||
}
|
|
||||||
end
|
|
||||||
if headless then
|
|
||||||
table.insert(steps, { 4200, function() manager.machine.video:snapshot() end })
|
|
||||||
table.insert(steps, { 6600, function() manager.machine.video:snapshot() end })
|
|
||||||
table.insert(steps, { 9000, function() manager.machine.video:snapshot() end })
|
|
||||||
table.insert(steps, { 9100, function() manager.machine:exit() end })
|
|
||||||
end
|
|
||||||
table.sort(steps, function(a, b) return a[1] < b[1] end)
|
|
||||||
local step_idx = 1
|
|
||||||
emu.register_periodic(function()
|
|
||||||
frames = frames + 1
|
|
||||||
while step_idx <= #steps and frames >= steps[step_idx][1] do
|
|
||||||
steps[step_idx][2]()
|
|
||||||
step_idx = step_idx + 1
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
LUA
|
|
||||||
|
|
||||||
video_arg="-window"
|
|
||||||
sound_arg=""
|
|
||||||
throttle_arg=""
|
|
||||||
if [[ "$headless" = "1" ]]; then
|
|
||||||
video_arg="-video soft"
|
|
||||||
sound_arg="-sound none"
|
|
||||||
throttle_arg="-nothrottle"
|
|
||||||
export QT_QPA_PLATFORM=offscreen
|
|
||||||
export SDL_VIDEODRIVER=dummy
|
|
||||||
export SDL_AUDIODRIVER=dummy
|
|
||||||
else
|
|
||||||
export SDL_MOUSE_RELATIVE_MODE_WARP=0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n $target ]]; then
|
|
||||||
echo "MAME apple2gs: all-examples CFFA2 disk, auto-launching $target."
|
|
||||||
else
|
|
||||||
echo "MAME apple2gs: all-examples CFFA2 disk. Open JOEYLIB and pick a demo."
|
|
||||||
fi
|
|
||||||
echo "GS/OS takes ~50 s to reach Finder. Quit MAME to end the session."
|
|
||||||
|
|
||||||
cd "$work"
|
|
||||||
# -ramsize 4M: AGI (on this disk) needs the larger Memory Manager pool;
|
|
||||||
# harmless for the smaller demos.
|
|
||||||
"$MAME" apple2gs \
|
|
||||||
-ramsize 4M \
|
|
||||||
-rompath "$rompath" \
|
|
||||||
-sl7 cffa2 -hard1 "$work/boot.po" -hard2 "$work/joey-all.2mg" \
|
|
||||||
$video_arg $sound_arg $throttle_arg \
|
|
||||||
-skip_gameinfo \
|
|
||||||
-snapshot_directory "$work/snap" \
|
|
||||||
-autoboot_script "$work/launch.lua" &
|
|
||||||
mame_pid=$!
|
|
||||||
wait "$mame_pid" || true
|
|
||||||
mame_pid=""
|
|
||||||
if [[ -d $work/snap ]]; then
|
|
||||||
rm -rf /tmp/run-iigs-snap
|
|
||||||
mv "$work/snap" /tmp/run-iigs-snap
|
|
||||||
echo "run-iigs: snapshots -> /tmp/run-iigs-snap"
|
|
||||||
fi
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------- MAME
|
|
||||||
# Example given: auto-launching MAME path.
|
|
||||||
if [[ $# -eq 1 ]]; then
|
|
||||||
target=${1^^}
|
|
||||||
if [[ ! -f "$bin_dir/$target" ]]; then
|
|
||||||
echo "$bin_dir/$target not built. Run 'make iigs' first." >&2
|
|
||||||
if compgen -G "$bin_dir/*" > /dev/null; then
|
|
||||||
echo "available examples in $bin_dir:" >&2
|
|
||||||
find "$bin_dir" -maxdepth 1 -type f -printf ' %f\n' \
|
|
||||||
| grep -vE '\.2mg$|\.txt$' >&2 || true
|
|
||||||
fi
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if [[ ! -f $sys_disk ]]; then
|
|
||||||
echo "missing: $sys_disk (run ./toolchains/install.sh)" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ------------------------------------------------------------- AGI
|
|
||||||
# AGI needs ~880KB (240KB interpreter + 644KB KQ3 resources), which
|
|
||||||
# overflows the 800KB 3.5" floppy the generic path uses, and MAME's
|
|
||||||
# IIgs 3.5 drive is 800K-GCR-only. So AGI runs off a CFFA2 CompactFlash
|
|
||||||
# card instead: partition 1 (-hard1) is a bootable GS/OS, partition 2
|
|
||||||
# (-hard2) is a 2MB ProDOS volume with AGI + the game data. The boot
|
|
||||||
# scan hits slot 7 first, boots GS/OS from hard1, and GS/OS then mounts
|
|
||||||
# the AGI volume; the Lua driver opens it and launches AGI. No BSS/BRAM
|
|
||||||
# or startup-slot changes are needed with this two-partition topology.
|
|
||||||
if [[ $target == AGI ]]; then
|
|
||||||
game=${JOEY_AGI_GAME:-kq3}
|
|
||||||
# make-agi-iigs-disk.sh needs the toolchain env (cadius path).
|
|
||||||
if [[ -z "${LLVM816_ROOT:-}" ]]; then
|
|
||||||
# shellcheck disable=SC1091
|
|
||||||
source "$repo/toolchains/env.sh" >/dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
# MAME loads the CFFA2 firmware as an a2cffa2 device ROM. Prefer the
|
|
||||||
# copy staged in the repo (toolchains/install.sh territory) so the
|
|
||||||
# run does not depend on the user's ~/.mame; fall back to the user
|
|
||||||
# rompath. Legal: the firmware is freely redistributed by dreher.net
|
|
||||||
# and the card is owned.
|
|
||||||
staged_roms=$repo/toolchains/emulators/support/mame-roms
|
|
||||||
user_roms="${MAME_ROMPATH:-$HOME/.mame/roms}"
|
|
||||||
rompath="$user_roms"
|
|
||||||
if [[ -f "$staged_roms/a2cffa2/cffa20eec02.bin" ]]; then
|
|
||||||
rompath="$user_roms;$staged_roms"
|
|
||||||
elif [[ ! -f "$user_roms/a2cffa2/cffa20eec02.bin" ]]; then
|
|
||||||
cat >&2 <<EOF
|
|
||||||
missing CFFA2 firmware: a2cffa2/cffa20eec02.bin
|
|
||||||
Needed to mount AGI's data disk (4096 bytes, SHA1
|
|
||||||
080ff88f19de22328e162954ee2b51ee65f9d5cd). It ships in dreher.net's
|
|
||||||
Run6_CDROM.zip as Firmware/V2.0/CFFA20EEC02.BIN. Drop it in either
|
|
||||||
$staged_roms/a2cffa2/ or $user_roms/a2cffa2/
|
|
||||||
EOF
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
headless=${MAME_HEADLESS:-0}
|
|
||||||
work=$(mktemp -d -t joeylib-run-agi.XXXXXX)
|
|
||||||
mame_pid=""
|
|
||||||
cleanup() {
|
|
||||||
if [[ -n $mame_pid ]] && kill -0 "$mame_pid" 2>/dev/null; then
|
|
||||||
kill "$mame_pid" 2>/dev/null || true
|
|
||||||
sleep 1
|
|
||||||
kill -9 "$mame_pid" 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
rm -rf "$work"
|
|
||||||
}
|
|
||||||
trap cleanup EXIT INT TERM
|
|
||||||
|
|
||||||
# hard1: writable bootable GS/OS copy. hard2: freshly built AGI vol.
|
|
||||||
cp "$sys_disk" "$work/boot.po"
|
|
||||||
echo "run-iigs: building AGI CFFA data disk ($game)..."
|
|
||||||
"$repo/scripts/make-agi-iigs-disk.sh" "$work/agi.2mg" "$game"
|
|
||||||
|
|
||||||
# Finder driver: at calibrated frame counts type A (select the AGI
|
|
||||||
# volume -- boot volume is System.Disk, so 'A' is unambiguous), Cmd-O
|
|
||||||
# to open it, the full name AGI to select the app, Cmd-O to launch.
|
|
||||||
cat > "$work/launch.lua" <<LUA
|
|
||||||
local nat = manager.machine.natkeyboard
|
|
||||||
local function get_field(port, field_name)
|
|
||||||
local p = manager.machine.ioport.ports[port]
|
|
||||||
if p == nil then return nil end
|
|
||||||
return p.fields[field_name]
|
|
||||||
end
|
|
||||||
local key_cmd = get_field(":macadb:KEY3", "Command / Open Apple")
|
|
||||||
local function press(f) if f then f:set_value(1) end end
|
|
||||||
local function release(f) if f then f:set_value(0) end end
|
|
||||||
local headless = ($headless == 1)
|
|
||||||
local frames = 0
|
|
||||||
local steps = {
|
|
||||||
{ 3000, function() nat:post("A") end },
|
|
||||||
{ 3120, function() press(key_cmd) end },
|
|
||||||
{ 3126, function() nat:post("o") end },
|
|
||||||
{ 3180, function() release(key_cmd) end },
|
|
||||||
{ 3540, function() nat:post("AGI") end },
|
|
||||||
{ 3660, function() press(key_cmd) end },
|
|
||||||
{ 3666, function() nat:post("o") end },
|
|
||||||
{ 3720, function() release(key_cmd) end },
|
|
||||||
}
|
|
||||||
if headless then
|
|
||||||
table.insert(steps, { 4200, function() manager.machine.video:snapshot() end })
|
|
||||||
table.insert(steps, { 6600, function() manager.machine.video:snapshot() end })
|
|
||||||
table.insert(steps, { 9000, function() manager.machine.video:snapshot() end })
|
|
||||||
table.insert(steps, { 9100, function() manager.machine:exit() end })
|
|
||||||
end
|
|
||||||
table.sort(steps, function(a, b) return a[1] < b[1] end)
|
|
||||||
local step_idx = 1
|
|
||||||
emu.register_periodic(function()
|
|
||||||
frames = frames + 1
|
|
||||||
while step_idx <= #steps and frames >= steps[step_idx][1] do
|
|
||||||
steps[step_idx][2]()
|
|
||||||
step_idx = step_idx + 1
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
LUA
|
|
||||||
|
|
||||||
video_arg="-window"
|
|
||||||
sound_arg=""
|
|
||||||
throttle_arg=""
|
|
||||||
if [[ "$headless" = "1" ]]; then
|
|
||||||
video_arg="-video soft"
|
|
||||||
sound_arg="-sound none"
|
|
||||||
throttle_arg="-nothrottle"
|
|
||||||
export QT_QPA_PLATFORM=offscreen
|
|
||||||
export SDL_VIDEODRIVER=dummy
|
|
||||||
export SDL_AUDIODRIVER=dummy
|
|
||||||
else
|
|
||||||
export SDL_MOUSE_RELATIVE_MODE_WARP=0
|
|
||||||
fi
|
|
||||||
|
|
||||||
cat <<EOF
|
|
||||||
MAME apple2gs: AGI ($game) on a CFFA2 disk. GS/OS boots from hard1
|
|
||||||
(~50 s) and mounts the AGI volume on hard2; the Lua driver opens it and
|
|
||||||
launches AGI. Quit MAME (or close its window) to end the session.
|
|
||||||
EOF
|
|
||||||
|
|
||||||
cd "$work"
|
|
||||||
# -ramsize 4M: AGI's ~240KB app plus GS/OS/Finder leaves the stock
|
|
||||||
# 2MB Memory Manager pool too tight -- its first jlAlloc (the view
|
|
||||||
# cache) returns NULL and the app exits. 4MB is the assumed target.
|
|
||||||
"$MAME" apple2gs \
|
|
||||||
-ramsize 4M \
|
|
||||||
-rompath "$rompath" \
|
|
||||||
-sl7 cffa2 -hard1 "$work/boot.po" -hard2 "$work/agi.2mg" \
|
|
||||||
$video_arg $sound_arg $throttle_arg \
|
|
||||||
-skip_gameinfo \
|
|
||||||
-snapshot_directory "$work/snap" \
|
|
||||||
-autoboot_script "$work/launch.lua" &
|
|
||||||
mame_pid=$!
|
|
||||||
wait "$mame_pid" || true
|
|
||||||
mame_pid=""
|
|
||||||
if [[ -d $work/snap ]]; then
|
|
||||||
rm -rf /tmp/run-iigs-snap
|
|
||||||
mv "$work/snap" /tmp/run-iigs-snap
|
|
||||||
echo "run-iigs: snapshots -> /tmp/run-iigs-snap"
|
|
||||||
fi
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Dedicated run disk so joey.2mg (the bench/golden disk) is never
|
|
||||||
# replaced -- a stale-swapped joey.2mg silently corrupts the next
|
|
||||||
# UBER bench run. Rebuild when missing, holding a different
|
|
||||||
# example, or older than the example binary.
|
|
||||||
run_disk=$bin_dir/joey-run.2mg
|
|
||||||
stamp=$run_disk.contents
|
|
||||||
need_disk=0
|
|
||||||
if [[ ! -f $run_disk ]]; then
|
|
||||||
need_disk=1
|
|
||||||
elif [[ "$(cat "$stamp" 2>/dev/null)" != "$target" ]]; then
|
|
||||||
need_disk=1
|
|
||||||
elif [[ "$bin_dir/$target" -nt $run_disk ]]; then
|
|
||||||
need_disk=1
|
|
||||||
fi
|
|
||||||
if [[ $need_disk -eq 1 ]]; then
|
|
||||||
if [[ -z "${LLVM816_ROOT:-}" ]]; then
|
|
||||||
# make-iigs-disk.sh needs the toolchain env (cadius path).
|
|
||||||
# shellcheck disable=SC1091
|
|
||||||
source "$repo/toolchains/env.sh" >/dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
echo "run-iigs: building single-example run disk ($target)..."
|
|
||||||
JOEY_DISK_EXAMPLES="$target" "$repo/scripts/make-iigs-disk.sh" "$run_disk"
|
|
||||||
printf '%s' "$target" > "$stamp"
|
|
||||||
else
|
|
||||||
echo "run-iigs: run disk is current ($target)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
work=$(mktemp -d -t joeylib-run-iigs.XXXXXX)
|
|
||||||
mame_pid=""
|
|
||||||
cleanup() {
|
|
||||||
# Kill the emulator if we are exiting first (Ctrl-C, timeout):
|
|
||||||
# bash does not forward signals to a foreground child, and an
|
|
||||||
# orphaned MAME keeps burning CPU with its work dir deleted
|
|
||||||
# from under it.
|
|
||||||
if [[ -n $mame_pid ]] && kill -0 "$mame_pid" 2>/dev/null; then
|
|
||||||
kill "$mame_pid" 2>/dev/null || true
|
|
||||||
sleep 1
|
|
||||||
kill -9 "$mame_pid" 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
# Preserve the post-run disk (high scores etc.) for inspection.
|
|
||||||
if [[ -f $work/joey-run.2mg ]]; then
|
|
||||||
cp "$work/joey-run.2mg" "$bin_dir/joey-run-after.2mg"
|
|
||||||
fi
|
|
||||||
rm -rf "$work"
|
|
||||||
}
|
|
||||||
trap cleanup EXIT INT TERM
|
|
||||||
|
|
||||||
# Finder-less boot disk: /System/Start is tools/iigsStart, which
|
|
||||||
# SetPrefixGS's to the data volume and QuitGS-launches the example.
|
|
||||||
# This replaces the old Lua keyboard driver, which typed J / Cmd-O /
|
|
||||||
# name / Cmd-O at calibrated frame counts -- workable but fragile,
|
|
||||||
# and it made every run sit through the Finder's ~60 s desktop paint
|
|
||||||
# before anything happened. Straight to the example in ~50 s now.
|
|
||||||
if [[ -z "${LLVM816_ROOT:-}" ]]; then
|
|
||||||
# shellcheck disable=SC1091
|
|
||||||
source "$repo/toolchains/env.sh" >/dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
echo "run-iigs: building Finder-less boot disk for $target..."
|
|
||||||
"$repo/scripts/make-iigs-boot-disk.sh" "$work/boot.po" "$target" >/dev/null
|
|
||||||
cp "$run_disk" "$work/joey-run.2mg"
|
|
||||||
headless=${MAME_HEADLESS:-0}
|
|
||||||
cat > "$work/launch.lua" <<LUA
|
|
||||||
-- The boot disk launches the example by itself (tools/iigsStart), so
|
|
||||||
-- nothing here drives the Finder any more. This script only exists for
|
|
||||||
-- the headless snapshot/exit schedule.
|
|
||||||
local headless = ($headless == 1)
|
|
||||||
local frames = 0
|
|
||||||
local steps = {}
|
|
||||||
if headless then
|
|
||||||
-- Batch verification: snapshot right after the launch lands, mid
|
|
||||||
-- asset load, and after the example settles on its first screen,
|
|
||||||
-- then exit so the caller regains control. Interactive runs never
|
|
||||||
-- exit on their own.
|
|
||||||
table.insert(steps, { 3900, function() manager.machine.video:snapshot() end })
|
|
||||||
table.insert(steps, { 6600, function() manager.machine.video:snapshot() end })
|
|
||||||
table.insert(steps, { 9000, function() manager.machine.video:snapshot() end })
|
|
||||||
table.insert(steps, { 9100, function() manager.machine:exit() end })
|
|
||||||
end
|
|
||||||
local step_idx = 1
|
|
||||||
|
|
||||||
emu.register_periodic(function()
|
|
||||||
frames = frames + 1
|
|
||||||
if headless and frames % 600 == 0 then
|
|
||||||
-- Pacing heartbeat for batch runs (rescued with the snaps).
|
|
||||||
local f = io.open("launch-progress.txt", "w")
|
|
||||||
if f ~= nil then
|
|
||||||
f:write(string.format("frames=%d\n", frames))
|
|
||||||
f:close()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
while step_idx <= #steps and frames >= steps[step_idx][1] do
|
|
||||||
steps[step_idx][2]()
|
|
||||||
step_idx = step_idx + 1
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
LUA
|
|
||||||
|
|
||||||
video_arg="-window"
|
|
||||||
sound_arg=""
|
|
||||||
throttle_arg=""
|
|
||||||
if [[ "$headless" = "1" ]]; then
|
|
||||||
# Verification mode: no window, no sound, and unthrottled so
|
|
||||||
# the 12k-frame scripted run finishes in about a minute.
|
|
||||||
video_arg="-video soft"
|
|
||||||
sound_arg="-sound none"
|
|
||||||
throttle_arg="-nothrottle"
|
|
||||||
export QT_QPA_PLATFORM=offscreen
|
|
||||||
export SDL_VIDEODRIVER=dummy
|
|
||||||
export SDL_AUDIODRIVER=dummy
|
|
||||||
fi
|
|
||||||
rompath="${MAME_ROMPATH:-$HOME/.mame/roms}"
|
|
||||||
|
|
||||||
cat <<EOF
|
|
||||||
MAME apple2gs: auto-launching $target off $run_disk.
|
|
||||||
Booting a Finder-less GS/OS whose startup application IS $target, so it
|
|
||||||
comes up in ~50 s with no Finder in the way. Quit MAME (or close its
|
|
||||||
window) to end the session.
|
|
||||||
EOF
|
|
||||||
|
|
||||||
cd "$work"
|
|
||||||
# Throttled (real IIgs speed) -- this is the "play it" path,
|
|
||||||
# unlike the verify/bench harnesses which run -nothrottle. Runs
|
|
||||||
# as a tracked child so the cleanup trap can reap it on Ctrl-C.
|
|
||||||
# -skip_gameinfo matters: without it MAME parks on the "driver is
|
|
||||||
# not working correctly" info screen waiting for a keypress and
|
|
||||||
# the machine never boots (the -debug harnesses never hit this).
|
|
||||||
# -snapshot_directory is explicit because MAME otherwise resolves
|
|
||||||
# it against the user's mame.ini (snapshots vanished into
|
|
||||||
# ~/.mame/snap during bring-up).
|
|
||||||
"$MAME" apple2gs \
|
|
||||||
-rompath "$rompath" \
|
|
||||||
-flop3 "$work/boot.po" \
|
|
||||||
-flop4 "$work/joey-run.2mg" \
|
|
||||||
$video_arg $sound_arg $throttle_arg \
|
|
||||||
-skip_gameinfo \
|
|
||||||
-snapshot_directory "$work/snap" \
|
|
||||||
-autoboot_script "$work/launch.lua" &
|
|
||||||
mame_pid=$!
|
|
||||||
wait "$mame_pid" || true
|
|
||||||
mame_pid=""
|
|
||||||
if [[ -d $work/snap ]]; then
|
|
||||||
rm -rf /tmp/run-iigs-snap
|
|
||||||
mv "$work/snap" /tmp/run-iigs-snap
|
|
||||||
echo "run-iigs: snapshots -> /tmp/run-iigs-snap"
|
|
||||||
fi
|
|
||||||
if [[ -f $work/launch-progress.txt ]]; then
|
|
||||||
cp "$work/launch-progress.txt" /tmp/run-iigs-progress.txt
|
|
||||||
fi
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# -------------------------------------------------------------- GSplus
|
|
||||||
# No argument: original GSplus + interactive Finder path.
|
|
||||||
gsplus=$repo/toolchains/emulators/gsplus/bin/gsplus
|
|
||||||
rom=$repo/toolchains/emulators/support/apple-iigs.rom
|
|
||||||
data_disk=$bin_dir/joey.2mg
|
|
||||||
null_c600=$repo/toolchains/emulators/support/iigs-null-c600.rom
|
|
||||||
|
|
||||||
for f in "$gsplus" "$rom" "$sys_disk" "$data_disk" "$null_c600"; do
|
|
||||||
if [[ ! -f $f ]]; then
|
|
||||||
echo "missing: $f" >&2
|
|
||||||
if [[ $f == "$data_disk" ]]; then
|
|
||||||
echo "run 'make iigs-disk' to build it." >&2
|
|
||||||
else
|
|
||||||
echo "run ./toolchains/install.sh (support files should have been staged)." >&2
|
|
||||||
fi
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# GSplus writes back to disk images during the session; stage writable
|
|
||||||
# copies so repeated runs do not mutate the originals.
|
|
||||||
work=$(mktemp -d -t joeylib-iigs.XXXXXX)
|
|
||||||
|
|
||||||
cleanup() {
|
|
||||||
# Stash the post-run disk image for manual inspection.
|
|
||||||
if [[ -f $work/joey.2mg ]]; then
|
|
||||||
cp "$work/joey.2mg" "$repo/build/iigs/bin/joey-after-run.2mg"
|
|
||||||
echo "run-iigs: saved post-run disk -> $repo/build/iigs/bin/joey-after-run.2mg" >&2
|
|
||||||
fi
|
|
||||||
rm -rf "$work"
|
|
||||||
}
|
|
||||||
trap cleanup EXIT
|
|
||||||
|
|
||||||
cp "$sys_disk" "$work/boot.po"
|
|
||||||
cp "$data_disk" "$work/joey.2mg"
|
|
||||||
|
|
||||||
# Stage the null slot-6 PROM as c600.rom in the work dir. GSplus loads
|
|
||||||
# any file named c600.rom in its search path (cwd first) as the slot 6
|
|
||||||
# ROM card, overriding the built-in Disk II firmware. The staged image
|
|
||||||
# is 256 bytes with a leading RTS and no Pascal 1.1 firmware signature,
|
|
||||||
# so the IIgs boot ROM's slot scan skips slot 6 and the two empty 5.25
|
|
||||||
# drives never get probed. Source: toolchains/install.sh's
|
|
||||||
# install_support_iigs_null_c600.
|
|
||||||
cp "$null_c600" "$work/c600.rom"
|
|
||||||
|
|
||||||
cat <<EOF
|
|
||||||
GSplus launching GS/OS 6.0.4.
|
|
||||||
Once Finder is up:
|
|
||||||
1. Open the JOEYLIB disk on the desktop.
|
|
||||||
2. Double-click whichever demo you want to run.
|
|
||||||
(Tip: '$0 <example>' auto-launches via MAME instead.)
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# GSplus auto-creates config.kegs in its cwd on first run. cd into
|
|
||||||
# the scratch dir so that file lands there and gets cleaned up with
|
|
||||||
# the rest of the temp state, rather than polluting the directory
|
|
||||||
# the user invoked the script from.
|
|
||||||
cd "$work"
|
|
||||||
# GSplus accepts any config.kegs key as a CLI override via "-<key>
|
|
||||||
# <value>". g_limit_speed=2 caps emulation at the IIgs's native
|
|
||||||
# 2.8 MHz; without this override GSplus boots at 8 MHz and our
|
|
||||||
# performance characteristics don't match real hardware. The other
|
|
||||||
# settings (0=unlimited, 1=1.024 MHz, 3=8 MHz) are reachable through
|
|
||||||
# the in-emulator speed-cycle key if needed.
|
|
||||||
#
|
|
||||||
# No exec: let the bash EXIT trap fire so the scratch dir (and the
|
|
||||||
# config.kegs GSplus auto-creates) gets cleaned up.
|
|
||||||
"$gsplus" -rom "$rom" \
|
|
||||||
-s5d1 "$work/boot.po" \
|
|
||||||
-s5d2 "$work/joey.2mg" \
|
|
||||||
-g_limit_speed 2
|
|
||||||
|
|
@ -1,87 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# run-x68000.sh - launch a built X68000 example under MAME.
|
|
||||||
#
|
|
||||||
# scripts/run-x68000.sh staxi Space Taxi (binary + its DATA tree)
|
|
||||||
# scripts/run-x68000.sh uber any other built example, by name
|
|
||||||
# X68K_HEADLESS=1 scripts/run-x68000.sh staxi windowless + snapshots
|
|
||||||
#
|
|
||||||
# TWO FLOPPIES, the same split verify-x68000-golden.sh uses: Human68k plus
|
|
||||||
# AUTOEXEC on A:, and the program with its assets on B:. A 1232 KB floppy
|
|
||||||
# cannot hold Human68k and a ~285 KB .X together, and AUTOEXEC switches to
|
|
||||||
# B: first so the program's CWD is the disk its DATA/ tree is on.
|
|
||||||
#
|
|
||||||
# Needs toolchains/x68mame (HUMAN302.XDF + roms/) -- see the X68000 memory
|
|
||||||
# note for where to copy it from.
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
|
||||||
prog=${1:-staxi}
|
|
||||||
PROG=$(echo "$prog" | tr '[:lower:]' '[:upper:]')
|
|
||||||
bin=$repo/build/x68000/bin/$PROG.X
|
|
||||||
x68=${X68K_DIR:-$repo/toolchains/x68mame}
|
|
||||||
template=$x68/HUMAN302.XDF
|
|
||||||
|
|
||||||
# MAME: the x68k build this repo carries, unless one is installed or named.
|
|
||||||
MAME="${MAME:-$repo/toolchains/cache/mame-mame0264/x68k}"
|
|
||||||
if [[ ! -x $MAME ]]; then
|
|
||||||
MAME="$(command -v mame || true)"
|
|
||||||
fi
|
|
||||||
[[ -n ${MAME:-} && -x $MAME ]] || { echo "run-x68000: no MAME (set MAME=...)" >&2; exit 2; }
|
|
||||||
[[ -f $bin ]] || { echo "run-x68000: $bin not built (make -f make/x68000.mk)" >&2; exit 2; }
|
|
||||||
[[ -f $template ]] || { echo "run-x68000: missing $template (copy toolchains/x68mame in)" >&2; exit 2; }
|
|
||||||
|
|
||||||
work=$(mktemp -d -t joey-x68run.XXXXXX)
|
|
||||||
trap 'rm -rf "$work"' EXIT
|
|
||||||
|
|
||||||
printf 'B:\r\nB:\\%s.X\r\n\x1a' "$PROG" > "$work/AUTOEXEC.BAT"
|
|
||||||
cp "$template" "$work/boot.xdf"
|
|
||||||
cp "$template" "$work/data.xdf"
|
|
||||||
# Data disk = the template emptied (keeps the BPB and boot sector intact).
|
|
||||||
# `empty` and not a list of filenames: the stock disk carries ~900 KB of
|
|
||||||
# Human68k utilities in SUBDIRECTORIES (SYS, BIN, BASIC2, ...), and deleting
|
|
||||||
# only the root files leaves every one of those clusters allocated -- which is
|
|
||||||
# what made a 1232 KB floppy look like it had 330 KB to give.
|
|
||||||
python3 "$repo/tools/xdftool.py" empty "$work/data.xdf" >/dev/null
|
|
||||||
# Boot disk keeps only what it takes to boot and launch one .X.
|
|
||||||
for f in USKCG.SYS BEEP.SYS KEY.SYS STARTUP.ENV; do
|
|
||||||
python3 "$repo/tools/xdftool.py" delete "$work/boot.xdf" "$f" >/dev/null 2>&1 || true
|
|
||||||
done
|
|
||||||
for d in SYS HIS BIN BASIC2 ASK ETC; do
|
|
||||||
python3 "$repo/tools/xdftool.py" deltree "$work/boot.xdf" "$d" >/dev/null 2>&1 || true
|
|
||||||
done
|
|
||||||
python3 "$repo/tools/xdftool.py" add "$work/boot.xdf" "$work/AUTOEXEC.BAT" AUTOEXEC.BAT >/dev/null
|
|
||||||
python3 "$repo/tools/xdftool.py" add "$work/data.xdf" "$bin" "$PROG.X" >/dev/null
|
|
||||||
|
|
||||||
# The example's DATA tree, if it has one, at the same paths jlDataOpen builds
|
|
||||||
# ("DATA/" + the app's relative name). Human68k names are 8.3 upper case.
|
|
||||||
data_dir=$repo/build/x68000/bin/DATA
|
|
||||||
if [[ -d $data_dir ]]; then
|
|
||||||
while IFS= read -r f; do
|
|
||||||
rel=${f#"$data_dir"/}
|
|
||||||
python3 "$repo/tools/xdftool.py" add "$work/data.xdf" "$f" \
|
|
||||||
"DATA/$(echo "$rel" | tr '[:lower:]' '[:upper:]')" >/dev/null
|
|
||||||
done < <(find "$data_dir" -type f | sort)
|
|
||||||
fi
|
|
||||||
echo "run-x68000: $(python3 "$repo/tools/xdftool.py" free "$work/data.xdf")"
|
|
||||||
|
|
||||||
if [[ ${X68K_HEADLESS:-0} == 1 ]]; then
|
|
||||||
snap=${X68K_SNAP:-/tmp/run-x68000-snap}
|
|
||||||
rm -rf "$snap"; mkdir -p "$snap"
|
|
||||||
cat > "$work/snap.lua" <<'LUA'
|
|
||||||
local n = 0
|
|
||||||
emu.register_frame_done(function()
|
|
||||||
n = n + 1
|
|
||||||
if n % 1200 == 0 then manager.machine.video:snapshot() end
|
|
||||||
if n >= 24000 then manager.machine:exit() end
|
|
||||||
end)
|
|
||||||
LUA
|
|
||||||
"$MAME" x68000 -bios ipl10 -rompath "$x68/roms" \
|
|
||||||
-flop1 "$work/boot.xdf" -flop2 "$work/data.xdf" \
|
|
||||||
-video none -sound none -nothrottle \
|
|
||||||
-snapshot_directory "$snap" -autoboot_script "$work/snap.lua" </dev/null >/dev/null 2>&1 || true
|
|
||||||
echo "run-x68000: snapshots -> $snap"
|
|
||||||
else
|
|
||||||
echo "run-x68000: booting Human68k, then B:\\$PROG.X. Quit MAME to end."
|
|
||||||
"$MAME" x68000 -bios ipl10 -rompath "$x68/roms" \
|
|
||||||
-flop1 "$work/boot.xdf" -flop2 "$work/data.xdf" -skip_gameinfo
|
|
||||||
fi
|
|
||||||
|
|
@ -40,14 +40,17 @@
|
||||||
# example binary name in build/amiga/bin, default Audio
|
# example binary name in build/amiga/bin, default Audio
|
||||||
# Env: AMIGA_AUDIO_KEEP set to keep the work dir and the WAV
|
# Env: AMIGA_AUDIO_KEEP set to keep the work dir and the WAV
|
||||||
# Requires: fs-uae, Xvfb, xdotool, parec/pactl (PulseAudio or PipeWire),
|
# Requires: fs-uae, Xvfb, xdotool, parec/pactl (PulseAudio or PipeWire),
|
||||||
# python3, toolchains/emulators/support/kickstart.rom,
|
# python3, the Kickstart (install.sh install emulator:amiga),
|
||||||
# build/amiga/bin/<example>
|
# build/amiga/bin/<example>
|
||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
|
|
||||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
source "$repo/scripts/lib/joeyEnv.sh"
|
||||||
|
joeyNeed FSUAE fsuae
|
||||||
|
joeyNeed KICKSTART_ROM kickstart
|
||||||
prog=${1:-Audio}
|
prog=${1:-Audio}
|
||||||
binDir=$repo/build/amiga/bin
|
binDir=$repo/build/amiga/bin
|
||||||
kickstart=$repo/toolchains/emulators/support/kickstart.rom
|
kickstart=$KICKSTART_ROM
|
||||||
grader=$repo/tools/gradeAudioPhases.py
|
grader=$repo/tools/gradeAudioPhases.py
|
||||||
pulseLib=$repo/scripts/pulseRecordLib.sh
|
pulseLib=$repo/scripts/pulseRecordLib.sh
|
||||||
label=verify-amiga-audio
|
label=verify-amiga-audio
|
||||||
|
|
@ -57,7 +60,7 @@ label=verify-amiga-audio
|
||||||
[ -f "$pulseLib" ] || { echo "$label: missing $pulseLib" >&2; exit 2; }
|
[ -f "$pulseLib" ] || { echo "$label: missing $pulseLib" >&2; exit 2; }
|
||||||
# shellcheck source=scripts/pulseRecordLib.sh
|
# shellcheck source=scripts/pulseRecordLib.sh
|
||||||
. "$pulseLib"
|
. "$pulseLib"
|
||||||
for tool in fs-uae Xvfb xdotool parec pactl python3; do
|
for tool in Xvfb xdotool parec pactl python3; do
|
||||||
command -v "$tool" >/dev/null || { echo "$label: $tool not installed" >&2; exit 2; }
|
command -v "$tool" >/dev/null || { echo "$label: $tool not installed" >&2; exit 2; }
|
||||||
done
|
done
|
||||||
pulseEnsureServer || { echo "$label: no PulseAudio/PipeWire server to record from (tried XDG_RUNTIME_DIR and /run/user/$(id -u)/pulse/native)" >&2; exit 2; }
|
pulseEnsureServer || { echo "$label: no PulseAudio/PipeWire server to record from (tried XDG_RUNTIME_DIR and /run/user/$(id -u)/pulse/native)" >&2; exit 2; }
|
||||||
|
|
@ -80,7 +83,7 @@ parecPid=""
|
||||||
|
|
||||||
cleanup() {
|
cleanup() {
|
||||||
[ -n "$parecPid" ] && kill "$parecPid" 2>/dev/null
|
[ -n "$parecPid" ] && kill "$parecPid" 2>/dev/null
|
||||||
[ -n "$fsPid" ] && kill "$fsPid" 2>/dev/null
|
joeyStop "$fsPid"
|
||||||
[ -n "$xvfbPid" ] && kill "$xvfbPid" 2>/dev/null
|
[ -n "$xvfbPid" ] && kill "$xvfbPid" 2>/dev/null
|
||||||
# Always unload the sink: leaving null sinks behind on the user's
|
# Always unload the sink: leaving null sinks behind on the user's
|
||||||
# audio server is the one side effect this gate could have.
|
# audio server is the one side effect this gate could have.
|
||||||
|
|
@ -94,7 +97,7 @@ cleanup() {
|
||||||
}
|
}
|
||||||
trap cleanup EXIT
|
trap cleanup EXIT
|
||||||
|
|
||||||
# Stage the Amiga volume exactly as run-amiga-headless.sh does.
|
# Stage the Amiga volume the way joey-run does.
|
||||||
mkdir -p "$work/hd/s"
|
mkdir -p "$work/hd/s"
|
||||||
if [ -n "$progDir" ]; then
|
if [ -n "$progDir" ]; then
|
||||||
cp -r "$progDir/." "$work/hd/"
|
cp -r "$progDir/." "$work/hd/"
|
||||||
|
|
@ -114,7 +117,7 @@ sleep 1
|
||||||
|
|
||||||
# --fast_memory=0 --slow_memory=0 keeps this a bare A500, the port's floor.
|
# --fast_memory=0 --slow_memory=0 keeps this a bare A500, the port's floor.
|
||||||
DISPLAY="$display" PULSE_SINK="$sinkName" ALSOFT_DRIVERS=pulse SDL_AUDIODRIVER=pulse \
|
DISPLAY="$display" PULSE_SINK="$sinkName" ALSOFT_DRIVERS=pulse SDL_AUDIODRIVER=pulse \
|
||||||
fs-uae \
|
"$FSUAE" \
|
||||||
--amiga_model=A500 \
|
--amiga_model=A500 \
|
||||||
--hard_drive_0="$work/hd" \
|
--hard_drive_0="$work/hd" \
|
||||||
--hard_drive_0_label=JOEYLIB \
|
--hard_drive_0_label=JOEYLIB \
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,15 @@
|
||||||
#
|
#
|
||||||
# Usage: scripts/verify-amiga-input.sh
|
# Usage: scripts/verify-amiga-input.sh
|
||||||
# Requires: fs-uae, Xvfb, xdotool; build/amiga/bin/Keys (make amiga);
|
# Requires: fs-uae, Xvfb, xdotool; build/amiga/bin/Keys (make amiga);
|
||||||
# toolchains/emulators/support/kickstart.rom.
|
# the Kickstart (install.sh install emulator:amiga).
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
source "$repo/scripts/lib/joeyEnv.sh"
|
||||||
|
joeyNeed FSUAE fsuae
|
||||||
|
joeyNeed KICKSTART_ROM kickstart
|
||||||
bin_dir=$repo/build/amiga/bin
|
bin_dir=$repo/build/amiga/bin
|
||||||
kickstart=$repo/toolchains/emulators/support/kickstart.rom
|
kickstart=$KICKSTART_ROM
|
||||||
|
|
||||||
target="192.168.1.10:6510"
|
target="192.168.1.10:6510"
|
||||||
|
|
||||||
|
|
@ -25,7 +28,7 @@ target="192.168.1.10:6510"
|
||||||
work=$(mktemp -d -t joeylib-amiga-input.XXXXXX)
|
work=$(mktemp -d -t joeylib-amiga-input.XXXXXX)
|
||||||
display_num=$((RANDOM % 500 + 300))
|
display_num=$((RANDOM % 500 + 300))
|
||||||
cleanup() {
|
cleanup() {
|
||||||
[ -n "${fsuae_pid:-}" ] && kill "$fsuae_pid" 2>/dev/null
|
joeyStop "${fsuae_pid:-}"
|
||||||
[ -n "${xvfb_pid:-}" ] && kill "$xvfb_pid" 2>/dev/null
|
[ -n "${xvfb_pid:-}" ] && kill "$xvfb_pid" 2>/dev/null
|
||||||
rm -rf "$work"
|
rm -rf "$work"
|
||||||
}
|
}
|
||||||
|
|
@ -45,7 +48,7 @@ sleep 1
|
||||||
# XSendEvent routes are ignored by SDL. A fullscreen SDL window takes
|
# XSendEvent routes are ignored by SDL. A fullscreen SDL window takes
|
||||||
# focus itself, after which plain XTEST typing (xdotool without
|
# focus itself, after which plain XTEST typing (xdotool without
|
||||||
# --window) lands in the emulated Amiga.
|
# --window) lands in the emulated Amiga.
|
||||||
DISPLAY=":$display_num" fs-uae \
|
DISPLAY=":$display_num" "$FSUAE" \
|
||||||
--amiga_model=A500 \
|
--amiga_model=A500 \
|
||||||
--fast_memory=2048 \
|
--fast_memory=2048 \
|
||||||
--kickstart_file="$kickstart" \
|
--kickstart_file="$kickstart" \
|
||||||
|
|
|
||||||
|
|
@ -46,14 +46,17 @@
|
||||||
# example PRG base name in build/atarist/bin, default AUDIO
|
# example PRG base name in build/atarist/bin, default AUDIO
|
||||||
# Env: ST_AUDIO_TAPS how many held SPACE presses (default 6)
|
# Env: ST_AUDIO_TAPS how many held SPACE presses (default 6)
|
||||||
# ST_AUDIO_KEEP set to keep the work dir for inspection
|
# ST_AUDIO_KEEP set to keep the work dir for inspection
|
||||||
# Requires: hatari, toolchains/emulators/support/emutos-512k.img,
|
# Requires: ./install.sh install emulator:atarist (Hatari, EmuTOS),
|
||||||
# build/atarist/bin/<example>.PRG
|
# build/atarist/bin/<example>.PRG
|
||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
|
|
||||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
source "$repo/scripts/lib/joeyEnv.sh"
|
||||||
|
joeyNeed HATARI hatari
|
||||||
|
joeyNeed EMUTOS_IMG emutos
|
||||||
prog=${1:-AUDIO}
|
prog=${1:-AUDIO}
|
||||||
taps=${ST_AUDIO_TAPS:-6}
|
taps=${ST_AUDIO_TAPS:-6}
|
||||||
tos=$repo/toolchains/emulators/support/emutos-512k.img
|
tos=$EMUTOS_IMG
|
||||||
binDir=$repo/build/atarist/bin
|
binDir=$repo/build/atarist/bin
|
||||||
|
|
||||||
# Space Taxi ships as its own folder; everything else is a bare PRG.
|
# Space Taxi ships as its own folder; everything else is a bare PRG.
|
||||||
|
|
@ -90,7 +93,7 @@ trap cleanup EXIT INT TERM
|
||||||
#
|
#
|
||||||
# The psg_write trace runs ~800 KB/s, so it is filtered as it is produced
|
# The psg_write trace runs ~800 KB/s, so it is filtered as it is produced
|
||||||
# -- keeping the raw stream would put hundreds of MB in the work dir.
|
# -- keeping the raw stream would put hundreds of MB in the work dir.
|
||||||
SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy hatari \
|
SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy "$HATARI" \
|
||||||
--tos "$tos" \
|
--tos "$tos" \
|
||||||
--harddrive "$driveDir" \
|
--harddrive "$driveDir" \
|
||||||
--gemdos-drive C \
|
--gemdos-drive C \
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,11 @@
|
||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
|
|
||||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
source "$repo/scripts/lib/joeyEnv.sh"
|
||||||
|
joeyNeed HATARI hatari
|
||||||
|
joeyNeed EMUTOS_IMG emutos
|
||||||
bin_dir=$repo/build/atarist/bin
|
bin_dir=$repo/build/atarist/bin
|
||||||
tos=$repo/toolchains/emulators/support/emutos-512k.img
|
tos=$EMUTOS_IMG
|
||||||
|
|
||||||
target="192.168.1.10:6510"
|
target="192.168.1.10:6510"
|
||||||
|
|
||||||
|
|
@ -29,13 +32,13 @@ target="192.168.1.10:6510"
|
||||||
|
|
||||||
work=$(mktemp -d -t joeylib-st-input.XXXXXX)
|
work=$(mktemp -d -t joeylib-st-input.XXXXXX)
|
||||||
cleanup() {
|
cleanup() {
|
||||||
[ -n "${hatari_pid:-}" ] && kill "$hatari_pid" 2>/dev/null
|
joeyStop "${hatari_pid:-}"
|
||||||
rm -rf "$work"
|
rm -rf "$work"
|
||||||
}
|
}
|
||||||
trap cleanup EXIT
|
trap cleanup EXIT
|
||||||
|
|
||||||
cd "$work"
|
cd "$work"
|
||||||
SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy hatari \
|
SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy "$HATARI" \
|
||||||
--tos "$tos" \
|
--tos "$tos" \
|
||||||
--harddrive "$bin_dir" \
|
--harddrive "$bin_dir" \
|
||||||
--gemdos-drive C \
|
--gemdos-drive C \
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
# verify-dos-audio.sh - DOS audio gate, graded on recorded sound.
|
# verify-dos-audio.sh - DOS audio gate, graded on recorded sound.
|
||||||
#
|
#
|
||||||
# The DOS HAL drives a Sound Blaster Pro/SB16 with 8237 auto-init DMA fed
|
# The DOS HAL drives a Sound Blaster Pro/SB16 with 8237 auto-init DMA fed
|
||||||
# by libxmp-lite (src/dos/audio.c). DOSBox 0.74 exposes none of that to a
|
# by libxmp-lite (src/dos/audio.c). DOSBox exposes none of that to a
|
||||||
# script -- no register trace, no debugger this harness can drive -- so
|
# script -- no register trace, no debugger this harness can drive -- so
|
||||||
# unlike the IIgs and ST gates (which read the DOC and the YM2149) this
|
# unlike the IIgs and ST gates (which read the DOC and the YM2149) this
|
||||||
# one records what actually came out and grades it. The Amiga gate works
|
# one records what actually came out and grades it. The Amiga gate works
|
||||||
|
|
@ -28,7 +28,8 @@
|
||||||
# unambiguous. This also covers the SMIX overlay specifically -- SFX on
|
# unambiguous. This also covers the SMIX overlay specifically -- SFX on
|
||||||
# DOS mix through libxmp-lite's smix rather than a separate channel, so
|
# DOS mix through libxmp-lite's smix rather than a separate channel, so
|
||||||
# "SFX audible while the module player is loaded but paused" is exactly
|
# "SFX audible while the module player is loaded but paused" is exactly
|
||||||
# the path that can break. Measured: music ~3290, paused ~113, sfx ~1341.
|
# the path that can break. Measured under DOSBox Staging 0.83 (whose mixer
|
||||||
|
# runs quieter than the old 0.74's): music ~1220, paused ~17, sfx ~659.
|
||||||
#
|
#
|
||||||
# Keys are HELD (keydown, pause, keyup), not tapped: the demo tests
|
# Keys are HELD (keydown, pause, keyup), not tapped: the demo tests
|
||||||
# jlKeyPressed, an edge predicate polled once a frame, and a make/break
|
# jlKeyPressed, an edge predicate polled once a frame, and a make/break
|
||||||
|
|
@ -38,11 +39,13 @@
|
||||||
# example EXE base name in build/dos/bin, default AUDIO
|
# example EXE base name in build/dos/bin, default AUDIO
|
||||||
# Env: DOS_AUDIO_KEEP set to keep the work dir and the WAV
|
# Env: DOS_AUDIO_KEEP set to keep the work dir and the WAV
|
||||||
# CYCLES override the conf's 386SX-16 cpu cap
|
# CYCLES override the conf's 386SX-16 cpu cap
|
||||||
# Requires: dosbox, Xvfb, xdotool, parec/pactl (PulseAudio or PipeWire),
|
# Requires: DOSBox Staging (install.sh install emulator:dos), Xvfb, xdotool, parec/pactl,
|
||||||
# python3, build/dos/bin/<example>.EXE
|
# python3, build/dos/bin/<example>.EXE
|
||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
|
|
||||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
source "$repo/scripts/lib/joeyEnv.sh"
|
||||||
|
joeyNeed DOSBOX dosbox
|
||||||
prog=${1:-AUDIO}
|
prog=${1:-AUDIO}
|
||||||
binDir=$repo/build/dos/bin
|
binDir=$repo/build/dos/bin
|
||||||
conf=$repo/scripts/dosbox-386sx16.conf
|
conf=$repo/scripts/dosbox-386sx16.conf
|
||||||
|
|
@ -55,7 +58,7 @@ label=verify-dos-audio
|
||||||
# shellcheck source=scripts/pulseRecordLib.sh
|
# shellcheck source=scripts/pulseRecordLib.sh
|
||||||
. "$pulseLib"
|
. "$pulseLib"
|
||||||
[ -f "$conf" ] || { echo "$label: missing $conf" >&2; exit 2; }
|
[ -f "$conf" ] || { echo "$label: missing $conf" >&2; exit 2; }
|
||||||
for tool in dosbox Xvfb xdotool parec pactl python3; do
|
for tool in Xvfb xdotool parec pactl python3; do
|
||||||
command -v "$tool" >/dev/null || { echo "$label: $tool not installed" >&2; exit 2; }
|
command -v "$tool" >/dev/null || { echo "$label: $tool not installed" >&2; exit 2; }
|
||||||
done
|
done
|
||||||
pulseEnsureServer || { echo "$label: no PulseAudio/PipeWire server to record from (tried XDG_RUNTIME_DIR and /run/user/$(id -u)/pulse/native)" >&2; exit 2; }
|
pulseEnsureServer || { echo "$label: no PulseAudio/PipeWire server to record from (tried XDG_RUNTIME_DIR and /run/user/$(id -u)/pulse/native)" >&2; exit 2; }
|
||||||
|
|
@ -79,7 +82,7 @@ parecPid=""
|
||||||
|
|
||||||
cleanup() {
|
cleanup() {
|
||||||
[ -n "$parecPid" ] && kill "$parecPid" 2>/dev/null
|
[ -n "$parecPid" ] && kill "$parecPid" 2>/dev/null
|
||||||
[ -n "$dosPid" ] && kill "$dosPid" 2>/dev/null
|
joeyStop "$dosPid"
|
||||||
[ -n "$xvfbPid" ] && kill "$xvfbPid" 2>/dev/null
|
[ -n "$xvfbPid" ] && kill "$xvfbPid" 2>/dev/null
|
||||||
# Always unload the sink: a leaked null sink on the user's audio
|
# Always unload the sink: a leaked null sink on the user's audio
|
||||||
# server is the one side effect this gate could have.
|
# server is the one side effect this gate could have.
|
||||||
|
|
@ -101,16 +104,16 @@ Xvfb "$display" -screen 0 1024x768x24 -nolisten tcp >/dev/null 2>&1 &
|
||||||
xvfbPid=$!
|
xvfbPid=$!
|
||||||
sleep 1
|
sleep 1
|
||||||
|
|
||||||
dosArgs=(-conf "$conf")
|
dosArgs=("${JOEY_DOSBOX_ISOLATE[@]}" --conf "$conf")
|
||||||
[ -n "${CYCLES:-}" ] && dosArgs+=(-set "cpu cycles=$CYCLES")
|
[ -n "${CYCLES:-}" ] && dosArgs+=(--set "cpu_cycles=$CYCLES" --set "cpu_cycles_protected=$CYCLES")
|
||||||
dosArgs+=(
|
dosArgs+=(
|
||||||
-c "MOUNT C $mountDir"
|
-c "MOUNT C $mountDir"
|
||||||
-c "C:"
|
-c "C:"
|
||||||
-c "$prog.EXE"
|
-c "$prog.EXE"
|
||||||
)
|
)
|
||||||
|
|
||||||
DISPLAY="$display" PULSE_SINK="$sinkName" SDL_AUDIODRIVER=pulse \
|
XDG_CONFIG_HOME="$work/xdg" DISPLAY="$display" PULSE_SINK="$sinkName" SDL_AUDIODRIVER=pulse \
|
||||||
dosbox "${dosArgs[@]}" > "$work/dosbox.log" 2>&1 &
|
"$DOSBOX" "${dosArgs[@]}" > "$work/dosbox.log" 2>&1 &
|
||||||
dosPid=$!
|
dosPid=$!
|
||||||
|
|
||||||
sleep 12
|
sleep 12
|
||||||
|
|
@ -120,7 +123,7 @@ if ! kill -0 "$dosPid" 2>/dev/null; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
win=$(DISPLAY="$display" xdotool search --name -- "DOSBox" | head -1)
|
win=$(joeyFindWindow "$display" "$dosPid")
|
||||||
if [ -z "$win" ]; then
|
if [ -z "$win" ]; then
|
||||||
echo "$label: FAIL - no DOSBox window to send keys to" >&2
|
echo "$label: FAIL - no DOSBox window to send keys to" >&2
|
||||||
exit 1
|
exit 1
|
||||||
|
|
@ -166,13 +169,13 @@ parecPid=""
|
||||||
|
|
||||||
[ -s "$work/out.wav" ] || { echo "$label: FAIL - recorded no audio at all" >&2; exit 1; }
|
[ -s "$work/out.wav" ] || { echo "$label: FAIL - recorded no audio at all" >&2; exit 1; }
|
||||||
|
|
||||||
# Thresholds sit far from the measured values (music ~3290, paused ~113,
|
# Thresholds sit at about half the measured levels (music ~1220, paused
|
||||||
# sfx ~1341) so ordinary mixing jitter cannot flip the verdict.
|
# ~17, sfx ~659) so ordinary mixing jitter cannot flip the verdict.
|
||||||
python3 "$grader" "$label" "$work/out.wav" \
|
python3 "$grader" "$label" "$work/out.wav" \
|
||||||
"music:1:5:1500:" \
|
"music:1:5:600:" \
|
||||||
"paused:8:13::600" \
|
"paused:8:13::200" \
|
||||||
"sfx:16:22:700:" \
|
"sfx:16:22:330:" \
|
||||||
"resumed:27:32:1500:"
|
"resumed:27:32:600:"
|
||||||
status=$?
|
status=$?
|
||||||
[ -n "${DOS_AUDIO_KEEP:-}" ] && cp "$work/out.wav" "$repo/build/dos/headless-audio.wav" 2>/dev/null
|
[ -n "${DOS_AUDIO_KEEP:-}" ] && cp "$work/out.wav" "$repo/build/dos/headless-audio.wav" 2>/dev/null
|
||||||
exit $status
|
exit $status
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue