Add EXD/EXH file parsing
This commit is contained in:
parent
2e74f477cf
commit
d428c6c3f5
7 changed files with 245 additions and 3 deletions
|
@ -5,5 +5,7 @@ add_library(libxiv STATIC
|
|||
src/indexparser.cpp
|
||||
src/crc32.cpp
|
||||
src/gamedata.cpp
|
||||
src/compression.cpp)
|
||||
src/compression.cpp
|
||||
src/exhparser.cpp
|
||||
src/exdparser.cpp)
|
||||
target_include_directories(libxiv PUBLIC include PRIVATE src)
|
8
include/exdparser.h
Normal file
8
include/exdparser.h
Normal file
|
@ -0,0 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
struct EXH;
|
||||
struct ExcelDataPagination;
|
||||
|
||||
void readEXD(EXH& exh, ExcelDataPagination& page);
|
82
include/exhparser.h
Normal file
82
include/exhparser.h
Normal file
|
@ -0,0 +1,82 @@
|
|||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
// taken from https://xiv.dev/game-data/file-formats/excel
|
||||
struct ExhHeader {
|
||||
char magic[0x4];
|
||||
uint16_t unknown;
|
||||
uint16_t dataOffset;
|
||||
uint16_t columnCount;
|
||||
uint16_t pageCount;
|
||||
uint16_t languageCount;
|
||||
uint16_t unknown1;
|
||||
uint8_t u2;
|
||||
uint8_t variant;
|
||||
uint16_t u3;
|
||||
uint32_t rowCount;
|
||||
uint32_t u4[2];
|
||||
};
|
||||
|
||||
enum ExcelColumnDataType : uint16_t {
|
||||
String = 0x0,
|
||||
Bool = 0x1,
|
||||
Int8 = 0x2,
|
||||
UInt8 = 0x3,
|
||||
Int16 = 0x4,
|
||||
UInt16 = 0x5,
|
||||
Int32 = 0x6,
|
||||
UInt32 = 0x7,
|
||||
Float32 = 0x9,
|
||||
Int64 = 0xA,
|
||||
UInt64 = 0xB,
|
||||
|
||||
// 0 is read like data & 1, 1 is like data & 2, 2 = data & 4, etc...
|
||||
PackedBool0 = 0x19,
|
||||
PackedBool1 = 0x1A,
|
||||
PackedBool2 = 0x1B,
|
||||
PackedBool3 = 0x1C,
|
||||
PackedBool4 = 0x1D,
|
||||
PackedBool5 = 0x1E,
|
||||
PackedBool6 = 0x1F,
|
||||
PackedBool7 = 0x20,
|
||||
};
|
||||
|
||||
struct ExcelColumnDefinition {
|
||||
ExcelColumnDataType type;
|
||||
uint16_t offset;
|
||||
};
|
||||
|
||||
struct ExcelDataPagination {
|
||||
uint32_t startId;
|
||||
uint32_t rowCount;
|
||||
};
|
||||
|
||||
enum Language : uint16_t {
|
||||
None,
|
||||
// ja
|
||||
Japanese,
|
||||
// en
|
||||
English,
|
||||
// de
|
||||
German,
|
||||
// fr
|
||||
French,
|
||||
// chs
|
||||
ChineseSimplified,
|
||||
// cht
|
||||
ChineseTraditional,
|
||||
// ko
|
||||
Korean
|
||||
};
|
||||
|
||||
struct EXH {
|
||||
ExhHeader header;
|
||||
|
||||
std::vector<ExcelColumnDefinition> columnDefinitions;
|
||||
std::vector<ExcelDataPagination> pages;
|
||||
std::vector<Language> language;
|
||||
};
|
||||
|
||||
EXH readEXH(std::string_view path);
|
9
include/utility.h
Normal file
9
include/utility.h
Normal file
|
@ -0,0 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
template <class T>
|
||||
void endianSwap(T *objp) {
|
||||
auto memp = reinterpret_cast<unsigned char*>(objp);
|
||||
std::reverse(memp, memp + sizeof(T));
|
||||
}
|
89
src/exdparser.cpp
Normal file
89
src/exdparser.cpp
Normal file
|
@ -0,0 +1,89 @@
|
|||
#include "exdparser.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <stdexcept>
|
||||
#include <algorithm>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "exhparser.h"
|
||||
#include "utility.h"
|
||||
|
||||
struct ExcelDataHeader {
|
||||
char magic[0x4];
|
||||
uint16_t version;
|
||||
uint16_t unknown1;
|
||||
uint32_t indexSize;
|
||||
uint32_t unknown2[5];
|
||||
};
|
||||
|
||||
struct ExcelDataOffset {
|
||||
uint32_t rowId;
|
||||
uint32_t offset;
|
||||
};
|
||||
|
||||
struct ExcelDataRowHeader {
|
||||
uint32_t dataSize;
|
||||
uint16_t rowCount;
|
||||
};
|
||||
|
||||
void readEXD(EXH& exh, ExcelDataPagination& page) {
|
||||
auto path = fmt::format("{}_{}.exd", "map", page.startId);
|
||||
|
||||
FILE* file = fopen(path.data(), "rb");
|
||||
if(file == nullptr) {
|
||||
throw std::runtime_error("Failed to open exd file " + std::string(path));
|
||||
}
|
||||
|
||||
ExcelDataHeader header;
|
||||
fread(&header, sizeof(ExcelDataHeader), 1, file);
|
||||
|
||||
endianSwap(&header.indexSize);
|
||||
|
||||
std::vector<ExcelDataOffset> dataOffsets;
|
||||
dataOffsets.resize(header.indexSize / sizeof( ExcelDataOffset ));
|
||||
|
||||
fread(dataOffsets.data(), sizeof(ExcelDataOffset) * dataOffsets.size(), 1, file);
|
||||
|
||||
for(auto& offset : dataOffsets) {
|
||||
endianSwap(&offset.offset);
|
||||
endianSwap(&offset.rowId);
|
||||
}
|
||||
|
||||
for(auto& offset : dataOffsets) {
|
||||
fseek(file, exh.header.dataOffset + offset.offset, SEEK_SET);
|
||||
|
||||
ExcelDataRowHeader rowHeader;
|
||||
fread(&rowHeader, sizeof(ExcelDataRowHeader), 1, file);
|
||||
|
||||
endianSwap(&rowHeader.dataSize);
|
||||
endianSwap(&rowHeader.rowCount);
|
||||
|
||||
const int rowOffset = offset.offset + 6;
|
||||
|
||||
for(auto column : exh.columnDefinitions) {
|
||||
switch(column.type) {
|
||||
case String:
|
||||
{
|
||||
fseek(file, rowOffset + column.offset, SEEK_SET);
|
||||
|
||||
uint32_t stringLength;
|
||||
fread(&stringLength, sizeof(uint32_t), 1, file);
|
||||
|
||||
fseek(file, rowOffset + exh.header.dataOffset + stringLength, SEEK_SET);
|
||||
|
||||
std::string string;
|
||||
|
||||
int ch;
|
||||
while ((ch = fgetc(file)) != '\0') {
|
||||
string.push_back((char)ch);
|
||||
}
|
||||
|
||||
fmt::print("{}\n", string.data());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
47
src/exhparser.cpp
Normal file
47
src/exhparser.cpp
Normal file
|
@ -0,0 +1,47 @@
|
|||
#include "exhparser.h"
|
||||
#include "utility.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
EXH readEXH(const std::string_view path) {
|
||||
EXH exh;
|
||||
|
||||
FILE* file = fopen(path.data(), "rb");
|
||||
if(file == nullptr) {
|
||||
throw std::runtime_error("Failed to open exh file " + std::string(path));
|
||||
}
|
||||
|
||||
fread(&exh.header, sizeof(ExhHeader), 1, file);
|
||||
|
||||
endianSwap(&exh.header.dataOffset);
|
||||
endianSwap(&exh.header.columnCount);
|
||||
endianSwap(&exh.header.pageCount);
|
||||
endianSwap(&exh.header.languageCount);
|
||||
endianSwap(&exh.header.rowCount);
|
||||
|
||||
exh.columnDefinitions.resize(exh.header.columnCount);
|
||||
|
||||
fread(exh.columnDefinitions.data(), sizeof(ExcelColumnDefinition) * exh.header.columnCount, 1, file);
|
||||
|
||||
exh.pages.resize(exh.header.pageCount);
|
||||
|
||||
fread(exh.pages.data(), sizeof(ExcelDataPagination) * exh.header.pageCount, 1, file);
|
||||
|
||||
exh.language.resize(exh.header.languageCount);
|
||||
fread(exh.language.data(), sizeof(Language) * exh.header.languageCount, 1, file);
|
||||
|
||||
for(auto& columnDef : exh.columnDefinitions) {
|
||||
endianSwap(&columnDef.offset);
|
||||
endianSwap(&columnDef.type);
|
||||
}
|
||||
|
||||
for(auto& page : exh.pages) {
|
||||
endianSwap(&page.rowCount);
|
||||
endianSwap(&page.startId);
|
||||
}
|
||||
|
||||
return exh;
|
||||
}
|
|
@ -53,7 +53,7 @@ std::tuple<std::string, std::string> GameData::calculateRepositoryCategory(std::
|
|||
std::string repository, category;
|
||||
|
||||
auto tokens = tokenize(path, "/");
|
||||
if(stringContains(tokens[1], "ex") && !stringContains(tokens[0], "exd")) {
|
||||
if(stringContains(tokens[1], "ex") && !stringContains(tokens[0], "exd") && !stringContains(tokens[0], "exh")) {
|
||||
repository = tokens[1];
|
||||
} else {
|
||||
repository = "ffxiv";
|
||||
|
@ -79,6 +79,9 @@ void GameData::extractFile(std::string_view dataFilePath, std::string_view outPa
|
|||
const uint64_t hash = calculateHash(dataFilePath);
|
||||
auto [repository, category] = calculateRepositoryCategory(dataFilePath);
|
||||
|
||||
fmt::print("repository = {}\n", repository);
|
||||
fmt::print("category = {}\n", category);
|
||||
|
||||
// TODO: handle platforms other than win32
|
||||
auto indexFilename = calculateFilename(categoryToID[category], getExpansionID(repository), 0, "win32", "index");
|
||||
|
||||
|
@ -114,6 +117,8 @@ void GameData::extractFile(std::string_view dataFilePath, std::string_view outPa
|
|||
|
||||
fread(&info, sizeof(FileInfo), 1, file);
|
||||
|
||||
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));
|
||||
}
|
||||
|
@ -179,5 +184,5 @@ void GameData::extractFile(std::string_view dataFilePath, std::string_view outPa
|
|||
}
|
||||
}
|
||||
|
||||
fmt::print("Extracted {} to {}", dataFilePath, outPath);
|
||||
fmt::print("Extracted {} to {}\n", dataFilePath, outPath);
|
||||
}
|
||||
|
|
Reference in a new issue