8bitworkshop/presets/astrocade-bios/bios.c

475 lines
9.4 KiB
C

/**
AstroLibre
An open source Bally Astrocade BIOS implementation in C
by Steven Hugg (@sehugg) for 8bitworkshop.com
To the extent possible under law, the author(s) have
dedicated all copyright and related and neighboring
rights to this software to the public domain worldwide.
This software is distributed without any warranty.
See: http://creativecommons.org/publicdomain/zero/1.0/
**/
//#resource "astrocade.inc"
//#link "biosasm.s"
// demo code
//#link "test1.s"
// music processor
//#link "bmusic.c"
// input
//#link "input.c"
// graphics
//#link "gfx.c"
#include <string.h>
#include "bios.h"
// uncomment to make code better, but slower compile
#pragma opt_code_speed
// start @ $4FCE
volatile word MUZPC; // music PC
volatile word MUZSP; // music SP
volatile byte PVOLAB; // channels A and B volume
volatile byte PVOLMC; // channel C volume and noise mask
volatile byte VOICES; // voice smask
volatile byte CT[8]; // counter timers
// for SENTRY
volatile byte CNT;
volatile byte SEMI4S;
volatile byte OPOT[4];
volatile byte KEYSEX;
volatile byte OSW[4];
volatile word COLLST;
volatile byte DURAT; // note duration
volatile byte TMR60; // 1/60 sec timer
volatile byte TIMOUT; // blackout timer
volatile byte GTSECS; // seconds timer
volatile byte GTMINS; // minutes timer
unsigned long RANSHT; // RNG
byte NUMPLY; // # players
byte ENDSCR[3]; // end score
byte MRLOCK; // magic register lock out
byte GAMSTB; // game status
byte PRIOR; // music protect
byte SENFLG; // sentry control
byte* UMARGT; // user mask table (-64 bytes)
word* USERTB; // user routine table (-128 bytes)
// from bmusic.c
extern void music_update(void);
// INTERRUPT HANDLER
//#define USE_ALT_REGS
void hw_interrupt()
#ifdef USE_ALT_REGS
__naked {
__asm__("ex af,af'");
__asm__("exx");
#else
__interrupt {
#endif
CT[0]++;
CT[1]++;
CT[2]++;
CT[3]++;
if (++TMR60 == 60) {
TMR60 = 0;
++TIMOUT;
KEYSEX |= 0x80; // notify SENTRY
if (++GTSECS == 60) {
GTMINS++;
}
}
// TODO?
if (VOICES) {
if (DURAT) {
DURAT--;
} else {
music_update();
}
}
#ifdef USE_ALT_REGS
__asm__("exx");
__asm__("ex af,af'");
__asm__("ei");
__asm__("reti");
#endif
}
void add_counters(byte mask, byte delta) {
byte i = 0;
while (mask) {
if (mask & 1) {
if (CT[i]) {
// notify SENTRY if counters go to 0
if (!(CT[i] += delta)) {
SENFLG |= 1<<i;
}
}
}
mask >>= 1;
i++;
}
}
void STIMER() {
}
void CTIMER() {
}
void TIMEZ() {
// updates game timer, blackout timer, and music processor
}
void TIMEY() {
add_counters(0x0f, -1);
}
void TIMEX() {
}
void DECCTS(ContextBlock *ctx) {
add_counters(_C, -1);
}
// INTERPRETER
void INTPC(ContextBlock *ctx) {
while (ctx->params[0] > 2) { // 0,2 = exit
SYSCALL(ctx);
}
ctx->params++; // skip EXIT opcode
}
// never called, hopefully
void EXIT(ContextBlock *ctx) {
ctx;
}
// jumps to HL
void RCALL(ContextBlock *ctx) {
((Routine*)_HL)();
}
// start interpreting at HL
void MCALL(ContextBlock *ctx) {
ctx->params = (byte*)_HL;
while (ctx->params[0] != 8) { // 8 = MRET
SYSCALL(ctx);
}
}
// exit MCALL loop
void MRET(ContextBlock *ctx) {
ctx; // TODO
}
void NOOP(ContextBlock *ctx) {
ctx;
}
// jump within MCALL
void MJUMP(ContextBlock *ctx) {
ctx->params = (byte*) _HL; // TODO?
}
void suckParams(ContextBlock *ctx, byte argmask) {
byte* dest = (byte*) ctx;
byte* src = ctx->params;
if (argmask & REG_IX) {
_IXL = *src++;
_IXH = *src++;
}
if (argmask & REG_E)
_E = *src++;
if (argmask & REG_D)
_D = *src++;
if (argmask & REG_C)
_C = *src++;
if (argmask & REG_B)
_B = *src++;
if (argmask & REG_A)
_A = *src++;
if (argmask & REG_HL) {
_L = *src++;
_H = *src++;
}
ctx->params = src;
}
void SUCK(ContextBlock* ctx) {
suckParams(ctx, _B);
}
const void* const actint_vec = &hw_interrupt;
void ACTINT(ContextBlock *ctx) {
ctx;
__asm
LD BC,#(_actint_vec) ; upper 8 bits of address
LD A,B
LD I,A
IM 2 ; mode 2
EI ; enable interrupts
__endasm;
hw_inlin = 200;
hw_infbk = (byte) &actint_vec;
hw_inmod = 0x8;
TIMEZ();
TIMEY();
}
// Outputs D to port 0A, B to port 09, A to port 0E.
void SETOUT(ContextBlock *ctx) {
hw_verbl = _D;
hw_horcb = _B;
hw_inmod = _A;
}
// set entire palette at once (8 bytes to port 0xb)
// bytes in array should be in reverse
void set_palette(byte palette[8]) __z88dk_fastcall;
// sets color palettes from (HL)
void COLSET(ContextBlock *ctx) {
set_palette((byte*)_HL);
}
// Stores A in BC bytes starting at location DE.
void FILL(ContextBlock *ctx) {
byte* dest = (byte*) _DE;
word count = _BC;
byte val = _A;
memset(dest, val, count);
}
void wait_vsync() {
byte x = TMR60;
while (x == TMR60) ; // wait until timer/60 changes
}
void PAWS(ContextBlock *ctx) {
while (_B--) {
wait_vsync();
}
}
// MATH
void MOVE(ContextBlock *ctx) {
memcpy((byte*)_DE, (const byte*)_HL, _BC);
}
word bcdadd8(byte a, byte b, byte c);
void BCDADD(ContextBlock *ctx) {
byte* dest = (byte*) _DE;
const byte* src = (const byte*) _HL;
byte n = _B;
byte carry = 0;
while (n--) {
byte a = *src++;
byte b = *dest;
word r = bcdadd8(a,b,carry);
carry = (r>>8);
*dest++ = r;
}
}
void RANGED(ContextBlock *ctx) {
/* Algorithm "xor" from p. 4 of Marsaglia, "Xorshift RNGs" */
RANSHT ^= RANSHT << 13;
RANSHT ^= RANSHT >> 17;
RANSHT ^= RANSHT << 5;
if (_A == 0) {
_A = (byte)RANSHT;
} else {
_A = (byte)(RANSHT % _A);
}
}
void INDEXB(ContextBlock *ctx) {
word addr = _HL + _A;
_HL = addr;
_A = *((byte*)addr);
}
void INDEXW(ContextBlock *ctx) {
word addr = _HL + _A*2;
_HL = addr;
_DE = *((word*)addr);
}
void INDEXN(ContextBlock *ctx) {
byte ofs = _C;
word addr = _HL + ofs/2;
byte val = *((byte*)addr);
_A = (ofs & 1) ? (val>>4) : (val&0xf);
}
// input
extern const byte KCTASC_TABLE[25];
void KCTASC(ContextBlock *ctx) {
_A = KCTASC_TABLE[_B];
}
// music
void BMUSIC(ContextBlock *ctx) {
VOICES = _A;
MUZPC = _HL;
MUZSP = _IX;
DURAT = 0;
}
void EMUSIC(ContextBlock *ctx) {
ctx;
VOICES = 0;
}
// externals
extern void SENTRY(ContextBlock *ctx);
extern void DOIT(ContextBlock *ctx);
extern void DOITB(ContextBlock *ctx);
extern void RECTAN(const ContextBlock *ctx);
extern void WRITR(const ContextBlock *ctx);
extern void WRITP(const ContextBlock *ctx);
extern void WRIT(const ContextBlock *ctx);
extern void CHRDIS(const ContextBlock *ctx);
extern void STRDIS(const ContextBlock *ctx);
extern void DISNUM(const ContextBlock *ctx);
// table
const SysCallEntry SYSCALL_TABLE[64] = {
/* 0 */
{ &INTPC, 0 },
{ &EXIT, 0 },
{ &RCALL, REG_HL },
{ &MCALL, REG_HL },
{ &MRET, 0 },
/* 10 */
{ &MJUMP, REG_HL },
{ &SUCK, REG_B },
{ &ACTINT, 0 },
{ &DECCTS, REG_C },
{ &BMUSIC, REG_HL|REG_IX|REG_A },
/* 20 */
{ &EMUSIC, },
{ &SETOUT, REG_D|REG_B|REG_A },
{ &COLSET, REG_HL },
{ &FILL, REG_A|REG_BC|REG_DE },
{ &RECTAN, REG_A|REG_B|REG_C|REG_D|REG_E },
/* 30 */
{ &NOOP, REG_HL|REG_IX }, // VWRITR
{ &WRITR, REG_E|REG_D|REG_A|REG_HL },
{ &WRITP, REG_E|REG_D|REG_A|REG_HL },
{ &WRIT, REG_E|REG_D|REG_C|REG_B|REG_A|REG_HL },
{ &NOOP, REG_E|REG_D|REG_C|REG_B|REG_A|REG_HL }, // WRITA
/* 40 */
{ &NOOP, REG_E|REG_D|REG_IX }, // VBLANK
{ &NOOP, REG_E|REG_D|REG_B|REG_HL }, // BLANK
{ &NOOP, REG_B|REG_C|REG_DE|REG_HL }, // SAVE
{ &NOOP, REG_DE|REG_HL }, // RESTORE
{ &NOOP, REG_B|REG_C|REG_DE|REG_HL }, // SCROLL
/* 50 */
{ &CHRDIS, REG_E|REG_D|REG_C|REG_A },
{ &STRDIS, REG_E|REG_D|REG_C|REG_HL },
{ &DISNUM, REG_E|REG_D|REG_C|REG_B|REG_HL },
{ &NOOP, REG_DE|REG_A }, // RELABS
{ &NOOP, REG_E|REG_D|REG_A }, // RELAB1
/* 60 */
{ &NOOP, REG_IX|REG_HL|REG_C }, // VECTC
{ &NOOP, REG_IX|REG_HL }, // VECT
{ &KCTASC, 0 },
{ &SENTRY, REG_DE },
{ &DOIT, REG_HL },
/* 70 */
{ &DOITB, REG_HL },
{ &NOOP, 0 }, // PIZBRK
{ &NOOP, REG_DE|REG_HL }, // MENU
{ &NOOP, REG_BC|REG_HL }, // GETPAR
{ &NOOP, REG_B|REG_C|REG_D|REG_E|REG_HL }, // GETNUM
/* 80 */
{ &PAWS, REG_B },
{ &NOOP, REG_E|REG_D|REG_C }, // DISTIM
{ &NOOP, REG_HL }, // INCSCR
{ &INDEXN, REG_C|REG_HL }, // INDEXN
{ &NOOP, REG_HL }, // STOREN
/* 90 */
{ &INDEXW, REG_A|REG_HL }, // INDEXW
{ &INDEXB, REG_A|REG_HL }, // INDEXB
{ &MOVE, REG_DE|REG_BC|REG_HL },
{ &NOOP, 0 }, // SHIFTU
{ &BCDADD, REG_DE|REG_B|REG_HL },
/* 100 */
{ &NOOP, 0 }, // BCDSUB
{ &NOOP, 0 }, // BCDMUL
{ &NOOP, 0 }, // BCDDIV
{ &NOOP, 0 }, // BCDCHS
{ &NOOP, 0 }, // BCDNEG
/* 110 */
{ &NOOP, 0 }, // DADD
{ &NOOP, 0 }, // DSMG
{ &NOOP, 0 }, // DABS
{ &NOOP, 0 }, // NEGT
{ &RANGED, REG_A },
/* 120 */
{ &NOOP, 0 }, // QUIT
{ &NOOP, REG_A|REG_HL }, // SETB
{ &NOOP, REG_DE|REG_HL }, // SETW
{ &NOOP, REG_C|REG_DE|REG_HL }, // MSKTD
};
void SYSCALL(ContextBlock *ctx) {
byte op = *ctx->params++;
byte argmask;
SysRoutine* routine;
// user-defined?
if (op & 0x80) {
argmask = UMARGT[op>>1];
routine = (SysRoutine*) USERTB[op>>1];
} else {
const SysCallEntry* entry = &SYSCALL_TABLE[op>>1];
argmask = entry->argmask;
routine = entry->routine;
}
// suck params into context block?
if (op & 1) {
suckParams(ctx, argmask);
}
// call the routine
routine(ctx);
}
void bios_init() {
ContextBlock ctx;
memset((void*)0x4fce, 0, 0x5000-0x4fce);
ACTINT(0);
hw_verbl = 96*2;
hw_horcb = 41;
hw_inmod = 0x8;
// call SENTRY once to set current values
// (context block doesn't matter)
SENTRY(&ctx);
}