shithub: pt2-clone

Download patch

ref: 222e15c0fd199046c3c9ed539b7c5f57729d4566
parent: 22cc03078470ce1f6d05a1c38f590b51f59937dc
author: Olav Sørensen <olav.sorensen@live.no>
date: Thu Feb 24 13:41:07 EST 2022

Added support for loading XPK-compressed modules

--- a/src/pt2_module_loader.c
+++ b/src/pt2_module_loader.c
@@ -28,6 +28,7 @@
 #include "pt2_sample_loader.h"
 #include "pt2_config.h"
 #include "pt2_sampling.h"
+#include "pt2_xpk.h"
 
 typedef struct mem_t
 {
@@ -220,16 +221,36 @@
 	}
 	else
 	{
-		modBuffer = (uint8_t *)malloc(filesize);
-		if (modBuffer == NULL)
+		if (DetectXPK(f))
 		{
-			statusOutOfMemory();
-			goto modLoadError;
+			if (!UnpackXPK(f, &filesize, &modBuffer))
+			{
+				displayErrorMsg("XPK UNPACK ERROR !");
+				goto modLoadError;
+			}
+
+			if (modBuffer == NULL)
+			{
+				statusOutOfMemory();
+				goto modLoadError;
+			}
+
+			fclose(f);
 		}
+		else
+		{
+			modBuffer = (uint8_t *)malloc(filesize);
 
-		fseek(f, 0, SEEK_SET);
-		fread(modBuffer, 1, filesize, f);
-		fclose(f);
+			if (modBuffer == NULL)
+			{
+				statusOutOfMemory();
+				goto modLoadError;
+			}
+
+			fseek(f, 0, SEEK_SET);
+			fread(modBuffer, 1, filesize, f);
+			fclose(f);
+		}
 	}
 
 	// smallest and biggest possible PT .MOD
