525 lines
11 KiB
C
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;
|
|
}
|
|
|