DS2Revival/mods/modloader/test/gltfconv.cpp

136 lines
5.6 KiB
C++

// gltfconv.exe - the in-process glTF converter of DS2ModLoader (mods/modloader/gltf_*.cpp) as a command-line tool,
// with the same arguments and output files as tools/ds2mesh.py, so the two can be compared byte for byte
// (tools/gltf_cpp_check.py builds this and runs both).
//
// usage: gltfconv fromgltf IN.glb|IN.gltf OUT.asp [--name NAME] [--texture NAME ...] [--scale S] [--max-texture N]
// [--static] [--anims PREFIX [--loop]]
// gltfconv toprs IN.glb OUT.prs [--anim NAME|INDEX] [--scale S] [--loop]
// gltfconv prepare MODS_FOLDER RESOURCES_FOLDER LOG.txt
// Writes OUT.asp + <texture>.dds next to it (+ PREFIX<animation>.prs), or OUT.prs.
// prepare runs DS2ModLoader's cache step (gltf_mods.cpp) on a Mods folder, as the plugin does at game start, and
// writes its log; it prints the folder each mod's cache would be mounted from.
#include "../gltf.h"
#include "../gltf_mods.h"
#include "../../common/patch.h"
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#include <cstdio>
#include <string>
#include <vector>
namespace {
bool Write(const std::wstring& path, const gltf::Bytes& data) {
HANDLE h = CreateFileW(path.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, 0, nullptr);
if (h == INVALID_HANDLE_VALUE) return false;
DWORD put = 0;
bool ok = WriteFile(h, data.data(), static_cast<DWORD>(data.size()), &put, nullptr) && put == data.size();
CloseHandle(h);
return ok;
}
std::string U8(const std::wstring& w) {
if (w.empty()) return std::string();
int n = WideCharToMultiByte(CP_UTF8, 0, w.c_str(), static_cast<int>(w.size()), nullptr, 0, nullptr, nullptr);
std::string s(n, '\0');
WideCharToMultiByte(CP_UTF8, 0, w.c_str(), static_cast<int>(w.size()), &s[0], n, nullptr, nullptr);
return s;
}
std::wstring Opt(const std::vector<std::wstring>& a, const wchar_t* name, const std::wstring& def, bool* found = nullptr) {
std::wstring v = def;
if (found) *found = false;
for (size_t i = 0; i + 1 < a.size(); ++i)
if (a[i] == name) {
v = a[i + 1];
if (found) *found = true;
}
return v;
}
bool Has(const std::vector<std::wstring>& a, const wchar_t* name) {
for (const auto& x : a)
if (x == name) return true;
return false;
}
} // namespace
int wmain(int argc, wchar_t** argv) {
std::vector<std::wstring> a(argv + 1, argv + argc);
if (a.size() < 3) {
fprintf(stderr, "usage: gltfconv fromgltf IN OUT.asp [options] | gltfconv toprs IN OUT.prs [options]\n");
return 2;
}
if (a[0] == L"prepare" && a.size() >= 4) {
ds2::log().Open(U8(a[3]).c_str());
std::vector<gltfmods::ModInfo> mods;
mods.push_back({L"(Mods folder)", a[1], true});
WIN32_FIND_DATAW fd;
HANDLE h = FindFirstFileW((a[1] + L"\\*").c_str(), &fd);
if (h != INVALID_HANDLE_VALUE) {
do {
if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && fd.cFileName[0] != L'.' && fd.cFileName[0] != L'_')
mods.push_back({fd.cFileName, a[1] + L"\\" + fd.cFileName, false});
} while (FindNextFileW(h, &fd));
FindClose(h);
}
std::vector<std::string> caches = gltfmods::Prepare(mods, {a[2]});
for (size_t i = 0; i < mods.size(); ++i) printf("%s -> %s\n", U8(mods[i].name).c_str(), caches[i].c_str());
return 0;
}
gltf::Options opt;
opt.scale = _wtof(Opt(a, L"--scale", L"1").c_str());
opt.maxTexture = _wtoi(Opt(a, L"--max-texture", L"1024").c_str());
opt.loop = Has(a, L"--loop");
opt.forceStatic = Has(a, L"--static");
std::wstring in = a[1], out = a[2];
size_t slash = out.find_last_of(L"\\/");
std::wstring dir = slash == std::wstring::npos ? L"." : out.substr(0, slash);
std::wstring stem = out.substr(slash == std::wstring::npos ? 0 : slash + 1);
if (stem.find(L'.') != std::wstring::npos) stem = stem.substr(0, stem.rfind(L'.'));
gltf::Result r;
std::string err;
if (a[0] == L"fromgltf") {
opt.meshName = U8(Opt(a, L"--name", stem));
for (size_t i = 0; i + 1 < a.size(); ++i)
if (a[i] == L"--texture") opt.textureNames.push_back(U8(a[i + 1]));
bool anims = false;
std::wstring prefix = Opt(a, L"--anims", L"", &anims);
if (!gltf::ConvertMesh(in, opt, anims, r, err)) {
fprintf(stderr, "error: %s\n", err.c_str());
return 1;
}
if (!Write(out, r.asp)) return 1;
printf("wrote %s (%s, %u textures)\n", U8(out).c_str(), r.skinned ? "skinned" : "static", static_cast<unsigned>(r.textures.size()));
for (const auto& t : r.textures) {
std::wstring p = dir + L"\\" + std::wstring(t.name.begin(), t.name.end()) + L".dds";
if (!Write(p, t.data)) return 1;
printf("wrote %s\n", U8(p).c_str());
}
for (const auto& an : r.anims) {
std::wstring p = dir + L"\\" + prefix + std::wstring(an.name.begin(), an.name.end()) + L".prs";
if (!Write(p, an.data)) return 1;
printf("wrote %s\n", U8(p).c_str());
}
} else if (a[0] == L"toprs") {
if (!gltf::ConvertAnimation(in, U8(Opt(a, L"--anim", L"0")), opt, r, err)) {
fprintf(stderr, "error: %s\n", err.c_str());
return 1;
}
if (!Write(out, r.anims.at(0).data)) return 1;
printf("wrote %s\n", U8(out).c_str());
} else {
fprintf(stderr, "unknown command\n");
return 2;
}
for (const auto& w : r.warnings) printf("note: %s\n", w.c_str());
return 0;
}