--- /dev/null
+++ b/src/pt2_xpk.c
@@ -1,0 +1,438 @@
+/* Taken from the OpenMPT project and converted to C.
+** OpenMPT shares the same license as the PT2 clone (BSD 3-clause),
+** so the licensing is compatible.
+**
+** This is probably not 100% safe code, but it works.
+*/
+
+#include <assert.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "pt2_helpers.h"
+
+typedef struct XPKFILEHEADER
+{
+	char XPKF[4];
+	uint32_t SrcLen;
+	char SQSH[4];
+	uint32_t DstLen;
+	char Name[16];
+	uint32_t Reserved;
+} XPKFILEHEADER;
+
+typedef struct XPK_BufferBounds
+{
+	const uint8_t *pSrcBeg;
+	size_t SrcSize;
+} XPK_BufferBounds;
+
+static const uint8_t xpk_table[56] =
+{
+	2,3,4,5,6,7,8,0,3,2,4,5,6,7,8,0,4,3,5,
+	2,6,7,8,0,5,4,6,2,3,7,8,0,6,5,7,2,3,4,
+	8,0,7,6,8,2,3,4,5,0,8,7,6,2,3,4,5,0
+};
+
+static inline uint8_t SrcRead(size_t index, XPK_BufferBounds *bufs)
+{
+	assert(index < bufs->SrcSize);
+	if (index >= bufs->SrcSize)
+		return 0; // this is actually not how to do it, ugh...
+
+	return bufs->pSrcBeg[index];
+}
+
+static inline int32_t bfextu(size_t p, int32_t bo, int32_t bc, XPK_BufferBounds *bufs)
+{
+	uint32_t r;
+
+	p += (uint32_t)bo >> 3;
+	r = SrcRead(p, bufs); p++;
+	r <<= 8;
+	r |= SrcRead(p, bufs); p++;
+	r <<= 8;
+	r |= SrcRead(p, bufs);
+	r <<= bo & 7;
+	r &= 0x00FFFFFF;
+	r >>= 24 - bc;
+
+	return r;
+}
+
+static inline int32_t bfexts(size_t p, int32_t bo, int32_t bc, XPK_BufferBounds *bufs)
+{
+	uint32_t r;
+
+	p += (uint32_t)bo >> 3;
+	r = SrcRead(p, bufs); p++;
+	r <<= 8;
+	r |= SrcRead(p, bufs); p++;
+	r <<= 8;
+	r |= SrcRead(p, bufs);
+	r <<= (bo & 7) + 8;
+	r = (int32_t)r >> (32-bc);
+
+	return r;
+}
+
+static inline uint8_t XPK_ReadTable(int32_t index)
+{
+	if (index < 0 || index >= sizeof (xpk_table))
+		return 0; // this is actually not how to do it, ugh...
+
+	return xpk_table[index];
+}
+
+static bool XPK_DoUnpack(const uint8_t *src_, uint32_t srcLen, int32_t len, uint8_t **out)
+{
+	*out = NULL;
+	if (len <= 0)
+		return false;
+
+	int32_t d0,d1,d2,d3,d4,d5,d6,a2,a5;
+	int32_t cp, cup1, type;
+	size_t c, src, phist = 0;
+
+	const uint32_t unpackedLen = min((uint32_t)len, min(srcLen, UINT32_MAX / 20) * 20);
+
+	uint8_t *unpackedData = (uint8_t *)malloc(unpackedLen);
+	if (unpackedData == NULL)
+		return false;
+
+	uint8_t *PtrEnd = unpackedData + unpackedLen;
+	uint8_t *PtrOut = unpackedData;
+
+	XPK_BufferBounds bufs;
+	bufs.pSrcBeg = src_;
+	bufs.SrcSize = srcLen;
+
+	src = 0;
+	c = src;
+	while (len > 0)
+	{
+		type = SrcRead(c+0, &bufs);
+		cp = (SrcRead(c+4, &bufs)<<8) | (SrcRead(c+5, &bufs)); // packed
+		cup1 = (SrcRead(c+6, &bufs)<<8) | (SrcRead(c+7, &bufs)); // unpacked
+
+		c += 8;
+		src = c+2;
+		if (type == 0)
+		{
+			// RAW chunk
+			if (cp < 0 || cp > len)
+			{
+				free(unpackedData);
+				return false;
+			}
+
+			for (int32_t i = 0; i < cp; i++)
+				*PtrOut++ = SrcRead(c + i, &bufs);
+
+			c += cp;
+			len -= cp;
+			continue;
+		}
+
+		if (type != 1)
+			break;
+
+		if (cup1 > len)
+			cup1 = len;
+
+		len -= cup1;
+		cp = (cp + 3) & 0xFFFC;
+		c += cp;
+
+		d0 = d1 = d2 = a2 = 0;
+		d3 = SrcRead(src, &bufs); src++;
+		*PtrOut++ = (uint8_t)d3;
+		cup1--;
+
+		while (cup1 > 0)
+		{
+			if (d1 >= 8) goto l6dc;
+			if (bfextu(src,d0,1,&bufs)) goto l75a;
+			d0 += 1;
+			d5 = 0;
+			d6 = 8;
+			goto l734;
+
+l6dc:
+			if (bfextu(src,d0,1,&bufs)) goto l726;
+			d0 += 1;
+			if (!bfextu(src,d0,1,&bufs)) goto l75a;
+			d0 += 1;
+			if (bfextu(src,d0,1,&bufs)) goto l6f6;
+			d6 = 2;
+			goto l708;
+
+l6f6:
+			d0 += 1;
+			if (!bfextu(src,d0,1,&bufs)) goto l706;
+			d6 = bfextu(src,d0,3,&bufs);
+			d0 += 3;
+			goto l70a;
+
+l706:
+			d6 = 3;
+l708:
+			d0 += 1;
+l70a:
+			d6 = XPK_ReadTable((a2*8) + d6 - 17);
+			if (d6 != 8) goto l730;
+l718:
+			if (d2 >= 20)
+			{
+				d5 = 1;
+				goto l732;
+			}
+			d5 = 0;
+			goto l734;
+
+l726:
+			d0 += 1;
+			d6 = 8;
+			if (d6 == a2) goto l718;
+			d6 = a2;
+l730:
+			d5 = 4;
+l732:
+			d2 += 8;
+l734:
+			while (d5 >= 0 && cup1 > 0)
+			{
+				d4 = bfexts(src,d0,d6,&bufs);
+				d0 += d6;
+				d3 -= d4;
+				*PtrOut++ = (uint8_t)d3;
+				cup1--;
+				d5--;
+			}
+
+			if (d1 != 31)
+				d1++;
+
+			a2 = d6;
+l74c:
+			d6 = d2;
+			d6 >>= 3;
+			d2 -= d6;
+		}
+	}
+
+	if (PtrOut > PtrEnd)
+	{
+		free(unpackedData);
+		return false;
+	}
+
+	*out = unpackedData;
+	return true;
+
+l75a:
+	d0 += 1;
+	if (bfextu(src,d0,1,&bufs)) goto l766;
+	d4 = 2;
+	goto l79e;
+
+l766:
+	d0 += 1;
+	if (bfextu(src,d0,1,&bufs)) goto l772;
+	d4 = 4;
+	goto l79e;
+
+l772:
+	d0 += 1;
+	if (bfextu(src,d0,1,&bufs)) goto l77e;
+	d4 = 6;
+	goto l79e;
+
+l77e:
+	d0 += 1;
+	if (bfextu(src,d0,1,&bufs)) goto l792;
+	d0 += 1;
+	d6 = bfextu(src,d0,3,&bufs);
+	d0 += 3;
+	d6 += 8;
+	goto l7a8;
+
+l792:
+	d0 += 1;
+	d6 = bfextu(src,d0,5,&bufs);
+	d0 += 5;
+	d4 = 16;
+	goto l7a6;
+
+l79e:
+	d0 += 1;
+	d6 = bfextu(src,d0,1,&bufs);
+	d0 += 1;
+l7a6:
+	d6 += d4;
+l7a8:
+	if (bfextu(src, d0, 1, &bufs))
+	{
+		d5 = 12;
+		a5 = -0x100;
+	}
+	else
+	{
+		d0 += 1;
+		if (bfextu(src, d0, 1, &bufs))
+		{
+			d5 = 14;
+			a5 = -0x1100;
+		}
+		else
+		{
+			d5 = 8;
+			a5 = 0;
+		}
+	}
+
+	d0 += 1;
+	d4 = bfextu(src,d0,d5,&bufs);
+	d0 += d5;
+	d6 -= 3;
+	if (d6 >= 0)
+	{
+		if (d6 > 0)
+			d1 -= 1;
+
+		d1 -= 1;
+		if (d1 < 0)
+			d1 = 0;
+	}
+	d6 += 2;
+
+	phist = (size_t)(PtrOut-unpackedData) + a5 - d4 - 1;
+	if (phist >= (size_t)(PtrOut-unpackedData))
+	{
+		free(unpackedData);
+		return false;
+	}
+
+	while (d6 >= 0 && cup1 > 0)
+	{
+		d3 = unpackedData[phist];
+		phist++;
+		*PtrOut++ = (uint8_t)d3;
+		cup1--;
+		d6--;
+	}
+
+	goto l74c;
+}
+
+static bool ValidateHeader(XPKFILEHEADER *header)
+{
+	if (memcmp(header->XPKF, "XPKF", 4) != 0)
+		return false;
+
+	if (memcmp(header->SQSH, "SQSH", 4) != 0)
+		return false;
+
+	if (header->SrcLen == 0 || header->DstLen == 0)
+		return false;
+
+	if (header->SrcLen < sizeof (XPKFILEHEADER)-8)
+		return false;
+
+	return true;
+}
+
+bool ReadHeader(FILE *f, XPKFILEHEADER *header)
+{
+	if (f == NULL || fread(header, sizeof (XPKFILEHEADER), 1, f) != 1)
+		return false;
+
+	header->SrcLen = SWAP32(header->SrcLen);
+	header->DstLen = SWAP32(header->DstLen);
+	
+	return true;
+}
+
+bool DetectXPK(FILE *f)
+{
+	XPKFILEHEADER header;
+
+	uint32_t OldPos = ftell(f);
+	rewind(f);
+	bool result = ReadHeader(f, &header);
+	fseek(f, OldPos, SEEK_SET);
+
+	if (result == false)
+		return false;
+
+	return ValidateHeader(&header);
+}
+
+static bool ValidateHeaderFileSize(const XPKFILEHEADER *header, uint64_t filesize)
+{
+	if (filesize < header->SrcLen-8)
+		return false;
+
+	return true;
+}
+
+bool UnpackXPK(FILE *f, uint32_t *filesize, uint8_t **out)
+{
+	XPKFILEHEADER header;
+
+	*filesize = 0;
+	*out = NULL;
+
+	if (f == NULL)
+		return false;
+
+	uint32_t oldPos = ftell(f);
+
+	rewind(f);
+
+	if (!ReadHeader(f, &header))
+	{
+		fseek(f, oldPos, SEEK_SET);
+		return false;
+	}
+
+	if (!ValidateHeader(&header))
+	{
+		fseek(f, oldPos, SEEK_SET);
+		return false;
+	}
+
+	fseek(f, 0, SEEK_END);
+	uint32_t inputfilesize = ftell(f) - sizeof (XPKFILEHEADER);
+	fseek(f, sizeof (XPKFILEHEADER), SEEK_SET);
+
+	uint8_t *packedData = (uint8_t *)malloc(inputfilesize);
+	if (packedData == NULL)
+	{
+		fseek(f, oldPos, SEEK_SET);
+		return false;
+	}
+
+	if (fread(packedData, 1, inputfilesize, f) != inputfilesize)
+	{
+		free(packedData);
+		fseek(f, oldPos, SEEK_SET);
+		return false;
+	}
+
+	uint8_t *unpackedData = NULL;
+	bool result = XPK_DoUnpack(packedData, header.SrcLen - (sizeof (XPKFILEHEADER) - 8), header.DstLen, &unpackedData);
+	free(packedData);
+
+	if (result == true)
+	{
+		*filesize = header.DstLen;
+		*out = unpackedData;
+		return true;
+	}
+	else
+	{
+		return false;
+	}
+}
\ No newline at end of file
--- /dev/null
+++ b/src/pt2_xpk.h
@@ -1,0 +1,7 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+bool DetectXPK(FILE *f);
+bool UnpackXPK(FILE *f, uint32_t *filesize, uint8_t **out);
--- a/vs2019_project/pt2-clone/pt2-clone.vcxproj
+++ b/vs2019_project/pt2-clone/pt2-clone.vcxproj
@@ -270,6 +270,7 @@
     <ClInclude Include="..\..\src\pt2_textout.h" />
     <ClInclude Include="..\..\src\pt2_unicode.h" />
     <ClInclude Include="..\..\src\pt2_visuals.h" />
