////////////////////////////////////////////////////////////////////// // This file is part of Remere's Map Editor ////////////////////////////////////////////////////////////////////// // Remere's 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. // // Remere's 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 "settings.h" #include "filehandle.h" #include "gui.h" #include "client_version.h" #include "otml.h" #include // Static methods to load/save ClientVersion::VersionMap ClientVersion::client_versions; ClientVersion* ClientVersion::latest_version = nullptr; ClientVersion::OtbMap ClientVersion::otb_versions; void ClientVersion::loadVersions() { // Clean up old stuff ClientVersion::unloadVersions(); // Locate the clients.xml file wxFileName file_to_load; wxFileName exec_dir_client_xml; exec_dir_client_xml.Assign(g_gui.GetExecDirectory()); exec_dir_client_xml.SetFullName("clients.xml"); wxFileName data_dir_client_xml; data_dir_client_xml.Assign(g_gui.GetDataDirectory()); data_dir_client_xml.SetFullName("clients.xml"); wxFileName work_dir_client_xml; work_dir_client_xml.Assign(g_gui.getFoundDataDirectory()); work_dir_client_xml.SetFullName("clients.xml"); file_to_load = exec_dir_client_xml; if (!file_to_load.FileExists()) { file_to_load = data_dir_client_xml; if (!file_to_load.FileExists()) { file_to_load = work_dir_client_xml; if (!file_to_load.FileExists()) { file_to_load.Clear(); } } } if (!file_to_load.FileExists()) { wxLogError(wxString() + "Could not load clients.xml, editor will NOT be able to load any client data files.\n" + "Checked paths:\n" + exec_dir_client_xml.GetFullPath() + "\n" + data_dir_client_xml.GetFullPath() + "\n" + work_dir_client_xml.GetFullPath()); return; } // Parse the file pugi::xml_document doc; pugi::xml_parse_result result = doc.load_file(file_to_load.GetFullPath().mb_str()); if (result) { pugi::xml_node node = doc.child("client_config"); if (!node) { wxLogError("Could not load clients.xml (syntax error), editor will NOT be able to load any client data files."); return; } for (pugi::xml_node childNode = node.first_child(); childNode; childNode = childNode.next_sibling()) { const std::string& childName = as_lower_str(childNode.name()); if (childName == "otbs") { for (pugi::xml_node otbNode = childNode.first_child(); otbNode; otbNode = otbNode.next_sibling()) { if (as_lower_str(otbNode.name()) == "otb") { loadOTBInfo(otbNode); } } } else if (childName == "clients") { for (pugi::xml_node versionNode = childNode.first_child(); versionNode; versionNode = versionNode.next_sibling()) { if (as_lower_str(versionNode.name()) == "client") { loadVersion(versionNode); } } } } for (pugi::xml_node childNode = node.first_child(); childNode; childNode = childNode.next_sibling()) { if (as_lower_str(childNode.name()) != "clients") { continue; } for (pugi::xml_node versionNode = childNode.first_child(); versionNode; versionNode = versionNode.next_sibling()) { if (as_lower_str(versionNode.name()) == "client") { loadVersionExtensions(versionNode); } } } } // Assign a default if there isn't one. if (!latest_version && !client_versions.empty()) { latest_version = client_versions.begin()->second; } // Load the data directory info try { json::mValue read_obj; json::read_or_throw(g_settings.getString(Config::ASSETS_DATA_DIRS), read_obj); json::mArray& vers_obj = read_obj.get_array(); for (json::mArray::iterator ver_iter = vers_obj.begin(); ver_iter != vers_obj.end(); ++ver_iter) { json::mObject& ver_obj = ver_iter->get_obj(); ClientVersion* version = get(ver_obj["id"].get_str()); if (version == nullptr) { continue; } version->setClientPath(wxstr(ver_obj["path"].get_str())); } } catch (std::runtime_error&) { // pass ; } } void ClientVersion::unloadVersions() { for (VersionMap::iterator it = client_versions.begin(); it != client_versions.end(); ++it) { delete it->second; } client_versions.clear(); latest_version = nullptr; otb_versions.clear(); } void ClientVersion::loadOTBInfo(pugi::xml_node otbNode) { if (as_lower_str(otbNode.name()) != "otb") { return; } pugi::xml_attribute attribute; if (!(attribute = otbNode.attribute("client"))) { wxLogError("Node 'otb' must contain 'client' tag."); return; } OtbVersion otb = { "", OTB_VERSION_3, CLIENT_VERSION_NONE }; otb.name = attribute.as_string(); if (!(attribute = otbNode.attribute("id"))) { wxLogError("Node 'otb' must contain 'id' tag."); return; } otb.id = static_cast(attribute.as_int()); if (!(attribute = otbNode.attribute("version"))) { wxLogError("Node 'otb' must contain 'version' tag."); return; } OtbFormatVersion versionId = static_cast(attribute.as_uint()); if (versionId < OTB_VERSION_1 || versionId > OTB_VERSION_3) { wxLogError("Node 'otb' unrecognized format version (version 1..3 supported)."); return; } otb.format_version = versionId; otb_versions[otb.name] = otb; } void ClientVersion::loadVersion(pugi::xml_node versionNode) { pugi::xml_attribute attribute; if (!(attribute = versionNode.attribute("name"))) { wxLogError("Node 'client' must contain 'name', 'data_directory' and 'otb' tags."); return; } const std::string& versionName = attribute.as_string(); if (!(attribute = versionNode.attribute("data_directory"))) { wxLogError("Node 'client' must contain 'name', 'data_directory' and 'otb' tags."); return; } const std::string& dataPath = attribute.as_string(); if (!(attribute = versionNode.attribute("otb"))) { wxLogError("Node 'client' must contain 'name', 'data_directory' and 'otb' tags."); return; } const std::string& otbVersionName = attribute.as_string(); if (otb_versions.find(otbVersionName) == otb_versions.end()) { wxLogError("Node 'client' 'otb' tag is invalid (couldn't find this otb version)."); return; } ClientVersion* version = newd ClientVersion(otb_versions[otbVersionName], versionName, wxstr(dataPath)); bool should_be_default = versionNode.attribute("default").as_bool(); version->visible = versionNode.attribute("visible").as_bool(); for (pugi::xml_node childNode = versionNode.first_child(); childNode; childNode = childNode.next_sibling()) { const std::string& childName = as_lower_str(childNode.name()); if (childName == "otbm") { if (!(attribute = childNode.attribute("version"))) { wxLogError("Node 'otbm' missing version."); continue; } int32_t otbmVersion = attribute.as_int() - 1; if (otbmVersion < MAP_OTBM_1 || otbmVersion > MAP_OTBM_4) { wxLogError("Node 'otbm' unsupported version."); continue; } if (childNode.attribute("preffered").as_bool() || version->preferred_map_version == MAP_OTBM_UNKNOWN) { version->preferred_map_version = static_cast(otbmVersion); } version->map_versions_supported.push_back(version->preferred_map_version); } else if (childName == "fucked_up_charges") { version->usesFuckedUpCharges = true; } else if (childName == "data") { if (!(attribute = childNode.attribute("format"))) { wxLogError("Node 'data' does not have 'format' tag."); continue; } const std::string& format = attribute.as_string(); ClientData client_data = { DAT_FORMAT_74, 0, 0 }; if (format == "7.4") { client_data.datFormat = DAT_FORMAT_74; } else if (format == "7.55") { client_data.datFormat = DAT_FORMAT_755; } else if (format == "7.8") { client_data.datFormat = DAT_FORMAT_78; } else if (format == "8.6") { client_data.datFormat = DAT_FORMAT_86; } else if (format == "9.6") { client_data.datFormat = DAT_FORMAT_96; } else if (format == "10.10") { client_data.datFormat = DAT_FORMAT_1010; } else if (format == "10.50") { client_data.datFormat = DAT_FORMAT_1050; } else if (format == "10.57") { client_data.datFormat = DAT_FORMAT_1057; } else { wxLogError("Node 'data' 'format' is invalid (7.4, 7.55, 7.8, 8.6, 9.6, 10.10, 10.50, 10.57 are supported)"); continue; } if (!(attribute = childNode.attribute("dat")) || !wxString(attribute.as_string(), wxConvUTF8).ToULong((unsigned long*)&client_data.datSignature, 16)) { wxLogError("Node 'data' 'dat' tag is not hex-formatted."); continue; } if (!(attribute = childNode.attribute("spr")) || !wxString(attribute.as_string(), wxConvUTF8).ToULong((unsigned long*)&client_data.sprSignature, 16)) { wxLogError("Node 'data' 'spr' tag is not hex-formatted."); continue; } version->data_versions.push_back(client_data); } } if (client_versions[version->getID()]) { wxLogError("Duplicate version id %i, discarding version '%s'.", version->getID(), version->name); delete version; return; } client_versions[version->getID()] = version; if (should_be_default) { latest_version = version; } } void ClientVersion::loadVersionExtensions(pugi::xml_node versionNode) { pugi::xml_attribute attribute; if (!(attribute = versionNode.attribute("name"))) { // Error has already been displayed earlier, no need to show another error about the same thing return; } ClientVersion* version = get(attribute.as_string()); if (!version) { // Same rationale as above return; } for (pugi::xml_node childNode = versionNode.first_child(); childNode; childNode = childNode.next_sibling()) { if (as_lower_str(childNode.name()) != "extensions") { continue; } const std::string& from = childNode.attribute("from").as_string(); const std::string& to = childNode.attribute("to").as_string(); ClientVersion* fromVersion = get(from); ClientVersion* toVersion = get(to); if (!fromVersion && !toVersion) { wxLogError("Unknown client extension data."); continue; } if (!fromVersion) { fromVersion = client_versions.begin()->second; } if (!toVersion) { toVersion = client_versions.rbegin()->second; } for (const auto& versionEntry : client_versions) { ClientVersion* version = versionEntry.second; if (version->getID() >= fromVersion->getID() && version->getID() <= toVersion->getID()) { version->extension_versions.push_back(version); } } std::sort(version->extension_versions.begin(), version->extension_versions.end(), VersionComparisonPredicate); } } void ClientVersion::saveVersions() { json::Array vers_obj; for (VersionMap::iterator i = client_versions.begin(); i != client_versions.end(); ++i) { ClientVersion* version = i->second; json::Object ver_obj; ver_obj.push_back(json::Pair("id", version->getName())); ver_obj.push_back(json::Pair("path", nstr(version->getClientPath().GetFullPath()))); vers_obj.push_back(ver_obj); } std::ostringstream out; json::write(vers_obj, out); g_settings.setString(Config::ASSETS_DATA_DIRS, out.str()); } // Client version class ClientVersion::ClientVersion(OtbVersion otb, std::string versionName, wxString path) : otb(otb), name(versionName), visible(false), preferred_map_version(MAP_OTBM_UNKNOWN), data_path(path) { //// } ClientVersion* ClientVersion::get(ClientVersionID id) { VersionMap::iterator i = client_versions.find(id); if (i == client_versions.end()) { return nullptr; } return i->second; } ClientVersion* ClientVersion::get(std::string id) { for (VersionMap::iterator i = client_versions.begin(); i != client_versions.end(); ++i) { if (i->second->getName() == id) { return i->second; } } return nullptr; } ClientVersionList ClientVersion::getAll() { ClientVersionList l; for (VersionMap::iterator i = client_versions.begin(); i != client_versions.end(); ++i) { l.push_back(i->second); } return l; } ClientVersionList ClientVersion::getAllVisible() { ClientVersionList l; for (VersionMap::iterator i = client_versions.begin(); i != client_versions.end(); ++i) { if (i->second->isVisible()) { l.push_back(i->second); } } return l; } ClientVersionList ClientVersion::getAllForOTBMVersion(MapVersionID id) { ClientVersionList list; for (VersionMap::iterator i = client_versions.begin(); i != client_versions.end(); ++i) { if (i->second->isVisible()) { for (std::vector::iterator v = i->second->map_versions_supported.begin(); v != i->second->map_versions_supported.end(); ++v) { if (*v == id) { list.push_back(i->second); } } } } return list; } ClientVersion* ClientVersion::getLatestVersion() { return latest_version; } FileName ClientVersion::getDataPath() const { wxString basePath = g_gui.GetDataDirectory(); if (!wxFileName(basePath).DirExists()) { basePath = g_gui.getFoundDataDirectory(); } return basePath + data_path + FileName::GetPathSeparator(); } FileName ClientVersion::getLocalDataPath() const { FileName f = g_gui.GetLocalDataDirectory() + data_path + FileName::GetPathSeparator(); f.Mkdir(0755, wxPATH_MKDIR_FULL); return f; } bool ClientVersion::hasValidPaths() { if (!client_path.DirExists()) { return false; } wxDir dir(client_path.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR)); wxString otfi_file; metadata_path = wxFileName(client_path.GetFullPath(), wxString(ASSETS_NAME) + ".dat"); sprites_path = wxFileName(client_path.GetFullPath(), wxString(ASSETS_NAME) + ".spr"); if (dir.GetFirst(&otfi_file, "*.otfi", wxDIR_FILES)) { wxFileName otfi(client_path.GetFullPath(), otfi_file); OTMLDocumentPtr doc = OTMLDocument::parse(otfi.GetFullPath().ToStdString()); if (doc->size() != 0 && doc->hasChildAt("DatSpr")) { OTMLNodePtr node = doc->get("DatSpr"); std::string metadata = node->valueAt("metadata-file", std::string(ASSETS_NAME) + ".dat"); std::string sprites = node->valueAt("sprites-file", std::string(ASSETS_NAME) + ".spr"); metadata_path = wxFileName(client_path.GetFullPath(), wxString(metadata)); sprites_path = wxFileName(client_path.GetFullPath(), wxString(sprites)); } } if (!metadata_path.FileExists() || !sprites_path.FileExists()) { return false; } if (!g_settings.getInteger(Config::CHECK_SIGNATURES)) { return true; } // Peek the version of the files FileReadHandle dat_file(static_cast(metadata_path.GetFullPath().mb_str())); if (!dat_file.isOk()) { wxLogError("Could not open metadata file."); return false; } uint32_t datSignature; dat_file.getU32(datSignature); dat_file.close(); FileReadHandle spr_file(static_cast(sprites_path.GetFullPath().mb_str())); if (!spr_file.isOk()) { wxLogError("Could not open sprites file."); return false; } uint32_t sprSignature; spr_file.getU32(sprSignature); spr_file.close(); for (const auto& clientData : data_versions) { if (clientData.sprSignature == sprSignature && clientData.datSignature == datSignature) { return true; } } wxString message = "Signatures are incorrect.\n"; message << "Metadata signature: %X\n"; message << "Sprites signature: %X"; wxLogError(wxString::Format(message, datSignature, sprSignature)); return false; } bool ClientVersion::loadValidPaths() { while (!hasValidPaths()) { wxString message = "Could not locate metadata and/or sprite files, please navigate to your client assets %s installation folder.\n"; message << "Attempted metadata file: %s\n"; message << "Attempted sprites file: %s\n"; g_gui.PopupDialog("Error", wxString::Format(message, name, metadata_path.GetFullPath(), sprites_path.GetFullPath()), wxOK); wxString dirHelpText("Select assets directory."); wxDirDialog file_dlg(nullptr, dirHelpText, "", wxDD_DIR_MUST_EXIST); int ok = file_dlg.ShowModal(); if (ok == wxID_CANCEL) { return false; } client_path.Assign(file_dlg.GetPath() + FileName::GetPathSeparator()); } ClientVersion::saveVersions(); return true; } DatFormat ClientVersion::getDatFormatForSignature(uint32_t signature) const { for (std::vector::const_iterator iter = data_versions.begin(); iter != data_versions.end(); ++iter) { if (iter->datSignature == signature) { return iter->datFormat; } } return DAT_FORMAT_UNKNOWN; } std::string ClientVersion::getName() const { return name; } ClientVersionID ClientVersion::getID() const { return otb.id; } bool ClientVersion::isVisible() const { return visible; } void ClientVersion::setClientPath(const FileName& dir) { client_path.Assign(dir); } MapVersionID ClientVersion::getPrefferedMapVersionID() const { return preferred_map_version; } OtbVersion ClientVersion::getOTBVersion() const { return otb; } ClientVersionList ClientVersion::getExtensionsSupported() const { return extension_versions; } ClientVersionList ClientVersion::getAllVersionsSupportedForClientVersion(ClientVersion* clientVersion) { ClientVersionList versionList; for (const auto& versionEntry : client_versions) { ClientVersion* version = versionEntry.second; for (ClientVersion* checkVersion : version->getExtensionsSupported()) { if (clientVersion == checkVersion) { versionList.push_back(version); } } } std::sort(versionList.begin(), versionList.end(), VersionComparisonPredicate); return versionList; }