I have added a new demo (in two parts) to the demos repo. The first part (/sys/modules/famicom
) is a "module" that extends Engine BASIC with a couple of commands that allow you to start and control a Famicom emulator from BASIC. It implements commands like FAMILOAD
to load a ROM, FAMULATE
to emulate one frame, FAMINPUT
to pass gamepad input to the emulator, and FAMIREAD()
to read the emulated Famicom's memory.
The second part (/famicom
) consists of two BASIC programs that use the famicom module. The first program (play.bas
) simply forwards gamepad input to the emulator and looks like this:
#REQUIRE "famicom"
INPUT "ROM file? ";rom$
FAMIMOVE PSIZE(0)-256, 0
FAMILOAD rom$
DO
f=FRAME()
FAMULATE
FAMINPUT PAD(0)
VSYNC f+1
LOOP
The second program (autoplay.bas
) does a very bad job at playing Super Mario Bros. automatically. Feel free to improve it.
About modules
A "module" is an extension to the Engine BASIC system implemented in C (or whatever you can compile into an ELF object file). It resides in the root filesystem in /sys/modules/<module name>
, and can be loaded with LOADMOD "<module name>"
or by adding a #REQUIRE "<module name>"
statement at the start of your BASIC program.
The system will then either run a program called loader.bas
in the module directory, if one exists, or look for pre-compiled object files called <module name>_<cpu architecture>.o
, e.g. famicom_arm.o
or famicom_x86_64.o
and load one of those.
Check https://github.com/uli/basicengine-demos/tree/master/sys/modules/famicom (README.md
and Makefile
) for examples on how to cross-compile and natively compile C code for Engine BASIC.
Adding new command and functions to Engine BASIC
Extending BASIC works by calling the eb_add_command()
, eb_add_numfun()
or eb_add_strfun()
functions from an __initcall()
function that is executed at load time, for instance:
eb_add_command("FAMILOAD", syn_famiload, famiload);
eb_add_numfun("FAMIREAD", syn_famiread, famiread);
syn_famiload
and syn_famiread
are syntax descriptions. famiload()
and famiread()
are the handlers that implement the command/function.
The syntax is defined by an array of tokens that end with I_EOL
:
const enum token_t syn_famiload[] = { I_STR, I_EOL };
describes a command that takes a string as its argument.
const enum token_t syn_famiread[] = { I_OPEN, I_NUM, I_CLOSE, I_EOL };
describes a function that takes a numeric argument enclosed in parentheses.
The command handlers receive a pointer to a parameter array. Only string and numeric parameters are included in the array; "filler" tokens (such as the parentheses in the syn_famiread
example) are not.
Check https://github.com/uli/basicengine-demos/blob/master/sys/modules/famicom/fami.c to see how it all works together in practice.
Custom background layers
The famicom module also uses new functionality that allows you to hook custom background layer painters into the BG/sprite engine to display the emulated screen. Check the painter()
function in fami.c
and the eb_add_bg_layer()
and eb_remove_bg_layer()
functions for an example of how to do that.