diff options
Diffstat (limited to 'docs/release/src/frontend/mame/ui/inifile.cpp')
-rw-r--r-- | docs/release/src/frontend/mame/ui/inifile.cpp | 583 |
1 files changed, 583 insertions, 0 deletions
diff --git a/docs/release/src/frontend/mame/ui/inifile.cpp b/docs/release/src/frontend/mame/ui/inifile.cpp new file mode 100644 index 00000000000..02713cd5f57 --- /dev/null +++ b/docs/release/src/frontend/mame/ui/inifile.cpp @@ -0,0 +1,583 @@ +// license:BSD-3-Clause +// copyright-holders:Maurizio Petrarota, Vas Crabb +/*************************************************************************** + + ui/inifile.cpp + + UI INIs file manager. + +***************************************************************************/ + +#include "emu.h" +#include "ui/inifile.h" + +#include "ui/moptions.h" + +#include "language.h" + +#include "drivenum.h" +#include "softlist_dev.h" + +#include "corestr.h" + +#include <algorithm> +#include <cstring> +#include <iterator> + + +namespace { + +char const FAVORITE_FILENAME[] = "favorites.ini"; + +} // anonymous namespace + + +//------------------------------------------------- +// ctor +//------------------------------------------------- + +inifile_manager::inifile_manager(ui_options &options) + : m_options(options) + , m_ini_index() +{ + // scan directories and create index + file_enumerator path(m_options.categoryini_path()); + for (osd::directory::entry const *dir = path.next(); dir; dir = path.next()) + { + std::string name(dir->name); + if (core_filename_ends_with(name, ".ini")) + { + emu_file file(m_options.categoryini_path(), OPEN_FLAG_READ); + if (!file.open(name)) + { + init_category(std::move(name), file); + file.close(); + } + } + } + std::stable_sort(m_ini_index.begin(), m_ini_index.end(), [] (auto const &x, auto const &y) { return 0 > core_stricmp(x.first.c_str(), y.first.c_str()); }); +} + +//------------------------------------------------- +// load and indexing ini files +//------------------------------------------------- + +// HBMAME - rewritten using native file usage because mame's file.gets doesn't always work +void inifile_manager::load_ini_category(size_t file, size_t category, std::unordered_set<game_driver const *> &result) const +{ + std::string const &filename(m_ini_index[file].first); + FILE *fp = fopen(filename.c_str(), "r"); + if (!fp) + { + osd_printf_error("Failed to open category file %s for reading\n", filename); + return; + } + + int64_t const offset(m_ini_index[file].second[category].second); + if (fseek(fp, offset, SEEK_SET)) + { + fclose(fp); + osd_printf_error("Failed to seek to category offset in file %s\n", filename); + return; + } + + char rbuf[MAX_CHAR_INFO]; + while (fgets(rbuf, MAX_CHAR_INFO, fp) && rbuf[0] && ('[' != rbuf[0])) + { + auto const tail(std::find_if(std::begin(rbuf), std::prev(std::end(rbuf)), [] (char ch) { return !ch || ('\r' == ch) || ('\n' == ch); })); + *tail = '\0'; + int const dfind(driver_list::find(rbuf)); + if (0 <= dfind) + result.emplace(&driver_list::driver(dfind)); + } + + fclose(fp); +} + +//------------------------------------------------- +// initialize category +//------------------------------------------------- + +// HBMAME - rewritten using native file usage because mame's file.gets doesn't always work +void inifile_manager::init_category(std::string &&filename, emu_file &file) +{ + FILE *fp; + fp = fopen(file.fullpath(), "r"); + if (!fp) + return; + categoryindex index; + char rbuf[MAX_CHAR_INFO]; + std::string name; + while (fgets(rbuf, std::size(rbuf), fp)) + { + if ('[' == rbuf[0]) + { + auto const head(std::next(std::begin(rbuf))); + auto const tail(std::find_if(head, std::end(rbuf), [] (char ch) { return !ch || (']' == ch); })); + name.assign(head, tail); + if ("FOLDER_SETTINGS" != name) + index.emplace_back(std::move(name), ftell(fp)); + } + } + std::stable_sort(index.begin(), index.end(), [] (auto const &x, auto const &y) { return 0 > core_stricmp(x.first.c_str(), y.first.c_str()); }); + if (!index.empty()) + m_ini_index.emplace_back(file.fullpath(), std::move(index)); + + fclose(fp); +} + + +/************************************************************************** + FAVORITE MANAGER +**************************************************************************/ + +bool favorite_manager::favorite_compare::operator()(ui_software_info const &lhs, ui_software_info const &rhs) const +{ + assert(lhs.driver); + assert(rhs.driver); + + if (!lhs.startempty) + { + if (rhs.startempty) + return false; + else if (lhs.listname < rhs.listname) + return true; + else if (lhs.listname > rhs.listname) + return false; + else if (lhs.shortname < rhs.shortname) + return true; + else if (lhs.shortname > rhs.shortname) + return false; + } + else if (!rhs.startempty) + { + return true; + } + + return 0 > std::strncmp(lhs.driver->name, rhs.driver->name, std::size(lhs.driver->name)); +} + +bool favorite_manager::favorite_compare::operator()(ui_software_info const &lhs, game_driver const &rhs) const +{ + assert(lhs.driver); + + if (!lhs.startempty) + return false; + else + return 0 > std::strncmp(lhs.driver->name, rhs.name, std::size(rhs.name)); +} + +bool favorite_manager::favorite_compare::operator()(game_driver const &lhs, ui_software_info const &rhs) const +{ + assert(rhs.driver); + + if (!rhs.startempty) + return true; + else + return 0 > std::strncmp(lhs.name, rhs.driver->name, std::size(lhs.name)); +} + +bool favorite_manager::favorite_compare::operator()(ui_software_info const &lhs, running_software_key const &rhs) const +{ + assert(lhs.driver); + assert(std::get<1>(rhs)); + + if (lhs.startempty) + return true; + else if (lhs.listname < std::get<1>(rhs)) + return true; + else if (lhs.listname > std::get<1>(rhs)) + return false; + else if (lhs.shortname < std::get<2>(rhs)) + return true; + else if (lhs.shortname > std::get<2>(rhs)) + return false; + else + return 0 > std::strncmp(lhs.driver->name, std::get<0>(rhs).name, std::size(lhs.driver->name)); +} + +bool favorite_manager::favorite_compare::operator()(running_software_key const &lhs, ui_software_info const &rhs) const +{ + assert(std::get<1>(lhs)); + assert(rhs.driver); + + if (rhs.startempty) + return false; + else if (std::get<1>(lhs) < rhs.listname) + return true; + else if (std::get<1>(lhs) > rhs.listname) + return false; + else if (std::get<2>(lhs) < rhs.shortname) + return true; + else if (std::get<2>(lhs) > rhs.shortname) + return false; + else + return 0 > std::strncmp(std::get<0>(lhs).name, rhs.driver->name, std::size(rhs.driver->name)); +} + + +//------------------------------------------------- +// construction/destruction +//------------------------------------------------- + +favorite_manager::favorite_manager(ui_options &options) + : m_options(options) + , m_favorites() + , m_sorted() + , m_need_sort(true) +{ + emu_file file(m_options.ui_path(), OPEN_FLAG_READ); + if (!file.open(FAVORITE_FILENAME)) + { + char readbuf[1024]; + file.gets(readbuf, std::size(readbuf)); + + while (readbuf[0] == '[') + file.gets(readbuf, std::size(readbuf)); + + while (file.gets(readbuf, std::size(readbuf))) + { + ui_software_info tmpmatches; + tmpmatches.shortname = chartrimcarriage(readbuf); + file.gets(readbuf, std::size(readbuf)); + tmpmatches.longname = chartrimcarriage(readbuf); + file.gets(readbuf, std::size(readbuf)); + tmpmatches.parentname = chartrimcarriage(readbuf); + file.gets(readbuf, std::size(readbuf)); + tmpmatches.year = chartrimcarriage(readbuf); + file.gets(readbuf, std::size(readbuf)); + tmpmatches.publisher = chartrimcarriage(readbuf); + file.gets(readbuf, std::size(readbuf)); + tmpmatches.supported = software_support(atoi(readbuf)); + file.gets(readbuf, std::size(readbuf)); + tmpmatches.part = chartrimcarriage(readbuf); + file.gets(readbuf, std::size(readbuf)); + chartrimcarriage(readbuf); + auto dx = driver_list::find(readbuf); + if (0 > dx) + continue; + tmpmatches.driver = &driver_list::driver(dx); + file.gets(readbuf, std::size(readbuf)); + tmpmatches.listname = chartrimcarriage(readbuf); + file.gets(readbuf, std::size(readbuf)); + tmpmatches.interface = chartrimcarriage(readbuf); + file.gets(readbuf, std::size(readbuf)); + tmpmatches.instance = chartrimcarriage(readbuf); + file.gets(readbuf, std::size(readbuf)); + tmpmatches.startempty = atoi(readbuf); + file.gets(readbuf, std::size(readbuf)); + tmpmatches.parentlongname = chartrimcarriage(readbuf); + file.gets(readbuf, std::size(readbuf)); + //tmpmatches.usage = chartrimcarriage(readbuf); TODO: recover multi-line info + file.gets(readbuf, std::size(readbuf)); + tmpmatches.devicetype = chartrimcarriage(readbuf); + file.gets(readbuf, std::size(readbuf)); + tmpmatches.available = atoi(readbuf); + + // need to populate this, it isn't displayed anywhere else + tmpmatches.infotext.append(tmpmatches.longname); + tmpmatches.infotext.append(1, '\n'); + tmpmatches.infotext.append(_("swlist-info", "Software list/item")); + tmpmatches.infotext.append(1, '\n'); + tmpmatches.infotext.append(tmpmatches.listname); + tmpmatches.infotext.append(1, ':'); + tmpmatches.infotext.append(tmpmatches.shortname); + + m_favorites.emplace(std::move(tmpmatches)); + } + file.close(); + } +} + + +//------------------------------------------------- +// add +//------------------------------------------------- + +void favorite_manager::add_favorite_system(game_driver const &driver) +{ + add_impl(driver); +} + +void favorite_manager::add_favorite_software(ui_software_info const &swinfo) +{ + add_impl(swinfo); +} + +void favorite_manager::add_favorite(running_machine &machine) +{ + apply_running_machine( + machine, + [this, &machine] (game_driver const &driver, device_image_interface *imagedev, software_info const *software, bool &done) + { + if (imagedev) + { + // creating this is fairly expensive, but we'll assume this usually succeeds + software_part const *const part(imagedev->part_entry()); + assert(software); + assert(part); + ui_software_info info( + *software, + *part, + driver, + imagedev->software_list_name(), + imagedev->instance_name(), + strensure(imagedev->image_type_name())); + + // assume it's available if it's mounted + info.available = true; + + // look up the parent in the list if necessary (eugh, O(n) walk) + if (!info.parentname.empty()) + { + auto const listdev = software_list_device::find_by_name(machine.config(), info.listname); + assert(listdev); + for (software_info const &other : listdev->get_info()) + { + if (other.shortname() == info.parentname) + { + info.parentlongname = other.longname(); + break; + } + } + } + + // hooray for move semantics! + add_impl(std::move(info)); + } + else + { + add_impl(driver); + } + }); +} + +template <typename T> void favorite_manager::add_impl(T &&key) +{ + auto const ins(m_favorites.emplace(std::forward<T>(key))); + if (ins.second) + { + if (!m_sorted.empty()) + m_sorted.emplace_back(std::ref(*ins.first)); + m_need_sort = true; + save_favorites(); + } +} + + +//------------------------------------------------- +// check +//------------------------------------------------- + +bool favorite_manager::is_favorite_system(game_driver const &driver) const +{ + return check_impl(driver); +} + +bool favorite_manager::is_favorite_software(ui_software_info const &swinfo) const +{ + auto found(m_favorites.lower_bound(swinfo)); + if ((m_favorites.end() != found) && (found->listname == swinfo.listname) && (found->shortname == swinfo.shortname)) + return true; + else if (m_favorites.begin() == found) + return false; + + // need to back up and check for matching software with lexically earlier driver + --found; + return (found->listname == swinfo.listname) && (found->shortname == swinfo.shortname); +} + +bool favorite_manager::is_favorite(running_machine &machine) const +{ + bool result(false); + apply_running_machine( + machine, + [this, &result] (game_driver const &driver, device_image_interface *imagedev, software_info const *software, bool &done) + { + assert(!result); + result = imagedev + ? check_impl(running_software_key(driver, imagedev->software_list_name(), software->shortname())) + : check_impl(driver); + done = done || result; + }); + return result; +} + +bool favorite_manager::is_favorite_system_software(ui_software_info const &swinfo) const +{ + return check_impl(swinfo); +} + +template <typename T> bool favorite_manager::check_impl(T const &key) const +{ + return m_favorites.find(key) != m_favorites.end(); +} + + +//------------------------------------------------- +// remove +//------------------------------------------------- + +void favorite_manager::remove_favorite_system(game_driver const &driver) +{ + remove_impl(driver); +} + +void favorite_manager::remove_favorite_software(ui_software_info const &swinfo) +{ + remove_impl(swinfo); +} + +void favorite_manager::remove_favorite(running_machine &machine) +{ + apply_running_machine( + machine, + [this] (game_driver const &driver, device_image_interface *imagedev, software_info const *software, bool &done) + { + done = imagedev + ? remove_impl(running_software_key(driver, imagedev->software_list_name(), software->shortname())) + : remove_impl(driver); + }); +} + +template <typename T> bool favorite_manager::remove_impl(T const &key) +{ + auto const found(m_favorites.find(key)); + if (m_favorites.end() != found) + { + m_favorites.erase(found); + m_sorted.clear(); + m_need_sort = true; + save_favorites(); + return true; + } + else + { + return false; + } +} + + +//------------------------------------------------- +// implementation +//------------------------------------------------- + +template <typename T> +void favorite_manager::apply_running_machine(running_machine &machine, T &&action) +{ + bool done(false); + + // TODO: this should be changed - it interacts poorly with cartslots on arcade systems + if ((machine.system().flags & machine_flags::MASK_TYPE) == machine_flags::TYPE_ARCADE) + { + action(machine.system(), nullptr, nullptr, done); + } + else + { + bool have_software(false); + for (device_image_interface &image_dev : image_interface_enumerator(machine.root_device())) + { + software_info const *const sw(image_dev.software_entry()); + if (image_dev.exists() && image_dev.loaded_through_softlist() && sw) + { + assert(image_dev.software_list_name()); + + have_software = true; + action(machine.system(), &image_dev, sw, done); + if (done) + return; + } + } + + if (!have_software) + action(machine.system(), nullptr, nullptr, done); + } +} + +void favorite_manager::update_sorted() +{ + if (m_need_sort) + { + if (m_sorted.empty()) + std::copy(m_favorites.begin(), m_favorites.end(), std::back_inserter(m_sorted)); + + assert(m_favorites.size() == m_sorted.size()); + std::stable_sort( + m_sorted.begin(), + m_sorted.end(), + [] (ui_software_info const &lhs, ui_software_info const &rhs) -> bool + { + assert(lhs.driver); + assert(rhs.driver); + + int cmp; + + cmp = core_stricmp(lhs.longname.c_str(), rhs.longname.c_str()); + if (0 > cmp) + return true; + else if (0 < cmp) + return false; + + cmp = core_stricmp(lhs.driver->type.fullname(), rhs.driver->type.fullname()); + if (0 > cmp) + return true; + else if (0 < cmp) + return false; + + cmp = std::strcmp(lhs.listname.c_str(), rhs.listname.c_str()); + if (0 > cmp) + return true; + else if (0 < cmp) + return false; + + return false; + }); + + m_need_sort = false; + } +} + +void favorite_manager::save_favorites() +{ + // attempt to open the output file + emu_file file(m_options.ui_path(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); + if (!file.open(FAVORITE_FILENAME)) + { + if (m_favorites.empty()) + { + // delete it if there are no favorites + file.remove_on_close(); + } + else + { + // generate the favorite INI + file.puts("[ROOT_FOLDER]\n[Favorite]\n\n"); + util::ovectorstream buf; + for (ui_software_info const &info : m_favorites) + { + buf.clear(); + buf.rdbuf()->clear(); + + buf << info.shortname << '\n'; + buf << info.longname << '\n'; + buf << info.parentname << '\n'; + buf << info.year << '\n'; + buf << info.publisher << '\n'; + util::stream_format(buf, "%d\n", int(info.supported)); + buf << info.part << '\n'; + util::stream_format(buf, "%s\n", info.driver->name); + buf << info.listname << '\n'; + buf << info.interface << '\n'; + buf << info.instance << '\n'; + util::stream_format(buf, "%d\n", info.startempty); + buf << info.parentlongname << '\n'; + buf << '\n'; //buf << info.usage << '\n'; TODO: store multi-line info in a recoverable format + buf << info.devicetype << '\n'; + util::stream_format(buf, "%d\n", info.available); + + file.puts(util::buf_to_string_view(buf)); + } + } + file.close(); + } +} |