#!/usr/bin/env bash # # sprinter-cc — wrapper around SDCC for building Sprinter .EXE files. # # Usage: # sprinter-cc -o foo.exe foo.c [more.c ...] [options] # # Options: # -o NAME output executable name (required) # --crt0=TYPE crt0 variant: default | minimal | banked (default: default) # -I PATH additional include path (repeatable) # -L 0xADDR code load address (default: 0x4100) # -E 0xADDR entry address (default: same as -L) # -S 0xADDR stack address (default: 0xBFFE) # --code-loc 0xN forwarded to SDCC --code-loc (default: 0x4100) # --data-loc 0xN forwarded to SDCC --data-loc (default: 0x8000) # -Wl FLAG extra linker flag (repeatable) # --bank N=FILE.c compile FILE.c as bank N; repeatable; pulls crt0_banked # automatically and adds -Wl-b_BANKN=0x{N}C000 # --mkexe FLAG extra mkexe flag (repeatable; e.g. --mkexe -p --mkexe 0) # -v verbose (echo every command) # -h, --help show this help # # Example: # sprinter-cc -o hello.exe hello.c # sprinter-cc -o app.exe main.c --bank 1=engine.c # sprinter-cc -o tiny.exe tiny.c --crt0=minimal set -eo pipefail # ------- Locate the toolchain ------------------------------------------------ SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" PROJ_ROOT="$( cd "$SCRIPT_DIR/.." && pwd )" SDCC_BIN="$PROJ_ROOT/third_party/sdcc/bin" SDCC="$SDCC_BIN/sdcc" SDASZ80="$SDCC_BIN/sdasz80" MKEXE="$PROJ_ROOT/toolchain/mkexe/mkexe" CHECK_BANKS="$PROJ_ROOT/toolchain/check_banks.py" RUNTIME="$PROJ_ROOT/runtime" LIB_DIR="$PROJ_ROOT/lib" INC_DIR="$PROJ_ROOT/libc/include" # ------- Defaults ------------------------------------------------------------ OUT="" CRT0_TYPE="default" CODE_LOC="0x8100" # TINY mode default — everything in W2 (single page) DATA_LOC="0" # 0 = let linker auto-place after code areas LOAD_ADDR="" ENTRY_ADDR="" STACK_ADDR="0xBFFE" VERBOSE=0 USER_INCS=() SOURCES=() LD_EXTRA=() MKEXE_EXTRA=() BANK_SPECS=() # entries like "1=engine.c" # ------- Parse args ---------------------------------------------------------- usage() { sed -n '3,28p' "$0" | sed 's/^# \{0,1\}//' exit "${1:-0}" } while [[ $# -gt 0 ]]; do case "$1" in -o) OUT="$2"; shift 2;; --crt0=*) CRT0_TYPE="${1#*=}"; shift;; -I) USER_INCS+=("-I" "$2"); shift 2;; -L) LOAD_ADDR="$2"; shift 2;; -E) ENTRY_ADDR="$2"; shift 2;; -S) STACK_ADDR="$2"; shift 2;; --code-loc) CODE_LOC="$2"; shift 2;; --data-loc) DATA_LOC="$2"; shift 2;; -Wl) LD_EXTRA+=("$2"); shift 2;; --bank) BANK_SPECS+=("$2"); shift 2;; --mkexe) MKEXE_EXTRA+=("$2"); shift 2;; -v) VERBOSE=1; shift;; -h|--help) usage 0;; -*) echo "sprinter-cc: unknown option: $1" >&2; usage 1;; *) SOURCES+=("$1"); shift;; esac done [[ -z "$OUT" ]] && { echo "sprinter-cc: -o NAME is required" >&2; exit 1; } [[ ${#SOURCES[@]} -eq 0 ]] && { echo "sprinter-cc: no input files" >&2; exit 1; } LOAD_ADDR="${LOAD_ADDR:-$CODE_LOC}" ENTRY_ADDR="${ENTRY_ADDR:-$LOAD_ADDR}" # Pick crt0 source. case "$CRT0_TYPE" in default) CRT0_SRC="$RUNTIME/crt0.s"; ;; minimal) CRT0_SRC="$RUNTIME/crt0_minimal.s";; banked) CRT0_SRC="$RUNTIME/crt0_banked.s"; ;; *) echo "sprinter-cc: bad --crt0 type: $CRT0_TYPE" >&2; exit 1;; esac # Banked builds always need crt0_banked, regardless of what the user asked. if [[ ${#BANK_SPECS[@]} -gt 0 ]] && [[ "$CRT0_TYPE" != "banked" ]]; then [[ $VERBOSE -eq 1 ]] && echo " --bank present → switching to crt0_banked" CRT0_SRC="$RUNTIME/crt0_banked.s" fi # ------- Build in a per-output workdir --------------------------------------- WORK="$(dirname "$OUT")/.sprinter-cc-$(basename "$OUT" .exe)" mkdir -p "$WORK" run() { [[ $VERBOSE -eq 1 ]] && echo " $*" "$@" } # 1. crt0 CRT0_REL="$WORK/$(basename "$CRT0_SRC" .s).rel" run "$SDASZ80" -o "$CRT0_REL" "$CRT0_SRC" # 2. user sources → .rel (HOME) USER_RELS=() CC_FLAGS=(-mz80 --no-std-crt0 --std-c99 --opt-code-size -I "$INC_DIR" "${USER_INCS[@]}") for src in "${SOURCES[@]}"; do rel="$WORK/$(basename "$src" .c).rel" run "$SDCC" "${CC_FLAGS[@]}" -c -o "$rel" "$src" USER_RELS+=("$rel") done # 3. bank sources BANK_RELS=() BANK_LD_FLAGS=() for spec in "${BANK_SPECS[@]}"; do bank_n="${spec%%=*}" bank_src="${spec#*=}" rel="$WORK/bank${bank_n}_$(basename "$bank_src" .c).rel" run "$SDCC" "${CC_FLAGS[@]}" \ --codeseg "BANK${bank_n}" --constseg "BANK${bank_n}" --dataseg "BANK${bank_n}" \ -c -o "$rel" "$bank_src" BANK_RELS+=("$rel") # virtual address: bank_n in upper byte, 0xC000 low addr=$(printf "0x%X" $(( (bank_n << 16) | 0xC000 ))) BANK_LD_FLAGS+=("-Wl-b_BANK${bank_n}=${addr}") done # 4. link → .ihx IHX="$WORK/$(basename "$OUT" .exe).ihx" LINK_FLAGS=(-mz80 --no-std-crt0 --std-c99 --opt-code-size --code-loc "$CODE_LOC" --data-loc "$DATA_LOC") LINK_FLAGS+=("${BANK_LD_FLAGS[@]}") for f in "${LD_EXTRA[@]}"; do LINK_FLAGS+=("$f"); done # libsprinter.lib via -l/-L (sdcc passes -lsprinter through to sdldz80) run "$SDCC" "${LINK_FLAGS[@]}" -o "$IHX" \ "$CRT0_REL" "${USER_RELS[@]}" "${BANK_RELS[@]}" \ "-L$LIB_DIR" "-lsprinter" # Quick bank-size check (only meaningful if there are banks). if [[ ${#BANK_SPECS[@]} -gt 0 ]] && [[ -f "${IHX%.ihx}.map" ]]; then python3 "$CHECK_BANKS" "${IHX%.ihx}.map" || true fi # 5. mkexe → .exe MK_FLAGS=(-L "$LOAD_ADDR" -E "$ENTRY_ADDR" -S "$STACK_ADDR" -o "$OUT" "$IHX") [[ $VERBOSE -eq 1 ]] && MK_FLAGS=(-v "${MK_FLAGS[@]}") for f in "${MKEXE_EXTRA[@]}"; do MK_FLAGS=("$f" "${MK_FLAGS[@]}"); done run "$MKEXE" "${MK_FLAGS[@]}" echo "sprinter-cc: wrote $OUT"