Updated with new tools and texture generation

This commit is contained in:
David Benepe
2020-06-12 17:09:01 -05:00
parent 4d038b42a5
commit d2c942115b
36 changed files with 23185 additions and 2897 deletions
+3 -1
View File
@@ -1 +1,3 @@
/n64crc
/n64crc
/dkr_extractor
/dkr_texbuilder
+14 -1
View File
@@ -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
+25
View File
@@ -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
+121
View File
@@ -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;
};
+453
View File
@@ -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;
};
+138
View File
@@ -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;
}
+33
View File
@@ -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;
};
+429
View File
@@ -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
+100
View File
@@ -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_
+276
View File
@@ -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));
}
+153
View File
@@ -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
View File
@@ -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):
+3 -2
View File
@@ -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