136 lines
5.6 KiB
C++
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;
|
|
}
|