<repo_name>uesp/uesp-shadowkey<file_sep>/ParseModels/ParseModels/ParseModels.cpp
// ParseModels.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <stdio.h>
#include <stdarg.h>
#include <cstdint>
#include <string>
#include <unordered_map>
#include <vector>
#include <algorithm> 
#include <functional> 
#include <cctype>
#include <locale>
#include "d:\\src\uesp\\EsoApps\\common\\devil\\include\\IL\\il.h"
#include "d:\\src\uesp\\EsoApps\\common\\devil\\include\\IL\\ilu.h"
#include "d:\\src\uesp\\EsoApps\\common\\devil\\include\\IL\\ilut.h"
#include "zlib.h"


typedef uint32_t dword;
typedef uint16_t word;


#define MODELSINDEX "c:\\downloads\\Shadowkey\\Shadowkey Release\\system\\apps\\6r51\\models.idx"
#define MODELSHUGE  "c:\\downloads\\Shadowkey\\Shadowkey Release\\system\\apps\\6r51\\models.huge"
#define MODELSTXT   "c:\\downloads\\Shadowkey\\Shadowkey Release\\system\\apps\\6r51\\models.txt"
#define MODELOUTPUTPATH "c:\\downloads\\Shadowkey\\models\\"
#define TEXTUREOUTPUTPATH "c:\\downloads\\Shadowkey\\ZippedTextures\\"
#define BITMAPOUTPUTPATH "c:\\downloads\\Shadowkey\\ZippedBmps\\"
#define ZIPPEDOUTPUTPATH "c:\\downloads\\Shadowkey\\Zipped\\"
#define MODELINPUTPATH    "c:\\downloads\\Shadowkey\\Shadowkey Release\\system\\apps\\6r51\\"
#define MODELZTXFILESPEC  "c:\\downloads\\Shadowkey\\Shadowkey Release\\system\\apps\\6r51\\*.ztx"
#define MODELZMPFILESPEC  "c:\\downloads\\Shadowkey\\Shadowkey Release\\system\\apps\\6r51\\*.zmp"
#define MODELZFILESPEC    "c:\\downloads\\Shadowkey\\Shadowkey Release\\system\\apps\\6r51\\*.z??"


const size_t MODEL_REC1_SIZE = 6;
const size_t MODEL_REC2_SIZE = 4;
const size_t MODEL_REC3_SIZE = 12;
const size_t MODEL_REC4_SIZE = 4;
const size_t MODEL_REC5_SIZE = 8;


struct modelindex_t 
{
	dword size;
	dword offset;
};


struct color_t {
	byte r;
	byte g;
	byte b;
};


struct coor_t {
	word x;
	word y;
	word z;
};


struct uvcoor_t {
	word u;
	word v;
};


struct face_t {
	word v[6];
};


struct modeldata_t
{
	dword index;
	dword fileOffset;

	dword size1;
	dword size2;
	dword size3;
	std::string name;

	dword dataSize;
	byte* pData;
	
	word textureCount;
	word textureWidth;
	word textureHeight;

	std::vector<coor_t> vertexes;
	std::vector<uvcoor_t> uvcoors;
	std::vector<face_t> faces;
	std::vector<byte* > textures;

};


std::vector<modelindex_t> g_ModelIndex;
std::vector<modeldata_t> g_Models;


bool ReportError(const char* pMsg, ...)
{
	va_list Args;

	va_start(Args, pMsg);
	vprintf(pMsg, Args);
	printf("\n");
	va_end(Args);

	return false;
}


// trim from start
static inline std::string &ltrim(std::string &s) {
	s.erase(s.begin(), std::find_if(s.begin(), s.end(),
		std::not1(std::ptr_fun<int, int>(std::isspace))));
	return s;
}


