Archived
1
Fork 0

Add very experimental skeleton loading

Added functions to extract Havok pak files, and extract some skeleton
data from an externally processed Havok XML file. This still requires
you to use the proprietary Havok SDK though, but I'll think of a
solution in the future.
This commit is contained in:
Joshua Goins 2022-04-12 23:27:21 -04:00
parent a7751a50bb
commit 68ecdca929
5 changed files with 199 additions and 4 deletions

View file

@ -72,7 +72,8 @@ add_library(libxiv STATIC
src/installextract.cpp src/installextract.cpp
src/patch.cpp src/patch.cpp
src/exlparser.cpp src/exlparser.cpp
src/mdlparser.cpp) src/mdlparser.cpp
src/havokxmlparser.cpp)
target_include_directories(libxiv PUBLIC include PRIVATE src) 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}) target_link_directories(libxiv PUBLIC ${LIB_DIRS})

View file

@ -26,6 +26,8 @@ public:
*/ */
void extractFile(std::string_view dataFilePath, std::string_view outPath); void extractFile(std::string_view dataFilePath, std::string_view outPath);
void extractSkeleton();
std::optional<EXH> readExcelSheet(std::string_view name); std::optional<EXH> readExcelSheet(std::string_view name);
std::vector<std::string> getAllSheetNames(); std::vector<std::string> getAllSheetNames();

27
include/havokxmlparser.h Normal file
View file

@ -0,0 +1,27 @@
#pragma once
#include <string>
#include <vector>
#include <array>
struct Bone {
std::string name;
Bone* parent = nullptr;
std::array<float, 16> localTransform, finalTransform;
std::array<float, 3> position;
std::array<float, 4> rotation;
std::array<float, 3> scale;
};
struct Skeleton {
std::vector<Bone> 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);

View file

@ -106,7 +106,7 @@ void GameData::extractFile(std::string_view dataFilePath, std::string_view outPa
// TODO: handle platforms other than win32 // TODO: handle platforms other than win32
auto indexFilename = calculateFilename(categoryToID[category], getExpansionID(repository), 0, "win32", "index"); 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.) // TODO: handle hashes in index2 files (we can read them but it's not setup yet.)
auto indexFile = readIndexFile(dataDirectory + "/" + repository + "/" + indexFilename); auto indexFile = readIndexFile(dataDirectory + "/" + repository + "/" + indexFilename);
@ -446,3 +446,55 @@ std::optional<EXH> GameData::readExcelSheet(std::string_view name) {
return {}; return {};
} }
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<uint8_t> 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);
}

113
src/havokxmlparser.cpp Normal file
View file

@ -0,0 +1,113 @@
#include "havokxmlparser.h"
#include <pugixml.hpp>
#include <fmt/core.h>
#include <iostream>
#include <algorithm>
void walkSkeleton(Skeleton& skeleton, Bone *pBone, int level = 0);
inline std::vector<std::string> tokenize(const std::string_view string, const std::string_view& delimiters) {
std::vector<std::string> 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<float, 3> position = {std::stof(tokens[0]), std::stof(tokens[1]), std::stof(tokens[2])};
std::array<float, 4> rotation = {std::stof(tokens[3]), std::stof(tokens[4]), std::stof(tokens[5]), std::stof(tokens[6])};
std::array<float, 3> 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++);
}
}
}