// Copyright (c) rAthena Dev Teams - Licensed under GNU GPL // For more information, see LICENCE in the main folder #include "database.hpp" #include #include #include "malloc.hpp" #include "showmsg.hpp" #include "utilities.hpp" using namespace rathena; bool YamlDatabase::nodeExists( const ryml::NodeRef& node, const std::string& name ){ return (node.num_children() > 0 && node.has_child(c4::to_csubstr(name))); } bool YamlDatabase::nodesExist( const ryml::NodeRef& node, std::initializer_list names ){ bool missing = false; for( const std::string& name : names ){ if( !this->nodeExists( node, name ) ){ ShowError( "Missing mandatory node \"%s\".\n", name.c_str() ); missing = true; } } if( missing ){ this->invalidWarning( node, "At least one mandatory node did not exist.\n" ); return false; } return true; } bool YamlDatabase::verifyCompatibility( const ryml::Tree& tree ){ if( !this->nodeExists( tree.rootref(), "Header" ) ){ ShowError( "No database \"Header\" was found.\n" ); return false; } const ryml::NodeRef& headerNode = tree["Header"]; std::string tmpType; if( !this->asString( headerNode, "Type", tmpType ) ){ return false; } if( this->type != tmpType ){ ShowError( "Database type mismatch: %s != %s.\n", this->type.c_str(), tmpType.c_str() ); return false; } uint16 tmpVersion; if( !this->asUInt16( headerNode, "Version", tmpVersion ) ){ return false; } if( tmpVersion != this->version ){ if( tmpVersion > this->version ){ ShowError( "Database version %hu is not supported. Maximum version is: %hu\n", tmpVersion, this->version ); return false; }else if( tmpVersion >= this->minimumVersion ){ ShowWarning( "Database version %hu is outdated and should be updated. Current version is: %hu\n", tmpVersion, this->version ); ShowWarning( "Reduced compatibility with %s database file from '" CL_WHITE "%s" CL_RESET "'.\n", this->type.c_str(), this->currentFile.c_str() ); }else{ ShowError( "Database version %hu is not supported anymore. Minimum version is: %hu\n", tmpVersion, this->minimumVersion ); return false; } } return true; } bool YamlDatabase::load(){ bool ret = this->load( this->getDefaultLocation() ); this->loadingFinished(); return ret; } bool YamlDatabase::reload(){ this->clear(); return this->load(); } bool YamlDatabase::load(const std::string& path) { ShowStatus("Loading '" CL_WHITE "%s" CL_RESET "'..." CL_CLL "\r", path.c_str()); FILE* f = fopen(path.c_str(), "r"); if (f == nullptr) { ShowError("Failed to open %s database file from '" CL_WHITE "%s" CL_RESET "'.\n", this->type.c_str(), path.c_str()); return false; } fseek(f, 0, SEEK_END); size_t size = ftell(f); char* buf = (char *)aMalloc(size+1); rewind(f); size_t real_size = fread(buf, sizeof(char), size, f); // Zero terminate buf[real_size] = '\0'; fclose(f); parser = {}; ryml::Tree tree; try{ tree = parser.parse_in_arena(c4::to_csubstr(path), c4::to_csubstr(buf)); }catch( const std::runtime_error& e ){ ShowError( "Failed to load %s database file from '" CL_WHITE "%s" CL_RESET "'.\n", this->type.c_str(), path.c_str() ); ShowError( "There is likely a syntax error in the file.\n" ); ShowError( "Error message: %s\n", e.what() ); return false; } // Required here already for header error reporting this->currentFile = path; if (!this->verifyCompatibility(tree)){ ShowError("Failed to verify compatibility with %s database file from '" CL_WHITE "%s" CL_RESET "'.\n", this->type.c_str(), this->currentFile.c_str()); aFree(buf); return false; } const ryml::NodeRef& header = tree["Header"]; if( this->nodeExists( header, "Clear" ) ){ bool clear; if( this->asBool( header, "Clear", clear ) && clear ){ this->clear(); } } this->parse( tree ); this->parseImports( tree ); aFree(buf); return true; } void YamlDatabase::loadingFinished(){ // Does nothing by default, just for hooking } void YamlDatabase::parse( const ryml::Tree& tree ){ uint64 count = 0; if( this->nodeExists( tree.rootref(), "Body" ) ){ const ryml::NodeRef& bodyNode = tree["Body"]; size_t childNodesCount = bodyNode.num_children(); size_t childNodesProgressed = 0; const char* fileName = this->currentFile.c_str(); for( const ryml::NodeRef &node : bodyNode ){ count += this->parseBodyNode( node ); ShowStatus( "Loading [%" PRIdPTR "/%" PRIdPTR "] entries from '" CL_WHITE "%s" CL_RESET "'" CL_CLL "\r", ++childNodesProgressed, childNodesCount, fileName ); } ShowStatus( "Done reading '" CL_WHITE "%" PRIu64 CL_RESET "' entries in '" CL_WHITE "%s" CL_RESET "'" CL_CLL "\n", count, fileName ); } } void YamlDatabase::parseImports( const ryml::Tree& rootNode ){ if( this->nodeExists( rootNode.rootref(), "Footer" ) ){ const ryml::NodeRef& footerNode = rootNode["Footer"]; if( this->nodeExists( footerNode, "Imports") ){ const ryml::NodeRef& importsNode = footerNode["Imports"]; for( const ryml::NodeRef &node : importsNode ){ std::string importFile; if( !this->asString( node, "Path", importFile ) ){ continue; } if( this->nodeExists( node, "Mode" ) ){ std::string mode; if( !this->asString( node, "Mode", mode ) ){ continue; } #ifdef RENEWAL std::string compiledMode = "Renewal"; #else std::string compiledMode = "Prerenewal"; #endif if( compiledMode != mode ){ // Skip this import continue; } } this->load( importFile ); } } } } template bool YamlDatabase::asType( const ryml::NodeRef& node, const std::string& name, R& out ){ if( this->nodeExists( node, name ) ){ const ryml::NodeRef& dataNode = node[c4::to_csubstr(name)]; if (dataNode.val_is_null()) { this->invalidWarning(node, "Node \"%s\" is missing a value.\n", name.c_str()); return false; } try{ dataNode >> out; }catch( std::runtime_error const& ){ this->invalidWarning( node, "Node \"%s\" cannot be parsed as %s.\n", name.c_str(), typeid( R ).name() ); return false; } return true; }else{ this->invalidWarning( node, "Missing node \"%s\".\n", name.c_str() ); return false; } } bool YamlDatabase::asBool(const ryml::NodeRef& node, const std::string &name, bool &out) { const ryml::NodeRef& targetNode = node[c4::to_csubstr(name)]; if (targetNode.val_is_null()) { this->invalidWarning(node, "Node \"%s\" is missing a value.\n", name.c_str()); return false; } std::string str; targetNode >> str; util::tolower( str ); if( str == "true" ){ out = true; return true; }else if( str == "false" ){ out = false; return true; }else{ this->invalidWarning( targetNode, "Unknown boolean value: \"%s\".\n", str.c_str() ); return false; } } bool YamlDatabase::asInt16( const ryml::NodeRef& node, const std::string& name, int16& out ){ return asType( node, name, out); } bool YamlDatabase::asUInt16(const ryml::NodeRef& node, const std::string& name, uint16& out) { return asType(node, name, out); } bool YamlDatabase::asInt32(const ryml::NodeRef& node, const std::string &name, int32 &out) { return asType(node, name, out); } bool YamlDatabase::asUInt32(const ryml::NodeRef& node, const std::string &name, uint32 &out) { return asType(node, name, out); } bool YamlDatabase::asInt64(const ryml::NodeRef& node, const std::string &name, int64 &out) { return asType(node, name, out); } bool YamlDatabase::asUInt64(const ryml::NodeRef& node, const std::string &name, uint64 &out) { return asType(node, name, out); } bool YamlDatabase::asFloat(const ryml::NodeRef& node, const std::string &name, float &out) { return asType(node, name, out); } bool YamlDatabase::asDouble(const ryml::NodeRef& node, const std::string &name, double &out) { return asType(node, name, out); } bool YamlDatabase::asString(const ryml::NodeRef& node, const std::string &name, std::string &out) { return asType(node, name, out); } bool YamlDatabase::asUInt16Rate( const ryml::NodeRef& node, const std::string& name, uint16& out, uint16 maximum ){ if( this->asUInt16( node, name, out ) ){ if( out > maximum ){ this->invalidWarning( node[c4::to_csubstr(name)], "Node \"%s\" with value %" PRIu16 " exceeds maximum of %" PRIu16 ".\n", name.c_str(), out, maximum ); return false; }else if( out == 0 ){ this->invalidWarning( node[c4::to_csubstr(name)], "Node \"%s\" needs to be at least 1.\n", name.c_str() ); return false; }else{ return true; } }else{ return false; } } bool YamlDatabase::asUInt32Rate( const ryml::NodeRef& node, const std::string& name, uint32& out, uint32 maximum ){ if( this->asUInt32( node, name, out ) ){ if( out > maximum ){ this->invalidWarning( node[c4::to_csubstr(name)], "Node \"%s\" with value %" PRIu32 " exceeds maximum of %" PRIu32 ".\n", name.c_str(), out, maximum ); return false; }else if( out == 0 ){ this->invalidWarning( node[c4::to_csubstr(name)], "Node \"%s\" needs to be at least 1.\n", name.c_str() ); return false; }else{ return true; } }else{ return false; } } int32 YamlDatabase::getLineNumber(const ryml::NodeRef& node) { return parser.source().has_str() ? (int32)parser.location(node).line : 0; } int32 YamlDatabase::getColumnNumber(const ryml::NodeRef& node) { return parser.source().has_str() ? (int32)parser.location(node).col : 0; } void YamlDatabase::invalidWarning( const ryml::NodeRef& node, const char* fmt, ... ){ va_list ap; va_start(ap, fmt); // Remove any remaining garbage of a previous loading line ShowMessage( CL_CLL ); // Print the actual error _vShowMessage( MSG_ERROR, fmt, ap ); va_end(ap); ShowError( "Occurred in file '" CL_WHITE "%s" CL_RESET "' on line %d and column %d.\n", this->currentFile.c_str(), this->getLineNumber(node), this->getColumnNumber(node)); #ifdef DEBUG std::cout << node; #endif } std::string YamlDatabase::getCurrentFile(){ return this->currentFile; } void on_yaml_error( const char* msg, size_t len, ryml::Location loc, void *user_data ){ throw std::runtime_error( msg ); } void do_init_database(){ ryml::set_callbacks( ryml::Callbacks( nullptr, nullptr, nullptr, on_yaml_error ) ); }