Skip to content

Instantly share code, notes, and snippets.

@FelixWolf
Created November 10, 2024 04:38
Show Gist options
  • Save FelixWolf/00e46fa39ad3f92a4b1ef8ae884233c9 to your computer and use it in GitHub Desktop.
Save FelixWolf/00e46fa39ad3f92a4b1ef8ae884233c9 to your computer and use it in GitHub Desktop.
#include "fox5.h"
#if __has_include("fox5cipher.h")
#include "fox5cipher.h"
#define HAS_CIPHER
#endif
#include <fstream>
#include <string>
#include "LzmaDec.h"
#include <iostream>
#include <vector>
#include <cstring>
#include <stdexcept>
#include <cstdlib>
#define LZMA_PROPS_SIZE 5
#define LZMA_ALONE_HEADER_SIZE 13 // LZMA Alone has 5 bytes properties + 8 bytes uncompressed size
// Implement memory allocation functions
void* SzAlloc(const ISzAlloc* /*p*/, size_t size)
{
return std::malloc(size);
}
void SzFree(const ISzAlloc* /*p*/, void* address)
{
std::free(address);
}
static ISzAlloc g_Alloc = { SzAlloc, SzFree };
std::vector<uint8_t> decompressLZMA(const std::vector<uint8_t>& compressedData, SizeT uncompressedSize)
{
std::vector<uint8_t> decompressedData;
// LZMA "Alone" format includes an 8-byte uncompressed size in the header (after the properties)
if(uncompressedSize == 0)
{
memcpy(&uncompressedSize, compressedData.data() + LZMA_PROPS_SIZE, sizeof(uncompressedSize));
}
decompressedData.resize(uncompressedSize);
SizeT destLen = uncompressedSize;
SizeT srcLen = compressedData.size() - LZMA_ALONE_HEADER_SIZE; // Skip the 13-byte header
ELzmaStatus status;
int res = LzmaDecode(
decompressedData.data(), &destLen,
compressedData.data() + LZMA_ALONE_HEADER_SIZE, &srcLen,
compressedData.data(), LZMA_PROPS_SIZE,
LZMA_FINISH_ANY, &status, &g_Alloc);
if(res != SZ_OK)
throw std::runtime_error("LZMA decompression failed");
return decompressedData;
}
// Utilities
uint32_t readUint32(uint8_t** dataPtr, uint8_t* dataEnd) {
if (*dataPtr + 4 > dataEnd)
throw std::runtime_error("Not enough data to read uint32_t.");
uint32_t value = (static_cast<uint32_t>((*dataPtr)[0]) << 24) |
(static_cast<uint32_t>((*dataPtr)[1]) << 16) |
(static_cast<uint32_t>((*dataPtr)[2]) << 8) |
(static_cast<uint32_t>((*dataPtr)[3]));
*dataPtr += 4; // Move the pointer forward by 4 bytes
return value;
}
int32_t readInt32(uint8_t** dataPtr, uint8_t* dataEnd) {
if (*dataPtr + 4 > dataEnd)
throw std::runtime_error("Not enough data to read uint32_t.");
int32_t value = (static_cast<int32_t>((*dataPtr)[0]) << 24) |
(static_cast<int32_t>((*dataPtr)[1]) << 16) |
(static_cast<int32_t>((*dataPtr)[2]) << 8) |
(static_cast<int32_t>((*dataPtr)[3]));
*dataPtr += 4; // Move the pointer forward by 4 bytes
return value;
}
uint16_t readUint16(uint8_t** dataPtr, uint8_t* dataEnd) {
if (*dataPtr + 2 > dataEnd)
throw std::runtime_error("Not enough data to read uint16_t.");
uint16_t value = (static_cast<uint16_t>((*dataPtr)[0]) << 8) |
(static_cast<uint16_t>((*dataPtr)[1]));
*dataPtr += 2; // Move the pointer forward by 2 bytes
return value;
}
int16_t readInt16(uint8_t** dataPtr, uint8_t* dataEnd) {
if (*dataPtr + 2 > dataEnd)
throw std::runtime_error("Not enough data to read uint16_t.");
int16_t value = (static_cast<int16_t>((*dataPtr)[0]) << 8) |
(static_cast<int16_t>((*dataPtr)[1]));
*dataPtr += 2; // Move the pointer forward by 2 bytes
return value;
}
uint8_t readUint8(uint8_t** dataPtr, uint8_t* dataEnd) {
if (*dataPtr + 1 > dataEnd)
throw std::runtime_error("Not enough data to read uint8_t.");
uint8_t value = static_cast<uint8_t>((*dataPtr)[0]);
*dataPtr += 1; // Move the pointer forward by 2 bytes
return value;
}
int8_t readInt8(uint8_t** dataPtr, uint8_t* dataEnd) {
if (*dataPtr + 1 > dataEnd)
throw std::runtime_error("Not enough data to read uint8_t.");
int8_t value = static_cast<int8_t>((*dataPtr)[0]);
*dataPtr += 1; // Move the pointer forward by 2 bytes
return value;
}
uint32_t readUint32(std::ifstream& file)
{
uint8_t buffer[4];
file.read(reinterpret_cast<char*>(buffer), 4);
if (!file)
throw std::runtime_error("Failed to read uint32_t from file.");
uint32_t value = (static_cast<uint32_t>(buffer[0]) << 24) |
(static_cast<uint32_t>(buffer[1]) << 16) |
(static_cast<uint32_t>(buffer[2]) << 8) |
(static_cast<uint32_t>(buffer[3]));
return value;
}
uint16_t readUint16(std::ifstream& file)
{
uint8_t buffer[2];
file.read(reinterpret_cast<char*>(buffer), 2);
if (!file)
throw std::runtime_error("Failed to read uint32_t from file.");
uint16_t value = (static_cast<uint32_t>(buffer[0]) << 8) |
(static_cast<uint32_t>(buffer[1]));
return value;
}
// Implementation
void FOX5Sprite::parseData(uint8_t** dataPtr, uint8_t* dataEnd)
{
while (*dataPtr < dataEnd)
{
uint8_t cmd = readUint8(dataPtr, dataEnd);
switch(static_cast<FOX5Sprite::BlockCommand>(cmd))
{
case FOX5Sprite::BlockCommand::NOP:
break;
case FOX5Sprite::BlockCommand::PURPOSE:
{
mPurpose = readUint16(dataPtr, dataEnd);
break;
}
case FOX5Sprite::BlockCommand::IMAGE_ID:
{
mImageID = readUint16(dataPtr, dataEnd);
break;
}
case FOX5Sprite::BlockCommand::OFFSET:
{
mOffset[0] = readInt16(dataPtr, dataEnd);
mOffset[1] = readInt16(dataPtr, dataEnd);
break;
}
case FOX5Sprite::BlockCommand::LIST_START:
{
throw std::runtime_error("FOX5Sprite can't contain lists");
break;
}
case FOX5Sprite::BlockCommand::LIST_END:
return;
default:
throw std::runtime_error("Unknown command " + std::to_string(cmd));
}
}
}
void FOX5Frame::parseData(uint8_t** dataPtr, uint8_t* dataEnd)
{
while (*dataPtr < dataEnd)
{
uint8_t cmd = readUint8(dataPtr, dataEnd);
switch(static_cast<FOX5Frame::BlockCommand>(cmd))
{
case FOX5Frame::BlockCommand::NOP:
break;
case FOX5Frame::BlockCommand::FRAME_OFFSET:
{
mFrameOffset[0] = readInt16(dataPtr, dataEnd);
mFrameOffset[1] = readInt16(dataPtr, dataEnd);
break;
}
case FOX5Frame::BlockCommand::FURRE_OFFSET:
{
mFurreOffset[0] = readInt16(dataPtr, dataEnd);
mFurreOffset[1] = readInt16(dataPtr, dataEnd);
break;
}
case FOX5Frame::BlockCommand::LIST_START:
{
uint8_t level = readUint8(dataPtr, dataEnd);
if(level != 4)
{
throw std::runtime_error("Expected sprite level 4 in FOX5Frame");
}
uint32_t count = readUint32(dataPtr, dataEnd);
mSprites.resize(count);
for(uint32_t i = 0; i < count; i++)
{
mSprites[i] = std::make_shared<FOX5Sprite>(
dataPtr, dataEnd
);;
}
break;
}
case FOX5Frame::BlockCommand::LIST_END:
return;
default:
throw std::runtime_error("Unknown command " + std::to_string(cmd));
}
}
}
void FOX5Shape::parseData(uint8_t** dataPtr, uint8_t* dataEnd)
{
while (*dataPtr < dataEnd)
{
uint8_t cmd = readUint8(dataPtr, dataEnd);
switch(static_cast<FOX5Shape::BlockCommand>(cmd))
{
case FOX5Shape::BlockCommand::NOP:
break;
case FOX5Shape::BlockCommand::PURPOSE:
{
mPurpose = static_cast<FOX5Shape::Purpose>(readUint8(dataPtr, dataEnd));
break;
}
case FOX5Shape::BlockCommand::STATE:
{
mState = readUint8(dataPtr, dataEnd);
break;
}
case FOX5Shape::BlockCommand::DIRECTION:
{
mDirection = static_cast<FOX5Shape::Direction>(readUint8(dataPtr, dataEnd));
break;
}
case FOX5Shape::BlockCommand::RATIO:
{
mRatio[0] = readUint8(dataPtr, dataEnd);
mRatio[1] = readUint8(dataPtr, dataEnd);
break;
}
case FOX5Shape::BlockCommand::KITTERSPEAK:
{
uint16_t count = readUint16(dataPtr, dataEnd);
mKitterspeak.resize(count);
for(uint16_t i; i < count; i++)
{
mKitterspeak[i] = std::make_unique<Kitterspeak>(
readUint16(dataPtr, dataEnd),
readInt16(dataPtr, dataEnd),
readInt16(dataPtr, dataEnd)
);
}
break;
}
case FOX5Shape::BlockCommand::LIST_START:
{
uint8_t level = readUint8(dataPtr, dataEnd);
if(level != 3)
{
throw std::runtime_error("Expected frame level 3 in FOX5Shape");
}
uint32_t count = readUint32(dataPtr, dataEnd);
mFrames.resize(count);
for(uint32_t i = 0; i < count; i++)
{
mFrames[i] = std::make_shared<FOX5Frame>(
dataPtr, dataEnd
);;
}
break;
}
case FOX5Shape::BlockCommand::LIST_END:
return;
default:
throw std::runtime_error("Unknown command " + std::to_string(cmd));
}
}
}
void FOX5Object::parseData(uint8_t** dataPtr, uint8_t* dataEnd)
{
while (*dataPtr < dataEnd)
{
uint8_t cmd = readUint8(dataPtr, dataEnd);
switch(static_cast<FOX5Object::BlockCommand>(cmd))
{
case FOX5Object::BlockCommand::NOP:
break;
case FOX5Object::BlockCommand::AUTHOR_REVISION:
{
mAuthorRevision = readUint32(dataPtr, dataEnd);
break;
}
case FOX5Object::BlockCommand::AUTHORS:
{
// TODO: Actually add strings here
uint16_t count = readUint16(dataPtr, dataEnd);
//mAuthors.resize(count);
for(uint16_t i; i < count; i++)
{
uint16_t size = readUint16(dataPtr, dataEnd);
dataPtr += size;
}
break;
}
// case FOX5Object::BlockCommand::AUTHORS_HASH: // Unused ?
case FOX5Object::BlockCommand::LICENSE:
{
mLicense = static_cast<FOX5Object::License>(readUint8(dataPtr, dataEnd));
break;
}
case FOX5Object::BlockCommand::KEYWORDS:
{
// TODO: Actually add strings here
uint16_t count = readUint16(dataPtr, dataEnd);
//mKeywords.resize(count);
for(uint16_t i; i < count; i++)
{
uint16_t size = readUint16(dataPtr, dataEnd);
dataPtr += size;
}
break;
}
case FOX5Object::BlockCommand::NAME:
{
// TODO: Actually add strings here
uint16_t size = readUint16(dataPtr, dataEnd);
dataPtr += size;
break;
}
case FOX5Object::BlockCommand::DESCRIPTION:
{
// TODO: Actually add strings here
uint16_t size = readUint16(dataPtr, dataEnd);
dataPtr += size;
break;
}
case FOX5Object::BlockCommand::FLAGS:
{
mFlags = readUint8(dataPtr, dataEnd);
break;
}
case FOX5Object::BlockCommand::TELEPORT_DREAMURL:
{
// TODO: Actually add strings here
uint16_t size = readUint16(dataPtr, dataEnd);
dataPtr += size;
break;
}
case FOX5Object::BlockCommand::MORE_FLAGS:
{
mMoreFlags = readUint32(dataPtr, dataEnd);
break;
}
case FOX5Object::BlockCommand::OBJECT_IDENTIFIER:
{
mObjectID = readInt32(dataPtr, dataEnd);
break;
}
case FOX5Object::BlockCommand::EDIT_TYPE:
{
mEditType = readUint8(dataPtr, dataEnd);
break;
}
case FOX5Object::BlockCommand::FX_TYPE:
{
mFilterTarget = readUint8(dataPtr, dataEnd);
mFilterMode = readUint8(dataPtr, dataEnd);
break;
}
case FOX5Object::BlockCommand::LIST_START:
{
uint8_t level = readUint8(dataPtr, dataEnd);
if(level != 2)
throw std::runtime_error("Expected shape level 2 in FOX5Object");
uint32_t count = readUint32(dataPtr, dataEnd);
mShapes.resize(count);
for(uint32_t i = 0; i < count; i++)
{
mShapes[i] = std::make_shared<FOX5Shape>(
dataPtr, dataEnd
);;
}
break;
}
case FOX5Object::BlockCommand::LIST_END:
return;
default:
throw std::runtime_error("Unknown command FOX5Object " + std::to_string(cmd));
}
}
}
void FOX5File::parseData(uint8_t** dataPtr, uint8_t* dataEnd)
{
while (*dataPtr < dataEnd)
{
uint8_t cmd = readUint8(dataPtr, dataEnd);
switch(static_cast<FOX5File::BlockCommand>(cmd))
{
case FOX5File::BlockCommand::NOP:
break;
case FOX5File::BlockCommand::IMAGE_LIST:
{
uint32_t count = readUint32(dataPtr, dataEnd);
mImageList.resize(count);
uint32_t offset = mFile.tellg();
for(uint32_t i = 0; i < count; i++)
{
uint32_t compressedSize = readUint32(dataPtr, dataEnd);
uint16_t width = readUint16(dataPtr, dataEnd);
uint16_t height = readUint16(dataPtr, dataEnd);
uint8_t format = readUint8(dataPtr, dataEnd);
mImageList[i] = std::make_shared<FOX5Image>(
offset,
compressedSize,
width, height,
static_cast<FOX5Image::ImageFormat>(format)
);
offset += compressedSize;
}
break;
}
case FOX5File::BlockCommand::GENERATOR:
{
mGenerator = readUint8(dataPtr, dataEnd);
break;
}
case FOX5File::BlockCommand::LIST_START:
{
uint8_t level = readUint8(dataPtr, dataEnd);
if(level != 1)
throw std::runtime_error("Expected object level 1 in FOX5File");
uint32_t count = readUint32(dataPtr, dataEnd);
mObjects.resize(count);
for(uint32_t i = 0; i < count; i++)
{
mObjects[i] = std::make_shared<FOX5Object>(
dataPtr, dataEnd
);
}
break;
}
case FOX5File::BlockCommand::LIST_END:
return;
default:
throw std::runtime_error("Unknown command in FOX5File " + std::to_string(cmd));
}
}
}
FOX5Image FOX5File::getImage(uint32_t id)
{
if(id >= mImageList.size())
throw std::runtime_error("Image index out of bounds");
FOX5Image im = FOX5Image(*mImageList[id]);
printf("%i %i %i\n", im.mWidth, im.mHeight, im.getMemSize());
im.mData.resize(im.getMemSize());
mFile.seekg(im.mOffset, std::ios::beg);
mFile.read(reinterpret_cast<char*>(im.mData.data()), im.mCompressedSize);
if(mEncryptionType == EncryptionType::ENCRYPTED)
{
#ifdef HAS_CIPHER
Fox5Cipher(im.mData, im.mCompressedSize, im.mData.size(), mSeed);
#else
throw std::runtime_error("Can't decrypt without fox5 cipher library");
#endif
}
if(mCompressionType == FOX5File::CompressionType::LZMA)
{
im.mData = decompressLZMA(im.mData, im.mData.size());
}
else if(mCompressionType != FOX5File::CompressionType::NOT)
{
throw std::runtime_error("Unknown compression type");
}
return im;
}
FOX5File::FOX5File(const std::string& filename) :
mFile(filename, std::ios::binary)
{
if (!mFile) throw std::runtime_error("Failed to open file.");
mFile.seekg(0, std::ios::end);
if(mFile.tellg() < 20) throw std::runtime_error("Too small to be a FOX5 file.");
mFile.seekg(-20, std::ios::end);
uint8_t uint8read;
mFile.read(reinterpret_cast<char*>(&uint8read), sizeof(uint8read));
if (!mFile) throw std::runtime_error("Failed to read compression type.");
mCompressionType = static_cast<CompressionType>(uint8read);
mFile.read(reinterpret_cast<char*>(&uint8read), sizeof(uint8read));
if (!mFile) throw std::runtime_error("Failed to read encryption type.");
mEncryptionType = static_cast<EncryptionType>(uint8read);
mFile.seekg(2, std::ios::cur); //Skip two reserved
uint32_t dbCompressedSize = readUint32(mFile);
uint32_t dbUncompressedSize = readUint32(mFile);
char magic[9] = {0};
mFile.read(magic, 8);
if (!mFile) throw std::runtime_error("Failed to read magic from FOX5.");
if(std::string(magic, sizeof(magic) - 1) != "FOX5.1.1")
throw std::runtime_error("Not a FOX5 file.");
if(mEncryptionType == EncryptionType::ENCRYPTED)
{
mFile.seekg(-36, std::ios::end);
mFile.read(reinterpret_cast<char*>(mSeed), sizeof(mSeed));
if (!mFile) throw std::runtime_error("Failed to read seed from FOX5.");
}
mFile.seekg(0, std::ios::beg);
std::vector<uint8_t> commandBlock(dbUncompressedSize);
mFile.read(reinterpret_cast<char*>(commandBlock.data()), dbCompressedSize);
if(mEncryptionType == EncryptionType::ENCRYPTED)
{
#ifdef HAS_CIPHER
Fox5Cipher(commandBlock, dbCompressedSize, dbUncompressedSize, mSeed);
#else
throw std::runtime_error("Can't decrypt without fox5 cipher library");
#endif
}
if(mCompressionType == FOX5File::CompressionType::LZMA)
{
commandBlock = decompressLZMA(commandBlock, dbUncompressedSize);
}
else if(mCompressionType != FOX5File::CompressionType::NOT)
{
throw std::runtime_error("Unknown compression type");
}
uint8_t* pointer = commandBlock.data();
uint8_t* dataEnd = pointer + commandBlock.size();
pointer += 4;
uint8_t cmd = readUint8(&pointer, dataEnd);
if(static_cast<FOX5File::BlockCommand>(cmd) != FOX5File::BlockCommand::LIST_START)
throw std::runtime_error("Expected list start as first entry");
uint8_t level = readUint8(&pointer, dataEnd);
if(level != 0)
throw std::runtime_error("Expected file level 0 at start");
uint32_t count = readUint32(&pointer, dataEnd);
if(count != 1)
throw std::runtime_error("File level list should always have 1 entry");
parseData(&pointer, dataEnd);
}
#ifndef FOX5_H
#define FOX5_H
#include <cstdint>
#include <fstream>
#include <vector>
#include <string>
#include <map>
#include <unordered_map>
#include <memory>
class FOX5Image
{
public:
uint32_t mOffset;
uint32_t mCompressedSize;
uint16_t mWidth;
uint16_t mHeight;
std::vector<uint8_t> mData;
enum class ImageFormat : uint8_t
{
E_8BIT = 0,
E_32BIT = 1
};
ImageFormat mImageFormat;
uint32_t getMemSize()
{
uint32_t s = mWidth*mHeight;
if(mImageFormat == ImageFormat::E_32BIT)
s *= 4;
return s;
}
FOX5Image(uint32_t offset, uint32_t compressedSize, uint16_t width, uint16_t height, ImageFormat format)
: mOffset(offset), mCompressedSize(compressedSize), mWidth(width), mHeight(height), mImageFormat(format)
{}
};
class FOX5List
{
public:
enum class BlockCommand : uint8_t {
NOP = 0x00,
LIST_START = 0x4C, // 'L'
LIST_END = 0x3C // '<'
};
FOX5List() = default;
FOX5List(const std::vector<uint8_t> data){};
};
class FOX5Sprite : FOX5List
{
public:
enum class BlockCommand : uint8_t
{
NOP = 0x00,
LIST_START = 0x4C, // 'L'
LIST_END = 0x3C, // '<'
PURPOSE = 0x43, // 'C'
IMAGE_ID = 0x63, // 'c'
OFFSET = 0x4F // 'O'
};
union
{
uint16_t mPurpose;
struct
{
uint8_t : 5;
bool mRemap : 1;
bool mShadow : 1;
bool mMarkup : 1;
};
};
uint16_t mImageID; //Number in FOX5File ImageList
int16_t mOffset[2] = {0};
void parseData(uint8_t** dataPtr, uint8_t* dataEnd);
FOX5Sprite(uint8_t** dataPtr, uint8_t* dataEnd)
{
parseData(dataPtr, dataEnd);
};
};
class FOX5Frame : FOX5List
{
public:
enum class BlockCommand : uint8_t
{
NOP = 0x00,
LIST_START = 0x4C, // 'L'
LIST_END = 0x3C, // '<'
FRAME_OFFSET = 0x6F, // 'o'
FURRE_OFFSET = 0x66 // 'f'
};
int16_t mFrameOffset[2] = {0};
int16_t mFurreOffset[2] = {0};
std::vector<std::shared_ptr<FOX5Sprite>> mSprites;
void parseData(uint8_t** dataPtr, uint8_t* dataEnd);
FOX5Frame(uint8_t** dataPtr, uint8_t* dataEnd)
{
parseData(dataPtr, dataEnd);
};
};
class FOX5Shape : FOX5List
{
public:
enum class BlockCommand : uint8_t
{
NOP = 0x00,
LIST_START = 0x4C, // 'L'
LIST_END = 0x3C, // '<'
PURPOSE = 0x70, // 'p'
STATE = 0x73, // 's'
DIRECTION = 0x44, // 'D'
RATIO = 0x52, // 'R'
KITTERSPEAK = 0x4B, // 'K'
};
enum class Purpose : uint8_t
{
UNSPECIFIED = 0,
MENU_ICON = 1,
UI_BUTTON = 2,
BUTLER = 3,
PORTRAIT = 4,
DS_BUTTON = 5,
AVATAR = 11,
FLOOR = 21,
ITEM = 22,
WALL = 23,
REGION = 24,
EFFECT = 25,
PAD_ITEM = 28,
PORTAL_ITEM = 29,
SPECITAG = 35,
LIGHTING = 41,
AMBIENCE = 42
};
Purpose mPurpose;
union
{
uint8_t mState;
struct
{
bool mFemale : 1;
bool mMale : 1;
bool mUnspecified : 1;
};
struct
{
bool mClicked : 1;
bool mMouseOver : 1;
bool mActivated : 1;
};
};
enum class Direction : uint8_t
{
UNSPECIFIED = 0,
SW = 1,
S = 2,
SE = 3,
W = 4,
NO_DIRECTION = 5,
E = 6,
NW = 7,
LEFT = 7,
N = 8,
NE = 9,
RIGHT = 9,
UP = 10,
DOWN = 11
};
Direction mDirection;
uint8_t mRatio[2] = {0,0};
struct Kitterspeak {
uint16_t mCommand;
uint16_t mArg1;
uint16_t mArg2;
Kitterspeak(uint16_t command, uint16_t arg1, uint16_t arg2)
: mCommand(command), mArg1(arg1), mArg2(arg2) {}
};
std::vector<std::unique_ptr<Kitterspeak>> mKitterspeak;
std::vector<std::shared_ptr<FOX5Frame>> mFrames;
void parseData(uint8_t** dataPtr, uint8_t* dataEnd);
FOX5Shape(uint8_t** dataPtr, uint8_t* dataEnd)
{
parseData(dataPtr, dataEnd);
};
};
class FOX5Object : FOX5List
{
public:
enum class BlockCommand : uint8_t
{
NOP = 0x00,
LIST_START = 0x4C, // 'L'
LIST_END = 0x3C, // '<'
AUTHOR_REVISION = 0x72, // 'r'
AUTHORS = 0x61, // 'a'
AUTHORS_HASH = 0x61, // 'h'
LICENSE = 0x6C, // 'l'
KEYWORDS = 0x6B, // 'k'
NAME = 0x6E, // 'n'
DESCRIPTION = 0x64, // 'd'
FLAGS = 0x21, // '!'
TELEPORT_DREAMURL = 0x50, // 'P'
MORE_FLAGS = 0x3F, // '?'
OBJECT_IDENTIFIER = 0x69, // 'i'
EDIT_TYPE = 0x74, // 't'
FX_TYPE = 0x46 // 'F'
};
uint32_t mAuthorRevision;
std::vector<std::string> mAuthors;
enum class License : uint8_t
{
STANDARD = 0,
FREEDOM = 1,
LIMITED = 2,
EXCLUSIVE = 3,
PRIVATE = 4,
CONDITIONAL = 5
};
License mLicense;
std::vector<std::string> mKeywords;
std::string mObjectName;
std::string mObjectDescription;
union
{
uint8_t mFlags;
struct
{
bool mWalkable : 1;
bool mGettable : 1;
bool mSittable : 1;
bool mFlyable : 1;
bool mSwimmable : 1;
bool mClickable : 1;
bool mMouseOver : 1;
bool mKickable : 1;
};
};
std::string mTeleportDreamURL;
union
{
uint32_t mMoreFlags;
struct
{
bool mDreamPadAll : 1;
bool mDreamPadSS : 1;
bool mDreamPadGS : 1;
bool mDreamPadLG : 1;
bool mDreamPadHG : 1;
bool mDreamPadDEP : 1;
};
struct
{
bool mHopping : 1;
bool mFlying : 1;
bool mSwimming : 1;
bool mChild : 1;
};
};
int32_t mObjectID;
uint8_t mEditType;
uint8_t mFilterTarget;
uint8_t mFilterMode;
std::vector<std::shared_ptr<FOX5Shape>> mShapes;
void parseData(uint8_t** dataPtr, uint8_t* dataEnd);
FOX5Object(uint8_t** dataPtr, uint8_t* dataEnd)
{
parseData(dataPtr, dataEnd);
};
};
class FOX5File : FOX5List
{
protected:
std::ifstream mFile;
public: // Footer
uint8_t mSeed[16] = {0};
enum class CompressionType : uint8_t
{
NOT = 0,
ZLIB = 1,
LZMA = 2
};
CompressionType mCompressionType;
enum class EncryptionType : uint8_t
{
NOT = 0,
ENCRYPTED = 1
};
EncryptionType mEncryptionType;
public: // FOX5List
enum class BlockCommand : uint8_t
{
NOP = 0x00,
LIST_START = 0x4C, // 'L'
LIST_END = 0x3C, // '<'
GENERATOR = 0x67, // 'g'
IMAGE_LIST = 0x53 // 'S'
};
uint8_t mGenerator;
std::vector<std::shared_ptr<FOX5Image>> mImageList;
std::vector<std::shared_ptr<FOX5Object>> mObjects;
public:
FOX5Image getImage(uint32_t id);
public:
FOX5File(const std::string& filename);
void parseData(uint8_t** dataPtr, uint8_t* dataEnd);
};
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment