diff --git a/Runtime/CBasics.hpp b/Runtime/CBasics.hpp index ecffc9ca2..32f24a297 100644 --- a/Runtime/CBasics.hpp +++ b/Runtime/CBasics.hpp @@ -67,7 +67,6 @@ public: static int RecursiveMakeDir(const char* dir); static void MakeDir(const char* dir); static int Stat(const char* path, Sstat* statOut); - static inline void ToLower(std::string& str) { std::transform(str.begin(), str.end(), str.begin(), ::tolower); } }; } // namespace metaforce diff --git a/Runtime/CMain.cpp b/Runtime/CMain.cpp index 8afac07e6..3be3df737 100644 --- a/Runtime/CMain.cpp +++ b/Runtime/CMain.cpp @@ -579,7 +579,7 @@ int main(int argc, char** argv) { } SetupBasics(IsClientLoggingEnabled(argc, argv)); - metaforce::FileStoreManager fileMgr{"metaforce"}; + metaforce::FileStoreManager fileMgr{"AxioDL", "metaforce"}; metaforce::CVarManager cvarMgr{fileMgr}; metaforce::CVarCommons cvarCmns{cvarMgr}; diff --git a/Runtime/CMakeLists.txt b/Runtime/CMakeLists.txt index de77316bc..7275fcb85 100644 --- a/Runtime/CMakeLists.txt +++ b/Runtime/CMakeLists.txt @@ -88,6 +88,8 @@ set(RUNTIME_SOURCES_B Streams/CMemoryInStream.hpp Streams/CZipInputStream.hpp Streams/CZipInputStream.cpp Streams/ContainerReaders.hpp + Streams/CTextInStream.hpp Streams/CTextInStream.cpp + Streams/CTextOutStream.hpp Streams/CTextOutStream.cpp CGameAllocator.hpp CGameAllocator.cpp CMemoryCardSys.hpp CMemoryCardSys.cpp CScannableObjectInfo.hpp CScannableObjectInfo.cpp @@ -135,6 +137,7 @@ set(RUNTIME_SOURCES_B GCNTypes.hpp CTextureCache.hpp CTextureCache.cpp CMayaSpline.hpp CMayaSpline.cpp + ImGuiPlayerLoadouts.hpp ImGuiPlayerLoadouts.cpp ${PLAT_SRCS}) function(add_runtime_common_library name) diff --git a/Runtime/CPlayerState.cpp b/Runtime/CPlayerState.cpp index e19fd9d1d..853a332e5 100644 --- a/Runtime/CPlayerState.cpp +++ b/Runtime/CPlayerState.cpp @@ -4,6 +4,7 @@ #include #include +#include "Runtime/CStringExtras.hpp" #include "Runtime/CMemoryCardSys.hpp" #include "Runtime/CStateManager.hpp" #include "Runtime/GameGlobalObjects.hpp" @@ -437,7 +438,7 @@ CPlayerState::EItemType CPlayerState::ItemNameToType(std::string_view name) { }}; std::string lowName{name}; - CBasics::ToLower(lowName); + CStringExtras::ToLower(lowName); const auto iter = std::find_if(typeNameMap.cbegin(), typeNameMap.cend(), [&lowName](const auto& entry) { return entry.first == lowName; }); diff --git a/Runtime/CStringExtras.hpp b/Runtime/CStringExtras.hpp index ef6a69090..ad9072860 100644 --- a/Runtime/CStringExtras.hpp +++ b/Runtime/CStringExtras.hpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace metaforce { class CInputStream; @@ -42,7 +43,71 @@ public: return s; } + static inline void ToLower(std::string& str) { std::transform(str.begin(), str.end(), str.begin(), ::tolower); } static std::string ReadString(CInputStream& in); + static inline bool ParseBool(std::string_view boolean, bool* valid) { + std::string val(boolean); + // compare must be case insensitive + // This is the cleanest solution since I only need to do it once + ToLower(val); + + // Check for true first + if (val == "true" || val == "1" || val == "yes" || val == "on") { + if (valid) + *valid = true; + + return true; + } + + // Now false + if (val == "false" || val == "0" || val == "no" || val == "off") { + if (valid) + *valid = true; + + return false; + } + + // Well that could've gone better + + if (valid) + *valid = false; + + return false; + } + + static inline std::vector& Split(std::string_view s, char delim, std::vector& elems) { + std::string tmps(s); + std::stringstream ss(tmps); + std::string item; + + while (std::getline(ss, item, delim)) { + elems.push_back(item); + } + + return elems; + } + + static inline std::vector Split(std::string_view s, char delim) { + std::vector elems; + Split(s, delim, elems); + return elems; + } + + static inline std::string LeftTrim(const std::string &s) + { + size_t start = s.find_first_not_of(" \n\r\t\f\v"); + return (start == std::string::npos) ? "" : s.substr(start); + } + + static inline std::string RightTrim(const std::string &s) + { + size_t end = s.find_last_not_of(" \n\r\t\f\v"); + return (end == std::string::npos) ? "" : s.substr(0, end + 1); + } + + static inline std::string Trim(const std::string &s) { + return RightTrim(LeftTrim(s)); + } }; } // namespace metaforce diff --git a/Runtime/ConsoleVariables/CVar.cpp b/Runtime/ConsoleVariables/CVar.cpp index 2125174cf..d8cf81d5d 100644 --- a/Runtime/ConsoleVariables/CVar.cpp +++ b/Runtime/ConsoleVariables/CVar.cpp @@ -1,5 +1,6 @@ #include "Runtime/ConsoleVariables/CVar.hpp" #include "Runtime/CBasics.hpp" +#include "Runtime/CStringExtras.hpp" #include @@ -8,56 +9,6 @@ #include "Runtime/ConsoleVariables/CVarManager.hpp" namespace metaforce { -namespace { -// TODO: Move these to CBasics? -inline bool parseBool(std::string_view boolean, bool* valid) { - std::string val(boolean); - // compare must be case insensitive - // This is the cleanest solution since I only need to do it once - CBasics::ToLower(val); - - // Check for true first - if (val == "true" || val == "1" || val == "yes" || val == "on") { - if (valid) - *valid = true; - - return true; - } - - // Now false - if (val == "false" || val == "0" || val == "no" || val == "off") { - if (valid) - *valid = true; - - return false; - } - - // Well that could've gone better - - if (valid) - *valid = false; - - return false; -} - -static std::vector& split(std::string_view s, char delim, std::vector& elems) { - std::string tmps(s); - std::stringstream ss(tmps); - std::string item; - - while (std::getline(ss, item, delim)) { - elems.push_back(item); - } - - return elems; -} - -std::vector split(std::string_view s, char delim) { - std::vector elems; - split(s, delim, elems); - return elems; -} -} extern CVar* com_developer; extern CVar* com_enableCheats; @@ -250,7 +201,7 @@ bool CVar::toBoolean(bool* isValid) const { return false; } - return parseBool(m_value, isValid); + return CStringExtras::ParseBool(m_value, isValid); } int32_t CVar::toSigned(bool* isValid) const { @@ -494,12 +445,12 @@ bool isReal(const std::vector& v) { } bool CVar::isValidInput(std::string_view input) const { - std::vector parts = split(input, ' '); + std::vector parts = CStringExtras::Split(input, ' '); char* p; switch (m_type) { case EType::Boolean: { bool valid = false; - parseBool(input, &valid); + CStringExtras::ParseBool(input, &valid); return valid; } case EType::Signed: diff --git a/Runtime/ConsoleVariables/CVarManager.cpp b/Runtime/ConsoleVariables/CVarManager.cpp index a20daa10c..aed6562e2 100644 --- a/Runtime/ConsoleVariables/CVarManager.cpp +++ b/Runtime/ConsoleVariables/CVarManager.cpp @@ -2,7 +2,11 @@ #include "Runtime/ConsoleVariables/FileStoreManager.hpp" #include "Runtime/CBasics.hpp" - +#include "Runtime/Streams/CTextInStream.hpp" +#include "Runtime/Streams/CTextOutStream.hpp" +#include "Runtime/Streams/CMemoryInStream.hpp" +#include "Runtime/Streams/CMemoryStreamOut.hpp" +#include "Runtime/CStringExtras.hpp" #include #include #include @@ -19,8 +23,7 @@ static const std::regex cmdLineRegex(R"(\+([\w\.]+)([=])?([\/\\\s\w\.\-]+)?)"); CVarManager* CVarManager::m_instance = nullptr; static logvisor::Module CVarLog("CVarManager"); -CVarManager::CVarManager(FileStoreManager& store, bool useBinary) -: m_store(store), m_useBinary(useBinary) { +CVarManager::CVarManager(FileStoreManager& store, bool useBinary) : m_store(store), m_useBinary(useBinary) { m_instance = this; com_configfile = newCVar("config", "File to store configuration", std::string("config"), @@ -38,7 +41,7 @@ CVarManager::~CVarManager() {} CVar* CVarManager::registerCVar(std::unique_ptr&& cvar) { std::string tmp(cvar->name()); - CBasics::ToLower(tmp); + CStringExtras::ToLower(tmp); if (m_cvars.find(tmp) != m_cvars.end()) { return nullptr; @@ -51,7 +54,7 @@ CVar* CVarManager::registerCVar(std::unique_ptr&& cvar) { CVar* CVarManager::findCVar(std::string_view name) { std::string lower(name); - CBasics::ToLower(lower); + CStringExtras::ToLower(lower); auto search = m_cvars.find(lower); if (search == m_cvars.end()) return nullptr; @@ -78,15 +81,14 @@ std::vector CVarManager::cvars(CVar::EFlags filter) const { } void CVarManager::deserialize(CVar* cvar) { - /* Make sure we're not trying to deserialize a CVar that is invalid or not exposed, unless it's been specified on the - * command line (i.e deferred) */ + /* Make sure we're not trying to deserialize a CVar that is invalid*/ if (!cvar) { return; } /* First let's check for a deferred value */ std::string lowName = cvar->name().data(); - CBasics::ToLower(lowName); + CStringExtras::ToLower(lowName); if (const auto iter = m_deferedCVars.find(lowName); iter != m_deferedCVars.end()) { std::string val = std::move(iter->second); m_deferedCVars.erase(lowName); @@ -106,103 +108,89 @@ void CVarManager::deserialize(CVar* cvar) { if (!cvar->isArchive() && !cvar->isInternalArchivable()) { return; } -#if 0 // TODO: Reimplement this /* We were either unable to find a deferred value or got an invalid value */ - std::string filename = - std::string(m_store.getStoreRoot()) + '/' + com_configfile->toLiteral(); - CBascis::Sstat st; - - if (m_useBinary) { - CVarContainer container; - filename += ".bin"; - if (CBascis::Stat(filename.c_str(), &st) || !S_ISREG(st.st_mode)) - return; - athena::io::FileReader reader(filename); - if (reader.isOpen()) - container.read(reader); - - if (container.cvars.size() > 0) { - auto serialized = std::find_if(container.cvars.begin(), container.cvars.end(), - [&cvar](const DNACVAR::CVar& c) { return c.m_name == cvar->name(); }); - - if (serialized != container.cvars.end()) { - DNACVAR::CVar& tmp = *serialized; - - if (cvar->m_value != tmp.m_value) { - CVarUnlocker lc(cvar); - cvar->fromLiteralToType(tmp.m_value); - cvar->m_wasDeserialized = true; - } - } - } - } else { - filename += ".yaml"; - if (Stat(filename.c_str(), &st) || !S_ISREG(st.st_mode)) - return; - athena::io::FileReader reader(filename); - if (reader.isOpen()) { - athena::io::YAMLDocReader docReader; - if (docReader.parse(&reader)) { - std::unique_ptr root = docReader.releaseRootNode(); - auto serialized = std::find_if(root->m_mapChildren.begin(), root->m_mapChildren.end(), - [&cvar](const auto& c) { return c.first == cvar->name(); }); - - if (serialized != root->m_mapChildren.end()) { - const std::unique_ptr& tmp = serialized->second; - - if (cvar->m_value != tmp->m_scalarString) { - CVarUnlocker lc(cvar); - cvar->fromLiteralToType(tmp->m_scalarString); - cvar->m_wasDeserialized = true; - } - } - } + std::string filename = std::string(m_store.getStoreRoot()) + '/' + com_configfile->toLiteral() + ".yaml"; + auto container = loadCVars(filename); + auto serialized = + std::find_if(container.cbegin(), container.cend(), [&cvar](const auto& c) { return c.m_name == cvar->name(); }); + if (serialized != container.cend()) { + if (cvar->m_value != serialized->m_value) { + CVarUnlocker lc(cvar); + cvar->fromLiteralToType(serialized->m_value); + cvar->m_wasDeserialized = true; } } -#endif } void CVarManager::serialize() { -#if 0 // TODO: reimplement this - std::string filename = - std::string(m_store.getStoreRoot()) + '/' + com_configfile->toLiteral(); + std::string filename = std::string(m_store.getStoreRoot()) + '/' + com_configfile->toLiteral() + ".yaml"; - if (m_useBinary) { - CVarContainer container; - for (const auto& pair : m_cvars) { - const auto& cvar = pair.second; + /* If we have an existing config load it in, so we can update it */ + auto container = loadCVars(filename); - if (cvar->isArchive() || (cvar->isInternalArchivable() && cvar->wasDeserialized() && !cvar->hasDefaultValue())) { - container.cvars.push_back(*cvar); + u32 minLength = 0; + for (const auto& pair : m_cvars) { + const auto& cvar = pair.second; + + if (cvar->isArchive() || (cvar->isInternalArchivable() && cvar->wasDeserialized() && !cvar->hasDefaultValue())) { + /* Look for an existing CVar in the file... */ + auto serialized = + std::find_if(container.begin(), container.end(), [&cvar](const auto& c) { return c.m_name == cvar->name(); }); + if (serialized != container.end()) { + /* Found it! Update the value */ + serialized->m_value = cvar->value(); + } else { + /* Store this value as a new CVar in the config */ + container.emplace_back(StoreCVar::CVar{std::string(cvar->name()), cvar->value()}); } + /* Compute length needed for this cvar */ + minLength += cvar->name().length() + cvar->value().length() + 2; } - container.cvarCount = u32(container.cvars.size()); - - filename += ".bin"; - athena::io::FileWriter writer(filename); - if (writer.isOpen()) - container.write(writer); - } else { - filename += ".yaml"; - - athena::io::FileReader r(filename); - athena::io::YAMLDocWriter docWriter(r.isOpen() ? &r : nullptr); - r.close(); - - docWriter.setStyle(athena::io::YAMLNodeStyle::Block); - for (const auto& pair : m_cvars) { - const auto& cvar = pair.second; - - if (cvar->isArchive() || (cvar->isInternalArchivable() && cvar->wasDeserialized() && !cvar->hasDefaultValue())) { - docWriter.writeString(cvar->name().data(), cvar->toLiteral()); - } - } - - athena::io::FileWriter w(filename); - if (w.isOpen()) - docWriter.finish(&w); } -#endif + + // Allocate enough space to write all the strings with some space to spare + const auto requiredLen = minLength + (4 * container.size()); + std::unique_ptr workBuf(new u8[requiredLen]); + CMemoryStreamOut memOut(workBuf.get(), requiredLen); + CTextOutStream textOut(memOut); + for (const auto& cvar : container) { + textOut.WriteString(fmt::format(FMT_STRING("{}: {}"), cvar.m_name, cvar.m_value)); + } + + auto* file = fopen(filename.c_str(), "wbe"); + if (file != nullptr) { + fwrite(workBuf.get(), 1, memOut.GetWritePosition(), file); + } + fclose(file); +} + +std::vector CVarManager::loadCVars(const std::string& filename) const { + std::vector ret; + CBasics::Sstat st; + if (CBasics::Stat(filename.c_str(), &st) == 0 && S_ISREG(st.st_mode)) { + + auto* file = fopen(filename.c_str(), "rbe"); + if (file != nullptr) { + + std::unique_ptr inBuf(new u8[st.st_size]); + fread(inBuf.get(), 1, st.st_size, file); + fclose(file); + CMemoryInStream mem(inBuf.get(), st.st_size, CMemoryInStream::EOwnerShip::NotOwned); + CTextInStream textIn(mem, st.st_size); + while (!textIn.IsEOF()) { + auto cvString = textIn.GetNextLine(); + if (cvString.empty()) { + continue; + } + auto parts = CStringExtras::Split(cvString, ':'); + if (parts.size() < 2) { + continue; + } + ret.emplace_back(StoreCVar::CVar{CStringExtras::Trim(parts[0]), CStringExtras::Trim(parts[1])}); + } + } + } + return ret; } CVarManager* CVarManager::instance() { return m_instance; } @@ -234,7 +222,7 @@ bool CVarManager::restartRequired() const { void CVarManager::parseCommandLine(const std::vector& args) { bool oldDeveloper = suppressDeveloper(); std::string developerName(com_developer->name()); - CBasics::ToLower(developerName); + CStringExtras::ToLower(developerName); for (const std::string& arg : args) { if (arg[0] != '+') { continue; @@ -267,13 +255,13 @@ void CVarManager::parseCommandLine(const std::vector& args) { cv->fromLiteralToType(cvarValue); } cv->m_wasDeserialized = true; - CBasics::ToLower(cvarName); + CStringExtras::ToLower(cvarName); if (developerName == cvarName) /* Make sure we're not overriding developer mode when we restore */ oldDeveloper = com_developer->toBoolean(); } else { /* Unable to find an existing CVar, let's defer for the time being 8 */ - CBasics::ToLower(cvarName); + CStringExtras::ToLower(cvarName); m_deferedCVars.insert_or_assign(std::move(cvarName), std::move(cvarValue)); } } @@ -303,4 +291,4 @@ void CVarManager::proc() { } } -} // namespace hecl +} // namespace metaforce diff --git a/Runtime/ConsoleVariables/CVarManager.hpp b/Runtime/ConsoleVariables/CVarManager.hpp index d2fad8599..de049d2aa 100644 --- a/Runtime/ConsoleVariables/CVarManager.hpp +++ b/Runtime/ConsoleVariables/CVarManager.hpp @@ -102,12 +102,15 @@ public: void parseCommandLine(const std::vector& args); + FileStoreManager& fileStoreManager() { return m_store; } + private: bool suppressDeveloper(); void restoreDeveloper(bool oldDeveloper); std::unordered_map> m_cvars; std::unordered_map m_deferedCVars; + std::vector loadCVars(const std::string& filename) const; }; } // namespace hecl diff --git a/Runtime/ConsoleVariables/FileStoreManager.cpp b/Runtime/ConsoleVariables/FileStoreManager.cpp index 167bf4844..bbc0f0852 100644 --- a/Runtime/ConsoleVariables/FileStoreManager.cpp +++ b/Runtime/ConsoleVariables/FileStoreManager.cpp @@ -2,6 +2,7 @@ #include "Runtime/CBasics.hpp" +#include #include #if _WIN32 #include @@ -16,48 +17,69 @@ using namespace Windows::Storage; #endif namespace metaforce { +namespace { static logvisor::Module Log("FileStoreManager"); - -FileStoreManager::FileStoreManager(std::string_view domain) : m_domain(domain) { -#if _WIN32 -#if !WINDOWS_STORE - WCHAR home[MAX_PATH]; - if (!SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_PROFILE, NULL, 0, home))) - Log.report(logvisor::Fatal, FMT_STRING("unable to locate profile for file store")); - - std::string path = nowide::narrow(home); -#else - StorageFolder ^ cacheFolder = ApplicationData::Current->LocalCacheFolder; - std::string path(cacheFolder->Path->Data()); -#endif - path += "/.heclrun"; - - CBasics::MakeDir(path.c_str()); - path += '/'; - path += domain.data(); - - CBasics::MakeDir(path.c_str()); - m_storeRoot = path; -#else - const char* xdg_data_home = getenv("XDG_DATA_HOME"); - std::string path; - if (xdg_data_home) { - if (xdg_data_home[0] != '/') - Log.report(logvisor::Fatal, FMT_STRING("invalid $XDG_DATA_HOME for file store (must be absolute)")); - path = xdg_data_home; - } else { - const char* home = getenv("HOME"); - if (!home) - Log.report(logvisor::Fatal, FMT_STRING("unable to locate $HOME for file store")); - path = home; - path += "/.local/share"; - } - path += "/hecl/"; - path += domain.data(); - if (CBasics::RecursiveMakeDir(path.c_str()) != 0) - Log.report(logvisor::Fatal, FMT_STRING("unable to mkdir at {}"), path); - m_storeRoot = path; -#endif +FileStoreManager* g_instance = nullptr; } +FileStoreManager::FileStoreManager(std::string_view org, std::string_view domain) : m_org(org), m_domain(domain) { + if (g_instance != nullptr) { + Log.report(logvisor::Fatal, FMT_STRING("Attempting to build another FileStoreManager!!")); + return; + } + + auto prefPath = SDL_GetPrefPath(org.data(), domain.data()); + if (prefPath == nullptr) { +#if _WIN32 +#if !WINDOWS_STORE + WCHAR home[MAX_PATH]; + if (!SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_PROFILE, NULL, 0, home))) + Log.report(logvisor::Fatal, FMT_STRING("unable to locate profile for file store")); + + std::string path = nowide::narrow(home); +#else + StorageFolder ^ cacheFolder = ApplicationData::Current->LocalCacheFolder; + std::string path(cacheFolder->Path->Data()); +#endif + path += "/." + m_org; + + CBasics::MakeDir(path.c_str()); + path += '/'; + path += domain.data(); + + CBasics::MakeDir(path.c_str()); + m_storeRoot = path; +#else + const char* xdg_data_home = getenv("XDG_DATA_HOME"); + std::string path; + if (xdg_data_home) { + if (xdg_data_home[0] != '/') + Log.report(logvisor::Fatal, FMT_STRING("invalid $XDG_DATA_HOME for file store (must be absolute)")); + path = xdg_data_home; + } else { + const char* home = getenv("HOME"); + if (!home) + Log.report(logvisor::Fatal, FMT_STRING("unable to locate $HOME for file store")); + path = home; + path += "/.local/share"; + } + path += "/" + m_org + "/" + domain.data(); + if (CBasics::RecursiveMakeDir(path.c_str()) != 0) { + Log.report(logvisor::Fatal, FMT_STRING("unable to mkdir at {}"), path); + } + m_storeRoot = path; +#endif + } else { + m_storeRoot = std::string(prefPath); + } + g_instance = this; +} + +FileStoreManager* FileStoreManager::instance() { + if (g_instance == nullptr) { + Log.report(logvisor::Fatal, FMT_STRING("Requested FileStoreManager instance before it's built!")); + return nullptr; + } + return g_instance; +} } // namespace hecl::Runtime diff --git a/Runtime/ConsoleVariables/FileStoreManager.hpp b/Runtime/ConsoleVariables/FileStoreManager.hpp index 20dcf150c..143138cbf 100644 --- a/Runtime/ConsoleVariables/FileStoreManager.hpp +++ b/Runtime/ConsoleVariables/FileStoreManager.hpp @@ -7,16 +7,23 @@ namespace metaforce { * @brief Per-platform file store resolution */ class FileStoreManager { + std::string m_org; std::string m_domain; std::string m_storeRoot; public: - FileStoreManager(std::string_view domain); + FileStoreManager(FileStoreManager&) = delete; + FileStoreManager(FileStoreManager&&) = delete; + void operator=(FileStoreManager&) = delete; + void operator=(FileStoreManager&&) = delete; + FileStoreManager(std::string_view org, std::string_view domain); + std::string_view getOrg() const { return m_org; } std::string_view getDomain() const { return m_domain; } /** * @brief Returns the full path to the file store, including domain * @return Full path to store e.g /home/foo/.hecl/bar */ std::string_view getStoreRoot() const { return m_storeRoot; } + static FileStoreManager* instance(); }; } \ No newline at end of file diff --git a/Runtime/ImGuiConsole.cpp b/Runtime/ImGuiConsole.cpp index 76073a35e..be544db72 100644 --- a/Runtime/ImGuiConsole.cpp +++ b/Runtime/ImGuiConsole.cpp @@ -263,7 +263,7 @@ static void RenderEntityColumns(const ImGuiEntityEntry& entry) { } void ImGuiConsole::ShowInspectWindow(bool* isOpen) { - float initialWindowSize = 400.f * ImGui::GetIO().DisplayFramebufferScale.x; + float initialWindowSize = 400.f * aurora::get_window_size().scale; ImGui::SetNextWindowSize(ImVec2{initialWindowSize, initialWindowSize * 1.5f}, ImGuiCond_FirstUseEver); if (ImGui::Begin("Inspect", isOpen)) { @@ -383,7 +383,7 @@ bool ImGuiConsole::ShowEntityInfoWindow(TUniqueId uid) { void ImGuiConsole::ShowConsoleVariablesWindow() { // For some reason the window shows up tiny without this - float initialWindowSize = 350.f * ImGui::GetIO().DisplayFramebufferScale.x; + float initialWindowSize = 350.f * aurora::get_window_size().scale; ImGui::SetNextWindowSize(ImVec2{initialWindowSize, initialWindowSize}, ImGuiCond_FirstUseEver); if (ImGui::Begin("Console Variables", &m_showConsoleVariablesWindow)) { if (ImGui::Button("Clear")) { @@ -633,7 +633,7 @@ std::optional ImGuiConsole::ShowAboutWindow(bool canClose, std::str flags |= ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove; } if (ImGui::Begin("About", open, flags)) { - float iconSize = 128.f * ImGui::GetIO().DisplayFramebufferScale.x; + float iconSize = 128.f * aurora::get_window_size().scale; ImGui::SameLine(ImGui::GetWindowSize().x / 2 - iconSize + (iconSize / 2)); ImGui::Image(ImGuiEngine::metaforceIcon, ImVec2{iconSize, iconSize}); ImGui::PushFont(ImGuiEngine::fontLarge); @@ -942,7 +942,7 @@ void ImGuiConsole::ShowInputViewer() { } ImGui::SetNextWindowBgAlpha(0.65f); if (ImGui::Begin("Input Overlay", nullptr, windowFlags)) { - float scale = ImGui::GetIO().DisplayFramebufferScale.x; + float scale = aurora::get_window_size().scale; if (!m_controllerName.empty()) { TextCenter(m_controllerName); ImGui::Separator(); @@ -1508,7 +1508,7 @@ void ImGuiConsole::ShowItemsWindow() { void ImGuiConsole::ShowLayersWindow() { // For some reason the window shows up tiny without this - float initialWindowSize = 350.f * ImGui::GetIO().DisplayFramebufferScale.x; + float initialWindowSize = 350.f * aurora::get_window_size().scale; ImGui::SetNextWindowSize(ImVec2{initialWindowSize, initialWindowSize}, ImGuiCond_FirstUseEver); if (ImGui::Begin("Layers", &m_showLayersWindow)) { diff --git a/Runtime/ImGuiPlayerLoadouts.cpp b/Runtime/ImGuiPlayerLoadouts.cpp new file mode 100644 index 000000000..5ecae7a27 --- /dev/null +++ b/Runtime/ImGuiPlayerLoadouts.cpp @@ -0,0 +1,43 @@ +#include "Runtime/ImGuiPlayerLoadouts.hpp" +#include "Runtime/Streams/ContainerReaders.hpp" +#include "Runtime/Streams/ContainerWriters.hpp" + +#include + +#include "magic_enum.hpp" +namespace metaforce { +namespace { +logvisor::Module Log("metaforce::ImGuiPlayerLoadouts"); +constexpr u32 CurrentVersion = 1; +} // namespace +ImGuiPlayerLoadouts::Item::Item(CInputStream& in) +: type(magic_enum::enum_cast(in.Get()).value()), amount(in.ReadLong()) {} + +void ImGuiPlayerLoadouts::Item::PutTo(COutputStream& out) const { + out.Put(magic_enum::enum_name(type)); + out.Put(amount); +} +ImGuiPlayerLoadouts::LoadOut::LoadOut(CInputStream& in) : name(in.Get()) { read_vector(items, in); } + +void ImGuiPlayerLoadouts::LoadOut::PutTo(COutputStream& out) const { + out.Put(name); + write_vector(items, out); +} + +ImGuiPlayerLoadouts::ImGuiPlayerLoadouts(CInputStream& in) { + FourCC magic; + in.Get(reinterpret_cast(&magic), 4); + auto version = in.ReadLong(); + if (magic != FOURCC('LOAD') && version != CurrentVersion) { + Log.report(logvisor::Error, FMT_STRING("Incorrect loadout version, expected {} got {}"), CurrentVersion, version); + return; + } + read_vector(loadouts, in); +} +void ImGuiPlayerLoadouts::PutTo(COutputStream& out) const { + auto magic = FOURCC('LOAD'); + out.Put(reinterpret_cast(&magic), 4); + out.Put(CurrentVersion); + write_vector(loadouts, out); +} +} // namespace metaforce \ No newline at end of file diff --git a/Runtime/ImGuiPlayerLoadouts.hpp b/Runtime/ImGuiPlayerLoadouts.hpp index d76de2c58..01c309e53 100644 --- a/Runtime/ImGuiPlayerLoadouts.hpp +++ b/Runtime/ImGuiPlayerLoadouts.hpp @@ -1,23 +1,27 @@ #pragma once -#include -#include +#include "Runtime/CPlayerState.hpp" namespace metaforce { -struct ImGuiPlayerLoadouts : athena::io::DNA { - AT_DECL_DNA_YAML - struct Item : athena::io::DNA { - AT_DECL_DNA_YAML - String<-1> type; - Value amount; +struct ImGuiPlayerLoadouts { + struct Item { + CPlayerState::EItemType type = CPlayerState::EItemType::Invalid; + u32 amount = 0; + Item() = default; + explicit Item(CInputStream& in); + void PutTo(COutputStream& out) const; }; - struct LoadOut : athena::io::DNA { - AT_DECL_DNA_YAML - String<-1> name; - Value itemCount; - Vector items; + struct LoadOut{ + std::string name; + std::vector items; + LoadOut() = default; + explicit LoadOut(CInputStream& in); + void PutTo(COutputStream& out) const; }; - Value loadoutCount; - Vector loadouts; + std::vector loadouts; + + ImGuiPlayerLoadouts() = default; + explicit ImGuiPlayerLoadouts(CInputStream& in); + void PutTo(COutputStream& out) const; }; } // namespace metaforce diff --git a/Runtime/Streams/CInputStream.cpp b/Runtime/Streams/CInputStream.cpp index 606dd0c26..6641d4445 100644 --- a/Runtime/Streams/CInputStream.cpp +++ b/Runtime/Streams/CInputStream.cpp @@ -89,32 +89,36 @@ u32 CInputStream::ReadBytes(void* dest, u32 len) { return curReadLen; } -u32 CInputStream::ReadBits(u32 bitCount) { - u32 ret = x20_bitOffset; - if (ret < bitCount) { - const u32 shiftAmt = bitCount - x20_bitOffset; - const u32 mask = ret == 32 ? -1 : (1 << x20_bitOffset) - 1; - u32 uVar2 = x1c_bitWord; - x20_bitOffset = 0; - u32 len = min_containing_bytes(shiftAmt); - Get(reinterpret_cast(&x1c_bitWord), len); +u32 CInputStream::ReadBits(u32 bitCount) { + u32 ret = 0; + const s32 shiftAmt = x20_bitOffset - s32(bitCount); + if (shiftAmt < 0) { + /* OR in remaining bits of cached value */ + u32 mask = bitCount == 32 ? UINT32_MAX : ((1U << bitCount) - 1); + ret |= (x1c_bitWord << u32(-shiftAmt)) & mask; + /* Load in exact number of bytes remaining */ + auto loadDiv = std::div(-shiftAmt, 8); + if (loadDiv.rem != 0) { + ++loadDiv.quot; + } + Get(reinterpret_cast(&x1c_bitWord) + 4 - loadDiv.quot, loadDiv.quot); #if METAFORCE_TARGET_BYTE_ORDER == __ORDER_LITTLE_ENDIAN__ x1c_bitWord = CBasics::SwapBytes(x1c_bitWord); #endif - - const u32 retMask = shiftAmt == 32 ? -1 : (1 << shiftAmt) - 1; - const u32 tmpOffset = x20_bitOffset; - x20_bitOffset = len * 8; - ret = ((mask & uVar2) >> (32 - ret) << shiftAmt) | (retMask & (x1c_bitWord >> (32 - shiftAmt))) << tmpOffset; - x20_bitOffset -= shiftAmt; - x1c_bitWord <<= shiftAmt; + /* New bit offset */ + x20_bitOffset = loadDiv.quot * 8 + shiftAmt; + /* OR in next bits */ + mask = (1U << u32(-shiftAmt)) - 1; + ret |= (x1c_bitWord >> x20_bitOffset) & mask; } else { + /* OR in bits of cached value */ + const u32 mask = bitCount == 32 ? UINT32_MAX : ((1U << bitCount) - 1); + ret |= (x1c_bitWord >> u32(shiftAmt)) & mask; + /* New bit offset */ x20_bitOffset -= bitCount; - ret = bitCount == 32 ? -1 : (1 << bitCount) - 1; - ret &= x1c_bitWord >> (32 - bitCount); - x1c_bitWord <<= bitCount; } + return ret; } diff --git a/Runtime/Streams/CMemoryStreamOut.cpp b/Runtime/Streams/CMemoryStreamOut.cpp index e13760bee..fb451b62f 100644 --- a/Runtime/Streams/CMemoryStreamOut.cpp +++ b/Runtime/Streams/CMemoryStreamOut.cpp @@ -19,6 +19,7 @@ void CMemoryStreamOut::Write(const u8* ptr, u32 len) { if (len != 0) { memcpy(x7c_ptr + x84_position, ptr, len); + x84_position += len; } } } // namespace metaforce diff --git a/Runtime/Streams/CMemoryStreamOut.hpp b/Runtime/Streams/CMemoryStreamOut.hpp index 69e4d2792..41633e0d4 100644 --- a/Runtime/Streams/CMemoryStreamOut.hpp +++ b/Runtime/Streams/CMemoryStreamOut.hpp @@ -22,5 +22,6 @@ public: ~CMemoryStreamOut() override; void Write(const u8* ptr, u32 len) override; + u32 GetWritePosition() const { return x84_position; } }; } // namespace metaforce diff --git a/Runtime/Streams/COutputStream.cpp b/Runtime/Streams/COutputStream.cpp index 9dbe9178b..0fdf327c5 100644 --- a/Runtime/Streams/COutputStream.cpp +++ b/Runtime/Streams/COutputStream.cpp @@ -78,27 +78,27 @@ void COutputStream::Put(const u8* ptr, u32 len) { } void COutputStream::WriteBits(u32 val, u32 bitCount) { - const u32 bitOffset = x18_shiftRegisterOffset; - if (x18_shiftRegisterOffset < bitCount) { + const s32 shiftAmt = x18_shiftRegisterOffset - s32(bitCount); + if (shiftAmt < 0) { /* OR remaining bits to cached value */ - const u32 shiftAmt = (bitCount - x18_shiftRegisterOffset); - const u32 mask = bitOffset == 32 ? -1 : (1 << bitOffset) - 1; + const u32 mask = (1U << x18_shiftRegisterOffset) - 1; + x14_shiftRegister |= (val >> u32(-shiftAmt)) & mask; /* Write out 32-bits */ - x14_shiftRegister |= (val >> shiftAmt) & mask; - x18_shiftRegisterOffset = 0; FlushShiftRegister(); /* Cache remaining bits */ - x14_shiftRegister = (val & (shiftAmt == 32 ? -1 : (1 << shiftAmt) - 1)) << (32 - shiftAmt); - x18_shiftRegisterOffset -= shiftAmt; + x18_shiftRegisterOffset = 0x20 + shiftAmt; + x14_shiftRegister = val << x18_shiftRegisterOffset; } else { /* OR bits to cached value */ - const u32 mask = bitOffset == 0x20 ? -1 : (1 << bitOffset) - 1; - x14_shiftRegister |= (val & mask) << (bitOffset - bitCount); + const u32 mask = bitCount == 32 ? UINT32_MAX : ((1U << bitCount) - 1); + x14_shiftRegister |= (val & mask) << u32(shiftAmt); + /* New bit offset */ x18_shiftRegisterOffset -= bitCount; } + } void COutputStream::WriteChar(u8 c) { @@ -199,6 +199,14 @@ void coutput_stream_helper(const std::string& t, COutputStream& out) { } } +template <> +void coutput_stream_helper(const std::string_view& t, COutputStream& out) { + for (size_t i = 0; i < t.size() + 1; ++i) { + out.FlushShiftRegister(); + out.Put(t[i]); + } +} + u32 COutputStream::GetBitCount(u32 val) { int bits = 0; for (; val != 0; val >>= 1) { diff --git a/Runtime/Streams/COutputStream.hpp b/Runtime/Streams/COutputStream.hpp index 9e09f2738..2bd1e2c2d 100644 --- a/Runtime/Streams/COutputStream.hpp +++ b/Runtime/Streams/COutputStream.hpp @@ -86,4 +86,6 @@ template <> void coutput_stream_helper(const double& t, COutputStream& out); template <> void coutput_stream_helper(const std::string& t, COutputStream& out); +template <> +void coutput_stream_helper(const std::string_view& t, COutputStream& out); } // namespace metaforce diff --git a/Runtime/Streams/CTextInStream.cpp b/Runtime/Streams/CTextInStream.cpp new file mode 100644 index 000000000..cfc4f95b7 --- /dev/null +++ b/Runtime/Streams/CTextInStream.cpp @@ -0,0 +1,24 @@ +#include "Runtime/Streams/CTextInStream.hpp" +#include + +namespace metaforce { +CTextInStream::CTextInStream(CInputStream& in, int len) : m_in(&in), m_len(len) {} + +std::string CTextInStream::GetNextLine() { + std::string ret; + while (true) { + auto chr = m_in->ReadChar(); + ret += chr; + if (ret.back() == '\r' || ret.back() == '\n') { + if (ret.back() == '\r') { + m_in->ReadChar(); + } + break; + } + } + + ret.erase(std::remove(ret.begin(), ret.end(), '\r'), ret.end()); + ret.erase(std::remove(ret.begin(), ret.end(), '\n'), ret.end()); + return ret; +} +} // namespace metaforce diff --git a/Runtime/Streams/CTextInStream.hpp b/Runtime/Streams/CTextInStream.hpp new file mode 100644 index 000000000..bebfda352 --- /dev/null +++ b/Runtime/Streams/CTextInStream.hpp @@ -0,0 +1,15 @@ +#pragma once +#include "Runtime/Streams/CInputStream.hpp" + +namespace metaforce { +class CTextInStream { + CInputStream* m_in; + s32 m_len; + +public: + CTextInStream(CInputStream& in, int len); + + bool IsEOF() { return m_in->GetReadPosition() >= m_len; } + std::string GetNextLine(); +}; +} // namespace metaforce \ No newline at end of file diff --git a/Runtime/Streams/CTextOutStream.cpp b/Runtime/Streams/CTextOutStream.cpp new file mode 100644 index 000000000..da6a4b148 --- /dev/null +++ b/Runtime/Streams/CTextOutStream.cpp @@ -0,0 +1,30 @@ +#include "Runtime/Streams/CTextOutStream.hpp" + +namespace metaforce { +CTextOutStream::CTextOutStream(COutputStream& out) : m_out(&out) {} + +void CTextOutStream::WriteString(const std::string& str) { CTextOutStream::WriteString(str.c_str(), str.length()); } +void CTextOutStream::WriteString(const char* str, u32 len) { + bool wroteCarriageReturn = false; + bool wroteLineFeed = false; + for (u32 i = 0; i < len; ++i) { + if (str[i] == '\r') { + wroteCarriageReturn = true; + } else if (str[i] == '\n' && !wroteCarriageReturn) { + m_out->WriteChar('\r'); + wroteLineFeed = true; + wroteCarriageReturn = true; + } + m_out->WriteChar(str[i]); + } + + /* If we didn't write either a line feed or carriage return we need to do that now */ + if (!wroteCarriageReturn && !wroteLineFeed) { + m_out->WriteChar('\r'); + m_out->WriteChar('\n'); + } + + /* Since this is a text buffer, we always want to flush after writing a string */ + m_out->Flush(); +} +} // namespace metaforce diff --git a/Runtime/Streams/CTextOutStream.hpp b/Runtime/Streams/CTextOutStream.hpp new file mode 100644 index 000000000..23aa0f141 --- /dev/null +++ b/Runtime/Streams/CTextOutStream.hpp @@ -0,0 +1,14 @@ +#pragma once +#include "Runtime/Streams/COutputStream.hpp" + +namespace metaforce { +class CTextOutStream { + COutputStream* m_out; + +public: + explicit CTextOutStream(COutputStream& out); + + void WriteString(const std::string& str); + void WriteString(const char* str, u32 len); +}; +} // namespace metaforce diff --git a/Runtime/Streams/ContainerReaders.hpp b/Runtime/Streams/ContainerReaders.hpp index 2308d13f6..e98a2ec21 100644 --- a/Runtime/Streams/ContainerReaders.hpp +++ b/Runtime/Streams/ContainerReaders.hpp @@ -1,7 +1,8 @@ #pragma once + #include "Runtime/Streams/CInputStream.hpp" #include "Runtime/rstl.hpp" - +#include namespace metaforce { template void read_reserved_vector(rstl::reserved_vector& v, CInputStream& in) { @@ -11,4 +12,13 @@ void read_reserved_vector(rstl::reserved_vector& v, CInputStream& in) { v[i] = in.Get(); } } + +template +void read_vector(std::vector& v, CInputStream& in) { + u32 count = in.ReadLong(); + v.reserve(count); + for (u32 i = 0; i < count; ++i) { + v.emplace_back(in.Get()); + } +} } // namespace metaforce diff --git a/Runtime/Streams/ContainerWriters.hpp b/Runtime/Streams/ContainerWriters.hpp new file mode 100644 index 000000000..ca5330cc6 --- /dev/null +++ b/Runtime/Streams/ContainerWriters.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "Runtime/Streams/COutputStream.hpp" +#include "Runtime/rstl.hpp" +#include +namespace metaforce { +template +void write_reserved_vector(const rstl::reserved_vector& v, COutputStream& out) { + out.Put(v.size()); + for (const auto& t : v) { + out.Put(t); + } +} + +template +void write_vector(const std::vector& v, COutputStream& out) { + out.Put(v.size()); + for (const auto& t : v) { + out.Put(t); + } +} +} // namespace metaforce \ No newline at end of file diff --git a/aurora/CMakeLists.txt b/aurora/CMakeLists.txt index c4243dcfc..35abd4bd3 100644 --- a/aurora/CMakeLists.txt +++ b/aurora/CMakeLists.txt @@ -29,7 +29,7 @@ add_library(aurora STATIC lib/gfx/colored_quad/shader.cpp ) target_compile_definitions(aurora PRIVATE IMGUI_USER_CONFIG="imconfig_user.h") # IMGUI_USE_WCHAR32 -target_include_directories(aurora PUBLIC include ../Runtime) +target_include_directories(aurora PUBLIC include ../ ) target_include_directories(aurora PRIVATE ../imgui ../extern/imgui) target_include_directories(aurora PRIVATE ../extern/dawn/src diff --git a/aurora/include/aurora/common.hpp b/aurora/include/aurora/common.hpp index cee65b348..b2b779a66 100644 --- a/aurora/include/aurora/common.hpp +++ b/aurora/include/aurora/common.hpp @@ -4,7 +4,7 @@ #include #include -#include "RetroTypes.hpp" +#include "Runtime/RetroTypes.hpp" namespace aurora { template diff --git a/aurora/lib/imgui.cpp b/aurora/lib/imgui.cpp index a012a915d..2379f4e7b 100644 --- a/aurora/lib/imgui.cpp +++ b/aurora/lib/imgui.cpp @@ -2,6 +2,7 @@ #include "gpu.hpp" +#include "Runtime/ConsoleVariables/FileStoreManager.hpp" #include #include #include @@ -16,11 +17,16 @@ using gpu::g_queue; static float g_scale; +static std::string g_imguiSettings{}; +static std::string g_imguiLog{}; void create_context() noexcept { IMGUI_CHECKVERSION(); ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); - io.IniFilename = nullptr; + g_imguiSettings = std::string(metaforce::FileStoreManager::instance()->getStoreRoot()) + "/imgui.ini"; + g_imguiLog = std::string(metaforce::FileStoreManager::instance()->getStoreRoot()) + "/imgui.log"; + io.IniFilename = g_imguiSettings.c_str(); + io.LogFilename = g_imguiLog.c_str(); } void initialize(SDL_Window* window) noexcept { diff --git a/extern/nod b/extern/nod index 72893dcac..258076c29 160000 --- a/extern/nod +++ b/extern/nod @@ -1 +1 @@ -Subproject commit 72893dcacb02ebff11b354832c55f3763ab523b7 +Subproject commit 258076c2986f2d77d887a8405f1194405bb4b99d