Archived
1
Fork 0

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:
Joshua Goins 2022-04-11 15:52:39 -04:00
parent f5bc8b9414
commit f066452ed8

View file

@ -139,29 +139,13 @@ void GameData::extractFile(std::string_view dataFilePath, std::string_view outPa
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 {
int32_t offset;
int16_t dummy;
int16_t dummy2;
};
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) {
const auto readFileBlock = [](FILE* file, size_t startingPos) -> std::vector<std::uint8_t> {
struct BlockHeader {
int32_t size;
int32_t dummy;
@ -169,7 +153,7 @@ void GameData::extractFile(std::string_view dataFilePath, std::string_view outPa
int32_t decompressedLength;
} header;
fseek(file, startingPos + block.offset, SEEK_SET);
fseek(file, startingPos, SEEK_SET);
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);
zlib::no_header_decompress(reinterpret_cast<uint8_t*>(compressed_data.data()),
compressed_data.size(),
reinterpret_cast<uint8_t*>(localdata.data()),
header.decompressedLength);
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());
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);
}
}