aboutsummaryrefslogtreecommitdiff
path: root/engines
diff options
context:
space:
mode:
authorPaul Gilbert2018-11-10 21:22:57 -0800
committerPaul Gilbert2018-12-08 19:05:59 -0800
commitb8fcb62dd242db6d50a252ec7cfabe97fe5c9b6c (patch)
treeab64da0ee43e3fb1fc0b219c6c57815da67767f5 /engines
parent4497a55a860a5ec5de47075e11cf69deffd2031d (diff)
downloadscummvm-rg350-b8fcb62dd242db6d50a252ec7cfabe97fe5c9b6c.tar.gz
scummvm-rg350-b8fcb62dd242db6d50a252ec7cfabe97fe5c9b6c.tar.bz2
scummvm-rg350-b8fcb62dd242db6d50a252ec7cfabe97fe5c9b6c.zip
GLK: Added Blorb container file handling
Diffstat (limited to 'engines')
-rw-r--r--engines/gargoyle/blorb.cpp543
-rw-r--r--engines/gargoyle/blorb.h146
-rw-r--r--engines/gargoyle/glk.h3
-rw-r--r--engines/gargoyle/module.mk1
4 files changed, 692 insertions, 1 deletions
diff --git a/engines/gargoyle/blorb.cpp b/engines/gargoyle/blorb.cpp
new file mode 100644
index 0000000000..064f9c48a1
--- /dev/null
+++ b/engines/gargoyle/blorb.cpp
@@ -0,0 +1,543 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "gargoyle/blorb.h"
+
+namespace Gargoyle {
+
+#define giblorb_Inited_Magic 0xB7012BEDU
+
+/**
+ * Describes one chunk of the Blorb file.
+ */
+struct giblorb_chunkdesc_struct {
+ glui32 type;
+ glui32 len;
+ glui32 startpos; ///< start of chunk header
+ glui32 datpos; ///< start of data (either startpos or startpos+8)
+
+ void *ptr; ///< pointer to malloc'd data, if loaded
+ int auxdatnum; ///< entry in the auxsound/auxpict array; -1 if none. This only applies to chunks that represent resources;
+};
+typedef giblorb_chunkdesc_struct giblorb_chunkdesc_t;
+
+/**
+ * Describes one resource in the Blorb file.
+ */
+struct giblorb_resdesc_struct {
+ glui32 usage;
+ glui32 resnum;
+ glui32 chunknum;
+};
+
+/**
+ * Holds the complete description of an open Blorb file.
+ */
+struct giblorb_map_struct {
+ glui32 inited; ///< holds giblorb_Inited_Magic if the map structure is valid
+ Common::SeekableReadStream *file;
+
+ uint numchunks;
+ giblorb_chunkdesc_t *chunks; ///< list of chunk descriptors
+
+ int numresources;
+ giblorb_resdesc_t *resources; ///< list of resource descriptors
+ giblorb_resdesc_t **ressorted; ///< list of pointers to descriptors in map->resources -- sorted by usage and resource number.
+};
+
+/*--------------------------------------------------------------------------*/
+
+giblorb_err_t Blorb::giblorb_initialize() {
+ _file = nullptr;
+ _map = nullptr;
+ return giblorb_err_None;
+}
+
+giblorb_err_t Blorb::giblorb_create_map(Common::SeekableReadStream *file, giblorb_map_t **newmap) {
+ giblorb_err_t err;
+ giblorb_map_t *map;
+ glui32 readlen;
+ glui32 nextpos, totallength;
+ giblorb_chunkdesc_t *chunks;
+ int chunks_size, numchunks;
+ char buffer[16];
+
+ *newmap = nullptr;
+
+ if (!_libInited) {
+ err = giblorb_initialize();
+ if (err)
+ return err;
+ _libInited = true;
+ }
+
+ /* First, chew through the file and index the chunks. */
+ file->seek(0);
+
+ readlen = file->read(buffer, 12);
+ if (readlen != 12)
+ return giblorb_err_Read;
+
+ if (READ_BE_INT32(buffer + 0) != giblorb_ID_FORM)
+ return giblorb_err_Format;
+ if (READ_BE_INT32(buffer + 8) != giblorb_ID_IFRS)
+ return giblorb_err_Format;
+
+ totallength = READ_BE_INT32(buffer + 4) + 8;
+ nextpos = 12;
+
+ chunks_size = 8;
+ numchunks = 0;
+ chunks = new giblorb_chunkdesc_t[chunks_size];
+
+ while (nextpos < totallength) {
+ glui32 type, len;
+ int chunum;
+ giblorb_chunkdesc_t *chu;
+
+ file->seek(nextpos);
+
+ readlen = file->read(buffer, 8);
+ if (readlen != 8) {
+ delete[] chunks;
+ return giblorb_err_Read;
+ }
+
+ type = READ_BE_INT32(buffer + 0);
+ len = READ_BE_INT32(buffer + 4);
+
+ if (numchunks >= chunks_size) {
+ chunks_size *= 2;
+ chunks = new giblorb_chunkdesc_t[chunks_size];
+ }
+
+ chunum = numchunks;
+ chu = &(chunks[chunum]);
+ numchunks++;
+
+ chu->type = type;
+ chu->startpos = nextpos;
+ if (type == giblorb_ID_FORM) {
+ chu->datpos = nextpos;
+ chu->len = len + 8;
+ } else {
+ chu->datpos = nextpos + 8;
+ chu->len = len;
+ }
+ chu->ptr = nullptr;
+ chu->auxdatnum = -1;
+
+ nextpos = nextpos + len + 8;
+ if (nextpos & 1)
+ nextpos++;
+
+ if (nextpos > totallength) {
+ delete[] chunks;
+ return giblorb_err_Format;
+ }
+ }
+
+ // The basic IFF structure seems to be ok, and we have a list of chunks.
+ // Now we allocate the map structure itself.
+ map = new giblorb_map_t();
+ if (!map) {
+ delete[] chunks;
+ return giblorb_err_Alloc;
+ }
+
+ map->inited = giblorb_Inited_Magic;
+ map->file = file;
+ map->chunks = chunks;
+ map->numchunks = numchunks;
+ map->resources = nullptr;
+ map->ressorted = nullptr;
+ map->numresources = 0;
+
+ // Now we do everything else involved in loading the Blorb file,
+ // such as building resource lists.
+ err = giblorb_initialize_map(map);
+ if (err) {
+ giblorb_destroy_map(map);
+ return err;
+ }
+
+ *newmap = map;
+ return giblorb_err_None;
+}
+
+
+giblorb_err_t Blorb::giblorb_initialize_map(giblorb_map_t *map) {
+ // It is important that the map structure be kept valid during this function.
+ // If this returns an error, giblorb_destroy_map() will be called.
+ uint ix, jx;
+ giblorb_result_t chunkres;
+ giblorb_err_t err;
+ char *ptr;
+ glui32 len;
+ glui32 numres;
+ int gotindex = false;
+
+ for (ix = 0; ix<map->numchunks; ix++) {
+ giblorb_chunkdesc_t *chu = &map->chunks[ix];
+
+ switch (chu->type) {
+ case giblorb_ID_RIdx:
+ // Resource index chunk: build the resource list and sort it.
+
+ if (gotindex)
+ return giblorb_err_Format; // duplicate index chunk
+ err = giblorb_load_chunk_by_number(map, giblorb_method_Memory, &chunkres, ix);
+ if (err)
+ return err;
+
+ ptr = (char *)chunkres.data.ptr;
+ len = chunkres.length;
+ numres = READ_BE_INT32(ptr + 0);
+
+ if (numres) {
+ uint ix2;
+ giblorb_resdesc_t *resources;
+ giblorb_resdesc_t **ressorted;
+
+ if (len != numres * 12 + 4)
+ return giblorb_err_Format; // bad length field
+
+ resources = new giblorb_resdesc_t[numres];
+ ressorted = new giblorb_resdesc_t *[numres];
+ if (!ressorted || !resources) {
+ delete[] resources;
+ delete[] ressorted;
+ return giblorb_err_Alloc;
+ }
+
+ ix2 = 0;
+ for (jx = 0; jx < numres; jx++) {
+ giblorb_resdesc_t *res = &(resources[jx]);
+ glui32 respos;
+
+ res->usage = READ_BE_INT32(ptr + jx * 12 + 4);
+ res->resnum = READ_BE_INT32(ptr + jx * 12 + 8);
+ respos = READ_BE_INT32(ptr + jx * 12 + 12);
+
+ while (ix2 < map->numchunks
+ && map->chunks[ix2].startpos < respos)
+ ix2++;
+
+ if (ix2 >= map->numchunks
+ || map->chunks[ix2].startpos != respos) {
+ delete[] resources;
+ delete[] ressorted;
+ return giblorb_err_Format; // start pos does not match a real chunk
+ }
+
+ res->chunknum = ix2;
+
+ ressorted[jx] = res;
+ }
+
+ // Sort a resource list (actually a list of pointers to structures in map->resources.)
+ // This makes it easy to find resources by usage and resource number.
+ giblorb_qsort(ressorted, numres);
+
+ map->numresources = numres;
+ map->resources = resources;
+ map->ressorted = ressorted;
+ }
+
+ giblorb_unload_chunk(map, ix);
+ gotindex = true;
+ break;
+ }
+ }
+
+ return giblorb_err_None;
+}
+
+void Blorb::giblorb_qsort(giblorb_resdesc_t **list, size_t len) {
+ int ix, jx, res;
+ giblorb_resdesc_t *tmpptr, *pivot;
+
+ if (len < 6) {
+ // The list is short enough for a bubble-sort.
+ for (jx = len - 1; jx>0; jx--) {
+ for (ix = 0; ix<jx; ix++) {
+ res = sortsplot(list[ix], list[ix + 1]);
+ if (res > 0) {
+ tmpptr = list[ix];
+ list[ix] = list[ix + 1];
+ list[ix + 1] = tmpptr;
+ }
+ }
+ }
+ } else {
+ // Split the list.
+ pivot = list[len / 2];
+ ix = 0;
+ jx = len;
+ for (;;) {
+ while (ix < jx - 1 && sortsplot(list[ix], pivot) < 0)
+ ix++;
+ while (ix < jx - 1 && sortsplot(list[jx - 1], pivot) > 0)
+ jx--;
+ if (ix >= jx - 1)
+ break;
+ tmpptr = list[ix];
+ list[ix] = list[jx - 1];
+ list[jx - 1] = tmpptr;
+ }
+ ix++;
+ // Sort the halves.
+ giblorb_qsort(list + 0, ix);
+ giblorb_qsort(list + ix, len - ix);
+ }
+}
+
+giblorb_resdesc_t *Blorb::giblorb_bsearch(giblorb_resdesc_t *sample,
+ giblorb_resdesc_t **list, int len) {
+ int top, bot, val, res;
+
+ bot = 0;
+ top = len;
+
+ while (bot < top) {
+ val = (top + bot) / 2;
+ res = sortsplot(list[val], sample);
+ if (res == 0)
+ return list[val];
+ if (res < 0) {
+ bot = val + 1;
+ } else {
+ top = val;
+ }
+ }
+
+ return nullptr;
+}
+
+int Blorb::sortsplot(giblorb_resdesc_t *v1, giblorb_resdesc_t *v2) {
+ if (v1->usage < v2->usage)
+ return -1;
+ if (v1->usage > v2->usage)
+ return 1;
+ if (v1->resnum < v2->resnum)
+ return -1;
+ if (v1->resnum > v2->resnum)
+ return 1;
+ return 0;
+}
+
+giblorb_err_t Blorb::giblorb_destroy_map(giblorb_map_t *map) {
+ if (!map || !map->chunks || map->inited != giblorb_Inited_Magic)
+ return giblorb_err_NotAMap;
+
+ for (uint ix = 0; ix<map->numchunks; ix++) {
+ giblorb_chunkdesc_t *chu = &(map->chunks[ix]);
+ if (chu->ptr) {
+ delete chu->ptr;
+ chu->ptr = nullptr;
+ }
+ }
+
+ if (map->chunks) {
+ delete[] map->chunks;
+ map->chunks = nullptr;
+ }
+
+ map->numchunks = 0;
+
+ if (map->resources) {
+ delete[] map->resources;
+ map->resources = nullptr;
+ }
+
+ if (map->ressorted) {
+ delete[] map->ressorted;
+ map->ressorted = nullptr;
+ }
+
+ map->numresources = 0;
+ map->file = nullptr;
+ map->inited = 0;
+
+ delete map;
+
+ return giblorb_err_None;
+}
+
+giblorb_err_t Blorb::giblorb_load_chunk_by_type(giblorb_map_t *map,
+ glui32 method, giblorb_result_t *res, glui32 chunktype, glui32 count) {
+ uint ix;
+
+ for (ix = 0; ix < map->numchunks; ix++) {
+ if (map->chunks[ix].type == chunktype) {
+ if (count == 0)
+ break;
+ count--;
+ }
+ }
+
+ if (ix >= map->numchunks) {
+ return giblorb_err_NotFound;
+ }
+
+ return giblorb_load_chunk_by_number(map, method, res, ix);
+}
+
+giblorb_err_t Blorb::giblorb_load_chunk_by_number(giblorb_map_t *map,
+ glui32 method, giblorb_result_t *res, glui32 chunknum) {
+ giblorb_chunkdesc_t *chu;
+
+ if (chunknum < 0 || chunknum >= map->numchunks)
+ return giblorb_err_NotFound;
+
+ chu = &(map->chunks[chunknum]);
+
+ switch (method) {
+ case giblorb_method_DontLoad:
+ // do nothing
+ break;
+
+ case giblorb_method_FilePos:
+ res->data.startpos = chu->datpos;
+ break;
+
+ case giblorb_method_Memory:
+ if (!chu->ptr) {
+ glui32 readlen;
+ byte *dat = new byte[chu->len];
+
+ if (!dat)
+ return giblorb_err_Alloc;
+
+ map->file->seek(chu->datpos);
+
+ readlen = map->file->read(dat, chu->len);
+ if (readlen != chu->len)
+ return giblorb_err_Read;
+
+ chu->ptr = dat;
+ }
+
+ res->data.ptr = chu->ptr;
+ break;
+ }
+
+ res->chunknum = chunknum;
+ res->length = chu->len;
+ res->chunktype = chu->type;
+
+ return giblorb_err_None;
+}
+
+giblorb_err_t Blorb::giblorb_unload_chunk(giblorb_map_t *map, glui32 chunknum) {
+ giblorb_chunkdesc_t *chu;
+
+ if (chunknum < 0 || chunknum >= map->numchunks)
+ return giblorb_err_NotFound;
+
+ chu = &(map->chunks[chunknum]);
+
+ if (chu->ptr) {
+ delete chu->ptr;
+ chu->ptr = nullptr;
+ }
+
+ return giblorb_err_None;
+}
+
+giblorb_err_t Blorb::giblorb_load_resource(giblorb_map_t *map, glui32 method,
+ giblorb_result_t *res, glui32 usage, glui32 resnum) {
+ giblorb_resdesc_t sample;
+ giblorb_resdesc_t *found;
+
+ sample.usage = usage;
+ sample.resnum = resnum;
+
+ found = giblorb_bsearch(&sample, map->ressorted, map->numresources);
+
+ if (!found)
+ return giblorb_err_NotFound;
+
+ return giblorb_load_chunk_by_number(map, method, res, found->chunknum);
+}
+
+giblorb_err_t Blorb::giblorb_count_resources(giblorb_map_t *map,
+ glui32 usage, glui32 *num, glui32 *min, glui32 *max) {
+ int ix;
+ int count;
+ glui32 val;
+ glui32 minval, maxval;
+
+ count = 0;
+ minval = 0;
+ maxval = 0;
+
+ for (ix = 0; ix<map->numresources; ix++) {
+ if (map->resources[ix].usage == usage) {
+ val = map->resources[ix].resnum;
+ if (count == 0) {
+ count++;
+ minval = val;
+ maxval = val;
+ }
+ else {
+ count++;
+ if (val < minval)
+ minval = val;
+ if (val > maxval)
+ maxval = val;
+ }
+ }
+ }
+
+ if (num)
+ *num = count;
+ if (min)
+ *min = minval;
+ if (max)
+ *max = maxval;
+
+ return giblorb_err_None;
+}
+
+giblorb_err_t Blorb::giblorb_set_resource_map(Common::SeekableReadStream *file) {
+ giblorb_err_t err;
+
+ err = giblorb_create_map(file, &_map);
+ if (err) {
+ _map = nullptr;
+ return err;
+ }
+
+ _file = file;
+ return giblorb_err_None;
+}
+
+giblorb_map_t *Blorb::giblorb_get_resource_map(void) {
+ return _map;
+}
+
+bool Blorb::giblorb_is_resource_map(void) const {
+ return _map != nullptr;
+}
+
+} // End of namespace Gargoyle
diff --git a/engines/gargoyle/blorb.h b/engines/gargoyle/blorb.h
new file mode 100644
index 0000000000..9de1947cb0
--- /dev/null
+++ b/engines/gargoyle/blorb.h
@@ -0,0 +1,146 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef GARGOYLE_BLORB_H
+#define GARGOYLE_BLORB_H
+
+#include "gargoyle/glk_types.h"
+#include "gargoyle/streams.h"
+
+namespace Gargoyle {
+
+
+/**
+ * Error type
+ */
+typedef glui32 giblorb_err_t;
+
+/**
+ * Error codes
+ */
+enum giblorbError {
+ giblorb_err_None = 0,
+ giblorb_err_CompileTime = 1,
+ giblorb_err_Alloc = 2,
+ giblorb_err_Read = 3,
+ giblorb_err_NotAMap = 4,
+ giblorb_err_Format = 5,
+ giblorb_err_NotFound = 6
+};
+
+/**
+ * Methods for loading a chunk
+ */
+enum giblorbMethod {
+ giblorb_method_DontLoad = 0,
+ giblorb_method_Memory = 1,
+ giblorb_method_FilePos = 2
+};
+
+enum {
+ giblorb_ID_Snd = MKTAG('S', 'n', 'd', ' '),
+ giblorb_ID_Exec = MKTAG('E', 'x', 'e', 'c'),
+ giblorb_ID_Pict = MKTAG('P', 'i', 'c', 't'),
+ giblorb_ID_Copyright = MKTAG('(', 'c', ')', ' '),
+ giblorb_ID_AUTH = MKTAG('A', 'U', 'T', 'H'),
+ giblorb_ID_ANNO = MKTAG('A', 'N', 'N', 'O')
+};
+
+
+enum {
+ giblorb_ID_MOD = MKTAG('M', 'O', 'D', ' '),
+ giblorb_ID_FORM = MKTAG('F', 'O', 'R', 'M'),
+ giblorb_ID_IFRS = MKTAG('I', 'F', 'R', 'S'),
+ giblorb_ID_RIdx = MKTAG('R', 'I', 'd', 'x'),
+ giblorb_ID_OGG = MKTAG('O', 'G', 'G', 'V'),
+
+ // non-standard types
+ giblorb_ID_MIDI = MKTAG('M', 'I', 'D', 'I'),
+ giblorb_ID_MP3 = MKTAG('M', 'P', '3', ' '),
+ giblorb_ID_WAVE = MKTAG('W', 'A', 'V', 'E')
+};
+
+/**
+ * Holds the complete description of an open Blorb file.
+ * This type is opaque for normal interpreter use.
+ */
+typedef struct giblorb_map_struct giblorb_map_t;
+
+/* giblorb_result_t: Result when you try to load a chunk. */
+typedef struct giblorb_result_struct {
+ glui32 chunknum; // The chunk number (for use in giblorb_unload_chunk(), etc.)
+ union {
+ void *ptr; ///< A pointer to the data (if you used giblorb_method_Memory)
+ glui32 startpos; ///< The position in the file (if you used giblorb_method_FilePos)
+ } data;
+
+ glui32 length; ///< The length of the data
+ glui32 chunktype; ///< The type of the chunk.
+} giblorb_result_t;
+
+typedef struct giblorb_resdesc_struct giblorb_resdesc_t;
+
+class Blorb {
+private:
+ bool _libInited;
+ Common::SeekableReadStream *_file;
+ giblorb_map_t *_map;
+private:
+ /**
+ * Initializes Blorb
+ */
+ giblorb_err_t giblorb_initialize();
+
+ giblorb_err_t giblorb_initialize_map(giblorb_map_t *map);
+ void giblorb_qsort(giblorb_resdesc_t **list, size_t len);
+ giblorb_resdesc_t *giblorb_bsearch(giblorb_resdesc_t *sample,
+ giblorb_resdesc_t **list, int len);
+ int sortsplot(giblorb_resdesc_t *v1, giblorb_resdesc_t *v2);
+public:
+ /**
+ * Constructor
+ */
+ Blorb() : _libInited(false), _file(nullptr), _map(nullptr) {}
+
+ giblorb_err_t giblorb_set_resource_map(Common::SeekableReadStream *file);
+ giblorb_map_t *giblorb_get_resource_map(void);
+ bool giblorb_is_resource_map(void) const;
+
+
+ giblorb_err_t giblorb_create_map(Common::SeekableReadStream *file, giblorb_map_t **newmap);
+ giblorb_err_t giblorb_destroy_map(giblorb_map_t *map);
+
+ giblorb_err_t giblorb_load_chunk_by_type(giblorb_map_t *map,
+ glui32 method, giblorb_result_t *res, glui32 chunktype, glui32 count);
+ giblorb_err_t giblorb_load_chunk_by_number(giblorb_map_t *map,
+ glui32 method, giblorb_result_t *res, glui32 chunknum);
+ giblorb_err_t giblorb_unload_chunk(giblorb_map_t *map, glui32 chunknum);
+
+ giblorb_err_t giblorb_load_resource(giblorb_map_t *map, glui32 method,
+ giblorb_result_t *res, glui32 usage, glui32 resnum);
+ giblorb_err_t giblorb_count_resources(giblorb_map_t *map,
+ glui32 usage, glui32 *num, glui32 *min, glui32 *max);
+};
+
+} // End of namespace Gargoyle
+
+#endif
diff --git a/engines/gargoyle/glk.h b/engines/gargoyle/glk.h
index b14546c684..9dc7f97b2c 100644
--- a/engines/gargoyle/glk.h
+++ b/engines/gargoyle/glk.h
@@ -25,6 +25,7 @@
#include "gargoyle/gargoyle.h"
#include "gargoyle/glk_types.h"
+#include "gargoyle/blorb.h"
#include "gargoyle/time.h"
#include "gargoyle/windows.h"
@@ -33,7 +34,7 @@ namespace Gargoyle {
/**
* Implements the GLK interface
*/
-class Glk : public GargoyleEngine {
+class Glk : public GargoyleEngine, public Blorb {
private:
bool _gliFirstEvent;
unsigned char _charTolowerTable[256];
diff --git a/engines/gargoyle/module.mk b/engines/gargoyle/module.mk
index f40a79a4e7..d359429487 100644
--- a/engines/gargoyle/module.mk
+++ b/engines/gargoyle/module.mk
@@ -1,6 +1,7 @@
MODULE := engines/gargoyle
MODULE_OBJS := \
+ blorb.o \
conf.o \
detection.o \
events.o \