// trim from end
static inline std::string &rtrim(std::string &s) {
	s.erase(std::find_if(s.rbegin(), s.rend(),
		std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end());
	return s;
}


// trim from both ends
static inline std::string &trim(std::string &s) {
	return ltrim(rtrim(s));
}


word WordSwap(const word s)
{
	unsigned char b1, b2;

	b1 = s & 255;
	b2 = (s >> 8) & 255;

	return (b1 << 8) + b2;
}


dword DwordSwap(const dword i)
{
	unsigned char b1, b2, b3, b4;

	b1 = i & 255;
	b2 = (i >> 8) & 255;
	b3 = (i >> 16) & 255;
	b4 = (i >> 24) & 255;

	return ((dword)b1 << 24) + ((dword)b2 << 16) + ((dword)b3 << 8) + b4;
}


bool ReadDword(FILE* pFile, dword& Output, const bool LittleEndian = true)
{
	if (pFile == nullptr) return ReportError("Error: No file defined to read from!");
	size_t ReadBytes = fread(&Output, 1, sizeof(dword), pFile);
	if (ReadBytes != sizeof(dword)) return ReportError("Error: Only read %u of %u bytes from position 0x%X!", ReadBytes, sizeof(dword), ftell(pFile));
	if (!LittleEndian) Output = DwordSwap(Output);
	return true;
}



bool ReadWord(FILE* pFile, word&  Output, const bool LittleEndian = true)
{
	if (pFile == nullptr) return ReportError("Error: No file defined to read from!");
	size_t ReadBytes = fread(&Output, 1, sizeof(word), pFile);
	if (ReadBytes != sizeof(word)) return ReportError("Error: Only read %u of %u bytes from position 0x%X!", ReadBytes, sizeof(word), ftell(pFile));
	if (!LittleEndian) Output = WordSwap(Output);
	return true;
}


bool ParseDword(const byte* pData, dword& Output, const bool LittleEndian = true)
{
	memcpy((void *)&Output, pData, sizeof(dword));
	
	if (!LittleEndian) Output = DwordSwap(Output);
	return true;
}


bool ParseWord(const byte* pData, word&  Output, const bool LittleEndian = true)
{
	memcpy((void *)&Output, pData, sizeof(word));
	
	if (!LittleEndian) Output = WordSwap(Output);
	return true;
}


bool HasExtension(const char* pFilename, const char* pExtension)
{
	size_t extSize = strlen(pExtension);
	size_t filenameSize = strlen(pFilename);

	if (filenameSize < extSize) return false;

	return _strnicmp(pFilename + filenameSize - extSize, pExtension, extSize) == 0;
}


bool InflateZlibBlock(byte* pOutputData, dword &OutputSize, const size_t MaxOutputSize, const byte* pInputData, const size_t InputSize, const bool Quiet = false)
{
	z_stream Stream;
	int Result;

	Stream.zalloc = Z_NULL;
	Stream.zfree = Z_NULL;
	Stream.opaque = Z_NULL;
	Stream.avail_in = 0;
	Stream.next_in = Z_NULL;

	Result = inflateInit(&Stream);
	if (Result != Z_OK) return Quiet ? false : ReportError("Error: Failed to initialize the zlib stream!");

	Stream.avail_in = InputSize;
	Stream.avail_out = MaxOutputSize;
	Stream.next_out = pOutputData;
	Stream.next_in = (byte *)pInputData;

	/* Decompress until deflate stream ends or end of block data */
	do {
		Result = inflate(&Stream, Z_NO_FLUSH);

		switch (Result) {
		case Z_BUF_ERROR:
			if (Stream.avail_in == 0) Result = Z_STREAM_END;
			break;
		case Z_NEED_DICT:
			Result = Z_DATA_ERROR;     /* and fall through */
		case Z_DATA_ERROR:
		case Z_MEM_ERROR:
		case Z_STREAM_ERROR:
			//case Z_BUF_ERROR:
			OutputSize = Stream.total_out;
			inflateEnd(&Stream);
			return Quiet ? false : ReportError("Error: Failed to uncompress data stream!");
		};

	} while (Result != Z_STREAM_END);

	OutputSize = Stream.total_out;
	inflateEnd(&Stream);
	return true;
}


bool LoadModelIndex()
{
	FILE* pFile = fopen(MODELSINDEX, "rb");
	dword numModels = 0;

	if (pFile == nullptr) return ReportError("Error: Failed to open models index file '%s'!", MODELSINDEX);
	
	if (!ReadDword(pFile, numModels)) return ReportError("Error: Failed to read number of models from index file!");

	g_ModelIndex.clear();
	g_ModelIndex.reserve(numModels);

	for (dword i = 0; i < numModels; ++i) 
	{
		modelindex_t value;

		if (!ReadDword(pFile, value.offset)) return ReportError("Error: Failed to read number model offset from index file at offset 0x%08X!", ftell(pFile));
		if (!ReadDword(pFile, value.size)) return ReportError("Error: Failed to read number model size from index file at offset 0x%08X!", ftell(pFile));

		g_ModelIndex.push_back(value);
	}

	printf("Found %d models in index file.\n", g_ModelIndex.size());
	
	fclose(pFile);
	return true;
}


bool LoadModelData(FILE* pFile, modelindex_t Index, const dword modelIndex)
{
	modeldata_t Model;

	Model.dataSize = Index.size;
	Model.fileOffset = Index.offset;
	Model.pData = new byte[Model.dataSize + 256];
	Model.index = modelIndex;

	if (Model.dataSize == 0) 
	{
		g_Models.push_back(Model);
		return true;
	}

	int seekErr = fseek(pFile, Index.offset, SEEK_SET);
	if (seekErr != 0) return ReportError("%d) Error: Failed to seek to model data at offset 0x%08X!", modelIndex, Index.offset);

	size_t bytesRead = fread(Model.pData, 1, Model.dataSize, pFile);
	if (bytesRead != Model.dataSize) return ReportError("%d) Error: Only read %d of %d bytes from model at offset 0x%08X!", modelIndex, bytesRead, Model.dataSize, Index.offset);

	g_Models.push_back(Model);

	return true;
}


bool SaveRawModel(modeldata_t& Model)
{
	char buffer[256];
	snprintf(buffer, 255, "%s%d.dat", MODELOUTPUTPATH, Model.index);

	FILE* pFile = fopen(buffer, "wb");
	if (pFile == nullptr) return ReportError("Error: Failed to open file '%s' for output!", buffer);

	size_t bytesWritten = fwrite(Model.pData, 1, Model.dataSize, pFile);
	fclose(pFile);

	if (bytesWritten != Model.dataSize) return ReportError("Error: Only wrote %d of %d bytes for model %d!", bytesWritten, Model.dataSize, Model.index);

	snprintf(buffer, 255, "%s%s", MODELOUTPUTPATH, Model.name.c_str());

	pFile = fopen(buffer, "wb");
	if (pFile == nullptr) return ReportError("Error: Failed to open file '%s' for output!", buffer);

	bytesWritten = fwrite(Model.pData, 1, Model.dataSize, pFile);
	fclose(pFile);

	if (bytesWritten != Model.dataSize) return ReportError("Error: Only wrote %d of %d bytes for model %d!", bytesWritten, Model.dataSize, Model.index);
	return true;
}


bool SaveRawModels()
{
	int successCount = 0;

	for (auto &model : g_Models) 
	{
		if (SaveRawModel(model)) ++successCount;
	}

	printf("Successfully saved %d of %d model data.\n", successCount, g_Models.size());

	return true;
}


bool LoadModelData()
{
	int successCount = 0;

	FILE* pFile = fopen(MODELSHUGE, "rb");
	if (pFile == nullptr) return ReportError("Error: Failed to open models data file '%s'!", MODELSHUGE);

	g_Models.clear();
	g_Models.reserve(g_ModelIndex.size());

	for (size_t i = 0; i < g_ModelIndex.size(); ++i)
	{
		if (LoadModelData(pFile, g_ModelIndex[i], i)) ++successCount;
	}

	fclose(pFile);

	printf("Successfully loaded %d of %d models!\n", successCount, g_ModelIndex.size());
	return true;
}


bool LoadModelText()
{
	char inputLine[256];
	int errorCount = 0;
	int lineCount = 0;

	FILE* pFile = fopen(MODELSTXT, "rb");
	if (pFile == nullptr) return ReportError("Error: Failed to open models text file '%s'!", MODELSTXT);

	while (!feof(pFile))
	{
		char* pError = fgets(inputLine, 255, pFile);
		++lineCount;

		if (pError == nullptr)
		{
			if (feof(pFile)) break;
			ReportError("Error: Failed reading line %d in models txt file!", lineCount);
			++errorCount;
			break;
		}

		char* pToken = strtok(inputLine, " ");
		std::vector<char*> Tokens;

		while (pToken) 
		{
			Tokens.push_back(pToken);
			pToken = strtok(nullptr, " ");
		}

		if (Tokens.size() < 5) 
		{
			ReportError("Error: Only found %d tokens on line %d in models.txt file!", Tokens.size(), lineCount);
			++errorCount;
			continue;
		}
		else if (Tokens.size() > 5)
		{
			ReportError("Warning: Found %d tokens on line %d in models.txt file!", Tokens.size(), lineCount);
			++errorCount;
		}

		dword index = strtoul(Tokens[0], nullptr, 10);
		dword count = strtoul(Tokens[1], nullptr, 10);
		dword size1 = strtoul(Tokens[2], nullptr, 10);
		dword size2 = strtoul(Tokens[3], nullptr, 10);
		char* pFilename = Tokens[4];

		if (index < g_Models.size())
		{
			modeldata_t& model = g_Models[index];
			model.size1 = count;
			model.size2 = size1;
			model.size3 = size2;
			model.name = trim(std::string(pFilename));
		}
		else
		{
			ReportError("Error: Found invalid model index %u on line %d!", index, lineCount);
			++errorCount;
		}
	}

	fclose(pFile);

	printf("Successfully loaded %d of %d lines from model txt file!\n", lineCount - errorCount, lineCount);

	return true;
}


bool ParseModelData(modeldata_t& model)
{
	word header[7];
	dword sectionSizes[7];
	byte* pData = model.pData;
	size_t Offset = 0;

	if (model.dataSize == 0) return true;
	if (model.dataSize < 7 * 2) return ReportError("Error: Model %d data too small to parse 14 bytes of header data!", model.index);

	ParseWord(pData +  0, header[0]);
	ParseWord(pData +  2, header[1]);
	ParseWord(pData +  4, header[2]);
	ParseWord(pData +  6, header[3]);
	ParseWord(pData +  8, header[4]);
	ParseWord(pData + 10, header[5]);
	ParseWord(pData + 12, header[6]);
	Offset += 14;

	sectionSizes[0] = 0;
	sectionSizes[1] = header[1];
	sectionSizes[2] = header[2] * 6;
	sectionSizes[3] = header[3] * 4;
	sectionSizes[4] = header[4] * 12;
	sectionSizes[5] = 2;
	sectionSizes[6] = header[6];

	dword section2Size = (dword) sectionSizes[1] * (dword) sectionSizes[2];
	if (Offset + section2Size > model.dataSize) return ReportError("Error: Model %d data overrun in vertex data (Offset 0x%08X, Size 0x%08X!", model.index, Offset, section2Size);

	word numVertexes = section2Size / 6;
	model.vertexes.reserve(numVertexes);

	for (size_t i = 0; i < numVertexes; ++i)
	{
		coor_t coor;

		ParseWord(pData + Offset + i * 6 + 0, coor.x);
		ParseWord(pData + Offset + i * 6 + 2, coor.y);
		ParseWord(pData + Offset + i * 6 + 4, coor.z);

		model.vertexes.push_back(coor);
	}

	Offset += section2Size;

	if (Offset + sectionSizes[3] > model.dataSize) return ReportError("Error: Model %d data overrun in UV data (Offset 0x%08X, Size 0x%08X!", model.index, Offset, sectionSizes[3]);

	word numUVCoors = sectionSizes[3] / 4;
	model.uvcoors.reserve(numVertexes);

	for (size_t i = 0; i < numUVCoors; ++i)
	{
		uvcoor_t coor;

		ParseWord(pData + Offset + i * 4 + 0, coor.u);
		ParseWord(pData + Offset + i * 4 + 2, coor.v);

		model.uvcoors.push_back(coor);
	}

	Offset += sectionSizes[3];

	if (Offset + sectionSizes[4] > model.dataSize) return ReportError("Error: Model %d data overrun in face data (Offset 0x%08X, Size 0x%08X!", model.index, Offset, sectionSizes[4]);

	word numFaces = sectionSizes[4] / 12;
	model.faces.reserve(numFaces);

	for (size_t i = 0; i < numFaces; ++i)
	{
		face_t face;

		ParseWord(pData + Offset + i * 12 + 0, face.v[0]);
		ParseWord(pData + Offset + i * 12 + 0, face.v[1]);
		ParseWord(pData + Offset + i * 12 + 0, face.v[2]);
		ParseWord(pData + Offset + i * 12 + 0, face.v[3]);
		ParseWord(pData + Offset + i * 12 + 0, face.v[4]);
		ParseWord(pData + Offset + i * 12 + 0, face.v[5]);

		model.faces.push_back(face);
	}

	Offset += sectionSizes[4];
	
	if (Offset + 6 > model.dataSize) return ReportError("Error: Model %d data overrun in texture header (Offset 0x%08X, Size 0x%08X!", model.index, Offset, 6);
	word section5Header[3];
	ParseWord(pData + Offset + 0, section5Header[0]);
	ParseWord(pData + Offset + 2, section5Header[1]);
	ParseWord(pData + Offset + 4, section5Header[2]);
	Offset += 6;

	model.textureCount  = section5Header[0];
	model.textureWidth  = section5Header[1];
	model.textureHeight = section5Header[2];

	sectionSizes[5] *= (dword) section5Header[0] * (dword) section5Header[1] * (dword) section5Header[2];
	if (Offset + sectionSizes[5] > model.dataSize) return ReportError("Error: Model %d data overrun in texture data (Offset 0x%08X, Size 0x%08X!", model.index, Offset, sectionSizes[5]);

	for (size_t i = 0; i < model.textureCount; ++i)
	{
		byte* pTexture = pData + Offset + 2 * model.textureWidth * model.textureHeight * i;
		model.textures.push_back(pTexture);
	}

	Offset += sectionSizes[5];

	if (sectionSizes[6] != 1) ReportError("Warning: Model %d data section 6 size is %d.", model.index, sectionSizes[6]);

	if (Offset + 2 > model.dataSize) return ReportError("Error: Model %d data overrun in footer header (Offset 0x%08X, Size 0x%08X!", model.index, Offset, 2);
	word section6count;
	ParseWord(pData + Offset, section6count);
	Offset += 2;

	dword section6Size = (dword) section6count * 6;
	if (Offset + section6Size > model.dataSize) return ReportError("Error: Model %d data overrun in footer data (Offset 0x%08X, Size 0x%08X!", model.index, Offset, section6Size);
	Offset += section6Size;

	if (Offset != model.dataSize) 
	{
		int leftOverBytes = model.dataSize - Offset;
		ReportError("Warning: Model %d has 0x%08X bytes left over in data buffer!", model.index, leftOverBytes);
	}
	
	return true;
}


bool ParseModelData()
{

	for (auto& model : g_Models)
	{
		ParseModelData(model);
	}

	return true;
}


bool CheckUVCoors() 
{
	int minUValue = 65536;
	int maxUValue = 0;
	int minVValue = 65536;
	int maxVValue = 0;
	int count = 0;

	std::unordered_map<word, uvcoor_t> uValues;
	std::unordered_map<word, uvcoor_t> vValues;

	for (auto& model : g_Models)
	{
		if (uValues.find(model.textureWidth)  == uValues.end()) uValues[model.textureWidth]  = { 65535, 0 };
		if (vValues.find(model.textureHeight) == vValues.end()) vValues[model.textureHeight] = { 65535, 0 };

		uvcoor_t& uVal = uValues[model.textureWidth];
		uvcoor_t& vVal = vValues[model.textureHeight];
			
		for (auto& uv : model.uvcoors)
		{
			++count;

			if (minUValue > uv.u) minUValue = uv.u;
			if (minVValue > uv.v) minVValue = uv.v;
			if (maxUValue < uv.u) maxUValue = uv.u;
			if (maxVValue < uv.v) maxVValue = uv.v;

			if (uVal.u > uv.u) uVal.u = uv.u;
			if (uVal.v < uv.u) uVal.v = uv.u;
			if (vVal.u > uv.v) vVal.u = uv.v;
			if (vVal.v < uv.v) vVal.v = uv.v;
		}
	}

	printf("Found %d UV coordinates.\n", count);
	printf("U Coordinates = %d to %d\n", minUValue, maxUValue);
	printf("V Coordinates = %d to %d\n", minVValue, maxVValue);

	printf("U Coordinates (%d):\n", uValues.size());

	for (auto& value : uValues)
	{
		word width = value.first;
		uvcoor_t uv = value.second;
		printf("\t%d = %d to %d\n", width, uv.u, uv.v);
	}
	
	printf("V Coordinates (%d):\n", vValues.size());

	for (auto& value : vValues)
	{
		word width = value.first;
		uvcoor_t uv = value.second;
		printf("\t%d = %d to %d\n", width, uv.u, uv.v);
	}

	return true;
}


void TranslateColorWord(byte& r, byte& g, byte& b, const byte* pTexture)
{
	r = ((pTexture[1] & 0x0f) << 4) + (pTexture[1] & 0x0f);
	g = ((pTexture[0] & 0xf0) >> 4) + (pTexture[0] & 0xf0);
	b = ((pTexture[0] & 0x0f) << 4) + (pTexture[0] & 0x0f);
}


dword TranslateColorIndex(byte& r, byte& g, byte& b, byte index, byte* pPalettte = nullptr)
{
	if (pPalettte == nullptr) return -1;

	r = pPalettte[index * 3 + 0];
	g = pPalettte[index * 3 + 1];
	b = pPalettte[index * 3 + 2];

	return (dword)r + ((dword)g << 8) + ((dword)b << 16);
}


bool ExportTexture(const byte* pTexture, const dword width, const dword height, const dword modelIndex, const dword textureIndex, const std::string name)
{
	if (height == 0 || width == 0) return true;

	char OutputFilename[256];
	//snprintf(OutputFilename, 250, "%s%d-%d.png", MODELOUTPUTPATH, modelIndex, textureIndex);
	snprintf(OutputFilename, 250, "%s%s-%d.png", MODELOUTPUTPATH, name.c_str(), textureIndex);

	byte* pOutputImage = new byte[height * width * 4 + 1024];
	byte r, g, b;
	size_t imageOffset = 0;

	for (size_t y = 0; y < height; ++y)
	{
		for (size_t x = 0; x < width; ++x)
		{
			byte alpha = 0xff;

			TranslateColorWord(r, g, b, pTexture);

			if (r == 0xff && g == 0 && b == 0xff) alpha = 0;

			imageOffset = (height - y - 1) * width * 4 + x * 4;
			//imageOffset = y * width * 4 + x * 4;

			pOutputImage[imageOffset + 0] = r;
			pOutputImage[imageOffset + 1] = g;
			pOutputImage[imageOffset + 2] = b;
			pOutputImage[imageOffset + 3] = alpha;

			pTexture += 2;
		}
	}

	ilTexImage(width, height, 1, 4, IL_RGBA, IL_UNSIGNED_BYTE, pOutputImage);
	ilSave(IL_PNG, OutputFilename);
	
	delete[] pOutputImage;
	return true;
}


bool ExportTextures()
{
	
	for (auto& model : g_Models)
	{
		dword textureIndex = 0;

		for (auto& texture : model.textures)
		{
			ExportTexture(texture, model.textureWidth, model.textureHeight, model.index, textureIndex, model.name);
			++textureIndex;
		}
	}

	return true;
}


bool ParseZtx(const byte* pData, const dword Size, const char* pFullFilename)
{
	if (Size <= 0) return true;

	dword numTextures = (dword) pData[0];
	size_t offset = 1;
	size_t width = 0x40*2;
	size_t height = 0x40/2;
	size_t imageSize = width * height;
	char Drive[10], Dir[256], Filename[256], Extension[16];
	char palFilename[256];

	_splitpath(pFullFilename, Drive, Dir, Filename, Extension);
	snprintf(palFilename, 255, "%s%s%s", MODELINPUTPATH, Filename, ".pal");

	FILE* pFile = fopen(palFilename, "rb");
	if (pFile == nullptr) return ReportError("Error: Failed to open palette file '%s' for reading!", palFilename);
	byte PalData[768];
	size_t bytesRead = fread(PalData, 1, 768, pFile);
	fclose(pFile);
	if (bytesRead != 768) return ReportError("Error: Only read %d of %d bytes from open palette file '%s' for reading!", bytesRead, 768, palFilename);

	byte r, g, b;
	byte index;
	byte* pOutputImage = new byte[height * width * 4 + 1024];
	
	for (dword i = 0; i < numTextures; ++i)
	{
		if (offset + imageSize > Size) return ReportError("Error: Buffer overflow in parsing ZTX texture #%d at offset 0x%X!", i, offset);

		for (size_t y = 0; y < height; ++y)
		{
			for (size_t x = 0; x < width; ++x)
			{
				if (offset > Size) return ReportError("Error: Buffer overflow in parsing ZTX texture #%d at offset 0x%X!", i, offset);

				index = pData[offset];
				++offset;

				TranslateColorIndex(r, g, b, index, PalData);

				byte alpha = 0xff;

				if (r == 0xff && g == 0 && b == 0xff) alpha = 0;

				size_t imageOffset = (height - y - 1) * width * 4 + x * 4;
				//imageOffset = y * width * 4 + x * 4;

				pOutputImage[imageOffset + 0] = r;
				pOutputImage[imageOffset + 1] = g;
				pOutputImage[imageOffset + 2] = b;
				pOutputImage[imageOffset + 3] = alpha;
			}
		}

		char OutputFilename[256];
		snprintf(OutputFilename, 255, "%s%s-%d.png", TEXTUREOUTPUTPATH, Filename, i);
		printf("%s\n", OutputFilename);
		ilTexImage(width, height, 1, 4, IL_RGBA, IL_UNSIGNED_BYTE, pOutputImage);
		ilSave(IL_PNG, OutputFilename);

		//offset += imageSize;
	}

	return true;
}


bool DecompressZFile(const char* pFilename, const char* pOutputPath)
{
	char inputFilename[256];
	char outputFilename[256];
	dword decompressedSize;

	snprintf(inputFilename,  255, "%s%s", MODELINPUTPATH, pFilename);
	snprintf(outputFilename, 255, "%s%s", pOutputPath, pFilename);

	FILE* pInputFile = fopen(inputFilename, "rb");
	if (pInputFile == nullptr) return ReportError("Error: Failed to open file '%s' for reading!", inputFilename);

	fseek(pInputFile, 0, SEEK_END);
	dword compressedSize = ftell(pInputFile) - 4;
	fseek(pInputFile, 0, SEEK_SET);
	
	if (!ReadDword(pInputFile, decompressedSize)) return ReportError("Error: Failed to read 4 bytes from file '%s'!", inputFilename);

	byte* pCompressedData = new byte[compressedSize + 1000];
	byte* pDecompressedData = new byte[decompressedSize + 1000];

	size_t bytesRead = fread(pCompressedData, 1, compressedSize, pInputFile);
	if (bytesRead != compressedSize) return ReportError("Error: Only read %d of %d bytes from file '%s'!", bytesRead, compressedSize, inputFilename);
	
	fclose(pInputFile);

	dword decompressSizeResult = 0;
	bool compressResult = InflateZlibBlock(pDecompressedData, decompressSizeResult, decompressedSize + 500, pCompressedData, compressedSize);
	if (!compressResult) return ReportError("Error: Failed to decompressed file '%s'!", inputFilename);

	FILE* pOutputFile = fopen(outputFilename, "wb");
	if (pOutputFile == nullptr) return ReportError("Error: Failed to open file '%s' for output!", outputFilename);

	fwrite(pDecompressedData, 1, decompressSizeResult, pOutputFile);
	fclose(pOutputFile);

	if (HasExtension(outputFilename, ".ztx"))
	{
		ParseZtx(pDecompressedData, decompressSizeResult, inputFilename);
	}

	delete[] pCompressedData;
	delete[] pDecompressedData;

	printf("Successfully decompressed file '%s'.\n", inputFilename);
	return true;
}


bool DecompressZtx()
{
	WIN32_FIND_DATA FindData;
	HANDLE hFind = FindFirstFile(MODELZTXFILESPEC, &FindData);
	
	if (hFind == INVALID_HANDLE_VALUE) return ReportError("Error: No files matching '%s' found!", MODELZTXFILESPEC);

	do {
		DecompressZFile(FindData.cFileName, TEXTUREOUTPUTPATH);
	} while (FindNextFile(hFind, &FindData));

	FindClose(hFind);
	return true;
}



bool DecompressZmp()
{
	WIN32_FIND_DATA FindData;
	HANDLE hFind = FindFirstFile(MODELZMPFILESPEC, &FindData);

	if (hFind == INVALID_HANDLE_VALUE) return ReportError("Error: No files matching '%s' found!", MODELZMPFILESPEC);

	do {
		DecompressZFile(FindData.cFileName, BITMAPOUTPUTPATH);
	} while (FindNextFile(hFind, &FindData));

	FindClose(hFind);
	return true;
}


bool DecompressAll()
{
	WIN32_FIND_DATA FindData;
	HANDLE hFind = FindFirstFile(MODELZFILESPEC, &FindData);

	if (hFind == INVALID_HANDLE_VALUE) return ReportError("Error: No files matching '%s' found!", MODELZFILESPEC);

	do {
		DecompressZFile(FindData.cFileName, ZIPPEDOUTPUTPATH);
	} while (FindNextFile(hFind, &FindData));

	FindClose(hFind);
	return true;
}


int main()
{
	ilInit();
	iluInit();
	ilEnable(IL_FILE_OVERWRITE);

	DecompressZtx();
	//DecompressZmp();
	//DecompressAll();
	return true;
	
	LoadModelIndex();
	LoadModelData();
	LoadModelText();

	//SaveRawModels();

	ParseModelData();
	CheckUVCoors();
	ExportTextures();

    return 0;
}

<file_sep>/ParseModels/ParseModels/ReadMe.txt
This program attempts to parse 3D model data from the Shadowkey MODELS.* files. It uses hard coded input and ouput
files so must be edited and recompiled in order to work on your computer.<file_sep>/ParseGlobalSpr/ParseGlobalSpr/ReadMe.txt
This program extracts image files from the Shadowkey GLOBAL.SPR file. It uses hard coded file input and
output locations so must be recompiled with Visual Studio C++ to work on your computer.<file_sep>/ParseGlobalSpr/ParseGlobalSpr/ParseGlobalSpr.cpp
// ParseGlobalSpr.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <stdio.h>
#include <stdarg.h>
#include <cstdint>
#include <string>
#include <unordered_map>
#include "d:\\src\uesp\\EsoApps\\common\\devil\\include\\IL\\il.h"
#include "d:\\src\uesp\\EsoApps\\common\\devil\\include\\IL\\ilu.h"
#include "d:\\src\uesp\\EsoApps\\common\\devil\\include\\IL\\ilut.h"

#define GLOBALSPR "c:\\Downloads\\Shadowkey\\global.spr"
#define OUTPUTPATH "c:\\Downloads\\Shadowkey\\images\\"

#define REFIMAGE1 "c:\\Downloads\\Shadowkey\\SK-narrative-COH1-2a.png"
#define REFIMAGE2 "c:\\Downloads\\Shadowkey\\images\\222a.png"
#define REFINDEX 222
#define OUTPUTPALETTE "c:\\Downloads\\Shadowkey\\global1.pal"
#define GLOBALPALETTE "c:\\Downloads\\Shadowkey\\global.pal"
#define TRANSPARENTINDEX 125

const int bytesPerPixel = 3; /// red, green, blue
const int fileHeaderSize = 14;
const int infoHeaderSize = 40;


typedef uint32_t dword;
typedef uint16_t word;

byte* pGlobalPal = nullptr;


bool ReportError(const char* pMsg, ...)
{
	va_list Args;

	va_start(Args, pMsg);
	vprintf(pMsg, Args);
	printf("\n");
	va_end(Args);

	return false;
}


word WordSwap(const word s)
{
	unsigned char b1, b2;

	b1 = s & 255;
	b2 = (s >> 8) & 255;

	return (b1 << 8) + b2;
}


dword DwordSwap(const dword i)
{
	unsigned char b1, b2, b3, b4;

	b1 = i & 255;
	b2 = (i >> 8) & 255;
	b3 = (i >> 16) & 255;
	b4 = (i >> 24) & 255;

	return ((dword)b1 << 24) + ((dword)b2 << 16) + ((dword)b3 << 8) + b4;
}


bool ReadDword(FILE* pFile, dword& Output, const bool LittleEndian)
{
	if (pFile == nullptr) return ReportError("Error: No file defined to read from!");
	size_t ReadBytes = fread(&Output, 1, sizeof(dword), pFile);
	if (ReadBytes != sizeof(dword)) return ReportError("Error: Only read %u of %u bytes from position 0x%X!", ReadBytes, sizeof(dword), ftell(pFile));
	if (!LittleEndian) Output = DwordSwap(Output);
	return true;
}



bool ReadWord(FILE* pFile, word&  Output, const bool LittleEndian)
{
	if (pFile == nullptr) return ReportError("Error: No file defined to read from!");
	size_t ReadBytes = fread(&Output, 1, sizeof(word), pFile);
	if (ReadBytes != sizeof(word)) return ReportError("Error: Only read %u of %u bytes from position 0x%X!", ReadBytes, sizeof(word), ftell(pFile));
	if (!LittleEndian) Output = WordSwap(Output);
	return true;
}


dword TranslateColorIndex(byte& r, byte& g, byte& b, byte index, byte* pPalettte = nullptr)
{
	if (pPalettte == nullptr) pPalettte = pGlobalPal;
	if (pPalettte == nullptr) return -1;

	r = pPalettte[index * 3 + 0];
	g = pPalettte[index * 3 + 1];
	b = pPalettte[index * 3 + 2];

	return (dword)r + ((dword)g << 8) + ((dword)b << 16);
}


unsigned char* createBitmapFileHeader(int height, int width, int paddingSize) {
	int fileSize = fileHeaderSize + infoHeaderSize + (bytesPerPixel*width + paddingSize) * height;

	static unsigned char fileHeader[] = {
		0,0, /// signature
		0,0,0,0, /// image file size in bytes
		0,0,0,0, /// reserved
		0,0,0,0, /// start of pixel array
	};

	fileHeader[0] = (unsigned char)('B');
	fileHeader[1] = (unsigned char)('M');
	fileHeader[2] = (unsigned char)(fileSize);
	fileHeader[3] = (unsigned char)(fileSize >> 8);
	fileHeader[4] = (unsigned char)(fileSize >> 16);
	fileHeader[5] = (unsigned char)(fileSize >> 24);
	fileHeader[10] = (unsigned char)(fileHeaderSize + infoHeaderSize);

	return fileHeader;
}


unsigned char* createBitmapInfoHeader(int height, int width) {
	static unsigned char infoHeader[] = {
		0,0,0,0, /// header size
		0,0,0,0, /// image width
		0,0,0,0, /// image height
		0,0, /// number of color planes
		0,0, /// bits per pixel
		0,0,0,0, /// compression
		0,0,0,0, /// image size
		0,0,0,0, /// horizontal resolution
		0,0,0,0, /// vertical resolution
		0,0,0,0, /// colors in color table
		0,0,0,0, /// important color count
	};

	infoHeader[0] = (unsigned char)(infoHeaderSize);
	infoHeader[4] = (unsigned char)(width);
	infoHeader[5] = (unsigned char)(width >> 8);
	infoHeader[6] = (unsigned char)(width >> 16);
	infoHeader[7] = (unsigned char)(width >> 24);
	infoHeader[8] = (unsigned char)(height);
	infoHeader[9] = (unsigned char)(height >> 8);
	infoHeader[10] = (unsigned char)(height >> 16);
	infoHeader[11] = (unsigned char)(height >> 24);
	infoHeader[12] = (unsigned char)(1);
	infoHeader[14] = (unsigned char)(bytesPerPixel * 8);

	return infoHeader;
}


void generateBitmapImage(unsigned char *image, int width, int height, char* imageFileName) {

	unsigned char padding[3] = { 0, 0, 0 };
	int paddingSize = (4 - (width*bytesPerPixel) % 4) % 4;

	unsigned char* fileHeader = createBitmapFileHeader(height, width, paddingSize);
	unsigned char* infoHeader = createBitmapInfoHeader(height, width);

	FILE* imageFile = fopen(imageFileName, "wb");

	fwrite(fileHeader, 1, fileHeaderSize, imageFile);
	fwrite(infoHeader, 1, infoHeaderSize, imageFile);

	int i;
	for (i = 0; i<height; i++) {
		fwrite(image + (i*width*bytesPerPixel), bytesPerPixel, width, imageFile);
		fwrite(padding, 1, paddingSize, imageFile);
	}

	fclose(imageFile);
}


bool ConvertRawPalette(byte* pRawPal, byte* pOutputPal) 
{

	for (size_t i = 0; i < 256; ++i) 
	{
		byte b = ((pRawPal[i * 2] & 0x0f) << 4) + (pRawPal[i * 2] & 0x0f);
		byte g = ((pRawPal[i * 2] & 0xf0) >> 4) + (pRawPal[i * 2] & 0xf0);
		byte r = ((pRawPal[i*2 + 1] & 0x0f) << 4) + (pRawPal[i * 2 + 1] & 0x0f);

		pOutputPal[i * 3 + 0] = r;
		pOutputPal[i * 3 + 1] = g;
		pOutputPal[i * 3 + 2] = b;
	}

	return true;
}


bool ReadImage(FILE* pFile)
{
	static int imageIndex = 0;
	FILE* pOutputFile;
	char OutputFilename[256];
	unsigned char ReadBytes[4096];
	size_t OutputImageOffset = 0;
	size_t OutputAlphaImageOffset = 0;
	int imageReadSize = 0;
	int imageY = 0;
	unsigned char* pOutputImage = nullptr;
	unsigned char* pOutputAlphaImage = nullptr;
	word imageWidth = 0;
	word imageHeight = 0;
	byte RawPalette[0x200 + 100];
	byte Palette[768 + 100];

	++imageIndex;

	long curPos = ftell(pFile);
	printf("%d) Starting image at 0x%08X\n", imageIndex, curPos);

	snprintf(OutputFilename, 250, "%s%d.raw", OUTPUTPATH, imageIndex);
	pOutputFile = fopen(OutputFilename, "wb");
	if (pOutputFile == nullptr) return ReportError("Error: Failed to open image file for output!");
	
	if (!ReadWord(pFile, imageWidth, true)) return false;
	if (!ReadWord(pFile, imageHeight, true)) return false;
	imageReadSize += 4;

	printf("\tFound image %d x %d\n", (int) imageWidth, (int) imageHeight);

	int palBytesRead = fread(RawPalette, 1, 0x200, pFile);
	if (palBytesRead != 0x200)  return ReportError("\tError: Failed to read image palette data at 0x%08X!", ftell(pFile));

	//int seekResult = fseek(pFile, 0x200, SEEK_CUR);
	//if (seekResult != 0) return ReportError("\tError: Failed to seek past image header at 0x%08X!", ftell(pFile));

	ConvertRawPalette(RawPalette, Palette);
	imageReadSize += 0x200;
	
	pOutputImage = new unsigned char[imageHeight * imageWidth * bytesPerPixel + 1024];
	pOutputAlphaImage = new unsigned char[imageHeight * imageWidth * 4 + 1024];

	while (!feof(pFile) && imageY < imageHeight)
	{
		++imageY;

		//printf("\t%d) Reading line at 0x%08X\n", imageY, ftell(pFile));

		word zeroSize = 0;
		word lineSize = 0;

		if (!ReadWord(pFile, zeroSize, true)) return false;
		if (!ReadWord(pFile, lineSize, true)) return false;

		imageReadSize += 4;

		int readSize = lineSize - zeroSize;

		//printf("\t\tZeros = 0x%X, LineSize = 0x%X, ReadSize = 0x%X\n", zeroSize, lineSize, readSize);
				
		int bytesRead = fread(ReadBytes, 1, readSize, pFile);
		imageReadSize += bytesRead;
		if (bytesRead != readSize) return ReportError("\tError: Only read %d of %d bytes at at 0x%08X!", bytesRead, lineSize, ftell(pFile));

			/* Flip image vertically */
		OutputImageOffset = (imageHeight - imageY) * imageWidth * bytesPerPixel;
		OutputAlphaImageOffset = (imageHeight - imageY) * imageWidth * 4;

		for (int i = 0; i < imageWidth; ++i) {
			pOutputImage[OutputImageOffset + i*bytesPerPixel] = 0xff;
			pOutputImage[OutputImageOffset + i*bytesPerPixel + 1] = 0;
			pOutputImage[OutputImageOffset + i*bytesPerPixel + 2] = 0xff;

			pOutputAlphaImage[OutputAlphaImageOffset + i*4] = 0;
			pOutputAlphaImage[OutputAlphaImageOffset + i*4 + 1] = 0;
			pOutputAlphaImage[OutputAlphaImageOffset + i*4 + 2] = 0;
			pOutputAlphaImage[OutputAlphaImageOffset + i*4 + 3] = 0;
		}

		size_t startOffset = 0;
		size_t startAlphaOffset = 0;

		if (zeroSize > 0) 
		{
			startOffset = zeroSize * bytesPerPixel;
			startAlphaOffset = zeroSize * 4;
		}
		
		for (int i = 0; i < readSize; ++i) {
			byte r, g, b;
			byte alpha = 0xff;
			

			if (TranslateColorIndex(r, g, b, ReadBytes[i], Palette) >= 0)
			{
				if (r == 0xff && g == 0 && b == 0xff) alpha = 0;

				pOutputImage[OutputImageOffset + startOffset + i*bytesPerPixel + 0] = b;
				pOutputImage[OutputImageOffset + startOffset + i*bytesPerPixel + 1] = g;
				pOutputImage[OutputImageOffset + startOffset + i*bytesPerPixel + 2] = r;

				
				pOutputAlphaImage[OutputAlphaImageOffset + startAlphaOffset + i*4] = r;
				pOutputAlphaImage[OutputAlphaImageOffset + startAlphaOffset + i*4 + 1] = g;
				pOutputAlphaImage[OutputAlphaImageOffset + startAlphaOffset + i*4 + 2] = b;
				pOutputAlphaImage[OutputAlphaImageOffset + startAlphaOffset + i*4 + 3] = alpha;
			}
			else
			{
				if (ReadBytes[i] == TRANSPARENTINDEX) alpha = 0;

				pOutputImage[OutputImageOffset + startOffset + i*bytesPerPixel] = ReadBytes[i];
				pOutputImage[OutputImageOffset + startOffset + i*bytesPerPixel + 1] = ReadBytes[i];
				pOutputImage[OutputImageOffset + startOffset + i*bytesPerPixel + 2] = ReadBytes[i];

				pOutputAlphaImage[OutputAlphaImageOffset + startAlphaOffset + i*4] = ReadBytes[i];
				pOutputAlphaImage[OutputAlphaImageOffset + startAlphaOffset + i*4 + 1] = ReadBytes[i];
				pOutputAlphaImage[OutputAlphaImageOffset + startAlphaOffset + i*4 + 2] = ReadBytes[i];
				pOutputAlphaImage[OutputAlphaImageOffset + startAlphaOffset + i*4 + 3] = alpha;
			}
		}

		//OutputImageOffset += imageWidth * bytesPerPixel;

		fwrite(ReadBytes, 1, readSize, pOutputFile);

		//seekResult = fseek(pFile, readSize, SEEK_CUR);
		//if (seekResult != 0) return ReportError("\tError: Failed to seek past image line, 0x%X bytes at 0x%08X!", lineSize, ftell(pFile));
	}	
	
	fclose(pOutputFile);
	
	ilTexImage(imageWidth, imageHeight, 1, 4, IL_RGBA, IL_UNSIGNED_BYTE, pOutputAlphaImage);
	snprintf(OutputFilename, 250, "%s%d.png", OUTPUTPATH, imageIndex);
	ilSave(IL_PNG, OutputFilename);
	
	snprintf(OutputFilename, 250, "%s%d.bmp", OUTPUTPATH, imageIndex);
	generateBitmapImage(pOutputImage, imageWidth, imageHeight, OutputFilename);
	delete[] pOutputImage;
	delete[] pOutputAlphaImage;

	curPos = ftell(pFile);
	printf("\tFinished image at 0x%08X (read %d bytes)\n", curPos, imageReadSize);

	return true;
}


bool TestParse()
{
	FILE* pFile;

	pFile = fopen(GLOBALSPR, "rb");
	if (pFile == nullptr) return ReportError("Failed to open file!");

	int seekResult = fseek(pFile, 0, SEEK_END);
	if (seekResult != 0) return ReportError("Failed to seek to EOF!");
	long fileSize = ftell(pFile);
	fseek(pFile, 0, SEEK_SET);

	seekResult = fseek(pFile, 0x600, SEEK_SET);
	if (seekResult != 0) return ReportError("Failed to seek past file header!");

	while (ReadImage(pFile))
	{
	}

	long curPos = ftell(pFile);
	long leftOverBytes = fileSize - curPos;

	printf("Stopped reading images at file position 0x%08X (0x%X bytes left over).\n", curPos, leftOverBytes);

	fclose(pFile);

	return true;
}


bool CreatePalette()
{
	ILuint ImageName1;
	ILuint ImageName2;

	ilGenImages(1, &ImageName1);
	ilGenImages(1, &ImageName2);

	ilBindImage(ImageName1);
	auto loadResult1 = ilLoadImage(REFIMAGE1);

	ILuint width1, height1;
	width1 = ilGetInteger(IL_IMAGE_WIDTH);
	height1 = ilGetInteger(IL_IMAGE_HEIGHT);
	ILubyte * pImageData1 = ilGetData();

	ilBindImage(ImageName2);
	auto loadResult2 = ilLoadImage(REFIMAGE2);

	ILuint width2, height2;
	width2 = ilGetInteger(IL_IMAGE_WIDTH);
	height2 = ilGetInteger(IL_IMAGE_HEIGHT);
	ILubyte * pImageData2 = ilGetData();

	if (!loadResult1) return ReportError("Error: Failed to load image '%s'!", REFIMAGE1);
	if (!loadResult2) return ReportError("Error: Failed to load image '%s'!", REFIMAGE2);

	ilBindImage(ImageName1);
	//iluScale(width2, height2, 1);
	
	width1 = ilGetInteger(IL_IMAGE_WIDTH);
	height1 = ilGetInteger(IL_IMAGE_HEIGHT);
	//pImageData1 = ilGetData();

	ReportError("\tImage1 = %d x %d", width1, height1);
	ReportError("\tImage2 = %d x %d", width2, height2);

	size_t imageSize = width1 * height2;
	ILubyte* pData1 = pImageData1;
	ILubyte* pData2 = pImageData2;

	std::unordered_map<byte, dword> ColorMap;

	ReportError("Start Palette Creation..\n");
	//imageSize = 1000;

	dword x = 0;
	dword y = 0;
	dword errorCount = 0;

	for (size_t i = 0; i < imageSize; ++i) {
		byte grayScale = *pData2;
		byte r = pData1[0];
		byte g = pData1[1];
		byte b = pData1[2];
		dword color = (dword)r + ((dword)g << 8) + ((dword)b << 16);

		//ReportError("\t(%d, %d) = #%d, (%d, %d, %d)", x, y, grayScale, r, g, b);

		if (ColorMap.find(grayScale) != ColorMap.end()) {
			dword origColor = ColorMap[grayScale];

			if (origColor != color) {
				ReportError("\tColor #%d Mismatch: 0x%06X != 0x%06X", grayScale, origColor, color);
				++errorCount;
			}
			
		}
		else {
			ColorMap[grayScale] = color;
			ReportError("\tColor #%d = 0x%06X", grayScale, color);
		}

		pData1 += 3;
		pData2 += 3;

		x++;

		if (x >= width1) {
			x = 0;
			y++;
		}
	}

	size_t count = ColorMap.size();
	ReportError("Found %d color indexes with %d mismatches!", count, errorCount);

	FILE* pFile = fopen(OUTPUTPALETTE, "wb");

	for (size_t i = 0; i < 256; ++i) 
	{
		dword color = 0;

		if (ColorMap.find((byte) i) != ColorMap.end()) color = ColorMap[(byte)i];

		fwrite((void *)&color, 1, 3, pFile);
	}

	fclose(pFile);

	return true;
}


bool LoadGlobalPalette()
{
	FILE* pFile = fopen(GLOBALPALETTE, "rb");
	if (pFile == nullptr) return ReportError("Error: Failed to load global palette!");

	pGlobalPal = new byte[768 + 16];

	size_t bytesRead = fread(pGlobalPal, 1, 768, pFile);

	fclose(pFile);

	if (bytesRead != 768) return ReportError("Error: Only read %u of 768 bytes from global palette!", bytesRead);

	return true;
}


int main()
{
	ilInit();
	iluInit();
	ilEnable(IL_FILE_OVERWRITE);

	LoadGlobalPalette();

	TestParse();
	//CreatePalette();

    return 0;
}

