diff --git a/CMakeLists.txt b/CMakeLists.txt index e373412..2d02ee0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,7 +72,8 @@ add_library(libxiv STATIC src/installextract.cpp src/patch.cpp src/exlparser.cpp - src/mdlparser.cpp) + src/mdlparser.cpp + src/havokxmlparser.cpp) target_include_directories(libxiv PUBLIC include PRIVATE src) -target_link_libraries(libxiv PUBLIC ${LIBRARIES}) +target_link_libraries(libxiv PUBLIC ${LIBRARIES} pugixml) target_link_directories(libxiv PUBLIC ${LIB_DIRS}) \ No newline at end of file diff --git a/include/gamedata.h b/include/gamedata.h index 9490747..28458d7 100644 --- a/include/gamedata.h +++ b/include/gamedata.h @@ -26,6 +26,8 @@ public: */ void extractFile(std::string_view dataFilePath, std::string_view outPath); + void extractSkeleton(); + std::optional readExcelSheet(std::string_view name); std::vector getAllSheetNames(); diff --git a/include/havokxmlparser.h b/include/havokxmlparser.h new file mode 100644 index 0000000..50f502e --- /dev/null +++ b/include/havokxmlparser.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include + +struct Bone { + std::string name; + + Bone* parent = nullptr; + + std::array localTransform, finalTransform; + + std::array position; + std::array rotation; + std::array scale; +}; + +struct Skeleton { + std::vector bones; + Bone* root_bone = nullptr; +}; + +/* + * This reads a havok xml scene file, which is generated from your preferred assetcc.exe. + */ +Skeleton parseHavokXML(const std::string_view path); \ No newline at end of file diff --git a/src/gamedata.cpp b/src/gamedata.cpp index 985a407..7980fd5 100644 --- a/src/gamedata.cpp +++ b/src/gamedata.cpp @@ -106,7 +106,7 @@ void GameData::extractFile(std::string_view dataFilePath, std::string_view outPa // TODO: handle platforms other than win32 auto indexFilename = calculateFilename(categoryToID[category], getExpansionID(repository), 0, "win32", "index"); - fmt::print("calculated index filename: "); + fmt::print("calculated index filename: {}\n", indexFilename); // TODO: handle hashes in index2 files (we can read them but it's not setup yet.) auto indexFile = readIndexFile(dataDirectory + "/" + repository + "/" + indexFilename); @@ -445,4 +445,56 @@ std::optional GameData::readExcelSheet(std::string_view name) { } return {}; -} \ No newline at end of file +} + +void GameData::extractSkeleton() { + std::string path = fmt::format("chara/human/c0201/skeleton/base/b0001/skl_c0201b0001.sklb"); + + extractFile(path, "test.skel"); + + FILE* file = fopen("test.skel", "rb"); + + fseek(file, 0, SEEK_END); + size_t end = ftell(file); + fseek(file, 0, SEEK_SET); + + int32_t magic; + fread(&magic, sizeof(int32_t), 1, file); + + int32_t format; + fread(&format, sizeof(int32_t), 1, file); + + fseek(file, sizeof(uint16_t), SEEK_CUR); + + if(magic != 0x736B6C62) + fmt::print("INVALID SKLB magic"); + + size_t dataOffset = 0; + + switch(format) { + case 0x31323030: + fread(&dataOffset, sizeof(int16_t), 1, file); + break; + case 0x31333030: + case 0x31333031: + fseek(file, sizeof(uint16_t), SEEK_CUR); + fread(&dataOffset, sizeof(int16_t), 1, file); + break; + default: + fmt::print("INVALID SKLB format {}", format); + break; + } + + fseek(file, dataOffset, SEEK_SET); + + fmt::print("data offset: {}\n", dataOffset); + + std::vector havokData(end - dataOffset); + fread(havokData.data(), havokData.size(), 1, file); + + FILE* newFile = fopen("test.sklb.havok", "wb"); + fwrite(havokData.data(), havokData.size(), 1, newFile); + + fclose(newFile); + fclose(file); +} diff --git a/src/havokxmlparser.cpp b/src/havokxmlparser.cpp new file mode 100644 index 0000000..06a387b --- /dev/null +++ b/src/havokxmlparser.cpp @@ -0,0 +1,113 @@ +#include "havokxmlparser.h" + +#include +#include +#include +#include + +void walkSkeleton(Skeleton& skeleton, Bone *pBone, int level = 0); + +inline std::vector tokenize(const std::string_view string, const std::string_view& delimiters) { + std::vector tokens; + + const size_t length = string.length(); + size_t lastPos = 0; + + while(lastPos < length + 1) { + size_t pos = string.find_first_of(delimiters, lastPos); + if(pos == std::string_view::npos) + pos = length; + + if(pos != lastPos) + tokens.emplace_back(string.data() + lastPos, pos - lastPos); + + lastPos = pos + 1; + } + + return tokens; +} + +Skeleton parseHavokXML(const std::string_view path) { + Skeleton skeleton; + + pugi::xml_document doc; + doc.load_file(path.data()); + + pugi::xpath_node build_tool = doc.select_node("//hkobject[@name=\"#0052\"]/hkparam[@name=\"bones\"]"); + + auto bonesNode = build_tool.node(); + + fmt::print("num bones: {}\n", bonesNode.attribute("numelements").as_int()); + + skeleton.bones.reserve(bonesNode.attribute("numelements").as_int()); + + for(auto node : bonesNode.children()) { + pugi::xpath_node name_node = node.select_node("./hkparam[@name=\"name\"]"); + //fmt::print("{}\n", node.print()); + name_node.node().print(std::cout); + + Bone bone; + bone.name = name_node.node().text().as_string(); + + fmt::print("bone {}\n", bone.name); + + skeleton.bones.push_back(bone); + } + pugi::xpath_node parentNode = doc.select_node("//hkparam[@name=\"parentIndices\"]"); + + std::string text = parentNode.node().text().as_string(); + + auto parentIndices = tokenize(text, " "); + + for(int i = 0; i < parentIndices.size(); i++) { + auto indice = std::atoi(parentIndices[i].c_str()); + if(indice != -1) + skeleton.bones[i].parent = &skeleton.bones[indice]; + else + skeleton.root_bone = &skeleton.bones[i]; + } + + walkSkeleton(skeleton, skeleton.root_bone); + + pugi::xpath_node build_tool2 = doc.select_node("//hkobject[@name=\"#0052\"]/hkparam[@name=\"referencePose\"]"); + + fmt::print("num ref poses: {}\n", build_tool2.node().attribute("numelements").as_int()); + + auto matrices = tokenize(build_tool2.node().text().as_string(), "\n"); + int i = 0; + for(auto matrix : matrices) { + size_t firstParenthesis = matrix.find_first_of('('); + if(firstParenthesis != std::string::npos) { + matrix = matrix.substr(firstParenthesis); + std::replace(matrix.begin(), matrix.end(), ')', ' '); + matrix.erase(std::remove(matrix.begin(), matrix.end(), '('), matrix.end()); + fmt::print("{}\n", matrix); + + auto tokens = tokenize(matrix, " "); + std::array position = {std::stof(tokens[0]), std::stof(tokens[1]), std::stof(tokens[2])}; + std::array rotation = {std::stof(tokens[3]), std::stof(tokens[4]), std::stof(tokens[5]), std::stof(tokens[6])}; + std::array scale = {std::stof(tokens[7]), std::stof(tokens[8]), std::stof(tokens[9])}; + + skeleton.bones[i].position = position; + skeleton.bones[i].rotation = rotation; + skeleton.bones[i].scale = scale; + } + + i++; + } + + return skeleton; +} + +void walkSkeleton(Skeleton& skeleton, Bone *pBone, int level) { + for(int i = 0; i < level; i++) + fmt::print(" "); + + fmt::print("- {}\n", pBone->name); + + for(auto& bone : skeleton.bones) { + if(bone.parent == pBone) { + walkSkeleton(skeleton, &bone, level++); + } + } +}