gigatron/rom/Contrib/dhkolf/libgtemu/luagt.c
2025-01-28 19:17:01 +03:00

525 lines
11 KiB
C

/*
This file is part of libgtemu, a library for Gigatron emulation.
Copyright (C) 2019 David Heiko Kolf
Published under the BSD-2-Clause license.
https://opensource.org/licenses/BSD-2-Clause
*/
#include <lua.h>
#include <lauxlib.h>
#include <SDL2/SDL.h>
#include <time.h>
#include "gtemu.h"
#include "gtsdl.h"
#define newud(L,t) ((t*)lua_newuserdata((L),sizeof(t)))
struct GTEmulationData {
struct GTState gt;
struct GTPeriph ph;
struct GTRomEntry rom[0x10000];
unsigned char ram[0x10000];
};
static struct GTEmulationData *checkemu (lua_State *L, int n)
{
return (struct GTEmulationData *)
luaL_checkudata(L, 1, "gtemu.emulation");
}
static int lgtsdlerror (lua_State *L)
{
return luaL_error(L, "SDL: %s", SDL_GetError());
}
static void lgtclosesdl (void)
{
SDL_Quit();
}
static int lgtinitsdl (lua_State *L)
{
if (SDL_Init(0) < 0) {
return lgtsdlerror(L);
}
atexit(lgtclosesdl);
return 0;
}
static int lgtopenwindow (lua_State *L)
{
const char *str = lua_tostring(L, 1);
struct GTSDLState *s = newud(L, struct GTSDLState);
memset(s, 0, sizeof(*s));
luaL_setmetatable(L, "gtemu.sdlstate");
if (!gtsdl_openwindow(s, str)) {
return lgtsdlerror(L);
}
return 1;
}
static int lgtclosewindow (lua_State *L)
{
struct GTSDLState *s = (struct GTSDLState *)
luaL_checkudata(L, 1, "gtemu.sdlstate");
gtsdl_close(s);
return 0;
}
static int lgtnewemulation (lua_State *L)
{
struct GTSDLState *s = (struct GTSDLState *)
luaL_checkudata(L, 1, "gtemu.sdlstate");
int ramkb = luaL_optinteger(L, 2, 32);
size_t ramsize;
unsigned long rngstate;
struct GTEmulationData *emu;
if (ramkb == 32) {
ramsize = 0x8000;
} else if (ramkb == 64) {
ramsize = 0x10000;
} else {
return luaL_argerror(L, 2, "is neither 32 nor 64");
}
emu = newud(L, struct GTEmulationData);
rngstate = time(0);
rngstate = gtemu_randomizemem(rngstate, emu->rom, sizeof(emu->rom));
rngstate = gtemu_randomizemem(rngstate, emu->ram, sizeof(emu->ram));
gtemu_init(&emu->gt, emu->rom, sizeof(emu->rom),
emu->ram, ramsize);
gtemu_initperiph(&emu->ph, gtsdl_getaudiofreq(s), rngstate);
luaL_setmetatable(L, "gtemu.emulation");
return 1;
}
static int lgtemuloadrom (lua_State *L)
{
struct GTEmulationData *emu = checkemu(L, 1);
size_t len, i, j;
const char *str = lua_tolstring(L, 2, &len);
for (i = 0, j = 0;
i + 1 < len && j < sizeof(emu->rom) / sizeof(emu->rom[0]);
i += 2, j++) {
emu->rom[j].i = (unsigned char) str[i];
emu->rom[j].d = (unsigned char) str[i+1];
}
return 0;
}
static int lgtemuprocesstick (lua_State *L)
{
struct GTEmulationData *emu = checkemu(L, 1);
lua_pushinteger(L, gtemu_processtick(&emu->gt, &emu->ph));
return 1;
}
static int lgtemuprocessscreen (lua_State *L)
{
struct GTEmulationData *emu = checkemu(L, 1);
size_t dummy;
lua_pushinteger(L, gtemu_processscreen(&emu->gt, &emu->ph,
NULL, 0, NULL, 0, &dummy));
return 1;
}
static int lgtemusendgt1 (lua_State *L)
{
struct GTEmulationData *emu = checkemu(L, 1);
size_t len;
const char *str = lua_tolstring(L, 2, &len);
int res;
res = gtloader_validategt1(str, len);
if (!res) {
return luaL_error(L, "invalid GT1 structure");
}
if (gtloader_sendgt1(&emu->ph, str, len)) {
/* prevent garbage collection while the loader is working */
lua_pushvalue(L, 1);
lua_pushvalue(L, 2);
lua_settable(L, lua_upvalueindex(1));
} else {
luaL_error(L, "loader is busy");
}
return 0;
}
static int lgtemusendtext (lua_State *L)
{
struct GTEmulationData *emu = checkemu(L, 1);
size_t len;
const char *str = lua_tolstring(L, 2, &len);
if (gtloader_sendtext(&emu->ph, str, len)) {
/* prevent garbage collection while the loader is working */
lua_pushvalue(L, 1);
lua_pushvalue(L, 2);
lua_settable(L, lua_upvalueindex(1));
} else {
luaL_error(L, "loader is busy");
}
return 0;
}
static int lgtemucreatebuffer (lua_State *L)
{
struct GTEmulationData *emu = checkemu(L, 1);
lua_Integer size = luaL_checkinteger(L, 2);
size_t *buf = (size_t *) lua_newuserdata(L, sizeof(size_t) + size);
buf[0] = 0;
gtserialout_setbuffer(&emu->ph, (char *)&buf[1], size, buf);
lua_pushvalue(L, 1);
lua_pushvalue(L, -2);
lua_rawset(L, lua_upvalueindex(2));
return 1;
}
static int lgtemugetbuffer (lua_State *L)
{
size_t *buf;
lua_pushvalue(L, 1);
lua_rawget(L, lua_upvalueindex(2));
buf = lua_touserdata(L, -1);
if (buf != NULL) {
lua_pushlstring(L, (char *)&buf[1], buf[0]);
} else {
lua_pushnil(L);
}
return 1;
}
static int lgtemuresetbuffer (lua_State *L)
{
size_t *buf;
lua_pushvalue(L, 1);
lua_rawget(L, lua_upvalueindex(2));
buf = lua_touserdata(L, -1);
if (buf != NULL) {
buf[0] = 0;
}
return 0;
}
static int lgtemuindex (lua_State *L)
{
struct GTEmulationData *emu = checkemu(L, 1);
int idx, isnum;
idx = lua_tointegerx(L, 2, &isnum);
if (isnum && idx >= 0 && idx < 0x10000) {
lua_pushinteger(L, emu->ram[idx & emu->gt.rammask]);
return 1;
}
lua_pushvalue(L, 2);
lua_rawget(L, lua_upvalueindex(2));
if (!lua_isnil(L, -1)) {
lua_pushvalue(L, 1);
lua_call(L, 1, 1);
return 1;
}
lua_pushvalue(L, 2);
lua_rawget(L, lua_upvalueindex(1));
return 1;
}
static int lgtemunewindex (lua_State *L)
{
struct GTEmulationData *emu = checkemu(L, 1);
int idx, isnum;
idx = lua_tointegerx(L, 2, &isnum);
if (isnum && idx >= 0 && idx < 0x10000) {
emu->ram[idx & emu->gt.rammask] = luaL_checkinteger(L, 3);
return 0;
}
lua_pushvalue(L, 2);
lua_rawget(L, lua_upvalueindex(1));
if (!lua_isnil(L, -1)) {
lua_pushvalue(L, 1);
lua_pushvalue(L, 3);
lua_call(L, 2, 0);
return 0;
}
return luaL_error(L, "element is not writable");
}
#define makepropfn(prop, T) \
static int lgtemuget_##prop (lua_State *L) \
{ \
lua_pushinteger(L, checkemu(L, 1)->gt.prop); \
return 1; \
} \
static int lgtemuset_##prop (lua_State *L) \
{ \
checkemu(L, 1)->gt.prop = (T)luaL_checkinteger(L, 2); \
return 0; \
}
makepropfn(pc, unsigned short)
makepropfn(ir, unsigned char)
makepropfn(d, unsigned char)
makepropfn(ac, unsigned char)
makepropfn(x, unsigned char)
makepropfn(y, unsigned char)
makepropfn(out, unsigned char)
makepropfn(in, unsigned char)
#undef makegetpropfn
static int lgtemuget_vpc (lua_State *L)
{
struct GTEmulationData *emu = checkemu(L, 1);
lua_pushinteger(L, emu->ram[0x16] | (emu->ram[0x17] << 8));
return 1;
}
static int lgtemuset_vpc (lua_State *L)
{
struct GTEmulationData *emu = checkemu(L, 1);
int value = luaL_checkinteger(L, 2);
emu->ram[0x16] = value & 0xff;
emu->ram[0x17] = (value >> 8) & 0xff;
return 0;
}
static int lgtemuget_clock (lua_State *L)
{
struct GTEmulationData *emu = checkemu(L, 1);
lua_pushinteger(L, gtemu_getclock(&emu->ph));
return 1;
}
static int lgtemuget_xout (lua_State *L)
{
struct GTEmulationData *emu = checkemu(L, 1);
lua_pushinteger(L, gtemu_getxout(&emu->ph));
return 1;
}
static void pushkeysym(lua_State *L, SDL_KeyboardEvent *ev)
{
lua_pushstring(L, SDL_GetKeyName(ev->keysym.sym));
lua_pushinteger(L, ev->keysym.mod);
lua_pushboolean(L, ev->repeat);
lua_pushinteger(L, ev->keysym.scancode);
lua_pushinteger(L, ev->keysym.sym);
}
static int lgtrunloop (lua_State *L)
{
struct GTSDLState *s = (struct GTSDLState *)
luaL_checkudata(L, 1, "gtemu.sdlstate");
struct GTEmulationData *emu = (struct GTEmulationData *)
luaL_checkudata(L, 2, "gtemu.emulation");
int onkeydown = 0, onkeyup = 0, ontextinput = 0, ondropfile = 0,
onframe = 0, breaksym = 9;
if (lua_isnoneornil(L, 3)) {
lua_settop(L, breaksym);
} else {
lua_settop(L, 3);
luaL_checktype(L, 3, LUA_TTABLE);
lua_getfield(L, 3, "onkeydown");
onkeydown = lua_isnil(L, 4) ? 0 : 4;
lua_getfield(L, 3, "onkeyup");
onkeyup = lua_isnil(L, 5) ? 0 : 5;
lua_getfield(L, 3, "ontextinput");
ontextinput = lua_isnil(L, 6) ? 0 : 6;
lua_getfield(L, 3, "ondropfile");
ondropfile = lua_isnil(L, 7) ? 0 : 7;
lua_getfield(L, 3, "onframe");
onframe = lua_isnil(L, 8) ? 0 : 8;
lua_pushliteral(L, "break");
}
for (;;) {
SDL_Event ev;
int checkres = 0;
if (onframe != 0) {
lua_pushvalue(L, onframe);
lua_call(L, 0, 1);
if (lua_compare(L, -1, breaksym, LUA_OPEQ)) {
lua_pop(L, 1);
break;
} else {
lua_pop(L, 1);
} }
if (gtsdl_runuiframe(s, &emu->gt, &emu->ph, &ev) == 0) {
continue;
}
if (ev.type == SDL_QUIT) {
break;
}
if (ev.type == SDL_KEYDOWN && onkeydown != 0) {
lua_pushvalue(L, onkeydown);
pushkeysym(L, &ev.key);
lua_call(L, 5, 1);
checkres = 1;
} else if (ev.type == SDL_KEYUP && onkeyup != 0) {
lua_pushvalue(L, onkeyup);
pushkeysym(L, &ev.key);
lua_call(L, 5, 1);
checkres = 1;
} else if (ev.type == SDL_TEXTINPUT && ontextinput != 0) {
lua_pushvalue(L, ontextinput);
lua_pushstring(L, ev.text.text);
lua_call(L, 1, 1);
checkres = 1;
} else if (ev.type == SDL_DROPFILE) {
if (ondropfile != 0) {
lua_pushvalue(L, ondropfile);
lua_pushstring(L, ev.drop.file);
}
SDL_free(ev.drop.file);
ev.drop.file = NULL;
if (ondropfile != 0) {
lua_call(L, 1, 1);
} else {
lua_pushnil(L);
}
/* We freed the file name already, this event
cannot be handled by a later stage anymore. */
checkres = 2;
}
if (checkres) {
if (lua_compare(L, -1, breaksym, LUA_OPEQ)) {
lua_pop(L, 1);
break;
} else if (checkres == 2 || lua_toboolean(L, -1)) {
lua_pop(L, 1);
continue;
} else {
lua_pop(L, 1);
}
}
if (ev.type == SDL_KEYDOWN &&
ev.key.keysym.scancode == SDL_SCANCODE_ESCAPE) {
break;
}
gtsdl_handleevent(s, &emu->gt, &emu->ph, &ev);
}
return 0;
}
static luaL_Reg lgtsdlstatemeta[] = {
{"__gc", lgtclosewindow},
{NULL, NULL}
};
static luaL_Reg lgtsdlstatefns[] = {
{"runloop", lgtrunloop},
{"close", lgtclosewindow},
{NULL, NULL}
};
static luaL_Reg lgtemufns[] = {
{"loadrom", lgtemuloadrom},
{"processtick", lgtemuprocesstick},
{"processscreen", lgtemuprocessscreen},
{"sendgt1", lgtemusendgt1},
{"sendtext", lgtemusendtext},
{"createbuffer", lgtemucreatebuffer},
{"getbuffer", lgtemugetbuffer},
{"resetbuffer", lgtemuresetbuffer},
{NULL, NULL}
};
static luaL_Reg lgtemugetfns[] = {
{"pc", lgtemuget_pc},
{"ir", lgtemuget_ir},
{"d", lgtemuget_d},
{"ac", lgtemuget_ac},
{"x", lgtemuget_x},
{"y", lgtemuget_y},
{"out", lgtemuget_out},
{"inp", lgtemuget_in}, /* "in" is a keyword in Lua */
{"vpc", lgtemuget_vpc},
{"clock", lgtemuget_clock},
{"xout", lgtemuget_xout},
{NULL, NULL}
};
static luaL_Reg lgtemusetfns[] = {
{"pc", lgtemuset_pc},
{"ir", lgtemuset_ir},
{"d", lgtemuset_d},
{"ac", lgtemuset_ac},
{"x", lgtemuset_x},
{"y", lgtemuset_y},
{"out", lgtemuset_out},
{"inp", lgtemuset_in}, /* "in" is a keyword in Lua */
{"vpc", lgtemuset_vpc},
{NULL, NULL}
};
static luaL_Reg lgtemulibfns[] = {
{"initsdl", lgtinitsdl},
{"openwindow", lgtopenwindow},
{"newemulation", lgtnewemulation},
{NULL, NULL}
};
int luaopen_gtemu (lua_State *L)
{
luaL_newmetatable(L, "gtemu.sdlstate");
luaL_setfuncs(L, lgtsdlstatemeta, 0);
luaL_newlib(L, lgtsdlstatefns);
lua_setfield(L, -2, "__index");
lua_pop(L, 1);
luaL_newmetatable(L, "gtemu.emulation");
luaL_newlibtable(L, lgtemufns);
lua_newtable(L); /* data for loader */
lua_newtable(L); /* buffer for storage */
luaL_setfuncs(L, lgtemufns, 2);
luaL_newlib(L, lgtemugetfns);
lua_pushcclosure(L, lgtemuindex, 2);
lua_setfield(L, -2, "__index");
luaL_newlib(L, lgtemusetfns);
lua_pushcclosure(L, lgtemunewindex, 1);
lua_setfield(L, -2, "__newindex");
lua_pop(L, 1);
luaL_newlibtable(L, lgtemulibfns);
luaL_setfuncs(L, lgtemulibfns, 0);
return 1;
}