////////////////////////////////////////////////////////////////////// // This file is part of Canary Map Editor ////////////////////////////////////////////////////////////////////// // Canary Map Editor is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Canary Map Editor is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . ////////////////////////////////////////////////////////////////////// #include "main.h" #include "client_assets.h" #include "settings.h" #include "filehandle.h" #include "preferences.h" #include "sprite_appearances.h" #include "gui.h" #include "otml.h" #include namespace { bool logErrorAndSetMessage(const std::string &message, wxString &error) { spdlog::error(message); error = message; return false; } } // namespace (internal use only) using json = nlohmann::json; std::string ClientAssets::version_name; wxString ClientAssets::data_path; wxString ClientAssets::assets_path; bool ClientAssets::loaded = false; void ClientAssets::load() { // Load the data directory info try { auto dataDirs = g_settings.getString(Config::ASSETS_DATA_DIRS); if (!dataDirs.empty()) { json read_obj = json::parse(dataDirs); auto ver_obj = read_obj.at(0).get(); auto path = ver_obj.at("path").get(); setPath(wxstr(path)); } } catch ([[maybe_unused]] const json::exception &e) { spdlog::info("Json exception with error code {}", e.what()); } } bool ClientAssets::loadAppearanceProtobuf(wxString &error, wxArrayString &warnings) { using namespace canary::protobuf::appearances; using json = nlohmann::json; const std::filesystem::path selectedDirectory(ClientAssets::getPath().ToStdString()); std::error_code filesystemError; if (!std::filesystem::is_directory(selectedDirectory, filesystemError)) { if (filesystemError && filesystemError != std::errc::no_such_file_or_directory) { return logErrorAndSetMessage(fmt::format("Could not inspect client path {}: {}", selectedDirectory.string(), filesystemError.message()), error); } return logErrorAndSetMessage(fmt::format("Client directory is not a valid path: {}", selectedDirectory.string()), error); } std::filesystem::path assetsDirectory = selectedDirectory / "assets"; if (!std::filesystem::is_directory(assetsDirectory, filesystemError)) { if (filesystemError && filesystemError != std::errc::no_such_file_or_directory) { return logErrorAndSetMessage(fmt::format("Could not inspect assets path {}: {}", assetsDirectory.string(), filesystemError.message()), error); } assetsDirectory = selectedDirectory / "Contents" / "Resources" / "assets"; } if (!std::filesystem::is_directory(assetsDirectory, filesystemError)) { if (filesystemError && filesystemError != std::errc::no_such_file_or_directory) { return logErrorAndSetMessage(fmt::format("Could not inspect assets path {}: {}", assetsDirectory.string(), filesystemError.message()), error); } return logErrorAndSetMessage(fmt::format("Assets directory not found for client path: {}", selectedDirectory.string()), error); } const std::string assetsPath = assetsDirectory.string() + "/"; if (!g_spriteAppearances.loadCatalogContent(assetsPath, false)) { return logErrorAndSetMessage(fmt::format("Failed to load catalog content from directory: {}", assetsPath), error); } std::filesystem::path packagePath; for (const auto &candidate : { selectedDirectory / "package.json", assetsDirectory.parent_path() / "package.json", assetsDirectory.parent_path() / "app" / "package.json", assetsDirectory.parent_path().parent_path().parent_path() / "package.json", }) { if (std::filesystem::exists(candidate, filesystemError)) { packagePath = candidate; break; } if (filesystemError && filesystemError != std::errc::no_such_file_or_directory) { return logErrorAndSetMessage(fmt::format("Could not inspect package path {}: {}", candidate.string(), filesystemError.message()), error); } } if (packagePath.empty()) { return logErrorAndSetMessage(fmt::format("The file package.json is not present for client path: {}", selectedDirectory.string()), error); } std::ifstream file(packagePath, std::ios::in); if (!file.is_open()) { error = "Failed to open packages.json"; spdlog::error("Failed to open packages.json"); return false; } json document = json::parse(file, nullptr, false); file.close(); // Save version from package.json std::string version = document.at("version").get(); version_name = version; const std::string appearanceFileName = g_spriteAppearances.getAppearanceFileName(); std::fstream fileStream(assetsDirectory / appearanceFileName, std::ios::in | std::ios::binary); if (!fileStream.is_open()) { error = "Failed to load " + appearanceFileName + " from the client folder, file cannot be oppened"; spdlog::error("[{}] - Failed to load {}, file cannot be oppened", __func__, appearanceFileName); fileStream.close(); return false; } // Verify that the version of the library that we linked against is // compatible with the version of the headers we compiled against. GOOGLE_PROTOBUF_VERIFY_VERSION; g_gui.m_appearancesPtr = std::make_unique(); if (!g_gui.m_appearancesPtr->ParseFromIstream(&fileStream)) { error = "Failed to parse binary file " + appearanceFileName + ", file is invalid"; spdlog::error("[{}] - Failed to parse binary file {}, file is invalid", __func__, appearanceFileName); fileStream.close(); return false; } // Parsing all items into ItemType bool rt = g_items.loadFromProtobuf(error, warnings, *g_gui.m_appearancesPtr); if (!rt) { error = "Failed to parse item types from protobuf"; spdlog::error("[{}] - Failed to parse item types from protobuf", __func__); fileStream.close(); return false; } // Load looktypes for (int i = 0; i < g_gui.m_appearancesPtr->outfit().size(); i++) { const auto &outfit = g_gui.m_appearancesPtr->outfit().Get(i); if (!g_gui.gfx.loadOutfitSpriteMetadata(outfit, error, warnings)) { error = "Failed to parse outfit types from protobuf"; spdlog::error("[{}] - Failed to parse outfit types from protobuf", __func__); fileStream.close(); return false; } } fileStream.close(); // Client loaded setLoaded(true); return true; } void ClientAssets::save() { try { json vers_obj; json ver_obj; ver_obj["id"] = getVersionName(); wxFileName fileName; fileName.Assign(getPath()); ver_obj["path"] = fileName.GetFullPath().ToStdString(); auto path = fileName.GetFullPath().ToStdString(); vers_obj.push_back(ver_obj); std::ostringstream out; out << vers_obj; g_settings.setString(Config::ASSETS_DATA_DIRS, out.str()); } catch ([[maybe_unused]] const json::exception &e) { // pass } } FileName ClientAssets::getDataPath() { wxString basePath = g_gui.GetDataDirectory(); if (!wxFileName(basePath).DirExists()) { basePath = g_gui.getFoundDataDirectory(); } return basePath + data_path + FileName::GetPathSeparator(); } FileName ClientAssets::getLocalPath() { FileName f = g_gui.GetLocalDataDirectory() + data_path + FileName::GetPathSeparator(); f.Mkdir(0755, wxPATH_MKDIR_FULL); return f; } wxString ClientAssets::getPath() { return assets_path; } std::string ClientAssets::getVersionName() { return version_name; }