Initial work on globals.iff
- iffexplore to decocde globals.iff - font2png parses the font blobs found therein
This commit is contained in:
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
game/
|
||||
objs/
|
||||
tmp/
|
||||
*~
|
||||
iffexplore
|
||||
font2png
|
||||
|
||||
33
Makefile
Normal file
33
Makefile
Normal file
@@ -0,0 +1,33 @@
|
||||
CXX=g++
|
||||
CXXOPTS=-Og -ggdb -Wall -Wextra -pedantic -std=c++14 -flto
|
||||
LDOPTS=
|
||||
|
||||
IFFEXPLORE_CXXSRCS=iffexplore.cc
|
||||
IFFEXPLORE_OBJS=$(addprefix objs/,$(IFFEXPLORE_CXXSRCS:.cc=.o))
|
||||
|
||||
FONT2PNG_CXXSRCS=font2png.cc
|
||||
FONT2PNG_OBJS=$(addprefix objs/,$(FONT2PNG_CXXSRCS:.cc=.o))
|
||||
FONT2PNG_LIBS=-lpng
|
||||
|
||||
all: iffexplore font2png
|
||||
|
||||
objs/%.o: %.cc
|
||||
$(CXX) $(CXXOPTS) -c -MMD -MP -o $@ $<
|
||||
@cp objs/$*.d objs/$*.P; rm -f objs/$*.d
|
||||
|
||||
%.pb.cc %.pb.h: %.proto
|
||||
protoc --cpp_out=. $<
|
||||
|
||||
iffexplore: $(IFFEXPLORE_OBJS)
|
||||
$(CXX) $(CXXOPTS) $(LDOPTS) -o $@ $(IFFEXPLORE_OBJS)
|
||||
|
||||
font2png: $(FONT2PNG_OBJS)
|
||||
$(CXX) $(CXXOPTS) $(LDOPTS) -o $@ $(FONT2PNG_OBJS) $(FONT2PNG_LIBS)
|
||||
|
||||
|
||||
clean:
|
||||
rm -f iffexplore $(IFFEXPLORE_OBJS) $(addprefix objs/,$(IFFEXPLORE_CXXSRCS:.cc=.P))
|
||||
|
||||
.PHONY: clean all
|
||||
|
||||
-include $(addprefix objs/,$(IFFEXPLORE_CXXSRCS:.cc=.P))
|
||||
62
common.hh
Normal file
62
common.hh
Normal file
@@ -0,0 +1,62 @@
|
||||
#ifndef WC3RE__COMMON_HH__
|
||||
#define WC3RE__COMMON_HH__
|
||||
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include <cerrno>
|
||||
#include <memory>
|
||||
|
||||
using namespace std::string_literals;
|
||||
|
||||
class Exception {
|
||||
public:
|
||||
Exception() : msg_() {
|
||||
}
|
||||
|
||||
Exception(std::string msg) : msg_(std::move(msg)) {
|
||||
}
|
||||
|
||||
virtual ~Exception() {}
|
||||
|
||||
virtual std::string toString() const {
|
||||
return "Exception: "s + msg_;
|
||||
}
|
||||
protected:
|
||||
std::string msg_;
|
||||
};
|
||||
|
||||
class FormatException : public Exception {
|
||||
public:
|
||||
FormatException(std::string msg) : Exception(std::move(msg)) {
|
||||
}
|
||||
|
||||
std::string toString() const override {
|
||||
return "FormatException: "s + msg_;
|
||||
}
|
||||
};
|
||||
|
||||
class POSIXException : public Exception {
|
||||
public:
|
||||
POSIXException(int err, std::string msg = ""): Exception(std::move(msg)), err_(err) {
|
||||
}
|
||||
|
||||
int getErr() const {return err_;}
|
||||
|
||||
std::string toString() const override {
|
||||
return "POSIXException: "s + msg_ + ": "s + std::strerror(err_);
|
||||
}
|
||||
|
||||
private:
|
||||
int err_;
|
||||
};
|
||||
|
||||
struct FILEDeleter {
|
||||
void operator()(FILE* file) const {
|
||||
fclose(file);
|
||||
}
|
||||
};
|
||||
|
||||
using FILEUPtr = std::unique_ptr<FILE, FILEDeleter>;
|
||||
|
||||
#endif
|
||||
208
font2png.cc
Normal file
208
font2png.cc
Normal file
@@ -0,0 +1,208 @@
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <cerrno>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <png.h>
|
||||
|
||||
#include "common.hh"
|
||||
|
||||
struct Image {
|
||||
unsigned height, width;
|
||||
std::vector<uint8_t> data;
|
||||
};
|
||||
|
||||
struct Font {
|
||||
unsigned numChars;
|
||||
unsigned charHeight;
|
||||
unsigned maxCharWidth;
|
||||
std::vector<Image> chars;
|
||||
};
|
||||
|
||||
struct FontHeader { // little endian
|
||||
uint32_t unknown1; // (always)? "1.\0\0"
|
||||
uint32_t entries;
|
||||
uint32_t charWidth;
|
||||
uint32_t unknown3; // (always)? 0x000000ff
|
||||
};
|
||||
|
||||
Font parseFont(FILE* file, size_t length)
|
||||
{
|
||||
// if (fseeko(file, 16, SEEK_SET) != 0) {
|
||||
// throw POSIXException(errno, "Could not seek");
|
||||
// }
|
||||
|
||||
// uint32_t data, last_data = 0;
|
||||
// int last_diff = -1, count = 0;
|
||||
// size_t pos = 16;
|
||||
// while (pos < length) {
|
||||
// if (fread(&data, 4, 1, file) != 1) {
|
||||
// if (feof(file))
|
||||
// throw Exception{"Unexpected EOF"};
|
||||
// throw POSIXException{errno, "Could not read"};
|
||||
// }
|
||||
|
||||
// if ((last_diff == -1) ||
|
||||
// (data-last_data == last_diff)) {
|
||||
// ++count;
|
||||
// last_diff = data-last_data;
|
||||
// // printf("%.8x\n", data);
|
||||
// } else {
|
||||
// if ((count > 1) && last_diff )
|
||||
// printf("%d times diff %d\n", count, last_diff);
|
||||
// count = 0;
|
||||
// last_diff = -1;
|
||||
// }
|
||||
// last_data = data;
|
||||
// pos += 4;
|
||||
// }
|
||||
|
||||
FontHeader header;
|
||||
if (fread(&header, sizeof(FontHeader), 1, file) != 1)
|
||||
throw POSIXException{errno, "Could not read"};
|
||||
|
||||
const unsigned index_start = sizeof(FontHeader);
|
||||
const unsigned index_end = index_start+4*header.entries;
|
||||
const unsigned char_width = header.charWidth;
|
||||
|
||||
std::vector<uint32_t> indices;
|
||||
for (unsigned i = index_start;i < index_end;i+=4) {
|
||||
uint32_t data;
|
||||
if (fread(&data, 4, 1, file) != 1)
|
||||
throw POSIXException{errno, "Could not read"};
|
||||
indices.push_back(data);
|
||||
}
|
||||
|
||||
auto minmax = std::minmax_element(indices.begin(), indices.end());
|
||||
printf("Index range %u .. %u\n",
|
||||
*minmax.first, *minmax.second);
|
||||
|
||||
Font ret;
|
||||
ret.numChars = header.entries;
|
||||
ret.charHeight = char_width;
|
||||
ret.maxCharWidth = 0;
|
||||
for (auto const& index : indices) {
|
||||
uint32_t len;
|
||||
if (fseeko(file, index, SEEK_SET) != 0)
|
||||
throw POSIXException(errno, "Could not seek");
|
||||
|
||||
if (fread(&len, 4, 1, file) != 1)
|
||||
throw POSIXException{errno, "Could not read"};
|
||||
|
||||
if (!len) {
|
||||
ret.chars.push_back(Image{});
|
||||
continue;
|
||||
}
|
||||
|
||||
Image chr;
|
||||
chr.height = char_width;
|
||||
chr.width = len;
|
||||
chr.data.resize(char_width*len);
|
||||
|
||||
if (chr.width > ret.maxCharWidth)
|
||||
ret.maxCharWidth = chr.width;
|
||||
|
||||
if (fread(chr.data.data(), len*char_width, 1, file) != 1)
|
||||
throw POSIXException{errno, "Could not read"};
|
||||
|
||||
ret.chars.push_back(chr);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void saveImage(std::string const& name, Image const& img)
|
||||
{
|
||||
FILEUPtr file{fopen(name.c_str(), "wb")};
|
||||
if (!file)
|
||||
throw POSIXException{errno, "Could not open "s + name};
|
||||
|
||||
png_image pngImage;
|
||||
pngImage.version = PNG_IMAGE_VERSION;
|
||||
pngImage.width = img.width;
|
||||
pngImage.height = img.height;
|
||||
pngImage.format = PNG_FORMAT_GRAY;
|
||||
pngImage.flags = 0;
|
||||
pngImage.opaque = nullptr;
|
||||
pngImage.colormap_entries = 0;
|
||||
pngImage.warning_or_error = 0;
|
||||
memset(pngImage.message, '\0', 64);
|
||||
|
||||
if (!png_image_write_to_stdio(&pngImage, file.get(), false, img.data.data(),
|
||||
img.width, nullptr))
|
||||
throw Exception{"PNG write failed: "s + pngImage.message};
|
||||
}
|
||||
|
||||
Image makeFontImage(Font const& font)
|
||||
{
|
||||
const unsigned charsPerLine = 16;
|
||||
|
||||
Image ret;
|
||||
ret.width = font.maxCharWidth*charsPerLine;
|
||||
unsigned numCharLines = font.numChars/charsPerLine;
|
||||
if ((font.numChars%charsPerLine) != 0)
|
||||
++numCharLines;
|
||||
ret.height = font.charHeight*numCharLines;
|
||||
|
||||
ret.data.reserve(ret.width*ret.height);
|
||||
|
||||
for (unsigned int c_y = 0;c_y < numCharLines;++c_y) {
|
||||
for (unsigned int y = 0;y < font.charHeight;++y) {
|
||||
for (unsigned int c_x = 0;c_x < charsPerLine;++c_x) {
|
||||
unsigned charNo = c_y*charsPerLine+c_x;
|
||||
if (charNo > font.numChars) {
|
||||
for (unsigned x = 0;x < font.maxCharWidth;++x)
|
||||
ret.data.push_back(0xff);
|
||||
} else {
|
||||
for (unsigned x = 0;x < font.chars[charNo].width;++x)
|
||||
ret.data.push_back(font.chars[charNo].data[y*font.chars[charNo].width+x]);
|
||||
for (unsigned x = font.chars[charNo].width; x < font.maxCharWidth;++x)
|
||||
ret.data.push_back(0xff);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
if (argc != 2) {
|
||||
fprintf(stderr, "Usage: %s font-file\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
try {
|
||||
FILEUPtr fontFile{fopen(argv[1], "rb")};
|
||||
if (!fontFile) {
|
||||
throw POSIXException{errno, "Could not open "s + argv[1]};
|
||||
}
|
||||
|
||||
struct stat statBuf;
|
||||
|
||||
if (fstat(fileno(fontFile.get()), &statBuf) != 0) {
|
||||
throw POSIXException(errno, "Could not stat");
|
||||
}
|
||||
|
||||
auto font = parseFont(fontFile.get(), statBuf.st_size);
|
||||
saveImage(argv[1] + ".png"s, makeFontImage(font));
|
||||
|
||||
} catch (POSIXException &ex) {
|
||||
fprintf(stderr, "%s\n", ex.toString().c_str());
|
||||
return 2;
|
||||
} catch (Exception &ex) {
|
||||
fprintf(stderr, "%s\n", ex.toString().c_str());
|
||||
return 3;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
167
iffexplore.cc
Normal file
167
iffexplore.cc
Normal file
@@ -0,0 +1,167 @@
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <cerrno>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "common.hh"
|
||||
|
||||
struct ChunkHeader {
|
||||
char typeID[4];
|
||||
uint32_t length;
|
||||
};
|
||||
|
||||
void parseBlob(FILE *iffFile, off_t start, off_t length, char name[4], unsigned level)
|
||||
{
|
||||
static unsigned count = 0;
|
||||
|
||||
if (fseeko(iffFile, start, SEEK_SET) != 0) {
|
||||
throw POSIXException(errno, "Could not seek");
|
||||
}
|
||||
|
||||
std::vector<char> buf(length);
|
||||
if (fread(buf.data(), length, 1, iffFile) != 1)
|
||||
throw POSIXException{errno, "Could not read data"};
|
||||
|
||||
// Check if BLOB is string
|
||||
{
|
||||
enum class State { STARTASCII, NULLS, ERROR };
|
||||
State state = State::STARTASCII;
|
||||
for (char const& c : buf) {
|
||||
switch(state) {
|
||||
case State::STARTASCII:
|
||||
if (isprint(c))
|
||||
continue;
|
||||
if (c == '\0')
|
||||
state = State::NULLS;
|
||||
else
|
||||
state = State::ERROR;
|
||||
break;
|
||||
case State::NULLS:
|
||||
if (c != '\0')
|
||||
state = State::ERROR;
|
||||
break;
|
||||
case State::ERROR:
|
||||
break;
|
||||
}
|
||||
|
||||
if (state == State::ERROR)
|
||||
break;
|
||||
}
|
||||
if (state == State::NULLS) { // BLOB is zero(s)-terminated string
|
||||
for (unsigned i = 0;i < level;++i)
|
||||
putchar('\t');
|
||||
printf("= '%s'\n", buf.data());
|
||||
return;
|
||||
} else if (state == State::STARTASCII) { // BLOB is string
|
||||
buf.push_back('\0');
|
||||
for (unsigned i = 0;i < level;++i)
|
||||
putchar('\t');
|
||||
printf("= '%s'\n", buf.data());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing string-y, so just dump it to a file
|
||||
std::string filename{"tmp/"};
|
||||
if (count < 10)
|
||||
filename.append("0");
|
||||
filename.append(std::to_string(count) + "-");
|
||||
filename.append(name, 4);
|
||||
|
||||
FILEUPtr outFile{fopen(filename.c_str(), "wb")};
|
||||
if (!outFile)
|
||||
throw POSIXException{errno, "Could not open " + filename};
|
||||
|
||||
for (unsigned i = 0;i < level;++i)
|
||||
putchar('\t');
|
||||
printf("Dumping BLOB of length %ld (0x%lx) to %s\n", length, length, filename.c_str());
|
||||
|
||||
if (fwrite(buf.data(), length, 1, outFile.get()) != 1)
|
||||
throw POSIXException{errno, "Could not write data"};
|
||||
|
||||
++count;
|
||||
}
|
||||
|
||||
void parseChunk(FILE* iffFile, off_t start, off_t length, unsigned level = 0)
|
||||
{
|
||||
off_t pos = 0;
|
||||
|
||||
while (pos < length) {
|
||||
if (fseeko(iffFile, pos+start, SEEK_SET) != 0) {
|
||||
throw POSIXException(errno, "Could not seek");
|
||||
}
|
||||
|
||||
ChunkHeader header;
|
||||
|
||||
if (fread(&header, sizeof(ChunkHeader), 1, iffFile) != 1) {
|
||||
if (feof(iffFile))
|
||||
return;
|
||||
throw POSIXException(errno, "Could not read header");
|
||||
}
|
||||
|
||||
header.length = ntohl(header.length);
|
||||
for (unsigned i = 0;i < level;++i)
|
||||
putchar('\t');
|
||||
printf("Type: %.4s, Length: %u (0x%x)", header.typeID, header.length, header.length);
|
||||
if (memcmp(header.typeID, "FORM", 4) == 0) {
|
||||
if (header.length > (length-(pos+8))) {
|
||||
throw FormatException{"Length in header > remaining parent size"};
|
||||
}
|
||||
|
||||
char subType[4];
|
||||
if (fread(&subType, 4, 1, iffFile) != 1) {
|
||||
throw POSIXException(errno, "Could not read form subtype");
|
||||
}
|
||||
printf(", SubType: %.4s\n", subType);
|
||||
|
||||
parseChunk(iffFile, start+pos+12, header.length-4, level+1);
|
||||
} else {
|
||||
putchar('\n');
|
||||
parseBlob(iffFile, start+pos+8, header.length, header.typeID, level+1);
|
||||
}
|
||||
|
||||
if (header.length%2 != 0)
|
||||
pos += 1;
|
||||
|
||||
pos += header.length+8;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
if (argc != 2) {
|
||||
fprintf(stderr, "Usage: %s iff-file\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
try {
|
||||
FILEUPtr iffFile{fopen(argv[1], "rb")};
|
||||
if (!iffFile) {
|
||||
throw POSIXException{errno, "Could not open "s + argv[1]};
|
||||
}
|
||||
|
||||
struct stat statBuf;
|
||||
|
||||
if (fstat(fileno(iffFile.get()), &statBuf) != 0) {
|
||||
throw POSIXException(errno, "Could not stat");
|
||||
}
|
||||
|
||||
parseChunk(iffFile.get(), 0, statBuf.st_size);
|
||||
|
||||
} catch (POSIXException &ex) {
|
||||
fprintf(stderr, "%s\n", ex.toString().c_str());
|
||||
return 2;
|
||||
} catch (FormatException &ex) {
|
||||
fprintf(stderr, "%s\n", ex.toString().c_str());
|
||||
return 3;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
0
objs/.nodelete
Normal file
0
objs/.nodelete
Normal file
Reference in New Issue
Block a user