Add experimental model extraction
This is based off of Lumina's C# model extraction code. It currently doesn't support edge geometry, and I have no way to actually test what this actually does since no one has made a standalone FFXIV mdl viewer. Huh?
This commit is contained in:
parent
f5bc8b9414
commit
f066452ed8
1 changed files with 238 additions and 28 deletions
266
src/gamedata.cpp
266
src/gamedata.cpp
|
@ -139,29 +139,13 @@ void GameData::extractFile(std::string_view dataFilePath, std::string_view outPa
|
||||||
|
|
||||||
fmt::print("file size = {}\n", info.fileSize);
|
fmt::print("file size = {}\n", info.fileSize);
|
||||||
|
|
||||||
if(info.fileType != FileType::Standard) {
|
|
||||||
throw std::runtime_error("File type is not handled yet for " + std::string(dataFilePath));
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Block {
|
struct Block {
|
||||||
int32_t offset;
|
int32_t offset;
|
||||||
int16_t dummy;
|
int16_t dummy;
|
||||||
int16_t dummy2;
|
int16_t dummy2;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<Block> blocks;
|
const auto readFileBlock = [](FILE* file, size_t startingPos) -> std::vector<std::uint8_t> {
|
||||||
|
|
||||||
for(int i = 0; i < info.numBlocks; i++) {
|
|
||||||
Block block;
|
|
||||||
fread(&block, sizeof(Block), 1, file);
|
|
||||||
|
|
||||||
blocks.push_back(block);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::uint8_t> data;
|
|
||||||
|
|
||||||
const size_t startingPos = offset + info.size;
|
|
||||||
for(auto block : blocks) {
|
|
||||||
struct BlockHeader {
|
struct BlockHeader {
|
||||||
int32_t size;
|
int32_t size;
|
||||||
int32_t dummy;
|
int32_t dummy;
|
||||||
|
@ -169,7 +153,7 @@ void GameData::extractFile(std::string_view dataFilePath, std::string_view outPa
|
||||||
int32_t decompressedLength;
|
int32_t decompressedLength;
|
||||||
} header;
|
} header;
|
||||||
|
|
||||||
fseek(file, startingPos + block.offset, SEEK_SET);
|
fseek(file, startingPos, SEEK_SET);
|
||||||
|
|
||||||
fread(&header, sizeof(BlockHeader), 1, file);
|
fread(&header, sizeof(BlockHeader), 1, file);
|
||||||
|
|
||||||
|
@ -184,23 +168,249 @@ void GameData::extractFile(std::string_view dataFilePath, std::string_view outPa
|
||||||
fread(compressed_data.data(), header.compressedLength, 1, file);
|
fread(compressed_data.data(), header.compressedLength, 1, file);
|
||||||
|
|
||||||
zlib::no_header_decompress(reinterpret_cast<uint8_t*>(compressed_data.data()),
|
zlib::no_header_decompress(reinterpret_cast<uint8_t*>(compressed_data.data()),
|
||||||
compressed_data.size(),
|
compressed_data.size(),
|
||||||
reinterpret_cast<uint8_t*>(localdata.data()),
|
reinterpret_cast<uint8_t*>(localdata.data()),
|
||||||
header.decompressedLength);
|
header.decompressedLength);
|
||||||
} else {
|
} else {
|
||||||
localdata.resize(header.decompressedLength);
|
localdata.resize(header.decompressedLength);
|
||||||
|
|
||||||
fread(localdata.data(), header.decompressedLength, 1, file);
|
fread(localdata.data(), header.decompressedLength, 1, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
data.insert(data.end(), localdata.begin(), localdata.end());
|
return localdata;
|
||||||
|
};
|
||||||
|
|
||||||
|
if(info.fileType == FileType::Standard) {
|
||||||
|
std::vector<Block> blocks;
|
||||||
|
|
||||||
|
for(int i = 0; i < info.numBlocks; i++) {
|
||||||
|
Block block;
|
||||||
|
fread(&block, sizeof(Block), 1, file);
|
||||||
|
|
||||||
|
blocks.push_back(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::uint8_t> data;
|
||||||
|
|
||||||
|
const size_t startingPos = offset + info.size;
|
||||||
|
for(auto block : blocks) {
|
||||||
|
struct BlockHeader {
|
||||||
|
int32_t size;
|
||||||
|
int32_t dummy;
|
||||||
|
int32_t compressedLength; // < 32000 is uncompressed data
|
||||||
|
int32_t decompressedLength;
|
||||||
|
} header;
|
||||||
|
|
||||||
|
fseek(file, startingPos + block.offset, SEEK_SET);
|
||||||
|
|
||||||
|
fread(&header, sizeof(BlockHeader), 1, file);
|
||||||
|
|
||||||
|
std::vector<uint8_t> localdata;
|
||||||
|
|
||||||
|
bool isCompressed = header.compressedLength < 32000;
|
||||||
|
if(isCompressed) {
|
||||||
|
localdata.resize(header.decompressedLength);
|
||||||
|
|
||||||
|
std::vector<uint8_t> compressed_data;
|
||||||
|
compressed_data.resize(header.compressedLength);
|
||||||
|
fread(compressed_data.data(), header.compressedLength, 1, file);
|
||||||
|
|
||||||
|
zlib::no_header_decompress(reinterpret_cast<uint8_t*>(compressed_data.data()),
|
||||||
|
compressed_data.size(),
|
||||||
|
reinterpret_cast<uint8_t*>(localdata.data()),
|
||||||
|
header.decompressedLength);
|
||||||
|
} else {
|
||||||
|
localdata.resize(header.decompressedLength);
|
||||||
|
|
||||||
|
fread(localdata.data(), header.decompressedLength, 1, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
data.insert(data.end(), localdata.begin(), localdata.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(file);
|
||||||
|
|
||||||
|
FILE* newFile = fopen(outPath.data(), "w");
|
||||||
|
fwrite(data.data(), data.size(), 1, newFile);
|
||||||
|
fclose(newFile);
|
||||||
|
|
||||||
|
} else if(info.fileType == FileType::Model) {
|
||||||
|
FILE* newFile = fopen(outPath.data(), "w");
|
||||||
|
|
||||||
|
// reset
|
||||||
|
fseek(file, offset, SEEK_SET);
|
||||||
|
|
||||||
|
struct ModelFileInfo {
|
||||||
|
uint32_t size;
|
||||||
|
FileType fileType;
|
||||||
|
uint32_t fileSize;
|
||||||
|
uint32_t numBlocks;
|
||||||
|
uint32_t numUsedBlocks;
|
||||||
|
uint32_t version;
|
||||||
|
uint32_t stackSize;
|
||||||
|
uint32_t runtimeSize;
|
||||||
|
uint32_t vertexBufferSize[3];
|
||||||
|
uint32_t edgeGeometryVertexBufferSize[3];
|
||||||
|
uint32_t indexBufferSize[3];
|
||||||
|
uint32_t compressedStackMemorySize;
|
||||||
|
uint32_t compressedRuntimeMemorySize;
|
||||||
|
uint32_t compressedVertexBufferSize[3];
|
||||||
|
uint32_t compressedEdgeGeometrySize[3];
|
||||||
|
uint32_t compressedIndexBufferSize[3];
|
||||||
|
uint32_t stackOffset;
|
||||||
|
uint32_t runtimeOffset;
|
||||||
|
uint32_t vertexBufferOffset[3];
|
||||||
|
uint32_t edgeGeometryVertexBufferOffset[3];
|
||||||
|
uint32_t indexBufferOffset[3];
|
||||||
|
uint16_t stackBlockIndex;
|
||||||
|
uint16_t runtimeBlockIndex;
|
||||||
|
uint16_t vertexBufferBlockIndex[3];
|
||||||
|
uint16_t edgeGeometryVertexBufferBlockIndex[3];
|
||||||
|
uint16_t indexBufferBlockIndex[3];
|
||||||
|
uint16_t stackBlockNum;
|
||||||
|
uint16_t runtimeBlockNum;
|
||||||
|
uint16_t vertexBlockBufferBlockNum[3];
|
||||||
|
uint16_t edgeGeometryVertexBufferBlockNum[3];
|
||||||
|
uint16_t indexBufferBlockNum[3];
|
||||||
|
uint16_t vertexDeclarationNum;
|
||||||
|
uint16_t materialNum;
|
||||||
|
uint8_t numLods;
|
||||||
|
bool indexBufferStreamingEnabled;
|
||||||
|
bool edgeGeometryEnabled;
|
||||||
|
uint8_t padding;
|
||||||
|
} modelInfo;
|
||||||
|
|
||||||
|
fread(&modelInfo, sizeof(ModelFileInfo), 1, file);
|
||||||
|
|
||||||
|
const size_t baseOffset = offset + modelInfo.size;
|
||||||
|
|
||||||
|
int totalBlocks = modelInfo.stackBlockNum;
|
||||||
|
totalBlocks += modelInfo.runtimeBlockNum;
|
||||||
|
for(int i = 0; i < 3; i++) {
|
||||||
|
totalBlocks += modelInfo.vertexBlockBufferBlockNum[i];
|
||||||
|
totalBlocks += modelInfo.edgeGeometryVertexBufferBlockNum[i];
|
||||||
|
totalBlocks += modelInfo.indexBufferBlockNum[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint16_t> compressedBlockSizes(totalBlocks);
|
||||||
|
fread(compressedBlockSizes.data(), compressedBlockSizes.size() * sizeof(uint16_t), 1, file);
|
||||||
|
int currentBlock = 0;
|
||||||
|
int stackSize = 0;
|
||||||
|
int runtimeSize = 0;
|
||||||
|
|
||||||
|
std::array<int, 3> vertexDataOffsets;
|
||||||
|
std::array<int, 3> indexDataOffsets;
|
||||||
|
|
||||||
|
std::array<int, 3> vertexDataSizes;
|
||||||
|
std::array<int, 3> indexDataSizes;
|
||||||
|
|
||||||
|
// data.append 0x44
|
||||||
|
fseek(newFile, 0x44, SEEK_SET);
|
||||||
|
|
||||||
|
fseek(file, baseOffset + modelInfo.stackOffset, SEEK_SET);
|
||||||
|
size_t stackStart = ftell(newFile);
|
||||||
|
for(int i = 0; i < modelInfo.stackBlockNum; i++) {
|
||||||
|
size_t lastPos = ftell(file);
|
||||||
|
auto data = readFileBlock(file, lastPos);
|
||||||
|
fwrite(data.data(), data.size(), 1, newFile); // i think we write this to file?
|
||||||
|
fseek(file, lastPos + compressedBlockSizes[currentBlock], SEEK_SET);
|
||||||
|
currentBlock++;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t stackEnd = ftell(newFile);
|
||||||
|
stackSize = (int)(stackEnd - stackStart);
|
||||||
|
|
||||||
|
fseek(file, baseOffset + modelInfo.runtimeOffset, SEEK_SET);
|
||||||
|
size_t runtimeStart = ftell(newFile);
|
||||||
|
for(int i = 0; i < modelInfo.runtimeBlockNum; i++) {
|
||||||
|
size_t lastPos = ftell(file);
|
||||||
|
auto data = readFileBlock(file, lastPos);
|
||||||
|
fwrite(data.data(), data.size(), 1, newFile);
|
||||||
|
fseek(file, lastPos + compressedBlockSizes[currentBlock], SEEK_SET);
|
||||||
|
currentBlock++;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t runtimeEnd = ftell(newFile);
|
||||||
|
runtimeSize = (int)(runtimeEnd - runtimeStart);
|
||||||
|
|
||||||
|
fmt::print("stack size: {}\n", stackSize);
|
||||||
|
fmt::print("runtime size: {}\n", runtimeSize);
|
||||||
|
|
||||||
|
// process all 3 lods
|
||||||
|
for(int i = 0; i < 3; i++) {
|
||||||
|
if(modelInfo.vertexBlockBufferBlockNum[i] != 0) {
|
||||||
|
int currentVertexOffset = ftell(newFile);
|
||||||
|
if(i == 0 || currentVertexOffset != vertexDataOffsets[i - 1])
|
||||||
|
vertexDataOffsets[i] = currentVertexOffset;
|
||||||
|
else
|
||||||
|
vertexDataOffsets[i] = 0;
|
||||||
|
|
||||||
|
fseek(file, baseOffset + modelInfo.vertexBufferOffset[i], SEEK_SET);
|
||||||
|
|
||||||
|
for(int j = 0; j < modelInfo.vertexBlockBufferBlockNum[i]; j++) {
|
||||||
|
size_t lastPos = ftell(file);
|
||||||
|
auto data = readFileBlock(file, lastPos);
|
||||||
|
fwrite(data.data(), data.size(), 1, newFile); // i think we write this to file?
|
||||||
|
vertexDataSizes[i] += (int)data.size();
|
||||||
|
fseek(file, lastPos + compressedBlockSizes[currentBlock], SEEK_SET);
|
||||||
|
currentBlock++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: lol no edge geometry
|
||||||
|
|
||||||
|
if(modelInfo.indexBufferBlockNum[i] != 0) {
|
||||||
|
int currentIndexOffset = ftell(newFile);
|
||||||
|
if(i == 0 || currentIndexOffset != indexDataOffsets[i - 1])
|
||||||
|
indexDataOffsets[i] = currentIndexOffset;
|
||||||
|
else
|
||||||
|
indexDataOffsets[i] = 0;
|
||||||
|
|
||||||
|
for(int j = 0; j < modelInfo.indexBufferBlockNum[i]; j++) {
|
||||||
|
size_t lastPos = ftell(file);
|
||||||
|
auto data = readFileBlock(file, lastPos);
|
||||||
|
fwrite(data.data(), data.size(), 1, newFile); // i think we write this to file?
|
||||||
|
indexDataSizes[i] += (int)data.size();
|
||||||
|
fseek(file, lastPos + compressedBlockSizes[currentBlock], SEEK_SET);
|
||||||
|
currentBlock++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// now write mdl header
|
||||||
|
fseek(newFile, 0, SEEK_SET);
|
||||||
|
fwrite(&modelInfo.version, sizeof(uint32_t), 1, newFile);
|
||||||
|
fwrite(&stackSize, sizeof(int), 1, newFile);
|
||||||
|
fwrite(&runtimeSize, sizeof(int), 1, newFile);
|
||||||
|
fwrite(&modelInfo.vertexDeclarationNum, sizeof(uint16_t), 1, newFile);
|
||||||
|
fwrite(&modelInfo.materialNum, sizeof(uint16_t), 1, newFile);
|
||||||
|
|
||||||
|
for(int i = 0; i < 3; i++)
|
||||||
|
fwrite(&vertexDataOffsets[i], sizeof(int), 1, newFile);
|
||||||
|
|
||||||
|
for(int i = 0; i < 3; i++)
|
||||||
|
fwrite(&indexDataOffsets[i], sizeof(int), 1, newFile);
|
||||||
|
|
||||||
|
for(int i = 0; i < 3; i++)
|
||||||
|
fwrite(&vertexDataSizes[i], sizeof(int), 1, newFile);
|
||||||
|
|
||||||
|
for(int i = 0; i < 3; i++)
|
||||||
|
fwrite(&indexDataSizes[i], sizeof(int), 1, newFile);
|
||||||
|
|
||||||
|
fwrite(&modelInfo.numLods, sizeof(uint8_t), 1, file);
|
||||||
|
fwrite(&modelInfo.indexBufferStreamingEnabled, sizeof(bool), 1, file);
|
||||||
|
fwrite(&modelInfo.edgeGeometryEnabled, sizeof(bool), 1, file);
|
||||||
|
|
||||||
|
uint8_t dummy[] = {0};
|
||||||
|
fwrite(&dummy, sizeof(dummy), 1, file);
|
||||||
|
|
||||||
|
fmt::print("data size: {}\n", modelInfo.fileSize);
|
||||||
|
|
||||||
|
fclose(newFile);
|
||||||
|
fclose(file);
|
||||||
|
} else {
|
||||||
|
throw std::runtime_error("File type is not handled yet for " + std::string(dataFilePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
fclose(file);
|
|
||||||
|
|
||||||
FILE* newFile = fopen(outPath.data(), "w");
|
|
||||||
fwrite(data.data(), data.size(), 1, newFile);
|
|
||||||
fclose(newFile);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Reference in a new issue