Pārlūkot izejas kodu

Add LucasArts 3d shooter archives

Read-Only support for the GOB, LAB and LFD archives found in
1990's LucasArts titles like "Dark Forces", "Outlaws" and
"Jedi Knight" / "Mysteries of the Sith".

Signed-off-by: Manuel Lauss <manuel.lauss@gmail.com>
Manuel Lauss 1 gadu atpakaļ
vecāks
revīzija
1990660427
5 mainītis faili ar 416 papildinājumiem un 1 dzēšanām
  1. 7 0
      CMakeLists.txt
  2. 2 1
      src/Makefile.os2
  3. 5 0
      src/physfs.c
  4. 395 0
      src/physfs_archiver_lec3d.c
  5. 7 0
      src/physfs_internal.h

+ 7 - 0
CMakeLists.txt

@@ -98,6 +98,7 @@ set(PHYSFS_SRCS
     src/physfs_archiver_slb.c
     src/physfs_archiver_iso9660.c
     src/physfs_archiver_vdf.c
+    src/physfs_archiver_lec3d.c
     ${PHYSFS_CPP_SRCS}
     ${PHYSFS_M_SRCS}
 )
@@ -162,6 +163,11 @@ if(NOT PHYSFS_ARCHIVE_VDF)
     add_definitions(-DPHYSFS_SUPPORTS_VDF=0)
 endif()
 
+option(PHYSFS_ARCHIVE_LECARCHIVES "Enable LucasArts GOB/LAB/LFD Archive support" TRUE)
+if(NOT PHYSFS_ARCHIVE_LECARCHIVES)
+    add_definitions(-DPHYSFS_SUPPORTS_LECARCHIVES=0)
+endif()
+
 
 option(PHYSFS_BUILD_STATIC "Build static library" TRUE)
 if(PHYSFS_BUILD_STATIC)
@@ -333,6 +339,7 @@ message_bool_option("QPAK support" PHYSFS_ARCHIVE_QPAK)
 message_bool_option("SLB support" PHYSFS_ARCHIVE_SLB)
 message_bool_option("VDF support" PHYSFS_ARCHIVE_VDF)
 message_bool_option("ISO9660 support" PHYSFS_ARCHIVE_ISO9660)
+message_bool_option("GOB/LAB/LFD support" PHYSFS_ARCHIVE_LECARCHIVES)
 message_bool_option("Build static library" PHYSFS_BUILD_STATIC)
 message_bool_option("Build shared library" PHYSFS_BUILD_SHARED)
 message_bool_option("Build stdio test program" PHYSFS_BUILD_TEST)

+ 2 - 1
src/Makefile.os2

@@ -26,7 +26,8 @@ SRCS = physfs.c                   &
        physfs_archiver_slb.c      &
        physfs_archiver_iso9660.c  &
        physfs_archiver_csm.c      &
-       physfs_archiver_vdf.c
+       physfs_archiver_vdf.c      &
+       physfs_archiver_lec3d.c
 
 
 OBJS = $(SRCS:.c=.obj)

+ 5 - 0
src/physfs.c

@@ -1200,6 +1200,11 @@ static int initStaticArchivers(void)
     #if PHYSFS_SUPPORTS_VDF
         REGISTER_STATIC_ARCHIVER(VDF)
     #endif
+    #if PHYSFS_SUPPORTS_LECARCHIVES
+        REGISTER_STATIC_ARCHIVER(GOB)
+        REGISTER_STATIC_ARCHIVER(LFD)
+        REGISTER_STATIC_ARCHIVER(LAB)
+    #endif
 
     #undef REGISTER_STATIC_ARCHIVER
 

+ 395 - 0
src/physfs_archiver_lec3d.c

@@ -0,0 +1,395 @@
+/*
+ * LucasArts 1990s-era shooters container files
+ *  GOB (Dark Forces 1994,  Jedi Knight 1997)
+ *  LAB (Outlaws 1996)
+ *  LFD (XWing, Tie Fighter, Dark Forces, probably others)
+ *
+ * Written by Manuel Lauss <manuel.lauss@gmail.com>
+ */
+
+#define __PHYSICSFS_INTERNAL__
+#include "physfs_internal.h"
+
+#if PHYSFS_SUPPORTS_LECARCHIVES
+
+/* GOB1: WAD-Style */
+static int gob1LoadEntries(PHYSFS_Io *io, const PHYSFS_uint32 offset, void *arc)
+{
+	PHYSFS_uint32 i, entries, dofs, dlen;
+	char name[13];
+
+	if (!io->seek(io, offset))
+		return 0;
+
+	BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &entries, 4), 0);
+	entries = PHYSFS_swapULE32(entries);
+
+	for (i = 0; i < entries; i++)
+	{
+		BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &dofs, 4), 0);
+		BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &dlen, 4), 0);
+		BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, name, 13), 0);
+		name[12] = '\0';
+
+		dofs = PHYSFS_swapULE32(dofs);
+		dlen = PHYSFS_swapULE32(dlen);
+		BAIL_IF_ERRPASS(!UNPK_addEntry(arc, name, 0, -1, -1, dofs, dlen), 0);
+	}
+
+	return 1;
+}
+
+/* GOB2: GOB1 with 127 byte filepath hierarchy */
+static int gob2LoadEntries(PHYSFS_Io *io, const PHYSFS_uint32 offset, void *arc)
+{
+	PHYSFS_uint32 i, entries, dofs, dlen;
+	char name[128], *c;
+
+	if (!io->seek(io, offset))
+		return 0;
+
+	BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &entries, 4), 0);
+	entries = PHYSFS_swapULE32(entries);
+
+	for (i = 0; i < entries; i++)
+	{
+		BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &dofs, 4), 0);
+		BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &dlen, 4), 0);
+		BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, name, 128), 0);
+		name[127] = '\0';
+
+		/* replace backslashes */
+		c = name;
+		while (*c)
+		{
+			if (*c == '\\')
+				*c = '/';
+			++c;
+		}
+
+		dofs = PHYSFS_swapULE32(dofs);
+		dlen = PHYSFS_swapULE32(dlen);
+		BAIL_IF_ERRPASS(!UNPK_addEntry(arc, name, 0, -1, -1, dofs, dlen), 0);
+	}
+
+	return 1;
+}
+
+static void *GOB_openArchive(PHYSFS_Io *io, const char *name,
+			     int forWriting, int *claimed)
+{
+	PHYSFS_uint8 buf[12];
+	PHYSFS_uint32 catofs = 0;
+	void *unpkarc = NULL;
+	int ret, gob;
+
+	assert(io != NULL);  /* shouldn't ever happen. */
+
+	BAIL_IF(forWriting, PHYSFS_ERR_READ_ONLY, NULL);
+	BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, buf, 4), NULL);
+
+	if (memcmp(buf, "GOB\x0a", 4) == 0)
+	{
+		gob = 1;
+	}
+	else if (memcmp(buf, "GOB\x20", 4) == 0)
+	{
+		/* Version */
+		BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &catofs, 4), NULL);
+		catofs = PHYSFS_swapULE32(catofs);
+		if (catofs != 0x14)
+			BAIL(PHYSFS_ERR_UNSUPPORTED, NULL);
+
+		gob = 2;
+	}
+	else
+	{
+		BAIL(PHYSFS_ERR_UNSUPPORTED, NULL);
+		gob = 0;
+	}
+
+	BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &catofs, 4), NULL);
+	catofs = PHYSFS_swapULE32(catofs);
+
+	*claimed = 1;
+
+	unpkarc = UNPK_openArchive(io, 0, 1);
+	BAIL_IF_ERRPASS(!unpkarc, NULL);
+
+	switch (gob)
+	{
+	case 1: ret = gob1LoadEntries(io, catofs, unpkarc); break;
+	case 2: ret = gob2LoadEntries(io, catofs, unpkarc); break;
+	default: ret = 0;
+	}
+
+	if (ret == 0)
+	{
+		UNPK_abandonArchive(unpkarc);
+		return NULL;
+	}
+
+	return unpkarc;
+}
+
+const PHYSFS_Archiver __PHYSFS_Archiver_GOB =
+{
+	CURRENT_PHYSFS_ARCHIVER_API_VERSION,
+	{
+		"GOB",
+		"LucasArts GOB container",
+		"Manuel Lauss <manuel.lauss@gmail.com>",
+		"https://icculus.org/physfs/",
+		0,  /* supportsSymlinks */
+	},
+	GOB_openArchive,
+	UNPK_enumerate,
+	UNPK_openRead,
+	UNPK_openWrite,
+	UNPK_openAppend,
+	UNPK_remove,
+	UNPK_mkdir,
+	UNPK_stat,
+	UNPK_closeArchive
+};
+
+/** LFD **/
+
+static int lfdLoadEntries(PHYSFS_Io *io, const PHYSFS_uint32 skip, void *arc)
+{
+	char ext[5], finalname[16];
+	PHYSFS_uint32 dlen, pos;
+	PHYSFS_sint64 len;
+	int i;
+
+	len = io->length(io);
+	if (len < 4)
+	{
+		PHYSFS_setErrorCode(PHYSFS_ERR_PAST_EOF);
+		return 0;
+	}
+
+	pos = skip;
+	while (pos < len)
+	{
+		if (!io->seek(io, pos))
+			return 0;
+
+		memset(finalname, 0, 16);
+		BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ext, 4), 0);
+		BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, finalname, 8), 0);
+		BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &dlen, 4), 0);
+		dlen = PHYSFS_swapULE32(dlen);
+
+		ext[4] = 0;
+		i = strlen(finalname);
+		finalname[i] = '.';
+		strcpy(finalname + i + 1, ext);
+
+		BAIL_IF_ERRPASS(!UNPK_addEntry(arc, finalname, 0, -1, -1, pos, dlen), 0);
+
+		pos += 16 + dlen;	/* name+size entry + actual size */
+	}
+
+	return 1;
+}
+
+static void *LFD_openArchive(PHYSFS_Io *io, const char *name,
+			     int forWriting, int *claimed)
+{
+	PHYSFS_uint8 buf[16];
+	PHYSFS_uint32 catsize;
+	void *unpkarc = NULL;
+
+	assert(io != NULL);	/* shouldn't ever happen. */
+
+	BAIL_IF(forWriting, PHYSFS_ERR_READ_ONLY, NULL);
+	BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, buf, 12), NULL);
+
+	if (memcmp(buf, "RMAPresource", 12) == 0)
+	{
+		/* has a content directory */
+		BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &catsize, 4), NULL);
+		catsize = PHYSFS_swapULE32(catsize);
+		if ((catsize & 15) || (catsize == 0))
+			BAIL(PHYSFS_ERR_UNSUPPORTED, NULL);
+
+		catsize += 16;	/* include header */
+	}
+	else
+	{
+		catsize = 0;
+	}
+
+	if (!io->seek(io, 0))
+		return NULL;
+
+	*claimed = 1;
+
+	unpkarc = UNPK_openArchive(io, 0, 1);
+	BAIL_IF_ERRPASS(!unpkarc, NULL);
+
+	if (!lfdLoadEntries(io, catsize, unpkarc))
+	{
+		UNPK_abandonArchive(unpkarc);
+		return NULL;
+	}
+
+	return unpkarc;
+}
+
+const PHYSFS_Archiver __PHYSFS_Archiver_LFD =
+{
+	CURRENT_PHYSFS_ARCHIVER_API_VERSION,
+	{
+		"LFD",
+		"LucasArts LFD container",
+		"Manuel Lauss <manuel.lauss@gmail.com>",
+		"https://icculus.org/physfs/",
+		0,  /* supportsSymlinks */
+	},
+	LFD_openArchive,
+	UNPK_enumerate,
+	UNPK_openRead,
+	UNPK_openWrite,
+	UNPK_openAppend,
+	UNPK_remove,
+	UNPK_mkdir,
+	UNPK_stat,
+	UNPK_closeArchive
+};
+
+/** LAB **/
+
+/* read characters until \0 is found */
+static PHYSFS_sint32 readstring(PHYSFS_Io *io, char *dest, unsigned max)
+{
+	PHYSFS_uint32 bytes = 0;
+	PHYSFS_sint64 ret;
+	char c;
+
+	while (bytes < max)
+	{
+		ret = io->read(io, &c, 1);
+		if (ret == 0)
+			return 0; /* file ended prematurely */
+		else if (ret < 0)
+			return -1;
+		else if (c == 0)
+			return bytes;
+		*dest++ = c;
+		bytes++;
+	}
+	return bytes;
+}
+
+static int labLoadEntries(PHYSFS_Io *io, const PHYSFS_uint32 cnt, void *arc)
+{
+	const PHYSFS_uint32 lab_name_table_start = 16 + (cnt * 16);
+	PHYSFS_uint32 nofs, dofs, dlen, fcc;
+	PHYSFS_sint32 readlen;
+	PHYSFS_sint64 savepos;
+	char fn[32];
+	int i;
+
+	for (i = 0; i < cnt; i++)
+	{
+		BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &nofs, 4), 0);
+		BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &dofs, 4), 0);
+		BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &dlen, 4), 0);
+		BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &fcc, 4), 0);
+		nofs = PHYSFS_swapULE32(nofs);
+		dofs = PHYSFS_swapULE32(dofs);
+		dlen = PHYSFS_swapULE32(dlen);
+		fcc = PHYSFS_swapULE32(fcc);
+
+		/* remember where we parked */
+		savepos = io->tell(io);
+		if (savepos < 0)
+			return 0;
+		/* go to the filename table entry and read it */
+		if (!io->seek(io, lab_name_table_start + nofs))
+			return 0;
+		memset(fn, 0, 32);
+		readlen = readstring(io, fn, 31);
+		if (readlen > 0)
+		{
+			BAIL_IF_ERRPASS(!UNPK_addEntry(arc, fn, 0, -1, -1, dofs, dlen), 0);
+		}
+		else
+		{
+			if (readlen == 0)
+				PHYSFS_setErrorCode(PHYSFS_ERR_PAST_EOF);
+
+			return 0;
+		}
+
+		/* ah that's the spot */
+		if (!io->seek(io, savepos))
+			return 0;
+	}
+
+	return 1;
+}
+
+static void *LAB_openArchive(PHYSFS_Io *io, const char *name,
+			     int forWriting, int *claimed)
+{
+	PHYSFS_uint8 buf[16];
+	PHYSFS_uint32 catsize, entries, i;
+	void *unpkarc = NULL;
+
+	assert(io != NULL);  /* shouldn't ever happen. */
+
+	BAIL_IF(forWriting, PHYSFS_ERR_READ_ONLY, NULL);
+	BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, buf, 4), NULL);
+	if (memcmp(buf, "LABN", 4) != 0)
+		BAIL(PHYSFS_ERR_UNSUPPORTED, NULL);
+
+	/* Container Format version, always 0x00010000 */
+	BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &i, 4), NULL);
+	i = PHYSFS_swapULE32(i);
+	if (i != 0x00010000)
+		BAIL(PHYSFS_ERR_UNSUPPORTED, NULL);
+
+	BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &entries, 4), NULL);
+	BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &catsize, 4), NULL);
+	entries = PHYSFS_swapULE32(entries);
+	catsize = PHYSFS_swapULE32(catsize);
+
+	*claimed = 1;
+
+	unpkarc = UNPK_openArchive(io, 0, 1);
+	BAIL_IF_ERRPASS(!unpkarc, NULL);
+
+	if (!labLoadEntries(io, entries, unpkarc))
+	{
+		UNPK_abandonArchive(unpkarc);
+		return NULL;
+	}
+
+	return unpkarc;
+}
+
+const PHYSFS_Archiver __PHYSFS_Archiver_LAB =
+{
+	CURRENT_PHYSFS_ARCHIVER_API_VERSION,
+	{
+		"LAB",
+		"LucasArts LAB container",
+		"Manuel Lauss <manuel.lauss@gmail.com>",
+		"https://icculus.org/physfs/",
+		0,  /* supportsSymlinks */
+	},
+	LAB_openArchive,
+	UNPK_enumerate,
+	UNPK_openRead,
+	UNPK_openWrite,
+	UNPK_openAppend,
+	UNPK_remove,
+	UNPK_mkdir,
+	UNPK_stat,
+	UNPK_closeArchive
+};
+
+#endif

