mirror of
https://github.com/izzy2lost/Diddy-Kong-Racing.git
synced 2026-06-19 01:16:26 -07:00
Updated with new tools and texture generation
This commit is contained in:
+3
-1
@@ -1 +1,3 @@
|
||||
/n64crc
|
||||
/n64crc
|
||||
/dkr_extractor
|
||||
/dkr_texbuilder
|
||||
+14
-1
@@ -2,12 +2,19 @@ CC := gcc
|
||||
CXX := g++
|
||||
CFLAGS := -I . -Wall -Wextra -Wno-unused-parameter -pedantic -std=c99 -O2 -s
|
||||
LDFLAGS := -lm
|
||||
PROGRAMS := n64crc
|
||||
PROGRAMS := n64crc
|
||||
CXX_PROGRAMS := dkr_extractor dkr_texbuilder
|
||||
|
||||
default: all
|
||||
|
||||
n64crc_SOURCES := n64crc.c
|
||||
|
||||
dkr_extractor_SOURCES := dkr_extractor.cpp $(wildcard dkr_decompressor_src/*.cpp dkr_decompressor_src/*.c dkr_extractor_classes/*.cpp n64graphics/*.c)
|
||||
dkr_extractor_CXXFLAGS := -lstdc++fs -lcrypto -lssl
|
||||
|
||||
dkr_texbuilder_SOURCES := dkr_texbuilder.cpp $(wildcard dkr_decompressor_src/*.cpp dkr_decompressor_src/*.c n64graphics/*.c)
|
||||
dkr_texbuilder_CXXFLAGS := -lstdc++fs
|
||||
|
||||
all: $(PROGRAMS) $(CXX_PROGRAMS)
|
||||
|
||||
clean:
|
||||
@@ -18,6 +25,12 @@ $(1): $($1_SOURCES)
|
||||
$(CC) $(CFLAGS) $($1_CFLAGS) $$^ -o $$@ $(LDFLAGS) $($1_LDFLAGS)
|
||||
endef
|
||||
|
||||
define COMPILE_CXX
|
||||
$(1): $($1_SOURCES)
|
||||
$(CXX) $$^ -o $$@ $($1_CXXFLAGS) $(LDFLAGS) $($1_LDFLAGS)
|
||||
endef
|
||||
|
||||
$(foreach p,$(PROGRAMS),$(eval $(call COMPILE,$(p))))
|
||||
$(foreach p,$(CXX_PROGRAMS),$(eval $(call COMPILE_CXX,$(p))))
|
||||
|
||||
.PHONY: all clean default
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
#include "DKRCompression.h"
|
||||
|
||||
DKRCompression::DKRCompression()
|
||||
{
|
||||
compressed.SetGame(DKR);
|
||||
}
|
||||
|
||||
DKRCompression::~DKRCompression(void)
|
||||
{
|
||||
}
|
||||
|
||||
std::vector<uint8_t> DKRCompression::compressBuffer(std::vector<uint8_t>& data)
|
||||
{
|
||||
int uncompressedSize = data.size();
|
||||
int compressedSize = 0;
|
||||
|
||||
uint8_t* compressed = dkr_gzip_compress(&data[0], uncompressedSize, 9, &compressedSize);
|
||||
|
||||
std::vector<uint8_t> dkr_compressed(compressed + 10, compressed + compressedSize - 8);
|
||||
|
||||
dkr_compressed.insert(dkr_compressed.begin(), 0x09); // gzip compression level?
|
||||
dkr_compressed.insert(dkr_compressed.begin(), (uncompressedSize >> 24) & 0xFF);
|
||||
dkr_compressed.insert(dkr_compressed.begin(), (uncompressedSize >> 16) & 0xFF);
|
||||
dkr_compressed.insert(dkr_compressed.begin(), (uncompressedSize >> 8) & 0xFF);
|
||||
dkr_compressed.insert(dkr_compressed.begin(), uncompressedSize & 0xFF);
|
||||
|
||||
while((dkr_compressed.size() & 0xF) != 0) {
|
||||
dkr_compressed.push_back(0); // Pad out compressed file to be 16-byte aligned.
|
||||
}
|
||||
|
||||
return dkr_compressed;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> DKRCompression::decompressBuffer(std::vector<uint8_t>& data)
|
||||
{
|
||||
compressed.SetCompressedBuffer(&data[0], data.size());
|
||||
|
||||
int fileSize = 0, fileSizeCompressed = 0;
|
||||
uint8_t* decompressed = compressed.OutputDecompressedBuffer(fileSize, fileSizeCompressed);
|
||||
if(decompressed == NULL) {
|
||||
std::cout << "Error decompressing input" << std::endl;
|
||||
throw 1;
|
||||
}
|
||||
std::vector<uint8_t> out(decompressed, decompressed + fileSize);
|
||||
delete[] decompressed;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
bool DKRCompression::writeBinaryFile(std::vector<uint8_t>& output, std::string filename)
|
||||
{
|
||||
try
|
||||
{
|
||||
std::ofstream wf(filename.c_str(), std::ios::out | std::ios::binary);
|
||||
for(int i = 0; i < output.size(); i++)
|
||||
wf.write((char *)&output[i], 1);
|
||||
wf.close();
|
||||
}
|
||||
catch (int e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DKRCompression::readBinaryFile(std::vector<uint8_t>& input, std::string filename)
|
||||
{
|
||||
try
|
||||
{
|
||||
std::ifstream is;
|
||||
is.open(filename.c_str(), std::ios::binary);
|
||||
is.seekg(0, std::ios::end);
|
||||
size_t filesize = is.tellg();
|
||||
is.seekg(0, std::ios::beg);
|
||||
input.resize(filesize);
|
||||
is.read((char *)input.data(), filesize);
|
||||
is.close();
|
||||
}
|
||||
catch (int e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include "GECompression.h"
|
||||
#include "DKRGzip.h"
|
||||
|
||||
// C++17
|
||||
#include <experimental/filesystem>
|
||||
namespace fs = std::experimental::filesystem;
|
||||
|
||||
class DKRCompression
|
||||
{
|
||||
public:
|
||||
DKRCompression();
|
||||
~DKRCompression();
|
||||
|
||||
std::vector<uint8_t> compressBuffer(std::vector<uint8_t>& data);
|
||||
std::vector<uint8_t> decompressBuffer(std::vector<uint8_t>& data);
|
||||
bool readBinaryFile(std::vector<uint8_t>& input, std::string filename);
|
||||
bool writeBinaryFile(std::vector<uint8_t>& output, std::string filename);
|
||||
|
||||
private:
|
||||
GECompression compressed;
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
* This is a custom version of gzip that was made specifically for the
|
||||
* Diddy Kong Racing decompilation project, since I cannot get zlib to
|
||||
* produce matching compressed files.
|
||||
*
|
||||
* The main difference with this version of gzip is that you can pass in
|
||||
* a simple byte array instead of having to use the FILE structure.
|
||||
*
|
||||
* This library can only compress files at the moment.
|
||||
*
|
||||
* Feel free to use it for your own project. The license is the same as gzip.
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
unsigned char* dkr_gzip_compress(unsigned char* input, int inputSize, int gzipLevel, int* outputSize);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,121 @@
|
||||
// GE Recompression C++ Class
|
||||
// Painstakingly done by Henry Ford (mrhtford_ps2dev@hotmail.com)
|
||||
// Ported to C++ Class by SubDrag (subdrag@rarewitchproject.com) July 13, 2005
|
||||
// Compression Games Added April 03, 2007
|
||||
|
||||
|
||||
#pragma once
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#define maxByteSize 0x800000
|
||||
|
||||
#define GOLDENEYE 0
|
||||
#define PD 1
|
||||
#define BANJOKAZOOIE 2
|
||||
#define KILLERINSTINCT 3
|
||||
#define DONKEYKONG64 4
|
||||
#define BLASTCORPS 5
|
||||
#define BANJOTOOIE 6
|
||||
#define DONKEYKONG64KIOSK 7
|
||||
#define CONKER 8
|
||||
#define TOPGEARRALLY 9
|
||||
#define MILO 10
|
||||
#define JFG 11
|
||||
#define DKR 12
|
||||
#define JFGKIOSK 13
|
||||
#define MICKEYSPEEDWAY 14
|
||||
#define MORTALKOMBAT 15
|
||||
#define STUNTRACER64 16
|
||||
#define ZLB 17
|
||||
#define RESIDENTEVIL2 18
|
||||
|
||||
#define BOOL bool
|
||||
#define TRUE true
|
||||
#define FALSE false
|
||||
#define DWORD int
|
||||
#define CString std::string
|
||||
#define PTSTR std::string
|
||||
#define LPSTR std::string
|
||||
|
||||
struct tableEntry {
|
||||
unsigned long bits;
|
||||
unsigned char flags;
|
||||
int nextIndex;
|
||||
unsigned long wordValue;
|
||||
};
|
||||
|
||||
class GECompression
|
||||
{
|
||||
public:
|
||||
GECompression();
|
||||
~GECompression(void);
|
||||
void SetCompressedBuffer(unsigned char* Buffer, int bufferSize);
|
||||
unsigned char* OutputDecompressedBuffer(int& fileSize, int& compressedSize);
|
||||
//bool CompressGZipFile(CString inputFile, CString outputFile, bool byteFlipCompressed);
|
||||
int game;
|
||||
CString mainFolder;
|
||||
void SetGame(int replaceGame);
|
||||
//void SetPath(CString directory);
|
||||
//old stuff
|
||||
//unsigned char* Compress(int& compressedSize);
|
||||
//unsigned char* OutputCompressed(int compressionType, int& fileSize);
|
||||
//void SetInputBuffer(unsigned char* Buffer, int bufferSize);
|
||||
|
||||
BOOL hiddenExec (PTSTR pCmdLine, CString currentDirectory);
|
||||
BOOL IsFileExist(LPSTR lpszFilename);
|
||||
|
||||
private:
|
||||
static unsigned char bt1Table1[288];
|
||||
static unsigned char bt1Table2[30];
|
||||
|
||||
static unsigned short bt12Table1S[0x20];
|
||||
static unsigned short bt12Table1B[0x20];
|
||||
static unsigned short bt12Table2S[0x20];
|
||||
static unsigned short bt12Table2B[0x20];
|
||||
|
||||
static unsigned char bt2Table1B[0x13];
|
||||
|
||||
tableEntry* unpackTable;
|
||||
int unpackTableIndex;
|
||||
|
||||
unsigned long bitsCache;
|
||||
int bitsRemain;
|
||||
unsigned long bytesIndex;
|
||||
|
||||
//unsigned char* inputBuffer;
|
||||
//int inputBufferSize;
|
||||
unsigned char* compressedBuffer;
|
||||
int compressedBufferSize;
|
||||
|
||||
unsigned long GetNBits(int nBits);
|
||||
unsigned long GetNBitsAndPreserve(int nBits);
|
||||
|
||||
bool UncompressType0(unsigned char* returnBuffer, int& fileSize);
|
||||
bool UncompressType1(unsigned char* returnBuffer, int& fileSize);
|
||||
bool UncompressType2(unsigned char* returnBuffer, int& fileSize);
|
||||
|
||||
void CreateGlobalDecompressionTable(int bit1TableChoice, int size2, int bit12STableSChoice, int bit12BTableChoice, int numBits, bool& returnValue, int& numReturnBits, int& returnStartIndex);
|
||||
bool DecompressBasedOnTable(int startIndex1, int startIndex2, int bitLen1, int bitLen2, unsigned char* returnBuffer, int& fileSize);
|
||||
|
||||
unsigned char variableTable[0x13];
|
||||
unsigned short tableSize;
|
||||
unsigned char fiveBits;
|
||||
unsigned long* wordTable;
|
||||
|
||||
// old stuff
|
||||
/*void FlipBits(unsigned char* indexes, int sizeBytes, int offset);
|
||||
bool* StringToBits(CString bitString, int& lengthBool);
|
||||
static CString dictionaryFirstStr[0x10];
|
||||
static CString dictionarySecondStr[0x10];
|
||||
CString EncodeLZBitPart(int backAmt);
|
||||
CString EncodeRLEBitPart(int repeatAmt);
|
||||
CString HexToBits(int hexNum, int outLength);
|
||||
CString FlipStringBits(CString inputString);*/
|
||||
|
||||
// better compression way
|
||||
|
||||
|
||||
|
||||
unsigned long iterationCounter;
|
||||
};
|
||||
@@ -0,0 +1,453 @@
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "dkr_extractor_classes/extract_config.h"
|
||||
#include "dkr_extractor_classes/rom.h"
|
||||
#include "dkr_decompressor_src/DKRCompression.h"
|
||||
#include "n64graphics/n64graphics.h"
|
||||
|
||||
// C++17
|
||||
#include <experimental/filesystem>
|
||||
namespace fs = std::experimental::filesystem;
|
||||
|
||||
const std::string CONFIG_EXTENSION = ".extract-config";
|
||||
|
||||
const std::string ROM_EXTENSIONS[3] = { ".z64", ".v64", ".n64" };
|
||||
|
||||
std::string outDirectory;
|
||||
DKRCompression compression;
|
||||
int numberOfFilesExtracted;
|
||||
|
||||
#define TEX_FORMAT_RGBA32 0
|
||||
#define TEX_FORMAT_RGBA16 1
|
||||
#define TEX_FORMAT_I8 2
|
||||
#define TEX_FORMAT_I4 3
|
||||
#define TEX_FORMAT_IA16 4
|
||||
#define TEX_FORMAT_IA8 5
|
||||
#define TEX_FORMAT_IA4 6
|
||||
#define TEX_FORMAT_CI4 7
|
||||
|
||||
#define TEX_HEADER_SIZE 0x20
|
||||
|
||||
/*********************************************/
|
||||
|
||||
void show_help() {
|
||||
std::cout << "Usage: ./dkr_extractor <configs_directory> <baseroms_directory> <out_directory>" << std::endl;
|
||||
}
|
||||
|
||||
bool ends_with_extension(std::string filename, std::string extension) {
|
||||
return std::equal(extension.rbegin(), extension.rend(), filename.rbegin());
|
||||
}
|
||||
|
||||
bool ends_with_config_extension(std::string filename) {
|
||||
return ends_with_extension(filename, CONFIG_EXTENSION);
|
||||
}
|
||||
|
||||
bool ends_with_rom_extension(std::string filename) {
|
||||
for (const std::string romExtension : ROM_EXTENSIONS) {
|
||||
if(ends_with_extension(filename, romExtension)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void to_lowercase(std::string& input) {
|
||||
for(char& character : input) {
|
||||
character = std::tolower(character);
|
||||
}
|
||||
}
|
||||
|
||||
/*********************************************/
|
||||
|
||||
void write_binary_file(std::vector<uint8_t>& data, std::string& filename) {
|
||||
std::ofstream wf(filename.c_str(), std::ios::out | std::ios::binary);
|
||||
for(int i = 0; i < data.size(); i++)
|
||||
wf.write((char *)&data[i], 1);
|
||||
wf.close();
|
||||
}
|
||||
|
||||
void extract_binary(std::vector<uint8_t>& data, int startOffset, int endOffset, std::string& name, std::string& subfolder, std::string& outFolder) {
|
||||
// std::cout << "Extracting Binary! " << startHex << std::endl;
|
||||
std::stringstream hexStream, rangeStream, filenameStream;
|
||||
|
||||
hexStream << std::setfill('0') << std::setw(6) << std::hex << std::uppercase << startOffset;
|
||||
std::string startHex = hexStream.str();
|
||||
|
||||
rangeStream << startHex << "-" << std::setfill('0') << std::setw(6) << std::hex << std::uppercase << endOffset;
|
||||
std::string rangeHex = rangeStream.str();
|
||||
|
||||
to_lowercase(startHex);
|
||||
|
||||
std::string outputDirectory = outDirectory + "/assets/" + subfolder + "/" + outFolder;
|
||||
if(!fs::is_directory(outputDirectory)) {
|
||||
fs::create_directories(outputDirectory);
|
||||
}
|
||||
|
||||
filenameStream << outputDirectory << "/" << name << "." << startHex << ".bin";
|
||||
std::string filename = filenameStream.str();
|
||||
|
||||
// Make sure the file is 16-byte aligned.
|
||||
while(data.size() % 16 != 0) {
|
||||
data.push_back(0);
|
||||
}
|
||||
|
||||
write_binary_file(data, filename);
|
||||
|
||||
std::cout << "Extracted " << rangeHex << " as /" << outFolder << "/" << name << "." << startHex << ".bin" << std::endl;
|
||||
}
|
||||
|
||||
void extract_compressed(std::vector<uint8_t>& data, int startOffset, int endOffset, std::string& name, std::string& subfolder, std::string& outFolder) {
|
||||
// Needed padding to prevent errors with decompressing.
|
||||
if(data[data.size() - 1] != 0) {
|
||||
data.push_back(0);
|
||||
data.push_back(0);
|
||||
data.push_back(0);
|
||||
data.push_back(0);
|
||||
}
|
||||
data = compression.decompressBuffer(data);
|
||||
extract_binary(data, startOffset, endOffset, name, subfolder, outFolder);
|
||||
}
|
||||
|
||||
int get_texture_size(int width, int height, int textureFormat) {
|
||||
switch(textureFormat) {
|
||||
case TEX_FORMAT_RGBA32:
|
||||
return (width * height * 4) + TEX_HEADER_SIZE;
|
||||
case TEX_FORMAT_RGBA16:
|
||||
case TEX_FORMAT_IA16:
|
||||
return (width * height * 2) + TEX_HEADER_SIZE;
|
||||
case TEX_FORMAT_I8:
|
||||
case TEX_FORMAT_IA8:
|
||||
return (width * height) + TEX_HEADER_SIZE;
|
||||
case TEX_FORMAT_I4:
|
||||
case TEX_FORMAT_IA4:
|
||||
return (width * height / 2) + TEX_HEADER_SIZE;
|
||||
case TEX_FORMAT_CI4:
|
||||
std::cout << "Error: CI4 texture format is not currently supported." << std::endl;
|
||||
throw 1;
|
||||
}
|
||||
|
||||
std::cout << "Error: Invalid texture format " << textureFormat << std::endl;
|
||||
throw 1;
|
||||
}
|
||||
|
||||
std::string get_header_string(std::vector<uint8_t>& header) {
|
||||
std::stringstream headerBytesStream;
|
||||
headerBytesStream << std::hex << std::uppercase
|
||||
<< std::setfill('0')
|
||||
<< std::setw(1) << (int)((int)header[0x02] >> 4)
|
||||
<< std::setw(2) << (int)header[0x03]
|
||||
<< std::setw(2) << (int)header[0x04]
|
||||
<< std::setw(2) << (int)header[0x06]
|
||||
<< std::setw(2) << (int)header[0x07]
|
||||
<< std::setw(2) << (int)header[0x15];
|
||||
return headerBytesStream.str();
|
||||
}
|
||||
|
||||
void deinterlace(std::vector<uint8_t>& data, int width, int height, int bitDepth, int bufferSize) {
|
||||
uint8_t* temp = new uint8_t[bufferSize];
|
||||
|
||||
int numPixels;
|
||||
if(bitDepth == 4) {
|
||||
numPixels = ((width * height) / 2);
|
||||
} else {
|
||||
numPixels = (width * height * (bitDepth / 8));
|
||||
}
|
||||
|
||||
int stride = bufferSize * 2;
|
||||
|
||||
int size = numPixels / stride;
|
||||
|
||||
for(int i = 0; i < size; i++) {
|
||||
int row;
|
||||
if(bitDepth == 4) {
|
||||
row = (i * stride) / width * 2;
|
||||
} else {
|
||||
row = (i * stride) / width / (bitDepth / 8);
|
||||
}
|
||||
if(row % 2 == 0) continue;
|
||||
for(int j = 0; j < bufferSize; j++) {
|
||||
temp[j] = data[i * stride + j];
|
||||
data[i * stride + j] = data[i * stride + j + bufferSize];
|
||||
data[i * stride + j + bufferSize] = temp[j];
|
||||
}
|
||||
}
|
||||
|
||||
delete[] temp;
|
||||
}
|
||||
|
||||
void flip_vertically(std::vector<uint8_t>& data, int width, int height, int bitDepth) {
|
||||
int rowSize;
|
||||
if(bitDepth == 4) {
|
||||
rowSize = width / 2;
|
||||
} else {
|
||||
rowSize = width * (bitDepth / 8);
|
||||
}
|
||||
|
||||
uint8_t temp = 0;
|
||||
|
||||
for(int y = 0; y < height/2; y++) {
|
||||
for(int x = 0; x < rowSize; x++) {
|
||||
temp = data[y * rowSize + x];
|
||||
data[y * rowSize + x] = data[(height - y - 1) * rowSize + x];
|
||||
data[(height - y - 1) * rowSize + x] = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void extract_single_texture(std::vector<uint8_t>& header, std::vector<uint8_t>& data, int index, int startOffset, int endOffset,
|
||||
std::string& name, std::string& subfolder, std::string& outFolder, bool isCompressed, bool shouldFlip) {
|
||||
std::stringstream hexStream, rangeStream, filenameStream;
|
||||
|
||||
hexStream << std::setfill('0') << std::setw(6) << std::hex << std::uppercase << startOffset;
|
||||
std::string startHex = hexStream.str();
|
||||
|
||||
rangeStream << startHex << "-" << std::setfill('0') << std::setw(6) << std::hex << std::uppercase << endOffset;
|
||||
std::string rangeHex = rangeStream.str();
|
||||
|
||||
to_lowercase(startHex);
|
||||
|
||||
std::string outputDirectory = outDirectory + "/assets/" + subfolder + "/" + outFolder;
|
||||
if(!fs::is_directory(outputDirectory)) {
|
||||
fs::create_directories(outputDirectory);
|
||||
}
|
||||
|
||||
int width = header[0];
|
||||
int height = header[1];
|
||||
bool isInterlaced = ((header[0x06] & 0x04) == 0x04);
|
||||
bool computeSizeInHeader = !(header[0x16] == 0x00 && header[0x17] == 0x00);
|
||||
int textureFormat = header[0x02] & 0xF;
|
||||
|
||||
int textureSize = get_texture_size(width, height, textureFormat);
|
||||
|
||||
filenameStream << name << '.' << startHex
|
||||
<< "." << index << "."
|
||||
<< (isCompressed ? "C" : "U")
|
||||
<< (shouldFlip ? "F" : "N")
|
||||
<< (computeSizeInHeader ? "S" : "Z")
|
||||
<< "." << get_header_string(header);
|
||||
|
||||
std::string filename;
|
||||
|
||||
switch(textureFormat) {
|
||||
case TEX_FORMAT_RGBA32:
|
||||
{
|
||||
filenameStream << ".rgba32.png";
|
||||
filename = filenameStream.str();
|
||||
if(isInterlaced) deinterlace(data, width, height, 32, 8);
|
||||
if(shouldFlip) flip_vertically(data, width, height, 32);
|
||||
rgba2png((outputDirectory + "/" + filename).c_str(), (const rgba*)&data[0], width, height);
|
||||
break;
|
||||
}
|
||||
case TEX_FORMAT_RGBA16:
|
||||
{
|
||||
filenameStream << ".rgba16.png";
|
||||
filename = filenameStream.str();
|
||||
if(isInterlaced) deinterlace(data, width, height, 16, 4);
|
||||
if(shouldFlip) flip_vertically(data, width, height, 16);
|
||||
rgba* outTex = raw2rgba(&data[0], width, height, 16);
|
||||
rgba2png((outputDirectory + "/" + filename).c_str(), outTex, width, height);
|
||||
break;
|
||||
}
|
||||
case TEX_FORMAT_I8:
|
||||
{
|
||||
filenameStream << ".i8.png";
|
||||
filename = filenameStream.str();
|
||||
if(isInterlaced) deinterlace(data, width, height, 8, 4);
|
||||
if(shouldFlip) flip_vertically(data, width, height, 8);
|
||||
ia* outTex = raw2i(&data[0], width, height, 8);
|
||||
ia2png((outputDirectory + "/" + filename).c_str(), outTex, width, height);
|
||||
break;
|
||||
}
|
||||
case TEX_FORMAT_I4:
|
||||
{
|
||||
filenameStream << ".i4.png";
|
||||
filename = filenameStream.str();
|
||||
if(isInterlaced) deinterlace(data, width, height, 4, 4);
|
||||
if(shouldFlip) flip_vertically(data, width, height, 4);
|
||||
ia* outTex = raw2i(&data[0], width, height, 4);
|
||||
ia2png((outputDirectory + "/" + filename).c_str(), outTex, width, height);
|
||||
break;
|
||||
}
|
||||
case TEX_FORMAT_IA16:
|
||||
{
|
||||
filenameStream << ".ia16.png";
|
||||
filename = filenameStream.str();
|
||||
if(isInterlaced) deinterlace(data, width, height, 16, 4);
|
||||
if(shouldFlip) flip_vertically(data, width, height, 16);
|
||||
ia* outTex = raw2ia(&data[0], width, height, 16);
|
||||
ia2png((outputDirectory + "/" + filename).c_str(), outTex, width, height);
|
||||
break;
|
||||
}
|
||||
case TEX_FORMAT_IA8:
|
||||
{
|
||||
filenameStream << ".ia8.png";
|
||||
filename = filenameStream.str();
|
||||
if(isInterlaced) deinterlace(data, width, height, 8, 4);
|
||||
if(shouldFlip) flip_vertically(data, width, height, 8);
|
||||
ia* outTex = raw2ia(&data[0], width, height, 8);
|
||||
ia2png((outputDirectory + "/" + filename).c_str(), outTex, width, height);
|
||||
break;
|
||||
}
|
||||
case TEX_FORMAT_IA4:
|
||||
{
|
||||
filenameStream << ".ia4.png";
|
||||
filename = filenameStream.str();
|
||||
if(isInterlaced) deinterlace(data, width, height, 4, 4);
|
||||
if(shouldFlip) flip_vertically(data, width, height, 4);
|
||||
ia* outTex = raw2ia(&data[0], width, height, 4);
|
||||
ia2png((outputDirectory + "/" + filename).c_str(), outTex, width, height);
|
||||
break;
|
||||
}
|
||||
case TEX_FORMAT_CI4:
|
||||
{
|
||||
// filename += ".ci4.png";
|
||||
std::cout << "Error: CI4 texture format is not currently supported." << std::endl;
|
||||
throw 1;
|
||||
}
|
||||
default: // Invalid texture format
|
||||
{
|
||||
std::cout << "Error: Unknown texture format " << textureFormat << std::endl;
|
||||
throw 1;
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "Extracted " << rangeHex << " as /" << outFolder << "/" << filename << std::endl;
|
||||
}
|
||||
|
||||
void extract_texture(ROM& rom, int startOffset, int endOffset, std::string& name, std::string& subfolder, std::string& outFolder, bool shouldFlip) {
|
||||
|
||||
std::vector<uint8_t> header = rom.get_bytes_from_range(startOffset, TEX_HEADER_SIZE);
|
||||
std::vector<uint8_t> data;
|
||||
|
||||
bool isCompressed = (header[0x1D] == 0x01);
|
||||
if(isCompressed) {
|
||||
std::vector<uint8_t> compressedData = rom.get_bytes_from_range(startOffset + TEX_HEADER_SIZE, endOffset - startOffset - TEX_HEADER_SIZE);
|
||||
// Needed padding to prevent errors with decompressing.
|
||||
if(compressedData[compressedData.size() - 1] != 0) {
|
||||
compressedData.push_back(0);
|
||||
compressedData.push_back(0);
|
||||
compressedData.push_back(0);
|
||||
compressedData.push_back(0);
|
||||
}
|
||||
data = compression.decompressBuffer(compressedData);
|
||||
} else {
|
||||
data = rom.get_bytes_from_range(startOffset, endOffset - startOffset);
|
||||
}
|
||||
|
||||
int numTextures = header[0x12];
|
||||
int dataOffset = 0;
|
||||
|
||||
for(int i = 0; i < numTextures; i++){
|
||||
int width = data[dataOffset + 0x00];
|
||||
int height = data[dataOffset + 0x01];
|
||||
int textureFormat = data[dataOffset + 0x02] & 0xF;
|
||||
int textureSize = get_texture_size(width, height, textureFormat);
|
||||
|
||||
std::vector<uint8_t> texHeader(data.begin() + dataOffset, data.begin() + dataOffset + TEX_HEADER_SIZE);
|
||||
std::vector<uint8_t> texData(data.begin() + dataOffset + TEX_HEADER_SIZE, data.begin() + dataOffset + textureSize);
|
||||
|
||||
extract_single_texture(texHeader, texData, i, startOffset, endOffset, name, subfolder, outFolder, isCompressed, shouldFlip);
|
||||
dataOffset += textureSize;
|
||||
}
|
||||
}
|
||||
|
||||
void extract_range(std::string subfolder, ConfigRange& range, ROM& rom) {
|
||||
std::string name = range.get_property(1);
|
||||
std::string outFolder = range.get_property(2);
|
||||
|
||||
int startOffset = range.get_start();
|
||||
int endOffset = range.get_start() + range.get_size();
|
||||
|
||||
switch(range.get_type()) {
|
||||
case ConfigRangeType::BINARY:
|
||||
{
|
||||
std::vector<uint8_t> data = rom.get_bytes_from_range(startOffset, range.get_size());
|
||||
extract_binary(data, startOffset, endOffset, name, subfolder, outFolder);
|
||||
numberOfFilesExtracted++;
|
||||
break;
|
||||
}
|
||||
case ConfigRangeType::COMPRESSED:
|
||||
{
|
||||
std::vector<uint8_t> data = rom.get_bytes_from_range(startOffset, range.get_size());
|
||||
extract_compressed(data, startOffset, endOffset, name, subfolder, outFolder);
|
||||
numberOfFilesExtracted++;
|
||||
break;
|
||||
}
|
||||
case ConfigRangeType::TEXTURE:
|
||||
{
|
||||
std::string flip_verticallyString = range.get_property(3);
|
||||
to_lowercase(flip_verticallyString);
|
||||
bool shouldFlip = (flip_verticallyString == "flipvertically");
|
||||
extract_texture(rom, startOffset, endOffset, name, subfolder, outFolder, shouldFlip);
|
||||
numberOfFilesExtracted++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void extract_assets_from_rom(Config& config, ROM& rom) {
|
||||
if(config.get_size() != rom.get_size()) {
|
||||
std::cout << "Error: Config does not add up to the ROM size." << std::endl
|
||||
<< std::hex << "ROM size is " << rom.get_size()
|
||||
<< "; Config ends at " << config.get_size() << std::dec << std::endl;
|
||||
}
|
||||
|
||||
numberOfFilesExtracted = 0;
|
||||
int numRanges = config.get_number_of_ranges();
|
||||
for (int i = 0; i < numRanges; i++) {
|
||||
ConfigRange range = config.get_range(i);
|
||||
extract_range(config.get_subfolder(), range, rom);
|
||||
}
|
||||
}
|
||||
|
||||
/*********************************************/
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
|
||||
if(argc != 4) {
|
||||
show_help();
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string configsDirectory = argv[1];
|
||||
std::string baseromsDirectory = argv[2];
|
||||
outDirectory = argv[3];
|
||||
|
||||
std::vector<Config> configs;
|
||||
std::vector<ROM> roms;
|
||||
|
||||
for (const auto & entry : fs::directory_iterator(configsDirectory)){
|
||||
std::string filename = entry.path().filename();
|
||||
if(ends_with_config_extension(filename)) {
|
||||
configs.push_back(Config(configsDirectory, filename));
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto & entry : fs::directory_iterator(baseromsDirectory)){
|
||||
std::string filename = entry.path().string();
|
||||
if(ends_with_rom_extension(filename)) {
|
||||
roms.push_back(ROM(filename));
|
||||
}
|
||||
}
|
||||
|
||||
for(auto& config : configs) {
|
||||
if(config.is_supported()) {
|
||||
for(auto& rom : roms) {
|
||||
if(rom.get_md5() == config.get_md5()) {
|
||||
std::cout << "Found ROM file for config \"" << config.get_name() << "\"" << std::endl;
|
||||
extract_assets_from_rom(config, rom);
|
||||
std::cout << "Finished extracting " << numberOfFilesExtracted << " files." << std::endl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
std::cout << "This version of the game is currently not supported." << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
#include "extract_config.h"
|
||||
|
||||
ConfigRange::ConfigRange(int start, int size, ConfigRangeType type, std::vector<std::string> properties){
|
||||
rangeStart = start;
|
||||
rangeSize = size;
|
||||
rangeType = type;
|
||||
rangeProperties = properties;
|
||||
}
|
||||
|
||||
ConfigRange::~ConfigRange(){
|
||||
}
|
||||
|
||||
int ConfigRange::get_start(){
|
||||
return rangeStart;
|
||||
}
|
||||
int ConfigRange::get_size(){
|
||||
return rangeSize;
|
||||
}
|
||||
ConfigRangeType ConfigRange::get_type(){
|
||||
return rangeType;
|
||||
}
|
||||
std::string ConfigRange::get_property(int propertyIndex){
|
||||
if(propertyIndex >= rangeProperties.size()) {
|
||||
return "";
|
||||
}
|
||||
return rangeProperties[propertyIndex];
|
||||
}
|
||||
|
||||
/**********************************************/
|
||||
|
||||
Config::Config(std::string directory, std::string filename){
|
||||
std::cout << "Reading config \"" << filename << "\"" << std::endl;
|
||||
this->directory = directory;
|
||||
currentRangeOffset = 0;
|
||||
|
||||
std::string text = read_file(directory + '/' + filename);
|
||||
|
||||
parse(text);
|
||||
}
|
||||
|
||||
Config::~Config(){
|
||||
}
|
||||
|
||||
bool Config::is_supported() {
|
||||
return !notSupported;
|
||||
}
|
||||
std::string Config::get_name(){
|
||||
return name;
|
||||
}
|
||||
std::string Config::get_md5(){
|
||||
return md5;
|
||||
}
|
||||
std::string Config::get_subfolder(){
|
||||
return subfolder;
|
||||
}
|
||||
int Config::get_size() {
|
||||
return currentRangeOffset;
|
||||
}
|
||||
int Config::get_number_of_ranges(){
|
||||
return ranges.size();
|
||||
}
|
||||
ConfigRange Config::get_range(int index){
|
||||
return ranges[index];
|
||||
}
|
||||
|
||||
void Config::parse(std::string text){
|
||||
std::regex regex_property("^\\s*([0-9a-zA-Z\\-]+)\\s*:\\s*[\"]([^\"]*)[\"]\\s*$");
|
||||
std::regex regex_range("^\\s*\\[\\s*(0x[0-9a-fA-F]+)\\s*\\]\\s*:\\s*(.*)$");
|
||||
|
||||
std::istringstream f(text);
|
||||
std::string line;
|
||||
std::smatch match;
|
||||
while (std::getline(f, line)) {
|
||||
line = strip_comments(line);
|
||||
|
||||
if(line.length() < 1) {
|
||||
continue;
|
||||
}
|
||||
if (std::regex_search(line, match, regex_property)) {
|
||||
parse_property(match[1], match[2]);
|
||||
continue;
|
||||
}
|
||||
if (std::regex_search(line, match, regex_range)) {
|
||||
parse_range(match[1], match[2]);
|
||||
continue;
|
||||
}
|
||||
|
||||
std::cout << "Invalid line: " << line << std::endl;
|
||||
throw 1;
|
||||
}
|
||||
}
|
||||
|
||||
void Config::parse_property(std::string name, std::string value){
|
||||
name = get_lowercase(trim(name));
|
||||
|
||||
if(name == "config-name") {
|
||||
this->name = value;
|
||||
return;
|
||||
} else if (name == "subfolder") {
|
||||
this->subfolder = value;
|
||||
return;
|
||||
} else if (name == "checksum-md5") {
|
||||
this->md5 = value;
|
||||
return;
|
||||
} else if (name == "not-supported") {
|
||||
this->notSupported = get_lowercase(trim(value)) == "true";
|
||||
return;
|
||||
} else if (name == "include") {
|
||||
std::string text = read_file(this->directory + '/' + value);
|
||||
parse(text);
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void Config::parse_range(std::string rangeSize, std::string rangeProperties) {
|
||||
int size = std::stoi(rangeSize, 0, 16);
|
||||
|
||||
std::vector<std::string> properties = split_string(rangeProperties, ',');
|
||||
|
||||
for(size_t i = 0; i < properties.size(); i++) {
|
||||
// Remove surrounding whitespace
|
||||
properties[i] = trim(properties[i]);
|
||||
// Remove doublequotes
|
||||
properties[i] = trim(properties[i].substr(1, properties[i].size() - 2));
|
||||
}
|
||||
|
||||
ConfigRangeType type = get_range_type(properties[0]);
|
||||
ranges.push_back(ConfigRange(currentRangeOffset, size, type, properties));
|
||||
currentRangeOffset += size;
|
||||
}
|
||||
|
||||
ConfigRangeType Config::get_range_type(std::string typeString) {
|
||||
std::string type = get_lowercase(trim(typeString));
|
||||
|
||||
if(type == "binary") return ConfigRangeType::BINARY;
|
||||
if(type == "noextract") return ConfigRangeType::NOEXTRACT;
|
||||
if(type == "compressed") return ConfigRangeType::COMPRESSED;
|
||||
if(type == "texture") return ConfigRangeType::TEXTURE;
|
||||
|
||||
std::cout << "Unknown extraction type: " << type << std::endl;
|
||||
throw 1;
|
||||
return ConfigRangeType::UNDEFINED;
|
||||
}
|
||||
|
||||
std::string Config::read_file(std::string filename) {
|
||||
std::ifstream t(filename);
|
||||
t.seekg(0, std::ios::end);
|
||||
size_t size = t.tellg();
|
||||
std::string buffer(size, ' ');
|
||||
t.seekg(0);
|
||||
t.read(&buffer[0], size);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
std::string Config::trim(std::string input) {
|
||||
size_t b = input.find_first_not_of(' ');
|
||||
if (b == std::string::npos) b = 0;
|
||||
return input.substr(b, input.find_last_not_of(' ') + 1 - b);
|
||||
}
|
||||
|
||||
std::string Config::strip_comments(std::string input) {
|
||||
return trim(input.substr(0, input.find_first_of("#")));
|
||||
}
|
||||
|
||||
std::vector<std::string> Config::split_string(std::string s, char delim) {
|
||||
std::stringstream ss(s);
|
||||
std::string item;
|
||||
std::vector<std::string> elems;
|
||||
while (std::getline(ss, item, delim)) {
|
||||
elems.push_back(item);
|
||||
}
|
||||
return elems;
|
||||
}
|
||||
|
||||
std::string Config::get_lowercase(std::string input) {
|
||||
std::string output = input;
|
||||
for(auto& element : output) {
|
||||
element = std::tolower(element);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/**********************************************/
|
||||
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <regex>
|
||||
|
||||
// C++17
|
||||
#include <experimental/filesystem>
|
||||
namespace fs = std::experimental::filesystem;
|
||||
|
||||
enum ConfigRangeType {
|
||||
UNDEFINED,
|
||||
BINARY,
|
||||
NOEXTRACT,
|
||||
COMPRESSED,
|
||||
TEXTURE
|
||||
};
|
||||
|
||||
class ConfigRange {
|
||||
public:
|
||||
ConfigRange(int start, int size, ConfigRangeType type, std::vector<std::string> properties);
|
||||
~ConfigRange();
|
||||
|
||||
int get_start();
|
||||
int get_size();
|
||||
ConfigRangeType get_type();
|
||||
std::string get_property(int propertyIndex);
|
||||
|
||||
private:
|
||||
int rangeStart, rangeSize;
|
||||
ConfigRangeType rangeType;
|
||||
std::vector<std::string> rangeProperties;
|
||||
};
|
||||
|
||||
class Config {
|
||||
public:
|
||||
Config(std::string directory, std::string filename);
|
||||
~Config();
|
||||
|
||||
bool is_supported();
|
||||
std::string get_name();
|
||||
std::string get_md5();
|
||||
std::string get_subfolder();
|
||||
int get_size();
|
||||
int get_number_of_ranges();
|
||||
ConfigRange get_range(int index);
|
||||
|
||||
private:
|
||||
void parse(std::string text);
|
||||
void parse_property(std::string name, std::string value);
|
||||
void parse_range(std::string rangeSize, std::string rangeProperties);
|
||||
|
||||
ConfigRangeType get_range_type(std::string typeString);
|
||||
|
||||
std::string read_file(std::string filename);
|
||||
std::string trim(std::string input);
|
||||
std::string strip_comments(std::string input);
|
||||
std::vector<std::string> split_string(std::string s, char delim);
|
||||
std::string get_lowercase(std::string input);
|
||||
|
||||
bool notSupported;
|
||||
std::string directory;
|
||||
std::string name;
|
||||
std::string md5;
|
||||
std::string subfolder;
|
||||
std::vector<ConfigRange> ranges;
|
||||
|
||||
int currentRangeOffset = 0;
|
||||
};
|
||||
@@ -0,0 +1,138 @@
|
||||
#include "rom.h"
|
||||
|
||||
ROM::ROM(std::string filename){
|
||||
if(!readROMFile(filename)) {
|
||||
// ROM failed to load.
|
||||
throw 1;
|
||||
}
|
||||
romFilename = filename;
|
||||
|
||||
test_endianness();
|
||||
calculate_md5();
|
||||
}
|
||||
|
||||
ROM::~ROM(){
|
||||
}
|
||||
|
||||
std::vector<uint8_t> ROM::get_bytes_from_range(int start, int numBytes){
|
||||
std::vector<uint8_t> subVector(numBytes);
|
||||
std::copy(bytes.begin() + start, bytes.begin() + start + numBytes, subVector.begin());
|
||||
return subVector;
|
||||
}
|
||||
|
||||
uint8_t ROM::get_byte(int romOffset){
|
||||
return bytes[romOffset];
|
||||
}
|
||||
|
||||
std::string ROM::get_md5(){
|
||||
return md5;
|
||||
}
|
||||
|
||||
int ROM::get_size(){
|
||||
return bytes.size();
|
||||
}
|
||||
|
||||
void ROM::test_endianness(){
|
||||
bool convertedToBigEndian = false;
|
||||
if(bytes[0] == 0x80 && bytes[1] == 0x37) { // Big endian
|
||||
// Do nothing
|
||||
} else if(bytes[0] == 0x37 && bytes[1] == 0x80) { // Mixed endian (byteswapped)
|
||||
// Mixed endian, convert to big endian
|
||||
int numBytes = get_size();
|
||||
for(int i = 0; i < numBytes; i+=2) {
|
||||
uint8_t temp = bytes[i];
|
||||
bytes[i] = bytes[i + 1];
|
||||
bytes[i + 1] = temp;
|
||||
}
|
||||
convertedToBigEndian = true;
|
||||
} else if(bytes[0] == 0x40 && bytes[1] == 0x12) { // Little endian
|
||||
// Little endian, convert to big endian
|
||||
int numBytes = get_size();
|
||||
uint8_t temp[4];
|
||||
for(int i = 0; i < numBytes; i+=4) {
|
||||
temp[0] = bytes[i + 0];
|
||||
temp[1] = bytes[i + 1];
|
||||
temp[2] = bytes[i + 2];
|
||||
temp[3] = bytes[i + 3];
|
||||
bytes[i + 0] = temp[3];
|
||||
bytes[i + 1] = temp[2];
|
||||
bytes[i + 2] = temp[1];
|
||||
bytes[i + 3] = temp[0];
|
||||
}
|
||||
convertedToBigEndian = true;
|
||||
} else {
|
||||
// Invalid ROM file
|
||||
throw 1;
|
||||
}
|
||||
|
||||
if(convertedToBigEndian) { // Overwrite ROM file
|
||||
fs::remove(fs::path(romFilename)); // Delete old romfile
|
||||
std::string z64Filename = romFilename.substr(0, romFilename.length() - 4) + ".z64";
|
||||
if(!writeROMFile(z64Filename)) { // Save new big-endian rom.
|
||||
// ROM failed to save.
|
||||
throw 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ROM::calculate_md5() {
|
||||
uint8_t buffer[0x4000];
|
||||
uint8_t digest[MD5_DIGEST_LENGTH];
|
||||
|
||||
std::stringstream ss;
|
||||
|
||||
MD5_CTX md5Context;
|
||||
|
||||
MD5_Init(&md5Context);
|
||||
MD5_Update(&md5Context, &bytes[0], bytes.size());
|
||||
int res = MD5_Final(digest, &md5Context);
|
||||
|
||||
if(res == 0){ // hash failed or raise an exception
|
||||
md5 = "";
|
||||
return;
|
||||
}
|
||||
|
||||
// set up stringstream format
|
||||
ss << std::hex << std::setfill('0');
|
||||
|
||||
|
||||
for(uint8_t uc: digest) {
|
||||
ss << std::setw(2) << (int)uc;
|
||||
}
|
||||
|
||||
md5 = ss.str();
|
||||
}
|
||||
|
||||
bool ROM::readROMFile(std::string filename) {
|
||||
try
|
||||
{
|
||||
std::ifstream is;
|
||||
is.open(filename.c_str(), std::ios::binary);
|
||||
is.seekg(0, std::ios::end);
|
||||
size_t filesize = is.tellg();
|
||||
is.seekg(0, std::ios::beg);
|
||||
bytes.resize(filesize);
|
||||
is.read((char *)bytes.data(), filesize);
|
||||
is.close();
|
||||
}
|
||||
catch (int e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ROM::writeROMFile(std::string filename) {
|
||||
try
|
||||
{
|
||||
std::ofstream wf(filename.c_str(), std::ios::out | std::ios::binary);
|
||||
for(int i = 0; i < bytes.size(); i++)
|
||||
wf.write((char *)&bytes[i], 1);
|
||||
wf.close();
|
||||
}
|
||||
catch (int e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <iomanip>
|
||||
#include <openssl/md5.h>
|
||||
|
||||
// C++17
|
||||
#include <experimental/filesystem>
|
||||
namespace fs = std::experimental::filesystem;
|
||||
|
||||
class ROM {
|
||||
public:
|
||||
ROM(std::string filename);
|
||||
~ROM();
|
||||
|
||||
std::vector<uint8_t> get_bytes_from_range(int start, int numBytes);
|
||||
uint8_t get_byte(int romOffset);
|
||||
|
||||
std::string get_md5();
|
||||
int get_size();
|
||||
|
||||
private:
|
||||
void test_endianness();
|
||||
void calculate_md5();
|
||||
bool readROMFile(std::string filename);
|
||||
bool writeROMFile(std::string filename);
|
||||
|
||||
std::vector<uint8_t> bytes;
|
||||
std::string md5;
|
||||
std::string romFilename;
|
||||
};
|
||||
@@ -0,0 +1,429 @@
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <tuple>
|
||||
#include "dkr_decompressor_src/DKRCompression.h"
|
||||
#include "n64graphics/n64graphics.h"
|
||||
|
||||
#define TEX_FORMAT_RGBA32 0
|
||||
#define TEX_FORMAT_RGBA16 1
|
||||
#define TEX_FORMAT_I8 2
|
||||
#define TEX_FORMAT_I4 3
|
||||
#define TEX_FORMAT_IA16 4
|
||||
#define TEX_FORMAT_IA8 5
|
||||
#define TEX_FORMAT_IA4 6
|
||||
#define TEX_FORMAT_CI4 7
|
||||
|
||||
#define TEX_HEADER_SIZE 0x20
|
||||
|
||||
// C++17
|
||||
#include <experimental/filesystem>
|
||||
namespace fs = std::experimental::filesystem;
|
||||
|
||||
const std::string PNG_EXTENSION = ".png";
|
||||
|
||||
/*********************************************/
|
||||
|
||||
void show_help() {
|
||||
std::cout << "Usage: ./dkr_texbuilder <input_directory> <output_filepath> <texture_name> <out_texture_name>" << std::endl;
|
||||
}
|
||||
|
||||
bool starts_with(std::string filename, std::string pattern) {
|
||||
return std::equal(pattern.begin(), pattern.end(), filename.begin());
|
||||
}
|
||||
|
||||
std::vector<std::tuple<std::string, std::string>> get_filenames_from_directory(std::string directory, std::string name) {
|
||||
std::vector<std::tuple<std::string, std::string>> filenames;
|
||||
for (const auto & entry : fs::directory_iterator(directory)){
|
||||
std::string filename = entry.path().filename();
|
||||
if(starts_with(filename, name)) {
|
||||
filenames.push_back(std::make_tuple(filename, entry.path().string()));
|
||||
}
|
||||
}
|
||||
return filenames;
|
||||
}
|
||||
|
||||
void write_binary_file(std::vector<uint8_t>& data, std::string& filename) {
|
||||
std::ofstream wf(filename.c_str(), std::ios::out | std::ios::binary);
|
||||
for(int i = 0; i < data.size(); i++)
|
||||
wf.write((char *)&data[i], 1);
|
||||
wf.close();
|
||||
}
|
||||
|
||||
void to_lowercase(std::string& input) {
|
||||
for(char& character : input) {
|
||||
character = std::tolower(character);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> split(const std::string &s, char delim) {
|
||||
std::stringstream ss(s);
|
||||
std::string item;
|
||||
std::vector<std::string> elems;
|
||||
while (std::getline(ss, item, delim)) {
|
||||
elems.push_back(std::move(item));
|
||||
}
|
||||
return elems;
|
||||
}
|
||||
|
||||
/*********************************************/
|
||||
|
||||
|
||||
void deinterlace(std::vector<uint8_t>& data, int width, int height, int bitDepth, int bufferSize) {
|
||||
uint8_t* temp = new uint8_t[bufferSize];
|
||||
|
||||
int numPixels;
|
||||
if(bitDepth == 4) {
|
||||
numPixels = ((width * height) / 2);
|
||||
} else {
|
||||
numPixels = (width * height * (bitDepth / 8));
|
||||
}
|
||||
|
||||
int stride = bufferSize * 2;
|
||||
|
||||
int size = numPixels / stride;
|
||||
|
||||
for(int i = 0; i < size; i++) {
|
||||
int row;
|
||||
if(bitDepth == 4) {
|
||||
row = (i * stride) / width * 2;
|
||||
} else {
|
||||
row = (i * stride) / width / (bitDepth / 8);
|
||||
}
|
||||
if(row % 2 == 0) continue;
|
||||
for(int j = 0; j < bufferSize; j++) {
|
||||
temp[j] = data[i * stride + j];
|
||||
data[i * stride + j] = data[i * stride + j + bufferSize];
|
||||
data[i * stride + j + bufferSize] = temp[j];
|
||||
}
|
||||
}
|
||||
|
||||
delete[] temp;
|
||||
}
|
||||
|
||||
void flip_vertically(std::vector<uint8_t>& data, int width, int height, int bitDepth) {
|
||||
int rowSize;
|
||||
if(bitDepth == 4) {
|
||||
rowSize = width / 2;
|
||||
} else {
|
||||
rowSize = width * (bitDepth / 8);
|
||||
}
|
||||
|
||||
uint8_t temp = 0;
|
||||
|
||||
for(int y = 0; y < height/2; y++) {
|
||||
for(int x = 0; x < rowSize; x++) {
|
||||
temp = data[y * rowSize + x];
|
||||
data[y * rowSize + x] = data[(height - y - 1) * rowSize + x];
|
||||
data[(height - y - 1) * rowSize + x] = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int get_texture_type(std::string textureFormatString) {
|
||||
to_lowercase(textureFormatString);
|
||||
|
||||
if(textureFormatString == "rgba32") return TEX_FORMAT_RGBA32;
|
||||
else if(textureFormatString == "rgba16") return TEX_FORMAT_RGBA16;
|
||||
else if(textureFormatString == "i8") return TEX_FORMAT_I8;
|
||||
else if(textureFormatString == "i4") return TEX_FORMAT_I4;
|
||||
else if(textureFormatString == "ia16") return TEX_FORMAT_IA16;
|
||||
else if(textureFormatString == "ia8") return TEX_FORMAT_IA8;
|
||||
else if(textureFormatString == "ia4") return TEX_FORMAT_IA4;
|
||||
else if(textureFormatString == "ci4") return TEX_FORMAT_CI4;
|
||||
|
||||
std::cout << "Error: Invalid texture type: " << textureFormatString << std::endl;
|
||||
throw 1;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> load_texture_from_png(std::string filepath, int textureFormat, int* width, int* height) {
|
||||
switch(textureFormat) {
|
||||
case TEX_FORMAT_RGBA32:
|
||||
case TEX_FORMAT_RGBA16:
|
||||
{
|
||||
uint8_t* data = (uint8_t*)png2rgba(filepath.c_str(), width, height);
|
||||
std::vector<uint8_t> vec(data, data + ((*width) * (*height) * 4));
|
||||
free(data);
|
||||
return vec;
|
||||
}
|
||||
case TEX_FORMAT_I8:
|
||||
case TEX_FORMAT_I4:
|
||||
case TEX_FORMAT_IA16:
|
||||
case TEX_FORMAT_IA8:
|
||||
case TEX_FORMAT_IA4:
|
||||
{
|
||||
uint8_t* data = (uint8_t*)png2ia(filepath.c_str(), width, height);
|
||||
std::vector<uint8_t> vec(data, data + ((*width) * (*height) * 2));
|
||||
free(data);
|
||||
return vec;
|
||||
}
|
||||
case TEX_FORMAT_CI4:
|
||||
std::cout << "Error: CI4 is currently not supported." << std::endl;
|
||||
throw 1;
|
||||
default:
|
||||
std::cout << "Error: Invalid texture type: " << textureFormat << std::endl;
|
||||
throw 1;
|
||||
}
|
||||
}
|
||||
|
||||
int get_texture_size(int width, int height, int textureFormat) {
|
||||
switch(textureFormat) {
|
||||
case TEX_FORMAT_RGBA32:
|
||||
return (width * height * 4) + TEX_HEADER_SIZE;
|
||||
case TEX_FORMAT_RGBA16:
|
||||
case TEX_FORMAT_IA16:
|
||||
return (width * height * 2) + TEX_HEADER_SIZE;
|
||||
case TEX_FORMAT_I8:
|
||||
case TEX_FORMAT_IA8:
|
||||
return (width * height) + TEX_HEADER_SIZE;
|
||||
case TEX_FORMAT_I4:
|
||||
case TEX_FORMAT_IA4:
|
||||
return (width * height / 2) + TEX_HEADER_SIZE;
|
||||
case TEX_FORMAT_CI4:
|
||||
std::cout << "Error: CI4 texture format is not currently supported." << std::endl;
|
||||
throw 1;
|
||||
}
|
||||
|
||||
std::cout << "Error: Invalid texture format " << textureFormat << std::endl;
|
||||
throw 1;
|
||||
}
|
||||
|
||||
void generate_texture_header(std::vector<uint8_t>& outData, int width, int height, int textureFormat,
|
||||
int numberOfTextures, std::string flags, std::string headerBytes) {
|
||||
// I currently do not know what these are for.
|
||||
int A = std::stoi(headerBytes.substr(0, 1), 0, 16);
|
||||
int B = std::stoi(headerBytes.substr(1, 2), 0, 16);
|
||||
int C = std::stoi(headerBytes.substr(3, 2), 0, 16);
|
||||
int D = std::stoi(headerBytes.substr(5, 4), 0, 16);
|
||||
int E = std::stoi(headerBytes.substr(9, 2), 0, 16);
|
||||
|
||||
bool dontComputeSize = (flags.at(2) == 'Z');
|
||||
|
||||
int textureSize = get_texture_size(width, height, textureFormat);
|
||||
|
||||
/* Texture header is 0x20 bytes long */
|
||||
/* 0x00 */ outData.push_back(width);
|
||||
/* 0x01 */ outData.push_back(height);
|
||||
/* 0x02 */ outData.push_back((A << 4) | textureFormat);
|
||||
/* 0x03 */ outData.push_back(B);
|
||||
/* 0x04 */ outData.push_back(C);
|
||||
/* 0x05 */ outData.push_back(0x01); // Always 0x01?
|
||||
/* 0x06 */ outData.push_back((D >> 8) & 0xFF);
|
||||
outData.push_back(D & 0xFF);
|
||||
/* 0x08 */ outData.push_back(0); // Offset to CI4 palette
|
||||
outData.push_back(0);
|
||||
/* 0x0A */ outData.push_back(0);
|
||||
/* 0x0B */ outData.push_back(0); // Initalized in RAM; Number of commands in display list.
|
||||
/* 0x0C */ outData.push_back(0); // Initalized in RAM; Pointer to texture display list
|
||||
outData.push_back(0);
|
||||
outData.push_back(0);
|
||||
outData.push_back(0);
|
||||
/* 0x10 */ outData.push_back(0);
|
||||
/* 0x11 */ outData.push_back(0);
|
||||
/* 0x12 */ outData.push_back(numberOfTextures);
|
||||
/* 0x13 */ outData.push_back(0);
|
||||
/* 0x14 */ outData.push_back(0);
|
||||
/* 0x15 */ outData.push_back(E);
|
||||
if(dontComputeSize) {
|
||||
/* 0x16 */ outData.push_back(0); // Keep it zero, Not sure why though.
|
||||
outData.push_back(0);
|
||||
} else {
|
||||
/* 0x16 */ outData.push_back((textureSize >> 8) & 0xFF); // Texture size
|
||||
outData.push_back(textureSize & 0xFF);
|
||||
}
|
||||
/* 0x18 */ outData.push_back(0);
|
||||
/* 0x19 */ outData.push_back(0);
|
||||
/* 0x1A */ outData.push_back(0);
|
||||
/* 0x1B */ outData.push_back(0);
|
||||
/* 0x1C */ outData.push_back(0);
|
||||
/* 0x1D */ outData.push_back(dontComputeSize ? 1 : 0);
|
||||
/* 0x1E */ outData.push_back(0);
|
||||
/* 0x1F */ outData.push_back(0);
|
||||
}
|
||||
|
||||
int get_bitdepth_from_format(int textureFormat){
|
||||
switch(textureFormat) {
|
||||
case TEX_FORMAT_RGBA32:
|
||||
return 32;
|
||||
case TEX_FORMAT_RGBA16:
|
||||
case TEX_FORMAT_IA16:
|
||||
return 16;
|
||||
case TEX_FORMAT_I8:
|
||||
case TEX_FORMAT_IA8:
|
||||
return 8;
|
||||
case TEX_FORMAT_I4:
|
||||
case TEX_FORMAT_IA4:
|
||||
case TEX_FORMAT_CI4:
|
||||
return 4;
|
||||
}
|
||||
std::cout << "Error: Invalid texture format " << textureFormat << std::endl;
|
||||
throw 1;
|
||||
}
|
||||
|
||||
// std::get<0>(file) = filename, std::get<1>(file) = filepath
|
||||
std::vector<uint8_t> get_texture_binary(std::tuple<std::string, std::string> file, int numberOfTextures) {
|
||||
std::vector<uint8_t> outData;
|
||||
|
||||
std::vector<std::string> elems = split(std::get<0>(file), '.');
|
||||
std::string flags = elems[3];
|
||||
std::string headerBytes = elems[4];
|
||||
int textureFormat = get_texture_type(elems[5]);
|
||||
//std::cout << "format = " << textureFormat << std::endl;
|
||||
bool flipVertically = (flags.at(1) == 'F');
|
||||
|
||||
int width, height;
|
||||
|
||||
std::vector<uint8_t> pngData = load_texture_from_png(std::get<1>(file), textureFormat, &width, &height);
|
||||
|
||||
if(flipVertically) {
|
||||
flip_vertically(pngData, width, height, get_bitdepth_from_format(textureFormat));
|
||||
}
|
||||
|
||||
generate_texture_header(outData, width, height, textureFormat, numberOfTextures, flags, headerBytes);
|
||||
|
||||
bool interlace = ((outData[0x06] & 0x4) == 0x04);
|
||||
|
||||
switch(textureFormat) {
|
||||
case TEX_FORMAT_RGBA32:
|
||||
{
|
||||
if (interlace) {
|
||||
deinterlace(pngData, width, height, 32, 8);
|
||||
}
|
||||
outData.insert(outData.end(), pngData.begin(), pngData.end());
|
||||
break;
|
||||
}
|
||||
case TEX_FORMAT_RGBA16:
|
||||
{
|
||||
uint8_t* rgba16Data = (uint8_t*)malloc(width * height * 2);
|
||||
rgba2raw(rgba16Data, (const rgba*)&pngData[0], width, height, 16);
|
||||
std::vector<uint8_t> rgba16(rgba16Data, rgba16Data + (width * height * 2));
|
||||
free(rgba16Data);
|
||||
if (interlace) {
|
||||
deinterlace(rgba16, width, height, 16, 4);
|
||||
}
|
||||
outData.insert(outData.end(), rgba16.begin(), rgba16.end());
|
||||
break;
|
||||
}
|
||||
case TEX_FORMAT_I8:
|
||||
{
|
||||
uint8_t* i8Data = (uint8_t*)malloc(width * height);
|
||||
i2raw(i8Data, (const ia*)&pngData[0], width, height, 8);
|
||||
std::vector<uint8_t> i8(i8Data, i8Data + (width * height));
|
||||
free(i8Data);
|
||||
if (interlace) {
|
||||
deinterlace(i8, width, height, 8, 4);
|
||||
}
|
||||
outData.insert(outData.end(), i8.begin(), i8.end());
|
||||
break;
|
||||
}
|
||||
case TEX_FORMAT_I4:
|
||||
{
|
||||
uint8_t* i4Data = (uint8_t*)malloc(width * height / 2);
|
||||
i2raw(i4Data, (const ia*)&pngData[0], width, height, 4);
|
||||
std::vector<uint8_t> i4(i4Data, i4Data + (width * height / 2));
|
||||
free(i4Data);
|
||||
if (interlace) {
|
||||
deinterlace(i4, width, height, 4, 4);
|
||||
}
|
||||
outData.insert(outData.end(), i4.begin(), i4.end());
|
||||
break;
|
||||
}
|
||||
case TEX_FORMAT_IA16:
|
||||
{
|
||||
uint8_t* ia16Data = (uint8_t*)malloc(width * height * 2);
|
||||
ia2raw(ia16Data, (const ia*)&pngData[0], width, height, 16);
|
||||
std::vector<uint8_t> ia16(ia16Data, ia16Data + (width * height * 2));
|
||||
free(ia16Data);
|
||||
if (interlace) {
|
||||
deinterlace(ia16, width, height, 16, 4);
|
||||
}
|
||||
outData.insert(outData.end(), ia16.begin(), ia16.end());
|
||||
break;
|
||||
}
|
||||
case TEX_FORMAT_IA8:
|
||||
{
|
||||
uint8_t* ia8Data = (uint8_t*)malloc(width * height);
|
||||
ia2raw(ia8Data, (const ia*)&pngData[0], width, height, 8);
|
||||
std::vector<uint8_t> ia8(ia8Data, ia8Data + (width * height));
|
||||
free(ia8Data);
|
||||
if (interlace) {
|
||||
deinterlace(ia8, width, height, 8, 4);
|
||||
}
|
||||
outData.insert(outData.end(), ia8.begin(), ia8.end());
|
||||
break;
|
||||
}
|
||||
case TEX_FORMAT_IA4:
|
||||
{
|
||||
uint8_t* ia4Data = (uint8_t*)malloc(width * height / 2);
|
||||
ia2raw(ia4Data, (const ia*)&pngData[0], width, height, 4);
|
||||
std::vector<uint8_t> ia4(ia4Data, ia4Data + (width * height / 2));
|
||||
free(ia4Data);
|
||||
if (interlace) {
|
||||
deinterlace(ia4, width, height, 4, 4);
|
||||
}
|
||||
outData.insert(outData.end(), ia4.begin(), ia4.end());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return outData;
|
||||
}
|
||||
|
||||
/*********************************************/
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if(argc != 5) {
|
||||
show_help();
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string input_directory = argv[1];
|
||||
std::string output_filepath = argv[2];
|
||||
std::string texture_name = argv[3];
|
||||
std::string out_texture_name = argv[4];
|
||||
std::string outFilename;
|
||||
|
||||
std::vector<std::tuple<std::string, std::string>> files = get_filenames_from_directory(input_directory, texture_name);
|
||||
|
||||
int numberOfTextures = files.size();
|
||||
std::vector<uint8_t> uncompressed;
|
||||
//std::cout << "Number of textures: " << numberOfTextures << std::endl;
|
||||
for(int i = 0; i < numberOfTextures; i++) {
|
||||
//std::cout << std::get<0>(files[i]) << std::endl;
|
||||
std::vector<uint8_t> binary = get_texture_binary(files[i], numberOfTextures);
|
||||
uncompressed.insert(uncompressed.end(), binary.begin(), binary.end());
|
||||
}
|
||||
|
||||
//outFilename = output_filepath + "/" + texture_name + ".uncmp.bin";
|
||||
//write_binary_file(uncompressed, outFilename);
|
||||
|
||||
|
||||
std::vector<uint8_t> compressedHeader;
|
||||
compressedHeader.insert(compressedHeader.end(), uncompressed.begin(), uncompressed.begin() + 0x20);
|
||||
|
||||
std::string flags = split(std::get<0>(files[0]), '.')[3];
|
||||
if(flags.at(0) == 'C') {
|
||||
compressedHeader[0x1D] = 0x01;
|
||||
}
|
||||
|
||||
DKRCompression compression;
|
||||
std::vector<uint8_t> compressed = compression.compressBuffer(uncompressed);
|
||||
|
||||
// Note: uncompressed is garbled at this point.
|
||||
|
||||
compressed.insert(compressed.begin(), compressedHeader.begin(), compressedHeader.end());
|
||||
|
||||
outFilename = output_filepath + "/" + out_texture_name + ".bin";
|
||||
write_binary_file(compressed, outFilename);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,100 @@
|
||||
#ifndef N64GRAPHICS_H_
|
||||
#define N64GRAPHICS_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// intermediate formats
|
||||
typedef struct _rgba
|
||||
{
|
||||
uint8_t red;
|
||||
uint8_t green;
|
||||
uint8_t blue;
|
||||
uint8_t alpha;
|
||||
} rgba;
|
||||
|
||||
typedef struct _ia
|
||||
{
|
||||
uint8_t intensity;
|
||||
uint8_t alpha;
|
||||
} ia;
|
||||
|
||||
// CI palette
|
||||
typedef struct
|
||||
{
|
||||
uint16_t data[256];
|
||||
int max; // max number of entries
|
||||
int used; // number of entries used
|
||||
} palette_t;
|
||||
|
||||
//---------------------------------------------------------
|
||||
// N64 RGBA/IA/I/CI -> intermediate RGBA/IA
|
||||
//---------------------------------------------------------
|
||||
|
||||
// N64 raw RGBA16/RGBA32 -> intermediate RGBA
|
||||
rgba *raw2rgba(const uint8_t *raw, int width, int height, int depth);
|
||||
|
||||
// N64 raw IA1/IA4/IA8/IA16 -> intermediate IA
|
||||
ia *raw2ia(const uint8_t *raw, int width, int height, int depth);
|
||||
|
||||
// N64 raw I4/I8 -> intermediate IA
|
||||
ia *raw2i(const uint8_t *raw, int width, int height, int depth);
|
||||
|
||||
//---------------------------------------------------------
|
||||
// intermediate RGBA/IA -> N64 RGBA/IA/I/CI
|
||||
// returns length written to 'raw' used or -1 on error
|
||||
//---------------------------------------------------------
|
||||
|
||||
// intermediate RGBA -> N64 raw RGBA16/RGBA32
|
||||
int rgba2raw(uint8_t *raw, const rgba *img, int width, int height, int depth);
|
||||
|
||||
// intermediate IA -> N64 raw IA1/IA4/IA8/IA16
|
||||
int ia2raw(uint8_t *raw, const ia *img, int width, int height, int depth);
|
||||
|
||||
// intermediate IA -> N64 raw I4/I8
|
||||
int i2raw(uint8_t *raw, const ia *img, int width, int height, int depth);
|
||||
|
||||
|
||||
//---------------------------------------------------------
|
||||
// N64 CI <-> N64 RGBA16/IA16
|
||||
//---------------------------------------------------------
|
||||
|
||||
// N64 CI raw data and palette to raw data (either RGBA16 or IA16)
|
||||
uint8_t *ci2raw(const uint8_t *rawci, const uint8_t *palette, int width, int height, int ci_depth);
|
||||
|
||||
// convert from raw (RGBA16 or IA16) format to CI + palette
|
||||
int raw2ci(uint8_t *rawci, palette_t *pal, const uint8_t *raw, int raw_len, int ci_depth);
|
||||
|
||||
|
||||
//---------------------------------------------------------
|
||||
// intermediate RGBA/IA -> PNG
|
||||
//---------------------------------------------------------
|
||||
|
||||
// intermediate RGBA write to PNG file
|
||||
int rgba2png(const char *png_filename, const rgba *img, int width, int height);
|
||||
|
||||
// intermediate IA write to grayscale PNG file
|
||||
int ia2png(const char *png_filename, const ia *img, int width, int height);
|
||||
|
||||
|
||||
//---------------------------------------------------------
|
||||
// PNG -> intermediate RGBA/IA
|
||||
//---------------------------------------------------------
|
||||
|
||||
// PNG file -> intermediate RGBA
|
||||
rgba *png2rgba(const char *png_filename, int *width, int *height);
|
||||
|
||||
// PNG file -> intermediate IA
|
||||
ia *png2ia(const char *png_filename, int *width, int *height);
|
||||
|
||||
|
||||
//---------------------------------------------------------
|
||||
// version
|
||||
//---------------------------------------------------------
|
||||
|
||||
// get version of underlying graphics reading library
|
||||
const char *n64graphics_get_read_version(void);
|
||||
|
||||
// get version of underlying graphics writing library
|
||||
const char *n64graphics_get_write_version(void);
|
||||
|
||||
#endif // N64GRAPHICS_H_
|
||||
@@ -0,0 +1,276 @@
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#if defined(_MSC_VER) || defined(__MINGW32__)
|
||||
#include <io.h>
|
||||
#include <sys/utime.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <utime.h>
|
||||
#endif
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
// global verbosity setting
|
||||
int g_verbosity = 0;
|
||||
|
||||
int read_s16_be(unsigned char *buf)
|
||||
{
|
||||
unsigned tmp = read_u16_be(buf);
|
||||
int ret;
|
||||
if (tmp > 0x7FFF) {
|
||||
ret = -((int)0x10000 - (int)tmp);
|
||||
} else {
|
||||
ret = (int)tmp;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
float read_f32_be(unsigned char *buf)
|
||||
{
|
||||
union {uint32_t i; float f;} ret;
|
||||
ret.i = read_u32_be(buf);
|
||||
return ret.f;
|
||||
}
|
||||
|
||||
int is_power2(unsigned int val)
|
||||
{
|
||||
while (((val & 1) == 0) && (val > 1)) {
|
||||
val >>= 1;
|
||||
}
|
||||
return (val == 1);
|
||||
}
|
||||
|
||||
void fprint_hex(FILE *fp, const unsigned char *buf, int length)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < length; i++) {
|
||||
fprint_byte(fp, buf[i]);
|
||||
fputc(' ', fp);
|
||||
}
|
||||
}
|
||||
|
||||
void fprint_hex_source(FILE *fp, const unsigned char *buf, int length)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < length; i++) {
|
||||
if (i > 0) fputs(", ", fp);
|
||||
fputs("0x", fp);
|
||||
fprint_byte(fp, buf[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void print_hex(const unsigned char *buf, int length)
|
||||
{
|
||||
fprint_hex(stdout, buf, length);
|
||||
}
|
||||
|
||||
void swap_bytes(unsigned char *data, long length)
|
||||
{
|
||||
long i;
|
||||
unsigned char tmp;
|
||||
for (i = 0; i < length; i += 2) {
|
||||
tmp = data[i];
|
||||
data[i] = data[i+1];
|
||||
data[i+1] = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
void reverse_endian(unsigned char *data, long length)
|
||||
{
|
||||
long i;
|
||||
unsigned char tmp;
|
||||
for (i = 0; i < length; i += 4) {
|
||||
tmp = data[i];
|
||||
data[i] = data[i+3];
|
||||
data[i+3] = tmp;
|
||||
tmp = data[i+1];
|
||||
data[i+1] = data[i+2];
|
||||
data[i+2] = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
long filesize(const char *filename)
|
||||
{
|
||||
struct stat st;
|
||||
|
||||
if (stat(filename, &st) == 0) {
|
||||
return st.st_size;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void touch_file(const char *filename)
|
||||
{
|
||||
int fd;
|
||||
//fd = open(filename, O_WRONLY|O_CREAT|O_NOCTTY|O_NONBLOCK, 0666);
|
||||
fd = open(filename, O_WRONLY|O_CREAT, 0666);
|
||||
if (fd >= 0) {
|
||||
utime(filename, NULL);
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
|
||||
long read_file(const char *file_name, unsigned char **data)
|
||||
{
|
||||
FILE *in;
|
||||
unsigned char *in_buf = NULL;
|
||||
long file_size;
|
||||
long bytes_read;
|
||||
in = fopen(file_name, "rb");
|
||||
if (in == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// allocate buffer to read from offset to end of file
|
||||
fseek(in, 0, SEEK_END);
|
||||
file_size = ftell(in);
|
||||
|
||||
// sanity check
|
||||
if (file_size > 256*MB) {
|
||||
return -2;
|
||||
}
|
||||
|
||||
in_buf = (unsigned char *)malloc(file_size);
|
||||
fseek(in, 0, SEEK_SET);
|
||||
|
||||
// read bytes
|
||||
bytes_read = fread(in_buf, 1, file_size, in);
|
||||
if (bytes_read != file_size) {
|
||||
return -3;
|
||||
}
|
||||
|
||||
fclose(in);
|
||||
*data = in_buf;
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
long write_file(const char *file_name, unsigned char *data, long length)
|
||||
{
|
||||
FILE *out;
|
||||
long bytes_written;
|
||||
// open output file
|
||||
out = fopen(file_name, "wb");
|
||||
if (out == NULL) {
|
||||
perror(file_name);
|
||||
return -1;
|
||||
}
|
||||
bytes_written = fwrite(data, 1, length, out);
|
||||
fclose(out);
|
||||
return bytes_written;
|
||||
}
|
||||
|
||||
void generate_filename(const char *in_name, char *out_name, char *extension)
|
||||
{
|
||||
char tmp_name[FILENAME_MAX];
|
||||
int len;
|
||||
int i;
|
||||
strcpy(tmp_name, in_name);
|
||||
len = strlen(tmp_name);
|
||||
for (i = len - 1; i > 0; i--) {
|
||||
if (tmp_name[i] == '.') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i <= 0) {
|
||||
i = len;
|
||||
}
|
||||
tmp_name[i] = '\0';
|
||||
sprintf(out_name, "%s.%s", tmp_name, extension);
|
||||
}
|
||||
|
||||
char *basename_(const char *name)
|
||||
{
|
||||
const char *base = name;
|
||||
while (*name) {
|
||||
if (*name++ == '/') {
|
||||
base = name;
|
||||
}
|
||||
}
|
||||
return (char *)base;
|
||||
}
|
||||
|
||||
void make_dir(const char *dir_name)
|
||||
{
|
||||
struct stat st = {0};
|
||||
if (stat(dir_name, &st) == -1) {
|
||||
mkdir(dir_name, 0755);
|
||||
}
|
||||
}
|
||||
|
||||
long copy_file(const char *src_name, const char *dst_name)
|
||||
{
|
||||
unsigned char *buf;
|
||||
long bytes_written;
|
||||
long bytes_read;
|
||||
|
||||
bytes_read = read_file(src_name, &buf);
|
||||
|
||||
if (bytes_read > 0) {
|
||||
bytes_written = write_file(dst_name, buf, bytes_read);
|
||||
if (bytes_written != bytes_read) {
|
||||
bytes_read = -1;
|
||||
}
|
||||
free(buf);
|
||||
}
|
||||
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
void dir_list_ext(const char *dir, const char *extension, dir_list *list)
|
||||
{
|
||||
char *pool;
|
||||
char *pool_ptr;
|
||||
struct dirent *entry;
|
||||
DIR *dfd;
|
||||
int idx;
|
||||
|
||||
dfd = opendir(dir);
|
||||
if (dfd == NULL) {
|
||||
ERROR("Can't open '%s'\n", dir);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
pool = (char *)malloc(FILENAME_MAX * MAX_DIR_FILES);
|
||||
pool_ptr = pool;
|
||||
|
||||
idx = 0;
|
||||
while ((entry = readdir(dfd)) != NULL && idx < MAX_DIR_FILES) {
|
||||
if (!extension || str_ends_with(entry->d_name, extension)) {
|
||||
sprintf(pool_ptr, "%s/%s", dir, entry->d_name);
|
||||
list->files[idx] = pool_ptr;
|
||||
pool_ptr += strlen(pool_ptr) + 1;
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
list->count = idx;
|
||||
|
||||
closedir(dfd);
|
||||
}
|
||||
|
||||
void dir_list_free(dir_list *list)
|
||||
{
|
||||
// assume first entry in array is allocated
|
||||
if (list->files[0]) {
|
||||
free(list->files[0]);
|
||||
list->files[0] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
int str_ends_with(const char *str, const char *suffix)
|
||||
{
|
||||
if (!str || !suffix) {
|
||||
return 0;
|
||||
}
|
||||
size_t len_str = strlen(str);
|
||||
size_t len_suffix = strlen(suffix);
|
||||
if (len_suffix > len_str) {
|
||||
return 0;
|
||||
}
|
||||
return (0 == strncmp(str + len_str - len_suffix, suffix, len_suffix));
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
#ifndef UTILS_H_
|
||||
#define UTILS_H_
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
// defines
|
||||
|
||||
// printing size_t varies by compiler
|
||||
#if defined(_MSC_VER) || defined(__MINGW32__)
|
||||
#define SIZE_T_FORMAT "%Iu"
|
||||
#else
|
||||
#define SIZE_T_FORMAT "%zu"
|
||||
#endif
|
||||
|
||||
#define KB 1024
|
||||
#define MB (1024 * KB)
|
||||
|
||||
// number of elements in statically declared array
|
||||
#define DIM(S_ARR_) (sizeof(S_ARR_) / sizeof(S_ARR_[0]))
|
||||
|
||||
#define MIN(A_, B_) ((A_) < (B_) ? (A_) : (B_))
|
||||
#define MAX(A_, B_) ((A_) > (B_) ? (A_) : (B_))
|
||||
|
||||
// align value to N-byte boundary
|
||||
#define ALIGN(VAL_, ALIGNMENT_) (((VAL_) + ((ALIGNMENT_) - 1)) & ~((ALIGNMENT_) - 1))
|
||||
|
||||
// read/write u32/16 big/little endian
|
||||
#define read_u32_be(buf) (unsigned int)(((buf)[0] << 24) + ((buf)[1] << 16) + ((buf)[2] << 8) + ((buf)[3]))
|
||||
#define read_u32_le(buf) (unsigned int)(((buf)[1] << 24) + ((buf)[0] << 16) + ((buf)[3] << 8) + ((buf)[2]))
|
||||
#define write_u32_be(buf, val) do { \
|
||||
(buf)[0] = ((val) >> 24) & 0xFF; \
|
||||
(buf)[1] = ((val) >> 16) & 0xFF; \
|
||||
(buf)[2] = ((val) >> 8) & 0xFF; \
|
||||
(buf)[3] = (val) & 0xFF; \
|
||||
} while(0)
|
||||
#define read_u16_be(buf) (((buf)[0] << 8) + ((buf)[1]))
|
||||
#define write_u16_be(buf, val) do { \
|
||||
(buf)[0] = ((val) >> 8) & 0xFF; \
|
||||
(buf)[1] = ((val)) & 0xFF; \
|
||||
} while(0)
|
||||
|
||||
// print nibbles and bytes
|
||||
#define fprint_nibble(FP, NIB_) fputc((NIB_) < 10 ? ('0' + (NIB_)) : ('A' + (NIB_) - 0xA), FP)
|
||||
#define fprint_byte(FP, BYTE_) do { \
|
||||
fprint_nibble(FP, (BYTE_) >> 4); \
|
||||
fprint_nibble(FP, (BYTE_) & 0x0F); \
|
||||
} while(0)
|
||||
#define print_nibble(NIB_) fprint_nibble(stdout, NIB_)
|
||||
#define print_byte(BYTE_) fprint_byte(stdout, BYTE_)
|
||||
|
||||
// Windows compatibility
|
||||
#if defined(_MSC_VER) || defined(__MINGW32__)
|
||||
#include <direct.h>
|
||||
#define mkdir(DIR_, PERM_) _mkdir(DIR_)
|
||||
#ifndef strcasecmp
|
||||
#define strcasecmp(A, B) stricmp(A, B)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// typedefs
|
||||
|
||||
#define MAX_DIR_FILES 128
|
||||
typedef struct
|
||||
{
|
||||
char *files[MAX_DIR_FILES];
|
||||
int count;
|
||||
} dir_list;
|
||||
|
||||
// global verbosity setting
|
||||
extern int g_verbosity;
|
||||
|
||||
#define ERROR(...) fprintf(stderr, __VA_ARGS__)
|
||||
#define INFO(...) if (g_verbosity) printf(__VA_ARGS__)
|
||||
#define INFO_HEX(...) if (g_verbosity) print_hex(__VA_ARGS__)
|
||||
|
||||
// functions
|
||||
|
||||
// convert two bytes in big-endian to signed int
|
||||
int read_s16_be(unsigned char *buf);
|
||||
|
||||
// convert four bytes in big-endian to float
|
||||
float read_f32_be(unsigned char *buf);
|
||||
|
||||
// determine if value is power of 2
|
||||
// returns 1 if val is power of 2, 0 otherwise
|
||||
int is_power2(unsigned int val);
|
||||
|
||||
// print buffer as hex bytes
|
||||
// fp: file pointer
|
||||
// buf: buffer to read bytes from
|
||||
// length: length of buffer to print
|
||||
void fprint_hex(FILE *fp, const unsigned char *buf, int length);
|
||||
void fprint_hex_source(FILE *fp, const unsigned char *buf, int length);
|
||||
void print_hex(const unsigned char *buf, int length);
|
||||
|
||||
// perform byteswapping to convert from v64 to z64 ordering
|
||||
void swap_bytes(unsigned char *data, long length);
|
||||
|
||||
// reverse endian to convert from n64 to z64 ordering
|
||||
void reverse_endian(unsigned char *data, long length);
|
||||
|
||||
// get size of file without opening it;
|
||||
// returns file size or negative on error
|
||||
long filesize(const char *file_name);
|
||||
|
||||
// update file timestamp to now, creating it if it doesn't exist
|
||||
void touch_file(const char *filename);
|
||||
|
||||
// read entire contents of file into buffer
|
||||
// returns file size or negative on error
|
||||
long read_file(const char *file_name, unsigned char **data);
|
||||
|
||||
// write buffer to file
|
||||
// returns number of bytes written out or -1 on failure
|
||||
long write_file(const char *file_name, unsigned char *data, long length);
|
||||
|
||||
// generate an output file name from input name by replacing file extension
|
||||
// in_name: input file name
|
||||
// out_name: buffer to write output name in
|
||||
// extension: new file extension to use
|
||||
void generate_filename(const char *in_name, char *out_name, char *extension);
|
||||
|
||||
// extract base filename from file path
|
||||
// name: path to file
|
||||
// returns just the file name after the last '/'
|
||||
char *basename_(const char *name);
|
||||
|
||||
// make a directory if it doesn't exist
|
||||
// dir_name: name of the directory
|
||||
void make_dir(const char *dir_name);
|
||||
|
||||
// copy a file from src_name to dst_name. will not make directories
|
||||
// src_name: source file name
|
||||
// dst_name: destination file name
|
||||
long copy_file(const char *src_name, const char *dst_name);
|
||||
|
||||
// list a directory, optionally filtering files by extension
|
||||
// dir: directory to list files in
|
||||
// extension: extension to filter files by (NULL if no filtering)
|
||||
// list: output list and count
|
||||
void dir_list_ext(const char *dir, const char *extension, dir_list *list);
|
||||
|
||||
// free associated date from a directory list
|
||||
// list: directory list filled in by dir_list_ext() call
|
||||
void dir_list_free(dir_list *list);
|
||||
|
||||
// determine if a string ends with another string
|
||||
// str: string to check if ends with 'suffix'
|
||||
// suffix: string to see if 'str' ends with
|
||||
// returns 1 if 'str' ends with 'suffix'
|
||||
int str_ends_with(const char *str, const char *suffix);
|
||||
|
||||
#endif // UTILS_H_
|
||||
+32
-14
@@ -1,4 +1,4 @@
|
||||
from os import makedirs
|
||||
from os import makedirs, system
|
||||
import shutil
|
||||
|
||||
from file_util import FileUtil
|
||||
@@ -101,27 +101,45 @@ def extract_assets_from_rom(config, rom):
|
||||
rangeSize = romRange.size
|
||||
rangeStart = romOffset
|
||||
rangeEnd = romOffset + rangeSize
|
||||
romRange.start = rangeStart
|
||||
# TODO: Add more types
|
||||
if romRange.type == 'binary':
|
||||
binaryName = romRange.properties[0]
|
||||
binaryExtractLocation = romRange.properties[1]
|
||||
outputFilename = binaryName + '.' + "{:06x}".format(rangeStart) + '.bin'
|
||||
outputDirectory = ASSETS_DIRECTORY + '/'
|
||||
if len(config.subfolder) > 0:
|
||||
outputDirectory += config.subfolder + '/'
|
||||
outputDirectory += binaryExtractLocation + '/'
|
||||
data = rom.get_bytes_from_range(rangeStart, rangeEnd)
|
||||
write_data_to_file(romRange, rangeStart, outputDirectory, outputFilename, 'wb', data)
|
||||
if rangeStart > 0x1000: # Exclude boot.000040.bin from assets.s
|
||||
assetsImportText += '.incbin "' + outputDirectory + outputFilename + '"\n'
|
||||
outFilename = extractBinary(config, rom, romRange, rangeStart, rangeEnd)
|
||||
elif romRange.type == 'compressed':
|
||||
outFilename = extractCompressed(config, rom, romRange, rangeStart, rangeEnd)
|
||||
elif romRange.type == 'texture':
|
||||
outFilename = extractTexture(config, rom, romRange, rangeStart, rangeEnd)
|
||||
elif romRange.type == 'noextract':
|
||||
pass
|
||||
else:
|
||||
raise Exception('Invalid range type: "' + romRange.type + '"')
|
||||
romOffset += rangeSize
|
||||
assetsImportFile.write(assetsImportText)
|
||||
|
||||
def extractBinary(config, rom, romRange, rangeStart, rangeEnd):
|
||||
binaryName = romRange.properties[0]
|
||||
binaryExtractLocation = romRange.properties[1]
|
||||
outputFilename = binaryName + '.' + "{:06x}".format(romRange.start) + '.bin'
|
||||
outputDirectory = ASSETS_DIRECTORY + '/'
|
||||
if len(config.subfolder) > 0:
|
||||
outputDirectory += config.subfolder + '/'
|
||||
outputDirectory += binaryExtractLocation + '/'
|
||||
data = rom.get_bytes_from_range(rangeStart, rangeEnd)
|
||||
write_data_to_file(romRange, outputDirectory, outputFilename, 'wb', data)
|
||||
return outputDirectory + outputFilename
|
||||
|
||||
def extractCompressed(config, rom, romRange, rangeStart, rangeEnd):
|
||||
outFilename = extractBinary(config, rom, romRange, rangeStart, rangeEnd)
|
||||
system('./tools/dkr_decompressor -d "' + outFilename + '" "' + outFilename + '"')
|
||||
return outFilename
|
||||
|
||||
def extractTexture(config, rom, romRange, rangeStart, rangeEnd):
|
||||
if rom.bytes[rangeStart + 0x1D] == 0x01:
|
||||
return extractCompressed(config, rom, romRange, rangeStart + 0x20, rangeEnd)
|
||||
else:
|
||||
return extractBinary(config, rom, romRange, rangeStart, rangeEnd)
|
||||
|
||||
def write_data_to_file(range, rangeStart, directory, filename, flags, data):
|
||||
def write_data_to_file(range, directory, filename, flags, data):
|
||||
try:
|
||||
makedirs(directory)
|
||||
except OSError as error:
|
||||
@@ -131,7 +149,7 @@ def write_data_to_file(range, rangeStart, directory, filename, flags, data):
|
||||
outFile.write(bytearray(data))
|
||||
elif flags == 'w':
|
||||
outFile.write(data)
|
||||
print('Extracted ' + range.get_range_string(rangeStart) + ' to ' + directory + filename)
|
||||
print('Extracted ' + range.get_range_string() + ' to ' + directory + filename)
|
||||
|
||||
|
||||
def _bytes_to_int32(arr, offset):
|
||||
|
||||
@@ -5,9 +5,10 @@ class ConfigRange:
|
||||
self.size = size
|
||||
self.type = type.lower()
|
||||
self.properties = properties
|
||||
self.start = -1
|
||||
|
||||
def get_range_string(self, start):
|
||||
return "{:06x}".format(start) + '-' + "{:06x}".format(start + self.size)
|
||||
def get_range_string(self):
|
||||
return "{:06x}".format(self.start) + '-' + "{:06x}".format(self.start + self.size)
|
||||
|
||||
def __repr__(self):
|
||||
return "{:06x}".format(self.size) + ', ' + self.type + ', ' + str(self.properties)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user