#include "properties.hpp" #include "finally.hpp" #include #include #include "rapidjson/filereadstream.h" #include "rapidjson/filewritestream.h" #include "rapidjson/encodedstream.h" #include "io.hpp" #include "com.hpp" #include "string.hpp" namespace utils::properties { namespace { typedef rapidjson::EncodedOutputStream, rapidjson::FileWriteStream> OutputStream; typedef rapidjson::EncodedInputStream, rapidjson::FileReadStream> InputStream; std::filesystem::path get_properties_folder() { static auto props = get_appdata_path() / "user"; return props; } std::filesystem::path get_properties_file() { static auto props = [] { auto path = std::filesystem::path("t7x/players/properties.json"); const auto legacy_path = get_properties_folder() / "properties.json"; if (io::file_exists(legacy_path) && !io::file_exists(path)) { std::error_code e; std::filesystem::copy(legacy_path, path, std::filesystem::copy_options::skip_existing, e); } return path; }(); return props; } rapidjson::Document load_properties() { rapidjson::Document default_doc{}; default_doc.SetObject(); char read_buffer[256]{0}; // Raw buffer for reading const std::wstring& props = get_properties_file(); FILE* fp; auto err = _wfopen_s(&fp, props.data(), L"rb"); if (err || !fp) { return default_doc; } const auto _ = finally([&] { if (fp) { fclose(fp); } }); // This will handle the BOM rapidjson::FileReadStream bis(fp, read_buffer, sizeof(read_buffer)); InputStream eis(bis); rapidjson::Document doc{}; const rapidjson::ParseResult result = doc.ParseStream>(eis); if (!result || !doc.IsObject()) { return default_doc; } return doc; } void store_properties(const rapidjson::Document& doc) { char write_buffer[256]{0}; // Raw buffer for writing const std::wstring& props = get_properties_file(); io::create_directory(get_properties_folder()); FILE* fp; auto err = _wfopen_s(&fp, props.data(), L"wb"); if (err || !fp) { return; } const auto _ = finally([&] { if (fp) { fclose(fp); } }); rapidjson::FileWriteStream bos(fp, write_buffer, sizeof(write_buffer)); OutputStream eos(bos); rapidjson::Writer writer(eos); doc.Accept(writer); } } std::filesystem::path get_appdata_path() { PWSTR path; if (!SUCCEEDED(SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &path))) { throw std::runtime_error("Failed to read APPDATA path!"); } auto _ = utils::finally([&path] { CoTaskMemFree(path); }); static auto appdata = std::filesystem::path(path) / "t7x"; return appdata; } std::unique_lock lock() { static named_mutex mutex{"t7x-properties-lock"}; return std::unique_lock{mutex}; } std::optional load(const std::string& name) { const auto _ = lock(); const auto doc = load_properties(); if (!doc.HasMember(name)) { return {}; } const auto& value = doc[name]; if (!value.IsString()) { return {}; } return {std::string{value.GetString()}}; } void store(const std::string& name, const std::string& value) { const auto _ = lock(); auto doc = load_properties(); while (doc.HasMember(name)) { doc.RemoveMember(name); } rapidjson::Value key{}; key.SetString(name, doc.GetAllocator()); rapidjson::Value member{}; member.SetString(value, doc.GetAllocator()); doc.AddMember(key, member, doc.GetAllocator()); store_properties(doc); } }