+ 7 - 0
src/physfs_internal.h

@@ -92,6 +92,9 @@ extern const PHYSFS_Archiver __PHYSFS_Archiver_CSM;
 extern const PHYSFS_Archiver __PHYSFS_Archiver_SLB;
 extern const PHYSFS_Archiver __PHYSFS_Archiver_ISO9660;
 extern const PHYSFS_Archiver __PHYSFS_Archiver_VDF;
+extern const PHYSFS_Archiver __PHYSFS_Archiver_GOB;
+extern const PHYSFS_Archiver __PHYSFS_Archiver_LFD;
+extern const PHYSFS_Archiver __PHYSFS_Archiver_LAB;
 
 /* a real C99-compliant snprintf() is in Visual Studio 2015,
    but just use this everywhere for binary compatibility. */
@@ -221,6 +224,10 @@ void __PHYSFS_smallFree(void *ptr);
 #ifndef PHYSFS_SUPPORTS_VDF
 #define PHYSFS_SUPPORTS_VDF PHYSFS_SUPPORTS_DEFAULT
 #endif
+#ifndef PHYSFS_SUPPORTS_LECARCHIVES
+#define PHYSFS_SUPPORTS_LECARCHIVES PHYSFS_SUPPORTS_DEFAULT
+#endif
+
 
 #if PHYSFS_SUPPORTS_7Z
 /* 7zip support needs a global init function called at startup (no deinit). */