diff --git a/src/Linker/Linker.cpp b/src/Linker/Linker.cpp index 61050cf0..3da46b23 100644 --- a/src/Linker/Linker.cpp +++ b/src/Linker/Linker.cpp @@ -364,14 +364,16 @@ class LinkerImpl final : public Linker zoneDirectory = fs::current_path(); auto absoluteZoneDirectory = absolute(zoneDirectory).string(); - auto zone = ZoneLoading::LoadZone(zonePath); - if (!zone) + auto maybeZone = ZoneLoading::LoadZone(zonePath); + if (!maybeZone) { - con::error("Failed to load zone \"{}\".", zonePath); + con::error("Failed to load zone \"{}\": {}", zonePath, maybeZone.error()); return false; } - con::debug("Load zone \"{}\"", zone->m_name); + auto zone = std::move(*maybeZone); + + con::debug("Loaded zone \"{}\"", zone->m_name); m_loaded_zones.emplace_back(std::move(zone)); } diff --git a/src/ModMan.lua b/src/ModMan.lua index c160f700..00272503 100644 --- a/src/ModMan.lua +++ b/src/ModMan.lua @@ -47,10 +47,16 @@ function ModMan:project() self:include(includes) Utils:include(includes) + ZoneLoading:include(includes) + ObjLoading:include(includes) + ObjWriting:include(includes) json:include(includes) webview:include(includes) links:linkto(Utils) + links:linkto(ZoneLoading) + links:linkto(ObjLoading) + links:linkto(ObjWriting) links:linkto(webview) links:linkall() end diff --git a/src/ModMan/Context/FastFileContext.cpp b/src/ModMan/Context/FastFileContext.cpp new file mode 100644 index 00000000..3af0d92d --- /dev/null +++ b/src/ModMan/Context/FastFileContext.cpp @@ -0,0 +1,42 @@ +#include "FastFileContext.h" + +#include "Web/Binds/ZoneBinds.h" +#include "Web/UiCommunication.h" +#include "ZoneLoading.h" + +void FastFileContext::Destroy() +{ + // Unload all zones + m_loaded_zones.clear(); +} + +result::Expected FastFileContext::LoadFastFile(const std::string& path) +{ + auto zone = ZoneLoading::LoadZone(path); + if (!zone) + return result::Unexpected(std::move(zone.error())); + + auto* result = m_loaded_zones.emplace_back(std::move(*zone)).get(); + + ui::NotifyZoneLoaded(result->m_name, path); + + return result; +} + +result::Expected FastFileContext::UnloadZone(const std::string& zoneName) +{ + const auto existingZone = std::ranges::find_if(m_loaded_zones, + [&zoneName](const std::unique_ptr& zone) + { + return zone->m_name == zoneName; + }); + + if (existingZone != m_loaded_zones.end()) + { + m_loaded_zones.erase(existingZone); + ui::NotifyZoneUnloaded(zoneName); + return NoResult(); + } + + return result::Unexpected(std::format("No zone with name {} loaded", zoneName)); +} diff --git a/src/ModMan/Context/FastFileContext.h b/src/ModMan/Context/FastFileContext.h new file mode 100644 index 00000000..a3f87a42 --- /dev/null +++ b/src/ModMan/Context/FastFileContext.h @@ -0,0 +1,18 @@ +#pragma once + +#include "Utils/Result.h" +#include "Zone/Zone.h" + +#include +#include + +class FastFileContext +{ +public: + void Destroy(); + + result::Expected LoadFastFile(const std::string& path); + result::Expected UnloadZone(const std::string& zoneName); + + std::vector> m_loaded_zones; +}; diff --git a/src/ModMan/Context/ModManContext.cpp b/src/ModMan/Context/ModManContext.cpp new file mode 100644 index 00000000..221ebbf6 --- /dev/null +++ b/src/ModMan/Context/ModManContext.cpp @@ -0,0 +1,18 @@ +#include "ModManContext.h" + +ModManContext& ModManContext::Get() +{ + static ModManContext context; + return context; +} + +void ModManContext::Startup() +{ + m_db_thread.Start(); +} + +void ModManContext::Destroy() +{ + m_fast_file.Destroy(); + m_db_thread.Terminate(); +} diff --git a/src/ModMan/Context/ModManContext.h b/src/ModMan/Context/ModManContext.h new file mode 100644 index 00000000..ec09e183 --- /dev/null +++ b/src/ModMan/Context/ModManContext.h @@ -0,0 +1,22 @@ +#pragma once + +#include "FastFileContext.h" +#include "Utils/DispatchableThread.h" +#include "Web/WebViewLib.h" + +#include + +class ModManContext +{ +public: + static ModManContext& Get(); + + void Startup(); + void Destroy(); + + std::unique_ptr m_main_webview; + std::unique_ptr m_dev_tools_webview; + FastFileContext m_fast_file; + + DispatchableThread m_db_thread; +}; diff --git a/src/ModMan/ModManArgs.cpp b/src/ModMan/ModManArgs.cpp new file mode 100644 index 00000000..b5b1f88c --- /dev/null +++ b/src/ModMan/ModManArgs.cpp @@ -0,0 +1,105 @@ +#include "ModManArgs.h" + +#include "GitVersion.h" +#include "Utils/Arguments/UsageInformation.h" +#include "Utils/Logging/Log.h" + +#include +#include + +namespace +{ + // clang-format off + const CommandLineOption* const OPTION_HELP = + CommandLineOption::Builder::Create() + .WithShortName("?") + .WithLongName("help") + .WithDescription("Displays usage information.") + .Build(); + + const CommandLineOption* const OPTION_VERSION = + CommandLineOption::Builder::Create() + .WithLongName("version") + .WithDescription("Prints the application version.") + .Build(); + + const CommandLineOption* const OPTION_VERBOSE = + CommandLineOption::Builder::Create() + .WithShortName("v") + .WithLongName("verbose") + .WithDescription("Outputs a lot more and more detailed messages.") + .Build(); + + const CommandLineOption* const OPTION_NO_COLOR = + CommandLineOption::Builder::Create() + .WithLongName("no-color") + .WithDescription("Disables colored terminal output.") + .Build(); + // clang-format on + + const CommandLineOption* const COMMAND_LINE_OPTIONS[]{ + OPTION_HELP, + OPTION_VERSION, + OPTION_VERBOSE, + OPTION_NO_COLOR, + }; +} // namespace + +ModManArgs::ModManArgs() + : m_argument_parser(COMMAND_LINE_OPTIONS, std::extent_v) +{ +} + +void ModManArgs::PrintUsage() const +{ + UsageInformation usage(m_argument_parser.GetExecutableName()); + + for (const auto* commandLineOption : COMMAND_LINE_OPTIONS) + { + usage.AddCommandLineOption(commandLineOption); + } + + usage.Print(); +} + +void ModManArgs::PrintVersion() +{ + con::info("OpenAssetTools ModMan {}", GIT_VERSION); +} + +bool ModManArgs::ParseArgs(const int argc, const char** argv, bool& shouldContinue) +{ + shouldContinue = true; + if (!m_argument_parser.ParseArguments(argc, argv)) + { + PrintUsage(); + return false; + } + + // Check if the user requested help + if (m_argument_parser.IsOptionSpecified(OPTION_HELP)) + { + PrintUsage(); + shouldContinue = false; + return true; + } + + // Check if the user wants to see the version + if (m_argument_parser.IsOptionSpecified(OPTION_VERSION)) + { + PrintVersion(); + shouldContinue = false; + return true; + } + + // -v; --verbose + if (m_argument_parser.IsOptionSpecified(OPTION_VERBOSE)) + con::globalLogLevel = con::LogLevel::DEBUG; + else + con::globalLogLevel = con::LogLevel::INFO; + + // --no-color + con::globalUseColor = !m_argument_parser.IsOptionSpecified(OPTION_NO_COLOR); + + return true; +} diff --git a/src/ModMan/ModManArgs.h b/src/ModMan/ModManArgs.h new file mode 100644 index 00000000..feb19628 --- /dev/null +++ b/src/ModMan/ModManArgs.h @@ -0,0 +1,19 @@ +#pragma once + +#include "Utils/Arguments/ArgumentParser.h" + +class ModManArgs +{ +public: + ModManArgs(); + bool ParseArgs(int argc, const char** argv, bool& shouldContinue); + +private: + /** + * \brief Prints a command line usage help text for the ModMan tool to stdout. + */ + void PrintUsage() const; + static void PrintVersion(); + + ArgumentParser m_argument_parser; +}; diff --git a/src/ModMan/Utils/DispatchableThread.cpp b/src/ModMan/Utils/DispatchableThread.cpp new file mode 100644 index 00000000..58797b5f --- /dev/null +++ b/src/ModMan/Utils/DispatchableThread.cpp @@ -0,0 +1,77 @@ +#include "DispatchableThread.h" + +DispatchableThread::DispatchableThread() + : m_terminate(false) +{ +} + +DispatchableThread::~DispatchableThread() +{ + Terminate(); +} + +void DispatchableThread::Start() +{ + m_terminate = false; + m_thread = std::thread( + [&] + { + ThreadLoop(); + }); +} + +void DispatchableThread::Terminate() +{ + std::unique_lock lock(m_cb_mutex); + + if (!m_terminate) + { + m_terminate = true; + m_cv.notify_all(); + lock.unlock(); + m_thread.join(); + } + else + { + lock.unlock(); + } +} + +void DispatchableThread::Dispatch(cb_t cb) +{ + std::lock_guard lock(m_cb_mutex); + + m_cb_list.emplace_back(std::move(cb)); + m_cv.notify_one(); +} + +std::optional DispatchableThread::NextCallback() +{ + if (m_terminate || m_cb_list.empty()) + return std::nullopt; + + auto cb = std::move(m_cb_list.front()); + m_cb_list.pop_front(); + + return cb; +} + +void DispatchableThread::ThreadLoop() +{ + while (!m_terminate) + { + std::unique_lock lock(m_cb_mutex); + m_cv.wait(lock, + [&] + { + return !m_cb_list.empty() || m_terminate; + }); + + auto maybeCb = NextCallback(); + + lock.unlock(); + + if (maybeCb) + (*maybeCb)(); + } +} diff --git a/src/ModMan/Utils/DispatchableThread.h b/src/ModMan/Utils/DispatchableThread.h new file mode 100644 index 00000000..383ef586 --- /dev/null +++ b/src/ModMan/Utils/DispatchableThread.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +class DispatchableThread +{ +public: + using cb_t = std::function; + + DispatchableThread(); + ~DispatchableThread(); + DispatchableThread(const DispatchableThread& other) = delete; + DispatchableThread(DispatchableThread&& other) noexcept = default; + DispatchableThread& operator=(const DispatchableThread& other) = delete; + DispatchableThread& operator=(DispatchableThread&& other) noexcept = default; + + void Start(); + void Terminate(); + + void Dispatch(cb_t cb); + +private: + std::optional NextCallback(); + void ThreadLoop(); + + std::mutex m_cb_mutex; + std::deque m_cb_list; + + std::condition_variable m_cv; + std::thread m_thread; + bool m_terminate; +}; diff --git a/src/ModMan/Web/Binds/Binds.cpp b/src/ModMan/Web/Binds/Binds.cpp new file mode 100644 index 00000000..7ae5ab9c --- /dev/null +++ b/src/ModMan/Web/Binds/Binds.cpp @@ -0,0 +1,15 @@ +#include "Binds.h" + +#include "UnlinkingBinds.h" +#include "Web/Binds/DialogBinds.h" +#include "ZoneBinds.h" + +namespace ui +{ + void RegisterAllBinds(webview::webview& wv) + { + RegisterDialogHandlerBinds(wv); + RegisterUnlinkingBinds(wv); + RegisterZoneBinds(wv); + } +} // namespace ui diff --git a/src/ModMan/Web/Binds/Binds.h b/src/ModMan/Web/Binds/Binds.h new file mode 100644 index 00000000..3dd717ba --- /dev/null +++ b/src/ModMan/Web/Binds/Binds.h @@ -0,0 +1,8 @@ +#pragma once + +#include "Web/WebViewLib.h" + +namespace ui +{ + void RegisterAllBinds(webview::webview& wv); +} diff --git a/src/ModMan/Web/Binds/UnlinkingBinds.cpp b/src/ModMan/Web/Binds/UnlinkingBinds.cpp new file mode 100644 index 00000000..bc281dcf --- /dev/null +++ b/src/ModMan/Web/Binds/UnlinkingBinds.cpp @@ -0,0 +1,93 @@ +#include "UnlinkingBinds.h" + +#include "Context/ModManContext.h" +#include "IObjWriter.h" +#include "SearchPath/OutputPathFilesystem.h" +#include "SearchPath/SearchPaths.h" +#include "Utils/PathUtils.h" +#include "Web/UiCommunication.h" + +#include "Json/JsonExtension.h" +#include + +namespace fs = std::filesystem; + +namespace +{ + class ZoneLoadedDto + { + public: + std::string zoneName; + std::string filePath; + }; + + NLOHMANN_DEFINE_TYPE_EXTENSION(ZoneLoadedDto, zoneName, filePath); + + class ZoneUnloadedDto + { + public: + std::string zoneName; + }; + + NLOHMANN_DEFINE_TYPE_EXTENSION(ZoneUnloadedDto, zoneName); + + result::Expected 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& zone) + { + return zone->m_name == zoneName; + }); + + if (existingZone == context.m_loaded_zones.end()) + return result::Unexpected(std::format("No zone with name {} loaded", zoneName)); + + const auto& zone = *existingZone->get(); + + const 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(); + + OutputPathFilesystem outputFolderOutputPath(outputFolderPath); + SearchPaths searchPaths; + AssetDumpingContext dumpingContext(zone, outputFolderPathStr, outputFolderOutputPath, searchPaths); + objWriter->DumpZone(dumpingContext); + + return NoResult(); + } + + void UnlinkZone(webview::webview& wv, std::string id, std::string zoneName) // NOLINT(performance-unnecessary-value-param) Copy is made for thread safety + { + ModManContext::Get().m_db_thread.Dispatch( + [&wv, id, zoneName] + { + auto result = UnlinkZoneInDbThread(zoneName); + + if (result) + { + con::debug("Unlinked zone \"{}\"", zoneName); + ui::PromiseResolve(wv, id, true); + } + else + { + con::warn("Failed to unlink zone \"{}\": {}", zoneName, result.error()); + ui::PromiseReject(wv, id, std::move(result).error()); + } + }); + } +} // namespace + +namespace ui +{ + void RegisterUnlinkingBinds(webview::webview& wv) + { + BindAsync(wv, + "unlinkZone", + [&wv](const std::string& id, std::string zoneName) + { + UnlinkZone(wv, id, std::move(zoneName)); + }); + } +} // namespace ui diff --git a/src/ModMan/Web/Binds/UnlinkingBinds.h b/src/ModMan/Web/Binds/UnlinkingBinds.h new file mode 100644 index 00000000..4169a780 --- /dev/null +++ b/src/ModMan/Web/Binds/UnlinkingBinds.h @@ -0,0 +1,8 @@ +#pragma once + +#include "Web/WebViewLib.h" + +namespace ui +{ + void RegisterUnlinkingBinds(webview::webview& wv); +} // namespace ui diff --git a/src/ModMan/Web/Binds/ZoneBinds.cpp b/src/ModMan/Web/Binds/ZoneBinds.cpp new file mode 100644 index 00000000..8882a7d0 --- /dev/null +++ b/src/ModMan/Web/Binds/ZoneBinds.cpp @@ -0,0 +1,107 @@ +#include "ZoneBinds.h" + +#include "Context/ModManContext.h" +#include "Web/UiCommunication.h" + +#include "Json/JsonExtension.h" + +namespace +{ + class ZoneLoadedDto + { + public: + std::string zoneName; + std::string filePath; + }; + + NLOHMANN_DEFINE_TYPE_EXTENSION(ZoneLoadedDto, zoneName, filePath); + + class ZoneUnloadedDto + { + public: + std::string zoneName; + }; + + NLOHMANN_DEFINE_TYPE_EXTENSION(ZoneUnloadedDto, zoneName); + + void LoadFastFile(webview::webview& wv, std::string id, std::string path) // NOLINT(performance-unnecessary-value-param) Copy is made for thread safety + { + ModManContext::Get().m_db_thread.Dispatch( + [&wv, id, path] + { + auto maybeZone = ModManContext::Get().m_fast_file.LoadFastFile(path); + + if (maybeZone) + { + ui::PromiseResolve(wv, + id, + ZoneLoadedDto{ + .zoneName = maybeZone.value()->m_name, + .filePath = path, + }); + con::debug("Loaded zone \"{}\"", maybeZone.value()->m_name); + } + else + { + con::warn("Failed to load zone \"{}\": {}", path, maybeZone.error()); + ui::PromiseReject(wv, id, std::move(maybeZone).error()); + } + }); + } + + void UnloadZone(webview::webview& wv, std::string id, std::string zoneName) // NOLINT(performance-unnecessary-value-param) Copy is made for thread safety + { + ModManContext::Get().m_db_thread.Dispatch( + [&wv, id, zoneName] + { + auto result = ModManContext::Get().m_fast_file.UnloadZone(zoneName); + if (result) + { + con::debug("Unloaded zone \"{}\"", zoneName); + ui::PromiseResolve(wv, id, true); + } + else + { + con::warn("Failed unloading zone {}: {}", zoneName, result.error()); + ui::PromiseReject(wv, id, std::move(result).error()); + } + }); + } +} // namespace + +namespace ui +{ + void NotifyZoneLoaded(std::string zoneName, std::string fastFilePath) + { + const ZoneLoadedDto dto{ + .zoneName = std::move(zoneName), + .filePath = std::move(fastFilePath), + }; + Notify(*ModManContext::Get().m_main_webview, "zoneLoaded", dto); + } + + void NotifyZoneUnloaded(std::string zoneName) + { + const ZoneUnloadedDto dto{ + .zoneName = std::move(zoneName), + }; + Notify(*ModManContext::Get().m_main_webview, "zoneUnloaded", dto); + } + + void RegisterZoneBinds(webview::webview& wv) + { + BindAsync(wv, + "loadFastFile", + [&wv](const std::string& id, std::string path) + { + LoadFastFile(wv, id, std::move(path)); + }); + + BindAsync(wv, + "unloadZone", + [&wv](const std::string& id, std::string zoneName) + { + UnloadZone(wv, id, std::move(zoneName)); + }); + } +} // namespace ui diff --git a/src/ModMan/Web/Binds/ZoneBinds.h b/src/ModMan/Web/Binds/ZoneBinds.h new file mode 100644 index 00000000..cf11f10d --- /dev/null +++ b/src/ModMan/Web/Binds/ZoneBinds.h @@ -0,0 +1,11 @@ +#pragma once + +#include "Web/WebViewLib.h" + +namespace ui +{ + void NotifyZoneLoaded(std::string zoneName, std::string fastFilePath); + void NotifyZoneUnloaded(std::string zoneName); + + void RegisterZoneBinds(webview::webview& wv); +} // namespace ui diff --git a/src/ModMan/Web/WebViewLib.h b/src/ModMan/Web/WebViewLib.h index 5a8fd0b0..f120396d 100644 --- a/src/ModMan/Web/WebViewLib.h +++ b/src/ModMan/Web/WebViewLib.h @@ -1,5 +1,7 @@ #pragma once +#define NOMINMAX + #ifdef _MSC_VER #pragma warning(push, 0) #else diff --git a/src/ModMan/main.cpp b/src/ModMan/main.cpp index a6afc7bb..cd0e838d 100644 --- a/src/ModMan/main.cpp +++ b/src/ModMan/main.cpp @@ -1,32 +1,38 @@ -#include "GitVersion.h" -#include "Web/Binds/DialogBinds.h" +#include "Context/ModManContext.h" +#include "GitVersion.h" +#include "ModManArgs.h" +#include "Web/Binds/Binds.h" #include "Web/Platform/AssetHandler.h" #include "Web/UiCommunication.h" #include "Web/ViteAssets.h" #include "Web/WebViewLib.h" -#include #include #include -#include #include #include +#ifdef _WIN32 +#include +#endif + using namespace std::string_literals; using namespace PLATFORM_NAMESPACE; namespace { #ifdef _DEBUG - std::optional devToolWindow; - - void RunDevToolsWindow() + void SpawnDevToolsWindow() { con::debug("Creating dev tools window"); + auto& context = ModManContext::Get(); + try { - auto& newWindow = devToolWindow.emplace(false, nullptr); + context.m_dev_tools_webview = std::make_unique(false, nullptr); + auto& newWindow = *context.m_dev_tools_webview; + newWindow.set_title("Devtools"); newWindow.set_size(640, 480, WEBVIEW_HINT_NONE); newWindow.set_size(480, 320, WEBVIEW_HINT_MIN); @@ -39,59 +45,44 @@ namespace } #endif - int RunMainWindow() + int SpawnMainWindow() { con::debug("Creating main window"); + auto& context = ModManContext::Get(); try { - webview::webview w( + context.m_main_webview = std::make_unique( #ifdef _DEBUG true, #else false, #endif nullptr); - w.set_title("OpenAssetTools ModMan"); - w.set_size(1280, 640, WEBVIEW_HINT_NONE); - w.set_size(480, 320, WEBVIEW_HINT_MIN); + auto& newWindow = *context.m_main_webview; - ui::RegisterDialogHandlerBinds(w); + newWindow.set_title("OpenAssetTools ModMan"); + newWindow.set_size(1280, 640, WEBVIEW_HINT_NONE); + newWindow.set_size(480, 320, WEBVIEW_HINT_MIN); - // A binding that counts up or down and immediately returns the new value. - ui::Bind(w, - "greet", - [&w](std::string name) -> std::string - { - ui::Notify(w, "greeting", name); - return std::format("Hello from C++ {}!", name); - }); - -#if defined(WEBVIEW_PLATFORM_WINDOWS) && defined(WEBVIEW_EDGE) - InstallAssetHandler(w); - constexpr auto urlPrefix = URL_PREFIX; -#elif defined(WEBVIEW_PLATFORM_LINUX) && defined(WEBVIEW_GTK) - InstallAssetHandler(w); - constexpr auto urlPrefix = URL_PREFIX; -#else -#error Unsupported platform -#endif + InstallAssetHandler(newWindow); + ui::RegisterAllBinds(newWindow); #ifdef _DEBUG - w.navigate(VITE_DEV_SERVER ? std::format("http://localhost:{}", VITE_DEV_SERVER_PORT) : std::format("{}index.html", urlPrefix)); + newWindow.navigate(VITE_DEV_SERVER ? std::format("http://localhost:{}", VITE_DEV_SERVER_PORT) : std::format("{}index.html", URL_PREFIX)); if (VITE_DEV_SERVER) { - w.dispatch( + newWindow.dispatch( [] { - RunDevToolsWindow(); + SpawnDevToolsWindow(); }); } #else - w.navigate(std::format("{}index.html", urlPrefix)); + newWindow.navigate(std::format("{}index.html", URL_PREFIX)); #endif - w.run(); + newWindow.run(); } catch (const webview::exception& e) { @@ -104,16 +95,50 @@ namespace } // namespace #ifdef _WIN32 +#define MODMAN_ARGC __argc +#define MODMAN_ARGV const_cast(__argv) int WINAPI WinMain(HINSTANCE /*hInst*/, HINSTANCE /*hPrevInst*/, LPSTR /*lpCmdLine*/, int /*nCmdShow*/) -{ #else -int main() -{ +#define MODMAN_ARGC argc +#define MODMAN_ARGV argv +int main(int argc, const char** argv) #endif +{ +#ifdef _WIN32 + // Attach console if possible on Windows for stdout/stderr in console + if (AttachConsole(-1)) + { + FILE* fDummy; + (void)freopen_s(&fDummy, "CONOUT$", "w", stdout); + (void)freopen_s(&fDummy, "CONOUT$", "w", stderr); + (void)freopen_s(&fDummy, "CONIN$", "r", stdin); + std::cout.clear(); + std::clog.clear(); + std::cerr.clear(); + std::cin.clear(); + } +#endif + +#ifdef __linux__ + g_set_prgname("OpenAssetTools-ModMan"); + g_set_application_name("OpenAssetTools ModMan"); +#endif + + ModManArgs args; + auto shouldContinue = true; + if (!args.ParseArgs(MODMAN_ARGC, MODMAN_ARGV, shouldContinue)) + return false; + + if (!shouldContinue) + return true; con::info("Starting ModMan " GIT_VERSION); - const auto result = RunMainWindow(); + ModManContext::Get().Startup(); + + const auto result = SpawnMainWindow(); + + ModManContext::Get().Destroy(); return result; } diff --git a/src/ModManUi/src/App.vue b/src/ModManUi/src/App.vue index 1ff66ed6..8c4a4dc1 100644 --- a/src/ModManUi/src/App.vue +++ b/src/ModManUi/src/App.vue @@ -1,28 +1,72 @@ diff --git a/src/ModManUi/src/components/SpinningLoader.vue b/src/ModManUi/src/components/SpinningLoader.vue new file mode 100644 index 00000000..4161cd84 --- /dev/null +++ b/src/ModManUi/src/components/SpinningLoader.vue @@ -0,0 +1,30 @@ + + + diff --git a/src/ModManUi/src/main.scss b/src/ModManUi/src/main.scss index 20d4d9c6..37c9c0b0 100644 --- a/src/ModManUi/src/main.scss +++ b/src/ModManUi/src/main.scss @@ -71,13 +71,17 @@ button { cursor: pointer; } -button:hover { +button:not(:disabled):hover { border-color: #396cd8; } -button:active { +button:not(:disabled):active { border-color: #396cd8; background-color: #e8e8e8; } +button:disabled { + opacity: 0.6; + cursor: not-allowed; +} input, button { @@ -103,7 +107,7 @@ button { color: #ffffff; background-color: #0f0f0f98; } - button:active { + button:not(:disabled):active { background-color: #0f0f0f69; } } \ No newline at end of file diff --git a/src/ModManUi/src/native/DialogBinds.ts b/src/ModManUi/src/native/DialogBinds.ts index 8c7e66b7..97ddce0b 100644 --- a/src/ModManUi/src/native/DialogBinds.ts +++ b/src/ModManUi/src/native/DialogBinds.ts @@ -12,7 +12,7 @@ export interface SaveFileDialogDto { } export interface DialogBinds { - openFileDialog(options?: OpenFileDialogDto): string | null; - saveFileDialog(options?: SaveFileDialogDto): string | null; - folderSelectDialog(): string | null; + openFileDialog(options?: OpenFileDialogDto): Promise; + saveFileDialog(options?: SaveFileDialogDto): Promise; + folderSelectDialog(): Promise; } diff --git a/src/ModManUi/src/native/UnlinkingBinds.ts b/src/ModManUi/src/native/UnlinkingBinds.ts new file mode 100644 index 00000000..8fe73c71 --- /dev/null +++ b/src/ModManUi/src/native/UnlinkingBinds.ts @@ -0,0 +1,3 @@ +export interface UnlinkingBinds { + unlinkZone(zoneName: string): Promise; +} diff --git a/src/ModManUi/src/native/ZoneBinds.ts b/src/ModManUi/src/native/ZoneBinds.ts new file mode 100644 index 00000000..e0636061 --- /dev/null +++ b/src/ModManUi/src/native/ZoneBinds.ts @@ -0,0 +1,18 @@ +export interface ZoneLoadedDto { + zoneName: string; + filePath: string; +} + +export interface ZoneUnloadedDto { + zoneName: string; +} + +export interface ZoneBinds { + loadFastFile(path: string): Promise; + unloadZone(zoneName: string): Promise; +} + +export interface ZoneEventMap { + zoneLoaded: ZoneLoadedDto; + zoneUnloaded: ZoneUnloadedDto; +} diff --git a/src/ModManUi/src/native/index.ts b/src/ModManUi/src/native/index.ts index ae61f41f..dc455af9 100644 --- a/src/ModManUi/src/native/index.ts +++ b/src/ModManUi/src/native/index.ts @@ -1,13 +1,10 @@ import type { DialogBinds } from "./DialogBinds"; +import type { UnlinkingBinds } from "./UnlinkingBinds"; +import type { ZoneBinds, ZoneEventMap } from "./ZoneBinds"; +export type NativeMethods = DialogBinds & UnlinkingBinds & ZoneBinds; -export type NativeMethods = { - greet(name: string): Promise; -} & DialogBinds; - -interface NativeEventMap { - greeting: string; -} +type NativeEventMap = ZoneEventMap; type WebViewExtensions = { webviewBinds: NativeMethods; diff --git a/src/ModManUi/src/stores/ZoneStore.ts b/src/ModManUi/src/stores/ZoneStore.ts new file mode 100644 index 00000000..3877ab87 --- /dev/null +++ b/src/ModManUi/src/stores/ZoneStore.ts @@ -0,0 +1,20 @@ +import { readonly, ref } from "vue"; +import { defineStore } from "pinia"; +import { webviewAddEventListener } from "@/native"; + +export const useZoneStore = defineStore("zone", () => { + const loadedZones = ref([]); + + webviewAddEventListener("zoneLoaded", (dto) => { + loadedZones.value.push(dto.zoneName); + }); + + webviewAddEventListener("zoneUnloaded", (dto) => { + const index = loadedZones.value.indexOf(dto.zoneName); + if (index >= 0) { + loadedZones.value.splice(index, 1); + } + }); + + return { loadedZones: readonly(loadedZones) }; +}); diff --git a/src/ModManUi/src/stores/counter.ts b/src/ModManUi/src/stores/counter.ts deleted file mode 100644 index 374b4d03..00000000 --- a/src/ModManUi/src/stores/counter.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ref, computed } from "vue"; -import { defineStore } from "pinia"; - -export const useCounterStore = defineStore("counter", () => { - const count = ref(0); - const doubleCount = computed(() => count.value * 2); - function increment() { - count.value++; - } - - return { count, doubleCount, increment }; -}); diff --git a/src/Unlinker/Unlinker.cpp b/src/Unlinker/Unlinker.cpp index 977c1ad3..1503d215 100644 --- a/src/Unlinker/Unlinker.cpp +++ b/src/Unlinker/Unlinker.cpp @@ -1,19 +1,17 @@ #include "Unlinker.h" #include "ContentLister/ContentPrinter.h" -#include "ContentLister/ZoneDefWriter.h" #include "IObjLoader.h" #include "IObjWriter.h" #include "ObjWriting.h" #include "SearchPath/IWD.h" #include "SearchPath/OutputPathFilesystem.h" -#include "SearchPath/SearchPathFilesystem.h" -#include "SearchPath/SearchPaths.h" #include "UnlinkerArgs.h" #include "UnlinkerPaths.h" #include "Utils/ClassUtils.h" #include "Utils/Logging/Log.h" #include "Utils/ObjFileStream.h" +#include "Zone/Definition/ZoneDefWriter.h" #include "ZoneLoading.h" #include @@ -21,7 +19,6 @@ #include #include #include -#include namespace fs = std::filesystem; @@ -54,12 +51,12 @@ public: } private: - _NODISCARD bool ShouldLoadObj() const + [[nodiscard]] bool ShouldLoadObj() const { return m_args.m_task != UnlinkerArgs::ProcessingTask::LIST && !m_args.m_skip_obj; } - bool WriteZoneDefinitionFile(const Zone& zone, const fs::path& zoneDefinitionFileFolder) const + [[nodiscard]] bool WriteZoneDefinitionFile(const Zone& zone, const fs::path& zoneDefinitionFileFolder) const { auto zoneDefinitionFilePath(zoneDefinitionFileFolder); zoneDefinitionFilePath.append(zone.m_name); @@ -73,7 +70,7 @@ private: } const auto* zoneDefWriter = IZoneDefWriter::GetZoneDefWriterForGame(zone.m_game_id); - zoneDefWriter->WriteZoneDef(zoneDefinitionFile, m_args, zone); + zoneDefWriter->WriteZoneDef(zoneDefinitionFile, zone, m_args.m_use_gdt); zoneDefinitionFile.close(); @@ -230,13 +227,15 @@ private: auto absoluteZoneDirectory = absolute(std::filesystem::path(zonePath).remove_filename()).string(); auto searchPathsForZone = paths.GetSearchPathsForZone(absoluteZoneDirectory); - auto zone = ZoneLoading::LoadZone(zonePath); - if (zone == nullptr) + auto maybeZone = ZoneLoading::LoadZone(zonePath); + if (!maybeZone) { - con::error("Failed to load zone \"{}\".", zonePath); + con::error("Failed to load zone \"{}\": {}", zonePath, maybeZone.error()); return false; } + auto zone = std::move(*maybeZone); + con::debug("Loaded zone \"{}\"", zone->m_name); if (ShouldLoadObj()) @@ -290,16 +289,16 @@ private: auto searchPathsForZone = paths.GetSearchPathsForZone(absoluteZoneDirectory); - std::string zoneName; - auto zone = ZoneLoading::LoadZone(zonePath); - if (zone == nullptr) + auto maybeZone = ZoneLoading::LoadZone(zonePath); + if (!maybeZone) { - con::error("Failed to load zone \"{}\".", zonePath); + con::error("Failed to load zone \"{}\": {}", zonePath, maybeZone.error()); return false; } - zoneName = zone->m_name; - con::debug("Loaded zone \"{}\"", zoneName); + auto zone = std::move(*maybeZone); + + con::debug("Loaded zone \"{}\"", zone->m_name); const auto* objLoader = IObjLoader::GetObjLoaderForGame(zone->m_game_id); if (ShouldLoadObj()) @@ -311,6 +310,8 @@ private: if (ShouldLoadObj()) objLoader->UnloadContainersOfZone(*zone); + // Copy zone name for using it after freeing the zone + std::string zoneName = zone->m_name; zone.reset(); con::debug("Unloaded zone \"{}\"", zoneName); } diff --git a/src/Utils/Utils/Result.h b/src/Utils/Utils/Result.h new file mode 100644 index 00000000..b5d870c0 --- /dev/null +++ b/src/Utils/Utils/Result.h @@ -0,0 +1,185 @@ +#pragma once + +#include +#include + +using NoResult = std::monostate; + +// Can be replaced by std::expected with c++23 +namespace result +{ + template class Unexpected + { + public: + Unexpected(TError result) + : m_data(std::move(result)) + { + } + + constexpr std::add_lvalue_reference_t value() & + { + return m_data; + } + + constexpr std::add_const_t> value() const& + { + return m_data; + } + + constexpr std::add_rvalue_reference_t value() && + { + return std::move(m_data); + } + + constexpr std::add_const_t> value() const&& + { + return std::move(m_data); + } + + constexpr std::add_lvalue_reference_t operator*() & + { + return m_data; + } + + constexpr std::add_const_t> operator*() const& + { + return m_data; + } + + constexpr std::add_rvalue_reference_t operator*() && + { + return std::move(m_data); + } + + constexpr std::add_const_t> operator*() const&& + { + return std::move(m_data); + } + + constexpr std::add_pointer_t operator->() + { + return m_data; + } + + constexpr std::add_const_t> operator->() const + { + return m_data; + } + + private: + TError m_data; + }; + + template class Expected + { + public: + Expected(TResult result) + : m_data(std::variant(std::in_place_index<0>, std::move(result))) + { + } + + Expected(Unexpected unexpected) + : m_data(std::variant(std::in_place_index<1>, std::move(*unexpected))) + { + } + + constexpr operator bool() const noexcept + { + return m_data.index() == 0; + } + + constexpr bool has_value() const noexcept + { + return m_data.index() == 0; + } + + constexpr std::add_lvalue_reference_t value() & + { + return std::get<0>(m_data); + } + + constexpr std::add_const_t> value() const& + { + return std::get<0>(m_data); + } + + constexpr std::add_rvalue_reference_t value() && + { + return std::move(std::get<0>(m_data)); + } + + constexpr std::add_const_t> value() const&& + { + return std::move(std::get<0>(m_data)); + } + + constexpr std::add_lvalue_reference_t operator*() & + { + return std::get<0>(m_data); + } + + constexpr std::add_const_t> operator*() const& + { + return std::get<0>(m_data); + } + + constexpr std::add_rvalue_reference_t operator*() && + { + return std::move(std::get<0>(m_data)); + } + + constexpr std::add_const_t> operator*() const&& + { + return std::move(std::get<0>(m_data)); + } + + constexpr std::add_pointer_t operator->() + { + return std::get<0>(m_data); + } + + constexpr std::add_const_t> operator->() const + { + return std::get<0>(m_data); + } + + constexpr std::add_lvalue_reference_t error() & + { + return std::get<1>(m_data); + } + + constexpr std::add_const_t> error() const& + { + return std::get<1>(m_data); + } + + constexpr std::add_rvalue_reference_t error() && + { + return std::move(std::get<1>(m_data)); + } + + constexpr std::add_const_t> error() const&& + { + return std::move(std::get<1>(m_data)); + } + + private: + explicit Expected(std::variant data) + : m_data(std::move(data)) + { + } + + std::variant m_data; + }; + +#define ENSURE_RESULT_VAR(var) \ + if (!(var)) \ + return (var); +#define ENSURE_RESULT(expr) \ + { \ + const auto result = (expr); \ + if (!result) \ + return result; \ + } + +} // namespace result diff --git a/src/Unlinker/Game/IW3/ZoneDefWriterIW3.cpp b/src/ZoneCommon/Game/IW3/Zone/Definition/ZoneDefWriterIW3.cpp similarity index 88% rename from src/Unlinker/Game/IW3/ZoneDefWriterIW3.cpp rename to src/ZoneCommon/Game/IW3/Zone/Definition/ZoneDefWriterIW3.cpp index 3850cba7..a98caa82 100644 --- a/src/Unlinker/Game/IW3/ZoneDefWriterIW3.cpp +++ b/src/ZoneCommon/Game/IW3/Zone/Definition/ZoneDefWriterIW3.cpp @@ -6,9 +6,9 @@ using namespace IW3; -void ZoneDefWriter::WriteMetaData(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const {} +void ZoneDefWriter::WriteMetaData(ZoneDefinitionOutputStream& stream, const Zone& zone) const {} -void ZoneDefWriter::WriteContent(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const +void ZoneDefWriter::WriteContent(ZoneDefinitionOutputStream& stream, const Zone& zone) const { const auto* pools = dynamic_cast(zone.m_pools.get()); diff --git a/src/Unlinker/Game/IW3/ZoneDefWriterIW3.h b/src/ZoneCommon/Game/IW3/Zone/Definition/ZoneDefWriterIW3.h similarity index 62% rename from src/Unlinker/Game/IW3/ZoneDefWriterIW3.h rename to src/ZoneCommon/Game/IW3/Zone/Definition/ZoneDefWriterIW3.h index 3f378d9d..8bfe8f61 100644 --- a/src/Unlinker/Game/IW3/ZoneDefWriterIW3.h +++ b/src/ZoneCommon/Game/IW3/Zone/Definition/ZoneDefWriterIW3.h @@ -1,13 +1,13 @@ #pragma once -#include "ContentLister/ZoneDefWriter.h" +#include "Zone/Definition/ZoneDefWriter.h" namespace IW3 { class ZoneDefWriter final : public AbstractZoneDefWriter { protected: - void WriteMetaData(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const override; - void WriteContent(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const override; + void WriteMetaData(ZoneDefinitionOutputStream& stream, const Zone& zone) const override; + void WriteContent(ZoneDefinitionOutputStream& stream, const Zone& zone) const override; }; } // namespace IW3 diff --git a/src/Unlinker/Game/IW4/ZoneDefWriterIW4.cpp b/src/ZoneCommon/Game/IW4/Zone/Definition/ZoneDefWriterIW4.cpp similarity index 88% rename from src/Unlinker/Game/IW4/ZoneDefWriterIW4.cpp rename to src/ZoneCommon/Game/IW4/Zone/Definition/ZoneDefWriterIW4.cpp index 7e100945..6bd9cc93 100644 --- a/src/Unlinker/Game/IW4/ZoneDefWriterIW4.cpp +++ b/src/ZoneCommon/Game/IW4/Zone/Definition/ZoneDefWriterIW4.cpp @@ -6,9 +6,9 @@ using namespace IW4; -void ZoneDefWriter::WriteMetaData(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const {} +void ZoneDefWriter::WriteMetaData(ZoneDefinitionOutputStream& stream, const Zone& zone) const {} -void ZoneDefWriter::WriteContent(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const +void ZoneDefWriter::WriteContent(ZoneDefinitionOutputStream& stream, const Zone& zone) const { const auto* pools = dynamic_cast(zone.m_pools.get()); diff --git a/src/Unlinker/Game/IW4/ZoneDefWriterIW4.h b/src/ZoneCommon/Game/IW4/Zone/Definition/ZoneDefWriterIW4.h similarity index 62% rename from src/Unlinker/Game/IW4/ZoneDefWriterIW4.h rename to src/ZoneCommon/Game/IW4/Zone/Definition/ZoneDefWriterIW4.h index 83bf1642..e2f3bedc 100644 --- a/src/Unlinker/Game/IW4/ZoneDefWriterIW4.h +++ b/src/ZoneCommon/Game/IW4/Zone/Definition/ZoneDefWriterIW4.h @@ -1,13 +1,13 @@ #pragma once -#include "ContentLister/ZoneDefWriter.h" +#include "Zone/Definition/ZoneDefWriter.h" namespace IW4 { class ZoneDefWriter final : public AbstractZoneDefWriter { protected: - void WriteMetaData(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const override; - void WriteContent(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const override; + void WriteMetaData(ZoneDefinitionOutputStream& stream, const Zone& zone) const override; + void WriteContent(ZoneDefinitionOutputStream& stream, const Zone& zone) const override; }; } // namespace IW4 diff --git a/src/Unlinker/Game/IW5/ZoneDefWriterIW5.cpp b/src/ZoneCommon/Game/IW5/Zone/Definition/ZoneDefWriterIW5.cpp similarity index 88% rename from src/Unlinker/Game/IW5/ZoneDefWriterIW5.cpp rename to src/ZoneCommon/Game/IW5/Zone/Definition/ZoneDefWriterIW5.cpp index ff54d622..4678dc85 100644 --- a/src/Unlinker/Game/IW5/ZoneDefWriterIW5.cpp +++ b/src/ZoneCommon/Game/IW5/Zone/Definition/ZoneDefWriterIW5.cpp @@ -6,9 +6,9 @@ using namespace IW5; -void ZoneDefWriter::WriteMetaData(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const {} +void ZoneDefWriter::WriteMetaData(ZoneDefinitionOutputStream& stream, const Zone& zone) const {} -void ZoneDefWriter::WriteContent(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const +void ZoneDefWriter::WriteContent(ZoneDefinitionOutputStream& stream, const Zone& zone) const { const auto* pools = dynamic_cast(zone.m_pools.get()); diff --git a/src/Unlinker/Game/IW5/ZoneDefWriterIW5.h b/src/ZoneCommon/Game/IW5/Zone/Definition/ZoneDefWriterIW5.h similarity index 62% rename from src/Unlinker/Game/IW5/ZoneDefWriterIW5.h rename to src/ZoneCommon/Game/IW5/Zone/Definition/ZoneDefWriterIW5.h index fac80d00..29f75f75 100644 --- a/src/Unlinker/Game/IW5/ZoneDefWriterIW5.h +++ b/src/ZoneCommon/Game/IW5/Zone/Definition/ZoneDefWriterIW5.h @@ -1,13 +1,13 @@ #pragma once -#include "ContentLister/ZoneDefWriter.h" +#include "Zone/Definition/ZoneDefWriter.h" namespace IW5 { class ZoneDefWriter final : public AbstractZoneDefWriter { protected: - void WriteMetaData(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const override; - void WriteContent(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const override; + void WriteMetaData(ZoneDefinitionOutputStream& stream, const Zone& zone) const override; + void WriteContent(ZoneDefinitionOutputStream& stream, const Zone& zone) const override; }; } // namespace IW5 diff --git a/src/Unlinker/Game/T5/ZoneDefWriterT5.cpp b/src/ZoneCommon/Game/T5/Zone/Definition/ZoneDefWriterT5.cpp similarity index 88% rename from src/Unlinker/Game/T5/ZoneDefWriterT5.cpp rename to src/ZoneCommon/Game/T5/Zone/Definition/ZoneDefWriterT5.cpp index 5fa7774a..976f8798 100644 --- a/src/Unlinker/Game/T5/ZoneDefWriterT5.cpp +++ b/src/ZoneCommon/Game/T5/Zone/Definition/ZoneDefWriterT5.cpp @@ -6,9 +6,9 @@ using namespace T5; -void ZoneDefWriter::WriteMetaData(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const {} +void ZoneDefWriter::WriteMetaData(ZoneDefinitionOutputStream& stream, const Zone& zone) const {} -void ZoneDefWriter::WriteContent(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const +void ZoneDefWriter::WriteContent(ZoneDefinitionOutputStream& stream, const Zone& zone) const { const auto* pools = dynamic_cast(zone.m_pools.get()); diff --git a/src/Unlinker/Game/T5/ZoneDefWriterT5.h b/src/ZoneCommon/Game/T5/Zone/Definition/ZoneDefWriterT5.h similarity index 62% rename from src/Unlinker/Game/T5/ZoneDefWriterT5.h rename to src/ZoneCommon/Game/T5/Zone/Definition/ZoneDefWriterT5.h index 8b206436..7dfb85b4 100644 --- a/src/Unlinker/Game/T5/ZoneDefWriterT5.h +++ b/src/ZoneCommon/Game/T5/Zone/Definition/ZoneDefWriterT5.h @@ -1,13 +1,13 @@ #pragma once -#include "ContentLister/ZoneDefWriter.h" +#include "Zone/Definition/ZoneDefWriter.h" namespace T5 { class ZoneDefWriter final : public AbstractZoneDefWriter { protected: - void WriteMetaData(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const override; - void WriteContent(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const override; + void WriteMetaData(ZoneDefinitionOutputStream& stream, const Zone& zone) const override; + void WriteContent(ZoneDefinitionOutputStream& stream, const Zone& zone) const override; }; } // namespace T5 diff --git a/src/Unlinker/Game/T6/ZoneDefWriterT6.cpp b/src/ZoneCommon/Game/T6/Zone/Definition/ZoneDefWriterT6.cpp similarity index 96% rename from src/Unlinker/Game/T6/ZoneDefWriterT6.cpp rename to src/ZoneCommon/Game/T6/Zone/Definition/ZoneDefWriterT6.cpp index 5b50ed8d..4b59ebcc 100644 --- a/src/Unlinker/Game/T6/ZoneDefWriterT6.cpp +++ b/src/ZoneCommon/Game/T6/Zone/Definition/ZoneDefWriterT6.cpp @@ -48,7 +48,7 @@ namespace } } // namespace -void ZoneDefWriter::WriteMetaData(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const +void ZoneDefWriter::WriteMetaData(ZoneDefinitionOutputStream& stream, const Zone& zone) const { const auto* assetPoolT6 = dynamic_cast(zone.m_pools.get()); if (assetPoolT6 && !assetPoolT6->m_key_value_pairs->m_asset_lookup.empty()) @@ -64,7 +64,7 @@ void ZoneDefWriter::WriteMetaData(ZoneDefinitionOutputStream& stream, const Unli } } -void ZoneDefWriter::WriteContent(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const +void ZoneDefWriter::WriteContent(ZoneDefinitionOutputStream& stream, const Zone& zone) const { const auto* pools = dynamic_cast(zone.m_pools.get()); diff --git a/src/Unlinker/Game/T6/ZoneDefWriterT6.h b/src/ZoneCommon/Game/T6/Zone/Definition/ZoneDefWriterT6.h similarity index 62% rename from src/Unlinker/Game/T6/ZoneDefWriterT6.h rename to src/ZoneCommon/Game/T6/Zone/Definition/ZoneDefWriterT6.h index 5af9672c..842a7dd9 100644 --- a/src/Unlinker/Game/T6/ZoneDefWriterT6.h +++ b/src/ZoneCommon/Game/T6/Zone/Definition/ZoneDefWriterT6.h @@ -1,13 +1,13 @@ #pragma once -#include "ContentLister/ZoneDefWriter.h" +#include "Zone/Definition/ZoneDefWriter.h" namespace T6 { class ZoneDefWriter final : public AbstractZoneDefWriter { protected: - void WriteMetaData(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const override; - void WriteContent(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const override; + void WriteMetaData(ZoneDefinitionOutputStream& stream, const Zone& zone) const override; + void WriteContent(ZoneDefinitionOutputStream& stream, const Zone& zone) const override; }; } // namespace T6 diff --git a/src/Unlinker/ContentLister/ZoneDefWriter.cpp b/src/ZoneCommon/Zone/Definition/ZoneDefWriter.cpp similarity index 72% rename from src/Unlinker/ContentLister/ZoneDefWriter.cpp rename to src/ZoneCommon/Zone/Definition/ZoneDefWriter.cpp index 367199e5..2c032afe 100644 --- a/src/Unlinker/ContentLister/ZoneDefWriter.cpp +++ b/src/ZoneCommon/Zone/Definition/ZoneDefWriter.cpp @@ -1,10 +1,10 @@ #include "ZoneDefWriter.h" -#include "Game/IW3/ZoneDefWriterIW3.h" -#include "Game/IW4/ZoneDefWriterIW4.h" -#include "Game/IW5/ZoneDefWriterIW5.h" -#include "Game/T5/ZoneDefWriterT5.h" -#include "Game/T6/ZoneDefWriterT6.h" +#include "Game/IW3/Zone/Definition/ZoneDefWriterIW3.h" +#include "Game/IW4/Zone/Definition/ZoneDefWriterIW4.h" +#include "Game/IW5/Zone/Definition/ZoneDefWriterIW5.h" +#include "Game/T5/Zone/Definition/ZoneDefWriterT5.h" +#include "Game/T6/Zone/Definition/ZoneDefWriterT6.h" #include @@ -25,7 +25,7 @@ const IZoneDefWriter* IZoneDefWriter::GetZoneDefWriterForGame(GameId game) return result; } -void AbstractZoneDefWriter::WriteZoneDef(std::ostream& stream, const UnlinkerArgs& args, const Zone& zone) const +void AbstractZoneDefWriter::WriteZoneDef(std::ostream& stream, const Zone& zone, const bool useGdt) const { ZoneDefinitionOutputStream out(stream); const auto* game = IGame::GetGameById(zone.m_game_id); @@ -34,13 +34,13 @@ void AbstractZoneDefWriter::WriteZoneDef(std::ostream& stream, const UnlinkerArg out.WriteMetaData(META_DATA_KEY_GAME, game->GetShortName()); out.EmptyLine(); - if (args.m_use_gdt) + if (useGdt) { out.WriteComment("Load asset gdt files"); out.WriteMetaData(META_DATA_KEY_GDT, zone.m_name); out.EmptyLine(); } - WriteMetaData(out, args, zone); - WriteContent(out, args, zone); + WriteMetaData(out, zone); + WriteContent(out, zone); } diff --git a/src/Unlinker/ContentLister/ZoneDefWriter.h b/src/ZoneCommon/Zone/Definition/ZoneDefWriter.h similarity index 70% rename from src/Unlinker/ContentLister/ZoneDefWriter.h rename to src/ZoneCommon/Zone/Definition/ZoneDefWriter.h index 4cb7f6a4..1458c1c7 100644 --- a/src/Unlinker/ContentLister/ZoneDefWriter.h +++ b/src/ZoneCommon/Zone/Definition/ZoneDefWriter.h @@ -1,7 +1,7 @@ #pragma once -#include "UnlinkerArgs.h" #include "Zone/Definition/ZoneDefinitionStream.h" +#include "Zone/Zone.h" class IZoneDefWriter { @@ -13,7 +13,7 @@ public: IZoneDefWriter& operator=(const IZoneDefWriter& other) = default; IZoneDefWriter& operator=(IZoneDefWriter&& other) noexcept = default; - virtual void WriteZoneDef(std::ostream& stream, const UnlinkerArgs& args, const Zone& zone) const = 0; + virtual void WriteZoneDef(std::ostream& stream, const Zone& zone, bool useGdt) const = 0; static const IZoneDefWriter* GetZoneDefWriterForGame(GameId game); }; @@ -24,9 +24,9 @@ protected: static constexpr auto META_DATA_KEY_GAME = "game"; static constexpr auto META_DATA_KEY_GDT = "gdt"; - virtual void WriteMetaData(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const = 0; - virtual void WriteContent(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const = 0; + virtual void WriteMetaData(ZoneDefinitionOutputStream& stream, const Zone& zone) const = 0; + virtual void WriteContent(ZoneDefinitionOutputStream& stream, const Zone& zone) const = 0; public: - void WriteZoneDef(std::ostream& stream, const UnlinkerArgs& args, const Zone& zone) const override; + void WriteZoneDef(std::ostream& stream, const Zone& zone, bool useGdt) const override; }; diff --git a/src/ZoneCommon/Zone/Zone.cpp b/src/ZoneCommon/Zone/Zone.cpp index deae50d3..8c160d89 100644 --- a/src/ZoneCommon/Zone/Zone.cpp +++ b/src/ZoneCommon/Zone/Zone.cpp @@ -8,7 +8,8 @@ Zone::Zone(std::string name, const zone_priority_t priority, GameId gameId) m_language(GameLanguage::LANGUAGE_NONE), m_game_id(gameId), m_pools(ZoneAssetPools::CreateForGame(gameId, this, priority)), - m_memory(std::make_unique()) + m_memory(std::make_unique()), + m_registered(false) { } diff --git a/src/ZoneLoading/ZoneLoading.cpp b/src/ZoneLoading/ZoneLoading.cpp index 3d15b80c..36e2a2ed 100644 --- a/src/ZoneLoading/ZoneLoading.cpp +++ b/src/ZoneLoading/ZoneLoading.cpp @@ -2,7 +2,6 @@ #include "Loading/IZoneLoaderFactory.h" #include "Loading/ZoneLoader.h" -#include "Utils/Logging/Log.h" #include "Utils/ObjFileStream.h" #include @@ -12,24 +11,18 @@ namespace fs = std::filesystem; -std::unique_ptr ZoneLoading::LoadZone(const std::string& path) +result::Expected, std::string> ZoneLoading::LoadZone(const std::string& path) { auto zoneName = fs::path(path).filename().replace_extension().string(); std::ifstream file(path, std::fstream::in | std::fstream::binary); if (!file.is_open()) - { - con::error("Could not open file '{}'.", path); - return nullptr; - } + return result::Unexpected(std::format("Could not open file '{}'.", path)); ZoneHeader header{}; file.read(reinterpret_cast(&header), sizeof(header)); if (file.gcount() != sizeof(header)) - { - con::error("Failed to read zone header from file '{}'.", path); - return nullptr; - } + return result::Unexpected(std::format("Failed to read zone header from file '{}'.", path)); std::unique_ptr zoneLoader; for (auto game = 0u; game < static_cast(GameId::COUNT); game++) @@ -42,10 +35,7 @@ std::unique_ptr ZoneLoading::LoadZone(const std::string& path) } if (!zoneLoader) - { - con::error("Could not create factory for zone '{}'.", zoneName); - return nullptr; - } + return result::Unexpected(std::format("Could not create factory for zone '{}'.", zoneName)); auto loadedZone = zoneLoader->LoadZone(file); diff --git a/src/ZoneLoading/ZoneLoading.h b/src/ZoneLoading/ZoneLoading.h index 749db050..330b3421 100644 --- a/src/ZoneLoading/ZoneLoading.h +++ b/src/ZoneLoading/ZoneLoading.h @@ -1,4 +1,6 @@ #pragma once + +#include "Utils/Result.h" #include "Zone/Zone.h" #include @@ -6,5 +8,5 @@ class ZoneLoading { public: - static std::unique_ptr LoadZone(const std::string& path); + static result::Expected, std::string> LoadZone(const std::string& path); };