+    <ClInclude Include="..\..\src\pt2_xpk.h" />
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="..\..\src\gfx\pt2_gfx_aboutscreen.c" />
@@ -324,6 +325,7 @@
     <ClCompile Include="..\..\src\pt2_textout.c" />
     <ClCompile Include="..\..\src\pt2_unicode.c" />
     <ClCompile Include="..\..\src\pt2_visuals.c" />
+    <ClCompile Include="..\..\src\pt2_xpk.c" />
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="..\..\src\pt2-clone.rc">
--- a/vs2019_project/pt2-clone/pt2-clone.vcxproj.filters
+++ b/vs2019_project/pt2-clone/pt2-clone.vcxproj.filters
@@ -108,6 +108,9 @@
     <ClInclude Include="..\..\src\pt2_hpc.h">
       <Filter>headers</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\pt2_xpk.h">
+      <Filter>headers</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="..\..\src\pt2_audio.c" />
@@ -198,6 +201,7 @@
     <ClCompile Include="..\..\src\pt2_downsample2x.c" />
     <ClCompile Include="..\..\src\pt2_math.c" />
     <ClCompile Include="..\..\src\pt2_hpc.c" />
+    <ClCompile Include="..\..\src\pt2_xpk.c" />
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="..\..\src\pt2-clone.rc" />