diff options
| author | John Wilbert M. Villamor <lameguy64@gmail.com> | 2019-04-06 10:11:07 +0800 |
|---|---|---|
| committer | John Wilbert M. Villamor <lameguy64@gmail.com> | 2019-04-06 10:11:07 +0800 |
| commit | f3e040230772f978540a71aea43dfde200992922 (patch) | |
| tree | bd8ca31b72dd01e24980b073854e263589530f56 /tools | |
| download | psn00bsdk-f3e040230772f978540a71aea43dfde200992922.tar.gz | |
First commit
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/lzpack/filelist.cpp | 79 | ||||
| -rw-r--r-- | tools/lzpack/filelist.h | 43 | ||||
| -rw-r--r-- | tools/lzpack/lzp/lzconfig.h | 68 | ||||
| -rw-r--r-- | tools/lzpack/lzp/makefile | 30 | ||||
| -rw-r--r-- | tools/lzpack/main.cpp | 615 | ||||
| -rw-r--r-- | tools/lzpack/makefile | 46 | ||||
| -rw-r--r-- | tools/makefile | 12 | ||||
| -rw-r--r-- | tools/plugin/io_export_smx_v3.py | 320 | ||||
| -rw-r--r-- | tools/smxlink/main.cpp | 1003 | ||||
| -rw-r--r-- | tools/smxlink/makefile | 42 | ||||
| -rw-r--r-- | tools/smxlink/timreader.cpp | 65 | ||||
| -rw-r--r-- | tools/smxlink/timreader.h | 28 | ||||
| -rw-r--r-- | tools/tools.txt | 30 | ||||
| -rw-r--r-- | tools/util/elf2x.c | 300 | ||||
| -rw-r--r-- | tools/util/makefile | 19 |
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) |
