From f179ee0a8ada98c76ffab07b485c189cb3a4bfff Mon Sep 17 00:00:00 2001 From: Jamiras Date: Tue, 8 Sep 2026 14:40:17 -0600 Subject: [PATCH 1/2] wrap rapidjson in abstraction class --- src/RA_Defs.h | 1 - src/RA_Integration.vcxproj | 1 - src/RA_Integration.vcxproj.filters | 3 - src/RA_Json.cpp | 66 --- src/RA_Json.h | 12 - src/api/impl/ConnectedServer.cpp | 68 +-- src/devkit/RADevKit.vcxproj | 2 + src/devkit/RADevKit.vcxproj.filters | 6 + src/devkit/util/Json.cpp | 489 ++++++++++++++++++ src/devkit/util/Json.hh | 308 +++++++++++ src/pch.h | 9 - src/services/AchievementRuntime.cpp | 19 +- src/services/impl/FileLocalStorage.cpp | 1 - src/services/impl/JsonFileConfiguration.cpp | 248 ++++----- src/ui/Theme.cpp | 227 ++++---- .../viewmodels/MemoryBookmarksViewModel.cpp | 70 ++- .../viewmodels/MemoryWatchListViewModel.cpp | 1 - src/ui/viewmodels/MemoryWatchViewModel.cpp | 1 - tests/RA_Integration.Tests.vcxproj | 1 - tests/RA_Integration.Tests.vcxproj.filters | 3 - 20 files changed, 1077 insertions(+), 459 deletions(-) delete mode 100644 src/RA_Json.cpp delete mode 100644 src/RA_Json.h create mode 100644 src/devkit/util/Json.cpp create mode 100644 src/devkit/util/Json.hh diff --git a/src/RA_Defs.h b/src/RA_Defs.h index 0cbdc228..a4192776 100644 --- a/src/RA_Defs.h +++ b/src/RA_Defs.h @@ -27,7 +27,6 @@ #else #include "util\Log.hh" -#include "RA_Json.h" // RA-Only using namespace std::string_literals; diff --git a/src/RA_Integration.vcxproj b/src/RA_Integration.vcxproj index fbdecff4..ccf61870 100644 --- a/src/RA_Integration.vcxproj +++ b/src/RA_Integration.vcxproj @@ -84,7 +84,6 @@ - diff --git a/src/RA_Integration.vcxproj.filters b/src/RA_Integration.vcxproj.filters index 66152c94..3dc3891c 100644 --- a/src/RA_Integration.vcxproj.filters +++ b/src/RA_Integration.vcxproj.filters @@ -87,9 +87,6 @@ UI\Win32 - - Services - UI\ViewModels diff --git a/src/RA_Json.cpp b/src/RA_Json.cpp deleted file mode 100644 index c4d6f638..00000000 --- a/src/RA_Json.cpp +++ /dev/null @@ -1,66 +0,0 @@ -#include "RA_Json.h" - -#include "services\impl\FileTextReader.hh" -#include "services\impl\FileTextWriter.hh" -#include "services\impl\StringTextReader.hh" -#include "services\impl\StringTextWriter.hh" - -_Use_decl_annotations_ -bool LoadDocument(rapidjson::Document& doc, ra::services::TextReader& reader) -{ - auto* pFileTextReader = dynamic_cast(&reader); - if (pFileTextReader != nullptr) - { - auto& iFile = pFileTextReader->GetFStream(); - if (!iFile.is_open()) - return false; - - rapidjson::IStreamWrapper iStreamWrapper(iFile); - doc.ParseStream(iStreamWrapper); - } - else - { - auto* pStringTextReader = dynamic_cast(&reader); - if (pStringTextReader != nullptr) - { - doc.Parse(pStringTextReader->GetString()); - } - else - { - assert(!"Unsupported TextReader"); - return false; - } - } - - return !doc.HasParseError(); -} - -bool SaveDocument(_In_ const rapidjson::Document& doc, ra::services::TextWriter& writer) -{ - auto* pFileTextWriter = dynamic_cast(&writer); - if (pFileTextWriter != nullptr) - { - auto& oFile = pFileTextWriter->GetFStream(); - if (!oFile.is_open()) - return false; - - rapidjson::OStreamWrapper oStreamWrapper(oFile); - rapidjson::Writer oStreamWriter(oStreamWrapper); - return doc.Accept(oStreamWriter); - } - - auto* pStringTextWriter = dynamic_cast(&writer); - if (pStringTextWriter != nullptr) - { - rapidjson::StringBuffer oStringBuffer; - rapidjson::Writer oStreamWriter(oStringBuffer); - if (!doc.Accept(oStreamWriter)) - return false; - - pStringTextWriter->GetString().assign(oStringBuffer.GetString()); - return true; - } - - assert(!"Unsupported TextWriter"); - return false; -} diff --git a/src/RA_Json.h b/src/RA_Json.h deleted file mode 100644 index fab8c279..00000000 --- a/src/RA_Json.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef RA_JSON_H -#define RA_JSON_H -#pragma once - -#include "services\TextReader.hh" -#include "services\TextWriter.hh" - -_Success_(return) -_NODISCARD bool LoadDocument(_Out_ rapidjson::Document& doc, _In_ ra::services::TextReader& reader); -bool SaveDocument(_In_ const rapidjson::Document& doc, ra::services::TextWriter& writer); - -#endif // !RA_JSON_H diff --git a/src/api/impl/ConnectedServer.cpp b/src/api/impl/ConnectedServer.cpp index 96b1c309..9eb71b15 100644 --- a/src/api/impl/ConnectedServer.cpp +++ b/src/api/impl/ConnectedServer.cpp @@ -1,28 +1,19 @@ #include "ConnectedServer.hh" -#include "DisconnectedServer.hh" -#include "RA_Defs.h" +#include "util\Json.hh" #include "util\Log.hh" -#include "RA_md5factory.h" - #include "context\UserContext.hh" #include "services\Http.hh" #include "services\IFileSystem.hh" #include "services\IHttpRequester.hh" -#include "services\ILocalStorage.hh" #include "services\ServiceLocator.hh" -#include - -#include - #include // for parsing cached patchdata response #include #include #include -#include namespace ra { namespace api { @@ -56,12 +47,10 @@ _NODISCARD static bool HandleHttpError(_In_ const ra::services::Http::StatusCode _NODISCARD static bool GetJson([[maybe_unused]] _In_ const char* sApiName, _In_ const ra::services::Http::Response& httpResponse, - _Inout_ ApiResponseBase& pResponse, _Out_ rapidjson::Document& pDocument) + _Inout_ ApiResponseBase& pResponse, _Out_ ra::util::Json::Reader& pJson) { if (httpResponse.Content().empty()) { - pDocument.SetArray(); - if (!HandleHttpError(httpResponse.StatusCode(), pResponse)) { pResponse.ErrorMessage = "Empty JSON response"; @@ -74,8 +63,7 @@ _NODISCARD static bool GetJson([[maybe_unused]] _In_ const char* sApiName, RA_LOG_INFO("-- %s Response: %s", sApiName, httpResponse.Content()); - pDocument.Parse(httpResponse.Content()); - if (pDocument.HasParseError()) + if (!pJson.Parse(httpResponse.Content())) { if (HandleHttpError(httpResponse.StatusCode(), pResponse)) { @@ -87,7 +75,7 @@ _NODISCARD static bool GetJson([[maybe_unused]] _In_ const char* sApiName, pResponse.Result = ApiResult::Error; - if (pDocument.GetParseError() == rapidjson::kParseErrorValueInvalid && pDocument.GetErrorOffset() == 0) + if (pJson.GetParseErrorOffset() == 0) { // server did not return JSON, check for HTML if (ra::util::String::StartsWith(httpResponse.Content(), "")) @@ -123,15 +111,15 @@ _NODISCARD static bool GetJson([[maybe_unused]] _In_ const char* sApiName, if (pResponse.ErrorMessage.empty()) { pResponse.ErrorMessage = - ra::util::String::Printf("JSON Parse Error: %s (at %zu)", GetParseError_En(pDocument.GetParseError()), pDocument.GetErrorOffset()); + ra::util::String::Printf("JSON Parse Error: %s (at %zu)", pJson.GetParseError(), pJson.GetParseErrorOffset()); } return false; } - if (pDocument.HasMember("Error")) + std::string sError; + if (pJson.TryGetString("Error", pResponse.ErrorMessage)) { - pResponse.ErrorMessage = pDocument["Error"].GetString(); if (httpResponse.StatusCode() == ra::services::Http::StatusCode::TooManyRequests) { pResponse.Result = ApiResult::Incomplete; @@ -146,7 +134,7 @@ _NODISCARD static bool GetJson([[maybe_unused]] _In_ const char* sApiName, } } - if (pDocument.HasMember("Success") && !pDocument["Success"].GetBool()) + if (pJson.GetBoolean("Success", true) == false) { pResponse.Result = ApiResult::Failed; RA_LOG_ERR("-- %s Error: Success=false", sApiName); @@ -162,27 +150,6 @@ _NODISCARD static bool GetJson([[maybe_unused]] _In_ const char* sApiName, return true; } -static void GetRequiredJsonField(_Out_ std::string& sValue, _In_ const rapidjson::Value& pDocument, - _In_ const char* const sField, _Inout_ ApiResponseBase& response) -{ - if (!pDocument.HasMember(sField)) - { - sValue.clear(); - - response.Result = ApiResult::Error; - if (response.ErrorMessage.empty()) - response.ErrorMessage = ra::util::String::Printf("%s not found in response", sField); - } - else - { - auto& pField = pDocument[sField]; - if (pField.IsString()) - sValue = pField.GetString(); - else - sValue.clear(); - } -} - static bool DoRequestWithoutLog(const rc_api_request_t& api_request, _UNUSED const char* sApiName, ra::services::Http::Response& pHttpResponse, ApiResponseBase& pResponse) { ra::services::Http::Request httpRequest(api_request.url); @@ -326,7 +293,7 @@ static bool ValidateResponse(int nResult, const rc_api_response_t& api_response, } static bool DoUpload(const std::string& sHost, const char* _RESTRICT sApiName, const char* _RESTRICT sRequestName, - const std::wstring& sFilePath, ApiResponseBase& pResponse, rapidjson::Document& document) + const std::wstring& sFilePath, ApiResponseBase& pResponse, ra::util::Json::Reader& pJson) { const auto& pFileSystem = ra::services::ServiceLocator::Get(); const auto nFileSize = pFileSystem.GetFileSize(sFilePath); @@ -391,7 +358,7 @@ static bool DoUpload(const std::string& sHost, const char* _RESTRICT sApiName, c httpRequest.SetPostData(sPostData); const auto httpResponse = httpRequest.Call(); - return GetJson(sApiName, httpResponse, pResponse, document); + return GetJson(sApiName, httpResponse, pResponse, pJson); } // === APIs === @@ -728,12 +695,13 @@ UpdateRichPresence::Response ConnectedServer::UpdateRichPresence(const UpdateRic UploadBadge::Response ConnectedServer::UploadBadge(const UploadBadge::Request& request) { UploadBadge::Response response; - rapidjson::Document document; + ra::util::Json::Reader pJson; std::string sPostData; - if (DoUpload(m_sHost, UploadBadge::Name(), "uploadbadgeimage", request.ImageFilePath, response, document)) + if (DoUpload(m_sHost, UploadBadge::Name(), "uploadbadgeimage", request.ImageFilePath, response, pJson)) { - if (!document.HasMember("Response")) + ra::util::Json::Reader::Node pResponse; + if (!pJson.TryGetObject("Response", pResponse)) { if (response.Result == ApiResult::None) response.Result = ApiResult::Error; @@ -744,7 +712,13 @@ UploadBadge::Response ConnectedServer::UploadBadge(const UploadBadge::Request& r else { response.Result = ApiResult::Success; - GetRequiredJsonField(response.BadgeId, document["Response"], "BadgeIter", response); + + if (!pResponse.TryGetString("BadgeIter", response.BadgeId)) + { + response.Result = ApiResult::Error; + if (response.ErrorMessage.empty()) + response.ErrorMessage = ra::util::String::Printf("%s not found in response", "BadgeIter"); + } } } diff --git a/src/devkit/RADevKit.vcxproj b/src/devkit/RADevKit.vcxproj index e9d7ba5d..5a531e75 100644 --- a/src/devkit/RADevKit.vcxproj +++ b/src/devkit/RADevKit.vcxproj @@ -84,6 +84,7 @@ + @@ -144,6 +145,7 @@ + diff --git a/src/devkit/RADevKit.vcxproj.filters b/src/devkit/RADevKit.vcxproj.filters index 8caaac06..8b38dcef 100644 --- a/src/devkit/RADevKit.vcxproj.filters +++ b/src/devkit/RADevKit.vcxproj.filters @@ -222,6 +222,9 @@ services + + util + @@ -317,5 +320,8 @@ data\models + + util + \ No newline at end of file diff --git a/src/devkit/util/Json.cpp b/src/devkit/util/Json.cpp new file mode 100644 index 00000000..cab8ac87 --- /dev/null +++ b/src/devkit/util/Json.cpp @@ -0,0 +1,489 @@ +#include "Json.hh" + +#include "services/impl/FileTextReader.hh" +#include "services/impl/FileTextWriter.hh" +#include "services/impl/StringTextReader.hh" +#include "services/impl/StringTextWriter.hh" + +#pragma warning(push, 0) // disable all warnings for these headers +#pragma warning(disable: ALL_CODE_ANALYSIS_WARNINGS) +#pragma warning(disable: 6313) +#pragma warning(disable: 26429) +#pragma warning(disable: 26432) +#pragma warning(disable: 26440) +#pragma warning(disable: 26446) +#pragma warning(disable: 26447) +#pragma warning(disable: 26451) +#pragma warning(disable: 26455) +#pragma warning(disable: 26457) +#pragma warning(disable: 26460) +#pragma warning(disable: 26461) +#pragma warning(disable: 26462) +#pragma warning(disable: 26471) +#pragma warning(disable: 26472) +#pragma warning(disable: 26473) +#pragma warning(disable: 26475) +#pragma warning(disable: 26477) +#pragma warning(disable: 26490) +#pragma warning(disable: 26492) +#pragma warning(disable: 26493) +#pragma warning(disable: 26494) +#pragma warning(disable: 26495) +#pragma warning(disable: 26496) +#pragma warning(disable: 26497) +#pragma warning(disable: 26814) +#pragma warning(disable: 33010) + +#define RAPIDJSON_HAS_STDSTRING 1 +#define RAPIDJSON_NOMEMBERITERATORCLASS 1 +#include +#include +#include +#include +#include // has stringbuffer.h + +#pragma warning(pop) + +namespace ra { +namespace util { + +class Json::Impl : public rapidjson::Document +{ +}; + +Json::Reader::Reader() noexcept +{ + // required because of forward declaration of Impl +} + +Json::Reader::~Reader() +{ + // required because of forward declaration of Impl +} + +bool Json::Reader::Parse(ra::services::TextReader& pReader) +{ + m_pDocument = std::make_unique(); + + auto* pFileTextReader = dynamic_cast(&pReader); + if (pFileTextReader != nullptr) + { + auto& iFile = pFileTextReader->GetFStream(); + if (!iFile.is_open()) + return false; + + rapidjson::IStreamWrapper iStreamWrapper(iFile); + m_pDocument->ParseStream(iStreamWrapper); + } + else + { + auto* pStringTextReader = dynamic_cast(&pReader); + if (pStringTextReader != nullptr) + { + m_pDocument->Parse(pStringTextReader->GetString()); + } + else + { + assert(!"Unsupported TextReader"); + return false; + } + } + + return !m_pDocument->HasParseError(); +} + +bool Json::Reader::Parse(const std::string& sJson) +{ + m_pDocument = std::make_unique(); + m_pDocument->Parse(sJson); + + return !m_pDocument->HasParseError(); +} + +std::string Json::Reader::GetParseError() const +{ + return GetParseError_En(m_pDocument->GetParseError()); +} + +size_t Json::Reader::GetParseErrorOffset() const +{ + return m_pDocument->GetErrorOffset(); +} + +std::string Json::Reader::GetString(const std::string& sFieldName, const std::string& sDefaultValue) const +{ + std::string sValue; + if (!TryGetString(sFieldName, sValue)) + return sDefaultValue; + + return sValue; +} + +bool Json::Reader::TryGetString(const std::string& sFieldName, std::string& sValue) const +{ + const auto* pField = m_pDocument->FindMember(sFieldName); + if (pField == m_pDocument->MemberEnd()) + return false; + + if (!pField->value.IsString()) + return false; + + sValue.assign(pField->value.GetString(), pField->value.GetStringLength()); + return true; +} + +int Json::Reader::GetInteger(const std::string& sFieldName, int nDefaultValue) const +{ + int nValue; + if (!TryGetInteger(sFieldName, nValue)) + return nDefaultValue; + + return nValue; +} + +bool Json::Reader::TryGetInteger(const std::string& sFieldName, int& nValue) const +{ + const auto* pField = m_pDocument->FindMember(sFieldName); + if (pField == m_pDocument->MemberEnd()) + return false; + + if (!pField->value.IsInt()) + return false; + + nValue = pField->value.GetInt(); + return true; +} + +bool Json::Reader::GetBoolean(const std::string& sFieldName, bool bDefaultValue) const +{ + bool bValue; + if (!TryGetBoolean(sFieldName, bValue)) + return bDefaultValue; + + return bValue; +} + +bool Json::Reader::TryGetBoolean(const std::string& sFieldName, bool& bValue) const +{ + const auto* pField = m_pDocument->FindMember(sFieldName); + if (pField == m_pDocument->MemberEnd()) + return false; + + if (!pField->value.IsBool()) + return false; + + bValue = pField->value.GetBool(); + return true; +} + +bool Json::Reader::TryGetObject(const std::string& sFieldName, Node& pObject) +{ + const auto* pField = m_pDocument->FindMember(sFieldName); + if (pField == m_pDocument->MemberEnd()) + return false; + + if (!pField->value.IsObject()) + return false; + + pObject.m_pNode = &pField->value; + return true; +} + +bool Json::Reader::TryGetObjectArray(const std::string& sFieldName, std::vector& vObjects) +{ + const auto* pField = m_pDocument->FindMember(sFieldName); + if (pField == m_pDocument->MemberEnd()) + return false; + + if (!pField->value.IsArray()) + return false; + + for (const auto& pObject : pField->value.GetArray()) + { + auto& pNode = vObjects.emplace_back(); + pNode.m_pNode = &pObject; + } + + return true; +} + +size_t Json::Reader::GetKeys(std::vector& vKeys) const +{ + for (rapidjson::Value::ConstMemberIterator iter = m_pDocument->MemberBegin(); iter != m_pDocument->MemberEnd(); ++iter) + vKeys.push_back(iter->name.GetString()); + + return vKeys.size(); +} + +std::string Json::Reader::Node::GetString(const std::string& sFieldName, const std::string& sDefaultValue) const +{ + std::string sValue; + if (!TryGetString(sFieldName, sValue)) + return sDefaultValue; + + return sValue; +} + +bool Json::Reader::Node::TryGetString(const std::string& sFieldName, std::string& sValue) const +{ + const auto* pNode = static_cast(m_pNode); + Expects(pNode != nullptr); + + const auto* pField = pNode->FindMember(sFieldName); + if (pField == pNode->MemberEnd()) + return false; + + if (!pField->value.IsString()) + return false; + + sValue.assign(pField->value.GetString(), pField->value.GetStringLength()); + return true; +} + +int Json::Reader::Node::GetInteger(const std::string& sFieldName, int nDefaultValue) const +{ + int nValue; + if (!TryGetInteger(sFieldName, nValue)) + return nDefaultValue; + + return nValue; +} + +bool Json::Reader::Node::TryGetInteger(const std::string& sFieldName, int& nValue) const +{ + const auto* pNode = static_cast(m_pNode); + Expects(pNode != nullptr); + + const auto* pField = pNode->FindMember(sFieldName); + if (pField == pNode->MemberEnd()) + return false; + + if (!pField->value.IsInt()) + return false; + + nValue = pField->value.GetInt(); + return true; +} + +bool Json::Reader::Node::GetBoolean(const std::string& sFieldName, bool bDefaultValue) const +{ + bool bValue; + if (!TryGetBoolean(sFieldName, bValue)) + return bDefaultValue; + + return bValue; +} + +bool Json::Reader::Node::TryGetBoolean(const std::string& sFieldName, bool& bValue) const +{ + const auto* pNode = static_cast(m_pNode); + Expects(pNode != nullptr); + + const auto* pField = pNode->FindMember(sFieldName); + if (pField == pNode->MemberEnd()) + return false; + + if (!pField->value.IsBool()) + return false; + + bValue = pField->value.GetBool(); + return true; +} + +bool Json::Reader::Node::TryGetObject(const std::string& sFieldName, Node& pObject) +{ + const auto* pNode = static_cast(m_pNode); + Expects(pNode != nullptr); + + const auto* pField = pNode->FindMember(sFieldName); + if (pField == pNode->MemberEnd()) + return false; + + if (!pField->value.IsObject()) + return false; + + pObject.m_pNode = &pField->value; + return true; +} + +size_t Json::Reader::Node::GetKeys(std::vector& vKeys) const +{ + const auto* pNode = static_cast(m_pNode); + Expects(pNode != nullptr); + + for (rapidjson::Value::ConstMemberIterator iter = pNode->MemberBegin(); iter != pNode->MemberEnd(); ++iter) + vKeys.push_back(iter->name.GetString()); + + return vKeys.size(); +} + +GSL_SUPPRESS_F6 +Json::Writer::Writer() noexcept +{ + // required because of forward declaration of Impl + m_pDocument = std::make_unique(); + m_pDocument->SetObject(); +} + +Json::Writer::~Writer() +{ + // required because of forward declaration of Impl +} + +bool Json::Writer::Save(ra::services::TextWriter& pWriter) +{ + auto* pFileTextWriter = dynamic_cast(&pWriter); + if (pFileTextWriter != nullptr) + { + auto& oFile = pFileTextWriter->GetFStream(); + if (!oFile.is_open()) + return false; + + rapidjson::OStreamWrapper oStreamWrapper(oFile); + rapidjson::Writer oStreamWriter(oStreamWrapper); + return m_pDocument->Accept(oStreamWriter); + } + + auto* pStringTextWriter = dynamic_cast(&pWriter); + if (pStringTextWriter != nullptr) + { + rapidjson::StringBuffer oStringBuffer; + rapidjson::Writer oStreamWriter(oStringBuffer); + if (!m_pDocument->Accept(oStreamWriter)) + return false; + + pStringTextWriter->GetString().assign(oStringBuffer.GetString()); + return true; + } + + assert(!"Unsupported TextWriter"); + return false; +} + +void Json::Writer::SetString(const std::string& sFieldName, const std::string& sValue) +{ + auto& a = m_pDocument->GetAllocator(); + m_pDocument->AddMember(rapidjson::Value(sFieldName, a), rapidjson::Value(sValue, a), a); +} + +void Json::Writer::SetInteger(const std::string& sFieldName, int nValue) +{ + auto& a = m_pDocument->GetAllocator(); + rapidjson::Value pKey(sFieldName, a); + m_pDocument->AddMember(pKey, nValue, a); +} + +void Json::Writer::SetBoolean(const std::string& sFieldName, bool bValue) +{ + auto& a = m_pDocument->GetAllocator(); + rapidjson::Value pKey(sFieldName, a); + m_pDocument->AddMember(pKey, bValue, a); +} + +Json::Writer::Node Json::Writer::SetObject(const std::string& sFieldName) +{ + rapidjson::Value pObject(rapidjson::kObjectType); + + auto& a = m_pDocument->GetAllocator(); + rapidjson::Value pKey(sFieldName, a); + m_pDocument->AddMember(pKey, pObject.Move(), a); + + Node node; + node.m_pOwner = this; + node.m_pNode = &m_pDocument->FindMember(sFieldName)->value; + return node; +} + +Json::Writer::Node Json::Writer::SetObjectArray(const std::string& sFieldName) +{ + rapidjson::Value pObject(rapidjson::kArrayType); + + auto& a = m_pDocument->GetAllocator(); + rapidjson::Value pKey(sFieldName, a); + m_pDocument->AddMember(pKey, pObject.Move(), a); + + Node node; + node.m_pOwner = this; + node.m_pNode = &m_pDocument->FindMember(sFieldName)->value; + return node; +} + +void Json::Writer::Node::SetString(const std::string& sFieldName, const std::string& sValue) +{ + auto* pNode = static_cast(m_pNode); + Expects(pNode != nullptr); + + auto& a = m_pOwner->m_pDocument->GetAllocator(); + pNode->AddMember(rapidjson::Value(sFieldName, a), rapidjson::Value(sValue, a), a); +} + +void Json::Writer::Node::SetInteger(const std::string& sFieldName, int nValue) +{ + auto* pNode = static_cast(m_pNode); + Expects(pNode != nullptr); + + auto& a = m_pOwner->m_pDocument->GetAllocator(); + rapidjson::Value pKey(sFieldName, a); + pNode->AddMember(pKey, nValue, a); +} + +void Json::Writer::Node::SetBoolean(const std::string& sFieldName, bool bValue) +{ + auto* pNode = static_cast(m_pNode); + Expects(pNode != nullptr); + + auto& a = m_pOwner->m_pDocument->GetAllocator(); + rapidjson::Value pKey(sFieldName, a); + pNode->AddMember(pKey, bValue, a); +} + +Json::Writer::Node Json::Writer::Node::SetObject(const std::string& sFieldName) +{ + auto* pNode = static_cast(m_pNode); + Expects(pNode != nullptr); + + rapidjson::Value pObject(rapidjson::kObjectType); + + auto& a = m_pOwner->m_pDocument->GetAllocator(); + rapidjson::Value pKey(sFieldName, a); + pNode->AddMember(pKey, pObject.Move(), a); + + Node node; + node.m_pOwner = m_pOwner; + node.m_pNode = &pNode->FindMember(sFieldName)->value; + return node; +} + +Json::Writer::Node Json::Writer::Node::SetObjectArray(const std::string& sFieldName) +{ + auto* pNode = static_cast(m_pNode); + Expects(pNode != nullptr); + + rapidjson::Value pObject(rapidjson::kArrayType); + + auto& a = m_pOwner->m_pDocument->GetAllocator(); + rapidjson::Value pKey(sFieldName, a); + pNode->AddMember(pKey, pObject.Move(), a); + + Node node; + node.m_pOwner = m_pOwner; + node.m_pNode = &pNode->FindMember(sFieldName)->value; + return node; +} + +Json::Writer::Node Json::Writer::Node::AppendObject() +{ + auto* pNode = static_cast(m_pNode); + Expects(pNode != nullptr); + + rapidjson::Value pObject(rapidjson::kObjectType); + pNode->PushBack(pObject, m_pOwner->m_pDocument->GetAllocator()); + + Node node; + node.m_pOwner = m_pOwner; + node.m_pNode = &(*pNode)[pNode->Size() - 1]; + return node; +} + +} // namespace util +} // namespace ra diff --git a/src/devkit/util/Json.hh b/src/devkit/util/Json.hh new file mode 100644 index 00000000..06af07c5 --- /dev/null +++ b/src/devkit/util/Json.hh @@ -0,0 +1,308 @@ +#ifndef RA_UTIL_JSON_HH +#define RA_UTIL_JSON_HH +#pragma once + +#include +#include +#include + +#include "services/TextReader.hh" +#include "services/TextWriter.hh" + +#undef GetObject + +namespace ra { +namespace util { + +class Json +{ +private: + class Impl; + +public: + class Reader + { + public: + Reader() noexcept; + ~Reader(); + Reader(const Reader& other) noexcept = delete; + Reader& operator=(const Reader&) noexcept = delete; + Reader(Reader&& other) noexcept = delete; + Reader& operator=(Reader&&) noexcept = delete; + + /// + /// Parses the JSON data. + /// + /// true if parsing was successful, false if not. + bool Parse(ra::services::TextReader& pReader); + + /// + /// Parses the JSON data. + /// + /// true if parsing was successful, false if not. + bool Parse(const std::string& sJson); + + /// + /// Gets a human-readable error message describing why returned false. + /// + std::string GetParseError() const; + + /// + /// Gets the offset within the JSON data where the parse error occured. + /// + size_t GetParseErrorOffset() const; + + /// + /// Gets the string data from the provided field. + /// + /// Field to query. + /// Value to return if the field is not found, or is not a string. + std::string GetString(const std::string& sFieldName, const std::string& sDefaultValue = "") const; + + /// + /// Tries to gets the string data from the provided field. + /// + /// Field to query. + /// + /// String to populate with the data from the specified field. + /// It will not be modified if the field is not found or is not a string. + /// + /// true if the value was populated. + bool TryGetString(const std::string& sFieldName, std::string& sValue) const; + + /// + /// Gets the integer data from the provided field. + /// + /// Field to query. + /// Value to return if the field is not found, or is not an integer. + int GetInteger(const std::string& sFieldName, int nDefaultValue = 0) const; + + /// + /// Tries to gets the integer data from the provided field. + /// + /// Field to query. + /// + /// Integer to populate with the data from the specified field. + /// It will not be modified if the field is not found or is not an integer. + /// + /// true if the value was populated. + bool TryGetInteger(const std::string& sFieldName, int& nValue) const; + + /// + /// Gets the integer data from the provided field. + /// + /// Field to query. + /// Value to return if the field is not found, or is not an integer. + bool GetBoolean(const std::string& sFieldName, bool bDefaultValue = false) const; + + /// + /// Tries to gets the integer data from the provided field. + /// + /// Field to query. + /// + /// Boolean to populate with the data from the specified field. + /// It will not be modified if the field is not found or is not a boolean. + /// + /// true if the value was populated. + bool TryGetBoolean(const std::string& sFieldName, bool& bValue) const; + + /// + /// Gets the available field names. + /// + /// Number of fields found. + size_t GetKeys(std::vector& vKeys) const; + + class Node + { + public: + /// + /// Gets the string data from the provided field. + /// + /// Field to query. + /// Value to return if the field is not found, or is not a string. + std::string GetString(const std::string& sFieldName, const std::string& sDefaultValue = "") const; + + /// + /// Tries to gets the string data from the provided field. + /// + /// Field to query. + /// + /// String to populate with the data from the specified field. + /// It will not be modified if the field is not found or is not a string. + /// + /// true if the value was populated. + bool TryGetString(const std::string& sFieldName, std::string& sValue) const; + + /// + /// Gets the integer data from the provided field. + /// + /// Field to query. + /// Value to return if the field is not found, or is not an integer. + int GetInteger(const std::string& sFieldName, int nDefaultValue = 0) const; + + /// + /// Tries to gets the integer data from the provided field. + /// + /// Field to query. + /// + /// Integer to populate with the data from the specified field. + /// It will not be modified if the field is not found or is not an integer. + /// + /// true if the value was populated. + bool TryGetInteger(const std::string& sFieldName, int& nValue) const; + + /// + /// Gets the integer data from the provided field. + /// + /// Field to query. + /// Value to return if the field is not found, or is not an integer. + bool GetBoolean(const std::string& sFieldName, bool bDefaultValue = false) const; + + /// + /// Tries to gets the integer data from the provided field. + /// + /// Field to query. + /// + /// Boolean to populate with the data from the specified field. + /// It will not be modified if the field is not found or is not a boolean. + /// + /// true if the value was populated. + bool TryGetBoolean(const std::string& sFieldName, bool& bValue) const; + + /// + /// Tries to gets the object data from the provided field. + /// + /// Field to query. + /// + /// Node to populate with the data from the specified field. + /// It will not be modified if the field is not found or is not an object. + /// + /// true if the value was populated. + bool TryGetObject(const std::string& sFieldName, Node& pObject); + + /// + /// Gets the available field names. + /// + /// Number of fields found. + size_t GetKeys(std::vector& vKeys) const; + + private: + friend class Reader; + const void* m_pNode; + }; + + /// + /// Tries to gets the object data from the provided field. + /// + /// Field to query. + /// + /// Node to populate with the data from the specified field. + /// It will not be modified if the field is not found or is not an object. + /// + /// true if the value was populated. + bool TryGetObject(const std::string& sFieldName, Node& pObject); + + /// + /// Tries to gets an array of object data from the provided field. + /// + /// Field to query. + /// + /// Array to populate with the data from the specified field. + /// It will not be modified if the field is not found or is not an array of objects. + /// + /// true if the value was populated. + bool TryGetObjectArray(const std::string& sFieldName, std::vector& vObjects); + + private: + std::unique_ptr m_pDocument; + }; + + class Writer + { + public: + Writer() noexcept; + ~Writer(); + Writer(const Writer& other) noexcept = delete; + Writer& operator=(const Writer&) noexcept = delete; + Writer(Writer&& other) noexcept = delete; + Writer& operator=(Writer&&) noexcept = delete; + + /// + /// Writes the constructed JSON data. + /// + /// true if writing was successful, false if not. + bool Save(ra::services::TextWriter& pWriter); + + /// + /// Sets a field to a string value. + /// + void SetString(const std::string& sFieldName, const std::string& sValue); + + /// + /// Sets a field to an integer value. + /// + void SetInteger(const std::string& sFieldName, int nValue); + + /// + /// Sets a field to a boolean value. + /// + void SetBoolean(const std::string& sFieldName, bool bValue); + + class Node + { + public: + /// + /// Sets a field to a string value. + /// + void SetString(const std::string& sFieldName, const std::string& sValue); + + /// + /// Sets a field to an integer value. + /// + void SetInteger(const std::string& sFieldName, int nValue); + + /// + /// Sets a field to a boolean value. + /// + void SetBoolean(const std::string& sFieldName, bool bValue); + + /// + /// Returns an object to set fields in a nested object. + /// + Node SetObject(const std::string& sFieldName); + + /// + /// Returns an object to hold an array of objects. + /// + Node SetObjectArray(const std::string& sFieldName); + + /// + /// Returns an object appended to an array of objects. + /// + Node AppendObject(); + + private: + friend class Writer; + void* m_pNode; + Writer* m_pOwner; + }; + + /// + /// Sets a field to a string value. + /// + Node SetObject(const std::string& sFieldName); + + /// + /// Returns an object to hold an array of objects. + /// + Node SetObjectArray(const std::string& sFieldName); + + private: + std::unique_ptr m_pDocument; + }; +}; + +} // namespace util +} // namespace ra + +#endif // !RA_UTIL_JSON_HH diff --git a/src/pch.h b/src/pch.h index 6e310bb3..6ef6c57a 100644 --- a/src/pch.h +++ b/src/pch.h @@ -60,15 +60,6 @@ #include "pch_cppcorecheck_suppressions.h" #include "pch_microsoft_suppressions.h" -/* RapidJSON Stuff */ -#define RAPIDJSON_HAS_STDSTRING 1 -#define RAPIDJSON_NOMEMBERITERATORCLASS 1 -#include // has reader.h -#include -#include -#include -#include // has stringbuffer.h - /* gsl stuff */ #define GSL_THROW_ON_CONTRACT_VIOLATION #include diff --git a/src/services/AchievementRuntime.cpp b/src/services/AchievementRuntime.cpp index 15f48b51..d4812a38 100644 --- a/src/services/AchievementRuntime.cpp +++ b/src/services/AchievementRuntime.cpp @@ -3,9 +3,9 @@ #include "AchievementRuntimeExports.hh" #include "Exports.hh" #include "RA_Defs.h" -#include "RA_Json.h" #include "RA_Resource.h" #include "util\EnumOps.hh" +#include "util\Json.hh" #include "util\Log.hh" #include "util\Strings.hh" @@ -502,14 +502,17 @@ static void ProcessPatchData(const rc_api_server_response_t* server_response, auto pOldData = pLocalStorage.ReadText(StorageItemType::GameData, std::to_wstring(pGameContext.ActiveGameId())); if (pOldData != nullptr) { - rapidjson::Document pDocument; - if (LoadDocument(pDocument, *pOldData) && pDocument.HasMember("RichPresencePatch") && - pDocument["RichPresencePatch"].IsString()) + ra::util::Json::Reader pJson; + if (pJson.Parse(*pOldData)) { - auto* pRichPresence = pGameContext.Assets().FindRichPresence(); - Expects(pRichPresence != nullptr); - pRichPresence->SetScript(pDocument["RichPresencePatch"].GetString()); - pRichPresence->UpdateLocalCheckpoint(); + std::string sRichPresencePatch; + if (pJson.TryGetString("RichPresencePatch", sRichPresencePatch)) + { + auto* pRichPresence = pGameContext.Assets().FindRichPresence(); + Expects(pRichPresence != nullptr); + pRichPresence->SetScript(sRichPresencePatch); + pRichPresence->UpdateLocalCheckpoint(); + } } } diff --git a/src/services/impl/FileLocalStorage.cpp b/src/services/impl/FileLocalStorage.cpp index d47b65ee..e7326e29 100644 --- a/src/services/impl/FileLocalStorage.cpp +++ b/src/services/impl/FileLocalStorage.cpp @@ -1,6 +1,5 @@ #include "FileLocalStorage.hh" -#include "RA_Json.h" #include "util\Log.hh" #include "util\Strings.hh" diff --git a/src/services/impl/JsonFileConfiguration.cpp b/src/services/impl/JsonFileConfiguration.cpp index dab8b33e..07eff919 100644 --- a/src/services/impl/JsonFileConfiguration.cpp +++ b/src/services/impl/JsonFileConfiguration.cpp @@ -1,6 +1,6 @@ #include "JsonFileConfiguration.hh" -#include "RA_Json.h" +#include "util\Json.hh" #include "util\Log.hh" #include "util\Strings.hh" @@ -20,45 +20,41 @@ namespace services { namespace impl { static void ReadPopupLocation(JsonFileConfiguration& pConfiguration, ra::ui::viewmodels::Popup nPopup, - const rapidjson::Document& doc, const char* sFieldName, ra::ui::viewmodels::PopupLocation nDefaultLocation, bool bAllowCenter) + const ra::util::Json::Reader& doc, const char* sFieldName, ra::ui::viewmodels::PopupLocation nDefaultLocation, bool bAllowCenter) { ra::ui::viewmodels::PopupLocation nLocation = nDefaultLocation; - if (doc.HasMember(sFieldName)) + std::string sValue; + bool bValue; + + if (doc.TryGetString(sFieldName, sValue) && !sValue.empty()) { - const auto& pField = doc[sFieldName]; - if (pField.IsBool()) + if (sValue.at(0) == 'T') { - if (!pField.GetBool()) - nLocation = ra::ui::viewmodels::PopupLocation::None; + if (sValue == "TopLeft") + nLocation = ra::ui::viewmodels::PopupLocation::TopLeft; + else if (sValue == "TopRight") + nLocation = ra::ui::viewmodels::PopupLocation::TopRight; + else if (bAllowCenter && sValue == "TopMiddle") + nLocation = ra::ui::viewmodels::PopupLocation::TopMiddle; } - else if (pField.IsString()) + else if (sValue.at(0) == 'B') { - const char* pValue = pField.GetString(); - Expects(pValue != nullptr); - if (*pValue == 'T') - { - if (strcmp(pValue, "TopLeft") == 0) - nLocation = ra::ui::viewmodels::PopupLocation::TopLeft; - else if (strcmp(pValue, "TopRight") == 0) - nLocation = ra::ui::viewmodels::PopupLocation::TopRight; - else if (bAllowCenter && strcmp(pValue, "TopMiddle") == 0) - nLocation = ra::ui::viewmodels::PopupLocation::TopMiddle; - } - else if (*pValue == 'B') - { - if (strcmp(pValue, "BottomLeft") == 0) - nLocation = ra::ui::viewmodels::PopupLocation::BottomLeft; - else if (strcmp(pValue, "BottomRight") == 0) - nLocation = ra::ui::viewmodels::PopupLocation::BottomRight; - else if (bAllowCenter && strcmp(pValue, "BottomMiddle") == 0) - nLocation = ra::ui::viewmodels::PopupLocation::BottomMiddle; - } - else if (strcmp(pValue, "None") == 0) - { - nLocation = ra::ui::viewmodels::PopupLocation::None; - } + if (sValue == "BottomLeft") + nLocation = ra::ui::viewmodels::PopupLocation::BottomLeft; + else if (sValue == "BottomRight") + nLocation = ra::ui::viewmodels::PopupLocation::BottomRight; + else if (bAllowCenter && sValue == "BottomMiddle") + nLocation = ra::ui::viewmodels::PopupLocation::BottomMiddle; } + else if (sValue == "None") + { + nLocation = ra::ui::viewmodels::PopupLocation::None; + } + } + else if (doc.TryGetBoolean(sFieldName, bValue) && !bValue) + { + nLocation = ra::ui::viewmodels::PopupLocation::None; } pConfiguration.SetPopupLocation(nPopup, nLocation); @@ -93,66 +89,55 @@ bool JsonFileConfiguration::Load(const std::wstring& sFilename) if (pReader == nullptr) return false; - rapidjson::Document doc; - if (!LoadDocument(doc, *pReader)) + ra::util::Json::Reader pJson; + if (!pJson.Parse(*pReader)) return false; - if (doc.HasMember("Username")) - m_sUsername = doc["Username"].GetString(); - if (doc.HasMember("Token")) - m_sApiToken = doc["Token"].GetString(); - if (doc.HasMember("Hardcore Active")) - SetFeatureEnabled(Feature::Hardcore, doc["Hardcore Active"].GetBool()); - if (doc.HasMember("Non Hardcore Warning")) - SetFeatureEnabled(Feature::NonHardcoreWarning, doc["Non Hardcore Warning"].GetBool()); - if (doc.HasMember("Only Hardcore Unlocks")) - SetFeatureEnabled(Feature::OnlyHardcoreUnlocks, doc["Only Hardcore Unlocks"].GetBool()); - - ReadPopupLocation(*this, ra::ui::viewmodels::Popup::AchievementTriggered, doc, "Achievement Triggered Notification Display", ra::ui::viewmodels::PopupLocation::BottomLeft, true); - if (doc.HasMember("Achievement Triggered Screenshot")) - SetFeatureEnabled(Feature::AchievementTriggeredScreenshot, doc["Achievement Triggered Screenshot"].GetBool()); - ReadPopupLocation(*this, ra::ui::viewmodels::Popup::Mastery, doc, "Mastery Notification Display", ra::ui::viewmodels::PopupLocation::TopMiddle, true); - if (doc.HasMember("Mastery Screenshot")) - SetFeatureEnabled(Feature::MasteryNotificationScreenshot, doc["Mastery Screenshot"].GetBool()); - if (doc.HasMember("Screenshot Directory")) - SetScreenshotDirectory(ra::util::String::Widen(doc["Screenshot Directory"].GetString())); - - if (doc.HasMember("Leaderboards Active")) - SetFeatureEnabled(Feature::Leaderboards, doc["Leaderboards Active"].GetBool()); - ReadPopupLocation(*this, ra::ui::viewmodels::Popup::LeaderboardStarted, doc, "Leaderboard Notification Display", ra::ui::viewmodels::PopupLocation::BottomLeft, true); - ReadPopupLocation(*this, ra::ui::viewmodels::Popup::LeaderboardCanceled, doc, "Leaderboard Cancel Display", ra::ui::viewmodels::PopupLocation::BottomLeft, true); - ReadPopupLocation(*this, ra::ui::viewmodels::Popup::LeaderboardTracker, doc, "Leaderboard Counter Display", ra::ui::viewmodels::PopupLocation::BottomRight, true); - ReadPopupLocation(*this, ra::ui::viewmodels::Popup::LeaderboardScoreboard, doc, "Leaderboard Scoreboard Display", ra::ui::viewmodels::PopupLocation::BottomRight, false); - - ReadPopupLocation(*this, ra::ui::viewmodels::Popup::Challenge, doc, "Challenge Notification Display", ra::ui::viewmodels::PopupLocation::BottomRight, false); - ReadPopupLocation(*this, ra::ui::viewmodels::Popup::Progress, doc, "Progress Display", ra::ui::viewmodels::PopupLocation::BottomRight, false); - - ReadPopupLocation(*this, ra::ui::viewmodels::Popup::Message, doc, "Informational Notification Display", ra::ui::viewmodels::PopupLocation::BottomLeft, true); - - if (doc.HasMember("Prefer Decimal")) - SetFeatureEnabled(Feature::PreferDecimal, doc["Prefer Decimal"].GetBool()); - - if (doc.HasMember("Num Background Threads")) - m_nBackgroundThreads = doc["Num Background Threads"].GetUint(); - - if (doc.HasMember("Window Positions")) + m_sUsername = pJson.GetString("Username"); + m_sApiToken = pJson.GetString("Token"); + SetFeatureEnabled(Feature::Hardcore, pJson.GetBoolean("Hardcore Active", true)); + SetFeatureEnabled(Feature::NonHardcoreWarning, pJson.GetBoolean("Non Hardcore Warning", false)); + SetFeatureEnabled(Feature::OnlyHardcoreUnlocks, pJson.GetBoolean("Only Hardcore Unlocks", false)); + + ReadPopupLocation(*this, ra::ui::viewmodels::Popup::AchievementTriggered, pJson, "Achievement Triggered Notification Display", ra::ui::viewmodels::PopupLocation::BottomLeft, true); + SetFeatureEnabled(Feature::AchievementTriggeredScreenshot, pJson.GetBoolean("Achievement Triggered Screenshot", false)); + ReadPopupLocation(*this, ra::ui::viewmodels::Popup::Mastery, pJson, "Mastery Notification Display", ra::ui::viewmodels::PopupLocation::TopMiddle, true); + SetFeatureEnabled(Feature::MasteryNotificationScreenshot, pJson.GetBoolean("Mastery Screenshot", false)); + + std::string sScreenShotDirectory; + if (pJson.TryGetString("Screenshot Directory", sScreenShotDirectory)) + SetScreenshotDirectory(ra::util::String::Widen(sScreenShotDirectory)); + + SetFeatureEnabled(Feature::Leaderboards, pJson.GetBoolean("Leaderboards Active", true)); + ReadPopupLocation(*this, ra::ui::viewmodels::Popup::LeaderboardStarted, pJson, "Leaderboard Notification Display", ra::ui::viewmodels::PopupLocation::BottomLeft, true); + ReadPopupLocation(*this, ra::ui::viewmodels::Popup::LeaderboardCanceled, pJson, "Leaderboard Cancel Display", ra::ui::viewmodels::PopupLocation::BottomLeft, true); + ReadPopupLocation(*this, ra::ui::viewmodels::Popup::LeaderboardTracker, pJson, "Leaderboard Counter Display", ra::ui::viewmodels::PopupLocation::BottomRight, true); + ReadPopupLocation(*this, ra::ui::viewmodels::Popup::LeaderboardScoreboard, pJson, "Leaderboard Scoreboard Display", ra::ui::viewmodels::PopupLocation::BottomRight, false); + + ReadPopupLocation(*this, ra::ui::viewmodels::Popup::Challenge, pJson, "Challenge Notification Display", ra::ui::viewmodels::PopupLocation::BottomRight, false); + ReadPopupLocation(*this, ra::ui::viewmodels::Popup::Progress, pJson, "Progress Display", ra::ui::viewmodels::PopupLocation::BottomRight, false); + + ReadPopupLocation(*this, ra::ui::viewmodels::Popup::Message, pJson, "Informational Notification Display", ra::ui::viewmodels::PopupLocation::BottomLeft, true); + + SetFeatureEnabled(Feature::PreferDecimal, pJson.GetBoolean("Prefer Decimal", false)); + + m_nBackgroundThreads = pJson.GetInteger("Num Background Threads", 4); + + ra::util::Json::Reader::Node pWindowPositions; + if (pJson.TryGetObject("Window Positions", pWindowPositions)) { - const rapidjson::Value& positions = doc["Window Positions"]; - if (positions.IsObject()) + std::vector vKeys; + pWindowPositions.GetKeys(vKeys); + for (const auto& sKey : vKeys) { - for (rapidjson::Value::ConstMemberIterator iter = positions.MemberBegin(); iter != positions.MemberEnd(); ++iter) + ra::util::Json::Reader::Node pWindowPosition; + if (pWindowPositions.TryGetObject(sKey, pWindowPosition)) { - WindowPosition& pos = m_mWindowPositions[iter->name.GetString()]; - pos.oPosition.X = pos.oPosition.Y = pos.oSize.Width = pos.oSize.Height = INT32_MIN; - - if (iter->value.HasMember("X")) - pos.oPosition.X = iter->value["X"].GetInt(); - if (iter->value.HasMember("Y")) - pos.oPosition.Y = iter->value["Y"].GetInt(); - if (iter->value.HasMember("Width")) - pos.oSize.Width = iter->value["Width"].GetInt(); - if (iter->value.HasMember("Height")) - pos.oSize.Height = iter->value["Height"].GetInt(); + WindowPosition& pos = m_mWindowPositions[sKey]; + pos.oPosition.X = pWindowPosition.GetInteger("X", INT32_MIN); + pos.oPosition.Y = pWindowPosition.GetInteger("Y", INT32_MIN); + pos.oSize.Width = pWindowPosition.GetInteger("Width", INT32_MIN); + pos.oSize.Height = pWindowPosition.GetInteger("Height", INT32_MIN); } } } @@ -160,31 +145,31 @@ bool JsonFileConfiguration::Load(const std::wstring& sFilename) return true; } -static void WritePopupLocation(rapidjson::Document& doc, rapidjson::Document::AllocatorType& a, - const char* sFieldName, ra::ui::viewmodels::PopupLocation nPopupLocation) +static void WritePopupLocation(ra::util::Json::Writer& pWriter, + const std::string& sFieldName, ra::ui::viewmodels::PopupLocation nPopupLocation) { switch (nPopupLocation) { case ra::ui::viewmodels::PopupLocation::None: - doc.AddMember(rapidjson::StringRef(sFieldName), rapidjson::StringRef("None"), a); + pWriter.SetString(sFieldName, "None"); break; case ra::ui::viewmodels::PopupLocation::TopLeft: - doc.AddMember(rapidjson::StringRef(sFieldName), rapidjson::StringRef("TopLeft"), a); + pWriter.SetString(sFieldName, "TopLeft"); break; case ra::ui::viewmodels::PopupLocation::TopMiddle: - doc.AddMember(rapidjson::StringRef(sFieldName), rapidjson::StringRef("TopMiddle"), a); + pWriter.SetString(sFieldName, "TopMiddle"); break; case ra::ui::viewmodels::PopupLocation::TopRight: - doc.AddMember(rapidjson::StringRef(sFieldName), rapidjson::StringRef("TopRight"), a); + pWriter.SetString(sFieldName, "TopRight"); break; case ra::ui::viewmodels::PopupLocation::BottomLeft: - doc.AddMember(rapidjson::StringRef(sFieldName), rapidjson::StringRef("BottomLeft"), a); + pWriter.SetString(sFieldName, "BottomLeft"); break; case ra::ui::viewmodels::PopupLocation::BottomMiddle: - doc.AddMember(rapidjson::StringRef(sFieldName), rapidjson::StringRef("BottomMiddle"), a); + pWriter.SetString(sFieldName, "BottomMiddle"); break; case ra::ui::viewmodels::PopupLocation::BottomRight: - doc.AddMember(rapidjson::StringRef(sFieldName), rapidjson::StringRef("BottomRight"), a); + pWriter.SetString(sFieldName, "BottomRight"); break; } } @@ -199,56 +184,51 @@ void JsonFileConfiguration::Save() const return; } - rapidjson::Document doc; - doc.SetObject(); - - rapidjson::Document::AllocatorType& a = doc.GetAllocator(); - doc.AddMember("Username", rapidjson::StringRef(m_sUsername), a); - doc.AddMember("Token", rapidjson::StringRef(m_sApiToken), a); - doc.AddMember("Hardcore Active", IsFeatureEnabled(Feature::Hardcore), a); - doc.AddMember("Non Hardcore Warning", IsFeatureEnabled(Feature::NonHardcoreWarning), a); - doc.AddMember("Only Hardcore Unlocks", IsFeatureEnabled(Feature::OnlyHardcoreUnlocks), a); - WritePopupLocation(doc, a, "Achievement Triggered Notification Display", GetPopupLocation(ra::ui::viewmodels::Popup::AchievementTriggered)); - doc.AddMember("Achievement Triggered Screenshot", IsFeatureEnabled(Feature::AchievementTriggeredScreenshot), a); - WritePopupLocation(doc, a, "Mastery Notification Display", GetPopupLocation(ra::ui::viewmodels::Popup::Mastery)); - doc.AddMember("Mastery Screenshot", IsFeatureEnabled(Feature::MasteryNotificationScreenshot), a); - doc.AddMember("Leaderboards Active", IsFeatureEnabled(Feature::Leaderboards), a); - WritePopupLocation(doc, a, "Leaderboard Notification Display", GetPopupLocation(ra::ui::viewmodels::Popup::LeaderboardStarted)); - WritePopupLocation(doc, a, "Leaderboard Cancel Display", GetPopupLocation(ra::ui::viewmodels::Popup::LeaderboardCanceled)); - WritePopupLocation(doc, a, "Leaderboard Counter Display", GetPopupLocation(ra::ui::viewmodels::Popup::LeaderboardTracker)); - WritePopupLocation(doc, a, "Leaderboard Scoreboard Display", GetPopupLocation(ra::ui::viewmodels::Popup::LeaderboardScoreboard)); - WritePopupLocation(doc, a, "Challenge Notification Display", GetPopupLocation(ra::ui::viewmodels::Popup::Challenge)); - WritePopupLocation(doc, a, "Informational Notification Display", GetPopupLocation(ra::ui::viewmodels::Popup::Message)); - doc.AddMember("Prefer Decimal", IsFeatureEnabled(Feature::PreferDecimal), a); - doc.AddMember("Num Background Threads", m_nBackgroundThreads, a); + ra::util::Json::Writer pWriter; + pWriter.SetString("Username", m_sUsername); + pWriter.SetString("Token", m_sApiToken); + + pWriter.SetBoolean("Hardcore Active", IsFeatureEnabled(Feature::Hardcore)); + pWriter.SetBoolean("Non Hardcore Warning", IsFeatureEnabled(Feature::NonHardcoreWarning)); + pWriter.SetBoolean("Only Hardcore Unlocks", IsFeatureEnabled(Feature::OnlyHardcoreUnlocks)); + + WritePopupLocation(pWriter, "Achievement Triggered Notification Display", GetPopupLocation(ra::ui::viewmodels::Popup::AchievementTriggered)); + pWriter.SetBoolean("Achievement Triggered Screenshot", IsFeatureEnabled(Feature::AchievementTriggeredScreenshot)); + WritePopupLocation(pWriter, "Mastery Notification Display", GetPopupLocation(ra::ui::viewmodels::Popup::Mastery)); + pWriter.SetBoolean("Mastery Screenshot", IsFeatureEnabled(Feature::MasteryNotificationScreenshot)); + + pWriter.SetBoolean("Leaderboards Active", IsFeatureEnabled(Feature::Leaderboards)); + WritePopupLocation(pWriter, "Leaderboard Notification Display", GetPopupLocation(ra::ui::viewmodels::Popup::LeaderboardStarted)); + WritePopupLocation(pWriter, "Leaderboard Cancel Display", GetPopupLocation(ra::ui::viewmodels::Popup::LeaderboardCanceled)); + WritePopupLocation(pWriter, "Leaderboard Counter Display", GetPopupLocation(ra::ui::viewmodels::Popup::LeaderboardTracker)); + WritePopupLocation(pWriter, "Leaderboard Scoreboard Display", GetPopupLocation(ra::ui::viewmodels::Popup::LeaderboardScoreboard)); + WritePopupLocation(pWriter, "Challenge Notification Display", GetPopupLocation(ra::ui::viewmodels::Popup::Challenge)); + WritePopupLocation(pWriter, "Informational Notification Display", GetPopupLocation(ra::ui::viewmodels::Popup::Message)); + + pWriter.SetBoolean("Prefer Decimal", IsFeatureEnabled(Feature::PreferDecimal)); + pWriter.SetInteger("Num Background Threads", m_nBackgroundThreads); if (!m_sScreenshotDirectory.empty()) - doc.AddMember("Screenshot Directory", ra::util::String::Narrow(m_sScreenshotDirectory), a); + pWriter.SetString("Screenshot Directory", ra::util::String::Narrow(m_sScreenshotDirectory)); - rapidjson::Value positions(rapidjson::kObjectType); + ra::util::Json::Writer::Node pWindowPositions = pWriter.SetObject("Window Positions"); for (WindowPositionMap::const_iterator iter = m_mWindowPositions.begin(); iter != m_mWindowPositions.end(); ++iter) { - rapidjson::Value rect(rapidjson::kObjectType); + ra::util::Json::Writer::Node pWindowPosition = pWindowPositions.SetObject(iter->first); if (iter->second.oPosition.X != INT32_MIN) - rect.AddMember("X", iter->second.oPosition.X, a); + pWindowPosition.SetInteger("X", iter->second.oPosition.X); if (iter->second.oPosition.Y != INT32_MIN) - rect.AddMember("Y", iter->second.oPosition.Y, a); + pWindowPosition.SetInteger("Y", iter->second.oPosition.Y); if (iter->second.oSize.Width != INT32_MIN) - rect.AddMember("Width", iter->second.oSize.Width, a); + pWindowPosition.SetInteger("Width", iter->second.oSize.Width); if (iter->second.oSize.Height != INT32_MIN) - rect.AddMember("Height", iter->second.oSize.Height, a); - - if (rect.MemberCount() > 0) - positions.AddMember(rapidjson::StringRef(iter->first), rect, a); + pWindowPosition.SetInteger("Height", iter->second.oSize.Height); } - if (positions.MemberCount() > 0) - doc.AddMember("Window Positions", positions.Move(), a); - auto& pFileSystem = ra::services::ServiceLocator::Get(); - auto pWriter = pFileSystem.CreateTextFile(m_sFilename); - if (pWriter != nullptr) - SaveDocument(doc, *pWriter); + auto pFile = pFileSystem.CreateTextFile(m_sFilename); + if (pFile != nullptr) + pWriter.Save(*pFile); } bool JsonFileConfiguration::IsFeatureEnabled(Feature nFeature) const noexcept diff --git a/src/ui/Theme.cpp b/src/ui/Theme.cpp index 72ad458d..c210d729 100644 --- a/src/ui/Theme.cpp +++ b/src/ui/Theme.cpp @@ -1,7 +1,7 @@ #include "EditorTheme.hh" #include "OverlayTheme.hh" -#include "RA_Json.h" +#include "util\Json.hh" #include "util\Log.hh" #include "services\IFileSystem.hh" @@ -10,44 +10,20 @@ namespace ra { namespace ui { -static void ReadSize(int& nSize, const rapidjson::Value& pSizes, const char* pJsonField) +static void ReadColor(Color& nColor, const ra::util::Json::Reader::Node& pColors, const std::string& sFieldName) { - if (pSizes.HasMember(pJsonField)) + std::string sValue; + if (pColors.TryGetString(sFieldName, sValue)) { - const auto& pField = pSizes[pJsonField]; - if (pField.IsInt()) - nSize = pField.GetInt(); - } -} - -static void ReadBool(bool& bValue, const rapidjson::Value& pContainer, const char* pJsonField) -{ - if (pContainer.HasMember(pJsonField)) - { - const auto& pField = pContainer[pJsonField]; - if (pField.IsBool()) - bValue = pField.GetBool(); - } -} + if (sValue.length() == 7 && sValue.at(0) == '#') + sValue.erase(sValue.begin()); -static void ReadColor(Color& nColor, const rapidjson::Value& pColors, const char* pJsonField) -{ - if (pColors.HasMember(pJsonField)) - { - const auto& pField = pColors[pJsonField]; - if (pField.IsString()) + if (sValue.length() == 6) { - std::string sValue = pField.GetString(); - if (sValue.length() == 7 && sValue.at(0) == '#') - sValue.erase(sValue.begin()); - - if (sValue.length() == 6) - { - char* pEnd; - const auto nValue = strtoul(sValue.c_str(), &pEnd, 16); - if (pEnd && *pEnd == '\0') - nColor = Color(nValue | 0xFF000000); - } + char* pEnd; + const auto nValue = strtoul(sValue.c_str(), &pEnd, 16); + if (pEnd && *pEnd == '\0') + nColor = Color(nValue | 0xFF000000); } } } @@ -63,86 +39,77 @@ void OverlayTheme::LoadFromFile() if (!pFile) return; - rapidjson::Document document; - if (!LoadDocument(document, *pFile)) + ra::util::Json::Reader pJson; + if (!pJson.Parse(*pFile)) { - RA_LOG_ERR("Unable to read Overlay\\theme.json: %s (%zu)", - GetParseError_En(document.GetParseError()), document.GetErrorOffset()); + RA_LOG_ERR("Unable to read %s: %s (%zu)", L"Overlay\\theme.json", pJson.GetParseError(), pJson.GetParseErrorOffset()); return; } - if (document.HasMember("Popup")) + ra::util::Json::Reader::Node pPopup; + if (pJson.TryGetObject("Popup", pPopup)) { - const rapidjson::Value& popup = document["Popup"]; - - if (popup.HasMember("Font")) - m_sFontPopup = popup["Font"].GetString(); + pPopup.TryGetString("Font", m_sFontPopup); - if (popup.HasMember("FontSizes")) + ra::util::Json::Reader::Node pFontSizes; + if (pPopup.TryGetObject("FontSizes", pFontSizes)) { - const rapidjson::Value& sizes = popup["FontSizes"]; - - ReadSize(m_nFontSizePopupTitle, sizes, "Title"); - ReadSize(m_nFontSizePopupSubtitle, sizes, "Subtitle"); - ReadSize(m_nFontSizePopupDetail, sizes, "Detail"); - ReadSize(m_nFontSizePopupLeaderboardTitle, sizes, "LeaderboardTitle"); - ReadSize(m_nFontSizePopupLeaderboardEntry, sizes, "LeaderboardEntry"); - ReadSize(m_nFontSizePopupLeaderboardTracker, sizes, "LeaderboardTracker"); + pFontSizes.TryGetInteger("Title", m_nFontSizePopupTitle); + pFontSizes.TryGetInteger("Subtitle", m_nFontSizePopupSubtitle); + pFontSizes.TryGetInteger("Detail", m_nFontSizePopupDetail); + pFontSizes.TryGetInteger("LeaderboardTitle", m_nFontSizePopupLeaderboardTitle); + pFontSizes.TryGetInteger("LeaderboardEntry", m_nFontSizePopupLeaderboardEntry); + pFontSizes.TryGetInteger("LeaderboardTracker", m_nFontSizePopupLeaderboardTracker); } - if (popup.HasMember("Colors")) + ra::util::Json::Reader::Node pColors; + if (pPopup.TryGetObject("Colors", pColors)) { - const rapidjson::Value& colors = popup["Colors"]; - - ReadColor(m_colorBackground, colors, "Background"); - ReadColor(m_colorMasteryBackground, colors, "MasteryBackground"); - ReadColor(m_colorNonHardcoreBackground, colors, "NonHardcoreBackground"); - ReadColor(m_colorBorder, colors, "Border"); - ReadColor(m_colorTextShadow, colors, "TextShadow"); - ReadColor(m_colorTitle, colors, "Title"); - ReadColor(m_colorDescription, colors, "Description"); - ReadColor(m_colorDetail, colors, "Detail"); - ReadColor(m_colorError, colors, "Error"); - ReadColor(m_colorLeaderboardEntry, colors, "LeaderboardEntry"); - ReadColor(m_colorLeaderboardPlayer, colors, "LeaderboardPlayer"); + ReadColor(m_colorBackground, pColors, "Background"); + ReadColor(m_colorMasteryBackground, pColors, "MasteryBackground"); + ReadColor(m_colorNonHardcoreBackground, pColors, "NonHardcoreBackground"); + ReadColor(m_colorBorder, pColors, "Border"); + ReadColor(m_colorTextShadow, pColors, "TextShadow"); + ReadColor(m_colorTitle, pColors, "Title"); + ReadColor(m_colorDescription, pColors, "Description"); + ReadColor(m_colorDetail, pColors, "Detail"); + ReadColor(m_colorError, pColors, "Error"); + ReadColor(m_colorLeaderboardEntry, pColors, "LeaderboardEntry"); + ReadColor(m_colorLeaderboardPlayer, pColors, "LeaderboardPlayer"); } } - if (document.HasMember("Overlay")) + ra::util::Json::Reader::Node pOverlay; + if (pJson.TryGetObject("Overlay", pOverlay)) { - const rapidjson::Value& overlay = document["Overlay"]; + pOverlay.TryGetString("Font", m_sFontOverlay); - if (overlay.HasMember("Font")) - m_sFontOverlay = overlay["Font"].GetString(); - - if (overlay.HasMember("FontSizes")) + ra::util::Json::Reader::Node pFontSizes; + if (pOverlay.TryGetObject("FontSizes", pFontSizes)) { - const rapidjson::Value& sizes = overlay["FontSizes"]; - - ReadSize(m_nFontSizeOverlayTitle, sizes, "Title"); - ReadSize(m_nFontSizeOverlayHeader, sizes, "Header"); - ReadSize(m_nFontSizeOverlaySummary, sizes, "Summary"); - ReadSize(m_nFontSizeOverlayDetail, sizes, "Detail"); + pFontSizes.TryGetInteger("Title", m_nFontSizeOverlayTitle); + pFontSizes.TryGetInteger("Header", m_nFontSizeOverlayHeader); + pFontSizes.TryGetInteger("Summary", m_nFontSizeOverlaySummary); + pFontSizes.TryGetInteger("Detail", m_nFontSizeOverlayDetail); } - if (overlay.HasMember("Colors")) + ra::util::Json::Reader::Node pColors; + if (pOverlay.TryGetObject("Colors", pColors)) { - const rapidjson::Value& colors = overlay["Colors"]; - - ReadColor(m_colorOverlayPanel, colors, "Panel"); - ReadColor(m_colorOverlayText, colors, "Text"); - ReadColor(m_colorOverlayDisabledText, colors, "DisabledText"); - ReadColor(m_colorOverlaySubText, colors, "SubText"); - ReadColor(m_colorOverlayDisabledSubText, colors, "DisabledSubText"); - ReadColor(m_colorOverlaySelectionBackground, colors, "SelectionBackground"); - ReadColor(m_colorOverlaySelectionText, colors, "SelectionText"); - ReadColor(m_colorOverlaySelectionDisabledText, colors, "SelectionDisabledText"); - ReadColor(m_colorOverlayScrollBar, colors, "ScrollBar"); - ReadColor(m_colorOverlayScrollBarGripper, colors, "ScrollBarGripper"); + ReadColor(m_colorOverlayPanel, pColors, "Panel"); + ReadColor(m_colorOverlayText, pColors, "Text"); + ReadColor(m_colorOverlayDisabledText, pColors, "DisabledText"); + ReadColor(m_colorOverlaySubText, pColors, "SubText"); + ReadColor(m_colorOverlayDisabledSubText, pColors, "DisabledSubText"); + ReadColor(m_colorOverlaySelectionBackground, pColors, "SelectionBackground"); + ReadColor(m_colorOverlaySelectionText, pColors, "SelectionText"); + ReadColor(m_colorOverlaySelectionDisabledText, pColors, "SelectionDisabledText"); + ReadColor(m_colorOverlayScrollBar, pColors, "ScrollBar"); + ReadColor(m_colorOverlayScrollBarGripper, pColors, "ScrollBarGripper"); } } - ReadBool(m_bTransparent, document, "Transparent"); + pJson.TryGetBoolean("Transparent", m_bTransparent); } void EditorTheme::LoadFromFile() @@ -156,63 +123,55 @@ void EditorTheme::LoadFromFile() if (!pFile) return; - rapidjson::Document document; - if (!LoadDocument(document, *pFile)) + ra::util::Json::Reader pJson; + if (!pJson.Parse(*pFile)) { - RA_LOG_ERR("Unable to read Overlay\\editor_theme.json: %s (%zu)", - GetParseError_En(document.GetParseError()), document.GetErrorOffset()); + RA_LOG_ERR("Unable to read %s: %s (%zu)", L"Overlay\\editor_theme.json", pJson.GetParseError(), pJson.GetParseErrorOffset()); return; } - if (document.HasMember("MemoryViewer")) + ra::util::Json::Reader::Node pMemoryViewer; + if (pJson.TryGetObject("MemoryViewer", pMemoryViewer)) { - const rapidjson::Value& memoryViewer = document["MemoryViewer"]; + pMemoryViewer.TryGetString("Font", m_sFontMemoryViewer); + pMemoryViewer.TryGetInteger("FontSize", m_nFontSizeMemoryViewer); - if (memoryViewer.HasMember("Font")) - m_sFontMemoryViewer = memoryViewer["Font"].GetString(); - - if (memoryViewer.HasMember("FontSize")) - ReadSize(m_nFontSizeMemoryViewer, memoryViewer, "FontSize"); - - if (memoryViewer.HasMember("Colors")) + ra::util::Json::Reader::Node pColors; + if (pMemoryViewer.TryGetObject("Colors", pColors)) { - const rapidjson::Value& colors = memoryViewer["Colors"]; - - ReadColor(m_colorBackground, colors, "Background"); - ReadColor(m_colorSeparator, colors, "Separator"); - ReadColor(m_colorCursor, colors, "Cursor"); - ReadColor(m_colorNormal, colors, "Normal"); - ReadColor(m_colorSelected, colors, "Selected"); - ReadColor(m_colorHasNote, colors, "HasNote"); - ReadColor(m_colorHasSurrogateNote, colors, "HasSurrogateNote"); - ReadColor(m_colorHasBookmark, colors, "HasBookmark"); - ReadColor(m_colorFrozen, colors, "Frozen"); - ReadColor(m_colorHeader, colors, "Header"); - ReadColor(m_colorHeaderSelected, colors, "HeaderSelected"); + ReadColor(m_colorBackground, pColors, "Background"); + ReadColor(m_colorSeparator, pColors, "Separator"); + ReadColor(m_colorCursor, pColors, "Cursor"); + ReadColor(m_colorNormal, pColors, "Normal"); + ReadColor(m_colorSelected, pColors, "Selected"); + ReadColor(m_colorHasNote, pColors, "HasNote"); + ReadColor(m_colorHasSurrogateNote, pColors, "HasSurrogateNote"); + ReadColor(m_colorHasBookmark, pColors, "HasBookmark"); + ReadColor(m_colorFrozen, pColors, "Frozen"); + ReadColor(m_colorHeader, pColors, "Header"); + ReadColor(m_colorHeaderSelected, pColors, "HeaderSelected"); } } - if (document.HasMember("CodeNotes")) + ra::util::Json::Reader::Node pCodeNotes; + if (pJson.TryGetObject("CodeNotes", pCodeNotes)) { - const rapidjson::Value& codeNotes = document["CodeNotes"]; - - if (codeNotes.HasMember("Colors")) + ra::util::Json::Reader::Node pColors; + if (pCodeNotes.TryGetObject("Colors", pColors)) { - const rapidjson::Value& colors = codeNotes["Colors"]; - - ReadColor(m_colorNoteNormal, colors, "Normal"); - ReadColor(m_colorNoteModified, colors, "Modified"); + ReadColor(m_colorNoteNormal, pColors, "Normal"); + ReadColor(m_colorNoteModified, pColors, "Modified"); } } - if (document.HasMember("TriggerColors")) + ra::util::Json::Reader::Node pTriggerColors; + if (pJson.TryGetObject("TriggerColors", pTriggerColors)) { - const rapidjson::Value& triggerColors = document["TriggerColors"]; - ReadColor(m_colorTriggerIsTrue, triggerColors, "IsTrue"); - ReadColor(m_colorTriggerWasTrue, triggerColors, "WasTrue"); - ReadColor(m_colorTriggerBecomingTrue, triggerColors, "BecomingTrue"); - ReadColor(m_colorTriggerResetTrue, triggerColors, "ResetTrue"); - ReadColor(m_colorTriggerPauseTrue, triggerColors, "PauseTrue"); + ReadColor(m_colorTriggerIsTrue, pTriggerColors, "IsTrue"); + ReadColor(m_colorTriggerWasTrue, pTriggerColors, "WasTrue"); + ReadColor(m_colorTriggerBecomingTrue, pTriggerColors, "BecomingTrue"); + ReadColor(m_colorTriggerResetTrue, pTriggerColors, "ResetTrue"); + ReadColor(m_colorTriggerPauseTrue, pTriggerColors, "PauseTrue"); } } diff --git a/src/ui/viewmodels/MemoryBookmarksViewModel.cpp b/src/ui/viewmodels/MemoryBookmarksViewModel.cpp index 0c9ad814..3f2eaab0 100644 --- a/src/ui/viewmodels/MemoryBookmarksViewModel.cpp +++ b/src/ui/viewmodels/MemoryBookmarksViewModel.cpp @@ -1,7 +1,7 @@ #include "MemoryBookmarksViewModel.hh" #include "RA_Defs.h" -#include "RA_Json.h" +#include "util\Json.hh" #include "util\Strings.hh" #include "context\IEmulatorMemoryContext.hh" @@ -197,13 +197,13 @@ void MemoryBookmarksViewModel::LoadBookmarks(ra::services::TextReader& sBookmark m_vmMemoryWatchList.Items().BeginUpdate(); - rapidjson::Document document; - if (LoadDocument(document, sBookmarksFile)) + ra::util::Json::Reader pJson; + if (pJson.Parse(sBookmarksFile)) { - if (document.HasMember("Bookmarks")) + std::vector vBookmarks; + if (pJson.TryGetObjectArray("Bookmarks", vBookmarks)) { - const auto& bookmarks = document["Bookmarks"]; - for (const auto& bookmark : bookmarks.GetArray()) + for (const auto& bookmark : vBookmarks) { auto* vmBookmark = m_vmMemoryWatchList.Items().GetItemAt(nIndex); if (vmBookmark == nullptr) @@ -215,28 +215,26 @@ void MemoryBookmarksViewModel::LoadBookmarks(ra::services::TextReader& sBookmark vmBookmark->BeginInitialization(); - if (bookmark.HasMember("MemAddr")) + std::string sMemAddr; + if (bookmark.TryGetString("MemAddr", sMemAddr)) { // third bookmark format uses the memref serializer - const char* memaddr = bookmark["MemAddr"].GetString(); - InitializeBookmark(*vmBookmark, memaddr); + InitializeBookmark(*vmBookmark, sMemAddr); - if (bookmark.HasMember("Size")) + switch (bookmark.GetInteger("Size")) { - switch (bookmark["Size"].GetInt()) - { - case 15: vmBookmark->SetSize(ra::data::Memory::Size::Text); break; - } + case 15: vmBookmark->SetSize(ra::data::Memory::Size::Text); break; } } else { auto nSize = ra::data::Memory::Size::EightBit; - if (bookmark.HasMember("Type")) + int nType; + if (bookmark.TryGetInteger("Type", nType)) { // original bookmark format used Type for the three supported sizes. - switch (bookmark["Type"].GetInt()) + switch (nType) { case 1: nSize = ra::data::Memory::Size::EightBit; break; case 2: nSize = ra::data::Memory::Size::SixteenBit; break; @@ -247,7 +245,8 @@ void MemoryBookmarksViewModel::LoadBookmarks(ra::services::TextReader& sBookmark { // second bookmark format used the raw enum values, which was fragile. // this enumerates the mapping for backwards compatibility. - switch (bookmark["Size"].GetInt()) + nType = bookmark.GetInteger("Size", 10); + switch (nType) { case 0: nSize = ra::data::Memory::Size::Bit0; break; case 1: nSize = ra::data::Memory::Size::Bit1; break; @@ -269,10 +268,10 @@ void MemoryBookmarksViewModel::LoadBookmarks(ra::services::TextReader& sBookmark } vmBookmark->SetSize(nSize); - vmBookmark->SetAddress(bookmark["Address"].GetUint()); + vmBookmark->SetAddress(bookmark.GetInteger("Address")); } - if (bookmark.HasMember("Decimal") && bookmark["Decimal"].GetBool()) + if (bookmark.GetBoolean("Decimal", false)) vmBookmark->SetFormat(ra::data::Memory::Format::Dec); else vmBookmark->SetFormat(ra::data::Memory::Format::Hex); @@ -280,10 +279,11 @@ void MemoryBookmarksViewModel::LoadBookmarks(ra::services::TextReader& sBookmark if (!vmBookmark->IsIndirectAddress()) // Indirect note already called UpdateRealNote vmBookmark->UpdateRealNote(); - if (bookmark.HasMember("Description")) + std::string sDescription; + if (bookmark.TryGetString("Description", sDescription)) { - const auto sDescription = ra::util::String::Widen(bookmark["Description"].GetString()); - vmBookmark->SetDescription(sDescription); + const auto sDescriptionWide = ra::util::String::Widen(sDescription); + vmBookmark->SetDescription(sDescriptionWide); } vmBookmark->SetBehavior(MemoryBookmarksViewModel::BookmarkBehavior::None); @@ -305,53 +305,49 @@ void MemoryBookmarksViewModel::SaveBookmarks(ra::services::TextWriter& sBookmark { std::string sSerialized; - rapidjson::Document document; - auto& allocator = document.GetAllocator(); - document.SetObject(); + ra::util::Json::Writer pWriter; + ra::util::Json::Writer::Node pBookmarks = pWriter.SetObjectArray("Bookmarks"); - rapidjson::Value bookmarks(rapidjson::kArrayType); for (auto& vmBookmark : m_vmMemoryWatchList.Items()) { - rapidjson::Value item(rapidjson::kObjectType); + ra::util::Json::Writer::Node pBookmark = pBookmarks.AppendObject(); const auto nSize = vmBookmark.GetSize(); switch (nSize) { case ra::data::Memory::Size::Text: - item.AddMember("Size", 15, allocator); + pBookmark.SetInteger("Size", 15); if (vmBookmark.IsIndirectAddress()) - item.AddMember("MemAddr", vmBookmark.GetIndirectAddress(), allocator); + pBookmark.SetString("MemAddr", vmBookmark.GetIndirectAddress()); else - item.AddMember("Address", vmBookmark.GetAddress(), allocator); + pBookmark.SetInteger("Address", vmBookmark.GetAddress()); break; default: if (vmBookmark.IsIndirectAddress()) { - item.AddMember("MemAddr", vmBookmark.GetIndirectAddress(), allocator); + pBookmark.SetString("MemAddr", vmBookmark.GetIndirectAddress()); } else { sSerialized.clear(); ra::services::AchievementLogicSerializer::AppendOperand( sSerialized, ra::services::TriggerOperandType::Address, nSize, vmBookmark.GetAddress()); - item.AddMember("MemAddr", sSerialized, allocator); + pBookmark.SetString("MemAddr", sSerialized); } break; } if (vmBookmark.GetFormat() != ra::data::Memory::Format::Hex) - item.AddMember("Decimal", true, allocator); + pBookmark.SetBoolean("Decimal", true); if (vmBookmark.IsCustomDescription()) - item.AddMember("Description", ra::util::String::Narrow(vmBookmark.GetDescription()), allocator); + pBookmark.SetString("Description", ra::util::String::Narrow(vmBookmark.GetDescription())); - bookmarks.PushBack(item, allocator); vmBookmark.ResetModified(); } - document.AddMember("Bookmarks", bookmarks, allocator); - SaveDocument(document, sBookmarksFile); + pWriter.Save(sBookmarksFile); } void MemoryBookmarksViewModel::DoFrame() diff --git a/src/ui/viewmodels/MemoryWatchListViewModel.cpp b/src/ui/viewmodels/MemoryWatchListViewModel.cpp index c7566bd1..9d81b12e 100644 --- a/src/ui/viewmodels/MemoryWatchListViewModel.cpp +++ b/src/ui/viewmodels/MemoryWatchListViewModel.cpp @@ -1,7 +1,6 @@ #include "MemoryWatchListViewModel.hh" #include "RA_Defs.h" -#include "RA_Json.h" #include "util\Strings.hh" #include "data\Types.hh" diff --git a/src/ui/viewmodels/MemoryWatchViewModel.cpp b/src/ui/viewmodels/MemoryWatchViewModel.cpp index 34d8c38c..6043de78 100644 --- a/src/ui/viewmodels/MemoryWatchViewModel.cpp +++ b/src/ui/viewmodels/MemoryWatchViewModel.cpp @@ -1,7 +1,6 @@ #include "MemoryWatchViewModel.hh" #include "RA_Defs.h" -#include "RA_Json.h" #include "util\Strings.hh" #include "context\IConsoleContext.hh" diff --git a/tests/RA_Integration.Tests.vcxproj b/tests/RA_Integration.Tests.vcxproj index 004d93a0..7ccfb6ea 100644 --- a/tests/RA_Integration.Tests.vcxproj +++ b/tests/RA_Integration.Tests.vcxproj @@ -83,7 +83,6 @@ Create - diff --git a/tests/RA_Integration.Tests.vcxproj.filters b/tests/RA_Integration.Tests.vcxproj.filters index 00c5b4ae..064e9736 100644 --- a/tests/RA_Integration.Tests.vcxproj.filters +++ b/tests/RA_Integration.Tests.vcxproj.filters @@ -63,9 +63,6 @@ Code - - Code - Tests\Services From e7da51e8d45f1f76c1025b0bfa05c21327c79193 Mon Sep 17 00:00:00 2001 From: Jamiras Date: Tue, 8 Sep 2026 21:54:28 -0600 Subject: [PATCH 2/2] address analysis warning --- src/api/impl/ConnectedServer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/impl/ConnectedServer.cpp b/src/api/impl/ConnectedServer.cpp index 9eb71b15..0a0ec043 100644 --- a/src/api/impl/ConnectedServer.cpp +++ b/src/api/impl/ConnectedServer.cpp @@ -47,7 +47,7 @@ _NODISCARD static bool HandleHttpError(_In_ const ra::services::Http::StatusCode _NODISCARD static bool GetJson([[maybe_unused]] _In_ const char* sApiName, _In_ const ra::services::Http::Response& httpResponse, - _Inout_ ApiResponseBase& pResponse, _Out_ ra::util::Json::Reader& pJson) + _Inout_ ApiResponseBase& pResponse, _Inout_ ra::util::Json::Reader& pJson) { if (httpResponse.Content().empty()) {