mirror of
https://github.com/libretro/gw-libretro.git
synced 2024-11-30 11:20:21 +00:00
copy gwrom and gwlua source instead of using submodules
This commit is contained in:
parent
6615a483b5
commit
06b4cc4571
11
gwlua/LICENSE
Normal file
11
gwlua/LICENSE
Normal file
@ -0,0 +1,11 @@
|
||||
The zlib/libpng License
|
||||
|
||||
Copyright (c) <year> <copyright holders>
|
||||
|
||||
This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
25
gwlua/README.md
Normal file
25
gwlua/README.md
Normal file
@ -0,0 +1,25 @@
|
||||
# gwlua
|
||||
|
||||
A middleware to write Game & Watch-like games in [Lua](http://www.lua.org/). Provides pictures, sprites, timers and sounds.
|
||||
|
||||
**gwlua** is a *middleware*, meaning it knows nothing about how sound is actually played and sprites blitted onto the screen. You have to provide functions to do that according to the platform you're targetting.
|
||||
|
||||
For an working example of **gwlua**, see [gw-libretro](https://github.com/libretro/gw-libretro).
|
||||
|
||||
## Usage
|
||||
|
||||
Add gwlua.c and gwlua.h to your project. See gwlua.h for some documentation.
|
||||
|
||||
## Rationale
|
||||
|
||||
When I started porting [MADrigal](http://www.madrigaldesign.it/sim/)'s Game & Watch simulators to [libretro](http://www.libretro.com/), I had to make a decision on how to take advantage of the existing simulators' source code. The simulators are written in Delphi, so I could:
|
||||
|
||||
1. Try to compile the original source code with [Free Pascal](http://www.freepascal.org/): This would turn each simulator into its own libretro core, instead of a content able to be played by a core. In addition, I'd have to provide functions to make the Delphi compiled work, like Application and Form classes.
|
||||
1. Try to compile the original source code with [Free Pascal](http://www.freepascal.org/) to some machine code (I was thinking about MIPS) and add a CPU emulator to the libretro core: This would let me package each game into a content, but I'd have to bootstrap high level stuff as machine code, i.e. picture loading and blitting.
|
||||
1. Rewrite the original source code in another language: This would free me from Free Pascal, but would mean each simulator is a separate core just like the first option.
|
||||
|
||||
So I decided to go with an option that I believe has the best mix: rewrite the Pascal code in Lua, and package the Lua source code along with assets into a libretro content. The libretro core would then be able to read the archive and run the Lua code, which in turn can easily call native functions to deal with IO etc.
|
||||
|
||||
## Why Lua
|
||||
|
||||
I have experience with both the language and on embedding it in C/C++ programs, and it's syntax is similar to that of Pascal. In fact, I wrote a translator that takes Pascal source code and generates Lua source code. The translator is far from perfect, but it does the hard work, leaving me to just tweak a few lines.
|
981
gwlua/gwlua.c
Normal file
981
gwlua/gwlua.c
Normal file
@ -0,0 +1,981 @@
|
||||
#include <gwlua.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <lua.h>
|
||||
#include <lauxlib.h>
|
||||
#include <lualib.h>
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
|
||||
typedef struct
|
||||
{
|
||||
gwlua_state_t user;
|
||||
|
||||
lua_State* L;
|
||||
int bg_ref;
|
||||
int tick_ref;
|
||||
int button_down_ref;
|
||||
int button_up_ref;
|
||||
int64_t now;
|
||||
uint64_t seed;
|
||||
}
|
||||
state_t;
|
||||
|
||||
#ifdef NDEBUG
|
||||
void lua_setstateud( lua_State* L, void* ud );
|
||||
void* lua_getstateud( lua_State* L );
|
||||
#else
|
||||
static void* s_ud = NULL;
|
||||
void lua_setstateud( lua_State* L, void* ud ) { s_ud = ud; }
|
||||
void* lua_getstateud( lua_State* L ) { return s_ud; }
|
||||
#endif
|
||||
|
||||
static state_t* state_get( lua_State* L )
|
||||
{
|
||||
return (state_t*)lua_getstateud( L );
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
static void dump_stack( lua_State* L )
|
||||
{
|
||||
int top = lua_gettop( L );
|
||||
int i;
|
||||
|
||||
for ( i = 1; i <= top; i++ )
|
||||
{
|
||||
printf( "%2d %3d ", i, i - top - 1 );
|
||||
|
||||
lua_pushvalue( L, i );
|
||||
|
||||
switch ( lua_type( L, -1 ) )
|
||||
{
|
||||
case LUA_TNIL:
|
||||
printf( "nil\n" );
|
||||
break;
|
||||
case LUA_TNUMBER:
|
||||
printf( "%e\n", lua_tonumber( L, -1 ) );
|
||||
break;
|
||||
case LUA_TBOOLEAN:
|
||||
printf( "%s\n", lua_toboolean( L, -1 ) ? "true" : "false" );
|
||||
break;
|
||||
case LUA_TSTRING:
|
||||
printf( "\"%s\"\n", lua_tostring( L, -1 ) );
|
||||
break;
|
||||
case LUA_TTABLE:
|
||||
printf( "table\n" );
|
||||
break;
|
||||
case LUA_TFUNCTION:
|
||||
printf( "function\n" );
|
||||
break;
|
||||
case LUA_TUSERDATA:
|
||||
printf( "userdata\n" );
|
||||
break;
|
||||
case LUA_TTHREAD:
|
||||
printf( "thread\n" );
|
||||
break;
|
||||
case LUA_TLIGHTUSERDATA:
|
||||
printf( "light userdata\n" );
|
||||
break;
|
||||
default:
|
||||
printf( "?\n" );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
|
||||
static const char* str_int64( char* str_end, int64_t i64 )
|
||||
{
|
||||
int neg = 0;
|
||||
|
||||
if ( i64 < 0 )
|
||||
{
|
||||
neg = 1;
|
||||
i64 = -i64;
|
||||
}
|
||||
|
||||
uint64_t u64 = (uint64_t)i64;
|
||||
*--str_end = 0;
|
||||
|
||||
do
|
||||
{
|
||||
*--str_end = u64 % 10 + '0';
|
||||
u64 /= 10;
|
||||
}
|
||||
while ( u64 );
|
||||
|
||||
if ( neg )
|
||||
{
|
||||
*--str_end = '-';
|
||||
}
|
||||
|
||||
return str_end;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
|
||||
static int traceback( lua_State* L )
|
||||
{
|
||||
luaL_traceback( L, L, lua_tostring( L, -1 ), 1 );
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
|
||||
typedef void* (*ud_checker)( lua_State*, int );
|
||||
|
||||
static void* ref_create( lua_State* L, int index, int* ref, ud_checker checker, int empty_ok )
|
||||
{
|
||||
if ( empty_ok && lua_isnoneornil( L, index ) )
|
||||
{
|
||||
*ref = LUA_NOREF;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
lua_pushvalue( L, index );
|
||||
*ref = luaL_ref( L, LUA_REGISTRYINDEX );
|
||||
return checker( L, index );
|
||||
}
|
||||
|
||||
static void ref_destroy( lua_State* L, int* ref )
|
||||
{
|
||||
if ( *ref != LUA_NOREF )
|
||||
{
|
||||
luaL_unref( L, LUA_REGISTRYINDEX, *ref );
|
||||
*ref = LUA_NOREF;
|
||||
}
|
||||
}
|
||||
|
||||
static void* ref_new( lua_State* L, int index, int* ref, ud_checker checker, int empty_ok )
|
||||
{
|
||||
ref_destroy( L, ref );
|
||||
return ref_create( L, index, ref, checker, empty_ok );
|
||||
}
|
||||
|
||||
static void ref_get( lua_State* L, int ref )
|
||||
{
|
||||
if ( ref != LUA_NOREF )
|
||||
{
|
||||
lua_rawgeti( L, LUA_REGISTRYINDEX, ref );
|
||||
}
|
||||
else
|
||||
{
|
||||
lua_pushnil( L );
|
||||
}
|
||||
}
|
||||
|
||||
static void* function_check( lua_State* L, int index )
|
||||
{
|
||||
luaL_checktype( L, index, LUA_TFUNCTION );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
|
||||
static uint32_t djb2( const char* str )
|
||||
{
|
||||
const uint8_t* aux = (const uint8_t*)str;
|
||||
uint32_t hash = 5381;
|
||||
|
||||
while ( *aux )
|
||||
{
|
||||
hash = ( hash << 5 ) + hash + *aux++;
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
|
||||
typedef struct
|
||||
{
|
||||
gwlua_picture_t user;
|
||||
int parent_ref;
|
||||
}
|
||||
picture_t;
|
||||
|
||||
static picture_t* picture_check( lua_State* L, int index )
|
||||
{
|
||||
return (picture_t*)luaL_checkudata( L, index, "picture" );
|
||||
}
|
||||
|
||||
static int picture_gc( lua_State* L )
|
||||
{
|
||||
picture_t* self = (picture_t*)lua_touserdata( L, 1 );
|
||||
ref_destroy( L, &self->parent_ref );
|
||||
gwlua_destroy_picture( (gwlua_state_t*)state_get( L ), (gwlua_picture_t*)self );
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int picture_push( lua_State* L )
|
||||
{
|
||||
/* the userdata object must be on the top of the stack */
|
||||
|
||||
if ( luaL_newmetatable( L, "picture" ) != 0 )
|
||||
{
|
||||
static const luaL_Reg methods[] =
|
||||
{
|
||||
{ "__gc", picture_gc },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
luaL_setfuncs( L, methods, 0 );
|
||||
}
|
||||
|
||||
lua_setmetatable( L, -2 );
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int picture_load( lua_State* L )
|
||||
{
|
||||
const char* name = luaL_checkstring( L, 1 );
|
||||
picture_t* self = (picture_t*)lua_newuserdata( L, sizeof( picture_t ) );
|
||||
|
||||
if ( !gwlua_load_picture( (gwlua_state_t*)state_get( L ), (gwlua_picture_t*)self, name ) )
|
||||
{
|
||||
return picture_push( L );
|
||||
}
|
||||
|
||||
return luaL_error( L, "error loading picture %s", name );
|
||||
}
|
||||
|
||||
static int picture_sub( lua_State* L )
|
||||
{
|
||||
picture_t* parent = picture_check( L, 1 );
|
||||
int x0 = luaL_checkinteger( L, 2 );
|
||||
int y0 = luaL_checkinteger( L, 3 );
|
||||
unsigned width = luaL_checkinteger( L, 4 );
|
||||
unsigned height = luaL_checkinteger( L, 5 );
|
||||
|
||||
picture_t* self = (picture_t*)lua_newuserdata( L, sizeof( picture_t ) );
|
||||
|
||||
if ( !gwlua_sub_picture( (gwlua_state_t*)state_get( L ), (gwlua_picture_t*)self, (gwlua_picture_t*)parent, x0, y0, width, height ) )
|
||||
{
|
||||
ref_create( L, 1, &self->parent_ref, (ud_checker)picture_check, 0 );
|
||||
return picture_push( L );
|
||||
}
|
||||
|
||||
return luaL_error( L, "error creating sub picture" );
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int x;
|
||||
int y;
|
||||
int is_visible;
|
||||
|
||||
picture_t* picture;
|
||||
int picture_ref;
|
||||
}
|
||||
sprite_t;
|
||||
|
||||
static sprite_t* sprite_check( lua_State* L, int index )
|
||||
{
|
||||
return (sprite_t*)luaL_checkudata( L, index, "sprite" );
|
||||
}
|
||||
|
||||
static int sprite_gc( lua_State* L )
|
||||
{
|
||||
sprite_t* self = (sprite_t*)lua_touserdata( L, 1 );
|
||||
ref_destroy( L, &self->picture_ref );
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sprite_index( lua_State* L )
|
||||
{
|
||||
sprite_t* self = (sprite_t*)lua_touserdata( L, 1 );
|
||||
const char* key = luaL_checkstring( L, 2 );
|
||||
|
||||
switch ( djb2( key ) )
|
||||
{
|
||||
case 0x7c618d53U: // visible
|
||||
lua_pushboolean( L, self->is_visible );
|
||||
return 1;
|
||||
|
||||
case 0xad68f281U: // picture
|
||||
ref_get( L, self->picture_ref );
|
||||
return 1;
|
||||
|
||||
case 0x7c97e438U: // hint
|
||||
lua_pushliteral( L, "" );
|
||||
return 1;
|
||||
}
|
||||
|
||||
return luaL_error( L, "%s not found in sprite", key );
|
||||
}
|
||||
|
||||
static int sprite_newindex( lua_State* L )
|
||||
{
|
||||
sprite_t* self = (sprite_t*)lua_touserdata( L, 1 );
|
||||
const char* key = luaL_checkstring( L, 2 );
|
||||
|
||||
switch ( djb2( key ) )
|
||||
{
|
||||
case 0x7c618d53U: // visible
|
||||
{
|
||||
int is_visible = lua_toboolean( L, 3 );
|
||||
|
||||
if ( is_visible != self->is_visible && self->picture )
|
||||
{
|
||||
if ( is_visible )
|
||||
{
|
||||
gwlua_blit_picture( (gwlua_state_t*)state_get( L ), (gwlua_picture_t*)self->picture, self->x, self->y );
|
||||
}
|
||||
else
|
||||
{
|
||||
gwlua_unblit_picture( (gwlua_state_t*)state_get( L ), (gwlua_picture_t*)self->picture, self->x, self->y );
|
||||
}
|
||||
}
|
||||
|
||||
self->is_visible = is_visible;
|
||||
return 0;
|
||||
}
|
||||
|
||||
case 0xad68f281U: // picture
|
||||
{
|
||||
picture_t* picture = ref_new( L, 3, &self->picture_ref, (ud_checker)picture_check, 1 );
|
||||
|
||||
if ( picture != self->picture && self->is_visible )
|
||||
{
|
||||
if ( self->picture )
|
||||
{
|
||||
gwlua_unblit_picture( (gwlua_state_t*)state_get( L ), (gwlua_picture_t*)self->picture, self->x, self->y );
|
||||
}
|
||||
|
||||
if ( picture )
|
||||
{
|
||||
gwlua_blit_picture( (gwlua_state_t*)state_get( L ), (gwlua_picture_t*)picture, self->x, self->y );
|
||||
}
|
||||
}
|
||||
|
||||
self->picture = picture;
|
||||
return 0;
|
||||
}
|
||||
|
||||
case 0x7c97e438U: // hint
|
||||
return 0;
|
||||
}
|
||||
|
||||
return luaL_error( L, "%s not found in sprite", key );
|
||||
}
|
||||
|
||||
static int sprite_new( lua_State* L )
|
||||
{
|
||||
int x = luaL_checkinteger( L, 1 );
|
||||
int y = luaL_checkinteger( L, 2 );
|
||||
|
||||
if ( lua_gettop( L ) == 2 )
|
||||
{
|
||||
lua_pushnil( L );
|
||||
}
|
||||
|
||||
sprite_t* self = (sprite_t*)lua_newuserdata( L, sizeof( sprite_t ) );
|
||||
|
||||
self->x = x;
|
||||
self->y = y;
|
||||
self->is_visible = 0;
|
||||
self->picture = ref_create( L, 3, &self->picture_ref, (ud_checker)picture_check, 1 );
|
||||
|
||||
if ( luaL_newmetatable( L, "sprite" ) != 0 )
|
||||
{
|
||||
static const luaL_Reg methods[] =
|
||||
{
|
||||
{ "__index", sprite_index },
|
||||
{ "__newindex", sprite_newindex },
|
||||
{ "__gc", sprite_gc },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
luaL_setfuncs( L, methods, 0 );
|
||||
}
|
||||
|
||||
lua_setmetatable( L, -2 );
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int64_t interval;
|
||||
int64_t expiration;
|
||||
int is_enabled;
|
||||
int callback_ref;
|
||||
}
|
||||
gwtimer_t;
|
||||
|
||||
static gwtimer_t* timer_check( lua_State* L, int index )
|
||||
{
|
||||
return (gwtimer_t*)luaL_checkudata( L, index, "timer" );
|
||||
}
|
||||
|
||||
static int timer_gc( lua_State* L )
|
||||
{
|
||||
gwtimer_t* self = (gwtimer_t*)lua_touserdata( L, 1 );
|
||||
ref_destroy( L, &self->callback_ref );
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int timer_tick( lua_State* L )
|
||||
{
|
||||
gwtimer_t* self = timer_check( L, 1 );
|
||||
state_t* state = state_get( L );
|
||||
|
||||
if ( self->is_enabled && state->now >= self->expiration && self->callback_ref != LUA_NOREF )
|
||||
{
|
||||
self->expiration = self->interval + state->now;
|
||||
|
||||
lua_pushcfunction( L, traceback );
|
||||
ref_get( L, self->callback_ref );
|
||||
lua_pushvalue( L, 1 );
|
||||
|
||||
if ( lua_pcall( L, 1, 0, -3 ) != LUA_OK )
|
||||
{
|
||||
fprintf( stderr, "%s", lua_tostring( L, -1 ) );
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int timer_index( lua_State* L )
|
||||
{
|
||||
gwtimer_t* self = (gwtimer_t*)lua_touserdata( L, 1 );
|
||||
const char* key = luaL_checkstring( L, 2 );
|
||||
|
||||
switch ( djb2( key ) )
|
||||
{
|
||||
case 0x8c344f2aU: // interval
|
||||
lua_pushinteger( L, self->interval / 1000 );
|
||||
return 1;
|
||||
case 0x6a23e990U: // enabled
|
||||
lua_pushboolean( L, self->is_enabled );
|
||||
return 1;
|
||||
case 0xd7deacd3U: // onExpired
|
||||
ref_get( L, self->callback_ref );
|
||||
return 1;
|
||||
case 0x7c9e7750U: // tick
|
||||
lua_pushcfunction( L, timer_tick );
|
||||
return 1;
|
||||
}
|
||||
|
||||
return luaL_error( L, "%s not found in timer", key );
|
||||
}
|
||||
|
||||
static int timer_newindex( lua_State* L )
|
||||
{
|
||||
gwtimer_t* self = (gwtimer_t*)lua_touserdata( L, 1 );
|
||||
const char* key = luaL_checkstring( L, 2 );
|
||||
|
||||
switch ( djb2( key ) )
|
||||
{
|
||||
case 0x8c344f2aU: // interval
|
||||
{
|
||||
self->interval = luaL_checkinteger( L, 3 ) * 1000;
|
||||
state_t* state = state_get( L );
|
||||
self->expiration = self->interval + state->now;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
case 0x6a23e990U: // enabled
|
||||
{
|
||||
self->is_enabled = lua_toboolean( L, 3 );
|
||||
state_t* state = state_get( L );
|
||||
self->expiration = self->interval + state->now;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
case 0xd7deacd3U: // onExpired
|
||||
ref_new( L, 3, &self->callback_ref, function_check, 1 );
|
||||
return 0;
|
||||
}
|
||||
|
||||
return luaL_error( L, "%s not found in timer", key );
|
||||
}
|
||||
|
||||
static int timer_tostring( lua_State* L )
|
||||
{
|
||||
gwtimer_t* self = (gwtimer_t*)lua_touserdata( L, 1 );
|
||||
|
||||
char buf1[ 128 ];
|
||||
const char* interval = str_int64( buf1 + sizeof( buf1 ), self->interval );
|
||||
|
||||
char buf2[ 128 ];
|
||||
const char* expiration = str_int64( buf2 + sizeof( buf2 ), self->expiration );
|
||||
|
||||
lua_pushfstring( L, "timer( interval=%s, expiration=%s, is_enabled=%s, callback_ref=%d )", interval, expiration, self->is_enabled ? "true" : "false", self->callback_ref );
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int timer_new( lua_State* L )
|
||||
{
|
||||
gwtimer_t* self = (gwtimer_t*)lua_newuserdata( L, sizeof( gwtimer_t ) );
|
||||
|
||||
self->is_enabled = 0;
|
||||
|
||||
if ( luaL_newmetatable( L, "timer" ) != 0 )
|
||||
{
|
||||
static const luaL_Reg methods[] =
|
||||
{
|
||||
{ "__index", timer_index },
|
||||
{ "__newindex", timer_newindex },
|
||||
{ "__tostring", timer_tostring },
|
||||
{ "__gc", timer_gc },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
luaL_setfuncs( L, methods, 0 );
|
||||
}
|
||||
|
||||
lua_setmetatable( L, -2 );
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
|
||||
typedef struct
|
||||
{
|
||||
gwlua_sound_t user;
|
||||
}
|
||||
sound_t;
|
||||
|
||||
static sound_t* sound_check( lua_State* L, int index )
|
||||
{
|
||||
return (sound_t*)luaL_checkudata( L, index, "sound" );
|
||||
}
|
||||
|
||||
static int sound_gc( lua_State* L )
|
||||
{
|
||||
sound_t* self = (sound_t*)lua_touserdata( L, 1 );
|
||||
gwlua_destroy_sound( (gwlua_state_t*)state_get( L ), (gwlua_sound_t*)self );
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sound_play( lua_State* L )
|
||||
{
|
||||
sound_t* self = sound_check( L, 1 );
|
||||
gwlua_play_sound( (gwlua_state_t*)state_get( L ), (gwlua_sound_t*)self );
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sound_index( lua_State* L )
|
||||
{
|
||||
const char* key = luaL_checkstring( L, 2 );
|
||||
|
||||
switch ( djb2( key ) )
|
||||
{
|
||||
case 0x7c9c525bU: // play
|
||||
lua_pushcfunction( L, sound_play );
|
||||
return 1;
|
||||
}
|
||||
|
||||
return luaL_error( L, "%s not found in sound", key );
|
||||
}
|
||||
|
||||
static int sound_stop( lua_State* L )
|
||||
{
|
||||
gwlua_stop_all_sounds( (gwlua_state_t*)state_get( L ) );
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sound_newindex( lua_State* L )
|
||||
{
|
||||
const char* key = luaL_checkstring( L, 2 );
|
||||
return luaL_error( L, "%s not found in sound", key );
|
||||
}
|
||||
|
||||
static int sound_load( lua_State* L )
|
||||
{
|
||||
const char* name = luaL_checkstring( L, 1 );
|
||||
sound_t* self = (sound_t*)lua_newuserdata( L, sizeof( sound_t ) );
|
||||
|
||||
if ( !gwlua_load_sound( (gwlua_state_t*)state_get( L ), (gwlua_sound_t*)self, name ) )
|
||||
{
|
||||
if ( luaL_newmetatable( L, "sound" ) != 0 )
|
||||
{
|
||||
static const luaL_Reg methods[] =
|
||||
{
|
||||
{ "__index", sound_index },
|
||||
{ "__newindex", sound_newindex },
|
||||
{ "__gc", sound_gc },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
luaL_setfuncs( L, methods, 0 );
|
||||
}
|
||||
|
||||
lua_setmetatable( L, -2 );
|
||||
return 1;
|
||||
}
|
||||
|
||||
return luaL_error( L, "error loading sound %s", name );
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
|
||||
static int value_load( lua_State* L )
|
||||
{
|
||||
const char* key = luaL_checkstring( L, 1 );
|
||||
|
||||
int type;
|
||||
const char* value = gwlua_load_value( (gwlua_state_t*)state_get( L ), key, &type );
|
||||
|
||||
if ( value )
|
||||
{
|
||||
switch ( type )
|
||||
{
|
||||
case GWLUA_NULL:
|
||||
default:
|
||||
lua_pushnil( L );
|
||||
break;
|
||||
|
||||
case GWLUA_BOOLEAN:
|
||||
lua_pushboolean( L, !strcmp( value, "true" ) );
|
||||
break;
|
||||
|
||||
case GWLUA_NUMBER:
|
||||
if ( !lua_stringtonumber( L, value ) )
|
||||
{
|
||||
lua_pushinteger( L, 0 );
|
||||
}
|
||||
break;
|
||||
|
||||
case GWLUA_STRING:
|
||||
lua_pushstring( L, value );
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
lua_pushnil( L );
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int value_save( lua_State* L )
|
||||
{
|
||||
gwlua_state_t* state = (gwlua_state_t*)state_get( L );
|
||||
const char* key = luaL_checkstring( L, 1 );
|
||||
|
||||
switch ( lua_type( L, 2 ) )
|
||||
{
|
||||
case LUA_TBOOLEAN:
|
||||
gwlua_save_value( state, key, lua_toboolean( L, 2 ) ? "true" : "false", GWLUA_BOOLEAN );
|
||||
break;
|
||||
|
||||
case LUA_TNUMBER:
|
||||
gwlua_save_value( state, key, lua_tostring( L, 2 ), GWLUA_NUMBER );
|
||||
break;
|
||||
|
||||
case LUA_TSTRING:
|
||||
gwlua_save_value( state, key, lua_tostring( L, 2 ), GWLUA_STRING );
|
||||
break;
|
||||
|
||||
default:
|
||||
gwlua_save_value( state, key, NULL, GWLUA_NULL );
|
||||
break;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
|
||||
static int array_new( lua_State* L )
|
||||
{
|
||||
int top = lua_gettop( L ) - 2;
|
||||
int i, j, k, a, b, res;
|
||||
|
||||
/* level 1 */
|
||||
lua_newtable( L );
|
||||
a = b = res = lua_gettop( L );
|
||||
|
||||
/* level n */
|
||||
for ( i = 1; i <= top; i += 2 )
|
||||
{
|
||||
int begin = (int)luaL_checkinteger( L, i );
|
||||
int end = (int)luaL_checkinteger( L, i + 1 );
|
||||
|
||||
for ( j = a; j <= b; j++ )
|
||||
{
|
||||
for ( k = begin; k <= end; k++ )
|
||||
{
|
||||
lua_newtable( L );
|
||||
lua_pushvalue( L, -1 );
|
||||
lua_rawseti( L, j, k );
|
||||
}
|
||||
}
|
||||
|
||||
a = b + 1;
|
||||
b = lua_gettop( L );
|
||||
}
|
||||
|
||||
lua_pushvalue( L, res );
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int bg_set( lua_State* L )
|
||||
{
|
||||
picture_t* bg = picture_check( L, 1 );
|
||||
state_t* state = state_get( L );
|
||||
|
||||
if ( state->bg_ref == LUA_NOREF )
|
||||
{
|
||||
gwlua_set_bg( (gwlua_state_t*)state, (gwlua_picture_t*)bg );
|
||||
ref_create( L, 1, &state->bg_ref, (ud_checker)picture_check, 0 );
|
||||
return 0;
|
||||
}
|
||||
|
||||
return luaL_error( L, "background already set" );
|
||||
}
|
||||
|
||||
static int randomize( lua_State* L )
|
||||
{
|
||||
state_t* state = state_get( L );
|
||||
state->seed = time( NULL );
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rnd( lua_State* L )
|
||||
{
|
||||
state_t* state = state_get( L );
|
||||
state->seed = 6364136223846793005ULL * state->seed + 1;
|
||||
double frac = ( state->seed >> 11 ) / 9007199254740992.0;
|
||||
|
||||
if ( lua_isnumber( L, 1 ) )
|
||||
{
|
||||
lua_pushinteger( L, (int64_t)( frac * lua_tointeger( L, 1 ) ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
lua_pushnumber( L, frac );
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int time_now( lua_State* L )
|
||||
{
|
||||
lua_pushinteger( L, time( NULL ) );
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int time_split( lua_State* L )
|
||||
{
|
||||
time_t tt = (time_t)luaL_checkinteger( L, 1 );
|
||||
struct tm* tm = localtime( &tt );
|
||||
|
||||
lua_pushinteger( L, tm->tm_hour );
|
||||
lua_pushinteger( L, tm->tm_min );
|
||||
lua_pushinteger( L, tm->tm_sec );
|
||||
lua_pushinteger( L, 0 );
|
||||
lua_pushinteger( L, tm->tm_mday );
|
||||
lua_pushinteger( L, tm->tm_mon + 1 );
|
||||
lua_pushinteger( L, tm->tm_year + 1900 );
|
||||
return 7;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
void gwlua_tick( gwlua_state_t* state, int64_t now )
|
||||
{
|
||||
state_t* s = (state_t*)state;
|
||||
int top = lua_gettop( s->L );
|
||||
s->now = now;
|
||||
|
||||
lua_pushcfunction( s->L, traceback );
|
||||
ref_get( s->L, s->tick_ref );
|
||||
|
||||
if ( lua_pcall( s->L, 0, 0, -2 ) != LUA_OK )
|
||||
{
|
||||
fprintf( stderr, "%s", lua_tostring( s->L, -1 ) );
|
||||
}
|
||||
|
||||
lua_settop( s->L, top );
|
||||
}
|
||||
|
||||
static const char* button_name( int button )
|
||||
{
|
||||
switch ( button )
|
||||
{
|
||||
case GWLUA_UP: return "up";
|
||||
case GWLUA_DOWN: return "down";
|
||||
case GWLUA_LEFT: return "left";
|
||||
case GWLUA_RIGHT: return "right";
|
||||
case GWLUA_A: return "a";
|
||||
case GWLUA_B: return "b";
|
||||
case GWLUA_X: return "x";
|
||||
case GWLUA_Y: return "y";
|
||||
case GWLUA_L1: return "l1";
|
||||
case GWLUA_R1: return "r1";
|
||||
case GWLUA_L2: return "l2";
|
||||
case GWLUA_R2: return "r2";
|
||||
case GWLUA_L3: return "l3";
|
||||
case GWLUA_R3: return "r3";
|
||||
case GWLUA_SELECT: return "select";
|
||||
case GWLUA_START: return "start";
|
||||
default: return "?";
|
||||
}
|
||||
}
|
||||
|
||||
void gwlua_button_down( gwlua_state_t* state, unsigned controller_ndx, int button )
|
||||
{
|
||||
state_t* s = (state_t*)state;
|
||||
int top = lua_gettop( s->L );
|
||||
|
||||
lua_pushcfunction( s->L, traceback );
|
||||
|
||||
ref_get( s->L, s->button_down_ref );
|
||||
lua_pushstring( s->L, button_name( button ) );
|
||||
lua_pushinteger( s->L, controller_ndx );
|
||||
|
||||
if ( lua_pcall( s->L, 2, 0, -4 ) != LUA_OK )
|
||||
{
|
||||
fprintf( stderr, "%s", lua_tostring( s->L, -1 ) );
|
||||
}
|
||||
|
||||
lua_settop( s->L, top );
|
||||
}
|
||||
|
||||
void gwlua_button_up( gwlua_state_t* state, unsigned controller_ndx, int button )
|
||||
{
|
||||
state_t* s = (state_t*)state;
|
||||
int top = lua_gettop( s->L );
|
||||
|
||||
lua_pushcfunction( s->L, traceback );
|
||||
|
||||
ref_get( s->L, s->button_up_ref );
|
||||
lua_pushstring( s->L, button_name( button ) );
|
||||
lua_pushinteger( s->L, controller_ndx );
|
||||
|
||||
if ( lua_pcall( s->L, 2, 0, -4 ) != LUA_OK )
|
||||
{
|
||||
fprintf( stderr, "%s", lua_tostring( s->L, -1 ) );
|
||||
}
|
||||
|
||||
lua_settop( s->L, top );
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
|
||||
static void* l_alloc( void* ud, void* ptr, size_t osize, size_t nsize )
|
||||
{
|
||||
(void)ud;
|
||||
(void)osize;
|
||||
|
||||
if ( nsize == 0 )
|
||||
{
|
||||
gwlua_free( ptr );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return gwlua_realloc( ptr, nsize );
|
||||
}
|
||||
|
||||
int gwlua_create( gwlua_state_t* state, const void* main, size_t size )
|
||||
{
|
||||
static const luaL_Reg statics[] =
|
||||
{
|
||||
{ "loadPicture", picture_load },
|
||||
{ "newSubPicture", picture_sub },
|
||||
{ "newSprite", sprite_new },
|
||||
{ "newTimer", timer_new },
|
||||
{ "loadSound", sound_load },
|
||||
{ "stopSounds", sound_stop },
|
||||
{ "loadValue", value_load },
|
||||
{ "saveValue", value_save },
|
||||
{ "array", array_new },
|
||||
{ "setBackground", bg_set },
|
||||
{ "randomize", randomize },
|
||||
{ "random", rnd },
|
||||
{ "now", time_now },
|
||||
{ "splitTime", time_split },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
state_t* s = (state_t*)state;
|
||||
s->L = lua_newstate( l_alloc, (void*)s );
|
||||
|
||||
if ( s->L )
|
||||
{
|
||||
s->bg_ref = LUA_NOREF;
|
||||
s->tick_ref = LUA_NOREF;
|
||||
s->button_down_ref = LUA_NOREF;
|
||||
s->button_up_ref = LUA_NOREF;
|
||||
|
||||
lua_setstateud( s->L, (void*)s );
|
||||
int top = lua_gettop( s->L );
|
||||
|
||||
luaL_openlibs( s->L );
|
||||
luaL_newlib( s->L, statics );
|
||||
lua_setglobal( s->L, "gw" );
|
||||
|
||||
#ifndef NDEBUG
|
||||
lua_pushboolean( s->L, 1 );
|
||||
lua_setglobal( s->L, "_DEBUG" );
|
||||
#endif
|
||||
|
||||
lua_settop( s->L, top );
|
||||
lua_pushcfunction( s->L, traceback );
|
||||
|
||||
switch ( luaL_loadbufferx( s->L, (const char*)main, size, "main.lua", "t" ) )
|
||||
{
|
||||
case LUA_OK:
|
||||
if ( lua_pcall( s->L, 0, 3, -2 ) == LUA_OK )
|
||||
{
|
||||
ref_create( s->L, -3, &s->tick_ref, function_check, 0 );
|
||||
ref_create( s->L, -2, &s->button_down_ref, function_check, 0 );
|
||||
ref_create( s->L, -1, &s->button_up_ref, function_check, 0 );
|
||||
lua_settop( s->L, 0 );
|
||||
return 0;
|
||||
}
|
||||
|
||||
fprintf( stderr, "%s", lua_tostring( s->L, -1 ) );
|
||||
break;
|
||||
|
||||
case LUA_ERRSYNTAX:
|
||||
fprintf( stderr, "%s", lua_tostring( s->L, -1 ) );
|
||||
break;
|
||||
|
||||
case LUA_ERRMEM:
|
||||
fprintf( stderr, "out of memory" );
|
||||
break;
|
||||
|
||||
case LUA_ERRGCMM:
|
||||
fprintf( stderr, "error running __gc" );
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf( stderr, "main.lua not found" );
|
||||
}
|
||||
|
||||
lua_close( s->L );
|
||||
return -1;
|
||||
}
|
||||
|
||||
void gwlua_destroy( gwlua_state_t* state )
|
||||
{
|
||||
state_t* s = (state_t*)state;
|
||||
lua_close( s->L );
|
||||
}
|
||||
|
||||
void gwlua_reset( gwlua_state_t* state )
|
||||
{
|
||||
/* TODO implement */
|
||||
(void)state;
|
||||
}
|
112
gwlua/gwlua.h
Normal file
112
gwlua/gwlua.h
Normal file
@ -0,0 +1,112 @@
|
||||
#ifndef GWLUA_H
|
||||
#define GWLUA_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
/* Registry value types for gwlua_(load|save)_value */
|
||||
|
||||
#define GWLUA_NULL 0
|
||||
#define GWLUA_BOOLEAN 1
|
||||
#define GWLUA_NUMBER 2
|
||||
#define GWLUA_STRING 3
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
/* Controller buttons */
|
||||
|
||||
#define GWLUA_UP 1
|
||||
#define GWLUA_DOWN 2
|
||||
#define GWLUA_LEFT 3
|
||||
#define GWLUA_RIGHT 4
|
||||
#define GWLUA_A 5
|
||||
#define GWLUA_B 6
|
||||
#define GWLUA_X 7
|
||||
#define GWLUA_Y 8
|
||||
#define GWLUA_L1 9
|
||||
#define GWLUA_R1 10
|
||||
#define GWLUA_L2 11
|
||||
#define GWLUA_R2 12
|
||||
#define GWLUA_L3 13
|
||||
#define GWLUA_R3 14
|
||||
#define GWLUA_SELECT 15
|
||||
#define GWLUA_START 16
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
/*
|
||||
define these structures the way you need to properly handle states, pictures
|
||||
and sound
|
||||
*/
|
||||
|
||||
#include <gwrom.h>
|
||||
|
||||
typedef struct gwlua_picture_t gwlua_picture_t;
|
||||
typedef struct gwlua_sound_t gwlua_sound_t;
|
||||
typedef struct gwlua_state_t gwlua_state_t;
|
||||
|
||||
struct gwlua_picture_t
|
||||
{
|
||||
unsigned width, height, pitch;
|
||||
uint16_t* pixels;
|
||||
const gwlua_picture_t* parent;
|
||||
};
|
||||
|
||||
struct gwlua_sound_t
|
||||
{
|
||||
int16_t* data;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
struct gwlua_state_t
|
||||
{
|
||||
gwrom_t gwrom;
|
||||
const gwlua_picture_t* bg;
|
||||
gwlua_picture_t screen;
|
||||
int first_frame;
|
||||
int updated;
|
||||
|
||||
const gwlua_sound_t* playing;
|
||||
size_t position;
|
||||
int16_t sound[ 735 * 2 ];
|
||||
};
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
/* user-provided functions (must be defined elsewhere) */
|
||||
|
||||
/* memory management */
|
||||
void* gwlua_malloc( size_t size );
|
||||
void gwlua_free( void* pointer );
|
||||
void* gwlua_realloc( void* pointer, size_t size );
|
||||
|
||||
/* picture */
|
||||
int gwlua_load_picture( gwlua_state_t* state, gwlua_picture_t* picture, const char* name );
|
||||
int gwlua_sub_picture( gwlua_state_t* state, gwlua_picture_t* picture, const gwlua_picture_t* parent, int x0, int y0, unsigned width, unsigned height );
|
||||
void gwlua_destroy_picture( gwlua_state_t* state, gwlua_picture_t* picture );
|
||||
void gwlua_blit_picture( gwlua_state_t* state, const gwlua_picture_t* picture, int x, int y );
|
||||
void gwlua_unblit_picture( gwlua_state_t* state, const gwlua_picture_t* picture, int x, int y );
|
||||
|
||||
/* sound */
|
||||
int gwlua_load_sound( gwlua_state_t* state, gwlua_sound_t* sound, const char* name );
|
||||
void gwlua_destroy_sound( gwlua_state_t* state, gwlua_sound_t* sound );
|
||||
void gwlua_play_sound( gwlua_state_t* state, const gwlua_sound_t* sound );
|
||||
void gwlua_stop_all_sounds( gwlua_state_t* state );
|
||||
|
||||
/* registry */
|
||||
const char* gwlua_load_value( gwlua_state_t* state, const char* key, int* type );
|
||||
void gwlua_save_value( gwlua_state_t* state, const char* key, const char* value, int type );
|
||||
|
||||
/* control */
|
||||
int gwlua_set_bg( gwlua_state_t* state, const gwlua_picture_t* bg );
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
/* api */
|
||||
|
||||
int gwlua_create( gwlua_state_t* state, const void* main, size_t size );
|
||||
void gwlua_destroy( gwlua_state_t* state );
|
||||
void gwlua_reset( gwlua_state_t* state );
|
||||
|
||||
void gwlua_tick( gwlua_state_t* state, int64_t now );
|
||||
void gwlua_button_down( gwlua_state_t* state, unsigned controller_ndx, int button );
|
||||
void gwlua_button_up( gwlua_state_t* state, unsigned controller_ndx, int button );
|
||||
|
||||
#endif /* GWLUA_H */
|
11
gwrom/LICENSE
Normal file
11
gwrom/LICENSE
Normal file
@ -0,0 +1,11 @@
|
||||
The zlib/libpng License
|
||||
|
||||
Copyright (c) <year> <copyright holders>
|
||||
|
||||
This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
7
gwrom/README.md
Normal file
7
gwrom/README.md
Normal file
@ -0,0 +1,7 @@
|
||||
# gwrom
|
||||
|
||||
An archive reader for games.
|
||||
|
||||
## Usage
|
||||
|
||||
Add gwrom.c and gwrom.h to your project. See gwrom.h for some documentation.
|
643
gwrom/gwrom.c
Normal file
643
gwrom/gwrom.c
Normal file
@ -0,0 +1,643 @@
|
||||
#include <gwrom.h>
|
||||
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
/* internal flags (1 << 16 to 1 << 23) */
|
||||
#define GWROM_FREE_DATA ( 1 << 16 )
|
||||
|
||||
/******************************************************************************
|
||||
zlib
|
||||
******************************************************************************/
|
||||
|
||||
#ifdef GWROM_USE_GZIP
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
/* use gwrom allocation routines */
|
||||
void* zalloc( void* opaque, uInt items, uInt size )
|
||||
{
|
||||
(void)opaque;
|
||||
return gwrom_malloc( items * size );
|
||||
}
|
||||
|
||||
/* use gwrom allocation routines */
|
||||
void zfree( void* opaque, void* addr )
|
||||
{
|
||||
(void)opaque;
|
||||
gwrom_free( addr );
|
||||
}
|
||||
|
||||
static int identify_gzip( const void* data, size_t size )
|
||||
{
|
||||
/* basic header check */
|
||||
const uint8_t* magic = (const uint8_t*)data;
|
||||
|
||||
if ( magic[ 0 ] != 0x1f || magic[ 1 ] != 0x8b || magic[ 2 ] != 8 )
|
||||
{
|
||||
return GWROM_INVALID_ROM;
|
||||
}
|
||||
|
||||
/* TODO check the signature at the end of the data */
|
||||
return GWROM_OK;
|
||||
}
|
||||
|
||||
static int decompress_gzip( void** new_data, size_t* new_size, void* data, size_t size )
|
||||
{
|
||||
/* find the decompressed size */
|
||||
/* TODO is this always little-endian in a gzipped file? */
|
||||
*new_size = *(uint32_t*)( (char*)data + size - 4 );
|
||||
|
||||
/* allocate buffer */
|
||||
*new_data = gwrom_malloc( *new_size );
|
||||
|
||||
if ( !*new_data )
|
||||
{
|
||||
return GWROM_NO_MEMORY;
|
||||
}
|
||||
|
||||
/* decompress */
|
||||
uLong dest_len = *new_size;
|
||||
int res = uncompress( *new_data, &dest_len, data, size );
|
||||
|
||||
if ( res != Z_OK )
|
||||
{
|
||||
gwrom_free( *new_data );
|
||||
return GWROM_INVALID_ROM;
|
||||
}
|
||||
|
||||
return GWROM_OK;
|
||||
}
|
||||
|
||||
#endif /* GWROM_USE_GZIP */
|
||||
|
||||
/******************************************************************************
|
||||
bzip2
|
||||
******************************************************************************/
|
||||
|
||||
#ifdef GWROM_USE_BZIP2
|
||||
|
||||
#include <bzlib.h>
|
||||
#include <bzlib_private.h>
|
||||
|
||||
/* needed because of -DBZ_NO_STDIO, which should be defined for compilation */
|
||||
void bz_internal_error( int errcode )
|
||||
{
|
||||
(void)errcode;
|
||||
}
|
||||
|
||||
/* use gwrom allocation routines */
|
||||
void* bzalloc( void* opaque, Int32 items, Int32 size )
|
||||
{
|
||||
(void)opaque;
|
||||
return gwrom_malloc( items * size );
|
||||
}
|
||||
|
||||
/* use gwrom allocation routines */
|
||||
void bzfree( void* opaque, void* addr )
|
||||
{
|
||||
(void)opaque;
|
||||
gwrom_free( addr );
|
||||
}
|
||||
|
||||
static int identify_bzip2( const void* data, size_t size )
|
||||
{
|
||||
/* basic header check */
|
||||
const char* magic = (const char*)data;
|
||||
|
||||
if ( magic[ 0 ] != 'B' || magic[ 1 ] != 'Z' || magic[ 2 ] != 'h' ||
|
||||
magic[ 3 ] < '0' || magic[ 3 ] > '9' )
|
||||
{
|
||||
return GWROM_INVALID_ROM;
|
||||
}
|
||||
|
||||
/* TODO check the signature at the end of the data */
|
||||
return GWROM_OK;
|
||||
}
|
||||
|
||||
static int decompress_bzip2( void** new_data, size_t* new_size, void* data, size_t size )
|
||||
{
|
||||
bz_stream stream;
|
||||
|
||||
/* setup the decompression stream */
|
||||
stream.bzalloc = bzalloc;
|
||||
stream.bzfree = bzfree;
|
||||
|
||||
int res = BZ2_bzDecompressInit( &stream, 0, 0 );
|
||||
|
||||
if ( res != BZ_OK )
|
||||
{
|
||||
return GWROM_INVALID_ROM;
|
||||
}
|
||||
|
||||
stream.next_in = (char*)data;
|
||||
stream.avail_in = (unsigned)size;
|
||||
|
||||
#ifdef GWROM_NO_REALLOC
|
||||
/* first decompression run: evaluate size of decompressed data */
|
||||
for ( ;; )
|
||||
{
|
||||
char buffer[ GWROM_DECOMP_BUFFER ];
|
||||
stream.next_out = buffer;
|
||||
stream.avail_out = sizeof( buffer );
|
||||
|
||||
res = BZ2_bzDecompress( &stream );
|
||||
|
||||
if ( res == BZ_STREAM_END )
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if ( res != BZ_OK )
|
||||
{
|
||||
BZ2_bzDecompressEnd( &stream );
|
||||
return GWROM_INVALID_ROM;
|
||||
}
|
||||
}
|
||||
|
||||
/* basic check for when size_t can't hold 64-bit values */
|
||||
if ( sizeof( size_t ) > 4 )
|
||||
{
|
||||
*new_size = stream.total_out_hi32;
|
||||
*new_size = *new_size << 32 | stream.total_out_lo32;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( stream.total_out_hi32 != 0 )
|
||||
{
|
||||
BZ2_bzDecompressEnd( &stream );
|
||||
return GWROM_NO_MEMORY;
|
||||
}
|
||||
|
||||
*new_size = stream.total_out_lo32;
|
||||
}
|
||||
|
||||
BZ2_bzDecompressEnd( &stream );
|
||||
*new_data = gwrom_malloc( *new_size );
|
||||
|
||||
if ( !*new_data )
|
||||
{
|
||||
return GWROM_NO_MEMORY;
|
||||
}
|
||||
|
||||
/* second decompression run: decompress data to the allocated buffer */
|
||||
unsigned dest_len = *new_size;
|
||||
res = BZ2_bzBuffToBuffDecompress( (char*)*new_data, &dest_len, (char*)data, size, 0, 0 );
|
||||
|
||||
if ( res != BZ_OK )
|
||||
{
|
||||
return GWROM_INVALID_ROM;
|
||||
}
|
||||
#else
|
||||
*new_data = NULL;
|
||||
*new_size = 0;
|
||||
|
||||
/* decompress while reallocating the decompressed data as necessary */
|
||||
for ( ;; )
|
||||
{
|
||||
char buffer[ GWROM_DECOMP_BUFFER ];
|
||||
stream.next_out = buffer;
|
||||
stream.avail_out = sizeof( buffer );
|
||||
|
||||
res = BZ2_bzDecompress( &stream );
|
||||
|
||||
if ( res != BZ_OK && res != BZ_STREAM_END )
|
||||
{
|
||||
BZ2_bzDecompressEnd( &stream );
|
||||
gwrom_free( *new_data );
|
||||
return GWROM_INVALID_ROM;
|
||||
}
|
||||
|
||||
size_t count = sizeof( buffer ) - stream.avail_out;
|
||||
|
||||
if ( count )
|
||||
{
|
||||
char* realloc_data = gwrom_realloc( *new_data, *new_size + count );
|
||||
|
||||
if ( realloc_data == NULL )
|
||||
{
|
||||
gwrom_free( *new_data );
|
||||
return GWROM_NO_MEMORY;
|
||||
}
|
||||
|
||||
*new_data = realloc_data;
|
||||
memcpy( (void*)( (char*)*new_data + *new_size ), (void*)buffer, count );
|
||||
*new_size += count;
|
||||
}
|
||||
|
||||
if ( res == BZ_STREAM_END )
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
BZ2_bzDecompressEnd( &stream );
|
||||
#endif
|
||||
|
||||
return GWROM_OK;
|
||||
}
|
||||
|
||||
/* use the default destroy method */
|
||||
#define destroy_bzip2 default_destroy
|
||||
|
||||
/* bzip2 roms don't have any entries in them */
|
||||
#define find_bzip2 default_find
|
||||
|
||||
#endif /* GWROM_USE_BZIP2 */
|
||||
|
||||
/******************************************************************************
|
||||
uncompressed
|
||||
******************************************************************************/
|
||||
|
||||
static int identify_uncompressed( const void* data, size_t size )
|
||||
{
|
||||
/* uncompressed data is always identified */
|
||||
return GWROM_OK;
|
||||
}
|
||||
|
||||
static int inflate_uncompressed( void** new_data, size_t* new_size, void* data, size_t size )
|
||||
{
|
||||
/*
|
||||
returns the same data, the caller must check that it wasn't inflated to a new
|
||||
buffer if it wants to copy the data into a new buffer
|
||||
*/
|
||||
*new_data = data;
|
||||
*new_size = size;
|
||||
return GWROM_OK;
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
decompress methods
|
||||
******************************************************************************/
|
||||
|
||||
typedef struct
|
||||
{
|
||||
/* returns GWROM_OK if the decompression method is identified */
|
||||
int (*identify)( const void*, size_t );
|
||||
|
||||
/* decompresses the rom into a new buffer */
|
||||
int (*decompress)( void**, size_t*, void*, size_t );
|
||||
}
|
||||
decompress_t;
|
||||
|
||||
/* all inflate algorithms must have an entry here */
|
||||
static const decompress_t decompress[] =
|
||||
{
|
||||
#ifdef GWROM_USE_GZIP
|
||||
{ identify_gzip, decompress_gzip },
|
||||
#endif
|
||||
|
||||
#ifdef GWROM_USE_BZIP2
|
||||
{ identify_bzip2, decompress_bzip2 },
|
||||
#endif
|
||||
|
||||
/* add new inflate methods here */
|
||||
{ identify_uncompressed, inflate_uncompressed },
|
||||
};
|
||||
|
||||
/******************************************************************************
|
||||
default rom methods
|
||||
******************************************************************************/
|
||||
|
||||
static int default_init( gwrom_t* gwrom )
|
||||
{
|
||||
(void)gwrom;
|
||||
return GWROM_OK;
|
||||
}
|
||||
|
||||
static void default_destroy( gwrom_t* gwrom )
|
||||
{
|
||||
(void)gwrom;
|
||||
}
|
||||
|
||||
static int default_find( gwrom_entry_t* file, gwrom_t* gwrom, const char* file_name )
|
||||
{
|
||||
(void)file;
|
||||
(void)gwrom;
|
||||
(void)file_name;
|
||||
return GWROM_ENTRY_NOT_FOUND;
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
tar archive, use --format=v7 with gnu tar
|
||||
******************************************************************************/
|
||||
|
||||
#ifdef GWROM_USE_TAR_V7
|
||||
|
||||
typedef union
|
||||
{
|
||||
struct
|
||||
{
|
||||
char name[ 100 ];
|
||||
char mode[ 8 ];
|
||||
char owner[ 8 ];
|
||||
char group[ 8 ];
|
||||
char size[ 12 ];
|
||||
char modification[ 12 ];
|
||||
char checksum[ 8 ];
|
||||
char type;
|
||||
char linked[ 100 ];
|
||||
|
||||
/*
|
||||
a space for the user to store things related to the entry, i.e. data has
|
||||
been converted to little endian
|
||||
CAUTION: things stored in user_flags are *not* persistent!
|
||||
*/
|
||||
uint32_t user_flags;
|
||||
|
||||
/*
|
||||
a space for the user to store things related to the entry, i.e. a different
|
||||
representation of the data
|
||||
CAUTION: things stored in user_flags are *not* persistent!
|
||||
*/
|
||||
void* user_data;
|
||||
};
|
||||
|
||||
char fill[ 512 ];
|
||||
}
|
||||
entry_tar_v7;
|
||||
|
||||
static int identify_tar_v7( const void* data, size_t size )
|
||||
{
|
||||
/* tar size is always a multiple of 512 */
|
||||
if ( size & 511 )
|
||||
{
|
||||
return GWROM_INVALID_ROM;
|
||||
}
|
||||
|
||||
entry_tar_v7* entry = (entry_tar_v7*)data;
|
||||
char* end = (char*)data + size - 512;
|
||||
|
||||
/* iterate over the entries and do a basic chack on each on of them */
|
||||
while ( (char*)entry <= end && entry->name[ 0 ] )
|
||||
{
|
||||
char* endptr;
|
||||
long entry_size = strtol( entry->size, &endptr, 8 );
|
||||
|
||||
/* Check for a valid entry size */
|
||||
if ( *endptr != 0 || errno == ERANGE )
|
||||
{
|
||||
return GWROM_INVALID_ROM;
|
||||
}
|
||||
|
||||
char* name = entry->name;
|
||||
char* endname = name + 100;
|
||||
|
||||
/* Check for a valid entry name */
|
||||
do
|
||||
{
|
||||
if ( *name++ < 32 )
|
||||
{
|
||||
return GWROM_INVALID_ROM;
|
||||
}
|
||||
}
|
||||
while ( *name && name < endname );
|
||||
|
||||
/* go to the next entry */
|
||||
entry_size = ( entry_size + 511 ) / 512 + 1;
|
||||
entry += entry_size;
|
||||
}
|
||||
|
||||
/* the last entry must be followed by one or more empty entries */
|
||||
if ( (char*)entry >= end )
|
||||
{
|
||||
return GWROM_INVALID_ROM;
|
||||
}
|
||||
|
||||
/* check for empty entries */
|
||||
do
|
||||
{
|
||||
int i;
|
||||
|
||||
for ( i = 0; i < 512; i++ )
|
||||
{
|
||||
if ( ( (char*)entry )[ i ] != 0 )
|
||||
{
|
||||
return GWROM_INVALID_ROM;
|
||||
}
|
||||
}
|
||||
|
||||
entry++;
|
||||
}
|
||||
while ( (char*)entry < end );
|
||||
|
||||
return GWROM_OK;
|
||||
}
|
||||
|
||||
static int init_tar_v7( gwrom_t* gwrom )
|
||||
{
|
||||
entry_tar_v7* entry = (entry_tar_v7*)gwrom->data;
|
||||
|
||||
while ( entry->name[ 0 ] )
|
||||
{
|
||||
long entry_size = strtol( entry->size, NULL, 8 );
|
||||
|
||||
/* zero user space */
|
||||
entry->user_flags = 0;
|
||||
entry->user_data = NULL;
|
||||
|
||||
/* go to the next entry */
|
||||
entry_size = ( entry_size + 511 ) / 512 + 1;
|
||||
entry += entry_size;
|
||||
}
|
||||
|
||||
return GWROM_OK;
|
||||
}
|
||||
|
||||
static int find_tar_v7( gwrom_entry_t* file, gwrom_t* gwrom, const char* file_name )
|
||||
{
|
||||
entry_tar_v7* entry = (entry_tar_v7*)gwrom->data;
|
||||
|
||||
while ( entry->name[ 0 ] )
|
||||
{
|
||||
long entry_size = strtol( entry->size, NULL, 8 );
|
||||
|
||||
if ( !strcmp( entry->name, file_name ) )
|
||||
{
|
||||
/* found the entry, fill in gwrom_entry_t* */
|
||||
file->name = entry->name;
|
||||
file->data = (void*)( entry + 1 );
|
||||
file->size = entry_size;
|
||||
file->user_flags = &entry->user_flags;
|
||||
|
||||
return GWROM_OK;
|
||||
}
|
||||
|
||||
/* go to the next entry */
|
||||
entry_size = ( entry_size + 511 ) / 512 + 1;
|
||||
entry += entry_size;
|
||||
}
|
||||
|
||||
return GWROM_ENTRY_NOT_FOUND;
|
||||
}
|
||||
|
||||
static void iterate_tar_v7( gwrom_t* gwrom, int (*callback)( gwrom_entry_t*, gwrom_t* ) )
|
||||
{
|
||||
entry_tar_v7* entry = (entry_tar_v7*)gwrom->data;
|
||||
gwrom_entry_t file;
|
||||
|
||||
while ( entry->name[ 0 ] )
|
||||
{
|
||||
long entry_size = strtol( entry->size, NULL, 8 );
|
||||
|
||||
file.name = entry->name;
|
||||
file.data = (void*)( entry + 1 );
|
||||
file.size = entry_size;
|
||||
file.user_flags = &entry->user_flags;
|
||||
file.user_data = &entry->user_data;
|
||||
|
||||
if ( !callback( &file, gwrom ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/* go to the next entry */
|
||||
entry_size = ( entry_size + 511 ) / 512 + 1;
|
||||
entry += entry_size;
|
||||
}
|
||||
}
|
||||
|
||||
/* tar doesn't need destruction */
|
||||
#define destroy_tar_v7 default_destroy
|
||||
|
||||
#endif /* GWROM_USE_TAR_V7 */
|
||||
|
||||
/******************************************************************************
|
||||
rom methods
|
||||
******************************************************************************/
|
||||
|
||||
typedef struct
|
||||
{
|
||||
/* returns GWROM_OK if the rom type is identified */
|
||||
int (*identify)( const void*, size_t );
|
||||
|
||||
/* initializes the rom */
|
||||
int (*init)( gwrom_t* );
|
||||
|
||||
/* frees all memory allocated to the rom */
|
||||
void (*destroy)( gwrom_t* );
|
||||
|
||||
/* finds an entry in the rom */
|
||||
int (*find)( gwrom_entry_t*, gwrom_t*, const char* );
|
||||
|
||||
/* iterates over all rom entries */
|
||||
void (*iterate)( gwrom_t*, int (*)( gwrom_entry_t*, gwrom_t* ) );
|
||||
}
|
||||
methods_t;
|
||||
|
||||
/* all supported rom types must have an entry here */
|
||||
static const methods_t methods[] =
|
||||
{
|
||||
#ifdef GWROM_USE_TAR_V7
|
||||
{ identify_tar_v7, init_tar_v7, destroy_tar_v7, find_tar_v7, iterate_tar_v7 },
|
||||
#endif
|
||||
|
||||
/* add new rom types here */
|
||||
};
|
||||
|
||||
/******************************************************************************
|
||||
external API
|
||||
******************************************************************************/
|
||||
|
||||
int gwrom_init( gwrom_t* gwrom, void* data, size_t size, uint32_t flags )
|
||||
{
|
||||
void* new_data = data;
|
||||
size_t new_size = size;
|
||||
unsigned i;
|
||||
|
||||
/* check for compressed roms first */
|
||||
const decompress_t* decomp = decompress;
|
||||
|
||||
for ( i = 0; i < sizeof( decompress ) / sizeof( decompress[ 0 ] ); i++, decomp++ )
|
||||
{
|
||||
if ( decomp->identify( data, size ) == GWROM_OK )
|
||||
{
|
||||
if ( decomp->decompress( &new_data, &new_size, data, size ) != GWROM_OK )
|
||||
{
|
||||
return GWROM_INVALID_ROM;
|
||||
}
|
||||
|
||||
/* check if data was copied into a new buffer */
|
||||
if ( new_data != data )
|
||||
{
|
||||
/* yes, set flags to free the data */
|
||||
flags |= GWROM_FREE_DATA;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* no, check if the caller has asked to copy it */
|
||||
if ( flags & GWROM_COPY_ALWAYS )
|
||||
{
|
||||
/* yes, copy data into a new buffer */
|
||||
new_data = gwrom_malloc( size );
|
||||
|
||||
if ( !new_data )
|
||||
{
|
||||
return GWROM_NO_MEMORY;
|
||||
}
|
||||
|
||||
memcpy( new_data, data, size );
|
||||
new_size = size;
|
||||
|
||||
/* set flags to free the data */
|
||||
flags |= GWROM_FREE_DATA;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* iterates over the supported types and compress algorithms */
|
||||
const methods_t* meth = methods;
|
||||
|
||||
for ( i = 0; i < sizeof( methods ) / sizeof( methods[ 0 ] ); i++, meth++ )
|
||||
{
|
||||
if ( meth->identify( new_data, new_size ) == GWROM_OK )
|
||||
{
|
||||
/* type was identified, fill in gwrom and call its init method */
|
||||
gwrom->data = new_data;
|
||||
gwrom->size = new_size;
|
||||
gwrom->flags = flags;
|
||||
gwrom->destroy = meth->destroy;
|
||||
gwrom->find = meth->find;
|
||||
gwrom->iterate = meth->iterate;
|
||||
return meth->init( gwrom );
|
||||
}
|
||||
}
|
||||
|
||||
/* rom not identified */
|
||||
return GWROM_INVALID_ROM;
|
||||
}
|
||||
|
||||
void gwrom_destroy( gwrom_t* gwrom )
|
||||
{
|
||||
/* calls the destroy method */
|
||||
gwrom->destroy( gwrom );
|
||||
|
||||
/* free data */
|
||||
if ( gwrom->flags & GWROM_FREE_DATA )
|
||||
{
|
||||
gwrom_free( gwrom->data );
|
||||
}
|
||||
|
||||
/* zeroes gwrom data */
|
||||
gwrom->data = NULL;
|
||||
gwrom->size = 0;
|
||||
gwrom->user_data = NULL;
|
||||
gwrom->destroy = NULL;
|
||||
gwrom->find = NULL;
|
||||
}
|
||||
|
||||
const char* gwrom_error_message( int error )
|
||||
{
|
||||
switch ( error )
|
||||
{
|
||||
case GWROM_OK: return "Ok";
|
||||
case GWROM_INVALID_ROM: return "Invalid ROM (corrupted file?)";
|
||||
case GWROM_NO_MEMORY: return "Out of memory";
|
||||
case GWROM_ENTRY_NOT_FOUND: return "Entry not found";
|
||||
}
|
||||
|
||||
return "Unknown error";
|
||||
}
|
133
gwrom/gwrom.h
Normal file
133
gwrom/gwrom.h
Normal file
@ -0,0 +1,133 @@
|
||||
#ifndef GWROM_H
|
||||
#define GWROM_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
/* must be implemented by the user */
|
||||
|
||||
/*
|
||||
memory allocation routines
|
||||
*/
|
||||
void* gwrom_malloc( size_t size );
|
||||
void* gwrom_realloc( void* ptr, size_t size );
|
||||
void gwrom_free( void* ptr );
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
/* compilation config */
|
||||
|
||||
/* use tar v7 archives */
|
||||
#define GWROM_USE_TAR_V7
|
||||
|
||||
/* use zlib for decompression */
|
||||
/*#define GWROM_USE_GZIP*/
|
||||
|
||||
/* use bzip2 for decompression */
|
||||
#define GWROM_USE_BZIP2
|
||||
|
||||
/*
|
||||
prevents memory reallocation during bzip2 decompression
|
||||
note that in this case gwrom will do two decompression runs, one to evaluate
|
||||
the decompressed size and another to decompress the data to the allocated
|
||||
buffer, but memory will be entirely allocated in one go
|
||||
*/
|
||||
#define GWROM_NO_REALLOC 1
|
||||
|
||||
/* size of the buffer used during decompression */
|
||||
#define GWROM_DECOMP_BUFFER 65536
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
/* flags to gwrom_init (1 << 0 to 1 << 15) */
|
||||
|
||||
/*
|
||||
makes gwrom_init copy the rom into a new buffer, even if it's unecessary (i.e.
|
||||
data wasn't compressed)
|
||||
*/
|
||||
#define GWROM_COPY_ALWAYS 1
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
|
||||
/* everything went ok */
|
||||
#define GWROM_OK 0
|
||||
|
||||
/* invalid rom format */
|
||||
#define GWROM_INVALID_ROM -1
|
||||
|
||||
/* memory allocation error */
|
||||
#define GWROM_NO_MEMORY -2
|
||||
|
||||
/* entry not found in the rom */
|
||||
#define GWROM_ENTRY_NOT_FOUND -3
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
|
||||
typedef struct gwrom_t gwrom_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
/* entry name */
|
||||
const char* name;
|
||||
/* pointer to entry data */
|
||||
void* data;
|
||||
/* entry data size */
|
||||
size_t size;
|
||||
/* internal gwrom flags for the entry */
|
||||
uint32_t flags;
|
||||
|
||||
/* persistent (between gwrom_init and gwrom_destroy) user flags */
|
||||
uint32_t* user_flags;
|
||||
|
||||
/* available to the user */
|
||||
void** user_data;
|
||||
}
|
||||
gwrom_entry_t;
|
||||
|
||||
struct gwrom_t
|
||||
{
|
||||
/* rom data */
|
||||
void* data;
|
||||
/* rom data size */
|
||||
size_t size;
|
||||
/* internal gwrom flags for the rom */
|
||||
uint32_t flags;
|
||||
|
||||
/* persistent (between gwrom_init and gwrom_destroy) user flags */
|
||||
uint32_t user_flags;
|
||||
|
||||
/* available to the user */
|
||||
void* user_data;
|
||||
|
||||
/* frees all memory allocated to the rom */
|
||||
void (*destroy)( gwrom_t* );
|
||||
/* finds an entry in the rom */
|
||||
int (*find)( gwrom_entry_t*, gwrom_t*, const char* );
|
||||
/* iterates over all rom entries */
|
||||
void (*iterate)( gwrom_t*, int (*)( gwrom_entry_t*, gwrom_t* ) );
|
||||
};
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
/* external api */
|
||||
|
||||
/*
|
||||
initializes an archive. if flags contain GWROM_COPY_ALWAYS, data will be copied
|
||||
to another buffer. this buffer is automatically freed when gwrom_destroy is
|
||||
called.
|
||||
*/
|
||||
int gwrom_init( gwrom_t* gwrom, void* data, size_t size, uint32_t flags );
|
||||
|
||||
/* frees all memory allocated for the rom */
|
||||
void gwrom_destroy( gwrom_t* );
|
||||
|
||||
/* returns a readable descriptions for errors */
|
||||
const char* gwrom_error_message( int error );
|
||||
|
||||
/* finds an entry in the rom */
|
||||
#define gwrom_find( entry, gwrom, file_name ) ( ( gwrom )->find( entry, gwrom, file_name ) )
|
||||
|
||||
/* iterates over all rom entries */
|
||||
#define gwrom_iterate( gwrom, callback ) ( ( gwrom )->iterate( gwrom, callback ) )
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
|
||||
#endif /* GWROM_H */
|
Loading…
Reference in New Issue
Block a user