diff options
Diffstat (limited to 'src/libs')
372 files changed, 85337 insertions, 0 deletions
diff --git a/src/libs/Makeinfo b/src/libs/Makeinfo new file mode 100644 index 0000000..dbaa7d4 --- /dev/null +++ b/src/libs/Makeinfo @@ -0,0 +1,19 @@ +uqm_SUBDIRS="callback decomp file graphics heap input list math memory + resource sound strings task threads time uio video log" +if [ -n "$uqm_USE_INTERNAL_MIKMOD" ]; then + uqm_SUBDIRS="$uqm_SUBDIRS mikmod" +fi + +if [ -n "$uqm_NETPLAY" ]; then + uqm_SUBDIRS="$uqm_SUBDIRS network" +fi + +#if [ "$DEBUG" = 1 ]; then +# uqm_SUBDIRS="$UQM_SUBDIRS debug" +#fi + +uqm_HFILES="alarm.h async.h callback.h cdplib.h compiler.h declib.h file.h + gfxlib.h heap.h inplib.h list.h log.h mathlib.h md5.h memlib.h + misc.h net.h platform.h reslib.h sndlib.h strlib.h tasklib.h + threadlib.h timelib.h uio.h uioutils.h unicode.h vidlib.h" + diff --git a/src/libs/alarm.h b/src/libs/alarm.h new file mode 100644 index 0000000..b4a72ca --- /dev/null +++ b/src/libs/alarm.h @@ -0,0 +1,9 @@ +#if defined(__cplusplus) +extern "C" { +#endif + +#include "callback/alarm.h" + +#if defined(__cplusplus) +} +#endif diff --git a/src/libs/async.h b/src/libs/async.h new file mode 100644 index 0000000..25b71af --- /dev/null +++ b/src/libs/async.h @@ -0,0 +1,10 @@ +#if defined(__cplusplus) +extern "C" { +#endif + +#include "callback/async.h" + +#if defined(__cplusplus) +} +#endif + diff --git a/src/libs/callback.h b/src/libs/callback.h new file mode 100644 index 0000000..025160a --- /dev/null +++ b/src/libs/callback.h @@ -0,0 +1,10 @@ +#if defined(__cplusplus) +extern "C" { +#endif + +#include "callback/callback.h" + +#if defined(__cplusplus) +} +#endif + diff --git a/src/libs/callback/Makeinfo b/src/libs/callback/Makeinfo new file mode 100644 index 0000000..8842cba --- /dev/null +++ b/src/libs/callback/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="alarm.c async.c callback.c" +uqm_HFILES="alarm.h async.h callback.h" diff --git a/src/libs/callback/alarm.c b/src/libs/callback/alarm.c new file mode 100644 index 0000000..c9bd6ce --- /dev/null +++ b/src/libs/callback/alarm.c @@ -0,0 +1,177 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "alarm.h" + +#include SDL_INCLUDE(SDL.h) +#include "libs/heap.h" + +#include <assert.h> +#include <stdlib.h> + + +Heap *alarmHeap; + + +static inline Alarm * +Alarm_alloc(void) { + return malloc(sizeof (Alarm)); +} + +static inline void +Alarm_free(Alarm *alarm) { + free(alarm); +} + +static inline int +AlarmTime_compare(const AlarmTime t1, const AlarmTime t2) { + if (t1 < t2) + return -1; + if (t1 > t2) + return 1; + return 0; +} + +static int +Alarm_compare(const Alarm *a1, const Alarm *a2) { + return AlarmTime_compare(a1->time, a2->time); +} + +void +Alarm_init(void) { + assert(alarmHeap == NULL); + alarmHeap = Heap_new((HeapValue_Comparator) Alarm_compare, + 4, 4, 0.8); +} + +void +Alarm_uninit(void) { + assert(alarmHeap != NULL); + + while (Heap_hasMore(alarmHeap)) { + Alarm *alarm = (Alarm *) Heap_pop(alarmHeap); + Alarm_free(alarm); + } + Heap_delete(alarmHeap); + alarmHeap = NULL; +} + +static inline AlarmTime +AlarmTime_nowMs(void) { + return SDL_GetTicks(); +} + +Alarm * +Alarm_addAbsoluteMs(uint32 ms, AlarmCallback callback, + AlarmCallbackArg arg) { + Alarm *alarm; + + assert(alarmHeap != NULL); + + alarm = Alarm_alloc(); + alarm->time = ms; + alarm->callback = callback; + alarm->arg = arg; + + Heap_add(alarmHeap, (HeapValue *) alarm); + + return alarm; +} + +Alarm * +Alarm_addRelativeMs(uint32 ms, AlarmCallback callback, + AlarmCallbackArg arg) { + Alarm *alarm; + + assert(alarmHeap != NULL); + + alarm = Alarm_alloc(); + alarm->time = AlarmTime_nowMs() + ms; + alarm->callback = callback; + alarm->arg = arg; + + Heap_add(alarmHeap, (HeapValue *) alarm); + + return alarm; +} + +void +Alarm_remove(Alarm *alarm) { + assert(alarmHeap != NULL); + Heap_remove(alarmHeap, (HeapValue *) alarm); + Alarm_free(alarm); +} + +// Process at most one alarm, if its time has come. +// It is safe to call this function again from inside a callback function +// that it called. It should not be called from multiple threads at once. +bool +Alarm_processOne(void) +{ + AlarmTime now; + Alarm *alarm; + + assert(alarmHeap != NULL); + if (!Heap_hasMore(alarmHeap)) + return false; + + now = AlarmTime_nowMs(); + alarm = (Alarm *) Heap_first(alarmHeap); + if (now < alarm->time) + return false; + + Heap_pop(alarmHeap); + alarm->callback(alarm->arg); + Alarm_free(alarm); + return true; +} + +#if 0 +// It is safe to call this function again from inside a callback function +// that it called. It should not be called from multiple threads at once. +void +Alarm_processAll(void) { + AlarmTime now; + + assert(alarmHeap != NULL); + + now = AlarmTime_nowMs(); + while (Heap_hasMore(alarmHeap)) { + Alarm *alarm = (Alarm *) Heap_first(alarmHeap); + + if (now < alarm->time) + break; + + Heap_pop(alarmHeap); + alarm->callback(alarm->arg); + Alarm_free(alarm); + } +} +#endif + +uint32 +Alarm_timeBeforeNextMs(void) { + Alarm *alarm; + + if (!Heap_hasMore(alarmHeap)) + return UINT32_MAX; + + alarm = (Alarm *) Heap_first(alarmHeap); + return alarmTimeToMsUint32(alarm->time); +} + diff --git a/src/libs/callback/alarm.h b/src/libs/callback/alarm.h new file mode 100644 index 0000000..9f61263 --- /dev/null +++ b/src/libs/callback/alarm.h @@ -0,0 +1,56 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef LIBS_CALLBACK_ALARM_H_ +#define LIBS_CALLBACK_ALARM_H_ + +#include "port.h" +#include "types.h" + +typedef uint32 AlarmTime; +static inline uint32 +alarmTimeToMsUint32(AlarmTime time) { + return (uint32) time; +} + +typedef struct Alarm Alarm; +typedef void *AlarmCallbackArg; +typedef void (*AlarmCallback)(AlarmCallbackArg arg); + +struct Alarm { + size_t index; + // For the HeapValue 'base struct'. + + AlarmTime time; + AlarmCallback callback; + AlarmCallbackArg arg; +}; + +void Alarm_init(void); +void Alarm_uninit(void); +Alarm *Alarm_addAbsoluteMs(uint32 ms, AlarmCallback callback, + AlarmCallbackArg arg); +Alarm *Alarm_addRelativeMs(uint32 ms, AlarmCallback callback, + AlarmCallbackArg arg); +void Alarm_remove(Alarm *alarm); +bool Alarm_processOne(void); +void Alarm_processAll(void); +uint32 Alarm_timeBeforeNextMs(void); + +#endif /* LIBS_CALLBACK_ALARM_H_ */ + diff --git a/src/libs/callback/async.c b/src/libs/callback/async.c new file mode 100644 index 0000000..d901158 --- /dev/null +++ b/src/libs/callback/async.c @@ -0,0 +1,56 @@ +/* + * Copyright 2012 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "async.h" + +#include "libs/alarm.h" +#include "libs/callback.h" + + +// Process all alarms and callbacks. +// First, all scheduled callbacks are called. +// Then each alarm due is called, and after each of these alarms, the +// callbacks scheduled by this alarm are called. +void +Async_process(void) +{ + // Call pending callbacks. + Callback_process(); + + for (;;) { + if (!Alarm_processOne()) + return; + + // Call callbacks scheduled from the last alarm. + Callback_process(); + } +} + +// Returns the next time that some asynchronous callback is +// to be called. Note that all values lower than the current time +// should be considered as 'somewhere in the past'. +uint32 +Async_timeBeforeNextMs(void) { + if (Callback_haveMore()) { + // Any time before the current time is ok, though we reserve 0 so + // that the caller may use it as a special value in its own code. + return 1; + } + return Alarm_timeBeforeNextMs(); +} + diff --git a/src/libs/callback/async.h b/src/libs/callback/async.h new file mode 100644 index 0000000..8cfae39 --- /dev/null +++ b/src/libs/callback/async.h @@ -0,0 +1,28 @@ +/* + * Copyright 2012 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _ASYNC_H +#define _ASYNC_H + +#include "types.h" + +void Async_process(void); +uint32 Async_timeBeforeNextMs(void); + +#endif /* _ASYNC_H */ + diff --git a/src/libs/callback/callback.c b/src/libs/callback/callback.c new file mode 100644 index 0000000..e8ae8e9 --- /dev/null +++ b/src/libs/callback/callback.c @@ -0,0 +1,193 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "port.h" +#include "types.h" + +#include <assert.h> +#include <stdlib.h> +#include <sys/types.h> + +#include "libs/threadlib.h" + +typedef struct CallbackLink CallbackLink; + +#define CALLBACK_INTERNAL +#include "callback.h" + +struct CallbackLink { + CallbackLink *next; + CallbackFunction callback; + CallbackArg arg; +}; + +static CallbackLink *callbacks; +static CallbackLink **callbacksEnd; +static CallbackLink *const *callbacksProcessEnd; + +static Mutex callbackListLock; + +static inline void +CallbackList_lock(void) { + LockMutex(callbackListLock); +} + +static inline void +CallbackList_unlock(void) { + UnlockMutex(callbackListLock); +} + +#if 0 +static inline bool +CallbackList_isLocked(void) { + // TODO +} +#endif + +void +Callback_init(void) { + callbacks = NULL; + callbacksEnd = &callbacks; + callbacksProcessEnd = &callbacks; + callbackListLock = CreateMutex("Callback List Lock", SYNC_CLASS_TOPLEVEL); +} + +void +Callback_uninit(void) { + // TODO: cleanup the queue? + DestroyMutex (callbackListLock); + callbackListLock = 0; +} + +// Callbacks are guaranteed to be called in the order that they are queued. +CallbackID +Callback_add(CallbackFunction callback, CallbackArg arg) { + CallbackLink *link = malloc(sizeof (CallbackLink)); + link->callback = callback; + link->arg = arg; + link->next = NULL; + + CallbackList_lock(); + *callbacksEnd = link; + callbacksEnd = &link->next; + CallbackList_unlock(); + return (CallbackID) link; +} + + +static void +CallbackLink_delete(CallbackLink *link) { + free(link); +} + +// Pre: CallbackList is locked. +static CallbackLink ** +CallbackLink_find(CallbackLink *link) { + CallbackLink **ptr; + + //assert(CallbackList_isLocked()); + for (ptr = &callbacks; *ptr != NULL; ptr = &(*ptr)->next) { + if (*ptr == link) + return ptr; + } + return NULL; +} + +bool +Callback_remove(CallbackID id) { + CallbackLink *link = (CallbackLink *) id; + CallbackLink **linkPtr; + + CallbackList_lock(); + + linkPtr = CallbackLink_find(link); + if (linkPtr == NULL) { + CallbackList_unlock(); + return false; + } + + if (callbacksEnd == &(*linkPtr)->next) + callbacksEnd = linkPtr; + if (callbacksProcessEnd == &(*linkPtr)->next) + callbacksProcessEnd = linkPtr; + *linkPtr = (*linkPtr)->next; + + CallbackList_unlock(); + + CallbackLink_delete(link); + return true; +} + +static inline void +CallbackLink_doCallback(CallbackLink *link) { + (link->callback)(link->arg); +} + +// Call all queued callbacks currently in the queue. Callbacks queued +// from inside the called functions will not be processed until the next +// call of Callback_process(). +// It is allowed to remove callbacks from inside the called functions. +// NB: Callback_process() must never be called from more than one thread +// at the same time. It's the only sensible way to ensure that the +// callbacks are called in the order in which they were queued. +// It is however allowed to call Callback_process() from inside the +// callback function called by Callback_process() itself. +void +Callback_process(void) { + CallbackLink *link; + + // We set 'callbacksProcessEnd' to callbacksEnd. Callbacks added + // from inside a callback function will be placed after + // callbacksProcessEnd, and will hence not be processed this + // call of Callback_process(). + CallbackList_lock(); + callbacksProcessEnd = callbacksEnd; + CallbackList_unlock(); + + for (;;) { + CallbackList_lock(); + if (callbacksProcessEnd == &callbacks) { + CallbackList_unlock(); + break; + } + assert(callbacks != NULL); + // If callbacks == NULL, then callbacksProcessEnd == &callbacks + link = callbacks; + callbacks = link->next; + if (callbacksEnd == &link->next) + callbacksEnd = &callbacks; + if (callbacksProcessEnd == &link->next) + callbacksProcessEnd = &callbacks; + CallbackList_unlock(); + + CallbackLink_doCallback(link); + CallbackLink_delete(link); + } +} + +bool +Callback_haveMore(void) { + bool result; + + CallbackList_lock(); + result = (callbacks != NULL); + CallbackList_unlock(); + + return result; +} + diff --git a/src/libs/callback/callback.h b/src/libs/callback/callback.h new file mode 100644 index 0000000..e04ebe8 --- /dev/null +++ b/src/libs/callback/callback.h @@ -0,0 +1,43 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef LIBS_CALLBACK_CALLBACK_H_ +#define LIBS_CALLBACK_CALLBACK_H_ + +#include "types.h" + +#ifdef CALLBACK_INTERNAL +typedef CallbackLink *CallbackID; +#else +typedef void *CallbackID; + // Uniquely identifies a queued callback. +#endif +#define CallbackID_invalid ((CallbackID ) NULL) + +typedef void *CallbackArg; +typedef void (*CallbackFunction)(CallbackArg arg); + +void Callback_init(void); +void Callback_uninit(void); +CallbackID Callback_add(CallbackFunction callback, CallbackArg arg); +bool Callback_remove(CallbackID id); +void Callback_process(void); +bool Callback_haveMore(void); + +#endif /* LIBS_CALLBACK_CALLBACK_H_ */ + diff --git a/src/libs/cdp/Makeinfo b/src/libs/cdp/Makeinfo new file mode 100644 index 0000000..b2a83b5 --- /dev/null +++ b/src/libs/cdp/Makeinfo @@ -0,0 +1,3 @@ +uqm_CFILES="cdp.c cdpapi.c" +uqm_HFILES="cdp_alli.h cdpapi.h cdp.h cdp_iio.h cdp_imem.h cdpint.h + cdp_isnd.h cdp_ivid.h cdpmod.h windl.h" diff --git a/src/libs/cdp/cdp.c b/src/libs/cdp/cdp.c new file mode 100644 index 0000000..ca9536e --- /dev/null +++ b/src/libs/cdp/cdp.c @@ -0,0 +1,437 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* + * CDP library definitions + */ + +#include <string.h> +#include <stdio.h> +#include "cdp.h" +#include "port.h" +#include "cdpint.h" +#include "cdpmod.h" +#include "uio.h" +#include "uqmversion.h" +#ifdef WIN32 +# include "windl.h" +#else +# include <dlfcn.h> +#endif + +#define MAX_CDPS 63 +#define CDPDIR "cdps" + +// internal CDP module representation +struct cdp_Module +{ + bool builtin; // used at least once indicator + bool used; // used at least once indicator + void* hmodule; // loaded module handle + uint32 refcount; // reference count + cdp_ModuleInfo* info; // cdp exported info + +}; + +// Kernel module info +// not a real module, and not loadable either +// this just provides information to other modules +cdp_ModuleInfo cdp_kernel_info = +{ + sizeof (cdp_ModuleInfo), + CDPAPI_VERSION, // API version we are using + UQM_MAJOR_VERSION, UQM_MINOR_VERSION, UQM_PATCH_VERSION, + UQM_MAJOR_VERSION, UQM_MINOR_VERSION, UQM_PATCH_VERSION, + CDP_MODINFO_RESERVED1, + "UQM", // CDP context cannonical name + "UQM Kernel", // CDP mod name +# define S(i) #i + // CDP mod version + S(UQM_MAJOR_VERSION) "." S(UQM_MINOR_VERSION) UQM_EXTRA_VERSION, +# undef S + "UQM Team", // CDP mod author + "http://sc2.sf.net", // CDP mod URL + "Eternal doctrine executor", // CDP mod comment + CDP_MODINFO_RESERVED2, + NULL, NULL // no entrypoints defined/needed +}; + +static cdp_Module cdp_modules[MAX_CDPS + 1] = +{ + {true, true, NULL, 1, &cdp_kernel_info}, + + {false, false, NULL, 0, NULL} // term +}; + +extern uio_DirHandle *cdpDir; + +static bool cdp_inited = false; +static cdp_Error cdp_last_error = CDPERR_NONE; +static char cdp_path[PATH_MAX] = ""; + +cdp_Error +cdp_GetError (void) +{ + cdp_Error ret = cdp_last_error; + cdp_last_error = CDPERR_NONE; + return ret; +} + +bool +cdp_Init (void) +{ + int i; + void* hkernel; + + if (cdp_inited) + { + fprintf (stderr, "cdp_Init(): called when already inited\n"); + return true; + } + + // preprocess built-in modules + hkernel = dlopen (NULL, RTLD_LAZY); + + for (i = 0; cdp_modules[i].builtin; ++i) + cdp_modules[i].hmodule = hkernel; + + // clear the rest + //memset (cdp_modules + i, 0, + // sizeof (cdp_modules) - sizeof (cdp_Module) * i); + + //strcpy (cdp_path, cdpDir->path); + cdp_InitApi (); + cdp_inited = true; + + return true; +} + +void +cdp_Uninit (void) +{ + if (!cdp_inited) + { + fprintf (stderr, "cdp_Uninit(): called when not inited\n"); + return; + } + + cdp_UninitApi (); + cdp_FreeAllModules (); + cdp_inited = false; +} + +cdp_Module* +cdp_LoadModule (const char* modname) + // special value for modname: NULL - refers to kernel (UQM exe) +{ + void* mod; + char modpath[PATH_MAX]; + const char* errstr; + cdp_ModuleInfo* info; + int i; + cdp_Module* cdp; + cdp_Module* newslot = 0; + cdp_Itf* ihost; + + if (modname == NULL) + return cdp_modules; + + if (!cdp_inited) + { + fprintf (stderr, "cdp_LoadModule(): called when not inited\n"); + return 0; + } + + // load dynamic lib + sprintf (modpath, "%s/%s%s", CDPDIR, modname, CDPEXT); + mod = dlopen (modpath, RTLD_NOW); + if (!mod) + { + cdp_last_error = CDPERR_NOT_FOUND; + return NULL; + } + + // look it up in already loaded + for (i = 0, cdp = cdp_modules; cdp->used && cdp->hmodule != mod; + ++cdp, ++i) + { + // and pick up an empty slot (where available) + if (!newslot && !cdp->hmodule) + newslot = cdp; + } + if (i >= MAX_CDPS) + { + fprintf (stderr, "cdp_LoadModule(): " + "CDPs limit reached while loading %s\n", + modname); + dlclose (mod); + cdp_last_error = CDPERR_TOO_MANY; + return NULL; + } + + if (cdp->hmodule) + { // module has already been loaded + cdp->refcount++; + return cdp; + } + + dlerror (); // clear any error + info = dlsym (mod, CDP_INFO_SYM_NAME); + if (!info && (errstr = dlerror ())) + { + dlclose (mod); + cdp_last_error = CDPERR_BAD_MODULE; + return NULL; + } + + if (info->size < CDP_MODINFO_MIN_SIZE || info->api_ver > CDPAPI_VERSION) + { + fprintf (stderr, "cdp_LoadModule(): " + "CDP %s is invalid or newer API version\n", + modname); + dlclose (mod); + cdp_last_error = CDPERR_UNKNOWN_VER; + return NULL; + } + + ihost = cdp_GetInterface (CDPITF_KIND_HOST, info->api_ver); + if (!ihost) + { + fprintf (stderr, "cdp_LoadModule(): " + "CDP %s requested unsupported API version 0x%08x\n", + modname, info->api_ver); + dlclose (mod); + cdp_last_error = CDPERR_UNKNOWN_VER; + return NULL; + } + + if (!newslot) + { + newslot = cdp; + newslot->used = true; + // make next one a term + cdp[1].builtin = false; + cdp[1].used = false; + cdp[1].hmodule = NULL; + cdp[1].refcount = 0; + } + newslot->hmodule = mod; + newslot->refcount = 1; + newslot->info = info; + + if (!info->module_init (newslot, (cdp_Itf_Host*)ihost)) + { + fprintf (stderr, "cdp_LoadModule(): " + "CDP %s failed to init\n", + modname); + dlclose (mod); + newslot->hmodule = NULL; + newslot->info = NULL; + newslot->refcount = 0; + cdp_last_error = CDPERR_INIT_FAILED; + return NULL; + } + + + return newslot; +} + +cdp_Module* +cdp_CheckModule (cdp_Module* module) +{ + if (module < cdp_modules || module >= cdp_modules + MAX_CDPS || + !module->hmodule || !module->info) + return NULL; + else + return module; +} + +void +cdp_FreeModule (cdp_Module* module) +{ + cdp_Module* modslot = cdp_CheckModule (module); + + if (!modslot || modslot->builtin) + return; + + modslot->refcount--; + if (modslot->refcount == 0) + modslot->info->module_term (); + + dlclose (modslot->hmodule); + + if (modslot->refcount == 0) + { + modslot->hmodule = NULL; + modslot->info = NULL; + } +} + +const char* +cdp_GetModuleContext (cdp_Module* module, bool bMetaString) +{ + cdp_Module* modslot = cdp_CheckModule (module); + if (bMetaString) + { + if (!modslot) + return "(Error)"; + if (!modslot->info->context_name) + return "(Null)"; + } + else if (!modslot) + { + return NULL; + } + return modslot->info->context_name; +} + +const char* +cdp_GetModuleName (cdp_Module* module, bool bMetaString) +{ + cdp_Module* modslot = cdp_CheckModule (module); + if (bMetaString) + { + if (!modslot) + return "(Error)"; + if (!modslot->info->name) + return "(Null)"; + } + else if (!modslot) + { + return NULL; + } + return modslot->info->name; +} + +uint32 +cdp_GetModuleVersion (cdp_Module* module) +{ + cdp_Module* modslot = cdp_CheckModule (module); + if (!modslot) + return 0; + return (modslot->info->ver_major << 16) | modslot->info->ver_minor; +} + +const char* +cdp_GetModuleVersionString (cdp_Module* module, bool bMetaString) +{ + cdp_Module* modslot = cdp_CheckModule (module); + if (bMetaString) + { + if (!modslot) + return "(Error)"; + if (!modslot->info->ver_string) + return "(Null)"; + } + else if (!modslot) + { + return NULL; + } + return modslot->info->ver_string; +} + +const char* +cdp_GetModuleComment (cdp_Module* module, bool bMetaString) +{ + cdp_Module* modslot = cdp_CheckModule (module); + if (bMetaString) + { + if (!modslot) + return "(Error)"; + if (!modslot->info->comments) + return "(Null)"; + } + else if (!modslot) + { + return NULL; + } + return modslot->info->comments; +} + +// load-all and free-all are here temporarily until +// configs are in place +int +cdp_LoadAllModules (void) +{ + uio_DirList *dirList; + int nummods = 0; + int i; + + if (!cdp_inited) + { + fprintf (stderr, "cdp_LoadAllModules(): called when not inited\n"); + return 0; + } + + if (!cdpDir) + return 0; + + fprintf (stderr, "Loading all CDPs...\n"); + + dirList = uio_getDirList (cdpDir, "", CDPEXT, match_MATCH_SUFFIX); + if (!dirList) + return 0; + + for (i = 0; i < dirList->numNames; i++) + { + char modname[PATH_MAX]; + char* pext; + cdp_Module* mod; + + fprintf (stderr, "Loading CDP %s...\n", dirList->names[i]); + strcpy (modname, dirList->names[i]); + pext = strrchr (modname, '.'); + if (pext) // strip extension + *pext = 0; + + mod = cdp_LoadModule (modname); + if (mod) + { + nummods++; + fprintf (stderr, "\tloaded CDP: %s v%s (%s)\n", + cdp_GetModuleName (mod, true), + cdp_GetModuleVersionString (mod, true), + cdp_GetModuleComment (mod, true)); + } + else + { + fprintf (stderr, "\tload failed, error %u\n", + cdp_GetError ()); + } + } + uio_freeDirList (dirList); + + return nummods; +} + +void +cdp_FreeAllModules (void) +{ + cdp_Module* cdp; + + if (!cdp_inited) + { + fprintf (stderr, "cdp_FreeAllModules(): called when not inited\n"); + return; + } + + for (cdp = cdp_modules; cdp->used; ++cdp) + { + if (!cdp->builtin && cdp->hmodule) + cdp_FreeModule (cdp); + } +} diff --git a/src/libs/cdp/cdp.h b/src/libs/cdp/cdp.h new file mode 100644 index 0000000..0390f07 --- /dev/null +++ b/src/libs/cdp/cdp.h @@ -0,0 +1,47 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* + * CDP library declarations + */ + +#ifndef LIBS_CDP_CDP_H_ +#define LIBS_CDP_CDP_H_ + +#include "types.h" +#include "cdpapi.h" + +// these will be called by the UQM engine +// and plugins manager +bool cdp_Init (void); +void cdp_Uninit (void); +cdp_Error cdp_GetError (void); +cdp_Module* cdp_LoadModule (const char* modname); +void cdp_FreeModule (cdp_Module* module); +// in the following calls when bMetaString is set +// function will never return a NULL, instead it will +// return a valid string -- error meta-string +const char* cdp_GetModuleContext (cdp_Module* module, bool bMetaString); +const char* cdp_GetModuleName (cdp_Module* module, bool bMetaString); +uint32 cdp_GetModuleVersion (cdp_Module* module); +const char* cdp_GetModuleVersionString (cdp_Module* module, bool bMetaString); +const char* cdp_GetModuleComment (cdp_Module* module, bool bMetaString); + +int cdp_LoadAllModules (void); +void cdp_FreeAllModules (void); + +#endif /* LIBS_CDP_CDP_H_ */ diff --git a/src/libs/cdp/cdp_alli.h b/src/libs/cdp/cdp_alli.h new file mode 100644 index 0000000..31520e5 --- /dev/null +++ b/src/libs/cdp/cdp_alli.h @@ -0,0 +1,31 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* + * CDP All-interface list (for simplicity) + */ + +#ifndef LIBS_CDP_CDP_ALLI_H_ +#define LIBS_CDP_CDP_ALLI_H_ + +#include "cdp_iio.h" +#include "cdp_imem.h" +#include "cdp_isnd.h" +#include "cdp_ivid.h" +// TODO: add more cdp_iXXX.h here as they are defined + +#endif /* LIBS_CDP_CDP_ALLI_H_ */ diff --git a/src/libs/cdp/cdp_iio.h b/src/libs/cdp/cdp_iio.h new file mode 100644 index 0000000..19e6513 --- /dev/null +++ b/src/libs/cdp/cdp_iio.h @@ -0,0 +1,50 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* + * CDP Unified IO Interface + */ + +#ifndef LIBS_CDP_CDP_IIO_H_ +#define LIBS_CDP_CDP_IIO_H_ + +#include "types.h" +#include "libs/uio.h" + +// CDP IO Interface entry points +typedef struct +{ + uio_Stream* (* fopen) (uio_DirHandle *dir, const char *path, + const char *mode); + int (* fclose) (uio_Stream *stream); + size_t (* fread) (void *buf, size_t size, size_t nmemb, + uio_Stream *stream); + size_t (* fwrite) (const void *buf, size_t size, size_t nmemb, + uio_Stream *stream); + int (* fseek) (uio_Stream *stream, long offset, int whence); + long (* ftell) (uio_Stream *stream); + int (* fflush) (uio_Stream *stream); + int (* feof) (uio_Stream *stream); + int (* ferror) (uio_Stream *stream); + +} cdp_Itf_IoVtbl_v1; + +// the following are for the sake of module writers +typedef cdp_Itf_IoVtbl_v1 cdp_Itf_IoVtbl; +typedef cdp_Itf_IoVtbl cdp_Itf_Io; + +#endif /* LIBS_CDP_CDP_IIO_H_ */ diff --git a/src/libs/cdp/cdp_imem.h b/src/libs/cdp/cdp_imem.h new file mode 100644 index 0000000..7d1b59c --- /dev/null +++ b/src/libs/cdp/cdp_imem.h @@ -0,0 +1,42 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* + * CDP Memory Interface + */ + +#ifndef LIBS_CDP_CDP_IMEM_H_ +#define LIBS_CDP_CDP_IMEM_H_ + +#include "types.h" +#include "libs/memlib.h" + +// CDP Memory Interface entry points +typedef struct +{ + void* (* malloc) (int size); + void (* free) (void *p); + void* (* calloc) (int size); + void* (* realloc) (void *p, int size); + +} cdp_Itf_MemoryVtbl_v1; + +// the following are for the sake of module writers +typedef cdp_Itf_MemoryVtbl_v1 cdp_Itf_MemoryVtbl; +typedef cdp_Itf_MemoryVtbl cdp_Itf_Memory; + +#endif /* LIBS_CDP_CDP_IMEM_H_ */ diff --git a/src/libs/cdp/cdp_isnd.h b/src/libs/cdp/cdp_isnd.h new file mode 100644 index 0000000..ae4aa94 --- /dev/null +++ b/src/libs/cdp/cdp_isnd.h @@ -0,0 +1,43 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* + * CDP Sound Interface + */ + +#ifndef LIBS_CDP_CDP_ISND_H_ +#define LIBS_CDP_CDP_ISND_H_ + +#include "types.h" +#include "libs/sound/sound.h" +#include "libs/sound/decoders/decoder.h" + +// CDP Sound Interface entry points +typedef struct +{ + TFB_RegSoundDecoder* (* RegisterDecoder) (const char* fileext, + TFB_SoundDecoderFuncs*); + void (* UnregisterDecoder) (TFB_RegSoundDecoder*); + const TFB_SoundDecoderFuncs* (* LookupDecoder) (const char* fileext); + +} cdp_Itf_SoundVtbl_v1; + +// the following are for the sake of module writers +typedef cdp_Itf_SoundVtbl_v1 cdp_Itf_SoundVtbl; +typedef cdp_Itf_SoundVtbl cdp_Itf_Sound; + +#endif /* LIBS_CDP_CDP_ISND_H_ */ diff --git a/src/libs/cdp/cdp_ivid.h b/src/libs/cdp/cdp_ivid.h new file mode 100644 index 0000000..a9fc0b2 --- /dev/null +++ b/src/libs/cdp/cdp_ivid.h @@ -0,0 +1,43 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* + * CDP Video Interface + */ + +#ifndef LIBS_CDP_CDP_IVID_H_ +#define LIBS_CDP_CDP_IVID_H_ + +#include "types.h" +#include "libs/video/video.h" +#include "libs/video/videodec.h" + +// CDP Video Interface entry points +typedef struct +{ + TFB_RegVideoDecoder* (* RegisterDecoder) (const char* fileext, + TFB_VideoDecoderFuncs*); + void (* UnregisterDecoder) (TFB_RegVideoDecoder*); + const TFB_VideoDecoderFuncs* (* LookupDecoder) (const char* fileext); + +} cdp_Itf_VideoVtbl_v1; + +// the following are for the sake of module writers +typedef cdp_Itf_VideoVtbl_v1 cdp_Itf_VideoVtbl; +typedef cdp_Itf_VideoVtbl cdp_Itf_Video; + +#endif /* LIBS_CDP_CDP_IVID_H_ */ diff --git a/src/libs/cdp/cdpapi.c b/src/libs/cdp/cdpapi.c new file mode 100644 index 0000000..e50e39d --- /dev/null +++ b/src/libs/cdp/cdpapi.c @@ -0,0 +1,864 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* + * CDP API definitions + * the API is used by both the engine and modules + */ + +#include "cdp.h" +#include "port.h" +#include "cdpint.h" +#include "uqmversion.h" + +#define MAX_REG_ITFS 255 +#define MAX_REG_EVENTS 1023 + +static cdp_Error cdp_api_error = CDPERR_NONE; + +static uint32 cdp_Host_GetApiVersion (void); +static uint32 cdp_Host_GetVersion (void); +static cdp_Error cdp_Host_GetApiError (void); +static cdp_Itf* cdp_Host_GetItf (const char* name); +static bool cdp_Host_GetItfs (cdp_ItfDef* defs); +static cdp_ItfReg* cdp_Host_RegisterItf (const char* name, + cdp_ApiVersion ver_from, cdp_ApiVersion ver_to, + cdp_Itf*, cdp_Module*); +static void cdp_Host_UnregisterItf (cdp_ItfReg*); +static bool cdp_Host_RegisterItfs (cdp_ItfDef* defs, cdp_Module*); +static void cdp_Host_UnregisterItfs (cdp_ItfDef* defs); +static cdp_Event cdp_Host_GetEvent (const char* name); +static bool cdp_Host_GetEvents (cdp_EventDef* defs); +static cdp_EventReg* cdp_Host_RegisterEvent (const char* name, cdp_Module*); +static void cdp_Host_UnregisterEvent (cdp_EventReg*); +static bool cdp_Host_RegisterEvents (cdp_EventDef* defs, cdp_Module*); +static void cdp_Host_UnregisterEvents (cdp_EventDef* defs); +static bool cdp_Host_SubscribeEvent (cdp_Event, cdp_EventProc, cdp_Module*); +static void cdp_Host_UnsubscribeEvent (cdp_Event, cdp_EventProc); +static bool cdp_Host_SubscribeEvents (cdp_EventDef* defs, cdp_Module*); +static void cdp_Host_UnsubscribeEvents (cdp_EventDef* defs); +static cdp_EventResult cdp_Host_FireEvent (cdp_EventReg*, uint32, void*); + +// Interfaces +cdp_Itf_HostVtbl_v1 cdp_host_itf_v1 = +{ + cdp_Host_GetApiVersion, + cdp_Host_GetVersion, + cdp_Host_GetApiError, + cdp_Host_GetItf, + cdp_Host_GetItfs, + cdp_Host_RegisterItf, + cdp_Host_UnregisterItf, + cdp_Host_RegisterItfs, + cdp_Host_UnregisterItfs, + cdp_Host_GetEvent, + cdp_Host_GetEvents, + cdp_Host_RegisterEvent, + cdp_Host_UnregisterEvent, + cdp_Host_RegisterEvents, + cdp_Host_UnregisterEvents, + cdp_Host_SubscribeEvent, + cdp_Host_UnsubscribeEvent, + cdp_Host_SubscribeEvents, + cdp_Host_UnsubscribeEvents, + cdp_Host_FireEvent, +}; + +cdp_Itf_MemoryVtbl_v1 cdp_memory_itf_v1 = +{ + HMalloc, + HFree, + HCalloc, + HRealloc, +}; + +cdp_Itf_IoVtbl_v1 cdp_io_itf_v1 = +{ + uio_fopen, + uio_fclose, + uio_fread, + uio_fwrite, + uio_fseek, + uio_ftell, + uio_fflush, + uio_feof, + uio_ferror, +}; + +cdp_Itf_SoundVtbl_v1 cdp_sound_itf_v1 = +{ + SoundDecoder_Register, + SoundDecoder_Unregister, + SoundDecoder_Lookup, +}; + +cdp_Itf_VideoVtbl_v1 cdp_video_itf_v1 = +{ + VideoDecoder_Register, + VideoDecoder_Unregister, + VideoDecoder_Lookup, +}; + +// the actual interface registration struct/handle +struct cdp_ItfReg +{ + bool builtin; + bool used; + const char* name; + cdp_ApiVersion ver_from; + cdp_ApiVersion ver_to; + cdp_Itf* itfvtbl; + cdp_Module* module; +}; + +#define CDP_DECLARE_ITF(kind,vf,vt,vtbl) \ + {true, true, CDPITF_KIND_##kind, \ + CDPAPI_VERSION_##vf, CDPAPI_VERSION_##vt, vtbl, NULL} + +// Built-in interfaces + space for loadable +cdp_ItfReg cdp_itfs[MAX_REG_ITFS + 1] = +{ + CDP_DECLARE_ITF (HOST, 1, 1, &cdp_host_itf_v1), + CDP_DECLARE_ITF (MEMORY, 1, 1, &cdp_memory_itf_v1), + CDP_DECLARE_ITF (IO, 1, 1, &cdp_io_itf_v1), + CDP_DECLARE_ITF (SOUND, 1, 1, &cdp_sound_itf_v1), + CDP_DECLARE_ITF (VIDEO, 1, 1, &cdp_video_itf_v1), + // TODO: put newly defined built-in interfaces here + + {false, false, "", 0, 0, NULL} // term +}; + +// event bind descriptor +typedef struct +{ + cdp_EventProc proc; + cdp_Module* module; + +} cdp_EventBind; + +#define EVENT_BIND_GROW 16 + +// the actual event registration struct/handle +struct cdp_EventReg +{ + bool builtin; + bool used; + const char* name; + cdp_EventBind* binds; + uint32 bindslots; + cdp_Module* module; +}; + +#define CDP_DECLARE_EVENT(name) \ + {true, true, "UQM." #name, NULL, 0, NULL} + +// Built-in events + space for loadable +// a cdp_Event handle is an index into this array +cdp_EventReg cdp_evts[MAX_REG_EVENTS + 1] = +{ + // sample - no real events defined yet + CDP_DECLARE_EVENT (PlanetSide.TouchDown), + CDP_DECLARE_EVENT (PlanetSide.LiftOff), + // TODO: put newly defined built-in events here + + {false, false, "", NULL, 0, NULL} // term +}; + +cdp_Error +cdp_GetApiError (void) +{ + cdp_Error ret = cdp_api_error; + cdp_api_error = CDPERR_NONE; + return ret; +} + +bool +cdp_InitApi (void) +{ + int i; + cdp_Module* kernel; + + // preprocess built-in itfs + kernel = cdp_LoadModule (NULL); + + for (i = 0; cdp_itfs[i].builtin; ++i) + { + cdp_itfs[i].module = kernel; + } + // clear the rest + //memset (cdp_itfs + i, 0, + // sizeof (cdp_itfs) - sizeof (cdp_ItfReg) * i); + + for (i = 0; cdp_evts[i].builtin; ++i) + { + cdp_evts[i].module = kernel; + cdp_evts[i].bindslots = 0; + cdp_evts[i].binds = NULL; + } + + return true; +} + +void +cdp_UninitApi (void) +{ + cdp_ItfReg* itf; + + // unregister custom interfaces + for (itf = cdp_itfs; itf->used; ++itf) + { + if (!itf->builtin) + { + itf->used = false; + if (itf->name) + HFree (itf->name); + itf->name = NULL; + itf->itfvtbl = NULL; + itf->module = NULL; + } + } +} + +static uint32 +cdp_Host_GetApiVersion (void) +{ + return CDPAPI_VERSION; +} + +static uint32 +cdp_Host_GetVersion (void) +{ + return (UQM_MAJOR_VERSION << 20) | (UQM_MINOR_VERSION << 15) | + UQM_PATCH_VERSION; +} + +static cdp_Error +cdp_Host_GetApiError (void) +{ + return cdp_GetApiError (); +} + +static char* +cdp_MakeContextName (const char* ctx, const char* name) +{ + int namelen; + char* id_name; + + namelen = strlen(ctx) + strlen(name) + 2; + id_name = HMalloc (namelen); + strcpy(id_name, ctx); + strcat(id_name, "."); + strcat(id_name, name); + + return id_name; +} + +/*********************************************************** + * Interface system * + ***********************************************************/ + +cdp_ItfReg* +cdp_GetInterfaceReg (const char* name, cdp_ApiVersion api_ver) +{ + cdp_ItfReg* itf; + + for (itf = cdp_itfs; itf->used && + (!itf->name || strcasecmp(itf->name, name) != 0 || + api_ver < itf->ver_from || api_ver > itf->ver_to); + itf++) + ; + if (!itf->name) + { + cdp_api_error = CDPERR_NO_ITF; + return NULL; + } + + return itf; +} + +cdp_Itf* +cdp_GetInterface (const char* name, cdp_ApiVersion api_ver) +{ + cdp_ItfReg* reg; + + reg = cdp_GetInterfaceReg (name, api_ver); + return reg ? reg->itfvtbl : NULL; +} + +static cdp_Itf* +cdp_Host_GetItf (const char* name) +{ + return cdp_GetInterface (name, CDPAPI_VERSION_1); +} + +static bool +cdp_Host_GetItfs (cdp_ItfDef* defs) +{ + cdp_ItfDef* def; + cdp_ItfReg* reg; + int errors = 0; + + for (def = defs; def->name; ++def) + { + // registration handle is not returned + def->reg = NULL; + + reg = cdp_GetInterfaceReg (def->name, CDPAPI_VERSION_1); + if (reg) + { + def->itf = reg->itfvtbl; + def->name = reg->name; // set to cannonical name + def->ver_from = reg->ver_from; + def->ver_to = reg->ver_to; + def->module = reg->module; + } + else + { + def->itf = NULL; + def->module = NULL; + def->ver_from = 0; + def->ver_to = 0; + ++errors; + } + } + + return !errors; +} + +static cdp_ItfReg* +cdp_Host_RegisterItf (const char* name, cdp_ApiVersion ver_from, + cdp_ApiVersion ver_to, cdp_Itf* itfvtbl, + cdp_Module* owner) +{ + cdp_ItfReg* itfreg; + cdp_ItfReg* newslot = NULL; + char* id_name; + const char* ctx; + + if (!owner) + { + fprintf (stderr, "cdp_Host_RegisterItf(): " + "No owner info supplied\n"); + //return NULL; + } + if (!name || !*name || !itfvtbl) + { + fprintf (stderr, "cdp_Host_RegisterItf(): " + "Null or invalid interface (from %s)\n", + cdp_GetModuleName (owner, true)); + return NULL; + } + ctx = cdp_GetModuleContext (owner, false); + if (!ctx) + { + fprintf (stderr, "cdp_Host_RegisterItf(): " + "Null or invalid context (from %s)\n", + cdp_GetModuleName (owner, true)); + return NULL; + } + + // TODO: review version policy (below) + // enforce version policy and do not allow obsolete interfaces + // POLICY: all modules MUST be aware of recent API changes and will not + // be allowed to expose interfaces that support and/or utilize obsoleted + // API versions + if (ver_from < CDPAPI_VERSION_MIN) + ver_from = CDPAPI_VERSION_MIN; + if (ver_to < CDPAPI_VERSION_MIN) + { + fprintf (stderr, "cdp_Host_RegisterItf(): " + "Obsolete interface %s (from %s)\n", + name, cdp_GetModuleName (owner, true)); + return NULL; + } + + id_name = cdp_MakeContextName (ctx, name); + + // check if interface already registered + for (itfreg = cdp_itfs; itfreg->used && + (!itfreg->name || strcasecmp(itfreg->name, id_name) != 0 || + ver_from < itfreg->ver_from || ver_to > itfreg->ver_to); + ++itfreg) + { + // and pick up an empty slot (where available) + if (!newslot && !itfreg->name) + newslot = itfreg; + } + + if (itfreg >= cdp_itfs + MAX_REG_ITFS) + { + fprintf (stderr, "cdp_Host_RegisterItf(): " + "Interfaces limit reached\n"); + HFree (id_name); + return NULL; + } + else if (itfreg->name) + { + fprintf (stderr, "cdp_Host_RegisterItf(): " + "Interface %s already registered for these versions, " + "%s denied\n", + name, cdp_GetModuleName (owner, true)); + HFree (id_name); + return NULL; + } + + if (!newslot) + { + newslot = itfreg; + newslot->used = true; + // make next one a term + itfreg[1].builtin = false; + itfreg[1].used = false; + itfreg[1].name = NULL; + itfreg[1].itfvtbl = NULL; + } + + newslot->name = id_name; + newslot->ver_from = ver_from; + newslot->ver_to = ver_to; + newslot->itfvtbl = itfvtbl; + newslot->module = owner; + + return newslot; +} + +static void +cdp_Host_UnregisterItf (cdp_ItfReg* itfreg) +{ + if (itfreg < cdp_itfs || itfreg >= cdp_itfs + MAX_REG_ITFS || + !itfreg->name || !itfreg->itfvtbl) + { + fprintf (stderr, "cdp_Host_UnregisterItf(): " + "Invalid or expired interface passed\n"); + return; + } + + if (!itfreg->builtin) + { + HFree (itfreg->name); + } + itfreg->module = NULL; + itfreg->name = NULL; + itfreg->itfvtbl = NULL; +} + +static bool +cdp_Host_RegisterItfs (cdp_ItfDef* defs, cdp_Module* owner) +{ + cdp_ItfDef* def; + int errors = 0; + + for (def = defs; def->name; ++def) + { + def->reg = cdp_Host_RegisterItf (def->name, def->ver_from, + def->ver_to, def->itf, owner); + if (def->reg) + { + def->module = owner; + } + else + { + def->module = NULL; + ++errors; + } + } + + return !errors; +} + +static void +cdp_Host_UnregisterItfs (cdp_ItfDef* defs) +{ + cdp_ItfDef* def; + + for (def = defs; def->name; ++def) + { + if (def->reg) + cdp_Host_UnregisterItf (def->reg); + } +} + +/*********************************************************** + * Event system * + ***********************************************************/ + +cdp_EventReg* +cdp_GetEventReg (const char* name) +{ + cdp_EventReg* evt; + + for (evt = cdp_evts; evt->used && + (!evt->name || strcasecmp(evt->name, name) != 0); + evt++) + ; + if (!evt->name) + { + cdp_api_error = CDPERR_NO_EVENT; + return NULL; + } + + return evt; +} + +// hopefully inlinable +static cdp_Event +cdp_EventFromReg (cdp_EventReg* reg) +{ + return (reg - cdp_evts) / sizeof (cdp_EventReg); +} + +// hopefully inlinable +static cdp_EventReg* +cdp_RegFromEvent (cdp_Event event) +{ + return cdp_evts + event; +} + +cdp_Event +cdp_GetEvent (const char* name) +{ + cdp_EventReg* reg; + + reg = cdp_GetEventReg (name); + return reg ? cdp_EventFromReg (reg) : CDP_EVENT_INVALID; +} + +static cdp_EventBind* +cdp_AllocEventBinds (cdp_EventBind* binds, uint32 ccur, uint32 cnew) +{ + cdp_EventBind* newbinds; + uint32 newsize; + + newsize = cnew * sizeof (cdp_EventBind); + if (binds) + newbinds = HRealloc (binds, newsize); + else + newbinds = HMalloc (newsize); + + if (cnew > ccur) + memset (newbinds + ccur, 0, + (cnew - ccur) * sizeof (cdp_EventBind)); + + return newbinds; +} + +static cdp_Event +cdp_Host_GetEvent (const char* name) +{ + return cdp_GetEvent (name); +} + +static bool +cdp_Host_GetEvents (cdp_EventDef* defs) +{ + cdp_EventDef* def; + cdp_EventReg* reg; + int errors = 0; + + for (def = defs; def->name; ++def) + { + // registration handle is not returned + def->reg = NULL; + + reg = cdp_GetEventReg (def->name); + if (reg) + { + def->event = cdp_EventFromReg(reg); + def->name = reg->name; // set to cannonical name + def->module = reg->module; + } + else + { + def->event = CDP_EVENT_INVALID; + def->module = NULL; + ++errors; + } + } + + return !errors; +} + +static cdp_EventReg* +cdp_Host_RegisterEvent (const char* name, cdp_Module* owner) +{ + cdp_EventReg* evtreg; + cdp_EventReg* newslot = NULL; + char* id_name; + const char* ctx; + + if (!owner) + { + fprintf (stderr, "cdp_Host_RegisterEvent(): " + "No owner info supplied\n"); + //return NULL; + } + if (!name || !*name) + { + fprintf (stderr, "cdp_Host_RegisterEvent(): " + "Null or invalid event (from %s)\n", + cdp_GetModuleName (owner, true)); + return NULL; + } + ctx = cdp_GetModuleContext (owner, false); + if (!ctx) + { + fprintf (stderr, "cdp_Host_RegisterEvent(): " + "Null or invalid context (from %s)\n", + cdp_GetModuleName (owner, true)); + return NULL; + } + + id_name = cdp_MakeContextName (ctx, name); + + // check if event already registered + for (evtreg = cdp_evts; evtreg->used && + (!evtreg->name || strcasecmp(evtreg->name, id_name) != 0); + ++evtreg) + { + // and pick up an empty slot (where available) + if (!newslot && !evtreg->name) + newslot = evtreg; + } + + if (evtreg >= cdp_evts + MAX_REG_EVENTS) + { + fprintf (stderr, "cdp_Host_RegisterEvent(): " + "Event limit reached\n"); + HFree (id_name); + return NULL; + } + else if (evtreg->name) + { + fprintf (stderr, "cdp_Host_RegisterEvent(): " + "Event %s already registered, " + "%s denied\n", + name, cdp_GetModuleName (owner, true)); + HFree (id_name); + return NULL; + } + + if (!newslot) + { + newslot = evtreg; + newslot->used = true; + // make next one a term + evtreg[1].builtin = false; + evtreg[1].used = false; + evtreg[1].name = NULL; + } + + newslot->name = id_name; + newslot->module = owner; + newslot->binds = NULL; + newslot->bindslots = 0; + + return newslot; +} + +static void +cdp_Host_UnregisterEvent (cdp_EventReg* evtreg) +{ + if (evtreg < cdp_evts || evtreg >= cdp_evts + MAX_REG_EVENTS || + !evtreg->name) + { + fprintf (stderr, "cdp_Host_UnregisterEvent(): " + "Invalid or expired event passed\n"); + return; + } + + if (!evtreg->builtin) + { + HFree (evtreg->name); + } + evtreg->module = NULL; + evtreg->name = NULL; + if (evtreg->binds) + HFree (evtreg->binds); + evtreg->binds = NULL; + evtreg->bindslots = 0; +} + +static bool +cdp_Host_RegisterEvents (cdp_EventDef* defs, cdp_Module* owner) +{ + cdp_EventDef* def; + int errors = 0; + + for (def = defs; def->name; ++def) + { + def->reg = cdp_Host_RegisterEvent (def->name, owner); + if (def->reg) + { + def->module = owner; + } + else + { + def->module = NULL; + ++errors; + } + } + + return !errors; +} + +static void +cdp_Host_UnregisterEvents (cdp_EventDef* defs) +{ + cdp_EventDef* def; + + for (def = defs; def->name; ++def) + { + if (def->reg) + cdp_Host_UnregisterEvent (def->reg); + } +} + +static bool +cdp_Host_SubscribeEvent (cdp_Event event, cdp_EventProc proc, cdp_Module* module) +{ + cdp_EventReg* reg = cdp_RegFromEvent (event); + cdp_EventBind* bind = NULL; + uint32 i; + + if (reg < cdp_evts || reg >= cdp_evts + MAX_REG_EVENTS || + !reg->name) + { + fprintf (stderr, "cdp_Host_SubscribeEvent(): " + "Invalid or expired event passed\n"); + return false; + } + + if (reg->binds) + { + // check for duplicate or find a new slot + for (i = 0, bind = reg->binds; i < reg->bindslots && + (!bind->proc || bind->proc != proc); + ++i, ++bind) + ; + if (i >= reg->bindslots) + { // full - add more slots + reg->binds = cdp_AllocEventBinds (reg->binds, + reg->bindslots, reg->bindslots + EVENT_BIND_GROW); + bind = reg->binds + reg->bindslots; + reg->bindslots += EVENT_BIND_GROW; + } + else if (bind->proc == proc) + { // already bound + return true; + } + } + else + { + reg->binds = cdp_AllocEventBinds (NULL, 0, EVENT_BIND_GROW); + reg->bindslots = EVENT_BIND_GROW; + bind = reg->binds; + } + + bind->proc = proc; + bind->module = module; + + return true; +} + +static void +cdp_Host_UnsubscribeEvent (cdp_Event event, cdp_EventProc proc) +{ + cdp_EventReg* reg = cdp_RegFromEvent (event); + cdp_EventBind* bind = NULL; + uint32 i; + + if (reg < cdp_evts || reg >= cdp_evts + MAX_REG_EVENTS || + !reg->name) + { // event either expired or invalid + return; + } + + if (!reg->binds || !reg->bindslots) + return; // hmm, no bindings + + // check for duplicate or find a new slot + for (i = 0, bind = reg->binds; i < reg->bindslots && + bind->proc != proc; + ++i, ++bind) + ; + if (i >= reg->bindslots) + return; // binding not found + + bind->proc = NULL; + bind->module = NULL; +} + +static bool +cdp_Host_SubscribeEvents (cdp_EventDef* defs, cdp_Module* module) +{ + cdp_EventDef* def; + int errors = 0; + + for (def = defs; def->name; ++def) + { + if (def->event != CDP_EVENT_INVALID && def->proc) + if (!cdp_Host_SubscribeEvent (def->event, def->proc, module)) + ++errors; + } + return !errors; +} + +static void +cdp_Host_UnsubscribeEvents (cdp_EventDef* defs) +{ + cdp_EventDef* def; + + for (def = defs; def->name; ++def) + { + if (def->event != CDP_EVENT_INVALID && def->proc) + cdp_Host_UnsubscribeEvent (def->event, def->proc); + } +} + +static cdp_EventResult +cdp_Host_FireEvent (cdp_EventReg* evtreg, uint32 iparam, void* pparam) +{ + bool bHandled = false; + cdp_EventResult ret = 0; + cdp_Event event; + cdp_EventBind* bind; + uint32 i; + + if (evtreg < cdp_evts || evtreg >= cdp_evts + MAX_REG_EVENTS || + !evtreg->name) + { +#ifdef DEBUG + fprintf (stderr, "cdp_Host_FireEvent(): Invalid event\n"); +#endif + return 0; + } + + if (!evtreg->binds) + return 0; // no subscribers + + event = cdp_EventFromReg (evtreg); + + // call event procs in opposite order of binding + for (i = evtreg->bindslots, bind = evtreg->binds + i - 1; + !bHandled && i > 0; + --i, --bind) + { + if (bind->proc) + ret = bind->proc (event, iparam, pparam, &bHandled); + } + return ret; +} diff --git a/src/libs/cdp/cdpapi.h b/src/libs/cdp/cdpapi.h new file mode 100644 index 0000000..e1a0e0b --- /dev/null +++ b/src/libs/cdp/cdpapi.h @@ -0,0 +1,154 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* + * CDP API declarations + * the API is used by both the engine and modules + */ + +#ifndef LIBS_CDP_CDPAPI_H_ +#define LIBS_CDP_CDPAPI_H_ + +#include "types.h" + +typedef enum +{ + CDPAPI_VERSION_1 = 0x00000001, // version 0.1 + + CDPAPI_VERSION = CDPAPI_VERSION_1, + CDPAPI_VERSION_MIN = CDPAPI_VERSION_1, + +} cdp_ApiVersion; + +typedef enum +{ + CDPERR_NONE = 0, + CDPERR_UKNOWN = 1, + CDPERR_NOT_FOUND = 2, + CDPERR_BAD_MODULE = 3, + CDPERR_OLD_VER = 4, + CDPERR_UNKNOWN_VER = 5, + CDPERR_TOO_MANY = 6, + CDPERR_INIT_FAILED = 7, + CDPERR_NO_ITF = 8, + CDPERR_DUPE_ITF = 9, + CDPERR_NO_EVENT = 10, + CDPERR_DUPE_EVENT = 11, + CDPERR_OTHER = 1000, + +} cdp_Error; + +typedef struct cdp_Module cdp_Module; +typedef void cdp_Itf; +typedef struct cdp_ItfReg cdp_ItfReg; + +// Interface KINDs - for convinience and uniformity +#define CDPITF_KIND_INVALID NULL +#define CDPITF_KIND_HOST "UQM.Host" +#define CDPITF_KIND_MEMORY "UQM.Memory" +#define CDPITF_KIND_IO "UQM.IO" +#define CDPITF_KIND_THREADS "UQM.Threads" +#define CDPITF_KIND_TIME "UQM.Time" +#define CDPITF_KIND_INPUT "UQM.Input" +#define CDPITF_KIND_TASK "UQM.Task" +#define CDPITF_KIND_RESOURCE "UQM.Resource" +#define CDPITF_KIND_SOUND "UQM.Sound" +#define CDPITF_KIND_VIDEO "UQM.Video" +#define CDPITF_KIND_GFX "UQM.Gfx" +#define CDPITF_KIND_MIXER "UQM.Mixer" + +// Interface definition structure +// pass an array of these to Host->GetItfs() for batch lookup +// pass an array of these to Host->RegisterItfs() for batch registration +typedef struct +{ + // fill in the first 4 members for batch registration + // fill in the 1st member for batch lookup + // terminate an array of these defs with name == NULL + const char* name; // interface ID + cdp_Itf* itf; // interface pointer + cdp_ApiVersion ver_from; // lowest supported version + cdp_ApiVersion ver_to; // highest supported version + + cdp_Module* module; // owner module + // the following member is only set during registration + cdp_ItfReg* reg; // registration handle (not set on lookup) + +} cdp_ItfDef; + +typedef unsigned int cdp_Event; +typedef struct cdp_EventReg cdp_EventReg; +typedef intptr_t cdp_EventResult; + +#define CDP_EVENT_INVALID (-1) + // used with cdp_Event + +typedef cdp_EventResult (* cdp_EventProc) + (cdp_Event, uint32, void*, bool* pbHandled); + +// Event definition structure +// pass an array of these to Host->GetItfs() for batch lookup +typedef struct +{ + // fill in the 1st member for batch lookup or registration + // also fill in the 2nd member for batch subscription + // terminate an array of these defs with name == NULL + const char* name; // event ID + cdp_EventProc proc; // event proc, set to NULL for no bind + + cdp_Event event; // subscribable event handle + cdp_Module* module; // owner module + // the following member is only set during registration + cdp_EventReg* reg; // registration handle (not set on lookup) + +} cdp_EventDef; + +// Host Interface +// the main itf of the API, it is passed to a loaded module +// module does everything else through this itf and itfs +// acquired through this itf +typedef struct +{ + uint32 (* GetApiVersion) (void); + uint32 (* GetVersion) (void); + cdp_Error (* GetApiError) (void); + cdp_Itf* (* GetItf) (const char* name); + bool (* GetItfs) (cdp_ItfDef* defs); + cdp_ItfReg* (* RegisterItf) (const char* name, + cdp_ApiVersion ver_from, cdp_ApiVersion ver_to, + cdp_Itf*, cdp_Module*); + void (* UnregisterItf) (cdp_ItfReg*); + bool (* RegisterItfs) (cdp_ItfDef* defs, cdp_Module*); + void (* UnregisterItfs) (cdp_ItfDef* defs); + cdp_Event (* GetEvent) (const char* name); + bool (* GetEvents) (cdp_EventDef* defs); + cdp_EventReg* (* RegisterEvent) (const char* name, cdp_Module*); + void (* UnregisterEvent) (cdp_EventReg*); + bool (* RegisterEvents) (cdp_EventDef* defs, cdp_Module*); + void (* UnregisterEvents) (cdp_EventDef* defs); + bool (* SubscribeEvent) (cdp_Event, cdp_EventProc, cdp_Module*); + void (* UnsubscribeEvent) (cdp_Event, cdp_EventProc); + bool (* SubscribeEvents) (cdp_EventDef* defs, cdp_Module*); + void (* UnsubscribeEvents) (cdp_EventDef* defs); + cdp_EventResult (* FireEvent) (cdp_EventReg*, uint32, void*); + +} cdp_Itf_HostVtbl_v1; + +typedef cdp_Itf_HostVtbl_v1 cdp_Itf_HostVtbl; +typedef cdp_Itf_HostVtbl cdp_Itf_Host; + +#endif /* LIBS_CDP_CDPAPI_H_ */ diff --git a/src/libs/cdp/cdpint.h b/src/libs/cdp/cdpint.h new file mode 100644 index 0000000..fbc3238 --- /dev/null +++ b/src/libs/cdp/cdpint.h @@ -0,0 +1,48 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* + * CDP common internal definitions + */ + +#ifndef LIBS_CDP_CDPINT_H_ +#define LIBS_CDP_CDPINT_H_ + +#include "cdpapi.h" +#include "cdp_imem.h" +#include "cdp_iio.h" +#include "cdp_isnd.h" +#include "cdp_ivid.h" + +#ifdef WIN32 +# define CDPEXT ".dll" +#else +# define CDPEXT ".so" +#endif + +extern cdp_Itf_HostVtbl_v1 cdp_host_itf_v1; +extern cdp_Itf_MemoryVtbl_v1 cdp_memory_itf_v1; +extern cdp_Itf_IoVtbl_v1 cdp_io_itf_v1; +extern cdp_Itf_SoundVtbl_v1 cdp_sound_itf_v1; + +bool cdp_InitApi (void); +void cdp_UninitApi (void); +cdp_Error cdp_GetApiError (void); +cdp_Itf* cdp_GetInterface (const char* name, cdp_ApiVersion); +cdp_ItfReg* cdp_GetInterfaceReg (const char* name, cdp_ApiVersion); + +#endif /* _CDPISND_H */ diff --git a/src/libs/cdp/cdpmod.h b/src/libs/cdp/cdpmod.h new file mode 100644 index 0000000..a1fdcae --- /dev/null +++ b/src/libs/cdp/cdpmod.h @@ -0,0 +1,92 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* + * CDP module definitions + * all CDP modules should #include this .h + */ + +#ifndef LIBS_CDP_CDPMOD_H_ +#define LIBS_CDP_CDPMOD_H_ + +#include "types.h" +#include "cdpapi.h" + +#define CDP_INFO_SYM cdpmodinfo +#define CDP_INFO_SYM_NAME "cdpmodinfo" + +// this struct will be exported from the module +// under 'cdpmodinfo' +typedef struct +{ + // mandatory, version control + uint32 size; // size of this structure + cdp_ApiVersion api_ver; // version of cdp API used, set to CDPAPI_VERSION + uint16 ver_major; // module version, somewhat informational + uint16 ver_minor; + uint16 ver_patch; + uint16 host_ver_major; // minimum host version required, purely informational + uint16 host_ver_minor; + uint16 host_ver_patch; + + // reserved members: set all to 0 or use CDP_MODINFO_RESERVED1 + uint32 _32_reserved1; + uint32 _32_reserved2; + uint32 _32_reserved3; + uint32 _32_reserved4; + + const char* context_name; + // cannonical context name (in proper case) + // this context will be used with all exposed objects + // English preferred; try to keep it below 32 chars + // informational, human-only; these fields have no real size + // restriction other than to keep it reasonable + const char* name; // descriptive name + const char* ver_string; // descriptive version + const char* author; // go nuts + const char* url; // go nuts + const char* comments; // go nuts + + // reserved members, set all to 0 or use CDP_MODINFO_RESERVED2 + const char* _sz_reserved1; + const char* _sz_reserved2; + const char* _sz_reserved3; + const char* _sz_reserved4; + + // mandatory, CDP entry points + // TODO: decide whether more EPs are necessary and if not move + // EPs above info-string members, abolishing _sz_reservedX + bool (* module_init) (cdp_Module* module, cdp_Itf_Host* hostitf); + void (* module_term) (); + +} cdp_ModuleInfo; + +#define CDP_MODINFO_RESERVED1 0,0,0,0 +#define CDP_MODINFO_RESERVED2 0,0,0,0 + +// the following is defined via the last mandatory member +#define CDP_MODINFO_MIN_SIZE \ + ( ((uint32) &((cdp_ModuleInfo*)0)->module_term) + \ + sizeof (((cdp_ModuleInfo*)0)->module_term) ) + +#if defined(WIN32) +# define CDPEXPORT __declspec(dllexport) +#else +# define CDPEXPORT +#endif + +#endif /* LIBS_CDP_CDPMOD_H_ */ diff --git a/src/libs/cdp/windl.c b/src/libs/cdp/windl.c new file mode 100644 index 0000000..4bc76f6 --- /dev/null +++ b/src/libs/cdp/windl.c @@ -0,0 +1,76 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* + * CDP dlopen() & Co. WIN32 implementation + */ + +#include "windl.h" +#include "port.h" +#define WIN32_LEAN_AND_MEAN +//#include <windows.h> +#include <wtypes.h> +#include <winbase.h> +#include <stdio.h> + +static uint32 wdl_last_error = 0; +static char wdl_errstr[128] = ""; + +void* +dlopen (const char *filename, int flag) + // all defined flags are not possible on win32 +{ + HMODULE hlib; + + if (filename == NULL) + hlib = GetModuleHandleA(NULL); + else + hlib = LoadLibraryA (filename); + + if (!hlib) + wdl_last_error = GetLastError (); + + return hlib; +} + +void* +dlsym (void *handle, const char *symbol) +{ + void* ptr = GetProcAddress (handle, symbol); + if (!ptr) + wdl_last_error = GetLastError (); + return ptr; +} + +int +dlclose (void *handle) +{ + return FreeLibrary (handle); +} + +char* +dlerror (void) +{ + if (wdl_last_error) + { + sprintf (wdl_errstr, "Windows error %u", wdl_last_error); + wdl_last_error = 0; + return wdl_errstr; + } + else + return NULL; +} diff --git a/src/libs/cdp/windl.h b/src/libs/cdp/windl.h new file mode 100644 index 0000000..71ca240 --- /dev/null +++ b/src/libs/cdp/windl.h @@ -0,0 +1,37 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* + * CDP dlopen() & Co. WIN32 implementation + */ + +#ifndef LIBS_CDP_WINDL_H_ +#define LIBS_CDP_WINDL_H_ + +#include "types.h" + +extern void *dlopen (const char *filename, int flag); +extern void *dlsym (void *handle, const char *symbol); +extern int dlclose (void *handle); +extern char *dlerror (void); + +/* these dlopen() flags are meaningless on win32 */ +#define RTLD_LAZY 1 /* lazy function call binding */ +#define RTLD_NOW 2 /* immediate function call binding */ +#define RTLD_GLOBAL 4 /* symbols in this dlopen'ed obj are visible to other dlopen'ed objs */ + +#endif /* LIBS_CDP_WINDL_H_ */ diff --git a/src/libs/cdplib.h b/src/libs/cdplib.h new file mode 100644 index 0000000..83d6a69 --- /dev/null +++ b/src/libs/cdplib.h @@ -0,0 +1,32 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef LIBS_CDPLIB_H_ +#define LIBS_CDPLIB_H_ + +#if defined(__cplusplus) +extern "C" { +#endif + +#include "cdp/cdp.h" + +#if defined(__cplusplus) +} +#endif + +#endif /* LIBS_CDPLIB_H_ */ diff --git a/src/libs/compiler.h b/src/libs/compiler.h new file mode 100644 index 0000000..a53f779 --- /dev/null +++ b/src/libs/compiler.h @@ -0,0 +1,96 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_COMPILER_H_ +#define LIBS_COMPILER_H_ + +#include "types.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +typedef uint8 BYTE; +typedef uint8 UBYTE; +typedef sint8 SBYTE; +typedef uint16 UWORD; +typedef sint16 SWORD; +typedef uint32 DWORD; +typedef sint32 SDWORD; + +typedef UWORD COUNT; +typedef SWORD SIZE; + +typedef char UNICODE; + + +typedef enum +{ + FALSE = 0, + TRUE +} BOOLEAN; + +typedef void (*PVOIDFUNC) (void); +typedef BOOLEAN (*PBOOLFUNC) (void); +typedef BYTE (*PBYTEFUNC) (void); +typedef UWORD (*PUWORDFUNC) (void); +typedef SWORD (*PSWORDFUNC) (void); +typedef DWORD (*PDWORDFUNC) (void); + +#define MAKE_BYTE(lo, hi) ((BYTE) (((BYTE) (hi) << (BYTE) 4) | (BYTE) (lo))) +#define LONIBBLE(x) ((BYTE) ((BYTE) (x) & (BYTE) 0x0F)) +#define HINIBBLE(x) ((BYTE) ((BYTE) (x) >> (BYTE) 4)) +#define MAKE_WORD(lo, hi) ((UWORD) ((BYTE) (hi) << 8) | (BYTE) (lo)) +#define LOBYTE(x) ((BYTE) ((UWORD) (x))) +#define HIBYTE(x) ((BYTE) ((UWORD) (x) >> 8)) +#define MAKE_DWORD(lo, hi) (((DWORD) (hi) << 16) | (UWORD) (lo)) +#define LOWORD(x) ((UWORD) ((DWORD) (x))) +#define HIWORD(x) ((UWORD) ((DWORD) (x) >> 16)) + + +// To be moved to port.h: +// _ALIGNED_ANY specifies an alignment suitable for any type +// _ALIGNED_ON specifies a caller-supplied alignment (should be a power of 2) +#if defined(__GNUC__) +# define _PACKED __attribute__((packed)) +# define _ALIGNED_ANY __attribute__((aligned)) +# define _ALIGNED_ON(bytes) __attribute__((aligned(bytes))) +#elif defined(_MSC_VER) +# define _ALIGNED_ANY +//# define _ALIGNED_ON(bytes) __declspec(align(bytes)) + // __declspec(align(bytes)) expects a constant. 'sizeof (type)' + // will not do. This is something that needs some attention, + // once we find someone with a 64 bits Windows machine. + // Leaving it alone for now. +# define _PACKED +# define _ALIGNED_ON(bytes) +#elif defined(__ARMCC__) +# define _PACKED __attribute__((packed)) +# define _ALIGNED_ANY __attribute__((aligned)) +# define _ALIGNED_ON(bytes) __attribute__((aligned(bytes))) +#elif defined(__WINSCW__) +# define _PACKED +# define _ALIGNED_ANY +# define _ALIGNED_ON(bytes) +#endif + +#if defined(__cplusplus) +} +#endif + +#endif /* LIBS_COMPILER_H_ */ diff --git a/src/libs/declib.h b/src/libs/declib.h new file mode 100644 index 0000000..80d767c --- /dev/null +++ b/src/libs/declib.h @@ -0,0 +1,57 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_DECLIB_H_ +#define LIBS_DECLIB_H_ + +#include "libs/compiler.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +typedef struct _LZHCODE_DESC* DECODE_REF; + +enum +{ + FILE_STREAM = 0, + MEMORY_STREAM +}; +typedef BYTE STREAM_TYPE; + +enum +{ + STREAM_READ = 0, + STREAM_WRITE +}; +typedef BYTE STREAM_MODE; + +extern DECODE_REF copen (void *InStream, STREAM_TYPE SType, + STREAM_MODE SMode); +extern DWORD cclose (DECODE_REF DecodeRef); +extern void cfilelength (DECODE_REF DecodeRef, DWORD *pfilelen); +extern COUNT cread (void *pStr, COUNT size, COUNT count, + DECODE_REF DecodeRef); +extern COUNT cwrite (const void *pStr, COUNT size, COUNT count, + DECODE_REF DecodeRef); + +#if defined(__cplusplus) +} +#endif + +#endif /* LIBS_DECLIB_H_ */ diff --git a/src/libs/decomp/Makeinfo b/src/libs/decomp/Makeinfo new file mode 100644 index 0000000..699a24e --- /dev/null +++ b/src/libs/decomp/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="lzdecode.c lzencode.c update.c" +uqm_HFILES="lzh.h" diff --git a/src/libs/decomp/lzdecode.c b/src/libs/decomp/lzdecode.c new file mode 100644 index 0000000..3b64a90 --- /dev/null +++ b/src/libs/decomp/lzdecode.c @@ -0,0 +1,415 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + /* + * LZHUF.C English version 1.0 + * Based on Japanese version 29-NOV-1988 + * LZSS coded by Haruhiko OKUMURA + * Adaptive Huffman Coding coded by Haruyasu YOSHIZAKI + * Edited and translated to English by Kenji RIKITAKE + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include "lzh.h" +#include "libs/reslib.h" + +PLZHCODE_DESC _lpCurCodeDesc; +STREAM_TYPE _StreamType; +BYTE* _Stream; +UWORD _workbuf; +BYTE _workbuflen; + +/* get one bit */ +static SWORD +GetBit (void) +{ + SWORD i; + + while (_workbuflen <= 8) + { + if ((i = InChar ()) < 0) + i = 0; + _workbuf |= i << (8 - _workbuflen); + _workbuflen += 8; + } + i = (_workbuf & 0xFFFF) >> (16 - 1); + _workbuf = (_workbuf << 1) & 0xFFFF; + _workbuflen--; + + return (i); +} + +static UWORD +GetBits (BYTE num_bits) +{ + SWORD i; + + while (_workbuflen <= 8) + { + if ((i = InChar ()) < 0) + i = 0; + _workbuf |= i << (8 - _workbuflen); + _workbuflen += 8; + } + i = (_workbuf & 0xFFFF) >> (16 - num_bits); + _workbuf = (_workbuf << num_bits) & 0xFFFF; + _workbuflen -= num_bits; + + return (i); +} + +/* initialize freq tree */ + +void +StartHuff (void) +{ + COUNT i, j; + + for (i = 0; i < N_CHAR; i++) + { + _lpCurCodeDesc->freq[i] = 1; + _lpCurCodeDesc->son[i] = i + T; + _lpCurCodeDesc->prnt[i + T] = i; + } + i = 0; j = N_CHAR; + while (j <= R) + { + _lpCurCodeDesc->freq[j] = _lpCurCodeDesc->freq[i] + _lpCurCodeDesc->freq[i + 1]; + _lpCurCodeDesc->son[j] = i; + _lpCurCodeDesc->prnt[i] = _lpCurCodeDesc->prnt[i + 1] = j; + i += 2; j++; + } + _lpCurCodeDesc->freq[T] = 0xffff; + _lpCurCodeDesc->prnt[R] = 0; +} + +DECODE_REF +copen (void *InStream, STREAM_TYPE SType, STREAM_MODE SMode) +{ + DWORD StreamLength; + + _StreamType = SType; + _Stream = InStream; + if (SMode == STREAM_WRITE) /* writing */ + { + OutChar (0); /* skip future StreamLength */ + OutChar (0); + OutChar (0); + OutChar (0); + + StreamLength = 0; + } + else /* reading */ + { + BYTE lobyte, hibyte; + UWORD loword, hiword; + + lobyte = (BYTE)InChar (); + hibyte = (BYTE)InChar (); + loword = MAKE_WORD (lobyte, hibyte); + lobyte = (BYTE)InChar (); + hibyte = (BYTE)InChar (); + hiword = MAKE_WORD (lobyte, hibyte); + + StreamLength = MAKE_DWORD (loword, hiword); + } + + if (StreamLength == 0xFFFFFFFF + || (_lpCurCodeDesc = AllocCodeDesc ()) == NULL) + { + FreeCodeDesc (_lpCurCodeDesc); + _lpCurCodeDesc = NULL; + } + else + { + _lpCurCodeDesc->Stream = _Stream; + _lpCurCodeDesc->StreamType = _StreamType; + _lpCurCodeDesc->StreamMode = SMode; + _lpCurCodeDesc->StreamLength = StreamLength; + _lpCurCodeDesc->buf_index = N - F; + memset (&_lpCurCodeDesc->text_buf[0], ' ', N - F); + + StartHuff (); + } + + return ((DECODE_REF)_lpCurCodeDesc); +} + +DWORD +cclose (PLZHCODE_DESC lpCodeDesc) +{ + _lpCurCodeDesc = lpCodeDesc; + if (_lpCurCodeDesc) + { + DWORD StreamIndex; + + if (_lpCurCodeDesc->CleanupFunc) + (*_lpCurCodeDesc->CleanupFunc) (); + + StreamIndex = lpCodeDesc->StreamIndex; + FreeCodeDesc (lpCodeDesc); + _lpCurCodeDesc = NULL; + + return (StreamIndex); + } + + return (0); +} + +void +cfilelength (PLZHCODE_DESC lpCodeDesc, DWORD *pfilelen) +{ + if (lpCodeDesc == 0) + *pfilelen = 0; + else + *pfilelen = lpCodeDesc->StreamLength; +} + + /* decoder table */ +static const BYTE d_code[256] = +{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, + 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, + 0x0C, 0x0C, 0x0C, 0x0C, 0x0D, 0x0D, 0x0D, 0x0D, + 0x0E, 0x0E, 0x0E, 0x0E, 0x0F, 0x0F, 0x0F, 0x0F, + 0x10, 0x10, 0x10, 0x10, 0x11, 0x11, 0x11, 0x11, + 0x12, 0x12, 0x12, 0x12, 0x13, 0x13, 0x13, 0x13, + 0x14, 0x14, 0x14, 0x14, 0x15, 0x15, 0x15, 0x15, + 0x16, 0x16, 0x16, 0x16, 0x17, 0x17, 0x17, 0x17, + 0x18, 0x18, 0x19, 0x19, 0x1A, 0x1A, 0x1B, 0x1B, + 0x1C, 0x1C, 0x1D, 0x1D, 0x1E, 0x1E, 0x1F, 0x1F, + 0x20, 0x20, 0x21, 0x21, 0x22, 0x22, 0x23, 0x23, + 0x24, 0x24, 0x25, 0x25, 0x26, 0x26, 0x27, 0x27, + 0x28, 0x28, 0x29, 0x29, 0x2A, 0x2A, 0x2B, 0x2B, + 0x2C, 0x2C, 0x2D, 0x2D, 0x2E, 0x2E, 0x2F, 0x2F, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, +}; +static const BYTE d_len[256] = +{ + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, +}; + + /* decode upper 6 bits from given table */ +#define DecodePosition(p) \ +{ \ + while (_workbuflen <= 8) \ + { \ + *(p) = InChar (); \ + _workbuf |= *(p) << (8 - _workbuflen); \ + _workbuflen += 8; \ + } \ + *(p) = HIBYTE (_workbuf); \ + _workbuf = (_workbuf << 8) & 0xFFFF; \ + _workbuflen -= 8; \ + \ + /* input lower 6 bits directly */ \ + j = d_len[*(p)]; \ + *(p) = ((UWORD)d_code[*(p)] << 6) \ + | (((*(p) << j) | GetBits (j)) & 0x3f); \ +} + + /* start searching tree from the root to leaves. + * choose node #(son[]) if input bit == 0 + * else choose #(son[]+1) (input bit == 1) + */ +#define DecodeChar(c) \ +{ \ + for (*(c) = lpCodeDesc->son[R]; \ + *(c) < T; \ + *(c) = lpCodeDesc->son[*(c) + GetBit ()]) \ + ; \ + _update (*(c)); \ + *(c) -= T; \ +} + +COUNT +cread (void *buf, COUNT size, COUNT count, PLZHCODE_DESC lpCodeDesc) +{ + COUNT r, j, i; + BYTE *lpStr; + + if ((_lpCurCodeDesc = lpCodeDesc) == 0) + return (0); + + size *= count; + if (lpCodeDesc->StreamIndex + size > lpCodeDesc->StreamLength) + { + size /= count; + count = (COUNT)((lpCodeDesc->StreamLength + - lpCodeDesc->StreamIndex) / size); + + size *= count; + } + + if (size == 0) + return (0); + + lpStr = (BYTE*)buf; + _StreamType = lpCodeDesc->StreamType; + + _Stream = lpCodeDesc->Stream; + _workbuf = lpCodeDesc->workbuf; + _workbuflen = lpCodeDesc->workbuflen; + + lpCodeDesc->StreamIndex += size; + r = lpCodeDesc->buf_index; + j = lpCodeDesc->bytes_left; + if (j) + { + lpCodeDesc->bytes_left = 0; + i = lpCodeDesc->restart_index; + + goto ReenterRun; + } + + do + { + COUNT c; + + DecodeChar (&c); + + if (c < 256) + { + size--; + + *lpStr++ = lpCodeDesc->text_buf[r++ & (N - 1)] = (BYTE)c; + } + else + { + COUNT copy_size; + + //i is a COUNT; + DecodePosition(&i); + i = r - i - 1; + j = c - 255 + THRESHOLD; +ReenterRun: + if (j > size) + { + lpCodeDesc->bytes_left = j - size; + lpCodeDesc->restart_index = i + size; + j = size; + } + + size -= j; + do + { + COUNT loc_size; + + i &= (N - 1); + r &= (N - 1); + if ((i < r && i + j > r) || (i > r && i + j > r + N)) + copy_size = (r - i) & (N - 1); + else if ((copy_size = j) > N) + copy_size = N; + + loc_size = copy_size; + if (i + loc_size > N) + { + COUNT k; + + k = N - i; + memcpy (lpStr, &lpCodeDesc->text_buf[i], k); + lpStr += k; + loc_size -= k; + i = 0; + } + + memcpy (lpStr, &lpCodeDesc->text_buf[i], loc_size); + lpStr += loc_size; + i += loc_size; + + lpStr -= copy_size; + + loc_size = copy_size; + if (r + loc_size > N) + { + COUNT k; + + k = N - r; + memcpy (&lpCodeDesc->text_buf[r], lpStr, k); + lpStr += k; + loc_size -= k; + r = 0; + } + + memcpy (&lpCodeDesc->text_buf[r], lpStr, loc_size); + lpStr += loc_size; + r += loc_size; + } while (j -= copy_size); + } + } while (size); + + lpCodeDesc->buf_index = r; + lpCodeDesc->Stream = _Stream; + lpCodeDesc->workbuf = _workbuf; + lpCodeDesc->workbuflen = _workbuflen; + + return (count); +} + diff --git a/src/libs/decomp/lzencode.c b/src/libs/decomp/lzencode.c new file mode 100644 index 0000000..e26f344 --- /dev/null +++ b/src/libs/decomp/lzencode.c @@ -0,0 +1,468 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + /* + * LZHUF.C English version 1.0 + * Based on Japanese version 29-NOV-1988 + * LZSS coded by Haruhiko OKUMURA + * Adaptive Huffman Coding coded by Haruyasu YOSHIZAKI + * Edited and translated to English by Kenji RIKITAKE + */ + +#include <stdio.h> +#include "lzh.h" +#include "libs/reslib.h" + +static UWORD match_position, match_length; +static SWORD *lson; +static SWORD *rson; +static SWORD *dad; +static SWORD *encode_arrays; + +#define AllocEncodeArrays() \ + HCalloc ( \ + (((N + 1) + (N + 257) + (N + 1)) \ + * sizeof (lson[0]))) +#define FreeCodeArrays HFree + +static BOOLEAN +InitTree (void) +{ + if ((encode_arrays = AllocEncodeArrays ()) == NULL) + { + FreeCodeArrays (encode_arrays); + encode_arrays = NULL; + return (FALSE); + } + else + { + SWORD i; + + lson = encode_arrays; + rson = lson + (N + 1); + dad = rson + (N + 257); + + for (i = N + 1; i <= N + 256; i++) + rson[i] = NIL; /* root */ + for (i = 0; i < N; i++) + dad[i] = NIL; /* node */ + + return (TRUE); + } +} + +static void +InsertNode (SWORD r) +{ + SWORD p, cmp; + BYTE *lpBuf; + + cmp = 1; + lpBuf = _lpCurCodeDesc->text_buf; + p = N + 1 + lpBuf[r]; + rson[r] = lson[r] = NIL; + match_length = 0; + for (;;) + { + UWORD i; + + if (cmp >= 0) + { + if (rson[p] != NIL) + p = rson[p]; + else + { + rson[p] = r; + dad[r] = p; + return; + } + } + else + { + if (lson[p] != NIL) + p = lson[p]; + else + { + lson[p] = r; + dad[r] = p; + return; + } + } + + i = F; + { + SWORD _r, _p; + + _r = r; + _p = p; + while (--i && (cmp = lpBuf[++_r] - lpBuf[++_p]) == 0) + ; + } + if ((i = F - i) > THRESHOLD) + { + if (i > match_length) + { + match_position = ((r - p) & (N - 1)) - 1; + if ((match_length = i) >= F) + break; + } + else if (i == match_length) + { + if ((i = ((r - p) & (N - 1)) - 1) < match_position) + { + match_position = i; + } + } + } + } + dad[r] = dad[p]; + lson[r] = lson[p]; + rson[r] = rson[p]; + dad[lson[p]] = r; + dad[rson[p]] = r; + if (rson[dad[p]] == p) + rson[dad[p]] = r; + else + lson[dad[p]] = r; + dad[p] = NIL; /* remove p */ +} + +static void +DeleteNode (SWORD p) +{ + SWORD q; + + if (dad[p] == NIL) + return; /* unregistered */ + if (rson[p] == NIL) + q = lson[p]; + else if (lson[p] == NIL) + q = rson[p]; + else + { + q = lson[p]; + if (rson[q] != NIL) + { + do + { + q = rson[q]; + } while (rson[q] != NIL); + rson[dad[q]] = lson[q]; + dad[lson[q]] = dad[q]; + lson[q] = lson[p]; + dad[lson[p]] = q; + } + rson[q] = rson[p]; + dad[rson[p]] = q; + } + dad[q] = dad[p]; + if (rson[dad[p]] == p) + rson[dad[p]] = q; + else + lson[dad[p]] = q; + dad[p] = NIL; +} + +static void +Putcode (SWORD l, UWORD c) +{ + _workbuf |= c >> _workbuflen; + if ((_workbuflen += l) >= 8) + { + OutChar ((BYTE)(_workbuf >> 8)); + ++_lpCurCodeDesc->StreamIndex; + if ((_workbuflen -= 8) >= 8) + { + OutChar ((BYTE)(_workbuf)); + ++_lpCurCodeDesc->StreamIndex; + _workbuflen -= 8; + _workbuf = c << (l - _workbuflen); + } + else + { + _workbuf <<= 8; + } + _workbuf &= 0xFFFF; + } +} + +static void +EncodeChar (UWORD c) +{ + UWORD i; + SWORD j, k; + + i = 0; + j = 0; + k = _lpCurCodeDesc->prnt[c + T]; + + /* search connections from leaf node to the root */ + do + { + i >>= 1; + + /* + if node's address is odd, output 1 + else output 0 + */ + if (k & 1) + i += 0x8000; + + j++; + } while ((k = _lpCurCodeDesc->prnt[k]) != R); + Putcode (j, i); + _update (c + T); +} + +static void +EncodePosition (UWORD c) +{ + UWORD i; + /* + * Tables for encoding/decoding upper 6 bits of + * sliding dictionary pointer + */ + /* encoder table */ + static const BYTE p_len[64] = + { + 0x03, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08 + }; + + static const BYTE p_code[64] = + { + 0x00, 0x20, 0x30, 0x40, 0x50, 0x58, 0x60, 0x68, + 0x70, 0x78, 0x80, 0x88, 0x90, 0x94, 0x98, 0x9C, + 0xA0, 0xA4, 0xA8, 0xAC, 0xB0, 0xB4, 0xB8, 0xBC, + 0xC0, 0xC2, 0xC4, 0xC6, 0xC8, 0xCA, 0xCC, 0xCE, + 0xD0, 0xD2, 0xD4, 0xD6, 0xD8, 0xDA, 0xDC, 0xDE, + 0xE0, 0xE2, 0xE4, 0xE6, 0xE8, 0xEA, 0xEC, 0xEE, + 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, + 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF + }; + + /* output upper 6 bits with encoding */ + i = c >> 6; + Putcode (p_len[i], (UWORD)p_code[i] << 8); + + /* output lower 6 bits directly */ + Putcode (6, (c & 0x3f) << 10); +} + +static void +UninitTree (void) +{ + if (_workbuflen) + { + OutChar ((BYTE)(_workbuf >> 8)); + ++_lpCurCodeDesc->StreamIndex; + } + + FreeCodeArrays (encode_arrays); + encode_arrays = NULL; + lson = NULL; + rson = NULL; + dad = NULL; +} + +static void +_encode_cleanup (void) +{ + UWORD r, s, last_match_length, len; + + _StreamType = _lpCurCodeDesc->StreamType; + _Stream = _lpCurCodeDesc->Stream; + _workbuf = _lpCurCodeDesc->workbuf; + _workbuflen = _lpCurCodeDesc->workbuflen; + + r = _lpCurCodeDesc->buf_index; + s = _lpCurCodeDesc->restart_index; + last_match_length = _lpCurCodeDesc->bytes_left; + if (_lpCurCodeDesc->StreamLength >= F) + len = F; + else + { + UWORD i; + + for (i = 1; i <= F; i++) + InsertNode (r - i); + InsertNode (r); + + len = (UWORD)_lpCurCodeDesc->StreamLength; + } + + while (1) + { + while (last_match_length--) + { + DeleteNode (s); + if (--len == 0) + { + BYTE lobyte, hibyte; + UWORD loword, hiword; + + UninitTree (); + + _lpCurCodeDesc->StreamIndex += 4; + /* rewind */ + if (_lpCurCodeDesc->StreamType == FILE_STREAM) + SeekResFile ((uio_Stream *)_Stream, + -(int)_lpCurCodeDesc->StreamIndex, SEEK_CUR); + else /* _lpCurCodeDesc->StreamType == MEMORY_STREAM */ + _Stream = (BYTE*)_Stream - _lpCurCodeDesc->StreamIndex; + + loword = LOWORD (_lpCurCodeDesc->StreamLength); + lobyte = LOBYTE (loword); + hibyte = HIBYTE (loword); + OutChar (lobyte); + OutChar (hibyte); + hiword = HIWORD (_lpCurCodeDesc->StreamLength); + lobyte = LOBYTE (hiword); + hibyte = HIBYTE (hiword); + OutChar (lobyte); + OutChar (hibyte); + + return; + } + s = (s + 1) & (N - 1); + r = (r + 1) & (N - 1); + InsertNode (r); + } + if (match_length > len) + match_length = len; + if (match_length <= THRESHOLD) + { + match_length = 1; + EncodeChar (_lpCurCodeDesc->text_buf[r]); + } + else + { + EncodeChar (255 - THRESHOLD + match_length); + EncodePosition (match_position); + } + last_match_length = match_length; + } +} + +COUNT +cwrite (const void *buf, COUNT size, COUNT count, PLZHCODE_DESC lpCodeDesc) +{ + UWORD r, s, last_match_length; + BYTE *lpBuf; + const BYTE *lpStr; + + if ((_lpCurCodeDesc = lpCodeDesc) == 0 + || (size *= count) == 0) + return (0); + + _StreamType = lpCodeDesc->StreamType; + _Stream = lpCodeDesc->Stream; + _workbuf = lpCodeDesc->workbuf; + _workbuflen = lpCodeDesc->workbuflen; + lpStr = (const BYTE *) buf; + lpBuf = lpCodeDesc->text_buf; + + r = lpCodeDesc->buf_index; + s = lpCodeDesc->restart_index; + last_match_length = lpCodeDesc->bytes_left; + if (last_match_length) + { + lpCodeDesc->StreamLength += size; + goto EncodeRestart; + } + else if (lpCodeDesc->StreamLength < F) + { + UWORD i; + + if ((i = (UWORD)lpCodeDesc->StreamLength) == 0) + { + if (!InitTree ()) + return (0); + + _lpCurCodeDesc->StreamIndex = 0; + lpCodeDesc->CleanupFunc = _encode_cleanup; + } + + lpCodeDesc->StreamLength += size; + + for (; i < F && size; ++i, --size) + lpBuf[r + i] = *lpStr++; + if (i < F) + goto EncodeExit; + + for (i = 1; i <= F; i++) + InsertNode (r - i); + InsertNode (r); + if (size == 0) + goto EncodeExit; + } + else + lpCodeDesc->StreamLength += size; + + do + { + if (match_length > F) + match_length = F; + if (match_length <= THRESHOLD) + { + match_length = 1; + EncodeChar (lpBuf[r]); + } + else + { + EncodeChar (255 - THRESHOLD + match_length); + EncodePosition (match_position); + } + last_match_length = match_length; +EncodeRestart: + while (last_match_length && size) + { + BYTE c; + + --size; + --last_match_length; + + DeleteNode (s); + c = *lpStr++; + lpBuf[s] = c; + if (s < F - 1) + lpBuf[s + N] = c; + s = (s + 1) & (N - 1); + r = (r + 1) & (N - 1); + InsertNode (r); + } + } while (last_match_length == 0); + +EncodeExit: + lpCodeDesc->buf_index = r; + lpCodeDesc->restart_index = s; + lpCodeDesc->bytes_left = last_match_length; + + lpCodeDesc->Stream = _Stream; + lpCodeDesc->workbuf = _workbuf; + lpCodeDesc->workbuflen = _workbuflen; + + return (count); +} + diff --git a/src/libs/decomp/lzh.h b/src/libs/decomp/lzh.h new file mode 100644 index 0000000..8ebdef4 --- /dev/null +++ b/src/libs/decomp/lzh.h @@ -0,0 +1,91 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_DECOMP_LZH_H_ +#define LIBS_DECOMP_LZH_H_ + +#include "libs/declib.h" +#include "libs/memlib.h" + +/* LZSS Parameters */ + +#define N 4096 /* Size of string buffer */ +#define F 16 /* Size of look-ahead buffer */ +//#define F 60 /* Size of look-ahead buffer */ +#define THRESHOLD 2 +#define NIL N /* End of tree's node */ + +/* Huffman coding parameters */ + +#define N_CHAR (256 - THRESHOLD + F) + /* character code (= 0..N_CHAR-1) */ +#define T (N_CHAR * 2 - 1) /* Size of table */ +#define R (T - 1) /* root position */ +#define MAX_FREQ 0x8000 + /* update when cumulative frequency */ + +struct _LZHCODE_DESC +{ + COUNT buf_index, restart_index, bytes_left; + BYTE text_buf[N + F - 1]; + /* reconstruct freq tree */ + COUNT freq[T + 1]; /* cumulative freq table */ + /* + * pointing parent nodes. + * area [T..(T + N_CHAR - 1)] are pointers for leaves + */ + COUNT prnt[T + N_CHAR]; + /* pointing children nodes (son[], son[] + 1)*/ + COUNT son[T]; + UWORD workbuf; + BYTE workbuflen; + + STREAM_TYPE StreamType; + + void *Stream; + DWORD StreamIndex, StreamLength; + + STREAM_MODE StreamMode; + PVOIDFUNC CleanupFunc; +}; + +typedef struct _LZHCODE_DESC LZHCODE_DESC; +typedef LZHCODE_DESC *PLZHCODE_DESC; + +#define InChar() (_StreamType == FILE_STREAM ? \ + GetResFileChar ((uio_Stream *)_Stream) : \ + (int)*_Stream++) +#define OutChar(c) (_StreamType == FILE_STREAM ? \ + PutResFileChar ((c), (uio_Stream *)_Stream) : \ + (*_Stream++ = (BYTE)(c))) + + +#define AllocCodeDesc() HCalloc (sizeof (LZHCODE_DESC)) +#define FreeCodeDesc HFree + +extern void _update (COUNT c); +extern void StartHuff (void); + +extern PLZHCODE_DESC _lpCurCodeDesc; +extern STREAM_TYPE _StreamType; +extern BYTE* _Stream; +extern UWORD _workbuf; +extern BYTE _workbuflen; + +#endif /* LIBS_DECOMP_LZH_H_ */ + diff --git a/src/libs/decomp/update.c b/src/libs/decomp/update.c new file mode 100644 index 0000000..13de988 --- /dev/null +++ b/src/libs/decomp/update.c @@ -0,0 +1,115 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <string.h> +#include "lzh.h" + +static void +reconst (void) +{ + COUNT i, j; + + /* halven cumulative freq for leaf nodes */ + j = 0; + for (i = 0; i < T; i++) + { + if (_lpCurCodeDesc->son[i] >= T) + { + _lpCurCodeDesc->freq[j] = (_lpCurCodeDesc->freq[i] + 1) >> 1; + _lpCurCodeDesc->son[j] = _lpCurCodeDesc->son[i]; + j++; + } + } + /* make a tree : first, connect children nodes */ + for (i = 0, j = N_CHAR; j < T; i += 2, j++) + { + SWORD k; + UWORD f, l; + + k = i + 1; + f = _lpCurCodeDesc->freq[j] = _lpCurCodeDesc->freq[i] + _lpCurCodeDesc->freq[k]; + for (k = j - 1; f < _lpCurCodeDesc->freq[k]; k--) + ; + k++; + l = (j - k); + + memmove (_lpCurCodeDesc->freq + k + 1, _lpCurCodeDesc->freq + k, + sizeof(_lpCurCodeDesc->freq[0]) * l); + _lpCurCodeDesc->freq[k] = f; + memmove (_lpCurCodeDesc->son + k + 1, _lpCurCodeDesc->son + k, + sizeof(_lpCurCodeDesc->son[0]) * l); + _lpCurCodeDesc->son[k] = i; + } + /* connect parent nodes */ + for (i = 0; i < T; i++) + { + if ((j = _lpCurCodeDesc->son[i]) >= T) + _lpCurCodeDesc->prnt[j] = i; + else + _lpCurCodeDesc->prnt[j] = _lpCurCodeDesc->prnt[j + 1] = i; + } +} + + +/* update freq tree */ + +void +_update (COUNT c) +{ + PLZHCODE_DESC lpCD; + + if ((lpCD = _lpCurCodeDesc)->freq[R] == MAX_FREQ) + reconst (); + + c = lpCD->prnt[c]; + do + { + COUNT i, l; + + i = ++lpCD->freq[c]; + + /* swap nodes to keep the tree freq-ordered */ + if (i > lpCD->freq[l = c + 1]) + { + COUNT j; + + while (i > lpCD->freq[++l]) + ; + l--; + lpCD->freq[c] = lpCD->freq[l]; + lpCD->freq[l] = i; + + i = lpCD->son[c]; + j = lpCD->son[l]; + lpCD->son[l] = i; + lpCD->son[c] = j; + + lpCD->prnt[i] = l; + if (i < T) + lpCD->prnt[i + 1] = l; + + lpCD->prnt[j] = c; + if (j < T) + lpCD->prnt[j + 1] = c; + + c = l; + } + } while ((c = lpCD->prnt[c]) != 0); /* do it until reaching the root */ +} + + diff --git a/src/libs/file.h b/src/libs/file.h new file mode 100644 index 0000000..df8de5e --- /dev/null +++ b/src/libs/file.h @@ -0,0 +1,95 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +// Contains file handling code + +#ifndef LIBS_FILE_H_ +#define LIBS_FILE_H_ + +#include "port.h" +#include "libs/uio.h" + +// for bool +#include "types.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +#if 0 +// from temp.h +void initTempDir (void); +void unInitTempDir (void); +char *tempFilePath (const char *filename); +extern uio_DirHandle *tempDir; +#endif + + +// from dirs.h +int mkdirhier (const char *path); +const char *getHomeDir (void); +int createDirectory (const char *dir, int mode); + +int expandPath (char *dest, size_t len, const char *src, int what); +// values for 'what': +#define EP_HOME 1 + // Expand '~' for home dirs. +#define EP_ABSOLUTE 2 + // Make paths absolute +#define EP_ENVVARS 4 + // Expand environment variables. +#define EP_DOTS 8 + // Process ".." and "." +#define EP_SLASHES 16 + // Consider backslashes as path component separators. + // They will be replaced by slashes. Windows UNC paths will always + // start with "\\server\share", with backslashes. +#define EP_SINGLESEP 32 + // Replace multiple consecutive path separators by a single one. +#define EP_ALL (EP_HOME | EP_ENVVARS | EP_ABSOLUTE | EP_DOTS | EP_SLASHES \ + EP_SINGLESEP) + // Everything +// Everything except Windows style backslashes on Unix Systems: +#ifdef WIN32 +# define EP_ALL_SYSTEM (EP_HOME | EP_ENVVARS | EP_ABSOLUTE | EP_DOTS | \ + EP_SLASHES | EP_SINGLESEP) +#else +# define EP_ALL_SYSTEM (EP_HOME | EP_ENVVARS | EP_ABSOLUTE | EP_DOTS | \ + EP_SINGLESEP) +#endif + +// from files.h +int copyFile (uio_DirHandle *srcDir, const char *srcName, + uio_DirHandle *dstDir, const char *newName); +bool fileExists (const char *name); +bool fileExists2(uio_DirHandle *dir, const char *fileName); +#ifdef HAVE_UNC_PATHS +size_t skipUNCServerShare(const char *inPath); +#endif /* HAVE_UNC_PATHS */ + +#ifdef HAVE_DRIVE_LETTERS +static inline int isDriveLetter(int c) +{ + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); +} +#endif /* HAVE_DRIVE_LETTERS */ + +#if defined(__cplusplus) +} +#endif + +#endif /* LIBS_FILE_H_ */ + diff --git a/src/libs/file/Makeinfo b/src/libs/file/Makeinfo new file mode 100644 index 0000000..b4ea11d --- /dev/null +++ b/src/libs/file/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="dirs.c files.c" +uqm_HFILES="filintrn.h" diff --git a/src/libs/file/dirs.c b/src/libs/file/dirs.c new file mode 100644 index 0000000..39a44f5 --- /dev/null +++ b/src/libs/file/dirs.c @@ -0,0 +1,830 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +// Contains code handling directories + +#include <stdlib.h> +#include <sys/stat.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <assert.h> +#include "port.h" +#include "config.h" +#include "filintrn.h" +#include "libs/compiler.h" +#include "libs/memlib.h" +#include "libs/misc.h" +#include "libs/log.h" + +#ifdef HAVE_DRIVE_LETTERS +# include <ctype.h> + // For tolower() +#endif /* HAVE_DRIVE_LETTERS */ +#ifdef WIN32 +# include <direct.h> + // For _getdcwd() +#else +# include <pwd.h> + // For getpwuid() +#endif + +/* Try to find a suitable value for %APPDATA% if it isn't defined on + * Windows. + */ +#define APPDATA_FALLBACK + + +static char *expandPathAbsolute (char *dest, size_t destLen, const char *src, + size_t *skipSrc, int what); +static char *strrchr2(const char *start, int c, const char *end); + + +int +createDirectory(const char *dir, int mode) +{ + return MKDIR(dir, mode); +} + +// make all components of the path if they don't exist already +// returns 0 on success, -1 on failure. +// on failure, some parts may still have been created. +int +mkdirhier (const char *path) +{ + char *buf; // buffer + char *ptr; // end of the string in buf + const char *pathstart; // start of a component of path + const char *pathend; // first char past the end of a component of path + size_t len; + struct stat statbuf; + + len = strlen (path); + buf = HMalloc (len + 2); // one extra for possibly added '/' + + ptr = buf; + pathstart = path; + +#ifdef HAVE_DRIVE_LETTERS + if (isDriveLetter(pathstart[0]) && pathstart[1] == ':') + { + // Driveletter + semicolon on Windows. + // Copy as is; don't try to create directories for it. + *(ptr++) = *(pathstart++); + *(ptr++) = *(pathstart++); + + ptr[0] = '/'; + ptr[1] = '\0'; + if (stat (buf, &statbuf) == -1) + { + log_add (log_Error, "Can't stat \"%s\": %s", buf, strerror (errno)); + goto err; + } + } + else +#endif /* HAVE_DRIVE_LETTERS */ +#ifdef HAVE_UNC_PATHS + if (pathstart[0] == '\\' && pathstart[1] == '\\') + { + // Universal Naming Convention path. (\\server\share\...) + // Copy the server part as is; don't try to create directories for + // it, or stat it. Don't create a dir for the share either. + *(ptr++) = *(pathstart++); + *(ptr++) = *(pathstart++); + + // Copy the server part + while (*pathstart != '\0' && *pathstart != '\\' && *pathstart != '/') + *(ptr++) = *(pathstart++); + + if (*pathstart == '\0') + { + log_add (log_Error, "Incomplete UNC path \"%s\"", pathstart); + goto err; + } + + // Copy the path seperator. + *(ptr++) = *(pathstart++); + + // Copy the share part + while (*pathstart != '\0' && *pathstart != '\\' && *pathstart != '/') + *(ptr++) = *(pathstart++); + + ptr[0] = '/'; + ptr[1] = '\0'; + if (stat (buf, &statbuf) == -1) + { + log_add (log_Error, "Can't stat \"%s\": %s", buf, strerror (errno)); + goto err; + } + } +#else + { + // Making sure that there is an 'else' case if HAVE_DRIVE_LETTERS is + // defined. + } +#endif /* HAVE_UNC_PATHS */ + + if (*pathstart == '/') + *(ptr++) = *(pathstart++); + + if (*pathstart == '\0') { + // path exists completely, nothing more to do + goto success; + } + + // walk through the path as long as the components exist + while (1) + { + pathend = strchr (pathstart, '/'); + if (pathend == NULL) + pathend = path + len; + memcpy(ptr, pathstart, pathend - pathstart); + ptr += pathend - pathstart; + *ptr = '\0'; + + if (stat (buf, &statbuf) == -1) + { + if (errno == ENOENT) + break; +#ifdef __SYMBIAN32__ + // XXX: HACK: If we don't have access to a directory, we can + // still have access to the underlying entries. We don't + // actually know whether the entry is a directory, but I know of + // no way to find out. We just pretend that it is; if we were + // wrong, an error will occur when we try to do something with + // the directory. That /should/ not be a problem, as any such + // action should have its own error checking. + if (errno != EACCES) +#endif + { + log_add (log_Error, "Can't stat \"%s\": %s", buf, + strerror (errno)); + goto err; + } + } + + if (*pathend == '\0') + goto success; + + *ptr = '/'; + ptr++; + pathstart = pathend + 1; + while (*pathstart == '/') + pathstart++; + // pathstart is the next non-slash character + + if (*pathstart == '\0') + goto success; + } + + // create all components left + while (1) + { + if (createDirectory (buf, 0777) == -1) + { + log_add (log_Error, "Error: Can't create %s: %s", buf, + strerror (errno)); + goto err; + } + + if (*pathend == '\0') + break; + + *ptr = '/'; + ptr++; + pathstart = pathend + 1; + while (*pathstart == '/') + pathstart++; + // pathstart is the next non-slash character + + if (*pathstart == '\0') + break; + + pathend = strchr (pathstart, '/'); + if (pathend == NULL) + pathend = path + len; + + memcpy (ptr, pathstart, pathend - pathstart); + ptr += pathend - pathstart; + *ptr = '\0'; + } + +success: + HFree (buf); + return 0; + +err: + { + int savedErrno = errno; + HFree (buf); + errno = savedErrno; + } + return -1; +} + +// Get the user's home dir +// returns a pointer to a static buffer from either getenv() or getpwuid(). +const char * +getHomeDir (void) +{ +#ifdef WIN32 + return getenv ("HOME"); +#else + const char *home; + struct passwd *pw; + + home = getenv ("HOME"); + if (home != NULL) + return home; + + pw = getpwuid (getuid ()); + if (pw == NULL) + return NULL; + // NB: pw points to a static buffer. + + return pw->pw_dir; +#endif +} + +// Performs various types of string expansions on a path. +// 'what' is an OR'd compination of the folowing flags, which +// specify what type of exmansions will be performed. +// EP_HOME - Expand '~' for home dirs. +// EP_ABSOLUTE - Make relative paths absolute +// EP_ENVVARS - Expand environment variables +// EP_DOTS - Process ".." and "." +// EP_SLASHES - Consider backslashes as path component separators. +// They will be replaced by slashes. +// EP_SINGLESEP - Replace multiple consecutive path seperators (which POSIX +// considers equivalent to a single one) by a single one. +// Additionally, there's EP_ALL, which indicates all of the above, +// and EP_ALL_SYSTEM, which does the same as EP_ALL, with the exception +// of EP_SLASHES, which will only be included if the operating system +// accepts backslashes as path terminators. +// Returns 0 on success. +// Returns -1 on failure, setting errno. +int +expandPath (char *dest, size_t len, const char *src, int what) +{ + char *destptr, *destend; + char *buf = NULL; + char *bufptr, *bufend; + const char *srcend; + +#define CHECKLEN(bufname, n) \ + if (bufname##ptr + (n) >= bufname##end) \ + { \ + errno = ENAMETOOLONG; \ + goto err; \ + } \ + else \ + (void) 0 + + destptr = dest; + destend = dest + len; + + if (what & EP_ENVVARS) + { + buf = HMalloc (len); + bufptr = buf; + bufend = buf + len; + while (*src != '\0') + { + switch (*src) + { +#ifdef WIN32 + case '%': + { + /* Environment variable substitution in Windows */ + const char *end; // end of env var name in src + const char *envVar; + char *envName; + size_t envNameLen, envVarLen; + + src++; + end = strchr (src, '%'); + if (end == NULL) + { + errno = EINVAL; + goto err; + } + + envNameLen = end - src; + envName = HMalloc (envNameLen + 1); + memcpy (envName, src, envNameLen + 1); + envName[envNameLen] = '\0'; + envVar = getenv (envName); + HFree (envName); + + if (envVar == NULL) + { +#ifdef APPDATA_FALLBACK + if (strncmp (src, "APPDATA", envNameLen) != 0) + { + // Substitute an empty string + src = end + 1; + break; + } + + // fallback for when the APPDATA env var is not set + // Using SHGetFolderPath or SHGetSpecialFolderPath + // is problematic (not everywhere available). + log_add (log_Warning, "Warning: %%APPDATA%% is not set. " + "Falling back to \"%%USERPROFILE%%\\Application " + "Data\""); + envVar = getenv ("USERPROFILE"); + if (envVar != NULL) + { +#define APPDATA_STRING "\\Application Data" + envVarLen = strlen (envVar); + CHECKLEN (buf, + envVarLen + sizeof (APPDATA_STRING) - 1); + strcpy (bufptr, envVar); + bufptr += envVarLen; + strcpy (bufptr, APPDATA_STRING); + bufptr += sizeof (APPDATA_STRING) - 1; + src = end + 1; + break; + } + + // fallback to "./userdata" +#define APPDATA_FALLBACK_STRING ".\\userdata" + log_add (log_Warning, + "Warning: %%USERPROFILE%% is not set. " + "Falling back to \"%s\" for %%APPDATA%%", + APPDATA_FALLBACK_STRING); + CHECKLEN (buf, sizeof (APPDATA_FALLBACK_STRING) - 1); + strcpy (bufptr, APPDATA_FALLBACK_STRING); + bufptr += sizeof (APPDATA_FALLBACK_STRING) - 1; + src = end + 1; + break; + +#else /* !defined (APPDATA_FALLBACK) */ + // Substitute an empty string + src = end + 1; + break; +#endif /* APPDATA_FALLBACK */ + } + + envVarLen = strlen (envVar); + CHECKLEN (buf, envVarLen); + strcpy (bufptr, envVar); + bufptr += envVarLen; + src = end + 1; + break; + } +#endif +#ifndef WIN32 + case '$': + { + const char *end; + char *envName; + size_t envNameLen; + const char *envVar; + size_t envVarLen; + + src++; + if (*src == '{') + { + src++; + end = strchr(src, '}'); + if (end == NULL) + { + errno = EINVAL; + goto err; + } + envNameLen = end - src; + end++; // Skip the '}' + } + else + { + end = src; + while ((*end >= 'A' && *end <= 'Z') || + (*end >= 'a' && *end <= 'z') || + (*end >= '0' && *end <= '9') || + *end == '_') + end++; + envNameLen = end - src; + } + + envName = HMalloc (envNameLen + 1); + memcpy (envName, src, envNameLen + 1); + envName[envNameLen] = '\0'; + envVar = getenv (envName); + HFree (envName); + + if (envVar != NULL) + { + envVarLen = strlen (envVar); + CHECKLEN (buf, envVarLen); + memcpy (bufptr, envVar, envVarLen); + bufptr += envVarLen; + } + + src = end; + break; + } +#endif + default: + CHECKLEN(buf, 1); + *(bufptr++) = *(src++); + break; + } // switch + } // while + *bufptr = '\0'; + src = buf; + srcend = bufptr; + } // if (what & EP_ENVVARS) + else + srcend = src + strlen (src); + + if (what & EP_HOME) + { + if (src[0] == '~') + { + const char *home; + size_t homelen; + + if (src[1] != '/') + { + errno = EINVAL; + goto err; + } + + home = getHomeDir (); + if (home == NULL) + { + errno = ENOENT; + goto err; + } + homelen = strlen (home); + + if (what & EP_ABSOLUTE) { + size_t skip; + destptr = expandPathAbsolute (dest, destend - dest, + home, &skip, what); + if (destptr == NULL) + { + // errno is set + goto err; + } + home += skip; + what &= ~EP_ABSOLUTE; + // The part after the '~' should not be seen + // as absolute. + } + + CHECKLEN (dest, homelen); + memcpy (destptr, home, homelen); + destptr += homelen; + src++; /* skip the ~ */ + } + } + + if (what & EP_ABSOLUTE) + { + size_t skip; + destptr = expandPathAbsolute (destptr, destend - destptr, src, + &skip, what); + if (destptr == NULL) + { + // errno is set + goto err; + } + src += skip; + } + + CHECKLEN (dest, srcend - src); + memcpy (destptr, src, srcend - src + 1); + // The +1 is for the '\0'. It is already taken into account by + // CHECKLEN. + + if (what & EP_SLASHES) + { + /* Replacing backslashes in path by slashes. */ + destptr = dest; +#ifdef HAVE_UNC_PATHS + { + // A UNC path should always start with two backslashes + // and have a backslash in between the server and share part. + size_t skip = skipUNCServerShare (destptr); + if (skip != 0) + { + char *slash = (char *) memchr (destptr + 2, '/', skip - 2); + if (slash) + *slash = '\\'; + destptr += skip; + } + } +#endif /* HAVE_UNC_PATHS */ + while (*destptr != '\0') + { + if (*destptr == '\\') + *destptr = '/'; + destptr++; + } + } + + if (what & EP_DOTS) { + // At this point backslashes are already replaced by slashes if they + // are specified to be path seperators. + // Note that the path can only get smaller, so no size checks + // need to be done. + char *pathStart; + // Start of the first path component, after any + // leading slashes or drive letters. + char *startPart; + char *endPart; + + pathStart = dest; +#ifdef HAVE_DRIVE_LETTERS + if (isDriveLetter(pathStart[0]) && (pathStart[1] == ':')) + { + pathStart += 2; + } + else +#endif /* HAVE_DRIVE_LETTERS */ +#ifdef HAVE_UNC_PATHS + { + // Test for a Universal Naming Convention path. + pathStart += skipUNCServerShare(pathStart); + } +#else + { + // Making sure that there is an 'else' case if HAVE_DRIVE_LETTERS is + // defined. + } +#endif /* HAVE_UNC_PATHS */ + if (pathStart[0] == '/') + pathStart++; + + startPart = pathStart; + destptr = pathStart; + for (;;) + { + endPart = strchr(startPart, '/'); + if (endPart == NULL) + endPart = startPart + strlen(startPart); + + if (endPart - startPart == 1 && startPart[0] == '.') + { + // Found "." as path component. Ignore this component. + } + else if (endPart - startPart == 2 && + startPart[0] == '.' && startPart[1] == '.') + { + // Found ".." as path component. Remove the previous + // component, and ignore this one. + char *lastSlash; + lastSlash = strrchr2(pathStart, '/', destptr - 1); + if (lastSlash == NULL) + { + if (destptr == pathStart) + { + // We ran out of path components to back out of. + errno = EINVAL; + goto err; + } + destptr = pathStart; + } + else + { + destptr = lastSlash; + if (*endPart == '/') + destptr++; + } + } + else + { + // A normal path component; copy it. + // Using memmove as source and destination may overlap. + memmove(destptr, startPart, endPart - startPart); + destptr += (endPart - startPart); + if (*endPart == '/') + { + *destptr = '/'; + destptr++; + } + } + if (*endPart == '\0') + break; + startPart = endPart + 1; + } + *destptr = '\0'; + } + + if (what & EP_SINGLESEP) + { + char *srcptr; + srcptr = dest; + destptr = dest; + while (*srcptr != '\0') + { + char ch = *srcptr; + *(destptr++) = *(srcptr++); + if (ch == '/') + { + while (*srcptr == '/') + srcptr++; + } + } + *destptr = '\0'; + } + + HFree (buf); + return 0; + +err: + if (buf != NULL) { + int savedErrno = errno; + HFree (buf); + errno = savedErrno; + } + return -1; +} + +#if defined(HAVE_DRIVE_LETTERS) && defined(HAVE_CWD_PER_DRIVE) + // This code is only needed if we have a current working directory + // per drive. +// letter is 0 based: 0 = A, 1 = B, ... +static bool +driveLetterExists(int letter) +{ + unsigned long drives; + + drives = _getdrives (); + + return ((drives >> letter) & 1) != 0; +} +#endif /* if defined(HAVE_DRIVE_LETTERS) && defined(HAVE_CWD_PER_DRIVE) */ + +// helper for expandPath, expanding an absolute path +// returns a pointer to the end of the filled in part of dest. +static char * +expandPathAbsolute (char *dest, size_t destLen, const char *src, + size_t *skipSrc, int what) +{ + const char *orgSrc; + + if (src[0] == '/' || ((what & EP_SLASHES) && src[0] == '\\')) + { + // Path is already absolute; nothing to do + *skipSrc = 0; + return dest; + } + + orgSrc = src; +#ifdef HAVE_DRIVE_LETTERS + if (isDriveLetter(src[0]) && (src[1] == ':')) + { + int letter; + + if (src[2] == '/' || src[2] == '\\') + { + // Path is already absolute (of the form "d:/"); nothing to do + *skipSrc = 0; + return dest; + } + + // Path is of the form "d:path", without a (back)slash after the + // semicolon. + +#ifdef REJECT_DRIVE_PATH_WITHOUT_SLASH + // We reject paths of the form "d:foo/bar". + errno = EINVAL; + return NULL; +#elif defined(HAVE_CWD_PER_DRIVE) + // Paths of the form "d:foo/bar" are treated as "foo/bar" relative + // to the working directory of d:. + letter = tolower(src[0]) - 'a'; + + // _getdcwd() should only be called on drives that exist. + // This is weird though, because it means a race condition + // in between the existance check and the call to _getdcwd() + // cannot be avoided, unless a drive still exists for Windows + // when the physical drive is removed. + if (!driveLetterExists (letter)) + { + errno = ENOENT; + return NULL; + } + + // Get the working directory for a specific drive. + if (_getdcwd (letter + 1, dest, destLen) == NULL) + { + // errno is set + return NULL; + } + + src += 2; +#else /* if !defined(HAVE_CWD_PER_DRIVE) */ + // We treat paths of the form "d:foo/bar" as "d:/foo/bar". + if (destLen < 3) { + errno = ERANGE; + return NULL; + } + dest[0] = src[0]; + dest[1] = ':'; + dest[2] = '/'; + *skipSrc = 2; + dest += 3; + return dest; +#endif /* HAVE_CWD_PER_DRIVE */ + } + else +#endif /* HAVE_DRIVE_LETTERS */ + { + // Relative dir + if (getcwd (dest, destLen) == NULL) + { + // errno is set + return NULL; + } + } + + { + size_t tempLen; + tempLen = strlen (dest); + if (tempLen == 0) + { + // getcwd() or _getdcwd() returned a 0-length string. + errno = ENOENT; + return NULL; + } + dest += tempLen; + destLen -= tempLen; + } + if (dest[-1] != '/' +#ifdef BACKSLASH_IS_PATH_SEPARATOR + && dest[-1] != '\\' +#endif /* BACKSLASH_IS_PATH_SEPARATOR */ + ) + { + // Need to add a slash. + // There's always space, as we overwrite the '\0' that getcwd() + // always returns. + dest[0] = '/'; + dest++; + destLen--; + } + + *skipSrc = (size_t) (src - orgSrc); + return dest; +} + +// As strrchr, but starts searching from the indicated end of the string. +static char * +strrchr2(const char *start, int c, const char *end) { + for (;;) { + end--; + if (end < start) + return (char *) NULL; + if (*end == c) + return (char *) unconst(end); + } +} + +#ifdef HAVE_UNC_PATHS +// returns 0 if the path is not a valid UNC path. +// Does not skip trailing slashes. +size_t +skipUNCServerShare(const char *inPath) { + const char *path = inPath; + + // Skip the initial two backslashes. + if (path[0] != '\\' || path[1] != '\\') + return (size_t) 0; + path += 2; + + // Skip the server part. + while (*path != '\\' && *path != '/') { + if (*path == '\0') + return (size_t) 0; + path++; + } + + // Skip the seperator. + path++; + + // Skip the share part. + while (*path != '\0' && *path != '\\' && *path != '/') + path++; + + return (size_t) (path - inPath); +} +#endif /* HAVE_UNC_PATHS */ + + diff --git a/src/libs/file/files.c b/src/libs/file/files.c new file mode 100644 index 0000000..5ce7dac --- /dev/null +++ b/src/libs/file/files.c @@ -0,0 +1,165 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +// Contains code handling files + +#include <string.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include "port.h" +#include "libs/uio.h" +#include "config.h" +#include "types.h" +#include "filintrn.h" +#include "libs/memlib.h" +#include "libs/log.h" + +static int copyError(uio_Handle *srcHandle, uio_Handle *dstHandle, + uio_DirHandle *unlinkHandle, const char *unlinkPath, uint8 *buf); + +bool +fileExists (const char *name) +{ + return access (name, F_OK) == 0; +} + +bool +fileExists2(uio_DirHandle *dir, const char *fileName) +{ + uio_Stream *stream; + + stream = uio_fopen (dir, fileName, "rb"); + if (stream == NULL) + return 0; + + uio_fclose (stream); + return 1; +} + +/* + * Copy a file with path srcName to a file with name newName. + * If the destination already exists, the operation fails. + * Links are followed. + * Special files (fifos, char devices, block devices, etc) will be + * read as long as there is data available and the destination will be + * a regular file with that data. + * The new file will have the same permissions as the old. + * If an error occurs during copying, an attempt will be made to + * remove the copy. + */ +int +copyFile (uio_DirHandle *srcDir, const char *srcName, + uio_DirHandle *dstDir, const char *newName) +{ + uio_Handle *src, *dst; + struct stat sb; +#define BUFSIZE 65536 + uint8 *buf, *bufPtr; + ssize_t numInBuf, numWritten; + + src = uio_open (srcDir, srcName, O_RDONLY +#ifdef WIN32 + | O_BINARY +#endif + , 0); + if (src == NULL) + return -1; + + if (uio_fstat (src, &sb) == -1) + return copyError (src, NULL, NULL, NULL, NULL); + + dst = uio_open (dstDir, newName, O_WRONLY | O_CREAT | O_EXCL +#ifdef WIN32 + | O_BINARY +#endif + , sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)); + if (dst == NULL) + return copyError (src, NULL, NULL, NULL, NULL); + + buf = HMalloc(BUFSIZE); + // This was originally a statically allocated buffer, + // but as this function might be run from a thread with + // a small stack, this is better. + while (1) + { + numInBuf = uio_read (src, buf, BUFSIZE); + if (numInBuf == -1) + { + if (errno == EINTR) + continue; + return copyError (src, dst, dstDir, newName, buf); + } + if (numInBuf == 0) + break; + + bufPtr = buf; + do + { + numWritten = uio_write (dst, bufPtr, numInBuf); + if (numWritten == -1) + { + if (errno == EINTR) + continue; + return copyError (src, dst, dstDir, newName, buf); + } + numInBuf -= numWritten; + bufPtr += numWritten; + } while (numInBuf > 0); + } + + HFree (buf); + uio_close (src); + uio_close (dst); + errno = 0; + return 0; +} + +/* + * Closes srcHandle if it's not -1. + * Closes dstHandle if it's not -1. + * Removes unlinkpath from the unlinkHandle dir if it's not NULL. + * Frees 'buf' if not NULL. + * Always returns -1. + * errno is what was before the call. + */ +static int +copyError(uio_Handle *srcHandle, uio_Handle *dstHandle, + uio_DirHandle *unlinkHandle, const char *unlinkPath, uint8 *buf) +{ + int savedErrno; + + savedErrno = errno; + + log_add (log_Debug, "Error while copying: %s", strerror (errno)); + + if (srcHandle != NULL) + uio_close (srcHandle); + + if (dstHandle != NULL) + uio_close (dstHandle); + + if (unlinkPath != NULL) + uio_unlink (unlinkHandle, unlinkPath); + + if (buf != NULL) + HFree(buf); + + errno = savedErrno; + return -1; +} + diff --git a/src/libs/file/filintrn.h b/src/libs/file/filintrn.h new file mode 100644 index 0000000..2c6512a --- /dev/null +++ b/src/libs/file/filintrn.h @@ -0,0 +1,24 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +// Contains code handling temporary files and dirs + +#ifndef _FILEINTRN_H + +#include "../file.h" + +#endif /* _FILEINTRN_H */ + diff --git a/src/libs/file/temp.c b/src/libs/file/temp.c new file mode 100644 index 0000000..3253e0d --- /dev/null +++ b/src/libs/file/temp.c @@ -0,0 +1,199 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +// Contains code handling temporary files and dirs + +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#ifdef WIN32 +# include <io.h> +#endif +#include <string.h> +#include <time.h> +#include "filintrn.h" +#include "libs/timelib.h" +#include "port.h" +#include "libs/compiler.h" +#include "libs/log.h" +#include "libs/memlib.h" + +static char *tempDirName; +uio_DirHandle *tempDir; + +static void +removeTempDir (void) +{ + rmdir (tempDirName); +} + +// Try if the null-terminated path 'dir' to a directory is valid +// as temp path. +// On success, 'buf' will be filled with the path, with a trailing /, +// null-terminated, and 0 is returned. +// On failure, EINVAL, ENAMETOOLONG, or one of the errors access() can return +// is returned, and the contents of buf is unspecified. +static int +tryTempDir (char *buf, size_t buflen, const char *dir) +{ + size_t len; + int haveSlash; + + if (dir == NULL) + return EINVAL; + + if (dir[0] == '\0') + return EINVAL; + + len = strlen (dir); + haveSlash = (dir[len - 1] == '/' +#ifdef WIN32 + || dir[len - 1] == '\\' +#endif + ); + if ((haveSlash ? len : len + 1) >= buflen) + return ENAMETOOLONG; + + strcpy (buf, dir); +#if 0 + //def WIN32 + { + char *bufPtr; + for (bufPtr = buf; *bufPtr != '\0'; bufPtr++) + { + if (*bufPtr == '\\') + *bufPtr = '/'; + } + } +#endif + if (!haveSlash) + { + buf[len] = '/'; + len++; + buf[len] = '\0'; + } + if (access (buf, R_OK | W_OK) == -1) + return errno; + + return 0; +} + +static void +getTempDir (char *buf, size_t buflen) { + char cwd[PATH_MAX]; + + if (tryTempDir (buf, buflen, getenv("TMP")) && + tryTempDir (buf, buflen, getenv("TEMP")) && +#if !defined(WIN32) || defined (__CYGWIN__) + tryTempDir (buf, buflen, "/tmp/") && + tryTempDir (buf, buflen, "/var/tmp/") && +#endif + tryTempDir (buf, buflen, getcwd (cwd, sizeof cwd))) + { + log_add (log_Fatal, "Fatal Error: Cannot find a suitable location " + "to store temporary files."); + exit (EXIT_FAILURE); + } +} + +// Sets the global var 'tempDir' +static int +mountTempDir(const char *name) { + static uio_AutoMount *autoMount[] = { NULL }; + uio_MountHandle *tempHandle; + extern uio_Repository *repository; + + tempHandle = uio_mountDir (repository, "/tmp/", + uio_FSTYPE_STDIO, NULL, NULL, name, autoMount, + uio_MOUNT_TOP, NULL); + if (tempHandle == NULL) { + int saveErrno = errno; + log_add (log_Fatal, "Fatal error: Couldn't mount temp dir '%s': " + "%s", name, strerror (errno)); + errno = saveErrno; + return -1; + } + + tempDir = uio_openDir (repository, "/tmp", 0); + if (tempDir == NULL) { + int saveErrno = errno; + log_add (log_Fatal, "Fatal error: Could not open temp dir: %s", + strerror (errno)); + errno = saveErrno; + return -1; + } + return 0; +} + +#define NUM_TEMP_RETRIES 16 + // Number of files to try to open before giving up. +void +initTempDir (void) { + size_t len; + DWORD num; + int i; + char *tempPtr; + // Pointer to the location in the tempDirName string where the + // path to the temp dir ends and the dir starts. + + tempDirName = HMalloc (PATH_MAX); + getTempDir (tempDirName, PATH_MAX - 21); + // reserve 8 chars for dirname, 1 for slash, and 12 for filename + len = strlen(tempDirName); + + num = ((DWORD) time (NULL)); +// num = GetTimeCounter () % 0xffffffff; + tempPtr = tempDirName + len; + for (i = 0; i < NUM_TEMP_RETRIES; i++) + { + sprintf (tempPtr, "%08x", num + i); + if (createDirectory (tempDirName, 0700) == -1) + continue; + + // Success, we've got a temp dir. + tempDirName = HRealloc (tempDirName, len + 9); + atexit (removeTempDir); + if (mountTempDir (tempDirName) == -1) + exit (EXIT_FAILURE); + return; + } + + // Failure, could not make a temporary directory. + log_add (log_Fatal, "Fatal error: Cannot get a name for a temporary " + "directory."); + exit (EXIT_FAILURE); +} + +void +unInitTempDir (void) { + uio_closeDir(tempDir); + // the removing of the dir is handled via atexit +} + +// return the path to a file in the temp dir with the specified filename. +// returns a pointer to a static buffer. +char * +tempFilePath (const char *filename) { + static char file[PATH_MAX]; + + if (snprintf (file, PATH_MAX, "%s/%s", tempDirName, filename) == -1) { + log_add (log_Fatal, "Path to temp file too long."); + exit (EXIT_FAILURE); + } + return file; +} + + diff --git a/src/libs/gfxlib.h b/src/libs/gfxlib.h new file mode 100644 index 0000000..9a16a69 --- /dev/null +++ b/src/libs/gfxlib.h @@ -0,0 +1,474 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_GFXLIB_H_ +#define LIBS_GFXLIB_H_ + +#include "port.h" +#include "libs/compiler.h" + +typedef struct Color Color; +struct Color { + BYTE r; + BYTE g; + BYTE b; + BYTE a; +}; + +#include "libs/reslib.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +typedef struct context_desc CONTEXT_DESC; +typedef struct frame_desc FRAME_DESC; +typedef struct font_desc FONT_DESC; +typedef struct drawable_desc DRAWABLE_DESC; + +typedef CONTEXT_DESC *CONTEXT; +typedef FRAME_DESC *FRAME; +typedef FONT_DESC *FONT; +typedef DRAWABLE_DESC *DRAWABLE; + +typedef UWORD TIME_VALUE; + +#define TIME_SHIFT 8 +#define MAX_TIME_VALUE ((1 << TIME_SHIFT) + 1) + +typedef SWORD COORD; + +static inline bool +sameColor(Color c1, Color c2) +{ + return c1.r == c2.r && + c1.g == c2.g && + c1.b == c2.b && + c1.a == c2.a; +} + +// Transform a 5-bits color component to an 8-bits color component. +// Form 1, calculates '(r5 / 31.0) * 255.0, highest value is 0xff: +#define CC5TO8(c) (((c) << 3) | ((c) >> 2)) +// Form 2, calculates '(r5 / 32.0) * 256.0, highest value is 0xf8: +//#define CC5TO8(c) ((c) << 3) + +#define BUILD_COLOR(col, c256) col + // BUILD_COLOR used to combine a 15-bit RGB color tripple with a + // destination VGA palette index into a 32-bit value. + // Now, it is an empty wrapper which returns the first argument, + // which is of type Color, and ignores the second argument, + // the palette index. + // + // It is a remnant of 8bpp hardware paletted display (VGA). + // The palette index would be overwritten with the RGB value + // and the drawing op would use this index on screen. + // The palette indices 0-15, as used in DOS SC2, are unchanged + // from the standard VGA palette and are identical to 16-color EGA. + // Various frames, borders, menus, etc. frequently refer to these + // first 16 colors and normally do not change the RGB values from + // the standard ones (see colors.h; most likely unchanged from SC1) + // The palette index is meaningless in UQM for the most part. + // New code should just use index 0. + +// Turn a 15 bits color into a 24-bits color. +// r, g, and b are each 5-bits color components. +static inline Color +colorFromRgb15 (BYTE r, BYTE g, BYTE b) +{ + Color c; + c.r = CC5TO8 (r); + c.g = CC5TO8 (g); + c.b = CC5TO8 (b); + c.a = 0xff; + + return c; +} +#define MAKE_RGB15(r, g, b) colorFromRgb15 ((r), (g), (b)) + +#ifdef NOTYET /* Need C'99 support */ +#define MAKE_RGB15(r, g, b) (Color) { \ + .r = CC5TO8 (r), \ + .g = CC5TO8 (g), \ + .b = CC5TO8 (b), \ + .a = 0xff \ + } +#endif + +// Temporary, until we can use C'99 features. Then MAKE_RGB15 will be usable +// anywhere. +// This define is intended for global initialisations, where the +// expression must be constant. +#define MAKE_RGB15_INIT(r, g, b) { \ + CC5TO8 (r), \ + CC5TO8 (g), \ + CC5TO8 (b), \ + 0xff \ + } + +static inline Color +buildColorRgba (BYTE r, BYTE g, BYTE b, BYTE a) +{ + Color c; + c.r = r; + c.g = g; + c.b = b; + c.a = a; + + return c; +} +#define BUILD_COLOR_RGBA(r, g, b, a) \ + buildColorRgba ((r), (g), (b), (a)) + + +typedef BYTE CREATE_FLAGS; +// WANT_MASK is deprecated (and non-functional). It used to generate a bitmap +// of changed pixels for a target DRAWABLE, so that DRAW_SUBTRACTIVE could +// paint background pixels over them, i.e. a revert draw. The backgrounds +// are fully erased now instead. +#define WANT_MASK (CREATE_FLAGS)(1 << 0) +#define WANT_PIXMAP (CREATE_FLAGS)(1 << 1) +// MAPPED_TO_DISPLAY is deprecated but still checked by LoadDisplayPixmap(). +// Its former use was to indicate a pre-scaled graphic for the display. +#define MAPPED_TO_DISPLAY (CREATE_FLAGS)(1 << 2) +#define WANT_ALPHA (CREATE_FLAGS)(1 << 3) + +typedef struct extent +{ + COORD width, height; +} EXTENT; + +typedef struct point +{ + COORD x, y; +} POINT; + +typedef struct stamp +{ + POINT origin; + FRAME frame; +} STAMP; + +typedef struct rect +{ + POINT corner; + EXTENT extent; +} RECT; + +typedef struct line +{ + POINT first, second; +} LINE; + +static inline POINT +MAKE_POINT (COORD x, COORD y) +{ + POINT pt = {x, y}; + return pt; +} + +static inline bool +pointsEqual (POINT p1, POINT p2) +{ + return p1.x == p2.x && p1.y == p2.y; +} + +static inline bool +extentsEqual (EXTENT e1, EXTENT e2) +{ + return e1.width == e2.width && e1.height == e2.height; +} + +static inline bool +rectsEqual (RECT r1, RECT r2) +{ + return pointsEqual (r1.corner, r2.corner) + && extentsEqual (r1.extent, r2.extent); +} + +static inline bool +pointWithinRect (RECT r, POINT p) +{ + return p.x >= r.corner.x && p.y >= r.corner.y + && p.x < r.corner.x + r.extent.width + && p.y < r.corner.y + r.extent.height; +} + +typedef enum +{ + ALIGN_LEFT, + ALIGN_CENTER, + ALIGN_RIGHT +} TEXT_ALIGN; + +typedef enum +{ + VALIGN_TOP, + VALIGN_MIDDLE, + VALIGN_BOTTOM +} TEXT_VALIGN; + +typedef struct text +{ + POINT baseline; + const UNICODE *pStr; + TEXT_ALIGN align; + COUNT CharCount; +} TEXT; + +#if defined(__cplusplus) +} +#endif + +#include "libs/strlib.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +typedef STRING_TABLE COLORMAP_REF; +typedef STRING COLORMAP; +// COLORMAPPTR is really a pointer to colortable entry structure +// which is documented in doc/devel/strtab, .ct files section +typedef void *COLORMAPPTR; + +#include "graphics/prim.h" + + +typedef BYTE BATCH_FLAGS; +// This flag is currently unused but it might make sense to restore it +#define BATCH_BUILD_PAGE (BATCH_FLAGS)(1 << 0) + +typedef struct +{ + TIME_VALUE last_time_val; + POINT EndPoint; + STAMP IntersectStamp; +} INTERSECT_CONTROL; + +typedef BYTE INTERSECT_CODE; + +#define INTERSECT_LEFT (INTERSECT_CODE)(1 << 0) +#define INTERSECT_TOP (INTERSECT_CODE)(1 << 1) +#define INTERSECT_RIGHT (INTERSECT_CODE)(1 << 2) +#define INTERSECT_BOTTOM (INTERSECT_CODE)(1 << 3) +#define INTERSECT_NOCLIP (INTERSECT_CODE)(1 << 7) +#define INTERSECT_ALL_SIDES (INTERSECT_CODE)(INTERSECT_LEFT | \ + INTERSECT_TOP | \ + INTERSECT_RIGHT | \ + INTERSECT_BOTTOM) + +typedef POINT HOT_SPOT; + +extern HOT_SPOT MAKE_HOT_SPOT (COORD, COORD); + +extern INTERSECT_CODE BoxIntersect (RECT *pr1, RECT *pr2, RECT *printer); +extern void BoxUnion (RECT *pr1, RECT *pr2, RECT *punion); + +typedef enum +{ + FadeAllToWhite = 250, + FadeSomeToWhite, + FadeAllToBlack, + FadeAllToColor, + FadeSomeToBlack, + FadeSomeToColor +} ScreenFadeType; + +typedef enum +{ + DRAW_REPLACE = 0, + // Pixels in the target FRAME are replaced entirely. + // Non-stamp primitives with Color.a < 255 to RGB targets are + // equivalent to DRAW_ALPHA with (DrawMode.factor = Color.a), + // except the Text primitives. + // DrawMode.factor: ignored + // Text: supported (except DRAW_ALPHA via Color.a) + // RGBA sources (WANT_ALPHA): per-pixel alpha blending performed + // RGBA targets (WANT_ALPHA): replace directly supported + DRAW_ADDITIVE, + // Pixel channels of the source FRAME or Color channels of + // a primitive are modulated by (DrawMode.factor / 255) and added + // to the pixel channels of the target FRAME. + // DrawMode.factor range: -32767..32767 (negative values make + // draw subtractive); 255 = 1:1 ratio + // Text: not yet supported + // RGBA sources (WANT_ALPHA): alpha channel ignored + // RGBA targets (WANT_ALPHA): not yet supported + DRAW_ALPHA, + // Pixel channels of the source FRAME or Color channels of + // a primitive are modulated by (DrawMode.factor / 255) and added + // to the pixel channels of the target FRAME, modulated by + // (1 - DrawMode.factor / 255) + // DrawMode.factor range: 0..255; 255 = fully opaque + // Text: supported + // RGBA sources (WANT_ALPHA): alpha channel ignored + // RGBA targets (WANT_ALPHA): not yet supported + + DRAW_DEFAULT = DRAW_REPLACE, +} DrawKind; + +typedef struct +{ + BYTE kind; + SWORD factor; +} DrawMode; + +#define DRAW_REPLACE_MODE MAKE_DRAW_MODE (DRAW_REPLACE, 0) +#define DRAW_FACTOR_1 0xff + +static inline DrawMode +MAKE_DRAW_MODE (DrawKind kind, SWORD factor) +{ + DrawMode mode; + mode.kind = kind; + mode.factor = factor; + return mode; +} + +extern CONTEXT SetContext (CONTEXT Context); +extern Color SetContextForeGroundColor (Color Color); +extern Color GetContextForeGroundColor (void); +extern Color SetContextBackGroundColor (Color Color); +extern Color GetContextBackGroundColor (void); +extern FRAME SetContextFGFrame (FRAME Frame); +extern FRAME GetContextFGFrame (void); +// Context cliprect defines the drawing bounds. Additionally, all +// drawing positions (x,y) are relative to the cliprect corner. +extern BOOLEAN SetContextClipRect (RECT *pRect); +// The returned rect is always filled in. If the context cliprect +// is undefined, the returned rect has foreground frame dimensions. +extern BOOLEAN GetContextClipRect (RECT *pRect); +// The actual origin will be orgOffset + context ClipRect.corner +extern POINT SetContextOrigin (POINT orgOffset); +extern DrawMode SetContextDrawMode (DrawMode); +extern DrawMode GetContextDrawMode (void); +// 'area' may be NULL to copy the entire CONTEXT cliprect +// 'area' is relative to the CONTEXT cliprect +extern DRAWABLE CopyContextRect (const RECT* area); + +extern TIME_VALUE DrawablesIntersect (INTERSECT_CONTROL *pControl0, + INTERSECT_CONTROL *pControl1, TIME_VALUE max_time_val); +extern void DrawStamp (STAMP *pStamp); +extern void DrawFilledStamp (STAMP *pStamp); +extern void DrawPoint (POINT *pPoint); +extern void DrawRectangle (RECT *pRect); +extern void DrawFilledRectangle (RECT *pRect); +extern void DrawLine (LINE *pLine); +extern void font_DrawText (TEXT *pText); +extern void font_DrawTracedText (TEXT *pText, Color text, Color trace); +extern void DrawBatch (PRIMITIVE *pBasePrim, PRIM_LINKS PrimLinks, + BATCH_FLAGS BatchFlags); +extern void BatchGraphics (void); +extern void UnbatchGraphics (void); +extern void FlushGraphics (void); +extern void ClearDrawable (void); +#ifdef DEBUG +extern CONTEXT CreateContextAux (const char *name); +#define CreateContext(name) CreateContextAux((name)) +#else /* if !defined(DEBUG) */ +extern CONTEXT CreateContextAux (void); +#define CreateContext(name) CreateContextAux() +#endif /* !defined(DEBUG) */ +extern BOOLEAN DestroyContext (CONTEXT ContextRef); +extern DRAWABLE CreateDisplay (CREATE_FLAGS CreateFlags, SIZE *pwidth, + SIZE *pheight); +extern DRAWABLE CreateDrawable (CREATE_FLAGS CreateFlags, SIZE width, + SIZE height, COUNT num_frames); +extern BOOLEAN DestroyDrawable (DRAWABLE Drawable); +extern BOOLEAN GetFrameRect (FRAME Frame, RECT *pRect); +#ifdef DEBUG +extern const char *GetContextName (CONTEXT context); +extern CONTEXT GetFirstContext (void); +extern CONTEXT GetNextContext (CONTEXT context); +extern size_t GetContextCount (void); +#endif /* DEBUG */ + +extern HOT_SPOT SetFrameHot (FRAME Frame, HOT_SPOT HotSpot); +extern HOT_SPOT GetFrameHot (FRAME Frame); +extern BOOLEAN InstallGraphicResTypes (void); +extern DRAWABLE LoadGraphicFile (const char *pStr); +extern FONT LoadFontFile (const char *pStr); +extern void *LoadGraphicInstance (RESOURCE res); +extern DRAWABLE LoadDisplayPixmap (const RECT *area, FRAME frame); +extern FRAME SetContextFontEffect (FRAME EffectFrame); +extern FONT SetContextFont (FONT Font); +extern BOOLEAN DestroyFont (FONT FontRef); +// The returned pRect is relative to the context drawing origin +extern BOOLEAN TextRect (TEXT *pText, RECT *pRect, BYTE *pdelta); +extern BOOLEAN GetContextFontLeading (SIZE *pheight); +extern BOOLEAN GetContextFontLeadingWidth (SIZE *pwidth); +extern COUNT GetFrameCount (FRAME Frame); +extern COUNT GetFrameIndex (FRAME Frame); +extern FRAME SetAbsFrameIndex (FRAME Frame, COUNT FrameIndex); +extern FRAME SetRelFrameIndex (FRAME Frame, SIZE FrameOffs); +extern FRAME SetEquFrameIndex (FRAME DstFrame, FRAME SrcFrame); +extern FRAME IncFrameIndex (FRAME Frame); +extern FRAME DecFrameIndex (FRAME Frame); +extern DRAWABLE CopyFrameRect (FRAME Frame, const RECT *area); +extern DRAWABLE CloneFrame (FRAME Frame); +extern DRAWABLE RotateFrame (FRAME Frame, int angle_deg); +extern DRAWABLE RescaleFrame (FRAME, int width, int height); +// This pair works for both paletted and trucolor frames +extern BOOLEAN ReadFramePixelColors (FRAME frame, Color *pixels, + int width, int height); +extern BOOLEAN WriteFramePixelColors (FRAME frame, const Color *pixels, + int width, int height); +// This pair only works for paletted frames +extern BOOLEAN ReadFramePixelIndexes (FRAME frame, BYTE *pixels, + int width, int height); +extern BOOLEAN WriteFramePixelIndexes (FRAME frame, const BYTE *pixels, + int width, int height); +extern void SetFrameTransparentColor (FRAME, Color); + +// If the frame is an active SCREEN_DRAWABLE, this call must be +// preceeded by FlushGraphics() for draw commands to have taken effect +extern Color GetFramePixel (FRAME, POINT pixelPt); + +extern FRAME CaptureDrawable (DRAWABLE Drawable); +extern DRAWABLE ReleaseDrawable (FRAME Frame); + +extern DRAWABLE GetFrameParentDrawable (FRAME Frame); + +extern BOOLEAN SetColorMap (COLORMAPPTR ColorMapPtr); +extern DWORD XFormColorMap (COLORMAPPTR ColorMapPtr, SIZE TimeInterval); +extern DWORD FadeScreen (ScreenFadeType fadeType, SIZE TimeInterval); +extern void FlushColorXForms (void); +#define InitColorMapResources InitStringTableResources +#define LoadColorMapFile LoadStringTableFile +#define LoadColorMapInstance LoadStringTableInstance +#define CaptureColorMap CaptureStringTable +#define ReleaseColorMap ReleaseStringTable +#define DestroyColorMap DestroyStringTable +#define GetColorMapRef GetStringTable +#define GetColorMapCount GetStringTableCount +#define GetColorMapIndex GetStringTableIndex +#define SetAbsColorMapIndex SetAbsStringTableIndex +#define SetRelColorMapIndex SetRelStringTableIndex +#define GetColorMapLength GetStringLengthBin + +extern COLORMAPPTR GetColorMapAddress (COLORMAP); + +void SetSystemRect (const RECT *pRect); +void ClearSystemRect (void); + +#if defined(__cplusplus) +} +#endif + +#endif /* LIBS_GFXLIB_H_ */ diff --git a/src/libs/graphics/Makeinfo b/src/libs/graphics/Makeinfo new file mode 100644 index 0000000..1a9f7ee --- /dev/null +++ b/src/libs/graphics/Makeinfo @@ -0,0 +1,12 @@ +if [ "$uqm_GFXMODULE" = "sdl" ]; then + uqm_SUBDIRS="sdl" +fi + +uqm_CFILES="boxint.c clipline.c cmap.c context.c drawable.c filegfx.c + bbox.c dcqueue.c gfxload.c + font.c frame.c gfx_common.c intersec.c loaddisp.c + pixmap.c resgfx.c tfb_draw.c tfb_prim.c widgets.c" + +uqm_HFILES="bbox.h cmap.h context.h dcqueue.h drawable.h drawcmd.h font.h + gfx_common.h gfxintrn.h prim.h tfb_draw.h tfb_prim.h widgets.h" + diff --git a/src/libs/graphics/bbox.c b/src/libs/graphics/bbox.c new file mode 100644 index 0000000..ce57d32 --- /dev/null +++ b/src/libs/graphics/bbox.c @@ -0,0 +1,133 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "port.h" +#include "libs/graphics/bbox.h" + +TFB_BoundingBox TFB_BBox; +int maxWidth; +int maxHeight; + +void +TFB_BBox_Init (int width, int height) +{ + maxWidth = width; + maxHeight = height; + TFB_BBox.clip.extent.width = width; + TFB_BBox.clip.extent.height = height; +} + +void +TFB_BBox_Reset (void) +{ + TFB_BBox.valid = 0; +} + +void +TFB_BBox_SetClipRect (const RECT *r) +{ + if (!r) + { /* No clipping -- full rect */ + TFB_BBox.clip.corner.x = 0; + TFB_BBox.clip.corner.y = 0; + TFB_BBox.clip.extent.width = maxWidth; + TFB_BBox.clip.extent.height = maxHeight; + return; + } + + TFB_BBox.clip = *r; + + /* Make sure the cliprect is sane */ + if (TFB_BBox.clip.corner.x < 0) + TFB_BBox.clip.corner.x = 0; + + if (TFB_BBox.clip.corner.y < 0) + TFB_BBox.clip.corner.y = 0; + + if (TFB_BBox.clip.corner.x + TFB_BBox.clip.extent.width > maxWidth) + TFB_BBox.clip.extent.width = maxWidth - TFB_BBox.clip.corner.x; + + if (TFB_BBox.clip.corner.y + TFB_BBox.clip.extent.height > maxHeight) + TFB_BBox.clip.extent.height = maxHeight - TFB_BBox.clip.corner.y; +} + +void +TFB_BBox_RegisterPoint (int x, int y) +{ + int x1 = TFB_BBox.clip.corner.x; + int y1 = TFB_BBox.clip.corner.y; + int x2 = TFB_BBox.clip.corner.x + TFB_BBox.clip.extent.width - 1; + int y2 = TFB_BBox.clip.corner.y + TFB_BBox.clip.extent.height - 1; + + /* Constrain coordinates */ + if (x < x1) x = x1; + if (x >= x2) x = x2; + if (y < y1) y = y1; + if (y >= y2) y = y2; + + /* Is this the first point? If so, set a pixel-region and return. */ + if (!TFB_BBox.valid) + { + TFB_BBox.valid = 1; + TFB_BBox.region.corner.x = x; + TFB_BBox.region.corner.y = y; + TFB_BBox.region.extent.width = 1; + TFB_BBox.region.extent.height = 1; + return; + } + + /* Otherwise expand the rectangle if necessary. */ + x1 = TFB_BBox.region.corner.x; + y1 = TFB_BBox.region.corner.y; + x2 = TFB_BBox.region.corner.x + TFB_BBox.region.extent.width - 1; + y2 = TFB_BBox.region.corner.y + TFB_BBox.region.extent.height - 1; + + if (x < x1) { + TFB_BBox.region.corner.x = x; + TFB_BBox.region.extent.width += x1 - x; + } + if (y < y1) { + TFB_BBox.region.corner.y = y; + TFB_BBox.region.extent.height += y1 - y; + } + if (x > x2) { + TFB_BBox.region.extent.width += x - x2; + } + if (y > y2) { + TFB_BBox.region.extent.height += y - y2; + } +} + +void +TFB_BBox_RegisterRect (const RECT *r) +{ + /* RECT will still register as a corner point of the cliprect even + * if it does not intersect with the cliprect at all. This is not + * a problem, as more is not less. */ + TFB_BBox_RegisterPoint (r->corner.x, r->corner.y); + TFB_BBox_RegisterPoint (r->corner.x + r->extent.width - 1, + r->corner.y + r->extent.height - 1); +} + +void +TFB_BBox_RegisterCanvas (TFB_Canvas c, int x, int y) +{ + RECT r; + r.corner.x = x; + r.corner.y = y; + TFB_DrawCanvas_GetExtent (c, &r.extent); + TFB_BBox_RegisterRect (&r); +} diff --git a/src/libs/graphics/bbox.h b/src/libs/graphics/bbox.h new file mode 100644 index 0000000..33d3000 --- /dev/null +++ b/src/libs/graphics/bbox.h @@ -0,0 +1,46 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef BBOX_H_INCL__ +#define BBOX_H_INCL__ + +#include "libs/gfxlib.h" +#include "libs/graphics/tfb_draw.h" + +/* Bounding Box operations. These operations are NOT synchronized. + * However, they should only be accessed by TFB_FlushGraphics and + * TFB_SwapBuffers, or the routines that they exclusively call -- all + * of which are only callable by the thread that is permitted to touch + * the screen. No explicit locks should therefore be required. */ + +typedef struct { + int valid; // If zero, the next point registered becomes the region + RECT region; // The actual modified rectangle + RECT clip; // Points outside of this rectangle are pushed to + // the closest border point +} TFB_BoundingBox; + +extern TFB_BoundingBox TFB_BBox; + +void TFB_BBox_RegisterPoint (int x, int y); +void TFB_BBox_RegisterRect (const RECT *r); +void TFB_BBox_RegisterCanvas (TFB_Canvas c, int x, int y); + +void TFB_BBox_Init (int width, int height); +void TFB_BBox_Reset (void); +void TFB_BBox_SetClipRect (const RECT *r); + +#endif /* BBOX_H_INCL__ */ diff --git a/src/libs/graphics/boxint.c b/src/libs/graphics/boxint.c new file mode 100644 index 0000000..0500311 --- /dev/null +++ b/src/libs/graphics/boxint.c @@ -0,0 +1,183 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "gfxintrn.h" + +#undef MIN +#define MIN(a, b) (((a) <= (b)) ? (a) : (b)) +#undef MAX +#define MAX(a, b) (((a) >= (b)) ? (a) : (b)) + +INTERSECT_CODE +BoxIntersect (RECT *pr1, RECT *pr2, RECT *pinter) +{ + INTERSECT_CODE intersect_code; + COORD x1; + SIZE w1, w2, delta; + + intersect_code = INTERSECT_NOCLIP; + + x1 = pr1->corner.x - pr2->corner.x; + + w1 = pr1->extent.width; + w2 = pr2->extent.width; + if ((delta = w2 - x1) <= w1) + { + if (delta != w1) + { + w1 = delta; + intersect_code &= ~INTERSECT_NOCLIP; + } + intersect_code |= INTERSECT_RIGHT; + } + if (x1 <= 0) + { + if (x1 < 0) + { + w1 += x1; + x1 = 0; + intersect_code &= ~INTERSECT_NOCLIP; + } + intersect_code |= INTERSECT_LEFT; + } + + if (w1 > 0) + { +#define h2 w2 + COORD y1; + SIZE h1; + + y1 = pr1->corner.y - pr2->corner.y; + + h1 = pr1->extent.height; + h2 = pr2->extent.height; + if ((delta = h2 - y1) <= h1) + { + if (delta != h1) + { + h1 = delta; + intersect_code &= ~INTERSECT_NOCLIP; + } + intersect_code |= INTERSECT_BOTTOM; + } + if (y1 <= 0) + { + if (y1 < 0) + { + h1 += y1; + y1 = 0; + intersect_code &= ~INTERSECT_NOCLIP; + } + intersect_code |= INTERSECT_TOP; + } + + if (h1 > 0) + { + pinter->corner.x = x1 + pr2->corner.x; + pinter->corner.y = y1 + pr2->corner.y; + pinter->extent.width = w1; + pinter->extent.height = h1; + + return (intersect_code); + } +#undef h2 + } + + return ((INTERSECT_CODE)0); +} + +void +BoxUnion (RECT *pr1, RECT *pr2, RECT *punion) +{ +#if NEVER // Part of lower FIXME. + COORD x2, y2, w2, h2; +#endif // NEVER + + // Union is A AND B, put together, correct? Returns a bigger box that + // encompasses the two. + punion->corner.x = MIN(pr1->corner.x, pr2->corner.x); + punion->corner.y = MIN(pr1->corner.y, pr2->corner.y); + + punion->extent.width = MAX(pr1->corner.x + pr1->extent.width, + pr2->corner.x + pr2->extent.width) - punion->corner.x; + punion->extent.height = MAX(pr1->corner.y + pr1->extent.height, + pr2->corner.y + pr2->extent.height) - punion->corner.y; + + +#if NEVER // FIXME - I think this is broken, but keeping it around for reference + // FIXME - just in case. + +#if 1 /* alter based on 0 widths */ + + x2 = + (pr1->corner.x < pr2->corner.x)? pr1->corner.x : pr2->corner.x; + + y2 = + (pr1->corner.y < pr2->corner.y)? pr1->corner.y : pr2->corner.y; + + w2 = ( + ((pr1->corner.x + pr1->extent.width) > (pr2->corner.x + pr2->extent.width))? + (pr1->corner.x + pr1->extent.width) : (pr2->corner.x + pr2->extent.width) + ) - punion->corner.x; + + h2 = ( + ((pr1->corner.y + pr1->extent.height) > (pr2->corner.y + pr2->extent.height))? + (pr1->corner.y + pr1->extent.height) : (pr2->corner.y + pr2->extent.height) + ) - punion->corner.y; +#else + SIZE delta; + COORD x1, y1, w1, h1; + + x1 = pr1->corner.x; + w1 = pr1->extent.width; + x2 = pr2->corner.x; + w2 = pr2->extent.width; + if ((delta = x1 - x2) >= 0) + w1 += delta; + else + { + w2 -= delta; + x2 += delta; + } + + y1 = pr1->corner.y; + h1 = pr1->extent.height; + y2 = pr2->corner.y; + h2 = pr2->extent.height; + if ((delta = y1 - y2) >= 0) + h1 += delta; + else + { + h2 -= delta; + y2 += delta; + } + + if ((delta = w1 - w2) > 0) + w2 += delta; + if ((delta = h1 - h2) > 0) + h2 += delta; +#endif + + punion->corner.x = x2; + punion->corner.y = y2; + punion->extent.width = w2; + punion->extent.height = h2; + +#endif // NEVER +} + diff --git a/src/libs/graphics/clipline.c b/src/libs/graphics/clipline.c new file mode 100644 index 0000000..ab2d7dd --- /dev/null +++ b/src/libs/graphics/clipline.c @@ -0,0 +1,241 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "gfxintrn.h" + +INTERSECT_CODE +_clip_line (const RECT *pClipRect, BRESENHAM_LINE *pLine) +{ + COORD p; + COORD x0, y0, xmin, ymin, xmax, ymax; + SIZE abs_delta_x, abs_delta_y; + INTERSECT_CODE intersect_code; + + xmin = pClipRect->corner.x; + ymin = pClipRect->corner.y; + xmax = pClipRect->corner.x + pClipRect->extent.width - 1; + ymax = pClipRect->corner.y + pClipRect->extent.height - 1; + if (pLine->first.x <= pLine->second.x) + pLine->end_points_exchanged = FALSE; + else + { + p = pLine->first.x; + pLine->first.x = pLine->second.x; + pLine->second.x = p; + + p = pLine->first.y; + pLine->first.y = pLine->second.y; + pLine->second.y = p; + + pLine->end_points_exchanged = TRUE; + } + + if (pLine->first.x > xmax || pLine->second.x < xmin || + (pLine->first.y > ymax && pLine->second.y > ymax) || + (pLine->first.y < ymin && pLine->second.y < ymin)) + return ((INTERSECT_CODE)0); + + intersect_code = INTERSECT_NOCLIP; + x0 = y0 = 0; + abs_delta_x = (pLine->second.x - pLine->first.x) << 1; + abs_delta_y = (pLine->second.y - pLine->first.y) << 1; + pLine->abs_delta_x = abs_delta_x; + pLine->abs_delta_y = abs_delta_y; + if (abs_delta_y == 0) + { + if (pLine->first.x < xmin) + { + pLine->first.x = xmin; + intersect_code |= INTERSECT_LEFT; + } + if (pLine->second.x > xmax) + { + pLine->second.x = xmax; + intersect_code |= INTERSECT_RIGHT; + } + } + else if (abs_delta_x == 0) + { + if (abs_delta_y < 0) + { + p = pLine->first.y; + pLine->first.y = pLine->second.y; + pLine->second.y = p; + + pLine->abs_delta_y = + abs_delta_y = -abs_delta_y; + } + + if (pLine->first.y < ymin) + { + pLine->first.y = ymin; + intersect_code |= INTERSECT_TOP; + } + if (pLine->second.y > ymax) + { + pLine->second.y = ymax; + intersect_code |= INTERSECT_BOTTOM; + } + } + else + { + COORD x1, y1; + + p = pLine->first.x; + x1 = pLine->second.x - p; + xmin = xmin - p; + xmax = xmax - p; + + p = pLine->first.y; + if (abs_delta_y > 0) + { + y1 = pLine->second.y - p; + ymin = ymin - p; + ymax = ymax - p; + } + else + { + y1 = p - pLine->second.y; + ymin = p - ymin; + ymax = p - ymax; + + p = ymin; + ymin = ymax; + ymax = p; + abs_delta_y = -abs_delta_y; + } + + if (abs_delta_x > abs_delta_y) + { + SIZE half_dx; + + half_dx = abs_delta_x >> 1; + if (x0 < xmin) + { + if ((y0 = (COORD)(((long)abs_delta_y * + (x0 = xmin) + half_dx) / abs_delta_x)) > ymax) + return ((INTERSECT_CODE)0); + intersect_code |= INTERSECT_LEFT; + } + if (x1 > xmax) + { + if ((y1 = (COORD)(((long)abs_delta_y * + (x1 = xmax) + half_dx) / abs_delta_x)) < ymin) + return ((INTERSECT_CODE)0); + intersect_code |= INTERSECT_RIGHT; + } + if (y0 < ymin) + { + if ((x0 = (COORD)(((long)abs_delta_x * + (y0 = ymin) - half_dx + (abs_delta_y - 1)) / + abs_delta_y)) > xmax) + return ((INTERSECT_CODE)0); + intersect_code |= INTERSECT_TOP; + intersect_code &= ~INTERSECT_LEFT; + } + if (y1 > ymax) + { + if ((x1 = (COORD)(((long)abs_delta_x * + ((y1 = ymax) + 1) - half_dx + (abs_delta_y - 1)) / + abs_delta_y) - 1) < xmin) + return ((INTERSECT_CODE)0); + intersect_code |= INTERSECT_BOTTOM; + intersect_code &= ~INTERSECT_RIGHT; + } + } + else + { + SIZE half_dy; + + half_dy = abs_delta_y >> 1; + if (y0 < ymin) + { + if ((x0 = (COORD)(((long)abs_delta_x * + (y0 = ymin) + half_dy) / abs_delta_y)) > xmax) + return ((INTERSECT_CODE)0); + intersect_code |= INTERSECT_TOP; + } + if (y1 > ymax) + { + if ((x1 = (COORD)(((long)abs_delta_x * + (y1 = ymax) + half_dy) / abs_delta_y)) < xmin) + return ((INTERSECT_CODE)0); + intersect_code |= INTERSECT_BOTTOM; + } + if (x0 < xmin) + { + if ((y0 = (COORD)(((long)abs_delta_y * + (x0 = xmin) - half_dy + (abs_delta_x - 1)) / + abs_delta_x)) > ymax) + return ((INTERSECT_CODE)0); + intersect_code |= INTERSECT_LEFT; + intersect_code &= ~INTERSECT_TOP; + } + if (x1 > xmax) + { + if ((y1 = (COORD)(((long)abs_delta_y * + ((x1 = xmax) + 1) - half_dy + (abs_delta_x - 1)) / + abs_delta_x) - 1) < ymin) + return ((INTERSECT_CODE)0); + intersect_code |= INTERSECT_RIGHT; + intersect_code &= ~INTERSECT_BOTTOM; + } + } + + pLine->second.x = pLine->first.x + x1; + pLine->first.x += x0; + if (pLine->abs_delta_y > 0) + { + pLine->second.y = pLine->first.y + y1; + pLine->first.y += y0; + } + else + { + INTERSECT_CODE y_code; + + pLine->second.y = pLine->first.y - y1; + pLine->first.y -= y0; + + y_code = (INTERSECT_CODE)(intersect_code + & (INTERSECT_TOP | INTERSECT_BOTTOM)); + if (y_code && y_code != (INTERSECT_TOP | INTERSECT_BOTTOM)) + intersect_code ^= (INTERSECT_TOP | INTERSECT_BOTTOM); + } + } + + if (!(intersect_code & INTERSECT_ALL_SIDES)) + { + if (abs_delta_x > abs_delta_y) + pLine->error_term = -(SIZE)(abs_delta_x >> 1); + else + pLine->error_term = -(SIZE)(abs_delta_y >> 1); + } + else + { + intersect_code &= ~INTERSECT_NOCLIP; + if (abs_delta_x > abs_delta_y) + pLine->error_term = (SIZE)((x0 * (long)abs_delta_y) - + (y0 * (long)abs_delta_x)) - (abs_delta_x >> 1); + else + pLine->error_term = (SIZE)((y0 * (long)abs_delta_x) - + (x0 * (long)abs_delta_y)) - (abs_delta_y >> 1); + } + + return (pLine->intersect_code = intersect_code); +} + diff --git a/src/libs/graphics/cmap.c b/src/libs/graphics/cmap.c new file mode 100644 index 0000000..53cd13f --- /dev/null +++ b/src/libs/graphics/cmap.c @@ -0,0 +1,663 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "libs/graphics/cmap.h" +#include "libs/threadlib.h" +#include "libs/timelib.h" +#include "libs/inplib.h" +#include "libs/strlib.h" + // for GetStringAddress() +#include "libs/log.h" +#include <string.h> +#include <stdlib.h> + + +typedef struct xform_control +{ + int CMapIndex; // -1 means unused + COLORMAPPTR CMapPtr; + SIZE Ticks; + DWORD StartTime; + DWORD EndTime; + Color OldCMap[NUMBER_OF_PLUTVALS]; +} XFORM_CONTROL; + +#define MAX_XFORMS 16 +static struct +{ + XFORM_CONTROL TaskControl[MAX_XFORMS]; + volatile int Highest; + // 'pending' is Highest >= 0 + Mutex Lock; +} XFormControl; + +static int fadeAmount = FADE_NORMAL_INTENSITY; +static int fadeDelta; +static TimeCount fadeStartTime; +static sint32 fadeInterval; +static Mutex fadeLock; + +#define SPARE_COLORMAPS 20 + +// Colormaps are rapidly replaced in some parts of the game, so +// it pays to have some spares on hand +static TFB_ColorMap *poolhead; +static int poolcount; + +static TFB_ColorMap * colormaps[MAX_COLORMAPS]; +static int mapcount; +static Mutex maplock; + + +static void release_colormap (TFB_ColorMap *map); +static void delete_colormap (TFB_ColorMap *map); + + +void +InitColorMaps (void) +{ + int i; + + // init colormaps + maplock = CreateMutex ("Colormaps Lock", SYNC_CLASS_TOPLEVEL | SYNC_CLASS_VIDEO); + + // init xform control + XFormControl.Highest = -1; + XFormControl.Lock = CreateMutex ("Transform Lock", SYNC_CLASS_TOPLEVEL | SYNC_CLASS_VIDEO); + for (i = 0; i < MAX_XFORMS; ++i) + XFormControl.TaskControl[i].CMapIndex = -1; + + fadeLock = CreateMutex ("Fade Lock", SYNC_CLASS_TOPLEVEL | SYNC_CLASS_VIDEO); +} + +void +UninitColorMaps (void) +{ + int i; + TFB_ColorMap *next; + + for (i = 0; i < MAX_COLORMAPS; ++i) + { + TFB_ColorMap *map = colormaps[i]; + if (!map) + continue; + release_colormap (map); + colormaps[i] = 0; + } + + // free spares + for ( ; poolhead; poolhead = next, --poolcount) + { + next = poolhead->next; + delete_colormap (poolhead); + } + + DestroyMutex (fadeLock); + // uninit xform control + DestroyMutex (XFormControl.Lock); + + // uninit colormaps + DestroyMutex (maplock); +} + +static inline TFB_ColorMap * +alloc_colormap (void) + // returns an addrefed object +{ + TFB_ColorMap *map; + + if (poolhead) + { // have some spares + map = poolhead; + poolhead = map->next; + --poolcount; + } + else + { // no spares, need a new one + map = HMalloc (sizeof (*map)); + map->palette = AllocNativePalette (); + if (!map->palette) + { + HFree (map); + return NULL; + } + } + map->next = NULL; + map->index = -1; + map->refcount = 1; + map->version = 0; + + return map; +} + +static TFB_ColorMap * +clone_colormap (TFB_ColorMap *from, int index) + // returns an addrefed object +{ + TFB_ColorMap *map; + + map = alloc_colormap (); + if (!map) + { + log_add (log_Warning, "FATAL: clone_colormap(): " + "could not allocate a map"); + exit (EXIT_FAILURE); + } + else + { // fresh new map + map->index = index; + if (from) + map->version = from->version; + } + map->version++; + + return map; +} + +static void +delete_colormap (TFB_ColorMap *map) +{ + FreeNativePalette (map->palette); + HFree (map); +} + +static inline void +free_colormap (TFB_ColorMap *map) +{ + if (!map) + { + log_add (log_Warning, "free_colormap(): tried to free a NULL map"); + return; + } + + if (poolcount < SPARE_COLORMAPS) + { // return to the spare pool + map->next = poolhead; + poolhead = map; + ++poolcount; + } + else + { // don't need any more spares + delete_colormap (map); + } +} + +static inline TFB_ColorMap * +get_colormap (int index) +{ + TFB_ColorMap *map; + + map = colormaps[index]; + if (!map) + { + log_add (log_Fatal, "BUG: get_colormap(): map not present"); + exit (EXIT_FAILURE); + } + + map->refcount++; + return map; +} + +static void +release_colormap (TFB_ColorMap *map) +{ + if (!map) + return; + + if (map->refcount <= 0) + { + log_add (log_Warning, "BUG: release_colormap(): refcount not >0"); + return; + } + + map->refcount--; + if (map->refcount == 0) + free_colormap (map); +} + +void +TFB_ReturnColorMap (TFB_ColorMap *map) +{ + LockMutex (maplock); + release_colormap (map); + UnlockMutex (maplock); +} + +TFB_ColorMap * +TFB_GetColorMap (int index) +{ + TFB_ColorMap *map; + + LockMutex (maplock); + map = get_colormap (index); + UnlockMutex (maplock); + + return map; +} + +void +GetColorMapColors (Color *colors, TFB_ColorMap *map) +{ + int i; + + if (!map) + return; + + for (i = 0; i < NUMBER_OF_PLUTVALS; ++i) + colors[i] = GetNativePaletteColor (map->palette, i); +} + +BOOLEAN +SetColorMap (COLORMAPPTR map) +{ + int start, end; + int total_size; + UBYTE *colors = (UBYTE*)map; + TFB_ColorMap **mpp; + + if (!map) + return TRUE; + + start = *colors++; + end = *colors++; + if (start > end) + { + log_add (log_Warning, "ERROR: SetColorMap(): " + "starting map (%d) not less or eq ending (%d)", + start, end); + return FALSE; + } + if (start >= MAX_COLORMAPS) + { + log_add (log_Warning, "ERROR: SetColorMap(): " + "starting map (%d) beyond range (0-%d)", + start, (int)MAX_COLORMAPS - 1); + return FALSE; + } + if (end >= MAX_COLORMAPS) + { + log_add (log_Warning, "SetColorMap(): " + "ending map (%d) beyond range (0-%d)\n", + end, (int)MAX_COLORMAPS - 1); + end = MAX_COLORMAPS - 1; + } + + total_size = end + 1; + + LockMutex (maplock); + + if (total_size > mapcount) + mapcount = total_size; + + // parse the supplied PLUTs into our colormaps + for (mpp = colormaps + start; start <= end; ++start, ++mpp) + { + int i; + TFB_ColorMap *newmap; + TFB_ColorMap *oldmap; + + oldmap = *mpp; + newmap = clone_colormap (oldmap, start); + + for (i = 0; i < NUMBER_OF_PLUTVALS; ++i, colors += PLUTVAL_BYTE_SIZE) + { + Color color; + + color.a = 0xff; + color.r = colors[PLUTVAL_RED]; + color.g = colors[PLUTVAL_GREEN]; + color.b = colors[PLUTVAL_BLUE]; + SetNativePaletteColor (newmap->palette, i, color); + } + + *mpp = newmap; + release_colormap (oldmap); + } + + UnlockMutex (maplock); + + return TRUE; +} + +/* Fade Transforms */ + +int +GetFadeAmount (void) +{ + int newAmount; + + LockMutex (fadeLock); + + if (fadeInterval) + { // have a pending fade + TimeCount Now = GetTimeCounter (); + sint32 elapsed; + + elapsed = Now - fadeStartTime; + if (elapsed > fadeInterval) + elapsed = fadeInterval; + + newAmount = fadeAmount + (long)fadeDelta * elapsed / fadeInterval; + + if (elapsed >= fadeInterval) + { // fade is over + fadeAmount = newAmount; + fadeInterval = 0; + } + } + else + { // no fade pending, return the current + newAmount = fadeAmount; + } + + UnlockMutex (fadeLock); + + return newAmount; +} + +static void +finishPendingFade (void) +{ + if (fadeInterval) + { // end the fade immediately + fadeAmount += fadeDelta; + fadeInterval = 0; + } +} + +static void +FlushFadeXForms (void) +{ + LockMutex (fadeLock); + finishPendingFade (); + UnlockMutex (fadeLock); +} + +DWORD +FadeScreen (ScreenFadeType fadeType, SIZE TimeInterval) +{ + TimeCount TimeOut; + int FadeEnd; + + switch (fadeType) + { + case FadeAllToBlack: + case FadeSomeToBlack: + FadeEnd = FADE_NO_INTENSITY; + break; + case FadeAllToColor: + case FadeSomeToColor: + FadeEnd = FADE_NORMAL_INTENSITY; + break; + case FadeAllToWhite: + case FadeSomeToWhite: + FadeEnd = FADE_FULL_INTENSITY; + break; + default: + return (GetTimeCounter ()); + } + + // Don't make users wait for fades + if (QuitPosted) + TimeInterval = 0; + + LockMutex (fadeLock); + + finishPendingFade (); + + if (TimeInterval <= 0) + { // end the fade immediately + fadeAmount = FadeEnd; + // cancel any pending fades + fadeInterval = 0; + TimeOut = GetTimeCounter (); + } + else + { + fadeInterval = TimeInterval; + fadeDelta = FadeEnd - fadeAmount; + fadeStartTime = GetTimeCounter (); + TimeOut = fadeStartTime + TimeInterval + 1; + } + + UnlockMutex (fadeLock); + + return TimeOut; +} + +/* Colormap Transforms */ + +static void +finish_colormap_xform (int which) +{ + SetColorMap (XFormControl.TaskControl[which].CMapPtr); + XFormControl.TaskControl[which].CMapIndex = -1; + // check Highest ptr + if (which == XFormControl.Highest) + { + do + --which; + while (which >= 0 && XFormControl.TaskControl[which].CMapIndex == -1); + + XFormControl.Highest = which; + } +} + +static inline BYTE +blendChan (BYTE c1, BYTE c2, int weight, int scale) +{ + return c1 + ((int)c2 - c1) * weight / scale; +} + +/* This gives the XFormColorMap task a timeslice to do its thing + * Only one thread should ever be allowed to be calling this at any time + */ +BOOLEAN +XFormColorMap_step (void) +{ + BOOLEAN Changed = FALSE; + int x; + DWORD Now = GetTimeCounter (); + + LockMutex (XFormControl.Lock); + + for (x = 0; x <= XFormControl.Highest; ++x) + { + XFORM_CONTROL *control = &XFormControl.TaskControl[x]; + int index = control->CMapIndex; + int TicksLeft = control->EndTime - Now; + TFB_ColorMap *curmap; + + if (index < 0) + continue; // unused slot + + LockMutex (maplock); + + curmap = colormaps[index]; + if (!curmap) + { + UnlockMutex (maplock); + log_add (log_Error, "BUG: XFormColorMap_step(): no current map"); + finish_colormap_xform (x); + continue; + } + + if (TicksLeft > 0) + { +#define XFORM_SCALE 0x10000 + TFB_ColorMap *newmap = NULL; + UBYTE *newClr; + Color *oldClr; + int frac; + int i; + + newmap = clone_colormap (curmap, index); + + oldClr = control->OldCMap; + newClr = (UBYTE*)control->CMapPtr + 2; + + frac = (int)(control->Ticks - TicksLeft) * XFORM_SCALE + / control->Ticks; + + for (i = 0; i < NUMBER_OF_PLUTVALS; ++i, ++oldClr, + newClr += PLUTVAL_BYTE_SIZE) + { + Color color; + + color.a = 0xff; + color.r = blendChan (oldClr->r, newClr[PLUTVAL_RED], + frac, XFORM_SCALE); + color.g = blendChan (oldClr->g, newClr[PLUTVAL_GREEN], + frac, XFORM_SCALE); + color.b = blendChan (oldClr->b, newClr[PLUTVAL_BLUE], + frac, XFORM_SCALE); + SetNativePaletteColor (newmap->palette, i, color); + } + + colormaps[index] = newmap; + release_colormap (curmap); + } + + UnlockMutex (maplock); + + if (TicksLeft <= 0) + { // asked for immediate xform or already done + finish_colormap_xform (x); + } + + Changed = TRUE; + } + + UnlockMutex (XFormControl.Lock); + + return Changed; +} + +static void +FlushPLUTXForms (void) +{ + int i; + + LockMutex (XFormControl.Lock); + + for (i = 0; i <= XFormControl.Highest; ++i) + { + if (XFormControl.TaskControl[i].CMapIndex >= 0) + finish_colormap_xform (i); + } + XFormControl.Highest = -1; // all gone + + UnlockMutex (XFormControl.Lock); +} + +static DWORD +XFormPLUT (COLORMAPPTR ColorMapPtr, SIZE TimeInterval) +{ + TFB_ColorMap *map; + XFORM_CONTROL *control; + int index; + int x; + int first_avail = -1; + DWORD EndTime; + DWORD Now; + + Now = GetTimeCounter (); + index = *(UBYTE*)ColorMapPtr; + + LockMutex (XFormControl.Lock); + // Find an available slot, or reuse if required + for (x = 0; x <= XFormControl.Highest + && index != XFormControl.TaskControl[x].CMapIndex; + ++x) + { + if (first_avail == -1 && XFormControl.TaskControl[x].CMapIndex == -1) + first_avail = x; + } + + if (index == XFormControl.TaskControl[x].CMapIndex) + { // already xforming this colormap -- cancel and reuse slot + finish_colormap_xform (x); + } + else if (first_avail >= 0) + { // picked up a slot along the way + x = first_avail; + } + else if (x >= MAX_XFORMS) + { // flush some xforms if the queue is full + log_add (log_Debug, "WARNING: XFormPLUT(): no slots available"); + x = XFormControl.Highest; + finish_colormap_xform (x); + } + // take next unused one + control = &XFormControl.TaskControl[x]; + if (x > XFormControl.Highest) + XFormControl.Highest = x; + + // make a copy of the current map + LockMutex (maplock); + map = colormaps[index]; + if (!map) + { + UnlockMutex (maplock); + UnlockMutex (XFormControl.Lock); + log_add (log_Warning, "BUG: XFormPLUT(): no current map"); + return (0); + } + GetColorMapColors (control->OldCMap, map); + UnlockMutex (maplock); + + control->CMapIndex = index; + control->CMapPtr = ColorMapPtr; + control->Ticks = TimeInterval; + if (control->Ticks < 0) + control->Ticks = 0; /* prevent negative fade */ + control->StartTime = Now; + control->EndTime = EndTime = Now + control->Ticks; + + UnlockMutex (XFormControl.Lock); + + return (EndTime); +} + +DWORD +XFormColorMap (COLORMAPPTR ColorMapPtr, SIZE TimeInterval) +{ + if (!ColorMapPtr) + return (0); + + // Don't make users wait for transforms + if (QuitPosted) + TimeInterval = 0; + + return XFormPLUT (ColorMapPtr, TimeInterval); +} + +void +FlushColorXForms (void) +{ + FlushFadeXForms (); + FlushPLUTXForms (); +} + +// The type conversions are implicit and will generate errors +// or warnings if types change imcompatibly +COLORMAPPTR +GetColorMapAddress (COLORMAP colormap) +{ + return GetStringAddress (colormap); +} diff --git a/src/libs/graphics/cmap.h b/src/libs/graphics/cmap.h new file mode 100644 index 0000000..f27f789 --- /dev/null +++ b/src/libs/graphics/cmap.h @@ -0,0 +1,77 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef CMAP_H +#define CMAP_H + +#include "libs/gfxlib.h" + +#define MAX_COLORMAPS 250 + +// These are pertinent to colortable file format +// We load colormaps as binary and parse them when needed +#define PLUTVAL_BYTE_SIZE 3 +// Channel order in colormap tables +#define PLUTVAL_RED 0 +#define PLUTVAL_GREEN 1 +#define PLUTVAL_BLUE 2 + +#define NUMBER_OF_PLUTVALS 256 +// Size of the colormap in a colortable file +#define PLUT_BYTE_SIZE (PLUTVAL_BYTE_SIZE * NUMBER_OF_PLUTVALS) + +#define FADE_NO_INTENSITY 0 +#define FADE_NORMAL_INTENSITY 255 +#define FADE_FULL_INTENSITY 510 + +typedef struct NativePalette NativePalette; + +typedef struct tfb_colormap +{ + int index; + // Colormap index as the game sees it + int version; + // Version goes up every time the colormap changes. This may + // be due to SetColorMap() or at every transformation step + // of XFormColorMap(). Paletted TFB_Images track the last + // colormap version they were drawn with for optimization. + int refcount; + struct tfb_colormap *next; + // for spares linking + NativePalette *palette; +} TFB_ColorMap; + +extern int GetFadeAmount (void); + +extern void InitColorMaps (void); +extern void UninitColorMaps (void); + +extern void GetColorMapColors (Color *colors, TFB_ColorMap *); + +extern TFB_ColorMap * TFB_GetColorMap (int index); +extern void TFB_ReturnColorMap (TFB_ColorMap *map); + +extern BOOLEAN XFormColorMap_step (void); + +// Native +NativePalette* AllocNativePalette (void); +void FreeNativePalette (NativePalette *); +void SetNativePaletteColor (NativePalette *, int index, Color); +Color GetNativePaletteColor (NativePalette *, int index); + +#endif /* CMAP_H */ diff --git a/src/libs/graphics/context.c b/src/libs/graphics/context.c new file mode 100644 index 0000000..d609ded --- /dev/null +++ b/src/libs/graphics/context.c @@ -0,0 +1,404 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "gfxintrn.h" + +GRAPHICS_STATUS _GraphicsStatusFlags; +CONTEXT _pCurContext; + +#ifdef DEBUG +// We keep track of all contexts +CONTEXT firstContext; + // The first one in the list. +CONTEXT *contextEnd = &firstContext; + // Where to put the next context. +#endif + +PRIMITIVE _locPrim; + +FONT _CurFontPtr; + +#define DEFAULT_FORE_COLOR BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x1F), 0x0F) +#define DEFAULT_BACK_COLOR BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x00), 0x00) + +#define DEFAULT_DRAW_MODE MAKE_DRAW_MODE (DRAW_DEFAULT, 255) + +CONTEXT +SetContext (CONTEXT Context) +{ + CONTEXT LastContext; + + LastContext = _pCurContext; + if (Context != LastContext) + { + if (LastContext) + { + UnsetContextFlags ( + MAKE_WORD (0, GRAPHICS_ACTIVE | DRAWABLE_ACTIVE)); + SetContextFlags ( + MAKE_WORD (0, _GraphicsStatusFlags + & (GRAPHICS_ACTIVE | DRAWABLE_ACTIVE))); + + DeactivateContext (); + } + + _pCurContext = Context; + if (_pCurContext) + { + ActivateContext (); + + _GraphicsStatusFlags &= ~(GRAPHICS_ACTIVE | DRAWABLE_ACTIVE); + _GraphicsStatusFlags |= HIBYTE (_get_context_flags ()); + + SetPrimColor (&_locPrim, _get_context_fg_color ()); + + _CurFramePtr = _get_context_fg_frame (); + _CurFontPtr = _get_context_font (); + } + } + + return (LastContext); +} + +#ifdef DEBUG +CONTEXT +CreateContextAux (const char *name) +#else /* if !defined(DEBUG) */ +CONTEXT +CreateContextAux (void) +#endif /* !defined(DEBUG) */ +{ + CONTEXT NewContext; + + NewContext = AllocContext (); + if (NewContext) + { + /* initialize context */ +#ifdef DEBUG + NewContext->name = name; + NewContext->next = NULL; + *contextEnd = NewContext; + contextEnd = &NewContext->next; +#endif /* DEBUG */ + + NewContext->Mode = DEFAULT_DRAW_MODE; + NewContext->ForeGroundColor = DEFAULT_FORE_COLOR; + NewContext->BackGroundColor = DEFAULT_BACK_COLOR; + } + + return NewContext; +} + +#ifdef DEBUG +// Loop through the list of context to the pointer which points to the +// specified context. This is either 'firstContext' or the address of +// the 'next' field of some other context. +static CONTEXT * +FindContextPtr (CONTEXT context) { + CONTEXT *ptr; + + for (ptr = &firstContext; *ptr != NULL; ptr = &(*ptr)->next) { + if (*ptr == context) + break; + } + return ptr; +} +#endif /* DEBUG */ + +BOOLEAN +DestroyContext (CONTEXT ContextRef) +{ + TFB_Image *img; + + if (ContextRef == 0) + return (FALSE); + + if (_pCurContext && _pCurContext == ContextRef) + SetContext ((CONTEXT)0); + +#ifdef DEBUG + // Unlink the context. + { + CONTEXT *contextPtr = FindContextPtr (ContextRef); + if (contextEnd == &ContextRef->next) + contextEnd = contextPtr; + *contextPtr = ContextRef->next; + } +#endif /* DEBUG */ + + img = ContextRef->FontBacking; + if (img) + TFB_DrawImage_Delete (img); + + FreeContext (ContextRef); + return TRUE; +} + +Color +SetContextForeGroundColor (Color color) +{ + Color oldColor; + + if (!ContextActive ()) + return DEFAULT_FORE_COLOR; + + oldColor = _get_context_fg_color (); + if (!sameColor(oldColor, color)) + { + SwitchContextForeGroundColor (color); + + if (!(_get_context_fbk_flags () & FBK_IMAGE)) + { + SetContextFBkFlags (FBK_DIRTY); + } + } + SetPrimColor (&_locPrim, color); + + return (oldColor); +} + +Color +GetContextForeGroundColor (void) +{ + if (!ContextActive ()) + return DEFAULT_FORE_COLOR; + + return _get_context_fg_color (); +} + +Color +SetContextBackGroundColor (Color color) +{ + Color oldColor; + + if (!ContextActive ()) + return DEFAULT_BACK_COLOR; + + oldColor = _get_context_bg_color (); + if (!sameColor(oldColor, color)) + SwitchContextBackGroundColor (color); + + return oldColor; +} + +Color +GetContextBackGroundColor (void) +{ + if (!ContextActive ()) + return DEFAULT_BACK_COLOR; + + return _get_context_bg_color (); +} + +DrawMode +SetContextDrawMode (DrawMode mode) +{ + DrawMode oldMode; + + if (!ContextActive ()) + return DEFAULT_DRAW_MODE; + + oldMode = _get_context_draw_mode (); + SwitchContextDrawMode (mode); + + return oldMode; +} + +DrawMode +GetContextDrawMode (void) +{ + if (!ContextActive ()) + return DEFAULT_DRAW_MODE; + + return _get_context_draw_mode (); +} + +// Returns a rect based at 0,0 and the size of context foreground frame +static inline RECT +_get_context_fg_rect (void) +{ + RECT r = { {0, 0}, {0, 0} }; + if (_CurFramePtr) + r.extent = GetFrameBounds (_CurFramePtr); + return r; +} + +BOOLEAN +SetContextClipRect (RECT *lpRect) +{ + if (!ContextActive ()) + return (FALSE); + + if (lpRect) + { + if (rectsEqual (*lpRect, _get_context_fg_rect ())) + { // Cliprect is undefined to mirror GetContextClipRect() + _pCurContext->ClipRect.extent.width = 0; + } + else + { // We have a cliprect + _pCurContext->ClipRect = *lpRect; + } + } + else + { // Set cliprect as undefined + _pCurContext->ClipRect.extent.width = 0; + } + + return TRUE; +} + +BOOLEAN +GetContextClipRect (RECT *lpRect) +{ + if (!ContextActive ()) + return (FALSE); + + *lpRect = _pCurContext->ClipRect; + if (!_pCurContext->ClipRect.extent.width) + { // Though the cliprect is undefined, drawing will be clipped + // to the extent of the foreground frame + *lpRect = _get_context_fg_rect (); + } + + return (_pCurContext->ClipRect.extent.width != 0); +} + +POINT +SetContextOrigin (POINT orgOffset) +{ + // XXX: This is a hack, kind of. But that's what the original did. + return SetFrameHot (_CurFramePtr, orgOffset); +} + +FRAME +SetContextFontEffect (FRAME EffectFrame) +{ + FRAME LastEffect; + + if (!ContextActive ()) + return (NULL); + + LastEffect = _get_context_fonteff (); + if (EffectFrame != LastEffect) + { + SwitchContextFontEffect (EffectFrame); + + if (EffectFrame != 0) + { + SetContextFBkFlags (FBK_IMAGE); + } + else + { + UnsetContextFBkFlags (FBK_IMAGE); + } + } + + return LastEffect; +} + +void +FixContextFontEffect (void) +{ + SIZE w, h; + TFB_Image* img; + + if (!ContextActive () || (_get_context_font_backing () != 0 + && !(_get_context_fbk_flags () & FBK_DIRTY))) + return; + + if (!GetContextFontLeading (&h) || !GetContextFontLeadingWidth (&w)) + return; + + img = _pCurContext->FontBacking; + if (img) + TFB_DrawScreen_DeleteImage (img); + + img = TFB_DrawImage_CreateForScreen (w, h, TRUE); + if (_get_context_fbk_flags () & FBK_IMAGE) + { // image pattern backing + FRAME EffectFrame = _get_context_fonteff (); + + TFB_DrawImage_Image (EffectFrame->image, + -EffectFrame->HotSpot.x, -EffectFrame->HotSpot.y, + 0, 0, NULL, DRAW_REPLACE_MODE, img); + } + else + { // solid color backing + RECT r = { {0, 0}, {w, h} }; + Color color = _get_context_fg_color (); + + TFB_DrawImage_Rect (&r, color, DRAW_REPLACE_MODE, img); + } + + _pCurContext->FontBacking = img; + UnsetContextFBkFlags (FBK_DIRTY); +} + +// 'area' may be NULL to copy the entire CONTEXT cliprect +// 'area' is relative to the CONTEXT cliprect +DRAWABLE +CopyContextRect (const RECT* area) +{ + RECT clipRect; + RECT fgRect; + RECT r; + + if (!ContextActive () || !_CurFramePtr) + return NULL; + + fgRect = _get_context_fg_rect (); + GetContextClipRect (&clipRect); + r = clipRect; + if (area) + { // a portion of the context + r.corner.x += area->corner.x; + r.corner.y += area->corner.y; + r.extent = area->extent; + } + // TODO: Should this take CONTEXT origin into account too? + // validate the rect + if (!BoxIntersect (&r, &fgRect, &r)) + return NULL; + + if (_CurFramePtr->Type == SCREEN_DRAWABLE) + return LoadDisplayPixmap (&r, NULL); + else + return CopyFrameRect (_CurFramePtr, &r); +} + +#ifdef DEBUG +const char * +GetContextName (CONTEXT context) +{ + return context->name; +} + +CONTEXT +GetFirstContext (void) +{ + return firstContext; +} + +CONTEXT +GetNextContext (CONTEXT context) +{ + return context->next; +} +#endif /* DEBUG */ + diff --git a/src/libs/graphics/context.h b/src/libs/graphics/context.h new file mode 100644 index 0000000..09b50cf --- /dev/null +++ b/src/libs/graphics/context.h @@ -0,0 +1,147 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_GRAPHICS_CONTEXT_H_ +#define LIBS_GRAPHICS_CONTEXT_H_ + +#include "tfb_draw.h" +#include "libs/memlib.h" + +typedef UWORD FBK_FLAGS; +#define FBK_DIRTY (1 << 0) +#define FBK_IMAGE (1 << 1) + +struct context_desc +{ + UWORD Flags; + // Low nibble currently unused + // High nibble contains GRAPHICS_STATUS + + Color ForeGroundColor, BackGroundColor; + DrawMode Mode; + FRAME ForeGroundFrame; + FONT Font; + + RECT ClipRect; + + FRAME FontEffect; + TFB_Image *FontBacking; + FBK_FLAGS BackingFlags; + +#ifdef DEBUG + const char *name; + CONTEXT next; +#endif +}; + +#define AllocContext() HCalloc (sizeof (CONTEXT_DESC)) +#define FreeContext HFree + +extern CONTEXT _pCurContext; +extern PRIMITIVE _locPrim; + +#define _get_context_fg_color() (_pCurContext->ForeGroundColor) +#define _get_context_bg_color() (_pCurContext->BackGroundColor) +#define _get_context_flags() (_pCurContext->Flags) +#define _get_context_fg_frame() (_pCurContext->ForeGroundFrame) +#define _get_context_font() (_pCurContext->Font) +#define _get_context_fbk_flags() (_pCurContext->BackingFlags) +#define _get_context_fonteff() (_pCurContext->FontEffect) +#define _get_context_font_backing() (_pCurContext->FontBacking) +#define _get_context_draw_mode() (_pCurContext->Mode) + +#define SwitchContextDrawMode(m) \ +{ \ + _pCurContext->Mode = (m); \ +} +#define SwitchContextForeGroundColor(c) \ +{ \ + _pCurContext->ForeGroundColor = (c); \ +} +#define SwitchContextBackGroundColor(c) \ +{ \ + _pCurContext->BackGroundColor = (c); \ +} +#define SetContextFlags(f) \ +{ \ + _pCurContext->Flags |= (f); \ +} +#define UnsetContextFlags(f) \ +{ \ + _pCurContext->Flags &= ~(f); \ +} +#define SwitchContextFGFrame(f) \ +{ \ + _pCurContext->ForeGroundFrame = (f); \ +} +#define SwitchContextFont(f) \ +{ \ + _pCurContext->Font = (f); \ + SetContextFBkFlags (FBK_DIRTY); \ +} +#define SwitchContextBGFunc(f) \ +{ \ + _pCurContext->BackGroundFunc = (f); \ +} +#define SetContextFBkFlags(f) \ +{ \ + _pCurContext->BackingFlags |= (f); \ +} +#define UnsetContextFBkFlags(f) \ +{ \ + _pCurContext->BackingFlags &= ~(f); \ +} +#define SwitchContextFontEffect(f) \ +{ \ + _pCurContext->FontEffect = (f); \ + SetContextFBkFlags (FBK_DIRTY); \ +} + +typedef BYTE GRAPHICS_STATUS; + +extern GRAPHICS_STATUS _GraphicsStatusFlags; +#define GRAPHICS_ACTIVE (GRAPHICS_STATUS)(1 << 0) +#define GRAPHICS_VISIBLE (GRAPHICS_STATUS)(1 << 1) +#define CONTEXT_ACTIVE (GRAPHICS_STATUS)(1 << 2) +#define DRAWABLE_ACTIVE (GRAPHICS_STATUS)(1 << 3) +#define DeactivateGraphics() (_GraphicsStatusFlags &= ~GRAPHICS_ACTIVE) +#define ActivateGraphics() (_GraphicsStatusFlags |= GRAPHICS_ACTIVE) +#define GraphicsActive() (_GraphicsStatusFlags & GRAPHICS_ACTIVE) +#define DeactivateVisible() (_GraphicsStatusFlags &= ~GRAPHICS_VISIBLE) +#define ActivateVisible() (_GraphicsStatusFlags |= GRAPHICS_VISIBLE) +#define DeactivateContext() (_GraphicsStatusFlags &= ~CONTEXT_ACTIVE) +#define ActivateContext() (_GraphicsStatusFlags |= CONTEXT_ACTIVE) +#define ContextActive() (_GraphicsStatusFlags & CONTEXT_ACTIVE) +#define DeactivateDrawable() (_GraphicsStatusFlags &= ~DRAWABLE_ACTIVE) +#define ActivateDrawable() (_GraphicsStatusFlags |= DRAWABLE_ACTIVE) +#define DrawableActive() (_GraphicsStatusFlags & DRAWABLE_ACTIVE) + +#define SYSTEM_ACTIVE (GRAPHICS_STATUS)(CONTEXT_ACTIVE | DRAWABLE_ACTIVE) + +#define GraphicsSystemActive() \ + ((_GraphicsStatusFlags & SYSTEM_ACTIVE) == SYSTEM_ACTIVE) +#define GraphicsStatus() \ + (_GraphicsStatusFlags & (GRAPHICS_STATUS)(GRAPHICS_ACTIVE \ + | GRAPHICS_VISIBLE)) + +// pValidRect or origin may be NULL +BOOLEAN GetContextValidRect (RECT *pValidRect, POINT *origin); +extern void FixContextFontEffect (void); + +#endif /* LIBS_GRAPHICS_CONTEXT_H_ */ + diff --git a/src/libs/graphics/dcqueue.c b/src/libs/graphics/dcqueue.c new file mode 100644 index 0000000..70c0662 --- /dev/null +++ b/src/libs/graphics/dcqueue.c @@ -0,0 +1,670 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "port.h" +#include "libs/threadlib.h" +#include "libs/graphics/drawcmd.h" +#include "libs/graphics/drawable.h" +#include "libs/graphics/context.h" +#include "libs/graphics/dcqueue.h" +#include "libs/graphics/gfx_common.h" +#include "libs/graphics/bbox.h" +#include "libs/timelib.h" +#include "libs/log.h" +#include "libs/misc.h" + // for TFB_DEBUG_HALT + + +static RecursiveMutex DCQ_Mutex; + +CondVar RenderingCond; + +TFB_DrawCommand DCQ[DCQ_MAX]; + +TFB_DrawCommandQueue DrawCommandQueue; + +#define FPS_PERIOD (ONE_SECOND / 100) +int RenderedFrames = 0; + + +// Wait for the queue to be emptied. +static void +TFB_WaitForSpace (int requested_slots) +{ + int old_depth, i; + log_add (log_Debug, "DCQ overload (Size = %d, FullSize = %d, " + "Requested = %d). Sleeping until renderer is done.", + DrawCommandQueue.Size, DrawCommandQueue.FullSize, + requested_slots); + // Restore the DCQ locking level. I *think* this is + // always 1, but... + TFB_BatchReset (); + old_depth = GetRecursiveMutexDepth (DCQ_Mutex); + for (i = 0; i < old_depth; i++) + UnlockRecursiveMutex (DCQ_Mutex); + WaitCondVar (RenderingCond); + for (i = 0; i < old_depth; i++) + LockRecursiveMutex (DCQ_Mutex); + log_add (log_Debug, "DCQ clear (Size = %d, FullSize = %d). Continuing.", + DrawCommandQueue.Size, DrawCommandQueue.FullSize); +} + +void +Lock_DCQ (int slots) +{ + LockRecursiveMutex (DCQ_Mutex); + while (DrawCommandQueue.FullSize >= DCQ_MAX - slots) + { + TFB_WaitForSpace (slots); + } +} + +void +Unlock_DCQ (void) +{ + UnlockRecursiveMutex (DCQ_Mutex); +} + +// Always have the DCQ locked when calling this. +static void +Synchronize_DCQ (void) +{ + if (!DrawCommandQueue.Batching) + { + int front = DrawCommandQueue.Front; + int back = DrawCommandQueue.InsertionPoint; + DrawCommandQueue.Back = DrawCommandQueue.InsertionPoint; + if (front <= back) + { + DrawCommandQueue.Size = (back - front); + } + else + { + DrawCommandQueue.Size = (back + DCQ_MAX - front); + } + DrawCommandQueue.FullSize = DrawCommandQueue.Size; + } +} + +void +TFB_BatchGraphics (void) +{ + LockRecursiveMutex (DCQ_Mutex); + DrawCommandQueue.Batching++; + UnlockRecursiveMutex (DCQ_Mutex); +} + +void +TFB_UnbatchGraphics (void) +{ + LockRecursiveMutex (DCQ_Mutex); + if (DrawCommandQueue.Batching) + { + DrawCommandQueue.Batching--; + } + Synchronize_DCQ (); + UnlockRecursiveMutex (DCQ_Mutex); +} + +// Cancel all pending batch operations, making them unbatched. This will +// cause a small amount of flicker when invoked, but prevents +// batching problems from freezing the game. +void +TFB_BatchReset (void) +{ + LockRecursiveMutex (DCQ_Mutex); + DrawCommandQueue.Batching = 0; + Synchronize_DCQ (); + UnlockRecursiveMutex (DCQ_Mutex); +} + + +// Draw Command Queue Stuff + +void +Init_DrawCommandQueue (void) +{ + DrawCommandQueue.Back = 0; + DrawCommandQueue.Front = 0; + DrawCommandQueue.InsertionPoint = 0; + DrawCommandQueue.Batching = 0; + DrawCommandQueue.FullSize = 0; + DrawCommandQueue.Size = 0; + + TFB_BBox_Init (ScreenWidth, ScreenHeight); + + DCQ_Mutex = CreateRecursiveMutex ("DCQ", + SYNC_CLASS_TOPLEVEL | SYNC_CLASS_VIDEO); + + RenderingCond = CreateCondVar ("DCQ empty", + SYNC_CLASS_TOPLEVEL | SYNC_CLASS_VIDEO); +} + +void +Uninit_DrawCommandQueue (void) +{ + if (RenderingCond) + { + DestroyCondVar (RenderingCond); + RenderingCond = 0; + } + + if (DCQ_Mutex) + { + DestroyRecursiveMutex (DCQ_Mutex); + DCQ_Mutex = 0; + } +} + +void +TFB_DrawCommandQueue_Push (TFB_DrawCommand* Command) +{ + Lock_DCQ (1); + DCQ[DrawCommandQueue.InsertionPoint] = *Command; + DrawCommandQueue.InsertionPoint = (DrawCommandQueue.InsertionPoint + 1) + % DCQ_MAX; + DrawCommandQueue.FullSize++; + Synchronize_DCQ (); + Unlock_DCQ (); +} + +int +TFB_DrawCommandQueue_Pop (TFB_DrawCommand *target) +{ + LockRecursiveMutex (DCQ_Mutex); + + if (DrawCommandQueue.Size == 0) + { + Unlock_DCQ (); + return (0); + } + + if (DrawCommandQueue.Front == DrawCommandQueue.Back && + DrawCommandQueue.Size != DCQ_MAX) + { + log_add (log_Debug, "Augh! Assertion failure in DCQ! " + "Front == Back, Size != DCQ_MAX"); + DrawCommandQueue.Size = 0; + Unlock_DCQ (); + return (0); + } + + *target = DCQ[DrawCommandQueue.Front]; + DrawCommandQueue.Front = (DrawCommandQueue.Front + 1) % DCQ_MAX; + + DrawCommandQueue.Size--; + DrawCommandQueue.FullSize--; + UnlockRecursiveMutex (DCQ_Mutex); + + return 1; +} + +void +TFB_DrawCommandQueue_Clear () +{ + LockRecursiveMutex (DCQ_Mutex); + DrawCommandQueue.Size = 0; + DrawCommandQueue.Front = 0; + DrawCommandQueue.Back = 0; + DrawCommandQueue.Batching = 0; + DrawCommandQueue.FullSize = 0; + DrawCommandQueue.InsertionPoint = 0; + UnlockRecursiveMutex (DCQ_Mutex); +} + +static void +checkExclusiveThread (TFB_DrawCommand* DrawCommand) +{ +#ifdef DEBUG_DCQ_THREADS + static uint32 exclusiveThreadId; + extern uint32 SDL_ThreadID(void); + + // Only one thread is currently allowed to enqueue commands + // This is not a technical limitation but rather a semantical one atm. + if (DrawCommand->Type == TFB_DRAWCOMMANDTYPE_REINITVIDEO) + { // TFB_DRAWCOMMANDTYPE_REINITVIDEO is an exception + // It is queued from the main() thread, which is safe to do + return; + } + + if (!exclusiveThreadId) + exclusiveThreadId = SDL_ThreadID(); + else + assert (SDL_ThreadID() == exclusiveThreadId); +#else + (void) DrawCommand; // suppress unused warning +#endif +} + +void +TFB_EnqueueDrawCommand (TFB_DrawCommand* DrawCommand) +{ + if (TFB_DEBUG_HALT) + { + return; + } + + checkExclusiveThread (DrawCommand); + + if (DrawCommand->Type <= TFB_DRAWCOMMANDTYPE_COPYTOIMAGE + && _CurFramePtr->Type == SCREEN_DRAWABLE) + { + static RECT scissor_rect; + + // Set the clipping region. + // We allow drawing with no current context set, so the whole screen + if ((_pCurContext && !rectsEqual (scissor_rect, _pCurContext->ClipRect)) + || (!_pCurContext && scissor_rect.extent.width != 0)) + { + // Enqueue command to set the glScissor spec + TFB_DrawCommand DC; + + if (_pCurContext) + scissor_rect = _pCurContext->ClipRect; + else + scissor_rect.extent.width = 0; + + if (scissor_rect.extent.width) + { + DC.Type = TFB_DRAWCOMMANDTYPE_SCISSORENABLE; + DC.data.scissor.rect = scissor_rect; + } + else + { + DC.Type = TFB_DRAWCOMMANDTYPE_SCISSORDISABLE; + } + + TFB_EnqueueDrawCommand(&DC); + } + } + + TFB_DrawCommandQueue_Push (DrawCommand); +} + +static void +computeFPS (void) +{ + static TimeCount last_time; + static TimePeriod fps_counter; + TimeCount current_time; + TimePeriod delta_time; + + current_time = GetTimeCounter (); + delta_time = current_time - last_time; + last_time = current_time; + + fps_counter += delta_time; + if (fps_counter > FPS_PERIOD) + { + log_add (log_User, "fps %.2f, effective %.2f", + (float)ONE_SECOND / delta_time, + (float)ONE_SECOND * RenderedFrames / fps_counter); + + fps_counter = 0; + RenderedFrames = 0; + } +} + +// Only call from main() thread!! +void +TFB_FlushGraphics (void) +{ + int commands_handled; + BOOLEAN livelock_deterrence; + + // This is technically a locking violation on DrawCommandQueue.Size, + // but it is likely to not be very destructive. + if (DrawCommandQueue.Size == 0) + { + static int last_fade = 255; + static int last_transition = 255; + int current_fade = GetFadeAmount (); + int current_transition = TransitionAmount; + + if ((current_fade != 255 && current_fade != last_fade) || + (current_transition != 255 && + current_transition != last_transition) || + (current_fade == 255 && last_fade != 255) || + (current_transition == 255 && last_transition != 255)) + { + TFB_SwapBuffers (TFB_REDRAW_FADING); + // if fading, redraw every frame + } + else + { + TaskSwitch (); + } + + last_fade = current_fade; + last_transition = current_transition; + BroadcastCondVar (RenderingCond); + return; + } + + if (GfxFlags & TFB_GFXFLAGS_SHOWFPS) + computeFPS (); + + commands_handled = 0; + livelock_deterrence = FALSE; + + if (DrawCommandQueue.FullSize > DCQ_FORCE_BREAK_SIZE) + { + TFB_BatchReset (); + } + + if (DrawCommandQueue.Size > DCQ_FORCE_SLOWDOWN_SIZE) + { + Lock_DCQ (-1); + livelock_deterrence = TRUE; + } + + TFB_BBox_Reset (); + + for (;;) + { + TFB_DrawCommand DC; + + if (!TFB_DrawCommandQueue_Pop (&DC)) + { + // the Queue is now empty. + break; + } + + ++commands_handled; + if (!livelock_deterrence && commands_handled + DrawCommandQueue.Size + > DCQ_LIVELOCK_MAX) + { + // log_add (log_Debug, "Initiating livelock deterrence!"); + livelock_deterrence = TRUE; + + Lock_DCQ (-1); + } + + switch (DC.Type) + { + case TFB_DRAWCOMMANDTYPE_SETMIPMAP: + { + TFB_DrawCommand_SetMipmap *cmd = &DC.data.setmipmap; + TFB_DrawImage_SetMipmap (cmd->image, cmd->mipmap, + cmd->hotx, cmd->hoty); + break; + } + + case TFB_DRAWCOMMANDTYPE_IMAGE: + { + TFB_DrawCommand_Image *cmd = &DC.data.image; + TFB_Image *DC_image = cmd->image; + const int x = cmd->x; + const int y = cmd->y; + + TFB_DrawCanvas_Image (DC_image, x, y, + cmd->scale, cmd->scaleMode, cmd->colormap, + cmd->drawMode, + TFB_GetScreenCanvas (cmd->destBuffer)); + + if (cmd->destBuffer == TFB_SCREEN_MAIN) + { + LockMutex (DC_image->mutex); + if (cmd->scale) + TFB_BBox_RegisterCanvas (DC_image->ScaledImg, + x - DC_image->last_scale_hs.x, + y - DC_image->last_scale_hs.y); + else + TFB_BBox_RegisterCanvas (DC_image->NormalImg, + x - DC_image->NormalHs.x, + y - DC_image->NormalHs.y); + UnlockMutex (DC_image->mutex); + } + + break; + } + + case TFB_DRAWCOMMANDTYPE_FILLEDIMAGE: + { + TFB_DrawCommand_FilledImage *cmd = &DC.data.filledimage; + TFB_Image *DC_image = cmd->image; + const int x = cmd->x; + const int y = cmd->y; + + TFB_DrawCanvas_FilledImage (DC_image, x, y, + cmd->scale, cmd->scaleMode, cmd->color, + cmd->drawMode, + TFB_GetScreenCanvas (cmd->destBuffer)); + + if (cmd->destBuffer == TFB_SCREEN_MAIN) + { + LockMutex (DC_image->mutex); + if (cmd->scale) + TFB_BBox_RegisterCanvas (DC_image->ScaledImg, + x - DC_image->last_scale_hs.x, + y - DC_image->last_scale_hs.y); + else + TFB_BBox_RegisterCanvas (DC_image->NormalImg, + x - DC_image->NormalHs.x, + y - DC_image->NormalHs.y); + UnlockMutex (DC_image->mutex); + } + + break; + } + + case TFB_DRAWCOMMANDTYPE_FONTCHAR: + { + TFB_DrawCommand_FontChar *cmd = &DC.data.fontchar; + TFB_Char *DC_char = cmd->fontchar; + const int x = cmd->x; + const int y = cmd->y; + + TFB_DrawCanvas_FontChar (DC_char, cmd->backing, x, y, + cmd->drawMode, TFB_GetScreenCanvas (cmd->destBuffer)); + + if (cmd->destBuffer == TFB_SCREEN_MAIN) + { + RECT r; + + r.corner.x = x - DC_char->HotSpot.x; + r.corner.y = y - DC_char->HotSpot.y; + r.extent.width = DC_char->extent.width; + r.extent.height = DC_char->extent.height; + + TFB_BBox_RegisterRect (&r); + } + + break; + } + + case TFB_DRAWCOMMANDTYPE_LINE: + { + TFB_DrawCommand_Line *cmd = &DC.data.line; + + if (cmd->destBuffer == TFB_SCREEN_MAIN) + { + TFB_BBox_RegisterPoint (cmd->x1, cmd->y1); + TFB_BBox_RegisterPoint (cmd->x2, cmd->y2); + } + TFB_DrawCanvas_Line (cmd->x1, cmd->y1, cmd->x2, cmd->y2, + cmd->color, cmd->drawMode, + TFB_GetScreenCanvas (cmd->destBuffer)); + break; + } + + case TFB_DRAWCOMMANDTYPE_RECTANGLE: + { + TFB_DrawCommand_Rect *cmd = &DC.data.rect; + + if (cmd->destBuffer == TFB_SCREEN_MAIN) + TFB_BBox_RegisterRect (&cmd->rect); + TFB_DrawCanvas_Rect (&cmd->rect, cmd->color, cmd->drawMode, + TFB_GetScreenCanvas (cmd->destBuffer)); + + break; + } + + case TFB_DRAWCOMMANDTYPE_SCISSORENABLE: + { + TFB_DrawCommand_Scissor *cmd = &DC.data.scissor; + + TFB_DrawCanvas_SetClipRect ( + TFB_GetScreenCanvas (TFB_SCREEN_MAIN), &cmd->rect); + TFB_BBox_SetClipRect (&DC.data.scissor.rect); + break; + } + + case TFB_DRAWCOMMANDTYPE_SCISSORDISABLE: + TFB_DrawCanvas_SetClipRect ( + TFB_GetScreenCanvas (TFB_SCREEN_MAIN), NULL); + TFB_BBox_SetClipRect (NULL); + break; + + case TFB_DRAWCOMMANDTYPE_COPYTOIMAGE: + { + TFB_DrawCommand_CopyToImage *cmd = &DC.data.copytoimage; + TFB_Image *DC_image = cmd->image; + const POINT dstPt = {0, 0}; + + if (DC_image == 0) + { + log_add (log_Debug, "DCQ ERROR: COPYTOIMAGE passed null " + "image ptr"); + break; + } + LockMutex (DC_image->mutex); + TFB_DrawCanvas_CopyRect ( + TFB_GetScreenCanvas (cmd->srcBuffer), &cmd->rect, + DC_image->NormalImg, dstPt); + UnlockMutex (DC_image->mutex); + break; + } + + case TFB_DRAWCOMMANDTYPE_COPY: + { + TFB_DrawCommand_Copy *cmd = &DC.data.copy; + const RECT r = cmd->rect; + + if (cmd->destBuffer == TFB_SCREEN_MAIN) + TFB_BBox_RegisterRect (&cmd->rect); + + TFB_DrawCanvas_CopyRect ( + TFB_GetScreenCanvas (cmd->srcBuffer), &r, + TFB_GetScreenCanvas (cmd->destBuffer), r.corner); + break; + } + + case TFB_DRAWCOMMANDTYPE_DELETEIMAGE: + { + TFB_Image *DC_image = DC.data.deleteimage.image; + TFB_DrawImage_Delete (DC_image); + break; + } + + case TFB_DRAWCOMMANDTYPE_DELETEDATA: + { + void *data = DC.data.deletedata.data; + HFree (data); + break; + } + + case TFB_DRAWCOMMANDTYPE_SENDSIGNAL: + ClearSemaphore (DC.data.sendsignal.sem); + break; + + case TFB_DRAWCOMMANDTYPE_REINITVIDEO: + { + TFB_DrawCommand_ReinitVideo *cmd = &DC.data.reinitvideo; + int oldDriver = GraphicsDriver; + int oldFlags = GfxFlags; + int oldWidth = ScreenWidthActual; + int oldHeight = ScreenHeightActual; + if (TFB_ReInitGraphics (cmd->driver, cmd->flags, + cmd->width, cmd->height)) + { + log_add (log_Error, "Could not provide requested mode: " + "reverting to last known driver."); + // We don't know what exactly failed, so roll it all back + if (TFB_ReInitGraphics (oldDriver, oldFlags, + oldWidth, oldHeight)) + { + log_add (log_Fatal, + "Couldn't reinit at that point either. " + "Your video has been somehow tied in knots."); + exit (EXIT_FAILURE); + } + } + TFB_SwapBuffers (TFB_REDRAW_YES); + break; + } + + case TFB_DRAWCOMMANDTYPE_CALLBACK: + { + DC.data.callback.callback (DC.data.callback.arg); + break; + } + } + } + + if (livelock_deterrence) + Unlock_DCQ (); + + TFB_SwapBuffers (TFB_REDRAW_NO); + RenderedFrames++; + BroadcastCondVar (RenderingCond); +} + +void +TFB_PurgeDanglingGraphics (void) +{ + Lock_DCQ (-1); + + for (;;) + { + TFB_DrawCommand DC; + + if (!TFB_DrawCommandQueue_Pop (&DC)) + { + // the Queue is now empty. + break; + } + + switch (DC.Type) + { + case TFB_DRAWCOMMANDTYPE_DELETEIMAGE: + { + TFB_Image *DC_image = DC.data.deleteimage.image; + TFB_DrawImage_Delete (DC_image); + break; + } + case TFB_DRAWCOMMANDTYPE_DELETEDATA: + { + void *data = DC.data.deletedata.data; + HFree (data); + break; + } + case TFB_DRAWCOMMANDTYPE_IMAGE: + { + TFB_ColorMap *cmap = DC.data.image.colormap; + if (cmap) + TFB_ReturnColorMap (cmap); + break; + } + case TFB_DRAWCOMMANDTYPE_SENDSIGNAL: + { + ClearSemaphore (DC.data.sendsignal.sem); + break; + } + } + } + Unlock_DCQ (); +} diff --git a/src/libs/graphics/dcqueue.h b/src/libs/graphics/dcqueue.h new file mode 100644 index 0000000..deed685 --- /dev/null +++ b/src/libs/graphics/dcqueue.h @@ -0,0 +1,55 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef DCQUEUE_H +#define DCQUEUE_H + +// Maximum size of the DCQ. The larger the DCQ, the larger frameskips +// become tolerable before initiating livelock deterrence and game +// slowdown. Other constants for controlling the frameskip/slowdown +// balance may be found in sdl_common.c near TFB_FlushGraphics. + +// Livelock deterrance constants. Because the entire screen is rarely +// refreshed, we may not drop draw commands on the floor with abandon. +// Furthermore, if the main program is queuing commands at a speed +// comparable to our processing of the commands, we never finish and +// the game freezes. Thus, if the queue starts out larger than +// DCQ_FORCE_SLOWDOWN_SIZE, or DCQ_LIVELOCK_MAX commands find +// themselves being processed in one go, livelock deterrence is +// enabled, and TFB_FlushGraphics locks the DCQ until it has processed +// all entries. If batched but pending commands exceed DCQ_FORCE_BREAK_SIZE, +// a continuity break is performed. This will effectively slow down the +// game logic, a fate we seek to avoid - however, it seems to be unavoidable +// on slower machines. Even there, it's seems nonexistent outside of +// communications screens. --Michael + +#ifdef DCQ_OF_DOOM +#define DCQ_MAX 512 +#define DCQ_FORCE_SLOWDOWN_SIZE 128 +#define DCQ_FORCE_BREAK_SIZE 512 +#define DCQ_LIVELOCK_MAX 256 +#else +#define DCQ_MAX 16384 +#define DCQ_FORCE_SLOWDOWN_SIZE 4096 +#define DCQ_FORCE_BREAK_SIZE 16384 +#define DCQ_LIVELOCK_MAX 4096 +#endif + +extern CondVar RenderingCond; + +#endif + + diff --git a/src/libs/graphics/drawable.c b/src/libs/graphics/drawable.c new file mode 100644 index 0000000..9766bc7 --- /dev/null +++ b/src/libs/graphics/drawable.c @@ -0,0 +1,501 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "libs/gfxlib.h" +#include "libs/graphics/context.h" +#include "libs/graphics/drawable.h" +#include "libs/graphics/tfb_draw.h" +#include "libs/memlib.h" +#include "tfb_draw.h" +#include <math.h> + +#ifndef M_PI +# define M_PI 3.14159265358979323846 +#endif + +FRAME _CurFramePtr; + +FRAME +SetContextFGFrame (FRAME Frame) +{ + FRAME LastFrame; + + if (Frame != (LastFrame = (FRAME)_CurFramePtr)) + { + if (LastFrame) + DeactivateDrawable (); + + _CurFramePtr = Frame; + if (_CurFramePtr) + ActivateDrawable (); + + if (ContextActive ()) + { + SwitchContextFGFrame (Frame); + } + } + + return (LastFrame); +} + +FRAME +GetContextFGFrame (void) +{ + return _CurFramePtr; +} + +static DRAWABLE +request_drawable (COUNT NumFrames, DRAWABLE_TYPE DrawableType, + CREATE_FLAGS flags, SIZE width, SIZE height) +{ + DRAWABLE Drawable; + COUNT i; + + Drawable = AllocDrawable (NumFrames); + if (!Drawable) + return NULL; + + Drawable->Flags = flags; + Drawable->MaxIndex = NumFrames - 1; + + for (i = 0; i < NumFrames; ++i) + { + FRAME FramePtr = &Drawable->Frame[i]; + + if (DrawableType == RAM_DRAWABLE && width > 0 && height > 0) + { + FramePtr->image = TFB_DrawImage_New (TFB_DrawCanvas_New_TrueColor ( + width, height, (flags & WANT_ALPHA) ? TRUE : FALSE)); + } + + FramePtr->Type = DrawableType; + FramePtr->Index = i; + SetFrameBounds (FramePtr, width, height); + } + + return Drawable; +} + +DRAWABLE +CreateDisplay (CREATE_FLAGS CreateFlags, SIZE *pwidth, SIZE *pheight) +{ + DRAWABLE Drawable; + + // TODO: ScreenWidth and ScreenHeight should be passed in + // instead of returned. + Drawable = request_drawable (1, SCREEN_DRAWABLE, + (CreateFlags & (WANT_PIXMAP | WANT_MASK)), + ScreenWidth, ScreenHeight); + if (Drawable) + { + FRAME F; + + F = CaptureDrawable (Drawable); + if (F == 0) + DestroyDrawable (Drawable); + else + { + *pwidth = GetFrameWidth (F); + *pheight = GetFrameHeight (F); + + ReleaseDrawable (F); + return (Drawable); + } + } + + *pwidth = *pheight = 0; + return (0); +} + +DRAWABLE +AllocDrawable (COUNT n) +{ + DRAWABLE Drawable; + Drawable = (DRAWABLE) HCalloc(sizeof (DRAWABLE_DESC)); + if (Drawable) + { + int i; + Drawable->Frame = (FRAME)HMalloc (sizeof (FRAME_DESC) * n); + if (Drawable->Frame == NULL) + { + HFree (Drawable); + return NULL; + } + + /* Zero out the newly allocated frames, since HMalloc doesn't have + * MEM_ZEROINIT. */ + for (i = 0; i < n; i++) { + FRAME F; + F = &Drawable->Frame[i]; + F->parent = Drawable; + F->Type = 0; + F->Index = 0; + F->image = 0; + F->Bounds.width = 0; + F->Bounds.height = 0; + F->HotSpot.x = 0; + F->HotSpot.y = 0; + } + } + return Drawable; +} + +DRAWABLE +CreateDrawable (CREATE_FLAGS CreateFlags, SIZE width, SIZE height, COUNT + num_frames) +{ + DRAWABLE Drawable; + + Drawable = request_drawable (num_frames, RAM_DRAWABLE, + (CreateFlags & (WANT_MASK | WANT_PIXMAP + | WANT_ALPHA | MAPPED_TO_DISPLAY)), + width, height); + if (Drawable) + { + FRAME F; + + F = CaptureDrawable (Drawable); + if (F) + { + ReleaseDrawable (F); + + return (Drawable); + } + } + + return (0); +} + +BOOLEAN +DestroyDrawable (DRAWABLE Drawable) +{ + if (_CurFramePtr && (Drawable == _CurFramePtr->parent)) + SetContextFGFrame ((FRAME)NULL); + + if (Drawable) + { + FreeDrawable (Drawable); + + return (TRUE); + } + + return (FALSE); +} + +BOOLEAN +GetFrameRect (FRAME FramePtr, RECT *pRect) +{ + if (FramePtr) + { + pRect->corner.x = -FramePtr->HotSpot.x; + pRect->corner.y = -FramePtr->HotSpot.y; + pRect->extent = GetFrameBounds (FramePtr); + + return (TRUE); + } + + return (FALSE); +} + +HOT_SPOT +SetFrameHot (FRAME FramePtr, HOT_SPOT HotSpot) +{ + if (FramePtr) + { + HOT_SPOT OldHot; + + OldHot = FramePtr->HotSpot; + FramePtr->HotSpot = HotSpot; + + return (OldHot); + } + + return (MAKE_HOT_SPOT (0, 0)); +} + +HOT_SPOT +GetFrameHot (FRAME FramePtr) +{ + if (FramePtr) + { + return FramePtr->HotSpot; + } + + return (MAKE_HOT_SPOT (0, 0)); +} + +DRAWABLE +RotateFrame (FRAME Frame, int angle_deg) +{ + DRAWABLE Drawable; + FRAME RotFramePtr; + double dx, dy; + double d; + double angle = angle_deg * M_PI / 180; + + if (!Frame) + return NULL; + + assert (Frame->Type != SCREEN_DRAWABLE); + + Drawable = request_drawable (1, RAM_DRAWABLE, WANT_PIXMAP, 0, 0); + if (!Drawable) + return 0; + RotFramePtr = CaptureDrawable (Drawable); + if (!RotFramePtr) + { + FreeDrawable (Drawable); + return 0; + } + + RotFramePtr->image = TFB_DrawImage_New_Rotated ( + Frame->image, angle_deg); + SetFrameBounds (RotFramePtr, RotFramePtr->image->extent.width, + RotFramePtr->image->extent.height); + + /* now we need to rotate the hot-spot, eww */ + dx = Frame->HotSpot.x - (GetFrameWidth (Frame) / 2); + dy = Frame->HotSpot.y - (GetFrameHeight (Frame) / 2); + d = sqrt ((double)dx*dx + (double)dy*dy); + if ((int)d != 0) + { + double organg = atan2 (-dy, dx); + dx = cos (organg + angle) * d; + dy = -sin (organg + angle) * d; + } + RotFramePtr->HotSpot.x = (GetFrameWidth (RotFramePtr) / 2) + (int)dx; + RotFramePtr->HotSpot.y = (GetFrameHeight (RotFramePtr) / 2) + (int)dy; + + ReleaseDrawable (RotFramePtr); + + return Drawable; +} + +// color.a is ignored +void +SetFrameTransparentColor (FRAME frame, Color color) +{ + TFB_Image *img; + + if (!frame) + return; + + assert (frame->Type != SCREEN_DRAWABLE); + + img = frame->image; + LockMutex (img->mutex); + + // TODO: This should defer to TFB_DrawImage instead + TFB_DrawCanvas_SetTransparentColor (img->NormalImg, color, FALSE); + + UnlockMutex (img->mutex); +} + +Color +GetFramePixel (FRAME frame, POINT pixelPt) +{ + TFB_Image *img; + Color ret; + + if (!frame) + return BUILD_COLOR_RGBA (0, 0, 0, 0); + + assert (frame->Type != SCREEN_DRAWABLE); + + img = frame->image; + LockMutex (img->mutex); + + // TODO: This should defer to TFB_DrawImage instead + ret = TFB_DrawCanvas_GetPixel (img->NormalImg, pixelPt.x, pixelPt.y); + + UnlockMutex (img->mutex); + + return ret; +} + +static FRAME +makeMatchingFrame (FRAME frame, int width, int height) +{ + DRAWABLE drawable; + FRAME newFrame; + CREATE_FLAGS flags; + + flags = GetFrameParentDrawable (frame)->Flags; + drawable = CreateDrawable (flags, width, height, 1); + if (!drawable) + return NULL; + newFrame = CaptureDrawable (drawable); + if (!newFrame) + { + FreeDrawable (drawable); + return NULL; + } + + return newFrame; +} + +// Creates an new DRAWABLE containing a copy of specified FRAME's rect +// Source FRAME must not be a SCREEN_DRAWABLE +DRAWABLE +CopyFrameRect (FRAME frame, const RECT *area) +{ + FRAME newFrame; + POINT nullPt = MAKE_POINT (0, 0); + + if (!frame) + return NULL; + + assert (frame->Type != SCREEN_DRAWABLE); + + newFrame = makeMatchingFrame (frame, area->extent.width, + area->extent.height); + if (!newFrame) + return NULL; + + TFB_DrawImage_CopyRect (frame->image, area, newFrame->image, nullPt); + + return ReleaseDrawable (newFrame); +} + +// Creates an new DRAWABLE mostly identical to specified FRAME +// Source FRAME must not be a SCREEN_DRAWABLE +DRAWABLE +CloneFrame (FRAME frame) +{ + FRAME newFrame; + RECT r; + + if (!frame) + return NULL; + + assert (frame->Type != SCREEN_DRAWABLE); + + GetFrameRect (frame, &r); + r.corner.x = 0; + r.corner.y = 0; + + newFrame = CaptureDrawable (CopyFrameRect (frame, &r)); + if (!newFrame) + return NULL; + + // copy the hot-spot + newFrame->HotSpot = frame->HotSpot; + + return ReleaseDrawable (newFrame); +} + +// Creates a new DRAWABLE of specified size and scales the passed +// frame onto it. The aspect ratio is not preserved. +DRAWABLE +RescaleFrame (FRAME frame, int width, int height) +{ + FRAME newFrame; + TFB_Image *img; + TFB_Canvas src, dst; + + if (!frame) + return NULL; + + assert (frame->Type != SCREEN_DRAWABLE); + + newFrame = makeMatchingFrame (frame, width, height); + if (!newFrame) + return NULL; + + // scale the hot-spot + newFrame->HotSpot.x = frame->HotSpot.x * width / frame->Bounds.width; + newFrame->HotSpot.y = frame->HotSpot.y * height / frame->Bounds.height; + + img = frame->image; + LockMutex (img->mutex); + // NOTE: We do not lock the target image because nothing has a + // reference to it yet! + src = img->NormalImg; + dst = newFrame->image->NormalImg; + TFB_DrawCanvas_Rescale_Nearest (src, dst, -1, NULL, NULL, NULL); + + UnlockMutex (img->mutex); + + return ReleaseDrawable (newFrame); +} + +BOOLEAN +ReadFramePixelColors (FRAME frame, Color *pixels, int width, int height) +{ + TFB_Image *img; + + if (!frame) + return FALSE; + + assert (frame->Type != SCREEN_DRAWABLE); + + // TODO: Do we need to lock the img->mutex here? + img = frame->image; + return TFB_DrawCanvas_GetPixelColors (img->NormalImg, pixels, + width, height); +} + +// Warning: this functions bypasses DCQ, which is why it is not a DrawXXX +BOOLEAN +WriteFramePixelColors (FRAME frame, const Color *pixels, int width, int height) +{ + TFB_Image *img; + + if (!frame) + return FALSE; + + assert (frame->Type != SCREEN_DRAWABLE); + + // TODO: Do we need to lock the img->mutex here? + img = frame->image; + return TFB_DrawCanvas_SetPixelColors (img->NormalImg, pixels, + width, height); +} + +BOOLEAN +ReadFramePixelIndexes (FRAME frame, BYTE *pixels, int width, int height) +{ + TFB_Image *img; + + if (!frame) + return FALSE; + + assert (frame->Type != SCREEN_DRAWABLE); + + // TODO: Do we need to lock the img->mutex here? + img = frame->image; + return TFB_DrawCanvas_GetPixelIndexes (img->NormalImg, pixels, + width, height); +} + +// Warning: this functions bypasses DCQ, which is why it is not a DrawXXX +BOOLEAN +WriteFramePixelIndexes (FRAME frame, const BYTE *pixels, int width, int height) +{ + TFB_Image *img; + + if (!frame) + return FALSE; + + assert (frame->Type != SCREEN_DRAWABLE); + + // TODO: Do we need to lock the img->mutex here? + img = frame->image; + return TFB_DrawCanvas_SetPixelIndexes (img->NormalImg, pixels, + width, height); +} diff --git a/src/libs/graphics/drawable.h b/src/libs/graphics/drawable.h new file mode 100644 index 0000000..98f5ada --- /dev/null +++ b/src/libs/graphics/drawable.h @@ -0,0 +1,88 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_GRAPHICS_DRAWABLE_H_ +#define LIBS_GRAPHICS_DRAWABLE_H_ + +#include <stdio.h> +#include "tfb_draw.h" + +#define ValidPrimType(pt) ((pt)<NUM_PRIMS) + +typedef struct bresenham_line +{ + POINT first, second; + SIZE abs_delta_x, abs_delta_y; + SIZE error_term; + BOOLEAN end_points_exchanged; + INTERSECT_CODE intersect_code; +} BRESENHAM_LINE; + +typedef UWORD DRAWABLE_TYPE; +#define ROM_DRAWABLE 0 +#define RAM_DRAWABLE 1 +#define SCREEN_DRAWABLE 2 + +struct frame_desc +{ + DRAWABLE_TYPE Type; + UWORD Index; + HOT_SPOT HotSpot; + EXTENT Bounds; + TFB_Image *image; + struct drawable_desc *parent; +}; + +struct drawable_desc +{ + CREATE_FLAGS Flags; + UWORD MaxIndex; + FRAME_DESC *Frame; +}; + +#define GetFrameWidth(f) ((f)->Bounds.width) +#define GetFrameHeight(f) ((f)->Bounds.height) +#define GetFrameBounds(f) ((f)->Bounds) +#define SetFrameBounds(f,w,h) \ + ((f)->Bounds.width=(w), \ + ((f))->Bounds.height=(h)) + +#define DRAWABLE_PRIORITY DEFAULT_MEM_PRIORITY + +extern DRAWABLE AllocDrawable (COUNT num_frames); +#define FreeDrawable(D) _ReleaseCelData (D) + +typedef struct +{ + RECT Box; + FRAME FramePtr; +} IMAGE_BOX; + +extern INTERSECT_CODE _clip_line (const RECT *pClipRect, + BRESENHAM_LINE *pLine); + +extern void *_GetCelData (uio_Stream *fp, DWORD length); +extern BOOLEAN _ReleaseCelData (void *handle); + +extern FRAME _CurFramePtr; + +// ClipRect is relative to ctxOrigin +extern void _text_blt (RECT *pClipRect, TEXT *TextPtr, POINT ctxOrigin); + +#endif /* LIBS_GRAPHICS_DRAWABLE_H_ */ + diff --git a/src/libs/graphics/drawcmd.h b/src/libs/graphics/drawcmd.h new file mode 100644 index 0000000..6d44153 --- /dev/null +++ b/src/libs/graphics/drawcmd.h @@ -0,0 +1,202 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef DRAWCMD_H +#define DRAWCMD_H + +#include "libs/graphics/tfb_draw.h" + +enum +{ + TFB_DRAWCOMMANDTYPE_LINE, + TFB_DRAWCOMMANDTYPE_RECTANGLE, + TFB_DRAWCOMMANDTYPE_IMAGE, + TFB_DRAWCOMMANDTYPE_FILLEDIMAGE, + TFB_DRAWCOMMANDTYPE_FONTCHAR, + + TFB_DRAWCOMMANDTYPE_COPY, + TFB_DRAWCOMMANDTYPE_COPYTOIMAGE, + + TFB_DRAWCOMMANDTYPE_SCISSORENABLE, + TFB_DRAWCOMMANDTYPE_SCISSORDISABLE, + + TFB_DRAWCOMMANDTYPE_SETMIPMAP, + TFB_DRAWCOMMANDTYPE_DELETEIMAGE, + TFB_DRAWCOMMANDTYPE_DELETEDATA, + TFB_DRAWCOMMANDTYPE_SENDSIGNAL, + TFB_DRAWCOMMANDTYPE_REINITVIDEO, + TFB_DRAWCOMMANDTYPE_CALLBACK, +}; + +typedef struct tfb_dc_line +{ + int x1, y1, x2, y2; + Color color; + DrawMode drawMode; + SCREEN destBuffer; +} TFB_DrawCommand_Line; + +typedef struct tfb_dc_rect +{ + RECT rect; + Color color; + DrawMode drawMode; + SCREEN destBuffer; +} TFB_DrawCommand_Rect; + +typedef struct tfb_dc_img +{ + TFB_Image *image; + int x, y; + SCREEN destBuffer; + TFB_ColorMap *colormap; + DrawMode drawMode; + int scale; + int scaleMode; +} TFB_DrawCommand_Image; + +typedef struct tfb_dc_filledimg +{ + TFB_Image *image; + int x, y; + Color color; + SCREEN destBuffer; + DrawMode drawMode; + int scale; + int scaleMode; +} TFB_DrawCommand_FilledImage; + +typedef struct tfb_dc_fontchar +{ + TFB_Char *fontchar; + TFB_Image *backing; + int x, y; + DrawMode drawMode; + SCREEN destBuffer; +} TFB_DrawCommand_FontChar; + +typedef struct tfb_dc_copy +{ + RECT rect; + SCREEN srcBuffer, destBuffer; +} TFB_DrawCommand_Copy; + +typedef struct tfb_dc_copyimg +{ + TFB_Image *image; + RECT rect; + SCREEN srcBuffer; +} TFB_DrawCommand_CopyToImage; + +typedef struct tfb_dc_scissor +{ + RECT rect; +} TFB_DrawCommand_Scissor; + +typedef struct tfb_dc_setmip +{ + TFB_Image *image; + TFB_Image *mipmap; + int hotx, hoty; +} TFB_DrawCommand_SetMipmap; + +typedef struct tfb_dc_delimg +{ + TFB_Image *image; +} TFB_DrawCommand_DeleteImage; + +typedef struct tfb_dc_deldata +{ + void *data; + // data must be a result of HXalloc() call +} TFB_DrawCommand_DeleteData; + +typedef struct tfb_dc_signal +{ + Semaphore sem; +} TFB_DrawCommand_SendSignal; + +typedef struct tfb_dc_reinit_video +{ + int driver, flags, width, height; +} TFB_DrawCommand_ReinitVideo; + +typedef struct tfb_dc_callback +{ + void (*callback)(void *arg); + void *arg; +} TFB_DrawCommand_Callback; + +typedef struct tfb_drawcommand +{ + int Type; + union { + TFB_DrawCommand_Line line; + TFB_DrawCommand_Rect rect; + TFB_DrawCommand_Image image; + TFB_DrawCommand_FilledImage filledimage; + TFB_DrawCommand_FontChar fontchar; + TFB_DrawCommand_Copy copy; + TFB_DrawCommand_CopyToImage copytoimage; + TFB_DrawCommand_Scissor scissor; + TFB_DrawCommand_SetMipmap setmipmap; + TFB_DrawCommand_DeleteImage deleteimage; + TFB_DrawCommand_DeleteData deletedata; + TFB_DrawCommand_SendSignal sendsignal; + TFB_DrawCommand_ReinitVideo reinitvideo; + TFB_DrawCommand_Callback callback; + } data; +} TFB_DrawCommand; + +// Queue Stuff + +typedef struct tfb_drawcommandqueue +{ + int Front; + int Back; + int InsertionPoint; + int Batching; + volatile int FullSize; + volatile int Size; +} TFB_DrawCommandQueue; + +void Init_DrawCommandQueue (void); + +void Uninit_DrawCommandQueue (void); + +void TFB_BatchGraphics (void); + +void TFB_UnbatchGraphics (void); + +void TFB_BatchReset (void); + +void TFB_DrawCommandQueue_Push (TFB_DrawCommand* Command); + +int TFB_DrawCommandQueue_Pop (TFB_DrawCommand* Command); + +void TFB_DrawCommandQueue_Clear (void); + +extern TFB_DrawCommandQueue DrawCommandQueue; + +void TFB_EnqueueDrawCommand (TFB_DrawCommand* DrawCommand); + +void Lock_DCQ (int slots); + +void Unlock_DCQ (void); + +#endif diff --git a/src/libs/graphics/filegfx.c b/src/libs/graphics/filegfx.c new file mode 100644 index 0000000..c6af7cd --- /dev/null +++ b/src/libs/graphics/filegfx.c @@ -0,0 +1,72 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "gfxintrn.h" +#include "options.h" +#include "libs/reslib.h" + + +DRAWABLE +LoadGraphicFile (const char *pStr) +{ + uio_Stream *fp; + + // FIXME: this theoretically needs a mechanism to prevent races + if (_cur_resfile_name) + // something else is loading resources atm + return 0; + + fp = res_OpenResFile (contentDir, pStr, "rb"); + if (fp != NULL) + { + DRAWABLE hData; + + _cur_resfile_name = pStr; + hData = (DRAWABLE)_GetCelData (fp, LengthResFile (fp)); + _cur_resfile_name = 0; + res_CloseResFile (fp); + return hData; + } + + return (NULL); +} + +FONT +LoadFontFile (const char *pStr) +{ + uio_Stream *fp; + + // FIXME: this theoretically needs a mechanism to prevent races + if (_cur_resfile_name) + // something else is loading resources atm + return 0; + + fp = res_OpenResFile (contentDir, pStr, "rb"); + if (fp != NULL) + { + FONT hData; + + _cur_resfile_name = pStr; + hData = (FONT)_GetFontData (fp, LengthResFile (fp)); + _cur_resfile_name = 0; + res_CloseResFile (fp); + return hData; + } + + return (0); +} diff --git a/src/libs/graphics/font.c b/src/libs/graphics/font.c new file mode 100644 index 0000000..638c814 --- /dev/null +++ b/src/libs/graphics/font.c @@ -0,0 +1,334 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "gfxintrn.h" +#include "tfb_prim.h" +#include "libs/log.h" + +static inline TFB_Char *getCharFrame (FONT_DESC *fontPtr, UniChar ch); + + +FONT +SetContextFont (FONT Font) +{ + FONT LastFont; + + LastFont = _CurFontPtr; + _CurFontPtr = Font; + if (ContextActive ()) + SwitchContextFont (Font); + + return (LastFont); +} + +BOOLEAN +DestroyFont (FONT FontRef) +{ + if (FontRef == NULL) + return (FALSE); + + if (_CurFontPtr && _CurFontPtr == FontRef) + SetContextFont ((FONT)NULL); + + return (FreeFont (FontRef)); +} + +// XXX: Should be in frame.c (renamed to something decent?) +void +font_DrawText (TEXT *lpText) +{ + RECT ClipRect; + POINT origin; + TEXT text; + + FixContextFontEffect (); + if (!GraphicsSystemActive () || !GetContextValidRect (NULL, &origin)) + return; + + // TextRect() clobbers TEXT.CharCount so we have to make a copy + text = *lpText; + if (!TextRect (&text, &ClipRect, NULL)) + return; + // ClipRect is relative to origin + _text_blt (&ClipRect, &text, origin); +} + +/* Draw the stroke by drawing the same text in the + * background color one pixel shifted to all 4 directions. + */ +void +font_DrawTracedText (TEXT *pText, Color text, Color trace) +{ + // Preserve current foreground color for full correctness + Color oldfg = SetContextForeGroundColor (trace); + pText->baseline.x--; + font_DrawText (pText); + pText->baseline.x += 2; + font_DrawText (pText); + pText->baseline.x--; + pText->baseline.y--; + font_DrawText (pText); + pText->baseline.y += 2; + font_DrawText (pText); + pText->baseline.y--; + SetContextForeGroundColor (text); + font_DrawText (pText); + SetContextForeGroundColor (oldfg); +} + +BOOLEAN +GetContextFontLeading (SIZE *pheight) +{ + if (_CurFontPtr != 0) + { + *pheight = (SIZE)_CurFontPtr->Leading; + return (TRUE); + } + + *pheight = 0; + return (FALSE); +} + +BOOLEAN +GetContextFontLeadingWidth (SIZE *pwidth) +{ + if (_CurFontPtr != 0) + { + *pwidth = (SIZE)_CurFontPtr->LeadingWidth; + return (TRUE); + } + + *pwidth = 0; + return (FALSE); +} + +BOOLEAN +TextRect (TEXT *lpText, RECT *pRect, BYTE *pdelta) +{ + BYTE char_delta_array[MAX_DELTAS]; + FONT FontPtr; + + FontPtr = _CurFontPtr; + if (FontPtr != 0 && lpText->CharCount != 0) + { + COORD top_y, bot_y; + SIZE width; + UniChar next_ch = 0; + const char *pStr; + COUNT num_chars; + + num_chars = lpText->CharCount; + /* At this point lpText->CharCount contains the *maximum* number of + * characters that lpText->pStr may contain. + * After the while loop below, it will contain the actual number. + */ + if (pdelta == 0) + { + pdelta = char_delta_array; + if (num_chars > MAX_DELTAS) + { + num_chars = MAX_DELTAS; + lpText->CharCount = MAX_DELTAS; + } + } + + top_y = 0; + bot_y = 0; + width = 0; + pStr = lpText->pStr; + if (num_chars > 0) + { + next_ch = getCharFromString (&pStr); + if (next_ch == '\0') + num_chars = 0; + } + while (num_chars--) + { + UniChar ch; + SIZE last_width; + TFB_Char *charFrame; + + last_width = width; + + ch = next_ch; + if (num_chars > 0) + { + next_ch = getCharFromString (&pStr); + if (next_ch == '\0') + { + lpText->CharCount -= num_chars; + num_chars = 0; + } + } + + charFrame = getCharFrame (FontPtr, ch); + if (charFrame != NULL && charFrame->disp.width) + { + COORD y; + + y = -charFrame->HotSpot.y; + if (y < top_y) + top_y = y; + y += charFrame->disp.height; + if (y > bot_y) + bot_y = y; + + width += charFrame->disp.width; +#if 0 + if (num_chars && next_ch < (UNICODE) MAX_CHARS + && !(FontPtr->KernTab[ch] + & (FontPtr->KernTab[next_ch] >> 2))) + width -= FontPtr->KernAmount; +#endif + } + + *pdelta++ = (BYTE)(width - last_width); + } + + if (width > 0 && (bot_y -= top_y) > 0) + { + /* subtract off default character spacing */ + if (pdelta[-1] > 0) + { + --pdelta[-1]; + --width; + } + + if (lpText->align == ALIGN_LEFT) + pRect->corner.x = 0; + else if (lpText->align == ALIGN_CENTER) + pRect->corner.x = -(width >> 1); + else + pRect->corner.x = -width; + pRect->corner.y = top_y; + pRect->extent.width = width; + pRect->extent.height = bot_y; + + pRect->corner.x += lpText->baseline.x; + pRect->corner.y += lpText->baseline.y; + + return (TRUE); + } + } + + pRect->corner = lpText->baseline; + pRect->extent.width = 0; + pRect->extent.height = 0; + + return (FALSE); +} + +void +_text_blt (RECT *pClipRect, TEXT *TextPtr, POINT ctxOrigin) +{ + FONT FontPtr; + COUNT num_chars; + UniChar next_ch; + const char *pStr; + POINT origin; + TFB_Image *backing; + DrawMode mode = _get_context_draw_mode (); + + FontPtr = _CurFontPtr; + if (FontPtr == NULL) + return; + backing = _get_context_font_backing (); + if (!backing) + return; + + origin.x = pClipRect->corner.x; + origin.y = TextPtr->baseline.y; + num_chars = TextPtr->CharCount; + if (num_chars == 0) + return; + + pStr = TextPtr->pStr; + + next_ch = getCharFromString (&pStr); + if (next_ch == '\0') + num_chars = 0; + while (num_chars--) + { + UniChar ch; + TFB_Char* fontChar; + + ch = next_ch; + if (num_chars > 0) + { + next_ch = getCharFromString (&pStr); + if (next_ch == '\0') + num_chars = 0; + } + + fontChar = getCharFrame (FontPtr, ch); + if (fontChar != NULL && fontChar->disp.width) + { + RECT r; + + r.corner.x = origin.x - fontChar->HotSpot.x; + r.corner.y = origin.y - fontChar->HotSpot.y; + r.extent.width = fontChar->disp.width; + r.extent.height = fontChar->disp.height; + if (BoxIntersect (&r, pClipRect, &r)) + { + TFB_Prim_FontChar (origin, fontChar, backing, mode, + ctxOrigin); + } + + origin.x += fontChar->disp.width; +#if 0 + if (num_chars && next_ch < (UNICODE) MAX_CHARS + && !(FontPtr->KernTab[ch] + & (FontPtr->KernTab[next_ch] >> 2))) + origin.x -= FontPtr->KernAmount; +#endif + } + } +} + +static inline TFB_Char * +getCharFrame (FONT_DESC *fontPtr, UniChar ch) +{ + UniChar pageStart = ch & CHARACTER_PAGE_MASK; + size_t charIndex; + + FONT_PAGE *page = fontPtr->fontPages; + for (;;) + { + if (page == NULL) + return NULL; + + if (page->pageStart == pageStart) + break; + + page = page->next; + } + + charIndex = ch - page->firstChar; + if (ch >= page->firstChar && charIndex < page->numChars + && page->charDesc[charIndex].data) + { + return &page->charDesc[charIndex]; + } + else + { + //log_add (log_Debug, "Character %u not present", (unsigned int) ch); + return NULL; + } +} + diff --git a/src/libs/graphics/font.h b/src/libs/graphics/font.h new file mode 100644 index 0000000..b6bd13b --- /dev/null +++ b/src/libs/graphics/font.h @@ -0,0 +1,71 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_GRAPHICS_FONT_H_ +#define LIBS_GRAPHICS_FONT_H_ + +#include "libs/memlib.h" + +#define MAX_DELTAS 100 + +typedef struct FontPage +{ + struct FontPage *next; + UniChar pageStart; +#define CHARACTER_PAGE_MASK 0xfffff800 + UniChar firstChar; + size_t numChars; + TFB_Char *charDesc; +} FONT_PAGE; + +static inline FONT_PAGE * +AllocFontPage (int numChars) +{ + FONT_PAGE *result = HMalloc (sizeof (FONT_PAGE)); + result->charDesc = HCalloc (numChars * sizeof *result->charDesc); + return result; +} + +static inline void +FreeFontPage (FONT_PAGE *page) +{ + HFree (page->charDesc); + HFree (page); +} + +struct font_desc +{ + UWORD Leading; + UWORD LeadingWidth; + FONT_PAGE *fontPages; +}; + +#define CHAR_DESCPTR PCHAR_DESC + +#define FONT_PRIORITY DEFAULT_MEM_PRIORITY + +#define AllocFont(size) (FONT)HCalloc (sizeof (FONT_DESC) + (size)) +#define FreeFont _ReleaseFontData + +extern FONT _CurFontPtr; + +extern void *_GetFontData (uio_Stream *fp, DWORD length); +extern BOOLEAN _ReleaseFontData (void *handle); + +#endif /* LIBS_GRAPHICS_FONT_H_ */ + diff --git a/src/libs/graphics/frame.c b/src/libs/graphics/frame.c new file mode 100644 index 0000000..0539746 --- /dev/null +++ b/src/libs/graphics/frame.c @@ -0,0 +1,266 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "gfxintrn.h" +#include "gfx_common.h" +#include "tfb_draw.h" +#include "tfb_prim.h" + +HOT_SPOT +MAKE_HOT_SPOT (COORD x, COORD y) +{ + HOT_SPOT hs; + hs.x = x; + hs.y = y; + return hs; +} + +// XXX: INTERNAL_PRIMITIVE and INTERNAL_PRIM_DESC are not used +typedef union +{ + POINT Point; + STAMP Stamp; + BRESENHAM_LINE Line; + TEXT Text; + RECT Rect; +} INTERNAL_PRIM_DESC; + +typedef struct +{ + PRIM_LINKS Links; + GRAPHICS_PRIM Type; + Color Color; + INTERNAL_PRIM_DESC Object; +} INTERNAL_PRIMITIVE; + + +// pValidRect or origin may be NULL +BOOLEAN +GetContextValidRect (RECT *pValidRect, POINT *origin) +{ + RECT tempRect; + POINT tempPt; + + if (!pValidRect) + pValidRect = &tempRect; + if (!origin) + origin = &tempPt; + + // Start with a rect the size of foreground frame + pValidRect->corner.x = 0; + pValidRect->corner.y = 0; + pValidRect->extent = GetFrameBounds (_CurFramePtr); + *origin = _CurFramePtr->HotSpot; + + if (_pCurContext->ClipRect.extent.width) + { + // If the cliprect is completely outside of the valid frame + // bounds we have nothing to draw + if (!BoxIntersect (&_pCurContext->ClipRect, + pValidRect, pValidRect)) + return (FALSE); + + // Foreground frame hotspot defines a drawing position offset + // WRT the context cliprect + origin->x += _pCurContext->ClipRect.corner.x; + origin->y += _pCurContext->ClipRect.corner.y; + } + + return (TRUE); +} + +static void +ClearBackGround (RECT *pClipRect) +{ + RECT clearRect; + Color color = _get_context_bg_color (); + clearRect.corner.x = 0; + clearRect.corner.y = 0; + clearRect.extent = pClipRect->extent; + TFB_Prim_FillRect (&clearRect, color, DRAW_REPLACE_MODE, + pClipRect->corner); +} + +void +DrawBatch (PRIMITIVE *lpBasePrim, PRIM_LINKS PrimLinks, + BATCH_FLAGS BatchFlags) +{ + RECT ValidRect; + POINT origin; + + if (GraphicsSystemActive () && GetContextValidRect (&ValidRect, &origin)) + { + COUNT CurIndex; + PRIMITIVE *lpPrim; + DrawMode mode = _get_context_draw_mode (); + + BatchGraphics (); + + if (BatchFlags & BATCH_BUILD_PAGE) + { + ClearBackGround (&ValidRect); + } + + CurIndex = GetPredLink (PrimLinks); + + for (; CurIndex != END_OF_LIST; + CurIndex = GetSuccLink (GetPrimLinks (lpPrim))) + { + GRAPHICS_PRIM PrimType; + PRIMITIVE *lpWorkPrim; + RECT ClipRect; + Color color; + + lpPrim = &lpBasePrim[CurIndex]; + PrimType = GetPrimType (lpPrim); + if (!ValidPrimType (PrimType)) + continue; + + lpWorkPrim = lpPrim; + + switch (PrimType) + { + case POINT_PRIM: + color = GetPrimColor (lpWorkPrim); + TFB_Prim_Point (&lpWorkPrim->Object.Point, color, + mode, origin); + break; + case STAMP_PRIM: + TFB_Prim_Stamp (&lpWorkPrim->Object.Stamp, mode, origin); + break; + case STAMPFILL_PRIM: + color = GetPrimColor (lpWorkPrim); + TFB_Prim_StampFill (&lpWorkPrim->Object.Stamp, color, + mode, origin); + break; + case LINE_PRIM: + color = GetPrimColor (lpWorkPrim); + TFB_Prim_Line (&lpWorkPrim->Object.Line, color, + mode, origin); + break; + case TEXT_PRIM: + if (!TextRect (&lpWorkPrim->Object.Text, &ClipRect, NULL)) + continue; + // ClipRect is relative to origin + _text_blt (&ClipRect, &lpWorkPrim->Object.Text, origin); + break; + case RECT_PRIM: + color = GetPrimColor (lpWorkPrim); + TFB_Prim_Rect (&lpWorkPrim->Object.Rect, color, + mode, origin); + break; + case RECTFILL_PRIM: + color = GetPrimColor (lpWorkPrim); + TFB_Prim_FillRect (&lpWorkPrim->Object.Rect, color, + mode, origin); + break; + } + } + + UnbatchGraphics (); + } +} + +void +ClearDrawable (void) +{ + RECT ValidRect; + + if (GraphicsSystemActive () && GetContextValidRect (&ValidRect, NULL)) + { + ClearBackGround (&ValidRect); + } +} + +void +DrawPoint (POINT *lpPoint) +{ + POINT origin; + + if (GraphicsSystemActive () && GetContextValidRect (NULL, &origin)) + { + Color color = GetPrimColor (&_locPrim); + DrawMode mode = _get_context_draw_mode (); + TFB_Prim_Point (lpPoint, color, mode, origin); + } +} + +void +DrawRectangle (RECT *lpRect) +{ + POINT origin; + + if (GraphicsSystemActive () && GetContextValidRect (NULL, &origin)) + { + Color color = GetPrimColor (&_locPrim); + DrawMode mode = _get_context_draw_mode (); + TFB_Prim_Rect (lpRect, color, mode, origin); + } +} + +void +DrawFilledRectangle (RECT *lpRect) +{ + POINT origin; + + if (GraphicsSystemActive () && GetContextValidRect (NULL, &origin)) + { + Color color = GetPrimColor (&_locPrim); + DrawMode mode = _get_context_draw_mode (); + TFB_Prim_FillRect (lpRect, color, mode, origin); + } +} + +void +DrawLine (LINE *lpLine) +{ + POINT origin; + + if (GraphicsSystemActive () && GetContextValidRect (NULL, &origin)) + { + Color color = GetPrimColor (&_locPrim); + DrawMode mode = _get_context_draw_mode (); + TFB_Prim_Line (lpLine, color, mode, origin); + } +} + +void +DrawStamp (STAMP *stmp) +{ + POINT origin; + + if (GraphicsSystemActive () && GetContextValidRect (NULL, &origin)) + { + DrawMode mode = _get_context_draw_mode (); + TFB_Prim_Stamp (stmp, mode, origin); + } +} + +void +DrawFilledStamp (STAMP *stmp) +{ + POINT origin; + + if (GraphicsSystemActive () && GetContextValidRect (NULL, &origin)) + { + Color color = GetPrimColor (&_locPrim); + DrawMode mode = _get_context_draw_mode (); + TFB_Prim_StampFill (stmp, color, mode, origin); + } +} + diff --git a/src/libs/graphics/gfx_common.c b/src/libs/graphics/gfx_common.c new file mode 100644 index 0000000..405f614 --- /dev/null +++ b/src/libs/graphics/gfx_common.c @@ -0,0 +1,196 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "gfxintrn.h" +#include "libs/graphics/gfx_common.h" +#include "libs/graphics/drawcmd.h" +#include "libs/timelib.h" +#include "libs/misc.h" + // for TFB_DEBUG_HALT + + +int ScreenWidth; +int ScreenHeight; +int ScreenWidthActual; +int ScreenHeightActual; +int ScreenColorDepth; +int GraphicsDriver; +int TFB_DEBUG_HALT = 0; + +volatile int TransitionAmount = 255; +RECT TransitionClipRect; + +static int gscale = GSCALE_IDENTITY; +static int gscale_mode = TFB_SCALE_NEAREST; + +void +DrawFromExtraScreen (RECT *r) +{ + TFB_DrawScreen_Copy(r, TFB_SCREEN_EXTRA, TFB_SCREEN_MAIN); +} + +void +LoadIntoExtraScreen (RECT *r) +{ + TFB_DrawScreen_Copy(r, TFB_SCREEN_MAIN, TFB_SCREEN_EXTRA); +} + +int +SetGraphicScale (int scale) +{ + int old_scale = gscale; + gscale = (scale ? scale : GSCALE_IDENTITY); + return old_scale; +} + +int +GetGraphicScale (void) +{ + return gscale; +} + +int +SetGraphicScaleMode (int mode) +{ + int old_mode = gscale_mode; + assert (mode >= TFB_SCALE_NEAREST && mode <= TFB_SCALE_TRILINEAR); + gscale_mode = mode; + return old_mode; +} + +int +GetGraphicScaleMode (void) +{ + return gscale_mode; +} + +/* Batching and Unbatching functions. A "Batch" is a collection of + DrawCommands that will never be flipped to the screen half-rendered. + BatchGraphics and UnbatchGraphics function vaguely like a non-blocking + recursive lock to do this respect. */ +void +BatchGraphics (void) +{ + TFB_BatchGraphics (); +} + +void +UnbatchGraphics (void) +{ + TFB_UnbatchGraphics (); +} + +/* Sleeps this thread until all Draw Commands queued by that thread have + been processed. */ + +void +FlushGraphics (void) +{ + TFB_DrawScreen_WaitForSignal (); +} + +static void +ExpandRect (RECT *rect, int expansion) +{ + if (rect->corner.x - expansion >= 0) + { + rect->extent.width += expansion; + rect->corner.x -= expansion; + } + else + { + rect->extent.width += rect->corner.x; + rect->corner.x = 0; + } + + if (rect->corner.y - expansion >= 0) + { + rect->extent.height += expansion; + rect->corner.y -= expansion; + } + else + { + rect->extent.height += rect->corner.y; + rect->corner.y = 0; + } + + if (rect->corner.x + rect->extent.width + expansion <= ScreenWidth) + rect->extent.width += expansion; + else + rect->extent.width = ScreenWidth - rect->corner.x; + + if (rect->corner.y + rect->extent.height + expansion <= ScreenHeight) + rect->extent.height += expansion; + else + rect->extent.height = ScreenHeight - rect->corner.y; +} + +void +SetTransitionSource (const RECT *pRect) +{ + RECT ActualRect; + + if (pRect) + { /* expand the rect to accomodate scalers in OpenGL mode */ + ActualRect = *pRect; + pRect = &ActualRect; + ExpandRect (&ActualRect, 2); + } + TFB_DrawScreen_Copy (pRect, TFB_SCREEN_MAIN, TFB_SCREEN_TRANSITION); +} + +// ScreenTransition() is synchronous (does not return until transition done) +void +ScreenTransition (int TransType, const RECT *pRect) +{ + const TimePeriod DURATION = ONE_SECOND * 31 / 60; + TimeCount startTime; + (void) TransType; /* dodge compiler warning */ + + if (pRect) + { + TransitionClipRect = *pRect; + } + else + { + TransitionClipRect.corner.x = 0; + TransitionClipRect.corner.y = 0; + TransitionClipRect.extent.width = ScreenWidth; + TransitionClipRect.extent.height = ScreenHeight; + } + + TFB_UploadTransitionScreen (); + + TransitionAmount = 0; + FlushGraphics (); + startTime = GetTimeCounter (); + while (TransitionAmount < 255) + { + TimePeriod deltaT; + int newAmount; + + SleepThread (ONE_SECOND / 100); + + deltaT = GetTimeCounter () - startTime; + newAmount = deltaT * 255 / DURATION; + if (newAmount > 255) + newAmount = 255; + + TransitionAmount = newAmount; + } +} diff --git a/src/libs/graphics/gfx_common.h b/src/libs/graphics/gfx_common.h new file mode 100644 index 0000000..e9e421a --- /dev/null +++ b/src/libs/graphics/gfx_common.h @@ -0,0 +1,112 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef GFX_COMMON_H +#define GFX_COMMON_H + +#include <stdio.h> +#include <stdlib.h> + +#include "libs/gfxlib.h" + +// driver for TFB_InitGraphics +enum +{ + TFB_GFXDRIVER_SDL_OPENGL, + TFB_GFXDRIVER_SDL_PURE, +}; + +// forced redraw +enum +{ + TFB_REDRAW_NO = 0, + TFB_REDRAW_FADING, + TFB_REDRAW_EXPOSE, + TFB_REDRAW_YES +}; + +// flags for TFB_InitGraphics +#define TFB_GFXFLAGS_FULLSCREEN (1<<0) +#define TFB_GFXFLAGS_SHOWFPS (1<<1) +#define TFB_GFXFLAGS_SCANLINES (1<<2) +#define TFB_GFXFLAGS_SCALE_BILINEAR (1<<3) +#define TFB_GFXFLAGS_SCALE_BIADAPT (1<<4) +#define TFB_GFXFLAGS_SCALE_BIADAPTADV (1<<5) +#define TFB_GFXFLAGS_SCALE_TRISCAN (1<<6) +#define TFB_GFXFLAGS_SCALE_HQXX (1<<7) +#define TFB_GFXFLAGS_SCALE_ANY \ + ( TFB_GFXFLAGS_SCALE_BILINEAR | \ + TFB_GFXFLAGS_SCALE_BIADAPT | \ + TFB_GFXFLAGS_SCALE_BIADAPTADV | \ + TFB_GFXFLAGS_SCALE_TRISCAN | \ + TFB_GFXFLAGS_SCALE_HQXX ) +#define TFB_GFXFLAGS_SCALE_SOFT_ONLY \ + ( TFB_GFXFLAGS_SCALE_ANY & ~TFB_GFXFLAGS_SCALE_BILINEAR ) + +// The flag variable itself +extern int GfxFlags; + +// The following functions are driver-defined +void TFB_PreInit (void); +int TFB_InitGraphics (int driver, int flags, const char *renderer, + int width, int height); +int TFB_ReInitGraphics (int driver, int flags, int width, int height); +void TFB_UninitGraphics (void); +void TFB_ProcessEvents (void); +bool TFB_SetGamma (float gamma); +void TFB_UploadTransitionScreen (void); +int TFB_SupportsHardwareScaling (void); +// This function should not be called directly +void TFB_SwapBuffers (int force_full_redraw); + +#define GSCALE_IDENTITY 256 + +typedef enum { + TFB_SCALE_STEP, /* not really a scaler */ + TFB_SCALE_NEAREST, + TFB_SCALE_BILINEAR, + TFB_SCALE_TRILINEAR +} SCALE; + +void LoadIntoExtraScreen (RECT *r); +void DrawFromExtraScreen (RECT *r); +int SetGraphicScale (int scale); +int GetGraphicScale (void); +int SetGraphicScaleMode (int mode /* enum SCALE */); +int GetGraphicScaleMode (void); +void SetTransitionSource (const RECT *pRect); +void ScreenTransition (int transition, const RECT *pRect); + +// TODO: there should be accessor functions for these +extern volatile int TransitionAmount; +extern RECT TransitionClipRect; + +extern float FrameRate; +extern int FrameRateTickBase; + +void TFB_FlushGraphics (void); // Only call from main thread!! +void TFB_PurgeDanglingGraphics (void); // Only call from main thread as part of shutdown. + +extern int ScreenWidth; +extern int ScreenHeight; +extern int ScreenWidthActual; +extern int ScreenHeightActual; +extern int ScreenColorDepth; +extern int GraphicsDriver; + +#endif diff --git a/src/libs/graphics/gfxintrn.h b/src/libs/graphics/gfxintrn.h new file mode 100644 index 0000000..72281e6 --- /dev/null +++ b/src/libs/graphics/gfxintrn.h @@ -0,0 +1,32 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_GRAPHICS_GFXINTRN_H_ +#define LIBS_GRAPHICS_GFXINTRN_H_ + +#include <stdio.h> +#include <string.h> + +#include "libs/gfxlib.h" +#include "libs/reslib.h" +#include "context.h" +#include "drawable.h" +#include "font.h" + +#endif /* LIBS_GRAPHICS_GFXINTRN_H_ */ + diff --git a/src/libs/graphics/gfxload.c b/src/libs/graphics/gfxload.c new file mode 100644 index 0000000..969a910 --- /dev/null +++ b/src/libs/graphics/gfxload.c @@ -0,0 +1,597 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <string.h> +#include <stdio.h> + +#include "options.h" +#include "port.h" +#include "libs/uio.h" +#include "libs/reslib.h" + // for _cur_resfile_name +#include "libs/log.h" +#include "libs/memlib.h" +#include "libs/graphics/tfb_draw.h" +#include "libs/graphics/drawable.h" +#include "libs/graphics/font.h" + + +typedef struct anidata +{ + int transparent_color; + int colormap_index; + int hotspot_x; + int hotspot_y; +} AniData; + +extern uio_Repository *repository; +static uio_AutoMount *autoMount[] = { NULL }; + +static void +process_image (FRAME FramePtr, TFB_Canvas img[], AniData *ani, int cel_ct) +{ + TFB_Image *tfbimg; + int hx, hy; + + FramePtr->Type = ROM_DRAWABLE; + FramePtr->Index = cel_ct; + + // handle transparency cases + if (TFB_DrawCanvas_IsPaletted (img[cel_ct])) + { // indexed color image + if (ani[cel_ct].transparent_color >= 0) + { + TFB_DrawCanvas_SetTransparentIndex (img[cel_ct], + ani[cel_ct].transparent_color, FALSE); + } + } + else + { // special transparency cases for truecolor images + if (ani[cel_ct].transparent_color == 0) + { // make RGB=0,0,0 transparent + Color color = {0, 0, 0, 0}; + TFB_DrawCanvas_SetTransparentColor (img[cel_ct], color, FALSE); + } + } + if (ani[cel_ct].transparent_color == -1) + { // enforce -1 to mean 'no transparency' + TFB_DrawCanvas_SetTransparentIndex (img[cel_ct], -1, FALSE); + // set transparent_color == -2 to use PNG tRNS transparency + } + + hx = ani[cel_ct].hotspot_x; + hy = ani[cel_ct].hotspot_y; + + FramePtr->image = TFB_DrawImage_New (img[cel_ct]); + + tfbimg = FramePtr->image; + tfbimg->colormap_index = ani[cel_ct].colormap_index; + img[cel_ct] = tfbimg->NormalImg; + + FramePtr->HotSpot = MAKE_HOT_SPOT (hx, hy); + SetFrameBounds (FramePtr, tfbimg->extent.width, tfbimg->extent.height); + +#ifdef CLIPDEBUG + { + /* for debugging clipping: + draws white (or most matching color from palette) pixels to + every corner of the image + */ + Color color = {0xff, 0xff, 0xff, 0xff}; + RECT r = {{0, 0}, {1, 1}}; + if (tfbimg->extent.width > 2 && tfbimg->extent.height > 2) + { + TFB_DrawImage_Rect (&r, color, tfbimg); + r.corner.x = tfbimg->extent.width - 1; + TFB_DrawImage_Rect (&r, color, tfbimg); + r.corner.y = tfbimg->extent.height - 1; + TFB_DrawImage_Rect (&r, color, tfbimg); + r.corner.x = 0; + TFB_DrawImage_Rect (&r, color, tfbimg); + } + } +#endif +} + +static void +processFontChar (TFB_Char* CharPtr, TFB_Canvas canvas) +{ + BYTE* newdata; + size_t dpitch; + + TFB_DrawCanvas_GetExtent (canvas, &CharPtr->extent); + + // Currently, each font char has its own separate data + // but that can change to common mem area + dpitch = CharPtr->extent.width; + newdata = HMalloc (dpitch * CharPtr->extent.height * sizeof (BYTE)); + TFB_DrawCanvas_GetFontCharData (canvas, newdata, dpitch); + + CharPtr->data = newdata; + CharPtr->pitch = dpitch; + CharPtr->disp.width = CharPtr->extent.width + 1; + CharPtr->disp.height = CharPtr->extent.height + 1; + // XXX: why the +1? + // I brought it into this function from the only calling + // function, but I don't know why it was there in the first + // place. + // XXX: the +1 appears to be for character and line spacing + // text_blt just adds the frame width to move to the next char + + { + // This tunes the font positioning to be about what it should + // TODO: prolly needs a little tweaking still + + int tune_amount = 0; + + if (CharPtr->extent.height == 8) + tune_amount = -1; + else if (CharPtr->extent.height == 9) + tune_amount = -2; + else if (CharPtr->extent.height > 9) + tune_amount = -3; + + CharPtr->HotSpot = MAKE_HOT_SPOT (0, + CharPtr->extent.height + tune_amount); + } +} + +void * +_GetCelData (uio_Stream *fp, DWORD length) +{ + int cel_total, cel_index, n; + DWORD opos; + char CurrentLine[1024], filename[PATH_MAX]; + TFB_Canvas *img; + AniData *ani; + DRAWABLE Drawable; + uio_MountHandle *aniMount = 0; + uio_DirHandle *aniDir = 0; + uio_Stream *aniFile = 0; + + opos = uio_ftell (fp); + + { + char *s1, *s2; + char aniDirName[PATH_MAX]; + const char *aniFileName; + uint8 buf[4] = { 0, 0, 0, 0 }; + uint32 header; + + if (_cur_resfile_name == 0 + || (((s2 = 0), (s1 = strrchr (_cur_resfile_name, '/')) == 0) + && (s2 = strrchr (_cur_resfile_name, '\\')) == 0)) + { + n = 0; + } + else + { + if (s2 > s1) + s1 = s2; + n = s1 - _cur_resfile_name + 1; + } + + uio_fread(buf, 4, 1, fp); + header = buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); + if (_cur_resfile_name && header == 0x04034b50) + { + // zipped ani file + if (n) + { + strncpy (aniDirName, _cur_resfile_name, n - 1); + aniDirName[n - 1] = 0; + aniFileName = _cur_resfile_name + n; + } + else + { + strcpy(aniDirName, "."); + aniFileName = _cur_resfile_name; + } + aniDir = uio_openDir (repository, aniDirName, 0); + aniMount = uio_mountDir (repository, aniDirName, uio_FSTYPE_ZIP, + aniDir, aniFileName, "/", autoMount, + uio_MOUNT_RDONLY | uio_MOUNT_TOP, + NULL); + aniFile = uio_fopen (aniDir, aniFileName, "r"); + opos = 0; + n = 0; + } + else + { + // unpacked ani file + strncpy (filename, _cur_resfile_name, n); + aniFile = fp; + aniDir = contentDir; + } + } + + cel_total = 0; + uio_fseek (aniFile, opos, SEEK_SET); + while (uio_fgets (CurrentLine, sizeof (CurrentLine), aniFile)) + { + ++cel_total; + } + + img = HMalloc (sizeof (TFB_Canvas) * cel_total); + ani = HMalloc (sizeof (AniData) * cel_total); + if (!img || !ani) + { + log_add (log_Warning, "Couldn't allocate space for '%s'", _cur_resfile_name); + if (aniMount) + { + uio_fclose(aniFile); + uio_closeDir(aniDir); + uio_unmountDir(aniMount); + } + HFree (img); + HFree (ani); + return NULL; + } + + cel_index = 0; + uio_fseek (aniFile, opos, SEEK_SET); + while (uio_fgets (CurrentLine, sizeof (CurrentLine), aniFile) && cel_index < cel_total) + { + sscanf (CurrentLine, "%s %d %d %d %d", &filename[n], + &ani[cel_index].transparent_color, &ani[cel_index].colormap_index, + &ani[cel_index].hotspot_x, &ani[cel_index].hotspot_y); + + img[cel_index] = TFB_DrawCanvas_LoadFromFile (aniDir, filename); + if (img[cel_index] == NULL) + { + const char *err; + + err = TFB_DrawCanvas_GetError (); + log_add (log_Warning, "_GetCelData: Unable to load image!"); + if (err != NULL) + log_add (log_Warning, "Gfx Driver reports: %s", err); + } + else + { + ++cel_index; + } + + if ((int)uio_ftell (aniFile) - (int)opos >= (int)length) + break; + } + + Drawable = NULL; + if (cel_index && (Drawable = AllocDrawable (cel_index))) + { + if (!Drawable) + { + while (cel_index--) + TFB_DrawCanvas_Delete (img[cel_index]); + + HFree (Drawable); + Drawable = NULL; + } + else + { + FRAME FramePtr; + + Drawable->Flags = WANT_PIXMAP; + Drawable->MaxIndex = cel_index - 1; + + FramePtr = &Drawable->Frame[cel_index]; + while (--FramePtr, cel_index--) + process_image (FramePtr, img, ani, cel_index); + } + } + + if (Drawable == NULL) + log_add (log_Warning, "Couldn't get cel data for '%s'", + _cur_resfile_name); + + if (aniMount) + { + uio_fclose(aniFile); + uio_closeDir(aniDir); + uio_unmountDir(aniMount); + } + + HFree (img); + HFree (ani); + return Drawable; +} + +BOOLEAN +_ReleaseCelData (void *handle) +{ + DRAWABLE DrawablePtr; + int cel_ct; + FRAME FramePtr = NULL; + + if ((DrawablePtr = handle) == 0) + return (FALSE); + + cel_ct = DrawablePtr->MaxIndex + 1; + FramePtr = DrawablePtr->Frame; + + HFree (handle); + if (FramePtr) + { + int i; + for (i = 0; i < cel_ct; i++) + { + TFB_Image *img = FramePtr[i].image; + if (img) + { + FramePtr[i].image = NULL; + TFB_DrawScreen_DeleteImage (img); + } + } + HFree (FramePtr); + } + + return (TRUE); +} + +typedef struct BuildCharDesc +{ + TFB_Canvas canvas; + UniChar index; +} BuildCharDesc; + +static int +compareBCDIndex (const void *arg1, const void *arg2) +{ + const BuildCharDesc *bcd1 = (const BuildCharDesc *) arg1; + const BuildCharDesc *bcd2 = (const BuildCharDesc *) arg2; + + return (int) bcd1->index - (int) bcd2->index; +} + +void * +_GetFontData (uio_Stream *fp, DWORD length) +{ + COUNT numDirEntries; + DIRENTRY fontDir = NULL; + BuildCharDesc *bcds = NULL; + size_t numBCDs = 0; + int dirEntryI; + uio_DirHandle *fontDirHandle = NULL; + uio_MountHandle *fontMount = NULL; + FONT fontPtr = NULL; + + if (_cur_resfile_name == 0) + goto err; + + if (fp != (uio_Stream*)~0) + { + // font is zipped instead of being in a directory + + char *s1, *s2; + int n; + const char *fontZipName; + char fontDirName[PATH_MAX]; + + if ((((s2 = 0), (s1 = strrchr (_cur_resfile_name, '/')) == 0) + && (s2 = strrchr (_cur_resfile_name, '\\')) == 0)) + { + strcpy(fontDirName, "."); + fontZipName = _cur_resfile_name; + } + else + { + if (s2 > s1) + s1 = s2; + n = s1 - _cur_resfile_name + 1; + strncpy (fontDirName, _cur_resfile_name, n - 1); + fontDirName[n - 1] = 0; + fontZipName = _cur_resfile_name + n; + } + + fontDirHandle = uio_openDir (repository, fontDirName, 0); + fontMount = uio_mountDir (repository, _cur_resfile_name, uio_FSTYPE_ZIP, + fontDirHandle, fontZipName, "/", autoMount, + uio_MOUNT_RDONLY | uio_MOUNT_TOP, + NULL); + uio_closeDir (fontDirHandle); + } + + fontDir = CaptureDirEntryTable (LoadDirEntryTable (contentDir, + _cur_resfile_name, ".", match_MATCH_SUBSTRING)); + if (fontDir == 0) + goto err; + numDirEntries = GetDirEntryTableCount (fontDir); + + fontDirHandle = uio_openDirRelative (contentDir, _cur_resfile_name, 0); + if (fontDirHandle == NULL) + goto err; + + bcds = HMalloc (numDirEntries * sizeof (BuildCharDesc)); + if (bcds == NULL) + goto err; + + // Load the surfaces for all dir Entries + for (dirEntryI = 0; dirEntryI < numDirEntries; dirEntryI++) + { + char *char_name; + unsigned int charIndex; + TFB_Canvas canvas; + EXTENT size; + + char_name = GetDirEntryAddress (SetAbsDirEntryTableIndex ( + fontDir, dirEntryI)); + if (sscanf (char_name, "%x.", &charIndex) != 1) + continue; + + if (charIndex > 0xffff) + continue; + + canvas = TFB_DrawCanvas_LoadFromFile (fontDirHandle, char_name); + if (canvas == NULL) + continue; + + TFB_DrawCanvas_GetExtent (canvas, &size); + if (size.width == 0 || size.height == 0) + { + TFB_DrawCanvas_Delete (canvas); + continue; + } + + bcds[numBCDs].canvas = canvas; + bcds[numBCDs].index = charIndex; + numBCDs++; + } + uio_closeDir (fontDirHandle); + DestroyDirEntryTable (ReleaseDirEntryTable (fontDir)); + if (fontMount != 0) + uio_unmountDir(fontMount); + +#if 0 + if (numBCDs == 0) + goto err; +#endif + + // sort on the character index + qsort (bcds, numBCDs, sizeof (BuildCharDesc), compareBCDIndex); + + fontPtr = AllocFont (0); + if (fontPtr == NULL) + goto err; + + fontPtr->Leading = 0; + fontPtr->LeadingWidth = 0; + + { + size_t startBCD = 0; + UniChar pageStart; + FONT_PAGE **pageEndPtr = &fontPtr->fontPages; + while (startBCD < numBCDs) + { + // Process one character page. + size_t endBCD; + pageStart = bcds[startBCD].index & CHARACTER_PAGE_MASK; + + endBCD = startBCD; + while (endBCD < numBCDs && + (bcds[endBCD].index & CHARACTER_PAGE_MASK) == pageStart) + endBCD++; + + { + size_t bcdI; + int numChars = bcds[endBCD - 1].index + 1 + - bcds[startBCD].index; + FONT_PAGE *page = AllocFontPage (numChars); + page->pageStart = pageStart; + page->firstChar = bcds[startBCD].index; + page->numChars = numChars; + *pageEndPtr = page; + pageEndPtr = &page->next; + + for (bcdI = startBCD; bcdI < endBCD; bcdI++) + { + // Process one character. + BuildCharDesc *bcd = &bcds[bcdI]; + TFB_Char *destChar = + &page->charDesc[bcd->index - page->firstChar]; + + if (destChar->data != NULL) + { + // There's already an image for this character. + log_add (log_Debug, "Duplicate image for character %d " + "for font %s.", (int) bcd->index, + _cur_resfile_name); + TFB_DrawCanvas_Delete (bcd->canvas); + continue; + } + + processFontChar (destChar, bcd->canvas); + TFB_DrawCanvas_Delete (bcd->canvas); + + if (destChar->disp.height > fontPtr->Leading) + fontPtr->Leading = destChar->disp.height; + if (destChar->disp.width > fontPtr->LeadingWidth) + fontPtr->LeadingWidth = destChar->disp.width; + } + } + + startBCD = endBCD; + } + *pageEndPtr = NULL; + } + + fontPtr->Leading++; + + HFree (bcds); + + (void) fp; /* Satisfying compiler (unused parameter) */ + (void) length; /* Satisfying compiler (unused parameter) */ + return fontPtr; + +err: + if (fontPtr != 0) + HFree (fontPtr); + + if (bcds != NULL) + { + size_t bcdI; + for (bcdI = 0; bcdI < numBCDs; bcdI++) + TFB_DrawCanvas_Delete (bcds[bcdI].canvas); + HFree (bcds); + } + + if (fontDirHandle != NULL) + uio_closeDir (fontDirHandle); + + if (fontDir != 0) + DestroyDirEntryTable (ReleaseDirEntryTable (fontDir)); + + if (fontMount != 0) + uio_unmountDir(fontMount); + + return 0; +} + +BOOLEAN +_ReleaseFontData (void *handle) +{ + FONT font = (FONT) handle; + if (font == NULL) + return FALSE; + + { + FONT_PAGE *page; + FONT_PAGE *nextPage; + + for (page = font->fontPages; page != NULL; page = nextPage) + { + size_t charI; + for (charI = 0; charI < page->numChars; charI++) + { + TFB_Char *c = &page->charDesc[charI]; + + if (c->data == NULL) + continue; + + // XXX: fix this if fonts get per-page data + // rather than per-char + TFB_DrawScreen_DeleteData (c->data); + } + + nextPage = page->next; + FreeFontPage (page); + } + } + + HFree (font); + + return TRUE; +} diff --git a/src/libs/graphics/intersec.c b/src/libs/graphics/intersec.c new file mode 100644 index 0000000..02a5226 --- /dev/null +++ b/src/libs/graphics/intersec.c @@ -0,0 +1,415 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "libs/graphics/context.h" +#include "libs/graphics/drawable.h" +#include "libs/graphics/tfb_draw.h" +#include "libs/log.h" + +//#define DEBUG_INTERSEC + +static inline BOOLEAN +images_intersect (IMAGE_BOX *box1, IMAGE_BOX *box2, const RECT *rect) +{ + return TFB_DrawImage_Intersect (box1->FramePtr->image, box1->Box.corner, + box2->FramePtr->image, box2->Box.corner, rect); +} + +static TIME_VALUE +frame_intersect (INTERSECT_CONTROL *pControl0, RECT *pr0, + INTERSECT_CONTROL *pControl1, RECT *pr1, TIME_VALUE t0, + TIME_VALUE t1) +{ + SIZE time_error0, time_error1; + SIZE cycle0, cycle1; + SIZE dx_0, dy_0, dx_1, dy_1; + SIZE xincr0, yincr0, xincr1, yincr1; + SIZE xerror0, xerror1, yerror0, yerror1; + RECT r_intersect; + IMAGE_BOX IB0, IB1; + BOOLEAN check0, check1; + + IB0.FramePtr = pControl0->IntersectStamp.frame; + IB0.Box.corner = pr0->corner; + IB0.Box.extent.width = GetFrameWidth (IB0.FramePtr); + IB0.Box.extent.height = GetFrameHeight (IB0.FramePtr); + IB1.FramePtr = pControl1->IntersectStamp.frame; + IB1.Box.corner = pr1->corner; + IB1.Box.extent.width = GetFrameWidth (IB1.FramePtr); + IB1.Box.extent.height = GetFrameHeight (IB1.FramePtr); + + dx_0 = pr0->extent.width; + dy_0 = pr0->extent.height; + if (dx_0 >= 0) + xincr0 = 1; + else + { + xincr0 = -1; + dx_0 = -dx_0; + } + if (dy_0 >= 0) + yincr0 = 1; + else + { + yincr0 = -1; + dy_0 = -dy_0; + } + if (dx_0 >= dy_0) + cycle0 = dx_0; + else + cycle0 = dy_0; + xerror0 = yerror0 = cycle0; + + dx_1 = pr1->extent.width; + dy_1 = pr1->extent.height; + if (dx_1 >= 0) + xincr1 = 1; + else + { + xincr1 = -1; + dx_1 = -dx_1; + } + if (dy_1 >= 0) + yincr1 = 1; + else + { + yincr1 = -1; + dy_1 = -dy_1; + } + if (dx_1 >= dy_1) + cycle1 = dx_1; + else + cycle1 = dy_1; + xerror1 = yerror1 = cycle1; + + check0 = check1 = FALSE; + if (t0 <= 1) + { + time_error0 = time_error1 = 0; + if (t0 == 0) + { + ++t0; + goto CheckFirstIntersection; + } + } + else + { + SIZE delta; + COUNT start; + long error; + + start = (COUNT)cycle0 * (COUNT)(t0 - 1); + time_error0 = start & ((1 << TIME_SHIFT) - 1); + if ((start >>= (COUNT)TIME_SHIFT) > 0) + { + if ((error = (long)xerror0 + - (long)dx_0 * (long)start) > 0) + xerror0 = (SIZE)error; + else + { + delta = -(SIZE)(error / (long)cycle0) + 1; + IB0.Box.corner.x += xincr0 * delta; + xerror0 = (SIZE)(error + (long)cycle0 * (long)delta); + } + if ((error = (long)yerror0 + - (long)dy_0 * (long)start) > 0) + yerror0 = (SIZE)error; + else + { + delta = -(SIZE)(error / (long)cycle0) + 1; + IB0.Box.corner.y += yincr0 * delta; + yerror0 = (SIZE)(error + (long)cycle0 * (long)delta); + } + pr0->corner = IB0.Box.corner; + } + + start = (COUNT)cycle1 * (COUNT)(t0 - 1); + time_error1 = start & ((1 << TIME_SHIFT) - 1); + if ((start >>= (COUNT)TIME_SHIFT) > 0) + { + if ((error = (long)xerror1 + - (long)dx_1 * (long)start) > 0) + xerror1 = (SIZE)error; + else + { + delta = -(SIZE)(error / (long)cycle1) + 1; + IB1.Box.corner.x += xincr1 * delta; + xerror1 = (SIZE)(error + (long)cycle1 * (long)delta); + } + if ((error = (long)yerror1 + - (long)dy_1 * (long)start) > 0) + yerror1 = (SIZE)error; + else + { + delta = -(SIZE)(error / (long)cycle1) + 1; + IB1.Box.corner.y += yincr1 * delta; + yerror1 = (SIZE)(error + (long)cycle1 * (long)delta); + } + pr1->corner = IB1.Box.corner; + } + } + + pControl0->last_time_val = pControl1->last_time_val = t0; + do + { + ++t0; + if ((time_error0 += cycle0) >= (1 << TIME_SHIFT)) + { + if ((xerror0 -= dx_0) <= 0) + { + IB0.Box.corner.x += xincr0; + xerror0 += cycle0; + } + if ((yerror0 -= dy_0) <= 0) + { + IB0.Box.corner.y += yincr0; + yerror0 += cycle0; + } + + check0 = TRUE; + time_error0 -= (1 << TIME_SHIFT); + } + + if ((time_error1 += cycle1) >= (1 << TIME_SHIFT)) + { + if ((xerror1 -= dx_1) <= 0) + { + IB1.Box.corner.x += xincr1; + xerror1 += cycle1; + } + if ((yerror1 -= dy_1) <= 0) + { + IB1.Box.corner.y += yincr1; + yerror1 += cycle1; + } + + check1 = TRUE; + time_error1 -= (1 << TIME_SHIFT); + } + + if (check0 || check1) + { /* if check0 && check1, this may not be quite right -- + * if shapes had a pixel's separation to begin with + * and both moved toward each other, you would actually + * get a pixel overlap but since the last positions were + * separated by a pixel, the shapes wouldn't be touching + * each other. + */ +CheckFirstIntersection: + if (BoxIntersect (&IB0.Box, &IB1.Box, &r_intersect) + && images_intersect (&IB0, &IB1, &r_intersect)) + return (t0); + + if (check0) + { + pr0->corner = IB0.Box.corner; + pControl0->last_time_val = t0; + check0 = FALSE; + } + if (check1) + { + pr1->corner = IB1.Box.corner; + pControl1->last_time_val = t0; + check1 = FALSE; + } + } + } while (t0 <= t1); + + return ((TIME_VALUE)0); +} + +TIME_VALUE +DrawablesIntersect (INTERSECT_CONTROL *pControl0, + INTERSECT_CONTROL *pControl1, TIME_VALUE max_time_val) +{ + SIZE dy; + SIZE time_y_0, time_y_1; + RECT r0, r1; + FRAME FramePtr0, FramePtr1; + + if (!ContextActive () || max_time_val == 0) + return ((TIME_VALUE)0); + else if (max_time_val > MAX_TIME_VALUE) + max_time_val = MAX_TIME_VALUE; + + pControl0->last_time_val = pControl1->last_time_val = 0; + + r0.corner = pControl0->IntersectStamp.origin; + r1.corner = pControl1->IntersectStamp.origin; + + r0.extent.width = pControl0->EndPoint.x - r0.corner.x; + r0.extent.height = pControl0->EndPoint.y - r0.corner.y; + r1.extent.width = pControl1->EndPoint.x - r1.corner.x; + r1.extent.height = pControl1->EndPoint.y - r1.corner.y; + + FramePtr0 = pControl0->IntersectStamp.frame; + if (FramePtr0 == 0) + return(0); + r0.corner.x -= FramePtr0->HotSpot.x; + r0.corner.y -= FramePtr0->HotSpot.y; + + FramePtr1 = pControl1->IntersectStamp.frame; + if (FramePtr1 == 0) + return(0); + r1.corner.x -= FramePtr1->HotSpot.x; + r1.corner.y -= FramePtr1->HotSpot.y; + + dy = r1.corner.y - r0.corner.y; + time_y_0 = dy - GetFrameHeight (FramePtr0) + 1; + time_y_1 = dy + GetFrameHeight (FramePtr1) - 1; + dy = r0.extent.height - r1.extent.height; + + if ((time_y_0 <= 0 && time_y_1 >= 0) + || (time_y_0 > 0 && dy >= time_y_0) + || (time_y_1 < 0 && dy <= time_y_1)) + { + SIZE dx; + SIZE time_x_0, time_x_1; + + dx = r1.corner.x - r0.corner.x; + time_x_0 = dx - GetFrameWidth (FramePtr0) + 1; + time_x_1 = dx + GetFrameWidth (FramePtr1) - 1; + dx = r0.extent.width - r1.extent.width; + + if ((time_x_0 <= 0 && time_x_1 >= 0) + || (time_x_0 > 0 && dx >= time_x_0) + || (time_x_1 < 0 && dx <= time_x_1)) + { + TIME_VALUE intersect_time; + + if (dx == 0 && dy == 0) + time_y_0 = time_y_1 = 0; + else + { + SIZE t; + long time_beg, time_end, fract; + + if (time_y_1 < 0) + { + t = time_y_0; + time_y_0 = -time_y_1; + time_y_1 = -t; + } + else if (time_y_0 <= 0) + { + if (dy < 0) + time_y_1 = -time_y_0; + time_y_0 = 0; + } + if (dy < 0) + dy = -dy; + if (dy < time_y_1) + time_y_1 = dy; + /* just to be safe, widen search area */ + --time_y_0; + ++time_y_1; + + if (time_x_1 < 0) + { + t = time_x_0; + time_x_0 = -time_x_1; + time_x_1 = -t; + } + else if (time_x_0 <= 0) + { + if (dx < 0) + time_x_1 = -time_x_0; + time_x_0 = 0; + } + if (dx < 0) + dx = -dx; + if (dx < time_x_1) + time_x_1 = dx; + /* just to be safe, widen search area */ + --time_x_0; + ++time_x_1; + +#ifdef DEBUG_INTERSEC + log_add (log_Debug, "FramePtr0<%d, %d> --> <%d, %d>", + GetFrameWidth (FramePtr0), GetFrameHeight (FramePtr0), + r0.corner.x, r0.corner.y); + log_add (log_Debug, "FramePtr1<%d, %d> --> <%d, %d>", + GetFrameWidth (FramePtr1), GetFrameHeight (FramePtr1), + r1.corner.x, r1.corner.y); + log_add (log_Debug, "time_x(%d, %d)-%d, time_y(%d, %d)-%d", + time_x_0, time_x_1, dx, time_y_0, time_y_1, dy); +#endif /* DEBUG_INTERSEC */ + if (dx == 0) + { + time_beg = time_y_0; + time_end = time_y_1; + fract = dy; + } + else if (dy == 0) + { + time_beg = time_x_0; + time_end = time_x_1; + fract = dx; + } + else + { + long time_x, time_y; + + time_x = (long)time_x_0 * (long)dy; + time_y = (long)time_y_0 * (long)dx; + time_beg = time_x < time_y ? time_y : time_x; + + time_x = (long)time_x_1 * (long)dy; + time_y = (long)time_y_1 * (long)dx; + time_end = time_x > time_y ? time_y : time_x; + + fract = (long)dx * (long)dy; + } + + if ((time_beg <<= TIME_SHIFT) < fract) + time_y_0 = 0; + else + time_y_0 = (SIZE)(time_beg / fract); + + if (time_end >= fract /* just in case of overflow */ + || (time_end <<= TIME_SHIFT) >= + fract * (long)max_time_val) + time_y_1 = max_time_val - 1; + else + time_y_1 = (SIZE)((time_end + fract - 1) / fract) - 1; + } + +#ifdef DEBUG_INTERSEC + log_add (log_Debug, "start_time = %d, end_time = %d", + time_y_0, time_y_1); +#endif /* DEBUG_INTERSEC */ + if (time_y_0 <= time_y_1 + && (intersect_time = frame_intersect ( + pControl0, &r0, pControl1, &r1, + (TIME_VALUE)time_y_0, (TIME_VALUE)time_y_1))) + { + FramePtr0 = pControl0->IntersectStamp.frame; + pControl0->EndPoint.x = r0.corner.x + FramePtr0->HotSpot.x; + pControl0->EndPoint.y = r0.corner.y + FramePtr0->HotSpot.y; + FramePtr1 = pControl1->IntersectStamp.frame; + pControl1->EndPoint.x = r1.corner.x + FramePtr1->HotSpot.x; + pControl1->EndPoint.y = r1.corner.y + FramePtr1->HotSpot.y; + + return (intersect_time); + } + } + } + + return ((TIME_VALUE)0); +} + diff --git a/src/libs/graphics/loaddisp.c b/src/libs/graphics/loaddisp.c new file mode 100644 index 0000000..ccc7919 --- /dev/null +++ b/src/libs/graphics/loaddisp.c @@ -0,0 +1,65 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "libs/gfxlib.h" +#include "libs/graphics/drawable.h" +#include "libs/log.h" + + +// Reads a piece of screen into a passed FRAME or a newly created one +DRAWABLE +LoadDisplayPixmap (const RECT *area, FRAME frame) +{ + // TODO: This should just return a FRAME instead of DRAWABLE + DRAWABLE buffer = GetFrameParentDrawable (frame); + COUNT index; + + if (!buffer) + { // asked to create a new DRAWABLE instead + buffer = CreateDrawable (WANT_PIXMAP | MAPPED_TO_DISPLAY, + area->extent.width, area->extent.height, 1); + if (!buffer) + return NULL; + + index = 0; + } + else + { + index = GetFrameIndex (frame); + } + + frame = SetAbsFrameIndex (CaptureDrawable (buffer), index); + + if (_CurFramePtr->Type != SCREEN_DRAWABLE + || frame->Type == SCREEN_DRAWABLE + || !(GetFrameParentDrawable (frame)->Flags & MAPPED_TO_DISPLAY)) + { + log_add (log_Warning, "Unimplemented function activated: " + "LoadDisplayPixmap()"); + } + else + { + TFB_Image *img = frame->image; + TFB_DrawScreen_CopyToImage (img, area, TFB_SCREEN_MAIN); + } + + ReleaseDrawable (frame); + + return buffer; +} + diff --git a/src/libs/graphics/pixmap.c b/src/libs/graphics/pixmap.c new file mode 100644 index 0000000..6e68244 --- /dev/null +++ b/src/libs/graphics/pixmap.c @@ -0,0 +1,170 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "gfxintrn.h" +#include "libs/log.h" + +DRAWABLE +GetFrameParentDrawable (FRAME f) +{ + if (f != NULL) + { + return f->parent; + } + return NULL; +} + +FRAME +CaptureDrawable (DRAWABLE DrawablePtr) +{ + if (DrawablePtr) + { + return &DrawablePtr->Frame[0]; + } + + return NULL; +} + +DRAWABLE +ReleaseDrawable (FRAME FramePtr) +{ + if (FramePtr != 0) + { + DRAWABLE Drawable; + + Drawable = GetFrameParentDrawable (FramePtr); + + return (Drawable); + } + + return NULL; +} + +COUNT +GetFrameCount (FRAME FramePtr) +{ + DRAWABLE_DESC *DrawablePtr; + + if (FramePtr == 0) + return (0); + + DrawablePtr = GetFrameParentDrawable (FramePtr); + return DrawablePtr->MaxIndex + 1; +} + +COUNT +GetFrameIndex (FRAME FramePtr) +{ + if (FramePtr == 0) + return (0); + + return FramePtr->Index; +} + +FRAME +SetAbsFrameIndex (FRAME FramePtr, COUNT FrameIndex) +{ + if (FramePtr != 0) + { + DRAWABLE_DESC *DrawablePtr; + + DrawablePtr = GetFrameParentDrawable (FramePtr); + + FrameIndex = FrameIndex % (DrawablePtr->MaxIndex + 1); + FramePtr = &DrawablePtr->Frame[FrameIndex]; + } + + return FramePtr; +} + +FRAME +SetRelFrameIndex (FRAME FramePtr, SIZE FrameOffs) +{ + if (FramePtr != 0) + { + COUNT num_frames; + DRAWABLE_DESC *DrawablePtr; + + DrawablePtr = GetFrameParentDrawable (FramePtr); + num_frames = DrawablePtr->MaxIndex + 1; + if (FrameOffs < 0) + { + while ((FrameOffs += num_frames) < 0) + ; + } + + FrameOffs = ((SWORD)FramePtr->Index + FrameOffs) % num_frames; + FramePtr = &DrawablePtr->Frame[FrameOffs]; + } + + return FramePtr; +} + +FRAME +SetEquFrameIndex (FRAME DstFramePtr, FRAME SrcFramePtr) +{ + COUNT Index; + + if (!DstFramePtr || !SrcFramePtr) + return 0; + + Index = GetFrameIndex (SrcFramePtr); +#ifdef DEBUG + { + DRAWABLE_DESC *DrawablePtr = GetFrameParentDrawable (DstFramePtr); + if (Index > DrawablePtr->MaxIndex) + log_add (log_Debug, "SetEquFrameIndex: source index (%d) beyond " + "destination range (%d)", (int)Index, + (int)DrawablePtr->MaxIndex); + } +#endif + + return SetAbsFrameIndex (DstFramePtr, Index); +} + +FRAME +IncFrameIndex (FRAME FramePtr) +{ + DRAWABLE_DESC *DrawablePtr; + + if (FramePtr == 0) + return (0); + + DrawablePtr = GetFrameParentDrawable (FramePtr); + if (FramePtr->Index < DrawablePtr->MaxIndex) + return ++FramePtr; + else + return DrawablePtr->Frame; +} + +FRAME +DecFrameIndex (FRAME FramePtr) +{ + if (FramePtr == 0) + return (0); + + if (FramePtr->Index > 0) + return --FramePtr; + else + { + DRAWABLE_DESC *DrawablePtr; + + DrawablePtr = GetFrameParentDrawable (FramePtr); + return &DrawablePtr->Frame[DrawablePtr->MaxIndex]; + } +} diff --git a/src/libs/graphics/prim.h b/src/libs/graphics/prim.h new file mode 100644 index 0000000..bce3c2a --- /dev/null +++ b/src/libs/graphics/prim.h @@ -0,0 +1,80 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef LIBS_GRAPHICS_PRIM_H_ +#define LIBS_GRAPHICS_PRIM_H_ + +enum gfx_object +{ + POINT_PRIM = 0, + STAMP_PRIM, + STAMPFILL_PRIM, + LINE_PRIM, + TEXT_PRIM, + RECT_PRIM, + RECTFILL_PRIM, + + NUM_PRIMS +}; +typedef BYTE GRAPHICS_PRIM; + +typedef union +{ + POINT Point; + STAMP Stamp; + LINE Line; + TEXT Text; + RECT Rect; +} PRIM_DESC; + +typedef DWORD PRIM_LINKS; + +typedef struct +{ + PRIM_LINKS Links; + GRAPHICS_PRIM Type; + Color color; + PRIM_DESC Object; +} PRIMITIVE; + +#define END_OF_LIST ((COUNT)0xFFFF) + +#define GetPredLink(l) LOWORD(l) +#define GetSuccLink(l) HIWORD(l) +#define MakeLinks MAKE_DWORD +#define SetPrimLinks(pPrim,p,s) ((pPrim)->Links = MakeLinks (p, s)) +#define GetPrimLinks(pPrim) ((pPrim)->Links) +#define SetPrimType(pPrim,t) ((pPrim)->Type = t) +#define GetPrimType(pPrim) ((pPrim)->Type) +#define SetPrimColor(pPrim,c) ((pPrim)->color = c) +#define GetPrimColor(pPrim) ((pPrim)->color) + +static inline void +SetPrimNextLink (PRIMITIVE *pPrim, COUNT Link) +{ + SetPrimLinks (pPrim, END_OF_LIST, Link); +} + + +static inline COUNT +GetPrimNextLink (PRIMITIVE *pPrim) +{ + return GetSuccLink (GetPrimLinks (pPrim)); +} + + +#endif /* PRIM_H */ + + diff --git a/src/libs/graphics/resgfx.c b/src/libs/graphics/resgfx.c new file mode 100644 index 0000000..c0760dc --- /dev/null +++ b/src/libs/graphics/resgfx.c @@ -0,0 +1,54 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "gfxintrn.h" + +static void +GetCelFileData (const char *pathname, RESOURCE_DATA *resdata) +{ + resdata->ptr = LoadResourceFromPath (pathname, _GetCelData); +} + +static void +GetFontFileData (const char *pathname, RESOURCE_DATA *resdata) +{ + resdata->ptr = LoadResourceFromPath (pathname, _GetFontData); +} + + +BOOLEAN +InstallGraphicResTypes (void) +{ + InstallResTypeVectors ("GFXRES", GetCelFileData, _ReleaseCelData, NULL); + InstallResTypeVectors ("FONTRES", GetFontFileData, _ReleaseFontData, NULL); + return (TRUE); +} + +/* Needs to be void * because it could be either a DRAWABLE or a FONT. */ +void * +LoadGraphicInstance (RESOURCE res) +{ + void *hData; + + hData = res_GetResource (res); + if (hData) + res_DetachResource (res); + + return (hData); +} + diff --git a/src/libs/graphics/sdl/2xscalers.c b/src/libs/graphics/sdl/2xscalers.c new file mode 100644 index 0000000..a612d2c --- /dev/null +++ b/src/libs/graphics/sdl/2xscalers.c @@ -0,0 +1,260 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "libs/graphics/sdl/sdl_common.h" +#include "types.h" +#include "scalers.h" +#include "scaleint.h" +#include "2xscalers.h" + + +// Scaler function lookup table +// +const Scale_FuncDef_t +Scale_C_Functions[] = +{ + {TFB_GFXFLAGS_SCALE_BILINEAR, Scale_BilinearFilter}, + {TFB_GFXFLAGS_SCALE_BIADAPT, Scale_BiAdaptFilter}, + {TFB_GFXFLAGS_SCALE_BIADAPTADV, Scale_BiAdaptAdvFilter}, + {TFB_GFXFLAGS_SCALE_TRISCAN, Scale_TriScanFilter}, + {TFB_GFXFLAGS_SCALE_HQXX, Scale_HqFilter}, + // Default + {0, Scale_Nearest} +}; + +// See +// nearest2x.c -- Nearest Neighboor scaling +// bilinear2x.c -- Bilinear scaling +// biadv2x.c -- Advanced Biadapt scaling +// triscan2x.c -- Triscan scaling + +// Biadapt scaling to 2x +void +SCALE_(BiAdaptFilter) (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r) +{ + int x, y; + const int w = src->w, h = src->h; + int xend, yend; + int dsrc, ddst; + SDL_Rect *region = r; + SDL_Rect limits; + SDL_PixelFormat *fmt = dst->format; + const int sp = src->pitch, dp = dst->pitch; + const int bpp = fmt->BytesPerPixel; + const int slen = sp / bpp, dlen = dp / bpp; + Uint32 *src_p = (Uint32 *)src->pixels; + Uint32 *dst_p = (Uint32 *)dst->pixels; + Uint32 pixval_tl, pixval_tr, pixval_bl, pixval_br; + + // these macros are for clarity; they make the current pixel (0,0) + // and allow to access pixels in all directions + #define SRC(x, y) (src_p + (x) + ((y) * slen)) + + SCALE_(PlatInit) (); + + // expand updated region if necessary + // pixels neighbooring the updated region may + // change as a result of updates + limits.x = 0; + limits.y = 0; + limits.w = src->w; + limits.h = src->h; + Scale_ExpandRect (region, 2, &limits); + + xend = region->x + region->w; + yend = region->y + region->h; + dsrc = slen - region->w; + ddst = (dlen - region->w) * 2; + + // move ptrs to the first updated pixel + src_p += slen * region->y + region->x; + dst_p += (dlen * region->y + region->x) * 2; + + for (y = region->y; y < yend; ++y, dst_p += ddst, src_p += dsrc) + { + for (x = region->x; x < xend; ++x, ++src_p, ++dst_p) + { + pixval_tl = SCALE_GETPIX (SRC (0, 0)); + + SCALE_SETPIX (dst_p, pixval_tl); + + if (y + 1 < h) + { + // check pixel below the current one + pixval_bl = SCALE_GETPIX (SRC (0, 1)); + + if (pixval_tl == pixval_bl) + SCALE_SETPIX (dst_p + dlen, pixval_tl); + else + SCALE_SETPIX (dst_p + dlen, Scale_Blend_11 ( + pixval_tl, pixval_bl) + ); + } + else + { + // last pixel in column - propagate + SCALE_SETPIX (dst_p + dlen, pixval_tl); + pixval_bl = pixval_tl; + } + ++dst_p; + + if (x + 1 >= w) + { + // last pixel in row - propagate + SCALE_SETPIX (dst_p, pixval_tl); + + if (pixval_tl == pixval_bl) + SCALE_SETPIX (dst_p + dlen, pixval_tl); + else + SCALE_SETPIX (dst_p + dlen, Scale_Blend_11 ( + pixval_tl, pixval_bl) + ); + continue; + } + + // check pixel to the right from the current one + pixval_tr = SCALE_GETPIX (SRC (1, 0)); + + if (pixval_tl == pixval_tr) + SCALE_SETPIX (dst_p, pixval_tr); + else + SCALE_SETPIX (dst_p, Scale_Blend_11 ( + pixval_tl, pixval_tr) + ); + + if (y + 1 >= h) + { + // last pixel in column - propagate + SCALE_SETPIX (dst_p + dlen, pixval_tl); + continue; + } + + // check pixel to the bottom-right + pixval_br = SCALE_GETPIX (SRC (1, 1)); + + if (pixval_tl == pixval_br && pixval_tr == pixval_bl) + { + int cl, cr; + Uint32 clr; + + if (pixval_tl == pixval_tr) + { + // all 4 are equal - propagate + SCALE_SETPIX (dst_p + dlen, pixval_tl); + continue; + } + + // both pairs are equal, have to resolve the pixel + // race; we try detecting which color is + // the background by looking for a line or an edge + // examine 8 pixels surrounding the current quad + + cl = cr = 1; + + if (x > 0) + { + clr = SCALE_GETPIX (SRC (-1, 0)); + if (clr == pixval_tl) + cl++; + else if (clr == pixval_tr) + cr++; + + clr = SCALE_GETPIX (SRC (-1, 1)); + if (clr == pixval_tl) + cl++; + else if (clr == pixval_tr) + cr++; + } + + if (y > 0) + { + clr = SCALE_GETPIX (SRC (0, -1)); + if (clr == pixval_tl) + cl++; + else if (clr == pixval_tr) + cr++; + + clr = SCALE_GETPIX (SRC (1, -1)); + if (clr == pixval_tl) + cl++; + else if (clr == pixval_tr) + cr++; + } + + if (x + 2 < w) + { + clr = SCALE_GETPIX (SRC (2, 0)); + if (clr == pixval_tl) + cl++; + else if (clr == pixval_tr) + cr++; + + clr = SCALE_GETPIX (SRC (2, 1)); + if (clr == pixval_tl) + cl++; + else if (clr == pixval_tr) + cr++; + } + + if (y + 2 < h) + { + clr = SCALE_GETPIX (SRC (0, 2)); + if (clr == pixval_tl) + cl++; + else if (clr == pixval_tr) + cr++; + + clr = SCALE_GETPIX (SRC (1, 2)); + if (clr == pixval_tl) + cl++; + else if (clr == pixval_tr) + cr++; + } + + // least count wins + if (cl > cr) + SCALE_SETPIX (dst_p + dlen, pixval_tr); + else if (cr > cl) + SCALE_SETPIX (dst_p + dlen, pixval_tl); + else + SCALE_SETPIX (dst_p + dlen, + Scale_Blend_11 (pixval_tl, pixval_tr)); + } + else if (pixval_tl == pixval_br) + { + // main diagonal is same color + // use its value + SCALE_SETPIX (dst_p + dlen, pixval_tl); + } + else if (pixval_tr == pixval_bl) + { + // 2nd diagonal is same color + // use its value + SCALE_SETPIX (dst_p + dlen, pixval_tr); + } + else + { + // blend all 4 + SCALE_SETPIX (dst_p + dlen, Scale_Blend_1111 ( + pixval_tl, pixval_bl, pixval_tr, pixval_br + )); + } + } + } + + SCALE_(PlatDone) (); +} + diff --git a/src/libs/graphics/sdl/2xscalers.h b/src/libs/graphics/sdl/2xscalers.h new file mode 100644 index 0000000..f004fcd --- /dev/null +++ b/src/libs/graphics/sdl/2xscalers.h @@ -0,0 +1,30 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_GRAPHICS_SDL_2XSCALERS_H_ +#define LIBS_GRAPHICS_SDL_2XSCALERS_H_ + +void Scale_Nearest (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r); +void Scale_BilinearFilter (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r); +void Scale_BiAdaptFilter (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r); +void Scale_BiAdaptAdvFilter (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r); +void Scale_TriScanFilter (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r); +void Scale_HqFilter (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r); + +extern const Scale_FuncDef_t Scale_C_Functions[]; + + +#endif /* LIBS_GRAPHICS_SDL_2XSCALERS_H_ */ diff --git a/src/libs/graphics/sdl/2xscalers_3dnow.c b/src/libs/graphics/sdl/2xscalers_3dnow.c new file mode 100644 index 0000000..da34e82 --- /dev/null +++ b/src/libs/graphics/sdl/2xscalers_3dnow.c @@ -0,0 +1,102 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "port.h" +#include "libs/platform.h" + +#if defined(MMX_ASM) + +#include "libs/graphics/sdl/sdl_common.h" +#include "types.h" +#include "scalers.h" +#include "scaleint.h" +#include "2xscalers.h" +#include "2xscalers_mmx.h" + +// 3DNow! name for all functions +#undef SCALE_ +#define SCALE_(name) Scale ## _3DNow_ ## name + +// Tell them which opcodes we want to support +#undef USE_MOVNTQ +#define USE_PREFETCH AMD_PREFETCH +#undef USE_PSADBW +// Bring in inline asm functions +#include "scalemmx.h" + + +// Scaler function lookup table +// +const Scale_FuncDef_t +Scale_3DNow_Functions[] = +{ + {TFB_GFXFLAGS_SCALE_BILINEAR, Scale_3DNow_BilinearFilter}, + {TFB_GFXFLAGS_SCALE_BIADAPT, Scale_BiAdaptFilter}, + {TFB_GFXFLAGS_SCALE_BIADAPTADV, Scale_MMX_BiAdaptAdvFilter}, + {TFB_GFXFLAGS_SCALE_TRISCAN, Scale_MMX_TriScanFilter}, + {TFB_GFXFLAGS_SCALE_HQXX, Scale_MMX_HqFilter}, + // Default + {0, Scale_3DNow_Nearest} +}; + + +void +Scale_3DNow_PrepPlatform (const SDL_PixelFormat* fmt) +{ + Scale_MMX_PrepPlatform (fmt); +} + +// Nearest Neighbor scaling to 2x +// void Scale_3DNow_Nearest (SDL_Surface *src, +// SDL_Surface *dst, SDL_Rect *r) + +#include "nearest2x.c" + + +// Bilinear scaling to 2x +// void Scale_3DNow_BilinearFilter (SDL_Surface *src, +// SDL_Surface *dst, SDL_Rect *r) + +#include "bilinear2x.c" + + +#if 0 && NO_IMPROVEMENT + +// Advanced Biadapt scaling to 2x +// void Scale_3DNow_BiAdaptAdvFilter (SDL_Surface *src, +// SDL_Surface *dst, SDL_Rect *r) + +#include "biadv2x.c" + + +// Triscan scaling to 2x +// derivative of scale2x -- scale2x.sf.net +// void Scale_3DNow_TriScanFilter (SDL_Surface *src, +// SDL_Surface *dst, SDL_Rect *r) + +#include "triscan2x.c" + +// Hq2x scaling +// (adapted from 'hq2x' by Maxim Stepin -- www.hiend3d.com/hq2x.html) +// void Scale_3DNow_HqFilter (SDL_Surface *src, +// SDL_Surface *dst, SDL_Rect *r) + +#include "hq2x.c" + +#endif /* NO_IMPROVEMENT */ + +#endif /* MMX_ASM */ + diff --git a/src/libs/graphics/sdl/2xscalers_mmx.c b/src/libs/graphics/sdl/2xscalers_mmx.c new file mode 100644 index 0000000..c321b16 --- /dev/null +++ b/src/libs/graphics/sdl/2xscalers_mmx.c @@ -0,0 +1,136 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "port.h" +#include "libs/platform.h" + +#if defined(MMX_ASM) + +#include "libs/graphics/sdl/sdl_common.h" +#include "types.h" +#include "scalers.h" +#include "scaleint.h" +#include "2xscalers.h" +#include "2xscalers_mmx.h" + +// MMX name for all functions +#undef SCALE_ +#define SCALE_(name) Scale ## _MMX_ ## name + +// Tell them which opcodes we want to support +#undef USE_MOVNTQ +#undef USE_PREFETCH +#undef USE_PSADBW +// And Bring in inline asm functions +#include "scalemmx.h" + + +// Scaler function lookup table +// +const Scale_FuncDef_t +Scale_MMX_Functions[] = +{ + {TFB_GFXFLAGS_SCALE_BILINEAR, Scale_MMX_BilinearFilter}, + {TFB_GFXFLAGS_SCALE_BIADAPT, Scale_BiAdaptFilter}, + {TFB_GFXFLAGS_SCALE_BIADAPTADV, Scale_MMX_BiAdaptAdvFilter}, + {TFB_GFXFLAGS_SCALE_TRISCAN, Scale_MMX_TriScanFilter}, + {TFB_GFXFLAGS_SCALE_HQXX, Scale_MMX_HqFilter}, + // Default + {0, Scale_MMX_Nearest} +}; + +// MMX transformation multipliers +Uint64 mmx_888to555_mult; +Uint64 mmx_Y_mult; +Uint64 mmx_U_mult; +Uint64 mmx_V_mult; +// Uint64 mmx_YUV_threshold = 0x00300706; original hq2x threshold +//Uint64 mmx_YUV_threshold = 0x0030100e; +Uint64 mmx_YUV_threshold = 0x0040120c; + +void +Scale_MMX_PrepPlatform (const SDL_PixelFormat* fmt) +{ + // prepare the channel-shuffle multiplier + mmx_888to555_mult = ((Uint64)0x0400) << (fmt->Rshift * 2) + | ((Uint64)0x0020) << (fmt->Gshift * 2) + | ((Uint64)0x0001) << (fmt->Bshift * 2); + + // prepare the RGB->YUV multipliers + mmx_Y_mult = ((Uint64)(uint16)YUV_matrix[YUV_XFORM_R][YUV_XFORM_Y]) + << (fmt->Rshift * 2) + | ((Uint64)(uint16)YUV_matrix[YUV_XFORM_G][YUV_XFORM_Y]) + << (fmt->Gshift * 2) + | ((Uint64)(uint16)YUV_matrix[YUV_XFORM_B][YUV_XFORM_Y]) + << (fmt->Bshift * 2); + + mmx_U_mult = ((Uint64)(uint16)YUV_matrix[YUV_XFORM_R][YUV_XFORM_U]) + << (fmt->Rshift * 2) + | ((Uint64)(uint16)YUV_matrix[YUV_XFORM_G][YUV_XFORM_U]) + << (fmt->Gshift * 2) + | ((Uint64)(uint16)YUV_matrix[YUV_XFORM_B][YUV_XFORM_U]) + << (fmt->Bshift * 2); + + mmx_V_mult = ((Uint64)(uint16)YUV_matrix[YUV_XFORM_R][YUV_XFORM_V]) + << (fmt->Rshift * 2) + | ((Uint64)(uint16)YUV_matrix[YUV_XFORM_G][YUV_XFORM_V]) + << (fmt->Gshift * 2) + | ((Uint64)(uint16)YUV_matrix[YUV_XFORM_B][YUV_XFORM_V]) + << (fmt->Bshift * 2); + + mmx_YUV_threshold = (SCALE_DIFFYUV_TY << 16) | (SCALE_DIFFYUV_TU << 8) + | SCALE_DIFFYUV_TV; +} + + +// Nearest Neighbor scaling to 2x +// void Scale_MMX_Nearest (SDL_Surface *src, +// SDL_Surface *dst, SDL_Rect *r) + +#include "nearest2x.c" + + +// Bilinear scaling to 2x +// void Scale_MMX_BilinearFilter (SDL_Surface *src, +// SDL_Surface *dst, SDL_Rect *r) + +#include "bilinear2x.c" + + +// Advanced Biadapt scaling to 2x +// void Scale_MMX_BiAdaptAdvFilter (SDL_Surface *src, +// SDL_Surface *dst, SDL_Rect *r) + +#include "biadv2x.c" + + +// Triscan scaling to 2x +// derivative of 'scale2x' -- scale2x.sf.net +// void Scale_MMX_TriScanFilter (SDL_Surface *src, +// SDL_Surface *dst, SDL_Rect *r) + +#include "triscan2x.c" + +// Hq2x scaling +// (adapted from 'hq2x' by Maxim Stepin -- www.hiend3d.com/hq2x.html) +// void Scale_MMX_HqFilter (SDL_Surface *src, +// SDL_Surface *dst, SDL_Rect *r) + +#include "hq2x.c" + + +#endif /* MMX_ASM */ + diff --git a/src/libs/graphics/sdl/2xscalers_mmx.h b/src/libs/graphics/sdl/2xscalers_mmx.h new file mode 100644 index 0000000..c8dba32 --- /dev/null +++ b/src/libs/graphics/sdl/2xscalers_mmx.h @@ -0,0 +1,56 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_GRAPHICS_SDL_2XSCALERS_MMX_H_ +#define LIBS_GRAPHICS_SDL_2XSCALERS_MMX_H_ + +// MMX versions +void Scale_MMX_PrepPlatform (const SDL_PixelFormat* fmt); + +void Scale_MMX_Nearest (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r); +void Scale_MMX_BilinearFilter (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r); +void Scale_MMX_BiAdaptAdvFilter (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r); +void Scale_MMX_TriScanFilter (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r); +void Scale_MMX_HqFilter (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r); + +extern const Scale_FuncDef_t Scale_MMX_Functions[]; + + +// SSE (Intel)/MMX Ext (Athlon) versions +void Scale_SSE_PrepPlatform (const SDL_PixelFormat* fmt); + +void Scale_SSE_Nearest (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r); +void Scale_SSE_BilinearFilter (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r); +void Scale_SSE_BiAdaptAdvFilter (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r); +void Scale_SSE_TriScanFilter (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r); +void Scale_SSE_HqFilter (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r); + +extern const Scale_FuncDef_t Scale_SSE_Functions[]; + + +// 3DNow (AMD K6/Athlon) versions +void Scale_3DNow_PrepPlatform (const SDL_PixelFormat* fmt); + +void Scale_3DNow_Nearest (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r); +void Scale_3DNow_BilinearFilter (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r); +void Scale_3DNow_BiAdaptAdvFilter (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r); +void Scale_3DNow_TriScanFilter (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r); +void Scale_3DNow_HqFilter (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r); + +extern const Scale_FuncDef_t Scale_3DNow_Functions[]; + + +#endif /* LIBS_GRAPHICS_SDL_2XSCALERS_MMX_H_ */ diff --git a/src/libs/graphics/sdl/2xscalers_sse.c b/src/libs/graphics/sdl/2xscalers_sse.c new file mode 100644 index 0000000..a089744 --- /dev/null +++ b/src/libs/graphics/sdl/2xscalers_sse.c @@ -0,0 +1,100 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "port.h" +#include "libs/platform.h" + +#if defined(MMX_ASM) + +#include "libs/graphics/sdl/sdl_common.h" +#include "types.h" +#include "scalers.h" +#include "scaleint.h" +#include "2xscalers.h" +#include "2xscalers_mmx.h" + +// SSE name for all functions +#undef SCALE_ +#define SCALE_(name) Scale ## _SSE_ ## name + +// Tell them which opcodes we want to support +#define USE_MOVNTQ +#define USE_PREFETCH INTEL_PREFETCH +#define USE_PSADBW +// Bring in inline asm functions +#include "scalemmx.h" + + +// Scaler function lookup table +// +const Scale_FuncDef_t +Scale_SSE_Functions[] = +{ + {TFB_GFXFLAGS_SCALE_BILINEAR, Scale_SSE_BilinearFilter}, + {TFB_GFXFLAGS_SCALE_BIADAPT, Scale_BiAdaptFilter}, + {TFB_GFXFLAGS_SCALE_BIADAPTADV, Scale_SSE_BiAdaptAdvFilter}, + {TFB_GFXFLAGS_SCALE_TRISCAN, Scale_SSE_TriScanFilter}, + {TFB_GFXFLAGS_SCALE_HQXX, Scale_MMX_HqFilter}, + // Default + {0, Scale_SSE_Nearest} +}; + + +void +Scale_SSE_PrepPlatform (const SDL_PixelFormat* fmt) +{ + Scale_MMX_PrepPlatform (fmt); +} + +// Nearest Neighbor scaling to 2x +// void Scale_SSE_Nearest (SDL_Surface *src, +// SDL_Surface *dst, SDL_Rect *r) + +#include "nearest2x.c" + + +// Bilinear scaling to 2x +// void Scale_SSE_BilinearFilter (SDL_Surface *src, +// SDL_Surface *dst, SDL_Rect *r) + +#include "bilinear2x.c" + + +// Advanced Biadapt scaling to 2x +// void Scale_SSE_BiAdaptAdvFilter (SDL_Surface *src, +// SDL_Surface *dst, SDL_Rect *r) + +#include "biadv2x.c" + + +// Triscan scaling to 2x +// derivative of scale2x -- scale2x.sf.net +// void Scale_SSE_TriScanFilter (SDL_Surface *src, +// SDL_Surface *dst, SDL_Rect *r) + +#include "triscan2x.c" + +#if 0 && NO_IMPROVEMENT +// Hq2x scaling +// (adapted from 'hq2x' by Maxim Stepin -- www.hiend3d.com/hq2x.html) +// void Scale_SSE_HqFilter (SDL_Surface *src, +// SDL_Surface *dst, SDL_Rect *r) + +#include "hq2x.c" +#endif + +#endif /* MMX_ASM */ + diff --git a/src/libs/graphics/sdl/Makeinfo b/src/libs/graphics/sdl/Makeinfo new file mode 100644 index 0000000..f940b76 --- /dev/null +++ b/src/libs/graphics/sdl/Makeinfo @@ -0,0 +1,9 @@ +uqm_CFILES="opengl.c palette.c primitives.c pure.c sdl2_pure.c + sdl_common.c sdl1_common.c sdl2_common.c + scalers.c 2xscalers.c + 2xscalers_mmx.c 2xscalers_sse.c 2xscalers_3dnow.c + nearest2x.c bilinear2x.c biadv2x.c triscan2x.c hq2x.c + canvas.c png2sdl.c sdluio.c rotozoom.c" +uqm_HFILES="2xscalers.h 2xscalers_mmx.h opengl.h palette.h png2sdl.h + primitives.h pure.h rotozoom.h scaleint.h scalemmx.h + scalers.h sdl_common.h sdluio.h" diff --git a/src/libs/graphics/sdl/biadv2x.c b/src/libs/graphics/sdl/biadv2x.c new file mode 100644 index 0000000..b38cdf7 --- /dev/null +++ b/src/libs/graphics/sdl/biadv2x.c @@ -0,0 +1,532 @@ +/* + * Portions Copyright (C) 2003-2005 Alex Volkov (codepro@usa.net) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +// Core algorithm of the Advanced BiAdaptive screen scaler +// Template +// When this file is built standalone is produces a plain C version +// Also #included by 2xscalers_mmx.c for an MMX version + +#include "libs/graphics/sdl/sdl_common.h" +#include "types.h" +#include "scalers.h" +#include "scaleint.h" +#include "2xscalers.h" + + +// Advanced biadapt scaling to 2x +// The name expands to either +// Scale_BiAdaptAdvFilter (for plain C) or +// Scale_MMX_BiAdaptAdvFilter (for MMX) +// [others when platforms are added] +void +SCALE_(BiAdaptAdvFilter) (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r) +{ + int x, y; + const int w = src->w, h = src->h; + int xend, yend; + int dsrc, ddst; + SDL_Rect *region = r; + SDL_Rect limits; + SDL_PixelFormat *fmt = dst->format; + const int sp = src->pitch, dp = dst->pitch; + const int bpp = fmt->BytesPerPixel; + const int slen = sp / bpp, dlen = dp / bpp; + // for clarity purposes, the 'pixels' array here is transposed + Uint32 pixels[4][4]; + static int resolve_coord[][2] = + { + {0, -1}, {1, -1}, { 2, 0}, { 2, 1}, + {1, 2}, {0, 2}, {-1, 1}, {-1, 0}, + {100, 100} // term + }; + Uint32 *src_p = (Uint32 *)src->pixels; + Uint32 *dst_p = (Uint32 *)dst->pixels; + + // these macros are for clarity; they make the current pixel (0,0) + // and allow to access pixels in all directions + #define PIX(x, y) (pixels[1 + (x)][1 + (y)]) + #define SRC(x, y) (src_p + (x) + ((y) * slen)) + // commonly used operations, for clarity also + // others are defined at their respective bpp levels + #define BIADAPT_RGBHIGH 8000 + #define BIADAPT_YUVLOW 30 + #define BIADAPT_YUVMED 70 + #define BIADAPT_YUVHIGH 130 + + // high tolerance pixel comparison + #define BIADAPT_CMPRGB_HIGH(p1, p2) \ + (p1 == p2 || SCALE_CMPRGB (p1, p2) <= BIADAPT_RGBHIGH) + + // low tolerance pixel comparison + #define BIADAPT_CMPYUV_LOW(p1, p2) \ + (p1 == p2 || SCALE_CMPYUV (p1, p2, BIADAPT_YUVLOW)) + // medium tolerance pixel comparison + #define BIADAPT_CMPYUV_MED(p1, p2) \ + (p1 == p2 || SCALE_CMPYUV (p1, p2, BIADAPT_YUVMED)) + // high tolerance pixel comparison + #define BIADAPT_CMPYUV_HIGH(p1, p2) \ + (p1 == p2 || SCALE_CMPYUV (p1, p2, BIADAPT_YUVHIGH)) + + SCALE_(PlatInit) (); + + // expand updated region if necessary + // pixels neighbooring the updated region may + // change as a result of updates + limits.x = 0; + limits.y = 0; + limits.w = src->w; + limits.h = src->h; + Scale_ExpandRect (region, 2, &limits); + + xend = region->x + region->w; + yend = region->y + region->h; + dsrc = slen - region->w; + ddst = (dlen - region->w) * 2; + + #define SCALE_GETPIX(p) ( *(Uint32 *)(p) ) + #define SCALE_SETPIX(p, c) ( *(Uint32 *)(p) = (c) ) + + // move ptrs to the first updated pixel + src_p += slen * region->y + region->x; + dst_p += (dlen * region->y + region->x) * 2; + + for (y = region->y; y < yend; ++y, dst_p += ddst, src_p += dsrc) + { + for (x = region->x; x < xend; ++x, ++src_p, ++dst_p) + { + // pixel equality counter + int cmatch; + + // most pixels will fall into 'all 4 equal' + // pattern, so we check it first + cmatch = 0; + + PIX (0, 0) = SCALE_GETPIX (SRC (0, 0)); + + SCALE_SETPIX (dst_p, PIX (0, 0)); + + if (y + 1 < h) + { + // check pixel below the current one + PIX (0, 1) = SCALE_GETPIX (SRC (0, 1)); + + if (PIX (0, 0) == PIX (0, 1)) + { + SCALE_SETPIX (dst_p + dlen, PIX (0, 0)); + cmatch |= 1; + } + } + else + { + // last pixel in column - propagate + PIX (0, 1) = PIX (0, 0); + SCALE_SETPIX (dst_p + dlen, PIX (0, 0)); + cmatch |= 1; + + } + + if (x + 1 < w) + { + // check pixel to the right from the current one + PIX (1, 0) = SCALE_GETPIX (SRC (1, 0)); + + if (PIX (0, 0) == PIX (1, 0)) + { + SCALE_SETPIX (dst_p + 1, PIX (0, 0)); + cmatch |= 2; + } + } + else + { + // last pixel in row - propagate + PIX (1, 0) = PIX (0, 0); + SCALE_SETPIX (dst_p + 1, PIX (0, 0)); + cmatch |= 2; + } + + if (cmatch == 3) + { + if (y + 1 >= h || x + 1 >= w) + { + // last pixel in row/column and nearest + // neighboor is identical + dst_p++; + SCALE_SETPIX (dst_p + dlen, PIX (0, 0)); + continue; + } + + // check pixel to the bottom-right + PIX (1, 1) = SCALE_GETPIX (SRC (1, 1)); + + if (PIX (0, 0) == PIX (1, 1)) + { + // all 4 are equal - propagate + dst_p++; + SCALE_SETPIX (dst_p + dlen, PIX (0, 0)); + continue; + } + } + + // some neighboors are different, lets check them + + if (x > 0) + PIX (-1, 0) = SCALE_GETPIX (SRC (-1, 0)); + else + PIX (-1, 0) = PIX (0, 0); + + if (x + 2 < w) + PIX (2, 0) = SCALE_GETPIX (SRC (2, 0)); + else + PIX (2, 0) = PIX (1, 0); + + if (y + 1 < h) + { + if (x > 0) + PIX (-1, 1) = SCALE_GETPIX (SRC (-1, 1)); + else + PIX (-1, 1) = PIX (0, 1); + + if (x + 2 < w) + { + PIX (1, 1) = SCALE_GETPIX (SRC (1, 1)); + PIX (2, 1) = SCALE_GETPIX (SRC (2, 1)); + } + else if (x + 1 < w) + { + PIX (1, 1) = SCALE_GETPIX (SRC (1, 1)); + PIX (2, 1) = PIX (1, 1); + } + else + { + PIX (1, 1) = PIX (0, 1); + PIX (2, 1) = PIX (0, 1); + } + } + else + { + // last pixel in column + PIX (-1, 1) = PIX (-1, 0); + PIX (1, 1) = PIX (1, 0); + PIX (2, 1) = PIX (2, 0); + } + + if (y + 2 < h) + { + PIX (0, 2) = SCALE_GETPIX (SRC (0, 2)); + + if (x > 0) + PIX (-1, 2) = SCALE_GETPIX (SRC (-1, 2)); + else + PIX (-1, 2) = PIX (0, 2); + + if (x + 2 < w) + { + PIX (1, 2) = SCALE_GETPIX (SRC (1, 2)); + PIX (2, 2) = SCALE_GETPIX (SRC (2, 2)); + } + else if (x + 1 < w) + { + PIX (1, 2) = SCALE_GETPIX (SRC (1, 2)); + PIX (2, 2) = PIX (1, 2); + } + else + { + PIX (1, 2) = PIX (0, 2); + PIX (2, 2) = PIX (0, 2); + } + } + else + { + // last pixel in column + PIX (-1, 2) = PIX (-1, 1); + PIX (0, 2) = PIX (0, 1); + PIX (1, 2) = PIX (1, 1); + PIX (2, 2) = PIX (2, 1); + } + + if (y > 0) + { + PIX (0, -1) = SCALE_GETPIX (SRC (0, -1)); + + if (x > 0) + PIX (-1, -1) = SCALE_GETPIX (SRC (-1, -1)); + else + PIX (-1, -1) = PIX (0, -1); + + if (x + 2 < w) + { + PIX (1, -1) = SCALE_GETPIX (SRC (1, -1)); + PIX (2, -1) = SCALE_GETPIX (SRC (2, -1)); + } + else if (x + 1 < w) + { + PIX (1, -1) = SCALE_GETPIX (SRC (1, -1)); + PIX (2, -1) = PIX (1, -1); + } + else + { + PIX (1, -1) = PIX (0, -1); + PIX (2, -1) = PIX (0, -1); + } + } + else + { + PIX (-1, -1) = PIX (-1, 0); + PIX (0, -1) = PIX (0, 0); + PIX (1, -1) = PIX (1, 0); + PIX (2, -1) = PIX (2, 0); + } + + // check pixel below the current one + if (!(cmatch & 1)) + { + if (SCALE_CMPYUV (PIX (0, 0), PIX (0, 1), BIADAPT_YUVLOW)) + { + SCALE_SETPIX (dst_p + dlen, Scale_Blend_11 ( + PIX (0, 0), PIX (0, 1)) + ); + cmatch |= 1; + } + // detect a 2:1 line going across the current pixel + else if ( (PIX (0, 0) == PIX (-1, 0) + && PIX (0, 0) == PIX (1, 1) + && PIX (0, 0) == PIX (2, 1) && + + ((!BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (-1, -1)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (0, -1)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (1, 0)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (2, 0))) || + (!BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (-1, 1)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (1, 2)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (2, 2))))) || + + (PIX (0, 0) == PIX (1, 0) + && PIX (0, 0) == PIX (-1, 1) + && PIX (0, 0) == PIX (2, -1) && + + ((!BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (-1, 0)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (0, -1)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (1, -1))) || + (!BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (-1, 2)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (1, 1)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (2, 0))))) ) + { + SCALE_SETPIX (dst_p + dlen, PIX (0, 0)); + } + // detect a 2:1 line going across the pixel below current + else if ( (PIX (0, 1) == PIX (-1, 0) + && PIX (0, 1) == PIX (1, 1) + && PIX (0, 1) == PIX (2, 2) && + + ((!BIADAPT_CMPRGB_HIGH (PIX (0, 1), PIX (-1, -1)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 1), PIX (1, 0)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 1), PIX (2, 1))) || + (!BIADAPT_CMPRGB_HIGH (PIX (0, 1), PIX (-1, 1)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 1), PIX (0, 2)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 1), PIX (1, 2))))) || + + (PIX (0, 1) == PIX (1, 0) + && PIX (0, 1) == PIX (-1, 1) + && PIX (0, 1) == PIX (2, 0) && + + ((!BIADAPT_CMPRGB_HIGH (PIX (0, 1), PIX (-1, 0)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 1), PIX (1, -1)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 1), PIX (2, -1))) || + (!BIADAPT_CMPRGB_HIGH (PIX (0, 1), PIX (-1, 2)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 1), PIX (0, 2)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 1), PIX (1, 1)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 1), PIX (2, 1))))) ) + { + SCALE_SETPIX (dst_p + dlen, PIX (0, 1)); + } + else + SCALE_SETPIX (dst_p + dlen, Scale_Blend_11 ( + PIX (0, 0), PIX (0, 1)) + ); + } + + dst_p++; + + // check pixel to the right from the current one + if (!(cmatch & 2)) + { + if (SCALE_CMPYUV (PIX (0, 0), PIX (1, 0), BIADAPT_YUVLOW)) + { + SCALE_SETPIX (dst_p, Scale_Blend_11 ( + PIX (0, 0), PIX (1, 0)) + ); + cmatch |= 2; + } + // detect a 1:2 line going across the current pixel + else if ( (PIX (0, 0) == PIX (1, -1) + && PIX (0, 0) == PIX (0, 1) + && PIX (0, 0) == PIX (-1, 2) && + + ((!BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (0, -1)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (-1, 0)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (-1, 1))) || + (!BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (2, -1)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (1, 1)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (0, 2))))) || + + (PIX (0, 0) == PIX (0, -1) + && PIX (0, 0) == PIX (1, 1) + && PIX (0, 0) == PIX (1, 2) && + + ((!BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (-1, -1)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (-1, 0)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (0, 1)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (0, 2))) || + (!BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (1, -1)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (2, 1)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (2, 2))))) ) + + { + SCALE_SETPIX (dst_p, PIX (0, 0)); + } + // detect a 1:2 line going across the pixel to the right + else if ( (PIX (1, 0) == PIX (1, -1) + && PIX (1, 0) == PIX (0, 1) + && PIX (1, 0) == PIX (0, 2) && + + ((!BIADAPT_CMPRGB_HIGH (PIX (1, 0), PIX (0, -1)) + && !BIADAPT_CMPRGB_HIGH (PIX (1, 0), PIX (-1, 1)) + && !BIADAPT_CMPRGB_HIGH (PIX (1, 0), PIX (-1, 2))) || + (!BIADAPT_CMPRGB_HIGH (PIX (1, 0), PIX (2, -1)) + && !BIADAPT_CMPRGB_HIGH (PIX (1, 0), PIX (2, 0)) + && !BIADAPT_CMPRGB_HIGH (PIX (1, 0), PIX (1, 1)) + && !BIADAPT_CMPRGB_HIGH (PIX (1, 0), PIX (1, 2))))) || + + (PIX (1, 0) == PIX (0, -1) + && PIX (1, 0) == PIX (1, 1) + && PIX (1, 0) == PIX (2, 2) && + + ((!BIADAPT_CMPRGB_HIGH (PIX (1, 0), PIX (-1, -1)) + && !BIADAPT_CMPRGB_HIGH (PIX (1, 0), PIX (0, 1)) + && !BIADAPT_CMPRGB_HIGH (PIX (1, 0), PIX (1, 2))) || + (!BIADAPT_CMPRGB_HIGH (PIX (1, 0), PIX (1, -1)) + && !BIADAPT_CMPRGB_HIGH (PIX (1, 0), PIX (2, 0)) + && !BIADAPT_CMPRGB_HIGH (PIX (1, 0), PIX (2, 1))))) ) + { + SCALE_SETPIX (dst_p, PIX (1, 0)); + } + else + SCALE_SETPIX (dst_p, Scale_Blend_11 ( + PIX (0, 0), PIX (1, 0)) + ); + } + + if (PIX (0, 0) == PIX (1, 1) && PIX (1, 0) == PIX (0, 1)) + { + // diagonals are equal + int *coord; + int cl, cr; + Uint32 clr; + + // both pairs are equal, have to resolve the pixel + // race; we try detecting which color is + // the background by looking for a line or an edge + // examine 8 pixels surrounding the current quad + + cl = cr = 2; + for (coord = resolve_coord[0]; *coord < 100; coord += 2) + { + clr = PIX (coord[0], coord[1]); + + if (BIADAPT_CMPYUV_MED (clr, PIX (0, 0))) + cl++; + else if (BIADAPT_CMPYUV_MED (clr, PIX (1, 0))) + cr++; + } + + // least count wins + if (cl > cr) + clr = PIX (1, 0); + else if (cr > cl) + clr = PIX (0, 0); + else + clr = Scale_Blend_11 (PIX (0, 0), PIX (1, 0)); + + SCALE_SETPIX (dst_p + dlen, clr); + continue; + } + + if (cmatch == 3 + || (BIADAPT_CMPYUV_LOW (PIX (1, 0), PIX (0, 1)) + && BIADAPT_CMPYUV_LOW (PIX (1, 0), PIX (1, 1)))) + { + SCALE_SETPIX (dst_p + dlen, Scale_Blend_11 ( + PIX (0, 1), PIX (1, 0)) + ); + continue; + } + else if (cmatch && BIADAPT_CMPYUV_LOW (PIX (0, 0), PIX (1, 1))) + { + SCALE_SETPIX (dst_p + dlen, Scale_Blend_11 ( + PIX (0, 0), PIX (1, 1)) + ); + continue; + } + + // check pixel to the bottom-right + if (BIADAPT_CMPYUV_HIGH (PIX (0, 0), PIX (1, 1)) + && BIADAPT_CMPYUV_HIGH (PIX (1, 0), PIX (0, 1))) + { + if (SCALE_GETY (PIX (0, 0)) > SCALE_GETY (PIX (1, 0))) + { + SCALE_SETPIX (dst_p + dlen, Scale_Blend_11 ( + PIX (0, 0), PIX (1, 1)) + ); + } + else + { + SCALE_SETPIX (dst_p + dlen, Scale_Blend_11 ( + PIX (1, 0), PIX (0, 1)) + ); + } + } + else if (BIADAPT_CMPYUV_HIGH (PIX (0, 0), PIX (1, 1))) + { + // main diagonal is same color + // use its value + SCALE_SETPIX (dst_p + dlen, Scale_Blend_11 ( + PIX (0, 0), PIX (1, 1)) + ); + } + else if (BIADAPT_CMPYUV_HIGH (PIX (1, 0), PIX (0, 1))) + { + // 2nd diagonal is same color + // use its value + SCALE_SETPIX (dst_p + dlen, Scale_Blend_11 ( + PIX (1, 0), PIX (0, 1)) + ); + } + else + { + // blend all 4 + SCALE_SETPIX (dst_p + dlen, Scale_Blend_1111 ( + PIX (0, 0), PIX (0, 1), + PIX (1, 0), PIX (1, 1) + )); + } + } + } + + SCALE_(PlatDone) (); +} + diff --git a/src/libs/graphics/sdl/bilinear2x.c b/src/libs/graphics/sdl/bilinear2x.c new file mode 100644 index 0000000..a12eb93 --- /dev/null +++ b/src/libs/graphics/sdl/bilinear2x.c @@ -0,0 +1,112 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +// Core algorithm of the BiLinear screen scaler +// Template +// When this file is built standalone is produces a plain C version +// Also #included by 2xscalers_mmx.c for an MMX version + +#include "libs/graphics/sdl/sdl_common.h" +#include "types.h" +#include "scalers.h" +#include "scaleint.h" +#include "2xscalers.h" + + +// Bilinear scaling to 2x +// The name expands to either +// Scale_BilinearFilter (for plain C) or +// Scale_MMX_BilinearFilter (for MMX) +// Scale_SSE_BilinearFilter (for SSE) +// [others when platforms are added] +void +SCALE_(BilinearFilter) (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r) +{ + int x, y; + const int w = src->w, h = src->h; + int xend, yend; + int dsrc, ddst; + SDL_Rect *region = r; + SDL_Rect limits; + SDL_PixelFormat *fmt = dst->format; + const int pitch = src->pitch, dp = dst->pitch; + const int bpp = fmt->BytesPerPixel; + const int len = pitch / bpp, dlen = dp / bpp; + Uint32 p[4]; // influential pixels array + Uint32 *srow0 = (Uint32 *) src->pixels; + Uint32 *dst_p = (Uint32 *) dst->pixels; + + SCALE_(PlatInit) (); + + // expand updated region if necessary + // pixels neighbooring the updated region may + // change as a result of updates + limits.x = 0; + limits.y = 0; + limits.w = w; + limits.h = h; + Scale_ExpandRect (region, 1, &limits); + + xend = region->x + region->w; + yend = region->y + region->h; + dsrc = len - region->w; + ddst = (dlen - region->w) * 2; + + // move ptrs to the first updated pixel + srow0 += len * region->y + region->x; + dst_p += (dlen * region->y + region->x) * 2; + + for (y = region->y; y < yend; ++y, dst_p += ddst, srow0 += dsrc) + { + Uint32 *srow1; + + SCALE_(Prefetch) (srow0 + 16); + SCALE_(Prefetch) (srow0 + 32); + + if (y < h - 1) + srow1 = srow0 + len; + else + srow1 = srow0; + + SCALE_(Prefetch) (srow1 + 16); + SCALE_(Prefetch) (srow1 + 32); + + for (x = region->x; x < xend; ++x, ++srow0, ++srow1, dst_p += 2) + { + if (x < w - 1) + { // can blend directly from pixels + SCALE_BILINEAR_BLEND4 (srow0, srow1, dst_p, dlen); + } + else + { // need to make temp pixel rows + p[0] = srow0[0]; + p[1] = p[0]; + p[2] = srow1[0]; + p[3] = p[2]; + + SCALE_BILINEAR_BLEND4 (&p[0], &p[2], dst_p, dlen); + } + } + + SCALE_(Prefetch) (srow0 + dsrc); + SCALE_(Prefetch) (srow0 + dsrc + 16); + SCALE_(Prefetch) (srow1 + dsrc); + SCALE_(Prefetch) (srow1 + dsrc + 16); + } + + SCALE_(PlatDone) (); +} + diff --git a/src/libs/graphics/sdl/canvas.c b/src/libs/graphics/sdl/canvas.c new file mode 100644 index 0000000..ad7024d --- /dev/null +++ b/src/libs/graphics/sdl/canvas.c @@ -0,0 +1,2176 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "port.h" +#include <string.h> + // for memcpy() + +#include SDL_INCLUDE(SDL.h) +#include "sdl_common.h" +#include "libs/graphics/gfx_common.h" +#include "libs/graphics/tfb_draw.h" +#include "libs/graphics/cmap.h" +#include "libs/log.h" +#include "libs/memlib.h" +#include "primitives.h" +#include "palette.h" +#include "sdluio.h" +#include "rotozoom.h" +#include "options.h" +#include "types.h" + +typedef SDL_Surface *NativeCanvas; + +// BYTE x BYTE weight (mult >> 8) table +static Uint8 btable[256][256]; + +void +TFB_DrawCanvas_Initialize (void) +{ + int i, j; + for (i = 0; i < 256; ++i) + for (j = 0; j < 256; ++j) + btable[j][i] = (j * i + 0x80) >> 8; + // need error correction here +} + +const char * +TFB_DrawCanvas_GetError (void) +{ + const char *err = SDL_GetError (); + // TODO: Should we call SDL_ClearError() here so that it is not + // returned again later? + return err; +} + +static void +checkPrimitiveMode (SDL_Surface *surf, Color *color, DrawMode *mode) +{ + const SDL_PixelFormat *fmt = surf->format; + // Special case: We support DRAW_ALPHA mode to non-alpha surfaces + // for primitives via Color.a + if (mode->kind == DRAW_REPLACE && fmt->Amask == 0 && color->a != 0xff) + { + mode->kind = DRAW_ALPHA; + mode->factor = color->a; + color->a = 0xff; + } +} + +void +TFB_DrawCanvas_Line (int x1, int y1, int x2, int y2, Color color, + DrawMode mode, TFB_Canvas target) +{ + SDL_Surface *dst = target; + SDL_PixelFormat *fmt = dst->format; + Uint32 sdlColor; + RenderPixelFn plotFn; + + checkPrimitiveMode (dst, &color, &mode); + sdlColor = SDL_MapRGBA (fmt, color.r, color.g, color.b, color.a); + + plotFn = renderpixel_for (target, mode.kind); + if (!plotFn) + { + log_add (log_Warning, "ERROR: TFB_DrawCanvas_Line " + "unsupported draw mode (%d)", (int)mode.kind); + return; + } + + SDL_LockSurface (dst); + line_prim (x1, y1, x2, y2, sdlColor, plotFn, mode.factor, dst); + SDL_UnlockSurface (dst); +} + +void +TFB_DrawCanvas_Rect (RECT *rect, Color color, DrawMode mode, TFB_Canvas target) +{ + SDL_Surface *dst = target; + SDL_PixelFormat *fmt = dst->format; + Uint32 sdlColor; + SDL_Rect sr; + sr.x = rect->corner.x; + sr.y = rect->corner.y; + sr.w = rect->extent.width; + sr.h = rect->extent.height; + + checkPrimitiveMode (dst, &color, &mode); + sdlColor = SDL_MapRGBA (fmt, color.r, color.g, color.b, color.a); + + if (mode.kind == DRAW_REPLACE) + { // Standard SDL fillrect rendering + Uint32 colorkey; + if (fmt->Amask && (TFB_GetColorKey (dst, &colorkey) == 0)) + { // special case -- alpha surface with colorkey + // colorkey rects are transparent + if ((sdlColor & ~fmt->Amask) == (colorkey & ~fmt->Amask)) + sdlColor &= ~fmt->Amask; // make transparent + } + SDL_FillRect (dst, &sr, sdlColor); + } + else + { // Custom fillrect rendering + RenderPixelFn plotFn = renderpixel_for (target, mode.kind); + if (!plotFn) + { + log_add (log_Warning, "ERROR: TFB_DrawCanvas_Rect " + "unsupported draw mode (%d)", (int)mode.kind); + return; + } + + SDL_LockSurface (dst); + fillrect_prim (sr, sdlColor, plotFn, mode.factor, dst); + SDL_UnlockSurface (dst); + } +} + +static void +TFB_DrawCanvas_Blit (SDL_Surface *src, SDL_Rect *src_r, + SDL_Surface *dst, SDL_Rect *dst_r, DrawMode mode) +{ + SDL_PixelFormat *srcfmt = src->format; + + if (mode.kind == DRAW_REPLACE) + { // Standard SDL simple blit + SDL_BlitSurface (src, src_r, dst, dst_r); + } + else if (mode.kind == DRAW_ALPHA && srcfmt->Amask == 0) + { // Standard SDL surface-alpha blit + // Note that surface alpha and per-pixel alpha cannot work + // at the same time, which is why the Amask test + int hasAlpha = TFB_HasSurfaceAlphaMod (src); + assert (!hasAlpha); + // Set surface alpha temporarily + TFB_SetSurfaceAlphaMod (src, mode.factor); + SDL_BlitSurface (src, src_r, dst, dst_r); + TFB_DisableSurfaceAlphaMod (src); + } + else + { // Custom blit + SDL_Rect loc_src_r, loc_dst_r; + RenderPixelFn plotFn = renderpixel_for (dst, mode.kind); + if (!plotFn) + { + log_add (log_Warning, "ERROR: TFB_DrawCanvas_Blit " + "unsupported draw mode (%d)", (int)mode.kind); + return; + } + + if (!src_r) + { // blit whole image; generate rect + loc_src_r.x = 0; + loc_src_r.y = 0; + loc_src_r.w = src->w; + loc_src_r.h = src->h; + src_r = &loc_src_r; + } + + if (!dst_r) + { // blit to 0,0; generate rect + loc_dst_r.x = 0; + loc_dst_r.y = 0; + loc_dst_r.w = dst->w; + loc_dst_r.h = dst->h; + dst_r = &loc_dst_r; + } + + SDL_LockSurface (dst); + blt_prim (src, *src_r, plotFn, mode.factor, dst, *dst_r); + SDL_UnlockSurface (dst); + } +} + +// XXX: If a colormap is passed in, it has to have been acquired via +// TFB_GetColorMap(). We release the colormap at the end. +void +TFB_DrawCanvas_Image (TFB_Image *img, int x, int y, int scale, + int scaleMode, TFB_ColorMap *cmap, DrawMode mode, TFB_Canvas target) +{ + SDL_Rect srcRect, targetRect, *pSrcRect; + SDL_Surface *surf; + SDL_Palette *NormalPal; + + if (img == 0) + { + log_add (log_Warning, + "ERROR: TFB_DrawCanvas_Image passed null image ptr"); + return; + } + + LockMutex (img->mutex); + + NormalPal = ((SDL_Surface *)img->NormalImg)->format->palette; + // only set the new palette if it changed + if (NormalPal && cmap && img->colormap_version != cmap->version) + TFB_SetColors (img->NormalImg, cmap->palette->colors, 0, 256); + + if (scale != 0 && scale != GSCALE_IDENTITY) + { + if (scaleMode == TFB_SCALE_TRILINEAR && img->MipmapImg) + { + // only set the new palette if it changed + if (TFB_DrawCanvas_IsPaletted (img->MipmapImg) + && cmap && img->colormap_version != cmap->version) + TFB_SetColors (img->MipmapImg, cmap->palette->colors, 0, 256); + } + else if (scaleMode == TFB_SCALE_TRILINEAR && !img->MipmapImg) + { // Do bilinear scaling instead when mipmap is unavailable + scaleMode = TFB_SCALE_BILINEAR; + } + + TFB_DrawImage_FixScaling (img, scale, scaleMode); + surf = img->ScaledImg; + if (TFB_DrawCanvas_IsPaletted (surf)) + { + // We may only get a paletted scaled image if the source is + // paletted. Currently, all scaling targets are truecolor. + assert (NormalPal && NormalPal->colors); + TFB_SetColors (surf, NormalPal->colors, 0, NormalPal->ncolors); + } + + srcRect.x = 0; + srcRect.y = 0; + srcRect.w = img->extent.width; + srcRect.h = img->extent.height; + pSrcRect = &srcRect; + + targetRect.x = x - img->last_scale_hs.x; + targetRect.y = y - img->last_scale_hs.y; + } + else + { + surf = img->NormalImg; + pSrcRect = NULL; + + targetRect.x = x - img->NormalHs.x; + targetRect.y = y - img->NormalHs.y; + } + + if (cmap) + { + img->colormap_version = cmap->version; + // TODO: Technically, this is not a proper place to release a + // colormap. As it stands now, the colormap must have been + // addrefed when passed to us. + TFB_ReturnColorMap (cmap); + } + + TFB_DrawCanvas_Blit (surf, pSrcRect, target, &targetRect, mode); + UnlockMutex (img->mutex); +} + +// Assumes the source and destination surfaces are in the same format +static void +TFB_DrawCanvas_Fill (SDL_Surface *src, Uint32 fillcolor, SDL_Surface *dst) +{ + const SDL_PixelFormat *srcfmt = src->format; + SDL_PixelFormat *dstfmt = dst->format; + const int width = src->w; + const int height = src->h; + const int bpp = dstfmt->BytesPerPixel; + const int sp = src->pitch, dp = dst->pitch; + const int slen = sp / bpp, dlen = dp / bpp; + const int dsrc = slen - width, ddst = dlen - width; + Uint32 *src_p; + Uint32 *dst_p; + int x, y; + Uint32 srckey = 0, dstkey = 0; // 0 means alpha=0 too + Uint32 amask = srcfmt->Amask; + int alpha = (fillcolor & amask) >> srcfmt->Ashift; + + if (srcfmt->BytesPerPixel != 4 || dstfmt->BytesPerPixel != 4) + { + log_add (log_Warning, "TFB_DrawCanvas_Fill: Unsupported surface " + "formats: %d bytes/pixel source, %d bytes/pixel destination", + (int)srcfmt->BytesPerPixel, (int)dstfmt->BytesPerPixel); + return; + } + + // Strip the alpha channel from fillcolor because we process the + // alpha separately + fillcolor &= ~amask; + + SDL_LockSurface(src); + SDL_LockSurface(dst); + + src_p = (Uint32 *)src->pixels; + dst_p = (Uint32 *)dst->pixels; + + if (dstkey == fillcolor) + { // color collision, must switch colorkey + // new colorkey is grey (1/2,1/2,1/2) + dstkey = SDL_MapRGBA (dstfmt, 127, 127, 127, 0); + } + + if (srcfmt->Amask) + { // alpha-based fill + for (y = 0; y < height; ++y, dst_p += ddst, src_p += dsrc) + { + for (x = 0; x < width; ++x, ++src_p, ++dst_p) + { + Uint32 p = *src_p & amask; + + if (p == 0) + { // fully transparent pixel + *dst_p = dstkey; + } + else if (alpha == 0xff) + { // not for DRAW_ALPHA; use alpha chan directly + *dst_p = p | fillcolor; + } + else + { // for DRAW_ALPHA; modulate the alpha channel + p >>= srcfmt->Ashift; + p = (p * alpha) >> 8; + p <<= srcfmt->Ashift; + *dst_p = p | fillcolor; + } + } + } + } + else if (TFB_GetColorKey (src, &srckey) == 0) + { // colorkey-based fill + + for (y = 0; y < height; ++y, dst_p += ddst, src_p += dsrc) + { + for (x = 0; x < width; ++x, ++src_p, ++dst_p) + { + Uint32 p = *src_p; + + *dst_p = (p == srckey) ? dstkey : fillcolor; + } + } + } + else + { + log_add (log_Warning, "TFB_DrawCanvas_Fill: Unsupported source" + "surface format\n"); + } + + SDL_UnlockSurface(dst); + SDL_UnlockSurface(src); + + // save the colorkey (dynamic image -- not using RLE coding here) + TFB_SetColorKey (dst, dstkey, 0); + // if the filled surface is RGBA, colorkey will only be used + // when SDL_SRCALPHA flag is cleared. this allows us to blit + // the surface in different ways to diff targets +} + +void +TFB_DrawCanvas_FilledImage (TFB_Image *img, int x, int y, int scale, + int scaleMode, Color color, DrawMode mode, TFB_Canvas target) +{ + SDL_Surface *dst = target; + SDL_Rect srcRect, targetRect, *pSrcRect; + SDL_Surface *surf; + SDL_Palette *palette; + int i; + bool force_fill = false; + + if (img == 0) + { + log_add (log_Warning, + "ERROR: TFB_DrawCanvas_FilledImage passed null image ptr"); + return; + } + + checkPrimitiveMode (dst, &color, &mode); + + LockMutex (img->mutex); + + if (scale != 0 && scale != GSCALE_IDENTITY) + { + if (scaleMode == TFB_SCALE_TRILINEAR) + scaleMode = TFB_SCALE_BILINEAR; + // no point in trilinear for filled images + + if (scale != img->last_scale || scaleMode != img->last_scale_type) + force_fill = true; + + TFB_DrawImage_FixScaling (img, scale, scaleMode); + surf = img->ScaledImg; + srcRect.x = 0; + srcRect.y = 0; + srcRect.w = img->extent.width; + srcRect.h = img->extent.height; + pSrcRect = &srcRect; + + targetRect.x = x - img->last_scale_hs.x; + targetRect.y = y - img->last_scale_hs.y; + } + else + { + if (img->last_scale != 0) + { + // Make sure we remember that the last fill was from + // an unscaled image + force_fill = true; + img->last_scale = 0; + } + + surf = img->NormalImg; + pSrcRect = NULL; + + targetRect.x = x - img->NormalHs.x; + targetRect.y = y - img->NormalHs.y; + } + + palette = surf->format->palette; + if (palette) + { // set palette for fill-stamp + // Calling TFB_SetColors() results in an expensive src -> dst + // color-mapping operation for an SDL blit, following the call. + // We want to avoid that as much as possible. + + // TODO: generate a 32bpp filled image? + + SDL_Color colors[256]; + + colors[0] = ColorToNative (color); + for (i = 1; i < palette->ncolors; i++) + colors[i] = colors[0]; + + TFB_SetColors (surf, colors, 0, palette->ncolors); + // reflect the change in *actual* image palette + img->colormap_version--; + } + else + { // fill the non-transparent parts of the image with fillcolor + SDL_Surface *newfill = img->FilledImg; + SDL_PixelFormat *fillfmt; + + if (newfill && (newfill->w < surf->w || newfill->h < surf->h)) + { + TFB_DrawCanvas_Delete (newfill); + newfill = NULL; + } + + // prepare the filled image + if (!newfill) + { + newfill = SDL_CreateRGBSurface (SDL_SWSURFACE, + surf->w, surf->h, + surf->format->BitsPerPixel, + surf->format->Rmask, + surf->format->Gmask, + surf->format->Bmask, + surf->format->Amask); + force_fill = true; + } + fillfmt = newfill->format; + + if (force_fill || !sameColor (img->last_fill, color)) + { // image or fillcolor changed - regenerate + Uint32 fillColor; + + if (mode.kind == DRAW_ALPHA && fillfmt->Amask) + { // Per-pixel alpha and surface alpha will not work together + // We have to handle DRAW_ALPHA differently by modulating + // the surface alpha channel ourselves. + color.a = mode.factor; + mode.kind = DRAW_REPLACE; + } + else + { // Make sure we do not modulate the alpha channel + color.a = 0xff; + } + fillColor = SDL_MapRGBA (newfill->format, color.r, color.g, + color.b, color.a); + TFB_DrawCanvas_Fill (surf, fillColor, newfill); + // cache filled image if possible + img->last_fill = color; + } + + img->FilledImg = newfill; + surf = newfill; + } + + TFB_DrawCanvas_Blit (surf, pSrcRect, dst, &targetRect, mode); + UnlockMutex (img->mutex); +} + +void +TFB_DrawCanvas_FontChar (TFB_Char *fontChar, TFB_Image *backing, + int x, int y, DrawMode mode, TFB_Canvas target) +{ + SDL_Surface *dst = target; + SDL_Rect srcRect, targetRect; + SDL_Surface *surf; + int w, h; + + if (fontChar == 0) + { + log_add (log_Warning, "ERROR: " + "TFB_DrawCanvas_FontChar passed null char ptr"); + return; + } + if (backing == 0) + { + log_add (log_Warning, "ERROR: " + "TFB_DrawCanvas_FontChar passed null backing ptr"); + return; + } + + w = fontChar->extent.width; + h = fontChar->extent.height; + + LockMutex (backing->mutex); + + surf = backing->NormalImg; + if (surf->format->BytesPerPixel != 4 + || surf->w < w || surf->h < h) + { + log_add (log_Warning, "ERROR: " + "TFB_DrawCanvas_FontChar bad backing surface: %dx%dx%d; " + "char: %dx%d", + surf->w, surf->h, (int)surf->format->BytesPerPixel, w, h); + UnlockMutex (backing->mutex); + return; + } + + srcRect.x = 0; + srcRect.y = 0; + srcRect.w = fontChar->extent.width; + srcRect.h = fontChar->extent.height; + + targetRect.x = x - fontChar->HotSpot.x; + targetRect.y = y - fontChar->HotSpot.y; + + // transfer the alpha channel to the backing surface + SDL_LockSurface (surf); + { + int x, y; + const int sskip = fontChar->pitch - w; + const int dskip = (surf->pitch / 4) - w; + const Uint32 dmask = ~surf->format->Amask; + const int ashift = surf->format->Ashift; + Uint8 *src_p = fontChar->data; + Uint32 *dst_p = (Uint32 *)surf->pixels; + + if (mode.kind == DRAW_ALPHA) + { // Per-pixel alpha and surface alpha will not work together + // We have to handle DRAW_ALPHA differently by modulating + // the backing surface alpha channel ourselves. + // The existing backing surface alpha channel is ignored. + int alpha = mode.factor; + mode.kind = DRAW_REPLACE; + + for (y = 0; y < h; ++y, src_p += sskip, dst_p += dskip) + { + for (x = 0; x < w; ++x, ++src_p, ++dst_p) + { + Uint32 p = *dst_p & dmask; + Uint32 a = *src_p; + + // we use >> 8 instead of / 255, and it does not handle + // alpha == 255 correctly + if (alpha != 0xff) + { // modulate the alpha channel + a = (a * alpha) >> 8; + } + *dst_p = p | (a << ashift); + } + } + } + else /* if (mode.kind != DRAW_ALPHA) */ + { // Transfer the alpha channel to the backing surface + // DRAW_REPLACE + Color.a is NOT supported right now + for (y = 0; y < h; ++y, src_p += sskip, dst_p += dskip) + { + for (x = 0; x < w; ++x, ++src_p, ++dst_p) + { + *dst_p = (*dst_p & dmask) | ((Uint32)*src_p << ashift); + } + } + } + } + SDL_UnlockSurface (surf); + + TFB_DrawCanvas_Blit (surf, &srcRect, dst, &targetRect, mode); + UnlockMutex (backing->mutex); +} + +TFB_Canvas +TFB_DrawCanvas_New_TrueColor (int w, int h, BOOLEAN hasalpha) +{ + SDL_Surface *new_surf; + SDL_PixelFormat* fmt = format_conv_surf->format; + + new_surf = SDL_CreateRGBSurface (SDL_SWSURFACE, w, h, + fmt->BitsPerPixel, fmt->Rmask, fmt->Gmask, fmt->Bmask, + hasalpha ? fmt->Amask : 0); + if (!new_surf) + { + log_add (log_Fatal, "INTERNAL PANIC: Failed to create TFB_Canvas: %s", + SDL_GetError()); + exit (EXIT_FAILURE); + } + return new_surf; +} + +TFB_Canvas +TFB_DrawCanvas_New_ForScreen (int w, int h, BOOLEAN withalpha) +{ + SDL_Surface *new_surf; + SDL_PixelFormat* fmt = SDL_Screen->format; + + if (fmt->palette) + { + log_add (log_Warning, "TFB_DrawCanvas_New_ForScreen() WARNING:" + "Paletted display format will be slow"); + + new_surf = TFB_DrawCanvas_New_TrueColor (w, h, withalpha); + } + else + { + if (withalpha && fmt->Amask == 0) + fmt = format_conv_surf->format; // Screen has no alpha and we need it + + new_surf = SDL_CreateRGBSurface (SDL_SWSURFACE, w, h, + fmt->BitsPerPixel, fmt->Rmask, fmt->Gmask, fmt->Bmask, + withalpha ? fmt->Amask : 0); + } + + if (!new_surf) + { + log_add (log_Fatal, "TFB_DrawCanvas_New_ForScreen() INTERNAL PANIC:" + "Failed to create TFB_Canvas: %s", SDL_GetError()); + exit (EXIT_FAILURE); + } + return new_surf; +} + +TFB_Canvas +TFB_DrawCanvas_New_Paletted (int w, int h, Color palette[256], + int transparent_index) +{ + SDL_Surface *new_surf; + new_surf = SDL_CreateRGBSurface (SDL_SWSURFACE, w, h, 8, 0, 0, 0, 0); + if (!new_surf) + { + log_add (log_Fatal, "INTERNAL PANIC: Failed to create TFB_Canvas: %s", + SDL_GetError()); + exit (EXIT_FAILURE); + } + if (palette != NULL) + { + TFB_DrawCanvas_SetPalette (new_surf, palette); + } + if (transparent_index >= 0) + { + TFB_SetColorKey (new_surf, transparent_index, 0); + } + else + { + TFB_DisableColorKey (new_surf); + } + return new_surf; +} + +TFB_Canvas +TFB_DrawCanvas_New_ScaleTarget (TFB_Canvas canvas, TFB_Canvas oldcanvas, int type, int last_type) +{ + SDL_Surface *src = canvas; + SDL_Surface *old = oldcanvas; + SDL_Surface *newsurf = NULL; + + // For the purposes of this function, bilinear == trilinear + if (type == TFB_SCALE_TRILINEAR) + type = TFB_SCALE_BILINEAR; + if (last_type == TFB_SCALE_TRILINEAR) + last_type = TFB_SCALE_BILINEAR; + + if (old && type != last_type) + { + TFB_DrawCanvas_Delete (old); + old = NULL; + } + if (old) + return old; /* can just reuse the old one */ + + if (type == TFB_SCALE_NEAREST) + { + newsurf = SDL_CreateRGBSurface (SDL_SWSURFACE, src->w, + src->h, + src->format->BitsPerPixel, + src->format->Rmask, + src->format->Gmask, + src->format->Bmask, + src->format->Amask); + TFB_DrawCanvas_CopyTransparencyInfo (src, newsurf); + } + else + { + // The scaled image may in fact be larger by 1 pixel than the source + // because of hotspot alignment and fractional edge pixels + if (SDL_Screen->format->BitsPerPixel == 32) + newsurf = TFB_DrawCanvas_New_ForScreen (src->w + 1, src->h + 1, TRUE); + else + newsurf = TFB_DrawCanvas_New_TrueColor (src->w + 1, src->h + 1, TRUE); + } + + return newsurf; +} + +TFB_Canvas +TFB_DrawCanvas_New_RotationTarget (TFB_Canvas src_canvas, int angle) +{ + SDL_Surface *src = src_canvas; + SDL_Surface *newsurf; + EXTENT size; + + TFB_DrawCanvas_GetRotatedExtent (src_canvas, angle, &size); + + newsurf = SDL_CreateRGBSurface (SDL_SWSURFACE, + size.width, size.height, + src->format->BitsPerPixel, + src->format->Rmask, + src->format->Gmask, + src->format->Bmask, + src->format->Amask); + if (!newsurf) + { + log_add (log_Fatal, "TFB_DrawCanvas_New_RotationTarget()" + " INTERNAL PANIC: Failed to create TFB_Canvas: %s", + SDL_GetError()); + exit (EXIT_FAILURE); + } + TFB_DrawCanvas_CopyTransparencyInfo (src, newsurf); + + return newsurf; +} + +TFB_Canvas +TFB_DrawCanvas_LoadFromFile (void *dir, const char *fileName) +{ + SDL_Surface *surf = sdluio_loadImage (dir, fileName); + if (!surf) + return NULL; + + if (surf->format->BitsPerPixel < 8) + { + SDL_SetError ("unsupported image format (min 8bpp)"); + SDL_FreeSurface (surf); + surf = NULL; + } + + return surf; +} + +void +TFB_DrawCanvas_Delete (TFB_Canvas canvas) +{ + if (!canvas) + { + log_add (log_Warning, "INTERNAL PANIC: Attempted" + " to delete a NULL canvas!"); + /* Should we actually die here? */ + } + else + { + SDL_FreeSurface (canvas); + } +} + +BOOLEAN +TFB_DrawCanvas_GetFontCharData (TFB_Canvas canvas, BYTE *outData, + unsigned dataPitch) +{ + SDL_Surface *surf = canvas; + int x, y; + Uint8 r, g, b, a; + Uint32 p; + SDL_PixelFormat *fmt = surf->format; + GetPixelFn getpix; + + if (!surf || !outData) + return FALSE; + + SDL_LockSurface (surf); + + getpix = getpixel_for (surf); + + // produce an alpha-only image in internal BYTE[] format + // from the SDL surface + for (y = 0; y < surf->h; ++y) + { + BYTE *dst = outData + dataPitch * y; + + for (x = 0; x < surf->w; ++x, ++dst) + { + p = getpix (surf, x, y); + SDL_GetRGBA (p, fmt, &r, &g, &b, &a); + + if (!fmt->Amask) + { // produce alpha from intensity (Y component) + // using a fast approximation + // contributions to Y are: R=2, G=4, B=1 + a = ((r * 2) + (g * 4) + b) / 7; + } + + *dst = a; + } + } + + SDL_UnlockSurface (surf); + + return TRUE; +} + +Color * +TFB_DrawCanvas_ExtractPalette (TFB_Canvas canvas) +{ + int i; + Color *result; + SDL_Surface *surf = canvas; + SDL_Palette *palette = surf->format->palette; + + if (!palette) + return NULL; + + // There may be less colors in the surface than 256. Init to 0 first. + result = HCalloc (sizeof (Color) * 256); + assert (palette->ncolors <= 256); + for (i = 0; i < palette->ncolors; ++i) + result[i] = NativeToColor (palette->colors[i]); + + return result; +} + +TFB_Canvas +TFB_DrawCanvas_ToScreenFormat (TFB_Canvas canvas) +{ + SDL_Surface *result = TFB_DisplayFormatAlpha (canvas); + if (result == NULL) + { + log_add (log_Debug, "WARNING: Could not convert" + " sprite-canvas to display format."); + return canvas; + } + else if (result == canvas) + { // no conversion was necessary + return canvas; + } + else + { // converted + TFB_DrawCanvas_Delete (canvas); + return result; + } + + return canvas; +} + +BOOLEAN +TFB_DrawCanvas_IsPaletted (TFB_Canvas canvas) +{ + return ((SDL_Surface *)canvas)->format->palette != NULL; +} + +void +TFB_DrawCanvas_SetPalette (TFB_Canvas target, Color palette[256]) +{ + SDL_Color colors[256]; + int i; + + for (i = 0; i < 256; ++i) + colors[i] = ColorToNative (palette[i]); + + TFB_SetColors (target, colors, 0, 256); +} + +int +TFB_DrawCanvas_GetTransparentIndex (TFB_Canvas canvas) +{ + Uint32 colorkey; + if (TFB_GetColorKey (canvas, &colorkey)) + { + return colorkey; + } + return -1; +} + +void +TFB_DrawCanvas_SetTransparentIndex (TFB_Canvas canvas, int index, BOOLEAN rleaccel) +{ + if (index >= 0) + { + TFB_SetColorKey (canvas, index, rleaccel); + + if (!TFB_DrawCanvas_IsPaletted (canvas)) + { + // disables surface alpha so color key transparency actually works + TFB_DisableSurfaceAlphaMod (canvas); + } + } + else + { + TFB_DisableColorKey (canvas); + } +} + +void +TFB_DrawCanvas_CopyTransparencyInfo (TFB_Canvas src_canvas, + TFB_Canvas dst_canvas) +{ + SDL_Surface* src = src_canvas; + + if (src->format->palette) + { + int index; + index = TFB_DrawCanvas_GetTransparentIndex (src_canvas); + TFB_DrawCanvas_SetTransparentIndex (dst_canvas, index, FALSE); + } + else + { + Color color; + if (TFB_DrawCanvas_GetTransparentColor (src_canvas, &color)) + TFB_DrawCanvas_SetTransparentColor (dst_canvas, color, FALSE); + } +} + +BOOLEAN +TFB_DrawCanvas_GetTransparentColor (TFB_Canvas canvas, Color *color) +{ + Uint32 colorkey; + if (!TFB_DrawCanvas_IsPaletted (canvas) + && (TFB_GetColorKey ((SDL_Surface *)canvas, &colorkey) == 0)) + { + Uint8 ur, ug, ub; + SDL_GetRGB (colorkey, ((SDL_Surface *)canvas)->format, &ur, &ug, &ub); + color->r = ur; + color->g = ug; + color->b = ub; + color->a = 0xff; + return TRUE; + } + return FALSE; +} + +void +TFB_DrawCanvas_SetTransparentColor (TFB_Canvas canvas, Color color, + BOOLEAN rleaccel) +{ + Uint32 sdlColor; + sdlColor = SDL_MapRGBA (((SDL_Surface *)canvas)->format, + color.r, color.g, color.b, 0); + TFB_SetColorKey (canvas, sdlColor, rleaccel); + + if (!TFB_DrawCanvas_IsPaletted (canvas)) + { + // disables surface alpha so color key transparency actually works + TFB_DisableSurfaceAlphaMod (canvas); + } +} + +void +TFB_DrawCanvas_GetScaledExtent (TFB_Canvas src_canvas, HOT_SPOT* src_hs, + TFB_Canvas src_mipmap, HOT_SPOT* mm_hs, + int scale, int type, EXTENT *size, HOT_SPOT *hs) +{ + SDL_Surface *src = src_canvas; + sint32 x, y, w, h; + int frac; + + if (!src_mipmap) + { + w = src->w * scale; + h = src->h * scale; + x = src_hs->x * scale; + y = src_hs->y * scale; + } + else + { + // interpolates extents between src and mipmap to get smoother + // transition when surface changes + SDL_Surface *mipmap = src_mipmap; + int ratio = scale * 2 - GSCALE_IDENTITY; + + assert (scale >= GSCALE_IDENTITY / 2); + + w = mipmap->w * GSCALE_IDENTITY + (src->w - mipmap->w) * ratio; + h = mipmap->h * GSCALE_IDENTITY + (src->h - mipmap->h) * ratio; + + // Seems it is better to use mipmap hotspot because some + // source and mipmap images have the same dimensions! + x = mm_hs->x * GSCALE_IDENTITY + (src_hs->x - mm_hs->x) * ratio; + y = mm_hs->y * GSCALE_IDENTITY + (src_hs->y - mm_hs->y) * ratio; + } + + if (type != TFB_SCALE_NEAREST) + { + // align hotspot on an whole pixel + if (x & (GSCALE_IDENTITY - 1)) + { + frac = GSCALE_IDENTITY - (x & (GSCALE_IDENTITY - 1)); + x += frac; + w += frac; + } + if (y & (GSCALE_IDENTITY - 1)) + { + frac = GSCALE_IDENTITY - (y & (GSCALE_IDENTITY - 1)); + y += frac; + h += frac; + } + // pad the extent to accomodate fractional edge pixels + w += (GSCALE_IDENTITY - 1); + h += (GSCALE_IDENTITY - 1); + } + + size->width = w / GSCALE_IDENTITY; + size->height = h / GSCALE_IDENTITY; + hs->x = x / GSCALE_IDENTITY; + hs->y = y / GSCALE_IDENTITY; + + // Scaled image can be larger than the source by 1 pixel + // because of hotspot alignment and fractional edge pixels + assert (size->width <= src->w + 1 && size->height <= src->h + 1); + + if (!size->width && src->w) + size->width = 1; + if (!size->height && src->h) + size->height = 1; +} + +void +TFB_DrawCanvas_GetExtent (TFB_Canvas canvas, EXTENT *size) +{ + SDL_Surface *src = canvas; + + size->width = src->w; + size->height = src->h; +} + +void +TFB_DrawCanvas_Rescale_Nearest (TFB_Canvas src_canvas, TFB_Canvas dst_canvas, + int scale, HOT_SPOT* src_hs, EXTENT* size, HOT_SPOT* dst_hs) +{ + SDL_Surface *src = src_canvas; + SDL_Surface *dst = dst_canvas; + int x, y; + int fsx = 0, fsy = 0; // source fractional dx and dy increments + int ssx = 0, ssy = 0; // source fractional x and y starting points + int w, h; + + if (scale > 0) + { + TFB_DrawCanvas_GetScaledExtent (src, src_hs, NULL, NULL, scale, + TFB_SCALE_NEAREST, size, dst_hs); + + w = size->width; + h = size->height; + } + else + { + // Just go with the dst surface dimensions + w = dst->w; + h = dst->h; + } + + if (w > dst->w || h > dst->h) + { + log_add (log_Warning, "TFB_DrawCanvas_Rescale_Nearest: Tried to scale" + " image to size %d %d when dest_canvas has only" + " dimensions of %d %d! Failing.", + w, h, dst->w, dst->h); + return; + } + + if (w > 1) + fsx = ((src->w - 1) << 16) / (w - 1); + if (h > 1) + fsy = ((src->h - 1) << 16) / (h - 1); + // We start with a value in 0..0.5 range to shift the bigger + // jumps towards the center of the image + ssx = 0x6000; + ssy = 0x6000; + + SDL_LockSurface (src); + SDL_LockSurface (dst); + + if (src->format->BytesPerPixel == 1 && dst->format->BytesPerPixel == 1) + { + Uint8 *sp, *csp, *dp, *cdp; + int sx, sy; // source fractional x and y positions + + sp = csp = (Uint8 *) src->pixels; + dp = cdp = (Uint8 *) dst->pixels; + + for (y = 0, sy = ssy; y < h; ++y) + { + csp += (sy >> 16) * src->pitch; + sp = csp; + dp = cdp; + for (x = 0, sx = ssx; x < w; ++x) + { + sp += (sx >> 16); + *dp = *sp; + sx &= 0xffff; + sx += fsx; + ++dp; + } + sy &= 0xffff; + sy += fsy; + cdp += dst->pitch; + } + } + else if (src->format->BytesPerPixel == 4 && dst->format->BytesPerPixel == 4) + { + Uint32 *sp, *csp, *dp, *cdp; + int sx, sy; // source fractional x and y positions + int sgap, dgap; + + sgap = src->pitch >> 2; + dgap = dst->pitch >> 2; + + sp = csp = (Uint32 *) src->pixels; + dp = cdp = (Uint32 *) dst->pixels; + + for (y = 0, sy = ssy; y < h; ++y) + { + csp += (sy >> 16) * sgap; + sp = csp; + dp = cdp; + for (x = 0, sx = ssx; x < w; ++x) + { + sp += (sx >> 16); + *dp = *sp; + sx &= 0xffff; + sx += fsx; + ++dp; + } + sy &= 0xffff; + sy += fsy; + cdp += dgap; + } + } + else + { + log_add (log_Warning, "Tried to deal with unknown BPP: %d -> %d", + src->format->BitsPerPixel, dst->format->BitsPerPixel); + } + SDL_UnlockSurface (dst); + SDL_UnlockSurface (src); +} + +typedef union +{ + Uint32 value; + Uint8 chan[4]; + struct + { + Uint8 r, g, b, a; + } c; +} pixel_t; + +static inline Uint8 +dot_product_8_4 (pixel_t* p, int c, Uint8* v) +{ // math expanded for speed +#if 0 + return ( + (Uint32)p[0].chan[c] * v[0] + (Uint32)p[1].chan[c] * v[1] + + (Uint32)p[2].chan[c] * v[2] + (Uint32)p[3].chan[c] * v[3] + ) >> 8; +#else + // mult-table driven version + return + btable[p[0].chan[c]][v[0]] + btable[p[1].chan[c]][v[1]] + + btable[p[2].chan[c]][v[2]] + btable[p[3].chan[c]][v[3]]; +#endif +} + +static inline Uint8 +weight_product_8_4 (pixel_t* p, int c, Uint8* w) +{ // math expanded for speed + return ( + (Uint32)p[0].chan[c] * w[0] + (Uint32)p[1].chan[c] * w[1] + + (Uint32)p[2].chan[c] * w[2] + (Uint32)p[3].chan[c] * w[3] + ) / (w[0] + w[1] + w[2] + w[3]); +} + +static inline Uint8 +blend_ratio_2 (Uint8 c1, Uint8 c2, int ratio) +{ // blend 2 color values according to ratio (0..256) + // identical to proper alpha blending + return (((c1 - c2) * ratio) >> 8) + c2; +} + +static inline Uint32 +scale_read_pixel (void* ppix, SDL_PixelFormat *fmt, SDL_Color *pal, + Uint32 mask, Uint32 key) +{ + pixel_t p; + + // start off with non-present pixel + p.value = 0; + + if (pal) + { // paletted pixel; mask not used + Uint32 c = *(Uint8 *)ppix; + + if (c != key) + { + p.c.r = pal[c].r; + p.c.g = pal[c].g; + p.c.b = pal[c].b; + p.c.a = SDL_ALPHA_OPAQUE; + } + } + else + { // RGB(A) pixel; (pix & mask) != key + Uint32 c = *(Uint32 *)ppix; + + if ((c & mask) != key) + { +#if 0 + SDL_GetRGBA (c, fmt, &p.c.r, &p.c.g, &p.c.b, &p.c.a); +#else + // Assume 8 bits/channel; a safe assumption with 32bpp surfaces + p.c.r = (c >> fmt->Rshift) & 0xff; + p.c.g = (c >> fmt->Gshift) & 0xff; + p.c.b = (c >> fmt->Bshift) & 0xff; + if (fmt->Amask) + p.c.a = (c >> fmt->Ashift) & 0xff; + else + p.c.a = SDL_ALPHA_OPAQUE; + } +#endif + } + + return p.value; +} + +static inline Uint32 +scale_get_pixel (SDL_Surface *src, Uint32 mask, Uint32 key, int x, int y) +{ + SDL_Color *pal = src->format->palette? src->format->palette->colors : 0; + + if (x < 0 || x >= src->w || y < 0 || y >= src->h) + return 0; + + return scale_read_pixel ((Uint8*)src->pixels + y * src->pitch + + x * src->format->BytesPerPixel, src->format, pal, mask, key); +} + +void +TFB_DrawCanvas_Rescale_Trilinear (TFB_Canvas src_canvas, TFB_Canvas src_mipmap, + TFB_Canvas dst_canvas, int scale, HOT_SPOT* src_hs, HOT_SPOT* mm_hs, + EXTENT* size, HOT_SPOT* dst_hs) +{ + SDL_Surface *src = src_canvas; + SDL_Surface *dst = dst_canvas; + SDL_Surface *mm = src_mipmap; + SDL_PixelFormat *srcfmt = src->format; + SDL_PixelFormat *mmfmt = mm->format; + SDL_PixelFormat *dstfmt = dst->format; + SDL_Color *srcpal = srcfmt->palette? srcfmt->palette->colors : 0; + const int sbpp = srcfmt->BytesPerPixel; + const int mmbpp = mmfmt->BytesPerPixel; + const int slen = src->pitch; + const int mmlen = mm->pitch; + const int dst_has_alpha = (dstfmt->Amask != 0); + Uint32 transparent = 0; + const int alpha_threshold = dst_has_alpha ? 0 : 127; + // src v. mipmap importance factor + int ratio = scale * 2 - GSCALE_IDENTITY; + // source masks and keys + Uint32 mk0 = 0, ck0 = ~0, mk1 = 0, ck1 = ~0; + // source fractional x and y positions + int sx0, sy0, sx1, sy1; + // source fractional dx and dy increments + int fsx0 = 0, fsy0 = 0, fsx1 = 0, fsy1 = 0; + // source fractional x and y starting points + int ssx0 = 0, ssy0 = 0, ssx1 = 0, ssy1 = 0; + int x, y, w, h; + + TFB_GetColorKey (dst, &transparent); + + if (mmfmt->palette && !srcpal) + { + log_add (log_Warning, "TFB_DrawCanvas_Rescale_Trilinear: " + "Mipmap is paletted, but source is not! Failing."); + return; + } + + if (scale > 0) + { + int fw, fh; + + // Use (scale / GSCALE_IDENTITY) sizing factor + TFB_DrawCanvas_GetScaledExtent (src, src_hs, mm, mm_hs, scale, + TFB_SCALE_TRILINEAR, size, dst_hs); + + w = size->width; + h = size->height; + + fw = mm->w * GSCALE_IDENTITY + (src->w - mm->w) * ratio; + fh = mm->h * GSCALE_IDENTITY + (src->h - mm->h) * ratio; + + // This limits the effective source dimensions to 2048x2048, + // and we also lose 4 bits of precision out of 16 (no problem) + fsx0 = (src->w << 20) / fw; + fsx0 <<= 4; + fsy0 = (src->h << 20) / fh; + fsy0 <<= 4; + + fsx1 = (mm->w << 20) / fw; + fsx1 <<= 4; + fsy1 = (mm->h << 20) / fh; + fsy1 <<= 4; + + // position the hotspots directly over each other + ssx0 = (src_hs->x << 16) - fsx0 * dst_hs->x; + ssy0 = (src_hs->y << 16) - fsy0 * dst_hs->y; + + ssx1 = (mm_hs->x << 16) - fsx1 * dst_hs->x; + ssy1 = (mm_hs->y << 16) - fsy1 * dst_hs->y; + } + else + { + // Just go with the dst surface dimensions + w = dst->w; + h = dst->h; + + fsx0 = (src->w << 16) / w; + fsy0 = (src->h << 16) / h; + + fsx1 = (mm->w << 16) / w; + fsy1 = (mm->h << 16) / h; + + // give equal importance to both edges + ssx0 = (((src->w - 1) << 16) - fsx0 * (w - 1)) >> 1; + ssy0 = (((src->h - 1) << 16) - fsy0 * (h - 1)) >> 1; + + ssx1 = (((mm->w - 1) << 16) - fsx1 * (w - 1)) >> 1; + ssy1 = (((mm->h - 1) << 16) - fsy1 * (h - 1)) >> 1; + } + + if (w > dst->w || h > dst->h) + { + log_add (log_Warning, "TFB_DrawCanvas_Rescale_Trilinear: " + "Tried to scale image to size %d %d when dest_canvas" + " has only dimensions of %d %d! Failing.", + w, h, dst->w, dst->h); + return; + } + + if ((srcfmt->BytesPerPixel != 1 && srcfmt->BytesPerPixel != 4) || + (mmfmt->BytesPerPixel != 1 && mmfmt->BytesPerPixel != 4) || + (dst->format->BytesPerPixel != 4)) + { + log_add (log_Warning, "TFB_DrawCanvas_Rescale_Trilinear: " + "Tried to deal with unknown BPP: %d -> %d, mipmap %d", + srcfmt->BitsPerPixel, dst->format->BitsPerPixel, + mmfmt->BitsPerPixel); + return; + } + + // use colorkeys where appropriate + if (srcfmt->Amask) + { // alpha transparency + mk0 = srcfmt->Amask; + ck0 = 0; + } + else if (TFB_GetColorKey (src, &ck0) == 0) + { // colorkey transparency + mk0 = ~srcfmt->Amask; + ck0 &= mk0; + } + + if (mmfmt->Amask) + { // alpha transparency + mk1 = mmfmt->Amask; + ck1 = 0; + } + else if (TFB_GetColorKey (mm, &ck1) == 0) + { // colorkey transparency + mk1 = ~mmfmt->Amask; + ck1 &= mk1; + } + + SDL_LockSurface(src); + SDL_LockSurface(dst); + SDL_LockSurface(mm); + + for (y = 0, sy0 = ssy0, sy1 = ssy1; + y < h; + ++y, sy0 += fsy0, sy1 += fsy1) + { + Uint32 *dst_p = (Uint32 *) ((Uint8*)dst->pixels + y * dst->pitch); + const int py0 = (sy0 >> 16); + const int py1 = (sy1 >> 16); + Uint8 *src_a0 = (Uint8*)src->pixels + py0 * slen; + Uint8 *src_a1 = (Uint8*)mm->pixels + py1 * mmlen; + // retrieve the fractional portions of y + const Uint8 v0 = (sy0 >> 8) & 0xff; + const Uint8 v1 = (sy1 >> 8) & 0xff; + Uint8 w0[4], w1[4]; // pixel weight vectors + + for (x = 0, sx0 = ssx0, sx1 = ssx1; + x < w; + ++x, ++dst_p, sx0 += fsx0, sx1 += fsx1) + { + const int px0 = (sx0 >> 16); + const int px1 = (sx1 >> 16); + // retrieve the fractional portions of x + const Uint8 u0 = (sx0 >> 8) & 0xff; + const Uint8 u1 = (sx1 >> 8) & 0xff; + // pixels are examined and numbered in pattern + // 0 1 + // 2 3 + // the ideal pixel (4) is somewhere between these four + // and is calculated from these using weight vector (w) + // with a dot product + pixel_t p0[5], p1[5]; + Uint8 res_a; + + w0[0] = btable[255 - u0][255 - v0]; + w0[1] = btable[u0][255 - v0]; + w0[2] = btable[255 - u0][v0]; + w0[3] = btable[u0][v0]; + + w1[0] = btable[255 - u1][255 - v1]; + w1[1] = btable[u1][255 - v1]; + w1[2] = btable[255 - u1][v1]; + w1[3] = btable[u1][v1]; + + // Collect interesting pixels from src image + // Optimization: speed is criticial on larger images; + // most pixel reads fall completely inside the image + if (px0 >= 0 && px0 + 1 < src->w && py0 >= 0 && py0 + 1 < src->h) + { + Uint8 *src_p = src_a0 + px0 * sbpp; + + p0[0].value = scale_read_pixel (src_p, srcfmt, + srcpal, mk0, ck0); + p0[1].value = scale_read_pixel (src_p + sbpp, srcfmt, + srcpal, mk0, ck0); + p0[2].value = scale_read_pixel (src_p + slen, srcfmt, + srcpal, mk0, ck0); + p0[3].value = scale_read_pixel (src_p + sbpp + slen, srcfmt, + srcpal, mk0, ck0); + } + else + { + p0[0].value = scale_get_pixel (src, mk0, ck0, px0, py0); + p0[1].value = scale_get_pixel (src, mk0, ck0, px0 + 1, py0); + p0[2].value = scale_get_pixel (src, mk0, ck0, px0, py0 + 1); + p0[3].value = scale_get_pixel (src, mk0, ck0, + px0 + 1, py0 + 1); + } + + // Collect interesting pixels from mipmap image + if (px1 >= 0 && px1 + 1 < mm->w && py1 >= 0 && py1 + 1 < mm->h) + { + Uint8 *mm_p = src_a1 + px1 * mmbpp; + + p1[0].value = scale_read_pixel (mm_p, mmfmt, + srcpal, mk1, ck1); + p1[1].value = scale_read_pixel (mm_p + mmbpp, mmfmt, + srcpal, mk1, ck1); + p1[2].value = scale_read_pixel (mm_p + mmlen, mmfmt, + srcpal, mk1, ck1); + p1[3].value = scale_read_pixel (mm_p + mmbpp + mmlen, mmfmt, + srcpal, mk1, ck1); + } + else + { + p1[0].value = scale_get_pixel (mm, mk1, ck1, px1, py1); + p1[1].value = scale_get_pixel (mm, mk1, ck1, px1 + 1, py1); + p1[2].value = scale_get_pixel (mm, mk1, ck1, px1, py1 + 1); + p1[3].value = scale_get_pixel (mm, mk1, ck1, + px1 + 1, py1 + 1); + } + + p0[4].c.a = dot_product_8_4 (p0, 3, w0); + p1[4].c.a = dot_product_8_4 (p1, 3, w1); + + res_a = blend_ratio_2 (p0[4].c.a, p1[4].c.a, ratio); + + if (res_a <= alpha_threshold) + { + *dst_p = transparent; + } + else if (!dst_has_alpha) + { // RGB surface handling + p0[4].c.r = dot_product_8_4 (p0, 0, w0); + p0[4].c.g = dot_product_8_4 (p0, 1, w0); + p0[4].c.b = dot_product_8_4 (p0, 2, w0); + + p1[4].c.r = dot_product_8_4 (p1, 0, w1); + p1[4].c.g = dot_product_8_4 (p1, 1, w1); + p1[4].c.b = dot_product_8_4 (p1, 2, w1); + + p0[4].c.r = blend_ratio_2 (p0[4].c.r, p1[4].c.r, ratio); + p0[4].c.g = blend_ratio_2 (p0[4].c.g, p1[4].c.g, ratio); + p0[4].c.b = blend_ratio_2 (p0[4].c.b, p1[4].c.b, ratio); + + // TODO: we should handle alpha-blending here, but we do + // not know the destination color for blending! + + *dst_p = + (p0[4].c.r << dstfmt->Rshift) | + (p0[4].c.g << dstfmt->Gshift) | + (p0[4].c.b << dstfmt->Bshift); + } + else + { // RGBA surface handling + + // we do not want to blend with non-present pixels + // (pixels that have alpha == 0) as these will + // skew the result and make resulting alpha useless + if (p0[4].c.a != 0) + { + int i; + for (i = 0; i < 4; ++i) + if (p0[i].c.a == 0) + w0[i] = 0; + + p0[4].c.r = weight_product_8_4 (p0, 0, w0); + p0[4].c.g = weight_product_8_4 (p0, 1, w0); + p0[4].c.b = weight_product_8_4 (p0, 2, w0); + } + if (p1[4].c.a != 0) + { + int i; + for (i = 0; i < 4; ++i) + if (p1[i].c.a == 0) + w1[i] = 0; + + p1[4].c.r = weight_product_8_4 (p1, 0, w1); + p1[4].c.g = weight_product_8_4 (p1, 1, w1); + p1[4].c.b = weight_product_8_4 (p1, 2, w1); + } + + if (p0[4].c.a != 0 && p1[4].c.a != 0) + { // blend if both present + p0[4].c.r = blend_ratio_2 (p0[4].c.r, p1[4].c.r, ratio); + p0[4].c.g = blend_ratio_2 (p0[4].c.g, p1[4].c.g, ratio); + p0[4].c.b = blend_ratio_2 (p0[4].c.b, p1[4].c.b, ratio); + } + else if (p1[4].c.a != 0) + { // other pixel is present + p0[4].value = p1[4].value; + } + + // error-correct alpha to fully opaque to remove + // the often unwanted and unnecessary blending + if (res_a > 0xf8) + res_a = 0xff; + + *dst_p = + (p0[4].c.r << dstfmt->Rshift) | + (p0[4].c.g << dstfmt->Gshift) | + (p0[4].c.b << dstfmt->Bshift) | + (res_a << dstfmt->Ashift); + } + } + } + + SDL_UnlockSurface(mm); + SDL_UnlockSurface(dst); + SDL_UnlockSurface(src); +} + +void +TFB_DrawCanvas_Rescale_Bilinear (TFB_Canvas src_canvas, TFB_Canvas dst_canvas, + int scale, HOT_SPOT* src_hs, EXTENT* size, HOT_SPOT* dst_hs) +{ + SDL_Surface *src = src_canvas; + SDL_Surface *dst = dst_canvas; + SDL_PixelFormat *srcfmt = src->format; + SDL_PixelFormat *dstfmt = dst->format; + SDL_Color *srcpal = srcfmt->palette? srcfmt->palette->colors : 0; + const int sbpp = srcfmt->BytesPerPixel; + const int slen = src->pitch; + const int dst_has_alpha = (dstfmt->Amask != 0); + Uint32 srckey = 0, transparent = 0; + const int alpha_threshold = dst_has_alpha ? 0 : 127; + // source masks and keys + Uint32 mk = 0, ck = ~0; + // source fractional x and y positions + int sx, sy; + // source fractional dx and dy increments + int fsx = 0, fsy = 0; + // source fractional x and y starting points + int ssx = 0, ssy = 0; + int x, y, w, h; + + // Get destination transparent color if it exists + TFB_GetColorKey (dst, &transparent); + + if (scale > 0) + { + // Use (scale / GSCALE_IDENTITY) sizing factor + TFB_DrawCanvas_GetScaledExtent (src, src_hs, NULL, NULL, scale, + TFB_SCALE_BILINEAR, size, dst_hs); + + w = size->width; + h = size->height; + fsx = (GSCALE_IDENTITY << 16) / scale; + fsy = (GSCALE_IDENTITY << 16) / scale; + + // position the hotspots directly over each other + ssx = (src_hs->x << 16) - fsx * dst_hs->x; + ssy = (src_hs->y << 16) - fsy * dst_hs->y; + } + else + { + // Just go with the dst surface dimensions + w = dst->w; + h = dst->h; + fsx = (src->w << 16) / w; + fsy = (src->h << 16) / h; + + // give equal importance to both edges + ssx = (((src->w - 1) << 16) - fsx * (w - 1)) >> 1; + ssy = (((src->h - 1) << 16) - fsy * (h - 1)) >> 1; + } + + if (w > dst->w || h > dst->h) + { + log_add (log_Warning, "TFB_DrawCanvas_Rescale_Bilinear: " + "Tried to scale image to size %d %d when dest_canvas" + " has only dimensions of %d %d! Failing.", + w, h, dst->w, dst->h); + return; + } + + if ((srcfmt->BytesPerPixel != 1 && srcfmt->BytesPerPixel != 4) || + (dst->format->BytesPerPixel != 4)) + { + log_add (log_Warning, "TFB_DrawCanvas_Rescale_Bilinear: " + "Tried to deal with unknown BPP: %d -> %d", + srcfmt->BitsPerPixel, dst->format->BitsPerPixel); + return; + } + + // use colorkeys where appropriate + if (srcfmt->Amask) + { // alpha transparency + mk = srcfmt->Amask; + ck = 0; + } + else if (TFB_GetColorKey (src, &srckey) == 0) + { // colorkey transparency + mk = ~srcfmt->Amask; + ck = srckey & mk; + } + + SDL_LockSurface(src); + SDL_LockSurface(dst); + + for (y = 0, sy = ssy; y < h; ++y, sy += fsy) + { + Uint32 *dst_p = (Uint32 *) ((Uint8*)dst->pixels + y * dst->pitch); + const int py = (sy >> 16); + Uint8 *src_a = (Uint8*)src->pixels + py * slen; + // retrieve the fractional portions of y + const Uint8 v = (sy >> 8) & 0xff; + Uint8 weight[4]; // pixel weight vectors + + for (x = 0, sx = ssx; x < w; ++x, ++dst_p, sx += fsx) + { + const int px = (sx >> 16); + // retrieve the fractional portions of x + const Uint8 u = (sx >> 8) & 0xff; + // pixels are examined and numbered in pattern + // 0 1 + // 2 3 + // the ideal pixel (4) is somewhere between these four + // and is calculated from these using weight vector (weight) + // with a dot product + pixel_t p[5]; + + weight[0] = btable[255 - u][255 - v]; + weight[1] = btable[u][255 - v]; + weight[2] = btable[255 - u][v]; + weight[3] = btable[u][v]; + + // Collect interesting pixels from src image + // Optimization: speed is criticial on larger images; + // most pixel reads fall completely inside the image + if (px >= 0 && px + 1 < src->w && py >= 0 && py + 1 < src->h) + { + Uint8 *src_p = src_a + px * sbpp; + + p[0].value = scale_read_pixel (src_p, srcfmt, srcpal, mk, ck); + p[1].value = scale_read_pixel (src_p + sbpp, srcfmt, + srcpal, mk, ck); + p[2].value = scale_read_pixel (src_p + slen, srcfmt, + srcpal, mk, ck); + p[3].value = scale_read_pixel (src_p + sbpp + slen, srcfmt, + srcpal, mk, ck); + } + else + { + p[0].value = scale_get_pixel (src, mk, ck, px, py); + p[1].value = scale_get_pixel (src, mk, ck, px + 1, py); + p[2].value = scale_get_pixel (src, mk, ck, px, py + 1); + p[3].value = scale_get_pixel (src, mk, ck, px + 1, py + 1); + } + + p[4].c.a = dot_product_8_4 (p, 3, weight); + + if (p[4].c.a <= alpha_threshold) + { + *dst_p = transparent; + } + else if (!dst_has_alpha) + { // RGB surface handling + p[4].c.r = dot_product_8_4 (p, 0, weight); + p[4].c.g = dot_product_8_4 (p, 1, weight); + p[4].c.b = dot_product_8_4 (p, 2, weight); + + // TODO: we should handle alpha-blending here, but we do + // not know the destination color for blending! + + *dst_p = + (p[4].c.r << dstfmt->Rshift) | + (p[4].c.g << dstfmt->Gshift) | + (p[4].c.b << dstfmt->Bshift); + } + else + { // RGBA surface handling + + // we do not want to blend with non-present pixels + // (pixels that have alpha == 0) as these will + // skew the result and make resulting alpha useless + int i; + for (i = 0; i < 4; ++i) + if (p[i].c.a == 0) + weight[i] = 0; + + p[4].c.r = weight_product_8_4 (p, 0, weight); + p[4].c.g = weight_product_8_4 (p, 1, weight); + p[4].c.b = weight_product_8_4 (p, 2, weight); + + // error-correct alpha to fully opaque to remove + // the often unwanted and unnecessary blending + if (p[4].c.a > 0xf8) + p[4].c.a = 0xff; + + *dst_p = + (p[4].c.r << dstfmt->Rshift) | + (p[4].c.g << dstfmt->Gshift) | + (p[4].c.b << dstfmt->Bshift) | + (p[4].c.a << dstfmt->Ashift); + } + } + } + + SDL_UnlockSurface(dst); + SDL_UnlockSurface(src); +} + +void +TFB_DrawCanvas_Lock (TFB_Canvas canvas) +{ + SDL_Surface *surf = canvas; + SDL_LockSurface (surf); +} + +void +TFB_DrawCanvas_Unlock (TFB_Canvas canvas) +{ + SDL_Surface *surf = canvas; + SDL_UnlockSurface (surf); +} + +void +TFB_DrawCanvas_GetScreenFormat (TFB_PixelFormat *fmt) +{ + SDL_PixelFormat *sdl = SDL_Screen->format; + + if (sdl->palette) + { + log_add (log_Warning, "TFB_DrawCanvas_GetScreenFormat() WARNING:" + "Paletted display format will be slow"); + + fmt->BitsPerPixel = 32; + fmt->Rmask = 0x000000ff; + fmt->Gmask = 0x0000ff00; + fmt->Bmask = 0x00ff0000; + fmt->Amask = 0xff000000; + } + else + { + fmt->BitsPerPixel = sdl->BitsPerPixel; + fmt->Rmask = sdl->Rmask; + fmt->Gmask = sdl->Gmask; + fmt->Bmask = sdl->Bmask; + fmt->Amask = sdl->Amask; + } +} + +int +TFB_DrawCanvas_GetStride (TFB_Canvas canvas) +{ + SDL_Surface *surf = canvas; + return surf->pitch; +} + +void* +TFB_DrawCanvas_GetLine (TFB_Canvas canvas, int line) +{ + SDL_Surface *surf = canvas; + return (uint8 *)surf->pixels + surf->pitch * line; +} + +Color +TFB_DrawCanvas_GetPixel (TFB_Canvas canvas, int x, int y) +{ + SDL_Surface* surf = canvas; + Uint32 pixel; + GetPixelFn getpixel; + Color c = {0, 0, 0, 0}; + + if (x < 0 || x >= surf->w || y < 0 || y >= surf->h) + { // outside bounds, return 0 + return c; + } + + SDL_LockSurface (surf); + + getpixel = getpixel_for(surf); + pixel = (*getpixel)(surf, x, y); + SDL_GetRGBA (pixel, surf->format, &c.r, &c.g, &c.b, &c.a); + + SDL_UnlockSurface (surf); + + return c; +} + +void +TFB_DrawCanvas_Rotate (TFB_Canvas src_canvas, TFB_Canvas dst_canvas, + int angle, EXTENT size) +{ + SDL_Surface *src = src_canvas; + SDL_Surface *dst = dst_canvas; + int ret; + Color color; + + if (size.width > dst->w || size.height > dst->h) + { + log_add (log_Warning, "TFB_DrawCanvas_Rotate: Tried to rotate" + " image to size %d %d when dst_canvas has only dimensions" + " of %d %d! Failing.", + size.width, size.height, dst->w, dst->h); + return; + } + + if (TFB_DrawCanvas_GetTransparentColor (src, &color)) + { + TFB_DrawCanvas_SetTransparentColor (dst, color, FALSE); + /* fill destination with transparent color before rotating */ + SDL_FillRect(dst, NULL, SDL_MapRGBA (dst->format, + color.r, color.g, color.b, 0)); + } + + ret = rotateSurface (src, dst, angle, 0); + if (ret != 0) + { + log_add (log_Warning, "TFB_DrawCanvas_Rotate: WARNING:" + " actual rotation func returned failure\n"); + } +} + +void +TFB_DrawCanvas_GetRotatedExtent (TFB_Canvas src_canvas, int angle, EXTENT *size) +{ + int dstw, dsth; + SDL_Surface *src = src_canvas; + + rotozoomSurfaceSize (src->w, src->h, angle, 1, &dstw, &dsth); + size->height = dsth; + size->width = dstw; +} + +void +TFB_DrawCanvas_CopyRect (TFB_Canvas source, const RECT *srcRect, + TFB_Canvas target, POINT dstPt) +{ + SDL_Rect sourceRect, targetRect; + + if (source == 0 || target == 0) + { + log_add (log_Warning, + "ERROR: TFB_DrawCanvas_CopyRect passed null canvas ptr"); + return; + } + + sourceRect.x = srcRect->corner.x; + sourceRect.y = srcRect->corner.y; + sourceRect.w = srcRect->extent.width; + sourceRect.h = srcRect->extent.height; + + targetRect.x = dstPt.x; + targetRect.y = dstPt.y; + // According to SDL docs, width and height are ignored, but + // we'll set them anyway, just in case. + targetRect.w = srcRect->extent.width; + targetRect.h = srcRect->extent.height; + + SDL_BlitSurface (source, &sourceRect, target, &targetRect); +} + +void +TFB_DrawCanvas_SetClipRect (TFB_Canvas canvas, const RECT *clipRect) +{ + if (canvas == 0) + { + log_add (log_Warning, + "ERROR: TFB_DrawCanvas_SetClipRect passed null canvas ptr"); + return; + } + + if (!clipRect) + { // clipping disabled + SDL_SetClipRect (canvas, NULL); + } + else + { + SDL_Rect r; + r.x = clipRect->corner.x; + r.y = clipRect->corner.y; + r.w = clipRect->extent.width; + r.h = clipRect->extent.height; + SDL_SetClipRect (canvas, &r); + } +} + +BOOLEAN +TFB_DrawCanvas_Intersect (TFB_Canvas canvas1, POINT c1org, + TFB_Canvas canvas2, POINT c2org, const RECT *interRect) +{ + BOOLEAN ret = FALSE; + SDL_Surface *surf1 = canvas1; + SDL_Surface *surf2 = canvas2; + int x, y; + Uint32 s1key, s2key; + Uint32 s1mask, s2mask; + GetPixelFn getpixel1, getpixel2; + + SDL_LockSurface (surf1); + SDL_LockSurface (surf2); + + getpixel1 = getpixel_for (surf1); + getpixel2 = getpixel_for (surf2); + + if (surf1->format->Amask) + { // use alpha transparency info + s1mask = surf1->format->Amask; + // consider any not fully transparent pixel collidable + s1key = 0; + } + else + { // colorkey transparency + Uint32 colorkey = 0; + TFB_GetColorKey(surf1, &colorkey); + s1mask = ~surf1->format->Amask; + s1key = colorkey & s1mask; + } + + if (surf2->format->Amask) + { // use alpha transparency info + s2mask = surf2->format->Amask; + // consider any not fully transparent pixel collidable + s2key = 0; + } + else + { // colorkey transparency + Uint32 colorkey = 0; + TFB_GetColorKey(surf2, &colorkey); + s2mask = ~surf2->format->Amask; + s2key = colorkey & s2mask; + } + + // convert surface origins to pixel offsets within + c1org.x = interRect->corner.x - c1org.x; + c1org.y = interRect->corner.y - c1org.y; + c2org.x = interRect->corner.x - c2org.x; + c2org.y = interRect->corner.y - c2org.y; + + for (y = 0; y < interRect->extent.height; ++y) + { + for (x = 0; x < interRect->extent.width; ++x) + { + Uint32 p1 = getpixel1 (surf1, x + c1org.x, y + c1org.y) & s1mask; + Uint32 p2 = getpixel2 (surf2, x + c2org.x, y + c2org.y) & s2mask; + + if (p1 != s1key && p2 != s2key) + { // pixel collision + ret = TRUE; + break; + } + } + } + + SDL_UnlockSurface (surf2); + SDL_UnlockSurface (surf1); + + return ret; +} + +// Read/write the canvas pixels in a Color format understood by the core. +// The pixels array is assumed to be at least width * height large. +// The pixels array can be wider/narrower or taller/shorter than the canvas, +// and in that case, only the relevant pixels will be transfered. +static BOOLEAN +TFB_DrawCanvas_TransferColors (TFB_Canvas canvas, BOOLEAN write, + Color *pixels, int width, int height) +{ + SDL_Surface *surf = canvas; + SDL_PixelFormat *fmt; + GetPixelFn getpix; + PutPixelFn putpix; + int x, y, w, h; + + if (canvas == 0) + { + log_add (log_Warning, "ERROR: TFB_DrawCanvas_TransferColors " + "passed null canvas"); + return FALSE; + } + + fmt = surf->format; + getpix = getpixel_for (surf); + putpix = putpixel_for (surf); + + w = width < surf->w ? width : surf->w; + h = height < surf->h ? height : surf->h; + + SDL_LockSurface (surf); + + // This could be done faster if we assumed 32bpp surfaces + for (y = 0; y < h; ++y) + { + // pixels array pitch is width so as not to violate the interface + Color *c = pixels + y * width; + + for (x = 0; x < w; ++x, ++c) + { + if (write) + { // writing from data to surface + Uint32 p = SDL_MapRGBA (fmt, c->r, c->g, c->b, c->a); + putpix (surf, x, y, p); + } + else + { // reading from surface to data + Uint32 p = getpix (surf, x, y); + SDL_GetRGBA (p, fmt, &c->r, &c->g, &c->b, &c->a); + } + } + } + + SDL_UnlockSurface (surf); + + return TRUE; +} + +// Read the canvas pixels in a Color format understood by the core. +// See TFB_DrawCanvas_TransferColors() for pixels array info +BOOLEAN +TFB_DrawCanvas_GetPixelColors (TFB_Canvas canvas, Color *pixels, + int width, int height) +{ + return TFB_DrawCanvas_TransferColors (canvas, FALSE, pixels, + width, height); +} + +// Write the canvas pixels from a Color format understood by the core. +// See TFB_DrawCanvas_TransferColors() for pixels array info +BOOLEAN +TFB_DrawCanvas_SetPixelColors (TFB_Canvas canvas, const Color *pixels, + int width, int height) +{ + // unconst pixels, but it is safe -- it will not be written to + return TFB_DrawCanvas_TransferColors (canvas, TRUE, (Color *)pixels, + width, height); +} + +// Read/write the indexed canvas pixels as palette indexes. +// The data array is assumed to be at least width * height large. +// The data array can be wider/narrower or taller/shorter than the canvas, +// and in that case, only the relevant pixels will be transfered. +static BOOLEAN +TFB_DrawCanvas_TransferIndexes (TFB_Canvas canvas, BOOLEAN write, + BYTE *data, int width, int height) +{ + SDL_Surface *surf = canvas; + const SDL_PixelFormat *fmt; + int y, w, h; + + if (canvas == 0) + { + log_add (log_Warning, "ERROR: TFB_DrawCanvas_TransferIndexes " + "passed null canvas"); + return FALSE; + } + fmt = surf->format; + if (!TFB_DrawCanvas_IsPaletted (canvas) || fmt->BitsPerPixel != 8) + { + log_add (log_Warning, "ERROR: TFB_DrawCanvas_TransferIndexes " + "unimplemeted function: not an 8bpp indexed canvas"); + return FALSE; + } + + w = width < surf->w ? width : surf->w; + h = height < surf->h ? height : surf->h; + + SDL_LockSurface (surf); + + for (y = 0; y < h; ++y) + { + Uint8 *surf_p = (Uint8 *)surf->pixels + y * surf->pitch; + // pixels array pitch is width so as not to violate the interface + BYTE *data_p = data + y * width; + + if (write) + { // writing from data to surface + memcpy (surf_p, data_p, w * sizeof (BYTE)); + } + else + { // reading from surface to data + memcpy (data_p, surf_p, w * sizeof (BYTE)); + } + } + + SDL_UnlockSurface (surf); + + return TRUE; +} + +// Read the indexed canvas pixels as palette indexes. +// See TFB_DrawCanvas_TransferIndexes() for data array info. +BOOLEAN +TFB_DrawCanvas_GetPixelIndexes (TFB_Canvas canvas, BYTE *data, + int width, int height) +{ + return TFB_DrawCanvas_TransferIndexes (canvas, FALSE, data, + width, height); +} + +// Write the indexed canvas pixels as palette indexes. +// See TFB_DrawCanvas_TransferIndexes() for data array info. +BOOLEAN +TFB_DrawCanvas_SetPixelIndexes (TFB_Canvas canvas, const BYTE *data, + int width, int height) +{ + // unconst data, but it is safe -- it will not be written to + return TFB_DrawCanvas_TransferIndexes (canvas, TRUE, (BYTE *)data, + width, height); +} diff --git a/src/libs/graphics/sdl/hq2x.c b/src/libs/graphics/sdl/hq2x.c new file mode 100644 index 0000000..02dc806 --- /dev/null +++ b/src/libs/graphics/sdl/hq2x.c @@ -0,0 +1,2888 @@ +//hq2x filter +//-------------------------------------------------------------------------- +//Copyright (C) 2003 MaxSt ( maxst@hiend3d.com ) - Original version +// +//Portions Copyright (C) 2005 Alex Volkov ( codepro@usa.net ) +// Modified Oct-2-2005 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +// Core algorithm of the HQ screen scaler +// adapted from hq2x -- www.hiend3d.com/hq2x.html +// Template +// When this file is built standalone is produces a plain C version +// Also #included by 2xscalers_mmx.c for an MMX version + +#include "libs/graphics/sdl/sdl_common.h" +#include "types.h" +#include "scalers.h" +#include "scaleint.h" +#include "2xscalers.h" + + +// Pixel blending/manipulation instructions +#define PIXEL00_0 dst_p[0] = pix[5]; +#define PIXEL00_10 dst_p[0] = Scale_Blend_31(pix[5], pix[1]); +#define PIXEL00_11 dst_p[0] = Scale_Blend_31(pix[5], pix[4]); +#define PIXEL00_12 dst_p[0] = Scale_Blend_31(pix[5], pix[2]); +#define PIXEL00_20 dst_p[0] = Scale_Blend_211(pix[5], pix[4], pix[2]); +#define PIXEL00_21 dst_p[0] = Scale_Blend_211(pix[5], pix[1], pix[2]); +#define PIXEL00_22 dst_p[0] = Scale_Blend_211(pix[5], pix[1], pix[4]); +#define PIXEL00_60 dst_p[0] = Scale_Blend_521(pix[5], pix[2], pix[4]); +#define PIXEL00_61 dst_p[0] = Scale_Blend_521(pix[5], pix[4], pix[2]); +#define PIXEL00_70 dst_p[0] = Scale_Blend_611(pix[5], pix[4], pix[2]); +#define PIXEL00_90 dst_p[0] = Scale_Blend_233(pix[5], pix[4], pix[2]); +#define PIXEL00_100 dst_p[0] = Scale_Blend_e11(pix[5], pix[4], pix[2]); +#define PIXEL01_0 dst_p[1] = pix[5]; +#define PIXEL01_10 dst_p[1] = Scale_Blend_31(pix[5], pix[3]); +#define PIXEL01_11 dst_p[1] = Scale_Blend_31(pix[5], pix[2]); +#define PIXEL01_12 dst_p[1] = Scale_Blend_31(pix[5], pix[6]); +#define PIXEL01_20 dst_p[1] = Scale_Blend_211(pix[5], pix[2], pix[6]); +#define PIXEL01_21 dst_p[1] = Scale_Blend_211(pix[5], pix[3], pix[6]); +#define PIXEL01_22 dst_p[1] = Scale_Blend_211(pix[5], pix[3], pix[2]); +#define PIXEL01_60 dst_p[1] = Scale_Blend_521(pix[5], pix[6], pix[2]); +#define PIXEL01_61 dst_p[1] = Scale_Blend_521(pix[5], pix[2], pix[6]); +#define PIXEL01_70 dst_p[1] = Scale_Blend_611(pix[5], pix[2], pix[6]); +#define PIXEL01_90 dst_p[1] = Scale_Blend_233(pix[5], pix[2], pix[6]); +#define PIXEL01_100 dst_p[1] = Scale_Blend_e11(pix[5], pix[2], pix[6]); +#define PIXEL10_0 dst_p[dlen] = pix[5]; +#define PIXEL10_10 dst_p[dlen] = Scale_Blend_31(pix[5], pix[7]); +#define PIXEL10_11 dst_p[dlen] = Scale_Blend_31(pix[5], pix[8]); +#define PIXEL10_12 dst_p[dlen] = Scale_Blend_31(pix[5], pix[4]); +#define PIXEL10_20 dst_p[dlen] = Scale_Blend_211(pix[5], pix[8], pix[4]); +#define PIXEL10_21 dst_p[dlen] = Scale_Blend_211(pix[5], pix[7], pix[4]); +#define PIXEL10_22 dst_p[dlen] = Scale_Blend_211(pix[5], pix[7], pix[8]); +#define PIXEL10_60 dst_p[dlen] = Scale_Blend_521(pix[5], pix[4], pix[8]); +#define PIXEL10_61 dst_p[dlen] = Scale_Blend_521(pix[5], pix[8], pix[4]); +#define PIXEL10_70 dst_p[dlen] = Scale_Blend_611(pix[5], pix[8], pix[4]); +#define PIXEL10_90 dst_p[dlen] = Scale_Blend_233(pix[5], pix[8], pix[4]); +#define PIXEL10_100 dst_p[dlen] = Scale_Blend_e11(pix[5], pix[8], pix[4]); +#define PIXEL11_0 dst_p[dlen + 1] = pix[5]; +#define PIXEL11_10 dst_p[dlen + 1] = Scale_Blend_31(pix[5], pix[9]); +#define PIXEL11_11 dst_p[dlen + 1] = Scale_Blend_31(pix[5], pix[6]); +#define PIXEL11_12 dst_p[dlen + 1] = Scale_Blend_31(pix[5], pix[8]); +#define PIXEL11_20 dst_p[dlen + 1] = Scale_Blend_211(pix[5], pix[6], pix[8]); +#define PIXEL11_21 dst_p[dlen + 1] = Scale_Blend_211(pix[5], pix[9], pix[8]); +#define PIXEL11_22 dst_p[dlen + 1] = Scale_Blend_211(pix[5], pix[9], pix[6]); +#define PIXEL11_60 dst_p[dlen + 1] = Scale_Blend_521(pix[5], pix[8], pix[6]); +#define PIXEL11_61 dst_p[dlen + 1] = Scale_Blend_521(pix[5], pix[6], pix[8]); +#define PIXEL11_70 dst_p[dlen + 1] = Scale_Blend_611(pix[5], pix[6], pix[8]); +#define PIXEL11_90 dst_p[dlen + 1] = Scale_Blend_233(pix[5], pix[6], pix[8]); +#define PIXEL11_100 dst_p[dlen + 1] = Scale_Blend_e11(pix[5], pix[6], pix[8]); + + +// HQ scaling to 2x +// The name expands to +// Scale_HqFilter (for plain C) +// Scale_MMX_HqFilter (for MMX) +// [others when platforms are added] +void +SCALE_(HqFilter) (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r) +{ + int x, y; + const int w = src->w, h = src->h; + int xend, yend; + int dsrc, ddst; + SDL_Rect *region = r; + SDL_Rect limits; + SDL_PixelFormat *fmt = dst->format; + const int sp = src->pitch, dp = dst->pitch; + const int bpp = fmt->BytesPerPixel; + const int slen = sp / bpp, dlen = dp / bpp; + Uint32 *src_p = (Uint32 *)src->pixels; + Uint32 *dst_p = (Uint32 *)dst->pixels; + + int prevline, nextline; + Uint32 pix[10]; + Uint32 yuv[10]; +// +----+----+----+ +// | | | | +// | p1 | p2 | p3 | +// +----+----+----+ +// | | | | +// | p4 | p5 | p6 | +// +----+----+----+ +// | | | | +// | p7 | p8 | p9 | +// +----+----+----+ + + // MMX code runs faster w/o branching + #define HQXX_DIFFYUV(p1, p2) \ + SCALE_DIFFYUV (p1, p2) + + SCALE_(PlatInit) (); + + // expand updated region if necessary + // pixels neighbooring the updated region may + // change as a result of updates + limits.x = 0; + limits.y = 0; + limits.w = src->w; + limits.h = src->h; + Scale_ExpandRect (region, 1, &limits); + + xend = region->x + region->w; + yend = region->y + region->h; + dsrc = slen - region->w; + ddst = (dlen - region->w) * 2; + + // move ptrs to the first updated pixel + src_p += slen * region->y + region->x; + dst_p += (dlen * region->y + region->x) * 2; + + for (y = region->y; y < yend; ++y, dst_p += ddst, src_p += dsrc) + { + if (y > 0) + prevline = -slen; + else + prevline = 0; + + if (y < h - 1) + nextline = slen; + else + nextline = 0; + + // prime the (tiny) sliding-window pixel arrays + pix[3] = src_p[prevline]; + pix[6] = src_p[0]; + pix[9] = src_p[nextline]; + + yuv[3] = SCALE_TOYUV (pix[3]); + yuv[6] = SCALE_TOYUV (pix[6]); + yuv[9] = SCALE_TOYUV (pix[9]); + + if (region->x > 0) + { + pix[2] = src_p[prevline - 1]; + pix[5] = src_p[-1]; + pix[8] = src_p[nextline - 1]; + + yuv[2] = SCALE_TOYUV (pix[2]); + yuv[5] = SCALE_TOYUV (pix[5]); + yuv[8] = SCALE_TOYUV (pix[8]); + } + else + { + pix[2] = pix[3]; + pix[5] = pix[6]; + pix[8] = pix[9]; + + yuv[2] = yuv[3]; + yuv[5] = yuv[6]; + yuv[8] = yuv[9]; + } + + for (x = region->x; x < xend; ++x, ++src_p, dst_p += 2) + { + int pattern = 0; + + // slide the window + pix[1] = pix[2]; + pix[4] = pix[5]; + pix[7] = pix[8]; + + yuv[1] = yuv[2]; + yuv[4] = yuv[5]; + yuv[7] = yuv[8]; + + pix[2] = pix[3]; + pix[5] = pix[6]; + pix[8] = pix[9]; + + yuv[2] = yuv[3]; + yuv[5] = yuv[6]; + yuv[8] = yuv[9]; + + if (x < w - 1) + { + pix[3] = src_p[prevline + 1]; + pix[6] = src_p[1]; + pix[9] = src_p[nextline + 1]; + + yuv[3] = SCALE_TOYUV (pix[3]); + yuv[6] = SCALE_TOYUV (pix[6]); + yuv[9] = SCALE_TOYUV (pix[9]); + } + else + { + pix[3] = pix[2]; + pix[6] = pix[5]; + pix[9] = pix[8]; + + yuv[3] = yuv[2]; + yuv[6] = yuv[5]; + yuv[9] = yuv[8]; + } + + // this runs much faster with branching removed + pattern |= HQXX_DIFFYUV (yuv[5], yuv[1]) & 0x0001; + pattern |= HQXX_DIFFYUV (yuv[5], yuv[2]) & 0x0002; + pattern |= HQXX_DIFFYUV (yuv[5], yuv[3]) & 0x0004; + pattern |= HQXX_DIFFYUV (yuv[5], yuv[4]) & 0x0008; + pattern |= HQXX_DIFFYUV (yuv[5], yuv[6]) & 0x0010; + pattern |= HQXX_DIFFYUV (yuv[5], yuv[7]) & 0x0020; + pattern |= HQXX_DIFFYUV (yuv[5], yuv[8]) & 0x0040; + pattern |= HQXX_DIFFYUV (yuv[5], yuv[9]) & 0x0080; + + switch (pattern) + { + case 0: + case 1: + case 4: + case 32: + case 128: + case 5: + case 132: + case 160: + case 33: + case 129: + case 36: + case 133: + case 164: + case 161: + case 37: + case 165: + { + PIXEL00_20 + PIXEL01_20 + PIXEL10_20 + PIXEL11_20 + break; + } + case 2: + case 34: + case 130: + case 162: + { + PIXEL00_22 + PIXEL01_21 + PIXEL10_20 + PIXEL11_20 + break; + } + case 16: + case 17: + case 48: + case 49: + { + PIXEL00_20 + PIXEL01_22 + PIXEL10_20 + PIXEL11_21 + break; + } + case 64: + case 65: + case 68: + case 69: + { + PIXEL00_20 + PIXEL01_20 + PIXEL10_21 + PIXEL11_22 + break; + } + case 8: + case 12: + case 136: + case 140: + { + PIXEL00_21 + PIXEL01_20 + PIXEL10_22 + PIXEL11_20 + break; + } + case 3: + case 35: + case 131: + case 163: + { + PIXEL00_11 + PIXEL01_21 + PIXEL10_20 + PIXEL11_20 + break; + } + case 6: + case 38: + case 134: + case 166: + { + PIXEL00_22 + PIXEL01_12 + PIXEL10_20 + PIXEL11_20 + break; + } + case 20: + case 21: + case 52: + case 53: + { + PIXEL00_20 + PIXEL01_11 + PIXEL10_20 + PIXEL11_21 + break; + } + case 144: + case 145: + case 176: + case 177: + { + PIXEL00_20 + PIXEL01_22 + PIXEL10_20 + PIXEL11_12 + break; + } + case 192: + case 193: + case 196: + case 197: + { + PIXEL00_20 + PIXEL01_20 + PIXEL10_21 + PIXEL11_11 + break; + } + case 96: + case 97: + case 100: + case 101: + { + PIXEL00_20 + PIXEL01_20 + PIXEL10_12 + PIXEL11_22 + break; + } + case 40: + case 44: + case 168: + case 172: + { + PIXEL00_21 + PIXEL01_20 + PIXEL10_11 + PIXEL11_20 + break; + } + case 9: + case 13: + case 137: + case 141: + { + PIXEL00_12 + PIXEL01_20 + PIXEL10_22 + PIXEL11_20 + break; + } + case 18: + case 50: + { + PIXEL00_22 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_10 + } + else + { + PIXEL01_20 + } + PIXEL10_20 + PIXEL11_21 + break; + } + case 80: + case 81: + { + PIXEL00_20 + PIXEL01_22 + PIXEL10_21 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_10 + } + else + { + PIXEL11_20 + } + break; + } + case 72: + case 76: + { + PIXEL00_21 + PIXEL01_20 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_10 + } + else + { + PIXEL10_20 + } + PIXEL11_22 + break; + } + case 10: + case 138: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_10 + } + else + { + PIXEL00_20 + } + PIXEL01_21 + PIXEL10_22 + PIXEL11_20 + break; + } + case 66: + { + PIXEL00_22 + PIXEL01_21 + PIXEL10_21 + PIXEL11_22 + break; + } + case 24: + { + PIXEL00_21 + PIXEL01_22 + PIXEL10_22 + PIXEL11_21 + break; + } + case 7: + case 39: + case 135: + { + PIXEL00_11 + PIXEL01_12 + PIXEL10_20 + PIXEL11_20 + break; + } + case 148: + case 149: + case 180: + { + PIXEL00_20 + PIXEL01_11 + PIXEL10_20 + PIXEL11_12 + break; + } + case 224: + case 228: + case 225: + { + PIXEL00_20 + PIXEL01_20 + PIXEL10_12 + PIXEL11_11 + break; + } + case 41: + case 169: + case 45: + { + PIXEL00_12 + PIXEL01_20 + PIXEL10_11 + PIXEL11_20 + break; + } + case 22: + case 54: + { + PIXEL00_22 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + PIXEL10_20 + PIXEL11_21 + break; + } + case 208: + case 209: + { + PIXEL00_20 + PIXEL01_22 + PIXEL10_21 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 104: + case 108: + { + PIXEL00_21 + PIXEL01_20 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + PIXEL11_22 + break; + } + case 11: + case 139: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + PIXEL01_21 + PIXEL10_22 + PIXEL11_20 + break; + } + case 19: + case 51: + { + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL00_11 + PIXEL01_10 + } + else + { + PIXEL00_60 + PIXEL01_90 + } + PIXEL10_20 + PIXEL11_21 + break; + } + case 146: + case 178: + { + PIXEL00_22 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_10 + PIXEL11_12 + } + else + { + PIXEL01_90 + PIXEL11_61 + } + PIXEL10_20 + break; + } + case 84: + case 85: + { + PIXEL00_20 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL01_11 + PIXEL11_10 + } + else + { + PIXEL01_60 + PIXEL11_90 + } + PIXEL10_21 + break; + } + case 112: + case 113: + { + PIXEL00_20 + PIXEL01_22 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL10_12 + PIXEL11_10 + } + else + { + PIXEL10_61 + PIXEL11_90 + } + break; + } + case 200: + case 204: + { + PIXEL00_21 + PIXEL01_20 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_10 + PIXEL11_11 + } + else + { + PIXEL10_90 + PIXEL11_60 + } + break; + } + case 73: + case 77: + { + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL00_12 + PIXEL10_10 + } + else + { + PIXEL00_61 + PIXEL10_90 + } + PIXEL01_20 + PIXEL11_22 + break; + } + case 42: + case 170: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_10 + PIXEL10_11 + } + else + { + PIXEL00_90 + PIXEL10_60 + } + PIXEL01_21 + PIXEL11_20 + break; + } + case 14: + case 142: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_10 + PIXEL01_12 + } + else + { + PIXEL00_90 + PIXEL01_61 + } + PIXEL10_22 + PIXEL11_20 + break; + } + case 67: + { + PIXEL00_11 + PIXEL01_21 + PIXEL10_21 + PIXEL11_22 + break; + } + case 70: + { + PIXEL00_22 + PIXEL01_12 + PIXEL10_21 + PIXEL11_22 + break; + } + case 28: + { + PIXEL00_21 + PIXEL01_11 + PIXEL10_22 + PIXEL11_21 + break; + } + case 152: + { + PIXEL00_21 + PIXEL01_22 + PIXEL10_22 + PIXEL11_12 + break; + } + case 194: + { + PIXEL00_22 + PIXEL01_21 + PIXEL10_21 + PIXEL11_11 + break; + } + case 98: + { + PIXEL00_22 + PIXEL01_21 + PIXEL10_12 + PIXEL11_22 + break; + } + case 56: + { + PIXEL00_21 + PIXEL01_22 + PIXEL10_11 + PIXEL11_21 + break; + } + case 25: + { + PIXEL00_12 + PIXEL01_22 + PIXEL10_22 + PIXEL11_21 + break; + } + case 26: + case 31: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + PIXEL10_22 + PIXEL11_21 + break; + } + case 82: + case 214: + { + PIXEL00_22 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + PIXEL10_21 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 88: + case 248: + { + PIXEL00_21 + PIXEL01_22 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 74: + case 107: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + PIXEL01_21 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + PIXEL11_22 + break; + } + case 27: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + PIXEL01_10 + PIXEL10_22 + PIXEL11_21 + break; + } + case 86: + { + PIXEL00_22 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + PIXEL10_21 + PIXEL11_10 + break; + } + case 216: + { + PIXEL00_21 + PIXEL01_22 + PIXEL10_10 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 106: + { + PIXEL00_10 + PIXEL01_21 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + PIXEL11_22 + break; + } + case 30: + { + PIXEL00_10 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + PIXEL10_22 + PIXEL11_21 + break; + } + case 210: + { + PIXEL00_22 + PIXEL01_10 + PIXEL10_21 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 120: + { + PIXEL00_21 + PIXEL01_22 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + PIXEL11_10 + break; + } + case 75: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + PIXEL01_21 + PIXEL10_10 + PIXEL11_22 + break; + } + case 29: + { + PIXEL00_12 + PIXEL01_11 + PIXEL10_22 + PIXEL11_21 + break; + } + case 198: + { + PIXEL00_22 + PIXEL01_12 + PIXEL10_21 + PIXEL11_11 + break; + } + case 184: + { + PIXEL00_21 + PIXEL01_22 + PIXEL10_11 + PIXEL11_12 + break; + } + case 99: + { + PIXEL00_11 + PIXEL01_21 + PIXEL10_12 + PIXEL11_22 + break; + } + case 57: + { + PIXEL00_12 + PIXEL01_22 + PIXEL10_11 + PIXEL11_21 + break; + } + case 71: + { + PIXEL00_11 + PIXEL01_12 + PIXEL10_21 + PIXEL11_22 + break; + } + case 156: + { + PIXEL00_21 + PIXEL01_11 + PIXEL10_22 + PIXEL11_12 + break; + } + case 226: + { + PIXEL00_22 + PIXEL01_21 + PIXEL10_12 + PIXEL11_11 + break; + } + case 60: + { + PIXEL00_21 + PIXEL01_11 + PIXEL10_11 + PIXEL11_21 + break; + } + case 195: + { + PIXEL00_11 + PIXEL01_21 + PIXEL10_21 + PIXEL11_11 + break; + } + case 102: + { + PIXEL00_22 + PIXEL01_12 + PIXEL10_12 + PIXEL11_22 + break; + } + case 153: + { + PIXEL00_12 + PIXEL01_22 + PIXEL10_22 + PIXEL11_12 + break; + } + case 58: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_10 + } + else + { + PIXEL00_70 + } + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_10 + } + else + { + PIXEL01_70 + } + PIXEL10_11 + PIXEL11_21 + break; + } + case 83: + { + PIXEL00_11 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_10 + } + else + { + PIXEL01_70 + } + PIXEL10_21 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_10 + } + else + { + PIXEL11_70 + } + break; + } + case 92: + { + PIXEL00_21 + PIXEL01_11 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_10 + } + else + { + PIXEL10_70 + } + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_10 + } + else + { + PIXEL11_70 + } + break; + } + case 202: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_10 + } + else + { + PIXEL00_70 + } + PIXEL01_21 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_10 + } + else + { + PIXEL10_70 + } + PIXEL11_11 + break; + } + case 78: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_10 + } + else + { + PIXEL00_70 + } + PIXEL01_12 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_10 + } + else + { + PIXEL10_70 + } + PIXEL11_22 + break; + } + case 154: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_10 + } + else + { + PIXEL00_70 + } + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_10 + } + else + { + PIXEL01_70 + } + PIXEL10_22 + PIXEL11_12 + break; + } + case 114: + { + PIXEL00_22 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_10 + } + else + { + PIXEL01_70 + } + PIXEL10_12 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_10 + } + else + { + PIXEL11_70 + } + break; + } + case 89: + { + PIXEL00_12 + PIXEL01_22 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_10 + } + else + { + PIXEL10_70 + } + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_10 + } + else + { + PIXEL11_70 + } + break; + } + case 90: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_10 + } + else + { + PIXEL00_70 + } + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_10 + } + else + { + PIXEL01_70 + } + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_10 + } + else + { + PIXEL10_70 + } + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_10 + } + else + { + PIXEL11_70 + } + break; + } + case 55: + case 23: + { + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL00_11 + PIXEL01_0 + } + else + { + PIXEL00_60 + PIXEL01_90 + } + PIXEL10_20 + PIXEL11_21 + break; + } + case 182: + case 150: + { + PIXEL00_22 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + PIXEL11_12 + } + else + { + PIXEL01_90 + PIXEL11_61 + } + PIXEL10_20 + break; + } + case 213: + case 212: + { + PIXEL00_20 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL01_11 + PIXEL11_0 + } + else + { + PIXEL01_60 + PIXEL11_90 + } + PIXEL10_21 + break; + } + case 241: + case 240: + { + PIXEL00_20 + PIXEL01_22 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL10_12 + PIXEL11_0 + } + else + { + PIXEL10_61 + PIXEL11_90 + } + break; + } + case 236: + case 232: + { + PIXEL00_21 + PIXEL01_20 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + PIXEL11_11 + } + else + { + PIXEL10_90 + PIXEL11_60 + } + break; + } + case 109: + case 105: + { + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL00_12 + PIXEL10_0 + } + else + { + PIXEL00_61 + PIXEL10_90 + } + PIXEL01_20 + PIXEL11_22 + break; + } + case 171: + case 43: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + PIXEL10_11 + } + else + { + PIXEL00_90 + PIXEL10_60 + } + PIXEL01_21 + PIXEL11_20 + break; + } + case 143: + case 15: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + PIXEL01_12 + } + else + { + PIXEL00_90 + PIXEL01_61 + } + PIXEL10_22 + PIXEL11_20 + break; + } + case 124: + { + PIXEL00_21 + PIXEL01_11 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + PIXEL11_10 + break; + } + case 203: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + PIXEL01_21 + PIXEL10_10 + PIXEL11_11 + break; + } + case 62: + { + PIXEL00_10 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + PIXEL10_11 + PIXEL11_21 + break; + } + case 211: + { + PIXEL00_11 + PIXEL01_10 + PIXEL10_21 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 118: + { + PIXEL00_22 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + PIXEL10_12 + PIXEL11_10 + break; + } + case 217: + { + PIXEL00_12 + PIXEL01_22 + PIXEL10_10 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 110: + { + PIXEL00_10 + PIXEL01_12 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + PIXEL11_22 + break; + } + case 155: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + PIXEL01_10 + PIXEL10_22 + PIXEL11_12 + break; + } + case 188: + { + PIXEL00_21 + PIXEL01_11 + PIXEL10_11 + PIXEL11_12 + break; + } + case 185: + { + PIXEL00_12 + PIXEL01_22 + PIXEL10_11 + PIXEL11_12 + break; + } + case 61: + { + PIXEL00_12 + PIXEL01_11 + PIXEL10_11 + PIXEL11_21 + break; + } + case 157: + { + PIXEL00_12 + PIXEL01_11 + PIXEL10_22 + PIXEL11_12 + break; + } + case 103: + { + PIXEL00_11 + PIXEL01_12 + PIXEL10_12 + PIXEL11_22 + break; + } + case 227: + { + PIXEL00_11 + PIXEL01_21 + PIXEL10_12 + PIXEL11_11 + break; + } + case 230: + { + PIXEL00_22 + PIXEL01_12 + PIXEL10_12 + PIXEL11_11 + break; + } + case 199: + { + PIXEL00_11 + PIXEL01_12 + PIXEL10_21 + PIXEL11_11 + break; + } + case 220: + { + PIXEL00_21 + PIXEL01_11 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_10 + } + else + { + PIXEL10_70 + } + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 158: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_10 + } + else + { + PIXEL00_70 + } + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + PIXEL10_22 + PIXEL11_12 + break; + } + case 234: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_10 + } + else + { + PIXEL00_70 + } + PIXEL01_21 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + PIXEL11_11 + break; + } + case 242: + { + PIXEL00_22 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_10 + } + else + { + PIXEL01_70 + } + PIXEL10_12 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 59: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_10 + } + else + { + PIXEL01_70 + } + PIXEL10_11 + PIXEL11_21 + break; + } + case 121: + { + PIXEL00_12 + PIXEL01_22 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_10 + } + else + { + PIXEL11_70 + } + break; + } + case 87: + { + PIXEL00_11 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + PIXEL10_21 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_10 + } + else + { + PIXEL11_70 + } + break; + } + case 79: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + PIXEL01_12 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_10 + } + else + { + PIXEL10_70 + } + PIXEL11_22 + break; + } + case 122: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_10 + } + else + { + PIXEL00_70 + } + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_10 + } + else + { + PIXEL01_70 + } + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_10 + } + else + { + PIXEL11_70 + } + break; + } + case 94: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_10 + } + else + { + PIXEL00_70 + } + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_10 + } + else + { + PIXEL10_70 + } + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_10 + } + else + { + PIXEL11_70 + } + break; + } + case 218: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_10 + } + else + { + PIXEL00_70 + } + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_10 + } + else + { + PIXEL01_70 + } + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_10 + } + else + { + PIXEL10_70 + } + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 91: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_10 + } + else + { + PIXEL01_70 + } + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_10 + } + else + { + PIXEL10_70 + } + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_10 + } + else + { + PIXEL11_70 + } + break; + } + case 229: + { + PIXEL00_20 + PIXEL01_20 + PIXEL10_12 + PIXEL11_11 + break; + } + case 167: + { + PIXEL00_11 + PIXEL01_12 + PIXEL10_20 + PIXEL11_20 + break; + } + case 173: + { + PIXEL00_12 + PIXEL01_20 + PIXEL10_11 + PIXEL11_20 + break; + } + case 181: + { + PIXEL00_20 + PIXEL01_11 + PIXEL10_20 + PIXEL11_12 + break; + } + case 186: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_10 + } + else + { + PIXEL00_70 + } + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_10 + } + else + { + PIXEL01_70 + } + PIXEL10_11 + PIXEL11_12 + break; + } + case 115: + { + PIXEL00_11 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_10 + } + else + { + PIXEL01_70 + } + PIXEL10_12 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_10 + } + else + { + PIXEL11_70 + } + break; + } + case 93: + { + PIXEL00_12 + PIXEL01_11 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_10 + } + else + { + PIXEL10_70 + } + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_10 + } + else + { + PIXEL11_70 + } + break; + } + case 206: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_10 + } + else + { + PIXEL00_70 + } + PIXEL01_12 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_10 + } + else + { + PIXEL10_70 + } + PIXEL11_11 + break; + } + case 205: + case 201: + { + PIXEL00_12 + PIXEL01_20 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_10 + } + else + { + PIXEL10_70 + } + PIXEL11_11 + break; + } + case 174: + case 46: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_10 + } + else + { + PIXEL00_70 + } + PIXEL01_12 + PIXEL10_11 + PIXEL11_20 + break; + } + case 179: + case 147: + { + PIXEL00_11 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_10 + } + else + { + PIXEL01_70 + } + PIXEL10_20 + PIXEL11_12 + break; + } + case 117: + case 116: + { + PIXEL00_20 + PIXEL01_11 + PIXEL10_12 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_10 + } + else + { + PIXEL11_70 + } + break; + } + case 189: + { + PIXEL00_12 + PIXEL01_11 + PIXEL10_11 + PIXEL11_12 + break; + } + case 231: + { + PIXEL00_11 + PIXEL01_12 + PIXEL10_12 + PIXEL11_11 + break; + } + case 126: + { + PIXEL00_10 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + PIXEL11_10 + break; + } + case 219: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + PIXEL01_10 + PIXEL10_10 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 125: + { + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL00_12 + PIXEL10_0 + } + else + { + PIXEL00_61 + PIXEL10_90 + } + PIXEL01_11 + PIXEL11_10 + break; + } + case 221: + { + PIXEL00_12 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL01_11 + PIXEL11_0 + } + else + { + PIXEL01_60 + PIXEL11_90 + } + PIXEL10_10 + break; + } + case 207: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + PIXEL01_12 + } + else + { + PIXEL00_90 + PIXEL01_61 + } + PIXEL10_10 + PIXEL11_11 + break; + } + case 238: + { + PIXEL00_10 + PIXEL01_12 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + PIXEL11_11 + } + else + { + PIXEL10_90 + PIXEL11_60 + } + break; + } + case 190: + { + PIXEL00_10 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + PIXEL11_12 + } + else + { + PIXEL01_90 + PIXEL11_61 + } + PIXEL10_11 + break; + } + case 187: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + PIXEL10_11 + } + else + { + PIXEL00_90 + PIXEL10_60 + } + PIXEL01_10 + PIXEL11_12 + break; + } + case 243: + { + PIXEL00_11 + PIXEL01_10 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL10_12 + PIXEL11_0 + } + else + { + PIXEL10_61 + PIXEL11_90 + } + break; + } + case 119: + { + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL00_11 + PIXEL01_0 + } + else + { + PIXEL00_60 + PIXEL01_90 + } + PIXEL10_12 + PIXEL11_10 + break; + } + case 237: + case 233: + { + PIXEL00_12 + PIXEL01_20 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_100 + } + PIXEL11_11 + break; + } + case 175: + case 47: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_100 + } + PIXEL01_12 + PIXEL10_11 + PIXEL11_20 + break; + } + case 183: + case 151: + { + PIXEL00_11 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_100 + } + PIXEL10_20 + PIXEL11_12 + break; + } + case 245: + case 244: + { + PIXEL00_20 + PIXEL01_11 + PIXEL10_12 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_100 + } + break; + } + case 250: + { + PIXEL00_10 + PIXEL01_10 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 123: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + PIXEL01_10 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + PIXEL11_10 + break; + } + case 95: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + PIXEL10_10 + PIXEL11_10 + break; + } + case 222: + { + PIXEL00_10 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + PIXEL10_10 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 252: + { + PIXEL00_21 + PIXEL01_11 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_100 + } + break; + } + case 249: + { + PIXEL00_12 + PIXEL01_22 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_100 + } + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 235: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + PIXEL01_21 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_100 + } + PIXEL11_11 + break; + } + case 111: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_100 + } + PIXEL01_12 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + PIXEL11_22 + break; + } + case 63: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_100 + } + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + PIXEL10_11 + PIXEL11_21 + break; + } + case 159: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_100 + } + PIXEL10_22 + PIXEL11_12 + break; + } + case 215: + { + PIXEL00_11 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_100 + } + PIXEL10_21 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 246: + { + PIXEL00_22 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + PIXEL10_12 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_100 + } + break; + } + case 254: + { + PIXEL00_10 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_100 + } + break; + } + case 253: + { + PIXEL00_12 + PIXEL01_11 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_100 + } + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_100 + } + break; + } + case 251: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + PIXEL01_10 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_100 + } + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 239: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_100 + } + PIXEL01_12 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_100 + } + PIXEL11_11 + break; + } + case 127: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_100 + } + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + PIXEL11_10 + break; + } + case 191: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_100 + } + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_100 + } + PIXEL10_11 + PIXEL11_12 + break; + } + case 223: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_100 + } + PIXEL10_10 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 247: + { + PIXEL00_11 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_100 + } + PIXEL10_12 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_100 + } + break; + } + case 255: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_100 + } + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_100 + } + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_100 + } + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_100 + } + break; + } + } + } + } + + SCALE_(PlatDone) (); +} + diff --git a/src/libs/graphics/sdl/nearest2x.c b/src/libs/graphics/sdl/nearest2x.c new file mode 100644 index 0000000..42e6813 --- /dev/null +++ b/src/libs/graphics/sdl/nearest2x.c @@ -0,0 +1,207 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +// Core algorithm of the BiLinear screen scaler +// Template +// When this file is built standalone is produces a plain C version +// Also #included by 2xscalers_mmx.c for an MMX version + +#include "libs/graphics/sdl/sdl_common.h" +#include "types.h" +#include "scalers.h" +#include "scaleint.h" +#include "2xscalers.h" + +// Nearest Neighbor scaling to 2x +// The name expands to +// Scale_Nearest (for plain C) +// Scale_MMX_Nearest (for MMX) +// Scale_SSE_Nearest (for SSE) +// [others when platforms are added] +void +SCALE_(Nearest) (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r) +{ + int y; + const int rw = r->w, rh = r->h; + const int sp = src->pitch, dp = dst->pitch; + const int bpp = dst->format->BytesPerPixel; + const int slen = sp / bpp, dlen = dp / bpp; + const int dsrc = slen-rw, ddst = (dlen-rw) * 2; + + Uint32 *src_p = (Uint32 *)src->pixels; + Uint32 *dst_p = (Uint32 *)dst->pixels; + + // guard asm code against such atrocities + if (rw == 0 || rh == 0) + return; + + SCALE_(PlatInit) (); + + // move ptrs to the first updated pixel + src_p += slen * r->y + r->x; + dst_p += (dlen * r->y + r->x) * 2; + +#if defined(MMX_ASM) && defined(MSVC_ASM) + // Just about everything has to be done in asm for MSVC + // to actually take advantage of asm here + // MSVC does not support beautiful GCC-like asm templates + + y = rh; + __asm + { + // setup vars + mov esi, src_p + mov edi, dst_p + + PREFETCH (esi + 0x40) + PREFETCH (esi + 0x80) + PREFETCH (esi + 0xc0) + + mov edx, dlen + lea edx, [edx * 4] + mov eax, dsrc + lea eax, [eax * 4] + mov ebx, ddst + lea ebx, [ebx * 4] + + mov ecx, rw + loop_y: + test ecx, 1 + jz even_x + + // one-pixel transfer + movd mm1, [esi] + punpckldq mm1, mm1 // pix1 | pix1 -> mm1 + add esi, 4 + MOVNTQ (edi, mm1) + add edi, 8 + MOVNTQ (edi - 8 + edx, mm1) + + even_x: + shr ecx, 1 // x = rw / 2 + jz end_x // rw was 1 + + loop_x: + // two-pixel transfer + movq mm1, [esi] + movq mm2, mm1 + PREFETCH (esi + 0x100) + punpckldq mm1, mm1 // pix1 | pix1 -> mm1 + add esi, 8 + MOVNTQ (edi, mm1) + punpckhdq mm2, mm2 // pix2 | pix2 -> mm2 + MOVNTQ (edi + edx, mm1) + add edi, 16 + MOVNTQ (edi - 8, mm2) + MOVNTQ (edi - 8 + edx, mm2) + + dec ecx + jnz loop_x + + end_x: + // try to prefetch as early as possible to have it on time + PREFETCH (esi + eax) + + mov ecx, rw + add esi, eax + + PREFETCH (esi + 0x40) + PREFETCH (esi + 0x80) + PREFETCH (esi + 0xc0) + + add edi, ebx + + dec y + jnz loop_y + } + +#elif defined(MMX_ASM) && defined(GCC_ASM) + + SCALE_(Prefetch) (src_p + 16); + SCALE_(Prefetch) (src_p + 32); + SCALE_(Prefetch) (src_p + 48); + + for (y = rh; y; --y) + { + int x = rw; + + if (x & 1) + { // one-pixel transfer + __asm__ ( + "movd (%0), %%mm1 \n\t" + "punpckldq %%mm1, %%mm1 \n\t" + MOVNTQ (%%mm1, (%1)) "\n\t" + MOVNTQ (%%mm1, (%1,%2)) "\n\t" + + : /* nothing */ + : /*0*/"r" (src_p), /*1*/"r" (dst_p), /*2*/"r" (dlen*sizeof(Uint32)) + ); + + ++src_p; + dst_p += 2; + --x; + } + + for (x >>= 1; x; --x, src_p += 2, dst_p += 4) + { // two-pixel transfer + __asm__ ( + "movq (%0), %%mm1 \n\t" + "movq %%mm1, %%mm2 \n\t" + PREFETCH (0x100(%0)) "\n\t" + "punpckldq %%mm1, %%mm1 \n\t" + MOVNTQ (%%mm1, (%1)) "\n\t" + MOVNTQ (%%mm1, (%1,%2)) "\n\t" + "punpckhdq %%mm2, %%mm2 \n\t" + MOVNTQ (%%mm2, 8(%1)) "\n\t" + MOVNTQ (%%mm2, 8(%1,%2)) "\n\t" + + : /* nothing */ + : /*0*/"r" (src_p), /*1*/"r" (dst_p), /*2*/"r" (dlen*sizeof(Uint32)) + ); + } + + src_p += dsrc; + // try to prefetch as early as possible to have it on time + SCALE_(Prefetch) (src_p); + + dst_p += ddst; + + SCALE_(Prefetch) (src_p + 16); + SCALE_(Prefetch) (src_p + 32); + SCALE_(Prefetch) (src_p + 48); + } + +#else + // Plain C version + for (y = 0; y < rh; ++y) + { + int x; + for (x = 0; x < rw; ++x, ++src_p, dst_p += 2) + { + Uint32 pix = *src_p; + dst_p[0] = pix; + dst_p[1] = pix; + dst_p[dlen] = pix; + dst_p[dlen + 1] = pix; + } + dst_p += ddst; + src_p += dsrc; + } +#endif + + SCALE_(PlatDone) (); +} + diff --git a/src/libs/graphics/sdl/opengl.c b/src/libs/graphics/sdl/opengl.c new file mode 100644 index 0000000..d8fe33f --- /dev/null +++ b/src/libs/graphics/sdl/opengl.c @@ -0,0 +1,575 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_OPENGL + +#include "libs/graphics/sdl/opengl.h" +#include "libs/graphics/bbox.h" +#include "scalers.h" +#include "options.h" +#include "libs/log.h" + +#if SDL_MAJOR_VERSION == 1 + +typedef struct _gl_screeninfo { + SDL_Surface *scaled; + GLuint texture; + BOOLEAN dirty, active; + SDL_Rect updated; +} TFB_GL_SCREENINFO; + +static TFB_GL_SCREENINFO GL_Screens[TFB_GFX_NUMSCREENS]; + +static int ScreenFilterMode; + +static TFB_ScaleFunc scaler = NULL; +static BOOLEAN first_init = TRUE; + +#if SDL_BYTEORDER == SDL_BIG_ENDIAN +#define R_MASK 0xff000000 +#define G_MASK 0x00ff0000 +#define B_MASK 0x0000ff00 +#define A_MASK 0x000000ff +#else +#define R_MASK 0x000000ff +#define G_MASK 0x0000ff00 +#define B_MASK 0x00ff0000 +#define A_MASK 0xff000000 +#endif + +static void TFB_GL_Preprocess (int force_full_redraw, int transition_amount, int fade_amount); +static void TFB_GL_Postprocess (void); +static void TFB_GL_UploadTransitionScreen (void); +static void TFB_GL_Scaled_ScreenLayer (SCREEN screen, Uint8 a, SDL_Rect *rect); +static void TFB_GL_Unscaled_ScreenLayer (SCREEN screen, Uint8 a, SDL_Rect *rect); +static void TFB_GL_ColorLayer (Uint8 r, Uint8 g, Uint8 b, Uint8 a, SDL_Rect *rect); + +static TFB_GRAPHICS_BACKEND opengl_scaled_backend = { + TFB_GL_Preprocess, + TFB_GL_Postprocess, + TFB_GL_UploadTransitionScreen, + TFB_GL_Scaled_ScreenLayer, + TFB_GL_ColorLayer }; + +static TFB_GRAPHICS_BACKEND opengl_unscaled_backend = { + TFB_GL_Preprocess, + TFB_GL_Postprocess, + TFB_GL_UploadTransitionScreen, + TFB_GL_Unscaled_ScreenLayer, + TFB_GL_ColorLayer }; + + +static int +AttemptColorDepth (int flags, int width, int height, int bpp) +{ + SDL_Surface *SDL_Video; + int videomode_flags; + ScreenColorDepth = bpp; + ScreenWidthActual = width; + ScreenHeightActual = height; + + switch (bpp) { + case 15: + SDL_GL_SetAttribute (SDL_GL_RED_SIZE, 5); + SDL_GL_SetAttribute (SDL_GL_GREEN_SIZE, 5); + SDL_GL_SetAttribute (SDL_GL_BLUE_SIZE, 5); + break; + + case 16: + SDL_GL_SetAttribute (SDL_GL_RED_SIZE, 5); + SDL_GL_SetAttribute (SDL_GL_GREEN_SIZE, 6); + SDL_GL_SetAttribute (SDL_GL_BLUE_SIZE, 5); + break; + + case 24: + SDL_GL_SetAttribute (SDL_GL_RED_SIZE, 8); + SDL_GL_SetAttribute (SDL_GL_GREEN_SIZE, 8); + SDL_GL_SetAttribute (SDL_GL_BLUE_SIZE, 8); + break; + + case 32: + SDL_GL_SetAttribute (SDL_GL_RED_SIZE, 8); + SDL_GL_SetAttribute (SDL_GL_GREEN_SIZE, 8); + SDL_GL_SetAttribute (SDL_GL_BLUE_SIZE, 8); + break; + default: + break; + } + + SDL_GL_SetAttribute (SDL_GL_DEPTH_SIZE, 0); + SDL_GL_SetAttribute (SDL_GL_DOUBLEBUFFER, 1); + + videomode_flags = SDL_OPENGL; + if (flags & TFB_GFXFLAGS_FULLSCREEN) + videomode_flags |= SDL_FULLSCREEN; + videomode_flags |= SDL_ANYFORMAT; + + SDL_Video = SDL_SetVideoMode (ScreenWidthActual, ScreenHeightActual, + bpp, videomode_flags); + if (SDL_Video == NULL) + { + log_add (log_Error, "Couldn't set OpenGL %ix%ix%i video mode: %s", + ScreenWidthActual, ScreenHeightActual, bpp, + SDL_GetError ()); + return -1; + } + else + { + log_add (log_Info, "Set the resolution to: %ix%ix%i" + " (surface reports %ix%ix%i)", + width, height, bpp, + SDL_GetVideoSurface()->w, SDL_GetVideoSurface()->h, + SDL_GetVideoSurface()->format->BitsPerPixel); + + log_add (log_Info, "OpenGL renderer: %s version: %s", + glGetString (GL_RENDERER), glGetString (GL_VERSION)); + } + return 0; +} + +int +TFB_GL_ConfigureVideo (int driver, int flags, int width, int height, int togglefullscreen) +{ + int i, texture_width, texture_height; + GraphicsDriver = driver; + + if (AttemptColorDepth (flags, width, height, 32) && + AttemptColorDepth (flags, width, height, 24) && + AttemptColorDepth (flags, width, height, 16)) + { + log_add (log_Error, "Couldn't set any OpenGL %ix%i video mode!", + width, height); + return -1; + } + + if (!togglefullscreen) + { + if (format_conv_surf) + SDL_FreeSurface (format_conv_surf); + format_conv_surf = SDL_CreateRGBSurface (SDL_SWSURFACE, 0, 0, 32, + R_MASK, G_MASK, B_MASK, A_MASK); + if (format_conv_surf == NULL) + { + log_add (log_Error, "Couldn't create format_conv_surf: %s", + SDL_GetError()); + return -1; + } + + for (i = 0; i < TFB_GFX_NUMSCREENS; i++) + { + if (0 != SDL1_ReInit_Screen (&SDL_Screens[i], format_conv_surf, + ScreenWidth, ScreenHeight)) + return -1; + } + + SDL_Screen = SDL_Screens[0]; + TransitionScreen = SDL_Screens[2]; + + if (first_init) + { + for (i = 0; i < TFB_GFX_NUMSCREENS; i++) + { + GL_Screens[i].scaled = NULL; + GL_Screens[i].dirty = TRUE; + GL_Screens[i].active = TRUE; + } + GL_Screens[1].active = FALSE; + first_init = FALSE; + } + } + + if (GfxFlags & TFB_GFXFLAGS_SCALE_SOFT_ONLY) + { + if (!togglefullscreen) + { + for (i = 0; i < TFB_GFX_NUMSCREENS; i++) + { + if (!GL_Screens[i].active) + continue; + if (0 != SDL1_ReInit_Screen (&GL_Screens[i].scaled, format_conv_surf, + ScreenWidth * 2, ScreenHeight * 2)) + return -1; + } + scaler = Scale_PrepPlatform (flags, SDL_Screen->format); + } + + texture_width = 1024; + texture_height = 512; + + graphics_backend = &opengl_scaled_backend; + } + else + { + texture_width = 512; + texture_height = 256; + + scaler = NULL; + graphics_backend = &opengl_unscaled_backend; + } + + + if (GfxFlags & TFB_GFXFLAGS_SCALE_ANY) + ScreenFilterMode = GL_LINEAR; + else + ScreenFilterMode = GL_NEAREST; + + glViewport (0, 0, ScreenWidthActual, ScreenHeightActual); + glClearColor (0,0,0,0); + glClear (GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); + SDL_GL_SwapBuffers (); + glClear (GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); + glDisable (GL_DITHER); + glDepthMask(GL_FALSE); + + for (i = 0; i < TFB_GFX_NUMSCREENS; i++) + { + if (!GL_Screens[i].active) + continue; + glGenTextures (1, &GL_Screens[i].texture); + glBindTexture (GL_TEXTURE_2D, GL_Screens[i].texture); + glPixelStorei (GL_UNPACK_ALIGNMENT, 1); + glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + glTexImage2D (GL_TEXTURE_2D, 0, GL_RGB, texture_width, texture_height, + 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + } + + return 0; +} + +int +TFB_GL_InitGraphics (int driver, int flags, int width, int height) +{ + char VideoName[256]; + + log_add (log_Info, "Initializing SDL with OpenGL support."); + + SDL_VideoDriverName (VideoName, sizeof (VideoName)); + log_add (log_Info, "SDL driver used: %s", VideoName); + log_add (log_Info, "SDL initialized."); + log_add (log_Info, "Initializing Screen."); + + ScreenWidth = 320; + ScreenHeight = 240; + + if (TFB_GL_ConfigureVideo (driver, flags, width, height, 0)) + { + log_add (log_Fatal, "Could not initialize video: " + "no fallback at start of program!"); + exit (EXIT_FAILURE); + } + + // Initialize scalers (let them precompute whatever) + Scale_Init (); + + return 0; +} + +void +TFB_GL_UninitGraphics (void) +{ + int i; + + for (i = 0; i < TFB_GFX_NUMSCREENS; i++) { + UnInit_Screen (&GL_Screens[i].scaled); + } +} + +static void +TFB_GL_UploadTransitionScreen (void) +{ + GL_Screens[TFB_SCREEN_TRANSITION].updated.x = 0; + GL_Screens[TFB_SCREEN_TRANSITION].updated.y = 0; + GL_Screens[TFB_SCREEN_TRANSITION].updated.w = ScreenWidth; + GL_Screens[TFB_SCREEN_TRANSITION].updated.h = ScreenHeight; + GL_Screens[TFB_SCREEN_TRANSITION].dirty = TRUE; +} + +static void +TFB_GL_ScanLines (void) +{ + int y; + + glDisable (GL_TEXTURE_2D); + glEnable (GL_BLEND); + glBlendFunc (GL_DST_COLOR, GL_ZERO); + glColor3f (0.85f, 0.85f, 0.85f); + for (y = 0; y < ScreenHeightActual; y += 2) + { + glBegin (GL_LINES); + glVertex2i (0, y); + glVertex2i (ScreenWidthActual, y); + glEnd (); + } + + glBlendFunc (GL_DST_COLOR, GL_ONE); + glColor3f (0.2f, 0.2f, 0.2f); + for (y = 1; y < ScreenHeightActual; y += 2) + { + glBegin (GL_LINES); + glVertex2i (0, y); + glVertex2i (ScreenWidthActual, y); + glEnd (); + } +} + +static void +TFB_GL_DrawQuad (SDL_Rect *r) +{ + BOOLEAN keep_aspect_ratio = optKeepAspectRatio; + int x1 = 0, y1 = 0, x2 = ScreenWidthActual, y2 = ScreenHeightActual; + int sx = 0, sy = 0; + int sw, sh; + float sx_multiplier = 1; + float sy_multiplier = 1; + + if (keep_aspect_ratio) + { + float threshold = 0.75f; + float ratio = ScreenHeightActual / (float)ScreenWidthActual; + + if (ratio > threshold) + { + // screen is narrower than 4:3 + int height = (int)(ScreenWidthActual * threshold); + y1 = (ScreenHeightActual - height) / 2; + y2 = ScreenHeightActual - y1; + + if (r != NULL) + { + sx_multiplier = ScreenWidthActual / (float)ScreenWidth; + sy_multiplier = height / (float)ScreenHeight; + sx = (int)(r->x * sx_multiplier); + sy = (int)(((ScreenHeight - (r->y + r->h)) * sy_multiplier) + y1); + } + } + else if (ratio < threshold) + { + // screen is wider than 4:3 + int width = (int)(ScreenHeightActual / threshold); + x1 = (ScreenWidthActual - width) / 2; + x2 = ScreenWidthActual - x1; + + if (r != NULL) + { + sx_multiplier = width / (float)ScreenWidth; + sy_multiplier = ScreenHeightActual / (float)ScreenHeight; + sx = (int)((r->x * sx_multiplier) + x1); + sy = (int)((ScreenHeight - (r->y + r->h)) * sy_multiplier); + } + } + else + { + // screen is 4:3 + keep_aspect_ratio = 0; + } + } + + if (r != NULL) + { + if (!keep_aspect_ratio) + { + sx_multiplier = ScreenWidthActual / (float)ScreenWidth; + sy_multiplier = ScreenHeightActual / (float)ScreenHeight; + sx = (int)(r->x * sx_multiplier); + sy = (int)((ScreenHeight - (r->y + r->h)) * sy_multiplier); + } + sw = (int)(r->w * sx_multiplier); + sh = (int)(r->h * sy_multiplier); + glScissor (sx, sy, sw, sh); + glEnable (GL_SCISSOR_TEST); + } + + glBegin (GL_TRIANGLE_FAN); + glTexCoord2f (0, 0); + glVertex2i (x1, y1); + glTexCoord2f (ScreenWidth / 512.0f, 0); + glVertex2i (x2, y1); + glTexCoord2f (ScreenWidth / 512.0f, ScreenHeight / 256.0f); + glVertex2i (x2, y2); + glTexCoord2f (0, ScreenHeight / 256.0f); + glVertex2i (x1, y2); + glEnd (); + if (r != NULL) + { + glDisable (GL_SCISSOR_TEST); + } +} + +static void +TFB_GL_Preprocess (int force_full_redraw, int transition_amount, int fade_amount) +{ + glMatrixMode (GL_PROJECTION); + glLoadIdentity (); + glOrtho (0,ScreenWidthActual,ScreenHeightActual, 0, -1, 1); + glMatrixMode (GL_MODELVIEW); + glLoadIdentity (); + if (optKeepAspectRatio) + glClear (GL_COLOR_BUFFER_BIT); + + (void) transition_amount; + (void) fade_amount; + + if (force_full_redraw == TFB_REDRAW_YES) + { + GL_Screens[TFB_SCREEN_MAIN].updated.x = 0; + GL_Screens[TFB_SCREEN_MAIN].updated.y = 0; + GL_Screens[TFB_SCREEN_MAIN].updated.w = ScreenWidth; + GL_Screens[TFB_SCREEN_MAIN].updated.h = ScreenHeight; + GL_Screens[TFB_SCREEN_MAIN].dirty = TRUE; + } + else if (TFB_BBox.valid) + { + GL_Screens[TFB_SCREEN_MAIN].updated.x = TFB_BBox.region.corner.x; + GL_Screens[TFB_SCREEN_MAIN].updated.y = TFB_BBox.region.corner.y; + GL_Screens[TFB_SCREEN_MAIN].updated.w = TFB_BBox.region.extent.width; + GL_Screens[TFB_SCREEN_MAIN].updated.h = TFB_BBox.region.extent.height; + GL_Screens[TFB_SCREEN_MAIN].dirty = TRUE; + } +} + +static void +TFB_GL_Unscaled_ScreenLayer (SCREEN screen, Uint8 a, SDL_Rect *rect) +{ + glBindTexture (GL_TEXTURE_2D, GL_Screens[screen].texture); + glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + + if (GL_Screens[screen].dirty) + { + int PitchWords = SDL_Screens[screen]->pitch / 4; + glPixelStorei (GL_UNPACK_ROW_LENGTH, PitchWords); + /* Matrox OpenGL drivers do not handle GL_UNPACK_SKIP_* + correctly */ + glPixelStorei (GL_UNPACK_SKIP_ROWS, 0); + glPixelStorei (GL_UNPACK_SKIP_PIXELS, 0); + SDL_LockSurface (SDL_Screens[screen]); + glTexSubImage2D (GL_TEXTURE_2D, 0, GL_Screens[screen].updated.x, + GL_Screens[screen].updated.y, + GL_Screens[screen].updated.w, + GL_Screens[screen].updated.h, + GL_RGBA, GL_UNSIGNED_BYTE, + (Uint32 *)SDL_Screens[screen]->pixels + + (GL_Screens[screen].updated.y * PitchWords + + GL_Screens[screen].updated.x)); + SDL_UnlockSurface (SDL_Screens[screen]); + GL_Screens[screen].dirty = FALSE; + } + + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, ScreenFilterMode); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, ScreenFilterMode); + glEnable (GL_TEXTURE_2D); + + if (a == 255) + { + glDisable (GL_BLEND); + glColor4f (1, 1, 1, 1); + } + else + { + float a_f = a / 255.0f; + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable (GL_BLEND); + glColor4f (1, 1, 1, a_f); + } + + TFB_GL_DrawQuad (rect); +} + +static void +TFB_GL_Scaled_ScreenLayer (SCREEN screen, Uint8 a, SDL_Rect *rect) +{ + glBindTexture (GL_TEXTURE_2D, GL_Screens[screen].texture); + glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + + if (GL_Screens[screen].dirty) + { + int PitchWords = GL_Screens[screen].scaled->pitch / 4; + scaler (SDL_Screens[screen], GL_Screens[screen].scaled, &GL_Screens[screen].updated); + glPixelStorei (GL_UNPACK_ROW_LENGTH, PitchWords); + + /* Matrox OpenGL drivers do not handle GL_UNPACK_SKIP_* + correctly */ + glPixelStorei (GL_UNPACK_SKIP_ROWS, 0); + glPixelStorei (GL_UNPACK_SKIP_PIXELS, 0); + SDL_LockSurface (GL_Screens[screen].scaled); + glTexSubImage2D (GL_TEXTURE_2D, 0, GL_Screens[screen].updated.x * 2, + GL_Screens[screen].updated.y * 2, + GL_Screens[screen].updated.w * 2, + GL_Screens[screen].updated.h * 2, + GL_RGBA, GL_UNSIGNED_BYTE, + (Uint32 *)GL_Screens[screen].scaled->pixels + + (GL_Screens[screen].updated.y * 2 * PitchWords + + GL_Screens[screen].updated.x * 2)); + SDL_UnlockSurface (GL_Screens[screen].scaled); + GL_Screens[screen].dirty = FALSE; + } + + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, ScreenFilterMode); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, ScreenFilterMode); + glEnable (GL_TEXTURE_2D); + + if (a == 255) + { + glDisable (GL_BLEND); + glColor4f (1, 1, 1, 1); + } + else + { + float a_f = a / 255.0f; + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable (GL_BLEND); + glColor4f (1, 1, 1, a_f); + } + + TFB_GL_DrawQuad (rect); +} + +static void +TFB_GL_ColorLayer (Uint8 r, Uint8 g, Uint8 b, Uint8 a, SDL_Rect *rect) +{ + float r_f = r / 255.0f; + float g_f = g / 255.0f; + float b_f = b / 255.0f; + float a_f = a / 255.0f; + glColor4f(r_f, g_f, b_f, a_f); + + glDisable (GL_TEXTURE_2D); + if (a != 255) + { + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable (GL_BLEND); + } + else + { + glDisable (GL_BLEND); + } + + TFB_GL_DrawQuad (rect); +} + +static void +TFB_GL_Postprocess (void) +{ + if (GfxFlags & TFB_GFXFLAGS_SCANLINES) + TFB_GL_ScanLines (); + + SDL_GL_SwapBuffers (); +} + +#endif +#endif diff --git a/src/libs/graphics/sdl/opengl.h b/src/libs/graphics/sdl/opengl.h new file mode 100644 index 0000000..6550bca --- /dev/null +++ b/src/libs/graphics/sdl/opengl.h @@ -0,0 +1,89 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef OPENGL_H +#define OPENGL_H + +#include "libs/graphics/sdl/sdl_common.h" +#if SDL_MAJOR_VERSION == 1 + +int TFB_GL_InitGraphics (int driver, int flags, int width, int height); +void TFB_GL_UninitGraphics (void); +int TFB_GL_ConfigureVideo (int driver, int flags, int width, int height, int togglefullscreen); + +#ifdef HAVE_OPENGL +#ifdef WIN32 + +#ifdef _MSC_VER +#pragma comment (lib, "opengl32.lib") +#pragma comment (lib, "glu32.lib") +#endif + +/* To avoid including windows.h, + Win32's <GL/gl.h> needs APIENTRY and WINGDIAPI defined properly. */ + +#ifndef APIENTRY +#define GLUT_APIENTRY_DEFINED +#if __MINGW32__ || (_MSC_VER >= 800) || defined(_STDCALL_SUPPORTED) +#define APIENTRY __stdcall +#else +#define APIENTRY +#endif +#endif + +#ifndef WINAPI +#define GLUT_WINAPI_DEFINED +#if __MINGW32__ || (_MSC_VER >= 800) || defined(_STDCALL_SUPPORTED) +#define WINAPI __stdcall +#else +#define WINAPI +#endif +#endif + +/* This is from Win32's <winnt.h> */ +#ifndef CALLBACK +#if (defined(_M_MRX000) || defined(_M_IX86) || defined(_M_ALPHA) || defined(_M_PPC)) && !defined(MIDL_PASS) +#define CALLBACK __stdcall +#else +#define CALLBACK +#endif +#endif + +/* This is from Win32's <wingdi.h> and <winnt.h> */ +#ifndef WINGDIAPI +#define GLUT_WINGDIAPI_DEFINED +#define WINGDIAPI __declspec(dllimport) +#endif + +/* This is from Win32's <ctype.h> */ +#ifndef LIBS_GRAPHICS_SDL_OPENGL_H_ +typedef unsigned short wchar_t; +#define LIBS_GRAPHICS_SDL_OPENGL_H_ +#endif + +#include "GL/glu.h" + +#else /* !defined(WIN32) */ + +#include "port.h" +#include SDL_INCLUDE(SDL_opengl.h) + +#endif /* WIN32 */ +#endif /* SDL_MAJOR_VERSION == 1 */ +#endif /* HAVE_OPENGL */ +#endif diff --git a/src/libs/graphics/sdl/palette.c b/src/libs/graphics/sdl/palette.c new file mode 100644 index 0000000..18f4418 --- /dev/null +++ b/src/libs/graphics/sdl/palette.c @@ -0,0 +1,47 @@ +/* + * Copyright 2009 Alex Volkov <codepro@usa.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "palette.h" +#include "libs/memlib.h" +#include "libs/log.h" + +NativePalette * +AllocNativePalette (void) +{ + return HCalloc (sizeof (NativePalette)); +} + +void +FreeNativePalette (NativePalette *palette) +{ + HFree (palette); +} + +void +SetNativePaletteColor (NativePalette *palette, int index, Color color) +{ + assert (index < NUMBER_OF_PLUTVALS); + palette->colors[index] = ColorToNative (color); +} + +Color +GetNativePaletteColor (NativePalette *palette, int index) +{ + assert (index < NUMBER_OF_PLUTVALS); + return NativeToColor (palette->colors[index]); +} diff --git a/src/libs/graphics/sdl/palette.h b/src/libs/graphics/sdl/palette.h new file mode 100644 index 0000000..2cefe7c --- /dev/null +++ b/src/libs/graphics/sdl/palette.h @@ -0,0 +1,57 @@ +/* + * Copyright 2009 Alex Volkov <codepro@usa.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef PALETTE_H_INCL__ +#define PALETTE_H_INCL__ + +#include "port.h" +#include SDL_INCLUDE(SDL.h) +#include "libs/graphics/cmap.h" + +struct NativePalette +{ + SDL_Color colors[NUMBER_OF_PLUTVALS]; +}; + +static inline Color +NativeToColor (SDL_Color native) +{ + Color color; + color.r = native.r; + color.g = native.g; + color.b = native.b; + color.a = 0xff; // fully opaque + return color; +} + +static inline SDL_Color +ColorToNative (Color color) +{ + SDL_Color native; + native.r = color.r; + native.g = color.g; + native.b = color.b; +#if SDL_MAJOR_VERSION == 1 + native.unused = 0; +#else + native.a = color.a; +#endif + return native; +} + +#endif /* PALETTE_H_INCL__ */ diff --git a/src/libs/graphics/sdl/png2sdl.c b/src/libs/graphics/sdl/png2sdl.c new file mode 100644 index 0000000..1717886 --- /dev/null +++ b/src/libs/graphics/sdl/png2sdl.c @@ -0,0 +1,300 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * This code is adapted from IMG_png.c in the SDL2_image library, which is + * available under the zlib license and is (c) 1997-2019 Sam Lantinga. The + * code itself is credited to Philippe Lavoie as the original author. It + * also shares some heritage with libpng's own example.c file, and + * ultimately inherits the GPL2+ license from the rest of UQM. + * + * Differences from SDL2_image are: + * - It is PNG-only + * - It directly links the relevant version of libpng at compile time + * - It always uses libpng and will never forward to alternative + * libraries such as ImageIO.framework. This means that palette + * information will always be preserved, as UQM requires. + * - It locks the surface as the API demands rather than using + * volatility markers + * - Palette assignment is done through the API rather than by + * directly editing the format contents + */ + +#include "png2sdl.h" +#include <png.h> + +/* Link function between SDL_RWops and PNG's data source */ +static void +png_read_data(png_structp ctx, png_bytep area, png_size_t size) +{ + SDL_RWops *src = (SDL_RWops *)png_get_io_ptr (ctx); + SDL_RWread (src, area, size, 1); +} + +SDL_Surface * +TFB_png_to_sdl (SDL_RWops *src) +{ + Sint64 start; + const char *error; + SDL_Surface *surface; + png_structp png_ptr; + png_infop info_ptr; + png_uint_32 width, height; + int bit_depth, color_type, interlace_type, num_channels; + Uint32 Rmask, Gmask, Bmask, Amask; + png_bytep *row_pointers; + int row, i; + int ckey = -1; + png_color_16 *transv; + + if (!src) + { + /* The error message has been set in SDL_RWFromFile */ + return NULL; + } + start = SDL_RWtell (src); + + /* Initialize the data we will clean up when we're done */ + error = NULL; + png_ptr = NULL; + info_ptr = NULL; + row_pointers = NULL; + surface = NULL; + + /* Create the PNG loading context structure */ + png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, + NULL, NULL, NULL); + if (png_ptr == NULL) + { + error = "Couldn't allocate memory for PNG file"; + goto done; + } + + /* Allocate/initialize the memory for image information */ + info_ptr = png_create_info_struct (png_ptr); + if (info_ptr == NULL) + { + error = "Couldn't create image information for PNG file"; + goto done; + } + + /* Set error handling */ + if (setjmp (png_jmpbuf (png_ptr))) + { + error = "Error reading the PNG file."; + goto done; + } + + /* Set up the input control */ + png_set_read_fn (png_ptr, src, png_read_data); + + /* Read PNG header info */ + png_read_info (png_ptr, info_ptr); + png_get_IHDR (png_ptr, info_ptr, &width, &height, &bit_depth, + &color_type, &interlace_type, NULL, NULL); + + + /* Configure the decode based on what we know of the image + * already: strip 16 bit color down to 8 bit, automatically + * deinterlace, expand grayscale images or those with more + * than one transparent color or any translucent colors into + * full RGB or RGBA, and expand 1, 2, or 4-bpp paletted + * images to 8bpp. */ + png_set_strip_16 (png_ptr); + png_set_interlace_handling (png_ptr); + png_set_packing (png_ptr); + if (color_type == PNG_COLOR_TYPE_GRAY) + { + png_set_expand (png_ptr); + } + if (png_get_valid (png_ptr, info_ptr, PNG_INFO_tRNS)) + { + int num_trans; + Uint8 *trans; + png_get_tRNS (png_ptr, info_ptr, &trans, &num_trans, &transv); + if (color_type == PNG_COLOR_TYPE_PALETTE) + { + /* Check if all tRNS entries are opaque except one */ + int j, t = -1; + for (j = 0; j < num_trans; j++) + { + if (trans[j] == 0) + { + if (t >= 0) + { + break; + } + t = j; + } + else if (trans[j] != 255) + { + break; + } + } + if (j == num_trans) + { + /* exactly one transparent index */ + ckey = t; + } + else + { + /* more than one transparent index, or translucency */ + png_set_expand (png_ptr); + } + } + else + { + ckey = 0; /* actual value will be set later */ + } + } + + if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + { + png_set_gray_to_rgb (png_ptr); + } + + /* Register our changes with the reading machinery and refresh + * our ancillary data about the image */ + png_read_update_info (png_ptr, info_ptr); + png_get_IHDR (png_ptr, info_ptr, &width, &height, &bit_depth, + &color_type, &interlace_type, NULL, NULL); + + /* Allocate the SDL surface to hold the image */ + Rmask = Gmask = Bmask = Amask = 0; + num_channels = png_get_channels (png_ptr, info_ptr); + if (num_channels >= 3) + { +#if SDL_BYTEORDER == SDL_LIL_ENDIAN + Rmask = 0x000000FF; + Gmask = 0x0000FF00; + Bmask = 0x00FF0000; + Amask = (num_channels == 4) ? 0xFF000000 : 0; +#else + int s = (num_channels == 4) ? 0 : 8; + Rmask = 0xFF000000 >> s; + Gmask = 0x00FF0000 >> s; + Bmask = 0x0000FF00 >> s; + Amask = 0x000000FF >> s; +#endif + } + surface = SDL_CreateRGBSurface (SDL_SWSURFACE, width, height, + bit_depth * num_channels, + Rmask, Gmask, Bmask, Amask); + if (surface == NULL) + { + error = SDL_GetError (); + goto done; + } + + if (ckey != -1) + { + if (color_type != PNG_COLOR_TYPE_PALETTE) + { + /* FIXME: Should these be truncated or shifted down? */ + ckey = SDL_MapRGB(surface->format, + (Uint8)transv->red, + (Uint8)transv->green, + (Uint8)transv->blue); + } +#if SDL_MAJOR_VERSION >= 2 + SDL_SetColorKey (surface, SDL_TRUE, ckey); +#else + SDL_SetColorKey (surface, SDL_SRCCOLORKEY, ckey); +#endif + } + + SDL_LockSurface (surface); + /* Create the array of pointers to image data */ + row_pointers = (png_bytep *)SDL_malloc (sizeof (png_bytep) * height); + if (!row_pointers) + { + error = "Out of memory"; + goto done; + } + for (row = 0; row < (int)height; row++) + { + row_pointers[row] = (png_bytep) + (Uint8 *)surface->pixels + row*surface->pitch; + } + + /* Read the entire image in one go */ + png_read_image (png_ptr, row_pointers); + SDL_UnlockSurface (surface); + + /* and we're done! (png_read_end() can be omitted if no + * processing of post-IDAT text/time/etc. is desired) + * In some cases it can't read PNGs created by some popular + * programs (ACDSEE), we do not want to process comments, so + * we omit png_read_end */ + + /* Load the palette, if any */ + if (surface->format->palette) + { + SDL_Color palette[256]; + int png_num_palette; + png_colorp png_palette; + png_get_PLTE (png_ptr, info_ptr, &png_palette, &png_num_palette); + if (color_type == PNG_COLOR_TYPE_GRAY) + { + png_num_palette = 256; + for (i = 0; i < 256; i++) + { + palette[i].r = (Uint8)i; + palette[i].g = (Uint8)i; + palette[i].b = (Uint8)i; + } + } + else if (png_num_palette > 0) + { + for (i = 0; i < png_num_palette; ++i) + { + palette[i].b = png_palette[i].blue; + palette[i].g = png_palette[i].green; + palette[i].r = png_palette[i].red; + } + } +#if SDL_MAJOR_VERSION >= 2 + SDL_SetPaletteColors (surface->format->palette, palette, + 0, png_num_palette); +#else + SDL_SetPalette (surface, SDL_LOGPAL, palette, + 0, png_num_palette); +#endif + } + +done: /* Clean up and return */ + if (png_ptr) + { + png_destroy_read_struct (&png_ptr, + info_ptr ? &info_ptr : (png_infopp)0, + (png_infopp)0); + } + if (row_pointers) + { + SDL_free (row_pointers); + } + if (error) + { + SDL_RWseek(src, start, RW_SEEK_SET); + if (surface) + { + SDL_FreeSurface (surface); + surface = NULL; + } + fprintf (stderr, "%s", error); + } + return surface; +} diff --git a/src/libs/graphics/sdl/png2sdl.h b/src/libs/graphics/sdl/png2sdl.h new file mode 100644 index 0000000..c0a9ec3 --- /dev/null +++ b/src/libs/graphics/sdl/png2sdl.h @@ -0,0 +1,24 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef PNG2SDL_H_ +#define PNG2SDL_H_ + +#include <SDL.h> + +SDL_Surface *TFB_png_to_sdl (SDL_RWops *src); + +#endif diff --git a/src/libs/graphics/sdl/primitives.c b/src/libs/graphics/sdl/primitives.c new file mode 100644 index 0000000..6dd539a --- /dev/null +++ b/src/libs/graphics/sdl/primitives.c @@ -0,0 +1,633 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "port.h" +#include "sdl_common.h" +#include "primitives.h" + + +// Pixel drawing routines + +static Uint32 +getpixel_8(SDL_Surface *surface, int x, int y) +{ + /* Here p is the address to the pixel we want to retrieve */ + Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x; + return *p; +} + +static void +putpixel_8(SDL_Surface *surface, int x, int y, Uint32 pixel) +{ + /* Here p is the address to the pixel we want to set */ + Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * 1; + *p = pixel; +} + +static Uint32 +getpixel_16(SDL_Surface *surface, int x, int y) +{ + /* Here p is the address to the pixel we want to retrieve */ + Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * 2; + return *(Uint16 *)p; +} + +static void +putpixel_16(SDL_Surface *surface, int x, int y, Uint32 pixel) +{ + /* Here p is the address to the pixel we want to set */ + Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * 2; + *(Uint16 *)p = pixel; +} + +static Uint32 +getpixel_24_be(SDL_Surface *surface, int x, int y) +{ + /* Here p is the address to the pixel we want to retrieve */ + Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * 3; + return p[0] << 16 | p[1] << 8 | p[2]; +} + +static void +putpixel_24_be(SDL_Surface *surface, int x, int y, Uint32 pixel) +{ + /* Here p is the address to the pixel we want to set */ + Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * 3; + p[0] = (pixel >> 16) & 0xff; + p[1] = (pixel >> 8) & 0xff; + p[2] = pixel & 0xff; +} + +static Uint32 +getpixel_24_le(SDL_Surface *surface, int x, int y) +{ + /* Here p is the address to the pixel we want to retrieve */ + Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * 3; + return p[0] | p[1] << 8 | p[2] << 16; +} + +static void +putpixel_24_le(SDL_Surface *surface, int x, int y, Uint32 pixel) +{ + /* Here p is the address to the pixel we want to set */ + Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * 3; + p[0] = pixel & 0xff; + p[1] = (pixel >> 8) & 0xff; + p[2] = (pixel >> 16) & 0xff; +} + +static Uint32 +getpixel_32(SDL_Surface *surface, int x, int y) +{ + /* Here p is the address to the pixel we want to retrieve */ + Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * 4; + return *(Uint32 *)p; +} + +static void +putpixel_32(SDL_Surface *surface, int x, int y, Uint32 pixel) +{ + /* Here p is the address to the pixel we want to set */ + Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * 4; + *(Uint32 *)p = pixel; +} + +GetPixelFn +getpixel_for(SDL_Surface *surface) +{ + int bpp = surface->format->BytesPerPixel; + switch (bpp) { + case 1: + return &getpixel_8; + case 2: + return &getpixel_16; + case 3: + if (SDL_BYTEORDER == SDL_BIG_ENDIAN) { + return &getpixel_24_be; + } else { + return &getpixel_24_le; + } + case 4: + return &getpixel_32; + } + return NULL; +} + +PutPixelFn +putpixel_for(SDL_Surface *surface) +{ + int bpp = surface->format->BytesPerPixel; + switch (bpp) { + case 1: + return &putpixel_8; + case 2: + return &putpixel_16; + case 3: + if (SDL_BYTEORDER == SDL_BIG_ENDIAN) { + return &putpixel_24_be; + } else { + return &putpixel_24_le; + } + case 4: + return &putpixel_32; + } + return NULL; +} + +static void +renderpixel_replace(SDL_Surface *surface, int x, int y, Uint32 pixel, + int factor) +{ + (void) factor; // ignored + putpixel_32(surface, x, y, pixel); +} + +static inline Uint8 +clip_channel(int c) +{ + if (c < 0) + c = 0; + else if (c > 255) + c = 255; + return c; +} + +static inline Uint8 +modulated_sum(Uint8 dc, Uint8 sc, int factor) +{ + // We use >> 8 instead of / 255 because it is faster, but it does + // not work 100% correctly. It should be safe because this should + // not be called for factor==255 + int b = dc + ((sc * factor) >> 8); + return clip_channel(b); +} + +static inline Uint8 +alpha_blend(Uint8 dc, Uint8 sc, int alpha) +{ + // We use >> 8 instead of / 255 because it is faster, but it does + // not work 100% correctly. It should be safe because this should + // not be called for alpha==255 + // No need to clip since we should never get values outside of 0..255 + // range, unless alpha is over 255, which is not supported. + return (((sc - dc) * alpha) >> 8) + dc; +} + +// Assumes 8 bits/channel, a safe assumption for 32bpp surfaces +#define UNPACK_PIXEL_32(p, fmt, r, g, b) \ + do { \ + (r) = ((p) >> (fmt)->Rshift) & 0xff; \ + (g) = ((p) >> (fmt)->Gshift) & 0xff; \ + (b) = ((p) >> (fmt)->Bshift) & 0xff; \ + } while (0) + +// Assumes the channels already clipped to 8 bits +static inline Uint32 +PACK_PIXEL_32(const SDL_PixelFormat *fmt, + Uint8 r, Uint8 g, Uint8 b) +{ + return ((Uint32)r << fmt->Rshift) | ((Uint32)g << fmt->Gshift) + | ((Uint32)b << fmt->Bshift); +} + +static void +renderpixel_additive(SDL_Surface *surface, int x, int y, Uint32 pixel, + int factor) +{ + const SDL_PixelFormat *fmt = surface->format; + Uint32 *p; + Uint32 sp; + Uint8 sr, sg, sb; + int r, g, b; + + p = (Uint32 *) ((Uint8 *)surface->pixels + y * surface->pitch + x * 4); + sp = *p; + UNPACK_PIXEL_32(sp, fmt, sr, sg, sb); + UNPACK_PIXEL_32(pixel, fmt, r, g, b); + + // TODO: We may need a special case for factor == -ADDITIVE_FACTOR_1 too, + // but it is not important enough right now to care ;) + if (factor == ADDITIVE_FACTOR_1) + { // no need to modulate the 'pixel', and modulation does not + // work correctly with factor==255 anyway + sr = clip_channel(sr + r); + sg = clip_channel(sg + g); + sb = clip_channel(sb + b); + } + else + { + sr = modulated_sum(sr, r, factor); + sg = modulated_sum(sg, g, factor); + sb = modulated_sum(sb, b, factor); + } + + *p = PACK_PIXEL_32(fmt, sr, sg, sb); +} + +static void +renderpixel_alpha(SDL_Surface *surface, int x, int y, Uint32 pixel, + int factor) +{ + const SDL_PixelFormat *fmt = surface->format; + Uint32 *p; + Uint32 sp; + Uint8 sr, sg, sb; + int r, g, b; + + if (factor == FULLY_OPAQUE_ALPHA) + { // alpha == 255 is equivalent to 'replace' and blending does not + // work correctly anyway because we use >> 8 instead of / 255 + putpixel_32(surface, x, y, pixel); + return; + } + + p = (Uint32 *) ((Uint8 *)surface->pixels + y * surface->pitch + x * 4); + sp = *p; + UNPACK_PIXEL_32(sp, fmt, sr, sg, sb); + UNPACK_PIXEL_32(pixel, fmt, r, g, b); + sr = alpha_blend(sr, r, factor); + sg = alpha_blend(sg, g, factor); + sb = alpha_blend(sb, b, factor); + *p = PACK_PIXEL_32(fmt, sr, sg, sb); +} + +RenderPixelFn +renderpixel_for(SDL_Surface *surface, RenderKind kind) +{ + const SDL_PixelFormat *fmt = surface->format; + + // The only supported rendering is to 32bpp surfaces + if (fmt->BytesPerPixel != 4) + return NULL; + + // Rendering other than REPLACE is not supported on RGBA surfaces + if (fmt->Amask != 0 && kind != renderReplace) + return NULL; + + switch (kind) + { + case renderReplace: + return &renderpixel_replace; + case renderAdditive: + return &renderpixel_additive; + case renderAlpha: + return &renderpixel_alpha; + } + // should not ever get here + return NULL; +} + +/* Line drawing routine + * Adapted from Paul Heckbert's implementation of Bresenham's algorithm, + * 3 Sep 85; taken from Graphics Gems I */ + +void +line_prim(int x1, int y1, int x2, int y2, Uint32 color, RenderPixelFn plot, + int factor, SDL_Surface *dst) +{ + int d, x, y, ax, ay, sx, sy, dx, dy; + SDL_Rect clip_r; + + SDL_GetClipRect (dst, &clip_r); + if (!clip_line (&x1, &y1, &x2, &y2, &clip_r)) + return; // line is completely outside clipping rectangle + + dx = x2-x1; + ax = ((dx < 0) ? -dx : dx) << 1; + sx = (dx < 0) ? -1 : 1; + dy = y2-y1; + ay = ((dy < 0) ? -dy : dy) << 1; + sy = (dy < 0) ? -1 : 1; + + x = x1; + y = y1; + if (ax > ay) { + d = ay - (ax >> 1); + for (;;) { + (*plot)(dst, x, y, color, factor); + if (x == x2) + return; + if (d >= 0) { + y += sy; + d -= ax; + } + x += sx; + d += ay; + } + } else { + d = ax - (ay >> 1); + for (;;) { + (*plot)(dst, x, y, color, factor); + if (y == y2) + return; + if (d >= 0) { + x += sx; + d -= ay; + } + y += sy; + d += ax; + } + } +} + + +// Clips line against rectangle using Cohen-Sutherland algorithm + +enum {C_TOP = 0x1, C_BOTTOM = 0x2, C_RIGHT = 0x4, C_LEFT = 0x8}; + +static int +compute_code (float x, float y, float xmin, float ymin, float xmax, float ymax) +{ + int c = 0; + if (y > ymax) + c |= C_TOP; + else if (y < ymin) + c |= C_BOTTOM; + if (x > xmax) + c |= C_RIGHT; + else if (x < xmin) + c |= C_LEFT; + return c; +} + +int +clip_line (int *lx1, int *ly1, int *lx2, int *ly2, const SDL_Rect *r) +{ + int C0, C1, C; + float x, y, x0, y0, x1, y1, xmin, ymin, xmax, ymax; + + x0 = (float)*lx1; + y0 = (float)*ly1; + x1 = (float)*lx2; + y1 = (float)*ly2; + + xmin = (float)r->x; + ymin = (float)r->y; + xmax = (float)r->x + r->w - 1; + ymax = (float)r->y + r->h - 1; + + C0 = compute_code (x0, y0, xmin, ymin, xmax, ymax); + C1 = compute_code (x1, y1, xmin, ymin, xmax, ymax); + + for (;;) { + /* trivial accept: both ends in rectangle */ + if ((C0 | C1) == 0) + { + *lx1 = (int)x0; + *ly1 = (int)y0; + *lx2 = (int)x1; + *ly2 = (int)y1; + return 1; + } + + /* trivial reject: both ends on the external side of the rectangle */ + if ((C0 & C1) != 0) + return 0; + + /* normal case: clip end outside rectangle */ + C = C0 ? C0 : C1; + if (C & C_TOP) + { + x = x0 + (x1 - x0) * (ymax - y0) / (y1 - y0); + y = ymax; + } + else if (C & C_BOTTOM) + { + x = x0 + (x1 - x0) * (ymin - y0) / (y1 - y0); + y = ymin; + } + else if (C & C_RIGHT) + { + x = xmax; + y = y0 + (y1 - y0) * (xmax - x0) / (x1 - x0); + } + else + { + x = xmin; + y = y0 + (y1 - y0) * (xmin - x0) / (x1 - x0); + } + + /* set new end point and iterate */ + if (C == C0) + { + x0 = x; y0 = y; + C0 = compute_code (x0, y0, xmin, ymin, xmax, ymax); + } + else + { + x1 = x; y1 = y; + C1 = compute_code (x1, y1, xmin, ymin, xmax, ymax); + } + } +} + +void +fillrect_prim(SDL_Rect r, Uint32 color, RenderPixelFn plot, int factor, + SDL_Surface *dst) +{ + int x, y; + int x1, y1; + SDL_Rect clip_r; + + SDL_GetClipRect (dst, &clip_r); + if (!clip_rect (&r, &clip_r)) + return; // rect is completely outside clipping rectangle + + // TODO: calculate destination pointer directly instead of + // using the plot(x,y) version + x1 = r.x + r.w; + y1 = r.y + r.h; + for (y = r.y; y < y1; ++y) + { + for (x = r.x; x < x1; ++x) + plot(dst, x, y, color, factor); + } +} + +// clip the rectangle against the clip rectangle +int +clip_rect(SDL_Rect *r, const SDL_Rect *clip_r) +{ + // NOTE: the following clipping code is copied in part + // from SDL-1.2.4 sources + int dx, dy; + int w = r->w; + int h = r->h; + // SDL_Rect.w and .h are unsigned, we need signed + + dx = clip_r->x - r->x; + if (dx > 0) + { + w -= dx; + r->x += dx; + } + dx = r->x + w - clip_r->x - clip_r->w; + if (dx > 0) + w -= dx; + + dy = clip_r->y - r->y; + if (dy > 0) + { + h -= dy; + r->y += dy; + } + dy = r->y + h - clip_r->y - clip_r->h; + if (dy > 0) + h -= dy; + + if (w <= 0 || h <= 0) + { + r->w = 0; + r->h = 0; + return 0; + } + + r->w = w; + r->h = h; + return 1; +} + +void +blt_prim(SDL_Surface *src, SDL_Rect src_r, RenderPixelFn plot, int factor, + SDL_Surface *dst, SDL_Rect dst_r) +{ + SDL_PixelFormat *srcfmt = src->format; + SDL_Palette *srcpal = srcfmt->palette; + SDL_PixelFormat *dstfmt = dst->format; + Uint32 mask = 0; + Uint32 key = ~0; + GetPixelFn getpix = getpixel_for(src); + SDL_Rect clip_r; + int x, y; + + SDL_GetClipRect (dst, &clip_r); + if (!clip_blt_rects (&src_r, &dst_r, &clip_r)) + return; // rect is completely outside clipping rectangle + + if (src_r.x >= src->w || src_r.y >= src->h) + return; // rect is completely outside source bounds + + if (src_r.x + src_r.w > src->w) + src_r.w = src->w - src_r.x; + if (src_r.y + src_r.h > src->h) + src_r.h = src->h - src_r.y; + + // use colorkeys where appropriate + if (srcfmt->Amask) + { // alpha transparency + mask = srcfmt->Amask; + key = 0; + } + else if (TFB_GetColorKey (src, &key) == 0) + { + mask = ~0; + } + // TODO: calculate the source and destination pointers directly + // instead of using the plot(x,y) version + for (y = 0; y < src_r.h; ++y) + { + for (x = 0; x < src_r.w; ++x) + { + Uint8 r, g, b, a; + Uint32 p; + + p = getpix(src, src_r.x + x, src_r.y + y); + if (srcpal) + { // source is paletted, colorkey does not use mask + if (p == key) + continue; // transparent pixel + } + else + { // source is RGB(A), colorkey uses mask + if ((p & mask) == key) + continue; // transparent pixel + } + + // convert pixel format to destination + SDL_GetRGBA(p, srcfmt, &r, &g, &b, &a); + // TODO: handle source pixel alpha; plot() should probably + // get a source alpha parameter + p = SDL_MapRGBA(dstfmt, r, g, b, a); + + plot(dst, dst_r.x + x, dst_r.y + y, p, factor); + } + } +} + +// clip the source and destination rectangles against the clip rectangle +int +clip_blt_rects(SDL_Rect *src_r, SDL_Rect *dst_r, const SDL_Rect *clip_r) +{ + // NOTE: the following clipping code is copied in part + // from SDL-1.2.4 sources + int w, h; + int dx, dy; + + // clip the source rectangle to the source surface + w = src_r->w; + if (src_r->x < 0) + { + w += src_r->x; + dst_r->x -= src_r->x; + src_r->x = 0; + } + + h = src_r->h; + if (src_r->y < 0) + { + h += src_r->y; + dst_r->y -= src_r->y; + src_r->y = 0; + } + + // clip the destination rectangle against the clip rectangle, + // minding the source rectangle in the process + dx = clip_r->x - dst_r->x; + if (dx > 0) + { + w -= dx; + dst_r->x += dx; + src_r->x += dx; + } + dx = dst_r->x + w - clip_r->x - clip_r->w; + if (dx > 0) + w -= dx; + + dy = clip_r->y - dst_r->y; + if (dy > 0) + { + h -= dy; + dst_r->y += dy; + src_r->y += dy; + } + dy = dst_r->y + h - clip_r->y - clip_r->h; + if (dy > 0) + h -= dy; + + if (w <= 0 || h <= 0) + { + src_r->w = 0; + src_r->h = 0; + return 0; + } + + src_r->w = w; + src_r->h = h; + return 1; +} + diff --git a/src/libs/graphics/sdl/primitives.h b/src/libs/graphics/sdl/primitives.h new file mode 100644 index 0000000..9d8aff2 --- /dev/null +++ b/src/libs/graphics/sdl/primitives.h @@ -0,0 +1,62 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef PRIMITIVES_H +#define PRIMITIVES_H + +/* Function types for the pixel functions */ + +typedef Uint32 (*GetPixelFn)(SDL_Surface *, int x, int y); +// 'pixel' is in destination surface format +typedef void (*PutPixelFn)(SDL_Surface *, int x, int y, Uint32 pixel); + +GetPixelFn getpixel_for(SDL_Surface *surface); +PutPixelFn putpixel_for(SDL_Surface *surface); + +// This currently matches gfxlib.h:DrawKind for simplicity +typedef enum +{ + renderReplace = 0, + renderAdditive, + renderAlpha, +} RenderKind; + +#define FULLY_OPAQUE_ALPHA 255 +#define ADDITIVE_FACTOR_1 255 + +// 'pixel' is in destination surface format +// See gfxlib.h:DrawKind for 'factor' spec +typedef void (*RenderPixelFn)(SDL_Surface *, int x, int y, Uint32 pixel, + int factor); + +RenderPixelFn renderpixel_for(SDL_Surface *surface, RenderKind); + +void line_prim(int x1, int y1, int x2, int y2, Uint32 color, + RenderPixelFn plot, int factor, SDL_Surface *dst); +void fillrect_prim(SDL_Rect r, Uint32 color, + RenderPixelFn plot, int factor, SDL_Surface *dst); +void blt_prim(SDL_Surface *src, SDL_Rect src_r, + RenderPixelFn plot, int factor, + SDL_Surface *dst, SDL_Rect dst_r); + +int clip_line(int *lx1, int *ly1, int *lx2, int *ly2, const SDL_Rect *clip_r); +int clip_rect(SDL_Rect *r, const SDL_Rect *clip_r); +int clip_blt_rects(SDL_Rect *src_r, SDL_Rect *dst_r, const SDL_Rect *clip_r); + + +#endif /* PRIMITIVES_H */ diff --git a/src/libs/graphics/sdl/pure.c b/src/libs/graphics/sdl/pure.c new file mode 100644 index 0000000..df4a329 --- /dev/null +++ b/src/libs/graphics/sdl/pure.c @@ -0,0 +1,474 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "pure.h" +#include "libs/graphics/bbox.h" +#include "scalers.h" +#include "libs/log.h" + +#if SDL_MAJOR_VERSION == 1 + +static SDL_Surface *SDL_Video = NULL; +static SDL_Surface *fade_color_surface = NULL; +static SDL_Surface *fade_temp = NULL; +static SDL_Surface *scaled_display = NULL; + +static TFB_ScaleFunc scaler = NULL; + +static Uint32 fade_color; + +static void TFB_Pure_Scaled_Preprocess (int force_full_redraw, int transition_amount, int fade_amount); +static void TFB_Pure_Scaled_Postprocess (void); +static void TFB_Pure_Unscaled_Preprocess (int force_full_redraw, int transition_amount, int fade_amount); +static void TFB_Pure_Unscaled_Postprocess (void); +static void TFB_Pure_UploadTransitionScreen (void); +static void TFB_Pure_ScreenLayer (SCREEN screen, Uint8 a, SDL_Rect *rect); +static void TFB_Pure_ColorLayer (Uint8 r, Uint8 g, Uint8 b, Uint8 a, SDL_Rect *rect); + +static TFB_GRAPHICS_BACKEND pure_scaled_backend = { + TFB_Pure_Scaled_Preprocess, + TFB_Pure_Scaled_Postprocess, + TFB_Pure_UploadTransitionScreen, + TFB_Pure_ScreenLayer, + TFB_Pure_ColorLayer }; + +static TFB_GRAPHICS_BACKEND pure_unscaled_backend = { + TFB_Pure_Unscaled_Preprocess, + TFB_Pure_Unscaled_Postprocess, + TFB_Pure_UploadTransitionScreen, + TFB_Pure_ScreenLayer, + TFB_Pure_ColorLayer }; + +// We cannot rely on SDL_DisplayFormatAlpha() anymore. It can return +// formats that we do not expect (SDL v1.2.14 on Mac OSX). Mac likes +// ARGB surfaces, but SDL_DisplayFormatAlpha thinks that only RGBA are fast. +// This is a generic replacement that gives what we want. +static void +CalcAlphaFormat (const SDL_PixelFormat* video, SDL_PixelFormat* ours) +{ + int valid = 0; + + // We use 32-bit surfaces internally + ours->BitsPerPixel = 32; + + // Try to get as close to the video format as possible + if (video->BitsPerPixel == 15 || video->BitsPerPixel == 16) + { // At least match the channel order + ours->Rshift = video->Rshift / 5 * 8; + ours->Gshift = video->Gshift / 5 * 8; + ours->Bshift = video->Bshift / 5 * 8; + valid = 1; + } + else if (video->BitsPerPixel == 24 || video->BitsPerPixel == 32) + { + // We can only use channels aligned on byte boundary + if (video->Rshift % 8 == 0 && video->Gshift % 8 == 0 + && video->Bshift % 8 == 0) + { // Match RGB in video + ours->Rshift = video->Rshift; + ours->Gshift = video->Gshift; + ours->Bshift = video->Bshift; + valid = 1; + } + } + + if (valid) + { // For alpha, use the unoccupied byte + ours->Ashift = 48 - (ours->Rshift + ours->Gshift + ours->Bshift); + // Set channels according to byte positions + ours->Rmask = 0xff << ours->Rshift; + ours->Gmask = 0xff << ours->Gshift; + ours->Bmask = 0xff << ours->Bshift; + ours->Amask = 0xff << ours->Ashift; + return; + } + + // Fallback case. It does not matter what we set, but SDL likes + // Alpha to be the highest. + ours->Rmask = 0x000000ff; + ours->Gmask = 0x0000ff00; + ours->Bmask = 0x00ff0000; + ours->Amask = 0xff000000; +} + +int +TFB_Pure_ConfigureVideo (int driver, int flags, int width, int height, int togglefullscreen) +{ + int i, videomode_flags; + SDL_PixelFormat conv_fmt; + + GraphicsDriver = driver; + + // must use SDL_SWSURFACE, HWSURFACE doesn't work properly + // with fades/scaling + if (width == 320 && height == 240) + { + videomode_flags = SDL_SWSURFACE; + ScreenWidthActual = 320; + ScreenHeightActual = 240; + graphics_backend = &pure_unscaled_backend; + } + else + { + videomode_flags = SDL_SWSURFACE; + ScreenWidthActual = 640; + ScreenHeightActual = 480; + graphics_backend = &pure_scaled_backend; + + if (width != 640 || height != 480) + log_add (log_Error, "Screen resolution of %dx%d not supported " + "under pure SDL, using 640x480", width, height); + } + + videomode_flags |= SDL_ANYFORMAT; + if (flags & TFB_GFXFLAGS_FULLSCREEN) + videomode_flags |= SDL_FULLSCREEN; + + /* We'll ask for a 32bpp frame, but it doesn't really matter, because we've set + SDL_ANYFORMAT */ + SDL_Video = SDL_SetVideoMode (ScreenWidthActual, ScreenHeightActual, + 32, videomode_flags); + + if (SDL_Video == NULL) + { + log_add (log_Error, "Couldn't set %ix%i video mode: %s", + ScreenWidthActual, ScreenHeightActual, + SDL_GetError ()); + return -1; + } + else + { + const SDL_Surface *video = SDL_GetVideoSurface (); + const SDL_PixelFormat* fmt = video->format; + + ScreenColorDepth = fmt->BitsPerPixel; + log_add (log_Info, "Set the resolution to: %ix%ix%i", + video->w, video->h, ScreenColorDepth); + log_add (log_Info, " Video: R %08x, G %08x, B %08x, A %08x", + fmt->Rmask, fmt->Gmask, fmt->Bmask, fmt->Amask); + + if (togglefullscreen) + { + // NOTE: We cannot change the format_conv_surf now because we + // have already loaded lots of graphics and changing it now + // will only lead to chaos. + // Just check if channel order has changed significantly + CalcAlphaFormat (fmt, &conv_fmt); + fmt = format_conv_surf->format; + if (conv_fmt.Rmask != fmt->Rmask || conv_fmt.Bmask != fmt->Bmask) + log_add (log_Warning, "Warning: pixel format has changed " + "significantly. Rendering will be slow."); + return 0; + } + } + + // Create a 32bpp surface in a compatible format which will supply + // the format information to all other surfaces used in the game + if (format_conv_surf) + { + SDL_FreeSurface (format_conv_surf); + format_conv_surf = NULL; + } + CalcAlphaFormat (SDL_Video->format, &conv_fmt); + format_conv_surf = SDL_CreateRGBSurface (SDL_SWSURFACE, 0, 0, + conv_fmt.BitsPerPixel, conv_fmt.Rmask, conv_fmt.Gmask, + conv_fmt.Bmask, conv_fmt.Amask); + if (!format_conv_surf) + { + log_add (log_Error, "Couldn't create format_conv_surf: %s", + SDL_GetError()); + return -1; + } + else + { + const SDL_PixelFormat* fmt = format_conv_surf->format; + log_add (log_Info, " Internal: R %08x, G %08x, B %08x, A %08x", + fmt->Rmask, fmt->Gmask, fmt->Bmask, fmt->Amask); + } + + for (i = 0; i < TFB_GFX_NUMSCREENS; i++) + { + if (0 != SDL1_ReInit_Screen (&SDL_Screens[i], format_conv_surf, + ScreenWidth, ScreenHeight)) + return -1; + } + + SDL_Screen = SDL_Screens[0]; + TransitionScreen = SDL_Screens[2]; + + if (0 != SDL1_ReInit_Screen (&fade_color_surface, format_conv_surf, + ScreenWidth, ScreenHeight)) + return -1; + fade_color = SDL_MapRGB (fade_color_surface->format, 0, 0, 0); + SDL_FillRect (fade_color_surface, NULL, fade_color); + + if (0 != SDL1_ReInit_Screen (&fade_temp, format_conv_surf, + ScreenWidth, ScreenHeight)) + return -1; + + if (ScreenWidthActual > ScreenWidth || ScreenHeightActual > ScreenHeight) + { + if (0 != SDL1_ReInit_Screen (&scaled_display, format_conv_surf, + ScreenWidthActual, ScreenHeightActual)) + return -1; + + scaler = Scale_PrepPlatform (flags, SDL_Screen->format); + } + else + { // no need to scale + scaler = NULL; + } + + return 0; +} + +int +TFB_Pure_InitGraphics (int driver, int flags, const char *renderer, int width, int height) +{ + char VideoName[256]; + + log_add (log_Info, "Initializing Pure-SDL graphics."); + + SDL_VideoDriverName (VideoName, sizeof (VideoName)); + log_add (log_Info, "SDL driver used: %s", VideoName); + (void) renderer; + // The "renderer" argument is ignored by SDL1. To control how SDL1 + // gets its pixmap, set the environment variable SDL_VIDEODRIVER. + // For Linux: x11 (default), dga, fbcon, directfb, svgalib, + // ggi, aalib + // For Windows: directx (default), windib + + log_add (log_Info, "SDL initialized."); + log_add (log_Info, "Initializing Screen."); + + ScreenWidth = 320; + ScreenHeight = 240; + + if (TFB_Pure_ConfigureVideo (driver, flags, width, height, 0)) + { + log_add (log_Fatal, "Could not initialize video: " + "no fallback at start of program!"); + exit (EXIT_FAILURE); + } + + // Initialize scalers (let them precompute whatever) + Scale_Init (); + + return 0; +} + +void +TFB_Pure_UninitGraphics (void) +{ + UnInit_Screen (&scaled_display); + UnInit_Screen (&fade_color_surface); + UnInit_Screen (&fade_temp); +} + +static void +ScanLines (SDL_Surface *dst, SDL_Rect *r) +{ + const int rw = r->w * 2; + const int rh = r->h * 2; + SDL_PixelFormat *fmt = dst->format; + const int pitch = dst->pitch; + const int len = pitch / fmt->BytesPerPixel; + int ddst; + Uint32 *p = (Uint32 *) dst->pixels; + int x, y; + + p += len * (r->y * 2) + (r->x * 2); + ddst = len + len - rw; + + for (y = rh; y; y -= 2, p += ddst) + { + for (x = rw; x; --x, ++p) + { + // we ignore the lower bits as the difference + // of 1 in 255 is negligible + *p = ((*p >> 1) & 0x7f7f7f7f) + ((*p >> 2) & 0x3f3f3f3f); + } + } +} + +static SDL_Surface *backbuffer = NULL, *scalebuffer = NULL; +static SDL_Rect updated; + +static void +TFB_Pure_Scaled_Preprocess (int force_full_redraw, int transition_amount, int fade_amount) +{ + if (force_full_redraw != TFB_REDRAW_NO) + { + updated.x = updated.y = 0; + updated.w = ScreenWidth; + updated.h = ScreenHeight; + } + else + { + updated.x = TFB_BBox.region.corner.x; + updated.y = TFB_BBox.region.corner.y; + updated.w = TFB_BBox.region.extent.width; + updated.h = TFB_BBox.region.extent.height; + } + + if (transition_amount == 255 && fade_amount == 255) + backbuffer = SDL_Screens[TFB_SCREEN_MAIN]; + else + backbuffer = fade_temp; + + // we can scale directly onto SDL_Video if video is compatible + if (SDL_Video->format->BitsPerPixel == SDL_Screen->format->BitsPerPixel + && SDL_Video->format->Rmask == SDL_Screen->format->Rmask + && SDL_Video->format->Bmask == SDL_Screen->format->Bmask) + scalebuffer = SDL_Video; + else + scalebuffer = scaled_display; + +} + +static void +TFB_Pure_Unscaled_Preprocess (int force_full_redraw, int transition_amount, int fade_amount) +{ + if (force_full_redraw != TFB_REDRAW_NO) + { + updated.x = updated.y = 0; + updated.w = ScreenWidth; + updated.h = ScreenHeight; + } + else + { + updated.x = TFB_BBox.region.corner.x; + updated.y = TFB_BBox.region.corner.y; + updated.w = TFB_BBox.region.extent.width; + updated.h = TFB_BBox.region.extent.height; + } + + backbuffer = SDL_Video; + (void)transition_amount; + (void)fade_amount; +} + +static void +TFB_Pure_Scaled_Postprocess (void) +{ + SDL_LockSurface (scalebuffer); + SDL_LockSurface (backbuffer); + + if (scaler) + scaler (backbuffer, scalebuffer, &updated); + + if (GfxFlags & TFB_GFXFLAGS_SCANLINES) + ScanLines (scalebuffer, &updated); + + SDL_UnlockSurface (backbuffer); + SDL_UnlockSurface (scalebuffer); + + updated.x *= 2; + updated.y *= 2; + updated.w *= 2; + updated.h *= 2; + if (scalebuffer != SDL_Video) + SDL_BlitSurface (scalebuffer, &updated, SDL_Video, &updated); + + SDL_UpdateRects (SDL_Video, 1, &updated); +} + +static void +TFB_Pure_Unscaled_Postprocess (void) +{ + SDL_UpdateRect (SDL_Video, updated.x, updated.y, + updated.w, updated.h); +} + +static void +TFB_Pure_UploadTransitionScreen (void) +{ + /* This is a no-op in SDL1 Pure mode */ +} + +static void +TFB_Pure_ScreenLayer (SCREEN screen, Uint8 a, SDL_Rect *rect) +{ + if (SDL_Screens[screen] == backbuffer) + return; + SDL_SetAlpha (SDL_Screens[screen], SDL_SRCALPHA, a); + SDL_BlitSurface (SDL_Screens[screen], rect, backbuffer, rect); +} + +static void +TFB_Pure_ColorLayer (Uint8 r, Uint8 g, Uint8 b, Uint8 a, SDL_Rect *rect) +{ + Uint32 col = SDL_MapRGB (fade_color_surface->format, r, g, b); + if (col != fade_color) + { + fade_color = col; + SDL_FillRect (fade_color_surface, NULL, fade_color); + } + SDL_SetAlpha (fade_color_surface, SDL_SRCALPHA, a); + SDL_BlitSurface (fade_color_surface, rect, backbuffer, rect); +} + +void +Scale_PerfTest (void) +{ + TimeCount TimeStart, TimeIn; + TimeCount Now = 0; + SDL_Rect updated = {0, 0, ScreenWidth, ScreenHeight}; + int i; + + if (!scaler) + { + log_add (log_Error, "No scaler configured! " + "Run with larger resolution, please"); + return; + } + if (!scaled_display) + { + log_add (log_Error, "Run scaler performance tests " + "in Pure mode, please"); + return; + } + + SDL_LockSurface (SDL_Screen); + SDL_LockSurface (scaled_display); + + TimeStart = TimeIn = SDL_GetTicks (); + + for (i = 1; i < 1001; ++i) // run for 1000 frames + { + scaler (SDL_Screen, scaled_display, &updated); + + if (GfxFlags & TFB_GFXFLAGS_SCANLINES) + ScanLines (scaled_display, &updated); + + if (i % 100 == 0) + { + Now = SDL_GetTicks (); + log_add (log_Debug, "%03d(%04u) ", 100*1000 / (Now - TimeIn), + Now - TimeIn); + TimeIn = Now; + } + } + + log_add (log_Debug, "Full frames scaled: %d; over %u ms; %d fps\n", + (i - 1), Now - TimeStart, i * 1000 / (Now - TimeStart)); + + SDL_UnlockSurface (scaled_display); + SDL_UnlockSurface (SDL_Screen); +} + +#endif diff --git a/src/libs/graphics/sdl/pure.h b/src/libs/graphics/sdl/pure.h new file mode 100644 index 0000000..50cf06f --- /dev/null +++ b/src/libs/graphics/sdl/pure.h @@ -0,0 +1,29 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef PURE_H +#define PURE_H + +#include "libs/graphics/sdl/sdl_common.h" + +int TFB_Pure_InitGraphics (int driver, int flags, const char *renderer, int width, int height); +void TFB_Pure_UninitGraphics (void); +int TFB_Pure_ConfigureVideo (int driver, int flags, int width, int height, int togglefullscreen); +void Scale_PerfTest (void); + +#endif diff --git a/src/libs/graphics/sdl/rotozoom.c b/src/libs/graphics/sdl/rotozoom.c new file mode 100644 index 0000000..9f22769 --- /dev/null +++ b/src/libs/graphics/sdl/rotozoom.c @@ -0,0 +1,1038 @@ +/* + + rotozoom.c - rotozoomer for 32bit or 8bit surfaces + LGPL (c) A. Schiffler + + Note by sc2 developers: + Taken from SDL_gfx library and modified, original code can be downloaded + from http://www.ferzkopp.net/Software/SDL_gfx-2.0/ + +*/ + +#include <stdlib.h> +#include <string.h> +#include "sdl_common.h" +#include "libs/memlib.h" +#include "port.h" +#include "rotozoom.h" + +#define MAX(a,b) (((a) > (b)) ? (a) : (b)) + + + +/* + + 32bit Zoomer with optional anti-aliasing by bilinear interpolation. + + Zoomes 32bit RGBA/ABGR 'src' surface to 'dst' surface. + +*/ + +int zoomSurfaceRGBA(SDL_Surface * src, SDL_Surface * dst, int smooth) +{ + int x, y, sx, sy, *sax, *say, *csax, *csay, csx, csy, ex, ey, t1, t2, sstep; + tColorRGBA *c00, *c01, *c10, *c11; + tColorRGBA *sp, *csp, *dp; + int sgap, dgap; + + /* + * Variable setup + */ + if (smooth) { + /* + * For interpolation: assume source dimension is one pixel + */ + /* + * smaller to avoid overflow on right and bottom edge. + */ + sx = (int) (65536.0 * (float) (src->w - 1) / (float) dst->w); + sy = (int) (65536.0 * (float) (src->h - 1) / (float) dst->h); + } else { + sx = (int) (65536.0 * (float) src->w / (float) dst->w); + sy = (int) (65536.0 * (float) src->h / (float) dst->h); + } + + /* + * Allocate memory for row increments + */ + +#ifndef __SYMBIAN32__ + if ((sax = (int *) alloca((dst->w + 1) * sizeof(Uint32))) == NULL) + return (-1); + if ((say = (int *) alloca((dst->h + 1) * sizeof(Uint32))) == NULL) + return (-1); +#else + if ((sax = (int *) HMalloc((dst->w + 1) * sizeof(Uint32))) == NULL) + return (-1); + if ((say = (int *) HMalloc((dst->h + 1) * sizeof(Uint32))) == NULL) + { + HFree(sax); + return (-1); + } +#endif + + + /* + * Precalculate row increments + */ + csx = 0; + csax = sax; + for (x = 0; x <= dst->w; x++) { + *csax = csx; + csax++; + csx &= 0xffff; + csx += sx; + } + csy = 0; + csay = say; + for (y = 0; y <= dst->h; y++) { + *csay = csy; + csay++; + csy &= 0xffff; + csy += sy; + } + + /* + * Pointer setup + */ + sp = csp = (tColorRGBA *) src->pixels; + dp = (tColorRGBA *) dst->pixels; + sgap = src->pitch - src->w * 4; + dgap = dst->pitch - dst->w * 4; + + /* + * Switch between interpolating and non-interpolating code + */ + if (smooth) { + + /* + * Interpolating Zoom + */ + + /* + * Scan destination + */ + csay = say; + for (y = 0; y < dst->h; y++) { + /* + * Setup color source pointers + */ + c00 = csp; + c01 = csp; + c01++; + c10 = (tColorRGBA *) ((Uint8 *) csp + src->pitch); + c11 = c10; + c11++; + csax = sax; + for (x = 0; x < dst->w; x++) { + + /* + * Interpolate colors + */ + ex = (*csax & 0xffff); + ey = (*csay & 0xffff); + t1 = ((((c01->r - c00->r) * ex) >> 16) + c00->r) & 0xff; + t2 = ((((c11->r - c10->r) * ex) >> 16) + c10->r) & 0xff; + dp->r = (((t2 - t1) * ey) >> 16) + t1; + t1 = ((((c01->g - c00->g) * ex) >> 16) + c00->g) & 0xff; + t2 = ((((c11->g - c10->g) * ex) >> 16) + c10->g) & 0xff; + dp->g = (((t2 - t1) * ey) >> 16) + t1; + t1 = ((((c01->b - c00->b) * ex) >> 16) + c00->b) & 0xff; + t2 = ((((c11->b - c10->b) * ex) >> 16) + c10->b) & 0xff; + dp->b = (((t2 - t1) * ey) >> 16) + t1; + t1 = ((((c01->a - c00->a) * ex) >> 16) + c00->a) & 0xff; + t2 = ((((c11->a - c10->a) * ex) >> 16) + c10->a) & 0xff; + dp->a = (((t2 - t1) * ey) >> 16) + t1; + + /* + * Advance source pointers + */ + csax++; + sstep = (*csax >> 16); + c00 += sstep; + c01 += sstep; + c10 += sstep; + c11 += sstep; + /* + * Advance destination pointer + */ + dp++; + } + /* + * Advance source pointer + */ + csay++; + csp = (tColorRGBA *) ((Uint8 *) csp + (*csay >> 16) * src->pitch); + /* + * Advance destination pointers + */ + dp = (tColorRGBA *) ((Uint8 *) dp + dgap); + } + + } else { + + /* + * Non-Interpolating Zoom + */ + + csay = say; + for (y = 0; y < dst->h; y++) { + sp = csp; + csax = sax; + for (x = 0; x < dst->w; x++) { + /* + * Draw + */ + *dp = *sp; + /* + * Advance source pointers + */ + csax++; + sp += (*csax >> 16); + /* + * Advance destination pointer + */ + dp++; + } + /* + * Advance source pointer + */ + csay++; + csp = (tColorRGBA *) ((Uint8 *) csp + (*csay >> 16) * src->pitch); + /* + * Advance destination pointers + */ + dp = (tColorRGBA *) ((Uint8 *) dp + dgap); + } + + } + +#ifdef __SYMBIAN32__ + HFree(sax); + HFree(say); +#endif + + return (0); +} + +/* + + 8bit Zoomer without smoothing. + + Zoomes 8bit palette/Y 'src' surface to 'dst' surface. + +*/ + +static +int zoomSurfaceY(SDL_Surface * src, SDL_Surface * dst) +{ + Uint32 sx, sy, *sax, *say, *csax, *csay, csx, csy; + int x, y; + Uint8 *sp, *dp, *csp; + int dgap; + + /* + * Variable setup + */ + sx = (Uint32) (65536.0 * (float) src->w / (float) dst->w); + sy = (Uint32) (65536.0 * (float) src->h / (float) dst->h); + + /* + * Allocate memory for row increments + */ +#ifndef __SYMBIAN32__ + if ((sax = (Uint32 *) alloca(dst->w * sizeof(Uint32))) == NULL) + return (-1); + if ((say = (Uint32 *) alloca(dst->h * sizeof(Uint32))) == NULL) + return (-1); +#else + if ((sax = (Uint32 *) HMalloc(dst->w * sizeof(Uint32))) == NULL) + return (-1); + if ((say = (Uint32 *) HMalloc(dst->h * sizeof(Uint32))) == NULL) + { + HFree(sax); + return (-1); + } +#endif + + /* + * Precalculate row increments + */ + csx = 0; + csax = sax; + for (x = 0; x < dst->w; x++) { + csx += sx; + *csax = (csx >> 16); + csx &= 0xffff; + csax++; + } + csy = 0; + csay = say; + for (y = 0; y < dst->h; y++) { + csy += sy; + *csay = (csy >> 16); + csy &= 0xffff; + csay++; + } + + csx = 0; + csax = sax; + for (x = 0; x < dst->w; x++) { + csx += (*csax); + csax++; + } + csy = 0; + csay = say; + for (y = 0; y < dst->h; y++) { + csy += (*csay); + csay++; + } + + /* + * Pointer setup + */ + sp = csp = (Uint8 *) src->pixels; + dp = (Uint8 *) dst->pixels; + dgap = dst->pitch - dst->w; + + /* + * Draw + */ + csay = say; + for (y = 0; y < dst->h; y++) { + csax = sax; + sp = csp; + for (x = 0; x < dst->w; x++) { + /* + * Draw + */ + *dp = *sp; + /* + * Advance source pointers + */ + sp += (*csax); + csax++; + /* + * Advance destination pointer + */ + dp++; + } + /* + * Advance source pointer (for row) + */ + csp += ((*csay) * src->pitch); + csay++; + /* + * Advance destination pointers + */ + dp += dgap; + } + +#ifdef __SYMBIAN32__ + HFree(sax); + HFree(say); +#endif + + return (0); +} + +/* + + 32bit Rotozoomer with optional anti-aliasing by bilinear interpolation. + + Rotates and zoomes 32bit RGBA/ABGR 'src' surface to 'dst' surface. + +*/ + +static +void transformSurfaceRGBA(SDL_Surface * src, SDL_Surface * dst, int cx, int cy, int isin, int icos, int smooth) +{ + int x, y, t1, t2, dx, dy, xd, yd, sdx, sdy, ax, ay, ex, ey, sw, sh; + tColorRGBA c00, c01, c10, c11; + tColorRGBA *pc, *sp; + int gap; + + /* + * Variable setup + */ + xd = ((src->w - dst->w) << 15); + yd = ((src->h - dst->h) << 15); + ax = (cx << 16) - (icos * cx); + ay = (cy << 16) - (isin * cx); + sw = src->w - 1; + sh = src->h - 1; + pc = dst->pixels; + gap = dst->pitch - dst->w * 4; + + /* + * Switch between interpolating and non-interpolating code + */ + if (smooth) { + for (y = 0; y < dst->h; y++) { + dy = cy - y; + sdx = (ax + (isin * dy)) + xd; + sdy = (ay - (icos * dy)) + yd; + for (x = 0; x < dst->w; x++) { + dx = (sdx >> 16); + dy = (sdy >> 16); + if ((dx >= -1) && (dy >= -1) && (dx < src->w) && (dy < src->h)) { + if ((dx >= 0) && (dy >= 0) && (dx < sw) && (dy < sh)) { + sp = (tColorRGBA *) ((Uint8 *) src->pixels + src->pitch * dy); + sp += dx; + c00 = *sp; + sp += 1; + c01 = *sp; + sp = (tColorRGBA *) ((Uint8 *) sp + src->pitch); + sp -= 1; + c10 = *sp; + sp += 1; + c11 = *sp; + } else if ((dx == sw) && (dy == sh)) { + sp = (tColorRGBA *) ((Uint8 *) src->pixels + src->pitch * dy); + sp += dx; + c00 = *sp; + c01 = *sp; + c10 = *sp; + c11 = *sp; + } else if ((dx == -1) && (dy == -1)) { + sp = (tColorRGBA *) (src->pixels); + c00 = *sp; + c01 = *sp; + c10 = *sp; + c11 = *sp; + } else if ((dx == -1) && (dy == sh)) { + sp = (tColorRGBA *) (src->pixels); + sp = (tColorRGBA *) ((Uint8 *) src->pixels + src->pitch * dy); + c00 = *sp; + c01 = *sp; + c10 = *sp; + c11 = *sp; + } else if ((dx == sw) && (dy == -1)) { + sp = (tColorRGBA *) (src->pixels); + sp += dx; + c00 = *sp; + c01 = *sp; + c10 = *sp; + c11 = *sp; + } else if (dx == -1) { + sp = (tColorRGBA *) ((Uint8 *) src->pixels + src->pitch * dy); + c00 = *sp; + c01 = *sp; + c10 = *sp; + sp = (tColorRGBA *) ((Uint8 *) sp + src->pitch); + c11 = *sp; + } else if (dy == -1) { + sp = (tColorRGBA *) (src->pixels); + sp += dx; + c00 = *sp; + c01 = *sp; + c10 = *sp; + sp += 1; + c11 = *sp; + } else if (dx == sw) { + sp = (tColorRGBA *) ((Uint8 *) src->pixels + src->pitch * dy); + sp += dx; + c00 = *sp; + c01 = *sp; + sp = (tColorRGBA *) ((Uint8 *) sp + src->pitch); + c10 = *sp; + c11 = *sp; + } else if (dy == sh) { + sp = (tColorRGBA *) ((Uint8 *) src->pixels + src->pitch * dy); + sp += dx; + c00 = *sp; + sp += 1; + c01 = *sp; + c10 = *sp; + c11 = *sp; + } + /* + * Interpolate colors + */ + ex = (sdx & 0xffff); + ey = (sdy & 0xffff); + t1 = ((((c01.r - c00.r) * ex) >> 16) + c00.r) & 0xff; + t2 = ((((c11.r - c10.r) * ex) >> 16) + c10.r) & 0xff; + pc->r = (((t2 - t1) * ey) >> 16) + t1; + t1 = ((((c01.g - c00.g) * ex) >> 16) + c00.g) & 0xff; + t2 = ((((c11.g - c10.g) * ex) >> 16) + c10.g) & 0xff; + pc->g = (((t2 - t1) * ey) >> 16) + t1; + t1 = ((((c01.b - c00.b) * ex) >> 16) + c00.b) & 0xff; + t2 = ((((c11.b - c10.b) * ex) >> 16) + c10.b) & 0xff; + pc->b = (((t2 - t1) * ey) >> 16) + t1; + t1 = ((((c01.a - c00.a) * ex) >> 16) + c00.a) & 0xff; + t2 = ((((c11.a - c10.a) * ex) >> 16) + c10.a) & 0xff; + pc->a = (((t2 - t1) * ey) >> 16) + t1; + } + sdx += icos; + sdy += isin; + pc++; + } + pc = (tColorRGBA *) ((Uint8 *) pc + gap); + } + } else { + for (y = 0; y < dst->h; y++) { + dy = cy - y; + sdx = (ax + (isin * dy)) + xd; + sdy = (ay - (icos * dy)) + yd; + for (x = 0; x < dst->w; x++) { + dx = (short) (sdx >> 16); + dy = (short) (sdy >> 16); + if ((dx >= 0) && (dy >= 0) && (dx < src->w) && (dy < src->h)) { + sp = (tColorRGBA *) ((Uint8 *) src->pixels + src->pitch * dy); + sp += dx; + *pc = *sp; + } + sdx += icos; + sdy += isin; + pc++; + } + pc = (tColorRGBA *) ((Uint8 *) pc + gap); + } + } +} + +/* + + 8bit Rotozoomer without smoothing + + Rotates and zoomes 8bit palette/Y 'src' surface to 'dst' surface. + +*/ + +static +void transformSurfaceY(SDL_Surface * src, SDL_Surface * dst, int cx, int cy, int isin, int icos) +{ + int x, y, dx, dy, xd, yd, sdx, sdy, ax, ay, sw, sh; + tColorY *pc, *sp; + int gap; + Uint32 colorkey = 0; + + /* + * Variable setup + */ + xd = ((src->w - dst->w) << 15); + yd = ((src->h - dst->h) << 15); + ax = (cx << 16) - (icos * cx); + ay = (cy << 16) - (isin * cx); + sw = src->w - 1; + sh = src->h - 1; + pc = dst->pixels; + gap = dst->pitch - dst->w; + /* + * Clear surface to colorkey + */ + TFB_GetColorKey (src, &colorkey); + memset(pc, (unsigned char) (colorkey & 0xff), dst->pitch * dst->h); + /* + * Iterate through destination surface + */ + for (y = 0; y < dst->h; y++) { + dy = cy - y; + sdx = (ax + (isin * dy)) + xd; + sdy = (ay - (icos * dy)) + yd; + for (x = 0; x < dst->w; x++) { + dx = (short) (sdx >> 16); + dy = (short) (sdy >> 16); + if ((dx >= 0) && (dy >= 0) && (dx < src->w) && (dy < src->h)) { + sp = (tColorY *) (src->pixels); + sp += (src->pitch * dy + dx); + *pc = *sp; + } + sdx += icos; + sdy += isin; + pc++; + } + pc += gap; + } +} + +/* + + rotozoomSurface() + + Rotates and zoomes a 32bit or 8bit 'src' surface to newly created 'dst' surface. + 'angle' is the rotation in degrees. 'zoom' a scaling factor. If 'smooth' is 1 + then the destination 32bit surface is anti-aliased. If the surface is not 8bit + or 32bit RGBA/ABGR it will be converted into a 32bit RGBA format on the fly. + +*/ + +#define VALUE_LIMIT 0.001 + + +/* Local rotozoom-size function with trig result return */ + +static +void rotozoomSurfaceSizeTrig(int width, int height, double angle, double zoom, int *dstwidth, int *dstheight, + double *canglezoom, double *sanglezoom) +{ + double x, y, cx, cy, sx, sy; + double radangle; + int dstwidthhalf, dstheighthalf; + + /* + * Determine destination width and height by rotating a centered source box + */ + radangle = angle * (M_PI / 180.0); + *sanglezoom = sin(radangle); + *canglezoom = cos(radangle); + *sanglezoom *= zoom; + *canglezoom *= zoom; + x = width / 2; + y = height / 2; + cx = *canglezoom * x; + cy = *canglezoom * y; + sx = *sanglezoom * x; + sy = *sanglezoom * y; + dstwidthhalf = MAX(ceil(fabs(cx) + fabs(sy)), 1); + dstheighthalf = MAX(ceil(fabs(sx) + fabs(cy)), 1); + *dstwidth = 2 * dstwidthhalf; + *dstheight = 2 * dstheighthalf; +} + + +/* Publically available rotozoom-size function */ + +void rotozoomSurfaceSize(int width, int height, double angle, double zoom, int *dstwidth, int *dstheight) +{ + double dummy_sanglezoom, dummy_canglezoom; + + rotozoomSurfaceSizeTrig(width, height, angle, zoom, dstwidth, dstheight, &dummy_sanglezoom, &dummy_canglezoom); +} + + +/* Publically available rotozoom function */ + +SDL_Surface *rotozoomSurface(SDL_Surface * src, double angle, double zoom, int smooth) +{ + SDL_Surface *rz_src; + SDL_Surface *rz_dst; + double zoominv; + double sanglezoom, canglezoom, sanglezoominv, canglezoominv; + int dstwidthhalf, dstwidth, dstheighthalf, dstheight; + int is32bit; + int i, src_converted; + + /* + * Sanity check + */ + if (src == NULL) + return (NULL); + + /* + * Determine if source surface is 32bit or 8bit + */ + is32bit = (src->format->BitsPerPixel == 32); + if ((is32bit) || (src->format->BitsPerPixel == 8)) { + /* + * Use source surface 'as is' + */ + rz_src = src; + src_converted = 0; + } else { + /* + * New source surface is 32bit with a defined RGBA ordering + */ + rz_src = + SDL_CreateRGBSurface(SDL_SWSURFACE, src->w, src->h, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000); + SDL_BlitSurface(src, NULL, rz_src, NULL); + src_converted = 1; + is32bit = 1; + } + + /* + * Sanity check zoom factor + */ + if (zoom < VALUE_LIMIT) { + zoom = VALUE_LIMIT; + } + zoominv = 65536.0 / (zoom * zoom); + + /* + * Check if we have a rotozoom or just a zoom + */ + if (fabs(angle) > VALUE_LIMIT) { + + /* + * Angle!=0: full rotozoom + */ + /* + * ----------------------- + */ + + /* Determine target size */ + rotozoomSurfaceSizeTrig(rz_src->w, rz_src->h, angle, zoom, &dstwidth, &dstheight, &canglezoom, &sanglezoom); + + /* + * Calculate target factors from sin/cos and zoom + */ + sanglezoominv = sanglezoom; + canglezoominv = canglezoom; + sanglezoominv *= zoominv; + canglezoominv *= zoominv; + + /* Calculate half size */ + dstwidthhalf = dstwidth / 2; + dstheighthalf = dstheight / 2; + + /* + * Alloc space to completely contain the rotated surface + */ + rz_dst = NULL; + if (is32bit) { + /* + * Target surface is 32bit with source RGBA/ABGR ordering + */ + rz_dst = + SDL_CreateRGBSurface(SDL_SWSURFACE, dstwidth, dstheight, 32, + rz_src->format->Rmask, rz_src->format->Gmask, + rz_src->format->Bmask, rz_src->format->Amask); + } else { + /* + * Target surface is 8bit + */ + rz_dst = SDL_CreateRGBSurface(SDL_SWSURFACE, dstwidth, dstheight, 8, 0, 0, 0, 0); + } + + /* + * Lock source surface + */ + SDL_LockSurface(rz_src); + /* + * Check which kind of surface we have + */ + if (is32bit) { + /* + * Call the 32bit transformation routine to do the rotation (using alpha) + */ + transformSurfaceRGBA(rz_src, rz_dst, dstwidthhalf, dstheighthalf, + (int) (sanglezoominv), (int) (canglezoominv), smooth); + /* + * Turn on source-alpha support + */ + TFB_SetSurfaceAlphaMod (rz_dst, 255); + } else { + /* + * Copy palette and colorkey info + */ + Uint32 srckey = 0; + TFB_GetColorKey (rz_src, &srckey); + for (i = 0; i < rz_src->format->palette->ncolors; i++) { + rz_dst->format->palette->colors[i] = rz_src->format->palette->colors[i]; + } + rz_dst->format->palette->ncolors = rz_src->format->palette->ncolors; + /* + * Call the 8bit transformation routine to do the rotation + */ + transformSurfaceY(rz_src, rz_dst, dstwidthhalf, dstheighthalf, + (int) (sanglezoominv), (int) (canglezoominv)); + TFB_SetColorKey(rz_dst, srckey, 1); + } + /* + * Unlock source surface + */ + SDL_UnlockSurface(rz_src); + + } else { + + /* + * Angle=0: Just a zoom + */ + /* + * -------------------- + */ + + /* + * Calculate target size + */ + zoomSurfaceSize(rz_src->w, rz_src->h, zoom, zoom, &dstwidth, &dstheight); + + /* + * Alloc space to completely contain the zoomed surface + */ + rz_dst = NULL; + if (is32bit) { + /* + * Target surface is 32bit with source RGBA/ABGR ordering + */ + rz_dst = + SDL_CreateRGBSurface(SDL_SWSURFACE, dstwidth, dstheight, 32, + rz_src->format->Rmask, rz_src->format->Gmask, + rz_src->format->Bmask, rz_src->format->Amask); + } else { + /* + * Target surface is 8bit + */ + rz_dst = SDL_CreateRGBSurface(SDL_SWSURFACE, dstwidth, dstheight, 8, 0, 0, 0, 0); + } + + /* + * Lock source surface + */ + SDL_LockSurface(rz_src); + /* + * Check which kind of surface we have + */ + if (is32bit) { + /* + * Call the 32bit transformation routine to do the zooming (using alpha) + */ + zoomSurfaceRGBA(rz_src, rz_dst, smooth); + /* + * Turn on source-alpha support + */ + TFB_SetSurfaceAlphaMod (rz_dst, 255); + } else { + /* + * Copy palette and colorkey info + */ + Uint32 srckey = 0; + TFB_GetColorKey (rz_src, &srckey); + for (i = 0; i < rz_src->format->palette->ncolors; i++) { + rz_dst->format->palette->colors[i] = rz_src->format->palette->colors[i]; + } + rz_dst->format->palette->ncolors = rz_src->format->palette->ncolors; + /* + * Call the 8bit transformation routine to do the zooming + */ + zoomSurfaceY(rz_src, rz_dst); + TFB_SetColorKey(rz_dst, srckey, 1); + } + /* + * Unlock source surface + */ + SDL_UnlockSurface(rz_src); + } + + /* + * Cleanup temp surface + */ + if (src_converted) { + SDL_FreeSurface(rz_src); + } + + /* + * Return destination surface + */ + return (rz_dst); +} + +/* Publically available rotate function */ + +int rotateSurface(SDL_Surface *src, SDL_Surface *dst, double angle, int smooth) +{ + double zoominv; + double sanglezoom, canglezoom, sanglezoominv, canglezoominv; + int dstwidthhalf, dstwidth, dstheighthalf, dstheight; + int is32bit; + int i; + + /* Sanity check */ + if (!src || !dst) + return -1; + if (src->format->BitsPerPixel != dst->format->BitsPerPixel) + return -1; + + /* Determine if source surface is 32bit or 8bit */ + is32bit = (src->format->BitsPerPixel == 32); + + zoominv = 65536.0; + + /* Check if we have to rotate anything */ + if (fabs(angle) <= VALUE_LIMIT) + { + SDL_BlitSurface(src, NULL, dst, NULL); + return 0; + } + + /* Determine target size */ + rotozoomSurfaceSizeTrig(src->w, src->h, angle, 1, &dstwidth, &dstheight, &canglezoom, &sanglezoom); + + /* + * Calculate target factors from sin/cos and zoom + */ + sanglezoominv = sanglezoom; + canglezoominv = canglezoom; + sanglezoominv *= zoominv; + canglezoominv *= zoominv; + + /* Calculate half size */ + dstwidthhalf = dstwidth / 2; + dstheighthalf = dstheight / 2; + + /* Check if the rotated surface will fit destination */ + if (dst->w < dstwidth || dst->h < dstheight) + return -1; + + /* Lock source surface */ + SDL_LockSurface(src); + SDL_LockSurface(dst); + /* Check which kind of surface we have */ + if (is32bit) + { /* Call the 32bit transformation routine to do the rotation (using alpha) */ + transformSurfaceRGBA(src, dst, dstwidthhalf, dstheighthalf, + (int) (sanglezoominv), (int) (canglezoominv), smooth); + } + else + { + /* Copy palette info */ + for (i = 0; i < src->format->palette->ncolors; i++) + dst->format->palette->colors[i] = src->format->palette->colors[i]; + dst->format->palette->ncolors = src->format->palette->ncolors; + /* Call the 8bit transformation routine to do the rotation */ + transformSurfaceY(src, dst, dstwidthhalf, dstheighthalf, + (int) (sanglezoominv), (int) (canglezoominv)); + } + /* Unlock source surface */ + SDL_UnlockSurface(dst); + SDL_UnlockSurface(src); + + return 0; +} + +/* + + zoomSurface() + + Zoomes a 32bit or 8bit 'src' surface to newly created 'dst' surface. + 'zoomx' and 'zoomy' are scaling factors for width and height. If 'smooth' is 1 + then the destination 32bit surface is anti-aliased. If the surface is not 8bit + or 32bit RGBA/ABGR it will be converted into a 32bit RGBA format on the fly. + +*/ + +#define VALUE_LIMIT 0.001 + +void zoomSurfaceSize(int width, int height, double zoomx, double zoomy, int *dstwidth, int *dstheight) +{ + /* + * Sanity check zoom factors + */ + if (zoomx < VALUE_LIMIT) { + zoomx = VALUE_LIMIT; + } + if (zoomy < VALUE_LIMIT) { + zoomy = VALUE_LIMIT; + } + + /* + * Calculate target size + */ + *dstwidth = (int) ((double) width * zoomx); + *dstheight = (int) ((double) height * zoomy); + if (*dstwidth < 1) { + *dstwidth = 1; + } + if (*dstheight < 1) { + *dstheight = 1; + } +} + +SDL_Surface *zoomSurface(SDL_Surface * src, double zoomx, double zoomy, int smooth) +{ + SDL_Surface *rz_src; + SDL_Surface *rz_dst; + int dstwidth, dstheight; + int is32bit; + int i, src_converted; + + /* + * Sanity check + */ + if (src == NULL) + return (NULL); + + /* + * Determine if source surface is 32bit or 8bit + */ + is32bit = (src->format->BitsPerPixel == 32); + if ((is32bit) || (src->format->BitsPerPixel == 8)) { + /* + * Use source surface 'as is' + */ + rz_src = src; + src_converted = 0; + } else { + /* + * New source surface is 32bit with a defined RGBA ordering + */ + rz_src = + SDL_CreateRGBSurface(SDL_SWSURFACE, src->w, src->h, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000); + SDL_BlitSurface(src, NULL, rz_src, NULL); + src_converted = 1; + is32bit = 1; + } + + /* Get size if target */ + zoomSurfaceSize(rz_src->w, rz_src->h, zoomx, zoomy, &dstwidth, &dstheight); + + /* + * Alloc space to completely contain the zoomed surface + */ + rz_dst = NULL; + if (is32bit) { + /* + * Target surface is 32bit with source RGBA/ABGR ordering + */ + rz_dst = + SDL_CreateRGBSurface(SDL_SWSURFACE, dstwidth, dstheight, 32, + rz_src->format->Rmask, rz_src->format->Gmask, + rz_src->format->Bmask, rz_src->format->Amask); + } else { + /* + * Target surface is 8bit + */ + rz_dst = SDL_CreateRGBSurface(SDL_SWSURFACE, dstwidth, dstheight, 8, 0, 0, 0, 0); + } + + /* + * Lock source surface + */ + SDL_LockSurface(rz_src); + /* + * Check which kind of surface we have + */ + if (is32bit) { + /* + * Call the 32bit transformation routine to do the zooming (using alpha) + */ + zoomSurfaceRGBA(rz_src, rz_dst, smooth); + /* + * Turn on source-alpha support + */ + TFB_SetSurfaceAlphaMod (rz_dst, 255); + } else { + /* + * Copy palette and colorkey info + */ + Uint32 srckey = 0; + TFB_GetColorKey (rz_src, &srckey); + for (i = 0; i < rz_src->format->palette->ncolors; i++) { + rz_dst->format->palette->colors[i] = rz_src->format->palette->colors[i]; + } + rz_dst->format->palette->ncolors = rz_src->format->palette->ncolors; + /* + * Call the 8bit transformation routine to do the zooming + */ + zoomSurfaceY(rz_src, rz_dst); + TFB_SetColorKey(rz_dst, srckey, 0); + } + /* + * Unlock source surface + */ + SDL_UnlockSurface(rz_src); + + /* + * Cleanup temp surface + */ + if (src_converted) { + SDL_FreeSurface(rz_src); + } + + /* + * Return destination surface + */ + return (rz_dst); +} + diff --git a/src/libs/graphics/sdl/rotozoom.h b/src/libs/graphics/sdl/rotozoom.h new file mode 100644 index 0000000..728fd42 --- /dev/null +++ b/src/libs/graphics/sdl/rotozoom.h @@ -0,0 +1,96 @@ +/* + + rotozoom.h - rotozoomer for 32bit or 8bit surfaces + LGPL (c) A. Schiffler + + Note by sc2 developers: + Taken from SDL_gfx library and modified, original code can be downloaded + from http://www.ferzkopp.net/Software/SDL_gfx-2.0/ + +*/ + + +#ifndef ROTOZOOM_H +#define ROTOZOOM_H + +#include <math.h> +#ifndef M_PI +#define M_PI 3.141592654 +#endif + +#include "port.h" +#include SDL_INCLUDE(SDL.h) + + +/* ---- Defines */ + +#define SMOOTHING_OFF 0 +#define SMOOTHING_ON 1 + +/* ---- Structures */ + +typedef struct tColorRGBA { + Uint8 r; + Uint8 g; + Uint8 b; + Uint8 a; +} tColorRGBA; + +typedef struct tColorY { + Uint8 y; +} tColorY; + + +/* ---- Prototypes */ + +/* + zoomSurfaceRGBA() + + Zoom the src surface into dst. The zoom amount is determined + by the dimensions of src and dst + +*/ +int zoomSurfaceRGBA(SDL_Surface * src, SDL_Surface * dst, int smooth); + +/* + + rotozoomSurface() + + Rotates and zoomes a 32bit or 8bit 'src' surface to newly created 'dst' surface. + 'angle' is the rotation in degrees. 'zoom' a scaling factor. If 'smooth' is 1 + then the destination 32bit surface is anti-aliased. If the surface is not 8bit + or 32bit RGBA/ABGR it will be converted into a 32bit RGBA format on the fly. + +*/ + +SDL_Surface *rotozoomSurface(SDL_Surface * src, double angle, double zoom, + int smooth); + +int rotateSurface(SDL_Surface * src, SDL_Surface * dst, double angle, + int smooth); + +/* Returns the size of the target surface for a rotozoomSurface() call */ + +void rotozoomSurfaceSize(int width, int height, double angle, double zoom, + int *dstwidth, int *dstheight); + +/* + + zoomSurface() + + Zoomes a 32bit or 8bit 'src' surface to newly created 'dst' surface. + 'zoomx' and 'zoomy' are scaling factors for width and height. If 'smooth' is 1 + then the destination 32bit surface is anti-aliased. If the surface is not 8bit + or 32bit RGBA/ABGR it will be converted into a 32bit RGBA format on the fly. + +*/ + +SDL_Surface *zoomSurface(SDL_Surface * src, double zoomx, double zoomy, + int smooth); + +/* Returns the size of the target surface for a zoomSurface() call */ + +void zoomSurfaceSize(int width, int height, double zoomx, double zoomy, + int *dstwidth, int *dstheight); + +#endif diff --git a/src/libs/graphics/sdl/scaleint.h b/src/libs/graphics/sdl/scaleint.h new file mode 100644 index 0000000..e54de80 --- /dev/null +++ b/src/libs/graphics/sdl/scaleint.h @@ -0,0 +1,433 @@ +/* + * Copyright (C) 2005 Alex Volkov (codepro@usa.net) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +// Scalers Internals + +#ifndef SCALEINT_H_ +#define SCALEINT_H_ + +#include "libs/graphics/sdl/sdl_common.h" +#include "types.h" + + +// Plain C names +#define SCALE_(name) Scale ## _ ## name + +// These are defaults +#define SCALE_GETPIX(p) ( *(Uint32 *)(p) ) +#define SCALE_SETPIX(p, c) ( *(Uint32 *)(p) = (c) ) + +// Plain C defaults +#define SCALE_CMPRGB(p1, p2) \ + SCALE_(GetRGBDelta) (fmt, p1, p2) + +#define SCALE_TOYUV(p) \ + SCALE_(RGBtoYUV) (fmt, p) + +#define SCALE_CMPYUV(p1, p2, toler) \ + SCALE_(CmpYUV) (fmt, p1, p2, toler) + +#define SCALE_DIFFYUV(p1, p2) \ + SCALE_(DiffYUV) (p1, p2) +#define SCALE_DIFFYUV_TY 0x40 +#define SCALE_DIFFYUV_TU 0x12 +#define SCALE_DIFFYUV_TV 0x0c + +#define SCALE_GETY(p) \ + SCALE_(GetPixY) (fmt, p) + +#define SCALE_BILINEAR_BLEND4(r0, r1, dst, dlen) \ + SCALE_(Blend_bilinear) (r0, r1, dst, dlen) + +#define NO_PREFETCH 0 +#define INTEL_PREFETCH 1 +#define AMD_PREFETCH 2 + +typedef enum +{ + YUV_XFORM_R = 0, + YUV_XFORM_G = 1, + YUV_XFORM_B = 2, + YUV_XFORM_Y = 0, + YUV_XFORM_U = 1, + YUV_XFORM_V = 2 +} RGB_YUV_INDEX; + +extern const int YUV_matrix[3][3]; + +// pre-computed transformations for 8 bits per channel +extern int RGB_to_YUV[/*RGB*/ 3][/*YUV*/ 3][ /*mult-res*/ 256]; +extern sint16 dRGB_to_dYUV[/*RGB*/ 3][/*YUV*/ 3][ /*mult-res*/ 512]; + +typedef Uint32 YUV_VECTOR; +// pre-computed transformations for RGB555 +extern YUV_VECTOR RGB15_to_YUV[0x8000]; + + +// Platform+Scaler function lookups +// +typedef struct +{ + int flag; + TFB_ScaleFunc func; +} Scale_FuncDef_t; + + +// expands the given rectangle in all directions by 'expansion' +// guarded by 'limits' +extern void Scale_ExpandRect (SDL_Rect* rect, int expansion, + const SDL_Rect* limits); + + +// Standard plain C versions of support functions + +// Initialize various platform-specific features +static inline void +SCALE_(PlatInit) (void) +{ +} + +// Finish with various platform-specific features +static inline void +SCALE_(PlatDone) (void) +{ +} + +#if 0 +static inline void +SCALE_(Prefetch) (const void* p) +{ + /* no-op in pure C */ + (void)p; +} +#else +# define Scale_Prefetch(p) +#endif + +// compute the RGB distance squared between 2 pixels +// Plain C version +static inline int +SCALE_(GetRGBDelta) (const SDL_PixelFormat* fmt, Uint32 pix1, Uint32 pix2) +{ + int c; + int delta; + + c = ((pix1 >> fmt->Rshift) & 0xff) - ((pix2 >> fmt->Rshift) & 0xff); + delta = c * c; + + c = ((pix1 >> fmt->Gshift) & 0xff) - ((pix2 >> fmt->Gshift) & 0xff); + delta += c * c; + + c = ((pix1 >> fmt->Bshift) & 0xff) - ((pix2 >> fmt->Bshift) & 0xff); + delta += c * c; + + return delta; +} + +// retrieve the Y (intensity) component of pixel's YUV +// Plain C version +static inline int +SCALE_(GetPixY) (const SDL_PixelFormat* fmt, Uint32 pix) +{ + Uint32 r, g, b; + + r = (pix >> fmt->Rshift) & 0xff; + g = (pix >> fmt->Gshift) & 0xff; + b = (pix >> fmt->Bshift) & 0xff; + + return RGB_to_YUV [YUV_XFORM_R][YUV_XFORM_Y][r] + + RGB_to_YUV [YUV_XFORM_G][YUV_XFORM_Y][g] + + RGB_to_YUV [YUV_XFORM_B][YUV_XFORM_Y][b]; +} + +static inline YUV_VECTOR +SCALE_(RGBtoYUV) (const SDL_PixelFormat* fmt, Uint32 pix) +{ + return RGB15_to_YUV[ + (((pix >> (fmt->Rshift + 3)) & 0x1f) << 10) | + (((pix >> (fmt->Gshift + 3)) & 0x1f) << 5) | + (((pix >> (fmt->Bshift + 3)) & 0x1f) ) + ]; +} + +// compare 2 pixels with respect to their YUV representations +// tolerance set by toler arg +// returns true: close; false: distant (-gt toler) +// Plain C version +static inline bool +SCALE_(CmpYUV) (const SDL_PixelFormat* fmt, Uint32 pix1, Uint32 pix2, int toler) +#if 1 +{ + int dr, dg, db; + int delta; + + dr = ((pix1 >> fmt->Rshift) & 0xff) - ((pix2 >> fmt->Rshift) & 0xff) + 255; + dg = ((pix1 >> fmt->Gshift) & 0xff) - ((pix2 >> fmt->Gshift) & 0xff) + 255; + db = ((pix1 >> fmt->Bshift) & 0xff) - ((pix2 >> fmt->Bshift) & 0xff) + 255; + + // compute Y delta + delta = abs (dRGB_to_dYUV [YUV_XFORM_R][YUV_XFORM_Y][dr] + + dRGB_to_dYUV [YUV_XFORM_G][YUV_XFORM_Y][dg] + + dRGB_to_dYUV [YUV_XFORM_B][YUV_XFORM_Y][db]); + if (delta > toler) + return false; + + // compute U delta + delta += abs (dRGB_to_dYUV [YUV_XFORM_R][YUV_XFORM_U][dr] + + dRGB_to_dYUV [YUV_XFORM_G][YUV_XFORM_U][dg] + + dRGB_to_dYUV [YUV_XFORM_B][YUV_XFORM_U][db]); + if (delta > toler) + return false; + + // compute V delta + delta += abs (dRGB_to_dYUV [YUV_XFORM_R][YUV_XFORM_V][dr] + + dRGB_to_dYUV [YUV_XFORM_G][YUV_XFORM_V][dg] + + dRGB_to_dYUV [YUV_XFORM_B][YUV_XFORM_V][db]); + + return delta <= toler; +} +#else +{ + int delta; + Uint32 yuv1, yuv2; + + yuv1 = RGB15_to_YUV[ + (((pix1 >> (fmt->Rshift + 3)) & 0x1f) << 10) | + (((pix1 >> (fmt->Gshift + 3)) & 0x1f) << 5) | + (((pix1 >> (fmt->Bshift + 3)) & 0x1f) ) + ]; + + yuv2 = RGB15_to_YUV[ + (((pix2 >> (fmt->Rshift + 3)) & 0x1f) << 10) | + (((pix2 >> (fmt->Gshift + 3)) & 0x1f) << 5) | + (((pix2 >> (fmt->Bshift + 3)) & 0x1f) ) + ]; + + // compute Y delta + delta = abs ((yuv1 & 0xff0000) - (yuv2 & 0xff0000)) >> 16; + if (delta > toler) + return false; + + // compute U delta + delta += abs ((yuv1 & 0x00ff00) - (yuv2 & 0x00ff00)) >> 8; + if (delta > toler) + return false; + + // compute V delta + delta += abs ((yuv1 & 0x0000ff) - (yuv2 & 0x0000ff)); + + return delta <= toler; +} +#endif + +// Check if 2 pixels are different with respect to their +// YUV representations +// returns 0: close; ~0: distant +static inline int +SCALE_(DiffYUV) (Uint32 yuv1, Uint32 yuv2) +{ + // non-branching version -- assumes 2's complement integers + // delta math only needs 25 bits and we have 32 available; + // only interested in the sign bits after subtraction + sint32 delta, ret; + + if (yuv1 == yuv2) + return 0; + + // compute Y delta + delta = abs ((yuv1 & 0xff0000) - (yuv2 & 0xff0000)); + ret = (SCALE_DIFFYUV_TY << 16) - delta; // save sign bit + + // compute U delta + delta = abs ((yuv1 & 0x00ff00) - (yuv2 & 0x00ff00)); + ret |= (SCALE_DIFFYUV_TU << 8) - delta; // save sign bit + + // compute V delta + delta = abs ((yuv1 & 0x0000ff) - (yuv2 & 0x0000ff)); + ret |= SCALE_DIFFYUV_TV - delta; // save sign bit + + return (ret >> 31); +} + +// blends two pixels with 1:1 ratio +static inline Uint32 +SCALE_(Blend_11) (Uint32 pix1, Uint32 pix2) +{ + /* (pix1 + pix2) >> 1 */ + return + /* lower bits can be safely ignored - the error is minimal + expression that calcs them is left for posterity + (pix1 & pix2 & low_mask) + + */ + ((pix1 & 0xfefefefe) >> 1) + ((pix2 & 0xfefefefe) >> 1); +} + +// blends four pixels with 1:1:1:1 ratio +static inline Uint32 +SCALE_(Blend_1111) (Uint32 pix1, Uint32 pix2, + Uint32 pix3, Uint32 pix4) +{ + /* (pix1 + pix2 + pix3 + pix4) >> 2 */ + return + /* lower bits can be safely ignored - the error is minimal + expression that calcs them is left for posterity + ((((pix1 & low_mask) + (pix2 & low_mask) + + (pix3 & low_mask) + (pix4 & low_mask) + ) >> 2) & low_mask) + + */ + ((pix1 & 0xfcfcfcfc) >> 2) + ((pix2 & 0xfcfcfcfc) >> 2) + + ((pix3 & 0xfcfcfcfc) >> 2) + ((pix4 & 0xfcfcfcfc) >> 2); +} + +// blends pixels with 3:1 ratio +static inline Uint32 +Scale_Blend_31 (Uint32 pix1, Uint32 pix2) +{ + /* (pix1 * 3 + pix2) / 4 */ + /* lower bits can be safely ignored - the error is minimal */ + return ((pix1 & 0xfefefefe) >> 1) + ((pix1 & 0xfcfcfcfc) >> 2) + + ((pix2 & 0xfcfcfcfc) >> 2); +} + +// blends pixels with 2:1:1 ratio +static inline Uint32 +Scale_Blend_211 (Uint32 pix1, Uint32 pix2, Uint32 pix3) +{ + /* (pix1 * 2 + pix2 + pix3) / 4 */ + /* lower bits can be safely ignored - the error is minimal */ + return ((pix1 & 0xfefefefe) >> 1) + + ((pix2 & 0xfcfcfcfc) >> 2) + + ((pix3 & 0xfcfcfcfc) >> 2); +} + +// blends pixels with 5:2:1 ratio +static inline Uint32 +Scale_Blend_521 (Uint32 pix1, Uint32 pix2, Uint32 pix3) +{ + /* (pix1 * 5 + pix2 * 2 + pix3) / 8 */ + /* lower bits can be safely ignored - the error is minimal */ + return ((pix1 & 0xfefefefe) >> 1) + ((pix1 & 0xf8f8f8f8) >> 3) + + ((pix2 & 0xfcfcfcfc) >> 2) + + ((pix3 & 0xf8f8f8f8) >> 3) + + 0x02020202 /* half-error */; +} + +// blends pixels with 6:1:1 ratio +static inline Uint32 +Scale_Blend_611 (Uint32 pix1, Uint32 pix2, Uint32 pix3) +{ + /* (pix1 * 6 + pix2 + pix3) / 8 */ + /* lower bits can be safely ignored - the error is minimal */ + return ((pix1 & 0xfefefefe) >> 1) + ((pix1 & 0xfcfcfcfc) >> 2) + + ((pix2 & 0xf8f8f8f8) >> 3) + + ((pix3 & 0xf8f8f8f8) >> 3) + + 0x02020202 /* half-error */; +} + +// blends pixels with 2:3:3 ratio +static inline Uint32 +Scale_Blend_233 (Uint32 pix1, Uint32 pix2, Uint32 pix3) +{ + /* (pix1 * 2 + pix2 * 3 + pix3 * 3) / 8 */ + /* lower bits can be safely ignored - the error is minimal */ + return ((pix1 & 0xfcfcfcfc) >> 2) + + ((pix2 & 0xfcfcfcfc) >> 2) + ((pix2 & 0xf8f8f8f8) >> 3) + + ((pix3 & 0xfcfcfcfc) >> 2) + ((pix3 & 0xf8f8f8f8) >> 3) + + 0x02020202 /* half-error */; +} + +// blends pixels with 14:1:1 ratio +static inline Uint32 +Scale_Blend_e11 (Uint32 pix1, Uint32 pix2, Uint32 pix3) +{ + /* (pix1 * 14 + pix2 + pix3) >> 4 */ + /* lower bits can be safely ignored - the error is minimal */ + return ((pix1 & 0xfefefefe) >> 1) + ((pix1 & 0xfcfcfcfc) >> 2) + + ((pix1 & 0xf8f8f8f8) >> 3) + + ((pix2 & 0xf0f0f0f0) >> 4) + + ((pix3 & 0xf0f0f0f0) >> 4) + + 0x03030303 /* half-error */; +} + +// Halfs the pixel's intensity +static inline Uint32 +SCALE_(HalfPixel) (Uint32 pix) +{ + return ((pix & 0xfefefefe) >> 1); +} + + +// Bilinear weighted blend of four pixels +// Function produces 4 blended pixels and writes them +// out to the surface (in 2x2 matrix) +// Pixels are computed using expanded weight matrix like so: +// ('sp' - source pixel, 'dp' - destination pixel) +// dp[0] = (9*sp[0] + 3*sp[1] + 3*sp[2] + 1*sp[3]) / 16 +// dp[1] = (3*sp[0] + 9*sp[1] + 1*sp[2] + 3*sp[3]) / 16 +// dp[2] = (3*sp[0] + 1*sp[1] + 9*sp[2] + 3*sp[3]) / 16 +// dp[3] = (1*sp[0] + 3*sp[1] + 3*sp[2] + 9*sp[3]) / 16 +static inline void +SCALE_(Blend_bilinear) (const Uint32* row0, const Uint32* row1, + Uint32* dst_p, Uint32 dlen) +{ + // We loose some lower bits here and try to compensate for + // that by adding half-error values. + // In general, the error is minimal (+-7) + // The >>4 reduction is achieved gradually +# define BL_PACKED_HALF(p) \ + (((p) & 0xfefefefe) >> 1) +# define BL_SUM(p1, p2) \ + (BL_PACKED_HALF(p1) + BL_PACKED_HALF(p2)) +# define BL_HALF_ERR 0x01010101 +# define BL_SUM_WERR(p1, p2) \ + (BL_PACKED_HALF(p1) + BL_PACKED_HALF(p2) + BL_HALF_ERR) + + Uint32 sum1111, sum1331, sum3113; + + // cache p[0] + 3*(p[1] + p[2]) + p[3] in sum1331 + // cache p[1] + 3*(p[0] + p[3]) + p[2] in sum3113 + sum1331 = BL_SUM (row0[1], row1[0]); + sum3113 = BL_SUM (row0[0], row1[1]); + + // cache p[0] + p[1] + p[2] + p[3] in sum1111 + sum1111 = BL_SUM_WERR (sum1331, sum3113); + + sum1331 = BL_SUM_WERR (sum1331, sum1111); + sum1331 = BL_PACKED_HALF (sum1331); + sum3113 = BL_SUM_WERR (sum3113, sum1111); + sum3113 = BL_PACKED_HALF (sum3113); + + // pixel 0 math -- (9*p[0] + 3*(p[1] + p[2]) + p[3]) / 16 + dst_p[0] = BL_PACKED_HALF (row0[0]) + sum1331; + + // pixel 1 math -- (9*p[1] + 3*(p[0] + p[3]) + p[2]) / 16 + dst_p[1] = BL_PACKED_HALF (row0[1]) + sum3113; + + // pixel 2 math -- (9*p[2] + 3*(p[0] + p[3]) + p[1]) / 16 + dst_p[dlen] = BL_PACKED_HALF (row1[0]) + sum3113; + + // pixel 3 math -- (9*p[3] + 3*(p[1] + p[2]) + p[0]) / 16 + dst_p[dlen + 1] = BL_PACKED_HALF (row1[1]) + sum1331; + +# undef BL_PACKED_HALF +# undef BL_SUM +# undef BL_HALF_ERR +# undef BL_SUM_WERR +} + +#endif /* SCALEINT_H_ */ diff --git a/src/libs/graphics/sdl/scalemmx.h b/src/libs/graphics/sdl/scalemmx.h new file mode 100644 index 0000000..69c83fe --- /dev/null +++ b/src/libs/graphics/sdl/scalemmx.h @@ -0,0 +1,793 @@ +/* + * Copyright (C) 2005 Alex Volkov (codepro@usa.net) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef SCALEMMX_H_ +#define SCALEMMX_H_ + +#if !defined(SCALE_) +# error Please define SCALE_(name) before including scalemmx.h +#endif + +#if !defined(MSVC_ASM) && !defined(GCC_ASM) +# error Please define target assembler (MSVC_ASM, GCC_ASM) before including scalemmx.h +#endif + +// MMX defaults (no Format param) +#undef SCALE_CMPRGB +#define SCALE_CMPRGB(p1, p2) \ + SCALE_(GetRGBDelta) (p1, p2) + +#undef SCALE_TOYUV +#define SCALE_TOYUV(p) \ + SCALE_(RGBtoYUV) (p) + +#undef SCALE_CMPYUV +#define SCALE_CMPYUV(p1, p2, toler) \ + SCALE_(CmpYUV) (p1, p2, toler) + +#undef SCALE_GETY +#define SCALE_GETY(p) \ + SCALE_(GetPixY) (p) + +// MMX transformation multipliers +extern Uint64 mmx_888to555_mult; +extern Uint64 mmx_Y_mult; +extern Uint64 mmx_U_mult; +extern Uint64 mmx_V_mult; +extern Uint64 mmx_YUV_threshold; + +#define USE_YUV_LOOKUP + +#if defined(MSVC_ASM) +// MSVC inline assembly versions + +#if defined(USE_MOVNTQ) +# define MOVNTQ(addr, val) movntq [addr], val +#else +# define MOVNTQ(addr, val) movq [addr], val +#endif + +#if USE_PREFETCH == INTEL_PREFETCH +// using Intel SSE non-temporal prefetch +# define PREFETCH(addr) prefetchnta [addr] +# define HAVE_PREFETCH +#elif USE_PREFETCH == AMD_PREFETCH +// using AMD 3DNOW! prefetch +# define PREFETCH(addr) prefetch [addr] +# define HAVE_PREFETCH +#else +// no prefetch -- too bad for poor MMX-only souls +# define PREFETCH(addr) +# undef HAVE_PREFETCH +#endif + +#if defined(_MSC_VER) && (_MSC_VER >= 1300) +# pragma warning( disable : 4799 ) +#endif + +static inline void +SCALE_(PlatInit) (void) +{ + __asm + { + // mm0 will be kept == 0 throughout + // 0 is needed for bytes->words unpack instructions + pxor mm0, mm0 + } +} + +static inline void +SCALE_(PlatDone) (void) +{ + // finish with MMX registers and yield them to FPU + __asm + { + emms + } +} + +#if defined(HAVE_PREFETCH) +static inline void +SCALE_(Prefetch) (const void* p) +{ + __asm + { + mov eax, p + PREFETCH (eax) + } +} + +#else /* Not HAVE_PREFETCH */ + +static inline void +SCALE_(Prefetch) (const void* p) +{ + (void)p; // silence compiler + /* no-op */ +} + +#endif /* HAVE_PREFETCH */ + +// compute the RGB distance squared between 2 pixels +static inline int +SCALE_(GetRGBDelta) (Uint32 pix1, Uint32 pix2) +{ + __asm + { + // load pixels + movd mm1, pix1 + punpcklbw mm1, mm0 + movd mm2, pix2 + punpcklbw mm2, mm0 + // get the difference between RGBA components + psubw mm1, mm2 + // squared and sumed + pmaddwd mm1, mm1 + // finish suming the squares + movq mm2, mm1 + punpckhdq mm2, mm0 + paddd mm1, mm2 + // store result + movd eax, mm1 + } +} + +// retrieve the Y (intensity) component of pixel's YUV +static inline int +SCALE_(GetPixY) (Uint32 pix) +{ + __asm + { + // load pixel + movd mm1, pix + punpcklbw mm1, mm0 + // process + pmaddwd mm1, mmx_Y_mult // RGB * Yvec + movq mm2, mm1 // finish suming + punpckhdq mm2, mm0 // ditto + paddd mm1, mm2 // ditto + // store result + movd eax, mm1 + shr eax, 14 + } +} + +#ifdef USE_YUV_LOOKUP + +// convert pixel RGB vector into YUV representation vector +static inline YUV_VECTOR +SCALE_(RGBtoYUV) (Uint32 pix) +{ + __asm + { + // convert RGB888 to 555 + movd mm1, pix + punpcklbw mm1, mm0 + psrlw mm1, 3 // 8->5 bit + pmaddwd mm1, mmx_888to555_mult // shuffle into the right channel order + movq mm2, mm1 // finish shuffling + punpckhdq mm2, mm0 // ditto + por mm1, mm2 // ditto + + // lookup the YUV vector + movd eax, mm1 + mov eax, [RGB15_to_YUV + eax * 4] + } +} + +// compare 2 pixels with respect to their YUV representations +// tolerance set by toler arg +// returns true: close; false: distant (-gt toler) +static inline bool +SCALE_(CmpYUV) (Uint32 pix1, Uint32 pix2, int toler) +{ + __asm + { + // convert RGB888 to 555 + movd mm1, pix1 + punpcklbw mm1, mm0 + psrlw mm1, 3 // 8->5 bit + movd mm3, pix2 + punpcklbw mm3, mm0 + psrlw mm3, 3 // 8->5 bit + pmaddwd mm1, mmx_888to555_mult // shuffle into the right channel order + movq mm2, mm1 // finish shuffling + pmaddwd mm3, mmx_888to555_mult // shuffle into the right channel order + movq mm4, mm3 // finish shuffling + punpckhdq mm2, mm0 // ditto + por mm1, mm2 // ditto + punpckhdq mm4, mm0 // ditto + por mm3, mm4 // ditto + + // lookup the YUV vector + movd eax, mm1 + movd edx, mm3 + movd mm1, [RGB15_to_YUV + eax * 4] + movq mm4, mm1 + movd mm2, [RGB15_to_YUV + edx * 4] + + // get abs difference between YUV components +#ifdef USE_PSADBW + // we can use PSADBW and save us some grief + psadbw mm1, mm2 + movd edx, mm1 +#else + // no PSADBW -- have to do it the hard way + psubusb mm1, mm2 + psubusb mm2, mm4 + por mm1, mm2 + + // sum the differences + // XXX: technically, this produces a MAX diff of 510 + // but we do not need anything bigger, currently + movq mm2, mm1 + psrlq mm2, 8 + paddusb mm1, mm2 + psrlq mm2, 8 + paddusb mm1, mm2 + movd edx, mm1 + and edx, 0xff +#endif /* USE_PSADBW */ + xor eax, eax + shl edx, 1 + cmp edx, toler + // store result + setle al + } +} + +#else /* Not USE_YUV_LOOKUP */ + +// convert pixel RGB vector into YUV representation vector +static inline YUV_VECTOR +SCALE_(RGBtoYUV) (Uint32 pix) +{ + __asm + { + movd mm1, pix + punpcklbw mm1, mm0 + + movq mm2, mm1 + + // Y vector multiply + pmaddwd mm1, mmx_Y_mult + movq mm4, mm1 + punpckhdq mm4, mm0 + punpckldq mm1, mm0 // clear out the high dword + paddd mm1, mm4 + psrad mm1, 15 + + movq mm3, mm2 + + // U vector multiply + pmaddwd mm2, mmx_U_mult + psrad mm2, 10 + + // V vector multiply + pmaddwd mm3, mmx_V_mult + psrad mm3, 10 + + // load (1|1|1|1) into mm4 + pcmpeqw mm4, mm4 + psrlw mm4, 15 + + packssdw mm3, mm2 + pmaddwd mm3, mm4 + psrad mm3, 5 + + // load (64|64) into mm4 + punpcklwd mm4, mm0 + pslld mm4, 6 + paddd mm3, mm4 + + packssdw mm3, mm1 + packuswb mm3, mm0 + + movd eax, mm3 + } +} + +// compare 2 pixels with respect to their YUV representations +// tolerance set by toler arg +// returns true: close; false: distant (-gt toler) +static inline bool +SCALE_(CmpYUV) (Uint32 pix1, Uint32 pix2, int toler) +{ + __asm + { + movd mm1, pix1 + punpcklbw mm1, mm0 + movd mm2, pix2 + punpcklbw mm2, mm0 + + psubw mm1, mm2 + movq mm2, mm1 + + // Y vector multiply + pmaddwd mm1, mmx_Y_mult + movq mm4, mm1 + punpckhdq mm4, mm0 + paddd mm1, mm4 + // abs() + movq mm4, mm1 + psrad mm4, 31 + pxor mm4, mm1 + psubd mm1, mm4 + + movq mm3, mm2 + + // U vector multiply + pmaddwd mm2, mmx_U_mult + movq mm4, mm2 + punpckhdq mm4, mm0 + paddd mm2, mm4 + // abs() + movq mm4, mm2 + psrad mm4, 31 + pxor mm4, mm2 + psubd mm2, mm4 + + paddd mm1, mm2 + + // V vector multiply + pmaddwd mm3, mmx_V_mult + movq mm4, mm3 + punpckhdq mm3, mm0 + paddd mm3, mm4 + // abs() + movq mm4, mm3 + psrad mm4, 31 + pxor mm4, mm3 + psubd mm3, mm4 + + paddd mm1, mm3 + + movd edx, mm1 + xor eax, eax + shr edx, 14 + cmp edx, toler + // store result + setle al + } +} + +#endif /* USE_YUV_LOOKUP */ + +// Check if 2 pixels are different with respect to their +// YUV representations +// returns 0: close; ~0: distant +static inline int +SCALE_(DiffYUV) (Uint32 yuv1, Uint32 yuv2) +{ + __asm + { + // load YUV pixels + movd mm1, yuv1 + movq mm4, mm1 + movd mm2, yuv2 + // abs difference between channels + psubusb mm1, mm2 + psubusb mm2, mm4 + por mm1, mm2 + // compare to threshold + psubusb mm1, mmx_YUV_threshold + + movd edx, mm1 + // transform eax to 0 or ~0 + xor eax, eax + or edx, edx + setz al + dec eax + } +} + +// bilinear weighted blend of four pixels +// MSVC asm version +static inline void +SCALE_(Blend_bilinear) (const Uint32* row0, const Uint32* row1, + Uint32* dst_p, Uint32 dlen) +{ + __asm + { + // EL0: setup vars + mov ebx, row0 // EL0 + + // EL0: load pixels + movq mm1, [ebx] // EL0 + movq mm2, mm1 // EL0: p[1] -> mm2 + PREFETCH (ebx + 0x80) + punpckhbw mm2, mm0 // EL0: p[1] -> mm2 + mov ebx, row1 + punpcklbw mm1, mm0 // EL0: p[0] -> mm1 + movq mm3, [ebx] + movq mm4, mm3 // EL0: p[3] -> mm4 + movq mm6, mm2 // EL1.1: p[1] -> mm6 + PREFETCH (ebx + 0x80) + punpcklbw mm3, mm0 // EL0: p[2] -> mm3 + movq mm5, mm1 // EL1.1: p[0] -> mm5 + punpckhbw mm4, mm0 // EL0: p[3] -> mm4 + + mov edi, dst_p // EL0 + + // EL1: cache p[0] + 3*(p[1] + p[2]) + p[3] in mm6 + paddw mm6, mm3 // EL1.2: p[1] + p[2] -> mm6 + // EL1: cache p[0] + p[1] + p[2] + p[3] in mm7 + movq mm7, mm6 // EL1.3: p[1] + p[2] -> mm7 + // EL1: cache p[1] + 3*(p[0] + p[3]) + p[2] in mm5 + paddw mm5, mm4 // EL1.2: p[0] + p[3] -> mm5 + psllw mm6, 1 // EL1.4: 2*(p[1] + p[2]) -> mm6 + paddw mm7, mm5 // EL1.4: sum(p[]) -> mm7 + psllw mm5, 1 // EL1.5: 2*(p[0] + p[3]) -> mm5 + paddw mm6, mm7 // EL1.5: p[0] + 3*(p[1] + p[2]) + p[3] -> mm6 + paddw mm5, mm7 // EL1.6: p[1] + 3*(p[0] + p[3]) + p[2] -> mm5 + + // EL2: pixel 0 math -- (9*p[0] + 3*(p[1] + p[2]) + p[3]) / 16 + psllw mm1, 3 // EL2.1: 8*p[0] -> mm1 + paddw mm1, mm6 // EL2.2: 9*p[0] + 3*(p[1] + p[2]) + p[3] -> mm1 + psrlw mm1, 4 // EL2.3: sum[0]/16 -> mm1 + + mov edx, dlen // EL0 + + // EL3: pixel 1 math -- (9*p[1] + 3*(p[0] + p[3]) + p[2]) / 16 + psllw mm2, 3 // EL3.1: 8*p[1] -> mm2 + paddw mm2, mm5 // EL3.2: 9*p[1] + 3*(p[0] + p[3]) + p[2] -> mm2 + psrlw mm2, 4 // EL3.3: sum[1]/16 -> mm5 + + // EL2/3: store pixels 0 & 1 + packuswb mm1, mm2 // EL2/3: pack into bytes + MOVNTQ (edi, mm1) // EL2/3: store 2 pixels + + // EL4: pixel 2 math -- (9*p[2] + 3*(p[0] + p[3]) + p[1]) / 16 + psllw mm3, 3 // EL4.1: 8*p[2] -> mm3 + paddw mm3, mm5 // EL4.2: 9*p[2] + 3*(p[0] + p[3]) + p[1] -> mm3 + psrlw mm3, 4 // EL4.3: sum[2]/16 -> mm3 + + // EL5: pixel 3 math -- (9*p[3] + 3*(p[1] + p[2]) + p[0]) / 16 + psllw mm4, 3 // EL5.1: 8*p[3] -> mm4 + paddw mm4, mm6 // EL5.2: 9*p[3] + 3*(p[1] + p[2]) + p[0] -> mm4 + psrlw mm4, 4 // EL5.3: sum[3]/16 -> mm4 + + // EL4/5: store pixels 2 & 3 + packuswb mm3, mm4 // EL4/5: pack into bytes + MOVNTQ (edi + edx*4, mm3) // EL4/5: store 2 pixels + } +} +// End MSVC_ASM + +#elif defined(GCC_ASM) +// GCC inline assembly versions + +#if defined(USE_MOVNTQ) +# define MOVNTQ(val, addr) "movntq " #val "," #addr +#else +# define MOVNTQ(val, addr) "movq " #val "," #addr +#endif + +#if USE_PREFETCH == INTEL_PREFETCH +// using Intel SSE non-temporal prefetch +# define PREFETCH(addr) "prefetchnta " #addr +#elif USE_PREFETCH == AMD_PREFETCH +// using AMD 3DNOW! prefetch +# define PREFETCH(addr) "prefetch " #addr +#else +// no prefetch -- too bad for poor MMX-only souls +# define PREFETCH(addr) +#endif + +#if defined(__x86_64__) +# define A_REG "rax" +# define D_REG "rdx" +# define CLR_UPPER32(r) "xor " "%%" r "," "%%" r +#else +# define A_REG "eax" +# define D_REG "edx" +# define CLR_UPPER32(r) +#endif + +static inline void +SCALE_(PlatInit) (void) +{ + __asm__ ( + // mm0 will be kept == 0 throughout + // 0 is needed for bytes->words unpack instructions + "pxor %%mm0, %%mm0 \n\t" + + : /* nothing */ + : /* nothing */ + ); +} + +static inline void +SCALE_(PlatDone) (void) +{ + // finish with MMX registers and yield them to FPU + __asm__ ( + "emms \n\t" + : /* nothing */ : /* nothing */ + ); +} + +static inline void +SCALE_(Prefetch) (const void* p) +{ + __asm__ __volatile__ ("" PREFETCH (%0) : /*nothing*/ : "m" (p) ); +} + +// compute the RGB distance squared between 2 pixels +static inline int +SCALE_(GetRGBDelta) (Uint32 pix1, Uint32 pix2) +{ + int res; + + __asm__ ( + // load pixels + "movd %1, %%mm1 \n\t" + "punpcklbw %%mm0, %%mm1 \n\t" + "movd %2, %%mm2 \n\t" + "punpcklbw %%mm0, %%mm2 \n\t" + // get the difference between RGBA components + "psubw %%mm2, %%mm1 \n\t" + // squared and sumed + "pmaddwd %%mm1, %%mm1 \n\t" + // finish suming the squares + "movq %%mm1, %%mm2 \n\t" + "punpckhdq %%mm0, %%mm2 \n\t" + "paddd %%mm2, %%mm1 \n\t" + // store result + "movd %%mm1, %0 \n\t" + + : /*0*/"=rm" (res) + : /*1*/"rm" (pix1), /*2*/"rm" (pix2) + ); + + return res; +} + +// retrieve the Y (intensity) component of pixel's YUV +static inline int +SCALE_(GetPixY) (Uint32 pix) +{ + int ret; + + __asm__ ( + // load pixel + "movd %1, %%mm1 \n\t" + "punpcklbw %%mm0, %%mm1 \n\t" + // process + "pmaddwd %2, %%mm1 \n\t" // R,G,B * Yvec + "movq %%mm1, %%mm2 \n\t" // finish suming + "punpckhdq %%mm0, %%mm2 \n\t" // ditto + "paddd %%mm2, %%mm1 \n\t" // ditto + // store index + "movd %%mm1, %0 \n\t" + + : /*0*/"=r" (ret) + : /*1*/"rm" (pix), /*2*/"m" (mmx_Y_mult) + ); + return ret >> 14; +} + +#ifdef USE_YUV_LOOKUP + +// convert pixel RGB vector into YUV representation vector +static inline YUV_VECTOR +SCALE_(RGBtoYUV) (Uint32 pix) +{ + int i; + + __asm__ ( + // convert RGB888 to 555 + "movd %1, %%mm1 \n\t" + "punpcklbw %%mm0, %%mm1 \n\t" + "psrlw $3, %%mm1 \n\t" // 8->5 bit + "pmaddwd %2, %%mm1 \n\t" // shuffle into the right channel order + "movq %%mm1, %%mm2 \n\t" // finish shuffling + "punpckhdq %%mm0, %%mm2 \n\t" // ditto + "por %%mm2, %%mm1 \n\t" // ditto + "movd %%mm1, %0 \n\t" + + : /*0*/"=rm" (i) + : /*1*/"rm" (pix), /*2*/"m" (mmx_888to555_mult) + ); + return RGB15_to_YUV[i]; +} + +// compare 2 pixels with respect to their YUV representations +// tolerance set by toler arg +// returns true: close; false: distant (-gt toler) +static inline bool +SCALE_(CmpYUV) (Uint32 pix1, Uint32 pix2, int toler) +{ + int delta; + + __asm__ ( + "movd %1, %%mm1 \n\t" + "movd %2, %%mm3 \n\t" + + // convert RGB888 to 555 + // this is somewhat parallelized + "punpcklbw %%mm0, %%mm1 \n\t" + CLR_UPPER32 (A_REG) "\n\t" + "psrlw $3, %%mm1 \n\t" // 8->5 bit + "punpcklbw %%mm0, %%mm3 \n\t" + "psrlw $3, %%mm3 \n\t" // 8->5 bit + "pmaddwd %4, %%mm1 \n\t" // shuffle into the right channel order + "movq %%mm1, %%mm2 \n\t" // finish shuffling + "pmaddwd %4, %%mm3 \n\t" // shuffle into the right channel order + CLR_UPPER32 (D_REG) "\n\t" + "movq %%mm3, %%mm4 \n\t" // finish shuffling + "punpckhdq %%mm0, %%mm2 \n\t" // ditto + "por %%mm2, %%mm1 \n\t" // ditto + "punpckhdq %%mm0, %%mm4 \n\t" // ditto + "por %%mm4, %%mm3 \n\t" // ditto + + // lookup the YUV vector + "movd %%mm1, %%eax \n\t" + "movd %%mm3, %%edx \n\t" + "movd (%3, %%" A_REG ", 4), %%mm1 \n\t" + "movq %%mm1, %%mm4 \n\t" + "movd (%3, %%" D_REG ", 4), %%mm2 \n\t" + + // get abs difference between YUV components +#ifdef USE_PSADBW + // we can use PSADBW and save us some grief + "psadbw %%mm2, %%mm1 \n\t" + "movd %%mm1, %0 \n\t" +#else + // no PSADBW -- have to do it the hard way + "psubusb %%mm2, %%mm1 \n\t" + "psubusb %%mm4, %%mm2 \n\t" + "por %%mm2, %%mm1 \n\t" + + // sum the differences + // technically, this produces a MAX diff of 510 + // but we do not need anything bigger, currently + "movq %%mm1, %%mm2 \n\t" + "psrlq $8, %%mm2 \n\t" + "paddusb %%mm2, %%mm1 \n\t" + "psrlq $8, %%mm2 \n\t" + "paddusb %%mm2, %%mm1 \n\t" + // store intermediate delta + "movd %%mm1, %0 \n\t" + "andl $0xff, %0 \n\t" +#endif /* USE_PSADBW */ + : /*0*/"=rm" (delta) + : /*1*/"rm" (pix1), /*2*/"rm" (pix2), + /*3*/ "r" (RGB15_to_YUV), + /*4*/"m" (mmx_888to555_mult) + : "%" A_REG, "%" D_REG, "cc" + ); + + return (delta << 1) <= toler; +} + +#endif /* USE_YUV_LOOKUP */ + +// Check if 2 pixels are different with respect to their +// YUV representations +// returns 0: close; ~0: distant +static inline int +SCALE_(DiffYUV) (Uint32 yuv1, Uint32 yuv2) +{ + sint32 ret; + + __asm__ ( + // load YUV pixels + "movd %1, %%mm1 \n\t" + "movq %%mm1, %%mm4 \n\t" + "movd %2, %%mm2 \n\t" + // abs difference between channels + "psubusb %%mm2, %%mm1 \n\t" + "psubusb %%mm4, %%mm2 \n\t" + CLR_UPPER32(D_REG) "\n\t" + "por %%mm2, %%mm1 \n\t" + // compare to threshold + "psubusb %3, %%mm1 \n\t" + + "movd %%mm1, %%edx \n\t" + // transform eax to 0 or ~0 + "xor %%" A_REG ", %%" A_REG "\n\t" + "or %%" D_REG ", %%" D_REG "\n\t" + "setz %%al \n\t" + "dec %%" A_REG " \n\t" + + : /*0*/"=a" (ret) + : /*1*/"rm" (yuv1), /*2*/"rm" (yuv2), + /*3*/"m" (mmx_YUV_threshold) + : "%" D_REG, "cc" + ); + return ret; +} + +// Bilinear weighted blend of four pixels +// Function produces 4 blended pixels (in 2x2 matrix) and writes them +// out to the surface +// Last version +static inline void +SCALE_(Blend_bilinear) (const Uint32* row0, const Uint32* row1, + Uint32* dst_p, Uint32 dlen) +{ + __asm__ ( + // EL0: load pixels + "movq %0, %%mm1 \n\t" // EL0 + "movq %%mm1, %%mm2 \n\t" // EL0: p[1] -> mm2 + PREFETCH (0x80%0) "\n\t" + "punpckhbw %%mm0, %%mm2 \n\t" // EL0: p[1] -> mm2 + "punpcklbw %%mm0, %%mm1 \n\t" // EL0: p[0] -> mm1 + "movq %1, %%mm3 \n\t" + "movq %%mm3, %%mm4 \n\t" // EL0: p[3] -> mm4 + "movq %%mm2, %%mm6 \n\t" // EL1.1: p[1] -> mm6 + PREFETCH (0x80%1) "\n\t" + "punpcklbw %%mm0, %%mm3 \n\t" // EL0: p[2] -> mm3 + "movq %%mm1, %%mm5 \n\t" // EL1.1: p[0] -> mm5 + "punpckhbw %%mm0, %%mm4 \n\t" // EL0: p[3] -> mm4 + + // EL1: cache p[0] + 3*(p[1] + p[2]) + p[3] in mm6 + "paddw %%mm3, %%mm6 \n\t" // EL1.2: p[1] + p[2] -> mm6 + // EL1: cache p[0] + p[1] + p[2] + p[3] in mm7 + "movq %%mm6, %%mm7 \n\t" // EL1.3: p[1] + p[2] -> mm7 + // EL1: cache p[1] + 3*(p[0] + p[3]) + p[2] in mm5 + "paddw %%mm4, %%mm5 \n\t" // EL1.2: p[0] + p[3] -> mm5 + "psllw $1, %%mm6 \n\t" // EL1.4: 2*(p[1] + p[2]) -> mm6 + "paddw %%mm5, %%mm7 \n\t" // EL1.4: sum(p[]) -> mm7 + "psllw $1, %%mm5 \n\t" // EL1.5: 2*(p[0] + p[3]) -> mm5 + "paddw %%mm7, %%mm6 \n\t" // EL1.5: p[0] + 3*(p[1] + p[2]) + p[3] -> mm6 + "paddw %%mm7, %%mm5 \n\t" // EL1.6: p[1] + 3*(p[0] + p[3]) + p[2] -> mm5 + + // EL2: pixel 0 math -- (9*p[0] + 3*(p[1] + p[2]) + p[3]) / 16 + "psllw $3, %%mm1 \n\t" // EL2.1: 8*p[0] -> mm1 + "paddw %%mm6, %%mm1 \n\t" // EL2.2: 9*p[0] + 3*(p[1] + p[2]) + p[3] -> mm1 + "psrlw $4, %%mm1 \n\t" // EL2.3: sum[0]/16 -> mm1 + + // EL3: pixel 1 math -- (9*p[1] + 3*(p[0] + p[3]) + p[2]) / 16 + "psllw $3, %%mm2 \n\t" // EL3.1: 8*p[1] -> mm2 + "paddw %%mm5, %%mm2 \n\t" // EL3.2: 9*p[1] + 3*(p[0] + p[3]) + p[2] -> mm5 + "psrlw $4, %%mm2 \n\t" // EL3.3: sum[1]/16 -> mm5 + + // EL2/4: store pixels 0 & 1 + "packuswb %%mm2, %%mm1 \n\t" // EL2/4: pack into bytes + MOVNTQ (%%mm1, (%2)) "\n\t" // EL2/4: store 2 pixels + + // EL4: pixel 2 math -- (9*p[2] + 3*(p[0] + p[3]) + p[1]) / 16 + "psllw $3, %%mm3 \n\t" // EL4.1: 8*p[2] -> mm3 + "paddw %%mm5, %%mm3 \n\t" // EL4.2: 9*p[2] + 3*(p[0] + p[3]) + p[1] -> mm3 + "psrlw $4, %%mm3 \n\t" // EL4.3: sum[2]/16 -> mm3 + + // EL5: pixel 3 math -- (9*p[3] + 3*(p[1] + p[2]) + p[0]) / 16 + "psllw $3, %%mm4 \n\t" // EL5.1: 8*p[3] -> mm4 + "paddw %%mm6, %%mm4 \n\t" // EL5.2: 9*p[3] + 3*(p[1] + p[2]) + p[0] -> mm4 + "psrlw $4, %%mm4 \n\t" // EL5.3: sum[3]/16 -> mm4 + + // EL4/5: store pixels 2 & 3 + "packuswb %%mm4, %%mm3 \n\t" // EL4/5: pack into bytes + MOVNTQ (%%mm3, (%2,%3,4)) "\n\t" // EL4/5: store 2 pixels + + : /* nothing */ + : /*0*/"m" (*row0), /*1*/"m" (*row1), /*2*/"r" (dst_p), + /*3*/"r" ((unsigned long)dlen) /* 'long' is for proper reg alloc on amd64 */ + : "memory" + ); +} + +#undef A_REG +#undef D_REG +#undef CLR_UPPER32 + +#endif // GCC_ASM + +#endif /* SCALEMMX_H_ */ diff --git a/src/libs/graphics/sdl/scalers.c b/src/libs/graphics/sdl/scalers.c new file mode 100644 index 0000000..751dae3 --- /dev/null +++ b/src/libs/graphics/sdl/scalers.c @@ -0,0 +1,289 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "types.h" +#include "libs/graphics/sdl/sdl_common.h" +#include "libs/platform.h" +#include "libs/log.h" +#include "scalers.h" +#include "scaleint.h" +#include "2xscalers.h" +#ifdef USE_PLATFORM_ACCEL +# ifndef __APPLE__ + // MacOS X framework has no SDL_cpuinfo.h for some reason +# include SDL_INCLUDE(SDL_cpuinfo.h) +# endif +# ifdef MMX_ASM +# include "2xscalers_mmx.h" +# endif /* MMX_ASM */ +#endif /* USE_PLATFORM_ACCEL */ + +#if SDL_MAJOR_VERSION == 1 +#define SDL_HasMMX SDL_HasMMXExt +#endif + +typedef enum +{ + SCALEPLAT_NULL = PLATFORM_NULL, + SCALEPLAT_C = PLATFORM_C, + SCALEPLAT_MMX = PLATFORM_MMX, + SCALEPLAT_SSE = PLATFORM_SSE, + SCALEPLAT_3DNOW = PLATFORM_3DNOW, + SCALEPLAT_ALTIVEC = PLATFORM_ALTIVEC, + + SCALEPLAT_C_RGBA, + SCALEPLAT_C_BGRA, + SCALEPLAT_C_ARGB, + SCALEPLAT_C_ABGR, + +} Scale_PlatType_t; + + +// RGB -> YUV transformation +// the RGB vector is multiplied by the transformation matrix +// to get the YUV vector +#if 0 +// original table -- not used +const int YUV_matrix[3][3] = +{ + /* Y U V */ + /* R */ {0.2989, -0.1687, 0.5000}, + /* G */ {0.5867, -0.3312, -0.4183}, + /* B */ {0.1144, 0.5000, -0.0816} +}; +#else +// scaled up by a 2^14 factor, with Y doubled +const int YUV_matrix[3][3] = +{ + /* Y U V */ + /* R */ { 9794, -2764, 8192}, + /* G */ {19224, -5428, -6853}, + /* B */ { 3749, 8192, -1339} +}; +#endif + +// pre-computed transformations for 8 bits per channel +int RGB_to_YUV[/*RGB*/ 3][/*YUV*/ 3][ /*mult-res*/ 256]; +sint16 dRGB_to_dYUV[/*RGB*/ 3][/*YUV*/ 3][ /*mult-res*/ 512]; + +// pre-computed transformations for RGB555 +YUV_VECTOR RGB15_to_YUV[0x8000]; + +PLATFORM_TYPE force_platform = PLATFORM_NULL; +Scale_PlatType_t Scale_Platform = SCALEPLAT_NULL; + + +// pre-compute the RGB->YUV transformations +void +Scale_Init (void) +{ + int i1, i2, i3; + + for (i1 = 0; i1 < 3; i1++) // enum R,G,B + for (i2 = 0; i2 < 3; i2++) // enum Y,U,V + for (i3 = 0; i3 < 256; i3++) // enum possible channel vals + { + RGB_to_YUV[i1][i2][i3] = + (YUV_matrix[i1][i2] * i3) >> 14; + } + + for (i1 = 0; i1 < 3; i1++) // enum R,G,B + for (i2 = 0; i2 < 3; i2++) // enum Y,U,V + for (i3 = -255; i3 < 256; i3++) // enum possible channel delta vals + { + dRGB_to_dYUV[i1][i2][i3 + 255] = + (YUV_matrix[i1][i2] * i3) >> 14; + } + + for (i1 = 0; i1 < 32; ++i1) + for (i2 = 0; i2 < 32; ++i2) + for (i3 = 0; i3 < 32; ++i3) + { + int y, u, v; + // adding upper bits halved for error correction + int r = (i1 << 3) | (i1 >> 3); + int g = (i2 << 3) | (i2 >> 3); + int b = (i3 << 3) | (i3 >> 3); + + y = ( r * YUV_matrix[YUV_XFORM_R][YUV_XFORM_Y] + + g * YUV_matrix[YUV_XFORM_G][YUV_XFORM_Y] + + b * YUV_matrix[YUV_XFORM_B][YUV_XFORM_Y] + ) >> 15; // we dont need Y doubled, need Y to fit 8 bits + + // U and V are half the importance of Y + u = 64+(( r * YUV_matrix[YUV_XFORM_R][YUV_XFORM_U] + + g * YUV_matrix[YUV_XFORM_G][YUV_XFORM_U] + + b * YUV_matrix[YUV_XFORM_B][YUV_XFORM_U] + ) >> 15); // halved + + v = 64+(( r * YUV_matrix[YUV_XFORM_R][YUV_XFORM_V] + + g * YUV_matrix[YUV_XFORM_G][YUV_XFORM_V] + + b * YUV_matrix[YUV_XFORM_B][YUV_XFORM_V] + ) >> 15); // halved + + RGB15_to_YUV[(i1 << 10) | (i2 << 5) | i3] = (y << 16) | (u << 8) | v; + } +} + + +// expands the given rectangle in all directions by 'expansion' +// guarded by 'limits' +void +Scale_ExpandRect (SDL_Rect* rect, int expansion, const SDL_Rect* limits) +{ + if (rect->x - expansion >= limits->x) + { + rect->w += expansion; + rect->x -= expansion; + } + else + { + rect->w += rect->x - limits->x; + rect->x = limits->x; + } + + if (rect->y - expansion >= limits->y) + { + rect->h += expansion; + rect->y -= expansion; + } + else + { + rect->h += rect->y - limits->y; + rect->y = limits->y; + } + + if (rect->x + rect->w + expansion <= limits->w) + rect->w += expansion; + else + rect->w = limits->w - rect->x; + + if (rect->y + rect->h + expansion <= limits->h) + rect->h += expansion; + else + rect->h = limits->h - rect->y; +} + + +// Platform+Scaler function lookups + +typedef struct +{ + Scale_PlatType_t platform; + const Scale_FuncDef_t* funcdefs; +} Scale_PlatDef_t; + + +static const Scale_PlatDef_t +Scale_PlatDefs[] = +{ +#if defined(MMX_ASM) + {SCALEPLAT_SSE, Scale_SSE_Functions}, + {SCALEPLAT_3DNOW, Scale_3DNow_Functions}, + {SCALEPLAT_MMX, Scale_MMX_Functions}, +#endif /* MMX_ASM */ + // Default + {SCALEPLAT_NULL, Scale_C_Functions} +}; + + +TFB_ScaleFunc +Scale_PrepPlatform (int flags, const SDL_PixelFormat* fmt) +{ + const Scale_PlatDef_t* pdef; + const Scale_FuncDef_t* fdef; + + (void)flags; + + Scale_Platform = SCALEPLAT_NULL; + + // first match wins + // add better platform techs to the top +#ifdef MMX_ASM + if ( (!force_platform && (SDL_HasSSE () || SDL_HasMMX ())) + || force_platform == PLATFORM_SSE) + { + log_add (log_Info, "Screen scalers are using SSE/MMX-Ext/MMX code"); + Scale_Platform = SCALEPLAT_SSE; + + Scale_SSE_PrepPlatform (fmt); + } + else + if ( (!force_platform && SDL_HasAltiVec ()) + || force_platform == PLATFORM_ALTIVEC) + { + log_add (log_Info, "Screen scalers would use AltiVec code " + "if someone actually wrote it"); + //Scale_Platform = SCALEPLAT_ALTIVEC; + } + else + if ( (!force_platform && SDL_Has3DNow ()) + || force_platform == PLATFORM_3DNOW) + { + log_add (log_Info, "Screen scalers are using 3DNow/MMX code"); + Scale_Platform = SCALEPLAT_3DNOW; + + Scale_3DNow_PrepPlatform (fmt); + } + else + if ( (!force_platform && SDL_HasMMX ()) + || force_platform == PLATFORM_MMX) + { + log_add (log_Info, "Screen scalers are using MMX code"); + Scale_Platform = SCALEPLAT_MMX; + + Scale_MMX_PrepPlatform (fmt); + } +#endif + + if (Scale_Platform == SCALEPLAT_NULL) + { // Plain C versions + if (fmt->Rmask == 0xff000000 && fmt->Bmask == 0x0000ff00) + Scale_Platform = SCALEPLAT_C_RGBA; + else if (fmt->Rmask == 0x00ff0000 && fmt->Bmask == 0x000000ff) + Scale_Platform = SCALEPLAT_C_ARGB; + else if (fmt->Rmask == 0x0000ff00 && fmt->Bmask == 0xff000000) + Scale_Platform = SCALEPLAT_C_BGRA; + else if (fmt->Rmask == 0x000000ff && fmt->Bmask == 0x00ff0000) + Scale_Platform = SCALEPLAT_C_ABGR; + else + { // use slowest default + log_add (log_Warning, "Scale_PrepPlatform(): unknown masks " + "(Red %08x, Blue %08x)", fmt->Rmask, fmt->Bmask); + Scale_Platform = SCALEPLAT_C; + } + + if (Scale_Platform == SCALEPLAT_C) + log_add (log_Info, "Screen scalers are using slow generic C code"); + else + log_add (log_Info, "Screen scalers are using optimized C code"); + } + + // Lookup the scaling function + // First find the right platform + for (pdef = Scale_PlatDefs; + pdef->platform != Scale_Platform && pdef->platform != SCALEPLAT_NULL; + ++pdef) + ; + // Next find the right function + for (fdef = pdef->funcdefs; + (flags & fdef->flag) != fdef->flag; + ++fdef) + ; + + return fdef->func; +} + diff --git a/src/libs/graphics/sdl/scalers.h b/src/libs/graphics/sdl/scalers.h new file mode 100644 index 0000000..cd36fe5 --- /dev/null +++ b/src/libs/graphics/sdl/scalers.h @@ -0,0 +1,27 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef SCALERS_H_ +#define SCALERS_H_ + +void Scale_Init (void); + +typedef void (* TFB_ScaleFunc) (SDL_Surface *src, SDL_Surface *dst, + SDL_Rect *r); + +TFB_ScaleFunc Scale_PrepPlatform (int flags, const SDL_PixelFormat* fmt); + +#endif /* SCALERS_H_ */ diff --git a/src/libs/graphics/sdl/sdl1_common.c b/src/libs/graphics/sdl/sdl1_common.c new file mode 100644 index 0000000..5c675e1 --- /dev/null +++ b/src/libs/graphics/sdl/sdl1_common.c @@ -0,0 +1,247 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "sdl_common.h" +#include "opengl.h" +#include "pure.h" +#include "primitives.h" +#include "options.h" +#include "uqmversion.h" +#include "libs/graphics/drawcmd.h" +#include "libs/graphics/dcqueue.h" +#include "libs/graphics/cmap.h" +#include "libs/input/sdl/input.h" + // for ProcessInputEvent() +#include "libs/graphics/bbox.h" +#include "port.h" +#include "libs/uio.h" +#include "libs/log.h" +#include "libs/memlib.h" +#include "libs/vidlib.h" + +#if SDL_MAJOR_VERSION == 1 + +static void TFB_PreQuit (void); + +void +TFB_PreInit (void) +{ + log_add (log_Info, "Initializing base SDL functionality."); + log_add (log_Info, "Using SDL version %d.%d.%d (compiled with " + "%d.%d.%d)", SDL_Linked_Version ()->major, + SDL_Linked_Version ()->minor, SDL_Linked_Version ()->patch, + SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_PATCHLEVEL); +#if 0 + if (SDL_Linked_Version ()->major != SDL_MAJOR_VERSION || + SDL_Linked_Version ()->minor != SDL_MINOR_VERSION || + SDL_Linked_Version ()->patch != SDL_PATCHLEVEL) { + log_add (log_Warning, "The used SDL library is not the same version " + "as the one used to compile The Ur-Quan Masters with! " + "If you experience any crashes, this would be an excellent " + "suspect."); + } +#endif + + if ((SDL_Init (SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE) == -1)) + { + log_add (log_Fatal, "Could not initialize SDL: %s.", SDL_GetError ()); + exit (EXIT_FAILURE); + } + + atexit (TFB_PreQuit); +} + +static void +TFB_PreQuit (void) +{ + SDL_Quit (); +} + +int +TFB_ReInitGraphics (int driver, int flags, int width, int height) +{ + int result; + int togglefullscreen = 0; + char caption[200]; + + if (GfxFlags == (flags ^ TFB_GFXFLAGS_FULLSCREEN) && + driver == GraphicsDriver && + width == ScreenWidthActual && height == ScreenHeightActual) + { + togglefullscreen = 1; + } + + GfxFlags = flags; + + if (driver == TFB_GFXDRIVER_SDL_OPENGL) + { +#ifdef HAVE_OPENGL + result = TFB_GL_ConfigureVideo (driver, flags, width, height, + togglefullscreen); +#else + driver = TFB_GFXDRIVER_SDL_PURE; + log_add (log_Warning, "OpenGL support not compiled in," + " so using pure SDL driver"); + result = TFB_Pure_ConfigureVideo (driver, flags, width, height, + togglefullscreen); +#endif + } + else + { + result = TFB_Pure_ConfigureVideo (driver, flags, width, height, + togglefullscreen); + } + + sprintf (caption, "The Ur-Quan Masters v%d.%d.%d%s", + UQM_MAJOR_VERSION, UQM_MINOR_VERSION, + UQM_PATCH_VERSION, UQM_EXTRA_VERSION); + SDL_WM_SetCaption (caption, NULL); + + if (flags & TFB_GFXFLAGS_FULLSCREEN) + SDL_ShowCursor (SDL_DISABLE); + else + SDL_ShowCursor (SDL_ENABLE); + + return result; +} + +bool +TFB_SetGamma (float gamma) +{ + return (SDL_SetGamma (gamma, gamma, gamma) == 0); +} + +int +TFB_HasSurfaceAlphaMod (SDL_Surface *surface) +{ + if (!surface) + { + return 0; + } + return (surface->flags & SDL_SRCALPHA) ? 1 : 0; +} + +int +TFB_GetSurfaceAlphaMod (SDL_Surface *surface, Uint8 *alpha) +{ + if (!surface || !surface->format || !alpha) + { + return -1; + } + if (surface->flags & SDL_SRCALPHA) + { + *alpha = surface->format->alpha; + } + else + { + *alpha = 255; + } + return 0; +} + +int +TFB_SetSurfaceAlphaMod (SDL_Surface *surface, Uint8 alpha) +{ + if (!surface) + { + return -1; + } + return SDL_SetAlpha (surface, SDL_SRCALPHA, alpha); +} + +int +TFB_DisableSurfaceAlphaMod (SDL_Surface *surface) +{ + if (!surface) + { + return -1; + } + return SDL_SetAlpha (surface, 0, 255); +} + +int +TFB_GetColorKey (SDL_Surface *surface, Uint32 *key) +{ + if (surface && surface->format && key && + (surface->flags & SDL_SRCCOLORKEY)) + { + *key = surface->format->colorkey; + return 0; + } + return -1; +} + +int +TFB_SetColorKey (SDL_Surface *surface, Uint32 key, int rleaccel) +{ + if (!surface) + { + return -1; + } + return SDL_SetColorKey (surface, SDL_SRCCOLORKEY | (rleaccel ? SDL_RLEACCEL : 0), key); +} + +int +TFB_DisableColorKey (SDL_Surface *surface) +{ + if (!surface) + { + return -1; + } + return SDL_SetColorKey (surface, 0, 0); +} + +int +TFB_SetColors (SDL_Surface *surface, SDL_Color *colors, int firstcolor, int ncolors) +{ + return SDL_SetColors (surface, colors, firstcolor, ncolors); +} + +int +TFB_SupportsHardwareScaling (void) +{ +#ifdef HAVE_OPENGL + return 1; +#else + return 0; +#endif +} + +static SDL_Surface * +Create_Screen (SDL_Surface *templat, int w, int h) +{ + SDL_Surface *newsurf = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, + templat->format->BitsPerPixel, + templat->format->Rmask, templat->format->Gmask, + templat->format->Bmask, 0); + if (newsurf == 0) { + log_add (log_Error, "Couldn't create screen buffes: %s", + SDL_GetError()); + } + return newsurf; +} + +int +SDL1_ReInit_Screen (SDL_Surface **screen, SDL_Surface *templat, int w, int h) +{ + UnInit_Screen (screen); + *screen = Create_Screen (templat, w, h); + + return *screen == 0 ? -1 : 0; +} +#endif diff --git a/src/libs/graphics/sdl/sdl2_common.c b/src/libs/graphics/sdl/sdl2_common.c new file mode 100644 index 0000000..3eaf7af --- /dev/null +++ b/src/libs/graphics/sdl/sdl2_common.c @@ -0,0 +1,222 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "sdl_common.h" +#include "opengl.h" +#include "pure.h" +#include "primitives.h" +#include "options.h" +#include "uqmversion.h" +#include "libs/graphics/drawcmd.h" +#include "libs/graphics/dcqueue.h" +#include "libs/graphics/cmap.h" +#include "libs/input/sdl/input.h" + // for ProcessInputEvent() +#include "libs/graphics/bbox.h" +#include "port.h" +#include "libs/uio.h" +#include "libs/log.h" +#include "libs/memlib.h" +#include "libs/vidlib.h" + +#if SDL_MAJOR_VERSION > 1 + +static void TFB_PreQuit (void); + +void +TFB_PreInit (void) +{ + SDL_version compiled, linked; + SDL_VERSION(&compiled); + SDL_GetVersion(&linked); + log_add (log_Info, "Initializing base SDL functionality."); + log_add (log_Info, "Using SDL version %d.%d.%d (compiled with " + "%d.%d.%d)", linked.major, linked.minor, linked.patch, + compiled.major, compiled.minor, compiled.patch); +#if 0 + if (compiled.major != linked.major || compiled.minor != linked.minor || + compiled.patch != linked.patch) + { + log_add (log_Warning, "The used SDL library is not the same version " + "as the one used to compile The Ur-Quan Masters with! " + "If you experience any crashes, this would be an excellent " + "suspect."); + } +#endif + + if ((SDL_Init (SDL_INIT_VIDEO) == -1)) + { + log_add (log_Fatal, "Could not initialize SDL: %s.", SDL_GetError ()); + exit (EXIT_FAILURE); + } + + atexit (TFB_PreQuit); +} + +static void +TFB_PreQuit (void) +{ + SDL_Quit (); +} + +int +TFB_ReInitGraphics (int driver, int flags, int width, int height) +{ + int result; + int togglefullscreen = 0; + + if (GfxFlags == (flags ^ TFB_GFXFLAGS_FULLSCREEN) && + driver == GraphicsDriver && + width == ScreenWidthActual && height == ScreenHeightActual) + { + togglefullscreen = 1; + } + + GfxFlags = flags; + + result = TFB_Pure_ConfigureVideo (TFB_GFXDRIVER_SDL_PURE, flags, + width, height, togglefullscreen); + + if (flags & TFB_GFXFLAGS_FULLSCREEN) + SDL_ShowCursor (SDL_DISABLE); + else + SDL_ShowCursor (SDL_ENABLE); + + return result; +} + +bool +TFB_SetGamma (float gamma) +{ + log_add (log_Warning, "Custom gamma correction is not available in the SDL2 engine."); + return 0; +} + +int +TFB_HasSurfaceAlphaMod (SDL_Surface *surface) +{ + SDL_BlendMode blend_mode; + if (!surface) + { + return 0; + } + if (SDL_GetSurfaceBlendMode (surface, &blend_mode) != 0) + { + return 0; + } + return blend_mode == SDL_BLENDMODE_BLEND; +} + +int +TFB_GetSurfaceAlphaMod (SDL_Surface *surface, Uint8 *alpha) +{ + SDL_BlendMode blend_mode; + if (!surface || !alpha) + { + return -1; + } + if (SDL_GetSurfaceBlendMode (surface, &blend_mode) == 0) + { + if (blend_mode == SDL_BLENDMODE_BLEND) + { + return SDL_GetSurfaceAlphaMod (surface, alpha); + } + } + *alpha = 255; + return 0; +} + +int +TFB_SetSurfaceAlphaMod (SDL_Surface *surface, Uint8 alpha) +{ + int result; + if (!surface) + { + return -1; + } + result = SDL_SetSurfaceBlendMode (surface, SDL_BLENDMODE_BLEND); + if (result == 0) + { + result = SDL_SetSurfaceAlphaMod (surface, alpha); + } + return result; +} + +int +TFB_DisableSurfaceAlphaMod (SDL_Surface *surface) +{ + if (!surface) + { + return -1; + } + SDL_SetSurfaceAlphaMod (surface, 255); + return SDL_SetSurfaceBlendMode (surface, SDL_BLENDMODE_NONE); +} + +int +TFB_GetColorKey (SDL_Surface *surface, Uint32 *key) +{ + if (!surface || !key) + { + return -1; + } + return SDL_GetColorKey (surface, key); +} + +int +TFB_SetColorKey (SDL_Surface *surface, Uint32 key, int rleaccel) +{ + if (!surface) + { + return -1; + } + SDL_SetSurfaceRLE (surface, rleaccel); + return SDL_SetColorKey (surface, SDL_TRUE, key); +} + +int +TFB_DisableColorKey (SDL_Surface *surface) +{ + if (!surface) + { + return -1; + } + return SDL_SetColorKey (surface, SDL_FALSE, 0); +} + +int +TFB_SetColors (SDL_Surface *surface, SDL_Color *colors, int firstcolor, int ncolors) +{ + if (!surface || !colors || !surface->format || !surface->format->palette) + { + return 0; + } + if (SDL_SetPaletteColors (surface->format->palette, colors, firstcolor, ncolors) == 0) + { + // SDL2's success code is opposite from SDL1's SDL_SetColors + return 1; + } + return 0; +} + +int +TFB_SupportsHardwareScaling (void) +{ + return 1; +} +#endif diff --git a/src/libs/graphics/sdl/sdl2_pure.c b/src/libs/graphics/sdl/sdl2_pure.c new file mode 100644 index 0000000..c6503db --- /dev/null +++ b/src/libs/graphics/sdl/sdl2_pure.c @@ -0,0 +1,465 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "pure.h" +#include "libs/graphics/bbox.h" +#include "libs/log.h" +#include "scalers.h" +#include "uqmversion.h" + +#if SDL_MAJOR_VERSION > 1 + +typedef struct tfb_sdl2_screeninfo_s { + SDL_Surface *scaled; + SDL_Texture *texture; + BOOLEAN dirty, active; + SDL_Rect updated; +} TFB_SDL2_SCREENINFO; + +static TFB_SDL2_SCREENINFO SDL2_Screens[TFB_GFX_NUMSCREENS]; + +static SDL_Window *window = NULL; +static SDL_Renderer *renderer = NULL; +static const char *rendererBackend = NULL; + +static int ScreenFilterMode; + +static TFB_ScaleFunc scaler = NULL; + +#if SDL_BYTEORDER == SDL_BIG_ENDIAN +#define A_MASK 0xff000000 +#define B_MASK 0x00ff0000 +#define G_MASK 0x0000ff00 +#define R_MASK 0x000000ff +#else +#define A_MASK 0x000000ff +#define B_MASK 0x0000ff00 +#define G_MASK 0x00ff0000 +#define R_MASK 0xff000000 +#endif + +static void TFB_SDL2_Preprocess (int force_full_redraw, int transition_amount, int fade_amount); +static void TFB_SDL2_Postprocess (void); +static void TFB_SDL2_UploadTransitionScreen (void); +static void TFB_SDL2_Scaled_ScreenLayer (SCREEN screen, Uint8 a, SDL_Rect *rect); +static void TFB_SDL2_Unscaled_ScreenLayer (SCREEN screen, Uint8 a, SDL_Rect *rect); +static void TFB_SDL2_ColorLayer (Uint8 r, Uint8 g, Uint8 b, Uint8 a, SDL_Rect *rect); + +static TFB_GRAPHICS_BACKEND sdl2_scaled_backend = { + TFB_SDL2_Preprocess, + TFB_SDL2_Postprocess, + TFB_SDL2_UploadTransitionScreen, + TFB_SDL2_Scaled_ScreenLayer, + TFB_SDL2_ColorLayer }; + +static TFB_GRAPHICS_BACKEND sdl2_unscaled_backend = { + TFB_SDL2_Preprocess, + TFB_SDL2_Postprocess, + TFB_SDL2_UploadTransitionScreen, + TFB_SDL2_Unscaled_ScreenLayer, + TFB_SDL2_ColorLayer }; + +static SDL_Surface * +Create_Screen (int w, int h) +{ + SDL_Surface *newsurf = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, + 32, R_MASK, G_MASK, B_MASK, 0); + if (newsurf == 0) + { + log_add (log_Error, "Couldn't create screen buffers: %s", + SDL_GetError()); + } + return newsurf; +} + +static int +ReInit_Screen (SDL_Surface **screen, int w, int h) +{ + if (*screen) + SDL_FreeSurface (*screen); + *screen = Create_Screen (w, h); + + return *screen == 0 ? -1 : 0; +} + +static int +FindBestRenderDriver (void) +{ + int i, n; + if (!rendererBackend) { + /* If the user has no preference, just let SDL2 choose */ + return -1; + } + n = SDL_GetNumRenderDrivers (); + log_add (log_Info, "Searching for render driver \"%s\".", rendererBackend); + + for (i = 0; i < n; i++) { + SDL_RendererInfo info; + if (SDL_GetRenderDriverInfo (i, &info) < 0) { + continue; + } + if (!strcmp(info.name, rendererBackend)) { + return i; + } + log_add (log_Info, "Skipping render driver \"%s\"", info.name); + } + /* We did not find any accelerated drivers that weren't D3D9. + * Return -1 to ask SDL2 to do its best. */ + log_add (log_Info, "Render driver \"%s\" not available, using system default", rendererBackend); + return -1; +} + +int +TFB_Pure_ConfigureVideo (int driver, int flags, int width, int height, int togglefullscreen) +{ + int i; + GraphicsDriver = driver; + (void) togglefullscreen; + if (window == NULL) + { + SDL_RendererInfo info; + char caption[200]; + + sprintf (caption, "The Ur-Quan Masters v%d.%d.%d%s", + UQM_MAJOR_VERSION, UQM_MINOR_VERSION, + UQM_PATCH_VERSION, UQM_EXTRA_VERSION); + window = SDL_CreateWindow (caption, + SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, + width, height, 0); + if (flags & TFB_GFXFLAGS_FULLSCREEN) + { + /* If we create the window fullscreen, it will have + * no icon if and when it becomes windowed. */ + SDL_SetWindowFullscreen (window, SDL_WINDOW_FULLSCREEN_DESKTOP); + } + if (!window) + { + return -1; + } + renderer = SDL_CreateRenderer (window, FindBestRenderDriver (), 0); + if (!renderer) + { + return -1; + } + if (SDL_GetRendererInfo (renderer, &info) == 0) + { + log_add (log_Info, "SDL2 renderer '%s' selected.\n", info.name); + } + else + { + log_add (log_Info, "SDL2 renderer had no name."); + } + SDL_RenderSetLogicalSize (renderer, ScreenWidth, ScreenHeight); + for (i = 0; i < TFB_GFX_NUMSCREENS; i++) + { + SDL2_Screens[i].scaled = NULL; + SDL2_Screens[i].texture = NULL; + SDL2_Screens[i].dirty = TRUE; + SDL2_Screens[i].active = TRUE; + if (0 != ReInit_Screen (&SDL_Screens[i], ScreenWidth, ScreenHeight)) + { + return -1; + } + } + SDL2_Screens[1].active = FALSE; + SDL_Screen = SDL_Screens[0]; + TransitionScreen = SDL_Screens[2]; + format_conv_surf = SDL_CreateRGBSurface(SDL_SWSURFACE, 0, 0, + 32, R_MASK, G_MASK, B_MASK, A_MASK); + if (!format_conv_surf) + { + return -1; + } + } + else + { + if (flags & TFB_GFXFLAGS_FULLSCREEN) + { + SDL_SetWindowFullscreen (window, SDL_WINDOW_FULLSCREEN_DESKTOP); + } + else + { + SDL_SetWindowFullscreen (window, 0); + SDL_SetWindowSize (window, width, height); + } + } + + if (GfxFlags & TFB_GFXFLAGS_SCALE_ANY) + { + /* Linear scaling */ + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1"); + } + else + { + /* Nearest-neighbor scaling */ + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"); + } + + if (GfxFlags & TFB_GFXFLAGS_SCALE_SOFT_ONLY) + { + for (i = 0; i < TFB_GFX_NUMSCREENS; i++) + { + if (!SDL2_Screens[i].active) + { + continue; + } + if (0 != ReInit_Screen(&SDL2_Screens[i].scaled, + ScreenWidth * 2, ScreenHeight * 2)) + { + return -1; + } + if (SDL2_Screens[i].texture) + { + SDL_DestroyTexture (SDL2_Screens[i].texture); + SDL2_Screens[i].texture = NULL; + } + SDL2_Screens[i].texture = SDL_CreateTexture (renderer, SDL_PIXELFORMAT_RGBX8888, SDL_TEXTUREACCESS_STREAMING, ScreenWidth * 2, ScreenHeight * 2); + SDL_LockSurface (SDL2_Screens[i].scaled); + SDL_UpdateTexture (SDL2_Screens[i].texture, NULL, SDL2_Screens[i].scaled->pixels, SDL2_Screens[i].scaled->pitch); + SDL_UnlockSurface (SDL2_Screens[i].scaled); + } + scaler = Scale_PrepPlatform (flags, SDL2_Screens[0].scaled->format); + graphics_backend = &sdl2_scaled_backend; + } + else + { + for (i = 0; i < TFB_GFX_NUMSCREENS; i++) + { + if (SDL2_Screens[i].scaled) + { + SDL_FreeSurface (SDL2_Screens[i].scaled); + SDL2_Screens[i].scaled = NULL; + } + if (SDL2_Screens[i].texture) + { + SDL_DestroyTexture (SDL2_Screens[i].texture); + SDL2_Screens[i].texture = NULL; + } + SDL2_Screens[i].texture = SDL_CreateTexture (renderer, SDL_PIXELFORMAT_RGBX8888, SDL_TEXTUREACCESS_STREAMING, ScreenWidth, ScreenHeight); + SDL_LockSurface (SDL_Screens[i]); + SDL_UpdateTexture (SDL2_Screens[i].texture, NULL, SDL_Screens[i]->pixels, SDL_Screens[i]->pitch); + SDL_UnlockSurface (SDL_Screens[i]); + } + scaler = NULL; + graphics_backend = &sdl2_unscaled_backend; + } + + /* We succeeded, so alter the screen size to our new sizes */ + ScreenWidthActual = width; + ScreenHeightActual = height; + + return 0; +} + +int +TFB_Pure_InitGraphics (int driver, int flags, const char *renderer, int width, int height) +{ + log_add (log_Info, "Initializing SDL."); + log_add (log_Info, "SDL initialized."); + log_add (log_Info, "Initializing Screen."); + + ScreenWidth = 320; + ScreenHeight = 240; + rendererBackend = renderer; + + if (TFB_Pure_ConfigureVideo (driver, flags, width, height, 0)) + { + log_add (log_Fatal, "Could not initialize video: %s", + SDL_GetError ()); + exit (EXIT_FAILURE); + } + + /* Initialize scalers (let them precompute whatever) */ + Scale_Init (); + + return 0; +} + +void +TFB_Pure_UninitGraphics (void) +{ + if (renderer) { + SDL_DestroyRenderer (renderer); + } + if (window) { + SDL_DestroyWindow (window); + } +} + +static void +TFB_SDL2_UploadTransitionScreen (void) +{ + SDL2_Screens[TFB_SCREEN_TRANSITION].updated.x = 0; + SDL2_Screens[TFB_SCREEN_TRANSITION].updated.y = 0; + SDL2_Screens[TFB_SCREEN_TRANSITION].updated.w = ScreenWidth; + SDL2_Screens[TFB_SCREEN_TRANSITION].updated.h = ScreenHeight; + SDL2_Screens[TFB_SCREEN_TRANSITION].dirty = TRUE; +} + +static void +TFB_SDL2_UpdateTexture (SDL_Texture *dest, SDL_Surface *src, SDL_Rect *rect) +{ + char *srcBytes; + SDL_LockSurface (src); + srcBytes = src->pixels; + if (rect) + { + /* SDL2 screen surfaces are always 32bpp */ + srcBytes += (src->pitch * rect->y) + (rect->x * 4); + } + /* 2020-08-02: At time of writing, the documentation for + * SDL_UpdateTexture states this: "If the texture is intended to be + * updated often, it is preferred to create the texture as streaming + * and use [SDL_LockTexture and SDL_UnlockTexture]." Unfortunately, + * SDL_LockTexture will corrupt driver-space memory in the 32-bit + * Direct3D 9 driver on Intel Integrated graphics chips, resulting + * in an immediate crash with no detectable errors from the API up + * to that point. + * + * We also cannot simply forbid the Direct3D driver outright, because + * pre-Windows 10 machines appear to fail to initialize D3D11 even + * while claiming to support it. + * + * These bugs may be fixed in the future, but in the meantime we + * rely on this allegedly slower but definitely more reliable + * function. */ + SDL_UpdateTexture (dest, rect, srcBytes, src->pitch); + SDL_UnlockSurface (src); +} + +static void +TFB_SDL2_ScanLines (void) +{ + int y; + SDL_SetRenderDrawColor (renderer, 0, 0, 0, 64); + SDL_SetRenderDrawBlendMode (renderer, SDL_BLENDMODE_BLEND); + SDL_RenderSetLogicalSize (renderer, ScreenWidth * 2, ScreenHeight * 2); + for (y = 0; y < ScreenHeight * 2; y += 2) + { + SDL_RenderDrawLine (renderer, 0, y, ScreenWidth * 2 - 1, y); + } + SDL_RenderSetLogicalSize (renderer, ScreenWidth, ScreenHeight); +} + +static void +TFB_SDL2_Preprocess (int force_full_redraw, int transition_amount, int fade_amount) +{ + (void) transition_amount; + (void) fade_amount; + + if (force_full_redraw == TFB_REDRAW_YES) + { + SDL2_Screens[TFB_SCREEN_MAIN].updated.x = 0; + SDL2_Screens[TFB_SCREEN_MAIN].updated.y = 0; + SDL2_Screens[TFB_SCREEN_MAIN].updated.w = ScreenWidth; + SDL2_Screens[TFB_SCREEN_MAIN].updated.h = ScreenHeight; + SDL2_Screens[TFB_SCREEN_MAIN].dirty = TRUE; + } + else if (TFB_BBox.valid) + { + SDL2_Screens[TFB_SCREEN_MAIN].updated.x = TFB_BBox.region.corner.x; + SDL2_Screens[TFB_SCREEN_MAIN].updated.y = TFB_BBox.region.corner.y; + SDL2_Screens[TFB_SCREEN_MAIN].updated.w = TFB_BBox.region.extent.width; + SDL2_Screens[TFB_SCREEN_MAIN].updated.h = TFB_BBox.region.extent.height; + SDL2_Screens[TFB_SCREEN_MAIN].dirty = TRUE; + } + + SDL_SetRenderDrawBlendMode (renderer, SDL_BLENDMODE_NONE); + SDL_SetRenderDrawColor (renderer, 0, 0, 0, 255); + SDL_RenderClear (renderer); +} + +static void +TFB_SDL2_Unscaled_ScreenLayer (SCREEN screen, Uint8 a, SDL_Rect *rect) +{ + SDL_Texture *texture = SDL2_Screens[screen].texture; + if (SDL2_Screens[screen].dirty) + { + TFB_SDL2_UpdateTexture (texture, SDL_Screens[screen], &SDL2_Screens[screen].updated); + } + if (a == 255) + { + SDL_SetTextureBlendMode (texture, SDL_BLENDMODE_NONE); + } + else + { + SDL_SetTextureBlendMode (texture, SDL_BLENDMODE_BLEND); + SDL_SetTextureAlphaMod (texture, a); + } + SDL_RenderCopy (renderer, texture, rect, rect); +} + +static void +TFB_SDL2_Scaled_ScreenLayer (SCREEN screen, Uint8 a, SDL_Rect *rect) +{ + SDL_Texture *texture = SDL2_Screens[screen].texture; + SDL_Rect srcRect, *pSrcRect = NULL; + if (SDL2_Screens[screen].dirty) + { + SDL_Surface *src = SDL2_Screens[screen].scaled; + SDL_Rect scaled_update = SDL2_Screens[screen].updated; + scaler (SDL_Screens[screen], src, &SDL2_Screens[screen].updated); + scaled_update.x *= 2; + scaled_update.y *= 2; + scaled_update.w *= 2; + scaled_update.h *= 2; + TFB_SDL2_UpdateTexture (texture, src, &scaled_update); + } + if (a == 255) + { + SDL_SetTextureBlendMode (texture, SDL_BLENDMODE_NONE); + } + else + { + SDL_SetTextureBlendMode (texture, SDL_BLENDMODE_BLEND); + SDL_SetTextureAlphaMod (texture, a); + } + /* The texture has twice the resolution when scaled, but the + * screen's logical resolution has not changed, so the clip + * rectangle does not need to be scaled. The *source* clip + * rect, however, must be scaled to match. */ + if (rect) + { + srcRect = *rect; + srcRect.x *= 2; + srcRect.y *= 2; + srcRect.w *= 2; + srcRect.h *= 2; + pSrcRect = &srcRect; + } + SDL_RenderCopy (renderer, texture, pSrcRect, rect); +} + +static void +TFB_SDL2_ColorLayer (Uint8 r, Uint8 g, Uint8 b, Uint8 a, SDL_Rect *rect) +{ + SDL_SetRenderDrawBlendMode (renderer, a == 255 ? SDL_BLENDMODE_NONE + : SDL_BLENDMODE_BLEND); + SDL_SetRenderDrawColor (renderer, r, g, b, a); + SDL_RenderFillRect (renderer, rect); +} + +static void +TFB_SDL2_Postprocess (void) +{ + if (GfxFlags & TFB_GFXFLAGS_SCANLINES) + TFB_SDL2_ScanLines (); + + SDL_RenderPresent (renderer); +} + +#endif diff --git a/src/libs/graphics/sdl/sdl_common.c b/src/libs/graphics/sdl/sdl_common.c new file mode 100644 index 0000000..b699bf8 --- /dev/null +++ b/src/libs/graphics/sdl/sdl_common.c @@ -0,0 +1,308 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "sdl_common.h" +#include "opengl.h" +#include "pure.h" +#include "primitives.h" +#include "options.h" +#include "uqmversion.h" +#include "libs/graphics/drawcmd.h" +#include "libs/graphics/dcqueue.h" +#include "libs/graphics/cmap.h" +#include "libs/input/sdl/input.h" + // for ProcessInputEvent() +#include "libs/graphics/bbox.h" +#include "port.h" +#include "libs/uio.h" +#include "libs/log.h" +#include "libs/memlib.h" +#include "libs/vidlib.h" + +SDL_Surface *SDL_Screen; +SDL_Surface *TransitionScreen; + +SDL_Surface *SDL_Screens[TFB_GFX_NUMSCREENS]; + +SDL_Surface *format_conv_surf = NULL; + +static volatile BOOLEAN abortFlag = FALSE; + +int GfxFlags = 0; + +TFB_GRAPHICS_BACKEND *graphics_backend = NULL; + +volatile int QuitPosted = 0; +volatile int GameActive = 1; // Track the SDL_ACTIVEEVENT state SDL_APPACTIVE + +int +TFB_InitGraphics (int driver, int flags, const char *renderer, int width, int height) +{ + int result, i; + char caption[200]; + + /* Null out screen pointers the first time */ + for (i = 0; i < TFB_GFX_NUMSCREENS; i++) + { + SDL_Screens[i] = NULL; + } + + GfxFlags = flags; + + if (driver == TFB_GFXDRIVER_SDL_OPENGL) + { +#ifdef HAVE_OPENGL + result = TFB_GL_InitGraphics (driver, flags, width, height); +#else + driver = TFB_GFXDRIVER_SDL_PURE; + log_add (log_Warning, "OpenGL support not compiled in," + " so using pure SDL driver"); + result = TFB_Pure_InitGraphics (driver, flags, renderer, width, height); +#endif + } + else + { + result = TFB_Pure_InitGraphics (driver, flags, renderer, width, height); + } + +#if SDL_MAJOR_VERSION == 1 + /* Other versions do this when setting up the window */ + sprintf (caption, "The Ur-Quan Masters v%d.%d.%d%s", + UQM_MAJOR_VERSION, UQM_MINOR_VERSION, + UQM_PATCH_VERSION, UQM_EXTRA_VERSION); + SDL_WM_SetCaption (caption, NULL); +#endif + + if (flags & TFB_GFXFLAGS_FULLSCREEN) + SDL_ShowCursor (SDL_DISABLE); + + Init_DrawCommandQueue (); + + TFB_DrawCanvas_Initialize (); + + return 0; +} + +void +TFB_UninitGraphics (void) +{ + int i; + + Uninit_DrawCommandQueue (); + + for (i = 0; i < TFB_GFX_NUMSCREENS; i++) + UnInit_Screen (&SDL_Screens[i]); + + TFB_Pure_UninitGraphics (); +#ifdef HAVE_OPENGL + TFB_GL_UninitGraphics (); +#endif + + UnInit_Screen (&format_conv_surf); +} + +void +TFB_ProcessEvents () +{ + SDL_Event Event; + + while (SDL_PollEvent (&Event) > 0) + { + /* Run through the InputEvent filter. */ + ProcessInputEvent (&Event); + /* Handle graphics and exposure events. */ + switch (Event.type) { +#if 0 /* Currently disabled in mainline */ + case SDL_ACTIVEEVENT: /* Lose/gain visibility or focus */ + /* Up to three different state changes can occur in one event. */ + /* Here, disregard least significant change (mouse focus). */ + // This controls the automatic sleep/pause when minimized. + // On small displays (e.g. mobile devices), APPINPUTFOCUS would + // be an appropriate substitution for APPACTIVE: + // if (Event.active.state & SDL_APPINPUTFOCUS) + if (Event.active.state & SDL_APPACTIVE) + GameActive = Event.active.gain; + break; + case SDL_VIDEORESIZE: /* User resized video mode */ + // TODO + break; +#endif + case SDL_QUIT: + QuitPosted = 1; + break; +#if SDL_MAJOR_VERSION == 1 + case SDL_VIDEOEXPOSE: /* Screen needs to be redrawn */ + TFB_SwapBuffers (TFB_REDRAW_EXPOSE); + break; +#else + case SDL_WINDOWEVENT: + if (Event.window.event == SDL_WINDOWEVENT_EXPOSED) + { + /* Screen needs to be redrawn */ + TFB_SwapBuffers (TFB_REDRAW_EXPOSE); + } + break; +#endif + default: + break; + } + } +} + +static BOOLEAN system_box_active = 0; +static SDL_Rect system_box; + +void +SetSystemRect (const RECT *r) +{ + system_box_active = TRUE; + system_box.x = r->corner.x; + system_box.y = r->corner.y; + system_box.w = r->extent.width; + system_box.h = r->extent.height; +} + +void +ClearSystemRect (void) +{ + system_box_active = FALSE; +} + +void +TFB_SwapBuffers (int force_full_redraw) +{ + static int last_fade_amount = 255, last_transition_amount = 255; + static int fade_amount = 255, transition_amount = 255; + + fade_amount = GetFadeAmount (); + transition_amount = TransitionAmount; + + if (force_full_redraw == TFB_REDRAW_NO && !TFB_BBox.valid && + fade_amount == 255 && transition_amount == 255 && + last_fade_amount == 255 && last_transition_amount == 255) + return; + + if (force_full_redraw == TFB_REDRAW_NO && + (fade_amount != 255 || transition_amount != 255 || + last_fade_amount != 255 || last_transition_amount != 255)) + force_full_redraw = TFB_REDRAW_FADING; + + last_fade_amount = fade_amount; + last_transition_amount = transition_amount; + + graphics_backend->preprocess (force_full_redraw, transition_amount, + fade_amount); + graphics_backend->screen (TFB_SCREEN_MAIN, 255, NULL); + + if (transition_amount != 255) + { + SDL_Rect r; + r.x = TransitionClipRect.corner.x; + r.y = TransitionClipRect.corner.y; + r.w = TransitionClipRect.extent.width; + r.h = TransitionClipRect.extent.height; + graphics_backend->screen (TFB_SCREEN_TRANSITION, + 255 - transition_amount, &r); + } + + if (fade_amount != 255) + { + if (fade_amount < 255) + { + graphics_backend->color (0, 0, 0, 255 - fade_amount, NULL); + } + else + { + graphics_backend->color (255, 255, 255, + fade_amount - 255, NULL); + } + } + + if (system_box_active) + { + graphics_backend->screen (TFB_SCREEN_MAIN, 255, &system_box); + } + + graphics_backend->postprocess (); +} + +/* Probably ought to clean this away at some point. */ +SDL_Surface * +TFB_DisplayFormatAlpha (SDL_Surface *surface) +{ + SDL_Surface* newsurf; + SDL_PixelFormat* dstfmt; + const SDL_PixelFormat* srcfmt = surface->format; + + // figure out what format to use (alpha/no alpha) + if (surface->format->Amask) + dstfmt = format_conv_surf->format; + else + dstfmt = SDL_Screen->format; + + if (srcfmt->BytesPerPixel == dstfmt->BytesPerPixel && + srcfmt->Rmask == dstfmt->Rmask && + srcfmt->Gmask == dstfmt->Gmask && + srcfmt->Bmask == dstfmt->Bmask && + srcfmt->Amask == dstfmt->Amask) + return surface; // no conversion needed + + newsurf = SDL_ConvertSurface (surface, dstfmt, surface->flags); + // Colorkeys and surface-level alphas cannot work at the same time, + // so we need to disable one of them + if (TFB_HasColorKey (surface) && newsurf && + TFB_HasColorKey (newsurf) && + TFB_HasSurfaceAlphaMod (newsurf)) + { + TFB_DisableSurfaceAlphaMod (newsurf); + } + + return newsurf; +} + +// This function should only be called from the graphics thread, +// like from a TFB_DrawCommand_Callback command. +TFB_Canvas +TFB_GetScreenCanvas (SCREEN screen) +{ + return SDL_Screens[screen]; +} + +void +TFB_UploadTransitionScreen (void) +{ + graphics_backend->uploadTransitionScreen (); +} + +int +TFB_HasColorKey (SDL_Surface *surface) +{ + Uint32 key; + return TFB_GetColorKey (surface, &key) == 0; +} + +void +UnInit_Screen (SDL_Surface **screen) +{ + if (*screen == NULL) { + return; + } + + SDL_FreeSurface (*screen); + *screen = NULL; +} diff --git a/src/libs/graphics/sdl/sdl_common.h b/src/libs/graphics/sdl/sdl_common.h new file mode 100644 index 0000000..76d1cb6 --- /dev/null +++ b/src/libs/graphics/sdl/sdl_common.h @@ -0,0 +1,63 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef SDL_COMMON_H +#define SDL_COMMON_H + +#include "port.h" +#include SDL_INCLUDE(SDL.h) + +#include "../gfxintrn.h" +#include "libs/graphics/tfb_draw.h" +#include "libs/graphics/gfx_common.h" + +// The Graphics Backend vtable +typedef struct _tfb_graphics_backend { + void (*preprocess) (int force_redraw, int transition_amount, int fade_amount); + void (*postprocess) (void); + void (*uploadTransitionScreen) (void); + void (*screen) (SCREEN screen, Uint8 alpha, SDL_Rect *rect); + void (*color) (Uint8 r, Uint8 g, Uint8 b, Uint8 a, SDL_Rect *rect); +} TFB_GRAPHICS_BACKEND; + +extern TFB_GRAPHICS_BACKEND *graphics_backend; + +extern SDL_Surface *SDL_Screen; +extern SDL_Surface *TransitionScreen; + +extern SDL_Surface *SDL_Screens[TFB_GFX_NUMSCREENS]; + +extern SDL_Surface *format_conv_surf; + +SDL_Surface* TFB_DisplayFormatAlpha (SDL_Surface *surface); +int TFB_HasSurfaceAlphaMod (SDL_Surface *surface); +int TFB_GetSurfaceAlphaMod (SDL_Surface *surface, Uint8 *alpha); +int TFB_SetSurfaceAlphaMod (SDL_Surface *surface, Uint8 alpha); +int TFB_DisableSurfaceAlphaMod (SDL_Surface *surface); +int TFB_HasColorKey (SDL_Surface *surface); +int TFB_GetColorKey (SDL_Surface *surface, Uint32 *key); +int TFB_SetColorKey (SDL_Surface *surface, Uint32 key, int rleaccel); +int TFB_DisableColorKey (SDL_Surface *surface); +int TFB_SetColors (SDL_Surface *surface, SDL_Color *colors, int firstcolor, int ncolors); + +#if SDL_MAJOR_VERSION == 1 +int SDL1_ReInit_Screen (SDL_Surface **screen, SDL_Surface *templat, int w, int h); +#endif +void UnInit_Screen (SDL_Surface **screen); + +#endif diff --git a/src/libs/graphics/sdl/sdluio.c b/src/libs/graphics/sdl/sdluio.c new file mode 100644 index 0000000..5e4554d --- /dev/null +++ b/src/libs/graphics/sdl/sdluio.c @@ -0,0 +1,153 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "sdluio.h" + +#include "port.h" +#include "libs/uio.h" +#include SDL_INCLUDE(SDL.h) +#include SDL_INCLUDE(SDL_error.h) +#include SDL_INCLUDE(SDL_rwops.h) +#include "libs/memlib.h" +#include "png2sdl.h" +#include <errno.h> +#include <string.h> + + +static SDL_RWops *sdluio_makeRWops (uio_Stream *stream); + +#if 0 +// For use for initialisation, using structure assignment. +static SDL_RWops sdluio_templateRWops = +{ + .seek = sdluio_seek, + .read = sdluio_read, + .write = sdluio_write, + .close = sdluio_close, +}; +#endif + +SDL_Surface * +sdluio_loadImage (uio_DirHandle *dir, const char *fileName) { + uio_Stream *stream; + SDL_RWops *rwops; + SDL_Surface *result = NULL; + + stream = uio_fopen (dir, fileName, "rb"); + if (stream == NULL) + { + SDL_SetError ("Couldn't open '%s': %s", fileName, + strerror(errno)); + return NULL; + } + rwops = sdluio_makeRWops (stream); + if (rwops) { + result = TFB_png_to_sdl (rwops); + SDL_RWclose (rwops); + } + return result; +} + +#if SDL_MAJOR_VERSION == 1 +int +sdluio_seek (SDL_RWops *context, int offset, int whence) +#else +Sint64 +sdluio_seek (SDL_RWops *context, Sint64 offset, int whence) +#endif +{ + if (uio_fseek ((uio_Stream *) context->hidden.unknown.data1, offset, + whence) == -1) + { + SDL_SetError ("Error seeking in uio_Stream: %s", + strerror(errno)); + return -1; + } + return uio_ftell ((uio_Stream *) context->hidden.unknown.data1); +} + +#if SDL_MAJOR_VERSION == 1 +int +sdluio_read (SDL_RWops *context, void *ptr, int size, int maxnum) +#else +size_t +sdluio_read (SDL_RWops *context, void *ptr, size_t size, size_t maxnum) +#endif +{ + size_t numRead; + + numRead = uio_fread (ptr, (size_t) size, (size_t) maxnum, + (uio_Stream *) context->hidden.unknown.data1); + if (numRead == 0 && uio_ferror ((uio_Stream *) + context->hidden.unknown.data1)) + { + SDL_SetError ("Error reading from uio_Stream: %s", + strerror(errno)); + return 0; + } + return (int) numRead; +} + +#if SDL_MAJOR_VERSION == 1 +int +sdluio_write (SDL_RWops *context, const void *ptr, int size, int num) +#else +size_t +sdluio_write (SDL_RWops *context, const void *ptr, size_t size, size_t num) +#endif +{ + size_t numWritten; + + numWritten = uio_fwrite (ptr, (size_t) size, (size_t) num, + (uio_Stream *) context->hidden.unknown.data1); + if (numWritten == 0 && uio_ferror ((uio_Stream *) + context->hidden.unknown.data1)) + { + SDL_SetError ("Error writing to uio_Stream: %s", + strerror(errno)); + return 0; + } + return (size_t) numWritten; +} + +int +sdluio_close (SDL_RWops *context) { + int result; + + result = uio_fclose ((uio_Stream *) context->hidden.unknown.data1); + HFree (context); + return result; +} + +static SDL_RWops * +sdluio_makeRWops (uio_Stream *stream) { + SDL_RWops *result; + + result = HMalloc (sizeof (SDL_RWops)); +#if 0 + *(struct SDL_RWops *) result = sdluio_templateRWops; + // structure assignment +#endif + result->seek = sdluio_seek; + result->read = sdluio_read; + result->write = sdluio_write; + result->close = sdluio_close; + result->hidden.unknown.data1 = stream; + return result; +} + + + diff --git a/src/libs/graphics/sdl/sdluio.h b/src/libs/graphics/sdl/sdluio.h new file mode 100644 index 0000000..0f8c701 --- /dev/null +++ b/src/libs/graphics/sdl/sdluio.h @@ -0,0 +1,39 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_GRAPHICS_SDL_SDLUIO_H_ +#define LIBS_GRAPHICS_SDL_SDLUIO_H_ + +#include "port.h" +#include "libs/uio.h" +#include SDL_INCLUDE(SDL.h) +#include SDL_INCLUDE(SDL_rwops.h) + +SDL_Surface *sdluio_loadImage (uio_DirHandle *dir, const char *fileName); +#if SDL_MAJOR_VERSION == 1 +int sdluio_seek (SDL_RWops *context, int offset, int whence); +int sdluio_read (SDL_RWops *context, void *ptr, int size, int maxnum); +int sdluio_write (SDL_RWops *context, const void *ptr, int size, int num); +#else +Sint64 sdlui_seek (SDL_RWops *context, Sint64 offset, int whence); +size_t sdlui_read (SDL_RWops *context, void *ptr, size_t size, size_t maxnum); +size_t sdlui_write (SDL_RWops *contex, const void *ptr, size_t size, size_t num); +#endif +int sdluio_close (SDL_RWops *context); + + +#endif /* LIBS_GRAPHICS_SDL_SDLUIO_H_ */ + diff --git a/src/libs/graphics/sdl/triscan2x.c b/src/libs/graphics/sdl/triscan2x.c new file mode 100644 index 0000000..830dc7c --- /dev/null +++ b/src/libs/graphics/sdl/triscan2x.c @@ -0,0 +1,155 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +// Core algorithm of the Triscan screen scaler (based on Scale2x) +// (for scale2x please see http://scale2x.sf.net) +// Template +// When this file is built standalone is produces a plain C version +// Also #included by 2xscalers_mmx.c for an MMX version + +#include "libs/graphics/sdl/sdl_common.h" +#include "types.h" +#include "scalers.h" +#include "scaleint.h" +#include "2xscalers.h" + + +// Triscan scaling to 2x +// derivative of scale2x -- scale2x.sf.net +// The name expands to either +// Scale_TriScanFilter (for plain C) or +// Scale_MMX_TriScanFilter (for MMX) +// [others when platforms are added] +void +SCALE_(TriScanFilter) (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r) +{ + int x, y; + const int w = src->w, h = src->h; + int xend, yend; + int dsrc, ddst; + SDL_Rect *region = r; + SDL_Rect limits; + SDL_PixelFormat *fmt = dst->format; + const int sp = src->pitch, dp = dst->pitch; + const int bpp = fmt->BytesPerPixel; + const int slen = sp / bpp, dlen = dp / bpp; + // for clarity purposes, the 'pixels' array here is transposed + Uint32 pixels[3][3]; + Uint32 *src_p = (Uint32 *)src->pixels; + Uint32 *dst_p = (Uint32 *)dst->pixels; + + int prevline, nextline; + + // these macros are for clarity; they make the current pixel (0,0) + // and allow to access pixels in all directions + #define PIX(x, y) (pixels[1 + (x)][1 + (y)]) + + #define TRISCAN_YUV_MED 100 + // medium tolerance pixel comparison + #define TRISCAN_CMPYUV(p1, p2) \ + (PIX p1 == PIX p2 || SCALE_CMPYUV (PIX p1, PIX p2, TRISCAN_YUV_MED)) + + + SCALE_(PlatInit) (); + + // expand updated region if necessary + // pixels neighbooring the updated region may + // change as a result of updates + limits.x = 0; + limits.y = 0; + limits.w = src->w; + limits.h = src->h; + Scale_ExpandRect (region, 1, &limits); + + xend = region->x + region->w; + yend = region->y + region->h; + dsrc = slen - region->w; + ddst = (dlen - region->w) * 2; + + // move ptrs to the first updated pixel + src_p += slen * region->y + region->x; + dst_p += (dlen * region->y + region->x) * 2; + + for (y = region->y; y < yend; ++y, dst_p += ddst, src_p += dsrc) + { + if (y > 0) + prevline = -slen; + else + prevline = 0; + + if (y < h - 1) + nextline = slen; + else + nextline = 0; + + // prime the (tiny) sliding-window pixel arrays + PIX( 1, 0) = src_p[0]; + + if (region->x > 0) + PIX( 0, 0) = src_p[-1]; + else + PIX( 0, 0) = PIX( 1, 0); + + for (x = region->x; x < xend; ++x, ++src_p, dst_p += 2) + { + // slide the window + PIX(-1, 0) = PIX( 0, 0); + + PIX( 0, -1) = src_p[prevline]; + PIX( 0, 0) = PIX( 1, 0); + PIX( 0, 1) = src_p[nextline]; + + if (x < w - 1) + PIX( 1, 0) = src_p[1]; + else + PIX( 1, 0) = PIX( 0, 0); + + if (!TRISCAN_CMPYUV (( 0, -1), ( 0, 1)) && + !TRISCAN_CMPYUV ((-1, 0), ( 1, 0))) + { + if (TRISCAN_CMPYUV ((-1, 0), ( 0, -1))) + dst_p[0] = Scale_Blend_11 (PIX(-1, 0), PIX(0, -1)); + else + dst_p[0] = PIX(0, 0); + + if (TRISCAN_CMPYUV (( 1, 0), ( 0, -1))) + dst_p[1] = Scale_Blend_11 (PIX(1, 0), PIX(0, -1)); + else + dst_p[1] = PIX(0, 0); + + if (TRISCAN_CMPYUV ((-1, 0), ( 0, 1))) + dst_p[dlen] = Scale_Blend_11 (PIX(-1, 0), PIX(0, 1)); + else + dst_p[dlen] = PIX(0, 0); + + if (TRISCAN_CMPYUV (( 1, 0), ( 0, 1))) + dst_p[dlen+1] = Scale_Blend_11 (PIX(1, 0), PIX(0, 1)); + else + dst_p[dlen+1] = PIX(0, 0); + } + else + { + dst_p[0] = PIX(0, 0); + dst_p[1] = PIX(0, 0); + dst_p[dlen] = PIX(0, 0); + dst_p[dlen+1] = PIX(0, 0); + } + } + } + + SCALE_(PlatDone) (); +} + diff --git a/src/libs/graphics/tfb_draw.c b/src/libs/graphics/tfb_draw.c new file mode 100644 index 0000000..1ac3c34 --- /dev/null +++ b/src/libs/graphics/tfb_draw.c @@ -0,0 +1,493 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "gfx_common.h" +#include "tfb_draw.h" +#include "drawcmd.h" +#include "libs/log.h" +#include "libs/memlib.h" + + +static const HOT_SPOT NullHs = {0, 0}; + +void +TFB_DrawScreen_Line (int x1, int y1, int x2, int y2, Color color, + DrawMode mode, SCREEN dest) +{ + TFB_DrawCommand DC; + + DC.Type = TFB_DRAWCOMMANDTYPE_LINE; + DC.data.line.x1 = x1; + DC.data.line.y1 = y1; + DC.data.line.x2 = x2; + DC.data.line.y2 = y2; + DC.data.line.color = color; + DC.data.line.drawMode = mode; + DC.data.line.destBuffer = dest; + + TFB_EnqueueDrawCommand (&DC); +} + +void +TFB_DrawScreen_Rect (RECT *rect, Color color, DrawMode mode, SCREEN dest) +{ + RECT locRect; + TFB_DrawCommand DC; + + if (!rect) + { + locRect.corner.x = locRect.corner.y = 0; + locRect.extent.width = ScreenWidth; + locRect.extent.height = ScreenHeight; + rect = &locRect; + } + + DC.Type = TFB_DRAWCOMMANDTYPE_RECTANGLE; + DC.data.rect.rect = *rect; + DC.data.rect.color = color; + DC.data.rect.drawMode = mode; + DC.data.rect.destBuffer = dest; + + TFB_EnqueueDrawCommand (&DC); +} + +void +TFB_DrawScreen_Image (TFB_Image *img, int x, int y, int scale, + int scaleMode, TFB_ColorMap *cmap, DrawMode mode, SCREEN dest) +{ + TFB_DrawCommand DC; + + DC.Type = TFB_DRAWCOMMANDTYPE_IMAGE; + DC.data.image.image = img; + DC.data.image.colormap = cmap; + DC.data.image.x = x; + DC.data.image.y = y; + DC.data.image.scale = (scale == GSCALE_IDENTITY) ? 0 : scale; + DC.data.image.scaleMode = scaleMode; + DC.data.image.drawMode = mode; + DC.data.image.destBuffer = dest; + + TFB_EnqueueDrawCommand (&DC); +} + +void +TFB_DrawScreen_FilledImage (TFB_Image *img, int x, int y, int scale, + int scaleMode, Color color, DrawMode mode, SCREEN dest) +{ + TFB_DrawCommand DC; + + DC.Type = TFB_DRAWCOMMANDTYPE_FILLEDIMAGE; + DC.data.filledimage.image = img; + DC.data.filledimage.x = x; + DC.data.filledimage.y = y; + DC.data.filledimage.scale = (scale == GSCALE_IDENTITY) ? 0 : scale; + DC.data.filledimage.scaleMode = scaleMode; + DC.data.filledimage.color = color; + DC.data.filledimage.drawMode = mode; + DC.data.filledimage.destBuffer = dest; + + TFB_EnqueueDrawCommand (&DC); +} + +void +TFB_DrawScreen_FontChar (TFB_Char *fontChar, TFB_Image *backing, + int x, int y, DrawMode mode, SCREEN dest) +{ + TFB_DrawCommand DC; + + DC.Type = TFB_DRAWCOMMANDTYPE_FONTCHAR; + DC.data.fontchar.fontchar = fontChar; + DC.data.fontchar.backing = backing; + DC.data.fontchar.x = x; + DC.data.fontchar.y = y; + DC.data.fontchar.drawMode = mode; + DC.data.fontchar.destBuffer = dest; + + TFB_EnqueueDrawCommand (&DC); +} + +void +TFB_DrawScreen_CopyToImage (TFB_Image *img, const RECT *r, SCREEN src) +{ + TFB_DrawCommand DC; + + DC.Type = TFB_DRAWCOMMANDTYPE_COPYTOIMAGE; + DC.data.copytoimage.rect = *r; + DC.data.copytoimage.image = img; + DC.data.copytoimage.srcBuffer = src; + + TFB_EnqueueDrawCommand (&DC); +} + +void +TFB_DrawScreen_Copy (const RECT *r, SCREEN src, SCREEN dest) +{ + RECT locRect; + TFB_DrawCommand DC; + + if (!r) + { + locRect.corner.x = locRect.corner.y = 0; + locRect.extent.width = ScreenWidth; + locRect.extent.height = ScreenHeight; + r = &locRect; + } + + DC.Type = TFB_DRAWCOMMANDTYPE_COPY; + DC.data.copy.rect = *r; + DC.data.copy.srcBuffer = src; + DC.data.copy.destBuffer = dest; + + TFB_EnqueueDrawCommand (&DC); +} + +void +TFB_DrawScreen_SetMipmap (TFB_Image *img, TFB_Image *mmimg, int hotx, int hoty) +{ + TFB_DrawCommand DC; + + DC.Type = TFB_DRAWCOMMANDTYPE_SETMIPMAP; + DC.data.setmipmap.image = img; + DC.data.setmipmap.mipmap = mmimg; + DC.data.setmipmap.hotx = hotx; + DC.data.setmipmap.hoty = hoty; + + TFB_EnqueueDrawCommand (&DC); +} + +void +TFB_DrawScreen_DeleteImage (TFB_Image *img) +{ + if (img) + { + TFB_DrawCommand DC; + + DC.Type = TFB_DRAWCOMMANDTYPE_DELETEIMAGE; + DC.data.deleteimage.image = img; + + TFB_EnqueueDrawCommand (&DC); + } +} + +void +TFB_DrawScreen_DeleteData (void *data) + // data must be a result of HXalloc() call +{ + if (data) + { + TFB_DrawCommand DC; + + DC.Type = TFB_DRAWCOMMANDTYPE_DELETEDATA; + DC.data.deletedata.data = data; + + TFB_EnqueueDrawCommand (&DC); + } +} + +void +TFB_DrawScreen_WaitForSignal (void) +{ + TFB_DrawCommand DrawCommand; + Semaphore s; + s = GetMyThreadLocal ()->flushSem; + DrawCommand.Type = TFB_DRAWCOMMANDTYPE_SENDSIGNAL; + DrawCommand.data.sendsignal.sem = s; + Lock_DCQ (1); + TFB_BatchReset (); + TFB_EnqueueDrawCommand (&DrawCommand); + Unlock_DCQ(); + SetSemaphore (s); +} + +void +TFB_DrawScreen_ReinitVideo (int driver, int flags, int width, int height) +{ + TFB_DrawCommand DrawCommand; + DrawCommand.Type = TFB_DRAWCOMMANDTYPE_REINITVIDEO; + DrawCommand.data.reinitvideo.driver = driver; + DrawCommand.data.reinitvideo.flags = flags; + DrawCommand.data.reinitvideo.width = width; + DrawCommand.data.reinitvideo.height = height; + TFB_EnqueueDrawCommand (&DrawCommand); +} + +void +TFB_DrawScreen_Callback (void (*callback) (void *arg), void *arg) +{ + TFB_DrawCommand DrawCommand; + DrawCommand.Type = TFB_DRAWCOMMANDTYPE_CALLBACK; + DrawCommand.data.callback.callback = callback; + DrawCommand.data.callback.arg = arg; + TFB_EnqueueDrawCommand(&DrawCommand); +} + +void +TFB_DrawImage_Line (int x1, int y1, int x2, int y2, Color color, + DrawMode mode, TFB_Image *target) +{ + LockMutex (target->mutex); + TFB_DrawCanvas_Line (x1, y1, x2, y2, color, mode, target->NormalImg); + target->dirty = TRUE; + UnlockMutex (target->mutex); +} + +void +TFB_DrawImage_Rect (RECT *rect, Color color, DrawMode mode, TFB_Image *target) +{ + LockMutex (target->mutex); + TFB_DrawCanvas_Rect (rect, color, mode, target->NormalImg); + target->dirty = TRUE; + UnlockMutex (target->mutex); +} + +void +TFB_DrawImage_Image (TFB_Image *img, int x, int y, int scale, + int scaleMode, TFB_ColorMap *cmap, DrawMode mode, TFB_Image *target) +{ + LockMutex (target->mutex); + TFB_DrawCanvas_Image (img, x, y, scale, scaleMode, cmap, + mode, target->NormalImg); + target->dirty = TRUE; + UnlockMutex (target->mutex); +} + +void +TFB_DrawImage_FilledImage (TFB_Image *img, int x, int y, int scale, + int scaleMode, Color color, DrawMode mode, TFB_Image *target) +{ + LockMutex (target->mutex); + TFB_DrawCanvas_FilledImage (img, x, y, scale, scaleMode, color, + mode, target->NormalImg); + target->dirty = TRUE; + UnlockMutex (target->mutex); +} + +void +TFB_DrawImage_FontChar (TFB_Char *fontChar, TFB_Image *backing, + int x, int y, DrawMode mode, TFB_Image *target) +{ + LockMutex (target->mutex); + TFB_DrawCanvas_FontChar (fontChar, backing, x, y, mode, target->NormalImg); + target->dirty = TRUE; + UnlockMutex (target->mutex); +} + + +TFB_Image * +TFB_DrawImage_New (TFB_Canvas canvas) +{ + TFB_Image *img = HMalloc (sizeof (TFB_Image)); + img->mutex = CreateMutex ("image lock", SYNC_CLASS_VIDEO); + img->ScaledImg = NULL; + img->MipmapImg = NULL; + img->FilledImg = NULL; + img->colormap_index = -1; + img->colormap_version = 0; + img->NormalHs = NullHs; + img->MipmapHs = NullHs; + img->last_scale_hs = NullHs; + img->last_scale_type = -1; + img->last_scale = 0; + img->dirty = FALSE; + TFB_DrawCanvas_GetExtent (canvas, &img->extent); + + if (TFB_DrawCanvas_IsPaletted (canvas)) + { + img->NormalImg = canvas; + } + else + { + img->NormalImg = TFB_DrawCanvas_ToScreenFormat (canvas); + } + + return img; +} + +TFB_Image* +TFB_DrawImage_CreateForScreen (int w, int h, BOOLEAN withalpha) +{ + TFB_Image* img = HMalloc (sizeof (TFB_Image)); + img->mutex = CreateMutex ("image lock", SYNC_CLASS_VIDEO); + img->ScaledImg = NULL; + img->MipmapImg = NULL; + img->FilledImg = NULL; + img->colormap_index = -1; + img->colormap_version = 0; + img->NormalHs = NullHs; + img->MipmapHs = NullHs; + img->last_scale_hs = NullHs; + img->last_scale_type = -1; + img->last_scale = 0; + img->extent.width = w; + img->extent.height = h; + + img->NormalImg = TFB_DrawCanvas_New_ForScreen (w, h, withalpha); + + return img; +} + +TFB_Image * +TFB_DrawImage_New_Rotated (TFB_Image *img, int angle) +{ + TFB_Canvas dst; + EXTENT size; + TFB_Image* newimg; + + /* sanity check */ + if (!img->NormalImg) + { + log_add (log_Warning, "TFB_DrawImage_New_Rotated: " + "source canvas is NULL! Failing."); + return NULL; + } + + TFB_DrawCanvas_GetRotatedExtent (img->NormalImg, angle, &size); + dst = TFB_DrawCanvas_New_RotationTarget (img->NormalImg, angle); + if (!dst) + { + log_add (log_Warning, "TFB_DrawImage_New_Rotated: " + "rotation target canvas not created! Failing."); + return NULL; + } + TFB_DrawCanvas_Rotate (img->NormalImg, dst, angle, size); + + newimg = TFB_DrawImage_New (dst); + return newimg; +} + +void +TFB_DrawImage_SetMipmap (TFB_Image *img, TFB_Image *mmimg, int hotx, int hoty) +{ + bool imgpal; + bool mmpal; + + if (!img || !mmimg) + return; + + LockMutex (img->mutex); + LockMutex (mmimg->mutex); + + // Either both images must be using the same colormap, or mipmap image + // must not be paletted. This restriction is due to the current + // implementation of fill-stamp, which replaces the palette with + // fill color. + imgpal = TFB_DrawCanvas_IsPaletted (img->NormalImg); + mmpal = TFB_DrawCanvas_IsPaletted (mmimg->NormalImg); + if (!mmpal || (mmpal && imgpal && + img->colormap_index == mmimg->colormap_index)) + { + img->MipmapImg = mmimg->NormalImg; + img->MipmapHs.x = hotx; + img->MipmapHs.y = hoty; + } + else + { + img->MipmapImg = NULL; + } + + UnlockMutex (mmimg->mutex); + UnlockMutex (img->mutex); +} + +void +TFB_DrawImage_Delete (TFB_Image *image) +{ + if (image == 0) + { + log_add (log_Warning, "INTERNAL ERROR: Tried to delete a null image!"); + /* Should we die here? */ + return; + } + LockMutex (image->mutex); + + TFB_DrawCanvas_Delete (image->NormalImg); + + if (image->ScaledImg) + { + TFB_DrawCanvas_Delete (image->ScaledImg); + image->ScaledImg = 0; + } + + if (image->FilledImg) + { + TFB_DrawCanvas_Delete (image->FilledImg); + image->FilledImg = 0; + } + + UnlockMutex (image->mutex); + DestroyMutex (image->mutex); + + HFree (image); +} + +void +TFB_DrawImage_FixScaling (TFB_Image *image, int target, int type) +{ + if (image->dirty || !image->ScaledImg || + target != image->last_scale || + type != image->last_scale_type) + { + image->dirty = FALSE; + image->ScaledImg = TFB_DrawCanvas_New_ScaleTarget (image->NormalImg, + image->ScaledImg, type, image->last_scale_type); + + if (type == TFB_SCALE_NEAREST) + TFB_DrawCanvas_Rescale_Nearest (image->NormalImg, + image->ScaledImg, target, &image->NormalHs, + &image->extent, &image->last_scale_hs); + else if (type == TFB_SCALE_BILINEAR) + TFB_DrawCanvas_Rescale_Bilinear (image->NormalImg, + image->ScaledImg, target, &image->NormalHs, + &image->extent, &image->last_scale_hs); + else + TFB_DrawCanvas_Rescale_Trilinear (image->NormalImg, + image->MipmapImg, image->ScaledImg, target, + &image->NormalHs, &image->MipmapHs, + &image->extent, &image->last_scale_hs); + + image->last_scale_type = type; + image->last_scale = target; + } +} + +BOOLEAN +TFB_DrawImage_Intersect (TFB_Image *img1, POINT img1org, + TFB_Image *img2, POINT img2org, const RECT *interRect) +{ + BOOLEAN ret; + + LockMutex (img1->mutex); + LockMutex (img2->mutex); + ret = TFB_DrawCanvas_Intersect (img1->NormalImg, img1org, + img2->NormalImg, img2org, interRect); + UnlockMutex (img2->mutex); + UnlockMutex (img1->mutex); + + return ret; +} + +void +TFB_DrawImage_CopyRect (TFB_Image *source, const RECT *srcRect, + TFB_Image *target, POINT dstPt) +{ + LockMutex (source->mutex); + LockMutex (target->mutex); + TFB_DrawCanvas_CopyRect (source->NormalImg, srcRect, + target->NormalImg, dstPt); + target->dirty = TRUE; + UnlockMutex (target->mutex); + UnlockMutex (source->mutex); +} diff --git a/src/libs/graphics/tfb_draw.h b/src/libs/graphics/tfb_draw.h new file mode 100644 index 0000000..11de5b0 --- /dev/null +++ b/src/libs/graphics/tfb_draw.h @@ -0,0 +1,199 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef TFB_DRAW_H +#define TFB_DRAW_H + +#include "libs/threadlib.h" + + +typedef void *TFB_Canvas; + +typedef enum { + TFB_SCREEN_MAIN, + TFB_SCREEN_EXTRA, + TFB_SCREEN_TRANSITION, + + TFB_GFX_NUMSCREENS +} SCREEN; + +#include "libs/graphics/gfx_common.h" +#include "libs/graphics/cmap.h" + +typedef struct tfb_image +{ + TFB_Canvas NormalImg; + TFB_Canvas ScaledImg; + TFB_Canvas MipmapImg; + TFB_Canvas FilledImg; + int colormap_index; + int colormap_version; + HOT_SPOT NormalHs; + HOT_SPOT MipmapHs; + HOT_SPOT last_scale_hs; + int last_scale; + int last_scale_type; + Color last_fill; + EXTENT extent; + Mutex mutex; + BOOLEAN dirty; +} TFB_Image; + +typedef struct tfb_char +{ + EXTENT extent; + EXTENT disp; + // Display extent + HOT_SPOT HotSpot; + BYTE* data; + DWORD pitch; + // Pitch is for storing all chars of a page + // in one rectangular pixel matrix +} TFB_Char; + +// we do not support paletted format for now +typedef struct tfb_pixelformat +{ + int BitsPerPixel; + int BytesPerPixel; + DWORD Rmask, Gmask, Bmask, Amask; + DWORD Rshift, Gshift, Bshift, Ashift; + DWORD Rloss, Gloss, Bloss, Aloss; +} TFB_PixelFormat; + +// Drawing commands + +void TFB_DrawScreen_Line (int x1, int y1, int x2, int y2, Color color, + DrawMode, SCREEN dest); +void TFB_DrawScreen_Rect (RECT *rect, Color, DrawMode, SCREEN dest); +void TFB_DrawScreen_Image (TFB_Image *img, int x, int y, int scale, + int scaleMode, TFB_ColorMap *, DrawMode, SCREEN dest); +void TFB_DrawScreen_Copy (const RECT *r, SCREEN src, SCREEN dest); +void TFB_DrawScreen_FilledImage (TFB_Image *img, int x, int y, int scale, + int scaleMode, Color, DrawMode, SCREEN dest); +void TFB_DrawScreen_FontChar (TFB_Char *, TFB_Image *backing, int x, int y, + DrawMode, SCREEN dest); + +void TFB_DrawScreen_CopyToImage (TFB_Image *img, const RECT *r, SCREEN src); +void TFB_DrawScreen_SetMipmap (TFB_Image *img, TFB_Image *mmimg, int hotx, + int hoty); +void TFB_DrawScreen_DeleteImage (TFB_Image *img); +void TFB_DrawScreen_DeleteData (void *); +void TFB_DrawScreen_WaitForSignal (void); +void TFB_DrawScreen_ReinitVideo (int driver, int flags, int width, int height); +void TFB_DrawScreen_Callback (void (*callback) (void *arg), void *arg); + +TFB_Image *TFB_DrawImage_New (TFB_Canvas canvas); +TFB_Image *TFB_DrawImage_CreateForScreen (int w, int h, BOOLEAN withalpha); +TFB_Image *TFB_DrawImage_New_Rotated (TFB_Image *img, int angle); +void TFB_DrawImage_SetMipmap (TFB_Image *img, TFB_Image *mmimg, int hotx, + int hoty); +void TFB_DrawImage_Delete (TFB_Image *image); +void TFB_DrawImage_FixScaling (TFB_Image *image, int target, int type); +BOOLEAN TFB_DrawImage_Intersect (TFB_Image *img1, POINT img1org, + TFB_Image *img2, POINT img2org, const RECT *interRect); +void TFB_DrawImage_CopyRect (TFB_Image *source, const RECT *srcRect, + TFB_Image *target, POINT dstPt); + +void TFB_DrawImage_Line (int x1, int y1, int x2, int y2, Color color, + DrawMode, TFB_Image *target); +void TFB_DrawImage_Rect (RECT *rect, Color, DrawMode, TFB_Image *target); +void TFB_DrawImage_Image (TFB_Image *img, int x, int y, int scale, + int scaleMode, TFB_ColorMap *, DrawMode, TFB_Image *target); +void TFB_DrawImage_FilledImage (TFB_Image *img, int x, int y, int scale, + int scaleMode, Color, DrawMode, TFB_Image *target); +void TFB_DrawImage_FontChar (TFB_Char *, TFB_Image *backing, int x, int y, + DrawMode, TFB_Image *target); + +TFB_Canvas TFB_DrawCanvas_LoadFromFile (void *dir, const char *fileName); +TFB_Canvas TFB_DrawCanvas_New_TrueColor (int w, int h, BOOLEAN hasalpha); +TFB_Canvas TFB_DrawCanvas_New_ForScreen (int w, int h, BOOLEAN withalpha); +TFB_Canvas TFB_DrawCanvas_New_Paletted (int w, int h, Color palette[256], + int transparent_index); +TFB_Canvas TFB_DrawCanvas_New_ScaleTarget (TFB_Canvas canvas, + TFB_Canvas oldcanvas, int type, int last_type); +TFB_Canvas TFB_DrawCanvas_New_RotationTarget (TFB_Canvas src, int angle); +TFB_Canvas TFB_DrawCanvas_ToScreenFormat (TFB_Canvas canvas); +BOOLEAN TFB_DrawCanvas_IsPaletted (TFB_Canvas canvas); +void TFB_DrawCanvas_Rescale_Nearest (TFB_Canvas src, TFB_Canvas dst, + int scale, HOT_SPOT* src_hs, EXTENT* size, HOT_SPOT* dst_hs); +void TFB_DrawCanvas_Rescale_Bilinear (TFB_Canvas src, TFB_Canvas dst, + int scale, HOT_SPOT* src_hs, EXTENT* size, HOT_SPOT* dst_hs); +void TFB_DrawCanvas_Rescale_Trilinear (TFB_Canvas src, TFB_Canvas mipmap, + TFB_Canvas dst, int scale, HOT_SPOT* src_hs, HOT_SPOT* mm_hs, + EXTENT* size, HOT_SPOT* dst_hs); +void TFB_DrawCanvas_GetScaledExtent (TFB_Canvas src_canvas, HOT_SPOT* src_hs, + TFB_Canvas src_mipmap, HOT_SPOT* mm_hs, + int scale, int type, EXTENT *size, HOT_SPOT *hs); +void TFB_DrawCanvas_Rotate (TFB_Canvas src, TFB_Canvas dst, int angle, + EXTENT size); +void TFB_DrawCanvas_GetRotatedExtent (TFB_Canvas src, int angle, EXTENT *size); +void TFB_DrawCanvas_GetExtent (TFB_Canvas canvas, EXTENT *size); +void TFB_DrawCanvas_SetClipRect (TFB_Canvas canvas, const RECT *clipRect); + +void TFB_DrawCanvas_Delete (TFB_Canvas canvas); + +void TFB_DrawCanvas_Line (int x1, int y1, int x2, int y2, Color color, + DrawMode, TFB_Canvas target); +void TFB_DrawCanvas_Rect (RECT *rect, Color, DrawMode, TFB_Canvas target); +void TFB_DrawCanvas_Image (TFB_Image *img, int x, int y, int scale, + int scaleMode, TFB_ColorMap *, DrawMode, TFB_Canvas target); +void TFB_DrawCanvas_FilledImage (TFB_Image *img, int x, int y, int scale, + int scaleMode, Color, DrawMode, TFB_Canvas target); +void TFB_DrawCanvas_FontChar (TFB_Char *, TFB_Image *backing, int x, int y, + DrawMode, TFB_Canvas target); +void TFB_DrawCanvas_CopyRect (TFB_Canvas source, const RECT *srcRect, + TFB_Canvas target, POINT dstPt); + +BOOLEAN TFB_DrawCanvas_GetFontCharData (TFB_Canvas canvas, BYTE *outData, + unsigned dataPitch); +Color *TFB_DrawCanvas_ExtractPalette (TFB_Canvas canvas); +void TFB_DrawCanvas_SetPalette (TFB_Canvas target, Color palette[256]); +int TFB_DrawCanvas_GetTransparentIndex (TFB_Canvas canvas); +void TFB_DrawCanvas_SetTransparentIndex (TFB_Canvas canvas, int i, + BOOLEAN rleaccel); +BOOLEAN TFB_DrawCanvas_GetTransparentColor (TFB_Canvas canvas, + Color *color); +void TFB_DrawCanvas_SetTransparentColor (TFB_Canvas canvas, + Color color, BOOLEAN rleaccel); +void TFB_DrawCanvas_CopyTransparencyInfo (TFB_Canvas src, TFB_Canvas dst); +void TFB_DrawCanvas_Initialize (void); +void TFB_DrawCanvas_Lock (TFB_Canvas canvas); +void TFB_DrawCanvas_Unlock (TFB_Canvas canvas); +void TFB_DrawCanvas_GetScreenFormat (TFB_PixelFormat *fmt); +int TFB_DrawCanvas_GetStride (TFB_Canvas canvas); +void *TFB_DrawCanvas_GetLine (TFB_Canvas canvas, int line); +Color TFB_DrawCanvas_GetPixel (TFB_Canvas canvas, int x, int y); +BOOLEAN TFB_DrawCanvas_Intersect (TFB_Canvas canvas1, POINT c1org, + TFB_Canvas canvas2, POINT c2org, const RECT *interRect); + +BOOLEAN TFB_DrawCanvas_GetPixelColors (TFB_Canvas, Color *pixels, + int width, int height); +BOOLEAN TFB_DrawCanvas_SetPixelColors (TFB_Canvas, const Color *pixels, + int width, int height); +BOOLEAN TFB_DrawCanvas_GetPixelIndexes (TFB_Canvas, BYTE *data, + int width, int height); +BOOLEAN TFB_DrawCanvas_SetPixelIndexes (TFB_Canvas, const BYTE *data, + int width, int height); + +const char *TFB_DrawCanvas_GetError (void); + +TFB_Canvas TFB_GetScreenCanvas (SCREEN screen); + +#endif + diff --git a/src/libs/graphics/tfb_prim.c b/src/libs/graphics/tfb_prim.c new file mode 100644 index 0000000..f8a2df4 --- /dev/null +++ b/src/libs/graphics/tfb_prim.c @@ -0,0 +1,237 @@ +// Copyright Michael Martin, 2003 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* The original Primitive routines do various elaborate checks to + * ensure we're within bounds for the clipping. Since clipping is + * handled by the underlying TFB_Canvas implementation, we need not + * worry about this. */ + +#include "gfxintrn.h" +#include "gfx_common.h" +#include "tfb_draw.h" +#include "tfb_prim.h" +#include "cmap.h" +#include "libs/log.h" + +void +TFB_Prim_Point (POINT *p, Color color, DrawMode mode, POINT ctxOrigin) +{ + RECT r; + + // The caller must scale the origin! + r.corner.x = p->x + ctxOrigin.x; + r.corner.y = p->y + ctxOrigin.y; + r.extent.width = r.extent.height = 1; + + if (_CurFramePtr->Type == SCREEN_DRAWABLE) + TFB_DrawScreen_Rect (&r, color, mode, TFB_SCREEN_MAIN); + else + TFB_DrawImage_Rect (&r, color, mode, _CurFramePtr->image); +} + +void +TFB_Prim_Rect (RECT *r, Color color, DrawMode mode, POINT ctxOrigin) +{ + RECT arm; + int gscale; + + // XXX: Rect prim scaling is currently unused + // We scale the rect size just to be consistent with stamp prim, + // which does same. The caller must scale the origin! + gscale = GetGraphicScale (); + arm = *r; + arm.extent.width = r->extent.width; + arm.extent.height = 1; + TFB_Prim_FillRect (&arm, color, mode, ctxOrigin); + arm.extent.height = r->extent.height; + arm.extent.width = 1; + TFB_Prim_FillRect (&arm, color, mode, ctxOrigin); + // rounding error correction here + arm.corner.x += ((r->extent.width * gscale + (GSCALE_IDENTITY >> 1)) + / GSCALE_IDENTITY) - 1; + TFB_Prim_FillRect (&arm, color, mode, ctxOrigin); + arm.corner.x = r->corner.x; + arm.corner.y += ((r->extent.height * gscale + (GSCALE_IDENTITY >> 1)) + / GSCALE_IDENTITY) - 1; + arm.extent.width = r->extent.width; + arm.extent.height = 1; + TFB_Prim_FillRect (&arm, color, mode, ctxOrigin); +} + +void +TFB_Prim_FillRect (RECT *r, Color color, DrawMode mode, POINT ctxOrigin) +{ + RECT rect; + int gscale; + + rect.corner.x = r->corner.x + ctxOrigin.x; + rect.corner.y = r->corner.y + ctxOrigin.y; + rect.extent.width = r->extent.width; + rect.extent.height = r->extent.height; + + // XXX: Rect prim scaling is currently unused + // We scale the rect size just to be consistent with stamp prim, + // which does same. The caller must scale the origin! + gscale = GetGraphicScale (); + if (gscale != GSCALE_IDENTITY) + { // rounding error correction here + rect.extent.width = (rect.extent.width * gscale + + (GSCALE_IDENTITY >> 1)) / GSCALE_IDENTITY; + rect.extent.height = (rect.extent.height * gscale + + (GSCALE_IDENTITY >> 1)) / GSCALE_IDENTITY; + } + + if (_CurFramePtr->Type == SCREEN_DRAWABLE) + TFB_DrawScreen_Rect (&rect, color, mode, TFB_SCREEN_MAIN); + else + TFB_DrawImage_Rect (&rect, color, mode, _CurFramePtr->image); +} + +void +TFB_Prim_Line (LINE *line, Color color, DrawMode mode, POINT ctxOrigin) +{ + int x1, y1, x2, y2; + + // The caller must scale the origins! + x1=line->first.x + ctxOrigin.x; + y1=line->first.y + ctxOrigin.y; + x2=line->second.x + ctxOrigin.x; + y2=line->second.y + ctxOrigin.y; + + if (_CurFramePtr->Type == SCREEN_DRAWABLE) + TFB_DrawScreen_Line (x1, y1, x2, y2, color, mode, TFB_SCREEN_MAIN); + else + TFB_DrawImage_Line (x1, y1, x2, y2, color, mode, _CurFramePtr->image); +} + +void +TFB_Prim_Stamp (STAMP *stmp, DrawMode mode, POINT ctxOrigin) +{ + int x, y; + FRAME SrcFramePtr; + TFB_Image *img; + TFB_ColorMap *cmap = NULL; + + SrcFramePtr = stmp->frame; + if (!SrcFramePtr) + { + log_add (log_Warning, "TFB_Prim_Stamp: Tried to draw a NULL frame" + " (Stamp address = %p)", (void *) stmp); + return; + } + img = SrcFramePtr->image; + + if (!img) + { + log_add (log_Warning, "Non-existent image to TFB_Prim_Stamp()"); + return; + } + + LockMutex (img->mutex); + + img->NormalHs = SrcFramePtr->HotSpot; + // We scale the image size here, but the caller must scale the origin! + x = stmp->origin.x + ctxOrigin.x; + y = stmp->origin.y + ctxOrigin.y; + + if (TFB_DrawCanvas_IsPaletted(img->NormalImg) && img->colormap_index != -1) + { + // returned cmap is addrefed, must release later + cmap = TFB_GetColorMap (img->colormap_index); + } + + UnlockMutex (img->mutex); + + if (_CurFramePtr->Type == SCREEN_DRAWABLE) + { + TFB_DrawScreen_Image (img, x, y, GetGraphicScale (), + GetGraphicScaleMode (), cmap, mode, TFB_SCREEN_MAIN); + } + else + { + TFB_DrawImage_Image (img, x, y, GetGraphicScale (), + GetGraphicScaleMode (), cmap, mode, _CurFramePtr->image); + } +} + +void +TFB_Prim_StampFill (STAMP *stmp, Color color, DrawMode mode, POINT ctxOrigin) +{ + int x, y; + FRAME SrcFramePtr; + TFB_Image *img; + + SrcFramePtr = stmp->frame; + if (!SrcFramePtr) + { + log_add (log_Warning, "TFB_Prim_StampFill: Tried to draw a NULL frame" + " (Stamp address = %p)", (void *) stmp); + return; + } + img = SrcFramePtr->image; + + if (!img) + { + log_add (log_Warning, "Non-existent image to TFB_Prim_StampFill()"); + return; + } + + LockMutex (img->mutex); + + img->NormalHs = SrcFramePtr->HotSpot; + // We scale the image size here, but the caller must scale the origin! + x = stmp->origin.x + ctxOrigin.x; + y = stmp->origin.y + ctxOrigin.y; + + UnlockMutex (img->mutex); + + if (_CurFramePtr->Type == SCREEN_DRAWABLE) + { + TFB_DrawScreen_FilledImage (img, x, y, GetGraphicScale (), + GetGraphicScaleMode (), color, mode, TFB_SCREEN_MAIN); + } + else + { + TFB_DrawImage_FilledImage (img, x, y, GetGraphicScale (), + GetGraphicScaleMode (), color, mode, _CurFramePtr->image); + } +} + +void +TFB_Prim_FontChar (POINT charOrigin, TFB_Char *fontChar, TFB_Image *backing, + DrawMode mode, POINT ctxOrigin) +{ + int x, y; + + // Text prim does not scale + x = charOrigin.x + ctxOrigin.x; + y = charOrigin.y + ctxOrigin.y; + + if (_CurFramePtr->Type == SCREEN_DRAWABLE) + { + TFB_DrawScreen_FontChar (fontChar, backing, x, y, mode, + TFB_SCREEN_MAIN); + } + else + { + TFB_DrawImage_FontChar (fontChar, backing, x, y, mode, + _CurFramePtr->image); + } +} + +// Text rendering is in font.c, under the name _text_blt diff --git a/src/libs/graphics/tfb_prim.h b/src/libs/graphics/tfb_prim.h new file mode 100644 index 0000000..6fcd546 --- /dev/null +++ b/src/libs/graphics/tfb_prim.h @@ -0,0 +1,30 @@ +// Copyright Michael Martin, 2003 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "libs/gfxlib.h" +#include "tfb_draw.h" + + +void TFB_Prim_Line (LINE *, Color, DrawMode, POINT ctxOrigin); +void TFB_Prim_Point (POINT *, Color, DrawMode, POINT ctxOrigin); +void TFB_Prim_Rect (RECT *, Color, DrawMode, POINT ctxOrigin); +void TFB_Prim_FillRect (RECT *, Color, DrawMode, POINT ctxOrigin); +void TFB_Prim_Stamp (STAMP *, DrawMode, POINT ctxOrigin); +void TFB_Prim_StampFill (STAMP *, Color, DrawMode, POINT ctxOrigin); +void TFB_Prim_FontChar (POINT charOrigin, TFB_Char *fontChar, + TFB_Image *backing, DrawMode, POINT ctxOrigin); diff --git a/src/libs/graphics/widgets.c b/src/libs/graphics/widgets.c new file mode 100644 index 0000000..0c444bc --- /dev/null +++ b/src/libs/graphics/widgets.c @@ -0,0 +1,941 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "gfx_common.h" +#include "widgets.h" +#include "libs/strlib.h" + +WIDGET *widget_focus = NULL; + +/* Some basic color defines */ +#define WIDGET_ACTIVE_COLOR \ + BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x00), 0x0E) +#define WIDGET_INACTIVE_COLOR \ + BUILD_COLOR (MAKE_RGB15 (0x18, 0x18, 0x1F), 0x00) +#define WIDGET_INACTIVE_SELECTED_COLOR \ + BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x1F), 0x0F) +#define WIDGET_CURSOR_COLOR \ + BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x00), 0x00) +#define WIDGET_DIALOG_COLOR \ + BUILD_COLOR (MAKE_RGB15 (0x14, 0x14, 0x14), 0x07) +#define WIDGET_DIALOG_TEXT_COLOR \ + BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x00), 0x00) + +static Color win_bg_clr = + BUILD_COLOR (MAKE_RGB15_INIT (0x18, 0x18, 0x1F), 0x00); +static Color win_medium_clr = + BUILD_COLOR (MAKE_RGB15_INIT (0x10, 0x10, 0x18), 0x00); +static Color win_dark_clr = + BUILD_COLOR (MAKE_RGB15_INIT (0x08, 0x08, 0x10), 0x00); + +static FONT cur_font; + +void +DrawShadowedBox (RECT *r, Color bg, Color dark, Color medium) +{ + RECT t; + Color oldcolor; + + BatchGraphics (); + + t.corner.x = r->corner.x - 2; + t.corner.y = r->corner.y - 2; + t.extent.width = r->extent.width + 4; + t.extent.height = r->extent.height + 4; + oldcolor = SetContextForeGroundColor (dark); + DrawFilledRectangle (&t); + + t.corner.x += 2; + t.corner.y += 2; + t.extent.width -= 2; + t.extent.height -= 2; + SetContextForeGroundColor (medium); + DrawFilledRectangle (&t); + + t.corner.x -= 1; + t.corner.y += r->extent.height + 1; + t.extent.height = 1; + DrawFilledRectangle (&t); + + t.corner.x += r->extent.width + 2; + t.corner.y -= r->extent.height + 2; + t.extent.width = 1; + DrawFilledRectangle (&t); + + SetContextForeGroundColor (bg); + DrawFilledRectangle (r); + + SetContextForeGroundColor (oldcolor); + UnbatchGraphics (); +} + +// windowRect, if not NULL, will be filled with the dimensions of the +// window drawn. +void +DrawLabelAsWindow (WIDGET_LABEL *label, RECT *windowRect) +{ + Color oldfg = SetContextForeGroundColor (WIDGET_DIALOG_TEXT_COLOR); + FONT oldfont = 0; + FRAME oldFontEffect = SetContextFontEffect (NULL); + RECT r; + TEXT t; + int i, win_w, win_h; + + if (cur_font) + oldfont = SetContextFont (cur_font); + + /* Compute the dimensions of the label */ + win_h = label->height ((WIDGET *)label) + 16; + win_w = 0; + for (i = 0; i < label->line_count; i++) + { + int len = utf8StringCount (label->lines[i]); + if (len > win_w) + { + win_w = len; + } + } + win_w = (win_w * 6) + 16; + + BatchGraphics (); + r.corner.x = (ScreenWidth - win_w) >> 1; + r.corner.y = (ScreenHeight - win_h) >> 1; + r.extent.width = win_w; + r.extent.height = win_h; + DrawShadowedBox (&r, win_bg_clr, win_dark_clr, win_medium_clr); + + t.baseline.x = r.corner.x + (r.extent.width >> 1); + t.baseline.y = r.corner.y + 16; + for (i = 0; i < label->line_count; i++) + { + t.pStr = label->lines[i]; + t.align = ALIGN_CENTER; + t.CharCount = (COUNT)~0; + font_DrawText (&t); + t.baseline.y += 8; + } + + UnbatchGraphics (); + + SetContextFontEffect (oldFontEffect); + if (oldfont) + SetContextFont (oldfont); + SetContextForeGroundColor (oldfg); + + if (windowRect != NULL) { + // Add the outer border added by DrawShadowedBox. + // XXX: It may be nicer to add a border size parameter to + // DrawShadowedBox, instead of assuming 2 here. + windowRect->corner.x = r.corner.x - 2; + windowRect->corner.y = r.corner.y - 2; + windowRect->extent.width = r.extent.width + 4; + windowRect->extent.height = r.extent.height + 4; + } +} + +void +Widget_SetWindowColors (Color bg, Color dark, Color medium) +{ + win_bg_clr = bg; + win_dark_clr = dark; + win_medium_clr = medium; +} + +FONT +Widget_SetFont (FONT newFont) +{ + FONT oldFont = cur_font; + cur_font = newFont; + return oldFont; +} + +static void +Widget_DrawToolTips (int numlines, const char **tips) +{ + RECT r; + FONT oldfont = 0; + FRAME oldFontEffect = SetContextFontEffect (NULL); + Color oldtext = SetContextForeGroundColor (WIDGET_INACTIVE_SELECTED_COLOR); + TEXT t; + int i; + + if (cur_font) + oldfont = SetContextFont (cur_font); + + r.corner.x = 2; + r.corner.y = 2; + r.extent.width = ScreenWidth - 4; + r.extent.height = ScreenHeight - 4; + + t.align = ALIGN_CENTER; + t.CharCount = ~0; + t.baseline.x = r.corner.x + (r.extent.width >> 1); + t.baseline.y = r.corner.y + (r.extent.height - 8 - 8 * numlines); + + for (i = 0; i < numlines; i++) + { + t.pStr = tips[i]; + font_DrawText(&t); + t.baseline.y += 8; + } + + SetContextFontEffect (oldFontEffect); + if (oldfont) + SetContextFont (oldfont); + SetContextForeGroundColor (oldtext); +} + +void +Widget_DrawMenuScreen (WIDGET *_self, int x, int y) +{ + RECT r; + Color title, oldtext; + Color inactive, default_color, selected; + FONT oldfont = 0; + FRAME oldFontEffect = SetContextFontEffect (NULL); + TEXT t; + int widget_index, height, widget_y; + + WIDGET_MENU_SCREEN *self = (WIDGET_MENU_SCREEN *)_self; + + if (cur_font) + oldfont = SetContextFont (cur_font); + + r.corner.x = 2; + r.corner.y = 2; + r.extent.width = ScreenWidth - 4; + r.extent.height = ScreenHeight - 4; + + title = WIDGET_INACTIVE_SELECTED_COLOR; + selected = WIDGET_ACTIVE_COLOR; + inactive = WIDGET_INACTIVE_COLOR; + default_color = title; + + DrawStamp (&self->bgStamp); + + oldtext = SetContextForeGroundColor (title); + t.baseline.x = r.corner.x + (r.extent.width >> 1); + t.baseline.y = r.corner.y + 8; + t.pStr = self->title; + t.align = ALIGN_CENTER; + t.CharCount = ~0; + font_DrawText (&t); + t.baseline.y += 8; + t.pStr = self->subtitle; + font_DrawText (&t); + + height = 0; + for (widget_index = 0; widget_index < self->num_children; widget_index++) + { + WIDGET *child = self->child[widget_index]; + height += (*child->height)(child); + height += 8; /* spacing */ + } + + height -= 8; + + widget_y = (ScreenHeight - height) >> 1; + for (widget_index = 0; widget_index < self->num_children; widget_index++) + { + WIDGET *c = self->child[widget_index]; + (*c->draw)(c, 0, widget_y); + widget_y += (*c->height)(c) + 8; + } + + SetContextFontEffect (oldFontEffect); + if (oldfont) + SetContextFont (oldfont); + SetContextForeGroundColor (oldtext); + + (void) x; + (void) y; +} + +void +Widget_DrawChoice (WIDGET *_self, int x, int y) +{ + WIDGET_CHOICE *self = (WIDGET_CHOICE *)_self; + Color oldtext; + Color inactive, default_color, selected; + FONT oldfont = 0; + FRAME oldFontEffect = SetContextFontEffect (NULL); + TEXT t; + int i, home_x, home_y; + + if (cur_font) + oldfont = SetContextFont (cur_font); + + default_color = WIDGET_INACTIVE_SELECTED_COLOR; + selected = WIDGET_ACTIVE_COLOR; + inactive = WIDGET_INACTIVE_COLOR; + + t.baseline.x = x; + t.baseline.y = y; + t.align = ALIGN_LEFT; + t.CharCount = ~0; + t.pStr = self->category; + if (widget_focus == _self) + { + oldtext = SetContextForeGroundColor (selected); + } + else + { + oldtext = SetContextForeGroundColor (default_color); + } + font_DrawText (&t); + + home_x = t.baseline.x + 3 * (ScreenWidth / ((self->maxcolumns + 1) * 2)); + home_y = t.baseline.y; + t.align = ALIGN_CENTER; + for (i = 0; i < self->numopts; i++) + { + t.baseline.x = home_x + ((i % 3) * + (ScreenWidth / (self->maxcolumns + 1))); + t.baseline.y = home_y + (8 * (i / 3)); + t.pStr = self->options[i].optname; + if ((widget_focus == _self) && + (self->highlighted == i)) + { + SetContextForeGroundColor (selected); + Widget_DrawToolTips (3, self->options[i].tooltip); + } + else if (i == self->selected) + { + SetContextForeGroundColor (default_color); + } + else + { + SetContextForeGroundColor (inactive); + } + font_DrawText (&t); + } + SetContextFontEffect (oldFontEffect); + if (oldfont) + SetContextFont (oldfont); + SetContextForeGroundColor (oldtext); +} + +void +Widget_DrawButton (WIDGET *_self, int x, int y) +{ + WIDGET_BUTTON *self = (WIDGET_BUTTON *)_self; + Color oldtext; + Color inactive, selected; + FONT oldfont = 0; + FRAME oldFontEffect = SetContextFontEffect (NULL); + TEXT t; + + if (cur_font) + oldfont = SetContextFont (cur_font); + + selected = WIDGET_ACTIVE_COLOR; + inactive = WIDGET_INACTIVE_COLOR; + + t.baseline.x = 160; + t.baseline.y = y; + t.align = ALIGN_CENTER; + t.CharCount = ~0; + t.pStr = self->name; + if (widget_focus == _self) + { + Widget_DrawToolTips (3, self->tooltip); + oldtext = SetContextForeGroundColor (selected); + } + else + { + oldtext = SetContextForeGroundColor (inactive); + } + font_DrawText (&t); + SetContextFontEffect (oldFontEffect); + if (oldfont) + SetContextFont (oldfont); + SetContextForeGroundColor (oldtext); + (void) x; +} + +void +Widget_DrawLabel (WIDGET *_self, int x, int y) +{ + WIDGET_LABEL *self = (WIDGET_LABEL *)_self; + Color oldtext = SetContextForeGroundColor (WIDGET_INACTIVE_SELECTED_COLOR); + FONT oldfont = 0; + FRAME oldFontEffect = SetContextFontEffect (NULL); + TEXT t; + int i; + + if (cur_font) + oldfont = SetContextFont (cur_font); + + t.baseline.x = 160; + t.baseline.y = y; + t.align = ALIGN_CENTER; + t.CharCount = ~0; + + for (i = 0; i < self->line_count; i++) + { + t.pStr = self->lines[i]; + font_DrawText (&t); + t.baseline.y += 8; + } + SetContextFontEffect (oldFontEffect); + if (oldfont) + SetContextFont (oldfont); + SetContextForeGroundColor (oldtext); + (void) x; +} + +void +Widget_DrawSlider(WIDGET *_self, int x, int y) +{ + WIDGET_SLIDER *self = (WIDGET_SLIDER *)_self; + Color oldtext; + Color inactive, default_color, selected; + FONT oldfont = 0; + FRAME oldFontEffect = SetContextFontEffect (NULL); + TEXT t; + RECT r; + int tick = (ScreenWidth - x) / 8; + + if (cur_font) + oldfont = SetContextFont (cur_font); + + default_color = WIDGET_INACTIVE_SELECTED_COLOR; + selected = WIDGET_ACTIVE_COLOR; + inactive = WIDGET_INACTIVE_COLOR; + + t.baseline.x = x; + t.baseline.y = y; + t.align = ALIGN_LEFT; + t.CharCount = ~0; + t.pStr = self->category; + if (widget_focus == _self) + { + Widget_DrawToolTips (3, self->tooltip); + oldtext = SetContextForeGroundColor (selected); + } + else + { + oldtext = SetContextForeGroundColor (default_color); + } + font_DrawText (&t); + + r.corner.x = t.baseline.x + 3 * tick; + r.corner.y = t.baseline.y - 4; + r.extent.height = 2; + r.extent.width = 3 * tick; + DrawFilledRectangle (&r); + + r.extent.width = 3; + r.extent.height = 8; + r.corner.y = t.baseline.y - 7; + r.corner.x = t.baseline.x + 3 * tick + (3 * tick * (self->value - self->min) / + (self->max - self->min)) - 1; + DrawFilledRectangle (&r); + + (*self->draw_value)(self, t.baseline.x + 7 * tick, t.baseline.y); + + SetContextFontEffect (oldFontEffect); + if (oldfont) + SetContextFont (oldfont); + SetContextForeGroundColor (oldtext); +} + +void +Widget_Slider_DrawValue (WIDGET_SLIDER *self, int x, int y) +{ + TEXT t; + char buffer[16]; + + sprintf (buffer, "%d", self->value); + + t.baseline.x = x; + t.baseline.y = y; + t.align = ALIGN_CENTER; + t.CharCount = ~0; + t.pStr = buffer; + + font_DrawText (&t); +} + +void +Widget_DrawTextEntry (WIDGET *_self, int x, int y) +{ + WIDGET_TEXTENTRY *self = (WIDGET_TEXTENTRY *)_self; + Color oldtext; + Color inactive, default_color, selected; + FONT oldfont = 0; + FRAME oldFontEffect = SetContextFontEffect (NULL); + TEXT t; + + if (cur_font) + oldfont = SetContextFont (cur_font); + + default_color = WIDGET_INACTIVE_SELECTED_COLOR; + selected = WIDGET_ACTIVE_COLOR; + inactive = WIDGET_INACTIVE_COLOR; + + BatchGraphics (); + + t.baseline.x = x; + t.baseline.y = y; + t.align = ALIGN_LEFT; + t.CharCount = ~0; + t.pStr = self->category; + if (widget_focus == _self) + { + oldtext = SetContextForeGroundColor (selected); + } + else + { + oldtext = SetContextForeGroundColor (default_color); + } + font_DrawText (&t); + + /* Force string termination */ + self->value[WIDGET_TEXTENTRY_WIDTH-1] = 0; + + t.baseline.y = y; + t.CharCount = utf8StringCount (self->value); + t.pStr = self->value; + + if (!(self->state & WTE_EDITING)) + { // normal or selected state + t.baseline.x = 160; + t.align = ALIGN_CENTER; + + if (widget_focus == _self) + { + oldtext = SetContextForeGroundColor (selected); + } + else + { + oldtext = SetContextForeGroundColor (inactive); + } + font_DrawText (&t); + } + else + { // editing state + COUNT i; + RECT text_r; + BYTE char_deltas[WIDGET_TEXTENTRY_WIDTH]; + BYTE *pchar_deltas; + RECT r; + SIZE leading; + + t.baseline.x = 90; + t.align = ALIGN_LEFT; + + // calc background box dimensions + // XXX: this may need some tuning, especially if a + // different font is used. The font 'leading' values + // are not what they should be. +#define BOX_VERT_OFFSET 2 + GetContextFontLeading (&leading); + r.corner.x = t.baseline.x - 1; + r.corner.y = t.baseline.y - leading + BOX_VERT_OFFSET; + r.extent.width = ScreenWidth - r.corner.x - 10; + r.extent.height = leading + 2; + + TextRect (&t, &text_r, char_deltas); +#if 0 + // XXX: this should potentially be used in ChangeCallback + if ((text_r.extent.width + 2) >= r.extent.width) + { // the text does not fit the input box size and so + // will not fit when displayed later + UnbatchGraphics (); + // disallow the change + return (FALSE); + } +#endif + + oldtext = SetContextForeGroundColor (selected); + DrawFilledRectangle (&r); + + // calculate the cursor position and draw it + pchar_deltas = char_deltas; + for (i = self->cursor_pos; i > 0; --i) + r.corner.x += (SIZE)*pchar_deltas++; + if (self->cursor_pos < t.CharCount) /* cursor mid-line */ + --r.corner.x; + if (self->state & WTE_BLOCKCUR) + { // Use block cursor for keyboardless systems + if (self->cursor_pos == t.CharCount) + { // cursor at end-line -- use insertion point + r.extent.width = 1; + } + else if (self->cursor_pos + 1 == t.CharCount) + { // extra pixel for last char margin + r.extent.width = (SIZE)*pchar_deltas + 2; + } + else + { // normal mid-line char + r.extent.width = (SIZE)*pchar_deltas + 1; + } + } + else + { // Insertion point cursor + r.extent.width = 1; + } + // position cursor within input field rect + ++r.corner.x; + ++r.corner.y; + r.extent.height -= 2; + SetContextForeGroundColor (WIDGET_CURSOR_COLOR); + DrawFilledRectangle (&r); + + SetContextForeGroundColor (inactive); + font_DrawText (&t); + } + + UnbatchGraphics (); + SetContextFontEffect (oldFontEffect); + if (oldfont) + SetContextFont (oldfont); + SetContextForeGroundColor (oldtext); +} + +void +Widget_DrawControlEntry (WIDGET *_self, int x, int y) +{ + WIDGET_CONTROLENTRY *self = (WIDGET_CONTROLENTRY *)_self; + Color oldtext; + Color inactive, default_color, selected; + FONT oldfont = 0; + FRAME oldFontEffect = SetContextFontEffect (NULL); + TEXT t; + int i, home_x, home_y; + + if (cur_font) + oldfont = SetContextFont (cur_font); + + default_color = WIDGET_INACTIVE_SELECTED_COLOR; + selected = WIDGET_ACTIVE_COLOR; + inactive = WIDGET_INACTIVE_COLOR; + + t.baseline.x = x; + t.baseline.y = y; + t.align = ALIGN_LEFT; + t.CharCount = ~0; + t.pStr = self->category; + if (widget_focus == _self) + { + oldtext = SetContextForeGroundColor (selected); + } + else + { + oldtext = SetContextForeGroundColor (default_color); + } + font_DrawText (&t); + + // 3 * ScreenWidth / ((self->maxcolumns + 1) * 2)) as per CHOICE, but only two options. + home_x = t.baseline.x + (ScreenWidth / 2); + home_y = t.baseline.y; + t.align = ALIGN_CENTER; + for (i = 0; i < 2; i++) + { + t.baseline.x = home_x + ((i % 3) * (ScreenWidth / 3)); // self->maxcolumns + 1 as per CHOICE. + t.baseline.y = home_y + (8 * (i / 3)); + t.pStr = self->controlname[i]; + if (!t.pStr[0]) + { + t.pStr = "---"; + } + if ((widget_focus == _self) && + (self->highlighted == i)) + { + SetContextForeGroundColor (selected); + } + else + { + SetContextForeGroundColor (default_color); + } + font_DrawText (&t); + } + SetContextFontEffect (oldFontEffect); + if (oldfont) + SetContextFont (oldfont); + SetContextForeGroundColor (oldtext); +} + +int +Widget_HeightChoice (WIDGET *_self) +{ + return ((((WIDGET_CHOICE *)_self)->numopts + 2) / 3) * 8; +} + +int +Widget_HeightFullScreen (WIDGET *_self) +{ + (void)_self; + return ScreenHeight; +} + +int +Widget_HeightOneLine (WIDGET *_self) +{ + (void)_self; + return 8; +} + +int +Widget_HeightLabel (WIDGET *_self) +{ + WIDGET_LABEL *self = (WIDGET_LABEL *)_self; + return self->line_count * 8; +} + +int +Widget_WidthFullScreen (WIDGET *_self) +{ + (void)_self; + return ScreenWidth; +} + +int +Widget_ReceiveFocusSimple (WIDGET *_self, int event) +{ + widget_focus = _self; + (void)event; + return TRUE; +} + +int +Widget_ReceiveFocusChoice (WIDGET *_self, int event) +{ + WIDGET_CHOICE *self = (WIDGET_CHOICE *)_self; + widget_focus = _self; + self->highlighted = self->selected; + (void)event; + return TRUE; +} + +int +Widget_ReceiveFocusControlEntry (WIDGET *_self, int event) +{ + WIDGET_CONTROLENTRY *self = (WIDGET_CONTROLENTRY *)_self; + int oldval = 0; + if (widget_focus->tag == WIDGET_TYPE_CONTROLENTRY) + { + oldval = ((WIDGET_CONTROLENTRY *)widget_focus)->highlighted; + } + widget_focus = _self; + self->highlighted = oldval; + (void)event; + return TRUE; +} + +int +Widget_ReceiveFocusMenuScreen (WIDGET *_self, int event) +{ + WIDGET_MENU_SCREEN *self = (WIDGET_MENU_SCREEN *)_self; + int x, last_x, dx; + for (x = 0; x < self->num_children; x++) + { + self->child[x]->parent = _self; + } + if (event == WIDGET_EVENT_UP) + { + x = self->num_children - 1; + dx = -1; + last_x = -1; + } + else if (event == WIDGET_EVENT_DOWN) + { + x = 0; + dx = 1; + last_x = self->num_children; + } + else + { + /* Leave highlighted value the same */ + WIDGET *child = self->child[self->highlighted]; + child->receiveFocus (child, event); + return TRUE; + } + for ( ; x != last_x; x += dx) + { + WIDGET *child = self->child[x]; + if ((*child->receiveFocus)(child, event)) + { + self->highlighted = x; + return TRUE; + } + } + return FALSE; +} + +int +Widget_ReceiveFocusRefuseFocus (WIDGET *self, int event) +{ + (void)self; + (void)event; + return FALSE; +} + +int +Widget_HandleEventIgnoreAll (WIDGET *self, int event) +{ + (void)event; + (void)self; + return FALSE; +} + +int +Widget_HandleEventChoice (WIDGET *_self, int event) +{ + WIDGET_CHOICE *self = (WIDGET_CHOICE *)_self; + switch (event) + { + case WIDGET_EVENT_LEFT: + self->highlighted -= 1; + if (self->highlighted < 0) + self->highlighted = self->numopts - 1; + return TRUE; + case WIDGET_EVENT_RIGHT: + self->highlighted += 1; + if (self->highlighted >= self->numopts) + self->highlighted = 0; + return TRUE; + case WIDGET_EVENT_SELECT: + { + int oldval = self->selected; + self->selected = self->highlighted; + if (self->onChange) + { + (*(self->onChange))(self, oldval); + } + return TRUE; + } + default: + return FALSE; + } +} + +int +Widget_HandleEventSlider (WIDGET *_self, int event) +{ + WIDGET_SLIDER *self = (WIDGET_SLIDER *)_self; + switch (event) + { + case WIDGET_EVENT_LEFT: + self->value -= self->step; + if (self->value < self->min) + self->value = self->min; + return TRUE; + case WIDGET_EVENT_RIGHT: + self->value += self->step; + if (self->value > self->max) + self->value = self->max; + return TRUE; + default: + return FALSE; + } +} + +int +Widget_HandleEventMenuScreen (WIDGET *_self, int event) +{ + WIDGET_MENU_SCREEN *self = (WIDGET_MENU_SCREEN *)_self; + int x, last_x, dx; + switch (event) + { + case WIDGET_EVENT_UP: + dx = -1; + break; + case WIDGET_EVENT_DOWN: + dx = 1; + break; + case WIDGET_EVENT_CANCEL: + /* On cancel, shift focus to last element and send a SELECT. */ + self->highlighted = self->num_children - 1; + widget_focus = self->child[self->highlighted]; + return (widget_focus->handleEvent)(widget_focus, WIDGET_EVENT_SELECT); + default: + return FALSE; + } + last_x = self->highlighted; + x = self->highlighted + dx; + while (x != last_x) + { + WIDGET *child; + if (x == -1) + x = self->num_children - 1; + if (x == self->num_children) + x = 0; + child = self->child[x]; + if ((*child->receiveFocus)(child, event)) + { + self->highlighted = x; + return TRUE; + } + x += dx; + } + return FALSE; +} + +int +Widget_HandleEventTextEntry (WIDGET *_self, int event) +{ + WIDGET_TEXTENTRY *self = (WIDGET_TEXTENTRY *)_self; + if (event == WIDGET_EVENT_SELECT) { + if (!self->handleEventSelect) + return FALSE; + return (*self->handleEventSelect)(self); + } + return FALSE; +} + +int +Widget_HandleEventControlEntry (WIDGET *_self, int event) +{ + WIDGET_CONTROLENTRY *self = (WIDGET_CONTROLENTRY *)_self; + if (event == WIDGET_EVENT_SELECT) + { + if (self->onChange) + { + (self->onChange)(self); + return TRUE; + } + } + if (event == WIDGET_EVENT_DELETE) + { + if (self->onDelete) + { + (self->onDelete)(self); + return TRUE; + } + } + if ((event == WIDGET_EVENT_RIGHT) || + (event == WIDGET_EVENT_LEFT)) + { + self->highlighted = 1-self->highlighted; + return TRUE; + } + return FALSE; +} + +int +Widget_Event (int event) +{ + WIDGET *widget = widget_focus; + while (widget != NULL) + { + if ((*widget->handleEvent)(widget, event)) + return TRUE; + widget = widget->parent; + } + return FALSE; +} diff --git a/src/libs/graphics/widgets.h b/src/libs/graphics/widgets.h new file mode 100644 index 0000000..4548e35 --- /dev/null +++ b/src/libs/graphics/widgets.h @@ -0,0 +1,222 @@ +// Copyright Michael Martin, 2004. + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_GRAPHICS_WIDGETS_H_ +#define LIBS_GRAPHICS_WIDGETS_H_ + +#include "libs/gfxlib.h" + +enum { + WIDGET_EVENT_UP, + WIDGET_EVENT_DOWN, + WIDGET_EVENT_LEFT, + WIDGET_EVENT_RIGHT, + WIDGET_EVENT_SELECT, + WIDGET_EVENT_CANCEL, + WIDGET_EVENT_DELETE, + NUM_WIDGET_EVENTS +}; + +typedef enum { + WIDGET_TYPE_MENU_SCREEN, + WIDGET_TYPE_CHOICE, + WIDGET_TYPE_BUTTON, + WIDGET_TYPE_LABEL, + WIDGET_TYPE_SLIDER, + WIDGET_TYPE_TEXTENTRY, + WIDGET_TYPE_CONTROLENTRY, + NUM_WIDGET_TYPES +} WIDGET_TYPE; + +#define WIDGET_TEXTENTRY_WIDTH 50 +#define WIDGET_CONTROLENTRY_WIDTH 16 + +typedef struct _widget { + WIDGET_TYPE tag; + struct _widget *parent; + int (*handleEvent)(struct _widget *self, int event); + int (*receiveFocus)(struct _widget *self, int event); + void (*draw)(struct _widget *self, int x, int y); + int (*height)(struct _widget *self); + int (*width)(struct _widget *self); +} WIDGET; + +typedef struct _widget_menu_screen { + WIDGET_TYPE tag; + struct _widget *parent; + int (*handleEvent)(struct _widget *self, int event); + int (*receiveFocus)(struct _widget *self, int event); + void (*draw)(struct _widget *self, int x, int y); + int (*height)(struct _widget *self); + int (*width)(struct _widget *self); + const char *title; + const char *subtitle; + STAMP bgStamp; + int num_children; + struct _widget **child; + int highlighted; +} WIDGET_MENU_SCREEN; + +typedef struct { + const char *optname; + const char *tooltip[3]; +} CHOICE_OPTION; + +typedef struct _widget_choice { + WIDGET_TYPE tag; + struct _widget *parent; + int (*handleEvent)(struct _widget *self, int event); + int (*receiveFocus)(struct _widget *self, int event); + void (*draw)(struct _widget *self, int x, int y); + int (*height)(struct _widget *self); + int (*width)(struct _widget *self); + const char *category; + int numopts; + int maxcolumns; + CHOICE_OPTION *options; + int selected, highlighted; + void (*onChange)(struct _widget_choice *self, int oldval); +} WIDGET_CHOICE; + +typedef struct _widget_button { + WIDGET_TYPE tag; + struct _widget *parent; + int (*handleEvent)(struct _widget *self, int event); + int (*receiveFocus)(struct _widget *self, int event); + void (*draw)(struct _widget *self, int x, int y); + int (*height)(struct _widget *self); + int (*width)(struct _widget *self); + const char *name; + const char *tooltip[3]; +} WIDGET_BUTTON; + +typedef struct _widget_label { + WIDGET_TYPE tag; + struct _widget *parent; + int (*handleEvent)(struct _widget *self, int event); + int (*receiveFocus)(struct _widget *self, int event); + void (*draw)(struct _widget *self, int x, int y); + int (*height)(struct _widget *self); + int (*width)(struct _widget *self); + int line_count; + const char **lines; +} WIDGET_LABEL; + +typedef struct _widget_slider { + WIDGET_TYPE tag; + struct _widget *parent; + int (*handleEvent)(struct _widget *self, int event); + int (*receiveFocus)(struct _widget *self, int event); + void (*draw)(struct _widget *self, int x, int y); + int (*height)(struct _widget *self); + int (*width)(struct _widget *self); + void (*draw_value)(struct _widget_slider *self, int x, int y); + int min, max, step; + int value; + const char *category; + const char *tooltip[3]; +} WIDGET_SLIDER; + +typedef enum { + WTE_NORMAL = 0, + WTE_EDITING, + WTE_BLOCKCUR, + +} WIDGET_TEXTENTRY_STATE; + +typedef struct _widget_textentry { + WIDGET_TYPE tag; + struct _widget *parent; + int (*handleEvent)(struct _widget *self, int event); + int (*receiveFocus)(struct _widget *self, int event); + void (*draw)(struct _widget *self, int x, int y); + int (*height)(struct _widget *self); + int (*width)(struct _widget *self); + int (*handleEventSelect)(struct _widget_textentry *self); + // handleEventSelect is an overridable callback event + // called by the default handleEvent implementation + // can be NULL, in which case SELECT is ignored + void (*onChange)(struct _widget_textentry *self); + const char *category; + char value[WIDGET_TEXTENTRY_WIDTH]; + int maxlen; + WIDGET_TEXTENTRY_STATE state; + int cursor_pos; +} WIDGET_TEXTENTRY; + +typedef struct _widget_controlentry { + WIDGET_TYPE tag; + struct _widget *parent; + int (*handleEvent)(struct _widget *self, int event); + int (*receiveFocus)(struct _widget *self, int event); + void (*draw)(struct _widget *self, int x, int y); + int (*height)(struct _widget *self); + int (*width)(struct _widget *self); + void (*onChange)(struct _widget_controlentry *self); + void (*onDelete)(struct _widget_controlentry *self); + const char *category; + int controlindex; + int highlighted; + char controlname[2][WIDGET_CONTROLENTRY_WIDTH]; +} WIDGET_CONTROLENTRY; + +void DrawShadowedBox (RECT *r, Color bg, Color dark, Color medium); +void DrawLabelAsWindow (WIDGET_LABEL *label, RECT *windowRect); +void Widget_SetWindowColors (Color bg, Color dark, Color medium); +FONT Widget_SetFont (FONT newFont); + + +int Widget_Event (int event); + +/* Methods for filling in widgets with */ + +int Widget_ReceiveFocusMenuScreen (WIDGET *_self, int event); +int Widget_ReceiveFocusChoice (WIDGET *_self, int event); +int Widget_ReceiveFocusSimple (WIDGET *_self, int event); +int Widget_ReceiveFocusSlider (WIDGET *_self, int event); +int Widget_ReceiveFocusControlEntry (WIDGET *_self, int event); +int Widget_ReceiveFocusRefuseFocus (WIDGET *_self, int event); + +int Widget_HandleEventMenuScreen (WIDGET *_self, int event); +int Widget_HandleEventChoice (WIDGET *_self, int event); +int Widget_HandleEventSlider (WIDGET *_self, int event); +int Widget_HandleEventTextEntry (WIDGET *_self, int event); +int Widget_HandleEventControlEntry (WIDGET *_self, int event); +int Widget_HandleEventIgnoreAll (WIDGET *_self, int event); + +int Widget_HeightChoice (WIDGET *_self); +int Widget_HeightFullScreen (WIDGET *_self); +int Widget_HeightOneLine (WIDGET *_self); +int Widget_HeightLabel (WIDGET *_self); + +int Widget_WidthFullScreen (WIDGET *_self); + +void Widget_DrawMenuScreen (WIDGET *_self, int x, int y); +void Widget_DrawChoice (WIDGET *_self, int x, int y); +void Widget_DrawButton (WIDGET *_self, int x, int y); +void Widget_DrawLabel (WIDGET *_self, int x, int y); +void Widget_DrawSlider (WIDGET *_self, int x, int y); +void Widget_DrawTextEntry (WIDGET *_self, int x, int y); +void Widget_DrawControlEntry (WIDGET *_self, int x, int y); + +void Widget_Slider_DrawValue (WIDGET_SLIDER *self, int x, int y); + +/* Other implementations will need these values */ +extern WIDGET *widget_focus; + +#endif /* LIBS_GRAPHICS_WIDGETS_H_ */ diff --git a/src/libs/heap.h b/src/libs/heap.h new file mode 100644 index 0000000..674649b --- /dev/null +++ b/src/libs/heap.h @@ -0,0 +1,9 @@ +#if defined(__cplusplus) +extern "C" { +#endif + +#include "heap/heap.h" + +#if defined(__cplusplus) +} +#endif diff --git a/src/libs/heap/Makeinfo b/src/libs/heap/Makeinfo new file mode 100644 index 0000000..1622e1c --- /dev/null +++ b/src/libs/heap/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="heap.c" +uqm_HFILES="heap.h" diff --git a/src/libs/heap/heap.c b/src/libs/heap/heap.c new file mode 100644 index 0000000..1f4c0f7 --- /dev/null +++ b/src/libs/heap/heap.c @@ -0,0 +1,197 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "heap.h" + +#include <assert.h> +#include <math.h> +#include <stdlib.h> +#include "port.h" + +static inline size_t nextPower2(size_t x); + + +static void +Heap_resize(Heap *heap, size_t size) { + heap->entries = realloc(heap->entries, size * sizeof (HeapValue *)); + heap->size = size; +} + +// Heap inv: comparator(parent, child) <= 0 for every child of every parent. +Heap * +Heap_new(HeapValue_Comparator comparator, size_t initialSize, size_t minSize, + double minFillQuotient) { + Heap *heap; + + assert(minFillQuotient >= 0.0); + + heap = malloc(sizeof (Heap)); + + if (initialSize < minSize) + initialSize = minSize; + + heap->comparator = comparator; + heap->minSize = minSize; + heap->minFillQuotient = minFillQuotient; + heap->size = nextPower2(initialSize); + heap->minFill = ceil(((double) (heap->size >> 1)) + * heap->minFillQuotient); + heap->entries = malloc(heap->size * sizeof (HeapValue *)); + heap->numEntries = 0; + + return heap; +} + +void +Heap_delete(Heap *heap) { + free(heap->entries); + free(heap); +} + +void +Heap_add(Heap *heap, HeapValue *value) { + size_t i; + + if (heap->numEntries >= heap->size) + Heap_resize(heap, heap->size * 2); + + i = heap->numEntries; + heap->numEntries++; + + while (i > 0) { + size_t parentI = (i - 1) / 2; + if (heap->comparator(heap->entries[parentI], value) <= 0) + break; + + heap->entries[i] = heap->entries[parentI]; + heap->entries[i]->index = i; + i = parentI; + } + heap->entries[i] = value; + heap->entries[i]->index = i; +} + +HeapValue * +Heap_first(const Heap *heap) { + assert(heap->numEntries > 0); + + return heap->entries[0]; +} + +static void +Heap_removeByIndex(Heap *heap, size_t i) { + assert(heap->numEntries > i); + + heap->numEntries--; + + if (heap->numEntries != 0) { + // Restore the heap invariant. We're shifting entries into the + // gap that was created until we find the place where we can + // insert the last entry. + HeapValue *lastEntry = heap->entries[heap->numEntries]; + + for (;;) { + size_t childI = i * 2 + 1; + // The two children are childI and 'childI + 1'. + + if (childI + 1 >= heap->numEntries) { + // There is no right child. + + if (childI >= heap->numEntries) { + // There is no left child either. + break; + } + } else { + if (heap->comparator(heap->entries[childI + 1], + heap->entries[childI]) < 0) { + // The right child is the child with the lowest value. + childI++; + } + } + // childI is now the child with the lowest value. + + if (heap->comparator(lastEntry, heap->entries[childI]) <= 0) { + // The last entry goes here. + break; + } + + // Move the child into the gap. + heap->entries[i] = heap->entries[childI]; + heap->entries[i]->index = i; + + // and repeat for the child. + i = childI; + } + + // Fill the gap with the last entry. + heap->entries[i] = lastEntry; + heap->entries[i]->index = i; + } + + // Resize if necessary: + if (heap->numEntries < heap->minFill && + heap->numEntries > heap->minSize) + Heap_resize(heap, heap->size / 2); +} + +HeapValue * +Heap_pop(Heap *heap) { + HeapValue *result; + + assert(heap->numEntries > 0); + + result = heap->entries[0]; + Heap_removeByIndex(heap, 0); + + return result; +} + +size_t +Heap_count(const Heap *heap) { + return heap->numEntries; +} + +bool +Heap_hasMore(const Heap *heap) { + return heap->numEntries > 0; +} + +void +Heap_remove(Heap *heap, HeapValue *value) { + Heap_removeByIndex(heap, value->index); +} + +// Adapted from "Hackers Delight" +// Returns the smallest power of two greater or equal to x. +static inline size_t +nextPower2(size_t x) { + x--; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; +# if (SIZE_MAX > 0xffff) + x |= x >> 16; +# if (SIZE_MAX > 0xffffffff) + x |= x >> 32; +# endif +# endif + return x + 1; +} + + diff --git a/src/libs/heap/heap.h b/src/libs/heap/heap.h new file mode 100644 index 0000000..9a3f829 --- /dev/null +++ b/src/libs/heap/heap.h @@ -0,0 +1,69 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef LIBS_HEAP_HEAP_H_ +#define LIBS_HEAP_HEAP_H_ + +#include "types.h" + +#include <sys/types.h> +#include <stdlib.h> + + +typedef struct Heap Heap; +typedef struct HeapValue HeapValue; + +// The actual value stored should "inherit" from this. +struct HeapValue { + size_t index; +}; +typedef int (*HeapValue_Comparator)(HeapValue *v1, HeapValue *v2); + + +struct Heap { + HeapValue_Comparator comparator; + // Comparison function to determine the order of the + // elements. + size_t minSize; + // Never resize below this many number of entries. + double minFillQuotient; + // How much of half of the heap needs to be filled before + // resizing to size/2. + + HeapValue **entries; + // Actual values + size_t numEntries; + size_t size; + // Number of entries that fit in the heap as it is now. + size_t minFill; + // Resize to size/2 when below this size. +}; + + +Heap *Heap_new(HeapValue_Comparator comparator, size_t initialSize, + size_t minSize, double minFillQuotient); +void Heap_delete(Heap *heap); +void Heap_add(Heap *heap, HeapValue *value); +HeapValue *Heap_first(const Heap *heap); +HeapValue *Heap_pop(Heap *heap); +size_t Heap_count(const Heap *heap); +bool Heap_hasMore(const Heap *heap); +void Heap_remove(Heap *heap, HeapValue *value); + +#endif /* LIBS_HEAP_HEAP_H_ */ + diff --git a/src/libs/inplib.h b/src/libs/inplib.h new file mode 100644 index 0000000..2df1f79 --- /dev/null +++ b/src/libs/inplib.h @@ -0,0 +1,70 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_INPLIB_H_ +#define LIBS_INPLIB_H_ + +#include <stddef.h> +#include "libs/compiler.h" +#include "libs/uio.h" +#include "libs/unicode.h" + +#if defined(__cplusplus) +extern "C" { +#endif + + +extern BOOLEAN AnyButtonPress (BOOLEAN DetectSpecial); + +extern void TFB_ResetControls (void); + +/* + * Not used right now +extern BOOLEAN FindMouse (void); +extern void MoveMouse (SWORD x, SWORD y); +extern BYTE LocateMouse (SWORD *px, SWORD *py); +*/ + +extern volatile int MouseButtonDown; +extern volatile int QuitPosted; +extern volatile int GameActive; + +/* Functions for dealing with Character Mode */ + +void EnterCharacterMode (void); +void ExitCharacterMode (void); +UniChar GetNextCharacter (void); +UniChar GetLastCharacter (void); + +/* Interrogating the current key configuration */ + +void InterrogateInputState (int templat, int control, int index, char *buffer, int maxlen); +void RemoveInputState (int templat, int control, int index); +void RebindInputState (int templat, int control, int index); + +void SaveKeyConfiguration (uio_DirHandle *path, const char *fname); + +/* Separate inputs into frames for dealing with very fast inputs */ + +void BeginInputFrame (void); + +#if defined(__cplusplus) +} +#endif + +#endif /* LIBS_INPLIB_H */ diff --git a/src/libs/input/Makeinfo b/src/libs/input/Makeinfo new file mode 100644 index 0000000..b76604f --- /dev/null +++ b/src/libs/input/Makeinfo @@ -0,0 +1,6 @@ +if [ "$uqm_GFXMODULE" = "sdl" ]; then + uqm_SUBDIRS="sdl" +fi + +uqm_CFILES="input_common.c" +uqm_HFILES="inpintrn.h input_common.h" diff --git a/src/libs/input/inpintrn.h b/src/libs/input/inpintrn.h new file mode 100644 index 0000000..1d48ccc --- /dev/null +++ b/src/libs/input/inpintrn.h @@ -0,0 +1,25 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_INPUT_INPINTRN_H_ +#define LIBS_INPUT_INPINTRN_H_ + +#include "libs/inplib.h" +#include "libs/input/input_common.h" + +#endif /* LIBS_INPUT_INPINTRN_H_ */ diff --git a/src/libs/input/input_common.c b/src/libs/input/input_common.c new file mode 100644 index 0000000..7a0bbc1 --- /dev/null +++ b/src/libs/input/input_common.c @@ -0,0 +1,20 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "port.h" +#include "inpintrn.h" diff --git a/src/libs/input/input_common.h b/src/libs/input/input_common.h new file mode 100644 index 0000000..5979320 --- /dev/null +++ b/src/libs/input/input_common.h @@ -0,0 +1,39 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef INPUT_COMMON_H +#define INPUT_COMMON_H + +// driver for TFB_InitInput +enum +{ + TFB_INPUTDRIVER_SDL +}; + +// flags for TFB_InitInput +//#define TFB_INPUTFLAGS_ETC (1<<0) + +extern int TFB_InitInput (int driver, int flags); +extern void TFB_UninitInput (void); + +#define MAX_FLIGHT_ALTERNATES 2 + +extern void TFB_SetInputVectors (volatile int menu[], int num_menu, + volatile int flight[], int num_templ, int num_flight); + +#endif diff --git a/src/libs/input/sdl/Makeinfo b/src/libs/input/sdl/Makeinfo new file mode 100644 index 0000000..427da53 --- /dev/null +++ b/src/libs/input/sdl/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="input.c keynames.c vcontrol.c" +uqm_HFILES="input.h keynames.h vcontrol.h" diff --git a/src/libs/input/sdl/input.c b/src/libs/input/sdl/input.c new file mode 100644 index 0000000..bff17aa --- /dev/null +++ b/src/libs/input/sdl/input.c @@ -0,0 +1,625 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <assert.h> +#include <errno.h> +#include <string.h> +#include "input.h" +#include "../inpintrn.h" +#include "libs/threadlib.h" +#include "libs/input/sdl/vcontrol.h" +#include "libs/input/sdl/keynames.h" +#include "libs/memlib.h" +#include "libs/file.h" +#include "libs/log.h" +#include "libs/reslib.h" +#include "options.h" + + +#define KBDBUFSIZE (1 << 8) +static int kbdhead=0, kbdtail=0; +static UniChar kbdbuf[KBDBUFSIZE]; +static UniChar lastchar; +#if SDL_MAJOR_VERSION == 1 +static unsigned int num_keys = 0; +static int *kbdstate = NULL; + // Holds all SDL keys +1 for holding invalid values +#else // Later versions of SDL use the text input API instead +static BOOLEAN set_character_mode = FALSE; + // Records whether the UI thread has caught up with game thread + // on this setting +#endif + +static volatile int *menu_vec; +static int num_menu; +// The last vector element is the character repeat "key" +// This is only used in SDL1 input but it's mostly harmless everywhere else +#define KEY_MENU_ANY (num_menu - 1) +static volatile int *flight_vec; +static int num_templ; +static int num_flight; + +static BOOLEAN InputInitialized = FALSE; + +static BOOLEAN in_character_mode = FALSE; + +static const char *menu_res_names[] = { + "pause", + "exit", + "abort", + "debug", + "fullscreen", + "up", + "down", + "left", + "right", + "select", + "cancel", + "special", + "pageup", + "pagedown", + "home", + "end", + "zoomin", + "zoomout", + "delete", + "backspace", + "editcancel", + "search", + "next", + NULL +}; + +static const char *flight_res_names[] = { + "up", + "down", + "left", + "right", + "weapon", + "special", + "escape", + NULL +}; + +static void +register_menu_controls (int index) +{ + int i; + char buf[40]; + buf[39] = '\0'; + + i = 1; + while (TRUE) + { + VCONTROL_GESTURE g; + snprintf (buf, 39, "menu.%s.%d", menu_res_names[index], i); + if (!res_IsString (buf)) + break; + VControl_ParseGesture (&g, res_GetString (buf)); + VControl_AddGestureBinding (&g, (int *)&menu_vec[index]); + i++; + } +} + + +static VCONTROL_GESTURE *controls; +#define CONTROL_PTR(i, j, k) \ + (controls + ((i) * num_flight + (j)) * MAX_FLIGHT_ALTERNATES + (k)) + +static void +register_flight_controls (void) +{ + int i, j, k; + char buf[40]; + + buf[39] = '\0'; + + for (i = 0; i < num_templ; i++) + { + /* Copy in name */ + snprintf (buf, 39, "keys.%d.name", i+1); + if (res_IsString (buf)) + { + strncpy (input_templates[i].name, res_GetString (buf), 29); + input_templates[i].name[29] = '\0'; + } + else + { + input_templates[i].name[0] = '\0'; + } + for (j = 0; j < num_flight; j++) + { + for (k = 0; k < MAX_FLIGHT_ALTERNATES; k++) + { + VCONTROL_GESTURE *g = CONTROL_PTR(i, j, k); + snprintf (buf, 39, "keys.%d.%s.%d", i+1, flight_res_names[j], k+1); + if (!res_IsString (buf)) + { + g->type = VCONTROL_NONE; + continue; + } + VControl_ParseGesture (g, res_GetString (buf)); + VControl_AddGestureBinding (g, (int *)(flight_vec + i * num_flight + j)); + } + } + } +} + +static void +initKeyConfig (void) +{ + int i; + + if (!menu_vec || !flight_vec) + { + log_add (log_Fatal, "initKeyConfig(): invalid input vectors"); + exit (EXIT_FAILURE); + } + + controls = HCalloc (sizeof (*controls) * num_templ * num_flight + * MAX_FLIGHT_ALTERNATES); + + /* First, load in the menu keys */ + LoadResourceIndex (contentDir, "menu.key", "menu."); + LoadResourceIndex (configDir, "override.cfg", "menu."); + for (i = 0; i < num_menu; i++) + { + if (!menu_res_names[i]) + break; + register_menu_controls (i); + } + + LoadResourceIndex (configDir, "flight.cfg", "keys."); + if (!res_HasKey ("keys.1.name")) + { + /* Either flight.cfg doesn't exist, or we're using an old version + of flight.cfg, and thus we wound up loading untyped values into + 'keys.keys.1.name' and such. Load the defaults from the content + directory. */ + LoadResourceIndex (contentDir, "uqm.key", "keys."); + } + + register_flight_controls (); + + return; +} + +static void +resetKeyboardState (void) +{ +#if SDL_MAJOR_VERSION == 1 + memset (kbdstate, 0, sizeof (int) * num_keys); + menu_vec[KEY_MENU_ANY] = 0; +#endif +} + +void +TFB_SetInputVectors (volatile int menu[], int num_menu_, volatile int flight[], + int num_templ_, int num_flight_) +{ + if (num_menu_ < 0 || num_templ_ < 0 || num_flight_ < 0) + { + log_add (log_Fatal, "TFB_SetInputVectors(): invalid vector size"); + exit (EXIT_FAILURE); + } + menu_vec = menu; + num_menu = num_menu_; + flight_vec = flight; + num_templ = num_templ_; + num_flight = num_flight_; +} + +#ifdef HAVE_JOYSTICK + +static void +initJoystick (void) +{ + int nJoysticks; + + if ((SDL_InitSubSystem(SDL_INIT_JOYSTICK)) == -1) + { + log_add (log_Fatal, "Couldn't initialize joystick subsystem: %s", + SDL_GetError()); + exit (EXIT_FAILURE); + } + + log_add (log_Info, "%i joysticks were found.", SDL_NumJoysticks ()); + + nJoysticks = SDL_NumJoysticks (); + if (nJoysticks > 0) + { + int i; + + log_add (log_Info, "The names of the joysticks are:"); + for (i = 0; i < nJoysticks; i++) + { + log_add (log_Info, " %s", +#if SDL_MAJOR_VERSION == 1 + SDL_JoystickName (i)); +#else + SDL_JoystickNameForIndex (i)); +#endif + } + SDL_JoystickEventState (SDL_ENABLE); + } +} + +#endif /* HAVE_JOYSTICK */ + +int +TFB_InitInput (int driver, int flags) +{ + (void)driver; + (void)flags; + +#if SDL_MAJOR_VERSION == 1 + SDL_EnableUNICODE(1); + (void)SDL_GetKeyState (&num_keys); + kbdstate = (int *)HMalloc (sizeof (int) * (num_keys + 1)); +#endif + +#ifdef HAVE_JOYSTICK + initJoystick (); +#endif + + in_character_mode = FALSE; + resetKeyboardState (); + + /* Prepare the Virtual Controller system. */ + VControl_Init (); + + initKeyConfig (); + + VControl_ResetInput (); + InputInitialized = TRUE; + + return 0; +} + +void +TFB_UninitInput (void) +{ + VControl_Uninit (); + HFree (controls); +#if SDL_MAJOR_VERSION == 1 + HFree (kbdstate); +#endif +} + +void +EnterCharacterMode (void) +{ + kbdhead = kbdtail = 0; + lastchar = 0; + in_character_mode = TRUE; + VControl_ResetInput (); +} + +void +ExitCharacterMode (void) +{ + VControl_ResetInput (); + in_character_mode = FALSE; + kbdhead = kbdtail = 0; + lastchar = 0; +} + +UniChar +GetNextCharacter (void) +{ + UniChar result; + if (kbdhead == kbdtail) + return 0; + result = kbdbuf[kbdhead]; + kbdhead = (kbdhead + 1) & (KBDBUFSIZE - 1); + return result; +} + +UniChar +GetLastCharacter (void) +{ + return lastchar; +} + +volatile int MouseButtonDown = 0; + +static void +ProcessMouseEvent (const SDL_Event *e) +{ + switch (e->type) + { + case SDL_MOUSEBUTTONDOWN: + MouseButtonDown = 1; + break; + case SDL_MOUSEBUTTONUP: + MouseButtonDown = 0; + break; + default: + break; + } +} + +#if SDL_MAJOR_VERSION == 1 + +static inline int +is_numpad_char_event (const SDL_Event *Event) +{ + return in_character_mode && + (Event->type == SDL_KEYDOWN || Event->type == SDL_KEYUP) && + (Event->key.keysym.mod & KMOD_NUM) && /* NumLock is ON */ + Event->key.keysym.unicode > 0 && /* Printable char */ + Event->key.keysym.sym >= SDLK_KP0 && /* Keypad key */ + Event->key.keysym.sym <= SDLK_KP_PLUS; +} + +void +ProcessInputEvent (const SDL_Event *Event) +{ + if (!InputInitialized) + return; + + ProcessMouseEvent (Event); + + // In character mode with NumLock on, numpad chars bypass VControl + // so that menu arrow events are not produced + if (!is_numpad_char_event (Event)) + VControl_HandleEvent (Event); + + if (Event->type == SDL_KEYDOWN || Event->type == SDL_KEYUP) + { // process character input event, if any + // keysym.sym is an SDLKey type which is an enum and can be signed + // or unsigned on different platforms; we'll use a guaranteed type + int k = Event->key.keysym.sym; + UniChar map_key = Event->key.keysym.unicode; + + if (k < 0 || k > num_keys) + k = num_keys; // for unknown keys + + if (Event->type == SDL_KEYDOWN) + { + int newtail; + + // dont care about the non-printable, non-char + if (!map_key) + return; + + kbdstate[k]++; + + newtail = (kbdtail + 1) & (KBDBUFSIZE - 1); + // ignore the char if the buffer is full + if (newtail != kbdhead) + { + kbdbuf[kbdtail] = map_key; + kbdtail = newtail; + lastchar = map_key; + menu_vec[KEY_MENU_ANY]++; + } + } + else if (Event->type == SDL_KEYUP) + { + if (kbdstate[k] == 0) + { // something is fishy -- better to reset the + // repeatable state to avoid big problems + menu_vec[KEY_MENU_ANY] = 0; + } + else + { + kbdstate[k]--; + if (menu_vec[KEY_MENU_ANY] > 0) + menu_vec[KEY_MENU_ANY]--; + } + } + } +} +#else +void +ProcessInputEvent (const SDL_Event *Event) +{ + if (!InputInitialized) + return; + + ProcessMouseEvent (Event); + + if (in_character_mode && !set_character_mode) + { + set_character_mode = TRUE; + SDL_StartTextInput (); + } + + if (!in_character_mode && set_character_mode) + { + set_character_mode = FALSE; + SDL_StopTextInput (); + } + + /* TODO: Block numpad input when NUM_LOCK is on */ + VControl_HandleEvent (Event); + + if (Event->type == SDL_TEXTINPUT) + { + int newtail; + int i = 0; + + while (Event->text.text[i]) + { + UniChar map_key = Event->text.text[i++]; + + /* Decode any UTF-8 keys */ + if (map_key >= 0xC0 && map_key < 0xE0) + { + /* 2-byte UTF-8 */ + map_key = (map_key & 0x1f) << 6; + map_key |= Event->text.text[i++] & 0x3f; + } + else if (map_key >= 0xE0 && map_key < 0xF0) + { + /* 3-byte UTF-8 */ + map_key = (map_key & 0x0f) << 6; + map_key |= Event->text.text[i++] & 0x3f; + map_key <<= 6; + map_key |= Event->text.text[i++] & 0x3f; + } + else if (map_key >= 0xF0) + { + /* Out of the BMP, won't fit in a UniChar */ + /* Use the replacement character instead */ + map_key = 0xFFFD; + while ((UniChar)Event->text.text[i] > 0x7F) + { + ++i; + } + } + + /* dont care about the non-printable, non-char */ + if (!map_key) + return; + + newtail = (kbdtail + 1) & (KBDBUFSIZE - 1); + + /* ignore the char if the buffer is full */ + if (newtail != kbdhead) + { + kbdbuf[kbdtail] = map_key; + kbdtail = newtail; + lastchar = map_key; + } + + /* Loop back in case there are more chars in the + * text input buffer */ + } + } +} + +#endif + +void +TFB_ResetControls (void) +{ + VControl_ResetInput (); + resetKeyboardState (); + // flush character buffer + kbdhead = kbdtail = 0; + lastchar = 0; +} + +void +InterrogateInputState (int templat, int control, int index, char *buffer, int maxlen) +{ + VCONTROL_GESTURE *g = CONTROL_PTR(templat, control, index); + + if (templat >= num_templ || control >= num_flight + || index >= MAX_FLIGHT_ALTERNATES) + { + log_add (log_Warning, "InterrogateInputState(): invalid control index"); + buffer[0] = 0; + return; + } + + switch (g->type) + { + case VCONTROL_KEY: + snprintf (buffer, maxlen, "%s", VControl_code2name (g->gesture.key)); + buffer[maxlen-1] = 0; + break; + case VCONTROL_JOYBUTTON: + snprintf (buffer, maxlen, "[J%d B%d]", g->gesture.button.port, g->gesture.button.index + 1); + buffer[maxlen-1] = 0; + break; + case VCONTROL_JOYAXIS: + snprintf (buffer, maxlen, "[J%d A%d %c]", g->gesture.axis.port, g->gesture.axis.index, g->gesture.axis.polarity > 0 ? '+' : '-'); + break; + case VCONTROL_JOYHAT: + snprintf (buffer, maxlen, "[J%d H%d %d]", g->gesture.hat.port, g->gesture.hat.index, g->gesture.hat.dir); + break; + default: + /* Something we don't handle yet */ + buffer[0] = 0; + break; + } + return; +} + +void +RemoveInputState (int templat, int control, int index) +{ + VCONTROL_GESTURE *g = CONTROL_PTR(templat, control, index); + char keybuf[40]; + keybuf[39] = '\0'; + + if (templat >= num_templ || control >= num_flight + || index >= MAX_FLIGHT_ALTERNATES) + { + log_add (log_Warning, "RemoveInputState(): invalid control index"); + return; + } + + VControl_RemoveGestureBinding (g, + (int *)(flight_vec + templat * num_flight + control)); + g->type = VCONTROL_NONE; + + snprintf (keybuf, 39, "keys.%d.%s.%d", templat+1, flight_res_names[control], index+1); + res_Remove (keybuf); + + return; +} + +void +RebindInputState (int templat, int control, int index) +{ + VCONTROL_GESTURE g; + char keybuf[40], valbuf[40]; + keybuf[39] = valbuf[39] = '\0'; + + if (templat >= num_templ || control >= num_flight + || index >= MAX_FLIGHT_ALTERNATES) + { + log_add (log_Warning, "RebindInputState(): invalid control index"); + return; + } + + /* Remove the old binding on this spot */ + RemoveInputState (templat, control, index); + + /* Wait for the next interesting bit of user input */ + VControl_ClearGesture (); + while (!VControl_GetLastGesture (&g)) + { + TaskSwitch (); + } + + /* And now, add the new binding. */ + VControl_AddGestureBinding (&g, + (int *)(flight_vec + templat * num_flight + control)); + *CONTROL_PTR(templat, control, index) = g; + snprintf (keybuf, 39, "keys.%d.%s.%d", templat+1, flight_res_names[control], index+1); + VControl_DumpGesture (valbuf, 39, &g); + res_PutString (keybuf, valbuf); +} + +void +SaveKeyConfiguration (uio_DirHandle *path, const char *fname) +{ + SaveResourceIndex (path, fname, "keys.", TRUE); +} + +void +BeginInputFrame (void) +{ + VControl_BeginFrame (); +} + diff --git a/src/libs/input/sdl/input.h b/src/libs/input/sdl/input.h new file mode 100644 index 0000000..3c599aa --- /dev/null +++ b/src/libs/input/sdl/input.h @@ -0,0 +1,27 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef INPUT_H +#define INPUT_H + +#include "port.h" +#include SDL_INCLUDE(SDL.h) + +extern void ProcessInputEvent (const SDL_Event *Event); + +#endif diff --git a/src/libs/input/sdl/keynames.c b/src/libs/input/sdl/keynames.c new file mode 100644 index 0000000..86c104a --- /dev/null +++ b/src/libs/input/sdl/keynames.c @@ -0,0 +1,229 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "port.h" +#include SDL_INCLUDE(SDL.h) +#include <string.h> +#include "keynames.h" + +/* This code is adapted from the code in SDL_keysym.h. Though this + * would almost certainly be fast if we were to use a direct char * + * array, this technique permits us to be independent of the actual + * character encoding to keysyms. */ + +/* These names are case-insensitive when compared, but we format + * them to look pretty when output */ + +/* This version of Virtual Controller does not support SDLK_WORLD_* + * keysyms or the Num/Caps/ScrollLock keys. SDL treats locking keys + * specially, and we cannot treat them as normal keys. Pain, + * tragedy. */ + +typedef struct vcontrol_keyname { + const char *name; + int code; +} keyname; + +static keyname keynames[] = { + {"Backspace", SDLK_BACKSPACE}, + {"Tab", SDLK_TAB}, + {"Clear", SDLK_CLEAR}, + {"Return", SDLK_RETURN}, + {"Pause", SDLK_PAUSE}, + {"Escape", SDLK_ESCAPE}, + {"Space", SDLK_SPACE}, + {"!", SDLK_EXCLAIM}, + {"\"", SDLK_QUOTEDBL}, + {"Hash", SDLK_HASH}, + {"$", SDLK_DOLLAR}, + {"&", SDLK_AMPERSAND}, + {"'", SDLK_QUOTE}, + {"(", SDLK_LEFTPAREN}, + {")", SDLK_RIGHTPAREN}, + {"*", SDLK_ASTERISK}, + {"+", SDLK_PLUS}, + {",", SDLK_COMMA}, + {"-", SDLK_MINUS}, + {".", SDLK_PERIOD}, + {"/", SDLK_SLASH}, + {"0", SDLK_0}, + {"1", SDLK_1}, + {"2", SDLK_2}, + {"3", SDLK_3}, + {"4", SDLK_4}, + {"5", SDLK_5}, + {"6", SDLK_6}, + {"7", SDLK_7}, + {"8", SDLK_8}, + {"9", SDLK_9}, + {":", SDLK_COLON}, + {";", SDLK_SEMICOLON}, + {"<", SDLK_LESS}, + {"=", SDLK_EQUALS}, + {">", SDLK_GREATER}, + {"?", SDLK_QUESTION}, + {"@", SDLK_AT}, + {"[", SDLK_LEFTBRACKET}, + {"\\", SDLK_BACKSLASH}, + {"]", SDLK_RIGHTBRACKET}, + {"^", SDLK_CARET}, + {"_", SDLK_UNDERSCORE}, + {"`", SDLK_BACKQUOTE}, + {"a", SDLK_a}, + {"b", SDLK_b}, + {"c", SDLK_c}, + {"d", SDLK_d}, + {"e", SDLK_e}, + {"f", SDLK_f}, + {"g", SDLK_g}, + {"h", SDLK_h}, + {"i", SDLK_i}, + {"j", SDLK_j}, + {"k", SDLK_k}, + {"l", SDLK_l}, + {"m", SDLK_m}, + {"n", SDLK_n}, + {"o", SDLK_o}, + {"p", SDLK_p}, + {"q", SDLK_q}, + {"r", SDLK_r}, + {"s", SDLK_s}, + {"t", SDLK_t}, + {"u", SDLK_u}, + {"v", SDLK_v}, + {"w", SDLK_w}, + {"x", SDLK_x}, + {"y", SDLK_y}, + {"z", SDLK_z}, + {"Delete", SDLK_DELETE}, +#if SDL_MAJOR_VERSION == 1 + {"Keypad-0", SDLK_KP0}, + {"Keypad-1", SDLK_KP1}, + {"Keypad-2", SDLK_KP2}, + {"Keypad-3", SDLK_KP3}, + {"Keypad-4", SDLK_KP4}, + {"Keypad-5", SDLK_KP5}, + {"Keypad-6", SDLK_KP6}, + {"Keypad-7", SDLK_KP7}, + {"Keypad-8", SDLK_KP8}, + {"Keypad-9", SDLK_KP9}, +#else + {"Keypad-0", SDLK_KP_0}, + {"Keypad-1", SDLK_KP_1}, + {"Keypad-2", SDLK_KP_2}, + {"Keypad-3", SDLK_KP_3}, + {"Keypad-4", SDLK_KP_4}, + {"Keypad-5", SDLK_KP_5}, + {"Keypad-6", SDLK_KP_6}, + {"Keypad-7", SDLK_KP_7}, + {"Keypad-8", SDLK_KP_8}, + {"Keypad-9", SDLK_KP_9}, +#endif + {"Keypad-.", SDLK_KP_PERIOD}, + {"Keypad-/", SDLK_KP_DIVIDE}, + {"Keypad-*", SDLK_KP_MULTIPLY}, + {"Keypad--", SDLK_KP_MINUS}, + {"Keypad-+", SDLK_KP_PLUS}, + {"Keypad-Enter", SDLK_KP_ENTER}, + {"Keypad-=", SDLK_KP_EQUALS}, + {"Up", SDLK_UP}, + {"Down", SDLK_DOWN}, + {"Right", SDLK_RIGHT}, + {"Left", SDLK_LEFT}, + {"Insert", SDLK_INSERT}, + {"Home", SDLK_HOME}, + {"End", SDLK_END}, + {"PageUp", SDLK_PAGEUP}, + {"PageDown", SDLK_PAGEDOWN}, + {"F1", SDLK_F1}, + {"F2", SDLK_F2}, + {"F3", SDLK_F3}, + {"F4", SDLK_F4}, + {"F5", SDLK_F5}, + {"F6", SDLK_F6}, + {"F7", SDLK_F7}, + {"F8", SDLK_F8}, + {"F9", SDLK_F9}, + {"F10", SDLK_F10}, + {"F11", SDLK_F11}, + {"F12", SDLK_F12}, + {"F13", SDLK_F13}, + {"F14", SDLK_F14}, + {"F15", SDLK_F15}, + {"RightShift", SDLK_RSHIFT}, + {"LeftShift", SDLK_LSHIFT}, + {"RightControl", SDLK_RCTRL}, + {"LeftControl", SDLK_LCTRL}, + {"RightAlt", SDLK_RALT}, + {"LeftAlt", SDLK_LALT}, +#if SDL_MAJOR_VERSION == 1 + {"RightMeta", SDLK_RMETA}, + {"LeftMeta", SDLK_LMETA}, + {"RightSuper", SDLK_RSUPER}, + {"LeftSuper", SDLK_LSUPER}, + {"AltGr", SDLK_MODE}, + {"Compose", SDLK_COMPOSE}, + {"Help", SDLK_HELP}, + {"Print", SDLK_PRINT}, + {"SysReq", SDLK_SYSREQ}, + {"Break", SDLK_BREAK}, + {"Menu", SDLK_MENU}, + {"Power", SDLK_POWER}, + {"Euro", SDLK_EURO}, + {"Undo", SDLK_UNDO}, +#ifdef _WIN32_WCE + {"App1", SDLK_APP1}, + {"App2", SDLK_APP2}, + {"App3", SDLK_APP3}, + {"App4", SDLK_APP4}, + {"App5", SDLK_APP5}, + {"App6", SDLK_APP6}, +#endif /* _WIN32_WCE */ +#endif /* SDL_MAJOR_VERSION == 1 */ + + {"Unknown", 0}}; +/* Last element must have code zero */ + +const char * +VControl_code2name (int code) +{ + int i = 0; + while (1) + { + int test = keynames[i].code; + if (test == code || !test) + { + return keynames[i].name; + } + ++i; + } +} + +int +VControl_name2code (const char *name) +{ + int i = 0; + while (1) + { + const char *test = keynames[i].name; + int code = keynames[i].code; + if (!strcasecmp(test, name) || !code) + { + return code; + } + ++i; + } +} diff --git a/src/libs/input/sdl/keynames.h b/src/libs/input/sdl/keynames.h new file mode 100644 index 0000000..affd854 --- /dev/null +++ b/src/libs/input/sdl/keynames.h @@ -0,0 +1,22 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_INPUT_SDL_KEYNAMES_H_ +#define LIBS_INPUT_SDL_KEYNAMES_H_ + +const char *VControl_code2name (int code); +int VControl_name2code (const char *code); +#endif diff --git a/src/libs/input/sdl/vcontrol.c b/src/libs/input/sdl/vcontrol.c new file mode 100644 index 0000000..9c226ae --- /dev/null +++ b/src/libs/input/sdl/vcontrol.c @@ -0,0 +1,1300 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "port.h" +#include SDL_INCLUDE(SDL.h) +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include "vcontrol.h" +#include "libs/memlib.h" +#include "keynames.h" +#include "libs/log.h" +#include "libs/reslib.h" + +/* How many binding slots are allocated at once. */ +#define POOL_CHUNK_SIZE 64 + +/* Total number of key input buckets. SDL1 keys are a simple enum, + * but SDL2 scatters key symbols through the entire 32-bit space, + * so we do not rely on being able to declare an array with one + * entry per key. */ +#define KEYBOARD_INPUT_BUCKETS 512 + +typedef struct vcontrol_keybinding { + int *target; + sdl_key_t keycode; + struct vcontrol_keypool *parent; + struct vcontrol_keybinding *next; +} keybinding; + +typedef struct vcontrol_keypool { + keybinding pool[POOL_CHUNK_SIZE]; + int remaining; + struct vcontrol_keypool *next; +} keypool; + + +#ifdef HAVE_JOYSTICK + +typedef struct vcontrol_joystick_axis { + keybinding *neg, *pos; + int polarity; +} axis_type; + +typedef struct vcontrol_joystick_hat { + keybinding *left, *right, *up, *down; + Uint8 last; +} hat_type; + +typedef struct vcontrol_joystick { + SDL_Joystick *stick; + int numaxes, numbuttons, numhats; + int threshold; + axis_type *axes; + keybinding **buttons; + hat_type *hats; +} joystick; + +static joystick *joysticks; + +#endif /* HAVE_JOYSTICK */ + +static unsigned int joycount; +static keybinding *bindings[KEYBOARD_INPUT_BUCKETS]; + +static keypool *pool; + +/* Last interesting event */ +static int event_ready; +static SDL_Event last_interesting; + +static keypool * +allocate_key_chunk (void) +{ + keypool *x = HMalloc (sizeof (keypool)); + if (x) + { + int i; + x->remaining = POOL_CHUNK_SIZE; + x->next = NULL; + for (i = 0; i < POOL_CHUNK_SIZE; i++) + { + x->pool[i].target = NULL; + x->pool[i].keycode = SDLK_UNKNOWN; + x->pool[i].next = NULL; + x->pool[i].parent = x; + } + } + return x; +} + +static void +free_key_pool (keypool *x) +{ + if (x) + { + free_key_pool (x->next); + HFree (x); + } +} + +#ifdef HAVE_JOYSTICK + +static void +create_joystick (int index) +{ + SDL_Joystick *stick; + int axes, buttons, hats; + if ((unsigned int) index >= joycount) + { + log_add (log_Warning, "VControl warning: Tried to open a non-existent joystick!"); + return; + } + if (joysticks[index].stick) + { + // Joystick is already created. Return. + return; + } + stick = SDL_JoystickOpen (index); + if (stick) + { + joystick *x = &joysticks[index]; + int j; +#if SDL_MAJOR_VERSION == 1 + log_add (log_Info, "VControl opened joystick: %s", SDL_JoystickName (index)); +#else + log_add (log_Info, "VControl opened joystick: %s", SDL_JoystickName (stick)); +#endif + axes = SDL_JoystickNumAxes (stick); + buttons = SDL_JoystickNumButtons (stick); + hats = SDL_JoystickNumHats (stick); + log_add (log_Info, "%d axes, %d buttons, %d hats.", axes, buttons, hats); + x->numaxes = axes; + x->numbuttons = buttons; + x->numhats = hats; + x->axes = HMalloc (sizeof (axis_type) * axes); + x->buttons = HMalloc (sizeof (keybinding *) * buttons); + x->hats = HMalloc (sizeof (hat_type) * hats); + for (j = 0; j < axes; j++) + { + x->axes[j].neg = x->axes[j].pos = NULL; + } + for (j = 0; j < hats; j++) + { + x->hats[j].left = x->hats[j].right = NULL; + x->hats[j].up = x->hats[j].down = NULL; + x->hats[j].last = SDL_HAT_CENTERED; + } + for (j = 0; j < buttons; j++) + { + x->buttons[j] = NULL; + } + x->stick = stick; + } + else + { + log_add (log_Warning, "VControl: Could not initialize joystick #%d", index); + } +} + +static void +destroy_joystick (int index) +{ + SDL_Joystick *stick = joysticks[index].stick; + if (stick) + { + SDL_JoystickClose (stick); + joysticks[index].stick = NULL; + HFree (joysticks[index].axes); + HFree (joysticks[index].buttons); + HFree (joysticks[index].hats); + joysticks[index].numaxes = joysticks[index].numbuttons = 0; + joysticks[index].axes = NULL; + joysticks[index].buttons = NULL; + joysticks[index].hats = NULL; + } +} + +#endif /* HAVE_JOYSTICK */ + +static void +key_init (void) +{ + unsigned int i; + int num_keys; // Temp to match type of param for SDL_GetKeyState(). + pool = allocate_key_chunk (); + for (i = 0; i < KEYBOARD_INPUT_BUCKETS; i++) + bindings[i] = NULL; + +#ifdef HAVE_JOYSTICK + /* Prepare for possible joystick controls. We don't actually + GRAB joysticks unless we're asked to make a joystick + binding, though. */ + joycount = SDL_NumJoysticks (); + if (joycount) + { + joysticks = HMalloc (sizeof (joystick) * joycount); + for (i = 0; i < joycount; i++) + { + joysticks[i].stick = NULL; + joysticks[i].numaxes = joysticks[i].numbuttons = 0; + joysticks[i].axes = NULL; + joysticks[i].buttons = NULL; + joysticks[i].threshold = 10000; + } + } + else + { + joysticks = NULL; + } +#else + joycount = 0; +#endif /* HAVE_JOYSTICK */ +} + +static void +key_uninit (void) +{ + unsigned int i; + free_key_pool (pool); + for (i = 0; i < KEYBOARD_INPUT_BUCKETS; i++) + bindings[i] = NULL; + pool = NULL; + +#ifdef HAVE_JOYSTICK + for (i = 0; i < joycount; i++) + destroy_joystick (i); + HFree (joysticks); +#endif /* HAVE_JOYSTICK */ +} + +void +VControl_Init (void) +{ + key_init (); +} + +void +VControl_Uninit (void) +{ + key_uninit (); +} + +int +VControl_SetJoyThreshold (int port, int threshold) +{ +#ifdef HAVE_JOYSTICK + if (port >= 0 && (unsigned int) port < joycount) + { + joysticks[port].threshold = threshold; + return 0; + } + else +#else + (void) port; + (void) threshold; +#endif /* HAVE_JOYSTICK */ + { + // log_add (log_Warning, "VControl_SetJoyThreshold passed illegal port %d", port); + return -1; + } +} + + +static void +add_binding (keybinding **newptr, int *target, sdl_key_t keycode) +{ + keybinding *newbinding; + keypool *searchbase; + int i; + + /* Acquire a pointer to the keybinding * that we'll be + * overwriting. Along the way, ensure we haven't already + * bound this symbol to this target. If we have, return.*/ + while (*newptr != NULL) + { + if (((*newptr)->target == target) && ((*newptr)->keycode == keycode)) + { + return; + } + newptr = &((*newptr)->next); + } + + /* Now hunt through the binding pool for a free binding. */ + + /* First, find a chunk with free spots in it */ + + searchbase = pool; + while (searchbase->remaining == 0) + { + /* If we're completely full, allocate a new chunk */ + if (searchbase->next == NULL) + { + searchbase->next = allocate_key_chunk (); + } + searchbase = searchbase->next; + } + + /* Now find a free binding within it */ + + newbinding = NULL; + for (i = 0; i < POOL_CHUNK_SIZE; i++) + { + if (searchbase->pool[i].target == NULL) + { + newbinding = &searchbase->pool[i]; + break; + } + } + + /* Sanity check. */ + if (!newbinding) + { + log_add (log_Warning, "add_binding failed to find a free binding slot!"); + return; + } + + newbinding->target = target; + newbinding->keycode = keycode; + newbinding->next = NULL; + *newptr = newbinding; + searchbase->remaining--; +} + +static void +remove_binding (keybinding **ptr, int *target, sdl_key_t keycode) +{ + if (!(*ptr)) + { + /* Nothing bound to symbol; return. */ + return; + } + else if (((*ptr)->target == target) && ((*ptr)->keycode == keycode)) + { + keybinding *todel = *ptr; + *ptr = todel->next; + todel->target = NULL; + todel->keycode = SDLK_UNKNOWN; + todel->next = NULL; + todel->parent->remaining++; + } + else + { + keybinding *prev = *ptr; + while (prev && prev->next != NULL) + { + if (prev->next->target == target) + { + keybinding *todel = prev->next; + prev->next = todel->next; + todel->target = NULL; + todel->keycode = SDLK_UNKNOWN; + todel->next = NULL; + todel->parent->remaining++; + } + prev = prev->next; + } + } +} + +static void +activate (keybinding *i, sdl_key_t keycode) +{ + while (i != NULL) + { + if (i->keycode == keycode) + { + *(i->target) = (*(i->target)+1) | VCONTROL_STARTBIT; + } + i = i->next; + } +} + +static void +deactivate (keybinding *i, sdl_key_t keycode) +{ + while (i != NULL) + { + int v = *(i->target) & VCONTROL_MASK; + if ((i->keycode == keycode) && (v > 0)) + { + *(i->target) = (v-1) | (*(i->target) & VCONTROL_STARTBIT); + } + i = i->next; + } +} + +static void +event2gesture (SDL_Event *e, VCONTROL_GESTURE *g) +{ + switch (e->type) + { + case SDL_KEYDOWN: + g->type = VCONTROL_KEY; + g->gesture.key = e->key.keysym.sym; + break; + case SDL_JOYAXISMOTION: + g->type = VCONTROL_JOYAXIS; + g->gesture.axis.port = e->jaxis.which; + g->gesture.axis.index = e->jaxis.axis; + g->gesture.axis.polarity = (e->jaxis.value < 0) ? -1 : 1; + break; + case SDL_JOYHATMOTION: + g->type = VCONTROL_JOYHAT; + g->gesture.hat.port = e->jhat.which; + g->gesture.hat.index = e->jhat.hat; + g->gesture.hat.dir = e->jhat.value; + break; + case SDL_JOYBUTTONDOWN: + g->type = VCONTROL_JOYBUTTON; + g->gesture.button.port = e->jbutton.which; + g->gesture.button.index = e->jbutton.button; + break; + + default: + g->type = VCONTROL_NONE; + break; + } +} + +int +VControl_AddGestureBinding (VCONTROL_GESTURE *g, int *target) +{ + int result = -1; + switch (g->type) + { + case VCONTROL_KEY: + result = VControl_AddKeyBinding (g->gesture.key, target); + break; + + case VCONTROL_JOYAXIS: +#ifdef HAVE_JOYSTICK + result = VControl_AddJoyAxisBinding (g->gesture.axis.port, g->gesture.axis.index, (g->gesture.axis.polarity < 0) ? -1 : 1, target); + break; +#endif + case VCONTROL_JOYHAT: +#ifdef HAVE_JOYSTICK + result = VControl_AddJoyHatBinding (g->gesture.hat.port, g->gesture.hat.index, g->gesture.hat.dir, target); + break; +#endif + case VCONTROL_JOYBUTTON: +#ifdef HAVE_JOYSTICK + result = VControl_AddJoyButtonBinding (g->gesture.button.port, g->gesture.button.index, target); + break; +#endif /* HAVE_JOYSTICK */ + case VCONTROL_NONE: + /* Do nothing */ + break; + + default: + log_add (log_Warning, "VControl_AddGestureBinding didn't understand argument gesture"); + result = -1; + break; + } + return result; +} + +void +VControl_RemoveGestureBinding (VCONTROL_GESTURE *g, int *target) +{ + switch (g->type) + { + case VCONTROL_KEY: + VControl_RemoveKeyBinding (g->gesture.key, target); + break; + + case VCONTROL_JOYAXIS: +#ifdef HAVE_JOYSTICK + VControl_RemoveJoyAxisBinding (g->gesture.axis.port, g->gesture.axis.index, (g->gesture.axis.polarity < 0) ? -1 : 1, target); + break; +#endif /* HAVE_JOYSTICK */ + case VCONTROL_JOYHAT: +#ifdef HAVE_JOYSTICK + VControl_RemoveJoyHatBinding (g->gesture.hat.port, g->gesture.hat.index, g->gesture.hat.dir, target); + break; +#endif /* HAVE_JOYSTICK */ + case VCONTROL_JOYBUTTON: +#ifdef HAVE_JOYSTICK + VControl_RemoveJoyButtonBinding (g->gesture.button.port, g->gesture.button.index, target); + break; +#endif /* HAVE_JOYSTICK */ + case VCONTROL_NONE: + break; + default: + log_add (log_Warning, "VControl_RemoveGestureBinding didn't understand argument gesture"); + break; + } +} + +int +VControl_AddKeyBinding (sdl_key_t symbol, int *target) +{ + add_binding(&bindings[symbol % KEYBOARD_INPUT_BUCKETS], target, symbol); + return 0; +} + +void +VControl_RemoveKeyBinding (sdl_key_t symbol, int *target) +{ + remove_binding (&bindings[symbol % KEYBOARD_INPUT_BUCKETS], target, symbol); +} + +int +VControl_AddJoyAxisBinding (int port, int axis, int polarity, int *target) +{ +#ifdef HAVE_JOYSTICK + if (port >= 0 && (unsigned int) port < joycount) + { + joystick *j = &joysticks[port]; + if (!(j->stick)) + create_joystick (port); + if ((axis >= 0) && (axis < j->numaxes)) + { + if (polarity < 0) + { + add_binding(&joysticks[port].axes[axis].neg, target, SDLK_UNKNOWN); + } + else if (polarity > 0) + { + add_binding(&joysticks[port].axes[axis].pos, target, SDLK_UNKNOWN); + } + else + { + log_add (log_Debug, "VControl: Attempted to bind to polarity zero"); + return -1; + } + } + else + { + // log_add (log_Debug, "VControl: Attempted to bind to illegal axis %d", axis); + return -1; + } + } + else +#else + (void) port; + (void) axis; + (void) polarity; + (void) target; +#endif /* HAVE_JOYSTICK */ + { + // log_add (log_Debug, "VControl: Attempted to bind to illegal port %d", port); + return -1; + } + return 0; +} + +void +VControl_RemoveJoyAxisBinding (int port, int axis, int polarity, int *target) +{ +#ifdef HAVE_JOYSTICK + if (port >= 0 && (unsigned int) port < joycount) + { + joystick *j = &joysticks[port]; + if (!(j->stick)) + create_joystick (port); + if ((axis >= 0) && (axis < j->numaxes)) + { + if (polarity < 0) + { + remove_binding(&joysticks[port].axes[axis].neg, target, SDLK_UNKNOWN); + } + else if (polarity > 0) + { + remove_binding(&joysticks[port].axes[axis].pos, target, SDLK_UNKNOWN); + } + else + { + log_add (log_Debug, "VControl: Attempted to unbind from polarity zero"); + } + } + else + { + log_add (log_Debug, "VControl: Attempted to unbind from illegal axis %d", axis); + } + } + else +#else + (void) port; + (void) axis; + (void) polarity; + (void) target; +#endif /* HAVE_JOYSTICK */ + { + log_add (log_Debug, "VControl: Attempted to unbind from illegal port %d", port); + } +} + +int +VControl_AddJoyButtonBinding (int port, int button, int *target) +{ +#ifdef HAVE_JOYSTICK + if (port >= 0 && (unsigned int) port < joycount) + { + joystick *j = &joysticks[port]; + if (!(j->stick)) + create_joystick (port); + if ((button >= 0) && (button < j->numbuttons)) + { + add_binding(&joysticks[port].buttons[button], target, SDLK_UNKNOWN); + return 0; + } + else + { + // log_add (log_Debug, "VControl: Attempted to bind to illegal button %d", button); + return -1; + } + } + else +#else + (void) port; + (void) button; + (void) target; +#endif /* HAVE_JOYSTICK */ + { + // log_add (log_Debug, "VControl: Attempted to bind to illegal port %d", port); + return -1; + } +} + +void +VControl_RemoveJoyButtonBinding (int port, int button, int *target) +{ +#ifdef HAVE_JOYSTICK + if (port >= 0 && (unsigned int) port < joycount) + { + joystick *j = &joysticks[port]; + if (!(j->stick)) + create_joystick (port); + if ((button >= 0) && (button < j->numbuttons)) + { + remove_binding (&joysticks[port].buttons[button], target, SDLK_UNKNOWN); + } + else + { + log_add (log_Debug, "VControl: Attempted to unbind from illegal button %d", button); + } + } + else +#else + (void) port; + (void) button; + (void) target; +#endif /* HAVE_JOYSTICK */ + { + log_add (log_Debug, "VControl: Attempted to unbind from illegal port %d", port); + } +} + +int +VControl_AddJoyHatBinding (int port, int which, Uint8 dir, int *target) +{ +#ifdef HAVE_JOYSTICK + if (port >= 0 && (unsigned int) port < joycount) + { + joystick *j = &joysticks[port]; + if (!(j->stick)) + create_joystick (port); + if ((which >= 0) && (which < j->numhats)) + { + if (dir == SDL_HAT_LEFT) + { + add_binding(&joysticks[port].hats[which].left, target, SDLK_UNKNOWN); + } + else if (dir == SDL_HAT_RIGHT) + { + add_binding(&joysticks[port].hats[which].right, target, SDLK_UNKNOWN); + } + else if (dir == SDL_HAT_UP) + { + add_binding(&joysticks[port].hats[which].up, target, SDLK_UNKNOWN); + } + else if (dir == SDL_HAT_DOWN) + { + add_binding(&joysticks[port].hats[which].down, target, SDLK_UNKNOWN); + } + else + { + // log_add (log_Debug, "VControl: Attempted to bind to illegal direction"); + return -1; + } + return 0; + } + else + { + // log_add (log_Debug, "VControl: Attempted to bind to illegal hat %d", which); + return -1; + } + } + else +#else + (void) port; + (void) which; + (void) dir; + (void) target; +#endif /* HAVE_JOYSTICK */ + { + // log_add (log_Debug, "VControl: Attempted to bind to illegal port %d", port); + return -1; + } +} + +void +VControl_RemoveJoyHatBinding (int port, int which, Uint8 dir, int *target) +{ +#ifdef HAVE_JOYSTICK + if (port >= 0 && (unsigned int) port < joycount) + { + joystick *j = &joysticks[port]; + if (!(j->stick)) + create_joystick (port); + if ((which >= 0) && (which < j->numhats)) + { + if (dir == SDL_HAT_LEFT) + { + remove_binding(&joysticks[port].hats[which].left, target, SDLK_UNKNOWN); + } + else if (dir == SDL_HAT_RIGHT) + { + remove_binding(&joysticks[port].hats[which].right, target, SDLK_UNKNOWN); + } + else if (dir == SDL_HAT_UP) + { + remove_binding(&joysticks[port].hats[which].up, target, SDLK_UNKNOWN); + } + else if (dir == SDL_HAT_DOWN) + { + remove_binding(&joysticks[port].hats[which].down, target, SDLK_UNKNOWN); + } + else + { + log_add (log_Debug, "VControl: Attempted to unbind from illegal direction"); + } + } + else + { + log_add (log_Debug, "VControl: Attempted to unbind from illegal hat %d", which); + } + } + else +#else + (void) port; + (void) which; + (void) dir; + (void) target; +#endif /* HAVE_JOYSTICK */ + { + log_add (log_Debug, "VControl: Attempted to unbind from illegal port %d", port); + } +} + +void +VControl_RemoveAllBindings (void) +{ + key_uninit (); + key_init (); +} + +void +VControl_ProcessKeyDown (sdl_key_t symbol) +{ + activate (bindings[symbol % KEYBOARD_INPUT_BUCKETS], symbol); +} + +void +VControl_ProcessKeyUp (sdl_key_t symbol) +{ + deactivate (bindings[symbol % KEYBOARD_INPUT_BUCKETS], symbol); +} + +void +VControl_ProcessJoyButtonDown (int port, int button) +{ +#ifdef HAVE_JOYSTICK + if (!joysticks[port].stick) + return; + activate (joysticks[port].buttons[button], SDLK_UNKNOWN); +#else + (void) port; + (void) button; +#endif /* HAVE_JOYSTICK */ +} + +void +VControl_ProcessJoyButtonUp (int port, int button) +{ +#ifdef HAVE_JOYSTICK + if (!joysticks[port].stick) + return; + deactivate (joysticks[port].buttons[button], SDLK_UNKNOWN); +#else + (void) port; + (void) button; +#endif /* HAVE_JOYSTICK */ +} + +void +VControl_ProcessJoyAxis (int port, int axis, int value) +{ +#ifdef HAVE_JOYSTICK + int t; + if (!joysticks[port].stick) + return; + t = joysticks[port].threshold; + if (value > t) + { + if (joysticks[port].axes[axis].polarity != 1) + { + if (joysticks[port].axes[axis].polarity == -1) + { + deactivate (joysticks[port].axes[axis].neg, SDLK_UNKNOWN); + } + joysticks[port].axes[axis].polarity = 1; + activate (joysticks[port].axes[axis].pos, SDLK_UNKNOWN); + } + } + else if (value < -t) + { + if (joysticks[port].axes[axis].polarity != -1) + { + if (joysticks[port].axes[axis].polarity == 1) + { + deactivate (joysticks[port].axes[axis].pos, SDLK_UNKNOWN); + } + joysticks[port].axes[axis].polarity = -1; + activate (joysticks[port].axes[axis].neg, SDLK_UNKNOWN); + } + } + else + { + if (joysticks[port].axes[axis].polarity == -1) + { + deactivate (joysticks[port].axes[axis].neg, SDLK_UNKNOWN); + } + else if (joysticks[port].axes[axis].polarity == 1) + { + deactivate (joysticks[port].axes[axis].pos, SDLK_UNKNOWN); + } + joysticks[port].axes[axis].polarity = 0; + } +#else + (void) port; + (void) axis; + (void) value; +#endif /* HAVE_JOYSTICK */ +} + +void +VControl_ProcessJoyHat (int port, int which, Uint8 value) +{ +#ifdef HAVE_JOYSTICK + Uint8 old; + if (!joysticks[port].stick) + return; + old = joysticks[port].hats[which].last; + if (!(old & SDL_HAT_LEFT) && (value & SDL_HAT_LEFT)) + activate (joysticks[port].hats[which].left, SDLK_UNKNOWN); + if (!(old & SDL_HAT_RIGHT) && (value & SDL_HAT_RIGHT)) + activate (joysticks[port].hats[which].right, SDLK_UNKNOWN); + if (!(old & SDL_HAT_UP) && (value & SDL_HAT_UP)) + activate (joysticks[port].hats[which].up, SDLK_UNKNOWN); + if (!(old & SDL_HAT_DOWN) && (value & SDL_HAT_DOWN)) + activate (joysticks[port].hats[which].down, SDLK_UNKNOWN); + if ((old & SDL_HAT_LEFT) && !(value & SDL_HAT_LEFT)) + deactivate (joysticks[port].hats[which].left, SDLK_UNKNOWN); + if ((old & SDL_HAT_RIGHT) && !(value & SDL_HAT_RIGHT)) + deactivate (joysticks[port].hats[which].right, SDLK_UNKNOWN); + if ((old & SDL_HAT_UP) && !(value & SDL_HAT_UP)) + deactivate (joysticks[port].hats[which].up, SDLK_UNKNOWN); + if ((old & SDL_HAT_DOWN) && !(value & SDL_HAT_DOWN)) + deactivate (joysticks[port].hats[which].down, SDLK_UNKNOWN); + joysticks[port].hats[which].last = value; +#else + (void) port; + (void) which; + (void) value; +#endif /* HAVE_JOYSTICK */ +} + +void +VControl_ResetInput (void) +{ + /* Step through every valid entry in the binding pool and zero + * them out. This will probably zero entries multiple times; + * oh well, no harm done. */ + + keypool *base = pool; + while (base != NULL) + { + int i; + for (i = 0; i < POOL_CHUNK_SIZE; i++) + { + if(base->pool[i].target) + { + *(base->pool[i].target) = 0; + } + } + base = base->next; + } +} + +void +VControl_BeginFrame (void) +{ + /* Step through every valid entry in the binding pool and zero + * out the frame-start bit. This will probably zero entries + * multiple times; oh well, no harm done. */ + + keypool *base = pool; + while (base != NULL) + { + int i; + for (i = 0; i < POOL_CHUNK_SIZE; i++) + { + if(base->pool[i].target) + { + *(base->pool[i].target) &= VCONTROL_MASK; + } + } + base = base->next; + } +} + +void +VControl_HandleEvent (const SDL_Event *e) +{ + switch (e->type) + { + case SDL_KEYDOWN: +#if SDL_MAJOR_VERSION > 1 + if (!e->key.repeat) +#endif + { + VControl_ProcessKeyDown (e->key.keysym.sym); + last_interesting = *e; + event_ready = 1; + } + break; + case SDL_KEYUP: + VControl_ProcessKeyUp (e->key.keysym.sym); + break; + +#ifdef HAVE_JOYSTICK + case SDL_JOYAXISMOTION: + VControl_ProcessJoyAxis (e->jaxis.which, e->jaxis.axis, e->jaxis.value); + if ((e->jaxis.value > 15000) || (e->jaxis.value < -15000)) + { + last_interesting = *e; + event_ready = 1; + } + break; + case SDL_JOYHATMOTION: + VControl_ProcessJoyHat (e->jhat.which, e->jhat.hat, e->jhat.value); + last_interesting = *e; + event_ready = 1; + break; + case SDL_JOYBUTTONDOWN: + VControl_ProcessJoyButtonDown (e->jbutton.which, e->jbutton.button); + last_interesting = *e; + event_ready = 1; + break; + case SDL_JOYBUTTONUP: + VControl_ProcessJoyButtonUp (e->jbutton.which, e->jbutton.button); + break; +#endif /* HAVE_JOYSTICK */ + + default: + break; + } +} + +/* Tracking the last interesting event */ + +void +VControl_ClearGesture (void) +{ + event_ready = 0; +} + +int +VControl_GetLastGesture (VCONTROL_GESTURE *g) +{ + if (event_ready && g != NULL) + { + event2gesture(&last_interesting, g); + } + return event_ready; +} + +/* Configuration file grammar is as follows: One command per line, + * hashes introduce comments that persist to end of line. Blank lines + * are ignored. + * + * Terminals are represented here as quoted strings, e.g. "foo" for + * the literal string foo. These are matched case-insensitively. + * Special terminals are: + * + * KEYNAME: This names a key, as defined in keynames.c. + * IDNAME: This is an arbitrary string of alphanumerics, + * case-insensitive, and ending with a colon. This + * names an application-specific control value. + * NUM: This is an unsigned integer. + * EOF: End of file + * + * Nonterminals (the grammar itself) have the following productions: + * + * configline <- IDNAME binding + * | "joystick" NUM "threshold" NUM + * | "version" NUM + * + * binding <- "key" KEYNAME + * | "joystick" NUM joybinding + * + * joybinding <- "axis" NUM polarity + * | "button" NUM + * | "hat" NUM direction + * + * polarity <- "positive" | "negative" + * + * dir <- "up" | "down" | "left" | "right" + * + * This grammar is amenable to simple recursive descent parsing; + * in fact, it's fully LL(1). */ + +/* Actual maximum line and token sizes are two less than this, since + * we need space for the \n\0 at the end */ +#define LINE_SIZE 256 +#define TOKEN_SIZE 64 + +typedef struct vcontrol_parse_state { + char line[LINE_SIZE]; + char token[TOKEN_SIZE]; + int index; + int error; + int linenum; +} parse_state; + +static void +next_token (parse_state *state) +{ + int index, base; + + state->token[0] = 0; + /* skip preceding whitespace */ + base = state->index; + while (state->line[base] && isspace (state->line[base])) + { + base++; + } + + index = 0; + while (index < (TOKEN_SIZE-1) && state->line[base+index] && !isspace (state->line[base+index])) + { + state->token[index] = state->line[base+index]; + index++; + } + state->token[index] = 0; + + /* If the token was too long, skip ahead until we get to whitespace */ + while (state->line[base+index] && !isspace (state->line[base+index])) + { + index++; + } + + state->index = base+index; +} + +static void +expected_error (parse_state *state, const char *expected) +{ + log_add (log_Warning, "VControl: Expected '%s' on config file line %d", + expected, state->linenum); + state->error = 1; +} + +static void +consume (parse_state *state, const char *expected) +{ + if (strcasecmp (expected, state->token)) + { + expected_error (state, expected); + } + next_token (state); +} + +static int +consume_keyname (parse_state *state) +{ + int keysym = VControl_name2code (state->token); + if (!keysym) + { + log_add (log_Warning, "VControl: Illegal key name '%s' on config file line %d", + state->token, state->linenum); + state->error = 1; + } + next_token (state); + return keysym; +} + +static int +consume_num (parse_state *state) +{ + char *end; + int result = strtol (state->token, &end, 10); + if (*end != '\0') + { + log_add (log_Warning, "VControl: Expected integer on config line %d", + state->linenum); + state->error = 1; + } + next_token (state); + return result; +} + +static int +consume_polarity (parse_state *state) +{ + int result = 0; + if (!strcasecmp (state->token, "positive")) + { + result = 1; + } + else if (!strcasecmp (state->token, "negative")) + { + result = -1; + } + else + { + expected_error (state, "positive' or 'negative"); + } + next_token (state); + return result; +} + +static Uint8 +consume_dir (parse_state *state) +{ + Uint8 result = 0; + if (!strcasecmp (state->token, "left")) + { + result = SDL_HAT_LEFT; + } + else if (!strcasecmp (state->token, "right")) + { + result = SDL_HAT_RIGHT; + } + else if (!strcasecmp (state->token, "up")) + { + result = SDL_HAT_UP; + } + else if (!strcasecmp (state->token, "down")) + { + result = SDL_HAT_DOWN; + } + else + { + expected_error (state, "left', 'right', 'up' or 'down"); + } + next_token (state); + return result; +} + +static void +parse_joybinding (parse_state *state, VCONTROL_GESTURE *gesture) +{ + int sticknum; + consume (state, "joystick"); + sticknum = consume_num (state); + if (!state->error) + { + if (!strcasecmp (state->token, "axis")) + { + int axisnum; + consume (state, "axis"); + axisnum = consume_num (state); + if (!state->error) + { + int polarity = consume_polarity (state); + if (!state->error) + { + gesture->type = VCONTROL_JOYAXIS; + gesture->gesture.axis.port = sticknum; + gesture->gesture.axis.index = axisnum; + gesture->gesture.axis.polarity = polarity; + } + } + } + else if (!strcasecmp (state->token, "button")) + { + int buttonnum; + consume (state, "button"); + buttonnum = consume_num (state); + if (!state->error) + { + gesture->type = VCONTROL_JOYBUTTON; + gesture->gesture.button.port = sticknum; + gesture->gesture.button.index = buttonnum; + } + } + else if (!strcasecmp (state->token, "hat")) + { + int hatnum; + consume (state, "hat"); + hatnum = consume_num (state); + if (!state->error) + { + Uint8 dir = consume_dir (state); + if (!state->error) + { + gesture->type = VCONTROL_JOYHAT; + gesture->gesture.hat.port = sticknum; + gesture->gesture.hat.index = hatnum; + gesture->gesture.hat.dir = dir; + } + } + } + else + { + expected_error (state, "axis', 'button', or 'hat"); + } + } +} + +static void +parse_gesture (parse_state *state, VCONTROL_GESTURE *gesture) +{ + gesture->type = VCONTROL_NONE; /* Default to error */ + if (!strcasecmp (state->token, "key")) + { + /* Parse key binding */ + int keysym; + consume (state, "key"); + keysym = consume_keyname (state); + if (!state->error) + { + gesture->type = VCONTROL_KEY; + gesture->gesture.key = keysym; + } + } + else if (!strcasecmp (state->token, "joystick")) + { + parse_joybinding (state, gesture); + } + else + { + expected_error (state, "key' or 'joystick"); + } +} + +void +VControl_ParseGesture (VCONTROL_GESTURE *g, const char *spec) +{ + parse_state ps; + + strncpy (ps.line, spec, LINE_SIZE); + ps.line[LINE_SIZE - 1] = '\0'; + ps.index = ps.error = 0; + ps.linenum = -1; + + next_token (&ps); + parse_gesture (&ps, g); + if (ps.error) + printf ("Error parsing %s\n", spec); +} + +int +VControl_DumpGesture (char *buf, int n, VCONTROL_GESTURE *g) +{ + switch (g->type) + { + case VCONTROL_KEY: + return snprintf (buf, n, "key %s", VControl_code2name (g->gesture.key)); + case VCONTROL_JOYAXIS: + return snprintf (buf, n, "joystick %d axis %d %s", g->gesture.axis.port, g->gesture.axis.index, + (g->gesture.axis.polarity > 0) ? "positive" : "negative"); + case VCONTROL_JOYBUTTON: + return snprintf (buf, n, "joystick %d button %d", g->gesture.button.port, g->gesture.button.index); + case VCONTROL_JOYHAT: + return snprintf (buf, n, "joystick %d hat %d %s", g->gesture.hat.port, g->gesture.hat.index, + (g->gesture.hat.dir == SDL_HAT_UP) ? "up" : + ((g->gesture.hat.dir == SDL_HAT_DOWN) ? "down" : + ((g->gesture.hat.dir == SDL_HAT_LEFT) ? "left" : "right"))); + default: + buf[0] = '\0'; + return 0; + } +} diff --git a/src/libs/input/sdl/vcontrol.h b/src/libs/input/sdl/vcontrol.h new file mode 100644 index 0000000..9c33445 --- /dev/null +++ b/src/libs/input/sdl/vcontrol.h @@ -0,0 +1,108 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_INPUT_SDL_VCONTROL_H_ +#define LIBS_INPUT_SDL_VCONTROL_H_ + +#include "port.h" +#include SDL_INCLUDE(SDL.h) + +#if SDL_MAJOR_VERSION == 1 +typedef SDLKey sdl_key_t; +#else +typedef SDL_Keycode sdl_key_t; +#endif + +/* Initialization routines */ +void VControl_Init (void); +void VControl_Uninit (void); + +/* Structures for representing actual VControl Inputs. Returned by + iterators and used to construct bindings. */ + +typedef enum { + VCONTROL_NONE, + VCONTROL_KEY, + VCONTROL_JOYAXIS, + VCONTROL_JOYBUTTON, + VCONTROL_JOYHAT, + NUM_VCONTROL_GESTURES +} VCONTROL_GESTURE_TYPE; + +typedef struct { + VCONTROL_GESTURE_TYPE type; + union { + sdl_key_t key; + struct { int port, index, polarity; } axis; + struct { int port, index; } button; + struct { int port, index; Uint8 dir; } hat; + } gesture; +} VCONTROL_GESTURE; + +/* Control of bindings */ +int VControl_AddGestureBinding (VCONTROL_GESTURE *g, int *target); +void VControl_RemoveGestureBinding (VCONTROL_GESTURE *g, int *target); + +int VControl_AddKeyBinding (sdl_key_t symbol, int *target); +void VControl_RemoveKeyBinding (sdl_key_t symbol, int *target); +int VControl_AddJoyAxisBinding (int port, int axis, int polarity, int *target); +void VControl_RemoveJoyAxisBinding (int port, int axis, int polarity, int *target); +int VControl_SetJoyThreshold (int port, int threshold); +int VControl_AddJoyButtonBinding (int port, int button, int *target); +void VControl_RemoveJoyButtonBinding (int port, int button, int *target); +int VControl_AddJoyHatBinding (int port, int which, Uint8 dir, int *target); +void VControl_RemoveJoyHatBinding (int port, int which, Uint8 dir, int *target); + +void VControl_RemoveAllBindings (void); + +/* Signal to VControl that a frame is about to begin. */ +void VControl_BeginFrame (void); + +/* The listener. Routines besides HandleEvent may be used to 'fake' inputs without + * fabricating an SDL_Event. + */ +void VControl_HandleEvent (const SDL_Event *e); +void VControl_ProcessKeyDown (sdl_key_t symbol); +void VControl_ProcessKeyUp (sdl_key_t symbol); +void VControl_ProcessJoyButtonDown (int port, int button); +void VControl_ProcessJoyButtonUp (int port, int button); +void VControl_ProcessJoyAxis (int port, int axis, int value); +void VControl_ProcessJoyHat (int port, int which, Uint8 value); + +/* Force the input into the blank state. For preventing "sticky" keys. */ +void VControl_ResetInput (void); + +/* Translate between gestures and string representations thereof. */ +void VControl_ParseGesture (VCONTROL_GESTURE *g, const char *spec); +int VControl_DumpGesture (char *buf, int n, VCONTROL_GESTURE *g); + +/* Tracking the "last interesting gesture." Used to poll to find new + control keys. */ + +void VControl_ClearGesture (void); +int VControl_GetLastGesture (VCONTROL_GESTURE *g); + +/* Constants for handling the "Start bit." If a gesture is made, and + * then ends, within a single frame, it will still, for one frame, + * have a nonzero value. This is because Bit 16 will be on for the + * first frame a gesture is struck. This bit is cleared when + * VControl_BeginFrame() is called. These constants are used to mask + * out results if necessary. */ + +#define VCONTROL_STARTBIT 0x10000 +#define VCONTROL_MASK 0x0FFFF + +#endif diff --git a/src/libs/list.h b/src/libs/list.h new file mode 100644 index 0000000..42a28d4 --- /dev/null +++ b/src/libs/list.h @@ -0,0 +1,29 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#if defined(__cplusplus) +extern "C" { +#endif + +#include "list/list.h" + +#if defined(__cplusplus) +} +#endif + + diff --git a/src/libs/list/Makeinfo b/src/libs/list/Makeinfo new file mode 100644 index 0000000..c2bc72b --- /dev/null +++ b/src/libs/list/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="list.c" +uqm_HFILES="list.h" diff --git a/src/libs/list/list.c b/src/libs/list/list.c new file mode 100644 index 0000000..9d86538 --- /dev/null +++ b/src/libs/list/list.c @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2005 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef LIST_INTERNAL + // If list is already defined, this file is included + // as a template. In this case list.h has already been included. +# define LIST_INTERNAL +# include "list.h" +#endif + +#include "libs/memlib.h" +#define malloc HMalloc +#define free HFree +#define realloc HRealloc + +#include <assert.h> + +static inline LIST_(List) *LIST_(allocList)(void); +static inline void LIST_(freeList)(LIST_(List) *list); +static inline LIST_(Link) * LIST_(allocLink)(void); +static inline void LIST_(freeLink)(LIST_(Link) *link); + + +LIST_(List) * +LIST_(newList)(void) { + LIST_(List) *list; + + list = LIST_(allocList)(); + if (list == NULL) + return NULL; + + list->first = NULL; + list->end = &list->first; + return list; +} + +void +LIST_(deleteList)(LIST_(List) *list) +{ + LIST_(Link) *link; + LIST_(Link) *next; + + for (link = list->first; link != NULL; link = next) + { + next = link->next; + LIST_(freeLink)(link); + } + + LIST_(freeList)(list); +} + +void +LIST_(add)(LIST_(List) *list, LIST_(Entry) entry) { + LIST_(Link) *link; + + link = LIST_(allocLink)(); + link->entry = entry; + link->next = NULL; + *list->end = link; + list->end = &link->next; +} + +static inline LIST_(Link) ** +LIST_(findLink)(LIST_(List) *list, LIST_(Entry) entry) { + LIST_(Link) **linkPtr; + + for (linkPtr = &list->first; *linkPtr != NULL; + linkPtr = &(*linkPtr)->next) { + if ((*linkPtr)->entry == entry) + return linkPtr; + } + return NULL; +} + +static inline void +LIST_(removeLink)(LIST_(List) *list, LIST_(Link) **linkPtr) { + LIST_(Link) *link = *linkPtr; + + *linkPtr = link->next; + if (&link->next == list->end) + list->end = linkPtr; + LIST_(freeLink)(link); +} + +void +LIST_(remove)(LIST_(List) *list, LIST_(Entry) entry) { + LIST_(Link) **linkPtr; + + linkPtr = LIST_(findLink)(list, entry); + assert(linkPtr != NULL); + LIST_(removeLink)(list, linkPtr); +} + + +static inline LIST_(List) * +LIST_(allocList)(void) { + return malloc(sizeof (LIST_(List))); +} + +static inline void +LIST_(freeList)(LIST_(List) *list) { + free(list); +} + +static inline LIST_(Link) * +LIST_(allocLink)(void) { + return malloc(sizeof (LIST_(Link))); +} + +static inline void +LIST_(freeLink)(LIST_(Link) *link) { + free(link); +} + + diff --git a/src/libs/list/list.h b/src/libs/list/list.h new file mode 100644 index 0000000..2fd3332 --- /dev/null +++ b/src/libs/list/list.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2005 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +// The 'already included' check must be done slightly more complicated +// than usually. This file may be included directly only once, +// but it may be included my derivative List definitions that use +// this file as a template more than once. +#if !defined(_LIST_H) || defined(LIST_GENERIC) +#if defined(LIST_) +# define LIST_GENERIC +#endif + +#include "types.h" +#include "port.h" + +// You can use inline lists, by using this file as a template. +// To do this, make a new .h and .c file. In the .h file, define the macros +// (and typedefs) from the LIST_ block below. +// In the .c file, #define LIST_INTERNAL, #include the .h file +// and list.c (in this order), and add the necessary functions. +#ifndef LIST_ +# define LIST_(identifier) List ## _ ## identifier + typedef void *List_Entry; +#endif + + +typedef struct LIST_(List) LIST_(List); +typedef struct LIST_(Link) LIST_(Link); + +struct LIST_(Link) { + LIST_(Entry) entry; + LIST_(Link) *next; +}; + +struct LIST_(List) { + LIST_(Link) *first; + LIST_(Link) **end; +}; + + +LIST_(List) *LIST_(newList)(void); +void LIST_(deleteList)(LIST_(List) *list); +void LIST_(add)(LIST_(List) *list, LIST_(Entry) entry); +void LIST_(remove)(LIST_(List) *list, LIST_(Entry) entry); + + +#ifndef LIST_INTERNAL +# undef LIST_ +#endif + +#endif /* !defined(_LIST_H) || defined(LIST_GENERIC) */ + + diff --git a/src/libs/log.h b/src/libs/log.h new file mode 100644 index 0000000..11537c7 --- /dev/null +++ b/src/libs/log.h @@ -0,0 +1,25 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#if defined(__cplusplus) +extern "C" { +#endif + +#include "log/uqmlog.h" + +#if defined(__cplusplus) +} +#endif diff --git a/src/libs/log/Makeinfo b/src/libs/log/Makeinfo new file mode 100644 index 0000000..124260f --- /dev/null +++ b/src/libs/log/Makeinfo @@ -0,0 +1,15 @@ +uqm_CFILES="uqmlog.c" +uqm_HFILES="loginternal.h msgbox.h uqmlog.h" + +case "$HOST_SYSTEM" in + Darwin) + uqm_MFILES="msgbox_macosx.m" + ;; + MINGW32*|CYGWIN*) + uqm_CFILES="$uqm_CFILES msgbox_win.c" + ;; + *) + uqm_CFILES="$uqm_CFILES msgbox_stub.c" + ;; +esac + diff --git a/src/libs/log/loginternal.h b/src/libs/log/loginternal.h new file mode 100644 index 0000000..4457155 --- /dev/null +++ b/src/libs/log/loginternal.h @@ -0,0 +1,24 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef UQM_LOGINTERNAL_H_INCL__ +#define UQM_LOGINTERNAL_H_INCL__ + +#include <stdio.h> + +extern FILE *streamOut; + +#endif /* UQM_LOGINTERNAL_H_INCL__ */ diff --git a/src/libs/log/msgbox.h b/src/libs/log/msgbox.h new file mode 100644 index 0000000..72934a5 --- /dev/null +++ b/src/libs/log/msgbox.h @@ -0,0 +1,23 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef UQM_MSGBOX_H_INCL__ +#define UQM_MSGBOX_H_INCL__ + +extern void log_displayBox (const /*UTF-8*/char *title, int isError, + const /*UTF-8*/char *msg); + +#endif /* UQM_MSGBOX_H_INCL__ */ diff --git a/src/libs/log/msgbox_macosx.m b/src/libs/log/msgbox_macosx.m new file mode 100644 index 0000000..eca32be --- /dev/null +++ b/src/libs/log/msgbox_macosx.m @@ -0,0 +1,42 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#import <Cocoa/Cocoa.h> +#import "msgbox.h" + +void +log_displayBox (const /*UTF-8*/char *title, int isError, + const /*UTF-8*/char *msg) +{ + @autoreleasepool { + NSAlert *alert = [[NSAlert alloc] init]; + NSString *titleStr = [NSString stringWithUTF8String:title]; + NSString *msgStr = [NSString stringWithUTF8String:msg]; + + if (alert && titleStr && msgStr) { + alert.alertStyle = isError ? NSAlertStyleCritical : NSAlertStyleInformational; + alert.messageText = titleStr; + alert.informativeText = msgStr; + + [alert runModal]; + } + + [msgStr release]; + [titleStr release]; + [alert release]; + } +} + diff --git a/src/libs/log/msgbox_stub.c b/src/libs/log/msgbox_stub.c new file mode 100644 index 0000000..8e0b6b6 --- /dev/null +++ b/src/libs/log/msgbox_stub.c @@ -0,0 +1,34 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "msgbox.h" +#include "loginternal.h" + +void +log_displayBox (const /*UTF-8*/char *title, int isError, + const /*UTF-8*/char *msg) +{ + // We do not know how to display a box. Perhaps it's done with a + // hefty dose of pixie dust, or perhaps with a hammer and nails. + // So just inform the user of our predicament + fprintf (streamOut, "Do not know how to display %s box\n", + isError ? "an error" : "a"); + + // Suppress the compiler warnings in any case. + (void)title; + (void)msg; +} + diff --git a/src/libs/log/msgbox_win.c b/src/libs/log/msgbox_win.c new file mode 100644 index 0000000..c4e0021 --- /dev/null +++ b/src/libs/log/msgbox_win.c @@ -0,0 +1,67 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "msgbox.h" +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#include <stdlib.h> +#include <malloc.h> + + +// Converts a UTF-8 string to Windows WideChar. +// Caller is responsible for free()ing the returned string. +static LPWSTR +toWideChar (const /*UTF-8*/char *str) +{ + int cch; + LPWSTR wstr; + + cch = MultiByteToWideChar (CP_UTF8, 0, str, -1, NULL, 0); + if (cch == 0) + return NULL; // failed, probably no UTF8 converter + + wstr = malloc (cch * sizeof (WCHAR)); + if (!wstr) + return NULL; // out of memory + + cch = MultiByteToWideChar (CP_UTF8, 0, str, -1, wstr, cch); + if (cch == 0) + { // Failed. It should not fail here if it succeeded just above, + // but it did. Not much can be done about it. + free (wstr); + return NULL; + } + + return wstr; +} + +void +log_displayBox (const /*UTF-8*/char *title, int isError, + const /*UTF-8*/char *msg) +{ + LPWSTR swTitle = toWideChar (title); + LPWSTR swMsg = toWideChar (msg); + UINT uType = isError ? MB_ICONWARNING : MB_ICONINFORMATION; + + if (swTitle && swMsg) + MessageBoxW (NULL, swMsg, swTitle, uType); + else // Could not convert; let's try ASCII, though it may look ugly + MessageBoxA (NULL, msg, title, uType); + + free (swTitle); + free (swMsg); +} + diff --git a/src/libs/log/uqmlog.c b/src/libs/log/uqmlog.c new file mode 100644 index 0000000..e054edb --- /dev/null +++ b/src/libs/log/uqmlog.c @@ -0,0 +1,331 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "uqmlog.h" +#include "loginternal.h" +#include "msgbox.h" +#include <string.h> +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <signal.h> +#include <errno.h> +#include "libs/threadlib.h" + +#ifndef MAX_LOG_ENTRY_SIZE +# define MAX_LOG_ENTRY_SIZE 256 +#endif + +#ifndef MAX_LOG_ENTRIES +# define MAX_LOG_ENTRIES 128 +#endif + +typedef char log_Entry[MAX_LOG_ENTRY_SIZE]; + +// static buffers in case we run out of memory +static log_Entry queue[MAX_LOG_ENTRIES]; +static log_Entry msgNoThread; +static char msgBuf[16384]; + +static int maxLevel = log_Error; +static int maxStreamLevel = log_Debug; +static int maxDisp = 10; +static int qtotal = 0; +static int qhead = 0; +static int qtail = 0; +static volatile bool noThreadReady = false; +static bool showBox = true; +static bool errorBox = true; + +FILE *streamOut; + +static volatile int qlock = 0; +static Mutex qmutex; + +static void exitCallback (void); +static void displayLog (bool isError); + +static void +lockQueue (void) +{ + if (!qlock) + return; + + LockMutex (qmutex); +} + +static void +unlockQueue (void) +{ + if (!qlock) + return; + + UnlockMutex (qmutex); +} + +static void +removeExcess (int room) +{ + room = maxDisp - room; + if (room < 0) + room = 0; + + for ( ; qtotal > room; --qtotal, ++qtail) + ; + qtail %= MAX_LOG_ENTRIES; +} + +static int +acquireSlot (void) +{ + int slot; + + lockQueue (); + + removeExcess (1); + slot = qhead; + qhead = (qhead + 1) % MAX_LOG_ENTRIES; + ++qtotal; + + unlockQueue (); + + return slot; +} + +// queues the non-threaded message when present +static void +queueNonThreaded (void) +{ + int slot; + + // This is not perfect. A race condition still exists + // between buffering the no-thread message and setting + // the noThreadReady flag. Neither does this prevent + // the fully or partially overwritten message (by + // another competing thread). But it is 'good enough' + if (!noThreadReady) + return; + noThreadReady = false; + + slot = acquireSlot (); + memcpy (queue[slot], msgNoThread, sizeof (msgNoThread)); +} + +void +log_init (int max_lines) +{ + int i; + + maxDisp = max_lines; + streamOut = stderr; + + // pre-term queue strings + for (i = 0; i < MAX_LOG_ENTRIES; ++i) + queue[i][MAX_LOG_ENTRY_SIZE - 1] = '\0'; + + msgBuf[sizeof (msgBuf) - 1] = '\0'; + msgNoThread[sizeof (msgNoThread) - 1] = '\0'; + + // install exit handlers + atexit (exitCallback); +} + +void +log_initThreads (void) +{ + qmutex = CreateMutex ("Logging Lock", SYNC_CLASS_RESOURCE); + qlock = 1; +} + +int +log_exit (int code) +{ + showBox = false; + + if (qlock) + { + qlock = 0; + DestroyMutex (qmutex); + qmutex = 0; + } + + return code; +} + +void +log_setLevel (int level) +{ + maxLevel = level; + //maxStreamLevel = level; +} + +FILE * +log_setOutput (FILE *out) +{ + FILE *old = streamOut; + streamOut = out; + + return old; +} + +void +log_addV (log_Level level, const char *fmt, va_list list) +{ + log_Entry full_msg; + vsnprintf (full_msg, sizeof (full_msg) - 1, fmt, list); + full_msg[sizeof (full_msg) - 1] = '\0'; + + if ((int)level <= maxStreamLevel) + { + fprintf (streamOut, "%s\n", full_msg); + } + + if ((int)level <= maxLevel) + { + int slot; + + queueNonThreaded (); + + slot = acquireSlot (); + memcpy (queue[slot], full_msg, sizeof (queue[0])); + } +} + +void +log_add (log_Level level, const char *fmt, ...) +{ + va_list list; + + va_start (list, fmt); + log_addV (level, fmt, list); + va_end (list); +} + +// non-threaded version of 'add' +// uses single-instance static storage with entry into the +// queue delayed until the next threaded 'add' or 'exit' +void +log_add_nothreadV (log_Level level, const char *fmt, va_list list) +{ + log_Entry full_msg; + vsnprintf (full_msg, sizeof (full_msg) - 1, fmt, list); + full_msg[sizeof (full_msg) - 1] = '\0'; + + if ((int)level <= maxStreamLevel) + { + fprintf (streamOut, "%s\n", full_msg); + } + + if ((int)level <= maxLevel) + { + memcpy (msgNoThread, full_msg, sizeof (msgNoThread)); + noThreadReady = true; + } +} + +void +log_add_nothread (log_Level level, const char *fmt, ...) +{ + va_list list; + + va_start (list, fmt); + log_add_nothreadV (level, fmt, list); + va_end (list); +} + +void +log_showBox (bool show, bool err) +{ + showBox = show; + errorBox = err; +} + +// sets the maximum log lines captured for the final +// display to the user on failure exit +void +log_captureLines (int num) +{ + if (num > MAX_LOG_ENTRIES) + num = MAX_LOG_ENTRIES; + if (num < 1) + num = 1; + maxDisp = num; + + // remove any extra lines already on queue + lockQueue (); + removeExcess (0); + unlockQueue (); +} + +static void +exitCallback (void) +{ + if (showBox) + displayLog (errorBox); + + log_exit (0); +} + +static void +displayLog (bool isError) +{ + char *p = msgBuf; + int left = sizeof (msgBuf) - 1; + int len; + int ptr; + + if (isError) + { + strcpy (p, "The Ur-Quan Masters encountered a fatal error.\n" + "Part of the log follows:\n\n"); + len = strlen (p); + p += len; + left -= len; + } + + // Glue the log entries together + // Locking is not a good idea at this point and we do not + // really need it -- the worst that can happen is we get + // an extra or an incomplete message + for (ptr = qtail; ptr != qhead && left > 0; + ptr = (ptr + 1) % MAX_LOG_ENTRIES) + { + len = strlen (queue[ptr]) + 1; + if (len > left) + len = left; + memcpy (p, queue[ptr], len); + p[len - 1] = '\n'; + p += len; + left -= len; + } + + // Glue the non-threaded message if present + if (noThreadReady) + { + noThreadReady = false; + len = strlen (msgNoThread); + if (len > left) + len = left; + memcpy (p, msgNoThread, len); + p += len; + left -= len; + } + + *p = '\0'; + + log_displayBox ("The Ur-Quan Masters", isError, msgBuf); +} + diff --git a/src/libs/log/uqmlog.h b/src/libs/log/uqmlog.h new file mode 100644 index 0000000..38db089 --- /dev/null +++ b/src/libs/log/uqmlog.h @@ -0,0 +1,59 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef UQMLOG_H_INCL__ +#define UQMLOG_H_INCL__ + +#include "port.h" +#include "types.h" +#include <stdio.h> +#include <stdarg.h> + +extern void log_init (int max_lines); +extern void log_initThreads (void); +extern int log_exit (int code); + +extern FILE * log_setOutput (FILE *out); + // sets the new output stream and returns the previous one +extern void log_setLevel (int level); +extern void log_showBox (bool show, bool err); +extern void log_captureLines (int num); +#define LOG_CAPTURE_ALL 1000000 // unreasonably big number + +typedef enum +{ + log_Nothing = 0, + log_User, + log_Fatal = log_User, + log_Error, + log_Warning, + log_Info, + log_Debug, + log_All, + +} log_Level; + +extern void log_add (log_Level, const char *fmt, ...) + PRINTF_FUNCTION(2, 3); +extern void log_addV (log_Level, const char *fmt, va_list) + VPRINTF_FUNCTION(2); +extern void log_add_nothread (log_Level, const char *fmt, ...) + PRINTF_FUNCTION(2, 3); +extern void log_add_nothreadV (log_Level, const char *fmt, va_list) + VPRINTF_FUNCTION(2); + + +#endif /* UQMLOG_H_INCL__ */ diff --git a/src/libs/math/Makeinfo b/src/libs/math/Makeinfo new file mode 100644 index 0000000..da10867 --- /dev/null +++ b/src/libs/math/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="random.c random2.c sqrt.c" +uqm_HFILES="mthintrn.h random.h" diff --git a/src/libs/math/mthintrn.h b/src/libs/math/mthintrn.h new file mode 100644 index 0000000..cfda767 --- /dev/null +++ b/src/libs/math/mthintrn.h @@ -0,0 +1,25 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_MATH_MTHINTRN_H_ +#define LIBS_MATH_MTHINTRN_H_ + +#include "libs/mathlib.h" + +#endif /* LIBS_MATH_MTHINTRN_H_ */ + diff --git a/src/libs/math/random.c b/src/libs/math/random.c new file mode 100644 index 0000000..83fcff8 --- /dev/null +++ b/src/libs/math/random.c @@ -0,0 +1,101 @@ +///Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + /**************************************************************************** +* FILE: random.c +* DESC: a library of random number generators for general purpose use. +* +* References: +* "Random Number Generators: Good ones are hard to find" S.K.Park & K.W.Miller +* Communications of the ACM, Vol31 Number 10, October 1988, Pp 1192-1201 +* +* HISTORY: Created 1/23/1989 +* LAST CHANGED: +* +* Copyright (c) 1989, Robert Leyland and Fred Ford +****************************************************************************/ + +/* ----------------------------INCLUDES----------------------------------- */ +#include "mthintrn.h" /* get the externs for error checking */ + +/* ----------------------------DEFINES------------------------------------ */ +/* constants for licongruential random number generator from CACM article + referenced above */ +#define A 16807 /* a relatively prime number -- also M div Q */ +#define M 2147483647L /* 0xFFFFFFFF / 2 */ +#define Q 127773L /* M div A */ +#define R 2836 /* M mod A */ + +/* ----------------------------STATIC DATA-------------------------------- */ + +static DWORD seed = 12345L; /* random number seed */ + +/* ----------------------------CODE--------------------------------------- */ + +/***************************************************************************** +* FUNC: DWORD TFB_Random() +* +* DESC: random number generator +* +* NOTES: +* +* HISTORY: Created By Robert leyland +* +*****************************************************************************/ + +DWORD +TFB_Random (void) +{ + seed = A * (seed % Q) - R * (seed / Q); + if (seed > M) + return (seed -= M); + else if (seed) + return (seed); + else + return (seed = 1L); +} + +/***************************************************************************** +* FUNC: DWORD TFB_SeedRandom(DWORD l) +* +* DESC: set the seed for the random number generator to parameter "l", and +* return the value of the previously active seed, to allow for multiple +* random number streams. +* +* NOTES: if the seed is not valid it will be coerced into a valid range +* +* HISTORY: Created By Robert leyland +* +*****************************************************************************/ + +DWORD +TFB_SeedRandom (DWORD new_seed) +{ + DWORD old_seed; + + /* coerce the seed to be in the range 1..M */ + if (new_seed == 0L) /* 0 becomes 1 */ + new_seed = 1; + else if (new_seed > M) /* and less than M */ + new_seed -= M; + + old_seed = seed; + seed = new_seed; + return (old_seed); +} + diff --git a/src/libs/math/random.h b/src/libs/math/random.h new file mode 100644 index 0000000..0e8d602 --- /dev/null +++ b/src/libs/math/random.h @@ -0,0 +1,56 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/**************************************************************************** +* FILE: random.h +* DESC: definitions and externs for random number generators +* +* HISTORY: Created 6/ 6/1989 +* LAST CHANGED: +* +* Copyright (c) 1989, Robert Leyland and Scott Anderson +****************************************************************************/ + +#ifndef LIBS_MATH_RANDOM_H_ +#define LIBS_MATH_RANDOM_H_ + +/* ----------------------------GLOBALS/EXTERNS---------------------------- */ + +DWORD TFB_SeedRandom (DWORD seed); +DWORD TFB_Random (void); + + +typedef struct RandomContext RandomContext; + +#ifdef RANDOM2_INTERNAL +struct RandomContext { + DWORD seed; +}; +#endif + +RandomContext *RandomContext_New (void); +void RandomContext_Delete (RandomContext *context); +RandomContext *RandomContext_Copy (const RandomContext *source); +DWORD RandomContext_Random (RandomContext *context); +DWORD RandomContext_SeedRandom (RandomContext *context, DWORD new_seed); +DWORD RandomContext_GetSeed (RandomContext *context); + + +#endif /* LIBS_MATH_RANDOM_H_ */ + + diff --git a/src/libs/math/random2.c b/src/libs/math/random2.c new file mode 100644 index 0000000..9d354b4 --- /dev/null +++ b/src/libs/math/random2.c @@ -0,0 +1,89 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +// This file contains variants of the random functions in random.c +// that store the state of the RNG in a context, allowing for multiple +// independant RNGs to be used simultaneously. +// The RNG behavior itself is the same. + +#include "libs/compiler.h" + +#define RANDOM2_INTERNAL +#include "random.h" + +#include "libs/memlib.h" + + +#define A 16807 /* a relatively prime number -- also M div Q */ +#define M 2147483647 /* 0xFFFFFFFF / 2 */ +#define Q 127773 /* M div A */ +#define R 2836 /* M mod A */ + +RandomContext * +RandomContext_New (void) +{ + RandomContext *result = (RandomContext *) HMalloc (sizeof (RandomContext)); + result->seed = 12345; + return result; +} + +void +RandomContext_Delete (RandomContext *context) +{ + HFree ((void *) context); +} + +RandomContext * +RandomContext_Copy (const RandomContext *source) +{ + RandomContext *result = (RandomContext *) HMalloc (sizeof (RandomContext)); + *result = *source; + return result; +} + +DWORD +RandomContext_Random (RandomContext *context) +{ + context->seed = A * (context->seed % Q) - R * (context->seed / Q); + if (context->seed > M) { + context->seed -= M; + } else if (context->seed == 0) + context->seed = 1; + + return context->seed; +} + +DWORD +RandomContext_SeedRandom (RandomContext *context, DWORD new_seed) +{ + DWORD old_seed; + + /* coerce the seed to be in the range 1..M */ + if (new_seed == 0) /* 0 becomes 1 */ + new_seed = 1; + else if (new_seed > M) /* and less than M */ + new_seed -= M; + + old_seed = context->seed; + context->seed = new_seed; + return old_seed; +} + +DWORD +RandomContext_GetSeed (RandomContext *context) +{ + return context->seed; +} diff --git a/src/libs/math/sqrt.c b/src/libs/math/sqrt.c new file mode 100644 index 0000000..1f02cac --- /dev/null +++ b/src/libs/math/sqrt.c @@ -0,0 +1,97 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "mthintrn.h" + +COUNT +square_root (DWORD value) +{ + UWORD sig_word, mask; + COUNT result, shift; + + if ((sig_word = HIWORD (value)) > 0) + { + DWORD mask_squared, result_shift; + + for (mask = 1 << 15, shift = 31; + !(mask & sig_word); mask >>= 1, --shift) + ; + shift >>= 1; + mask = 1 << shift; + + result = mask; + mask_squared = result_shift = (DWORD)mask << shift; + value -= mask_squared; + while (mask >>= 1) + { + DWORD remainder; + + mask_squared >>= 1; + mask_squared >>= 1; + if ((remainder = result_shift + mask_squared) > value) + result_shift >>= 1; + else + { + value -= remainder; + + result_shift = (result_shift >> 1) + mask_squared; + + result |= mask; + } + } + + return (result); + } + else if ((sig_word = LOWORD (value)) > 0) + { + UWORD mask_squared, result_shift; + + for (mask = 1 << 15, shift = 15; + !(mask & sig_word); mask >>= 1, --shift) + ; + shift >>= 1; + mask = 1 << shift; + + result = mask; + mask_squared = result_shift = mask << shift; + sig_word -= mask_squared; + while (mask >>= 1) + { + UWORD remainder; + + mask_squared >>= 1; + mask_squared >>= 1; + if ((remainder = result_shift + mask_squared) > sig_word) + result_shift >>= 1; + else + { + sig_word -= remainder; + + result_shift = (result_shift >> 1) + mask_squared; + + result |= mask; + } + } + + return (result); + } + + return (0); +} + + diff --git a/src/libs/mathlib.h b/src/libs/mathlib.h new file mode 100644 index 0000000..a7b27d1 --- /dev/null +++ b/src/libs/mathlib.h @@ -0,0 +1,36 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_MATHLIB_H_ +#define LIBS_MATHLIB_H_ + +#include "libs/compiler.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +#include "math/random.h" + +extern COUNT square_root (DWORD value); + +#if defined(__cplusplus) +} +#endif + +#endif /* LIBS_MATHLIB_H_ */ diff --git a/src/libs/md5.h b/src/libs/md5.h new file mode 100644 index 0000000..f7732dc --- /dev/null +++ b/src/libs/md5.h @@ -0,0 +1,32 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef LIBS_MD5_H_ +#define LIBS_MD5_H_ + +#if defined(__cplusplus) +extern "C" { +#endif + +#include "md5/md5.h" + +#if defined(__cplusplus) +} +#endif + +#endif /* LIBS_MD5_H_ */ diff --git a/src/libs/md5/Makeinfo b/src/libs/md5/Makeinfo new file mode 100644 index 0000000..7ba0a78 --- /dev/null +++ b/src/libs/md5/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="md5.c" +uqm_HFILES="md5.h" diff --git a/src/libs/md5/README b/src/libs/md5/README new file mode 100644 index 0000000..5ca6c5a --- /dev/null +++ b/src/libs/md5/README @@ -0,0 +1,6 @@ +The files md5.c and md5.h from this directory come from the GNU TLS +library (http://www.gnutls.org/), version 1.6.1. + +These files are unchanged, except for the replacement of the LGPL notices by +GPL notices. + diff --git a/src/libs/md5/md5.c b/src/libs/md5/md5.c new file mode 100644 index 0000000..d57b3df --- /dev/null +++ b/src/libs/md5/md5.c @@ -0,0 +1,452 @@ +/* Functions to compute MD5 message digest of files or memory blocks. + according to the definition of MD5 in RFC 1321 from April 1992. + Copyright (C) 1995,1996,1997,1999,2000,2001,2005,2006 + Free Software Foundation, Inc. + This file is part of the GNU C Library. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* Written by Ulrich Drepper <drepper@gnu.ai.mit.edu>, 1995. */ + +#include <config.h> + +#include "md5.h" + +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> + +#if USE_UNLOCKED_IO +# include "unlocked-io.h" +#endif + +#ifdef _LIBC +# include <endian.h> +# if __BYTE_ORDER == __BIG_ENDIAN +# define WORDS_BIGENDIAN 1 +# endif +/* We need to keep the namespace clean so define the MD5 function + protected using leading __ . */ +# define md5_init_ctx __md5_init_ctx +# define md5_process_block __md5_process_block +# define md5_process_bytes __md5_process_bytes +# define md5_finish_ctx __md5_finish_ctx +# define md5_read_ctx __md5_read_ctx +# define md5_stream __md5_stream +# define md5_buffer __md5_buffer +#endif + +#ifdef WORDS_BIGENDIAN +# define SWAP(n) \ + (((n) << 24) | (((n) & 0xff00) << 8) | (((n) >> 8) & 0xff00) | ((n) >> 24)) +#else +# define SWAP(n) (n) +#endif + +#define BLOCKSIZE 4096 +#if BLOCKSIZE % 64 != 0 +# error "invalid BLOCKSIZE" +#endif + +/* This array contains the bytes used to pad the buffer to the next + 64-byte boundary. (RFC 1321, 3.1: Step 1) */ +static const unsigned char fillbuf[64] = { 0x80, 0 /* , 0, 0, ... */ }; + + +/* Initialize structure containing state of computation. + (RFC 1321, 3.3: Step 3) */ +void +md5_init_ctx (struct md5_ctx *ctx) +{ + ctx->A = 0x67452301; + ctx->B = 0xefcdab89; + ctx->C = 0x98badcfe; + ctx->D = 0x10325476; + + ctx->total[0] = ctx->total[1] = 0; + ctx->buflen = 0; +} + +/* Put result from CTX in first 16 bytes following RESBUF. The result + must be in little endian byte order. + + IMPORTANT: On some systems it is required that RESBUF is correctly + aligned for a 32-bit value. */ +void * +md5_read_ctx (const struct md5_ctx *ctx, void *resbuf) +{ + ((uint32_t *) resbuf)[0] = SWAP (ctx->A); + ((uint32_t *) resbuf)[1] = SWAP (ctx->B); + ((uint32_t *) resbuf)[2] = SWAP (ctx->C); + ((uint32_t *) resbuf)[3] = SWAP (ctx->D); + + return resbuf; +} + +/* Process the remaining bytes in the internal buffer and the usual + prolog according to the standard and write the result to RESBUF. + + IMPORTANT: On some systems it is required that RESBUF is correctly + aligned for a 32-bit value. */ +void * +md5_finish_ctx (struct md5_ctx *ctx, void *resbuf) +{ + /* Take yet unprocessed bytes into account. */ + uint32_t bytes = ctx->buflen; + size_t size = (bytes < 56) ? 64 / 4 : 64 * 2 / 4; + + /* Now count remaining bytes. */ + ctx->total[0] += bytes; + if (ctx->total[0] < bytes) + ++ctx->total[1]; + + /* Put the 64-bit file length in *bits* at the end of the buffer. */ + ctx->buffer[size - 2] = SWAP (ctx->total[0] << 3); + ctx->buffer[size - 1] = SWAP ((ctx->total[1] << 3) | (ctx->total[0] >> 29)); + + memcpy (&((char *) ctx->buffer)[bytes], fillbuf, (size - 2) * 4 - bytes); + + /* Process last bytes. */ + md5_process_block (ctx->buffer, size * 4, ctx); + + return md5_read_ctx (ctx, resbuf); +} + +/* Compute MD5 message digest for bytes read from STREAM. The + resulting message digest number will be written into the 16 bytes + beginning at RESBLOCK. */ +int +md5_stream (FILE *stream, void *resblock) +{ + struct md5_ctx ctx; + char buffer[BLOCKSIZE + 72]; + size_t sum; + + /* Initialize the computation context. */ + md5_init_ctx (&ctx); + + /* Iterate over full file contents. */ + while (1) + { + /* We read the file in blocks of BLOCKSIZE bytes. One call of the + computation function processes the whole buffer so that with the + next round of the loop another block can be read. */ + size_t n; + sum = 0; + + /* Read block. Take care for partial reads. */ + while (1) + { + n = fread (buffer + sum, 1, BLOCKSIZE - sum, stream); + + sum += n; + + if (sum == BLOCKSIZE) + break; + + if (n == 0) + { + /* Check for the error flag IFF N == 0, so that we don't + exit the loop after a partial read due to e.g., EAGAIN + or EWOULDBLOCK. */ + if (ferror (stream)) + return 1; + goto process_partial_block; + } + + /* We've read at least one byte, so ignore errors. But always + check for EOF, since feof may be true even though N > 0. + Otherwise, we could end up calling fread after EOF. */ + if (feof (stream)) + goto process_partial_block; + } + + /* Process buffer with BLOCKSIZE bytes. Note that + BLOCKSIZE % 64 == 0 + */ + md5_process_block (buffer, BLOCKSIZE, &ctx); + } + +process_partial_block: + + /* Process any remaining bytes. */ + if (sum > 0) + md5_process_bytes (buffer, sum, &ctx); + + /* Construct result in desired memory. */ + md5_finish_ctx (&ctx, resblock); + return 0; +} + +/* Compute MD5 message digest for LEN bytes beginning at BUFFER. The + result is always in little endian byte order, so that a byte-wise + output yields to the wanted ASCII representation of the message + digest. */ +void * +md5_buffer (const char *buffer, size_t len, void *resblock) +{ + struct md5_ctx ctx; + + /* Initialize the computation context. */ + md5_init_ctx (&ctx); + + /* Process whole buffer but last len % 64 bytes. */ + md5_process_bytes (buffer, len, &ctx); + + /* Put result in desired memory area. */ + return md5_finish_ctx (&ctx, resblock); +} + + +void +md5_process_bytes (const void *buffer, size_t len, struct md5_ctx *ctx) +{ + /* When we already have some bits in our internal buffer concatenate + both inputs first. */ + if (ctx->buflen != 0) + { + size_t left_over = ctx->buflen; + size_t add = 128 - left_over > len ? len : 128 - left_over; + + memcpy (&((char *) ctx->buffer)[left_over], buffer, add); + ctx->buflen += add; + + if (ctx->buflen > 64) + { + md5_process_block (ctx->buffer, ctx->buflen & ~63, ctx); + + ctx->buflen &= 63; + /* The regions in the following copy operation cannot overlap. */ + memcpy (ctx->buffer, + &((char *) ctx->buffer)[(left_over + add) & ~63], + ctx->buflen); + } + + buffer = (const char *) buffer + add; + len -= add; + } + + /* Process available complete blocks. */ + if (len >= 64) + { +#if !_STRING_ARCH_unaligned +# define alignof(type) offsetof (struct align_ ## type { char c; type x; }, x) +# define UNALIGNED_P(p) (((size_t) p) % alignof (uint32_t) != 0) + if (UNALIGNED_P (buffer)) + while (len > 64) + { + md5_process_block (memcpy (ctx->buffer, buffer, 64), 64, ctx); + buffer = (const char *) buffer + 64; + len -= 64; + } + else +#endif + { + md5_process_block (buffer, len & ~63, ctx); + buffer = (const char *) buffer + (len & ~63); + len &= 63; + } + } + + /* Move remaining bytes in internal buffer. */ + if (len > 0) + { + size_t left_over = ctx->buflen; + + memcpy (&((char *) ctx->buffer)[left_over], buffer, len); + left_over += len; + if (left_over >= 64) + { + md5_process_block (ctx->buffer, 64, ctx); + left_over -= 64; + memcpy (ctx->buffer, &ctx->buffer[16], left_over); + } + ctx->buflen = left_over; + } +} + + +/* These are the four functions used in the four steps of the MD5 algorithm + and defined in the RFC 1321. The first function is a little bit optimized + (as found in Colin Plumbs public domain implementation). */ +/* #define FF(b, c, d) ((b & c) | (~b & d)) */ +#define FF(b, c, d) (d ^ (b & (c ^ d))) +#define FG(b, c, d) FF (d, b, c) +#define FH(b, c, d) (b ^ c ^ d) +#define FI(b, c, d) (c ^ (b | ~d)) + +/* Process LEN bytes of BUFFER, accumulating context into CTX. + It is assumed that LEN % 64 == 0. */ + +void +md5_process_block (const void *buffer, size_t len, struct md5_ctx *ctx) +{ + uint32_t correct_words[16]; + const uint32_t *words = buffer; + size_t nwords = len / sizeof (uint32_t); + const uint32_t *endp = words + nwords; + uint32_t A = ctx->A; + uint32_t B = ctx->B; + uint32_t C = ctx->C; + uint32_t D = ctx->D; + + /* First increment the byte count. RFC 1321 specifies the possible + length of the file up to 2^64 bits. Here we only compute the + number of bytes. Do a double word increment. */ + ctx->total[0] += len; + if (ctx->total[0] < len) + ++ctx->total[1]; + + /* Process all bytes in the buffer with 64 bytes in each round of + the loop. */ + while (words < endp) + { + uint32_t *cwp = correct_words; + uint32_t A_save = A; + uint32_t B_save = B; + uint32_t C_save = C; + uint32_t D_save = D; + + /* First round: using the given function, the context and a constant + the next context is computed. Because the algorithms processing + unit is a 32-bit word and it is determined to work on words in + little endian byte order we perhaps have to change the byte order + before the computation. To reduce the work for the next steps + we store the swapped words in the array CORRECT_WORDS. */ + +#define OP(a, b, c, d, s, T) \ + do \ + { \ + a += FF (b, c, d) + (*cwp++ = SWAP (*words)) + T; \ + ++words; \ + CYCLIC (a, s); \ + a += b; \ + } \ + while (0) + + /* It is unfortunate that C does not provide an operator for + cyclic rotation. Hope the C compiler is smart enough. */ +#define CYCLIC(w, s) (w = (w << s) | (w >> (32 - s))) + + /* Before we start, one word to the strange constants. + They are defined in RFC 1321 as + + T[i] = (int) (4294967296.0 * fabs (sin (i))), i=1..64 + + Here is an equivalent invocation using Perl: + + perl -e 'foreach(1..64){printf "0x%08x\n", int (4294967296 * abs (sin $_))}' + */ + + /* Round 1. */ + OP (A, B, C, D, 7, 0xd76aa478); + OP (D, A, B, C, 12, 0xe8c7b756); + OP (C, D, A, B, 17, 0x242070db); + OP (B, C, D, A, 22, 0xc1bdceee); + OP (A, B, C, D, 7, 0xf57c0faf); + OP (D, A, B, C, 12, 0x4787c62a); + OP (C, D, A, B, 17, 0xa8304613); + OP (B, C, D, A, 22, 0xfd469501); + OP (A, B, C, D, 7, 0x698098d8); + OP (D, A, B, C, 12, 0x8b44f7af); + OP (C, D, A, B, 17, 0xffff5bb1); + OP (B, C, D, A, 22, 0x895cd7be); + OP (A, B, C, D, 7, 0x6b901122); + OP (D, A, B, C, 12, 0xfd987193); + OP (C, D, A, B, 17, 0xa679438e); + OP (B, C, D, A, 22, 0x49b40821); + + /* For the second to fourth round we have the possibly swapped words + in CORRECT_WORDS. Redefine the macro to take an additional first + argument specifying the function to use. */ +#undef OP +#define OP(f, a, b, c, d, k, s, T) \ + do \ + { \ + a += f (b, c, d) + correct_words[k] + T; \ + CYCLIC (a, s); \ + a += b; \ + } \ + while (0) + + /* Round 2. */ + OP (FG, A, B, C, D, 1, 5, 0xf61e2562); + OP (FG, D, A, B, C, 6, 9, 0xc040b340); + OP (FG, C, D, A, B, 11, 14, 0x265e5a51); + OP (FG, B, C, D, A, 0, 20, 0xe9b6c7aa); + OP (FG, A, B, C, D, 5, 5, 0xd62f105d); + OP (FG, D, A, B, C, 10, 9, 0x02441453); + OP (FG, C, D, A, B, 15, 14, 0xd8a1e681); + OP (FG, B, C, D, A, 4, 20, 0xe7d3fbc8); + OP (FG, A, B, C, D, 9, 5, 0x21e1cde6); + OP (FG, D, A, B, C, 14, 9, 0xc33707d6); + OP (FG, C, D, A, B, 3, 14, 0xf4d50d87); + OP (FG, B, C, D, A, 8, 20, 0x455a14ed); + OP (FG, A, B, C, D, 13, 5, 0xa9e3e905); + OP (FG, D, A, B, C, 2, 9, 0xfcefa3f8); + OP (FG, C, D, A, B, 7, 14, 0x676f02d9); + OP (FG, B, C, D, A, 12, 20, 0x8d2a4c8a); + + /* Round 3. */ + OP (FH, A, B, C, D, 5, 4, 0xfffa3942); + OP (FH, D, A, B, C, 8, 11, 0x8771f681); + OP (FH, C, D, A, B, 11, 16, 0x6d9d6122); + OP (FH, B, C, D, A, 14, 23, 0xfde5380c); + OP (FH, A, B, C, D, 1, 4, 0xa4beea44); + OP (FH, D, A, B, C, 4, 11, 0x4bdecfa9); + OP (FH, C, D, A, B, 7, 16, 0xf6bb4b60); + OP (FH, B, C, D, A, 10, 23, 0xbebfbc70); + OP (FH, A, B, C, D, 13, 4, 0x289b7ec6); + OP (FH, D, A, B, C, 0, 11, 0xeaa127fa); + OP (FH, C, D, A, B, 3, 16, 0xd4ef3085); + OP (FH, B, C, D, A, 6, 23, 0x04881d05); + OP (FH, A, B, C, D, 9, 4, 0xd9d4d039); + OP (FH, D, A, B, C, 12, 11, 0xe6db99e5); + OP (FH, C, D, A, B, 15, 16, 0x1fa27cf8); + OP (FH, B, C, D, A, 2, 23, 0xc4ac5665); + + /* Round 4. */ + OP (FI, A, B, C, D, 0, 6, 0xf4292244); + OP (FI, D, A, B, C, 7, 10, 0x432aff97); + OP (FI, C, D, A, B, 14, 15, 0xab9423a7); + OP (FI, B, C, D, A, 5, 21, 0xfc93a039); + OP (FI, A, B, C, D, 12, 6, 0x655b59c3); + OP (FI, D, A, B, C, 3, 10, 0x8f0ccc92); + OP (FI, C, D, A, B, 10, 15, 0xffeff47d); + OP (FI, B, C, D, A, 1, 21, 0x85845dd1); + OP (FI, A, B, C, D, 8, 6, 0x6fa87e4f); + OP (FI, D, A, B, C, 15, 10, 0xfe2ce6e0); + OP (FI, C, D, A, B, 6, 15, 0xa3014314); + OP (FI, B, C, D, A, 13, 21, 0x4e0811a1); + OP (FI, A, B, C, D, 4, 6, 0xf7537e82); + OP (FI, D, A, B, C, 11, 10, 0xbd3af235); + OP (FI, C, D, A, B, 2, 15, 0x2ad7d2bb); + OP (FI, B, C, D, A, 9, 21, 0xeb86d391); + + /* Add the starting values of the context. */ + A += A_save; + B += B_save; + C += C_save; + D += D_save; + } + + /* Put checksum in context given as argument. */ + ctx->A = A; + ctx->B = B; + ctx->C = C; + ctx->D = D; +} diff --git a/src/libs/md5/md5.h b/src/libs/md5/md5.h new file mode 100644 index 0000000..0639525 --- /dev/null +++ b/src/libs/md5/md5.h @@ -0,0 +1,130 @@ +/* Declaration of functions and data types used for MD5 sum computing + library functions. + Copyright (C) 1995-1997,1999,2000,2001,2004,2005,2006 + Free Software Foundation, Inc. + This file is part of the GNU C Library. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef LIBS_MD5_MD5_H_ +#define LIBS_MD5_MD5_H_ 1 + +#include <stdio.h> + +#ifdef _MSC_VER +typedef unsigned int uint32_t; +#else +#include <stdint.h> +#endif + +#define MD5_DIGEST_SIZE 16 +#define MD5_BLOCK_SIZE 64 + +#ifndef __GNUC_PREREQ +# if defined __GNUC__ && defined __GNUC_MINOR__ +# define __GNUC_PREREQ(maj, min) \ + ((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min)) +# else +# define __GNUC_PREREQ(maj, min) 0 +# endif +#endif + +#ifndef __THROW +# if defined __cplusplus && __GNUC_PREREQ (2,8) +# define __THROW throw () +# else +# define __THROW +# endif +#endif + +#ifndef _LIBC +# define __md5_buffer md5_buffer +# define __md5_finish_ctx md5_finish_ctx +# define __md5_init_ctx md5_init_ctx +# define __md5_process_block md5_process_block +# define __md5_process_bytes md5_process_bytes +# define __md5_read_ctx md5_read_ctx +# define __md5_stream md5_stream +#endif + +/* Structure to save state of computation between the single steps. */ +struct md5_ctx +{ + uint32_t A; + uint32_t B; + uint32_t C; + uint32_t D; + + uint32_t total[2]; + uint32_t buflen; + uint32_t buffer[32]; +}; + +/* + * The following three functions are build up the low level used in + * the functions `md5_stream' and `md5_buffer'. + */ + +/* Initialize structure containing state of computation. + (RFC 1321, 3.3: Step 3) */ +extern void __md5_init_ctx (struct md5_ctx *ctx) __THROW; + +/* Starting with the result of former calls of this function (or the + initialization function update the context for the next LEN bytes + starting at BUFFER. + It is necessary that LEN is a multiple of 64!!! */ +extern void __md5_process_block (const void *buffer, size_t len, + struct md5_ctx *ctx) __THROW; + +/* Starting with the result of former calls of this function (or the + initialization function update the context for the next LEN bytes + starting at BUFFER. + It is NOT required that LEN is a multiple of 64. */ +extern void __md5_process_bytes (const void *buffer, size_t len, + struct md5_ctx *ctx) __THROW; + +/* Process the remaining bytes in the buffer and put result from CTX + in first 16 bytes following RESBUF. The result is always in little + endian byte order, so that a byte-wise output yields to the wanted + ASCII representation of the message digest. + + IMPORTANT: On some systems, RESBUF must be aligned to a 32-bit + boundary. */ +extern void *__md5_finish_ctx (struct md5_ctx *ctx, void *resbuf) __THROW; + + +/* Put result from CTX in first 16 bytes following RESBUF. The result is + always in little endian byte order, so that a byte-wise output yields + to the wanted ASCII representation of the message digest. + + IMPORTANT: On some systems, RESBUF must be aligned to a 32-bit + boundary. */ +extern void *__md5_read_ctx (const struct md5_ctx *ctx, void *resbuf) __THROW; + + +/* Compute MD5 message digest for bytes read from STREAM. The + resulting message digest number will be written into the 16 bytes + beginning at RESBLOCK. */ +extern int __md5_stream (FILE *stream, void *resblock) __THROW; + +/* Compute MD5 message digest for LEN bytes beginning at BUFFER. The + result is always in little endian byte order, so that a byte-wise + output yields to the wanted ASCII representation of the message + digest. */ +extern void *__md5_buffer (const char *buffer, size_t len, + void *resblock) __THROW; + +#endif /* md5.h */ diff --git a/src/libs/memlib.h b/src/libs/memlib.h new file mode 100644 index 0000000..c4fa385 --- /dev/null +++ b/src/libs/memlib.h @@ -0,0 +1,43 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_MEMLIB_H_ +#define LIBS_MEMLIB_H_ + +#include <stddef.h> + +#include "types.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +extern bool mem_init (void); +extern bool mem_uninit (void); + +extern void *HMalloc (size_t size); +extern void HFree (void *p); +extern void *HCalloc (size_t size); +extern void *HRealloc (void *p, size_t size); + +#if defined(__cplusplus) +} +#endif + +#endif /* LIBS_MEMLIB_H_ */ + diff --git a/src/libs/memory/Makeinfo b/src/libs/memory/Makeinfo new file mode 100644 index 0000000..74dfc29 --- /dev/null +++ b/src/libs/memory/Makeinfo @@ -0,0 +1 @@ +uqm_CFILES="w_memlib.c" diff --git a/src/libs/memory/w_memlib.c b/src/libs/memory/w_memlib.c new file mode 100644 index 0000000..342e34e --- /dev/null +++ b/src/libs/memory/w_memlib.c @@ -0,0 +1,84 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include "libs/memlib.h" +#include "libs/log.h" +#include "libs/misc.h" + + +bool +mem_init (void) +{ // This is a stub + return true; +} + +bool +mem_uninit (void) +{ // This is a stub + return true; +} + +void * +HMalloc (size_t size) +{ + void *p = malloc (size); + if (p == NULL && size > 0) + { + log_add (log_Fatal, "HMalloc() FATAL: out of memory."); + fflush (stderr); + explode (); + } + + return p; +} + +void +HFree (void *p) +{ + free (p); +} + +void * +HCalloc (size_t size) +{ + void *p; + + p = HMalloc (size); + memset (p, 0, size); + + return p; +} + +void * +HRealloc (void *p, size_t size) +{ + p = realloc (p, size); + if (p == NULL && size > 0) + { + log_add (log_Fatal, "HRealloc() FATAL: out of memory."); + fflush (stderr); + explode (); + } + + return p; +} + diff --git a/src/libs/mikmod/AUTHORS b/src/libs/mikmod/AUTHORS new file mode 100644 index 0000000..5752fdf --- /dev/null +++ b/src/libs/mikmod/AUTHORS @@ -0,0 +1,124 @@ +libmikmod main authors +---------------------- + +* Jean-Paul Mikkers (MikMak) <mikmak@via.nl> + wrote MikMod and maintained it until version 3. +* Jake Stine (Air Richter) <dracoirs@epix.net> + [email doesn't work anymore...] + made decisive contributions to the code (esp. IT support) and + maintained MikMod version 3 until it was discontinued. He still works + on the WinAmp module plugin, roughly based on MikMod. +* Miod Vallat <miod@mikmod.org> + current overbooked libmikmod maintainer (since version 3.0.4), made + an audit of the code resulting in many bugs fixed. + +Previous Unix maintainers +------------------------- + +* Steve McIntyre <steven@chiark.greenend.org.uk> + maintained MikMod'Unix version 2. Used to maintain the Debian package + for MikMod. +* Peter Amstutz <tetron@student.umass.edu> + maintained MikMod'Unix version 3 up to version 3.0.3. + +General contributors +-------------------- + +* Arne de Bruijn <arne@knoware.nl> + wrote the compressed IT sample support. +* Shlomi Fish <shlomif@vipe.technion.ac.il> + wrote the Java port, bug fixes. +* Juan Linietsky <coding@reduz.com.ar> + overall bug fixes. +* Claudio Matsuoka <claudio@helllabs.org> + wrote the STX loader and submitted bug fixes. +* Sebastiaan A. Megens <samegens@xs4all.nl> + fixed various bugs (memory leaks, endianness issues, etc). +* ``UFO'' <ufo303@poczta.onet.pl> + wrote the OKT loader. +* Kev Vance <kvance@zeux.org> + wrote the GDM loader. + +* Paul Fisher made decisive contributions and improvements. +* Alexander Kerkhove fixed an ULT panning effect bug. +* ``Kodiak'' helped on the interfaces of libmikmod. +* Sylvain Marchand make MikMod more portable and GCC compilable. + + +Contributors on the Unix side +----------------------------- + +* Douglas Carmichael <dcarmich@mcs.com> + ported MikMod to FreeBSD. +* Chris Conn <cconn@tohs.abacom.com> + wrote the OSS driver. +* Roine Gustaffson <e93_rog@e.kth.se> + wrote the Digital AudioFile driver. +* Stephan Kanthak <kanthak@informatik.rwth-aachen.de> + wrote the SGI driver. +* Lutz Vieweg <lkv@mania.robin.de> + wrote the AIX and HP-UX drivers. +* Valtteri Vuorikoski <vuori@sci.fi> + wrote the Sun driver. +* Andy Lo A Foe <andy@alsa-project.org> + wrote the Ultra driver (for the Gravis Ultrasound sound card). +* C Ray C <crayc@pyro.net> + updated the Ultra driver to work with libmikmod 3. +* ``MenTaLguY'' <mental@kludge.org> + autoconfized the Unix libmikmod distribution. +* Tobias Gloth <gloth@geomagic.com> + created the new I/O interface, made the code MT-safe and submitted bug fixes. +* Simon Hosie <gumboot@clear.net.nz> + wrote the piped output driver, and submitted speed optimizations and bugfixes + for the software mixer. +* Gerd Rausch <gerd@alf.gun.de> + wrote the sam9407 driver. +* Joseph Carter <knghtbrd@debian.org> + maintains the Debian package for MikMod and libmikmod, submitted + bugfixes. + +Contributors on the Windows side +-------------------------------- + +* Brian McKinney <Brian.McKinney@colorado.edu> + created the DirectSound driver. +* Bjornar Henden <bhenden@online.no> + created the Multimedia API windows driver. + +Contributors on the Dos side +---------------------------- + +Their code isn't there anymore, but they contributed to the success of +libmikmod... + +* Jean-Philippe Ajirent wrote the EMS memory routines. +* Peter Breitling ported MikMod to DJGPP. +* Arnout Cosman wrote the PAS driver. +* Mario Koeppen wrote the WSS driver. +* Mike Leibow wrote the GUS driver. +* Jeremy McDonald wrote a fast assembly-language mixer. +* Steffen Rusitschka and Vince Vu wrote the AWE driver. + +Contributors on the Macintosh side +---------------------------------- + +* Anders Bjoerklund <afb@algonet.se> + ported libmikmod to the Macintosh. + +Contributors on the OS/2 side +----------------------------- + +* Stefan Tibus <Stefan_Tibus@ThePentagon.com> + ported libmikmod to OS/2. +* Andrew Zabolotny <bit@eltech.ru> + improved the existing OS/2 drivers. + +Contributors on the BeOS side +----------------------------- + +* Thomas Neumann <tneumann@polycode.dk> + integrated libmikmod into his BeOS APlayer, and contributed many bug fixes. + +-- +If your name is missing, don't hesitate to remind me at +<miod@mikmod.org> diff --git a/src/libs/mikmod/Makeinfo b/src/libs/mikmod/Makeinfo new file mode 100644 index 0000000..cd037f9 --- /dev/null +++ b/src/libs/mikmod/Makeinfo @@ -0,0 +1,5 @@ +uqm_CFILES="drv_nos.c load_it.c load_mod.c load_s3m.c load_stm.c load_xm.c + mdreg.c mdriver.c mloader.c + mlreg.c mlutil.c mmalloc.c mmerror.c mmio.c mplayer.c munitrk.c + mwav.c npertab.c sloader.c virtch.c virtch2.c virtch_common.c" +uqm_HFILES="mikmod_build.h mikmod.h mikmod_internals.h" diff --git a/src/libs/mikmod/README b/src/libs/mikmod/README new file mode 100644 index 0000000..c5c0be2 --- /dev/null +++ b/src/libs/mikmod/README @@ -0,0 +1,5 @@ +NOTE by UQM developers: this is a modified version of libmikmod. + +This version of the library is based on the official libmikmod library +version 3.1.11a with some internal and API changes backported from v3.2.2. +The official library is found at http://sourceforge.net/projects/mikmod. diff --git a/src/libs/mikmod/drv_nos.c b/src/libs/mikmod/drv_nos.c new file mode 100644 index 0000000..0ea3d1f --- /dev/null +++ b/src/libs/mikmod/drv_nos.c @@ -0,0 +1,107 @@ +/* MikMod sound library + (c) 1998, 1999, 2000 Miodrag Vallat and others - see file AUTHORS for + complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + Driver for no output + +==============================================================================*/ + +/* + + Written by Jean-Paul Mikkers <mikmak@via.nl> + +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "mikmod_internals.h" + +#define ZEROLEN 32768 + +static SBYTE *zerobuf=NULL; + +static BOOL NS_IsThere(void) +{ + return 1; +} + +static BOOL NS_Init(void) +{ + zerobuf=(SBYTE*)MikMod_malloc(ZEROLEN); + return VC_Init(); +} + +static void NS_Exit(void) +{ + VC_Exit(); + MikMod_free(zerobuf); +} + +static void NS_Update(void) +{ + if (zerobuf) + VC_WriteBytes(zerobuf,ZEROLEN); +} + +MIKMODAPI MDRIVER drv_nos={ + NULL, + "No Sound", + "Nosound Driver v3.0", + 255,255, + "nosound", + + NULL, + NS_IsThere, + VC_SampleLoad, + VC_SampleUnload, + VC_SampleSpace, + VC_SampleLength, + NS_Init, + NS_Exit, + NULL, + VC_SetNumVoices, + VC_PlayStart, + VC_PlayStop, + NS_Update, + NULL, + VC_VoiceSetVolume, + VC_VoiceGetVolume, + VC_VoiceSetFrequency, + VC_VoiceGetFrequency, + VC_VoiceSetPanning, + VC_VoiceGetPanning, + VC_VoicePlay, + VC_VoiceStop, + VC_VoiceStopped, + VC_VoiceGetPosition, + VC_VoiceRealVolume +}; + + +/* ex:set ts=4: */ diff --git a/src/libs/mikmod/load_it.c b/src/libs/mikmod/load_it.c new file mode 100644 index 0000000..747f23a --- /dev/null +++ b/src/libs/mikmod/load_it.c @@ -0,0 +1,1008 @@ +/* MikMod sound library + (c) 1998, 1999, 2000, 2001, 2002 Miodrag Vallat and others - see file + AUTHORS for complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + Impulse tracker (IT) module loader + +==============================================================================*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include <ctype.h> +#include <stdio.h> +#ifdef HAVE_MEMORY_H +#include <memory.h> +#endif +#include <string.h> + +#include "mikmod_internals.h" + +#ifdef SUNOS +extern int fprintf(FILE *, const char *, ...); +extern int toupper(int); +#endif + +/*========== Module structure */ + +/* header */ +typedef struct ITHEADER { + CHAR songname[26]; + UBYTE blank01[2]; + UWORD ordnum; + UWORD insnum; + UWORD smpnum; + UWORD patnum; + UWORD cwt; /* Created with tracker (y.xx = 0x0yxx) */ + UWORD cmwt; /* Compatible with tracker ver > than val. */ + UWORD flags; + UWORD special; /* bit 0 set = song message attached */ + UBYTE globvol; + UBYTE mixvol; /* mixing volume [ignored] */ + UBYTE initspeed; + UBYTE inittempo; + UBYTE pansep; /* panning separation between channels */ + UBYTE zerobyte; + UWORD msglength; + ULONG msgoffset; + UBYTE blank02[4]; + UBYTE pantable[64]; + UBYTE voltable[64]; +} ITHEADER; + +/* sample information */ +typedef struct ITSAMPLE { + CHAR filename[12]; + UBYTE zerobyte; + UBYTE globvol; + UBYTE flag; + UBYTE volume; + UBYTE panning; + CHAR sampname[28]; + UWORD convert; /* sample conversion flag */ + ULONG length; + ULONG loopbeg; + ULONG loopend; + ULONG c5spd; + ULONG susbegin; + ULONG susend; + ULONG sampoffset; + UBYTE vibspeed; + UBYTE vibdepth; + UBYTE vibrate; + UBYTE vibwave; /* 0=sine, 1=rampdown, 2=square, 3=random (speed ignored) */ +} ITSAMPLE; + +/* instrument information */ + +#define ITENVCNT 25 +#define ITNOTECNT 120 +typedef struct ITINSTHEADER { + ULONG size; /* (dword) Instrument size */ + CHAR filename[12]; /* (char) Instrument filename */ + UBYTE zerobyte; /* (byte) Instrument type (always 0) */ + UBYTE volflg; + UBYTE volpts; + UBYTE volbeg; /* (byte) Volume loop start (node) */ + UBYTE volend; /* (byte) Volume loop end (node) */ + UBYTE volsusbeg; /* (byte) Volume sustain begin (node) */ + UBYTE volsusend; /* (byte) Volume Sustain end (node) */ + UBYTE panflg; + UBYTE panpts; + UBYTE panbeg; /* (byte) channel loop start (node) */ + UBYTE panend; /* (byte) channel loop end (node) */ + UBYTE pansusbeg; /* (byte) channel sustain begin (node) */ + UBYTE pansusend; /* (byte) channel Sustain end (node) */ + UBYTE pitflg; + UBYTE pitpts; + UBYTE pitbeg; /* (byte) pitch loop start (node) */ + UBYTE pitend; /* (byte) pitch loop end (node) */ + UBYTE pitsusbeg; /* (byte) pitch sustain begin (node) */ + UBYTE pitsusend; /* (byte) pitch Sustain end (node) */ + UWORD blank; + UBYTE globvol; + UBYTE chanpan; + UWORD fadeout; /* Envelope end / NNA volume fadeout */ + UBYTE dnc; /* Duplicate note check */ + UBYTE dca; /* Duplicate check action */ + UBYTE dct; /* Duplicate check type */ + UBYTE nna; /* New Note Action [0,1,2,3] */ + UWORD trkvers; /* tracker version used to save [files only] */ + UBYTE ppsep; /* Pitch-pan Separation */ + UBYTE ppcenter; /* Pitch-pan Center */ + UBYTE rvolvar; /* random volume varations */ + UBYTE rpanvar; /* random panning varations */ + UWORD numsmp; /* Number of samples in instrument [files only] */ + CHAR name[26]; /* Instrument name */ + UBYTE blank01[6]; + UWORD samptable[ITNOTECNT];/* sample for each note [note / samp pairs] */ + UBYTE volenv[200]; /* volume envelope (IT 1.x stuff) */ + UBYTE oldvoltick[ITENVCNT];/* volume tick position (IT 1.x stuff) */ + UBYTE volnode[ITENVCNT]; /* amplitude of volume nodes */ + UWORD voltick[ITENVCNT]; /* tick value of volume nodes */ + SBYTE pannode[ITENVCNT]; /* panenv - node points */ + UWORD pantick[ITENVCNT]; /* tick value of panning nodes */ + SBYTE pitnode[ITENVCNT]; /* pitchenv - node points */ + UWORD pittick[ITENVCNT]; /* tick value of pitch nodes */ +} ITINSTHEADER; + +/* unpacked note */ + +typedef struct ITNOTE { + UBYTE note,ins,volpan,cmd,inf; +} ITNOTE; + +/*========== Loader data */ + +static ULONG *paraptr=NULL; /* parapointer array (see IT docs) */ +static ITHEADER *mh=NULL; +static ITNOTE *itpat=NULL; /* allocate to space for one full pattern */ +static UBYTE *mask=NULL; /* arrays allocated to 64 elements and used for */ +static ITNOTE *last=NULL; /* uncompressing IT's pattern information */ +static int numtrk=0; +static unsigned int old_effect; /* if set, use S3M old-effects stuffs */ + +static CHAR* IT_Version[]={ + "ImpulseTracker . ", + "Compressed ImpulseTracker . ", + "ImpulseTracker 2.14p3", + "Compressed ImpulseTracker 2.14p3", + "ImpulseTracker 2.14p4", + "Compressed ImpulseTracker 2.14p4", +}; + +/* table for porta-to-note command within volume/panning column */ +static UBYTE portatable[10]= {0,1,4,8,16,32,64,96,128,255}; + +/*========== Loader code */ + +BOOL IT_Test(void) +{ + UBYTE id[4]; + + if(!_mm_read_UBYTES(id,4,modreader)) return 0; + if(!memcmp(id,"IMPM",4)) return 1; + return 0; +} + +BOOL IT_Init(void) +{ + if(!(mh=(ITHEADER*)MikMod_malloc(sizeof(ITHEADER)))) return 0; + if(!(poslookup=(UBYTE*)MikMod_malloc(256*sizeof(UBYTE)))) return 0; + if(!(itpat=(ITNOTE*)MikMod_malloc(200*64*sizeof(ITNOTE)))) return 0; + if(!(mask=(UBYTE*)MikMod_malloc(64*sizeof(UBYTE)))) return 0; + if(!(last=(ITNOTE*)MikMod_malloc(64*sizeof(ITNOTE)))) return 0; + + return 1; +} + +void IT_Cleanup(void) +{ + FreeLinear(); + + MikMod_free(mh); + MikMod_free(poslookup); + MikMod_free(itpat); + MikMod_free(mask); + MikMod_free(last); + MikMod_free(paraptr); + MikMod_free(origpositions); +} + +/* Because so many IT files have 64 channels as the set number used, but really + only use far less (usually from 8 to 24 still), I had to make this function, + which determines the number of channels that are actually USED by a pattern. + + NOTE: You must first seek to the file location of the pattern before calling + this procedure. + + Returns 1 on error +*/ +static BOOL IT_GetNumChannels(UWORD patrows) +{ + int row=0,flag,ch; + + do { + if((flag=_mm_read_UBYTE(modreader))==EOF) { + _mm_errno=MMERR_LOADING_PATTERN; + return 1; + } + if(!flag) + row++; + else { + ch=(flag-1)&63; + remap[ch]=0; + if(flag & 128) mask[ch]=_mm_read_UBYTE(modreader); + if(mask[ch]&1) _mm_read_UBYTE(modreader); + if(mask[ch]&2) _mm_read_UBYTE(modreader); + if(mask[ch]&4) _mm_read_UBYTE(modreader); + if(mask[ch]&8) { _mm_read_UBYTE(modreader);_mm_read_UBYTE(modreader); } + } + } while(row<patrows); + + return 0; +} + +static UBYTE* IT_ConvertTrack(ITNOTE* tr,UWORD numrows) +{ + int t; + UBYTE note,ins,volpan; + + UniReset(); + + for(t=0;t<numrows;t++) { + note=tr[t*of.numchn].note; + ins=tr[t*of.numchn].ins; + volpan=tr[t*of.numchn].volpan; + + if(note!=255) { + if(note==253) + UniWriteByte(UNI_KEYOFF); + else if(note==254) { + UniPTEffect(0xc,-1); /* note cut command */ + volpan=255; + } else + UniNote(note); + } + + if((ins)&&(ins<100)) + UniInstrument(ins-1); + else if(ins==253) + UniWriteByte(UNI_KEYOFF); + else if(ins!=255) { /* crap */ + _mm_errno=MMERR_LOADING_PATTERN; + return NULL; + } + + /* process volume / panning column + volume / panning effects do NOT all share the same memory address + yet. */ + if(volpan<=64) + UniVolEffect(VOL_VOLUME,volpan); + else if(volpan==65) /* fine volume slide up (65-74) - A0 case */ + UniVolEffect(VOL_VOLSLIDE,0); + else if(volpan<=74) { /* fine volume slide up (65-74) - general case */ + UniVolEffect(VOL_VOLSLIDE,0x0f+((volpan-65)<<4)); + } else if(volpan==75) /* fine volume slide down (75-84) - B0 case */ + UniVolEffect(VOL_VOLSLIDE,0); + else if(volpan<=84) { /* fine volume slide down (75-84) - general case*/ + UniVolEffect(VOL_VOLSLIDE,0xf0+(volpan-75)); + } else if(volpan<=94) /* volume slide up (85-94) */ + UniVolEffect(VOL_VOLSLIDE,((volpan-85)<<4)); + else if(volpan<=104)/* volume slide down (95-104) */ + UniVolEffect(VOL_VOLSLIDE,(volpan-95)); + else if(volpan<=114)/* pitch slide down (105-114) */ + UniVolEffect(VOL_PITCHSLIDEDN,(volpan-105)); + else if(volpan<=124)/* pitch slide up (115-124) */ + UniVolEffect(VOL_PITCHSLIDEUP,(volpan-115)); + else if(volpan<=127) { /* crap */ + _mm_errno=MMERR_LOADING_PATTERN; + return NULL; + } else if(volpan<=192) + UniVolEffect(VOL_PANNING,((volpan-128)==64)?255:((volpan-128)<<2)); + else if(volpan<=202)/* portamento to note */ + UniVolEffect(VOL_PORTAMENTO,portatable[volpan-193]); + else if(volpan<=212)/* vibrato */ + UniVolEffect(VOL_VIBRATO,(volpan-203)); + else if((volpan!=239)&&(volpan!=255)) { /* crap */ + _mm_errno=MMERR_LOADING_PATTERN; + return NULL; + } + + S3MIT_ProcessCmd(tr[t*of.numchn].cmd,tr[t*of.numchn].inf, + old_effect|S3MIT_IT); + + UniNewline(); + } + return UniDup(); +} + +static BOOL IT_ReadPattern(UWORD patrows) +{ + int row=0,flag,ch,blah; + ITNOTE *itt=itpat,dummy,*n,*l; + + memset(itt,255,200*64*sizeof(ITNOTE)); + + do { + if((flag=_mm_read_UBYTE(modreader))==EOF) { + _mm_errno = MMERR_LOADING_PATTERN; + return 0; + } + if(!flag) { + itt=&itt[of.numchn]; + row++; + } else { + ch=remap[(flag-1)&63]; + if(ch!=-1) { + n=&itt[ch]; + l=&last[ch]; + } else + n=l=&dummy; + + if(flag&128) mask[ch]=_mm_read_UBYTE(modreader); + if(mask[ch]&1) + /* convert IT note off to internal note off */ + if((l->note=n->note=_mm_read_UBYTE(modreader))==255) + l->note=n->note=253; + if(mask[ch]&2) + l->ins=n->ins=_mm_read_UBYTE(modreader); + if(mask[ch]&4) + l->volpan=n->volpan=_mm_read_UBYTE(modreader); + if(mask[ch]&8) { + l->cmd=n->cmd=_mm_read_UBYTE(modreader); + l->inf=n->inf=_mm_read_UBYTE(modreader); + } + if(mask[ch]&16) + n->note=l->note; + if(mask[ch]&32) + n->ins=l->ins; + if(mask[ch]&64) + n->volpan=l->volpan; + if(mask[ch]&128) { + n->cmd=l->cmd; + n->inf=l->inf; + } + } + } while(row<patrows); + + for(blah=0;blah<of.numchn;blah++) { + if(!(of.tracks[numtrk++]=IT_ConvertTrack(&itpat[blah],patrows))) + return 0; + } + + return 1; +} + +static void LoadMidiString(MREADER* modreader,CHAR* dest) +{ + CHAR *cur,*last; + + _mm_read_UBYTES(dest,32,modreader); + cur=last=dest; + /* remove blanks and uppercase all */ + while(*last) { + if(isalnum((int)*last)) *(cur++)=toupper((int)*last); + last++; + } + *cur=0; +} + +/* Load embedded midi information for resonant filters */ +static void IT_LoadMidiConfiguration(MREADER* modreader) +{ + int i; + + memset(filtermacros,0,sizeof(filtermacros)); + memset(filtersettings,0,sizeof(filtersettings)); + + if (modreader) { /* information is embedded in file */ + UWORD dat; + CHAR midiline[33]; + + dat=_mm_read_I_UWORD(modreader); + _mm_fseek(modreader,8*dat+0x120,SEEK_CUR); + + /* read midi macros */ + for(i=0;i<UF_MAXMACRO;i++) { + LoadMidiString(modreader,midiline); + if((!strncmp(midiline,"F0F00",5))&& + ((midiline[5]=='0')||(midiline[5]=='1'))) + filtermacros[i]=(midiline[5]-'0')|0x80; + } + + /* read standalone filters */ + for(i=0x80;i<0x100;i++) { + LoadMidiString(modreader,midiline); + if((!strncmp(midiline,"F0F00",5))&& + ((midiline[5]=='0')||(midiline[5]=='1'))) { + filtersettings[i].filter=(midiline[5]-'0')|0x80; + dat=(midiline[6])?(midiline[6]-'0'):0; + if(midiline[7])dat=(dat<<4)|(midiline[7]-'0'); + filtersettings[i].inf=dat; + } + } + } else { /* use default information */ + filtermacros[0]=FILT_CUT; + for(i=0x80;i<0x90;i++) { + filtersettings[i].filter=FILT_RESONANT; + filtersettings[i].inf=(i&0x7f)<<3; + } + } + activemacro=0; + for(i=0;i<0x80;i++) { + filtersettings[i].filter=filtermacros[0]; + filtersettings[i].inf=i; + } +} + +BOOL IT_Load(BOOL curious) +{ + int t,u,lp; + INSTRUMENT *d; + SAMPLE *q; + BOOL compressed=0; + + numtrk=0; + filters=0; + + /* try to read module header */ + _mm_read_I_ULONG(modreader); /* kill the 4 byte header */ + _mm_read_string(mh->songname,26,modreader); + _mm_read_UBYTES(mh->blank01,2,modreader); + mh->ordnum =_mm_read_I_UWORD(modreader); + mh->insnum =_mm_read_I_UWORD(modreader); + mh->smpnum =_mm_read_I_UWORD(modreader); + mh->patnum =_mm_read_I_UWORD(modreader); + mh->cwt =_mm_read_I_UWORD(modreader); + mh->cmwt =_mm_read_I_UWORD(modreader); + mh->flags =_mm_read_I_UWORD(modreader); + mh->special =_mm_read_I_UWORD(modreader); + mh->globvol =_mm_read_UBYTE(modreader); + mh->mixvol =_mm_read_UBYTE(modreader); + mh->initspeed =_mm_read_UBYTE(modreader); + mh->inittempo =_mm_read_UBYTE(modreader); + mh->pansep =_mm_read_UBYTE(modreader); + mh->zerobyte =_mm_read_UBYTE(modreader); + mh->msglength =_mm_read_I_UWORD(modreader); + mh->msgoffset =_mm_read_I_ULONG(modreader); + _mm_read_UBYTES(mh->blank02,4,modreader); + _mm_read_UBYTES(mh->pantable,64,modreader); + _mm_read_UBYTES(mh->voltable,64,modreader); + + if(_mm_eof(modreader)) { + _mm_errno=MMERR_LOADING_HEADER; + return 0; + } + + /* set module variables */ + of.songname = DupStr(mh->songname,26,0); /* make a cstr of songname */ + of.reppos = 0; + of.numpat = mh->patnum; + of.numins = mh->insnum; + of.numsmp = mh->smpnum; + of.initspeed = mh->initspeed; + of.inittempo = mh->inittempo; + of.initvolume = mh->globvol; + of.flags |= UF_BGSLIDES | UF_ARPMEM; + if (!(mh->flags & 1)) + of.flags |= UF_PANNING; + of.bpmlimit=32; + + if(mh->songname[25]) { + of.numvoices=1+mh->songname[25]; +#ifdef MIKMOD_DEBUG + fprintf(stderr,"Embedded IT limitation to %d voices\n",of.numvoices); +#endif + } + + /* set the module type */ + /* 2.17 : IT 2.14p4 */ + /* 2.16 : IT 2.14p3 with resonant filters */ + /* 2.15 : IT 2.14p3 (improved compression) */ + if((mh->cwt<=0x219)&&(mh->cwt>=0x217)) + of.modtype=strdup(IT_Version[mh->cmwt<0x214?4:5]); + else if (mh->cwt>=0x215) + of.modtype=strdup(IT_Version[mh->cmwt<0x214?2:3]); + else { + of.modtype = strdup(IT_Version[mh->cmwt<0x214?0:1]); + of.modtype[mh->cmwt<0x214?15:26] = (mh->cwt>>8)+'0'; + of.modtype[mh->cmwt<0x214?17:28] = ((mh->cwt>>4)&0xf)+'0'; + of.modtype[mh->cmwt<0x214?18:29] = ((mh->cwt)&0xf)+'0'; + } + + if(mh->flags&8) + of.flags |= UF_XMPERIODS | UF_LINEAR; + + if((mh->cwt>=0x106)&&(mh->flags&16)) + old_effect=S3MIT_OLDSTYLE; + else + old_effect=0; + + /* set panning positions */ + if (mh->flags & 1) + for(t=0;t<64;t++) { + mh->pantable[t]&=0x7f; + if(mh->pantable[t]<64) + of.panning[t]=mh->pantable[t]<<2; + else if(mh->pantable[t]==64) + of.panning[t]=255; + else if(mh->pantable[t]==100) + of.panning[t]=PAN_SURROUND; + else if(mh->pantable[t]==127) + of.panning[t]=PAN_CENTER; + else { + _mm_errno=MMERR_LOADING_HEADER; + return 0; + } + } + else + for(t=0;t<64;t++) + of.panning[t]=PAN_CENTER; + + /* set channel volumes */ + memcpy(of.chanvol,mh->voltable,64); + + /* read the order data */ + if(!AllocPositions(mh->ordnum)) return 0; + if(!(origpositions=MikMod_calloc(mh->ordnum,sizeof(UWORD)))) return 0; + + for(t=0;t<mh->ordnum;t++) { + origpositions[t]=_mm_read_UBYTE(modreader); + if((origpositions[t]>mh->patnum)&&(origpositions[t]<254)) + origpositions[t]=255; + } + + if(_mm_eof(modreader)) { + _mm_errno = MMERR_LOADING_HEADER; + return 0; + } + + poslookupcnt=mh->ordnum; + S3MIT_CreateOrders(curious); + + if(!(paraptr=(ULONG*)MikMod_malloc((mh->insnum+mh->smpnum+of.numpat)* + sizeof(ULONG)))) return 0; + + /* read the instrument, sample, and pattern parapointers */ + _mm_read_I_ULONGS(paraptr,mh->insnum+mh->smpnum+of.numpat,modreader); + + if(_mm_eof(modreader)) { + _mm_errno = MMERR_LOADING_HEADER; + return 0; + } + + /* Check for and load midi information for resonant filters */ + if(mh->cmwt>=0x216) { + if(mh->special&8) { + IT_LoadMidiConfiguration(modreader); + if(_mm_eof(modreader)) { + _mm_errno = MMERR_LOADING_HEADER; + return 0; + } + } else + IT_LoadMidiConfiguration(NULL); + filters=1; + } + + /* Check for and load song comment */ + if((mh->special&1)&&(mh->cwt>=0x104)&&(mh->msglength)) { + _mm_fseek(modreader,(long)(mh->msgoffset),SEEK_SET); + if(!ReadComment(mh->msglength)) return 0; + } + + if(!(mh->flags&4)) of.numins=of.numsmp; + if(!AllocSamples()) return 0; + + if(!AllocLinear()) return 0; + + /* Load all samples */ + q = of.samples; + for(t=0;t<mh->smpnum;t++) { + ITSAMPLE s; + + /* seek to sample position */ + _mm_fseek(modreader,(long)(paraptr[mh->insnum+t]+4),SEEK_SET); + + /* load sample info */ + _mm_read_string(s.filename,12,modreader); + s.zerobyte = _mm_read_UBYTE(modreader); + s.globvol = _mm_read_UBYTE(modreader); + s.flag = _mm_read_UBYTE(modreader); + s.volume = _mm_read_UBYTE(modreader); + _mm_read_string(s.sampname,26,modreader); + s.convert = _mm_read_UBYTE(modreader); + s.panning = _mm_read_UBYTE(modreader); + s.length = _mm_read_I_ULONG(modreader); + s.loopbeg = _mm_read_I_ULONG(modreader); + s.loopend = _mm_read_I_ULONG(modreader); + s.c5spd = _mm_read_I_ULONG(modreader); + s.susbegin = _mm_read_I_ULONG(modreader); + s.susend = _mm_read_I_ULONG(modreader); + s.sampoffset = _mm_read_I_ULONG(modreader); + s.vibspeed = _mm_read_UBYTE(modreader); + s.vibdepth = _mm_read_UBYTE(modreader); + s.vibrate = _mm_read_UBYTE(modreader); + s.vibwave = _mm_read_UBYTE(modreader); + + /* Generate an error if c5spd is > 512k, or samplelength > 256 megs + (nothing would EVER be that high) */ + + if(_mm_eof(modreader)||(s.c5spd>0x7ffffL)||(s.length>0xfffffffUL)) { + _mm_errno = MMERR_LOADING_SAMPLEINFO; + return 0; + } + + /* Reality check for sample loop information */ + if((s.flag&16)&& + ((s.loopbeg>0xfffffffUL)||(s.loopend>0xfffffffUL))) { + _mm_errno = MMERR_LOADING_SAMPLEINFO; + return 0; + } + + q->samplename = DupStr(s.sampname,26,0); + q->speed = s.c5spd / 2; + q->panning = ((s.panning&127)==64)?255:(s.panning&127)<<2; + q->length = s.length; + q->loopstart = s.loopbeg; + q->loopend = s.loopend; + q->volume = s.volume; + q->globvol = s.globvol; + q->seekpos = s.sampoffset; + + /* Convert speed to XM linear finetune */ + if(of.flags&UF_LINEAR) + q->speed=speed_to_finetune(s.c5spd,t); + + if(s.panning&128) q->flags|=SF_OWNPAN; + + if(s.vibrate) { + q->vibflags |= AV_IT; + q->vibtype = s.vibwave; + q->vibsweep = s.vibrate * 2; + q->vibdepth = s.vibdepth; + q->vibrate = s.vibspeed; + } + + if(s.flag&2) q->flags|=SF_16BITS; + if((s.flag&8)&&(mh->cwt>=0x214)) { + q->flags|=SF_ITPACKED; + compressed=1; + } + if(s.flag&16) q->flags|=SF_LOOP; + if(s.flag&64) q->flags|=SF_BIDI; + + if(mh->cwt>=0x200) { + if(s.convert&1) q->flags|=SF_SIGNED; + if(s.convert&4) q->flags|=SF_DELTA; + } + q++; + } + + /* Load instruments if instrument mode flag enabled */ + if(mh->flags&4) { + if(!AllocInstruments()) return 0; + d=of.instruments; + of.flags|=UF_NNA|UF_INST; + + for(t=0;t<mh->insnum;t++) { + ITINSTHEADER ih; + + /* seek to instrument position */ + _mm_fseek(modreader,paraptr[t]+4,SEEK_SET); + + /* load instrument info */ + _mm_read_string(ih.filename,12,modreader); + ih.zerobyte = _mm_read_UBYTE(modreader); + if(mh->cwt<0x200) { + /* load IT 1.xx inst header */ + ih.volflg = _mm_read_UBYTE(modreader); + ih.volbeg = _mm_read_UBYTE(modreader); + ih.volend = _mm_read_UBYTE(modreader); + ih.volsusbeg = _mm_read_UBYTE(modreader); + ih.volsusend = _mm_read_UBYTE(modreader); + _mm_read_I_UWORD(modreader); + ih.fadeout = _mm_read_I_UWORD(modreader); + ih.nna = _mm_read_UBYTE(modreader); + ih.dnc = _mm_read_UBYTE(modreader); + } else { + /* Read IT200+ header */ + ih.nna = _mm_read_UBYTE(modreader); + ih.dct = _mm_read_UBYTE(modreader); + ih.dca = _mm_read_UBYTE(modreader); + ih.fadeout = _mm_read_I_UWORD(modreader); + ih.ppsep = _mm_read_UBYTE(modreader); + ih.ppcenter = _mm_read_UBYTE(modreader); + ih.globvol = _mm_read_UBYTE(modreader); + ih.chanpan = _mm_read_UBYTE(modreader); + ih.rvolvar = _mm_read_UBYTE(modreader); + ih.rpanvar = _mm_read_UBYTE(modreader); + } + + ih.trkvers = _mm_read_I_UWORD(modreader); + ih.numsmp = _mm_read_UBYTE(modreader); + _mm_read_UBYTE(modreader); + _mm_read_string(ih.name,26,modreader); + _mm_read_UBYTES(ih.blank01,6,modreader); + _mm_read_I_UWORDS(ih.samptable,ITNOTECNT,modreader); + if(mh->cwt<0x200) { + /* load IT 1xx volume envelope */ + _mm_read_UBYTES(ih.volenv,200,modreader); + for(lp=0;lp<ITENVCNT;lp++) { + ih.oldvoltick[lp] = _mm_read_UBYTE(modreader); + ih.volnode[lp] = _mm_read_UBYTE(modreader); + } + } else { + /* load IT 2xx volume, pan and pitch envelopes */ +#if defined __STDC__ || defined _MSC_VER || defined MPW_C +#define IT_LoadEnvelope(name,type) \ + ih. name##flg =_mm_read_UBYTE(modreader); \ + ih. name##pts =_mm_read_UBYTE(modreader); \ + ih. name##beg =_mm_read_UBYTE(modreader); \ + ih. name##end =_mm_read_UBYTE(modreader); \ + ih. name##susbeg=_mm_read_UBYTE(modreader); \ + ih. name##susend=_mm_read_UBYTE(modreader); \ + for(lp=0;lp<ITENVCNT;lp++) { \ + ih. name##node[lp]=_mm_read_##type (modreader); \ + ih. name##tick[lp]=_mm_read_I_UWORD(modreader); \ + } \ + _mm_read_UBYTE(modreader) +#else +#define IT_LoadEnvelope(name,type) \ + ih. name/**/flg =_mm_read_UBYTE(modreader); \ + ih. name/**/pts =_mm_read_UBYTE(modreader); \ + ih. name/**/beg =_mm_read_UBYTE(modreader); \ + ih. name/**/end =_mm_read_UBYTE(modreader); \ + ih. name/**/susbeg=_mm_read_UBYTE(modreader); \ + ih. name/**/susend=_mm_read_UBYTE(modreader); \ + for(lp=0;lp<ITENVCNT;lp++) { \ + ih. name/**/node[lp]=_mm_read_/**/type (modreader); \ + ih. name/**/tick[lp]=_mm_read_I_UWORD(modreader); \ + } \ + _mm_read_UBYTE(modreader) +#endif + + IT_LoadEnvelope(vol,UBYTE); + IT_LoadEnvelope(pan,SBYTE); + IT_LoadEnvelope(pit,SBYTE); +#undef IT_LoadEnvelope + } + + if(_mm_eof(modreader)) { + _mm_errno = MMERR_LOADING_SAMPLEINFO; + return 0; + } + + d->volflg|=EF_VOLENV; + d->insname = DupStr(ih.name,26,0); + d->nnatype = ih.nna & NNA_MASK; + + if(mh->cwt<0x200) { + d->volfade=ih.fadeout<< 6; + if(ih.dnc) { + d->dct=DCT_NOTE; + d->dca=DCA_CUT; + } + + if(ih.volflg&1) d->volflg|=EF_ON; + if(ih.volflg&2) d->volflg|=EF_LOOP; + if(ih.volflg&4) d->volflg|=EF_SUSTAIN; + + /* XM conversion of IT envelope Array */ + d->volbeg = ih.volbeg; + d->volend = ih.volend; + d->volsusbeg = ih.volsusbeg; + d->volsusend = ih.volsusend; + + if(ih.volflg&1) { + for(u=0;u<ITENVCNT;u++) + if(ih.oldvoltick[d->volpts]!=0xff) { + d->volenv[d->volpts].val=(ih.volnode[d->volpts]<<2); + d->volenv[d->volpts].pos=ih.oldvoltick[d->volpts]; + d->volpts++; + } else + break; + } + } else { + d->panning=((ih.chanpan&127)==64)?255:(ih.chanpan&127)<<2; + if(!(ih.chanpan&128)) d->flags|=IF_OWNPAN; + + if(!(ih.ppsep & 128)) { + d->pitpansep=ih.ppsep<<2; + d->pitpancenter=ih.ppcenter; + d->flags|=IF_PITCHPAN; + } + d->globvol=ih.globvol>>1; + d->volfade=ih.fadeout<<5; + d->dct =ih.dct; + d->dca =ih.dca; + + if(mh->cwt>=0x204) { + d->rvolvar = ih.rvolvar; + d->rpanvar = ih.rpanvar; + } + +#if defined __STDC__ || defined _MSC_VER || defined MPW_C +#define IT_ProcessEnvelope(name) \ + if(ih. name##flg&1) d-> name##flg|=EF_ON; \ + if(ih. name##flg&2) d-> name##flg|=EF_LOOP; \ + if(ih. name##flg&4) d-> name##flg|=EF_SUSTAIN; \ + d-> name##pts=ih. name##pts; \ + d-> name##beg=ih. name##beg; \ + d-> name##end=ih. name##end; \ + d-> name##susbeg=ih. name##susbeg; \ + d-> name##susend=ih. name##susend; \ + \ + for(u=0;u<ih. name##pts;u++) \ + d-> name##env[u].pos=ih. name##tick[u]; \ + \ + if((d-> name##flg&EF_ON)&&(d-> name##pts<2)) \ + d-> name##flg&=~EF_ON +#else +#define IT_ProcessEnvelope(name) \ + if(ih. name/**/flg&1) d-> name/**/flg|=EF_ON; \ + if(ih. name/**/flg&2) d-> name/**/flg|=EF_LOOP; \ + if(ih. name/**/flg&4) d-> name/**/flg|=EF_SUSTAIN; \ + d-> name/**/pts=ih. name/**/pts; \ + d-> name/**/beg=ih. name/**/beg; \ + d-> name/**/end=ih. name/**/end; \ + d-> name/**/susbeg=ih. name/**/susbeg; \ + d-> name/**/susend=ih. name/**/susend; \ + \ + for(u=0;u<ih. name/**/pts;u++) \ + d-> name/**/env[u].pos=ih. name/**/tick[u]; \ + \ + if((d-> name/**/flg&EF_ON)&&(d-> name/**/pts<2)) \ + d-> name/**/flg&=~EF_ON +#endif + + IT_ProcessEnvelope(vol); + for(u=0;u<ih.volpts;u++) + d->volenv[u].val=(ih.volnode[u]<<2); + + IT_ProcessEnvelope(pan); + for(u=0;u<ih.panpts;u++) + d->panenv[u].val= + ih.pannode[u]==32?255:(ih.pannode[u]+32)<<2; + + IT_ProcessEnvelope(pit); + for(u=0;u<ih.pitpts;u++) + d->pitenv[u].val=ih.pitnode[u]+32; +#undef IT_ProcessEnvelope + + if(ih.pitflg&0x80) { + /* filter envelopes not supported yet */ + d->pitflg&=~EF_ON; + ih.pitpts=ih.pitbeg=ih.pitend=0; +#ifdef MIKMOD_DEBUG + { + static int warn=0; + + if(!warn) + fprintf(stderr, "\rFilter envelopes not supported yet\n"); + warn=1; + } +#endif + } + } + + for(u=0;u<ITNOTECNT;u++) { + d->samplenote[u]=(ih.samptable[u]&255); + d->samplenumber[u]= + (ih.samptable[u]>>8)?((ih.samptable[u]>>8)-1):0xffff; + if(d->samplenumber[u]>=of.numsmp) + d->samplenote[u]=255; + else if (of.flags&UF_LINEAR) { + int note=(int)d->samplenote[u]+noteindex[d->samplenumber[u]]; + d->samplenote[u]=(note<0)?0:(note>255?255:note); + } + } + + d++; + } + } else if(of.flags & UF_LINEAR) { + if(!AllocInstruments()) return 0; + d=of.instruments; + of.flags|=UF_INST; + + for(t=0;t<mh->smpnum;t++,d++) + for(u=0;u<ITNOTECNT;u++) { + if(d->samplenumber[u]>=of.numsmp) + d->samplenote[u]=255; + else { + int note=(int)d->samplenote[u]+noteindex[d->samplenumber[u]]; + d->samplenote[u]=(note<0)?0:(note>255?255:note); + } + } + } + + /* Figure out how many channels this song actually uses */ + of.numchn=0; + memset(remap,-1,UF_MAXCHAN*sizeof(UBYTE)); + for(t=0;t<of.numpat;t++) { + UWORD packlen; + + /* seek to pattern position */ + if(paraptr[mh->insnum+mh->smpnum+t]) { /* 0 -> empty 64 row pattern */ + _mm_fseek(modreader,((long)paraptr[mh->insnum+mh->smpnum+t]),SEEK_SET); + _mm_read_I_UWORD(modreader); + /* read pattern length (# of rows) + Impulse Tracker never creates patterns with less than 32 rows, + but some other trackers do, so we only check for more than 256 + rows */ + packlen=_mm_read_I_UWORD(modreader); + if(packlen>256) { + _mm_errno=MMERR_LOADING_PATTERN; + return 0; + } + _mm_read_I_ULONG(modreader); + if(IT_GetNumChannels(packlen)) return 0; + } + } + + /* give each of them a different number */ + for(t=0;t<UF_MAXCHAN;t++) + if(!remap[t]) + remap[t]=of.numchn++; + + of.numtrk = of.numpat*of.numchn; + if(of.numvoices) + if (of.numvoices<of.numchn) of.numvoices=of.numchn; + + if(!AllocPatterns()) return 0; + if(!AllocTracks()) return 0; + + for(t=0;t<of.numpat;t++) { + UWORD packlen; + + /* seek to pattern position */ + if(!paraptr[mh->insnum+mh->smpnum+t]) { /* 0 -> empty 64 row pattern */ + of.pattrows[t]=64; + for(u=0;u<of.numchn;u++) { + int k; + + UniReset(); + for(k=0;k<64;k++) UniNewline(); + of.tracks[numtrk++]=UniDup(); + } + } else { + _mm_fseek(modreader,((long)paraptr[mh->insnum+mh->smpnum+t]),SEEK_SET); + packlen=_mm_read_I_UWORD(modreader); + of.pattrows[t]=_mm_read_I_UWORD(modreader); + _mm_read_I_ULONG(modreader); + if(!IT_ReadPattern(of.pattrows[t])) return 0; + } + } + + return 1; +} + +CHAR *IT_LoadTitle(void) +{ + CHAR s[26]; + + _mm_fseek(modreader,4,SEEK_SET); + if(!_mm_read_UBYTES(s,26,modreader)) return NULL; + + return(DupStr(s,26,0)); +} + +/*========== Loader information */ + +MIKMODAPI MLOADER load_it={ + NULL, + "IT", + "IT (Impulse Tracker)", + IT_Init, + IT_Test, + IT_Load, + IT_Cleanup, + IT_LoadTitle +}; + +/* ex:set ts=4: */ diff --git a/src/libs/mikmod/load_mod.c b/src/libs/mikmod/load_mod.c new file mode 100644 index 0000000..40d4b9a --- /dev/null +++ b/src/libs/mikmod/load_mod.c @@ -0,0 +1,512 @@ +/* MikMod sound library + (c) 1998, 1999, 2000, 2001, 2002 Miodrag Vallat and others - see file + AUTHORS for complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + Generic MOD loader (Protracker, StarTracker, FastTracker, etc) + +==============================================================================*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include <ctype.h> +#include <stdio.h> +#ifdef HAVE_MEMORY_H +#include <memory.h> +#endif +#include <string.h> + +#include "mikmod_internals.h" + +#ifdef SUNOS +extern int fprintf(FILE *, const char *, ...); +#endif + +/*========== Module structure */ + +typedef struct MSAMPINFO { + CHAR samplename[23]; /* 22 in module, 23 in memory */ + UWORD length; + UBYTE finetune; + UBYTE volume; + UWORD reppos; + UWORD replen; +} MSAMPINFO; + +typedef struct MODULEHEADER { + CHAR songname[21]; /* the songname.. 20 in module, 21 in memory */ + MSAMPINFO samples[31]; /* all sampleinfo */ + UBYTE songlength; /* number of patterns used */ + UBYTE magic1; /* should be 127 */ + UBYTE positions[128]; /* which pattern to play at pos */ + UBYTE magic2[4]; /* string "M.K." or "FLT4" or "FLT8" */ +} MODULEHEADER; + +typedef struct MODTYPE { + CHAR id[5]; + UBYTE channels; + CHAR *name; +} MODTYPE; + +typedef struct MODNOTE { + UBYTE a, b, c, d; +} MODNOTE; + +/*========== Loader variables */ + +#define MODULEHEADERSIZE 0x438 + +static CHAR protracker[] = "Protracker"; +static CHAR startrekker[] = "Startrekker"; +static CHAR fasttracker[] = "Fasttracker"; +static CHAR oktalyser[] = "Oktalyser"; +static CHAR oktalyzer[] = "Oktalyzer"; +static CHAR taketracker[] = "TakeTracker"; +static CHAR orpheus[] = "Imago Orpheus (MOD format)"; + +static MODULEHEADER *mh = NULL; +static MODNOTE *patbuf = NULL; +static int modtype, trekker; + +/*========== Loader code */ + +/* given the module ID, determine the number of channels and the tracker + description ; also alters modtype */ +static BOOL MOD_CheckType(UBYTE *id, UBYTE *numchn, CHAR **descr) +{ + modtype = trekker = 0; + + /* Protracker and variants */ + if ((!memcmp(id, "M.K.", 4)) || (!memcmp(id, "M!K!", 4))) { + *descr = protracker; + modtype = 0; + *numchn = 4; + return 1; + } + + /* Star Tracker */ + if (((!memcmp(id, "FLT", 3)) || (!memcmp(id, "EXO", 3))) && + (isdigit(id[3]))) { + *descr = startrekker; + modtype = trekker = 1; + *numchn = id[3] - '0'; + if (*numchn == 4 || *numchn == 8) + return 1; +#ifdef MIKMOD_DEBUG + else + fprintf(stderr, "\rUnknown FLT%d module type\n", *numchn); +#endif + return 0; + } + + /* Oktalyzer (Amiga) */ + if (!memcmp(id, "OKTA", 4)) { + *descr = oktalyzer; + modtype = 1; + *numchn = 8; + return 1; + } + + /* Oktalyser (Atari) */ + if (!memcmp(id, "CD81", 4)) { + *descr = oktalyser; + modtype = 1; + *numchn = 8; + return 1; + } + + /* Fasttracker */ + if ((!memcmp(id + 1, "CHN", 3)) && (isdigit(id[0]))) { + *descr = fasttracker; + modtype = 1; + *numchn = id[0] - '0'; + return 1; + } + /* Fasttracker or Taketracker */ + if (((!memcmp(id + 2, "CH", 2)) || (!memcmp(id + 2, "CN", 2))) + && (isdigit(id[0])) && (isdigit(id[1]))) { + if (id[3] == 'H') { + *descr = fasttracker; + modtype = 2; /* this can also be Imago Orpheus */ + } else { + *descr = taketracker; + modtype = 1; + } + *numchn = (id[0] - '0') * 10 + (id[1] - '0'); + return 1; + } + + return 0; +} + +static BOOL MOD_Test(void) +{ + UBYTE id[4], numchn; + CHAR *descr; + + _mm_fseek(modreader, MODULEHEADERSIZE, SEEK_SET); + if (!_mm_read_UBYTES(id, 4, modreader)) + return 0; + + if (MOD_CheckType(id, &numchn, &descr)) + return 1; + + return 0; +} + +static BOOL MOD_Init(void) +{ + if (!(mh = (MODULEHEADER *)MikMod_malloc(sizeof(MODULEHEADER)))) + return 0; + return 1; +} + +static void MOD_Cleanup(void) +{ + MikMod_free(mh); + MikMod_free(patbuf); +} + +/* +Old (amiga) noteinfo: + +_____byte 1_____ byte2_ _____byte 3_____ byte4_ +/ \ / \ / \ / \ +0000 0000-00000000 0000 0000-00000000 + +Upper four 12 bits for Lower four Effect command. +bits of sam- note period. bits of sam- +ple number. ple number. + +*/ + +static UBYTE ConvertNote(MODNOTE *n, UBYTE lasteffect) +{ + UBYTE instrument, effect, effdat, note; + UWORD period; + UBYTE lastnote = 0; + + /* extract the various information from the 4 bytes that make up a note */ + instrument = (n->a & 0x10) | (n->c >> 4); + period = (((UWORD)n->a & 0xf) << 8) + n->b; + effect = n->c & 0xf; + effdat = n->d; + + /* Convert the period to a note number */ + note = 0; + if (period) { + for (note = 0; note < 7 * OCTAVE; note++) + if (period >= npertab[note]) + break; + if (note == 7 * OCTAVE) + note = 0; + else + note++; + } + + if (instrument) { + /* if instrument does not exist, note cut */ + if ((instrument > 31) || (!mh->samples[instrument - 1].length)) { + UniPTEffect(0xc, 0); + if (effect == 0xc) + effect = effdat = 0; + } else { + /* Protracker handling */ + if (!modtype) { + /* if we had a note, then change instrument... */ + if (note) + UniInstrument(instrument - 1); + /* ...otherwise, only adjust volume... */ + else { + /* ...unless an effect was specified, which forces a new + note to be played */ + if (effect || effdat) { + UniInstrument(instrument - 1); + note = lastnote; + } else + UniPTEffect(0xc, + mh->samples[instrument - + 1].volume & 0x7f); + } + } else { + /* Fasttracker handling */ + UniInstrument(instrument - 1); + if (!note) + note = lastnote; + } + } + } + if (note) { + UniNote(note + 2 * OCTAVE - 1); + lastnote = note; + } + + /* Convert pattern jump from Dec to Hex */ + if (effect == 0xd) + effdat = (((effdat & 0xf0) >> 4) * 10) + (effdat & 0xf); + + /* Volume slide, up has priority */ + if ((effect == 0xa) && (effdat & 0xf) && (effdat & 0xf0)) + effdat &= 0xf0; + + /* Handle ``heavy'' volumes correctly */ + if ((effect == 0xc) && (effdat > 0x40)) + effdat = 0x40; + + /* An isolated 100, 200 or 300 effect should be ignored (no + "standalone" porta memory in mod files). However, a sequence such + as 1XX, 100, 100, 100 is fine. */ + if ((!effdat) && ((effect == 1)||(effect == 2)||(effect ==3)) && + (lasteffect < 0x10) && (effect != lasteffect)) + effect = 0; + + UniPTEffect(effect, effdat); + if (effect == 8) + of.flags |= UF_PANNING; + + return effect; +} + +static UBYTE *ConvertTrack(MODNOTE *n, int numchn) +{ + int t; + UBYTE lasteffect = 0x10; /* non existant effect */ + + UniReset(); + for (t = 0; t < 64; t++) { + lasteffect = ConvertNote(n,lasteffect); + UniNewline(); + n += numchn; + } + return UniDup(); +} + +/* Loads all patterns of a modfile and converts them into the 3 byte format. */ +static BOOL ML_LoadPatterns(void) +{ + int t, s, tracks = 0; + + if (!AllocPatterns()) + return 0; + if (!AllocTracks()) + return 0; + + /* Allocate temporary buffer for loading and converting the patterns */ + if (!(patbuf = (MODNOTE *)MikMod_calloc(64U * of.numchn, sizeof(MODNOTE)))) + return 0; + + if (trekker && of.numchn == 8) { + /* Startrekker module dual pattern */ + for (t = 0; t < of.numpat; t++) { + for (s = 0; s < (64 * 4); s++) { + patbuf[s].a = _mm_read_UBYTE(modreader); + patbuf[s].b = _mm_read_UBYTE(modreader); + patbuf[s].c = _mm_read_UBYTE(modreader); + patbuf[s].d = _mm_read_UBYTE(modreader); + } + for (s = 0; s < 4; s++) + if (!(of.tracks[tracks++] = ConvertTrack(patbuf + s, 4))) + return 0; + for (s = 0; s < (64 * 4); s++) { + patbuf[s].a = _mm_read_UBYTE(modreader); + patbuf[s].b = _mm_read_UBYTE(modreader); + patbuf[s].c = _mm_read_UBYTE(modreader); + patbuf[s].d = _mm_read_UBYTE(modreader); + } + for (s = 0; s < 4; s++) + if (!(of.tracks[tracks++] = ConvertTrack(patbuf + s, 4))) + return 0; + } + } else { + /* Generic module pattern */ + for (t = 0; t < of.numpat; t++) { + /* Load the pattern into the temp buffer and convert it */ + for (s = 0; s < (64 * of.numchn); s++) { + patbuf[s].a = _mm_read_UBYTE(modreader); + patbuf[s].b = _mm_read_UBYTE(modreader); + patbuf[s].c = _mm_read_UBYTE(modreader); + patbuf[s].d = _mm_read_UBYTE(modreader); + } + for (s = 0; s < of.numchn; s++) + if (!(of.tracks[tracks++] = ConvertTrack(patbuf + s, of.numchn))) + return 0; + } + } + return 1; +} + +static BOOL MOD_Load(BOOL curious) +{ + int t, scan; + SAMPLE *q; + MSAMPINFO *s; + CHAR *descr; + + /* try to read module header */ + _mm_read_string((CHAR *)mh->songname, 20, modreader); + mh->songname[20] = 0; /* just in case */ + + for (t = 0; t < 31; t++) { + s = &mh->samples[t]; + _mm_read_string(s->samplename, 22, modreader); + s->samplename[22] = 0; /* just in case */ + s->length = _mm_read_M_UWORD(modreader); + s->finetune = _mm_read_UBYTE(modreader); + s->volume = _mm_read_UBYTE(modreader); + s->reppos = _mm_read_M_UWORD(modreader); + s->replen = _mm_read_M_UWORD(modreader); + } + + mh->songlength = _mm_read_UBYTE(modreader); + + /* this fixes mods which declare more than 128 positions. + * eg: beatwave.mod */ + if (mh->songlength > 128) { mh->songlength = 128; } + + mh->magic1 = _mm_read_UBYTE(modreader); + _mm_read_UBYTES(mh->positions, 128, modreader); + _mm_read_UBYTES(mh->magic2, 4, modreader); + + if (_mm_eof(modreader)) { + _mm_errno = MMERR_LOADING_HEADER; + return 0; + } + + /* set module variables */ + of.initspeed = 6; + of.inittempo = 125; + if (!(MOD_CheckType(mh->magic2, &of.numchn, &descr))) { + _mm_errno = MMERR_NOT_A_MODULE; + return 0; + } + if (trekker && of.numchn == 8) + for (t = 0; t < 128; t++) + /* if module pretends to be FLT8, yet the order table + contains odd numbers, chances are it's a lying FLT4... */ + if (mh->positions[t] & 1) { + of.numchn = 4; + break; + } + if (trekker && of.numchn == 8) + for (t = 0; t < 128; t++) + mh->positions[t] >>= 1; + + of.songname = DupStr(mh->songname, 21, 1); + of.numpos = mh->songlength; + of.reppos = 0; + + /* Count the number of patterns */ + of.numpat = 0; + for (t = 0; t < of.numpos; t++) + if (mh->positions[t] > of.numpat) + of.numpat = mh->positions[t]; + + /* since some old modules embed extra patterns, we have to check the + whole list to get the samples' file offsets right - however we can find + garbage here, so check carefully */ + scan = 1; + for (t = of.numpos; t < 128; t++) + if (mh->positions[t] >= 0x80) + scan = 0; + if (scan) + for (t = of.numpos; t < 128; t++) { + if (mh->positions[t] > of.numpat) + of.numpat = mh->positions[t]; + if ((curious) && (mh->positions[t])) + of.numpos = t + 1; + } + of.numpat++; + of.numtrk = of.numpat * of.numchn; + + if (!AllocPositions(of.numpos)) + return 0; + for (t = 0; t < of.numpos; t++) + of.positions[t] = mh->positions[t]; + + /* Finally, init the sampleinfo structures */ + of.numins = of.numsmp = 31; + if (!AllocSamples()) + return 0; + s = mh->samples; + q = of.samples; + for (t = 0; t < of.numins; t++) { + /* convert the samplename */ + q->samplename = DupStr(s->samplename, 23, 1); + /* init the sampleinfo variables and convert the size pointers */ + q->speed = finetune[s->finetune & 0xf]; + q->volume = s->volume & 0x7f; + q->loopstart = (ULONG)s->reppos << 1; + q->loopend = q->loopstart + ((ULONG)s->replen << 1); + q->length = (ULONG)s->length << 1; + q->flags = SF_SIGNED; + /* Imago Orpheus creates MODs with 16 bit samples, check */ + if ((modtype == 2) && (s->volume & 0x80)) { + q->flags |= SF_16BITS; + descr = orpheus; + } + if (s->replen > 2) + q->flags |= SF_LOOP; + + s++; + q++; + } + + of.modtype = strdup(descr); + + if (!ML_LoadPatterns()) + return 0; + + return 1; +} + +static CHAR *MOD_LoadTitle(void) +{ + CHAR s[21]; + + _mm_fseek(modreader, 0, SEEK_SET); + if (!_mm_read_UBYTES(s, 20, modreader)) + return NULL; + s[20] = 0; /* just in case */ + + return (DupStr(s, 21, 1)); +} + +/*========== Loader information */ + +MIKMODAPI MLOADER load_mod = { + NULL, + "Standard module", + "MOD (31 instruments)", + MOD_Init, + MOD_Test, + MOD_Load, + MOD_Cleanup, + MOD_LoadTitle +}; + +/* ex:set ts=4: */ diff --git a/src/libs/mikmod/load_s3m.c b/src/libs/mikmod/load_s3m.c new file mode 100644 index 0000000..782ee23 --- /dev/null +++ b/src/libs/mikmod/load_s3m.c @@ -0,0 +1,470 @@ +/* MikMod sound library + (c) 1998, 1999, 2000, 2001, 2002 Miodrag Vallat and others - see file + AUTHORS for complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + Screamtracker (S3M) module loader + +==============================================================================*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include <stdio.h> +#ifdef HAVE_MEMORY_H +#include <memory.h> +#endif +#include <string.h> + +#include "mikmod_internals.h" + +#ifdef SUNOS +extern int fprintf(FILE *, const char *, ...); +#endif + +/*========== Module structure */ + +/* header */ +typedef struct S3MHEADER { + CHAR songname[28]; + UBYTE t1a; + UBYTE type; + UBYTE unused1[2]; + UWORD ordnum; + UWORD insnum; + UWORD patnum; + UWORD flags; + UWORD tracker; + UWORD fileformat; + CHAR scrm[4]; + UBYTE mastervol; + UBYTE initspeed; + UBYTE inittempo; + UBYTE mastermult; + UBYTE ultraclick; + UBYTE pantable; + UBYTE unused2[8]; + UWORD special; + UBYTE channels[32]; +} S3MHEADER; + +/* sample information */ +typedef struct S3MSAMPLE { + UBYTE type; + CHAR filename[12]; + UBYTE memsegh; + UWORD memsegl; + ULONG length; + ULONG loopbeg; + ULONG loopend; + UBYTE volume; + UBYTE dsk; + UBYTE pack; + UBYTE flags; + ULONG c2spd; + UBYTE unused[12]; + CHAR sampname[28]; + CHAR scrs[4]; +} S3MSAMPLE; + +typedef struct S3MNOTE { + UBYTE note,ins,vol,cmd,inf; +} S3MNOTE; + +/*========== Loader variables */ + +static S3MNOTE *s3mbuf = NULL; /* pointer to a complete S3M pattern */ +static S3MHEADER *mh = NULL; +static UWORD *paraptr = NULL; /* parapointer array (see S3M docs) */ +static unsigned int tracker; /* tracker id */ + +/* tracker identifiers */ +#define NUMTRACKERS 4 +static CHAR* S3M_Version[] = { + "Screamtracker x.xx", + "Imago Orpheus x.xx (S3M format)", + "Impulse Tracker x.xx (S3M format)", + "Unknown tracker x.xx (S3M format)", + "Impulse Tracker 2.14p3 (S3M format)", + "Impulse Tracker 2.14p4 (S3M format)" +}; +/* version number position in above array */ +static int numeric[NUMTRACKERS]={14,14,16,16}; + +/*========== Loader code */ + +BOOL S3M_Test(void) +{ + UBYTE id[4]; + + _mm_fseek(modreader,0x2c,SEEK_SET); + if(!_mm_read_UBYTES(id,4,modreader)) return 0; + if(!memcmp(id,"SCRM",4)) return 1; + return 0; +} + +BOOL S3M_Init(void) +{ + if(!(s3mbuf=(S3MNOTE*)MikMod_malloc(32*64*sizeof(S3MNOTE)))) return 0; + if(!(mh=(S3MHEADER*)MikMod_malloc(sizeof(S3MHEADER)))) return 0; + if(!(poslookup=(UBYTE*)MikMod_malloc(sizeof(UBYTE)*256))) return 0; + memset(poslookup,-1,256); + + return 1; +} + +void S3M_Cleanup(void) +{ + MikMod_free(s3mbuf); + MikMod_free(paraptr); + MikMod_free(poslookup); + MikMod_free(mh); + MikMod_free(origpositions); +} + +/* Because so many s3m files have 16 channels as the set number used, but really + only use far less (usually 8 to 12 still), I had to make this function, which + determines the number of channels that are actually USED by a pattern. + + For every channel that's used, it sets the appropriate array entry of the + global variable 'remap' + + NOTE: You must first seek to the file location of the pattern before calling + this procedure. + + Returns 1 on fail. */ +static BOOL S3M_GetNumChannels(void) +{ + int row=0,flag,ch; + + while(row<64) { + flag=_mm_read_UBYTE(modreader); + + if(_mm_eof(modreader)) { + _mm_errno = MMERR_LOADING_PATTERN; + return 1; + } + + if(flag) { + ch=flag&31; + if(mh->channels[ch]<32) remap[ch] = 0; + if(flag&32) {_mm_read_UBYTE(modreader);_mm_read_UBYTE(modreader);} + if(flag&64) _mm_read_UBYTE(modreader); + if(flag&128){_mm_read_UBYTE(modreader);_mm_read_UBYTE(modreader);} + } else row++; + } + return 0; +} + +static BOOL S3M_ReadPattern(void) +{ + int row=0,flag,ch; + S3MNOTE *n,dummy; + + /* clear pattern data */ + memset(s3mbuf,255,32*64*sizeof(S3MNOTE)); + + while(row<64) { + flag=_mm_read_UBYTE(modreader); + + if(_mm_eof(modreader)) { + _mm_errno = MMERR_LOADING_PATTERN; + return 0; + } + + if(flag) { + ch=remap[flag&31]; + + if(ch!=-1) + n=&s3mbuf[(64U*ch)+row]; + else + n=&dummy; + + if(flag&32) { + n->note=_mm_read_UBYTE(modreader); + n->ins=_mm_read_UBYTE(modreader); + } + if(flag&64) { + n->vol=_mm_read_UBYTE(modreader); + if (n->vol>64) n->vol=64; + } + if(flag&128) { + n->cmd=_mm_read_UBYTE(modreader); + n->inf=_mm_read_UBYTE(modreader); + } + } else row++; + } + return 1; +} + +static UBYTE* S3M_ConvertTrack(S3MNOTE* tr) +{ + int t; + + UniReset(); + for(t=0;t<64;t++) { + UBYTE note,ins,vol; + + note=tr[t].note; + ins=tr[t].ins; + vol=tr[t].vol; + + if((ins)&&(ins!=255)) UniInstrument(ins-1); + if(note!=255) { + if(note==254) { + UniPTEffect(0xc,0); /* note cut command */ + vol=255; + } else + UniNote(((note>>4)*OCTAVE)+(note&0xf)); /* normal note */ + } + if(vol<255) UniPTEffect(0xc,vol); + + S3MIT_ProcessCmd(tr[t].cmd,tr[t].inf, + tracker == 1 ? S3MIT_OLDSTYLE | S3MIT_SCREAM : S3MIT_OLDSTYLE); + UniNewline(); + } + return UniDup(); +} + +BOOL S3M_Load(BOOL curious) +{ + int t,u,track = 0; + SAMPLE *q; + UBYTE pan[32]; + + /* try to read module header */ + _mm_read_string(mh->songname,28,modreader); + mh->t1a =_mm_read_UBYTE(modreader); + mh->type =_mm_read_UBYTE(modreader); + _mm_read_UBYTES(mh->unused1,2,modreader); + mh->ordnum =_mm_read_I_UWORD(modreader); + mh->insnum =_mm_read_I_UWORD(modreader); + mh->patnum =_mm_read_I_UWORD(modreader); + mh->flags =_mm_read_I_UWORD(modreader); + mh->tracker =_mm_read_I_UWORD(modreader); + mh->fileformat =_mm_read_I_UWORD(modreader); + _mm_read_string(mh->scrm,4,modreader); + mh->mastervol =_mm_read_UBYTE(modreader); + mh->initspeed =_mm_read_UBYTE(modreader); + mh->inittempo =_mm_read_UBYTE(modreader); + mh->mastermult =_mm_read_UBYTE(modreader); + mh->ultraclick =_mm_read_UBYTE(modreader); + mh->pantable =_mm_read_UBYTE(modreader); + _mm_read_UBYTES(mh->unused2,8,modreader); + mh->special =_mm_read_I_UWORD(modreader); + _mm_read_UBYTES(mh->channels,32,modreader); + + if(_mm_eof(modreader)) { + _mm_errno = MMERR_LOADING_HEADER; + return 0; + } + + /* then we can decide the module type */ + tracker=mh->tracker>>12; + if((!tracker)||(tracker>=NUMTRACKERS)) + tracker=NUMTRACKERS-1; /* unknown tracker */ + else { + if(mh->tracker>=0x3217) + tracker=NUMTRACKERS+1; /* IT 2.14p4 */ + else if(mh->tracker>=0x3216) + tracker=NUMTRACKERS; /* IT 2.14p3 */ + else tracker--; + } + of.modtype = strdup(S3M_Version[tracker]); + if(tracker<NUMTRACKERS) { + of.modtype[numeric[tracker]] = ((mh->tracker>>8) &0xf)+'0'; + of.modtype[numeric[tracker]+2] = ((mh->tracker>>4)&0xf)+'0'; + of.modtype[numeric[tracker]+3] = ((mh->tracker)&0xf)+'0'; + } + /* set module variables */ + of.songname = DupStr(mh->songname,28,0); + of.numpat = mh->patnum; + of.reppos = 0; + of.numins = of.numsmp = mh->insnum; + of.initspeed = mh->initspeed; + of.inittempo = mh->inittempo; + of.initvolume = mh->mastervol<<1; + of.flags |= UF_ARPMEM | UF_PANNING; + if((mh->tracker==0x1300)||(mh->flags&64)) + of.flags|=UF_S3MSLIDES; + of.bpmlimit = 32; + + /* read the order data */ + if(!AllocPositions(mh->ordnum)) return 0; + if(!(origpositions=MikMod_calloc(mh->ordnum,sizeof(UWORD)))) return 0; + + for(t=0;t<mh->ordnum;t++) { + origpositions[t]=_mm_read_UBYTE(modreader); + if((origpositions[t]>=mh->patnum)&&(origpositions[t]<254)) + origpositions[t]=255/*mh->patnum-1*/; + } + + if(_mm_eof(modreader)) { + _mm_errno = MMERR_LOADING_HEADER; + return 0; + } + + poslookupcnt=mh->ordnum; + S3MIT_CreateOrders(curious); + + if(!(paraptr=(UWORD*)MikMod_malloc((of.numins+of.numpat)*sizeof(UWORD)))) + return 0; + + /* read the instrument+pattern parapointers */ + _mm_read_I_UWORDS(paraptr,of.numins+of.numpat,modreader); + + if(mh->pantable==252) { + /* read the panning table (ST 3.2 addition. See below for further + portions of channel panning [past reampper]). */ + _mm_read_UBYTES(pan,32,modreader); + } + + if(_mm_eof(modreader)) { + _mm_errno = MMERR_LOADING_HEADER; + return 0; + } + + /* load samples */ + if(!AllocSamples()) return 0; + q = of.samples; + for(t=0;t<of.numins;t++) { + S3MSAMPLE s; + + /* seek to instrument position */ + _mm_fseek(modreader,((long)paraptr[t])<<4,SEEK_SET); + /* and load sample info */ + s.type =_mm_read_UBYTE(modreader); + _mm_read_string(s.filename,12,modreader); + s.memsegh =_mm_read_UBYTE(modreader); + s.memsegl =_mm_read_I_UWORD(modreader); + s.length =_mm_read_I_ULONG(modreader); + s.loopbeg =_mm_read_I_ULONG(modreader); + s.loopend =_mm_read_I_ULONG(modreader); + s.volume =_mm_read_UBYTE(modreader); + s.dsk =_mm_read_UBYTE(modreader); + s.pack =_mm_read_UBYTE(modreader); + s.flags =_mm_read_UBYTE(modreader); + s.c2spd =_mm_read_I_ULONG(modreader); + _mm_read_UBYTES(s.unused,12,modreader); + _mm_read_string(s.sampname,28,modreader); + _mm_read_string(s.scrs,4,modreader); + + /* ScreamTracker imposes a 64000 bytes (not 64k !) limit */ + if (s.length > 64000) + s.length = 64000; + + if(_mm_eof(modreader)) { + _mm_errno = MMERR_LOADING_SAMPLEINFO; + return 0; + } + + q->samplename = DupStr(s.sampname,28,0); + q->speed = s.c2spd; + q->length = s.length; + q->loopstart = s.loopbeg; + q->loopend = s.loopend; + q->volume = s.volume; + q->seekpos = (((long)s.memsegh)<<16|s.memsegl)<<4; + + if(s.flags&1) q->flags |= SF_LOOP; + if(s.flags&4) q->flags |= SF_16BITS; + if(mh->fileformat==1) q->flags |= SF_SIGNED; + + /* don't load sample if it doesn't have the SCRS tag */ + if(memcmp(s.scrs,"SCRS",4)) q->length = 0; + + q++; + } + + /* determine the number of channels actually used. */ + of.numchn = 0; + memset(remap,-1,32*sizeof(UBYTE)); + for(t=0;t<of.numpat;t++) { + /* seek to pattern position (+2 skip pattern length) */ + _mm_fseek(modreader,(long)((paraptr[of.numins+t])<<4)+2,SEEK_SET); + if(S3M_GetNumChannels()) return 0; + } + + /* build the remap array */ + for(t=0;t<32;t++) + if(!remap[t]) + remap[t]=of.numchn++; + + /* set panning positions after building remap chart! */ + for(t=0;t<32;t++) + if((mh->channels[t]<32)&&(remap[t]!=-1)) { + if(mh->channels[t]<8) + of.panning[remap[t]]=0x30; + else + of.panning[remap[t]]=0xc0; + } + if(mh->pantable==252) + /* set panning positions according to panning table (new for st3.2) */ + for(t=0;t<32;t++) + if((pan[t]&0x20)&&(mh->channels[t]<32)&&(remap[t]!=-1)) + of.panning[remap[t]]=(pan[t]&0xf)<<4; + + /* load pattern info */ + of.numtrk=of.numpat*of.numchn; + if(!AllocTracks()) return 0; + if(!AllocPatterns()) return 0; + + for(t=0;t<of.numpat;t++) { + /* seek to pattern position (+2 skip pattern length) */ + _mm_fseek(modreader,(((long)paraptr[of.numins+t])<<4)+2,SEEK_SET); + if(!S3M_ReadPattern()) return 0; + for(u=0;u<of.numchn;u++) + if(!(of.tracks[track++]=S3M_ConvertTrack(&s3mbuf[u*64]))) return 0; + } + + return 1; +} + +CHAR *S3M_LoadTitle(void) +{ + CHAR s[28]; + + _mm_fseek(modreader,0,SEEK_SET); + if(!_mm_read_UBYTES(s,28,modreader)) return NULL; + + return(DupStr(s,28,0)); +} + +/*========== Loader information */ + +MIKMODAPI MLOADER load_s3m={ + NULL, + "S3M", + "S3M (Scream Tracker 3)", + S3M_Init, + S3M_Test, + S3M_Load, + S3M_Cleanup, + S3M_LoadTitle +}; + +/* ex:set ts=4: */ diff --git a/src/libs/mikmod/load_stm.c b/src/libs/mikmod/load_stm.c new file mode 100644 index 0000000..299f33b --- /dev/null +++ b/src/libs/mikmod/load_stm.c @@ -0,0 +1,376 @@ +/* MikMod sound library + (c) 1998, 1999, 2000, 2001, 2002 Miodrag Vallat and others - see file + AUTHORS for complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + Screamtracker 2 (STM) module loader + +==============================================================================*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include <stdio.h> +#ifdef HAVE_MEMORY_H +#include <memory.h> +#endif +#include <string.h> + +#include "mikmod_internals.h" + +#ifdef SUNOS +extern int fprintf(FILE *, const char *, ...); +#endif + +/*========== Module structure */ + +/* sample information */ +typedef struct STMSAMPLE { + CHAR filename[12]; + UBYTE unused; /* 0x00 */ + UBYTE instdisk; /* Instrument disk */ + UWORD reserved; + UWORD length; /* Sample length */ + UWORD loopbeg; /* Loop start point */ + UWORD loopend; /* Loop end point */ + UBYTE volume; /* Volume */ + UBYTE reserved2; + UWORD c2spd; /* Good old c2spd */ + ULONG reserved3; + UWORD isa; +} STMSAMPLE; + +/* header */ +typedef struct STMHEADER { + CHAR songname[20]; + CHAR trackername[8]; /* !Scream! for ST 2.xx */ + UBYTE unused; /* 0x1A */ + UBYTE filetype; /* 1=song, 2=module */ + UBYTE ver_major; + UBYTE ver_minor; + UBYTE inittempo; /* initspeed= stm inittempo>>4 */ + UBYTE numpat; /* number of patterns */ + UBYTE globalvol; + UBYTE reserved[13]; + STMSAMPLE sample[31]; /* STM sample data */ + UBYTE patorder[128]; /* Docs say 64 - actually 128 */ +} STMHEADER; + +typedef struct STMNOTE { + UBYTE note,insvol,volcmd,cmdinf; +} STMNOTE; + +/*========== Loader variables */ + +static STMNOTE *stmbuf = NULL; +static STMHEADER *mh = NULL; + +/* tracker identifiers */ +static CHAR* STM_Version[STM_NTRACKERS] = { + "Screamtracker 2", + "Converted by MOD2STM (STM format)", + "Wuzamod (STM format)" +}; + +/*========== Loader code */ + +BOOL STM_Test(void) +{ + UBYTE str[44]; + int t; + + _mm_fseek(modreader,20,SEEK_SET); + _mm_read_UBYTES(str,44,modreader); + if(str[9]!=2) return 0; /* STM Module = filetype 2 */ + + /* Prevent false positives for S3M files */ + if(!memcmp(str+40,"SCRM",4)) + return 0; + + for (t=0;t<STM_NTRACKERS;t++) + if(!memcmp(str,STM_Signatures[t],8)) + return 1; + + return 0; +} + +BOOL STM_Init(void) +{ + if(!(mh=(STMHEADER*)MikMod_malloc(sizeof(STMHEADER)))) return 0; + if(!(stmbuf=(STMNOTE*)MikMod_calloc(64U*4,sizeof(STMNOTE)))) return 0; + + return 1; +} + +static void STM_Cleanup(void) +{ + MikMod_free(mh); + MikMod_free(stmbuf); +} + +static void STM_ConvertNote(STMNOTE *n) +{ + UBYTE note,ins,vol,cmd,inf; + + /* extract the various information from the 4 bytes that make up a note */ + note = n->note; + ins = n->insvol>>3; + vol = (n->insvol&7)+((n->volcmd&0x70)>>1); + cmd = n->volcmd&15; + inf = n->cmdinf; + + if((ins)&&(ins<32)) UniInstrument(ins-1); + + /* special values of [SBYTE0] are handled here + we have no idea if these strange values will ever be encountered. + but it appears as those stms sound correct. */ + if((note==254)||(note==252)) { + UniPTEffect(0xc,0); /* note cut */ + n->volcmd|=0x80; + } else + /* if note < 251, then all three bytes are stored in the file */ + if(note<251) UniNote((((note>>4)+2)*OCTAVE)+(note&0xf)); + + if((!(n->volcmd&0x80))&&(vol<65)) UniPTEffect(0xc,vol); + if(cmd!=255) + switch(cmd) { + case 1: /* Axx set speed to xx */ + UniPTEffect(0xf,inf>>4); + break; + case 2: /* Bxx position jump */ + UniPTEffect(0xb,inf); + break; + case 3: /* Cxx patternbreak to row xx */ + UniPTEffect(0xd,(((inf&0xf0)>>4)*10)+(inf&0xf)); + break; + case 4: /* Dxy volumeslide */ + UniEffect(UNI_S3MEFFECTD,inf); + break; + case 5: /* Exy toneslide down */ + UniEffect(UNI_S3MEFFECTE,inf); + break; + case 6: /* Fxy toneslide up */ + UniEffect(UNI_S3MEFFECTF,inf); + break; + case 7: /* Gxx Tone portamento,speed xx */ + UniPTEffect(0x3,inf); + break; + case 8: /* Hxy vibrato */ + UniPTEffect(0x4,inf); + break; + case 9: /* Ixy tremor, ontime x, offtime y */ + UniEffect(UNI_S3MEFFECTI,inf); + break; + case 0: /* protracker arpeggio */ + if(!inf) break; + /* fall through */ + case 0xa: /* Jxy arpeggio */ + UniPTEffect(0x0,inf); + break; + case 0xb: /* Kxy Dual command H00 & Dxy */ + UniPTEffect(0x4,0); + UniEffect(UNI_S3MEFFECTD,inf); + break; + case 0xc: /* Lxy Dual command G00 & Dxy */ + UniPTEffect(0x3,0); + UniEffect(UNI_S3MEFFECTD,inf); + break; + /* Support all these above, since ST2 can LOAD these values but can + actually only play up to J - and J is only half-way implemented + in ST2 */ + case 0x18: /* Xxx amiga panning command 8xx */ + UniPTEffect(0x8,inf); + of.flags |= UF_PANNING; + break; + } +} + +static UBYTE *STM_ConvertTrack(STMNOTE *n) +{ + int t; + + UniReset(); + for(t=0;t<64;t++) { + STM_ConvertNote(n); + UniNewline(); + n+=of.numchn; + } + return UniDup(); +} + +static BOOL STM_LoadPatterns(void) +{ + int t,s,tracks=0; + + if(!AllocPatterns()) return 0; + if(!AllocTracks()) return 0; + + /* Allocate temporary buffer for loading and converting the patterns */ + for(t=0;t<of.numpat;t++) { + for(s=0;s<(64*of.numchn);s++) { + stmbuf[s].note = _mm_read_UBYTE(modreader); + stmbuf[s].insvol = _mm_read_UBYTE(modreader); + stmbuf[s].volcmd = _mm_read_UBYTE(modreader); + stmbuf[s].cmdinf = _mm_read_UBYTE(modreader); + } + + if(_mm_eof(modreader)) { + _mm_errno = MMERR_LOADING_PATTERN; + return 0; + } + + for(s=0;s<of.numchn;s++) + if(!(of.tracks[tracks++]=STM_ConvertTrack(stmbuf+s))) return 0; + } + return 1; +} + +BOOL STM_Load(BOOL curious) +{ + int t; + ULONG MikMod_ISA; /* We must generate our own ISA, it's not stored in stm */ + SAMPLE *q; + + (void)curious; /* unused arg */ + + /* try to read stm header */ + _mm_read_string(mh->songname,20,modreader); + _mm_read_string(mh->trackername,8,modreader); + mh->unused =_mm_read_UBYTE(modreader); + mh->filetype =_mm_read_UBYTE(modreader); + mh->ver_major =_mm_read_UBYTE(modreader); + mh->ver_minor =_mm_read_UBYTE(modreader); + mh->inittempo =_mm_read_UBYTE(modreader); + if(!mh->inittempo) { + _mm_errno=MMERR_NOT_A_MODULE; + return 0; + } + mh->numpat =_mm_read_UBYTE(modreader); + mh->globalvol =_mm_read_UBYTE(modreader); + _mm_read_UBYTES(mh->reserved,13,modreader); + + for(t=0;t<31;t++) { + STMSAMPLE *s=&mh->sample[t]; /* STM sample data */ + + _mm_read_string(s->filename,12,modreader); + s->unused =_mm_read_UBYTE(modreader); + s->instdisk =_mm_read_UBYTE(modreader); + s->reserved =_mm_read_I_UWORD(modreader); + s->length =_mm_read_I_UWORD(modreader); + s->loopbeg =_mm_read_I_UWORD(modreader); + s->loopend =_mm_read_I_UWORD(modreader); + s->volume =_mm_read_UBYTE(modreader); + s->reserved2=_mm_read_UBYTE(modreader); + s->c2spd =_mm_read_I_UWORD(modreader); + s->reserved3=_mm_read_I_ULONG(modreader); + s->isa =_mm_read_I_UWORD(modreader); + } + _mm_read_UBYTES(mh->patorder,128,modreader); + + if(_mm_eof(modreader)) { + _mm_errno = MMERR_LOADING_HEADER; + return 0; + } + + /* set module variables */ + for(t=0;t<STM_NTRACKERS;t++) + if(!memcmp(mh->trackername,STM_Signatures[t],8)) break; + of.modtype = strdup(STM_Version[t]); + of.songname = DupStr(mh->songname,20,1); /* make a cstr of songname */ + of.numpat = mh->numpat; + of.inittempo = 125; /* mh->inittempo+0x1c; */ + of.initspeed = mh->inittempo>>4; + of.numchn = 4; /* get number of channels */ + of.reppos = 0; + of.flags |= UF_S3MSLIDES; + of.bpmlimit = 32; + + t=0; + if(!AllocPositions(0x80)) return 0; + /* 99 terminates the patorder list */ + while((mh->patorder[t]<=99)&&(mh->patorder[t]<mh->numpat)) { + of.positions[t]=mh->patorder[t]; + t++; + } + if(mh->patorder[t]<=99) t++; + of.numpos=t; + of.numtrk=of.numpat*of.numchn; + of.numins=of.numsmp=31; + + if(!AllocSamples()) return 0; + if(!STM_LoadPatterns()) return 0; + MikMod_ISA=_mm_ftell(modreader); + MikMod_ISA=(MikMod_ISA+15)&0xfffffff0; /* normalize */ + + for(q=of.samples,t=0;t<of.numsmp;t++,q++) { + /* load sample info */ + q->samplename = DupStr(mh->sample[t].filename,12,1); + q->speed = (mh->sample[t].c2spd * 8363) / 8448; + q->volume = mh->sample[t].volume; + q->length = mh->sample[t].length; + if (/*(!mh->sample[t].volume)||*/(q->length==1)) q->length=0; + q->loopstart = mh->sample[t].loopbeg; + q->loopend = mh->sample[t].loopend; + q->seekpos = MikMod_ISA; + + MikMod_ISA+=q->length; + MikMod_ISA=(MikMod_ISA+15)&0xfffffff0; /* normalize */ + + /* contrary to the STM specs, sample data is signed */ + q->flags = SF_SIGNED; + + if(q->loopend && q->loopend != 0xffff) + q->flags|=SF_LOOP; + } + return 1; +} + +CHAR *STM_LoadTitle(void) +{ + CHAR s[20]; + + _mm_fseek(modreader,0,SEEK_SET); + if(!_mm_read_UBYTES(s,20,modreader)) return NULL; + + return(DupStr(s,20,1)); +} + +/*========== Loader information */ + +MIKMODAPI MLOADER load_stm={ + NULL, + "STM", + "STM (Scream Tracker)", + STM_Init, + STM_Test, + STM_Load, + STM_Cleanup, + STM_LoadTitle +}; + + +/* ex:set ts=4: */ diff --git a/src/libs/mikmod/load_xm.c b/src/libs/mikmod/load_xm.c new file mode 100644 index 0000000..97e0b73 --- /dev/null +++ b/src/libs/mikmod/load_xm.c @@ -0,0 +1,817 @@ +/* MikMod sound library + (c) 1998, 1999, 2000, 2001, 2002 Miodrag Vallat and others - see file + AUTHORS for complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + Fasttracker (XM) module loader + +==============================================================================*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include <stdio.h> +#ifdef HAVE_MEMORY_H +#include <memory.h> +#endif +#include <string.h> + +#include "mikmod_internals.h" + +#ifdef SUNOS +extern int fprintf(FILE *, const char *, ...); +#endif + +/*========== Module structure */ + +typedef struct XMHEADER { + CHAR id[17]; /* ID text: 'Extended module: ' */ + CHAR songname[21]; /* Module name */ + CHAR trackername[20]; /* Tracker name */ + UWORD version; /* Version number */ + ULONG headersize; /* Header size */ + UWORD songlength; /* Song length (in patten order table) */ + UWORD restart; /* Restart position */ + UWORD numchn; /* Number of channels (2,4,6,8,10,...,32) */ + UWORD numpat; /* Number of patterns (max 256) */ + UWORD numins; /* Number of instruments (max 128) */ + UWORD flags; + UWORD tempo; /* Default tempo */ + UWORD bpm; /* Default BPM */ + UBYTE orders[256]; /* Pattern order table */ +} XMHEADER; + +typedef struct XMINSTHEADER { + ULONG size; /* Instrument size */ + CHAR name[22]; /* Instrument name */ + UBYTE type; /* Instrument type (always 0) */ + UWORD numsmp; /* Number of samples in instrument */ + ULONG ssize; +} XMINSTHEADER; + +#define XMENVCNT (12*2) +#define XMNOTECNT (8*OCTAVE) +typedef struct XMPATCHHEADER { + UBYTE what[XMNOTECNT]; /* Sample number for all notes */ + UWORD volenv[XMENVCNT]; /* Points for volume envelope */ + UWORD panenv[XMENVCNT]; /* Points for panning envelope */ + UBYTE volpts; /* Number of volume points */ + UBYTE panpts; /* Number of panning points */ + UBYTE volsus; /* Volume sustain point */ + UBYTE volbeg; /* Volume loop start point */ + UBYTE volend; /* Volume loop end point */ + UBYTE pansus; /* Panning sustain point */ + UBYTE panbeg; /* Panning loop start point */ + UBYTE panend; /* Panning loop end point */ + UBYTE volflg; /* Volume type: bit 0: On; 1: Sustain; 2: Loop */ + UBYTE panflg; /* Panning type: bit 0: On; 1: Sustain; 2: Loop */ + UBYTE vibflg; /* Vibrato type */ + UBYTE vibsweep; /* Vibrato sweep */ + UBYTE vibdepth; /* Vibrato depth */ + UBYTE vibrate; /* Vibrato rate */ + UWORD volfade; /* Volume fadeout */ +} XMPATCHHEADER; + +typedef struct XMWAVHEADER { + ULONG length; /* Sample length */ + ULONG loopstart; /* Sample loop start */ + ULONG looplength; /* Sample loop length */ + UBYTE volume; /* Volume */ + SBYTE finetune; /* Finetune (signed byte -128..+127) */ + UBYTE type; /* Loop type */ + UBYTE panning; /* Panning (0-255) */ + SBYTE relnote; /* Relative note number (signed byte) */ + UBYTE reserved; + CHAR samplename[22]; /* Sample name */ + UBYTE vibtype; /* Vibrato type */ + UBYTE vibsweep; /* Vibrato sweep */ + UBYTE vibdepth; /* Vibrato depth */ + UBYTE vibrate; /* Vibrato rate */ +} XMWAVHEADER; + +typedef struct XMPATHEADER { + ULONG size; /* Pattern header length */ + UBYTE packing; /* Packing type (always 0) */ + UWORD numrows; /* Number of rows in pattern (1..256) */ + SWORD packsize; /* Packed patterndata size */ +} XMPATHEADER; + +typedef struct XMNOTE { + UBYTE note,ins,vol,eff,dat; +} XMNOTE; + +/*========== Loader variables */ + +static XMNOTE *xmpat=NULL; +static XMHEADER *mh=NULL; + +/* increment unit for sample array reallocation */ +#define XM_SMPINCR 64 +static ULONG *nextwav=NULL; +static XMWAVHEADER *wh=NULL,*s=NULL; + +/*========== Loader code */ + +BOOL XM_Test(void) +{ + UBYTE id[38]; + + if(!_mm_read_UBYTES(id,38,modreader)) return 0; + if(memcmp(id,"Extended Module: ",17)) return 0; + if(id[37]==0x1a) return 1; + return 0; +} + +BOOL XM_Init(void) +{ + if(!(mh=(XMHEADER *)MikMod_malloc(sizeof(XMHEADER)))) return 0; + return 1; +} + +void XM_Cleanup(void) +{ + MikMod_free(mh); +} + +static int XM_ReadNote(XMNOTE* n) +{ + UBYTE cmp,result=1; + + memset(n,0,sizeof(XMNOTE)); + cmp=_mm_read_UBYTE(modreader); + + if(cmp&0x80) { + if(cmp&1) { result++;n->note = _mm_read_UBYTE(modreader); } + if(cmp&2) { result++;n->ins = _mm_read_UBYTE(modreader); } + if(cmp&4) { result++;n->vol = _mm_read_UBYTE(modreader); } + if(cmp&8) { result++;n->eff = _mm_read_UBYTE(modreader); } + if(cmp&16) { result++;n->dat = _mm_read_UBYTE(modreader); } + } else { + n->note = cmp; + n->ins = _mm_read_UBYTE(modreader); + n->vol = _mm_read_UBYTE(modreader); + n->eff = _mm_read_UBYTE(modreader); + n->dat = _mm_read_UBYTE(modreader); + result += 4; + } + return result; +} + +static UBYTE* XM_Convert(XMNOTE* xmtrack,UWORD rows) +{ + int t; + UBYTE note,ins,vol,eff,dat; + + UniReset(); + for(t=0;t<rows;t++) { + note = xmtrack->note; + ins = xmtrack->ins; + vol = xmtrack->vol; + eff = xmtrack->eff; + dat = xmtrack->dat; + + if(note) { + if(note>XMNOTECNT) + UniEffect(UNI_KEYFADE,0); + else + UniNote(note-1); + } + if(ins) UniInstrument(ins-1); + + switch(vol>>4) { + case 0x6: /* volslide down */ + if(vol&0xf) UniEffect(UNI_XMEFFECTA,vol&0xf); + break; + case 0x7: /* volslide up */ + if(vol&0xf) UniEffect(UNI_XMEFFECTA,vol<<4); + break; + + /* volume-row fine volume slide is compatible with protracker + EBx and EAx effects i.e. a zero nibble means DO NOT SLIDE, as + opposed to 'take the last sliding value'. */ + case 0x8: /* finevol down */ + UniPTEffect(0xe,0xb0|(vol&0xf)); + break; + case 0x9: /* finevol up */ + UniPTEffect(0xe,0xa0|(vol&0xf)); + break; + case 0xa: /* set vibrato speed */ + UniEffect(UNI_XMEFFECT4,vol<<4); + break; + case 0xb: /* vibrato */ + UniEffect(UNI_XMEFFECT4,vol&0xf); + break; + case 0xc: /* set panning */ + UniPTEffect(0x8,vol<<4); + break; + case 0xd: /* panning slide left (only slide when data not zero) */ + if(vol&0xf) UniEffect(UNI_XMEFFECTP,vol&0xf); + break; + case 0xe: /* panning slide right (only slide when data not zero) */ + if(vol&0xf) UniEffect(UNI_XMEFFECTP,vol<<4); + break; + case 0xf: /* tone porta */ + UniPTEffect(0x3,vol<<4); + break; + default: + if((vol>=0x10)&&(vol<=0x50)) + UniPTEffect(0xc,vol-0x10); + } + + switch(eff) { + case 0x4: + UniEffect(UNI_XMEFFECT4,dat); + break; + case 0x6: + UniEffect(UNI_XMEFFECT6,dat); + break; + case 0xa: + UniEffect(UNI_XMEFFECTA,dat); + break; + case 0xe: /* Extended effects */ + switch(dat>>4) { + case 0x1: /* XM fine porta up */ + UniEffect(UNI_XMEFFECTE1,dat&0xf); + break; + case 0x2: /* XM fine porta down */ + UniEffect(UNI_XMEFFECTE2,dat&0xf); + break; + case 0xa: /* XM fine volume up */ + UniEffect(UNI_XMEFFECTEA,dat&0xf); + break; + case 0xb: /* XM fine volume down */ + UniEffect(UNI_XMEFFECTEB,dat&0xf); + break; + default: + UniPTEffect(eff,dat); + } + break; + case 'G'-55: /* G - set global volume */ + UniEffect(UNI_XMEFFECTG,dat>64?128:dat<<1); + break; + case 'H'-55: /* H - global volume slide */ + UniEffect(UNI_XMEFFECTH,dat); + break; + case 'K'-55: /* K - keyOff and KeyFade */ + UniEffect(UNI_KEYFADE,dat); + break; + case 'L'-55: /* L - set envelope position */ + UniEffect(UNI_XMEFFECTL,dat); + break; + case 'P'-55: /* P - panning slide */ + UniEffect(UNI_XMEFFECTP,dat); + break; + case 'R'-55: /* R - multi retrig note */ + UniEffect(UNI_S3MEFFECTQ,dat); + break; + case 'T'-55: /* T - Tremor */ + UniEffect(UNI_S3MEFFECTI,dat); + break; + case 'X'-55: + switch(dat>>4) { + case 1: /* X1 - Extra Fine Porta up */ + UniEffect(UNI_XMEFFECTX1,dat&0xf); + break; + case 2: /* X2 - Extra Fine Porta down */ + UniEffect(UNI_XMEFFECTX2,dat&0xf); + break; + } + break; + default: + if(eff<=0xf) { + /* the pattern jump destination is written in decimal, + but it seems some poor tracker software writes them + in hexadecimal... (sigh) */ + if (eff==0xd) + /* don't change anything if we're sure it's in hexa */ + if ((((dat&0xf0)>>4)<=9)&&((dat&0xf)<=9)) + /* otherwise, convert from dec to hex */ + dat=(((dat&0xf0)>>4)*10)+(dat&0xf); + UniPTEffect(eff,dat); + } + break; + } + UniNewline(); + xmtrack++; + } + return UniDup(); +} + +static BOOL LoadPatterns(BOOL dummypat) +{ + int t,u,v,numtrk; + + if(!AllocTracks()) return 0; + if(!AllocPatterns()) return 0; + + numtrk=0; + for(t=0;t<mh->numpat;t++) { + XMPATHEADER ph; + + ph.size =_mm_read_I_ULONG(modreader); + if (ph.size<(mh->version==0x0102?8:9)) { + _mm_errno=MMERR_LOADING_PATTERN; + return 0; + } + ph.packing =_mm_read_UBYTE(modreader); + if(ph.packing) { + _mm_errno=MMERR_LOADING_PATTERN; + return 0; + } + if(mh->version==0x0102) + ph.numrows =_mm_read_UBYTE(modreader)+1; + else + ph.numrows =_mm_read_I_UWORD(modreader); + ph.packsize =_mm_read_I_UWORD(modreader); + + ph.size-=(mh->version==0x0102?8:9); + if(ph.size) + _mm_fseek(modreader,ph.size,SEEK_CUR); + + of.pattrows[t]=ph.numrows; + + if(ph.numrows) { + if(!(xmpat=(XMNOTE*)MikMod_calloc(ph.numrows*of.numchn,sizeof(XMNOTE)))) + return 0; + + /* when packsize is 0, don't try to load a pattern.. it's empty. */ + if(ph.packsize) + for(u=0;u<ph.numrows;u++) + for(v=0;v<of.numchn;v++) { + if(!ph.packsize) break; + + ph.packsize-=XM_ReadNote(&xmpat[(v*ph.numrows)+u]); + if(ph.packsize<0) { + MikMod_free(xmpat);xmpat=NULL; + _mm_errno=MMERR_LOADING_PATTERN; + return 0; + } + } + + if(ph.packsize) { + _mm_fseek(modreader,ph.packsize,SEEK_CUR); + } + + if(_mm_eof(modreader)) { + MikMod_free(xmpat);xmpat=NULL; + _mm_errno=MMERR_LOADING_PATTERN; + return 0; + } + + for(v=0;v<of.numchn;v++) + of.tracks[numtrk++]=XM_Convert(&xmpat[v*ph.numrows],ph.numrows); + + MikMod_free(xmpat);xmpat=NULL; + } else { + for(v=0;v<of.numchn;v++) + of.tracks[numtrk++]=XM_Convert(NULL,ph.numrows); + } + } + + if(dummypat) { + of.pattrows[t]=64; + if(!(xmpat=(XMNOTE*)MikMod_calloc(64*of.numchn,sizeof(XMNOTE)))) return 0; + for(v=0;v<of.numchn;v++) + of.tracks[numtrk++]=XM_Convert(&xmpat[v*64],64); + MikMod_free(xmpat);xmpat=NULL; + } + + return 1; +} + +static void FixEnvelope(ENVPT *cur, int pts) +{ + int u, old, tmp; + ENVPT *prev; + + /* Some broken XM editing program will only save the low byte + of the position value. Try to compensate by adding the + missing high byte. */ + + prev = cur++; + old = prev->pos; + + for (u = 1; u < pts; u++, prev++, cur++) { + if (cur->pos < prev->pos) { + if (cur->pos < 0x100) { + if (cur->pos > old) /* same hex century */ + tmp = cur->pos + (prev->pos - old); + else + tmp = cur->pos | ((prev->pos + 0x100) & 0xff00); + old = cur->pos; + cur->pos = tmp; +#ifdef MIKMOD_DEBUG + fprintf(stderr, "\rbroken envelope position(%d/%d), %d %d -> %d\n", + u, pts, prev->pos, old, cur->pos); +#endif + } else { +#ifdef MIKMOD_DEBUG + /* different brokenness style... fix unknown */ + fprintf(stderr, "\rbroken envelope position(%d/%d), %d %d\n", + u, pts, old, cur->pos); +#endif + old = cur->pos; + } + } else + old = cur->pos; + } +} + +static BOOL LoadInstruments(void) +{ + int t,u; + INSTRUMENT *d; + ULONG next=0; + UWORD wavcnt=0; + + if(!AllocInstruments()) return 0; + d=of.instruments; + for(t=0;t<of.numins;t++,d++) { + XMINSTHEADER ih; + long headend; + + memset(d->samplenumber,0xff,INSTNOTES*sizeof(UWORD)); + + /* read instrument header */ + headend = _mm_ftell(modreader); + ih.size = _mm_read_I_ULONG(modreader); + headend += ih.size; + _mm_read_string(ih.name, 22, modreader); + ih.type = _mm_read_UBYTE(modreader); + ih.numsmp = _mm_read_I_UWORD(modreader); + + d->insname = DupStr(ih.name,22,1); + + if((SWORD)ih.size>29) { + ih.ssize = _mm_read_I_ULONG(modreader); + if(((SWORD)ih.numsmp>0)&&(ih.numsmp<=XMNOTECNT)) { + XMPATCHHEADER pth; + int p; + + _mm_read_UBYTES (pth.what,XMNOTECNT,modreader); + _mm_read_I_UWORDS (pth.volenv, XMENVCNT, modreader); + _mm_read_I_UWORDS (pth.panenv, XMENVCNT, modreader); + pth.volpts = _mm_read_UBYTE(modreader); + pth.panpts = _mm_read_UBYTE(modreader); + pth.volsus = _mm_read_UBYTE(modreader); + pth.volbeg = _mm_read_UBYTE(modreader); + pth.volend = _mm_read_UBYTE(modreader); + pth.pansus = _mm_read_UBYTE(modreader); + pth.panbeg = _mm_read_UBYTE(modreader); + pth.panend = _mm_read_UBYTE(modreader); + pth.volflg = _mm_read_UBYTE(modreader); + pth.panflg = _mm_read_UBYTE(modreader); + pth.vibflg = _mm_read_UBYTE(modreader); + pth.vibsweep = _mm_read_UBYTE(modreader); + pth.vibdepth = _mm_read_UBYTE(modreader); + pth.vibrate = _mm_read_UBYTE(modreader); + pth.volfade = _mm_read_I_UWORD(modreader); + + /* read the remainder of the header + (2 bytes for 1.03, 22 for 1.04) */ + for(u=headend-_mm_ftell(modreader);u;u--) _mm_read_UBYTE(modreader); + + /* we can't trust the envelope point count here, as some + modules have incorrect values (K_OSPACE.XM reports 32 volume + points, for example). */ + if(pth.volpts>XMENVCNT/2) pth.volpts=XMENVCNT/2; + if(pth.panpts>XMENVCNT/2) pth.panpts=XMENVCNT/2; + + if((_mm_eof(modreader))||(pth.volpts>XMENVCNT/2)||(pth.panpts>XMENVCNT/2)) { + if(nextwav) { MikMod_free(nextwav);nextwav=NULL; } + if(wh) { MikMod_free(wh);wh=NULL; } + _mm_errno = MMERR_LOADING_SAMPLEINFO; + return 0; + } + + for(u=0;u<XMNOTECNT;u++) + d->samplenumber[u]=pth.what[u]+of.numsmp; + d->volfade = pth.volfade; + +#if defined __STDC__ || defined _MSC_VER || defined MPW_C +#define XM_ProcessEnvelope(name) \ + for (u = 0; u < (XMENVCNT >> 1); u++) { \ + d-> name##env[u].pos = pth. name##env[u << 1]; \ + d-> name##env[u].val = pth. name##env[(u << 1)+ 1]; \ + } \ + if (pth. name##flg&1) d-> name##flg|=EF_ON; \ + if (pth. name##flg&2) d-> name##flg|=EF_SUSTAIN; \ + if (pth. name##flg&4) d-> name##flg|=EF_LOOP; \ + d-> name##susbeg=d-> name##susend=pth. name##sus; \ + d-> name##beg=pth. name##beg; \ + d-> name##end=pth. name##end; \ + d-> name##pts=pth. name##pts; \ + \ + /* scale envelope */ \ + for (p=0;p<XMENVCNT/2;p++) \ + d-> name##env[p].val<<=2; \ + \ + if ((d-> name##flg&EF_ON)&&(d-> name##pts<2)) \ + d-> name##flg&=~EF_ON +#else +#define XM_ProcessEnvelope(name) \ + for (u = 0; u < (XMENVCNT >> 1); u++) { \ + d-> name/**/env[u].pos = pth. name/**/env[u << 1]; \ + d-> name/**/env[u].val = pth. name/**/env[(u << 1)+ 1]; \ + } \ + if (pth. name/**/flg&1) d-> name/**/flg|=EF_ON; \ + if (pth. name/**/flg&2) d-> name/**/flg|=EF_SUSTAIN; \ + if (pth. name/**/flg&4) d-> name/**/flg|=EF_LOOP; \ + d-> name/**/susbeg=d-> name/**/susend= \ + pth. name/**/sus; \ + d-> name/**/beg=pth. name/**/beg; \ + d-> name/**/end=pth. name/**/end; \ + d-> name/**/pts=pth. name/**/pts; \ + \ + /* scale envelope */ \ + for (p=0;p<XMENVCNT/2;p++) \ + d-> name/**/env[p].val<<=2; \ + \ + if ((d-> name/**/flg&EF_ON)&&(d-> name/**/pts<2)) \ + d-> name/**/flg&=~EF_ON +#endif + + XM_ProcessEnvelope(vol); + XM_ProcessEnvelope(pan); +#undef XM_ProcessEnvelope + + if (d->volflg & EF_ON) + FixEnvelope(d->volenv, d->volpts); + if (d->panflg & EF_ON) + FixEnvelope(d->panenv, d->panpts); + + /* Samples are stored outside the instrument struct now, so we + have to load them all into a temp area, count the of.numsmp + along the way and then do an AllocSamples() and move + everything over */ + if(mh->version>0x0103) next = 0; + for(u=0;u<ih.numsmp;u++,s++) { + /* Allocate more room for sample information if necessary */ + if(of.numsmp+u==wavcnt) { + wavcnt+=XM_SMPINCR; + if(!(nextwav=MikMod_realloc(nextwav,wavcnt*sizeof(ULONG)))){ + if(wh) { MikMod_free(wh);wh=NULL; } + _mm_errno = MMERR_OUT_OF_MEMORY; + return 0; + } + if(!(wh=MikMod_realloc(wh,wavcnt*sizeof(XMWAVHEADER)))) { + MikMod_free(nextwav);nextwav=NULL; + _mm_errno = MMERR_OUT_OF_MEMORY; + return 0; + } + s=wh+(wavcnt-XM_SMPINCR); + } + + s->length =_mm_read_I_ULONG (modreader); + s->loopstart =_mm_read_I_ULONG (modreader); + s->looplength =_mm_read_I_ULONG (modreader); + s->volume =_mm_read_UBYTE (modreader); + s->finetune =_mm_read_SBYTE (modreader); + s->type =_mm_read_UBYTE (modreader); + s->panning =_mm_read_UBYTE (modreader); + s->relnote =_mm_read_SBYTE (modreader); + s->vibtype = pth.vibflg; + s->vibsweep = pth.vibsweep; + s->vibdepth = pth.vibdepth*4; + s->vibrate = pth.vibrate; + s->reserved =_mm_read_UBYTE (modreader); + _mm_read_string(s->samplename, 22, modreader); + + nextwav[of.numsmp+u]=next; + next+=s->length; + + if(_mm_eof(modreader)) { + MikMod_free(nextwav);MikMod_free(wh); + nextwav=NULL;wh=NULL; + _mm_errno = MMERR_LOADING_SAMPLEINFO; + return 0; + } + } + + if(mh->version>0x0103) { + for(u=0;u<ih.numsmp;u++) + nextwav[of.numsmp++]+=_mm_ftell(modreader); + _mm_fseek(modreader,next,SEEK_CUR); + } else + of.numsmp+=ih.numsmp; + } else { + /* read the remainder of the header */ + for(u=headend-_mm_ftell(modreader);u;u--) _mm_read_UBYTE(modreader); + + if(_mm_eof(modreader)) { + MikMod_free(nextwav);MikMod_free(wh); + nextwav=NULL;wh=NULL; + _mm_errno = MMERR_LOADING_SAMPLEINFO; + return 0; + } + } + } + } + + /* sanity check */ + if(!of.numsmp) { + if(nextwav) { MikMod_free(nextwav);nextwav=NULL; } + if(wh) { MikMod_free(wh);wh=NULL; } + _mm_errno = MMERR_LOADING_SAMPLEINFO; + return 0; + } + + return 1; +} + +BOOL XM_Load(BOOL curious) +{ + INSTRUMENT *d; + SAMPLE *q; + int t,u; + BOOL dummypat=0; + char tracker[21],modtype[60]; + + (void)curious; /* unused arg */ + + /* try to read module header */ + _mm_read_string(mh->id,17,modreader); + _mm_read_string(mh->songname,21,modreader); + _mm_read_string(mh->trackername,20,modreader); + mh->version =_mm_read_I_UWORD(modreader); + if((mh->version<0x102)||(mh->version>0x104)) { + _mm_errno=MMERR_NOT_A_MODULE; + return 0; + } + mh->headersize =_mm_read_I_ULONG(modreader); + mh->songlength =_mm_read_I_UWORD(modreader); + mh->restart =_mm_read_I_UWORD(modreader); + mh->numchn =_mm_read_I_UWORD(modreader); + mh->numpat =_mm_read_I_UWORD(modreader); + mh->numins =_mm_read_I_UWORD(modreader); + mh->flags =_mm_read_I_UWORD(modreader); + mh->tempo =_mm_read_I_UWORD(modreader); + mh->bpm =_mm_read_I_UWORD(modreader); + if(!mh->bpm) { + _mm_errno=MMERR_NOT_A_MODULE; + return 0; + } + _mm_read_UBYTES(mh->orders,256,modreader); + + if(_mm_eof(modreader)) { + _mm_errno = MMERR_LOADING_HEADER; + return 0; + } + + /* set module variables */ + of.initspeed = mh->tempo; + of.inittempo = mh->bpm; + strncpy(tracker,mh->trackername,20);tracker[20]=0; + for(t=20;(tracker[t]<=' ')&&(t>=0);t--) tracker[t]=0; + + /* some modules have the tracker name empty */ + if (!tracker[0]) + strcpy(tracker,"Unknown tracker"); + +#ifdef HAVE_SNPRINTF + snprintf(modtype,60,"%s (XM format %d.%02d)", + tracker,mh->version>>8,mh->version&0xff); +#else + sprintf(modtype,"%s (XM format %d.%02d)", + tracker,mh->version>>8,mh->version&0xff); +#endif + of.modtype = strdup(modtype); + of.numchn = mh->numchn; + of.numpat = mh->numpat; + of.numtrk = (UWORD)of.numpat*of.numchn; /* get number of channels */ + of.songname = DupStr(mh->songname,20,1); + of.numpos = mh->songlength; /* copy the songlength */ + of.reppos = mh->restart<mh->songlength?mh->restart:0; + of.numins = mh->numins; + of.flags |= UF_XMPERIODS | UF_INST | UF_NOWRAP | UF_FT2QUIRKS | + UF_PANNING; + if(mh->flags&1) of.flags |= UF_LINEAR; + of.bpmlimit = 32; + + memset(of.chanvol,64,of.numchn); /* store channel volumes */ + + if(!AllocPositions(of.numpos+1)) return 0; + for(t=0;t<of.numpos;t++) + of.positions[t]=mh->orders[t]; + + /* We have to check for any pattern numbers in the order list greater than + the number of patterns total. If one or more is found, we set it equal to + the pattern total and make a dummy pattern to workaround the problem */ + for(t=0;t<of.numpos;t++) { + if(of.positions[t]>=of.numpat) { + of.positions[t]=of.numpat; + dummypat=1; + } + } + if(dummypat) { + of.numpat++;of.numtrk+=of.numchn; + } + + if(mh->version<0x0104) { + if(!LoadInstruments()) return 0; + if(!LoadPatterns(dummypat)) return 0; + for(t=0;t<of.numsmp;t++) + nextwav[t]+=_mm_ftell(modreader); + } else { + if(!LoadPatterns(dummypat)) return 0; + if(!LoadInstruments()) return 0; + } + + if(!AllocSamples()) { + MikMod_free(nextwav);MikMod_free(wh); + nextwav=NULL;wh=NULL; + return 0; + } + q = of.samples; + s = wh; + for(u=0;u<of.numsmp;u++,q++,s++) { + q->samplename = DupStr(s->samplename,22,1); + q->length = s->length; + q->loopstart = s->loopstart; + q->loopend = s->loopstart+s->looplength; + q->volume = s->volume; + q->speed = s->finetune+128; + q->panning = s->panning; + q->seekpos = nextwav[u]; + q->vibtype = s->vibtype; + q->vibsweep = s->vibsweep; + q->vibdepth = s->vibdepth; + q->vibrate = s->vibrate; + + if(s->type & 0x10) { + q->length >>= 1; + q->loopstart >>= 1; + q->loopend >>= 1; + } + + q->flags|=SF_OWNPAN|SF_DELTA|SF_SIGNED; + if(s->type&0x3) q->flags|=SF_LOOP; + if(s->type&0x2) q->flags|=SF_BIDI; + if(s->type&0x10) q->flags|=SF_16BITS; + } + + d=of.instruments; + s=wh; + for(u=0;u<of.numins;u++,d++) + for(t=0;t<XMNOTECNT;t++) { + if (d->samplenumber[t]>=of.numsmp) + d->samplenote[t]=255; + else { + int note=t+s[d->samplenumber[t]].relnote; + d->samplenote[t]=(note<0)?0:note; + } + } + + MikMod_free(wh);MikMod_free(nextwav); + wh=NULL;nextwav=NULL; + return 1; +} + +CHAR *XM_LoadTitle(void) +{ + CHAR s[21]; + + _mm_fseek(modreader,17,SEEK_SET); + if(!_mm_read_UBYTES(s,21,modreader)) return NULL; + + return(DupStr(s,21,1)); +} + +/*========== Loader information */ + +MIKMODAPI MLOADER load_xm={ + NULL, + "XM", + "XM (FastTracker 2)", + XM_Init, + XM_Test, + XM_Load, + XM_Cleanup, + XM_LoadTitle +}; + +/* ex:set ts=4: */ diff --git a/src/libs/mikmod/mdreg.c b/src/libs/mikmod/mdreg.c new file mode 100644 index 0000000..2ad65c2 --- /dev/null +++ b/src/libs/mikmod/mdreg.c @@ -0,0 +1,47 @@ +/* MikMod sound library + (c) 1998, 1999 Miodrag Vallat and others - see file AUTHORS for + complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + Routine for registering all drivers in libmikmod for the current platform. + +==============================================================================*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "mikmod_internals.h" + +void _mm_registeralldrivers(void) +{ + _mm_registerdriver(&drv_nos); +} + +void MikMod_RegisterAllDrivers(void) +{ + MUTEX_LOCK(lists); + _mm_registeralldrivers(); + MUTEX_UNLOCK(lists); +} + +/* ex:set ts=4: */ diff --git a/src/libs/mikmod/mdriver.c b/src/libs/mikmod/mdriver.c new file mode 100644 index 0000000..68a2e79 --- /dev/null +++ b/src/libs/mikmod/mdriver.c @@ -0,0 +1,935 @@ +/* MikMod sound library + (c) 1998, 1999, 2000, 2001 Miodrag Vallat and others - see file AUTHORS + for complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + These routines are used to access the available soundcard drivers. + +==============================================================================*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#if defined unix || (defined __APPLE__ && defined __MACH__) +#include <pwd.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/types.h> +#endif + +#include <string.h> +#ifdef HAVE_STRINGS_H +#include <strings.h> +#endif + +#include "mikmod_internals.h" + +#ifdef SUNOS +extern int fprintf(FILE *, const char *, ...); +#endif + +static MDRIVER *firstdriver=NULL; +MIKMODAPI MDRIVER *md_driver=NULL; +extern MODULE *pf; /* modfile being played */ + +/* Initial global settings */ +MIKMODAPI UWORD md_device = 0; /* autodetect */ +MIKMODAPI UWORD md_mixfreq = 44100; +MIKMODAPI UWORD md_mode = DMODE_STEREO | DMODE_16BITS | + DMODE_SURROUND |DMODE_SOFT_MUSIC | + DMODE_SOFT_SNDFX; +MIKMODAPI UBYTE md_pansep = 128; /* 128 == 100% (full left/right) */ +MIKMODAPI UBYTE md_reverb = 0; /* no reverb */ +MIKMODAPI UBYTE md_volume = 128; /* global sound volume (0-128) */ +MIKMODAPI UBYTE md_musicvolume = 128; /* volume of song */ +MIKMODAPI UBYTE md_sndfxvolume = 128; /* volume of sound effects */ + UWORD md_bpm = 125; /* tempo */ + +/* Do not modify the numchn variables yourself! use MD_SetVoices() */ + UBYTE md_numchn=0,md_sngchn=0,md_sfxchn=0; + UBYTE md_hardchn=0,md_softchn=0; + + void (*md_player)(void) = Player_HandleTick; +static BOOL isplaying=0, initialized = 0; +static UBYTE *sfxinfo; +static int sfxpool; + +static SAMPLE **md_sample = NULL; + +/* Previous driver in use */ +static SWORD olddevice = -1; + +/* Limits the number of hardware voices to the specified amount. + This function should only be used by the low-level drivers. */ +static void LimitHardVoices(int limit) +{ + int t=0; + + if (!(md_mode & DMODE_SOFT_SNDFX) && (md_sfxchn>limit)) md_sfxchn=limit; + if (!(md_mode & DMODE_SOFT_MUSIC) && (md_sngchn>limit)) md_sngchn=limit; + + if (!(md_mode & DMODE_SOFT_SNDFX)) + md_hardchn=md_sfxchn; + else + md_hardchn=0; + + if (!(md_mode & DMODE_SOFT_MUSIC)) md_hardchn += md_sngchn; + + while (md_hardchn>limit) { + if (++t & 1) { + if (!(md_mode & DMODE_SOFT_SNDFX) && (md_sfxchn>4)) md_sfxchn--; + } else { + if (!(md_mode & DMODE_SOFT_MUSIC) && (md_sngchn>8)) md_sngchn--; + } + + if (!(md_mode & DMODE_SOFT_SNDFX)) + md_hardchn=md_sfxchn; + else + md_hardchn=0; + + if (!(md_mode & DMODE_SOFT_MUSIC)) + md_hardchn+=md_sngchn; + } + md_numchn=md_hardchn+md_softchn; +} + +/* Limits the number of hardware voices to the specified amount. + This function should only be used by the low-level drivers. */ +static void LimitSoftVoices(int limit) +{ + int t=0; + + if ((md_mode & DMODE_SOFT_SNDFX) && (md_sfxchn>limit)) md_sfxchn=limit; + if ((md_mode & DMODE_SOFT_MUSIC) && (md_sngchn>limit)) md_sngchn=limit; + + if (md_mode & DMODE_SOFT_SNDFX) + md_softchn=md_sfxchn; + else + md_softchn=0; + + if (md_mode & DMODE_SOFT_MUSIC) md_softchn+=md_sngchn; + + while (md_softchn>limit) { + if (++t & 1) { + if ((md_mode & DMODE_SOFT_SNDFX) && (md_sfxchn>4)) md_sfxchn--; + } else { + if ((md_mode & DMODE_SOFT_MUSIC) && (md_sngchn>8)) md_sngchn--; + } + + if (!(md_mode & DMODE_SOFT_SNDFX)) + md_softchn=md_sfxchn; + else + md_softchn=0; + + if (!(md_mode & DMODE_SOFT_MUSIC)) + md_softchn+=md_sngchn; + } + md_numchn=md_hardchn+md_softchn; +} + +/* Note: 'type' indicates whether the returned value should be for music or for + sound effects. */ +ULONG MD_SampleSpace(int type) +{ + if(type==MD_MUSIC) + type=(md_mode & DMODE_SOFT_MUSIC)?MD_SOFTWARE:MD_HARDWARE; + else if(type==MD_SNDFX) + type=(md_mode & DMODE_SOFT_SNDFX)?MD_SOFTWARE:MD_HARDWARE; + + return md_driver->FreeSampleSpace(type); +} + +ULONG MD_SampleLength(int type,SAMPLE* s) +{ + if(type==MD_MUSIC) + type=(md_mode & DMODE_SOFT_MUSIC)?MD_SOFTWARE:MD_HARDWARE; + else + if(type==MD_SNDFX) + type=(md_mode & DMODE_SOFT_SNDFX)?MD_SOFTWARE:MD_HARDWARE; + + return md_driver->RealSampleLength(type,s); +} + +MIKMODAPI CHAR* MikMod_InfoDriver(void) +{ + int t; + size_t len=0; + MDRIVER *l; + CHAR *list=NULL; + + MUTEX_LOCK(lists); + /* compute size of buffer */ + for(l=firstdriver;l;l=l->next) + len+=4+(l->next?1:0)+strlen(l->Version); + + if(len) + if((list=MikMod_malloc(len*sizeof(CHAR)))) { + list[0]=0; + /* list all registered device drivers : */ + for(t=1,l=firstdriver;l;l=l->next,t++) + sprintf(list,(l->next)?"%s%2d %s\n":"%s%2d %s", + list,t,l->Version); + } + MUTEX_UNLOCK(lists); + return list; +} + +void _mm_registerdriver(struct MDRIVER* drv) +{ + MDRIVER *cruise = firstdriver; + + /* don't register a MISSING() driver */ + if ((drv->Name) && (drv->Version)) { + if (cruise) { + while (cruise->next) cruise = cruise->next; + cruise->next = drv; + } else + firstdriver = drv; + } +} + +MIKMODAPI void MikMod_RegisterDriver(struct MDRIVER* drv) +{ + /* if we try to register an invalid driver, or an already registered driver, + ignore this attempt */ + if ((!drv)||(drv->next)||(!drv->Name)) + return; + + MUTEX_LOCK(lists); + _mm_registerdriver(drv); + MUTEX_UNLOCK(lists); +} + +MIKMODAPI int MikMod_DriverFromAlias(CHAR *alias) +{ + int rank=1; + MDRIVER *cruise; + + MUTEX_LOCK(lists); + cruise=firstdriver; + while(cruise) { + if (cruise->Alias) { + if (!(strcasecmp(alias,cruise->Alias))) break; + rank++; + } + cruise=cruise->next; + } + if(!cruise) rank=0; + MUTEX_UNLOCK(lists); + + return rank; +} + +SWORD MD_SampleLoad(SAMPLOAD* s, int type) +{ + SWORD result; + + if(type==MD_MUSIC) + type=(md_mode & DMODE_SOFT_MUSIC)?MD_SOFTWARE:MD_HARDWARE; + else if(type==MD_SNDFX) + type=(md_mode & DMODE_SOFT_SNDFX)?MD_SOFTWARE:MD_HARDWARE; + + SL_Init(s); + result=md_driver->SampleLoad(s,type); + SL_Exit(s); + + return result; +} + +void MD_SampleUnload(SWORD handle) +{ + md_driver->SampleUnload(handle); +} + +MIKMODAPI MikMod_player_t MikMod_RegisterPlayer(MikMod_player_t player) +{ + MikMod_player_t result; + + MUTEX_LOCK(vars); + result=md_player; + md_player=player; + MUTEX_UNLOCK(vars); + + return result; +} + +MIKMODAPI void MikMod_Update(void) +{ + MUTEX_LOCK(vars); + if(isplaying) { + if((!pf)||(!pf->forbid)) + md_driver->Update(); + else { + if (md_driver->Pause) + md_driver->Pause(); + } + } + MUTEX_UNLOCK(vars); +} + +void Voice_SetVolume_internal(SBYTE voice,UWORD vol) +{ + ULONG tmp; + + if((voice<0)||(voice>=md_numchn)) return; + + /* range checks */ + if(md_musicvolume>128) md_musicvolume=128; + if(md_sndfxvolume>128) md_sndfxvolume=128; + if(md_volume>128) md_volume=128; + + tmp=(ULONG)vol*(ULONG)md_volume* + ((voice<md_sngchn)?(ULONG)md_musicvolume:(ULONG)md_sndfxvolume); + md_driver->VoiceSetVolume(voice,tmp/16384UL); +} + +MIKMODAPI void Voice_SetVolume(SBYTE voice,UWORD vol) +{ + MUTEX_LOCK(vars); + Voice_SetVolume_internal(voice,vol); + MUTEX_UNLOCK(vars); +} + +MIKMODAPI UWORD Voice_GetVolume(SBYTE voice) +{ + UWORD result=0; + + MUTEX_LOCK(vars); + if((voice>=0)&&(voice<md_numchn)) + result=md_driver->VoiceGetVolume(voice); + MUTEX_UNLOCK(vars); + + return result; +} + +void Voice_SetFrequency_internal(SBYTE voice,ULONG frq) +{ + if((voice<0)||(voice>=md_numchn)) return; + if((md_sample[voice])&&(md_sample[voice]->divfactor)) + frq/=md_sample[voice]->divfactor; + md_driver->VoiceSetFrequency(voice,frq); +} + +MIKMODAPI void Voice_SetFrequency(SBYTE voice,ULONG frq) +{ + MUTEX_LOCK(vars); + Voice_SetFrequency_internal(voice,frq); + MUTEX_UNLOCK(vars); +} + +MIKMODAPI ULONG Voice_GetFrequency(SBYTE voice) +{ + ULONG result=0; + + MUTEX_LOCK(vars); + if((voice>=0)&&(voice<md_numchn)) + result=md_driver->VoiceGetFrequency(voice); + MUTEX_UNLOCK(vars); + + return result; +} + +void Voice_SetPanning_internal(SBYTE voice,ULONG pan) +{ + if((voice<0)||(voice>=md_numchn)) return; + if(pan!=PAN_SURROUND) { + if(md_pansep>128) md_pansep=128; + if(md_mode & DMODE_REVERSE) pan=255-pan; + pan = (((SWORD)(pan-128)*md_pansep)/128)+128; + } + md_driver->VoiceSetPanning(voice, pan); +} + +MIKMODAPI void Voice_SetPanning(SBYTE voice,ULONG pan) +{ +#ifdef MIKMOD_DEBUG + if((pan!=PAN_SURROUND)&&((pan<0)||(pan>255))) + fprintf(stderr,"\rVoice_SetPanning called with pan=%ld\n",(long)pan); +#endif + + MUTEX_LOCK(vars); + Voice_SetPanning_internal(voice,pan); + MUTEX_UNLOCK(vars); +} + +MIKMODAPI ULONG Voice_GetPanning(SBYTE voice) +{ + ULONG result=PAN_CENTER; + + MUTEX_LOCK(vars); + if((voice>=0)&&(voice<md_numchn)) + result=md_driver->VoiceGetPanning(voice); + MUTEX_UNLOCK(vars); + + return result; +} + +void Voice_Play_internal(SBYTE voice,SAMPLE* s,ULONG start) +{ + ULONG repend; + + if((voice<0)||(voice>=md_numchn)) return; + + md_sample[voice]=s; + repend=s->loopend; + + if(s->flags&SF_LOOP) + /* repend can't be bigger than size */ + if(repend>s->length) repend=s->length; + + md_driver->VoicePlay(voice,s->handle,start,s->length,s->loopstart,repend,s->flags); +} + +MIKMODAPI void Voice_Play(SBYTE voice,SAMPLE* s,ULONG start) +{ + if(start>s->length) return; + + MUTEX_LOCK(vars); + Voice_Play_internal(voice,s,start); + MUTEX_UNLOCK(vars); +} + +void Voice_Stop_internal(SBYTE voice) +{ + if((voice<0)||(voice>=md_numchn)) return; + if(voice>=md_sngchn) + /* It is a sound effects channel, so flag the voice as non-critical! */ + sfxinfo[voice-md_sngchn]=0; + md_driver->VoiceStop(voice); +} + +MIKMODAPI void Voice_Stop(SBYTE voice) +{ + MUTEX_LOCK(vars); + Voice_Stop_internal(voice); + MUTEX_UNLOCK(vars); +} + +BOOL Voice_Stopped_internal(SBYTE voice) +{ + if((voice<0)||(voice>=md_numchn)) return 0; + return(md_driver->VoiceStopped(voice)); +} + +MIKMODAPI BOOL Voice_Stopped(SBYTE voice) +{ + BOOL result; + + MUTEX_LOCK(vars); + result=Voice_Stopped_internal(voice); + MUTEX_UNLOCK(vars); + + return result; +} + +MIKMODAPI SLONG Voice_GetPosition(SBYTE voice) +{ + SLONG result=0; + + MUTEX_LOCK(vars); + if((voice>=0)&&(voice<md_numchn)) { + if (md_driver->VoiceGetPosition) + result=(md_driver->VoiceGetPosition(voice)); + else + result=-1; + } + MUTEX_UNLOCK(vars); + + return result; +} + +MIKMODAPI ULONG Voice_RealVolume(SBYTE voice) +{ + ULONG result=0; + + MUTEX_LOCK(vars); + if((voice>=0)&&(voice<md_numchn)&& md_driver->VoiceRealVolume) + result=(md_driver->VoiceRealVolume(voice)); + MUTEX_UNLOCK(vars); + + return result; +} + +static BOOL _mm_init(CHAR *cmdline) +{ + UWORD t; + + _mm_critical = 1; + + /* if md_device==0, try to find a device number */ + if(!md_device) { + cmdline=NULL; + + for(t=1,md_driver=firstdriver;md_driver;md_driver=md_driver->next,t++) + if(md_driver->IsPresent()) break; + + if(!md_driver) { + _mm_errno = MMERR_DETECTING_DEVICE; + if(_mm_errorhandler) _mm_errorhandler(); + md_driver = &drv_nos; + return 1; + } + + md_device = t; + } else { + /* if n>0, use that driver */ + for(t=1,md_driver=firstdriver;(md_driver)&&(t!=md_device);md_driver=md_driver->next) + t++; + + if(!md_driver) { + _mm_errno = MMERR_INVALID_DEVICE; + if(_mm_errorhandler) _mm_errorhandler(); + md_driver = &drv_nos; + return 1; + } + + /* arguments here might be necessary for the presence check to succeed */ + if(cmdline&&(md_driver->CommandLine)) + md_driver->CommandLine(cmdline); + + if(!md_driver->IsPresent()) { + _mm_errno = MMERR_DETECTING_DEVICE; + if(_mm_errorhandler) _mm_errorhandler(); + md_driver = &drv_nos; + return 1; + } + } + + olddevice = md_device; + if(md_driver->Init()) { + MikMod_Exit_internal(); + if(_mm_errorhandler) _mm_errorhandler(); + return 1; + } + + initialized=1; + _mm_critical=0; + + return 0; +} + +MIKMODAPI BOOL MikMod_Init(CHAR *cmdline) +{ + BOOL result; + + MUTEX_LOCK(vars); + MUTEX_LOCK(lists); + result=_mm_init(cmdline); + MUTEX_UNLOCK(lists); + MUTEX_UNLOCK(vars); + + return result; +} + +void MikMod_Exit_internal(void) +{ + MikMod_DisableOutput_internal(); + md_driver->Exit(); + md_numchn = md_sfxchn = md_sngchn = 0; + md_driver = &drv_nos; + + if(sfxinfo) MikMod_free(sfxinfo); + if(md_sample) MikMod_free(md_sample); + md_sample = NULL; + sfxinfo = NULL; + + initialized = 0; +} + +MIKMODAPI void MikMod_Exit(void) +{ + MUTEX_LOCK(vars); + MUTEX_LOCK(lists); + MikMod_Exit_internal(); + MUTEX_UNLOCK(lists); + MUTEX_UNLOCK(vars); +} + +/* Reset the driver using the new global variable settings. + If the driver has not been initialized, it will be now. */ +static BOOL _mm_reset(CHAR *cmdline) +{ + BOOL wasplaying = 0; + + if(!initialized) return _mm_init(cmdline); + + if (isplaying) { + wasplaying = 1; + md_driver->PlayStop(); + } + + if((!md_driver->Reset)||(md_device != olddevice)) { + /* md_driver->Reset was NULL, or md_device was changed, so do a full + reset of the driver. */ + md_driver->Exit(); + if(_mm_init(cmdline)) { + MikMod_Exit_internal(); + if(_mm_errno) + if(_mm_errorhandler) _mm_errorhandler(); + return 1; + } + } else { + if(md_driver->Reset()) { + MikMod_Exit_internal(); + if(_mm_errno) + if(_mm_errorhandler) _mm_errorhandler(); + return 1; + } + } + + if (wasplaying) md_driver->PlayStart(); + return 0; +} + +MIKMODAPI BOOL MikMod_Reset(CHAR *cmdline) +{ + BOOL result; + + MUTEX_LOCK(vars); + MUTEX_LOCK(lists); + result=_mm_reset(cmdline); + MUTEX_UNLOCK(lists); + MUTEX_UNLOCK(vars); + + return result; +} + +/* If either parameter is -1, the current set value will be retained. */ +BOOL MikMod_SetNumVoices_internal(int music, int sfx) +{ + BOOL resume = 0; + int t, oldchn = 0; + + if((!music)&&(!sfx)) return 1; + _mm_critical = 1; + if(isplaying) { + MikMod_DisableOutput_internal(); + oldchn = md_numchn; + resume = 1; + } + + if(sfxinfo) MikMod_free(sfxinfo); + if(md_sample) MikMod_free(md_sample); + md_sample = NULL; + sfxinfo = NULL; + + if(music!=-1) md_sngchn = music; + if(sfx!=-1) md_sfxchn = sfx; + md_numchn = md_sngchn + md_sfxchn; + + LimitHardVoices(md_driver->HardVoiceLimit); + LimitSoftVoices(md_driver->SoftVoiceLimit); + + if(md_driver->SetNumVoices()) { + MikMod_Exit_internal(); + if(_mm_errno) + if(_mm_errorhandler!=NULL) _mm_errorhandler(); + md_numchn = md_softchn = md_hardchn = md_sfxchn = md_sngchn = 0; + return 1; + } + + if(md_sngchn+md_sfxchn) + md_sample=(SAMPLE**)MikMod_calloc(md_sngchn+md_sfxchn,sizeof(SAMPLE*)); + if(md_sfxchn) + sfxinfo = (UBYTE *)MikMod_calloc(md_sfxchn,sizeof(UBYTE)); + + /* make sure the player doesn't start with garbage */ + for(t=oldchn;t<md_numchn;t++) Voice_Stop_internal(t); + + sfxpool = 0; + if(resume) MikMod_EnableOutput_internal(); + _mm_critical = 0; + + return 0; +} + +MIKMODAPI BOOL MikMod_SetNumVoices(int music, int sfx) +{ + BOOL result; + + MUTEX_LOCK(vars); + result=MikMod_SetNumVoices_internal(music,sfx); + MUTEX_UNLOCK(vars); + + return result; +} + +BOOL MikMod_EnableOutput_internal(void) +{ + _mm_critical = 1; + if(!isplaying) { + if(md_driver->PlayStart()) return 1; + isplaying = 1; + } + _mm_critical = 0; + return 0; +} + +MIKMODAPI BOOL MikMod_EnableOutput(void) +{ + BOOL result; + + MUTEX_LOCK(vars); + result=MikMod_EnableOutput_internal(); + MUTEX_UNLOCK(vars); + + return result; +} + +void MikMod_DisableOutput_internal(void) +{ + if(isplaying && md_driver) { + isplaying = 0; + md_driver->PlayStop(); + } +} + +MIKMODAPI void MikMod_DisableOutput(void) +{ + MUTEX_LOCK(vars); + MikMod_DisableOutput_internal(); + MUTEX_UNLOCK(vars); +} + +BOOL MikMod_Active_internal(void) +{ + return isplaying; +} + +MIKMODAPI BOOL MikMod_Active(void) +{ + BOOL result; + + MUTEX_LOCK(vars); + result=MikMod_Active_internal(); + MUTEX_UNLOCK(vars); + + return result; +} + +/* Plays a sound effects sample. Picks a voice from the number of voices + allocated for use as sound effects (loops through voices, skipping all active + criticals). + + Returns the voice that the sound is being played on. */ +SBYTE Sample_Play_internal(SAMPLE *s,ULONG start,UBYTE flags) +{ + int orig=sfxpool;/* for cases where all channels are critical */ + int c; + + if(!md_sfxchn) return -1; + if(s->volume>64) s->volume = 64; + + /* check the first location after sfxpool */ + do { + if(sfxinfo[sfxpool]&SFX_CRITICAL) { + if(md_driver->VoiceStopped(c=sfxpool+md_sngchn)) { + sfxinfo[sfxpool]=flags; + Voice_Play_internal(c,s,start); + md_driver->VoiceSetVolume(c,s->volume<<2); + Voice_SetPanning_internal(c,s->panning); + md_driver->VoiceSetFrequency(c,s->speed); + sfxpool++; + if(sfxpool>=md_sfxchn) sfxpool=0; + return c; + } + } else { + sfxinfo[sfxpool]=flags; + Voice_Play_internal(c=sfxpool+md_sngchn,s,start); + md_driver->VoiceSetVolume(c,s->volume<<2); + Voice_SetPanning_internal(c,s->panning); + md_driver->VoiceSetFrequency(c,s->speed); + sfxpool++; + if(sfxpool>=md_sfxchn) sfxpool=0; + return c; + } + + sfxpool++; + if(sfxpool>=md_sfxchn) sfxpool = 0; + } while(sfxpool!=orig); + + return -1; +} + +MIKMODAPI SBYTE Sample_Play(SAMPLE *s,ULONG start,UBYTE flags) +{ + SBYTE result; + + MUTEX_LOCK(vars); + result=Sample_Play_internal(s,start,flags); + MUTEX_UNLOCK(vars); + + return result; +} + +MIKMODAPI long MikMod_GetVersion(void) +{ + return LIBMIKMOD_VERSION; +} + +/*========== MT-safe stuff */ + +#ifdef HAVE_PTHREAD +#define INIT_MUTEX(name) \ + pthread_mutex_t _mm_mutex_##name=PTHREAD_MUTEX_INITIALIZER +#elif defined(__OS2__)||defined(__EMX__) +#define INIT_MUTEX(name) \ + HMTX _mm_mutex_##name +#elif defined(WIN32) +#define INIT_MUTEX(name) \ + HANDLE _mm_mutex_##name +#else +#define INIT_MUTEX(name) \ + void *_mm_mutex_##name = NULL +#endif + +INIT_MUTEX(vars); +INIT_MUTEX(lists); + +MIKMODAPI BOOL MikMod_InitThreads(void) +{ + static int firstcall=1; + static int result=0; + + if (firstcall) { + firstcall=0; +#ifdef HAVE_PTHREAD + result=1; +#elif defined(__OS2__)||defined(__EMX__) + if(DosCreateMutexSem((PSZ)NULL,&_mm_mutex_lists,0,0) || + DosCreateMutexSem((PSZ)NULL,&_mm_mutex_vars,0,0)) { + _mm_mutex_lists=_mm_mutex_vars=(HMTX)NULL; + result=0; + } else + result=1; +#elif defined(WIN32) + if((!(_mm_mutex_lists=CreateMutex(NULL,FALSE,"libmikmod(lists)")))|| + (!(_mm_mutex_vars=CreateMutex(NULL,FALSE,"libmikmod(vars)")))) + result=0; + else + result=1; +#endif + } + return result; +} + +MIKMODAPI void MikMod_Unlock(void) +{ + MUTEX_UNLOCK(lists); + MUTEX_UNLOCK(vars); +} + +MIKMODAPI void MikMod_Lock(void) +{ + MUTEX_LOCK(vars); + MUTEX_LOCK(lists); +} + +/*========== Parameter extraction helper */ + +CHAR *MD_GetAtom(CHAR *atomname,CHAR *cmdline,BOOL implicit) +{ + CHAR *ret=NULL; + + if(cmdline) { + CHAR *buf=strstr(cmdline,atomname); + + if((buf)&&((buf==cmdline)||(*(buf-1)==','))) { + CHAR *ptr=buf+strlen(atomname); + + if(*ptr=='=') { + for(buf=++ptr;(*ptr)&&((*ptr)!=',');ptr++); + ret=MikMod_malloc((1+ptr-buf)*sizeof(CHAR)); + if(ret) + strncpy(ret,buf,ptr-buf); + } else if((*ptr==',')||(!*ptr)) { + if(implicit) { + ret=MikMod_malloc((1+ptr-buf)*sizeof(CHAR)); + if(ret) + strncpy(ret,buf,ptr-buf); + } + } + } + } + return ret; +} + +#if defined unix || (defined __APPLE__ && defined __MACH__) + +/*========== Posix helper functions */ + +/* Check if the file is a regular or nonexistant file (or a link to a such a + file), and that, should the calling program be setuid, the access rights are + reasonable. Returns 1 if it is safe to rewrite the file, 0 otherwise. + The goal is to prevent a setuid root libmikmod application from overriding + files like /etc/passwd with digital sound... */ +BOOL MD_Access(CHAR *filename) +{ + struct stat buf; + + if(!stat(filename,&buf)) { + /* not a regular file ? */ + if(!S_ISREG(buf.st_mode)) return 0; + /* more than one hard link to the file ? */ + if(buf.st_nlink>1) return 0; + /* check access rights with the real user and group id */ + if(getuid()==buf.st_uid) { + if(!(buf.st_mode&S_IWUSR)) return 0; + } else if(getgid()==buf.st_gid) { + if(!(buf.st_mode&S_IWGRP)) return 0; + } else + if(!(buf.st_mode&S_IWOTH)) return 0; + } + + return 1; +} + +/* Drop all root privileges we might have */ +BOOL MD_DropPrivileges(void) +{ + if(!geteuid()) { + if(getuid()) { + /* we are setuid root -> drop setuid to become the real user */ + if(setuid(getuid())) return 1; + } else { + /* we are run as root -> drop all and become user 'nobody' */ + struct passwd *nobody; + int uid; + + if(!(nobody=getpwnam("nobody"))) return 1; /* no such user ? */ + uid=nobody->pw_uid; + if (!uid) /* user 'nobody' has root privileges ? weird... */ + return 1; + if (setuid(uid)) return 1; + } + } + return 0; +} + +#endif + +/* ex:set ts=4: */ diff --git a/src/libs/mikmod/mikmod.h b/src/libs/mikmod/mikmod.h new file mode 100644 index 0000000..1fd5322 --- /dev/null +++ b/src/libs/mikmod/mikmod.h @@ -0,0 +1,730 @@ +/* MikMod sound library + (c) 1998, 1999, 2000 Miodrag Vallat and others - see file AUTHORS + for complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + MikMod sound library include file + +==============================================================================*/ + +#ifndef LIBS_MIKMOD_MIKMOD_H_ +#define LIBS_MIKMOD_MIKMOD_H_ + +#include <stdio.h> +#include <stdlib.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * ========== Compiler magic for shared libraries + */ + +#if defined(LIBMIKMOD_DLL) +#if defined(DLL_EXPORTS) +#define MIKMODAPI __declspec(dllexport) +#else +#define MIKMODAPI __declspec(dllimport) +#endif +#else +#define MIKMODAPI +#endif + +/* + * ========== Library version + */ + +#define LIBMIKMOD_VERSION_MAJOR 3L +#define LIBMIKMOD_VERSION_MINOR 1L +#define LIBMIKMOD_REVISION 12L + +#define LIBMIKMOD_VERSION \ + ((LIBMIKMOD_VERSION_MAJOR<<16)| \ + (LIBMIKMOD_VERSION_MINOR<< 8)| \ + (LIBMIKMOD_REVISION)) + +MIKMODAPI extern long MikMod_GetVersion(void); + +/* + * ========== Platform independent-type definitions + */ + +#if defined(WIN32)||defined(WIN64) +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#include <io.h> +#include <mmsystem.h> +#endif + +#if defined(__OS2__)||defined(__EMX__) +#define INCL_DOSSEMAPHORES +#include <os2.h> +#else +typedef char CHAR; +#endif + + + +#if defined(__arch64__) || defined(__alpha) || defined(__x86_64) \ + || defined(__powerpc64__) || defined(_M_IA64) || defined(_M_AMD64) +/* 64 bit architectures */ + +typedef signed char SBYTE; /* 1 byte, signed */ +typedef unsigned char UBYTE; /* 1 byte, unsigned */ +typedef signed short SWORD; /* 2 bytes, signed */ +typedef unsigned short UWORD; /* 2 bytes, unsigned */ +typedef signed int SLONG; /* 4 bytes, signed */ +typedef unsigned int ULONG; /* 4 bytes, unsigned */ +typedef int BOOL; /* 0=false, <>0 true */ + +#else +/* 32 bit architectures */ + +typedef signed char SBYTE; /* 1 byte, signed */ +typedef unsigned char UBYTE; /* 1 byte, unsigned */ +typedef signed short SWORD; /* 2 bytes, signed */ +typedef unsigned short UWORD; /* 2 bytes, unsigned */ +typedef signed long SLONG; /* 4 bytes, signed */ +#if !defined(__OS2__)&&!defined(__EMX__)&&!defined(WIN32) +typedef unsigned long ULONG; /* 4 bytes, unsigned */ +typedef int BOOL; /* 0=false, <>0 true */ +#endif +#endif + +/* + * ========== Error codes + */ + +enum { + MMERR_OPENING_FILE = 1, + MMERR_OUT_OF_MEMORY, + MMERR_DYNAMIC_LINKING, + + MMERR_SAMPLE_TOO_BIG, + MMERR_OUT_OF_HANDLES, + MMERR_UNKNOWN_WAVE_TYPE, + + MMERR_LOADING_PATTERN, + MMERR_LOADING_TRACK, + MMERR_LOADING_HEADER, + MMERR_LOADING_SAMPLEINFO, + MMERR_NOT_A_MODULE, + MMERR_NOT_A_STREAM, + MMERR_MED_SYNTHSAMPLES, + MMERR_ITPACK_INVALID_DATA, + + MMERR_DETECTING_DEVICE, + MMERR_INVALID_DEVICE, + MMERR_INITIALIZING_MIXER, + MMERR_OPENING_AUDIO, + MMERR_8BIT_ONLY, + MMERR_16BIT_ONLY, + MMERR_STEREO_ONLY, + MMERR_ULAW, + MMERR_NON_BLOCK, + + MMERR_AF_AUDIO_PORT, + + MMERR_AIX_CONFIG_INIT, + MMERR_AIX_CONFIG_CONTROL, + MMERR_AIX_CONFIG_START, + + MMERR_GUS_SETTINGS, + MMERR_GUS_RESET, + MMERR_GUS_TIMER, + + MMERR_HP_SETSAMPLESIZE, + MMERR_HP_SETSPEED, + MMERR_HP_CHANNELS, + MMERR_HP_AUDIO_OUTPUT, + MMERR_HP_AUDIO_DESC, + MMERR_HP_BUFFERSIZE, + + MMERR_OSS_SETFRAGMENT, + MMERR_OSS_SETSAMPLESIZE, + MMERR_OSS_SETSTEREO, + MMERR_OSS_SETSPEED, + + MMERR_SGI_SPEED, + MMERR_SGI_16BIT, + MMERR_SGI_8BIT, + MMERR_SGI_STEREO, + MMERR_SGI_MONO, + + MMERR_SUN_INIT, + + MMERR_OS2_MIXSETUP, + MMERR_OS2_SEMAPHORE, + MMERR_OS2_TIMER, + MMERR_OS2_THREAD, + + MMERR_DS_PRIORITY, + MMERR_DS_BUFFER, + MMERR_DS_FORMAT, + MMERR_DS_NOTIFY, + MMERR_DS_EVENT, + MMERR_DS_THREAD, + MMERR_DS_UPDATE, + + MMERR_WINMM_HANDLE, + MMERR_WINMM_ALLOCATED, + MMERR_WINMM_DEVICEID, + MMERR_WINMM_FORMAT, + MMERR_WINMM_UNKNOWN, + + MMERR_MAC_SPEED, + MMERR_MAC_START, + + MMERR_MAX +}; + +/* + * ========== Error handling + */ + +typedef void (MikMod_handler)(void); +typedef MikMod_handler *MikMod_handler_t; + +MIKMODAPI extern int MikMod_errno; +MIKMODAPI extern BOOL MikMod_critical; +MIKMODAPI extern char *MikMod_strerror(int); + +MIKMODAPI extern MikMod_handler_t MikMod_RegisterErrorHandler(MikMod_handler_t); + +/* + * ========== Library initialization and core functions + */ + +struct MDRIVER; + +MIKMODAPI extern void MikMod_RegisterAllDrivers(void); + +MIKMODAPI extern CHAR* MikMod_InfoDriver(void); +MIKMODAPI extern void MikMod_RegisterDriver(struct MDRIVER*); +MIKMODAPI extern int MikMod_DriverFromAlias(CHAR*); + +MIKMODAPI extern BOOL MikMod_Init(CHAR*); +MIKMODAPI extern void MikMod_Exit(void); +MIKMODAPI extern BOOL MikMod_Reset(CHAR*); +MIKMODAPI extern BOOL MikMod_SetNumVoices(int,int); +MIKMODAPI extern BOOL MikMod_Active(void); +MIKMODAPI extern BOOL MikMod_EnableOutput(void); +MIKMODAPI extern void MikMod_DisableOutput(void); +MIKMODAPI extern void MikMod_Update(void); + +MIKMODAPI extern BOOL MikMod_InitThreads(void); +MIKMODAPI extern void MikMod_Lock(void); +MIKMODAPI extern void MikMod_Unlock(void); + +MIKMODAPI extern void* MikMod_malloc(size_t); +MIKMODAPI extern void* MikMod_realloc(void *, size_t); +MIKMODAPI extern void* MikMod_calloc(size_t,size_t); +MIKMODAPI extern void MikMod_free(void *); + +/* + * ========== Reader, Writer + */ + +typedef struct MREADER { + BOOL (*Seek)(struct MREADER*,long,int); + long (*Tell)(struct MREADER*); + BOOL (*Read)(struct MREADER*,void*,size_t); + int (*Get)(struct MREADER*); + BOOL (*Eof)(struct MREADER*); + long iobase; + long prev_iobase; +} MREADER; + +typedef struct MWRITER { + BOOL (*Seek)(struct MWRITER*,long,int); + long (*Tell)(struct MWRITER*); + BOOL (*Write)(struct MWRITER*,void*,size_t); + BOOL (*Put)(struct MWRITER*,int); +} MWRITER; + +/* + * ========== Samples + */ + +/* Sample playback should not be interrupted */ +#define SFX_CRITICAL 1 + +/* Sample format [loading and in-memory] flags: */ +#define SF_16BITS 0x0001 +#define SF_STEREO 0x0002 +#define SF_SIGNED 0x0004 +#define SF_BIG_ENDIAN 0x0008 +#define SF_DELTA 0x0010 +#define SF_ITPACKED 0x0020 + +#define SF_FORMATMASK 0x003F + +/* General Playback flags */ + +#define SF_LOOP 0x0100 +#define SF_BIDI 0x0200 +#define SF_REVERSE 0x0400 +#define SF_SUSTAIN 0x0800 + +#define SF_PLAYBACKMASK 0x0C00 + +/* Module-only Playback Flags */ + +#define SF_OWNPAN 0x1000 +#define SF_UST_LOOP 0x2000 + +#define SF_EXTRAPLAYBACKMASK 0x3000 + +/* Panning constants */ +#define PAN_LEFT 0 +#define PAN_HALFLEFT 64 +#define PAN_CENTER 128 +#define PAN_HALFRIGHT 192 +#define PAN_RIGHT 255 +#define PAN_SURROUND 512 /* panning value for Dolby Surround */ + +typedef struct SAMPLE { + SWORD panning; /* panning (0-255 or PAN_SURROUND) */ + ULONG speed; /* Base playing speed/frequency of note */ + UBYTE volume; /* volume 0-64 */ + UWORD inflags; /* sample format on disk */ + UWORD flags; /* sample format in memory */ + ULONG length; /* length of sample (in samples!) */ + ULONG loopstart; /* repeat position (relative to start, in samples) */ + ULONG loopend; /* repeat end */ + ULONG susbegin; /* sustain loop begin (in samples) \ Not Supported */ + ULONG susend; /* sustain loop end / Yet! */ + + /* Variables used by the module player only! (ignored for sound effects) */ + UBYTE globvol; /* global volume */ + UBYTE vibflags; /* autovibrato flag stuffs */ + UBYTE vibtype; /* Vibratos moved from INSTRUMENT to SAMPLE */ + UBYTE vibsweep; + UBYTE vibdepth; + UBYTE vibrate; + CHAR* samplename; /* name of the sample */ + + /* Values used internally only */ + UWORD avibpos; /* autovibrato pos [player use] */ + UBYTE divfactor; /* for sample scaling, maintains proper period slides */ + ULONG seekpos; /* seek position in file */ + SWORD handle; /* sample handle used by individual drivers */ +} SAMPLE; + +/* Sample functions */ + +MIKMODAPI extern SAMPLE *Sample_Load(CHAR*); +MIKMODAPI extern SAMPLE *Sample_LoadFP(FILE*); +MIKMODAPI extern SAMPLE *Sample_LoadMem(const char *buf, int len); +MIKMODAPI extern SAMPLE *Sample_LoadGeneric(MREADER*); +MIKMODAPI extern void Sample_Free(SAMPLE*); +MIKMODAPI extern SBYTE Sample_Play(SAMPLE*,ULONG,UBYTE); + +MIKMODAPI extern void Voice_SetVolume(SBYTE,UWORD); +MIKMODAPI extern UWORD Voice_GetVolume(SBYTE); +MIKMODAPI extern void Voice_SetFrequency(SBYTE,ULONG); +MIKMODAPI extern ULONG Voice_GetFrequency(SBYTE); +MIKMODAPI extern void Voice_SetPanning(SBYTE,ULONG); +MIKMODAPI extern ULONG Voice_GetPanning(SBYTE); +MIKMODAPI extern void Voice_Play(SBYTE,SAMPLE*,ULONG); +MIKMODAPI extern void Voice_Stop(SBYTE); +MIKMODAPI extern BOOL Voice_Stopped(SBYTE); +MIKMODAPI extern SLONG Voice_GetPosition(SBYTE); +MIKMODAPI extern ULONG Voice_RealVolume(SBYTE); + +/* + * ========== Internal module representation (UniMod) + */ + +/* + Instrument definition - for information only, the only field which may be + of use in user programs is the name field +*/ + +/* Instrument note count */ +#define INSTNOTES 120 + +/* Envelope point */ +typedef struct ENVPT { + SWORD pos; + SWORD val; +} ENVPT; + +/* Envelope point count */ +#define ENVPOINTS 32 + +/* Instrument structure */ +typedef struct INSTRUMENT { + CHAR* insname; + + UBYTE flags; + UWORD samplenumber[INSTNOTES]; + UBYTE samplenote[INSTNOTES]; + + UBYTE nnatype; + UBYTE dca; /* duplicate check action */ + UBYTE dct; /* duplicate check type */ + UBYTE globvol; + UWORD volfade; + SWORD panning; /* instrument-based panning var */ + + UBYTE pitpansep; /* pitch pan separation (0 to 255) */ + UBYTE pitpancenter; /* pitch pan center (0 to 119) */ + UBYTE rvolvar; /* random volume varations (0 - 100%) */ + UBYTE rpanvar; /* random panning varations (0 - 100%) */ + + /* volume envelope */ + UBYTE volflg; /* bit 0: on 1: sustain 2: loop */ + UBYTE volpts; + UBYTE volsusbeg; + UBYTE volsusend; + UBYTE volbeg; + UBYTE volend; + ENVPT volenv[ENVPOINTS]; + /* panning envelope */ + UBYTE panflg; /* bit 0: on 1: sustain 2: loop */ + UBYTE panpts; + UBYTE pansusbeg; + UBYTE pansusend; + UBYTE panbeg; + UBYTE panend; + ENVPT panenv[ENVPOINTS]; + /* pitch envelope */ + UBYTE pitflg; /* bit 0: on 1: sustain 2: loop */ + UBYTE pitpts; + UBYTE pitsusbeg; + UBYTE pitsusend; + UBYTE pitbeg; + UBYTE pitend; + ENVPT pitenv[ENVPOINTS]; +} INSTRUMENT; + +struct MP_CONTROL; +struct MP_VOICE; + +/* + Module definition +*/ + +/* maximum master channels supported */ +#define UF_MAXCHAN 64 + +/* Module flags */ +#define UF_XMPERIODS 0x0001 /* XM periods / finetuning */ +#define UF_LINEAR 0x0002 /* LINEAR periods (UF_XMPERIODS must be set) */ +#define UF_INST 0x0004 /* Instruments are used */ +#define UF_NNA 0x0008 /* IT: NNA used, set numvoices rather + than numchn */ +#define UF_S3MSLIDES 0x0010 /* uses old S3M volume slides */ +#define UF_BGSLIDES 0x0020 /* continue volume slides in the background */ +#define UF_HIGHBPM 0x0040 /* MED: can use >255 bpm */ +#define UF_NOWRAP 0x0080 /* XM-type (i.e. illogical) pattern break + semantics */ +#define UF_ARPMEM 0x0100 /* IT: need arpeggio memory */ +#define UF_FT2QUIRKS 0x0200 /* emulate some FT2 replay quirks */ +#define UF_PANNING 0x0400 /* module uses panning effects or have + non-tracker default initial panning */ + +typedef struct MODULE { + /* general module information */ + CHAR* songname; /* name of the song */ + CHAR* modtype; /* string type of module loaded */ + CHAR* comment; /* module comments */ + + UWORD flags; /* See module flags above */ + UBYTE numchn; /* number of module channels */ + UBYTE numvoices; /* max # voices used for full NNA playback */ + UWORD numpos; /* number of positions in this song */ + UWORD numpat; /* number of patterns in this song */ + UWORD numins; /* number of instruments */ + UWORD numsmp; /* number of samples */ +struct INSTRUMENT* instruments; /* all instruments */ +struct SAMPLE* samples; /* all samples */ + UBYTE realchn; /* real number of channels used */ + UBYTE totalchn; /* total number of channels used (incl NNAs) */ + + /* playback settings */ + UWORD reppos; /* restart position */ + UBYTE initspeed; /* initial song speed */ + UWORD inittempo; /* initial song tempo */ + UBYTE initvolume; /* initial global volume (0 - 128) */ + UWORD panning[UF_MAXCHAN]; /* panning positions */ + UBYTE chanvol[UF_MAXCHAN]; /* channel positions */ + UWORD bpm; /* current beats-per-minute speed */ + UWORD sngspd; /* current song speed */ + SWORD volume; /* song volume (0-128) (or user volume) */ + + BOOL extspd; /* extended speed flag (default enabled) */ + BOOL panflag; /* panning flag (default enabled) */ + BOOL wrap; /* wrap module ? (default disabled) */ + BOOL loop; /* allow module to loop ? (default enabled) */ + BOOL fadeout; /* volume fade out during last pattern */ + + UWORD patpos; /* current row number */ + SWORD sngpos; /* current song position */ + ULONG sngtime; /* current song time in 2^-10 seconds */ + + SWORD relspd; /* relative speed factor */ + + /* internal module representation */ + UWORD numtrk; /* number of tracks */ + UBYTE** tracks; /* array of numtrk pointers to tracks */ + UWORD* patterns; /* array of Patterns */ + UWORD* pattrows; /* array of number of rows for each pattern */ + UWORD* positions; /* all positions */ + + BOOL forbid; /* if true, no player update! */ + UWORD numrow; /* number of rows on current pattern */ + UWORD vbtick; /* tick counter (counts from 0 to sngspd) */ + UWORD sngremainder;/* used for song time computation */ + +struct MP_CONTROL* control; /* Effects Channel info (size pf->numchn) */ +struct MP_VOICE* voice; /* Audio Voice information (size md_numchn) */ + + UBYTE globalslide; /* global volume slide rate */ + UBYTE pat_repcrazy;/* module has just looped to position -1 */ + UWORD patbrk; /* position where to start a new pattern */ + UBYTE patdly; /* patterndelay counter (command memory) */ + UBYTE patdly2; /* patterndelay counter (real one) */ + SWORD posjmp; /* flag to indicate a jump is needed... */ + UWORD bpmlimit; /* threshold to detect bpm or speed values */ +} MODULE; + +/* + * ========== Module loaders + */ + +struct MLOADER; + +MIKMODAPI extern CHAR* MikMod_InfoLoader(void); +MIKMODAPI extern void MikMod_RegisterAllLoaders(void); +MIKMODAPI extern void MikMod_RegisterLoader(struct MLOADER*); + +MIKMODAPI extern struct MLOADER load_669; /* 669 and Extended-669 (by Tran/Renaissance) */ +MIKMODAPI extern struct MLOADER load_amf; /* DMP Advanced Module Format (by Otto Chrons) */ +MIKMODAPI extern struct MLOADER load_dsm; /* DSIK internal module format */ +MIKMODAPI extern struct MLOADER load_far; /* Farandole Composer (by Daniel Potter) */ +MIKMODAPI extern struct MLOADER load_gdm; /* General DigiMusic (by Edward Schlunder) */ +MIKMODAPI extern struct MLOADER load_it; /* Impulse Tracker (by Jeffrey Lim) */ +MIKMODAPI extern struct MLOADER load_imf; /* Imago Orpheus (by Lutz Roeder) */ +MIKMODAPI extern struct MLOADER load_med; /* Amiga MED modules (by Teijo Kinnunen) */ +MIKMODAPI extern struct MLOADER load_m15; /* Soundtracker 15-instrument */ +MIKMODAPI extern struct MLOADER load_mod; /* Standard 31-instrument Module loader */ +MIKMODAPI extern struct MLOADER load_mtm; /* Multi-Tracker Module (by Renaissance) */ +MIKMODAPI extern struct MLOADER load_okt; /* Amiga Oktalyzer */ +MIKMODAPI extern struct MLOADER load_stm; /* ScreamTracker 2 (by Future Crew) */ +MIKMODAPI extern struct MLOADER load_stx; /* STMIK 0.2 (by Future Crew) */ +MIKMODAPI extern struct MLOADER load_s3m; /* ScreamTracker 3 (by Future Crew) */ +MIKMODAPI extern struct MLOADER load_ult; /* UltraTracker (by MAS) */ +MIKMODAPI extern struct MLOADER load_uni; /* MikMod and APlayer internal module format */ +MIKMODAPI extern struct MLOADER load_xm; /* FastTracker 2 (by Triton) */ + +/* + * ========== Module player + */ + +MIKMODAPI extern MODULE* Player_Load(CHAR*,int,BOOL); +MIKMODAPI extern MODULE* Player_LoadFP(FILE*,int,BOOL); +MIKMODAPI extern MODULE* Player_LoadMem(const char *buffer,int len,int maxchan,BOOL curious); +MIKMODAPI extern MODULE* Player_LoadGeneric(MREADER*,int,BOOL); +MIKMODAPI extern CHAR* Player_LoadTitle(CHAR*); +MIKMODAPI extern CHAR* Player_LoadTitleFP(FILE*); +MIKMODAPI extern CHAR* Player_LoadTitleMem(const char *buffer,int len); +MIKMODAPI extern CHAR* Player_LoadTitleGeneric(MREADER*); + +MIKMODAPI extern void Player_Free(MODULE*); +MIKMODAPI extern void Player_Start(MODULE*); +MIKMODAPI extern BOOL Player_Active(void); +MIKMODAPI extern void Player_Stop(void); +MIKMODAPI extern void Player_TogglePause(void); +MIKMODAPI extern BOOL Player_Paused(void); +MIKMODAPI extern void Player_NextPosition(void); +MIKMODAPI extern void Player_PrevPosition(void); +MIKMODAPI extern void Player_SetPosition(UWORD); +MIKMODAPI extern BOOL Player_Muted(UBYTE); +MIKMODAPI extern void Player_SetVolume(SWORD); +MIKMODAPI extern MODULE* Player_GetModule(void); +MIKMODAPI extern void Player_SetSpeed(UWORD); +MIKMODAPI extern void Player_SetTempo(UWORD); +MIKMODAPI extern void Player_Unmute(SLONG,...); +MIKMODAPI extern void Player_Mute(SLONG,...); +MIKMODAPI extern void Player_ToggleMute(SLONG,...); +MIKMODAPI extern int Player_GetChannelVoice(UBYTE); +MIKMODAPI extern UWORD Player_GetChannelPeriod(UBYTE); + +typedef void (MikMod_player)(void); +typedef MikMod_player *MikMod_player_t; + +MIKMODAPI extern MikMod_player_t MikMod_RegisterPlayer(MikMod_player_t); + +#define MUTE_EXCLUSIVE 32000 +#define MUTE_INCLUSIVE 32001 + +/* + * ========== Drivers + */ + +enum { + MD_MUSIC = 0, + MD_SNDFX +}; + +enum { + MD_HARDWARE = 0, + MD_SOFTWARE +}; + +/* Mixing flags */ + +/* These ones take effect only after MikMod_Init or MikMod_Reset */ +#define DMODE_16BITS 0x0001 /* enable 16 bit output */ +#define DMODE_STEREO 0x0002 /* enable stereo output */ +#define DMODE_SOFT_SNDFX 0x0004 /* Process sound effects via software mixer */ +#define DMODE_SOFT_MUSIC 0x0008 /* Process music via software mixer */ +#define DMODE_HQMIXER 0x0010 /* Use high-quality (slower) software mixer */ +/* These take effect immediately. */ +#define DMODE_SURROUND 0x0100 /* enable surround sound */ +#define DMODE_INTERP 0x0200 /* enable interpolation */ +#define DMODE_REVERSE 0x0400 /* reverse stereo */ + +struct SAMPLOAD; +typedef struct MDRIVER { +struct MDRIVER* next; + CHAR* Name; + CHAR* Version; + + UBYTE HardVoiceLimit; /* Limit of hardware mixer voices */ + UBYTE SoftVoiceLimit; /* Limit of software mixer voices */ + + CHAR* Alias; + + void (*CommandLine) (CHAR*); + BOOL (*IsPresent) (void); + SWORD (*SampleLoad) (struct SAMPLOAD*,int); + void (*SampleUnload) (SWORD); + ULONG (*FreeSampleSpace) (int); + ULONG (*RealSampleLength) (int,struct SAMPLE*); + BOOL (*Init) (void); + void (*Exit) (void); + BOOL (*Reset) (void); + BOOL (*SetNumVoices) (void); + BOOL (*PlayStart) (void); + void (*PlayStop) (void); + void (*Update) (void); + void (*Pause) (void); + void (*VoiceSetVolume) (UBYTE,UWORD); + UWORD (*VoiceGetVolume) (UBYTE); + void (*VoiceSetFrequency)(UBYTE,ULONG); + ULONG (*VoiceGetFrequency)(UBYTE); + void (*VoiceSetPanning) (UBYTE,ULONG); + ULONG (*VoiceGetPanning) (UBYTE); + void (*VoicePlay) (UBYTE,SWORD,ULONG,ULONG,ULONG,ULONG,UWORD); + void (*VoiceStop) (UBYTE); + BOOL (*VoiceStopped) (UBYTE); + SLONG (*VoiceGetPosition) (UBYTE); + ULONG (*VoiceRealVolume) (UBYTE); +} MDRIVER; + +/* These variables can be changed at ANY time and results will be immediate */ +MIKMODAPI extern UBYTE md_volume; /* global sound volume (0-128) */ +MIKMODAPI extern UBYTE md_musicvolume; /* volume of song */ +MIKMODAPI extern UBYTE md_sndfxvolume; /* volume of sound effects */ +MIKMODAPI extern UBYTE md_reverb; /* 0 = none; 15 = chaos */ +MIKMODAPI extern UBYTE md_pansep; /* 0 = mono; 128 == 100% (full left/right) */ + +/* The variables below can be changed at any time, but changes will not be + implemented until MikMod_Reset is called. A call to MikMod_Reset may result + in a skip or pop in audio (depending on the soundcard driver and the settings + changed). */ +MIKMODAPI extern UWORD md_device; /* device */ +MIKMODAPI extern UWORD md_mixfreq; /* mixing frequency */ +MIKMODAPI extern UWORD md_mode; /* mode. See DMODE_? flags above */ + +/* The following variable should not be changed! */ +MIKMODAPI extern MDRIVER* md_driver; /* Current driver in use. */ + +/* Known drivers list */ + +MIKMODAPI extern struct MDRIVER drv_nos; /* no sound */ +MIKMODAPI extern struct MDRIVER drv_pipe; /* piped output */ +MIKMODAPI extern struct MDRIVER drv_raw; /* raw file disk writer [music.raw] */ +MIKMODAPI extern struct MDRIVER drv_stdout; /* output to stdout */ +MIKMODAPI extern struct MDRIVER drv_wav; /* RIFF WAVE file disk writer [music.wav] */ + +MIKMODAPI extern struct MDRIVER drv_ultra; /* Linux Ultrasound driver */ +MIKMODAPI extern struct MDRIVER drv_sam9407; /* Linux sam9407 driver */ + +MIKMODAPI extern struct MDRIVER drv_AF; /* Dec Alpha AudioFile */ +MIKMODAPI extern struct MDRIVER drv_aix; /* AIX audio device */ +MIKMODAPI extern struct MDRIVER drv_alsa; /* Advanced Linux Sound Architecture (ALSA) */ +MIKMODAPI extern struct MDRIVER drv_esd; /* Enlightened sound daemon (EsounD) */ +MIKMODAPI extern struct MDRIVER drv_hp; /* HP-UX audio device */ +MIKMODAPI extern struct MDRIVER drv_oss; /* OpenSound System (Linux,FreeBSD...) */ +MIKMODAPI extern struct MDRIVER drv_sgi; /* SGI audio library */ +MIKMODAPI extern struct MDRIVER drv_sun; /* Sun/NetBSD/OpenBSD audio device */ + +MIKMODAPI extern struct MDRIVER drv_dart; /* OS/2 Direct Audio RealTime */ +MIKMODAPI extern struct MDRIVER drv_os2; /* OS/2 MMPM/2 */ + +MIKMODAPI extern struct MDRIVER drv_ds; /* Win32 DirectSound driver */ +MIKMODAPI extern struct MDRIVER drv_win; /* Win32 multimedia API driver */ + +MIKMODAPI extern struct MDRIVER drv_mac; /* Macintosh Sound Manager driver */ + +/*========== Virtual channel mixer interface (for user-supplied drivers only) */ + +MIKMODAPI extern BOOL VC_Init(void); +MIKMODAPI extern void VC_Exit(void); +MIKMODAPI extern BOOL VC_SetNumVoices(void); +MIKMODAPI extern ULONG VC_SampleSpace(int); +MIKMODAPI extern ULONG VC_SampleLength(int,SAMPLE*); + +MIKMODAPI extern BOOL VC_PlayStart(void); +MIKMODAPI extern void VC_PlayStop(void); + +MIKMODAPI extern SWORD VC_SampleLoad(struct SAMPLOAD*,int); +MIKMODAPI extern void VC_SampleUnload(SWORD); + +MIKMODAPI extern ULONG VC_WriteBytes(SBYTE*,ULONG); +MIKMODAPI extern ULONG VC_SilenceBytes(SBYTE*,ULONG); + +MIKMODAPI extern void VC_VoiceSetVolume(UBYTE,UWORD); +MIKMODAPI extern UWORD VC_VoiceGetVolume(UBYTE); +MIKMODAPI extern void VC_VoiceSetFrequency(UBYTE,ULONG); +MIKMODAPI extern ULONG VC_VoiceGetFrequency(UBYTE); +MIKMODAPI extern void VC_VoiceSetPanning(UBYTE,ULONG); +MIKMODAPI extern ULONG VC_VoiceGetPanning(UBYTE); +MIKMODAPI extern void VC_VoicePlay(UBYTE,SWORD,ULONG,ULONG,ULONG,ULONG,UWORD); + +MIKMODAPI extern void VC_VoiceStop(UBYTE); +MIKMODAPI extern BOOL VC_VoiceStopped(UBYTE); +MIKMODAPI extern SLONG VC_VoiceGetPosition(UBYTE); +MIKMODAPI extern ULONG VC_VoiceRealVolume(UBYTE); + +#ifdef __cplusplus +} +#endif + +#endif + +/* ex:set ts=4: */ diff --git a/src/libs/mikmod/mikmod_build.h b/src/libs/mikmod/mikmod_build.h new file mode 100644 index 0000000..29fe3b4 --- /dev/null +++ b/src/libs/mikmod/mikmod_build.h @@ -0,0 +1,9 @@ +#include "mikmod.h" + +#if defined(WIN32) && !defined(__STDC__) +# define __STDC__ 1 +#endif + +#if defined(WIN32) && defined(_MSC_VER) +# pragma warning(disable: 4018 4244) +#endif diff --git a/src/libs/mikmod/mikmod_internals.h b/src/libs/mikmod/mikmod_internals.h new file mode 100644 index 0000000..f84c4b7 --- /dev/null +++ b/src/libs/mikmod/mikmod_internals.h @@ -0,0 +1,679 @@ +/* MikMod sound library + (c) 1998, 1999, 2000 Miodrag Vallat and others - see file AUTHORS for + complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + MikMod sound library internal definitions + +==============================================================================*/ + +#ifndef LIBS_MIKMOD_MIKMOD_INTERNALS_H_ +#define LIBS_MIKMOD_MIKMOD_INTERNALS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdarg.h> +#if defined(__OS2__)||defined(__EMX__)||defined(WIN32)||defined(WIN64) +#define strcasecmp(s,t) stricmp(s,t) +#endif + +#define MIKMOD_INTERNAL +#include "mikmod_build.h" + +/*========== More type definitions */ + +/* SLONGLONG: 64bit, signed */ +#if defined(__arch64__) || defined(__alpha) || defined(__x86_64) \ + || defined(_M_IA64) || defined(_M_AMD64) +typedef long SLONGLONG; +#define NATIVE_64BIT_INT +#elif defined(__powerpc64__) +typedef long long SLONGLONG; +#define NATIVE_64BIT_INT +#elif defined(__WATCOMC__) +typedef __int64 SLONGLONG; +#elif defined(WIN32) && !defined(__MWERKS__) +typedef LONGLONG SLONGLONG; +#elif macintosh && !TYPE_LONGLONG +#include <Types.h> +typedef SInt64 SLONGLONG; +#else +typedef long long SLONGLONG; +#endif + +/*========== Error handling */ + +#define _mm_errno MikMod_errno +#define _mm_critical MikMod_critical +extern MikMod_handler_t _mm_errorhandler; + +/*========== MT stuff */ + +#ifdef HAVE_PTHREAD +#include <pthread.h> +#define DECLARE_MUTEX(name) \ + extern pthread_mutex_t _mm_mutex_##name +#define MUTEX_LOCK(name) \ + pthread_mutex_lock(&_mm_mutex_##name) +#define MUTEX_UNLOCK(name) \ + pthread_mutex_unlock(&_mm_mutex_##name) +#elif defined(__OS2__)||defined(__EMX__) +#define DECLARE_MUTEX(name) \ + extern HMTX _mm_mutex_##name +#define MUTEX_LOCK(name) \ + if(_mm_mutex_##name) \ + DosRequestMutexSem(_mm_mutex_##name,SEM_INDEFINITE_WAIT) +#define MUTEX_UNLOCK(name) \ + if(_mm_mutex_##name) \ + DosReleaseMutexSem(_mm_mutex_##name) +#elif defined(WIN32) || defined(WIN64) +#include <windows.h> +#define DECLARE_MUTEX(name) \ + extern HANDLE _mm_mutex_##name +#define MUTEX_LOCK(name) \ + if(_mm_mutex_##name) \ + WaitForSingleObject(_mm_mutex_##name,INFINITE) +#define MUTEX_UNLOCK(name) \ + if(_mm_mutex_##name) \ + ReleaseMutex(_mm_mutex_##name) +#else +#define DECLARE_MUTEX(name) \ + extern void *_mm_mutex_##name +#define MUTEX_LOCK(name) +#define MUTEX_UNLOCK(name) +#endif + +DECLARE_MUTEX(lists); +DECLARE_MUTEX(vars); + +/*========== Portable file I/O */ + +extern MREADER* _mm_new_mem_reader(const void *buffer, int len); +extern void _mm_delete_mem_reader(MREADER *reader); + +extern MREADER* _mm_new_file_reader(FILE* fp); +extern void _mm_delete_file_reader(MREADER*); + +extern MWRITER* _mm_new_file_writer(FILE *fp); +extern void _mm_delete_file_writer(MWRITER*); + +extern BOOL _mm_FileExists(CHAR *fname); + +#define _mm_write_SBYTE(x,y) y->Put(y,(int)x) +#define _mm_write_UBYTE(x,y) y->Put(y,(int)x) + +#define _mm_read_SBYTE(x) (SBYTE)x->Get(x) +#define _mm_read_UBYTE(x) (UBYTE)x->Get(x) + +#define _mm_write_SBYTES(x,y,z) z->Write(z,(void *)x,y) +#define _mm_write_UBYTES(x,y,z) z->Write(z,(void *)x,y) +#define _mm_read_SBYTES(x,y,z) z->Read(z,(void *)x,y) +#define _mm_read_UBYTES(x,y,z) z->Read(z,(void *)x,y) + +#define _mm_fseek(x,y,z) x->Seek(x,y,z) +#define _mm_ftell(x) x->Tell(x) +#define _mm_rewind(x) _mm_fseek(x,0,SEEK_SET) + +#define _mm_eof(x) x->Eof(x) + +extern void _mm_iobase_setcur(MREADER*); +extern void _mm_iobase_revert(MREADER*); +extern FILE *_mm_fopen(CHAR*,CHAR*); +extern int _mm_fclose(FILE *); +extern void _mm_write_string(CHAR*,MWRITER*); +extern int _mm_read_string (CHAR*,int,MREADER*); + +extern SWORD _mm_read_M_SWORD(MREADER*); +extern SWORD _mm_read_I_SWORD(MREADER*); +extern UWORD _mm_read_M_UWORD(MREADER*); +extern UWORD _mm_read_I_UWORD(MREADER*); + +extern SLONG _mm_read_M_SLONG(MREADER*); +extern SLONG _mm_read_I_SLONG(MREADER*); +extern ULONG _mm_read_M_ULONG(MREADER*); +extern ULONG _mm_read_I_ULONG(MREADER*); + +extern int _mm_read_M_SWORDS(SWORD*,int,MREADER*); +extern int _mm_read_I_SWORDS(SWORD*,int,MREADER*); +extern int _mm_read_M_UWORDS(UWORD*,int,MREADER*); +extern int _mm_read_I_UWORDS(UWORD*,int,MREADER*); + +extern int _mm_read_M_SLONGS(SLONG*,int,MREADER*); +extern int _mm_read_I_SLONGS(SLONG*,int,MREADER*); +extern int _mm_read_M_ULONGS(ULONG*,int,MREADER*); +extern int _mm_read_I_ULONGS(ULONG*,int,MREADER*); + +extern void _mm_write_M_SWORD(SWORD,MWRITER*); +extern void _mm_write_I_SWORD(SWORD,MWRITER*); +extern void _mm_write_M_UWORD(UWORD,MWRITER*); +extern void _mm_write_I_UWORD(UWORD,MWRITER*); + +extern void _mm_write_M_SLONG(SLONG,MWRITER*); +extern void _mm_write_I_SLONG(SLONG,MWRITER*); +extern void _mm_write_M_ULONG(ULONG,MWRITER*); +extern void _mm_write_I_ULONG(ULONG,MWRITER*); + +extern void _mm_write_M_SWORDS(SWORD*,int,MWRITER*); +extern void _mm_write_I_SWORDS(SWORD*,int,MWRITER*); +extern void _mm_write_M_UWORDS(UWORD*,int,MWRITER*); +extern void _mm_write_I_UWORDS(UWORD*,int,MWRITER*); + +extern void _mm_write_M_SLONGS(SLONG*,int,MWRITER*); +extern void _mm_write_I_SLONGS(SLONG*,int,MWRITER*); +extern void _mm_write_M_ULONGS(ULONG*,int,MWRITER*); +extern void _mm_write_I_ULONGS(ULONG*,int,MWRITER*); + +/*========== Samples */ + +/* This is a handle of sorts attached to any sample registered with + SL_RegisterSample. Generally, this only need be used or changed by the + loaders and drivers of mikmod. */ +typedef struct SAMPLOAD { + struct SAMPLOAD *next; + + ULONG length; /* length of sample (in samples!) */ + ULONG loopstart; /* repeat position (relative to start, in samples) */ + ULONG loopend; /* repeat end */ + UWORD infmt,outfmt; + int scalefactor; + SAMPLE* sample; + MREADER* reader; +} SAMPLOAD; + +/*========== Sample and waves loading interface */ + +extern void SL_HalveSample(SAMPLOAD*,int); +extern void SL_Sample8to16(SAMPLOAD*); +extern void SL_Sample16to8(SAMPLOAD*); +extern void SL_SampleSigned(SAMPLOAD*); +extern void SL_SampleUnsigned(SAMPLOAD*); +extern BOOL SL_LoadSamples(void); +extern SAMPLOAD* SL_RegisterSample(SAMPLE*,int,MREADER*); +extern BOOL SL_Load(void*,SAMPLOAD*,ULONG); +extern BOOL SL_Init(SAMPLOAD*); +extern void SL_Exit(SAMPLOAD*); + +/*========== Internal module representation (UniMod) interface */ + +/* number of notes in an octave */ +#define OCTAVE 12 + +extern void UniSetRow(UBYTE*); +extern UBYTE UniGetByte(void); +extern UWORD UniGetWord(void); +extern UBYTE* UniFindRow(UBYTE*,UWORD); +extern void UniSkipOpcode(void); +extern void UniReset(void); +extern void UniWriteByte(UBYTE); +extern void UniWriteWord(UWORD); +extern void UniNewline(void); +extern UBYTE* UniDup(void); +extern BOOL UniInit(void); +extern void UniCleanup(void); +extern void UniEffect(UWORD,UWORD); +#define UniInstrument(x) UniEffect(UNI_INSTRUMENT,x) +#define UniNote(x) UniEffect(UNI_NOTE,x) +extern void UniPTEffect(UBYTE,UBYTE); +extern void UniVolEffect(UWORD,UBYTE); + +/*========== Module Commands */ + +enum { + /* Simple note */ + UNI_NOTE = 1, + /* Instrument change */ + UNI_INSTRUMENT, + /* Protracker effects */ + UNI_PTEFFECT0, /* arpeggio */ + UNI_PTEFFECT1, /* porta up */ + UNI_PTEFFECT2, /* porta down */ + UNI_PTEFFECT3, /* porta to note */ + UNI_PTEFFECT4, /* vibrato */ + UNI_PTEFFECT5, /* dual effect 3+A */ + UNI_PTEFFECT6, /* dual effect 4+A */ + UNI_PTEFFECT7, /* tremolo */ + UNI_PTEFFECT8, /* pan */ + UNI_PTEFFECT9, /* sample offset */ + UNI_PTEFFECTA, /* volume slide */ + UNI_PTEFFECTB, /* pattern jump */ + UNI_PTEFFECTC, /* set volume */ + UNI_PTEFFECTD, /* pattern break */ + UNI_PTEFFECTE, /* extended effects */ + UNI_PTEFFECTF, /* set speed */ + /* Scream Tracker effects */ + UNI_S3MEFFECTA, /* set speed */ + UNI_S3MEFFECTD, /* volume slide */ + UNI_S3MEFFECTE, /* porta down */ + UNI_S3MEFFECTF, /* porta up */ + UNI_S3MEFFECTI, /* tremor */ + UNI_S3MEFFECTQ, /* retrig */ + UNI_S3MEFFECTR, /* tremolo */ + UNI_S3MEFFECTT, /* set tempo */ + UNI_S3MEFFECTU, /* fine vibrato */ + UNI_KEYOFF, /* note off */ + /* Fast Tracker effects */ + UNI_KEYFADE, /* note fade */ + UNI_VOLEFFECTS, /* volume column effects */ + UNI_XMEFFECT4, /* vibrato */ + UNI_XMEFFECT6, /* dual effect 4+A */ + UNI_XMEFFECTA, /* volume slide */ + UNI_XMEFFECTE1, /* fine porta up */ + UNI_XMEFFECTE2, /* fine porta down */ + UNI_XMEFFECTEA, /* fine volume slide up */ + UNI_XMEFFECTEB, /* fine volume slide down */ + UNI_XMEFFECTG, /* set global volume */ + UNI_XMEFFECTH, /* global volume slide */ + UNI_XMEFFECTL, /* set envelope position */ + UNI_XMEFFECTP, /* pan slide */ + UNI_XMEFFECTX1, /* extra fine porta up */ + UNI_XMEFFECTX2, /* extra fine porta down */ + /* Impulse Tracker effects */ + UNI_ITEFFECTG, /* porta to note */ + UNI_ITEFFECTH, /* vibrato */ + UNI_ITEFFECTI, /* tremor (xy not incremented) */ + UNI_ITEFFECTM, /* set channel volume */ + UNI_ITEFFECTN, /* slide / fineslide channel volume */ + UNI_ITEFFECTP, /* slide / fineslide channel panning */ + UNI_ITEFFECTT, /* slide tempo */ + UNI_ITEFFECTU, /* fine vibrato */ + UNI_ITEFFECTW, /* slide / fineslide global volume */ + UNI_ITEFFECTY, /* panbrello */ + UNI_ITEFFECTZ, /* resonant filters */ + UNI_ITEFFECTS0, + /* UltraTracker effects */ + UNI_ULTEFFECT9, /* Sample fine offset */ + /* OctaMED effects */ + UNI_MEDSPEED, + UNI_MEDEFFECTF1, /* play note twice */ + UNI_MEDEFFECTF2, /* delay note */ + UNI_MEDEFFECTF3, /* play note three times */ + /* Oktalyzer effects */ + UNI_OKTARP, /* arpeggio */ + + UNI_LAST +}; + +extern UWORD unioperands[UNI_LAST]; + +/* IT / S3M Extended SS effects: */ +enum { + SS_GLISSANDO = 1, + SS_FINETUNE, + SS_VIBWAVE, + SS_TREMWAVE, + SS_PANWAVE, + SS_FRAMEDELAY, + SS_S7EFFECTS, + SS_PANNING, + SS_SURROUND, + SS_HIOFFSET, + SS_PATLOOP, + SS_NOTECUT, + SS_NOTEDELAY, + SS_PATDELAY +}; + +/* IT Volume column effects */ +enum { + VOL_VOLUME = 1, + VOL_PANNING, + VOL_VOLSLIDE, + VOL_PITCHSLIDEDN, + VOL_PITCHSLIDEUP, + VOL_PORTAMENTO, + VOL_VIBRATO +}; + +/* IT resonant filter information */ + +#define UF_MAXMACRO 0x10 +#define UF_MAXFILTER 0x100 + +#define FILT_CUT 0x80 +#define FILT_RESONANT 0x81 + +typedef struct FILTER { + UBYTE filter,inf; +} FILTER; + +/*========== Instruments */ + +/* Instrument format flags */ +#define IF_OWNPAN 1 +#define IF_PITCHPAN 2 + +/* Envelope flags: */ +#define EF_ON 1 +#define EF_SUSTAIN 2 +#define EF_LOOP 4 +#define EF_VOLENV 8 + +/* New Note Action Flags */ +#define NNA_CUT 0 +#define NNA_CONTINUE 1 +#define NNA_OFF 2 +#define NNA_FADE 3 + +#define NNA_MASK 3 + +#define DCT_OFF 0 +#define DCT_NOTE 1 +#define DCT_SAMPLE 2 +#define DCT_INST 3 + +#define DCA_CUT 0 +#define DCA_OFF 1 +#define DCA_FADE 2 + +#define KEY_KICK 0 +#define KEY_OFF 1 +#define KEY_FADE 2 +#define KEY_KILL (KEY_OFF|KEY_FADE) + +#define KICK_ABSENT 0 +#define KICK_NOTE 1 +#define KICK_KEYOFF 2 +#define KICK_ENV 4 + +#define AV_IT 1 /* IT vs. XM vibrato info */ + +/*========== Playing */ + +#define POS_NONE (-2) /* no loop position defined */ + +#define LAST_PATTERN (UWORD)(-1) /* special ``end of song'' pattern */ + +typedef struct ENVPR { + UBYTE flg; /* envelope flag */ + UBYTE pts; /* number of envelope points */ + UBYTE susbeg; /* envelope sustain index begin */ + UBYTE susend; /* envelope sustain index end */ + UBYTE beg; /* envelope loop begin */ + UBYTE end; /* envelope loop end */ + SWORD p; /* current envelope counter */ + UWORD a; /* envelope index a */ + UWORD b; /* envelope index b */ + ENVPT* env; /* envelope points */ +} ENVPR; + +typedef struct MP_CHANNEL { + INSTRUMENT* i; + SAMPLE* s; + UBYTE sample; /* which sample number */ + UBYTE note; /* the audible note as heard, direct rep of period */ + SWORD outvolume; /* output volume (vol + sampcol + instvol) */ + SBYTE chanvol; /* channel's "global" volume */ + UWORD fadevol; /* fading volume rate */ + SWORD panning; /* panning position */ + UBYTE kick; /* if true = sample has to be restarted */ + UWORD period; /* period to play the sample at */ + UBYTE nna; /* New note action type + master/slave flags */ + + UBYTE volflg; /* volume envelope settings */ + UBYTE panflg; /* panning envelope settings */ + UBYTE pitflg; /* pitch envelope settings */ + + UBYTE keyoff; /* if true = fade out and stuff */ + SWORD handle; /* which sample-handle */ + UBYTE notedelay; /* (used for note delay) */ + SLONG start; /* The starting byte index in the sample */ +} MP_CHANNEL; + +typedef struct MP_CONTROL { + struct MP_CHANNEL main; + + struct MP_VOICE *slave; /* Audio Slave of current effects control channel */ + + UBYTE slavechn; /* Audio Slave of current effects control channel */ + UBYTE muted; /* if set, channel not played */ + UWORD ultoffset; /* fine sample offset memory */ + UBYTE anote; /* the note that indexes the audible */ + UBYTE oldnote; + SWORD ownper; + SWORD ownvol; + UBYTE dca; /* duplicate check action */ + UBYTE dct; /* duplicate check type */ + UBYTE* row; /* row currently playing on this channel */ + SBYTE retrig; /* retrig value (0 means don't retrig) */ + ULONG speed; /* what finetune to use */ + SWORD volume; /* amiga volume (0 t/m 64) to play the sample at */ + + SWORD tmpvolume; /* tmp volume */ + UWORD tmpperiod; /* tmp period */ + UWORD wantedperiod; /* period to slide to (with effect 3 or 5) */ + + UBYTE arpmem; /* arpeggio command memory */ + UBYTE pansspd; /* panslide speed */ + UWORD slidespeed; + UWORD portspeed; /* noteslide speed (toneportamento) */ + + UBYTE s3mtremor; /* s3m tremor (effect I) counter */ + UBYTE s3mtronof; /* s3m tremor ontime/offtime */ + UBYTE s3mvolslide; /* last used volslide */ + SBYTE sliding; + UBYTE s3mrtgspeed; /* last used retrig speed */ + UBYTE s3mrtgslide; /* last used retrig slide */ + + UBYTE glissando; /* glissando (0 means off) */ + UBYTE wavecontrol; + + SBYTE vibpos; /* current vibrato position */ + UBYTE vibspd; /* "" speed */ + UBYTE vibdepth; /* "" depth */ + + SBYTE trmpos; /* current tremolo position */ + UBYTE trmspd; /* "" speed */ + UBYTE trmdepth; /* "" depth */ + + UBYTE fslideupspd; + UBYTE fslidednspd; + UBYTE fportupspd; /* fx E1 (extra fine portamento up) data */ + UBYTE fportdnspd; /* fx E2 (extra fine portamento dn) data */ + UBYTE ffportupspd; /* fx X1 (extra fine portamento up) data */ + UBYTE ffportdnspd; /* fx X2 (extra fine portamento dn) data */ + + ULONG hioffset; /* last used high order of sample offset */ + UWORD soffset; /* last used low order of sample-offset (effect 9) */ + + UBYTE sseffect; /* last used Sxx effect */ + UBYTE ssdata; /* last used Sxx data info */ + UBYTE chanvolslide; /* last used channel volume slide */ + + UBYTE panbwave; /* current panbrello waveform */ + UBYTE panbpos; /* current panbrello position */ + SBYTE panbspd; /* "" speed */ + UBYTE panbdepth; /* "" depth */ + + UWORD newsamp; /* set to 1 upon a sample / inst change */ + UBYTE voleffect; /* Volume Column Effect Memory as used by IT */ + UBYTE voldata; /* Volume Column Data Memory */ + + SWORD pat_reppos; /* patternloop position */ + UWORD pat_repcnt; /* times to loop */ +} MP_CONTROL; + +/* Used by NNA only player (audio control. AUDTMP is used for full effects + control). */ +typedef struct MP_VOICE { + struct MP_CHANNEL main; + + ENVPR venv; + ENVPR penv; + ENVPR cenv; + + UWORD avibpos; /* autovibrato pos */ + UWORD aswppos; /* autovibrato sweep pos */ + + ULONG totalvol; /* total volume of channel (before global mixings) */ + + BOOL mflag; + SWORD masterchn; + UWORD masterperiod; + + MP_CONTROL* master; /* index of "master" effects channel */ +} MP_VOICE; + +/*========== Loaders */ + +typedef struct MLOADER { +struct MLOADER* next; + CHAR* type; + CHAR* version; + BOOL (*Init)(void); + BOOL (*Test)(void); + BOOL (*Load)(BOOL); + void (*Cleanup)(void); + CHAR* (*LoadTitle)(void); +} MLOADER; + +/* internal loader variables */ +extern MREADER* modreader; +extern UWORD finetune[16]; +extern MODULE of; /* static unimod loading space */ +extern UWORD npertab[7*OCTAVE]; /* used by the original MOD loaders */ + +extern SBYTE remap[UF_MAXCHAN]; /* for removing empty channels */ +extern UBYTE* poslookup; /* lookup table for pattern jumps after + blank pattern removal */ +extern UBYTE poslookupcnt; +extern UWORD* origpositions; + +extern BOOL filters; /* resonant filters in use */ +extern UBYTE activemacro; /* active midi macro number for Sxx */ +extern UBYTE filtermacros[UF_MAXMACRO]; /* midi macro settings */ +extern FILTER filtersettings[UF_MAXFILTER]; /* computed filter settings */ + +extern int* noteindex; + +/*========== Internal loader interface */ + +extern BOOL ReadComment(UWORD); +extern BOOL ReadLinedComment(UWORD,UWORD); +extern BOOL AllocPositions(int); +extern BOOL AllocPatterns(void); +extern BOOL AllocTracks(void); +extern BOOL AllocInstruments(void); +extern BOOL AllocSamples(void); +extern CHAR* DupStr(CHAR*,UWORD,BOOL); + +/* loader utility functions */ +extern int* AllocLinear(void); +extern void FreeLinear(void); +extern int speed_to_finetune(ULONG,int); +extern void S3MIT_ProcessCmd(UBYTE,UBYTE,unsigned int); +extern void S3MIT_CreateOrders(BOOL); + +/* flags for S3MIT_ProcessCmd */ +#define S3MIT_OLDSTYLE 1 /* behave as old scream tracker */ +#define S3MIT_IT 2 /* behave as impulse tracker */ +#define S3MIT_SCREAM 4 /* enforce scream tracker specific limits */ + +/* used to convert c4spd to linear XM periods (IT and IMF loaders). */ +extern UWORD getlinearperiod(UWORD,ULONG); +extern ULONG getfrequency(UWORD,ULONG); + +/* loader shared data */ +#define STM_NTRACKERS 3 +extern CHAR *STM_Signatures[STM_NTRACKERS]; + +/*========== Player interface */ + +extern BOOL Player_Init(MODULE*); +extern void Player_Exit(MODULE*); +extern void Player_HandleTick(void); + +/*========== Drivers */ + +/* max. number of handles a driver has to provide. (not strict) */ +#define MAXSAMPLEHANDLES 384 + +/* These variables can be changed at ANY time and results will be immediate */ +extern UWORD md_bpm; /* current song / hardware BPM rate */ + +/* Variables below can be changed via MD_SetNumVoices at any time. However, a + call to MD_SetNumVoicess while the driver is active will cause the sound to + skip slightly. */ +extern UBYTE md_numchn; /* number of song + sound effects voices */ +extern UBYTE md_sngchn; /* number of song voices */ +extern UBYTE md_sfxchn; /* number of sound effects voices */ +extern UBYTE md_hardchn; /* number of hardware mixed voices */ +extern UBYTE md_softchn; /* number of software mixed voices */ + +/* This is for use by the hardware drivers only. It points to the registered + tickhandler function. */ +extern void (*md_player)(void); + +extern SWORD MD_SampleLoad(SAMPLOAD*,int); +extern void MD_SampleUnload(SWORD); +extern ULONG MD_SampleSpace(int); +extern ULONG MD_SampleLength(int,SAMPLE*); + +/* uLaw conversion */ +extern void unsignedtoulaw(char *,int); + +/* Parameter extraction helper */ +extern CHAR *MD_GetAtom(CHAR*,CHAR*,BOOL); + +/* Internal software mixer stuff */ +extern void VC_SetupPointers(void); +extern BOOL VC1_Init(void); +extern BOOL VC2_Init(void); + +#if defined(unix) || defined(__APPLE__) && defined(__MACH__) +/* POSIX helper functions */ +extern BOOL MD_Access(CHAR *); +extern BOOL MD_DropPrivileges(void); +#endif + +/* Macro to define a missing driver, yet allowing binaries to dynamically link + with the library without missing symbol errors */ +#define MISSING(a) MDRIVER a = { NULL, NULL, NULL, 0, 0 } + +/*========== Prototypes for non-MT safe versions of some public functions */ + +extern void _mm_registerdriver(struct MDRIVER*); +extern void _mm_registerloader(struct MLOADER*); +extern BOOL MikMod_Active_internal(void); +extern void MikMod_DisableOutput_internal(void); +extern BOOL MikMod_EnableOutput_internal(void); +extern void MikMod_Exit_internal(void); +extern BOOL MikMod_SetNumVoices_internal(int,int); +extern void Player_Exit_internal(MODULE*); +extern void Player_Stop_internal(void); +extern BOOL Player_Paused_internal(void); +extern void Sample_Free_internal(SAMPLE*); +extern void Voice_Play_internal(SBYTE,SAMPLE*,ULONG); +extern void Voice_SetFrequency_internal(SBYTE,ULONG); +extern void Voice_SetPanning_internal(SBYTE,ULONG); +extern void Voice_SetVolume_internal(SBYTE,UWORD); +extern void Voice_Stop_internal(SBYTE); +extern BOOL Voice_Stopped_internal(SBYTE); + +#ifdef __cplusplus +} +#endif + +#endif + +/* ex:set ts=4: */ diff --git a/src/libs/mikmod/mloader.c b/src/libs/mikmod/mloader.c new file mode 100644 index 0000000..494334c --- /dev/null +++ b/src/libs/mikmod/mloader.c @@ -0,0 +1,607 @@ +/* MikMod sound library + (c) 1998, 1999, 2000, 2001, 2002 Miodrag Vallat and others - see file + AUTHORS for complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + These routines are used to access the available module loaders + +==============================================================================*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#ifdef HAVE_MEMORY_H +#include <memory.h> +#endif +#include <string.h> + +#include "mikmod_internals.h" + +#ifdef SUNOS +extern int fprintf(FILE *, const char *, ...); +#endif + + MREADER *modreader; + MODULE of; + +static MLOADER *firstloader=NULL; + +UWORD finetune[16]={ + 8363,8413,8463,8529,8581,8651,8723,8757, + 7895,7941,7985,8046,8107,8169,8232,8280 +}; + +MIKMODAPI CHAR* MikMod_InfoLoader(void) +{ + int len=0; + MLOADER *l; + CHAR *list=NULL; + + MUTEX_LOCK(lists); + /* compute size of buffer */ + for(l=firstloader;l;l=l->next) len+=1+(l->next?1:0)+strlen(l->version); + + if(len) + if((list=MikMod_malloc(len*sizeof(CHAR)))) { + list[0]=0; + /* list all registered module loders */ + for(l=firstloader;l;l=l->next) + sprintf(list,(l->next)?"%s%s\n":"%s%s",list,l->version); + } + MUTEX_UNLOCK(lists); + return list; +} + +void _mm_registerloader(MLOADER* ldr) +{ + MLOADER *cruise=firstloader; + + if(cruise) { + while(cruise->next) cruise = cruise->next; + cruise->next=ldr; + } else + firstloader=ldr; +} + +MIKMODAPI void MikMod_RegisterLoader(struct MLOADER* ldr) +{ + /* if we try to register an invalid loader, or an already registered loader, + ignore this attempt */ + if ((!ldr)||(ldr->next)) + return; + + MUTEX_LOCK(lists); + _mm_registerloader(ldr); + MUTEX_UNLOCK(lists); +} + +BOOL ReadComment(UWORD len) +{ + if(len) { + int i; + + if(!(of.comment=(CHAR*)MikMod_malloc(len+1))) return 0; + _mm_read_UBYTES(of.comment,len,modreader); + + /* translate IT linefeeds */ + for(i=0;i<len;i++) + if(of.comment[i]=='\r') of.comment[i]='\n'; + + of.comment[len]=0; /* just in case */ + } + if(!of.comment[0]) { + MikMod_free(of.comment); + of.comment=NULL; + } + return 1; +} + +BOOL ReadLinedComment(UWORD len,UWORD linelen) +{ + CHAR *tempcomment,*line,*storage; + UWORD total=0,t,lines; + int i; + + lines = (len + linelen - 1) / linelen; + if (len) { + if(!(tempcomment=(CHAR*)MikMod_malloc(len+1))) return 0; + if(!(storage=(CHAR*)MikMod_malloc(linelen+1))) { + MikMod_free(tempcomment); + return 0; + } + memset(tempcomment, ' ', len); + _mm_read_UBYTES(tempcomment,len,modreader); + + /* compute message length */ + for(line=tempcomment,total=t=0;t<lines;t++,line+=linelen) { + for(i=linelen;(i>=0)&&(line[i]==' ');i--) line[i]=0; + for(i=0;i<linelen;i++) if (!line[i]) break; + total+=1+i; + } + + if(total>lines) { + if(!(of.comment=(CHAR*)MikMod_malloc(total+1))) { + MikMod_free(storage); + MikMod_free(tempcomment); + return 0; + } + + /* convert message */ + for(line=tempcomment,t=0;t<lines;t++,line+=linelen) { + for(i=0;i<linelen;i++) if(!(storage[i]=line[i])) break; + storage[i]=0; /* if (i==linelen) */ + strcat(of.comment,storage);strcat(of.comment,"\r"); + } + MikMod_free(storage); + MikMod_free(tempcomment); + } + } + return 1; +} + +BOOL AllocPositions(int total) +{ + if(!total) { + _mm_errno=MMERR_NOT_A_MODULE; + return 0; + } + if(!(of.positions=MikMod_calloc(total,sizeof(UWORD)))) return 0; + return 1; +} + +BOOL AllocPatterns(void) +{ + int s,t,tracks = 0; + + if((!of.numpat)||(!of.numchn)) { + _mm_errno=MMERR_NOT_A_MODULE; + return 0; + } + /* Allocate track sequencing array */ + if(!(of.patterns=(UWORD*)MikMod_calloc((ULONG)(of.numpat+1)*of.numchn,sizeof(UWORD)))) return 0; + if(!(of.pattrows=(UWORD*)MikMod_calloc(of.numpat+1,sizeof(UWORD)))) return 0; + + for(t=0;t<=of.numpat;t++) { + of.pattrows[t]=64; + for(s=0;s<of.numchn;s++) + of.patterns[(t*of.numchn)+s]=tracks++; + } + + return 1; +} + +BOOL AllocTracks(void) +{ + if(!of.numtrk) { + _mm_errno=MMERR_NOT_A_MODULE; + return 0; + } + if(!(of.tracks=(UBYTE **)MikMod_calloc(of.numtrk,sizeof(UBYTE *)))) return 0; + return 1; +} + +BOOL AllocInstruments(void) +{ + int t,n; + + if(!of.numins) { + _mm_errno=MMERR_NOT_A_MODULE; + return 0; + } + if(!(of.instruments=(INSTRUMENT*)MikMod_calloc(of.numins,sizeof(INSTRUMENT)))) + return 0; + + for(t=0;t<of.numins;t++) { + for(n=0;n<INSTNOTES;n++) { + /* Init note / sample lookup table */ + of.instruments[t].samplenote[n] = n; + of.instruments[t].samplenumber[n] = t; + } + of.instruments[t].globvol = 64; + } + return 1; +} + +BOOL AllocSamples(void) +{ + UWORD u; + + if(!of.numsmp) { + _mm_errno=MMERR_NOT_A_MODULE; + return 0; + } + if(!(of.samples=(SAMPLE*)MikMod_calloc(of.numsmp,sizeof(SAMPLE)))) return 0; + + for(u=0;u<of.numsmp;u++) { + of.samples[u].panning = 128; /* center */ + of.samples[u].handle = -1; + of.samples[u].globvol = 64; + of.samples[u].volume = 64; + } + return 1; +} + +static BOOL ML_LoadSamples(void) +{ + SAMPLE *s; + int u; + + for(u=of.numsmp,s=of.samples;u;u--,s++) + if(s->length) SL_RegisterSample(s,MD_MUSIC,modreader); + + return 1; +} + +/* Creates a CSTR out of a character buffer of 'len' bytes, but strips any + terminating non-printing characters like 0, spaces etc. */ +CHAR *DupStr(CHAR* s,UWORD len,BOOL strict) +{ + UWORD t; + CHAR *d=NULL; + + /* Scan for last printing char in buffer [includes high ascii up to 254] */ + while(len) { + if(s[len-1]>0x20) break; + len--; + } + + /* Scan forward for possible NULL character */ + if(strict) { + for(t=0;t<len;t++) if (!s[t]) break; + if (t<len) len=t; + } + + /* When the buffer wasn't completely empty, allocate a cstring and copy the + buffer into that string, except for any control-chars */ + if((d=(CHAR*)MikMod_malloc(sizeof(CHAR)*(len+1)))) { + for(t=0;t<len;t++) d[t]=(s[t]<32)?'.':s[t]; + d[len]=0; + } + return d; +} + +static void ML_XFreeSample(SAMPLE *s) +{ + if(s->handle>=0) + MD_SampleUnload(s->handle); + if(s->samplename) MikMod_free(s->samplename); +} + +static void ML_XFreeInstrument(INSTRUMENT *i) +{ + if(i->insname) MikMod_free(i->insname); +} + +static void ML_FreeEx(MODULE *mf) +{ + UWORD t; + + if(mf->songname) MikMod_free(mf->songname); + if(mf->comment) MikMod_free(mf->comment); + + if(mf->modtype) MikMod_free(mf->modtype); + if(mf->positions) MikMod_free(mf->positions); + if(mf->patterns) MikMod_free(mf->patterns); + if(mf->pattrows) MikMod_free(mf->pattrows); + + if(mf->tracks) { + for(t=0;t<mf->numtrk;t++) + if(mf->tracks[t]) MikMod_free(mf->tracks[t]); + MikMod_free(mf->tracks); + } + if(mf->instruments) { + for(t=0;t<mf->numins;t++) + ML_XFreeInstrument(&mf->instruments[t]); + MikMod_free(mf->instruments); + } + if(mf->samples) { + for(t=0;t<mf->numsmp;t++) + if(mf->samples[t].length) ML_XFreeSample(&mf->samples[t]); + MikMod_free(mf->samples); + } + memset(mf,0,sizeof(MODULE)); + if(mf!=&of) MikMod_free(mf); +} + +static MODULE *ML_AllocUniMod(void) +{ + MODULE *mf; + + return (mf=MikMod_malloc(sizeof(MODULE))); +} + +void Player_Free_internal(MODULE *mf) +{ + if(mf) { + Player_Exit_internal(mf); + ML_FreeEx(mf); + } +} + +MIKMODAPI void Player_Free(MODULE *mf) +{ + MUTEX_LOCK(vars); + Player_Free_internal(mf); + MUTEX_UNLOCK(vars); +} + +static CHAR* Player_LoadTitle_internal(MREADER *reader) +{ + MLOADER *l; + + modreader=reader; + _mm_errno = 0; + _mm_critical = 0; + _mm_iobase_setcur(modreader); + + /* Try to find a loader that recognizes the module */ + for(l=firstloader;l;l=l->next) { + _mm_rewind(modreader); + if(l->Test()) break; + } + + if(!l) { + _mm_errno = MMERR_NOT_A_MODULE; + if(_mm_errorhandler) _mm_errorhandler(); + return NULL; + } + + return l->LoadTitle(); +} + +MIKMODAPI CHAR* Player_LoadTitleFP(FILE *fp) +{ + CHAR* result=NULL; + MREADER* reader; + + if(fp && (reader=_mm_new_file_reader(fp))) { + MUTEX_LOCK(lists); + result=Player_LoadTitle_internal(reader); + MUTEX_UNLOCK(lists); + _mm_delete_file_reader(reader); + } + return result; +} + +MIKMODAPI CHAR* Player_LoadTitleMem(const char *buffer,int len) +{ + CHAR *result=NULL; + MREADER* reader; + + if ((reader=_mm_new_mem_reader(buffer,len))) + { + MUTEX_LOCK(lists); + result=Player_LoadTitle_internal(reader); + MUTEX_UNLOCK(lists); + _mm_delete_mem_reader(reader); + } + + + return result; +} + +MIKMODAPI CHAR* Player_LoadTitleGeneric(MREADER *reader) +{ + CHAR *result=NULL; + + if (reader) { + MUTEX_LOCK(lists); + result=Player_LoadTitle_internal(reader); + MUTEX_UNLOCK(lists); + } + return result; +} + +MIKMODAPI CHAR* Player_LoadTitle(CHAR* filename) +{ + CHAR* result=NULL; + FILE* fp; + MREADER* reader; + + if((fp=_mm_fopen(filename,"rb"))) { + if((reader=_mm_new_file_reader(fp))) { + MUTEX_LOCK(lists); + result=Player_LoadTitle_internal(reader); + MUTEX_UNLOCK(lists); + _mm_delete_file_reader(reader); + } + _mm_fclose(fp); + } + return result; +} + +/* Loads a module given an reader */ +MODULE* Player_LoadGeneric_internal(MREADER *reader,int maxchan,BOOL curious) +{ + int t; + MLOADER *l; + BOOL ok; + MODULE *mf; + + modreader = reader; + _mm_errno = 0; + _mm_critical = 0; + _mm_iobase_setcur(modreader); + + /* Try to find a loader that recognizes the module */ + for(l=firstloader;l;l=l->next) { + _mm_rewind(modreader); + if(l->Test()) break; + } + + if(!l) { + _mm_errno = MMERR_NOT_A_MODULE; + if(_mm_errorhandler) _mm_errorhandler(); + _mm_rewind(modreader);_mm_iobase_revert(modreader); + return NULL; + } + + /* init unitrk routines */ + if(!UniInit()) { + if(_mm_errorhandler) _mm_errorhandler(); + _mm_rewind(modreader);_mm_iobase_revert(modreader); + return NULL; + } + + /* init the module structure with vanilla settings */ + memset(&of,0,sizeof(MODULE)); + of.bpmlimit = 33; + of.initvolume = 128; + for (t = 0; t < UF_MAXCHAN; t++) of.chanvol[t] = 64; + for (t = 0; t < UF_MAXCHAN; t++) + of.panning[t] = ((t + 1) & 2) ? PAN_RIGHT : PAN_LEFT; + + /* init module loader and load the header / patterns */ + if (!l->Init || l->Init()) { + _mm_rewind(modreader); + ok = l->Load(curious); + if (ok) { + /* propagate inflags=flags for in-module samples */ + for (t = 0; t < of.numsmp; t++) + if (of.samples[t].inflags == 0) + of.samples[t].inflags = of.samples[t].flags; + } + } else + ok = 0; + + /* free loader and unitrk allocations */ + if (l->Cleanup) l->Cleanup(); + UniCleanup(); + + if(!ok) { + ML_FreeEx(&of); + if(_mm_errorhandler) _mm_errorhandler(); + _mm_rewind(modreader);_mm_iobase_revert(modreader); + return NULL; + } + + if(!ML_LoadSamples()) { + ML_FreeEx(&of); + if(_mm_errorhandler) _mm_errorhandler(); + _mm_rewind(modreader);_mm_iobase_revert(modreader); + return NULL; + } + + if(!(mf=ML_AllocUniMod())) { + ML_FreeEx(&of); + _mm_rewind(modreader);_mm_iobase_revert(modreader); + if(_mm_errorhandler) _mm_errorhandler(); + return NULL; + } + + /* If the module doesn't have any specific panning, create a + MOD-like panning, with the channels half-separated. */ + if (!(of.flags & UF_PANNING)) + for (t = 0; t < of.numchn; t++) + of.panning[t] = ((t + 1) & 2) ? PAN_HALFRIGHT : PAN_HALFLEFT; + + /* Copy the static MODULE contents into the dynamic MODULE struct. */ + memcpy(mf,&of,sizeof(MODULE)); + + if(maxchan>0) { + if(!(mf->flags&UF_NNA)&&(mf->numchn<maxchan)) + maxchan = mf->numchn; + else + if((mf->numvoices)&&(mf->numvoices<maxchan)) + maxchan = mf->numvoices; + + if(maxchan<mf->numchn) mf->flags |= UF_NNA; + + if(MikMod_SetNumVoices_internal(maxchan,-1)) { + _mm_iobase_revert(modreader); + Player_Free(mf); + return NULL; + } + } + if(SL_LoadSamples()) { + _mm_iobase_revert(modreader); + Player_Free_internal(mf); + return NULL; + } + if(Player_Init(mf)) { + _mm_iobase_revert(modreader); + Player_Free_internal(mf); + mf=NULL; + } + _mm_iobase_revert(modreader); + return mf; +} + +MIKMODAPI MODULE* Player_LoadGeneric(MREADER *reader,int maxchan,BOOL curious) +{ + MODULE* result; + + MUTEX_LOCK(vars); + MUTEX_LOCK(lists); + result=Player_LoadGeneric_internal(reader,maxchan,curious); + MUTEX_UNLOCK(lists); + MUTEX_UNLOCK(vars); + + return result; +} + +MIKMODAPI MODULE* Player_LoadMem(const char *buffer,int len,int maxchan,BOOL curious) +{ + MODULE* result=NULL; + MREADER* reader; + + if ((reader=_mm_new_mem_reader(buffer, len))) { + result=Player_LoadGeneric(reader,maxchan,curious); + _mm_delete_mem_reader(reader); + } + return result; +} + +/* Loads a module given a file pointer. + File is loaded from the current file seek position. */ +MIKMODAPI MODULE* Player_LoadFP(FILE* fp,int maxchan,BOOL curious) +{ + MODULE* result=NULL; + struct MREADER* reader=_mm_new_file_reader (fp); + + if (reader) { + result=Player_LoadGeneric(reader,maxchan,curious); + _mm_delete_file_reader(reader); + } + return result; +} + +/* Open a module via its filename. The loader will initialize the specified + song-player 'player'. */ +MIKMODAPI MODULE* Player_Load(CHAR* filename,int maxchan,BOOL curious) +{ + FILE *fp; + MODULE *mf=NULL; + + if((fp=_mm_fopen(filename,"rb"))) { + mf=Player_LoadFP(fp,maxchan,curious); + _mm_fclose(fp); + } + return mf; +} + +/* ex:set ts=4: */ diff --git a/src/libs/mikmod/mlreg.c b/src/libs/mikmod/mlreg.c new file mode 100644 index 0000000..14f2d7f --- /dev/null +++ b/src/libs/mikmod/mlreg.c @@ -0,0 +1,50 @@ +/* MikMod sound library + (c) 1998, 1999 Miodrag Vallat and others - see file AUTHORS for + complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + Routine for registering all loaders in libmikmod for the current platform. + +==============================================================================*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "mikmod_internals.h" + +void MikMod_RegisterAllLoaders_internal(void) +{ + _mm_registerloader(&load_it); + _mm_registerloader(&load_mod); + _mm_registerloader(&load_s3m); + _mm_registerloader(&load_stm); + _mm_registerloader(&load_xm); +} + +void MikMod_RegisterAllLoaders(void) +{ + MUTEX_LOCK(lists); + MikMod_RegisterAllLoaders_internal(); + MUTEX_UNLOCK(lists); +} +/* ex:set ts=4: */ diff --git a/src/libs/mikmod/mlutil.c b/src/libs/mikmod/mlutil.c new file mode 100644 index 0000000..2ecf64e --- /dev/null +++ b/src/libs/mikmod/mlutil.c @@ -0,0 +1,337 @@ +/* MikMod sound library + (c) 1998, 1999, 2000, 2001 Miodrag Vallat and others - see file AUTHORS + for complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + Utility functions for the module loader + +==============================================================================*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_MEMORY_H +#include <memory.h> +#endif +#include <string.h> + +#include "mikmod_internals.h" + +#ifdef SUNOS +extern int fprintf(FILE *, const char *, ...); +#endif + +/*========== Shared tracker identifiers */ + +CHAR *STM_Signatures[STM_NTRACKERS] = { + "!Scream!", + "BMOD2STM", + "WUZAMOD!" +}; + +CHAR *STM_Version[STM_NTRACKERS] = { + "Screamtracker 2", + "Converted by MOD2STM (STM format)", + "Wuzamod (STM format)" +}; + +/*========== Shared loader variables */ + +SBYTE remap[UF_MAXCHAN]; /* for removing empty channels */ +UBYTE* poslookup=NULL; /* lookup table for pattern jumps after blank + pattern removal */ +UBYTE poslookupcnt; +UWORD* origpositions=NULL; + +BOOL filters; /* resonant filters in use */ +UBYTE activemacro; /* active midi macro number for Sxx,xx<80h */ +UBYTE filtermacros[UF_MAXMACRO]; /* midi macro settings */ +FILTER filtersettings[UF_MAXFILTER]; /* computed filter settings */ + +/*========== Linear periods stuff */ + +int* noteindex=NULL; /* remap value for linear period modules */ +static int noteindexcount=0; + +int *AllocLinear(void) +{ + if(of.numsmp>noteindexcount) { + noteindexcount=of.numsmp; + noteindex=realloc(noteindex,noteindexcount*sizeof(int)); + } + return noteindex; +} + +void FreeLinear(void) +{ + if(noteindex) { + MikMod_free(noteindex); + noteindex=NULL; + } + noteindexcount=0; +} + +int speed_to_finetune(ULONG speed,int sample) +{ + int note=1,finetune=0; + ULONG ctmp=0,tmp; + + speed>>=1; + while((tmp=getfrequency(of.flags,getlinearperiod(note<<1,0)))<speed) { + ctmp=tmp; + note++; + } + + if(tmp!=speed) { + if((tmp-speed)<(speed-ctmp)) + while(tmp>speed) + tmp=getfrequency(of.flags,getlinearperiod(note<<1,--finetune)); + else { + note--; + while(ctmp<speed) + ctmp=getfrequency(of.flags,getlinearperiod(note<<1,++finetune)); + } + } + + noteindex[sample]=note-4*OCTAVE; + return finetune; +} + +/*========== Order stuff */ + +/* handles S3M and IT orders */ +void S3MIT_CreateOrders(BOOL curious) +{ + int t; + + of.numpos = 0; + memset(of.positions,0,poslookupcnt*sizeof(UWORD)); + memset(poslookup,-1,256); + for(t=0;t<poslookupcnt;t++) { + int order=origpositions[t]; + if(order==255) order=LAST_PATTERN; + of.positions[of.numpos]=order; + poslookup[t]=of.numpos; /* bug fix for freaky S3Ms / ITs */ + if(origpositions[t]<254) of.numpos++; + else + /* end of song special order */ + if((order==LAST_PATTERN)&&(!(curious--))) break; + } +} + +/*========== Effect stuff */ + +/* handles S3M and IT effects */ +void S3MIT_ProcessCmd(UBYTE cmd,UBYTE inf,unsigned int flags) +{ + UBYTE hi,lo; + + lo=inf&0xf; + hi=inf>>4; + + /* process S3M / IT specific command structure */ + + if(cmd!=255) { + switch(cmd) { + case 1: /* Axx set speed to xx */ + UniEffect(UNI_S3MEFFECTA,inf); + break; + case 2: /* Bxx position jump */ + if (inf<poslookupcnt) { + /* switch to curious mode if necessary, for example + sympex.it, deep joy.it */ + if(((SBYTE)poslookup[inf]<0)&&(origpositions[inf]!=255)) + S3MIT_CreateOrders(1); + + if(!((SBYTE)poslookup[inf]<0)) + UniPTEffect(0xb,poslookup[inf]); + } + break; + case 3: /* Cxx patternbreak to row xx */ + if ((flags & S3MIT_OLDSTYLE) && !(flags & S3MIT_IT)) + UniPTEffect(0xd,(inf>>4)*10+(inf&0xf)); + else + UniPTEffect(0xd,inf); + break; + case 4: /* Dxy volumeslide */ + UniEffect(UNI_S3MEFFECTD,inf); + break; + case 5: /* Exy toneslide down */ + UniEffect(UNI_S3MEFFECTE,inf); + break; + case 6: /* Fxy toneslide up */ + UniEffect(UNI_S3MEFFECTF,inf); + break; + case 7: /* Gxx Tone portamento, speed xx */ + if (flags & S3MIT_OLDSTYLE) + UniPTEffect(0x3,inf); + else + UniEffect(UNI_ITEFFECTG,inf); + break; + case 8: /* Hxy vibrato */ + if (flags & S3MIT_OLDSTYLE) + UniPTEffect(0x4,inf); + else + UniEffect(UNI_ITEFFECTH,inf); + break; + case 9: /* Ixy tremor, ontime x, offtime y */ + if (flags & S3MIT_OLDSTYLE) + UniEffect(UNI_S3MEFFECTI,inf); + else + UniEffect(UNI_ITEFFECTI,inf); + break; + case 0xa: /* Jxy arpeggio */ + UniPTEffect(0x0,inf); + break; + case 0xb: /* Kxy Dual command H00 & Dxy */ + if (flags & S3MIT_OLDSTYLE) + UniPTEffect(0x4,0); + else + UniEffect(UNI_ITEFFECTH,0); + UniEffect(UNI_S3MEFFECTD,inf); + break; + case 0xc: /* Lxy Dual command G00 & Dxy */ + if (flags & S3MIT_OLDSTYLE) + UniPTEffect(0x3,0); + else + UniEffect(UNI_ITEFFECTG,0); + UniEffect(UNI_S3MEFFECTD,inf); + break; + case 0xd: /* Mxx Set Channel Volume */ + UniEffect(UNI_ITEFFECTM,inf); + break; + case 0xe: /* Nxy Slide Channel Volume */ + UniEffect(UNI_ITEFFECTN,inf); + break; + case 0xf: /* Oxx set sampleoffset xx00h */ + UniPTEffect(0x9,inf); + break; + case 0x10: /* Pxy Slide Panning Commands */ + UniEffect(UNI_ITEFFECTP,inf); + break; + case 0x11: /* Qxy Retrig (+volumeslide) */ + UniWriteByte(UNI_S3MEFFECTQ); + if(inf && !lo && !(flags & S3MIT_OLDSTYLE)) + UniWriteByte(1); + else + UniWriteByte(inf); + break; + case 0x12: /* Rxy tremolo speed x, depth y */ + UniEffect(UNI_S3MEFFECTR,inf); + break; + case 0x13: /* Sxx special commands */ + if (inf>=0xf0) { + /* change resonant filter settings if necessary */ + if((filters)&&((inf&0xf)!=activemacro)) { + activemacro=inf&0xf; + for(inf=0;inf<0x80;inf++) + filtersettings[inf].filter=filtermacros[activemacro]; + } + } else { + /* Scream Tracker does not have samples larger than + 64 Kb, thus doesn't need the SAx effect */ + if ((flags & S3MIT_SCREAM) && ((inf & 0xf0) == 0xa0)) + break; + + UniEffect(UNI_ITEFFECTS0,inf); + } + break; + case 0x14: /* Txx tempo */ + if(inf>=0x20) + UniEffect(UNI_S3MEFFECTT,inf); + else { + if(!(flags & S3MIT_OLDSTYLE)) + /* IT Tempo slide */ + UniEffect(UNI_ITEFFECTT,inf); + } + break; + case 0x15: /* Uxy Fine Vibrato speed x, depth y */ + if(flags & S3MIT_OLDSTYLE) + UniEffect(UNI_S3MEFFECTU,inf); + else + UniEffect(UNI_ITEFFECTU,inf); + break; + case 0x16: /* Vxx Set Global Volume */ + UniEffect(UNI_XMEFFECTG,inf); + break; + case 0x17: /* Wxy Global Volume Slide */ + UniEffect(UNI_ITEFFECTW,inf); + break; + case 0x18: /* Xxx amiga command 8xx */ + if(flags & S3MIT_OLDSTYLE) { + if(inf>128) + UniEffect(UNI_ITEFFECTS0,0x91); /* surround */ + else + UniPTEffect(0x8,(inf==128)?255:(inf<<1)); + } else + UniPTEffect(0x8,inf); + break; + case 0x19: /* Yxy Panbrello speed x, depth y */ + UniEffect(UNI_ITEFFECTY,inf); + break; + case 0x1a: /* Zxx midi/resonant filters */ + if(filtersettings[inf].filter) { + UniWriteByte(UNI_ITEFFECTZ); + UniWriteByte(filtersettings[inf].filter); + UniWriteByte(filtersettings[inf].inf); + } + break; + } + } +} + +/*========== Unitrk stuff */ + +/* Generic effect writing routine */ +void UniEffect(UWORD eff,UWORD dat) +{ + if((!eff)||(eff>=UNI_LAST)) return; + + UniWriteByte(eff); + if(unioperands[eff]==2) + UniWriteWord(dat); + else + UniWriteByte(dat); +} + +/* Appends UNI_PTEFFECTX opcode to the unitrk stream. */ +void UniPTEffect(UBYTE eff, UBYTE dat) +{ +#ifdef MIKMOD_DEBUG + if (eff>=0x10) + fprintf(stderr,"UniPTEffect called with incorrect eff value %d\n",eff); + else +#endif + if((eff)||(dat)||(of.flags&UF_ARPMEM)) UniEffect(UNI_PTEFFECT0+eff,dat); +} + +/* Appends UNI_VOLEFFECT + effect/dat to unistream. */ +void UniVolEffect(UWORD eff,UBYTE dat) +{ + if((eff)||(dat)) { /* don't write empty effect */ + UniWriteByte(UNI_VOLEFFECTS); + UniWriteByte(eff);UniWriteByte(dat); + } +} + +/* ex:set ts=4: */ diff --git a/src/libs/mikmod/mmalloc.c b/src/libs/mikmod/mmalloc.c new file mode 100644 index 0000000..cc8f8d9 --- /dev/null +++ b/src/libs/mikmod/mmalloc.c @@ -0,0 +1,73 @@ +/* MikMod sound library + (c) 1998, 1999 Miodrag Vallat and others - see file AUTHORS for + complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + Dynamic memory routines + +==============================================================================*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "mikmod_internals.h" + +void* MikMod_realloc(void *data, size_t size) +{ + if (data) + return realloc(data, size); + else + return MikMod_malloc(size); +} + +/* Same as malloc, but sets error variable _mm_error when fails */ +void* MikMod_malloc(size_t size) +{ + void *d; + + if(!(d=calloc(1,size))) { + _mm_errno = MMERR_OUT_OF_MEMORY; + if(_mm_errorhandler) _mm_errorhandler(); + } + return d; +} + +/* Same as calloc, but sets error variable _mm_error when fails */ +void* MikMod_calloc(size_t nitems,size_t size) +{ + void *d; + + if(!(d=calloc(nitems,size))) { + _mm_errno = MMERR_OUT_OF_MEMORY; + if(_mm_errorhandler) _mm_errorhandler(); + } + return d; +} + +void MikMod_free(void *p) +{ + if (p) + free(p); +} + +/* ex:set ts=4: */ diff --git a/src/libs/mikmod/mmerror.c b/src/libs/mikmod/mmerror.c new file mode 100644 index 0000000..efbde19 --- /dev/null +++ b/src/libs/mikmod/mmerror.c @@ -0,0 +1,197 @@ +/* MikMod sound library + (c) 1998, 1999, 2000 Miodrag Vallat and others - see file AUTHORS for + complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + Error handling functions. + Register an error handler with _mm_RegisterErrorHandler() and you're all set. + +==============================================================================*/ + +/* + + The global variables _mm_errno, and _mm_critical are set before the error + handler in called. See below for the values of these variables. + +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "mikmod_internals.h" + +CHAR *_mm_errmsg[MMERR_MAX+1] = +{ +/* No error */ + + "No error", + +/* Generic errors */ + + "Could not open requested file", + "Out of memory", + "Dynamic linking failed", + +/* Sample errors */ + + "Out of memory to load sample", + "Out of sample handles to load sample", + "Sample format not recognized", + +/* Module errors */ + + "Failure loading module pattern", + "Failure loading module track", + "Failure loading module header", + "Failure loading sampleinfo", + "Module format not recognized", + "Module sample format not recognized", + "Synthsounds not supported in MED files", + "Compressed sample is invalid", + +/* Driver errors: */ + + "Sound device not detected", + "Device number out of range", + "Software mixer failure", + "Could not open sound device", + "This driver supports 8 bit linear output only", + "This driver supports 16 bit linear output only", + "This driver supports stereo output only", + "This driver supports uLaw output (8 bit mono, 8 kHz) only", + "Unable to set non-blocking mode for audio device", + +/* AudioFile driver errors */ + + "Cannot find suitable AudioFile audio port", + +/* AIX driver errors */ + + "Configuration (init step) of audio device failed", + "Configuration (control step) of audio device failed", + "Configuration (start step) of audio device failed", + +/* ALSA driver errors */ + +/* EsounD driver errors */ + +/* Ultrasound driver errors */ + + "Ultrasound driver only works in 16 bit stereo 44 KHz", + "Ultrasound card could not be reset", + "Could not start Ultrasound timer", + +/* HP driver errors */ + + "Unable to select 16bit-linear sample format", + "Could not select requested sample-rate", + "Could not select requested number of channels", + "Unable to select audio output", + "Unable to get audio description", + "Could not set transmission buffer size", + +/* Open Sound System driver errors */ + + "Could not set fragment size", + "Could not set sample size", + "Could not set mono/stereo setting", + "Could not set sample rate", + +/* SGI driver errors */ + + "Unsupported sample rate", + "Hardware does not support 16 bit sound", + "Hardware does not support 8 bit sound", + "Hardware does not support stereo sound", + "Hardware does not support mono sound", + +/* Sun driver errors */ + + "Sound device initialization failed", + +/* OS/2 drivers errors */ + + "Could not set mixing parameters", + "Could not create playback semaphores", + "Could not create playback timer", + "Could not create playback thread", + +/* DirectSound driver errors */ + + "Could not set playback priority", + "Could not create playback buffers", + "Could not set playback format", + "Could not register callback", + "Could not register event", + "Could not create playback thread", + "Could not initialize playback thread", + +/* Windows Multimedia API driver errors */ + + "Invalid device handle", + "The resource is already allocated", + "Invalid device identifier", + "Unsupported output format", + "Unknown error", + +/* Macintosh driver errors */ + + "Unsupported sample rate", + "Could not start playback", + +/* Invalid error */ + + "Invalid error code" +}; + +MIKMODAPI char *MikMod_strerror(int code) +{ + if ((code<0)||(code>MMERR_MAX)) code=MMERR_MAX+1; + return _mm_errmsg[code]; +} + +/* User installed error callback */ +MikMod_handler_t _mm_errorhandler = NULL; +MIKMODAPI int _mm_errno = 0; +MIKMODAPI BOOL _mm_critical = 0; + +MikMod_handler_t _mm_registererrorhandler(MikMod_handler_t proc) +{ + MikMod_handler_t oldproc=_mm_errorhandler; + + _mm_errorhandler = proc; + return oldproc; +} + +MIKMODAPI MikMod_handler_t MikMod_RegisterErrorHandler(MikMod_handler_t proc) +{ + MikMod_handler_t result; + + MUTEX_LOCK(vars); + result=_mm_registererrorhandler(proc); + MUTEX_UNLOCK(vars); + + return result; +} + +/* ex:set ts=4: */ diff --git a/src/libs/mikmod/mmio.c b/src/libs/mikmod/mmio.c new file mode 100644 index 0000000..0f8a079 --- /dev/null +++ b/src/libs/mikmod/mmio.c @@ -0,0 +1,490 @@ +/* MikMod sound library + (c) 1998, 1999, 2000 Miodrag Vallat and others - see file AUTHORS for + complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + Portable file I/O routines + +==============================================================================*/ + +/* + + The way this module works: + + - _mm_fopen will call the errorhandler [see mmerror.c] in addition to + setting _mm_errno on exit. + - _mm_iobase is for internal use. It is used by Player_LoadFP to + ensure that it works properly with wad files. + - _mm_read_I_* and _mm_read_M_* differ : the first is for reading data + written by a little endian (intel) machine, and the second is for reading + big endian (Mac, RISC, Alpha) machine data. + - _mm_write functions work the same as the _mm_read functions. + - _mm_read_string is for reading binary strings. It is basically the same + as an fread of bytes. + +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include <stdio.h> +#include <string.h> + +//#include "mikmod.h" +#include "mikmod_internals.h" + +#ifdef SUNOS +extern int fclose(FILE *); +extern int fgetc(FILE *); +extern int fputc(int, FILE *); +extern size_t fread(void *, size_t, size_t, FILE *); +extern int fseek(FILE *, long, int); +extern size_t fwrite(const void *, size_t, size_t, FILE *); +#endif + +#define COPY_BUFSIZE 1024 + +/* some prototypes */ +static BOOL _mm_MemReader_Eof(MREADER* reader); +static BOOL _mm_MemReader_Read(MREADER* reader,void* ptr,size_t size); +static int _mm_MemReader_Get(MREADER* reader); +static BOOL _mm_MemReader_Seek(MREADER* reader,long offset,int whence); +static long _mm_MemReader_Tell(MREADER* reader); + + +FILE* _mm_fopen(CHAR* fname,CHAR* attrib) +{ + FILE *fp; + + if(!(fp=fopen(fname,attrib))) { + _mm_errno = MMERR_OPENING_FILE; + if(_mm_errorhandler) _mm_errorhandler(); + } + return fp; +} + +BOOL _mm_FileExists(CHAR* fname) +{ + FILE *fp; + + if(!(fp=fopen(fname,"r"))) return 0; + fclose(fp); + + return 1; +} + +int _mm_fclose(FILE *fp) +{ + return fclose(fp); +} + +/* Sets the current file-position as the new iobase */ +void _mm_iobase_setcur(MREADER* reader) +{ + reader->prev_iobase=reader->iobase; /* store old value in case of revert */ + reader->iobase=reader->Tell(reader); +} + +/* Reverts to the last known iobase value. */ +void _mm_iobase_revert(MREADER* reader) +{ + reader->iobase=reader->prev_iobase; +} + +/*========== File Reader */ + +typedef struct MFILEREADER { + MREADER core; + FILE* file; +} MFILEREADER; + +static BOOL _mm_FileReader_Eof(MREADER* reader) +{ + return feof(((MFILEREADER*)reader)->file); +} + +static BOOL _mm_FileReader_Read(MREADER* reader,void* ptr,size_t size) +{ + return !!fread(ptr,size,1,((MFILEREADER*)reader)->file); +} + +static int _mm_FileReader_Get(MREADER* reader) +{ + return fgetc(((MFILEREADER*)reader)->file); +} + +static BOOL _mm_FileReader_Seek(MREADER* reader,long offset,int whence) +{ + return fseek(((MFILEREADER*)reader)->file, + (whence==SEEK_SET)?offset+reader->iobase:offset,whence); +} + +static long _mm_FileReader_Tell(MREADER* reader) +{ + return ftell(((MFILEREADER*)reader)->file)-reader->iobase; +} + +MREADER *_mm_new_file_reader(FILE* fp) +{ + MFILEREADER* reader=(MFILEREADER*)MikMod_malloc(sizeof(MFILEREADER)); + if (reader) { + reader->core.Eof =&_mm_FileReader_Eof; + reader->core.Read=&_mm_FileReader_Read; + reader->core.Get =&_mm_FileReader_Get; + reader->core.Seek=&_mm_FileReader_Seek; + reader->core.Tell=&_mm_FileReader_Tell; + reader->file=fp; + } + return (MREADER*)reader; +} + +void _mm_delete_file_reader (MREADER* reader) +{ + if(reader) MikMod_free(reader); +} + +/*========== File Writer */ + +typedef struct MFILEWRITER { + MWRITER core; + FILE* file; +} MFILEWRITER; + +static BOOL _mm_FileWriter_Seek(MWRITER* writer,long offset,int whence) +{ + return fseek(((MFILEWRITER*)writer)->file,offset,whence); +} + +static long _mm_FileWriter_Tell(MWRITER* writer) +{ + return ftell(((MFILEWRITER*)writer)->file); +} + +static BOOL _mm_FileWriter_Write(MWRITER* writer,void* ptr,size_t size) +{ + return (fwrite(ptr,size,1,((MFILEWRITER*)writer)->file)==size); +} + +static BOOL _mm_FileWriter_Put(MWRITER* writer,int value) +{ + return fputc(value,((MFILEWRITER*)writer)->file); +} + +MWRITER *_mm_new_file_writer(FILE* fp) +{ + MFILEWRITER* writer=(MFILEWRITER*)MikMod_malloc(sizeof(MFILEWRITER)); + if (writer) { + writer->core.Seek =&_mm_FileWriter_Seek; + writer->core.Tell =&_mm_FileWriter_Tell; + writer->core.Write=&_mm_FileWriter_Write; + writer->core.Put =&_mm_FileWriter_Put; + writer->file=fp; + } + return (MWRITER*) writer; +} + +void _mm_delete_file_writer (MWRITER* writer) +{ + if(writer) MikMod_free (writer); +} + +/*========== Memory Reader */ + + +typedef struct MMEMREADER { + MREADER core; + const void *buffer; + long len; + long pos; +} MMEMREADER; + +void _mm_delete_mem_reader(MREADER* reader) +{ + if (reader) { MikMod_free(reader); } +} + +MREADER *_mm_new_mem_reader(const void *buffer, int len) +{ + MMEMREADER* reader=(MMEMREADER*)MikMod_malloc(sizeof(MMEMREADER)); + if (reader) + { + reader->core.Eof =&_mm_MemReader_Eof; + reader->core.Read=&_mm_MemReader_Read; + reader->core.Get =&_mm_MemReader_Get; + reader->core.Seek=&_mm_MemReader_Seek; + reader->core.Tell=&_mm_MemReader_Tell; + reader->buffer = buffer; + reader->len = len; + reader->pos = 0; + } + return (MREADER*)reader; +} + +static BOOL _mm_MemReader_Eof(MREADER* reader) +{ + if (!reader) { return 1; } + if ( ((MMEMREADER*)reader)->pos > ((MMEMREADER*)reader)->len ) { + return 1; + } + return 0; +} + +static BOOL _mm_MemReader_Read(MREADER* reader,void* ptr,size_t size) +{ + unsigned char *d=ptr; + const unsigned char *s; + + if (!reader) { return 0; } + + if (reader->Eof(reader)) { return 0; } + + s = ((MMEMREADER*)reader)->buffer; + s += ((MMEMREADER*)reader)->pos; + + if ( ((MMEMREADER*)reader)->pos + (long)size > ((MMEMREADER*)reader)->len) + { + ((MMEMREADER*)reader)->pos = ((MMEMREADER*)reader)->len; + return 0; /* not enough remaining bytes */ + } + + ((MMEMREADER*)reader)->pos += (long)size; + + while (size--) + { + *d = *s; + s++; + d++; + } + + return 1; +} + +static int _mm_MemReader_Get(MREADER* reader) +{ + int pos; + + if (reader->Eof(reader)) { return 0; } + + pos = ((MMEMREADER*)reader)->pos; + ((MMEMREADER*)reader)->pos++; + + return ((unsigned char*)(((MMEMREADER*)reader)->buffer))[pos]; +} + +static BOOL _mm_MemReader_Seek(MREADER* reader,long offset,int whence) +{ + if (!reader) { return -1; } + + switch(whence) + { + case SEEK_CUR: + ((MMEMREADER*)reader)->pos += offset; + break; + case SEEK_SET: + ((MMEMREADER*)reader)->pos = offset; + break; + case SEEK_END: + ((MMEMREADER*)reader)->pos = ((MMEMREADER*)reader)->len - offset - 1; + break; + } + if ( ((MMEMREADER*)reader)->pos < 0) { ((MMEMREADER*)reader)->pos = 0; } + if ( ((MMEMREADER*)reader)->pos > ((MMEMREADER*)reader)->len ) { + ((MMEMREADER*)reader)->pos = ((MMEMREADER*)reader)->len; + } + return 0; +} + +static long _mm_MemReader_Tell(MREADER* reader) +{ + if (reader) { + return ((MMEMREADER*)reader)->pos; + } + return 0; +} + +/*========== Write functions */ + +void _mm_write_string(CHAR* data,MWRITER* writer) +{ + if(data) + _mm_write_UBYTES(data,strlen(data),writer); +} + +void _mm_write_M_UWORD(UWORD data,MWRITER* writer) +{ + _mm_write_UBYTE(data>>8,writer); + _mm_write_UBYTE(data&0xff,writer); +} + +void _mm_write_I_UWORD(UWORD data,MWRITER* writer) +{ + _mm_write_UBYTE(data&0xff,writer); + _mm_write_UBYTE(data>>8,writer); +} + +void _mm_write_M_ULONG(ULONG data,MWRITER* writer) +{ + _mm_write_M_UWORD(data>>16,writer); + _mm_write_M_UWORD(data&0xffff,writer); +} + +void _mm_write_I_ULONG(ULONG data,MWRITER* writer) +{ + _mm_write_I_UWORD(data&0xffff,writer); + _mm_write_I_UWORD(data>>16,writer); +} + +void _mm_write_M_SWORD(SWORD data,MWRITER* writer) +{ + _mm_write_M_UWORD((UWORD)data,writer); +} + +void _mm_write_I_SWORD(SWORD data,MWRITER* writer) +{ + _mm_write_I_UWORD((UWORD)data,writer); +} + +void _mm_write_M_SLONG(SLONG data,MWRITER* writer) +{ + _mm_write_M_ULONG((ULONG)data,writer); +} + +void _mm_write_I_SLONG(SLONG data,MWRITER* writer) +{ + _mm_write_I_ULONG((ULONG)data,writer); +} + +#if defined __STDC__ || defined _MSC_VER || defined MPW_C +#define DEFINE_MULTIPLE_WRITE_FUNCTION(type_name,type) \ +void _mm_write_##type_name##S (type *buffer,int number,MWRITER* writer) \ +{ \ + while(number-->0) \ + _mm_write_##type_name(*(buffer++),writer); \ +} +#else +#define DEFINE_MULTIPLE_WRITE_FUNCTION(type_name,type) \ +void _mm_write_/**/type_name/**/S (type *buffer,int number,MWRITER* writer) \ +{ \ + while(number-->0) \ + _mm_write_/**/type_name(*(buffer++),writer); \ +} +#endif + +DEFINE_MULTIPLE_WRITE_FUNCTION(M_SWORD,SWORD) +DEFINE_MULTIPLE_WRITE_FUNCTION(M_UWORD,UWORD) +DEFINE_MULTIPLE_WRITE_FUNCTION(I_SWORD,SWORD) +DEFINE_MULTIPLE_WRITE_FUNCTION(I_UWORD,UWORD) + +DEFINE_MULTIPLE_WRITE_FUNCTION(M_SLONG,SLONG) +DEFINE_MULTIPLE_WRITE_FUNCTION(M_ULONG,ULONG) +DEFINE_MULTIPLE_WRITE_FUNCTION(I_SLONG,SLONG) +DEFINE_MULTIPLE_WRITE_FUNCTION(I_ULONG,ULONG) + +/*========== Read functions */ + +int _mm_read_string(CHAR* buffer,int number,MREADER* reader) +{ + return reader->Read(reader,buffer,number); +} + +UWORD _mm_read_M_UWORD(MREADER* reader) +{ + UWORD result=((UWORD)_mm_read_UBYTE(reader))<<8; + result|=_mm_read_UBYTE(reader); + return result; +} + +UWORD _mm_read_I_UWORD(MREADER* reader) +{ + UWORD result=_mm_read_UBYTE(reader); + result|=((UWORD)_mm_read_UBYTE(reader))<<8; + return result; +} + +ULONG _mm_read_M_ULONG(MREADER* reader) +{ + ULONG result=((ULONG)_mm_read_M_UWORD(reader))<<16; + result|=_mm_read_M_UWORD(reader); + return result; +} + +ULONG _mm_read_I_ULONG(MREADER* reader) +{ + ULONG result=_mm_read_I_UWORD(reader); + result|=((ULONG)_mm_read_I_UWORD(reader))<<16; + return result; +} + +SWORD _mm_read_M_SWORD(MREADER* reader) +{ + return((SWORD)_mm_read_M_UWORD(reader)); +} + +SWORD _mm_read_I_SWORD(MREADER* reader) +{ + return((SWORD)_mm_read_I_UWORD(reader)); +} + +SLONG _mm_read_M_SLONG(MREADER* reader) +{ + return((SLONG)_mm_read_M_ULONG(reader)); +} + +SLONG _mm_read_I_SLONG(MREADER* reader) +{ + return((SLONG)_mm_read_I_ULONG(reader)); +} + +#if defined __STDC__ || defined _MSC_VER || defined MPW_C +#define DEFINE_MULTIPLE_READ_FUNCTION(type_name,type) \ +int _mm_read_##type_name##S (type *buffer,int number,MREADER* reader) \ +{ \ + while(number-->0) \ + *(buffer++)=_mm_read_##type_name(reader); \ + return !reader->Eof(reader); \ +} +#else +#define DEFINE_MULTIPLE_READ_FUNCTION(type_name,type) \ +int _mm_read_/**/type_name/**/S (type *buffer,int number,MREADER* reader) \ +{ \ + while(number-->0) \ + *(buffer++)=_mm_read_/**/type_name(reader); \ + return !reader->Eof(reader); \ +} +#endif + +DEFINE_MULTIPLE_READ_FUNCTION(M_SWORD,SWORD) +DEFINE_MULTIPLE_READ_FUNCTION(M_UWORD,UWORD) +DEFINE_MULTIPLE_READ_FUNCTION(I_SWORD,SWORD) +DEFINE_MULTIPLE_READ_FUNCTION(I_UWORD,UWORD) + +DEFINE_MULTIPLE_READ_FUNCTION(M_SLONG,SLONG) +DEFINE_MULTIPLE_READ_FUNCTION(M_ULONG,ULONG) +DEFINE_MULTIPLE_READ_FUNCTION(I_SLONG,SLONG) +DEFINE_MULTIPLE_READ_FUNCTION(I_ULONG,ULONG) + +/* ex:set ts=4: */ diff --git a/src/libs/mikmod/mplayer.c b/src/libs/mikmod/mplayer.c new file mode 100644 index 0000000..812410a --- /dev/null +++ b/src/libs/mikmod/mplayer.c @@ -0,0 +1,3561 @@ +/* MikMod sound library + (c) 1998, 1999, 2000, 2001 Miodrag Vallat and others - see file AUTHORS + for complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + The Protracker Player Driver + + The protracker driver supports all base Protracker 3.x commands and features. + +==============================================================================*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <string.h> +#include <stdarg.h> +#ifdef SRANDOM_IN_MATH_H +#include <math.h> +#else +#include <stdlib.h> +#endif + +#include "mikmod_internals.h" + +#ifdef SUNOS +extern int fprintf(FILE *, const char *, ...); +extern long int random(void); +#endif + +/* The currently playing module */ +/* This variable should better be static, but it would break the ABI, so this + will wait */ +/*static*/ MODULE *pf = NULL; + +#define HIGH_OCTAVE 2 /* number of above-range octaves */ + +static UWORD oldperiods[OCTAVE*2]={ + 0x6b00, 0x6800, 0x6500, 0x6220, 0x5f50, 0x5c80, + 0x5a00, 0x5740, 0x54d0, 0x5260, 0x5010, 0x4dc0, + 0x4b90, 0x4960, 0x4750, 0x4540, 0x4350, 0x4160, + 0x3f90, 0x3dc0, 0x3c10, 0x3a40, 0x38b0, 0x3700 +}; + +static UBYTE VibratoTable[32]={ + 0, 24, 49, 74, 97,120,141,161,180,197,212,224,235,244,250,253, + 255,253,250,244,235,224,212,197,180,161,141,120, 97, 74, 49, 24 +}; + +static UBYTE avibtab[128]={ + 0, 1, 3, 4, 6, 7, 9,10,12,14,15,17,18,20,21,23, + 24,25,27,28,30,31,32,34,35,36,38,39,40,41,42,44, + 45,46,47,48,49,50,51,52,53,54,54,55,56,57,57,58, + 59,59,60,60,61,61,62,62,62,63,63,63,63,63,63,63, + 64,63,63,63,63,63,63,63,62,62,62,61,61,60,60,59, + 59,58,57,57,56,55,54,54,53,52,51,50,49,48,47,46, + 45,44,42,41,40,39,38,36,35,34,32,31,30,28,27,25, + 24,23,21,20,18,17,15,14,12,10, 9, 7, 6, 4, 3, 1 +}; + +/* Triton's linear periods to frequency translation table (for XM modules) */ +static ULONG lintab[768]={ + 535232,534749,534266,533784,533303,532822,532341,531861, + 531381,530902,530423,529944,529466,528988,528511,528034, + 527558,527082,526607,526131,525657,525183,524709,524236, + 523763,523290,522818,522346,521875,521404,520934,520464, + 519994,519525,519057,518588,518121,517653,517186,516720, + 516253,515788,515322,514858,514393,513929,513465,513002, + 512539,512077,511615,511154,510692,510232,509771,509312, + 508852,508393,507934,507476,507018,506561,506104,505647, + 505191,504735,504280,503825,503371,502917,502463,502010, + 501557,501104,500652,500201,499749,499298,498848,498398, + 497948,497499,497050,496602,496154,495706,495259,494812, + 494366,493920,493474,493029,492585,492140,491696,491253, + 490809,490367,489924,489482,489041,488600,488159,487718, + 487278,486839,486400,485961,485522,485084,484647,484210, + 483773,483336,482900,482465,482029,481595,481160,480726, + 480292,479859,479426,478994,478562,478130,477699,477268, + 476837,476407,475977,475548,475119,474690,474262,473834, + 473407,472979,472553,472126,471701,471275,470850,470425, + 470001,469577,469153,468730,468307,467884,467462,467041, + 466619,466198,465778,465358,464938,464518,464099,463681, + 463262,462844,462427,462010,461593,461177,460760,460345, + 459930,459515,459100,458686,458272,457859,457446,457033, + 456621,456209,455797,455386,454975,454565,454155,453745, + 453336,452927,452518,452110,451702,451294,450887,450481, + 450074,449668,449262,448857,448452,448048,447644,447240, + 446836,446433,446030,445628,445226,444824,444423,444022, + 443622,443221,442821,442422,442023,441624,441226,440828, + 440430,440033,439636,439239,438843,438447,438051,437656, + 437261,436867,436473,436079,435686,435293,434900,434508, + 434116,433724,433333,432942,432551,432161,431771,431382, + 430992,430604,430215,429827,429439,429052,428665,428278, + 427892,427506,427120,426735,426350,425965,425581,425197, + 424813,424430,424047,423665,423283,422901,422519,422138, + 421757,421377,420997,420617,420237,419858,419479,419101, + 418723,418345,417968,417591,417214,416838,416462,416086, + 415711,415336,414961,414586,414212,413839,413465,413092, + 412720,412347,411975,411604,411232,410862,410491,410121, + 409751,409381,409012,408643,408274,407906,407538,407170, + 406803,406436,406069,405703,405337,404971,404606,404241, + 403876,403512,403148,402784,402421,402058,401695,401333, + 400970,400609,400247,399886,399525,399165,398805,398445, + 398086,397727,397368,397009,396651,396293,395936,395579, + 395222,394865,394509,394153,393798,393442,393087,392733, + 392378,392024,391671,391317,390964,390612,390259,389907, + 389556,389204,388853,388502,388152,387802,387452,387102, + 386753,386404,386056,385707,385359,385012,384664,384317, + 383971,383624,383278,382932,382587,382242,381897,381552, + 381208,380864,380521,380177,379834,379492,379149,378807, + 378466,378124,377783,377442,377102,376762,376422,376082, + 375743,375404,375065,374727,374389,374051,373714,373377, + 373040,372703,372367,372031,371695,371360,371025,370690, + 370356,370022,369688,369355,369021,368688,368356,368023, + 367691,367360,367028,366697,366366,366036,365706,365376, + 365046,364717,364388,364059,363731,363403,363075,362747, + 362420,362093,361766,361440,361114,360788,360463,360137, + 359813,359488,359164,358840,358516,358193,357869,357547, + 357224,356902,356580,356258,355937,355616,355295,354974, + 354654,354334,354014,353695,353376,353057,352739,352420, + 352103,351785,351468,351150,350834,350517,350201,349885, + 349569,349254,348939,348624,348310,347995,347682,347368, + 347055,346741,346429,346116,345804,345492,345180,344869, + 344558,344247,343936,343626,343316,343006,342697,342388, + 342079,341770,341462,341154,340846,340539,340231,339924, + 339618,339311,339005,338700,338394,338089,337784,337479, + 337175,336870,336566,336263,335959,335656,335354,335051, + 334749,334447,334145,333844,333542,333242,332941,332641, + 332341,332041,331741,331442,331143,330844,330546,330247, + 329950,329652,329355,329057,328761,328464,328168,327872, + 327576,327280,326985,326690,326395,326101,325807,325513, + 325219,324926,324633,324340,324047,323755,323463,323171, + 322879,322588,322297,322006,321716,321426,321136,320846, + 320557,320267,319978,319690,319401,319113,318825,318538, + 318250,317963,317676,317390,317103,316817,316532,316246, + 315961,315676,315391,315106,314822,314538,314254,313971, + 313688,313405,313122,312839,312557,312275,311994,311712, + 311431,311150,310869,310589,310309,310029,309749,309470, + 309190,308911,308633,308354,308076,307798,307521,307243, + 306966,306689,306412,306136,305860,305584,305308,305033, + 304758,304483,304208,303934,303659,303385,303112,302838, + 302565,302292,302019,301747,301475,301203,300931,300660, + 300388,300117,299847,299576,299306,299036,298766,298497, + 298227,297958,297689,297421,297153,296884,296617,296349, + 296082,295815,295548,295281,295015,294749,294483,294217, + 293952,293686,293421,293157,292892,292628,292364,292100, + 291837,291574,291311,291048,290785,290523,290261,289999, + 289737,289476,289215,288954,288693,288433,288173,287913, + 287653,287393,287134,286875,286616,286358,286099,285841, + 285583,285326,285068,284811,284554,284298,284041,283785, + 283529,283273,283017,282762,282507,282252,281998,281743, + 281489,281235,280981,280728,280475,280222,279969,279716, + 279464,279212,278960,278708,278457,278206,277955,277704, + 277453,277203,276953,276703,276453,276204,275955,275706, + 275457,275209,274960,274712,274465,274217,273970,273722, + 273476,273229,272982,272736,272490,272244,271999,271753, + 271508,271263,271018,270774,270530,270286,270042,269798, + 269555,269312,269069,268826,268583,268341,268099,267857 +}; + +#define LOGFAC 2*16 +static UWORD logtab[104]={ + LOGFAC*907,LOGFAC*900,LOGFAC*894,LOGFAC*887, + LOGFAC*881,LOGFAC*875,LOGFAC*868,LOGFAC*862, + LOGFAC*856,LOGFAC*850,LOGFAC*844,LOGFAC*838, + LOGFAC*832,LOGFAC*826,LOGFAC*820,LOGFAC*814, + LOGFAC*808,LOGFAC*802,LOGFAC*796,LOGFAC*791, + LOGFAC*785,LOGFAC*779,LOGFAC*774,LOGFAC*768, + LOGFAC*762,LOGFAC*757,LOGFAC*752,LOGFAC*746, + LOGFAC*741,LOGFAC*736,LOGFAC*730,LOGFAC*725, + LOGFAC*720,LOGFAC*715,LOGFAC*709,LOGFAC*704, + LOGFAC*699,LOGFAC*694,LOGFAC*689,LOGFAC*684, + LOGFAC*678,LOGFAC*675,LOGFAC*670,LOGFAC*665, + LOGFAC*660,LOGFAC*655,LOGFAC*651,LOGFAC*646, + LOGFAC*640,LOGFAC*636,LOGFAC*632,LOGFAC*628, + LOGFAC*623,LOGFAC*619,LOGFAC*614,LOGFAC*610, + LOGFAC*604,LOGFAC*601,LOGFAC*597,LOGFAC*592, + LOGFAC*588,LOGFAC*584,LOGFAC*580,LOGFAC*575, + LOGFAC*570,LOGFAC*567,LOGFAC*563,LOGFAC*559, + LOGFAC*555,LOGFAC*551,LOGFAC*547,LOGFAC*543, + LOGFAC*538,LOGFAC*535,LOGFAC*532,LOGFAC*528, + LOGFAC*524,LOGFAC*520,LOGFAC*516,LOGFAC*513, + LOGFAC*508,LOGFAC*505,LOGFAC*502,LOGFAC*498, + LOGFAC*494,LOGFAC*491,LOGFAC*487,LOGFAC*484, + LOGFAC*480,LOGFAC*477,LOGFAC*474,LOGFAC*470, + LOGFAC*467,LOGFAC*463,LOGFAC*460,LOGFAC*457, + LOGFAC*453,LOGFAC*450,LOGFAC*447,LOGFAC*443, + LOGFAC*440,LOGFAC*437,LOGFAC*434,LOGFAC*431 +}; + +static SBYTE PanbrelloTable[256]={ + 0, 2, 3, 5, 6, 8, 9, 11, 12, 14, 16, 17, 19, 20, 22, 23, + 24, 26, 27, 29, 30, 32, 33, 34, 36, 37, 38, 39, 41, 42, 43, 44, + 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 56, 57, 58, 59, + 59, 60, 60, 61, 61, 62, 62, 62, 63, 63, 63, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 63, 63, 63, 62, 62, 62, 61, 61, 60, 60, + 59, 59, 58, 57, 56, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, + 45, 44, 43, 42, 41, 39, 38, 37, 36, 34, 33, 32, 30, 29, 27, 26, + 24, 23, 22, 20, 19, 17, 16, 14, 12, 11, 9, 8, 6, 5, 3, 2, + 0,- 2,- 3,- 5,- 6,- 8,- 9,-11,-12,-14,-16,-17,-19,-20,-22,-23, + -24,-26,-27,-29,-30,-32,-33,-34,-36,-37,-38,-39,-41,-42,-43,-44, + -45,-46,-47,-48,-49,-50,-51,-52,-53,-54,-55,-56,-56,-57,-58,-59, + -59,-60,-60,-61,-61,-62,-62,-62,-63,-63,-63,-64,-64,-64,-64,-64, + -64,-64,-64,-64,-64,-64,-63,-63,-63,-62,-62,-62,-61,-61,-60,-60, + -59,-59,-58,-57,-56,-56,-55,-54,-53,-52,-51,-50,-49,-48,-47,-46, + -45,-44,-43,-42,-41,-39,-38,-37,-36,-34,-33,-32,-30,-29,-27,-26, + -24,-23,-22,-20,-19,-17,-16,-14,-12,-11,- 9,- 8,- 6,- 5,- 3,- 2 +}; + +/* returns a random value between 0 and ceil-1, ceil must be a power of two */ +static int getrandom(int ceil) +{ +#ifdef HAVE_SRANDOM + return random()&(ceil-1); +#else + return (rand()*ceil)/(RAND_MAX+1.0); +#endif +} + +/* New Note Action Scoring System : + -------------------------------- + 1) total-volume (fadevol, chanvol, volume) is the main scorer. + 2) a looping sample is a bonus x2 + 3) a foreground channel is a bonus x4 + 4) an active envelope with keyoff is a handicap -x2 +*/ +static int MP_FindEmptyChannel(MODULE *mod) +{ + MP_VOICE *a; + ULONG t,k,tvol,pp; + + for (t=0;t<md_sngchn;t++) + if (((mod->voice[t].main.kick==KICK_ABSENT)|| + (mod->voice[t].main.kick==KICK_ENV))&& + Voice_Stopped_internal(t)) + return t; + + tvol=0xffffffUL;t=-1;a=mod->voice; + for (k=0;k<md_sngchn;k++,a++) { + /* allow us to take over a nonexisting sample */ + if (!a->main.s) + return k; + + if ((a->main.kick==KICK_ABSENT)||(a->main.kick==KICK_ENV)) { + pp=a->totalvol<<((a->main.s->flags&SF_LOOP)?1:0); + if ((a->master)&&(a==a->master->slave)) + pp<<=2; + + if (pp<tvol) { + tvol=pp; + t=k; + } + } + } + + if (tvol>8000*7) return -1; + return t; +} + +static SWORD Interpolate(SWORD p,SWORD p1,SWORD p2,SWORD v1,SWORD v2) +{ + if ((p1==p2)||(p==p1)) return v1; + return v1+((SLONG)((p-p1)*(v2-v1))/(p2-p1)); +} + +UWORD getlinearperiod(UWORD note,ULONG fine) +{ + UWORD t; + + t=((20L+2*HIGH_OCTAVE)*OCTAVE+2-note)*32L-(fine>>1); + return t; +} + +static UWORD getlogperiod(UWORD note,ULONG fine) +{ + UWORD n,o; + UWORD p1,p2; + ULONG i; + + n=note%(2*OCTAVE); + o=note/(2*OCTAVE); + i=(n<<2)+(fine>>4); /* n*8 + fine/16 */ + + p1=logtab[i]; + p2=logtab[i+1]; + + return (Interpolate(fine>>4,0,15,p1,p2)>>o); +} + +static UWORD getoldperiod(UWORD note,ULONG speed) +{ + UWORD n,o; + + /* This happens sometimes on badly converted AMF, and old MOD */ + if (!speed) { +#ifdef MIKMOD_DEBUG + fprintf(stderr,"\rmplayer: getoldperiod() called with note=%d, speed=0 !\n",note); +#endif + return 4242; /* <- prevent divide overflow.. (42 hehe) */ + } + + n=note%(2*OCTAVE); + o=note/(2*OCTAVE); + return ((8363L*(ULONG)oldperiods[n])>>o)/speed; +} + +static UWORD GetPeriod(UWORD flags, UWORD note, ULONG speed) +{ + if (flags & UF_XMPERIODS) { + if (flags & UF_LINEAR) + return getlinearperiod(note, speed); + else + return getlogperiod(note, speed); + } else + return getoldperiod(note, speed); +} + +static SWORD InterpolateEnv(SWORD p,ENVPT *a,ENVPT *b) +{ + return (Interpolate(p,a->pos,b->pos,a->val,b->val)); +} + +static SWORD DoPan(SWORD envpan,SWORD pan) +{ + int newpan; + + newpan=pan+(((envpan-PAN_CENTER)*(128-abs(pan-PAN_CENTER)))/128); + + return (newpan<PAN_LEFT)?PAN_LEFT:(newpan>PAN_RIGHT?PAN_RIGHT:newpan); +} + +static SWORD StartEnvelope(ENVPR *t,UBYTE flg,UBYTE pts,UBYTE susbeg,UBYTE susend,UBYTE beg,UBYTE end,ENVPT *p,UBYTE keyoff) +{ + t->flg=flg; + t->pts=pts; + t->susbeg=susbeg; + t->susend=susend; + t->beg=beg; + t->end=end; + t->env=p; + t->p=0; + t->a=0; + t->b=((t->flg&EF_SUSTAIN)&&(!(keyoff&KEY_OFF)))?0:1; + + /* Imago Orpheus sometimes stores an extra initial point in the envelope */ + if ((t->pts>=2)&&(t->env[0].pos==t->env[1].pos)) { + t->a++;t->b++; + } + + /* Fit in the envelope, still */ + if (t->a >= t->pts) + t->a = t->pts - 1; + if (t->b >= t->pts) + t->b = t->pts-1; + + return t->env[t->a].val; +} + +/* This procedure processes all envelope types, include volume, pitch, and + panning. Envelopes are defined by a set of points, each with a magnitude + [relating either to volume, panning position, or pitch modifier] and a tick + position. + + Envelopes work in the following manner: + + (a) Each tick the envelope is moved a point further in its progression. For + an accurate progression, magnitudes between two envelope points are + interpolated. + + (b) When progression reaches a defined point on the envelope, values are + shifted to interpolate between this point and the next, and checks for + loops or envelope end are done. + + Misc: + Sustain loops are loops that are only active as long as the keyoff flag is + clear. When a volume envelope terminates, so does the current fadeout. +*/ +static SWORD ProcessEnvelope(MP_VOICE *aout, ENVPR *t, SWORD v) +{ + if (t->flg & EF_ON) { + UBYTE a, b; /* actual points in the envelope */ + UWORD p; /* the 'tick counter' - real point being played */ + + a = t->a; + b = t->b; + p = t->p; + + /* + * Sustain loop on one point (XM type). + * Not processed if KEYOFF. + * Don't move and don't interpolate when the point is reached + */ + if ((t->flg & EF_SUSTAIN) && t->susbeg == t->susend && + (!(aout->main.keyoff & KEY_OFF) && p == t->env[t->susbeg].pos)) { + v = t->env[t->susbeg].val; + } else { + /* + * All following situations will require interpolation between + * two envelope points. + */ + + /* + * Sustain loop between two points (IT type). + * Not processed if KEYOFF. + */ + /* if we were on a loop point, loop now */ + if ((t->flg & EF_SUSTAIN) && !(aout->main.keyoff & KEY_OFF) && + a >= t->susend) { + a = t->susbeg; + b = (t->susbeg==t->susend)?a:a+1; + p = t->env[a].pos; + v = t->env[a].val; + } else + /* + * Regular loop. + * Be sure to correctly handle single point loops. + */ + if ((t->flg & EF_LOOP) && a >= t->end) { + a = t->beg; + b = t->beg == t->end ? a : a + 1; + p = t->env[a].pos; + v = t->env[a].val; + } else + /* + * Non looping situations. + */ + if (a != b) + v = InterpolateEnv(p, &t->env[a], &t->env[b]); + else + v = t->env[a].val; + + /* + * Start to fade if the volume envelope is finished. + */ + if (p >= t->env[t->pts - 1].pos) { + if (t->flg & EF_VOLENV) { + aout->main.keyoff |= KEY_FADE; + if (!v) + aout->main.fadevol = 0; + } + } else { + p++; + /* did pointer reach point b? */ + if (p >= t->env[b].pos) + a = b++; /* shift points a and b */ + } + t->a = a; + t->b = b; + t->p = p; + } + } + return v; +} + +/* XM linear period to frequency conversion */ +ULONG getfrequency(UWORD flags,ULONG period) +{ + if (flags & UF_LINEAR) { + SLONG shift = ((SLONG)period / 768) - HIGH_OCTAVE; + + if (shift >= 0) + return lintab[period % 768] >> shift; + else + return lintab[period % 768] << (-shift); + } else + return (8363L*1712L)/(period?period:1); +} + +/*========== Protracker effects */ + +static void DoArpeggio(UWORD tick, UWORD flags, MP_CONTROL *a, UBYTE style) +{ + UBYTE note=a->main.note; + + if (a->arpmem) { + switch (style) { + case 0: /* mod style: N, N+x, N+y */ + switch (tick % 3) { + /* case 0: unchanged */ + case 1: + note += (a->arpmem >> 4); + break; + case 2: + note += (a->arpmem & 0xf); + break; + } + break; + case 3: /* okt arpeggio 3: N-x, N, N+y */ + switch (tick % 3) { + case 0: + note -= (a->arpmem >> 4); + break; + /* case 1: unchanged */ + case 2: + note += (a->arpmem & 0xf); + break; + } + break; + case 4: /* okt arpeggio 4: N, N+y, N, N-x */ + switch (tick % 4) { + /* case 0, case 2: unchanged */ + case 1: + note += (a->arpmem & 0xf); + break; + case 3: + note -= (a->arpmem >> 4); + break; + } + break; + case 5: /* okt arpeggio 5: N-x, N+y, N, and nothing at tick 0 */ + if (!tick) + break; + switch (tick % 3) { + /* case 0: unchanged */ + case 1: + note -= (a->arpmem >> 4); + break; + case 2: + note += (a->arpmem & 0xf); + break; + } + break; + } + a->main.period = GetPeriod(flags, (UWORD)note << 1, a->speed); + a->ownper = 1; + } +} + +static int DoPTEffect0(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat = UniGetByte(); + if (!tick) { + if (!dat && (flags & UF_ARPMEM)) + dat=a->arpmem; + else + a->arpmem=dat; + } + if (a->main.period) + DoArpeggio(tick, flags, a, 0); + + return 0; +} + +static int DoPTEffect1(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat = UniGetByte(); + if (!tick && dat) + a->slidespeed = (UWORD)dat << 2; + if (a->main.period) + if (tick) + a->tmpperiod -= a->slidespeed; + + return 0; +} + +static int DoPTEffect2(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat = UniGetByte(); + if (!tick && dat) + a->slidespeed = (UWORD)dat << 2; + if (a->main.period) + if (tick) + a->tmpperiod += a->slidespeed; + + return 0; +} + +static void DoToneSlide(UWORD tick, MP_CONTROL *a) +{ + if (!a->main.fadevol) + a->main.kick = (a->main.kick == KICK_NOTE)? KICK_NOTE : KICK_KEYOFF; + else + a->main.kick = (a->main.kick == KICK_NOTE)? KICK_ENV : KICK_ABSENT; + + if (tick != 0) { + int dist; + + /* We have to slide a->main.period towards a->wantedperiod, so compute + the difference between those two values */ + dist=a->main.period-a->wantedperiod; + + /* if they are equal or if portamentospeed is too big ...*/ + if (dist == 0 || a->portspeed > abs(dist)) + /* ...make tmpperiod equal tperiod */ + a->tmpperiod=a->main.period=a->wantedperiod; + else if (dist>0) { + a->tmpperiod-=a->portspeed; + a->main.period-=a->portspeed; /* dist>0, slide up */ + } else { + a->tmpperiod+=a->portspeed; + a->main.period+=a->portspeed; /* dist<0, slide down */ + } + } else + a->tmpperiod=a->main.period; + a->ownper = 1; +} + +static int DoPTEffect3(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat=UniGetByte(); + if ((!tick)&&(dat)) a->portspeed=(UWORD)dat<<2; + if (a->main.period) + DoToneSlide(tick, a); + + return 0; +} + +static void DoVibrato(UWORD tick, MP_CONTROL *a) +{ + UBYTE q; + UWORD temp = 0; /* silence warning */ + + if (!tick) + return; + + q=(a->vibpos>>2)&0x1f; + + switch (a->wavecontrol&3) { + case 0: /* sine */ + temp=VibratoTable[q]; + break; + case 1: /* ramp down */ + q<<=3; + if (a->vibpos<0) q=255-q; + temp=q; + break; + case 2: /* square wave */ + temp=255; + break; + case 3: /* random wave */ + temp=getrandom(256); + break; + } + + temp*=a->vibdepth; + temp>>=7;temp<<=2; + + if (a->vibpos>=0) + a->main.period=a->tmpperiod+temp; + else + a->main.period=a->tmpperiod-temp; + a->ownper = 1; + + if (tick != 0) + a->vibpos+=a->vibspd; +} + +static int DoPTEffect4(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat=UniGetByte(); + if (!tick) { + if (dat&0x0f) a->vibdepth=dat&0xf; + if (dat&0xf0) a->vibspd=(dat&0xf0)>>2; + } + if (a->main.period) + DoVibrato(tick, a); + + return 0; +} + +static void DoVolSlide(MP_CONTROL *a, UBYTE dat) +{ + if (dat&0xf) { + a->tmpvolume-=(dat&0x0f); + if (a->tmpvolume<0) + a->tmpvolume=0; + } else { + a->tmpvolume+=(dat>>4); + if (a->tmpvolume>64) + a->tmpvolume=64; + } +} + +static int DoPTEffect5(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat=UniGetByte(); + if (a->main.period) + DoToneSlide(tick, a); + + if (tick) + DoVolSlide(a, dat); + + return 0; +} + +/* DoPTEffect6 after DoPTEffectA */ + +static int DoPTEffect7(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + UBYTE q; + UWORD temp = 0; /* silence warning */ + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat=UniGetByte(); + if (!tick) { + if (dat&0x0f) a->trmdepth=dat&0xf; + if (dat&0xf0) a->trmspd=(dat&0xf0)>>2; + } + if (a->main.period) { + q=(a->trmpos>>2)&0x1f; + + switch ((a->wavecontrol>>4)&3) { + case 0: /* sine */ + temp=VibratoTable[q]; + break; + case 1: /* ramp down */ + q<<=3; + if (a->trmpos<0) q=255-q; + temp=q; + break; + case 2: /* square wave */ + temp=255; + break; + case 3: /* random wave */ + temp=getrandom(256); + break; + } + temp*=a->trmdepth; + temp>>=6; + + if (a->trmpos>=0) { + a->volume=a->tmpvolume+temp; + if (a->volume>64) a->volume=64; + } else { + a->volume=a->tmpvolume-temp; + if (a->volume<0) a->volume=0; + } + a->ownvol = 1; + + if (tick) + a->trmpos+=a->trmspd; + } + + return 0; +} + +static int DoPTEffect8(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)tick; /* unused arg */ + (void)flags; /* unused arg */ + + dat = UniGetByte(); + if (mod->panflag) + a->main.panning = mod->panning[channel] = dat; + + return 0; +} + +static int DoPTEffect9(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat=UniGetByte(); + if (!tick) { + if (dat) a->soffset=(UWORD)dat<<8; + a->main.start=a->hioffset|a->soffset; + + if ((a->main.s)&&(a->main.start>a->main.s->length)) + a->main.start=a->main.s->flags&(SF_LOOP|SF_BIDI)? + a->main.s->loopstart:a->main.s->length; + } + + return 0; +} + +static int DoPTEffectA(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat=UniGetByte(); + if (tick) + DoVolSlide(a, dat); + + return 0; +} + +static int DoPTEffect6(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + if (a->main.period) + DoVibrato(tick, a); + DoPTEffectA(tick, flags, a, mod, channel); + + return 0; +} + +static int DoPTEffectB(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)a; /* unused arg */ + (void)channel; /* unused arg */ + + dat=UniGetByte(); + + if (tick || mod->patdly2) + return 0; + + /* Vincent Voois uses a nasty trick in "Universal Bolero" */ + if (dat == mod->sngpos && mod->patbrk == mod->patpos) + return 0; + + if (!mod->loop && !mod->patbrk && + (dat < mod->sngpos || + (mod->sngpos == (mod->numpos - 1) && !mod->patbrk) || + (dat == mod->sngpos && (flags & UF_NOWRAP)) + )) { + /* if we don't loop, better not to skip the end of the + pattern, after all... so: + mod->patbrk=0; */ + mod->posjmp=3; + } else { + /* if we were fading, adjust... */ + if (mod->sngpos == (mod->numpos-1)) + mod->volume=mod->initvolume>128?128:mod->initvolume; + mod->sngpos=dat; + mod->posjmp=2; + mod->patpos=0; + } + + return 0; +} + +static int DoPTEffectC(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat=UniGetByte(); + if (tick) return 0; + if (dat==(UBYTE)-1) a->anote=dat=0; /* note cut */ + else if (dat>64) dat=64; + a->tmpvolume=dat; + + return 0; +} + +static int DoPTEffectD(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)a; /* unused arg */ + (void)channel; /* unused arg */ + + dat=UniGetByte(); + if ((tick)||(mod->patdly2)) return 0; + if ((mod->positions[mod->sngpos]!=LAST_PATTERN)&& + (dat>mod->pattrows[mod->positions[mod->sngpos]])) + dat=mod->pattrows[mod->positions[mod->sngpos]]; + mod->patbrk=dat; + if (!mod->posjmp) { + /* don't ask me to explain this code - it makes + backwards.s3m and children.xm (heretic's version) play + correctly, among others. Take that for granted, or write + the page of comments yourself... you might need some + aspirin - Miod */ + if ((mod->sngpos==mod->numpos-1)&&(dat)&&((mod->loop)|| + (mod->positions[mod->sngpos]==(mod->numpat-1) + && !(flags&UF_NOWRAP)))) { + mod->sngpos=0; + mod->posjmp=2; + } else + mod->posjmp=3; + } + + return 0; +} + +static void DoEEffects(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, + SWORD channel, UBYTE dat) +{ + UBYTE nib = dat & 0xf; + + switch (dat>>4) { + case 0x0: /* hardware filter toggle, not supported */ + break; + case 0x1: /* fineslide up */ + if (a->main.period) + if (!tick) + a->tmpperiod-=(nib<<2); + break; + case 0x2: /* fineslide dn */ + if (a->main.period) + if (!tick) + a->tmpperiod+=(nib<<2); + break; + case 0x3: /* glissando ctrl */ + a->glissando=nib; + break; + case 0x4: /* set vibrato waveform */ + a->wavecontrol&=0xf0; + a->wavecontrol|=nib; + break; + case 0x5: /* set finetune */ + if (a->main.period) { + if (flags&UF_XMPERIODS) + a->speed=nib+128; + else + a->speed=finetune[nib]; + a->tmpperiod=GetPeriod(flags, (UWORD)a->main.note<<1,a->speed); + } + break; + case 0x6: /* set patternloop */ + if (tick) + break; + if (nib) { /* set reppos or repcnt ? */ + /* set repcnt, so check if repcnt already is set, which means we + are already looping */ + if (a->pat_repcnt) + a->pat_repcnt--; /* already looping, decrease counter */ + else { +#if 0 + /* this would make walker.xm, shipped with Xsoundtracker, + play correctly, but it's better to remain compatible + with FT2 */ + if ((!(flags&UF_NOWRAP))||(a->pat_reppos!=POS_NONE)) +#endif + a->pat_repcnt=nib; /* not yet looping, so set repcnt */ + } + + if (a->pat_repcnt) { /* jump to reppos if repcnt>0 */ + if (a->pat_reppos==POS_NONE) + a->pat_reppos=mod->patpos-1; + if (a->pat_reppos==-1) { + mod->pat_repcrazy=1; + mod->patpos=0; + } else + mod->patpos=a->pat_reppos; + } else a->pat_reppos=POS_NONE; + } else + a->pat_reppos=mod->patpos-1; /* set reppos - can be (-1) */ + break; + case 0x7: /* set tremolo waveform */ + a->wavecontrol&=0x0f; + a->wavecontrol|=nib<<4; + break; + case 0x8: /* set panning */ + if (mod->panflag) { + if (nib<=8) nib<<=4; + else nib*=17; + a->main.panning=mod->panning[channel]=nib; + } + break; + case 0x9: /* retrig note */ + /* do not retrigger on tick 0, until we are emulating FT2 and effect + data is zero */ + if (!tick && !((flags & UF_FT2QUIRKS) && (!nib))) + break; + /* only retrigger if data nibble > 0, or if tick 0 (FT2 compat) */ + if (nib || !tick) { + if (!a->retrig) { + /* when retrig counter reaches 0, reset counter and restart + the sample */ + if (a->main.period) a->main.kick=KICK_NOTE; + a->retrig=nib; + } + a->retrig--; /* countdown */ + } + break; + case 0xa: /* fine volume slide up */ + if (tick) + break; + a->tmpvolume+=nib; + if (a->tmpvolume>64) a->tmpvolume=64; + break; + case 0xb: /* fine volume slide dn */ + if (tick) + break; + a->tmpvolume-=nib; + if (a->tmpvolume<0)a->tmpvolume=0; + break; + case 0xc: /* cut note */ + /* When tick reaches the cut-note value, turn the volume to + zero (just like on the amiga) */ + if (tick>=nib) + a->tmpvolume=0; /* just turn the volume down */ + break; + case 0xd: /* note delay */ + /* delay the start of the sample until tick==nib */ + if (!tick) + a->main.notedelay=nib; + else if (a->main.notedelay) + a->main.notedelay--; + break; + case 0xe: /* pattern delay */ + if (!tick) + if (!mod->patdly2) + mod->patdly=nib+1; /* only once, when tick=0 */ + break; + case 0xf: /* invert loop, not supported */ + break; + } +} + +static int DoPTEffectE(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + DoEEffects(tick, flags, a, mod, channel, UniGetByte()); + + return 0; +} + +static int DoPTEffectF(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)flags; /* unused arg */ + (void)a; /* unused arg */ + (void)channel; /* unused arg */ + + dat=UniGetByte(); + if (tick||mod->patdly2) return 0; + if (mod->extspd&&(dat>=mod->bpmlimit)) + mod->bpm=dat; + else + if (dat) { + mod->sngspd=(dat>=mod->bpmlimit)?mod->bpmlimit-1:dat; + mod->vbtick=0; + } + + return 0; +} + +/*========== Scream Tracker effects */ + +static int DoS3MEffectA(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE speed; + + (void)flags; /* unused arg */ + (void)a; /* unused arg */ + (void)channel; /* unused arg */ + + speed = UniGetByte(); + + if (tick || mod->patdly2) + return 0; + + if (speed > 128) + speed -= 128; + if (speed) { + mod->sngspd = speed; + mod->vbtick = 0; + } + + return 0; +} + +static void DoS3MVolSlide(UWORD tick, UWORD flags, MP_CONTROL *a, UBYTE inf) +{ + UBYTE lo, hi; + + if (inf) + a->s3mvolslide=inf; + else + inf=a->s3mvolslide; + + lo=inf&0xf; + hi=inf>>4; + + if (!lo) { + if ((tick)||(flags&UF_S3MSLIDES)) a->tmpvolume+=hi; + } else + if (!hi) { + if ((tick)||(flags&UF_S3MSLIDES)) a->tmpvolume-=lo; + } else + if (lo==0xf) { + if (!tick) a->tmpvolume+=(hi?hi:0xf); + } else + if (hi==0xf) { + if (!tick) a->tmpvolume-=(lo?lo:0xf); + } else + return; + + if (a->tmpvolume<0) + a->tmpvolume=0; + else if (a->tmpvolume>64) + a->tmpvolume=64; +} + +static int DoS3MEffectD(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + DoS3MVolSlide(tick, flags, a, UniGetByte()); + + return 1; +} + +static void DoS3MSlideDn(UWORD tick, MP_CONTROL *a, UBYTE inf) +{ + UBYTE hi,lo; + + if (inf) + a->slidespeed=inf; + else + inf=a->slidespeed; + + hi=inf>>4; + lo=inf&0xf; + + if (hi==0xf) { + if (!tick) a->tmpperiod+=(UWORD)lo<<2; + } else + if (hi==0xe) { + if (!tick) a->tmpperiod+=lo; + } else { + if (tick) a->tmpperiod+=(UWORD)inf<<2; + } +} + +static int DoS3MEffectE(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat=UniGetByte(); + if (a->main.period) + DoS3MSlideDn(tick, a,dat); + + return 0; +} + +static void DoS3MSlideUp(UWORD tick, MP_CONTROL *a, UBYTE inf) +{ + UBYTE hi,lo; + + if (inf) a->slidespeed=inf; + else inf=a->slidespeed; + + hi=inf>>4; + lo=inf&0xf; + + if (hi==0xf) { + if (!tick) a->tmpperiod-=(UWORD)lo<<2; + } else + if (hi==0xe) { + if (!tick) a->tmpperiod-=lo; + } else { + if (tick) a->tmpperiod-=(UWORD)inf<<2; + } +} + +static int DoS3MEffectF(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat=UniGetByte(); + if (a->main.period) + DoS3MSlideUp(tick, a,dat); + + return 0; +} + +static int DoS3MEffectI(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE inf, on, off; + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + inf = UniGetByte(); + if (inf) + a->s3mtronof = inf; + else { + inf = a->s3mtronof; + if (!inf) + return 0; + } + + if (!tick) + return 0; + + on=(inf>>4)+1; + off=(inf&0xf)+1; + a->s3mtremor%=(on+off); + a->volume=(a->s3mtremor<on)?a->tmpvolume:0; + a->ownvol=1; + a->s3mtremor++; + + return 0; +} + +static int DoS3MEffectQ(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE inf; + + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + inf = UniGetByte(); + if (a->main.period) { + if (inf) { + a->s3mrtgslide=inf>>4; + a->s3mrtgspeed=inf&0xf; + } + + /* only retrigger if low nibble > 0 */ + if (a->s3mrtgspeed>0) { + if (!a->retrig) { + /* when retrig counter reaches 0, reset counter and restart the + sample */ + if (a->main.kick!=KICK_NOTE) a->main.kick=KICK_KEYOFF; + a->retrig=a->s3mrtgspeed; + + if ((tick)||(flags&UF_S3MSLIDES)) { + switch (a->s3mrtgslide) { + case 1: + case 2: + case 3: + case 4: + case 5: + a->tmpvolume-=(1<<(a->s3mrtgslide-1)); + break; + case 6: + a->tmpvolume=(2*a->tmpvolume)/3; + break; + case 7: + a->tmpvolume>>=1; + break; + case 9: + case 0xa: + case 0xb: + case 0xc: + case 0xd: + a->tmpvolume+=(1<<(a->s3mrtgslide-9)); + break; + case 0xe: + a->tmpvolume=(3*a->tmpvolume)>>1; + break; + case 0xf: + a->tmpvolume=a->tmpvolume<<1; + break; + } + if (a->tmpvolume<0) + a->tmpvolume=0; + else if (a->tmpvolume>64) + a->tmpvolume=64; + } + } + a->retrig--; /* countdown */ + } + } + + return 0; +} + +static int DoS3MEffectR(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat, q; + UWORD temp=0; /* silence warning */ + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat = UniGetByte(); + if (!tick) { + if (dat&0x0f) a->trmdepth=dat&0xf; + if (dat&0xf0) a->trmspd=(dat&0xf0)>>2; + } + + q=(a->trmpos>>2)&0x1f; + + switch ((a->wavecontrol>>4)&3) { + case 0: /* sine */ + temp=VibratoTable[q]; + break; + case 1: /* ramp down */ + q<<=3; + if (a->trmpos<0) q=255-q; + temp=q; + break; + case 2: /* square wave */ + temp=255; + break; + case 3: /* random */ + temp=getrandom(256); + break; + } + + temp*=a->trmdepth; + temp>>=7; + + if (a->trmpos>=0) { + a->volume=a->tmpvolume+temp; + if (a->volume>64) a->volume=64; + } else { + a->volume=a->tmpvolume-temp; + if (a->volume<0) a->volume=0; + } + a->ownvol = 1; + + if (tick) + a->trmpos+=a->trmspd; + + return 0; +} + +static int DoS3MEffectT(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE tempo; + + (void)flags; /* unused arg */ + (void)a; /* unused arg */ + (void)channel; /* unused arg */ + + tempo = UniGetByte(); + + if (tick || mod->patdly2) + return 0; + + mod->bpm = (tempo < 32) ? 32 : tempo; + + return 0; +} + +static int DoS3MEffectU(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat, q; + UWORD temp = 0; /* silence warning */ + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat = UniGetByte(); + if (!tick) { + if (dat&0x0f) a->vibdepth=dat&0xf; + if (dat&0xf0) a->vibspd=(dat&0xf0)>>2; + } else + if (a->main.period) { + q=(a->vibpos>>2)&0x1f; + + switch (a->wavecontrol&3) { + case 0: /* sine */ + temp=VibratoTable[q]; + break; + case 1: /* ramp down */ + q<<=3; + if (a->vibpos<0) q=255-q; + temp=q; + break; + case 2: /* square wave */ + temp=255; + break; + case 3: /* random */ + temp=getrandom(256); + break; + } + + temp*=a->vibdepth; + temp>>=8; + + if (a->vibpos>=0) + a->main.period=a->tmpperiod+temp; + else + a->main.period=a->tmpperiod-temp; + a->ownper = 1; + + a->vibpos+=a->vibspd; + } + + return 0; +} + +/*========== Envelope helpers */ + +static int DoKeyOff(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + (void)tick; /* unused arg */ + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + a->main.keyoff|=KEY_OFF; + if ((!(a->main.volflg&EF_ON))||(a->main.volflg&EF_LOOP)) + a->main.keyoff=KEY_KILL; + + return 0; +} + +static int DoKeyFade(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)flags; /* unused arg */ + (void)channel; /* unused arg */ + + dat=UniGetByte(); + if ((tick>=dat)||(tick==mod->sngspd-1)) { + a->main.keyoff=KEY_KILL; + if (!(a->main.volflg&EF_ON)) + a->main.fadevol=0; + } + + return 0; +} + +/*========== Fast Tracker effects */ + +/* DoXMEffect6 after DoXMEffectA */ + +static int DoXMEffectA(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE inf, lo, hi; + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + inf = UniGetByte(); + if (inf) + a->s3mvolslide = inf; + else + inf = a->s3mvolslide; + + if (tick) { + lo=inf&0xf; + hi=inf>>4; + + if (!hi) { + a->tmpvolume-=lo; + if (a->tmpvolume<0) a->tmpvolume=0; + } else { + a->tmpvolume+=hi; + if (a->tmpvolume>64) a->tmpvolume=64; + } + } + + return 0; +} + +static int DoXMEffect6(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + if (a->main.period) + DoVibrato(tick, a); + + return DoXMEffectA(tick, flags, a, mod, channel); +} + +static int DoXMEffectE1(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat=UniGetByte(); + if (!tick) { + if (dat) a->fportupspd=dat; + if (a->main.period) + a->tmpperiod-=(a->fportupspd<<2); + } + + return 0; +} + +static int DoXMEffectE2(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat=UniGetByte(); + if (!tick) { + if (dat) a->fportdnspd=dat; + if (a->main.period) + a->tmpperiod+=(a->fportdnspd<<2); + } + + return 0; +} + +static int DoXMEffectEA(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat=UniGetByte(); + if (!tick) + if (dat) a->fslideupspd=dat; + a->tmpvolume+=a->fslideupspd; + if (a->tmpvolume>64) a->tmpvolume=64; + + return 0; +} + +static int DoXMEffectEB(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat=UniGetByte(); + if (!tick) + if (dat) a->fslidednspd=dat; + a->tmpvolume-=a->fslidednspd; + if (a->tmpvolume<0) a->tmpvolume=0; + + return 0; +} + +static int DoXMEffectG(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + (void)tick; /* unused arg */ + (void)flags; /* unused arg */ + (void)a; /* unused arg */ + (void)channel; /* unused arg */ + + mod->volume=UniGetByte()<<1; + if (mod->volume>128) mod->volume=128; + + return 0; +} + +static int DoXMEffectH(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE inf; + + (void)flags; /* unused arg */ + (void)a; /* unused arg */ + (void)channel; /* unused arg */ + + inf = UniGetByte(); + + if (tick) { + if (inf) mod->globalslide=inf; + else inf=mod->globalslide; + if (inf & 0xf0) inf&=0xf0; + mod->volume=mod->volume+((inf>>4)-(inf&0xf))*2; + + if (mod->volume<0) + mod->volume=0; + else if (mod->volume>128) + mod->volume=128; + } + + return 0; +} + +static int DoXMEffectL(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat=UniGetByte(); + if ((!tick)&&(a->main.i)) { + UWORD points; + INSTRUMENT *i=a->main.i; + MP_VOICE *aout; + + if ((aout=a->slave)) { + if (aout->venv.env) { + points=i->volenv[i->volpts-1].pos; + aout->venv.p=aout->venv.env[(dat>points)?points:dat].pos; + } + if (aout->penv.env) { + points=i->panenv[i->panpts-1].pos; + aout->penv.p=aout->penv.env[(dat>points)?points:dat].pos; + } + } + } + + return 0; +} + +static int DoXMEffectP(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE inf, lo, hi; + SWORD pan; + + (void)flags; /* unused arg */ + (void)channel; /* unused arg */ + + inf = UniGetByte(); + if (!mod->panflag) + return 0; + + if (inf) + a->pansspd = inf; + else + inf =a->pansspd; + + if (tick) { + lo=inf&0xf; + hi=inf>>4; + + /* slide right has absolute priority */ + if (hi) + lo = 0; + + pan=((a->main.panning==PAN_SURROUND)?PAN_CENTER:a->main.panning)+hi-lo; + a->main.panning=(pan<PAN_LEFT)?PAN_LEFT:(pan>PAN_RIGHT?PAN_RIGHT:pan); + } + + return 0; +} + +static int DoXMEffectX1(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat = UniGetByte(); + if (dat) + a->ffportupspd = dat; + else + dat = a->ffportupspd; + + if (a->main.period) + if (!tick) { + a->main.period-=dat; + a->tmpperiod-=dat; + a->ownper = 1; + } + + return 0; +} + +static int DoXMEffectX2(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat = UniGetByte(); + if (dat) + a->ffportdnspd=dat; + else + dat = a->ffportdnspd; + + if (a->main.period) + if (!tick) { + a->main.period+=dat; + a->tmpperiod+=dat; + a->ownper = 1; + } + + return 0; +} + +/*========== Impulse Tracker effects */ + +static void DoITToneSlide(UWORD tick, MP_CONTROL *a, UBYTE dat) +{ + if (dat) + a->portspeed = dat; + + /* if we don't come from another note, ignore the slide and play the note + as is */ + if (!a->oldnote || !a->main.period) + return; + + if ((!tick)&&(a->newsamp)){ + a->main.kick=KICK_NOTE; + a->main.start=-1; + } else + a->main.kick=(a->main.kick==KICK_NOTE)?KICK_ENV:KICK_ABSENT; + + if (tick) { + int dist; + + /* We have to slide a->main.period towards a->wantedperiod, compute the + difference between those two values */ + dist=a->main.period-a->wantedperiod; + + /* if they are equal or if portamentospeed is too big... */ + if ((!dist)||((a->portspeed<<2)>abs(dist))) + /* ... make tmpperiod equal tperiod */ + a->tmpperiod=a->main.period=a->wantedperiod; + else + if (dist>0) { + a->tmpperiod-=a->portspeed<<2; + a->main.period-=a->portspeed<<2; /* dist>0 slide up */ + } else { + a->tmpperiod+=a->portspeed<<2; + a->main.period+=a->portspeed<<2; /* dist<0 slide down */ + } + } else + a->tmpperiod=a->main.period; + a->ownper=1; +} + +static int DoITEffectG(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + DoITToneSlide(tick, a, UniGetByte()); + + return 0; +} + +static void DoITVibrato(UWORD tick, MP_CONTROL *a, UBYTE dat) +{ + UBYTE q; + UWORD temp=0; + + if (!tick) { + if (dat&0x0f) a->vibdepth=dat&0xf; + if (dat&0xf0) a->vibspd=(dat&0xf0)>>2; + } + if (!a->main.period) + return; + + q=(a->vibpos>>2)&0x1f; + + switch (a->wavecontrol&3) { + case 0: /* sine */ + temp=VibratoTable[q]; + break; + case 1: /* square wave */ + temp=255; + break; + case 2: /* ramp down */ + q<<=3; + if (a->vibpos<0) q=255-q; + temp=q; + break; + case 3: /* random */ + temp=getrandom(256); + break; + } + + temp*=a->vibdepth; + temp>>=8; + temp<<=2; + + if (a->vibpos>=0) + a->main.period=a->tmpperiod+temp; + else + a->main.period=a->tmpperiod-temp; + a->ownper=1; + + a->vibpos+=a->vibspd; +} + +static int DoITEffectH(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + DoITVibrato(tick, a, UniGetByte()); + + return 0; +} + +static int DoITEffectI(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE inf, on, off; + + (void)tick; /* unused arg */ + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + inf = UniGetByte(); + if (inf) + a->s3mtronof = inf; + else { + inf = a->s3mtronof; + if (!inf) + return 0; + } + + on=(inf>>4); + off=(inf&0xf); + + a->s3mtremor%=(on+off); + a->volume=(a->s3mtremor<on)?a->tmpvolume:0; + a->ownvol = 1; + a->s3mtremor++; + + return 0; +} + +static int DoITEffectM(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + (void)tick; /* unused arg */ + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + a->main.chanvol=UniGetByte(); + if (a->main.chanvol>64) + a->main.chanvol=64; + else if (a->main.chanvol<0) + a->main.chanvol=0; + + return 0; +} + +static int DoITEffectN(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE inf, lo, hi; + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + inf = UniGetByte(); + + if (inf) + a->chanvolslide = inf; + else + inf = a->chanvolslide; + + lo=inf&0xf; + hi=inf>>4; + + if (!hi) + a->main.chanvol-=lo; + else + if (!lo) { + a->main.chanvol+=hi; + } else + if (hi==0xf) { + if (!tick) a->main.chanvol-=lo; + } else + if (lo==0xf) { + if (!tick) a->main.chanvol+=hi; + } + + if (a->main.chanvol<0) + a->main.chanvol=0; + else if (a->main.chanvol>64) + a->main.chanvol=64; + + return 0; +} + +static int DoITEffectP(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE inf, lo, hi; + SWORD pan; + + (void)flags; /* unused arg */ + (void)channel; /* unused arg */ + + inf = UniGetByte(); + if (inf) + a->pansspd = inf; + else + inf = a->pansspd; + + if (!mod->panflag) + return 0; + + lo=inf&0xf; + hi=inf>>4; + + pan=(a->main.panning==PAN_SURROUND)?PAN_CENTER:a->main.panning; + + if (!hi) + pan+=lo<<2; + else + if (!lo) { + pan-=hi<<2; + } else + if (hi==0xf) { + if (!tick) pan+=lo<<2; + } else + if (lo==0xf) { + if (!tick) pan-=hi<<2; + } + a->main.panning= + (pan<PAN_LEFT)?PAN_LEFT:(pan>PAN_RIGHT?PAN_RIGHT:pan); + + return 0; +} + +static int DoITEffectT(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE tempo; + SWORD temp; + + (void)tick; /* unused arg */ + (void)flags; /* unused arg */ + (void)a; /* unused arg */ + (void)channel; /* unused arg */ + + tempo = UniGetByte(); + + if (mod->patdly2) + return 0; + + temp = mod->bpm; + if (tempo & 0x10) + temp += (tempo & 0x0f); + else + temp -= tempo; + + mod->bpm=(temp>255)?255:(temp<1?1:temp); + + return 0; +} + +static int DoITEffectU(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat, q; + UWORD temp = 0; /* silence warning */ + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat = UniGetByte(); + if (!tick) { + if (dat&0x0f) a->vibdepth=dat&0xf; + if (dat&0xf0) a->vibspd=(dat&0xf0)>>2; + } + if (a->main.period) { + q=(a->vibpos>>2)&0x1f; + + switch (a->wavecontrol&3) { + case 0: /* sine */ + temp=VibratoTable[q]; + break; + case 1: /* square wave */ + temp=255; + break; + case 2: /* ramp down */ + q<<=3; + if (a->vibpos<0) q=255-q; + temp=q; + break; + case 3: /* random */ + temp=getrandom(256); + break; + } + + temp*=a->vibdepth; + temp>>=8; + + if (a->vibpos>=0) + a->main.period=a->tmpperiod+temp; + else + a->main.period=a->tmpperiod-temp; + a->ownper = 1; + + a->vibpos+=a->vibspd; + } + + return 0; +} + +static int DoITEffectW(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE inf, lo, hi; + + (void)flags; /* unused arg */ + (void)a; /* unused arg */ + (void)channel; /* unused arg */ + + inf = UniGetByte(); + + if (inf) + mod->globalslide = inf; + else + inf = mod->globalslide; + + lo=inf&0xf; + hi=inf>>4; + + if (!lo) { + if (tick) mod->volume+=hi; + } else + if (!hi) { + if (tick) mod->volume-=lo; + } else + if (lo==0xf) { + if (!tick) mod->volume+=hi; + } else + if (hi==0xf) { + if (!tick) mod->volume-=lo; + } + + if (mod->volume<0) + mod->volume=0; + else if (mod->volume>128) + mod->volume=128; + + return 0; +} + +static int DoITEffectY(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat, q; + SLONG temp = 0; /* silence warning */ + + (void)flags; /* unused arg */ + + dat=UniGetByte(); + if (!tick) { + if (dat&0x0f) a->panbdepth=(dat&0xf); + if (dat&0xf0) a->panbspd=(dat&0xf0)>>4; + } + if (mod->panflag) { + q=a->panbpos; + + switch (a->panbwave) { + case 0: /* sine */ + temp=PanbrelloTable[q]; + break; + case 1: /* square wave */ + temp=(q<0x80)?64:0; + break; + case 2: /* ramp down */ + q<<=3; + temp=q; + break; + case 3: /* random */ + temp=getrandom(256); + break; + } + + temp*=a->panbdepth; + temp=(temp/8)+mod->panning[channel]; + + a->main.panning= + (temp<PAN_LEFT)?PAN_LEFT:(temp>PAN_RIGHT?PAN_RIGHT:temp); + a->panbpos+=a->panbspd; + + } + + return 0; +} + +static void DoNNAEffects(MODULE *, MP_CONTROL *, UBYTE); + +/* Impulse/Scream Tracker Sxx effects. + All Sxx effects share the same memory space. */ +static int DoITEffectS0(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat, inf, c; + + dat = UniGetByte(); + inf=dat&0xf; + c=dat>>4; + + if (!dat) { + c=a->sseffect; + inf=a->ssdata; + } else { + a->sseffect=c; + a->ssdata=inf; + } + + switch (c) { + case SS_GLISSANDO: /* S1x set glissando voice */ + DoEEffects(tick, flags, a, mod, channel, 0x30|inf); + break; + case SS_FINETUNE: /* S2x set finetune */ + DoEEffects(tick, flags, a, mod, channel, 0x50|inf); + break; + case SS_VIBWAVE: /* S3x set vibrato waveform */ + DoEEffects(tick, flags, a, mod, channel, 0x40|inf); + break; + case SS_TREMWAVE: /* S4x set tremolo waveform */ + DoEEffects(tick, flags, a, mod, channel, 0x70|inf); + break; + case SS_PANWAVE: /* S5x panbrello */ + a->panbwave=inf; + break; + case SS_FRAMEDELAY: /* S6x delay x number of frames (patdly) */ + DoEEffects(tick, flags, a, mod, channel, 0xe0|inf); + break; + case SS_S7EFFECTS: /* S7x instrument / NNA commands */ + DoNNAEffects(mod, a, inf); + break; + case SS_PANNING: /* S8x set panning position */ + DoEEffects(tick, flags, a, mod, channel, 0x80 | inf); + break; + case SS_SURROUND: /* S9x set surround sound */ + if (mod->panflag) + a->main.panning = mod->panning[channel] = PAN_SURROUND; + break; + case SS_HIOFFSET: /* SAy set high order sample offset yxx00h */ + if (!tick) { + a->hioffset=inf<<16; + a->main.start=a->hioffset|a->soffset; + + if ((a->main.s)&&(a->main.start>a->main.s->length)) + a->main.start=a->main.s->flags&(SF_LOOP|SF_BIDI)? + a->main.s->loopstart:a->main.s->length; + } + break; + case SS_PATLOOP: /* SBx pattern loop */ + DoEEffects(tick, flags, a, mod, channel, 0x60|inf); + break; + case SS_NOTECUT: /* SCx notecut */ + if (!inf) inf = 1; + DoEEffects(tick, flags, a, mod, channel, 0xC0|inf); + break; + case SS_NOTEDELAY: /* SDx notedelay */ + DoEEffects(tick, flags, a, mod, channel, 0xD0|inf); + break; + case SS_PATDELAY: /* SEx patterndelay */ + DoEEffects(tick, flags, a, mod, channel, 0xE0|inf); + break; + } + + return 0; +} + +/*========== Impulse Tracker Volume/Pan Column effects */ + +/* + * All volume/pan column effects share the same memory space. + */ + +static int DoVolEffects(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE c, inf; + + (void)channel; /* unused arg */ + + c = UniGetByte(); + inf = UniGetByte(); + + if ((!c)&&(!inf)) { + c=a->voleffect; + inf=a->voldata; + } else { + a->voleffect=c; + a->voldata=inf; + } + + if (c) + switch (c) { + case VOL_VOLUME: + if (tick) break; + if (inf>64) inf=64; + a->tmpvolume=inf; + break; + case VOL_PANNING: + if (mod->panflag) + a->main.panning=inf; + break; + case VOL_VOLSLIDE: + DoS3MVolSlide(tick, flags, a, inf); + return 1; + case VOL_PITCHSLIDEDN: + if (a->main.period) + DoS3MSlideDn(tick, a, inf); + break; + case VOL_PITCHSLIDEUP: + if (a->main.period) + DoS3MSlideUp(tick, a, inf); + break; + case VOL_PORTAMENTO: + DoITToneSlide(tick, a, inf); + break; + case VOL_VIBRATO: + DoITVibrato(tick, a, inf); + break; + } + + return 0; +} + +/*========== UltraTracker effects */ + +static int DoULTEffect9(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UWORD offset=UniGetWord(); + + (void)tick; /* unused arg */ + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + if (offset) + a->ultoffset=offset; + + a->main.start=a->ultoffset<<2; + if ((a->main.s)&&(a->main.start>a->main.s->length)) + a->main.start=a->main.s->flags&(SF_LOOP|SF_BIDI)? + a->main.s->loopstart:a->main.s->length; + + return 0; +} + +/*========== OctaMED effects */ + +static int DoMEDSpeed(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UWORD speed=UniGetWord(); + + (void)tick; /* unused arg */ + (void)flags; /* unused arg */ + (void)a; /* unused arg */ + (void)channel; /* unused arg */ + + mod->bpm=speed; + + return 0; +} + +static int DoMEDEffectF1(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + DoEEffects(tick, flags, a, mod, channel, 0x90|(mod->sngspd/2)); + + return 0; +} + +static int DoMEDEffectF2(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + DoEEffects(tick, flags, a, mod, channel, 0xd0|(mod->sngspd/2)); + + return 0; +} + +static int DoMEDEffectF3(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + DoEEffects(tick, flags, a, mod, channel, 0x90|(mod->sngspd/3)); + + return 0; +} + +/*========== Oktalyzer effects */ + +static int DoOktArp(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat, dat2; + + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat2 = UniGetByte(); /* arpeggio style */ + dat = UniGetByte(); + if (!tick) { + if (!dat && (flags & UF_ARPMEM)) + dat=a->arpmem; + else + a->arpmem=dat; + } + if (a->main.period) + DoArpeggio(tick, flags, a, dat2); + + return 0; +} + +/*========== General player functions */ + +static int DoNothing(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + (void)tick; /* unused arg */ + (void)flags; /* unused arg */ + (void)a; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + UniSkipOpcode(); + + return 0; +} + +typedef int (*effect_func) (UWORD, UWORD, MP_CONTROL *, MODULE *, SWORD); + +static effect_func effects[UNI_LAST] = { + DoNothing, /* 0 */ + DoNothing, /* UNI_NOTE */ + DoNothing, /* UNI_INSTRUMENT */ + DoPTEffect0, /* UNI_PTEFFECT0 */ + DoPTEffect1, /* UNI_PTEFFECT1 */ + DoPTEffect2, /* UNI_PTEFFECT2 */ + DoPTEffect3, /* UNI_PTEFFECT3 */ + DoPTEffect4, /* UNI_PTEFFECT4 */ + DoPTEffect5, /* UNI_PTEFFECT5 */ + DoPTEffect6, /* UNI_PTEFFECT6 */ + DoPTEffect7, /* UNI_PTEFFECT7 */ + DoPTEffect8, /* UNI_PTEFFECT8 */ + DoPTEffect9, /* UNI_PTEFFECT9 */ + DoPTEffectA, /* UNI_PTEFFECTA */ + DoPTEffectB, /* UNI_PTEFFECTB */ + DoPTEffectC, /* UNI_PTEFFECTC */ + DoPTEffectD, /* UNI_PTEFFECTD */ + DoPTEffectE, /* UNI_PTEFFECTE */ + DoPTEffectF, /* UNI_PTEFFECTF */ + DoS3MEffectA, /* UNI_S3MEFFECTA */ + DoS3MEffectD, /* UNI_S3MEFFECTD */ + DoS3MEffectE, /* UNI_S3MEFFECTE */ + DoS3MEffectF, /* UNI_S3MEFFECTF */ + DoS3MEffectI, /* UNI_S3MEFFECTI */ + DoS3MEffectQ, /* UNI_S3MEFFECTQ */ + DoS3MEffectR, /* UNI_S3MEFFECTR */ + DoS3MEffectT, /* UNI_S3MEFFECTT */ + DoS3MEffectU, /* UNI_S3MEFFECTU */ + DoKeyOff, /* UNI_KEYOFF */ + DoKeyFade, /* UNI_KEYFADE */ + DoVolEffects, /* UNI_VOLEFFECTS */ + DoPTEffect4, /* UNI_XMEFFECT4 */ + DoXMEffect6, /* UNI_XMEFFECT6 */ + DoXMEffectA, /* UNI_XMEFFECTA */ + DoXMEffectE1, /* UNI_XMEFFECTE1 */ + DoXMEffectE2, /* UNI_XMEFFECTE2 */ + DoXMEffectEA, /* UNI_XMEFFECTEA */ + DoXMEffectEB, /* UNI_XMEFFECTEB */ + DoXMEffectG, /* UNI_XMEFFECTG */ + DoXMEffectH, /* UNI_XMEFFECTH */ + DoXMEffectL, /* UNI_XMEFFECTL */ + DoXMEffectP, /* UNI_XMEFFECTP */ + DoXMEffectX1, /* UNI_XMEFFECTX1 */ + DoXMEffectX2, /* UNI_XMEFFECTX2 */ + DoITEffectG, /* UNI_ITEFFECTG */ + DoITEffectH, /* UNI_ITEFFECTH */ + DoITEffectI, /* UNI_ITEFFECTI */ + DoITEffectM, /* UNI_ITEFFECTM */ + DoITEffectN, /* UNI_ITEFFECTN */ + DoITEffectP, /* UNI_ITEFFECTP */ + DoITEffectT, /* UNI_ITEFFECTT */ + DoITEffectU, /* UNI_ITEFFECTU */ + DoITEffectW, /* UNI_ITEFFECTW */ + DoITEffectY, /* UNI_ITEFFECTY */ + DoNothing, /* UNI_ITEFFECTZ */ + DoITEffectS0, /* UNI_ITEFFECTS0 */ + DoULTEffect9, /* UNI_ULTEFFECT9 */ + DoMEDSpeed, /* UNI_MEDSPEED */ + DoMEDEffectF1, /* UNI_MEDEFFECTF1 */ + DoMEDEffectF2, /* UNI_MEDEFFECTF2 */ + DoMEDEffectF3, /* UNI_MEDEFFECTF3 */ + DoOktArp, /* UNI_OKTARP */ +}; + +static int pt_playeffects(MODULE *mod, SWORD channel, MP_CONTROL *a) +{ + UWORD tick = mod->vbtick; + UWORD flags = mod->flags; + UBYTE c; + int explicitslides = 0; + effect_func f; + + while((c=UniGetByte())) { + f = effects[c]; + if (f != DoNothing) + a->sliding = 0; + explicitslides |= f(tick, flags, a, mod, channel); + } + return explicitslides; +} + +static void DoNNAEffects(MODULE *mod, MP_CONTROL *a, UBYTE dat) +{ + int t; + MP_VOICE *aout; + + dat&=0xf; + aout=(a->slave)?a->slave:NULL; + + switch (dat) { + case 0x0: /* past note cut */ + for (t=0;t<md_sngchn;t++) + if (mod->voice[t].master==a) + mod->voice[t].main.fadevol=0; + break; + case 0x1: /* past note off */ + for (t=0;t<md_sngchn;t++) + if (mod->voice[t].master==a) { + mod->voice[t].main.keyoff|=KEY_OFF; + if ((!(mod->voice[t].venv.flg & EF_ON))|| + (mod->voice[t].venv.flg & EF_LOOP)) + mod->voice[t].main.keyoff=KEY_KILL; + } + break; + case 0x2: /* past note fade */ + for (t=0;t<md_sngchn;t++) + if (mod->voice[t].master==a) + mod->voice[t].main.keyoff|=KEY_FADE; + break; + case 0x3: /* set NNA note cut */ + a->main.nna=(a->main.nna&~NNA_MASK)|NNA_CUT; + break; + case 0x4: /* set NNA note continue */ + a->main.nna=(a->main.nna&~NNA_MASK)|NNA_CONTINUE; + break; + case 0x5: /* set NNA note off */ + a->main.nna=(a->main.nna&~NNA_MASK)|NNA_OFF; + break; + case 0x6: /* set NNA note fade */ + a->main.nna=(a->main.nna&~NNA_MASK)|NNA_FADE; + break; + case 0x7: /* disable volume envelope */ + if (aout) + aout->main.volflg&=~EF_ON; + break; + case 0x8: /* enable volume envelope */ + if (aout) + aout->main.volflg|=EF_ON; + break; + case 0x9: /* disable panning envelope */ + if (aout) + aout->main.panflg&=~EF_ON; + break; + case 0xa: /* enable panning envelope */ + if (aout) + aout->main.panflg|=EF_ON; + break; + case 0xb: /* disable pitch envelope */ + if (aout) + aout->main.pitflg&=~EF_ON; + break; + case 0xc: /* enable pitch envelope */ + if (aout) + aout->main.pitflg|=EF_ON; + break; + } +} + +void pt_UpdateVoices(MODULE *mod, int max_volume) +{ + SWORD envpan,envvol,envpit,channel; + UWORD playperiod; + SLONG vibval,vibdpt; + ULONG tmpvol; + + MP_VOICE *aout; + INSTRUMENT *i; + SAMPLE *s; + + mod->totalchn=mod->realchn=0; + for (channel=0;channel<md_sngchn;channel++) { + aout=&mod->voice[channel]; + i=aout->main.i; + s=aout->main.s; + + if (!s || !s->length) continue; + + if (aout->main.period<40) + aout->main.period=40; + else if (aout->main.period>50000) + aout->main.period=50000; + + if ((aout->main.kick==KICK_NOTE)||(aout->main.kick==KICK_KEYOFF)) { + Voice_Play_internal(channel,s,(aout->main.start==-1)? + ((s->flags&SF_UST_LOOP)?s->loopstart:0):aout->main.start); + aout->main.fadevol=32768; + aout->aswppos=0; + } + + envvol = 256; + envpan = PAN_CENTER; + envpit = 32; + if (i && ((aout->main.kick==KICK_NOTE)||(aout->main.kick==KICK_ENV))) { + if (aout->main.volflg & EF_ON) + envvol = StartEnvelope(&aout->venv,aout->main.volflg, + i->volpts,i->volsusbeg,i->volsusend, + i->volbeg,i->volend,i->volenv,aout->main.keyoff); + if (aout->main.panflg & EF_ON) + envpan = StartEnvelope(&aout->penv,aout->main.panflg, + i->panpts,i->pansusbeg,i->pansusend, + i->panbeg,i->panend,i->panenv,aout->main.keyoff); + if (aout->main.pitflg & EF_ON) + envpit = StartEnvelope(&aout->cenv,aout->main.pitflg, + i->pitpts,i->pitsusbeg,i->pitsusend, + i->pitbeg,i->pitend,i->pitenv,aout->main.keyoff); + + if (aout->cenv.flg & EF_ON) + aout->masterperiod=GetPeriod(mod->flags, + (UWORD)aout->main.note<<1, aout->master->speed); + } else { + if (aout->main.volflg & EF_ON) + envvol = ProcessEnvelope(aout,&aout->venv,256); + if (aout->main.panflg & EF_ON) + envpan = ProcessEnvelope(aout,&aout->penv,PAN_CENTER); + if (aout->main.pitflg & EF_ON) + envpit = ProcessEnvelope(aout,&aout->cenv,32); + } + aout->main.kick=KICK_ABSENT; + + tmpvol = aout->main.fadevol; /* max 32768 */ + tmpvol *= aout->main.chanvol; /* * max 64 */ + tmpvol *= aout->main.outvolume; /* * max 256 */ + tmpvol /= (256 * 64); /* tmpvol is max 32768 again */ + aout->totalvol = tmpvol >> 2; /* used to determine samplevolume */ + tmpvol *= envvol; /* * max 256 */ + tmpvol *= mod->volume; /* * max 128 */ + tmpvol /= (128 * 256 * 128); + + /* fade out */ + if (mod->sngpos>=mod->numpos) + tmpvol=0; + else + tmpvol=(tmpvol*max_volume)/128; + + if ((aout->masterchn!=-1)&& mod->control[aout->masterchn].muted) + Voice_SetVolume_internal(channel,0); + else { + Voice_SetVolume_internal(channel,tmpvol); + if ((tmpvol)&&(aout->master)&&(aout->master->slave==aout)) + mod->realchn++; + mod->totalchn++; + } + + if (aout->main.panning==PAN_SURROUND) + Voice_SetPanning_internal(channel,PAN_SURROUND); + else + if ((mod->panflag)&&(aout->penv.flg & EF_ON)) + Voice_SetPanning_internal(channel, + DoPan(envpan,aout->main.panning)); + else + Voice_SetPanning_internal(channel,aout->main.panning); + + if (aout->main.period && s->vibdepth) + switch (s->vibtype) { + case 0: + vibval=avibtab[s->avibpos&127]; + if (aout->avibpos & 0x80) vibval=-vibval; + break; + case 1: + vibval=64; + if (aout->avibpos & 0x80) vibval=-vibval; + break; + case 2: + vibval=63-(((aout->avibpos+128)&255)>>1); + break; + default: + vibval=(((aout->avibpos+128)&255)>>1)-64; + break; + } + else + vibval=0; + + if (s->vibflags & AV_IT) { + if ((aout->aswppos>>8)<s->vibdepth) { + aout->aswppos += s->vibsweep; + vibdpt=aout->aswppos; + } else + vibdpt=s->vibdepth<<8; + vibval=(vibval*vibdpt)>>16; + if (aout->mflag) { + if (!(mod->flags&UF_LINEAR)) vibval>>=1; + aout->main.period-=vibval; + } + } else { + /* do XM style auto-vibrato */ + if (!(aout->main.keyoff & KEY_OFF)) { + if (aout->aswppos<s->vibsweep) { + vibdpt=(aout->aswppos*s->vibdepth)/s->vibsweep; + aout->aswppos++; + } else + vibdpt=s->vibdepth; + } else { + /* keyoff -> depth becomes 0 if final depth wasn't reached or + stays at final level if depth WAS reached */ + if (aout->aswppos>=s->vibsweep) + vibdpt=s->vibdepth; + else + vibdpt=0; + } + vibval=(vibval*vibdpt)>>8; + aout->main.period-=vibval; + } + + /* update vibrato position */ + aout->avibpos=(aout->avibpos+s->vibrate)&0xff; + + /* process pitch envelope */ + playperiod=aout->main.period; + + if ((aout->main.pitflg&EF_ON)&&(envpit!=32)) { + long p1; + + envpit-=32; + if ((aout->main.note<<1)+envpit<=0) envpit=-(aout->main.note<<1); + + p1=GetPeriod(mod->flags, ((UWORD)aout->main.note<<1)+envpit, + aout->master->speed)-aout->masterperiod; + if (p1>0) { + if ((UWORD)(playperiod+p1)<=playperiod) { + p1=0; + aout->main.keyoff|=KEY_OFF; + } + } else if (p1<0) { + if ((UWORD)(playperiod+p1)>=playperiod) { + p1=0; + aout->main.keyoff|=KEY_OFF; + } + } + playperiod+=p1; + } + + if (!aout->main.fadevol) { /* check for a dead note (fadevol=0) */ + Voice_Stop_internal(channel); + mod->totalchn--; + if ((tmpvol)&&(aout->master)&&(aout->master->slave==aout)) + mod->realchn--; + } else { + Voice_SetFrequency_internal(channel, + getfrequency(mod->flags,playperiod)); + + /* if keyfade, start substracting fadeoutspeed from fadevol: */ + if ((i)&&(aout->main.keyoff&KEY_FADE)) { + if (aout->main.fadevol>=i->volfade) + aout->main.fadevol-=i->volfade; + else + aout->main.fadevol=0; + } + } + + md_bpm=mod->bpm+mod->relspd; + if (md_bpm<32) + md_bpm=32; + else if ((!(mod->flags&UF_HIGHBPM)) && md_bpm>255) + md_bpm=255; + } +} + +/* Handles new notes or instruments */ +void pt_Notes(MODULE *mod) +{ + SWORD channel; + MP_CONTROL *a; + UBYTE c,inst; + int tr,funky; /* funky is set to indicate note or instrument change */ + + for (channel=0;channel<mod->numchn;channel++) { + a=&mod->control[channel]; + + if (mod->sngpos>=mod->numpos) { + tr=mod->numtrk; + mod->numrow=0; + } else { + tr=mod->patterns[(mod->positions[mod->sngpos]*mod->numchn)+channel]; + mod->numrow=mod->pattrows[mod->positions[mod->sngpos]]; + } + + a->row=(tr<mod->numtrk)?UniFindRow(mod->tracks[tr],mod->patpos):NULL; + a->newsamp=0; + if (!mod->vbtick) a->main.notedelay=0; + + if (!a->row) continue; + UniSetRow(a->row); + funky=0; + + while((c=UniGetByte())) + switch (c) { + case UNI_NOTE: + funky|=1; + a->oldnote=a->anote,a->anote=UniGetByte(); + a->main.kick =KICK_NOTE; + a->main.start=-1; + a->sliding=0; + + /* retrig tremolo and vibrato waves ? */ + if (!(a->wavecontrol & 0x80)) a->trmpos=0; + if (!(a->wavecontrol & 0x08)) a->vibpos=0; + if (!a->panbwave) a->panbpos=0; + break; + case UNI_INSTRUMENT: + inst=UniGetByte(); + if (inst>=mod->numins) break; /* safety valve */ + funky|=2; + a->main.i=(mod->flags & UF_INST)?&mod->instruments[inst]:NULL; + a->retrig=0; + a->s3mtremor=0; + a->ultoffset=0; + a->main.sample=inst; + break; + default: + UniSkipOpcode(); + break; + } + + if (funky) { + INSTRUMENT *i; + SAMPLE *s; + + if ((i=a->main.i)) { + if (i->samplenumber[a->anote] >= mod->numsmp) continue; + s=&mod->samples[i->samplenumber[a->anote]]; + a->main.note=i->samplenote[a->anote]; + } else { + a->main.note=a->anote; + s=&mod->samples[a->main.sample]; + } + + if (a->main.s!=s) { + a->main.s=s; + a->newsamp=a->main.period; + } + + /* channel or instrument determined panning ? */ + a->main.panning=mod->panning[channel]; + if (s->flags & SF_OWNPAN) + a->main.panning=s->panning; + else if ((i)&&(i->flags & IF_OWNPAN)) + a->main.panning=i->panning; + + a->main.handle=s->handle; + a->speed=s->speed; + + if (i) { + if ((mod->panflag)&&(i->flags & IF_PITCHPAN) + &&(a->main.panning!=PAN_SURROUND)){ + a->main.panning+= + ((a->anote-i->pitpancenter)*i->pitpansep)/8; + if (a->main.panning<PAN_LEFT) + a->main.panning=PAN_LEFT; + else if (a->main.panning>PAN_RIGHT) + a->main.panning=PAN_RIGHT; + } + a->main.pitflg=i->pitflg; + a->main.volflg=i->volflg; + a->main.panflg=i->panflg; + a->main.nna=i->nnatype; + a->dca=i->dca; + a->dct=i->dct; + } else { + a->main.pitflg=a->main.volflg=a->main.panflg=0; + a->main.nna=a->dca=0; + a->dct=DCT_OFF; + } + + if (funky&2) /* instrument change */ { + /* IT random volume variations: 0:8 bit fixed, and one bit for + sign. */ + a->volume=a->tmpvolume=s->volume; + if ((s)&&(i)) { + if (i->rvolvar) { + a->volume=a->tmpvolume=s->volume+ + ((s->volume*((SLONG)i->rvolvar*(SLONG)getrandom(512) + ))/25600); + if (a->volume<0) + a->volume=a->tmpvolume=0; + else if (a->volume>64) + a->volume=a->tmpvolume=64; + } + if ((mod->panflag)&&(a->main.panning!=PAN_SURROUND)) { + a->main.panning+=((a->main.panning*((SLONG)i->rpanvar* + (SLONG)getrandom(512)))/25600); + if (a->main.panning<PAN_LEFT) + a->main.panning=PAN_LEFT; + else if (a->main.panning>PAN_RIGHT) + a->main.panning=PAN_RIGHT; + } + } + } + + a->wantedperiod=a->tmpperiod= + GetPeriod(mod->flags, (UWORD)a->main.note<<1,a->speed); + a->main.keyoff=KEY_KICK; + } + } +} + +/* Handles effects */ +void pt_EffectsPass1(MODULE *mod) +{ + SWORD channel; + MP_CONTROL *a; + MP_VOICE *aout; + int explicitslides; + + for (channel=0;channel<mod->numchn;channel++) { + a=&mod->control[channel]; + + if ((aout=a->slave)) { + a->main.fadevol=aout->main.fadevol; + a->main.period=aout->main.period; + if (a->main.kick==KICK_KEYOFF) + a->main.keyoff=aout->main.keyoff; + } + + if (!a->row) continue; + UniSetRow(a->row); + + a->ownper=a->ownvol=0; + explicitslides = pt_playeffects(mod, channel, a); + + /* continue volume slide if necessary for XM and IT */ + if (mod->flags&UF_BGSLIDES) { + if (!explicitslides && a->sliding) + DoS3MVolSlide(mod->vbtick, mod->flags, a, 0); + else if (a->tmpvolume) + a->sliding = explicitslides; + } + + if (!a->ownper) + a->main.period=a->tmpperiod; + if (!a->ownvol) + a->volume=a->tmpvolume; + + if (a->main.s) { + if (a->main.i) + a->main.outvolume= + (a->volume*a->main.s->globvol*a->main.i->globvol)>>10; + else + a->main.outvolume=(a->volume*a->main.s->globvol)>>4; + if (a->main.outvolume>256) + a->main.outvolume=256; + else if (a->main.outvolume<0) + a->main.outvolume=0; + } + } +} + +/* NNA management */ +void pt_NNA(MODULE *mod) +{ + SWORD channel; + MP_CONTROL *a; + + for (channel=0;channel<mod->numchn;channel++) { + a=&mod->control[channel]; + + if (a->main.kick==KICK_NOTE) { + BOOL kill=0; + + if (a->slave) { + MP_VOICE *aout; + + aout=a->slave; + if (aout->main.nna & NNA_MASK) { + /* Make sure the old MP_VOICE channel knows it has no + master now ! */ + a->slave=NULL; + /* assume the channel is taken by NNA */ + aout->mflag=0; + + switch (aout->main.nna) { + case NNA_CONTINUE: /* continue note, do nothing */ + break; + case NNA_OFF: /* note off */ + aout->main.keyoff|=KEY_OFF; + if ((!(aout->main.volflg & EF_ON))|| + (aout->main.volflg & EF_LOOP)) + aout->main.keyoff=KEY_KILL; + break; + case NNA_FADE: + aout->main.keyoff |= KEY_FADE; + break; + } + } + } + + if (a->dct!=DCT_OFF) { + int t; + + for (t=0;t<md_sngchn;t++) + if ((!Voice_Stopped_internal(t))&& + (mod->voice[t].masterchn==channel)&& + (a->main.sample==mod->voice[t].main.sample)) { + kill=0; + switch (a->dct) { + case DCT_NOTE: + if (a->main.note==mod->voice[t].main.note) + kill=1; + break; + case DCT_SAMPLE: + if (a->main.handle==mod->voice[t].main.handle) + kill=1; + break; + case DCT_INST: + kill=1; + break; + } + if (kill) + switch (a->dca) { + case DCA_CUT: + mod->voice[t].main.fadevol=0; + break; + case DCA_OFF: + mod->voice[t].main.keyoff|=KEY_OFF; + if ((!(mod->voice[t].main.volflg&EF_ON))|| + (mod->voice[t].main.volflg&EF_LOOP)) + mod->voice[t].main.keyoff=KEY_KILL; + break; + case DCA_FADE: + mod->voice[t].main.keyoff|=KEY_FADE; + break; + } + } + } + } /* if (a->main.kick==KICK_NOTE) */ + } +} + +/* Setup module and NNA voices */ +void pt_SetupVoices(MODULE *mod) +{ + SWORD channel; + MP_CONTROL *a; + MP_VOICE *aout; + + for (channel=0;channel<mod->numchn;channel++) { + a=&mod->control[channel]; + + if (a->main.notedelay) continue; + if (a->main.kick==KICK_NOTE) { + /* if no channel was cut above, find an empty or quiet channel + here */ + if (mod->flags&UF_NNA) { + if (!a->slave) { + int newchn; + + if ((newchn=MP_FindEmptyChannel(mod))!=-1) + a->slave=&mod->voice[a->slavechn=newchn]; + } + } else + a->slave=&mod->voice[a->slavechn=channel]; + + /* assign parts of MP_VOICE only done for a KICK_NOTE */ + if ((aout=a->slave)) { + if (aout->mflag && aout->master) aout->master->slave=NULL; + aout->master=a; + a->slave=aout; + aout->masterchn=channel; + aout->mflag=1; + } + } else + aout=a->slave; + + if (aout) + aout->main=a->main; + a->main.kick=KICK_ABSENT; + } +} + +/* second effect pass */ +void pt_EffectsPass2(MODULE *mod) +{ + SWORD channel; + MP_CONTROL *a; + UBYTE c; + + for (channel=0;channel<mod->numchn;channel++) { + a=&mod->control[channel]; + + if (!a->row) continue; + UniSetRow(a->row); + + while((c=UniGetByte())) + if (c==UNI_ITEFFECTS0) { + c=UniGetByte(); + if ((c>>4)==SS_S7EFFECTS) + DoNNAEffects(mod, a, c&0xf); + } else + UniSkipOpcode(); + } +} + +void Player_HandleTick(void) +{ + SWORD channel; + int max_volume; + +#if 0 + /* don't handle the very first ticks, this allows the other hardware to + settle down so we don't loose any starting notes */ + if (isfirst) { + isfirst--; + return; + } +#endif + + if ((!pf)||(pf->forbid)||(pf->sngpos>=pf->numpos)) return; + + /* update time counter (sngtime is in milliseconds (in fact 2^-10)) */ + pf->sngremainder+=(1<<9)*5; /* thus 2.5*(1<<10), since fps=0.4xtempo */ + pf->sngtime+=pf->sngremainder/pf->bpm; + pf->sngremainder%=pf->bpm; + + if (++pf->vbtick>=pf->sngspd) { + if (pf->pat_repcrazy) + pf->pat_repcrazy=0; /* play 2 times row 0 */ + else + pf->patpos++; + pf->vbtick=0; + + /* process pattern-delay. pf->patdly2 is the counter and pf->patdly is + the command memory. */ + if (pf->patdly) + pf->patdly2=pf->patdly,pf->patdly=0; + if (pf->patdly2) { + /* patterndelay active */ + if (--pf->patdly2) + /* so turn back pf->patpos by 1 */ + if (pf->patpos) pf->patpos--; + } + + /* do we have to get a new patternpointer ? (when pf->patpos reaches the + pattern size, or when a patternbreak is active) */ + if (((pf->patpos>=pf->numrow)&&(pf->numrow>0))&&(!pf->posjmp)) + pf->posjmp=3; + + if (pf->posjmp) { + pf->patpos=pf->numrow?(pf->patbrk%pf->numrow):0; + pf->pat_repcrazy=0; + pf->sngpos+=(pf->posjmp-2); + for (channel=0;channel<pf->numchn;channel++) + pf->control[channel].pat_reppos=-1; + + pf->patbrk=pf->posjmp=0; + /* handle the "---" (end of song) pattern since it can occur + *inside* the module in some formats */ + if ((pf->sngpos>=pf->numpos)|| + (pf->positions[pf->sngpos]==LAST_PATTERN)) { + if (!pf->wrap) return; + if (!(pf->sngpos=pf->reppos)) { + pf->volume=pf->initvolume>128?128:pf->initvolume; + if(pf->initspeed!=0) + pf->sngspd=pf->initspeed<32?pf->initspeed:32; + else + pf->sngspd=6; + pf->bpm=pf->inittempo<32?32:pf->inittempo; + } + } + if (pf->sngpos<0) pf->sngpos=pf->numpos-1; + } + + if (!pf->patdly2) + pt_Notes(pf); + } + + /* Fade global volume if enabled and we're playing the last pattern */ + if (((pf->sngpos==pf->numpos-1)|| + (pf->positions[pf->sngpos+1]==LAST_PATTERN))&& + (pf->fadeout)) + max_volume=pf->numrow?((pf->numrow-pf->patpos)*128)/pf->numrow:0; + else + max_volume=128; + + pt_EffectsPass1(pf); + if (pf->flags&UF_NNA) + pt_NNA(pf); + pt_SetupVoices(pf); + pt_EffectsPass2(pf); + + /* now set up the actual hardware channel playback information */ + pt_UpdateVoices(pf, max_volume); +} + +static void Player_Init_internal(MODULE* mod) +{ + int t; + + for (t=0;t<mod->numchn;t++) { + mod->control[t].main.chanvol=mod->chanvol[t]; + mod->control[t].main.panning=mod->panning[t]; + } + + mod->sngtime=0; + mod->sngremainder=0; + + mod->pat_repcrazy=0; + mod->sngpos=0; + if(mod->initspeed!=0) + mod->sngspd=mod->initspeed<32?mod->initspeed:32; + else + mod->sngspd=6; + mod->volume=mod->initvolume>128?128:mod->initvolume; + + mod->vbtick=mod->sngspd; + mod->patdly=0; + mod->patdly2=0; + mod->bpm=mod->inittempo<32?32:mod->inittempo; + mod->realchn=0; + + mod->patpos=0; + mod->posjmp=2; /* make sure the player fetches the first note */ + mod->numrow=-1; + mod->patbrk=0; +} + +BOOL Player_Init(MODULE* mod) +{ + mod->extspd=1; + mod->panflag=1; + mod->wrap=0; + mod->loop=1; + mod->fadeout=0; + + mod->relspd=0; + + /* make sure the player doesn't start with garbage */ + if (!(mod->control=(MP_CONTROL*)MikMod_calloc(mod->numchn,sizeof(MP_CONTROL)))) + return 1; + if (!(mod->voice=(MP_VOICE*)MikMod_calloc(md_sngchn,sizeof(MP_VOICE)))) + return 1; + + Player_Init_internal(mod); + return 0; +} + +void Player_Exit_internal(MODULE* mod) +{ + if (!mod) + return; + + /* Stop playback if necessary */ + if (mod==pf) { + Player_Stop_internal(); + pf=NULL; + } + + if (mod->control) + MikMod_free(mod->control); + if (mod->voice) + MikMod_free(mod->voice); + mod->control=NULL; + mod->voice=NULL; +} + +void Player_Exit(MODULE* mod) +{ + MUTEX_LOCK(vars); + Player_Exit_internal(mod); + MUTEX_UNLOCK(vars); +} + +MIKMODAPI void Player_SetVolume(SWORD volume) +{ + MUTEX_LOCK(vars); + if (pf) + pf->volume=(volume<0)?0:(volume>128)?128:volume; + MUTEX_UNLOCK(vars); +} + +MIKMODAPI MODULE* Player_GetModule(void) +{ + MODULE* result; + + MUTEX_LOCK(vars); + result=pf; + MUTEX_UNLOCK(vars); + + return result; +} + +MIKMODAPI void Player_Start(MODULE *mod) +{ + int t; + + if (!mod) + return; + + if (!MikMod_Active()) + MikMod_EnableOutput(); + + mod->forbid=0; + + MUTEX_LOCK(vars); + if (pf!=mod) { + /* new song is being started, so completely stop out the old one. */ + if (pf) pf->forbid=1; + for (t=0;t<md_sngchn;t++) Voice_Stop_internal(t); + } + pf=mod; + MUTEX_UNLOCK(vars); +} + +void Player_Stop_internal(void) +{ + if (!md_sfxchn) MikMod_DisableOutput_internal(); + if (pf) pf->forbid=1; + pf=NULL; +} + +MIKMODAPI void Player_Stop(void) +{ + MUTEX_LOCK(vars); + Player_Stop_internal(); + MUTEX_UNLOCK(vars); +} + +MIKMODAPI BOOL Player_Active(void) +{ + BOOL result=0; + + MUTEX_LOCK(vars); + if (pf) + result=(!(pf->sngpos>=pf->numpos)); + MUTEX_UNLOCK(vars); + + return result; +} + +MIKMODAPI void Player_NextPosition(void) +{ + MUTEX_LOCK(vars); + if (pf) { + int t; + + pf->forbid=1; + pf->posjmp=3; + pf->patbrk=0; + pf->vbtick=pf->sngspd; + + for (t=0;t<md_sngchn;t++) { + Voice_Stop_internal(t); + pf->voice[t].main.i=NULL; + pf->voice[t].main.s=NULL; + } + for (t=0;t<pf->numchn;t++) { + pf->control[t].main.i=NULL; + pf->control[t].main.s=NULL; + } + pf->forbid=0; + } + MUTEX_UNLOCK(vars); +} + +MIKMODAPI void Player_PrevPosition(void) +{ + MUTEX_LOCK(vars); + if (pf) { + int t; + + pf->forbid=1; + pf->posjmp=1; + pf->patbrk=0; + pf->vbtick=pf->sngspd; + + for (t=0;t<md_sngchn;t++) { + Voice_Stop_internal(t); + pf->voice[t].main.i=NULL; + pf->voice[t].main.s=NULL; + } + for (t=0;t<pf->numchn;t++) { + pf->control[t].main.i=NULL; + pf->control[t].main.s=NULL; + } + pf->forbid=0; + } + MUTEX_UNLOCK(vars); +} + +MIKMODAPI void Player_SetPosition(UWORD pos) +{ + MUTEX_LOCK(vars); + if (pf) { + int t; + + pf->forbid=1; + if (pos>=pf->numpos) pos=pf->numpos; + pf->posjmp=2; + pf->patbrk=0; + pf->sngpos=pos; + pf->vbtick=pf->sngspd; + + for (t=0;t<md_sngchn;t++) { + Voice_Stop_internal(t); + pf->voice[t].main.i=NULL; + pf->voice[t].main.s=NULL; + } + for (t=0;t<pf->numchn;t++) { + pf->control[t].main.i=NULL; + pf->control[t].main.s=NULL; + } + pf->forbid=0; + + if (!pos) + Player_Init_internal(pf); + } + MUTEX_UNLOCK(vars); +} + +static void Player_Unmute_internal(SLONG arg1,va_list ap) +{ + SLONG t,arg2,arg3=0; + + if (pf) { + switch (arg1) { + case MUTE_INCLUSIVE: + if (((!(arg2=va_arg(ap,SLONG)))&&(!(arg3=va_arg(ap,SLONG))))|| + (arg2>arg3)||(arg3>=pf->numchn)) + return; + for (;arg2<pf->numchn && arg2<=arg3;arg2++) + pf->control[arg2].muted=0; + break; + case MUTE_EXCLUSIVE: + if (((!(arg2=va_arg(ap,SLONG)))&&(!(arg3=va_arg(ap,SLONG))))|| + (arg2>arg3)||(arg3>=pf->numchn)) + return; + for (t=0;t<pf->numchn;t++) { + if ((t>=arg2) && (t<=arg3)) + continue; + pf->control[t].muted=0; + } + break; + default: + if (arg1<pf->numchn) pf->control[arg1].muted=0; + break; + } + } +} + +MIKMODAPI void Player_Unmute(SLONG arg1, ...) +{ + va_list args; + + va_start(args,arg1); + MUTEX_LOCK(vars); + Player_Unmute_internal(arg1,args); + MUTEX_UNLOCK(vars); + va_end(args); +} + +static void Player_Mute_internal(SLONG arg1,va_list ap) +{ + SLONG t,arg2,arg3=0; + + if (pf) { + switch (arg1) { + case MUTE_INCLUSIVE: + if (((!(arg2=va_arg(ap,SLONG)))&&(!(arg3=va_arg(ap,SLONG))))|| + (arg2>arg3)||(arg3>=pf->numchn)) + return; + for (;arg2<pf->numchn && arg2<=arg3;arg2++) + pf->control[arg2].muted=1; + break; + case MUTE_EXCLUSIVE: + if (((!(arg2=va_arg(ap,SLONG)))&&(!(arg3=va_arg(ap,SLONG))))|| + (arg2>arg3)||(arg3>=pf->numchn)) + return; + for (t=0;t<pf->numchn;t++) { + if ((t>=arg2) && (t<=arg3)) + continue; + pf->control[t].muted=1; + } + break; + default: + if (arg1<pf->numchn) + pf->control[arg1].muted=1; + break; + } + } +} + +MIKMODAPI void Player_Mute(SLONG arg1,...) +{ + va_list args; + + va_start(args,arg1); + MUTEX_LOCK(vars); + Player_Mute_internal(arg1,args); + MUTEX_UNLOCK(vars); + va_end(args); +} + +static void Player_ToggleMute_internal(SLONG arg1,va_list ap) +{ + SLONG arg2,arg3=0; + SLONG t; + + if (pf) { + switch (arg1) { + case MUTE_INCLUSIVE: + if (((!(arg2=va_arg(ap,SLONG)))&&(!(arg3=va_arg(ap,SLONG))))|| + (arg2>arg3)||(arg3>=pf->numchn)) + return; + for (;arg2<pf->numchn && arg2<=arg3;arg2++) + pf->control[arg2].muted=1-pf->control[arg2].muted; + break; + case MUTE_EXCLUSIVE: + if (((!(arg2=va_arg(ap,SLONG)))&&(!(arg3=va_arg(ap,SLONG))))|| + (arg2>arg3)||(arg3>=pf->numchn)) + return; + for (t=0;t<pf->numchn;t++) { + if ((t>=arg2) && (t<=arg3)) + continue; + pf->control[t].muted=1-pf->control[t].muted; + } + break; + default: + if (arg1<pf->numchn) + pf->control[arg1].muted=1-pf->control[arg1].muted; + break; + } + } +} + +MIKMODAPI void Player_ToggleMute(SLONG arg1,...) +{ + va_list args; + + va_start(args,arg1); + MUTEX_LOCK(vars); + Player_ToggleMute_internal(arg1,args); + MUTEX_UNLOCK(vars); + va_end(args); +} + +MIKMODAPI BOOL Player_Muted(UBYTE chan) +{ + BOOL result=1; + + MUTEX_LOCK(vars); + if (pf) + result=(chan<pf->numchn)?pf->control[chan].muted:1; + MUTEX_UNLOCK(vars); + + return result; +} + +MIKMODAPI int Player_GetChannelVoice(UBYTE chan) +{ + int result=0; + + MUTEX_LOCK(vars); + if (pf) + result=(chan<pf->numchn)?pf->control[chan].slavechn:-1; + MUTEX_UNLOCK(vars); + + return result; +} + +MIKMODAPI UWORD Player_GetChannelPeriod(UBYTE chan) +{ + UWORD result=0; + + MUTEX_LOCK(vars); + if (pf) + result=(chan<pf->numchn)?pf->control[chan].main.period:0; + MUTEX_UNLOCK(vars); + + return result; +} + +BOOL Player_Paused_internal(void) +{ + return pf?pf->forbid:1; +} + +MIKMODAPI BOOL Player_Paused(void) +{ + BOOL result; + + MUTEX_LOCK(vars); + result=Player_Paused_internal(); + MUTEX_UNLOCK(vars); + + return result; +} + +MIKMODAPI void Player_TogglePause(void) +{ + MUTEX_LOCK(vars); + if (pf) + pf->forbid=1-pf->forbid; + MUTEX_UNLOCK(vars); +} + +MIKMODAPI void Player_SetSpeed(UWORD speed) +{ + MUTEX_LOCK(vars); + if (pf) + pf->sngspd=speed?(speed<32?speed:32):1; + MUTEX_UNLOCK(vars); +} + +MIKMODAPI void Player_SetTempo(UWORD tempo) +{ + if (tempo<32) tempo=32; + MUTEX_LOCK(vars); + if (pf) { + if ((!(pf->flags&UF_HIGHBPM))&&(tempo>255)) tempo=255; + pf->bpm=tempo; + } + MUTEX_UNLOCK(vars); +} + +/* ex:set ts=4: */ diff --git a/src/libs/mikmod/munitrk.c b/src/libs/mikmod/munitrk.c new file mode 100644 index 0000000..2818969 --- /dev/null +++ b/src/libs/mikmod/munitrk.c @@ -0,0 +1,303 @@ +/* MikMod sound library + (c) 1998, 1999, 2000, 2001 Miodrag Vallat and others - see file AUTHORS + for complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + All routines dealing with the manipulation of UNITRK streams + +==============================================================================*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "mikmod_internals.h" + +#include <string.h> + +/* Unibuffer chunk size */ +#define BUFPAGE 128 + +UWORD unioperands[UNI_LAST]={ + 0, /* not used */ + 1, /* UNI_NOTE */ + 1, /* UNI_INSTRUMENT */ + 1, /* UNI_PTEFFECT0 */ + 1, /* UNI_PTEFFECT1 */ + 1, /* UNI_PTEFFECT2 */ + 1, /* UNI_PTEFFECT3 */ + 1, /* UNI_PTEFFECT4 */ + 1, /* UNI_PTEFFECT5 */ + 1, /* UNI_PTEFFECT6 */ + 1, /* UNI_PTEFFECT7 */ + 1, /* UNI_PTEFFECT8 */ + 1, /* UNI_PTEFFECT9 */ + 1, /* UNI_PTEFFECTA */ + 1, /* UNI_PTEFFECTB */ + 1, /* UNI_PTEFFECTC */ + 1, /* UNI_PTEFFECTD */ + 1, /* UNI_PTEFFECTE */ + 1, /* UNI_PTEFFECTF */ + 1, /* UNI_S3MEFFECTA */ + 1, /* UNI_S3MEFFECTD */ + 1, /* UNI_S3MEFFECTE */ + 1, /* UNI_S3MEFFECTF */ + 1, /* UNI_S3MEFFECTI */ + 1, /* UNI_S3MEFFECTQ */ + 1, /* UNI_S3MEFFECTR */ + 1, /* UNI_S3MEFFECTT */ + 1, /* UNI_S3MEFFECTU */ + 0, /* UNI_KEYOFF */ + 1, /* UNI_KEYFADE */ + 2, /* UNI_VOLEFFECTS */ + 1, /* UNI_XMEFFECT4 */ + 1, /* UNI_XMEFFECT6 */ + 1, /* UNI_XMEFFECTA */ + 1, /* UNI_XMEFFECTE1 */ + 1, /* UNI_XMEFFECTE2 */ + 1, /* UNI_XMEFFECTEA */ + 1, /* UNI_XMEFFECTEB */ + 1, /* UNI_XMEFFECTG */ + 1, /* UNI_XMEFFECTH */ + 1, /* UNI_XMEFFECTL */ + 1, /* UNI_XMEFFECTP */ + 1, /* UNI_XMEFFECTX1 */ + 1, /* UNI_XMEFFECTX2 */ + 1, /* UNI_ITEFFECTG */ + 1, /* UNI_ITEFFECTH */ + 1, /* UNI_ITEFFECTI */ + 1, /* UNI_ITEFFECTM */ + 1, /* UNI_ITEFFECTN */ + 1, /* UNI_ITEFFECTP */ + 1, /* UNI_ITEFFECTT */ + 1, /* UNI_ITEFFECTU */ + 1, /* UNI_ITEFFECTW */ + 1, /* UNI_ITEFFECTY */ + 2, /* UNI_ITEFFECTZ */ + 1, /* UNI_ITEFFECTS0 */ + 2, /* UNI_ULTEFFECT9 */ + 2, /* UNI_MEDSPEED */ + 0, /* UNI_MEDEFFECTF1 */ + 0, /* UNI_MEDEFFECTF2 */ + 0, /* UNI_MEDEFFECTF3 */ + 2, /* UNI_OKTARP */ +}; + +/* Sparse description of the internal module format + ------------------------------------------------ + + A UNITRK stream is an array of bytes representing a single track of a pattern. +It's made up of 'repeat/length' bytes, opcodes and operands (sort of a assembly +language): + +rrrlllll +[REP/LEN][OPCODE][OPERAND][OPCODE][OPERAND] [REP/LEN][OPCODE][OPERAND].. +^ ^ ^ +|-------ROWS 0 - 0+REP of a track---------| |-------ROWS xx - xx+REP of a track... + + The rep/len byte contains the number of bytes in the current row, _including_ +the length byte itself (So the LENGTH byte of row 0 in the previous example +would have a value of 5). This makes it easy to search through a stream for a +particular row. A track is concluded by a 0-value length byte. + + The upper 3 bits of the rep/len byte contain the number of times -1 this row +is repeated for this track. (so a value of 7 means this row is repeated 8 times) + + Opcodes can range from 1 to 255 but currently only opcodes 1 to 62 are being +used. Each opcode can have a different number of operands. You can find the +number of operands to a particular opcode by using the opcode as an index into +the 'unioperands' table. + +*/ + +/*========== Reading routines */ + +static UBYTE *rowstart; /* startadress of a row */ +static UBYTE *rowend; /* endaddress of a row (exclusive) */ +static UBYTE *rowpc; /* current unimod(tm) programcounter */ + +static UBYTE lastbyte; /* for UniSkipOpcode() */ + +void UniSetRow(UBYTE* t) +{ + rowstart = t; + rowpc = rowstart; + rowend = t?rowstart+(*(rowpc++)&0x1f):t; +} + +UBYTE UniGetByte(void) +{ + return lastbyte = (rowpc<rowend)?*(rowpc++):0; +} + +UWORD UniGetWord(void) +{ + return ((UWORD)UniGetByte()<<8)|UniGetByte(); +} + +void UniSkipOpcode(void) +{ + if (lastbyte < UNI_LAST) { + UWORD t = unioperands[lastbyte]; + + while (t--) + UniGetByte(); + } +} + +/* Finds the address of row number 'row' in the UniMod(tm) stream 't' returns + NULL if the row can't be found. */ +UBYTE *UniFindRow(UBYTE* t,UWORD row) +{ + UBYTE c,l; + + if(t) + while(1) { + c = *t; /* get rep/len byte */ + if(!c) return NULL; /* zero ? -> end of track.. */ + l = (c>>5)+1; /* extract repeat value */ + if(l>row) break; /* reached wanted row? -> return pointer */ + row -= l; /* haven't reached row yet.. update row */ + t += c&0x1f; /* point t to the next row */ + } + return t; +} + +/*========== Writing routines */ + +static UBYTE *unibuf; /* pointer to the temporary unitrk buffer */ +static UWORD unimax; /* buffer size */ + +static UWORD unipc; /* buffer cursor */ +static UWORD unitt; /* current row index */ +static UWORD lastp; /* previous row index */ + +/* Resets index-pointers to create a new track. */ +void UniReset(void) +{ + unitt = 0; /* reset index to rep/len byte */ + unipc = 1; /* first opcode will be written to index 1 */ + lastp = 0; /* no previous row yet */ + unibuf[0] = 0; /* clear rep/len byte */ +} + +/* Expands the buffer */ +static BOOL UniExpand(int wanted) +{ + if ((unipc+wanted)>=unimax) { + UBYTE *newbuf; + + /* Expand the buffer by BUFPAGE bytes */ + newbuf=(UBYTE*)MikMod_realloc(unibuf,(unimax+BUFPAGE)*sizeof(UBYTE)); + + /* Check if realloc succeeded */ + if(newbuf) { + unibuf = newbuf; + unimax+=BUFPAGE; + return 1; + } else + return 0; + } + return 1; +} + +/* Appends one byte of data to the current row of a track. */ +void UniWriteByte(UBYTE data) +{ + if (UniExpand(1)) + /* write byte to current position and update */ + unibuf[unipc++]=data; +} + +void UniWriteWord(UWORD data) +{ + if (UniExpand(2)) { + unibuf[unipc++]=data>>8; + unibuf[unipc++]=data&0xff; + } +} + +static BOOL MyCmp(UBYTE* a,UBYTE* b,UWORD l) +{ + UWORD t; + + for(t=0;t<l;t++) + if(*(a++)!=*(b++)) return 0; + return 1; +} + +/* Closes the current row of a unitrk stream (updates the rep/len byte) and sets + pointers to start a new row. */ +void UniNewline(void) +{ + UWORD n,l,len; + + n = (unibuf[lastp]>>5)+1; /* repeat of previous row */ + l = (unibuf[lastp]&0x1f); /* length of previous row */ + + len = unipc-unitt; /* length of current row */ + + /* Now, check if the previous and the current row are identical.. when they + are, just increase the repeat field of the previous row */ + if(n<8 && len==l && MyCmp(&unibuf[lastp+1],&unibuf[unitt+1],len-1)) { + unibuf[lastp]+=0x20; + unipc = unitt+1; + } else { + if (UniExpand(unitt-unipc)) { + /* current and previous row aren't equal... update the pointers */ + unibuf[unitt] = len; + lastp = unitt; + unitt = unipc++; + } + } +} + +/* Terminates the current unitrk stream and returns a pointer to a copy of the + stream. */ +UBYTE* UniDup(void) +{ + UBYTE *d; + + if (!UniExpand(unitt-unipc)) return NULL; + unibuf[unitt] = 0; + + if(!(d=(UBYTE *)MikMod_malloc(unipc))) return NULL; + memcpy(d,unibuf,unipc); + + return d; +} + +BOOL UniInit(void) +{ + unimax = BUFPAGE; + + if(!(unibuf=(UBYTE*)MikMod_malloc(unimax*sizeof(UBYTE)))) return 0; + return 1; +} + +void UniCleanup(void) +{ + if(unibuf) MikMod_free(unibuf); + unibuf = NULL; +} + +/* ex:set ts=4: */ diff --git a/src/libs/mikmod/mwav.c b/src/libs/mikmod/mwav.c new file mode 100644 index 0000000..b629985 --- /dev/null +++ b/src/libs/mikmod/mwav.c @@ -0,0 +1,210 @@ +/* MikMod sound library + (c) 1998, 1999, 2000, 2001 Miodrag Vallat and others - see file AUTHORS + for complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + WAV sample loader + +==============================================================================*/ + +/* + FIXME: Stereo .WAV files are not yet supported as samples. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include <string.h> + +#include "mikmod_internals.h" + +#ifdef SUNOS +extern int fprintf(FILE *, const char *, ...); +#endif + +typedef struct WAV { + CHAR rID[4]; + ULONG rLen; + CHAR wID[4]; + CHAR fID[4]; + ULONG fLen; + UWORD wFormatTag; + UWORD nChannels; + ULONG nSamplesPerSec; + ULONG nAvgBytesPerSec; + UWORD nBlockAlign; + UWORD nFormatSpecific; +} WAV; + +SAMPLE* Sample_LoadGeneric_internal(MREADER* reader) +{ + SAMPLE *si=NULL; + WAV wh; + BOOL have_fmt=0; + + /* read wav header */ + _mm_read_string(wh.rID,4,reader); + wh.rLen = _mm_read_I_ULONG(reader); + _mm_read_string(wh.wID,4,reader); + + /* check for correct header */ + if(_mm_eof(reader)|| memcmp(wh.rID,"RIFF",4) || memcmp(wh.wID,"WAVE",4)) { + _mm_errno = MMERR_UNKNOWN_WAVE_TYPE; + return NULL; + } + + /* scan all RIFF blocks until we find the sample data */ + for(;;) { + CHAR dID[4]; + ULONG len,start; + + _mm_read_string(dID,4,reader); + len = _mm_read_I_ULONG(reader); + /* truncated file ? */ + if (_mm_eof(reader)) { + _mm_errno=MMERR_UNKNOWN_WAVE_TYPE; + return NULL; + } + start = _mm_ftell(reader); + + /* sample format block + should be present only once and before a data block */ + if(!memcmp(dID,"fmt ",4)) { + wh.wFormatTag = _mm_read_I_UWORD(reader); + wh.nChannels = _mm_read_I_UWORD(reader); + wh.nSamplesPerSec = _mm_read_I_ULONG(reader); + wh.nAvgBytesPerSec = _mm_read_I_ULONG(reader); + wh.nBlockAlign = _mm_read_I_UWORD(reader); + wh.nFormatSpecific = _mm_read_I_UWORD(reader); + +#ifdef MIKMOD_DEBUG + fprintf(stderr,"\rwavloader : wFormatTag=%04x blockalign=%04x nFormatSpc=%04x\n", + wh.wFormatTag,wh.nBlockAlign,wh.nFormatSpecific); +#endif + + if((have_fmt)||(wh.nChannels>1)) { + _mm_errno=MMERR_UNKNOWN_WAVE_TYPE; + return NULL; + } + have_fmt=1; + } else + /* sample data block + should be present only once and after a format block */ + if(!memcmp(dID,"data",4)) { + if(!have_fmt) { + _mm_errno=MMERR_UNKNOWN_WAVE_TYPE; + return NULL; + } + if(!(si=(SAMPLE*)MikMod_malloc(sizeof(SAMPLE)))) return NULL; + si->speed = wh.nSamplesPerSec/wh.nChannels; + si->volume = 64; + si->length = len; + if(wh.nBlockAlign == 2) { + si->flags = SF_16BITS | SF_SIGNED; + si->length >>= 1; + } + si->inflags = si->flags; + SL_RegisterSample(si,MD_SNDFX,reader); + SL_LoadSamples(); + + /* skip any other remaining blocks - so in case of repeated sample + fragments, we'll return the first anyway instead of an error */ + break; + } + /* onto next block */ + _mm_fseek(reader,start+len,SEEK_SET); + if (_mm_eof(reader)) + break; + } + + return si; +} + +MIKMODAPI SAMPLE* Sample_LoadGeneric(MREADER* reader) +{ + SAMPLE* result; + + MUTEX_LOCK(vars); + result=Sample_LoadGeneric_internal(reader); + MUTEX_UNLOCK(vars); + + return result; +} + +MIKMODAPI extern SAMPLE *Sample_LoadMem(const char *buf, int len) +{ + SAMPLE* result=NULL; + MREADER* reader; + + if ((reader=_mm_new_mem_reader(buf, len))) { + result=Sample_LoadGeneric(reader); + _mm_delete_mem_reader(reader); + } + return result; +} + +MIKMODAPI SAMPLE* Sample_LoadFP(FILE *fp) +{ + SAMPLE* result=NULL; + MREADER* reader; + + if((reader=_mm_new_file_reader(fp))) { + result=Sample_LoadGeneric(reader); + _mm_delete_file_reader(reader); + } + return result; +} + +MIKMODAPI SAMPLE* Sample_Load(CHAR* filename) +{ + FILE *fp; + SAMPLE *si=NULL; + + if(!(md_mode & DMODE_SOFT_SNDFX)) return NULL; + if((fp=_mm_fopen(filename,"rb"))) { + si = Sample_LoadFP(fp); + _mm_fclose(fp); + } + return si; +} + +MIKMODAPI void Sample_Free(SAMPLE* si) +{ + if(si) { + MD_SampleUnload(si->handle); + MikMod_free(si); + } +} + +void Sample_Free_internal(SAMPLE *si) +{ + MUTEX_LOCK(vars); + Sample_Free(si); + MUTEX_UNLOCK(vars); +} + +/* ex:set ts=4: */ diff --git a/src/libs/mikmod/npertab.c b/src/libs/mikmod/npertab.c new file mode 100644 index 0000000..c6659e8 --- /dev/null +++ b/src/libs/mikmod/npertab.c @@ -0,0 +1,48 @@ +/* MikMod sound library + (c) 1998, 1999 Miodrag Vallat and others - see file AUTHORS for + complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + MOD format period table. Used by both the MOD and M15 (15-inst mod) Loaders. + +==============================================================================*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "mikmod_internals.h" + +UWORD npertab[7 * OCTAVE] = { + /* Octaves 6 -> 0 */ + /* C C# D D# E F F# G G# A A# B */ + 0x6b0,0x650,0x5f4,0x5a0,0x54c,0x500,0x4b8,0x474,0x434,0x3f8,0x3c0,0x38a, + 0x358,0x328,0x2fa,0x2d0,0x2a6,0x280,0x25c,0x23a,0x21a,0x1fc,0x1e0,0x1c5, + 0x1ac,0x194,0x17d,0x168,0x153,0x140,0x12e,0x11d,0x10d,0x0fe,0x0f0,0x0e2, + 0x0d6,0x0ca,0x0be,0x0b4,0x0aa,0x0a0,0x097,0x08f,0x087,0x07f,0x078,0x071, + 0x06b,0x065,0x05f,0x05a,0x055,0x050,0x04b,0x047,0x043,0x03f,0x03c,0x038, + 0x035,0x032,0x02f,0x02d,0x02a,0x028,0x025,0x023,0x021,0x01f,0x01e,0x01c, + 0x01b,0x019,0x018,0x016,0x015,0x014,0x013,0x012,0x011,0x010,0x00f,0x00e +}; + +/* ex:set ts=4: */ + diff --git a/src/libs/mikmod/sloader.c b/src/libs/mikmod/sloader.c new file mode 100644 index 0000000..0b8c685 --- /dev/null +++ b/src/libs/mikmod/sloader.c @@ -0,0 +1,519 @@ +/* MikMod sound library + (c) 1998, 1999, 2000, 2001 Miodrag Vallat and others - see file AUTHORS + for complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + Routines for loading samples. The sample loader utilizes the routines + provided by the "registered" sample loader. + +==============================================================================*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "mikmod_internals.h" + +static int sl_rlength; +static SWORD sl_old; +static SWORD *sl_buffer=NULL; +static SAMPLOAD *musiclist=NULL,*sndfxlist=NULL; + +/* size of the loader buffer in words */ +#define SLBUFSIZE 2048 + +/* IT-Compressed status structure */ +typedef struct ITPACK { + UWORD bits; /* current number of bits */ + UWORD bufbits; /* bits in buffer */ + SWORD last; /* last output */ + UBYTE buf; /* bit buffer */ +} ITPACK; + +BOOL SL_Init(SAMPLOAD* s) +{ + if(!sl_buffer) + if(!(sl_buffer=MikMod_malloc(SLBUFSIZE*sizeof(SWORD)))) return 0; + + sl_rlength = s->length; + if(s->infmt & SF_16BITS) sl_rlength>>=1; + sl_old = 0; + + return 1; +} + +void SL_Exit(SAMPLOAD *s) +{ + if(sl_rlength>0) _mm_fseek(s->reader,sl_rlength,SEEK_CUR); + if(sl_buffer) { + MikMod_free(sl_buffer); + sl_buffer=NULL; + } +} + +/* unpack a 8bit IT packed sample */ +static BOOL read_itcompr8(ITPACK* status,MREADER *reader,SWORD *sl_buffer,UWORD count,UWORD* incnt) +{ + SWORD *dest=sl_buffer,*end=sl_buffer+count; + UWORD x,y,needbits,havebits,new_count=0; + UWORD bits = status->bits; + UWORD bufbits = status->bufbits; + SBYTE last = status->last; + UBYTE buf = status->buf; + + while (dest<end) { + needbits=new_count?3:bits; + x=havebits=0; + while (needbits) { + /* feed buffer */ + if (!bufbits) { + if((*incnt)--) + buf=_mm_read_UBYTE(reader); + else + buf=0; + bufbits=8; + } + /* get as many bits as necessary */ + y = needbits<bufbits?needbits:bufbits; + x|= (buf & ((1<<y)- 1))<<havebits; + buf>>=y; + bufbits-=y; + needbits-=y; + havebits+=y; + } + if (new_count) { + new_count = 0; + if (++x >= bits) + x++; + bits = x; + continue; + } + if (bits<7) { + if (x==(1<<(bits-1))) { + new_count = 1; + continue; + } + } + else if (bits<9) { + y = (0xff >> (9-bits)) - 4; + if ((x>y)&&(x<=y+8)) { + if ((x-=y)>=bits) + x++; + bits = x; + continue; + } + } + else if (bits<10) { + if (x>=0x100) { + bits=x-0x100+1; + continue; + } + } else { + /* error in compressed data... */ + _mm_errno=MMERR_ITPACK_INVALID_DATA; + return 0; + } + + if (bits<8) /* extend sign */ + x = ((SBYTE)(x <<(8-bits))) >> (8-bits); + *(dest++)= (last+=x) << 8; /* convert to 16 bit */ + } + status->bits = bits; + status->bufbits = bufbits; + status->last = last; + status->buf = buf; + return !!(dest-sl_buffer); +} + +/* unpack a 16bit IT packed sample */ +static BOOL read_itcompr16(ITPACK *status,MREADER *reader,SWORD *sl_buffer,UWORD count,UWORD* incnt) +{ + SWORD *dest=sl_buffer,*end=sl_buffer+count; + SLONG x,y,needbits,havebits,new_count=0; + UWORD bits = status->bits; + UWORD bufbits = status->bufbits; + SWORD last = status->last; + UBYTE buf = status->buf; + + while (dest<end) { + needbits=new_count?4:bits; + x=havebits=0; + while (needbits) { + /* feed buffer */ + if (!bufbits) { + if((*incnt)--) + buf=_mm_read_UBYTE(reader); + else + buf=0; + bufbits=8; + } + /* get as many bits as necessary */ + y=needbits<bufbits?needbits:bufbits; + x|=(buf &((1<<y)-1))<<havebits; + buf>>=y; + bufbits-=y; + needbits-=y; + havebits+=y; + } + if (new_count) { + new_count = 0; + if (++x >= bits) + x++; + bits = x; + continue; + } + if (bits<7) { + if (x==(1<<(bits-1))) { + new_count=1; + continue; + } + } + else if (bits<17) { + y=(0xffff>>(17-bits))-8; + if ((x>y)&&(x<=y+16)) { + if ((x-=y)>=bits) + x++; + bits = x; + continue; + } + } + else if (bits<18) { + if (x>=0x10000) { + bits=x-0x10000+1; + continue; + } + } else { + /* error in compressed data... */ + _mm_errno=MMERR_ITPACK_INVALID_DATA; + return 0; + } + + if (bits<16) /* extend sign */ + x = ((SWORD)(x<<(16-bits)))>>(16-bits); + *(dest++)=(last+=x); + } + status->bits = bits; + status->bufbits = bufbits; + status->last = last; + status->buf = buf; + return !!(dest-sl_buffer); +} + +static BOOL SL_LoadInternal(void* buffer,UWORD infmt,UWORD outfmt,int scalefactor,ULONG length,MREADER* reader,BOOL dither) +{ + SBYTE *bptr = (SBYTE*)buffer; + SWORD *wptr = (SWORD*)buffer; + int stodo,t,u; + + int result,c_block=0; /* compression bytes until next block */ + ITPACK status; + UWORD incnt; + + while(length) { + stodo=(length<SLBUFSIZE)?length:SLBUFSIZE; + + if(infmt&SF_ITPACKED) { + sl_rlength=0; + if (!c_block) { + status.bits = (infmt & SF_16BITS) ? 17 : 9; + status.last = status.bufbits = 0; + incnt=_mm_read_I_UWORD(reader); + c_block = (infmt & SF_16BITS) ? 0x4000 : 0x8000; + if(infmt&SF_DELTA) sl_old=0; + } + if (infmt & SF_16BITS) { + if(!(result=read_itcompr16(&status,reader,sl_buffer,stodo,&incnt))) + return 1; + } else { + if(!(result=read_itcompr8(&status,reader,sl_buffer,stodo,&incnt))) + return 1; + } + if(result!=stodo) { + _mm_errno=MMERR_ITPACK_INVALID_DATA; + return 1; + } + c_block -= stodo; + } else { + if(infmt&SF_16BITS) { + if(infmt&SF_BIG_ENDIAN) + _mm_read_M_SWORDS(sl_buffer,stodo,reader); + else + _mm_read_I_SWORDS(sl_buffer,stodo,reader); + } else { + SBYTE *src; + SWORD *dest; + + reader->Read(reader,sl_buffer,sizeof(SBYTE)*stodo); + src = (SBYTE*)sl_buffer; + dest = sl_buffer; + src += stodo;dest += stodo; + + for(t=0;t<stodo;t++) { + src--;dest--; + *dest = (*src)<<8; + } + } + sl_rlength-=stodo; + } + + if(infmt & SF_DELTA) + for(t=0;t<stodo;t++) { + sl_buffer[t] += sl_old; + sl_old = sl_buffer[t]; + } + + if((infmt^outfmt) & SF_SIGNED) + for(t=0;t<stodo;t++) + sl_buffer[t]^= 0x8000; + + if(scalefactor) { + int idx = 0; + SLONG scaleval; + + /* Sample Scaling... average values for better results. */ + t= 0; + while(t<stodo && length) { + scaleval = 0; + for(u=scalefactor;u && t<stodo;u--,t++) + scaleval+=sl_buffer[t]; + sl_buffer[idx++]=scaleval/(scalefactor-u); + length--; + } + stodo = idx; + } else + length -= stodo; + + if (dither) { + if((infmt & SF_STEREO) && !(outfmt & SF_STEREO)) { + /* dither stereo to mono, average together every two samples */ + SLONG avgval; + int idx = 0; + + t=0; + while(t<stodo && length) { + avgval=sl_buffer[t++]; + avgval+=sl_buffer[t++]; + sl_buffer[idx++]=avgval>>1; + length-=2; + } + stodo = idx; + } + } + + if(outfmt & SF_16BITS) { + for(t=0;t<stodo;t++) + *(wptr++)=sl_buffer[t]; + } else { + for(t=0;t<stodo;t++) + *(bptr++)=sl_buffer[t]>>8; + } + } + return 0; +} + +BOOL SL_Load(void* buffer,SAMPLOAD *smp,ULONG length) +{ + return SL_LoadInternal(buffer,smp->infmt,smp->outfmt,smp->scalefactor, + length,smp->reader,0); +} + +/* Registers a sample for loading when SL_LoadSamples() is called. */ +SAMPLOAD* SL_RegisterSample(SAMPLE* s,int type,MREADER* reader) +{ + SAMPLOAD *news,**samplist,*cruise; + + if(type==MD_MUSIC) { + samplist = &musiclist; + cruise = musiclist; + } else + if (type==MD_SNDFX) { + samplist = &sndfxlist; + cruise = sndfxlist; + } else + return NULL; + + /* Allocate and add structure to the END of the list */ + if(!(news=(SAMPLOAD*)MikMod_malloc(sizeof(SAMPLOAD)))) return NULL; + + if(cruise) { + while(cruise->next) cruise=cruise->next; + cruise->next = news; + } else + *samplist = news; + + news->infmt = s->flags & SF_FORMATMASK; + news->outfmt = news->infmt; + news->reader = reader; + news->sample = s; + news->length = s->length; + news->loopstart = s->loopstart; + news->loopend = s->loopend; + + return news; +} + +static void FreeSampleList(SAMPLOAD* s) +{ + SAMPLOAD *old; + + while(s) { + old = s; + s = s->next; + MikMod_free(old); + } +} + +/* Returns the total amount of memory required by the samplelist queue. */ +static ULONG SampleTotal(SAMPLOAD* samplist,int type) +{ + int total = 0; + + while(samplist) { + samplist->sample->flags= + (samplist->sample->flags&~SF_FORMATMASK)|samplist->outfmt; + total += MD_SampleLength(type,samplist->sample); + samplist=samplist->next; + } + + return total; +} + +static ULONG RealSpeed(SAMPLOAD *s) +{ + return(s->sample->speed/(s->scalefactor?s->scalefactor:1)); +} + +static BOOL DitherSamples(SAMPLOAD* samplist,int type) +{ + SAMPLOAD *c2smp=NULL; + ULONG maxsize, speed; + SAMPLOAD *s; + + if(!samplist) return 0; + + if((maxsize=MD_SampleSpace(type)*1024)) + while(SampleTotal(samplist,type)>maxsize) { + /* First Pass - check for any 16 bit samples */ + s = samplist; + while(s) { + if(s->outfmt & SF_16BITS) { + SL_Sample16to8(s); + break; + } + s=s->next; + } + /* Second pass (if no 16bits found above) is to take the sample with + the highest speed and dither it by half. */ + if(!s) { + s = samplist; + speed = 0; + while(s) { + if((s->sample->length) && (RealSpeed(s)>speed)) { + speed=RealSpeed(s); + c2smp=s; + } + s=s->next; + } + if (c2smp) + SL_HalveSample(c2smp,2); + } + } + + /* Samples dithered, now load them ! */ + s = samplist; + while(s) { + /* sample has to be loaded ? -> increase number of samples, allocate + memory and load sample. */ + if(s->sample->length) { + if(s->sample->seekpos) + _mm_fseek(s->reader, s->sample->seekpos, SEEK_SET); + + /* Call the sample load routine of the driver module. It has to + return a 'handle' (>=0) that identifies the sample. */ + s->sample->handle = MD_SampleLoad(s, type); + s->sample->flags = (s->sample->flags & ~SF_FORMATMASK) | s->outfmt; + if(s->sample->handle<0) { + FreeSampleList(samplist); + if(_mm_errorhandler) _mm_errorhandler(); + return 1; + } + } + s = s->next; + } + + FreeSampleList(samplist); + return 0; +} + +BOOL SL_LoadSamples(void) +{ + BOOL ok; + + _mm_critical = 0; + + if((!musiclist)&&(!sndfxlist)) return 0; + ok=DitherSamples(musiclist,MD_MUSIC)||DitherSamples(sndfxlist,MD_SNDFX); + musiclist=sndfxlist=NULL; + + return ok; +} + +void SL_Sample16to8(SAMPLOAD* s) +{ + s->outfmt &= ~SF_16BITS; + s->sample->flags = (s->sample->flags&~SF_FORMATMASK) | s->outfmt; +} + +void SL_Sample8to16(SAMPLOAD* s) +{ + s->outfmt |= SF_16BITS; + s->sample->flags = (s->sample->flags&~SF_FORMATMASK) | s->outfmt; +} + +void SL_SampleSigned(SAMPLOAD* s) +{ + s->outfmt |= SF_SIGNED; + s->sample->flags = (s->sample->flags&~SF_FORMATMASK) | s->outfmt; +} + +void SL_SampleUnsigned(SAMPLOAD* s) +{ + s->outfmt &= ~SF_SIGNED; + s->sample->flags = (s->sample->flags&~SF_FORMATMASK) | s->outfmt; +} + +void SL_HalveSample(SAMPLOAD* s,int factor) +{ + s->scalefactor=factor>0?factor:2; + + s->sample->divfactor = s->scalefactor; + s->sample->length = s->length / s->scalefactor; + s->sample->loopstart = s->loopstart / s->scalefactor; + s->sample->loopend = s->loopend / s->scalefactor; +} + + +/* ex:set ts=4: */ diff --git a/src/libs/mikmod/virtch.c b/src/libs/mikmod/virtch.c new file mode 100644 index 0000000..0bbb470 --- /dev/null +++ b/src/libs/mikmod/virtch.c @@ -0,0 +1,935 @@ +/* MikMod sound library + (c) 1998, 1999, 2000, 2001, 2002 Miodrag Vallat and others - see file + AUTHORS for complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + Sample mixing routines, using a 32 bits mixing buffer. + +==============================================================================*/ + +/* + + Optional features include: + (a) 4-step reverb (for 16 bit output only) + (b) Interpolation of sample data during mixing + (c) Dolby Surround Sound +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stddef.h> +#ifdef HAVE_MEMORY_H +#include <memory.h> +#endif +#include <string.h> + +#include "mikmod_internals.h" + +/* + Constant definitions + ==================== + + BITSHIFT + Controls the maximum volume of the sound output. All data is shifted + right by BITSHIFT after being mixed. Higher values result in quieter + sound and less chance of distortion. + + REVERBERATION + Controls the duration of the reverb. Larger values represent a shorter + reverb loop. Smaller values extend the reverb but can result in more of + an echo-ish sound. + +*/ + +#define BITSHIFT 9 +#define REVERBERATION 110000L + +#define FRACBITS 11 +#define FRACMASK ((1L<<FRACBITS)-1L) + +#define TICKLSIZE 8192 +#define TICKWSIZE (TICKLSIZE<<1) +#define TICKBSIZE (TICKWSIZE<<1) + +#define CLICK_SHIFT 6 +#define CLICK_BUFFER (1L<<CLICK_SHIFT) + +#ifndef MIN +#define MIN(a,b) (((a)<(b)) ? (a) : (b)) +#endif + +typedef struct VINFO { + UBYTE kick; /* =1 -> sample has to be restarted */ + UBYTE active; /* =1 -> sample is playing */ + UWORD flags; /* 16/8 bits looping/one-shot */ + SWORD handle; /* identifies the sample */ + ULONG start; /* start index */ + ULONG size; /* samplesize */ + ULONG reppos; /* loop start */ + ULONG repend; /* loop end */ + ULONG frq; /* current frequency */ + int vol; /* current volume */ + int pan; /* current panning position */ + + int rampvol; + int lvolsel,rvolsel; /* Volume factor in range 0-255 */ + int oldlvol,oldrvol; + + SLONGLONG current; /* current index in the sample */ + SLONGLONG increment; /* increment value */ +} VINFO; + +static SWORD **Samples; +static VINFO *vinf=NULL,*vnf; +static long tickleft,samplesthatfit,vc_memory=0; +static int vc_softchn; +static SLONGLONG idxsize,idxlpos,idxlend; +static SLONG *vc_tickbuf=NULL; +static UWORD vc_mode; + +/* Reverb control variables */ + +static int RVc1, RVc2, RVc3, RVc4, RVc5, RVc6, RVc7, RVc8; +static ULONG RVRindex; + +/* For Mono or Left Channel */ +static SLONG *RVbufL1=NULL,*RVbufL2=NULL,*RVbufL3=NULL,*RVbufL4=NULL, + *RVbufL5=NULL,*RVbufL6=NULL,*RVbufL7=NULL,*RVbufL8=NULL; + +/* For Stereo only (Right Channel) */ +static SLONG *RVbufR1=NULL,*RVbufR2=NULL,*RVbufR3=NULL,*RVbufR4=NULL, + *RVbufR5=NULL,*RVbufR6=NULL,*RVbufR7=NULL,*RVbufR8=NULL; + +#ifdef NATIVE_64BIT_INT +#define NATIVE SLONGLONG +#else +#define NATIVE SLONG +#endif + +/*========== 32 bit sample mixers - only for 32 bit platforms */ +#ifndef NATIVE_64BIT_INT + +static SLONG Mix32MonoNormal(const SWORD* srce,SLONG* dest,SLONG index,SLONG increment,SLONG todo) +{ + SWORD sample; + SLONG lvolsel = vnf->lvolsel; + + while(todo--) { + sample = srce[index >> FRACBITS]; + index += increment; + + *dest++ += lvolsel * sample; + } + return index; +} + +static SLONG Mix32StereoNormal(const SWORD* srce,SLONG* dest,SLONG index,SLONG increment,SLONG todo) +{ + SWORD sample; + SLONG lvolsel = vnf->lvolsel; + SLONG rvolsel = vnf->rvolsel; + + while(todo--) { + sample=srce[index >> FRACBITS]; + index += increment; + + *dest++ += lvolsel * sample; + *dest++ += rvolsel * sample; + } + return index; +} + +static SLONG Mix32SurroundNormal(const SWORD* srce,SLONG* dest,SLONG index,SLONG increment,SLONG todo) +{ + SWORD sample; + SLONG lvolsel = vnf->lvolsel; + SLONG rvolsel = vnf->rvolsel; + + if (lvolsel>=rvolsel) { + while(todo--) { + sample = srce[index >> FRACBITS]; + index += increment; + + *dest++ += lvolsel*sample; + *dest++ -= lvolsel*sample; + } + } else { + while(todo--) { + sample = srce[index >> FRACBITS]; + index += increment; + + *dest++ -= rvolsel*sample; + *dest++ += rvolsel*sample; + } + } + return index; +} + +static SLONG Mix32MonoInterp(const SWORD* srce,SLONG* dest,SLONG index,SLONG increment,SLONG todo) +{ + SLONG sample; + SLONG lvolsel = vnf->lvolsel; + SLONG rampvol = vnf->rampvol; + + if (rampvol) { + SLONG oldlvol = vnf->oldlvol - lvolsel; + while(todo--) { + sample=(SLONG)srce[index>>FRACBITS]+ + ((SLONG)(srce[(index>>FRACBITS)+1]-srce[index>>FRACBITS]) + *(index&FRACMASK)>>FRACBITS); + index += increment; + + *dest++ += ((lvolsel << CLICK_SHIFT) + oldlvol * rampvol) + * sample >> CLICK_SHIFT; + if (!--rampvol) + break; + } + vnf->rampvol = rampvol; + if (todo < 0) + return index; + } + + while(todo--) { + sample=(SLONG)srce[index>>FRACBITS]+ + ((SLONG)(srce[(index>>FRACBITS)+1]-srce[index>>FRACBITS]) + *(index&FRACMASK)>>FRACBITS); + index += increment; + + *dest++ += lvolsel * sample; + } + return index; +} + +static SLONG Mix32StereoInterp(const SWORD* srce,SLONG* dest,SLONG index,SLONG increment,SLONG todo) +{ + SLONG sample; + SLONG lvolsel = vnf->lvolsel; + SLONG rvolsel = vnf->rvolsel; + SLONG rampvol = vnf->rampvol; + + if (rampvol) { + SLONG oldlvol = vnf->oldlvol - lvolsel; + SLONG oldrvol = vnf->oldrvol - rvolsel; + while(todo--) { + sample=(SLONG)srce[index>>FRACBITS]+ + ((SLONG)(srce[(index>>FRACBITS)+1]-srce[index>>FRACBITS]) + *(index&FRACMASK)>>FRACBITS); + index += increment; + + *dest++ += ((lvolsel << CLICK_SHIFT) + oldlvol * rampvol) + * sample >> CLICK_SHIFT; + *dest++ += ((rvolsel << CLICK_SHIFT) + oldrvol * rampvol) + * sample >> CLICK_SHIFT; + if (!--rampvol) + break; + } + vnf->rampvol = rampvol; + if (todo < 0) + return index; + } + + while(todo--) { + sample=(SLONG)srce[index>>FRACBITS]+ + ((SLONG)(srce[(index>>FRACBITS)+1]-srce[index>>FRACBITS]) + *(index&FRACMASK)>>FRACBITS); + index += increment; + + *dest++ += lvolsel * sample; + *dest++ += rvolsel * sample; + } + return index; +} + +static SLONG Mix32SurroundInterp(const SWORD* srce,SLONG* dest,SLONG index,SLONG increment,SLONG todo) +{ + SLONG sample; + SLONG lvolsel = vnf->lvolsel; + SLONG rvolsel = vnf->rvolsel; + SLONG rampvol = vnf->rampvol; + SLONG oldvol, vol; + + if (lvolsel >= rvolsel) { + vol = lvolsel; + oldvol = vnf->oldlvol; + } else { + vol = rvolsel; + oldvol = vnf->oldrvol; + } + + if (rampvol) { + oldvol -= vol; + while(todo--) { + sample=(SLONG)srce[index>>FRACBITS]+ + ((SLONG)(srce[(index>>FRACBITS)+1]-srce[index>>FRACBITS]) + *(index&FRACMASK)>>FRACBITS); + index += increment; + + sample=((vol << CLICK_SHIFT) + oldvol * rampvol) + * sample >> CLICK_SHIFT; + *dest++ += sample; + *dest++ -= sample; + + if (!--rampvol) + break; + } + vnf->rampvol = rampvol; + if (todo < 0) + return index; + } + + while(todo--) { + sample=(SLONG)srce[index>>FRACBITS]+ + ((SLONG)(srce[(index>>FRACBITS)+1]-srce[index>>FRACBITS]) + *(index&FRACMASK)>>FRACBITS); + index += increment; + + *dest++ += vol*sample; + *dest++ -= vol*sample; + } + return index; +} +#endif + +/*========== 64 bit sample mixers - all platforms */ + +static SLONGLONG MixMonoNormal(const SWORD* srce,SLONG* dest,SLONGLONG index,SLONGLONG increment,SLONG todo) +{ + SWORD sample; + SLONG lvolsel = vnf->lvolsel; + + while(todo--) { + sample = srce[index >> FRACBITS]; + index += increment; + + *dest++ += lvolsel * sample; + } + return index; +} + +static SLONGLONG MixStereoNormal(const SWORD* srce,SLONG* dest,SLONGLONG index,SLONGLONG increment,SLONG todo) +{ + SWORD sample; + SLONG lvolsel = vnf->lvolsel; + SLONG rvolsel = vnf->rvolsel; + + while(todo--) { + sample=srce[index >> FRACBITS]; + index += increment; + + *dest++ += lvolsel * sample; + *dest++ += rvolsel * sample; + } + return index; +} + +static SLONGLONG MixSurroundNormal(const SWORD* srce,SLONG* dest,SLONGLONG index,SLONGLONG increment,SLONG todo) +{ + SWORD sample; + SLONG lvolsel = vnf->lvolsel; + SLONG rvolsel = vnf->rvolsel; + + if(vnf->lvolsel>=vnf->rvolsel) { + while(todo--) { + sample = srce[index >> FRACBITS]; + index += increment; + + *dest++ += lvolsel*sample; + *dest++ -= lvolsel*sample; + } + } else { + while(todo--) { + sample = srce[index >> FRACBITS]; + index += increment; + + *dest++ -= rvolsel*sample; + *dest++ += rvolsel*sample; + } + } + return index; +} + +static SLONGLONG MixMonoInterp(const SWORD* srce,SLONG* dest,SLONGLONG index,SLONGLONG increment,SLONG todo) +{ + SLONG sample; + SLONG lvolsel = vnf->lvolsel; + SLONG rampvol = vnf->rampvol; + + if (rampvol) { + SLONG oldlvol = vnf->oldlvol - lvolsel; + while(todo--) { + sample=(SLONG)srce[index>>FRACBITS]+ + ((SLONG)(srce[(index>>FRACBITS)+1]-srce[index>>FRACBITS]) + *(index&FRACMASK)>>FRACBITS); + index += increment; + + *dest++ += ((lvolsel << CLICK_SHIFT) + oldlvol * rampvol) + * sample >> CLICK_SHIFT; + if (!--rampvol) + break; + } + vnf->rampvol = rampvol; + if (todo < 0) + return index; + } + + while(todo--) { + sample=(SLONG)srce[index>>FRACBITS]+ + ((SLONG)(srce[(index>>FRACBITS)+1]-srce[index>>FRACBITS]) + *(index&FRACMASK)>>FRACBITS); + index += increment; + + *dest++ += lvolsel * sample; + } + return index; +} + +static SLONGLONG MixStereoInterp(const SWORD* srce,SLONG* dest,SLONGLONG index,SLONGLONG increment,SLONG todo) +{ + SLONG sample; + SLONG lvolsel = vnf->lvolsel; + SLONG rvolsel = vnf->rvolsel; + SLONG rampvol = vnf->rampvol; + + if (rampvol) { + SLONG oldlvol = vnf->oldlvol - lvolsel; + SLONG oldrvol = vnf->oldrvol - rvolsel; + while(todo--) { + sample=(SLONG)srce[index>>FRACBITS]+ + ((SLONG)(srce[(index>>FRACBITS)+1]-srce[index>>FRACBITS]) + *(index&FRACMASK)>>FRACBITS); + index += increment; + + *dest++ +=((lvolsel << CLICK_SHIFT) + oldlvol * rampvol) + * sample >> CLICK_SHIFT; + *dest++ +=((rvolsel << CLICK_SHIFT) + oldrvol * rampvol) + * sample >> CLICK_SHIFT; + if (!--rampvol) + break; + } + vnf->rampvol = rampvol; + if (todo < 0) + return index; + } + + while(todo--) { + sample=(SLONG)srce[index>>FRACBITS]+ + ((SLONG)(srce[(index>>FRACBITS)+1]-srce[index>>FRACBITS]) + *(index&FRACMASK)>>FRACBITS); + index += increment; + + *dest++ += lvolsel * sample; + *dest++ += rvolsel * sample; + } + return index; +} + +static SLONGLONG MixSurroundInterp(const SWORD* srce,SLONG* dest,SLONGLONG index,SLONGLONG increment,SLONG todo) +{ + SLONG sample; + SLONG lvolsel = vnf->lvolsel; + SLONG rvolsel = vnf->rvolsel; + SLONG rampvol = vnf->rampvol; + SLONG oldvol, vol; + + if (lvolsel >= rvolsel) { + vol = lvolsel; + oldvol = vnf->oldlvol; + } else { + vol = rvolsel; + oldvol = vnf->oldrvol; + } + + if (rampvol) { + oldvol -= vol; + while(todo--) { + sample=(SLONG)srce[index>>FRACBITS]+ + ((SLONG)(srce[(index>>FRACBITS)+1]-srce[index>>FRACBITS]) + *(index&FRACMASK)>>FRACBITS); + index += increment; + + sample=((vol << CLICK_SHIFT) + oldvol * rampvol) + * sample >> CLICK_SHIFT; + *dest++ += sample; + *dest++ -= sample; + if (!--rampvol) + break; + } + vnf->rampvol = rampvol; + if (todo < 0) + return index; + } + + while(todo--) { + sample=(SLONG)srce[index>>FRACBITS]+ + ((SLONG)(srce[(index>>FRACBITS)+1]-srce[index>>FRACBITS]) + *(index&FRACMASK)>>FRACBITS); + index += increment; + + *dest++ += vol*sample; + *dest++ -= vol*sample; + } + return index; +} + +static void (*MixReverb)(SLONG* srce,NATIVE count); + +/* Reverb macros */ +#define COMPUTE_LOC(n) loc##n = RVRindex % RVc##n +#define COMPUTE_LECHO(n) RVbufL##n [loc##n ]=speedup+((ReverbPct*RVbufL##n [loc##n ])>>7) +#define COMPUTE_RECHO(n) RVbufR##n [loc##n ]=speedup+((ReverbPct*RVbufR##n [loc##n ])>>7) + +static void MixReverb_Normal(SLONG* srce,NATIVE count) +{ + unsigned int speedup; + int ReverbPct; + unsigned int loc1,loc2,loc3,loc4; + unsigned int loc5,loc6,loc7,loc8; + + ReverbPct=58+(md_reverb<<2); + + COMPUTE_LOC(1); COMPUTE_LOC(2); COMPUTE_LOC(3); COMPUTE_LOC(4); + COMPUTE_LOC(5); COMPUTE_LOC(6); COMPUTE_LOC(7); COMPUTE_LOC(8); + + while(count--) { + /* Compute the left channel echo buffers */ + speedup = *srce >> 3; + + COMPUTE_LECHO(1); COMPUTE_LECHO(2); COMPUTE_LECHO(3); COMPUTE_LECHO(4); + COMPUTE_LECHO(5); COMPUTE_LECHO(6); COMPUTE_LECHO(7); COMPUTE_LECHO(8); + + /* Prepare to compute actual finalized data */ + RVRindex++; + + COMPUTE_LOC(1); COMPUTE_LOC(2); COMPUTE_LOC(3); COMPUTE_LOC(4); + COMPUTE_LOC(5); COMPUTE_LOC(6); COMPUTE_LOC(7); COMPUTE_LOC(8); + + /* left channel */ + *srce++ +=RVbufL1[loc1]-RVbufL2[loc2]+RVbufL3[loc3]-RVbufL4[loc4]+ + RVbufL5[loc5]-RVbufL6[loc6]+RVbufL7[loc7]-RVbufL8[loc8]; + } +} + +static void MixReverb_Stereo(SLONG* srce,NATIVE count) +{ + unsigned int speedup; + int ReverbPct; + unsigned int loc1, loc2, loc3, loc4; + unsigned int loc5, loc6, loc7, loc8; + + ReverbPct = 92+(md_reverb<<1); + + COMPUTE_LOC(1); COMPUTE_LOC(2); COMPUTE_LOC(3); COMPUTE_LOC(4); + COMPUTE_LOC(5); COMPUTE_LOC(6); COMPUTE_LOC(7); COMPUTE_LOC(8); + + while(count--) { + /* Compute the left channel echo buffers */ + speedup = *srce >> 3; + + COMPUTE_LECHO(1); COMPUTE_LECHO(2); COMPUTE_LECHO(3); COMPUTE_LECHO(4); + COMPUTE_LECHO(5); COMPUTE_LECHO(6); COMPUTE_LECHO(7); COMPUTE_LECHO(8); + + /* Compute the right channel echo buffers */ + speedup = srce[1] >> 3; + + COMPUTE_RECHO(1); COMPUTE_RECHO(2); COMPUTE_RECHO(3); COMPUTE_RECHO(4); + COMPUTE_RECHO(5); COMPUTE_RECHO(6); COMPUTE_RECHO(7); COMPUTE_RECHO(8); + + /* Prepare to compute actual finalized data */ + RVRindex++; + + COMPUTE_LOC(1); COMPUTE_LOC(2); COMPUTE_LOC(3); COMPUTE_LOC(4); + COMPUTE_LOC(5); COMPUTE_LOC(6); COMPUTE_LOC(7); COMPUTE_LOC(8); + + /* left channel then right channel */ + *srce++ +=RVbufL1[loc1]-RVbufL2[loc2]+RVbufL3[loc3]-RVbufL4[loc4]+ + RVbufL5[loc5]-RVbufL6[loc6]+RVbufL7[loc7]-RVbufL8[loc8]; + + *srce++ +=RVbufR1[loc1]-RVbufR2[loc2]+RVbufR3[loc3]-RVbufR4[loc4]+ + RVbufR5[loc5]-RVbufR6[loc6]+RVbufR7[loc7]-RVbufR8[loc8]; + } +} + +/* Mixing macros */ +#define EXTRACT_SAMPLE(var,size) var=*srce++>>(BITSHIFT+16-size) +#define CHECK_SAMPLE(var,bound) var=(var>=bound)?bound-1:(var<-bound)?-bound:var +#define PUT_SAMPLE(var) *dste++=var + +static void Mix32To16(SWORD* dste,const SLONG* srce,NATIVE count) +{ + SLONG x1,x2,x3,x4; + int remain; + + remain=count&3; + for(count>>=2;count;count--) { + EXTRACT_SAMPLE(x1,16); EXTRACT_SAMPLE(x2,16); + EXTRACT_SAMPLE(x3,16); EXTRACT_SAMPLE(x4,16); + + CHECK_SAMPLE(x1,32768); CHECK_SAMPLE(x2,32768); + CHECK_SAMPLE(x3,32768); CHECK_SAMPLE(x4,32768); + + PUT_SAMPLE(x1); PUT_SAMPLE(x2); PUT_SAMPLE(x3); PUT_SAMPLE(x4); + } + while(remain--) { + EXTRACT_SAMPLE(x1,16); + CHECK_SAMPLE(x1,32768); + PUT_SAMPLE(x1); + } +} + +static void Mix32To8(SBYTE* dste,const SLONG* srce,NATIVE count) +{ + SWORD x1,x2,x3,x4; + int remain; + + remain=count&3; + for(count>>=2;count;count--) { + EXTRACT_SAMPLE(x1,8); EXTRACT_SAMPLE(x2,8); + EXTRACT_SAMPLE(x3,8); EXTRACT_SAMPLE(x4,8); + + CHECK_SAMPLE(x1,128); CHECK_SAMPLE(x2,128); + CHECK_SAMPLE(x3,128); CHECK_SAMPLE(x4,128); + + PUT_SAMPLE(x1+128); PUT_SAMPLE(x2+128); + PUT_SAMPLE(x3+128); PUT_SAMPLE(x4+128); + } + while(remain--) { + EXTRACT_SAMPLE(x1,8); + CHECK_SAMPLE(x1,128); + PUT_SAMPLE(x1+128); + } +} + +static void AddChannel(SLONG* ptr,NATIVE todo) +{ + SLONGLONG end,done; + SWORD *s; + + if(!(s=Samples[vnf->handle])) { + vnf->current = vnf->active = 0; + return; + } + + /* update the 'current' index so the sample loops, or stops playing if it + reached the end of the sample */ + while(todo>0) { + SLONGLONG endpos; + + if(vnf->flags & SF_REVERSE) { + /* The sample is playing in reverse */ + if((vnf->flags&SF_LOOP)&&(vnf->current<idxlpos)) { + /* the sample is looping and has reached the loopstart index */ + if(vnf->flags & SF_BIDI) { + /* sample is doing bidirectional loops, so 'bounce' the + current index against the idxlpos */ + vnf->current = idxlpos+(idxlpos-vnf->current); + vnf->flags &= ~SF_REVERSE; + vnf->increment = -vnf->increment; + } else + /* normal backwards looping, so set the current position to + loopend index */ + vnf->current=idxlend-(idxlpos-vnf->current); + } else { + /* the sample is not looping, so check if it reached index 0 */ + if(vnf->current < 0) { + /* playing index reached 0, so stop playing this sample */ + vnf->current = vnf->active = 0; + break; + } + } + } else { + /* The sample is playing forward */ + if((vnf->flags & SF_LOOP) && + (vnf->current >= idxlend)) { + /* the sample is looping, check the loopend index */ + if(vnf->flags & SF_BIDI) { + /* sample is doing bidirectional loops, so 'bounce' the + current index against the idxlend */ + vnf->flags |= SF_REVERSE; + vnf->increment = -vnf->increment; + vnf->current = idxlend-(vnf->current-idxlend); + } else + /* normal backwards looping, so set the current position + to loopend index */ + vnf->current=idxlpos+(vnf->current-idxlend); + } else { + /* sample is not looping, so check if it reached the last + position */ + if(vnf->current >= idxsize) { + /* yes, so stop playing this sample */ + vnf->current = vnf->active = 0; + break; + } + } + } + + end=(vnf->flags&SF_REVERSE)?(vnf->flags&SF_LOOP)?idxlpos:0: + (vnf->flags&SF_LOOP)?idxlend:idxsize; + + /* if the sample is not blocked... */ + if((end==vnf->current)||(!vnf->increment)) + done=0; + else { + done=MIN((end-vnf->current)/vnf->increment+1,todo); + if(done<0) done=0; + } + + if(!done) { + vnf->active = 0; + break; + } + + endpos=vnf->current+done*vnf->increment; + + if(vnf->vol) { +#ifndef NATIVE_64BIT_INT + /* use the 32 bit mixers as often as we can (they're much faster) */ + if((vnf->current<0x7fffffff)&&(endpos<0x7fffffff)) { + if((md_mode & DMODE_INTERP)) { + if(vc_mode & DMODE_STEREO) { + if((vnf->pan==PAN_SURROUND)&&(md_mode&DMODE_SURROUND)) + vnf->current=Mix32SurroundInterp + (s,ptr,vnf->current,vnf->increment,done); + else + vnf->current=Mix32StereoInterp + (s,ptr,vnf->current,vnf->increment,done); + } else + vnf->current=Mix32MonoInterp + (s,ptr,vnf->current,vnf->increment,done); + } else if(vc_mode & DMODE_STEREO) { + if((vnf->pan==PAN_SURROUND)&&(md_mode&DMODE_SURROUND)) + vnf->current=Mix32SurroundNormal + (s,ptr,vnf->current,vnf->increment,done); + else + vnf->current=Mix32StereoNormal + (s,ptr,vnf->current,vnf->increment,done); + } else + vnf->current=Mix32MonoNormal + (s,ptr,vnf->current,vnf->increment,done); + } else +#endif + { + if((md_mode & DMODE_INTERP)) { + if(vc_mode & DMODE_STEREO) { + if((vnf->pan==PAN_SURROUND)&&(md_mode&DMODE_SURROUND)) + vnf->current=MixSurroundInterp + (s,ptr,vnf->current,vnf->increment,done); + else + vnf->current=MixStereoInterp + (s,ptr,vnf->current,vnf->increment,done); + } else + vnf->current=MixMonoInterp + (s,ptr,vnf->current,vnf->increment,done); + } else if(vc_mode & DMODE_STEREO) { + if((vnf->pan==PAN_SURROUND)&&(md_mode&DMODE_SURROUND)) + vnf->current=MixSurroundNormal + (s,ptr,vnf->current,vnf->increment,done); + else + vnf->current=MixStereoNormal + (s,ptr,vnf->current,vnf->increment,done); + } else + vnf->current=MixMonoNormal + (s,ptr,vnf->current,vnf->increment,done); + } + } else + /* update sample position */ + vnf->current=endpos; + + todo-=done; + ptr +=(vc_mode & DMODE_STEREO)?(done<<1):done; + } +} + +#define _IN_VIRTCH_ +#include "virtch_common.c" +#undef _IN_VIRTCH_ + +void VC1_WriteSamples(SBYTE* buf,ULONG todo) +{ + int left,portion=0,count; + SBYTE *buffer; + int t, pan, vol; + + while(todo) { + if(!tickleft) { + if(vc_mode & DMODE_SOFT_MUSIC) md_player(); + tickleft=(md_mixfreq*125L)/(md_bpm*50L); + } + left = MIN(tickleft, (long)todo); + buffer = buf; + tickleft -= left; + todo -= left; + buf += samples2bytes(left); + + while(left) { + portion = MIN(left, samplesthatfit); + count = (vc_mode & DMODE_STEREO)?(portion<<1):portion; + memset(vc_tickbuf, 0, count<<2); + for(t=0;t<vc_softchn;t++) { + vnf = &vinf[t]; + + if(vnf->kick) { + vnf->current=((SLONGLONG)vnf->start)<<FRACBITS; + vnf->kick =0; + vnf->active =1; + } + + if(!vnf->frq) vnf->active = 0; + + if(vnf->active) { + vnf->increment=((SLONGLONG)(vnf->frq<<FRACBITS))/md_mixfreq; + if(vnf->flags&SF_REVERSE) vnf->increment=-vnf->increment; + vol = vnf->vol; pan = vnf->pan; + + vnf->oldlvol=vnf->lvolsel;vnf->oldrvol=vnf->rvolsel; + if(vc_mode & DMODE_STEREO) { + if(pan != PAN_SURROUND) { + vnf->lvolsel=(vol*(PAN_RIGHT-pan))>>8; + vnf->rvolsel=(vol*pan)>>8; + } else + vnf->lvolsel=vnf->rvolsel=vol/2; + } else + vnf->lvolsel=vol; + + idxsize = (vnf->size)? ((SLONGLONG)vnf->size << FRACBITS)-1 : 0; + idxlend = (vnf->repend)? ((SLONGLONG)vnf->repend << FRACBITS)-1 : 0; + idxlpos = (SLONGLONG)vnf->reppos << FRACBITS; + AddChannel(vc_tickbuf, portion); + } + } + + if(md_reverb) { + if(md_reverb>15) md_reverb=15; + MixReverb(vc_tickbuf, portion); + } + + if(vc_mode & DMODE_16BITS) + Mix32To16((SWORD*) buffer, vc_tickbuf, count); + else + Mix32To8((SBYTE*) buffer, vc_tickbuf, count); + + buffer += samples2bytes(portion); + left -= portion; + } + } +} + +BOOL VC1_Init(void) +{ + VC_SetupPointers(); + + if (md_mode&DMODE_HQMIXER) + return VC2_Init(); + + if(!(Samples=(SWORD**)MikMod_calloc(MAXSAMPLEHANDLES,sizeof(SWORD*)))) { + _mm_errno = MMERR_INITIALIZING_MIXER; + return 1; + } + if(!vc_tickbuf) + if(!(vc_tickbuf=(SLONG*)MikMod_malloc((TICKLSIZE+32)*sizeof(SLONG)))) { + _mm_errno = MMERR_INITIALIZING_MIXER; + return 1; + } + + MixReverb=(md_mode&DMODE_STEREO)?MixReverb_Stereo:MixReverb_Normal; + vc_mode = md_mode; + return 0; +} + +BOOL VC1_PlayStart(void) +{ + samplesthatfit=TICKLSIZE; + if(vc_mode & DMODE_STEREO) samplesthatfit >>= 1; + tickleft = 0; + + RVc1 = (5000L * md_mixfreq) / REVERBERATION; + RVc2 = (5078L * md_mixfreq) / REVERBERATION; + RVc3 = (5313L * md_mixfreq) / REVERBERATION; + RVc4 = (5703L * md_mixfreq) / REVERBERATION; + RVc5 = (6250L * md_mixfreq) / REVERBERATION; + RVc6 = (6953L * md_mixfreq) / REVERBERATION; + RVc7 = (7813L * md_mixfreq) / REVERBERATION; + RVc8 = (8828L * md_mixfreq) / REVERBERATION; + + if(!(RVbufL1=(SLONG*)MikMod_calloc((RVc1+1),sizeof(SLONG)))) return 1; + if(!(RVbufL2=(SLONG*)MikMod_calloc((RVc2+1),sizeof(SLONG)))) return 1; + if(!(RVbufL3=(SLONG*)MikMod_calloc((RVc3+1),sizeof(SLONG)))) return 1; + if(!(RVbufL4=(SLONG*)MikMod_calloc((RVc4+1),sizeof(SLONG)))) return 1; + if(!(RVbufL5=(SLONG*)MikMod_calloc((RVc5+1),sizeof(SLONG)))) return 1; + if(!(RVbufL6=(SLONG*)MikMod_calloc((RVc6+1),sizeof(SLONG)))) return 1; + if(!(RVbufL7=(SLONG*)MikMod_calloc((RVc7+1),sizeof(SLONG)))) return 1; + if(!(RVbufL8=(SLONG*)MikMod_calloc((RVc8+1),sizeof(SLONG)))) return 1; + + if(!(RVbufR1=(SLONG*)MikMod_calloc((RVc1+1),sizeof(SLONG)))) return 1; + if(!(RVbufR2=(SLONG*)MikMod_calloc((RVc2+1),sizeof(SLONG)))) return 1; + if(!(RVbufR3=(SLONG*)MikMod_calloc((RVc3+1),sizeof(SLONG)))) return 1; + if(!(RVbufR4=(SLONG*)MikMod_calloc((RVc4+1),sizeof(SLONG)))) return 1; + if(!(RVbufR5=(SLONG*)MikMod_calloc((RVc5+1),sizeof(SLONG)))) return 1; + if(!(RVbufR6=(SLONG*)MikMod_calloc((RVc6+1),sizeof(SLONG)))) return 1; + if(!(RVbufR7=(SLONG*)MikMod_calloc((RVc7+1),sizeof(SLONG)))) return 1; + if(!(RVbufR8=(SLONG*)MikMod_calloc((RVc8+1),sizeof(SLONG)))) return 1; + + RVRindex = 0; + return 0; +} + +void VC1_PlayStop(void) +{ + if(RVbufL1) MikMod_free(RVbufL1); + if(RVbufL2) MikMod_free(RVbufL2); + if(RVbufL3) MikMod_free(RVbufL3); + if(RVbufL4) MikMod_free(RVbufL4); + if(RVbufL5) MikMod_free(RVbufL5); + if(RVbufL6) MikMod_free(RVbufL6); + if(RVbufL7) MikMod_free(RVbufL7); + if(RVbufL8) MikMod_free(RVbufL8); + RVbufL1=RVbufL2=RVbufL3=RVbufL4=RVbufL5=RVbufL6=RVbufL7=RVbufL8=NULL; + if(RVbufR1) MikMod_free(RVbufR1); + if(RVbufR2) MikMod_free(RVbufR2); + if(RVbufR3) MikMod_free(RVbufR3); + if(RVbufR4) MikMod_free(RVbufR4); + if(RVbufR5) MikMod_free(RVbufR5); + if(RVbufR6) MikMod_free(RVbufR6); + if(RVbufR7) MikMod_free(RVbufR7); + if(RVbufR8) MikMod_free(RVbufR8); + RVbufR1=RVbufR2=RVbufR3=RVbufR4=RVbufR5=RVbufR6=RVbufR7=RVbufR8=NULL; +} + +BOOL VC1_SetNumVoices(void) +{ + int t; + + if(!(vc_softchn=md_softchn)) return 0; + + if(vinf) MikMod_free(vinf); + if(!(vinf= MikMod_calloc(sizeof(VINFO),vc_softchn))) return 1; + + for(t=0;t<vc_softchn;t++) { + vinf[t].frq=10000; + vinf[t].pan=(t&1)?PAN_LEFT:PAN_RIGHT; + } + + return 0; +} + +/* ex:set ts=4: */ diff --git a/src/libs/mikmod/virtch2.c b/src/libs/mikmod/virtch2.c new file mode 100644 index 0000000..231f1a6 --- /dev/null +++ b/src/libs/mikmod/virtch2.c @@ -0,0 +1,887 @@ +/* MikMod sound library + (c) 1998, 1999, 2000 Miodrag Vallat and others - see file AUTHORS for + complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + High-quality sample mixing routines, using a 32 bits mixing buffer, + interpolation, and sample smoothing to improve sound quality and remove + clicks. + +==============================================================================*/ + +/* + + Future Additions: + Low-Pass filter to remove annoying staticy buzz. + +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_MEMORY_H +#include <memory.h> +#endif +#include <string.h> + +#include "mikmod_internals.h" + +/* + Constant Definitions + ==================== + + MAXVOL_FACTOR (was BITSHIFT in virtch.c) + Controls the maximum volume of the output data. All mixed data is + divided by this number after mixing, so larger numbers result in + quieter mixing. Smaller numbers will increase the likeliness of + distortion on loud modules. + + REVERBERATION + Larger numbers result in shorter reverb duration. Longer reverb + durations can cause unwanted static and make the reverb sound more + like a crappy echo. + + SAMPLING_SHIFT + Specified the shift multiplier which controls by how much the mixing + rate is multiplied while mixing. Higher values can improve quality by + smoothing the sound and reducing pops and clicks. Note, this is a shift + value, so a value of 2 becomes a mixing-rate multiplier of 4, and a + value of 3 = 8, etc. + + FRACBITS + The number of bits per integer devoted to the fractional part of the + number. Generally, this number should not be changed for any reason. + + !!! IMPORTANT !!! All values below MUST ALWAYS be greater than 0 + +*/ + +#define MAXVOL_FACTOR (1<<9) +#define REVERBERATION 11000L + +#define SAMPLING_SHIFT 2 +#define SAMPLING_FACTOR (1UL<<SAMPLING_SHIFT) + +#define FRACBITS 28 +#define FRACMASK ((1UL<<FRACBITS)-1UL) + +#define TICKLSIZE 8192 +#define TICKWSIZE (TICKLSIZE * 2) +#define TICKBSIZE (TICKWSIZE * 2) + +#define CLICK_SHIFT_BASE 6 +#define CLICK_SHIFT (CLICK_SHIFT_BASE + SAMPLING_SHIFT) +#define CLICK_BUFFER (1L << CLICK_SHIFT) + +#ifndef MIN +#define MIN(a,b) (((a)<(b)) ? (a) : (b)) +#endif + +typedef struct VINFO { + UBYTE kick; /* =1 -> sample has to be restarted */ + UBYTE active; /* =1 -> sample is playing */ + UWORD flags; /* 16/8 bits looping/one-shot */ + SWORD handle; /* identifies the sample */ + ULONG start; /* start index */ + ULONG size; /* samplesize */ + ULONG reppos; /* loop start */ + ULONG repend; /* loop end */ + ULONG frq; /* current frequency */ + int vol; /* current volume */ + int pan; /* current panning position */ + + int click; + int rampvol; + SLONG lastvalL,lastvalR; + int lvolsel,rvolsel; /* Volume factor in range 0-255 */ + int oldlvol,oldrvol; + + SLONGLONG current; /* current index in the sample */ + SLONGLONG increment; /* increment value */ +} VINFO; + +static SWORD **Samples; +static VINFO *vinf=NULL,*vnf; +static long tickleft,samplesthatfit,vc_memory=0; +static int vc_softchn; +static SLONGLONG idxsize,idxlpos,idxlend; +static SLONG *vc_tickbuf=NULL; +static UWORD vc_mode; + +/* Reverb control variables */ + +static int RVc1, RVc2, RVc3, RVc4, RVc5, RVc6, RVc7, RVc8; +static ULONG RVRindex; + +/* For Mono or Left Channel */ +static SLONG *RVbufL1=NULL,*RVbufL2=NULL,*RVbufL3=NULL,*RVbufL4=NULL, + *RVbufL5=NULL,*RVbufL6=NULL,*RVbufL7=NULL,*RVbufL8=NULL; + +/* For Stereo only (Right Channel) */ +static SLONG *RVbufR1=NULL,*RVbufR2=NULL,*RVbufR3=NULL,*RVbufR4=NULL, + *RVbufR5=NULL,*RVbufR6=NULL,*RVbufR7=NULL,*RVbufR8=NULL; + +#ifdef NATIVE_64BIT_INT +#define NATIVE SLONGLONG +#else +#define NATIVE SLONG +#endif + +/*========== 32 bit sample mixers - only for 32 bit platforms */ +#ifndef NATIVE_64BIT_INT + +static SLONG Mix32MonoNormal(const SWORD* srce,SLONG* dest,SLONG index,SLONG increment,SLONG todo) +{ + SWORD sample=0; + SLONG i,f; + + while(todo--) { + i=index>>FRACBITS,f=index&FRACMASK; + sample=(((SLONG)(srce[i]*(FRACMASK+1L-f)) + + ((SLONG)srce[i+1]*f)) >> FRACBITS); + index+=increment; + + if(vnf->rampvol) { + *dest++ += (long)( + ( ( (SLONG)(vnf->oldlvol*vnf->rampvol) + + (vnf->lvolsel*(CLICK_BUFFER-vnf->rampvol)) ) * + (SLONG)sample ) >> CLICK_SHIFT ); + vnf->rampvol--; + } else + if(vnf->click) { + *dest++ += (long)( + ( ( ((SLONG)vnf->lvolsel*(CLICK_BUFFER-vnf->click)) * + (SLONG)sample ) + + (vnf->lastvalL*vnf->click) ) >> CLICK_SHIFT ); + vnf->click--; + } else + *dest++ +=vnf->lvolsel*sample; + } + vnf->lastvalL=vnf->lvolsel * sample; + + return index; +} + +static SLONG Mix32StereoNormal(const SWORD* srce,SLONG* dest,SLONG index,SLONG increment,ULONG todo) +{ + SWORD sample=0; + SLONG i,f; + + while(todo--) { + i=index>>FRACBITS,f=index&FRACMASK; + sample=((((SLONG)srce[i]*(FRACMASK+1L-f)) + + ((SLONG)srce[i+1] * f)) >> FRACBITS); + index += increment; + + if(vnf->rampvol) { + *dest++ += (long)( + ( ( ((SLONG)vnf->oldlvol*vnf->rampvol) + + (vnf->lvolsel*(CLICK_BUFFER-vnf->rampvol)) + ) * (SLONG)sample ) >> CLICK_SHIFT ); + *dest++ += (long)( + ( ( ((SLONG)vnf->oldrvol*vnf->rampvol) + + (vnf->rvolsel*(CLICK_BUFFER-vnf->rampvol)) + ) * (SLONG)sample ) >> CLICK_SHIFT ); + vnf->rampvol--; + } else + if(vnf->click) { + *dest++ += (long)( + ( ( (SLONG)(vnf->lvolsel*(CLICK_BUFFER-vnf->click)) * + (SLONG)sample ) + (vnf->lastvalL * vnf->click) ) + >> CLICK_SHIFT ); + *dest++ += (long)( + ( ( ((SLONG)vnf->rvolsel*(CLICK_BUFFER-vnf->click)) * + (SLONG)sample ) + (vnf->lastvalR * vnf->click) ) + >> CLICK_SHIFT ); + vnf->click--; + } else { + *dest++ +=vnf->lvolsel*sample; + *dest++ +=vnf->rvolsel*sample; + } + } + vnf->lastvalL=vnf->lvolsel*sample; + vnf->lastvalR=vnf->rvolsel*sample; + + return index; +} + +static SLONG Mix32StereoSurround(const SWORD* srce,SLONG* dest,SLONG index,SLONG increment,ULONG todo) +{ + SWORD sample=0; + long whoop; + SLONG i, f; + + while(todo--) { + i=index>>FRACBITS,f=index&FRACMASK; + sample=((((SLONG)srce[i]*(FRACMASK+1L-f)) + + ((SLONG)srce[i+1]*f)) >> FRACBITS); + index+=increment; + + if(vnf->rampvol) { + whoop=(long)( + ( ( (SLONG)(vnf->oldlvol*vnf->rampvol) + + (vnf->lvolsel*(CLICK_BUFFER-vnf->rampvol)) ) * + (SLONG)sample) >> CLICK_SHIFT ); + *dest++ +=whoop; + *dest++ -=whoop; + vnf->rampvol--; + } else + if(vnf->click) { + whoop = (long)( + ( ( ((SLONG)vnf->lvolsel*(CLICK_BUFFER-vnf->click)) * + (SLONG)sample) + + (vnf->lastvalL * vnf->click) ) >> CLICK_SHIFT ); + *dest++ +=whoop; + *dest++ -=whoop; + vnf->click--; + } else { + *dest++ +=vnf->lvolsel*sample; + *dest++ -=vnf->lvolsel*sample; + } + } + vnf->lastvalL=vnf->lvolsel*sample; + vnf->lastvalR=vnf->lvolsel*sample; + + return index; +} +#endif + +/*========== 64 bit mixers */ + +static SLONGLONG MixMonoNormal(const SWORD* srce,SLONG* dest,SLONGLONG index,SLONGLONG increment,SLONG todo) +{ + SWORD sample=0; + SLONGLONG i,f; + + while(todo--) { + i=index>>FRACBITS,f=index&FRACMASK; + sample=(((SLONGLONG)(srce[i]*(FRACMASK+1L-f)) + + ((SLONGLONG)srce[i+1]*f)) >> FRACBITS); + index+=increment; + + if(vnf->rampvol) { + *dest++ += (long)( + ( ( (SLONGLONG)(vnf->oldlvol*vnf->rampvol) + + (vnf->lvolsel*(CLICK_BUFFER-vnf->rampvol)) ) * + (SLONGLONG)sample ) >> CLICK_SHIFT ); + vnf->rampvol--; + } else + if(vnf->click) { + *dest++ += (long)( + ( ( ((SLONGLONG)vnf->lvolsel*(CLICK_BUFFER-vnf->click)) * + (SLONGLONG)sample ) + + (vnf->lastvalL*vnf->click) ) >> CLICK_SHIFT ); + vnf->click--; + } else + *dest++ +=vnf->lvolsel*sample; + } + vnf->lastvalL=vnf->lvolsel * sample; + + return index; +} + +static SLONGLONG MixStereoNormal(const SWORD* srce,SLONG* dest,SLONGLONG index,SLONGLONG increment,ULONG todo) +{ + SWORD sample=0; + SLONGLONG i,f; + + while(todo--) { + i=index>>FRACBITS,f=index&FRACMASK; + sample=((((SLONGLONG)srce[i]*(FRACMASK+1L-f)) + + ((SLONGLONG)srce[i+1] * f)) >> FRACBITS); + index += increment; + + if(vnf->rampvol) { + *dest++ += (long)( + ( ( ((SLONGLONG)vnf->oldlvol*vnf->rampvol) + + (vnf->lvolsel*(CLICK_BUFFER-vnf->rampvol)) + ) * (SLONGLONG)sample ) >> CLICK_SHIFT ); + *dest++ += (long)( + ( ( ((SLONGLONG)vnf->oldrvol*vnf->rampvol) + + (vnf->rvolsel*(CLICK_BUFFER-vnf->rampvol)) + ) * (SLONGLONG)sample ) >> CLICK_SHIFT ); + vnf->rampvol--; + } else + if(vnf->click) { + *dest++ += (long)( + ( ( (SLONGLONG)(vnf->lvolsel*(CLICK_BUFFER-vnf->click)) * + (SLONGLONG)sample ) + (vnf->lastvalL * vnf->click) ) + >> CLICK_SHIFT ); + *dest++ += (long)( + ( ( ((SLONGLONG)vnf->rvolsel*(CLICK_BUFFER-vnf->click)) * + (SLONGLONG)sample ) + (vnf->lastvalR * vnf->click) ) + >> CLICK_SHIFT ); + vnf->click--; + } else { + *dest++ +=vnf->lvolsel*sample; + *dest++ +=vnf->rvolsel*sample; + } + } + vnf->lastvalL=vnf->lvolsel*sample; + vnf->lastvalR=vnf->rvolsel*sample; + + return index; +} + +static SLONGLONG MixStereoSurround(const SWORD* srce,SLONG* dest,SLONGLONG index,SLONGLONG increment,ULONG todo) +{ + SWORD sample=0; + long whoop; + SLONGLONG i, f; + + while(todo--) { + i=index>>FRACBITS,f=index&FRACMASK; + sample=((((SLONGLONG)srce[i]*(FRACMASK+1L-f)) + + ((SLONGLONG)srce[i+1]*f)) >> FRACBITS); + index+=increment; + + if(vnf->rampvol) { + whoop=(long)( + ( ( (SLONGLONG)(vnf->oldlvol*vnf->rampvol) + + (vnf->lvolsel*(CLICK_BUFFER-vnf->rampvol)) ) * + (SLONGLONG)sample) >> CLICK_SHIFT ); + *dest++ +=whoop; + *dest++ -=whoop; + vnf->rampvol--; + } else + if(vnf->click) { + whoop = (long)( + ( ( ((SLONGLONG)vnf->lvolsel*(CLICK_BUFFER-vnf->click)) * + (SLONGLONG)sample) + + (vnf->lastvalL * vnf->click) ) >> CLICK_SHIFT ); + *dest++ +=whoop; + *dest++ -=whoop; + vnf->click--; + } else { + *dest++ +=vnf->lvolsel*sample; + *dest++ -=vnf->lvolsel*sample; + } + } + vnf->lastvalL=vnf->lvolsel*sample; + vnf->lastvalR=vnf->lvolsel*sample; + + return index; +} + +static void(*Mix32to16)(SWORD* dste,const SLONG* srce,NATIVE count); +static void(*Mix32to8)(SBYTE* dste,const SLONG* srce,NATIVE count); +static void(*MixReverb)(SLONG* srce,NATIVE count); + +/* Reverb macros */ +#define COMPUTE_LOC(n) loc##n = RVRindex % RVc##n +#define COMPUTE_LECHO(n) RVbufL##n [loc##n ]=speedup+((ReverbPct*RVbufL##n [loc##n ])>>7) +#define COMPUTE_RECHO(n) RVbufR##n [loc##n ]=speedup+((ReverbPct*RVbufR##n [loc##n ])>>7) + +static void MixReverb_Normal(SLONG* srce,NATIVE count) +{ + NATIVE speedup; + int ReverbPct; + unsigned int loc1,loc2,loc3,loc4,loc5,loc6,loc7,loc8; + + ReverbPct=58+(md_reverb*4); + + COMPUTE_LOC(1); COMPUTE_LOC(2); COMPUTE_LOC(3); COMPUTE_LOC(4); + COMPUTE_LOC(5); COMPUTE_LOC(6); COMPUTE_LOC(7); COMPUTE_LOC(8); + + while(count--) { + /* Compute the left channel echo buffers */ + speedup = *srce >> 3; + + COMPUTE_LECHO(1); COMPUTE_LECHO(2); COMPUTE_LECHO(3); COMPUTE_LECHO(4); + COMPUTE_LECHO(5); COMPUTE_LECHO(6); COMPUTE_LECHO(7); COMPUTE_LECHO(8); + + /* Prepare to compute actual finalized data */ + RVRindex++; + + COMPUTE_LOC(1); COMPUTE_LOC(2); COMPUTE_LOC(3); COMPUTE_LOC(4); + COMPUTE_LOC(5); COMPUTE_LOC(6); COMPUTE_LOC(7); COMPUTE_LOC(8); + + /* left channel */ + *srce++ +=RVbufL1[loc1]-RVbufL2[loc2]+RVbufL3[loc3]-RVbufL4[loc4]+ + RVbufL5[loc5]-RVbufL6[loc6]+RVbufL7[loc7]-RVbufL8[loc8]; + } +} + +static void MixReverb_Stereo(SLONG *srce,NATIVE count) +{ + NATIVE speedup; + int ReverbPct; + unsigned int loc1,loc2,loc3,loc4,loc5,loc6,loc7,loc8; + + ReverbPct=58+(md_reverb*4); + + COMPUTE_LOC(1); COMPUTE_LOC(2); COMPUTE_LOC(3); COMPUTE_LOC(4); + COMPUTE_LOC(5); COMPUTE_LOC(6); COMPUTE_LOC(7); COMPUTE_LOC(8); + + while(count--) { + /* Compute the left channel echo buffers */ + speedup = *srce >> 3; + + COMPUTE_LECHO(1); COMPUTE_LECHO(2); COMPUTE_LECHO(3); COMPUTE_LECHO(4); + COMPUTE_LECHO(5); COMPUTE_LECHO(6); COMPUTE_LECHO(7); COMPUTE_LECHO(8); + + /* Compute the right channel echo buffers */ + speedup = srce[1] >> 3; + + COMPUTE_RECHO(1); COMPUTE_RECHO(2); COMPUTE_RECHO(3); COMPUTE_RECHO(4); + COMPUTE_RECHO(5); COMPUTE_RECHO(6); COMPUTE_RECHO(7); COMPUTE_RECHO(8); + + /* Prepare to compute actual finalized data */ + RVRindex++; + + COMPUTE_LOC(1); COMPUTE_LOC(2); COMPUTE_LOC(3); COMPUTE_LOC(4); + COMPUTE_LOC(5); COMPUTE_LOC(6); COMPUTE_LOC(7); COMPUTE_LOC(8); + + /* left channel */ + *srce++ +=RVbufL1[loc1]-RVbufL2[loc2]+RVbufL3[loc3]-RVbufL4[loc4]+ + RVbufL5[loc5]-RVbufL6[loc6]+RVbufL7[loc7]-RVbufL8[loc8]; + + /* right channel */ + *srce++ +=RVbufR1[loc1]-RVbufR2[loc2]+RVbufR3[loc3]-RVbufR4[loc4]+ + RVbufR5[loc5]-RVbufR6[loc6]+RVbufR7[loc7]-RVbufR8[loc8]; + } +} + +/* Mixing macros */ +#define EXTRACT_SAMPLE(var,attenuation) var=*srce++/(MAXVOL_FACTOR*attenuation) +#define CHECK_SAMPLE(var,bound) var=(var>=bound)?bound-1:(var<-bound)?-bound:var + +static void Mix32To16_Normal(SWORD* dste,const SLONG* srce,NATIVE count) +{ + NATIVE x1,x2,tmpx; + int i; + + for(count/=SAMPLING_FACTOR;count;count--) { + tmpx=0; + + for(i=SAMPLING_FACTOR/2;i;i--) { + EXTRACT_SAMPLE(x1,1); EXTRACT_SAMPLE(x2,1); + + CHECK_SAMPLE(x1,32768); CHECK_SAMPLE(x2,32768); + + tmpx+=x1+x2; + } + *dste++ =tmpx/SAMPLING_FACTOR; + } +} + +static void Mix32To16_Stereo(SWORD* dste,const SLONG* srce,NATIVE count) +{ + NATIVE x1,x2,x3,x4,tmpx,tmpy; + int i; + + for(count/=SAMPLING_FACTOR;count;count--) { + tmpx=tmpy=0; + + for(i=SAMPLING_FACTOR/2;i;i--) { + EXTRACT_SAMPLE(x1,1); EXTRACT_SAMPLE(x2,1); + EXTRACT_SAMPLE(x3,1); EXTRACT_SAMPLE(x4,1); + + CHECK_SAMPLE(x1,32768); CHECK_SAMPLE(x2,32768); + CHECK_SAMPLE(x3,32768); CHECK_SAMPLE(x4,32768); + + tmpx+=x1+x3; + tmpy+=x2+x4; + } + *dste++ =tmpx/SAMPLING_FACTOR; + *dste++ =tmpy/SAMPLING_FACTOR; + } +} + +static void Mix32To8_Normal(SBYTE* dste,const SLONG* srce,NATIVE count) +{ + NATIVE x1,x2,tmpx; + int i; + + for(count/=SAMPLING_FACTOR;count;count--) { + tmpx = 0; + + for(i=SAMPLING_FACTOR/2;i;i--) { + EXTRACT_SAMPLE(x1,256); EXTRACT_SAMPLE(x2,256); + + CHECK_SAMPLE(x1,128); CHECK_SAMPLE(x2,128); + + tmpx+=x1+x2; + } + *dste++ =(tmpx/SAMPLING_FACTOR)+128; + } +} + +static void Mix32To8_Stereo(SBYTE* dste,const SLONG* srce,NATIVE count) +{ + NATIVE x1,x2,x3,x4,tmpx,tmpy; + int i; + + for(count/=SAMPLING_FACTOR;count;count--) { + tmpx=tmpy=0; + + for(i=SAMPLING_FACTOR/2;i;i--) { + EXTRACT_SAMPLE(x1,256); EXTRACT_SAMPLE(x2,256); + EXTRACT_SAMPLE(x3,256); EXTRACT_SAMPLE(x4,256); + + CHECK_SAMPLE(x1,128); CHECK_SAMPLE(x2,128); + CHECK_SAMPLE(x3,128); CHECK_SAMPLE(x4,128); + + tmpx+=x1+x3; + tmpy+=x2+x4; + } + *dste++ =(tmpx/SAMPLING_FACTOR)+128; + *dste++ =(tmpy/SAMPLING_FACTOR)+128; + } +} + +static void AddChannel(SLONG* ptr,NATIVE todo) +{ + SLONGLONG end,done; + SWORD *s; + + if(!(s=Samples[vnf->handle])) { + vnf->current = vnf->active = 0; + vnf->lastvalL = vnf->lastvalR = 0; + return; + } + + /* update the 'current' index so the sample loops, or stops playing if it + reached the end of the sample */ + while(todo>0) { + SLONGLONG endpos; + + if(vnf->flags & SF_REVERSE) { + /* The sample is playing in reverse */ + if((vnf->flags&SF_LOOP)&&(vnf->current<idxlpos)) { + /* the sample is looping and has reached the loopstart index */ + if(vnf->flags & SF_BIDI) { + /* sample is doing bidirectional loops, so 'bounce' the + current index against the idxlpos */ + vnf->current = idxlpos+(idxlpos-vnf->current); + vnf->flags &= ~SF_REVERSE; + vnf->increment = -vnf->increment; + } else + /* normal backwards looping, so set the current position to + loopend index */ + vnf->current=idxlend-(idxlpos-vnf->current); + } else { + /* the sample is not looping, so check if it reached index 0 */ + if(vnf->current < 0) { + /* playing index reached 0, so stop playing this sample */ + vnf->current = vnf->active = 0; + break; + } + } + } else { + /* The sample is playing forward */ + if((vnf->flags & SF_LOOP) && + (vnf->current >= idxlend)) { + /* the sample is looping, check the loopend index */ + if(vnf->flags & SF_BIDI) { + /* sample is doing bidirectional loops, so 'bounce' the + current index against the idxlend */ + vnf->flags |= SF_REVERSE; + vnf->increment = -vnf->increment; + vnf->current = idxlend-(vnf->current-idxlend); + } else + /* normal backwards looping, so set the current position + to loopend index */ + vnf->current=idxlpos+(vnf->current-idxlend); + } else { + /* sample is not looping, so check if it reached the last + position */ + if(vnf->current >= idxsize) { + /* yes, so stop playing this sample */ + vnf->current = vnf->active = 0; + break; + } + } + } + + end=(vnf->flags&SF_REVERSE)?(vnf->flags&SF_LOOP)?idxlpos:0: + (vnf->flags&SF_LOOP)?idxlend:idxsize; + + /* if the sample is not blocked... */ + if((end==vnf->current)||(!vnf->increment)) + done=0; + else { + done=MIN((end-vnf->current)/vnf->increment+1,todo); + if(done<0) done=0; + } + + if(!done) { + vnf->active = 0; + break; + } + + endpos=vnf->current+done*vnf->increment; + + if(vnf->vol || vnf->rampvol) { +#ifndef NATIVE_64BIT_INT + /* use the 32 bit mixers as often as we can (they're much faster) */ + if((vnf->current<0x7fffffff)&&(endpos<0x7fffffff)) { + if(vc_mode & DMODE_STEREO) { + if((vnf->pan==PAN_SURROUND)&&(vc_mode&DMODE_SURROUND)) + vnf->current=Mix32StereoSurround + (s,ptr,vnf->current,vnf->increment,done); + else + vnf->current=Mix32StereoNormal + (s,ptr,vnf->current,vnf->increment,done); + } else + vnf->current=Mix32MonoNormal + (s,ptr,vnf->current,vnf->increment,done); + } else +#endif + { + if(vc_mode & DMODE_STEREO) { + if((vnf->pan==PAN_SURROUND)&&(vc_mode&DMODE_SURROUND)) + vnf->current=MixStereoSurround + (s,ptr,vnf->current,vnf->increment,done); + else + vnf->current=MixStereoNormal + (s,ptr,vnf->current,vnf->increment,done); + } else + vnf->current=MixMonoNormal + (s,ptr,vnf->current,vnf->increment,done); + } + } else { + vnf->lastvalL = vnf->lastvalR = 0; + /* update sample position */ + vnf->current=endpos; + } + + todo -= done; + ptr +=(vc_mode & DMODE_STEREO)?(done<<1):done; + } +} + +#define _IN_VIRTCH_ + +#define VC1_SilenceBytes VC2_SilenceBytes +#define VC1_WriteSamples VC2_WriteSamples +#define VC1_WriteBytes VC2_WriteBytes +#define VC1_Exit VC2_Exit +#define VC1_VoiceSetVolume VC2_VoiceSetVolume +#define VC1_VoiceGetVolume VC2_VoiceGetVolume +#define VC1_VoiceSetPanning VC2_VoiceSetPanning +#define VC1_VoiceGetPanning VC2_VoiceGetPanning +#define VC1_VoiceSetFrequency VC2_VoiceSetFrequency +#define VC1_VoiceGetFrequency VC2_VoiceGetFrequency +#define VC1_VoicePlay VC2_VoicePlay +#define VC1_VoiceStop VC2_VoiceStop +#define VC1_VoiceStopped VC2_VoiceStopped +#define VC1_VoiceGetPosition VC2_VoiceGetPosition +#define VC1_SampleUnload VC2_SampleUnload +#define VC1_SampleLoad VC2_SampleLoad +#define VC1_SampleSpace VC2_SampleSpace +#define VC1_SampleLength VC2_SampleLength +#define VC1_VoiceRealVolume VC2_VoiceRealVolume + +#include "virtch_common.c" +#undef _IN_VIRTCH_ + +void VC2_WriteSamples(SBYTE* buf,ULONG todo) +{ + int left,portion=0; + SBYTE *buffer; + int t,pan,vol; + + todo*=SAMPLING_FACTOR; + + while(todo) { + if(!tickleft) { + if(vc_mode & DMODE_SOFT_MUSIC) md_player(); + tickleft=(md_mixfreq*125L*SAMPLING_FACTOR)/(md_bpm*50L); + tickleft&=~(SAMPLING_FACTOR-1); + } + left = MIN(tickleft, (long)todo); + buffer = buf; + tickleft -= left; + todo -= left; + buf += samples2bytes(left)/SAMPLING_FACTOR; + + while(left) { + portion = MIN(left, samplesthatfit); + memset(vc_tickbuf,0,portion<<((vc_mode&DMODE_STEREO)?3:2)); + for(t=0;t<vc_softchn;t++) { + vnf = &vinf[t]; + + if(vnf->kick) { + vnf->current=((SLONGLONG)(vnf->start))<<FRACBITS; + vnf->kick = 0; + vnf->active = 1; + vnf->click = CLICK_BUFFER; + vnf->rampvol = 0; + } + + if(!vnf->frq) vnf->active = 0; + + if(vnf->active) { + vnf->increment=((SLONGLONG)(vnf->frq)<<(FRACBITS-SAMPLING_SHIFT)) + /md_mixfreq; + if(vnf->flags&SF_REVERSE) vnf->increment=-vnf->increment; + vol = vnf->vol; pan = vnf->pan; + + vnf->oldlvol=vnf->lvolsel;vnf->oldrvol=vnf->rvolsel; + if(vc_mode & DMODE_STEREO) { + if(pan!=PAN_SURROUND) { + vnf->lvolsel=(vol*(PAN_RIGHT-pan))>>8; + vnf->rvolsel=(vol*pan)>>8; + } else { + vnf->lvolsel=vnf->rvolsel=(vol * 256L) / 480; + } + } else + vnf->lvolsel=vol; + + idxsize=(vnf->size)?((SLONGLONG)(vnf->size)<<FRACBITS)-1:0; + idxlend=(vnf->repend)?((SLONGLONG)(vnf->repend)<<FRACBITS)-1:0; + idxlpos=(SLONGLONG)(vnf->reppos)<<FRACBITS; + AddChannel(vc_tickbuf,portion); + } + } + + if(md_reverb) { + if(md_reverb>15) md_reverb=15; + MixReverb(vc_tickbuf,portion); + } + + if(vc_mode & DMODE_16BITS) + Mix32to16((SWORD*)buffer,vc_tickbuf,portion); + else + Mix32to8((SBYTE*)buffer,vc_tickbuf,portion); + + buffer += samples2bytes(portion) / SAMPLING_FACTOR; + left -= portion; + } + } +} + +BOOL VC2_Init(void) +{ + VC_SetupPointers(); + + if (!(md_mode&DMODE_HQMIXER)) + return VC1_Init(); + + if(!(Samples=(SWORD**)MikMod_calloc(MAXSAMPLEHANDLES,sizeof(SWORD*)))) { + _mm_errno = MMERR_INITIALIZING_MIXER; + return 1; + } + if(!vc_tickbuf) + if(!(vc_tickbuf=(SLONG*)MikMod_malloc((TICKLSIZE+32)*sizeof(SLONG)))) { + _mm_errno = MMERR_INITIALIZING_MIXER; + return 1; + } + + if(md_mode & DMODE_STEREO) { + Mix32to16 = Mix32To16_Stereo; + Mix32to8 = Mix32To8_Stereo; + MixReverb = MixReverb_Stereo; + } else { + Mix32to16 = Mix32To16_Normal; + Mix32to8 = Mix32To8_Normal; + MixReverb = MixReverb_Normal; + } + md_mode |= DMODE_INTERP; + vc_mode = md_mode; + return 0; +} + +BOOL VC2_PlayStart(void) +{ + md_mode|=DMODE_INTERP; + + samplesthatfit = TICKLSIZE; + if(vc_mode & DMODE_STEREO) samplesthatfit >>= 1; + tickleft = 0; + + RVc1 = (5000L * md_mixfreq) / (REVERBERATION * 10); + RVc2 = (5078L * md_mixfreq) / (REVERBERATION * 10); + RVc3 = (5313L * md_mixfreq) / (REVERBERATION * 10); + RVc4 = (5703L * md_mixfreq) / (REVERBERATION * 10); + RVc5 = (6250L * md_mixfreq) / (REVERBERATION * 10); + RVc6 = (6953L * md_mixfreq) / (REVERBERATION * 10); + RVc7 = (7813L * md_mixfreq) / (REVERBERATION * 10); + RVc8 = (8828L * md_mixfreq) / (REVERBERATION * 10); + + if(!(RVbufL1=(SLONG*)MikMod_calloc((RVc1+1),sizeof(SLONG)))) return 1; + if(!(RVbufL2=(SLONG*)MikMod_calloc((RVc2+1),sizeof(SLONG)))) return 1; + if(!(RVbufL3=(SLONG*)MikMod_calloc((RVc3+1),sizeof(SLONG)))) return 1; + if(!(RVbufL4=(SLONG*)MikMod_calloc((RVc4+1),sizeof(SLONG)))) return 1; + if(!(RVbufL5=(SLONG*)MikMod_calloc((RVc5+1),sizeof(SLONG)))) return 1; + if(!(RVbufL6=(SLONG*)MikMod_calloc((RVc6+1),sizeof(SLONG)))) return 1; + if(!(RVbufL7=(SLONG*)MikMod_calloc((RVc7+1),sizeof(SLONG)))) return 1; + if(!(RVbufL8=(SLONG*)MikMod_calloc((RVc8+1),sizeof(SLONG)))) return 1; + + if(!(RVbufR1=(SLONG*)MikMod_calloc((RVc1+1),sizeof(SLONG)))) return 1; + if(!(RVbufR2=(SLONG*)MikMod_calloc((RVc2+1),sizeof(SLONG)))) return 1; + if(!(RVbufR3=(SLONG*)MikMod_calloc((RVc3+1),sizeof(SLONG)))) return 1; + if(!(RVbufR4=(SLONG*)MikMod_calloc((RVc4+1),sizeof(SLONG)))) return 1; + if(!(RVbufR5=(SLONG*)MikMod_calloc((RVc5+1),sizeof(SLONG)))) return 1; + if(!(RVbufR6=(SLONG*)MikMod_calloc((RVc6+1),sizeof(SLONG)))) return 1; + if(!(RVbufR7=(SLONG*)MikMod_calloc((RVc7+1),sizeof(SLONG)))) return 1; + if(!(RVbufR8=(SLONG*)MikMod_calloc((RVc8+1),sizeof(SLONG)))) return 1; + + RVRindex = 0; + return 0; +} + +void VC2_PlayStop(void) +{ + if(RVbufL1) MikMod_free(RVbufL1); + if(RVbufL2) MikMod_free(RVbufL2); + if(RVbufL3) MikMod_free(RVbufL3); + if(RVbufL4) MikMod_free(RVbufL4); + if(RVbufL5) MikMod_free(RVbufL5); + if(RVbufL6) MikMod_free(RVbufL6); + if(RVbufL7) MikMod_free(RVbufL7); + if(RVbufL8) MikMod_free(RVbufL8); + if(RVbufR1) MikMod_free(RVbufR1); + if(RVbufR2) MikMod_free(RVbufR2); + if(RVbufR3) MikMod_free(RVbufR3); + if(RVbufR4) MikMod_free(RVbufR4); + if(RVbufR5) MikMod_free(RVbufR5); + if(RVbufR6) MikMod_free(RVbufR6); + if(RVbufR7) MikMod_free(RVbufR7); + if(RVbufR8) MikMod_free(RVbufR8); + + RVbufL1=RVbufL2=RVbufL3=RVbufL4=RVbufL5=RVbufL6=RVbufL7=RVbufL8=NULL; + RVbufR1=RVbufR2=RVbufR3=RVbufR4=RVbufR5=RVbufR6=RVbufR7=RVbufR8=NULL; +} + +BOOL VC2_SetNumVoices(void) +{ + int t; + + md_mode|=DMODE_INTERP; + + if(!(vc_softchn=md_softchn)) return 0; + + if(vinf) MikMod_free(vinf); + if(!(vinf=MikMod_calloc(sizeof(VINFO),vc_softchn))) return 1; + + for(t=0;t<vc_softchn;t++) { + vinf[t].frq=10000; + vinf[t].pan=(t&1)?PAN_LEFT:PAN_RIGHT; + } + + return 0; +} + +/* ex:set ts=4: */ diff --git a/src/libs/mikmod/virtch_common.c b/src/libs/mikmod/virtch_common.c new file mode 100644 index 0000000..b0b81ee --- /dev/null +++ b/src/libs/mikmod/virtch_common.c @@ -0,0 +1,459 @@ +/* MikMod sound library + (c) 1998, 1999, 2000, 2001 Miodrag Vallat and others - see file AUTHORS + for complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + Common source parts between the two software mixers. + This file is probably the ugliest part of libmikmod... + +==============================================================================*/ + +#ifndef _IN_VIRTCH_ + +#include "mikmod_internals.h" + +extern BOOL VC1_Init(void); +extern BOOL VC2_Init(void); +static BOOL (*VC_Init_ptr)(void)=VC1_Init; +extern void VC1_Exit(void); +extern void VC2_Exit(void); +static void (*VC_Exit_ptr)(void)=VC1_Exit; +extern BOOL VC1_SetNumVoices(void); +extern BOOL VC2_SetNumVoices(void); +static BOOL (*VC_SetNumVoices_ptr)(void); +extern ULONG VC1_SampleSpace(int); +extern ULONG VC2_SampleSpace(int); +static ULONG (*VC_SampleSpace_ptr)(int); +extern ULONG VC1_SampleLength(int,SAMPLE*); +extern ULONG VC2_SampleLength(int,SAMPLE*); +static ULONG (*VC_SampleLength_ptr)(int,SAMPLE*); + +extern BOOL VC1_PlayStart(void); +extern BOOL VC2_PlayStart(void); +static BOOL (*VC_PlayStart_ptr)(void); +extern void VC1_PlayStop(void); +extern void VC2_PlayStop(void); +static void (*VC_PlayStop_ptr)(void); + +extern SWORD VC1_SampleLoad(struct SAMPLOAD*,int); +extern SWORD VC2_SampleLoad(struct SAMPLOAD*,int); +static SWORD (*VC_SampleLoad_ptr)(struct SAMPLOAD*,int); +extern void VC1_SampleUnload(SWORD); +extern void VC2_SampleUnload(SWORD); +static void (*VC_SampleUnload_ptr)(SWORD); + +extern ULONG VC1_WriteBytes(SBYTE*,ULONG); +extern ULONG VC2_WriteBytes(SBYTE*,ULONG); +static ULONG (*VC_WriteBytes_ptr)(SBYTE*,ULONG); +extern ULONG VC1_SilenceBytes(SBYTE*,ULONG); +extern ULONG VC2_SilenceBytes(SBYTE*,ULONG); +static ULONG (*VC_SilenceBytes_ptr)(SBYTE*,ULONG); + +extern void VC1_VoiceSetVolume(UBYTE,UWORD); +extern void VC2_VoiceSetVolume(UBYTE,UWORD); +static void (*VC_VoiceSetVolume_ptr)(UBYTE,UWORD); +extern UWORD VC1_VoiceGetVolume(UBYTE); +extern UWORD VC2_VoiceGetVolume(UBYTE); +static UWORD (*VC_VoiceGetVolume_ptr)(UBYTE); +extern void VC1_VoiceSetFrequency(UBYTE,ULONG); +extern void VC2_VoiceSetFrequency(UBYTE,ULONG); +static void (*VC_VoiceSetFrequency_ptr)(UBYTE,ULONG); +extern ULONG VC1_VoiceGetFrequency(UBYTE); +extern ULONG VC2_VoiceGetFrequency(UBYTE); +static ULONG (*VC_VoiceGetFrequency_ptr)(UBYTE); +extern void VC1_VoiceSetPanning(UBYTE,ULONG); +extern void VC2_VoiceSetPanning(UBYTE,ULONG); +static void (*VC_VoiceSetPanning_ptr)(UBYTE,ULONG); +extern ULONG VC1_VoiceGetPanning(UBYTE); +extern ULONG VC2_VoiceGetPanning(UBYTE); +static ULONG (*VC_VoiceGetPanning_ptr)(UBYTE); +extern void VC1_VoicePlay(UBYTE,SWORD,ULONG,ULONG,ULONG,ULONG,UWORD); +extern void VC2_VoicePlay(UBYTE,SWORD,ULONG,ULONG,ULONG,ULONG,UWORD); +static void (*VC_VoicePlay_ptr)(UBYTE,SWORD,ULONG,ULONG,ULONG,ULONG,UWORD); + +extern void VC1_VoiceStop(UBYTE); +extern void VC2_VoiceStop(UBYTE); +static void (*VC_VoiceStop_ptr)(UBYTE); +extern BOOL VC1_VoiceStopped(UBYTE); +extern BOOL VC2_VoiceStopped(UBYTE); +static BOOL (*VC_VoiceStopped_ptr)(UBYTE); +extern SLONG VC1_VoiceGetPosition(UBYTE); +extern SLONG VC2_VoiceGetPosition(UBYTE); +static SLONG (*VC_VoiceGetPosition_ptr)(UBYTE); +extern ULONG VC1_VoiceRealVolume(UBYTE); +extern ULONG VC2_VoiceRealVolume(UBYTE); +static ULONG (*VC_VoiceRealVolume_ptr)(UBYTE); + +#if defined __STDC__ || defined _MSC_VER || defined MPW_C +#define VC_PROC0(suffix) \ +MIKMODAPI void VC_##suffix (void) { VC_##suffix##_ptr(); } + +#define VC_FUNC0(suffix,ret) \ +MIKMODAPI ret VC_##suffix (void) { return VC_##suffix##_ptr(); } + +#define VC_PROC1(suffix,typ1) \ +MIKMODAPI void VC_##suffix (typ1 a) { VC_##suffix##_ptr(a); } + +#define VC_FUNC1(suffix,ret,typ1) \ +MIKMODAPI ret VC_##suffix (typ1 a) { return VC_##suffix##_ptr(a); } + +#define VC_PROC2(suffix,typ1,typ2) \ +MIKMODAPI void VC_##suffix (typ1 a,typ2 b) { VC_##suffix##_ptr(a,b); } + +#define VC_FUNC2(suffix,ret,typ1,typ2) \ +MIKMODAPI ret VC_##suffix (typ1 a,typ2 b) { return VC_##suffix##_ptr(a,b); } +#else +#define VC_PROC0(suffix) \ +MIKMODAPI void VC_/**/suffix (void) { VC_/**/suffix/**/_ptr(); } + +#define VC_FUNC0(suffix,ret) \ +MIKMODAPI ret VC_/**/suffix (void) { return VC_/**/suffix/**/_ptr(); } + +#define VC_PROC1(suffix,typ1) \ +MIKMODAPI void VC_/**/suffix (typ1 a) { VC_/**/suffix/**/_ptr(a); } + +#define VC_FUNC1(suffix,ret,typ1) \ +MIKMODAPI ret VC_/**/suffix (typ1 a) { return VC_/**/suffix/**/_ptr(a); } + +#define VC_PROC2(suffix,typ1,typ2) \ +MIKMODAPI void VC_/**/suffix (typ1 a,typ2 b) { VC_/**/suffix/**/_ptr(a,b); } + +#define VC_FUNC2(suffix,ret,typ1,typ2) \ +MIKMODAPI ret VC_/**/suffix (typ1 a,typ2 b) { return VC_/**/suffix/**/_ptr(a,b); } +#endif + +VC_FUNC0(Init,BOOL) +VC_PROC0(Exit) +VC_FUNC0(SetNumVoices,BOOL) +VC_FUNC1(SampleSpace,ULONG,int) +VC_FUNC2(SampleLength,ULONG,int,SAMPLE*) +VC_FUNC0(PlayStart,BOOL) +VC_PROC0(PlayStop) +VC_FUNC2(SampleLoad,SWORD,struct SAMPLOAD*,int) +VC_PROC1(SampleUnload,SWORD) +VC_FUNC2(WriteBytes,ULONG,SBYTE*,ULONG) +VC_FUNC2(SilenceBytes,ULONG,SBYTE*,ULONG) +VC_PROC2(VoiceSetVolume,UBYTE,UWORD) +VC_FUNC1(VoiceGetVolume,UWORD,UBYTE) +VC_PROC2(VoiceSetFrequency,UBYTE,ULONG) +VC_FUNC1(VoiceGetFrequency,ULONG,UBYTE) +VC_PROC2(VoiceSetPanning,UBYTE,ULONG) +VC_FUNC1(VoiceGetPanning,ULONG,UBYTE) + +void VC_VoicePlay(UBYTE a,SWORD b,ULONG c,ULONG d,ULONG e,ULONG f,UWORD g) +{ VC_VoicePlay_ptr(a,b,c,d,e,f,g); } + +VC_PROC1(VoiceStop,UBYTE) +VC_FUNC1(VoiceStopped,BOOL,UBYTE) +VC_FUNC1(VoiceGetPosition,SLONG,UBYTE) +VC_FUNC1(VoiceRealVolume,ULONG,UBYTE) + +void VC_SetupPointers(void) +{ + if (md_mode&DMODE_HQMIXER) { + VC_Init_ptr=VC2_Init; + VC_Exit_ptr=VC2_Exit; + VC_SetNumVoices_ptr=VC2_SetNumVoices; + VC_SampleSpace_ptr=VC2_SampleSpace; + VC_SampleLength_ptr=VC2_SampleLength; + VC_PlayStart_ptr=VC2_PlayStart; + VC_PlayStop_ptr=VC2_PlayStop; + VC_SampleLoad_ptr=VC2_SampleLoad; + VC_SampleUnload_ptr=VC2_SampleUnload; + VC_WriteBytes_ptr=VC2_WriteBytes; + VC_SilenceBytes_ptr=VC2_SilenceBytes; + VC_VoiceSetVolume_ptr=VC2_VoiceSetVolume; + VC_VoiceGetVolume_ptr=VC2_VoiceGetVolume; + VC_VoiceSetFrequency_ptr=VC2_VoiceSetFrequency; + VC_VoiceGetFrequency_ptr=VC2_VoiceGetFrequency; + VC_VoiceSetPanning_ptr=VC2_VoiceSetPanning; + VC_VoiceGetPanning_ptr=VC2_VoiceGetPanning; + VC_VoicePlay_ptr=VC2_VoicePlay; + VC_VoiceStop_ptr=VC2_VoiceStop; + VC_VoiceStopped_ptr=VC2_VoiceStopped; + VC_VoiceGetPosition_ptr=VC2_VoiceGetPosition; + VC_VoiceRealVolume_ptr=VC2_VoiceRealVolume; + } else { + VC_Init_ptr=VC1_Init; + VC_Exit_ptr=VC1_Exit; + VC_SetNumVoices_ptr=VC1_SetNumVoices; + VC_SampleSpace_ptr=VC1_SampleSpace; + VC_SampleLength_ptr=VC1_SampleLength; + VC_PlayStart_ptr=VC1_PlayStart; + VC_PlayStop_ptr=VC1_PlayStop; + VC_SampleLoad_ptr=VC1_SampleLoad; + VC_SampleUnload_ptr=VC1_SampleUnload; + VC_WriteBytes_ptr=VC1_WriteBytes; + VC_SilenceBytes_ptr=VC1_SilenceBytes; + VC_VoiceSetVolume_ptr=VC1_VoiceSetVolume; + VC_VoiceGetVolume_ptr=VC1_VoiceGetVolume; + VC_VoiceSetFrequency_ptr=VC1_VoiceSetFrequency; + VC_VoiceGetFrequency_ptr=VC1_VoiceGetFrequency; + VC_VoiceSetPanning_ptr=VC1_VoiceSetPanning; + VC_VoiceGetPanning_ptr=VC1_VoiceGetPanning; + VC_VoicePlay_ptr=VC1_VoicePlay; + VC_VoiceStop_ptr=VC1_VoiceStop; + VC_VoiceStopped_ptr=VC1_VoiceStopped; + VC_VoiceGetPosition_ptr=VC1_VoiceGetPosition; + VC_VoiceRealVolume_ptr=VC1_VoiceRealVolume; + } +} + +#else + +#ifndef _VIRTCH_COMMON_ +#define _VIRTCH_COMMON_ + +static ULONG samples2bytes(ULONG samples) +{ + if(vc_mode & DMODE_16BITS) samples <<= 1; + if(vc_mode & DMODE_STEREO) samples <<= 1; + return samples; +} + +static ULONG bytes2samples(ULONG bytes) +{ + if(vc_mode & DMODE_16BITS) bytes >>= 1; + if(vc_mode & DMODE_STEREO) bytes >>= 1; + return bytes; +} + +/* Fill the buffer with 'todo' bytes of silence (it depends on the mixing mode + how the buffer is filled) */ +ULONG VC1_SilenceBytes(SBYTE* buf,ULONG todo) +{ + todo=samples2bytes(bytes2samples(todo)); + + /* clear the buffer to zero (16 bits signed) or 0x80 (8 bits unsigned) */ + if(vc_mode & DMODE_16BITS) + memset(buf,0,todo); + else + memset(buf,0x80,todo); + + return todo; +} + +void VC1_WriteSamples(SBYTE*,ULONG); + +/* Writes 'todo' mixed SBYTES (!!) to 'buf'. It returns the number of SBYTES + actually written to 'buf' (which is rounded to number of samples that fit + into 'todo' bytes). */ +ULONG VC1_WriteBytes(SBYTE* buf,ULONG todo) +{ + if(!vc_softchn) + return VC1_SilenceBytes(buf,todo); + + todo = bytes2samples(todo); + VC1_WriteSamples(buf,todo); + + return samples2bytes(todo); +} + +void VC1_Exit(void) +{ + if(vc_tickbuf) MikMod_free(vc_tickbuf); + if(vinf) MikMod_free(vinf); + if(Samples) MikMod_free(Samples); + + vc_tickbuf = NULL; + vinf = NULL; + Samples = NULL; + + VC_SetupPointers(); +} + +UWORD VC1_VoiceGetVolume(UBYTE voice) +{ + return vinf[voice].vol; +} + +ULONG VC1_VoiceGetPanning(UBYTE voice) +{ + return vinf[voice].pan; +} + +void VC1_VoiceSetFrequency(UBYTE voice,ULONG frq) +{ + vinf[voice].frq=frq; +} + +ULONG VC1_VoiceGetFrequency(UBYTE voice) +{ + return vinf[voice].frq; +} + +void VC1_VoicePlay(UBYTE voice,SWORD handle,ULONG start,ULONG size,ULONG reppos,ULONG repend,UWORD flags) +{ + vinf[voice].flags = flags; + vinf[voice].handle = handle; + vinf[voice].start = start; + vinf[voice].size = size; + vinf[voice].reppos = reppos; + vinf[voice].repend = repend; + vinf[voice].kick = 1; +} + +void VC1_VoiceStop(UBYTE voice) +{ + vinf[voice].active = 0; +} + +BOOL VC1_VoiceStopped(UBYTE voice) +{ + return(vinf[voice].active==0); +} + +SLONG VC1_VoiceGetPosition(UBYTE voice) +{ + return(vinf[voice].current>>FRACBITS); +} + +void VC1_VoiceSetVolume(UBYTE voice,UWORD vol) +{ + /* protect against clicks if volume variation is too high */ + if(abs((int)vinf[voice].vol-(int)vol)>32) + vinf[voice].rampvol=CLICK_BUFFER; + vinf[voice].vol=vol; +} + +void VC1_VoiceSetPanning(UBYTE voice,ULONG pan) +{ + /* protect against clicks if panning variation is too high */ + if(abs((int)vinf[voice].pan-(int)pan)>48) + vinf[voice].rampvol=CLICK_BUFFER; + vinf[voice].pan=pan; +} + +/*========== External mixer interface */ + +void VC1_SampleUnload(SWORD handle) +{ + if (handle<MAXSAMPLEHANDLES) { + if (Samples[handle]) + MikMod_free(Samples[handle]); + Samples[handle]=NULL; + } +} + +SWORD VC1_SampleLoad(struct SAMPLOAD* sload,int type) +{ + SAMPLE *s = sload->sample; + int handle; + ULONG t, length,loopstart,loopend; + + if(type==MD_HARDWARE) return -1; + + /* Find empty slot to put sample address in */ + for(handle=0;handle<MAXSAMPLEHANDLES;handle++) + if(!Samples[handle]) break; + + if(handle==MAXSAMPLEHANDLES) { + _mm_errno = MMERR_OUT_OF_HANDLES; + return -1; + } + + /* Reality check for loop settings */ + if (s->loopend > s->length) + s->loopend = s->length; + if (s->loopstart >= s->loopend) + s->flags &= ~SF_LOOP; + + length = s->length; + loopstart = s->loopstart; + loopend = s->loopend; + + SL_SampleSigned(sload); + SL_Sample8to16(sload); + + if(!(Samples[handle]=(SWORD*)MikMod_malloc((length+20)<<1))) { + _mm_errno = MMERR_SAMPLE_TOO_BIG; + return -1; + } + + /* read sample into buffer */ + if (SL_Load(Samples[handle],sload,length)) + return -1; + + /* Unclick sample */ + if(s->flags & SF_LOOP) { + if(s->flags & SF_BIDI) + for(t=0;t<16;t++) + Samples[handle][loopend+t]=Samples[handle][(loopend-t)-1]; + else + for(t=0;t<16;t++) + Samples[handle][loopend+t]=Samples[handle][t+loopstart]; + } else + for(t=0;t<16;t++) + Samples[handle][t+length]=0; + + return handle; +} + +ULONG VC1_SampleSpace(int type) +{ + (void)type; /* unused arg */ + + return vc_memory; +} + +ULONG VC1_SampleLength(int type,SAMPLE* s) +{ + (void)type; /* unused arg */ + + if (!s) return 0; + + return (s->length*((s->flags&SF_16BITS)?2:1))+16; +} + +ULONG VC1_VoiceRealVolume(UBYTE voice) +{ + ULONG i,s,size; + int k,j; + SWORD *smp; + SLONG t; + + t = vinf[voice].current>>FRACBITS; + if(!vinf[voice].active) return 0; + + s = vinf[voice].handle; + size = vinf[voice].size; + + i=64; t-=64; k=0; j=0; + if(i>size) i = size; + if(t<0) t = 0; + if(t+i > size) t = size-i; + + i &= ~1; /* make sure it's EVEN. */ + + smp = &Samples[s][t]; + for(;i;i--,smp++) { + if(k<*smp) k = *smp; + if(j>*smp) j = *smp; + } + return abs(k-j); +} + +#endif + +#endif + +/* ex:set ts=4: */ diff --git a/src/libs/misc.h b/src/libs/misc.h new file mode 100644 index 0000000..39333a2 --- /dev/null +++ b/src/libs/misc.h @@ -0,0 +1,66 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +// This file includes some misc things, previously in SDL_wrapper.h +// before modularization. -Mika + +#ifndef MISC_H +#define MISC_H + +#include <sys/types.h> +#include <stdlib.h> +#include "port.h" + +#if defined(__cplusplus) +extern "C" { +#endif + + +extern int TFB_DEBUG_HALT; + +static inline void explode (void) _NORETURN; + +static inline void explode (void) +{ +#ifdef DEBUG + // give debugger a chance to hook + abort (); +#else + exit (EXIT_FAILURE); +#endif +} + +/* Sometimes you just have to remove a 'const'. + * (for instance, when implementing a function like strchr) + */ +static inline void * +unconst(const void *arg) { + union { + void *c; + const void *cc; + } u; + u.cc = arg; + return u.c; +} + +#if defined(__cplusplus) +} +#endif + +#endif + diff --git a/src/libs/net.h b/src/libs/net.h new file mode 100644 index 0000000..ca5931e --- /dev/null +++ b/src/libs/net.h @@ -0,0 +1,36 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef LIBS_NET_H_ +#define LIBS_NET_H_ + +#if defined(__cplusplus) +extern "C" { +#endif + +#include "network/network.h" +#include "network/netmanager/netmanager.h" +#include "network/connect/connect.h" +#include "network/connect/listen.h" +#include "network/connect/resolve.h" + +#if defined(__cplusplus) +} +#endif + +#endif /* LIBS_NET_H_ */ diff --git a/src/libs/network/FILES b/src/libs/network/FILES new file mode 100644 index 0000000..ddcd685 --- /dev/null +++ b/src/libs/network/FILES @@ -0,0 +1,26 @@ +In libs/network/: +netport.{c,h} Portability definitions. +bytesex.h Functions for endianness conversions. +network.h Functions for initialising the network subsystem. +network_bsd.c Network subsystem functions for BSD sockets. +network_win.c Network subsystem functions for Winsock sockets. +wspiapiwrap.{c,h} Hack to make sure <wspiapi.h> is #included in exactly + one file. + +In libs/network/netmanager/: +ndesc.{c,h} Defines network descriptors. +netmanager.h Handles callbacks for network activity. +netmanager_bsd.{c,h} NetManager for systems with BSD sockets. +netmanager_win.{c,h} NetManager for Winsock systems. + +In libs/network/socket/: +socket.{c,h} Platform-independant socket layer. +socket_bsd.{c,h} Socket operations for BSD sockets. +socket_win.{c,h} Socket operations for Winsock sockets. + +In libs/network/connect/: +connect.{c,h} Routines for establishing outgoing connections. +listen.{c,h} Routines for receiving incoming connections. +resolve.{c,h} Routines for hostname lookups. + + diff --git a/src/libs/network/Makeinfo b/src/libs/network/Makeinfo new file mode 100644 index 0000000..40171fa --- /dev/null +++ b/src/libs/network/Makeinfo @@ -0,0 +1,14 @@ +uqm_SUBDIRS="connect netmanager socket" +uqm_CFILES="netport.c" +uqm_HFILES="bytesex.h netport.h network.h" + +if [ -n "$uqm_USE_WINSOCK" ]; then + uqm_CFILES="$uqm_CFILES network_win.c" + if [ -n "$MACRO___MINGW32__" ]; then + uqm_CFILES="$uqm_CFILES wspiapiwrap.c" + uqm_HFILES="$uqm_HFILES wspiapiwrap.h" + fi +else + uqm_CFILES="$uqm_CFILES network_bsd.c" +fi + diff --git a/src/libs/network/bytesex.h b/src/libs/network/bytesex.h new file mode 100644 index 0000000..4ee89d7 --- /dev/null +++ b/src/libs/network/bytesex.h @@ -0,0 +1,96 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// Routines for changing the endianness of values. +// I'm not using ntohs() etc. as those would require include files that may +// have conflicting definitions. This is a problem on Windows, where these +// functions are in winsock2.h, which includes windows.h, which includes +// pretty much Microsoft's complete collection of .h files. + +#ifndef LIBS_NETWORK_BYTESEX_H_ +#define LIBS_NETWORK_BYTESEX_H_ + +#include "port.h" + // for inline +#include "endian_uqm.h" + // for WORDS_BIGENDIAN +#include "types.h" + +static inline uint16 +swapBytes16(uint16 x) { + return (x << 8) | (x >> 8); +} + +static inline uint32 +swapBytes32(uint32 x) { + return (x << 24) + | ((x & 0x0000ff00) << 8) + | ((x & 0x00ff0000) >> 8) + | (x >> 24); +} + +#ifdef WORDS_BIGENDIAN +// Already in network order. + +static inline uint16 +hton16(uint16 x) { + return x; +} + +static inline uint32 +hton32(uint32 x) { + return x; +} + +static inline uint16 +ntoh16(uint16 x) { + return x; +} + +static inline uint32 +ntoh32(uint32 x) { + return x; +} + +#else /* !defined(WORDS_BIGENDIAN) */ +// Need to swap bytes + +static inline uint16 +hton16(uint16 x) { + return swapBytes16(x); +} + +static inline uint32 +hton32(uint32 x) { + return swapBytes32(x); +} + +static inline uint16 +ntoh16(uint16 x) { + return swapBytes16(x); +} + +static inline uint32 +ntoh32(uint32 x) { + return swapBytes32(x); +} + +#endif /* defined(WORDS_BIGENDIAN) */ + +#endif /* LIBS_NETWORK_BYTESEX_H_ */ + diff --git a/src/libs/network/connect/Makeinfo b/src/libs/network/connect/Makeinfo new file mode 100644 index 0000000..5595270 --- /dev/null +++ b/src/libs/network/connect/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="connect.c listen.c resolve.c" +uqm_HFILES="connect.h listen.h resolve.h" diff --git a/src/libs/network/connect/connect.c b/src/libs/network/connect/connect.c new file mode 100644 index 0000000..4599b18 --- /dev/null +++ b/src/libs/network/connect/connect.c @@ -0,0 +1,490 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define PORT_WANT_ERRNO +#include "port.h" + +#define CONNECT_INTERNAL +#define SOCKET_INTERNAL +#include "connect.h" + +#include "resolve.h" +#include "libs/alarm.h" +#include "../socket/socket.h" +#include "libs/misc.h" +#include "libs/log.h" + +#include <assert.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#ifdef USE_WINSOCK +# include <winsock2.h> +# include <ws2tcpip.h> +# include "../wspiapiwrap.h" +#else +# include <netdb.h> +#endif + +#define DEBUG_CONNECT_REF +#ifdef DEBUG_CONNECT_REF +# include "types.h" +#endif + + +static void connectHostNext(ConnectState *connectState); +static void doConnectCallback(ConnectState *connectState, NetDescriptor *nd, + const struct sockaddr *addr, socklen_t addrLen); +static void doConnectErrorCallback(ConnectState *connectState, + const ConnectError *error); + + +static ConnectState * +ConnectState_alloc(void) { + return (ConnectState *) malloc(sizeof (ConnectState)); +} + +static void +ConnectState_free(ConnectState *connectState) { + free(connectState); +} + +static void +ConnectState_delete(ConnectState *connectState) { + assert(connectState->nd == NULL); + assert(connectState->alarm == NULL); + assert(connectState->info == NULL); + assert(connectState->infoPtr == NULL); + ConnectState_free(connectState); +} + +void +ConnectState_incRef(ConnectState *connectState) { + assert(connectState->refCount < REFCOUNT_MAX); + connectState->refCount++; +#ifdef DEBUG_CONNECT_REF + log_add(log_Debug, "ConnectState %08" PRIxPTR ": ref++ (%d)", + (uintptr_t) connectState, connectState->refCount); +#endif +} + +bool +ConnectState_decRef(ConnectState *connectState) { + assert(connectState->refCount > 0); + connectState->refCount--; +#ifdef DEBUG_CONNECT_REF + log_add(log_Debug, "ConnectState %08" PRIxPTR ": ref-- (%d)", + (uintptr_t) connectState, connectState->refCount); +#endif + if (connectState->refCount == 0) { + ConnectState_delete(connectState); + return true; + } + return false; +} + +// decrements ref count by 1 +void +ConnectState_close(ConnectState *connectState) { + if (connectState->resolveState != NULL) { + Resolve_close(connectState->resolveState); + connectState->resolveState = NULL; + } + if (connectState->alarm != NULL) { + Alarm_remove(connectState->alarm); + connectState->alarm = NULL; + } + if (connectState->nd != NULL) { + NetDescriptor_close(connectState->nd); + connectState->nd = NULL; + } + if (connectState->info != NULL) { + freeaddrinfo(connectState->info); + connectState->info = NULL; + connectState->infoPtr = NULL; + } + connectState->state = Connect_closed; + ConnectState_decRef(connectState); +} + +void +ConnectState_setExtra(ConnectState *connectState, void *extra) { + connectState->extra = extra; +} + +void * +ConnectState_getExtra(ConnectState *connectState) { + return connectState->extra; +} + +static void +connectCallback(NetDescriptor *nd) { + // Called by the NetManager when a connection has been established. + ConnectState *connectState = + (ConnectState *) NetDescriptor_getExtra(nd); + int err; + + if (connectState->alarm != NULL) { + Alarm_remove(connectState->alarm); + connectState->alarm = NULL; + } + + if (connectState->state == Connect_closed) { + // The connection attempt has been aborted. +#ifdef DEBUG + log_add(log_Debug, "Connection attempt was aborted."); +#endif + ConnectState_decRef(connectState); + return; + } + + if (Socket_getError(NetDescriptor_getSocket(nd), &err) == -1) { + log_add(log_Fatal, "Socket_getError() failed: %s.", + strerror(errno)); + explode(); + } + if (err != 0) { +#ifdef DEBUG + log_add(log_Debug, "connect() failed: %s.", strerror(err)); +#endif + NetDescriptor_close(nd); + connectState->nd = NULL; + connectState->infoPtr = connectState->infoPtr->ai_next; + connectHostNext(connectState); + return; + } + +#ifdef DEBUG + log_add(log_Debug, "Connection established."); +#endif + + // Notify the higher layer. + connectState->nd = NULL; + // The callback function takes over ownership of the + // NetDescriptor. + NetDescriptor_setWriteCallback(nd, NULL); + // Note that connectState->info and connectState->infoPtr are cleaned up + // when ConnectState_close() is called by the callback function. + + ConnectState_incRef(connectState); + doConnectCallback(connectState, nd, connectState->infoPtr->ai_addr, + connectState->infoPtr->ai_addrlen); + { + // The callback called should release the last reference to + // the connectState, by calling ConnectState_close(). + bool released = ConnectState_decRef(connectState); + assert(released); + (void) released; // In case assert() evaluates to nothing. + } +} + +static void +connectTimeoutCallback(ConnectState *connectState) { + connectState->alarm = NULL; + + NetDescriptor_close(connectState->nd); + connectState->nd = NULL; + + connectState->infoPtr = connectState->infoPtr->ai_next; + connectHostNext(connectState); +} + +static void +setConnectTimeout(ConnectState *connectState) { + assert(connectState->alarm == NULL); + + connectState->alarm = + Alarm_addRelativeMs(connectState->flags.timeout, + (AlarmCallback) connectTimeoutCallback, connectState); +} + +// Try connecting to the next address. +static Socket * +tryConnectHostNext(ConnectState *connectState) { + struct addrinfo *info; + Socket *sock; + int connectResult; + + assert(connectState->nd == NULL); + + info = connectState->infoPtr; + + sock = Socket_openNative(info->ai_family, info->ai_socktype, + info->ai_protocol); + if (sock == Socket_noSocket) { + int savedErrno = errno; + log_add(log_Error, "socket() failed: %s.", strerror(errno)); + errno = savedErrno; + return Socket_noSocket; + } + + if (Socket_setNonBlocking(sock) == -1) { + int savedErrno = errno; + log_add(log_Error, "Could not make socket non-blocking: %s.", + strerror(errno)); + errno = savedErrno; + return Socket_noSocket; + } + + (void) Socket_setReuseAddr(sock); + // Ignore errors; it's not a big deal. + (void) Socket_setInlineOOB(sock); + // Ignore errors; it's not a big deal as the other party is not + // not supposed to send any OOB data. + (void) Socket_setKeepAlive(sock); + // Ignore errors; it's not a big deal. + + connectResult = Socket_connect(sock, info->ai_addr, info->ai_addrlen); + if (connectResult == 0) { + // Connection has already succeeded. + // We just wait for the writability callback anyhow, so that + // we can use one code path. + return sock; + } + + switch (errno) { + case EINPROGRESS: + // Connection in progress; wait for the write callback. + return sock; + } + + // Connection failed immediately. This is just for one of the addresses, + // so this does not have to be final. + // Note that as the socket is non-blocking, most failed connection + // errors will usually not be reported immediately. + { + int savedErrno = errno; + Socket_close(sock); +#ifdef DEBUG + log_add(log_Debug, "connect() immediately failed for one address: " + "%s.", strerror(errno)); + // TODO: add the address in the status message. +#endif + errno = savedErrno; + } + return Socket_noSocket; +} + +static void +connectRetryCallback(ConnectState *connectState) { + connectState->alarm = NULL; + + connectState->infoPtr = connectState->info; + connectHostNext(connectState); +} + +static void +setConnectRetryAlarm(ConnectState *connectState) { + assert(connectState->alarm == NULL); + assert(connectState->flags.retryDelayMs != Connect_noRetry); + + connectState->alarm = + Alarm_addRelativeMs(connectState->flags.retryDelayMs, + (AlarmCallback) connectRetryCallback, connectState); +} + +static void +connectHostReportAllFailed(ConnectState *connectState) { + // Could not connect to any host. + ConnectError error; + freeaddrinfo(connectState->info); + connectState->info = NULL; + connectState->infoPtr = NULL; + connectState->state = Connect_closed; + error.state = Connect_connecting; + error.err = ETIMEDOUT; + // No errno code is exactly suitable. We have been unable + // to connect to any host, but the reasons may vary + // (unreachable, refused, ...). + // ETIMEDOUT is the least specific portable errno code that + // seems appropriate. + doConnectErrorCallback(connectState, &error); +} + +static void +connectHostNext(ConnectState *connectState) { + Socket *sock; + + while (connectState->infoPtr != NULL) { + sock = tryConnectHostNext(connectState); + + if (sock != Socket_noSocket) { + // Connection succeeded or connection in progress + connectState->nd = + NetDescriptor_new(sock, (void *) connectState); + if (connectState->nd == NULL) { + ConnectError error; + int savedErrno = errno; + + log_add(log_Error, "NetDescriptor_new() failed: %s.", + strerror(errno)); + Socket_close(sock); + freeaddrinfo(connectState->info); + connectState->info = NULL; + connectState->infoPtr = NULL; + connectState->state = Connect_closed; + error.state = Connect_connecting; + error.err = savedErrno; + doConnectErrorCallback(connectState, &error); + return; + } + + NetDescriptor_setWriteCallback(connectState->nd, connectCallback); + setConnectTimeout(connectState); + return; + } + + connectState->infoPtr = connectState->infoPtr->ai_next; + } + + // Connect failed to all addresses. + + if (connectState->flags.retryDelayMs == Connect_noRetry) { + connectHostReportAllFailed(connectState); + return; + } + + setConnectRetryAlarm(connectState); +} + +static void +connectHostResolveCallback(ResolveState *resolveState, + struct addrinfo *info) { + ConnectState *connectState = + (ConnectState *) ResolveState_getExtra(resolveState); + + connectState->state = Connect_connecting; + + Resolve_close(resolveState); + connectState->resolveState = NULL; + + if (connectState->flags.familyPrefer != PF_UNSPEC) { + // Reorganise the 'info' list to put the structures of the + // prefered family in front. + struct addrinfo *preferred; + struct addrinfo **preferredEnd; + struct addrinfo *rest; + struct addrinfo **restEnd; + splitAddrInfoOnFamily(info, connectState->flags.familyPrefer, + &preferred, &preferredEnd, &rest, &restEnd); + info = preferred; + *preferredEnd = rest; + } + + connectState->info = info; + connectState->infoPtr = info; + + connectHostNext(connectState); +} + +static void +connectHostResolveErrorCallback(ResolveState *resolveState, + const ResolveError *resolveError) { + ConnectState *connectState = + (ConnectState *) ResolveState_getExtra(resolveState); + ConnectError connectError; + + assert(resolveError->gaiRes != 0); + + Resolve_close(resolveState); + connectState->resolveState = NULL; + + connectError.state = Connect_resolving; + connectError.resolveError = resolveError; + connectError.err = resolveError->err; + doConnectErrorCallback(connectState, &connectError); +} + +ConnectState * +connectHostByName(const char *host, const char *service, Protocol proto, + const ConnectFlags *flags, ConnectConnectCallback connectCallback, + ConnectErrorCallback errorCallback, void *extra) { + struct addrinfo hints; + ConnectState *connectState; + ResolveFlags resolveFlags; + // Structure is empty (for now). + + assert(flags->familyDemand == PF_inet || + flags->familyDemand == PF_inet6 || + flags->familyDemand == PF_unspec); + assert(flags->familyPrefer == PF_inet || + flags->familyPrefer == PF_inet6 || + flags->familyPrefer == PF_unspec); + assert(proto == IPProto_tcp || proto == IPProto_udp); + + memset(&hints, '\0', sizeof hints); + hints.ai_family = protocolFamilyTranslation[flags->familyDemand]; + hints.ai_protocol = protocolTranslation[proto]; + + if (proto == IPProto_tcp) { + hints.ai_socktype = SOCK_STREAM; + } else { + assert(proto == IPProto_udp); + hints.ai_socktype = SOCK_DGRAM; + } + hints.ai_flags = 0; + + connectState = ConnectState_alloc(); + connectState->refCount = 1; +#ifdef DEBUG_CONNECT_REF + log_add(log_Debug, "ConnectState %08" PRIxPTR ": ref=1 (%d)", + (uintptr_t) connectState, connectState->refCount); +#endif + connectState->state = Connect_resolving; + connectState->flags = *flags; + connectState->connectCallback = connectCallback; + connectState->errorCallback = errorCallback; + connectState->extra = extra; + connectState->info = NULL; + connectState->infoPtr = NULL; + connectState->nd = NULL; + connectState->alarm = NULL; + + connectState->resolveState = getaddrinfoAsync( + host, service, &hints, &resolveFlags, + (ResolveCallback) connectHostResolveCallback, + (ResolveErrorCallback) connectHostResolveErrorCallback, + (ResolveCallbackArg) connectState); + + return connectState; +} + +// NB: The callback function becomes the owner of nd +static void +doConnectCallback(ConnectState *connectState, NetDescriptor *nd, + const struct sockaddr *addr, socklen_t addrLen) { + assert(connectState->connectCallback != NULL); + + ConnectState_incRef(connectState); + // No need to increment nd as the callback function takes over ownership. + (*connectState->connectCallback)(connectState, nd, addr, addrLen); + ConnectState_decRef(connectState); +} + +static void +doConnectErrorCallback(ConnectState *connectState, + const ConnectError *error) { + assert(connectState->errorCallback != NULL); + + ConnectState_incRef(connectState); + (*connectState->errorCallback)(connectState, error); + ConnectState_decRef(connectState); +} + + + diff --git a/src/libs/network/connect/connect.h b/src/libs/network/connect/connect.h new file mode 100644 index 0000000..77d7a44 --- /dev/null +++ b/src/libs/network/connect/connect.h @@ -0,0 +1,111 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef LIBS_NETWORK_CONNECT_CONNECT_H_ +#define LIBS_NETWORK_CONNECT_CONNECT_H_ + + +typedef struct ConnectFlags ConnectFlags; +typedef struct ConnectError ConnectError; +typedef struct ConnectState ConnectState; + +typedef enum { + Connect_closed, + Connect_resolving, + Connect_connecting +} ConnectStateState; + + +#include "../netmanager/netmanager.h" +#include "../socket/socket.h" +#include "resolve.h" + + +// For connectHost() +struct ConnectFlags { + ProtocolFamily familyDemand; + // Only accept a protocol family of this type. + // One of PF_inet, PF_inet6, or PF_unspec. + ProtocolFamily familyPrefer; + // Prefer a protocol family of this type. + // One of PF_inet, PF_inet6, or PF_unspec. + int timeout; + /* Number of milliseconds before timing out a connection attempt. + * Note that if a host has multiple addresses, a connect to that + * host will have this timeout *per address*. */ + int retryDelayMs; + /* Retry connecting this many ms after connecting to the last + * address for the specified host fails. Set to Connect_noRetry + * to give up after one try. */ +#define Connect_noRetry -1 +}; + +struct ConnectError { + ConnectStateState state; + // State where the error occured. + int err; + // errno value. Not relevant if state == resolving unless + // gaiRes == EAI_SYSTEM. + const ResolveError *resolveError; + // Only relevant if state == resolving. +}; + +typedef void (*ConnectConnectCallback)(ConnectState *connectState, + NetDescriptor *nd, const struct sockaddr *addr, socklen_t addrLen); +typedef void (*ConnectErrorCallback)(ConnectState *connectState, + const ConnectError *error); + +#ifdef CONNECT_INTERNAL + +#include "libs/alarm.h" + +struct ConnectState { + RefCount refCount; + + ConnectStateState state; + ConnectFlags flags; + + ConnectConnectCallback connectCallback; + ConnectErrorCallback errorCallback; + void *extra; + + struct addrinfo *info; + struct addrinfo *infoPtr; + + ResolveState *resolveState; + + NetDescriptor *nd; + Alarm *alarm; + // Used for both the timeout for a connection attempt + // and to retry after all addresses have been tried. +}; +#endif /* CONNECT_INTERNAL */ + +ConnectState *connectHostByName(const char *host, const char *service, + Protocol proto, const ConnectFlags *flags, + ConnectConnectCallback connectCallback, + ConnectErrorCallback errorCallback, void *extra); +void ConnectState_incRef(ConnectState *connectState); +bool ConnectState_decRef(ConnectState *connectState); +void ConnectState_close(ConnectState *connectState); +void ConnectState_setExtra(ConnectState *connectState, void *extra); +void *ConnectState_getExtra(ConnectState *connectState); + +#endif /* LIBS_NETWORK_CONNECT_CONNECT_H_ */ + + diff --git a/src/libs/network/connect/listen.c b/src/libs/network/connect/listen.c new file mode 100644 index 0000000..4a7a65c --- /dev/null +++ b/src/libs/network/connect/listen.c @@ -0,0 +1,456 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define PORT_WANT_ERRNO +#include "port.h" +#include "../netport.h" + +#define LISTEN_INTERNAL +#define SOCKET_INTERNAL +#include "listen.h" + +#include "resolve.h" +#include "../socket/socket.h" +#include "../netmanager/netmanager.h" +#include "libs/misc.h" +#include "libs/log.h" + +#include <assert.h> +#include <errno.h> +#ifdef USE_WINSOCK +# include <winsock2.h> +# include <ws2tcpip.h> +# include "../wspiapiwrap.h" +#else +# include <netdb.h> +#endif +#include <stdlib.h> +#include <string.h> + +#define DEBUG_LISTEN_REF +#ifdef DEBUG_LISTEN_REF +# include "types.h" +#endif + + +static void acceptCallback(NetDescriptor *nd); +static void doListenErrorCallback(ListenState *listenState, + const ListenError *error); + + +static ListenState * +ListenState_alloc(void) { + return (ListenState *) malloc(sizeof (ListenState)); +} + +static void +ListenState_free(ListenState *listenState) { + free(listenState); +} + +static void +ListenState_delete(ListenState *listenState) { + assert(listenState->nds == NULL); + ListenState_free(listenState); +} + +void +ListenState_incRef(ListenState *listenState) { + assert(listenState->refCount < REFCOUNT_MAX); + listenState->refCount++; +#ifdef DEBUG_LISTEN_REF + log_add(log_Debug, "ListenState %08" PRIxPTR ": ref++ (%d)", + (uintptr_t) listenState, listenState->refCount); +#endif +} + +bool +ListenState_decRef(ListenState *listenState) { + assert(listenState->refCount > 0); + listenState->refCount--; +#ifdef DEBUG_LISTEN_REF + log_add(log_Debug, "ListenState %08" PRIxPTR ": ref-- (%d)", + (uintptr_t) listenState, listenState->refCount); +#endif + if (listenState->refCount == 0) { + ListenState_delete(listenState); + return true; + } + return false; +} + +// Decrements ref count byh 1 +void +ListenState_close(ListenState *listenState) { + if (listenState->resolveState != NULL) { + Resolve_close(listenState->resolveState); + listenState->resolveState = NULL; + } + if (listenState->nds != NULL) { + while (listenState->numNd > 0) { + listenState->numNd--; + NetDescriptor_close(listenState->nds[listenState->numNd]); + } + free(listenState->nds); + listenState->nds = NULL; + } + listenState->state = Listen_closed; + ListenState_decRef(listenState); +} + +void +ListenState_setExtra(ListenState *listenState, void *extra) { + listenState->extra = extra; +} + +void * +ListenState_getExtra(ListenState *listenState) { + return listenState->extra; +} + +static NetDescriptor * +listenPortSingle(struct ListenState *listenState, struct addrinfo *info) { + Socket *sock; + int bindResult; + int listenResult; + NetDescriptor *nd; + + sock = Socket_openNative(info->ai_family, info->ai_socktype, + info->ai_protocol); + if (sock == Socket_noSocket) { + int savedErrno = errno; + log_add(log_Error, "socket() failed: %s.", strerror(errno)); + errno = savedErrno; + return NULL; + } + + (void) Socket_setReuseAddr(sock); + // Ignore errors; it's not a big deal. + if (Socket_setNonBlocking(sock) == -1) { + int savedErrno = errno; + // Error message is already printed. + Socket_close(sock); + errno = savedErrno; + return NULL; + } + + bindResult = Socket_bind(sock, info->ai_addr, info->ai_addrlen); + if (bindResult == -1) { + int savedErrno = errno; + if (errno == EADDRINUSE) { +#ifdef DEBUG + log_add(log_Warning, "bind() failed: %s.", strerror(errno)); +#endif + } else + log_add(log_Error, "bind() failed: %s.", strerror(errno)); + Socket_close(sock); + errno = savedErrno; + return NULL; + } + + listenResult = Socket_listen(sock, listenState->flags.backlog); + if (listenResult == -1) { + int savedErrno = errno; + log_add(log_Error, "listen() failed: %s.", strerror(errno)); + Socket_close(sock); + errno = savedErrno; + return NULL; + } + + nd = NetDescriptor_new(sock, (void *) listenState); + if (nd == NULL) { + int savedErrno = errno; + log_add(log_Error, "NetDescriptor_new() failed: %s.", + strerror(errno)); + Socket_close(sock); + errno = savedErrno; + return NULL; + } + + NetDescriptor_setReadCallback(nd, acceptCallback); + + return nd; +} + +static void +listenPortMulti(struct ListenState *listenState, struct addrinfo *info) { + struct addrinfo *addrPtr; + size_t addrCount; + size_t addrOkCount; + NetDescriptor **nds; + + // Count how many addresses we've got. + addrCount = 0; + for (addrPtr = info; addrPtr != NULL; addrPtr = addrPtr->ai_next) + addrCount++; + + // This is where we intend to store the file descriptors of the + // listening sockets. + nds = malloc(addrCount * sizeof listenState->nds[0]); + + // Bind to each address. + addrOkCount = 0; + for (addrPtr = info; addrPtr != NULL; addrPtr = addrPtr->ai_next) { + NetDescriptor *nd; + nd = listenPortSingle(listenState, addrPtr); + if (nd == NULL) { + // Failed. On to the next. + // An error message is already printed for serious errors. + // If the address is already in use, we here also print + // a message when we are not already listening on one of + // the other addresses. + // This is because on some IPv6 capabable systems (like Linux), + // IPv6 sockets also handle IPv4 connections, which means + // that a separate IPv4 socket won't be able to bind to the + // port. + // BUG: if the IPv4 socket is in the list before the + // IPv6 socket, it will be the IPv6 which will fail to bind, + // so only IPv4 connections will be handled, as v4 sockets can't + // accept v6 connections. + // In practice, on Linux, I haven't seen it happen, but + // it's a real possibility. + if (errno == EADDRINUSE && addrOkCount == 0) { + log_add(log_Error, "Error while preparing a network socket " + "for incoming connections: %s", strerror(errno)); + } + continue; + } + + nds[addrOkCount] = nd; + addrOkCount++; + } + + freeaddrinfo(info); + + listenState->nds = + realloc(nds, addrOkCount * sizeof listenState->nds[0]); + listenState->numNd = addrOkCount; + + if (addrOkCount == 0) { + // Could not listen on any port. + ListenError error; + error.state = Listen_listening; + error.err = EIO; + // Nothing better to offer. + doListenErrorCallback(listenState, &error); + return; + } +} + +static void +listenPortResolveCallback(ResolveState *resolveState, + struct addrinfo *result) { + ListenState *listenState = + (ListenState *) ResolveState_getExtra(resolveState); + Resolve_close(listenState->resolveState); + listenState->resolveState = NULL; + listenState->state = Listen_listening; + listenPortMulti(listenState, result); +} + +static void +listenPortResolveErrorCallback(ResolveState *resolveState, + const ResolveError *resolveError) { + ListenState *listenState = + (ListenState *) ResolveState_getExtra(resolveState); + ListenError listenError; + + assert(resolveError->gaiRes != 0); + + listenError.state = Listen_resolving; + listenError.resolveError = resolveError; + listenError.err = resolveError->err; + doListenErrorCallback(listenState, &listenError); +} + +// 'proto' is one of IPProto_tcp or IPProto_udp. +ListenState * +listenPort(const char *service, Protocol proto, const ListenFlags *flags, + ListenConnectCallback connectCallback, + ListenErrorCallback errorCallback, void *extra) { + struct addrinfo hints; + ListenState *listenState; + ResolveFlags resolveFlags; + // Structure is empty (for now). + + assert(flags->familyDemand == PF_inet || + flags->familyDemand == PF_inet6 || + flags->familyDemand == PF_unspec); + assert(flags->familyPrefer == PF_inet || + flags->familyPrefer == PF_inet6 || + flags->familyPrefer == PF_unspec); + assert(proto == IPProto_tcp || proto == IPProto_udp); + + // Acquire a list of addresses to bind to. + memset(&hints, '\0', sizeof hints); + hints.ai_family = protocolFamilyTranslation[flags->familyDemand]; + hints.ai_protocol = protocolTranslation[proto]; + + if (proto == IPProto_tcp) { + hints.ai_socktype = SOCK_STREAM; + } else { + assert(proto == IPProto_udp); + hints.ai_socktype = SOCK_DGRAM; + } + hints.ai_flags = AI_PASSIVE; + + listenState = ListenState_alloc(); + listenState->refCount = 1; +#ifdef DEBUG_LISTEN_REF + log_add(log_Debug, "ListenState %08" PRIxPTR ": ref=1 (%d)", + (uintptr_t) listenState, listenState->refCount); +#endif + listenState->state = Listen_resolving; + listenState->flags = *flags; + listenState->connectCallback = connectCallback; + listenState->errorCallback = errorCallback; + listenState->extra = extra; + listenState->nds = NULL; + listenState->numNd = 0; + + listenState->resolveState = getaddrinfoAsync(NULL, service, &hints, + &resolveFlags, listenPortResolveCallback, + listenPortResolveErrorCallback, + (ResolveCallbackArg) listenState); + + return listenState; +} + +// NB: The callback function becomes the owner of newNd. +static void +doListenConnectCallback(ListenState *listenState, NetDescriptor *listenNd, + NetDescriptor *newNd, + const struct sockaddr *addr, SOCKLEN_T addrLen) { + assert(listenState->connectCallback != NULL); + + ListenState_incRef(listenState); + // No need to increment listenNd, as there's guaranteed to be one + // reference from listenState. And no need to increment newNd, + // as the callback function takes over ownership. + (*listenState->connectCallback)(listenState, listenNd, newNd, + addr, (socklen_t) addrLen); + ListenState_decRef(listenState); +} + +static void +doListenErrorCallback(ListenState *listenState, const ListenError *error) { + assert(listenState->errorCallback != NULL); + + ListenState_incRef(listenState); + (*listenState->errorCallback)(listenState, error); + ListenState_decRef(listenState); +} + +static void +acceptSingleConnection(ListenState *listenState, NetDescriptor *nd) { + Socket *sock; + Socket *acceptResult; + struct sockaddr_storage addr; + socklen_t addrLen; + NetDescriptor *newNd; + + sock = NetDescriptor_getSocket(nd); + addrLen = sizeof (addr); + acceptResult = Socket_accept(sock, (struct sockaddr *) &addr, &addrLen); + if (acceptResult == Socket_noSocket) { + switch (errno) { + case EWOULDBLOCK: + case ECONNABORTED: + // Nothing serious. Keep listening. + return; + case EMFILE: + case ENFILE: + case ENOBUFS: + case ENOMEM: +#ifdef ENOSR + case ENOSR: +#endif + // Serious problems, but future connections may still + // be possible. + log_add(log_Warning, "accept() reported '%s'", + strerror(errno)); + return; + default: + // Should not happen. + log_add(log_Fatal, "Internal error: accept() reported " + "'%s'", strerror(errno)); + explode(); + } + } + + (void) Socket_setReuseAddr(acceptResult); + // Ignore errors; it's not a big deal. + if (Socket_setNonBlocking(acceptResult) == -1) { + int savedErrno = errno; + log_add(log_Error, "Could not make socket non-blocking: %s.", + strerror(errno)); + Socket_close(acceptResult); + errno = savedErrno; + return; + } + (void) Socket_setInlineOOB(acceptResult); + // Ignore errors; it's not a big deal as the other + // party is not not supposed to send any OOB data. + (void) Socket_setKeepAlive(sock); + // Ignore errors; it's not a big deal. + +#ifdef DEBUG + { + char hostname[NI_MAXHOST]; + int gniRes; + + gniRes = getnameinfo((struct sockaddr *) &addr, addrLen, + hostname, sizeof hostname, NULL, 0, 0); + if (gniRes != 0) { + log_add(log_Error, "Error while performing hostname " + "lookup for incoming connection: %s", + (gniRes == EAI_SYSTEM) ? strerror(errno) : + gai_strerror(gniRes)); + } else { + log_add(log_Debug, "Accepted incoming connection from '%s'.", + hostname); + } + } +#endif + + newNd = NetDescriptor_new(acceptResult, NULL); + if (newNd == NULL) { + int savedErrno = errno; + log_add(log_Error, "NetDescriptor_new() failed: %s.", + strerror(errno)); + Socket_close(acceptResult); + errno = savedErrno; + return; + } + + doListenConnectCallback(listenState, nd, newNd, + (struct sockaddr *) &addr, addrLen); + // NB: newNd is now handed over to the callback function, and should + // no longer be referenced from here. +} + +// Called when select() has indicated readability on a listening socket, +// i.e. when a connection is in the queue. +static void +acceptCallback(NetDescriptor *nd) { + ListenState *listenState = (ListenState *) NetDescriptor_getExtra(nd); + + acceptSingleConnection(listenState, nd); +} + + diff --git a/src/libs/network/connect/listen.h b/src/libs/network/connect/listen.h new file mode 100644 index 0000000..e44ef53 --- /dev/null +++ b/src/libs/network/connect/listen.h @@ -0,0 +1,106 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef LIBS_NETWORK_CONNECT_LISTEN_H_ +#define LIBS_NETWORK_CONNECT_LISTEN_H_ + +typedef struct ListenFlags ListenFlags; +typedef enum { + Listen_closed, + Listen_resolving, + Listen_listening +} ListenStateState; +typedef struct ListenError ListenError; +typedef struct ListenState ListenState; + +#include "port.h" + +#ifdef USE_WINSOCK +// I do not want to include winsock2.h, because of possible conflicts with +// code that includes this file. +// Note that listen.c itself includes winsock2.h; SOCKLEN_T is used there +// only where necessary to keep the API consistent. +# define SOCKLEN_T size_t +struct sockaddr; +#else +# include <netinet/in.h> +# define SOCKLEN_T socklen_t +#endif + +#include "resolve.h" +#include "../socket/socket.h" + +#include "../netmanager/netmanager.h" + +// For listenPort() +struct ListenFlags { + ProtocolFamily familyDemand; + // Only accept a protocol family of this type. + // One of PF_inet, PF_inet6, or PF_unspec. + ProtocolFamily familyPrefer; + // Prefer a protocol family of this type. + // One of PF_inet, PF_inet6, or PF_unspec. + int backlog; + // As the 2rd parameter to listen(); +}; + +struct ListenError { + ListenStateState state; + // State where the error occured. + int err; + // errno value. Not relevant if state == resolving unless + // gaiRes == EAI_SYSTEM. + const ResolveError *resolveError; + // Only relevant if state == resolving. +}; + +typedef void (*ListenConnectCallback)(ListenState *listenState, + NetDescriptor *listenNd, NetDescriptor *newNd, + const struct sockaddr *addr, SOCKLEN_T addrLen); +typedef void (*ListenErrorCallback)(ListenState *listenState, + const ListenError *error); + +#ifdef LISTEN_INTERNAL +struct ListenState { + RefCount refCount; + + ListenStateState state; + ListenFlags flags; + + ListenConnectCallback connectCallback; + ListenErrorCallback errorCallback; + void *extra; + + ResolveState *resolveState; + NetDescriptor **nds; + size_t numNd; + // INV: (numNd == NULL) == (nds == NULL) +}; +#endif /* defined(LISTEN_INTERNAL) */ + +ListenState *listenPort(const char *service, Protocol proto, + const ListenFlags *flags, ListenConnectCallback connectCallback, + ListenErrorCallback errorCallback, void *extra); +void ListenState_close(ListenState *listenState); +void ListenState_incRef(ListenState *listenState); +bool ListenState_decRef(ListenState *listenState); +void ListenState_setExtra(ListenState *listenState, void *extra); +void *ListenState_getExtra(ListenState *listenState); + +#endif /* LIBS_NETWORK_CONNECT_LISTEN_H_ */ + diff --git a/src/libs/network/connect/resolve.c b/src/libs/network/connect/resolve.c new file mode 100644 index 0000000..646e437 --- /dev/null +++ b/src/libs/network/connect/resolve.c @@ -0,0 +1,211 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define RESOLVE_INTERNAL +#include "resolve.h" + +#include <assert.h> +#include <errno.h> +#include <stdlib.h> + +#define DEBUG_RESOLVE_REF +#ifdef DEBUG_RESOLVE_REF +# include "types.h" +# include "libs/log.h" +# include <string.h> +#endif + +static ResolveState * +ResolveState_new(void) { + return (ResolveState *) malloc(sizeof (ResolveState)); +} + +static void +ResolveState_free(ResolveState *resolveState) { + free(resolveState); +} + +static void +ResolveState_delete(ResolveState *resolveState) { + assert(resolveState->callbackID == CallbackID_invalid); + ResolveState_free(resolveState); +} + +void +ResolveState_incRef(ResolveState *resolveState) { + assert(resolveState->refCount < REFCOUNT_MAX); + resolveState->refCount++; +#ifdef DEBUG_RESOLVE_REF + log_add(log_Debug, "ResolveState %08" PRIxPTR ": ref++ (%d)", + (uintptr_t) resolveState, resolveState->refCount); +#endif +} + +bool +ResolveState_decRef(ResolveState *resolveState) { + assert(resolveState->refCount > 0); + resolveState->refCount--; +#ifdef DEBUG_RESOLVE_REF + log_add(log_Debug, "ResolveState %08" PRIxPTR ": ref-- (%d)", + (uintptr_t) resolveState, resolveState->refCount); +#endif + if (resolveState->refCount == 0) { + ResolveState_delete(resolveState); + return true; + } + return false; +} + +void +ResolveState_setExtra(ResolveState *resolveState, void *extra) { + resolveState->extra = extra; +} + +void * +ResolveState_getExtra(ResolveState *resolveState) { + return resolveState->extra; +} + +static void +doResolveCallback(ResolveState *resolveState) { + ResolveState_incRef(resolveState); + (*resolveState->callback)(resolveState, resolveState->result); + { + bool released = ResolveState_decRef(resolveState); + assert(released); + (void) released; // In case assert() evaluates to nothing. + } +} + +static void +doResolveErrorCallback(ResolveState *resolveState) { + ResolveState_incRef(resolveState); + resolveState->errorCallback(resolveState, &resolveState->error); + { + bool released = ResolveState_decRef(resolveState); + assert(released); + (void) released; // In case assert() evaluates to nothing. + } +} + +static void +resolveCallback(ResolveState *resolveState) { + resolveState->callbackID = CallbackID_invalid; + if (resolveState->error.gaiRes == 0) { + // Successful lookup. + doResolveCallback(resolveState); + } else { + // Lookup failed. + doResolveErrorCallback(resolveState); + } +} + +// Function that does getaddrinfo() and calls the callback function when +// the result is available. +// TODO: actually make this function asynchronous. Right now it just calls +// getaddrinfo() (which blocks) and then schedules the callback. +ResolveState * +getaddrinfoAsync(const char *node, const char *service, + const struct addrinfo *hints, ResolveFlags *flags, + ResolveCallback callback, ResolveErrorCallback errorCallback, + ResolveCallbackArg extra) { + ResolveState *resolveState; + + resolveState = ResolveState_new(); + resolveState->refCount = 1; +#ifdef DEBUG_RESOLVE_REF + log_add(log_Debug, "ResolveState %08" PRIxPTR ": ref=1 (%d)", + (uintptr_t) resolveState, resolveState->refCount); +#endif + resolveState->state = Resolve_resolving; + resolveState->flags = *flags; + resolveState->callback = callback; + resolveState->errorCallback = errorCallback; + resolveState->extra = extra; + resolveState->result = NULL; + + resolveState->error.gaiRes = + getaddrinfo(node, service, hints, &resolveState->result); + resolveState->error.err = errno; + + resolveState->callbackID = Callback_add( + (CallbackFunction) resolveCallback, (CallbackArg) resolveState); + + return resolveState; +} + +void +Resolve_close(ResolveState *resolveState) { + if (resolveState->callbackID != CallbackID_invalid) { + Callback_remove(resolveState->callbackID); + resolveState->callbackID = CallbackID_invalid; + } + resolveState->state = Resolve_closed; + ResolveState_decRef(resolveState); +} + +// Split an addrinfo list into two separate lists, one with structures with +// the specified value for the ai_family field, the other with the other +// structures. The order of entries in the resulting lists will remain +// the same as in the original list. +// info - the original list +// family - the family for the first list +// selected - pointer to where the list of selected structures should be +// stored +// selectedEnd - pointer to where the end of 'selected' should be stored +// rest - pointer to where the list of not-selected structures should be +// stored +// restEnd - pointer to where the end of 'rest' should be stored +// +// Yes, it is allowed to modify the ai_next field of addrinfo structures. +// Or at least, it's not disallowed by RFC 3493, and the following +// text from that RFC seems to suggest it should be possible: +// ] The freeaddrinfo() function must support the freeing of arbitrary +// ] sublists of an addrinfo list originally returned by getaddrinfo(). +void +splitAddrInfoOnFamily(struct addrinfo *info, int family, + struct addrinfo **selected, struct addrinfo ***selectedEnd, + struct addrinfo **rest, struct addrinfo ***restEnd) { + struct addrinfo *selectedFirst; + struct addrinfo **selectedNext; + struct addrinfo *restFirst; + struct addrinfo **restNext; + + selectedNext = &selectedFirst; + restNext = &restFirst; + while (info != NULL) { + if (info->ai_family == family) { + *selectedNext = info; + selectedNext = &(*selectedNext)->ai_next; + } else { + *restNext = info; + restNext = &(*restNext)->ai_next; + } + info = info->ai_next; + } + *selectedNext = NULL; + *restNext = NULL; + + // Fill in the result parameters. + *selected = selectedFirst; + *selectedEnd = selectedNext; + *rest = restFirst; + *restEnd = restNext; +} + + diff --git a/src/libs/network/connect/resolve.h b/src/libs/network/connect/resolve.h new file mode 100644 index 0000000..509c3b9 --- /dev/null +++ b/src/libs/network/connect/resolve.h @@ -0,0 +1,109 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef LIBS_NETWORK_CONNECT_RESOLVE_H_ +#define LIBS_NETWORK_CONNECT_RESOLVE_H_ + + +typedef struct ResolveFlags ResolveFlags; +typedef struct ResolveError ResolveError; +typedef enum { + Resolve_closed, + Resolve_resolving +} ResolveStateState; +typedef struct ResolveState ResolveState; + +#include "port.h" +#include "../netport.h" + +// For addrinfo. +#ifdef USE_WINSOCK +// Not including <winsock2.h> because of possible conflicts with files +// including this file. +struct addrinfo; +#else +# include <netdb.h> +#endif + +#include "libs/callback.h" +#include "../netmanager/netmanager.h" + + +struct ResolveFlags { + // Nothing yet. + + int dummy; // empty struct declarations are not allowed by C'99. +}; + +struct ResolveError { + int gaiRes; + int err; + // errno value. Only relevant if gaiRes == EAI_SYSTEM. +}; + +typedef void *ResolveCallbackArg; +typedef void (*ResolveCallback)(ResolveState *resolveState, + struct addrinfo *result); + // The receiver of the callback is owner of 'result' and + // should call freeaddrinfo(). +typedef void (*ResolveErrorCallback)(ResolveState *resolveState, + const ResolveError *error); + +#ifdef RESOLVE_INTERNAL +#ifdef USE_WINSOCK +# include <winsock2.h> +# include <ws2tcpip.h> +# include "../wspiapiwrap.h" +#else /* !defined(USE_WINSOCK) */ +# include <netdb.h> +#endif /* !defined(USE_WINSOCK) */ + +struct ResolveState { + RefCount refCount; + + ResolveStateState state; + ResolveFlags flags; + + ResolveCallback callback; + ResolveErrorCallback errorCallback; + void *extra; + + CallbackID callbackID; + ResolveError error; + struct addrinfo *result; +}; +#endif /* RESOLVE_INTERNAL */ + + +void ResolveState_incRef(ResolveState *resolveState); +bool ResolveState_decRef(ResolveState *resolveState); +void ResolveState_setExtra(ResolveState *resolveState, void *extra); +void *ResolveState_getExtra(ResolveState *resolveState); +ResolveState *getaddrinfoAsync(const char *node, const char *service, + const struct addrinfo *hints, ResolveFlags *flags, + ResolveCallback callback, ResolveErrorCallback errorCallback, + ResolveCallbackArg extra); +void Resolve_close(ResolveState *resolveState); + +void splitAddrInfoOnFamily(struct addrinfo *info, int family, + struct addrinfo **selected, struct addrinfo ***selectedEnd, + struct addrinfo **rest, struct addrinfo ***restEnd); + + +#endif /* LIBS_NETWORK_CONNECT_RESOLVE_H_ */ + diff --git a/src/libs/network/netmanager/Makeinfo b/src/libs/network/netmanager/Makeinfo new file mode 100644 index 0000000..ddf28b3 --- /dev/null +++ b/src/libs/network/netmanager/Makeinfo @@ -0,0 +1,11 @@ +uqm_CFILES="ndesc.c" +uqm_HFILES="ndesc.h netmanager.h" + +if [ -n "$uqm_USE_WINSOCK" ]; then + uqm_CFILES="$uqm_CFILES netmanager_win.c" + uqm_HFILES="$uqm_HFILES netmanager_win.h" +else + uqm_CFILES="$uqm_CFILES netmanager_bsd.c" + uqm_HFILES="$uqm_HFILES netmanager_bsd.h" +fi + diff --git a/src/libs/network/netmanager/ndesc.c b/src/libs/network/netmanager/ndesc.c new file mode 100644 index 0000000..e407ff9 --- /dev/null +++ b/src/libs/network/netmanager/ndesc.c @@ -0,0 +1,211 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define NETDESCRIPTOR_INTERNAL +#include "ndesc.h" + +#include "netmanager.h" +#include "libs/callback.h" + +#include <assert.h> +#include <errno.h> +#include <stdlib.h> + +#undef DEBUG_NETDESCRIPTOR_REF +#ifdef DEBUG_NETDESCRIPTOR_REF +# include "libs/log.h" +# include <inttypes.h> +#endif + + +static NetDescriptor * +NetDescriptor_alloc(void) { + return malloc(sizeof (NetDescriptor)); +} + +static void +NetDescriptor_free(NetDescriptor *nd) { + free(nd); +} + +// Sets the ref count to 1. +NetDescriptor * +NetDescriptor_new(Socket *socket, void *extra) { + NetDescriptor *nd; + + nd = NetDescriptor_alloc(); + nd->refCount = 1; +#ifdef DEBUG_NETDESCRIPTOR_REF + log_add(log_Debug, "NetDescriptor %08" PRIxPTR ": ref=1 (%d)", + (uintptr_t) nd, nd->refCount); +#endif + + nd->flags.closed = false; + nd->readCallback = NULL; + nd->writeCallback = NULL; + nd->exceptionCallback = NULL; + nd->closeCallback = NULL; + nd->socket = socket; + nd->smd = NULL; + nd->extra = extra; + + if (NetManager_addDesc(nd) == -1) { + int savedErrno = errno; + NetDescriptor_free(nd); + errno = savedErrno; + return NULL; + } + + return nd; +} + +static void +NetDescriptor_delete(NetDescriptor *nd) { + assert(nd->socket == Socket_noSocket); + assert(nd->smd == NULL); + + NetDescriptor_free(nd); +} + +// Called from the callback handler. +static void +NetDescriptor_closeCallback(NetDescriptor *nd) { + if (nd->closeCallback != NULL) { + // The check is necessary because the close callback may have + // been removed before it is triggered. + (*nd->closeCallback)(nd); + } + NetDescriptor_decRef(nd); +} + +void +NetDescriptor_close(NetDescriptor *nd) { + assert(!nd->flags.closed); + assert(nd->socket != Socket_noSocket); + + NetManager_removeDesc(nd); + (void) Socket_close(nd->socket); + nd->socket = Socket_noSocket; + nd->flags.closed = true; + if (nd->closeCallback != NULL) { + // Keep one reference around until the close callback has been + // called. + (void) Callback_add( + (CallbackFunction) NetDescriptor_closeCallback, + (CallbackArg) nd); + } else + NetDescriptor_decRef(nd); +} + +void +NetDescriptor_incRef(NetDescriptor *nd) { + assert(nd->refCount < REFCOUNT_MAX); + nd->refCount++; +#ifdef DEBUG_NETDESCRIPTOR_REF + log_add(log_Debug, "NetDescriptor %08" PRIxPTR ": ref++ (%d)", + (uintptr_t) nd, nd->refCount); +#endif +} + +// returns true iff the ref counter has reached 0. +bool +NetDescriptor_decRef(NetDescriptor *nd) { + assert(nd->refCount > 0); + nd->refCount--; +#ifdef DEBUG_NETDESCRIPTOR_REF + log_add(log_Debug, "NetDescriptor %08" PRIxPTR ": ref-- (%d)", + (uintptr_t) nd, nd->refCount); +#endif + if (nd->refCount == 0) { + NetDescriptor_delete(nd); + return true; + } + return false; +} + +// The socket will no longer be managed by the NetManager. +void +NetDescriptor_detach(NetDescriptor *nd) { + NetManager_removeDesc(nd); + nd->socket = Socket_noSocket; + nd->flags.closed = true; + NetDescriptor_decRef(nd); +} + +Socket * +NetDescriptor_getSocket(NetDescriptor *nd) { + return nd->socket; +} + +void +NetDescriptor_setExtra(NetDescriptor *nd, void *extra) { + nd->extra = extra; +} + +void * +NetDescriptor_getExtra(const NetDescriptor *nd) { + return nd->extra; +} + +void +NetDescriptor_setReadCallback(NetDescriptor *nd, + NetDescriptor_ReadCallback callback) { + nd->readCallback = callback; + if (!nd->flags.closed) { + if (nd->readCallback != NULL) { + NetManager_activateReadCallback(nd); + } else + NetManager_deactivateReadCallback(nd); + } +} + +void +NetDescriptor_setWriteCallback(NetDescriptor *nd, + NetDescriptor_WriteCallback callback) { + nd->writeCallback = callback; + if (!nd->flags.closed) { + if (nd->writeCallback != NULL) { + NetManager_activateWriteCallback(nd); + } else + NetManager_deactivateWriteCallback(nd); + } +} + +void +NetDescriptor_setExceptionCallback(NetDescriptor *nd, + NetDescriptor_ExceptionCallback callback) { + nd->exceptionCallback = callback; + if (!nd->flags.closed) { + if (nd->exceptionCallback != NULL) { + NetManager_activateExceptionCallback(nd); + } else + NetManager_deactivateExceptionCallback(nd); + } +} + +// The close callback is called as a result of a socket being closed, either +// because of a local command or a remote disconnect. +// The close callback will only be scheduled when this happens. The +// callback will not be called until the Callback_process() is called. +void +NetDescriptor_setCloseCallback(NetDescriptor *nd, + NetDescriptor_CloseCallback callback) { + nd->closeCallback = callback; +} + + diff --git a/src/libs/network/netmanager/ndesc.h b/src/libs/network/netmanager/ndesc.h new file mode 100644 index 0000000..8834db9 --- /dev/null +++ b/src/libs/network/netmanager/ndesc.h @@ -0,0 +1,82 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef LIBS_NETWORK_NETMANAGER_NDESC_H_ +#define LIBS_NETWORK_NETMANAGER_NDESC_H_ + +#include "types.h" + + +typedef struct NetDescriptor NetDescriptor; + +typedef void (*NetDescriptor_ReadCallback)(NetDescriptor *nd); +typedef void (*NetDescriptor_WriteCallback)(NetDescriptor *nd); +typedef void (*NetDescriptor_ExceptionCallback)(NetDescriptor *nd); +typedef void (*NetDescriptor_CloseCallback)(NetDescriptor *nd); + +typedef uint32 RefCount; +#define REFCOUNT_MAX UINT32_MAX + +#include "../socket/socket.h" +#include "netmanager.h" + +#ifdef NETDESCRIPTOR_INTERNAL +// All created NetDescriptors are registered to the NetManager. +// They are unregisted when the NetDescriptor is closed. +// On creation the ref count is set to 1. On close it is decremented by 1. +struct NetDescriptor { + struct { + bool closed: 1; + } flags; + + RefCount refCount; + + NetDescriptor_ReadCallback readCallback; + NetDescriptor_WriteCallback writeCallback; + NetDescriptor_ExceptionCallback exceptionCallback; + NetDescriptor_CloseCallback closeCallback; + + Socket *socket; + SocketManagementData *smd; + + // Extra state-dependant information for the user. + void *extra; +}; +#endif + +NetDescriptor *NetDescriptor_new(Socket *socket, void *extra); +void NetDescriptor_close(NetDescriptor *nd); +void NetDescriptor_incRef(NetDescriptor *nd); +bool NetDescriptor_decRef(NetDescriptor *nd); +void NetDescriptor_detach(NetDescriptor *nd); +Socket *NetDescriptor_getSocket(NetDescriptor *nd); +void NetDescriptor_setExtra(NetDescriptor *nd, void *extra); +void *NetDescriptor_getExtra(const NetDescriptor *nd); +void NetDescriptor_setReadCallback(NetDescriptor *nd, + NetDescriptor_ReadCallback callback); +void NetDescriptor_setWriteCallback(NetDescriptor *nd, + NetDescriptor_WriteCallback callback); +void NetDescriptor_setExceptionCallback(NetDescriptor *nd, + NetDescriptor_ExceptionCallback callback); +void NetDescriptor_setCloseCallback(NetDescriptor *nd, + NetDescriptor_CloseCallback callback); + + +#endif /* LIBS_NETWORK_NETMANAGER_NDESC_H_ */ + + diff --git a/src/libs/network/netmanager/ndindex.ci b/src/libs/network/netmanager/ndindex.ci new file mode 100644 index 0000000..f922d8b --- /dev/null +++ b/src/libs/network/netmanager/ndindex.ci @@ -0,0 +1,103 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// This file is part of netmanager_bsd.c, from where it is #included. +// Only used for BSD sockets. + +// This file provides a mapping of Sockets to NetDescriptors. + + +static NetDescriptor *netDescriptors[FD_SETSIZE]; + // INV: flags.closed is not set for entries in netDescriptors. +static size_t maxND; + // One past the largest used ND in netDescriptors, as used in + // the first argument of select(); + + +static inline void +NDIndex_init(void) { + size_t i; + size_t numND = sizeof (netDescriptors) / sizeof (netDescriptors[0]); + + for (i = 0; i < numND; i++) + netDescriptors[i] = NULL; +} + +static inline void +NDIndex_uninit(void) { + // Nothing to do. +} + +static inline int +NDIndex_registerNDWithSocket(Socket *sock, NetDescriptor *nd) { + if ((unsigned int) sock->fd >= FD_SETSIZE) { + errno = EMFILE; + return -1; + } + + netDescriptors[sock->fd] = nd; + + if ((size_t) sock->fd >= maxND) + maxND = (size_t) sock->fd + 1; + + return 0; +} + +static inline void +NDIndex_unregisterNDForSocket(Socket *sock) { + NetDescriptor **last; + + netDescriptors[sock->fd] = NULL; + + last = &netDescriptors[sock->fd]; + + if ((size_t) sock->fd + 1 == maxND) { + do { + maxND--; + if (last == &netDescriptors[0]) + break; + last--; + } while (*last == NULL); + } +} + +static inline NetDescriptor * +NDIndex_getNDForSocket(Socket *sock) { + assert((size_t) sock->fd < maxND); + return netDescriptors[sock->fd]; +} + +static inline NetDescriptor * +NDIndex_getNDForSocketFd(int fd) { + assert((size_t) fd < maxND); + return netDescriptors[fd]; +} + +static inline bool +NDIndex_socketRegistered(Socket *sock) { + return ((size_t) sock->fd < maxND) + && (NDIndex_getNDForSocket(sock) != NULL); +} + +// Get the first argument to be used in select(). +static inline size_t +NDIndex_getSelectNumND(void) { + return maxND; +} + + diff --git a/src/libs/network/netmanager/netmanager.h b/src/libs/network/netmanager/netmanager.h new file mode 100644 index 0000000..42be572 --- /dev/null +++ b/src/libs/network/netmanager/netmanager.h @@ -0,0 +1,48 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef LIBS_NETWORK_NETMANAGER_NETMANAGER_H_ +#define LIBS_NETWORK_NETMANAGER_NETMANAGER_H_ + +#include "port.h" +#include "types.h" + +#ifdef USE_WINSOCK +# include "netmanager_win.h" +#else +# include "netmanager_bsd.h" +#endif + +#include "ndesc.h" + +void NetManager_init(void); +void NetManager_uninit(void); +int NetManager_process(uint32 *timeoutMs); + +// Only for internal use by the NetManager: +int NetManager_addDesc(NetDescriptor *nd); +void NetManager_removeDesc(NetDescriptor *nd); +void NetManager_activateReadCallback(NetDescriptor *nd); +void NetManager_deactivateReadCallback(NetDescriptor *nd); +void NetManager_activateWriteCallback(NetDescriptor *nd); +void NetManager_deactivateWriteCallback(NetDescriptor *nd); +void NetManager_activateExceptionCallback(NetDescriptor *nd); +void NetManager_deactivateExceptionCallback(NetDescriptor *nd); + +#endif /* LIBS_NETWORK_NETMANAGER_NETMANAGER_H_ */ + diff --git a/src/libs/network/netmanager/netmanager_bsd.c b/src/libs/network/netmanager/netmanager_bsd.c new file mode 100644 index 0000000..29159f8 --- /dev/null +++ b/src/libs/network/netmanager/netmanager_bsd.c @@ -0,0 +1,223 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define SOCKET_INTERNAL +#define NETDESCRIPTOR_INTERNAL +#include "netmanager_bsd.h" +#include "ndesc.h" +#include "../socket/socket.h" + +#include "ndesc.h" +#include "types.h" +#include "libs/log.h" + +#include <assert.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +#include "netmanager_common.ci" +#include "ndindex.ci" + + +// INV: The following sets only contain sockets present in the netDescriptor +// array. +static fd_set readSet; +static fd_set writeSet; +static fd_set exceptionSet; + + +void +NetManager_init(void) { + NDIndex_init(); + + FD_ZERO(&readSet); + FD_ZERO(&writeSet); + FD_ZERO(&exceptionSet); +} + +void +NetManager_uninit(void) { + NDIndex_uninit(); +} + +// Register the NetDescriptor with the NetManager. +int +NetManager_addDesc(NetDescriptor *nd) { + int fd; + assert(nd->socket != Socket_noSocket); + assert(!NDIndex_socketRegistered(nd->socket)); + + if (NDIndex_registerNDWithSocket(nd->socket, nd) == -1) { + // errno is set + return -1; + } + + fd = nd->socket->fd; + if (nd->readCallback != NULL) + FD_SET(fd, &readSet); + if (nd->writeCallback != NULL) + FD_SET(fd, &writeSet); + if (nd->exceptionCallback != NULL) + FD_SET(fd, &exceptionSet); + return 0; +} + +void +NetManager_removeDesc(NetDescriptor *nd) { + int fd; + + assert(nd->socket != Socket_noSocket); + assert(NDIndex_getNDForSocket(nd->socket) == nd); + + fd = nd->socket->fd; + FD_CLR(fd, &readSet); + FD_CLR(fd, &writeSet); + FD_CLR(fd, &exceptionSet); + + NDIndex_unregisterNDForSocket(nd->socket); +} + +void +NetManager_activateReadCallback(NetDescriptor *nd) { + FD_SET(nd->socket->fd, &readSet); +} + +void +NetManager_deactivateReadCallback(NetDescriptor *nd) { + FD_CLR(nd->socket->fd, &readSet); +} + +void +NetManager_activateWriteCallback(NetDescriptor *nd) { + FD_SET(nd->socket->fd, &writeSet); +} + +void +NetManager_deactivateWriteCallback(NetDescriptor *nd) { + FD_CLR(nd->socket->fd, &writeSet); +} + +void +NetManager_activateExceptionCallback(NetDescriptor *nd) { + FD_SET(nd->socket->fd, &exceptionSet); +} + +void +NetManager_deactivateExceptionCallback(NetDescriptor *nd) { + FD_CLR(nd->socket->fd, &exceptionSet); +} + +// This function may be called again from inside a callback function +// triggered by this function. BUG: This may result in callbacks being +// called multiple times. +// This function should however not be called from multiple threads at once. +int +NetManager_process(uint32 *timeoutMs) { + struct timeval timeout; + size_t i; + int selectResult; + fd_set newReadSet; + fd_set newWriteSet; + fd_set newExceptionSet; + bool bitSet; + + timeout.tv_sec = *timeoutMs / 1000; + timeout.tv_usec = (*timeoutMs % 1000) * 1000; + + // Structure assignment: + newReadSet = readSet; + newWriteSet = writeSet; + newExceptionSet = exceptionSet; + + do { + selectResult = select(NDIndex_getSelectNumND(), + &newReadSet, &newWriteSet, &newExceptionSet, &timeout); + // BUG: If select() is restarted because of EINTR, the timeout + // may start over. (Linux changes 'timeout' to the time left, + // but most other platforms don't.) + } while (selectResult == -1 && errno == EINTR); + if (selectResult == -1) { + int savedErrno = errno; + log_add(log_Error, "select() failed: %s.", strerror(errno)); + errno = savedErrno; + *timeoutMs = (timeout.tv_sec * 1000) + (timeout.tv_usec / 1000); + // XXX: rounding microseconds down. Is that the correct + // thing to do? + return -1; + } + + for (i = 0; i < maxND; i++) { + NetDescriptor *nd; + + if (selectResult == 0) { + // No more bits set in the fd_sets + break; + } + + nd = NDIndex_getNDForSocketFd(i); + if (nd == NULL) + continue; + + bitSet = false; + // Is one of the bits in the fd_sets set? + + // A callback may cause a NetDescriptor to be closed. The deletion + // of the structure will be scheduled, but will still be + // available at least until this function returns. + + if (FD_ISSET(i, &newExceptionSet)) + { + bool closed; + bitSet = true; + closed = NetManager_doExceptionCallback(nd); + if (closed) + goto next; + } + + if (FD_ISSET(i, &newWriteSet)) + { + bool closed; + bitSet = true; + closed = NetManager_doWriteCallback(nd); + if (closed) + goto next; + } + + if (FD_ISSET(i, &newReadSet)) + { + bool closed; + bitSet = true; + closed = NetManager_doReadCallback(nd); + if (closed) + goto next; + } + +next: + if (bitSet) + selectResult--; + } + + *timeoutMs = (timeout.tv_sec * 1000) + (timeout.tv_usec / 1000); + // XXX: rounding microseconds down. Is that the correct + // thing to do? + return 0; +} + + + diff --git a/src/libs/network/netmanager/netmanager_bsd.h b/src/libs/network/netmanager/netmanager_bsd.h new file mode 100644 index 0000000..b94dd69 --- /dev/null +++ b/src/libs/network/netmanager/netmanager_bsd.h @@ -0,0 +1,26 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef LIBS_NETWORK_NETMANAGER_NETMANAGER_BSD_H_ +#define LIBS_NETWORK_NETMANAGER_NETMANAGER_BSD_H_ + +typedef struct SocketManagementDataBsd SocketManagementDataBsd; +typedef SocketManagementDataBsd SocketManagementData; + +#endif /* LIBS_NETWORK_NETMANAGER_NETMANAGER_BSD_H_ */ + diff --git a/src/libs/network/netmanager/netmanager_common.ci b/src/libs/network/netmanager/netmanager_common.ci new file mode 100644 index 0000000..058e739 --- /dev/null +++ b/src/libs/network/netmanager/netmanager_common.ci @@ -0,0 +1,58 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// This file is part of netmanager_bsd.ci and netmanager_win.ci, +// from where it is #included. + +static bool +NetManager_doReadCallback(NetDescriptor *nd) { + bool closed; + + assert(nd->readCallback != NULL); + NetDescriptor_incRef(nd); + (*nd->readCallback)(nd); + closed = nd->flags.closed; + NetDescriptor_decRef(nd); + return closed; +} + +static bool +NetManager_doWriteCallback(NetDescriptor *nd) { + bool closed; + + assert(nd->writeCallback != NULL); + NetDescriptor_incRef(nd); + (*nd->writeCallback)(nd); + closed = nd->flags.closed; + NetDescriptor_decRef(nd); + return closed; +} + +static bool +NetManager_doExceptionCallback(NetDescriptor *nd) { + bool closed; + + assert(nd->exceptionCallback != NULL); + NetDescriptor_incRef(nd); + (*nd->exceptionCallback)(nd); + closed = nd->flags.closed; + NetDescriptor_decRef(nd); + return closed; +} + + diff --git a/src/libs/network/netmanager/netmanager_win.c b/src/libs/network/netmanager/netmanager_win.c new file mode 100644 index 0000000..f146732 --- /dev/null +++ b/src/libs/network/netmanager/netmanager_win.c @@ -0,0 +1,464 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define PORT_WANT_ERRNO +#include "port.h" +#include "../netport.h" + +#define NETMANAGER_INTERNAL +#define NETDESCRIPTOR_INTERNAL +#define SOCKET_INTERNAL +#include "netmanager_win.h" +#include "../socket/socket.h" + +#include "ndesc.h" +#include "types.h" +#include "libs/misc.h" +#include "libs/log.h" + +#include <assert.h> +#include <winsock2.h> + +#include "netmanager_common.ci" + +int closeWSAEvent(WSAEVENT event); + + +// The elements of the following arrays with the same index belong to +// eachother. +#define MAX_SOCKETS WSA_MAXIMUM_WAIT_EVENTS + // We cannot have more sockets than we can have events, as + // all may need an event at some point. +static NetDescriptor *netDescriptors[MAX_SOCKETS]; +static WSAEVENT events[WSA_MAXIMUM_WAIT_EVENTS]; +static size_t numSockets; +static size_t numActiveEvents; +// For each NetDescriptor registered with the NetManager, an event +// is created. Only the first numActiveEvents will be processed though. +// Inv: numActiveEvents <= numSockets +// Inv: for all i: 0 <= i < numActiveEvents: events[i] is active +// (events[i] being active also means netDescriptor[i]->smd->eventMask +// != 0) +// Inv: for all i: 0 <= i < numSockets: netDescriptor[i]->smd->index == i + +void +NetManager_init(void) { + numActiveEvents = 0; +} + +void +NetManager_uninit(void) { + assert(numActiveEvents == 0); +} + +static inline SocketManagementDataWin * +SocketManagementData_alloc(void) { + return malloc(sizeof (SocketManagementDataWin)); +} + +static inline void +SocketManagementData_free(SocketManagementDataWin *smd) { + free(smd); +} + +// XXX: This function should be moved to some file with generic network +// functions. +int +closeWSAEvent(WSAEVENT event) { + for (;;) { + int error; + + if (WSACloseEvent(event)) + break; + + error = WSAGetLastError(); + if (error != WSAEINPROGRESS) { + log_add(log_Error, + "WSACloseEvent() failed with error code %d.", error); + errno = winsockErrorToErrno(error); + return -1; + } + } + return 0; +} + +// Register the NetDescriptor with the NetManager. +int +NetManager_addDesc(NetDescriptor *nd) { + long eventMask = 0; + WSAEVENT event; + + if (numSockets >= WSA_MAXIMUM_WAIT_EVENTS) { + errno = EMFILE; + return -1; + } + + if (nd->readCallback != NULL) + eventMask |= FD_READ | FD_ACCEPT; + + if (nd->writeCallback != NULL) + eventMask |= FD_WRITE /* | FD_CONNECT */; + + if (nd->exceptionCallback != NULL) + eventMask |= FD_OOB; + + eventMask |= FD_CLOSE; + + event = WSACreateEvent(); + if (event == WSA_INVALID_EVENT) { + errno = getWinsockErrno(); + return -1; + } + + nd->smd = SocketManagementData_alloc(); + if (eventMask != 0) { + // XXX: This guard is now always true, because of FD_CLOSE. + // This means that numActiveEvents will always be equal to + // numEvents. + // Once I'm convinced this is the right way to go, + // I can remove some unnecessary code. + if (WSAEventSelect(nd->socket->sock, event, eventMask) == + SOCKET_ERROR) { + int savedErrno = getWinsockErrno(); + int closeStatus = closeWSAEvent(event); + if (closeStatus == -1) { + log_add(log_Fatal, "closeWSAEvent() failed: %s.", + strerror(errno)); + explode(); + } + SocketManagementData_free(nd->smd); + errno = savedErrno; + return -1; + } + + // Move existing socket for which there exists no event, so + // so that all sockets for which there exists an event are at + // the front of the array of netdescriptors. + if (numActiveEvents < numSockets) { + netDescriptors[numSockets] = netDescriptors[numActiveEvents]; + netDescriptors[numSockets]->smd->index = numSockets; + } + + nd->smd->index = numActiveEvents; + numActiveEvents++; + } else { + nd->smd->index = numSockets; + } + nd->smd->eventMask = eventMask; + + netDescriptors[nd->smd->index] = nd; + events[nd->smd->index] = event; + numSockets++; + + return 0; +} + +void +NetManager_removeDesc(NetDescriptor *nd) { + assert(nd->smd != NULL); + assert(nd->smd->index < numSockets); + assert(nd == netDescriptors[nd->smd->index]); + + { + int closeStatus = closeWSAEvent(events[nd->smd->index]); + if (closeStatus == -1) + explode(); + } + + if (nd->smd->index < numActiveEvents) { + size_t index = nd->smd->index; + if (index + 1 != numActiveEvents) { + // Keep the list of active events consecutive by filling + // the new hole with the last active event. + events[index] = events[numActiveEvents - 1]; + netDescriptors[index] = netDescriptors[numActiveEvents - 1]; + netDescriptors[index]->smd->index = index; + } + numActiveEvents--; + } + + SocketManagementData_free(nd->smd); + nd->smd = NULL; + + numSockets--; +} + +static void +swapSockets(int index1, int index2) { + NetDescriptor *tempNd; + WSAEVENT tempEvent; + + tempNd = netDescriptors[index2]; + tempEvent = events[index2]; + + netDescriptors[index2] = netDescriptors[index1]; + events[index2] = events[index1]; + netDescriptors[index2]->smd->index = index2; + + netDescriptors[index1] = tempNd; + events[index1] = tempEvent; + netDescriptors[index1]->smd->index = index1; +} + +static int +NetManager_updateEvent(NetDescriptor *nd) { + assert(nd == netDescriptors[nd->smd->index]); + + if (WSAEventSelect(nd->socket->sock, + events[nd->smd->index], nd->smd->eventMask) == SOCKET_ERROR) { + int savedErrno = getWinsockErrno(); + int closeStatus = closeWSAEvent(events[nd->smd->index]); + if (closeStatus == -1) { + log_add(log_Fatal, "closeWSAEvent() failed: %s.", + strerror(errno)); + explode(); + } + errno = savedErrno; + return -1; + } + + if (nd->smd->eventMask != 0) { + // There are some events that we are interested in. + if (nd->smd->index >= numActiveEvents) { + // Event was not yet active. + if (nd->smd->index != numActiveEvents) { + // Need to keep the active nds and events in the front of + // their arrays. + swapSockets(nd->smd->index, numActiveEvents); + } + numActiveEvents++; + } + } else { + // There are no events that we are interested in. + if (nd->smd->index < numActiveEvents) { + // Event was active. + if (nd->smd->index != numActiveEvents - 1) { + // Need to keep the active nds and events in the front of + // their arrays. + swapSockets(nd->smd->index, numActiveEvents - 1); + } + } + numActiveEvents--; + } + + return 0; +} + +static void +activateSomeCallback(NetDescriptor *nd, long eventMask) { + nd->smd->eventMask |= eventMask; + { + int status = NetManager_updateEvent(nd); + if (status == -1) { + log_add(log_Fatal, "NetManager_updateEvent() failed: %s.", + strerror(errno)); + explode(); + // TODO: better error handling. + } + } +} + +static void +deactivateSomeCallback(NetDescriptor *nd, long eventMask) { + nd->smd->eventMask &= ~eventMask; + { + int status = NetManager_updateEvent(nd); + if (status == -1) { + log_add(log_Fatal, "NetManager_updateEvent() failed: %s.", + strerror(errno)); + explode(); + // TODO: better error handling + } + } +} + +void +NetManager_activateReadCallback(NetDescriptor *nd) { + activateSomeCallback(nd, FD_READ | FD_ACCEPT); +} + +void +NetManager_deactivateReadCallback(NetDescriptor *nd) { + deactivateSomeCallback(nd, FD_READ | FD_ACCEPT); +} + +void +NetManager_activateWriteCallback(NetDescriptor *nd) { + activateSomeCallback(nd, FD_WRITE /* | FD_CONNECT */); +} + +void +NetManager_deactivateWriteCallback(NetDescriptor *nd) { + deactivateSomeCallback(nd, FD_WRITE /* | FD_CONNECT */); +} + +void +NetManager_activateExceptionCallback(NetDescriptor *nd) { + activateSomeCallback(nd, FD_OOB); +} + +void +NetManager_deactivateExceptionCallback(NetDescriptor *nd) { + deactivateSomeCallback(nd, FD_OOB); +} + +static inline int +NetManager_processEvent(size_t index) { + WSANETWORKEVENTS networkEvents; + int enumRes; + + enumRes = WSAEnumNetworkEvents(netDescriptors[index]->socket->sock, + events[index], &networkEvents); + if (enumRes == SOCKET_ERROR) { + errno = getWinsockErrno(); + return -1; + } + + if (networkEvents.lNetworkEvents & FD_READ) { + bool closed; + if (networkEvents.iErrorCode[FD_READ_BIT] != 0) { + // No special handling is required; the callback function + // will try to do a recv() and will get the error then. + } + + closed = NetManager_doReadCallback(netDescriptors[index]); + if (closed) + goto closed; + } + if (networkEvents.lNetworkEvents & FD_WRITE) { + bool closed; + if (networkEvents.iErrorCode[FD_WRITE_BIT] != 0) { + // No special handling is required; the callback function + // will try to do a send() and will get the error then. + } + + closed = NetManager_doWriteCallback(netDescriptors[index]); + if (closed) + goto closed; + } + if (networkEvents.lNetworkEvents & FD_OOB) { + bool closed; + if (networkEvents.iErrorCode[FD_OOB_BIT] != 0) { + // No special handling is required; the callback function + // will get the error then when it tries to do a recv(). + } + + closed = NetManager_doExceptionCallback(netDescriptors[index]); + if (closed) + goto closed; + } + if (networkEvents.lNetworkEvents & FD_ACCEPT) { + // There is no specific accept callback (because the BSD sockets + // don't work with specific notification for accept); we use + // the read callback instead. + bool closed; + if (networkEvents.iErrorCode[FD_READ_BIT] != 0) { + // No special handling is required; the callback function + // will try to do an accept() and will get the error then. + } + + closed = NetManager_doReadCallback(netDescriptors[index]); + if (closed) + goto closed; + } +#if 0 + // No need for this. Windows also sets FD_WRITE in this case, and + // writability is what we check for anyhow. + if (networkEvents.lNetworkEvents & FD_CONNECT) { + // There is no specific connect callback (because the BSD sockets + // don't work with specific notification for connect); we use + // the write callback instead. + bool closed; + if (networkEvents.iErrorCode[FD_WRITE_BIT] != 0) { + // No special handling is required; the callback function + // should do getsockopt() with SO_ERROR to get the error. + } + + closed = NetManager_doWriteCallback(netDescriptors[index]); + if (closed) + goto closed; + } +#endif + if (networkEvents.lNetworkEvents & FD_CLOSE) { + // The close event is handled last, in case there was still + // data in the buffers which could be processed. + NetDescriptor_close(netDescriptors[index]); + goto closed; + } + +closed: /* No special actions required for now. */ + + return 0; +} + +// This function may be called again from inside a callback function +// triggered by this function. BUG: This may result in callbacks being +// called multiple times. +// This function should however not be called from multiple threads at once. +int +NetManager_process(uint32 *timeoutMs) { + DWORD timeoutTemp; + DWORD waitResult; + DWORD startEvent; + + timeoutTemp = (DWORD) *timeoutMs; + + // WSAWaitForMultipleEvents only reports events for one socket at a + // time. In order to have each socket checked once, we call it + // again after it has reported an event, but passing only the + // events not yet processed. The second time, the timeout will be set + // to 0, so it won't wait. + startEvent = 0; + while (startEvent < numActiveEvents) { + waitResult = WSAWaitForMultipleEvents(numActiveEvents - startEvent, + &events[startEvent], FALSE, timeoutTemp, FALSE); + + if (waitResult == WSA_WAIT_IO_COMPLETION) + continue; + + if (waitResult == WSA_WAIT_TIMEOUT) { + // No events waiting. + *timeoutMs = 0; + return 0; + } + + if (waitResult == WSA_WAIT_FAILED) { + errno = getWinsockErrno(); + *timeoutMs = timeoutTemp; + return -1; + } + + { + DWORD eventIndex = waitResult - WSA_WAIT_EVENT_0; + if (NetManager_processEvent((size_t) eventIndex) == -1) { + // errno is set + *timeoutMs = timeoutTemp; + return -1; + } + + // Check the rest of the sockets, but don't wait anymore. + startEvent += eventIndex + 1; + timeoutTemp = 0; + } + } + + *timeoutMs = timeoutTemp; + return 0; +} + + diff --git a/src/libs/network/netmanager/netmanager_win.h b/src/libs/network/netmanager/netmanager_win.h new file mode 100644 index 0000000..03d65e4 --- /dev/null +++ b/src/libs/network/netmanager/netmanager_win.h @@ -0,0 +1,35 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef LIBS_NETWORK_NETMANAGER_NETMANAGER_WIN_H_ +#define LIBS_NETWORK_NETMANAGER_NETMANAGER_WIN_H_ + +typedef struct SocketManagementDataWin SocketManagementDataWin; +typedef SocketManagementDataWin SocketManagementData; + +#ifdef NETMANAGER_INTERNAL +struct SocketManagementDataWin { + size_t index; + long eventMask; +}; +#endif /* NETMANAGER_INTERNAL */ + + +#endif /* LIBS_NETWORK_NETMANAGER_NETMANAGER_WIN_H_ */ + + diff --git a/src/libs/network/netport.c b/src/libs/network/netport.c new file mode 100644 index 0000000..bfd478c --- /dev/null +++ b/src/libs/network/netport.c @@ -0,0 +1,91 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define PORT_WANT_ERRNO +#include "port.h" +#include "netport.h" + +#ifdef USE_WINSOCK +# include <winsock2.h> + +int +winsockErrorToErrno(int winsockError) { + switch (winsockError) { + case WSAEINTR: return EINTR; + case WSAEACCES: return EACCES; + case WSAEFAULT: return EFAULT; + case WSAEINVAL: return EINVAL; + case WSAEMFILE: return EMFILE; + case WSAEWOULDBLOCK: return EWOULDBLOCK; + case WSAEINPROGRESS: return EINPROGRESS; + case WSAEALREADY: return EALREADY; + case WSAENOTSOCK: return ENOTSOCK; + case WSAEDESTADDRREQ: return EDESTADDRREQ; + case WSAEMSGSIZE: return EMSGSIZE; + case WSAEPROTOTYPE: return EPROTOTYPE; + case WSAENOPROTOOPT: return ENOPROTOOPT; + case WSAEPROTONOSUPPORT: return EPROTONOSUPPORT; + case WSAESOCKTNOSUPPORT: return ESOCKTNOSUPPORT; + case WSAEOPNOTSUPP: return EOPNOTSUPP; + case WSAEPFNOSUPPORT: return EPFNOSUPPORT; + case WSAEAFNOSUPPORT: return EAFNOSUPPORT; + case WSAEADDRINUSE: return EADDRINUSE; + case WSAEADDRNOTAVAIL: return EADDRNOTAVAIL; + case WSAENETDOWN: return ENETDOWN; + case WSAENETUNREACH: return ENETUNREACH; + case WSAENETRESET: return ENETRESET; + case WSAECONNABORTED: return ECONNABORTED; + case WSAECONNRESET: return ECONNRESET; + case WSAENOBUFS: return ENOBUFS; + case WSAEISCONN: return EISCONN; + case WSAENOTCONN: return ENOTCONN; + case WSAESHUTDOWN: return ESHUTDOWN; + case WSAETIMEDOUT: return ETIMEDOUT; + case WSAECONNREFUSED: return ECONNREFUSED; + case WSAEHOSTDOWN: return EHOSTDOWN; + case WSAEHOSTUNREACH: return EHOSTUNREACH; + case WSAEPROCLIM: return EPROCLIM; + case WSASYSNOTREADY: return ENOSYS; + case WSAVERNOTSUPPORTED: return ENOSYS; + case WSANOTINITIALISED: return ENOSYS; + case WSAEDISCON: return ECONNRESET; + case WSATYPE_NOT_FOUND: return ENODATA; + case WSAHOST_NOT_FOUND: return ENODATA; + case WSATRY_AGAIN: return EAGAIN; + case WSANO_RECOVERY: return EIO; + case WSANO_DATA: return ENODATA; + case WSA_INVALID_HANDLE: return EBADF; + case WSA_INVALID_PARAMETER: return EINVAL; + case WSA_IO_INCOMPLETE: return EAGAIN; + case WSA_IO_PENDING: return EINPROGRESS; + case WSA_NOT_ENOUGH_MEMORY: return ENOMEM; + case WSA_OPERATION_ABORTED: return EINTR; + case WSAEINVALIDPROCTABLE: return ENOSYS; + case WSAEINVALIDPROVIDER: return ENOSYS; + case WSAEPROVIDERFAILEDINIT: return ENOSYS; + case WSASYSCALLFAILURE: return EIO; + default: return EIO; + } +} + +int +getWinsockErrno(void) { + return winsockErrorToErrno(WSAGetLastError()); +} +#endif /* defined (USE_WINSOCK) */ + diff --git a/src/libs/network/netport.h b/src/libs/network/netport.h new file mode 100644 index 0000000..ee99699 --- /dev/null +++ b/src/libs/network/netport.h @@ -0,0 +1,43 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef LIBS_NETWORK_NETPORT_H_ +#define LIBS_NETWORK_NETPORT_H_ + +#include "port.h" + +#ifdef USE_WINSOCK +int winsockErrorToErrno(int winsockError); +int getWinsockErrno(void); +# define EAI_SYSTEM 0x02000001 + // Any value will do that doesn't conflict with an existing value. + +#ifdef __MINGW32__ +// MinGW does not have a working gai_strerror() yet. +static inline const char * +gai_strerror(int err) { + (void) err; + return "[gai_strerror() is not available on MinGW]"; +} +#endif /* defined(__MINGW32__) */ + +#endif + +#endif /* LIBS_NETWORK_NETPORT_H_ */ + + diff --git a/src/libs/network/network.h b/src/libs/network/network.h new file mode 100644 index 0000000..d8c596e --- /dev/null +++ b/src/libs/network/network.h @@ -0,0 +1,27 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef LIBS_NETWORK_NETWORK_H_ +#define LIBS_NETWORK_NETWORK_H_ + +void Network_init(void); +void Network_uninit(void); + +#endif /* LIBS_NETWORK_NETWORK_H_ */ + + diff --git a/src/libs/network/network_bsd.c b/src/libs/network/network_bsd.c new file mode 100644 index 0000000..a213c4e --- /dev/null +++ b/src/libs/network/network_bsd.c @@ -0,0 +1,30 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "network.h" + +void +Network_init(void) { + // Nothing to do. +} + +void +Network_uninit(void) { + // Nothing to do. +} + diff --git a/src/libs/network/network_win.c b/src/libs/network/network_win.c new file mode 100644 index 0000000..a5c6abf --- /dev/null +++ b/src/libs/network/network_win.c @@ -0,0 +1,75 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "netport.h" + +#include "network.h" + +#include "libs/misc.h" +#include "libs/log.h" + +#include <errno.h> +#include <winsock2.h> + +void +Network_init(void) { + WSADATA data; + int startupResult; + WORD requestVersion = MAKEWORD(2, 2); + + startupResult = WSAStartup(requestVersion, &data); + if (startupResult != 0) { + int savedErrno = winsockErrorToErrno(startupResult); + log_add(log_Fatal, "WSAStartup failed."); + errno = savedErrno; + explode(); + } + +#ifdef DEBUG + log_add(log_Debug, "Winsock version %d.%d found: \"%s\".", + LOBYTE(data.wHighVersion), HIBYTE(data.wHighVersion), + data.szDescription); + log_add(log_Debug, "Requesting to use Winsock version %d.%d, got " + "version %d.%d.", + LOBYTE(requestVersion), HIBYTE(requestVersion), + LOBYTE(data.wVersion), HIBYTE(data.wVersion)); +#endif + if (data.wVersion != requestVersion) { + log_add(log_Fatal, "Winsock version %d.%d presented, requested " + "%d.%d.", LOBYTE(data.wVersion), HIBYTE(data.wVersion), + LOBYTE(requestVersion), HIBYTE(requestVersion)); + (void) WSACleanup(); + // Ignoring errors; we're going to abort anyhow. + explode(); + } +} + +void +Network_uninit(void) { + int cleanupResult; + + cleanupResult = WSACleanup(); + if (cleanupResult == SOCKET_ERROR) { + int savedErrno = getWinsockErrno(); + log_add(log_Fatal, "WSACleanup failed."); + errno = savedErrno; + explode(); + } +} + + diff --git a/src/libs/network/socket/Makeinfo b/src/libs/network/socket/Makeinfo new file mode 100644 index 0000000..19e1637 --- /dev/null +++ b/src/libs/network/socket/Makeinfo @@ -0,0 +1,11 @@ +uqm_CFILES="socket.c" +uqm_HFILES="socket.h" + +if [ -n "$uqm_USE_WINSOCK" ]; then + uqm_CFILES="$uqm_CFILES socket_win.c" + uqm_HFILES="$uqm_HFILES socket_win.h" +else + uqm_CFILES="$uqm_CFILES socket_bsd.c" + uqm_HFILES="$uqm_CFILES socket_bsd.h" +fi + diff --git a/src/libs/network/socket/socket.c b/src/libs/network/socket/socket.c new file mode 100644 index 0000000..be7d601 --- /dev/null +++ b/src/libs/network/socket/socket.c @@ -0,0 +1,61 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "port.h" + +#define SOCKET_INTERNAL +#include "socket.h" + +#ifdef USE_WINSOCK +# include <winsock2.h> +#else +# include <sys/socket.h> +# include <netinet/in.h> +#endif + + +//////////////////////////////////////////////////////////////////////////// + + +const int protocolFamilyTranslation[] = { + /* .[PF_unspec] = */ PF_UNSPEC, + /* .[PF_inet] = */ PF_INET, + /* .[PF_inet6] = */ PF_INET6, +}; + +const int protocolTranslation[] = { + /* .[IPProto_tcp] = */ IPPROTO_TCP, + /* .[IPProto_udp] = */ IPPROTO_UDP, +}; + +const int socketTypeTranslation[] = { + /* .[Sock_stream] = */ SOCK_STREAM, + /* .[Sock_dgram] = */ SOCK_DGRAM, +}; + + +Socket * +Socket_open(ProtocolFamily domain, SocketType type, Protocol protocol) { + return Socket_openNative(protocolFamilyTranslation[domain], + socketTypeTranslation[type], protocolTranslation[protocol]); +} + + +//////////////////////////////////////////////////////////////////////////// + + diff --git a/src/libs/network/socket/socket.h b/src/libs/network/socket/socket.h new file mode 100644 index 0000000..5c75467 --- /dev/null +++ b/src/libs/network/socket/socket.h @@ -0,0 +1,99 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef LIBS_NETWORK_SOCKET_SOCKET_H_ +#define LIBS_NETWORK_SOCKET_SOCKET_H_ + +typedef struct Socket Socket; +#define Socket_noSocket ((Socket *) NULL) + +#include "port.h" + +#ifdef USE_WINSOCK +# include "socket_win.h" +#else +# include "socket_bsd.h" +#endif + + +//////////////////////////////////////////////////////////////////////////// + + +// Defining our own types for protocol families and protocols instead of +// using the system defines, so that the layer using this API does not have +// to have anything to do with the system layer. + +typedef enum { + PF_unspec, + PF_inet, + PF_inet6, +} ProtocolFamily; +typedef ProtocolFamily AddressFamily; + +typedef enum { + IPProto_tcp, + IPProto_udp, +} Protocol; + +typedef enum { + Sock_stream, + Sock_dgram, +} SocketType; + +#ifdef SOCKET_INTERNAL +extern const int protocolFamilyTranslation[]; +#define addressFamilyTranslation protocolFamilyTranslation; +extern const int protocolTranslation[]; +extern const int socketTypeTranslation[]; +#endif + + +//////////////////////////////////////////////////////////////////////////// + + +Socket *Socket_open(ProtocolFamily domain, SocketType type, + Protocol protocol); +#ifdef SOCKET_INTERNAL +Socket *Socket_openNative(int domain, int type, int protocol); +#endif +int Socket_close(Socket *sock); + +int Socket_connect(Socket *sock, const struct sockaddr *addr, + socklen_t addrLen); +int Socket_bind(Socket *sock, const struct sockaddr *addr, + socklen_t addrLen); +int Socket_listen(Socket *sock, int backlog); +Socket *Socket_accept(Socket *sock, struct sockaddr *addr, socklen_t *addrLen); +ssize_t Socket_send(Socket *sock, const void *buf, size_t len, int flags); +ssize_t Socket_sendto(Socket *sock, const void *buf, size_t len, int flags, + const struct sockaddr *addr, socklen_t addrLen); +ssize_t Socket_recv(Socket *sock, void *buf, size_t len, int flags); +ssize_t Socket_recvfrom(Socket *sock, void *buf, size_t len, int flags, + struct sockaddr *from, socklen_t *fromLen); + +int Socket_setNonBlocking(Socket *sock); +int Socket_setReuseAddr(Socket *sock); +int Socket_setNodelay(Socket *sock); +int Socket_setTOS(Socket *sock, int tos); +int Socket_setInteractive(Socket *sock); +int Socket_setInlineOOB(Socket *sock); +int Socket_setKeepAlive(Socket *sock); +int Socket_getError(Socket *sock, int *err); + +#endif /* LIBS_NETWORK_SOCKET_SOCKET_H_ */ + diff --git a/src/libs/network/socket/socket_bsd.c b/src/libs/network/socket/socket_bsd.c new file mode 100644 index 0000000..2bf25b8 --- /dev/null +++ b/src/libs/network/socket/socket_bsd.c @@ -0,0 +1,283 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// Socket functions for BSD sockets. + +#define SOCKET_INTERNAL +#include "socket.h" + +#include "libs/log.h" + +#include <assert.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) +# include <netinet/in_systm.h> +# include <netinet/in.h> +#endif +#include <netinet/ip.h> +#include <netinet/tcp.h> +#include <unistd.h> +#include <fcntl.h> + + +static Socket * +Socket_alloc(void) { + return malloc(sizeof (Socket)); +} + +static void +Socket_free(Socket *sock) { + free(sock); +} + +Socket * +Socket_openNative(int domain, int type, int protocol) { + Socket *result; + int fd; + + fd = socket(domain, type, protocol); + if (fd == -1) { + // errno is set + return Socket_noSocket; + } + + result = Socket_alloc(); + result->fd = fd; + return result; +} + +int +Socket_close(Socket *sock) { + int closeResult; + + do { + closeResult = close(sock->fd); + if (closeResult == 0) { + Socket_free(sock); + return 0; + } + } while (errno == EINTR); + + return -1; +} + +int +Socket_connect(Socket *sock, const struct sockaddr *addr, + socklen_t addrLen) { + int connectResult; + + do { + connectResult = connect(sock->fd, addr, addrLen); + } while (connectResult == -1 && errno == EINTR); + + return connectResult; +} + +int +Socket_bind(Socket *sock, const struct sockaddr *addr, socklen_t addrLen) { + return bind(sock->fd, addr, addrLen); +} + +int +Socket_listen(Socket *sock, int backlog) { + return listen(sock->fd, backlog); +} + +Socket * +Socket_accept(Socket *sock, struct sockaddr *addr, socklen_t *addrLen) { + int acceptResult; + socklen_t tempAddrLen; + + do { + tempAddrLen = *addrLen; + acceptResult = accept(sock->fd, addr, &tempAddrLen); + if (acceptResult != -1) { + Socket *result = Socket_alloc(); + result->fd = acceptResult; + *addrLen = tempAddrLen; + return result; + } + + } while (errno == EINTR); + + // errno is set + return Socket_noSocket; +} + +ssize_t +Socket_send(Socket *sock, const void *buf, size_t len, int flags) { + return send(sock->fd, buf, len, flags); +} + +ssize_t +Socket_sendto(Socket *sock, const void *buf, size_t len, int flags, + const struct sockaddr *addr, socklen_t addrLen) { + return sendto(sock->fd, buf, len, flags, addr, addrLen); +} + +ssize_t +Socket_recv(Socket *sock, void *buf, size_t len, int flags) { + return recv(sock->fd, buf, len, flags); +} + +ssize_t +Socket_recvfrom(Socket *sock, void *buf, size_t len, int flags, + struct sockaddr *from, socklen_t *fromLen) { + return recvfrom(sock->fd, buf, len, flags, from, fromLen); +} + +int +Socket_setNonBlocking(Socket *sock) { + int flags; + + flags = fcntl(sock->fd, F_GETFL); + if (flags == -1) { + int savedErrno = errno; + log_add(log_Error, "Getting file descriptor flags of socket failed: " + "%s.", strerror(errno)); + errno = savedErrno; + return -1; + } + + if (fcntl(sock->fd, F_SETFL, flags | O_NONBLOCK) == -1) { + int savedErrno = errno; + log_add(log_Error, "Setting non-blocking mode on socket failed: " + "%s.", strerror(errno)); + errno = savedErrno; + return -1; + } + + return 0; +} + +int +Socket_setReuseAddr(Socket *sock) { + int flag = 1; + + if (setsockopt(sock->fd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof flag) + == -1) { + int savedErrno = errno; + log_add(log_Error, "Setting socket reuse failed: %s.", + strerror(errno)); + errno = savedErrno; + return -1; + } + return 0; +} + +// Send data as soon as it is available. Do not collect data to send at +// once. +int +Socket_setNodelay(Socket *sock) { + int flag = 1; + + if (setsockopt(sock->fd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof flag) + == -1) { +#ifdef DEBUG + int savedErrno = errno; + log_add(log_Warning, "Disabling Nagle algorithm failed: %s.", + strerror(errno)); + errno = savedErrno; +#endif + return -1; + } + return 0; +} + +// 'tos' should be IPTOS_LOWDELAY, IPTOS_THROUGHPUT, IPTOS_THROUGHPUT, +// IPTOS_RELIABILITY, or IPTOS_MINCOST. +int +Socket_setTOS(Socket *sock, int tos) { + if (setsockopt(sock->fd, IPPROTO_IP, IP_TOS, &tos, sizeof tos) == -1) { +#ifdef DEBUG + int savedErrno = errno; + log_add(log_Warning, "Setting socket type-of-service failed: %s.", + strerror(errno)); + errno = savedErrno; +#endif + return -1; + } + return 0; +} + +// This function setups the socket for optimal configuration for an +// interactive connection. +int +Socket_setInteractive(Socket *sock) { + if (Socket_setNodelay(sock) == -1) { + // errno is set + return -1; + } + + if (Socket_setTOS(sock, IPTOS_LOWDELAY) == -1) { + // errno is set + return -1; + } + return 0; +} + +int +Socket_setInlineOOB(Socket *sock) { + int flag = 1; + + if (setsockopt(sock->fd, SOL_SOCKET, SO_OOBINLINE, &flag, sizeof flag) + == -1) { + int savedErrno = errno; + log_add(log_Error, "Setting inline OOB on socket failed: %s", + strerror(errno)); + errno = savedErrno; + return -1; + } + return 0; +} + +int +Socket_setKeepAlive(Socket *sock) { + int flag = 1; + + if (setsockopt(sock->fd, IPPROTO_TCP, SO_KEEPALIVE, &flag, sizeof flag) + == -1) { + int savedErrno = errno; + log_add(log_Error, "Setting keep-alive on socket failed: %s", + strerror(errno)); + errno = savedErrno; + return -1; + } + return 0; +} + +int +Socket_getError(Socket *sock, int *err) { + socklen_t errLen = sizeof(*err); + + if (getsockopt(sock->fd, SOL_SOCKET, SO_ERROR, err, &errLen) == -1) { + // errno is set + return -1; + } + + assert(errLen == sizeof(*err)); + // err is set + return 0; +} + + diff --git a/src/libs/network/socket/socket_bsd.h b/src/libs/network/socket/socket_bsd.h new file mode 100644 index 0000000..8019c01 --- /dev/null +++ b/src/libs/network/socket/socket_bsd.h @@ -0,0 +1,33 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef LIBS_NETWORK_SOCKET_SOCKET_BSD_H_ +#define LIBS_NETWORK_SOCKET_SOCKET_BSD_H_ + +#include "types.h" +#include <sys/socket.h> + +#ifdef SOCKET_INTERNAL +struct Socket { + int fd; +}; +#endif /* SOCKET_INTERNAL */ + + +#endif /* LIBS_NETWORK_SOCKET_SOCKET_BSD_H_ */ + diff --git a/src/libs/network/socket/socket_win.c b/src/libs/network/socket/socket_win.c new file mode 100644 index 0000000..e1ed002 --- /dev/null +++ b/src/libs/network/socket/socket_win.c @@ -0,0 +1,314 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// Socket functions for Winsock sockets. + +#define PORT_WANT_ERRNO +#include "port.h" +#include "../netport.h" + +#define SOCKET_INTERNAL +#include "socket.h" + +#include "libs/log.h" + +#include <assert.h> +#include <errno.h> +#include <winsock2.h> + + +Socket * +Socket_alloc(void) { + return malloc(sizeof (Socket)); +} + +void +Socket_free(Socket *sock) { + free(sock); +} + +Socket * +Socket_openNative(int domain, int type, int protocol) { + Socket *result; + SOCKET sock; + + sock = socket(domain, type, protocol); + if (sock == INVALID_SOCKET) { + errno = getWinsockErrno(); + return Socket_noSocket; + } + + result = Socket_alloc(); + result->sock = sock; + return result; +} + +int +Socket_close(Socket *sock) { + int closeResult; + + do { + closeResult = closesocket(sock->sock); + if (closeResult != SOCKET_ERROR) { + Socket_free(sock); + return 0; + } + + errno = getWinsockErrno(); + } while (errno == EINTR); + + return -1; +} + +int +Socket_connect(Socket *sock, const struct sockaddr *addr, + socklen_t addrLen) { + int connectResult; + + do { + connectResult = connect(sock->sock, addr, addrLen); + if (connectResult == 0) + return 0; + + errno = getWinsockErrno(); + } while (errno == EINTR); + + if (errno == EWOULDBLOCK) { + // Windows returns (WSA)EWOULDBLOCK when a connection is being + // initiated on a non-blocking socket, while other platforms + // use EINPROGRESS in such cases. + errno = EINPROGRESS; + } + + return -1; +} + +int +Socket_bind(Socket *sock, const struct sockaddr *addr, socklen_t addrLen) { + int bindResult; + + bindResult = bind(sock->sock, addr, addrLen); + if (bindResult == SOCKET_ERROR) { + errno = getWinsockErrno(); + return -1; + } + + return 0; +} + +int +Socket_listen(Socket *sock, int backlog) { + int listenResult; + + listenResult = listen(sock->sock, backlog); + if (listenResult == SOCKET_ERROR) { + errno = getWinsockErrno(); + return -1; + } + + return 0; +} + +Socket * +Socket_accept(Socket *sock, struct sockaddr *addr, socklen_t *addrLen) { + SOCKET acceptResult; + socklen_t tempAddrLen; + + do { + tempAddrLen = *addrLen; + acceptResult = accept(sock->sock, addr, &tempAddrLen); + if (acceptResult != INVALID_SOCKET) { + Socket *result = Socket_alloc(); + result->sock = acceptResult; + *addrLen = tempAddrLen; + return result; + } + + errno = getWinsockErrno(); + } while (errno == EINTR); + + // errno is set + return Socket_noSocket; +} + +ssize_t +Socket_send(Socket *sock, const void *buf, size_t len, int flags) { + int sendResult; + + sendResult = send(sock->sock, buf, len, flags); + if (sendResult == SOCKET_ERROR) { + errno = getWinsockErrno(); + return -1; + } + + return sendResult; +} + +ssize_t +Socket_sendto(Socket *sock, const void *buf, size_t len, int flags, + const struct sockaddr *addr, socklen_t addrLen) { + int sendResult; + + sendResult = sendto(sock->sock, buf, len, flags, addr, addrLen); + if (sendResult == SOCKET_ERROR) { + errno = getWinsockErrno(); + return -1; + } + + return sendResult; +} + +ssize_t +Socket_recv(Socket *sock, void *buf, size_t len, int flags) { + int recvResult; + + recvResult = recv(sock->sock, buf, len, flags); + if (recvResult == SOCKET_ERROR) { + errno = getWinsockErrno(); + return -1; + } + + return recvResult; +} + +ssize_t +Socket_recvfrom(Socket *sock, void *buf, size_t len, int flags, + struct sockaddr *from, socklen_t *fromLen) { + int recvResult; + + recvResult = recvfrom(sock->sock, buf, len, flags, from, fromLen); + if (recvResult == SOCKET_ERROR) { + errno = getWinsockErrno(); + return -1; + } + + return recvResult; +} + +int +Socket_setNonBlocking(Socket *sock) { + unsigned long flag = 1; + + if (ioctlsocket(sock->sock, FIONBIO, &flag) == SOCKET_ERROR) { + int savedErrno = getWinsockErrno(); + log_add(log_Error, "Setting non-block mode on socket failed: %s.", + strerror(errno)); + errno = savedErrno; + return -1; + } + return 0; +} + +int +Socket_setReuseAddr(Socket *sock) { + BOOL flag = 1; + + if (setsockopt(sock->sock, SOL_SOCKET, SO_REUSEADDR, + (const char *) &flag, sizeof flag) == SOCKET_ERROR) { + int savedErrno = getWinsockErrno(); + log_add(log_Error, "Setting socket reuse failed: %s.", + strerror(errno)); + errno = savedErrno; + return -1; + } + return 0; +} + +// Send data as soon as it is available. Do not collect data to send at +// once. +int +Socket_setNodelay(Socket *sock) { + BOOL flag = 1; + + if (setsockopt(sock->sock, IPPROTO_TCP, TCP_NODELAY, + (const char *) &flag, sizeof flag) == SOCKET_ERROR) { +#ifdef DEBUG + int savedErrno = getWinsockErrno(); + log_add(log_Warning, "Disabling Nagle algorithm failed: %s.", + strerror(errno)); + errno = savedErrno; +#endif + return -1; + } + return 0; +} + +// This function setups the socket for optimal configuration for an +// interactive connection. +int +Socket_setInteractive(Socket *sock) { + if (Socket_setNodelay(sock) == -1) { + // errno is set + return -1; + } + +#if 0 + if (Socket_setTOS(sock, IPTOS_LOWDELAY) == -1) { + // errno is set + return -1; + } +#endif + return 0; +} + +int +Socket_setInlineOOB(Socket *sock) { + BOOL flag = 1; + + if (setsockopt(sock->sock, SOL_SOCKET, SO_OOBINLINE, (const char *) &flag, + sizeof flag) == SOCKET_ERROR) { + int savedErrno = getWinsockErrno(); + log_add(log_Error, "Setting inline OOB on socket failed: %s", + strerror(errno)); + errno = savedErrno; + return -1; + } + return 0; +} + +int +Socket_setKeepAlive(Socket *sock) { + BOOL flag = 1; + + if (setsockopt(sock->sock, IPPROTO_TCP, SO_KEEPALIVE, + (const char *) &flag, sizeof flag) == SOCKET_ERROR) { + int savedErrno = getWinsockErrno(); + log_add(log_Error, "Setting keep-alive on socket failed: %s", + strerror(errno)); + errno = savedErrno; + return -1; + } + return 0; +} + +int +Socket_getError(Socket *sock, int *err) { + int errLen = sizeof(*err); + + if (getsockopt(sock->sock, SOL_SOCKET, SO_ERROR, (char *) err, &errLen) + == SOCKET_ERROR) { + errno = getWinsockErrno(); + return -1; + } + + assert(errLen == sizeof(*err)); + // err is set + return 0; +} + + diff --git a/src/libs/network/socket/socket_win.h b/src/libs/network/socket/socket_win.h new file mode 100644 index 0000000..ed1674c --- /dev/null +++ b/src/libs/network/socket/socket_win.h @@ -0,0 +1,34 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef LIBS_NETWORK_SOCKET_SOCKET_WIN_H_ +#define LIBS_NETWORK_SOCKET_SOCKET_WIN_H_ + +#ifdef SOCKET_INTERNAL +#include <winsock2.h> +struct Socket { + SOCKET sock; +}; +#endif /* SOCKET_INTERNAL */ + +typedef int socklen_t; +struct sockaddr; + +#endif /* LIBS_NETWORK_SOCKET_SOCKET_WIN_H_ */ + + diff --git a/src/libs/network/wspiapiwrap.c b/src/libs/network/wspiapiwrap.c new file mode 100644 index 0000000..2eddeb4 --- /dev/null +++ b/src/libs/network/wspiapiwrap.c @@ -0,0 +1,34 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// Only used for MinGW + +// HACK. MinGW misses some functionality, so we're #including +// the actual Windows wspiapi.h file. Because that file includes +// inline functions that aren't static, it can only be included +// once per executable when using gcc (for MSVC this is apparently +// not a problem), so this file is it. The prototypes of these +// functions are added to wspiapiwrap.h + +#include "netport.h" + +#if defined(USE_WINSOCK) && defined(__MINGW32__) +# include <ws2tcpip.h> +# include <wspiapi.h> +#endif + diff --git a/src/libs/network/wspiapiwrap.h b/src/libs/network/wspiapiwrap.h new file mode 100644 index 0000000..23361a4 --- /dev/null +++ b/src/libs/network/wspiapiwrap.h @@ -0,0 +1,33 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef LIBS_NETWORK_WSPIAPIWRAP_H_ +#define LIBS_NETWORK_WSPIAPIWRAP_H_ + +// HACK. See wspiapiwrap.c +# define getaddrinfo WspiapiGetAddrInfo +# define getnameinfo WspiapiGetNameInfo +# define freeaddrinfo WspiapiFreeAddrInfo +void WINAPI WspiapiFreeAddrInfo (struct addrinfo *ai); +int WINAPI WspiapiGetAddrInfo(const char *nodename, const char *servname, + const struct addrinfo *hints, struct addrinfo **res); +int WINAPI WspiapiGetNameInfo (const struct sockaddr *sa, socklen_t salen, + char *host, size_t hostlen, char *serv, size_t servlen, int flags); + +#endif /* LIBS_NETWORK_WSPIAPIWRAP_H_ */ + diff --git a/src/libs/platform.h b/src/libs/platform.h new file mode 100644 index 0000000..f17674d --- /dev/null +++ b/src/libs/platform.h @@ -0,0 +1,57 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef PLATFORM_H_ +#define PLATFORM_H_ + +#if defined(__cplusplus) +extern "C" { +#endif + +#if defined(USE_PLATFORM_ACCEL) +# if defined(__GNUC__) && (defined(i386) || defined(__x86_64__)) +# define MMX_ASM +# define GCC_ASM +# elif (_MSC_VER >= 1100) && defined(_M_IX86) +// All 32bit AMD chips are IX86 equivalents +// We do not enable MSVC/MMX_ASM for _M_AMD64 as of now +# define MMX_ASM +# define MSVC_ASM +# endif +// other realistic possibilities for MSVC compiler are +// _M_AMD64 (AMD x86-64), _M_IA64 (Intel Arch 64) +#endif + +typedef enum +{ + PLATFORM_NULL = 0, + PLATFORM_C, + PLATFORM_MMX, + PLATFORM_SSE, + PLATFORM_3DNOW, + PLATFORM_ALTIVEC, + + PLATFORM_LAST = PLATFORM_ALTIVEC + +} PLATFORM_TYPE; + +extern PLATFORM_TYPE force_platform; + +#if defined(__cplusplus) +} +#endif + +#endif /* PLATFORM_H_ */ diff --git a/src/libs/reslib.h b/src/libs/reslib.h new file mode 100644 index 0000000..0b56262 --- /dev/null +++ b/src/libs/reslib.h @@ -0,0 +1,140 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_RESLIB_H_ +#define LIBS_RESLIB_H_ + +//#include <stdio.h> +#include "libs/compiler.h" +#include "port.h" +#include "libs/memlib.h" +#include "libs/uio.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +typedef struct resource_index_desc RESOURCE_INDEX_DESC; +typedef RESOURCE_INDEX_DESC *RESOURCE_INDEX; + +typedef const char *RESOURCE; + +typedef union { + DWORD num; + void *ptr; + const char *str; +} RESOURCE_DATA; + +#define NULL_RESOURCE NULL + +extern const char *_cur_resfile_name; + +typedef void (ResourceLoadFun) (const char *pathname, RESOURCE_DATA *resdata); +typedef BOOLEAN (ResourceFreeFun) (void *handle); +typedef void (ResourceStringFun) (RESOURCE_DATA *handle, char *buf, unsigned int size); + +typedef void *(ResourceLoadFileFun) (uio_Stream *fp, DWORD len); + +void *LoadResourceFromPath(const char *pathname, ResourceLoadFileFun fn); + +uio_Stream *res_OpenResFile (uio_DirHandle *dir, const char *filename, const char *mode); +size_t ReadResFile (void *lpBuf, size_t size, size_t count, uio_Stream *fp); +size_t WriteResFile (const void *lpBuf, size_t size, size_t count, uio_Stream *fp); +int GetResFileChar (uio_Stream *fp); +int PutResFileChar (char ch, uio_Stream *fp); +int PutResFileNewline (uio_Stream *fp); +long SeekResFile (uio_Stream *fp, long offset, int whence); +long TellResFile (uio_Stream *fp); +size_t LengthResFile (uio_Stream *fp); +BOOLEAN res_CloseResFile (uio_Stream *fp); +BOOLEAN DeleteResFile (uio_DirHandle *dir, const char *filename); + +RESOURCE_INDEX InitResourceSystem (void); +void UninitResourceSystem (void); +BOOLEAN InstallResTypeVectors (const char *res_type, ResourceLoadFun *loadFun, ResourceFreeFun *freeFun, ResourceStringFun *stringFun); +void *res_GetResource (RESOURCE res); +void *res_DetachResource (RESOURCE res); +void res_FreeResource (RESOURCE res); +COUNT CountResourceTypes (void); +DWORD res_GetIntResource (RESOURCE res); +BOOLEAN res_GetBooleanResource (RESOURCE res); +const char *res_GetResourceType (RESOURCE res); + +void LoadResourceIndex (uio_DirHandle *dir, const char *filename, const char *prefix); +void SaveResourceIndex (uio_DirHandle *dir, const char *rmpfile, const char *root, BOOLEAN strip_root); + +void *GetResourceData (uio_Stream *fp, DWORD length); + +#define AllocResourceData HMalloc +BOOLEAN FreeResourceData (void *); + +#if defined(__cplusplus) +} +#endif + +#include "libs/strlib.h" +#include "libs/gfxlib.h" + +#if defined(__cplusplus) +extern "C" { +#endif + // For Color + +typedef STRING_TABLE DIRENTRY_REF; +typedef STRING DIRENTRY; + +extern DIRENTRY_REF LoadDirEntryTable (uio_DirHandle *dirHandle, + const char *path, const char *pattern, match_MatchType matchType); +#define CaptureDirEntryTable CaptureStringTable +#define ReleaseDirEntryTable ReleaseStringTable +#define DestroyDirEntryTable DestroyStringTable +#define GetDirEntryTableRef GetStringTable +#define GetDirEntryTableCount GetStringTableCount +#define GetDirEntryTableIndex GetStringTableIndex +#define SetAbsDirEntryTableIndex SetAbsStringTableIndex +#define SetRelDirEntryTableIndex SetRelStringTableIndex +#define GetDirEntryLength GetStringLengthBin +#define GetDirEntryAddress GetStringAddress + +/* Key-Value resources */ + +BOOLEAN res_HasKey (const char *key); + +BOOLEAN res_IsString (const char *key); +const char *res_GetString (const char *key); +void res_PutString (const char *key, const char *value); + +BOOLEAN res_IsInteger (const char *key); +int res_GetInteger (const char *key); +void res_PutInteger (const char *key, int value); + +BOOLEAN res_IsBoolean (const char *key); +BOOLEAN res_GetBoolean (const char *key); +void res_PutBoolean (const char *key, BOOLEAN value); + +BOOLEAN res_IsColor (const char *key); +Color res_GetColor (const char *key); +void res_PutColor (const char *key, Color value); + +BOOLEAN res_Remove (const char *key); + +#if defined(__cplusplus) +} +#endif + +#endif /* LIBS_RESLIB_H_ */ diff --git a/src/libs/resource/Makeinfo b/src/libs/resource/Makeinfo new file mode 100644 index 0000000..ddac8e2 --- /dev/null +++ b/src/libs/resource/Makeinfo @@ -0,0 +1,3 @@ +uqm_CFILES="direct.c filecntl.c getres.c loadres.c stringbank.c + propfile.c resinit.c" +uqm_HFILES="index.h propfile.h resintrn.h stringbank.h" diff --git a/src/libs/resource/direct.c b/src/libs/resource/direct.c new file mode 100644 index 0000000..b3d3541 --- /dev/null +++ b/src/libs/resource/direct.c @@ -0,0 +1,101 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "libs/strings/strintrn.h" +#include "libs/memlib.h" +#include "port.h" +#include "libs/uio.h" +#include <sys/stat.h> + +DIRENTRY_REF +LoadDirEntryTable (uio_DirHandle *dirHandle, const char *path, + const char *pattern, match_MatchType matchType) +{ + uio_DirList *dirList; + COUNT num_entries; + COUNT i; + uio_DirHandle *dir; + STRING_TABLE StringTable; + STRING_TABLE_DESC *lpST; + STRING lpLastString; + + dir = uio_openDirRelative (dirHandle, path, 0); + assert(dir != NULL); + dirList = uio_getDirList (dir, "", pattern, matchType); + assert(dirList != NULL); + num_entries = 0; + + // First, count the amount of space needed + for (i = 0; i < dirList->numNames; i++) + { + struct stat sb; + + if (dirList->names[i][0] == '.') + { + dirList->names[i] = NULL; + continue; + } + if (uio_stat (dir, dirList->names[i], &sb) == -1) + { + dirList->names[i] = NULL; + continue; + } + if (!S_ISREG (sb.st_mode)) + { + dirList->names[i] = NULL; + continue; + } + num_entries++; + } + uio_closeDir (dir); + + if (num_entries == 0) { + uio_DirList_free(dirList); + return ((DIRENTRY_REF) 0); + } + + StringTable = AllocStringTable (num_entries, 0); + lpST = StringTable; + if (lpST == 0) + { + FreeStringTable (StringTable); + uio_DirList_free(dirList); + return ((DIRENTRY_REF) 0); + } + lpST->size = num_entries; + lpLastString = lpST->strings; + + for (i = 0; i < dirList->numNames; i++) + { + int size; + STRINGPTR target; + if (dirList->names[i] == NULL) + continue; + size = strlen (dirList->names[i]) + 1; + target = HMalloc (size); + memcpy (target, dirList->names[i], size); + lpLastString->data = target; + lpLastString->length = size; + lpLastString++; + } + + uio_DirList_free(dirList); + return StringTable; +} + + diff --git a/src/libs/resource/filecntl.c b/src/libs/resource/filecntl.c new file mode 100644 index 0000000..e2a81d9 --- /dev/null +++ b/src/libs/resource/filecntl.c @@ -0,0 +1,146 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef WIN32 +#include <io.h> +#endif +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include "port.h" +#include "resintrn.h" +#include "libs/uio.h" + +uio_Stream * +res_OpenResFile (uio_DirHandle *dir, const char *filename, const char *mode) +{ + uio_Stream *fp; + struct stat sb; + + if (uio_stat (dir, filename, &sb) == 0 && S_ISDIR(sb.st_mode)) + return ((uio_Stream *) ~0); + + fp = uio_fopen (dir, filename, mode); + + return (fp); +} + +BOOLEAN +res_CloseResFile (uio_Stream *fp) +{ + if (fp) + { + if (fp != (uio_Stream *)~0) + uio_fclose (fp); + return (TRUE); + } + + return (FALSE); +} + +BOOLEAN +DeleteResFile (uio_DirHandle *dir, const char *filename) +{ + return (uio_unlink (dir, filename) == 0); +} + +size_t +ReadResFile (void *lpBuf, size_t size, size_t count, uio_Stream *fp) +{ + int retval; + + retval = uio_fread (lpBuf, size, count, fp); + + return (retval); +} + +size_t +WriteResFile (const void *lpBuf, size_t size, size_t count, uio_Stream *fp) +{ + int retval; + + retval = uio_fwrite (lpBuf, size, count, fp); + + return (retval); +} + +int +GetResFileChar (uio_Stream *fp) +{ + int retval; + + retval = uio_getc (fp); + + return (retval); +} + +int +PutResFileChar (char ch, uio_Stream *fp) +{ + int retval; + + retval = uio_putc (ch, fp); + return (retval); +} + +int +PutResFileNewline (uio_Stream *fp) +{ + int retval; + +#ifdef WIN32 + PutResFileChar ('\r', fp); +#endif + retval = PutResFileChar ('\n', fp); + return (retval); +} + +long +SeekResFile (uio_Stream *fp, long offset, int whence) +{ + long retval; + + retval = uio_fseek (fp, offset, whence); + + return (retval); +} + +long +TellResFile (uio_Stream *fp) +{ + long retval; + + retval = uio_ftell (fp); + + return (retval); +} + +size_t +LengthResFile (uio_Stream *fp) +{ + struct stat sb; + + if (fp == (uio_Stream *)~0) + return (1); + if (uio_fstat(uio_streamHandle(fp), &sb) == -1) + return 1; + return sb.st_size; +} + + diff --git a/src/libs/resource/getres.c b/src/libs/resource/getres.c new file mode 100644 index 0000000..39e24a9 --- /dev/null +++ b/src/libs/resource/getres.c @@ -0,0 +1,257 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "options.h" +#include "port.h" +#include "resintrn.h" +#include "libs/memlib.h" +#include "libs/log.h" +#include "libs/uio/charhashtable.h" + +const char *_cur_resfile_name; +// When a file is being loaded, _cur_resfile_name is set to its name. +// At other times, it is NULL. + +ResourceDesc * +lookupResourceDesc (RESOURCE_INDEX idx, RESOURCE res) +{ + return (ResourceDesc *) CharHashTable_find (idx->map, res); +} + +void +loadResourceDesc (ResourceDesc *desc) +{ + desc->vtable->loadFun (desc->fname, &desc->resdata); +} + +void * +LoadResourceFromPath (const char *path, ResourceLoadFileFun *loadFun) +{ + uio_Stream *stream; + unsigned long dataLen; + void *resdata; + + stream = res_OpenResFile (contentDir, path, "rb"); + if (stream == NULL) + { + log_add (log_Warning, "Warning: Can't open '%s'", path); + return NULL; + } + + dataLen = LengthResFile (stream); + log_add (log_Info, "\t'%s' -- %lu bytes", path, dataLen); + + if (dataLen == 0) + { + log_add (log_Warning, "Warning: Trying to load empty file '%s'.", path); + goto err; + } + + _cur_resfile_name = path; + resdata = (*loadFun) (stream, dataLen); + _cur_resfile_name = NULL; + res_CloseResFile (stream); + + return resdata; + +err: + res_CloseResFile (stream); + return NULL; +} + +const char * +res_GetResourceType (RESOURCE res) +{ + RESOURCE_INDEX resourceIndex; + ResourceDesc *desc; + + if (res == NULL_RESOURCE) + { + log_add (log_Warning, "Trying to get type of null resource"); + return NULL; + } + + resourceIndex = _get_current_index_header (); + desc = lookupResourceDesc (resourceIndex, res); + if (desc == NULL) + { + log_add (log_Warning, "Trying to get type of undefined resource '%s'", + res); + return NULL; + } + + return desc->vtable->resType; +} + + +// Get a resource by its resource ID. +void * +res_GetResource (RESOURCE res) +{ + RESOURCE_INDEX resourceIndex; + ResourceDesc *desc; + + if (res == NULL_RESOURCE) + { + log_add (log_Warning, "Trying to get null resource"); + return NULL; + } + + resourceIndex = _get_current_index_header (); + + desc = lookupResourceDesc (resourceIndex, res); + if (desc == NULL) + { + log_add (log_Warning, "Trying to get undefined resource '%s'", + res); + return NULL; + } + + if (desc->resdata.ptr == NULL) + loadResourceDesc (desc); + if (desc->resdata.ptr != NULL) + ++desc->refcount; + + return desc->resdata.ptr; + // May still be NULL, if the load failed. +} + +DWORD +res_GetIntResource (RESOURCE res) +{ + RESOURCE_INDEX resourceIndex; + ResourceDesc *desc; + + if (res == NULL_RESOURCE) + { + log_add (log_Warning, "Trying to get null resource"); + return 0; + } + + resourceIndex = _get_current_index_header (); + + desc = lookupResourceDesc (resourceIndex, res); + if (desc == NULL) + { + log_add (log_Warning, "Trying to get undefined resource '%s'", + res); + return 0; + } + + return desc->resdata.num; +} + +BOOLEAN +res_GetBooleanResource (RESOURCE res) +{ + return (res_GetIntResource (res) != 0); +} + +// NB: this function appears to be never called! +void +res_FreeResource (RESOURCE res) +{ + ResourceDesc *desc; + ResourceFreeFun *freeFun; + + desc = lookupResourceDesc (_get_current_index_header(), res); + if (desc == NULL) + { + log_add (log_Debug, "Warning: trying to free an unrecognised " + "resource."); + return; + } + + if (desc->refcount > 0) + --desc->refcount; + else + log_add (log_Debug, "Warning: freeing an unreferenced resource."); + if (desc->refcount > 0) + return; // Still references left + + freeFun = desc->vtable->freeFun; + if (freeFun == NULL) + { + log_add (log_Debug, "Warning: trying to free a non-heap resource."); + return; + } + + if (desc->resdata.ptr == NULL) + { + log_add (log_Debug, "Warning: trying to free not loaded " + "resource."); + return; + } + + (*freeFun) (desc->resdata.ptr); + desc->resdata.ptr = NULL; +} + +// By calling this function the caller will be responsible of unloading +// the resource. If res_GetResource() get called again for this +// resource, a NEW copy will be loaded, regardless of whether a detached +// copy still exists. +void * +res_DetachResource (RESOURCE res) +{ + ResourceDesc *desc; + ResourceFreeFun *freeFun; + void *result; + + desc = lookupResourceDesc (_get_current_index_header(), res); + if (desc == NULL) + { + log_add (log_Debug, "Warning: trying to detach from an unrecognised " + "resource."); + return NULL; + } + + freeFun = desc->vtable->freeFun; + if (freeFun == NULL) + { + log_add (log_Debug, "Warning: trying to detach from a non-heap resource."); + return NULL; + } + + if (desc->resdata.ptr == NULL) + { + log_add (log_Debug, "Warning: trying to detach from a not loaded " + "resource."); + return NULL; + } + + if (desc->refcount > 1) + { + log_add (log_Debug, "Warning: trying to detach a resource referenced " + "%u times", desc->refcount); + return NULL; + } + + result = desc->resdata.ptr; + desc->resdata.ptr = NULL; + desc->refcount = 0; + + return result; +} + +BOOLEAN +FreeResourceData (void *data) +{ + HFree (data); + return TRUE; +} diff --git a/src/libs/resource/index.h b/src/libs/resource/index.h new file mode 100644 index 0000000..bdbb162 --- /dev/null +++ b/src/libs/resource/index.h @@ -0,0 +1,54 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_RESOURCE_INDEX_H_ +#define LIBS_RESOURCE_INDEX_H_ + +typedef struct resource_handlers ResourceHandlers; +typedef struct resource_desc ResourceDesc; + +#include <stdio.h> +#include "libs/reslib.h" +#include "libs/uio/charhashtable.h" + +struct resource_handlers +{ + const char *resType; + ResourceLoadFun *loadFun; + ResourceFreeFun *freeFun; + ResourceStringFun *toString; +}; + +struct resource_desc +{ + RESOURCE res_id; + char *fname; + ResourceHandlers *vtable; + RESOURCE_DATA resdata; + // refcount is rudimentary as nothing really frees the descriptors + unsigned refcount; +}; + +struct resource_index_desc +{ + CharHashTable_HashTable *map; + size_t numRes; +}; + +#endif /* LIBS_RESOURCE_INDEX_H_ */ + diff --git a/src/libs/resource/loadres.c b/src/libs/resource/loadres.c new file mode 100644 index 0000000..a9849e4 --- /dev/null +++ b/src/libs/resource/loadres.c @@ -0,0 +1,54 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "resintrn.h" +#include "libs/memlib.h" +#include "libs/log.h" + + +void * +GetResourceData (uio_Stream *fp, DWORD length) +{ + void *result; + DWORD compLen; + + // Resource data used to be prefixed by its length in package files. + // A valid length prefix indicated compressed data, and + // a length prefix ~0 meant uncompressed. + // Currently, .ct and .xlt files still carry a ~0 length prefix. + if (ReadResFile (&compLen, sizeof (compLen), 1, fp) != 1) + return NULL; + if (compLen != ~(DWORD)0) + { + log_add (log_Warning, "LZ-compressed binary data not supported"); + return NULL; + } + length -= sizeof (DWORD); + + result = AllocResourceData (length); + if (!result) + return NULL; + + if (ReadResFile (result, 1, length, fp) != length) + { + FreeResourceData (result); + result = NULL; + } + + return result; +} diff --git a/src/libs/resource/propfile.c b/src/libs/resource/propfile.c new file mode 100644 index 0000000..1784600 --- /dev/null +++ b/src/libs/resource/propfile.c @@ -0,0 +1,129 @@ +/* propfile.c, Copyright (c) 2008 Michael C. Martin */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope thta it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Se the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include "libs/log.h" +#include "propfile.h" +#include "libs/reslib.h" + +void +PropFile_from_string (char *d, PROPERTY_HANDLER handler, const char *prefix) +{ + int len, i; + + len = strlen(d); + i = 0; + while (i < len) { + int key_start, key_end, value_start, value_end; + /* Starting a line: search for non-whitespace */ + while ((i < len) && isspace (d[i])) i++; + if (i >= len) break; /* Done parsing! */ + /* If it was a comment, skip to end of comment/file */ + if (d[i] == '#') { + while ((i < len) && (d[i] != '\n')) i++; + if (i >= len) break; + continue; /* Back to keyword search */ + } + key_start = i; + /* Find the = on this line */ + while ((i < len) && (d[i] != '=') && + (d[i] != '\n') && (d[i] != '#')) i++; + if (i >= len) { /* Bare key at EOF */ + log_add (log_Warning, "Warning: Bare keyword at EOF"); + break; + } + /* Comments here mean incomplete line too */ + if (d[i] != '=') { + log_add (log_Warning, "Warning: Key without value"); + while ((i < len) && (d[i] != '\n')) i++; + if (i >= len) break; + continue; /* Back to keyword search */ + } + /* Key ends at first whitespace before = , or at key_start*/ + key_end = i; + while ((key_end > key_start) && isspace (d[key_end-1])) + key_end--; + + /* Consume the = */ + i++; + /* Value starts at first non-whitespace after = on line... */ + while ((i < len) && (d[i] != '#') && (d[i] != '\n') && + isspace (d[i])) + i++; + value_start = i; + /* Until first non-whitespace before terminator */ + while ((i < len) && (d[i] != '#') && (d[i] != '\n')) + i++; + value_end = i; + while ((value_end > value_start) && isspace (d[value_end-1])) + value_end--; + /* Skip past EOL or EOF */ + while ((i < len) && (d[i] != '\n')) + i++; + i++; + + /* We now have start and end values for key and value. + We terminate the strings for both by writing \0s, then + make a new map entry. */ + d[key_end] = '\0'; + d[value_end] = '\0'; + if (prefix) { + char buf[256]; + snprintf(buf, 255, "%s%s", prefix, d+key_start); + buf[255]=0; + handler(buf, d+value_start); + } else { + handler (d+key_start, d+value_start); + } + } +} + +void +PropFile_from_file (uio_Stream *f, PROPERTY_HANDLER handler, const char *prefix) +{ + size_t flen; + char *data; + + flen = LengthResFile (f); + + data = malloc (flen + 1); + if (!data) { + return; + } + + // We may end up with less bytes than we asked for due to the + // DOS->Unix newline conversion + flen = ReadResFile (data, 1, flen, f); + data[flen] = '\0'; + + PropFile_from_string (data, handler, prefix); + free (data); +} + +void +PropFile_from_filename (uio_DirHandle *path, const char *fname, PROPERTY_HANDLER handler, const char *prefix) +{ + uio_Stream *f = res_OpenResFile (path, fname, "rt"); + if (!f) { + return; + } + PropFile_from_file (f, handler, prefix); + res_CloseResFile(f); +} diff --git a/src/libs/resource/propfile.h b/src/libs/resource/propfile.h new file mode 100644 index 0000000..edc8c36 --- /dev/null +++ b/src/libs/resource/propfile.h @@ -0,0 +1,30 @@ +/* propfile.h, Copyright (c) 2008 Michael C. Martin */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope thta it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Se the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef PROPFILE_H_ +#define PROPFILE_H_ + +#include "libs/uio.h" + +typedef void (*PROPERTY_HANDLER) (const char *, const char *); + +void PropFile_from_string (char *d, PROPERTY_HANDLER handler, const char *prefix); +void PropFile_from_file (uio_Stream *f, PROPERTY_HANDLER handler, const char *prefix); +void PropFile_from_filename (uio_DirHandle *path, const char *fname, PROPERTY_HANDLER handler, const char *prefix); + +#endif diff --git a/src/libs/resource/resinit.c b/src/libs/resource/resinit.c new file mode 100644 index 0000000..dacbee4 --- /dev/null +++ b/src/libs/resource/resinit.c @@ -0,0 +1,651 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "resintrn.h" +#include "libs/memlib.h" +#include "options.h" +#include "types.h" +#include "libs/log.h" +#include "libs/gfxlib.h" +#include "libs/reslib.h" +#include "libs/sndlib.h" +#include "libs/vidlib.h" +#include "propfile.h" +#include <ctype.h> +#include <stdlib.h> +// XXX: we should not include anything from uqm/ inside libs/ +#include "uqm/coderes.h" + +static RESOURCE_INDEX +allocResourceIndex (void) { + RESOURCE_INDEX ndx = HMalloc (sizeof (RESOURCE_INDEX_DESC)); + ndx->map = CharHashTable_newHashTable (NULL, NULL, NULL, NULL, NULL, + 0, 0.85, 0.9); + return ndx; +} + +static void +freeResourceIndex (RESOURCE_INDEX h) { + if (h != NULL) + { + /* TODO: This leaks the contents of h->map */ + CharHashTable_deleteHashTable (h->map); + HFree (h); + } +} + +#define TYPESIZ 32 + +static ResourceDesc * +newResourceDesc (const char *res_id, const char *resval) +{ + const char *path; + int pathlen; + ResourceHandlers *vtable; + ResourceDesc *result, *handlerdesc; + RESOURCE_INDEX idx = _get_current_index_header (); + char typestr[TYPESIZ]; + + path = strchr (resval, ':'); + if (path == NULL) + { + log_add (log_Warning, "Could not find type information for resource '%s'", res_id); + strncpy(typestr, "sys.UNKNOWNRES", TYPESIZ); + path = resval; + } + else + { + int n = path - resval; + + if (n >= TYPESIZ - 4) + { + n = TYPESIZ - 5; + } + strncpy (typestr, "sys.", TYPESIZ); + strncat (typestr+1, resval, n); + typestr[n+4] = '\0'; + path++; + } + pathlen = strlen (path); + + handlerdesc = lookupResourceDesc(idx, typestr); + if (handlerdesc == NULL) { + path = resval; + log_add (log_Warning, "Illegal type '%s' for resource '%s'; treating as UNKNOWNRES", typestr, res_id); + handlerdesc = lookupResourceDesc(idx, "sys.UNKNOWNRES"); + } + + vtable = (ResourceHandlers *)handlerdesc->resdata.ptr; + + if (vtable->loadFun == NULL) + { + log_add (log_Warning, "Warning: Unable to load '%s'; no handler " + "for type %s defined.", res_id, typestr); + return NULL; + } + + result = HMalloc (sizeof (ResourceDesc)); + if (result == NULL) + return NULL; + + result->fname = HMalloc (pathlen + 1); + strncpy (result->fname, path, pathlen); + result->fname[pathlen] = '\0'; + result->vtable = vtable; + result->refcount = 0; + + if (vtable->freeFun == NULL) + { + /* Non-heap resources are raw values. Work those out at load time. */ + vtable->loadFun (result->fname, &result->resdata); + } + else + { + result->resdata.ptr = NULL; + } + return result; +} + +static void +process_resource_desc (const char *key, const char *value) +{ + CharHashTable_HashTable *map = _get_current_index_header ()->map; + ResourceDesc *newDesc = newResourceDesc (key, value); + if (newDesc != NULL) + { + if (!CharHashTable_add (map, key, newDesc)) + { + res_Remove (key); + CharHashTable_add (map, key, newDesc); + } + } +} + +static void +UseDescriptorAsRes (const char *descriptor, RESOURCE_DATA *resdata) +{ + resdata->str = descriptor; +} + +static void +DescriptorToInt (const char *descriptor, RESOURCE_DATA *resdata) +{ + resdata->num = atoi (descriptor); +} + +static void +DescriptorToBoolean (const char *descriptor, RESOURCE_DATA *resdata) +{ + if (!strcasecmp (descriptor, "true")) + { + resdata->num = TRUE; + } + else + { + resdata->num = FALSE; + } +} + +static inline size_t +skipWhiteSpace (const char *start) +{ + const char *ptr = start; + while (isspace (*ptr)) + ptr++; + return (ptr - start); +} + +// On success, resdata->num will be filled with a 32-bits RGBA value. +static void +DescriptorToColor (const char *descriptor, RESOURCE_DATA *resdata) +{ + int bytesParsed; + int componentBits; + int maxComponentValue; + size_t componentCount; + size_t compI; + int comps[4]; + // One element for each of r, g, b, a. + + descriptor += skipWhiteSpace (descriptor); + +#if 0 + // Can't use this; '#' starts a comment. + if (*descriptor == '#') + { + // "#rrggbb" + int i; + DWORD value = 0; + + descriptor++; + for (i = 0; i < 6; i++) + { + BYTE nibbleValue; + if (*descriptor >= '0' && *descriptor <= '9') + nibbleValue = *descriptor - '0'; + else if (*descriptor >= 'a' && *descriptor <= 'f') + nibbleValue = 0xa + *descriptor - 'a'; + else if (*descriptor >= 'A' && *descriptor <= 'F') + nibbleValue = 0xa + *descriptor - 'A'; + else + goto fail; + + value = (value * 16) + nibbleValue; + descriptor++; + } + + descriptor += skipWhiteSpace (descriptor); + + if (*descriptor != '\0') + log_add (log_Warning, "Junk after color resource string."); + + resdata->num = (value << 8) | 0xff; + return; + } +#endif + + // Color is of the form "rgb(r, g, b)", "rgba(r, g, b, a)", + // or "rgb15(r, g, b)". + + if (sscanf (descriptor, "rgb ( %i , %i , %i ) %n", + &comps[0], &comps[1], &comps[2], &bytesParsed) >= 3) + { + componentBits = 8; + componentCount = 3; + comps[3] = 0xff; + } + else if (sscanf (descriptor, "rgba ( %i , %i , %i , %i ) %n", + &comps[0], &comps[1], &comps[2], &comps[3], &bytesParsed) >= 4) + { + componentBits = 8; + componentCount = 4; + } + else if (sscanf (descriptor, "rgb15 ( %i , %i , %i ) %n", + &comps[0], &comps[1], &comps[2], &bytesParsed) >= 3) + { + componentBits = 5; + componentCount = 3; + comps[3] = 0xff; + } + else + goto fail; + + if (descriptor[bytesParsed] != '\0') + log_add (log_Warning, "Junk after color resource string."); + + maxComponentValue = (1 << componentBits) - 1; + + // Check the range of the components. + for (compI = 0; compI < componentCount; compI++) + { + if (comps[compI] < 0) + { + comps[compI] = 0; + log_add (log_Warning, "Color component value too small; " + "value clipped."); + } + + if (comps[compI] > (long) maxComponentValue) + { + comps[compI] = maxComponentValue; + log_add (log_Warning, "Color component value too large; " + "value clipped."); + } + } + + if (componentBits == 5) + resdata->num = ((CC5TO8 (comps[0]) << 24) | + (CC5TO8 (comps[1]) << 16) | (CC5TO8 (comps[2]) << 8) | + comps[3]); + else + resdata->num = ((comps[0] << 24) | (comps[1] << 16) | + (comps[2] << 8) | comps[3]); + + return; + +fail: + log_add (log_Error, "Invalid color description string for resource.\n"); + resdata->num = 0x00000000; +} + +static void +RawDescriptor (RESOURCE_DATA *resdata, char *buf, unsigned int size) +{ + snprintf (buf, size, "%s", resdata->str); +} + +static void +IntToString (RESOURCE_DATA *resdata, char *buf, unsigned int size) +{ + snprintf (buf, size, "%d", resdata->num); +} + + +static void +BooleanToString (RESOURCE_DATA *resdata, char *buf, unsigned int size) +{ + snprintf (buf, size, "%s", resdata->num ? "true" : "false"); +} + +static void +ColorToString (RESOURCE_DATA *resdata, char *buf, unsigned int size) +{ + if ((resdata->num & 0xff) == 0xff) + { + // Opaque color, save as "rgb". + snprintf (buf, size, "rgb(0x%02x, 0x%02x, 0x%02x)", + (resdata->num >> 24), (resdata->num >> 16) & 0xff, + (resdata->num >> 8) & 0xff); + } + else + { + // (Partially) transparent color, save as "rgba". + snprintf (buf, size, "rgba(0x%02x, 0x%02x, 0x%02x, 0x%02x)", + (resdata->num >> 24), (resdata->num >> 16) & 0xff, + (resdata->num >> 8) & 0xff, resdata->num & 0xff); + } +} + +static RESOURCE_INDEX curResourceIndex; + +void +_set_current_index_header (RESOURCE_INDEX newResourceIndex) +{ + curResourceIndex = newResourceIndex; +} + +RESOURCE_INDEX +InitResourceSystem (void) +{ + RESOURCE_INDEX ndx; + if (curResourceIndex) { + return curResourceIndex; + } + ndx = allocResourceIndex (); + + _set_current_index_header (ndx); + + InstallResTypeVectors ("UNKNOWNRES", UseDescriptorAsRes, NULL, NULL); + InstallResTypeVectors ("STRING", UseDescriptorAsRes, NULL, RawDescriptor); + InstallResTypeVectors ("INT32", DescriptorToInt, NULL, IntToString); + InstallResTypeVectors ("BOOLEAN", DescriptorToBoolean, NULL, + BooleanToString); + InstallResTypeVectors ("COLOR", DescriptorToColor, NULL, ColorToString); + InstallGraphicResTypes (); + InstallStringTableResType (); + InstallAudioResTypes (); + InstallVideoResType (); + InstallCodeResType (); + + return ndx; +} + +RESOURCE_INDEX +_get_current_index_header (void) +{ + if (!curResourceIndex) { + InitResourceSystem (); + } + return curResourceIndex; +} + +void +LoadResourceIndex (uio_DirHandle *dir, const char *rmpfile, const char *prefix) +{ + PropFile_from_filename (dir, rmpfile, process_resource_desc, prefix); +} + +void +SaveResourceIndex (uio_DirHandle *dir, const char *rmpfile, const char *root, BOOLEAN strip_root) +{ + uio_Stream *f; + CharHashTable_Iterator *it; + unsigned int prefix_len; + + f = res_OpenResFile (dir, rmpfile, "wb"); + if (!f) { + /* TODO: Warning message */ + return; + } + prefix_len = root ? strlen (root) : 0; + for (it = CharHashTable_getIterator (_get_current_index_header ()->map); + !CharHashTable_iteratorDone (it); + it = CharHashTable_iteratorNext (it)) { + char *key = CharHashTable_iteratorKey (it); + if (!root || !strncmp (root, key, prefix_len)) { + ResourceDesc *value = CharHashTable_iteratorValue (it); + if (!value) { + log_add(log_Warning, "Resource %s had no value", key); + } else if (!value->vtable) { + log_add(log_Warning, "Resource %s had no type", key); + } else if (value->vtable->toString) { + char buf[256]; + value->vtable->toString (&value->resdata, buf, 256); + buf[255]=0; + if (root && strip_root) { + WriteResFile (key+prefix_len, 1, strlen (key) - prefix_len, f); + } else { + WriteResFile (key, 1, strlen (key), f); + } + PutResFileChar(' ', f); + PutResFileChar('=', f); + PutResFileChar(' ', f); + WriteResFile (value->vtable->resType, 1, strlen (value->vtable->resType), f); + PutResFileChar(':', f); + WriteResFile (buf, 1, strlen (buf), f); + PutResFileNewline(f); + } + } + } + res_CloseResFile (f); + CharHashTable_freeIterator (it); +} + +void +UninitResourceSystem (void) +{ + freeResourceIndex (_get_current_index_header ()); + _set_current_index_header (NULL); +} + +BOOLEAN +InstallResTypeVectors (const char *resType, ResourceLoadFun *loadFun, + ResourceFreeFun *freeFun, ResourceStringFun *stringFun) +{ + ResourceHandlers *handlers; + ResourceDesc *result; + char key[TYPESIZ]; + int typelen; + CharHashTable_HashTable *map; + + snprintf(key, TYPESIZ, "sys.%s", resType); + key[TYPESIZ-1] = '\0'; + typelen = strlen(resType); + + handlers = HMalloc (sizeof (ResourceHandlers)); + if (handlers == NULL) + { + return FALSE; + } + handlers->loadFun = loadFun; + handlers->freeFun = freeFun; + handlers->toString = stringFun; + handlers->resType = resType; + + result = HMalloc (sizeof (ResourceDesc)); + if (result == NULL) + return FALSE; + + result->fname = HMalloc (strlen(resType) + 1); + strncpy (result->fname, resType, typelen); + result->fname[typelen] = '\0'; + result->vtable = NULL; + result->resdata.ptr = handlers; + + map = _get_current_index_header ()->map; + return CharHashTable_add (map, key, result) != 0; +} + +/* These replace the mapres.c calls and probably should be split out at some point. */ +BOOLEAN +res_IsString (const char *key) +{ + RESOURCE_INDEX idx = _get_current_index_header (); + ResourceDesc *desc = lookupResourceDesc (idx, key); + return desc && !strcmp(desc->vtable->resType, "STRING"); +} + +const char * +res_GetString (const char *key) +{ + RESOURCE_INDEX idx = _get_current_index_header (); + ResourceDesc *desc = lookupResourceDesc (idx, key); + if (!desc || !desc->resdata.str || strcmp(desc->vtable->resType, "STRING")) + return ""; + /* TODO: Work out exact STRING semantics, specifically, the lifetime of + * the returned value. If caller is allowed to reference the returned + * value forever, STRING has to be ref-counted. */ + return desc->resdata.str; +} + +void +res_PutString (const char *key, const char *value) +{ + RESOURCE_INDEX idx = _get_current_index_header (); + ResourceDesc *desc = lookupResourceDesc (idx, key); + int srclen, dstlen; + if (!desc || !desc->resdata.str || strcmp(desc->vtable->resType, "STRING")) + { + /* TODO: This is kind of roundabout. We can do better by refactoring newResourceDesc */ + process_resource_desc(key, "STRING:undefined"); + desc = lookupResourceDesc (idx, key); + } + srclen = strlen (value); + dstlen = strlen (desc->fname); + if (srclen > dstlen) { + char *newValue = HMalloc(srclen + 1); + char *oldValue = desc->fname; + log_add(log_Warning, "Reallocating string space for '%s'", key); + strncpy (newValue, value, srclen + 1); + desc->resdata.str = newValue; + desc->fname = newValue; + HFree (oldValue); + } else { + strncpy (desc->fname, value, dstlen + 1); + } +} + +BOOLEAN +res_IsInteger (const char *key) +{ + RESOURCE_INDEX idx = _get_current_index_header (); + ResourceDesc *desc = lookupResourceDesc (idx, key); + return desc && !strcmp(desc->vtable->resType, "INT32"); +} + +int +res_GetInteger (const char *key) +{ + RESOURCE_INDEX idx = _get_current_index_header (); + ResourceDesc *desc = lookupResourceDesc (idx, key); + if (!desc || strcmp(desc->vtable->resType, "INT32")) + { + // TODO: Better error handling + return 0; + } + return desc->resdata.num; +} + +void +res_PutInteger (const char *key, int value) +{ + RESOURCE_INDEX idx = _get_current_index_header (); + ResourceDesc *desc = lookupResourceDesc (idx, key); + if (!desc || strcmp(desc->vtable->resType, "INT32")) + { + /* TODO: This is kind of roundabout. We can do better by refactoring newResourceDesc */ + process_resource_desc(key, "INT32:0"); + desc = lookupResourceDesc (idx, key); + } + desc->resdata.num = value; +} + +BOOLEAN +res_IsBoolean (const char *key) +{ + RESOURCE_INDEX idx = _get_current_index_header (); + ResourceDesc *desc = lookupResourceDesc (idx, key); + return desc && !strcmp(desc->vtable->resType, "BOOLEAN"); +} + +BOOLEAN +res_GetBoolean (const char *key) +{ + RESOURCE_INDEX idx = _get_current_index_header (); + ResourceDesc *desc = lookupResourceDesc (idx, key); + if (!desc || strcmp(desc->vtable->resType, "BOOLEAN")) + { + // TODO: Better error handling + return FALSE; + } + return desc->resdata.num ? TRUE : FALSE; +} + +void +res_PutBoolean (const char *key, BOOLEAN value) +{ + RESOURCE_INDEX idx = _get_current_index_header (); + ResourceDesc *desc = lookupResourceDesc (idx, key); + if (!desc || strcmp(desc->vtable->resType, "BOOLEAN")) + { + /* TODO: This is kind of roundabout. We can do better by refactoring newResourceDesc */ + process_resource_desc(key, "BOOLEAN:false"); + desc = lookupResourceDesc (idx, key); + } + desc->resdata.num = value; +} + +BOOLEAN +res_IsColor (const char *key) +{ + RESOURCE_INDEX idx = _get_current_index_header (); + ResourceDesc *desc = lookupResourceDesc (idx, key); + return desc && !strcmp(desc->vtable->resType, "COLOR"); +} + +Color +res_GetColor (const char *key) +{ + RESOURCE_INDEX idx = _get_current_index_header (); + ResourceDesc *desc = lookupResourceDesc (idx, key); + DWORD num; + if (!desc || strcmp(desc->vtable->resType, "COLOR")) + { + // TODO: Better error handling + return buildColorRgba (0, 0, 0, 0); + } + + num = desc->resdata.num; + return buildColorRgba (num >> 24, (num >> 16) & 0xff, + (desc->resdata.num >> 8) & 0xff, num & 0xff); +} + +void +res_PutColor (const char *key, Color value) +{ + RESOURCE_INDEX idx = _get_current_index_header (); + ResourceDesc *desc = lookupResourceDesc (idx, key); + if (!desc || strcmp(desc->vtable->resType, "COLOR")) + { + /* TODO: This is kind of roundabout. We can do better by refactoring + * newResourceDesc */ + process_resource_desc(key, "COLOR:rgb(0, 0, 0)"); + desc = lookupResourceDesc (idx, key); + } + desc->resdata.num = + (value.r << 24) | (value.g << 16) | (value.b << 8) | value.a; +} + +BOOLEAN +res_HasKey (const char *key) +{ + RESOURCE_INDEX idx = _get_current_index_header (); + return (lookupResourceDesc(idx, key) != NULL); +} + +BOOLEAN +res_Remove (const char *key) +{ + CharHashTable_HashTable *map = _get_current_index_header ()->map; + ResourceDesc *oldDesc = (ResourceDesc *)CharHashTable_find (map, key); + if (oldDesc != NULL) + { + if (oldDesc->resdata.ptr != NULL) + { + if (oldDesc->refcount > 0) + log_add (log_Warning, "WARNING: Replacing '%s' while it is live", key); + if (oldDesc->vtable && oldDesc->vtable->freeFun) + { + oldDesc->vtable->freeFun(oldDesc->resdata.ptr); + } + } + HFree (oldDesc->fname); + HFree (oldDesc); + } + return CharHashTable_remove (map, key); +} diff --git a/src/libs/resource/resintrn.h b/src/libs/resource/resintrn.h new file mode 100644 index 0000000..e2255ea --- /dev/null +++ b/src/libs/resource/resintrn.h @@ -0,0 +1,34 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_RESOURCE_RESINTRN_H_ +#define LIBS_RESOURCE_RESINTRN_H_ + +#include <string.h> +#include "libs/reslib.h" +#include "index.h" + +ResourceDesc *lookupResourceDesc (RESOURCE_INDEX idx, RESOURCE res); +void loadResourceDesc (ResourceDesc *desc); + +void _set_current_index_header (RESOURCE_INDEX newResourceIndex); +RESOURCE_INDEX _get_current_index_header (void); + + +#endif /* LIBS_RESOURCE_RESINTRN_H_ */ + diff --git a/src/libs/resource/stringbank.c b/src/libs/resource/stringbank.c new file mode 100644 index 0000000..a1b9576 --- /dev/null +++ b/src/libs/resource/stringbank.c @@ -0,0 +1,181 @@ +/* stringbank.c, Copyright (c) 2005 Michael C. Martin */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope thta it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Se the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "stringbank.h" + +typedef stringbank chunk; + +static stringbank * +add_chunk (stringbank *bank) +{ + stringbank *n = malloc (sizeof (stringbank)); + n->len = 0; + n->next = NULL; + if (bank) + { + while (bank->next) + bank = bank->next; + bank->next = n; + } + return n; +} + +stringbank * +StringBank_Create (void) +{ + return add_chunk (NULL); +} + +void +StringBank_Free (stringbank *bank) +{ + if (bank) + { + StringBank_Free (bank->next); + free (bank); + } +} + +const char * +StringBank_AddString (stringbank *bank, const char *str) +{ + unsigned int len = strlen (str) + 1; + stringbank *x = bank; + if (len > STRBANK_CHUNK_SIZE) + return NULL; + while (x) { + unsigned int remaining = STRBANK_CHUNK_SIZE - x->len; + if (len < remaining) { + char *result = x->data + x->len; + strcpy (result, str); + x->len += len; + return result; + } + x = x->next; + } + /* No room in any currently existing chunk */ + x = add_chunk (bank); + strcpy (x->data, str); + x->len += len; + return x->data; +} + +const char * +StringBank_AddOrFindString (stringbank *bank, const char *str) +{ + unsigned int len = strlen (str) + 1; + stringbank *x = bank; + if (len > STRBANK_CHUNK_SIZE) + return NULL; + while (x) { + int i = 0; + while (i < x->len) { + if (!strcmp (x->data + i, str)) + return x->data + i; + while (x->data[i]) i++; + i++; + } + x = x->next; + } + /* We didn't find it, so add it */ + return StringBank_AddString (bank, str); +} + +static char buffer[STRBANK_CHUNK_SIZE]; + +const char * +StringBank_AddSubstring (stringbank *bank, const char *str, unsigned int n) +{ + unsigned int len = strlen (str); + if (n > len) + { + return StringBank_AddString (bank, str); + } + if (n >= STRBANK_CHUNK_SIZE) + { + return NULL; + } + strncpy (buffer, str, n); + buffer[n] = '\0'; + return StringBank_AddString(bank, buffer); +} + +const char * +StringBank_AddOrFindSubstring (stringbank *bank, const char *str, unsigned int n) +{ + unsigned int len = strlen (str); + if (n > len) + { + return StringBank_AddOrFindString (bank, str); + } + if (n >= STRBANK_CHUNK_SIZE) + { + return NULL; + } + strncpy (buffer, str, n); + buffer[n] = '\0'; + return StringBank_AddOrFindString(bank, buffer); +} + +int +SplitString (const char *s, char splitchar, int n, const char **result, stringbank *bank) +{ + int i; + const char *index = s; + + for (i = 0; i < n-1; i++) + { + const char *next; + int len; + + next = strchr (index, splitchar); + if (!next) + { + break; + } + + len = next - index; + result[i] = StringBank_AddOrFindSubstring (bank, index, len); + index = next+1; + } + result[i] = StringBank_AddOrFindString (bank, index); + return i+1; +} + +#ifdef SB_DEBUG + +void +StringBank_Dump (stringbank *bank, FILE *s) +{ + stringbank *x = bank; + while (x) { + int i = 0; + while (i < x->len) { + fprintf (s, "\"%s\"\n", x->data + i); + while (x->data[i]) i++; + i++; + } + x = x->next; + } +} + +#endif diff --git a/src/libs/resource/stringbank.h b/src/libs/resource/stringbank.h new file mode 100644 index 0000000..e77105b --- /dev/null +++ b/src/libs/resource/stringbank.h @@ -0,0 +1,57 @@ +/* stringbank.h, Copyright (c) 2005 Michael C. Martin */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope thta it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Se the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_RESOURCE_STRINGBANK_H_ +#define LIBS_RESOURCE_STRINGBANK_H_ + +#ifdef SB_DEBUG +#include <stdio.h> +#endif + +#define STRBANK_CHUNK_SIZE (1024 - sizeof (void *) - sizeof (int)) + +typedef struct _stringbank_chunk { + char data[STRBANK_CHUNK_SIZE]; + int len; + struct _stringbank_chunk *next; +} stringbank; + +/* Constructors and destructors */ +stringbank *StringBank_Create (void); +void StringBank_Free (stringbank *bank); + +/* Put str or n chars after str into the string bank. */ +const char *StringBank_AddString (stringbank *bank, const char *str); +const char *StringBank_AddSubstring (stringbank *bank, const char *str, unsigned int n); + +/* Put str or n chars after str into the string bank if it's not already + there. Much slower. */ +const char *StringBank_AddOrFindString (stringbank *bank, const char *str); +const char *StringBank_AddOrFindSubstring (stringbank *bank, const char *str, unsigned int n); + +/* Split a string s into at most n substrings, separated by splitchar. + Pointers to these substrings will be stored in result; the + substrings themselves will be filed in the specified stringbank. */ +int SplitString (const char *s, char splitchar, int n, const char **result, stringbank *bank); + +#ifdef SB_DEBUG +/* Print out a list of the contents of the string bank to the named stream. */ +void StringBank_Dump (stringbank *bank, FILE *s); +#endif /* SB_DEBUG */ + +#endif /* LIBS_RESOURCE_STRINGBANK_H_ */ diff --git a/src/libs/sndlib.h b/src/libs/sndlib.h new file mode 100644 index 0000000..e900707 --- /dev/null +++ b/src/libs/sndlib.h @@ -0,0 +1,107 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_SNDLIB_H_ +#define LIBS_SNDLIB_H_ + +#include "port.h" +#include "libs/strlib.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +typedef STRING_TABLE SOUND_REF; +typedef STRING SOUND; +// SOUNDPTR is really a TFB_SoundSample** +typedef void *SOUNDPTR; + +typedef struct soundposition +{ + BOOLEAN positional; + int x, y; +} SoundPosition; + +#define InitSoundResources InitStringTableResources +#define CaptureSound CaptureStringTable +#define ReleaseSound ReleaseStringTable +#define GetSoundRef GetStringTable +#define GetSoundCount GetStringTableCount +#define GetSoundIndex GetStringTableIndex +#define SetAbsSoundIndex SetAbsStringTableIndex +#define SetRelSoundIndex SetRelStringTableIndex + +extern SOUNDPTR GetSoundAddress (SOUND sound); + +typedef struct tfb_soundsample TFB_SoundSample; +typedef TFB_SoundSample **MUSIC_REF; + +extern BOOLEAN InitSound (int argc, char *argv[]); +extern void UninitSound (void); +extern SOUND_REF LoadSoundFile (const char *pStr); +extern MUSIC_REF LoadMusicFile (const char *pStr); +extern BOOLEAN InstallAudioResTypes (void); +extern SOUND_REF LoadSoundInstance (RESOURCE res); +extern MUSIC_REF LoadMusicInstance (RESOURCE res); +extern BOOLEAN DestroySound (SOUND_REF SoundRef); +extern BOOLEAN DestroyMusic (MUSIC_REF MusicRef); + +#define MAX_CHANNELS 8 +#define MAX_VOLUME 255 +#define NORMAL_VOLUME 160 + +#define FIRST_SFX_CHANNEL 0 +#define MIN_FX_CHANNEL 1 +#define NUM_FX_CHANNELS 4 +#define LAST_SFX_CHANNEL (MIN_FX_CHANNEL + NUM_FX_CHANNELS - 1) +#define NUM_SFX_CHANNELS (MIN_FX_CHANNEL + NUM_FX_CHANNELS) + +extern void PLRPlaySong (MUSIC_REF MusicRef, BOOLEAN Continuous, BYTE + Priority); +extern void PLRStop (MUSIC_REF MusicRef); +extern BOOLEAN PLRPlaying (MUSIC_REF MusicRef); +extern void PLRSeek (MUSIC_REF MusicRef, DWORD pos); +extern void PLRPause (MUSIC_REF MusicRef); +extern void PLRResume (MUSIC_REF MusicRef); +extern void snd_PlaySpeech (MUSIC_REF SpeechRef); +extern void snd_StopSpeech (void); +extern void PlayChannel (COUNT channel, SOUND snd, SoundPosition pos, + void *positional_object, unsigned char priority); +extern BOOLEAN ChannelPlaying (COUNT Channel); +extern void * GetPositionalObject (COUNT channel); +extern void SetPositionalObject (COUNT channel, void *positional_object); +extern void UpdateSoundPosition (COUNT channel, SoundPosition pos); +extern void StopChannel (COUNT Channel, BYTE Priority); +extern void SetMusicVolume (COUNT Volume); +extern void SetChannelVolume (COUNT Channel, COUNT Volume, BYTE + Priority); + +extern void StopSound (void); +extern BOOLEAN SoundPlaying (void); + +extern void WaitForSoundEnd (COUNT Channel); +#define TFBSOUND_WAIT_ALL ((COUNT)~0) + +extern DWORD FadeMusic (BYTE end_vol, SIZE TimeInterval); + +#if defined(__cplusplus) +} +#endif + +#endif /* LIBS_SNDLIB_H_ */ + diff --git a/src/libs/sound/Makeinfo b/src/libs/sound/Makeinfo new file mode 100644 index 0000000..28ee5cc --- /dev/null +++ b/src/libs/sound/Makeinfo @@ -0,0 +1,9 @@ +if [ "$uqm_SOUNDMODULE" = "openal" ]; then + uqm_SUBDIRS="openal mixer decoders" + uqm_CFLAGS="$uqm_CFLAGS -DHAVE_OPENAL" +else + uqm_SUBDIRS="mixer decoders" +fi + +uqm_CFILES="audiocore.c fileinst.c resinst.c sound.c sfx.c music.c stream.c trackplayer.c" +uqm_HFILES="audiocore.h sndintrn.h sound.h stream.h trackint.h trackplayer.h" diff --git a/src/libs/sound/audiocore.c b/src/libs/sound/audiocore.c new file mode 100644 index 0000000..440f63f --- /dev/null +++ b/src/libs/sound/audiocore.c @@ -0,0 +1,272 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Audio Core API (derived from OpenAL) + */ + +#include <stdio.h> +#include <stdlib.h> +#include "audiocore.h" +#include "sound.h" +#include "libs/log.h" + +static audio_Driver audiodrv; + +/* The globals that control the sound drivers. */ +int snddriver, soundflags; + +volatile bool audio_inited = false; + +/* + * Declarations for driver init funcs + */ + +#ifdef HAVE_OPENAL +sint32 openAL_Init (audio_Driver *driver, sint32 flags); +#endif +sint32 mixSDL_Init (audio_Driver *driver, sint32 flags); +sint32 noSound_Init (audio_Driver *driver, sint32 flags); + + +/* + * Initialization + */ + +sint32 +initAudio (sint32 driver, sint32 flags) +{ + sint32 ret; + +#ifdef HAVE_OPENAL + if (driver == audio_DRIVER_MIXSDL) + ret = mixSDL_Init (&audiodrv, flags); + else if (driver == audio_DRIVER_OPENAL) + ret = openAL_Init (&audiodrv, flags); + else + ret = noSound_Init (&audiodrv, flags); +#else + if (driver == audio_DRIVER_OPENAL) + { + log_add (log_Warning, "OpenAL driver not compiled in, so using MixSDL"); + driver = audio_DRIVER_MIXSDL; + } + if (driver == audio_DRIVER_MIXSDL) + ret = mixSDL_Init (&audiodrv, flags); + else + ret = noSound_Init (&audiodrv, flags); +#endif + + if (ret != 0) + { + log_add (log_Fatal, "Sound driver initialization failed.\n" + "This may happen when a soundcard is " + "not present or not available.\n" + "NOTICE: Try running UQM with '--sound=none' option"); + exit (EXIT_FAILURE); + } + + SetSFXVolume (sfxVolumeScale); + SetSpeechVolume (speechVolumeScale); + SetMusicVolume (musicVolume); + + audio_inited = true; + + return ret; +} + +void +unInitAudio (void) +{ + if (!audio_inited) + return; + + audio_inited = false; + audiodrv.Uninitialize (); +} + + +/* + * General + */ + +sint32 +audio_GetError (void) +{ + return audiodrv.GetError (); +} + + +/* + * Sources + */ + +void +audio_GenSources (uint32 n, audio_Object *psrcobj) +{ + audiodrv.GenSources (n, psrcobj); +} + +void +audio_DeleteSources (uint32 n, audio_Object *psrcobj) +{ + audiodrv.DeleteSources (n, psrcobj); +} + +bool +audio_IsSource (audio_Object srcobj) +{ + return audiodrv.IsSource (srcobj); +} + +void +audio_Sourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal value) + +{ + audiodrv.Sourcei (srcobj, audiodrv.EnumLookup[pname], value); +} + +void +audio_Sourcef (audio_Object srcobj, audio_SourceProp pname, + float value) +{ + audiodrv.Sourcef (srcobj, audiodrv.EnumLookup[pname], value); +} + +void +audio_Sourcefv (audio_Object srcobj, audio_SourceProp pname, + float *value) +{ + audiodrv.Sourcefv (srcobj, audiodrv.EnumLookup[pname], value); +} + +void +audio_GetSourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal *value) +{ + audiodrv.GetSourcei (srcobj, audiodrv.EnumLookup[pname], value); +} + +void +audio_GetSourcef (audio_Object srcobj, audio_SourceProp pname, + float *value) +{ + audiodrv.GetSourcef (srcobj, audiodrv.EnumLookup[pname], value); +} + +void +audio_SourceRewind (audio_Object srcobj) +{ + audiodrv.SourceRewind (srcobj); +} + +void +audio_SourcePlay (audio_Object srcobj) +{ + audiodrv.SourcePlay (srcobj); +} + +void +audio_SourcePause (audio_Object srcobj) +{ + audiodrv.SourcePause (srcobj); +} + +void +audio_SourceStop (audio_Object srcobj) +{ + audiodrv.SourceStop (srcobj); +} + +void +audio_SourceQueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj) +{ + audiodrv.SourceQueueBuffers (srcobj, n, pbufobj); +} + +void +audio_SourceUnqueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj) +{ + audiodrv.SourceUnqueueBuffers (srcobj, n, pbufobj); +} + + +/* + * Buffers + */ + +void +audio_GenBuffers (uint32 n, audio_Object *pbufobj) +{ + audiodrv.GenBuffers (n, pbufobj); +} + +void +audio_DeleteBuffers (uint32 n, audio_Object *pbufobj) +{ + audiodrv.DeleteBuffers (n, pbufobj); +} + +bool +audio_IsBuffer (audio_Object bufobj) +{ + return audiodrv.IsBuffer (bufobj); +} + +void +audio_GetBufferi (audio_Object bufobj, audio_BufferProp pname, + audio_IntVal *value) +{ + audiodrv.GetBufferi (bufobj, audiodrv.EnumLookup[pname], value); +} + +void +audio_BufferData (audio_Object bufobj, uint32 format, void* data, + uint32 size, uint32 freq) +{ + audiodrv.BufferData (bufobj, audiodrv.EnumLookup[format], data, size, + freq); +} + +bool +audio_GetFormatInfo (uint32 format, int *channels, int *sample_size) +{ + switch (format) + { + case audio_FORMAT_MONO8: + *channels = 1; + *sample_size = sizeof (uint8); + return true; + + case audio_FORMAT_STEREO8: + *channels = 2; + *sample_size = sizeof (uint8); + return true; + + case audio_FORMAT_MONO16: + *channels = 1; + *sample_size = sizeof (sint16); + return true; + + case audio_FORMAT_STEREO16: + *channels = 2; + *sample_size = sizeof (sint16); + return true; + } + return false; +} diff --git a/src/libs/sound/audiocore.h b/src/libs/sound/audiocore.h new file mode 100644 index 0000000..6f48b26 --- /dev/null +++ b/src/libs/sound/audiocore.h @@ -0,0 +1,169 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Audio Core API (derived from OpenAL) + */ + +#ifndef LIBS_SOUND_AUDIOCORE_H_ +#define LIBS_SOUND_AUDIOCORE_H_ + +#include "config.h" +#include "types.h" + + +/* Available drivers */ +enum +{ + audio_DRIVER_MIXSDL, + audio_DRIVER_NOSOUND, + audio_DRIVER_OPENAL +}; + +/* Initialization flags */ +#define audio_QUALITY_HIGH (1 << 0) +#define audio_QUALITY_MEDIUM (1 << 1) +#define audio_QUALITY_LOW (1 << 2) + + +/* Interface Types */ +typedef uintptr_t audio_Object; +typedef intptr_t audio_IntVal; +typedef const sint32 audio_SourceProp; +typedef const sint32 audio_BufferProp; + +enum +{ + /* Errors */ + audio_NO_ERROR = 0, + audio_INVALID_NAME, + audio_INVALID_ENUM, + audio_INVALID_VALUE, + audio_INVALID_OPERATION, + audio_OUT_OF_MEMORY, + audio_DRIVER_FAILURE, + + /* Source properties */ + audio_POSITION, + audio_LOOPING, + audio_BUFFER, + audio_GAIN, + audio_SOURCE_STATE, + audio_BUFFERS_QUEUED, + audio_BUFFERS_PROCESSED, + + /* Source state information */ + audio_INITIAL, + audio_STOPPED, + audio_PLAYING, + audio_PAUSED, + + /* Sound buffer properties */ + audio_FREQUENCY, + audio_BITS, + audio_CHANNELS, + audio_SIZE, + audio_FORMAT_MONO16, + audio_FORMAT_STEREO16, + audio_FORMAT_MONO8, + audio_FORMAT_STEREO8, + audio_ENUM_SIZE +}; + +extern int snddriver, soundflags; + +typedef struct { + /* General */ + void (* Uninitialize) (void); + sint32 (* GetError) (void); + sint32 driverID; + sint32 EnumLookup[audio_ENUM_SIZE]; + + /* Sources */ + void (* GenSources) (uint32 n, audio_Object *psrcobj); + void (* DeleteSources) (uint32 n, audio_Object *psrcobj); + bool (* IsSource) (audio_Object srcobj); + void (* Sourcei) (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal value); + void (* Sourcef) (audio_Object srcobj, audio_SourceProp pname, + float value); + void (* Sourcefv) (audio_Object srcobj, audio_SourceProp pname, + float *value); + void (* GetSourcei) (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal *value); + void (* GetSourcef) (audio_Object srcobj, audio_SourceProp pname, + float *value); + void (* SourceRewind) (audio_Object srcobj); + void (* SourcePlay) (audio_Object srcobj); + void (* SourcePause) (audio_Object srcobj); + void (* SourceStop) (audio_Object srcobj); + void (* SourceQueueBuffers) (audio_Object srcobj, uint32 n, + audio_Object* pbufobj); + void (* SourceUnqueueBuffers) (audio_Object srcobj, uint32 n, + audio_Object* pbufobj); + + /* Buffers */ + void (* GenBuffers) (uint32 n, audio_Object *pbufobj); + void (* DeleteBuffers) (uint32 n, audio_Object *pbufobj); + bool (* IsBuffer) (audio_Object bufobj); + void (* GetBufferi) (audio_Object bufobj, audio_BufferProp pname, + audio_IntVal *value); + void (* BufferData) (audio_Object bufobj, uint32 format, void* data, + uint32 size, uint32 freq); +} audio_Driver; + + +/* Initialization */ +sint32 initAudio (sint32 driver, sint32 flags); +void unInitAudio (void); + +/* General */ +sint32 audio_GetError (void); + +/* Sources */ +void audio_GenSources (uint32 n, audio_Object *psrcobj); +void audio_DeleteSources (uint32 n, audio_Object *psrcobj); +bool audio_IsSource (audio_Object srcobj); +void audio_Sourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal value); +void audio_Sourcef (audio_Object srcobj, audio_SourceProp pname, + float value); +void audio_Sourcefv (audio_Object srcobj, audio_SourceProp pname, + float *value); +void audio_GetSourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal *value); +void audio_GetSourcef (audio_Object srcobj, audio_SourceProp pname, + float *value); +void audio_SourceRewind (audio_Object srcobj); +void audio_SourcePlay (audio_Object srcobj); +void audio_SourcePause (audio_Object srcobj); +void audio_SourceStop (audio_Object srcobj); +void audio_SourceQueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj); +void audio_SourceUnqueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj); + +/* Buffers */ +void audio_GenBuffers (uint32 n, audio_Object *pbufobj); +void audio_DeleteBuffers (uint32 n, audio_Object *pbufobj); +bool audio_IsBuffer (audio_Object bufobj); +void audio_GetBufferi (audio_Object bufobj, audio_BufferProp pname, + audio_IntVal *value); +void audio_BufferData (audio_Object bufobj, uint32 format, void* data, + uint32 size, uint32 freq); + +bool audio_GetFormatInfo (uint32 format, int *channels, int *sample_size); + +#endif /* LIBS_SOUND_AUDIOCORE_H_ */ diff --git a/src/libs/sound/decoders/Makeinfo b/src/libs/sound/decoders/Makeinfo new file mode 100644 index 0000000..e1735a1 --- /dev/null +++ b/src/libs/sound/decoders/Makeinfo @@ -0,0 +1,8 @@ +uqm_CFILES="decoder.c aiffaud.c wav.c dukaud.c modaud.c" +uqm_HFILES="aiffaud.h decoder.h dukaud.h modaud.h wav.h" + +if [ "$uqm_OGGVORBIS" '!=' "none" ]; then + uqm_CFILES="$uqm_CFILES oggaud.c" + uqm_HFILES="$uqm_HFILES oggaud.h" +fi + diff --git a/src/libs/sound/decoders/aiffaud.c b/src/libs/sound/decoders/aiffaud.c new file mode 100644 index 0000000..102a78e --- /dev/null +++ b/src/libs/sound/decoders/aiffaud.c @@ -0,0 +1,650 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Portions (C) Serge van den Boom (svdb at stack.nl) */ +/* Portions (C) Alex Volkov (codepro at usa.net) */ + +/* AIFF decoder (.aif) + * + * Doesn't work on *all* aiff files in general, only 8/16 PCM and + * 16-bit AIFF-C SDX2-compressed. + */ + +#include <stdio.h> +#include <stdlib.h> + // for abs() +#include <errno.h> +#ifndef _WIN32_WCE +# include <memory.h> +#endif +#include <string.h> +#include "port.h" +#include "types.h" +#include "libs/uio.h" +#include "endian_uqm.h" +#include "libs/log.h" +#include "aiffaud.h" + +typedef uint32 aiff_ID; + +#define aiff_MAKE_ID(x1, x2, x3, x4) \ + (((x1) << 24) | ((x2) << 16) | ((x3) << 8) | (x4)) + +#define aiff_FormID aiff_MAKE_ID('F', 'O', 'R', 'M') +#define aiff_FormVersionID aiff_MAKE_ID('F', 'V', 'E', 'R') +#define aiff_CommonID aiff_MAKE_ID('C', 'O', 'M', 'M') +#define aiff_SoundDataID aiff_MAKE_ID('S', 'S', 'N', 'D') + +#define aiff_FormTypeAIFF aiff_MAKE_ID('A', 'I', 'F', 'F') +#define aiff_FormTypeAIFC aiff_MAKE_ID('A', 'I', 'F', 'C') + +#define aiff_CompressionTypeSDX2 aiff_MAKE_ID('S', 'D', 'X', '2') + + +typedef struct +{ + aiff_ID id; + uint32 size; +} aiff_ChunkHeader; + +#define AIFF_CHUNK_HDR_SIZE (4+4) + +typedef struct +{ + aiff_ChunkHeader chunk; + aiff_ID type; +} aiff_FileHeader; + +typedef struct +{ + uint32 version; /* format version, in Mac format */ +} aiff_FormatVersionChunk; + +typedef struct +{ + uint16 channels; /* number of channels */ + uint32 sampleFrames; /* number of sample frames */ + uint16 sampleSize; /* number of bits per sample */ + sint32 sampleRate; /* number of frames per second */ + /* this is actually stored as IEEE-754 80bit in files */ +} aiff_CommonChunk; + +#define AIFF_COMM_SIZE (2+4+2+10) + +typedef struct +{ + uint16 channels; /* number of channels */ + uint32 sampleFrames; /* number of sample frames */ + uint16 sampleSize; /* number of bits per sample */ + sint32 sampleRate; /* number of frames per second */ + aiff_ID extTypeID; /* compression type ID */ + char extName[32]; /* compression type name */ +} aiff_ExtCommonChunk; + +#define AIFF_EXT_COMM_SIZE (AIFF_COMM_SIZE+4) + +typedef struct +{ + uint32 offset; /* offset to sound data */ + uint32 blockSize; /* size of alignment blocks */ +} aiff_SoundDataChunk; + +#define AIFF_SSND_SIZE (4+4) + + +#define THIS_PTR TFB_SoundDecoder* This + +static const char* aifa_GetName (void); +static bool aifa_InitModule (int flags, const TFB_DecoderFormats*); +static void aifa_TermModule (void); +static uint32 aifa_GetStructSize (void); +static int aifa_GetError (THIS_PTR); +static bool aifa_Init (THIS_PTR); +static void aifa_Term (THIS_PTR); +static bool aifa_Open (THIS_PTR, uio_DirHandle *dir, const char *filename); +static void aifa_Close (THIS_PTR); +static int aifa_Decode (THIS_PTR, void* buf, sint32 bufsize); +static uint32 aifa_Seek (THIS_PTR, uint32 pcm_pos); +static uint32 aifa_GetFrame (THIS_PTR); + +TFB_SoundDecoderFuncs aifa_DecoderVtbl = +{ + aifa_GetName, + aifa_InitModule, + aifa_TermModule, + aifa_GetStructSize, + aifa_GetError, + aifa_Init, + aifa_Term, + aifa_Open, + aifa_Close, + aifa_Decode, + aifa_Seek, + aifa_GetFrame, +}; + + +typedef enum +{ + aifc_None, + aifc_Sdx2, +} aiff_CompressionType; + +#define MAX_CHANNELS 4 + +typedef struct tfb_wavesounddecoder +{ + // always the first member + TFB_SoundDecoder decoder; + + // private + sint32 last_error; + uio_Stream *fp; + aiff_ExtCommonChunk fmtHdr; + aiff_CompressionType comp_type; + unsigned bits_per_sample; + unsigned block_align; + unsigned file_block; + uint32 data_ofs; + uint32 data_size; + uint32 max_pcm; + uint32 cur_pcm; + sint32 prev_val[MAX_CHANNELS]; + +} TFB_AiffSoundDecoder; + +static const TFB_DecoderFormats* aifa_formats = NULL; + +static int aifa_DecodePCM (TFB_AiffSoundDecoder*, void* buf, sint32 bufsize); +static int aifa_DecodeSDX2 (TFB_AiffSoundDecoder*, void* buf, sint32 bufsize); + + +static const char* +aifa_GetName (void) +{ + return "AIFF"; +} + +static bool +aifa_InitModule (int flags, const TFB_DecoderFormats* fmts) +{ + aifa_formats = fmts; + return true; + + (void)flags; // laugh at compiler warning +} + +static void +aifa_TermModule (void) +{ + // no specific module term +} + +static uint32 +aifa_GetStructSize (void) +{ + return sizeof (TFB_AiffSoundDecoder); +} + +static int +aifa_GetError (THIS_PTR) +{ + TFB_AiffSoundDecoder* aifa = (TFB_AiffSoundDecoder*) This; + int ret = aifa->last_error; + aifa->last_error = 0; + return ret; +} + +static bool +aifa_Init (THIS_PTR) +{ + //TFB_AiffSoundDecoder* aifa = (TFB_AiffSoundDecoder*) This; + This->need_swap = !aifa_formats->want_big_endian; + return true; +} + +static void +aifa_Term (THIS_PTR) +{ + //TFB_AiffSoundDecoder* aifa = (TFB_AiffSoundDecoder*) This; + aifa_Close (This); // ensure cleanup +} + +static bool +read_be_16 (uio_Stream *fp, uint16 *v) +{ + if (!uio_fread (v, sizeof(*v), 1, fp)) + return false; + *v = UQM_SwapBE16 (*v); + return true; +} + +static bool +read_be_32 (uio_Stream *fp, uint32 *v) +{ + if (!uio_fread (v, sizeof(*v), 1, fp)) + return false; + *v = UQM_SwapBE32 (*v); + return true; +} + +// Read 80-bit IEEE 754 floating point number. +// We are only interested in values that we can work with, +// so using an sint32 here is fine. +static bool +read_be_f80 (uio_Stream *fp, sint32 *v) +{ + int sign, exp; + int shift; + uint16 se; + uint32 mant, mant_low; + if (!read_be_16 (fp, &se) || + !read_be_32 (fp, &mant) || !read_be_32 (fp, &mant_low)) + return false; + + sign = (se >> 15) & 1; // sign is the highest bit + exp = (se & ((1 << 15) - 1)); // exponent is next highest 15 bits +#if 0 // XXX: 80bit IEEE 754 used in AIFF uses explicit mantissa MS bit + // mantissa has an implied leading bit which is typically 1 + mant >>= 1; + if (exp != 0) + mant |= 0x80000000; +#endif + mant >>= 1; // we also need space for sign + exp -= (1 << 14) - 1; // exponent is biased by (2^(e-1) - 1) + shift = exp - 31 + 1; // mantissa is already 31 bits before decimal pt. + if (shift > 0) + mant = 0x7fffffff; // already too big + else if (shift < 0) + mant >>= -shift; + + *v = sign ? -(sint32)mant : (sint32)mant; + + return true; +} + +static bool +aifa_readFileHeader (TFB_AiffSoundDecoder* aifa, aiff_FileHeader* hdr) +{ + if (!read_be_32 (aifa->fp, &hdr->chunk.id) || + !read_be_32 (aifa->fp, &hdr->chunk.size) || + !read_be_32 (aifa->fp, &hdr->type)) + { + aifa->last_error = errno; + return false; + } + return true; +} + +static bool +aifa_readChunkHeader (TFB_AiffSoundDecoder* aifa, aiff_ChunkHeader* hdr) +{ + if (!read_be_32 (aifa->fp, &hdr->id) || + !read_be_32 (aifa->fp, &hdr->size)) + { + aifa->last_error = errno; + return false; + } + return true; +} + +static int +aifa_readCommonChunk (TFB_AiffSoundDecoder* aifa, uint32 size, + aiff_ExtCommonChunk* fmt) +{ + int bytes; + + memset(fmt, 0, sizeof(*fmt)); + if (size < AIFF_COMM_SIZE) + { + aifa->last_error = aifae_BadFile; + return 0; + } + + if (!read_be_16 (aifa->fp, &fmt->channels) || + !read_be_32 (aifa->fp, &fmt->sampleFrames) || + !read_be_16 (aifa->fp, &fmt->sampleSize) || + !read_be_f80 (aifa->fp, &fmt->sampleRate)) + { + aifa->last_error = errno; + return 0; + } + bytes = AIFF_COMM_SIZE; + + if (size >= AIFF_EXT_COMM_SIZE) + { + if (!read_be_32 (aifa->fp, &fmt->extTypeID)) + { + aifa->last_error = errno; + return 0; + } + bytes += sizeof(fmt->extTypeID); + } + + return bytes; +} + +static bool +aifa_readSoundDataChunk (TFB_AiffSoundDecoder* aifa, + aiff_SoundDataChunk* data) +{ + if (!read_be_32 (aifa->fp, &data->offset) || + !read_be_32 (aifa->fp, &data->blockSize)) + { + aifa->last_error = errno; + return false; + } + return true; +} + +static bool +aifa_Open (THIS_PTR, uio_DirHandle *dir, const char *filename) +{ + TFB_AiffSoundDecoder* aifa = (TFB_AiffSoundDecoder*) This; + aiff_FileHeader fileHdr; + aiff_ChunkHeader chunkHdr; + sint32 remSize; + + aifa->fp = uio_fopen (dir, filename, "rb"); + if (!aifa->fp) + { + aifa->last_error = errno; + return false; + } + + aifa->data_size = 0; + aifa->max_pcm = 0; + aifa->data_ofs = 0; + memset(&aifa->fmtHdr, 0, sizeof(aifa->fmtHdr)); + memset(aifa->prev_val, 0, sizeof(aifa->prev_val)); + + // read wave header + if (!aifa_readFileHeader (aifa, &fileHdr)) + { + aifa->last_error = errno; + aifa_Close (This); + return false; + } + if (fileHdr.chunk.id != aiff_FormID) + { + log_add (log_Warning, "aifa_Open(): not an aiff file, ID 0x%08x", + fileHdr.chunk.id); + aifa_Close (This); + return false; + } + if (fileHdr.type != aiff_FormTypeAIFF && fileHdr.type != aiff_FormTypeAIFC) + { + log_add (log_Warning, "aifa_Open(): unsupported aiff file" + ", Type 0x%08x", fileHdr.type); + aifa_Close (This); + return false; + } + + for (remSize = fileHdr.chunk.size - sizeof(aiff_ID); remSize > 0; + remSize -= ((chunkHdr.size + 1) & ~1) + AIFF_CHUNK_HDR_SIZE) + { + if (!aifa_readChunkHeader (aifa, &chunkHdr)) + { + aifa_Close (This); + return false; + } + + if (chunkHdr.id == aiff_CommonID) + { + int read = aifa_readCommonChunk (aifa, chunkHdr.size, &aifa->fmtHdr); + if (!read) + { + aifa_Close (This); + return false; + } + uio_fseek (aifa->fp, chunkHdr.size - read, SEEK_CUR); + } + else if (chunkHdr.id == aiff_SoundDataID) + { + aiff_SoundDataChunk data; + if (!aifa_readSoundDataChunk (aifa, &data)) + { + aifa_Close (This); + return false; + } + aifa->data_ofs = uio_ftell (aifa->fp) + data.offset; + uio_fseek (aifa->fp, chunkHdr.size - AIFF_SSND_SIZE, SEEK_CUR); + } + else + { // skip uninteresting chunk + uio_fseek (aifa->fp, chunkHdr.size, SEEK_CUR); + } + + // 2-align the file ptr + uio_fseek (aifa->fp, chunkHdr.size & 1, SEEK_CUR); + } + + if (aifa->fmtHdr.sampleFrames == 0) + { + log_add (log_Warning, "aifa_Open(): aiff file has no sound data"); + aifa_Close (This); + return false; + } + + // make bits-per-sample a multiple of 8 + aifa->bits_per_sample = (aifa->fmtHdr.sampleSize + 7) & ~7; + if (aifa->bits_per_sample == 0 || aifa->bits_per_sample > 16) + { // XXX: for now we do not support 24 and 32 bps + log_add (log_Warning, "aifa_Open(): unsupported sample size %u", + aifa->bits_per_sample); + aifa_Close (This); + return false; + } + if (aifa->fmtHdr.channels != 1 && aifa->fmtHdr.channels != 2) + { + log_add (log_Warning, "aifa_Open(): unsupported number of channels %u", + (unsigned)aifa->fmtHdr.channels); + aifa_Close (This); + return false; + } + if (aifa->fmtHdr.sampleRate < 300 || aifa->fmtHdr.sampleRate > 128000) + { + log_add (log_Warning, "aifa_Open(): unsupported sampling rate %ld", + (long)aifa->fmtHdr.sampleRate); + aifa_Close (This); + return false; + } + + aifa->block_align = aifa->bits_per_sample / 8 * aifa->fmtHdr.channels; + aifa->file_block = aifa->block_align; + if (!aifa->data_ofs) + { + log_add (log_Warning, "aifa_Open(): bad aiff file," + " no SSND chunk found"); + aifa_Close (This); + return false; + } + + if (fileHdr.type == aiff_FormTypeAIFF) + { + if (aifa->fmtHdr.extTypeID != 0) + { + log_add (log_Warning, "aifa_Open(): unsupported extension 0x%08x", + aifa->fmtHdr.extTypeID); + aifa_Close (This); + return false; + } + aifa->comp_type = aifc_None; + } + else if (fileHdr.type == aiff_FormTypeAIFC) + { + if (aifa->fmtHdr.extTypeID != aiff_CompressionTypeSDX2) + { + log_add (log_Warning, "aifa_Open(): unsupported compression 0x%08x", + aifa->fmtHdr.extTypeID); + aifa_Close (This); + return false; + } + aifa->comp_type = aifc_Sdx2; + aifa->file_block /= 2; + assert(aifa->fmtHdr.channels <= MAX_CHANNELS); + // after decompression, we will get samples in machine byte order + This->need_swap = (aifa_formats->big_endian + != aifa_formats->want_big_endian); + } + + aifa->data_size = aifa->fmtHdr.sampleFrames * aifa->file_block; + + if (aifa->comp_type == aifc_Sdx2 && aifa->bits_per_sample != 16) + { + log_add (log_Warning, "aifa_Open(): unsupported sample size %u for SDX2", + (unsigned)aifa->fmtHdr.sampleSize); + aifa_Close (This); + return false; + } + + This->format = (aifa->fmtHdr.channels == 1 ? + (aifa->bits_per_sample == 8 ? + aifa_formats->mono8 : aifa_formats->mono16) + : + (aifa->bits_per_sample == 8 ? + aifa_formats->stereo8 : aifa_formats->stereo16) + ); + This->frequency = aifa->fmtHdr.sampleRate; + + uio_fseek (aifa->fp, aifa->data_ofs, SEEK_SET); + aifa->max_pcm = aifa->fmtHdr.sampleFrames; + aifa->cur_pcm = 0; + This->length = (float) aifa->max_pcm / aifa->fmtHdr.sampleRate; + aifa->last_error = 0; + + return true; +} + +static void +aifa_Close (THIS_PTR) +{ + TFB_AiffSoundDecoder* aifa = (TFB_AiffSoundDecoder*) This; + + if (aifa->fp) + { + uio_fclose (aifa->fp); + aifa->fp = NULL; + } +} + +static int +aifa_Decode (THIS_PTR, void* buf, sint32 bufsize) +{ + TFB_AiffSoundDecoder* aifa = (TFB_AiffSoundDecoder*) This; + switch (aifa->comp_type) + { + case aifc_None: + return aifa_DecodePCM (aifa, buf, bufsize); + case aifc_Sdx2: + return aifa_DecodeSDX2 (aifa, buf, bufsize); + default: + assert(false && "Unknown comp_type"); + return 0; + } +} + +static int +aifa_DecodePCM (TFB_AiffSoundDecoder* aifa, void* buf, sint32 bufsize) +{ + uint32 dec_pcm; + uint32 size; + + dec_pcm = bufsize / aifa->block_align; + if (dec_pcm > aifa->max_pcm - aifa->cur_pcm) + dec_pcm = aifa->max_pcm - aifa->cur_pcm; + + dec_pcm = uio_fread (buf, aifa->file_block, dec_pcm, aifa->fp); + aifa->cur_pcm += dec_pcm; + size = dec_pcm * aifa->block_align; + + if (aifa->bits_per_sample == 8) + { // AIFF files store 8-bit data as signed + // and we need it unsigned + uint8* ptr = (uint8*)buf; + uint32 left; + for (left = size; left > 0; --left, ++ptr) + *ptr += 128; + } + + return size; +} + +static int +aifa_DecodeSDX2 (TFB_AiffSoundDecoder* aifa, void* buf, sint32 bufsize) +{ + uint32 dec_pcm; + sint8 *src; + sint16 *dst = buf; + uint32 left; + + dec_pcm = bufsize / aifa->block_align; + if (dec_pcm > aifa->max_pcm - aifa->cur_pcm) + dec_pcm = aifa->max_pcm - aifa->cur_pcm; + + src = (sint8*)buf + bufsize - (dec_pcm * aifa->file_block); + dec_pcm = uio_fread (src, aifa->file_block, dec_pcm, aifa->fp); + aifa->cur_pcm += dec_pcm; + + for (left = dec_pcm; left > 0; --left) + { + int i; + sint32 *prev = aifa->prev_val; + for (i = aifa->fmtHdr.channels; i > 0; --i, ++prev, ++src, ++dst) + { + sint32 v = (*src * abs(*src)) << 1; + if (*src & 1) + v += *prev; + // saturate the value + if (v > 32767) + v = 32767; + else if (v < -32768) + v = -32768; + *prev = v; + *dst = v; + } + } + + return dec_pcm * aifa->block_align; +} + +static uint32 +aifa_Seek (THIS_PTR, uint32 pcm_pos) +{ + TFB_AiffSoundDecoder* aifa = (TFB_AiffSoundDecoder*) This; + + if (pcm_pos > aifa->max_pcm) + pcm_pos = aifa->max_pcm; + aifa->cur_pcm = pcm_pos; + uio_fseek (aifa->fp, + aifa->data_ofs + pcm_pos * aifa->file_block, + SEEK_SET); + + // reset previous values for SDX2 on seek ops + // the delta will recover faster with reset + memset(aifa->prev_val, 0, sizeof(aifa->prev_val)); + + return pcm_pos; +} + +static uint32 +aifa_GetFrame (THIS_PTR) +{ + //TFB_AiffSoundDecoder* aifa = (TFB_AiffSoundDecoder*) This; + return 0; // only 1 frame for now + + (void)This; // laugh at compiler warning +} diff --git a/src/libs/sound/decoders/aiffaud.h b/src/libs/sound/decoders/aiffaud.h new file mode 100644 index 0000000..36c6679 --- /dev/null +++ b/src/libs/sound/decoders/aiffaud.h @@ -0,0 +1,36 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* AIFF decoder */ + +#ifndef AIFFAUD_H +#define AIFFAUD_H + +#include "decoder.h" + +extern TFB_SoundDecoderFuncs aifa_DecoderVtbl; + +typedef enum +{ + // positive values are the same as in errno + aifae_None = 0, + aifae_Unknown = -1, + aifae_BadFile = -2, + aifae_BadArg = -3, + aifae_Other = -1000, +} aifa_Error; + +#endif /* AIFFAUD_H */ diff --git a/src/libs/sound/decoders/decoder.c b/src/libs/sound/decoders/decoder.c new file mode 100644 index 0000000..8c20877 --- /dev/null +++ b/src/libs/sound/decoders/decoder.c @@ -0,0 +1,936 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Sound file decoder for .wav, .mod, .ogg (to be used with OpenAL) + * API is heavily influenced by SDL_sound. + */ + +#include <string.h> +#include <stdlib.h> +#include "port.h" +#include "libs/memlib.h" +#include "libs/file.h" +#include "libs/log.h" +#include "decoder.h" +#include "wav.h" +#include "dukaud.h" +#include "modaud.h" +#ifndef OVCODEC_NONE +# include "oggaud.h" +#endif /* OVCODEC_NONE */ +#include "aiffaud.h" + + +#define MAX_REG_DECODERS 31 + +#define THIS_PTR TFB_SoundDecoder* + +static const char* bufa_GetName (void); +static bool bufa_InitModule (int flags, const TFB_DecoderFormats*); +static void bufa_TermModule (void); +static uint32 bufa_GetStructSize (void); +static int bufa_GetError (THIS_PTR); +static bool bufa_Init (THIS_PTR); +static void bufa_Term (THIS_PTR); +static bool bufa_Open (THIS_PTR, uio_DirHandle *dir, const char *filename); +static void bufa_Close (THIS_PTR); +static int bufa_Decode (THIS_PTR, void* buf, sint32 bufsize); +static uint32 bufa_Seek (THIS_PTR, uint32 pcm_pos); +static uint32 bufa_GetFrame (THIS_PTR); + +TFB_SoundDecoderFuncs bufa_DecoderVtbl = +{ + bufa_GetName, + bufa_InitModule, + bufa_TermModule, + bufa_GetStructSize, + bufa_GetError, + bufa_Init, + bufa_Term, + bufa_Open, + bufa_Close, + bufa_Decode, + bufa_Seek, + bufa_GetFrame, +}; + +typedef struct tfb_bufsounddecoder +{ + // always the first member + TFB_SoundDecoder decoder; + + // private + void* data; + uint32 max_pcm; + uint32 cur_pcm; + +} TFB_BufSoundDecoder; + +#define SD_MIN_SIZE (sizeof (TFB_BufSoundDecoder)) + +static const char* nula_GetName (void); +static bool nula_InitModule (int flags, const TFB_DecoderFormats*); +static void nula_TermModule (void); +static uint32 nula_GetStructSize (void); +static int nula_GetError (THIS_PTR); +static bool nula_Init (THIS_PTR); +static void nula_Term (THIS_PTR); +static bool nula_Open (THIS_PTR, uio_DirHandle *dir, const char *filename); +static void nula_Close (THIS_PTR); +static int nula_Decode (THIS_PTR, void* buf, sint32 bufsize); +static uint32 nula_Seek (THIS_PTR, uint32 pcm_pos); +static uint32 nula_GetFrame (THIS_PTR); + +TFB_SoundDecoderFuncs nula_DecoderVtbl = +{ + nula_GetName, + nula_InitModule, + nula_TermModule, + nula_GetStructSize, + nula_GetError, + nula_Init, + nula_Term, + nula_Open, + nula_Close, + nula_Decode, + nula_Seek, + nula_GetFrame, +}; + +typedef struct tfb_nullsounddecoder +{ + // always the first member + TFB_SoundDecoder decoder; + + // private + uint32 cur_pcm; + +} TFB_NullSoundDecoder; + +#undef THIS_PTR + + +struct TFB_RegSoundDecoder +{ + bool builtin; + bool used; // ever used indicator + const char* ext; + const TFB_SoundDecoderFuncs* funcs; +}; +static TFB_RegSoundDecoder sd_decoders[MAX_REG_DECODERS + 1] = +{ + {true, true, "wav", &wava_DecoderVtbl}, + {true, true, "mod", &moda_DecoderVtbl}, +#ifndef OVCODEC_NONE + {true, true, "ogg", &ova_DecoderVtbl}, +#endif /* OVCODEC_NONE */ + {true, true, "duk", &duka_DecoderVtbl}, + {true, true, "aif", &aifa_DecoderVtbl}, + {false, false, NULL, NULL}, // null term +}; + +static TFB_DecoderFormats decoder_formats; +static int sd_flags = 0; + +/* change endianness of 16bit words + * Only works optimal when 'data' is aligned on a 32 bits boundary. + */ +void +SoundDecoder_SwapWords (uint16* data, uint32 size) +{ + uint32 fsize = size & (~3U); + + size -= fsize; + fsize >>= 2; + for (; fsize; fsize--, data += 2) + { + uint32 v = *(uint32*)data; + *(uint32*)data = ((v & 0x00ff00ff) << 8) + | ((v & 0xff00ff00) >> 8); + } + if (size) + { + /* leftover word */ + *data = ((*data & 0x00ff) << 8) | ((*data & 0xff00) >> 8); + } +} + +const char* +SoundDecoder_GetName (TFB_SoundDecoder *decoder) +{ + if (!decoder || !decoder->funcs) + return "(Null)"; + return decoder->funcs->GetName (); +} + +sint32 +SoundDecoder_Init (int flags, TFB_DecoderFormats *formats) +{ + TFB_RegSoundDecoder* info; + sint32 ret = 0; + + if (!formats) + { + log_add (log_Error, "SoundDecoder_Init(): missing decoder formats"); + return 1; + } + decoder_formats = *formats; + + // init built-in decoders + for (info = sd_decoders; info->ext; info++) + { + if (!info->funcs->InitModule (flags, &decoder_formats)) + { + log_add (log_Error, "SoundDecoder_Init(): " + "%s audio decoder init failed", + info->funcs->GetName ()); + ret = 1; + } + } + + sd_flags = flags; + + return ret; +} + +void +SoundDecoder_Uninit (void) +{ + TFB_RegSoundDecoder* info; + + // uninit all decoders + // and unregister loaded decoders + for (info = sd_decoders; info->used; info++) + { + if (info->ext) // check if present + info->funcs->TermModule (); + + if (!info->builtin) + { + info->used = false; + info->ext = NULL; + } + } +} + +TFB_RegSoundDecoder* +SoundDecoder_Register (const char* fileext, TFB_SoundDecoderFuncs* decvtbl) +{ + TFB_RegSoundDecoder* info; + TFB_RegSoundDecoder* newslot = NULL; + + if (!decvtbl) + { + log_add (log_Warning, "SoundDecoder_Register(): Null decoder table"); + return NULL; + } + if (!fileext) + { + log_add (log_Warning, "SoundDecoder_Register(): Bad file type for %s", + decvtbl->GetName ()); + return NULL; + } + + // check if extension already registered + for (info = sd_decoders; info->used && + (!info->ext || strcmp (info->ext, fileext) != 0); + ++info) + { + // and pick up an empty slot (where available) + if (!newslot && !info->ext) + newslot = info; + } + + if (info >= sd_decoders + MAX_REG_DECODERS) + { + log_add (log_Warning, "SoundDecoder_Register(): Decoders limit reached"); + return NULL; + } + else if (info->ext) + { + log_add (log_Warning, "SoundDecoder_Register(): " + "'%s' decoder already registered (%s denied)", + fileext, decvtbl->GetName ()); + return NULL; + } + + if (!decvtbl->InitModule (sd_flags, &decoder_formats)) + { + log_add (log_Warning, "SoundDecoder_Register(): %s decoder init failed", + decvtbl->GetName ()); + return NULL; + } + + if (!newslot) + { + newslot = info; + newslot->used = true; + // make next one a term + info[1].builtin = false; + info[1].used = false; + info[1].ext = NULL; + } + + newslot->ext = fileext; + newslot->funcs = decvtbl; + + return newslot; +} + +void +SoundDecoder_Unregister (TFB_RegSoundDecoder* regdec) +{ + if (regdec < sd_decoders || regdec >= sd_decoders + MAX_REG_DECODERS || + !regdec->ext || !regdec->funcs) + { + log_add (log_Warning, "SoundDecoder_Unregister(): " + "Invalid or expired decoder passed"); + return; + } + + regdec->funcs->TermModule (); + regdec->ext = NULL; + regdec->funcs = NULL; +} + +const TFB_SoundDecoderFuncs* +SoundDecoder_Lookup (const char* fileext) +{ + TFB_RegSoundDecoder* info; + + for (info = sd_decoders; info->used && + (!info->ext || strcmp (info->ext, fileext) != 0); + ++info) + ; + return info->ext ? info->funcs : NULL; +} + +TFB_SoundDecoder* +SoundDecoder_Load (uio_DirHandle *dir, char *filename, + uint32 buffer_size, uint32 startTime, sint32 runTime) + // runTime < 0 specifies a default length for a nul decoder +{ + const char* pext; + TFB_RegSoundDecoder* info; + const TFB_SoundDecoderFuncs* funcs; + TFB_SoundDecoder* decoder; + uint32 struct_size; + + pext = strrchr (filename, '.'); + if (!pext) + { + log_add (log_Warning, "SoundDecoder_Load(): Unknown file type (%s)", + filename); + return NULL; + } + ++pext; + + for (info = sd_decoders; info->used && + (!info->ext || strcmp (info->ext, pext) != 0); + ++info) + ; + if (!info->ext) + { + log_add (log_Warning, "SoundDecoder_Load(): Unsupported file type (%s)", + filename); + + if (runTime) + { + runTime = abs (runTime); + startTime = 0; + funcs = &nula_DecoderVtbl; + } + else + { + return NULL; + } + } + else + { + funcs = info->funcs; + } + + if (!fileExists2 (dir, filename)) + { + if (runTime) + { + runTime = abs (runTime); + startTime = 0; + funcs = &nula_DecoderVtbl; + } + else + { + log_add (log_Warning, "SoundDecoder_Load(): %s does not exist", + filename); + return NULL; + } + } + + struct_size = funcs->GetStructSize (); + if (struct_size < SD_MIN_SIZE) + struct_size = SD_MIN_SIZE; + + decoder = (TFB_SoundDecoder*) HCalloc (struct_size); + decoder->funcs = funcs; + if (!decoder->funcs->Init (decoder)) + { + log_add (log_Warning, "SoundDecoder_Load(): " + "%s decoder instance failed init", + decoder->funcs->GetName ()); + HFree (decoder); + return NULL; + } + + if (!decoder->funcs->Open (decoder, dir, filename)) + { + log_add (log_Warning, "SoundDecoder_Load(): " + "%s decoder could not load %s", + decoder->funcs->GetName (), filename); + decoder->funcs->Term (decoder); + HFree (decoder); + return NULL; + } + + decoder->buffer = HMalloc (buffer_size); + decoder->buffer_size = buffer_size; + decoder->looping = false; + decoder->error = SOUNDDECODER_OK; + decoder->dir = dir; + decoder->filename = (char *) HMalloc (strlen (filename) + 1); + strcpy (decoder->filename, filename); + + if (decoder->is_null) + { // fake decoder, keeps voiceovers and etc. going + decoder->length = (float) (runTime / 1000.0); + } + + decoder->length -= startTime / 1000.0f; + if (decoder->length < 0) + decoder->length = 0; + else if (runTime > 0 && runTime / 1000.0 < decoder->length) + decoder->length = (float)(runTime / 1000.0); + + decoder->start_sample = (uint32)(startTime / 1000.0f * decoder->frequency); + decoder->end_sample = decoder->start_sample + + (unsigned long)(decoder->length * decoder->frequency); + if (decoder->start_sample != 0) + decoder->funcs->Seek (decoder, decoder->start_sample); + + if (decoder->format == decoder_formats.mono8) + decoder->bytes_per_samp = 1; + else if (decoder->format == decoder_formats.mono16) + decoder->bytes_per_samp = 2; + else if (decoder->format == decoder_formats.stereo8) + decoder->bytes_per_samp = 2; + else if (decoder->format == decoder_formats.stereo16) + decoder->bytes_per_samp = 4; + + decoder->pos = decoder->start_sample * decoder->bytes_per_samp; + + return decoder; +} + +uint32 +SoundDecoder_Decode (TFB_SoundDecoder *decoder) +{ + long decoded_bytes; + long rc; + long buffer_size; + uint32 max_bytes = UINT32_MAX; + uint8 *buffer; + + if (!decoder || !decoder->funcs) + { + log_add (log_Warning, "SoundDecoder_Decode(): null or bad decoder"); + return 0; + } + + buffer = (uint8*) decoder->buffer; + buffer_size = decoder->buffer_size; + if (!decoder->looping && decoder->end_sample > 0) + { + max_bytes = decoder->end_sample * decoder->bytes_per_samp; + if (max_bytes - decoder->pos < decoder->buffer_size) + buffer_size = max_bytes - decoder->pos; + } + + if (buffer_size == 0) + { // nothing more to decode + decoder->error = SOUNDDECODER_EOF; + return 0; + } + + for (decoded_bytes = 0, rc = 1; rc > 0 && decoded_bytes < buffer_size; ) + { + rc = decoder->funcs->Decode (decoder, buffer + decoded_bytes, + buffer_size - decoded_bytes); + if (rc < 0) + { + log_add (log_Warning, "SoundDecoder_Decode(): " + "error decoding %s, code %ld", + decoder->filename, rc); + } + else if (rc == 0) + { // probably EOF + if (decoder->looping) + { + SoundDecoder_Rewind (decoder); + if (decoder->error) + { + log_add (log_Warning, "SoundDecoder_Decode(): " + "tried to loop %s but couldn't rewind, " + "error code %d", + decoder->filename, decoder->error); + } + else + { + log_add (log_Info, "SoundDecoder_Decode(): " + "looping %s", decoder->filename); + rc = 1; // prime the loop again + } + } + else + { + log_add (log_Info, "SoundDecoder_Decode(): eof for %s", + decoder->filename); + } + } + else + { // some bytes decoded + decoded_bytes += rc; + } + } + decoder->pos += decoded_bytes; + if (rc < 0) + decoder->error = SOUNDDECODER_ERROR; + else if (rc == 0 || decoder->pos >= max_bytes) + decoder->error = SOUNDDECODER_EOF; + else + decoder->error = SOUNDDECODER_OK; + + if (decoder->need_swap && decoded_bytes > 0 && + (decoder->format == decoder_formats.stereo16 || + decoder->format == decoder_formats.mono16)) + { + SoundDecoder_SwapWords ( + decoder->buffer, decoded_bytes); + } + + return decoded_bytes; +} + +uint32 +SoundDecoder_DecodeAll (TFB_SoundDecoder *decoder) +{ + uint32 decoded_bytes; + long rc; + uint32 reqbufsize; + + if (!decoder || !decoder->funcs) + { + log_add (log_Warning, "SoundDecoder_DecodeAll(): null or bad decoder"); + return 0; + } + + reqbufsize = decoder->buffer_size; + + if (decoder->looping) + { + log_add (log_Warning, "SoundDecoder_DecodeAll(): " + "called for %s with looping", decoder->filename); + return 0; + } + + if (reqbufsize < 4096) + reqbufsize = 4096; + + for (decoded_bytes = 0, rc = 1; rc > 0; ) + { + if (decoded_bytes >= decoder->buffer_size) + { // need to grow buffer + decoder->buffer_size += reqbufsize; + decoder->buffer = HRealloc ( + decoder->buffer, decoder->buffer_size); + } + + rc = decoder->funcs->Decode (decoder, + (uint8*) decoder->buffer + decoded_bytes, + decoder->buffer_size - decoded_bytes); + + if (rc > 0) + decoded_bytes += rc; + } + decoder->buffer_size = decoded_bytes; + decoder->pos += decoded_bytes; + // Free up some unused memory + decoder->buffer = HRealloc (decoder->buffer, decoded_bytes); + + if (decoder->need_swap && decoded_bytes > 0 && + (decoder->format == decoder_formats.stereo16 || + decoder->format == decoder_formats.mono16)) + { + SoundDecoder_SwapWords ( + decoder->buffer, decoded_bytes); + } + + if (rc < 0) + { + decoder->error = SOUNDDECODER_ERROR; + log_add (log_Warning, "SoundDecoder_DecodeAll(): " + "error decoding %s, code %ld", + decoder->filename, rc); + return decoded_bytes; + } + + // switch to Buffer decoder + decoder->funcs->Close (decoder); + decoder->funcs->Term (decoder); + + decoder->funcs = &bufa_DecoderVtbl; + decoder->funcs->Init (decoder); + decoder->pos = 0; + decoder->start_sample = 0; + decoder->error = SOUNDDECODER_OK; + + return decoded_bytes; +} + +void +SoundDecoder_Rewind (TFB_SoundDecoder *decoder) +{ + SoundDecoder_Seek (decoder, 0); +} + +// seekTime is specified in mili-seconds +void +SoundDecoder_Seek (TFB_SoundDecoder *decoder, uint32 seekTime) +{ + uint32 pcm_pos; + + if (!decoder) + return; + if (!decoder->funcs) + { + log_add (log_Warning, "SoundDecoder_Seek(): bad decoder passed"); + return; + } + + pcm_pos = (uint32) (seekTime / 1000.0f * decoder->frequency); + pcm_pos = decoder->funcs->Seek (decoder, + decoder->start_sample + pcm_pos); + decoder->pos = pcm_pos * decoder->bytes_per_samp; + decoder->error = SOUNDDECODER_OK; +} + +void +SoundDecoder_Free (TFB_SoundDecoder *decoder) +{ + if (!decoder) + return; + if (!decoder->funcs) + { + log_add (log_Warning, "SoundDecoder_Free(): bad decoder passed"); + return; + } + + decoder->funcs->Close (decoder); + decoder->funcs->Term (decoder); + + HFree (decoder->buffer); + HFree (decoder->filename); + HFree (decoder); +} + +float +SoundDecoder_GetTime (TFB_SoundDecoder *decoder) +{ + if (!decoder) + return 0.0f; + if (!decoder->funcs) + { + log_add (log_Warning, "SoundDecoder_GetTime(): bad decoder passed"); + return 0.0f; + } + + return (float) + ((decoder->pos / decoder->bytes_per_samp) + - decoder->start_sample + ) / decoder->frequency; +} + +uint32 +SoundDecoder_GetFrame (TFB_SoundDecoder *decoder) +{ + if (!decoder) + return 0; + if (!decoder->funcs) + { + log_add (log_Warning, "SoundDecoder_GetFrame(): bad decoder passed"); + return 0; + } + + return decoder->funcs->GetFrame (decoder); +} + + +#define THIS_PTR TFB_SoundDecoder* This + +static const char* +bufa_GetName (void) +{ + return "Buffer"; +} + +static bool +bufa_InitModule (int flags, const TFB_DecoderFormats* fmts) +{ + // this should never be called + log_add (log_Debug, "bufa_InitModule(): dead function called"); + return false; + + (void)flags; (void)fmts; // laugh at compiler warning +} + +static void +bufa_TermModule (void) +{ + // this should never be called + log_add (log_Debug, "bufa_TermModule(): dead function called"); +} + +static uint32 +bufa_GetStructSize (void) +{ + return sizeof (TFB_BufSoundDecoder); +} + +static int +bufa_GetError (THIS_PTR) +{ + return 0; // error? what error?! + + (void)This; // laugh at compiler warning +} + +static bool +bufa_Init (THIS_PTR) +{ + TFB_BufSoundDecoder* bufa = (TFB_BufSoundDecoder*) This; + + This->need_swap = false; + // hijack the buffer + bufa->data = This->buffer; + bufa->max_pcm = This->buffer_size / This->bytes_per_samp; + bufa->cur_pcm = bufa->max_pcm; + + return true; +} + +static void +bufa_Term (THIS_PTR) +{ + //TFB_BufSoundDecoder* bufa = (TFB_BufSoundDecoder*) This; + bufa_Close (This); // ensure cleanup +} + +static bool +bufa_Open (THIS_PTR, uio_DirHandle *dir, const char *filename) +{ + // this should never be called + log_add (log_Debug, "bufa_Open(): dead function called"); + return false; + + // laugh at compiler warnings + (void)This; (void)dir; (void)filename; +} + +static void +bufa_Close (THIS_PTR) +{ + TFB_BufSoundDecoder* bufa = (TFB_BufSoundDecoder*) This; + + // restore the status quo + if (bufa->data) + { + This->buffer = bufa->data; + bufa->data = NULL; + } + bufa->cur_pcm = 0; +} + +static int +bufa_Decode (THIS_PTR, void* buf, sint32 bufsize) +{ + TFB_BufSoundDecoder* bufa = (TFB_BufSoundDecoder*) This; + uint32 dec_pcm; + uint32 dec_bytes; + + dec_pcm = bufsize / This->bytes_per_samp; + if (dec_pcm > bufa->max_pcm - bufa->cur_pcm) + dec_pcm = bufa->max_pcm - bufa->cur_pcm; + dec_bytes = dec_pcm * This->bytes_per_samp; + + // Buffer decode is a hack + This->buffer = (uint8*) bufa->data + + bufa->cur_pcm * This->bytes_per_samp; + + if (dec_pcm > 0) + bufa->cur_pcm += dec_pcm; + + return dec_bytes; + + (void)buf; // laugh at compiler warning +} + +static uint32 +bufa_Seek (THIS_PTR, uint32 pcm_pos) +{ + TFB_BufSoundDecoder* bufa = (TFB_BufSoundDecoder*) This; + + if (pcm_pos > bufa->max_pcm) + pcm_pos = bufa->max_pcm; + bufa->cur_pcm = pcm_pos; + + return pcm_pos; +} + +static uint32 +bufa_GetFrame (THIS_PTR) +{ + return 0; // only 1 frame + + (void)This; // laugh at compiler warning +} + + +static const char* +nula_GetName (void) +{ + return "Null"; +} + +static bool +nula_InitModule (int flags, const TFB_DecoderFormats* fmts) +{ + // this should never be called + log_add (log_Debug, "nula_InitModule(): dead function called"); + return false; + + (void)flags; (void)fmts; // laugh at compiler warning +} + +static void +nula_TermModule (void) +{ + // this should never be called + log_add (log_Debug, "nula_TermModule(): dead function called"); +} + +static uint32 +nula_GetStructSize (void) +{ + return sizeof (TFB_NullSoundDecoder); +} + +static int +nula_GetError (THIS_PTR) +{ + return 0; // error? what error?! + + (void)This; // laugh at compiler warning +} + +static bool +nula_Init (THIS_PTR) +{ + TFB_NullSoundDecoder* nula = (TFB_NullSoundDecoder*) This; + + This->need_swap = false; + nula->cur_pcm = 0; + return true; +} + +static void +nula_Term (THIS_PTR) +{ + //TFB_NullSoundDecoder* nula = (TFB_NullSoundDecoder*) This; + nula_Close (This); // ensure cleanup +} + +static bool +nula_Open (THIS_PTR, uio_DirHandle *dir, const char *filename) +{ + This->frequency = 11025; + This->format = decoder_formats.mono16; + This->is_null = true; + return true; + + // laugh at compiler warnings + (void)dir; (void)filename; +} + +static void +nula_Close (THIS_PTR) +{ + TFB_NullSoundDecoder* nula = (TFB_NullSoundDecoder*) This; + + nula->cur_pcm = 0; +} + +static int +nula_Decode (THIS_PTR, void* buf, sint32 bufsize) +{ + TFB_NullSoundDecoder* nula = (TFB_NullSoundDecoder*) This; + uint32 max_pcm; + uint32 dec_pcm; + uint32 dec_bytes; + + max_pcm = (uint32) (This->length * This->frequency); + dec_pcm = bufsize / This->bytes_per_samp; + if (dec_pcm > max_pcm - nula->cur_pcm) + dec_pcm = max_pcm - nula->cur_pcm; + dec_bytes = dec_pcm * This->bytes_per_samp; + + if (dec_pcm > 0) + { + memset (buf, 0, dec_bytes); + nula->cur_pcm += dec_pcm; + } + + return dec_bytes; +} + +static uint32 +nula_Seek (THIS_PTR, uint32 pcm_pos) +{ + TFB_NullSoundDecoder* nula = (TFB_NullSoundDecoder*) This; + uint32 max_pcm; + + max_pcm = (uint32) (This->length * This->frequency); + if (pcm_pos > max_pcm) + pcm_pos = max_pcm; + nula->cur_pcm = pcm_pos; + + return pcm_pos; +} + +static uint32 +nula_GetFrame (THIS_PTR) +{ + return 0; // only 1 frame + + (void)This; // laugh at compiler warning +} diff --git a/src/libs/sound/decoders/decoder.h b/src/libs/sound/decoders/decoder.h new file mode 100644 index 0000000..2d6983c --- /dev/null +++ b/src/libs/sound/decoders/decoder.h @@ -0,0 +1,129 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Sound file decoder for .wav, .mod, .ogg + * API is heavily influenced by SDL_sound. + */ + +#ifndef DECODER_H +#define DECODER_H + +#include "port.h" +#include "types.h" +#include "libs/uio.h" + +#ifndef OVCODEC_NONE +# ifdef _MSC_VER +# pragma comment (lib, "vorbisfile.lib") +# endif /* _MSC_VER */ +#endif /* OVCODEC_NONE */ + +typedef struct tfb_decoderformats +{ + bool big_endian; + bool want_big_endian; + uint32 mono8; + uint32 stereo8; + uint32 mono16; + uint32 stereo16; +} TFB_DecoderFormats; + +// forward-declare +typedef struct tfb_sounddecoder TFB_SoundDecoder; + +#define THIS_PTR TFB_SoundDecoder* + +typedef struct tfb_sounddecoderfunc +{ + const char* (* GetName) (void); + bool (* InitModule) (int flags, const TFB_DecoderFormats*); + void (* TermModule) (void); + uint32 (* GetStructSize) (void); + int (* GetError) (THIS_PTR); + bool (* Init) (THIS_PTR); + void (* Term) (THIS_PTR); + bool (* Open) (THIS_PTR, uio_DirHandle *dir, const char *filename); + void (* Close) (THIS_PTR); + int (* Decode) (THIS_PTR, void* buf, sint32 bufsize); + // returns <0 on error, ==0 when no more data, >0 bytes returned + uint32 (* Seek) (THIS_PTR, uint32 pcm_pos); + // returns the pcm position set + uint32 (* GetFrame) (THIS_PTR); + +} TFB_SoundDecoderFuncs; + +#undef THIS_PTR + +struct tfb_sounddecoder +{ + // decoder virtual funcs - R/O + const TFB_SoundDecoderFuncs *funcs; + + // public R/O, set by decoder + uint32 format; + uint32 frequency; + float length; // total length in seconds + bool is_null; + bool need_swap; + + // public R/O, set by wrapper + void *buffer; + uint32 buffer_size; + sint32 error; + uint32 bytes_per_samp; + + // public R/W + bool looping; + + // semi-private + uio_DirHandle *dir; + char *filename; + uint32 pos; + uint32 start_sample; + uint32 end_sample; + +}; + +// return values +enum +{ + SOUNDDECODER_OK, + SOUNDDECODER_ERROR, + SOUNDDECODER_EOF, +}; + +typedef struct TFB_RegSoundDecoder TFB_RegSoundDecoder; + +TFB_RegSoundDecoder* SoundDecoder_Register (const char* fileext, + TFB_SoundDecoderFuncs* decvtbl); +void SoundDecoder_Unregister (TFB_RegSoundDecoder* regdec); +const TFB_SoundDecoderFuncs* SoundDecoder_Lookup (const char* fileext); + +void SoundDecoder_SwapWords (uint16* data, uint32 size); +sint32 SoundDecoder_Init (int flags, TFB_DecoderFormats* formats); +void SoundDecoder_Uninit (void); +TFB_SoundDecoder* SoundDecoder_Load (uio_DirHandle *dir, + char *filename, uint32 buffer_size, uint32 startTime, sint32 runTime); +uint32 SoundDecoder_Decode (TFB_SoundDecoder *decoder); +uint32 SoundDecoder_DecodeAll (TFB_SoundDecoder *decoder); +float SoundDecoder_GetTime (TFB_SoundDecoder *decoder); +uint32 SoundDecoder_GetFrame (TFB_SoundDecoder *decoder); +void SoundDecoder_Seek (TFB_SoundDecoder *decoder, uint32 msecs); +void SoundDecoder_Rewind (TFB_SoundDecoder *decoder); +void SoundDecoder_Free (TFB_SoundDecoder *decoder); +const char* SoundDecoder_GetName (TFB_SoundDecoder *decoder); + +#endif diff --git a/src/libs/sound/decoders/dukaud.c b/src/libs/sound/decoders/dukaud.c new file mode 100644 index 0000000..aeff373 --- /dev/null +++ b/src/libs/sound/decoders/dukaud.c @@ -0,0 +1,546 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* .duk sound track decoder + */ + +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include "libs/memlib.h" +#include "port.h" +#include "types.h" +#include "libs/uio.h" +#include "dukaud.h" +#include "decoder.h" +#include "endian_uqm.h" + +#define DATA_BUF_SIZE 0x8000 +#define DUCK_GENERAL_FPS 14.622f + + +#define THIS_PTR TFB_SoundDecoder* This + +static const char* duka_GetName (void); +static bool duka_InitModule (int flags, const TFB_DecoderFormats*); +static void duka_TermModule (void); +static uint32 duka_GetStructSize (void); +static int duka_GetError (THIS_PTR); +static bool duka_Init (THIS_PTR); +static void duka_Term (THIS_PTR); +static bool duka_Open (THIS_PTR, uio_DirHandle *dir, const char *filename); +static void duka_Close (THIS_PTR); +static int duka_Decode (THIS_PTR, void* buf, sint32 bufsize); +static uint32 duka_Seek (THIS_PTR, uint32 pcm_pos); +static uint32 duka_GetFrame (THIS_PTR); + +TFB_SoundDecoderFuncs duka_DecoderVtbl = +{ + duka_GetName, + duka_InitModule, + duka_TermModule, + duka_GetStructSize, + duka_GetError, + duka_Init, + duka_Term, + duka_Open, + duka_Close, + duka_Decode, + duka_Seek, + duka_GetFrame, +}; + +typedef struct tfb_ducksounddecoder +{ + // always the first member + TFB_SoundDecoder decoder; + + // public read-only + uint32 iframe; // current frame index + uint32 cframes; // total count of frames + uint32 channels; // number of channels + uint32 pcm_frame; // samples per frame + + // private + sint32 last_error; + uio_Stream* duk; + uint32* frames; + // buffer + void* data; + uint32 maxdata; + uint32 cbdata; + uint32 dataofs; + // decoder stuff + sint32 predictors[2]; + +} TFB_DuckSoundDecoder; + + +typedef struct +{ + uint32 audsize; + uint32 vidsize; +} DukAud_FrameHeader; + +typedef struct +{ + uint16 magic; // always 0xf77f + uint16 numsamples; + uint16 tag; + uint16 indices[2]; // initial indices for channels +} DukAud_AudSubframe; + +static const TFB_DecoderFormats* duka_formats = NULL; + +static sint32 +duka_readAudFrameHeader (TFB_DuckSoundDecoder* duka, uint32 iframe, + DukAud_AudSubframe* aud) +{ + DukAud_FrameHeader hdr; + + uio_fseek (duka->duk, duka->frames[iframe], SEEK_SET); + if (uio_fread (&hdr, sizeof(hdr), 1, duka->duk) != 1) + { + duka->last_error = errno; + return dukae_BadFile; + } + hdr.audsize = UQM_SwapBE32 (hdr.audsize); + + if (uio_fread (aud, sizeof(*aud), 1, duka->duk) != 1) + { + duka->last_error = errno; + return dukae_BadFile; + } + + aud->magic = UQM_SwapBE16 (aud->magic); + if (aud->magic != 0xf77f) + return duka->last_error = dukae_BadFile; + + aud->numsamples = UQM_SwapBE16 (aud->numsamples); + aud->tag = UQM_SwapBE16 (aud->tag); + aud->indices[0] = UQM_SwapBE16 (aud->indices[0]); + aud->indices[1] = UQM_SwapBE16 (aud->indices[1]); + + return 0; +} + +// This table is from one of the files that came with the original 3do source +// It's slightly different from the data used by MPlayer. +static int adpcm_step[89] = { + 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xF, + 0x10, 0x12, 0x13, 0x15, 0x17, 0x1A, 0x1C, 0x1F, + 0x22, 0x26, 0x29, 0x2E, 0x32, 0x37, 0x3D, 0x43, + 0x4A, 0x51, 0x59, 0x62, 0x6C, 0x76, 0x82, 0x8F, + 0x9E, 0xAD, 0xBF, 0xD2, 0xE7, 0xFE, 0x117, 0x133, + 0x152, 0x174, 0x199, 0x1C2, 0x1EF, 0x220, 0x256, 0x292, + 0x2D4, 0x31D, 0x36C, 0x3C4, 0x424, 0x48E, 0x503, 0x583, + 0x610, 0x6AC, 0x756, 0x812, 0x8E1, 0x9C4, 0xABE, 0xBD1, + 0xCFF, 0xE4C, 0xFBA, 0x114D, 0x1308, 0x14EF, 0x1707, 0x1954, + 0x1BDD, 0x1EA6, 0x21B7, 0x2516, + 0x28CB, 0x2CDF, 0x315C, 0x364C, + 0x3BBA, 0x41B2, 0x4844, 0x4F7E, + 0x5771, 0x6030, 0x69CE, 0x7463, + 0x7FFF + }; + + +// *** BEGIN part copied from MPlayer *** +// (some little changes) + +#if 0 +// pertinent tables for IMA ADPCM +static int adpcm_step[89] = { + 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, + 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, + 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, + 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, + 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, + 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, + 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, + 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, + 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 + }; +#endif + +static int adpcm_index[16] = { + -1, -1, -1, -1, 2, 4, 6, 8, + -1, -1, -1, -1, 2, 4, 6, 8 + }; + +// clamp a number between 0 and 88 +#define CLAMP_0_TO_88(x) \ + if ((x) < 0) (x) = 0; else if ((x) > 88) (x) = 88; + +// clamp a number within a signed 16-bit range +#define CLAMP_S16(x) \ + if ((x) < -32768) \ + (x) = -32768; \ + else if ((x) > 32767) \ + (x) = 32767; + +static void +decode_nibbles (sint16 *output, sint32 output_size, sint32 channels, + sint32* predictors, uint16* indices) +{ + sint32 step[2]; + sint32 index[2]; + sint32 diff; + sint32 i; + int sign; + sint32 delta; + int channel_number = 0; + + channels -= 1; + index[0] = indices[0]; + index[1] = indices[1]; + step[0] = adpcm_step[index[0]]; + step[1] = adpcm_step[index[1]]; + + for (i = 0; i < output_size; i++) + { + delta = output[i]; + + index[channel_number] += adpcm_index[delta]; + CLAMP_0_TO_88(index[channel_number]); + + sign = delta & 8; + delta = delta & 7; + +#if 0 + // fast approximation, used in most decoders + diff = step[channel_number] >> 3; + if (delta & 4) diff += step[channel_number]; + if (delta & 2) diff += step[channel_number] >> 1; + if (delta & 1) diff += step[channel_number] >> 2; +#else + // real thing +// diff = ((signed)delta + 0.5) * step[channel_number] / 4; + diff = (((delta << 1) + 1) * step[channel_number]) >> 3; +#endif + + if (sign) + predictors[channel_number] -= diff; + else + predictors[channel_number] += diff; + + CLAMP_S16(predictors[channel_number]); + output[i] = predictors[channel_number]; + step[channel_number] = adpcm_step[index[channel_number]]; + + // toggle channel + channel_number ^= channels; + } +} +// *** END part copied from MPlayer *** + +static sint32 +duka_decodeFrame (TFB_DuckSoundDecoder* duka, DukAud_AudSubframe* header, + uint8* input) +{ + uint8* inend; + sint16* output; + sint16* outptr; + sint32 outputsize; + + outputsize = header->numsamples * 2 * sizeof (sint16); + outptr = output = (sint16*) ((uint8*)duka->data + duka->cbdata); + + for (inend = input + header->numsamples; input < inend; ++input) + { + *(outptr++) = *input >> 4; + *(outptr++) = *input & 0x0f; + } + + decode_nibbles (output, header->numsamples * 2, duka->channels, + duka->predictors, header->indices); + + duka->cbdata += outputsize; + + return outputsize; +} + + +static sint32 +duka_readNextFrame (TFB_DuckSoundDecoder* duka) +{ + DukAud_FrameHeader hdr; + DukAud_AudSubframe* aud; + uint8* p; + + uio_fseek (duka->duk, duka->frames[duka->iframe], SEEK_SET); + if (uio_fread (&hdr, sizeof(hdr), 1, duka->duk) != 1) + { + duka->last_error = errno; + return dukae_BadFile; + } + hdr.audsize = UQM_SwapBE32 (hdr.audsize); + + // dump encoded data at the end of the buffer aligned on 8-byte + p = ((uint8*)duka->data + duka->maxdata - ((hdr.audsize + 7) & (-8))); + if (uio_fread (p, 1, hdr.audsize, duka->duk) != hdr.audsize) + { + duka->last_error = errno; + return dukae_BadFile; + } + aud = (DukAud_AudSubframe*) p; + p += sizeof(DukAud_AudSubframe); + + aud->magic = UQM_SwapBE16 (aud->magic); + if (aud->magic != 0xf77f) + return duka->last_error = dukae_BadFile; + + aud->numsamples = UQM_SwapBE16 (aud->numsamples); + aud->tag = UQM_SwapBE16 (aud->tag); + aud->indices[0] = UQM_SwapBE16 (aud->indices[0]); + aud->indices[1] = UQM_SwapBE16 (aud->indices[1]); + + duka->iframe++; + + return duka_decodeFrame (duka, aud, p); +} + +static sint32 +duka_stuffBuffer (TFB_DuckSoundDecoder* duka, void* buf, sint32 bufsize) +{ + sint32 dataleft; + + dataleft = duka->cbdata - duka->dataofs; + if (dataleft > 0) + { + if (dataleft > bufsize) + dataleft = bufsize & (-4); + memcpy (buf, (uint8*)duka->data + duka->dataofs, dataleft); + duka->dataofs += dataleft; + } + + if (duka->cbdata > 0 && duka->dataofs >= duka->cbdata) + duka->cbdata = duka->dataofs = 0; // reset for new data + + return dataleft; +} + + +static const char* +duka_GetName (void) +{ + return "DukAud"; +} + +static bool +duka_InitModule (int flags, const TFB_DecoderFormats* fmts) +{ + duka_formats = fmts; + return true; + + (void)flags; // laugh at compiler warning +} + +static void +duka_TermModule (void) +{ + // no specific module term +} + +static uint32 +duka_GetStructSize (void) +{ + return sizeof (TFB_DuckSoundDecoder); +} + +static int +duka_GetError (THIS_PTR) +{ + TFB_DuckSoundDecoder* duka = (TFB_DuckSoundDecoder*) This; + int ret = duka->last_error; + duka->last_error = dukae_None; + return ret; +} + +static bool +duka_Init (THIS_PTR) +{ + //TFB_DuckSoundDecoder* duka = (TFB_DuckSoundDecoder*) This; + This->need_swap = + duka_formats->big_endian != duka_formats->want_big_endian; + return true; +} + +static void +duka_Term (THIS_PTR) +{ + //TFB_DuckSoundDecoder* duka = (TFB_DuckSoundDecoder*) This; + duka_Close (This); // ensure cleanup +} + +static bool +duka_Open (THIS_PTR, uio_DirHandle *dir, const char *file) +{ + TFB_DuckSoundDecoder* duka = (TFB_DuckSoundDecoder*) This; + uio_Stream* duk; + uio_Stream* frm; + DukAud_AudSubframe aud; + char filename[256]; + uint32 filelen; + size_t cread; + uint32 i; + + filelen = strlen (file); + if (filelen > sizeof (filename) - 1) + return false; + strcpy (filename, file); + + duk = uio_fopen (dir, filename, "rb"); + if (!duk) + { + duka->last_error = errno; + return false; + } + + strcpy (filename + filelen - 3, "frm"); + frm = uio_fopen (dir, filename, "rb"); + if (!frm) + { + duka->last_error = errno; + uio_fclose (duk); + return false; + } + + duka->duk = duk; + + uio_fseek (frm, 0, SEEK_END); + duka->cframes = uio_ftell (frm) / sizeof (uint32); + uio_fseek (frm, 0, SEEK_SET); + if (!duka->cframes) + { + duka->last_error = dukae_BadFile; + uio_fclose (frm); + duka_Close (This); + return false; + } + + duka->frames = (uint32*) HMalloc (duka->cframes * sizeof (uint32)); + cread = uio_fread (duka->frames, sizeof (uint32), duka->cframes, frm); + uio_fclose (frm); + if (cread != duka->cframes) + { + duka->last_error = dukae_BadFile; + duka_Close (This); + return false; + } + + for (i = 0; i < duka->cframes; ++i) + duka->frames[i] = UQM_SwapBE32 (duka->frames[i]); + + if (duka_readAudFrameHeader (duka, 0, &aud) < 0) + { + duka_Close (This); + return false; + } + + This->frequency = 22050; + This->format = duka_formats->stereo16; + duka->channels = 2; + duka->pcm_frame = aud.numsamples; + duka->data = HMalloc (DATA_BUF_SIZE); + duka->maxdata = DATA_BUF_SIZE; + + // estimate + This->length = (float) duka->cframes / DUCK_GENERAL_FPS; + + duka->last_error = 0; + + return true; +} + +static void +duka_Close (THIS_PTR) +{ + TFB_DuckSoundDecoder* duka = (TFB_DuckSoundDecoder*) This; + + if (duka->data) + { + HFree (duka->data); + duka->data = NULL; + } + if (duka->frames) + { + HFree (duka->frames); + duka->frames = NULL; + } + if (duka->duk) + { + uio_fclose (duka->duk); + duka->duk = NULL; + } + duka->last_error = 0; +} + +static int +duka_Decode (THIS_PTR, void* buf, sint32 bufsize) +{ + TFB_DuckSoundDecoder* duka = (TFB_DuckSoundDecoder*) This; + sint32 stuffed; + sint32 total = 0; + + if (bufsize <= 0) + return duka->last_error = dukae_BadArg; + + do + { + stuffed = duka_stuffBuffer (duka, buf, bufsize); + buf = (uint8*)buf + stuffed; + bufsize -= stuffed; + total += stuffed; + + if (bufsize > 0 && duka->iframe < duka->cframes) + { + stuffed = duka_readNextFrame (duka); + if (stuffed <= 0) + return stuffed; + } + } while (bufsize > 0 && duka->iframe < duka->cframes); + + return total; +} + +static uint32 +duka_Seek (THIS_PTR, uint32 pcm_pos) +{ + TFB_DuckSoundDecoder* duka = (TFB_DuckSoundDecoder*) This; + uint32 iframe; + + iframe = pcm_pos / duka->pcm_frame; + if (iframe < duka->cframes) + { + duka->iframe = iframe; + duka->cbdata = 0; + duka->dataofs = 0; + duka->predictors[0] = 0; + duka->predictors[1] = 0; + } + return duka->iframe * duka->pcm_frame; +} + +static uint32 +duka_GetFrame (THIS_PTR) +{ + TFB_DuckSoundDecoder* duka = (TFB_DuckSoundDecoder*) This; + + // if there is nothing buffered return the actual current frame + // otherwise return previous + return duka->dataofs == duka->cbdata ? + duka->iframe : duka->iframe - 1; +} diff --git a/src/libs/sound/decoders/dukaud.h b/src/libs/sound/decoders/dukaud.h new file mode 100644 index 0000000..23c4201 --- /dev/null +++ b/src/libs/sound/decoders/dukaud.h @@ -0,0 +1,36 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* .duk sound track decoder */ + +#ifndef DUKAUD_H +#define DUKAUD_H + +#include "decoder.h" + +extern TFB_SoundDecoderFuncs duka_DecoderVtbl; + +typedef enum +{ + // positive values are the same as in errno + dukae_None = 0, + dukae_Unknown = -1, + dukae_BadFile = -2, + dukae_BadArg = -3, + dukae_Other = -1000, +} DukAud_Error; + +#endif // DUKAUD_H diff --git a/src/libs/sound/decoders/modaud.c b/src/libs/sound/decoders/modaud.c new file mode 100644 index 0000000..18c29a2 --- /dev/null +++ b/src/libs/sound/decoders/modaud.c @@ -0,0 +1,430 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* MikMod decoder (.mod adapter) + */ + +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include "libs/memlib.h" +#include "port.h" +#include "types.h" +#include "endian_uqm.h" +#include "libs/uio.h" +#include "decoder.h" +#include "libs/sound/audiocore.h" +#include "libs/log.h" +#include "modaud.h" + +#ifdef USE_INTERNAL_MIKMOD +# include "libs/mikmod/mikmod.h" +#else +# include <mikmod.h> +#endif + +#define THIS_PTR TFB_SoundDecoder* This + +static const char* moda_GetName (void); +static bool moda_InitModule (int flags, const TFB_DecoderFormats*); +static void moda_TermModule (void); +static uint32 moda_GetStructSize (void); +static int moda_GetError (THIS_PTR); +static bool moda_Init (THIS_PTR); +static void moda_Term (THIS_PTR); +static bool moda_Open (THIS_PTR, uio_DirHandle *dir, const char *filename); +static void moda_Close (THIS_PTR); +static int moda_Decode (THIS_PTR, void* buf, sint32 bufsize); +static uint32 moda_Seek (THIS_PTR, uint32 pcm_pos); +static uint32 moda_GetFrame (THIS_PTR); + +TFB_SoundDecoderFuncs moda_DecoderVtbl = +{ + moda_GetName, + moda_InitModule, + moda_TermModule, + moda_GetStructSize, + moda_GetError, + moda_Init, + moda_Term, + moda_Open, + moda_Close, + moda_Decode, + moda_Seek, + moda_GetFrame, +}; + +typedef struct tfb_modsounddecoder +{ + // always the first member + TFB_SoundDecoder decoder; + + // private + sint32 last_error; + MODULE* module; + +} TFB_ModSoundDecoder; + + + +// MikMod Output driver +// we provide our own so that we can use MikMod as +// generic decoder + +static void* buffer; +static ULONG bufsize; +static ULONG written; + +static ULONG* +moda_mmout_SetOutputBuffer (void* buf, ULONG size) +{ + buffer = buf; + bufsize = size; + written = 0; + return &written; +} + +static BOOL +moda_mmout_IsThere (void) +{ + return 1; +} + +static BOOL +moda_mmout_Init (void) +{ + md_mode |= DMODE_SOFT_MUSIC | DMODE_SOFT_SNDFX; + return VC_Init (); +} + +static void +moda_mmout_Exit (void) +{ + VC_Exit (); +} + +static void +moda_mmout_Update (void) +{ + written = 0; + if (!buffer || bufsize == 0) + return; + + written = VC_WriteBytes (buffer, bufsize); +} + +static BOOL +moda_mmout_Reset (void) +{ + return 0; +} + +static char MDRIVER_name[] = "Mem Buffer"; +static char MDRIVER_version[] = "Mem Buffer driver v1.1"; +static char MDRIVER_alias[] = "membuf"; + +static MDRIVER moda_mmout_drv = +{ + NULL, + //xxx libmikmod does not declare these fields const; it probably should. + MDRIVER_name, // Name + MDRIVER_version, // Version + 0, 255, // Voice limits + MDRIVER_alias, // Alias + +// The minimum mikmod version we support is 3.1.8 +#if (LIBMIKMOD_VERSION_MAJOR > 3) || \ + ((LIBMIKMOD_VERSION_MAJOR == 3) && (LIBMIKMOD_VERSION_MINOR >= 2)) + NULL, // Cmdline help +#endif + + NULL, + moda_mmout_IsThere, + VC_SampleLoad, + VC_SampleUnload, + VC_SampleSpace, + VC_SampleLength, + moda_mmout_Init, + moda_mmout_Exit, + moda_mmout_Reset, + VC_SetNumVoices, + VC_PlayStart, + VC_PlayStop, + moda_mmout_Update, + NULL, /* FIXME: Pause */ + VC_VoiceSetVolume, + VC_VoiceGetVolume, + VC_VoiceSetFrequency, + VC_VoiceGetFrequency, + VC_VoiceSetPanning, + VC_VoiceGetPanning, + VC_VoicePlay, + VC_VoiceStop, + VC_VoiceStopped, + VC_VoiceGetPosition, + VC_VoiceRealVolume +}; + + +static const TFB_DecoderFormats* moda_formats = NULL; + +// MikMod READER interface +// we provide our own so that we can do loading via uio +// +typedef struct MUIOREADER +{ + MREADER core; + uio_Stream* file; + +} MUIOREADER; + +static BOOL +moda_uioReader_Eof (MREADER* reader) +{ + return uio_feof (((MUIOREADER*)reader)->file); +} + +static BOOL +moda_uioReader_Read (MREADER* reader, void* ptr, size_t size) +{ + return uio_fread (ptr, size, 1, ((MUIOREADER*)reader)->file); +} + +static int +moda_uioReader_Get (MREADER* reader) +{ + return uio_fgetc (((MUIOREADER*)reader)->file); +} + +static BOOL +moda_uioReader_Seek (MREADER* reader, long offset, int whence) +{ + return uio_fseek (((MUIOREADER*)reader)->file, offset, whence); +} + +static long +moda_uioReader_Tell (MREADER* reader) +{ + return uio_ftell (((MUIOREADER*)reader)->file); +} + +static MREADER* +moda_new_uioReader (uio_Stream* fp) +{ + MUIOREADER* reader = (MUIOREADER*) HMalloc (sizeof(MUIOREADER)); + if (reader) + { + reader->core.Eof = &moda_uioReader_Eof; + reader->core.Read = &moda_uioReader_Read; + reader->core.Get = &moda_uioReader_Get; + reader->core.Seek = &moda_uioReader_Seek; + reader->core.Tell = &moda_uioReader_Tell; + reader->file = fp; + } + return (MREADER*)reader; +} + +static void +moda_delete_uioReader (MREADER* reader) +{ + if (reader) + HFree (reader); +} + + +static const char* +moda_GetName (void) +{ + return "MikMod"; +} + +static bool +moda_InitModule (int flags, const TFB_DecoderFormats* fmts) +{ + MikMod_RegisterDriver (&moda_mmout_drv); + MikMod_RegisterAllLoaders (); + + if (flags & audio_QUALITY_HIGH) + { + md_mode = DMODE_HQMIXER|DMODE_STEREO|DMODE_16BITS|DMODE_INTERP|DMODE_SURROUND; + md_mixfreq = 44100; + md_reverb = 1; + } + else if (flags & audio_QUALITY_LOW) + { + md_mode = DMODE_SOFT_MUSIC|DMODE_STEREO|DMODE_16BITS; +#ifdef __SYMBIAN32__ + md_mixfreq = 11025; +#else + md_mixfreq = 22050; +#endif + md_reverb = 0; + } + else + { + md_mode = DMODE_SOFT_MUSIC|DMODE_STEREO|DMODE_16BITS|DMODE_INTERP; + md_mixfreq = 44100; + md_reverb = 0; + } + + md_pansep = 64; + + if (MikMod_Init (NULL)) + { + log_add (log_Error, "MikMod_Init() failed, %s", + MikMod_strerror (MikMod_errno)); + return false; + } + + moda_formats = fmts; + + return true; +} + +static void +moda_TermModule (void) +{ + MikMod_Exit (); +} + +static uint32 +moda_GetStructSize (void) +{ + return sizeof (TFB_ModSoundDecoder); +} + +static int +moda_GetError (THIS_PTR) +{ + TFB_ModSoundDecoder* moda = (TFB_ModSoundDecoder*) This; + int ret = moda->last_error; + moda->last_error = 0; + return ret; +} + +static bool +moda_Init (THIS_PTR) +{ + //TFB_ModSoundDecoder* moda = (TFB_ModSoundDecoder*) This; + This->need_swap = + moda_formats->big_endian != moda_formats->want_big_endian; + return true; +} + +static void +moda_Term (THIS_PTR) +{ + //TFB_ModSoundDecoder* moda = (TFB_ModSoundDecoder*) This; + moda_Close (This); // ensure cleanup +} + +static bool +moda_Open (THIS_PTR, uio_DirHandle *dir, const char *filename) +{ + TFB_ModSoundDecoder* moda = (TFB_ModSoundDecoder*) This; + uio_Stream *fp; + MREADER* reader; + MODULE* mod; + + fp = uio_fopen (dir, filename, "rb"); + if (!fp) + { + moda->last_error = errno; + return false; + } + + reader = moda_new_uioReader (fp); + if (!reader) + { + moda->last_error = -1; + uio_fclose (fp); + return false; + } + + mod = Player_LoadGeneric (reader, 8, 0); + + // can already dispose of reader and fileh + moda_delete_uioReader (reader); + uio_fclose (fp); + if (!mod) + { + log_add (log_Warning, "moda_Open(): could not load %s", filename); + return false; + } + + moda->module = mod; + mod->extspd = 1; + mod->panflag = 1; + mod->wrap = 0; + mod->loop = 1; + + This->format = moda_formats->stereo16; + This->frequency = md_mixfreq; + This->length = 0; // FIXME way to obtain this from mikmod? + + moda->last_error = 0; + + return true; +} + +static void +moda_Close (THIS_PTR) +{ + TFB_ModSoundDecoder* moda = (TFB_ModSoundDecoder*) This; + + if (moda->module) + { + Player_Free (moda->module); + moda->module = NULL; + } +} + +static int +moda_Decode (THIS_PTR, void* buf, sint32 bufsize) +{ + TFB_ModSoundDecoder* moda = (TFB_ModSoundDecoder*) This; + volatile ULONG* poutsize; + + Player_Start (moda->module); + if (!Player_Active()) + return 0; + + poutsize = moda_mmout_SetOutputBuffer (buf, bufsize); + MikMod_Update (); + + return *poutsize; +} + +static uint32 +moda_Seek (THIS_PTR, uint32 pcm_pos) +{ + TFB_ModSoundDecoder* moda = (TFB_ModSoundDecoder*) This; + + Player_Start (moda->module); + if (pcm_pos) + log_add (log_Debug, "moda_Seek(): " + "non-zero seek positions not supported for mod"); + Player_SetPosition (0); + + return 0; +} + +static uint32 +moda_GetFrame (THIS_PTR) +{ + TFB_ModSoundDecoder* moda = (TFB_ModSoundDecoder*) This; + return moda->module->sngpos; +} diff --git a/src/libs/sound/decoders/modaud.h b/src/libs/sound/decoders/modaud.h new file mode 100644 index 0000000..3b0eb86 --- /dev/null +++ b/src/libs/sound/decoders/modaud.h @@ -0,0 +1,26 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* MikMod adapter */ + +#ifndef MODAUD_H +#define MODAUD_H + +#include "decoder.h" + +extern TFB_SoundDecoderFuncs moda_DecoderVtbl; + +#endif // MODAUD_H diff --git a/src/libs/sound/decoders/oggaud.c b/src/libs/sound/decoders/oggaud.c new file mode 100644 index 0000000..6227120 --- /dev/null +++ b/src/libs/sound/decoders/oggaud.c @@ -0,0 +1,278 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Ogg Vorbis decoder (.ogg adapter) + */ + +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include "libs/log.h" +#include "port.h" +#include "types.h" +#include "libs/uio.h" +#include "decoder.h" +#ifdef OVCODEC_TREMOR +# include <tremor/ivorbiscodec.h> +# include <tremor/ivorbisfile.h> +#else +# include <vorbis/codec.h> +# include <vorbis/vorbisfile.h> +#endif /* OVCODEC_TREMOR */ +#include "oggaud.h" + + +#define THIS_PTR TFB_SoundDecoder* This + +static const char* ova_GetName (void); +static bool ova_InitModule (int flags, const TFB_DecoderFormats*); +static void ova_TermModule (void); +static uint32 ova_GetStructSize (void); +static int ova_GetError (THIS_PTR); +static bool ova_Init (THIS_PTR); +static void ova_Term (THIS_PTR); +static bool ova_Open (THIS_PTR, uio_DirHandle *dir, const char *filename); +static void ova_Close (THIS_PTR); +static int ova_Decode (THIS_PTR, void* buf, sint32 bufsize); +static uint32 ova_Seek (THIS_PTR, uint32 pcm_pos); +static uint32 ova_GetFrame (THIS_PTR); + +TFB_SoundDecoderFuncs ova_DecoderVtbl = +{ + ova_GetName, + ova_InitModule, + ova_TermModule, + ova_GetStructSize, + ova_GetError, + ova_Init, + ova_Term, + ova_Open, + ova_Close, + ova_Decode, + ova_Seek, + ova_GetFrame, +}; + +typedef struct tfb_oggsounddecoder +{ + // always the first member + TFB_SoundDecoder decoder; + + // private + sint32 last_error; + OggVorbis_File vf; + +} TFB_OggSoundDecoder; + +static const TFB_DecoderFormats* ova_formats = NULL; + +static size_t +ogg_read (void *ptr, size_t size, size_t nmemb, void *datasource) +{ + return uio_fread (ptr, size, nmemb, (uio_Stream *) datasource); +} + +static int +ogg_seek (void *datasource, ogg_int64_t offset, int whence) +{ + long off = (long) offset; + return uio_fseek ((uio_Stream *) datasource, off, whence); +} + +static int +ogg_close (void *datasource) +{ + return uio_fclose ((uio_Stream *) datasource); +} + +static long +ogg_tell (void *datasource) +{ + return uio_ftell ((uio_Stream *) datasource); +} + +static const ov_callbacks ogg_callbacks = +{ + ogg_read, + ogg_seek, + ogg_close, + ogg_tell, +}; + +static const char* +ova_GetName (void) +{ + return "Ogg Vorbis"; +} + +static bool +ova_InitModule (int flags, const TFB_DecoderFormats* fmts) +{ + ova_formats = fmts; + return true; + + (void)flags; // laugh at compiler warning +} + +static void +ova_TermModule (void) +{ + // no specific module term +} + +static uint32 +ova_GetStructSize (void) +{ + return sizeof (TFB_OggSoundDecoder); +} + +static int +ova_GetError (THIS_PTR) +{ + TFB_OggSoundDecoder* ova = (TFB_OggSoundDecoder*) This; + int ret = ova->last_error; + ova->last_error = 0; + return ret; +} + +static bool +ova_Init (THIS_PTR) +{ + //TFB_OggSoundDecoder* ova = (TFB_OggSoundDecoder*) This; + This->need_swap = false; + return true; +} + +static void +ova_Term (THIS_PTR) +{ + //TFB_OggSoundDecoder* ova = (TFB_OggSoundDecoder*) This; + ova_Close (This); // ensure cleanup +} + +static bool +ova_Open (THIS_PTR, uio_DirHandle *dir, const char *filename) +{ + TFB_OggSoundDecoder* ova = (TFB_OggSoundDecoder*) This; + int rc; + uio_Stream *fp; + vorbis_info *vinfo; + + fp = uio_fopen (dir, filename, "rb"); + if (fp == NULL) + { + log_add (log_Warning, "ova_Open(): could not open %s", filename); + return false; + } + + rc = ov_open_callbacks (fp, &ova->vf, NULL, 0, ogg_callbacks); + if (rc != 0) + { + log_add (log_Warning, "ova_Open(): " + "ov_open_callbacks failed for %s, error code %d", + filename, rc); + uio_fclose (fp); + return false; + } + + vinfo = ov_info (&ova->vf, -1); + if (!vinfo) + { + log_add (log_Warning, "ova_Open(): " + "failed to retrieve ogg bitstream info for %s", + filename); + ov_clear (&ova->vf); + return false; + } + + This->frequency = vinfo->rate; +#ifdef OVCODEC_TREMOR + // With tremor ov_time_total returns an integer, in milliseconds. + This->length = ((float) ov_time_total (&ova->vf, -1)) / 1000.0f; +#else + // With libvorbis ov_time_total returns a double, in seconds. + This->length = (float) ov_time_total (&ova->vf, -1); +#endif /* OVCODEC_TREMOR */ + + if (vinfo->channels == 1) + This->format = ova_formats->mono16; + else + This->format = ova_formats->stereo16; + + ova->last_error = 0; + + return true; +} + +static void +ova_Close (THIS_PTR) +{ + TFB_OggSoundDecoder* ova = (TFB_OggSoundDecoder*) This; + + ov_clear (&ova->vf); +} + +static int +ova_Decode (THIS_PTR, void* buf, sint32 bufsize) +{ + TFB_OggSoundDecoder* ova = (TFB_OggSoundDecoder*) This; + long rc; + int bitstream; + +#ifdef OVCODEC_TREMOR + rc = ov_read (&ova->vf, buf, bufsize, &bitstream); +#else + rc = ov_read (&ova->vf, buf, bufsize, ova_formats->want_big_endian, + 2, 1, &bitstream); +#endif /* OVCODEC_TREMOR */ + + if (rc < 0) + ova->last_error = rc; + else + ova->last_error = 0; + + return rc; +} + +static uint32 +ova_Seek (THIS_PTR, uint32 pcm_pos) +{ + TFB_OggSoundDecoder* ova = (TFB_OggSoundDecoder*) This; + int ret; + + ret = ov_pcm_seek (&ova->vf, pcm_pos); + if (ret != 0) + { + ova->last_error = ret; + return (uint32) ov_pcm_tell (&ova->vf); + } + else + return pcm_pos; +} + +static uint32 +ova_GetFrame (THIS_PTR) +{ + TFB_OggSoundDecoder* ova = (TFB_OggSoundDecoder*) This; + // this is the closest to a frame there is in ogg vorbis stream + // doesn't seem to be a func to retrive it +#ifdef OVCODEC_TREMOR + return ova->vf.os->pageno; +#else + return ova->vf.os.pageno; +#endif /* OVCODEC_TREMOR */ +} + diff --git a/src/libs/sound/decoders/oggaud.h b/src/libs/sound/decoders/oggaud.h new file mode 100644 index 0000000..4e443c4 --- /dev/null +++ b/src/libs/sound/decoders/oggaud.h @@ -0,0 +1,26 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Ogg Vorbis adapter */ + +#ifndef OGGAUD_H +#define OGGAUD_H + +#include "decoder.h" + +extern TFB_SoundDecoderFuncs ova_DecoderVtbl; + +#endif // OGGAUD_H diff --git a/src/libs/sound/decoders/wav.c b/src/libs/sound/decoders/wav.c new file mode 100644 index 0000000..c22f63f --- /dev/null +++ b/src/libs/sound/decoders/wav.c @@ -0,0 +1,385 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Wave decoder (.wav adapter) + * Code is based on Creative's Win32 OpenAL implementation. + */ + +#include <stdio.h> +#include <errno.h> +#include "port.h" +#include "types.h" +#include "libs/uio.h" +#include "endian_uqm.h" +#include "libs/log.h" +#include "wav.h" + +#define wave_MAKE_ID(x1, x2, x3, x4) \ + (((x4) << 24) | ((x3) << 16) | ((x2) << 8) | (x1)) + +#define wave_RiffID wave_MAKE_ID('R', 'I', 'F', 'F') +#define wave_WaveID wave_MAKE_ID('W', 'A', 'V', 'E') +#define wave_FmtID wave_MAKE_ID('f', 'm', 't', ' ') +#define wave_DataID wave_MAKE_ID('d', 'a', 't', 'a') + +typedef struct +{ + uint32 id; + uint32 size; + uint32 type; +} wave_FileHeader; + +typedef struct +{ + uint16 format; + uint16 channels; + uint32 samplesPerSec; + uint32 bytesPerSec; + uint16 blockAlign; + uint16 bitsPerSample; +} wave_FormatHeader; + +typedef struct +{ + uint32 id; + uint32 size; +} wave_ChunkHeader; + + +#define THIS_PTR TFB_SoundDecoder* This + +static const char* wava_GetName (void); +static bool wava_InitModule (int flags, const TFB_DecoderFormats*); +static void wava_TermModule (void); +static uint32 wava_GetStructSize (void); +static int wava_GetError (THIS_PTR); +static bool wava_Init (THIS_PTR); +static void wava_Term (THIS_PTR); +static bool wava_Open (THIS_PTR, uio_DirHandle *dir, const char *filename); +static void wava_Close (THIS_PTR); +static int wava_Decode (THIS_PTR, void* buf, sint32 bufsize); +static uint32 wava_Seek (THIS_PTR, uint32 pcm_pos); +static uint32 wava_GetFrame (THIS_PTR); + +TFB_SoundDecoderFuncs wava_DecoderVtbl = +{ + wava_GetName, + wava_InitModule, + wava_TermModule, + wava_GetStructSize, + wava_GetError, + wava_Init, + wava_Term, + wava_Open, + wava_Close, + wava_Decode, + wava_Seek, + wava_GetFrame, +}; + +typedef struct tfb_wavesounddecoder +{ + // always the first member + TFB_SoundDecoder decoder; + + // private + sint32 last_error; + uio_Stream *fp; + wave_FormatHeader fmtHdr; + uint32 data_ofs; + uint32 data_size; + uint32 max_pcm; + uint32 cur_pcm; + +} TFB_WaveSoundDecoder; + +static const TFB_DecoderFormats* wava_formats = NULL; + + +static const char* +wava_GetName (void) +{ + return "Wave"; +} + +static bool +wava_InitModule (int flags, const TFB_DecoderFormats* fmts) +{ + wava_formats = fmts; + return true; + + (void)flags; // laugh at compiler warning +} + +static void +wava_TermModule (void) +{ + // no specific module term +} + +static uint32 +wava_GetStructSize (void) +{ + return sizeof (TFB_WaveSoundDecoder); +} + +static int +wava_GetError (THIS_PTR) +{ + TFB_WaveSoundDecoder* wava = (TFB_WaveSoundDecoder*) This; + int ret = wava->last_error; + wava->last_error = 0; + return ret; +} + +static bool +wava_Init (THIS_PTR) +{ + //TFB_WaveSoundDecoder* wava = (TFB_WaveSoundDecoder*) This; + This->need_swap = wava_formats->want_big_endian; + return true; +} + +static void +wava_Term (THIS_PTR) +{ + //TFB_WaveSoundDecoder* wava = (TFB_WaveSoundDecoder*) This; + wava_Close (This); // ensure cleanup +} + +static bool +read_le_16 (uio_Stream *fp, uint16 *v) +{ + if (!uio_fread (v, sizeof(*v), 1, fp)) + return false; + *v = UQM_SwapLE16 (*v); + return true; +} + +static bool +read_le_32 (uio_Stream *fp, uint32 *v) +{ + if (!uio_fread (v, sizeof(*v), 1, fp)) + return false; + *v = UQM_SwapLE32 (*v); + return true; +} + +static bool +wava_readFileHeader (TFB_WaveSoundDecoder* wava, wave_FileHeader* hdr) +{ + if (!read_le_32 (wava->fp, &hdr->id) || + !read_le_32 (wava->fp, &hdr->size) || + !read_le_32 (wava->fp, &hdr->type)) + { + wava->last_error = errno; + return false; + } + return true; +} + +static bool +wava_readChunkHeader (TFB_WaveSoundDecoder* wava, wave_ChunkHeader* chunk) +{ + if (!read_le_32 (wava->fp, &chunk->id) || + !read_le_32 (wava->fp, &chunk->size)) + { + wava->last_error = errno; + return false; + } + return true; +} + +static bool +wava_readFormatHeader (TFB_WaveSoundDecoder* wava, wave_FormatHeader* fmt) +{ + if (!read_le_16 (wava->fp, &fmt->format) || + !read_le_16 (wava->fp, &fmt->channels) || + !read_le_32 (wava->fp, &fmt->samplesPerSec) || + !read_le_32 (wava->fp, &fmt->bytesPerSec) || + !read_le_16 (wava->fp, &fmt->blockAlign) || + !read_le_16 (wava->fp, &fmt->bitsPerSample)) + { + wava->last_error = errno; + return false; + } + return true; +} + +static bool +wava_Open (THIS_PTR, uio_DirHandle *dir, const char *filename) +{ + TFB_WaveSoundDecoder* wava = (TFB_WaveSoundDecoder*) This; + wave_FileHeader fileHdr; + wave_ChunkHeader chunkHdr; + long dataLeft; + + wava->fp = uio_fopen (dir, filename, "rb"); + if (!wava->fp) + { + wava->last_error = errno; + return false; + } + + wava->data_size = 0; + wava->data_ofs = 0; + + // read wave header + if (!wava_readFileHeader (wava, &fileHdr)) + { + wava->last_error = errno; + wava_Close (This); + return false; + } + if (fileHdr.id != wave_RiffID || fileHdr.type != wave_WaveID) + { + log_add (log_Warning, "wava_Open(): " + "not a wave file, ID 0x%08x, Type 0x%08x", + fileHdr.id, fileHdr.type); + wava_Close (This); + return false; + } + + for (dataLeft = ((fileHdr.size + 1) & ~1) - 4; dataLeft > 0; + dataLeft -= (((chunkHdr.size + 1) & ~1) + 8)) + { + if (!wava_readChunkHeader (wava, &chunkHdr)) + { + wava_Close (This); + return false; + } + + if (chunkHdr.id == wave_FmtID) + { + if (!wava_readFormatHeader (wava, &wava->fmtHdr)) + { + wava_Close (This); + return false; + } + uio_fseek (wava->fp, chunkHdr.size - 16, SEEK_CUR); + } + else + { + if (chunkHdr.id == wave_DataID) + { + wava->data_size = chunkHdr.size; + wava->data_ofs = uio_ftell (wava->fp); + } + uio_fseek (wava->fp, chunkHdr.size, SEEK_CUR); + } + + // 2-align the file ptr + // XXX: I do not think this is necessary in WAVE files; + // possibly a remnant of ported AIFF reader + uio_fseek (wava->fp, chunkHdr.size & 1, SEEK_CUR); + } + + if (!wava->data_size || !wava->data_ofs) + { + log_add (log_Warning, "wava_Open(): bad wave file," + " no DATA chunk found"); + wava_Close (This); + return false; + } + + if (wava->fmtHdr.format != 0x0001) + { // not a PCM format + log_add (log_Warning, "wava_Open(): unsupported format %x", + wava->fmtHdr.format); + wava_Close (This); + return false; + } + if (wava->fmtHdr.channels != 1 && wava->fmtHdr.channels != 2) + { + log_add (log_Warning, "wava_Open(): unsupported number of channels %u", + (unsigned)wava->fmtHdr.channels); + wava_Close (This); + return false; + } + + if (dataLeft != 0) + log_add (log_Warning, "wava_Open(): bad or unsupported wave file, " + "size in header does not match read chunks"); + + This->format = (wava->fmtHdr.channels == 1 ? + (wava->fmtHdr.bitsPerSample == 8 ? + wava_formats->mono8 : wava_formats->mono16) + : + (wava->fmtHdr.bitsPerSample == 8 ? + wava_formats->stereo8 : wava_formats->stereo16) + ); + This->frequency = wava->fmtHdr.samplesPerSec; + + uio_fseek (wava->fp, wava->data_ofs, SEEK_SET); + wava->max_pcm = wava->data_size / wava->fmtHdr.blockAlign; + wava->cur_pcm = 0; + This->length = (float) wava->max_pcm / wava->fmtHdr.samplesPerSec; + wava->last_error = 0; + + return true; +} + +static void +wava_Close (THIS_PTR) +{ + TFB_WaveSoundDecoder* wava = (TFB_WaveSoundDecoder*) This; + + if (wava->fp) + { + uio_fclose (wava->fp); + wava->fp = NULL; + } +} + +static int +wava_Decode (THIS_PTR, void* buf, sint32 bufsize) +{ + TFB_WaveSoundDecoder* wava = (TFB_WaveSoundDecoder*) This; + uint32 dec_pcm; + + dec_pcm = bufsize / wava->fmtHdr.blockAlign; + if (dec_pcm > wava->max_pcm - wava->cur_pcm) + dec_pcm = wava->max_pcm - wava->cur_pcm; + + dec_pcm = uio_fread (buf, wava->fmtHdr.blockAlign, dec_pcm, wava->fp); + wava->cur_pcm += dec_pcm; + + return dec_pcm * wava->fmtHdr.blockAlign; +} + +static uint32 +wava_Seek (THIS_PTR, uint32 pcm_pos) +{ + TFB_WaveSoundDecoder* wava = (TFB_WaveSoundDecoder*) This; + + if (pcm_pos > wava->max_pcm) + pcm_pos = wava->max_pcm; + wava->cur_pcm = pcm_pos; + uio_fseek (wava->fp, + wava->data_ofs + pcm_pos * wava->fmtHdr.blockAlign, + SEEK_SET); + + return pcm_pos; +} + +static uint32 +wava_GetFrame (THIS_PTR) +{ + //TFB_WaveSoundDecoder* wava = (TFB_WaveSoundDecoder*) This; + return 0; // only 1 frame for now + + (void)This; // laugh at compiler warning +} diff --git a/src/libs/sound/decoders/wav.h b/src/libs/sound/decoders/wav.h new file mode 100644 index 0000000..9aaf347 --- /dev/null +++ b/src/libs/sound/decoders/wav.h @@ -0,0 +1,26 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Wave decoder */ + +#ifndef WAV_H +#define WAV_H + +#include "decoder.h" + +extern TFB_SoundDecoderFuncs wava_DecoderVtbl; + +#endif diff --git a/src/libs/sound/fileinst.c b/src/libs/sound/fileinst.c new file mode 100644 index 0000000..cafbb8f --- /dev/null +++ b/src/libs/sound/fileinst.c @@ -0,0 +1,87 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "sound.h" +#include "sndintrn.h" +#include "options.h" +#include "libs/reslib.h" +#include <string.h> + + +SOUND_REF +LoadSoundFile (const char *pStr) +{ + uio_Stream *fp; + + // FIXME: this theoretically needs a mechanism to prevent races + if (_cur_resfile_name) + // something else is loading resources atm + return 0; + + fp = res_OpenResFile (contentDir, pStr, "rb"); + if (fp) + { + SOUND_REF hData; + + _cur_resfile_name = pStr; + hData = (SOUND_REF)_GetSoundBankData (fp, LengthResFile (fp)); + _cur_resfile_name = 0; + + res_CloseResFile (fp); + + return hData; + } + + return NULL; +} + +MUSIC_REF +LoadMusicFile (const char *pStr) +{ + uio_Stream *fp; + char filename[256]; + + // FIXME: this theoretically needs a mechanism to prevent races + if (_cur_resfile_name) + // something else is loading resources atm + return 0; + + strncpy (filename, pStr, sizeof(filename) - 1); + filename[sizeof(filename) - 1] = '\0'; + CheckMusicResName (filename); + + // Opening the res file is not technically necessary right now + // since _GetMusicData() completely ignores the arguments + // But just for the sake of correctness + fp = res_OpenResFile (contentDir, filename, "rb"); + if (fp) + { + MUSIC_REF hData; + + _cur_resfile_name = filename; + hData = (MUSIC_REF)_GetMusicData (fp, LengthResFile (fp)); + _cur_resfile_name = 0; + + res_CloseResFile (fp); + + return hData; + } + + return (0); +} + diff --git a/src/libs/sound/mixer/Makeinfo b/src/libs/sound/mixer/Makeinfo new file mode 100644 index 0000000..66f960d --- /dev/null +++ b/src/libs/sound/mixer/Makeinfo @@ -0,0 +1,3 @@ +uqm_SUBDIRS="sdl nosound" +uqm_CFILES="mixer.c" +uqm_HFILES="mixer.h mixerint.h" diff --git a/src/libs/sound/mixer/mixer.c b/src/libs/sound/mixer/mixer.c new file mode 100644 index 0000000..3e14ddd --- /dev/null +++ b/src/libs/sound/mixer/mixer.c @@ -0,0 +1,1760 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Mixer for low-level sound output drivers + */ + +#include <stdio.h> +#include <string.h> +#include <math.h> +#include "mixer.h" +#include "mixerint.h" +#include "libs/misc.h" +#include "libs/threadlib.h" +#include "libs/log.h" +#include "libs/memlib.h" + +static uint32 mixer_initialized = 0; +static uint32 mixer_format; +static uint32 mixer_chansize; +static uint32 mixer_sampsize; +static uint32 mixer_freq; +static uint32 mixer_channels; +static uint32 last_error = MIX_NO_ERROR; +static mixer_Quality mixer_quality; +static mixer_Resampling mixer_resampling; +static mixer_Flags mixer_flags; + +/* when locking more than one mutex + * you must lock them in this order + */ +static RecursiveMutex src_mutex; +static RecursiveMutex buf_mutex; +static RecursiveMutex act_mutex; + +#define MAX_SOURCES 8 +mixer_Source *active_sources[MAX_SOURCES]; + + +/************************************************* + * Internals + */ + +static void +mixer_SetError (uint32 error) +{ + last_error = error; +} + + +/************************************************* + * General interface + */ + +uint32 +mixer_GetError (void) +{ + uint32 error = last_error; + last_error = MIX_NO_ERROR; + return error; +} + +/* Initialize the mixer with a certain audio format */ +bool +mixer_Init (uint32 frequency, uint32 format, mixer_Quality quality, + mixer_Flags flags) +{ + if (mixer_initialized) + mixer_Uninit (); + + last_error = MIX_NO_ERROR; + memset (active_sources, 0, sizeof(mixer_Source*) * MAX_SOURCES); + + mixer_chansize = MIX_FORMAT_BPC (format); + mixer_channels = MIX_FORMAT_CHANS (format); + mixer_sampsize = MIX_FORMAT_SAMPSIZE (format); + mixer_freq = frequency; + mixer_quality = quality; + mixer_format = format; + mixer_flags = flags; + + mixer_resampling.None = mixer_ResampleNone; + mixer_resampling.Downsample = mixer_ResampleNearest; + if (mixer_quality == MIX_QUALITY_DEFAULT) + mixer_resampling.Upsample = mixer_UpsampleLinear; + else if (mixer_quality == MIX_QUALITY_HIGH) + mixer_resampling.Upsample = mixer_UpsampleCubic; + else + mixer_resampling.Upsample = mixer_ResampleNearest; + + src_mutex = CreateRecursiveMutex("mixer_SourceMutex", SYNC_CLASS_AUDIO); + buf_mutex = CreateRecursiveMutex("mixer_BufferMutex", SYNC_CLASS_AUDIO); + act_mutex = CreateRecursiveMutex("mixer_ActiveMutex", SYNC_CLASS_AUDIO); + + mixer_initialized = 1; + + return true; +} + +/* Uninitialize the mixer */ +void +mixer_Uninit (void) +{ + if (mixer_initialized) + { + DestroyRecursiveMutex (src_mutex); + DestroyRecursiveMutex (buf_mutex); + DestroyRecursiveMutex (act_mutex); + mixer_initialized = 0; + } +} + + +/********************************************************** + * THE mixer + * + */ + +void +mixer_MixChannels (void *userdata, uint8 *stream, sint32 len) +{ + uint8 *end_stream = stream + len; + bool left = true; + + /* keep this order or die */ + LockRecursiveMutex (src_mutex); + LockRecursiveMutex (buf_mutex); + LockRecursiveMutex (act_mutex); + + for (; stream < end_stream; stream += mixer_chansize) + { + uint32 i; + float fullsamp = 0; + + for (i = 0; i < MAX_SOURCES; i++) + { + mixer_Source *src; + float samp = 0; + + /* find next source */ + for (; i < MAX_SOURCES && ( + (src = active_sources[i]) == 0 + || src->state != MIX_PLAYING + || !mixer_SourceGetNextSample (src, &samp, left)); + i++) + ; + + if (i < MAX_SOURCES) + { + /* sample acquired */ + fullsamp += samp; + } + } + + /* clip the sample */ + if (mixer_chansize == 2) + { + /* check S16 clipping */ + if (fullsamp > SINT16_MAX) + fullsamp = SINT16_MAX; + else if (fullsamp < SINT16_MIN) + fullsamp = SINT16_MIN; + } + else + { + /* check S8 clipping */ + if (fullsamp > SINT8_MAX) + fullsamp = SINT8_MAX; + else if (fullsamp < SINT8_MIN) + fullsamp = SINT8_MIN; + } + + mixer_PutSampleExt (stream, mixer_chansize, (sint32)fullsamp); + if (mixer_channels == 2) + left = !left; + } + + /* keep this order or die */ + UnlockRecursiveMutex (act_mutex); + UnlockRecursiveMutex (buf_mutex); + UnlockRecursiveMutex (src_mutex); + + (void) userdata; // satisfying compiler - unused arg +} + +/* fake mixer -- only process buffer and source states */ +void +mixer_MixFake (void *userdata, uint8 *stream, sint32 len) +{ + uint8 *end_stream = stream + len; + bool left = true; + + /* keep this order or die */ + LockRecursiveMutex (src_mutex); + LockRecursiveMutex (buf_mutex); + LockRecursiveMutex (act_mutex); + + for (; stream < end_stream; stream += mixer_chansize) + { + uint32 i; + + for (i = 0; i < MAX_SOURCES; i++) + { + mixer_Source *src; + float samp; + + /* find next source */ + for (; i < MAX_SOURCES && ( + (src = active_sources[i]) == 0 + || src->state != MIX_PLAYING + || !mixer_SourceGetFakeSample (src, &samp, left)); + i++) + ; + } + if (mixer_channels == 2) + left = !left; + } + + /* keep this order or die */ + UnlockRecursiveMutex (act_mutex); + UnlockRecursiveMutex (buf_mutex); + UnlockRecursiveMutex (src_mutex); + + (void) userdata; // satisfying compiler - unused arg +} + + +/************************************************* + * Sources interface + */ + +/* generate n sources */ +void +mixer_GenSources (uint32 n, mixer_Object *psrcobj) +{ + if (n == 0) + return; /* do nothing per OpenAL */ + + if (!psrcobj) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_GenSources() called with null ptr"); + return; + } + for (; n; n--, psrcobj++) + { + mixer_Source *src; + + src = (mixer_Source *) HMalloc (sizeof (mixer_Source)); + src->magic = mixer_srcMagic; + src->locked = false; + src->state = MIX_INITIAL; + src->looping = false; + src->gain = MIX_GAIN_ADJ; + src->cqueued = 0; + src->cprocessed = 0; + src->firstqueued = 0; + src->nextqueued = 0; + src->prevqueued = 0; + src->lastqueued = 0; + src->pos = 0; + src->count = 0; + + *psrcobj = (mixer_Object) src; + } +} + +/* delete n sources */ +void +mixer_DeleteSources (uint32 n, mixer_Object *psrcobj) +{ + uint32 i; + mixer_Object *pcurobj; + + if (n == 0) + return; /* do nothing per OpenAL */ + + if (!psrcobj) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_DeleteSources() called with null ptr"); + return; + } + + LockRecursiveMutex (src_mutex); + + /* check to make sure we can delete all sources */ + for (i = n, pcurobj = psrcobj; i && pcurobj; i--, pcurobj++) + { + mixer_Source *src = (mixer_Source *) *pcurobj; + + if (!src) + continue; + + if (src->magic != mixer_srcMagic) + break; + } + + if (i) + { /* some source failed */ + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_DeleteSources(): not a source"); + } + else + { /* all sources checked out */ + for (; n; n--, psrcobj++) + { + mixer_Source *src = (mixer_Source *) *psrcobj; + + if (!src) + continue; + + /* stopping should not be necessary + * under ideal circumstances + */ + if (src->state != MIX_INITIAL) + mixer_SourceStop_internal (src); + + /* unqueueing should not be necessary + * under ideal circumstances + */ + mixer_SourceUnqueueAll (src); + HFree (src); + *psrcobj = 0; + } + } + + UnlockRecursiveMutex (src_mutex); +} + +/* check if really is a source */ +bool +mixer_IsSource (mixer_Object srcobj) +{ + mixer_Source *src = (mixer_Source *) srcobj; + bool ret; + + if (!src) + return false; + + LockRecursiveMutex (src_mutex); + ret = src->magic == mixer_srcMagic; + UnlockRecursiveMutex (src_mutex); + + return ret; +} + +/* set source integer property */ +void +mixer_Sourcei (mixer_Object srcobj, mixer_SourceProp pname, + mixer_IntVal value) +{ + mixer_Source *src = (mixer_Source *) srcobj; + + if (!src) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_Sourcei() called with null source"); + return; + } + + LockRecursiveMutex (src_mutex); + + if (src->magic != mixer_srcMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_Sourcei(): not a source"); + } + else + { + switch (pname) + { + case MIX_LOOPING: + src->looping = value; + break; + case MIX_BUFFER: + { + mixer_Buffer *buf = (mixer_Buffer *) value; + + if (src->cqueued > 0) + mixer_SourceUnqueueAll (src); + + if (buf && !mixer_CheckBufferState (buf, "mixer_Sourcei")) + break; + + src->firstqueued = buf; + src->nextqueued = src->firstqueued; + src->prevqueued = 0; + src->lastqueued = src->nextqueued; + if (src->lastqueued) + src->lastqueued->next = 0; + src->cqueued = 1; + } + break; + case MIX_SOURCE_STATE: + if (value == MIX_INITIAL) + { + mixer_SourceRewind_internal (src); + } + else + { + log_add (log_Debug, "mixer_Sourcei(MIX_SOURCE_STATE): " + "unsupported state, call ignored"); + } + break; + default: + mixer_SetError (MIX_INVALID_ENUM); + log_add (log_Debug, "mixer_Sourcei() called " + "with unsupported property %u", pname); + } + } + + UnlockRecursiveMutex (src_mutex); +} + +/* set source float property */ +void +mixer_Sourcef (mixer_Object srcobj, mixer_SourceProp pname, float value) +{ + mixer_Source *src = (mixer_Source *) srcobj; + + if (!src) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_Sourcef() called with null source"); + return; + } + + LockRecursiveMutex (src_mutex); + + if (src->magic != mixer_srcMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_Sourcef(): not a source"); + } + else + { + switch (pname) + { + case MIX_GAIN: + src->gain = value * MIX_GAIN_ADJ; + break; + default: + log_add (log_Debug, "mixer_Sourcei() called " + "with unsupported property %u", pname); + } + } + + UnlockRecursiveMutex (src_mutex); +} + +/* set source float array property (CURRENTLY NOT IMPLEMENTED) */ +void mixer_Sourcefv (mixer_Object srcobj, mixer_SourceProp pname, float *value) +{ + (void)srcobj; + (void)pname; + (void)value; +} + + +/* get source integer property */ +void +mixer_GetSourcei (mixer_Object srcobj, mixer_SourceProp pname, + mixer_IntVal *value) +{ + mixer_Source *src = (mixer_Source *) srcobj; + + if (!src || !value) + { + mixer_SetError (src ? MIX_INVALID_VALUE : MIX_INVALID_NAME); + log_add (log_Debug, "mixer_GetSourcei() called with null param"); + return; + } + + LockRecursiveMutex (src_mutex); + + if (src->magic != mixer_srcMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_GetSourcei(): not a source"); + } + else + { + switch (pname) + { + case MIX_LOOPING: + *value = src->looping; + break; + case MIX_BUFFER: + *value = (mixer_IntVal) src->firstqueued; + break; + case MIX_SOURCE_STATE: + *value = src->state; + break; + case MIX_BUFFERS_QUEUED: + *value = src->cqueued; + break; + case MIX_BUFFERS_PROCESSED: + *value = src->cprocessed; + break; + default: + mixer_SetError (MIX_INVALID_ENUM); + log_add (log_Debug, "mixer_GetSourcei() called " + "with unsupported property %u", pname); + } + } + + UnlockRecursiveMutex (src_mutex); +} + +/* get source float property */ +void +mixer_GetSourcef (mixer_Object srcobj, mixer_SourceProp pname, + float *value) +{ + mixer_Source *src = (mixer_Source *) srcobj; + + if (!src || !value) + { + mixer_SetError (src ? MIX_INVALID_VALUE : MIX_INVALID_NAME); + log_add (log_Debug, "mixer_GetSourcef() called with null param"); + return; + } + + LockRecursiveMutex (src_mutex); + + if (src->magic != mixer_srcMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_GetSourcef(): not a source"); + } + else + { + switch (pname) + { + case MIX_GAIN: + *value = src->gain / MIX_GAIN_ADJ; + break; + default: + log_add (log_Debug, "mixer_GetSourcef() called " + "with unsupported property %u", pname); + } + } + + UnlockRecursiveMutex (src_mutex); +} + +/* start the source; add it to active array */ +void +mixer_SourcePlay (mixer_Object srcobj) +{ + mixer_Source *src = (mixer_Source *) srcobj; + + if (!src) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourcePlay() called with null source"); + return; + } + + LockRecursiveMutex (src_mutex); + + if (src->magic != mixer_srcMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourcePlay(): not a source"); + } + else /* should make the source active */ + { + if (src->state < MIX_PLAYING) + { + if (src->firstqueued && !src->nextqueued) + mixer_SourceRewind_internal (src); + mixer_SourceActivate (src); + } + src->state = MIX_PLAYING; + } + + UnlockRecursiveMutex (src_mutex); +} + +/* stop the source; remove it from active array and requeue buffers */ +void +mixer_SourceRewind (mixer_Object srcobj) +{ + mixer_Source *src = (mixer_Source *) srcobj; + + if (!src) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourceRewind() called with null source"); + return; + } + + LockRecursiveMutex (src_mutex); + + if (src->magic != mixer_srcMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourcePlay(): not a source"); + } + else + { + mixer_SourceRewind_internal (src); + } + + UnlockRecursiveMutex (src_mutex); +} + +/* pause the source; keep in active array */ +void +mixer_SourcePause (mixer_Object srcobj) +{ + mixer_Source *src = (mixer_Source *) srcobj; + + if (!src) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourcePause() called with null source"); + return; + } + + LockRecursiveMutex (src_mutex); + + if (src->magic != mixer_srcMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourcePause(): not a source"); + } + else /* should keep all buffers and offsets */ + { + if (src->state < MIX_PLAYING) + mixer_SourceActivate (src); + src->state = MIX_PAUSED; + } + + UnlockRecursiveMutex (src_mutex); +} + +/* stop the source; remove it from active array + * and unqueue 'queued' buffers + */ +void +mixer_SourceStop (mixer_Object srcobj) +{ + mixer_Source *src = (mixer_Source *) srcobj; + + if (!src) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourceStop() called with null source"); + return; + } + + LockRecursiveMutex (src_mutex); + + if (src->magic != mixer_srcMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourceStop(): not a source"); + } + else /* should remove queued buffers */ + { + if (src->state >= MIX_PLAYING) + mixer_SourceDeactivate (src); + mixer_SourceStop_internal (src); + src->state = MIX_STOPPED; + } + + UnlockRecursiveMutex (src_mutex); +} + +/* queue buffers on the source */ +void +mixer_SourceQueueBuffers (mixer_Object srcobj, uint32 n, + mixer_Object* pbufobj) +{ + uint32 i; + mixer_Object* pobj; + mixer_Source *src = (mixer_Source *) srcobj; + + if (!src || !pbufobj) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourceQueueBuffers() called " + "with null param"); + return; + } + + LockRecursiveMutex (buf_mutex); + /* check to make sure we can safely queue all buffers */ + for (i = n, pobj = pbufobj; i; i--, pobj++) + { + mixer_Buffer *buf = (mixer_Buffer *) *pobj; + if (!buf || !mixer_CheckBufferState (buf, + "mixer_SourceQueueBuffers")) + { + break; + } + } + UnlockRecursiveMutex (buf_mutex); + + if (i == 0) + { /* all buffers checked out */ + LockRecursiveMutex (src_mutex); + LockRecursiveMutex (buf_mutex); + + if (src->magic != mixer_srcMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourceQueueBuffers(): not a source"); + } + else + { + for (i = n, pobj = pbufobj; i; i--, pobj++) + { + mixer_Buffer *buf = (mixer_Buffer *) *pobj; + + /* add buffer to the chain */ + if (src->lastqueued) + src->lastqueued->next = buf; + src->lastqueued = buf; + + if (!src->firstqueued) + { + src->firstqueued = buf; + src->nextqueued = buf; + src->prevqueued = 0; + } + src->cqueued++; + buf->state = MIX_BUF_QUEUED; + } + } + + UnlockRecursiveMutex (buf_mutex); + UnlockRecursiveMutex (src_mutex); + } +} + +/* unqueue buffers from the source */ +void +mixer_SourceUnqueueBuffers (mixer_Object srcobj, uint32 n, + mixer_Object* pbufobj) +{ + uint32 i; + mixer_Source *src = (mixer_Source *) srcobj; + mixer_Buffer *curbuf = 0; + + if (!src || !pbufobj) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourceUnqueueBuffers() called " + "with null source"); + return; + } + + LockRecursiveMutex (src_mutex); + + if (src->magic != mixer_srcMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourceUnqueueBuffers(): not a source"); + } + else if (n > src->cqueued) + { + mixer_SetError (MIX_INVALID_OPERATION); + } + else + { + LockRecursiveMutex (buf_mutex); + + /* check to make sure we can unqueue all buffers */ + for (i = n, curbuf = src->firstqueued; + i && curbuf && curbuf->state != MIX_BUF_PLAYING; + i--, curbuf = curbuf->next) + ; + + if (i) + { + mixer_SetError (MIX_INVALID_OPERATION); + log_add (log_Debug, "mixer_SourceUnqueueBuffers(): " + "active buffer attempted"); + } + else + { /* all buffers checked out */ + for (i = n; i; i--, pbufobj++) + { + mixer_Buffer *buf = src->firstqueued; + + /* remove buffer from the chain */ + if (src->nextqueued == buf) + src->nextqueued = buf->next; + if (src->prevqueued == buf) + src->prevqueued = 0; + if (src->lastqueued == buf) + src->lastqueued = 0; + src->firstqueued = buf->next; + src->cqueued--; + + if (buf->state == MIX_BUF_PROCESSED) + src->cprocessed--; + + buf->state = MIX_BUF_FILLED; + buf->next = 0; + *pbufobj = (mixer_Object) buf; + } + } + + UnlockRecursiveMutex (buf_mutex); + } + + UnlockRecursiveMutex (src_mutex); +} + +/************************************************* + * Sources internals + */ + +static void +mixer_SourceUnqueueAll (mixer_Source *src) +{ + mixer_Buffer *buf; + mixer_Buffer *nextbuf; + + if (!src) + { + log_add (log_Debug, "mixer_SourceUnqueueAll() called " + "with null source"); + return; + } + + LockRecursiveMutex (buf_mutex); + + for (buf = src->firstqueued; buf; buf = nextbuf) + { + if (buf->state == MIX_BUF_PLAYING) + { + log_add (log_Debug, "mixer_SourceUnqueueAll(): " + "attempted on active buffer"); + } + nextbuf = buf->next; + buf->state = MIX_BUF_FILLED; + buf->next = 0; + } + + UnlockRecursiveMutex (buf_mutex); + + src->firstqueued = 0; + src->nextqueued = 0; + src->prevqueued = 0; + src->lastqueued = 0; + src->cqueued = 0; + src->cprocessed = 0; + src->pos = 0; + src->count = 0; +} + +/* add the source to the active array */ +static void +mixer_SourceActivate (mixer_Source* src) +{ + uint32 i; + + LockRecursiveMutex (act_mutex); + + /* check active sources, see if this source is there already */ + for (i = 0; i < MAX_SOURCES && active_sources[i] != src; i++) + ; + if (i < MAX_SOURCES) + { /* source found */ + log_add (log_Debug, "mixer_SourceActivate(): " + "source already active in slot %u", i); + UnlockRecursiveMutex (act_mutex); + return; + } + + /* find an empty slot */ + for (i = 0; i < MAX_SOURCES && active_sources[i] != 0; i++) + ; + if (i < MAX_SOURCES) + { /* slot found */ + active_sources[i] = src; + } + else + { + log_add (log_Debug, "mixer_SourceActivate(): " + "no more slots available (max=%d)", MAX_SOURCES); + } + + UnlockRecursiveMutex (act_mutex); +} + +/* remove the source from the active array */ +static void +mixer_SourceDeactivate (mixer_Source* src) +{ + uint32 i; + + LockRecursiveMutex (act_mutex); + + /* check active sources, see if this source is there */ + for (i = 0; i < MAX_SOURCES && active_sources[i] != src; i++) + ; + if (i < MAX_SOURCES) + { /* source found */ + active_sources[i] = 0; + } + else + { /* source not found */ + log_add (log_Debug, "mixer_SourceDeactivate(): source not active"); + } + + UnlockRecursiveMutex (act_mutex); +} + +static void +mixer_SourceStop_internal (mixer_Source *src) +{ + mixer_Buffer *buf; + mixer_Buffer *nextbuf; + + if (!src->firstqueued) + return; + + /* assert the source buffers state */ + if (!src->lastqueued) + { + log_add (log_Debug, "mixer_SourceStop_internal(): " + "desynced source state"); +#ifdef DEBUG + explode (); +#endif + } + + LockRecursiveMutex (buf_mutex); + + /* find last 'processed' buffer */ + for (buf = src->firstqueued; + buf && buf->next && buf->next != src->nextqueued; + buf = buf->next) + ; + src->lastqueued = buf; + if (buf) + buf->next = 0; /* break the chain */ + + /* unqueue all 'queued' buffers */ + for (buf = src->nextqueued; buf; buf = nextbuf) + { + nextbuf = buf->next; + buf->state = MIX_BUF_FILLED; + buf->next = 0; + src->cqueued--; + } + + if (src->cqueued == 0) + { /* all buffers were removed */ + src->firstqueued = 0; + src->lastqueued = 0; + } + src->nextqueued = 0; + src->prevqueued = 0; + src->pos = 0; + src->count = 0; + + UnlockRecursiveMutex (buf_mutex); +} + +static void +mixer_SourceRewind_internal (mixer_Source *src) +{ + /* should change the processed buffers to queued */ + mixer_Buffer *buf; + + if (src->state >= MIX_PLAYING) + mixer_SourceDeactivate (src); + + LockRecursiveMutex (buf_mutex); + + for (buf = src->firstqueued; + buf && buf->state != MIX_BUF_QUEUED; + buf = buf->next) + { + buf->state = MIX_BUF_QUEUED; + } + + UnlockRecursiveMutex (buf_mutex); + + src->pos = 0; + src->count = 0; + src->cprocessed = 0; + src->nextqueued = src->firstqueued; + src->prevqueued = 0; + src->state = MIX_INITIAL; +} + +/* get the sample next in queue in internal format */ +static inline bool +mixer_SourceGetNextSample (mixer_Source *src, float *psamp, bool left) +{ + /* fake the data if requested */ + if (mixer_flags & MIX_FAKE_DATA) + return mixer_SourceGetFakeSample (src, psamp, left); + + while (src->nextqueued) + { + mixer_Buffer *buf = src->nextqueued; + + if (!buf->data || buf->size < mixer_sampsize) + { + /* buffer invalid, go next */ + buf->state = MIX_BUF_PROCESSED; + src->pos = 0; + src->nextqueued = src->nextqueued->next; + src->cprocessed++; + continue; + } + + if (!left && buf->orgchannels == 1) + { + /* mono source so we can copy left channel to right */ + *psamp = src->samplecache; + } + else + { + *psamp = src->samplecache = buf->Resample(src, left) * src->gain; + } + + if (src->pos < buf->size || + (left && buf->sampsize != mixer_sampsize)) + { + buf->state = MIX_BUF_PLAYING; + } + else + { + /* buffer exhausted, go next */ + buf->state = MIX_BUF_PROCESSED; + src->pos = 0; + src->prevqueued = src->nextqueued; + src->nextqueued = src->nextqueued->next; + src->cprocessed++; + } + + return true; + } + + /* no more playable buffers */ + if (src->state >= MIX_PLAYING) + mixer_SourceDeactivate (src); + + src->state = MIX_STOPPED; + + return false; +} + +/* fake the next sample, but process buffers and states */ +static inline bool +mixer_SourceGetFakeSample (mixer_Source *src, float *psamp, bool left) +{ + while (src->nextqueued) + { + mixer_Buffer *buf = src->nextqueued; + + if (left || buf->orgchannels != 1) + { + if (mixer_freq == buf->orgfreq) + src->pos += mixer_chansize; + else + mixer_SourceAdvance(src, left); + } + *psamp = 0; + + if (src->pos < buf->size || + (left && buf->sampsize != mixer_sampsize)) + { + buf->state = MIX_BUF_PLAYING; + } + else + { + /* buffer exhausted, go next */ + buf->state = MIX_BUF_PROCESSED; + src->pos = 0; + src->prevqueued = src->nextqueued; + src->nextqueued = src->nextqueued->next; + src->cprocessed++; + } + + return true; + } + + /* no more playable buffers */ + if (src->state >= MIX_PLAYING) + mixer_SourceDeactivate (src); + + src->state = MIX_STOPPED; + + return false; +} + +/* advance position in currently queued buffer */ +static inline uint32 +mixer_SourceAdvance (mixer_Source *src, bool left) +{ + mixer_Buffer *curr = src->nextqueued; + if (curr->orgchannels == 2 && mixer_channels == 2) + { + if (!left) + { + src->pos += curr->high; + src->count += curr->low; + if (src->count > UINT16_MAX) + { + src->count -= UINT16_MAX; + src->pos += curr->sampsize; + } + return mixer_chansize; + } + } + else + { + src->pos += curr->high; + src->count += curr->low; + if (src->count > UINT16_MAX) + { + src->count -= UINT16_MAX; + src->pos += curr->sampsize; + } + } + return 0; +} + + +/************************************************* + * Buffers interface + */ + +/* generate n buffer objects */ +void +mixer_GenBuffers (uint32 n, mixer_Object *pbufobj) +{ + if (n == 0) + return; /* do nothing per OpenAL */ + + if (!pbufobj) + { + mixer_SetError (MIX_INVALID_VALUE); + log_add (log_Debug, "mixer_GenBuffers() called with null ptr"); + return; + } + for (; n; n--, pbufobj++) + { + mixer_Buffer *buf; + + buf = (mixer_Buffer *) HMalloc (sizeof (mixer_Buffer)); + buf->magic = mixer_bufMagic; + buf->locked = false; + buf->state = MIX_BUF_INITIAL; + buf->data = 0; + buf->size = 0; + buf->next = 0; + buf->orgdata = 0; + buf->orgfreq = 0; + buf->orgsize = 0; + buf->orgchannels = 0; + buf->orgchansize = 0; + + *pbufobj = (mixer_Object) buf; + } +} + +/* delete n buffer objects */ +void +mixer_DeleteBuffers (uint32 n, mixer_Object *pbufobj) +{ + uint32 i; + mixer_Object *pcurobj; + + if (n == 0) + return; /* do nothing per OpenAL */ + + if (!pbufobj) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_DeleteBuffers() called with null ptr"); + return; + } + + LockRecursiveMutex (buf_mutex); + + /* check to make sure we can delete all buffers */ + for (i = n, pcurobj = pbufobj; i && pcurobj; i--, pcurobj++) + { + mixer_Buffer *buf = (mixer_Buffer *) *pcurobj; + + if (!buf) + continue; + + if (buf->magic != mixer_bufMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_DeleteBuffers(): not a buffer"); + break; + } + else if (buf->locked) + { + mixer_SetError (MIX_INVALID_OPERATION); + log_add (log_Debug, "mixer_DeleteBuffers(): locked buffer"); + break; + } + else if (buf->state >= MIX_BUF_QUEUED) + { + mixer_SetError (MIX_INVALID_OPERATION); + log_add (log_Debug, "mixer_DeleteBuffers(): " + "attempted on queued/active buffer"); + break; + } + } + + if (i == 0) + { + /* all buffers check out */ + for (; n; n--, pbufobj++) + { + mixer_Buffer *buf = (mixer_Buffer *) *pbufobj; + + if (!buf) + continue; + + if (buf->data) + HFree (buf->data); + HFree (buf); + + *pbufobj = 0; + } + } + UnlockRecursiveMutex (buf_mutex); +} + +/* check if really a buffer object */ +bool +mixer_IsBuffer (mixer_Object bufobj) +{ + mixer_Buffer *buf = (mixer_Buffer *) bufobj; + bool ret; + + if (!buf) + return false; + + LockRecursiveMutex (buf_mutex); + ret = buf->magic == mixer_bufMagic; + UnlockRecursiveMutex (buf_mutex); + + return ret; +} + +/* get buffer property */ +void +mixer_GetBufferi (mixer_Object bufobj, mixer_BufferProp pname, + mixer_IntVal *value) +{ + mixer_Buffer *buf = (mixer_Buffer *) bufobj; + + if (!buf || !value) + { + mixer_SetError (buf ? MIX_INVALID_VALUE : MIX_INVALID_NAME); + log_add (log_Debug, "mixer_GetBufferi() called with null param"); + return; + } + + LockRecursiveMutex (buf_mutex); + + if (buf->locked) + { + UnlockRecursiveMutex (buf_mutex); + mixer_SetError (MIX_INVALID_OPERATION); + log_add (log_Debug, "mixer_GetBufferi() called with locked buffer"); + return; + } + + if (buf->magic != mixer_bufMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_GetBufferi(): not a buffer"); + } + else + { + /* Return original buffer values + */ + switch (pname) + { + case MIX_FREQUENCY: + *value = buf->orgfreq; + break; + case MIX_BITS: + *value = buf->orgchansize << 3; + break; + case MIX_CHANNELS: + *value = buf->orgchannels; + break; + case MIX_SIZE: + *value = buf->orgsize; + break; + case MIX_DATA: + *value = (mixer_IntVal) buf->orgdata; + break; + default: + mixer_SetError (MIX_INVALID_ENUM); + log_add (log_Debug, "mixer_GetBufferi() called " + "with invalid property %u", pname); + } + } + + UnlockRecursiveMutex (buf_mutex); +} + +/* fill buffer with external data */ +void +mixer_BufferData (mixer_Object bufobj, uint32 format, void* data, + uint32 size, uint32 freq) +{ + mixer_Buffer *buf = (mixer_Buffer *) bufobj; + mixer_Convertion conv; + uint32 dstsize; + + if (!buf || !data || !size) + { + mixer_SetError (buf ? MIX_INVALID_VALUE : MIX_INVALID_NAME); + log_add (log_Debug, "mixer_BufferData() called with bad param"); + return; + } + + LockRecursiveMutex (buf_mutex); + + if (buf->locked) + { + UnlockRecursiveMutex (buf_mutex); + mixer_SetError (MIX_INVALID_OPERATION); + log_add (log_Debug, "mixer_BufferData() called " + "with locked buffer"); + return; + } + + if (buf->magic != mixer_bufMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_BufferData(): not a buffer"); + } + else if (buf->state > MIX_BUF_FILLED) + { + mixer_SetError (MIX_INVALID_OPERATION); + log_add (log_Debug, "mixer_BufferData() attempted " + "on in-use buffer"); + } + else + { + if (buf->data) + HFree (buf->data); + buf->data = 0; + buf->size = 0; + + /* Store original buffer values for OpenAL compatibility */ + buf->orgdata = data; + buf->orgfreq = freq; + buf->orgsize = size; + buf->orgchannels = MIX_FORMAT_CHANS (format); + buf->orgchansize = MIX_FORMAT_BPC (format); + + conv.srcsamples = conv.dstsamples = + size / MIX_FORMAT_SAMPSIZE (format); + + if (conv.dstsamples > + UINT32_MAX / MIX_FORMAT_SAMPSIZE (format)) + { + mixer_SetError (MIX_INVALID_VALUE); + } + else + { + if (MIX_FORMAT_CHANS (format) < MIX_FORMAT_CHANS (mixer_format)) + buf->sampsize = MIX_FORMAT_BPC (mixer_format) * + MIX_FORMAT_CHANS (format); + else + buf->sampsize = MIX_FORMAT_SAMPSIZE (mixer_format); + buf->size = dstsize = conv.dstsamples * buf->sampsize; + + /* only copy/convert the data if not faking */ + if (! (mixer_flags & MIX_FAKE_DATA)) + { + buf->data = HMalloc (dstsize); + + if (MIX_FORMAT_BPC (format) == MIX_FORMAT_BPC (mixer_format) && + MIX_FORMAT_CHANS (format) <= MIX_FORMAT_CHANS (mixer_format)) + { + /* format is compatible with internal */ + buf->locked = true; + UnlockRecursiveMutex (buf_mutex); + + memcpy (buf->data, data, size); + if (MIX_FORMAT_BPC (format) == 1) + { + /* convert buffer to S8 format internally */ + uint8* dst; + for (dst = buf->data; dstsize; dstsize--, dst++) + *dst ^= 0x80; + } + + LockRecursiveMutex (buf_mutex); + buf->locked = false; + } + else + { + /* needs convertion */ + conv.srcfmt = format; + conv.srcdata = data; + conv.srcsize = size; + + if (MIX_FORMAT_CHANS (format) < MIX_FORMAT_CHANS (mixer_format)) + conv.dstfmt = MIX_FORMAT_MAKE (mixer_chansize, + MIX_FORMAT_CHANS (format)); + else + conv.dstfmt = mixer_format; + conv.dstdata = buf->data; + conv.dstsize = dstsize; + + buf->locked = true; + UnlockRecursiveMutex (buf_mutex); + + mixer_ConvertBuffer_internal (&conv); + + LockRecursiveMutex (buf_mutex); + buf->locked = false; + } + } + + buf->state = MIX_BUF_FILLED; + buf->high = (buf->orgfreq / mixer_freq) * buf->sampsize; + buf->low = (((buf->orgfreq % mixer_freq) << 16) / mixer_freq); + if (mixer_freq == buf->orgfreq) + buf->Resample = mixer_resampling.None; + else if (mixer_freq > buf->orgfreq) + buf->Resample = mixer_resampling.Upsample; + else + buf->Resample = mixer_resampling.Downsample; + } + } + + UnlockRecursiveMutex (buf_mutex); +} + + +/************************************************* + * Buffer internals + */ + +static inline bool +mixer_CheckBufferState (mixer_Buffer *buf, const char* FuncName) +{ + if (!buf) + return false; + + if (buf->magic != mixer_bufMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "%s(): not a buffer", FuncName); + return false; + } + + if (buf->locked) + { + mixer_SetError (MIX_INVALID_OPERATION); + log_add (log_Debug, "%s(): locked buffer attempted", FuncName); + return false; + } + + if (buf->state != MIX_BUF_FILLED) + { + mixer_SetError (MIX_INVALID_OPERATION); + log_add (log_Debug, "%s: invalid buffer attempted", FuncName); + return false; + } + return true; +} + +static void +mixer_ConvertBuffer_internal (mixer_Convertion *conv) +{ + conv->srcbpc = MIX_FORMAT_BPC (conv->srcfmt); + conv->srcchans = MIX_FORMAT_CHANS (conv->srcfmt); + conv->dstbpc = MIX_FORMAT_BPC (conv->dstfmt); + conv->dstchans = MIX_FORMAT_CHANS (conv->dstfmt); + + conv->flags = 0; + if (conv->srcbpc > conv->dstbpc) + conv->flags |= mixConvSizeDown; + else if (conv->srcbpc < conv->dstbpc) + conv->flags |= mixConvSizeUp; + if (conv->srcchans > conv->dstchans) + conv->flags |= mixConvStereoDown; + else if (conv->srcchans < conv->dstchans) + conv->flags |= mixConvStereoUp; + + mixer_ResampleFlat (conv); +} + +/************************************************* + * Resampling routines + */ + +/* get a sample from external buffer + * in internal format + */ +static inline sint32 +mixer_GetSampleExt (void *src, uint32 bpc) +{ + if (bpc == 2) + return *(sint16 *)src; + else + return (*(uint8 *)src) - 128; +} + +/* get a sample from internal buffer */ +static inline sint32 +mixer_GetSampleInt (void *src, uint32 bpc) +{ + if (bpc == 2) + return *(sint16 *)src; + else + return *(sint8 *)src; +} + +/* put a sample into an external buffer + * from internal format + */ +static inline void +mixer_PutSampleExt (void *dst, uint32 bpc, sint32 samp) +{ + if (bpc == 2) + *(sint16 *)dst = samp; + else + *(uint8 *)dst = samp ^ 0x80; +} + +/* put a sample into an internal buffer + * in internal format + */ +static inline void +mixer_PutSampleInt (void *dst, uint32 bpc, sint32 samp) +{ + if (bpc == 2) + *(sint16 *)dst = samp; + else + *(sint8 *)dst = samp; +} + +/* get a sample from source */ +static float +mixer_ResampleNone (mixer_Source *src, bool left) +{ + uint8 *d0 = src->nextqueued->data + src->pos; + src->pos += mixer_chansize; + (void) left; // satisfying compiler - unused arg + return mixer_GetSampleInt (d0, mixer_chansize); +} + +/* get a resampled (up/down) sample from source (nearest neighbor) */ +static float +mixer_ResampleNearest (mixer_Source *src, bool left) +{ + uint8 *d0 = src->nextqueued->data + src->pos; + d0 += mixer_SourceAdvance (src, left); + return mixer_GetSampleInt (d0, mixer_chansize); +} + +/* get an upsampled sample from source (linear interpolation) */ +static float +mixer_UpsampleLinear (mixer_Source *src, bool left) +{ + mixer_Buffer *curr = src->nextqueued; + mixer_Buffer *next = src->nextqueued->next; + uint8 *d0, *d1; + float s0, s1, t; + + t = src->count / 65536.0f; + d0 = curr->data + src->pos; + d0 += mixer_SourceAdvance (src, left); + + if (d0 + curr->sampsize >= curr->data + curr->size) + { + if (next && next->data && next->size >= curr->sampsize) + { + d1 = next->data; + if (!left) + d1 += mixer_chansize; + } + else + d1 = d0; + } + else + d1 = d0 + curr->sampsize; + + s0 = mixer_GetSampleInt (d0, mixer_chansize); + s1 = mixer_GetSampleInt (d1, mixer_chansize); + return s0 + t * (s1 - s0); +} + +/* get an upsampled sample from source (cubic interpolation) */ +static float +mixer_UpsampleCubic (mixer_Source *src, bool left) +{ + mixer_Buffer *prev = src->prevqueued; + mixer_Buffer *curr = src->nextqueued; + mixer_Buffer *next = src->nextqueued->next; + uint8 *d0, *d1, *d2, *d3; /* prev, curr, next, next + 1 */ + float t, t2, a, b, c, s0, s1, s2, s3; + + t = src->count / 65536.0f; + t2 = t * t; + d1 = curr->data + src->pos; + d1 += mixer_SourceAdvance (src, left); + + if (d1 - curr->sampsize < curr->data) + { + if (prev && prev->data && prev->size >= curr->sampsize) + { + d0 = prev->data + prev->size - curr->sampsize; + if (!left) + d0 += mixer_chansize; + } + else + d0 = d1; + } + else + d0 = d1 - curr->sampsize; + + if (d1 + curr->sampsize >= curr->data + curr->size) + { + if (next && next->data && next->size >= curr->sampsize * 2) + { + d2 = next->data; + if (!left) + d2 += mixer_chansize; + d3 = d2 + curr->sampsize; + } + else + d2 = d3 = d1; + } + else + { + d2 = d1 + curr->sampsize; + if (d2 + curr->sampsize >= curr->data + curr->size) + { + if (next && next->data && next->size >= curr->sampsize) + { + d3 = next->data; + if (!left) + d3 += mixer_chansize; + } + else + d3 = d2; + } + else + d3 = d2 + curr->sampsize; + } + + s0 = mixer_GetSampleInt (d0, mixer_chansize); + s1 = mixer_GetSampleInt (d1, mixer_chansize); + s2 = mixer_GetSampleInt (d2, mixer_chansize); + s3 = mixer_GetSampleInt (d3, mixer_chansize); + + a = (3.0f * (s1 - s2) - s0 + s3) * 0.5f; + b = 2.0f * s2 + s0 - ((5.0f * s1 + s3) * 0.5f); + c = (s2 - s0) * 0.5f; + + return a * t2 * t + b * t2 + c * t + s1; +} + +/* get next sample from external buffer + * in internal format, while performing + * convertion if necessary + */ +static inline sint32 +mixer_GetConvSample (uint8 **psrc, uint32 bpc, uint32 flags) +{ + sint32 samp; + + samp = mixer_GetSampleExt (*psrc, bpc); + *psrc += bpc; + if (flags & mixConvStereoDown) + { + /* downmix to mono - average up channels */ + samp = (samp + mixer_GetSampleExt (*psrc, bpc)) / 2; + *psrc += bpc; + } + + if (flags & mixConvSizeUp) + { + /* convert S8 to S16 */ + samp <<= 8; + } + else if (flags & mixConvSizeDown) + { + /* convert S16 to S8 + * if arithmetic shift is available to the compiler + * it will use it to optimize this + */ + samp /= 0x100; + } + + return samp; +} + +/* put next sample into an internal buffer + * in internal format, while performing + * convertion if necessary + */ +static inline void +mixer_PutConvSample (uint8 **pdst, uint32 bpc, uint32 flags, sint32 samp) +{ + mixer_PutSampleInt (*pdst, bpc, samp); + *pdst += bpc; + if (flags & mixConvStereoUp) + { + mixer_PutSampleInt (*pdst, bpc, samp); + *pdst += bpc; + } +} + +/* resampling with respect to sample size only */ +static void +mixer_ResampleFlat (mixer_Convertion *conv) +{ + mixer_ConvFlags flags = conv->flags; + uint8 *src = conv->srcdata; + uint8 *dst = conv->dstdata; + uint32 srcbpc = conv->srcbpc; + uint32 dstbpc = conv->dstbpc; + uint32 samples; + + samples = conv->srcsamples; + if ( !(conv->flags & (mixConvStereoUp | mixConvStereoDown))) + samples *= conv->srcchans; + + for (; samples; samples--) + { + sint32 samp; + + samp = mixer_GetConvSample (&src, srcbpc, flags); + mixer_PutConvSample (&dst, dstbpc, flags, samp); + } +} diff --git a/src/libs/sound/mixer/mixer.h b/src/libs/sound/mixer/mixer.h new file mode 100644 index 0000000..71e7878 --- /dev/null +++ b/src/libs/sound/mixer/mixer.h @@ -0,0 +1,274 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Mixer for low-level sound output drivers + */ + +#ifndef LIBS_SOUND_MIXER_MIXER_H_ +#define LIBS_SOUND_MIXER_MIXER_H_ + +#include "config.h" +#include "types.h" +#include "endian_uqm.h" + +/** + * The interface heavily influenced by OpenAL + * to the point where you should use OpenAL's + * documentation when programming the mixer. + * (some source properties are not supported) + * + * EXCEPTION: You may not queue the same buffer + * on more than one source + */ + +#ifdef WORDS_BIGENDIAN +# define MIX_IS_BIG_ENDIAN true +# define MIX_WANT_BIG_ENDIAN true +#else +# define MIX_IS_BIG_ENDIAN false +# define MIX_WANT_BIG_ENDIAN false +#endif + +/** + * Mixer errors (see OpenAL errors) + */ +enum +{ + MIX_NO_ERROR = 0, + MIX_INVALID_NAME = 0xA001U, + MIX_INVALID_ENUM = 0xA002U, + MIX_INVALID_VALUE = 0xA003U, + MIX_INVALID_OPERATION = 0xA004U, + MIX_OUT_OF_MEMORY = 0xA005U, + + MIX_DRIVER_FAILURE = 0xA101U +}; + +/** + * Source properties (see OpenAL) + */ +typedef enum +{ + MIX_POSITION = 0x1004, + MIX_LOOPING = 0x1007, + MIX_BUFFER = 0x1009, + MIX_GAIN = 0x100A, + MIX_SOURCE_STATE = 0x1010, + + MIX_BUFFERS_QUEUED = 0x1015, + MIX_BUFFERS_PROCESSED = 0x1016 + +} mixer_SourceProp; + +/** + * Source state information + */ +typedef enum +{ + MIX_INITIAL = 0, + MIX_STOPPED, + MIX_PLAYING, + MIX_PAUSED, + +} mixer_SourceState; + +/** + * Sound buffer properties + */ +typedef enum +{ + MIX_FREQUENCY = 0x2001, + MIX_BITS = 0x2002, + MIX_CHANNELS = 0x2003, + MIX_SIZE = 0x2004, + MIX_DATA = 0x2005 + +} mixer_BufferProp; + +/** + * Buffer states: semi-private + */ +typedef enum +{ + MIX_BUF_INITIAL = 0, + MIX_BUF_FILLED, + MIX_BUF_QUEUED, + MIX_BUF_PLAYING, + MIX_BUF_PROCESSED + +} mixer_BufferState; + +/** Sound buffers: format specifier. + * bits 00..07: bytes per sample + * bits 08..15: channels + * bits 15..31: meaningless + */ +#define MIX_FORMAT_DUMMYID 0x00170000 +#define MIX_FORMAT_BPC(f) ((f) & 0xff) +#define MIX_FORMAT_CHANS(f) (((f) >> 8) & 0xff) +#define MIX_FORMAT_BPC_MAX 2 +#define MIX_FORMAT_CHANS_MAX 2 +#define MIX_FORMAT_MAKE(b, c) \ + ( MIX_FORMAT_DUMMYID | ((b) & 0xff) | (((c) & 0xff) << 8) ) + +#define MIX_FORMAT_SAMPSIZE(f) \ + ( MIX_FORMAT_BPC(f) * MIX_FORMAT_CHANS(f) ) + +typedef enum +{ + MIX_FORMAT_MONO8 = MIX_FORMAT_MAKE (1, 1), + MIX_FORMAT_STEREO8 = MIX_FORMAT_MAKE (1, 2), + MIX_FORMAT_MONO16 = MIX_FORMAT_MAKE (2, 1), + MIX_FORMAT_STEREO16 = MIX_FORMAT_MAKE (2, 2) + +} mixer_Format; + +typedef enum +{ + MIX_QUALITY_LOW = 0, + MIX_QUALITY_MEDIUM, + MIX_QUALITY_HIGH, + MIX_QUALITY_DEFAULT = MIX_QUALITY_MEDIUM, + MIX_QUALITY_COUNT + +} mixer_Quality; + +typedef enum +{ + MIX_NOFLAGS = 0, + MIX_FAKE_DATA = 1 +} mixer_Flags; + +/************************************************* + * Interface Types + */ + +typedef intptr_t mixer_Object; +typedef intptr_t mixer_IntVal; + +typedef struct _mixer_Source mixer_Source; + +typedef struct _mixer_Buffer +{ + uint32 magic; + bool locked; + mixer_BufferState state; + uint8 *data; + uint32 size; + uint32 sampsize; + uint32 high; + uint32 low; + float (* Resample) (mixer_Source *src, bool left); + /* original buffer values for OpenAL compat */ + void* orgdata; + uint32 orgfreq; + uint32 orgsize; + uint32 orgchannels; + uint32 orgchansize; + /* next buffer in chain */ + struct _mixer_Buffer *next; + +} mixer_Buffer; + +#define mixer_bufMagic 0x4258494DU /* MIXB in LSB */ + +struct _mixer_Source +{ + uint32 magic; + bool locked; + mixer_SourceState state; + bool looping; + float gain; + uint32 cqueued; + uint32 cprocessed; + mixer_Buffer *firstqueued; /* first buf in the queue */ + mixer_Buffer *nextqueued; /* next to play, or 0 */ + mixer_Buffer *prevqueued; /* previously played */ + mixer_Buffer *lastqueued; /* last in queue */ + uint32 pos; /* position in current buffer */ + uint32 count; /* fractional part of pos */ + + float samplecache; + +}; + +#define mixer_srcMagic 0x5358494DU /* MIXS in LSB */ + +/************************************************* + * General interface + */ +uint32 mixer_GetError (void); + +bool mixer_Init (uint32 frequency, uint32 format, mixer_Quality quality, + mixer_Flags flags); +void mixer_Uninit (void); +void mixer_MixChannels (void *userdata, uint8 *stream, sint32 len); +void mixer_MixFake (void *userdata, uint8 *stream, sint32 len); + +/************************************************* + * Sources + */ +void mixer_GenSources (uint32 n, mixer_Object *psrcobj); +void mixer_DeleteSources (uint32 n, mixer_Object *psrcobj); +bool mixer_IsSource (mixer_Object srcobj); +void mixer_Sourcei (mixer_Object srcobj, mixer_SourceProp pname, + mixer_IntVal value); +void mixer_Sourcef (mixer_Object srcobj, mixer_SourceProp pname, + float value); +void mixer_Sourcefv (mixer_Object srcobj, mixer_SourceProp pname, + float *value); +void mixer_GetSourcei (mixer_Object srcobj, mixer_SourceProp pname, + mixer_IntVal *value); +void mixer_GetSourcef (mixer_Object srcobj, mixer_SourceProp pname, + float *value); +void mixer_SourceRewind (mixer_Object srcobj); +void mixer_SourcePlay (mixer_Object srcobj); +void mixer_SourcePause (mixer_Object srcobj); +void mixer_SourceStop (mixer_Object srcobj); +void mixer_SourceQueueBuffers (mixer_Object srcobj, uint32 n, + mixer_Object* pbufobj); +void mixer_SourceUnqueueBuffers (mixer_Object srcobj, uint32 n, + mixer_Object* pbufobj); + +/************************************************* + * Buffers + */ +void mixer_GenBuffers (uint32 n, mixer_Object *pbufobj); +void mixer_DeleteBuffers (uint32 n, mixer_Object *pbufobj); +bool mixer_IsBuffer (mixer_Object bufobj); +void mixer_GetBufferi (mixer_Object bufobj, mixer_BufferProp pname, + mixer_IntVal *value); +void mixer_BufferData (mixer_Object bufobj, uint32 format, void* data, + uint32 size, uint32 freq); + + +/* Make sure the prop-value type is of suitable size + * it must be able to store both int and void* + * Adapted from SDL + * This will generate "negative subscript or subscript is too large" + * error during compile, if the actual size of a type is wrong + */ +#define MIX_COMPILE_TIME_ASSERT(name, x) \ + typedef int mixer_dummy_##name [(x) * 2 - 1] + +MIX_COMPILE_TIME_ASSERT (mixer_Object, + sizeof(mixer_Object) >= sizeof(void*)); +MIX_COMPILE_TIME_ASSERT (mixer_IntVal, + sizeof(mixer_IntVal) >= sizeof(mixer_Object)); + +#undef MIX_COMPILE_TIME_ASSERT + +#endif /* LIBS_SOUND_MIXER_MIXER_H_ */ diff --git a/src/libs/sound/mixer/mixerint.h b/src/libs/sound/mixer/mixerint.h new file mode 100644 index 0000000..0605161 --- /dev/null +++ b/src/libs/sound/mixer/mixerint.h @@ -0,0 +1,110 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Mixer for low-level sound output drivers + * Internals + */ + +#ifndef LIBS_SOUND_MIXER_MIXERINT_H_ +#define LIBS_SOUND_MIXER_MIXERINT_H_ + +#include "port.h" +#include "types.h" + +/************************************************* + * Internals + */ + +/* Conversion info types and funcs */ +typedef enum +{ + mixConvNone = 0, + mixConvStereoUp = 1, + mixConvStereoDown = 2, + mixConvSizeUp = 4, + mixConvSizeDown = 8 + +} mixer_ConvFlags; + +typedef struct +{ + uint32 srcfmt; + void *srcdata; + uint32 srcsize; + uint32 srcbpc; /* bytes/sample for 1 chan */ + uint32 srcchans; + uint32 srcsamples; + + uint32 dstfmt; + void *dstdata; + uint32 dstsize; + uint32 dstbpc; /* bytes/sample for 1 chan */ + uint32 dstchans; + uint32 dstsamples; + + mixer_ConvFlags flags; + +} mixer_Convertion; + +typedef struct +{ + float (* Upsample) (mixer_Source *src, bool left); + float (* Downsample) (mixer_Source *src, bool left); + float (* None) (mixer_Source *src, bool left); +} mixer_Resampling; + +static void mixer_ConvertBuffer_internal (mixer_Convertion *conv); +static void mixer_ResampleFlat (mixer_Convertion *conv); + +static inline sint32 mixer_GetSampleExt (void *src, uint32 bpc); +static inline sint32 mixer_GetSampleInt (void *src, uint32 bpc); +static inline void mixer_PutSampleInt (void *dst, uint32 bpc, + sint32 samp); +static inline void mixer_PutSampleExt (void *dst, uint32 bpc, + sint32 samp); + +static float mixer_ResampleNone (mixer_Source *src, bool left); +static float mixer_ResampleNearest (mixer_Source *src, bool left); +static float mixer_UpsampleLinear (mixer_Source *src, bool left); +static float mixer_UpsampleCubic (mixer_Source *src, bool left); + +/* Source manipulation */ +static void mixer_SourceUnqueueAll (mixer_Source *src); +static void mixer_SourceStop_internal (mixer_Source *src); +static void mixer_SourceRewind_internal (mixer_Source *src); +static void mixer_SourceActivate (mixer_Source* src); +static void mixer_SourceDeactivate (mixer_Source* src); + +static inline bool mixer_CheckBufferState (mixer_Buffer *buf, + const char* FuncName); + +/* Clipping boundaries */ +#define MIX_S16_MAX ((float) SINT16_MAX) +#define MIX_S16_MIN ((float) SINT16_MIN) +#define MIX_S8_MAX ((float) SINT8_MAX) +#define MIX_S8_MIN ((float) SINT8_MIN) + +/* Channel gain adjustment for clipping reduction */ +#define MIX_GAIN_ADJ (0.75f) + +/* The Mixer */ +static inline bool mixer_SourceGetNextSample (mixer_Source *src, + float *psamp, bool left); +static inline bool mixer_SourceGetFakeSample (mixer_Source *src, + float *psamp, bool left); +static inline uint32 mixer_SourceAdvance (mixer_Source *src, bool left); + +#endif /* LIBS_SOUND_MIXER_MIXERINT_H_ */ diff --git a/src/libs/sound/mixer/nosound/Makeinfo b/src/libs/sound/mixer/nosound/Makeinfo new file mode 100644 index 0000000..17f0c34 --- /dev/null +++ b/src/libs/sound/mixer/nosound/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="audiodrv_nosound.c" +uqm_HFILES="audiodrv_nosound.h" diff --git a/src/libs/sound/mixer/nosound/audiodrv_nosound.c b/src/libs/sound/mixer/nosound/audiodrv_nosound.c new file mode 100644 index 0000000..005bb44 --- /dev/null +++ b/src/libs/sound/mixer/nosound/audiodrv_nosound.c @@ -0,0 +1,410 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Nosound audio driver + */ + +#include "audiodrv_nosound.h" +#include "../../sndintrn.h" +#include "libs/tasklib.h" +#include "libs/log.h" +#include "libs/memlib.h" +#include <stdlib.h> + + +static Task PlaybackTask; +static uint32 nosound_freq = 22050; + +static const audio_Driver noSound_Driver = +{ + noSound_Uninit, + noSound_GetError, + audio_DRIVER_NOSOUND, + { + /* Errors */ + MIX_NO_ERROR, + MIX_INVALID_NAME, + MIX_INVALID_ENUM, + MIX_INVALID_VALUE, + MIX_INVALID_OPERATION, + MIX_OUT_OF_MEMORY, + MIX_DRIVER_FAILURE, + + /* Source properties */ + MIX_POSITION, + MIX_LOOPING, + MIX_BUFFER, + MIX_GAIN, + MIX_SOURCE_STATE, + MIX_BUFFERS_QUEUED, + MIX_BUFFERS_PROCESSED, + + /* Source state information */ + MIX_INITIAL, + MIX_STOPPED, + MIX_PLAYING, + MIX_PAUSED, + + /* Sound buffer properties */ + MIX_FREQUENCY, + MIX_BITS, + MIX_CHANNELS, + MIX_SIZE, + MIX_FORMAT_MONO16, + MIX_FORMAT_STEREO16, + MIX_FORMAT_MONO8, + MIX_FORMAT_STEREO8 + }, + + /* Sources */ + noSound_GenSources, + noSound_DeleteSources, + noSound_IsSource, + noSound_Sourcei, + noSound_Sourcef, + noSound_Sourcefv, + noSound_GetSourcei, + noSound_GetSourcef, + noSound_SourceRewind, + noSound_SourcePlay, + noSound_SourcePause, + noSound_SourceStop, + noSound_SourceQueueBuffers, + noSound_SourceUnqueueBuffers, + + /* Buffers */ + noSound_GenBuffers, + noSound_DeleteBuffers, + noSound_IsBuffer, + noSound_GetBufferi, + noSound_BufferData +}; + + +/* + * Initialization + */ + +sint32 +noSound_Init (audio_Driver *driver, sint32 flags) +{ + int i; + TFB_DecoderFormats formats = + { + 0, 0, + audio_FORMAT_MONO8, audio_FORMAT_STEREO8, + audio_FORMAT_MONO16, audio_FORMAT_STEREO16 + }; + + log_add (log_Info, "Using nosound audio driver."); + log_add (log_Info, "Initializing mixer."); + + if (!mixer_Init (nosound_freq, MIX_FORMAT_MAKE (1, 1), + MIX_QUALITY_LOW, MIX_FAKE_DATA)) + { + log_add (log_Error, "Mixer initialization failed: %x", + mixer_GetError ()); + return -1; + } + log_add (log_Info, "Mixer initialized."); + + log_add (log_Info, "Initializing sound decoders."); + if (SoundDecoder_Init (flags, &formats)) + { + log_add (log_Error, "Sound decoders initialization failed."); + mixer_Uninit (); + return -1; + } + log_add (log_Info, "Sound decoders initialized."); + + *driver = noSound_Driver; + for (i = 0; i < NUM_SOUNDSOURCES; ++i) + { + audio_GenSources (1, &soundSource[i].handle); + soundSource[i].stream_mutex = CreateMutex ("Nosound stream mutex", SYNC_CLASS_AUDIO); + } + + if (InitStreamDecoder ()) + { + log_add (log_Error, "Stream decoder initialization failed."); + // TODO: cleanup source mutexes [or is it "muti"? :) ] + SoundDecoder_Uninit (); + mixer_Uninit (); + return -1; + } + + PlaybackTask = AssignTask (PlaybackTaskFunc, 1024, + "nosound audio playback"); + + return 0; +} + +void +noSound_Uninit (void) +{ + int i; + + UninitStreamDecoder (); + + for (i = 0; i < NUM_SOUNDSOURCES; ++i) + { + if (soundSource[i].sample && soundSource[i].sample->decoder) + { + StopStream (i); + } + if (soundSource[i].sbuffer) + { + void *sbuffer = soundSource[i].sbuffer; + soundSource[i].sbuffer = NULL; + HFree (sbuffer); + } + DestroyMutex (soundSource[i].stream_mutex); + + noSound_DeleteSources (1, &soundSource[i].handle); + } + + if (PlaybackTask) + { + ConcludeTask (PlaybackTask); + PlaybackTask = 0; + } + + mixer_Uninit (); + SoundDecoder_Uninit (); +} + + +/* + * Playback task + */ + +int +PlaybackTaskFunc (void *data) +{ + Task task = (Task)data; + uint8 *stream; + uint32 entryTime; + sint32 period, delay; + uint32 len = 2048; + + stream = (uint8 *) HMalloc (len); + period = (sint32)((len / (double)nosound_freq) * ONE_SECOND); + + while (!Task_ReadState (task, TASK_EXIT)) + { + entryTime = GetTimeCounter (); + mixer_MixFake (NULL, stream, len); + delay = period - (GetTimeCounter () - entryTime); + if (delay > 0) + HibernateThread (delay); + } + + HFree (stream); + FinishTask (task); + return 0; +} + + +/* + * General + */ + +sint32 +noSound_GetError (void) +{ + sint32 value = mixer_GetError (); + switch (value) + { + case MIX_NO_ERROR: + return audio_NO_ERROR; + case MIX_INVALID_NAME: + return audio_INVALID_NAME; + case MIX_INVALID_ENUM: + return audio_INVALID_ENUM; + case MIX_INVALID_VALUE: + return audio_INVALID_VALUE; + case MIX_INVALID_OPERATION: + return audio_INVALID_OPERATION; + case MIX_OUT_OF_MEMORY: + return audio_OUT_OF_MEMORY; + default: + log_add (log_Debug, "noSound_GetError: unknown value %x", + value); + return audio_DRIVER_FAILURE; + break; + } +} + + +/* + * Sources + */ + +void +noSound_GenSources (uint32 n, audio_Object *psrcobj) +{ + mixer_GenSources (n, (mixer_Object *) psrcobj); +} + +void +noSound_DeleteSources (uint32 n, audio_Object *psrcobj) +{ + mixer_DeleteSources (n, (mixer_Object *) psrcobj); +} + +bool +noSound_IsSource (audio_Object srcobj) +{ + return mixer_IsSource ((mixer_Object) srcobj); +} + +void +noSound_Sourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal value) + +{ + mixer_Sourcei ((mixer_Object) srcobj, (mixer_SourceProp) pname, + (mixer_IntVal) value); +} + +void +noSound_Sourcef (audio_Object srcobj, audio_SourceProp pname, + float value) +{ + mixer_Sourcef ((mixer_Object) srcobj, (mixer_SourceProp) pname, value); +} + +void +noSound_Sourcefv (audio_Object srcobj, audio_SourceProp pname, + float *value) +{ + mixer_Sourcefv ((mixer_Object) srcobj, (mixer_SourceProp) pname, value); +} + +void +noSound_GetSourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal *value) +{ + mixer_GetSourcei ((mixer_Object) srcobj, (mixer_SourceProp) pname, + (mixer_IntVal *) value); + if (pname == MIX_SOURCE_STATE) + { + switch (*value) + { + case MIX_INITIAL: + *value = audio_INITIAL; + break; + case MIX_STOPPED: + *value = audio_STOPPED; + break; + case MIX_PLAYING: + *value = audio_PLAYING; + break; + case MIX_PAUSED: + *value = audio_PAUSED; + break; + default: + log_add (log_Debug, "noSound_GetSourcei(): unknown value %lx", + (long int) *value); + *value = audio_DRIVER_FAILURE; + } + } +} + +void +noSound_GetSourcef (audio_Object srcobj, audio_SourceProp pname, + float *value) +{ + mixer_GetSourcef ((mixer_Object) srcobj, (mixer_SourceProp) pname, value); +} + +void +noSound_SourceRewind (audio_Object srcobj) +{ + mixer_SourceRewind ((mixer_Object) srcobj); +} + +void +noSound_SourcePlay (audio_Object srcobj) +{ + mixer_SourcePlay ((mixer_Object) srcobj); +} + +void +noSound_SourcePause (audio_Object srcobj) +{ + mixer_SourcePause ((mixer_Object) srcobj); +} + +void +noSound_SourceStop (audio_Object srcobj) +{ + mixer_SourceStop ((mixer_Object) srcobj); +} + +void +noSound_SourceQueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj) +{ + mixer_SourceQueueBuffers ((mixer_Object) srcobj, n, + (mixer_Object *) pbufobj); +} + +void +noSound_SourceUnqueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj) +{ + mixer_SourceUnqueueBuffers ((mixer_Object) srcobj, n, + (mixer_Object *) pbufobj); +} + + +/* + * Buffers + */ + +void +noSound_GenBuffers (uint32 n, audio_Object *pbufobj) +{ + mixer_GenBuffers (n, (mixer_Object *) pbufobj); +} + +void +noSound_DeleteBuffers (uint32 n, audio_Object *pbufobj) +{ + mixer_DeleteBuffers (n, (mixer_Object *) pbufobj); +} + +bool +noSound_IsBuffer (audio_Object bufobj) +{ + return mixer_IsBuffer ((mixer_Object) bufobj); +} + +void +noSound_GetBufferi (audio_Object bufobj, audio_BufferProp pname, + audio_IntVal *value) +{ + mixer_GetBufferi ((mixer_Object) bufobj, (mixer_BufferProp) pname, + (mixer_IntVal *) value); +} + +void +noSound_BufferData (audio_Object bufobj, uint32 format, void* data, + uint32 size, uint32 freq) +{ + mixer_BufferData ((mixer_Object) bufobj, format, data, size, freq); +} diff --git a/src/libs/sound/mixer/nosound/audiodrv_nosound.h b/src/libs/sound/mixer/nosound/audiodrv_nosound.h new file mode 100644 index 0000000..173b706 --- /dev/null +++ b/src/libs/sound/mixer/nosound/audiodrv_nosound.h @@ -0,0 +1,69 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Nosound audio driver + */ + +#ifndef LIBS_SOUND_MIXER_NOSOUND_AUDIODRV_NOSOUND_H_ +#define LIBS_SOUND_MIXER_NOSOUND_AUDIODRV_NOSOUND_H_ + +#include "config.h" +#include "libs/sound/sound.h" +#include "libs/sound/mixer/mixer.h" + + +/* Playback task */ +int PlaybackTaskFunc (void *data); + +/* General */ +sint32 noSound_Init (audio_Driver *driver, sint32 flags); +void noSound_Uninit (void); +sint32 noSound_GetError (void); + +/* Sources */ +void noSound_GenSources (uint32 n, audio_Object *psrcobj); +void noSound_DeleteSources (uint32 n, audio_Object *psrcobj); +bool noSound_IsSource (audio_Object srcobj); +void noSound_Sourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal value); +void noSound_Sourcef (audio_Object srcobj, audio_SourceProp pname, + float value); +void noSound_Sourcefv (audio_Object srcobj, audio_SourceProp pname, + float *value); +void noSound_GetSourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal *value); +void noSound_GetSourcef (audio_Object srcobj, audio_SourceProp pname, + float *value); +void noSound_SourceRewind (audio_Object srcobj); +void noSound_SourcePlay (audio_Object srcobj); +void noSound_SourcePause (audio_Object srcobj); +void noSound_SourceStop (audio_Object srcobj); +void noSound_SourceQueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj); +void noSound_SourceUnqueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj); + +/* Buffers */ +void noSound_GenBuffers (uint32 n, audio_Object *pbufobj); +void noSound_DeleteBuffers (uint32 n, audio_Object *pbufobj); +bool noSound_IsBuffer (audio_Object bufobj); +void noSound_GetBufferi (audio_Object bufobj, audio_BufferProp pname, + audio_IntVal *value); +void noSound_BufferData (audio_Object bufobj, uint32 format, void* data, + uint32 size, uint32 freq); + + +#endif /* LIBS_SOUND_MIXER_NOSOUND_AUDIODRV_NOSOUND_H_ */ diff --git a/src/libs/sound/mixer/sdl/Makeinfo b/src/libs/sound/mixer/sdl/Makeinfo new file mode 100644 index 0000000..64c22c0 --- /dev/null +++ b/src/libs/sound/mixer/sdl/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="audiodrv_sdl.c" +uqm_HFILES="audiodrv_sdl.h" diff --git a/src/libs/sound/mixer/sdl/audiodrv_sdl.c b/src/libs/sound/mixer/sdl/audiodrv_sdl.c new file mode 100644 index 0000000..7ef522e --- /dev/null +++ b/src/libs/sound/mixer/sdl/audiodrv_sdl.c @@ -0,0 +1,486 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* SDL audio driver + */ + +#include "audiodrv_sdl.h" +#include "../../sndintrn.h" +#include "libs/log.h" +#include "libs/memlib.h" +#include <stdlib.h> + + +/* SDL2 wants to talk to a specific device. We'll let SDL1 use the same + * function names and just throw the device argument away. */ +#if SDL_MAJOR_VERSION > 1 +static SDL_AudioDeviceID dev; +#else +#define SDL_CloseAudioDevice(x) SDL_CloseAudio () +#define SDL_PauseAudioDevice(x, y) SDL_PauseAudio (y) +#endif +static const audio_Driver mixSDL_Driver = +{ + mixSDL_Uninit, + mixSDL_GetError, + audio_DRIVER_MIXSDL, + { + /* Errors */ + MIX_NO_ERROR, + MIX_INVALID_NAME, + MIX_INVALID_ENUM, + MIX_INVALID_VALUE, + MIX_INVALID_OPERATION, + MIX_OUT_OF_MEMORY, + MIX_DRIVER_FAILURE, + + /* Source properties */ + MIX_POSITION, + MIX_LOOPING, + MIX_BUFFER, + MIX_GAIN, + MIX_SOURCE_STATE, + MIX_BUFFERS_QUEUED, + MIX_BUFFERS_PROCESSED, + + /* Source state information */ + MIX_INITIAL, + MIX_STOPPED, + MIX_PLAYING, + MIX_PAUSED, + + /* Sound buffer properties */ + MIX_FREQUENCY, + MIX_BITS, + MIX_CHANNELS, + MIX_SIZE, + MIX_FORMAT_MONO16, + MIX_FORMAT_STEREO16, + MIX_FORMAT_MONO8, + MIX_FORMAT_STEREO8 + }, + + /* Sources */ + mixSDL_GenSources, + mixSDL_DeleteSources, + mixSDL_IsSource, + mixSDL_Sourcei, + mixSDL_Sourcef, + mixSDL_Sourcefv, + mixSDL_GetSourcei, + mixSDL_GetSourcef, + mixSDL_SourceRewind, + mixSDL_SourcePlay, + mixSDL_SourcePause, + mixSDL_SourceStop, + mixSDL_SourceQueueBuffers, + mixSDL_SourceUnqueueBuffers, + + /* Buffers */ + mixSDL_GenBuffers, + mixSDL_DeleteBuffers, + mixSDL_IsBuffer, + mixSDL_GetBufferi, + mixSDL_BufferData +}; + + +static void audioCallback (void *userdata, Uint8 *stream, int len); + +/* + * Initialization + */ + +sint32 +mixSDL_Init (audio_Driver *driver, sint32 flags) +{ + int i; + SDL_AudioSpec desired, obtained; + mixer_Quality quality; + TFB_DecoderFormats formats = + { + MIX_IS_BIG_ENDIAN, MIX_WANT_BIG_ENDIAN, + audio_FORMAT_MONO8, audio_FORMAT_STEREO8, + audio_FORMAT_MONO16, audio_FORMAT_STEREO16 + }; + + log_add (log_Info, "Initializing SDL audio subsystem."); + if ((SDL_InitSubSystem(SDL_INIT_AUDIO)) == -1) + { + log_add (log_Error, "Couldn't initialize audio subsystem: %s", + SDL_GetError()); + return -1; + } + log_add (log_Info, "SDL audio subsystem initialized."); + + if (flags & audio_QUALITY_HIGH) + { + quality = MIX_QUALITY_HIGH; + desired.freq = 44100; + desired.samples = 4096; + } + else if (flags & audio_QUALITY_LOW) + { + quality = MIX_QUALITY_LOW; +#ifdef __SYMBIAN32__ + desired.freq = 11025; + desired.samples = 4096; +#else + desired.freq = 22050; + desired.samples = 2048; +#endif + } + else + { + quality = MIX_QUALITY_DEFAULT; + desired.freq = 44100; + desired.samples = 4096; + } + + desired.format = AUDIO_S16SYS; + desired.channels = 2; + desired.callback = audioCallback; + + log_add (log_Info, "Opening SDL audio device."); +#if SDL_MAJOR_VERSION > 1 + dev = SDL_OpenAudioDevice (NULL, 0, &desired, &obtained, + SDL_AUDIO_ALLOW_FREQUENCY_CHANGE | SDL_AUDIO_ALLOW_CHANNELS_CHANGE +#ifdef SDL_AUDIO_ALLOW_SAMPLES_CHANGE + | SDL_AUDIO_ALLOW_SAMPLES_CHANGE +#endif + ); + if (dev != 0 && obtained.channels != 1 && obtained.channels != 2) + { + /* Try again without SDL_AUDIO_ALLOW_CHANNELS_CHANGE + * in case the device only supports >2 channels for some + * reason */ + SDL_CloseAudioDevice (dev); + dev = SDL_OpenAudioDevice (NULL, 0, &desired, &obtained, + SDL_AUDIO_ALLOW_FREQUENCY_CHANGE +#ifdef SDL_AUDIO_ALLOW_SAMPLES_CHANGE + | SDL_AUDIO_ALLOW_SAMPLES_CHANGE +#endif + ); + } + if (dev == 0) +#else + if (SDL_OpenAudio (&desired, &obtained) < 0) +#endif + { + log_add (log_Error, "Unable to open audio device: %s", + SDL_GetError ()); + SDL_QuitSubSystem (SDL_INIT_AUDIO); + return -1; + } + if (obtained.format != desired.format || + (obtained.channels != 1 && obtained.channels != 2)) + { + log_add (log_Error, "Unable to obtain desired audio format."); + SDL_CloseAudio (); + SDL_QuitSubSystem (SDL_INIT_AUDIO); + return -1; + } + + { +#if SDL_MAJOR_VERSION == 1 + char devicename[256]; + SDL_AudioDriverName (devicename, sizeof (devicename)); +#else + const char *devicename = SDL_GetCurrentAudioDriver (); +#endif + log_add (log_Info, " using %s at %d Hz 16 bit %s, " + "%d samples audio buffer", + devicename, obtained.freq, + obtained.channels > 1 ? "stereo" : "mono", + obtained.samples); + } + + log_add (log_Info, "Initializing mixer."); + if (!mixer_Init (obtained.freq, MIX_FORMAT_MAKE (2, obtained.channels), + quality, 0)) + { + log_add (log_Error, "Mixer initialization failed: %x", + mixer_GetError ()); + SDL_CloseAudioDevice (dev); + SDL_QuitSubSystem (SDL_INIT_AUDIO); + return -1; + } + log_add (log_Info, "Mixer initialized."); + + log_add (log_Info, "Initializing sound decoders."); + if (SoundDecoder_Init (flags, &formats)) + { + log_add (log_Error, "Sound decoders initialization failed."); + SDL_CloseAudioDevice (dev); + mixer_Uninit (); + SDL_QuitSubSystem (SDL_INIT_AUDIO); + return -1; + } + log_add (log_Info, "Sound decoders initialized."); + + *driver = mixSDL_Driver; + for (i = 0; i < NUM_SOUNDSOURCES; ++i) + { + audio_GenSources (1, &soundSource[i].handle); + soundSource[i].stream_mutex = CreateMutex ("MixSDL stream mutex", SYNC_CLASS_AUDIO); + } + + if (InitStreamDecoder ()) + { + log_add (log_Error, "Stream decoder initialization failed."); + // TODO: cleanup source mutexes [or is it "muti"? :) ] + SDL_CloseAudioDevice (dev); + SoundDecoder_Uninit (); + mixer_Uninit (); + SDL_QuitSubSystem (SDL_INIT_AUDIO); + return -1; + } + + atexit (unInitAudio); + + SetSFXVolume (sfxVolumeScale); + SetSpeechVolume (speechVolumeScale); + SetMusicVolume ((COUNT)musicVolume); + + SDL_PauseAudioDevice (dev, 0); + + return 0; +} + +void +mixSDL_Uninit (void) +{ + int i; + + UninitStreamDecoder (); + + for (i = 0; i < NUM_SOUNDSOURCES; ++i) + { + if (soundSource[i].sample && soundSource[i].sample->decoder) + { + StopStream (i); + } + if (soundSource[i].sbuffer) + { + void *sbuffer = soundSource[i].sbuffer; + soundSource[i].sbuffer = NULL; + HFree (sbuffer); + } + DestroyMutex (soundSource[i].stream_mutex); + soundSource[i].stream_mutex = 0; + + mixSDL_DeleteSources (1, &soundSource[i].handle); + } + + SDL_CloseAudioDevice (dev); + mixer_Uninit (); + SoundDecoder_Uninit (); + SDL_QuitSubSystem (SDL_INIT_AUDIO); +} + +static void +audioCallback (void *userdata, Uint8 *stream, int len) +{ + mixer_MixChannels (userdata, stream, len); +} + +/* + * General + */ + +sint32 +mixSDL_GetError (void) +{ + sint32 value = mixer_GetError (); + switch (value) + { + case MIX_NO_ERROR: + return audio_NO_ERROR; + case MIX_INVALID_NAME: + return audio_INVALID_NAME; + case MIX_INVALID_ENUM: + return audio_INVALID_ENUM; + case MIX_INVALID_VALUE: + return audio_INVALID_VALUE; + case MIX_INVALID_OPERATION: + return audio_INVALID_OPERATION; + case MIX_OUT_OF_MEMORY: + return audio_OUT_OF_MEMORY; + default: + log_add (log_Debug, "mixSDL_GetError: unknown value %x", value); + return audio_DRIVER_FAILURE; + break; + } +} + + +/* + * Sources + */ + +void +mixSDL_GenSources (uint32 n, audio_Object *psrcobj) +{ + mixer_GenSources (n, (mixer_Object *) psrcobj); +} + +void +mixSDL_DeleteSources (uint32 n, audio_Object *psrcobj) +{ + mixer_DeleteSources (n, (mixer_Object *) psrcobj); +} + +bool +mixSDL_IsSource (audio_Object srcobj) +{ + return mixer_IsSource ((mixer_Object) srcobj); +} + +void +mixSDL_Sourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal value) + +{ + mixer_Sourcei ((mixer_Object) srcobj, (mixer_SourceProp) pname, + (mixer_IntVal) value); +} + +void +mixSDL_Sourcef (audio_Object srcobj, audio_SourceProp pname, + float value) +{ + mixer_Sourcef ((mixer_Object) srcobj, (mixer_SourceProp) pname, value); +} + +void +mixSDL_Sourcefv (audio_Object srcobj, audio_SourceProp pname, + float *value) +{ + mixer_Sourcefv ((mixer_Object) srcobj, (mixer_SourceProp) pname, value); +} + +void +mixSDL_GetSourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal *value) +{ + mixer_GetSourcei ((mixer_Object) srcobj, (mixer_SourceProp) pname, + (mixer_IntVal *) value); + if (pname == MIX_SOURCE_STATE) + { + switch (*value) + { + case MIX_INITIAL: + *value = audio_INITIAL; + break; + case MIX_STOPPED: + *value = audio_STOPPED; + break; + case MIX_PLAYING: + *value = audio_PLAYING; + break; + case MIX_PAUSED: + *value = audio_PAUSED; + break; + default: + *value = audio_DRIVER_FAILURE; + } + } +} + +void +mixSDL_GetSourcef (audio_Object srcobj, audio_SourceProp pname, + float *value) +{ + mixer_GetSourcef ((mixer_Object) srcobj, (mixer_SourceProp) pname, value); +} + +void +mixSDL_SourceRewind (audio_Object srcobj) +{ + mixer_SourceRewind ((mixer_Object) srcobj); +} + +void +mixSDL_SourcePlay (audio_Object srcobj) +{ + mixer_SourcePlay ((mixer_Object) srcobj); +} + +void +mixSDL_SourcePause (audio_Object srcobj) +{ + mixer_SourcePause ((mixer_Object) srcobj); +} + +void +mixSDL_SourceStop (audio_Object srcobj) +{ + mixer_SourceStop ((mixer_Object) srcobj); +} + +void +mixSDL_SourceQueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj) +{ + mixer_SourceQueueBuffers ((mixer_Object) srcobj, n, + (mixer_Object *) pbufobj); +} + +void +mixSDL_SourceUnqueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj) +{ + mixer_SourceUnqueueBuffers ((mixer_Object) srcobj, n, + (mixer_Object *) pbufobj); +} + + +/* + * Buffers + */ + +void +mixSDL_GenBuffers (uint32 n, audio_Object *pbufobj) +{ + mixer_GenBuffers (n, (mixer_Object *) pbufobj); +} + +void +mixSDL_DeleteBuffers (uint32 n, audio_Object *pbufobj) +{ + mixer_DeleteBuffers (n, (mixer_Object *) pbufobj); +} + +bool +mixSDL_IsBuffer (audio_Object bufobj) +{ + return mixer_IsBuffer ((mixer_Object) bufobj); +} + +void +mixSDL_GetBufferi (audio_Object bufobj, audio_BufferProp pname, + audio_IntVal *value) +{ + mixer_GetBufferi ((mixer_Object) bufobj, (mixer_BufferProp) pname, + (mixer_IntVal *) value); +} + +void +mixSDL_BufferData (audio_Object bufobj, uint32 format, void* data, + uint32 size, uint32 freq) +{ + mixer_BufferData ((mixer_Object) bufobj, format, data, size, freq); +} diff --git a/src/libs/sound/mixer/sdl/audiodrv_sdl.h b/src/libs/sound/mixer/sdl/audiodrv_sdl.h new file mode 100644 index 0000000..d74301b --- /dev/null +++ b/src/libs/sound/mixer/sdl/audiodrv_sdl.h @@ -0,0 +1,66 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* SDL audio driver + */ + +#ifndef LIBS_SOUND_MIXER_SDL_AUDIODRV_SDL_H_ +#define LIBS_SOUND_MIXER_SDL_AUDIODRV_SDL_H_ + +#include "port.h" +#include "libs/sound/sound.h" +#include "libs/sound/mixer/mixer.h" +#include SDL_INCLUDE(SDL.h) + +/* General */ +sint32 mixSDL_Init (audio_Driver *driver, sint32 flags); +void mixSDL_Uninit (void); +sint32 mixSDL_GetError (void); + +/* Sources */ +void mixSDL_GenSources (uint32 n, audio_Object *psrcobj); +void mixSDL_DeleteSources (uint32 n, audio_Object *psrcobj); +bool mixSDL_IsSource (audio_Object srcobj); +void mixSDL_Sourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal value); +void mixSDL_Sourcef (audio_Object srcobj, audio_SourceProp pname, + float value); +void mixSDL_Sourcefv (audio_Object srcobj, audio_SourceProp pname, + float *value); +void mixSDL_GetSourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal *value); +void mixSDL_GetSourcef (audio_Object srcobj, audio_SourceProp pname, + float *value); +void mixSDL_SourceRewind (audio_Object srcobj); +void mixSDL_SourcePlay (audio_Object srcobj); +void mixSDL_SourcePause (audio_Object srcobj); +void mixSDL_SourceStop (audio_Object srcobj); +void mixSDL_SourceQueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj); +void mixSDL_SourceUnqueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj); + +/* Buffers */ +void mixSDL_GenBuffers (uint32 n, audio_Object *pbufobj); +void mixSDL_DeleteBuffers (uint32 n, audio_Object *pbufobj); +bool mixSDL_IsBuffer (audio_Object bufobj); +void mixSDL_GetBufferi (audio_Object bufobj, audio_BufferProp pname, + audio_IntVal *value); +void mixSDL_BufferData (audio_Object bufobj, uint32 format, void* data, + uint32 size, uint32 freq); + + +#endif /* LIBS_SOUND_MIXER_SDL_AUDIODRV_SDL_H_ */ diff --git a/src/libs/sound/music.c b/src/libs/sound/music.c new file mode 100644 index 0000000..c3f92f0 --- /dev/null +++ b/src/libs/sound/music.c @@ -0,0 +1,233 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <string.h> +#include "libs/file.h" +#include "options.h" +#include "sound.h" +#include "sndintrn.h" +#include "libs/reslib.h" +#include "libs/log.h" +#include "libs/memlib.h" + + +static MUSIC_REF curMusicRef; +static MUSIC_REF curSpeechRef; + +void +PLRPlaySong (MUSIC_REF MusicRef, BOOLEAN Continuous, BYTE Priority) +{ + TFB_SoundSample **pmus = MusicRef; + + if (pmus) + { + LockMutex (soundSource[MUSIC_SOURCE].stream_mutex); + // Always scope the music data, we may need it + PlayStream ((*pmus), MUSIC_SOURCE, Continuous, true, true); + UnlockMutex (soundSource[MUSIC_SOURCE].stream_mutex); + + curMusicRef = MusicRef; + } + + (void) Priority; /* Satisfy compiler because of unused variable */ +} + +void +PLRStop (MUSIC_REF MusicRef) +{ + if (MusicRef == curMusicRef || MusicRef == (MUSIC_REF)~0) + { + LockMutex (soundSource[MUSIC_SOURCE].stream_mutex); + StopStream (MUSIC_SOURCE); + UnlockMutex (soundSource[MUSIC_SOURCE].stream_mutex); + + curMusicRef = 0; + } +} + +BOOLEAN +PLRPlaying (MUSIC_REF MusicRef) +{ + if (curMusicRef && (MusicRef == curMusicRef || MusicRef == (MUSIC_REF)~0)) + { + BOOLEAN playing; + + LockMutex (soundSource[MUSIC_SOURCE].stream_mutex); + playing = PlayingStream (MUSIC_SOURCE); + UnlockMutex (soundSource[MUSIC_SOURCE].stream_mutex); + + return playing; + } + + return FALSE; +} + +void +PLRSeek (MUSIC_REF MusicRef, DWORD pos) +{ + if (MusicRef == curMusicRef || MusicRef == (MUSIC_REF)~0) + { + LockMutex (soundSource[MUSIC_SOURCE].stream_mutex); + SeekStream (MUSIC_SOURCE, pos); + UnlockMutex (soundSource[MUSIC_SOURCE].stream_mutex); + } +} + +void +PLRPause (MUSIC_REF MusicRef) +{ + if (MusicRef == curMusicRef || MusicRef == (MUSIC_REF)~0) + { + LockMutex (soundSource[MUSIC_SOURCE].stream_mutex); + PauseStream (MUSIC_SOURCE); + UnlockMutex (soundSource[MUSIC_SOURCE].stream_mutex); + } +} + +void +PLRResume (MUSIC_REF MusicRef) +{ + if (MusicRef == curMusicRef || MusicRef == (MUSIC_REF)~0) + { + LockMutex (soundSource[MUSIC_SOURCE].stream_mutex); + ResumeStream (MUSIC_SOURCE); + UnlockMutex (soundSource[MUSIC_SOURCE].stream_mutex); + } +} + +void +snd_PlaySpeech (MUSIC_REF SpeechRef) +{ + TFB_SoundSample **pmus = SpeechRef; + + if (pmus) + { + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + // Do not need to scope the music-as-speech as of now + PlayStream (*pmus, SPEECH_SOURCE, false, false, true); + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + + curSpeechRef = SpeechRef; + } +} + +void +snd_StopSpeech (void) +{ + if (!curSpeechRef) + return; + + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + StopStream (SPEECH_SOURCE); + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + + curSpeechRef = 0; +} + +BOOLEAN +DestroyMusic (MUSIC_REF MusicRef) +{ + return _ReleaseMusicData (MusicRef); +} + +void +SetMusicVolume (COUNT Volume) +{ + float f = (Volume / (float)MAX_VOLUME) * musicVolumeScale; + musicVolume = Volume; + audio_Sourcef (soundSource[MUSIC_SOURCE].handle, audio_GAIN, f); +} + +char* +CheckMusicResName (char* fileName) +{ + if (!fileExists2 (contentDir, fileName)) + log_add (log_Warning, "Requested track '%s' not found.", fileName); + return fileName; +} + +void * +_GetMusicData (uio_Stream *fp, DWORD length) +{ + MUSIC_REF h; + TFB_SoundSample *sample; + TFB_SoundDecoder *decoder; + char filename[256]; + + if (!_cur_resfile_name) + return NULL; + + strncpy (filename, _cur_resfile_name, sizeof(filename) - 1); + filename[sizeof(filename) - 1] = '\0'; + CheckMusicResName (filename); + + log_add (log_Info, "_GetMusicData(): loading %s", filename); + decoder = SoundDecoder_Load (contentDir, filename, 4096, 0, 0); + if (!decoder) + { + log_add (log_Warning, "_GetMusicData(): couldn't load %s", filename); + return NULL; + } + + h = AllocMusicData (sizeof (void *)); + if (!h) + { + SoundDecoder_Free (decoder); + return NULL; + } + + sample = TFB_CreateSoundSample (decoder, 64, NULL); + *h = sample; + + log_add (log_Info, " decoder: %s, rate %d format %x", + SoundDecoder_GetName (sample->decoder), + sample->decoder->frequency, sample->decoder->format); + + (void) fp; /* satisfy compiler (unused parameter) */ + (void) length; /* satisfy compiler (unused parameter) */ + return (h); +} + +BOOLEAN +_ReleaseMusicData (void *data) +{ + TFB_SoundSample **pmus = data; + TFB_SoundSample *sample; + + if (pmus == NULL) + return (FALSE); + + sample = *pmus; + assert (sample != 0); + if (sample->decoder) + { + TFB_SoundDecoder *decoder = sample->decoder; + LockMutex (soundSource[MUSIC_SOURCE].stream_mutex); + if (soundSource[MUSIC_SOURCE].sample == sample) + { // Currently playing this sample! Not good. + StopStream (MUSIC_SOURCE); + } + UnlockMutex (soundSource[MUSIC_SOURCE].stream_mutex); + + sample->decoder = NULL; + SoundDecoder_Free (decoder); + } + TFB_DestroySoundSample (sample); + FreeMusicData (data); + + return (TRUE); +} + diff --git a/src/libs/sound/openal/Makeinfo b/src/libs/sound/openal/Makeinfo new file mode 100644 index 0000000..1469461 --- /dev/null +++ b/src/libs/sound/openal/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="audiodrv_openal.c" +uqm_HFILES="audiodrv_openal.h" diff --git a/src/libs/sound/openal/audiodrv_openal.c b/src/libs/sound/openal/audiodrv_openal.c new file mode 100644 index 0000000..eee1cd1 --- /dev/null +++ b/src/libs/sound/openal/audiodrv_openal.c @@ -0,0 +1,420 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* OpenAL audio driver + */ + +#ifdef HAVE_OPENAL + + +#include "audiodrv_openal.h" +#include "../sndintrn.h" +#include "libs/log.h" +#include "libs/memlib.h" +#include <stdlib.h> + + +ALCcontext *alcContext = NULL; +ALCdevice *alcDevice = NULL; +ALfloat defaultPos[] = {0.0f, 0.0f, -1.0f}; +ALfloat listenerPos[] = {0.0f, 0.0f, 0.0f}; +ALfloat listenerVel[] = {0.0f, 0.0f, 0.0f}; +ALfloat listenerOri[] = {0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f}; + +static const audio_Driver openAL_Driver = +{ + openAL_Uninit, + openAL_GetError, + audio_DRIVER_OPENAL, + { + /* Errors */ + AL_FALSE, + AL_INVALID_NAME, + AL_INVALID_ENUM, + AL_INVALID_VALUE, + AL_INVALID_OPERATION, + AL_OUT_OF_MEMORY, + audio_DRIVER_FAILURE, + + /* Source properties */ + AL_POSITION, + AL_LOOPING, + AL_BUFFER, + AL_GAIN, + AL_SOURCE_STATE, + AL_BUFFERS_QUEUED, + AL_BUFFERS_PROCESSED, + + /* Source state information */ + AL_INITIAL, + AL_STOPPED, + AL_PLAYING, + AL_PAUSED, + + /* Sound buffer properties */ + AL_FREQUENCY, + AL_BITS, + AL_CHANNELS, + AL_SIZE, + AL_FORMAT_MONO16, + AL_FORMAT_STEREO16, + AL_FORMAT_MONO8, + AL_FORMAT_STEREO8 + }, + + /* Sources */ + openAL_GenSources, + openAL_DeleteSources, + openAL_IsSource, + openAL_Sourcei, + openAL_Sourcef, + openAL_Sourcefv, + openAL_GetSourcei, + openAL_GetSourcef, + openAL_SourceRewind, + openAL_SourcePlay, + openAL_SourcePause, + openAL_SourceStop, + openAL_SourceQueueBuffers, + openAL_SourceUnqueueBuffers, + + /* Buffers */ + openAL_GenBuffers, + openAL_DeleteBuffers, + openAL_IsBuffer, + openAL_GetBufferi, + openAL_BufferData +}; + + +/* + * Initialization + */ + +sint32 +openAL_Init (audio_Driver *driver, sint32 flags) +{ + int i; + TFB_DecoderFormats formats = + { + MIX_IS_BIG_ENDIAN, MIX_WANT_BIG_ENDIAN, + audio_FORMAT_MONO8, audio_FORMAT_STEREO8, + audio_FORMAT_MONO16, audio_FORMAT_STEREO16 + }; + + log_add (log_Info, "Initializing OpenAL."); + alcDevice = alcOpenDevice (NULL); + + if (!alcDevice) + { + log_add (log_Error, "Couldn't initialize OpenAL: %d", + alcGetError (NULL)); + return -1; + } + + *driver = openAL_Driver; + + alcContext = alcCreateContext (alcDevice, NULL); + if (!alcContext) + { + log_add (log_Error, "Couldn't create OpenAL context: %d", + alcGetError (alcDevice)); + alcCloseDevice (alcDevice); + alcDevice = NULL; + return -1; + } + + alcMakeContextCurrent (alcContext); + + log_add (log_Info, "OpenAL initialized.\n" + " version: %s\n" + " vendor: %s\n" + " renderer: %s\n" + " device: %s", + alGetString (AL_VERSION), alGetString (AL_VENDOR), + alGetString (AL_RENDERER), + alcGetString (alcDevice, ALC_DEFAULT_DEVICE_SPECIFIER)); + //log_add (log_Info, " extensions: %s", alGetString (AL_EXTENSIONS)); + + log_add (log_Info, "Initializing sound decoders."); + if (SoundDecoder_Init (flags, &formats)) + { + log_add (log_Error, "Sound decoders initialization failed."); + alcMakeContextCurrent (NULL); + alcDestroyContext (alcContext); + alcContext = NULL; + alcCloseDevice (alcDevice); + alcDevice = NULL; + return -1; + } + log_add (log_Error, "Sound decoders initialized."); + + alListenerfv (AL_POSITION, listenerPos); + alListenerfv (AL_VELOCITY, listenerVel); + alListenerfv (AL_ORIENTATION, listenerOri); + + for (i = 0; i < NUM_SOUNDSOURCES; ++i) + { + float zero[3] = {0.0f, 0.0f, 0.0f}; + + alGenSources (1, &soundSource[i].handle); + alSourcei (soundSource[i].handle, AL_LOOPING, AL_FALSE); + alSourcefv (soundSource[i].handle, AL_POSITION, defaultPos); + alSourcefv (soundSource[i].handle, AL_VELOCITY, zero); + alSourcefv (soundSource[i].handle, AL_DIRECTION, zero); + + soundSource[i].stream_mutex = CreateMutex ("OpenAL stream mutex", SYNC_CLASS_AUDIO); + } + + if (InitStreamDecoder ()) + { + log_add (log_Error, "Stream decoder initialization failed."); + // TODO: cleanup source mutexes [or is it "muti"? :) ] + SoundDecoder_Uninit (); + alcMakeContextCurrent (NULL); + alcDestroyContext (alcContext); + alcContext = NULL; + alcCloseDevice (alcDevice); + alcDevice = NULL; + return -1; + } + + alDistanceModel (AL_INVERSE_DISTANCE); + + (void) driver; // eat compiler warning + + return 0; +} + +void +openAL_Uninit (void) +{ + int i; + + UninitStreamDecoder (); + + for (i = 0; i < NUM_SOUNDSOURCES; ++i) + { + if (soundSource[i].sample && soundSource[i].sample->decoder) + { + StopStream (i); + } + if (soundSource[i].sbuffer) + { + void *sbuffer = soundSource[i].sbuffer; + soundSource[i].sbuffer = NULL; + HFree (sbuffer); + } + DestroyMutex (soundSource[i].stream_mutex); + } + + alcMakeContextCurrent (NULL); + alcDestroyContext (alcContext); + alcContext = NULL; + alcCloseDevice (alcDevice); + alcDevice = NULL; + + SoundDecoder_Uninit (); +} + + +/* + * General + */ + +sint32 +openAL_GetError (void) +{ + ALint value = alGetError (); + switch (value) + { + case AL_FALSE: + return audio_NO_ERROR; + case AL_INVALID_NAME: + return audio_INVALID_NAME; + case AL_INVALID_ENUM: + return audio_INVALID_ENUM; + case AL_INVALID_VALUE: + return audio_INVALID_VALUE; + case AL_INVALID_OPERATION: + return audio_INVALID_OPERATION; + case AL_OUT_OF_MEMORY: + return audio_OUT_OF_MEMORY; + default: + log_add (log_Debug, "openAL_GetError: unknown value %x", value); + return audio_DRIVER_FAILURE; + break; + } +} + + +/* + * Sources + */ + +void +openAL_GenSources (uint32 n, audio_Object *psrcobj) +{ + alGenSources ((ALsizei) n, (ALuint *) psrcobj); +} + +void +openAL_DeleteSources (uint32 n, audio_Object *psrcobj) +{ + alDeleteSources ((ALsizei) n, (ALuint *) psrcobj); +} + +bool +openAL_IsSource (audio_Object srcobj) +{ + return alIsSource ((ALuint) srcobj); +} + +void +openAL_Sourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal value) + +{ + alSourcei ((ALuint) srcobj, (ALenum) pname, (ALint) value); +} + +void +openAL_Sourcef (audio_Object srcobj, audio_SourceProp pname, + float value) +{ + alSourcef ((ALuint) srcobj, (ALenum) pname, (ALfloat) value); +} + +void +openAL_Sourcefv (audio_Object srcobj, audio_SourceProp pname, + float *value) +{ + alSourcefv ((ALuint) srcobj, (ALenum) pname, (ALfloat *) value); +} + +void +openAL_GetSourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal *value) +{ + alGetSourcei ((ALuint) srcobj, (ALenum) pname, (ALint *) value); + if (pname == AL_SOURCE_STATE) + { + switch (*value) + { + case AL_INITIAL: + *value = audio_INITIAL; + break; + case AL_STOPPED: + *value = audio_STOPPED; + break; + case AL_PLAYING: + *value = audio_PLAYING; + break; + case AL_PAUSED: + *value = audio_PAUSED; + break; + default: + log_add (log_Debug, "openAL_GetSourcei(): unknown value %x", + *value); + *value = audio_DRIVER_FAILURE; + } + } +} + +void +openAL_GetSourcef (audio_Object srcobj, audio_SourceProp pname, + float *value) +{ + alGetSourcef ((ALuint) srcobj, (ALenum) pname, (ALfloat *) value); +} + +void +openAL_SourceRewind (audio_Object srcobj) +{ + alSourceRewind ((ALuint) srcobj); +} + +void +openAL_SourcePlay (audio_Object srcobj) +{ + alSourcePlay ((ALuint) srcobj); +} + +void +openAL_SourcePause (audio_Object srcobj) +{ + alSourcePause ((ALuint) srcobj); +} + +void +openAL_SourceStop (audio_Object srcobj) +{ + alSourceStop ((ALuint) srcobj); +} + +void +openAL_SourceQueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj) +{ + alSourceQueueBuffers ((ALuint) srcobj, (ALsizei) n, (ALuint *) pbufobj); +} + +void +openAL_SourceUnqueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj) +{ + alSourceUnqueueBuffers ((ALuint) srcobj, (ALsizei) n, (ALuint *) pbufobj); +} + + +/* + * Buffers + */ + +void +openAL_GenBuffers (uint32 n, audio_Object *pbufobj) +{ + alGenBuffers ((ALsizei) n, (ALuint *) pbufobj); +} + +void +openAL_DeleteBuffers (uint32 n, audio_Object *pbufobj) +{ + alDeleteBuffers ((ALsizei) n, (ALuint *) pbufobj); +} + +bool +openAL_IsBuffer (audio_Object bufobj) +{ + return alIsBuffer ((ALuint) bufobj); +} + +void +openAL_GetBufferi (audio_Object bufobj, audio_BufferProp pname, + audio_IntVal *value) +{ + alGetBufferi ((ALuint) bufobj, (ALenum) pname, (ALint *) value); +} + +void +openAL_BufferData (audio_Object bufobj, uint32 format, void* data, + uint32 size, uint32 freq) +{ + alBufferData ((ALuint) bufobj, (ALenum) format, (ALvoid *) data, + (ALsizei) size, (ALsizei) freq); +} + +#endif diff --git a/src/libs/sound/openal/audiodrv_openal.h b/src/libs/sound/openal/audiodrv_openal.h new file mode 100644 index 0000000..c1a3eff --- /dev/null +++ b/src/libs/sound/openal/audiodrv_openal.h @@ -0,0 +1,86 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* OpenAL audio driver + */ + +#ifndef LIBS_SOUND_OPENAL_AUDIODRV_OPENAL_H_ +#define LIBS_SOUND_OPENAL_AUDIODRV_OPENAL_H_ + +#include "config.h" +#include "libs/sound/sound.h" +#include "endian_uqm.h" + +#if defined (__APPLE__) +# include <OpenAL/al.h> +# include <OpenAL/alc.h> +#else +# include <AL/al.h> +# include <AL/alc.h> +# ifdef _MSC_VER +# pragma comment (lib, "OpenAL32.lib") +# endif +#endif + +/* This is just a simple endianness setup for decoders */ +#ifdef WORDS_BIGENDIAN +# define MIX_IS_BIG_ENDIAN true +# define MIX_WANT_BIG_ENDIAN true +#else +# define MIX_IS_BIG_ENDIAN false +# define MIX_WANT_BIG_ENDIAN false +#endif + + +/* General */ +sint32 openAL_Init (audio_Driver *driver, sint32 flags); +void openAL_Uninit (void); +sint32 openAL_GetError (void); + +/* Sources */ +void openAL_GenSources (uint32 n, audio_Object *psrcobj); +void openAL_DeleteSources (uint32 n, audio_Object *psrcobj); +bool openAL_IsSource (audio_Object srcobj); +void openAL_Sourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal value); +void openAL_Sourcef (audio_Object srcobj, audio_SourceProp pname, + float value); +void openAL_Sourcefv (audio_Object srcobj, audio_SourceProp pname, + float *value); +void openAL_GetSourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal *value); +void openAL_GetSourcef (audio_Object srcobj, audio_SourceProp pname, + float *value); +void openAL_SourceRewind (audio_Object srcobj); +void openAL_SourcePlay (audio_Object srcobj); +void openAL_SourcePause (audio_Object srcobj); +void openAL_SourceStop (audio_Object srcobj); +void openAL_SourceQueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj); +void openAL_SourceUnqueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj); + +/* Buffers */ +void openAL_GenBuffers (uint32 n, audio_Object *pbufobj); +void openAL_DeleteBuffers (uint32 n, audio_Object *pbufobj); +bool openAL_IsBuffer (audio_Object bufobj); +void openAL_GetBufferi (audio_Object bufobj, audio_BufferProp pname, + audio_IntVal *value); +void openAL_BufferData (audio_Object bufobj, uint32 format, void* data, + uint32 size, uint32 freq); + + +#endif /* LIBS_SOUND_OPENAL_AUDIODRV_OPENAL_H_ */ diff --git a/src/libs/sound/resinst.c b/src/libs/sound/resinst.c new file mode 100644 index 0000000..dc44c49 --- /dev/null +++ b/src/libs/sound/resinst.c @@ -0,0 +1,65 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "sound.h" +#include "sndintrn.h" + +static void +GetSoundBankFileData (const char *pathname, RESOURCE_DATA *resdata) +{ + resdata->ptr = LoadResourceFromPath (pathname, _GetSoundBankData); +} + +static void +GetMusicFileData (const char *pathname, RESOURCE_DATA *resdata) +{ + resdata->ptr = LoadResourceFromPath (pathname, _GetMusicData); +} + +BOOLEAN +InstallAudioResTypes (void) +{ + InstallResTypeVectors ("SNDRES", GetSoundBankFileData, _ReleaseSoundBankData, NULL); + InstallResTypeVectors ("MUSICRES", GetMusicFileData, _ReleaseMusicData, NULL); + return (TRUE); +} + +SOUND_REF +LoadSoundInstance (RESOURCE res) +{ + void *hData; + + hData = res_GetResource (res); + if (hData) + res_DetachResource (res); + + return ((SOUND_REF)hData); +} + +MUSIC_REF +LoadMusicInstance (RESOURCE res) +{ + void *hData; + + hData = res_GetResource (res); + if (hData) + res_DetachResource (res); + + return ((MUSIC_REF)hData); +} + diff --git a/src/libs/sound/sfx.c b/src/libs/sound/sfx.c new file mode 100644 index 0000000..3060434 --- /dev/null +++ b/src/libs/sound/sfx.c @@ -0,0 +1,306 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "options.h" +#include "sound.h" +#include "sndintrn.h" +#include "libs/reslib.h" +#include "libs/log.h" +#include "libs/strlib.h" + // for GetStringAddress() +#include "libs/strings/strintrn.h" + // for AllocStringTable(), FreeStringTable() +#include "libs/memlib.h" +#include <math.h> + + +static void CheckFinishedChannels (void); + +static const SoundPosition notPositional = {FALSE, 0, 0}; + +void +PlayChannel (COUNT channel, SOUND snd, SoundPosition pos, + void *positional_object, unsigned char priority) +{ + SOUNDPTR snd_ptr = GetSoundAddress (snd); + TFB_SoundSample *sample; + + StopSource (channel); + // all finished (stopped) channels can be cleaned up at this point + // since this is the only func that can initiate an sfx sound + CheckFinishedChannels (); + + if (!snd_ptr) + return; // nothing to play + + sample = *(TFB_SoundSample**) snd_ptr; + + soundSource[channel].sample = sample; + soundSource[channel].positional_object = positional_object; + + UpdateSoundPosition (channel, optStereoSFX ? pos : notPositional); + + audio_Sourcei (soundSource[channel].handle, audio_BUFFER, + sample->buffer[0]); + audio_SourcePlay (soundSource[channel].handle); + (void) priority; +} + +void +StopChannel (COUNT channel, unsigned char Priority) +{ + StopSource (channel); + (void)Priority; // ignored +} + +static void +CheckFinishedChannels (void) +{ + int i; + + for (i = FIRST_SFX_SOURCE; i <= LAST_SFX_SOURCE; ++i) + { + audio_IntVal state; + + audio_GetSourcei (soundSource[i].handle, audio_SOURCE_STATE, + &state); + if (state == audio_STOPPED) + { + CleanSource (i); + // and if it failed... we still dont care + audio_GetError(); + } + } +} + +BOOLEAN +ChannelPlaying (COUNT WhichChannel) +{ + audio_IntVal state; + + audio_GetSourcei (soundSource[WhichChannel].handle, + audio_SOURCE_STATE, &state); + if (state == audio_PLAYING) + return TRUE; + return FALSE; +} + +void * +GetPositionalObject (COUNT channel) +{ + return soundSource[channel].positional_object; +} + +void +SetPositionalObject (COUNT channel, void *positional_object) +{ + soundSource[channel].positional_object = positional_object; +} + +void +UpdateSoundPosition (COUNT channel, SoundPosition pos) +{ + const float ATTENUATION = 160.0f; + const float MIN_DISTANCE = 0.5f; + float fpos[3]; + + if (pos.positional) + { + float dist; + + fpos[0] = pos.x / ATTENUATION; + fpos[1] = 0.0f; + fpos[2] = pos.y / ATTENUATION; + dist = (float) sqrt (fpos[0] * fpos[0] + fpos[2] * fpos[2]); + if (dist < MIN_DISTANCE) + { // object is too close to listener + // move it away along the same vector + float scale = MIN_DISTANCE / dist; + fpos[0] *= scale; + fpos[2] *= scale; + } + + audio_Sourcefv (soundSource[channel].handle, audio_POSITION, fpos); + //log_add (log_Debug, "UpdateSoundPosition(): channel %d, pos %d %d, posobj %x", + // channel, pos.x, pos.y, (unsigned int)soundSource[channel].positional_object); + } + else + { + fpos[0] = fpos[1] = 0.0f; + fpos[2] = -1.0f; + audio_Sourcefv (soundSource[channel].handle, audio_POSITION, fpos); + } +} + +void +SetChannelVolume (COUNT channel, COUNT volume, BYTE priority) + // I wonder what this whole priority business is... + // I can probably ignore it. +{ + audio_Sourcef (soundSource[channel].handle, audio_GAIN, + (volume / (float)MAX_VOLUME) * sfxVolumeScale); + (void)priority; // ignored +} + +void * +_GetSoundBankData (uio_Stream *fp, DWORD length) +{ + int snd_ct, n; + DWORD opos; + char CurrentLine[1024], filename[1024]; +#define MAX_FX 256 + TFB_SoundSample *sndfx[MAX_FX]; + STRING_TABLE Snd; + STRING str; + int i; + + (void) length; // ignored + opos = uio_ftell (fp); + + { + char *s1, *s2; + + if (_cur_resfile_name == 0 + || (((s2 = 0), (s1 = strrchr (_cur_resfile_name, '/')) == 0) + && (s2 = strrchr (_cur_resfile_name, '\\')) == 0)) + n = 0; + else + { + if (s2 > s1) + s1 = s2; + n = s1 - _cur_resfile_name + 1; + strncpy (filename, _cur_resfile_name, n); + } + } + + snd_ct = 0; + while (uio_fgets (CurrentLine, sizeof (CurrentLine), fp) && + snd_ct < MAX_FX) + { + TFB_SoundSample* sample; + TFB_SoundDecoder* decoder; + uint32 decoded_bytes; + + if (sscanf (CurrentLine, "%s", &filename[n]) != 1) + { + log_add (log_Warning, "_GetSoundBankData: bad line: '%s'", + CurrentLine); + continue; + } + + log_add (log_Info, "_GetSoundBankData(): loading %s", filename); + + decoder = SoundDecoder_Load (contentDir, filename, 4096, 0, 0); + if (!decoder) + { + log_add (log_Warning, "_GetSoundBankData(): couldn't load %s", + filename); + continue; + } + + // SFX samples don't have decoders, everything is pre-decoded below + sample = TFB_CreateSoundSample (NULL, 1, NULL); + + // Decode everything and stash it in 1 buffer + decoded_bytes = SoundDecoder_DecodeAll (decoder); + log_add (log_Info, "_GetSoundBankData(): decoded bytes %d", + decoded_bytes); + + audio_BufferData (sample->buffer[0], decoder->format, + decoder->buffer, decoded_bytes, decoder->frequency); + // just for informational purposes + sample->length = decoder->length; + + SoundDecoder_Free (decoder); + + sndfx[snd_ct] = sample; + ++snd_ct; + } + + if (!snd_ct) + return NULL; // no sounds decoded + + Snd = AllocStringTable (snd_ct, 0); + if (!Snd) + { // Oops, have to delete everything now + while (snd_ct--) + TFB_DestroySoundSample (sndfx[snd_ct]); + + return NULL; + } + + // Populate the STRING_TABLE with ptrs to sample + for (i = 0, str = Snd->strings; i < snd_ct; ++i, ++str) + { + TFB_SoundSample **target = HMalloc (sizeof (sndfx[0])); + *target = sndfx[i]; + str->data = (STRINGPTR)target; + str->length = sizeof (sndfx[0]); + } + + return Snd; +} + +BOOLEAN +_ReleaseSoundBankData (void *Snd) +{ + STRING_TABLE fxTab = Snd; + int index; + + if (!fxTab) + return FALSE; + + for (index = 0; index < fxTab->size; ++index) + { + int i; + void **sptr = (void**)fxTab->strings[index].data; + TFB_SoundSample *sample = (TFB_SoundSample*)*sptr; + + // Check all sources and see if we are currently playing this sample + for (i = 0; i < NUM_SOUNDSOURCES; ++i) + { + if (soundSource[i].sample == sample) + { // Playing this sample. Have to stop it. + StopSource (i); + soundSource[i].sample = NULL; + } + } + + if (sample->decoder) + SoundDecoder_Free (sample->decoder); + sample->decoder = NULL; + TFB_DestroySoundSample (sample); + // sptr will be deleted by FreeStringTable() below + } + + FreeStringTable (fxTab); + + return TRUE; +} + +BOOLEAN +DestroySound(SOUND_REF target) +{ + return _ReleaseSoundBankData (target); +} + +// The type conversions are implicit and will generate errors +// or warnings if types change imcompatibly +SOUNDPTR +GetSoundAddress (SOUND sound) +{ + return GetStringAddress (sound); +} diff --git a/src/libs/sound/sndintrn.h b/src/libs/sound/sndintrn.h new file mode 100644 index 0000000..179028c --- /dev/null +++ b/src/libs/sound/sndintrn.h @@ -0,0 +1,76 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_SOUND_SNDINTRN_H_ +#define LIBS_SOUND_SNDINTRN_H_ + +#include <stdio.h> +#include "types.h" +#include "libs/reslib.h" +#include "libs/memlib.h" + +#define PAD_SCOPE_BYTES 256 + +extern void *_GetMusicData (uio_Stream *fp, DWORD length); +extern BOOLEAN _ReleaseMusicData (void *handle); + +extern void *_GetSoundBankData (uio_Stream *fp, DWORD length); +extern BOOLEAN _ReleaseSoundBankData (void *handle); + +#define AllocMusicData HMalloc +#define FreeMusicData HFree + +extern char* CheckMusicResName (char* filename); + +// audio data +struct tfb_soundsample +{ + TFB_SoundDecoder *decoder; // decoder to read from + float length; // total length of decoder chain in seconds + audio_Object *buffer; + uint32 num_buffers; + TFB_SoundTag *buffer_tag; + sint32 offset; // initial offset + void* data; // user-defined data + TFB_SoundCallbacks callbacks; // user-defined callbacks +}; + +// equivalent to channel in legacy sound code +typedef struct tfb_soundsource +{ + TFB_SoundSample *sample; + audio_Object handle; + bool stream_should_be_playing; + Mutex stream_mutex; + sint32 start_time; // for tracks played-time math + uint32 pause_time; // keep track for paused tracks + void *positional_object; + + audio_Object last_q_buf; // for callbacks processing + + // Cyclic waveform buffer for oscilloscope + void *sbuffer; + uint32 sbuf_size; + uint32 sbuf_tail; + uint32 sbuf_head; + uint32 sbuf_lasttime; // timestamp of the first queued buffer +} TFB_SoundSource; + +extern TFB_SoundSource soundSource[]; + +#endif /* LIBS_SOUND_SNDINTRN_H_ */ diff --git a/src/libs/sound/sound.c b/src/libs/sound/sound.c new file mode 100644 index 0000000..f2a790e --- /dev/null +++ b/src/libs/sound/sound.c @@ -0,0 +1,178 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "sound.h" +#include "sndintrn.h" +#include "libs/compiler.h" +#include "libs/inplib.h" +#include "libs/memlib.h" + + +int musicVolume = NORMAL_VOLUME; +float musicVolumeScale; +float sfxVolumeScale; +float speechVolumeScale; +TFB_SoundSource soundSource[NUM_SOUNDSOURCES]; + + +void +StopSound (void) +{ + int i; + + for (i = FIRST_SFX_SOURCE; i <= LAST_SFX_SOURCE; ++i) + { + StopSource (i); + } +} + +void +CleanSource (int iSource) +{ +#define MAX_STACK_BUFFERS 64 + audio_IntVal processed; + + soundSource[iSource].positional_object = NULL; + audio_GetSourcei (soundSource[iSource].handle, + audio_BUFFERS_PROCESSED, &processed); + if (processed != 0) + { + audio_Object stack_bufs[MAX_STACK_BUFFERS]; + audio_Object *bufs; + + if (processed > MAX_STACK_BUFFERS) + bufs = (audio_Object *) HMalloc ( + sizeof (audio_Object) * processed); + else + bufs = stack_bufs; + + audio_SourceUnqueueBuffers (soundSource[iSource].handle, + processed, bufs); + + if (processed > MAX_STACK_BUFFERS) + HFree (bufs); + } + // set the source state to 'initial' + audio_SourceRewind (soundSource[iSource].handle); +} + +void +StopSource (int iSource) +{ + audio_SourceStop (soundSource[iSource].handle); + CleanSource (iSource); +} + +BOOLEAN +SoundPlaying (void) +{ + int i; + + for (i = 0; i < NUM_SOUNDSOURCES; ++i) + { + TFB_SoundSample *sample; + sample = soundSource[i].sample; + if (sample && sample->decoder) + { + BOOLEAN result; + LockMutex (soundSource[i].stream_mutex); + result = PlayingStream (i); + UnlockMutex (soundSource[i].stream_mutex); + if (result) + return TRUE; + } + else + { + audio_IntVal state; + audio_GetSourcei (soundSource[i].handle, audio_SOURCE_STATE, &state); + if (state == audio_PLAYING) + return TRUE; + } + } + + return FALSE; +} + +// for now just spin in a sleep() loop +// perhaps later change to condvar implementation +void +WaitForSoundEnd (COUNT Channel) +{ + while (Channel == TFBSOUND_WAIT_ALL ? + SoundPlaying () : ChannelPlaying (Channel)) + { + SleepThread (ONE_SECOND / 20); + if (QuitPosted) // Don't make users wait for sounds to end + break; + } +} + + +// Status: Ignored +BOOLEAN +InitSound (int argc, char* argv[]) +{ + /* Quell compiler warnings */ + (void)argc; + (void)argv; + return TRUE; +} + +// Status: Ignored +void +UninitSound (void) +{ +} + +void +SetSFXVolume (float volume) +{ + int i; + for (i = FIRST_SFX_SOURCE; i <= LAST_SFX_SOURCE; ++i) + { + audio_Sourcef (soundSource[i].handle, audio_GAIN, volume); + } +} + +void +SetSpeechVolume (float volume) +{ + audio_Sourcef (soundSource[SPEECH_SOURCE].handle, audio_GAIN, volume); +} + +DWORD +FadeMusic (BYTE end_vol, SIZE TimeInterval) +{ + if (QuitPosted) // Don't make users wait for fades + TimeInterval = 0; + + if (TimeInterval < 0) + TimeInterval = 0; + + if (!SetMusicStreamFade (TimeInterval, end_vol)) + { // fade rejected, maybe due to TimeInterval==0 + SetMusicVolume (end_vol); + return GetTimeCounter (); + } + else + { + return GetTimeCounter () + TimeInterval + 1; + } +} + + diff --git a/src/libs/sound/sound.h b/src/libs/sound/sound.h new file mode 100644 index 0000000..2a4f447 --- /dev/null +++ b/src/libs/sound/sound.h @@ -0,0 +1,81 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_SOUND_SOUND_H_ // try avoiding collisions on id +#define LIBS_SOUND_SOUND_H_ + +#include "types.h" +#include "audiocore.h" +#include "decoders/decoder.h" +#include "libs/threadlib.h" +#include "libs/sndlib.h" + + +#define FIRST_SFX_SOURCE 0 +#define LAST_SFX_SOURCE (FIRST_SFX_SOURCE + NUM_SFX_CHANNELS - 1) +#define MUSIC_SOURCE (LAST_SFX_SOURCE + 1) +#define SPEECH_SOURCE (MUSIC_SOURCE + 1) +#define NUM_SOUNDSOURCES (SPEECH_SOURCE + 1) + +typedef struct +{ + int in_use; + audio_Object buf_name; + intptr_t data; // user-defined data +} TFB_SoundTag; + +typedef struct tfb_soundcallbacks +{ + // return TRUE to continue, FALSE to abort + bool (* OnStartStream) (TFB_SoundSample*); + // return TRUE to continue, FALSE to abort + bool (* OnEndChunk) (TFB_SoundSample*, audio_Object); + // return TRUE to continue, FALSE to abort + void (* OnEndStream) (TFB_SoundSample*); + // tagged buffer callback + void (* OnTaggedBuffer) (TFB_SoundSample*, TFB_SoundTag*); + // buffer just queued + void (* OnQueueBuffer) (TFB_SoundSample*, audio_Object); +} TFB_SoundCallbacks; + + +extern int musicVolume; +extern float musicVolumeScale; +extern float sfxVolumeScale; +extern float speechVolumeScale; + +void StopSource (int iSource); +void CleanSource (int iSource); + +void SetSFXVolume (float volume); +void SetSpeechVolume (float volume); + +TFB_SoundSample *TFB_CreateSoundSample (TFB_SoundDecoder*, uint32 num_buffers, + const TFB_SoundCallbacks* /* can be NULL */); +void TFB_DestroySoundSample (TFB_SoundSample*); +void TFB_SetSoundSampleData (TFB_SoundSample*, void* data); +void* TFB_GetSoundSampleData (TFB_SoundSample*); +void TFB_SetSoundSampleCallbacks (TFB_SoundSample*, + const TFB_SoundCallbacks* /* can be NULL */); +TFB_SoundDecoder* TFB_GetSoundSampleDecoder (TFB_SoundSample*); + +TFB_SoundTag* TFB_FindTaggedBuffer (TFB_SoundSample*, audio_Object buffer); +void TFB_ClearBufferTag (TFB_SoundTag*); +bool TFB_TagBuffer (TFB_SoundSample*, audio_Object buffer, intptr_t data); + +#include "stream.h" + +#endif // LIBS_SOUND_SOUND_H_ diff --git a/src/libs/sound/stream.c b/src/libs/sound/stream.c new file mode 100644 index 0000000..b7d2718 --- /dev/null +++ b/src/libs/sound/stream.c @@ -0,0 +1,814 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <assert.h> +#include <string.h> +#include <stdlib.h> + // for abs() +#include "sound.h" +#include "sndintrn.h" +#include "libs/tasklib.h" +#include "libs/timelib.h" +#include "libs/threadlib.h" +#include "libs/log.h" +#include "libs/memlib.h" + + +static Task decoderTask; + +static TimeCount musicFadeStartTime; +static sint32 musicFadeInterval; +static int musicFadeStartVolume; +static int musicFadeDelta; +// Mutex protects fade structures +static Mutex fade_mutex; + +static void add_scope_data (TFB_SoundSource *source, uint32 bytes); + + +void +PlayStream (TFB_SoundSample *sample, uint32 source, bool looping, bool scope, + bool rewind) +{ + uint32 i; + sint32 offset; + TFB_SoundDecoder *decoder; + + if (!sample) + return; + + StopStream (source); + if (sample->callbacks.OnStartStream && + !sample->callbacks.OnStartStream (sample)) + return; // callback failed + + if (sample->buffer_tag) + memset (sample->buffer_tag, 0, + sample->num_buffers * sizeof (sample->buffer_tag[0])); + + decoder = sample->decoder; + offset = sample->offset; + if (rewind) + SoundDecoder_Rewind (decoder); + else + offset += (sint32)(SoundDecoder_GetTime (decoder) * ONE_SECOND); + + soundSource[source].sample = sample; + decoder->looping = looping; + audio_Sourcei (soundSource[source].handle, audio_LOOPING, false); + + if (scope) + { // Prealloc the scope buffer in advance so that we do not + // realloc it a zillion times + soundSource[source].sbuf_size = sample->num_buffers * + decoder->buffer_size + PAD_SCOPE_BYTES; + soundSource[source].sbuffer = HCalloc (soundSource[source].sbuf_size); + } + + for (i = 0; i < sample->num_buffers; ++i) + { + uint32 decoded_bytes; + + decoded_bytes = SoundDecoder_Decode (decoder); +#if 0 + log_add (log_Debug, "PlayStream(): source:%d filename:%s start:%d " + "position:%d bytes:%d\n", + source, decoder->filename, decoder->start_sample, + decoder->pos, decoded_bytes); +#endif + if (decoded_bytes == 0) + break; + + audio_BufferData (sample->buffer[i], decoder->format, + decoder->buffer, decoded_bytes, decoder->frequency); + audio_SourceQueueBuffers (soundSource[source].handle, 1, + &sample->buffer[i]); + if (sample->callbacks.OnQueueBuffer) + sample->callbacks.OnQueueBuffer (sample, sample->buffer[i]); + + if (scope) + add_scope_data (&soundSource[source], decoded_bytes); + + if (decoder->error != SOUNDDECODER_OK) + { + if (decoder->error != SOUNDDECODER_EOF || + !sample->callbacks.OnEndChunk || + !sample->callbacks.OnEndChunk (sample, sample->buffer[i])) + { // Decoder probably run out of data before we could fill + // all buffers, and OnEndChunk() did not set a new one + break; + } + else + { // OnEndChunk() probably set a new decoder, get it + decoder = sample->decoder; + } + } + } + + soundSource[source].sbuf_lasttime = GetTimeCounter (); + // Adjust the start time so it looks like the stream has been playing + // from the very beginning + soundSource[source].start_time = GetTimeCounter () - offset; + soundSource[source].pause_time = 0; + soundSource[source].stream_should_be_playing = TRUE; + audio_SourcePlay (soundSource[source].handle); +} + +void +StopStream (uint32 source) +{ + StopSource (source); + + soundSource[source].stream_should_be_playing = FALSE; + soundSource[source].sample = NULL; + + if (soundSource[source].sbuffer) + { + void *sbuffer = soundSource[source].sbuffer; + soundSource[source].sbuffer = NULL; + HFree (sbuffer); + } + soundSource[source].sbuf_size = 0; + soundSource[source].sbuf_head = 0; + soundSource[source].sbuf_tail = 0; + soundSource[source].pause_time = 0; +} + +void +PauseStream (uint32 source) +{ + soundSource[source].stream_should_be_playing = FALSE; + if (!soundSource[source].pause_time) + soundSource[source].pause_time = GetTimeCounter (); + audio_SourcePause (soundSource[source].handle); +} + +void +ResumeStream (uint32 source) +{ + if (soundSource[source].pause_time) + { // Adjust the start time so it looks like the stream has + // been playing all this time non-stop + soundSource[source].start_time += GetTimeCounter () + - soundSource[source].pause_time; + } + soundSource[source].pause_time = 0; + soundSource[source].stream_should_be_playing = TRUE; + audio_SourcePlay (soundSource[source].handle); +} + +void +SeekStream (uint32 source, uint32 pos) +{ + TFB_SoundSample* sample = soundSource[source].sample; + bool looping; + bool scope; + + if (!sample) + return; + looping = sample->decoder->looping; + scope = soundSource[source].sbuffer != NULL; + + StopSource (source); + SoundDecoder_Seek (sample->decoder, pos); + PlayStream (sample, source, looping, scope, false); +} + +BOOLEAN +PlayingStream (uint32 source) +{ + return soundSource[source].stream_should_be_playing; +} + + +TFB_SoundSample * +TFB_CreateSoundSample (TFB_SoundDecoder *decoder, uint32 num_buffers, + const TFB_SoundCallbacks *pcbs /* can be NULL */) +{ + TFB_SoundSample *sample; + + sample = HCalloc (sizeof (*sample)); + sample->decoder = decoder; + sample->num_buffers = num_buffers; + sample->buffer = HCalloc (sizeof (audio_Object) * num_buffers); + audio_GenBuffers (num_buffers, sample->buffer); + if (pcbs) + sample->callbacks = *pcbs; + + return sample; +} + +// Deletes all TFB_SoundSample data structures, except decoder +void +TFB_DestroySoundSample (TFB_SoundSample *sample) +{ + if (sample->buffer) + { + audio_DeleteBuffers (sample->num_buffers, sample->buffer); + HFree (sample->buffer); + } + HFree (sample->buffer_tag); + HFree (sample); +} + +void +TFB_SetSoundSampleData (TFB_SoundSample *sample, void* data) +{ + sample->data = data; +} + +void* +TFB_GetSoundSampleData (TFB_SoundSample *sample) +{ + return sample->data; +} + +void +TFB_SetSoundSampleCallbacks (TFB_SoundSample *sample, + const TFB_SoundCallbacks *pcbs /* can be NULL */) +{ + if (pcbs) + sample->callbacks = *pcbs; + else + memset (&sample->callbacks, 0, sizeof (sample->callbacks)); +} + +TFB_SoundDecoder* +TFB_GetSoundSampleDecoder (TFB_SoundSample *sample) +{ + return sample->decoder; +} + +TFB_SoundTag* +TFB_FindTaggedBuffer (TFB_SoundSample *sample, audio_Object buffer) +{ + uint32 buf_num; + + if (!sample->buffer_tag) + return NULL; // do not have any tags + + for (buf_num = 0; + buf_num < sample->num_buffers && + (!sample->buffer_tag[buf_num].in_use || + sample->buffer_tag[buf_num].buf_name != buffer + ); + buf_num++) + ; + + return buf_num < sample->num_buffers ? + &sample->buffer_tag[buf_num] : NULL; +} + +bool +TFB_TagBuffer (TFB_SoundSample *sample, audio_Object buffer, intptr_t data) +{ + uint32 buf_num; + + if (!sample->buffer_tag) + sample->buffer_tag = HCalloc (sizeof (TFB_SoundTag) * + sample->num_buffers); + + for (buf_num = 0; + buf_num < sample->num_buffers && + sample->buffer_tag[buf_num].in_use && + sample->buffer_tag[buf_num].buf_name != buffer; + buf_num++) + ; + + if (buf_num >= sample->num_buffers) + return false; // no empty slot + + sample->buffer_tag[buf_num].in_use = 1; + sample->buffer_tag[buf_num].buf_name = buffer; + sample->buffer_tag[buf_num].data = data; + + return true; +} + +void +TFB_ClearBufferTag (TFB_SoundTag *ptag) +{ + ptag->in_use = 0; + ptag->buf_name = 0; +} + +static void +remove_scope_data (TFB_SoundSource *source, audio_Object buffer) +{ + audio_IntVal buf_size; + + audio_GetBufferi (buffer, audio_SIZE, &buf_size); + source->sbuf_head += buf_size; + // the buffer is cyclic + source->sbuf_head %= source->sbuf_size; + + source->sbuf_lasttime = GetTimeCounter (); +} + +static void +add_scope_data (TFB_SoundSource *source, uint32 bytes) +{ + uint8 *sbuffer = source->sbuffer; + uint8 *dec_buf = source->sample->decoder->buffer; + uint32 tail_bytes; + uint32 wrap_bytes; + + if (source->sbuf_tail + bytes > source->sbuf_size) + { // does not fit at the tail, have to split it up + tail_bytes = source->sbuf_size - source->sbuf_tail; + wrap_bytes = bytes - tail_bytes; + } + else + { // all fits at the tail + tail_bytes = bytes; + wrap_bytes = 0; + } + + if (tail_bytes) + { + memcpy (sbuffer + source->sbuf_tail, dec_buf, tail_bytes); + source->sbuf_tail += tail_bytes; + } + + if (wrap_bytes) + { + memcpy (sbuffer, dec_buf + tail_bytes, wrap_bytes); + source->sbuf_tail = wrap_bytes; + } +} + +static void +process_stream (TFB_SoundSource *source) +{ + TFB_SoundSample *sample = source->sample; + TFB_SoundDecoder *decoder = sample->decoder; + bool end_chunk_failed = false; + audio_IntVal processed; + audio_IntVal queued; + + audio_GetSourcei (source->handle, audio_BUFFERS_PROCESSED, &processed); + audio_GetSourcei (source->handle, audio_BUFFERS_QUEUED, &queued); + + if (processed == 0) + { // Nothing was played + audio_IntVal state; + + audio_GetSourcei (source->handle, audio_SOURCE_STATE, &state); + if (state != audio_PLAYING) + { + if (queued == 0 && decoder->error == SOUNDDECODER_EOF) + { // The stream has reached the end + log_add (log_Info, "StreamDecoderTaskFunc(): " + "finished playing %s", decoder->filename); + source->stream_should_be_playing = FALSE; + + if (sample->callbacks.OnEndStream) + sample->callbacks.OnEndStream (sample); + } + else + { + log_add (log_Warning, "StreamDecoderTaskFunc(): " + "buffer underrun playing %s", decoder->filename); + audio_SourcePlay (source->handle); + } + } + } + + // Unqueue processed buffers and replace them with new ones + for (; processed > 0; --processed) + { + uint32 error; + audio_Object buffer; + uint32 decoded_bytes; + + audio_GetError (); // clear error state + + // Get the buffer that finished playing + audio_SourceUnqueueBuffers (source->handle, 1, &buffer); + error = audio_GetError(); + if (error != audio_NO_ERROR) + { + log_add (log_Warning, "StreamDecoderTaskFunc(): " + "error after audio_SourceUnqueueBuffers: %x, file %s", + error, decoder->filename); + break; + } + + // Process a callback on a tagged buffer, if any + if (sample->callbacks.OnTaggedBuffer) + { + TFB_SoundTag* tag = TFB_FindTaggedBuffer (sample, buffer); + if (tag) + sample->callbacks.OnTaggedBuffer (sample, tag); + } + + if (source->sbuffer) + remove_scope_data (source, buffer); + + // See what state the decoder was left in last time around + if (decoder->error != SOUNDDECODER_OK) + { + if (decoder->error == SOUNDDECODER_EOF) + { + if (end_chunk_failed) + continue; // should not do it again + + if (!sample->callbacks.OnEndChunk || + !sample->callbacks.OnEndChunk (sample, source->last_q_buf)) + { // Reached the end of the current stream and we did not + // get another sample to play (relevant for Trackplayer) + end_chunk_failed = true; + continue; + } + else + { // OnEndChunk succeeded, so someone (read: Trackplayer) + // wants to keep going, probably with a new decoder. + // Get the new decoder + decoder = sample->decoder; + } + } + else + { // Decoder returned a real error, keep going +#if 0 + log_add (log_Debug, "StreamDecoderTaskFunc(): " + "decoder->error is %d for %s", decoder->error, + decoder->filename); +#endif + continue; + } + } + + // Now replace the unqueued buffer with a new one + decoded_bytes = SoundDecoder_Decode (decoder); + if (decoder->error == SOUNDDECODER_ERROR) + { + log_add (log_Warning, "StreamDecoderTaskFunc(): " + "SoundDecoder_Decode error %d, file %s", + decoder->error, decoder->filename); + source->stream_should_be_playing = FALSE; + continue; + } + + if (decoded_bytes == 0) + { // Nothing was decoded, keep going + continue; + // This loses a stream buffer, which we cannot get back + // w/o restarting the stream, but we should never get here. + } + + // And a new buffer is born + audio_BufferData (buffer, decoder->format, decoder->buffer, + decoded_bytes, decoder->frequency); + error = audio_GetError(); + if (error != audio_NO_ERROR) + { + log_add (log_Warning, "StreamDecoderTaskFunc(): " + "error after audio_BufferData: %x, file %s, decoded %d", + error, decoder->filename, decoded_bytes); + continue; + } + + // Now queue the buffer + audio_SourceQueueBuffers (source->handle, 1, &buffer); + error = audio_GetError(); + if (error != audio_NO_ERROR) + { + log_add (log_Warning, "StreamDecoderTaskFunc(): " + "error after audio_SourceQueueBuffers: %x, file %s, " + "decoded %d", error, decoder->filename, decoded_bytes); + continue; + } + + // Remember the last queued buffer so we can pass it to callbacks + source->last_q_buf = buffer; + if (sample->callbacks.OnQueueBuffer) + sample->callbacks.OnQueueBuffer (sample, buffer); + + if (source->sbuffer) + add_scope_data (source, decoded_bytes); + } +} + +static void +processMusicFade (void) +{ + TimeCount Now; + sint32 elapsed; + int newVolume; + + LockMutex (fade_mutex); + + if (!musicFadeInterval) + { // there is no fade set + UnlockMutex (fade_mutex); + return; + } + + Now = GetTimeCounter (); + elapsed = Now - musicFadeStartTime; + if (elapsed > musicFadeInterval) + elapsed = musicFadeInterval; + + newVolume = musicFadeStartVolume + (long)musicFadeDelta * elapsed + / musicFadeInterval; + SetMusicVolume (newVolume); + + if (elapsed >= musicFadeInterval) + musicFadeInterval = 0; // fade is over + + UnlockMutex (fade_mutex); +} + +static int +StreamDecoderTaskFunc (void *data) +{ + Task task = (Task)data; + int active_streams; + int i; + + while (!Task_ReadState (task, TASK_EXIT)) + { + active_streams = 0; + + processMusicFade (); + + for (i = MUSIC_SOURCE; i < NUM_SOUNDSOURCES; ++i) + { + TFB_SoundSource *source = &soundSource[i]; + + LockMutex (source->stream_mutex); + + if (!source->sample || + !source->sample->decoder || + !source->stream_should_be_playing || + source->sample->decoder->error == SOUNDDECODER_ERROR) + { + UnlockMutex (source->stream_mutex); + continue; + } + + process_stream (source); + active_streams++; + + UnlockMutex (source->stream_mutex); + } + + if (active_streams == 0) + { // Throttle down the thread when there are no active streams + HibernateThread (ONE_SECOND / 10); + } + else + TaskSwitch (); + } + + FinishTask (task); + return 0; +} + +static inline sint32 +readSoundSample (void *ptr, int sample_size) +{ + if (sample_size == sizeof (uint8)) + return (*(uint8*)ptr - 128) << 8; + else + return *(sint16*)ptr; +} + +// Graphs the current sound data for the oscilloscope. +// Includes a rudimentary automatic gain control (AGC) to properly graph +// the streams at different gain levels (based on running average). +// We use AGC because different pieces of music and speech can easily be +// at very different gain levels, because the game is moddable. +int +GraphForegroundStream (uint8 *data, sint32 width, sint32 height, + bool wantSpeech) +{ + int source_num; + TFB_SoundSource *source; + TFB_SoundDecoder *decoder; + int channels; + int sample_size; + int full_sample; + int step; + long played_time; + long delta; + uint8 *sbuffer; + unsigned long pos; + int scale; + sint32 i; + // AGC variables +#define DEF_PAGE_MAX 28000 +#define AGC_PAGE_COUNT 16 + static int page_sum = DEF_PAGE_MAX * AGC_PAGE_COUNT; + static int pages[AGC_PAGE_COUNT] = + { + DEF_PAGE_MAX, DEF_PAGE_MAX, DEF_PAGE_MAX, DEF_PAGE_MAX, + DEF_PAGE_MAX, DEF_PAGE_MAX, DEF_PAGE_MAX, DEF_PAGE_MAX, + DEF_PAGE_MAX, DEF_PAGE_MAX, DEF_PAGE_MAX, DEF_PAGE_MAX, + DEF_PAGE_MAX, DEF_PAGE_MAX, DEF_PAGE_MAX, DEF_PAGE_MAX, + }; + static int page_head; +#define AGC_FRAME_COUNT 8 + static int frame_sum; + static int frames; + static int avg_amp = DEF_PAGE_MAX; // running amplitude (sort of) average + int target_amp; + int max_a; +#define VAD_MIN_ENERGY 100 + long energy; + + + // Prefer speech to music + source_num = SPEECH_SOURCE; + source = &soundSource[source_num]; + LockMutex (source->stream_mutex); + if (wantSpeech && (!source->sample || + !source->sample->decoder || !source->sample->decoder->is_null)) + { // Use speech waveform, since it's available + // Step is picked experimentally. Using step of 1 sample at 11025Hz, + // because human speech is mostly in the low frequencies, and it looks + // better this way. + step = 1; + } + else + { // We do not have speech -- use music waveform + UnlockMutex (source->stream_mutex); + + source_num = MUSIC_SOURCE; + source = &soundSource[source_num]; + LockMutex (source->stream_mutex); + + // Step is picked experimentally. Using step of 4 samples at 11025Hz. + // It looks better this way. + step = 4; + } + + if (!PlayingStream (source_num) || !source->sample + || !source->sample->decoder || !source->sbuffer + || source->sbuf_size == 0) + { // We don't have data to return, oh well. + UnlockMutex (source->stream_mutex); + return 0; + } + decoder = source->sample->decoder; + + if (!audio_GetFormatInfo (decoder->format, &channels, &sample_size)) + { + UnlockMutex (source->stream_mutex); + log_add (log_Debug, "GraphForegroundStream(): uknown format %u", + (unsigned)decoder->format); + return 0; + } + full_sample = channels * sample_size; + + // See how far into the buffer we should be now + played_time = GetTimeCounter () - source->sbuf_lasttime; + delta = played_time * decoder->frequency * full_sample / ONE_SECOND; + // align delta to sample start + delta = delta & ~(full_sample - 1); + + if (delta < 0) + { + log_add (log_Debug, "GraphForegroundStream(): something is messed" + " with timing, delta %ld", delta); + delta = 0; + } + else if (delta > (long)source->sbuf_size) + { // Stream decoder task has just had a heart attack, not much we can do + delta = 0; + } + + // Step is in 11025 Hz units, so we need to adjust to source frequency + step = decoder->frequency * step / 11025; + if (step == 0) + step = 1; + step *= full_sample; + + sbuffer = source->sbuffer; + pos = source->sbuf_head + delta; + + // We are not basing the scaling factor on signal energy, because we + // want it to *look* pretty instead of sounding nice and even + target_amp = (height >> 1) >> 1; + scale = avg_amp / target_amp; + + max_a = 0; + energy = 0; + for (i = 0; i < width; ++i, pos += step) + { + sint32 s; + int t; + + pos %= source->sbuf_size; + + s = readSoundSample (sbuffer + pos, sample_size); + if (channels > 1) + s += readSoundSample (sbuffer + pos + sample_size, sample_size); + + energy += (s * s) / 0x10000; + t = abs(s); + if (t > max_a) + max_a = t; + + s = (s / scale) + (height >> 1); + if (s < 0) + s = 0; + else if (s > height - 1) + s = height - 1; + + data[i] = s; + } + energy /= width; + + // Very basic VAD. We don't want to count speech pauses in the average + if (energy > VAD_MIN_ENERGY) + { + // Record the maximum amplitude (sort of) + frame_sum += max_a; + ++frames; + if (frames == AGC_FRAME_COUNT) + { // Got a full page + frame_sum /= AGC_FRAME_COUNT; + // Record the page + page_sum -= pages[page_head]; + page_sum += frame_sum; + pages[page_head] = frame_sum; + page_head = (page_head + 1) % AGC_PAGE_COUNT; + + frame_sum = 0; + frames = 0; + + avg_amp = page_sum / AGC_PAGE_COUNT; + } + } + + UnlockMutex (source->stream_mutex); + return 1; +} + +// This function is normally called on the Starcon2Main thread +bool +SetMusicStreamFade (sint32 howLong, int endVolume) +{ + bool ret = true; + + LockMutex (fade_mutex); + + if (howLong < 0) + howLong = 0; + + musicFadeStartTime = GetTimeCounter (); + musicFadeInterval = howLong; + musicFadeStartVolume = musicVolume; + musicFadeDelta = endVolume - musicFadeStartVolume; + if (!musicFadeInterval) + ret = false; // reject + + UnlockMutex (fade_mutex); + + return ret; +} + +int +InitStreamDecoder (void) +{ + fade_mutex = CreateMutex ("Stream fade mutex", SYNC_CLASS_AUDIO); + if (!fade_mutex) + return -1; + + decoderTask = AssignTask (StreamDecoderTaskFunc, 1024, + "audio stream decoder"); + if (!decoderTask) + return -1; + + return 0; +} + +void +UninitStreamDecoder (void) +{ + if (decoderTask) + { + ConcludeTask (decoderTask); + decoderTask = NULL; + } + + if (fade_mutex) + { + DestroyMutex (fade_mutex); + fade_mutex = NULL; + } +} diff --git a/src/libs/sound/stream.h b/src/libs/sound/stream.h new file mode 100644 index 0000000..5766132 --- /dev/null +++ b/src/libs/sound/stream.h @@ -0,0 +1,37 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef STREAM_H +#define STREAM_H + +int InitStreamDecoder (void); +void UninitStreamDecoder (void); + +void PlayStream (TFB_SoundSample *sample, uint32 source, bool looping, + bool scope, bool rewind); +void StopStream (uint32 source); +void PauseStream (uint32 source); +void ResumeStream (uint32 source); +void SeekStream (uint32 source, uint32 pos); +BOOLEAN PlayingStream (uint32 source); + +int GraphForegroundStream (uint8 *data, sint32 width, sint32 height, + bool wantSpeech); + +// returns TRUE if the fade was accepted by stream decoder +bool SetMusicStreamFade (sint32 howLong, int endVolume); + +#endif diff --git a/src/libs/sound/trackint.h b/src/libs/sound/trackint.h new file mode 100644 index 0000000..fe39740 --- /dev/null +++ b/src/libs/sound/trackint.h @@ -0,0 +1,41 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef TRACKINT_H +#define TRACKINT_H + +#include "libs/callback.h" + +struct tfb_soundchunk +{ + TFB_SoundDecoder *decoder; // decoder for this chunk + float start_time; // relative time from track start + int tag_me; // set for chunks with subtitles + uint32 track_num; // logical track #, comm code needs this + UNICODE *text; // subtitle text + CallbackFunction callback; // comm callback, executed on chunk start + struct tfb_soundchunk *next; +}; + +typedef struct tfb_soundchunk TFB_SoundChunk; + +TFB_SoundChunk *create_SoundChunk (TFB_SoundDecoder *decoder, float start_time); +void destroy_SoundChunk_list (TFB_SoundChunk *chain); +TFB_SoundChunk *find_next_page (TFB_SoundChunk *cur); +TFB_SoundChunk *find_prev_page (TFB_SoundChunk *cur); + + +#endif // TRACKINT_H diff --git a/src/libs/sound/trackplayer.c b/src/libs/sound/trackplayer.c new file mode 100644 index 0000000..8068fc3 --- /dev/null +++ b/src/libs/sound/trackplayer.c @@ -0,0 +1,874 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "sound.h" +#include "sndintrn.h" +#include "libs/sound/trackplayer.h" +#include "trackint.h" +#include "libs/log.h" +#include "libs/memlib.h" +#include "options.h" +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <memory.h> + + +static int track_count; // total number of tracks +static int no_page_break; // set when combining several tracks into one + +// The one and only sample we play. Track switching is done by modifying +// this sample while it is playing. StreamDecoderTaskFunc() picks up the +// changes *mostly* seamlessly (keyword: mostly). +// This is technically a hack, but a decent one ;) +static TFB_SoundSample *sound_sample; + +static volatile uint32 tracks_length; // total length of tracks in game units + +static TFB_SoundChunk *chunks_head; // first decoder in linked list +static TFB_SoundChunk *chunks_tail; // last decoder in linked list +static TFB_SoundChunk *last_sub; // last chunk in the list with a subtitle + +static TFB_SoundChunk *cur_chunk; // currently playing chunk +static TFB_SoundChunk *cur_sub_chunk; // currently displayed subtitle chunk + +// Accesses to cur_chunk and cur_sub_chunk are guarded by stream_mutex, +// because these should only be accesses by the DoInput and the +// stream player threads. Any other accesses would go unguarded. +// Other data structures are unguarded and should only be accessed from +// the DoInput thread at certain times, i.e. nothing can be modified +// between StartTrack() and JumpTrack()/StopTrack() calls. +// Use caution when changing code, as you may need to guard other data +// structures the same way. + +static void seek_track (sint32 offset); + +// stream callbacks +static bool OnStreamStart (TFB_SoundSample* sample); +static bool OnChunkEnd (TFB_SoundSample* sample, audio_Object buffer); +static void OnStreamEnd (TFB_SoundSample* sample); +static void OnBufferTag (TFB_SoundSample* sample, TFB_SoundTag* tag); + +static TFB_SoundCallbacks trackCBs = +{ + OnStreamStart, + OnChunkEnd, + OnStreamEnd, + OnBufferTag, + NULL +}; + +static inline sint32 +chunk_end_time (TFB_SoundChunk *chunk) +{ + return (sint32) ((chunk->start_time + chunk->decoder->length) + * ONE_SECOND); +} + +static inline sint32 +tracks_end_time (void) +{ + return chunk_end_time (chunks_tail); +} + +//JumpTrack currently aborts the current track. However, it doesn't clear the +//data-structures as StopTrack does. this allows for rewind even after the +//track has finished playing +//Question: Should 'abort' call StopTrack? +void +JumpTrack (void) +{ + if (!sound_sample) + return; // nothing to skip + + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + seek_track (tracks_length + 1); + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); +} + +// This should just start playing a stream +void +PlayTrack (void) +{ + if (!sound_sample) + return; // nothing to play + + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + tracks_length = tracks_end_time (); + // decoder will be set in OnStreamStart() + cur_chunk = chunks_head; + // Always scope the speech data, we may need it + PlayStream (sound_sample, SPEECH_SOURCE, false, true, true); + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); +} + +void +PauseTrack (void) +{ + if (!sound_sample) + return; // nothing to pause + + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + PauseStream (SPEECH_SOURCE); + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); +} + +// ResumeTrack should resume a paused track, and do nothing for a playing track +void +ResumeTrack (void) +{ + audio_IntVal state; + + if (!sound_sample) + return; // nothing to resume + + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + + if (!cur_chunk) + { // not playing anything, so no resuming + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + return; + } + + audio_GetSourcei (soundSource[SPEECH_SOURCE].handle, audio_SOURCE_STATE, &state); + if (state == audio_PAUSED) + ResumeStream (SPEECH_SOURCE); + + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); +} + +COUNT +PlayingTrack (void) +{ + // This ignores the paused state and simply returns what track + // *should* be playing + COUNT result = 0; // default is none + + if (!sound_sample) + return 0; // not playing anything + + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + if (cur_chunk) + result = cur_chunk->track_num + 1; + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + + return result; +} + +void +StopTrack (void) +{ + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + StopStream (SPEECH_SOURCE); + track_count = 0; + tracks_length = 0; + cur_chunk = NULL; + cur_sub_chunk = NULL; + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + + if (chunks_head) + { + chunks_tail = NULL; + destroy_SoundChunk_list (chunks_head); + chunks_head = NULL; + last_sub = NULL; + } + if (sound_sample) + { + // We delete the decoders ourselves + sound_sample->decoder = NULL; + TFB_DestroySoundSample (sound_sample); + sound_sample = NULL; + } +} + +static void +DoTrackTag (TFB_SoundChunk *chunk) +{ + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + if (chunk->callback) + chunk->callback(0); + + cur_sub_chunk = chunk; + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); +} + +// This func is called by PlayStream() when stream is about +// to start. We have a chance to tweak the stream here. +// This is called on the DoInput thread. +static bool +OnStreamStart (TFB_SoundSample* sample) +{ + if (sample != sound_sample) + return false; // Huh? Why did we get called on this? + + if (!cur_chunk) + return false; // Stream shouldn't be playing at all + + // Adjust the sample to play what we want + sample->decoder = cur_chunk->decoder; + sample->offset = (sint32) (cur_chunk->start_time * ONE_SECOND); + + if (cur_chunk->tag_me) + DoTrackTag (cur_chunk); + + return true; +} + +// This func is called by StreamDecoderTaskFunc() when the last buffer +// of the current chunk has been decoded (not when it has been *played*). +// This is called on the stream task thread. +static bool +OnChunkEnd (TFB_SoundSample* sample, audio_Object buffer) +{ + if (sample != sound_sample) + return false; // Huh? Why did we get called on this? + + if (!cur_chunk || !cur_chunk->next) + { // all chunks and tracks are done + return false; + } + + // Move on to the next chunk + cur_chunk = cur_chunk->next; + // Adjust the sample to play what we want + sample->decoder = cur_chunk->decoder; + SoundDecoder_Rewind (sample->decoder); + + log_add (log_Info, "Switching to stream %s at pos %d", + sample->decoder->filename, sample->decoder->start_sample); + + if (cur_chunk->tag_me) + { // Tag the last buffer of the chunk with the next chunk + TFB_TagBuffer (sample, buffer, (intptr_t)cur_chunk); + } + + return true; +} + +// This func is called by StreamDecoderTaskFunc() when stream has ended +// This is called on the stream task thread. +static void +OnStreamEnd (TFB_SoundSample* sample) +{ + if (sample != sound_sample) + return; // Huh? Why did we get called on this? + + cur_chunk = NULL; + cur_sub_chunk = NULL; +} + +// This func is called by StreamDecoderTaskFunc() when a tagged buffer +// has finished playing. +// This is called on the stream task thread. +static void +OnBufferTag (TFB_SoundSample* sample, TFB_SoundTag* tag) +{ + TFB_SoundChunk* chunk = (TFB_SoundChunk*) tag->data; + + assert (sizeof (tag->data) >= sizeof (chunk)); + + if (sample != sound_sample) + return; // Huh? Why did we get called on this? + + TFB_ClearBufferTag (tag); + DoTrackTag (chunk); +} + +// Parse the timestamps string into an int array. +// Rerturns number of timestamps parsed. +static int +GetTimeStamps (UNICODE *TimeStamps, sint32 *time_stamps) +{ + int pos; + int num = 0; + + while (*TimeStamps && (pos = strcspn (TimeStamps, ",\r\n"))) + { + UNICODE valStr[32]; + uint32 val; + + memcpy (valStr, TimeStamps, pos); + valStr[pos] = '\0'; + val = strtoul (valStr, NULL, 10); + if (val) + { + *time_stamps = val; + num++; + time_stamps++; + } + TimeStamps += pos; + TimeStamps += strspn (TimeStamps, ",\r\n"); + } + return (num); +} + +#define TEXT_SPEED 80 +// Returns number of parsed pages +static int +SplitSubPages (UNICODE *text, UNICODE *pages[], sint32 timestamp[], int size) +{ + int lead_ellips = 0; + COUNT page; + + for (page = 0; page < size && *text != '\0'; ++page) + { + int aft_ellips; + int pos; + + // seek to EOL or end of the string + pos = strcspn (text, "\r\n"); + // XXX: this will only work when ASCII punctuation and spaces + // are used exclusively + aft_ellips = 3 * (text[pos] != '\0' && pos > 0 && + !ispunct (text[pos - 1]) && !isspace (text[pos - 1])); + pages[page] = HMalloc (sizeof (UNICODE) * + (lead_ellips + pos + aft_ellips + 1)); + if (lead_ellips) + strcpy (pages[page], ".."); + memcpy (pages[page] + lead_ellips, text, pos); + pages[page][lead_ellips + pos] = '\0'; // string term + if (aft_ellips) + strcpy (pages[page] + lead_ellips + pos, "..."); + timestamp[page] = pos * TEXT_SPEED; + if (timestamp[page] < 1000) + timestamp[page] = 1000; + lead_ellips = aft_ellips ? 2 : 0; + text += pos; + // Skip any EOL + text += strspn (text, "\r\n"); + } + + return page; +} + +// decodes several tracks into one and adds it to queue +// track list is NULL-terminated +// May only be called after at least one SpliceTrack(). This is a limitation +// for the sake of timestamps, but it does not have to be so. +void +SpliceMultiTrack (UNICODE *TrackNames[], UNICODE *TrackText) +{ +#define MAX_MULTI_TRACKS 20 +#define MAX_MULTI_BUFFERS 100 + TFB_SoundDecoder* track_decs[MAX_MULTI_TRACKS + 1]; + int tracks; + int slen1, slen2; + + if (!TrackText) + { + log_add (log_Debug, "SpliceMultiTrack(): no track text"); + return; + } + + if (!sound_sample || !chunks_tail) + { + log_add (log_Warning, "SpliceMultiTrack(): Cannot be called before SpliceTrack()"); + return; + } + + log_add (log_Info, "SpliceMultiTrack(): loading..."); + for (tracks = 0; *TrackNames && tracks < MAX_MULTI_TRACKS; TrackNames++, tracks++) + { + track_decs[tracks] = SoundDecoder_Load (contentDir, *TrackNames, + 32768, 0, - 3 * TEXT_SPEED); + if (track_decs[tracks]) + { + log_add (log_Info, " track: %s, decoder: %s, rate %d format %x", + *TrackNames, + SoundDecoder_GetName (track_decs[tracks]), + track_decs[tracks]->frequency, + track_decs[tracks]->format); + SoundDecoder_DecodeAll (track_decs[tracks]); + + chunks_tail->next = create_SoundChunk (track_decs[tracks], sound_sample->length); + chunks_tail = chunks_tail->next; + chunks_tail->track_num = track_count - 1; + sound_sample->length += track_decs[tracks]->length; + } + else + { + log_add (log_Warning, "SpliceMultiTrack(): couldn't load %s\n", + *TrackNames); + tracks--; + } + } + track_decs[tracks] = 0; // term + + if (tracks == 0) + { + log_add (log_Warning, "SpliceMultiTrack(): no tracks loaded"); + return; + } + + slen1 = strlen (last_sub->text); + slen2 = strlen (TrackText); + last_sub->text = HRealloc (last_sub->text, slen1 + slen2 + 1); + strcpy (last_sub->text + slen1, TrackText); + + no_page_break = 1; +} + +// XXX: This code and the entire trackplayer are begging to be overhauled +void +SpliceTrack (UNICODE *TrackName, UNICODE *TrackText, UNICODE *TimeStamp, CallbackFunction cb) +{ + static UNICODE last_track_name[128] = ""; + static unsigned long dec_offset = 0; +#define MAX_PAGES 50 + UNICODE *pages[MAX_PAGES]; + sint32 time_stamps[MAX_PAGES]; + int num_pages; + int page; + + if (!TrackText) + return; + + if (!TrackName) + { // Appending a piece of subtitles to the last track + int slen1, slen2; + + if (track_count == 0) + { + log_add (log_Warning, "SpliceTrack(): Tried to append a subtitle," + " but no current track"); + return; + } + + if (!last_sub || !last_sub->text) + { + log_add (log_Warning, "SpliceTrack(): Tried to append a subtitle" + " to a NULL string"); + return; + } + + num_pages = SplitSubPages (TrackText, pages, time_stamps, MAX_PAGES); + if (num_pages == 0) + { + log_add (log_Warning, "SpliceTrack(): Failed to parse subtitles"); + return; + } + // The last page's stamp is a suggested value. The track should + // actually play to the end. + time_stamps[num_pages - 1] = -time_stamps[num_pages - 1]; + + // Add the first piece to the last subtitle page + slen1 = strlen (last_sub->text); + slen2 = strlen (pages[0]); + last_sub->text = HRealloc (last_sub->text, slen1 + slen2 + 1); + strcpy (last_sub->text + slen1, pages[0]); + HFree (pages[0]); + + // Add the rest of the pages + for (page = 1; page < num_pages; ++page) + { + TFB_SoundChunk *next_sub = find_next_page (last_sub); + if (next_sub) + { // nodes prepared by previous call, just fill in the subs + next_sub->text = pages[page]; + last_sub = next_sub; + } + else + { // probably no timestamps were provided, so need more work + TFB_SoundDecoder *decoder = SoundDecoder_Load (contentDir, + last_track_name, 4096, dec_offset, time_stamps[page]); + if (!decoder) + { + log_add (log_Warning, "SpliceTrack(): couldn't load %s", TrackName); + break; + } + dec_offset += (unsigned long)(decoder->length * 1000); + chunks_tail->next = create_SoundChunk (decoder, sound_sample->length); + chunks_tail = chunks_tail->next; + chunks_tail->tag_me = 1; + chunks_tail->track_num = track_count - 1; + chunks_tail->text = pages[page]; + chunks_tail->callback = cb; + // TODO: We may have to tag only one page with a callback + //cb = NULL; + last_sub = chunks_tail; + sound_sample->length += decoder->length; + } + } + } + else + { // Adding a new track + int num_timestamps = 0; + + utf8StringCopy (last_track_name, sizeof (last_track_name), TrackName); + + num_pages = SplitSubPages (TrackText, pages, time_stamps, MAX_PAGES); + if (num_pages == 0) + { + log_add (log_Warning, "SpliceTrack(): Failed to parse sutitles"); + return; + } + // The last page's stamp is a suggested value. The track should + // actually play to the end. + time_stamps[num_pages - 1] = -time_stamps[num_pages - 1]; + + if (no_page_break && track_count) + { + int slen1, slen2; + + slen1 = strlen (last_sub->text); + slen2 = strlen (pages[0]); + last_sub->text = HRealloc (last_sub->text, slen1 + slen2 + 1); + strcpy (last_sub->text + slen1, pages[0]); + HFree (pages[0]); + } + else + track_count++; + + log_add (log_Info, "SpliceTrack(): loading %s", TrackName); + + if (TimeStamp) + { + num_timestamps = GetTimeStamps (TimeStamp, time_stamps) + 1; + if (num_timestamps < num_pages) + { + log_add (log_Warning, "SpliceTrack(): number of timestamps" + " doesn't match number of pages!"); + } + else if (num_timestamps > num_pages) + { // We most likely will get more subtitles appended later + // Set the last page to the rest of the track + time_stamps[num_timestamps - 1] = -100000; + } + } + else + { + num_timestamps = num_pages; + } + + // Reset the offset for the new track + dec_offset = 0; + for (page = 0; page < num_timestamps; ++page) + { + TFB_SoundDecoder *decoder = SoundDecoder_Load (contentDir, + TrackName, 4096, dec_offset, time_stamps[page]); + if (!decoder) + { + log_add (log_Warning, "SpliceTrack(): couldn't load %s", TrackName); + break; + } + + if (!sound_sample) + { + sound_sample = TFB_CreateSoundSample (NULL, 8, &trackCBs); + chunks_head = create_SoundChunk (decoder, 0.0); + chunks_tail = chunks_head; + } + else + { + chunks_tail->next = create_SoundChunk (decoder, sound_sample->length); + chunks_tail = chunks_tail->next; + } + dec_offset += (unsigned long)(decoder->length * 1000); +#if 0 + log_add (log_Debug, "page (%d of %d): %d ts: %d", + page, num_pages, + dec_offset, time_stamps[page]); +#endif + sound_sample->length += decoder->length; + chunks_tail->track_num = track_count - 1; + if (!no_page_break) + { + chunks_tail->tag_me = 1; + if (page < num_pages) + { + chunks_tail->text = pages[page]; + last_sub = chunks_tail; + } + chunks_tail->callback = cb; + // TODO: We may have to tag only one page with a callback + //cb = NULL; + } + no_page_break = 0; + } + } +} + +// This function figures out the chunk that should be playing based on +// 'offset' into the total playing time of all tracks. It then sets +// the speech source's sample to the necessary decoder and seeks the +// decoder to the proper point. +// XXX: This means that whatever speech has already been queued on the +// source will continue playing, so we may need some small timing +// adjustments. It may be simpler to just call PlayStream(). +static void +seek_track (sint32 offset) +{ + TFB_SoundChunk *cur; + TFB_SoundChunk *last_tag = NULL; + + if (!sound_sample) + return; // nothing to recompute + + if (offset < 0) + offset = 0; + else if ((uint32)offset > tracks_length) + offset = tracks_length + 1; + + // Adjusting the stream start time is the only way we can arbitrarily + // seek the stream right now + soundSource[SPEECH_SOURCE].start_time = GetTimeCounter () - offset; + + // Find the chunk that should be playing at this time offset + for (cur = chunks_head; cur && offset >= chunk_end_time (cur); + cur = cur->next) + { + // .. looking for the last callback as we go along + // XXX: this effectively set the last point where Fot is looking at. + // TODO: this should be somehow changed if we implement more + // callbacks, like Melnorme trading, offloading at Starbase, etc. + if (cur->tag_me) + last_tag = cur; + } + + if (cur) + { + cur_chunk = cur; + SoundDecoder_Seek (cur->decoder, (uint32) (((float)offset / ONE_SECOND + - cur->start_time) * 1000)); + sound_sample->decoder = cur->decoder; + + if (cur->tag_me) + last_tag = cur; + if (last_tag) + DoTrackTag (last_tag); + } + else + { // The offset is beyond the length of all tracks + StopStream (SPEECH_SOURCE); + cur_chunk = NULL; + cur_sub_chunk = NULL; + } +} + +static sint32 +get_current_track_pos (void) +{ + sint32 start_time = soundSource[SPEECH_SOURCE].start_time; + sint32 pos = GetTimeCounter () - start_time; + if (pos < 0) + pos = 0; + else if ((uint32)pos > tracks_length) + pos = tracks_length; + return pos; +} + +void +FastReverse_Smooth (void) +{ + sint32 offset; + + if (!sound_sample) + return; // nothing is playing, so.. bye! + + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + + offset = get_current_track_pos (); + offset -= ACCEL_SCROLL_SPEED; + seek_track (offset); + + // Restart the stream in case it ended previously + if (!PlayingStream (SPEECH_SOURCE)) + PlayStream (sound_sample, SPEECH_SOURCE, false, true, false); + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); +} + +void +FastForward_Smooth (void) +{ + sint32 offset; + + if (!sound_sample) + return; // nothing is playing, so.. bye! + + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + + offset = get_current_track_pos (); + offset += ACCEL_SCROLL_SPEED; + seek_track (offset); + + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); +} + +void +FastReverse_Page (void) +{ + TFB_SoundChunk *prev; + + if (!sound_sample) + return; // nothing is playing, so.. bye! + + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + prev = find_prev_page (cur_sub_chunk); + if (prev) + { // Set the chunk to be played + cur_chunk = prev; + cur_sub_chunk = prev; + // Decoder will be set in OnStreamStart() + PlayStream (sound_sample, SPEECH_SOURCE, false, true, true); + } + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); +} + +void +FastForward_Page (void) +{ + TFB_SoundChunk *next; + + if (!sound_sample) + return; // nothing is playing, so.. bye! + + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + next = find_next_page (cur_sub_chunk); + if (next) + { // Set the chunk to be played + cur_chunk = next; + cur_sub_chunk = next; + // Decoder will be set in OnStreamStart() + PlayStream (sound_sample, SPEECH_SOURCE, false, true, true); + } + else + { // End of the tracks (pun intended) + seek_track (tracks_length + 1); + } + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); +} + +// Tells current position of streaming speech in the units +// specified by the caller. +// This is normally called on the ambient_anim_task thread. +int +GetTrackPosition (int in_units) +{ + uint32 offset; + uint32 length = tracks_length; + // detach from the static one, otherwise, we can race for + // it and thus divide by 0 + + if (!sound_sample || length == 0) + return 0; // nothing is playing + + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + offset = get_current_track_pos (); + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + + return in_units * offset / length; +} + +TFB_SoundChunk * +create_SoundChunk (TFB_SoundDecoder *decoder, float start_time) +{ + TFB_SoundChunk *chunk; + chunk = HCalloc (sizeof (*chunk)); + chunk->decoder = decoder; + chunk->start_time = start_time; + return chunk; +} + +void +destroy_SoundChunk_list (TFB_SoundChunk *chunk) +{ + TFB_SoundChunk *next = NULL; + for ( ; chunk; chunk = next) + { + next = chunk->next; + if (chunk->decoder) + SoundDecoder_Free (chunk->decoder); + HFree (chunk->text); + HFree (chunk); + } +} + +// Returns the next chunk with a subtitle +TFB_SoundChunk * +find_next_page (TFB_SoundChunk *cur) +{ + if (!cur) + return NULL; + for (cur = cur->next; cur && !cur->tag_me; cur = cur->next) + ; + return cur; +} + +// Returns the previous chunk with a subtitle. +// cur == 0 is treated as end of the list. +TFB_SoundChunk * +find_prev_page (TFB_SoundChunk *cur) +{ + TFB_SoundChunk *prev; + TFB_SoundChunk *last_valid = chunks_head; + + if (cur == chunks_head) + return cur; // cannot go below the first track + + for (prev = chunks_head; prev && prev != cur; prev = prev->next) + { + if (prev->tag_me) + last_valid = prev; + } + return last_valid; +} + + +// External access to the chunks list +SUBTITLE_REF +GetFirstTrackSubtitle (void) +{ + return chunks_head; +} + +// External access to the chunks list +SUBTITLE_REF +GetNextTrackSubtitle (SUBTITLE_REF LastRef) +{ + if (!LastRef) + return NULL; // enumeration already ended + + return find_next_page (LastRef); +} + +// External access to the chunk subtitles +const UNICODE * +GetTrackSubtitleText (SUBTITLE_REF SubRef) +{ + if (!SubRef) + return NULL; + + return SubRef->text; +} + +// External access to currently active subtitle text +// Returns NULL is none is active +const UNICODE * +GetTrackSubtitle (void) +{ + const UNICODE *cur_sub = NULL; + + if (!sound_sample) + return NULL; // not playing anything + + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + if (cur_sub_chunk) + cur_sub = cur_sub_chunk->text; + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + + return cur_sub; +} diff --git a/src/libs/sound/trackplayer.h b/src/libs/sound/trackplayer.h new file mode 100644 index 0000000..5964e65 --- /dev/null +++ b/src/libs/sound/trackplayer.h @@ -0,0 +1,52 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef TRACKPLAYER_H +#define TRACKPLAYER_H + +#include "libs/compiler.h" +#include "libs/callback.h" + +#define ACCEL_SCROLL_SPEED 300 + +extern void PlayTrack (void); +extern void StopTrack (void); +extern void JumpTrack (void); +extern void PauseTrack (void); +extern void ResumeTrack (void); +extern COUNT PlayingTrack (void); + +extern void FastReverse_Smooth (void); +extern void FastForward_Smooth (void); +extern void FastReverse_Page (void); +extern void FastForward_Page (void); + +extern void SpliceTrack (UNICODE *filespec, UNICODE *textspec, UNICODE *TimeStamp, CallbackFunction cb); +extern void SpliceMultiTrack (UNICODE *TrackNames[], UNICODE *TrackText); + +extern int GetTrackPosition (int in_units); + +typedef struct tfb_soundchunk *SUBTITLE_REF; + +extern SUBTITLE_REF GetFirstTrackSubtitle (void); +extern SUBTITLE_REF GetNextTrackSubtitle (SUBTITLE_REF LastRef); +extern const UNICODE *GetTrackSubtitleText (SUBTITLE_REF SubRef); + +extern const UNICODE *GetTrackSubtitle (void); + +#endif diff --git a/src/libs/strings/Makeinfo b/src/libs/strings/Makeinfo new file mode 100644 index 0000000..f1e4a9e --- /dev/null +++ b/src/libs/strings/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="getstr.c sfileins.c sresins.c stringhashtable.c strings.c unicode.c" +uqm_HFILES="stringhashtable.c strintrn.h" diff --git a/src/libs/strings/getstr.c b/src/libs/strings/getstr.c new file mode 100644 index 0000000..ba428cf --- /dev/null +++ b/src/libs/strings/getstr.c @@ -0,0 +1,643 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "options.h" +#include "strintrn.h" +#include "libs/graphics/gfx_common.h" +#include "libs/reslib.h" +#include "libs/log.h" +#include "libs/memlib.h" + + +#define MAX_STRINGS 2048 +#define POOL_SIZE 4096 + +static void +dword_convert (DWORD *dword_array, COUNT num_dwords) +{ + BYTE *p = (BYTE*)dword_array; + + do + { + *dword_array++ = MAKE_DWORD ( + MAKE_WORD (p[3], p[2]), + MAKE_WORD (p[1], p[0]) + ); + p += 4; + } while (--num_dwords); +} + +static STRING +set_strtab_entry (STRING_TABLE_DESC *strtab, int index, const char *value, + int len) +{ + STRING str = &strtab->strings[index]; + + if (str->data) + { + HFree (str->data); + str->data = NULL; + str->length = 0; + } + if (len) + { + str->data = HMalloc (len); + str->length = len; + memcpy (str->data, value, len); + } + return str; +} + +static void +copy_strings_to_strtab (STRING_TABLE_DESC *strtab, size_t firstIndex, + size_t count, const char *data, const DWORD *lens) +{ + size_t stringI; + const char *off = data; + + for (stringI = 0; stringI < count; stringI++) + { + set_strtab_entry(strtab, firstIndex + stringI, + off, lens[stringI]); + off += lens[stringI]; + } +} + +// Check whether a buffer has a certain minimum size, and enlarge it +// if necessary. +// buf: pointer to the pointer to the buffer. May be NULL. +// curSize: pointer to the current size (multiple of 'increment') +// minSize: required minimum size +// increment: size to increment the buffer with if necessary +// On success, *buf and *curSize are updated. On failure, they are +// unchanged. +// returns FALSE if and only if the buffer needs to be enlarged but +// memory allocation failed. +static BOOLEAN +ensureBufSize (char **buf, size_t *curSize, size_t minSize, size_t increment) +{ + char *newBuf; + size_t newSize; + + if (minSize <= *curSize) + { + // Buffer is large enough as it is. + return TRUE; + } + + newSize = ((minSize + (increment - 1)) / increment) * increment; + // Smallest multiple of 'increment' larger or equal to minSize. + newBuf = HRealloc (*buf, newSize); + if (newBuf == NULL) + return FALSE; + + // Success + *buf = newBuf; + *curSize = newSize; + return TRUE; +} + +void +_GetConversationData (const char *path, RESOURCE_DATA *resdata) +{ + unsigned long dataLen; + void *result; + int stringI; + int path_len; + int num_data_sets; + DWORD opos; + + char *namedata = NULL; + // Contains the names (indexes) of the dialogs. + DWORD nlen[MAX_STRINGS]; + // Length of each of the names. + DWORD NameOffs; + size_t tot_name_size; + + char *strdata = NULL; + // Contains the dialog strings. + DWORD slen[MAX_STRINGS]; + // Length of each of the dialog strings. + DWORD StringOffs; + size_t tot_string_size; + + char *clipdata = NULL; + // Contains the file names of the speech files. + DWORD clen[MAX_STRINGS]; + // Length of each of the speech file names. + DWORD ClipOffs; + size_t tot_clip_size; + + char *ts_data = NULL; + // Contains the timestamp data for synching the text with the + // speech. + DWORD tslen[MAX_STRINGS]; + // Length of each of the timestamp strings. + DWORD TSOffs; + size_t tot_ts_size = 0; + + char CurrentLine[1024]; + char paths[1024]; + char *clip_path; + char *ts_path; + + uio_Stream *fp = NULL; + uio_Stream *timestamp_fp = NULL; + StringHashTable_HashTable *nameHashTable = NULL; + // Hash table of string names (such as "GLAD_WHEN_YOU_COME_BACK") + // to a STRING. + + /* Parse out the conversation components. */ + strncpy (paths, path, 1023); + paths[1023] = '\0'; + clip_path = strchr (paths, ':'); + if (clip_path == NULL) + { + ts_path = NULL; + } + else + { + *clip_path = '\0'; + clip_path++; + + ts_path = strchr (clip_path, ':'); + if (ts_path != NULL) + { + *ts_path = '\0'; + ts_path++; + } + } + + fp = res_OpenResFile (contentDir, paths, "rb"); + if (fp == NULL) + { + log_add (log_Warning, "Warning: Can't open '%s'", paths); + resdata->ptr = NULL; + return; + } + + dataLen = LengthResFile (fp); + log_add (log_Info, "\t'%s' -- conversation phrases -- %lu bytes", paths, + dataLen); + if (clip_path) + log_add (log_Info, "\t'%s' -- voice clip directory", clip_path); + else + log_add (log_Info, "\tNo associated voice clips"); + if (ts_path) + log_add (log_Info, "\t'%s' -- timestamps", ts_path); + else + log_add (log_Info, "\tNo associated timestamp file"); + + if (dataLen == 0) + { + log_add (log_Warning, "Warning: Trying to load empty file '%s'.", + path); + goto err; + } + + tot_string_size = POOL_SIZE; + strdata = HMalloc (tot_string_size); + if (strdata == 0) + goto err; + + tot_name_size = POOL_SIZE; + namedata = HMalloc (tot_name_size); + if (namedata == 0) + goto err; + + tot_clip_size = POOL_SIZE; + clipdata = HMalloc (tot_clip_size); + if (clipdata == 0) + goto err; + ts_data = NULL; + + nameHashTable = StringHashTable_newHashTable( + NULL, NULL, NULL, NULL, NULL, 0, 0.85, 0.9); + if (nameHashTable == NULL) + goto err; + + path_len = clip_path ? strlen (clip_path) : 0; + + if (ts_path) + { + timestamp_fp = uio_fopen (contentDir, ts_path, "rb"); + if (timestamp_fp != NULL) + { + tot_ts_size = POOL_SIZE; + ts_data = HMalloc (tot_ts_size); + if (ts_data == 0) + goto err; + } + } + + opos = uio_ftell (fp); + stringI = -1; + NameOffs = 0; + StringOffs = 0; + ClipOffs = 0; + TSOffs = 0; + for (;;) + { + int l; + + if (uio_fgets (CurrentLine, sizeof (CurrentLine), fp) == NULL) + { + // EOF or read error. + break; + } + + if (stringI >= MAX_STRINGS - 1) + { + // Too many strings. + break; + } + + if (CurrentLine[0] == '#') + { + // String header, of the following form: + // #(GLAD_WHEN_YOU_COME_BACK) commander-000.ogg + char CopyLine[1024]; + char *name; + char *ts; + + strcpy (CopyLine, CurrentLine); + name = strtok (&CopyLine[1], "()"); + if (name) + { + if (stringI >= 0) + { + while (slen[stringI] > 1 && + (strdata[StringOffs - 2] == '\n' || + strdata[StringOffs - 2] == '\r')) + { + --slen[stringI]; + --StringOffs; + strdata[StringOffs - 1] = '\0'; + } + } + + slen[++stringI] = 0; + + // Store the string name. + l = strlen (name) + 1; + if (!ensureBufSize (&namedata, &tot_name_size, + NameOffs + l, POOL_SIZE)) + goto err; + strcpy (&namedata[NameOffs], name); + NameOffs += l; + nlen[stringI] = l; + + // now lets check for timestamp data + if (timestamp_fp) + { + // We have a time stamp file. + char TimeStampLine[1024]; + char *tsptr; + BOOLEAN ts_ok = FALSE; + uio_fgets (TimeStampLine, sizeof (TimeStampLine), timestamp_fp); + if (TimeStampLine[0] == '#') + { + // Line is of the following form: + // #(GIVE_FUEL_AGAIN) 3304,3255 + tslen[stringI] = 0; + tsptr = strstr (TimeStampLine, name); + if (tsptr) + { + tsptr += strlen(name) + 1; + ts_ok = TRUE; + while (! strcspn(tsptr," \t\r\n") && *tsptr) + tsptr++; + if (*tsptr) + { + l = strlen (tsptr) + 1; + if (!ensureBufSize (&ts_data, &tot_ts_size, TSOffs + l, + POOL_SIZE)) + goto err; + + strcpy (&ts_data[TSOffs], tsptr); + TSOffs += l; + tslen[stringI] = l; + } + } + } + if (!ts_ok) + { + // timestamp data is invalid, remove all of it + log_add (log_Warning, "Invalid timestamp data " + "for '%s'. Disabling timestamps", name); + HFree (ts_data); + ts_data = NULL; + uio_fclose (timestamp_fp); + timestamp_fp = NULL; + TSOffs = 0; + } + } + clen[stringI] = 0; + ts = strtok (NULL, " \t\r\n)"); + if (ts) + { + l = path_len + strlen (ts) + 1; + if (!ensureBufSize (&clipdata, &tot_clip_size, + ClipOffs + l, POOL_SIZE)) + goto err; + + if (clip_path) + strcpy (&clipdata[ClipOffs], clip_path); + strcpy (&clipdata[ClipOffs + path_len], ts); + ClipOffs += l; + clen[stringI] = l; + } + } + } + else if (stringI >= 0) + { + char *s; + l = strlen (CurrentLine) + 1; + + if (!ensureBufSize (&strdata, &tot_string_size, StringOffs + l, + POOL_SIZE)) + goto err; + + if (slen[stringI]) + { + --slen[stringI]; + --StringOffs; + } + s = &strdata[StringOffs]; + slen[stringI] += l; + StringOffs += l; + + strcpy (s, CurrentLine); + } + + if ((int)uio_ftell (fp) - (int)opos >= (int)dataLen) + break; + } + if (stringI >= 0) + { + while (slen[stringI] > 1 && (strdata[StringOffs - 2] == '\n' + || strdata[StringOffs - 2] == '\r')) + { + --slen[stringI]; + --StringOffs; + strdata[StringOffs - 1] = '\0'; + } + } + + if (timestamp_fp) + uio_fclose (timestamp_fp); + + result = NULL; + num_data_sets = (ClipOffs ? 1 : 0) + (TSOffs ? 1 : 0) + 1; + if (++stringI) + { + int flags = 0; + int stringCount = stringI; + + if (ClipOffs) + flags |= HAS_SOUND_CLIPS; + if (TSOffs) + flags |= HAS_TIMESTAMP; + flags |= HAS_NAMEINDEX; + + result = AllocStringTable (stringCount, flags); + if (result) + { + // Copy all the gatherered data in a STRING_TABLE + STRING_TABLE_DESC *lpST = (STRING_TABLE) result; + STRING str; + stringI = 0; + + // Store the dialog string. + copy_strings_to_strtab ( + lpST, stringI, stringCount, strdata, slen); + stringI += stringCount; + + // Store the dialog names. + copy_strings_to_strtab ( + lpST, stringI, stringCount, namedata, nlen); + stringI += stringCount; + + // Store sound clip file names. + if (lpST->flags & HAS_SOUND_CLIPS) + { + copy_strings_to_strtab ( + lpST, stringI, stringCount, clipdata, clen); + stringI += stringCount; + } + + // Store time stamp data. + if (lpST->flags & HAS_TIMESTAMP) + { + copy_strings_to_strtab ( + lpST, stringI, stringCount, ts_data, tslen); + //stringI += stringCount; + } + + // Store the STRING in the hash table indexed by the dialog + // name. + str = &lpST->strings[stringCount]; + for (stringI = 0; stringI < stringCount; stringI++) + { + StringHashTable_add (nameHashTable, str[stringI].data, + &str[stringI]); + } + + lpST->nameIndex = nameHashTable; + } + } + HFree (strdata); + if (clipdata != NULL) + HFree (clipdata); + if (ts_data != NULL) + HFree (ts_data); + + resdata->ptr = result; + return; + +err: + if (nameHashTable != NULL) + StringHashTable_deleteHashTable (nameHashTable); + if (ts_data != NULL) + HFree (ts_data); + if (clipdata != NULL) + HFree (clipdata); + if (strdata != NULL) + HFree (strdata); + res_CloseResFile (fp); + resdata->ptr = NULL; +} + +void * +_GetStringData (uio_Stream *fp, DWORD length) +{ + void *result; + + int stringI; + DWORD opos; + DWORD slen[MAX_STRINGS]; + DWORD StringOffs; + size_t tot_string_size; + char CurrentLine[1024]; + char *strdata = NULL; + + tot_string_size = POOL_SIZE; + strdata = HMalloc (tot_string_size); + if (strdata == 0) + goto err; + + opos = uio_ftell (fp); + stringI = -1; + StringOffs = 0; + for (;;) + { + int l; + + if (uio_fgets (CurrentLine, sizeof (CurrentLine), fp) == NULL) + { + // EOF or read error. + break; + } + + if (stringI >= MAX_STRINGS - 1) + { + // Too many strings. + break; + } + + if (CurrentLine[0] == '#') + { + char CopyLine[1024]; + char *s; + + strcpy (CopyLine, CurrentLine); + s = strtok (&CopyLine[1], "()"); + if (s) + { + if (stringI >= 0) + { + while (slen[stringI] > 1 && + (strdata[StringOffs - 2] == '\n' || + strdata[StringOffs - 2] == '\r')) + { + --slen[stringI]; + --StringOffs; + strdata[StringOffs - 1] = '\0'; + } + } + + slen[++stringI] = 0; + } + } + else if (stringI >= 0) + { + char *s; + l = strlen (CurrentLine) + 1; + + if (!ensureBufSize (&strdata, &tot_string_size, StringOffs + l, + POOL_SIZE)) + goto err; + + if (slen[stringI]) + { + --slen[stringI]; + --StringOffs; + } + s = &strdata[StringOffs]; + slen[stringI] += l; + StringOffs += l; + + strcpy (s, CurrentLine); + } + + if ((int)uio_ftell (fp) - (int)opos >= (int)length) + break; + } + if (stringI >= 0) + { + while (slen[stringI] > 1 && (strdata[StringOffs - 2] == '\n' + || strdata[StringOffs - 2] == '\r')) + { + --slen[stringI]; + --StringOffs; + strdata[StringOffs - 1] = '\0'; + } + } + + result = NULL; + if (++stringI) + { + int flags = 0; + int stringCount = stringI; + + result = AllocStringTable (stringI, flags); + if (result) + { + STRING_TABLE_DESC *lpST = (STRING_TABLE) result; + copy_strings_to_strtab (lpST, 0, stringCount, strdata, slen); + } + } + HFree (strdata); + + return result; + +err: + if (strdata != NULL) + HFree (strdata); + return 0; +} + + +void * +_GetBinaryTableData (uio_Stream *fp, DWORD length) +{ + void *result; + result = GetResourceData (fp, length); + + if (result) + { + DWORD *fileData; + STRING_TABLE lpST; + + fileData = (DWORD *)result; + + dword_convert (fileData, 1); /* Length */ + + lpST = AllocStringTable (fileData[0], 0); + if (lpST) + { + int i, size; + BYTE *stringptr; + + size = lpST->size; + + dword_convert (fileData+1, size + 1); + stringptr = (BYTE *)(fileData + 2 + size + fileData[1]); + for (i = 0; i < size; i++) + { + set_strtab_entry (lpST, i, (char *)stringptr, fileData[2+i]); + stringptr += fileData[2+i]; + } + } + HFree (result); + result = lpST; + } + + return result; +} + diff --git a/src/libs/strings/sfileins.c b/src/libs/strings/sfileins.c new file mode 100644 index 0000000..6ff4422 --- /dev/null +++ b/src/libs/strings/sfileins.c @@ -0,0 +1,50 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "port.h" +#include "strintrn.h" +#include "libs/uio.h" +#include "libs/reslib.h" + + +STRING_TABLE +LoadStringTableFile (uio_DirHandle *dir, const char *fileName) +{ + uio_Stream *fp; + + // FIXME: this theoretically needs a mechanism to prevent races + if (_cur_resfile_name) + // something else is loading resources atm + return 0; + + fp = res_OpenResFile (dir, fileName, "rb"); + if (fp) + { + STRING_TABLE data; + + _cur_resfile_name = fileName; + data = (STRING_TABLE) _GetStringData (fp, LengthResFile (fp)); + _cur_resfile_name = 0; + res_CloseResFile (fp); + + return data; + } + + return (0); +} + diff --git a/src/libs/strings/sresins.c b/src/libs/strings/sresins.c new file mode 100644 index 0000000..af2de79 --- /dev/null +++ b/src/libs/strings/sresins.c @@ -0,0 +1,55 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "strintrn.h" + +static void +GetStringTableFileData (const char *pathname, RESOURCE_DATA *resdata) +{ + resdata->ptr = LoadResourceFromPath (pathname, _GetStringData); +} + +static void +GetBinaryTableFileData (const char *pathname, RESOURCE_DATA *resdata) +{ + resdata->ptr = LoadResourceFromPath (pathname, _GetBinaryTableData); +} + +BOOLEAN +InstallStringTableResType (void) +{ + InstallResTypeVectors ("STRTAB", GetStringTableFileData, FreeResourceData, NULL); + InstallResTypeVectors ("BINTAB", GetBinaryTableFileData, FreeResourceData, NULL); + InstallResTypeVectors ("CONVERSATION", _GetConversationData, FreeResourceData, NULL); + return TRUE; +} + +STRING_TABLE +LoadStringTableInstance (RESOURCE res) +{ + void *data; + + data = res_GetResource (res); + if (data) + { + res_DetachResource (res); + } + + return (STRING_TABLE)data; +} + diff --git a/src/libs/strings/stringhashtable.c b/src/libs/strings/stringhashtable.c new file mode 100644 index 0000000..ac4b4f4 --- /dev/null +++ b/src/libs/strings/stringhashtable.c @@ -0,0 +1,67 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#define HASHTABLE_INTERNAL +#include "stringhashtable.h" +#include "types.h" +#include "libs/misc.h" + // For unconst() +#include "libs/uio/uioport.h" + +static inline uio_uint32 StringHashTable_hash( + StringHashTable_HashTable *hashTable, const char *string); +static inline uio_bool StringHashTable_equal( + StringHashTable_HashTable *hashTable, + const char *key1, const char *key2); +static inline char *StringHashTable_copy( + StringHashTable_HashTable *hashTable, const char *key); + +#include "libs/uio/hashtable.c" + + +static inline uio_uint32 +StringHashTable_hash(StringHashTable_HashTable *hashTable, const char *key) { + uio_uint32 hash; + + (void) hashTable; + // Rotating hash, variation of something on the web which + // wasn't original itself. + hash = 0; + // Hash was on that web page initialised as the length, + // but that isn't known at this time. + while (*key != '\0') { + hash = (hash << 4) ^ (hash >> 28) ^ *key; + key++; + } + return hash ^ (hash >> 10) ^ (hash >> 20); +} + +static inline uio_bool +StringHashTable_equal(StringHashTable_HashTable *hashTable, + const char *key1, const char *key2) { + (void) hashTable; + return strcmp(key1, key2) == 0; +} + +static inline char * +StringHashTable_copy(StringHashTable_HashTable *hashTable, + const char *key) { + (void) hashTable; + return unconst(key); +} + diff --git a/src/libs/strings/stringhashtable.h b/src/libs/strings/stringhashtable.h new file mode 100644 index 0000000..36f9e47 --- /dev/null +++ b/src/libs/strings/stringhashtable.h @@ -0,0 +1,43 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _STRINGHASHTABLE_H +#define _STRINGHASHTABLE_H + +// HashTable from 'char *' to STRING. +// We don't actually copy the index, which means that the caller is +// responsible for keeping them unchanged during the time that it is used in +// the hash table. + +#include "libs/strlib.h" + +#define HASHTABLE_(identifier) StringHashTable ## _ ## identifier +typedef char HASHTABLE_(Key); +typedef STRING_TABLE_ENTRY_DESC HASHTABLE_(Value); +#define StringHashTable_HASH StringHashTable_hash +#define StringHashTable_EQUAL StringHashTable_equal +#define StringHashTable_COPY StringHashTable_copy +#define StringHashTable_FREEKEY(hashTable, key) \ + ((void) (hashTable), (void) (key)) +#define StringHashTable_FREEVALUE(hashTable, value) \ + ((void) (hashTable), (void) (value)) + +#include "libs/uio/hashtable.h" + + +#endif /* _STRINGHASHTABLE_H */ diff --git a/src/libs/strings/strings.c b/src/libs/strings/strings.c new file mode 100644 index 0000000..7f8d5e4 --- /dev/null +++ b/src/libs/strings/strings.c @@ -0,0 +1,347 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "strintrn.h" +#include "libs/memlib.h" + +STRING_TABLE +AllocStringTable (int num_entries, int flags) +{ + STRING_TABLE strtab = HMalloc (sizeof (STRING_TABLE_DESC)); + int i, multiplier = 1; + + if (flags & HAS_NAMEINDEX) + { + multiplier++; + } + if (flags & HAS_SOUND_CLIPS) + { + multiplier++; + } + if (flags & HAS_TIMESTAMP) + { + multiplier++; + } + strtab->flags = flags; + strtab->size = num_entries; + num_entries *= multiplier; + strtab->strings = HMalloc (sizeof (STRING_TABLE_ENTRY_DESC) * num_entries); + for (i = 0; i < num_entries; i++) + { + strtab->strings[i].data = NULL; + strtab->strings[i].length = 0; + strtab->strings[i].parent = strtab; + strtab->strings[i].index = i; + } + strtab->nameIndex = NULL; + return strtab; +} + +void +FreeStringTable (STRING_TABLE strtab) +{ + int i, multiplier = 1; + + if (strtab == NULL) + { + return; + } + + if (strtab->flags & HAS_SOUND_CLIPS) + { + multiplier++; + } + if (strtab->flags & HAS_TIMESTAMP) + { + multiplier++; + } + + for (i = 0; i < strtab->size * multiplier; i++) + { + if (strtab->strings[i].data != NULL) + { + HFree (strtab->strings[i].data); + } + } + + HFree (strtab->strings); + HFree (strtab); +} + +BOOLEAN +DestroyStringTable (STRING_TABLE StringTable) +{ + FreeStringTable (StringTable); + return TRUE; +} + +STRING +CaptureStringTable (STRING_TABLE StringTable) +{ + if ((StringTable != 0) && (StringTable->size > 0)) + { + return StringTable->strings; + } + + return NULL; +} + +STRING_TABLE +ReleaseStringTable (STRING String) +{ + STRING_TABLE StringTable; + + StringTable = GetStringTable (String); + + return (StringTable); +} + +STRING_TABLE +GetStringTable (STRING String) +{ + if (String && String->parent) + { + return String->parent; + } + return NULL; +} + +COUNT +GetStringTableCount (STRING String) +{ + if (String && String->parent) + { + return String->parent->size; + } + return 0; +} + +COUNT +GetStringTableIndex (STRING String) +{ + if (String) + { + return String->index; + } + return 0; +} + +STRING +SetAbsStringTableIndex (STRING String, COUNT StringTableIndex) +{ + STRING_TABLE StringTablePtr; + + if (!String) + return NULL; + + StringTablePtr = String->parent; + + if (StringTablePtr == NULL) + { + String = NULL; + } + else + { + StringTableIndex = StringTableIndex % StringTablePtr->size; + String = &StringTablePtr->strings[StringTableIndex]; + } + + return (String); +} + +STRING +SetRelStringTableIndex (STRING String, SIZE StringTableOffs) +{ + STRING_TABLE StringTablePtr; + + if (!String) + return NULL; + + StringTablePtr = String->parent; + + if (StringTablePtr == NULL) + { + String = NULL; + } + else + { + COUNT StringTableIndex; + + while (StringTableOffs < 0) + StringTableOffs += StringTablePtr->size; + StringTableIndex = (String->index + StringTableOffs) + % StringTablePtr->size; + + String = &StringTablePtr->strings[StringTableIndex]; + } + + return (String); +} + +COUNT +GetStringLength (STRING String) +{ + if (String == NULL) + { + return 0; + } + return utf8StringCountN(String->data, String->data + String->length); +} + +COUNT +GetStringLengthBin (STRING String) +{ + if (String == NULL) + { + return 0; + } + return String->length; +} + +STRINGPTR +GetStringName (STRING String) +{ + STRING_TABLE StringTablePtr; + COUNT StringIndex; + + if (String == NULL) + { + return NULL; + } + + StringTablePtr = String->parent; + if (StringTablePtr == NULL) + { + return NULL; + } + + StringIndex = String->index; + + if (!(StringTablePtr->flags & HAS_NAMEINDEX)) + { + return NULL; + } + StringIndex += StringTablePtr->size; + + String = &StringTablePtr->strings[StringIndex]; + if (String->length == 0) + { + return NULL; + } + + return String->data; +} + +STRINGPTR +GetStringSoundClip (STRING String) +{ + STRING_TABLE StringTablePtr; + COUNT StringIndex; + + if (String == NULL) + { + return NULL; + } + + StringTablePtr = String->parent; + if (StringTablePtr == NULL) + { + return NULL; + } + + StringIndex = String->index; + if (!(StringTablePtr->flags & HAS_SOUND_CLIPS)) + { + return NULL; + } + StringIndex += StringTablePtr->size; + + if (StringTablePtr->flags & HAS_NAMEINDEX) + { + StringIndex += StringTablePtr->size; + } + + String = &StringTablePtr->strings[StringIndex]; + if (String->length == 0) + { + return NULL; + } + + return String->data; +} + +STRINGPTR +GetStringTimeStamp (STRING String) +{ + STRING_TABLE StringTablePtr; + COUNT StringIndex; + + if (String == NULL) + { + return NULL; + } + + StringTablePtr = String->parent; + if (StringTablePtr == NULL) + { + return NULL; + } + + StringIndex = String->index; + if (!(StringTablePtr->flags & HAS_TIMESTAMP)) + { + return NULL; + } + StringIndex += StringTablePtr->size; + + if (StringTablePtr->flags & HAS_NAMEINDEX) + { + StringIndex += StringTablePtr->size; + } + + if (StringTablePtr->flags & HAS_SOUND_CLIPS) + { + StringIndex += StringTablePtr->size; + } + + String = &StringTablePtr->strings[StringIndex]; + if (String->length == 0) + { + return NULL; + } + + return String->data; +} + +STRINGPTR +GetStringAddress (STRING String) +{ + if (String == NULL) + { + return NULL; + } + return String->data; +} + +STRING +GetStringByName (STRING_TABLE StringTable, const char *index) +{ + return (STRING) StringHashTable_find (StringTable->nameIndex, index); +} + + diff --git a/src/libs/strings/strintrn.h b/src/libs/strings/strintrn.h new file mode 100644 index 0000000..0c41fb0 --- /dev/null +++ b/src/libs/strings/strintrn.h @@ -0,0 +1,56 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_STRINGS_STRINTRN_H_ +#define LIBS_STRINGS_STRINTRN_H_ + +#include <stdio.h> +#include <string.h> +#include "libs/strlib.h" +#include "libs/reslib.h" +#include "stringhashtable.h" + +struct string_table_entry +{ + STRINGPTR data; + int length; /* Internal NULs are allowed */ + int index; + struct string_table *parent; +}; + +struct string_table +{ + unsigned short flags; + int size; + STRING_TABLE_ENTRY_DESC *strings; + StringHashTable_HashTable *nameIndex; +}; + +#define HAS_SOUND_CLIPS (1 << 0) +#define HAS_TIMESTAMP (1 << 1) +#define HAS_NAMEINDEX (1 << 2) + +STRING_TABLE AllocStringTable (int num_entries, int flags); +void FreeStringTable (STRING_TABLE strtab); + +void *_GetStringData (uio_Stream *fp, DWORD length); +void *_GetBinaryTableData (uio_Stream *fp, DWORD length); +void _GetConversationData (const char *path, RESOURCE_DATA *resdata); + +#endif /* LIBS_STRINGS_STRINTRN_H_ */ + diff --git a/src/libs/strings/unicode.c b/src/libs/strings/unicode.c new file mode 100644 index 0000000..1750507 --- /dev/null +++ b/src/libs/strings/unicode.c @@ -0,0 +1,541 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "port.h" + +#define UNICODE_INTERNAL +#include "libs/unicode.h" + +#include <ctype.h> +#include <stddef.h> +#include <stdio.h> +#include <string.h> +#include "libs/log.h" +#include "libs/misc.h" + + +// Resynchronise (skip everything starting with 0x10xxxxxx): +static inline void +resyncUTF8(const unsigned char **ptr) { + while ((**ptr & 0xc0) == 0x80) + (*ptr)++; +} + +// Get one character from a UTF-8 encoded string. +// *ptr will point to the start of the next character. +// Returns 0 if the encoding is bad. This can be distinguished from the +// '\0' character by checking whether **ptr == '\0' before calling this +// function. +UniChar +getCharFromString(const unsigned char **ptr) { + UniChar result; + + if (**ptr < 0x80) { + // 0xxxxxxx, regular ASCII + result = **ptr; + (*ptr)++; + + return result; + } + + if ((**ptr & 0xe0) == 0xc0) { + // 110xxxxx; 10xxxxxx must follow + // Value between 0x00000080 and 0x000007ff (inclusive) + result = **ptr & 0x1f; + (*ptr)++; + + if ((**ptr & 0xc0) != 0x80) + goto err; + result = (result << 6) | ((**ptr) & 0x3f); + (*ptr)++; + + if (result < 0x00000080) { + // invalid encoding - must reject + goto err; + } + return result; + } + + if ((**ptr & 0xf0) == 0xe0) { + // 1110xxxx; 10xxxxxx 10xxxxxx must follow + // Value between 0x00000800 and 0x0000ffff (inclusive) + result = **ptr & 0x0f; + (*ptr)++; + + if ((**ptr & 0xc0) != 0x80) + goto err; + result = (result << 6) | ((**ptr) & 0x3f); + (*ptr)++; + + if ((**ptr & 0xc0) != 0x80) + goto err; + result = (result << 6) | ((**ptr) & 0x3f); + (*ptr)++; + + if (result < 0x00000800) { + // invalid encoding - must reject + goto err; + } + return result; + } + + if ((**ptr & 0xf8) == 0xf0) { + // 11110xxx; 10xxxxxx 10xxxxxx 10xxxxxx must follow + // Value between 0x00010000 and 0x0010ffff (inclusive) + result = **ptr & 0x07; + (*ptr)++; + + if ((**ptr & 0xc0) != 0x80) + goto err; + result = (result << 6) | ((**ptr) & 0x3f); + (*ptr)++; + + if ((**ptr & 0xc0) != 0x80) + goto err; + result = (result << 6) | ((**ptr) & 0x3f); + (*ptr)++; + + if ((**ptr & 0xc0) != 0x80) + goto err; + result = (result << 6) | ((**ptr) & 0x3f); + (*ptr)++; + + if (result < 0x00010000) { + // invalid encoding - must reject + goto err; + } + return result; + } + +err: + log_add(log_Warning, "Warning: Invalid UTF8 sequence."); + + // Resynchronise (skip everything starting with 0x10xxxxxx): + resyncUTF8(ptr); + + return 0; +} + +UniChar +getCharFromStringN(const unsigned char **ptr, const unsigned char *end) { + size_t numBytes; + + if (*ptr == end) + goto err; + + if (**ptr < 0x80) { + numBytes = 1; + } else if ((**ptr & 0xe0) == 0xc0) { + numBytes = 2; + } else if ((**ptr & 0xf0) == 0xe0) { + numBytes = 3; + } else if ((**ptr & 0xf8) == 0xf0) { + numBytes = 4; + } else + goto err; + + if (*ptr + numBytes > end) + goto err; + + return getCharFromString(ptr); + +err: + *ptr = end; + return 0; +} + +// Get one line from a string. +// A line is terminated with either CRLF (DOS/Windows), +// LF (Unix, MacOS X), or CR (old MacOS). +// The end of the string is reached when **startNext == '\0'. +// NULL is returned if the string is not valid UTF8. In this case +// *end points to the first invalid character (or the character before if +// it was a LF), and *startNext to the start of the next (possibly invalid +// too) character. +unsigned char * +getLineFromString(const unsigned char *start, const unsigned char **end, + const unsigned char **startNext) { + const unsigned char *ptr = start; + const unsigned char *lastPtr; + UniChar ch; + + // Search for the first newline. + for (;;) { + if (*ptr == '\0') { + *end = ptr; + *startNext = ptr; + return (unsigned char *) unconst(start); + } + lastPtr = ptr; + ch = getCharFromString(&ptr); + if (ch == '\0') { + // Bad string + *end = lastPtr; + *startNext = ptr; + return NULL; + } + if (ch == '\n') { + *end = lastPtr; + if (*ptr == '\0'){ + // LF at the end of the string. + *startNext = ptr; + return (unsigned char *) unconst(start); + } + ch = getCharFromString(&ptr); + if (ch == '\0') { + // Bad string + return NULL; + } + if (ch == '\r') { + // LFCR + *startNext = ptr; + } else { + // LF + *startNext = *end; + } + return (unsigned char *) unconst(start); + } else if (ch == '\r') { + *end = lastPtr; + *startNext = ptr; + return (unsigned char *) unconst(start); + } // else: a normal character + } +} + +size_t +utf8StringCount(const unsigned char *start) { + size_t count = 0; + UniChar ch; + + for (;;) { + ch = getCharFromString(&start); + if (ch == '\0') + return count; + count++; + } +} + +size_t +utf8StringCountN(const unsigned char *start, const unsigned char *end) { + size_t count = 0; + UniChar ch; + + for (;;) { + ch = getCharFromStringN(&start, end); + if (ch == '\0') + return count; + count++; + } +} + +// Locates a unicode character (ch) in a UTF-8 string (pStr) +// returns the char positions when found +// -1 when not found +int +utf8StringPos (const unsigned char *pStr, UniChar ch) +{ + int pos; + + for (pos = 0; *pStr != '\0'; ++pos) + { + if (getCharFromString (&pStr) == ch) + return pos; + } + + if (ch == '\0' && *pStr == '\0') + return pos; + + return -1; +} + +// Safe version of strcpy(), somewhat analogous to strncpy() +// except it guarantees a 0-term when size > 0 +// when size == 0, returns NULL +// BUG: this may result in the last character being only partially in the +// buffer +unsigned char * +utf8StringCopy (unsigned char *dst, size_t size, const unsigned char *src) +{ + if (size == 0) + return 0; + + strncpy ((char *) dst, (const char *) src, size); + dst[size - 1] = '\0'; + + return dst; +} + +// TODO: this is not implemented with respect to collating order +int +utf8StringCompare (const unsigned char *str1, const unsigned char *str2) +{ +#if 0 + // UniChar comparing version + UniChar ch1; + UniChar ch2; + + for (;;) + { + int cmp; + + ch1 = getCharFromString(&str1); + ch2 = getCharFromString(&str2); + if (ch1 == '\0' || ch2 == '\0') + break; + + cmp = utf8CompareChar (ch1, ch2); + if (cmp != 0) + return cmp; + } + + if (ch1 != '\0') + { + // ch2 == '\0' + // str2 ends, str1 continues + return 1; + } + + if (ch2 != '\0') + { + // ch1 == '\0' + // str1 ends, str2 continues + return -1; + } + + // ch1 == '\0' && ch2 == '\0'. + // Strings match completely. + return 0; +#else + // this will do for now + return strcmp ((const char *) str1, (const char *) str2); +#endif +} + +unsigned char * +skipUTF8Chars(const unsigned char *ptr, size_t num) { + UniChar ch; + const unsigned char *oldPtr; + + while (num--) { + oldPtr = ptr; + ch = getCharFromString(&ptr); + if (ch == '\0') + return (unsigned char *) unconst(oldPtr); + } + return (unsigned char *) unconst(ptr); +} + +// Decodes a UTF-8 string (start) into a unicode character string (wstr) +// returns number of chars decoded and stored, not counting 0-term +// any chars that do not fit are truncated +// wide string term 0 is always appended, unless the destination +// buffer is 0 chars long +size_t +getUniCharFromStringN(UniChar *wstr, size_t maxcount, + const unsigned char *start, const unsigned char *end) +{ + UniChar *next; + + if (maxcount == 0) + return 0; + + // always leave room for 0-term + --maxcount; + + for (next = wstr; maxcount > 0; ++next, --maxcount) + { + *next = getCharFromStringN(&start, end); + if (*next == 0) + break; + } + + *next = 0; // term + + return next - wstr; +} + +// See getStringFromWideN() for functionality +// the only difference is that the source string (start) length is +// calculated by searching for 0-term +size_t +getUniCharFromString(UniChar *wstr, size_t maxcount, + const unsigned char *start) +{ + UniChar *next; + + if (maxcount == 0) + return 0; + + // always leave room for 0-term + --maxcount; + + for (next = wstr; maxcount > 0; ++next, --maxcount) + { + *next = getCharFromString(&start); + if (*next == 0) + break; + } + + *next = 0; // term + + return next - wstr; +} + +// Encode one wide character into UTF-8 +// returns number of bytes used in the buffer, +// 0 : invalid or unsupported char +// <0 : negative of bytes needed if buffer too small +// string term '\0' is *not* appended or counted +int +getStringFromChar(unsigned char *ptr, size_t size, UniChar ch) +{ + int i; + static const struct range_def + { + UniChar lim; + int marker; + int mask; + } + ranges[] = + { + {0x0000007f, 0x00, 0x7f}, + {0x000007ff, 0xc0, 0x1f}, + {0x0000ffff, 0xe0, 0x0f}, + {0x001fffff, 0xf0, 0x07}, + {0x03ffffff, 0xf8, 0x03}, + {0x7fffffff, 0xfc, 0x01}, + {0x00000000, 0x00, 0x00} // term + }; + const struct range_def *def; + + // lookup the range + for (i = 0, def = ranges; ch > def->lim && def->mask != 0; ++i, ++def) + ; + if (def->mask == 0) + { // invalid or unsupported char + log_add(log_Warning, "Warning: Invalid or unsupported unicode " + "char (%lu)", (unsigned long) ch); + return 0; + } + + if ((size_t)i + 1 > size) + return -(i + 1); + + // unrolled for speed + switch (i) + { + case 5: ptr[5] = (ch & 0x3f) | 0x80; + ch >>= 6; + case 4: ptr[4] = (ch & 0x3f) | 0x80; + ch >>= 6; + case 3: ptr[3] = (ch & 0x3f) | 0x80; + ch >>= 6; + case 2: ptr[2] = (ch & 0x3f) | 0x80; + ch >>= 6; + case 1: ptr[1] = (ch & 0x3f) | 0x80; + ch >>= 6; + case 0: ptr[0] = (ch & def->mask) | def->marker; + } + + return i + 1; +} + +// Encode a wide char string (wstr) into a UTF-8 string (ptr) +// returns number of bytes used in the buffer (includes 0-term) +// any chars that do not fit are truncated +// string term '\0' is always appended, unless the destination +// buffer is 0 bytes long +size_t +getStringFromWideN(unsigned char *ptr, size_t size, + const UniChar *wstr, size_t count) +{ + unsigned char *next; + int used; + + if (size == 0) + return 0; + + // always leave room for 0-term + --size; + + for (next = ptr; size > 0 && count > 0; + size -= used, next += used, --count, ++wstr) + { + used = getStringFromChar(next, size, *wstr); + if (used < 0) + break; // not enough room + if (used == 0) + { // bad char? + *next = '?'; + used = 1; + } + } + + *next = '\0'; // term + + return next - ptr + 1; +} + +// See getStringFromWideN() for functionality +// the only difference is that the source string (wstr) length is +// calculated by searching for 0-term +size_t +getStringFromWide(unsigned char *ptr, size_t size, const UniChar *wstr) +{ + const UniChar *end; + + for (end = wstr; *end != 0; ++end) + ; + + return getStringFromWideN(ptr, size, wstr, (end - wstr)); +} + +int +UniChar_isGraph(UniChar ch) +{ // this is not technically sufficient, but close enough for us + // we'll consider all non-control (CO and C1) chars in 'graph' class + // except for the "Private Use Area" (0xE000 - 0xF8FF) + + // TODO: The private use area is really only glommed by OS X, + // and even there, not all of it. (Delete and Backspace both + // end up producing characters there -- see bug #942 for the + // gory details.) + return (ch > 0xa0 && (ch < 0xE000 || ch > 0xF8FF)) || + (ch > 0x20 && ch < 0x7f); +} + +int +UniChar_isPrint(UniChar ch) +{ // this is not technically sufficient, but close enough for us + // chars in 'print' class are 'graph' + 'space' classes + // the only space we currently have defined is 0x20 + return (ch == 0x20) || UniChar_isGraph(ch); +} + +UniChar +UniChar_toUpper(UniChar ch) +{ // this is a very basic Latin-1 implementation + // just to get things going + return (ch < 0x100) ? (UniChar) toupper((int) ch) : ch; +} + +UniChar +UniChar_toLower(UniChar ch) +{ // this is a very basic Latin-1 implementation + // just to get things going + return (ch < 0x100) ? (UniChar) tolower((int) ch) : ch; +} + diff --git a/src/libs/strlib.h b/src/libs/strlib.h new file mode 100644 index 0000000..c313f7f --- /dev/null +++ b/src/libs/strlib.h @@ -0,0 +1,80 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_STRLIB_H_ +#define LIBS_STRLIB_H_ + +#include "libs/compiler.h" +#include "port.h" +#include "libs/uio.h" +#include "libs/unicode.h" + +#include <stddef.h> + +typedef struct string_table_entry STRING_TABLE_ENTRY_DESC; +typedef struct string_table STRING_TABLE_DESC; + +typedef STRING_TABLE_DESC *STRING_TABLE; +typedef STRING_TABLE_ENTRY_DESC *STRING; +typedef char *STRINGPTR; + +/* This has to go here because reslib requires the above typedefs. */ +#include "libs/reslib.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +extern BOOLEAN InstallStringTableResType (void); +extern STRING_TABLE LoadStringTableInstance (RESOURCE res); +extern STRING_TABLE LoadStringTableFile (uio_DirHandle *dir, + const char *fileName); +extern BOOLEAN DestroyStringTable (STRING_TABLE StringTable); +extern STRING CaptureStringTable (STRING_TABLE StringTable); +extern STRING_TABLE ReleaseStringTable (STRING String); +extern STRING_TABLE GetStringTable (STRING String); +extern COUNT GetStringTableCount (STRING String); +extern COUNT GetStringTableIndex (STRING String); +extern STRING SetAbsStringTableIndex (STRING String, COUNT + StringTableIndex); +extern STRING SetRelStringTableIndex (STRING String, SIZE + StringTableOffs); +extern COUNT GetStringLength (STRING String); +extern COUNT GetStringLengthBin (STRING String); +extern STRINGPTR GetStringAddress (STRING String); +extern STRINGPTR GetStringName (STRING String); +extern STRINGPTR GetStringSoundClip (STRING String); +extern STRINGPTR GetStringTimeStamp (STRING String); +extern STRING GetStringByName (STRING_TABLE StringTable, const char *index); + +#define UNICHAR_DEGREE_SIGN 0x00b0 +#define STR_DEGREE_SIGN "\xC2\xB0" +#define UNICHAR_INFINITY_SIGN 0x221e +#define STR_INFINITY_SIGN "\xE2\x88\x9E" +#define UNICHAR_EARTH_SIGN 0x2641 +#define STR_EARTH_SIGN "\xE2\x99\x81" +#define UNICHAR_MIDDLE_DOT 0x00b7 +#define STR_MIDDLE_DOT "\xC2\xB7" +#define UNICHAR_BULLET 0x2022 +#define STR_BULLET "\xE2\x80\xA2" + +#if defined(__cplusplus) +} +#endif + +#endif /* LIBS_STRLIB_H_ */ diff --git a/src/libs/task/Makeinfo b/src/libs/task/Makeinfo new file mode 100644 index 0000000..f780c46 --- /dev/null +++ b/src/libs/task/Makeinfo @@ -0,0 +1 @@ +uqm_CFILES="tasklib.c" diff --git a/src/libs/task/tasklib.c b/src/libs/task/tasklib.c new file mode 100644 index 0000000..81f77af --- /dev/null +++ b/src/libs/task/tasklib.c @@ -0,0 +1,139 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* By Michael Martin, 2002-09-21 + */ + +#include <stdio.h> +#include <stdlib.h> +#include "libs/tasklib.h" +#include "libs/log.h" + +#define TASK_MAX 64 + +static struct taskstruct task_array[TASK_MAX]; + +Task +AssignTask (ThreadFunction task_func, SDWORD stackSize, const char *name) +{ + int i; + for (i = 0; i < TASK_MAX; ++i) + { + if (!Task_SetState (task_array+i, TASK_INUSE)) + { + // log_add (log_Debug, "Assigning Task #%i: %s", i+1, name); + Task_ClearState (task_array+i, ~TASK_INUSE); + task_array[i].name = name; + task_array[i].thread = CreateThread (task_func, task_array+i, + stackSize, name); + return task_array+i; + } + } + log_add (log_Error, "Task error! Task array exhausted. Check for thread leaks."); + return NULL; +} + +void +FinishTask (Task task) +{ + // log_add (log_Debug, "Releasing Task: %s", task->name); + task->thread = 0; + if (!Task_ClearState (task, TASK_INUSE)) + { + log_add (log_Debug, "Task error! Attempted to FinishTask '%s'... " + "but it was already done!", task->name); + } +} + +/* This could probably be done better with a condition variable of some kind. */ +void +ConcludeTask (Task task) +{ + Thread old = task->thread; + // log_add (log_Debug, "Awaiting conclusion of %s", task->name); + if (old) + { + Task_SetState (task, TASK_EXIT); + while (task->thread == old) + { + TaskSwitch (); + } + } +} + +DWORD +Task_SetState (Task task, DWORD state_mask) +{ + DWORD old_state; + LockMutex (task->state_mutex); + old_state = task->state; + task->state |= state_mask; + UnlockMutex (task->state_mutex); + old_state &= state_mask; + return old_state; +} + +DWORD +Task_ClearState (Task task, DWORD state_mask) +{ + DWORD old_state; + LockMutex (task->state_mutex); + old_state = task->state; + task->state &= ~state_mask; + UnlockMutex (task->state_mutex); + old_state &= state_mask; + return old_state; +} + +DWORD +Task_ToggleState (Task task, DWORD state_mask) +{ + DWORD old_state; + LockMutex (task->state_mutex); + old_state = task->state; + task->state ^= state_mask; + UnlockMutex (task->state_mutex); + old_state &= state_mask; + return old_state; +} + +DWORD +Task_ReadState (Task task, DWORD state_mask) +{ + return task->state & state_mask; +} + +void +InitTaskSystem (void) +{ + int i; + for (i = 0; i < TASK_MAX; ++i) + { + task_array[i].state_mutex = CreateMutex ("task manager lock", SYNC_CLASS_TOPLEVEL | SYNC_CLASS_RESOURCE); + } +} + +void +CleanupTaskSystem (void) +{ + int i; + for (i = 0; i < TASK_MAX; ++i) + { + DestroyMutex (task_array[i].state_mutex); + task_array[i].state_mutex = 0; + } +} + diff --git a/src/libs/tasklib.h b/src/libs/tasklib.h new file mode 100644 index 0000000..41544db --- /dev/null +++ b/src/libs/tasklib.h @@ -0,0 +1,62 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* By Michael Martin, 2002-09-21 + */ + +/* The task libraries are a set of facilities for controlling synchronous + * processes. They are built on top of threads, but add the ability to + * modify a "state" variable to pass messages back and forth. */ + +#ifndef LIBS_TASKLIB_H_ +#define LIBS_TASKLIB_H_ + +#include "libs/threadlib.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +/* Bitmasks for setting task state. */ +#define TASK_INUSE 1 +#define TASK_EXIT 2 + +struct taskstruct { + Mutex state_mutex; + volatile DWORD state; // Protected by state_mutex + const char *name; + volatile Thread thread; +}; + +typedef struct taskstruct *Task; + +extern void InitTaskSystem (void); +extern void CleanupTaskSystem (void); + +extern Task AssignTask (ThreadFunction task_func, SDWORD Stacksize, const char *name); +extern DWORD Task_SetState (Task task, DWORD state_mask); +extern DWORD Task_ClearState (Task task, DWORD state_mask); +extern DWORD Task_ToggleState (Task task, DWORD state_mask); +extern DWORD Task_ReadState (Task task, DWORD state_mask); +extern void FinishTask (Task task); +extern void ConcludeTask (Task task); + +#if defined(__cplusplus) +} +#endif + +#endif + diff --git a/src/libs/threadlib.h b/src/libs/threadlib.h new file mode 100644 index 0000000..6586d7f --- /dev/null +++ b/src/libs/threadlib.h @@ -0,0 +1,186 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* By Serge van den Boom, 2002-09-12 + */ + +#ifndef LIBS_THREADLIB_H_ +#define LIBS_THREADLIB_H_ + +#define NAMED_SYNCHRO /* Should synchronizable objects have names? */ +#define TRACK_CONTENTION /* Should we report when a thread sleeps on synchronize? */ + +/* TRACK_CONTENTION implies NAMED_SYNCHRO. */ +#ifdef TRACK_CONTENTION +# ifndef NAMED_SYNCHRO +# define NAMED_SYNCHRO +# endif +#endif /* TRACK_CONTENTION */ + +#ifdef DEBUG +# ifndef DEBUG_THREADS +# define DEBUG_THREADS +# endif +#endif /* DEBUG */ + +#ifdef DEBUG_THREADS +//# ifndef PROFILE_THREADS +//# define PROFILE_THREADS +//# endif +#endif /* DEBUG_THREADS */ + +#include <sys/types.h> +#include "libs/timelib.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +#if defined (PROFILE_THREADS) || defined (DEBUG_THREADS) +#define THREAD_NAMES +#endif + +void InitThreadSystem (void); +void UnInitThreadSystem (void); + +typedef int (*ThreadFunction) (void *); + +typedef void *Thread; +typedef void *Mutex; +typedef void *Semaphore; +typedef void *RecursiveMutex; +typedef void *CondVar; + +/* Local data associated with each thread */ +typedef struct _threadLocal { + Semaphore flushSem; +} ThreadLocal; + +/* The classes of synchronization objects */ + +enum +{ + SYNC_CLASS_TOPLEVEL = (1 << 0), /* Exposed to the game logic */ + SYNC_CLASS_AUDIO = (1 << 1), /* Involves the audio system */ + SYNC_CLASS_VIDEO = (1 << 2), /* Involves the video system. Very noisy because of FlushGraphics(). */ + SYNC_CLASS_RESOURCE = (1 << 3) /* Involves system resources (_MemoryLock) */ +}; + +/* Note. NEVER call CreateThread from the main thread, or deadlocks + are guaranteed. Use StartThread instead (which doesn't wait around + for the main thread to actually create the thread and return + it). */ + +#ifdef NAMED_SYNCHRO +/* Logical OR of all classes we want to track. */ +#define TRACK_CONTENTION_CLASSES (SYNC_CLASS_TOPLEVEL) + +/* Prototypes with the "name" field */ + +Thread CreateThread_Core (ThreadFunction func, void *data, SDWORD stackSize, const char *name); +void StartThread_Core (ThreadFunction func, void *data, SDWORD stackSize, const char *name); +Semaphore CreateSemaphore_Core (DWORD initial, const char *name, DWORD syncClass); +Mutex CreateMutex_Core (const char *name, DWORD syncClass); +RecursiveMutex CreateRecursiveMutex_Core (const char *name, DWORD syncClass); +CondVar CreateCondVar_Core (const char *name, DWORD syncClass); + +/* Preprocessor directives to forward to the appropriate routines */ + +#define CreateThread(func, data, stackSize, name) \ + CreateThread_Core ((func), (data), (stackSize), (name)) +#define StartThread(func, data, stackSize, name) \ + StartThread_Core ((func), (data), (stackSize), (name)) +#define CreateSemaphore(initial, name, syncClass) \ + CreateSemaphore_Core ((initial), (name), (syncClass)) +#define CreateMutex(name, syncClass) \ + CreateMutex_Core ((name), (syncClass)) +#define CreateRecursiveMutex(name, syncClass) \ + CreateRecursiveMutex_Core((name), (syncClass)) +#define CreateCondVar(name, syncClass) \ + CreateCondVar_Core ((name), (syncClass)) + +#else + +/* Prototypes without the "name" field. */ +Thread CreateThread_Core (ThreadFunction func, void *data, SDWORD stackSize); +void StartThread_Core (ThreadFunction func, void *data, SDWORD stackSize); +Semaphore CreateSemaphore_Core (DWORD initial); +Mutex CreateMutex_Core (void); +RecursiveMutex CreateRecursiveMutex_Core (void); +CondVar CreateCondVar_Core (void); + + +/* Preprocessor directives to forward to the appropriate routines. + The "name" field is stripped away in preprocessing. */ + +#define CreateThread(func, data, stackSize, name) \ + CreateThread_Core ((func), (data), (stackSize)) +#define StartThread(func, data, stackSize, name) \ + StartThread_Core ((func), (data), (stackSize)) +#define CreateSemaphore(initial, name, syncClass) \ + CreateSemaphore_Core ((initial)) +#define CreateMutex(name, syncClass) \ + CreateMutex_Core () +#define CreateRecursiveMutex(name, syncClass) \ + CreateRecursiveMutex_Core() +#define CreateCondVar(name, syncClass) \ + CreateCondVar_Core () + +#endif + +ThreadLocal *CreateThreadLocal (void); +void DestroyThreadLocal (ThreadLocal *tl); +ThreadLocal *GetMyThreadLocal (void); + +void HibernateThread (TimePeriod timePeriod); +void HibernateThreadUntil (TimeCount wakeTime); +void SleepThread (TimePeriod timePeriod); +void SleepThreadUntil (TimeCount wakeTime); +void DestroyThread (Thread); +void TaskSwitch (void); +void WaitThread (Thread thread, int *status); + +void FinishThread (Thread); +void ProcessThreadLifecycles (void); + +#ifdef PROFILE_THREADS +void PrintThreadsStats (void); +#endif /* PROFILE_THREADS */ + + +void DestroySemaphore (Semaphore sem); +void SetSemaphore (Semaphore sem); +void ClearSemaphore (Semaphore sem); + +void DestroyMutex (Mutex sem); +void LockMutex (Mutex sem); +void UnlockMutex (Mutex sem); + +void DestroyRecursiveMutex (RecursiveMutex m); +void LockRecursiveMutex (RecursiveMutex m); +void UnlockRecursiveMutex (RecursiveMutex m); +int GetRecursiveMutexDepth (RecursiveMutex m); + +void DestroyCondVar (CondVar); +void WaitCondVar (CondVar); +void SignalCondVar (CondVar); +void BroadcastCondVar (CondVar); + +#if defined(__cplusplus) +} +#endif + +#endif /* LIBS_THREADLIB_H_ */ diff --git a/src/libs/threads/Makeinfo b/src/libs/threads/Makeinfo new file mode 100644 index 0000000..57f56a8 --- /dev/null +++ b/src/libs/threads/Makeinfo @@ -0,0 +1,11 @@ +case "$uqm_THREADLIB" in + SDL) + uqm_SUBDIRS="sdl" + ;; + PTHREAD) + uqm_SUBDIRS="pthread" + ;; +esac + +uqm_CFILES="thrcommon.c" +uqm_HFILES="thrcommon.h" diff --git a/src/libs/threads/pthread/Makeinfo b/src/libs/threads/pthread/Makeinfo new file mode 100644 index 0000000..251db7b --- /dev/null +++ b/src/libs/threads/pthread/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="posixthreads.c" +uqm_HFILES="posixthreads.h" diff --git a/src/libs/threads/pthread/posixthreads.c b/src/libs/threads/pthread/posixthreads.c new file mode 100644 index 0000000..4f0d2e8 --- /dev/null +++ b/src/libs/threads/pthread/posixthreads.c @@ -0,0 +1,672 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <stdlib.h> +#include "libs/misc.h" +#include "libs/memlib.h" +#include "posixthreads.h" +#include <pthread.h> +#include <unistd.h> + +#include <semaphore.h> + +#include "libs/log/uqmlog.h" + +typedef struct _thread { + pthread_t native; +#ifdef NAMED_SYNCHRO + const char *name; +#endif + ThreadLocal *localData; + struct _thread *next; +} *TrueThread; + +static volatile TrueThread threadQueue = NULL; +static pthread_mutex_t threadQueueMutex; + +struct ThreadStartInfo +{ + ThreadFunction func; + void *data; + sem_t sem; + TrueThread thread; +}; + +void +InitThreadSystem_PT (void) +{ + pthread_mutex_init (&threadQueueMutex, NULL); +} + +void +UnInitThreadSystem_PT (void) +{ + pthread_mutex_destroy (&threadQueueMutex); +} + +static void +QueueThread (TrueThread thread) +{ + pthread_mutex_lock (&threadQueueMutex); + thread->next = threadQueue; + threadQueue = thread; + pthread_mutex_unlock (&threadQueueMutex); +} + +static void +UnQueueThread (TrueThread thread) +{ + volatile TrueThread *ptr; + + pthread_mutex_lock (&threadQueueMutex); + ptr = &threadQueue; + while (*ptr != thread) + { +#ifdef DEBUG_THREADS + if (*ptr == NULL) + { + // Should not happen. + log_add (log_Debug, "Error: Trying to remove non-present thread " + "from thread queue."); + fflush (stderr); + explode (); + } +#endif /* DEBUG_THREADS */ + ptr = &(*ptr)->next; + } + *ptr = (*ptr)->next; + pthread_mutex_unlock (&threadQueueMutex); +} + +static TrueThread +FindThreadInfo (pthread_t threadID) +{ + TrueThread ptr; + + pthread_mutex_lock (&threadQueueMutex); + ptr = threadQueue; + while (ptr) + { + if (ptr->native == threadID) + { + pthread_mutex_unlock (&threadQueueMutex); + return ptr; + } + ptr = ptr->next; + } + pthread_mutex_unlock (&threadQueueMutex); + return NULL; +} + +#ifdef NAMED_SYNCHRO +static const char * +MyThreadName (void) +{ + TrueThread t = FindThreadInfo (pthread_self()); + return t ? t->name : "Unknown (probably renderer)"; +} +#endif + +static void * +ThreadHelper (void *startInfo) { + ThreadFunction func; + void *data; + sem_t *sem; + TrueThread thread; + int result; + + //log_add (log_Debug, "ThreadHelper()"); + + func = ((struct ThreadStartInfo *) startInfo)->func; + data = ((struct ThreadStartInfo *) startInfo)->data; + sem = &((struct ThreadStartInfo *) startInfo)->sem; + + // Wait until the Thread structure is available. + if (sem_wait (sem)) + { + log_add(log_Fatal, "ThreadHelper sem_wait fail"); + exit(EXIT_FAILURE); + } + if (sem_destroy (sem)) + { + log_add(log_Fatal, "ThreadHelper sem_destroy fail"); + exit(EXIT_FAILURE); + } + + thread = ((struct ThreadStartInfo *) startInfo)->thread; + HFree (startInfo); + + result = (*func) (data); + +#ifdef DEBUG_THREADS + log_add (log_Debug, "Thread '%s' done (returned %d).", + thread->name, result); + fflush (stderr); +#endif + + UnQueueThread (thread); + DestroyThreadLocal (thread->localData); + FinishThread (thread); + /* Destroying the thread is the responsibility of ProcessThreadLifecycles() */ + return (void*)result; +} + +void +DestroyThread_PT (Thread t) +{ + HFree (t); +} + +Thread +CreateThread_PT (ThreadFunction func, void *data, SDWORD stackSize +#ifdef NAMED_SYNCHRO + , const char *name +#endif + ) +{ + TrueThread thread; + struct ThreadStartInfo *startInfo; + pthread_attr_t attr; + + + //log_add (log_Debug, "CreateThread_PT '%s'", name); + + thread = (struct _thread *) HMalloc (sizeof *thread); +#ifdef NAMED_SYNCHRO + thread->name = name; +#endif + + thread->localData = CreateThreadLocal (); + + startInfo = (struct ThreadStartInfo *) HMalloc (sizeof (*startInfo)); + startInfo->func = func; + startInfo->data = data; + if (sem_init(&startInfo->sem, 0, 0) < 0) + { + log_add (log_Fatal, "createthread seminit fail"); + exit(EXIT_FAILURE); + } + startInfo->thread = thread; + + pthread_attr_init(&attr); + if (pthread_attr_setstacksize(&attr, 75000)) + { + log_add (log_Debug, "pthread stacksize fail"); + } + if (pthread_create(&thread->native, &attr, ThreadHelper, (void *)startInfo)) + { + log_add (log_Debug, "pthread create fail"); + DestroyThreadLocal (thread->localData); + HFree (startInfo); + HFree (thread); + return NULL; + } + // The responsibility to free 'startInfo' and 'thread' is now by the new + // thread. + + QueueThread (thread); + +#ifdef DEBUG_THREADS +//#if 0 + log_add (log_Debug, "Thread '%s' created.", thread->name); + fflush (stderr); +//#endif +#endif + + // Signal to the new thread that the thread structure is ready + // and it can begin to use it. + if (sem_post (&startInfo->sem)) + { + log_add(log_Fatal, "CreateThread sem_post fail"); + exit(EXIT_FAILURE); + } + + (void) stackSize; /* Satisfying compiler (unused parameter) */ + return thread; +} + +void +SleepThread_PT (TimeCount sleepTime) +{ + usleep (sleepTime * 1000000 / ONE_SECOND); +} + +void +SleepThreadUntil_PT (TimeCount wakeTime) { + TimeCount now; + + now = GetTimeCounter (); + if (wakeTime <= now) + TaskSwitch_PT (); + else + usleep ((wakeTime - now) * 1000000 / ONE_SECOND); +} + +void +TaskSwitch_PT (void) { + usleep (1000); +} + +void +WaitThread_PT (Thread thread, int *status) { + //log_add(log_Debug, "WaitThread_PT '%s', status %x", ((TrueThread)thread)->name, status); + //pthread_join(((TrueThread)thread)->native, status); + pthread_join(((TrueThread)thread)->native, NULL); + //log_add(log_Debug, "WaitThread_PT '%s' complete", ((TrueThread)thread)->name); +} + +ThreadLocal * +GetMyThreadLocal_PT (void) +{ + TrueThread t = FindThreadInfo (pthread_self()); + return t ? t->localData : NULL; +} + +/* These are the pthread implementations of the UQM synchronization objects. */ + +/* Mutexes. */ +/* TODO. The w_memlib uses Mutexes right now, so we can't use HMalloc + * or HFree. Once that goes, this needs to change. */ + +typedef struct _mutex { + pthread_mutex_t mutex; +#ifdef TRACK_CONTENTION + pthread_t owner; +#endif +#ifdef NAMED_SYNCHRO + const char *name; + DWORD syncClass; +#endif +} Mut; + + +Mutex +#ifdef NAMED_SYNCHRO +CreateMutex_PT (const char *name, DWORD syncClass) +#else +CreateMutex_PT (void) +#endif +{ + Mut *mutex = malloc (sizeof (Mut)); + + if (mutex != NULL) + { + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + if (pthread_mutex_init(&mutex->mutex, &attr)) + { +#ifdef NAMED_SYNCHRO + /* logging depends on Mutexes, so we have to use the + * non-threaded version instead */ + log_add_nothread (log_Fatal, "Could not initialize mutex '%s':" + "aborting.", name); +#else + log_add_nothread (log_Fatal, "Could not initialize mutex:" + "aborting."); +#endif + exit (EXIT_FAILURE); + } + pthread_mutexattr_destroy(&attr); + +#ifdef TRACK_CONTENTION + mutex->owner = 0; +#endif +#ifdef NAMED_SYNCHRO + mutex->name = name; + mutex->syncClass = syncClass; +#endif + } + + return mutex; +} + +void +DestroyMutex_PT (Mutex m) +{ + Mut *mutex = (Mut *)m; + //log_add_nothread(log_Debug, "Destroying mutex '%s'", mutex->name); + pthread_mutex_destroy (&mutex->mutex); + free (mutex); +} + +void +LockMutex_PT (Mutex m) +{ + Mut *mutex = (Mut *)m; +#ifdef TRACK_CONTENTION + /* This code isn't really quite right; race conditions between + * check and lock remain and can produce reports of contention + * where the thread never sleeps, or fail to report in + * situations where it does. If tracking with perfect + * accuracy becomes important, the TRACK_CONTENTION mutex will + * need to handle its own wake/sleep cycles with condition + * variables (check the history of this file for the + * CrossThreadMutex code). This almost-measure is being added + * because for the most part it should suffice. */ + if (mutex->owner && (mutex->syncClass & TRACK_CONTENTION_CLASSES)) + { /* logging depends on Mutexes, so we have to use the + * non-threaded version instead */ + log_add_nothread (log_Debug, "Thread '%s' blocking on mutex '%s'", + MyThreadName (), mutex->name); + } +#endif + + while (pthread_mutex_lock (&mutex->mutex) != 0) + { + //log_add_nothread (log_Debug, "Attempt to acquire mutex '%s' failretry", mutex->name); + TaskSwitch_PT (); + } +#ifdef TRACK_CONTENTION + mutex->owner = pthread_self(); +#endif +} + +void +UnlockMutex_PT (Mutex m) +{ + Mut *mutex = (Mut *)m; +#ifdef TRACK_CONTENTION + mutex->owner = 0; +#endif + while (pthread_mutex_unlock (&mutex->mutex) != 0) + { + TaskSwitch_PT (); + } +} + +/* Semaphores. */ + +typedef struct _sem { + sem_t sem; +#ifdef NAMED_SYNCHRO + const char *name; + DWORD syncClass; +#endif +} Sem; + +Semaphore +CreateSemaphore_PT (DWORD initial +#ifdef NAMED_SYNCHRO + , const char *name, DWORD syncClass +#endif + ) +{ + Sem *sem = (Sem *) HMalloc (sizeof (struct _sem)); +#ifdef NAMED_SYNCHRO + sem->name = name; + sem->syncClass = syncClass; +#endif + + //log_add (log_Debug, "Creating semaphore '%s'", sem->name); + + if (sem_init(&sem->sem, 0, initial) < 0) + { +#ifdef NAMED_SYNCHRO + log_add (log_Fatal, "Could not initialize semaphore '%s':" + " aborting.", name); +#else + log_add (log_Fatal, "Could not initialize semaphore:" + " aborting."); +#endif + exit (EXIT_FAILURE); + } + //log_add (log_Debug, "Creating semaphore '%s' success", sem->name); + return sem; +} + +void +DestroySemaphore_PT (Semaphore s) +{ + Sem *sem = (Sem *)s; + //log_add (log_Debug, "Destroying semaphore '%s'", sem->name); + if (sem_destroy (&sem->sem)) + { + log_add (log_Debug, "Destroying semaphore '%s' failed", sem->name); + } + HFree (sem); +} + +void +SetSemaphore_PT (Semaphore s) +{ + Sem *sem = (Sem *)s; +#ifdef TRACK_CONTENTION + int contention = 0; + sem_getvalue(&sem->sem, &contention); + contention = !contention; + if (contention && (sem->syncClass & TRACK_CONTENTION_CLASSES)) + { + log_add (log_Debug, "Thread '%s' blocking on semaphore '%s'", + MyThreadName (), sem->name); + } +#endif + //log_add (log_Debug, "Attempt to set semaphore '%s'", sem->name); + while (sem_wait (&sem->sem) == -1) + { + //log_add (log_Debug, "Attempt to set semaphore '%s' failretry", sem->name); + TaskSwitch_PT (); + } + //log_add (log_Debug, "Attempt to set semaphore '%s' success", sem->name); +#ifdef TRACK_CONTENTION + if (contention && (sem->syncClass & TRACK_CONTENTION_CLASSES)) + { + log_add (log_Debug, "Thread '%s' awakens," + " released from semaphore '%s'", MyThreadName (), sem->name); + } +#endif +} + +void +ClearSemaphore_PT (Semaphore s) +{ + Sem *sem = (Sem *)s; + //log_add (log_Debug, "Attempt to clear semaphore '%s' %x", sem->name, sem); + while (sem_post (&sem->sem) == -1) + { + //log_add (log_Debug, "Attempt to clear semaphore %x failretry", sem); + TaskSwitch_PT (); + } + //log_add (log_Debug, "Attempt to clear semaphore %x success", sem); +} + +/* Recursive mutexes. Adapted from mixSDL code, which was adapted from + the original DCQ code. */ + +typedef struct _recm { + pthread_mutex_t mutex; + pthread_t thread_id; + unsigned int locks; +#ifdef NAMED_SYNCHRO + const char *name; + DWORD syncClass; +#endif +} RecM; + +RecursiveMutex +#ifdef NAMED_SYNCHRO +CreateRecursiveMutex_PT (const char *name, DWORD syncClass) +#else +CreateRecursiveMutex_PT (void) +#endif +{ + RecM *mtx = (RecM *) HMalloc (sizeof (struct _recm)); + + mtx->thread_id = 0; + if (pthread_mutex_init(&mtx->mutex, NULL)) + { +#ifdef NAMED_SYNCHRO + log_add (log_Fatal, "Could not initialize recursive " + "mutex '%s': aborting.", name); +#else + log_add (log_Fatal, "Could not initialize recursive " + "mutex: aborting."); +#endif + exit (EXIT_FAILURE); + } +#ifdef NAMED_SYNCHRO + mtx->name = name; + mtx->syncClass = syncClass; +#endif + mtx->locks = 0; + return (RecursiveMutex) mtx; +} + +void +DestroyRecursiveMutex_PT (RecursiveMutex val) +{ + RecM *mtx = (RecM *)val; + pthread_mutex_destroy(&mtx->mutex); + HFree (mtx); +} + +void +LockRecursiveMutex_PT (RecursiveMutex val) +{ + RecM *mtx = (RecM *)val; + pthread_t thread_id = pthread_self(); + if (!mtx->locks || mtx->thread_id != thread_id) + { +#ifdef TRACK_CONTENTION + if (mtx->thread_id && (mtx->syncClass & TRACK_CONTENTION_CLASSES)) + { + log_add (log_Debug, "Thread '%s' blocking on '%s'", + MyThreadName (), mtx->name); + } +#endif + while (pthread_mutex_lock (&mtx->mutex)) + TaskSwitch_PT (); + mtx->thread_id = thread_id; + } + mtx->locks++; +} + +void +UnlockRecursiveMutex_PT (RecursiveMutex val) +{ + RecM *mtx = (RecM *)val; + pthread_t thread_id = pthread_self(); + if (!mtx->locks || mtx->thread_id != thread_id) + { +#ifdef NAMED_SYNCHRO + log_add (log_Debug, "'%s' attempted to unlock %s when it " + "didn't hold it", MyThreadName (), mtx->name); +#endif + } + else + { + mtx->locks--; + if (!mtx->locks) + { + mtx->thread_id = 0; + pthread_mutex_unlock (&mtx->mutex); + } + } +} + +int +GetRecursiveMutexDepth_PT (RecursiveMutex val) +{ + RecM *mtx = (RecM *)val; + return mtx->locks; +} + +typedef struct _cond { + pthread_cond_t cond; + pthread_mutex_t mutex; +#ifdef NAMED_SYNCHRO + const char *name; + DWORD syncClass; +#endif +} cvar; + +CondVar +#ifdef NAMED_SYNCHRO +CreateCondVar_PT (const char *name, DWORD syncClass) +#else +CreateCondVar_PT (void) +#endif +{ + int err1, err2; + cvar *cv = (cvar *) HMalloc (sizeof (cvar)); + err1 = pthread_cond_init(&cv->cond, NULL); + err2 = pthread_mutex_init(&cv->mutex, NULL); + if (err1 || err2) + { +#ifdef NAMED_SYNCHRO + log_add (log_Fatal, "Could not initialize condition variable '%s':" + " aborting.", name); +#else + log_add (log_Fatal, "Could not initialize condition variable:" + " aborting."); +#endif + exit (EXIT_FAILURE); + } +#ifdef NAMED_SYNCHRO + cv->name = name; + cv->syncClass = syncClass; +#endif + return cv; +} + +void +DestroyCondVar_PT (CondVar c) +{ + cvar *cv = (cvar *) c; + pthread_cond_destroy(&cv->cond); + pthread_mutex_destroy(&cv->mutex); + HFree (cv); +} + +void +WaitCondVar_PT (CondVar c) +{ + cvar *cv = (cvar *) c; + pthread_mutex_lock (&cv->mutex); +#ifdef TRACK_CONTENTION + if (cv->syncClass & TRACK_CONTENTION_CLASSES) + { + log_add (log_Debug, "Thread '%s' waiting for signal from '%s'", + MyThreadName (), cv->name); + } +#endif + while (pthread_cond_wait (&cv->cond, &cv->mutex) != 0) + { + TaskSwitch_PT (); + } +#ifdef TRACK_CONTENTION + if (cv->syncClass & TRACK_CONTENTION_CLASSES) + { + log_add (log_Debug, "Thread '%s' received signal from '%s'," + " awakening.", MyThreadName (), cv->name); + } +#endif + pthread_mutex_unlock (&cv->mutex); +} + +void +SignalCondVar_PT (CondVar c) +{ + cvar *cv = (cvar *) c; + pthread_cond_signal(&cv->cond); +} + +void +BroadcastCondVar_PT (CondVar c) +{ + cvar *cv = (cvar *) c; + pthread_cond_broadcast(&cv->cond); +} diff --git a/src/libs/threads/pthread/posixthreads.h b/src/libs/threads/pthread/posixthreads.h new file mode 100644 index 0000000..9945b53 --- /dev/null +++ b/src/libs/threads/pthread/posixthreads.h @@ -0,0 +1,103 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_THREADS_PTHREAD_POSIXTHREADS_H_ +#define LIBS_THREADS_PTHREAD_POSIXTHREADS_H_ + +#include "port.h" +#include "libs/threadlib.h" + +void InitThreadSystem_PT (void); +void UnInitThreadSystem_PT (void); + +#ifdef NAMED_SYNCHRO +/* Prototypes with the "name" field */ +Thread CreateThread_PT (ThreadFunction func, void *data, SDWORD stackSize, const char *name); +Mutex CreateMutex_PT (const char *name, DWORD syncClass); +Semaphore CreateSemaphore_PT (DWORD initial, const char *name, DWORD syncClass); +RecursiveMutex CreateRecursiveMutex_PT (const char *name, DWORD syncClass); +CondVar CreateCondVar_PT (const char *name, DWORD syncClass); +#else +/* Prototypes without the "name" field. */ +Thread CreateThread_PT (ThreadFunction func, void *data, SDWORD stackSize); +Mutex CreateMutex_PT (void); +Semaphore CreateSemaphore_PT (DWORD initial); +RecursiveMutex CreateRecursiveMutex_PT (void); +CondVar CreateCondVar_PT (void); +#endif + +ThreadLocal *GetMyThreadLocal_PT (void); + +void SleepThread_PT (TimeCount sleepTime); +void SleepThreadUntil_PT (TimeCount wakeTime); +void TaskSwitch_PT (void); +void WaitThread_PT (Thread thread, int *status); +void DestroyThread_PT (Thread thread); + +void DestroyMutex_PT (Mutex m); +void LockMutex_PT (Mutex m); +void UnlockMutex_PT (Mutex m); + +void DestroySemaphore_PT (Semaphore sem); +void SetSemaphore_PT (Semaphore sem); +void ClearSemaphore_PT (Semaphore sem); + +void DestroyCondVar_PT (CondVar c); +void WaitCondVar_PT (CondVar c); +void SignalCondVar_PT (CondVar c); +void BroadcastCondVar_PT (CondVar c); + +void DestroyRecursiveMutex_PT (RecursiveMutex m); +void LockRecursiveMutex_PT (RecursiveMutex m); +void UnlockRecursiveMutex_PT (RecursiveMutex m); +int GetRecursiveMutexDepth_PT (RecursiveMutex m); + +#define NativeInitThreadSystem InitThreadSystem_PT +#define NativeUnInitThreadSystem UnInitThreadSystem_PT + +#define NativeGetMyThreadLocal GetMyThreadLocal_PT + +#define NativeCreateThread CreateThread_PT +#define NativeSleepThread SleepThread_PT +#define NativeSleepThreadUntil SleepThreadUntil_PT +#define NativeTaskSwitch TaskSwitch_PT +#define NativeWaitThread WaitThread_PT +#define NativeDestroyThread DestroyThread_PT + +#define NativeCreateMutex CreateMutex_PT +#define NativeDestroyMutex DestroyMutex_PT +#define NativeLockMutex LockMutex_PT +#define NativeUnlockMutex UnlockMutex_PT + +#define NativeCreateSemaphore CreateSemaphore_PT +#define NativeDestroySemaphore DestroySemaphore_PT +#define NativeSetSemaphore SetSemaphore_PT +#define NativeClearSemaphore ClearSemaphore_PT + +#define NativeCreateCondVar CreateCondVar_PT +#define NativeDestroyCondVar DestroyCondVar_PT +#define NativeWaitCondVar WaitCondVar_PT +#define NativeSignalCondVar SignalCondVar_PT +#define NativeBroadcastCondVar BroadcastCondVar_PT + +#define NativeCreateRecursiveMutex CreateRecursiveMutex_PT +#define NativeDestroyRecursiveMutex DestroyRecursiveMutex_PT +#define NativeLockRecursiveMutex LockRecursiveMutex_PT +#define NativeUnlockRecursiveMutex UnlockRecursiveMutex_PT +#define NativeGetRecursiveMutexDepth GetRecursiveMutexDepth_PT + +#endif /* _PTTHREAD_H */ + diff --git a/src/libs/threads/sdl/Makeinfo b/src/libs/threads/sdl/Makeinfo new file mode 100644 index 0000000..7a7ad76 --- /dev/null +++ b/src/libs/threads/sdl/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="sdlthreads.c" +uqm_HFILES="sdlthreads.h" diff --git a/src/libs/threads/sdl/sdlthreads.c b/src/libs/threads/sdl/sdlthreads.c new file mode 100644 index 0000000..118951d --- /dev/null +++ b/src/libs/threads/sdl/sdlthreads.c @@ -0,0 +1,706 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <stdlib.h> +#include "libs/misc.h" +#include "libs/memlib.h" +#include "sdlthreads.h" +#ifdef PROFILE_THREADS +#include <signal.h> +#include <unistd.h> +#endif +#include "libs/log.h" + +#if defined(PROFILE_THREADS) && !defined(WIN32) +#include <sys/time.h> +#include <sys/resource.h> +#endif + +#if SDL_MAJOR_VERSION == 1 +typedef Uint32 SDL_threadID; +#endif + +typedef struct _thread { + void *native; +#ifdef NAMED_SYNCHRO + const char *name; +#endif +#ifdef PROFILE_THREADS + int startTime; +#endif /* PROFILE_THREADS */ + ThreadLocal *localData; + struct _thread *next; +} *TrueThread; + +static volatile TrueThread threadQueue = NULL; +static SDL_mutex *threadQueueMutex; + +struct ThreadStartInfo +{ + ThreadFunction func; + void *data; + SDL_sem *sem; + TrueThread thread; +}; + +#ifdef PROFILE_THREADS +static void +SigUSR1Handler (int signr) { + if (getpgrp () != getpid ()) + { + // Only act for the main process + return; + } + PrintThreadsStats (); + // It's not a good idea in general to do many things in a signal + // handler, (and especially the locking) but I guess it will + // have to do for now (and it's only for debugging). + (void) signr; /* Satisfying compiler (unused parameter) */ +} + +static void +LocalStats (SDL_Thread *thread) { +#if defined (WIN32) || !defined(SDL_PTHREADS) + fprintf (stderr, "Thread ID %08lx\n", (Uint64)SDL_GetThreadID (thread)); +#else /* !defined (WIN32) && defined(SDL_PTHREADS) */ + // This only works if SDL implements threads as processes + pid_t pid; + struct rusage ru; + long seconds; + + pid = (pid_t) SDL_GetThreadID (thread); + fprintf (stderr, "Pid %d\n", (int) pid); + getrusage(RUSAGE_SELF, &ru); + seconds = ru.ru_utime.tv_sec + ru.ru_utime.tv_sec; + fprintf (stderr, "Used %ld.%ld minutes of processor time.\n", + seconds / 60, seconds % 60); +#endif /* defined (WIN32) && defined(SDL_PTHREADS) */ +} + +void +PrintThreadsStats_SDL (void) +{ + TrueThread ptr; + int now; + + now = GetTimeCounter (); + SDL_mutexP (threadQueueMutex); + fprintf(stderr, "--- Active threads ---\n"); + for (ptr = threadQueue; ptr != NULL; ptr = ptr->next) { + fprintf (stderr, "Thread named '%s'.\n", ptr->name); + fprintf (stderr, "Started %d.%d minutes ago.\n", + (now - ptr->startTime) / 60000, + ((now - ptr->startTime) / 1000) % 60); + LocalStats (ptr->native); + if (ptr->next != NULL) + fprintf(stderr, "\n"); + } + SDL_mutexV (threadQueueMutex); + fprintf(stderr, "----------------------\n"); + fflush (stderr); +} +#endif /* PROFILE_THREADS */ + +void +InitThreadSystem_SDL (void) +{ + threadQueueMutex = SDL_CreateMutex (); +#ifdef PROFILE_THREADS + signal(SIGUSR1, SigUSR1Handler); +#endif +} + +void +UnInitThreadSystem_SDL (void) +{ +#ifdef PROFILE_THREADS + signal(SIGUSR1, SIG_DFL); +#endif + SDL_DestroyMutex (threadQueueMutex); +} + +static void +QueueThread (TrueThread thread) +{ + SDL_mutexP (threadQueueMutex); + thread->next = threadQueue; + threadQueue = thread; + SDL_mutexV (threadQueueMutex); +} + +static void +UnQueueThread (TrueThread thread) +{ + volatile TrueThread *ptr; + + SDL_mutexP (threadQueueMutex); + ptr = &threadQueue; + while (*ptr != thread) + { +#ifdef DEBUG_THREADS + if (*ptr == NULL) + { + // Should not happen. + log_add (log_Debug, "Error: Trying to remove non-present thread " + "from thread queue."); + fflush (stderr); + explode (); + } +#endif /* DEBUG_THREADS */ + ptr = &(*ptr)->next; + } + *ptr = (*ptr)->next; + SDL_mutexV (threadQueueMutex); +} + +static TrueThread +FindThreadInfo (SDL_threadID threadID) +{ + TrueThread ptr; + + SDL_mutexP (threadQueueMutex); + ptr = threadQueue; + while (ptr) + { + if (SDL_GetThreadID (ptr->native) == threadID) + { + SDL_mutexV (threadQueueMutex); + return ptr; + } + ptr = ptr->next; + } + SDL_mutexV (threadQueueMutex); + return NULL; +} + +#ifdef NAMED_SYNCHRO +static const char * +MyThreadName (void) +{ + TrueThread t = FindThreadInfo (SDL_ThreadID ()); + return t ? t->name : "Unknown (probably renderer)"; +} +#endif + +static int +ThreadHelper (void *startInfo) { + ThreadFunction func; + void *data; + SDL_sem *sem; + TrueThread thread; + int result; + + func = ((struct ThreadStartInfo *) startInfo)->func; + data = ((struct ThreadStartInfo *) startInfo)->data; + sem = ((struct ThreadStartInfo *) startInfo)->sem; + + // Wait until the Thread structure is available. + SDL_SemWait (sem); + SDL_DestroySemaphore (sem); + thread = ((struct ThreadStartInfo *) startInfo)->thread; + HFree (startInfo); + + result = (*func) (data); + +#ifdef DEBUG_THREADS + log_add (log_Debug, "Thread '%s' done (returned %d).", + thread->name, result); + fflush (stderr); +#endif + + UnQueueThread (thread); + DestroyThreadLocal (thread->localData); + FinishThread (thread); + /* Destroying the thread is the responsibility of ProcessThreadLifecycles() */ + return result; +} + +void +DestroyThread_SDL (Thread t) +{ + HFree (t); +} + +Thread +CreateThread_SDL (ThreadFunction func, void *data, SDWORD stackSize +#ifdef NAMED_SYNCHRO + , const char *name +#endif + ) +{ + TrueThread thread; + struct ThreadStartInfo *startInfo; + + thread = (struct _thread *) HMalloc (sizeof *thread); +#ifdef NAMED_SYNCHRO + thread->name = name; +#endif +#ifdef PROFILE_THREADS + thread->startTime = GetTimeCounter (); +#endif + + thread->localData = CreateThreadLocal (); + + startInfo = (struct ThreadStartInfo *) HMalloc (sizeof (*startInfo)); + startInfo->func = func; + startInfo->data = data; + startInfo->sem = SDL_CreateSemaphore (0); + startInfo->thread = thread; + +#if SDL_MAJOR_VERSION == 1 + // SDL1 case + thread->native = SDL_CreateThread (ThreadHelper, (void *) startInfo); +#elif defined(NAMED_SYNCHRO) + // SDL2 with UQM-aware named threads case + thread->native = SDL_CreateThread (ThreadHelper, thread->name, (void *) startInfo); +#else + // SDL2 without UQM-aware named threads; use a placeholder for debuggers + thread->native = SDL_CreateThread (ThreadHelper, "UQM worker thread", (void *)startInfo); +#endif + if (!(thread->native)) + { + DestroyThreadLocal (thread->localData); + HFree (startInfo); + HFree (thread); + return NULL; + } + // The responsibility to free 'startInfo' and 'thread' is now by the new + // thread. + + QueueThread (thread); + +#ifdef DEBUG_THREADS +#if 0 + log_add (log_Debug, "Thread '%s' created.", ThreadName (thread)); + fflush (stderr); +#endif +#endif + + // Signal to the new thread that the thread structure is ready + // and it can begin to use it. + SDL_SemPost (startInfo->sem); + + (void) stackSize; /* Satisfying compiler (unused parameter) */ + return thread; +} + +void +SleepThread_SDL (TimeCount sleepTime) +{ + SDL_Delay (sleepTime * 1000 / ONE_SECOND); +} + +void +SleepThreadUntil_SDL (TimeCount wakeTime) { + TimeCount now; + + now = GetTimeCounter (); + if (wakeTime <= now) + TaskSwitch_SDL (); + else + SDL_Delay ((wakeTime - now) * 1000 / ONE_SECOND); +} + +void +TaskSwitch_SDL (void) { + SDL_Delay (1); +} + +void +WaitThread_SDL (Thread thread, int *status) { + SDL_WaitThread (((TrueThread)thread)->native, status); +} + +ThreadLocal * +GetMyThreadLocal_SDL (void) +{ + TrueThread t = FindThreadInfo (SDL_ThreadID ()); + return t ? t->localData : NULL; +} + +/* These are the SDL implementations of the UQM synchronization objects. */ + +/* Mutexes. */ +/* TODO. The w_memlib uses Mutexes right now, so we can't use HMalloc + * or HFree. Once that goes, this needs to change. */ + +typedef struct _mutex { + SDL_mutex *mutex; +#ifdef TRACK_CONTENTION + SDL_threadID owner; +#endif +#ifdef NAMED_SYNCHRO + const char *name; + DWORD syncClass; +#endif +} Mut; + + +Mutex +#ifdef NAMED_SYNCHRO +CreateMutex_SDL (const char *name, DWORD syncClass) +#else +CreateMutex_SDL (void) +#endif +{ + Mut *mutex = malloc (sizeof (Mut)); + if (mutex != NULL) + { + mutex->mutex = SDL_CreateMutex(); +#ifdef TRACK_CONTENTION + mutex->owner = 0; +#endif +#ifdef NAMED_SYNCHRO + mutex->name = name; + mutex->syncClass = syncClass; +#endif + } + + if ((mutex == NULL) || (mutex->mutex == NULL)) + { +#ifdef NAMED_SYNCHRO + /* logging depends on Mutexes, so we have to use the + * non-threaded version instead */ + log_add_nothread (log_Fatal, "Could not initialize mutex '%s':" + "aborting.", name); +#else + log_add_nothread (log_Fatal, "Could not initialize mutex:" + "aborting."); +#endif + exit (EXIT_FAILURE); + } + + return mutex; +} + +void +DestroyMutex_SDL (Mutex m) +{ + Mut *mutex = (Mut *)m; + SDL_DestroyMutex (mutex->mutex); + free (mutex); +} + +void +LockMutex_SDL (Mutex m) +{ + Mut *mutex = (Mut *)m; +#ifdef TRACK_CONTENTION + /* This code isn't really quite right; race conditions between + * check and lock remain and can produce reports of contention + * where the thread never sleeps, or fail to report in + * situations where it does. If tracking with perfect + * accuracy becomes important, the TRACK_CONTENTION mutex will + * need to handle its own wake/sleep cycles with condition + * variables (check the history of this file for the + * CrossThreadMutex code). This almost-measure is being added + * because for the most part it should suffice. */ + if (mutex->owner && (mutex->syncClass & TRACK_CONTENTION_CLASSES)) + { /* logging depends on Mutexes, so we have to use the + * non-threaded version instead */ + log_add_nothread (log_Debug, "Thread '%s' blocking on mutex '%s'", + MyThreadName (), mutex->name); + } +#endif + while (SDL_mutexP (mutex->mutex) != 0) + { + TaskSwitch_SDL (); + } +#ifdef TRACK_CONTENTION + mutex->owner = SDL_ThreadID (); +#endif +} + +void +UnlockMutex_SDL (Mutex m) +{ + Mut *mutex = (Mut *)m; +#ifdef TRACK_CONTENTION + mutex->owner = 0; +#endif + while (SDL_mutexV (mutex->mutex) != 0) + { + TaskSwitch_SDL (); + } +} + +/* Semaphores. */ + +typedef struct _sem { + SDL_sem *sem; +#ifdef NAMED_SYNCHRO + const char *name; + DWORD syncClass; +#endif +} Sem; + +Semaphore +CreateSemaphore_SDL (DWORD initial +#ifdef NAMED_SYNCHRO + , const char *name, DWORD syncClass +#endif + ) +{ + Sem *sem = (Sem *) HMalloc (sizeof (struct _sem)); +#ifdef NAMED_SYNCHRO + sem->name = name; + sem->syncClass = syncClass; +#endif + sem->sem = SDL_CreateSemaphore (initial); + if (sem->sem == NULL) + { +#ifdef NAMED_SYNCHRO + log_add (log_Fatal, "Could not initialize semaphore '%s':" + " aborting.", name); +#else + log_add (log_Fatal, "Could not initialize semaphore:" + " aborting."); +#endif + exit (EXIT_FAILURE); + } + return sem; +} + +void +DestroySemaphore_SDL (Semaphore s) +{ + Sem *sem = (Sem *)s; + SDL_DestroySemaphore (sem->sem); + HFree (sem); +} + +void +SetSemaphore_SDL (Semaphore s) +{ + Sem *sem = (Sem *)s; +#ifdef TRACK_CONTENTION + BOOLEAN contention = !(SDL_SemValue (sem->sem)); + if (contention && (sem->syncClass & TRACK_CONTENTION_CLASSES)) + { + log_add (log_Debug, "Thread '%s' blocking on semaphore '%s'", + MyThreadName (), sem->name); + } +#endif + while (SDL_SemWait (sem->sem) == -1) + { + TaskSwitch_SDL (); + } +#ifdef TRACK_CONTENTION + if (contention && (sem->syncClass & TRACK_CONTENTION_CLASSES)) + { + log_add (log_Debug, "Thread '%s' awakens," + " released from semaphore '%s'", MyThreadName (), sem->name); + } +#endif +} + +void +ClearSemaphore_SDL (Semaphore s) +{ + Sem *sem = (Sem *)s; + while (SDL_SemPost (sem->sem) == -1) + { + TaskSwitch_SDL (); + } +} + +/* Recursive mutexes. Adapted from mixSDL code, which was adapted from + the original DCQ code. */ + +typedef struct _recm { + SDL_mutex *mutex; + SDL_threadID thread_id; + Uint32 locks; +#ifdef NAMED_SYNCHRO + const char *name; + DWORD syncClass; +#endif +} RecM; + +RecursiveMutex +#ifdef NAMED_SYNCHRO +CreateRecursiveMutex_SDL (const char *name, DWORD syncClass) +#else +CreateRecursiveMutex_SDL (void) +#endif +{ + RecM *mtx = (RecM *) HMalloc (sizeof (struct _recm)); + + mtx->thread_id = 0; + mtx->mutex = SDL_CreateMutex (); + if (mtx->mutex == NULL) + { +#ifdef NAMED_SYNCHRO + log_add (log_Fatal, "Could not initialize recursive " + "mutex '%s': aborting.", name); +#else + log_add (log_Fatal, "Could not initialize recursive " + "mutex: aborting."); +#endif + exit (EXIT_FAILURE); + } +#ifdef NAMED_SYNCHRO + mtx->name = name; + mtx->syncClass = syncClass; +#endif + mtx->locks = 0; + return (RecursiveMutex) mtx; +} + +void +DestroyRecursiveMutex_SDL (RecursiveMutex val) +{ + RecM *mtx = (RecM *)val; + SDL_DestroyMutex (mtx->mutex); + HFree (mtx); +} + +void +LockRecursiveMutex_SDL (RecursiveMutex val) +{ + RecM *mtx = (RecM *)val; + SDL_threadID thread_id = SDL_ThreadID(); + if (!mtx->locks || mtx->thread_id != thread_id) + { +#ifdef TRACK_CONTENTION + if (mtx->thread_id && (mtx->syncClass & TRACK_CONTENTION_CLASSES)) + { + log_add (log_Debug, "Thread '%s' blocking on '%s'", + MyThreadName (), mtx->name); + } +#endif + while (SDL_mutexP (mtx->mutex)) + TaskSwitch_SDL (); + mtx->thread_id = thread_id; + } + mtx->locks++; +} + +void +UnlockRecursiveMutex_SDL (RecursiveMutex val) +{ + RecM *mtx = (RecM *)val; + SDL_threadID thread_id = SDL_ThreadID(); + if (!mtx->locks || mtx->thread_id != thread_id) + { +#ifdef NAMED_SYNCHRO + log_add (log_Debug, "'%s' attempted to unlock %s when it " + "didn't hold it", MyThreadName (), mtx->name); +#endif + } + else + { + mtx->locks--; + if (!mtx->locks) + { + mtx->thread_id = 0; + SDL_mutexV (mtx->mutex); + } + } +} + +int +GetRecursiveMutexDepth_SDL (RecursiveMutex val) +{ + RecM *mtx = (RecM *)val; + return mtx->locks; +} + +typedef struct _cond { + SDL_cond *cond; + SDL_mutex *mutex; +#ifdef NAMED_SYNCHRO + const char *name; + DWORD syncClass; +#endif +} cvar; + +CondVar +#ifdef NAMED_SYNCHRO +CreateCondVar_SDL (const char *name, DWORD syncClass) +#else +CreateCondVar_SDL (void) +#endif +{ + cvar *cv = (cvar *) HMalloc (sizeof (cvar)); + cv->cond = SDL_CreateCond (); + cv->mutex = SDL_CreateMutex (); + if ((cv->cond == NULL) || (cv->mutex == NULL)) + { +#ifdef NAMED_SYNCHRO + log_add (log_Fatal, "Could not initialize condition variable '%s':" + " aborting.", name); +#else + log_add (log_Fatal, "Could not initialize condition variable:" + " aborting."); +#endif + exit (EXIT_FAILURE); + } +#ifdef NAMED_SYNCHRO + cv->name = name; + cv->syncClass = syncClass; +#endif + return cv; +} + +void +DestroyCondVar_SDL (CondVar c) +{ + cvar *cv = (cvar *) c; + SDL_DestroyCond (cv->cond); + SDL_DestroyMutex (cv->mutex); + HFree (cv); +} + +void +WaitCondVar_SDL (CondVar c) +{ + cvar *cv = (cvar *) c; + SDL_mutexP (cv->mutex); +#ifdef TRACK_CONTENTION + if (cv->syncClass & TRACK_CONTENTION_CLASSES) + { + log_add (log_Debug, "Thread '%s' waiting for signal from '%s'", + MyThreadName (), cv->name); + } +#endif + while (SDL_CondWait (cv->cond, cv->mutex) != 0) + { + TaskSwitch_SDL (); + } +#ifdef TRACK_CONTENTION + if (cv->syncClass & TRACK_CONTENTION_CLASSES) + { + log_add (log_Debug, "Thread '%s' received signal from '%s'," + " awakening.", MyThreadName (), cv->name); + } +#endif + SDL_mutexV (cv->mutex); +} + +void +SignalCondVar_SDL (CondVar c) +{ + cvar *cv = (cvar *) c; + SDL_CondSignal (cv->cond); +} + +void +BroadcastCondVar_SDL (CondVar c) +{ + cvar *cv = (cvar *) c; + SDL_CondBroadcast (cv->cond); +} diff --git a/src/libs/threads/sdl/sdlthreads.h b/src/libs/threads/sdl/sdlthreads.h new file mode 100644 index 0000000..c93c947 --- /dev/null +++ b/src/libs/threads/sdl/sdlthreads.h @@ -0,0 +1,106 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_THREADS_SDL_SDLTHREADS_H_ +#define LIBS_THREADS_SDL_SDLTHREADS_H_ + +#include "port.h" +#include SDL_INCLUDE(SDL.h) +#include SDL_INCLUDE(SDL_thread.h) +#include "libs/threadlib.h" +#include "libs/timelib.h" + +void InitThreadSystem_SDL (void); +void UnInitThreadSystem_SDL (void); + +#ifdef NAMED_SYNCHRO +/* Prototypes with the "name" field */ +Thread CreateThread_SDL (ThreadFunction func, void *data, SDWORD stackSize, const char *name); +Mutex CreateMutex_SDL (const char *name, DWORD syncClass); +Semaphore CreateSemaphore_SDL (DWORD initial, const char *name, DWORD syncClass); +RecursiveMutex CreateRecursiveMutex_SDL (const char *name, DWORD syncClass); +CondVar CreateCondVar_SDL (const char *name, DWORD syncClass); +#else +/* Prototypes without the "name" field. */ +Thread CreateThread_SDL (ThreadFunction func, void *data, SDWORD stackSize); +Mutex CreateMutex_SDL (void); +Semaphore CreateSemaphore_SDL (DWORD initial); +RecursiveMutex CreateRecursiveMutex_SDL (void); +CondVar CreateCondVar_SDL (void); +#endif + +ThreadLocal *GetMyThreadLocal_SDL (void); + +void SleepThread_SDL (TimeCount sleepTime); +void SleepThreadUntil_SDL (TimeCount wakeTime); +void TaskSwitch_SDL (void); +void WaitThread_SDL (Thread thread, int *status); +void DestroyThread_SDL (Thread thread); + +void DestroyMutex_SDL (Mutex m); +void LockMutex_SDL (Mutex m); +void UnlockMutex_SDL (Mutex m); + +void DestroySemaphore_SDL (Semaphore sem); +void SetSemaphore_SDL (Semaphore sem); +void ClearSemaphore_SDL (Semaphore sem); + +void DestroyCondVar_SDL (CondVar c); +void WaitCondVar_SDL (CondVar c); +void SignalCondVar_SDL (CondVar c); +void BroadcastCondVar_SDL (CondVar c); + +void DestroyRecursiveMutex_SDL (RecursiveMutex m); +void LockRecursiveMutex_SDL (RecursiveMutex m); +void UnlockRecursiveMutex_SDL (RecursiveMutex m); +int GetRecursiveMutexDepth_SDL (RecursiveMutex m); + +#define NativeInitThreadSystem InitThreadSystem_SDL +#define NativeUnInitThreadSystem UnInitThreadSystem_SDL + +#define NativeGetMyThreadLocal GetMyThreadLocal_SDL + +#define NativeCreateThread CreateThread_SDL +#define NativeSleepThread SleepThread_SDL +#define NativeSleepThreadUntil SleepThreadUntil_SDL +#define NativeTaskSwitch TaskSwitch_SDL +#define NativeWaitThread WaitThread_SDL +#define NativeDestroyThread DestroyThread_SDL + +#define NativeCreateMutex CreateMutex_SDL +#define NativeDestroyMutex DestroyMutex_SDL +#define NativeLockMutex LockMutex_SDL +#define NativeUnlockMutex UnlockMutex_SDL + +#define NativeCreateSemaphore CreateSemaphore_SDL +#define NativeDestroySemaphore DestroySemaphore_SDL +#define NativeSetSemaphore SetSemaphore_SDL +#define NativeClearSemaphore ClearSemaphore_SDL + +#define NativeCreateCondVar CreateCondVar_SDL +#define NativeDestroyCondVar DestroyCondVar_SDL +#define NativeWaitCondVar WaitCondVar_SDL +#define NativeSignalCondVar SignalCondVar_SDL +#define NativeBroadcastCondVar BroadcastCondVar_SDL + +#define NativeCreateRecursiveMutex CreateRecursiveMutex_SDL +#define NativeDestroyRecursiveMutex DestroyRecursiveMutex_SDL +#define NativeLockRecursiveMutex LockRecursiveMutex_SDL +#define NativeUnlockRecursiveMutex UnlockRecursiveMutex_SDL +#define NativeGetRecursiveMutexDepth GetRecursiveMutexDepth_SDL + +#endif /* LIBS_THREADS_SDL_SDLTHREADS_H_ */ + diff --git a/src/libs/threads/thrcommon.c b/src/libs/threads/thrcommon.c new file mode 100644 index 0000000..bc54751 --- /dev/null +++ b/src/libs/threads/thrcommon.c @@ -0,0 +1,451 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include "libs/threadlib.h" +#include "libs/timelib.h" +#include "libs/log.h" +#include "libs/async.h" +#include "libs/memlib.h" +#include "thrcommon.h" + +#define LIFECYCLE_SIZE 8 +typedef struct { + ThreadFunction func; + void *data; + SDWORD stackSize; + Semaphore sem; + Thread value; +#ifdef NAMED_SYNCHRO + const char *name; +#endif +} SpawnRequest_struct; + +typedef SpawnRequest_struct *SpawnRequest; + +static Mutex lifecycleMutex; +static SpawnRequest pendingBirth[LIFECYCLE_SIZE]; +static Thread pendingDeath[LIFECYCLE_SIZE]; + +void +InitThreadSystem (void) +{ + int i; + NativeInitThreadSystem (); + for (i = 0; i < LIFECYCLE_SIZE; i++) + { + pendingBirth[i] = NULL; + pendingDeath[i] = NULL; + } + lifecycleMutex = CreateMutex ("Thread Lifecycle Mutex", SYNC_CLASS_RESOURCE); +} + +void +UnInitThreadSystem (void) +{ + NativeUnInitThreadSystem (); + DestroyMutex (lifecycleMutex); +} + +static Thread +FlagStartThread (SpawnRequest s) +{ + int i; + LockMutex (lifecycleMutex); + for (i = 0; i < LIFECYCLE_SIZE; i++) + { + if (pendingBirth[i] == NULL) + { + pendingBirth[i] = s; + UnlockMutex (lifecycleMutex); + if (s->sem) + { + Thread result; + SetSemaphore (s->sem); + DestroySemaphore (s->sem); + result = s->value; + HFree (s); + return result; + } + return NULL; + } + } + log_add (log_Fatal, "Thread Lifecycle array filled. This is a fatal error! Make LIFECYCLE_SIZE something larger than %d.", LIFECYCLE_SIZE); + exit (EXIT_FAILURE); +} + +void +FinishThread (Thread thread) +{ + int i; + LockMutex (lifecycleMutex); + for (i = 0; i < LIFECYCLE_SIZE; i++) + { + if (pendingDeath[i] == NULL) + { + pendingDeath[i] = thread; + UnlockMutex (lifecycleMutex); + return; + } + } + log_add (log_Fatal, "Thread Lifecycle array filled. This is a fatal error! Make LIFECYCLE_SIZE something larger than %d.", LIFECYCLE_SIZE); + exit (EXIT_FAILURE); +} + +/* Only call from main thread! */ +void +ProcessThreadLifecycles (void) +{ + int i; + LockMutex (lifecycleMutex); + for (i = 0; i < LIFECYCLE_SIZE; i++) + { + SpawnRequest s = pendingBirth[i]; + if (s != NULL) + { +#ifdef NAMED_SYNCHRO + s->value = NativeCreateThread (s->func, s->data, s->stackSize, s->name); +#else + s->value = NativeCreateThread (s->func, s->data, s->stackSize); +#endif + if (s->sem) + { + ClearSemaphore (s->sem); + /* The spawning thread's FlagStartThread will clean up s */ + } + else + { + /* The thread value has been lost to the game logic. We must + clean up s ourself. */ + HFree (s); + } + pendingBirth[i] = NULL; + } + } + + for (i = 0; i < LIFECYCLE_SIZE; i++) + { + Thread t = pendingDeath[i]; + if (t != NULL) + { + WaitThread (t, NULL); + pendingDeath[i] = NULL; + DestroyThread (t); + } + } + UnlockMutex (lifecycleMutex); +} + + +/* The Create routines look different based on whether NAMED_SYNCHRO + is defined or not. */ + +#ifdef NAMED_SYNCHRO +Thread +CreateThread_Core (ThreadFunction func, void *data, SDWORD stackSize, const char *name) +{ + SpawnRequest s = HMalloc(sizeof (SpawnRequest_struct)); + s->func = func; + s->data = data; + s->stackSize = stackSize; + s->name = name; + s->sem = CreateSemaphore (0, "SpawnRequest semaphore", SYNC_CLASS_RESOURCE); + return FlagStartThread (s); +} + +void +StartThread_Core (ThreadFunction func, void *data, SDWORD stackSize, const char *name) +{ + SpawnRequest s = HMalloc(sizeof (SpawnRequest_struct)); + s->func = func; + s->data = data; + s->stackSize = stackSize; + s->name = name; + s->sem = NULL; + FlagStartThread (s); +} + +Mutex +CreateMutex_Core (const char *name, DWORD syncClass) +{ + return NativeCreateMutex (name, syncClass); +} + +Semaphore +CreateSemaphore_Core (DWORD initial, const char *name, DWORD syncClass) +{ + return NativeCreateSemaphore (initial, name, syncClass); +} + +RecursiveMutex +CreateRecursiveMutex_Core (const char *name, DWORD syncClass) +{ + return NativeCreateRecursiveMutex (name, syncClass); +} + +CondVar +CreateCondVar_Core (const char *name, DWORD syncClass) +{ + return NativeCreateCondVar (name, syncClass); +} + +#else +/* These are the versions of Create* without the names. */ +Thread +CreateThread_Core (ThreadFunction func, void *data, SDWORD stackSize) +{ + SpawnRequest s = HMalloc(sizeof (SpawnRequest_struct)); + s->func = func; + s->data = data; + s->stackSize = stackSize; + s->sem = CreateSemaphore (0, "SpawnRequest semaphore", SYNC_CLASS_RESOURCE); + return FlagStartThread (s); +} + +void +StartThread_Core (ThreadFunction func, void *data, SDWORD stackSize) +{ + SpawnRequest s = HMalloc(sizeof (SpawnRequest_struct)); + s->func = func; + s->data = data; + s->stackSize = stackSize; + s->sem = NULL; + FlagStartThread (s); +} + +Mutex +CreateMutex_Core (void) +{ + return NativeCreateMutex (); +} + +Semaphore +CreateSemaphore_Core (DWORD initial) +{ + return NativeCreateSemaphore (initial); +} + +RecursiveMutex +CreateRecursiveMutex_Core (void) +{ + return NativeCreateRecursiveMutex (); +} + +CondVar +CreateCondVar_Core (void) +{ + return NativeCreateCondVar (); +} +#endif + +void +DestroyThread (Thread t) +{ + NativeDestroyThread (t); +} + +ThreadLocal * +CreateThreadLocal (void) +{ + ThreadLocal *tl = HMalloc (sizeof (ThreadLocal)); + tl->flushSem = CreateSemaphore (0, "FlushGraphics", SYNC_CLASS_VIDEO); + return tl; +} + +void +DestroyThreadLocal (ThreadLocal *tl) +{ + DestroySemaphore (tl->flushSem); + HFree (tl); +} + +ThreadLocal * +GetMyThreadLocal (void) +{ + return NativeGetMyThreadLocal (); +} + +void +WaitThread (Thread thread, int *status) +{ + NativeWaitThread (thread, status); +} + +#ifdef DEBUG_SLEEP +extern uint32 mainThreadId; +extern uint32 SDL_ThreadID(void); +#endif /* DEBUG_SLEEP */ + +void +HibernateThread (TimePeriod timePeriod) +{ +#ifdef DEBUG_SLEEP + if (SDL_ThreadID() == mainThreadId) + log_add (log_Debug, "HibernateThread called from main thread.\n"); +#endif /* DEBUG_SLEEP */ + + NativeSleepThread (timePeriod); +} + +void +HibernateThreadUntil (TimeCount wakeTime) +{ +#ifdef DEBUG_SLEEP + if (SDL_ThreadID() == mainThreadId) + log_add (log_Debug, "HibernateThreadUntil called from main " + "thread.\n"); +#endif /* DEBUG_SLEEP */ + + NativeSleepThreadUntil (wakeTime); +} + +void +SleepThread (TimePeriod timePeriod) +{ + TimeCount now; + +#ifdef DEBUG_SLEEP + if (SDL_ThreadID() != mainThreadId) + log_add (log_Debug, "SleepThread called from non-main " + "thread.\n"); +#endif /* DEBUG_SLEEP */ + + now = GetTimeCounter (); + SleepThreadUntil (now + timePeriod); +} + +// Sleep until wakeTime, but call asynchrounous operations until then. +void +SleepThreadUntil (TimeCount wakeTime) +{ +#ifdef DEBUG_SLEEP + if (SDL_ThreadID() != mainThreadId) + log_add (log_Debug, "SleepThreadUntil called from non-main " + "thread.\n"); +#endif /* DEBUG_SLEEP */ + + for (;;) { + uint32 nextTimeMs; + TimeCount nextTime; + TimeCount now; + + Async_process (); + + now = GetTimeCounter (); + if (wakeTime <= now) + return; + + nextTimeMs = Async_timeBeforeNextMs (); + nextTime = (nextTimeMs / 1000) * ONE_SECOND + + ((nextTimeMs % 1000) * ONE_SECOND / 1000); + // Overflow-safe conversion. + if (wakeTime < nextTime) + nextTime = wakeTime; + + NativeSleepThreadUntil (nextTime); + } +} + +void +TaskSwitch (void) +{ + NativeTaskSwitch (); +} + +void +DestroyMutex (Mutex sem) +{ + NativeDestroyMutex (sem); +} + +void +LockMutex (Mutex sem) +{ + NativeLockMutex (sem); +} + +void +UnlockMutex (Mutex sem) +{ + NativeUnlockMutex (sem); +} + +void +DestroySemaphore (Semaphore sem) +{ + NativeDestroySemaphore (sem); +} + +void +SetSemaphore (Semaphore sem) +{ + NativeSetSemaphore (sem); +} + +void +ClearSemaphore (Semaphore sem) +{ + NativeClearSemaphore (sem); +} + +void +DestroyCondVar (CondVar cv) +{ + NativeDestroyCondVar (cv); +} + +void +WaitCondVar (CondVar cv) +{ + NativeWaitCondVar (cv); +} + +void +SignalCondVar (CondVar cv) +{ + NativeSignalCondVar (cv); +} + +void +BroadcastCondVar (CondVar cv) +{ + NativeBroadcastCondVar (cv); +} + +void +DestroyRecursiveMutex (RecursiveMutex mutex) +{ + NativeDestroyRecursiveMutex (mutex); +} + +void +LockRecursiveMutex (RecursiveMutex mutex) +{ + NativeLockRecursiveMutex (mutex); +} + +void +UnlockRecursiveMutex (RecursiveMutex mutex) +{ + NativeUnlockRecursiveMutex (mutex); +} + +int +GetRecursiveMutexDepth (RecursiveMutex mutex) +{ + return NativeGetRecursiveMutexDepth (mutex); +} diff --git a/src/libs/threads/thrcommon.h b/src/libs/threads/thrcommon.h new file mode 100644 index 0000000..d6d6d41 --- /dev/null +++ b/src/libs/threads/thrcommon.h @@ -0,0 +1,28 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_THREADS_THRCOMMON_H_ +#define LIBS_THREADS_THRCOMMON_H_ + + +#if defined(THREADLIB_SDL) +# include "sdl/sdlthreads.h" +#elif defined(THREADLIB_PTHREAD) +# include "pthread/posixthreads.h" +#endif /* defined(THREADLIB_PTHREAD) */ + + +#endif /* _THR_COMMON_H */ diff --git a/src/libs/time/Makeinfo b/src/libs/time/Makeinfo new file mode 100644 index 0000000..14a7ca8 --- /dev/null +++ b/src/libs/time/Makeinfo @@ -0,0 +1,3 @@ +uqm_SUBDIRS="sdl" +uqm_CFILES="timecommon.c" +uqm_HFILES="timecommon.h" diff --git a/src/libs/time/sdl/Makeinfo b/src/libs/time/sdl/Makeinfo new file mode 100644 index 0000000..47180ad --- /dev/null +++ b/src/libs/time/sdl/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="sdltime.c" +uqm_HFILES="sdltime.h" diff --git a/src/libs/time/sdl/sdltime.c b/src/libs/time/sdl/sdltime.c new file mode 100644 index 0000000..35ab856 --- /dev/null +++ b/src/libs/time/sdl/sdltime.c @@ -0,0 +1,30 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* By Serge van den Boom + */ + +#include "sdltime.h" +#include "libs/timelib.h" + +Uint32 +SDLWrapper_GetTimeCounter (void) +{ + Uint32 ticks = SDL_GetTicks (); + return (ticks / 1000) * ONE_SECOND + ((ticks % 1000) * ONE_SECOND / 1000); + // Use the following instead when confirming "random" lockup bugs (see #668) + //return ticks * ONE_SECOND / 1000; +} diff --git a/src/libs/time/sdl/sdltime.h b/src/libs/time/sdl/sdltime.h new file mode 100644 index 0000000..ee72dc9 --- /dev/null +++ b/src/libs/time/sdl/sdltime.h @@ -0,0 +1,35 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* By Serge van den Boom, 2002-09-12 + */ + +#ifndef LIBS_TIME_SDL_SDLTIME_H_ +#define LIBS_TIME_SDL_SDLTIME_H_ + +#include "port.h" +#include SDL_INCLUDE(SDL.h) +#include "../timecommon.h" + +#define NativeInitTimeSystem() +#define NativeUnInitTimeSystem() +extern Uint32 SDLWrapper_GetTimeCounter (void); +#define NativeGetTimeCounter() \ + SDLWrapper_GetTimeCounter () + + +#endif /* LIBS_TIME_SDL_SDLTIME_H_ */ + diff --git a/src/libs/time/timecommon.c b/src/libs/time/timecommon.c new file mode 100644 index 0000000..a281efe --- /dev/null +++ b/src/libs/time/timecommon.c @@ -0,0 +1,41 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* By Serge van den Boom, 2002-09-12 + */ + +#include <stdio.h> +#include "libs/timelib.h" +#include "timecommon.h" + +void +InitTimeSystem (void) +{ + NativeInitTimeSystem (); +} + +void +UnInitTimeSystem (void) +{ + NativeUnInitTimeSystem (); +} + +TimeCount +GetTimeCounter (void) +{ + return NativeGetTimeCounter (); +} + diff --git a/src/libs/time/timecommon.h b/src/libs/time/timecommon.h new file mode 100644 index 0000000..ec5892c --- /dev/null +++ b/src/libs/time/timecommon.h @@ -0,0 +1,30 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* By Serge van den Boom, 2002-09-12 + */ + +#ifndef LIBS_TIME_TIMECOMMON_H_ +#define LIBS_TIME_TIMECOMMON_H_ + + +#if TIMELIB == SDL +#include "sdl/sdltime.h" +#endif /* TIMELIB == SDL */ + + +#endif /* LIBS_TIME_TIMECOMMON_H_ */ + diff --git a/src/libs/timelib.h b/src/libs/timelib.h new file mode 100644 index 0000000..8fc6522 --- /dev/null +++ b/src/libs/timelib.h @@ -0,0 +1,49 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_TIMELIB_H_ +#define LIBS_TIMELIB_H_ + +#define TIMELIB SDL + +#include "libs/compiler.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +/* ONE_SECOND is the LCM of all the fractions of a second the game uses. + * Battle is 24 FPS, Landers are 35 FPS, most UI-level things are 15 FPS, + * The Interplanetary flight is 30 FPS, Comm ambient animation is 40 FPS, + * (also Comm Oscilloscope is 32 FPS, but it does not require a stable + * timer and currently runs within the Comm ambient anim paradigm anyway) + * Thus, the minimum value for ONE_SECOND is 840. */ +#if TIMELIB == SDL +# define ONE_SECOND 840 +#endif + +typedef DWORD TimeCount; +typedef DWORD TimePeriod; + +extern void InitTimeSystem (void); +extern void UnInitTimeSystem (void); +extern TimeCount GetTimeCounter (void); + +#if defined(__cplusplus) +} +#endif + +#endif /* LIBS_TIMLIB_H_ */ diff --git a/src/libs/uio.h b/src/libs/uio.h new file mode 100644 index 0000000..f455049 --- /dev/null +++ b/src/libs/uio.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef LIBS_UIO_H_ +#define LIBS_UIO_H_ + +#if defined(__cplusplus) +extern "C" { +#endif + +#include "uio/io.h" + +#if defined(__cplusplus) +} +#endif + +#endif /* LIBS_UIO_H_ */ diff --git a/src/libs/uio/COPYING b/src/libs/uio/COPYING new file mode 100644 index 0000000..2774fef --- /dev/null +++ b/src/libs/uio/COPYING @@ -0,0 +1,350 @@ +Below the text of the GNU General Public License is quoted verbatim. +It applies to all files in and below this directory that say so. +Note that most of these files will say only version 2 of the +GNU General Public License applies, not any later version. + + +--------------------------------------------------------------------------- + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. + + diff --git a/src/libs/uio/Makeinfo b/src/libs/uio/Makeinfo new file mode 100644 index 0000000..9d127fc --- /dev/null +++ b/src/libs/uio/Makeinfo @@ -0,0 +1,22 @@ +uqm_SUBDIRS="stdio" +uqm_CFILES="charhashtable.c defaultfs.c fileblock.c fstypes.c gphys.c io.c + ioaux.c match.c mount.c mounttree.c paths.c physical.c uiostream.c + uioutils.c utils.c" +uqm_HFILES="charhashtable.h defaultfs.h fileblock.h fstypes.h getint.h + gphys.h ioaux.h io.h iointrn.h match.h mem.h mount.h mounttree.h + paths.h physical.h types.h uioport.h uiostream.h uioutils.h utils.h" + +if [ -n "$uqm_USE_ZIP_IO" ]; then + uqm_SUBDIRS="$uqm_SUBDIRS zip" +fi + +#if [ -n "$DEBUG" -o -n "$uqm_UIO_DEBUG" ]; then + uqm_CFILES="$uqm_CFILES debug.c" + uqm_HFILES="$uqm_HFILES debug.h" +#fi + +if [ -n "$MEMDEBUG" ]; then + uqm_CFILES="$uqm_CFILES hashtable.c memdebug.c" + uqm_HFILES="$uqm_HFILES hashtable.h memdebug.h" +fi + diff --git a/src/libs/uio/charhashtable.c b/src/libs/uio/charhashtable.c new file mode 100644 index 0000000..df10b2a --- /dev/null +++ b/src/libs/uio/charhashtable.c @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + + +#define HASHTABLE_INTERNAL +#include "charhashtable.h" +#include "types.h" +#include "uioport.h" + +static inline uio_uint32 CharHashTable_hash(CharHashTable_HashTable *hashTable, + const char *string); +static inline uio_bool CharHashTable_equal(CharHashTable_HashTable *hashTable, + const char *key1, const char *key2); +static inline char *CharHashTable_copy(CharHashTable_HashTable *hashTable, + const char *key); +static inline void CharHashTable_freeKey(CharHashTable_HashTable *hashTable, + char *key); + +#include "hashtable.c" + + +static inline uio_uint32 +CharHashTable_hash(CharHashTable_HashTable *hashTable, const char *key) { + uio_uint32 hash; + + (void) hashTable; + // Rotating hash, variation of something on the web which + // wasn't original itself. + hash = 0; + // Hash was on that web page initialised as the length, + // but that isn't known at this time. + while (*key != '\0') { + hash = (hash << 4) ^ (hash >> 28) ^ *key; + key++; + } + return hash ^ (hash >> 10) ^ (hash >> 20); +} + +static inline uio_bool +CharHashTable_equal(CharHashTable_HashTable *hashTable, + const char *key1, const char *key2) { + (void) hashTable; + return strcmp(key1, key2) == 0; +} + +static inline char * +CharHashTable_copy(CharHashTable_HashTable *hashTable, + const char *key) { + (void) hashTable; + return uio_strdup(key); +} + +static inline void +CharHashTable_freeKey(CharHashTable_HashTable *hashTable, + char *key) { + (void) hashTable; + uio_free(key); +} + + diff --git a/src/libs/uio/charhashtable.h b/src/libs/uio/charhashtable.h new file mode 100644 index 0000000..3cd0add --- /dev/null +++ b/src/libs/uio/charhashtable.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef LIBS_UIO_CHARHASHTABLE_H_ +#define LIBS_UIO_CHARHASHTABLE_H_ + + +#define HASHTABLE_(identifier) CharHashTable ## _ ## identifier +typedef char HASHTABLE_(Key); +typedef void HASHTABLE_(Value); +#define CharHashTable_HASH CharHashTable_hash +#define CharHashTable_EQUAL CharHashTable_equal +#define CharHashTable_COPY CharHashTable_copy +#define CharHashTable_FREEKEY CharHashTable_freeKey +#define CharHashTable_FREEVALUE(hashTable, value) \ + ((void) (hashTable), (void) (value)) + +#include "hashtable.h" + + +#endif /* _CHARHASHTABLE_H */ + diff --git a/src/libs/uio/debug.c b/src/libs/uio/debug.c new file mode 100644 index 0000000..66bab47 --- /dev/null +++ b/src/libs/uio/debug.c @@ -0,0 +1,914 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <time.h> +#if defined(__unix__) && !defined(_WIN32_WCE) +# include <sys/wait.h> +#endif + +#include "debug.h" +#include "uioport.h" +#include "io.h" +#include "utils.h" +#include "types.h" +#include "mem.h" +#include "uioutils.h" +#ifdef uio_MEM_DEBUG +# include "memdebug.h" +#endif + +#define LINEBUFLEN 1024 + +typedef struct DebugContext { + uio_bool exit; + FILE *in; + FILE *out; + FILE *err; + uio_DirHandle *cwd; +} DebugContext; + +typedef struct { + const char *name; + int (*fun)(DebugContext *, int, char *[]); +} DebugCommand; + +#ifdef STAND_ALONE +void initRepository(void); +void unInitRepository(void); +#endif + +static int debugCmdCat(DebugContext *debugContext, int argc, char *argv[]); +static int debugCmdCd(DebugContext *debugContext, int argc, char *argv[]); +static int debugCmdExec(DebugContext *debugContext, int argc, char *argv[]); +static int debugCmdExit(DebugContext *debugContext, int argc, char *argv[]); +static int debugCmdLs(DebugContext *debugContext, int argc, char *argv[]); +static int debugCmdMem(DebugContext *debugContext, int argc, char *argv[]); +static int debugCmdMkDir(DebugContext *debugContext, int argc, char *argv[]); +static int debugCmdMount(DebugContext *debugContext, int argc, char *argv[]); +static int debugCmdMv(DebugContext *debugContext, int argc, char *argv[]); +static int debugCmdPwd(DebugContext *debugContext, int argc, char *argv[]); +static int debugCmdRm(DebugContext *debugContext, int argc, char *argv[]); +static int debugCmdRmDir(DebugContext *debugContext, int argc, char *argv[]); +static int debugCmdStat(DebugContext *debugContext, int argc, char *argv[]); +static int debugCmdWriteTest(DebugContext *debugContext, int argc, + char *argv[]); +static int debugCmdFwriteTest(DebugContext *debugContext, int argc, + char *argv[]); + +static void makeArgs(char *lineBuf, int *argc, char ***argv); +static int debugCallCommand(DebugContext *debugContext, + int argc, char *argv[]); +uio_MountHandle * +debugMountOne(uio_Repository *destRep, const char *mountPoint, + uio_FileSystemID fsType, + uio_DirHandle *sourceDir, const char *sourcePath, + const char *inPath, uio_AutoMount **autoMount, int flags, + uio_MountHandle *relative); + +// In alphabetic order: +DebugCommand debugCommands[] = { + { "cat", debugCmdCat }, + { "cd", debugCmdCd }, + { "exec", debugCmdExec }, + { "exit", debugCmdExit }, + { "fwritetest", debugCmdFwriteTest }, + { "ls", debugCmdLs }, + { "mem", debugCmdMem }, + { "mkdir", debugCmdMkDir }, + { "mount", debugCmdMount }, + { "mv", debugCmdMv }, + { "pwd", debugCmdPwd }, + { "rm", debugCmdRm }, + { "rmdir", debugCmdRmDir }, + { "stat", debugCmdStat }, + { "writetest", debugCmdWriteTest }, +}; + + +#ifndef STAND_ALONE +extern uio_Repository *repository; +#else +uio_Repository *repository; +uio_MountHandle *mountHandles[9]; // TODO: remove (this is just a test) + +int +main(int argc, char *argv[]) { + initRepository(); + uio_debugInteractive(stdin, stdout, stderr); + unInitRepository(); + (void) argc; + (void) argv; + return EXIT_SUCCESS; +} + +uio_MountHandle * +debugMountOne(uio_Repository *destRep, const char *mountPoint, + uio_FileSystemID fsType, + uio_DirHandle *sourceDir, const char *sourcePath, + const char *inPath, uio_AutoMount **autoMount, int flags, + uio_MountHandle *relative) { + uio_MountHandle *mountHandle; + + mountHandle = uio_mountDir(destRep, mountPoint, fsType, sourceDir, + sourcePath, inPath, autoMount, flags, relative); + if (mountHandle == NULL) { + int savedErrno = errno; + fprintf(stderr, "Could not mount '%s' and graft '%s' from that " + "into the repository at '%s': %s\n", + sourcePath, inPath, mountPoint, strerror(errno)); + errno = savedErrno; + } + return mountHandle; +} + +void +initRepository(void) { + static uio_AutoMount autoMountZip = { + .pattern = "*.zip", + .matchType = match_MATCH_SUFFIX, + .fileSystemID = uio_FSTYPE_ZIP, + .mountFlags = uio_MOUNT_BELOW | uio_MOUNT_RDONLY + }; + static uio_AutoMount *autoMount[] = { + &autoMountZip, + NULL + }; + + uio_init(); + repository = uio_openRepository(0); + + memset(&mountHandles, '\0', sizeof mountHandles); +#if 1 + mountHandles[0] = debugMountOne(repository, "/", uio_FSTYPE_STDIO, + NULL, NULL, "/home/svdb/cvs/sc2/content", autoMount, + uio_MOUNT_TOP | uio_MOUNT_RDONLY, NULL); +#endif +#if 1 + mountHandles[1] = debugMountOne(repository, "/", uio_FSTYPE_STDIO, + NULL, NULL, "/home/svdb/cvs/sc2/src/sc2code/ships", + autoMount, uio_MOUNT_TOP | uio_MOUNT_RDONLY, NULL); +#endif +#if 1 + mountHandles[2] = debugMountOne(repository, "/", uio_FSTYPE_STDIO, + NULL, NULL, "/tmp/vfstest", autoMount, uio_MOUNT_TOP, NULL); +#endif +#if 1 + mountHandles[3] = debugMountOne(repository, "/", uio_FSTYPE_STDIO, + NULL, NULL, "/tmp/vfstest2", autoMount, uio_MOUNT_TOP, NULL); +#endif + + // TODO: should work too: +#if 0 + mountHandle[4] = debugMountOne(repository, "/zip/", uio_FSTYPE_ZIP, NULL, + NULL, "/ziptest/foo.zip", autoMount, uio_MOUNT_TOP, NULL); +#endif + { + uio_DirHandle *rootDir; + rootDir = uio_openDir(repository, "/", 0); + if (rootDir == NULL) { + fprintf(stderr, "Could not open '/' dir.\n"); + } else { +#if 1 + mountHandles[4] = debugMountOne(repository, "/example/", + uio_FSTYPE_ZIP, rootDir, "/example2.zip", "/", autoMount, + uio_MOUNT_TOP | uio_MOUNT_RDONLY, NULL); +#endif +#if 1 + mountHandles[5] = debugMountOne(repository, "/example/", + uio_FSTYPE_ZIP, rootDir, "/example/example.zip", "/", + autoMount, uio_MOUNT_TOP | uio_MOUNT_RDONLY, NULL); +#endif +#if 1 + mountHandles[6] = debugMountOne(repository, "/zip/", + uio_FSTYPE_ZIP, rootDir, "/voice.zip", "/", autoMount, + uio_MOUNT_TOP | uio_MOUNT_RDONLY, NULL); +#endif +#if 1 + mountHandles[7] = debugMountOne(repository, "/foo/", + uio_FSTYPE_ZIP, rootDir, "/foo2.zip", "/", autoMount, + uio_MOUNT_TOP | uio_MOUNT_RDONLY, NULL); +#endif + uio_closeDir(rootDir); + } + } + mountHandles[8] = debugMountOne(repository, "/tmp/", + uio_FSTYPE_STDIO, NULL, NULL, "/tmp/", autoMount, + uio_MOUNT_TOP, NULL); + +#if 1 + mountHandles[8] = debugMountOne(repository, "/root/root/", + uio_FSTYPE_STDIO, NULL, NULL, "/", autoMount, + uio_MOUNT_TOP | uio_MOUNT_RDONLY, NULL); +#endif + +} + +void +unInitRepository(void) { +#if 1 + int i; +// uio_printMountTree(stderr, repository->mountTree, 0); +// fprintf(stderr, "\n"); + for (i = 7; i >= 0; i--) { + if (mountHandles[i] != NULL) + uio_unmountDir(mountHandles[i]); +// uio_printMountTree(stderr, repository->mountTree, 0); +// uio_printMounts(stderr, repository); +// fprintf(stderr, "\n"); + } +#endif + uio_closeRepository(repository); + uio_unInit(); +} +#endif /* STAND_ALONE */ + +void +uio_debugInteractive(FILE *in, FILE *out, FILE *err) { + char lineBuf[LINEBUFLEN]; + size_t lineLen; + int argc; + char **argv; + DebugContext debugContext; + uio_bool interactive; + + memset(&debugContext, '\0', sizeof (DebugContext)); + debugContext.exit = false; + debugContext.in = in; + debugContext.out = out; + debugContext.err = err; + debugContext.cwd = uio_openDir(repository, "/", 0); + if (debugContext.cwd == NULL) { + fprintf(err, "Fatal: Could not open working dir.\n"); + abort(); + } + + interactive = isatty(fileno(in)); + do { + if (interactive) + fprintf(out, "> "); + if (fgets(lineBuf, LINEBUFLEN, in) == NULL) { + if (feof(in)) { + // user pressed ^D + break; + } + // error occured + clearerr(in); + continue; + } + lineLen = strlen(lineBuf); + if (lineBuf[lineLen - 1] != '\n' && lineBuf[lineLen - 1] != '\r') { + fprintf(err, "Too long command line.\n"); + // TODO: read until EOL + continue; + } + makeArgs(lineBuf, &argc, &argv); + if (argc == 0) { + uio_free(argv); + continue; + } + debugCallCommand(&debugContext, argc, argv); + uio_free(argv); + } while (!debugContext.exit); + if (interactive) + fprintf(out, "\n"); + uio_closeDir(debugContext.cwd); +} + +static void +makeArgs(char *lineBuf, int *argc, char ***argv) { + int numArg; + char **args; + char *ptr; + + numArg = 0; + ptr = lineBuf; + while(true) { + while (isspace((int) *ptr)) + ptr++; + if (*ptr == '\0') + break; + numArg++; + while (!isspace((int) *ptr)) + ptr++; + } + + args = uio_malloc((numArg + 1) * sizeof (char *)); + numArg = 0; + ptr = lineBuf; + while(true) { + while (isspace((int) *ptr)) + ptr++; + if (*ptr == '\0') + break; + args[numArg] = ptr; + numArg++; + while (!isspace((int) *ptr)) + ptr++; + if (*ptr == '\0') + break; + *ptr = '\0'; + ptr++; + } + args[numArg] = NULL; + *argv = args; + *argc = numArg; +} + +static int +debugCallCommand(DebugContext *debugContext, int argc, char *argv[]) { + int i; + int numDebugCommands; + + i = 0; + numDebugCommands = sizeof debugCommands / sizeof debugCommands[0]; + // could be improved with binary search + while(1) { + if (i == numDebugCommands) { + fprintf(debugContext->err, "Invalid command.\n"); + return 1; + } + if (strcmp(argv[0], debugCommands[i].name) == 0) + break; + i++; + } + return debugCommands[i].fun(debugContext, argc, argv); +} + +static int +debugCmdCat(DebugContext *debugContext, int argc, char *argv[]) { + uio_Handle *handle; +#define READBUFSIZE 0x10000 + char readBuf[READBUFSIZE]; + char *bufPtr; + ssize_t numInBuf, numWritten; + size_t totalWritten; + + if (argc != 2) { + fprintf(debugContext->err, "Invalid number of arguments.\n"); + return 1; + } + + handle = uio_open(debugContext->cwd, argv[1], O_RDONLY +#ifdef WIN32 + | O_BINARY +#endif + , 0); + if (handle == NULL) { + fprintf(debugContext->err, "Could not open file: %s\n", + strerror(errno)); + return 1; + } + + totalWritten = 0; + while (1) { + numInBuf = uio_read(handle, readBuf, READBUFSIZE); + if (numInBuf == -1) { + if (errno == EINTR) + continue; + fprintf(debugContext->err, "Could not read from file: %s\n", + strerror(errno)); + uio_close(handle); + return 1; + } + if (numInBuf == 0) + break; + bufPtr = readBuf; + do { + numWritten = write(fileno(debugContext->out), bufPtr, numInBuf); + if (numWritten == -1) + { + if (errno == EINTR) + continue; + fprintf(debugContext->err, "Could not read from file: %s\n", + strerror(errno)); + uio_close(handle); + } + numInBuf -= numWritten; + bufPtr += numWritten; + totalWritten += numWritten; + } while (numInBuf > 0); + } + fprintf(debugContext->out, "[%u bytes]\n", (unsigned int) totalWritten); + + uio_close(handle); + return 0; +} + +static int +debugCmdCd(DebugContext *debugContext, int argc, char *argv[]) { + uio_DirHandle *newWd; + + if (argc != 2) { + fprintf(debugContext->err, "Invalid number of arguments.\n"); + return 1; + } + newWd = uio_openDirRelative(debugContext->cwd, argv[1], 0); + if (newWd == NULL) { + fprintf(debugContext->err, "Could not access new dir: %s\n", + strerror(errno)); + return 1; + } + uio_closeDir(debugContext->cwd); + debugContext->cwd = newWd; + return 0; +} + +static int +debugCmdExec(DebugContext *debugContext, int argc, char *argv[]) { + int i; + const char **newArgs; + int errCode = 0; + uio_StdioAccessHandle **handles; + uio_DirHandle *tempDir; + + if (argc < 2) { + fprintf(debugContext->err, "Invalid number of arguments.\n"); + return 1; + } + + tempDir = uio_openDirRelative(debugContext->cwd, "/tmp", 0); + if (tempDir == 0) { + fprintf(debugContext->err, "Could not open temp dir: %s.\n", + strerror(errno)); + return 1; + } + + newArgs = uio_malloc(argc * sizeof (char *)); + newArgs[0] = argv[1]; + handles = uio_malloc(argc * sizeof (uio_StdioAccessHandle *)); + handles[0] = NULL; + + for (i = 2; i < argc; i++) { +#if 0 + if (argv[i][0] == '-') { + // Don't try to parse arguments that start with '-'. + // They are probably option flags. + newArgs[i - 1] = argv[i]; + } +#endif + handles[i - 1] = uio_getStdioAccess(debugContext->cwd, argv[i], + O_RDONLY, tempDir); + if (handles[i - 1] == NULL) { + if (errno == ENOENT) { + // No match; we keep what's typed literally. + newArgs[i - 1] = argv[i]; + continue; + } + + // error + fprintf(debugContext->err, + "Cannot execute: Cannot get stdio access to %s: %s.\n", + argv[i], strerror(errno)); + errCode = 1; + argc = i + 1; + goto err; + } + + newArgs[i - 1] = uio_StdioAccessHandle_getPath(handles[i - 1]); + } + newArgs[argc - 1] = NULL; + + fprintf(debugContext->err, "Executing: %s", newArgs[0]); + for (i = 1; i < argc - 1; i++) + fprintf(debugContext->err, " %s", newArgs[i]); + fprintf(debugContext->err, "\n"); + +#if defined(__unix__) && !defined(_WIN32_WCE) + { + pid_t pid; + + pid = fork(); + switch (pid) { + case -1: + fprintf(debugContext->err, "Error: fork() failed: %s.\n", + strerror(errno)); + break; + case 0: + // child + execvp(newArgs[0], (char * const *) newArgs); + fprintf(debugContext->err, "Error: execvp() failed: %s.\n", + strerror(errno)); + _exit(EXIT_FAILURE); + break; + default: { + // parent + int status; + pid_t retVal; + + while (1) { + retVal = waitpid(pid, &status, 0); + if (retVal != -1) + break; + if (errno != EINTR) { + fprintf(debugContext->err, "Error: waitpid() " + "failed: %s\n", strerror(errno)); + break; + } + } + if (retVal == -1) + break; + + if (WIFEXITED(status)) { + fprintf(debugContext->err, "Exit status: %d\n", + WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + fprintf(debugContext->err, "Terminated on signal %d.\n", + WTERMSIG(status)); + } else { + fprintf(debugContext->err, "Error: weird exit status.\n"); + } + break; + } + } + } +#else + fprintf(debugContext->err, "Cannot execute: not supported on this " + "platform.\n"); +#endif + +err: + for (i = 1; i < argc - 1; i++) { + if (handles[i] != NULL) + uio_releaseStdioAccess(handles[i]); + } + uio_free(handles); + uio_free((void *) newArgs); + uio_closeDir(tempDir); + + return errCode; +} + +static int +debugCmdExit(DebugContext *debugContext, int argc, char *argv[]) { + debugContext->exit = true; + (void) argc; + (void) argv; + return 0; +} + +static int +debugCmdFwriteTest(DebugContext *debugContext, int argc, char *argv[]) { + uio_Stream *stream; + const char testString[] = "0123456789\n"; + + if (argc != 2) { + fprintf(debugContext->err, "Invalid number of arguments.\n"); + return 1; + } + + stream = uio_fopen(debugContext->cwd, argv[1], "w+b"); + if (stream == NULL) { + fprintf(debugContext->err, "Could not open file: %s\n", + strerror(errno)); + goto err; + } + + if (uio_fwrite(testString, strlen(testString), 1, stream) != 1) { + fprintf(debugContext->err, "uio_fwrite() failed: %s\n", + strerror(errno)); + goto err; + } + if (uio_fputs(testString, stream) == EOF) { + fprintf(debugContext->err, "uio_fputs() failed: %s\n", + strerror(errno)); + goto err; + } + if (uio_fseek(stream, 15, SEEK_SET) != 0) { + fprintf(debugContext->err, "uio_fseek() failed: %s\n", + strerror(errno)); + goto err; + } + if (uio_fputc('A', stream) != 'A') { + fprintf(debugContext->err, "uio_fputc() failed: %s\n", + strerror(errno)); + goto err; + } + if (uio_fseek(stream, 0, SEEK_SET) != 0) { + fprintf(debugContext->err, "uio_fseek() failed: %s\n", + strerror(errno)); + goto err; + } + { + char buf[6]; + char *ptr; + int i; + i = 1; + while (1) { + ptr = uio_fgets(buf, sizeof buf, stream); + if (ptr == NULL) + break; + fprintf(debugContext->out, "%d: [%s]\n", i, ptr); + i++; + } + if (uio_ferror(stream)) { + fprintf(debugContext->err, "uio_fgets() failed: %s\n", + strerror(errno)); + goto err; + } + uio_clearerr(stream); + } + if (uio_fseek(stream, 4, SEEK_END) != 0) { + fprintf(debugContext->err, "uio_fseek() failed: %s\n", + strerror(errno)); + goto err; + } + { + char buf[2000]; + memset(buf, 'Q', sizeof buf); + if (uio_fwrite(buf, 100, 20, stream) != 20) { + fprintf(debugContext->err, "uio_fwrite() failed: %s\n", + strerror(errno)); + goto err; + } + } + if (uio_fseek(stream, 5, SEEK_SET) != 0) { + fprintf(debugContext->err, "uio_fseek() failed: %s\n", + strerror(errno)); + goto err; + } + if (uio_fputc('B', stream) != 'B') { + fprintf(debugContext->err, "uio_fputc() failed: %s\n", + strerror(errno)); + goto err; + } + uio_fclose(stream); + return 0; +err: + uio_fclose(stream); + return 1; +} + +static int +listOneDir(DebugContext *debugContext, const char *arg) { + uio_DirList *dirList; + int i; + const char *pattern; + const char *cpath; + char *buf = NULL; + + if (arg[0] == '\0') { + cpath = arg; + pattern = "*"; + } else { + pattern = strrchr(arg, '/'); + if (pattern == NULL) { + // No directory component in 'arg'. + cpath = ""; + pattern = arg; + } else if (pattern[1] == '\0') { + // 'arg' ends on / + cpath = arg; + pattern = "*"; + } else { + if (pattern == arg) { + cpath = "/"; + } else { + buf = uio_malloc(pattern - arg + 1); + memcpy(buf, arg, pattern - arg); + buf[pattern - arg] = '\0'; + cpath = buf; + } + pattern++; + } + } +#ifdef HAVE_GLOB + dirList = uio_getDirList(debugContext->cwd, cpath, pattern, + match_MATCH_GLOB); +#else + if (pattern[0] == '*' && pattern[1] == '\0') { + dirList = uio_getDirList(debugContext->cwd, cpath, "", + match_MATCH_PREFIX); + } else { + dirList = uio_getDirList(debugContext->cwd, cpath, pattern, + match_MATCH_LITERAL); + } +#endif + if (dirList == NULL) { + fprintf(debugContext->out, "Error in uio_getDirList(): %s.\n", + strerror(errno)); + if (buf != NULL) + uio_free(buf); + return 1; + } + for (i = 0; i < dirList->numNames; i++) + fprintf(debugContext->out, "%s\n", dirList->names[i]); + uio_DirList_free(dirList); + if (buf != NULL) + uio_free(buf); + return 0; +} + +static int +debugCmdLs(DebugContext *debugContext, int argc, char *argv[]) { + int argI; + int retVal; + + if (argc == 1) + return listOneDir(debugContext, unconst("")); + + for (argI = 1; argI < argc; argI++) { + retVal = listOneDir(debugContext, argv[argI]); + if (retVal != 0) + return retVal; + } + + return 0; +} + +static int +debugCmdMem(DebugContext *debugContext, int argc, char *argv[]) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_printPointers(debugContext->out); +#else + fprintf(debugContext->out, "Memory debugging not compiled in.\n"); +#endif + (void) argc; + (void) argv; + return 0; +} + +static int +debugCmdMkDir(DebugContext *debugContext, int argc, char *argv[]) { + int retVal; + + if (argc != 2) { + fprintf(debugContext->err, "Invalid number of arguments.\n"); + return 1; + } + + retVal = uio_mkdir(debugContext->cwd, argv[1], 0777); + if (retVal == -1) { + fprintf(debugContext->err, "Could not create directory: %s\n", + strerror(errno)); + return 1; + } + return 0; +} + +static int +debugCmdMount(DebugContext *debugContext, int argc, char *argv[]) { + if (argc == 1) { + uio_printMounts(debugContext->out, repository); +// uio_printMountTree(debugContext->out, repository->mountTree, 0); + } + (void) argv; + return 0; +} + +static int +debugCmdMv(DebugContext *debugContext, int argc, char *argv[]) { + int retVal; + + if (argc != 3) { + fprintf(debugContext->err, "Invalid number of arguments.\n"); + return 1; + } + + retVal = uio_rename(debugContext->cwd, argv[1], + debugContext->cwd, argv[2]); + if (retVal == -1) { + fprintf(debugContext->err, "Could not rename: %s\n", strerror(errno)); + return 1; + } + return 0; +} + +static int +debugCmdPwd(DebugContext *debugContext, int argc, char *argv[]) { + uio_DirHandle_print(debugContext->cwd, debugContext->out); + (void) argc; + (void) argv; + return 0; +} + +static int +debugCmdRm(DebugContext *debugContext, int argc, char *argv[]) { + int retVal; + + if (argc != 2) { + fprintf(debugContext->err, "Invalid number of arguments.\n"); + return 1; + } + + retVal = uio_unlink(debugContext->cwd, argv[1]); + if (retVal == -1) { + fprintf(debugContext->err, "Could not remove file: %s\n", + strerror(errno)); + return 1; + } + return 0; +} + +static int +debugCmdRmDir(DebugContext *debugContext, int argc, char *argv[]) { + int retVal; + + if (argc != 2) { + fprintf(debugContext->err, "Invalid number of arguments.\n"); + return 1; + } + + retVal = uio_rmdir(debugContext->cwd, argv[1]); + if (retVal == -1) { + fprintf(debugContext->err, "Could not remove directory: %s\n", + strerror(errno)); + return 1; + } + return 0; +} + +static int +debugCmdStat(DebugContext *debugContext, int argc, char *argv[]) { + struct stat statBuf; + + if (argc != 2) { + fprintf(debugContext->err, "Invalid number of arguments.\n"); + return 1; + } + + if (uio_stat(debugContext->cwd, argv[1], &statBuf) == -1) { + // errno is set + int savedErrno; + savedErrno = errno; + fprintf(debugContext->err, "Could not stat file: %s\n", + strerror(errno)); + errno = savedErrno; + return 1; + } + + fprintf(debugContext->out, + "size %ld bytes\n" + "uid %d gid %d mode 0%o\n", + (unsigned long) statBuf.st_size, + (unsigned int) statBuf.st_uid, + (unsigned int) statBuf.st_gid, + (unsigned int) statBuf.st_mode & + (S_IRWXU | S_IRWXG | S_IRWXO | S_ISUID | S_ISGID)); + // Can't do these next three in one fprintf, as ctime uses a static buffer + // that is overwritten with each call. + fprintf(debugContext->out, + "last access: %s", ctime(&statBuf.st_atime)); + fprintf(debugContext->out, + "last modification: %s", ctime(&statBuf.st_mtime)); + fprintf(debugContext->out, + "last status change: %s", ctime(&statBuf.st_ctime)); + + return 0; +} + +static int +debugCmdWriteTest(DebugContext *debugContext, int argc, char *argv[]) { + uio_Handle *handle; + const char testString[] = "Hello world!\n"; + + if (argc != 2) { + fprintf(debugContext->err, "Invalid number of arguments.\n"); + return 1; + } + + handle = uio_open(debugContext->cwd, argv[1], + O_WRONLY | O_CREAT | O_EXCL +#ifdef WIN32 + | O_BINARY +#endif + , 0644); + if (handle == NULL) { + fprintf(debugContext->err, "Could not open file: %s\n", + strerror(errno)); + return 1; + } + + if (uio_write(handle, testString, sizeof testString) == -1) { + fprintf(debugContext->err, "Write failed: %s\n", + strerror(errno)); + return 1; + } + + uio_close(handle); + return 0; +} + + diff --git a/src/libs/uio/debug.h b/src/libs/uio/debug.h new file mode 100644 index 0000000..fd6a3ff --- /dev/null +++ b/src/libs/uio/debug.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef UIO_DEBUG_H +#define UIO_DEBUG_H + +void uio_debugInteractive(FILE *in, FILE *out, FILE *err); + + +#endif /* _DEBUG_H */ + + diff --git a/src/libs/uio/defaultfs.c b/src/libs/uio/defaultfs.c new file mode 100644 index 0000000..bf6d247 --- /dev/null +++ b/src/libs/uio/defaultfs.c @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "defaultfs.h" +#include "uioport.h" + +extern uio_FileSystemHandler stdio_fileSystemHandler; +#ifdef HAVE_ZIP +extern uio_FileSystemHandler zip_fileSystemHandler; +#endif + +const uio_DefaultFileSystemSetup defaultFileSystems[] = { + { uio_FSTYPE_STDIO, "stdio", &stdio_fileSystemHandler }, +#ifdef HAVE_ZIP + { uio_FSTYPE_ZIP, "zip", &zip_fileSystemHandler }, +#endif +}; + +int +uio_numDefaultFileSystems(void) { + return sizeof defaultFileSystems / sizeof defaultFileSystems[0]; +} + + diff --git a/src/libs/uio/defaultfs.h b/src/libs/uio/defaultfs.h new file mode 100644 index 0000000..711ac0c --- /dev/null +++ b/src/libs/uio/defaultfs.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef uio_DEFAULTFS_H +#define uio_DEFAULTFS_H + +typedef struct uio_DefaultFileSystemSetup uio_DefaultFileSystemSetup; + +#include "iointrn.h" +#include "uioport.h" +#include "fstypes.h" + +struct uio_DefaultFileSystemSetup { + uio_FileSystemID id; + const char *name; + uio_FileSystemHandler *handler; +}; + +extern const uio_DefaultFileSystemSetup defaultFileSystems[]; + +int uio_numDefaultFileSystems(void); + +#endif /* uio_DEFAULTFS_H */ + diff --git a/src/libs/uio/doc/basics b/src/libs/uio/doc/basics new file mode 100644 index 0000000..e04afd8 --- /dev/null +++ b/src/libs/uio/doc/basics @@ -0,0 +1,178 @@ +-= Introduction =- + +The file io system I present here provides the user with a virtual file +systems on which normal POSIX-like file operations can be performed. + +A virtual file system consists of a number of underlying filesystems merged +together in one directory structure. +Underlying file systems can be POSIX filesystems, or zip archives, but +others can be added easilly. +Filesystems are grafted into the virtual filesystem either explicitely, by +mounting, or implicitely, by having files of a specific type (for instance +those ending on .zip) mounted automatically. +When a filesystem is mounted to a directory which already exists in an +earlier mounted file system, files in the later mounted file system hide +files in an earlier mounted file system. Files present in a filesystem +mounted earlier that don't exist in a filesystem mounted later will remain +visible. +Accessing compressed files inside compressed files is possible, though slow. + + +-= Nomenclature =- +'repository' +A collection of files from various sources as a virtual file system. + +'physical directory structure' +An underlying filesystem, such as a POSIX filesystem, or a .zip archive. + +'logical directory structure' +The merger of one or more Physical directory structures. + +'mounting' +Grafting a physical directory structure in a logical directory system. + +When mounting several dirs on top of eachother, I refer to later mounted +dirs as 'higher' dirs, and earlier mounted dirs as 'lower' dirs. + +'directory entry' +A file or subdirectory within a directory. + + +-= API =- + +Types: +uio_Repository - A struct describing a repository +uio_Handle - A handle for working on files +uio_DirHandle - A handle for working on directories + + +TODO: functions + + +-= Behaviour relating directory structures =- + +The design of the virtual filesystem is guided by the following rules: +- Combined directories contain the entries of each directory. + If some of the source directories contain an entry with the same name, + the combined directory will contain the entry of the topmost directory. + (this means a directory can hide a file and the other way around) +- Entries hidden in this way are never accessable. +- Where possible, actions on directory entries within a combined directory + are done as in a normal file system. +Because of these, some design decisions have been made: +- New entries are created in the topmost writable filesystem. If the path to + the directory where the entry is to be created does not exist, it is + created [1]. +- When a file is to be opened for writing, and the file in the combined + filesystem exists, but is not writable, and there exists a writable + filesystem, mounted higher than the filesystem where the existing file + resides, the file is first copied to the topmost writable filesystem. + +[1] I could have decided to use a pre-existing writable directory if one + is available. + With this choice, when no such pre-existing directory exists, + it would make sense to complete the path in the writable filesystem + in which the part of the path that does exist is the longest. + As you can't create a directory inside a specific filesystem, + this would complicate and confuse things for the user. + + +In specific, for various actions: + +opening a file: +- if O_RDONLY: + - open the file in the highest dir. +- if O_WRONLY or O_RDWR: + - if file already exists: + - if O_CREAT and O_EXCL: return EEXIST + - if file is writable, use that one + - if file is not writable, copy it to the highest writable location + higher than the location of that file (don't bother if O_TRUNC) and use + the new file. If necessary, create the path leading upto it. + If no such location exists, return EACCESS. + - if file does not exist: + - if not O_CREAT: return ENOENT + - if the path to the file does not exists, return ENOENT. + - find the highest writable dir and open the file there, creating + the path leading upto it if necessary. + +removing a file: +- try removing the specified file from all physical directory structures + for a repository. +- once a file is encountered that can't be removed, return an error for + that and don't try the rest. + +creating a directory: +- as for opening a file with O_WRONLY | O_CREAT | O_EXCL + +removing a directory: +- as for removing a file + (but a physical directory not being empty is a reason for failure) + + +-= Limitations =- + +There's no limit to the length of a path in the logical file system. +Paths in the underlying physical filesystem can be limited though. + +At the moment, the system is not thread safe. Only one thread should access +a repository at once. Seperate threads working on seperate repositories is +no problem, as long as the repositories don't overlap. + + +-= Internals =- + +Types: + +uio_MountTree + A node in a data structure describing the mounted directories. + +uio_PRoot + A struct describing the a physical file system. + +uio_PRootExtra + Filesystem-dependant extra data for a PRoot. + +uio_GPRoot + Generic filesystem-dependant data for a PRoot, used as uio_PRootExtra. + +uio_GPRootExtra + Extra filesystem-dependant data for a PRoot, when using uio_GPRoot for + generic filesystem-dependant data. + +uio_GPDir + Generic structure representing a node in a physical directory structure + describing one directory. + +uio_GPFile + Generic structure describing a file in a physical file system. + + +Helper functions (defined in ioaux.c): + +uio_copyFilePhysical + Copy a file from one physical directory to another. + +uio_getPathPhysicalDirs + Get handles to the (existing) physical dirs that are effective in a + path 'path' relative from 'dirHandle' + +uio_getPhysicalAccess + Find PDirHandle and MountInfo structures for reading and writing for a path + from a DirHandle. + +uio_makePath + Create a directory inside a physical directory. All non-existant + parent directories will be created as well. + +uio_resolvePath + Determine the absolute path given a path relative to a given directory. + +uio_verifyPath + Test whether a path is valid and exists. + +uio_walkPhysicalPath + Follow a path starting from a specified physical dir for as long as + possible. + + diff --git a/src/libs/uio/doc/conventions b/src/libs/uio/doc/conventions new file mode 100644 index 0000000..f156101 --- /dev/null +++ b/src/libs/uio/doc/conventions @@ -0,0 +1,30 @@ +This file describes the various conventions used in the source. + + +-= Naming of constructors and destructors =- + +uio_Thing *uio_Thing_new(args) + Allocates the structure, and initialises it from the arguments. + Arguments are always used as-is; if they are reference-counted, + and the caller still needs a reference, it is up to the caller + to increment the reference counter. +void uio_Thing_delete(uio_Thing *thing) + Frees the structure and all sub-structures. + Decrements reference counters to shared structures. +uio_Thing *uio_Thing_alloc() + Allocates memory for a new structure. + Could be omited for standard allocators. + Particularly relevant when a non-standard allocation scheme is + used (for instance, allocation from a pool of pre-allocated strucures). +void uio_Thing_free(Thing *thing) + Frees the memory allocated for thing (reverse of uio_Thing_alloc). + Could be omited for standard deallocators. +void uio_Thing_ref(Thing *thing) + Increments the reference counter to the structure. +void uio_Thing_unref(Thing *thing) + Decrements the reference counter to the structure. + Calls uio_Thing_Delete() if the refcounter becomes 0. + +These functions are always declared and defined in this order. + + diff --git a/src/libs/uio/doc/todo b/src/libs/uio/doc/todo new file mode 100644 index 0000000..b9f232c --- /dev/null +++ b/src/libs/uio/doc/todo @@ -0,0 +1,144 @@ +Needed for use in UQM: +- documentation +- configuring for GLOB +- when doing uio_getStdioPhysical(), if write access is required, but + not available on the original location, also copy the file to the + temporary dir. +- Call fsync() at appropriate times. + +Documentation: +- use doxygen +- Warning: getting (part of) the contents of a directory is fairly slow, + as dirs need to be merged. +- It would be (theoretically) possible to add HTTP and FTP support for + remote file systems. +- on adding extra file system types: + open, mkdir, rmdir, and unlink should themselves make sure that + the physical structure is kept up to date when an entry is + removed or added. +- stream stuff is not thread safe +- uio_fflush() does not accept NULL as argument to flush all streams, + like stdio fflush() does. +- uio_close() does never fail +- The physical open function should call uio_Handle_new and store its own + data in it. + The physical close function should delete its own data. It's called + Will cleanup the general Handle. + Analogous for mount/unmount with PRoot +- No need to store MountHandles. They will be automatically freed + when the repository is closed. +- You can use mount stuff from other repositories. +- ".." works by nullifying one path component, not by following the ".." link + in the directory. "/a/../b" will always be functionally equivalent to + "/b", even when "/a" is a symlink. + +Testing: +- Test mounting an UNC directory + +Bugs: +- 'openDir(repository, "dir/")' will have the trailing '/' + in the dirHandle, which will cause problems. +- 'openDirRelative(repository, "/")' causes segfaults later on +- uio_rename() doesn't work on directories +- uio_getPhysicalAccess() needs to be changed so that it works the same on + dirs as on files. stat() can be cleaned up too then. +- uio_getPhysicalAccess() will not return ENOENT when (only) the last + component does not exist, even when O_RDONLY is used. + For O_RDRW, O_CREAT should probably be checked too (function description + needs to be updated too then). +- A lot of inlining is not possible because of the order of function + definitions. +- sizeof(size_t) may be less than 4 on some platforms. Check what problems + this may cause. At least the size of zip_INPUT_BUFFER_SIZE is an + issue. SIZE_MAX can be used to check for sizeof(size_t) at runtime. +- remove() is probably more compatible than unlink(). remove() is part + of the C standard (as well as POSIX), unlink() is just POSIX. +- seeking in files opened as "text" on Windows goes wrong. +- Network paths on Windows are not accepted. +- No CRLF translation (and ^Z recognition) is done for files read from + zip files, even though that may be expected on Windows. +- The order in which "uio_unmountAllDirs() unmounts the directories, may lead + it to unmount a dir while there still is a file descriptor for another + dir open, causing a warning. + +Extra features (not necessary for UQM): +- Make functions to use for uio_malloc, uio_free and uio_realloc + configurable at init. +- add uio_mmap() +- add match_MATCH_ALL and match_MATCH_NONE +- automounting + - Unmount automounted filesystems when the originating filesystem + is unmounted. +- when doing stat() on a directory, merge the stat info of the underlying + physical dirs. +- read directory information from zip files. +- implement ungetc() +- make fileblocks public +- setmode() for windows? +- prevent aliasing of files/dirs, both between two physical file + systems (where possible), as within the same filesystem (in particular + stdio). On Windows, aliasing can occur easilly when a file or dir + is accessed with the long vs the short ("PROGRA~1") name, or when + capitalisation is involved. +- accept non-dir, non-regular-file dir entries in stdio. +- Add non-merging mounts. Make 'merging' an option for the mount. +- make uio_rename() work cross-fs, or provide a wrapper which does that. + (the system rename() doesn't work cross-fs either, so keeping uio_rename() + as it is makes sense, as I'm trying to stay close to the system functions, + even though hiding file systems from the user would be nicer) +- Add a readdir_r(). Right now, for Symbian readdir_r() is defined in + uioport.h, but the actual implementation is out of the uio tree. + +Optimisations (not necessary for UQM): +- use mmap for fileBlocks +- Use a pre-allocated pool of hash table entries for allocHashEntry. +- optimise certain strings (specifically directory entries) by making + a string type which has pointers to a shared char array. + Invariant: if two strings are the same, they point to the same + character array. Consequence: string comparison is pointer comparison. + Making a new string would imply checking the existing strings. + Existing strings should be in a hash table. +- optimise paths + Encode all paths internally as (double-linked) chains of path components + This way, operations like going up one path component, are cheap. + Each path component could either be represented by a pointer to a string, + (possibly shared as above), or as a pair of begin and end pointers + (or begin pointer and length). In the latter case, there's no need for + copying strings when parsing a user-supplied string, but sharing of strings + would not be possible. + It's probably possible to use both methods next to eachother; + shared strings for stored paths, and pointers into a path for user-supplied + strings (which would be freed when the call returns). + Instead of a linked list, perhaps an array can be used, as paths rarely + change. It would be a problem if for some reason, recursively path + components are added to a path. +- (maybe) keep track of already issued handles, so that the ref counter + could just be incremented, instead of issuing a new one. + (uio_getPDirEntryHandle) +- Make it possible for people to add their own file system, without giving + away the internals. (no wanton including of .h files) +- Don't cache stdio dir structure + (and don't read the dir leading up to the dir that is actually needed) +- mounting foo to /Foo and foo/bar to /Foo/Bar should result in only one + physical structure. Preferably, it shouldn't even lead to two mountInfo + structures for /Foo/Bar, so that merging the dirs is not unnecessarilly + expensive. +- add uio_access() + (uqm fileExists can be redone then) +- implement the physical function cleanup() to clean up the physical + structure. Int argument that specifies how thoroughly. + On it depends whether cache is cleaned, whether stuff is realloc'ed + to defragment memory, etc. + +Cleanups (not necessary for UQM): +- rename function names to be more like class names for as far as that's + not already done. + uio_PRoot_unref etc +- Add uio_fatal(), uio_error(), uio_warning() +- Clean up the include structure +- use stdint.h and stdbool.h types directly, instead of using uio_int16 etc. + Remove types.h, and instead, if these types are missing on some platforms, + put the fixes in port.h. + + + diff --git a/src/libs/uio/fileblock.c b/src/libs/uio/fileblock.c new file mode 100644 index 0000000..bb4f466 --- /dev/null +++ b/src/libs/uio/fileblock.c @@ -0,0 +1,332 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#define uio_INTERNAL_FILEBLOCK + +#include "iointrn.h" +#include "fileblock.h" +#include "uioport.h" + +#include <errno.h> + +static uio_FileBlock *uio_FileBlock_new(uio_Handle *handle, int flags, + off_t offset, size_t blockSize, char *buffer, size_t bufSize, + off_t bufOffset, size_t bufFill, size_t readAheadBufSize); +static inline uio_FileBlock *uio_FileBlock_alloc(void); +static void uio_FileBlock_free(uio_FileBlock *block); + +// caller should uio_Handle_ref(handle) (unless it doesn't need it's own +// reference anymore). +static uio_FileBlock * +uio_FileBlock_new(uio_Handle *handle, int flags, off_t offset, + size_t blockSize, char *buffer, size_t bufSize, off_t bufOffset, + size_t bufFill, size_t readAheadBufSize) { + uio_FileBlock *result; + + result = uio_FileBlock_alloc(); + result->handle = handle; + result->flags = flags; + result->offset = offset; + result->blockSize = blockSize; + result->buffer = buffer; + result->bufSize = bufSize; + result->bufOffset = bufOffset; + result->bufFill = bufFill; + result->readAheadBufSize = readAheadBufSize; + return result; +} + +static inline uio_FileBlock * +uio_FileBlock_alloc(void) { + return uio_malloc(sizeof (uio_FileBlock)); +} + +static inline void +uio_FileBlock_free(uio_FileBlock *block) { + uio_free(block); +} + +uio_FileBlock * +uio_openFileBlock(uio_Handle *handle) { + // TODO: if mmap support is available, and it is available natively + // for the filesystem (make some sort of sysctl for filesystems + // to check this?), use mmap. + // mmap the entire file if it's small enough. + // N.B. Keep in mind streams of which the size is not known in + // advance. + struct stat statBuf; + if (uio_fstat(handle, &statBuf) == -1) { + // errno is set + return NULL; + } + uio_Handle_ref(handle); + return uio_FileBlock_new(handle, 0, 0, statBuf.st_size, NULL, 0, 0, 0, 0); +} + +uio_FileBlock * +uio_openFileBlock2(uio_Handle *handle, off_t offset, size_t size) { + // TODO: mmap (see uio_openFileBlock) + + // TODO: check if offset and size are acceptable. + // Need to handle streams of which the size is unknown. +#if 0 + if (uio_stat(handle, &statBuf) == -1) { + // errno is set + return NULL; + } + if (statBuf.st_size > offset || (statBuf.st_size - offset > size)) { + // NOT: 'if (statBuf.st_size > offset + size)', to protect + // against overflow. + + } +#endif + uio_Handle_ref(handle); + return uio_FileBlock_new(handle, 0, offset, size, NULL, 0, 0, 0, 0); +} + +static inline ssize_t +uio_accessFileBlockMmap(uio_FileBlock *block, off_t offset, size_t length, + char **buffer) { + // TODO + errno = ENOSYS; + (void) block; + (void) offset; + (void) length; + (void) buffer; + return -1; +} + +static inline ssize_t +uio_accessFileBlockNoMmap(uio_FileBlock *block, off_t offset, size_t length, + char **buffer) { + ssize_t numRead; + off_t start; + off_t end; + size_t bufSize; + char *oldBuffer; + //size_t oldBufSize; + + // Don't go beyond the end of the block. + if (offset > (off_t) block->blockSize) { + *buffer = block->buffer; + return 0; + } + if (length > block->blockSize - offset) + length = block->blockSize - offset; + + if (block->buffer != NULL) { + // Check whether the requested data is already in the buffer. + if (offset >= block->bufOffset && + (offset - block->bufOffset) + length < block->bufFill) { + *buffer = block->buffer + (offset - block->bufOffset); + return length; + } + } + + if (length < block->readAheadBufSize && + (block->flags & uio_FB_USAGE_MASK) != 0) { + // We can buffer more data. + switch (block->flags & uio_FB_USAGE_MASK) { + case uio_FB_USAGE_FORWARD: + // Read extra data after the requested data. + start = offset; + end = (block->readAheadBufSize > block->blockSize - offset) ? + block->blockSize : offset + block->readAheadBufSize; + break; + case uio_FB_USAGE_BACKWARD: + // Read extra data before the requested data. + end = offset + length; + start = (end <= (off_t) block->blockSize) ? + 0 : end - block->bufSize; + break; + case uio_FB_USAGE_FORWARD | uio_FB_USAGE_BACKWARD: { + // Read extra data both before and after the requested data. + off_t extraBefore = (block->readAheadBufSize - length) / 2; + start = (offset < extraBefore) ? 0 : offset - extraBefore; + + end = (block->readAheadBufSize > block->blockSize - start) ? + block->blockSize : start + block->readAheadBufSize; + break; + } + } + } else { + start = offset; + end = offset + length; + } + bufSize = (length > block->readAheadBufSize) ? + length : block->readAheadBufSize; + + // Start contains the start index in the block of the data we're going + // to read. + // End contains the end index. + // bufSize contains the size of the buffer. bufSize >= end - start. + + oldBuffer = block->buffer; + //oldBufSize = block->bufSize; + if (block->buffer != NULL || block->bufSize != bufSize) { + // We don't have a buffer, or we have one, but of the wrong size. + block->buffer = uio_malloc(bufSize); + block->bufSize = bufSize; + } + + if (oldBuffer != NULL) { + // TODO: If we have part of the data still in the old buffer, we + // can keep that. + // memmove(...) + + if (oldBuffer != block->buffer) + uio_free(oldBuffer); + } + block->bufFill = 0; + block->bufOffset = start; + + // TODO: lock handle + if (uio_lseek(block->handle, block->offset + start, SEEK_SET) == + (off_t) -1) { + // errno is set + return -1; + } + + numRead = uio_read(block->handle, block->buffer, end - start); + if (numRead == -1) { + // errno is set + // TODO: unlock handle + return -1; + } + // TODO: unlock handle + + block->bufFill = numRead; + *buffer = block->buffer + (offset - block->bufOffset); + if (numRead <= (offset - block->bufOffset)) + return 0; + if ((size_t) numRead >= length) + return length; + return numRead - offset; +} + +// block remains usable until the next call to uio_accessFileBlock +// with the same block as argument, or until uio_closeFileBlock with +// that block as argument. +// The 'offset' parameter is wrt. the start of the block. +// Requesting access to data beyond the file block is not an error. The +// FileBlock is meant to be used as a replacement of seek() and read(), and +// as with those functions, trying to go beyond the end of a file just +// goes to the end. The return value is the number of bytes in the buffer. +ssize_t +uio_accessFileBlock(uio_FileBlock *block, off_t offset, size_t length, + char **buffer) { + if (block->flags & uio_FB_USE_MMAP) { + return uio_accessFileBlockMmap(block, offset, length, buffer); + } else { + return uio_accessFileBlockNoMmap(block, offset, length, buffer); + } +} + +int +uio_copyFileBlock(uio_FileBlock *block, off_t offset, char *buffer, + size_t length) { + if (block->flags & uio_FB_USE_MMAP) { + // TODO + errno = ENOSYS; + return -1; + } else { + ssize_t numCopied = 0; + ssize_t readResult; + + // Don't go beyond the end of the block. + if (offset > (off_t) block->blockSize) + return 0; + if (length > block->blockSize - offset) + length = block->blockSize - offset; + + // Check whether (part of) the requested data is already in our + // own buffer. + if (block->buffer != NULL && offset >= block->bufOffset + && offset < block->bufOffset + (off_t) block->bufFill) { + size_t toCopy = block->bufFill - offset; + if (toCopy > length) + toCopy = length; + memcpy(buffer, block->buffer + (offset - block->bufOffset), + toCopy); + numCopied += toCopy; + length -= toCopy; + if (length == 0) + return numCopied; + buffer += toCopy; + offset += toCopy; + } + + // TODO: lock handle + if (uio_lseek(block->handle, block->offset + offset, SEEK_SET) == + (off_t) -1) { + // errno is set + return -1; + } + + readResult = uio_read(block->handle, buffer, length); + // TODO: unlock handle + if (readResult == -1) { + // errno is set + return -1; + } + numCopied += readResult; + + return numCopied; + } +} + +int +uio_closeFileBlock(uio_FileBlock *block) { + if (block->flags & uio_FB_USE_MMAP) { +#if 0 + if (block->buffer != NULL) + uio_mmunmap(block->buffer); +#endif + } else { + if (block->buffer != NULL) + uio_free(block->buffer); + } + uio_Handle_unref(block->handle); + uio_FileBlock_free(block); + return 0; +} + +// Usage is the or'ed value of zero or more of uio_FB_USAGE_FORWARD, +// and uio_FB_USAGE_BACKWARD. +void +uio_setFileBlockUsageHint(uio_FileBlock *block, int usage, + size_t readAheadBufSize) { + block->flags = (block->flags & ~uio_FB_USAGE_MASK) | + (usage & uio_FB_USAGE_MASK); + block->readAheadBufSize = readAheadBufSize; +} + +// Call if you want the memory used by the fileblock to be released, but +// still want to use the fileblock later. If you don't need the fileblock, +// call uio_closeFileBlock() instead. +void +uio_clearFileBlockBuffers(uio_FileBlock *block) { + if (block->buffer != NULL) { + uio_free(block->buffer); + block->buffer = NULL; + } +} + + diff --git a/src/libs/uio/fileblock.h b/src/libs/uio/fileblock.h new file mode 100644 index 0000000..97a81e1 --- /dev/null +++ b/src/libs/uio/fileblock.h @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef LIBS_UIO_FILEBLOCK_H_ +#define LIBS_UIO_FILEBLOCK_H_ + +typedef struct uio_FileBlock uio_FileBlock; + +#include "io.h" +#include "uioport.h" + +#include <sys/types.h> + +#define uio_FB_USAGE_FORWARD 1 +#define uio_FB_USAGE_BACKWARD 2 +#define uio_FB_USAGE_MASK (uio_FB_USAGE_FORWARD | uio_FB_USAGE_BACKWARD) + +#ifdef uio_INTERNAL_FILEBLOCK + +// A fileblock represents a contiguous block of data from a file. +// It's purpose is to avoid needless copying of data, while enabling +// buffering. + +struct uio_FileBlock { + uio_Handle *handle; + int flags; + // See above for uio_FB_USAGE_FORWARD, uio_FB_USAGE_BACKWARD. +#define uio_FB_USE_MMAP 4 + off_t offset; + // Offset to the start of the block in the file. + size_t blockSize; + // Size of the block of data represented by this FileBlock. + char *buffer; + // either allocated buffer, or buffer to mmap'ed area. + size_t bufSize; + // Size of the buffer. + off_t bufOffset; + // Offset of the start of the buffer into the block. + size_t bufFill; + // Part of 'buffer' which is in use. + size_t readAheadBufSize; + // Try to read up to this many bytes at a time, even when less + // is immediately needed. +}; +// INV: The FileBlock represents 'fileData[offset..(offset + blockSize - 1)]' +// where 'fileData' is the contents of the file. +// INV: If buf != NULL then: +// bufFill <= bufSize +// bufFill <= blockSize +// buffer[0..bufFill - 1] == fileData[ +// (offset + bufOffset)..(offset + bufOffset + bufFill - 1)] + + +#endif /* uio_INTERNAL_FILEBLOCK */ + +uio_FileBlock *uio_openFileBlock(uio_Handle *handle); +uio_FileBlock *uio_openFileBlock2(uio_Handle *handle, off_t offset, + size_t size); +ssize_t uio_accessFileBlock(uio_FileBlock *block, off_t offset, size_t length, + char **buffer); +int uio_copyFileBlock(uio_FileBlock *block, off_t offset, char *buffer, + size_t length); +int uio_closeFileBlock(uio_FileBlock *block); +#define uio_FB_READAHEAD_BUFSIZE_MAX ((size_t) -1) +void uio_setFileBlockUsageHint(uio_FileBlock *block, int usage, + size_t readAheadBufSize); +void uio_clearFileBlockBuffers(uio_FileBlock *block); + +#endif /* LIBS_UIO_FILEBLOCK_H_ */ + + diff --git a/src/libs/uio/fstypes.c b/src/libs/uio/fstypes.c new file mode 100644 index 0000000..d940e8f --- /dev/null +++ b/src/libs/uio/fstypes.c @@ -0,0 +1,272 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <errno.h> +#include <stdio.h> + +#include "iointrn.h" +#include "uioport.h" +#include "fstypes.h" +#include "mem.h" +#include "defaultfs.h" +#ifdef uio_MEM_DEBUG +# include "memdebug.h" +#endif + + +static uio_bool uio_validFileSystemHandler(uio_FileSystemHandler *handler); +static uio_FileSystemInfo *uio_FileSystemInfo_new(uio_FileSystemID id, + uio_FileSystemHandler *handler, char *name); +static uio_FileSystemInfo **uio_getFileSystemInfoPtr(uio_FileSystemID id); + +static inline uio_FileSystemInfo *uio_FileSystemInfo_alloc(void); + +static inline void uio_FileSystemInfo_free( + uio_FileSystemInfo *fileSystemInfo); + + +uio_FileSystemInfo *uio_fileSystems = NULL; + // list sorted by id + + + +void +uio_registerDefaultFileSystems(void) { + int i; + int num; + uio_FileSystemID registerResult; + + num = uio_numDefaultFileSystems(); + for (i = 0; i < num; i++) { + registerResult = uio_registerFileSystem( + defaultFileSystems[i].id, + defaultFileSystems[i].name, + defaultFileSystems[i].handler); + switch (registerResult) { + case 0: + fprintf(stderr, "Warning: Default file system '%s' is " + "already registered.\n", + defaultFileSystems[i].name); + break; + case -1: + fprintf(stderr, "Error: Could not register '%s' file \n" + "system: %s\n", defaultFileSystems[i].name, + strerror(errno)); + break; + default: + assert(registerResult == defaultFileSystems[i].id); + break; + } + } +} + +void +uio_unRegisterDefaultFileSystems(void) { + int i; + int num; + + num = uio_numDefaultFileSystems(); + for (i = 0; i < num; i++) { + if (uio_unRegisterFileSystem(defaultFileSystems[i].id) == -1) { + fprintf(stderr, "Could not unregister '%s' file system: %s\n", + defaultFileSystems[i].name, strerror(errno)); + } + } +} + +// if wantedID = 0, just pick one +// if wantedID != 0, 0 will be returned if that id wasn't available +// a copy of 'name' is made +uio_FileSystemID +uio_registerFileSystem(uio_FileSystemID wantedID, const char *name, + uio_FileSystemHandler *handler) { + uio_FileSystemInfo **ptr; + + if (!uio_validFileSystemHandler(handler)) { + errno = EINVAL; + return -1; + } + if (wantedID == 0) { + // Search for the first free id >= uio_FIRST_CUSTOM_ID + // it is put in wantedID + + for (ptr = &uio_fileSystems; *ptr != NULL; ptr = &(*ptr)->next) + if ((*ptr)->id >= uio_FS_FIRST_CUSTOM_ID) + break; + + wantedID = uio_FS_FIRST_CUSTOM_ID; + while (*ptr != NULL) { + if ((*ptr)->id != wantedID) { + // wantedID is not in use + break; + } + wantedID++; + ptr = &(*ptr)->next; + } + // wantedID contains the new ID + } else { + // search for the place in the list where to insert the wanted + // id, keeping the list sorted + for (ptr = &uio_fileSystems; *ptr != NULL; ptr = &(*ptr)->next) { + if ((*ptr)->id <= wantedID) { + if ((*ptr)->id == wantedID) + return 0; + break; + } + } + + } + // ptr points to the place where the new link can inserted + + if (handler->init != NULL && handler->init() == -1) { + // errno is set + return -1; + } + + { + uio_FileSystemInfo *newInfo; + + newInfo = uio_FileSystemInfo_new(wantedID, handler, uio_strdup(name)); + newInfo->next = *ptr; + *ptr = newInfo; + return wantedID; + } +} + +int +uio_unRegisterFileSystem(uio_FileSystemID id) { + uio_FileSystemInfo **ptr; + uio_FileSystemInfo *temp; + + ptr = uio_getFileSystemInfoPtr(id); + if (ptr == NULL) { + errno = EINVAL; + return -1; + } + if ((*ptr)->ref > 1) { + errno = EBUSY; + return -1; + } + + if ((*ptr)->handler->unInit != NULL && + ((*ptr)->handler->unInit() == -1)) { + // errno is set + return -1; + } + + temp = *ptr; + *ptr = (*ptr)->next; + +// uio_FileSystemHandler_unref(temp->handler); + uio_free(temp->name); + uio_FileSystemInfo_free(temp); + + return 0; +} + +static uio_bool +uio_validFileSystemHandler(uio_FileSystemHandler *handler) { + // Check for the essentials + if (handler->mount == NULL || + handler->umount == NULL || + handler->open == NULL || + handler->close == NULL || + handler->read == NULL || + handler->openEntries == NULL || + handler->readEntries == NULL || + handler->closeEntries == NULL) { +#ifdef DEBUG + fprintf(stderr, "Invalid file system handler.\n"); +#endif + return false; + } + return true; +} + +uio_FileSystemHandler * +uio_getFileSystemHandler(uio_FileSystemID id) { + uio_FileSystemInfo *ptr; + + for (ptr = uio_fileSystems; ptr != NULL; ptr = ptr->next) { + if (ptr->id == id) + return ptr->handler; + } + return NULL; +} + +uio_FileSystemInfo * +uio_getFileSystemInfo(uio_FileSystemID id) { + uio_FileSystemInfo *ptr; + + for (ptr = uio_fileSystems; ptr != NULL; ptr = ptr->next) { + if (ptr->id == id) + return ptr; + } + return NULL; +} + +static uio_FileSystemInfo ** +uio_getFileSystemInfoPtr(uio_FileSystemID id) { + uio_FileSystemInfo **ptr; + + for (ptr = &uio_fileSystems; *ptr != NULL; ptr = &(*ptr)->next) { + if ((*ptr)->id == id) + return ptr; + } + return NULL; +} + +// sets ref to 1 +static uio_FileSystemInfo * +uio_FileSystemInfo_new(uio_FileSystemID id, uio_FileSystemHandler *handler, + char *name) { + uio_FileSystemInfo *result; + + result = uio_FileSystemInfo_alloc(); + result->id = id; + result->handler = handler; + result->name = name; + result->ref = 1; + return result; +} + +// *** Allocators *** + +static inline uio_FileSystemInfo * +uio_FileSystemInfo_alloc(void) { + uio_FileSystemInfo *result = uio_malloc(sizeof (uio_FileSystemInfo)); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugAlloc(uio_FileSystemInfo, (void *) result); +#endif + return result; +} + + +// *** Deallocators *** + +static inline void +uio_FileSystemInfo_free(uio_FileSystemInfo *fileSystemInfo) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugFree(uio_FileSystemInfo, (void *) fileSystemInfo); +#endif + uio_free(fileSystemInfo); +} + + diff --git a/src/libs/uio/fstypes.h b/src/libs/uio/fstypes.h new file mode 100644 index 0000000..3ff01a2 --- /dev/null +++ b/src/libs/uio/fstypes.h @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef LIBS_UIO_FSTYPES_H_ +#define LIBS_UIO_FSTYPES_H_ + +typedef int uio_FileSystemID; +#define uio_FSTYPE_STDIO 1 +#define uio_FSTYPE_ZIP 2 + + +#ifdef uio_INTERNAL + +#define uio_FS_FIRST_CUSTOM_ID 0x10 + +#ifndef uio_INTERNAL_PHYSICAL +typedef void *uio_NativeHandle; +#endif + +// 'forward' declarations +typedef struct uio_FileSystemHandler uio_FileSystemHandler; +typedef struct uio_FileSystemInfo uio_FileSystemInfo; + +#include <sys/stat.h> +#include <sys/types.h> +#include "physical.h" +#include "uioport.h" + + +/* Structure describing how to access files _in_ a directory of a certain + * type (not files _of_ a certain type.) Except for mount(). + * in open(), the first arg points to the dir where the file should be + * in, and the second arg is the name of that file (no path) + */ +struct uio_FileSystemHandler { + int (*init) (void); + int (*unInit) (void); + void (*cleanup) (uio_PRoot *, int); + // Called to cleanup memory. The second argument specifies + // how thoroughly. + + struct uio_PRoot * (*mount) (uio_Handle *, int); + int (*umount) (uio_PRoot *); + + int (*access) (uio_PDirHandle *, const char *, int mode); + void (*close) (uio_Handle *); + // called when the last reference is closed, not + // necessarilly each time when uio_close() is called + int (*fstat) (uio_Handle *, struct stat *); + int (*stat) (uio_PDirHandle *, const char *, + struct stat *); + uio_PDirHandle * (*mkdir) (uio_PDirHandle *, const char *, mode_t); + uio_Handle * (*open) (uio_PDirHandle *, const char *, int, + mode_t); + ssize_t (*read) (uio_Handle *, void *, size_t); + int (*rename) (uio_PDirHandle *, const char *, + uio_PDirHandle *, const char *); + int (*rmdir) (uio_PDirHandle *, const char *); + off_t (*seek) (uio_Handle *, off_t, int); + ssize_t (*write) (uio_Handle *, const void *, size_t); + int (*unlink) (uio_PDirHandle *, const char *); + + uio_NativeEntriesContext (*openEntries) (uio_PDirHandle *); + int (*readEntries) (uio_NativeEntriesContext *, char *, + size_t); + void (*closeEntries) (uio_NativeEntriesContext); + + uio_PDirEntryHandle * (*getPDirEntryHandle) ( + const uio_PDirHandle *, const char *); + void (*deletePRootExtra) (uio_PRootExtra pRootExtra); + void (*deletePDirHandleExtra) ( + uio_PDirHandleExtra pDirHandleExtra); + void (*deletePFileHandleExtra) ( + uio_PFileHandleExtra pFileHandleExtra); +}; + +struct uio_FileSystemInfo { + int ref; + uio_FileSystemID id; + char *name; // name of the file system + uio_FileSystemHandler *handler; + struct uio_FileSystemInfo *next; +}; + +void uio_registerDefaultFileSystems(void); +void uio_unRegisterDefaultFileSystems(void); +uio_FileSystemID uio_registerFileSystem(uio_FileSystemID wantedID, + const char *name, uio_FileSystemHandler *handler); +int uio_unRegisterFileSystem(uio_FileSystemID id); +uio_FileSystemHandler *uio_getFileSystemHandler(uio_FileSystemID id); +uio_FileSystemInfo *uio_getFileSystemInfo(uio_FileSystemID id); + +#endif /* uio_INTERNAL */ + +#endif /* LIBS_UIO_FSTYPES_H_ */ + diff --git a/src/libs/uio/getint.h b/src/libs/uio/getint.h new file mode 100644 index 0000000..ad6e810 --- /dev/null +++ b/src/libs/uio/getint.h @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2007 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef LIBS_UIO_GETINT_H_ +#define LIBS_UIO_GETINT_H_ + +/* All these functions return true on success, or false on failure */ + +#include "types.h" +#include "uioport.h" + +static inline uio_bool +uio_getU8(uio_Stream *stream, uio_uint8 *result) { + int val = uio_getc(stream); + if (val == EOF) + return false; + + *result = (uio_uint8) val; + return true; +} + +static inline uio_bool +uio_getS8(uio_Stream *stream, uio_sint8 *result) { + int val = uio_getc(stream); + if (val == EOF) + return false; + + *result = (uio_sint8) val; + return true; +} + +static inline uio_bool +uio_getU16LE(uio_Stream *stream, uio_uint16 *result) { + uio_uint8 buf[2]; + + if (uio_fread(buf, sizeof buf, 1, stream) != 1) + return false; + + *result = (buf[1] << 8) | buf[0]; + return true; +} + +static inline uio_bool +uio_getU16BE(uio_Stream *stream, uio_uint16 *result) { + uio_uint8 buf[2]; + + if (uio_fread(buf, sizeof buf, 1, stream) != 1) + return false; + + *result = (buf[0] << 8) | buf[1]; + return true; +} + +static inline uio_bool +uio_getS16LE(uio_Stream *stream, uio_sint16 *result) { + uio_uint8 buf[2]; + + if (uio_fread(buf, sizeof buf, 1, stream) != 1) + return false; + + *result = (uio_sint16) ((buf[1] << 8) | buf[0]); + return true; +} + +static inline uio_bool +uio_getS16BE(uio_Stream *stream, uio_sint16 *result) { + uio_uint8 buf[2]; + + if (uio_fread(buf, sizeof buf, 1, stream) != 1) + return false; + + *result = (uio_sint16) ((buf[0] << 8) | buf[1]); + return true; +} + +static inline uio_bool +uio_getU32LE(uio_Stream *stream, uio_uint32 *result) { + uio_uint8 buf[4]; + + if (uio_fread(buf, sizeof buf, 1, stream) != 1) + return false; + + *result = (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0]; + return true; +} + +static inline uio_bool +uio_getU32BE(uio_Stream *stream, uio_uint32 *result) { + uio_uint8 buf[4]; + + if (uio_fread(buf, sizeof buf, 1, stream) != 1) + return false; + + *result = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; + return true; +} + +static inline uio_bool +uio_getS32LE(uio_Stream *stream, uio_sint32 *result) { + uio_uint8 buf[4]; + + if (uio_fread(buf, sizeof buf, 1, stream) != 1) + return false; + + *result = (uio_sint32) ((buf[3] << 24) | (buf[2] << 16) | + (buf[1] << 8) | buf[0]); + return true; +} + +static inline uio_bool +uio_getS32BE(uio_Stream *stream, uio_sint32 *result) { + uio_uint8 buf[4]; + + if (uio_fread(buf, sizeof buf, 1, stream) != 1) + return false; + + *result = (uio_sint32) ((buf[0] << 24) | (buf[1] << 16) | + (buf[2] << 8) | buf[3]); + return true; +} + + +#endif /* LIBS_UIO_GETINT_H_ */ + diff --git a/src/libs/uio/gphys.c b/src/libs/uio/gphys.c new file mode 100644 index 0000000..f25f557 --- /dev/null +++ b/src/libs/uio/gphys.c @@ -0,0 +1,620 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#define uio_INTERNAL_PHYSICAL +#define uio_INTERNAL_GPHYS +typedef void *uio_NativeHandle; +typedef void *uio_GPRootExtra; +typedef void *uio_GPDirExtra; +typedef void *uio_GPFileExtra; + +#include <errno.h> +#include <stdio.h> + +#include "gphys.h" +#include "paths.h" +#include "uioport.h" +#ifdef uio_MEM_DEBUG +# include "memdebug.h" +#endif + +static void uio_GPDir_deepPersistentUnref(uio_GPDir *gPDir); +static uio_GPRoot *uio_GPRoot_alloc(void); + +static void uio_GPRoot_free(uio_GPRoot *gPRoot); + +static inline uio_GPDir *uio_GPDir_alloc(void); +void uio_GPDir_delete(uio_GPDir *gPDir); +static inline void uio_GPDir_free(uio_GPDir *gPDir); + +static inline uio_GPFile *uio_GPFile_alloc(void); +void uio_GPFile_delete(uio_GPFile *gPFile); +static inline void uio_GPFile_free(uio_GPFile *gPFile); + +// Call this when you need to edit a file 'dirName' in the GPDir 'gPDir'. +// a new entry is created when necessary. +// uio_gPDirCommitSubDir should be called when you're done with it. +// +// a copy of dirName is made if needed; the caller remains responsible for +// freeing the original +// Allocates a new dir if necessary. +uio_GPDir * +uio_GPDir_prepareSubDir(uio_GPDir *gPDir, const char *dirName) { + uio_GPDir *subDir; + uio_GPDirEntry *entry; + + entry = uio_GPDirEntries_find(gPDir->entries, dirName); + if (entry != NULL) { + if (uio_GPDirEntry_isDir(entry)) { + // Return existing subdir. + uio_GPDir_ref((uio_GPDir *) entry); + return (uio_GPDir *) entry; + } else { + // There already exists a file with the same name. + // This should not happen within one file system. + fprintf(stderr, "Directory %s shadows file with the same name " + "from the same filesystem.\n", dirName); + } + } + + // return new subdir + subDir = uio_GPDir_new(gPDir->pRoot, NULL, uio_GPDir_DETACHED); + // subDir->ref is initialised at 1 + return subDir; +} + +// call this when you're done with a dir acquired by a call to +// uio_gPDirPrepareSubDir +void +uio_GPDir_commitSubDir(uio_GPDir *gPDir, const char *dirName, + uio_GPDir *subDir) { + if (subDir->flags & uio_GPDir_DETACHED) { + // New dir. + // reference to the subDir is passed along to the upDir, + // so subDir->ref should not be changed. + uio_GPDirEntries_add(gPDir->entries, dirName, subDir); + subDir->flags &= ~uio_GPDir_DETACHED; + if (!(subDir->flags & uio_GPDir_PERSISTENT)) { + // Persistent dirs have an extra reference. + uio_GPDir_unref(subDir); + } + } else { + uio_GPDir_unref(subDir); + } +} + +// a copy of fileName is made if needed; the caller remains responsible for +// freeing the original +void +uio_GPDir_addFile(uio_GPDir *gPDir, const char *fileName, uio_GPFile *file) { + // A file will never already exist in a dir. There can only be + // one entry in a physical dir with the same name. + uio_GPDirEntries_add(gPDir->entries, fileName, (uio_GPDirEntry *) file); +} + +// Pre: 'fileName' exists in 'gPDir' and is a dir. +void +uio_GPDir_removeFile(uio_GPDir *gPDir, const char *fileName) { + uio_GPDirEntry *entry; + uio_GPFile *file; + uio_bool retVal; + + entry = uio_GPDirEntries_find(gPDir->entries, fileName); + if (entry == NULL) { + // This means the file has no associated GPFile. + // This can happen when the GPFile structure is only used for caching. + return; + } + + assert(!uio_GPDirEntry_isDir(entry)); + file = (uio_GPFile *) entry; + uio_GPFile_unref(file); + retVal = uio_GPDirEntries_remove(gPDir->entries, fileName); + assert(retVal); +} + +// Pre: 'dirName' exists in 'gPDir' and is a dir. +void +uio_GPDir_removeSubDir(uio_GPDir *gPDir, const char *dirName) { + uio_GPDirEntry *entry; + uio_GPDir *subDir; + uio_bool retVal; + + entry = uio_GPDirEntries_find(gPDir->entries, dirName); + if (entry == NULL) { + // This means the directory has no associated GPDir. + // This can happen when the GPDir structure is only used for caching. + return; + } + + assert(uio_GPDirEntry_isDir(entry)); + subDir = (uio_GPDir *) entry; + if (subDir->flags & uio_GPDir_PERSISTENT) { + // Persistent dirs have an extra reference. + uio_GPDir_unref(subDir); + } + retVal = uio_GPDirEntries_remove(gPDir->entries, dirName); + assert(retVal); +} + +void +uio_GPDir_setComplete(uio_GPDir *gPDir, uio_bool flag) { + if (flag) { + gPDir->flags |= uio_GPDir_COMPLETE; + } else + gPDir->flags &= ~uio_GPDir_COMPLETE; +} + +int +uio_GPDir_entryCount(const uio_GPDir *gPDir) { + return uio_GPDirEntries_count(gPDir->entries); +} + +static void +uio_GPDir_access(uio_GPDir *gPDir) { + if (!(gPDir->flags & uio_GPDir_COMPLETE)) + uio_GPDir_fill(gPDir); +} + +// The ref counter for the dir entry is not incremented. +uio_GPDirEntry * +uio_GPDir_getGPDirEntry(uio_GPDir *gPDir, const char *name) { + uio_GPDir_access(gPDir); + return uio_GPDirEntries_find(gPDir->entries, name); +} + +// The ref counter for the dir entry is not incremented. +uio_PDirEntryHandle * +uio_GPDir_getPDirEntryHandle(const uio_PDirHandle *pDirHandle, + const char *name) { + uio_GPDirEntry *gPDirEntry; + + gPDirEntry = uio_GPDir_getGPDirEntry(pDirHandle->extra, name); + if (gPDirEntry == NULL) + return NULL; + uio_GPDirEntry_ref(gPDirEntry); + if (uio_GPDirEntry_isDir(gPDirEntry)) { + return (uio_PDirEntryHandle *) uio_PDirHandle_new(pDirHandle->pRoot, + (uio_GPDir *) gPDirEntry); + } else { + return (uio_PDirEntryHandle *) uio_PFileHandle_new(pDirHandle->pRoot, + (uio_GPFile *) gPDirEntry); + } +} + +/* + * Follow a path starting from a specified physical dir as long as possible. + * When you can get no further, 'endGPDir' will be filled in with the dir + * where you ended up, and 'pathRest' will point into the original path. to + * the beginning of the part that was not matched. + * It is allowed to have endGPDir point to gPDir and/or restPath + * point to path when calling this function. + * returns: 0 if the complete path was matched + * ENOENT if some component (the next one) didn't exists + * ENODIR if a component (the next one) exists but isn't a dir + * See also uio_walkPhysicalPath. The difference is that this one works + * directly on the GPDirs and is faster because of that. + */ +int +uio_walkGPPath(uio_GPDir *startGPDir, const char *path, + size_t pathLen, uio_GPDir **endGPDir, const char **pathRest) { + const char *pathEnd; + const char *partStart, *partEnd; + char *tempBuf; + uio_GPDir *gPDir; + uio_GPDirEntry *entry; + int retVal; + + gPDir = startGPDir; + tempBuf = uio_malloc(strlen(path) + 1); + // XXX: Use a dynamically allocated array when moving to C99. + pathEnd = path + pathLen; + getFirstPathComponent(path, pathEnd, &partStart, &partEnd); + while (1) { + if (partStart == pathEnd) { + retVal = 0; + break; + } + memcpy(tempBuf, partStart, partEnd - partStart); + tempBuf[partEnd - partStart] = '\0'; + + entry = uio_GPDir_getGPDirEntry(gPDir, tempBuf); + if (entry == NULL) { + retVal = ENOENT; + break; + } + if (!uio_GPDirEntry_isDir(entry)) { + retVal = ENOTDIR; + break; + } + gPDir = (uio_GPDir *) entry; + getNextPathComponent(pathEnd, &partStart, &partEnd); + } + + uio_free(tempBuf); + *pathRest = partStart; + *endGPDir = gPDir; + return retVal; +} + +uio_GPDirEntries_Iterator * +uio_GPDir_openEntries(uio_PDirHandle *pDirHandle) { + uio_GPDir_access(pDirHandle->extra); + return uio_GPDirEntries_getIterator(pDirHandle->extra->entries); +} + +// the start of 'buf' will be filled with pointers to strings +// those strings are stored elsewhere in buf. +// The function returns the number of strings passed along, or -1 for error. +// If there are no more entries, the last pointer will be NULL. +// (this pointer counts towards the return value) +int +uio_GPDir_readEntries(uio_GPDirEntries_Iterator **iterator, + char *buf, size_t len) { + char *end; + char **start; + int num; + const char *name; + size_t nameLen; + + // buf will be filled like this: + // The start of buf will contain pointers to char *, + // the end will contain the actual char[] that those pointers point to. + // The start and the end will grow towards eachother. + start = (char **) buf; + end = buf + len; + num = 0; + while (!uio_GPDirEntries_iteratorDone(*iterator)) { + name = uio_GPDirEntries_iteratorName(*iterator); + nameLen = strlen(name) + 1; + + // Does this work with systems that need memory access to be + // aligned on a certain number of bytes? + if ((size_t) (sizeof (char *) + nameLen) > + (size_t) (end - (char *) start)) { + // Not enough room to fit the pointer (at the beginning) and + // the string (at the end). + return num; + } + end -= nameLen; + memcpy(end, name, nameLen); + *start = end; + start++; + num++; + *iterator = uio_GPDirEntries_iteratorNext(*iterator); + } + if (sizeof (char *) > (size_t) (end - (char *) start)) { + // not enough room to fit the NULL pointer. + // It will have to be reported seperately the next time. + return num; + } + *start = NULL; + num++; + return num; +} + +void +uio_GPDir_closeEntries(uio_GPDirEntries_Iterator *iterator) { + uio_GPDirEntries_freeIterator(iterator); +} + +void +uio_GPDir_fill(uio_GPDir *gPDir) { + if ((gPDir->flags & uio_GPDir_COMPLETE) && + !(gPDir->flags & uio_GPDir_NOCACHE)) + return; + assert(gPDir->pRoot->extra->ops->fillGPDir != NULL); + gPDir->pRoot->extra->ops->fillGPDir(gPDir); +} + +void +uio_GPRoot_deleteGPRootExtra(uio_GPRoot *gPRoot) { + if (gPRoot->extra == NULL) + return; + assert(gPRoot->ops->deleteGPRootExtra != NULL); + gPRoot->ops->deleteGPRootExtra(gPRoot->extra); +} + +void +uio_GPDir_deleteGPDirExtra(uio_GPDir *gPDir) { + if (gPDir->extra == NULL) + return; + assert(gPDir->pRoot->extra->ops->deleteGPDirExtra != NULL); + gPDir->pRoot->extra->ops->deleteGPDirExtra(gPDir->extra); +} + +void +uio_GPFile_deleteGPFileExtra(uio_GPFile *gPFile) { + if (gPFile->extra == NULL) + return; + assert(gPFile->pRoot->extra->ops->deleteGPFileExtra != NULL); + gPFile->pRoot->extra->ops->deleteGPFileExtra(gPFile->extra); +} + +int +uio_gPDirFlagsFromPRootFlags(int flags) { + int newFlags; + + newFlags = 0; + if (flags & uio_PRoot_NOCACHE) + newFlags |= uio_GPDir_NOCACHE; + + return newFlags; +} + +int +uio_gPFileFlagsFromPRootFlags(int flags) { + int newFlags; + + newFlags = 0; + if (flags & uio_PRoot_NOCACHE) + newFlags |= uio_GPFile_NOCACHE; + + return newFlags; +} + +// This function is to be called from the physical layer. +// uio_GPDirHandle is the extra information for an uio_PDirHandle. +// This is in practice a pointer to the uio_GPDir. +void +uio_GPDirHandle_delete(uio_GPDirHandle *gPDirHandle) { + uio_GPDir_unref((uio_GPDir *) gPDirHandle); + (void) gPDirHandle; +} + +// This function is to be called from the physical layer. +// uio_GPFileHandle is the extra information for an uio_PFileHandle. +// This is in practice a pointer to the uio_GPFile. +void +uio_GPFileHandle_delete(uio_GPFileHandle *gPFileHandle) { + uio_GPFile_unref((uio_GPFile *) gPFileHandle); + (void) gPFileHandle; +} + +void +uio_GPDirEntry_delete(uio_GPDirEntry *gPDirEntry) { + if (uio_GPDirEntry_isDir(gPDirEntry)) { + uio_GPDir_delete((uio_GPDir *) gPDirEntry); + } else { + uio_GPFile_delete((uio_GPFile *) gPDirEntry); + } +} + +// note: sets ref count to 1 +uio_GPDir * +uio_GPDir_new(uio_PRoot *pRoot, uio_GPDirExtra extra, int flags) { + uio_GPDir *gPDir; + + gPDir = uio_GPDir_alloc(); + gPDir->ref = 1; + gPDir->pRoot = pRoot; + gPDir->entries = uio_GPDirEntries_new(); + gPDir->extra = extra; + flags |= uio_gPDirFlagsFromPRootFlags(gPDir->pRoot->flags); + if (pRoot->extra->flags & uio_GPRoot_PERSISTENT) + flags |= uio_GPDir_PERSISTENT; + gPDir->flags = flags | uio_GPDirEntry_TYPE_DIR; + return gPDir; +} + +// pre: There are no more references to within the gPDir, and +// gPDir is the last reference to the gPDir itself. +void +uio_GPDir_delete(uio_GPDir *gPDir) { +#if 0 + uio_GPDirEntry *entry; + + uio_GPDirEntries_Iterator *iterator; + + iterator = uio_GPDirEntries_getIterator(gPDir->entries); + while (!uio_GPDirEntries_iteratorDone(iterator)) { + entry = uio_GPDirEntries_iteratorItem(iterator); + assert(entry->ref == 0); + if (uio_GPDirEntry_isDir(entry)) { + uio_GPDir_delete((uio_GPDir *) entry); + } else { + uio_GPFile_delete((uio_GPFile *) entry); + } + iterator = uio_GPDirEntries_iteratorNext(iterator); + } +#endif + + assert(gPDir->ref == 0); + uio_GPDirEntries_deleteHashTable(gPDir->entries); + uio_GPDir_deleteGPDirExtra(gPDir); + uio_GPDir_free(gPDir); +} + +static void +uio_GPDir_deepPersistentUnref(uio_GPDir *gPDir) { + uio_GPDirEntry *entry; + uio_GPDirEntries_Iterator *iterator; + + iterator = uio_GPDirEntries_getIterator(gPDir->entries); + while (!uio_GPDirEntries_iteratorDone(iterator)) { + entry = uio_GPDirEntries_iteratorItem(iterator); + if (uio_GPDirEntry_isDir(entry)) { + uio_GPDir_deepPersistentUnref((uio_GPDir *) entry); + } else { + uio_GPFile_unref((uio_GPFile *) entry); + } + iterator = uio_GPDirEntries_iteratorNext(iterator); + } + uio_GPDirEntries_freeIterator(iterator); + if (gPDir->flags & uio_GPDir_PERSISTENT) { + uio_GPDir_unref(gPDir); + } else { + gPDir->flags &= ~uio_GPDir_COMPLETE; + } +} + +static inline uio_GPDir * +uio_GPDir_alloc(void) { + uio_GPDir *result = uio_malloc(sizeof (uio_GPDir)); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugAlloc(uio_GPDir, (void *) result); +#endif + return result; +} + +static inline void +uio_GPDir_free(uio_GPDir *gPDir) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugFree(uio_GPDir, (void *) gPDir); +#endif + uio_free(gPDir); +} + +// note: sets ref count to 1 +uio_GPFile * +uio_GPFile_new(uio_PRoot *pRoot, uio_GPFileExtra extra, int flags) { + uio_GPFile *gPFile; + + gPFile = uio_GPFile_alloc(); + gPFile->ref = 1; + gPFile->pRoot = pRoot; + gPFile->extra = extra; + gPFile->flags = flags; + return gPFile; +} + +void +uio_GPFile_delete(uio_GPFile *gPFile) { + assert(gPFile->ref == 0); + uio_GPFile_deleteGPFileExtra(gPFile); + uio_GPFile_free(gPFile); +} + +static inline uio_GPFile * +uio_GPFile_alloc(void) { + uio_GPFile *result = uio_malloc(sizeof (uio_GPFile)); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugAlloc(uio_GPFile, (void *) result); +#endif + return result; +} + +static inline void +uio_GPFile_free(uio_GPFile *gPFile) { + uio_free(gPFile); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugFree(uio_GPFile, (void *) gPFile); +#endif +} + +// The ref counter to 'handle' is not incremented. +uio_PRoot * +uio_GPRoot_makePRoot(uio_FileSystemHandler *handler, int pRootFlags, + uio_GPRoot_Operations *ops, uio_GPRootExtra gPRootExtra, int gPRootFlags, + uio_Handle *handle, uio_GPDirExtra gPDirExtra, int gPDirFlags) { + uio_PRoot *result; + uio_GPDir *gPTopDir; + uio_GPRoot *gPRoot; + + gPRoot = uio_GPRoot_new(ops, gPRootExtra, gPRootFlags); + result = uio_PRoot_new(NULL, handler, handle, gPRoot, pRootFlags); + + gPTopDir = uio_GPDir_new(result, gPDirExtra, gPDirFlags); + if (gPRoot->flags & uio_GPRoot_PERSISTENT) + uio_GPDir_ref(gPTopDir); + result->rootDir = uio_GPDir_makePDirHandle(gPTopDir); + + return result; +} + +// Pre: there are no more references to PRoot or anything inside it. +int +uio_GPRoot_umount(uio_PRoot *pRoot) { + uio_PDirHandle *topDirHandle; + uio_GPDir *topDir; + + topDirHandle = uio_PRoot_getRootDirHandle(pRoot); + topDir = topDirHandle->extra; + if (pRoot->extra->flags & uio_GPRoot_PERSISTENT) + uio_GPDir_deepPersistentUnref(topDir); + uio_PDirHandle_unref(topDirHandle); + (void) pRoot; + return 0; +} + +uio_GPRoot * +uio_GPRoot_new(uio_GPRoot_Operations *ops, uio_GPRootExtra extra, int flags) { + uio_GPRoot *result; + + result = uio_GPRoot_alloc(); + result->ops = ops; + result->extra = extra; + result->flags = flags; + return result; +} + +void +uio_GPRoot_delete(uio_GPRoot *gPRoot) { + uio_GPRoot_deleteGPRootExtra(gPRoot); + uio_GPRoot_free(gPRoot); +} + +static uio_GPRoot * +uio_GPRoot_alloc(void) { + uio_GPRoot *result = uio_malloc(sizeof (uio_GPRoot)); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugAlloc(uio_GPRoot, (void *) result); +#endif + return result; +} + +static void +uio_GPRoot_free(uio_GPRoot *gPRoot) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugFree(uio_GPRoot, (void *) gPRoot); +#endif + uio_free(gPRoot); +} + +// The ref counter to the gPDir is not inremented. +uio_PDirHandle * +uio_GPDir_makePDirHandle(uio_GPDir *gPDir) { + return uio_PDirHandle_new(gPDir->pRoot, gPDir); +} + +#ifdef DEBUG +void +uio_GPDirEntry_print(FILE *outStream, uio_GPDirEntry *gPDirEntry) { + if (uio_GPDirEntry_isDir(gPDirEntry)) { + uio_GPDir_print(outStream, (uio_GPDir *) gPDirEntry); + } else { + uio_GPFile_print(outStream, (uio_GPFile *) gPDirEntry); + } +} + +void +uio_GPDir_print(FILE *outStream, uio_GPDir *gPDir) { + (void) outStream; + (void) gPDir; +} + +void +uio_GPFile_print(FILE *outStream, uio_GPFile *gPFile) { + (void) outStream; + (void) gPFile; +} +#endif + + diff --git a/src/libs/uio/gphys.h b/src/libs/uio/gphys.h new file mode 100644 index 0000000..7a9d6be --- /dev/null +++ b/src/libs/uio/gphys.h @@ -0,0 +1,313 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef LIBS_UIO_GPHYS_H_ +#define LIBS_UIO_GPHYS_H_ + +#include "uioport.h" + +#ifndef uio_INTERNAL_PHYSICAL +typedef void *uio_GPRootExtra; +typedef void *uio_GPDirExtra; +typedef void *uio_GPFileExtra; +#endif + +typedef struct CharHashTable_HashTable uio_GPDirEntries; + +#define uio_GPDirEntries_new() \ + ((uio_GPDirEntries *) CharHashTable_newHashTable(NULL, NULL, NULL, \ + NULL, NULL, 0, 0.85, 0.9)) +#define uio_GPDirEntries_add(hashTable, name, item) \ + CharHashTable_add((CharHashTable_HashTable *) hashTable, name, \ + (void *) item) +#define uio_GPDirEntries_remove(hashTable, name) \ + CharHashTable_remove((CharHashTable_HashTable *) hashTable, name) +#define uio_GPDirEntries_count(hashTable) \ + CharHashTable_count((CharHashTable_HashTable *) hashTable) +#define uio_GPDirEntries_find(hashTable, name) \ + ((uio_GPDirEntry *) CharHashTable_find( \ + (CharHashTable_HashTable *) hashTable, name)) +#define uio_GPDirEntries_deleteHashTable(hashTable) \ + CharHashTable_deleteHashTable((CharHashTable_HashTable *) hashTable) +//#define uio_GPDirEntries_clear(hashTable) +// CharHashTable_clear((CharHashTable_HashTable *) hashTable) +#define uio_GPDirEntries_getIterator(hashTable) \ + ((uio_GPDirEntries_Iterator *) CharHashTable_getIterator( \ + (const CharHashTable_HashTable *) hashTable)) +#define uio_GPDirEntries_iteratorDone(iterator) \ + CharHashTable_iteratorDone((const CharHashTable_Iterator *) iterator) +#define uio_GPDirEntries_iteratorName(iterator) \ + CharHashTable_iteratorKey((CharHashTable_Iterator *) iterator) +#define uio_GPDirEntries_iteratorItem(iterator) \ + ((uio_GPDirEntry *) CharHashTable_iteratorValue( \ + (CharHashTable_Iterator *) iterator)) +#define uio_GPDirEntries_iteratorNext(iterator) \ + ((uio_GPDirEntries_Iterator *) CharHashTable_iteratorNext( \ + (CharHashTable_Iterator *) iterator)) +#define uio_GPDirEntries_freeIterator(iterator) \ + CharHashTable_freeIterator(iterator) + +// 'forward' declarations +typedef struct uio_GPDirEntry uio_GPDirEntry; +typedef struct uio_GPDir uio_GPDir; +typedef struct uio_GPFile uio_GPFile; +typedef struct uio_GPRoot_Operations uio_GPRoot_Operations; +typedef struct uio_GPRoot uio_GPRoot; + +#include "charhashtable.h" +typedef CharHashTable_Iterator uio_GPDirEntries_Iterator; + +#ifdef uio_INTERNAL_GPHYS +typedef uio_GPDirEntries_Iterator *uio_NativeEntriesContext; +#endif +typedef struct uio_GPRoot *uio_PRootExtra; +typedef struct uio_GPDir uio_GPDirHandle; +typedef uio_GPDirHandle *uio_PDirHandleExtra; +typedef struct uio_GPFile uio_GPFileHandle; +typedef uio_GPFileHandle *uio_PFileHandleExtra; + + +#ifdef DEBUG +# include <stdio.h> +#endif +#include "iointrn.h" +#ifdef uio_MEM_DEBUG +# include "memdebug.h" +#endif + +struct uio_GPRoot_Operations { + void (*fillGPDir)(uio_GPDir *); + void (*deleteGPRootExtra)(uio_GPRootExtra); + void (*deleteGPDirExtra)(uio_GPDirExtra); + void (*deleteGPFileExtra)(uio_GPFileExtra); +}; + +struct uio_GPRoot { + int flags; +#define uio_GPRoot_PERSISTENT 0x4000 + /* Set if directories in this file system should not be deleted + * as long as the file system is mounted. If this flag is not + * set, the GPDir structure is only a cache. + */ + uio_GPRoot_Operations *ops; + uio_GPRootExtra extra; +}; + +#define uio_GPDirEntry_COMMON \ + int flags; \ + int ref; \ + /* Number of times this structure is referenced from the \ + * outside (so not counting the references from subdirs \ + * or files when the entry is a directory) \ + */ + +#define uio_GPDirEntry_NOCACHE uio_PRoot_NOCACHE + +/* + * uio_GPDirEntry + * super-'class' of uio_GPDir and uio_GPFile + */ +struct uio_GPDirEntry { + uio_GPDirEntry_COMMON + void *extra; +}; + +#define uio_GPDirEntry_TYPE_REG 0x0000 +#define uio_GPDirEntry_TYPE_DIR 0x0001 +#define uio_GPDirEntry_TYPEMASK 0x0001 + +/* + * uio_GPDir + * Represents a directory in a physical directory structure. + * sub-'class' of uio_GPDirEntry + */ +struct uio_GPDir { + uio_GPDirEntry_COMMON +# define uio_GPDir_NOCACHE uio_GPDirEntry_NOCACHE + /* This directory info will not be cached. + * PDIR_COMPLETE is irrelevant in this case */ +# define uio_GPDir_COMPLETE 0x1000 + /* Set if fillDir should not be called if an entry does not + * exist in a directory. Usually set if the entire dir has been + * completely read in. + */ +# define uio_GPDir_DETACHED 0x2000 + /* Set if this dir is not linked to from elsewhere in the physical + * structure */ +# define uio_GPDir_PERSISTENT 0x4000 + /* Set if this dir should not be deleted as long as the file + * system is mounted. If this flag is not set, the GPDir + * structure is only a cache. + */ + uio_GPDirExtra extra; + /* extra internal data for some filesystem types */ + uio_PRoot *pRoot; + uio_GPDirEntries *entries; +}; + + +/* + * uio_GPFile + * Represents a file in a physical directory structure. + * sub-'class' of uio_GPDirEntry + */ +struct uio_GPFile { + uio_GPDirEntry_COMMON +# define uio_GPFile_NOCACHE uio_GPDirEntry_NOCACHE + /* Info on this file will not be cached. */ + uio_GPFileExtra extra; + /* extra internal data for some filesystem types */ + uio_PRoot *pRoot; +}; + + +static inline uio_bool +uio_GPDirEntry_isReg(uio_GPDirEntry *gPDirEntry) { + return (gPDirEntry->flags & uio_GPDirEntry_TYPEMASK) == + uio_GPDirEntry_TYPE_REG; +} + +static inline uio_bool +uio_GPDirEntry_isDir(uio_GPDirEntry *gPDirEntry) { + return (gPDirEntry->flags & uio_GPDirEntry_TYPEMASK) == + uio_GPDirEntry_TYPE_DIR; +} + + +#ifdef DEBUG +void uio_GPDirEntry_print(FILE *outStream, uio_GPDirEntry *gPDirEntry); +void uio_GPDir_print(FILE *outStream, uio_GPDir *gPDir); +void uio_GPFile_print(FILE *outStream, uio_GPFile *pFile); +#endif + +uio_NativeEntriesContext uio_GPDir_openEntries(uio_PDirHandle *pDirHandle); +int uio_GPDir_readEntries(uio_NativeEntriesContext *iterator, + char *buf, size_t len); +void uio_GPDir_closeEntries(uio_NativeEntriesContext iterator); +int uio_GPDir_entryCount(const uio_GPDir *gPDir); + +int uio_gPDirFlagsFromPRootFlags(int flags); +int uio_gPFileFlagsFromPRootFlags(int flags); +uio_PRoot *uio_GPRoot_makePRoot(uio_FileSystemHandler *handler, int pRootFlags, + uio_GPRoot_Operations *ops, uio_GPRootExtra gPRootExtra, int gPRootFlags, + uio_Handle *handle, uio_GPDirExtra gPDirExtra, int gPDirFlags); +int uio_GPRoot_umount(uio_PRoot *pRoot); + +uio_GPDir *uio_GPDir_prepareSubDir(uio_GPDir *gPDir, const char *dirName); +void uio_GPDir_commitSubDir(uio_GPDir *gPDir, const char *dirName, + uio_GPDir *subDir); +void uio_GPDir_addFile(uio_GPDir *gPDir, const char *fileName, + uio_GPFile *file); +void uio_GPDir_removeFile(uio_GPDir *gPDir, const char *fileName); +void uio_GPDir_removeSubDir(uio_GPDir *gPDir, const char *dirName); +void uio_GPDir_setComplete(uio_GPDir *gPDir, uio_bool flag); +uio_GPDirEntry *uio_GPDir_getGPDirEntry(uio_GPDir *gPDir, + const char *name); +uio_PDirEntryHandle *uio_GPDir_getPDirEntryHandle( + const uio_PDirHandle *pDirHandle, const char *name); +int uio_walkGPPath(uio_GPDir *startGPDir, const char *path, + size_t pathLen, uio_GPDir **endGPDir, const char **pathRest); +uio_PDirHandle *uio_GPDir_makePDirHandle(uio_GPDir *gPDir); + +void uio_GPDir_fill(uio_GPDir *gPDir); +void uio_GPRoot_deleteGPRootExtra(uio_GPRoot *gPRoot); +void uio_GPDir_deleteGPDirExtra(uio_GPDir *gPDir); +void uio_GPFile_deleteGPFileExtra(uio_GPFile *gPFile); + +void uio_GPDirHandle_delete(uio_GPDirHandle *gPDirHandle); +void uio_GPFileHandle_delete(uio_GPFileHandle *gPFileHandle); +void uio_GPDirEntry_delete(uio_GPDirEntry *gPDirEntry); +uio_GPRoot *uio_GPRoot_new(uio_GPRoot_Operations *ops, uio_GPRootExtra extra, + int flags); +void uio_GPRoot_delete(uio_GPRoot *gPRoot); +uio_GPDir *uio_GPDir_new(uio_PRoot *pRoot, uio_GPDirExtra extra, int flags); +void uio_GPDir_delete(uio_GPDir *gPDir); +uio_GPFile *uio_GPFile_new(uio_PRoot *pRoot, uio_GPFileExtra extra, int flags); +void uio_GPFile_delete(uio_GPFile *gPFile); + + +static inline void +uio_GPDirEntry_ref(uio_GPDirEntry *gPDirEntry) { +#ifdef uio_MEM_DEBUG + if (uio_GPDirEntry_isDir(gPDirEntry)) { + uio_MemDebug_debugRef(uio_GPDir, (void *) gPDirEntry); + } else { + uio_MemDebug_debugRef(uio_GPFile, (void *) gPDirEntry); + } +#endif + gPDirEntry->ref++; +} + +static inline void +uio_GPDirEntry_unref(uio_GPDirEntry *gPDirEntry) { + assert(gPDirEntry->ref > 0); +#ifdef uio_MEM_DEBUG + if (uio_GPDirEntry_isDir(gPDirEntry)) { + uio_MemDebug_debugUnref(uio_GPDir, (void *) gPDirEntry); + } else { + uio_MemDebug_debugUnref(uio_GPFile, (void *) gPDirEntry); + } +#endif + gPDirEntry->ref--; + if (gPDirEntry->ref == 0) + uio_GPDirEntry_delete(gPDirEntry); +} + +static inline void +uio_GPDir_ref(uio_GPDir *gPDir) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugRef(uio_GPDir, (void *) gPDir); +#endif + gPDir->ref++; +} + +static inline void +uio_GPDir_unref(uio_GPDir *gPDir) { + assert(gPDir->ref > 0); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugUnref(uio_GPDir, (void *) gPDir); +#endif + gPDir->ref--; + if (gPDir->ref == 0) + uio_GPDir_delete(gPDir); +} + +static inline void +uio_GPFile_ref(uio_GPFile *gPFile) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugRef(uio_GPFile, (void *) gPFile); +#endif + gPFile->ref++; +} + +static inline void +uio_GPFile_unref(uio_GPFile *gPFile) { + assert(gPFile->ref > 0); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugUnref(uio_GPFile, (void *) gPFile); +#endif + gPFile->ref--; + if (gPFile->ref == 0) + uio_GPFile_delete(gPFile); +} + + +#endif /* LIBS_UIO_GPHYS_H_ */ + diff --git a/src/libs/uio/hashtable.c b/src/libs/uio/hashtable.c new file mode 100644 index 0000000..1f376e1 --- /dev/null +++ b/src/libs/uio/hashtable.c @@ -0,0 +1,374 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef HASHTABLE_INTERNAL + // If HASHTABLE_INTERNAL is already defined, this file is included + // as a template. In this case hashtable.h has already been included. +# define HASHTABLE_INTERNAL +# include "hashtable.h" +#endif + +#include <stdlib.h> +#include <assert.h> +#include <string.h> +#include <math.h> + +#include "mem.h" +#include "uioport.h" + +static void HASHTABLE_(setup)(HASHTABLE_(HashTable) *HashTable, + uio_uint32 size); +static void HASHTABLE_(resize)(HASHTABLE_(HashTable) *hashTable); +static inline uio_uint32 nextPower2(uio_uint32 x); + +static inline HASHTABLE_(HashTable) *HASHTABLE_(allocHashTable)(void); +static inline HASHTABLE_(HashEntry) *HASHTABLE_(newHashEntry)(uio_uint32 hash, + HASHTABLE_(Key) *key, HASHTABLE_(Value) *value, + HASHTABLE_(HashEntry) *next); +static inline HASHTABLE_(HashEntry) *HASHTABLE_(allocHashEntry)(void); +static inline void HASHTABLE_(freeHashEntry)( + HASHTABLE_(HashEntry) *entry); + +// Create a new HashTable. +HASHTABLE_(HashTable) * +HASHTABLE_(newHashTable)( + HASHTABLE_(HashFunction) hashFunction, + HASHTABLE_(EqualFunction) equalFunction, + HASHTABLE_(CopyFunction) copyFunction, + HASHTABLE_(FreeKeyFunction) freeKeyFunction, + HASHTABLE_(FreeValueFunction) freeValueFunction, + uio_uint32 initialSize, + double minFillQuotient, + double maxFillQuotient) { + HASHTABLE_(HashTable) *hashTable; + + assert(maxFillQuotient >= minFillQuotient); + + hashTable = HASHTABLE_(allocHashTable)(); + hashTable->hashFunction = hashFunction; + hashTable->equalFunction = equalFunction; + hashTable->copyFunction = copyFunction; + hashTable->freeKeyFunction = freeKeyFunction; + hashTable->freeValueFunction = freeValueFunction; + + hashTable->minFillQuotient = minFillQuotient; + hashTable->maxFillQuotient = maxFillQuotient; + HASHTABLE_(setup)(hashTable, initialSize); + + return hashTable; +} + +// Add an entry to the HashTable. +uio_bool +HASHTABLE_(add)(HASHTABLE_(HashTable) *hashTable, + const HASHTABLE_(Key) *key, HASHTABLE_(Value) *value) { + uio_uint32 hash; + struct HASHTABLE_(HashEntry) *entry; + + hash = HASHTABLE_(HASH)(hashTable, key); + entry = hashTable->entries[hash & hashTable->hashMask]; + while (entry != NULL) { + if (HASHTABLE_(EQUAL)(hashTable, key, entry->key)) { + // key is already present + return false; + } + entry = entry->next; + } + +#ifdef HashTable_PROFILE + if (hashTable->entries[hash & hashTable->hashMask] != NULL) + hashTable->numCollisions++; +#endif + hashTable->entries[hash & hashTable->hashMask] = + HASHTABLE_(newHashEntry)(hash, + HASHTABLE_(COPY)(hashTable, key), value, + hashTable->entries[hash & hashTable->hashMask]); + + hashTable->numEntries++; + if (hashTable->numEntries > hashTable->maxSize) + HASHTABLE_(resize)(hashTable); + + return true; +} + +// Remove an entry with a specified Key from the HashTable. +uio_bool +HASHTABLE_(remove)(HASHTABLE_(HashTable) *hashTable, + const HASHTABLE_(Key) *key) { + uio_uint32 hash; + struct HASHTABLE_(HashEntry) **entry, *next; + + hash = HASHTABLE_(HASH)(hashTable, key); + entry = &hashTable->entries[hash & hashTable->hashMask]; + while (1) { + if (*entry == NULL) + return false; + if (HASHTABLE_(EQUAL)(hashTable, key, (*entry)->key)) { + // found the key + break; + } + entry = &(*entry)->next; + } + next = (*entry)->next; + HASHTABLE_(FREEKEY)(hashTable, (*entry)->key); + HASHTABLE_(FREEVALUE)(hashTable, (*entry)->value); + HASHTABLE_(freeHashEntry)(*entry); + *entry = next; + + hashTable->numEntries--; + if (hashTable->numEntries < hashTable->minSize) + HASHTABLE_(resize)(hashTable); + + return true; +} + +// Find the Value stored for some Key. +HASHTABLE_(Value) * +HASHTABLE_(find)(HASHTABLE_(HashTable) *hashTable, + const HASHTABLE_(Key) *key) { + uio_uint32 hash; + struct HASHTABLE_(HashEntry) *entry; + + hash = HASHTABLE_(HASH)(hashTable, key); + entry = hashTable->entries[hash & hashTable->hashMask]; + while (entry != NULL) { + if (HASHTABLE_(EQUAL)(hashTable, key, entry->key)) { + // found the key + return entry->value; + } + entry = entry->next; + } + return NULL; +} + +// Returns the number of entries in the HashTable. +uio_uint32 +HASHTABLE_(count)(const HASHTABLE_(HashTable) *hashTable) { + return hashTable->numEntries; +} + +// Auxiliary function to (re)initialise the buckets in the HashTable. +static void +HASHTABLE_(setup)(HASHTABLE_(HashTable) *hashTable, uio_uint32 initialSize) { + if (initialSize < 4) + initialSize = 4; + hashTable->size = nextPower2(ceil(((double) initialSize) / + hashTable->maxFillQuotient)); + hashTable->hashMask = hashTable->size - 1; + hashTable->minSize = ceil(((double) (hashTable->size >> 1)) + * hashTable->minFillQuotient); + hashTable->maxSize = floor(((double) hashTable->size) + * hashTable->maxFillQuotient); + hashTable->entries = uio_calloc(hashTable->size, + sizeof (HASHTABLE_(HashEntry) *)); + hashTable->numEntries = 0; +#ifdef HashTable_PROFILE + hashTable->numCollisions = 0; +#endif +} + +// Resize the buckets in the HashTable. +static void +HASHTABLE_(resize)(HASHTABLE_(HashTable) *hashTable) { + HASHTABLE_(HashEntry) **oldEntries; + HASHTABLE_(HashEntry) *entry, *next; + HASHTABLE_(HashEntry) **newLocation; + uio_uint32 oldNumEntries; + uio_uint32 i; + + oldNumEntries = hashTable->numEntries; + oldEntries = hashTable->entries; + + HASHTABLE_(setup)(hashTable, hashTable->numEntries); + hashTable->numEntries = oldNumEntries; + + i = 0; + while (oldNumEntries > 0) { + entry = oldEntries[i]; + while (entry != NULL) { + next = entry->next; + newLocation = &hashTable->entries[entry->hash & + hashTable->hashMask]; +#ifdef HashTable_PROFILE + if (*newLocation != NULL) + hashTable->numCollisions++; +#endif + entry->next = *newLocation; + *newLocation = entry; + oldNumEntries--; + entry = next; + } + i++; + } + + uio_free(oldEntries); +} + +// Adapted from "Hackers Delight" +// Returns the smallest power of two greater or equal to x. +static inline uio_uint32 +nextPower2(uio_uint32 x) { + x--; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + return x + 1; +} + +// Get an iterator to iterate through all the entries in the HashTable. +// NB: Iterator should be considered invalid if the HashTable is changed. +// TODO: change this (make it thread-safe) +// this can be done by only marking items as deleted when +// there are outstanding iterators. +HASHTABLE_(Iterator) * +HASHTABLE_(getIterator)(const HASHTABLE_(HashTable) *hashTable) { + HASHTABLE_(Iterator) *iterator; + uio_uint32 i; + + iterator = uio_malloc(sizeof (HASHTABLE_(Iterator))); + iterator->hashTable = hashTable; + + // Look for the first used bucket. + for (i = 0; i < iterator->hashTable->size; i++) { + if (iterator->hashTable->entries[i] != NULL) { + // Found a used bucket. + iterator->bucketNr = i; + iterator->entry = iterator->hashTable->entries[i]; + return iterator; + } + } + + // No entries were found. + iterator->bucketNr = i; + iterator->entry = NULL; + return iterator; +} + +// Returns true if and only if there are no more entries in the hash table +// for the Iterator to find. +int +HASHTABLE_(iteratorDone)(const HASHTABLE_(Iterator) *iterator) { + return iterator->bucketNr >= iterator->hashTable->size; +} + +// Get the Key of the entry pointed to by an Iterator. +HASHTABLE_(Key) * +HASHTABLE_(iteratorKey)(HASHTABLE_(Iterator) *iterator) { + return iterator->entry->key; +} + +// Get the Value of the entry pointed to by an Iterator. +HASHTABLE_(Value) * +HASHTABLE_(iteratorValue)(HASHTABLE_(Iterator) *iterator) { + return iterator->entry->value; +} + +// Move the Iterator to the next entry in the HashTable. +// Should not be called if the iterator is already past the last entry. +HASHTABLE_(Iterator) * +HASHTABLE_(iteratorNext)(HASHTABLE_(Iterator) *iterator) { + uio_uint32 i; + + // If there's another entry in this bucket, use that. + iterator->entry = iterator->entry->next; + if (iterator->entry != NULL) + return iterator; + + // Look for the next used bucket. + for (i = iterator->bucketNr + 1; i < iterator->hashTable->size; i++) { + if (iterator->hashTable->entries[i] != NULL) { + // Found another used bucket. + iterator->bucketNr = i; + iterator->entry = iterator->hashTable->entries[i]; + return iterator; + } + } + + // No more entries were found. + iterator->bucketNr = i; + iterator->entry = NULL; + return iterator; +} + +// Free the Iterator. +void +HASHTABLE_(freeIterator)(HASHTABLE_(Iterator) *iterator) { + uio_free(iterator); +} + +// Auxiliary function to allocate a HashTable. +static inline HASHTABLE_(HashTable) * +HASHTABLE_(allocHashTable)(void) { + return uio_malloc(sizeof (HASHTABLE_(HashTable))); +} + +// Auxiliary function to create a HashEntry. +static inline HASHTABLE_(HashEntry) * +HASHTABLE_(newHashEntry)(uio_uint32 hash, HASHTABLE_(Key) *key, + HASHTABLE_(Value) *value, HASHTABLE_(HashEntry) *next) { + HASHTABLE_(HashEntry) *result; + + result = HASHTABLE_(allocHashEntry)(); + result->hash = hash; + result->key = key; + result->value = value; + result->next = next; + return result; +} + +// Allocate a new HashEntry. +static inline HASHTABLE_(HashEntry) * +HASHTABLE_(allocHashEntry)(void) { + return uio_malloc(sizeof (HASHTABLE_(HashEntry))); +} + +// Delete the HashTable. +void +HASHTABLE_(deleteHashTable)(HASHTABLE_(HashTable) *hashTable) { + uio_uint32 i; + HASHTABLE_(HashEntry) *entry, *next; + HASHTABLE_(HashEntry) **bucketPtr; + + i = hashTable->numEntries; + bucketPtr = hashTable->entries; + while (i > 0) { + entry = *bucketPtr; + while (entry != NULL) { + next = entry->next; + HASHTABLE_(FREEKEY)(hashTable, entry->key); + HASHTABLE_(FREEVALUE)(hashTable, entry->value); + HASHTABLE_(freeHashEntry)(entry); + entry = next; + i--; + } + bucketPtr++; + } + uio_free(hashTable->entries); + uio_free(hashTable); +} + +// Auxiliary function to deallocate a HashEntry. +static inline void +HASHTABLE_(freeHashEntry)(HASHTABLE_(HashEntry) *entry) { + uio_free(entry); +} + diff --git a/src/libs/uio/hashtable.h b/src/libs/uio/hashtable.h new file mode 100644 index 0000000..eb4437f --- /dev/null +++ b/src/libs/uio/hashtable.h @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +// The 'already included' check must be done slightly more complicated +// than usually. This file may be included directly only once, +// but it may be included my derivative HashTable definitions that use +// this file as a template more than once. +#if !defined(_HASHTABLE_H) || defined(HASHTABLE_GENERIC) +#if defined(HASHTABLE_) +# define HASHTABLE_GENERIC +#endif + +#include "types.h" +#include "uioport.h" + +// Define to enable profiling. +#define HashTable_PROFILE + +// You can use inline hash functions for extra speed, by using this file as +// a template. +// To do this, make a new .h and .c file. In the .h file, define the macros +// (and typedefs) from the HASHTABLE_ block below. +// In the .c file, #define HASHTABLE_INTERNAL, #include the .h file +// and hashtable.c (in this order), and add the necessary functions. +// If you do not need to free the Value, you can define HashTable_FREEVALUE +// as a no-op. +#ifndef HASHTABLE_ +# define HASHTABLE_(identifier) HashTable ## _ ## identifier + typedef void HashTable_Key; + typedef void HashTable_Value; +# define HashTable_HASH(hashTable, hashValue) \ + (hashTable)->hashFunction(hashValue) +# define HashTable_EQUAL(hashTable, hashKey1, hashKey2) \ + (hashTable)->equalFunction(hashKey1, hashKey2) +# define HashTable_COPY(hashTable, hashKey) \ + (hashTable)->copyFunction(hashKey) +# define HashTable_FREEKEY(hashTable, hashKey) \ + (hashTable)->freeKeyFunction(hashKey) +# define HashTable_FREEVALUE(hashTable, hashValue) \ + (hashTable)->freeValueFunction(hashValue) +#endif + + + +typedef uio_uint32 (*HASHTABLE_(HashFunction))(const HASHTABLE_(Key) *); +typedef uio_bool (*HASHTABLE_(EqualFunction))(const HASHTABLE_(Key) *, + const HASHTABLE_(Key) *); +typedef HASHTABLE_(Value) *(*HASHTABLE_(CopyFunction))( + const HASHTABLE_(Key) *); +typedef void (*HASHTABLE_(FreeKeyFunction))(HASHTABLE_(Key) *); +typedef void (*HASHTABLE_(FreeValueFunction))(HASHTABLE_(Value) *); + +typedef struct HASHTABLE_(HashTable) HASHTABLE_(HashTable); +typedef struct HASHTABLE_(HashEntry) HASHTABLE_(HashEntry); +typedef struct HASHTABLE_(Iterator) HASHTABLE_(Iterator); + +struct HASHTABLE_(HashTable) { + HASHTABLE_(HashFunction) hashFunction; + // Function creating a uio_uint32 hash of a key. + HASHTABLE_(EqualFunction) equalFunction; + // Function used to compare two keys. + HASHTABLE_(CopyFunction) copyFunction; + // Function used to copy a key. + HASHTABLE_(FreeKeyFunction) freeKeyFunction; + // Function used to free a key. + HASHTABLE_(FreeValueFunction) freeValueFunction; + // Function used to free a value. Called when an entry is + // removed using the remove function, or for entries + // still in the HashTable when the HashTable is deleted. + + double minFillQuotient; + // How much of half of the hashtable needs to be filled + // before resizing to size/2. + double maxFillQuotient; + // How much of the hashTable needs to be filled before + // resizing to size*2. + uio_uint32 minSize; + // Resize to size/2 when below this size. + uio_uint32 maxSize; + // Resize to size*2 when above this size. + uio_uint32 size; + // The number of buckets in the hash table. + uio_uint32 hashMask; + // Mask to take on a the calculated hash value, to make it + // fit into the table. + + HASHTABLE_(HashEntry) **entries; + // The actual entries + + uio_uint32 numEntries; +#ifdef HashTable_PROFILE + uio_uint32 numCollisions; +#endif +}; + +struct HASHTABLE_(HashEntry) { + uio_uint32 hash; + HASHTABLE_(Key) *key; + HASHTABLE_(Value) *value; + HASHTABLE_(HashEntry) *next; +}; + +struct HASHTABLE_(Iterator) { + const HASHTABLE_(HashTable) *hashTable; + uio_uint32 bucketNr; + HASHTABLE_(HashEntry) *entry; +}; + +HASHTABLE_(HashTable) *HASHTABLE_(newHashTable)( + HASHTABLE_(HashFunction) hashFunction, + HASHTABLE_(EqualFunction) equalFunction, + HASHTABLE_(CopyFunction) copyFunction, + HASHTABLE_(FreeKeyFunction) freeKeyFunction, + HASHTABLE_(FreeValueFunction) freeValueFunction, + uio_uint32 initialSize, + double minFillQuotient, double maxFillQuotient); +uio_bool HASHTABLE_(add)(HASHTABLE_(HashTable) *hashTable, + const HASHTABLE_(Key) *key, HASHTABLE_(Value) *value); +uio_bool HASHTABLE_(remove)(HASHTABLE_(HashTable) *hashTable, + const HASHTABLE_(Key) *key); +HASHTABLE_(Value) *HASHTABLE_(find)( + HASHTABLE_(HashTable) *hashTable, const HASHTABLE_(Key) *key); +uio_uint32 HASHTABLE_(count)(const HASHTABLE_(HashTable) *hashTable); +void HASHTABLE_(deleteHashTable)(HASHTABLE_(HashTable) *hashTable); +HASHTABLE_(Iterator) *HASHTABLE_(getIterator)( + const HASHTABLE_(HashTable) *hashTable); +int HASHTABLE_(iteratorDone)(const HASHTABLE_(Iterator) *iterator); +HASHTABLE_(Key) *HASHTABLE_(iteratorKey)(HASHTABLE_(Iterator) *iterator); +HASHTABLE_(Value) *HASHTABLE_(iteratorValue)(HASHTABLE_(Iterator) *iterator); +HASHTABLE_(Iterator) *HASHTABLE_(iteratorNext)(HASHTABLE_(Iterator) *iterator); +void HASHTABLE_(freeIterator)(HASHTABLE_(Iterator) *iterator); + +#ifndef HASHTABLE_INTERNAL +# undef HASHTABLE_ +#endif + +#endif /* !defined(_HASHTABLE_H) || defined(HASHTABLE_GENERIC) */ + + + diff --git a/src/libs/uio/io.c b/src/libs/uio/io.c new file mode 100644 index 0000000..247d1e2 --- /dev/null +++ b/src/libs/uio/io.c @@ -0,0 +1,1859 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <errno.h> +#include <fcntl.h> +#include <assert.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> + +#include "iointrn.h" +#include "ioaux.h" +#include "mount.h" +#include "fstypes.h" +#include "mounttree.h" +#include "physical.h" +#include "paths.h" +#include "mem.h" +#include "uioutils.h" +#include "uioport.h" +#ifdef uio_MEM_DEBUG +# include "memdebug.h" +#endif + +#if 0 +static int uio_accessDir(uio_DirHandle *dirHandle, const char *path, + int mode); +#endif +static int uio_statDir(uio_DirHandle *dirHandle, const char *path, + struct stat *statBuf); +static int uio_statOneDir(uio_PDirHandle *pDirHandle, struct stat *statBuf); + +static void uio_PDirHandles_delete(uio_PDirHandle *pDirHandles[], + int numPDirHandles); + +static inline uio_PDirHandle *uio_PDirHandle_alloc(void); +static inline void uio_PDirHandle_free(uio_PDirHandle *pDirHandle); +static inline uio_PFileHandle *uio_PFileHandle_alloc(void); +static inline void uio_PFileHandle_free(uio_PFileHandle *pFileHandle); + +static uio_DirHandle *uio_DirHandle_new(uio_Repository *repository, char *path, + char *rootEnd); +static inline uio_DirHandle *uio_DirHandle_alloc(void); +static inline void uio_DirHandle_free(uio_DirHandle *dirHandle); + +static inline uio_Handle *uio_Handle_alloc(void); +static inline void uio_Handle_free(uio_Handle *handle); + +static uio_MountHandle *uio_MountHandle_new(uio_Repository *repository, + uio_MountInfo *mountInfo); +static inline void uio_MountHandle_delete(uio_MountHandle *mountHandle); +static inline uio_MountHandle *uio_MountHandle_alloc(void); +static inline void uio_MountHandle_free(uio_MountHandle *mountHandle); + + + +void +uio_init(void) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_init(); +#endif + uio_registerDefaultFileSystems(); +} + +void +uio_unInit(void) { + uio_unRegisterDefaultFileSystems(); +#ifdef uio_MEM_DEBUG +# ifdef DEBUG + uio_MemDebug_printPointers(stderr); + fflush(stderr); +# endif + uio_MemDebug_unInit(); +#endif +} + +uio_Repository * +uio_openRepository(int flags) { + return uio_Repository_new(flags); +} + +void +uio_closeRepository(uio_Repository *repository) { + uio_unmountAllDirs(repository); + uio_Repository_unref(repository); +} + +/* + * Function name: uio_mountDir + * Description: Grafts a directory from inside a physical fileSystem + * into the locical filesystem, at a specified directory. + * Arguments: destRep - the repository where the newly mounted dir + * is to be grafted. + * mountPoint - the path to the directory where the dir + * is to be grafted. + * fsType - the file system type of physical fileSystem + * pointed to by sourcePath. + * sourceDir - the directory to which 'sourcePath' is to + * be taken relative. + * sourcePath - a path relative to sourceDir, which contains + * the file/directory to be mounted. + * If sourceDir and sourcePath are NULL, the file + * system of the operating system will be used. + * inPath - the location relative to the root of the newly + * mounted fileSystem, pointing to the directory + * that is to be grafted. + * Note: If fsType is uio_FSTYPE_STDIO, inPath is + * relative to the root of the filesystem, NOT to + * the current working dir. + * autoMount - array of automount options in function + * in this mountPoint. + * flags - one of uio_MOUNT_TOP, uio_MOUNT_BOTTOM, + * uio_MOUNT_BELOW, uio_MOUNT_ABOVE, specifying + * the precedence of this mount, OR'ed with + * one or more of the following flags: + * uio_MOUNT_RDONLY (no writing is allowed) + * relative - If 'flags' includes uio_MOUNT_BELOW or + * uio_MOUNT_ABOVE, this is the mount handle + * where the new mount is relative to. + * Otherwise, it should be NULL. + * Returns: a handle suitable for uio_unmountDir() + * NULL if an error occured. In this case 'errno' is set. + */ +uio_MountHandle * +uio_mountDir(uio_Repository *destRep, const char *mountPoint, + uio_FileSystemID fsType, + uio_DirHandle *sourceDir, const char *sourcePath, + const char *inPath, uio_AutoMount **autoMount, int flags, + uio_MountHandle *relative) { + uio_PRoot *pRoot; + uio_Handle *handle; + uio_FileSystemHandler *handler; + uio_MountInfo *relativeInfo; + + switch (flags & uio_MOUNT_LOCATION_MASK) { + case uio_MOUNT_TOP: + case uio_MOUNT_BOTTOM: + if (relative != NULL) { + errno = EINVAL; + return NULL; + } + relativeInfo = NULL; + break; + case uio_MOUNT_BELOW: + case uio_MOUNT_ABOVE: + if (relative == NULL) { + errno = EINVAL; + return NULL; + } + relativeInfo = relative->mountInfo; + break; + default: + abort(); + } + + if (mountPoint[0] == '/') + mountPoint++; + if (!validPathName(mountPoint, strlen(mountPoint))) { + errno = EINVAL; + return NULL; + } + + // TODO: check if the filesystem is already mounted, and if so, reuse it. + // A RO filedescriptor will need to be replaced though if the + // filesystem needs to be remounted RW now. + if (sourceDir == NULL) { + if (sourcePath != NULL) { + // bad: sourceDir is NULL, but sourcePath isn't + errno = EINVAL; + return NULL; + } + handle = NULL; + } else { + if (sourcePath == NULL) { + // bad: sourcePath is NULL, but sourceDir isn't + errno = EINVAL; + return NULL; + } + handle = uio_open(sourceDir, sourcePath, + ((flags & uio_MOUNT_RDONLY) == uio_MOUNT_RDONLY ? + O_RDONLY : O_RDWR) +#ifdef WIN32 + | O_BINARY +#endif + , 0); + if (handle == NULL) { + // errno is set + return NULL; + } + } + + handler = uio_getFileSystemHandler(fsType); + if (handler == NULL) { + if (handle) + uio_close(handle); + errno = ENODEV; + return NULL; + } + + assert(handler->mount != NULL); + pRoot = (handler->mount)(handle, flags); + if (pRoot == NULL) { + int savedErrno; + + savedErrno = errno; + if (handle) + uio_close(handle); + errno = savedErrno; + return NULL; + } + + if (handle) { + // Close this reference to handle. + // The physical layer may store the link in pRoot, in which it + // will be cleaned up from uio_unmount(). + uio_close(handle); + } + + // The new file system is ready, now we need to find the specified + // dir inside it and put it in its place in the mountTree. + { + uio_PDirHandle *endDirHandle; + const char *endInPath; + char *dirName; + uio_MountInfo *mountInfo; + uio_MountTree *mountTree; + uio_PDirHandle *pRootHandle; +#ifdef BACKSLASH_IS_PATH_SEPARATOR + char *unixPath; + + unixPath = dosToUnixPath(inPath); + inPath = unixPath; +#endif /* BACKSLASH_IS_PATH_SEPARATOR */ + + if (inPath[0] == '/') + inPath++; + pRootHandle = uio_PRoot_getRootDirHandle(pRoot); + uio_walkPhysicalPath(pRootHandle, inPath, strlen(inPath), + &endDirHandle, &endInPath); + if (*endInPath != '\0') { + // Path inside the filesystem to mount does not exist. +#ifdef BACKSLASH_IS_PATH_SEPARATOR + uio_free(unixPath); +#endif /* BACKSLASH_IS_PATH_SEPARATOR */ + uio_PDirHandle_unref(endDirHandle); + uio_PRoot_unrefMount(pRoot); + errno = ENOENT; + return NULL; + } + + dirName = uio_malloc(endInPath - inPath + 1); + memcpy(dirName, inPath, endInPath - inPath); + dirName[endInPath - inPath] = '\0'; +#ifdef BACKSLASH_IS_PATH_SEPARATOR + // InPath is a copy with the paths fixed. + uio_free(unixPath); +#endif /* BACKSLASH_IS_PATH_SEPARATOR */ + mountInfo = uio_MountInfo_new(fsType, NULL, endDirHandle, dirName, + autoMount, NULL, flags); + uio_repositoryAddMount(destRep, mountInfo, + flags & uio_MOUNT_LOCATION_MASK, relativeInfo); + mountTree = uio_mountTreeAddMountInfo(destRep, destRep->mountTree, + mountInfo, mountPoint, flags & uio_MOUNT_LOCATION_MASK, + relativeInfo); + // mountTree is the node in destRep->mountTree where mountInfo + // leads to. + mountInfo->mountTree = mountTree; + mountInfo->mountHandle = uio_MountHandle_new(destRep, mountInfo); + return mountInfo->mountHandle; + } +} + +// Mount a repository directory into same repository at a different location +// From fossil. +uio_MountHandle * +uio_transplantDir(const char *mountPoint, uio_DirHandle *sourceDir, int flags, + uio_MountHandle *relative) { + uio_MountInfo *relativeInfo; + int numPDirHandles; + uio_PDirHandle **pDirHandles; + uio_MountTreeItem **treeItems; + int i; + uio_MountHandle *handle = NULL; + + if ((flags & uio_MOUNT_RDONLY) != uio_MOUNT_RDONLY) { + // Only read-only transplants supported atm + errno = ENOSYS; + return NULL; + } + + switch (flags & uio_MOUNT_LOCATION_MASK) { + case uio_MOUNT_TOP: + case uio_MOUNT_BOTTOM: + if (relative != NULL) { + errno = EINVAL; + return NULL; + } + relativeInfo = NULL; + break; + case uio_MOUNT_BELOW: + case uio_MOUNT_ABOVE: + if (relative == NULL) { + errno = EINVAL; + return NULL; + } + relativeInfo = relative->mountInfo; + break; + default: + abort(); + } + + if (mountPoint[0] == '/') + mountPoint++; + if (!validPathName(mountPoint, strlen(mountPoint))) { + errno = EINVAL; + return NULL; + } + + if (uio_getPathPhysicalDirs(sourceDir, "", 0, + &pDirHandles, &numPDirHandles, &treeItems) == -1) { + // errno is set + return NULL; + } + if (numPDirHandles == 0) { + errno = ENOENT; + return NULL; + } + + // TODO: We only transplant the first read-only physical dir that we find + // Maybe transplant all of them? We would then have several + // uio_MountHandles to return. + for (i = 0; i < numPDirHandles; ++i) { + uio_PDirHandle *pDirHandle = pDirHandles[i]; + uio_MountInfo *oldMountInfo = treeItems[i]->mountInfo; + uio_Repository *rep = oldMountInfo->mountHandle->repository; + uio_MountInfo *mountInfo; + uio_MountTree *mountTree; + + // Only interested in read-only dirs in this incarnation + if (!uio_mountInfoIsReadOnly(oldMountInfo)) + continue; + + mountInfo = uio_MountInfo_new(oldMountInfo->fsID, NULL, pDirHandle, + uio_strdup(""), oldMountInfo->autoMount, NULL, flags); + // New mount references the same handles + uio_PDirHandle_ref(pDirHandle); + uio_PRoot_refMount(pDirHandle->pRoot); + + uio_repositoryAddMount(rep, mountInfo, + flags & uio_MOUNT_LOCATION_MASK, relativeInfo); + mountTree = uio_mountTreeAddMountInfo(rep, rep->mountTree, + mountInfo, mountPoint, flags & uio_MOUNT_LOCATION_MASK, + relativeInfo); + // mountTree is the node in rep->mountTree where mountInfo leads to + mountInfo->mountTree = mountTree; + mountInfo->mountHandle = uio_MountHandle_new(rep, mountInfo); + handle = mountInfo->mountHandle; + break; + } + + uio_PDirHandles_delete(pDirHandles, numPDirHandles); + uio_free(treeItems); + + if (handle == NULL) + errno = ENOENT; + + return handle; +} + +int +uio_unmountDir(uio_MountHandle *mountHandle) { + uio_PRoot *pRoot; + + pRoot = mountHandle->mountInfo->pDirHandle->pRoot; + + // check if it's in use +#ifdef DEBUG + if (pRoot->mountRef == 1 && pRoot->handleRef > 0) { + fprintf(stderr, "Warning: File system to be unmounted still " + "has file descriptors open. The file system will not " + "be deallocated until these are all closed.\n"); + } +#endif + + // TODO: lock (and furtheron unlock) repository + + // remove from mount tree + uio_mountTreeRemoveMountInfo(mountHandle->repository, + mountHandle->mountInfo->mountTree, + mountHandle->mountInfo); + + // remove from mount list. + uio_repositoryRemoveMount(mountHandle->repository, + mountHandle->mountInfo); + + uio_MountInfo_delete(mountHandle->mountInfo); + + uio_MountHandle_delete(mountHandle); + uio_PRoot_unrefMount(pRoot); + return 0; +} + +int +uio_unmountAllDirs(uio_Repository *repository) { + int i; + + i = repository->numMounts; + while (i--) + uio_unmountDir(repository->mounts[i]->mountHandle); + return 0; +} + +uio_FileSystemID +uio_getMountFileSystemType(uio_MountHandle *mountHandle) { + return mountHandle->mountInfo->fsID; +} + +int +uio_close(uio_Handle *handle) { + uio_Handle_unref(handle); + return 0; +} + +int +uio_rename(uio_DirHandle *oldDir, const char *oldPath, + uio_DirHandle *newDir, const char *newPath) { + uio_PDirHandle *oldPReadDir, *newPReadDir, *newPWriteDir; + uio_MountInfo *oldReadMountInfo, *newReadMountInfo, *newWriteMountInfo; + char *oldName, *newName; + int retVal; + + if (uio_getPhysicalAccess(oldDir, oldPath, O_RDONLY, 0, + &oldReadMountInfo, &oldPReadDir, NULL, + NULL, NULL, NULL, &oldName) == -1) { + // errno is set + return -1; + } + + if (uio_getPhysicalAccess(newDir, newPath, O_WRONLY | O_CREAT | O_EXCL, + uio_GPA_NOWRITE, &newReadMountInfo, &newPReadDir, NULL, + &newWriteMountInfo, &newPWriteDir, NULL, &newName) == -1) { + int savedErrno = errno; + uio_PDirHandle_unref(oldPReadDir); + uio_free(oldName); + errno = savedErrno; + return -1; + } + + if (oldReadMountInfo != newWriteMountInfo) { + uio_PDirHandle_unref(oldPReadDir); + uio_PDirHandle_unref(newPReadDir); + uio_PDirHandle_unref(newPWriteDir); + uio_free(oldName); + uio_free(newName); + errno = EXDEV; + return -1; + } + + if (uio_mountInfoIsReadOnly(oldReadMountInfo)) { + // XXX: Doesn't uio_getPhysicalAccess already handle this? + // It doesn't return EROFS though; perhaps it should. + uio_PDirHandle_unref(oldPReadDir); + uio_PDirHandle_unref(newPReadDir); + uio_PDirHandle_unref(newPWriteDir); + uio_free(oldName); + uio_free(newName); + errno = EROFS; + return -1; + } + + if (oldReadMountInfo->pDirHandle->pRoot->handler->rename == NULL) { + uio_PDirHandle_unref(oldPReadDir); + uio_PDirHandle_unref(newPReadDir); + uio_PDirHandle_unref(newPWriteDir); + uio_free(oldName); + uio_free(newName); + errno = ENOSYS; + return -1; + } + retVal = (oldReadMountInfo->pDirHandle->pRoot->handler->rename)( + oldPReadDir, oldName, newPWriteDir, newName); + if (retVal == -1) { + int savedErrno = errno; + uio_PDirHandle_unref(oldPReadDir); + uio_PDirHandle_unref(newPReadDir); + uio_PDirHandle_unref(newPWriteDir); + uio_free(oldName); + uio_free(newName); + errno = savedErrno; + return -1; + } + + uio_PDirHandle_unref(oldPReadDir); + uio_PDirHandle_unref(newPReadDir); + uio_PDirHandle_unref(newPWriteDir); + uio_free(oldName); + uio_free(newName); + return 0; +} + +int +uio_access(uio_DirHandle *dir, const char *path, int mode) { + (void) dir; + (void) path; + (void) mode; + errno = ENOSYS; // Not implemented. + return -1; + +#if 0 + uio_PDirHandle *pReadDir; + uio_MountInfo *readMountInfo; + char *name; + int result; + + if (uio_getPhysicalAccess(dir, path, O_RDONLY, 0, + &readMountInfo, &pReadDir, NULL, + NULL, NULL, NULL, &name) == -1) { + // XXX: I copied this part from uio_stat(). Is this what I need? + if (uio_accessDir(dir, path, statBuf) == -1) { + // errno is set + return -1; + } + return 0; + } + + if (pReadDir->pRoot->handler->access == NULL) { + uio_PDirHandle_unref(pReadDir); + uio_free(name); + errno = ENOSYS; + return -1; + } + + result = (pReadDir->pRoot->handler->access)(pReadDir, name, mode); + if (result == -1) { + int savedErrno = errno; + uio_PDirHandle_unref(pReadDir); + uio_free(name); + errno = savedErrno; + return -1; + } + + uio_PDirHandle_unref(pReadDir); + uio_free(name); + return result; +#endif +} + +#if 0 +// auxiliary function to uio_access +static int +uio_accessDir(uio_DirHandle *dirHandle, const char *path, int mode) { + int numPDirHandles; + uio_PDirHandle **pDirHandles; + + if (mode & R_OK) + { + // Read permission is always granted. Nothing to check here. + } + + if (uio_getPathPhysicalDirs(dirHandle, path, strlen(path), + &pDirHandles, &numPDirHandles, NULL) == -1) { + // errno is set + return -1; + } + + if (numPDirHandles == 0) { + errno = ENOENT; + return -1; + } + + if (mode & F_OK) + { + // We need to check whether each of the directories is complete + + // WORK + } + + if (mode & W_OK) { + // If there is any directory where writing is allowed, then + // we can write. + + // WORK + errno = ENOENT; + return -1; + +#if 0 + if (uio_statOneDir(pDirHandles[0], statBuf) == -1) { + int savedErrno = errno; + uio_PDirHandles_delete(pDirHandles, numPDirHandles); + errno = savedErrno; + return -1; + } + // TODO: atm, fstat'ing a dir will show the info for the topmost + // dir. Maybe it would make sense of merging the bits. (How?) + +#if 0 + for (i = 1; i < numPDirHandles; i++) { + struct stat statOne; + uio_PDirHandle *pDirHandle; + + if (statOneDir(pDirHandles[i], &statOne) == -1) { + // errno is set + int savedErrno = errno; + uio_PDirHandles_delete(pDirHandles, numPDirHandles); + errno = savedErrno; + return -1; + } + + // Merge dirs: + + + } +#endif +#endif + } + + if (mode & X_OK) { + // XXX: Not implemented. + uio_PDirHandles_delete(pDirHandles, numPDirHandles); + errno = ENOSYS; + return -1; + } + + uio_PDirHandles_delete(pDirHandles, numPDirHandles); + return 0; +} +#endif + +int +uio_fstat(uio_Handle *handle, struct stat *statBuf) { + if (handle->root->handler->fstat == NULL) { + errno = ENOSYS; + return -1; + } + return (handle->root->handler->fstat)(handle, statBuf); +} + +int +uio_stat(uio_DirHandle *dir, const char *path, struct stat *statBuf) { + uio_PDirHandle *pReadDir; + uio_MountInfo *readMountInfo; + char *name; + int result; + + if (uio_getPhysicalAccess(dir, path, O_RDONLY, 0, + &readMountInfo, &pReadDir, NULL, + NULL, NULL, NULL, &name) == -1) { + if (uio_statDir(dir, path, statBuf) == -1) { + // errno is set + return -1; + } + return 0; + } + + if (pReadDir->pRoot->handler->stat == NULL) { + uio_PDirHandle_unref(pReadDir); + uio_free(name); + errno = ENOSYS; + return -1; + } + + result = (pReadDir->pRoot->handler->stat)(pReadDir, name, statBuf); + if (result == -1) { + int savedErrno = errno; + uio_PDirHandle_unref(pReadDir); + uio_free(name); + errno = savedErrno; + return -1; + } + + uio_PDirHandle_unref(pReadDir); + uio_free(name); + return result; +} + +// auxiliary function to uio_stat +static int +uio_statDir(uio_DirHandle *dirHandle, const char *path, + struct stat *statBuf) { + int numPDirHandles; + uio_PDirHandle **pDirHandles; + + if (uio_getPathPhysicalDirs(dirHandle, path, strlen(path), + &pDirHandles, &numPDirHandles, NULL) == -1) { + // errno is set + return -1; + } + + if (numPDirHandles == 0) { + errno = ENOENT; + return -1; + } + + if (uio_statOneDir(pDirHandles[0], statBuf) == -1) { + int savedErrno = errno; + uio_PDirHandles_delete(pDirHandles, numPDirHandles); + errno = savedErrno; + return -1; + } + // TODO: atm, fstat'ing a dir will show the info for the topmost + // dir. Maybe it would make sense of merging the bits. (How?) + +#if 0 + for (i = 1; i < numPDirHandles; i++) { + struct stat statOne; + uio_PDirHandle *pDirHandle; + + if (statOneDir(pDirHandles[i], &statOne) == -1) { + // errno is set + int savedErrno = errno; + uio_PDirHandles_delete(pDirHandles, numPDirHandles); + errno = savedErrno; + return -1; + } + + // Merge dirs: + + + } +#endif + + uio_PDirHandles_delete(pDirHandles, numPDirHandles); + return 0; +} + +static int +uio_statOneDir(uio_PDirHandle *pDirHandle, struct stat *statBuf) { + if (pDirHandle->pRoot->handler->stat == NULL) { + errno = ENOSYS; + return -1; + } + return (pDirHandle->pRoot->handler->stat)(pDirHandle, ".", statBuf); + // sets errno on error +} + +int +uio_mkdir(uio_DirHandle *dir, const char *path, mode_t mode) { + uio_PDirHandle *pReadDir, *pWriteDir; + uio_MountInfo *readMountInfo, *writeMountInfo; + char *name; + uio_PDirHandle *newDirHandle; + + if (uio_getPhysicalAccess(dir, path, O_WRONLY | O_CREAT | O_EXCL, 0, + &readMountInfo, &pReadDir, NULL, + &writeMountInfo, &pWriteDir, NULL, &name) == -1) { + // errno is set + if (errno == EISDIR) + errno = EEXIST; + return -1; + } + uio_PDirHandle_unref(pReadDir); + + if (pWriteDir->pRoot->handler->mkdir == NULL) { + uio_free(name); + uio_PDirHandle_unref(pWriteDir); + errno = ENOSYS; + return -1; + } + + newDirHandle = (pWriteDir->pRoot->handler->mkdir)(pWriteDir, name, mode); + if (newDirHandle == NULL) { + int savedErrno = errno; + uio_free(name); + uio_PDirHandle_unref(pWriteDir); + errno = savedErrno; + return -1; + } + + uio_PDirHandle_unref(pWriteDir); + uio_PDirHandle_unref(newDirHandle); + uio_free(name); + return 0; +} + +uio_Handle * +uio_open(uio_DirHandle *dir, const char *path, int flags, mode_t mode) { + uio_PDirHandle *readPDirHandle, *writePDirHandle, *pDirHandle; + uio_MountInfo *readMountInfo, *writeMountInfo; + char *name; + uio_Handle *handle; + + if (uio_getPhysicalAccess(dir, path, flags, 0, + &readMountInfo, &readPDirHandle, NULL, + &writeMountInfo, &writePDirHandle, NULL, &name) == -1) { + // errno is set + return NULL; + } + + if ((flags & O_ACCMODE) == O_RDONLY) { + // WritePDirHandle is not filled in. + pDirHandle = readPDirHandle; + } else if (readPDirHandle == writePDirHandle) { + // In general, the dirs can be the same even when the handles are + // not the same. But here it works, because uio_getPhysicalAccess + // guarantees it. + uio_PDirHandle_unref(writePDirHandle); + pDirHandle = readPDirHandle; + } else { + // need to write + uio_PDirEntryHandle *entry; + + entry = uio_getPDirEntryHandle(readPDirHandle, name); + if (entry != NULL) { + // file already exists + uio_PDirEntryHandle_unref(entry); + if ((flags & O_CREAT) == O_CREAT && + (flags & O_EXCL) == O_EXCL) { + uio_free(name); + uio_PDirHandle_unref(readPDirHandle); + uio_PDirHandle_unref(writePDirHandle); + errno = EEXIST; + return NULL; + } + if ((flags & O_TRUNC) == O_TRUNC) { + // No use copying the file to the writable dir. + // As it doesn't exists there, O_TRUNC needs to be turned off + // though. + flags &= ~O_TRUNC; + } else { + // file needs to be copied + if (uio_copyFilePhysical(readPDirHandle, name, writePDirHandle, + name) == -1) { + int savedErrno = errno; + uio_free(name); + uio_PDirHandle_unref(readPDirHandle); + uio_PDirHandle_unref(writePDirHandle); + errno = savedErrno; + return NULL; + } + } + } else { + // file does not exist + if (((flags & O_ACCMODE) == O_RDONLY) || + (flags & O_CREAT) != O_CREAT) { + uio_free(name); + uio_PDirHandle_unref(readPDirHandle); + uio_PDirHandle_unref(writePDirHandle); + errno = ENOENT; + return NULL; + } + } + uio_PDirHandle_unref(readPDirHandle); + pDirHandle = writePDirHandle; + } + + handle = (pDirHandle->pRoot->handler->open)(pDirHandle, name, flags, mode); + // Also adds a new entry to the physical dir if appropriate. + if (handle == NULL) { + int savedErrno = errno; + uio_free(name); + uio_PDirHandle_unref(pDirHandle); + errno = savedErrno; + return NULL; + } + + uio_free(name); + uio_PDirHandle_unref(pDirHandle); + return handle; +} + +uio_DirHandle * +uio_openDir(uio_Repository *repository, const char *path, int flags) { + uio_DirHandle *dirHandle; + const char * const rootStr = ""; + + dirHandle = uio_DirHandle_new(repository, + unconst(rootStr), unconst(rootStr)); + // dirHandle->path will be replaced before uio_openDir() + // exits() + if (uio_verifyPath(dirHandle, path, &dirHandle->path) == -1) { + int savedErrno = errno; + uio_DirHandle_free(dirHandle); + errno = savedErrno; + return NULL; + } + // dirHandle->path is no longer equal to 'path' at this point. + // TODO: increase ref in repository? + dirHandle->rootEnd = dirHandle->path; + if (flags & uio_OD_ROOT) + dirHandle->rootEnd += strlen(dirHandle->path); + return dirHandle; +} + +uio_DirHandle * +uio_openDirRelative(uio_DirHandle *base, const char *path, int flags) { + uio_DirHandle *dirHandle; + char *newPath; + + if (uio_verifyPath(base, path, &newPath) == -1) { + // errno is set + return NULL; + } + if (flags & uio_OD_ROOT) { + dirHandle = uio_DirHandle_new(base->repository, + newPath, newPath + strlen(newPath)); + // TODO: increase ref in base->repository? + } else { + // use the root of the base dir + dirHandle = uio_DirHandle_new(base->repository, + newPath, newPath + (base->rootEnd - base->path)); + } + return dirHandle; +} + +int +uio_closeDir(uio_DirHandle *dirHandle) { + uio_DirHandle_unref(dirHandle); + return 0; +} + +ssize_t +uio_read(uio_Handle *handle, void *buf, size_t count) { + return (handle->root->handler->read)(handle, buf, count); +} + +int +uio_rmdir(uio_DirHandle *dirHandle, const char *path) { + int numPDirHandles; + uio_PDirHandle *pDirHandle, **pDirHandles; + const char *pathEnd, *name; + uio_PDirEntryHandle *entry; + uio_MountTreeItem **items; + int i; + int numDeleted; + + pathEnd = strrchr(path, '/'); + if (pathEnd == NULL) { + pathEnd = path; + name = path; + } else + name = pathEnd + 1; + + if (uio_getPathPhysicalDirs(dirHandle, path, pathEnd - path, + &pDirHandles, &numPDirHandles, &items) == -1) { + // errno is set + return -1; + } + + entry = NULL; + // Should be set before a possible goto. + + if (name[0] == '\0') { + // path was of the form "foo/bar/" or "/foo/bar/" + // These are intentionally not accepted. + // I see this as a path and not as a directory identifier. + errno = ENOENT; + goto err; + } + + numDeleted = 0; + for (i = 0; i < numPDirHandles; i++) { + pDirHandle = pDirHandles[i]; + entry = uio_getPDirEntryHandle(pDirHandle, name); + + if (entry == NULL) + continue; + + if (!uio_PDirEntryHandle_isDir(entry)) { + errno = ENOTDIR; + goto err; + } + + if (uio_mountInfoIsReadOnly(items[i]->mountInfo)) { + errno = EROFS; + goto err; + } + + if (pDirHandle->pRoot->handler->rmdir == NULL) { + errno = ENOSYS; + goto err; + } + + if ((pDirHandle->pRoot->handler->rmdir)(pDirHandle, name) == -1) { + // errno is set + goto err; + } + numDeleted++; + uio_PDirEntryHandle_unref(entry); + } + entry = NULL; + + if (numDeleted == 0) { + errno = ENOENT; + goto err; + } + + uio_PDirHandles_delete(pDirHandles, numPDirHandles); + uio_free(items); + return 0; + +err: + { + int savedErrno = errno; + uio_PDirHandles_delete(pDirHandles, numPDirHandles); + uio_free(items); + if (entry != NULL) + uio_PDirEntryHandle_unref(entry); + errno = savedErrno; + return -1; + } +} + +static void +uio_PDirHandles_delete(uio_PDirHandle *pDirHandles[], int numPDirHandles) { + while (numPDirHandles--) + uio_PDirHandle_unref(pDirHandles[numPDirHandles]); + uio_free(pDirHandles); +} + +int +uio_lseek(uio_Handle *handle, off_t offset, int whence) { + if (handle->root->handler->seek == NULL) { + errno = ENOSYS; + return -1; + } + return (handle->root->handler->seek)(handle, offset, whence); +} + +ssize_t +uio_write(uio_Handle *handle, const void *buf, size_t count) { + if (handle->root->handler->write == NULL) { + errno = ENOSYS; + return -1; + } + return (handle->root->handler->write)(handle, buf, count); +} + +int +uio_unlink(uio_DirHandle *dirHandle, const char *path) { + int numPDirHandles; + uio_PDirHandle *pDirHandle, **pDirHandles; + const char *pathEnd, *name; + uio_PDirEntryHandle *entry; + uio_MountTreeItem **items; + int i; + int numDeleted; + + pathEnd = strrchr(path, '/'); + if (pathEnd == NULL) { + pathEnd = path; + name = path; + } else + name = pathEnd + 1; + + if (uio_getPathPhysicalDirs(dirHandle, path, pathEnd - path, + &pDirHandles, &numPDirHandles, &items) == -1) { + // errno is set + return -1; + } + + entry = NULL; + // Should be set before a possible goto. + + if (name[0] == '\0') { + // path was of the form "foo/bar/" or "/foo/bar/" + errno = ENOENT; + goto err; + } + + numDeleted = 0; + for (i = 0; i < numPDirHandles; i++) { + pDirHandle = pDirHandles[i]; + entry = uio_getPDirEntryHandle(pDirHandle, name); + + if (entry == NULL) + continue; + + if (uio_PDirEntryHandle_isDir(entry)) { + errno = EISDIR; + goto err; + } + + if (uio_mountInfoIsReadOnly(items[i]->mountInfo)) { + errno = EROFS; + goto err; + } + + if (pDirHandle->pRoot->handler->unlink == NULL) { + errno = ENOSYS; + goto err; + } + + if ((pDirHandle->pRoot->handler->unlink)(pDirHandle, name) == -1) { + // errno is set + goto err; + } + numDeleted++; + uio_PDirEntryHandle_unref(entry); + } + entry = NULL; + + if (numDeleted == 0) { + errno = ENOENT; + goto err; + } + + uio_PDirHandles_delete(pDirHandles, numPDirHandles); + uio_free(items); + return 0; + +err: + { + int savedErrno = errno; + uio_PDirHandles_delete(pDirHandles, numPDirHandles); + uio_free(items); + if (entry != NULL) + uio_PDirEntryHandle_unref(entry); + errno = savedErrno; + return -1; + } +} + +// inPath and *outPath may point to the same location +int +uio_getFileLocation(uio_DirHandle *dir, const char *inPath, + int flags, uio_MountHandle **mountHandle, char **outPath) { + uio_PDirHandle *readPDirHandle, *writePDirHandle; + uio_MountInfo *readMountInfo, *writeMountInfo, *mountInfo; + char *name; + char *readPRootPath, *writePRootPath, *pRootPath; + + if (uio_getPhysicalAccess(dir, inPath, flags, 0, + &readMountInfo, &readPDirHandle, &readPRootPath, + &writeMountInfo, &writePDirHandle, &writePRootPath, + &name) == -1) { + // errno is set + return -1; + } + + // TODO: This code is partly the same as the code in uio_open(). + // probably some code could be put in a seperate function. + if ((flags & O_ACCMODE) == O_RDONLY) { + // WritePDirHandle is not filled in. + uio_PDirHandle_unref(readPDirHandle); + pRootPath = readPRootPath; + mountInfo = readMountInfo; + } else if (readPDirHandle == writePDirHandle) { + // In general, the dirs can be the same even when the handles are + // not the same. But here it works, because uio_getPhysicalAccess + // guarantees it. + uio_PDirHandle_unref(readPDirHandle); + uio_PDirHandle_unref(writePDirHandle); + pRootPath = readPRootPath; + mountInfo = readMountInfo; + uio_free(writePRootPath); + } else { + // need to write + uio_PDirEntryHandle *entry; + + entry = uio_getPDirEntryHandle(readPDirHandle, name); + if (entry != NULL) { + // file already exists + uio_PDirEntryHandle_unref(entry); + if ((flags & O_CREAT) == O_CREAT && + (flags & O_EXCL) == O_EXCL) { + uio_free(name); + uio_PDirHandle_unref(readPDirHandle); + uio_free(readPRootPath); + uio_PDirHandle_unref(writePDirHandle); + uio_free(writePRootPath); + errno = EEXIST; + return -1; + } + if ((flags & O_TRUNC) == O_TRUNC) { + // No use copying the file to the writable dir. + // As it doesn't exists there, O_TRUNC needs to be turned off + // though. + flags &= ~O_TRUNC; + } else { + // file needs to be copied + if (uio_copyFilePhysical(readPDirHandle, name, writePDirHandle, + name) == -1) { + int savedErrno = errno; + uio_free(name); + uio_PDirHandle_unref(readPDirHandle); + uio_free(readPRootPath); + uio_PDirHandle_unref(writePDirHandle); + uio_free(writePRootPath); + errno = savedErrno; + return -1; + } + } + } else { + // file does not exist + if (((flags & O_ACCMODE) == O_RDONLY) || + (flags & O_CREAT) != O_CREAT) { + uio_free(name); + uio_PDirHandle_unref(readPDirHandle); + uio_free(readPRootPath); + uio_PDirHandle_unref(writePDirHandle); + uio_free(writePRootPath); + errno = ENOENT; + return -1; + } + } + uio_PDirHandle_unref(readPDirHandle); + uio_PDirHandle_unref(writePDirHandle); + pRootPath = writePRootPath; + mountInfo = writeMountInfo; + uio_free(readPRootPath); + } + + uio_free(name); + + *mountHandle = mountInfo->mountHandle; + *outPath = pRootPath; + return 0; +} + + +// *** begin dirList stuff *** // + +#define uio_DIR_BUFFER_SIZE 2048 + // Large values will give significantly better speed for large + // directories. What is stored in the buffer is file names + // plus one pointer per file name, so with an average size of 16 + // characters per file (including \0), a buffer of size 2048 will + // store approximately 100 files. + // It should be at least big enough to store one entry (NAME_MAX is + // 255 on POSIX systems). + // TODO: add a compile-time check for this + +typedef struct uio_DirBufferLink { + char *buffer; + int numEntries; + struct uio_DirBufferLink *next; +} uio_DirBufferLink; + +static int strPtrCmp(const char * const *ptr1, const char * const *ptr2); +static void uio_DirBufferLink_free(uio_DirBufferLink *sdbl); +static void uio_DirBufferChain_free(uio_DirBufferLink *dirBufferLink); +static uio_DirList *uio_getDirListMulti(uio_PDirHandle **pDirHandles, + int numPDirHandles, const char *pattern, match_MatchType matchType); +static uio_DirList *uio_makeDirList(const char **newNames, + const char * const *names, int numNames); +static uio_DirList *uio_DirList_new(const char **names, int numNames, + char *buffer); +static void uio_collectDirEntries(uio_PDirHandle *pDirHandle, + uio_DirBufferLink **linkPtr, int *numEntries); +static inline uio_DirList *uio_DirList_alloc(void); +static void uio_filterNames(const char * const *names, int numNames, + const char **newNames, int *numNewNames, + match_MatchContext *matchContext); + +static uio_EntriesContext *uio_openEntriesPhysical(uio_PDirHandle *dirHandle); +static int uio_readEntriesPhysical(uio_EntriesContext *iterator, char *buf, + size_t len); +static void uio_closeEntriesPhysical(uio_EntriesContext *iterator); +static uio_EntriesContext *uio_EntriesContext_new(uio_PRoot *pRoot, + uio_NativeEntriesContext *native); +static inline uio_EntriesContext *uio_EntriesContext_alloc(void); +static inline void uio_EntriesContext_delete(uio_EntriesContext *entriesContext); +static inline void uio_EntriesContext_free(uio_EntriesContext + *entriesContext); + +// The caller may modify the elements of the .names field of the result, but +// .names itself, and the rest of the elements of dirList should be left +// alone, so that they will be freed by uio_DirList_free(). +uio_DirList * +uio_getDirList(uio_DirHandle *dirHandle, const char *path, const char *pattern, + match_MatchType matchType) { + int numPDirHandles; + uio_PDirHandle **pDirHandles; + uio_DirList *result; + + if (uio_getPathPhysicalDirs(dirHandle, path, strlen(path), + &pDirHandles, &numPDirHandles, NULL) == -1) { + // errno is set + return NULL; + } + + if (numPDirHandles == 0) { + assert(pDirHandles == NULL); + // nothing to free + return uio_DirList_new(NULL, 0, NULL); + } + + result = uio_getDirListMulti(pDirHandles, numPDirHandles, pattern, + matchType); + + { + int savedErrno; + savedErrno = errno; + uio_PDirHandles_delete(pDirHandles, numPDirHandles); + errno = savedErrno; + } + return result; +} + +// Names and newNames may point to the same location. +// numNewNames may point to &numNames. +static void +uio_filterDoubleNames(const char * const *names, int numNames, + const char **newNames, int *numNewNames) { + const char * const *endNames; + const char *prevName; + int newNum; + + if (numNames == 0) { + *numNewNames = 0; + return; + } + + endNames = names + numNames; + prevName = *names; + *newNames = *names; + newNames++; + names++; + newNum = 1; + while (names < endNames) { + if (strcmp(prevName, *names) != 0) { + *newNames = *names; + newNum++; + prevName = *names; + newNames++; + } + names++; + } + *numNewNames = newNum; +} + +static uio_DirList * +uio_getDirListMulti(uio_PDirHandle **pDirHandles, + int numPDirHandles, const char *pattern, match_MatchType matchType) { + int pDirI; // physical dir iterator + uio_DirBufferLink **links; // array of bufferLinks for each physical dir + uio_DirBufferLink *linkPtr; + int *numNames; // number of entries in each physical dir + int totalNumNames; + const char **bigNameBuffer; // buffer where all names will end up together + const char **destPtr; + uio_DirList *result; + match_Result matchResult; + match_MatchContext *matchContext; + + matchResult = match_prepareContext(pattern, &matchContext, matchType); + if (matchResult != match_OK) { +#ifdef DEBUG + fprintf(stderr, "Error compiling match function: %s.\n", + match_errorString(matchContext, matchResult)); +#endif + match_freeContext(matchContext); + errno = EIO; + // I actually want to signal an internal error. + // EIO comes closes + return NULL; + } + + // first get the directory listings for all seperate relevant dirs. + totalNumNames = 0; + links = uio_malloc(numPDirHandles * sizeof (uio_DirBufferLink *)); + numNames = uio_malloc(numPDirHandles * sizeof (int)); + for (pDirI = 0; pDirI < numPDirHandles; pDirI++) { + uio_collectDirEntries(pDirHandles[pDirI], &links[pDirI], + &numNames[pDirI]); + totalNumNames += numNames[pDirI]; + } + + bigNameBuffer = uio_malloc(totalNumNames * sizeof (uio_DirBufferLink *)); + + // Fill the bigNameBuffer with all the names from all the DirBufferLinks + // of all the physical dirs. + destPtr = bigNameBuffer; + totalNumNames = 0; + for (pDirI = 0; pDirI < numPDirHandles; pDirI++) { + for (linkPtr = links[pDirI]; linkPtr != NULL; + linkPtr = linkPtr->next) { + int numNewNames; + uio_filterNames((const char * const *) linkPtr->buffer, + linkPtr->numEntries, destPtr, + &numNewNames, matchContext); + totalNumNames += numNewNames; + destPtr += numNewNames; + } + } + + match_freeContext(matchContext); + + // Sort the bigNameBuffer + // Necessary for removing doubles. + // Not really necessary if the big list was the result of only one + // physical dir, but let's output a sorted list anyhow. + qsort((void *) bigNameBuffer, totalNumNames, sizeof (char *), + (int (*)(const void *, const void *)) strPtrCmp); + + // remove doubles + // (unnecessary if the big list was the result of only one physical dir) + if (numPDirHandles > 1) { + uio_filterDoubleNames(bigNameBuffer, totalNumNames, + bigNameBuffer, &totalNumNames); + } + + // resize the bigNameBuffer + bigNameBuffer = uio_realloc((void *) bigNameBuffer, + totalNumNames * sizeof (char *)); + + // put the lot in a DirList, copying the strings themselves + result = uio_makeDirList(bigNameBuffer, bigNameBuffer, + totalNumNames); + + // free the old junk + for (pDirI = 0; pDirI < numPDirHandles; pDirI++) + uio_DirBufferChain_free(links[pDirI]); + uio_free(links); + uio_free(numNames); + + return result; +} + +// 'buffer' and 'names' may be the same dir +// 'names' contains an array of 'numNames' pointers. +// 'newNames', if non-NULL, will be used as the array of new pointers +// (to a copy of the strings) in the DirList. +static uio_DirList * +uio_makeDirList(const char **newNames, const char * const *names, + int numNames) { + int i; + size_t len, totLen; + char *bufPtr; + uio_DirList *result; + + if (newNames == NULL) + newNames = uio_malloc(numNames * sizeof (char *)); + + totLen = 0; + for (i = 0; i < numNames; i++) + totLen += strlen(names[i]); + totLen += numNames; + // for the \0's + + result = uio_DirList_new(newNames, numNames, uio_malloc(totLen)); + + bufPtr = result->buffer; + for (i = 0; i < numNames; i++) { + len = strlen(names[i]) + 1; + memcpy(bufPtr, names[i], len); + newNames[i] = bufPtr; + bufPtr += len; + } + return result; +} + +static void +uio_collectDirEntries(uio_PDirHandle *pDirHandle, uio_DirBufferLink **linkPtr, + int *numEntries) { + uio_EntriesContext *entriesContext; + uio_DirBufferLink **linkEndPtr; // where to attach the next link + int numRead; + int totalEntries; + char *buffer; + + entriesContext = uio_openEntriesPhysical(pDirHandle); + if (entriesContext == NULL) { +#ifdef DEBUG + fprintf(stderr, "Error: uio_openEntriesPhysical() failed: %s\n", + strerror(errno)); +#endif + *linkPtr = NULL; + *numEntries = 0; + return; + } + linkEndPtr = linkPtr; + totalEntries = 0; + while (1) { + *linkEndPtr = uio_malloc(sizeof (uio_DirBufferLink)); + buffer = uio_malloc(uio_DIR_BUFFER_SIZE); + (*linkEndPtr)->buffer = buffer; + numRead = uio_readEntriesPhysical(entriesContext, buffer, + uio_DIR_BUFFER_SIZE); + if (numRead == 0) { + fprintf(stderr, "Warning: uio_DIR_BUFFER_SIZE is too small to " + "hold a certain large entry on its own!\n"); + uio_DirBufferLink_free(*linkEndPtr); + break; + } + totalEntries += numRead; + (*linkEndPtr)->numEntries = numRead; + if (((char **) buffer)[numRead - 1] == NULL) { + // The entry being NULL means this is the last buffer + // Decrement the amount of queries to get the real number. + (*linkEndPtr)->numEntries--; + totalEntries--; + linkEndPtr = &(*linkEndPtr)->next; + break; + } + linkEndPtr = &(*linkEndPtr)->next; + } + *linkEndPtr = NULL; + uio_closeEntriesPhysical(entriesContext); + *numEntries = totalEntries; +} + +static void +uio_filterNames(const char * const *names, int numNames, + const char **newNames, int *numNewNames, + match_MatchContext *matchContext) { + int newNum; + const char * const *namesEnd; + match_Result matchResult; + + newNum = 0; + namesEnd = names + numNames; + while (names < namesEnd) { + matchResult = match_matchPattern(matchContext, *names); + if (matchResult == match_MATCH) { + *newNames = *names; + newNames++; + newNum++; + } else if (matchResult != match_NOMATCH) { + fprintf(stderr, "Error trying to match pattern: %s.\n", + match_errorString(matchContext, matchResult)); + } + names++; + } + *numNewNames = newNum; +} + +static int +strPtrCmp(const char * const *ptr1, const char * const *ptr2) { + return strcmp(*ptr1, *ptr2); +} + +static uio_EntriesContext * +uio_openEntriesPhysical(uio_PDirHandle *dirHandle) { + uio_NativeEntriesContext *native; + uio_PRoot *pRoot; + + pRoot = dirHandle->pRoot; + + assert(pRoot->handler->openEntries != NULL); + native = pRoot->handler->openEntries(dirHandle); + if (native == NULL) + return NULL; + uio_PRoot_refHandle(pRoot); + return uio_EntriesContext_new(pRoot, native); +} + +static int +uio_readEntriesPhysical(uio_EntriesContext *iterator, char *buf, size_t len) { + assert(iterator->pRoot->handler->readEntries != NULL); + return iterator->pRoot->handler->readEntries(&iterator->native, buf, len); +} + +static void +uio_closeEntriesPhysical(uio_EntriesContext *iterator) { + assert(iterator->pRoot->handler->closeEntries != NULL); + iterator->pRoot->handler->closeEntries(iterator->native); + uio_PRoot_unrefHandle(iterator->pRoot); + uio_EntriesContext_delete(iterator); +} + +static uio_EntriesContext * +uio_EntriesContext_new(uio_PRoot *pRoot, uio_NativeEntriesContext *native) { + uio_EntriesContext *result; + result = uio_EntriesContext_alloc(); + result->pRoot = pRoot; + result->native = native; + return result; +} + +static inline uio_EntriesContext * +uio_EntriesContext_alloc(void) { + return uio_malloc(sizeof (uio_EntriesContext)); +} + +static inline void +uio_EntriesContext_delete(uio_EntriesContext *entriesContext) { + uio_EntriesContext_free(entriesContext); +} + +static inline void +uio_EntriesContext_free(uio_EntriesContext *entriesContext) { + uio_free(entriesContext); +} + +static void +uio_DirBufferLink_free(uio_DirBufferLink *dirBufferLink) { + uio_free(dirBufferLink->buffer); + uio_free(dirBufferLink); +} + +static void +uio_DirBufferChain_free(uio_DirBufferLink *dirBufferLink) { + uio_DirBufferLink *next; + + while (dirBufferLink != NULL) { + next = dirBufferLink->next; + uio_DirBufferLink_free(dirBufferLink); + dirBufferLink = next; + } +} + +static uio_DirList * +uio_DirList_new(const char **names, int numNames, char *buffer) { + uio_DirList *result; + + result = uio_DirList_alloc(); + result->names = names; + result->numNames = numNames; + result->buffer = buffer; + return result; +} + +static uio_DirList * +uio_DirList_alloc(void) { + return uio_malloc(sizeof (uio_DirList)); +} + +void +uio_DirList_free(uio_DirList *dirList) { + if (dirList->buffer) + uio_free(dirList->buffer); + if (dirList->names) + uio_free((void *) dirList->names); + uio_free(dirList); +} + +// *** end DirList stuff *** // + + +// *** PDirEntryHandle stuff *** // + +uio_PDirEntryHandle * +uio_getPDirEntryHandle(const uio_PDirHandle *dirHandle, + const char *name) { + assert(dirHandle->pRoot->handler != NULL); + return dirHandle->pRoot->handler->getPDirEntryHandle(dirHandle, name); +} + +void +uio_PDirHandle_deletePDirHandleExtra(uio_PDirHandle *pDirHandle) { + if (pDirHandle->extra == NULL) + return; + assert(pDirHandle->pRoot->handler->deletePDirHandleExtra != NULL); + pDirHandle->pRoot->handler->deletePDirHandleExtra(pDirHandle->extra); +} + +void +uio_PFileHandle_deletePFileHandleExtra(uio_PFileHandle *pFileHandle) { + if (pFileHandle->extra == NULL) + return; + assert(pFileHandle->pRoot->handler->deletePFileHandleExtra != NULL); + pFileHandle->pRoot->handler->deletePFileHandleExtra(pFileHandle->extra); +} + +uio_PDirHandle * +uio_PDirHandle_new(uio_PRoot *pRoot, uio_PDirHandleExtra extra) { + uio_PDirHandle *result; + + result = uio_PDirHandle_alloc(); + result->flags = uio_PDirEntryHandle_TYPE_DIR; + result->ref = 1; + result->pRoot = pRoot; + result->extra = extra; + return result; +} + +void +uio_PDirEntryHandle_delete(uio_PDirEntryHandle *pDirEntryHandle) { + if (uio_PDirEntryHandle_isDir(pDirEntryHandle)) { + uio_PDirHandle_delete((uio_PDirHandle *) pDirEntryHandle); + } else { + uio_PFileHandle_delete((uio_PFileHandle *) pDirEntryHandle); + } +} + +void +uio_PDirHandle_delete(uio_PDirHandle *pDirHandle) { + uio_PDirHandle_deletePDirHandleExtra(pDirHandle); + uio_PDirHandle_free(pDirHandle); +} + +static inline uio_PDirHandle * +uio_PDirHandle_alloc(void) { + uio_PDirHandle *result = uio_malloc(sizeof (uio_PDirHandle)); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugAlloc(uio_PDirHandle, (void *) result); +#endif + return result; +} + +static inline void +uio_PDirHandle_free(uio_PDirHandle *pDirHandle) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugFree(uio_PDirHandle, (void *) pDirHandle); +#endif + uio_free(pDirHandle); +} + +uio_PFileHandle * +uio_PFileHandle_new(uio_PRoot *pRoot, uio_PFileHandleExtra extra) { + uio_PFileHandle *result; + + result = uio_PFileHandle_alloc(); + result->flags = uio_PDirEntryHandle_TYPE_REG; + result->ref = 1; + result->pRoot = pRoot; + result->extra = extra; + return result; +} + +void +uio_PFileHandle_delete(uio_PFileHandle *pFileHandle) { + uio_PFileHandle_deletePFileHandleExtra(pFileHandle); + uio_PFileHandle_free(pFileHandle); +} + +static inline uio_PFileHandle * +uio_PFileHandle_alloc(void) { + uio_PFileHandle *result = uio_malloc(sizeof (uio_PFileHandle)); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugAlloc(uio_PFileHandle, (void *) result); +#endif + return result; +} + +static inline void +uio_PFileHandle_free(uio_PFileHandle *pFileHandle) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugFree(uio_PFileHandle, (void *) pFileHandle); +#endif + uio_free(pFileHandle); +} + +// *** PDirEntryHandle stuff *** // + + +// ref count set to 1 +uio_Handle * +uio_Handle_new(uio_PRoot *root, uio_NativeHandle native, int openFlags) { + uio_Handle *handle; + + handle = uio_Handle_alloc(); + handle->ref = 1; + uio_PRoot_refHandle(root); + handle->root = root; + handle->native = native; + handle->openFlags = openFlags; + return handle; +} + +void +uio_Handle_delete(uio_Handle *handle) { + (handle->root->handler->close)(handle); + uio_PRoot_unrefHandle(handle->root); + uio_Handle_free(handle); +} + +static inline uio_Handle * +uio_Handle_alloc(void) { + uio_Handle *result = uio_malloc(sizeof (uio_Handle)); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugAlloc(uio_Handle, (void *) result); +#endif + return result; +} + +static inline void +uio_Handle_free(uio_Handle *handle) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugFree(uio_Handle, (void *) handle); +#endif + uio_free(handle); +} + +// ref count set to 1 +static uio_DirHandle * +uio_DirHandle_new(uio_Repository *repository, char *path, char *rootEnd) { + uio_DirHandle *result; + + result = uio_DirHandle_alloc(); + result->ref = 1; + result->repository = repository; + result->path = path; + result->rootEnd = rootEnd; + return result; +} + +void +uio_DirHandle_delete(uio_DirHandle *dirHandle) { + uio_free(dirHandle->path); + uio_DirHandle_free(dirHandle); +} + +static inline uio_DirHandle * +uio_DirHandle_alloc(void) { + uio_DirHandle *result = uio_malloc(sizeof (uio_DirHandle)); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugAlloc(uio_DirHandle, (void *) result); +#endif + return result; +} + +static inline void +uio_DirHandle_free(uio_DirHandle *dirHandle) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugFree(uio_DirHandle, (void *) dirHandle); +#endif + uio_free(dirHandle); +} + +void +uio_DirHandle_print(const uio_DirHandle *dirHandle, FILE *out) { + fprintf(out, "["); + fwrite(dirHandle->path, dirHandle->path - dirHandle->rootEnd, 1, out); + fprintf(out, "]%s", dirHandle->rootEnd); +} + +static uio_MountHandle * +uio_MountHandle_new(uio_Repository *repository, uio_MountInfo *mountInfo) { + uio_MountHandle *result; + + result = uio_MountHandle_alloc(); + result->repository = repository; + result->mountInfo = mountInfo; + return result; +} + +static inline void +uio_MountHandle_delete(uio_MountHandle *mountHandle) { + uio_MountHandle_free(mountHandle); +} + +static inline uio_MountHandle * +uio_MountHandle_alloc(void) { + uio_MountHandle *result = uio_malloc(sizeof (uio_MountHandle)); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugAlloc(uio_MountHandle, (void *) result); +#endif + return result; +} + +static inline void +uio_MountHandle_free(uio_MountHandle *mountHandle) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugFree(uio_MountHandle, (void *) mountHandle); +#endif + uio_free(mountHandle); +} + + + diff --git a/src/libs/uio/io.h b/src/libs/uio/io.h new file mode 100644 index 0000000..813a2c3 --- /dev/null +++ b/src/libs/uio/io.h @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef LIBS_UIO_IO_H_ +#define LIBS_UIO_IO_H_ + +#include <assert.h> +#include <sys/stat.h> + +typedef struct uio_Handle uio_Handle; +typedef struct uio_DirHandle uio_DirHandle; +typedef struct uio_DirList uio_DirList; +typedef struct uio_MountHandle uio_MountHandle; + +typedef enum { + uio_MOUNT_BOTTOM = (0 << 2), + uio_MOUNT_TOP = (1 << 2), + uio_MOUNT_BELOW = (2 << 2), + uio_MOUNT_ABOVE = (3 << 2) +} uio_MountLocation; + +#include "match.h" +#include "fstypes.h" +#include "mount.h" +#include "mounttree.h" +#include "uiostream.h" +#include "getint.h" +#include "debug.h" + +struct uio_AutoMount { + const char *pattern; + match_MatchType matchType; + uio_FileSystemID fileSystemID; + int mountFlags; +// uio_AutoMount **autoMount; + // automount rules to apply to file systems automounted + // because of this automount rule. +}; + +#ifndef uio_INTERNAL +struct uio_DirList { + const char **names; + int numNames; + // The rest of the fields are not visible from the outside +}; +#endif + + +// Initialise the resource system +void uio_init(void); + +// Uninitialise the resource system +void uio_unInit(void); + +// Open a repository. +uio_Repository *uio_openRepository(int flags); + +// Close a repository opened by uio_openRepository(). +void uio_closeRepository(uio_Repository *repository); + +// Mount a directory into a repository +uio_MountHandle *uio_mountDir(uio_Repository *destRep, const char *mountPoint, + uio_FileSystemID fsType, + uio_DirHandle *sourceDir, const char *sourcePath, + const char *inPath, uio_AutoMount **autoMount, int flags, + uio_MountHandle *relative); + +// Mount a repository directory into same repository at a different +// location. +// From fossil. +uio_MountHandle *uio_transplantDir(const char *mountPoint, + uio_DirHandle *sourceDir, int flags, uio_MountHandle *relative); + +// Unmount a previously mounted dir. +int uio_unmountDir(uio_MountHandle *mountHandle); + +// Unmount all previously mounted dirs. +int uio_unmountAllDirs(uio_Repository *repository); + +// Get the filesystem identifier for a mounted directory. +uio_FileSystemID uio_getMountFileSystemType(uio_MountHandle *mountHandle); + +// Open a file +uio_Handle *uio_open(uio_DirHandle *dir, const char *file, int flags, + mode_t mode); + +// Close a file descriptor for a file opened by uio_open +int uio_close(uio_Handle *handle); + +// Rename or move a file or directory. +int uio_rename(uio_DirHandle *oldDir, const char *oldPath, + uio_DirHandle *newDir, const char *newPath); + +// Test permissions on a file or directory. +int uio_access(uio_DirHandle *dir, const char *path, int mode); + +// Fstat a file descriptor +int uio_fstat(uio_Handle *handle, struct stat *statBuf); + +int uio_stat(uio_DirHandle *dir, const char *path, struct stat *statBuf); + +int uio_mkdir(uio_DirHandle *dir, const char *name, mode_t mode); + +ssize_t uio_read(uio_Handle *handle, void *buf, size_t count); + +int uio_rmdir(uio_DirHandle *dirHandle, const char *path); + +int uio_lseek(uio_Handle *handle, off_t offset, int whence); + +ssize_t uio_write(uio_Handle *handle, const void *buf, size_t count); + +int uio_unlink(uio_DirHandle *dirHandle, const char *path); + +int uio_getFileLocation(uio_DirHandle *dir, const char *inPath, + int flags, uio_MountHandle **mountHandle, char **outPath); + +// Get a directory handle. +uio_DirHandle *uio_openDir(uio_Repository *repository, const char *path, + int flags); +#define uio_OD_ROOT 1 + +// Get a directory handle using a path relative to another handle. +uio_DirHandle *uio_openDirRelative(uio_DirHandle *base, const char *path, + int flags); + +// Release a directory handle +int uio_closeDir(uio_DirHandle *dirHandle); + +uio_DirList *uio_getDirList(uio_DirHandle *dirHandle, const char *path, + const char *pattern, match_MatchType matchType); +void uio_DirList_free(uio_DirList *dirList); + +// For debugging purposes +void uio_DirHandle_print(const uio_DirHandle *dirHandle, FILE *out); + + +#ifdef DEBUG +# define uio_DEBUG +#endif + +#endif /* LIBS_UIO_IO_H_ */ + diff --git a/src/libs/uio/ioaux.c b/src/libs/uio/ioaux.c new file mode 100644 index 0000000..3dc0880 --- /dev/null +++ b/src/libs/uio/ioaux.c @@ -0,0 +1,930 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +// auxiliary functions for use by io.c + +#include <errno.h> +#include <fcntl.h> + +#include "ioaux.h" +#include "iointrn.h" +#include "uioport.h" +#include "paths.h" + +static int copyError(int error, + uio_FileSystemHandler *fromHandler, uio_Handle *fromHandle, + uio_FileSystemHandler *toHandler, uio_Handle *toHandle, + uio_PDirHandle *toDir, const char *toName, char *buf); + +/** + * Follow a path starting from a specified physical dir for as long as + * possible. + * + * @param[in] startPDirHandle The physical dir to start from. + * @param[in] path The path to follow, relative to + * 'startPDirHandle'. + * @param[in] pathLen The string length of 'path'. + * @param[out] endPDirHandle The physical dir where you end up after + * following 'path' for as long as possible. Unmodified if an error + * occurs. + * @param[out] pathRest '*pathRest' will point into 'path' to the + * start the part that was not matched. Unmodified if an error occurs. + * + * @retval 0 if the complete path was matched + * @retval ENOENT if some component (the next one in '*pathRest') didn't + * exist. + * @retval ENODIR if a component (the next one in '*pathRest') did exist, + * but wasn't a dir. + * + * @note It is allowed to have 'endPDirHandle' point to pDirHandle, but + * care should be taken to keep a reference to the original so its + * reference counter can be decremented. + * @note It is allowed to have 'pathRest' point to 'path'. + */ +int +uio_walkPhysicalPath(uio_PDirHandle *startPDirHandle, const char *path, + size_t pathLen, uio_PDirHandle **endPDirHandle, + const char **pathRest) { + const char *pathEnd; + const char *partStart, *partEnd; + char *tempBuf; + uio_PDirHandle *pDirHandle; + uio_PDirEntryHandle *entry; + int retVal; + + uio_PDirHandle_ref(startPDirHandle); + pDirHandle = startPDirHandle; + tempBuf = uio_malloc(strlen(path) + 1); + // XXX: Use a dynamically allocated array when moving to C99. + pathEnd = path + pathLen; + getFirstPathComponent(path, pathEnd, &partStart, &partEnd); + for (;;) { + if (partStart == pathEnd) { + retVal = 0; + break; + } + memcpy(tempBuf, partStart, partEnd - partStart); + tempBuf[partEnd - partStart] = '\0'; + + entry = uio_getPDirEntryHandle(pDirHandle, tempBuf); + if (entry == NULL) { + retVal = ENOENT; + break; + } + if (!uio_PDirEntryHandle_isDir(entry)) { + uio_PDirEntryHandle_unref(entry); + retVal = ENOTDIR; + break; + } + uio_PDirHandle_unref(pDirHandle); + pDirHandle = (uio_PDirHandle *) entry; + getNextPathComponent(pathEnd, &partStart, &partEnd); + } + + uio_free(tempBuf); + *pathRest = partStart; + *endPDirHandle = pDirHandle; + return retVal; +} + +/** + * Create a directory inside a physical directory. All non-existant + * parent directories will be created as well. + * + * @param[in] pDirHandle The physical directory to which 'path' is relative + * @param[in] path The path to the directory to create, relative to + * 'pDirHandle' + * @param[in] pathLen The string length of 'path'. + * @param[in] mode The access mode for the newly created directories. + * + * @returns the new (physical) directory, or NULL if an error occurs, in + * which case #errno will be set. + */ +uio_PDirHandle * +uio_makePath(uio_PDirHandle *pDirHandle, const char *path, size_t pathLen, + mode_t mode) { + const char *rest, *start, *end; + uio_PDirHandle *(*mkdirFun)(uio_PDirHandle *, const char *, mode_t); + char *buf; + const char *pathEnd; + uio_PDirHandle *newPDirHandle; + + mkdirFun = pDirHandle->pRoot->handler->mkdir; + if (mkdirFun == NULL) { + errno = ENOSYS; + return NULL; + } + + pathEnd = path + pathLen; + + buf = uio_malloc(pathLen + 1); + // worst case length + // XXX: Use a dynamically allocated array when moving to C99. + + uio_walkPhysicalPath(pDirHandle, path, pathLen, &pDirHandle, &rest); + // The reference to the original pDirHandle is still kept + // by the calling function; uio_PDirHandle_unref should not + // be called for it. + // Rest now points into 'path' to the part from where no physical + // dir exists. + + getFirstPathComponent(rest, pathEnd, &start, &end); + while (start < pathEnd) { + memcpy(buf, start, end - start); + buf[end - start] = '\0'; + + newPDirHandle = mkdirFun(pDirHandle, buf, mode); + if (newPDirHandle == NULL) { + int savedErrno = errno; + uio_PDirHandle_unref(pDirHandle); + errno = savedErrno; + uio_free(buf); + return NULL; + } + uio_PDirHandle_unref(pDirHandle); + pDirHandle = newPDirHandle; + getNextPathComponent(pathEnd, &start, &end); + } + uio_free(buf); + return pDirHandle; +} + + +/** + * Copy a file from one physical directory to another. + * The copy will have the same file permissions as the original. + * + * @param[in] fromDir The physical directory where the file to copy is + * located. + * @param[in] fromName The name of the file to copy. + * @param[in] toDir The physical directory where to put the copy. + * @param[in] toName The name to use for the copy. + * + * @note It is up to the caller to make any relevant permissions checks. + * + * @note This function will fail if a file with the name in 'toName' already + * exists, leaving the original file intact. If an error occurs during + * copying, an attempt is made to remove the file that was to be the + * copy. + */ +int +uio_copyFilePhysical(uio_PDirHandle *fromDir, const char *fromName, + uio_PDirHandle *toDir, const char *toName) { + uio_FileSystemHandler *fromHandler, *toHandler; + uio_Handle *fromHandle; + uio_Handle *toHandle; +#define BUFSIZE 0x10000 + struct stat statBuf; + char *buf, *bufPtr; + ssize_t numInBuf, numWritten; + + fromHandler = fromDir->pRoot->handler; + toHandler = toDir->pRoot->handler; + if (toHandler->write == NULL || fromHandler->fstat == NULL || + toHandler->unlink == NULL) { + errno = ENOSYS; + return -1; + } + + fromHandle = (fromHandler->open)(fromDir, fromName, O_RDONLY, 0); + if (fromHandle == NULL) { + // errno is set + return -1; + } + + if ((fromHandler->fstat)(fromHandle, &statBuf) == -1) + return copyError(errno, fromHandler, fromHandle, + toHandler, NULL, NULL, NULL, NULL); + + toHandle = (toHandler->open)(toDir, toName, O_WRONLY | O_CREAT | O_EXCL, + statBuf.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)); + if (toHandle == NULL) + return copyError(errno, fromHandler, fromHandle, + toHandler, NULL, NULL, NULL, NULL); + + buf = uio_malloc(BUFSIZE); + // not allocated on the stack, as this function may be called + // from a thread with little stack space. + while (1) { + numInBuf = (fromHandler->read)(fromHandle, buf, BUFSIZE); + if (numInBuf == -1) + { + if (errno == EINTR) + continue; + return copyError (errno, fromHandler, fromHandle, + toHandler, toHandle, toDir, toName, buf); + } + if (numInBuf == 0) + break; + + bufPtr = buf; + do { + numWritten = (toHandler->write)(toHandle, bufPtr, numInBuf); + if (numWritten == -1) + { + if (errno == EINTR) + continue; + return copyError (errno, fromHandler, fromHandle, + toHandler, toHandle, toDir, toName, buf); + } + numInBuf -= numWritten; + bufPtr += numWritten; + } while (numInBuf > 0); + } + + uio_free(buf); + (toHandler->close)(toHandle); + (fromHandler->close)(fromHandle); + return 0; +} + +/* + * Closes fromHandle if it's not -1. + * Closes fromHandle if it's not -1. + * Removes 'toName' from 'toDir' if it's not NULL. + * Frees 'buf' if not NULL. + * Always returns -1, setting errno to 'error'. + */ +static int +copyError(int error, + uio_FileSystemHandler *fromHandler, uio_Handle *fromHandle, + uio_FileSystemHandler *toHandler, uio_Handle *toHandle, + uio_PDirHandle *toDir, const char *toName, char *buf) { +#ifdef DEBUG + fprintf(stderr, "Error while copying: %s\n", strerror(error)); +#endif + + if (fromHandle != NULL) + (fromHandler->close)(fromHandle); + + if (toHandle != NULL) + (toHandler->close)(toHandle); + + if (toName != NULL) + (toHandler->unlink)(toDir, toName); + + if (buf != NULL) + uio_free(buf); + + errno = error; + return -1; +} + +/* + * Description: find PDirHandle and MountInfo structures for reading and + * writing for a path from a DirHandle. + * This can be used for opening a file and creating directories. + * Arguments: dirHandle - the directory to which the path is relative. + * path - the path to the component that is to be accessed. + * The last component does not have to exist. + * flags - used to specify what kind of access is requested + * Either O_RDONLY, O_RDWR, O_WRONLY. They may be + * OR'd with other values accepted by open(). These + * are ignored. + * XXX: this is no longer true. + * TODO: update this doc, and check uio_open() and + * perhaps others (uio_mkdir()) for unnecessary + * checks on O_CREAT and O_EXCL. + * extraFlags - either 0 or uio_GPA_NOWRITE + * When 0, the path will be created if it doesn't + * exist in the writing location, but does exist + * in the reading location. With uio_GPA_NOWRITE, it + * won't be created, and -1 will be returned and errno + * will be set to ENOENT. + * mountInfoReadPtr - pointer to location where the pointer + * to the MountInfo structure for the reading location + * should be stored. + * readPDirHandlePtr - pointer to the location where the pointer + * to the PDirHandle used for reading should be stored. + * readPRootPath - pointer to the location where the pointer + * to the physical path to the reading location + * is to be stored. + * The caller is responsible for freeing this. + * Ignored if NULL. + * mountInfoWritePtr - pointer to location where the pointer + * to the MountInfo structure for the writing location + * should be stored. + * writePDirHandlePtr - pointer to the location where the pointer + * to the PDirHandle used for writing should be stored. + * NULL if O_RDONLY was specified. + * If this is the same dir as the one refered + * to by readPDirHandlePtr, the handles will be the + * same too. + * writePRootPath - pointer to the location where the pointer + * to the physical path to the writing location + * is to be stored. + * The caller is responsible for freeing this. + * Ignored if NULL. + * restPtr - pointer to the location where a newly created + * string with as contents the last component of 'path' + * is to be stored. + * The caller is responsible for freeing this. + * Ignored if NULL. + * Returns: 0 - success + * -1 - failure (errno set) + * NB: This is the function that would most benefit from + * the introduction of LDirs. + * This is also the most messy function. It could + * use some more comments, or a cleanup (if possible). + */ +int +uio_getPhysicalAccess(uio_DirHandle *dirHandle, const char *path, + int flags, int extraFlags, + uio_MountInfo **mountInfoReadPtr, uio_PDirHandle **readPDirHandlePtr, + char **readPRootPathPtr, + uio_MountInfo **mountInfoWritePtr, uio_PDirHandle **writePDirHandlePtr, + char **writePRootPathPtr, + char **restPtr) { + char *fullPath; // path from dirHandle with 'path' added + const char *pRootPath; // path from the pRoot of a physical tree + const char *readPRootPath, *writePRootPath; + const char *rest, *readRest; + uio_MountTree *tree; + uio_MountTreeItem *item; + uio_MountTreeItem *readItem, *writeItem; + uio_PDirHandle *readPDirHandle, *writePDirHandle, *pDirHandle; + int retVal; + uio_bool entryExists; + // Set if the entry pointed to by path exists (including + // the last component of the path) + + // 'path' is relative to dirHandle. + // Fill 'fullPath' with the absolute path. + if (uio_resolvePath(dirHandle, path, strlen(path), &fullPath) == -1) { + // errno is set + return -1; + } + + // Walk the tree of mount points along 'fullPath'. + // 'tree' will be the part of the tree where we end up when we can go + // no further. tree->pLocs are all the mounts relevant there. + // 'rest' will point within 'fullPath' to what is left, after we can + // walk the tree of mountpoints no further. + uio_findMountTree(dirHandle->repository->mountTree, fullPath, + &tree, &rest); + + readItem = NULL; + readPDirHandle = NULL; + readPRootPath = NULL; + writeItem = NULL; + writePDirHandle = NULL; + writePRootPath = NULL; + readRest = NULL; + // Satisfy compiler. + entryExists = false; + // try all the MountInfo structures in effect for this MountTree + for (item = tree->pLocs; item != NULL; item = item->next) { + pRootPath = uio_mountTreeItemRestPath(item, tree->lastComp, fullPath); + retVal = uio_walkPhysicalPath(item->mountInfo->pDirHandle, pRootPath, + strlen(pRootPath), &pDirHandle, &rest); + // rest points inside fullPath + if (retVal == 0) { + // Even the last component appeared to be a dir. + // As the last component did exist, we don't go on. + uio_free(fullPath); + uio_PDirHandle_unref(pDirHandle); + if (readPDirHandle != NULL) + uio_PDirHandle_unref(readPDirHandle); + errno = EISDIR; + return -1; + } + // check if this MountTreeItem is suitable for writing + if (writeItem == NULL && !uio_mountInfoIsReadOnly(item->mountInfo)) + writeItem = item; + if (strchr(rest, '/') == NULL) { + // There's only one dir component that was not matched. + uio_PDirEntryHandle *entry; + + // This MountInfo will do for reading, if the file from the last + // component is present in this dir. + entry = uio_getPDirEntryHandle(pDirHandle, rest); + if (entry != NULL) { + // 'rest' exists, and it is not a dir, as otherwise + // uio_getPDirEntryHandle wouldn't have stopped where it did. + uio_PDirEntryHandle_unref(entry); + readItem = item; + if (readPDirHandle != NULL) + uio_PDirHandle_unref(readPDirHandle); + readPDirHandle = pDirHandle; + readPRootPath = pRootPath; + readRest = rest; + entryExists = true; + break; + } else { + // 'rest' doesn't exist + // We're only interested in this dir if we want to write too. + if (((flags & O_ACCMODE) != O_RDONLY) && readItem == NULL) { + // Keep the first one. + readItem = item; + assert(readPDirHandle == NULL); + readPDirHandle = pDirHandle; + readPRootPath = pRootPath; + readRest = rest; + continue; + } + } + } else { + // There is more than one dir component that was not matched. + // If the first component of the non-matched part is a file, + // stop here and don't check lower dirs. + if (retVal == ENOTDIR) { + uio_free(fullPath); + uio_PDirHandle_unref(pDirHandle); + if (readPDirHandle != NULL) + uio_PDirHandle_unref(readPDirHandle); + errno = ENOTDIR; + return -1; + } + } + uio_PDirHandle_unref(pDirHandle); + } // for + // readItem is set to the first readItem for which the path completely + // exists (including the final component). If there's no such readItem, + // it is set to the first path for which the path exists, but without + // the final component (entryExists is false in this case). If there's + // no such path either, it's set to NULL. + + if (readItem == NULL) { + uio_free(fullPath); + errno = ENOENT; + return -1; + } + if ((flags & O_ACCMODE) == O_RDONLY) { + // write access is not needed + *mountInfoReadPtr = readItem->mountInfo; + *readPDirHandlePtr = readPDirHandle; + if (readPRootPathPtr != NULL) + *readPRootPathPtr = joinPathsAbsolute( + readItem->mountInfo->dirName, readPRootPath); + // Don't touch mountInfoWritePtr and writePDirHandlePtr. + // they'd be NULL. + *restPtr = uio_strdup(readRest); + uio_free(fullPath); + return 0; + } else { + if (entryExists) { + if ((flags & O_CREAT) && (flags & O_EXCL)) { + // An entry should be created, but it already exists and + // it may not be overwritten. + uio_PDirHandle_unref(readPDirHandle); + uio_free(fullPath); + errno = EEXIST; + return -1; + } + } else { + // Though the path to the entry existed (readPDirHandle is + // set to it), the entry itself doesn't, so we can't use it + // unless we intend to create it. + if (flags & O_CREAT) { + // The entry does not exist, but we can create it. + // Handled below. + } else { + // O_CREAT was not specified, so we cannot create + // this entry. + uio_PDirHandle_unref(readPDirHandle); + uio_free(fullPath); + errno = ENOENT; + return -1; + } + + } + } + if (writeItem == readItem) { + // The read directory is usable as write directory too. + *mountInfoReadPtr = readItem->mountInfo; + *readPDirHandlePtr = readPDirHandle; + if (readPRootPathPtr != NULL) + *readPRootPathPtr = joinPathsAbsolute( + readItem->mountInfo->dirName, readPRootPath); + *mountInfoWritePtr = writeItem->mountInfo; + // writeItem == readItem + uio_PDirHandle_ref(readPDirHandle); + *writePDirHandlePtr = readPDirHandle; + // No copy&paste error, the read PDirHandle is the write + // pDirHandle too. + if (writePRootPathPtr != NULL) + *writePRootPathPtr = joinPathsAbsolute( + writeItem->mountInfo->dirName, writePRootPath); + if (restPtr != NULL) + *restPtr = uio_strdup(readRest); + uio_free(fullPath); + return 0; + } + if (writeItem == NULL) { + uio_free(fullPath); + uio_PDirHandle_unref(readPDirHandle); + errno = EPERM; + // readItem is not NULL, so ENOENT would not be correct here. + return -1; + } + + // Left is the case where the write location is different from the + // read location. + + pRootPath = uio_mountTreeItemRestPath(writeItem, tree->lastComp, + fullPath); + + rest = strrchr(pRootPath, '/'); + // rest points inside fullPath + if (rest == NULL) { + rest = pRootPath; + uio_PDirHandle_ref(writeItem->mountInfo->pDirHandle); + writePDirHandle = writeItem->mountInfo->pDirHandle; + } else { + // There exists no path for a write dir, so it will have to be created. + // writeMountInfo indicates the physical tree where it should end up. + + if (extraFlags & uio_GPA_NOWRITE) { + // The caller has specified that the path should not be created. + uio_PDirHandle_unref(readPDirHandle); + uio_free(fullPath); + errno = ENOENT; + return -1; + } + + writePDirHandle = uio_makePath(writeItem->mountInfo->pDirHandle, + pRootPath, rest - pRootPath, 0777); + if (writePDirHandle == NULL) { + int savedErrno; + if (errno == ENOSYS) { + // mkdir not supported. We want to report that we failed + // because of an error in the underlying layer. + // EIO sounds like the best choice. + errno = EIO; + } + savedErrno = errno; + uio_PDirHandle_unref(readPDirHandle); + uio_free(fullPath); + errno = savedErrno; + return -1; + } + rest++; // skip the '/' + } + + if (!entryExists) { + // The path to the read dir exists, but the entry itself doesn't. + // After we created the write dir, the same thing holds for + // the write dir. As it occurs in an earlier MountItem, we'll use + // the writeItem (and writePDirHandle) for reading too. + readItem = writeItem; + uio_PDirHandle_ref(writePDirHandle); + uio_PDirHandle_unref(readPDirHandle); + readPDirHandle = writePDirHandle; + } + + *mountInfoReadPtr = readItem->mountInfo; + *readPDirHandlePtr = readPDirHandle; + if (readPRootPathPtr != NULL) + *readPRootPathPtr = joinPathsAbsolute( + readItem->mountInfo->dirName, readPRootPath); + *mountInfoWritePtr = writeItem->mountInfo; + *writePDirHandlePtr = writePDirHandle; + if (writePRootPathPtr != NULL) + *writePRootPathPtr = joinPathsAbsolute( + writeItem->mountInfo->dirName, writePRootPath); + if (restPtr != NULL) + *restPtr = uio_strdup(rest); + uio_free(fullPath); + return 0; +} + + +/** + * Get handles to the (existing) physical dirs that are effective in a + * path 'path' relative from 'dirHandle' + * + * @param[in] pDirHandle The physical directory to which 'path' is + * relative. + * @param[in] path The path to get the physical dirs for, relative to + * 'pDirHandle' + * @param[in] pathLen The string length of 'path'. + * @param[out] resPDirHandles *resPDirHandles is set to the handles to the + * (existing) physical dirs that are effective in 'path' (relative to + * pDirHandle), or NULL if there are none. + * @param[out] resNumPDirHandles The number of PDirHandles found. + * @param[out] resItems If 'resItems' != NULL, *resItems is set to the + * MountTreeItems belonging to $pDirHandles, or NULL if none were found. + * + * @retval 0 if everything went ok. + * @retval -1 if an error occurred; #errno is set. + */ +int +uio_getPathPhysicalDirs(uio_DirHandle *dirHandle, const char *path, + size_t pathLen, uio_PDirHandle ***resPDirHandles, + int *resNumPDirHandles, uio_MountTreeItem ***resItems) { + uio_PDirHandle **pDirHandles; + char *fullPath; // path from dirHandle with 'path' added + uio_MountTree *tree; + const char *rest; + uio_MountTreeItem *item, **items; + const char *pRootPath; // path from the pRoot of a physical tree + int numPDirHandles; + int pDirI; // PDirHandle iterator + + // Determine the absolute path from 'path', which is relative to dirHandle. + if (uio_resolvePath(dirHandle, path, pathLen, &fullPath) == -1) { + // errno is set + return -1; + } + + // get the MountTree effective for the path + uio_findMountTree(dirHandle->repository->mountTree, fullPath, + &tree, &rest); + + // fill pDirHandles with all the PDirHandles for the path + numPDirHandles = uio_mountTreeCountPLocs(tree); + pDirHandles = uio_malloc(numPDirHandles * sizeof (uio_PDirHandle *)); + if (resItems != NULL) { + items = uio_malloc(numPDirHandles * sizeof (uio_MountTreeItem *)); + } else { + items = NULL; // satisfy compiler + } + pDirI = 0; + for (item = tree->pLocs; item != NULL; item = item->next) { + uio_PDirHandle *pDirHandle; + + pRootPath = uio_mountTreeItemRestPath(item, tree->lastComp, fullPath); + switch (uio_walkPhysicalPath(item->mountInfo->pDirHandle, pRootPath, + strlen(pRootPath), &pDirHandle, &rest)) { + case 0: + // complete path was matched + pDirHandles[pDirI] = pDirHandle; + if (resItems != NULL) + items[pDirI] = item; + pDirI++; + continue; + case ENOENT: + // some component couldn't be matched + uio_PDirHandle_unref(pDirHandle); + continue; + case ENOTDIR: + // next component was not a dir + // Don't look further at other mount Items. + uio_PDirHandle_unref(pDirHandle); + break; + default: + assert(false); + uio_PDirHandle_unref(pDirHandle); + continue; + } + break; + } + numPDirHandles = pDirI; + + uio_free(fullPath); + + *resPDirHandles = uio_realloc(pDirHandles, + numPDirHandles * sizeof (uio_PDirHandle *)); + if (resItems != NULL) + *resItems = uio_realloc(items, + numPDirHandles * sizeof (uio_MountTreeItem *)); + *resNumPDirHandles = numPDirHandles; + + return 0; +} + +// returns 0 if the path is valid and exists +// returns -1 if the path is not valid or does not exist. +// in this case errno will be set to: +// ENOENT if some component didn't exist +// ENOTDIR is some component exists, but is not a dir +// something else (like EPATHTOOLONG) for internal errors +// On success, 'resolvedPath' will be set to the absolute path as returned by +// uio_resolvePath. +int +uio_verifyPath(uio_DirHandle *dirHandle, const char *path, + char **resolvedPath) { + uio_MountTree *tree; + uio_MountTreeItem *item; + const char *rest; + int retVal; + + // TODO: "////", "/somedir//", and "//somedir" are accepted as valid + + // Determine the absolute path from 'path' which is relative to dirHandle. + if (uio_resolvePath(dirHandle, path, strlen(path), resolvedPath) == -1) { + // errno is set + return -1; + } + + // get the MountTree effective for the path + uio_findMountTree(dirHandle->repository->mountTree, *resolvedPath, + &tree, &rest); + + if (rest[0] == '\0') { + // Complete match. Even if there are no pLocs in effect here + // (which can only happen in case the mount Tree is empty and + // we're viewing '/'). + return 0; + } + + // Try all the MountInfo structures in effect for this MountTree. + for (item = tree->pLocs; item != NULL; item = item->next) { + const char *pRootPath; + uio_PDirHandle *pDirHandle; + + pRootPath = uio_mountTreeItemRestPath(item, tree->lastComp, + *resolvedPath); + retVal = uio_walkPhysicalPath(item->mountInfo->pDirHandle, pRootPath, + strlen(pRootPath), &pDirHandle, &rest); + uio_PDirHandle_unref(pDirHandle); + switch (retVal) { + case 0: + // Complete match. We're done. + return 0; + case ENOTDIR: + // A component is matched, but not as a dir. Failed. + uio_free(*resolvedPath); + errno = ENOTDIR; + return -1; + case ENOENT: + // No match; try the next pLoc. + continue; + default: + // Unknown error. Let's bail out just to be safe. +#ifdef DEBUG + fprintf(stderr, "Warning: Unknown error from " + "uio_walkPhysicalPath: %s\n", strerror(retVal)); +#endif + uio_free(*resolvedPath); + errno = retVal; + return -1; + } + } + + // No match, exit with ENOENT. + uio_free(*resolvedPath); + errno = ENOENT; + return -1; +} + +/** + * Determine the absolute path given a path relative to a given directory. + * + * @param[in] dirHandle The directory to which 'path' is relative. + * @param[in] path The path, relative to 'dirHandle', to make + * absolute. + * @param[in] pathLen The string length of 'path'. + * @param[out] destPath Filled with a newly allocated string containing + * the sought absolute path. It will not contain a '/' as the first + * or last character. It should be freed with uio_free(). + * Unmodified if an error occurs. + * + * @returns the length of '*destPath', or -1 if an error occurs, in which + * case #errno will be set. + */ +ssize_t +uio_resolvePath(uio_DirHandle *dirHandle, const char *path, size_t pathLen, + char **destPath) { + size_t len; + const char *pathEnd, *start, *end; + int numUp; // number of ".." dirs still need to be matched. + char *buffer; + char *endBufPtr; + uio_bool absolute; + + absolute = path[0] == '/'; + pathEnd = path + pathLen; + + // Pass 1: count the amount of space needed + len = 0; + numUp = 0; + for (getLastPathComponent(path, pathEnd, &start, &end); + end > path; + getPreviousPathComponent(path, &start, &end)) { + if (start[0] == '.') { + if (start + 1 == end) { + // "." matched + continue; + } + if (start[1] == '.' && start + 2 == end) { + // ".." matched + numUp++; + continue; + } + } + if (numUp > 0) { + // last 'numUp' components were ".." + numUp--; + continue; + } + len += (end - start) + 1; + // the 1 is for the '/' + } + + // The part from 'dirHandle->path' to 'dirHandle->rootEnd' is + // always copied (for a valid path). The rest we'll have to count. + // (Note the 'rootEnd' in the initialiser of the for loop.) + len += (dirHandle->rootEnd - dirHandle->path); + if (!absolute) { + for (getLastPath0Component(dirHandle->rootEnd, &start, &end); + end > dirHandle->rootEnd; + getPreviousPath0Component(dirHandle->rootEnd, &start, &end)) { + if (numUp > 0) { + numUp--; + continue; + } + len += (end - start) + 1; + // the 1 is for the '/' + } + } + if (numUp > 0) { + // too many ".." + errno = ENOENT; + return -1; + } + + if (len == 0) { + *destPath = uio_malloc(1); + (*destPath)[0] = '\0'; + return 0; + } + + // len--; // we don't want a '/' at the start + // len++; // for the terminating '\0' + buffer = uio_malloc(len); + + // Pass 2: fill the buffer + endBufPtr = buffer + len - 1; + *endBufPtr = '\0'; + numUp = 0; + for (getLastPathComponent(path, pathEnd, &start, &end); + end > path; + getPreviousPathComponent(path, &start, &end)) { + if (start[0] == '.') { + if (start + 1 == end) { + // "." matched + continue; + } + if (start[1] == '.' && start + 2 == end) { + // ".." matched + numUp++; + continue; + } + } + if (numUp > 0) { + // last 'numUp' components were ".." + numUp--; + continue; + } + endBufPtr -= (end - start); + memcpy(endBufPtr, start, end - start); + if (endBufPtr != buffer) { + // We want no '/' at the beginning + endBufPtr--; + *endBufPtr = '/'; + } else { + // We're already done. We might as well take advantage of + // the fact that we know that and exit immediatly: + *destPath = buffer; + return len; + } + } + // copy the part from dirHandle->path to dirHandle->rootEnd + endBufPtr -= (dirHandle->rootEnd - dirHandle->path); + memcpy(endBufPtr, dirHandle->path, dirHandle->rootEnd - dirHandle->path); + if (!absolute) { + // copy (some of) the components from dirHandle->rootEnd on. + for (getLastPath0Component(dirHandle->rootEnd, &start, &end); + end > dirHandle->rootEnd; + getPreviousPath0Component(dirHandle->rootEnd, &start, &end)) { + if (numUp > 0) { + numUp--; + continue; + } + endBufPtr -= (end - start); + memcpy(endBufPtr, start, end - start); + if (endBufPtr != buffer) { + // We want no '/' at the beginning + endBufPtr--; + *endBufPtr = '/'; + } else { + // We're already done. We might as well take advantage of + // the fact that we know that and exit immediatly: + break; + } + } + } + + *destPath = buffer; + return len; +} + + diff --git a/src/libs/uio/ioaux.h b/src/libs/uio/ioaux.h new file mode 100644 index 0000000..41c7e39 --- /dev/null +++ b/src/libs/uio/ioaux.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef LIBS_UIO_IOAUX_H_ +#define LIBS_UIO_IOAUX_H_ + +#include "iointrn.h" +#include "physical.h" +#include "uioport.h" + +int uio_walkPhysicalPath(uio_PDirHandle *startPDirHandle, const char *path, + size_t pathLen, uio_PDirHandle **endPDirHandle, + const char **pathRest); +uio_PDirHandle *uio_makePath(uio_PDirHandle *pDirHandle, const char *path, + size_t pathLen, mode_t mode); +int uio_copyFilePhysical(uio_PDirHandle *fromDir, const char *fromName, + uio_PDirHandle *toDir, const char *toName); +int uio_getPhysicalAccess(uio_DirHandle *dirHandle, const char *path, + int flags, int extraFlags, + uio_MountInfo **mountInfoReadPtr, uio_PDirHandle **readPDirHandlePtr, + char **readPRootPathPtr, + uio_MountInfo **mountInfoWritePtr, uio_PDirHandle **writePDirHandlePtr, + char **writePRootPathPtr, + char **restPtr); +#define uio_GPA_NOWRITE 1 +int uio_getPathPhysicalDirs(uio_DirHandle *dirHandle, const char *path, + size_t pathLen, uio_PDirHandle ***resPDirHandles, + int *resNumPDirHandles, uio_MountTreeItem ***resItems); +int uio_verifyPath(uio_DirHandle *dirHandle, const char *path, + char **resolvedPath); +ssize_t uio_resolvePath(uio_DirHandle *dirHandle, const char *path, + size_t pathLen, char **destPath); + + +#endif /* LIBS_UIO_IOAUX_H_ */ + diff --git a/src/libs/uio/iointrn.h b/src/libs/uio/iointrn.h new file mode 100644 index 0000000..522ce18 --- /dev/null +++ b/src/libs/uio/iointrn.h @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef LIBS_UIO_IOINTRN_H_ +#define LIBS_UIO_IOINTRN_H_ + +#define uio_INTERNAL + +typedef struct uio_PDirEntryHandle uio_PDirEntryHandle; +typedef struct uio_PDirHandle uio_PDirHandle; +typedef struct uio_PFileHandle uio_PFileHandle; +typedef struct uio_EntriesContext uio_EntriesContext; + +#ifndef uio_INTERNAL_PHYSICAL +typedef void *uio_PDirHandleExtra; +typedef void *uio_PFileHandleExtra; +#endif + +#include "io.h" +#include "uioport.h" +#include "physical.h" +#include "mount.h" +#include "mounttree.h" +#include "match.h" +#include "mem.h" + +struct uio_Handle { + int ref; + struct uio_PRoot *root; + uio_NativeHandle native; + int openFlags; + // need to know whether the handle is a RO or RW handle. +}; + +struct uio_DirHandle { + int ref; + struct uio_Repository *repository; + char *path; + // does not contain any '.' or '..'; does not start or end + // with a / + char *rootEnd; + // points to the end of the part of path that is considered + // the root dir. (you can't use '..' to get above the root dir) +}; + +struct uio_MountHandle { + struct uio_Repository *repository; + struct uio_MountInfo *mountInfo; +}; + +struct uio_DirList { + const char **names; + int numNames; + char *buffer; +}; + + +#define uio_PHandle_COMMON \ + int flags; \ + int ref; \ + uio_PRoot *pRoot; + +#define uio_PDirEntryHandle_TYPE_REG 0x0000 +#define uio_PDirEntryHandle_TYPE_DIR 0x0001 +#define uio_PDirEntryHandle_TYPEMASK 0x0001 + +struct uio_PDirEntryHandle { + uio_PHandle_COMMON +}; + +struct uio_PDirHandle { + uio_PHandle_COMMON + uio_PDirHandleExtra extra; +}; + +struct uio_PFileHandle { + uio_PHandle_COMMON + uio_PFileHandleExtra extra; +}; + +struct uio_EntriesContext { + uio_PRoot *pRoot; + uio_NativeEntriesContext native; +}; + + +uio_Handle *uio_Handle_new(uio_PRoot *root, uio_NativeHandle native, + int openFlags); +void uio_Handle_delete(uio_Handle *handle); +void uio_DirHandle_delete(uio_DirHandle *dirHandle); + +uio_PDirEntryHandle *uio_getPDirEntryHandle( + const uio_PDirHandle *dirHandle, const char *name); +void uio_PDirHandle_deletePDirHandleExtra(uio_PDirHandle *pDirHandle); +void uio_PFileHandle_deletePFileHandleExtra(uio_PFileHandle *pFileHandle); + +uio_PDirHandle *uio_PDirHandle_new(uio_PRoot *pRoot, + uio_PDirHandleExtra extra); +uio_PFileHandle *uio_PFileHandle_new(uio_PRoot *pRoot, + uio_PFileHandleExtra extra); +void uio_PDirEntryHandle_delete(uio_PDirEntryHandle *pDirEntryHandle); +void uio_PDirHandle_delete(uio_PDirHandle *pDirHandle); +void uio_PFileHandle_delete(uio_PFileHandle *pFileHandle); + + +static inline void +uio_Handle_ref(uio_Handle *handle) { + handle->ref++; +} + +static inline void +uio_Handle_unref(uio_Handle *handle) { + assert(handle->ref > 0); + handle->ref--; + if (handle->ref == 0) + uio_Handle_delete(handle); +} + +static inline void +uio_DirHandle_ref(uio_DirHandle *dirHandle) { + dirHandle->ref++; +} + +static inline void +uio_DirHandle_unref(uio_DirHandle *dirHandle) { + assert(dirHandle->ref > 0); + dirHandle->ref--; + if (dirHandle->ref == 0) + uio_DirHandle_delete(dirHandle); +} + +static inline uio_bool +uio_PDirEntryHandle_isDir(const uio_PDirEntryHandle *handle) { + return (handle->flags & uio_PDirEntryHandle_TYPEMASK) == + uio_PDirEntryHandle_TYPE_DIR; +} + +static inline void +uio_PDirEntryHandle_ref(uio_PDirEntryHandle *handle) { + handle->ref++; +} + +static inline void +uio_PDirEntryHandle_unref(uio_PDirEntryHandle *handle) { + assert(handle->ref > 0); + handle->ref--; + if (handle->ref == 0) + uio_PDirEntryHandle_delete(handle); +} + +static inline void +uio_PDirHandle_ref(uio_PDirHandle *handle) { + handle->ref++; +} + +static inline void +uio_PDirHandle_unref(uio_PDirHandle *handle) { + assert(handle->ref > 0); + handle->ref--; + if (handle->ref == 0) + uio_PDirHandle_delete(handle); +} + +static inline void +uio_PFileHandle_ref(uio_PFileHandle *handle) { + handle->ref++; +} + +static inline void +uio_PFileHandle_unref(uio_PFileHandle *handle) { + assert(handle->ref > 0); + handle->ref--; + if (handle->ref == 0) + uio_PFileHandle_delete(handle); +} + + +#endif /* LIBS_UIO_IOINTRN_H_ */ + + diff --git a/src/libs/uio/match.c b/src/libs/uio/match.c new file mode 100644 index 0000000..68e6897 --- /dev/null +++ b/src/libs/uio/match.c @@ -0,0 +1,569 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <string.h> +#ifdef DEBUG +# include <stdio.h> +#endif + +#define match_INTERNAL + +#include "match.h" +#include "mem.h" +#include "uioport.h" + +#ifdef HAVE_GLOB +# include <fnmatch.h> +#endif + + +static inline match_MatchContext *match_allocMatchContext(void); +static inline void match_freeMatchContext(match_MatchContext *context); + +static inline match_LiteralContext *match_newLiteralContext(char *pattern); +static inline match_LiteralContext *match_allocLiteralContext(void); +static inline void match_freeLiteralContext(match_LiteralContext *context); +static inline match_PrefixContext *match_newPrefixContext(char *pattern); +static inline match_PrefixContext *match_allocPrefixContext(void); +static inline void match_freePrefixContext(match_PrefixContext *context); +static inline match_SuffixContext *match_newSuffixContext( + char *pattern, size_t len); +static inline match_SuffixContext *match_allocSuffixContext(void); +static inline void match_freeSuffixContext(match_SuffixContext *context); +static inline match_SubStringContext *match_newSubStringContext( + char *pattern); +static inline match_SubStringContext *match_allocSubStringContext(void); +static inline void match_freeSubStringContext(match_SubStringContext *context); +#ifdef HAVE_GLOB +static inline match_GlobContext *match_newGlobContext(char *pattern); +static inline match_GlobContext *match_allocGlobContext(void); +static inline void match_freeGlobContext(match_GlobContext *context); +#endif /* HAVE_GLOB */ +#ifdef HAVE_REGEX +static inline match_RegexContext *match_newRegexContext(void); +static inline match_RegexContext *match_allocRegexContext(void); +static inline void match_freeRegexContext(match_RegexContext *context); +#endif /* HAVE_REGEX */ + + +// *** General part *** + +static inline match_MatchContext * +match_allocMatchContext(void) { + return uio_malloc(sizeof (match_MatchContext)); +} + +static inline void +match_freeMatchContext(match_MatchContext *context) { + uio_free(context); +} + +// NB: Even if this function fails, *contextPtr contains a context +// which needs to be freed. +match_Result +match_prepareContext(const char *pattern, match_MatchContext **contextPtr, + match_MatchType type) { + match_Result result; + + *contextPtr = match_allocMatchContext(); + (*contextPtr)->type = type; + switch (type) { + case match_MATCH_LITERAL: + result = match_prepareLiteral(pattern, + &(*contextPtr)->u.literal); + break; + case match_MATCH_PREFIX: + result = match_preparePrefix(pattern, &(*contextPtr)->u.prefix); + break; + case match_MATCH_SUFFIX: + result = match_prepareSuffix(pattern, &(*contextPtr)->u.suffix); + break; + case match_MATCH_SUBSTRING: + result = match_prepareSubString(pattern, + &(*contextPtr)->u.subString); + break; +#ifdef HAVE_GLOB + case match_MATCH_GLOB: + result = match_prepareGlob(pattern, &(*contextPtr)->u.glob); + break; +#endif +#ifdef HAVE_REGEX + case match_MATCH_REGEX: + result = match_prepareRegex(pattern, &(*contextPtr)->u.regex); + break; +#endif + default: +#ifdef DEBUG + fprintf(stderr, "match_prepareContext called with unsupported " + "type %d matching.\n", type); +#endif + return match_ENOSYS; + } + return result; +} + +match_Result +match_matchPattern(match_MatchContext *context, const char *string) { + switch (context->type) { + case match_MATCH_LITERAL: + return match_matchLiteral(context->u.literal, string); + case match_MATCH_PREFIX: + return match_matchPrefix(context->u.prefix, string); + case match_MATCH_SUFFIX: + return match_matchSuffix(context->u.suffix, string); + case match_MATCH_SUBSTRING: + return match_matchSubString(context->u.subString, string); +#ifdef HAVE_GLOB + case match_MATCH_GLOB: + return match_matchGlob(context->u.glob, string); +#endif +#ifdef HAVE_REGEX + case match_MATCH_REGEX: + return match_matchRegex(context->u.regex, string); +#endif + default: + abort(); + } +} + +// context may be NULL +const char * +match_errorString(match_MatchContext *context, match_Result result) { + switch (result) { + case match_OK: + case match_MATCH: +// case match_NOMATCH: // same value as match_OK + return "Success"; + case match_ENOSYS: + return "Not implemented"; + case match_ENOTINIT: + return "Uninitialised use"; + case match_ECUSTOM: + // Depends on match type. Printed below. + break; + default: + return "Unknown error"; + } + + if (context == NULL) + return "Unknown match-type specific error."; + // We can't be any more specific if no 'context' is supplied. + + switch (context->type) { +#if 0 + case match_MATCH_LITERAL: + return match_errorStringLiteral(context->u.literal, result); + case match_MATCH_PREFIX: + return match_errorStringPrefix(context->u.prefix, result); + case match_MATCH_SUFFIX: + return match_errorStringSuffix(context->u.suffix, result); + case match_MATCH_SUBSTRING: + return match_errorStringSubString(context->u.subString, result); +#ifdef HAVE_GLOB + case match_MATCH_GLOB: + return match_errorStringGlob(context->u.glob, result); +#endif +#endif +#ifdef HAVE_REGEX + case match_MATCH_REGEX: + return match_errorStringRegex(context->u.regex, result); +#endif + default: + abort(); + } +} + +void +match_freeContext(match_MatchContext *context) { + switch (context->type) { + case match_MATCH_LITERAL: + match_freeLiteral(context->u.literal); + break; + case match_MATCH_PREFIX: + match_freePrefix(context->u.prefix); + break; + case match_MATCH_SUFFIX: + match_freeSuffix(context->u.suffix); + break; + case match_MATCH_SUBSTRING: + match_freeSubString(context->u.subString); + break; +#ifdef HAVE_GLOB + case match_MATCH_GLOB: + match_freeGlob(context->u.glob); + break; +#endif +#ifdef HAVE_REGEX + case match_MATCH_REGEX: + match_freeRegex(context->u.regex); + break; +#endif + default: + abort(); + } + match_freeMatchContext(context); +} + +match_Result +match_matchPatternOnce(const char *pattern, match_MatchType type, + const char *string) { + match_MatchContext *context; + match_Result result; + + result = match_prepareContext(pattern, &context, type); + if (result != match_OK) + goto out; + + result = match_matchPattern(context, string); + +out: + match_freeContext(context); + return result; +} + + +// *** Literal part *** + +match_Result +match_prepareLiteral(const char *pattern, + match_LiteralContext **contextPtr) { + *contextPtr = match_newLiteralContext(uio_strdup(pattern)); + return match_OK; +} + +match_Result +match_matchLiteral(match_LiteralContext *context, const char *string) { + return (strcmp(context->pattern, string) == 0) ? + match_MATCH : match_NOMATCH; +} + +void +match_freeLiteral(match_LiteralContext *context) { + uio_free(context->pattern); + match_freeLiteralContext(context); +} + +static inline match_LiteralContext * +match_newLiteralContext(char *pattern) { + match_LiteralContext *result; + + result = match_allocLiteralContext(); + result->pattern = pattern; + return result; +} + +static inline match_LiteralContext * +match_allocLiteralContext(void) { + return uio_malloc(sizeof (match_LiteralContext)); +} + +static inline void +match_freeLiteralContext(match_LiteralContext *context) { + uio_free(context); +} + + +// *** Prefix part *** + +match_Result +match_preparePrefix(const char *pattern, + match_PrefixContext **contextPtr) { + *contextPtr = match_newPrefixContext(uio_strdup(pattern)); + return match_OK; +} + +match_Result +match_matchPrefix(match_PrefixContext *context, const char *string) { + char *patPtr; + + patPtr = context->pattern; + while (1) { + if (*patPtr == '\0') { + // prefix has completely matched + return match_MATCH; + } + if (*string == '\0') { + // no more string left, and still prefix to match + return match_NOMATCH; + } + if (*patPtr != *string) + return match_NOMATCH; + patPtr++; + string++; + } +} + +void +match_freePrefix(match_PrefixContext *context) { + uio_free(context->pattern); + match_freePrefixContext(context); +} + +static inline match_PrefixContext * +match_newPrefixContext(char *pattern) { + match_PrefixContext *result; + + result = match_allocPrefixContext(); + result->pattern = pattern; + return result; +} + +static inline match_PrefixContext * +match_allocPrefixContext(void) { + return uio_malloc(sizeof (match_PrefixContext)); +} + +static inline void +match_freePrefixContext(match_PrefixContext *context) { + uio_free(context); +} + + +// *** Suffix part *** + +match_Result +match_prepareSuffix(const char *pattern, + match_SuffixContext **contextPtr) { + *contextPtr = match_newSuffixContext( + uio_strdup(pattern), strlen(pattern)); + return match_OK; +} + +match_Result +match_matchSuffix(match_SuffixContext *context, const char *string) { + size_t stringLen; + + stringLen = strlen(string); + if (stringLen < context->len) { + // Supplied suffix is larger than string + return match_NOMATCH; + } + + return memcmp(string + stringLen - context->len, context->pattern, + context->len) == 0 ? match_MATCH : match_NOMATCH; +} + +void +match_freeSuffix(match_SuffixContext *context) { + uio_free(context->pattern); + match_freeSuffixContext(context); +} + +static inline match_SuffixContext * +match_newSuffixContext(char *pattern, size_t len) { + match_SuffixContext *result; + + result = match_allocSuffixContext(); + result->pattern = pattern; + result->len = len; + return result; +} + +static inline match_SuffixContext * +match_allocSuffixContext(void) { + return uio_malloc(sizeof (match_SuffixContext)); +} + +static inline void +match_freeSuffixContext(match_SuffixContext *context) { + uio_free(context); +} + + +// *** SubString part *** + +match_Result +match_prepareSubString(const char *pattern, + match_SubStringContext **contextPtr) { + *contextPtr = match_newSubStringContext(uio_strdup(pattern)); + return match_OK; +} + +match_Result +match_matchSubString(match_SubStringContext *context, const char *string) { + return strstr(string, context->pattern) != NULL; +} + +void +match_freeSubString(match_SubStringContext *context) { + uio_free(context->pattern); + match_freeSubStringContext(context); +} + +static inline match_SubStringContext * +match_newSubStringContext(char *pattern) { + match_SubStringContext *result; + + result = match_allocSubStringContext(); + result->pattern = pattern; + return result; +} + +static inline match_SubStringContext * +match_allocSubStringContext(void) { + return uio_malloc(sizeof (match_SubStringContext)); +} + +static inline void +match_freeSubStringContext(match_SubStringContext *context) { + uio_free(context); +} + + +// *** Glob part *** + +#ifdef HAVE_GLOB +match_Result +match_prepareGlob(const char *pattern, match_GlobContext **contextPtr) { + *contextPtr = match_newGlobContext(uio_strdup(pattern)); + return match_OK; +} + +match_Result +match_matchGlob(match_GlobContext *context, const char *string) { + int retval; + + retval = fnmatch(context->pattern, string, 0); + switch (retval) { + case 0: + return match_MATCH; + case FNM_NOMATCH: + return match_NOMATCH; +#if 0 + case FNM_NOSYS: + return match_ENOSYS; +#endif + default: + return match_EUNKNOWN; + } +} + +void +match_freeGlob(match_GlobContext *context) { + uio_free(context->pattern); + match_freeGlobContext(context); +} + +static inline match_GlobContext * +match_newGlobContext(char *pattern) { + match_GlobContext *result; + + result = match_allocGlobContext(); + result->pattern = pattern; + return result; +} + +static inline match_GlobContext * +match_allocGlobContext(void) { + return uio_malloc(sizeof (match_GlobContext)); +} + +static inline void +match_freeGlobContext(match_GlobContext *context) { + uio_free(context); +} +#endif /* HAVE_GLOB */ + + +// *** Regex part *** + +#ifdef HAVE_REGEX +match_Result +match_prepareRegex(const char *pattern, match_RegexContext **contextPtr) { + *contextPtr = match_newRegexContext(); + (*contextPtr)->errorCode = regcomp(&(*contextPtr)->native, pattern, + REG_EXTENDED | REG_NOSUB); + if ((*contextPtr)->errorCode == 0) { + (*contextPtr)->flags = match_REGEX_INITIALISED; + return match_OK; + } + return match_ECUSTOM; +} + +match_Result +match_matchRegex(match_RegexContext *context, const char *string) { + int retval; + + if ((context->flags & match_REGEX_INITIALISED) != + match_REGEX_INITIALISED) { + return match_ENOTINIT; + } + if (context->errorString) { + uio_free(context->errorString); + context->errorString = NULL; + } + retval = regexec(&context->native, string, 0, NULL, 0); + switch (retval) { + case 0: + return match_MATCH; + case REG_NOMATCH: + return match_NOMATCH; + default: + context->errorCode = retval; + return match_ECUSTOM; + } +} + +const char * +match_errorStringRegex(match_RegexContext *context, int errorCode) { + size_t errorStringLength; + + if (context->errorString != NULL) + uio_free(context->errorString); + + errorStringLength = regerror(context->errorCode, &context->native, + NULL, 0); + context->errorString = uio_malloc(errorStringLength); + regerror(context->errorCode, &context->native, + context->errorString, errorStringLength); + + (void) errorCode; + return context->errorString; +} + +void +match_freeRegex(match_RegexContext *context) { + regfree(&context->native); + if (context->errorString) + uio_free(context->errorString); + match_freeRegexContext(context); +} + +static inline match_RegexContext * +match_newRegexContext(void) { + match_RegexContext *result; + result = match_allocRegexContext(); + result->errorString = NULL; + result->errorCode = 0; + result->flags = 0; + return result; +} + +static inline match_RegexContext * +match_allocRegexContext(void) { + return uio_malloc(sizeof (match_RegexContext)); +} + +static inline void +match_freeRegexContext(match_RegexContext *context) { + uio_free(context); +} +#endif /* HAVE_REGEX */ + diff --git a/src/libs/uio/match.h b/src/libs/uio/match.h new file mode 100644 index 0000000..0557e7d --- /dev/null +++ b/src/libs/uio/match.h @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef LIBS_UIO_MATCH_H_ +#define LIBS_UIO_MATCH_H_ + +typedef struct match_MatchContext match_MatchContext; + +#include <sys/types.h> + +// TODO: make this into a configurable option +//#define HAVE_GLOB +#define HAVE_REGEX + + +typedef enum { + match_MATCH_LITERAL = 0, + match_MATCH_PREFIX, + match_MATCH_SUFFIX, + match_MATCH_SUBSTRING, +#ifdef HAVE_GLOB + match_MATCH_GLOB, +#endif +#ifdef HAVE_REGEX + match_MATCH_REGEX, +#endif +} match_MatchType; + +typedef int match_Result; +#define match_NOMATCH 0 +#define match_MATCH 1 +#define match_OK 0 +#define match_EUNKNOWN -1 +#define match_ENOSYS -2 +#define match_ECUSTOM -3 +#define match_ENOTINIT -4 + +typedef struct match_LiteralContext match_LiteralContext; +typedef struct match_PrefixContext match_PrefixContext; +typedef struct match_SuffixContext match_SuffixContext; +typedef struct match_SubStringContext match_SubStringContext; +#ifdef HAVE_GLOB +typedef struct match_GlobContext match_GlobContext; +#endif +#ifdef HAVE_REGEX +typedef struct match_RegexContext match_RegexContext; +#endif + +match_Result match_prepareContext(const char *pattern, + match_MatchContext **contextPtr, match_MatchType type); +match_Result match_matchPattern(match_MatchContext *context, + const char *string); +const char *match_errorString(match_MatchContext *context, + match_Result result); +void match_freeContext(match_MatchContext *context); +match_Result match_matchPatternOnce(const char *pattern, match_MatchType type, + const char *string); + + +/* *** Internal definitions follow *** */ +#ifdef match_INTERNAL + +#include <sys/types.h> +#ifdef HAVE_REGEX +# include <regex.h> +#endif + +#include "uioport.h" + +struct match_MatchContext { + match_MatchType type; + union { + match_LiteralContext *literal; + match_PrefixContext *prefix; + match_SuffixContext *suffix; + match_SubStringContext *subString; +#ifdef HAVE_GLOB + match_GlobContext *glob; +#endif +#ifdef HAVE_REGEX + match_RegexContext *regex; +#endif + } u; +}; + +struct match_LiteralContext { + char *pattern; +}; + +struct match_PrefixContext { + char *pattern; +}; + +struct match_SuffixContext { + char *pattern; + size_t len; + // for speed +}; + +struct match_SubStringContext { + char *pattern; +}; + +#ifdef HAVE_GLOB +struct match_GlobContext { + char *pattern; +}; +#endif + +#ifdef HAVE_REGEX +struct match_RegexContext { + regex_t native; + char *errorString; + int errorCode; + int flags; +#define match_REGEX_INITIALISED 1 +}; +#endif + +match_Result match_prepareLiteral(const char *pattern, + match_LiteralContext **contextPtr); +match_Result match_matchLiteral(match_LiteralContext *context, + const char *string); +void match_freeLiteral(match_LiteralContext *context); + +match_Result match_preparePrefix(const char *pattern, + match_PrefixContext **contextPtr); +match_Result match_matchPrefix(match_PrefixContext *context, + const char *string); +void match_freePrefix(match_PrefixContext *context); + +match_Result match_prepareSuffix(const char *pattern, + match_SuffixContext **contextPtr); +match_Result match_matchSuffix(match_SuffixContext *context, + const char *string); +void match_freeSuffix(match_SuffixContext *context); + +match_Result match_prepareSubString(const char *pattern, + match_SubStringContext **contextPtr); +match_Result match_matchSubString(match_SubStringContext *context, + const char *string); +void match_freeSubString(match_SubStringContext *context); + +#ifdef HAVE_GLOB +match_Result match_prepareGlob(const char *pattern, + match_GlobContext **contextPtr); +match_Result match_matchGlob(match_GlobContext *context, + const char *string); +void match_freeGlob(match_GlobContext *context); +#endif /* HAVE_GLOB */ + +#ifdef HAVE_REGEX +match_Result match_prepareRegex(const char *pattern, + match_RegexContext **contextPtr); +match_Result match_matchRegex(match_RegexContext *context, + const char *string); +const char *match_errorStringRegex(match_RegexContext *context, + int errorCode); +void match_freeRegex(match_RegexContext *context); +#endif + +#endif /* match_INTERNAL */ + +#endif /* LIBS_UIO_MATCH_H_ */ + diff --git a/src/libs/uio/mem.h b/src/libs/uio/mem.h new file mode 100644 index 0000000..915837e --- /dev/null +++ b/src/libs/uio/mem.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef LIBS_UIO_MEM_H_ +#define LIBS_UIO_MEM_H_ + +#include <stdlib.h> +#include <string.h> +#include "uioport.h" + +#define uio_malloc malloc +#define uio_realloc realloc +#define uio_free free +#define uio_calloc calloc + +#ifdef uio_MEM_DEBUG +// When uio_strdup is defined to the libc strdup, there's no opportunity +// to intercept the alloc. Hence this function here. +static inline char * +uio_strdup(const char *s) { + char *result; + size_t size; + + size = strlen(s) + 1; + result = uio_malloc(size); + memcpy(result, s, size); + return result; +} +#else +# define uio_strdup strdup +#endif + +// Allocates new memory, copies 'len' characters from 'src', and adds a '\0'. +static inline char * +uio_memdup0(const char *src, size_t len) { + char *dst = uio_malloc(len + 1); + memcpy(dst, src, len); + dst[len] = '\0'; + return dst; +} + +#endif + + diff --git a/src/libs/uio/memdebug.c b/src/libs/uio/memdebug.c new file mode 100644 index 0000000..b6bd1b6 --- /dev/null +++ b/src/libs/uio/memdebug.c @@ -0,0 +1,293 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <assert.h> +#include <stdio.h> + +#include "memdebug.h" +#include "hashtable.h" +#include "mem.h" +#include "uioutils.h" +#include "types.h" +#include "uioport.h" + +// I'm intentionally not including the .h files. It would only make things +// messy, because the second arguments would be other pointers, which +// would require typecasts of the functions in uio_MemDebug_logTypeInfo. +extern void uio_DirHandle_print(FILE *out, const void *); +extern void uio_printMountInfo(FILE *out, const void *); +extern void uio_printMountTreeItem(FILE *out, const void *); +extern void uio_printPathComp(FILE *out, const void *); + +// Must be in the same order as in uio_MemDebug_LogTypes +// Change the third field to have debug info printed for specific actions. +// See memdebug.h file the possible values. +const uio_MemDebug_LogTypeInfo uio_MemDebug_logTypeInfo[] = { + { "uio_DirHandle", uio_DirHandle_print, 0 }, + { "uio_FileSystemInfo", NULL, 0 }, + { "uio_GPDir", NULL, 0 }, + { "uio_GPFile", NULL, 0 }, + { "uio_GPRoot", NULL, 0 }, + { "uio_Handle", NULL, 0 }, + { "uio_MountHandle", NULL, 0 }, + { "uio_MountInfo", uio_printMountInfo, 0 }, + { "uio_MountTree", NULL, 0 }, + { "uio_MountTreeItem", uio_printMountTreeItem, 0 }, + { "uio_PathComp", uio_printPathComp, 0 }, + { "uio_PFileHandle", NULL, 0 }, + { "uio_PDirHandle", NULL, 0 }, + { "uio_PRoot", NULL, 0 }, + { "uio_Repository", NULL, 0 }, + { "uio_Stream", NULL, 0 }, + { "stdio_GPDirData", NULL, 0 }, +#ifdef HAVE_ZIP + { "zip_GPFileData", NULL, 0 }, + { "zip_GPDirData", NULL, 0 }, +#endif +}; + +HashTable_HashTable **uio_MemDebug_logs; + + +static uio_uint32 uio_MemDebug_pointerHash(const void *ptr); +static uio_bool uio_MemDebug_pointerCompare(const void *ptr1, const void *ptr2); +static void *uio_MemDebug_pointerCopy(const void *ptr); +static void uio_MemDebug_pointerFree(void *ptr); + +static inline uio_MemDebug_PointerInfo *uio_MemDebug_PointerInfo_new(int ref); +static inline void uio_MemDebug_PointerInfo_delete( + uio_MemDebug_PointerInfo *pointerInfo); +static inline uio_MemDebug_PointerInfo *uio_MemDebug_PointerInfo_alloc(void); +static inline void uio_MemDebug_PointerInfo_free( + uio_MemDebug_PointerInfo *pointerInfo); + +void +uio_MemDebug_init(void) { + int i; + + assert((int) uio_MemDebug_numLogTypes == + (sizeof uio_MemDebug_logTypeInfo / + sizeof uio_MemDebug_logTypeInfo[0])); + uio_MemDebug_logs = uio_malloc(uio_MemDebug_numLogTypes * + sizeof (HashTable_HashTable *)); + + for (i = 0; i < uio_MemDebug_numLogTypes; i++) { + uio_MemDebug_logs[i] = HashTable_newHashTable( + uio_MemDebug_pointerHash, + uio_MemDebug_pointerCompare, + uio_MemDebug_pointerCopy, + uio_MemDebug_pointerFree, + 4, 0.85, 0.90); + } +} + +void +uio_MemDebug_unInit(void) { + int i; + + for (i = 0; i < uio_MemDebug_numLogTypes; i++) + HashTable_deleteHashTable(uio_MemDebug_logs[i]); + + uio_free(uio_MemDebug_logs); +} + +static uio_uint32 +uio_MemDebug_pointerHash(const void *ptr) { + uio_uintptr ptrInt; + + ptrInt = (uio_uintptr) ptr; + return (uio_uint32) (ptrInt ^ (ptrInt >> 10) ^ (ptrInt >> 20)); +} + +static uio_bool +uio_MemDebug_pointerCompare(const void *ptr1, const void *ptr2) { + return ptr1 == ptr2; +} + +static void * +uio_MemDebug_pointerCopy(const void *ptr) { + return unconst(ptr); +} + +static void +uio_MemDebug_pointerFree(void *ptr) { + (void) ptr; +} + +void +uio_MemDebug_logAllocation(uio_MemDebug_LogType type, void *ptr) { + uio_MemDebug_PointerInfo *pointerInfo; + + if (ptr == NULL) { + fprintf(stderr, "Fatal: Allocated pointer is (%s *) NULL.\n", + uio_MemDebug_logTypeInfo[(int) type].name); + abort(); + } + if (uio_MemDebug_logTypeInfo[(int) type].flags & uio_MemDebug_PRINT_ALLOC) { + fprintf(stderr, "Alloc "); + uio_MemDebug_printPointer(stderr, type, ptr); + fprintf(stderr, "\n"); + } + pointerInfo = uio_MemDebug_PointerInfo_new(1); + HashTable_add(uio_MemDebug_logs[type], ptr, (void *) pointerInfo); +} + +void +uio_MemDebug_logDeallocation(uio_MemDebug_LogType type, void *ptr) { + uio_MemDebug_PointerInfo *pointerInfo; + + if (ptr == NULL) { + fprintf(stderr, "Fatal: Attempt to free (%s *) NULL pointer.\n", + uio_MemDebug_logTypeInfo[(int) type].name); + abort(); + } + if (uio_MemDebug_logTypeInfo[(int) type].flags & uio_MemDebug_PRINT_FREE) { + fprintf(stderr, "Free "); + uio_MemDebug_printPointer(stderr, type, ptr); + fprintf(stderr, "\n"); + } + pointerInfo = HashTable_find(uio_MemDebug_logs[type], ptr); + if (pointerInfo == NULL) { + fprintf(stderr, "Fatal: Attempt to free unallocated pointer " + "(%s *) %p.\n", + uio_MemDebug_logTypeInfo[(int) type].name, ptr); + abort(); + } +#if 0 + if (pointerInfo->ref != 0) { + fprintf(stderr, "Fatal: Attempt to free pointer with references " + "left (%s *) %p.\n", + uio_MemDebug_logTypeInfo[(int) type].name, ptr); + abort(); + } +#endif + uio_MemDebug_PointerInfo_free(pointerInfo); + HashTable_remove(uio_MemDebug_logs[type], ptr); +} + +void +uio_MemDebug_logRef(uio_MemDebug_LogType type, void *ptr) { + uio_MemDebug_PointerInfo *pointerInfo; + + if (ptr == NULL) { + fprintf(stderr, "Fatal: Attempt to increment reference to a " + "(%s *) NULL pointer.\n", + uio_MemDebug_logTypeInfo[(int) type].name); + abort(); + } + pointerInfo = HashTable_find(uio_MemDebug_logs[type], ptr); + if (pointerInfo == NULL) { + fprintf(stderr, "Fatal: Attempt to increment reference to " + "unallocated pointer (%s *) %p.\n", + uio_MemDebug_logTypeInfo[(int) type].name, ptr); + abort(); + } + pointerInfo->pointerRef++; + if (uio_MemDebug_logTypeInfo[(int) type].flags & uio_MemDebug_PRINT_REF) { + fprintf(stderr, "Ref++ to %d, ", pointerInfo->pointerRef); + uio_MemDebug_printPointer(stderr, type, ptr); + fprintf(stderr, "\n"); + } +} + +void +uio_MemDebug_logUnref(uio_MemDebug_LogType type, void *ptr) { + uio_MemDebug_PointerInfo *pointerInfo; + + if (ptr == NULL) { + fprintf(stderr, "Fatal: Attempt to decrement reference to a " + "(%s *) NULL pointer.\n", + uio_MemDebug_logTypeInfo[(int) type].name); + abort(); + } + pointerInfo = HashTable_find(uio_MemDebug_logs[type], ptr); + if (pointerInfo == NULL) { + fprintf(stderr, "Fatal: Attempt to decrement reference to " + "unallocated pointer (%s *) %p.\n", + uio_MemDebug_logTypeInfo[(int) type].name, ptr); + abort(); + } + if (pointerInfo->pointerRef == 0) { + fprintf(stderr, "Fatal: Attempt to decrement reference below 0 for " + "pointer (%s *) %p.\n", + uio_MemDebug_logTypeInfo[(int) type].name, ptr); + abort(); + } + pointerInfo->pointerRef--; + if (uio_MemDebug_logTypeInfo[(int) type].flags & uio_MemDebug_PRINT_UNREF) { + fprintf(stderr, "Ref-- to %d, ", pointerInfo->pointerRef); + uio_MemDebug_printPointer(stderr, type, ptr); + fprintf(stderr, "\n"); + } +} + +void +uio_MemDebug_printPointer(FILE *out, uio_MemDebug_LogType type, void *ptr) { + fprintf(out, "(%s *) %p", uio_MemDebug_logTypeInfo[(int) type].name, ptr); + if (uio_MemDebug_logTypeInfo[(int) type].printFunction != NULL) { + fprintf(out, ": "); + uio_MemDebug_logTypeInfo[(int) type].printFunction(out, ptr); + } +} + +void +uio_MemDebug_printPointersType(FILE *out, uio_MemDebug_LogType type) { + HashTable_Iterator *iterator; + + for (iterator = HashTable_getIterator(uio_MemDebug_logs[type]); + !HashTable_iteratorDone(iterator); + iterator = HashTable_iteratorNext(iterator)) { + uio_MemDebug_printPointer(out, type, HashTable_iteratorKey(iterator)); + fprintf(out, "\n"); + } + HashTable_freeIterator(iterator); +} + +void +uio_MemDebug_printPointers(FILE *out) { + int i; + + for (i = 0; i < uio_MemDebug_numLogTypes; i++) + uio_MemDebug_printPointersType(out, i); +} + +static inline uio_MemDebug_PointerInfo * +uio_MemDebug_PointerInfo_new(int ref) { + uio_MemDebug_PointerInfo *result; + result = uio_MemDebug_PointerInfo_alloc(); + result->pointerRef = ref; + return result; +} + +static inline void +uio_MemDebug_PointerInfo_delete(uio_MemDebug_PointerInfo *pointerInfo) { + uio_MemDebug_PointerInfo_free(pointerInfo); +} + +static inline uio_MemDebug_PointerInfo * +uio_MemDebug_PointerInfo_alloc(void) { + return uio_malloc(sizeof (uio_MemDebug_PointerInfo)); +} + +static inline void +uio_MemDebug_PointerInfo_free(uio_MemDebug_PointerInfo *pointerInfo) { + uio_free(pointerInfo); +} + diff --git a/src/libs/uio/memdebug.h b/src/libs/uio/memdebug.h new file mode 100644 index 0000000..e04a8d2 --- /dev/null +++ b/src/libs/uio/memdebug.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef LIBS_UIO_MEMDEBUG_H_ +#define LIBS_UIO_MEMDEBUG_H_ + +#include <stdio.h> +#include "uioport.h" + +// Important: if you add an item here, add it to uio_MemDebug_LogTypeInfo +// too. The order should be the same. +typedef enum { + uio_MemDebug_LogType_uio_DirHandle, + uio_MemDebug_LogType_uio_FileSystemInfo, + uio_MemDebug_LogType_uio_GPDir, + uio_MemDebug_LogType_uio_GPFile, + uio_MemDebug_LogType_uio_GPRoot, + uio_MemDebug_LogType_uio_Handle, + uio_MemDebug_LogType_uio_MountHandle, + uio_MemDebug_LogType_uio_MountInfo, + uio_MemDebug_LogType_uio_MountTree, + uio_MemDebug_LogType_uio_MountTreeItem, + uio_MemDebug_LogType_uio_PathComp, + uio_MemDebug_LogType_uio_PFileHandle, + uio_MemDebug_LogType_uio_PDirHandle, + uio_MemDebug_LogType_uio_PRoot, + uio_MemDebug_LogType_uio_Repository, + uio_MemDebug_LogType_uio_Stream, + uio_MemDebug_LogType_stdio_GPDirData, + uio_MemDebug_LogType_zip_GPFileData, + uio_MemDebug_LogType_zip_GPDirData, + + uio_MemDebug_numLogTypes, /* This needs to be the last entry */ +} uio_MemDebug_LogType; + +typedef void (uio_MemDebug_PrintFunction)(FILE *out, const void *arg); + +typedef struct uio_MemDebug_LogTypeInfo { + const char *name; + uio_MemDebug_PrintFunction *printFunction; + int flags; +#define uio_MemDebug_PRINT_ALLOC 0x1 +#define uio_MemDebug_PRINT_FREE 0x2 +#define uio_MemDebug_PRINT_REF 0x4 +#define uio_MemDebug_PRINT_UNREF 0x8 +#define uio_MemDebug_PRINT_ALL \ + (uio_MemDebug_PRINT_ALLOC | uio_MemDebug_PRINT_FREE | \ + uio_MemDebug_PRINT_REF | uio_MemDebug_PRINT_UNREF) +} uio_MemDebug_LogTypeInfo; + +extern const uio_MemDebug_LogTypeInfo uio_MemDebug_logTypeInfo[]; + +typedef struct uio_MemDebug_PointerInfo { + int pointerRef; + // Ref counter for the associated pointer, not the pointerInfo + // itself. +} uio_MemDebug_PointerInfo; + +void uio_MemDebug_init(void); +void uio_MemDebug_unInit(void); +void uio_MemDebug_logAllocation(uio_MemDebug_LogType type, void *ptr); +void uio_MemDebug_logDeallocation(uio_MemDebug_LogType type, void *ptr); +void uio_MemDebug_logRef(uio_MemDebug_LogType type, void *ptr); +void uio_MemDebug_logUnref(uio_MemDebug_LogType type, void *ptr); +void uio_MemDebug_printPointer(FILE *out, uio_MemDebug_LogType type, + void *ptr); +void uio_MemDebug_printPointersType(FILE *out, uio_MemDebug_LogType type); +void uio_MemDebug_printPointers(FILE *out); + +#define uio_MemDebug_debugAlloc(type, pointer) \ + uio_MemDebug_logAllocation(uio_MemDebug_LogType_ ## type, pointer) +#define uio_MemDebug_debugFree(type, pointer) \ + uio_MemDebug_logDeallocation(uio_MemDebug_LogType_ ## type, pointer) +#define uio_MemDebug_debugRef(type, pointer) \ + uio_MemDebug_logRef(uio_MemDebug_LogType_ ## type, pointer) +#define uio_MemDebug_debugUnref(type, pointer) \ + uio_MemDebug_logUnref(uio_MemDebug_LogType_ ## type, pointer) + +#endif /* LIBS_UIO_MEMDEBUG_H_ */ + diff --git a/src/libs/uio/mount.c b/src/libs/uio/mount.c new file mode 100644 index 0000000..f095d3f --- /dev/null +++ b/src/libs/uio/mount.c @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <assert.h> +#include <string.h> + +#include "iointrn.h" +#include "uioport.h" +#include "mount.h" +#include "mounttree.h" +#include "mem.h" +#include "uioutils.h" +#ifdef uio_MEM_DEBUG +# include "memdebug.h" +#endif + +static void uio_Repository_delete(uio_Repository *repository); +static uio_Repository *uio_Repository_alloc(void); +static void uio_Repository_free(uio_Repository *repository); + + +void +uio_repositoryAddMount(uio_Repository *repository, + uio_MountInfo *mountInfo, uio_MountLocation location, + uio_MountInfo *relative) { + lockRepository(repository, uio_LOCK_WRITE); + switch (location) { + case uio_MOUNT_TOP: { + uio_MountInfo **newMounts; + + newMounts = uio_malloc( + (repository->numMounts + 2) * sizeof (uio_MountInfo *)); + newMounts[0] = mountInfo; + memcpy(&newMounts[1], repository->mounts, + (repository->numMounts + 1) * sizeof (uio_MountInfo *)); + uio_free(repository->mounts); + repository->mounts = newMounts; + repository->numMounts++; + break; + } + case uio_MOUNT_BOTTOM: + repository->mounts = uio_realloc(repository->mounts, + (repository->numMounts + 2) * sizeof (uio_MountInfo *)); + repository->mounts[repository->numMounts] = mountInfo; + repository->numMounts++; + break; + case uio_MOUNT_ABOVE: { + int i; + uio_MountInfo **newMounts; + + i = 0; + while (repository->mounts[i] != relative) + i++; + newMounts = (uio_MountInfo **) insertArrayPointer( + (const void **) repository->mounts, + repository->numMounts + 1, i, (void *) mountInfo); + uio_free(repository->mounts); + repository->mounts = newMounts; + repository->numMounts++; + break; + } + case uio_MOUNT_BELOW: { + int i; + uio_MountInfo **newMounts; + + i = 0; + while (repository->mounts[i] != relative) + i++; + i++; + newMounts = (uio_MountInfo **) insertArrayPointer( + (const void **) repository->mounts, + repository->numMounts + 1, i, (void *) mountInfo); + uio_free(repository->mounts); + repository->mounts = newMounts; + repository->numMounts++; + break; + } + default: + assert(false); + } + unlockRepository(repository); +} + +void +uio_repositoryRemoveMount(uio_Repository *repository, uio_MountInfo *mountInfo) { + int i; + uio_MountInfo **newMounts; + + lockRepository(repository, uio_LOCK_WRITE); + + i = 0; + while (repository->mounts[i] != mountInfo) + i++; + newMounts = (uio_MountInfo **) excludeArrayPointer( + (const void **) repository->mounts, repository->numMounts + 1, + i, 1); + uio_free(repository->mounts); + repository->mounts = newMounts; + repository->numMounts--; + unlockRepository(repository); +} + +// sets ref to 1 +uio_Repository * +uio_Repository_new(int flags) { + uio_Repository *result; + + result = uio_Repository_alloc(); + result->ref = 1; + result->flags = flags; + result->numMounts = 0; + result->mounts = uio_malloc(1 * sizeof (uio_MountInfo *)); + result->mounts[0] = NULL; + result->mountTree = uio_makeRootMountTree(); + return result; +} + +void +uio_Repository_unref(uio_Repository *repository) { + assert(repository->ref > 0); + repository->ref--; + if (repository->ref == 0) + uio_Repository_delete(repository); +} + +static uio_Repository * +uio_Repository_alloc(void) { + uio_Repository *result = uio_malloc(sizeof (uio_Repository)); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugAlloc(uio_Repository, (void *) result); +#endif + return result; +} + +static void +uio_Repository_delete(uio_Repository *repository) { + assert(repository->numMounts == 0); + uio_free(repository->mounts); + uio_MountTree_delete(repository->mountTree); + uio_Repository_free(repository); +} + +static void +uio_Repository_free(uio_Repository *repository) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugFree(uio_Repository, (void *) repository); +#endif + uio_free(repository); +} + + diff --git a/src/libs/uio/mount.h b/src/libs/uio/mount.h new file mode 100644 index 0000000..237f54b --- /dev/null +++ b/src/libs/uio/mount.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef LIBS_UIO_MOUNT_H_ +#define LIBS_UIO_MOUNT_H_ + + +typedef struct uio_Repository uio_Repository; +typedef struct uio_AutoMount uio_AutoMount; +#define uio_MOUNT_RDONLY (1 << 1) + +/* *** Internal definitions follow */ +#ifdef uio_INTERNAL + +#define uio_MOUNT_LOCATION_MASK (3 << 2) + +#include "uioport.h" +#include "fstypes.h" +#include "mounttree.h" +#include "match.h" + +struct uio_Repository { + int ref; /* reference counter */ + int flags; + int numMounts; + struct uio_MountInfo **mounts; + // first in the list is considered the top + // last entry is NULL + struct uio_MountTree *mountTree; +}; + +#define lockRepository(repository, prot) +#define unlockRepository(repository) + +uio_Repository *uio_Repository_new(int flags); +void uio_Repository_unref(uio_Repository *repository); +void uio_repositoryAddMount(uio_Repository *repository, + uio_MountInfo *mountInfo, uio_MountLocation location, + uio_MountInfo *relative); +void uio_repositoryRemoveMount(uio_Repository *repository, + uio_MountInfo *mountInfo); + + +#endif /* uio_INTERNAL */ + +#endif /* LIBS_UIO_MOUNT_H_ */ + diff --git a/src/libs/uio/mounttree.c b/src/libs/uio/mounttree.c new file mode 100644 index 0000000..eb5bdec --- /dev/null +++ b/src/libs/uio/mounttree.c @@ -0,0 +1,814 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <stdlib.h> +#include <string.h> +#ifdef DEBUG +# include <stdio.h> +#endif +#include <assert.h> + +#include "iointrn.h" +#include "uioport.h" +#include "mounttree.h" +#include "paths.h" +#include "types.h" +#include "mem.h" +#include "uioutils.h" +#ifdef uio_MEM_DEBUG +# include "memdebug.h" +#endif + +static uio_MountTree *uio_mountTreeAddMountInfoRecTree( + uio_Repository *repository, uio_MountTree *tree, + uio_MountInfo *mountInfo, const char *start, const char *end, + uio_PathComp *upComp, uio_MountLocation location, + const uio_MountInfo *relative); +static inline uio_MountTree *uio_mountTreeAddMountInfoRecTreeSub( + uio_Repository *repository, uio_MountTree **tree, + uio_MountInfo *mountInfo, const char *start, + const char *end, uio_MountLocation location, + const uio_MountInfo *relative); +static void uio_mountTreeAddMountInfoLocAll(uio_Repository *repository, + uio_MountTree *tree, uio_MountInfo *mountInfo, int depth, + uio_MountLocation location, const uio_MountInfo *relative); +static uio_MountTreeItem *uio_copyMountTreeItems(uio_MountTreeItem *item, + int extraDepth); +static void uio_addMountTreeItem(uio_Repository *repository, + uio_MountTreeItem **pLocs, uio_MountTreeItem *item, + uio_MountLocation location, const uio_MountInfo *relative); +static uio_MountTree *uio_mountTreeAddNewSubTree(uio_Repository *repository, + uio_MountTree *tree, const char *path, uio_MountInfo *mountInfo, + uio_PathComp *upComp, uio_MountLocation location, + const uio_MountInfo *relative); +static void uio_mountTreeAddSub(uio_MountTree *tree, uio_MountTree *sub); +static uio_MountTree * uio_splitMountTree(uio_MountTree **tree, uio_PathComp + *lastComp, int depth); +static void uio_mountTreeRemoveMountInfoRec(uio_MountTree *mountTree, + uio_MountInfo *mountInfo); +static void uio_printMount(FILE *outStream, const uio_MountInfo *mountInfo); + +static inline uio_MountTree * uio_MountTree_new(uio_MountTree *subTrees, + uio_MountTreeItem *pLocs, uio_MountTree *upTree, uio_PathComp + *comps, uio_PathComp *lastComp, uio_MountTree *next); +static inline uio_MountTreeItem *uio_MountTree_newItem( + uio_MountInfo *mountInfo, int depth, uio_MountTreeItem *next); + +static inline void uio_MountTreeItem_delete(uio_MountTreeItem *item); + +static inline uio_MountTree *uio_MountTree_alloc(void); +static inline uio_MountTreeItem *uio_MountTreeItem_alloc(void); +static inline uio_MountInfo *uio_MountInfo_alloc(void); + +static inline void uio_MountTree_free(uio_MountTree *mountTree); +static inline void uio_MountTreeItem_free(uio_MountTreeItem *mountTreeItem); +static inline void uio_MountInfo_free(uio_MountInfo *mountInfo); + + +// make the root mount Tree +uio_MountTree * +uio_makeRootMountTree(void) { + return uio_MountTree_new(NULL, NULL, NULL, NULL, NULL, NULL); +} + +// Add a MountInfo structure to a MountTree in the place pointed +// to by 'path'. +// returns the MountTree for the location at the end of the path +uio_MountTree * +uio_mountTreeAddMountInfo(uio_Repository *repository, uio_MountTree *mountTree, + uio_MountInfo *mountInfo, const char *path, uio_MountLocation location, + const uio_MountInfo *relative) { + const char *start, *end; + + getFirstPath0Component(path, &start, &end); + return uio_mountTreeAddMountInfoRecTree(repository, mountTree, mountInfo, + start, end, NULL, location, relative); +} + +// recursive helper for uio_mountTreeAddMountInfo +// returns the MountTree for the location at the end of the path +static uio_MountTree * +uio_mountTreeAddMountInfoRecTree(uio_Repository *repository, uio_MountTree *tree, + uio_MountInfo *mountInfo, const char *start, const char *end, + uio_PathComp *upComp, + uio_MountLocation location, const uio_MountInfo *relative) { + uio_MountTree **sub; + + if (*start == '\0') { + // End of the path. Put the MountInfo here and on all subtrees below + // this level. + uio_mountTreeAddMountInfoLocAll(repository, tree, mountInfo, 0, + location, relative); + return tree; + } + + // Check if sub trees match the path. + for (sub = &tree->subTrees; *sub != NULL; sub = &(*sub)->next) { + uio_MountTree *resTree; + + resTree = uio_mountTreeAddMountInfoRecTreeSub(repository, sub, + mountInfo, start, end, location, relative); + if (resTree != NULL) { + // handled + return resTree; + } + } + // No subtree found matching (part of) 'path'. + + // Need to add a new tree sub + return uio_mountTreeAddNewSubTree(repository, tree, start, mountInfo, + upComp, location, relative); +} + +// recursive helper for uio_mountTreeAddMountInfo +// Pre: *start != '\0' +// returns the MountTree for the location at the end of the path, if +// that falls within this tree. If not, returns NULL. +static inline uio_MountTree * +uio_mountTreeAddMountInfoRecTreeSub(uio_Repository *repository, + uio_MountTree **tree, uio_MountInfo *mountInfo, + const char *start, const char *end, uio_MountLocation location, + const uio_MountInfo *relative) { + uio_PathComp *comp, *lastComp; + int depth; + + comp = (*tree)->comps; + if (strncmp(comp->name, start, end - start) != 0 || + comp->name[end - start] != '\0') { + // first component does not match; this is not the correct subTree + return NULL; + } + + depth = 1; + // try to match all components of the directory path to this subTree. + while (1) { + getNextPath0Component(&start, &end); + lastComp = comp; + comp = comp->next; + + if (comp == NULL) + break; + + if (*start == '\0') { + // end of the path reached + // We need to split up the components and insert a new + // MountTree here. + uio_MountTree *newTree; + newTree = uio_splitMountTree(tree, lastComp, depth); + + // Add mountInfo to each of the MountTrees below newTree. + uio_mountTreeAddMountInfoLocAll(repository, newTree, mountInfo, 0, + location, relative); + + return newTree; + } + if (strncmp(comp->name, start, end - start) != 0 || + comp->name[end - start] != '\0') { + // Some, but not all components matched; we need to split + // up the components and add a new subTree here for the + // (non-matching) rest of the path. + uio_MountTree *newTree; + + newTree = uio_splitMountTree(tree, lastComp, depth); + + // A new Tree is added at the split-point. + return uio_mountTreeAddNewSubTree(repository, newTree, start, + mountInfo, lastComp, location, relative); + } + getNextPath0Component(&start, &end); + depth++; + } + + // All components matched. We can recurse to the next subdir. + return uio_mountTreeAddMountInfoRecTree(repository, *tree, mountInfo, + start, end, lastComp, location, relative); +} + +// Add a MountInfo struct 'mountInfo' to the pLocs fields of all subTrees +// starting with 'tree'. +// 'depth' is the distance to the MountTree where the MountInfo is located. +static void +uio_mountTreeAddMountInfoLocAll(uio_Repository *repository, uio_MountTree *tree, + uio_MountInfo *mountInfo, int depth, uio_MountLocation location, + const uio_MountInfo *relative) { + uio_MountTreeItem *newPLoc; + uio_MountTree *subTree; + int compCount; + + // Add a new PLoc to this mountTree + newPLoc = uio_MountTree_newItem(mountInfo, depth, NULL); + uio_addMountTreeItem(repository, &tree->pLocs, newPLoc, location, relative); + + // Recurse for subtrees + for (subTree = tree->subTrees; subTree != NULL; + subTree = subTree->next) { + compCount = uio_countPathComps(subTree->comps); + uio_mountTreeAddMountInfoLocAll( + repository, subTree, mountInfo, depth + compCount, + location, relative); + } +} + +// pre: repository->mounts is already updated +// pre: if location is uio_MOUNT_BELOW or uio_MOUNT_ABOVE, 'relative' +// exists in repository->mounts +static void +uio_addMountTreeItem(uio_Repository *repository, uio_MountTreeItem **pLocs, + uio_MountTreeItem *item, + uio_MountLocation location, const uio_MountInfo *relative) { + switch (location) { + case uio_MOUNT_TOP: + item->next = *pLocs; + *pLocs = item; + break; + case uio_MOUNT_BOTTOM: + while (*pLocs != NULL) + pLocs = &(*pLocs)->next; + item->next = NULL; + *pLocs = item; + break; + case uio_MOUNT_ABOVE: { + uio_MountInfo **mountInfo; + mountInfo = repository->mounts; + while (*mountInfo != relative) { + assert(*mountInfo != NULL); + if ((*pLocs)->mountInfo == *mountInfo) + pLocs = &(*pLocs)->next; + mountInfo++; + } + item->next = *pLocs; + *pLocs = item; + break; + } + case uio_MOUNT_BELOW: { + uio_MountInfo **mountInfo; + mountInfo = repository->mounts; + while (*mountInfo != relative) { + assert(*mountInfo != NULL); + if ((*pLocs)->mountInfo == *mountInfo) + pLocs = &(*pLocs)->next; + mountInfo++; + } + item->next = (*pLocs)->next; + (*pLocs)->next = item; + break; + } + default: + assert(false); + } +} + +// Copy a chain of MountTreeItems, but increase the depth by 'extraDepth'. +static uio_MountTreeItem * +uio_copyMountTreeItems(uio_MountTreeItem *item, int extraDepth) { + uio_MountTreeItem *result, **resPtr; + uio_MountTreeItem *newItem; + + resPtr = &result; + while (item != NULL) { + newItem = uio_MountTree_newItem( + item->mountInfo, item->depth + extraDepth, NULL); + *resPtr = newItem; + resPtr = &newItem->next; + item = item->next; + } + *resPtr = NULL; + return result; +} + +// add a new sub tree under a tree 'tree'. +// 'path' is the part leading up to the new tree and +// 'mountInfo' is the MountInfo structure to at there. +// 'upComp' points to the last path component that lead to 'tree'. +static uio_MountTree * +uio_mountTreeAddNewSubTree(uio_Repository *repository, uio_MountTree *tree, + const char *path, uio_MountInfo *mountInfo, uio_PathComp *upComp, + uio_MountLocation location, const uio_MountInfo *relative) { + uio_MountTreeItem *item, *items; + uio_MountTree *newTree; + uio_PathComp *compList, *lastComp; + int compCount; + + compList = uio_makePathComps(path, upComp); + compCount = uio_countPathComps(compList); + lastComp = uio_lastPathComp(compList); + item = uio_MountTree_newItem(mountInfo, 0, NULL); + item->next = NULL; + items = uio_copyMountTreeItems(tree->pLocs, compCount); + uio_addMountTreeItem(repository, &items, item, location, + relative); + newTree = uio_MountTree_new( + NULL /* subTrees */, + items /* pLocs */, + tree /* upTree */, + compList /* comps */, + lastComp /* lastComp */, + NULL /* next */); + uio_mountTreeAddSub(tree, newTree); + return newTree; +} + +// add a sub structure to the end of the 'subTrees' list of a tree. +static void +uio_mountTreeAddSub(uio_MountTree *tree, uio_MountTree *sub) { + uio_MountTree **subPtr; + + for (subPtr = &tree->subTrees; *subPtr != NULL; + subPtr = &(*subPtr)->next) { + // Nothing to do here. + } + *subPtr = sub; +} + +// Add a new MountTree structure in between two MountTrees. +// Tree points to the pointer for the tree in front of which the new +// tree needs to be placed (at depth 'depth'). +// 'lastComp' is the last pathComp of the part before the splitting point +// It returns the new MountTree. +static uio_MountTree * +uio_splitMountTree(uio_MountTree **tree, uio_PathComp *lastComp, int depth) { + uio_MountTree *newTree; + uio_MountTreeItem *items; + + items = uio_copyMountTreeItems((*tree)->upTree->pLocs, depth); + newTree = uio_MountTree_new( + *tree /* subTrees */, + items /* pLocs */, + (*tree)->upTree /* upTree */, + (*tree)->comps /* comps */, + lastComp /* lastComp */, + NULL /* next */); + (*tree)->upTree = newTree; + (*tree)->comps = lastComp->next; + lastComp->next = NULL; + *tree = newTree; + return newTree; +} + +void +uio_mountTreeRemoveMountInfo(uio_Repository *repository, + uio_MountTree *mountTree, uio_MountInfo *mountInfo) { + uio_MountTree **subTreePtr; + uio_MountTree *upTree; + + // If the tree has no sub-trees and it has the same items as the + // upTree, with 'mountInfo' added, then the tree is a dead end + // and can be removed entirely. + // First we handle the other case. + // Note that if the upTree has exactly one item less than the tree + // itself, these items must be the same, plus mountInfo for the + // tree itself, as each tree has at least the items of its upTree. + if (mountTree->upTree == NULL || mountTree->subTrees != NULL || + uio_mountTreeCountPLocs(mountTree) != + uio_mountTreeCountPLocs(mountTree->upTree) + 1) { + // We can't remove the tree itself. + // We need to remove the mountInfo from the tree, and all subTrees. + // Then we're done. + uio_mountTreeRemoveMountInfoRec(mountTree, mountInfo); + return; + } + + // mountTree itself can be removed. + // First remove the tree from the list of subtrees of the upTree. + subTreePtr = &mountTree->upTree->subTrees; + while (1) { + assert(*subTreePtr != NULL); + if (*subTreePtr == mountTree) + break; + subTreePtr = &(*subTreePtr)->next; + } + *subTreePtr = mountTree->next; + + // Save the upTree for later. + upTree = mountTree->upTree; + + // Remove the tree itself. + uio_MountTree_delete(mountTree); + + // The upTree itself could have become unnecessary now. + // This is the case when upTree now only has one subTree, and upTree + // and the subTree have the same items. + // Again, same item count implies same items. + if (upTree->subTrees == NULL || upTree->subTrees->next != NULL || + uio_mountTreeCountPLocs(upTree) != + uio_mountTreeCountPLocs(upTree->subTrees)) { + // upTree is still necessary. We're done. + return; + } + + // Merge upTree and upTree->subTrees. + // It would be easiest to keep upTree, and throw upTree->subTrees away, + // but that's not possible as external links point to upTree->subTrees. + // First merge the path components: + assert(upTree->subTrees->lastComp != NULL); + upTree->subTrees->lastComp->next = upTree->subTrees->comps; + upTree->subTrees->lastComp = upTree->lastComp; + upTree->subTrees->comps = upTree->comps; + // Now let the pointer that pointed to upTree, point to upTree->subTrees. + // Change upTree->next accordingly. + if (upTree->upTree == NULL) { + assert(repository->mountTree == upTree); + repository->mountTree = upTree->subTrees; + // upTree->subTrees->next is already NULL + } else { + uio_MountTree *next; + subTreePtr = &upTree->upTree->subTrees; + while (1) { + assert(*subTreePtr != NULL); + if (*subTreePtr == upTree) + break; + subTreePtr = &(*subTreePtr)->next; + } + next = (*subTreePtr)->next; + *subTreePtr = upTree->subTrees; + upTree->subTrees->next = next; + } + + // Now delete the tree itself + upTree->subTrees = NULL; + upTree->comps = NULL; + uio_MountTree_delete(upTree); +} + +// pre: mountInfo exists in mountTree->pLocs (and hence in pLocs for +// every sub-tree) +static void +uio_mountTreeRemoveMountInfoRec(uio_MountTree *mountTree, + uio_MountInfo *mountInfo) { + uio_MountTree *subTree; + uio_MountTreeItem **itemPtr, *item; + + // recurse for all subTrees + for (subTree = mountTree->subTrees; subTree != NULL; + subTree = subTree->next) + uio_mountTreeRemoveMountInfoRec(subTree, mountInfo); + + // Find the mount info in this tree. + itemPtr = &mountTree->pLocs; + while (1) { + assert(*itemPtr != NULL); + // We know an item with the specified mountInfo + // must be here somewhere. + if ((*itemPtr)->mountInfo == mountInfo) { + // Found it. + break; + } + itemPtr = &(*itemPtr)->next; + } + + item = *itemPtr; + *itemPtr = item->next; + uio_MountTreeItem_delete(item); +} + +// Count the number of pLocs in a tree that leads to. +int +uio_mountTreeCountPLocs(const uio_MountTree *tree) { + int count; + uio_MountTreeItem *item; + + count = 0; + for (item = tree->pLocs; item != NULL; item = item->next) + count++; + return count; +} + +// resTree may point to top +// pPath may point to path +void +uio_findMountTree(uio_MountTree *top, const char *path, + uio_MountTree **resTree, const char **pPath) { + const char *start, *end, *pathFromTree; + uio_MountTree *tree, *sub; + uio_PathComp *comp; + + getFirstPath0Component(path, &start, &end); + tree = top; + while(1) { + if (*start == '\0') { + *resTree = tree; + *pPath = start; + return; + } + + pathFromTree = start; + sub = tree->subTrees; + while(1) { + if (sub == NULL) { + // No matching sub Dirs found. So we report back the current + // dir. + *resTree = tree; + *pPath = pathFromTree; + return; + } + comp = sub->comps; + if (strncmp(comp->name, start, end - start) == 0 && + comp->name[end - start] == '\0') + break; + sub = sub->next; + } + // Found a Sub dir which matches at least partially. + + while (1) { + getNextPath0Component(&start, &end); + comp = comp->next; + if (comp == NULL) + break; + if (*start == '\0' || + strncmp(comp->name, start, end - start) != 0 || + comp->name[end - start] != '\0') { + // either the path ends here, or the path in the tree does. + // either way, the last Tree is the one we want. + *resTree = tree; + *pPath = pathFromTree; + return; + } + } + // all components matched until the next MountTree + tree = sub; + } +} + +// finds the path to the MountInfo associated with a mountTreeItem +// given a path to the 'item' itself. +// 'item' is the mountTreeItem +// 'endComp' is the last PathComp leading to 'item' +// 'start' is the start of the path to the item +char * +uio_mountTreeItemRestPath(const uio_MountTreeItem *item, + uio_PathComp *endComp, const char *path) { + int i; + const char *pathPtr; + + i = item->depth; + while (i--) + endComp = endComp->up; + + pathPtr = path; + if (endComp != NULL) { + while (1) { + pathPtr += endComp->nameLen; + endComp = endComp->up; + if (endComp == NULL) + break; + pathPtr++; + // for a '/' + } + } + if (*path == '/') { + // / at the beginning of the path + pathPtr++; + } + if (*pathPtr == '/') { + // / at the end of the path + pathPtr++; + } +// return (char *) pathPtr; + // gives warning +// return *((char **)((void *) &pathPtr)); + // not portable + return (char *) unconst((const void *) pathPtr); +} + +void +uio_printMountTree(FILE *outStream, const uio_MountTree *tree, int indent) { + uio_MountTree *sub; + uio_PathComp *comp; + + fprintf(outStream, "("); + uio_printMountTreeItems(outStream, tree->pLocs); + fprintf(outStream, ")\n"); + for (sub = tree->subTrees; sub != NULL; sub = sub->next) { + int newIndent; + + newIndent = indent; + fprintf(outStream, "%*s", indent, ""); + for (comp = sub->comps; comp != NULL; comp = comp->next) { + fprintf(outStream, "/%s", comp->name); + newIndent += 1 + comp->nameLen; + } + fprintf(outStream, " "); + newIndent += 1; + uio_printMountTree(outStream, sub, newIndent); + } +} + +void +uio_printMountTreeItem(FILE *outStream, const uio_MountTreeItem *item) { + uio_printMountInfo(outStream, item->mountInfo); + fprintf(outStream, ":%d", item->depth); +} + +void +uio_printMountTreeItems(FILE *outStream, const uio_MountTreeItem *item) { + if (!item) + return; + while(1) { + uio_printMountTreeItem(outStream, item); + item = item->next; + if (item == NULL) + break; + fprintf(outStream, ", "); + } +} + +void +uio_printPathToMountTree(FILE *outStream, const uio_MountTree *tree) { + if (tree->upTree == NULL) { + fprintf(outStream, "/"); + } else + uio_printPathToComp(outStream, tree->lastComp); +} + +void +uio_printMountInfo(FILE *outStream, const uio_MountInfo *mountInfo) { + uio_FileSystemInfo *fsInfo; + + fsInfo = uio_getFileSystemInfo(mountInfo->fsID); + fprintf(outStream, "%s:/%s", fsInfo->name, mountInfo->dirName); +} + +static void +uio_printMount(FILE *outStream, const uio_MountInfo *mountInfo) { + uio_FileSystemInfo *fsInfo; + + fsInfo = uio_getFileSystemInfo(mountInfo->fsID); + fprintf(outStream, "???:%s on ", mountInfo->dirName); + uio_printPathToMountTree(outStream, mountInfo->mountTree); + fprintf(outStream, " type %s (", fsInfo->name); + if (mountInfo->flags & uio_MOUNT_RDONLY) { + fprintf(outStream, "ro"); + } else + fprintf(outStream, "rw"); + fprintf(outStream, ")\n"); +} + +void +uio_printMounts(FILE *outStream, const uio_Repository *repository) { + int i; + + for (i = 0; i < repository->numMounts; i++) { + uio_printMount(outStream, repository->mounts[i]); + } +} + + +// *** uio_MountTree*** // + +static inline uio_MountTree * +uio_MountTree_new(uio_MountTree *subTrees, uio_MountTreeItem *pLocs, + uio_MountTree *upTree, uio_PathComp *comps, uio_PathComp *lastComp, + uio_MountTree *next) { + uio_MountTree *result; + + result = uio_MountTree_alloc(); + result->subTrees = subTrees; + result->pLocs = pLocs; + result->upTree = upTree; + result->comps = comps; + result->lastComp = lastComp; + result->next = next; + return result; +} + +void +uio_MountTree_delete(uio_MountTree *tree) { + uio_MountTree *subTree, *nextTree; + uio_MountTreeItem *item, *nextItem; + + subTree = tree->subTrees; + while (subTree != NULL) { + nextTree = subTree->next; + uio_MountTree_delete(subTree); + subTree = nextTree; + } + + item = tree->pLocs; + while (item != NULL) { + nextItem = item->next; + uio_MountTreeItem_delete(item); + item = nextItem; + } + + if (tree->comps != NULL) + uio_PathComp_delete(tree->comps); + + uio_MountTree_free(tree); +} + +static inline uio_MountTree * +uio_MountTree_alloc(void) { + uio_MountTree *result = uio_malloc(sizeof (uio_MountTree)); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugAlloc(uio_MountTree, (void *) result); +#endif + return result; +} + +static inline void +uio_MountTree_free(uio_MountTree *mountTree) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugFree(uio_MountTree, (void *) mountTree); +#endif + uio_free(mountTree); +} + + +// *** uio_MountTreeItem *** // + +static inline uio_MountTreeItem * +uio_MountTree_newItem(uio_MountInfo *mountInfo, int depth, + uio_MountTreeItem *next) { + uio_MountTreeItem *result; + + result = uio_MountTreeItem_alloc(); + result->mountInfo = mountInfo; + result->depth = depth; + result->next = next; + return result; +} + +static inline void +uio_MountTreeItem_delete(uio_MountTreeItem *item) { + uio_MountTreeItem_free(item); +} + +static inline uio_MountTreeItem * +uio_MountTreeItem_alloc(void) { + uio_MountTreeItem *result = uio_malloc(sizeof (uio_MountTreeItem)); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugAlloc(uio_MountTreeItem, (void *) result); +#endif + return result; +} + +static inline void +uio_MountTreeItem_free(uio_MountTreeItem *mountTreeItem) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugFree(uio_MountTreeItem, (void *) mountTreeItem); +#endif + uio_free(mountTreeItem); +} + + +// *** uio_MountInfo *** // + +uio_MountInfo * +uio_MountInfo_new(uio_FileSystemID fsID, uio_MountTree *mountTree, + uio_PDirHandle *pDirHandle, char *dirName, uio_AutoMount **autoMount, + uio_MountHandle *mountHandle, int flags) { + uio_MountInfo *result; + + result = uio_MountInfo_alloc(); + result->fsID = fsID; + result->mountTree = mountTree; + result->pDirHandle = pDirHandle; + result->dirName = dirName; + result->autoMount = autoMount; + result->mountHandle = mountHandle; + result->flags = flags; + return result; +} + +void +uio_MountInfo_delete(uio_MountInfo *mountInfo) { + uio_free(mountInfo->dirName); + uio_PDirHandle_unref(mountInfo->pDirHandle); + uio_MountInfo_free(mountInfo); +} + +static inline uio_MountInfo * +uio_MountInfo_alloc(void) { + uio_MountInfo *result = uio_malloc(sizeof (uio_MountInfo)); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugAlloc(uio_MountInfo, (void *) result); +#endif + return result; +} + +static inline void +uio_MountInfo_free(uio_MountInfo *mountInfo) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugFree(uio_MountInfo, (void *) mountInfo); +#endif + uio_free(mountInfo); +} + + diff --git a/src/libs/uio/mounttree.h b/src/libs/uio/mounttree.h new file mode 100644 index 0000000..b0041bc --- /dev/null +++ b/src/libs/uio/mounttree.h @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef LIBS_UIO_MOUNTTREE_H_ +#define LIBS_UIO_MOUNTTREE_H_ + +#include <stdio.h> +#include "mount.h" + +void uio_printMounts(FILE *outStream, const uio_Repository *repository); + +/* *** Internal definitions follow *** */ +#ifdef uio_INTERNAL + +#include <sys/types.h> + +typedef struct uio_MountTreeItem uio_MountTreeItem; +typedef struct uio_MountTree uio_MountTree; +typedef struct uio_MountInfo uio_MountInfo; + +#include "physical.h" +#include "types.h" +#include "uioport.h" +#include "paths.h" + + +/* + * A MountTree describes the relation between dirs (PRoot structures) + * mounted in a directory structure. + * A MountTree structure represents a node (a dir) in this directory + * structure. + * It describes what MountInfo structures apply in that dir and below (if + * not overrided in subnodes) (the 'pLocs' field). + * These path components are also linked together 'up'-wards (towards the + * root of the tree) by the 'up' field. + */ + +struct uio_MountTreeItem { + struct uio_MountInfo *mountInfo; + int depth; + // 'mountInfo->pDirHandle' and 'depth' together point to a + // location in a physical tree. An uio_pDirHandle alone can't be + // used as the directory might not exist. + // So pDirHandle points to the top dir that was mounted, and + // 'depth' indicates how many directory names of the path to + // this point in the Mount Tree need to be followed. + // Example: + // This MountTreeItem is somewhere in a tree /foo/bar/bla + // and depth = 1. Then this MountTreeItem points to + // /bla in the specified root. + struct uio_MountTreeItem *next; + // The next MountTreeItem in a MountTree +}; + +struct uio_MountTree { + struct uio_MountTree *subTrees; + // Trees for subdirs in this MountTree + struct uio_MountTreeItem *pLocs; + // The physical locations that have effect in this MountTree. + struct uio_MountTree *upTree; + // the MountTree that pointed to this MountTree + struct uio_PathComp *comps; + // the names of the path components that lead to the tree. + // Not necessary every PathComp is connected to a MountTree. + // If you have /foo and /foo/bar/zut mounted, then + // there are MountTrees for /, /foo and /foo/bar/zut, + // but there are PathComps for 'foo', 'bar' and 'zut'. + struct uio_PathComp *lastComp; + // The last PathComp of comps that pointed to this MountTree. + // This can be used to trace the path back to the top. + struct uio_MountTree *next; + // If this tree is a subTree of a tree, 'next' points to the + // next subTree of that tree. +}; + +/* + * A MountInfo structure describes how a physical structure was mounted. + * A physical structure can be used by several MountInfo structures. + */ +struct uio_MountInfo { + int flags; + /* Mount flags */ +# define uio_MOUNTINFO_RDONLY uio_MOUNT_RDONLY + uio_FileSystemID fsID; + char *dirName; + /* The path inside the mounted fs leading to pDirHandle */ + uio_PDirHandle *pDirHandle; + /* The pDirHandle belonging to this mount */ + uio_MountTree *mountTree; + /* The MountTree node for the mountpoint */ + uio_AutoMount **autoMount; + uio_MountHandle *mountHandle; +}; + + +/* + * Say we've got mounted (in order): + * Bla -> / + * Bar -> /foo/bar + * Foo -> /foo + * Zut -> /zut/123 + * Fop -> /zut/123 + * + * This will build a tree that looks like this: + * (the strings between brackets are the mounted filesystems that have effect + * in a dir, in order) + * + * / (Bar) + * foo (Foo, Bla) + * bar (Foo, Bar, Bla) + * zut/123 (Fop, Zut, Bla) + * + * The MountTree will look like: + * / = { + * sub = { + * /foo, + * /zut/123 + * }, + * pLocs = { + * BlaDir:/ (0) + * } + * } + * /foo = { + * sub = { + * /foo/bar + * }, + * pLocs = { + * BlaDir:/foo (1), + * FooDir:/ (0) + * } + * } + * /foo/bar = { + * sub = { }, + * pLocs = { + * BlaDir:/foo/bar (2), + * BarDir:/ (0), + * FooDir:/bar (1) + * } + * } + * /zut/123 = { + * sub = { }, + * pLocs = { + * FooDir:/ (0) + * ZutDir:/ (0) + * BlaDir:/zut/123 (2) + * } + * } + * + * 'BlaDir:/zut/123 (2)' means the pDirHandle is 'Bla', and the dir into + * that directory is '/zut/123', but as this is always a postfix of the + * path where we are, the number of dirs (the '(2)') is enough to store + * (apart from the pDirHandle). + */ + +uio_MountTree *uio_makeRootMountTree(void); +void uio_MountTree_delete(uio_MountTree *tree); +uio_MountTree *uio_mountTreeAddMountInfo(uio_Repository *repository, + uio_MountTree *mountTree, uio_MountInfo *mountInfo, const char *path, + uio_MountLocation location, const uio_MountInfo *relative); +void uio_mountTreeRemoveMountInfo(uio_Repository *repository, + uio_MountTree *mountTree, uio_MountInfo *mountInfo); +void uio_findMountTree(uio_MountTree *top, const char *path, + uio_MountTree **resTree, const char **pPath); +char *uio_mountTreeItemRestPath(const uio_MountTreeItem *item, + uio_PathComp *endComp, const char *path); +int uio_mountTreeCountPLocs(const uio_MountTree *tree); +uio_MountInfo *uio_MountInfo_new(uio_FileSystemID fsID, + uio_MountTree *mountTree, uio_PDirHandle *pDirHandle, + char *dirName, uio_AutoMount **autoMount, + uio_MountHandle *mountHandle, int flags); +void uio_MountInfo_delete(uio_MountInfo *mountInfo); +void uio_printMountTree(FILE *outStream, const uio_MountTree *tree, + int indent); +void uio_printMountTreeItem(FILE *outStream, const uio_MountTreeItem *item); +void uio_printMountTreeItems(FILE *outStream, const uio_MountTreeItem *item); +void uio_printPathToMountTree(FILE *outStream, const uio_MountTree *tree); +void uio_printMountInfo(FILE *outStream, const uio_MountInfo *mountInfo); + +static inline uio_bool +uio_mountInfoIsReadOnly(uio_MountInfo *mountInfo) { + return (mountInfo->flags & uio_MOUNTINFO_RDONLY) != 0; +} + +#endif /* uio_INTERNAL */ + +#endif /* LIBS_UIO_MOUNTTREE_H_ */ + diff --git a/src/libs/uio/paths.c b/src/libs/uio/paths.c new file mode 100644 index 0000000..f8411cd --- /dev/null +++ b/src/libs/uio/paths.c @@ -0,0 +1,602 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <assert.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +#include "paths.h" +#include "uioport.h" +#include "mem.h" + +static inline uio_PathComp *uio_PathComp_alloc(void); +static inline void uio_PathComp_free(uio_PathComp *pathComp); + +// gets the first dir component of a path +// sets '*start' to the start of the first component +// sets '*end' to the end of the first component +// if *start >= dirEnd, then the end has been reached. +void +getFirstPathComponent(const char *dir, const char *dirEnd, + const char **startComp, const char **endComp) { + assert(dir <= dirEnd); + *startComp = dir; + if (*startComp == dirEnd) { + *endComp = *startComp; + return; + } + *endComp = memchr(*startComp, '/', dirEnd - *startComp); + if (*endComp == NULL) + *endComp = dirEnd; +} + +// gets the first dir component of a path +// sets '*start' to the start of the first component +// sets '*end' to the end of the first component +// if **start == '\0', then the end has been reached. +void +getFirstPath0Component(const char *dir, + const char **startComp, const char **endComp) { + *startComp = dir; + if (**startComp == '\0') { + *endComp = *startComp; + return; + } + *endComp = strchr(*startComp, '/'); + if (*endComp == NULL) + *endComp = *startComp + strlen(*startComp); +} + +// gets the next component of a path +// '*start' should be set to the start of the last component +// '*end' should be set to the end of the last component +// '*start' will be set to the start of the next component +// '*end' will be set to the end of the next component +// if *start >= dirEnd, then the end has been reached. +void +getNextPathComponent(const char *dirEnd, + const char **startComp, const char **endComp) { + assert(*endComp <= dirEnd); + if (*endComp == dirEnd) { + *startComp = *endComp; + return; + } + assert(**endComp == '/'); + *startComp = *endComp + 1; + *endComp = memchr(*startComp, '/', dirEnd - *startComp); + if (*endComp == NULL) + *endComp = dirEnd; +} + +// gets the next component of a path +// '*start' should be set to the start of the last component +// '*end' should be set to the end of the last component +// '*start' will be set to the start of the next component +// '*end' will be set to the end of the next component +// if **start == '\0', then the end has been reached. +void +getNextPath0Component(const char **startComp, const char **endComp) { + if (**endComp == '\0') { + *startComp = *endComp; + return; + } + assert(**endComp == '/'); + *startComp = *endComp + 1; + *endComp = strchr(*startComp, '/'); + if (*endComp == NULL) + *endComp = *startComp + strlen(*startComp); +} + +// if *end == dir, the beginning has been reached +void +getLastPathComponent(const char *dir, const char *endDir, + const char **startComp, const char **endComp) { + *endComp = endDir; +// if (*(*endComp - 1) == '/') +// (*endComp)--; + *startComp = *endComp; + // TODO: use memrchr where available + while ((*startComp) - 1 >= dir && *(*startComp - 1) != '/') + (*startComp)--; +} + +// if *end == dir, the beginning has been reached +// pre: dir is \0-terminated +void +getLastPath0Component(const char *dir, + const char **startComp, const char **endComp) { + *endComp = dir + strlen(dir); +// if (*(*endComp - 1) == '/') +// (*endComp)--; + *startComp = *endComp; + // TODO: use memrchr where available + while ((*startComp) - 1 >= dir && *(*startComp - 1) != '/') + (*startComp)--; +} + +// if *end == dir, the beginning has been reached +void +getPreviousPathComponent(const char *dir, + const char **startComp, const char **endComp) { + if (*startComp == dir) { + *endComp = *startComp; + return; + } + *endComp = *startComp - 1; + *startComp = *endComp; + while ((*startComp) - 1 >= dir && *(*startComp - 1) != '/') + (*startComp)--; +} + +// Combine two parts of a paths into a new path. +// The new path starts with a '/' only when the first part does. +// The first path may (but doesn't have to) end on a '/', or may be empty. +// Pre: the second path doesn't start with a '/' +char * +joinPaths(const char *first, const char *second) { + char *result, *resPtr; + size_t firstLen, secondLen; + + if (first[0] == '\0') + return uio_strdup(second); + + firstLen = strlen(first); + if (first[firstLen - 1] == '/') + firstLen--; + secondLen = strlen(second); + result = uio_malloc(firstLen + secondLen + 2); + resPtr = result; + + memcpy(resPtr, first, firstLen); + resPtr += firstLen; + + *resPtr = '/'; + resPtr++; + + memcpy(resPtr, second, secondLen); + resPtr += secondLen; + + *resPtr = '\0'; + return result; +} + +// Combine two parts of a paths into a new path. +// The new path will always start with a '/'. +// The first path may (but doesn't have to) end on a '/', or may be empty. +// Pre: the second path doesn't start with a '/' +char * +joinPathsAbsolute(const char *first, const char *second) { + char *result, *resPtr; + size_t firstLen, secondLen; + + if (first[0] == '\0') { + secondLen = strlen(second); + result = uio_malloc(secondLen + 2); + result[0] = '/'; + memcpy(&result[1], second, secondLen); + result[secondLen + 1] = '\0'; + return result; + } + + firstLen = strlen(first); + if (first[firstLen - 1] == '/') + firstLen--; + secondLen = strlen(second); + result = uio_malloc(firstLen + secondLen + 3); + resPtr = result; + + *resPtr = '/'; + resPtr++; + + memcpy(resPtr, first, firstLen); + resPtr += firstLen; + + *resPtr = '/'; + resPtr++; + + memcpy(resPtr, second, secondLen); + resPtr += secondLen; + + *resPtr = '\0'; + return result; +} + +// Returns 'false' if +// - one of the path components is empty, or +// - one of the path components is ".", or +// - one of the path components is ".." +// and 'true' otherwise. +uio_bool +validPathName(const char *path, size_t len) { + const char *pathEnd; + const char *start, *end; + + pathEnd = path + len; + getFirstPathComponent(path, pathEnd, &start, &end); + while (start < pathEnd) { + if (end == start || (end - start == 1 && start[0] == '.') || + (end - start == 2 && start[0] == '.' && start[1] == '.')) + return false; + getNextPathComponent(pathEnd, &start, &end); + } + return true; +} + +// returns 0 if the path is not a valid UNC path. +// Does not skip trailing slashes. +size_t +uio_skipUNCServerShare(const char *inPath) { + const char *path = inPath; + + // Skip the initial two backslashes. + if (path[0] != '\\' || path[1] != '\\') + return (size_t) 0; + path += 2; + + // Skip the server part. + while (*path != '\\' && *path != '/') { + if (*path == '\0') + return (size_t) 0; + path++; + } + + // Skip the seperator. + path++; + + // Skip the share part. + while (*path != '\0' && *path != '\\' && *path != '/') + path++; + + return (size_t) (path - inPath); +} + +/** + * Find the server and share part of a Windows "Universal Naming Convention" + * path (a path of the form '\\server\share\path\file'). + * The path must contain at least a server and share path to be valid. + * The initial two slashes must be backslashes, the other slashes may each + * be either a forward slash or a backslash. + * + * @param[in] inPath The path to parse. + * @param[out] outPath Will contain a newly allocated string (to be + * freed using uio_free(), containing the server and share part + * of inPath, separated by a backslash, or NULL if 'inPath' was + * not a valid UNC path. + * @param[out] outLen If not NULL on entry, it will contain the string + * length of '*outPath', or 0 if 'inPath' was not a valid UNC path. + * + * @returns The number of characters to add to 'inPath' to get to the first + * path component past the server and share part, or 0 if 'inPath' + * was not a valid UNC path. + */ +size_t +uio_getUNCServerShare(const char *inPath, char **outPath, size_t *outLen) { + const char *ptr; + const char *server; + const char *serverEnd; + const char *share; + const char *shareEnd; + char *name; + char *nameEnd; + size_t nameLen; + + ptr = inPath; + + if (ptr[0] != '\\' || ptr[1] != '\\') + goto noMatch; + + // Parse the server part. + server = ptr + 2; + serverEnd = server; + for (;;) { + if (*serverEnd == '\0') + goto noMatch; + if (isPathDelimiter(*serverEnd)) + break; + serverEnd++; + } + if (serverEnd == server) + goto noMatch; + + // Parse the share part. + share = serverEnd + 1; + shareEnd = share; + while (*shareEnd != '\0') { + if (isPathDelimiter(*shareEnd)) + break; + serverEnd++; + } + + // Skip any trailing path delimiters. + ptr = shareEnd; + while (isPathDelimiter(*ptr)) + ptr++; + + // Allocate a new string and fill it. + nameLen = (serverEnd - server) + (shareEnd - share) + 3; + name = uio_malloc(nameLen + 1); + nameEnd = name; + *(nameEnd++) = '\\'; + *(nameEnd++) = '\\'; + memcpy(nameEnd, server, serverEnd - server); + *(nameEnd++) = '\\'; + memcpy(nameEnd, share, shareEnd - share); + *nameEnd = '\0'; + + *outPath = name; + if (outLen != NULL) + *outLen = nameLen; + return (size_t) (ptr - inPath); + +noMatch: + *outPath = NULL; + if (outLen != NULL) + *outLen = 0; + return (size_t) 0; +} + +// Decomposes a path into its components. +// If isAbsolute is not NULL, *isAbsolute will be set to true +// iff the path is absolute. +// As POSIX considers multiple consecutive slashes to be equivalent to +// a single slash, so will uio (but not in the "\\MACHINE\share" part +// of a Windows UNC path). +int +decomposePath(const char *path, uio_PathComp **pathComp, + uio_bool *isAbsolute) { + uio_PathComp *result; + uio_PathComp *last; + uio_PathComp **endResult = &result; + uio_bool absolute = false; + char *name; +#ifdef HAVE_UNC_PATHS + size_t nameLen; +#endif /* HAVE_UNC_PATHS */ + + if (path[0] == '\0') { + errno = ENOENT; + return -1; + } + + last = NULL; +#ifdef HAVE_UNC_PATHS + path += uio_getUNCServerShare(path, &name, &nameLen); + if (name != NULL) { + // UNC path + *endResult = uio_PathComp_new(name, nameLen, last); + last = *endResult; + endResult = &last->next; + + absolute = true; + } else +#endif /* HAVE_UNC_PATHS */ +#ifdef HAVE_DRIVE_LETTERS + if (isDriveLetter(path[0]) && path[1] == ':') { + // DOS/Windows drive letter. + if (path[2] != '\0' && !isPathDelimiter(path[2])) { + errno = ENOENT; + return -1; + } + name = uio_memdup0(path, 2); + *endResult = uio_PathComp_new(name, 2, last); + last = *endResult; + endResult = &last->next; + absolute = true; + } else +#endif /* HAVE_DRIVE_LETTERS */ + { + if (isPathDelimiter(*path)) { + absolute = true; + do { + path++; + } while (isPathDelimiter(*path)); + } + } + + while (*path != '\0') { + const char *start = path; + while (*path != '\0' && !isPathDelimiter(*path)) + path++; + + name = uio_memdup0(path, path - start); + *endResult = uio_PathComp_new(name, path - start, last); + last = *endResult; + endResult = &last->next; + + while (isPathDelimiter(*path)) + path++; + } + + *endResult = NULL; + *pathComp = result; + if (isAbsolute != NULL) + *isAbsolute = absolute; + return 0; +} + +// Pre: pathComp forms a valid path for the platform. +void +composePath(const uio_PathComp *pathComp, uio_bool absolute, + char **path, size_t *pathLen) { + size_t len; + const uio_PathComp *ptr; + char *result; + char *pathPtr; + + assert(pathComp != NULL); + + // First determine how much space is required. + len = 0; + if (absolute) + len++; + ptr = pathComp; + while (ptr != NULL) { + len += ptr->nameLen; + ptr = ptr->next; + } + + // Allocate the required space. + result = (char *) uio_malloc(len + 1); + + // Fill the path. + pathPtr = result; + ptr = pathComp; + if (absolute) { +#ifdef HAVE_UNC_PATHS + if (ptr->name[0] == '\\') { + // UNC path + assert(ptr->name[1] == '\\'); + // Nothing to do. + } else +#endif /* HAVE_UNC_PATHS */ +#ifdef HAVE_DRIVE_LETTERS + if (ptr->nameLen == 2 && ptr->name[1] == ':' + && isDriveLetter(ptr->name[0])) { + // Nothing to do. + } + else +#endif /* HAVE_DRIVE_LETTERS */ + { + *(pathPtr++) = '/'; + } + } + + for (;;) { + memcpy(pathPtr, ptr->name, ptr->nameLen); + pathPtr += ptr->nameLen; + + ptr = ptr->next; + if (ptr == NULL) + break; + + *(pathPtr++) = '/'; + } + + *path = result; + *pathLen = len; +} + + +// *** uio_PathComp *** // + +static inline uio_PathComp * +uio_PathComp_alloc(void) { + uio_PathComp *result = uio_malloc(sizeof (uio_PathComp)); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugAlloc(uio_PathComp, (void *) result); +#endif + return result; +} + +static inline void +uio_PathComp_free(uio_PathComp *pathComp) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugFree(uio_PathComp, (void *) pathComp); +#endif + uio_free(pathComp); +} + +// 'name' should be a null terminated string. It is stored in the PathComp, +// no copy is made. +// 'namelen' should be the length of 'name' +uio_PathComp * +uio_PathComp_new(char *name, size_t nameLen, uio_PathComp *upComp) { + uio_PathComp *result; + + result = uio_PathComp_alloc(); + result->name = name; + result->nameLen = nameLen; + result->up = upComp; + return result; +} + +void +uio_PathComp_delete(uio_PathComp *pathComp) { + uio_PathComp *next; + + while (pathComp != NULL) { + next = pathComp->next; + uio_free(pathComp->name); + uio_PathComp_free(pathComp); + pathComp = next; + } +} + +// Count the number of path components that 'comp' leads to. +int +uio_countPathComps(const uio_PathComp *comp) { + int count; + + count = 0; + for (; comp != NULL; comp = comp->next) + count++; + return count; +} + +uio_PathComp * +uio_lastPathComp(uio_PathComp *comp) { + if (comp == NULL) + return NULL; + + while (comp->next != NULL) + comp = comp->next; + return comp; +} + +// make a list of uio_PathComps from a path string +uio_PathComp * +uio_makePathComps(const char *path, uio_PathComp *upComp) { + const char *start, *end; + char *str; + uio_PathComp *result; + uio_PathComp **compPtr; // Where to put the next PathComp + + compPtr = &result; + getFirstPath0Component(path, &start, &end); + while (*start != '\0') { + str = uio_malloc(end - start + 1); + memcpy(str, start, end - start); + str[end - start] = '\0'; + + *compPtr = uio_PathComp_new(str, end - start, upComp); + upComp = *compPtr; + compPtr = &(*compPtr)->next; + getNextPath0Component(&start, &end); + } + *compPtr = NULL; + return result; +} + +void +uio_printPathComp(FILE *outStream, const uio_PathComp *comp) { + fprintf(outStream, "%s", comp->name); +} + +void +uio_printPathToComp(FILE *outStream, const uio_PathComp *comp) { + if (comp == NULL) + return; + uio_printPathToComp(outStream, comp->up); + fprintf(outStream, "/"); + uio_printPathComp(outStream, comp); +} + + diff --git a/src/libs/uio/paths.h b/src/libs/uio/paths.h new file mode 100644 index 0000000..bb5090d --- /dev/null +++ b/src/libs/uio/paths.h @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef LIBS_UIO_PATHS_H_ +#define LIBS_UIO_PATHS_H_ + +typedef struct uio_PathComp uio_PathComp; + +#include "types.h" +#include "uioport.h" + +#include <stdio.h> + +struct uio_PathComp { + char *name; + // The name of this path component, 0-terminated + size_t nameLen; + // The length of the 'name' field, for fast lookups. + struct uio_PathComp *next; + // Next component in the path. + struct uio_PathComp *up; + // Previous component in the path. +}; + +void getFirstPathComponent(const char *dir, const char *dirEnd, + const char **startComp, const char **endComp); +void getFirstPath0Component(const char *dir, const char **startComp, + const char **endComp); +void getNextPathComponent(const char *dirEnd, + const char **startComp, const char **endComp); +void getNextPath0Component(const char **startComp, const char **endComp); +void getLastPathComponent(const char *dir, const char *dirEnd, + const char **startComp, const char **endComp); +void getLastPath0Component(const char *dir, const char **startComp, + const char **endComp); +void getPreviousPathComponent(const char *dir, const char **startComp, + const char **endComp); +#define getPreviousPath0Component getPreviousPathComponent +char *joinPaths(const char *first, const char *second); +char *joinPathsAbsolute(const char *first, const char *second); + +uio_bool validPathName(const char *path, size_t len); +size_t uio_skipUNCServerShare(const char *inPath); +size_t uio_getUNCServerShare(const char *inPath, char **outPath, + size_t *outLen); + +#ifdef HAVE_DRIVE_LETTERS +static inline int +isDriveLetter(int c) +{ + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); +} +#endif /* HAVE_DRIVE_LETTERS */ + +static inline int +isPathDelimiter(int c) +{ +#ifdef BACKSLASH_IS_PATH_SEPARATOR + return c == '/' || c == '\\'; +#else + return c == '/'; +#endif /* BACKSLASH_IS_PATH_SEPARATOR */ +} + +int decomposePath(const char *path, uio_PathComp **pathComp, + uio_bool *isAbsolute); +void composePath(const uio_PathComp *pathComp, uio_bool absolute, + char **path, size_t *pathLen); +uio_PathComp *uio_PathComp_new(char *name, size_t nameLen, + uio_PathComp *upComp); +void uio_PathComp_delete(uio_PathComp *pathComp); +int uio_countPathComps(const uio_PathComp *comp); +uio_PathComp *uio_lastPathComp(uio_PathComp *comp); +uio_PathComp *uio_makePathComps(const char *path, uio_PathComp *upComp); +void uio_printPathComp(FILE *outStream, const uio_PathComp *comp); +void uio_printPathToComp(FILE *outStream, const uio_PathComp *comp); + +#endif /* LIBS_UIO_PATHS_H_ */ + diff --git a/src/libs/uio/physical.c b/src/libs/uio/physical.c new file mode 100644 index 0000000..18f96d1 --- /dev/null +++ b/src/libs/uio/physical.c @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <assert.h> +#include <errno.h> + +#include "physical.h" +#ifdef uio_MEM_DEBUG +# include "memdebug.h" +#endif +#include "uioport.h" + +static inline uio_PRoot *uio_PRoot_alloc(void); +static inline void uio_PRoot_free(uio_PRoot *pRoot); + +// NB: ref counter is not incremented +uio_PDirHandle * +uio_PRoot_getRootDirHandle(uio_PRoot *pRoot) { + return pRoot->rootDir; +} + +void +uio_PRoot_deletePRootExtra(uio_PRoot *pRoot) { + if (pRoot->extra == NULL) + return; + assert(pRoot->handler->deletePRootExtra != NULL); + pRoot->handler->deletePRootExtra(pRoot->extra); +} + +// note: sets refMount count to 1 +// set handlerRef count to 0 +uio_PRoot * +uio_PRoot_new(uio_PDirHandle *topDirHandle, + uio_FileSystemHandler *handler, uio_Handle *handle, + uio_PRootExtra extra, int flags) { + uio_PRoot *pRoot; + + pRoot = uio_PRoot_alloc(); + pRoot->mountRef = 1; + pRoot->handleRef = 0; + pRoot->rootDir = topDirHandle; + pRoot->handler = handler; + pRoot->handle = handle; + pRoot->extra = extra; + pRoot->flags = flags; +#ifdef uio_PROOT_HAVE_CLOSE_HANDLERS + pRoot->numCloseHandlers = 0; + pRoot->closeHandlers = NULL; +#endif + + return pRoot; +} + +#ifdef uio_PROOT_HAVE_CLOSE_HANDLERS +// Closehandlers code disabled. +// It was only meant for internal use, but I don't need it any more. +// Keeping it around for a while until I'm confident I won't need it in the +// future. + +void +uio_PRoot_addCloseHandler(uio_PRoot *pRoot, void (*fun)(void *), void *arg) { + pRoot->numCloseHandlers++; + pRoot->closeHandlers = uio_realloc(pRoot->closeHandlers, + pRoot->numCloseHandlers * sizeof (uio_PRoot_CloseHandler)); + pRoot->closeHandlers[pRoot->numCloseHandlers - 1].fun = fun; + pRoot->closeHandlers[pRoot->numCloseHandlers - 1].arg = arg; +} + +void +uio_PRoot_callCloseHandlers(uio_PRoot *pRoot) { + int i; + + i = pRoot->numCloseHandlers; + while (i--) { + uio_PRoot_CloseHandler *closeHandler; + + closeHandler = &pRoot->closeHandlers[i]; + (closeHandler->fun)(closeHandler->arg); + } +} + +void +uio_PRoot_removeCloseHandlers(uio_PRoot *pRoot) { + pRoot->numCloseHandlers = 0; + if (pRoot->closeHandlers != NULL) + uio_free(pRoot->closeHandlers); + pRoot->closeHandlers = NULL; +} +#endif + +static inline void +uio_PRoot_delete(uio_PRoot *pRoot) { +#ifdef uio_PROOT_HAVE_CLOSE_HANDLERS + uio_PRoot_callCloseHandlers(pRoot); + uio_PRoot_removeCloseHandlers(pRoot); +#endif + assert(pRoot->handler->umount != NULL); + pRoot->handler->umount(pRoot); + if (pRoot->handle) + uio_Handle_unref(pRoot->handle); + uio_PRoot_deletePRootExtra(pRoot); + uio_PRoot_free(pRoot); +} + +static inline uio_PRoot * +uio_PRoot_alloc(void) { + uio_PRoot *result = uio_malloc(sizeof (uio_PRoot)); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugAlloc(uio_PRoot, (void *) result); +#endif + return result; +} + +static inline void +uio_PRoot_free(uio_PRoot *pRoot) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugFree(uio_PRoot, (void *) pRoot); +#endif + uio_free(pRoot); +} + +void +uio_PRoot_refHandle(uio_PRoot *pRoot) { + pRoot->handleRef++; +} + +void +uio_PRoot_unrefHandle(uio_PRoot *pRoot) { + assert(pRoot->handleRef > 0); + pRoot->handleRef--; + if (pRoot->handleRef == 0 && pRoot->mountRef == 0) + uio_PRoot_delete(pRoot); +} + +void +uio_PRoot_refMount(uio_PRoot *pRoot) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugRef(uio_PRoot, (void *) pRoot); +#endif + pRoot->mountRef++; +} + +void +uio_PRoot_unrefMount(uio_PRoot *pRoot) { + assert(pRoot->mountRef > 0); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugUnref(uio_PRoot, (void *) pRoot); +#endif + pRoot->mountRef--; + if (pRoot->mountRef == 0 && pRoot->handleRef == 0) + uio_PRoot_delete(pRoot); +} + + diff --git a/src/libs/uio/physical.h b/src/libs/uio/physical.h new file mode 100644 index 0000000..71bc8e6 --- /dev/null +++ b/src/libs/uio/physical.h @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef LIBS_UIO_PHYSICAL_H_ +#define LIBS_UIO_PHYSICAL_H_ + +#ifndef uio_INTERNAL_PHYSICAL +typedef void *uio_PRootExtra; +typedef void *uio_NativeEntriesContext; +#endif + +// 'forward' declarations +typedef struct uio_PRoot uio_PRoot; +typedef struct uio_PRoot_CloseHandler uio_PRoot_CloseHandler; + + +#include "iointrn.h" +#include "uioport.h" +#include "fstypes.h" + + +/* + * Represents the root of a physical directory structure. + */ +struct uio_PRoot { + int mountRef; + /* Number of times this structure is referenced from + * mount trees. */ + int handleRef; + /* Number of file or directory handles that point inside the + * physical directory strucure of this pRoot. + */ + struct uio_PDirHandle *rootDir; + struct uio_FileSystemHandler *handler; + /* How to handle files in this PRoot tree */ + int flags; +# define uio_PRoot_NOCACHE 0x0002 + struct uio_Handle *handle; + /* The handle through which this PRoot is opened, + * this is NULL for the top PRoot */ + // TODO: move this to extra? +#ifdef uio_PROOT_HAVE_CLOSE_HANDLERS + int numCloseHandlers; + uio_PRoot_CloseHandler *closeHandlers; +#endif + uio_PRootExtra extra; + /* extra internal data for some filesystem types */ +}; + +#ifdef uio_PROOT_HAVE_CLOSE_HANDLERS +struct uio_PRoot_CloseHandler { + void (*fun)(void *); + void *arg; +}; +#endif + +void uio_PRoot_deletePRootExtra(uio_PRoot *pRoot); +uio_PRoot *uio_PRoot_new(uio_PDirHandle *topDirHandle, + uio_FileSystemHandler *handler, uio_Handle *handle, + uio_PRootExtra extra, int flags); +#ifdef uio_PROOT_HAVE_CLOSE_HANDLERS +void uio_PRoot_addCloseHandler(uio_PRoot *pRoot, void (*fun)(void *), + void *arg); +void uio_PRoot_callCloseHandlers(uio_PRoot *pRoot); +void uio_PRoot_removeCloseHandlers(uio_PRoot *pRoot); +#endif +uio_PDirHandle *uio_PRoot_getRootDirHandle(uio_PRoot *pRoot); +void uio_PRoot_refHandle(uio_PRoot *pRoot); +void uio_PRoot_unrefHandle(uio_PRoot *pRoot); +void uio_PRoot_refMount(uio_PRoot *pRoot); +void uio_PRoot_unrefMount(uio_PRoot *pRoot); + +#endif /* LIBS_UIO_PHYSICAL_H_ */ + + diff --git a/src/libs/uio/stdio/Makeinfo b/src/libs/uio/stdio/Makeinfo new file mode 100644 index 0000000..2d99c66 --- /dev/null +++ b/src/libs/uio/stdio/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="stdio.c" +uqm_HFILES="stdio.h" diff --git a/src/libs/uio/stdio/stdio.c b/src/libs/uio/stdio/stdio.c new file mode 100644 index 0000000..a4421d1 --- /dev/null +++ b/src/libs/uio/stdio/stdio.c @@ -0,0 +1,854 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +// The GPDir structures and functions are used for caching only. + +#ifdef __svr4__ +# define _POSIX_PTHREAD_SEMANTICS + // For the POSIX variant of readdir_r() +#endif + +#include "./stdio.h" + +#ifdef WIN32 +# include <io.h> +#else +# include <sys/stat.h> +# include <unistd.h> +# include <dirent.h> +#endif +#include <stdio.h> +#include <sys/types.h> +#include <errno.h> +#include <assert.h> +#include <fcntl.h> +#include <ctype.h> + +#include "../uioport.h" +#include "../paths.h" +#include "../mem.h" +#include "../physical.h" +#ifdef uio_MEM_DEBUG +# include "../memdebug.h" +#endif + +static inline uio_GPFile *stdio_addFile(uio_GPDir *gPDir, + const char *fileName); +static inline uio_GPDir *stdio_addDir(uio_GPDir *gPDir, const char *dirName); +static char *stdio_getPath(uio_GPDir *gPDir); +static stdio_GPDirData *stdio_GPDirData_new(char *name, char *cachedPath, + uio_GPDir *upDir); +static void stdio_GPDirData_delete(stdio_GPDirData *gPDirData); +static inline stdio_GPDirData *stdio_GPDirData_alloc(void); +static inline void stdio_GPDirData_free(stdio_GPDirData *gPDirData); +static inline stdio_EntriesIterator *stdio_EntriesIterator_alloc(void); +static inline void stdio_EntriesIterator_free( + stdio_EntriesIterator *iterator); + +uio_FileSystemHandler stdio_fileSystemHandler = { + /* .init = */ NULL, + /* .unInit = */ NULL, + /* .cleanup = */ NULL, + + /* .mount = */ stdio_mount, + /* .umount = */ uio_GPRoot_umount, + + /* .access = */ stdio_access, + /* .close = */ stdio_close, + /* .fstat = */ stdio_fstat, + /* .stat = */ stdio_stat, + /* .mkdir = */ stdio_mkdir, + /* .open = */ stdio_open, + /* .read = */ stdio_read, + /* .rename = */ stdio_rename, + /* .rmdir = */ stdio_rmdir, + /* .seek = */ stdio_seek, + /* .write = */ stdio_write, + /* .unlink = */ stdio_unlink, + + /* .openEntries = */ stdio_openEntries, + /* .readEntries = */ stdio_readEntries, + /* .closeEntries = */ stdio_closeEntries, + + /* .getPDirEntryHandle = */ stdio_getPDirEntryHandle, + /* .deletePRootExtra = */ uio_GPRoot_delete, + /* .deletePDirHandleExtra = */ uio_GPDirHandle_delete, + /* .deletePFileHandleExtra = */ uio_GPFileHandle_delete, +}; + +uio_GPRoot_Operations stdio_GPRootOperations = { + /* .fillGPDir = */ NULL, + /* .deleteGPRootExtra = */ NULL, + /* .deleteGPDirExtra = */ stdio_GPDirData_delete, + /* .deleteGPFileExtra = */ NULL, +}; + + +void +stdio_close(uio_Handle *handle) { + int fd; + int result; + + fd = handle->native->fd; + uio_free(handle->native); + + while (1) { + result = close(fd); + if (result == 0) + break; + if (errno != EINTR) { + fprintf(stderr, "Warning: Error while closing socket: %s\n", + strerror(errno)); + break; + } + } +} + +int +stdio_access(uio_PDirHandle *pDirHandle, const char *name, int mode) { + char *path; + int result; + + path = joinPaths(stdio_getPath(pDirHandle->extra), name); + if (path == NULL) { + // errno is set + return -1; + } + + result = access(path, mode); + if (result == -1) { + int savedErrno = errno; + uio_free(path); + errno = savedErrno; + return -1; + } + + uio_free(path); + return result; +} + +int +stdio_fstat(uio_Handle *handle, struct stat *statBuf) { + return fstat(handle->native->fd, statBuf); +} + +int +stdio_stat(uio_PDirHandle *pDirHandle, const char *name, + struct stat *statBuf) { + char *path; + int result; + + path = joinPaths(stdio_getPath(pDirHandle->extra), name); + if (path == NULL) { + // errno is set + return -1; + } + + result = stat(path, statBuf); + if (result == -1) { + int savedErrno = errno; + uio_free(path); + errno = savedErrno; + return -1; + } + + uio_free(path); + return result; +} + +uio_PDirHandle * +stdio_mkdir(uio_PDirHandle *pDirHandle, const char *name, mode_t mode) { + char *path; + uio_GPDir *newGPDir; + + path = joinPaths(stdio_getPath(pDirHandle->extra), name); + if (path == NULL) { + // errno is set + return NULL; + } + + if (MKDIR(path, mode) == -1) { + int savedErrno = errno; + uio_free(path); + errno = savedErrno; + return NULL; + } + uio_free(path); + + newGPDir = stdio_addDir(pDirHandle->extra, name); + uio_GPDir_ref(newGPDir); + return uio_PDirHandle_new(pDirHandle->pRoot, newGPDir); +} + +/* + * Function name: stdio_open + * Description: open a file from a normal stdio environment + * Arguments: gPDir - the dir where to open the file + * file - the name of the file to open + * flags - flags, as to stdio open() + * mode - mode, as to stdio open() + * Returns: handle, for use in functions accessing the opened file. + * If failed, errno is set and handle is -1. + */ +uio_Handle * +stdio_open(uio_PDirHandle *pDirHandle, const char *file, int flags, + mode_t mode) { + stdio_Handle *handle; + char *path; + int fd; + + path = joinPaths(stdio_getPath(pDirHandle->extra), file); + if (path == NULL) { + // errno is set + return NULL; + } + + fd = open(path, flags, mode); + if (fd == -1) { + int save_errno; + + save_errno = errno; + uio_free(path); + errno = save_errno; + return NULL; + } + uio_free(path); + +#if 0 + if (flags & O_CREAT) { + if (uio_GPDir_getGPDirEntry(pDirHandle->extra, file) == NULL) + stdio_addFile(pDirHandle->extra, file); + } +#endif + + handle = uio_malloc(sizeof (stdio_Handle)); + handle->fd = fd; + + return uio_Handle_new(pDirHandle->pRoot, handle, flags); +} + +ssize_t +stdio_read(uio_Handle *handle, void *buf, size_t count) { + return read(handle->native->fd, buf, count); +} + +int +stdio_rename(uio_PDirHandle *oldPDirHandle, const char *oldName, + uio_PDirHandle *newPDirHandle, const char *newName) { + char *newPath, *oldPath; + int result; + + oldPath = joinPaths(stdio_getPath(oldPDirHandle->extra), oldName); + if (oldPath == NULL) { + // errno is set + return -1; + } + + newPath = joinPaths(stdio_getPath(newPDirHandle->extra), newName); + if (newPath == NULL) { + // errno is set + uio_free(oldPath); + return -1; + } + + result = rename(oldPath, newPath); + if (result == -1) { + int savedErrno = errno; + uio_free(oldPath); + uio_free(newPath); + errno = savedErrno; + return -1; + } + + uio_free(oldPath); + uio_free(newPath); + + { + // update the GPDir structure + uio_GPDirEntry *entry; + + // TODO: add locking + entry = uio_GPDir_getGPDirEntry(oldPDirHandle->extra, oldName); + if (entry != NULL) { + uio_GPDirEntries_remove(oldPDirHandle->extra->entries, oldName); + uio_GPDirEntries_add(newPDirHandle->extra->entries, newName, + entry); + } + } + + return result; +} + +int +stdio_rmdir(uio_PDirHandle *pDirHandle, const char *name) { + char *path; + int result; + + path = joinPaths(stdio_getPath(pDirHandle->extra), name); + if (path == NULL) { + // errno is set + return -1; + } + + result = rmdir(path); + if (result == -1) { + int savedErrno = errno; + uio_free(path); + errno = savedErrno; + return -1; + } + + uio_free(path); + + uio_GPDir_removeSubDir(pDirHandle->extra, name); + + return result; +} + +off_t +stdio_seek(uio_Handle *handle, off_t offset, int whence) { + return lseek(handle->native->fd, offset, whence); +} + +ssize_t +stdio_write(uio_Handle *handle, const void *buf, size_t count) { + return write(handle->native->fd, buf, count); +} + +int +stdio_unlink(uio_PDirHandle *pDirHandle, const char *name) { + char *path; + int result; + + path = joinPaths(stdio_getPath(pDirHandle->extra), name); + if (path == NULL) { + // errno is set + return -1; + } + + result = unlink(path); + if (result == -1) { + int savedErrno = errno; + uio_free(path); + errno = savedErrno; + return -1; + } + + uio_free(path); + + uio_GPDir_removeFile(pDirHandle->extra, name); + + return result; +} + +uio_PDirEntryHandle * +stdio_getPDirEntryHandle(const uio_PDirHandle *pDirHandle, const char *name) { + uio_PDirEntryHandle *result; + const char *pathUpTo; + char *path; + struct stat statBuf; +#ifdef HAVE_DRIVE_LETTERS + char driveName[3]; +#endif /* HAVE_DRIVE_LETTERS */ + +#if defined(HAVE_DRIVE_LETTERS) || defined(HAVE_UNC_PATHS) + if (pDirHandle->extra->extra->upDir == NULL) { + // Top dir. Contains only drive letters and UNC \\server\share + // parts. +#ifdef HAVE_DRIVE_LETTERS + if (isDriveLetter(name[0]) && name[1] == ':' && name[2] == '\0') { + driveName[0] = tolower(name[0]); + driveName[1] = ':'; + driveName[2] = '\0'; + name = driveName; + } else +#endif /* HAVE_DRIVE_LETTERS */ +#ifdef HAVE_UNC_PATHS + { + size_t uncLen; + + uncLen = uio_skipUNCServerShare(name); + if (name[uncLen] != '\0') { + // 'name' contains neither a drive letter, nor the + // first part of a UNC path. + return NULL; + } + } +#else /* !defined(HAVE_UNC_PATHS) */ + { + // Make sure that there is an 'else' case if HAVE_DRIVE_LETTERS + // is defined. + } +#endif /* HAVE_UNC_PATHS */ + } +#endif /* defined(HAVE_DRIVE_LETTERS) || defined(HAVE_UNC_PATHS) */ + + result = uio_GPDir_getPDirEntryHandle(pDirHandle, name); + if (result != NULL) + return result; + +#if defined(HAVE_DRIVE_LETTERS) || defined(HAVE_UNC_PATHS) + if (pDirHandle->extra->extra->upDir == NULL) { + // Need to create a 'directory' for the drive letter or UNC + // "\\server\share" part. + // It's no problem if we happen to create a dir for a non-existing + // drive. It should just produce an empty dir. + uio_GPDir *gPDir; + + gPDir = stdio_addDir(pDirHandle->extra, name); + uio_GPDir_ref(gPDir); + return (uio_PDirEntryHandle *) uio_PDirHandle_new( + pDirHandle->pRoot, gPDir); + } +#endif /* defined(HAVE_DRIVE_LETTERS) || defined(HAVE_UNC_PATHS) */ + + pathUpTo = stdio_getPath(pDirHandle->extra); + if (pathUpTo == NULL) { + // errno is set + return NULL; + } + path = joinPaths(pathUpTo, name); + if (path == NULL) { + // errno is set + return NULL; + } + + if (stat(path, &statBuf) == -1) { +#ifdef __SYMBIAN32__ + // XXX: HACK: If we don't have access to a directory, we can still + // have access to the underlying entries. We don't actually know + // whether the entry is a directory, but I know of no way to find + // out. We just pretend that it is; worst case, a file which we can't + // access shows up as a directory which we can't access. + if (errno == EACCES) { + statBuf.st_mode = S_IFDIR; + // Fake a directory; the other fields of the stat + // structure are unused. + } else +#endif + { + // errno is set. + int savedErrno = errno; + uio_free(path); + errno = savedErrno; + return NULL; + } + } + uio_free(path); + + if (S_ISREG(statBuf.st_mode)) { + uio_GPFile *gPFile; + + gPFile = stdio_addFile(pDirHandle->extra, name); + uio_GPFile_ref(gPFile); + return (uio_PDirEntryHandle *) uio_PFileHandle_new( + pDirHandle->pRoot, gPFile); + } else if (S_ISDIR(statBuf.st_mode)) { + uio_GPDir *gPDir; + + gPDir = stdio_addDir(pDirHandle->extra, name); + uio_GPDir_ref(gPDir); + return (uio_PDirEntryHandle *) uio_PDirHandle_new( + pDirHandle->pRoot, gPDir); + } else { +#ifdef DEBUG + fprintf(stderr, "Warning: Attempt to access '%s' from '%s', " + "which is not a regular file, nor a directory.\n", name, + pathUpTo); +#endif + return NULL; + } +} + +uio_PRoot * +stdio_mount(uio_Handle *handle, int flags) { + uio_PRoot *result; + stdio_GPDirData *extra; + + assert (handle == NULL); + extra = stdio_GPDirData_new( + uio_strdup("") /* name */, +#if defined(HAVE_DRIVE_LETTERS) || defined(HAVE_UNC_PATHS) + // Full paths start with a drive letter or \\server\share + uio_strdup("") /* cached path */, +#else + uio_strdup("/") /* cached path */, +#endif /* HAVE_DRIVE_LETTERS */ + NULL /* parent dir */); + + result = uio_GPRoot_makePRoot( + uio_getFileSystemHandler(uio_FSTYPE_STDIO), flags, + &stdio_GPRootOperations, NULL, uio_GPRoot_PERSISTENT, + handle, extra, 0); + + uio_GPDir_setComplete(result->rootDir->extra, true); + + return result; +} + +#ifdef WIN32 +stdio_EntriesIterator * +stdio_openEntries(uio_PDirHandle *pDirHandle) { + const char *dirPath; + char path[PATH_MAX]; + char *pathEnd; + size_t dirPathLen; + stdio_EntriesIterator *iterator; + +// uio_GPDir_access(pDirHandle->extra); + + dirPath = stdio_getPath(pDirHandle->extra); + if (dirPath == NULL) { + // errno is set + return NULL; + } + + dirPathLen = strlen(dirPath); + if (dirPathLen > PATH_MAX - 3) { + // dirPath ++ '/' ++ '*' ++ '\0' + errno = ENAMETOOLONG; + return NULL; + } + memcpy(path, dirPath, dirPathLen); + pathEnd = path + dirPathLen; + pathEnd[0] = '/'; + pathEnd[1] = '*'; + pathEnd[2] = '\0'; + iterator = stdio_EntriesIterator_new(0); + iterator->dirHandle = _findfirst(path, &iterator->findData); + if (iterator->dirHandle == 1) { + if (errno != ENOENT) { + stdio_EntriesIterator_delete(iterator); + return NULL; + } + iterator->status = 1; + } else + iterator->status = 0; + return iterator; +} +#endif + +#ifndef WIN32 +stdio_EntriesIterator * +stdio_openEntries(uio_PDirHandle *pDirHandle) { + const char *dirPath; + DIR *dirHandle; + stdio_EntriesIterator *result; + +// uio_GPDir_access(pDirHandle->extra); + + dirPath = stdio_getPath(pDirHandle->extra); + if (dirPath == NULL) { + // errno is set + return NULL; + } + + dirHandle = opendir(dirPath); + if (dirHandle == NULL) { + // errno is set; + return NULL; + } + + result = stdio_EntriesIterator_new(dirHandle); + result->status = readdir_r(dirHandle, result->direntBuffer, + &result->entry); +#ifndef WIN32 +# ifdef DEBUG + if (result->status != 0) { + fprintf(stderr, "Warning: readdir_r() failed: %s\n", + strerror(result->status)); + } +# endif +#endif + return result; +} +#endif + +// the start of 'buf' will be filled with pointers to strings +// those strings are stored elsewhere in buf. +// The function returns the number of strings passed along, or -1 for error. +// If there are no more entries, the last pointer will be NULL. +// (this pointer counts towards the return value) +int +stdio_readEntries(stdio_EntriesIterator **iteratorPtr, + char *buf, size_t len) { + char *end; + char **start; + int num; + const char *name; + size_t nameLen; + stdio_EntriesIterator *iterator; + + iterator = *iteratorPtr; + + // buf will be filled like this: + // The start of buf will contain pointers to char *, + // the end will contain the actual char[] that those pointers point to. + // The start and the end will grow towards eachother. + start = (char **) buf; + end = buf + len; + num = 0; +#ifdef WIN32 + for (; iterator->status == 0; + iterator->status = _findnext(iterator->dirHandle, + &iterator->findData)) +#else + for (; iterator->status == 0 && iterator->entry != NULL; + iterator->status = readdir_r(iterator->dirHandle, + iterator->direntBuffer, &iterator->entry)) +#endif + { +#ifdef WIN32 + name = iterator->findData.name; +#else + name = iterator->entry->d_name; +#endif + if (name[0] == '.' && + (name[1] == '\0' || + (name[1] == '.' && name[2] == '\0'))) { + // skip directories "." and ".." + continue; + } + nameLen = strlen(name) + 1; + + // Does this work with systems that need memory access to be + // aligned on a certain number of bytes? + if ((size_t) (sizeof (char *) + nameLen) > + (size_t) (end - (char *) start)) { + // Not enough room to fit the pointer (at the beginning) and + // the string (at the end). + return num; + } + end -= nameLen; + memcpy(end, name, nameLen); + *start = end; + start++; + num++; + } +#ifndef WIN32 +# ifdef DEBUG + if (iterator->status != 0) { + fprintf(stderr, "Warning: readdir_r() failed: %s\n", + strerror(iterator->status)); + } +# endif +#endif + if (sizeof (char *) > (size_t) (end - (char *) start)) { + // not enough room to fit the NULL pointer. + // It will have to be reported seperately the next time. + return num; + } + *start = NULL; + num++; + return num; +} + +void +stdio_closeEntries(stdio_EntriesIterator *iterator) { +#ifdef WIN32 + _findclose(iterator->dirHandle); +#else + closedir(iterator->dirHandle); +#endif + stdio_EntriesIterator_delete(iterator); +} + +#ifdef WIN32 +stdio_EntriesIterator * +stdio_EntriesIterator_new(long dirHandle) { + stdio_EntriesIterator *result; + + result = stdio_EntriesIterator_alloc(); + result->dirHandle = dirHandle; + return result; +} +#else +stdio_EntriesIterator * +stdio_EntriesIterator_new(DIR *dirHandle) { + stdio_EntriesIterator *result; + size_t bufferSize; + + result = stdio_EntriesIterator_alloc(); + result->dirHandle = dirHandle; + + // Linux's and FreeBSD's struct dirent are defined with a + // maximum d_name field (NAME_MAX). + // However, POSIX doesn't require this, and in fact + // at least QNX defines struct dirent with an empty d_name field. + // Solaris defineds it with a d_name field of length 1. + // This should take care of it: + bufferSize = sizeof (struct dirent) + - sizeof (((struct dirent *) 0)->d_name) + (NAME_MAX + 1); + // Take the length of the dirent structure as it is defined, + // subtract the length of the d_name field, and add the length + // of the maximum length d_name field (NAME_MAX plus 1 for + // the '\0'). + // XXX: Could this give problems with weird alignments? + result->direntBuffer = uio_malloc(bufferSize); + return result; +} +#endif + +void +stdio_EntriesIterator_delete(stdio_EntriesIterator *iterator) { +#ifndef WIN32 + uio_free(iterator->direntBuffer); +#endif + stdio_EntriesIterator_free(iterator); +} + +static inline stdio_EntriesIterator * +stdio_EntriesIterator_alloc(void) { + return uio_malloc(sizeof (stdio_EntriesIterator)); +} + +static inline void +stdio_EntriesIterator_free(stdio_EntriesIterator *iterator) { + uio_free(iterator); +} + +static inline uio_GPFile * +stdio_addFile(uio_GPDir *gPDir, const char *fileName) { + uio_GPFile *file; + + file = uio_GPFile_new(gPDir->pRoot, NULL, + uio_gPFileFlagsFromPRootFlags(gPDir->pRoot->flags)); + uio_GPDir_addFile(gPDir, fileName, file); + return file; +} + +// called by fillGPDir when a subdir is found +static inline uio_GPDir * +stdio_addDir(uio_GPDir *gPDir, const char *dirName) { + uio_GPDir *subDir; + + subDir = uio_GPDir_prepareSubDir(gPDir, dirName); + if (subDir->extra == NULL) { + // It's a new dir, we'll need to add our own data. + uio_GPDir_ref(gPDir); + subDir->extra = stdio_GPDirData_new(uio_strdup(dirName), + NULL, gPDir); + uio_GPDir_setComplete(subDir, true); + // fillPDir should not be called. + } + uio_GPDir_commitSubDir(gPDir, dirName, subDir); + return subDir; +} + +// returns a pointer to gPDir->extra->cachedPath +// pointer should not be stored, the memory it points to can be freed +// lateron. TODO: not threadsafe. +static char * +stdio_getPath(uio_GPDir *gPDir) { + if (gPDir->extra->cachedPath == NULL) { + char *upPath; + size_t upPathLen, nameLen; + + if (gPDir->extra->upDir == NULL) { +#if defined(HAVE_DRIVE_LETTERS) || defined(HAVE_UNC_PATHS) + // Drive letter or UNC \\server\share still needs to follow. + gPDir->extra->cachedPath = uio_malloc(1); + gPDir->extra->cachedPath[0] = '\0'; +#else + gPDir->extra->cachedPath = uio_malloc(2); + gPDir->extra->cachedPath[0] = '/'; + gPDir->extra->cachedPath[1] = '\0'; +#endif /* defined(HAVE_DRIVE_LETTERS) || defined(HAVE_UNC_PATHS) */ + return gPDir->extra->cachedPath; + } + + upPath = stdio_getPath(gPDir->extra->upDir); + if (upPath == NULL) { + // errno is set + return NULL; + } + +#if defined(HAVE_DRIVE_LETTERS) || defined(HAVE_UNC_PATHS) + if (upPath[0] == '\0') { + // The up dir is the root dir. Directly below the root dir are + // only dirs for drive letters and UNC \\share\server parts. + // No '/' needs to be attached. + gPDir->extra->cachedPath = uio_strdup(gPDir->extra->name); + return gPDir->extra->cachedPath; + } +#endif /* defined(HAVE_DRIVE_LETTERS) || defined(HAVE_UNC_PATHS) */ + upPathLen = strlen(upPath); +#if !defined(HAVE_DRIVE_LETTERS) && !defined(HAVE_UNC_PATHS) + if (upPath[upPathLen - 1] == '/') { + // should only happen for "/" + upPathLen--; + } +#endif /* !defined(HAVE_DRIVE_LETTERS) && !defined(HAVE_UNC_PATHS) */ + nameLen = strlen(gPDir->extra->name); + if (upPathLen + nameLen + 1 >= PATH_MAX) { + errno = ENAMETOOLONG; + return NULL; + } + gPDir->extra->cachedPath = uio_malloc(upPathLen + nameLen + 2); + memcpy(gPDir->extra->cachedPath, upPath, upPathLen); + gPDir->extra->cachedPath[upPathLen] = '/'; + memcpy(gPDir->extra->cachedPath + upPathLen + 1, + gPDir->extra->name, nameLen); + gPDir->extra->cachedPath[upPathLen + nameLen + 1] = '\0'; + } + return gPDir->extra->cachedPath; +} + +static stdio_GPDirData * +stdio_GPDirData_new(char *name, char *cachedPath, uio_GPDir *upDir) { + stdio_GPDirData *result; + + result = stdio_GPDirData_alloc(); + result->name = name; + result->cachedPath = cachedPath; + result->upDir = upDir; + return result; +} + +static void +stdio_GPDirData_delete(stdio_GPDirData *gPDirData) { + if (gPDirData->upDir != NULL) + uio_GPDir_unref(gPDirData->upDir); + stdio_GPDirData_free(gPDirData); +} + +static inline stdio_GPDirData * +stdio_GPDirData_alloc(void) { + stdio_GPDirData *result; + + result = uio_malloc(sizeof (stdio_GPDirData)); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugAlloc(stdio_GPDirData, (void *) result); +#endif + return result; +} + +static inline void +stdio_GPDirData_free(stdio_GPDirData *gPDirData) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugFree(stdio_GPDirData, (void *) gPDirData); +#endif + uio_free(gPDirData->name); + if (gPDirData->cachedPath != NULL) + uio_free(gPDirData->cachedPath); + uio_free(gPDirData); +} + + diff --git a/src/libs/uio/stdio/stdio.h b/src/libs/uio/stdio/stdio.h new file mode 100644 index 0000000..914a1d7 --- /dev/null +++ b/src/libs/uio/stdio/stdio.h @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +typedef struct stdio_Handle *uio_NativeHandle; +typedef void *uio_GPRootExtra; +typedef struct stdio_GPDirData *uio_GPDirExtra; +typedef void *uio_GPFileExtra; +typedef struct stdio_EntriesIterator stdio_EntriesIterator; +typedef stdio_EntriesIterator *uio_NativeEntriesContext; + + +#define uio_INTERNAL_PHYSICAL + +#include "../gphys.h" +#include "../iointrn.h" +#include "../uioport.h" +#include "../fstypes.h" +#include "../physical.h" + +#include <sys/stat.h> +#ifndef WIN32 +# include <dirent.h> +#endif + + +typedef struct stdio_GPDirData { + // The reason that names are stored is that in the system filesystem + // you need names to refer to files and directories. + // (you could keep a file descriptor to each one, but that would + // mean a lot of open file descriptors, and for some it won't even + // be enough). + // This is not needed for all filesystems; therefor this info is not + // in uio_GPDir itself. + // The reasons for including upDir here are similar. + char *name; + char *cachedPath; + uio_GPDir *upDir; +} stdio_GPDirData; + +typedef struct stdio_Handle { + int fd; +} stdio_Handle; + +#ifdef WIN32 +struct stdio_EntriesIterator { + long dirHandle; + struct _finddata_t findData; + int status; +}; +#endif + +#ifndef WIN32 +struct stdio_EntriesIterator { + DIR *dirHandle; + struct dirent *entry; + struct dirent *direntBuffer; + int status; +}; +#endif + + +uio_PRoot *stdio_mount(uio_Handle *handle, int flags); +int stdio_umount(uio_PRoot *); +uio_PDirHandle *stdio_mkdir(uio_PDirHandle *pDirHandle, const char *name, + mode_t mode); +uio_Handle *stdio_open(uio_PDirHandle *pDirHandle, const char *file, int flags, + mode_t mode); +void stdio_close(uio_Handle *handle); +int zip_access(uio_PDirHandle *pDirHandle, const char *name, int mode); +int stdio_access(uio_PDirHandle *pDirHandle, const char *name, int mode); +int stdio_fstat(uio_Handle *handle, struct stat *statBuf); +int stdio_stat(uio_PDirHandle *pDirHandle, const char *name, + struct stat *statBuf); +ssize_t stdio_read(uio_Handle *handle, void *buf, size_t count); +int stdio_rename(uio_PDirHandle *oldPDirHandle, const char *oldName, + uio_PDirHandle *newPDirHandle, const char *newName); +int stdio_rmdir(uio_PDirHandle *pDirHandle, const char *name); +off_t stdio_seek(uio_Handle *handle, off_t offset, int whence); +ssize_t stdio_write(uio_Handle *handle, const void *buf, size_t count); +int stdio_unlink(uio_PDirHandle *pDirHandle, const char *name); + +stdio_EntriesIterator *stdio_openEntries(uio_PDirHandle *pDirHandle); +int stdio_readEntries(stdio_EntriesIterator **iterator, + char *buf, size_t len); +void stdio_closeEntries(stdio_EntriesIterator *iterator); +#ifdef WIN32 +stdio_EntriesIterator *stdio_EntriesIterator_new(long dirHandle); +#else +stdio_EntriesIterator *stdio_EntriesIterator_new(DIR *dirHandle); +#endif +void stdio_EntriesIterator_delete(stdio_EntriesIterator *iterator); +uio_PDirEntryHandle *stdio_getPDirEntryHandle( + const uio_PDirHandle *pDirHandle, const char *name); + diff --git a/src/libs/uio/types.h b/src/libs/uio/types.h new file mode 100644 index 0000000..b92f7a4 --- /dev/null +++ b/src/libs/uio/types.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _uio_TYPES_H +#define _uio_TYPES_H + +#include "config.h" + +// ISO C99 compatible boolean types. The ISO C99 standard defines: +// - An object declared as type _Bool, large enough to store the values 0 +// and 1, the rank of which is less than the rank of all other standard +// integer types. +// - A macro "bool", which expands to "_Bool". +// - A macro "true", which expands to the integer constant 1, suitable for +// use in #if preprocessing directives. +// - A macro "false", which expands to the integer constant 0, suitable for +// use in #if preprocessing directives. +// - A macro "__bool_true_false_are_defined", which expands to the integer +// constant 1, suitable for use in #if preprocessing directives. +#ifndef __bool_true_false_are_defined +#undef bool +#undef false +#undef true +#ifndef HAVE__BOOL +typedef unsigned char _Bool; +#endif /* HAVE_BOOL */ +#define bool _Bool +#define true 1 +#define false 0 +#define __bool_true_false_are_defined +#endif /* __bool_true_false_are_defined */ + +typedef bool uio_bool; + +typedef unsigned char uio_uint8; +typedef signed char uio_sint8; +typedef unsigned short uio_uint16; +typedef signed short uio_sint16; +typedef unsigned int uio_uint32; +typedef signed int uio_sint32; + +typedef unsigned long uio_uintptr; + // Needs to be adapted for 64 bits systems + +#endif /* _uio_TYPES_H */ + + diff --git a/src/libs/uio/uioport.h b/src/libs/uio/uioport.h new file mode 100644 index 0000000..69d7e8e --- /dev/null +++ b/src/libs/uio/uioport.h @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef LIBS_UIO_UIOPORT_H_ +#define LIBS_UIO_UIOPORT_H_ + +#ifdef _MSC_VER +# include <io.h> +#else +# include <unistd.h> +#endif + + +// Compilation related +#ifndef inline +# ifdef _MSC_VER +# define inline __inline +# else +# define inline __inline__ +# endif +#endif + +// Paths +#ifdef WIN32 +# include <stdlib.h> +# define PATH_MAX _MAX_PATH +# define NAME_MAX _MAX_FNAME + // _MAX_DIR and FILENAME_MAX could also be candidates. + // If anyone can tell me which one matches NAME_MAX, please + // let me know. +#elif defined(_WIN32_WCE) +# include <sys/syslimits.h> +#else +# include <limits.h> + /* PATH_MAX is per POSIX defined in <limits.h>, but: + * "A definition of one of the values from Table 2.6 shall bea + * omitted from <limits.h> on specific implementations where the + * corresponding value is equal to or greater than the + * stated minimum, but where the value can vary depending + * on the file to which it is applied. The actual value supported + * for a specific pathname shall be provided by the pathconf() + * function." + * _POSIX_NAME_MAX will provide a minimum (14). + * This is relevant (at least) for Solaris. + */ +# ifndef NAME_MAX +# define NAME_MAX _POSIX_NAME_MAX +# endif +#endif + +// Variations in path handling +#if defined(WIN32) || defined(__SYMBIAN32__) + // HAVE_DRIVE_LETTERS is defined to signify that DOS/Windows style drive + // letters are to be recognised on this platform. +# define HAVE_DRIVE_LETTERS + // BACKSLASH_IS_PATH_SEPARATOR is defined to signify that the backslash + // character is to be recognised as a path separator on this platform. + // This does not affect the acceptance of forward slashes as path + // separators. +# define BACKSLASH_IS_PATH_SEPARATOR +#endif +#if defined(WIN32) + // HAVE_UNC_PATHS is defined to signify that Universal Naming Convention + // style paths are to be recognised on this platform. +# define HAVE_UNC_PATHS +#endif + +// User ids +#ifdef WIN32 +typedef short uid_t; +typedef short gid_t; +#endif + +// Some types +#ifdef _MSC_VER +typedef int ssize_t; +typedef unsigned short mode_t; +#endif + +// Directories +#include <sys/stat.h> +#ifdef WIN32 +# ifdef _MSC_VER +# define MKDIR(name, mode) ((void) mode, _mkdir(name)) +# else +# define MKDIR(name, mode) ((void) mode, mkdir(name)) +# endif +#else +# define MKDIR mkdir +#endif +#ifdef _MSC_VER +# include <direct.h> +# define chdir _chdir +# define getcwd _getcwd +# define chdir _chdir +# define getcwd _getcwd +# define access _access +# define F_OK 0 +# define W_OK 2 +# define R_OK 4 +# define open _open +# define read _read +# define rmdir _rmdir +# define lseek _lseek +# define lstat _lstat +# define fstat _fstat +# define S_IRUSR S_IREAD +# define S_IWUSR S_IWRITE +# define S_IXUSR S_IEXEC +# define S_IRWXU (S_IRUSR | S_IWUSR | S_IXUSR) +# define S_IRGRP 0 +# define S_IWGRP 0 +# define S_IXGRP 0 +# define S_IROTH 0 +# define S_IWOTH 0 +# define S_IXOTH 0 +# define S_IRWXG 0 +# define S_IRWXO 0 +# define S_ISUID 0 +# define S_ISGID 0 +# define O_ACCMODE (O_RDONLY | O_WRONLY | O_RDWR) +# define S_IFMT _S_IFMT +# define S_IFREG _S_IFREG +# define S_IFCHR _S_IFCHR +# define S_IFDIR _S_IFDIR +# define S_ISDIR(mode) (((mode) & _S_IFMT) == _S_IFDIR) +# define S_ISREG(mode) (((mode) & _S_IFMT) == _S_IFREG) +# define write _write +# define stat _stat +# define unlink _unlink +#elif defined (__MINGW32__) +# define S_IRGRP 0 +# define S_IWGRP 0 +# define S_IXGRP 0 +# define S_IROTH 0 +# define S_IWOTH 0 +# define S_IXOTH 0 +# define S_IRWXG 0 +# define S_IRWXO 0 +# define S_ISUID 0 +# define S_ISGID 0 +# define S_IFMT _S_IFMT +# define S_IFREG _S_IFREG +# define S_IFCHR _S_IFCHR +# define S_IFDIR _S_IFDIR +#endif +#ifdef __SYMBIAN32__ + // TODO: Symbian doesn't have readdir_r(). If uio is to be usable + // outside of uqm (which defines its own backup readdir_r()), an + // implementation of that function needs to be added to uio. +# include <dirent.h> + int readdir_r (DIR *dirp, struct dirent *entry, struct dirent **result); +#endif + +#endif /* LIBS_UIO_UIOPORT_H_ */ + diff --git a/src/libs/uio/uiostream.c b/src/libs/uio/uiostream.c new file mode 100644 index 0000000..eb101a5 --- /dev/null +++ b/src/libs/uio/uiostream.c @@ -0,0 +1,603 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "uioport.h" +#include "iointrn.h" +#include "uiostream.h" + +#include <errno.h> +#include <stdio.h> +#include <stdarg.h> + +#include "uioutils.h" +#include "utils.h" +#ifdef uio_MEM_DEBUG +# include "memdebug.h" +#endif + +#define uio_Stream_BLOCK_SIZE 1024 + +static inline uio_Stream *uio_Stream_new(uio_Handle *handle, int openFlags); +static inline void uio_Stream_delete(uio_Stream *stream); +static inline uio_Stream *uio_Stream_alloc(void); +static inline void uio_Stream_free(uio_Stream *stream); +#ifdef NDEBUG +# define uio_assertReadSanity(stream) +# define uio_assertWriteSanity(stream) +#else +static void uio_assertReadSanity(uio_Stream *stream); +static void uio_assertWriteSanity(uio_Stream *stream); +#endif +static int uio_Stream_fillReadBuffer(uio_Stream *stream); +static int uio_Stream_flushWriteBuffer(uio_Stream *stream); +static void uio_Stream_discardReadBuffer(uio_Stream *stream); + + +uio_Stream * +uio_fopen(uio_DirHandle *dir, const char *path, const char *mode) { + int openFlags; + uio_Handle *handle; + uio_Stream *stream; + int i; + + switch (*mode) { + case 'r': + openFlags = O_RDONLY; + break; + case 'w': + openFlags = O_WRONLY | O_CREAT | O_TRUNC; + break; + case 'a': + openFlags = O_WRONLY| O_CREAT | O_APPEND; + default: + errno = EINVAL; + fprintf(stderr, "Invalid mode string in call to uio_fopen().\n"); + return NULL; + } + mode++; + + // C'89 says 'b' may either be the second or the third character. + // If someone specifies both 'b' and 't', he/she is out of luck. + i = 2; + while (i-- && (*mode != '\0')) { + switch (*mode) { + case 'b': +#ifdef WIN32 + openFlags |= O_BINARY; +#endif + break; + case 't': +#ifdef WIN32 + openFlags |= O_TEXT; +#endif + break; + case '+': + openFlags = (openFlags & ~O_ACCMODE) | O_RDWR; + break; + default: + i = 0; + // leave the while loop + break; + } + mode++; + } + + // Any characters in the mode string that might follow are ignored. + + handle = uio_open(dir, path, openFlags, S_IRUSR | S_IWUSR | + S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + if (handle == NULL) { + // errno is set + return NULL; + } + + stream = uio_Stream_new(handle, openFlags); + return stream; +} + +int +uio_fclose(uio_Stream *stream) { + if (stream->operation == uio_StreamOperation_write) + uio_Stream_flushWriteBuffer(stream); + uio_close(stream->handle); + uio_Stream_delete(stream); + return 0; +} + +// "The file position indicator for the stream (if defined) is advanced by +// the number of characters successfully read. If an error occurs, the +// resulting value of the file position indicator for the stream is +// indeterminate. If a partial element is read, its value is +// indeterminate." (from POSIX for fread()). +size_t +uio_fread(void *buf, size_t size, size_t nmemb, uio_Stream *stream) { + size_t bytesToRead; + size_t bytesRead; + + bytesToRead = size * nmemb; + bytesRead = 0; + + uio_assertReadSanity(stream); + stream->operation = uio_StreamOperation_read; + + if (stream->dataEnd > stream->dataStart) { + // First use what's in the buffer. + size_t numRead; + + numRead = minu(stream->dataEnd - stream->dataStart, bytesToRead); + memcpy(buf, stream->dataStart, numRead); + buf = (void *) ((char *) buf + numRead); + stream->dataStart += numRead; + bytesToRead -= numRead; + bytesRead += numRead; + } + if (bytesToRead == 0) { + // Done already + return nmemb; + } + + { + // Read the rest directly into the caller's buffer. + ssize_t numRead; + numRead = uio_read(stream->handle, buf, bytesToRead); + if (numRead == -1) { + stream->status = uio_Stream_STATUS_ERROR; + goto out; + } + bytesRead += numRead; + if ((size_t) numRead < bytesToRead) { + // End of file + stream->status = uio_Stream_STATUS_EOF; + stream->operation = uio_StreamOperation_none; + goto out; + } + } + +out: + if (bytesToRead == 0) + return nmemb; + return bytesRead / size; +} + +char * +uio_fgets(char *s, int size, uio_Stream *stream) { + int orgSize; + char *buf; + + uio_assertReadSanity(stream); + stream->operation = uio_StreamOperation_read; + + size--; + orgSize = size; + buf = s; + while (size > 0) { + size_t maxRead; + const char *newLinePos; + + // Fill buffer if empty. + if (stream->dataStart == stream->dataEnd) { + if (uio_Stream_fillReadBuffer(stream) == -1) { + // errno is set + stream->status = uio_Stream_STATUS_ERROR; + return NULL; + } + if (stream->dataStart == stream->dataEnd) { + // End-of-file + stream->status = uio_Stream_STATUS_EOF; + stream->operation = uio_StreamOperation_none; + if (size == orgSize) { + // Nothing was read. + return NULL; + } + break; + } + } + + // Search in buffer + maxRead = minu(stream->dataEnd - stream->dataStart, size); + newLinePos = memchr(stream->dataStart, '\n', maxRead); + if (newLinePos != NULL) { + // Newline found. + maxRead = newLinePos + 1 - stream->dataStart; + memcpy(buf, stream->dataStart, maxRead); + stream->dataStart += maxRead; + buf[maxRead] = '\0'; + return buf; + } + // No newline present. + memcpy(buf, stream->dataStart, maxRead); + stream->dataStart += maxRead; + buf += maxRead; + size -= maxRead; + } + + *buf = '\0'; + return s; +} + +int +uio_fgetc(uio_Stream *stream) { + int result; + + uio_assertReadSanity(stream); + stream->operation = uio_StreamOperation_read; + + if (stream->dataStart == stream->dataEnd) { + // Buffer is empty + if (uio_Stream_fillReadBuffer(stream) == -1) { + stream->status = uio_Stream_STATUS_ERROR; + return (int) EOF; + } + if (stream->dataStart == stream->dataEnd) { + // End-of-file + stream->status = uio_Stream_STATUS_EOF; + stream->operation = uio_StreamOperation_none; + return (int) EOF; + } + } + + result = (int) *((unsigned char *) stream->dataStart); + stream->dataStart++; + return result; +} + +// Only one character pushback is guaranteed, just like with stdio ungetc(). +int +uio_ungetc(int c, uio_Stream *stream) { + assert((stream->openFlags & O_ACCMODE) != O_WRONLY); + assert(c >= 0 && c <= 255); + + return (int) EOF; + // not implemented +// return c; +} + +// NB. POSIX allows errno to be set for vsprintf(), but does not require it: +// "The value of errno may be set to nonzero by a library function call +// whether or not there is an error, provided the use of errno is not +// documented in the description of the function in this International +// Standard." The latter is the case for vsprintf(). +int +uio_vfprintf(uio_Stream *stream, const char *format, va_list args) { + // This could be done faster, but going through snprintf() is easiest, + // and is fast enough for now. + char *buf; + int putResult; + int savedErrno; + + buf = uio_vasprintf(format, args); + if (buf == NULL) { + // errno may or may not be set + return -1; + } + + putResult = uio_fputs(buf, stream); + savedErrno = errno; + + uio_free(buf); + + errno = savedErrno; + return putResult; +} + +int +uio_fprintf(uio_Stream *stream, const char *format, ...) { + va_list args; + int result; + + va_start(args, format); + result = uio_vfprintf(stream, format, args); + va_end(args); + + return result; +} + +int +uio_fputc(int c, uio_Stream *stream) { + assert((stream->openFlags & O_ACCMODE) != O_RDONLY); + assert(c >= 0 && c <= 255); + + uio_assertWriteSanity(stream); + stream->operation = uio_StreamOperation_write; + + if (stream->dataEnd == stream->bufEnd) { + // The buffer is full. Flush it out. + if (uio_Stream_flushWriteBuffer(stream) == -1) { + // errno is set + // Error status (for ferror()) is set. + return EOF; + } + } + + *(unsigned char *) stream->dataEnd = (unsigned char) c; + stream->dataEnd++; + return c; +} + +int +uio_fputs(const char *s, uio_Stream *stream) { + int result; + + result = uio_fwrite(s, strlen(s), 1, stream); + if (result != 1) + return EOF; + return 0; +} + +int +uio_fseek(uio_Stream *stream, long offset, int whence) { + int newPos; + + if (stream->operation == uio_StreamOperation_read) { + uio_Stream_discardReadBuffer(stream); + } else if (stream->operation == uio_StreamOperation_write) { + if (uio_Stream_flushWriteBuffer(stream) == -1) { + // errno is set + return -1; + } + } + assert(stream->dataStart == stream->buf); + assert(stream->dataEnd == stream->buf); + stream->operation = uio_StreamOperation_none; + + newPos = uio_lseek(stream->handle, offset, whence); + if (newPos == -1) { + // errno is set + return -1; + } + stream->status = uio_Stream_STATUS_OK; + // Clear error or end-of-file flag. + + return 0; +} + +long +uio_ftell(uio_Stream *stream) { + off_t newPos; + + newPos = uio_lseek(stream->handle, 0, SEEK_CUR); + if (newPos == (off_t) -1) { + // errno is set + return (long) -1; + } + + if (stream->operation == uio_StreamOperation_write) { + newPos += stream->dataEnd - stream->dataStart; + } else if (stream->operation == uio_StreamOperation_read) { + newPos -= stream->dataEnd - stream->dataStart; + } + + return (long) newPos; +} + +// If less that nmemb elements could be written, or an error occurs, the +// file pointer is undefined. clearerr() followed by fseek() need to be +// called before attempting to read or write again. +// I don't have the C standard myself, but I suspect this is the official +// behaviour for fread() and fwrite(). +size_t +uio_fwrite(const void *buf, size_t size, size_t nmemb, uio_Stream *stream) { + ssize_t bytesToWrite; + ssize_t bytesWritten; + + uio_assertWriteSanity(stream); + stream->operation = uio_StreamOperation_write; + + // NB. If a file is opened in append mode, the file position indicator + // is moved to the end of the file before writing. + // We leave that up to the physical layer. + + bytesToWrite = size * nmemb; + if (bytesToWrite < stream->bufEnd - stream->dataEnd) { + // There's enough space in the write buffer to store everything. + memcpy(stream->dataEnd, buf, bytesToWrite); + stream->dataEnd += bytesToWrite; + return nmemb; + } + + // Not enough space in the write buffer to write everything. + // Flush what's left in the write buffer first. + if (uio_Stream_flushWriteBuffer(stream) == -1) { + // errno is set + // Error status (for ferror()) is set. + return 0; + } + + if (bytesToWrite < stream->bufEnd - stream->dataEnd) { + // The now empty write buffer is large enough to store everything. + memcpy(stream->dataEnd, buf, bytesToWrite); + stream->dataEnd += bytesToWrite; + return nmemb; + } + + // There is more data to write than fits in the (empty) write buffer. + // The data is written directly, in its entirety, without going + // through the write buffer. + bytesWritten = uio_write(stream->handle, buf, bytesToWrite); + if (bytesWritten != bytesToWrite) { + stream->status = uio_Stream_STATUS_ERROR; + if (bytesWritten == -1) + return 0; + } + + if (bytesWritten == bytesToWrite) + return nmemb; + return (size_t) bytesWritten / size; +} + +// NB: stdio fflush() accepts NULL to flush all streams. uio_flush() does +// not. +int +uio_fflush(uio_Stream *stream) { + assert(stream != NULL); + + if (stream->operation == uio_StreamOperation_write) { + if (uio_Stream_flushWriteBuffer(stream) == -1) { + // errno is set + return (int) EOF; + } + stream->operation = uio_StreamOperation_none; + } + + return 0; +} + +int +uio_feof(uio_Stream *stream) { + return stream->status == uio_Stream_STATUS_EOF; +} + +int +uio_ferror(uio_Stream *stream) { + return stream->status == uio_Stream_STATUS_ERROR; +} + +void +uio_clearerr(uio_Stream *stream) { + stream->status = uio_Stream_STATUS_OK; +} + +// Counterpart of fileno() +uio_Handle * +uio_streamHandle(uio_Stream *stream) { + return stream->handle; +} + +#ifndef NDEBUG +static void +uio_assertReadSanity(uio_Stream *stream) { + assert((stream->openFlags & O_ACCMODE) != O_WRONLY); + + if (stream->operation == uio_StreamOperation_write) { + // "[...] output shall not be directly followed by input without an + // intervening call to the fflush function or to a file positioning + // function (fseek, fsetpos, or rewind), and input shall not be + // directly followed by output without an intervening call to a file + // positioning function, unless the input operation encounters + // end-of-file." (POSIX, C) + fprintf(stderr, "Error: Reading on a file directly after writing, " + "without an intervening call to fflush() or a file " + "positioning function.\n"); + abort(); + } +} +#endif + +#ifndef NDEBUG +static void +uio_assertWriteSanity(uio_Stream *stream) { + assert((stream->openFlags & O_ACCMODE) != O_RDONLY); + + if (stream->operation == uio_StreamOperation_read) { + // "[...] output shall not be directly followed by input without an + // intervening call to the fflush function or to a file positioning + // function (fseek, fsetpos, or rewind), and input shall not be + // directly followed by output without an intervening call to a file + // positioning function, unless the input operation encounters + // end-of-file." (POSIX, C) + fprintf(stderr, "Error: Writing on a file directly after reading, " + "without an intervening call to a file positioning " + "function.\n"); + abort(); + } + assert(stream->dataStart == stream->buf); +} +#endif + +static int +uio_Stream_flushWriteBuffer(uio_Stream *stream) { + ssize_t bytesWritten; + + assert(stream->operation == uio_StreamOperation_write); + + bytesWritten = uio_write(stream->handle, stream->dataStart, + stream->dataEnd - stream->dataStart); + if (bytesWritten != stream->dataEnd - stream->dataStart) { + stream->status = uio_Stream_STATUS_ERROR; + return -1; + } + assert(stream->dataStart == stream->buf); + stream->dataEnd = stream->buf; + + return 0; +} + +static void +uio_Stream_discardReadBuffer(uio_Stream *stream) { + assert(stream->operation == uio_StreamOperation_read); + stream->dataStart = stream->buf; + stream->dataEnd = stream->buf; + // TODO: when implementing pushback: throw away pushback buffer. +} + +static int +uio_Stream_fillReadBuffer(uio_Stream *stream) { + ssize_t numRead; + + assert(stream->operation == uio_StreamOperation_read); + + numRead = uio_read(stream->handle, stream->buf, + uio_Stream_BLOCK_SIZE); + if (numRead == -1) + return -1; + stream->dataStart = stream->buf; + stream->dataEnd = stream->buf + numRead; + return 0; +} + +static inline uio_Stream * +uio_Stream_new(uio_Handle *handle, int openFlags) { + uio_Stream *result; + + result = uio_Stream_alloc(); + result->handle = handle; + result->openFlags = openFlags; + result->status = uio_Stream_STATUS_OK; + result->operation = uio_StreamOperation_none; + result->buf = uio_malloc(uio_Stream_BLOCK_SIZE); + result->dataStart = result->buf; + result->dataEnd = result->buf; + result->bufEnd = result->buf + uio_Stream_BLOCK_SIZE; + return result; +} + +static inline uio_Stream * +uio_Stream_alloc(void) { + uio_Stream *result = uio_malloc(sizeof (uio_Stream)); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugAlloc(uio_Stream, (void *) result); +#endif + return result; +} + +static inline void +uio_Stream_delete(uio_Stream *stream) { + uio_free(stream->buf); + uio_Stream_free(stream); +} + +static inline void +uio_Stream_free(uio_Stream *stream) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugFree(uio_Stream, (void *) stream); +#endif + uio_free(stream); +} + diff --git a/src/libs/uio/uiostream.h b/src/libs/uio/uiostream.h new file mode 100644 index 0000000..f65487e --- /dev/null +++ b/src/libs/uio/uiostream.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef LIBS_UIO_UIOSTREAM_H_ +#define LIBS_UIO_UIOSTREAM_H_ + + +typedef struct uio_Stream uio_Stream; + +#include "io.h" + +#include <stdarg.h> + + +uio_Stream *uio_fopen(uio_DirHandle *dir, const char *path, const char *mode); +int uio_fclose(uio_Stream *stream); +size_t uio_fread(void *buf, size_t size, size_t nmemb, uio_Stream *stream); +char *uio_fgets(char *buf, int size, uio_Stream *stream); +int uio_fgetc(uio_Stream *stream); +#define uio_getc uio_fgetc +int uio_ungetc(int c, uio_Stream *stream); +int uio_vfprintf(uio_Stream *stream, const char *format, va_list args); +int uio_fprintf(uio_Stream *stream, const char *format, ...); +int uio_fputc(int c, uio_Stream *stream); +#define uio_putc uio_fputc +int uio_fputs(const char *s, uio_Stream *stream); +int uio_fseek(uio_Stream *stream, long offset, int whence); +long uio_ftell(uio_Stream *stream); +size_t uio_fwrite(const void *buf, size_t size, size_t nmemb, + uio_Stream *stream); +int uio_fflush(uio_Stream *stream); +int uio_feof(uio_Stream *stream); +int uio_ferror(uio_Stream *stream); +void uio_clearerr(uio_Stream *stream); +uio_Handle *uio_streamHandle(uio_Stream *stream); + + +/* *** Internal definitions follow *** */ +#ifdef uio_INTERNAL + +#include <sys/types.h> +#include <fcntl.h> +#include "iointrn.h" + +typedef enum { + uio_StreamOperation_none, + uio_StreamOperation_read, + uio_StreamOperation_write +} uio_StreamOperation; + +struct uio_Stream { + char *buf; + // Start of the buffer. + char *dataStart; + // Start of the part of the buffer that is in use. + char *dataEnd; + // Start of the unused part of the buffer. + char *bufEnd; + // End of the buffer. + // INV: buf <= dataStart <= dataEnd <= bufEnd + // INV: if 'operation == uio_StreamOperation_write' then buf == dataStart + + uio_Handle *handle; + int status; +#define uio_Stream_STATUS_OK 0 +#define uio_Stream_STATUS_EOF 1 +#define uio_Stream_STATUS_ERROR 2 + uio_StreamOperation operation; + // What was the last action (reading or writing). This + // determines whether the buffer is a read or write buffer. + int openFlags; + // Flags used for opening the file. +}; + + +#endif /* uio_INTERNAL */ + +#endif /* LIBS_UIO_UIOSTREAM_H_ */ + + diff --git a/src/libs/uio/uioutils.c b/src/libs/uio/uioutils.c new file mode 100644 index 0000000..fbe3cd4 --- /dev/null +++ b/src/libs/uio/uioutils.c @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sys/types.h> +#include <string.h> + +#include "uioutils.h" +#include "mem.h" +#include "paths.h" +#include "uioport.h" + +/** + * Concatenate two strings into a newly allocated buffer. + * + * @param[in] first The first (left) string, '\0' terminated. + * @param[in] second The second (right) string, '\0' terminated. + * + * @returns A newly allocated string consisting of the concatenation of + * 'first' and 'second', to be freed using uio_free(). + */ +char * +strcata(const char *first, const char *second) { + char *result, *resPtr; + size_t firstLen, secondLen; + + firstLen = strlen(first); + secondLen = strlen(second); + result = uio_malloc(firstLen + secondLen + 1); + resPtr = result; + + memcpy(resPtr, first, firstLen); + resPtr += firstLen; + + memcpy(resPtr, second, secondLen); + resPtr += secondLen; + + *resPtr = '\0'; + return result; +} + +// returns a copy of a generic array 'array' with 'element' inserted in +// position 'insertPos' +void * +insertArray(const void *array, size_t oldNumElements, int insertPos, + const void *element, size_t elementSize) { + void *newArray, *newArrayPtr; + const void *arrayPtr; + size_t preInsertSize; + + newArray = uio_malloc((oldNumElements + 1) * elementSize); + preInsertSize = insertPos * elementSize; + memcpy(newArray, array, preInsertSize); + newArrayPtr = (char *) newArray + preInsertSize; + arrayPtr = (const char *) array + preInsertSize; + memcpy(newArrayPtr, element, elementSize); + newArrayPtr = (char *) newArrayPtr + elementSize; + memcpy(newArrayPtr, arrayPtr, + (oldNumElements - insertPos) * elementSize); + return newArray; +} + +// returns a copy of a pointer array 'array' with 'element' inserted in +// position 'insertPos' +void ** +insertArrayPointer(const void **array, size_t oldNumElements, int insertPos, + const void *element) { + void **newArray, **newArrayPtr; + const void **arrayPtr; + size_t preInsertSize; + + newArray = uio_malloc((oldNumElements + 1) * sizeof (void *)); + preInsertSize = insertPos * sizeof (void *); + memcpy(newArray, array, preInsertSize); + newArrayPtr = newArray + insertPos; + arrayPtr = array + insertPos; + *newArrayPtr = unconst(element); + newArrayPtr++; + memcpy(newArrayPtr, arrayPtr, + (oldNumElements - insertPos) * sizeof (void *)); + return newArray; +} + +// returns a copy of a generic array 'array' with 'numExclude' elements, +// starting from startpos, removed. +void * +excludeArray(const void *array, size_t oldNumElements, int startPos, + int numExclude, size_t elementSize) { + void *newArray, *newArrayPtr; + const void *arrayPtr; + size_t preExcludeSize; + + newArray = uio_malloc((oldNumElements - numExclude) * elementSize); + preExcludeSize = startPos * elementSize; + memcpy(newArray, array, preExcludeSize); + newArrayPtr = (char *) newArray + preExcludeSize; + arrayPtr = (const char *) array + + (startPos + numExclude) * sizeof (elementSize); + memcpy(newArrayPtr, arrayPtr, + (oldNumElements - startPos - numExclude) * elementSize); + return newArray; +} + +// returns a copy of a pointer array 'array' with 'numExclude' elements, +// starting from startpos, removed. +void ** +excludeArrayPointer(const void **array, size_t oldNumElements, int startPos, + int numExclude) { + void **newArray; + + newArray = uio_malloc((oldNumElements - numExclude) * sizeof (void *)); + memcpy(newArray, array, startPos * sizeof (void *)); + memcpy(&newArray[startPos], &array[startPos + numExclude], + (oldNumElements - startPos - numExclude) * sizeof (void *)); + return newArray; +} + +// If the given DOS date/time is invalid, the result is unspecified, +// but the function won't crash. +time_t +dosToUnixTime(uio_uint16 date, uio_uint16 tm) { + // DOS date has the following format: + // bits 0-4 specify the number of the day in the month (1-31). + // bits 5-8 specify the number of the month in the year (1-12). + // bits 9-15 specify the year number since 1980 (0-127) + // DOS time has the fillowing format: + // bits 0-4 specify the number of seconds/2 in the minute (0-29) + // (only accurate on 2 seconds) + // bits 5-10 specify the number of minutes in the hour (0-59) + // bits 11-15 specify the number of hours since midnight (0-23) + + int year, month, day; + int hours, minutes, seconds; + long result; + + static const int daysUntilMonth[] = { + 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, + 334, 334, 334, 334 }; + // The last 4 entries are there so that there's no + // invalid memory access if the date is invalid. + + year = date >> 9; + month = ((date >> 5) - 1) & 0x0f; // Number in [0..15] + day = (date - 1) & 0x1f; // Number in [0..31] + hours = tm >> 11; + minutes = (tm >> 5) & 0x3f; + seconds = (tm & 0x1f) * 2; // Even number in [0..62] + + result = year * 365 + daysUntilMonth[month] + day; + // Count the (non-leap) days in all those years + + // Add a leapday for each 4th year + if (year % 4 == 0 && month <= 2) { + // The given date is a leap-year but the leapday hasn't occured yet. + result += year / 4; + } else { + result += 1 + year / 4; + } + // result now is the number of days between 1980-01-01 and the given day. + + // Add the days between 1970-01-01 and 1980-01-01 + // (2 leapdays in this period) + result += 365 * 10 + 2; + + result = (result * 24) + hours; // days to hours + result = (result * 60) + minutes; // hours to minutes + result = (result * 60) + seconds; // minutes to seconds + + return (time_t) result; +} + +char * +dosToUnixPath(const char *path) { + const char *srcPtr; + char *result, *dstPtr; + size_t skip; + + result = uio_malloc(strlen(path) + 1); + srcPtr = path; + dstPtr = result; + + // A UNC path will look like this: "\\server\share/..."; the first two + // characters will be backslashes, and the separator between the server + // and the share too. The rest will be slashes. + // The goal is that at every forward slash, the path should be + // stat()'able. + skip = uio_skipUNCServerShare(srcPtr); + if (skip != 0) { + char *slash; + memcpy(dstPtr, srcPtr, skip); + + slash = memchr(srcPtr + 2, '/', skip - 2); + if (slash != NULL) + *slash = '\\'; + + srcPtr += skip; + dstPtr += skip; + } + + while (*srcPtr != '\0') { + if (*srcPtr == '\\') { + *dstPtr = '/'; + } else + *dstPtr = *srcPtr; + srcPtr++; + dstPtr++; + } + *dstPtr = '\0'; + return result; +} + + diff --git a/src/libs/uio/uioutils.h b/src/libs/uio/uioutils.h new file mode 100644 index 0000000..6e04843 --- /dev/null +++ b/src/libs/uio/uioutils.h @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef LIBS_UIO_UIOUTILS_H_ +#define LIBS_UIO_UIOUTILS_H_ + +#include <time.h> + +#include "types.h" +#include "uioport.h" + +char *strcata(const char *first, const char *second); +void *insertArray(const void *array, size_t oldNumElements, int insertPos, + const void *element, size_t elementSize); +void **insertArrayPointer(const void **array, size_t oldNumElements, + int insertPos, const void *element); +void *excludeArray(const void *array, size_t oldNumElements, int startPos, + int numExclude, size_t elementSize); +void **excludeArrayPointer(const void **array, size_t oldNumElements, + int startPos, int numExclude); +time_t dosToUnixTime(uio_uint16 date, uio_uint16 tm); +char *dosToUnixPath(const char *path); + +/* Sometimes you just have to remove a 'const'. + * (for instance, when implementing a function like strchr) + */ +static inline void * +unconst(const void *arg) { + union { + void *c; + const void *cc; + } u; + u.cc = arg; + return u.c; +} + +// byte1 is the lowest byte, byte4 the highest +static inline uio_uint32 +makeUInt32(uio_uint8 byte1, uio_uint8 byte2, uio_uint8 byte3, uio_uint8 byte4) { + return byte1 | (byte2 << 8) | (byte3 << 16) | (byte4 << 24); +} + +static inline uio_uint16 +makeUInt16(uio_uint8 byte1, uio_uint8 byte2) { + return byte1 | (byte2 << 8); +} + +static inline uio_sint32 +makeSInt32(uio_uint8 byte1, uio_uint8 byte2, uio_uint8 byte3, uio_uint8 byte4) { + return byte1 | (byte2 << 8) | (byte3 << 16) | (byte4 << 24); +} + +static inline uio_sint16 +makeSInt16(uio_uint8 byte1, uio_uint8 byte2) { + return byte1 | (byte2 << 8); +} + +static inline uio_bool +isBitSet(uio_uint32 bitField, int bit) { + return ((bitField >> bit) & 1) == 1; +} + +static inline int +mins(int i1, int i2) { + return i1 <= i2 ? i1 : i2; +} + +static inline unsigned int +minu(unsigned int i1, unsigned int i2) { + return i1 <= i2 ? i1 : i2; +} + + +#endif /* LIBS_UIO_UIOUTILS_H_ */ + diff --git a/src/libs/uio/utils.c b/src/libs/uio/utils.c new file mode 100644 index 0000000..1f705bb --- /dev/null +++ b/src/libs/uio/utils.c @@ -0,0 +1,497 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <errno.h> +#include <time.h> +#include <stdio.h> +#ifdef _MSC_VER +# include <stdarg.h> +#endif /* _MSC_VER */ + +#include "iointrn.h" +#include "ioaux.h" +#include "utils.h" + +static int uio_copyError(uio_Handle *srcHandle, uio_Handle *dstHandle, + uio_DirHandle *unlinkHandle, const char *unlinkPath, uio_uint8 *buf); + +struct uio_StdioAccessHandle { + uio_DirHandle *tempRoot; + char *tempDirName; + uio_DirHandle *tempDir; + char *fileName; + char *stdioPath; +}; + +static inline uio_StdioAccessHandle *uio_StdioAccessHandle_new( + uio_DirHandle *tempRoot, char *tempDirName, + uio_DirHandle *tempDir, char *fileName, + char *stdioPath); +static inline void uio_StdioAccessHandle_delete( + uio_StdioAccessHandle *handle); +static inline uio_StdioAccessHandle *uio_StdioAccessHandle_alloc(void); +static inline void uio_StdioAccessHandle_free(uio_StdioAccessHandle *handle); + +/* + * Copy a file with path srcName to a file with name newName. + * If the destination already exists, the operation fails. + * Links are followed. + * Special files (fifos, char devices, block devices, etc) will be + * read as long as there is data available and the destination will be + * a regular file with that data. + * The new file will have the same permissions as the old. + * If an error occurs during copying, an attempt will be made to + * remove the copy. + */ +int +uio_copyFile(uio_DirHandle *srcDir, const char *srcName, + uio_DirHandle *dstDir, const char *newName) { + uio_Handle *src, *dst; + struct stat sb; +#define BUFSIZE 65536 + uio_uint8 *buf, *bufPtr; + ssize_t numInBuf, numWritten; + + src = uio_open(srcDir, srcName, O_RDONLY +#ifdef WIN32 + | O_BINARY +#endif + , 0); + if (src == NULL) + return -1; + + if (uio_fstat(src, &sb) == -1) + return uio_copyError(src, NULL, NULL, NULL, NULL); + + dst = uio_open(dstDir, newName, O_WRONLY | O_CREAT | O_EXCL +#ifdef WIN32 + | O_BINARY +#endif + , sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)); + if (dst == NULL) + return uio_copyError(src, NULL, NULL, NULL, NULL); + + buf = uio_malloc(BUFSIZE); + // This was originally a statically allocated buffer, + // but as this function might be run from a thread with + // a small Stack, this is better. + while (1) { + numInBuf = uio_read(src, buf, BUFSIZE); + if (numInBuf == -1) { + if (errno == EINTR) + continue; + return uio_copyError(src, dst, dstDir, newName, buf); + } + if (numInBuf == 0) + break; + + bufPtr = buf; + do + { + numWritten = uio_write(dst, bufPtr, numInBuf); + if (numWritten == -1) { + if (errno == EINTR) + continue; + return uio_copyError(src, dst, dstDir, newName, buf); + } + numInBuf -= numWritten; + bufPtr += numWritten; + } while (numInBuf > 0); + } + + uio_free(buf); + uio_close(src); + uio_close(dst); + errno = 0; + return 0; +} + +/* + * Closes srcHandle if it's not -1. + * Closes dstHandle if it's not -1. + * Removes unlinkpath from the unlinkHandle dir if it's not NULL. + * Frees 'buf' if not NULL. + * Always returns -1. + * errno is what was before the call. + */ +static int +uio_copyError(uio_Handle *srcHandle, uio_Handle *dstHandle, + uio_DirHandle *unlinkHandle, const char *unlinkPath, uio_uint8 *buf) { + int savedErrno; + + savedErrno = errno; + +#ifdef DEBUG + fprintf(stderr, "Error while copying: %s\n", strerror(errno)); +#endif + + if (srcHandle != NULL) + uio_close(srcHandle); + + if (dstHandle != NULL) + uio_close(dstHandle); + + if (unlinkPath != NULL) + uio_unlink(unlinkHandle, unlinkPath); + + if (buf != NULL) + uio_free(buf); + + errno = savedErrno; + return -1; +} + +#define NUM_TEMP_RETRIES 16 + // Retry this many times to create a temporary dir, before giving + // up. If undefined, keep trying indefinately. + +uio_StdioAccessHandle * +uio_getStdioAccess(uio_DirHandle *dir, const char *path, int flags, + uio_DirHandle *tempDir) { + int res; + uio_MountHandle *mountHandle; + const char *name; + char *newPath; + char *tempDirName; + uio_DirHandle *newDir; + uio_FileSystemID fsID; + + res = uio_getFileLocation(dir, path, flags, &mountHandle, &newPath); + if (res == -1) { + // errno is set + return NULL; + } + + fsID = uio_getMountFileSystemType(mountHandle); + if (fsID == uio_FSTYPE_STDIO) { + // Current location is usable. + return uio_StdioAccessHandle_new(NULL, NULL, NULL, NULL, newPath); + } + uio_free(newPath); + + { + uio_uint32 dirNum; + int i; + + // Current location is not usable. Create a directory with a + // generated name, as a temporary location to store a copy of + // the file. + dirNum = (uio_uint32) time(NULL); + tempDirName = uio_malloc(sizeof "01234567"); + for (i = 0; ; i++) { +#ifdef NUM_TEMP_RETRIES + if (i >= NUM_TEMP_RETRIES) { + // Using ENOSPC to report that we couldn't create a + // temporary dir, getting EEXIST. + uio_free(tempDirName); + errno = ENOSPC; + return NULL; + } +#endif + + sprintf(tempDirName, "%08lx", (unsigned long) dirNum + i); + + res = uio_mkdir(tempDir, tempDirName, 0700); + if (res == -1) { + int savedErrno; + if (errno == EEXIST) + continue; + savedErrno = errno; +#ifdef DEBUG + fprintf(stderr, "Error: Could not create temporary dir: %s\n", + strerror(errno)); +#endif + uio_free(tempDirName); + errno = savedErrno; + return NULL; + } + break; + } + + newDir = uio_openDirRelative(tempDir, tempDirName, 0); + if (newDir == NULL) { +#ifdef DEBUG + fprintf(stderr, "Error: Could not open temporary dir: %s\n", + strerror(errno)); +#endif + res = uio_rmdir(tempDir, tempDirName); +#ifdef DEBUG + if (res == -1) + fprintf(stderr, "Warning: Could not remove temporary dir: " + "%s.\n", strerror(errno)); +#endif + uio_free(tempDirName); + errno = EIO; + return NULL; + } + + // Get the last component of path. This should be the file to + // access. + name = strrchr(path, '/'); + if (name == NULL) + name = path; + + // Copy the file + res = uio_copyFile(dir, path, newDir, name); + if (res == -1) { + int savedErrno = errno; +#ifdef DEBUG + fprintf(stderr, "Error: Could not copy file to temporary dir: " + "%s\n", strerror(errno)); +#endif + uio_closeDir(newDir); + uio_free(tempDirName); + errno = savedErrno; + return NULL; + } + } + + res = uio_getFileLocation(newDir, name, flags, &mountHandle, &newPath); + if (res == -1) { + int savedErrno = errno; + fprintf(stderr, "Error: uio_getStdioAccess: Could not get location " + "of temporary dir: %s.\n", strerror(errno)); + uio_closeDir(newDir); + uio_free(tempDirName); + errno = savedErrno; + return NULL; + } + + fsID = uio_getMountFileSystemType(mountHandle); + if (fsID != uio_FSTYPE_STDIO) { + // Temp dir isn't on a stdio fs either. + fprintf(stderr, "Error: uio_getStdioAccess: Temporary file location " + "isn't on a stdio filesystem.\n"); + uio_closeDir(newDir); + uio_free(tempDirName); + uio_free(newPath); +// errno = EXDEV; + errno = EINVAL; + return NULL; + } + + uio_DirHandle_ref(tempDir); + return uio_StdioAccessHandle_new(tempDir, tempDirName, newDir, + uio_strdup(name), newPath); +} + +void +uio_releaseStdioAccess(uio_StdioAccessHandle *handle) { + if (handle->tempDir != NULL) { + if (uio_unlink(handle->tempDir, handle->fileName) == -1) { +#ifdef DEBUG + fprintf(stderr, "Error: Could not remove temporary file: " + "%s\n", strerror(errno)); +#endif + } + + // Need to free this handle in advance. There should be no handles + // to a dir left when removing it. + uio_DirHandle_unref(handle->tempDir); + handle->tempDir = NULL; + + if (uio_rmdir(handle->tempRoot, handle->tempDirName) == -1) { +#ifdef DEBUG + fprintf(stderr, "Error: Could not remove temporary directory: " + "%s\n", strerror(errno)); +#endif + } + } + + uio_StdioAccessHandle_delete(handle); +} + +const char * +uio_StdioAccessHandle_getPath(uio_StdioAccessHandle *handle) { + return (const char *) handle->stdioPath; +} + +// references to tempRoot and tempDir are not increased. +// no copies of arguments are made. +// By calling this function control of the values is transfered to +// the handle. +static inline uio_StdioAccessHandle * +uio_StdioAccessHandle_new( + uio_DirHandle *tempRoot, char *tempDirName, + uio_DirHandle *tempDir, char *fileName, char *stdioPath) { + uio_StdioAccessHandle *result; + + result = uio_StdioAccessHandle_alloc(); + result->tempRoot = tempRoot; + result->tempDirName = tempDirName; + result->tempDir = tempDir; + result->fileName = fileName; + result->stdioPath = stdioPath; + + return result; +} + +static inline void +uio_StdioAccessHandle_delete(uio_StdioAccessHandle *handle) { + if (handle->tempDir != NULL) + uio_DirHandle_unref(handle->tempDir); + if (handle->fileName != NULL) + uio_free(handle->fileName); + if (handle->tempRoot != NULL) + uio_DirHandle_unref(handle->tempRoot); + if (handle->tempDirName != NULL) + uio_free(handle->tempDirName); + uio_free(handle->stdioPath); + uio_StdioAccessHandle_free(handle); +} + +static inline uio_StdioAccessHandle * +uio_StdioAccessHandle_alloc(void) { + return uio_malloc(sizeof (uio_StdioAccessHandle)); +} + +static inline void +uio_StdioAccessHandle_free(uio_StdioAccessHandle *handle) { + uio_free(handle); +} + +#ifdef _MSC_VER +# include <stdarg.h> +#if 0 /* Unneeded for now */ +// MSVC does not have snprintf(). It does have a _snprintf(), but it does +// not \0-terminate a truncated string as the C standard prescribes. +static inline int +snprintf(char *str, size_t size, const char *format, ...) +{ + int result; + va_list args; + + va_start (args, format); + result = _vsnprintf (str, size, format, args); + if (str != NULL && size != 0) + str[size - 1] = '\0'; + va_end (args); + + return result; +} +#endif + +// MSVC does not have vsnprintf(). It does have a _vsnprintf(), but it does +// not \0-terminate a truncated string as the C standard prescribes. +static inline int +vsnprintf(char *str, size_t size, const char *format, va_list args) +{ + int result = _vsnprintf (str, size, format, args); + if (str != NULL && size != 0) + str[size - 1] = '\0'; + return result; +} +#endif /* _MSC_VER */ + +// The result should be freed using uio_free(). +// NB. POSIX allows errno to be set for vsprintf(), but does not require it: +// "The value of errno may be set to nonzero by a library function call +// whether or not there is an error, provided the use of errno is not +// documented in the description of the function in this International +// Standard." The latter is the case for vsprintf(). +char * +uio_vasprintf(const char *format, va_list args) { + // TODO: If there is a system vasprintf, use that. + // XXX: That would mean that the allocation would always go through + // malloc() or so, instead of uio_malloc(), which may not be + // desirable. + + char *buf; + size_t bufSize = 128; + // Start with enough for one screen line, and a power of 2, + // which might give faster result with allocations. + + buf = uio_malloc(bufSize); + if (buf == NULL) { + // errno is set. + return NULL; + } + + for (;;) { + int printResult = vsnprintf(buf, bufSize, format, args); + if (printResult < 0) { + // This means the buffer was not large enough, but vsnprintf() + // does not give us any clue on how large it should be. + // Note that this does not happen with a C'99 compliant + // vsnprintf(), but it will happen on MS Windows, and on + // glibc before version 2.1. + bufSize *= 2; + } else if ((unsigned int) printResult >= bufSize) { + // The buffer was too small, but printResult contains the size + // that the buffer needs to be (excluding the '\0' character). + bufSize = printResult + 1; + } else { + // Success. + if ((unsigned int) printResult + 1 != bufSize) { + // Shorten the resulting buffer to the size that was + // actually needed. + char *newBuf = uio_realloc(buf, printResult + 1); + if (newBuf == NULL) { + // We could have returned the (overly large) original + // buffer, but the unused memory might not be + // acceptable, and the program would be likely to run + // into problems sooner or later anyhow. + int savedErrno = errno; + uio_free(buf); + errno = savedErrno; + return NULL; + } + return newBuf; + } + + return buf; + } + + { + char *newBuf = uio_realloc(buf, bufSize); + if (newBuf == NULL) + { + int savedErrno = errno; + uio_free(buf); + errno = savedErrno; + return NULL; + } + buf = newBuf; + } + } +} + +// As uio_vasprintf(), but with an argument list. +char * +uio_asprintf(const char *format, ...) { + // TODO: If there is a system asprintf, use that. + // XXX: That would mean that the allocation would always go through + // malloc() or so, instead of uio_malloc(), which may not be + // desirable. + + va_list args; + char *result; + int savedErrno; + + va_start(args, format); + result = uio_vasprintf(format, args); + savedErrno = errno; + va_end(args); + + errno = savedErrno; + return result; +} + + diff --git a/src/libs/uio/utils.h b/src/libs/uio/utils.h new file mode 100644 index 0000000..313bf67 --- /dev/null +++ b/src/libs/uio/utils.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef LIBS_UIO_UTILS_H_ +#define LIBS_UIO_UTILS_H_ + +#include <stdarg.h> + +#ifdef uio_INTERNAL +typedef struct uio_StdioAccessHandle uio_StdioAccessHandle; +#else +typedef void uio_StdioAccessHandle; +#endif + +int uio_copyFile(uio_DirHandle *srcDir, const char *srcName, + uio_DirHandle *dstDir, const char *newName); +uio_StdioAccessHandle *uio_getStdioAccess(uio_DirHandle *dir, + const char *path, int flags, uio_DirHandle *tempDir); +const char *uio_StdioAccessHandle_getPath(uio_StdioAccessHandle *handle); +void uio_releaseStdioAccess(uio_StdioAccessHandle *handle); + +char *uio_vasprintf(const char *format, va_list args); +char *uio_asprintf(const char *format, ...); + +#endif /* LIBS_UIO_UTILS_H_ */ + diff --git a/src/libs/uio/zip/Makeinfo b/src/libs/uio/zip/Makeinfo new file mode 100644 index 0000000..64ae8d5 --- /dev/null +++ b/src/libs/uio/zip/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="zip.c" +uqm_HFILES="zip.h" diff --git a/src/libs/uio/zip/zip.c b/src/libs/uio/zip/zip.c new file mode 100644 index 0000000..6da462b --- /dev/null +++ b/src/libs/uio/zip/zip.c @@ -0,0 +1,1680 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + * This file makes use of zlib (http://www.gzip.org/zlib/) + * + * References: + * The .zip format description from PKWare: + * http://www.pkware.com/products/enterprise/white_papers/appnote.html + * The .zip format description from InfoZip: + * ftp://ftp.info-zip.org/pub/infozip/doc/appnote-011203-iz.zip + */ + +#include <stdio.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/stat.h> + +#include "zip.h" +#include "../physical.h" +#include "../uioport.h" +#include "../paths.h" +#include "../uioutils.h" +#ifdef uio_MEM_DEBUG +# include "../memdebug.h" +#endif + + +#define DIR_STRUCTURE_READ_BUFSIZE 0x10000 + +static int zip_badFile(zip_GPFileData *gPFileData, char *fileName); +static int zip_fillDirStructure(uio_GPDir *top, uio_Handle *handle); +#if zip_USE_HEADERS == zip_USE_LOCAL_HEADERS +static int zip_fillDirStructureLocal(uio_GPDir *top, uio_Handle *handle); +static int zip_fillDirStructureLocalProcessEntry(uio_GPDir *topGPDir, + uio_FileBlock *fileBlock, off_t *pos); +#endif +#if zip_USE_HEADERS == zip_USE_CENTRAL_HEADERS +static off_t zip_findEndOfCentralDirectoryRecord(uio_Handle *handle, + uio_FileBlock *fileBlock); +static int zip_fillDirStructureCentral(uio_GPDir *top, uio_Handle *handle); +static int zip_fillDirStructureCentralProcessEntry(uio_GPDir *topGPDir, + uio_FileBlock *fileBlock, off_t *pos); +static int zip_updatePFileDataFromLocalFileHeader(zip_GPFileData *gPFileData, + uio_FileBlock *fileBlock, int pos); +int zip_updateFileDataFromLocalHeader(uio_Handle *handle, + zip_GPFileData *gPFileData); +#endif +static int zip_fillDirStructureProcessExtraFields( + uio_FileBlock *fileBlock, off_t extraFieldLength, + zip_GPFileData *gPFileData, const char *path, off_t pos, + uio_bool central); +static inline int zip_foundFile(uio_GPDir *gPDir, const char *path, + zip_GPFileData *gPFileData); +static inline int zip_foundDir(uio_GPDir *gPDir, const char *dirName, + zip_GPDirData *gPDirData); +static int zip_initZipStream(z_stream *zipStream); +static int zip_unInitZipStream(z_stream *zipStream); +static int zip_reInitZipStream(z_stream *zipStream); + +static voidpf zip_alloc(voidpf opaque, uInt items, uInt size); +static void zip_free(voidpf opaque, voidpf address); + +static inline zip_GPFileData * zip_GPFileData_new(void); +static inline void zip_GPFileData_delete(zip_GPFileData *gPFileData); +static inline zip_GPFileData *zip_GPFileData_alloc(void); +static inline void zip_GPFileData_free(zip_GPFileData *gPFileData); +static inline void zip_GPDirData_delete(zip_GPDirData *gPDirData); +static inline void zip_GPDirData_free(zip_GPDirData *gPDirData); + +static ssize_t zip_readStored(uio_Handle *handle, void *buf, size_t count); +static ssize_t zip_readDeflated(uio_Handle *handle, void *buf, size_t count); +static off_t zip_seekStored(uio_Handle *handle, off_t offset); +static off_t zip_seekDeflated(uio_Handle *handle, off_t offset); + +uio_FileSystemHandler zip_fileSystemHandler = { + /* .init = */ NULL, + /* .unInit = */ NULL, + /* .cleanup = */ NULL, + + /* .mount = */ zip_mount, + /* .umount = */ uio_GPRoot_umount, + + /* .access = */ zip_access, + /* .close = */ zip_close, + /* .fstat = */ zip_fstat, + /* .stat = */ zip_stat, + /* .mkdir = */ NULL, + /* .open = */ zip_open, + /* .read = */ zip_read, + /* .rename = */ NULL, + /* .rmdir = */ NULL, + /* .seek = */ zip_seek, + /* .write = */ NULL, + /* .unlink = */ NULL, + + /* .openEntries = */ uio_GPDir_openEntries, + /* .readEntries = */ uio_GPDir_readEntries, + /* .closeEntries = */ uio_GPDir_closeEntries, + + /* .getPDirEntryHandle = */ uio_GPDir_getPDirEntryHandle, + /* .deletePRootExtra = */ uio_GPRoot_delete, + /* .deletePDirHandleExtra = */ uio_GPDirHandle_delete, + /* .deletePFileHandleExtra = */ uio_GPFileHandle_delete, +}; + +uio_GPRoot_Operations zip_GPRootOperations = { + /* .fillGPDir = */ NULL, + /* .deleteGPRootExtra = */ NULL, + /* .deleteGPDirExtra = */ zip_GPDirData_delete, + /* .deleteGPFileExtra = */ zip_GPFileData_delete, +}; + + +#define NUM_COMPRESSION_METHODS 11 +/* + * [0] = stored uncompressed + * [1] = Shrunk + * [2] = Reduced with compression factor 1 + * [3] = Reduced with compression factor 2 + * [4] = Reduced with compression factor 3 + * [5] = Reduced with compression factor 4 + * [6] = Imploded + * [7] = Reserved for Tokenizing + * [8] = Deflated + * [9] = Deflate64 + * [10] = "PKWARE Data Compression Library Imploding" + */ +static const uio_bool + zip_compressionMethodSupported[NUM_COMPRESSION_METHODS] = { + true, false, false, false, false, false, false, false, + true, false, false }; + +typedef ssize_t (*zip_readFunctionType)(uio_Handle *handle, + void *buf, size_t count); +zip_readFunctionType zip_readMethods[NUM_COMPRESSION_METHODS] = { + zip_readStored, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + zip_readDeflated, NULL, NULL +}; +typedef off_t (*zip_seekFunctionType)(uio_Handle *handle, off_t offset); +zip_seekFunctionType zip_seekMethods[NUM_COMPRESSION_METHODS] = { + zip_seekStored, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + zip_seekDeflated, NULL, NULL +}; + + +typedef enum { + zip_OSType_FAT, + zip_OSType_Amiga, + zip_OSType_OpenVMS, + zip_OSType_UNIX, + zip_OSType_VMCMS, + zip_OSType_AtariST, + zip_OSType_HPFS, + zip_OSType_HFS, + zip_OSType_ZSystem, + zip_OSType_CPM, + zip_OSType_TOPS20, + zip_OSType_NTFS, + zip_OSType_QDOS, + zip_OSType_Acorn, + zip_OSType_VFAT, + zip_OSType_MVS, + zip_OSType_BeOS, + zip_OSType_Tandem, + + zip_numOSTypes +} zip_OSType; + +#if zip_USE_HEADERS == zip_USE_CENTRAL_HEADERS +static mode_t zip_makeFileMode(zip_OSType creatorOS, uio_uint32 modeBytes); +#endif + +#define zip_INPUT_BUFFER_SIZE 0x10000 + // TODO: make this configurable a la sysctl? +#define zip_SEEK_BUFFER_SIZE zip_INPUT_BUFFER_SIZE + + +void +zip_close(uio_Handle *handle) { + zip_Handle *zip_handle; + +#if defined(DEBUG) && DEBUG > 1 + fprintf(stderr, "zip_close - handle=%p\n", (void *) handle); +#endif + zip_handle = handle->native; + uio_GPFile_unref(zip_handle->file); + zip_unInitZipStream(&zip_handle->zipStream); + uio_closeFileBlock(zip_handle->fileBlock); + uio_free(zip_handle); +} + +static void +zip_fillStat(struct stat *statBuf, const zip_GPFileData *gPFileData) { + memset(statBuf, '\0', sizeof (struct stat)); + statBuf->st_size = gPFileData->uncompressedSize; + statBuf->st_uid = gPFileData->uid; + statBuf->st_gid = gPFileData->gid; + statBuf->st_mode = gPFileData->mode; + statBuf->st_atime = gPFileData->atime; + statBuf->st_mtime = gPFileData->mtime; + statBuf->st_ctime = gPFileData->ctime; +} + +int +zip_access(uio_PDirHandle *pDirHandle, const char *name, int mode) { + errno = ENOSYS; // Not implemented. + (void) pDirHandle; + (void) name; + (void) mode; + return -1; + +#if 0 + uio_GPDirEntry *entry; + + if (name[0] == '.' && name[1] == '\0') { + entry = (uio_GPDirEntry *) pDirHandle->extra; + } else { + entry = uio_GPDir_getGPDirEntry(pDirHandle->extra, name); + if (entry == NULL) { + errno = ENOENT; + return -1; + } + } + + if (mode & R_OK) + { + // Read permission is always granted. Nothing to check here. + } + + if (mode & W_OK) { + errno = EACCES; + return -1; + } + + if (mode & X_OK) { + if (uio_GPDirEntry_isDir(entry)) { + // Search permission on directories is always granted. + } else { + // WORK +#error + } + } + + if (mode & F_OK) { + // WORK +#error + } + + return 0; +#endif +} + +int +zip_fstat(uio_Handle *handle, struct stat *statBuf) { +#if zip_USE_HEADERS == zip_USE_CENTRAL_HEADERS + if (handle->native->file->extra->fileOffset == -1) { + // The local header wasn't read in yet. + if (zip_updateFileDataFromLocalHeader(handle->root->handle, + handle->native->file->extra) == -1) { + // errno is set + return -1; + } + } +#endif /* zip_USE_HEADERS == zip_USE_CENTRAL_HEADERS */ + zip_fillStat(statBuf, handle->native->file->extra); + return 0; +} + +int +zip_stat(uio_PDirHandle *pDirHandle, const char *name, struct stat *statBuf) { + uio_GPDirEntry *entry; + + if (name[0] == '.' && name[1] == '\0') { + entry = (uio_GPDirEntry *) pDirHandle->extra; + } else { + entry = uio_GPDir_getGPDirEntry(pDirHandle->extra, name); + if (entry == NULL) { + errno = ENOENT; + return -1; + } + } + + if (uio_GPDirEntry_isDir(entry) && entry->extra == NULL) { + // No information about this directory was stored. + // We'll have to make something up. + memset(statBuf, '\0', sizeof (struct stat)); + statBuf->st_mode = S_IFDIR | S_IRUSR | S_IWUSR | S_IXUSR | + S_IRGRP | S_IWGRP | S_IXOTH | + S_IROTH | S_IWOTH | S_IXOTH; + statBuf->st_uid = 0; + statBuf->st_gid = 0; + return 0; + } + +#if zip_USE_HEADERS == zip_USE_CENTRAL_HEADERS +#ifndef zip_INCOMPLETE_STAT + if (((zip_GPFileData *) entry->extra)->fileOffset == -1) { + // The local header wasn't read in yet. + if (zip_updateFileDataFromLocalHeader(pDirHandle->pRoot->handle, + (zip_GPFileData *) entry->extra) == -1) { + // errno is set + return -1; + } + } +#endif /* !defined(zip_INCOMPLETE_STAT) */ +#endif /* zip_USE_HEADERS == zip_USE_CENTRAL_HEADERS */ + + zip_fillStat(statBuf, (zip_GPFileData *) entry->extra); + return 0; +} + +/* + * Function name: zip_open + * Description: open a file in zip file + * Arguments: pDirHandle - handle to the dir where to open the file + * name - the name of the file to open + * flags - flags, as to stdio open() + * mode - mode, as to stdio open() + * Returns: handle, as from stdio open() + * If failed, errno is set and handle is -1. + */ +uio_Handle * +zip_open(uio_PDirHandle *pDirHandle, const char *name, int flags, + mode_t mode) { + zip_Handle *handle; + uio_GPFile *gPFile; + +#if defined(DEBUG) && DEBUG > 1 + fprintf(stderr, "zip_open - pDirHandle=%p name=%s flags=%d mode=0%o\n", + (void *) pDirHandle, name, flags, mode); +#endif + + if ((flags & O_ACCMODE) != O_RDONLY) { + errno = EACCES; + return NULL; + } + + gPFile = (uio_GPFile *) uio_GPDir_getGPDirEntry(pDirHandle->extra, name); + if (gPFile == NULL) { + errno = ENOENT; + return NULL; + } + +#if zip_USE_HEADERS == zip_USE_CENTRAL_HEADERS + if (gPFile->extra->fileOffset == -1) { + // The local header wasn't read in yet. + if (zip_updateFileDataFromLocalHeader(pDirHandle->pRoot->handle, + gPFile->extra) == -1) { + // errno is set + return NULL; + } + } +#endif + + handle = uio_malloc(sizeof (zip_Handle)); + uio_GPFile_ref(gPFile); + handle->file = gPFile; + handle->fileBlock = uio_openFileBlock2(pDirHandle->pRoot->handle, + gPFile->extra->fileOffset, gPFile->extra->compressedSize); + if (handle->fileBlock == NULL) { + // errno is set + return NULL; + } + + if (zip_initZipStream(&handle->zipStream) == -1) { + uio_GPFile_unref(gPFile); + uio_closeFileBlock(handle->fileBlock); + return NULL; + } + handle->compressedOffset = 0; + handle->uncompressedOffset = 0; + + (void) mode; + return uio_Handle_new(pDirHandle->pRoot, handle, flags); +} + +ssize_t +zip_read(uio_Handle *handle, void *buf, size_t count) { + ssize_t result; + +#if defined(DEBUG) && DEBUG > 1 + fprintf(stderr, "zip_read - handle=%p buf=%p count=%d: ", (void *) handle, + (void *) buf, count); +#endif + result = zip_readMethods[handle->native->file->extra->compressionMethod] + (handle, buf, count); +#if defined(DEBUG) && DEBUG > 1 + fprintf(stderr, "%d\n", result); +#endif + return result; +} + +static ssize_t +zip_readStored(uio_Handle *handle, void *buf, size_t count) { + int numBytes; + zip_Handle *zipHandle; + + zipHandle = handle->native; + numBytes = uio_copyFileBlock(zipHandle->fileBlock, + zipHandle->uncompressedOffset, buf, count); + if (numBytes == -1) { + // errno is set + return -1; + } + zipHandle->uncompressedOffset += numBytes; + zipHandle->compressedOffset += numBytes; + return numBytes; +} + +static ssize_t +zip_readDeflated(uio_Handle *handle, void *buf, size_t count) { + zip_Handle *zipHandle; + int inflateResult; + + zipHandle = handle->native; + + if (count > ((zip_GPFileData *) (zipHandle->file->extra))-> + uncompressedSize - zipHandle->zipStream.total_out) + count = ((zip_GPFileData *) (zipHandle->file->extra))-> + uncompressedSize - zipHandle->zipStream.total_out; + + zipHandle->zipStream.next_out = (Bytef *) buf; + zipHandle->zipStream.avail_out = count; + while (zipHandle->zipStream.avail_out > 0) { + if (zipHandle->zipStream.avail_in == 0) { + ssize_t numBytes; + numBytes = uio_accessFileBlock(zipHandle->fileBlock, + zipHandle->compressedOffset, zip_INPUT_BUFFER_SIZE, + (char **) &zipHandle->zipStream.next_in); + if (numBytes == -1) { + // errno is set + return -1; + } +#if 0 + if (numBytes == 0) { + if (zipHandle->uncompressedOffset != + zipHandle->file->extra->uncompressedSize) { + // premature eof + errno = EIO; + return -1; + } + break; + } +#endif + zipHandle->zipStream.avail_in = numBytes; + zipHandle->compressedOffset += numBytes; + } + inflateResult = inflate(&zipHandle->zipStream, Z_SYNC_FLUSH); + zipHandle->uncompressedOffset = zipHandle->zipStream.total_out; + if (inflateResult == Z_STREAM_END) { + // Everything is decompressed + break; + } + if (inflateResult != Z_OK) { + switch (inflateResult) { + case Z_VERSION_ERROR: + fprintf(stderr, "Error: Incompatible version problem for " + " decompression.\n"); + break; + case Z_NEED_DICT: + fprintf(stderr, "Error: Decompressing requires " + "preset dictionary.\n"); + break; + case Z_DATA_ERROR: + fprintf(stderr, "Error: Compressed file is corrupted.\n"); + break; + case Z_STREAM_ERROR: + // This means zipHandle->zipStream is bad, which is + // most likely an error in the code using zlib. + fprintf(stderr, "Fatal: internal error using zlib.\n"); + abort(); + break; + case Z_MEM_ERROR: + fprintf(stderr, "Error: Not enough memory available " + "while decompressing.\n"); + break; + case Z_BUF_ERROR: + // No progress possible. Probably caused by premature + // end of input file. + fprintf(stderr, "Error: When decompressing: premature " + "end of input file.\n"); + errno = EIO; + return -1; +#if 0 + // If this happens, either the input buffer is empty + // or the output buffer is full. This should not happen. + fprintf(stderr, "Fatal: internal error using zlib: " + " no progress is possible in decompression.\n"); + abort(); + break; +#endif + default: + fprintf(stderr, "Fatal: unknown error from inflate().\n"); + abort(); + } + if (zipHandle->zipStream.msg != NULL) + fprintf(stderr, "ZLib reports: %s\n", + zipHandle->zipStream.msg); + errno = EIO; + // Using EIO to report an error in the backend. + return -1; + } + } + return count - zipHandle->zipStream.avail_out; +} + +off_t +zip_seek(uio_Handle *handle, off_t offset, int whence) { + zip_Handle *zipHandle; + +#if defined(DEBUG) && DEBUG > 1 + fprintf(stderr, "zip_seek - handle=%p offset=%d whence=%s\n", + (void *) handle, (int) offset, + whence == SEEK_SET ? "SEEK_SET" : + whence == SEEK_CUR ? "SEEK_CUR" : + whence == SEEK_END ? "SEEK_END" : "INVALID"); +#endif + zipHandle = handle->native; + + assert(whence == SEEK_SET || whence == SEEK_CUR || whence == SEEK_END); + switch(whence) { + case SEEK_SET: + break; + case SEEK_CUR: + offset += zipHandle->uncompressedOffset; + break; + case SEEK_END: + offset += zipHandle->file->extra->uncompressedSize; + break; + } + if (offset < 0) { + offset = 0; + } else if (offset > zipHandle->file->extra->uncompressedSize) { + offset = zipHandle->file->extra->uncompressedSize; + } + return zip_seekMethods[handle->native->file->extra->compressionMethod] + (handle, offset); +} + +static off_t +zip_seekStored(uio_Handle *handle, off_t offset) { + zip_Handle *zipHandle; + + zipHandle = handle->native; + if (offset > zipHandle->file->extra->uncompressedSize) + offset = zipHandle->file->extra->uncompressedSize; + + zipHandle->compressedOffset = offset; + zipHandle->uncompressedOffset = offset; + return offset; +} + +static off_t +zip_seekDeflated(uio_Handle *handle, off_t offset) { + zip_Handle *zipHandle; + + zipHandle = handle->native; + + if (offset < zipHandle->uncompressedOffset) { + // The new offset is earlier than the current offset. We need to + // seek from the beginning. + if (zip_reInitZipStream(&zipHandle->zipStream) == -1) { + // Need to abort. Handle would get in an inconsistent state. + // Should not fail anyhow. + fprintf(stderr, "Fatal: Could not reinitialise zip stream: " + "%s.\n", strerror(errno)); + abort(); + } + zipHandle->compressedOffset = 0; + zipHandle->uncompressedOffset = 0; + } + + if (offset == zipHandle->uncompressedOffset) + return offset; + + // Seek from the current position. + { + char *buffer; + ssize_t numRead; + size_t toRead; + + buffer = uio_malloc(zip_SEEK_BUFFER_SIZE); + toRead = offset - zipHandle->uncompressedOffset; + while (toRead > 0) { + numRead = zip_read(handle, buffer, + toRead < zip_SEEK_BUFFER_SIZE ? + toRead : zip_SEEK_BUFFER_SIZE); + if (numRead == -1) { + fprintf(stderr, "Warning: Could not read zipped file: %s\n", + strerror(errno)); + break; + // The current location is returned. + } + toRead -= numRead; + } + uio_free(buffer); + } + return zipHandle->uncompressedOffset; +} + +uio_PRoot * +zip_mount(uio_Handle *handle, int flags) { + uio_PRoot *result; + uio_PDirHandle *rootDirHandle; + + if ((flags & uio_MOUNT_RDONLY) != uio_MOUNT_RDONLY) { + errno = EACCES; + return NULL; + } + + uio_Handle_ref(handle); + result = uio_GPRoot_makePRoot( + uio_getFileSystemHandler(uio_FSTYPE_ZIP), flags, + &zip_GPRootOperations, NULL, uio_GPRoot_PERSISTENT, + handle, NULL, uio_GPDir_COMPLETE); + + rootDirHandle = uio_PRoot_getRootDirHandle(result); + if (zip_fillDirStructure(rootDirHandle->extra, handle) == -1) { + int savedErrno = errno; +#ifdef DEBUG + fprintf(stderr, "Error: failed to read the zip directory " + "structure - %s.\n", strerror(errno)); +#endif + uio_GPRoot_umount(result); + errno = savedErrno; + return NULL; + } + + return result; +} + +static int +zip_fillDirStructure(uio_GPDir *top, uio_Handle *handle) { +#if zip_USE_HEADERS == zip_USE_CENTRAL_HEADERS + return zip_fillDirStructureCentral(top, handle); +#endif +#if zip_USE_HEADERS == zip_USE_LOCAL_HEADERS + return zip_fillDirStructureLocal(top, handle); +#endif +} + +#if zip_USE_HEADERS == zip_USE_CENTRAL_HEADERS +static int +zip_fillDirStructureCentral(uio_GPDir *top, uio_Handle *handle) { + uio_FileBlock *fileBlock; + off_t pos; + char *buf; + ssize_t numBytes; + uio_uint16 numEntries; + // TODO: use numEntries to initialise the hash table + // to a smart size + off_t eocdr; + off_t startCentralDir; + + fileBlock = uio_openFileBlock(handle); + if (fileBlock == NULL) { + // errno is set + goto err; + } + + // first find the 'End of Central Directory Record' + eocdr = zip_findEndOfCentralDirectoryRecord(handle, fileBlock); + if (eocdr == -1) { + // errno is set + goto err; + } + + numBytes = uio_accessFileBlock(fileBlock, eocdr, 22, &buf); + if (numBytes == -1) { + // errno is set + goto err; + } + if (numBytes != 22) { + errno = EIO; + goto err; + } + + numEntries = makeUInt16(buf[10], buf[11]); + if (numEntries == 0xffff) { + fprintf(stderr, "Error: Zip64 .zip files are not supported.\n"); + errno = ENOSYS; + goto err; + } + + startCentralDir = makeUInt32(buf[16], buf[17], buf[18], buf[19]); + + // Enable read-ahead buffering, for speed. + uio_setFileBlockUsageHint(fileBlock, uio_FB_USAGE_FORWARD, + DIR_STRUCTURE_READ_BUFSIZE); + + pos = startCentralDir; + while (numEntries--) { + if (zip_fillDirStructureCentralProcessEntry(top, fileBlock, &pos) + == -1) { + // errno is set + goto err; + } + } + + uio_closeFileBlock(fileBlock); + return 0; + +err: + { + int savedErrno = errno; + + if (fileBlock != NULL) + uio_closeFileBlock(fileBlock); + errno = savedErrno; + return -1; + } +} + +static int +zip_fillDirStructureCentralProcessEntry(uio_GPDir *topGPDir, + uio_FileBlock *fileBlock, off_t *pos) { + char *buf; + zip_GPFileData *gPFileData; + ssize_t numBytes; + + uio_uint32 signature; + uio_uint16 lastModTime; + uio_uint16 lastModDate; + uio_uint32 crc; + uio_uint16 fileNameLength; + uio_uint16 extraFieldLength; + uio_uint16 fileCommentLength; + char *fileName; + zip_OSType creatorOS; + + off_t nextEntryOffset; + + numBytes = uio_accessFileBlock(fileBlock, *pos, 46, &buf); + if (numBytes != 46) + return zip_badFile(NULL, NULL); + + signature = makeUInt32(buf[0], buf[1], buf[2], buf[3]); + if (signature != 0x02014b50) { + fprintf(stderr, "Error: Premature end of central directory.\n"); + errno = EIO; + return -1; + } + + gPFileData = zip_GPFileData_new(); + creatorOS = (zip_OSType) buf[5]; + gPFileData->compressionFlags = makeUInt16(buf[8], buf[9]); + gPFileData->compressionMethod = makeUInt16(buf[10], buf[11]); + lastModTime = makeUInt16(buf[12], buf[13]); + lastModDate = makeUInt16(buf[14], buf[15]); + gPFileData->atime = (time_t) 0; + gPFileData->mtime = dosToUnixTime(lastModDate, lastModTime); + gPFileData->ctime = (time_t) 0; + crc = makeUInt32(buf[16], buf[17], buf[18], buf[19]); + gPFileData->compressedSize = + makeUInt32(buf[20], buf[21], buf[22], buf[23]); + gPFileData->uncompressedSize = + makeUInt32(buf[24], buf[25], buf[26], buf[27]); + fileNameLength = makeUInt16(buf[28], buf[29]); + extraFieldLength = makeUInt16(buf[30], buf[31]); + fileCommentLength = makeUInt16(buf[32], buf[33]); + gPFileData->uid = 0; + gPFileData->gid = 0; + gPFileData->mode = zip_makeFileMode(creatorOS, + makeUInt32(buf[38], buf[39], buf[40], buf[41])); + + gPFileData->headerOffset = + (off_t) makeSInt32(buf[42], buf[43], buf[44], buf[45]); + gPFileData->fileOffset = (off_t) -1; + + *pos += 46; + nextEntryOffset = *pos + fileNameLength + extraFieldLength + + fileCommentLength; + + numBytes = uio_accessFileBlock(fileBlock, *pos, fileNameLength, &buf); + if (numBytes != fileNameLength) + return zip_badFile(gPFileData, NULL); + fileName = uio_malloc(fileNameLength + 1); + memcpy(fileName, buf, fileNameLength); + fileName[fileNameLength] = '\0'; + *pos += fileNameLength; + + if (gPFileData->compressionMethod >= NUM_COMPRESSION_METHODS || + !zip_compressionMethodSupported[gPFileData->compressionMethod]) { + fprintf(stderr, "Warning: File '%s' is compressed with " + "unsupported method %d - skipped.\n", fileName, + gPFileData->compressionMethod); + *pos = nextEntryOffset; + zip_GPFileData_delete(gPFileData); + return 0; + } + + if (gPFileData->compressedSize == (off_t) 0xffffffff || + gPFileData->uncompressedSize == (off_t) 0xffffffff || + gPFileData->headerOffset < 0) { + fprintf(stderr, "Warning: Skipping Zip64 file '%s'\n", fileName); + *pos = nextEntryOffset; + zip_GPFileData_delete(gPFileData); + return 0; + } + + if (isBitSet(gPFileData->compressionFlags, 0)) { + fprintf(stderr, "Warning: Skipping encrypted file '%s'\n", fileName); + *pos = nextEntryOffset; + zip_GPFileData_delete(gPFileData); + return 0; + } + + switch (zip_fillDirStructureProcessExtraFields(fileBlock, + extraFieldLength, gPFileData, fileName, *pos, true)) { + case 0: // file is ok + break; + case 1: // file is not acceptable - skip file + *pos = nextEntryOffset; + zip_GPFileData_delete(gPFileData); + uio_free(fileName); + return 0; + case -1: + return zip_badFile(gPFileData, fileName); + } + + *pos += extraFieldLength; + + // If ctime or atime is 0, they will be filled in when the local + // file header is read. + + if (S_ISREG(gPFileData->mode)) { + if (zip_foundFile(topGPDir, fileName, gPFileData) == -1) { + if (errno == EISDIR) { + zip_GPFileData_delete(gPFileData); + uio_free(fileName); + return 0; + } + return zip_badFile(gPFileData, fileName); + } + +#if defined(DEBUG) && DEBUG > 1 + fprintf(stderr, "Debug: Found file '%s'.\n", fileName); +#endif + } else if (S_ISDIR(gPFileData->mode)) { + if (fileName[fileNameLength - 1] == '/') + fileName[fileNameLength - 1] = '\0'; + if (zip_foundDir(topGPDir, fileName, gPFileData) == -1) { + if (errno == EISDIR) { + fprintf(stderr, "Warning: file '%s' already exists as a dir - " + "skipped.\n", fileName); + zip_GPFileData_delete(gPFileData); + uio_free(fileName); + return 0; + } + return zip_badFile(gPFileData, fileName); + } +#if defined(DEBUG) && DEBUG > 1 + fprintf(stderr, "Debug: Found dir '%s'.\n", fileName); +#endif + } else { + fprintf(stderr, "Warning: '%s' is not a regular file, nor a " + "directory - skipped.\n", fileName); + zip_GPFileData_delete(gPFileData); + uio_free(fileName); + return 0; + } + + uio_free(fileName); + return 0; +} + +static off_t +zip_findEndOfCentralDirectoryRecord(uio_Handle *handle, + uio_FileBlock *fileBlock) { + off_t fileSize; + off_t endPos, startPos; + char *buf, *bufPtr; + ssize_t bufLen; + + struct stat statBuf; + if (uio_fstat(handle, &statBuf) == -1) { + // errno is set + return -1; + } + fileSize = statBuf.st_size; + startPos = fileSize - 0xffff - 22; // max comment and record size + if (startPos < 0) + startPos = 0; + endPos = fileSize - 22; // last position to be checked + bufLen = uio_accessFileBlock(fileBlock, startPos, endPos - startPos + 4, + &buf); + if (bufLen == -1) { + int savedErrno = errno; + fprintf(stderr, "Error: Read error while searching for " + "'end-of-central-directory record'.\n"); + errno = savedErrno; + return -1; + } + if (bufLen != endPos - startPos + 4) { + fprintf(stderr, "Error: Read error while searching for " + "'end-of-central-directory record'.\n"); + errno = EIO; + return -1; + } + bufPtr = buf + (endPos - startPos); + while (1) { + if (bufPtr < buf) { + fprintf(stderr, "Error: Zip file corrupt; could not find " + "'end-of-central-directory record'.\n"); + errno = EIO; + return -1; + } + if (bufPtr[0] == 0x50 && bufPtr[1] == 0x4b && bufPtr[2] == 0x05 && + bufPtr[3] == 0x06) + break; + bufPtr--; + } + return startPos + (bufPtr - buf); +} + +static mode_t +zip_makeFileMode(zip_OSType creatorOS, uio_uint32 modeBytes) { + switch (creatorOS) { + case zip_OSType_FAT: + case zip_OSType_NTFS: + case zip_OSType_VFAT: { + // Only the least signigicant byte is relevant. + mode_t mode; + + if (modeBytes == 0) { + // File came from standard input + return S_IFREG | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | + S_IROTH | S_IWOTH; + } + if (modeBytes & 0x10) { + // Directory + mode = S_IFDIR | S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | + S_IROTH | S_IXOTH; + } else { + // Regular file + mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; + } + if (modeBytes & 0x01) { + // readonly + return mode; + } else { + // Write allowed + return mode | S_IWUSR | S_IWGRP | S_IWOTH; + } + } + case zip_OSType_UNIX: + return (mode_t) (modeBytes >> 16); + default: + fprintf(stderr, "Warning: file created by unknown OS.\n"); + return S_IFREG | S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; + } +} + +// If the data is read from the central directory, certain data +// will need to be updated from the local directory when it is needed. +// This function does that. +// returns 0 for success, -1 for error (errno is set) +// NB: Only the fields that may offer new information are checked. +// the fields that were already read from the central directory +// aren't verified. +static int +zip_updatePFileDataFromLocalFileHeader(zip_GPFileData *gPFileData, + uio_FileBlock *fileBlock, int pos) { + ssize_t numBytes; + char *buf; + uio_uint32 signature; + uio_uint16 fileNameLength; + uio_uint16 extraFieldLength; + + numBytes = uio_accessFileBlock(fileBlock, pos, 30, &buf); + if (numBytes != 30) { + errno = EIO; + return -1; + } + signature = makeUInt32(buf[0], buf[1], buf[2], buf[3]); + if (signature != 0x04034b50) { + errno = EIO; + return -1; + } + fileNameLength = makeUInt16(buf[26], buf[27]); + extraFieldLength = makeUInt16(buf[28], buf[29]); + pos += 30 + fileNameLength; + + switch (zip_fillDirStructureProcessExtraFields(fileBlock, + extraFieldLength, gPFileData, "<unknown>", pos, false)) { + case 0: // file is ok + break; + case 1: + // File is not acceptable (but according to the central header + // it was) + fprintf(stderr, "Warning: according to the central directory " + "of a zip file, some file inside is acceptable, " + "but according to the local header it isn't.\n"); + errno = EIO; + return -1; + case -1: + errno = EIO; + return -1; + } + pos += extraFieldLength; + gPFileData->fileOffset = pos; + return 0; +} +#endif /* zip_USE_HEADERS == zip_USE_CENTRAL_HEADERS */ + +#if zip_USE_HEADERS == zip_USE_LOCAL_HEADERS +static int +zip_fillDirStructureLocal(uio_GPDir *top, uio_Handle *handle) { + uio_FileBlock *fileBlock; + off_t pos; + char *buf; + ssize_t numBytes; + + pos = uio_lseek(handle, 0, SEEK_SET); + if (pos == -1) { + int savedErrno = errno; + errno = savedErrno; + return -1; + } + if (pos != 0) { + errno = EIO; + // Using EIO to report an error in the backend. + return -1; + } + + // We read all the files from the beginning of the zip file to the end. + // (the directory record at the end of the file is ignored) + fileBlock = uio_openFileBlock(handle); + if (fileBlock == NULL) { + // errno is set + return -1; + } + + pos = 0; + while (1) { + uio_uint32 signature; + + numBytes = uio_accessFileBlock(fileBlock, pos, 4, &buf); + if (numBytes == -1) + goto err; + if (numBytes != 4) + break; + signature = makeUInt32(buf[0], buf[1], buf[2], buf[3]); + if (signature != 0x04034b50) { + // End of file data reached. + break; + } + pos += 4; + if (zip_fillDirStructureLocalProcessEntry(top, fileBlock, &pos) == -1) + goto err; + } + + uio_closeFileBlock(fileBlock); + return 0; + +err: + { + int savedErrno = errno; + + uio_closeFileBlock(fileBlock); + errno = savedErrno; + return -1; + } +} + +static int +zip_fillDirStructureLocalProcessEntry(uio_GPDir *topGPDir, + uio_FileBlock *fileBlock, off_t *pos) { + char *buf; + zip_GPFileData *gPFileData; + ssize_t numBytes; + + uio_uint16 lastModTime; + uio_uint16 lastModDate; + uio_uint32 crc; + uio_uint16 fileNameLength; + uio_uint16 extraFieldLength; + char *fileName; + + off_t nextEntryOffset; + + numBytes = uio_accessFileBlock(fileBlock, *pos, 26, &buf); + if (numBytes != 26) + return zip_badFile(NULL, NULL); + + gPFileData = zip_GPFileData_new(); + gPFileData->compressionFlags = makeUInt16(buf[2], buf[3]); + gPFileData->compressionMethod = makeUInt16(buf[4], buf[5]); + lastModTime = makeUInt16(buf[6], buf[7]); + lastModDate = makeUInt16(buf[8], buf[9]); + gPFileData->atime = (time_t) 0; + gPFileData->mtime = dosToUnixTime(lastModDate, lastModTime); + gPFileData->ctime = (time_t) 0; + gPFileData->uid = 0; + gPFileData->gid = 0; + gPFileData->mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; + if (!isBitSet(gPFileData->compressionFlags, 3)) { + // If bit 3 is not set, this info will be in the data descriptor + // behind the file data. + crc = makeUInt32(buf[10], buf[11], buf[12], buf[13]); + gPFileData->compressedSize = + makeUInt32(buf[14], buf[15], buf[16], buf[17]); + gPFileData->uncompressedSize = + makeUInt32(buf[18], buf[19], buf[20], buf[21]); + } + fileNameLength = makeUInt16(buf[22], buf[23]); + extraFieldLength = makeUInt16(buf[24], buf[25]); + *pos += 26; + nextEntryOffset = *pos + fileNameLength + extraFieldLength + + gPFileData->compressedSize; + if (isBitSet(gPFileData->compressionFlags, 3)) { + // There's a data descriptor present behind the file data. + nextEntryOffset += 16; + } + + if (gPFileData->compressionMethod >= NUM_COMPRESSION_METHODS || + !zip_compressionMethodSupported[gPFileData->compressionMethod]) { + fprintf(stderr, "Warning: File '%s' is compressed with " + "unsupported method %d - skipped.\n", fileName, + gPFileData->compressionMethod); + *pos = nextEntryOffset; + zip_GPFileData_delete(gPFileData); + return 0; + } + + if (gPFileData->compressedSize == (off_t) 0xffffffff || + gPFileData->uncompressedSize == (off_t) 0xffffffff) { + fprintf(stderr, "Warning: Skipping Zip64 file '%s'\n", fileName); + *pos = nextEntryOffset; + zip_GPFileData_delete(gPFileData); + return 0; + } + + if (isBitSet(gPFileData->compressionFlags, 0)) { + fprintf(stderr, "Warning: Skipping encrypted file '%s'\n", fileName); + *pos = nextEntryOffset; + zip_GPFileData_delete(gPFileData); + return 0; + } + + numBytes = uio_accessFileBlock(fileBlock, *pos, fileNameLength, &buf); + if (numBytes != fileNameLength) + return zip_badFile(gPFileData, NULL); + *pos += fileNameLength; + if (buf[fileNameLength - 1] == '/') { + gPFileData->mode |= S_IFDIR; + fileNameLength--; + } else + gPFileData->mode |= S_IFREG; + fileName = uio_malloc(fileNameLength + 1); + memcpy(fileName, buf, fileNameLength); + fileName[fileNameLength] = '\0'; + + switch (zip_fillDirStructureProcessExtraFields(fileBlock, + extraFieldLength, gPFileData, fileName, *pos, false)) { + case 0: // file is ok + break; + case 1: // file is not acceptable - skip file + *pos = nextEntryOffset; + zip_GPFileData_delete(gPFileData); + uio_free(fileName); + return 0; + case -1: + return zip_badFile(gPFileData, fileName); + } + *pos += extraFieldLength; + + gPFileData->fileOffset = *pos; + + *pos += gPFileData->compressedSize; + if (isBitSet(gPFileData->compressionFlags, 3)) { + // Now comes a data descriptor. + // The PKWare version (which was never used) misses the signature. + // The InfoZip version is used below. + uio_uint32 signature; + + numBytes = uio_accessFileBlock(fileBlock, *pos, 16, &buf); + if (numBytes != 16) + return zip_badFile(gPFileData, fileName); + + signature = makeUInt32(buf[0], buf[1], buf[2], buf[3]); + if (signature != 0x08074b50) + return zip_badFile(gPFileData, fileName); + crc = makeUInt32(buf[4], buf[5], buf[6], buf[7]); + gPFileData->compressedSize = + makeUInt32(buf[8], buf[9], buf[10], buf[11]); + gPFileData->uncompressedSize = + makeUInt32(buf[12], buf[13], buf[14], buf[15]); + } + + if (gPFileData->ctime == (time_t) 0) + gPFileData->ctime = gPFileData->mtime; + + if (gPFileData->atime == (time_t) 0) + gPFileData->atime = gPFileData->mtime; + + if (S_ISREG(gPFileData->mode)) { + if (zip_foundFile(topGPDir, fileName, gPFileData) == -1) { + if (errno == EISDIR) { + zip_GPFileData_delete(gPFileData); + uio_free(fileName); + return 0; + } + return zip_badFile(gPFileData, fileName); + } + +#if defined(DEBUG) && DEBUG > 1 + fprintf(stderr, "Debug: Found file '%s'.\n", fileName); +#endif + } else if (S_ISDIR(gPFileData->mode)) { + if (fileName[fileNameLength - 1] == '/') + fileName[fileNameLength - 1] = '\0'; + if (zip_foundDir(topGPDir, fileName, gPFileData) == -1) { + if (errno == EISDIR) { + fprintf(stderr, "Warning: file '%s' already exists as a dir - " + "skipped.\n", fileName); + zip_GPFileData_delete(gPFileData); + uio_free(fileName); + return 0; + } + return zip_badFile(gPFileData, fileName); + } +#if defined(DEBUG) && DEBUG > 1 + fprintf(stderr, "Debug: Found dir '%s'.\n", fileName); +#endif + } else { + fprintf(stderr, "Warning: '%s' is not a regular file, nor a " + "directory - skipped.\n", fileName); + zip_GPFileData_delete(gPFileData); + uio_free(fileName); + return 0; + } + + uio_free(fileName); + return 0; +} +#endif /* zip_USE_HEADERS == zip_USE_LOCAL_HEADERS */ + +#if zip_USE_HEADERS == zip_USE_CENTRAL_HEADERS +int +zip_updateFileDataFromLocalHeader(uio_Handle *handle, + zip_GPFileData *gPFileData) { + uio_FileBlock *fileBlock; + + fileBlock = uio_openFileBlock(handle); + if (fileBlock == NULL) { + // errno is set + return -1; + } + if (zip_updatePFileDataFromLocalFileHeader(gPFileData, + fileBlock, gPFileData->headerOffset) == -1) { + int savedErrno = errno; + uio_closeFileBlock(fileBlock); + errno = savedErrno; + return -1; + } + if (gPFileData->ctime == (time_t) 0) + gPFileData->ctime = gPFileData->mtime; + if (gPFileData->atime == (time_t) 0) + gPFileData->atime = gPFileData->mtime; + uio_closeFileBlock(fileBlock); + return 0; +} +#endif + +// If the zip file is bad, -1 is returned (no errno set!) +// If the file in the zip file should be skipped, 1 is returned. +// If the file in the zip file is ok, 0 is returned. +static int +zip_fillDirStructureProcessExtraFields(uio_FileBlock *fileBlock, + off_t extraFieldLength, zip_GPFileData *gPFileData, + const char *fileName, off_t pos, uio_bool central) { + off_t posEnd; + uio_uint16 headerID; + ssize_t dataSize; + ssize_t numBytes; + char *buf; + + posEnd = pos + extraFieldLength; + while (pos < posEnd) { + numBytes = uio_accessFileBlock(fileBlock, pos, 4, &buf); + if (numBytes != 4) + return -1; + headerID = makeUInt16(buf[0], buf[1]); + dataSize = (ssize_t) makeUInt16(buf[2], buf[3]); + pos += 4; + numBytes = uio_accessFileBlock(fileBlock, pos, dataSize, &buf); + if (numBytes != dataSize) + return -1; + switch(headerID) { + case 0x000d: // 'Unix0' + // fallthrough + case 0x5855: // 'Unix1' + gPFileData->atime = (time_t) makeUInt32( + buf[0], buf[1], buf[2], buf[3]); + gPFileData->mtime = (time_t) makeUInt32( + buf[4], buf[5], buf[6], buf[7]); + if (central) + break; + if (dataSize > 8) { + gPFileData->uid = (uid_t) makeUInt16(buf[8], buf[9]); + gPFileData->gid = (uid_t) makeUInt16(buf[10], buf[11]); + } + // Unix0 has an extra (ignored) field at the end. + break; + case 0x5455: { // 'time' + uio_uint8 flags; + const char *bufPtr; + flags = buf[0]; + bufPtr = buf + 1; + if (isBitSet(flags, 0)) { + // modification time is present + gPFileData->mtime = (time_t) makeUInt32( + bufPtr[0], bufPtr[1], bufPtr[2], bufPtr[3]); + bufPtr += 4; + } + // The flags field, even in the central header, specifies what + // is present in the local header. + // The central header only contains a field for the mtime + // when it is present in the local header too, and + // never contains fields for other times. + if (central) + break; + if (isBitSet(flags, 1)) { + // modification time is present + gPFileData->atime = (time_t) makeUInt32( + bufPtr[0], bufPtr[1], bufPtr[2], bufPtr[3]); + bufPtr += 4; + } + // Creation time and possible other times are skipped. + break; + } + case 0x7855: // 'Unix2' + if (central) + break; + gPFileData->uid = (uid_t) makeUInt16(buf[0], buf[1]); + gPFileData->gid = (uid_t) makeUInt16(buf[2], buf[3]); + break; + case 0x756e: { // 'Unix3' + mode_t mode; + mode = (mode_t) makeUInt16(buf[4], buf[5]); + if (!S_ISREG(mode) && !S_ISDIR(mode)) { + fprintf(stderr, "Warning: Skipping '%s'; not a regular " + "file, nor a directory.\n", fileName); + return 1; + } + gPFileData->uid = (uid_t) makeUInt16(buf[10], buf[11]); + gPFileData->gid = (uid_t) makeUInt16(buf[12], buf[13]); + break; + } + default: +#ifdef DEBUG + fprintf(stderr, "Debug: Extra field 0x%04x unsupported, " + "used for file '%s' - ignored.\n", headerID, + fileName); +#endif + break; + } // switch + pos += dataSize; + } // while + if (pos != posEnd) + return -1; + return 0; +} + +static int +zip_badFile(zip_GPFileData *gPFileData, char *fileName) { + fprintf(stderr, "Error: Bad file format for .zip file.\n"); + if (gPFileData != NULL) + zip_GPFileData_delete(gPFileData); + if (fileName != NULL) + uio_free(fileName); + errno = EINVAL; // Is this the best choice? + return -1; +} + +static inline int +zip_foundFile(uio_GPDir *gPDir, const char *path, zip_GPFileData *gPFileData) { + uio_GPFile *file; + size_t pathLen; + const char *rest; + const char *pathEnd; + const char *start, *end; + char *buf; + + if (path[0] == '/') + path++; + pathLen = strlen(path); + if (path[pathLen - 1] == '/') { + fprintf(stderr, "Warning: '%s' is not a valid file name - skipped.\n", + path); + errno = EISDIR; + return -1; + } + pathEnd = path + pathLen; + + switch (uio_walkGPPath(gPDir, path, pathLen, &gPDir, &rest)) { + case 0: + // The entire path was matched. The last part was not supposed + // to be a dir. + fprintf(stderr, "Warning: '%s' already exists as a dir - " + "skipped.\n", path); + errno = EISDIR; + return -1; + case ENOTDIR: + fprintf(stderr, "Warning: A component to '%s' is not a " + "directory - file skipped.\n", path); + errno = ENOTDIR; + return -1; + case ENOENT: + break; + } + + buf = uio_malloc(pathLen + 1); + getFirstPathComponent(rest, pathEnd, &start, &end); + while (1) { + uio_GPDir *newGPDir; + + if (end == start || (end - start == 1 && start[0] == '.') || + (end - start == 2 && start[0] == '.' && start[1] == '.')) { + fprintf(stderr, "Warning: file '%s' has an invalid path - " + "skipped.\n", path); + uio_free(buf); + errno = EINVAL; + return -1; + } + if (end == pathEnd) { + // This is the last component; it should be the name of the dir. + rest = start; + break; + } + memcpy(buf, start, end - start); + buf[end - start] = '\0'; + newGPDir = uio_GPDir_prepareSubDir(gPDir, buf); + newGPDir->flags |= uio_GPDir_COMPLETE; + // It will be complete when we're done adding + // all files, and it won't be used before that. + uio_GPDir_commitSubDir(gPDir, buf, newGPDir); + + gPDir = newGPDir; + getNextPathComponent(pathEnd, &start, &end); + } + + uio_free(buf); + + file = uio_GPFile_new(gPDir->pRoot, (uio_GPFileExtra) gPFileData, + uio_gPFileFlagsFromPRootFlags(gPDir->pRoot->flags)); + uio_GPDir_addFile(gPDir, rest, file); + return 0; +} + +static inline int +zip_foundDir(uio_GPDir *gPDir, const char *path, zip_GPDirData *gPDirData) { + size_t pathLen; + const char *pathEnd; + const char *rest; + const char *start, *end; + char *buf; + + if (path[0] == '/') + path++; + pathLen = strlen(path); + pathEnd = path + pathLen; + + switch (uio_walkGPPath(gPDir, path, pathLen, &gPDir, &rest)) { + case 0: + // The dir already exists. Only need to add gPDirData + if (gPDir->extra != NULL) { + fprintf(stderr, "Warning: '%s' is present more than once " + "in the zip file. The last entry will be used.\n", + path); + zip_GPDirData_delete(gPDir->extra); + } + gPDir->extra = gPDirData; + return 0; + case ENOTDIR: + fprintf(stderr, "Warning: A component of '%s' is not a " + "directory - file skipped.\n", path); + errno = ENOTDIR; + return -1; + case ENOENT: + break; + } + + buf = uio_malloc(pathLen + 1); + getFirstPathComponent(rest, pathEnd, &start, &end); + while (start < pathEnd) { + uio_GPDir *newGPDir; + + if (end == start || (end - start == 1 && start[0] == '.') || + (end - start == 2 && start[0] == '.' && start[1] == '.')) { + fprintf(stderr, "Warning: directory '%s' has an invalid path - " + "skipped.\n", path); + uio_free(buf); + errno = EINVAL; + return -1; + } + memcpy(buf, start, end - start); + buf[end - start] = '\0'; + newGPDir = uio_GPDir_prepareSubDir(gPDir, buf); + newGPDir->flags |= uio_GPDir_COMPLETE; + // It will be complete when we're done adding + // all files, and it won't be used before that. + uio_GPDir_commitSubDir(gPDir, buf, newGPDir); + + gPDir = newGPDir; + getNextPathComponent(pathEnd, &start, &end); + } + gPDir->extra = gPDirData; + + uio_free(buf); + return 0; +} + +static int +zip_initZipStream(z_stream *zipStream) { + int retVal; + + zipStream->next_in = Z_NULL; + zipStream->avail_in = 0; + zipStream->zalloc = zip_alloc; + zipStream->zfree = zip_free; + zipStream->opaque = NULL; + retVal = inflateInit2(zipStream, -MAX_WBITS); + // Negative window size means that no zlib header is present. + // This feature is undocumented in zlib, but it's used + // in the minizip program from the zlib contrib dir. + // The absolute value is used as real Window size. + if (retVal != Z_OK) { + switch (retVal) { + case Z_MEM_ERROR: + fprintf(stderr, "Error: Not enough memory available for " + " decompression.\n"); + break; + case Z_VERSION_ERROR: + fprintf(stderr, "Error: Incompatible version problem for " + " decompression.\n"); + break; + default: + fprintf(stderr, "Fatal: unknown error from inflateInit().\n"); + abort(); + } + if (zipStream->msg != NULL) + fprintf(stderr, "ZLib reports: %s\n", zipStream->msg); + errno = EIO; + // Using EIO to report an error in the backend. + return -1; + } + return 0; +} + +static int +zip_unInitZipStream(z_stream *zipStream) { + int retVal; + + retVal = inflateEnd(zipStream); + if (retVal != Z_OK) { + switch (retVal) { + case Z_STREAM_ERROR: + // This means zipStream is bad, which is most likely an + // error in the code using zlib. + fprintf(stderr, "Fatal: internal error using zlib.\n"); + abort(); + break; + default: + fprintf(stderr, "Fatal: unknown error from inflateEnd().\n"); + abort(); + } + if (zipStream->msg != NULL) + fprintf(stderr, "ZLib reports: %s\n", zipStream->msg); + errno = EIO; + // Using EIO to report an error in the backend. + return -1; + } + return 0; +} + +static int +zip_reInitZipStream(z_stream *zipStream) { + int retVal; + + zipStream->next_in = Z_NULL; + zipStream->avail_in = 0; + retVal = inflateReset(zipStream); + if (retVal != Z_OK) { + switch (retVal) { + case Z_STREAM_ERROR: + // This means zipStream is bad, which is most likely an + // error in the code using zlib. + fprintf(stderr, "Fatal: internal error using zlib.\n"); + abort(); + break; + default: + fprintf(stderr, "Fatal: unknown error from inflateInit().\n"); + abort(); + } + if (zipStream->msg != NULL) + fprintf(stderr, "ZLib reports: %s\n", zipStream->msg); + errno = EIO; + // Using EIO to report an error in the backend. + return -1; + } + return 0; +} + + +// Used internally by zlib for allocating memory. +static voidpf +zip_alloc(voidpf opaque, uInt items, uInt size) { + (void) opaque; + return (voidpf) uio_calloc((size_t) items, (size_t) size); +} + +// Used internally by zlib for freeing memory. +static void +zip_free(voidpf opaque, voidpf address) { + (void) opaque; + uio_free((void *) address); +} + +static inline zip_GPFileData * +zip_GPFileData_new(void) { + return zip_GPFileData_alloc(); +} + +static inline void +zip_GPFileData_delete(zip_GPFileData *gPFileData) { + zip_GPFileData_free(gPFileData); +} + +static inline zip_GPFileData * +zip_GPFileData_alloc(void) { + zip_GPFileData *result = uio_malloc(sizeof (zip_GPFileData)); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugAlloc(zip_GPFileData, (void *) result); +#endif + return result; +} + +static inline void +zip_GPFileData_free(zip_GPFileData *gPFileData) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugFree(zip_GPFileData, (void *) gPFileData); +#endif + uio_free(gPFileData); +} + +static inline void +zip_GPDirData_delete(zip_GPDirData *gPDirData) { + zip_GPDirData_free(gPDirData); +} + +static inline void +zip_GPDirData_free(zip_GPDirData *gPDirData) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugFree(zip_GPFileData, (void *) gPDirData); +#endif + uio_free(gPDirData); +} + + + diff --git a/src/libs/uio/zip/zip.h b/src/libs/uio/zip/zip.h new file mode 100644 index 0000000..4b2762f --- /dev/null +++ b/src/libs/uio/zip/zip.h @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +typedef struct zip_Handle *uio_NativeHandle; +typedef void *uio_GPRootExtra; +typedef struct zip_GPFileData *uio_GPFileExtra; +typedef struct zip_GPFileData *uio_GPDirExtra; +typedef struct uio_GPDirEntries_Iterator *uio_NativeEntriesContext; + +#define uio_INTERNAL_PHYSICAL + +#include "../gphys.h" +#include "../iointrn.h" +#include "../uioport.h" +#include "../physical.h" +#include "../types.h" +#include "../fileblock.h" + +#include <zlib.h> +#include <sys/types.h> +#include <sys/stat.h> + +// zip_USE_HEADERS determinines what header for files within a .zip file +// is used when building the directory structure. +// Set to 'zip_USE_CENTRAL_HEADERS' to use the central directory header, +// set to 'zip_USE_LOCAL_HEADERS' to use the local file header. +// Central is highly adviced: it uses much less seeking, and hence is much +// faster. +#define zip_USE_HEADERS zip_USE_CENTRAL_HEADERS +#define zip_USE_CENTRAL_HEADERS 1 +#define zip_USE_LOCAL_HEADERS 2 + +#define zip_INCOMPLETE_STAT + // Ignored unless zip_USE_HEADERS == zip_USE_CENTRAL_HEADERS. + // If defined, extra meta-data for files in the .zip archive + // isn't retrieved from the local file header when zip_stat() + // is called. The uid, gid, file mode, and file times may be + // inaccurate. The advantage is that a possibly costly seek and + // read can be avoided. + +typedef struct zip_GPFileData { + off_t compressedSize; + off_t uncompressedSize; + uio_uint16 compressionFlags; + uio_uint16 compressionMethod; +#if zip_USE_HEADERS == zip_USE_CENTRAL_HEADERS + off_t headerOffset; // start of the local header for this file +#endif + off_t fileOffset; // start of the compressed data in the .zip file + uid_t uid; + gid_t gid; + mode_t mode; + time_t atime; // access time + time_t mtime; // modification time + time_t ctime; // change time +} zip_GPFileData; + +typedef zip_GPFileData zip_GPDirData; +// TODO: some of the fields from zip_GPFileData are not needed for +// directories. A few bytes could be saved here by making a seperate +// structure. + +typedef struct zip_Handle { + uio_GPFile *file; + z_stream zipStream; + uio_FileBlock *fileBlock; + off_t uncompressedOffset; + // seek location in the uncompressed stream + off_t compressedOffset; + // seek location in the compressed stream, from the start + // of the compressed file +} zip_Handle; + + +uio_PRoot *zip_mount(uio_Handle *handle, int flags); +int zip_umount(struct uio_PRoot *); +uio_Handle *zip_open(uio_PDirHandle *pDirHandle, const char *file, int flags, + mode_t mode); +void zip_close(uio_Handle *handle); +int zip_access(uio_PDirHandle *pDirHandle, const char *name, int mode); +int zip_fstat(uio_Handle *handle, struct stat *statBuf); +int zip_stat(uio_PDirHandle *pDirHandle, const char *name, + struct stat *statBuf); +ssize_t zip_read(uio_Handle *handle, void *buf, size_t count); +off_t zip_seek(uio_Handle *handle, off_t offset, int whence); + + + + diff --git a/src/libs/uioutils.h b/src/libs/uioutils.h new file mode 100644 index 0000000..ec8a5a5 --- /dev/null +++ b/src/libs/uioutils.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef LIBS_UIOUTILS_H_ +#define LIBS_UIOUTILS_H_ + +#if defined(__cplusplus) +extern "C" { +#endif + +#include "uio/utils.h" + +#if defined(__cplusplus) +} +#endif + +#endif /* LIBS_UIOUTILS_H_ */ diff --git a/src/libs/unicode.h b/src/libs/unicode.h new file mode 100644 index 0000000..922a3cc --- /dev/null +++ b/src/libs/unicode.h @@ -0,0 +1,72 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef UNICODE_H +#define UNICODE_H + +#include "port.h" +#include "types.h" + // for uint32 +#include <sys/types.h> + // For size_t + +#if defined(__cplusplus) +extern "C" { +#endif + +typedef uint32 UniChar; + +#ifdef UNICODE_INTERNAL +# define UNICODE_CHAR unsigned char +#else +# define UNICODE_CHAR char +#endif + +UniChar getCharFromString(const UNICODE_CHAR **ptr); +UniChar getCharFromStringN(const UNICODE_CHAR **ptr, + const UNICODE_CHAR *end); +unsigned char *getLineFromString(const UNICODE_CHAR *start, + const UNICODE_CHAR **end, const UNICODE_CHAR **startNext); +size_t utf8StringCount(const UNICODE_CHAR *start); +size_t utf8StringCountN(const UNICODE_CHAR *start, + const UNICODE_CHAR *end); +int utf8StringPos (const UNICODE_CHAR *pStr, UniChar ch); +unsigned char *utf8StringCopy (UNICODE_CHAR *dst, size_t size, + const UNICODE_CHAR *src); +int utf8StringCompare (const UNICODE_CHAR *str1, const UNICODE_CHAR *str2); +UNICODE_CHAR *skipUTF8Chars(const UNICODE_CHAR *ptr, size_t num); +size_t getUniCharFromString(UniChar *wstr, size_t maxcount, + const UNICODE_CHAR *start); +size_t getUniCharFromStringN(UniChar *wstr, size_t maxcount, + const UNICODE_CHAR *start, const UNICODE_CHAR *end); +int getStringFromChar(UNICODE_CHAR *ptr, size_t size, UniChar ch); +size_t getStringFromWideN(UNICODE_CHAR *ptr, size_t size, + const UniChar *wstr, size_t count); +size_t getStringFromWide(UNICODE_CHAR *ptr, size_t size, + const UniChar *wstr); +int UniChar_isGraph(UniChar ch); +int UniChar_isPrint(UniChar ch); +UniChar UniChar_toUpper(UniChar ch); +UniChar UniChar_toLower(UniChar ch); + +#undef UNICODE_CHAR + +#if defined(__cplusplus) +} +#endif + +#endif /* UNICODE_H */ + diff --git a/src/libs/video/Makeinfo b/src/libs/video/Makeinfo new file mode 100644 index 0000000..1282e49 --- /dev/null +++ b/src/libs/video/Makeinfo @@ -0,0 +1,3 @@ +uqm_CFILES="vfileins.c vresins.c video.c videodec.c vidplayer.c dukvid.c \ + legacyplayer.c" +uqm_HFILES="dukvid.h videodec.h video.h vidintrn.h vidplayer.h" diff --git a/src/libs/video/dukvid.c b/src/libs/video/dukvid.c new file mode 100644 index 0000000..d9cb8fa --- /dev/null +++ b/src/libs/video/dukvid.c @@ -0,0 +1,748 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* DUCK video player + * + * Status: fully functional + */ + +#include "video.h" +#include "dukvid.h" +#include <stdio.h> +#include <string.h> +#include "libs/uio.h" +#include "libs/memlib.h" +#include "endian_uqm.h" + +#define THIS_PTR TFB_VideoDecoder* This + +static const char* dukv_GetName (void); +static bool dukv_InitModule (int flags); +static void dukv_TermModule (void); +static uint32 dukv_GetStructSize (void); +static int dukv_GetError (THIS_PTR); +static bool dukv_Init (THIS_PTR, TFB_PixelFormat* fmt); +static void dukv_Term (THIS_PTR); +static bool dukv_Open (THIS_PTR, uio_DirHandle *dir, const char *filename); +static void dukv_Close (THIS_PTR); +static int dukv_DecodeNext (THIS_PTR); +static uint32 dukv_SeekFrame (THIS_PTR, uint32 frame); +static float dukv_SeekTime (THIS_PTR, float time); +static uint32 dukv_GetFrame (THIS_PTR); +static float dukv_GetTime (THIS_PTR); + +TFB_VideoDecoderFuncs dukv_DecoderVtbl = +{ + dukv_GetName, + dukv_InitModule, + dukv_TermModule, + dukv_GetStructSize, + dukv_GetError, + dukv_Init, + dukv_Term, + dukv_Open, + dukv_Close, + dukv_DecodeNext, + dukv_SeekFrame, + dukv_SeekTime, + dukv_GetFrame, + dukv_GetTime, +}; + +typedef struct tfb_duckvideoheader +{ + uint32 version; + uint32 scrn_x_ofs; // horz screen offset in pixels + uint32 scrn_y_ofs; // vert screen offset in pixels + uint16 wb, hb; // width + height in blocks + sint16 lumas[8]; // future luminance deltas + sint16 chromas[8]; // future chrominance deltas + +} TFB_DuckVideoHeader; + +#define NUM_VEC_ITEMS 0x010 +#define NUM_VECTORS 0x100 + +typedef struct tfb_duckvideodeltas +{ + sint32 lumas[NUM_VECTORS][NUM_VEC_ITEMS]; + sint32 chromas[NUM_VECTORS][NUM_VEC_ITEMS]; + +} TFB_DuckVideoDeltas; + +// specific video decoder struct derived from TFB_VideoDecoder +// the only sane way in C one can :) +typedef struct tfb_duckvideodecoder +{ + // always the first member + TFB_VideoDecoder decoder; + + sint32 last_error; + uio_DirHandle* basedir; + char* basename; + uio_Stream *stream; + +// loaded from disk + uint32* frames; + uint32 cframes; + uint32 iframe; + uint32 version; + uint32 wb, hb; // width, height in blocks + +// generated + TFB_DuckVideoDeltas d; + + uint8* inbuf; + uint32* decbuf; + +} TFB_DuckVideoDecoder; + +#define DUCK_GENERAL_FPS 14.622f +#define DUCK_MAX_FRAME_SIZE 0x8000U +#define DUCK_END_OF_SEQUENCE 1 + +static void +dukv_DecodeFrame (uint8* src_p, uint32* dst_p, uint32 wb, uint32 hb, + TFB_DuckVideoDeltas* deltas) +{ + int iVec; + int iSeq; + uint32 x, y; + sint32 w; + uint32 *d_p0, *d_p1; + int i; + + w = wb * 4; + + iVec = *(src_p++); + iSeq = 0; + + for (y = 0; y < hb; ++y) + { + sint32 accum0, accum1, corr, corr0, corr1, delta; + sint32 pix[4]; + + d_p0 = dst_p + y * w * 2; + d_p1 = d_p0 + w; + + accum0 = 0; + accum1 = 0; + corr0 = 0; + corr1 = 0; + + for (x = 0; x < wb; ++x) + { + if (y == 0) + { + pix[0] = pix[1] = pix[2] = pix[3] = 0; + } + else + { + uint32* p_p = d_p0 - w; + pix[0] = p_p[0]; + pix[1] = p_p[1]; + pix[2] = p_p[2]; + pix[3] = p_p[3]; + } + + // start with chroma delta + delta = deltas->chromas[iVec][iSeq++]; + iSeq++; // correctors ignored + + accum0 += delta >> 1; + if (delta & 1) + { + iVec = *(src_p++); + iSeq = 0; + } + + // line 0 + for (i = 0; i < 4; ++i, ++d_p0) + { + delta = deltas->lumas[iVec][iSeq++]; + corr = deltas->lumas[iVec][iSeq++]; + + accum0 += delta >> 1; + corr0 ^= corr; + pix[i] += accum0; + pix[i] ^= corr0; + + if (delta & 1) + { + iVec = *(src_p++); + iSeq = 0; + } + + *d_p0 = pix[i]; + } + + // line 1 + for (i = 0; i < 4; ++i, ++d_p1) + { + delta = deltas->lumas[iVec][iSeq++]; + corr = deltas->lumas[iVec][iSeq++]; + + accum1 += delta >> 1; + corr1 ^= corr; + pix[i] += accum1; + pix[i] ^= corr1; + + if (delta & 1) + { + iVec = *(src_p++); + iSeq = 0; + } + + *d_p1 = pix[i]; + } + } + } +} + +static void +dukv_DecodeFrameV3 (uint8* src_p, uint32* dst_p, uint32 wb, uint32 hb, + TFB_DuckVideoDeltas* deltas) +{ + int iVec; + int iSeq; + uint32 x, y; + sint32 w; + uint32* d_p; + int i; + + iVec = *(src_p++); + iSeq = 0; + + hb *= 2; + w = wb * 4; + + for (y = 0; y < hb; ++y) + { + sint32 accum, delta, pix; + + d_p = dst_p + y * w; + + accum = 0; + + for (x = 0; x < wb; ++x) + { + // start with chroma delta + delta = deltas->chromas[iVec][iSeq]; + iSeq += 2; // correctors ignored + + accum += delta >> 1; + + if (delta & DUCK_END_OF_SEQUENCE) + { + iVec = *(src_p++); + iSeq = 0; + } + + for (i = 0; i < 4; ++i, ++d_p) + { + if (y == 0) + pix = 0; + else + pix = d_p[-w]; + + // get next luma delta + delta = deltas->lumas[iVec][iSeq]; + iSeq += 2; // correctors ignored + + accum += delta >> 1; + pix += accum; + + if (delta & DUCK_END_OF_SEQUENCE) + { + iVec = *(src_p++); + iSeq = 0; + } + + *d_p = pix; + } + } + } +} + +static bool +dukv_OpenStream (TFB_DuckVideoDecoder* dukv) +{ + char filename[280]; + + strcat (strcpy (filename, dukv->basename), ".duk"); + + return (dukv->stream = + uio_fopen (dukv->basedir, filename, "rb")) != NULL; +} + +static bool +dukv_ReadFrames (TFB_DuckVideoDecoder* dukv) +{ + char filename[280]; + uint32 i; + uio_Stream *fp; + + strcat (strcpy (filename, dukv->basename), ".frm"); + + if (!(fp = uio_fopen (dukv->basedir, filename, "rb"))) + return false; + + // get number of frames + uio_fseek (fp, 0, SEEK_END); + dukv->cframes = uio_ftell (fp) / sizeof (uint32); + uio_fseek (fp, 0, SEEK_SET); + dukv->frames = (uint32*) HMalloc (dukv->cframes * sizeof (uint32)); + + if (uio_fread (dukv->frames, sizeof (uint32), dukv->cframes, + fp) != dukv->cframes) + { + HFree (dukv->frames); + dukv->frames = 0; + return 0; + } + uio_fclose (fp); + + for (i = 0; i < dukv->cframes; ++i) + dukv->frames[i] = UQM_SwapBE32 (dukv->frames[i]); + + return true; +} + +static bool +dukv_ReadVectors (TFB_DuckVideoDecoder* dukv, uint8* vectors) +{ + uio_Stream *fp; + char filename[280]; + int ret; + + strcat (strcpy (filename, dukv->basename), ".tbl"); + + if (!(fp = uio_fopen (dukv->basedir, filename, "rb"))) + return false; + + ret = uio_fread (vectors, NUM_VEC_ITEMS, NUM_VECTORS, fp); + uio_fclose (fp); + + return ret == NUM_VECTORS; +} + +static bool +dukv_ReadHeader (TFB_DuckVideoDecoder* dukv, sint32* pl, sint32* pc) +{ + uio_Stream *fp; + char filename[280]; + int ret; + int i; + TFB_DuckVideoHeader hdr; + + strcat (strcpy (filename, dukv->basename), ".hdr"); + + if (!(fp = uio_fopen (dukv->basedir, filename, "rb"))) + return false; + + ret = uio_fread (&hdr, sizeof (hdr), 1, fp); + uio_fclose (fp); + if (!ret) + return false; + + dukv->version = UQM_SwapBE32 (hdr.version); + dukv->wb = UQM_SwapBE16 (hdr.wb); + dukv->hb = UQM_SwapBE16 (hdr.hb); + + for (i = 0; i < 8; ++i) + { + pl[i] = (sint16) UQM_SwapBE16 (hdr.lumas[i]); + pc[i] = (sint16) UQM_SwapBE16 (hdr.chromas[i]); + } + + dukv->decoder.w = dukv->wb * 4; + dukv->decoder.h = dukv->hb * 4; + + return true; +} + +static sint32 +dukv_make_delta (sint32* protos, bool is_chroma, int i1, int i2) +{ + sint32 d1, d2; + + if (!is_chroma) + { + // 0x421 is (r,g,b)=(1,1,1) in 15bit pixel coding + d1 = (protos[i1] >> 1) * 0x421; + d2 = (protos[i2] >> 1) * 0x421; + return ((d1 << 16) + d2) << 1; + } + else + { + d1 = (protos[i1] << 10) + protos[i2]; + return ((d1 << 16) + d1) << 1; + } +} + +static sint32 +dukv_make_corr (sint32* protos, bool is_chroma, int i1, int i2) +{ + sint32 d1, d2; + + if (!is_chroma) + { + d1 = (protos[i1] & 1) << 15; + d2 = (protos[i2] & 1) << 15; + return (d1 << 16) + d2; + } + else + { + return (i1 << 3) + i2; + } +} + +static void +dukv_DecodeVector (uint8* vec, sint32* p, bool is_chroma, sint32* deltas) +{ + int citems = vec[0]; + int i; + + for (i = 0; i < citems; i += 2, vec += 2, deltas += 2) + { + sint32 d = dukv_make_delta (p, is_chroma, vec[1], vec[2]); + + if (i == citems - 2) + d |= DUCK_END_OF_SEQUENCE; + + deltas[0] = d; + deltas[1] = dukv_make_corr (p, is_chroma, vec[1], vec[2]); + } + +} + +static void +dukv_InitDeltas (TFB_DuckVideoDecoder* dukv, uint8* vectors, + sint32* pl, sint32* pc) +{ + int i; + + for (i = 0; i < NUM_VECTORS; ++i) + { + uint8* vector = vectors + i * NUM_VEC_ITEMS; + dukv_DecodeVector (vector, pl, false, dukv->d.lumas[i]); + dukv_DecodeVector (vector, pc, true, dukv->d.chromas[i]); + } +} + +static inline uint32 +dukv_PixelConv (uint16 pix, const TFB_PixelFormat* fmt) +{ + uint32 r, g, b; + + r = (pix >> 7) & 0xf8; + g = (pix >> 2) & 0xf8; + b = (pix << 3) & 0xf8; + + return + ((r >> fmt->Rloss) << fmt->Rshift) | + ((g >> fmt->Gloss) << fmt->Gshift) | + ((b >> fmt->Bloss) << fmt->Bshift); +} + +static void +dukv_RenderFrame (THIS_PTR) +{ + TFB_DuckVideoDecoder* dukv = (TFB_DuckVideoDecoder*) This; + const TFB_PixelFormat* fmt = This->format; + uint32 h, x, y; + uint32* dec = dukv->decbuf; + + h = dukv->decoder.h / 2; + + // separate bpp versions for speed + switch (fmt->BytesPerPixel) + { + case 2: + { + for (y = 0; y < h; ++y) + { + uint16 *dst0, *dst1; + + dst0 = (uint16*) This->callbacks.GetCanvasLine (This, y * 2); + dst1 = (uint16*) This->callbacks.GetCanvasLine (This, y * 2 + 1); + + for (x = 0; x < dukv->decoder.w; ++x, ++dec, ++dst0, ++dst1) + { + uint32 pair = *dec; + *dst0 = dukv_PixelConv ((uint16)(pair >> 16), fmt); + *dst1 = dukv_PixelConv ((uint16)(pair & 0xffff), fmt); + } + } + break; + } + case 3: + { + for (y = 0; y < h; ++y) + { + uint8 *dst0, *dst1; + + dst0 = (uint8*) This->callbacks.GetCanvasLine (This, y * 2); + dst1 = (uint8*) This->callbacks.GetCanvasLine (This, y * 2 + 1); + + for (x = 0; x < dukv->decoder.w; + ++x, ++dec, dst0 += 3, dst1 += 3) + { + uint32 pair = *dec; + *(uint32*)dst0 = + dukv_PixelConv ((uint16)(pair >> 16), fmt); + *(uint32*)dst1 = + dukv_PixelConv ((uint16)(pair & 0xffff), fmt); + } + } + break; + } + case 4: + { + for (y = 0; y < h; ++y) + { + uint32 *dst0, *dst1; + + dst0 = (uint32*) This->callbacks.GetCanvasLine (This, y * 2); + dst1 = (uint32*) This->callbacks.GetCanvasLine (This, y * 2 + 1); + + for (x = 0; x < dukv->decoder.w; ++x, ++dec, ++dst0, ++dst1) + { + uint32 pair = *dec; + *dst0 = dukv_PixelConv ((uint16)(pair >> 16), fmt); + *dst1 = dukv_PixelConv ((uint16)(pair & 0xffff), fmt); + } + } + break; + } + default: + ; + } +} + +static const char* +dukv_GetName (void) +{ + return "DukVid"; +} + +static bool +dukv_InitModule (int flags) +{ + // no flags are defined for now + return true; + + (void)flags; // dodge compiler warning +} + +static void +dukv_TermModule (void) +{ + // do an extensive search on the word 'nothing' +} + +static uint32 +dukv_GetStructSize (void) +{ + return sizeof (TFB_DuckVideoDecoder); +} + +static int +dukv_GetError (THIS_PTR) +{ + return This->error; +} + +static bool +dukv_Init (THIS_PTR, TFB_PixelFormat* fmt) +{ + This->format = fmt; + This->audio_synced = true; + return true; +} + +static void +dukv_Term (THIS_PTR) +{ + //TFB_DuckVideoDecoder* dukv = (TFB_DuckVideoDecoder*) This; + + dukv_Close (This); +} + +static bool +dukv_Open (THIS_PTR, uio_DirHandle *dir, const char *filename) +{ + TFB_DuckVideoDecoder* dukv = (TFB_DuckVideoDecoder*) This; + char* pext; + sint32 lumas[8], chromas[8]; + uint8* vectors; + + dukv->basedir = dir; + dukv->basename = HMalloc (strlen (filename) + 1); + strcpy (dukv->basename, filename); + pext = strrchr (dukv->basename, '.'); + if (pext) // strip extension + *pext = 0; + + vectors = HMalloc (NUM_VEC_ITEMS * NUM_VECTORS); + + if (!dukv_OpenStream (dukv) + || !dukv_ReadFrames (dukv) + || !dukv_ReadHeader (dukv, lumas, chromas) + || !dukv_ReadVectors (dukv, vectors)) + { + HFree (vectors); + dukv_Close (This); + dukv->last_error = dukve_BadFile; + return false; + } + + dukv_InitDeltas (dukv, vectors, lumas, chromas); + HFree (vectors); + + This->length = (float) dukv->cframes / DUCK_GENERAL_FPS; + This->frame_count = dukv->cframes; + This->interframe_wait = (uint32) (1000.0 / DUCK_GENERAL_FPS); + + dukv->inbuf = HMalloc (DUCK_MAX_FRAME_SIZE); + dukv->decbuf = HMalloc ( + dukv->decoder.w * dukv->decoder.h * sizeof (uint16)); + + return true; +} + +static void +dukv_Close (THIS_PTR) +{ + TFB_DuckVideoDecoder* dukv = (TFB_DuckVideoDecoder*) This; + + if (dukv->basename) + { + HFree (dukv->basename); + dukv->basename = NULL; + } + if (dukv->frames) + { + HFree (dukv->frames); + dukv->frames = NULL; + } + if (dukv->stream) + { + uio_fclose (dukv->stream); + dukv->stream = NULL; + } + if (dukv->inbuf) + { + HFree (dukv->inbuf); + dukv->inbuf = NULL; + } + if (dukv->decbuf) + { + HFree (dukv->decbuf); + dukv->decbuf = NULL; + } +} + +static int +dukv_DecodeNext (THIS_PTR) +{ + TFB_DuckVideoDecoder* dukv = (TFB_DuckVideoDecoder*) This; + uint32 fh[2]; + uint32 vofs; + uint32 vsize; + uint16 ver; + + if (!dukv->stream || dukv->iframe >= dukv->cframes) + return 0; + + uio_fseek (dukv->stream, dukv->frames[dukv->iframe], SEEK_SET); + if (uio_fread (&fh, sizeof (fh), 1, dukv->stream) != 1) + { + dukv->last_error = dukve_EOF; + return 0; + } + + vofs = UQM_SwapBE32 (fh[0]); + vsize = UQM_SwapBE32 (fh[1]); + if (vsize > DUCK_MAX_FRAME_SIZE) + { + dukv->last_error = dukve_OutOfBuf; + return -1; + } + + uio_fseek (dukv->stream, vofs, SEEK_CUR); + if (uio_fread (dukv->inbuf, 1, vsize, dukv->stream) != vsize) + { + dukv->last_error = dukve_EOF; + return 0; + } + + ver = UQM_SwapBE16 (*(uint16*)dukv->inbuf); + if (ver == 0x0300) + dukv_DecodeFrameV3 (dukv->inbuf + 0x10, dukv->decbuf, + dukv->wb, dukv->hb, &dukv->d); + else + dukv_DecodeFrame (dukv->inbuf + 0x10, dukv->decbuf, + dukv->wb, dukv->hb, &dukv->d); + + dukv->iframe++; + + This->callbacks.BeginFrame (This); + dukv_RenderFrame (This); + This->callbacks.EndFrame (This); + + if (!This->audio_synced) + This->callbacks.SetTimer (This, (uint32) (1000.0f / DUCK_GENERAL_FPS)); + + return 1; +} + +static uint32 +dukv_SeekFrame (THIS_PTR, uint32 frame) +{ + TFB_DuckVideoDecoder* dukv = (TFB_DuckVideoDecoder*) This; + + if (frame > dukv->cframes) + frame = dukv->cframes; // EOS + + return dukv->iframe = frame; +} + +static float +dukv_SeekTime (THIS_PTR, float time) +{ + //TFB_DuckVideoDecoder* dukv = (TFB_DuckVideoDecoder*) This; + uint32 frame = (uint32) (time * DUCK_GENERAL_FPS); + + // Note that DUCK_GENERAL_FPS is a float constant + return dukv_SeekFrame (This, frame) / DUCK_GENERAL_FPS; +} + +static uint32 +dukv_GetFrame (THIS_PTR) +{ + TFB_DuckVideoDecoder* dukv = (TFB_DuckVideoDecoder*) This; + + return dukv->iframe; +} + +static float +dukv_GetTime (THIS_PTR) +{ + TFB_DuckVideoDecoder* dukv = (TFB_DuckVideoDecoder*) This; + + return (float) dukv->iframe / DUCK_GENERAL_FPS; +} diff --git a/src/libs/video/dukvid.h b/src/libs/video/dukvid.h new file mode 100644 index 0000000..a6d70b0 --- /dev/null +++ b/src/libs/video/dukvid.h @@ -0,0 +1,36 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_VIDEO_DUKVID_H_ +#define LIBS_VIDEO_DUKVID_H_ + +#include "libs/video/videodec.h" + +extern TFB_VideoDecoderFuncs dukv_DecoderVtbl; + +typedef enum +{ + // positive values are the same as in errno + dukve_None = 0, + dukve_Unknown = -1, + dukve_BadFile = -2, + dukve_BadArg = -3, + dukve_OutOfBuf = -4, + dukve_EOF = -5, + dukve_Other = -1000, +} DukVid_Error; + +#endif // LIBS_VIDEO_DUKVID_H_ diff --git a/src/libs/video/legacyplayer.c b/src/libs/video/legacyplayer.c new file mode 100644 index 0000000..5dfddad --- /dev/null +++ b/src/libs/video/legacyplayer.c @@ -0,0 +1,81 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "vidintrn.h" +#include "video.h" +#include "vidplayer.h" +#include "libs/memlib.h" + + +LEGACY_VIDEO_REF +PlayLegacyVideo (LEGACY_VIDEO vid) +{ + const char *name, *audname, *speechname; + uint32 loopframe; + LEGACY_VIDEO_REF ref; + VIDEO_TYPE type; + + if (!vid) + return NULL; + ref = HCalloc (sizeof (*ref)); + if (!ref) + return NULL; + name = vid->video; + audname = vid->audio; + speechname = vid->speech; + loopframe = vid->loop; + + ref->vidref = LoadVideoFile (name); + if (!ref->vidref) + return NULL; + if (audname) + ref->audref = LoadMusicFile (audname); + if (speechname) + ref->speechref = LoadMusicFile (speechname); + + type = VidPlayEx (ref->vidref, ref->audref, ref->speechref, loopframe); + if (type == NO_FMV) + { // Video failed to start + StopLegacyVideo (ref); + return NULL; + } + + return ref; +} + +void +StopLegacyVideo (LEGACY_VIDEO_REF ref) +{ + if (!ref) + return; + VidStop (); + + DestroyVideo (ref->vidref); + if (ref->speechref) + DestroyMusic (ref->speechref); + if (ref->audref) + DestroyMusic (ref->audref); + + HFree (ref); +} + +BOOLEAN +PlayingLegacyVideo (LEGACY_VIDEO_REF ref) +{ + if (!ref) + return FALSE; + return TFB_VideoPlaying (ref->vidref); +} diff --git a/src/libs/video/vfileins.c b/src/libs/video/vfileins.c new file mode 100644 index 0000000..26cb7c1 --- /dev/null +++ b/src/libs/video/vfileins.c @@ -0,0 +1,28 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "libs/vidlib.h" +#include "video.h" + +VIDEO_REF +LoadVideoFile (const char *pStr) +{ + return _init_video_file (pStr); +} + + diff --git a/src/libs/video/video.c b/src/libs/video/video.c new file mode 100644 index 0000000..dd4cd46 --- /dev/null +++ b/src/libs/video/video.c @@ -0,0 +1,190 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "video.h" + +#include "vidintrn.h" +#include "options.h" +#include "vidplayer.h" +#include "libs/memlib.h" +#include "libs/sndlib.h" + + +#define NULL_VIDEO_REF (0) +static VIDEO_REF _cur_video = NULL_VIDEO_REF; +static MUSIC_REF _cur_speech = 0; + +BOOLEAN +InitVideoPlayer (BOOLEAN useCDROM) + //useCDROM doesn't really apply to us +{ + TFB_PixelFormat fmt; + + TFB_DrawCanvas_GetScreenFormat (&fmt); + if (!VideoDecoder_Init (0, fmt.BitsPerPixel, fmt.Rmask, + fmt.Gmask, fmt.Bmask, 0)) + return FALSE; + + return TFB_InitVideoPlayer (); + + (void)useCDROM; /* dodge compiler warning */ +} + +void +UninitVideoPlayer (void) +{ + TFB_UninitVideoPlayer (); + VideoDecoder_Uninit (); +} + +void +VidStop (void) +{ + if (_cur_speech) + snd_StopSpeech (); + if (_cur_video) + TFB_StopVideo (_cur_video); + _cur_speech = 0; + _cur_video = NULL_VIDEO_REF; +} + +VIDEO_REF +VidPlaying (void) + // this should just probably return BOOLEAN +{ + if (!_cur_video) + return NULL_VIDEO_REF; + + if (TFB_VideoPlaying (_cur_video)) + return _cur_video; + + return NULL_VIDEO_REF; +} + +BOOLEAN +VidProcessFrame (void) +{ + if (!_cur_video) + return FALSE; + return TFB_ProcessVideoFrame (_cur_video); +} + +// return current video position in milliseconds +DWORD +VidGetPosition (void) +{ + if (!VidPlaying ()) + return 0; + return TFB_GetVideoPosition (_cur_video); +} + +BOOLEAN +VidSeek (DWORD pos) + // pos in milliseconds +{ + if (!VidPlaying ()) + return FALSE; + return TFB_SeekVideo (_cur_video, pos); +} + +VIDEO_TYPE +VidPlayEx (VIDEO_REF vid, MUSIC_REF AudRef, MUSIC_REF SpeechRef, + DWORD LoopFrame) +{ + VIDEO_TYPE ret; + + if (!vid) + return NO_FMV; + + if (AudRef) + { + if (vid->hAudio) + DestroyMusic (vid->hAudio); + vid->hAudio = AudRef; + vid->decoder->audio_synced = FALSE; + } + + vid->loop_frame = LoopFrame; + vid->loop_to = 0; + + if (_cur_speech) + snd_StopSpeech (); + if (_cur_video) + TFB_StopVideo (_cur_video); + _cur_speech = 0; + _cur_video = NULL_VIDEO_REF; + + // play video in the center of the screen + if (TFB_PlayVideo (vid, (ScreenWidth - vid->w) / 2, + (ScreenHeight - vid->h) / 2)) + { + _cur_video = vid; + ret = SOFTWARE_FMV; + if (SpeechRef) + { + snd_PlaySpeech (SpeechRef); + _cur_speech = SpeechRef; + } + } + else + { + ret = NO_FMV; + } + + return ret; +} + +VIDEO_TYPE +VidPlay (VIDEO_REF VidRef) +{ + return VidPlayEx (VidRef, 0, 0, VID_NO_LOOP); +} + +VIDEO_REF +_init_video_file (const char *pStr) +{ + TFB_VideoClip* vid; + TFB_VideoDecoder* dec; + + dec = VideoDecoder_Load (contentDir, pStr); + if (!dec) + return NULL_VIDEO_REF; + + vid = HCalloc (sizeof (*vid)); + vid->decoder = dec; + vid->length = dec->length; + vid->w = vid->decoder->w; + vid->h = vid->decoder->h; + vid->guard = CreateMutex ("video guard", SYNC_CLASS_VIDEO); + + return (VIDEO_REF) vid; +} + +BOOLEAN +DestroyVideo (VIDEO_REF vid) +{ + if (!vid) + return FALSE; + + // just some armouring; should already be stopped + TFB_StopVideo (vid); + + VideoDecoder_Free (vid->decoder); + DestroyMutex (vid->guard); + HFree (vid); + + return TRUE; +} diff --git a/src/libs/video/video.h b/src/libs/video/video.h new file mode 100644 index 0000000..5e76aa4 --- /dev/null +++ b/src/libs/video/video.h @@ -0,0 +1,56 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_VIDEO_VIDEO_H_ +#define LIBS_VIDEO_VIDEO_H_ + +#include "libs/vidlib.h" +#include "libs/sndlib.h" +#include "libs/graphics/tfb_draw.h" +#include "types.h" +#include "videodec.h" +#include "libs/sound/sound.h" + + +typedef struct tfb_videoclip +{ + TFB_VideoDecoder *decoder; // decoder to read from + float length; // total length of clip seconds + uint32 w, h; + + // video player data + RECT dst_rect; // destination screen rect + RECT src_rect; // source rect + MUSIC_REF hAudio; + uint32 frame_time; // time when next frame should be rendered + TFB_Image* frame; // frame preped and optimized for rendering + uint32 cur_frame; // index of frame currently displayed + bool playing; + bool own_audio; + uint32 loop_frame; // frame index to loop from + uint32 loop_to; // frame index to loop to + + Mutex guard; + uint32 want_frame; // audio-signaled desired frame index + int lag_cnt; // N of frames video is behind or ahead of audio + + void* data; // user-defined data + +} TFB_VideoClip; + +extern VIDEO_REF _init_video_file(const char *pStr); + +#endif // LIBS_VIDEO_VIDEO_H_ diff --git a/src/libs/video/videodec.c b/src/libs/video/videodec.c new file mode 100644 index 0000000..b99a442 --- /dev/null +++ b/src/libs/video/videodec.c @@ -0,0 +1,363 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <string.h> +#include "video.h" +#include "videodec.h" +#include "dukvid.h" +#include "libs/log.h" +#include "libs/memlib.h" + +#define MAX_REG_DECODERS 31 + +static bool vd_inited = false; +static TFB_PixelFormat vd_vidfmt; +static int vd_flags = 0; + +struct TFB_RegVideoDecoder +{ + bool builtin; + bool used; // ever used indicator + const char* ext; + const TFB_VideoDecoderFuncs* funcs; +}; +static TFB_RegVideoDecoder vd_decoders[MAX_REG_DECODERS + 1] = +{ + {true, true, "duk", &dukv_DecoderVtbl}, + {false, false, NULL, NULL}, // null term +}; + +static void vd_computeMasks (uint32 mask, DWORD* shift, DWORD* loss); + +const char* +VideoDecoder_GetName (TFB_VideoDecoder *decoder) +{ + if (!decoder || !decoder->funcs) + return "(Null)"; + return decoder->funcs->GetName (); +} + +bool +VideoDecoder_Init (int flags, int depth, uint32 Rmask, uint32 Gmask, + uint32 Bmask, uint32 Amask) +{ + TFB_RegVideoDecoder* info; + + vd_inited = false; + + if (depth < 15 || depth > 32) + { + log_add (log_Error, "VideoDecoder_Init: " + "Unsupported video depth %d", depth); + return false; + } + + if ((Rmask & Gmask) || (Rmask & Bmask) || (Rmask & Amask) || + (Gmask & Bmask) || (Gmask & Amask) || (Bmask & Amask)) + { + log_add (log_Error, "VideoDecoder_Init: Invalid channel masks"); + return false; + } + + // BEGIN: adapted from SDL + vd_vidfmt.BitsPerPixel = depth; + vd_vidfmt.BytesPerPixel = (depth + 7) / 8; + vd_vidfmt.Rmask = Rmask; + vd_vidfmt.Gmask = Gmask; + vd_vidfmt.Bmask = Bmask; + vd_vidfmt.Amask = Amask; + vd_computeMasks (Rmask, &vd_vidfmt.Rshift, &vd_vidfmt.Rloss); + vd_computeMasks (Gmask, &vd_vidfmt.Gshift, &vd_vidfmt.Gloss); + vd_computeMasks (Bmask, &vd_vidfmt.Bshift, &vd_vidfmt.Bloss); + vd_computeMasks (Amask, &vd_vidfmt.Ashift, &vd_vidfmt.Aloss); + // END: adapted from SDL + + // init built-in decoders + for (info = vd_decoders; info->ext; info++) + { + if (!info->funcs->InitModule (flags)) + { + log_add (log_Error, "VideoDecoder_Init(): " + "%s video decoder init failed", + info->funcs->GetName ()); + } + } + + vd_flags = flags; + vd_inited = true; + + return true; +} + +void +VideoDecoder_Uninit (void) +{ + TFB_RegVideoDecoder* info; + + // uninit all decoders + // and unregister loaded decoders + for (info = vd_decoders; info->used; info++) + { + if (info->ext) // check if present + info->funcs->TermModule (); + + if (!info->builtin) + { + info->used = false; + info->ext = NULL; + } + } + + vd_inited = false; +} + +TFB_RegVideoDecoder* +VideoDecoder_Register (const char* fileext, TFB_VideoDecoderFuncs* decvtbl) +{ + TFB_RegVideoDecoder* info; + TFB_RegVideoDecoder* newslot = NULL; + + if (!decvtbl) + { + log_add (log_Warning, "VideoDecoder_Register(): Null decoder table"); + return NULL; + } + if (!fileext) + { + log_add (log_Warning, "VideoDecoder_Register(): Bad file type for %s", + decvtbl->GetName ()); + return NULL; + } + + // check if extension already registered + for (info = vd_decoders; info->used && + (!info->ext || strcmp (info->ext, fileext) != 0); + ++info) + { + // and pick up an empty slot (where available) + if (!newslot && !info->ext) + newslot = info; + } + + if (info >= vd_decoders + MAX_REG_DECODERS) + { + log_add (log_Warning, "VideoDecoder_Register(): Decoders limit reached"); + return NULL; + } + else if (info->ext) + { + log_add (log_Warning, "VideoDecoder_Register(): " + "'%s' decoder already registered (%s denied)", + fileext, decvtbl->GetName ()); + return NULL; + } + + if (!decvtbl->InitModule (vd_flags)) + { + log_add (log_Warning, "VideoDecoder_Register(): %s decoder init failed", + decvtbl->GetName ()); + return NULL; + } + + if (!newslot) + { + newslot = info; + newslot->used = true; + // make next one a term + info[1].builtin = false; + info[1].used = false; + info[1].ext = NULL; + } + + newslot->ext = fileext; + newslot->funcs = decvtbl; + + return newslot; +} + +void +VideoDecoder_Unregister (TFB_RegVideoDecoder* regdec) +{ + if (regdec < vd_decoders || regdec >= vd_decoders + MAX_REG_DECODERS || + !regdec->ext || !regdec->funcs) + { + log_add (log_Warning, "VideoDecoder_Unregister(): " + "Invalid or expired decoder passed"); + return; + } + + regdec->funcs->TermModule (); + regdec->ext = NULL; + regdec->funcs = NULL; +} + +const TFB_VideoDecoderFuncs* +VideoDecoder_Lookup (const char* fileext) +{ + TFB_RegVideoDecoder* info; + + for (info = vd_decoders; info->used && + (!info->ext || strcmp (info->ext, fileext) != 0); + ++info) + ; + return info->ext ? info->funcs : NULL; +} + +TFB_VideoDecoder* +VideoDecoder_Load (uio_DirHandle *dir, const char *filename) +{ + const char* pext; + TFB_RegVideoDecoder* info; + TFB_VideoDecoder* decoder; + + + if (!vd_inited) + return NULL; + + pext = strrchr (filename, '.'); + if (!pext) + { + log_add (log_Warning, "VideoDecoder_Load: Unknown file type"); + return NULL; + } + ++pext; + + for (info = vd_decoders; info->used && + (!info->ext || strcmp (info->ext, pext) != 0); + ++info) + ; + if (!info->ext) + { + log_add (log_Warning, "VideoDecoder_Load: Unsupported file type"); + return NULL; + } + + decoder = HCalloc (info->funcs->GetStructSize ()); + decoder->funcs = info->funcs; + if (!decoder->funcs->Init (decoder, &vd_vidfmt)) + { + log_add (log_Warning, "VideoDecoder_Load: " + "Cannot init '%s' decoder, code %d", + decoder->funcs->GetName (), + decoder->funcs->GetError (decoder)); + HFree (decoder); + return NULL; + } + + decoder->dir = dir; + decoder->filename = (char *) HMalloc (strlen (filename) + 1); + strcpy (decoder->filename, filename); + decoder->error = VIDEODECODER_OK; + + if (!decoder->funcs->Open (decoder, dir, filename)) + { + log_add (log_Warning, "VideoDecoder_Load: " + "'%s' decoder did not load %s, code %d", + decoder->funcs->GetName (), filename, + decoder->funcs->GetError (decoder)); + + VideoDecoder_Free (decoder); + return NULL; + } + + return decoder; +} + +// return: >0 = OK, 0 = EOF, <0 = Error +int +VideoDecoder_Decode (TFB_VideoDecoder *decoder) +{ + int ret; + + if (!decoder) + return 0; + + decoder->cur_frame = decoder->funcs->GetFrame (decoder); + decoder->pos = decoder->funcs->GetTime (decoder); + + ret = decoder->funcs->DecodeNext (decoder); + if (ret == 0) + decoder->error = VIDEODECODER_EOF; + else if (ret < 0) + decoder->error = VIDEODECODER_ERROR; + else + decoder->error = VIDEODECODER_OK; + + return ret; +} + +float +VideoDecoder_Seek (TFB_VideoDecoder *decoder, float pos) +{ + if (!decoder) + return 0.0; + + decoder->pos = decoder->funcs->SeekTime (decoder, pos); + decoder->cur_frame = decoder->funcs->GetFrame (decoder); + + return decoder->pos; +} + +uint32 +VideoDecoder_SeekFrame (TFB_VideoDecoder *decoder, uint32 frame) +{ + if (!decoder) + return 0; + + decoder->cur_frame = decoder->funcs->SeekFrame (decoder, frame); + decoder->pos = decoder->funcs->GetTime (decoder); + + return decoder->cur_frame; +} + +void +VideoDecoder_Rewind (TFB_VideoDecoder *decoder) +{ + if (!decoder) + return; + + VideoDecoder_Seek (decoder, 0); +} + +void +VideoDecoder_Free (TFB_VideoDecoder *decoder) +{ + if (!decoder) + return; + + decoder->funcs->Close (decoder); + decoder->funcs->Term (decoder); + + HFree (decoder->filename); + HFree (decoder); +} + +// BEGIN: adapted from SDL +static void +vd_computeMasks (uint32 mask, DWORD* shift, DWORD* loss) +{ + *shift = 0; + *loss = 8; + if (mask) + { + for (; !(mask & 1); mask >>= 1 ) + ++*shift; + + for (; (mask & 1); mask >>= 1 ) + --*loss; + } +} +// END: adapted from SDL diff --git a/src/libs/video/videodec.h b/src/libs/video/videodec.h new file mode 100644 index 0000000..2d75c98 --- /dev/null +++ b/src/libs/video/videodec.h @@ -0,0 +1,124 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_VIDEO_VIDEODEC_H_ +#define LIBS_VIDEO_VIDEODEC_H_ + +#include "libs/vidlib.h" +#include "libs/video/video.h" +#include "libs/reslib.h" + +// forward-declare +typedef struct tfb_videodecoder TFB_VideoDecoder; + +#define THIS_PTR TFB_VideoDecoder* + +typedef struct tfb_videodecoderfunc +{ + const char* (* GetName) (void); + bool (* InitModule) (int flags); + void (* TermModule) (void); + uint32 (* GetStructSize) (void); + int (* GetError) (THIS_PTR); + bool (* Init) (THIS_PTR, TFB_PixelFormat* fmt); + void (* Term) (THIS_PTR); + bool (* Open) (THIS_PTR, uio_DirHandle *dir, const char *filename); + void (* Close) (THIS_PTR); + int (* DecodeNext) (THIS_PTR); + uint32 (* SeekFrame) (THIS_PTR, uint32 frame); + float (* SeekTime) (THIS_PTR, float time); + uint32 (* GetFrame) (THIS_PTR); + float (* GetTime) (THIS_PTR); + +} TFB_VideoDecoderFuncs; + +// decoder will call these to get info +// from the player +typedef struct tfb_videocallbacks +{ + // any decoder calls these + void (* BeginFrame) (THIS_PTR); + void (* EndFrame) (THIS_PTR); + void* (* GetCanvasLine) (THIS_PTR, uint32 line); + // non-audio-driven decoders call this to figure out + // when the next frame should be drawn + uint32 (* GetTicks) (THIS_PTR); + // non-audio-driven decoders call this to inform + // the player when the next frame should be drawn + bool (* SetTimer) (THIS_PTR, uint32 msecs); + +} TFB_VideoCallbacks; + +#undef THIS_PTR + +struct tfb_videodecoder +{ + // decoder virtual funcs - R/O + const TFB_VideoDecoderFuncs *funcs; + // video formats - R/O + const TFB_PixelFormat *format; + // decoder-set data - R/O + uint32 w, h; + float length; // total length in seconds + uint32 frame_count; + uint32 interframe_wait; // nominal interframe delay in msecs + bool audio_synced; + // decoder callbacks + TFB_VideoCallbacks callbacks; + + // other - public + bool looping; + void* data; // user-defined data + // info - public R/O + sint32 error; + float pos; // position in seconds + uint32 cur_frame; + + // semi-private + uio_DirHandle *dir; + char *filename; + +}; + +// return values +enum +{ + VIDEODECODER_OK, + VIDEODECODER_ERROR, + VIDEODECODER_EOF, +}; + +typedef struct TFB_RegVideoDecoder TFB_RegVideoDecoder; + +TFB_RegVideoDecoder* VideoDecoder_Register (const char* fileext, + TFB_VideoDecoderFuncs* decvtbl); +void VideoDecoder_Unregister (TFB_RegVideoDecoder* regdec); +const TFB_VideoDecoderFuncs* VideoDecoder_Lookup (const char* fileext); + +bool VideoDecoder_Init (int flags, int depth, uint32 Rmask, uint32 Gmask, + uint32 Bmask, uint32 Amask); +void VideoDecoder_Uninit (void); +TFB_VideoDecoder* VideoDecoder_Load (uio_DirHandle *dir, + const char *filename); +int VideoDecoder_Decode (TFB_VideoDecoder *decoder); +float VideoDecoder_Seek (TFB_VideoDecoder *decoder, float time_pos); +uint32 VideoDecoder_SeekFrame (TFB_VideoDecoder *decoder, uint32 frame_pos); +void VideoDecoder_Rewind (TFB_VideoDecoder *decoder); +void VideoDecoder_Free (TFB_VideoDecoder *decoder); +const char* VideoDecoder_GetName (TFB_VideoDecoder *decoder); + + +#endif // LIBS_VIDEO_VIDEODEC_H_ diff --git a/src/libs/video/vidintrn.h b/src/libs/video/vidintrn.h new file mode 100644 index 0000000..40a19e4 --- /dev/null +++ b/src/libs/video/vidintrn.h @@ -0,0 +1,41 @@ +// Copyright 2008 Michael Martin + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef VIDINTERN_H_ +#define VIDINTERN_H_ + +#include "types.h" +#include "libs/vidlib.h" +#include "libs/threadlib.h" + +struct legacy_video_desc +{ + char *video, *audio, *speech; + uint32 loop; +}; + +typedef struct legacy_video_desc LEGACY_VIDEO_DESC; + +struct legacy_video_ref +{ + VIDEO_REF vidref; + MUSIC_REF audref; + MUSIC_REF speechref; +}; + +#endif diff --git a/src/libs/video/vidplayer.c b/src/libs/video/vidplayer.c new file mode 100644 index 0000000..09a506d --- /dev/null +++ b/src/libs/video/vidplayer.c @@ -0,0 +1,481 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "vidplayer.h" + +#include "vidintrn.h" +#include "libs/graphics/gfx_common.h" +#include "libs/graphics/tfb_draw.h" +#include "libs/log.h" +#include "libs/memlib.h" +#include "libs/sndlib.h" + +// video callbacks +static void vp_BeginFrame (TFB_VideoDecoder*); +static void vp_EndFrame (TFB_VideoDecoder*); +static void* vp_GetCanvasLine (TFB_VideoDecoder*, uint32 line); +static uint32 vp_GetTicks (TFB_VideoDecoder*); +static bool vp_SetTimer (TFB_VideoDecoder*, uint32 msecs); + + +static const TFB_VideoCallbacks vp_DecoderCBs = +{ + vp_BeginFrame, + vp_EndFrame, + vp_GetCanvasLine, + vp_GetTicks, + vp_SetTimer +}; + +// audio stream callbacks +static bool vp_AudioStart (TFB_SoundSample* sample); +static void vp_AudioEnd (TFB_SoundSample* sample); +static void vp_BufferTag (TFB_SoundSample* sample, TFB_SoundTag* tag); +static void vp_QueueBuffer (TFB_SoundSample* sample, audio_Object buffer); + +static const TFB_SoundCallbacks vp_AudioCBs = +{ + vp_AudioStart, + NULL, + vp_AudioEnd, + vp_BufferTag, + vp_QueueBuffer +}; + + +bool +TFB_InitVideoPlayer (void) +{ + // now just a stub + return true; +} + +void +TFB_UninitVideoPlayer (void) +{ + // now just a stub +} + +static inline sint32 +msecToTimeCount (sint32 msec) +{ + return msec * ONE_SECOND / 1000; +} + +// audio-synced video playback frame function +// the frame rate and timing is dictated by the audio +static bool +processAudioSyncedFrame (VIDEO_REF vid) +{ +#define MAX_FRAME_LAG 8 +#define LAG_FRACTION 6 +#define SYNC_BIAS 1 / 3 + int ret; + uint32 want_frame; + uint32 prev_want_frame; + sint32 wait_msec; + CONTEXT oldContext; + TimeCount Now = GetTimeCounter (); + + if (!vid->playing) + return false; + + if (Now < vid->frame_time) + return true; // not time yet + + LockMutex (vid->guard); + want_frame = vid->want_frame; + UnlockMutex (vid->guard); + + if (want_frame >= vid->decoder->frame_count) + { + vid->playing = false; + return false; + } + + // this works like so (audio-synced): + // 1. you call VideoDecoder_Seek() [when necessary] and + // VideoDecoder_Decode() + // 2. wait till it's time for this frame to be drawn + // the timeout is necessary because the audio signaling is not + // precise (see vp_AudioStart, vp_AudioEnd, vp_BufferTag) + // 3. output the frame; if the audio is behind, the lag counter + // goes up; if the video is behind, the lag counter goes down + // 4. set the next frame timeout; lag counter increases or + // decreases the timeout to allow audio or video to catch up + // 5. on a seek operation, the audio stream is moved to the + // correct position and then the audio signals the frame + // that should be rendered + // The system of timeouts and lag counts should make the video + // *relatively* smooth + // + prev_want_frame = vid->cur_frame - vid->lag_cnt; + if (want_frame > prev_want_frame - MAX_FRAME_LAG + && want_frame <= prev_want_frame + MAX_FRAME_LAG) + { + // we will draw the next frame right now, thus +1 + vid->lag_cnt = vid->cur_frame + 1 - want_frame; + } + else + { // out of sequence frame, let's get it + vid->lag_cnt = 0; + vid->cur_frame = VideoDecoder_SeekFrame (vid->decoder, want_frame); + ret = VideoDecoder_Decode (vid->decoder); + if (ret < 0) + { // decoder returned a failure + vid->playing = false; + return false; + } + } + vid->cur_frame = vid->decoder->cur_frame; + + // draw the frame + // We have the cliprect precalculated and don't need the rest + oldContext = SetContext (NULL); + TFB_DrawScreen_Image (vid->frame, + vid->dst_rect.corner.x, vid->dst_rect.corner.y, 0, 0, + NULL, DRAW_REPLACE_MODE, TFB_SCREEN_MAIN); + SetContext (oldContext); + FlushGraphics (); // needed to prevent half-frame updates + + // increase interframe with positive lag-count to allow audio to catch up + // decrease interframe with negative lag-count to allow video to catch up + wait_msec = vid->decoder->interframe_wait + - (int)vid->decoder->interframe_wait * SYNC_BIAS + + (int)vid->decoder->interframe_wait * vid->lag_cnt / LAG_FRACTION; + vid->frame_time = Now + msecToTimeCount (wait_msec); + + ret = VideoDecoder_Decode (vid->decoder); + if (ret < 0) + { + // TODO: decide what to do on error + } + + return vid->playing; +} + +// audio-independent video playback frame function +// the frame rate and timing is dictated by the video decoder +static bool +processMuteFrame (VIDEO_REF vid) +{ + int ret; + TimeCount Now = GetTimeCounter (); + + if (!vid->playing) + return false; + + // this works like so: + // 1. you call VideoDecoder_Seek() [when necessary] and + // VideoDecoder_Decode() + // 2. the decoder calls back vp_GetTicks() and vp_SetTimer() + // to figure out and tell you when to render the frame + // being decoded + // On a seek operation, the decoder should reset its internal + // clock and call vp_GetTicks() again + // + if (Now >= vid->frame_time) + { + CONTEXT oldContext; + + vid->cur_frame = vid->decoder->cur_frame; + + // We have the cliprect precalculated and don't need the rest + oldContext = SetContext (NULL); + TFB_DrawScreen_Image (vid->frame, + vid->dst_rect.corner.x, vid->dst_rect.corner.y, 0, 0, + NULL, DRAW_REPLACE_MODE, TFB_SCREEN_MAIN); + SetContext (oldContext); + FlushGraphics (); // needed to prevent half-frame updates + + if (vid->cur_frame == vid->loop_frame) + VideoDecoder_SeekFrame (vid->decoder, vid->loop_to); + + ret = VideoDecoder_Decode (vid->decoder); + if (ret <= 0) + vid->playing = false; + } + + return vid->playing; +} + +bool +TFB_PlayVideo (VIDEO_REF vid, uint32 x, uint32 y) +{ + RECT scrn_r; + RECT clip_r = {{0, 0}, {vid->w, vid->h}}; + RECT vid_r = {{0, 0}, {ScreenWidth, ScreenHeight}}; + RECT dr = {{x, y}, {vid->w, vid->h}}; + RECT sr; + bool loop_music = false; + int ret; + + if (!vid) + return false; + + // calculate the frame-source and screen-destination rects + GetContextClipRect (&scrn_r); + if (!BoxIntersect(&scrn_r, &vid_r, &scrn_r)) + return false; // drawing outside visible + + sr = dr; + sr.corner.x = -sr.corner.x; + sr.corner.y = -sr.corner.y; + if (!BoxIntersect (&clip_r, &sr, &sr)) + return false; // drawing outside visible + + dr.corner.x += scrn_r.corner.x; + dr.corner.y += scrn_r.corner.y; + if (!BoxIntersect (&scrn_r, &dr, &vid->dst_rect)) + return false; // drawing outside visible + + vid->src_rect = vid->dst_rect; + vid->src_rect.corner.x = sr.corner.x; + vid->src_rect.corner.y = sr.corner.y; + + vid->decoder->callbacks = vp_DecoderCBs; + vid->decoder->data = vid; + + vid->frame = TFB_DrawImage_CreateForScreen (vid->w, vid->h, FALSE); + vid->cur_frame = -1; + vid->want_frame = -1; + + if (!vid->hAudio) + { + vid->hAudio = LoadMusicFile (vid->decoder->filename); + vid->own_audio = true; + } + + if (vid->decoder->audio_synced) + { + if (!vid->hAudio) + { + log_add (log_Warning, "TFB_PlayVideo: " + "Cannot load sound-track for audio-synced video"); + return false; + } + + TFB_SetSoundSampleCallbacks (*vid->hAudio, &vp_AudioCBs); + TFB_SetSoundSampleData (*vid->hAudio, vid); + } + + // get the first frame + ret = VideoDecoder_Decode (vid->decoder); + if (ret < 0) + return false; + + vid->playing = true; + + loop_music = !vid->decoder->audio_synced && vid->loop_frame != VID_NO_LOOP; + if (vid->hAudio) + PLRPlaySong (vid->hAudio, loop_music, 1); + + if (vid->decoder->audio_synced) + { + // draw the first frame now + vid->frame_time = GetTimeCounter (); + } + + return true; +} + +void +TFB_StopVideo (VIDEO_REF vid) +{ + if (!vid) + return; + + vid->playing = false; + + if (vid->hAudio) + { + PLRStop (vid->hAudio); + if (vid->own_audio) + { + DestroyMusic (vid->hAudio); + vid->hAudio = 0; + vid->own_audio = false; + } + } + if (vid->frame) + { + TFB_DrawScreen_DeleteImage (vid->frame); + vid->frame = NULL; + } +} + +bool +TFB_VideoPlaying (VIDEO_REF vid) +{ + if (!vid) + return false; + + return vid->playing; +} + +bool +TFB_ProcessVideoFrame (VIDEO_REF vid) +{ + if (!vid) + return false; + + if (vid->decoder->audio_synced) + return processAudioSyncedFrame (vid); + else + return processMuteFrame (vid); +} + +uint32 +TFB_GetVideoPosition (VIDEO_REF vid) +{ + uint32 pos; + + if (!TFB_VideoPlaying (vid)) + return 0; + + LockMutex (vid->guard); + pos = (uint32) (vid->decoder->pos * 1000); + UnlockMutex (vid->guard); + + return pos; +} + +bool +TFB_SeekVideo (VIDEO_REF vid, uint32 pos) +{ + if (!TFB_VideoPlaying (vid)) + return false; + + if (vid->decoder->audio_synced) + { + PLRSeek (vid->hAudio, pos); + TaskSwitch (); + return true; + } + else + { // TODO: Non-a/s decoder seeking is not supported yet + // Decide what to do with these. Seeking this kind of + // video is trivial, but we may not want to do it. + // The only non-a/s videos right now are ship spins. + return false; + } +} + +static void +vp_BeginFrame (TFB_VideoDecoder* decoder) +{ + TFB_VideoClip* vid = decoder->data; + + if (vid) + TFB_DrawCanvas_Lock (vid->frame->NormalImg); +} + +static void +vp_EndFrame (TFB_VideoDecoder* decoder) +{ + TFB_VideoClip* vid = decoder->data; + + if (vid) + TFB_DrawCanvas_Unlock (vid->frame->NormalImg); +} + +static void* +vp_GetCanvasLine (TFB_VideoDecoder* decoder, uint32 line) +{ + TFB_VideoClip* vid = decoder->data; + + if (!vid) + return NULL; + + return TFB_DrawCanvas_GetLine (vid->frame->NormalImg, line); +} + +static uint32 +vp_GetTicks (TFB_VideoDecoder* decoder) +{ + uint32 ctr = GetTimeCounter (); + return (ctr / ONE_SECOND) * 1000 + ((ctr % ONE_SECOND) * 1000) / ONE_SECOND; + + (void)decoder; // gobble up compiler warning +} + +static bool +vp_SetTimer (TFB_VideoDecoder* decoder, uint32 msecs) +{ + TFB_VideoClip* vid = decoder->data; + + if (!vid) + return false; + + // time when next frame should be displayed + vid->frame_time = GetTimeCounter () + msecs * ONE_SECOND / 1000; + return true; +} + +static bool +vp_AudioStart (TFB_SoundSample* sample) +{ + TFB_VideoClip* vid = TFB_GetSoundSampleData (sample); + TFB_SoundDecoder *decoder; + + assert (sizeof (intptr_t) >= sizeof (vid)); + assert (vid != NULL); + + decoder = TFB_GetSoundSampleDecoder (sample); + + LockMutex (vid->guard); + vid->want_frame = SoundDecoder_GetFrame (decoder); + UnlockMutex (vid->guard); + + return true; +} + +static void +vp_AudioEnd (TFB_SoundSample* sample) +{ + TFB_VideoClip* vid = TFB_GetSoundSampleData (sample); + + assert (vid != NULL); + + LockMutex (vid->guard); + vid->want_frame = vid->decoder->frame_count; // end it + UnlockMutex (vid->guard); +} + +static void +vp_BufferTag (TFB_SoundSample* sample, TFB_SoundTag* tag) +{ + TFB_VideoClip* vid = TFB_GetSoundSampleData (sample); + uint32 frame = (uint32) tag->data; + + assert (sizeof (tag->data) >= sizeof (frame)); + assert (vid != NULL); + + LockMutex (vid->guard); + vid->want_frame = frame; // let it go! + UnlockMutex (vid->guard); +} + +static void +vp_QueueBuffer (TFB_SoundSample* sample, audio_Object buffer) +{ + //TFB_VideoClip* vid = (TFB_VideoClip*) TFB_GetSoundSampleData (sample); + TFB_SoundDecoder *decoder = TFB_GetSoundSampleDecoder (sample); + + TFB_TagBuffer (sample, buffer, + (intptr_t) SoundDecoder_GetFrame (decoder)); +} + diff --git a/src/libs/video/vidplayer.h b/src/libs/video/vidplayer.h new file mode 100644 index 0000000..78e4edb --- /dev/null +++ b/src/libs/video/vidplayer.h @@ -0,0 +1,31 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_VIDEO_VIDPLAYER_H_ +#define LIBS_VIDEO_VIDPLAYER_H_ + +#include "video.h" + +extern bool TFB_InitVideoPlayer (void); +extern void TFB_UninitVideoPlayer (void); +extern bool TFB_PlayVideo (VIDEO_REF VidRef, uint32 x, uint32 y); +extern void TFB_StopVideo (VIDEO_REF VidRef); +extern bool TFB_VideoPlaying (VIDEO_REF VidRef); +extern bool TFB_ProcessVideoFrame (VIDEO_REF vid); +extern uint32 TFB_GetVideoPosition (VIDEO_REF VidRef); +extern bool TFB_SeekVideo (VIDEO_REF VidRef, uint32 pos); + +#endif // LIBS_VIDEO_VIDPLAYER_H_ diff --git a/src/libs/video/vresins.c b/src/libs/video/vresins.c new file mode 100644 index 0000000..dbb18e0 --- /dev/null +++ b/src/libs/video/vresins.c @@ -0,0 +1,186 @@ +// Copyright 2008 Michael Martin + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <stdlib.h> +#include <string.h> +#include "vidintrn.h" +#include "libs/log.h" +#include "libs/memlib.h" + + +static BOOLEAN +FreeLegacyVideoData (void *data) +{ + LEGACY_VIDEO pLV; + if (!data) + return FALSE; + + pLV = (LEGACY_VIDEO) data; + if (pLV->video) + HFree (pLV->video); + if (pLV->audio) + HFree (pLV->audio); + if (pLV->speech) + HFree (pLV->speech); + HFree (pLV); + + return TRUE; +} + +static void +GetLegacyVideoData (const char *path, RESOURCE_DATA *resdata) +{ + void *result = NULL; + char paths[1024], *audio_path, *speech_path, *loop_str; + uint32 LoopFrame = VID_NO_LOOP; + + /* Parse out the video components. */ + strncpy (paths, path, 1023); + paths[1023] = '\0'; + audio_path = strchr (paths, ':'); + if (audio_path == NULL) + { + speech_path = NULL; + loop_str = NULL; + } + else + { + *audio_path = '\0'; + audio_path++; + + speech_path = strchr (audio_path, ':'); + if (speech_path == NULL) + { + loop_str = NULL; + } + else + { + *speech_path = '\0'; + speech_path++; + + loop_str = strchr (speech_path, ':'); + if (loop_str != NULL) { + *loop_str = '\0'; + loop_str++; + } + } + } + + log_add (log_Info, "\t'%s' -- video", paths); + if (audio_path) + log_add (log_Info, "\t'%s' -- audio", audio_path); + else + log_add (log_Info, "\tNo associated audio"); + if (speech_path) + log_add (log_Info, "\t'%s' -- speech path", speech_path); + else + log_add (log_Info, "\tNo associated speech"); + if (loop_str) + { + char *end; + LoopFrame = strtol (loop_str, &end, 10); + // We allow whitespace at the end, but nothing printable. + if (*end > 32) { + log_add (log_Warning, "Warning: Unparsable loop frame '%s'. Disabling loop.", loop_str); + LoopFrame = VID_NO_LOOP; + } + log_add (log_Info, "\tLoop frame is %u", LoopFrame); + } + else + log_add (log_Info, "\tNo specified loop frame"); + + result = HMalloc (sizeof (LEGACY_VIDEO_DESC)); + if (result) + { + LEGACY_VIDEO pLV = (LEGACY_VIDEO) result; + int len; + pLV->video = NULL; + pLV->audio = NULL; + pLV->speech = NULL; + pLV->loop = LoopFrame; + + len = strlen(paths)+1; + pLV->video = (char *)HMalloc (len); + if (!pLV->video) + { + log_add (log_Warning, "Warning: Couldn't allocate space for '%s'", paths); + goto err; + } + strncpy(pLV->video, paths, len); + + if (audio_path) + { + len = strlen(audio_path)+1; + pLV->audio = (char *)HMalloc (len); + if (!pLV->audio) + { + log_add (log_Warning, "Warning: Couldn't allocate space for '%s'", audio_path); + goto err; + } + strncpy(pLV->audio, audio_path, len); + } + + if (speech_path) + { + len = strlen(speech_path)+1; + pLV->speech = (char *)HMalloc (len); + if (!pLV->speech) + { + log_add (log_Warning, "Warning: Couldn't allocate space for '%s'", speech_path); + goto err; + } + strncpy(pLV->speech, speech_path, len); + } + + resdata->ptr = result; + } + return; +err: + if (result) + FreeLegacyVideoData ((LEGACY_VIDEO)result); + + resdata->ptr = NULL; + return; +} + +BOOLEAN +InstallVideoResType (void) +{ + InstallResTypeVectors ("3DOVID", GetLegacyVideoData, FreeLegacyVideoData, NULL); + return TRUE; +} + +LEGACY_VIDEO +LoadLegacyVideoInstance (RESOURCE res) +{ + void *data; + + data = res_GetResource (res); + if (data) + { + res_DetachResource (res); + } + + return (LEGACY_VIDEO)data; +} + +BOOLEAN +DestroyLegacyVideo (LEGACY_VIDEO vid) +{ + return FreeLegacyVideoData (vid); +} diff --git a/src/libs/vidlib.h b/src/libs/vidlib.h new file mode 100644 index 0000000..9c32d7c --- /dev/null +++ b/src/libs/vidlib.h @@ -0,0 +1,68 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_VIDLIB_H_ +#define LIBS_VIDLIB_H_ + +#include "libs/compiler.h" +#include "libs/sndlib.h" +#include "libs/reslib.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +typedef enum +{ + NO_FMV = 0, + HARDWARE_FMV, + SOFTWARE_FMV +} VIDEO_TYPE; + +typedef struct tfb_videoclip *VIDEO_REF; +typedef struct legacy_video_desc *LEGACY_VIDEO; +typedef struct legacy_video_ref *LEGACY_VIDEO_REF; + +extern BOOLEAN InstallVideoResType (void); + +extern BOOLEAN InitVideoPlayer (BOOLEAN UseCDROM); +extern void UninitVideoPlayer (void); + +extern VIDEO_REF LoadVideoFile (const char *pStr); +extern BOOLEAN DestroyVideo (VIDEO_REF VidRef); +extern VIDEO_TYPE VidPlay (VIDEO_REF VidRef); +extern VIDEO_TYPE VidPlayEx (VIDEO_REF VidRef, MUSIC_REF AudRef, + MUSIC_REF SpeechRef, DWORD LoopFrame); +#define VID_NO_LOOP (0U-1) +extern void VidStop (void); +extern VIDEO_REF VidPlaying (void); +extern BOOLEAN VidProcessFrame (void); +extern DWORD VidGetPosition (void); // position in milliseconds +extern BOOLEAN VidSeek (DWORD pos); // position in milliseconds + +extern LEGACY_VIDEO LoadLegacyVideoInstance (RESOURCE res); +extern BOOLEAN DestroyLegacyVideo (LEGACY_VIDEO vid); +extern LEGACY_VIDEO_REF PlayLegacyVideo (LEGACY_VIDEO vid); +extern void StopLegacyVideo (LEGACY_VIDEO_REF ref); +extern BOOLEAN PlayingLegacyVideo (LEGACY_VIDEO_REF ref); + +#if defined(__cplusplus) +} +#endif + +#endif /* LIBS_VIDLIB_H_ */ |