fceux/src/lua-engine.cpp

1765 lines
39 KiB
C++
Raw Normal View History

2008-07-23 15:15:46 +00:00
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <string.h>
#include <ctype.h>
#ifdef __linux
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#endif
extern "C"
{
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
}
2008-07-24 03:18:48 +00:00
2008-07-23 15:15:46 +00:00
#include "types.h"
#include "fceu.h"
#include "video.h"
#include "drawing.h"
#include "state.h"
#include "movie.h"
#include "driver.h"
#include "cheat.h"
#include "utils/xstring.h"
#include "utils/memory.h"
#include "fceulua.h"
#ifdef WIN32
#include "drivers/win/common.h"
#endif
#ifndef TRUE
#define TRUE 1
#define FALSE 0
#endif
2008-07-29 00:42:02 +00:00
static lua_State *L;
2008-07-23 15:15:46 +00:00
// Are we running any code right now?
static char *luaScriptName = NULL;
// Are we running any code right now?
static int luaRunning = FALSE;
// True at the frame boundary, false otherwise.
static int frameBoundary = FALSE;
// The execution speed we're running at.
static enum {SPEED_NORMAL, SPEED_NOTHROTTLE, SPEED_TURBO, SPEED_MAXIMUM} speedmode = SPEED_NORMAL;
// Rerecord count skip mode
static int skipRerecords = FALSE;
// Used by the registry to find our functions
static const char *frameAdvanceThread = "FCEU.FrameAdvance";
static const char *memoryWatchTable = "FCEU.Memory";
static const char *memoryValueTable = "FCEU.MemValues";
static const char *guiCallbackTable = "FCEU.GUI";
// True if there's a thread waiting to run after a run of frame-advance.
static int frameAdvanceWaiting = FALSE;
// We save our pause status in the case of a natural death.
static int wasPaused = FALSE;
// Transparency strength. 0=opaque, 4=so transparent it's invisible
// TODO: intermediate values would be nice...
static int transparency;
// Our joypads.
static uint8 lua_joypads[4];
static uint8 lua_joypads_used;
static enum { GUI_USED_SINCE_LAST_DISPLAY, GUI_USED_SINCE_LAST_FRAME, GUI_CLEAR } gui_used = GUI_CLEAR;
static uint8 *gui_data = NULL;
static int gui_saw_current_palette = FALSE;
// See drawing.h for comments about FCEU's palette. We interpret zero as transparent.
enum
{
GUI_COLOUR_CLEAR, GUI_COLOUR_WHITE,
GUI_COLOUR_BLACK, GUI_COLOUR_GREY,
GUI_COLOUR_RED, GUI_COLOUR_GREEN,
GUI_COLOUR_BLUE
};
// Protects Lua calls from going nuts.
// We set this to a big number like 1000 and decrement it
// over time. The script gets knifed once this reaches zero.
static int numTries;
// Look in fceu.h for macros named like JOY_UP to determine the order.
static const char *button_mappings[] = {
"A", "B", "select", "start", "up", "down", "left", "right"
};
/**
* Resets emulator speed / pause states after script exit.
*/
static void FCEU_LuaOnStop() {
luaRunning = FALSE;
lua_joypads_used = 0;
gui_used = GUI_CLEAR;
if (wasPaused && !FCEUI_EmulationPaused())
FCEUI_ToggleEmulationPause();
FCEUD_SetEmulationSpeed(EMUSPEED_NORMAL);
}
/**
* Asks Lua if it wants control of the emulator's speed.
* Returns 0 if no, 1 if yes. If yes, caller should also
* consult FCEU_LuaFrameSkip().
*/
int FCEU_LuaSpeed() {
2008-07-29 00:42:02 +00:00
if (!L || !luaRunning)
2008-07-23 15:15:46 +00:00
return 0;
//printf("%d\n", speedmode);
switch (speedmode) {
case SPEED_NOTHROTTLE:
case SPEED_TURBO:
case SPEED_MAXIMUM:
return 1;
case SPEED_NORMAL:
default:
return 0;
}
}
/**
* Asks Lua if it wants control whether this frame is skipped.
* Returns 0 if no, 1 if frame should be skipped, -1 if it should not be.
*/
int FCEU_LuaFrameSkip() {
2008-07-29 00:42:02 +00:00
if (!L || !luaRunning)
2008-07-23 15:15:46 +00:00
return 0;
switch (speedmode) {
case SPEED_NORMAL:
return 0;
case SPEED_NOTHROTTLE:
return -1;
case SPEED_TURBO:
return 0;
case SPEED_MAXIMUM:
return 1;
}
2008-07-24 15:16:18 +00:00
return 0;
2008-07-23 15:15:46 +00:00
}
/**
* When code determines that a write has occurred
* (not necessarily worth informing Lua), call this.
*
*/
void FCEU_LuaWriteInform() {
2008-07-29 00:42:02 +00:00
if (!L || !luaRunning) return;
2008-07-23 15:15:46 +00:00
// Nuke the stack, just in case.
2008-07-29 00:42:02 +00:00
lua_settop(L,0);
2008-07-23 15:15:46 +00:00
2008-07-29 00:42:02 +00:00
lua_getfield(L, LUA_REGISTRYINDEX, memoryWatchTable);
lua_pushnil(L);
while (lua_next(L, 1) != 0)
2008-07-23 15:15:46 +00:00
{
2008-07-29 00:42:02 +00:00
unsigned int addr = luaL_checkinteger(L, 2);
2008-07-23 15:15:46 +00:00
lua_Integer value;
2008-07-29 00:42:02 +00:00
lua_getfield(L, LUA_REGISTRYINDEX, memoryValueTable);
lua_pushvalue(L, 2);
lua_gettable(L, 4);
value = luaL_checkinteger(L, 5);
2008-07-23 15:15:46 +00:00
if (FCEU_CheatGetByte(addr) != value)
{
// Value changed; update & invoke the Lua callback
2008-07-29 00:42:02 +00:00
lua_pushinteger(L, addr);
lua_pushinteger(L, FCEU_CheatGetByte(addr));
lua_settable(L, 4);
lua_pop(L, 2);
2008-07-23 15:15:46 +00:00
numTries = 1000;
2008-07-29 00:42:02 +00:00
int res = lua_pcall(L, 0, 0, 0);
2008-07-23 15:15:46 +00:00
if (res) {
2008-07-29 00:42:02 +00:00
const char *err = lua_tostring(L, -1);
2008-07-23 15:15:46 +00:00
#ifdef WIN32
//StopSound(); //mbg merge 7/23/08
MessageBox(hAppWnd, err, "Lua Engine", MB_OK);
#else
fprintf(stderr, "Lua error: %s\n", err);
#endif
}
}
2008-07-29 00:42:02 +00:00
lua_settop(L, 2);
2008-07-23 15:15:46 +00:00
}
2008-07-29 00:42:02 +00:00
lua_settop(L, 0);
2008-07-23 15:15:46 +00:00
}
///////////////////////////
// FCEU.speedmode(string mode)
//
// Takes control of the emulation speed
// of the system. Normal is normal speed (60fps, 50 for PAL),
// nothrottle disables speed control but renders every frame,
// turbo renders only a few frames in order to speed up emulation,
// maximum renders no frames
// TODO: better enforcement, done in the same way as basicbot...
static int fceu_speedmode(lua_State *L) {
const char *mode = luaL_checkstring(L,1);
if (strcasecmp(mode, "normal")==0) {
speedmode = SPEED_NORMAL;
} else if (strcasecmp(mode, "nothrottle")==0) {
speedmode = SPEED_NOTHROTTLE;
} else if (strcasecmp(mode, "turbo")==0) {
speedmode = SPEED_TURBO;
} else if (strcasecmp(mode, "maximum")==0) {
speedmode = SPEED_MAXIMUM;
} else
luaL_error(L, "Invalid mode %s to FCEU.speedmode",mode);
//printf("new speed mode: %d\n", speedmode);
if (speedmode == SPEED_NORMAL) FCEUD_SetEmulationSpeed(EMUSPEED_NORMAL);
else FCEUD_SetEmulationSpeed(EMUSPEED_FASTEST);
return 0;
}
// FCEU.frameadvance()
//
// Executes a frame advance. Occurs by yielding the coroutine, then re-running
// when we break out.
static int fceu_frameadvance(lua_State *L) {
// We're going to sleep for a frame-advance. Take notes.
if (frameAdvanceWaiting)
return luaL_error(L, "can't call FCEU.frameadvance() from here");
frameAdvanceWaiting = TRUE;
// Now we can yield to the main
return lua_yield(L, 0);
// It's actually rather disappointing...
}
// FCEU.pause()
//
// Pauses the emulator, function "waits" until the user unpauses.
// This function MAY be called from a non-frame boundary, but the frame
// finishes executing anwyays. In this case, the function returns immediately.
static int fceu_pause(lua_State *L) {
if (!FCEUI_EmulationPaused())
FCEUI_ToggleEmulationPause();
speedmode = SPEED_NORMAL;
// Return control if we're midway through a frame. We can't pause here.
if (frameAdvanceWaiting) {
return 0;
}
// If it's on a frame boundary, we also yield.
frameAdvanceWaiting = TRUE;
return lua_yield(L, 0);
}
// FCEU.message(string msg)
//
// Displays the given message on the screen.
static int fceu_message(lua_State *L) {
const char *msg = luaL_checkstring(L,1);
FCEU_DispMessage("%s", msg);
return 0;
}
static int memory_readbyte(lua_State *L) { lua_pushinteger(L, FCEU_CheatGetByte(luaL_checkinteger(L,1))); return 1; }
static int memory_writebyte(lua_State *L) { FCEU_CheatSetByte(luaL_checkinteger(L,1), luaL_checkinteger(L,2)); return 0; }
static int memory_readbyterange(lua_State *L) {
int range_start = luaL_checkinteger(L,1);
int range_size = luaL_checkinteger(L,2);
if(range_size < 0)
return 0;
char* buf = (char*)alloca(range_size);
for(int i=0;i<range_size;i++) {
buf[i] = FCEU_CheatGetByte(range_start+i);
}
lua_pushlstring(L,buf,range_size);
return 1;
}
2008-07-23 15:15:46 +00:00
// Not for the signed versions though
static int memory_readbytesigned(lua_State *L) {
signed char c = (signed char) FCEU_CheatGetByte(luaL_checkinteger(L,1));
lua_pushinteger(L, c);
return 1;
}
// memory.register(int address, function func)
//
// Calls the given function when the indicated memory address is
// written to. No args are given to the function. The write has already
// occurred, so the new address is readable.
static int memory_register(lua_State *L) {
// Check args
unsigned int addr = luaL_checkinteger(L, 1);
if (lua_type(L,2) != LUA_TNIL && lua_type(L,2) != LUA_TFUNCTION)
luaL_error(L, "function or nil expected in arg 2 to memory.register");
// Check the address range
if (addr > 0xffff)
luaL_error(L, "arg 1 should be between 0x0000 and 0x0fff");
// Commit it to the registery
lua_getfield(L, LUA_REGISTRYINDEX, memoryWatchTable);
lua_pushvalue(L,1);
lua_pushvalue(L,2);
lua_settable(L, -3);
lua_getfield(L, LUA_REGISTRYINDEX, memoryValueTable);
lua_pushvalue(L,1);
if (lua_isnil(L,2)) lua_pushnil(L);
else lua_pushinteger(L, FCEU_CheatGetByte(addr));
lua_settable(L, -3);
return 0;
}
// table joypad.read(int which = 1)
//
// Reads the joypads as inputted by the user.
// This is really the only way to get input to the system.
// TODO: Don't read in *everything*...
static int joypad_read(lua_State *L) {
// Reads the joypads as inputted by the user
int which = luaL_checkinteger(L,1);
if (which < 1 || which > 4) {
luaL_error(L,"Invalid input port (valid range 1-2, specified %d)", which);
}
// Use the OS-specific code to do the reading.
extern void FCEUD_UpdateInput(void);
FCEUD_UpdateInput();
extern SFORMAT FCEUCTRL_STATEINFO[];
uint8 buttons = ((uint8 *) FCEUCTRL_STATEINFO[1].v)[which - 1];
lua_newtable(L);
int i;
for (i = 0; i < 8; i++) {
if (buttons & (1<<i)) {
lua_pushinteger(L,1);
lua_setfield(L, -2, button_mappings[i]);
}
}
return 1;
}
// joypad.write(int which, table buttons)
//
// Sets the given buttons to be pressed during the next
// frame advance. The table should have the right
// keys (no pun intended) set.
static int joypad_set(lua_State *L) {
// Which joypad we're tampering with
int which = luaL_checkinteger(L,1);
if (which < 1 || which > 4) {
luaL_error(L,"Invalid output port (valid range 1-4, specified %d)", which);
}
// And the table of buttons.
luaL_checktype(L,2,LUA_TTABLE);
// Set up for taking control of the indicated controller
lua_joypads_used |= 1 << (which-1);
lua_joypads[which-1] = 0;
int i;
for (i=0; i < 8; i++) {
lua_getfield(L, 2, button_mappings[i]);
if (!lua_isnil(L,-1))
lua_joypads[which-1] |= 1 << i;
lua_pop(L,1);
}
return 0;
}
// Helper function to convert a savestate object to the filename it represents.
static char *savestateobj2filename(lua_State *L, int offset) {
// First we get the metatable of the indicated object
int result = lua_getmetatable(L, offset);
if (!result)
luaL_error(L, "object not a savestate object");
// Also check that the type entry is set
lua_getfield(L, -1, "__metatable");
if (strcmp(lua_tostring(L,-1), "FCEU Savestate") != 0)
luaL_error(L, "object not a savestate object");
lua_pop(L,1);
// Now, get the field we want
lua_getfield(L, -1, "filename");
// Return it
return (char *) lua_tostring(L, -1);
}
// Helper function for garbage collection.
static int savestate_gc(lua_State *L) {
// The object we're collecting is on top of the stack
lua_getmetatable(L,1);
// Get the filename
const char *filename;
lua_getfield(L, -1, "filename");
filename = lua_tostring(L,-1);
// Delete the file
remove(filename);
// We exit, and the garbage collector takes care of the rest.
return 0;
}
// object savestate.create(int which = nil)
//
// Creates an object used for savestates.
// The object can be associated with a player-accessible savestate
// ("which" between 1 and 10) or not (which == nil).
static int savestate_create(lua_State *L) {
int which = -1;
if (lua_gettop(L) >= 1) {
which = luaL_checkinteger(L, 1);
if (which < 1 || which > 10) {
luaL_error(L, "invalid player's savestate %d", which);
}
}
std::string filename;
if (which > 0) {
// Find an appropriate filename. This is OS specific, unfortunately.
// So I turned the filename selection code into my bitch. :)
// Numbers are 0 through 9 though.
filename = FCEU_MakeFName(FCEUMKF_STATE, which - 1, 0);
}
else {
2008-08-13 16:30:31 +00:00
//char tempbuf[100] = "snluaXXXXXX";
//filename = mktemp(tempbuf);
//doesnt work -^
//mbg 8/13/08 - this needs to be this way. we'll make a better system later:
filename = tempnam(NULL, "snlua");
2008-07-23 15:15:46 +00:00
}
// Our "object". We don't care about the type, we just need the memory and GC services.
lua_newuserdata(L,1);
// The metatable we use, protected from Lua and contains garbage collection info and stuff.
lua_newtable(L);
// First, we must protect it
lua_pushstring(L, "FCEU Savestate");
lua_setfield(L, -2, "__metatable");
// Now we need to save the file itself.
lua_pushstring(L, filename.c_str());
lua_setfield(L, -2, "filename");
// If it's an anonymous savestate, we must delete the file from disk should it be gargage collected
if (which < 0) {
lua_pushcfunction(L, savestate_gc);
lua_setfield(L, -2, "__gc");
}
// Set the metatable
lua_setmetatable(L, -2);
// Awesome. Return the object
return 1;
}
// savestate.save(object state)
//
// Saves a state to the given object.
static int savestate_save(lua_State *L) {
char *filename = savestateobj2filename(L,1);
// printf("saving %s\n", filename);
// Save states are very expensive. They take time.
numTries--;
FCEUI_SaveState(filename);
return 0;
}
// savestate.load(object state)
//
// Loads the given state
static int savestate_load(lua_State *L) {
char *filename = savestateobj2filename(L,1);
numTries--;
// printf("loading %s\n", filename);
FCEUI_LoadState(filename);
return 0;
}
// int movie.framecount()
//
// Gets the frame counter for the movie, or nil if no movie running.
int movie_framecount(lua_State *L) {
if (!FCEUMOV_IsPlaying() && !FCEUMOV_IsRecording()) {
lua_pushnil(L);
return 1;
}
lua_pushinteger(L, FCEUMOV_GetFrame());
return 1;
}
// string movie.mode()
//
// "record", "playback" or nil
int movie_mode(lua_State *L) {
if (FCEUMOV_IsRecording())
lua_pushstring(L, "record");
else if (FCEUMOV_IsPlaying())
lua_pushstring(L, "playback");
else
lua_pushnil(L);
return 1;
}
static int movie_rerecordcounting(lua_State *L) {
if (lua_gettop(L) == 0)
luaL_error(L, "no parameters specified");
skipRerecords = lua_toboolean(L,1);
return 0;
}
// movie.stop()
//
// Stops movie playback/recording. Bombs out if movie is not running.
static int movie_stop(lua_State *L) {
if (!FCEUMOV_IsRecording() && !FCEUMOV_IsPlaying())
luaL_error(L, "no movie");
FCEUI_StopMovie();
return 0;
}
// Common code by the gui library: make sure the screen array is ready
static void gui_prepare() {
if (!gui_data)
gui_data = (uint8 *) FCEU_malloc(256 * 256 + 8);
if (gui_used != GUI_USED_SINCE_LAST_DISPLAY)
memset(gui_data,GUI_COLOUR_CLEAR,256*256);
gui_used = GUI_USED_SINCE_LAST_DISPLAY;
}
// Helper for a simple hex parser
static int hex2int(lua_State *L, char c) {
if (c >= '0' && c <= '9')
return c-'0';
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
return luaL_error(L, "invalid hex in colour");
}
/**
* Returns an index approximating an RGB colour.
* TODO: This is easily improvable in terms of speed and probably
* quality of matches. (gd overlay & transparency code call it a lot.)
* With effort we could also cheat and map indices 0x08 .. 0x3F
* ourselves.
*/
static uint8 gui_colour_rgb(uint8 r, uint8 g, uint8 b) {
static uint8 index_lookup[1 << (3+3+3)];
int k;
if (!gui_saw_current_palette)
{
memset(index_lookup, GUI_COLOUR_CLEAR, sizeof(index_lookup));
gui_saw_current_palette = TRUE;
}
k = ((r & 0xE0) << 1) | ((g & 0xE0) >> 2) | ((b & 0xE0) >> 5);
uint16 test, best = GUI_COLOUR_CLEAR;
uint32 best_score = 0xffffffffu, test_score;
if (index_lookup[k] != GUI_COLOUR_CLEAR) return index_lookup[k];
for (test = 0; test < 0xff; test++)
{
uint8 tr, tg, tb;
if (test == GUI_COLOUR_CLEAR) continue;
FCEUD_GetPalette(test, &tr, &tg, &tb);
test_score = abs(r - tr) * 66 +
abs(g - tg) * 129 +
abs(b - tb) * 25;
if (test_score < best_score) best_score = test_score, best = test;
}
index_lookup[k] = best;
return best;
}
void FCEU_LuaUpdatePalette()
{
gui_saw_current_palette = FALSE;
}
/**
* Converts an integer or a string on the stack at the given
* offset to a native colour. Several encodings are supported.
* The user may pass their own palette index, a simple colour name,
* or an HTML-style "#09abcd" colour, which is approximated.
*/
static uint16 gui_getcolour(lua_State *L, int offset) {
switch (lua_type(L,offset)) {
case LUA_TSTRING:
{
const char *str = lua_tostring(L,offset);
if (strcmp(str,"red")==0) {
return GUI_COLOUR_RED;
} else if (strcmp(str, "green")==0) {
return GUI_COLOUR_GREEN;
} else if (strcmp(str, "blue")==0) {
return GUI_COLOUR_BLUE;
} else if (strcmp(str, "black")==0) {
return GUI_COLOUR_BLACK;
} else if (strcmp(str, "white")==0) {
return GUI_COLOUR_WHITE;
} else if (strcmp(str, "clear")==0) {
return GUI_COLOUR_CLEAR;
} else if (str[0] == '#' && strlen(str) == 7) {
int red, green, blue;
red = (hex2int(L, str[1]) << 4) | hex2int(L, str[2]);
green = (hex2int(L, str[3]) << 4) | hex2int(L, str[4]);
blue = (hex2int(L, str[5]) << 4) | hex2int(L, str[6]);
return gui_colour_rgb(red, green, blue);
} else
return luaL_error(L, "unknown colour %s", str);
}
case LUA_TNUMBER:
return (uint8) lua_tointeger(L,offset);
default:
return luaL_error(L, "invalid colour");
}
}
// I'm going to use this a lot in here
#define swap(T, one, two) { \
T temp = one; \
one = two; \
two = temp; \
}
// gui.drawpixel(x,y,colour)
static int gui_drawpixel(lua_State *L) {
int x = luaL_checkinteger(L, 1);
int y = luaL_checkinteger(L,2);
uint8 colour = gui_getcolour(L,3);
2008-07-29 00:42:02 +00:00
if (x < 0 || x >= 256 || y < 0 || y >= 256)
2008-07-23 15:15:46 +00:00
luaL_error(L,"bad coordinates");
gui_prepare();
gui_data[y*256 + x] = colour;
return 0;
}
// gui.drawline(x1,y1,x2,y2,type colour)
static int gui_drawline(lua_State *L) {
int x1,y1,x2,y2;
uint8 colour;
x1 = luaL_checkinteger(L,1);
y1 = luaL_checkinteger(L,2);
x2 = luaL_checkinteger(L,3);
y2 = luaL_checkinteger(L,4);
colour = gui_getcolour(L,5);
2008-07-29 00:42:02 +00:00
if (x1 < 0 || x1 >= 256 || y1 < 0 || y1 >= 256)
2008-07-23 15:15:46 +00:00
luaL_error(L,"bad coordinates");
2008-07-29 00:42:02 +00:00
if (x2 < 0 || x2 >= 256 || y2 < 0 || y2 >= 256)
2008-07-23 15:15:46 +00:00
luaL_error(L,"bad coordinates");
gui_prepare();
// Horizontal line?
if (y1 == y2) {
if (x1 > x2)
swap(int, x1, x2);
int i;
for (i=x1; i <= x2; i++)
gui_data[y1*256+i] = colour;
} else if (x1 == x2) { // Vertical line?
if (y1 > y2)
swap(int, y1, y2);
int i;
for (i=y1; i < y2; i++)
gui_data[i*256+x1] = colour;
} else {
// Some very real slope. We want to increase along the x value, so we swap for that.
if (x1 > x2) {
swap(int, x1, x2);
swap(int, y1, y2);
}
double slope = ((double)y2-(double)y1) / ((double)x2-(double)x1);
int myX = x1, myY = y1;
double accum = 0;
while (myX <= x2) {
// Draw the current pixel
gui_data[myY*256 + myX] = colour;
// If it's above 1, we knock 1 off it and go up 1 pixel
if (accum >= 1.0) {
myY += 1;
accum -= 1.0;
} else if (accum <= -1.0) {
myY -= 1;
accum += 1.0;
} else {
myX += 1;
accum += slope; // Step up
}
}
}
return 0;
}
// gui.drawbox(x1, y1, x2, y2, colour)
static int gui_drawbox(lua_State *L) {
int x1,y1,x2,y2;
uint8 colour;
int i;
x1 = luaL_checkinteger(L,1);
y1 = luaL_checkinteger(L,2);
x2 = luaL_checkinteger(L,3);
y2 = luaL_checkinteger(L,4);
colour = gui_getcolour(L,5);
2008-07-29 00:42:02 +00:00
if (x1 < 0 || x1 >= 256 || y1 < 0 || y1 >= 256)
2008-07-23 15:15:46 +00:00
luaL_error(L,"bad coordinates");
2008-07-29 00:42:02 +00:00
if (x2 < 0 || x2 >= 256 || y2 < 0 || y2 >= 256)
2008-07-23 15:15:46 +00:00
luaL_error(L,"bad coordinates");
gui_prepare();
// For simplicity, we mandate that x1,y1 be the upper-left corner
if (x1 > x2)
swap(int, x1, x2);
if (y1 > y2)
swap(int, y1, y2);
// top surface
for (i=x1; i <= x2; i++)
gui_data[y1*256 + i] = colour;
// bottom surface
for (i=x1; i <= x2; i++)
gui_data[y2*256 + i] = colour;
// left surface
for (i=y1; i <= y2; i++)
gui_data[i*256+x1] = colour;
// right surface
for (i=y1; i <= y2; i++)
gui_data[i*256+x2] = colour;
return 0;
}
// gui.gdscreenshot()
//
// Returns a screen shot as a string in gd's v1 file format.
// This allows us to make screen shots available without gd installed locally.
// Users can also just grab pixels via substring selection.
//
// I think... Does lua support grabbing byte values from a string?
// Well, either way, just install gd and do what you like with it.
// It really is easier that way.
static int gui_gdscreenshot(lua_State *L) {
// Eat the stack
lua_settop(L,0);
// This is QUITE nasty...
2008-07-29 00:42:02 +00:00
const int width=256, height=256;
2008-07-23 15:15:46 +00:00
// Stack allocation
unsigned char *buffer = (unsigned char*)alloca(2+2+2+1+4 + (width*height*4));
unsigned char *pixels = (buffer + 2+2+2+1+4);
// Truecolour image
buffer[0] = 255;
buffer[1] = 254;
// Width
buffer[2] = width >> 8;
buffer[3] = width & 0xFF;
// height
buffer[4] = height >> 8;
buffer[5] = height & 0xFF;
// Make it truecolour... AGAIN?
buffer[6] = 1;
// No transparency
buffer[7] = buffer[8] = buffer[9] = buffer[10] = 255;
// Now we can actually save the image data
int i = 0;
int x,y;
2008-07-29 00:42:02 +00:00
for (y=0; y < height; y++) {
2008-07-23 15:15:46 +00:00
for (x=0; x < width; x++) {
uint8 index = XBuf[y*256 + x];
// Write A,R,G,B (alpha=0 for us):
pixels[i] = 0;
FCEUD_GetPalette(index, &pixels[i+1],&pixels[i+2], &pixels[i+3]);
i += 4;
}
}
// Ugh, ugh, ugh. Don't call this more than once a frame, for god's sake!
lua_pushlstring(L, (char*)buffer, 2+2+2+1+4 + (width*height*4));
// Buffers allocated with alloca are freed by the function's exit, since they're on the stack.
return 1;
}
// gui.transparency(int strength)
//
// 0 = solid,
static int gui_transparency(lua_State *L) {
int trans = luaL_checkinteger(L,1);
if (trans < 0 || trans > 4) {
luaL_error(L, "transparency out of range");
}
transparency = trans;
return 0;
}
// gui.text(int x, int y, string msg)
//
// Displays the given text on the screen, using the same font and techniques as the
// main HUD.
static int gui_text(lua_State *L) {
const char *msg;
int x, y;
x = luaL_checkinteger(L,1);
y = luaL_checkinteger(L,2);
msg = luaL_checkstring(L,3);
2008-07-29 00:42:02 +00:00
if (x < 0 || x >= 256 || y < 0 || y >= 256)
2008-07-23 15:15:46 +00:00
luaL_error(L,"bad coordinates");
gui_prepare();
2008-07-29 00:42:02 +00:00
DrawTextTransWH(gui_data+y*256+x, 256, (uint8 *)msg, 0x20+0x80, 256 - x, 256 - y);
2008-07-23 15:15:46 +00:00
return 0;
}
// gui.gdoverlay(string str)
//
// Overlays the given image on the screen.
static int gui_gdoverlay(lua_State *L) {
int baseX, baseY;
int width, height;
size_t size;
baseX = luaL_checkinteger(L,1);
baseY = luaL_checkinteger(L,2);
const uint8 *data = (const uint8*) luaL_checklstring(L, 3, &size);
if (size < 11)
luaL_error(L, "bad image data");
if (data[0] != 255 || data[1] != 254)
luaL_error(L, "bad image data or not truecolour");
width = data[2] << 8 | data[3];
height = data[4] << 8 | data[5];
if (!data[6])
luaL_error(L, "bad image data or not truecolour");
// Don't care about transparent colour
if ((int)size < (11+ width*height*4))
luaL_error(L, "bad image data");
const uint8* pixels = data + 11;
// Run image
gui_prepare();
// These coordinates are relative to the image itself.
int x,y;
// These coordinates are relative to the screen
int sx, sy;
if (baseY < 0) {
// Above the top of the screen
sy = 0;
y = -baseY;
} else {
// It starts on the screen itself
sy = baseY;
y = 0;
}
2008-07-29 00:42:02 +00:00
for (; y < height && sy < 256; y++, sy++) {
2008-07-23 15:15:46 +00:00
if (baseX < 0) {
x = -baseX;
sx = 0;
} else {
x = 0;
sx = baseX;
}
for (; x < width && sx < 256; x++, sx++) {
if (pixels[4 * (y*height+x)] == 127)
continue;
uint8 r = pixels[4 * (y*width+x)+1];
uint8 g = pixels[4 * (y*width+x)+2];
uint8 b = pixels[4 * (y*width+x)+3];
gui_data[256*(sy)+sx] = gui_colour_rgb(r, g, b);
}
}
return 0;
}
// function gui.register(function f)
//
// This function will be called just before a graphical update.
// More complicated, but doesn't suffer any frame delays.
// Nil will be accepted in place of a function to erase
// a previously registered function, and the previous function
// (if any) is returned, or nil if none.
static int gui_register(lua_State *L) {
// We'll do this straight up.
// First set up the stack.
lua_settop(L,1);
// Verify the validity of the entry
if (!lua_isnil(L,1))
luaL_checktype(L, 1, LUA_TFUNCTION);
// Get the old value
lua_getfield(L, LUA_REGISTRYINDEX, guiCallbackTable);
// Save the new value
lua_pushvalue(L,1);
lua_setfield(L, LUA_REGISTRYINDEX, guiCallbackTable);
// The old value is on top of the stack. Return it.
return 1;
}
// string gui.popup(string message, [string type = "ok"])
//
// Popup dialog!
int gui_popup(lua_State *L) {
const char *message = luaL_checkstring(L, 1);
const char *type = luaL_optstring(L, 2, "ok");
2008-07-27 17:31:59 +00:00
#ifdef WIN32
2008-07-23 15:15:46 +00:00
int t;
if (strcmp(type, "ok") == 0)
t = MB_OK;
else if (strcmp(type, "yesno") == 0)
t = MB_YESNO;
else if (strcmp(type, "yesnocancel") == 0)
t = MB_YESNOCANCEL;
else
return luaL_error(L, "invalid popup type \"%s\"", type);
2008-07-27 17:31:59 +00:00
//StopSound(); //mbg merge 7/27/08
2008-07-23 15:15:46 +00:00
int result = MessageBox(hAppWnd, message, "Lua Script Pop-up", t);
lua_settop(L,1);
if (t != MB_OK) {
if (result == IDYES)
lua_pushstring(L, "yes");
else if (result == IDNO)
lua_pushstring(L, "no");
else if (result == IDCANCEL)
lua_pushstring(L, "cancel");
else
luaL_error(L, "win32 unrecognized return value %d", result);
return 1;
}
// else, we don't care.
return 0;
#else
char *t;
#ifdef __linux
int pid; // appease compiler
// Before doing any work, verify the correctness of the parameters.
if (strcmp(type, "ok") == 0)
t = "OK:100";
else if (strcmp(type, "yesno") == 0)
t = "Yes:100,No:101";
else if (strcmp(type, "yesnocancel") == 0)
t = "Yes:100,No:101,Cancel:102";
else
return luaL_error(L, "invalid popup type \"%s\"", type);
// Can we find a copy of xmessage? Search the path.
char *path = strdup(getenv("PATH"));
char *current = path;
char *colon;
int found = 0;
while (current) {
colon = strchr(current, ':');
// Clip off the colon.
*colon++ = 0;
int len = strlen(current);
char *filename = (char*)malloc(len + 12); // always give excess
snprintf(filename, len+12, "%s/xmessage", current);
if (access(filename, X_OK) == 0) {
free(filename);
found = 1;
break;
}
// Failed, move on.
current = colon;
free(filename);
}
free(path);
// We've found it?
if (!found)
goto use_console;
pid = fork();
if (pid == 0) {// I'm the virgin sacrifice
// I'm gonna be dead in a matter of microseconds anyways, so wasted memory doesn't matter to me.
// Go ahead and abuse strdup.
char * parameters[] = {"xmessage", "-buttons", t, strdup(message), NULL};
execvp("xmessage", parameters);
// Aw shitty
perror("exec xmessage");
exit(1);
}
else if (pid < 0) // something went wrong!!! Oh hell... use the console
goto use_console;
else {
// We're the parent. Watch for the child.
int r;
int res = waitpid(pid, &r, 0);
if (res < 0) // wtf?
goto use_console;
// The return value gets copmlicated...
if (!WIFEXITED(r)) {
luaL_error(L, "don't screw with my xmessage process!");
}
r = WEXITSTATUS(r);
// We assume it's worked.
if (r == 0)
{
return 0; // no parameters for an OK
}
if (r == 100) {
lua_pushstring(L, "yes");
return 1;
}
if (r == 101) {
lua_pushstring(L, "no");
return 1;
}
if (r == 102) {
lua_pushstring(L, "cancel");
return 1;
}
// Wtf?
return luaL_error(L, "popup failed due to unknown results involving xmessage (%d)", r);
}
use_console:
#endif
// All else has failed
if (strcmp(type, "ok") == 0)
t = "";
else if (strcmp(type, "yesno") == 0)
t = "yn";
else if (strcmp(type, "yesnocancel") == 0)
t = "ync";
else
return luaL_error(L, "invalid popup type \"%s\"", type);
fprintf(stderr, "Lua Message: %s\n", message);
while (TRUE) {
char buffer[64];
// We don't want parameters
if (!t[0]) {
fprintf(stderr, "[Press Enter]");
fgets(buffer, sizeof(buffer), stdin);
// We're done
return 0;
}
fprintf(stderr, "(%s): ", t);
fgets(buffer, sizeof(buffer), stdin);
// Check if the option is in the list
if (strchr(t, tolower(buffer[0]))) {
switch (tolower(buffer[0])) {
case 'y':
lua_pushstring(L, "yes");
return 1;
case 'n':
lua_pushstring(L, "no");
return 1;
case 'c':
lua_pushstring(L, "cancel");
return 1;
default:
luaL_error(L, "internal logic error in console based prompts for gui.popup");
}
}
// We fell through, so we assume the user answered wrong and prompt again.
}
// Nothing here, since the only way out is in the loop.
#endif
}
// int AND(int one, int two, ..., int n)
//
// Since Lua doesn't provide binary, I provide this function.
// Does a full binary AND on all parameters and returns the result.
static int base_AND(lua_State *L) {
int count = lua_gettop(L);
lua_Integer result = ~((lua_Integer)0);
int i;
for (i=1; i <= count; i++)
result &= luaL_checkinteger(L,i);
lua_settop(L,0);
lua_pushinteger(L, result);
return 1;
}
// int OR(int one, int two, ..., int n)
//
// ..and similarly for a binary OR
static int base_OR(lua_State *L) {
int count = lua_gettop(L);
lua_Integer result = 0;
int i;
for (i=1; i <= count; i++)
result |= luaL_checkinteger(L,i);
lua_settop(L,0);
lua_pushinteger(L, result);
return 1;
}
// int XOR(int one, int two, ..., int n)
//
// ..and similarly for a binary XOR
static int base_XOR(lua_State *L) {
int count = lua_gettop(L);
lua_Integer result = 0;
int i;
for (i=1; i <= count; i++)
result ^= luaL_checkinteger(L,i);
lua_settop(L,0);
lua_pushinteger(L, result);
return 1;
}
// int BIT(int one, int two, ..., int n)
//
// Returns a number with the specified bit(s) set.
static int base_BIT(lua_State *L) {
int count = lua_gettop(L);
lua_Integer result = 0;
int i;
for (i=1; i <= count; i++)
result |= (lua_Integer)1 << luaL_checkinteger(L,i);
lua_settop(L,0);
lua_pushinteger(L, result);
return 1;
}
// The function called periodically to ensure Lua doesn't run amok.
static void FCEU_LuaHookFunction(lua_State *L, lua_Debug *dbg) {
if (numTries-- == 0) {
int kill = 0;
#ifdef WIN32
// Uh oh
//StopSound(); //mbg merge 7/23/08
int ret = MessageBox(hAppWnd, "The Lua script running has been running a long time. It may have gone crazy. Kill it?\n\n(No = don't check anymore either)", "Lua Script Gone Nuts?", MB_YESNO);
if (ret == IDYES) {
kill = 1;
}
#else
fprintf(stderr, "The Lua script running has been running a long time.\nIt may have gone crazy. Kill it? (I won't ask again if you say No)\n");
char buffer[64];
while (TRUE) {
fprintf(stderr, "(y/n): ");
fgets(buffer, sizeof(buffer), stdin);
if (buffer[0] == 'y' || buffer[0] == 'Y') {
kill = 1;
break;
}
if (buffer[0] == 'n' || buffer[0] == 'N')
break;
}
#endif
if (kill) {
luaL_error(L, "Killed by user request.");
FCEU_LuaOnStop();
}
// else, kill the debug hook.
lua_sethook(L, NULL, 0, 0);
}
}
static const struct luaL_reg fceulib [] = {
{"speedmode", fceu_speedmode},
{"frameadvance", fceu_frameadvance},
{"pause", fceu_pause},
{"message", fceu_message},
{NULL,NULL}
};
static const struct luaL_reg memorylib [] = {
{"readbyte", memory_readbyte},
{"readbyterange", memory_readbyterange},
2008-07-23 15:15:46 +00:00
{"readbytesigned", memory_readbytesigned},
{"writebyte", memory_writebyte},
{"register", memory_register},
{NULL,NULL}
};
static const struct luaL_reg joypadlib[] = {
{"read", joypad_read},
{"set", joypad_set},
{NULL,NULL}
};
static const struct luaL_reg savestatelib[] = {
{"create", savestate_create},
{"save", savestate_save},
{"load", savestate_load},
{NULL,NULL}
};
static const struct luaL_reg movielib[] = {
{"framecount", movie_framecount},
{"mode", movie_mode},
{"rerecordcounting", movie_rerecordcounting},
{"stop", movie_stop},
// {"record", movie_record},
// {"playback", movie_playback},
{NULL,NULL}
};
static const struct luaL_reg guilib[] = {
{"drawpixel", gui_drawpixel},
{"drawline", gui_drawline},
{"drawbox", gui_drawbox},
{"text", gui_text},
{"gdscreenshot", gui_gdscreenshot},
{"gdoverlay", gui_gdoverlay},
{"transparency", gui_transparency},
{"register", gui_register},
{"popup", gui_popup},
{NULL,NULL}
};
void FCEU_LuaFrameBoundary() {
// printf("Lua Frame\n");
// HA!
2008-07-29 00:42:02 +00:00
if (!L || !luaRunning)
2008-07-23 15:15:46 +00:00
return;
// Our function needs calling
2008-07-29 00:42:02 +00:00
lua_settop(L,0);
lua_getfield(L, LUA_REGISTRYINDEX, frameAdvanceThread);
lua_State *thread = lua_tothread(L,1);
2008-07-23 15:15:46 +00:00
// Lua calling C must know that we're busy inside a frame boundary
frameBoundary = TRUE;
frameAdvanceWaiting = FALSE;
numTries = 1000;
int result = lua_resume(thread, 0);
if (result == LUA_YIELD) {
// Okay, we're fine with that.
} else if (result != 0) {
// Done execution by bad causes
FCEU_LuaOnStop();
2008-07-29 00:42:02 +00:00
lua_pushnil(L);
lua_setfield(L, LUA_REGISTRYINDEX, frameAdvanceThread);
2008-07-23 15:15:46 +00:00
// Error?
#ifdef WIN32
//StopSound();//StopSound(); //mbg merge 7/23/08
MessageBox( hAppWnd, lua_tostring(thread,-1), "Lua run error", MB_OK | MB_ICONSTOP);
#else
fprintf(stderr, "Lua thread bombed out: %s\n", lua_tostring(thread,-1));
#endif
} else {
FCEU_LuaStop();
2008-07-23 15:15:46 +00:00
FCEU_DispMessage("Script died of natural causes.\n");
}
// Past here, the nes actually runs, so any Lua code is called mid-frame. We must
// not do anything too stupid, so let ourselves know.
frameBoundary = FALSE;
if (!frameAdvanceWaiting) {
FCEU_LuaOnStop();
}
}
/**
* Loads and runs the given Lua script.
* The emulator MUST be paused for this function to be
* called. Otherwise, all frame boundary assumptions go out the window.
*
* Returns true on success, false on failure.
*/
int FCEU_LoadLuaCode(const char *filename) {
if (filename != luaScriptName)
{
if (luaScriptName) free(luaScriptName);
luaScriptName = strdup(filename);
}
//stop any lua we might already have had running
FCEU_LuaStop();
2008-07-29 00:42:02 +00:00
if (!L) {
2008-07-27 16:22:45 +00:00
#ifdef WIN32
HMODULE test = LoadLibrary("lua5.1.dll");
if(!test)
{
FCEUD_PrintError("Couldn't initialize lua system due to failure loading lua5.1.dll");
return 0;
}
FreeLibrary(test);
#endif
2008-07-29 00:42:02 +00:00
L = lua_open();
luaL_openlibs(L);
luaL_register(L, "FCEU", fceulib);
luaL_register(L, "memory", memorylib);
luaL_register(L, "joypad", joypadlib);
luaL_register(L, "savestate", savestatelib);
luaL_register(L, "movie", movielib);
luaL_register(L, "gui", guilib);
lua_pushcfunction(L, base_AND);
lua_setfield(L, LUA_GLOBALSINDEX, "AND");
lua_pushcfunction(L, base_OR);
lua_setfield(L, LUA_GLOBALSINDEX, "OR");
lua_pushcfunction(L, base_XOR);
lua_setfield(L, LUA_GLOBALSINDEX, "XOR");
lua_pushcfunction(L, base_BIT);
lua_setfield(L, LUA_GLOBALSINDEX, "BIT");
lua_newtable(L);
lua_setglobal(L,"emu");
lua_getglobal(L,"emu");
lua_newtable(L);
lua_setfield(L,-2,"OnClose");
2008-07-23 15:15:46 +00:00
2008-07-29 00:42:02 +00:00
lua_newtable(L);
lua_setfield(L, LUA_REGISTRYINDEX, memoryWatchTable);
lua_newtable(L);
lua_setfield(L, LUA_REGISTRYINDEX, memoryValueTable);
2008-07-23 15:15:46 +00:00
}
// We make our thread NOW because we want it at the bottom of the stack.
// If all goes wrong, we let the garbage collector remove it.
2008-07-29 00:42:02 +00:00
lua_State *thread = lua_newthread(L);
2008-07-23 15:15:46 +00:00
// Load the data
2008-07-29 00:42:02 +00:00
int result = luaL_loadfile(L,filename);
2008-07-23 15:15:46 +00:00
if (result) {
#ifdef WIN32
// Doing this here caused nasty problems; reverting to MessageBox-from-dialog behavior.
//StopSound();//StopSound(); //mbg merge 7/23/08
2008-07-29 00:42:02 +00:00
MessageBox(NULL, lua_tostring(L,-1), "Lua load error", MB_OK | MB_ICONSTOP);
2008-07-23 15:15:46 +00:00
#else
2008-07-29 00:42:02 +00:00
fprintf(stderr, "Failed to compile file: %s\n", lua_tostring(L,-1));
2008-07-23 15:15:46 +00:00
#endif
// Wipe the stack. Our thread
2008-07-29 00:42:02 +00:00
lua_settop(L,0);
2008-07-23 15:15:46 +00:00
return 0; // Oh shit.
}
// Get our function into it
2008-07-29 00:42:02 +00:00
lua_xmove(L, thread, 1);
2008-07-23 15:15:46 +00:00
// Save the thread to the registry. This is why I make the thread FIRST.
2008-07-29 00:42:02 +00:00
lua_setfield(L, LUA_REGISTRYINDEX, frameAdvanceThread);
2008-07-23 15:15:46 +00:00
// Initialize settings
luaRunning = TRUE;
skipRerecords = FALSE;
wasPaused = FCEUI_EmulationPaused();
if (wasPaused) FCEUI_ToggleEmulationPause();
// And run it right now. :)
//FCEU_LuaFrameBoundary();
// Set up our protection hook to be executed once every 10,000 bytecode instructions.
//lua_sethook(thread, FCEU_LuaHookFunction, LUA_MASKCOUNT, 10000);
2008-07-23 15:15:46 +00:00
// We're done.
return 1;
}
/**
* Equivalent to repeating the last FCEU_LoadLuaCode() call.
*/
void FCEU_ReloadLuaCode()
{
if (!luaScriptName)
FCEU_DispMessage("There's no script to reload.");
else
FCEU_LoadLuaCode(luaScriptName);
}
/**
* Terminates a running Lua script by killing the whole Lua engine.
*
* Always safe to call, except from within a lua call itself (duh).
*
*/
void FCEU_LuaStop() {
//already killed
2008-07-29 00:42:02 +00:00
if (!L) return;
//execute the user's shutdown callbacks
//onCloseCallback
lua_getglobal(L, "emu");
lua_getfield(L, -1, "OnClose");
lua_pushnil(L);
while (lua_next(L, -2) != 0)
{
lua_call(L,0,0);
2008-07-23 15:15:46 +00:00
}
2008-07-29 00:42:02 +00:00
//sometimes iup uninitializes com
2008-07-30 15:02:10 +00:00
//MBG TODO - test whether this is really necessary. i dont think it is
#ifdef WIN32
2008-07-29 00:42:02 +00:00
CoInitialize(0);
2008-07-30 15:02:10 +00:00
#endif
2008-07-29 00:42:02 +00:00
//lua_gc(L,LUA_GCCOLLECT,0);
lua_close(L); // this invokes our garbage collectors for us
L = NULL;
FCEU_LuaOnStop();
2008-07-23 15:15:46 +00:00
}
/**
* Returns true if there is a Lua script running.
*
*/
int FCEU_LuaRunning() {
2008-07-29 00:42:02 +00:00
return L && luaRunning;
2008-07-23 15:15:46 +00:00
}
/**
* Returns true if Lua would like to steal the given joypad control.
*/
int FCEU_LuaUsingJoypad(int which) {
return lua_joypads_used & (1 << which);
}
/**
* Reads the buttons Lua is feeding for the given joypad, in the same
* format as the OS-specific code.
*
* This function must not be called more than once per frame. Ideally exactly once
* per frame (if FCEU_LuaUsingJoypad says it's safe to do so)
*/
uint8 FCEU_LuaReadJoypad(int which) {
lua_joypads_used &= !(1 << which);
return lua_joypads[which];
}
/**
* If this function returns true, the movie code should NOT increment
* the rerecord count for a load-state.
*
* This function will not return true if a script is not running.
*/
int FCEU_LuaRerecordCountSkip() {
2008-07-29 00:42:02 +00:00
return L && luaRunning && skipRerecords;
2008-07-23 15:15:46 +00:00
}
/**
* Given an 8-bit screen with the indicated resolution,
* draw the current GUI onto it.
*
* Currently we only support 256x* resolutions.
*/
void FCEU_LuaGui(uint8 *XBuf) {
2008-07-29 00:42:02 +00:00
if (!L || !luaRunning)
2008-07-23 15:15:46 +00:00
return;
// First, check if we're being called by anybody
2008-07-29 00:42:02 +00:00
lua_getfield(L, LUA_REGISTRYINDEX, guiCallbackTable);
2008-07-23 15:15:46 +00:00
2008-07-29 00:42:02 +00:00
if (lua_isfunction(L, -1)) {
2008-07-23 15:15:46 +00:00
// We call it now
numTries = 1000;
2008-07-29 00:42:02 +00:00
int ret = lua_pcall(L, 0, 0, 0);
2008-07-23 15:15:46 +00:00
if (ret != 0) {
#ifdef WIN32
//StopSound();//StopSound(); //mbg merge 7/23/08
2008-07-29 00:42:02 +00:00
MessageBox(hAppWnd, lua_tostring(L, -1), "Lua Error in GUI function", MB_OK);
2008-07-23 15:15:46 +00:00
#else
2008-07-29 00:42:02 +00:00
fprintf(stderr, "Lua error in gui.register function: %s\n", lua_tostring(L, -1));
2008-07-23 15:15:46 +00:00
#endif
// This is grounds for trashing the function
2008-07-29 00:42:02 +00:00
lua_pushnil(L);
lua_setfield(L, LUA_REGISTRYINDEX, guiCallbackTable);
2008-07-23 15:15:46 +00:00
}
}
// And wreak the stack
2008-07-29 00:42:02 +00:00
lua_settop(L, 0);
2008-07-23 15:15:46 +00:00
if (gui_used == GUI_CLEAR)
return;
gui_used = GUI_USED_SINCE_LAST_FRAME;
int x,y;
if (transparency == 4) // wtf?
return;
// direct copy
if (transparency == 0) {
2008-07-29 00:42:02 +00:00
for (y = 0; y < 256; y++) {
2008-07-23 15:15:46 +00:00
for (x=0; x < 256; x++) {
if (gui_data[y*256+x] != GUI_COLOUR_CLEAR)
XBuf[y*256 + x] = gui_data[y*256+x];
}
}
} else {
2008-07-29 00:42:02 +00:00
for (y = 0; y < 256; y++) {
2008-07-23 15:15:46 +00:00
for (x=0; x < 256; x++) {
if (gui_data[y*256+x] != GUI_COLOUR_CLEAR) {
uint8 rg, gg, bg, rx, gx, bx, r, g, b;
FCEUD_GetPalette(gui_data[y*256+x], &rg, &gg, &bg);
FCEUD_GetPalette( XBuf[y*256+x], &rx, &gx, &bx);
r = (rg * (4 - transparency) + rx * transparency) / 4;
g = (gg * (4 - transparency) + gx * transparency) / 4;
b = (bg * (4 - transparency) + bx * transparency) / 4;
XBuf[y*256+x] = gui_colour_rgb(r, g, b);
}
}
}
}
return;
}
2008-07-24 03:18:48 +00:00