2
0
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:
Jan
2026-06-18 18:52:52 +02:00
committed by GitHub
parent 4fd164ee33
commit 85aa7417c4
53 changed files with 2692 additions and 964 deletions
@@ -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);
}
+153 -16
View File
@@ -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);
}
+48 -1
View File
@@ -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;
};
+76 -76
View File
@@ -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);
}
+15 -11
View File
@@ -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 {};
+18 -17
View File
@@ -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
View File
@@ -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();