mirror of
https://github.com/Laupetin/OpenAssetTools.git
synced 2026-07-04 06:39:58 +00:00
feat: xmodel preview in ModMan (#835)
* chore: upgrade webwindowed for dynamic assets * chore: make enums in ModMan lowercase * chore: add missing platform wiiu in ModMan * fix: register asset handler on all windows * chore: properly localize game and platform * chore: render example cube as xmodel preview * chore: allow origin * in debug * feat: show preview of xmodels with ModMan * feat: show images in xmodel preview * feat: auto load search paths in ModMan * chore: load objcontainer of loaded zones in ModMan * chore: add iw4x specific recognized zone dirs * chore: show when models are loading * fix: make sure webwindowed handles window and app destruction in correct order * chore: track and properly free threejs resources * chore: add skybox for 3d preview * chore: add small border radius to preview * fix: linting * fix: linux compilation * chore: update package lock
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
#include "DynamicAssetsImage.h"
|
||||
|
||||
#include "Context/ModManContext.h"
|
||||
#include "Game/CommonAsset.h"
|
||||
#include "Image/DdsWriter.h"
|
||||
#include "Image/ImageToCommonConverter.h"
|
||||
#include "Pool/XAssetInfo.h"
|
||||
#include "SearchPath/SearchPaths.h"
|
||||
#include "Utils/Logging/Log.h"
|
||||
#include "Utils/StringUtils.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
|
||||
using namespace image;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace
|
||||
{
|
||||
bool FindImage(const std::string& zoneName, const std::string& assetName, XAssetInfoGeneric*& outAssetInfo, Zone*& outZone)
|
||||
{
|
||||
auto& context = ModManContext::Get().m_fast_file;
|
||||
const auto loadedZones = context.GetLoadedZones();
|
||||
for (const auto& loadedZone : loadedZones.Data())
|
||||
{
|
||||
const auto& zone = loadedZone->GetZone();
|
||||
if (zone.m_name != zoneName)
|
||||
continue;
|
||||
|
||||
const auto* assetTypeMapper = ICommonAssetTypeMapper::GetCommonAssetMapperByGame(zone.m_game_id);
|
||||
const auto gameAssetType = assetTypeMapper->CommonToGameAssetType(CommonAssetType::IMAGE);
|
||||
if (!gameAssetType)
|
||||
continue;
|
||||
|
||||
outAssetInfo = zone.m_pools.GetAsset(*gameAssetType, assetName);
|
||||
if (outAssetInfo)
|
||||
{
|
||||
outZone = &loadedZone->GetZone();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ImageDds(const webwindowed::dynamic_asset_request& request, webwindowed::dynamic_asset_response& response)
|
||||
{
|
||||
const auto imageName = request.get_query("name");
|
||||
const auto zoneName = request.get_query("zone");
|
||||
if (!imageName.has_value() || imageName->empty() || !zoneName.has_value() || zoneName->empty())
|
||||
{
|
||||
con::error("Bad dds request (name={} zone={})", imageName.value_or(""), zoneName.value_or(""));
|
||||
response.send_response(400);
|
||||
return;
|
||||
}
|
||||
|
||||
XAssetInfoGeneric* image;
|
||||
Zone* zone;
|
||||
if (!FindImage(*zoneName, *imageName, image, zone))
|
||||
{
|
||||
con::warn("Could not find image {} of zone {}", *imageName, *zoneName);
|
||||
response.send_response(404);
|
||||
return;
|
||||
}
|
||||
|
||||
assert(image);
|
||||
assert(zone);
|
||||
|
||||
const auto gameName = GameId_Names[std::to_underlying(zone->m_game_id)];
|
||||
const auto converter = ToCommonConverter::GetForGame(zone->m_game_id);
|
||||
if (!converter)
|
||||
{
|
||||
con::error("No image converter for game {}", gameName);
|
||||
response.send_response(500);
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_ptr<Texture> texture;
|
||||
{
|
||||
const auto searchPaths = ModManContext::Get().m_fast_file.GetSearchPaths();
|
||||
texture = converter->Convert(*image, searchPaths.Data());
|
||||
if (!texture)
|
||||
{
|
||||
con::warn("Failed to convert image {} of zone {}", *imageName, *zoneName);
|
||||
response.send_response(500);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::ostringstream ss;
|
||||
DdsWriter output;
|
||||
output.DumpImage(ss, texture.get());
|
||||
|
||||
const auto data = ss.str();
|
||||
response.set_content_type("image/x-direct-draw-surface");
|
||||
response.send_response(data.data(), data.size());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace image
|
||||
{
|
||||
void RegisterDynamicAssets(webwindowed::asset_handler_plugin& assetHandler)
|
||||
{
|
||||
assetHandler.add_dynamic_asset(webwindowed::dynamic_asset("image/dds", ImageDds));
|
||||
}
|
||||
} // namespace image
|
||||
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "Web/WebWindowedLib.h"
|
||||
|
||||
namespace image
|
||||
{
|
||||
void RegisterDynamicAssets(webwindowed::asset_handler_plugin& assetHandler);
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
#include "DynamicAssetsXModel.h"
|
||||
|
||||
#include "Context/ModManContext.h"
|
||||
#include "Game/CommonAsset.h"
|
||||
#include "Pool/XAssetInfo.h"
|
||||
#include "Utils/Logging/Log.h"
|
||||
#include "XModel/Gltf/GltfBinOutput.h"
|
||||
#include "XModel/Gltf/GltfWriter.h"
|
||||
#include "XModel/XModelToCommonConverter.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
using namespace xmodel;
|
||||
|
||||
namespace
|
||||
{
|
||||
bool FindXModel(const std::string& zoneName, const std::string& assetName, XAssetInfoGeneric*& outAssetInfo, Zone*& outZone)
|
||||
{
|
||||
auto& context = ModManContext::Get().m_fast_file;
|
||||
const auto loadedZones = context.GetLoadedZones();
|
||||
for (const auto& loadedZone : loadedZones.Data())
|
||||
{
|
||||
const auto& zone = loadedZone->GetZone();
|
||||
if (zone.m_name != zoneName)
|
||||
continue;
|
||||
|
||||
const auto* assetTypeMapper = ICommonAssetTypeMapper::GetCommonAssetMapperByGame(zone.m_game_id);
|
||||
const auto gameAssetType = assetTypeMapper->CommonToGameAssetType(CommonAssetType::XMODEL);
|
||||
if (!gameAssetType)
|
||||
continue;
|
||||
|
||||
outAssetInfo = zone.m_pools.GetAsset(*gameAssetType, assetName);
|
||||
if (outAssetInfo)
|
||||
{
|
||||
outZone = &loadedZone->GetZone();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void XModelGlb(const webwindowed::dynamic_asset_request& request, webwindowed::dynamic_asset_response& response)
|
||||
{
|
||||
const auto modelName = request.get_query("name");
|
||||
const auto zoneName = request.get_query("zone");
|
||||
if (!modelName.has_value() || modelName->empty() || !zoneName.has_value() || zoneName->empty())
|
||||
{
|
||||
con::error("Bad glb request (name={} zone={})", modelName.value_or(""), zoneName.value_or(""));
|
||||
response.send_response(400);
|
||||
return;
|
||||
}
|
||||
|
||||
XAssetInfoGeneric* model;
|
||||
Zone* zone;
|
||||
if (!FindXModel(*zoneName, *modelName, model, zone))
|
||||
{
|
||||
con::warn("Could not find xmodel {} of zone {}", *modelName, *zoneName);
|
||||
response.send_response(404);
|
||||
return;
|
||||
}
|
||||
|
||||
assert(model);
|
||||
assert(zone);
|
||||
|
||||
const auto gameName = GameId_Names[std::to_underlying(zone->m_game_id)];
|
||||
const auto converter = ToCommonConverter::GetForGame(zone->m_game_id);
|
||||
if (!converter)
|
||||
{
|
||||
con::error("No xmodel converter for game {}", gameName);
|
||||
response.send_response(500);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto maybeCommon = converter->Convert(*model, 0);
|
||||
if (!maybeCommon)
|
||||
{
|
||||
con::warn("Failed to convert xmodel {} of zone {}", *modelName, *zoneName);
|
||||
response.send_response(500);
|
||||
return;
|
||||
}
|
||||
|
||||
std::ostringstream ss;
|
||||
const gltf::BinOutput output(ss);
|
||||
const auto gltfWriter = gltf::Writer::CreateWriter(&output, gameName, *zoneName);
|
||||
gltfWriter->Write(*maybeCommon);
|
||||
|
||||
const auto data = ss.str();
|
||||
response.set_content_type("model/gltf-binary");
|
||||
response.send_response(data.data(), data.size());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace xmodel
|
||||
{
|
||||
void RegisterDynamicAssets(webwindowed::asset_handler_plugin& assetHandler)
|
||||
{
|
||||
assetHandler.add_dynamic_asset(webwindowed::dynamic_asset("xmodel/glb", XModelGlb));
|
||||
}
|
||||
} // namespace xmodel
|
||||
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "Web/WebWindowedLib.h"
|
||||
|
||||
namespace xmodel
|
||||
{
|
||||
void RegisterDynamicAssets(webwindowed::asset_handler_plugin& assetHandler);
|
||||
}
|
||||
@@ -1,5 +1,10 @@
|
||||
#include "FastFileContext.h"
|
||||
|
||||
#include "Game/AutoSearchPaths.h"
|
||||
#include "IObjLoader.h"
|
||||
#include "SearchPath/IWD.h"
|
||||
#include "SearchPath/SearchPathFilesystem.h"
|
||||
#include "Utils/StringUtils.h"
|
||||
#include "Web/Binds/ZoneBinds.h"
|
||||
#include "Web/UiCommunication.h"
|
||||
#include "ZoneLoading.h"
|
||||
@@ -36,14 +41,82 @@ namespace
|
||||
std::string m_zone_name;
|
||||
double m_last_progress;
|
||||
};
|
||||
|
||||
std::unique_ptr<ISearchPath> CreateSearchPath(const std::string& searchPathStr)
|
||||
{
|
||||
auto searchPath = std::make_unique<SearchPathFilesystem>(searchPathStr);
|
||||
con::debug("Loaded search path \"{}\"", searchPathStr);
|
||||
|
||||
SearchPaths searchPaths;
|
||||
bool hasIwds = false;
|
||||
|
||||
std::filesystem::directory_iterator iterator(searchPathStr);
|
||||
const auto end = fs::end(iterator);
|
||||
for (auto i = fs::begin(iterator); i != end; ++i)
|
||||
{
|
||||
if (!i->is_regular_file())
|
||||
continue;
|
||||
|
||||
auto extension = i->path().extension().string();
|
||||
utils::MakeStringLowerCase(extension);
|
||||
if (extension == ".iwd")
|
||||
{
|
||||
std::string iwdPath = i->path().string();
|
||||
auto iwd = iwd::LoadFromFile(iwdPath);
|
||||
if (iwd)
|
||||
{
|
||||
if (!hasIwds)
|
||||
{
|
||||
searchPaths.CommitSearchPath(std::move(searchPath));
|
||||
hasIwds = true;
|
||||
}
|
||||
|
||||
searchPaths.CommitSearchPath(std::move(iwd));
|
||||
con::debug("Loaded search path \"{}\"", iwdPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasIwds)
|
||||
return std::make_unique<SearchPaths>(std::move(searchPaths));
|
||||
|
||||
return searchPath;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
LoadedZone::LoadedZone(std::unique_ptr<Zone> zone, std::string filePath)
|
||||
: m_zone(std::move(zone)),
|
||||
m_file_path(std::move(filePath))
|
||||
ContextSearchPath::ContextSearchPath(std::unique_ptr<ISearchPath> searchPath)
|
||||
: m_search_path(std::move(searchPath)),
|
||||
m_ref_count(1)
|
||||
{
|
||||
}
|
||||
|
||||
LoadedZone::LoadedZone(std::unique_ptr<Zone> zone, std::string filePath, std::vector<std::string> searchPaths)
|
||||
: m_zone(std::move(zone)),
|
||||
m_file_path(std::move(filePath)),
|
||||
m_search_paths(std::move(searchPaths))
|
||||
{
|
||||
}
|
||||
|
||||
Zone& LoadedZone::GetZone()
|
||||
{
|
||||
return *m_zone;
|
||||
}
|
||||
|
||||
const Zone& LoadedZone::GetZone() const
|
||||
{
|
||||
return *m_zone;
|
||||
}
|
||||
|
||||
const std::string& LoadedZone::GetFilePath() const
|
||||
{
|
||||
return m_file_path;
|
||||
}
|
||||
|
||||
const std::vector<std::string>& LoadedZone::GetSearchPaths() const
|
||||
{
|
||||
return m_search_paths;
|
||||
}
|
||||
|
||||
void FastFileContext::Destroy()
|
||||
{
|
||||
// Unload all zones
|
||||
@@ -56,36 +129,100 @@ std::expected<LoadedZone*, std::string> FastFileContext::LoadFastFile(const std:
|
||||
if (!zone)
|
||||
return std::unexpected(std::move(zone.error()));
|
||||
|
||||
auto loadedZone = std::make_unique<LoadedZone>(std::move(*zone), path);
|
||||
|
||||
LoadedZone* result;
|
||||
auto searchPathsForZone = AutoSearchPaths::GetForGame((*zone)->m_game_id)->GetSearchPathsForZonePath(path);
|
||||
{
|
||||
std::lock_guard lock(m_zone_lock);
|
||||
result = m_loaded_zones.emplace_back(std::move(loadedZone)).get();
|
||||
std::lock_guard lock(m_search_path_lock);
|
||||
for (const auto& searchPathStr : searchPathsForZone)
|
||||
{
|
||||
const auto existingSearchPath = m_context_search_paths.find(searchPathStr);
|
||||
if (existingSearchPath == m_context_search_paths.end())
|
||||
{
|
||||
auto searchPath = CreateSearchPath(searchPathStr);
|
||||
m_search_paths.IncludeSearchPath(searchPath.get());
|
||||
m_context_search_paths.emplace(searchPathStr, std::make_unique<ContextSearchPath>(std::move(searchPath)));
|
||||
}
|
||||
else
|
||||
{
|
||||
existingSearchPath->second->m_ref_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ui::NotifyZoneLoaded(*result);
|
||||
auto loadedZone = std::make_unique<LoadedZone>(std::move(*zone), path, std::move(searchPathsForZone));
|
||||
|
||||
return result;
|
||||
LoadedZone* loadedZonePtr;
|
||||
{
|
||||
std::lock_guard lock(m_zone_lock);
|
||||
loadedZonePtr = m_loaded_zones.emplace_back(std::move(loadedZone)).get();
|
||||
}
|
||||
|
||||
{
|
||||
std::shared_lock lock(m_search_path_lock);
|
||||
IObjLoader::GetObjLoaderForGame(loadedZonePtr->GetZone().m_game_id)->LoadReferencedContainersForZone(m_search_paths, loadedZonePtr->GetZone());
|
||||
}
|
||||
|
||||
ui::NotifyZoneLoaded(*loadedZonePtr);
|
||||
|
||||
return loadedZonePtr;
|
||||
}
|
||||
|
||||
std::expected<void, std::string> FastFileContext::UnloadZone(const std::string& zoneName)
|
||||
{
|
||||
std::unique_ptr<LoadedZone> removedLoadedZone;
|
||||
|
||||
{
|
||||
std::lock_guard lock(m_zone_lock);
|
||||
const auto existingZone = std::ranges::find_if(m_loaded_zones,
|
||||
[&zoneName](const std::unique_ptr<LoadedZone>& loadedZone)
|
||||
{
|
||||
return loadedZone->m_zone->m_name == zoneName;
|
||||
return loadedZone->GetZone().m_name == zoneName;
|
||||
});
|
||||
|
||||
if (existingZone != m_loaded_zones.end())
|
||||
if (existingZone == m_loaded_zones.end())
|
||||
return std::unexpected(std::format("No zone with name {} loaded", zoneName));
|
||||
|
||||
removedLoadedZone = std::move(*existingZone);
|
||||
m_loaded_zones.erase(existingZone);
|
||||
|
||||
ui::NotifyZoneUnloaded(zoneName);
|
||||
}
|
||||
|
||||
assert(removedLoadedZone);
|
||||
|
||||
{
|
||||
std::shared_lock lock(m_search_path_lock);
|
||||
IObjLoader::GetObjLoaderForGame(removedLoadedZone->GetZone().m_game_id)->UnloadContainersOfZone(removedLoadedZone->GetZone());
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard lock(m_search_path_lock);
|
||||
for (const auto& searchPathStr : removedLoadedZone->GetSearchPaths())
|
||||
{
|
||||
m_loaded_zones.erase(existingZone);
|
||||
ui::NotifyZoneUnloaded(zoneName);
|
||||
return {};
|
||||
const auto existingSearchPath = m_context_search_paths.find(searchPathStr);
|
||||
if (existingSearchPath != m_context_search_paths.end())
|
||||
{
|
||||
assert(existingSearchPath->second->m_ref_count > 0);
|
||||
const auto newRefCount = --existingSearchPath->second->m_ref_count;
|
||||
|
||||
if (newRefCount == 0)
|
||||
{
|
||||
m_search_paths.RemoveSearchPath(existingSearchPath->second->m_search_path.get());
|
||||
m_context_search_paths.erase(existingSearchPath);
|
||||
con::debug("Unloaded search path \"{}\"", searchPathStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return std::unexpected(std::format("No zone with name {} loaded", zoneName));
|
||||
return {};
|
||||
}
|
||||
|
||||
ReadAccess<const std::vector<std::unique_ptr<LoadedZone>>> FastFileContext::GetLoadedZones()
|
||||
{
|
||||
return ReadAccess<const std::vector<std::unique_ptr<LoadedZone>>>(std::shared_lock(m_zone_lock), m_loaded_zones);
|
||||
}
|
||||
|
||||
ReadAccess<ISearchPath> FastFileContext::GetSearchPaths()
|
||||
{
|
||||
return ReadAccess<ISearchPath>(std::shared_lock(m_search_path_lock), m_search_paths);
|
||||
}
|
||||
|
||||
@@ -1,19 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include "SearchPath/SearchPaths.h"
|
||||
#include "Zone/Zone.h"
|
||||
|
||||
#include <expected>
|
||||
#include <memory>
|
||||
#include <shared_mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
class ContextSearchPath
|
||||
{
|
||||
public:
|
||||
explicit ContextSearchPath(std::unique_ptr<ISearchPath> searchPath);
|
||||
|
||||
std::unique_ptr<ISearchPath> m_search_path;
|
||||
unsigned m_ref_count;
|
||||
};
|
||||
|
||||
class LoadedZone
|
||||
{
|
||||
public:
|
||||
LoadedZone(std::unique_ptr<Zone> zone, std::string filePath, std::vector<std::string> searchPaths);
|
||||
|
||||
[[nodiscard]] Zone& GetZone();
|
||||
[[nodiscard]] const Zone& GetZone() const;
|
||||
|
||||
[[nodiscard]] const std::string& GetFilePath() const;
|
||||
[[nodiscard]] const std::vector<std::string>& GetSearchPaths() const;
|
||||
|
||||
private:
|
||||
std::unique_ptr<Zone> m_zone;
|
||||
std::string m_file_path;
|
||||
std::vector<std::string> m_search_paths;
|
||||
};
|
||||
|
||||
LoadedZone(std::unique_ptr<Zone> zone, std::string filePath);
|
||||
template<class T> class ReadAccess
|
||||
{
|
||||
public:
|
||||
ReadAccess(std::shared_lock<std::shared_mutex> lock, T& data)
|
||||
: m_read_lock(std::move(lock)),
|
||||
m_data(data)
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] T& Data() const
|
||||
{
|
||||
return m_data;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_lock<std::shared_mutex> m_read_lock;
|
||||
T& m_data;
|
||||
};
|
||||
|
||||
class FastFileContext
|
||||
@@ -24,6 +63,14 @@ public:
|
||||
std::expected<LoadedZone*, std::string> LoadFastFile(const std::string& path);
|
||||
std::expected<void, std::string> UnloadZone(const std::string& zoneName);
|
||||
|
||||
ReadAccess<const std::vector<std::unique_ptr<LoadedZone>>> GetLoadedZones();
|
||||
ReadAccess<ISearchPath> GetSearchPaths();
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<LoadedZone>> m_loaded_zones;
|
||||
std::shared_mutex m_zone_lock;
|
||||
|
||||
SearchPaths m_search_paths;
|
||||
std::unordered_map<std::string, std::unique_ptr<ContextSearchPath>> m_context_search_paths;
|
||||
std::shared_mutex m_search_path_lock;
|
||||
};
|
||||
|
||||
@@ -9,79 +9,79 @@
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(CommonAssetType,
|
||||
{
|
||||
{CommonAssetType::PHYS_PRESET, "PHYS_PRESET" },
|
||||
{CommonAssetType::XANIM, "XANIM" },
|
||||
{CommonAssetType::XMODEL, "XMODEL" },
|
||||
{CommonAssetType::MATERIAL, "MATERIAL" },
|
||||
{CommonAssetType::TECHNIQUE_SET, "TECHNIQUE_SET" },
|
||||
{CommonAssetType::IMAGE, "IMAGE" },
|
||||
{CommonAssetType::SOUND, "SOUND" },
|
||||
{CommonAssetType::SOUND_CURVE, "SOUND_CURVE" },
|
||||
{CommonAssetType::LOADED_SOUND, "LOADED_SOUND" },
|
||||
{CommonAssetType::CLIP_MAP, "CLIP_MAP" },
|
||||
{CommonAssetType::COM_WORLD, "COM_WORLD" },
|
||||
{CommonAssetType::GAME_WORLD_SP, "GAME_WORLD_SP" },
|
||||
{CommonAssetType::GAME_WORLD_MP, "GAME_WORLD_MP" },
|
||||
{CommonAssetType::MAP_ENTS, "MAP_ENTS" },
|
||||
{CommonAssetType::GFX_WORLD, "GFX_WORLD" },
|
||||
{CommonAssetType::LIGHT_DEF, "LIGHT_DEF" },
|
||||
{CommonAssetType::UI_MAP, "UI_MAP" },
|
||||
{CommonAssetType::FONT, "FONT" },
|
||||
{CommonAssetType::MENU_LIST, "MENU_LIST" },
|
||||
{CommonAssetType::MENU, "MENU" },
|
||||
{CommonAssetType::LOCALIZE_ENTRY, "LOCALIZE_ENTRY" },
|
||||
{CommonAssetType::WEAPON, "WEAPON" },
|
||||
{CommonAssetType::SOUND_DRIVER_GLOBALS, "SOUND_DRIVER_GLOBALS"},
|
||||
{CommonAssetType::FX, "FX" },
|
||||
{CommonAssetType::IMPACT_FX, "IMPACT_FX" },
|
||||
{CommonAssetType::AI_TYPE, "AI_TYPE" },
|
||||
{CommonAssetType::MP_TYPE, "MP_TYPE" },
|
||||
{CommonAssetType::CHARACTER, "CHARACTER" },
|
||||
{CommonAssetType::XMODEL_ALIAS, "XMODEL_ALIAS" },
|
||||
{CommonAssetType::RAW_FILE, "RAW_FILE" },
|
||||
{CommonAssetType::STRING_TABLE, "STRING_TABLE" },
|
||||
{CommonAssetType::XMODEL_PIECES, "XMODEL_PIECES" },
|
||||
{CommonAssetType::PHYS_COLL_MAP, "PHYS_COLL_MAP" },
|
||||
{CommonAssetType::XMODEL_SURFS, "XMODEL_SURFS" },
|
||||
{CommonAssetType::PIXEL_SHADER, "PIXEL_SHADER" },
|
||||
{CommonAssetType::VERTEX_SHADER, "VERTEX_SHADER" },
|
||||
{CommonAssetType::VERTEX_DECL, "VERTEX_DECL" },
|
||||
{CommonAssetType::FX_WORLD, "FX_WORLD" },
|
||||
{CommonAssetType::LEADERBOARD, "LEADERBOARD" },
|
||||
{CommonAssetType::STRUCTURED_DATA_DEF, "STRUCTURED_DATA_DEF" },
|
||||
{CommonAssetType::TRACER, "TRACER" },
|
||||
{CommonAssetType::VEHICLE, "VEHICLE" },
|
||||
{CommonAssetType::ADDON_MAP_ENTS, "ADDON_MAP_ENTS" },
|
||||
{CommonAssetType::GLASS_WORLD, "GLASS_WORLD" },
|
||||
{CommonAssetType::PATH_DATA, "PATH_DATA" },
|
||||
{CommonAssetType::VEHICLE_TRACK, "VEHICLE_TRACK" },
|
||||
{CommonAssetType::ATTACHMENT, "ATTACHMENT" },
|
||||
{CommonAssetType::SURFACE_FX, "SURFACE_FX" },
|
||||
{CommonAssetType::SCRIPT, "SCRIPT" },
|
||||
{CommonAssetType::PHYS_CONSTRAINTS, "PHYS_CONSTRAINTS" },
|
||||
{CommonAssetType::DESTRUCTIBLE_DEF, "DESTRUCTIBLE_DEF" },
|
||||
{CommonAssetType::SOUND_PATCH, "SOUND_PATCH" },
|
||||
{CommonAssetType::WEAPON_DEF, "WEAPON_DEF" },
|
||||
{CommonAssetType::WEAPON_VARIANT, "WEAPON_VARIANT" },
|
||||
{CommonAssetType::MP_BODY, "MP_BODY" },
|
||||
{CommonAssetType::MP_HEAD, "MP_HEAD" },
|
||||
{CommonAssetType::PACK_INDEX, "PACK_INDEX" },
|
||||
{CommonAssetType::XGLOBALS, "XGLOBALS" },
|
||||
{CommonAssetType::DDL, "DDL" },
|
||||
{CommonAssetType::GLASSES, "GLASSES" },
|
||||
{CommonAssetType::EMBLEM_SET, "EMBLEM_SET" },
|
||||
{CommonAssetType::FONT_ICON, "FONT_ICON" },
|
||||
{CommonAssetType::WEAPON_FULL, "WEAPON_FULL" },
|
||||
{CommonAssetType::ATTACHMENT_UNIQUE, "ATTACHMENT_UNIQUE" },
|
||||
{CommonAssetType::WEAPON_CAMO, "WEAPON_CAMO" },
|
||||
{CommonAssetType::KEY_VALUE_PAIRS, "KEY_VALUE_PAIRS" },
|
||||
{CommonAssetType::MEMORY_BLOCK, "MEMORY_BLOCK" },
|
||||
{CommonAssetType::SKINNED_VERTS, "SKINNED_VERTS" },
|
||||
{CommonAssetType::QDB, "QDB" },
|
||||
{CommonAssetType::SLUG, "SLUG" },
|
||||
{CommonAssetType::FOOTSTEP_TABLE, "FOOTSTEP_TABLE" },
|
||||
{CommonAssetType::FOOTSTEP_FX_TABLE, "FOOTSTEP_FX_TABLE" },
|
||||
{CommonAssetType::ZBARRIER, "ZBARRIER" },
|
||||
{CommonAssetType::PHYS_PRESET, "phys_preset" },
|
||||
{CommonAssetType::XANIM, "xanim" },
|
||||
{CommonAssetType::XMODEL, "xmodel" },
|
||||
{CommonAssetType::MATERIAL, "material" },
|
||||
{CommonAssetType::TECHNIQUE_SET, "technique_set" },
|
||||
{CommonAssetType::IMAGE, "image" },
|
||||
{CommonAssetType::SOUND, "sound" },
|
||||
{CommonAssetType::SOUND_CURVE, "sound_curve" },
|
||||
{CommonAssetType::LOADED_SOUND, "loaded_sound" },
|
||||
{CommonAssetType::CLIP_MAP, "clip_map" },
|
||||
{CommonAssetType::COM_WORLD, "com_world" },
|
||||
{CommonAssetType::GAME_WORLD_SP, "game_world_sp" },
|
||||
{CommonAssetType::GAME_WORLD_MP, "game_world_mp" },
|
||||
{CommonAssetType::MAP_ENTS, "map_ents" },
|
||||
{CommonAssetType::GFX_WORLD, "gfx_world" },
|
||||
{CommonAssetType::LIGHT_DEF, "light_def" },
|
||||
{CommonAssetType::UI_MAP, "ui_map" },
|
||||
{CommonAssetType::FONT, "font" },
|
||||
{CommonAssetType::MENU_LIST, "menu_list" },
|
||||
{CommonAssetType::MENU, "menu" },
|
||||
{CommonAssetType::LOCALIZE_ENTRY, "localize_entry" },
|
||||
{CommonAssetType::WEAPON, "weapon" },
|
||||
{CommonAssetType::SOUND_DRIVER_GLOBALS, "sound_driver_globals"},
|
||||
{CommonAssetType::FX, "fx" },
|
||||
{CommonAssetType::IMPACT_FX, "impact_fx" },
|
||||
{CommonAssetType::AI_TYPE, "ai_type" },
|
||||
{CommonAssetType::MP_TYPE, "mp_type" },
|
||||
{CommonAssetType::CHARACTER, "character" },
|
||||
{CommonAssetType::XMODEL_ALIAS, "xmodel_alias" },
|
||||
{CommonAssetType::RAW_FILE, "raw_file" },
|
||||
{CommonAssetType::STRING_TABLE, "string_table" },
|
||||
{CommonAssetType::XMODEL_PIECES, "xmodel_pieces" },
|
||||
{CommonAssetType::PHYS_COLL_MAP, "phys_coll_map" },
|
||||
{CommonAssetType::XMODEL_SURFS, "xmodel_surfs" },
|
||||
{CommonAssetType::PIXEL_SHADER, "pixel_shader" },
|
||||
{CommonAssetType::VERTEX_SHADER, "vertex_shader" },
|
||||
{CommonAssetType::VERTEX_DECL, "vertex_decl" },
|
||||
{CommonAssetType::FX_WORLD, "fx_world" },
|
||||
{CommonAssetType::LEADERBOARD, "leaderboard" },
|
||||
{CommonAssetType::STRUCTURED_DATA_DEF, "structured_data_def" },
|
||||
{CommonAssetType::TRACER, "tracer" },
|
||||
{CommonAssetType::VEHICLE, "vehicle" },
|
||||
{CommonAssetType::ADDON_MAP_ENTS, "addon_map_ents" },
|
||||
{CommonAssetType::GLASS_WORLD, "glass_world" },
|
||||
{CommonAssetType::PATH_DATA, "path_data" },
|
||||
{CommonAssetType::VEHICLE_TRACK, "vehicle_track" },
|
||||
{CommonAssetType::ATTACHMENT, "attachment" },
|
||||
{CommonAssetType::SURFACE_FX, "surface_fx" },
|
||||
{CommonAssetType::SCRIPT, "script" },
|
||||
{CommonAssetType::PHYS_CONSTRAINTS, "phys_constraints" },
|
||||
{CommonAssetType::DESTRUCTIBLE_DEF, "destructible_def" },
|
||||
{CommonAssetType::SOUND_PATCH, "sound_patch" },
|
||||
{CommonAssetType::WEAPON_DEF, "weapon_def" },
|
||||
{CommonAssetType::WEAPON_VARIANT, "weapon_variant" },
|
||||
{CommonAssetType::MP_BODY, "mp_body" },
|
||||
{CommonAssetType::MP_HEAD, "mp_head" },
|
||||
{CommonAssetType::PACK_INDEX, "pack_index" },
|
||||
{CommonAssetType::XGLOBALS, "xglobals" },
|
||||
{CommonAssetType::DDL, "ddl" },
|
||||
{CommonAssetType::GLASSES, "glasses" },
|
||||
{CommonAssetType::EMBLEM_SET, "emblem_set" },
|
||||
{CommonAssetType::FONT_ICON, "font_icon" },
|
||||
{CommonAssetType::WEAPON_FULL, "weapon_full" },
|
||||
{CommonAssetType::ATTACHMENT_UNIQUE, "attachment_unique" },
|
||||
{CommonAssetType::WEAPON_CAMO, "weapon_camo" },
|
||||
{CommonAssetType::KEY_VALUE_PAIRS, "key_value_pairs" },
|
||||
{CommonAssetType::MEMORY_BLOCK, "memory_block" },
|
||||
{CommonAssetType::SKINNED_VERTS, "skinned_verts" },
|
||||
{CommonAssetType::QDB, "qdb" },
|
||||
{CommonAssetType::SLUG, "slug" },
|
||||
{CommonAssetType::FOOTSTEP_TABLE, "footstep_table" },
|
||||
{CommonAssetType::FOOTSTEP_FX_TABLE, "footstep_fx_table" },
|
||||
{CommonAssetType::ZBARRIER, "zbarrier" },
|
||||
});
|
||||
|
||||
namespace
|
||||
@@ -146,11 +146,11 @@ namespace
|
||||
ZoneAssetsDto result;
|
||||
|
||||
{
|
||||
std::shared_lock lock(context.m_zone_lock);
|
||||
const auto loadedZones = context.GetLoadedZones();
|
||||
|
||||
for (const auto& loadedZone : context.m_loaded_zones)
|
||||
for (const auto& loadedZone : loadedZones.Data())
|
||||
{
|
||||
const auto& zone = *loadedZone->m_zone;
|
||||
const auto& zone = loadedZone->GetZone();
|
||||
if (zone.m_name == zoneName)
|
||||
return CreateZoneAssetsDto(zone);
|
||||
}
|
||||
|
||||
@@ -52,19 +52,23 @@ namespace
|
||||
|
||||
std::expected<void, std::string> UnlinkZoneInDbThread(const std::string& zoneName)
|
||||
{
|
||||
const auto& context = ModManContext::Get().m_fast_file;
|
||||
const auto existingZone = std::ranges::find_if(context.m_loaded_zones,
|
||||
[&zoneName](const std::unique_ptr<LoadedZone>& loadedZone)
|
||||
{
|
||||
return loadedZone->m_zone->m_name == zoneName;
|
||||
});
|
||||
Zone* zone;
|
||||
{
|
||||
auto& context = ModManContext::Get().m_fast_file;
|
||||
const auto loadedZones = context.GetLoadedZones();
|
||||
const auto existingZone = std::ranges::find_if(loadedZones.Data(),
|
||||
[&zoneName](const std::unique_ptr<LoadedZone>& loadedZone)
|
||||
{
|
||||
return loadedZone->GetZone().m_name == zoneName;
|
||||
});
|
||||
|
||||
if (existingZone == context.m_loaded_zones.end())
|
||||
return std::unexpected(std::format("No zone with name {} loaded", zoneName));
|
||||
if (existingZone == loadedZones.Data().end())
|
||||
return std::unexpected(std::format("No zone with name {} loaded", zoneName));
|
||||
|
||||
const auto& loadedZone = *existingZone->get();
|
||||
zone = &existingZone->get()->GetZone();
|
||||
}
|
||||
|
||||
auto* objWriter = IObjWriter::GetObjWriterForGame(loadedZone.m_zone->m_game_id);
|
||||
auto* objWriter = IObjWriter::GetObjWriterForGame(zone->m_game_id);
|
||||
|
||||
const auto outputFolderPath = fs::path(utils::GetExecutablePath()).parent_path() / "zone_dump" / zoneName;
|
||||
const auto outputFolderPathStr = outputFolderPath.string();
|
||||
@@ -72,7 +76,7 @@ namespace
|
||||
OutputPathFilesystem outputFolderOutputPath(outputFolderPath);
|
||||
SearchPaths searchPaths;
|
||||
AssetDumpingContext dumpingContext(
|
||||
*loadedZone.m_zone, outputFolderPathStr, outputFolderOutputPath, searchPaths, std::make_unique<UnlinkingEventProgressReporter>(zoneName));
|
||||
*zone, outputFolderPathStr, outputFolderOutputPath, searchPaths, std::make_unique<UnlinkingEventProgressReporter>(zoneName));
|
||||
objWriter->DumpZone(dumpingContext);
|
||||
|
||||
return {};
|
||||
|
||||
@@ -7,19 +7,20 @@
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(GameId,
|
||||
{
|
||||
{GameId::IW3, "IW3"},
|
||||
{GameId::IW4, "IW4"},
|
||||
{GameId::IW5, "IW5"},
|
||||
{GameId::T4, "T4" },
|
||||
{GameId::T5, "T5" },
|
||||
{GameId::T6, "T6" },
|
||||
{GameId::IW3, "iw3"},
|
||||
{GameId::IW4, "iw4"},
|
||||
{GameId::IW5, "iw5"},
|
||||
{GameId::T4, "t4" },
|
||||
{GameId::T5, "t5" },
|
||||
{GameId::T6, "t6" },
|
||||
});
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(GamePlatform,
|
||||
{
|
||||
{GamePlatform::PC, "PC" },
|
||||
{GamePlatform::XBOX, "XBOX"},
|
||||
{GamePlatform::PS3, "PS3" },
|
||||
{GamePlatform::PC, "pc" },
|
||||
{GamePlatform::XBOX, "xbox"},
|
||||
{GamePlatform::PS3, "ps3" },
|
||||
{GamePlatform::WIIU, "wiiu"},
|
||||
});
|
||||
|
||||
namespace
|
||||
@@ -63,10 +64,10 @@ namespace
|
||||
ZoneDto CreateZoneDto(const LoadedZone& loadedZone)
|
||||
{
|
||||
return ZoneDto{
|
||||
.name = loadedZone.m_zone->m_name,
|
||||
.filePath = loadedZone.m_file_path,
|
||||
.game = loadedZone.m_zone->m_game_id,
|
||||
.platform = loadedZone.m_zone->m_platform,
|
||||
.name = loadedZone.GetZone().m_name,
|
||||
.filePath = loadedZone.GetFilePath(),
|
||||
.game = loadedZone.GetZone().m_game_id,
|
||||
.platform = loadedZone.GetZone().m_platform,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -77,10 +78,10 @@ namespace
|
||||
std::vector<ZoneDto> result;
|
||||
|
||||
{
|
||||
std::shared_lock lock(context.m_zone_lock);
|
||||
result.reserve(context.m_loaded_zones.size());
|
||||
const auto loadedZones = context.GetLoadedZones();
|
||||
result.reserve(loadedZones.Data().size());
|
||||
|
||||
for (const auto& loadedZone : context.m_loaded_zones)
|
||||
for (const auto& loadedZone : loadedZones.Data())
|
||||
{
|
||||
result.emplace_back(CreateZoneDto(*loadedZone));
|
||||
}
|
||||
@@ -105,7 +106,7 @@ namespace
|
||||
ZoneLoadedDto{
|
||||
.zone = CreateZoneDto(*maybeZone.value()),
|
||||
});
|
||||
con::debug("Loaded zone \"{}\"", maybeZone.value()->m_zone->m_name);
|
||||
con::debug("Loaded zone \"{}\"", maybeZone.value()->GetZone().m_name);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+44
-16
@@ -6,6 +6,10 @@
|
||||
#include "Web/ViteAssets.h"
|
||||
#include "Web/WebWindowedLib.h"
|
||||
|
||||
// Assets
|
||||
#include "Asset/Image/DynamicAssetsImage.h"
|
||||
#include "Asset/XModel/DynamicAssetsXModel.h"
|
||||
|
||||
#include <format>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
@@ -19,7 +23,7 @@ using namespace std::string_literals;
|
||||
namespace
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
void SpawnDevToolsWindow()
|
||||
void CreateDevToolsWindow()
|
||||
{
|
||||
con::debug("Creating dev tools window");
|
||||
|
||||
@@ -31,11 +35,19 @@ namespace
|
||||
newWindow.set_title("Devtools");
|
||||
newWindow.set_window_size(640, 480);
|
||||
newWindow.set_window_min(480, 320);
|
||||
(void)newWindow.navigate(std::format("http://localhost:{}/__devtools__/", VITE_DEV_SERVER_PORT));
|
||||
const auto result = newWindow.navigate(std::format("http://localhost:{}/__devtools__/", VITE_DEV_SERVER_PORT));
|
||||
if (!result.has_value())
|
||||
con::error("Dev tools window navigation failed: {}", result.error().message());
|
||||
}
|
||||
#endif
|
||||
|
||||
int SpawnMainWindow()
|
||||
void RegisterDynamicAssets(webwindowed::asset_handler_plugin& assetHandler)
|
||||
{
|
||||
image::RegisterDynamicAssets(assetHandler);
|
||||
xmodel::RegisterDynamicAssets(assetHandler);
|
||||
}
|
||||
|
||||
int RunModManApp()
|
||||
{
|
||||
con::debug("Creating main window");
|
||||
|
||||
@@ -48,12 +60,21 @@ namespace
|
||||
#endif
|
||||
|
||||
newWindow.set_title("OpenAssetTools ModMan");
|
||||
// newWindow.set_window_min(640, 480);
|
||||
newWindow.set_window_min(640, 480);
|
||||
newWindow.set_window_size(1280, 640);
|
||||
|
||||
const auto assetHandlerPlugin = std::make_shared<webwindowed::asset_handler_plugin>(VITE_ASSETS, std::extent_v<decltype(VITE_ASSETS)>);
|
||||
const auto assetHandlerPlugin = std::make_shared<webwindowed::asset_handler_plugin>();
|
||||
assetHandlerPlugin->set_protocol_name("modman");
|
||||
newWindow.register_plugin(assetHandlerPlugin);
|
||||
|
||||
#ifdef _DEBUG
|
||||
// Allow assets from dev server to access dynamic assets
|
||||
assetHandlerPlugin->set_allow_all_origins(true);
|
||||
#endif
|
||||
|
||||
for (const auto& asset : VITE_ASSETS)
|
||||
assetHandlerPlugin->add_static_asset(webwindowed::static_asset(asset.filename, asset.data, asset.dataSize));
|
||||
|
||||
RegisterDynamicAssets(*assetHandlerPlugin);
|
||||
|
||||
webwindowed::commands_builder commands;
|
||||
ui::RegisterAllBinds(commands);
|
||||
@@ -62,23 +83,30 @@ namespace
|
||||
#ifdef _DEBUG
|
||||
auto result = newWindow.navigate(VITE_DEV_SERVER ? std::format("http://localhost:{}", VITE_DEV_SERVER_PORT)
|
||||
: assetHandlerPlugin->get_url_for_asset("index.html"));
|
||||
if (VITE_DEV_SERVER)
|
||||
{
|
||||
newWindow.dispatch(
|
||||
[]
|
||||
{
|
||||
SpawnDevToolsWindow();
|
||||
});
|
||||
}
|
||||
#else
|
||||
auto result = newWindow.navigate(assetHandlerPlugin->get_url_for_asset("index.html"));
|
||||
#endif
|
||||
if (!result.has_value())
|
||||
con::error("Main window navigation failed: {}", result.error().message());
|
||||
|
||||
webwindowed::app app;
|
||||
app.register_plugin(assetHandlerPlugin);
|
||||
app.register_plugin(std::make_shared<webwindowed::favicon_handler_plugin>());
|
||||
app.register_plugin(std::make_shared<webwindowed::title_handler_plugin>());
|
||||
|
||||
(void)app.run(context.m_main_window);
|
||||
#ifdef _DEBUG
|
||||
if (VITE_DEV_SERVER)
|
||||
{
|
||||
CreateDevToolsWindow();
|
||||
result = app.open_window(context.m_dev_tools_window);
|
||||
if (!result.has_value())
|
||||
con::error("Failed to open dev tools window: {}", result.error().message());
|
||||
}
|
||||
#endif
|
||||
|
||||
result = app.run(context.m_main_window);
|
||||
if (!result.has_value())
|
||||
con::error("Error while running app: {}", result.error().message());
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -128,7 +156,7 @@ int main(int argc, const char** argv)
|
||||
|
||||
ModManContext::Get().Startup();
|
||||
|
||||
const auto result = SpawnMainWindow();
|
||||
const auto result = RunModManApp();
|
||||
|
||||
ModManContext::Get().Destroy();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user