// license:BSD-3-Clause // copyright-holders:Nicola Salmoria, Aaron Giles /*************************************************************************** config.cpp Configuration file I/O. ***************************************************************************/ #include "emu.h" #include "config.h" #include "drivenum.h" #include "emuopts.h" #include "xmlfile.h" #define DEBUG_CONFIG 0 //************************************************************************** // CONFIGURATION MANAGER //************************************************************************** //------------------------------------------------- // configuration_manager - constructor //------------------------------------------------- configuration_manager::configuration_manager(running_machine &machine) : m_machine(machine) { } /************************************* * * Register to be involved in config * save/load * *************************************/ void configuration_manager::config_register(const char *nodename, load_delegate load, save_delegate save) { config_element element; element.name = nodename; element.load = std::move(load); element.save = std::move(save); m_typelist.emplace_back(std::move(element)); } /************************************* * * Settings save/load frontend * *************************************/ bool configuration_manager::load_settings() { // loop over all registrants and call their init function for (const auto &type : m_typelist) type.load(config_type::INIT, config_level::DEFAULT, nullptr); // now load the controller file char const *const controller = machine().options().ctrlr(); if (controller && *controller) { // open the config file emu_file file(machine().options().ctrlr_path(), OPEN_FLAG_READ); osd_printf_verbose("Attempting to parse: %s.cfg\n", controller); std::error_condition const filerr = file.open(std::string(controller) + ".cfg"); if (filerr) throw emu_fatalerror("Could not open controller file %s.cfg (%s:%d %s)", controller, filerr.category().name(), filerr.value(), filerr.message()); // load the XML if (!load_xml(file, config_type::CONTROLLER)) throw emu_fatalerror("Could not load controller file %s.cfg", controller); } // next load the defaults file emu_file file(machine().options().cfg_directory(), OPEN_FLAG_READ); std::error_condition filerr = file.open("default.cfg"); osd_printf_verbose("Attempting to parse: default.cfg\n"); if (!filerr) load_xml(file, config_type::DEFAULT); // finally, load the game-specific file filerr = file.open(machine().basename() + ".cfg"); osd_printf_verbose("Attempting to parse: %s.cfg\n",machine().basename()); const bool loaded = !filerr && load_xml(file, config_type::SYSTEM); // loop over all registrants and call their final function for (const auto &type : m_typelist) type.load(config_type::FINAL, config_level::DEFAULT, nullptr); // if we didn't find a saved config, return false so the main core knows that it // is the first time the game is run and it should display the disclaimer. return loaded; } void configuration_manager::save_settings() { // loop over all registrants and call their init function for (const auto &type : m_typelist) type.save(config_type::INIT, nullptr); // save the defaults file emu_file file(machine().options().cfg_directory(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); std::error_condition filerr = file.open("default.cfg"); if (!filerr) save_xml(file, config_type::DEFAULT); // finally, save the system-specific file filerr = file.open(machine().basename() + ".cfg"); if (!filerr) save_xml(file, config_type::SYSTEM); // loop over all registrants and call their final function for (const auto &type : m_typelist) type.save(config_type::FINAL, nullptr); } /************************************* * * XML file load * *************************************/ bool configuration_manager::load_xml(emu_file &file, config_type which_type) { // read the file util::xml::file::ptr const root(util::xml::file::read(file, nullptr)); if (!root) { osd_printf_verbose("Error parsing XML configuration file %s\n", file.filename()); return false; } // find the config node util::xml::data_node const *const confignode = root->get_child("mameconfig"); if (!confignode) { osd_printf_verbose("Could not find root mameconfig element in configuration file %s\n", file.filename()); return false; } // validate the config data version int const version = confignode->get_attribute_int("version", 0); if (version != CONFIG_VERSION) { osd_printf_verbose("Configuration file %s has unsupported version %d\n", file.filename(), version); return false; } // strip off all the path crap from the source filename const char *srcfile = strrchr(machine().system().type.source(), '/'); if (!srcfile) srcfile = strrchr(machine().system().type.source(), '\\'); if (!srcfile) srcfile = strrchr(machine().system().type.source(), ':'); if (!srcfile) srcfile = machine().system().type.source(); else srcfile++; // loop over all system nodes in the file int count = 0; for (util::xml::data_node const *systemnode = confignode->get_child("system"); systemnode; systemnode = systemnode->get_next_sibling("system")) { // look up the name of the system here; skip if none const char *name = systemnode->get_attribute_string("name", ""); // based on the file type, determine whether we have a match config_level level = config_level::DEFAULT; switch (which_type) { case config_type::SYSTEM: // only match on the specific system name if (strcmp(name, machine().system().name)) { osd_printf_verbose("Ignoring configuration for system %s in system configuration file %s\n", name, file.filename()); continue; } level = config_level::SYSTEM; break; case config_type::DEFAULT: // only match on default if (strcmp(name, "default")) { osd_printf_verbose("Ignoring configuration for system %s in default configuration file %s\n", name, file.filename()); continue; } level = config_level::DEFAULT; break; case config_type::CONTROLLER: { // match on: default, system name, source file name, parent name, grandparent name int clone_of; if (!strcmp(name, "default")) { osd_printf_verbose("Applying default configuration from controller configuration file %s\n", file.filename()); level = config_level::DEFAULT; } else if (!strcmp(name, machine().system().name)) { osd_printf_verbose("Applying configuration for system %s from controller configuration file %s\n", name, file.filename()); level = config_level::SYSTEM; } else if (!strcmp(name, srcfile)) { osd_printf_verbose("Applying configuration for source file %s from controller configuration file %s\n", name, file.filename()); level = config_level::SOURCE; } else if ( ((clone_of = driver_list::clone(machine().system())) != -1 && !strcmp(name, driver_list::driver(clone_of).name)) || (clone_of != -1 && ((clone_of = driver_list::clone(clone_of)) != -1) && !strcmp(name, driver_list::driver(clone_of).name))) { osd_printf_verbose("Applying configuration for parent/BIOS %s from controller configuration file %s\n", name, file.filename()); level = (driver_list::driver(clone_of).flags & MACHINE_IS_BIOS_ROOT) ? config_level::BIOS : config_level::PARENT; } else { continue; } break; } default: break; } // log that we are processing this entry if (DEBUG_CONFIG) osd_printf_debug("Entry: %s -- processing\n", name); // loop over all registrants and call their load function for (const auto &type : m_typelist) type.load(which_type, level, systemnode->get_child(type.name.c_str())); count++; } // error if this isn't a valid match return count != 0; } /************************************* * * XML file save * *************************************/ bool configuration_manager::save_xml(emu_file &file, config_type which_type) { // if we cant't create a root node, bail util::xml::file::ptr root(util::xml::file::create()); if (!root) return false; // create a config node util::xml::data_node *const confignode = root->add_child("mameconfig", nullptr); if (!confignode) return false; confignode->set_attribute_int("version", CONFIG_VERSION); // create a system node util::xml::data_node *const systemnode = confignode->add_child("system", nullptr); if (!systemnode) return false; systemnode->set_attribute("name", (which_type == config_type::DEFAULT) ? "default" : machine().system().name); // loop over all registrants and call their save function for (const auto &type : m_typelist) { util::xml::data_node *const curnode = systemnode->add_child(type.name.c_str(), nullptr); if (!curnode) return false; type.save(which_type, curnode); // if nothing was added, just nuke the node if (!curnode->get_value() && !curnode->get_first_child() && !curnode->count_attributes()) curnode->delete_node(); } // flush the file root->write(file); // free and get out of here return true; }