aboutsummaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorJohn Wilbert M. Villamor <lameguy64@gmail.com>2019-04-06 10:11:07 +0800
committerJohn Wilbert M. Villamor <lameguy64@gmail.com>2019-04-06 10:11:07 +0800
commitf3e040230772f978540a71aea43dfde200992922 (patch)
treebd8ca31b72dd01e24980b073854e263589530f56 /tools
downloadpsn00bsdk-f3e040230772f978540a71aea43dfde200992922.tar.gz
First commit
Diffstat (limited to 'tools')
-rw-r--r--tools/lzpack/filelist.cpp79
-rw-r--r--tools/lzpack/filelist.h43
-rw-r--r--tools/lzpack/lzp/lzconfig.h68
-rw-r--r--tools/lzpack/lzp/makefile30
-rw-r--r--tools/lzpack/main.cpp615
-rw-r--r--tools/lzpack/makefile46
-rw-r--r--tools/makefile12
-rw-r--r--tools/plugin/io_export_smx_v3.py320
-rw-r--r--tools/smxlink/main.cpp1003
-rw-r--r--tools/smxlink/makefile42
-rw-r--r--tools/smxlink/timreader.cpp65
-rw-r--r--tools/smxlink/timreader.h28
-rw-r--r--tools/tools.txt30
-rw-r--r--tools/util/elf2x.c300
-rw-r--r--tools/util/makefile19
15 files changed, 2700 insertions, 0 deletions
diff --git a/tools/lzpack/filelist.cpp b/tools/lzpack/filelist.cpp
new file mode 100644
index 0000000..8554ae0
--- /dev/null
+++ b/tools/lzpack/filelist.cpp
@@ -0,0 +1,79 @@
+#include "filelist.h"
+
+FileListClass::FileListClass() {
+
+ NumFiles = 0;
+ AllocFiles = 1;
+
+ FileList = (FileListEntry*)malloc(sizeof(FileListEntry));
+ memset(FileList, 0x00, sizeof(FileListEntry));
+
+}
+
+FileListClass::~FileListClass() {
+
+ for(int i=NumFiles-1; i>=0; i--) {
+
+ if (FileList[i].fileName != NULL)
+ free(FileList[i].fileName);
+
+ if (FileList[i].aliasName != NULL)
+ free(FileList[i].aliasName);
+
+ }
+
+ free(FileList);
+
+}
+
+void FileListClass::AddFileEntry(const char* fileName, const char* aliasName, short windowSize, short hash1Size, short hash2Size) {
+
+ if (NumFiles >= AllocFiles) {
+
+ FileList = (FileListEntry*)realloc(FileList, sizeof(FileListEntry)*(AllocFiles+1));
+ memset(&FileList[AllocFiles], 0x00, sizeof(FileListEntry));
+
+ AllocFiles++;
+
+ }
+
+ if (aliasName == NULL)
+ FileList[NumFiles].aliasName = NULL;
+ else
+ FileList[NumFiles].aliasName = strdup(aliasName);
+
+ FileList[NumFiles].fileName = strdup(fileName);
+ FileList[NumFiles].windowSize = windowSize;
+ FileList[NumFiles].hash1Size = hash1Size;
+ FileList[NumFiles].hash2Size = hash2Size;
+ NumFiles++;
+
+}
+
+const FileListEntry* FileListClass::Entry(int index) {
+
+ return(&FileList[index]);
+
+}
+
+int FileListClass::EntryCount() {
+
+ return(NumFiles);
+
+}
+
+void FileListClass::PrintEntries() {
+
+ for(int i=0; i<NumFiles; i++) {
+
+ printf("FL FILE:%s", FileList[i].fileName);
+
+ if (FileList[i].aliasName != NULL) {
+ printf(" ALIAS:%s\n", FileList[i].aliasName);
+ } else {
+ printf("\n");
+ }
+
+ }
+
+}
diff --git a/tools/lzpack/filelist.h b/tools/lzpack/filelist.h
new file mode 100644
index 0000000..5351335
--- /dev/null
+++ b/tools/lzpack/filelist.h
@@ -0,0 +1,43 @@
+#ifndef _FILELIST_H
+#define _FILELIST_H
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <limits.h>
+
+#ifndef MAX_PATH
+#define MAX_PATH PATH_MAX
+#endif
+
+typedef struct {
+ char* fileName;
+ char* aliasName;
+ int windowSize;
+ int hash1Size;
+ int hash2Size;
+} FileListEntry;
+
+class FileListClass {
+
+ int NumFiles;
+ int AllocFiles;
+ FileListEntry* FileList;
+
+public:
+
+ FileListClass();
+ virtual ~FileListClass();
+
+ void AddFileEntry(const char* fileName, const char* aliasName, short windowSize, short hash1Size, short hash2Size);
+
+ const FileListEntry* Entry(int index);
+ int EntryCount();
+
+ void PrintEntries();
+
+};
+
+
+#endif
diff --git a/tools/lzpack/lzp/lzconfig.h b/tools/lzpack/lzp/lzconfig.h
new file mode 100644
index 0000000..65e623c
--- /dev/null
+++ b/tools/lzpack/lzp/lzconfig.h
@@ -0,0 +1,68 @@
+/*! \file lzconfig.h
+ * \brief Library configuration header
+ * \details Define settings will only take effect when you recompile the library.
+ */
+
+#ifndef _LZP_CONFIG_H
+#define _LZP_CONFIG_H
+
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+
+/* Set to TRUE to compile without data compression routines useful if you
+ * plan to use this library on a program that does not require said routines
+ * especially on a platform with limited memory (such as the PlayStation).
+ *
+ * This define will rule out lzCompress(), lzSetHashSizes() and
+ * lzResetHashSizes() functions and their associated functions.
+ */
+#define LZP_NO_COMPRESS FALSE
+
+
+/* Set to TRUE to make default compression table sizes to maximum and works best
+ * when compressing large amounts of data. LZP_USE_MALLOC must be set to TRUE to
+ * prevent stack overflow errors.
+ *
+ * Do not enable this if you plan to compile for a platform with limited memory
+ * otherwise, the library will consume all memory and crash the system.
+ *
+ * This define only affects lzCompress().
+ */
+#define LZP_MAX_COMPRESS TRUE
+
+
+/* Uncomment to make the library use malloc() instead of array initializers to
+ * allocate hash tables. Enabling this is a must if you plan to use large hash
+ * and window table sizes.
+ */
+#define LZP_USE_MALLOC TRUE
+
+
+/* Hash table sizes (in power-of-two multiple units)
+ *
+ * These define only affect lzCompress().
+ */
+#if LZP_MAX_COMPRESS == TRUE
+
+// Minimal defaults
+#define LZP_WINDOW_SIZE 17
+#define LZP_HASH1_SIZE 8
+#define LZP_HASH2_SIZE 10
+
+#else
+
+// Maximum defaults
+#define LZP_WINDOW_SIZE 17
+#define LZP_HASH1_SIZE 22
+#define LZP_HASH2_SIZE 24
+
+#endif
+
+
+#endif // _LZP_CONFIG_H
diff --git a/tools/lzpack/lzp/makefile b/tools/lzpack/lzp/makefile
new file mode 100644
index 0000000..83da67d
--- /dev/null
+++ b/tools/lzpack/lzp/makefile
@@ -0,0 +1,30 @@
+# This LZP library builds off the lzp sources in libpsn00b/lzp. The only
+# difference is this is built with compression enabled specified in the
+# lzconfig.h file and the library is built for the host platform.
+
+TARGET = liblzp.a
+
+CFILES = $(wildcard ../../../libpsn00b/lzp/*.c)
+OFILES = $(addprefix build/, $(notdir $(CFILES:.c=.o)))
+
+INCLUDE = -I../include -I.
+
+CFLAGS = -g -O2
+
+CC = $(PREFIX)gcc
+AR = $(PREFIX)ar
+RANLIB = $(PREFIX)ranlib
+
+all: $(TARGET)
+
+$(TARGET): $(OFILES)
+ $(AR) cr $(TARGET) $(OFILES)
+ $(RANLIB) $(TARGET)
+
+# Dunno if there's a better way to do this but it works at least
+build/%.o: ../../../libpsn00b/lzp/%.c
+ @mkdir -p $(dir $@)
+ $(CC) $(CFLAGS) $(INCLUDE) -c $< -o $@
+
+clean:
+ rm -Rf build $(TARGET) \ No newline at end of file
diff --git a/tools/lzpack/main.cpp b/tools/lzpack/main.cpp
new file mode 100644
index 0000000..ed650d2
--- /dev/null
+++ b/tools/lzpack/main.cpp
@@ -0,0 +1,615 @@
+#include <stdio.h>
+#include <tinyxml2.h>
+
+#include "lzp/lzconfig.h"
+#include "lzp/lzp.h"
+#include "filelist.h"
+
+
+#define BUFF_SIZE 4096
+
+
+typedef struct {
+ char id[3];
+ u_char numFiles;
+} QLP_HEAD;
+
+typedef struct {
+ char fileName[16];
+ u_long fileSize;
+ u_long offset;
+} QLP_FILE;
+
+
+typedef struct {
+ char name[16];
+ int size;
+ int offset; // In 2048 byte sector units
+} PCK_FILE;
+
+typedef struct {
+ char id[3];
+ u_char numFiles;
+ PCK_FILE file[85]; // File entries
+ int lba; // LBA of the PCK file (in 2048 byte sector units)
+} PCK_TOC;
+
+
+namespace param {
+
+ bool AlwaysOverwrite = false;
+ char ScriptFile[MAX_PATH]= { 0 };
+
+}
+
+
+int ParseCreateElement(tinyxml2::XMLElement* element);
+
+char* lcase(char* str);
+const char* TrimPathName(const char* path);
+
+
+int main(int argc, const char* argv[]) {
+
+ printf("LZPack v0.60b - File Compression and Packing Utility\n");
+ printf("2016-2019 Meido-Tek Productions (Lameguy64)\n\n");
+
+ if (argc <= 1) {
+
+ printf("Parameters:\n");
+ printf(" lzpack [-y] <scriptFile>\n\n");
+ printf(" -y - Always overwrite existing files.\n");
+ printf(" <scriptFile> - Script file to parse (in XML format, see readme.txt).\n");
+
+ exit(0);
+
+ }
+
+
+ // Parse arguments
+ for(int i=1; i<argc; i++) {
+
+ if (strcmp("-y", argv[i]) == 0) {
+
+ param::AlwaysOverwrite = true;
+
+ } else if ((argv[i][0] == '-') || (argv[i][0] == '/')) {
+
+ printf("Unknown parameter: %s\n", argv[i]);
+
+ } else {
+
+ strcpy(param::ScriptFile, argv[i]);
+
+ }
+
+ }
+
+ if (strlen(param::ScriptFile) == 0) {
+ printf("ERROR: No script file specified.\n");
+ exit(EXIT_FAILURE);
+ }
+
+
+ tinyxml2::XMLDocument document;
+
+ switch(document.LoadFile(param::ScriptFile)) {
+ case tinyxml2::XML_SUCCESS:
+
+ break;
+
+ case tinyxml2::XML_ERROR_FILE_NOT_FOUND:
+
+ printf("ERROR: Could not find file: %s\n", param::ScriptFile);
+ exit(EXIT_FAILURE);
+
+ case tinyxml2::XML_ERROR_FILE_COULD_NOT_BE_OPENED:
+ case tinyxml2::XML_ERROR_FILE_READ_ERROR:
+
+ printf("ERROR: Could not load file: %s\n", param::ScriptFile);
+ exit(EXIT_FAILURE);
+
+ case tinyxml2::XML_ERROR_EMPTY_DOCUMENT:
+
+ printf("ERROR: %s is empty.\n", param::ScriptFile);
+ exit(EXIT_FAILURE);
+
+ default:
+
+ printf("ERROR: Unknown error when loading %s\n", param::ScriptFile);
+ exit(EXIT_FAILURE);
+
+ }
+
+
+ tinyxml2::XMLElement* element = document.FirstChildElement("lzp_project");
+
+ if (element == NULL) {
+
+ printf("ERROR: <lzp_project> element not found.\n");
+ exit(EXIT_FAILURE);
+
+ }
+
+
+ tinyxml2::XMLElement* createElement = element->FirstChildElement("create");
+
+ while(createElement != NULL) {
+
+ ParseCreateElement(createElement);
+
+ createElement = createElement->NextSiblingElement();
+
+ }
+
+
+ return(0);
+
+}
+
+
+int CreateLZPfile(const char* packFile, FileListClass* fileList) {
+
+ FILE* packp;
+ LZP_FILE entry[fileList->EntryCount()];
+ int overallSize=0;
+ int overallPackedSize=0;
+
+
+ packp = fopen(packFile, "wb");
+
+ fseek(packp, sizeof(LZP_HEAD)+(sizeof(LZP_FILE)*fileList->EntryCount()), SEEK_SET);
+
+ for(int i=0; i<fileList->EntryCount(); i++) {
+
+ const char* name;
+
+ if (fileList->Entry(i)->aliasName == NULL) {
+
+ name = TrimPathName(fileList->Entry(i)->fileName);
+
+ } else {
+
+ name = fileList->Entry(i)->aliasName;
+
+ }
+
+ if (strlen(name) > 15) {
+
+ printf("ERROR: Entry '%s' has more than 15 characters.\n", name);
+ fclose(packp);
+ unlink(packFile);
+
+ return(0);
+
+ }
+
+ strcpy(entry[i].fileName, name);
+
+ if (fileList->Entry(i)->aliasName == NULL) {
+ printf(" Packing %s... ", fileList->Entry(i)->fileName);
+ } else {
+ printf(" Packing %s as %s... ", fileList->Entry(i)->fileName, fileList->Entry(i)->aliasName);
+ }
+
+
+ FILE* fp = fopen(fileList->Entry(i)->fileName, "rb");
+
+ fseek(fp, 0, SEEK_END);
+ int fileSize = ftell(fp);
+ fseek(fp, 0, SEEK_SET);
+
+ void* fileBuff = malloc(fileSize);
+ fread(fileBuff, fileSize, 1, fp);
+
+ fclose(fp);
+
+
+ void* compBuff = malloc(fileSize+16384);
+ int compSize = lzCompress(compBuff, fileBuff, fileSize, 2);
+
+
+ entry[i].crc = lzCRC32(compBuff, compSize, LZP_CRC32_REMAINDER);
+ entry[i].fileSize = fileSize;
+ entry[i].packedSize = compSize;
+ entry[i].offset = ftell(packp);
+
+ fwrite(compBuff, compSize, 1, packp);
+
+ free(compBuff);
+ free(fileBuff);
+
+ printf("Ok. (%.02f%%)\n", 100.f*((float)compSize/fileSize));
+
+ overallSize += fileSize;
+ overallPackedSize += compSize;
+
+ }
+
+
+ LZP_HEAD head;
+
+ strncpy(head.id, "LZP", 4);
+ head.numFiles = fileList->EntryCount();
+
+ fseek(packp, 0, SEEK_SET);
+ fwrite(&head, sizeof(LZP_HEAD), 1, packp);
+
+ fwrite(entry, sizeof(LZP_FILE), fileList->EntryCount(), packp);
+
+ fclose(packp);
+
+
+ printf("Packed %d file(s) totaling %d bytes (%.02f%% compression ratio).\n",
+ fileList->EntryCount(),
+ overallPackedSize,
+ 100.f*((float)overallPackedSize/overallSize)
+ );
+
+
+ return(true);
+
+}
+
+int CreateQLPfile(const char* packFile, FileListClass* fileList) {
+
+ FILE* packp;
+ QLP_HEAD head;
+ QLP_FILE fileEntry[fileList->EntryCount()];
+
+ strncpy(head.id, "QLP", 3);
+ head.numFiles = fileList->EntryCount();
+
+ packp = fopen(packFile, "wb");
+
+ fseek(packp, sizeof(QLP_HEAD)+(sizeof(QLP_FILE)*head.numFiles), SEEK_SET);
+
+ for(int i=0; i<head.numFiles; i++) {
+
+ // Get name for the file entry either from its source file name or specified alias
+ const char* name;
+
+ if (fileList->Entry(i)->aliasName == NULL) {
+
+ name = TrimPathName(fileList->Entry(i)->fileName);
+
+ } else {
+
+ name = fileList->Entry(i)->aliasName;
+
+ }
+
+ // Make sure entry name does not exceed 15 characters (16 with null terminator byte)
+ if (strlen(name) > 15) {
+
+ printf("ERROR: Entry '%s' has more than 15 characters.\n", name);
+ fclose(packp);
+ unlink(packFile);
+
+ return(0);
+
+ }
+
+ strcpy(fileEntry[i].fileName, name);
+
+ if (fileList->Entry(i)->aliasName == NULL) {
+
+ printf(" Packing %s... ", fileList->Entry(i)->fileName);
+
+ } else {
+
+ printf(" Packing %s as %s... ", fileList->Entry(i)->fileName, fileList->Entry(i)->aliasName);
+
+ }
+
+ // Make sure written data is aligned in multiples of 4 bytes
+ if ((4*((ftell(packp)+3)/4)) != ftell(packp))
+ fseek(packp, (4*((ftell(packp)+3)/4)), SEEK_SET);
+
+ // Set name and offset of file entry
+ memset(fileEntry[i].fileName, 0x00, 16);
+ strcpy(fileEntry[i].fileName, name);
+ fileEntry[i].offset = ftell(packp);
+
+ // Open file and copy its contents to the pack file
+ FILE* fp = fopen(fileList->Entry(i)->fileName, "rb");
+
+ int bytesCopied = 0;
+ void* copyBuff = malloc(BUFF_SIZE);
+
+ while(!feof(fp)) {
+
+ int bytesRead = fread(copyBuff, 1, BUFF_SIZE, fp);
+
+ fwrite(copyBuff, bytesRead, 1, packp);
+
+ bytesCopied += bytesRead;
+
+ }
+
+ free(copyBuff);
+ fclose(fp);
+
+ fileEntry[i].fileSize = bytesCopied;
+
+ printf("Done.\n");
+
+ }
+
+ printf("Packed %d file(s) totaling %d bytes.\n", head.numFiles, (int)ftell(packp));
+
+ fseek(packp, 0, SEEK_SET);
+ fwrite(&head, sizeof(QLP_HEAD), 1, packp);
+
+ fwrite(fileEntry, sizeof(QLP_FILE), head.numFiles, packp);
+
+ fclose(packp);
+
+ return(true);
+
+}
+
+int CreatePCKfile(const char* packFile, FileListClass* fileList) {
+
+ FILE* packp;
+ PCK_TOC toc;
+
+ memset(&toc, 0x00, sizeof(PCK_TOC));
+
+ toc.numFiles = fileList->EntryCount();
+
+ packp = fopen(packFile, "wb");
+
+ fseek(packp, 2048, SEEK_SET);
+
+ for(int i=0; i<toc.numFiles; i++) {
+
+ // Get name for the file entry either from its source file name or specified alias
+ const char* name;
+
+ if (fileList->Entry(i)->aliasName == NULL) {
+
+ name = TrimPathName(fileList->Entry(i)->fileName);
+
+ } else {
+
+ name = fileList->Entry(i)->aliasName;
+
+ }
+
+ // Make sure entry name does not exceed 15 characters (16 with null terminator byte)
+ if (strlen(name) > 15) {
+
+ printf("ERROR: Entry '%s' has more than 15 characters.\n", name);
+ fclose(packp);
+ unlink(packFile);
+
+ return(0);
+
+ }
+
+ strcpy(toc.file[i].name, name);
+ toc.file[i].offset = ftell(packp)/2048;
+
+ if (fileList->Entry(i)->aliasName == NULL) {
+
+ printf(" Packing %s... ", fileList->Entry(i)->fileName);
+
+ } else {
+
+ printf(" Packing %s as %s... ", fileList->Entry(i)->fileName, fileList->Entry(i)->aliasName);
+
+ }
+
+ FILE* fp = fopen(fileList->Entry(i)->fileName, "rb");
+ void* buff = malloc(BUFF_SIZE);
+
+ int bytesTotal = 0;
+
+ while(!feof(fp)) {
+
+ int bytesRead = fread(buff, 1, BUFF_SIZE, fp);
+ fwrite(buff, 1, bytesRead, packp);
+ bytesTotal += bytesRead;
+
+ }
+
+ fclose(fp);
+ free(buff);
+
+ toc.file[i].size = bytesTotal;
+
+ if ((2048*((ftell(packp)+2047)/2048)) != ftell(packp)) {
+
+ int pad = (2048*(((ftell(packp)%2048)+2047)/2048))-(ftell(packp)%2048);
+ char padding[pad];
+
+ memset(padding, 0x00, pad);
+ fwrite(padding, pad, 1, packp);
+
+ }
+
+ printf("Done.\n");
+
+ }
+
+ printf("Packed %d file(s) totaling %d bytes.\n", toc.numFiles, (int)ftell(packp));
+
+ strncpy(toc.id, "PCK", 3);
+
+ fseek(packp, 0, SEEK_SET);
+ fwrite(&toc, sizeof(PCK_TOC), 1, packp);
+
+ fclose(packp);
+
+ return(true);
+
+}
+
+int ParseCreateElement(tinyxml2::XMLElement* element) {
+
+
+ const char* packName = element->Attribute("packname");
+
+ if (packName == NULL) {
+ printf("ERROR: No 'packname' attribute found in <create> element.\n");
+ return(false);
+ }
+
+
+ int packFormat;
+
+ {
+
+ char* packType = (char*)element->Attribute("format");
+
+ if (packType == NULL) {
+
+ packType = strdup("lzp");
+
+ } else {
+
+ packType = strdup(packType);
+ lcase(packType);
+
+ }
+
+ if (strcmp("lzp", packType) == 0) {
+ packFormat = 0;
+ } else if (strcmp("qlp", packType) == 0) {
+ packFormat = 1;
+ } else if (strcmp("pck", packType) == 0) {
+ packFormat = 2;
+ } else {
+
+ printf("ERROR: Unknown pack format: %s\n", packType);
+ free(packType);
+ return(false);
+
+ }
+
+ free(packType);
+
+ }
+
+
+ printf("Creating %s in ", packName);
+ switch(packFormat) {
+ case 0:
+
+ printf("LZP");
+ break;
+
+ case 1:
+
+ printf("QLP");
+ break;
+
+ case 2:
+
+ printf("PCK");
+ break;
+
+ }
+ printf(" format...\n");
+
+
+ tinyxml2::XMLElement* fileElement = element->FirstChildElement("file");
+
+ if (fileElement == NULL) {
+ printf("ERROR: No file element(s) found.\n");
+ return(false);
+ }
+
+
+ FileListClass fileList;
+
+ while(1) {
+
+ bool valid = true;
+
+ int entryWindowSize = LZP_WINDOW_SIZE;
+ int entryHash1Size = LZP_HASH1_SIZE;
+ int entryHash2Size = LZP_HASH2_SIZE;
+
+ if (fileElement->GetText() == NULL) {
+ printf("WARNING: <file> element not containing text found.\n");
+ valid = false;
+ }
+
+
+ FILE* fp = fopen(fileElement->GetText(), "rb");
+ if (!fp) {
+ printf("WARNING: File '%s' either does not exist or it cannot be opened.\n", fileElement->GetText());
+ valid = false;
+ }
+ fclose(fp);
+
+
+ if (valid) {
+ fileList.AddFileEntry(
+ fileElement->GetText(),
+ fileElement->Attribute("alias"),
+ entryWindowSize,
+ entryHash1Size,
+ entryHash2Size
+ );
+ }
+
+
+ fileElement = fileElement->NextSiblingElement();
+
+ if (fileElement == NULL)
+ break;
+
+ }
+
+
+ if (fileList.EntryCount() == 0) {
+ printf("No file(s) to pack.\n");
+ return(true);
+ }
+
+ switch(packFormat) {
+ case 0: // Create LZP
+ CreateLZPfile(packName, &fileList);
+ break;
+ case 1: // Create QLP
+ CreateQLPfile(packName, &fileList);
+ break;
+ case 2: // Create PCK
+ CreatePCKfile(packName, &fileList);
+ break;
+ }
+
+
+ return(true);
+
+}
+
+
+const char* TrimPathName(const char* path) {
+
+ if ((strrchr(path, '\\') == NULL) && (strrchr(path, '/') == NULL)) {
+
+ return(path);
+
+ } else {
+
+ if (strrchr(path, '\\') == NULL)
+ return(strrchr(path, '/')+1);
+
+ return(strrchr(path, '\\')+1);
+
+ }
+
+}
+
+char* lcase(char* str) {
+
+ for(int i=0; str[i]!=0x00; i++)
+ str[i] = tolower(str[i]);
+
+ return(str);
+
+}
diff --git a/tools/lzpack/makefile b/tools/lzpack/makefile
new file mode 100644
index 0000000..e11f5c7
--- /dev/null
+++ b/tools/lzpack/makefile
@@ -0,0 +1,46 @@
+TARGET := lzpack
+
+CPPFILES = main.cpp filelist.cpp
+CFLAGS = -O2
+LDFLAGS = -s
+
+LIBS = -ltinyxml2 -llzp
+
+CC = gcc
+CXX = g++
+
+OFILES = $(addprefix build/,$(CPPFILES:.cpp=.o))
+
+ifeq "$(OS)" "Windows_NT"
+
+# Config for Windows
+INCLUDE = -I/c/tinyxml2
+LIBDIRS = -L/c/tinyxml2
+TARGET := $(TARGET).exe
+
+else
+
+# Config for anything else that isn't Linux
+
+endif
+
+INCLUDE += -I../../libpsn00b
+LIBDIRS += -Llzp
+
+build/%.o: %.cpp
+ @mkdir -p $(dir $@)
+ $(CXX) $(CFLAGS) $(INCLUDE) -c $< -o $@
+
+all: $(OFILES)
+ $(MAKE) -C lzp
+ $(CXX) $(CFLAGS) $(LDFLAGS) $(LIBDIRS) $(OFILES) $(LIBS) -o $(TARGET)
+
+install:
+ mkdir -p ../bin
+ cp $(TARGET) ../bin/$(TARGET)
+
+clean:
+ $(MAKE) -C lzp clean
+ rm -Rf build $(TARGET)
+
+cleanall: clean
diff --git a/tools/makefile b/tools/makefile
new file mode 100644
index 0000000..aec452c
--- /dev/null
+++ b/tools/makefile
@@ -0,0 +1,12 @@
+TOPTARGETS = all install clean
+
+TOOLDIRS = lzpack smxlink util
+
+$(TOPTARGETS): $(TOOLDIRS)
+$(TOOLDIRS):
+ $(MAKE) -C $@ $(MAKECMDGOALS)
+
+clean: $(LIBDIRS)
+ rm -Rf bin
+
+.PHONY: $(TOPTARGETS) $(TOOLDIRS)
diff --git a/tools/plugin/io_export_smx_v3.py b/tools/plugin/io_export_smx_v3.py
new file mode 100644
index 0000000..22c38b6
--- /dev/null
+++ b/tools/plugin/io_export_smx_v3.py
@@ -0,0 +1,320 @@
+# This plugin is part of Scarlet Engine (formerly Project Scarlet)
+#
+# It is still a work in progress and the SMX file specification may change
+# in future versions.
+
+"""
+This script exports Scarlet Game Engine SDK compatible SMX files.
+Supports normals, colors and texture mapped triangles.
+Only one object can be exported at a time.
+"""
+
+import os
+import bpy
+
+from bpy.props import (CollectionProperty,
+ StringProperty,
+ BoolProperty,
+ EnumProperty,
+ FloatProperty,
+ )
+
+from bpy_extras.io_utils import (ImportHelper,
+ ExportHelper,
+ axis_conversion,
+ )
+
+bl_info = {
+ "name": "Export: Project Scarlet SMX Raw Model",
+ "author": "Jobert 'Lameguy' Villamor (Lameguy64)",
+ "blender": (2,6,9),
+ "version": (3,1,2),
+ "location": "File > Export",
+ "description": "Export mesh to Project Scarlet SMX model format",
+ "category": "Import-Export"
+}
+
+class ExportSMX(bpy.types.Operator, ExportHelper):
+
+ bl_idname = "export_test.smx";
+ bl_label = "Export SMX";
+
+ filename_ext = ".smx";
+ filter_glob = StringProperty(default="*.smx", options={'HIDDEN'})
+
+ # Export options
+ exp_applyModifiers = BoolProperty(
+ name="Apply Modifiers",
+ description="Apply modifiers to the exported mesh",
+ default=True,
+ )
+
+ exp_writeNormals = BoolProperty(
+ name="Normals",
+ description="Export normals for smooth and hard shaded faces",
+ default=True,
+ )
+
+ #exp_vertexWeights = BoolProperty(
+ # name="Vertex Weights",
+ # description="Export vertex weights",
+ # default=False,
+ # )
+
+ #exp_vertexGroups = BoolProperty(
+ # name="Vertex Groups",
+ # description="Export vertex group information",
+ # default=False,
+ # )
+
+ #exp_doubleSided = BoolProperty(
+ # name="Double-sided",
+ # description="Sets the double-sided attribute to all exported polygons",
+ # default=False,
+ # )
+
+ #exp_subsurfMode = BoolProperty(
+ # name="Subsurf-Mode",
+ # description="Enable this if the mesh you're exporting uses a subsurf divide modifier, faster but often wasteful",
+ # default=False,
+ # )
+
+ #exp_blendMode = EnumProperty(
+ # name="Semi-trans:",
+ # description="Sets the semi-transparency attribute for all exported polygons",
+ # items=(('BN', "Off", ""),
+ # ('B0', "0: 50%B + 50%F", ""),
+ # ('B1', "1: 100%B + 100%F", ""),
+ # ('B2', "2: 100%B - 100%F", ""),
+ # ('B3', "3: 100%B + 25%F", ""),
+ # ),
+ # default='BN',
+ # )
+
+ #exp_scaleFactor = FloatProperty(
+ # name="Scale Factor",
+ # description="Scale factor of exported mesh",
+ # min=0.01, max=1000.0,
+ # default=1.0,
+ # )
+
+ def execute(self, context):
+
+ print("Export execute...\n")
+
+ obj = context.object
+ mesh = obj.to_mesh(context.scene, self.exp_applyModifiers, 'PREVIEW')
+
+ if not mesh.tessfaces and mesh.polygons:
+ mesh.calc_tessface()
+
+ filepath = self.filepath
+ filepath = bpy.path.ensure_ext(filepath, self.filename_ext)
+
+ with open(filepath, "w") as f:
+
+ # Write a banner
+ f.write("<!-- Created using Project Scarlet SMX Export Plug-in for Blender -->\n")
+ f.write("<!-- NOTE: If you plan to use this model as a static mesh, it is recommended that you run this file through smxopt -->\n")
+ f.write("<!-- or smxtool to clean up duplicate/unused normals which are kept for animation purposes. -->\n")
+
+ f.write("<model version=\"1\">\n")
+
+ # Write vertices
+ f.write("<vertices count=\"%d\">\n" % len(mesh.vertices))
+ for v in mesh.vertices:
+ f.write("<v x=\"%f\" y=\"%f\" z=\"%f\"/>\n" % (v.co.x, -v.co.z, v.co.y))
+ f.write("</vertices>\n")
+
+
+ # Scan if there are any flat primitives
+ has_flats = False
+ for i,p in enumerate(mesh.tessfaces):
+ if p.use_smooth is False:
+ has_flats = True
+ break
+
+ # Export normals
+ if self.exp_writeNormals:
+ if has_flats:
+ f.write("<normals count=\"%d\">\n" % (len(mesh.vertices)+len(mesh.polygons)))
+ else:
+ f.write("<normals count=\"%d\">\n" % (len(mesh.vertices)))
+ f.write("<!-- Smooth normals begin here -->\n")
+ for v in mesh.vertices:
+ f.write("<v x=\"%f\" y=\"%f\" z=\"%f\"/>\n" % (v.normal.x, -v.normal.z, v.normal.y))
+ if has_flats:
+ f.write("<!-- Flat normals begin here -->\n")
+ flatnorms_start = len(mesh.vertices)
+ for p in mesh.polygons:
+ f.write("<v x=\"%f\" y=\"%f\" z=\"%f\"/>\n" % (p.normal.x, -p.normal.z, p.normal.y))
+ f.write("</normals>\n")
+
+
+ # Write texture files
+ mesh_uvs = mesh.tessface_uv_textures.active
+
+ if mesh_uvs is not None:
+ mesh_uvs = mesh_uvs.data
+
+ # Scan through all faces for assigned textures
+ if mesh_uvs is not None:
+ tex_table = []
+ tex_files = []
+ for uv in mesh_uvs:
+ if uv.image is not None:
+ addTex = True
+ texFileName = bpy.path.display_name_from_filepath(uv.image.filepath)
+ if len(tex_files)>0:
+ for c,t in enumerate(tex_files):
+ if t == texFileName:
+ tex_table.append(c+1)
+ addTex = False
+ break
+ if addTex:
+ print("TF:%s" % (texFileName))
+ tex_files.append(texFileName)
+ tex_table.append(len(tex_files))
+ else:
+ tex_table.append(0)
+
+ # Write texture files
+ f.write("<textures count=\"%d\">\n" % len(tex_files))
+ for n in tex_files:
+ f.write("<texture file=\"%s\"/>\n" % n)
+ f.write("</textures>\n")
+ else:
+ tex_table = None
+ tex_files = None
+
+
+ mesh_cols = mesh.tessface_vertex_colors.active
+
+ if mesh_cols is not None:
+ mesh_cols = mesh_cols.data
+
+ tri_indices = ( 0, 2, 1 );
+ quad_indices = ( 3, 2, 0, 1 );
+
+ f.write("<primitives count=\"%d\">\n" % len(mesh.tessfaces))
+ for i,p in enumerate(mesh.tessfaces):
+
+ # Write vertex indices
+ f.write("<poly ")
+ if (len(p.vertices) == 3):
+ f.write("v0=\"%d\" " % (p.vertices[0]))
+ f.write("v1=\"%d\" " % (p.vertices[2]))
+ f.write("v2=\"%d\" " % (p.vertices[1]))
+ elif (len(p.vertices) == 4):
+ f.write("v0=\"%d\" " % (p.vertices[3]))
+ f.write("v1=\"%d\" " % (p.vertices[2]))
+ f.write("v2=\"%d\" " % (p.vertices[0]))
+ f.write("v3=\"%d\" " % (p.vertices[1]))
+
+ # Write normal indices and shading mode
+ if self.exp_writeNormals:
+ if p.use_smooth:
+ if (len(p.vertices) == 3):
+ f.write("n0=\"%d\" " % (p.vertices[0]))
+ f.write("n1=\"%d\" " % (p.vertices[2]))
+ f.write("n2=\"%d\" " % (p.vertices[1]))
+ elif (len(p.vertices) == 4):
+ f.write("n0=\"%d\" " % (p.vertices[3]))
+ f.write("n1=\"%d\" " % (p.vertices[2]))
+ f.write("n2=\"%d\" " % (p.vertices[0]))
+ f.write("n3=\"%d\" " % (p.vertices[1]))
+ f.write("shading=\"S\" ")
+ else:
+ f.write("n0=\"%d\" " % (flatnorms_start+i))
+ f.write("shading=\"F\" ")
+
+ if tex_table is not None:
+ if (tex_table[i] > 0):
+ color_mul = 128.0
+ else:
+ color_mul = 255.0
+ else:
+ color_mul = 255.0
+
+ # Write out vertex colors if available
+ if mesh_cols is None:
+ f.write("r0=\"128\" g0=\"128\" b0=\"128\" ")
+ typecode = "F"
+ else:
+ col = mesh_cols[i]
+ col = col.color1[:], col.color2[:], col.color3[:], col.color4[:]
+ # Check if polygon is flat shaded
+ if (col[0] == col[1]) and (col[1] == col[2]) and (col[2] == col[0]):
+ # is flat...
+ color = col[0]
+ color = (int(color[0]*color_mul),
+ int(color[1]*color_mul),
+ int(color[2]*color_mul),
+ )
+ f.write("r0=\"%d\" g0=\"%d\" b0=\"%d\" " % color[:])
+ typecode = "F"
+ else:
+ # is gouraud...
+ for j,c in enumerate(p.vertices):
+ if (len(p.vertices) == 4):
+ color = col[quad_indices[j]]
+ else:
+ color = col[tri_indices[j]]
+ color = (int(color[0]*color_mul),
+ int(color[1]*color_mul),
+ int(color[2]*color_mul),
+ )
+ f.write("r%d=\"%d\" g%d=\"%d\" b%d=\"%d\" " %
+ (j, color[0], j, color[1], j, color[2]))
+ typecode = "G"
+
+ # Add texcoords
+ if tex_table is not None:
+ if (tex_table[i] > 0):
+ f.write("texture=\"%d\" " % (tex_table[i]-1));
+ if (len(p.vertices) == 3):
+ uv = (mesh_uvs[i].uv1,
+ mesh_uvs[i].uv3,
+ mesh_uvs[i].uv2
+ )
+ elif (len(p.vertices) == 4):
+ uv = (mesh_uvs[i].uv4,
+ mesh_uvs[i].uv3,
+ mesh_uvs[i].uv1,
+ mesh_uvs[i].uv2
+ )
+ tex_w = mesh_uvs[i].image.size[0]-0.85#(1.0/mesh_uvs[i].image.size[0])
+ tex_h = mesh_uvs[i].image.size[1]-0.85#(1.0-(1.0/mesh_uvs[i].image.size[1]))
+ for j,c in enumerate(uv):
+ f.write("tu%d=\"%d\" tv%d=\"%d\" " %
+ (j, round(tex_w*uv[j].x), j, round(tex_h-(tex_h*uv[j].y))))
+ typecode += "T"
+
+ typecode += "%d" % len(p.vertices)
+ f.write("type=\"%s\" " % typecode)
+ f.write("/>\n")
+
+ f.write("</primitives>\n")
+
+ f.write("</model>")
+
+ f.close()
+
+ return {'FINISHED'};
+
+# For registering to Blender menus
+def menu_func(self, context):
+ self.layout.operator(ExportSMX.bl_idname, text="Scarlet 3D SMX v3 (.smx)");
+
+def register():
+ bpy.utils.register_module(__name__);
+ bpy.types.INFO_MT_file_export.append(menu_func);
+
+def unregister():
+ bpy.utils.unregister_module(__name__);
+ bpy.types.INFO_MT_file_export.remove(menu_func);
+
+if __name__ == "__main__":
+ register()
+ # Uncomment when testing this script
+ #bpy.ops.export_test.smx('INVOKE_DEFAULT') \ No newline at end of file
diff --git a/tools/smxlink/main.cpp b/tools/smxlink/main.cpp
new file mode 100644
index 0000000..ec6f707
--- /dev/null
+++ b/tools/smxlink/main.cpp
@@ -0,0 +1,1003 @@
+/* This tool was originally developed for Scarlet Engine
+ * (formerly Project Scarlet)
+ *
+ * This utility is still a work in progress and the SMX and SMD specification
+ * is subject to change without notice. It is included as part of the
+ * PSn00bSDK project as an example utility for the n00bdemo example which
+ * uses SMD format model data files.
+ *
+ */
+
+#include <stdio.h>
+#include <math.h>
+#include <tinyxml2.h>
+#include <string>
+//#include <windef.h>
+#include "timreader.h"
+
+#define VERSION "0.25b"
+
+namespace param
+{
+ std::string smxFileName;
+ std::string smdFileName;
+ std::string texDir;
+
+ float scaleFactor = 1.f;
+}
+
+typedef struct {
+ char id[3]; // File ID (SMD)
+ unsigned char version; // Version number (0x01)
+ unsigned short flags;
+ unsigned short numverts;
+ unsigned short numnorms;
+ unsigned short numprims;
+ unsigned long vtxAddr;
+ unsigned long nrmAddr;
+ unsigned long priAddr;
+} SMD_HEADER;
+
+typedef struct {
+ short vx,vy,vz,vp;
+} SVECTOR;
+
+
+#define PRIM_TYPE_LINE 0
+#define PRIM_TYPE_TRI 1
+#define PRIM_TYPE_QUAD 2
+
+#define PRIM_LIGHTING_NONE 0 // No shading (no normals)
+#define PRIM_LIGHTING_FLAT 1 // Flat shading (1 normal)
+#define PRIM_LIGHTING_SMOOTH 2 // Smooth shading (3 normals per vertex)
+
+typedef struct {
+
+ unsigned char type:2; // Primitive type
+ unsigned char l_type:2; // Lighting type (0 - none, 1 - flat shading, 2 - smooth shading)
+ unsigned char c_type:1; // Coloring type (0 - solid color, 1 - gouraud)
+ unsigned char texture:1; // Texture mapped
+ unsigned char blend:2; // Blend mode setting (actual blend enable is determined by primitive code)
+ // byte boundary
+ unsigned char zoff:4;
+ unsigned char nocull:1; // Double sided (no cull)
+ unsigned char mask:1; // Force mask bit setting
+ unsigned char texwin:2;
+ // byte boundary
+ unsigned char texoff:2;
+ unsigned char reserved:6;
+ // byte boundary
+ unsigned char len;
+} PRIM_ID;
+
+typedef struct {
+ unsigned short v0,v1,v2,v3;
+} PRIM_V;
+
+typedef struct {
+ unsigned char r,g,b,c;
+} PRIM_RGBC;
+
+typedef struct {
+ unsigned short tpage,clut;
+} PRIM_TC;
+
+typedef struct {
+ unsigned char u,v;
+} PRIM_UV;
+
+
+int main(int argc, const char* argv[]) {
+
+ printf("SMXLINK " VERSION " - Scarlet SMX to SMD Model Converter "
+ "(part of Scarlet Engine)\n");
+ printf("Note: Outputs in *NEW* revision 1 format!\n");
+ printf("2017-2019 Meido-Tek Productions\n\n");
+
+ if (argc <= 1) {
+
+ printf("Parameters:\n");
+ printf(" smxlink [-o <filename>] [-s <scale>] <smxfile>\n\n");
+ printf(" -o <filename> - Specify output filename (default: first file specified)\n");
+ printf(" -s <scale> - Scale factor to apply to model on conversion (default: 1.0)\n");
+ printf(" -tp <path> - Specify directory path to TIM texture files\n");
+ printf(" <smxfile> - SMX file to convert to SMD\n");
+
+ return EXIT_SUCCESS;
+
+ }
+
+ for(int i=1; i<argc; i++) {
+
+ if (strcasecmp(argv[i], "-o") == 0) {
+
+ i++;
+ param::smdFileName = argv[i];
+
+ } else if (strcasecmp(argv[i], "-s") == 0) {
+
+ i++;
+ param::scaleFactor = atof(argv[i]);
+
+ } else if (strcasecmp(argv[i], "-tp") == 0) {
+
+ i++;
+ param::texDir = argv[i];
+
+ if( ( param::texDir[param::texDir.size()-1] != '\\' ) &&
+ ( param::texDir[param::texDir.size()-1] != '/') ) {
+
+ param::texDir += "/";
+
+ }
+
+ } else {
+
+ param::smxFileName = argv[i];
+
+ }
+
+ }
+
+ if (param::smdFileName.empty()) {
+
+ param::smdFileName = param::smxFileName;
+ param::smdFileName.erase(param::smdFileName.rfind("."));
+ param::smdFileName += ".smd";
+
+ }
+
+ printf("Input : %s\n", param::smxFileName.c_str());
+ printf("Output : %s\n", param::smdFileName.c_str());
+
+ if (!param::texDir.empty())
+ printf("TexDir : %s\n", param::texDir.c_str());
+
+ printf("\n");
+
+ tinyxml2::XMLDocument smxFile;
+
+ if (smxFile.LoadFile(param::smxFileName.c_str()) != tinyxml2::XML_SUCCESS) {
+ printf("ERROR: Unable to load SMX file:\n");
+ smxFile.PrintError();
+ return EXIT_FAILURE;
+ }
+
+ tinyxml2::XMLElement* smxModel = smxFile.FirstChildElement("model");
+
+ // Parse textures
+ TIM_COORDS *texCoords = NULL;
+ int numTextures = 0;
+
+ if (smxModel->FirstChildElement("textures") != NULL) {
+
+ tinyxml2::XMLElement *texFileElement = smxModel->FirstChildElement("textures");
+
+ if (texFileElement != NULL) {
+
+ numTextures = atoi(texFileElement->Attribute("count"));
+ texFileElement = texFileElement->FirstChildElement("texture");
+
+ texCoords = (TIM_COORDS*)malloc(sizeof(TIM_COORDS)*numTextures);
+
+ int index = 0;
+
+ while(texFileElement != NULL) {
+
+ std::string timFileName = texFileElement->Attribute("file");
+
+ if (timFileName.rfind(".") == std::string::npos)
+ {
+ timFileName.append(".tim");
+ }
+
+ timFileName = param::texDir + timFileName;
+
+ if (!GetTimCoords(timFileName.c_str(), &texCoords[index])) {
+ printf("ERROR: Unable to open texture file: %s\n", timFileName.c_str());
+ free(texCoords);
+ return EXIT_FAILURE;
+ }
+
+ switch(texCoords[index].flag.pmode) {
+ case 0: // 4-bit
+ texCoords[index].pixdata.pw *= 4;
+ break;
+ case 1: // 8-bit
+ texCoords[index].pixdata.pw *= 2;
+ break;
+ }
+
+ texFileElement = texFileElement->NextSiblingElement("texture");
+ index++;
+
+ }
+
+ }
+
+ }
+
+
+ FILE* smdFile = fopen(param::smdFileName.c_str(), "wb");
+
+ // Create temporary header
+ SMD_HEADER smdHeader;
+
+ memset(&smdHeader, 0x00, sizeof(SMD_HEADER));
+ fwrite(&smdHeader, sizeof(SMD_HEADER), 1, smdFile);
+
+
+ // Convert vertices
+ if (smxModel->FirstChildElement("vertices") != NULL) {
+
+ smdHeader.vtxAddr = ftell(smdFile);
+
+ tinyxml2::XMLElement* smxVertices = smxModel->FirstChildElement("vertices");
+ smxVertices = smxVertices->FirstChildElement("v");
+
+ while(smxVertices != NULL) {
+
+ SVECTOR vertex;
+
+ vertex.vx = round(param::scaleFactor * atof(smxVertices->Attribute("x")));
+ vertex.vy = round(param::scaleFactor * atof(smxVertices->Attribute("y")));
+ vertex.vz = round(param::scaleFactor * atof(smxVertices->Attribute("z")));
+ vertex.vp = 0;
+
+ fwrite(&vertex, sizeof(SVECTOR), 1, smdFile);
+ smdHeader.numverts++;
+
+ smxVertices = smxVertices->NextSiblingElement("v");
+
+ }
+
+ smdHeader.flags |= 0x1;
+
+ }
+
+
+ // Convert normals
+ if (smxModel->FirstChildElement("normals") != NULL) {
+
+ smdHeader.nrmAddr = ftell(smdFile);
+
+ tinyxml2::XMLElement* smxVertices = smxModel->FirstChildElement("normals");
+
+ smxVertices = smxVertices->FirstChildElement("v");
+
+ while(smxVertices != NULL) {
+
+ SVECTOR vertex;
+
+ vertex.vx = round(4096 * atof(smxVertices->Attribute("x")));
+ vertex.vy = round(4096 * atof(smxVertices->Attribute("y")));
+ vertex.vz = round(4096 * atof(smxVertices->Attribute("z")));
+ vertex.vp = 0;
+
+ fwrite(&vertex, sizeof(SVECTOR), 1, smdFile);
+ smdHeader.numnorms++;
+
+ smxVertices = smxVertices->NextSiblingElement("v");
+
+ }
+
+ smdHeader.flags |= 0x2;
+
+ } else {
+
+ smdHeader.numnorms = 0;
+
+ }
+
+
+ if (smxModel->FirstChildElement("primitives") != NULL) {
+
+ smdHeader.priAddr = ftell(smdFile);
+
+ tinyxml2::XMLElement* smxPrimitive = smxModel->FirstChildElement("primitives");
+ smxPrimitive = smxPrimitive->FirstChildElement("poly");
+
+ PRIM_ID *prim;
+ char pribuff[40];
+ char* priptr;
+ while(smxPrimitive != NULL) {
+
+ const char* primType = smxPrimitive->Attribute("type");
+
+ if( primType == NULL ) {
+ smxPrimitive = smxPrimitive->NextSiblingElement("poly");
+ continue;
+ }
+
+ memset( pribuff, 0x0, 32 );
+ priptr = pribuff;
+
+ prim = (PRIM_ID*)priptr;
+
+ if( smxPrimitive->IntAttribute( "double", 0 ) )
+ prim->nocull = true;
+
+ if( ( strcasecmp( "F3", primType ) == 0 ) ||
+ ( strcasecmp( "FT3", primType ) == 0 ) ) {
+
+ prim->type = PRIM_TYPE_TRI;
+ prim->len = 4;
+
+ priptr += sizeof(PRIM_ID);
+
+ // Write vertex indices
+ ((PRIM_V*)priptr)->v0 = atoi( smxPrimitive->Attribute( "v0" ) );
+ ((PRIM_V*)priptr)->v1 = atoi( smxPrimitive->Attribute( "v1" ) );
+ ((PRIM_V*)priptr)->v2 = atoi( smxPrimitive->Attribute( "v2" ) );
+ ((PRIM_V*)priptr)->v3 = 0;
+
+ priptr += 8;
+ prim->len += 8;
+
+ if( strcasecmp( "F", smxPrimitive->Attribute( "shading" ) ) == 0 ) {
+
+ prim->l_type = PRIM_LIGHTING_FLAT;
+
+ ((PRIM_V*)priptr)->v0 = atoi( smxPrimitive->Attribute( "n0" ) );
+ ((PRIM_V*)priptr)->v1 = 0;
+ priptr += 4;
+ prim->len += 4;
+
+ } else if( strcasecmp( "S", smxPrimitive->Attribute( "shading" ) ) == 0 ) {
+
+ prim->l_type = PRIM_LIGHTING_SMOOTH;
+
+ ((PRIM_V*)priptr)->v0 = atoi( smxPrimitive->Attribute( "n0" ) );
+ ((PRIM_V*)priptr)->v1 = atoi( smxPrimitive->Attribute( "n1" ) );
+ ((PRIM_V*)priptr)->v2 = atoi( smxPrimitive->Attribute( "n2" ) );
+ ((PRIM_V*)priptr)->v3 = 0;
+
+ priptr += 8;
+ prim->len += 8;
+
+ }
+
+ ((PRIM_RGBC*)priptr)->r = atoi( smxPrimitive->Attribute( "r0" ) );
+ ((PRIM_RGBC*)priptr)->g = atoi( smxPrimitive->Attribute( "g0" ) );
+ ((PRIM_RGBC*)priptr)->b = atoi( smxPrimitive->Attribute( "b0" ) );
+
+ if( smxPrimitive->IntAttribute( "blend" ) > 0 ) {
+ ((PRIM_RGBC*)priptr)->c = 0x2;
+ prim->blend = smxPrimitive->IntAttribute( "blend" )-1;
+ }
+
+ priptr += 4;
+ prim->len += 4;
+
+ if( strcasecmp( "FT3", primType ) == 0 ) { // Textured
+
+ TIM_COORDS *tex;
+ int uoffs,voffs;
+ int texNum = atoi( smxPrimitive->Attribute( "texture" ) );
+
+ if( texNum < 0 ) {
+
+ printf( "ERROR: Primitive with negative texture index encountered.\n" );
+
+ fclose( smdFile );
+
+ if( texCoords != NULL ) {
+ free( texCoords );
+ }
+
+ return EXIT_FAILURE;
+
+ } else if( texNum > numTextures-1 ) {
+
+ printf( "ERROR: Primitive with texture index greater than specified encountered.\n" );
+
+ fclose( smdFile );
+
+ if( texCoords != NULL ) {
+ free( texCoords );
+ }
+
+ return EXIT_FAILURE;
+
+ }
+
+ tex = &texCoords[texNum];
+
+ uoffs = tex->pixdata.px;
+ voffs = tex->pixdata.py&0xff;
+
+ switch(tex->flag.pmode) {
+ case 0: // 4-bit
+ uoffs = (uoffs*4)%256;
+ break;
+ case 1: // 8-bit
+ uoffs = (uoffs*2)%128;
+ break;
+ case 2: // 16-bit
+ uoffs = uoffs%64;
+ break;
+ }
+
+ ((PRIM_UV*)priptr)[0].u = smxPrimitive->IntAttribute( "tu0" )+uoffs;
+ ((PRIM_UV*)priptr)[0].v = smxPrimitive->IntAttribute( "tv0" )+voffs;
+ ((PRIM_UV*)priptr)[1].u = smxPrimitive->IntAttribute( "tu1" )+uoffs;
+ ((PRIM_UV*)priptr)[1].v = smxPrimitive->IntAttribute( "tv1" )+voffs;
+ ((PRIM_UV*)priptr)[2].u = smxPrimitive->IntAttribute( "tu2" )+uoffs;
+ ((PRIM_UV*)priptr)[2].v = smxPrimitive->IntAttribute( "tv2" )+voffs;
+ ((PRIM_UV*)priptr)[3].u = 0;
+ ((PRIM_UV*)priptr)[3].v = 0;
+
+ priptr += 8;
+ prim->len += 8;
+
+ ((PRIM_TC*)priptr)->tpage = GetTPage( tex->flag.pmode,
+ prim->blend, tex->pixdata.px, tex->pixdata.py );
+ ((PRIM_TC*)priptr)->clut = GetClut( tex->clutdata.px, tex->clutdata.py );
+
+ priptr += 4;
+ prim->len += 4;
+
+ prim->texture = true;
+
+ }
+
+ fwrite( pribuff, 1, prim->len, smdFile );
+ smdHeader.numprims++;
+
+ } else if( strcasecmp( "G3", primType ) == 0 ) {
+
+ prim->type = PRIM_TYPE_TRI;
+ prim->len = 4;
+ prim->c_type = 1; // Gouraud
+
+ priptr += sizeof(PRIM_ID);
+
+ // Write vertex indices
+ ((PRIM_V*)priptr)->v0 = atoi( smxPrimitive->Attribute( "v0" ) );
+ ((PRIM_V*)priptr)->v1 = atoi( smxPrimitive->Attribute( "v1" ) );
+ ((PRIM_V*)priptr)->v2 = atoi( smxPrimitive->Attribute( "v2" ) );
+ ((PRIM_V*)priptr)->v3 = 0;
+
+ priptr += 8;
+ prim->len += 8;
+
+ if( strcasecmp( "F", smxPrimitive->Attribute( "shading" ) ) == 0 ) {
+
+ prim->l_type = PRIM_LIGHTING_FLAT;
+
+ ((PRIM_V*)priptr)->v0 = atoi( smxPrimitive->Attribute( "n0" ) );
+ ((PRIM_V*)priptr)->v1 = 0;
+ priptr += 4;
+ prim->len += 4;
+
+ } else if( strcasecmp( "S", smxPrimitive->Attribute( "shading" ) ) == 0 ) {
+
+ prim->l_type = PRIM_LIGHTING_SMOOTH;
+
+ ((PRIM_V*)priptr)->v0 = atoi( smxPrimitive->Attribute( "n0" ) );
+ ((PRIM_V*)priptr)->v1 = atoi( smxPrimitive->Attribute( "n1" ) );
+ ((PRIM_V*)priptr)->v2 = atoi( smxPrimitive->Attribute( "n2" ) );
+ ((PRIM_V*)priptr)->v3 = 0;
+
+ priptr += 8;
+ prim->len += 8;
+
+ }
+
+ ((PRIM_RGBC*)priptr)->r = atoi( smxPrimitive->Attribute( "r0" ) );
+ ((PRIM_RGBC*)priptr)->g = atoi( smxPrimitive->Attribute( "g0" ) );
+ ((PRIM_RGBC*)priptr)->b = atoi( smxPrimitive->Attribute( "b0" ) );
+ if( smxPrimitive->IntAttribute( "blend" ) > 0 ) {
+ ((PRIM_RGBC*)priptr)->c = 0x2;
+ prim->blend = smxPrimitive->IntAttribute( "blend" )-1;
+ }
+ priptr += 4;
+ prim->len += 4;
+
+ // Color 1
+ ((PRIM_RGBC*)priptr)->r = atoi( smxPrimitive->Attribute( "r1" ) );
+ ((PRIM_RGBC*)priptr)->g = atoi( smxPrimitive->Attribute( "g1" ) );
+ ((PRIM_RGBC*)priptr)->b = atoi( smxPrimitive->Attribute( "b1" ) );
+ priptr += 4;
+ prim->len += 4;
+
+ // Color 2
+ ((PRIM_RGBC*)priptr)->r = atoi( smxPrimitive->Attribute( "r2" ) );
+ ((PRIM_RGBC*)priptr)->g = atoi( smxPrimitive->Attribute( "g2" ) );
+ ((PRIM_RGBC*)priptr)->b = atoi( smxPrimitive->Attribute( "b2" ) );
+ priptr += 4;
+ prim->len += 4;
+
+ fwrite( pribuff, 1, prim->len, smdFile );
+ smdHeader.numprims++;
+
+ } else if( ( strcasecmp( "F4", primType ) == 0 ) ||
+ ( strcasecmp( "FT4", primType ) == 0 ) ) {
+
+ prim->type = PRIM_TYPE_QUAD;
+ prim->len = 4;
+
+ priptr += sizeof(PRIM_ID);
+
+ // Write vertex indices
+ ((PRIM_V*)priptr)->v0 = atoi( smxPrimitive->Attribute( "v0" ) );
+ ((PRIM_V*)priptr)->v1 = atoi( smxPrimitive->Attribute( "v1" ) );
+ ((PRIM_V*)priptr)->v2 = atoi( smxPrimitive->Attribute( "v2" ) );
+ ((PRIM_V*)priptr)->v3 = atoi( smxPrimitive->Attribute( "v3" ) );
+
+ priptr += 8;
+ prim->len += 8;
+
+ if( strcasecmp( "F", smxPrimitive->Attribute( "shading" ) ) == 0 ) {
+
+ prim->l_type = PRIM_LIGHTING_FLAT;
+
+ ((PRIM_V*)priptr)->v0 = atoi( smxPrimitive->Attribute( "n0" ) );
+ ((PRIM_V*)priptr)->v1 = 0;
+ priptr += 4;
+ prim->len += 4;
+
+ } else if( strcasecmp( "S", smxPrimitive->Attribute( "shading" ) ) == 0 ) {
+
+ prim->l_type = PRIM_LIGHTING_SMOOTH;
+
+ ((PRIM_V*)priptr)->v0 = atoi( smxPrimitive->Attribute( "n0" ) );
+ ((PRIM_V*)priptr)->v1 = atoi( smxPrimitive->Attribute( "n1" ) );
+ ((PRIM_V*)priptr)->v2 = atoi( smxPrimitive->Attribute( "n2" ) );
+ ((PRIM_V*)priptr)->v3 = atoi( smxPrimitive->Attribute( "n3" ) );
+
+ priptr += 8;
+ prim->len += 8;
+
+ }
+
+ ((PRIM_RGBC*)priptr)->r = atoi( smxPrimitive->Attribute( "r0" ) );
+ ((PRIM_RGBC*)priptr)->g = atoi( smxPrimitive->Attribute( "g0" ) );
+ ((PRIM_RGBC*)priptr)->b = atoi( smxPrimitive->Attribute( "b0" ) );
+
+ if( smxPrimitive->IntAttribute( "blend" ) > 0 ) {
+ ((PRIM_RGBC*)priptr)->c = 0x2;
+ prim->blend = smxPrimitive->IntAttribute( "blend" )-1;
+ }
+
+ priptr += 4;
+ prim->len += 4;
+
+ if( strcasecmp( "FT4", primType ) == 0 ) { // Textured
+
+ TIM_COORDS *tex;
+ int uoffs,voffs;
+ int texNum = atoi( smxPrimitive->Attribute( "texture" ) );
+
+ if( texNum < 0 ) {
+
+ printf( "ERROR: Primitive with negative texture index encountered.\n" );
+
+ fclose( smdFile );
+
+ if( texCoords != NULL ) {
+ free( texCoords );
+ }
+
+ return EXIT_FAILURE;
+
+ } else if( texNum > numTextures-1 ) {
+
+ printf( "ERROR: Primitive with texture index greater than specified encountered.\n" );
+
+ fclose( smdFile );
+
+ if( texCoords != NULL ) {
+ free( texCoords );
+ }
+
+ return EXIT_FAILURE;
+
+ }
+
+ tex = &texCoords[texNum];
+
+ uoffs = tex->pixdata.px;
+ voffs = tex->pixdata.py&0xff;
+
+ switch(tex->flag.pmode) {
+ case 0: // 4-bit
+ uoffs = (uoffs*4)%256;
+ break;
+ case 1: // 8-bit
+ uoffs = (uoffs*2)%128;
+ break;
+ case 2: // 16-bit
+ uoffs = uoffs%64;
+ break;
+ }
+
+ ((PRIM_UV*)priptr)[0].u = smxPrimitive->IntAttribute( "tu0" )+uoffs;
+ ((PRIM_UV*)priptr)[0].v = smxPrimitive->IntAttribute( "tv0" )+voffs;
+ ((PRIM_UV*)priptr)[1].u = smxPrimitive->IntAttribute( "tu1" )+uoffs;
+ ((PRIM_UV*)priptr)[1].v = smxPrimitive->IntAttribute( "tv1" )+voffs;
+ ((PRIM_UV*)priptr)[2].u = smxPrimitive->IntAttribute( "tu2" )+uoffs;
+ ((PRIM_UV*)priptr)[2].v = smxPrimitive->IntAttribute( "tv2" )+voffs;
+ ((PRIM_UV*)priptr)[3].u = smxPrimitive->IntAttribute( "tu3" )+uoffs;
+ ((PRIM_UV*)priptr)[3].v = smxPrimitive->IntAttribute( "tv3" )+voffs;
+
+ priptr += 8;
+ prim->len += 8;
+
+ ((PRIM_TC*)priptr)->tpage = GetTPage( tex->flag.pmode,
+ prim->blend, tex->pixdata.px, tex->pixdata.py );
+ ((PRIM_TC*)priptr)->clut = GetClut( tex->clutdata.px, tex->clutdata.py );
+
+ priptr += 4;
+ prim->len += 4;
+
+ prim->texture = true;
+
+ }
+
+ fwrite( pribuff, 1, prim->len, smdFile );
+ smdHeader.numprims++;
+
+ } else if( strcasecmp( "G4", primType ) == 0 ) {
+
+ prim->type = PRIM_TYPE_QUAD;
+ prim->len = 4;
+ prim->c_type = 1; // Gouraud
+
+ priptr += sizeof(PRIM_ID);
+
+ // Write vertex indices
+ ((PRIM_V*)priptr)->v0 = atoi( smxPrimitive->Attribute( "v0" ) );
+ ((PRIM_V*)priptr)->v1 = atoi( smxPrimitive->Attribute( "v1" ) );
+ ((PRIM_V*)priptr)->v2 = atoi( smxPrimitive->Attribute( "v2" ) );
+ ((PRIM_V*)priptr)->v3 = atoi( smxPrimitive->Attribute( "v3" ) );
+
+ priptr += 8;
+ prim->len += 8;
+
+ if( strcasecmp( "F", smxPrimitive->Attribute( "shading" ) ) == 0 ) {
+
+ prim->l_type = PRIM_LIGHTING_FLAT;
+
+ ((PRIM_V*)priptr)->v0 = atoi( smxPrimitive->Attribute( "n0" ) );
+ ((PRIM_V*)priptr)->v1 = 0;
+ priptr += 4;
+ prim->len += 4;
+
+ } else if( strcasecmp( "S", smxPrimitive->Attribute( "shading" ) ) == 0 ) {
+
+ prim->l_type = PRIM_LIGHTING_SMOOTH;
+
+ ((PRIM_V*)priptr)->v0 = atoi( smxPrimitive->Attribute( "n0" ) );
+ ((PRIM_V*)priptr)->v1 = atoi( smxPrimitive->Attribute( "n1" ) );
+ ((PRIM_V*)priptr)->v2 = atoi( smxPrimitive->Attribute( "n2" ) );
+ ((PRIM_V*)priptr)->v3 = atoi( smxPrimitive->Attribute( "n3" ) );
+
+ priptr += 8;
+ prim->len += 8;
+
+ }
+
+ // Color 0
+ ((PRIM_RGBC*)priptr)->r = atoi( smxPrimitive->Attribute( "r0" ) );
+ ((PRIM_RGBC*)priptr)->g = atoi( smxPrimitive->Attribute( "g0" ) );
+ ((PRIM_RGBC*)priptr)->b = atoi( smxPrimitive->Attribute( "b0" ) );
+
+ if( smxPrimitive->IntAttribute( "blend" ) > 0 ) {
+ ((PRIM_RGBC*)priptr)->c = 0x2;
+ prim->blend = smxPrimitive->IntAttribute( "blend" )-1;
+ }
+
+ priptr += 4;
+ prim->len += 4;
+
+ // Color 1
+ ((PRIM_RGBC*)priptr)->r = atoi( smxPrimitive->Attribute( "r1" ) );
+ ((PRIM_RGBC*)priptr)->g = atoi( smxPrimitive->Attribute( "g1" ) );
+ ((PRIM_RGBC*)priptr)->b = atoi( smxPrimitive->Attribute( "b1" ) );
+ ((PRIM_RGBC*)priptr)->c = 0;
+ priptr += 4;
+ prim->len += 4;
+
+ // Color 2
+ ((PRIM_RGBC*)priptr)->r = atoi( smxPrimitive->Attribute( "r2" ) );
+ ((PRIM_RGBC*)priptr)->g = atoi( smxPrimitive->Attribute( "g2" ) );
+ ((PRIM_RGBC*)priptr)->b = atoi( smxPrimitive->Attribute( "b2" ) );
+ ((PRIM_RGBC*)priptr)->c = 0;
+ priptr += 4;
+ prim->len += 4;
+
+ // Color 3
+ ((PRIM_RGBC*)priptr)->r = atoi( smxPrimitive->Attribute( "r3" ) );
+ ((PRIM_RGBC*)priptr)->g = atoi( smxPrimitive->Attribute( "g3" ) );
+ ((PRIM_RGBC*)priptr)->b = atoi( smxPrimitive->Attribute( "b3" ) );
+ ((PRIM_RGBC*)priptr)->c = 0;
+ priptr += 4;
+ prim->len += 4;
+
+ fwrite( pribuff, 1, prim->len, smdFile );
+ smdHeader.numprims++;
+
+ } else {
+
+ printf( "ERROR: Unknown or unsupported primitive type: %s\n",
+ primType );
+
+ fclose( smdFile );
+
+ if( texCoords != NULL ) {
+ free( texCoords );
+ }
+
+ return EXIT_FAILURE;
+
+ }
+
+ smxPrimitive = smxPrimitive->NextSiblingElement("poly");
+
+ }
+
+ {
+ int term = 0;
+ fwrite( &term, 1, 4, smdFile );
+ }
+
+ /*
+ while(smxPrimitive != NULL) {
+
+ bool polyBlended = false;
+ const char* primType = smxPrimitive->Attribute("type");
+
+ memset(&prim, 0x00, sizeof(PRIM_ID));
+
+ if (strcasecmp("F", smxPrimitive->Attribute("shading")) == 0) {
+
+ prim.lighting = PRIM_LIGHTING_FLAT;
+
+ } else if (strcasecmp("S", smxPrimitive->Attribute("shading")) == 0) {
+
+ prim.lighting = PRIM_LIGHTING_SMOOTH;
+
+ }
+
+ // 3-point polygons
+ if ((strcasecmp("F3", primType) == 0) ||
+ (strcasecmp("G3", primType) == 0) ||
+ (strcasecmp("FT3", primType) == 0) ||
+ (strcasecmp("GT3", primType) == 0)) {
+
+ if (strcasecmp("F3", primType) == 0) {
+
+ prim.type = PRIM_TYPE_TRI;
+ prim.len = 20;
+
+ } else if (strcasecmp("G3", primType) == 0) {
+
+ prim.type = PRIM_TYPE_TRI;
+ prim.gouraud = true;
+ prim.len = 28;
+
+ } else if (strcasecmp("FT3", primType) == 0) {
+
+ prim.type = PRIM_TYPE_TRI;
+ prim.textured = true;
+ prim.len = 32;
+
+ } else if (strcasecmp("GT3", primType) == 0) {
+
+ prim.type = PRIM_TYPE_TRI;
+ prim.gouraud = true;
+ prim.textured = true;
+ prim.len = 42;
+
+ }
+
+ if ( smxPrimitive->IntAttribute("blendmode") > 0 ) {
+
+ polyBlended = true;
+ prim.blendmode = smxPrimitive->IntAttribute("blendmode")-1;
+
+ }
+
+ if ( smxPrimitive->IntAttribute("doublesided") ) {
+
+ prim.noculling = true;
+
+ }
+
+ PRIM_V3 vert;
+
+ vert.v0 = atoi(smxPrimitive->Attribute("v0"));
+ vert.v1 = atoi(smxPrimitive->Attribute("v1"));
+ vert.v2 = atoi(smxPrimitive->Attribute("v2"));
+
+ if ((vert.v0 < 0) || (vert.v1 < 0) || (vert.v2 < 0)) {
+
+ printf("ERROR: Primitive with negative vertex index encountered.\n");
+
+ fclose(smdFile);
+
+ if (texCoords != NULL)
+ free(texCoords);
+
+ return EXIT_FAILURE;
+
+ }
+
+ PRIM_V3 norm = { 0 };
+
+ if (prim.lighting == PRIM_LIGHTING_FLAT) {
+
+ norm.v0 = atoi(smxPrimitive->Attribute("n0"));
+ prim.len += 4;
+
+ } else if (prim.lighting == PRIM_LIGHTING_SMOOTH) {
+
+ norm.v0 = atoi(smxPrimitive->Attribute("n0"));
+ norm.v1 = atoi(smxPrimitive->Attribute("n1"));
+ norm.v2 = atoi(smxPrimitive->Attribute("n2"));
+ prim.len += 12;
+
+ }
+
+ if ((norm.v0 < 0) || (norm.v1 < 0) || (norm.v2 < 0)) {
+
+ printf("ERROR: Primitive with negative normal index encountered.\n");
+
+ fclose(smdFile);
+
+ if (texCoords != NULL)
+ free(texCoords);
+
+ return EXIT_FAILURE;
+
+ }
+
+ fwrite(&prim, sizeof(PRIM_ID), 1, smdFile);
+ fwrite(&vert, sizeof(PRIM_V3), 1, smdFile);
+
+ if (prim.lighting == PRIM_LIGHTING_FLAT) {
+ fwrite(&norm, 4, 1, smdFile);
+ } else if (prim.lighting == PRIM_LIGHTING_SMOOTH) {
+ fwrite(&norm, sizeof(PRIM_V3), 1, smdFile);
+ }
+
+ PRIM_RGBC col;
+
+ col.r = atoi(smxPrimitive->Attribute("r0"));
+ col.g = atoi(smxPrimitive->Attribute("g0"));
+ col.b = atoi(smxPrimitive->Attribute("b0"));
+ col.c = 0x00;
+
+ if (polyBlended) {
+ col.c |= 0x2;
+ }
+
+ fwrite(&col, sizeof(PRIM_RGBC), 1, smdFile);
+
+ if ((strcasecmp("G3", primType) == 0) ||
+ (strcasecmp("GT3", primType) == 0)) {
+
+ col.r = atoi(smxPrimitive->Attribute("r1"));
+ col.g = atoi(smxPrimitive->Attribute("g1"));
+ col.b = atoi(smxPrimitive->Attribute("b1"));
+ col.c = 0x00;
+
+ fwrite(&col, sizeof(PRIM_RGBC), 1, smdFile);
+
+ col.r = atoi(smxPrimitive->Attribute("r2"));
+ col.g = atoi(smxPrimitive->Attribute("g2"));
+ col.b = atoi(smxPrimitive->Attribute("b2"));
+ col.c = 0x00;
+
+ fwrite(&col, sizeof(PRIM_RGBC), 1, smdFile);
+
+ }
+
+ if ((strcasecmp("FT3", primType) == 0) || (strcasecmp("GT3", primType) == 0)) {
+
+ PRIM_TC tc;
+ PRIM_UV uvcd;
+
+ int texNum = atoi(smxPrimitive->Attribute("texture"));
+
+ if (texNum < 0) {
+
+ printf("ERROR: Primitive with negative texture index encountered.\n");
+
+ fclose(smdFile);
+
+ if (texCoords != NULL)
+ free(texCoords);
+
+ return EXIT_FAILURE;
+
+ } else if (texNum > numTextures-1) {
+
+ printf("ERROR: Primitive with texture index greater than specified encountered.\n");
+
+ fclose(smdFile);
+
+ if (texCoords != NULL)
+ free(texCoords);
+
+ return EXIT_FAILURE;
+
+ }
+
+ TIM_COORDS *tex = &texCoords[texNum];
+
+ int uoffs = tex->pixdata.px;
+ int voffs = tex->pixdata.py&0xff;
+
+ switch(tex->flag.pmode) {
+ case 0: // 4-bit
+ uoffs = (uoffs*4)%256;
+ break;
+ case 1: // 8-bit
+ uoffs = (uoffs*2)%128;
+ break;
+ case 2: // 16-bit
+ uoffs = uoffs%64;
+ break;
+ }
+
+ // TPAGE and CLUT
+ tc.tpage = GetTPage( tex->flag.pmode, prim.blendmode,
+ tex->pixdata.px, tex->pixdata.py );
+ tc.clut = GetClut( tex->clutdata.px, tex->clutdata.py );
+ fwrite(&tc, sizeof(PRIM_TC), 1, smdFile);
+
+ // Texcoords
+ uvcd.u = smxPrimitive->IntAttribute("tu0")+uoffs;
+ uvcd.v = smxPrimitive->IntAttribute("tv0")+voffs;
+ fwrite(&uvcd, sizeof(PRIM_UV), 1, smdFile);
+
+ uvcd.u = smxPrimitive->IntAttribute("tu1")+uoffs;
+ uvcd.v = smxPrimitive->IntAttribute("tv1")+voffs;
+ fwrite(&uvcd, sizeof(PRIM_UV), 1, smdFile);
+
+ uvcd.u = smxPrimitive->IntAttribute("tu2")+uoffs;
+ uvcd.v = smxPrimitive->IntAttribute("tv2")+voffs;
+ fwrite(&uvcd, sizeof(PRIM_UV), 1, smdFile);
+
+ // Padding
+ uvcd.u = uvcd.v = 0;
+ fwrite(&uvcd, sizeof(PRIM_UV), 1, smdFile);
+
+ }
+
+ } else {
+
+ printf("Unsupported primitive %s, ignoring...\n", primType);
+
+ }
+
+ smxPrimitive = smxPrimitive->NextSiblingElement("poly");
+
+ }
+
+ // Write terminator
+ memset(&prim, 0x00, sizeof(PRIM_ID));
+ fwrite(&prim, sizeof(PRIM_ID), 1, smdFile);
+ */
+
+ }
+
+
+ strcpy(smdHeader.id, "SMD");
+ smdHeader.version = 1;
+
+ fseek(smdFile, 0, SEEK_SET);
+ fwrite(&smdHeader, sizeof(SMD_HEADER), 1, smdFile);
+
+ fclose(smdFile);
+
+ if (texCoords != NULL)
+ free(texCoords);
+
+ printf("Converted successfully.\n");
+
+ return EXIT_SUCCESS;
+
+}
diff --git a/tools/smxlink/makefile b/tools/smxlink/makefile
new file mode 100644
index 0000000..b50fd26
--- /dev/null
+++ b/tools/smxlink/makefile
@@ -0,0 +1,42 @@
+TARGET := smxlink
+
+CPPFILES = main.cpp timreader.cpp
+CFLAGS = -O2
+LDFLAGS = -s
+
+LIBS = -ltinyxml2
+
+CC = gcc
+CXX = g++
+
+OFILES = $(addprefix build/,$(CPPFILES:.cpp=.o))
+
+ifeq "$(OS)" "Windows_NT"
+
+# Config for Windows
+INCLUDE = -I/c/tinyxml2
+LIBDIRS = -L/c/tinyxml2
+TARGET := $(TARGET).exe
+
+else
+
+# Config for anything else that isn't Windows
+EXE_SUFFIX =
+
+endif
+
+build/%.o: %.cpp
+ @mkdir -p $(dir $@)
+ $(CXX) $(CFLAGS) $(INCLUDE) -c $< -o $@
+
+all: $(OFILES)
+ $(CXX) $(CFLAGS) $(LDFLAGS) $(LIBDIRS) $(OFILES) $(LIBS) -o $(TARGET)
+
+install:
+ mkdir -p ../bin
+ cp $(TARGET) ../bin/$(TARGET)
+
+clean:
+ rm -Rf build $(TARGET)
+
+cleanall: clean
diff --git a/tools/smxlink/timreader.cpp b/tools/smxlink/timreader.cpp
new file mode 100644
index 0000000..a8fba94
--- /dev/null
+++ b/tools/smxlink/timreader.cpp
@@ -0,0 +1,65 @@
+#include <stdio.h>
+#include <string.h>
+#include "timreader.h"
+
+int GetTimCoords(const char* fileName, TIM_COORDS *coords) {
+
+ FILE* fp = fopen(fileName, "rb");
+
+ if (fp == NULL)
+ return false;
+
+
+ unsigned int id;
+
+ fread(&id, 4, 1, fp);
+
+ if (id != 0x00000010) {
+
+ fclose(fp);
+ return false;
+
+ }
+
+ fread(&coords->flag, 4, 1, fp);
+
+ if (coords->flag.cf) {
+
+ fread(&coords->clutdata, 12, 1, fp);
+ fseek(fp, coords->clutdata.length-12, SEEK_CUR);
+
+ } else {
+
+ memset(&coords->clutdata, 0x00, 12);
+
+ }
+
+ fread(&coords->pixdata, 12, 1, fp);
+
+ fclose(fp);
+
+ return true;
+
+}
+
+unsigned short GetClut(int cx, int cy) {
+
+ unsigned short clut = (cx/16)&0x3f;
+ clut |= (cy&0x1ff)<<6;
+
+ return clut;
+
+}
+
+unsigned short GetTPage(int tp, int abr, int x, int y) {
+
+ unsigned short tpage = (x/64)&0xf; // Set X
+ tpage |= ((y/256)&0x1)<<4; // Set Y
+
+ tpage |= (abr&0x3)<<5; // Set blend mode
+ tpage |= (tp&0x3)<<7; // Set page mode
+ tpage |= 1<<9; // Set dither processing bit
+
+ return tpage;
+
+}
diff --git a/tools/smxlink/timreader.h b/tools/smxlink/timreader.h
new file mode 100644
index 0000000..a1a48b3
--- /dev/null
+++ b/tools/smxlink/timreader.h
@@ -0,0 +1,28 @@
+#ifndef _TIMREADER_H
+#define _TIMREADER_H
+
+typedef struct {
+ struct {
+ unsigned int pmode:3; // Pixel mode (0: 4-bit, 1: 8-bit, 2: 16-bit, 3: 24-bit)
+ unsigned int cf:1; // CLUT flag (if 1, CLUT is present)
+ unsigned int reserved:28;
+ } flag;
+ struct {
+ unsigned int length;
+ unsigned short px,py;
+ unsigned short pw,ph;
+ } clutdata;
+ struct {
+ unsigned int length;
+ unsigned short px,py;
+ unsigned short pw,ph;
+ } pixdata;
+} TIM_COORDS;
+
+int GetTimCoords(const char *fileName, TIM_COORDS *coords);
+
+unsigned short GetClut(int cx, int cy);
+
+unsigned short GetTPage(int tp, int abr, int x, int y);
+
+#endif // _TIMREADER_H
diff --git a/tools/tools.txt b/tools/tools.txt
new file mode 100644
index 0000000..f701308
--- /dev/null
+++ b/tools/tools.txt
@@ -0,0 +1,30 @@
+To build the tools, simply run 'make all install' in this directory. The
+binaries of the tools should reside in a directory named bin for your
+convenience.
+
+
+Brief tools summary:
+
+lzpack - File compression and packing utility for creating LZP, PCK and
+ QLP archive files. Depends on tinyxml2.
+
+smxlink - SMX to SMD linker tool (from Project Scarlet/Scarlet Engine).
+ SMD drawing and parsing code can be found in the n00bdemo example.
+ Depends on tinyxml2.
+
+plugins - Includes a plugin for exporting models into Project Scarlet/Scarlet
+ Engine SMX model data format.
+
+util - A collection of small single C or C++ file tools such as elf2x.
+
+
+Other tools you may want:
+
+img2tim - Image to TIM texture file converter powered by FreeImage.
+ https://github.com/Lameguy64/img2tim
+
+smxtool - SMX model data tweaking tool.
+ https://github.com/Lameguy64/smxtool
+
+mkpsxiso - PlayStation ISO creation tool with CD-DA, CD-XA and STR file support.
+ https://github.com/Lameguy64/mkpsxiso \ No newline at end of file
diff --git a/tools/util/elf2x.c b/tools/util/elf2x.c
new file mode 100644
index 0000000..940e4a1
--- /dev/null
+++ b/tools/util/elf2x.c
@@ -0,0 +1,300 @@
+// Originally written in C++ by Lameguy64
+// Ported to plain C by Orion
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define MAX_prg_entry_count 128
+#define true (1)
+#define false (0)
+
+#pragma pack(push, 1)
+
+typedef struct {
+
+ unsigned int magic; // 0-3
+ unsigned char word_size; // 4
+ unsigned char endianness; // 5
+ unsigned char elf_version; // 6
+ unsigned char os_abi; // 7
+ unsigned int unused[2]; // 8-15
+
+ unsigned short type; // 16-17
+ unsigned short instr_set; // 18-19
+ unsigned int elf_version2; // 20-23
+
+ unsigned int prg_entry_addr; // 24-27
+ unsigned int prg_head_pos; // 28-31
+ unsigned int sec_head_pos; // 32-35
+ unsigned int flags; // 36-39
+ unsigned short head_size; // 40-41
+ unsigned short prg_entry_size; // 42-23
+ unsigned short prg_entry_count; // 44-45
+ unsigned short sec_entry_size; // 46-47
+ unsigned short sec_entry_count; // 48-49
+ unsigned short sec_names_index; // 50-51
+
+} ELF_HEADER;
+
+typedef struct {
+ unsigned int seg_type;
+ unsigned int p_offset;
+ unsigned int p_vaddr;
+ unsigned int undefined;
+ unsigned int p_filesz;
+ unsigned int p_memsz;
+ unsigned int flags;
+ unsigned int alignment;
+} PRG_HEADER;
+
+#pragma pack(pop)
+
+typedef struct {
+ unsigned int pc0;
+ unsigned int gp0;
+ unsigned int t_addr;
+ unsigned int t_size;
+ unsigned int d_addr;
+ unsigned int d_size;
+ unsigned int b_addr;
+ unsigned int b_size;
+ unsigned int sp_addr;
+ unsigned int sp_size;
+ unsigned int sp;
+ unsigned int fp;
+ unsigned int gp;
+ unsigned int ret;
+ unsigned int base;
+} EXEC;
+
+typedef struct {
+ char header[8];
+ char pad[8];
+ EXEC params;
+ char license[64];
+ char pad2[1908];
+} PSEXE;
+
+int main(int argc, char** argv) {
+
+ char* in_file = NULL;
+ char* out_file = NULL;
+ int quiet = false;
+ int i;
+ FILE* fp;
+ ELF_HEADER head;
+ PRG_HEADER prg_heads[MAX_prg_entry_count];
+ unsigned int exe_taddr = 0xffffffff;
+ unsigned int exe_haddr = 0;
+ unsigned int exe_tsize = 0;
+ unsigned char* binary;
+ PSEXE exe;
+ char *output_name;
+
+ for( i=1; i<argc; i++ ) {
+
+ if( strcasecmp( "-q", argv[i] ) == 0 ) {
+
+ quiet = true;
+
+ } else {
+
+ if( in_file == NULL ) {
+ in_file = argv[i];
+ } else if( out_file == NULL ) {
+ out_file = argv[i];
+ }
+
+ }
+
+ }
+
+ if( !quiet ) {
+ printf( "PSn00bSDK elf2x - ELF to PS-EXE Converter\n" );
+ printf( "2018-2019 Meido-Tek Productions\n\n" );
+ }
+
+ if( argc == 1 ) {
+ printf( "Usage:\n" );
+ printf( " elf2x [-q] <elf_file> [exe_file]\n" );
+ return 0;
+ }
+
+ if( in_file == NULL ) {
+ printf( "No input file specified.\n" );
+ return EXIT_FAILURE;
+ }
+
+
+ fp = fopen( in_file, "rb" );
+
+ if( fp == NULL ) {
+ printf( "Cannot open file %s.\n", in_file );
+ return EXIT_FAILURE;
+ }
+
+ fread( &head, 1, sizeof(head), fp );
+
+
+ // Check header
+ if( head.magic != 0x464c457f ) {
+ printf( "File is not an ELF file.\n" );
+ return EXIT_FAILURE;
+ }
+
+ if( head.type != 2 ) {
+ printf( "Only executable ELF files are supported.\n" );
+ fclose( fp );
+ return EXIT_FAILURE;
+ }
+
+ if( head.instr_set != 8 ) {
+ printf( "ELF file is not a MIPS binary.\n" );
+ fclose( fp );
+ return EXIT_FAILURE;
+ }
+
+ if( head.word_size != 1 ) {
+ printf( "Only 32-bit ELF files are supported.\n" );
+ fclose( fp );
+ return EXIT_FAILURE;
+ }
+
+ if( head.endianness != 1 ) {
+ printf( "Only little endian ELF files are supported.\n" );
+ fclose( fp );
+ return EXIT_FAILURE;
+ }
+
+
+ // Load program headers and determine binary size and load address
+
+ fseek( fp, head.prg_head_pos, SEEK_SET );
+ for( i=0; i<head.prg_entry_count; i++ ) {
+
+ fread( &prg_heads[i], 1, sizeof(PRG_HEADER), fp );
+
+ if( prg_heads[i].flags == 4 ) {
+ continue;
+ }
+
+ if( prg_heads[i].p_vaddr < exe_taddr ) {
+ exe_taddr = prg_heads[i].p_vaddr;
+ }
+
+ if( prg_heads[i].p_vaddr > exe_haddr ) {
+ exe_haddr = prg_heads[i].p_vaddr;
+ }
+
+ }
+
+ exe_tsize = (exe_haddr-exe_taddr);
+ exe_tsize += prg_heads[head.prg_entry_count-1].p_filesz;
+
+ if( !quiet ) {
+
+ printf( "pc:%08x t_addr:%08x t_size:%d\n",
+ head.prg_entry_addr, exe_taddr, exe_tsize );
+
+ }
+
+ // Check if load address is appropriate in main RAM locations
+ if( ( ( exe_taddr>>24 ) == 0x0 ) || ( ( exe_taddr>>24 ) == 0x80 ) ||
+ ( ( exe_taddr>>24 ) == 0xA0 ) ) {
+
+ if( ( exe_taddr&0x00ffffff ) < 65536 ) {
+
+ printf( "Warning: Program text address overlaps kernel area!\n" );
+
+ }
+
+ }
+
+
+ // Pad out the size to multiples of 2KB
+ exe_tsize = 2048*((exe_tsize+2047)/2048);
+
+ // Load the binary data
+ binary = (unsigned char*)malloc( exe_tsize );
+ memset( binary, 0x0, exe_tsize );
+
+ for( i=0; i<head.prg_entry_count; i++ ) {
+
+ if( prg_heads[i].flags == 4 ) {
+ continue;
+ }
+
+ fseek( fp, prg_heads[i].p_offset, SEEK_SET );
+ fread( &binary[(int)(prg_heads[i].p_vaddr-exe_taddr)],
+ 1, prg_heads[i].p_filesz, fp );
+
+ }
+
+ fclose( fp );
+
+
+ if( out_file ) {
+
+ output_name = out_file;
+
+ } else {
+ char *ptr;
+
+ // Generate output filename if no output is specified
+ output_name = in_file;
+
+ ptr = &output_name[strlen(output_name)];
+ while (ptr != output_name)
+ {
+ if (*ptr == '.')
+ break;
+ else
+ ptr--;
+ }
+
+ if (ptr != output_name)
+ {
+ strcpy(ptr, ".exe");
+ }
+ else
+ {
+ strcat(ptr, ".exe");
+ }
+ }
+
+
+ // Prepare PS-EXE header
+ memset( &exe, 0, sizeof(PSEXE) );
+
+ exe.params.sp_addr = 0x801FFFF0;
+ exe.params.t_addr = exe_taddr;
+ exe.params.t_size = exe_tsize;
+ exe.params.pc0 = head.prg_entry_addr;
+
+ strncpy( exe.header, "PS-X EXE", 8 );
+ strcpy( exe.license,
+ "Not Licensed or Endorsed by Sony Computer Entertainment Inc." );
+ strcpy( exe.pad2, "Built using GCC and PSn00bSDK libraries" );
+
+
+ // Write file
+ fp = fopen( output_name, "wb" );
+
+ if( !fp ) {
+ printf( "Cannot write output: %s\n", output_name );
+ free( binary );
+ fclose( fp );
+ }
+
+ fwrite( &exe, 1, sizeof( PSEXE ), fp );
+ fwrite( binary, 1, exe_tsize, fp );
+
+ fclose( fp );
+
+
+ free( binary );
+
+ return 0;
+}
+
diff --git a/tools/util/makefile b/tools/util/makefile
new file mode 100644
index 0000000..c49a581
--- /dev/null
+++ b/tools/util/makefile
@@ -0,0 +1,19 @@
+CFLAGS = -O2 -s
+
+CC = gcc
+
+ifeq "$(OS)" "Windows_NT"
+EXE_SUFFIX = .exe
+else
+EXE_SUFFIX =
+endif
+
+all:
+ $(CC) $(CFLAGS) elf2x.c -o elf2x$(EXE_SUFFIX)
+
+install:
+ mkdir -p ../bin
+ cp elf2x$(EXE_SUFFIX) ../bin/elf2x$(EXE_SUFFIX)
+
+clean:
+ rm -f elf2x$(EXE_SUFFIX)