Add ability to load add ons at configuration and game start (#98)

This commit is contained in:
N3xoX1 2026-07-26 14:24:39 +09:30 committed by GitHub
parent ed7acc5f99
commit 9270b8f9c3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 418 additions and 103 deletions

View File

@ -102,11 +102,13 @@ PageNav.page_tabs page.active {
color: #eff0f1;
}
#gameDirectoryList {
#gameDirectoryList,
#addOnDirectoryList {
width: 100%%;
}
#gameDirectoryList select {
#gameDirectoryList select,
#addOnDirectoryList select {
background-color: #1e1e1e;
border: 1px solid #404040;
border-radius: 4px;
@ -119,7 +121,8 @@ PageNav.page_tabs page.active {
width: 100%%;
}
#gameDirectoryList option
#gameDirectoryList option,
#addOnDirectoryList option
{
padding: 0.2rem 0.3rem;
margin: 0.2rem 0.3rem;
@ -127,7 +130,8 @@ PageNav.page_tabs page.active {
color: white;
}
#gameDirectoryList option:current
#gameDirectoryList option:current,
#addOnDirectoryList option:current
{
color: #1e1e1e;
}
@ -144,6 +148,7 @@ PageNav.page_tabs page.active {
<div class="ConfigPage" tooltip-host>
<PageNav target="ConfigContents" id="MainTabNav">
<Page contents="system_config_general.html" label="General" Name="General" />
<Page contents="system_config_game_browser.html" label="Game Browser" Name="GameBrowser" />
<Page contents="system_config_graphics.html" label="Graphics" Name="Graphics" />
<Page contents="system_config_audio.html" label="Audio" Name="Audio" />
<Page contents="system_config_profiles.html" label="Profiles" Name="Profiles" />

View File

@ -0,0 +1,30 @@
<PageNav target="GameBrowserContents" id="GameBrowserTabNav" class="page_tabs">
<Page label="Games" Name="Games">
<div class="input-group">
<div class="group-title">Game Directories</div>
<ListBox id="gameDirectoryList" />
<div class="buttons-container">
<button id="gameDirectoryAdd">Add</button>
<button id="gameDirectoryRemove">Remove</button>
</div>
</div>
<div class="input-group">
<div class="group-title">Display</div>
<div class="form-row slider-row">
<div class="left"><label for="myGamesIconSize">My Games Icon Size:</label></div>
<div class="right"><input type="hslider" min="1" max="4" value="2" id="myGamesIconSize" style="width: 19em" /></div>
</div>
</div>
</Page>
<Page label="Add-Ons" Name="AddOns">
<div class="input-group">
<div class="group-title" tooltip="Folders of updates and DLC that load automatically when playing a game. Individual add-ons can be disabled in Configure Game.">Add-On Directories</div>
<ListBox id="addOnDirectoryList" />
<div class="buttons-container">
<button id="addOnDirectoryAdd">Add</button>
<button id="addOnDirectoryRemove">Remove</button>
</div>
</div>
</Page>
</PageNav>
<div id="GameBrowserContents" class="page_content" />

View File

@ -1,21 +1,4 @@
<PageNav target="GeneralContents" id="GeneralTabNav" class="page_tabs">
<Page label="Game Browser" Name="GameBrowser">
<div class="input-group">
<div class="group-title">Game Directories</div>
<ListBox id="gameDirectoryList" />
<div class="buttons-container">
<button id="gameDirectoryAdd">Add</button>
<button id="gameDirectoryRemove">Remove</button>
</div>
</div>
<div class="input-group">
<div class="group-title">Game Browser</div>
<div class="form-row slider-row">
<div class="left"><label for="myGamesIconSize">My Games Icon Size:</label></div>
<div class="right"><input type="hslider" min="1" max="4" value="2" id="myGamesIconSize" style="width: 19em" /></div>
</div>
</div>
</Page>
<Page label="System" Name="System">
<div class="input-group">
<div class="form-row">

View File

@ -66,6 +66,7 @@ LoaderFileType GuessFromFilename(const std::string & name)
{
return LoaderFileType::NSP;
}
UNIMPLEMENTED();
return LoaderFileType::Unknown;
}
@ -80,6 +81,7 @@ std::string GetFileTypeString(LoaderFileType type)
case LoaderFileType::Unknown:
break;
}
UNIMPLEMENTED();
return "unknown";
}
@ -107,6 +109,7 @@ static std::shared_ptr<AppLoader> GetFileLoader(Systemloader & loader, FileSys::
case LoaderFileType::NSP:
return std::make_shared<AppLoader_NSP>(std::move(file), loader.GetFileSystemController(), loader.GetContentProvider(), program_id, program_index);
}
UNIMPLEMENTED();
return nullptr;
}

View File

@ -16,6 +16,7 @@ namespace
{
BooleanValue,
IntegerValue,
StringListValue,
TitleIdStringListMapValue,
};
@ -24,6 +25,7 @@ namespace
public:
LoaderSetting(const char * id, const char * path, bool * val, bool defaultValue);
LoaderSetting(const char * id, const char * path, int * val, int defaultValue);
LoaderSetting(const char * id, const char * path, StringList * val);
LoaderSetting(const char * path, TitleIdStringListMap * val);
const char * identifier;
@ -33,6 +35,7 @@ namespace
{
bool * boolValue;
int * intValue;
StringList * stringList;
TitleIdStringListMap * titleIdStringListMap;
} setting;
union
@ -46,6 +49,7 @@ namespace
{NXLoaderSetting::CheckForUpdatedFirmware, "CheckForUpdatedFirmware", &loaderSettings.checkForUpdatedFirmware, true},
{NXLoaderSetting::FirmwareInstallCurrent, nullptr, nullptr, 0},
{NXLoaderSetting::FirmwareInstallTotal, nullptr, nullptr, 0},
{NXLoaderSetting::AddOnDirectories, "AddOnDirectories", &loaderSettings.addOnDirectories},
{"DisabledAddOns", &loaderSettings.disabled_addons},
};
@ -124,6 +128,50 @@ namespace
}
return object;
}
void LoadStringList(StringList & out, const JsonValue & value)
{
out.clear();
if (!value.isArray())
{
return;
}
out.reserve(value.size());
for (uint32_t i = 0, n = value.size(); i < n; ++i)
{
if (value[i].isString())
{
out.push_back(value[i].asString());
}
}
}
std::string SerializeStringList(const StringList & list)
{
JsonValue jsonArray(JsonValueType::Array);
for (const std::string & item : list)
{
jsonArray.Append(JsonValue(item));
}
return JsonStyledWriter().write(jsonArray);
}
void ParseStringListFromStore(StringList & out, const char * setting)
{
out.clear();
const std::string json = g_settings->GetString(setting);
if (json.empty())
{
return;
}
JsonValue root;
JsonReader reader;
if (!reader.Parse(json.data(), json.data() + json.size(), root) || !root.isArray())
{
return;
}
LoadStringList(out, root);
}
};
void LoaderSettingChanged(const char * setting, void * /*userData*/)
@ -150,6 +198,12 @@ void LoaderSettingChanged(const char * setting, void * /*userData*/)
*loaderSetting.setting.intValue = g_settings->GetInt(setting);
}
break;
case SettingType::StringListValue:
if (loaderSetting.setting.stringList != nullptr)
{
ParseStringListFromStore(*loaderSetting.setting.stringList, setting);
}
break;
default:
UNIMPLEMENTED();
}
@ -171,6 +225,9 @@ void SetupLoaderSetting(void)
*loaderSetting.setting.intValue = loaderSetting.defaultValue.intValue;
}
break;
case SettingType::StringListValue:
loaderSetting.setting.stringList->clear();
break;
case SettingType::TitleIdStringListMapValue:
loaderSetting.setting.titleIdStringListMap->clear();
break;
@ -207,6 +264,9 @@ void SetupLoaderSetting(void)
*loaderSetting.setting.intValue = static_cast<int>(value.asInt64());
}
break;
case SettingType::StringListValue:
LoadStringList(*loaderSetting.setting.stringList, value);
break;
case SettingType::TitleIdStringListMapValue:
LoadTitleIdStringListMap(*loaderSetting.setting.titleIdStringListMap, value);
break;
@ -233,6 +293,10 @@ void SetupLoaderSetting(void)
g_settings->SetDefaultInt(loaderSetting.identifier, loaderSetting.defaultValue.intValue);
g_settings->SetInt(loaderSetting.identifier, loaderSetting.setting.intValue != nullptr ? *loaderSetting.setting.intValue : loaderSetting.defaultValue.intValue);
break;
case SettingType::StringListValue:
g_settings->SetDefaultString(loaderSetting.identifier, "[]");
g_settings->SetString(loaderSetting.identifier, SerializeStringList(*loaderSetting.setting.stringList).c_str());
break;
default:
UNIMPLEMENTED();
}
@ -283,6 +347,17 @@ void SaveLoaderSettings(void)
JsonSetNestedValue(root, loaderSetting.json_path, *loaderSetting.setting.intValue);
}
break;
case SettingType::StringListValue:
if (loaderSetting.json_path != nullptr && !loaderSetting.setting.stringList->empty())
{
JsonValue jsonList(JsonValueType::Array);
for (const std::string & item : *loaderSetting.setting.stringList)
{
jsonList.Append(JsonValue(item));
}
JsonSetNestedValue(root, loaderSetting.json_path, std::move(jsonList));
}
break;
case SettingType::TitleIdStringListMapValue:
{
JsonValue value = SaveTitleIdStringListMap(*loaderSetting.setting.titleIdStringListMap);
@ -330,6 +405,14 @@ LoaderSetting::LoaderSetting(const char * id, const char * path, int * val, int
defaultValue.intValue = defaultValue_;
}
LoaderSetting::LoaderSetting(const char * id, const char * path, StringList * val) :
identifier(id),
json_path(path),
settingType(SettingType::StringListValue)
{
setting.stringList = val;
}
LoaderSetting::LoaderSetting(const char * path, TitleIdStringListMap * val) :
identifier(nullptr),
json_path(path),

View File

@ -6,12 +6,14 @@
#include <vector>
using TitleIdStringListMap = std::map<uint64_t, std::vector<std::string>>;
using StringList = std::vector<std::string>;
struct LoaderSettings
{
bool checkForUpdatedFirmware;
// Add-Ons
StringList addOnDirectories;
TitleIdStringListMap disabled_addons;
};
@ -20,5 +22,6 @@ extern LoaderSettings loaderSettings;
void SetupLoaderSetting(void);
void CleanupLoaderSetting(void);
void SaveLoaderSettings(void);
void RefreshConfiguredAddOnDirectories(void);
void SetLoaderDisabledAddons(uint64_t program_id, std::vector<std::string> names);

View File

@ -6,4 +6,5 @@ constexpr const char * CheckForUpdatedFirmware = "nxloader:CheckForUpdatedFirmwa
constexpr const char * FirmwareInstallCurrent = "nxloader:FirmwareInstallCurrent";
constexpr const char * FirmwareInstallTotal = "nxloader:FirmwareInstallTotal";
constexpr const char * Has39BitAddressSpace = "nxloader:Has39BitAddressSpace";
constexpr const char * AddOnDirectories = "nxloader:AddOnDirectories";
} // namespace NXLoaderSetting

View File

@ -1,6 +1,7 @@
#include "system_loader.h"
#include "core/core.h"
#include "core/file_sys/card_image.h"
#include "core/file_sys/common_funcs.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/filesystem.h"
#include "core/file_sys/control_metadata.h"
@ -30,6 +31,7 @@
#include <nxemu-core/settings/identifiers.h>
#include <random>
#include <yuzu_common/fs/fs.h>
#include <yuzu_common/fs/filesystem_interfaces.h>
extern IModuleSettings * g_settings;
extern IModuleNotification * g_notify;
@ -78,7 +80,7 @@ std::string GetRegisteredNcaFilename(const std::string & source_name)
if (dot != std::string::npos)
{
ext = ext.substr(dot);
std::transform(ext.begin(), ext.end(), ext.begin(), [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
std::transform(ext.begin(), ext.end(), ext.begin(), [](unsigned char c) { return (char)std::tolower(c); });
}
else
{
@ -98,8 +100,7 @@ std::string GetRegisteredNcaFilename(const std::string & source_name)
std::string ToLowerAscii(std::string value)
{
std::transform(value.begin(), value.end(), value.begin(),
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
std::transform(value.begin(), value.end(), value.begin(), [](unsigned char c) { return (char)std::tolower(c); });
return value;
}
@ -386,7 +387,7 @@ bool InstallFirmwareFilesToRegistered(::FileSystemController & fs_controller, co
}
int32_t copied = 0;
g_settings->SetInt(NXLoaderSetting::FirmwareInstallTotal, static_cast<int32_t>(sources.size()));
g_settings->SetInt(NXLoaderSetting::FirmwareInstallTotal, (int32_t)sources.size());
g_settings->SetInt(NXLoaderSetting::FirmwareInstallCurrent, 0);
for (const FirmwareInstallSource & source : sources)
@ -434,6 +435,57 @@ bool HasSupportedFileExtension(const char * fileName)
return false;
}
bool IsAddOnContentExtension(const char * fileName)
{
static const char * supported_extensions[] = {
"nca",
"dxci",
"dnsp",
};
std::string ext = Path(fileName).GetExtension();
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
for (const char * supported : supported_extensions)
{
if (ext == supported)
{
return true;
}
}
return false;
}
void RegisterMatchingAddOnEntriesFromNsp(const FileSys::NSP & nsp, uint64_t base_program_id, IManualContentProvider & provider)
{
if (nsp.GetStatus() != LoaderResultStatus::Success)
{
return;
}
for (const auto & title : nsp.GetNCAs())
{
if (FileSys::GetBaseTitleID(title.first) != base_program_id)
{
continue;
}
for (const auto & entry : title.second)
{
if (entry.first.first != LoaderTitleType::Update && entry.first.first != LoaderTitleType::AOC)
{
continue;
}
FileSys::VirtualFile file = entry.second->BaseFile();
if (!file)
{
continue;
}
provider.AddEntry(entry.first.first, entry.first.second, title.first, std::make_unique<VirtualFileImpl>(file).release());
}
}
}
} // Anonymous namespace
class HostFilesystemImpl final :
@ -576,6 +628,7 @@ bool Systemloader::LoadRom(const char * fileName)
}
uint64_t program_id = 0;
const LoaderResultStatus res = app_loader->ReadProgramId(program_id);
RegisterConfiguredAddOnDirectories(res == LoaderResultStatus::Success ? program_id : 0);
if (res == LoaderResultStatus::Success && file_type == LoaderFileType::NCA)
{
UNIMPLEMENTED();
@ -583,10 +636,22 @@ bool Systemloader::LoadRom(const char * fileName)
else if (res == LoaderResultStatus::Success && (file_type == LoaderFileType::XCI || file_type == LoaderFileType::NSP))
{
const auto nsp = file_type == LoaderFileType::NSP ? std::make_shared<FileSys::NSP>(impl->m_file) : FileSys::XCI{impl->m_file}.GetSecurePartitionNSP();
FileSys::ManualContentProvider & manual = impl->m_manualContentProvider->Provider();
for (const auto & title : nsp->GetNCAs())
{
for (const auto & entry : title.second)
{
if (entry.first.first == LoaderTitleType::Update &&
manual.HasEntry(title.first, LoaderContentRecordType::Program))
{
continue;
}
if (entry.first.first == LoaderTitleType::AOC &&
manual.HasEntry(title.first, entry.first.second))
{
continue;
}
FileSys::VirtualFile file = entry.second->BaseFile();
impl->m_manualContentProvider->AddEntry(entry.first.first, entry.first.second, title.first, std::make_unique<VirtualFileImpl>(file).release());
}
@ -833,16 +898,96 @@ IManualContentProvider & Systemloader::ManualContentProvider()
return *(impl->m_manualContentProvider.get());
}
void Systemloader::RegisterConfiguredAddOnDirectories(uint64_t program_id)
{
IManualContentProvider & provider = ManualContentProvider();
provider.ClearAllEntries();
if (program_id == 0 || loaderSettings.addOnDirectories.empty())
{
return;
}
const uint64_t base_program_id = FileSys::GetBaseTitleID(program_id);
FileSys::VirtualFilesystem & vfs = impl->m_virtualFilesystem;
for (const std::string & dir : loaderSettings.addOnDirectories)
{
if (dir.empty())
{
continue;
}
const auto callback = [&](const std::filesystem::directory_entry & entry) -> bool {
const std::filesystem::path & path = entry.path();
if (!Common::FS::IsFile(path))
{
return true;
}
const std::string physical_name = Common::FS::PathToUTF8String(path);
if (!IsAddOnContentExtension(physical_name.c_str()))
{
return true;
}
FileSys::VirtualFile file = vfs->OpenFile(physical_name, VirtualFileOpenMode::Read);
if (!file)
{
return true;
}
const std::string ext = ToLowerAscii(std::string(Path(physical_name).GetExtension()));
if (ext == "dnsp")
{
RegisterMatchingAddOnEntriesFromNsp(FileSys::NSP(file), base_program_id, provider);
}
else if (ext == "dxci")
{
const FileSys::XCI xci(file);
if (xci.GetStatus() == LoaderResultStatus::Success)
{
const std::shared_ptr<FileSys::NSP> nsp = xci.GetSecurePartitionNSP();
if (nsp)
{
RegisterMatchingAddOnEntriesFromNsp(*nsp, base_program_id, provider);
}
}
}
else if (ext == "nca")
{
const FileSys::NCA nca(file);
if (nca.GetStatus() != LoaderResultStatus::Success)
{
return true;
}
const uint64_t title_id = nca.GetTitleId();
if (FileSys::GetBaseTitleID(title_id) != base_program_id || title_id == base_program_id)
{
return true;
}
const LoaderTitleType title_type = (title_id & 0x800) != 0 ? LoaderTitleType::Update : LoaderTitleType::AOC;
provider.AddEntry(title_type, FileSys::GetCRTypeFromNCAType(nca.GetType()), title_id, std::make_unique<VirtualFileImpl>(file).release());
}
return true;
};
Common::FS::IterateDirEntriesRecursively(dir, callback, Common::FS::DirEntryFilter::File);
}
}
uint32_t Systemloader::GetGamePatches(uint64_t program_id, GamePatchInfo * patches, uint32_t maxCount)
{
RegisterConfiguredAddOnDirectories(program_id);
const FileSys::PatchManager pm(program_id, GetFileSystemController(), GetContentProvider());
const std::vector<FileSys::Patch> list = pm.GetPatches();
if (patches == nullptr || maxCount == 0)
{
return static_cast<uint32_t>(list.size());
return (uint32_t)list.size();
}
const uint32_t count = std::min(maxCount, static_cast<uint32_t>(list.size()));
const uint32_t count = std::min(maxCount, (uint32_t)list.size());
for (uint32_t i = 0; i < count; ++i)
{
patches[i].enabled = list[i].enabled;
@ -907,7 +1052,7 @@ bool Systemloader::Impl::InstallFirmwareFromFolder(const char * utf8_folder_path
continue;
}
std::string ext = entry.path().extension().string();
std::transform(ext.begin(), ext.end(), ext.begin(), [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
std::transform(ext.begin(), ext.end(), ext.begin(), [](unsigned char c) { return (char)std::tolower(c); });
if (ext == ".dnca")
{
nca_paths.push_back(entry.path());
@ -1115,7 +1260,7 @@ bool Systemloader::Impl::QueryFirmwarePackage(const char * utf8_path, FirmwareVe
continue;
}
std::string ext = entry.path().extension().string();
std::transform(ext.begin(), ext.end(), ext.begin(), [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
std::transform(ext.begin(), ext.end(), ext.begin(), [](unsigned char c) { return (char)std::tolower(c); });
if (ext == ".dnca")
{
nca_paths.push_back(entry.path());
@ -1240,7 +1385,7 @@ uint32_t Systemloader::GetInstalledFirmwareDisplayVersion(char * buffer, uint32_
return 0;
}
const uint32_t copy_len = static_cast<uint32_t>(std::min(version.size(), static_cast<size_t>(buffer_size - 1)));
const uint32_t copy_len = (uint32_t)(std::min(version.size(), (size_t)(buffer_size - 1)));
std::memcpy(buffer, version.data(), copy_len);
buffer[copy_len] = '\0';
return copy_len;

View File

@ -50,6 +50,7 @@ public:
uint32_t GetGamePatches(uint64_t program_id, GamePatchInfo * patches, uint32_t maxCount) override;
void SetDisabledAddons(uint64_t program_id, const char * const * names, uint32_t count) override;
bool IsAddonDisabled(uint64_t program_id, const char * name) const override;
void RegisterConfiguredAddOnDirectories(uint64_t program_id);
private:
Systemloader() = delete;

View File

@ -2144,6 +2144,7 @@ bool SciterMainWindow::OnTimer(SCITER_ELEMENT /*element*/, uint32_t * timerId)
m_gameConfig.reset(new GameConfig(m_sciterUI, m_modules));
m_gameConfig->Display((void *)m_window->GetHandle(), path.c_str());
}
return false;
}
else if (timerId == (uint32_t *)TIMER_DEFERRED_STOP_GAME)
{

View File

@ -2,6 +2,7 @@
#include "settings/ui_settings.h"
#include "system_config.h"
#include "system_config_audio.h"
#include "system_config_game_browser.h"
#include "system_config_general.h"
#include "system_config_graphics.h"
#include "system_config_profiles.h"
@ -73,6 +74,10 @@ void SystemConfig::Display(void * parentWindow, const char * startPage)
{
m_systemConfigGeneral->SetInitialPage(subPage.c_str());
}
else if (initialPage == "GameBrowser" && m_systemConfigGameBrowser)
{
m_systemConfigGameBrowser->SetInitialPage(subPage.c_str());
}
}
}
}
@ -331,6 +336,10 @@ void SystemConfig::PageNavCreatedPage(const std::string & pageName, SCITER_ELEME
{
m_systemConfigGeneral.reset(new SystemConfigGeneral(m_sciterUI, *this, *m_window, page));
}
else if (pageName == "GameBrowser")
{
m_systemConfigGameBrowser.reset(new SystemConfigGameBrowser(m_sciterUI, *this, *m_window, page));
}
else if (pageName == "Profiles")
{
m_systemConfigProfiles.reset(new SystemConfigProfiles(m_sciterUI, *this, m_modules, *m_window, page));
@ -358,6 +367,10 @@ bool SystemConfig::OnClick(SCITER_ELEMENT element, SCITER_ELEMENT /*source*/, ui
{
m_systemConfigGeneral->SaveSetting();
}
if (m_systemConfigGameBrowser)
{
m_systemConfigGameBrowser->SaveSetting();
}
if (m_systemConfigProfiles)
{
m_systemConfigProfiles->SaveSetting();

View File

@ -13,6 +13,7 @@ class SciterElement;
class SystemConfigAudio;
class SystemConfigGraphics;
class SystemConfigGeneral;
class SystemConfigGameBrowser;
class SystemConfigProfiles;
class SystemConfigSystem;
class ConfigSetting;
@ -87,5 +88,6 @@ private:
std::unique_ptr<SystemConfigGraphics> m_systemConfigGraphics;
std::unique_ptr<SystemConfigAudio> m_systemConfigAudio;
std::unique_ptr<SystemConfigGeneral> m_systemConfigGeneral;
std::unique_ptr<SystemConfigGameBrowser> m_systemConfigGameBrowser;
std::unique_ptr<SystemConfigProfiles> m_systemConfigProfiles;
};

View File

@ -1,19 +1,21 @@
#include "config_setting.h"
#include "settings/ui_identifiers.h"
#include "settings/ui_settings.h"
#include "system_config.h"
#include "system_config_game_browser.h"
#include <common/path.h>
#include <nxemu-core/settings/identifiers.h>
#include <nxemu-loader/loader_settings_identifiers.h>
#include <widgets/list_box.h>
namespace
{
static ConfigSetting gameBrowserSettings[] = {
static ConfigSetting gamesSettings[] = {
ConfigSetting(ConfigSetting::Slider, "myGamesIconSize", true, NXUISetting::MyGameIconSize),
ConfigSetting(ConfigSetting::CheckBox, "ConfirmBeforeStopping", true, NXUISetting::ConfirmBeforeStopping),
ConfigSetting(ConfigSetting::ListBox, "gameDirectoryList", true, NXUISetting::GameDirectories),
};
static ConfigSetting addOnsSettings[] = {
ConfigSetting(ConfigSetting::ListBox, "addOnDirectoryList", true, NXLoaderSetting::AddOnDirectories),
};
}
SystemConfigGameBrowser::SystemConfigGameBrowser(ISciterUI & sciterUI, SystemConfig & config, ISciterWindow & window, SciterElement page) :
@ -22,28 +24,76 @@ SystemConfigGameBrowser::SystemConfigGameBrowser(ISciterUI & sciterUI, SystemCon
m_window(window),
m_page(page)
{
m_config.SetupPage(page, gameBrowserSettings, sizeof(gameBrowserSettings) / sizeof(gameBrowserSettings[0]));
SciterElement root(m_window.GetRootElement());
if (root.IsValid())
SciterElement pageNav = page.GetElementByID("GameBrowserTabNav");
std::shared_ptr<void> interfacePtr = pageNav.IsValid() ? m_sciterUI.GetElementInterface(pageNav, IID_IPAGENAV) : nullptr;
if (interfacePtr)
{
SciterElement addButton = root.GetElementByID("gameDirectoryAdd");
m_sciterUI.AttachHandler(addButton, IID_ICLICKSINK, (IClickSink *)this);
SciterElement removeButton = root.GetElementByID("gameDirectoryRemove");
m_sciterUI.AttachHandler(removeButton, IID_ICLICKSINK, (IClickSink *)this);
m_pageNav = std::static_pointer_cast<IPageNav>(interfacePtr);
m_pageNav->AddSink(this);
}
}
void SystemConfigGameBrowser::SetInitialPage(const char * path)
{
if (path == nullptr || path[0] == '\0' || !m_pageNav)
{
return;
}
m_pageNav->SetCurrentPage(path);
}
void SystemConfigGameBrowser::SetupGamesPage(SciterElement page)
{
m_gamesPage = page;
m_config.SetupPage(page, gamesSettings, sizeof(gamesSettings) / sizeof(gamesSettings[0]));
m_sciterUI.AttachHandler(page.GetElementByID("gameDirectoryAdd"), IID_ICLICKSINK, (IClickSink *)this);
m_sciterUI.AttachHandler(page.GetElementByID("gameDirectoryRemove"), IID_ICLICKSINK, (IClickSink *)this);
}
void SystemConfigGameBrowser::SetupAddOnsPage(SciterElement page)
{
m_addOnsPage = page;
m_config.SetupPage(page, addOnsSettings, sizeof(addOnsSettings) / sizeof(addOnsSettings[0]));
m_sciterUI.AttachHandler(page.GetElementByID("addOnDirectoryAdd"), IID_ICLICKSINK, (IClickSink *)this);
m_sciterUI.AttachHandler(page.GetElementByID("addOnDirectoryRemove"), IID_ICLICKSINK, (IClickSink *)this);
}
bool SystemConfigGameBrowser::PageNavChangeFrom(const std::string & /*pageName*/, SCITER_ELEMENT /*pageNav*/)
{
return true;
}
bool SystemConfigGameBrowser::PageNavChangeTo(const std::string & /*pageName*/, SCITER_ELEMENT /*pageNav*/)
{
return true;
}
void SystemConfigGameBrowser::PageNavCreatedPage(const std::string & pageName, SCITER_ELEMENT page)
{
if (pageName == "Games")
{
SetupGamesPage(page);
}
else if (pageName == "AddOns")
{
SetupAddOnsPage(page);
}
}
void SystemConfigGameBrowser::PageNavPageChanged(const std::string & /*pageName*/, SCITER_ELEMENT /*pageNav*/)
{
}
bool SystemConfigGameBrowser::OnClick(SCITER_ELEMENT element, SCITER_ELEMENT /*source*/, uint32_t /*reason*/)
{
SciterElement clickElem(element);
std::string elementID = clickElem.GetAttributeByName("id");
const std::string elementID = clickElem.GetAttributeByName("id");
if (elementID == "gameDirectoryAdd")
{
Path newDirectory = Path().BrowseForDirectory((void*)m_window.GetHandle(), "Select Game Directory");
if (newDirectory.DirectoryExists())
Path newDirectory = Path().BrowseForDirectory((void *)m_window.GetHandle(), "Select Game Directory");
if (newDirectory.DirectoryExists() && m_gamesPage.IsValid())
{
std::shared_ptr<void> interfacePtr = m_sciterUI.GetElementInterface(m_page.GetElementByID("gameDirectoryList"), IID_ILISTBOX);
std::shared_ptr<void> interfacePtr = m_sciterUI.GetElementInterface(m_gamesPage.GetElementByID("gameDirectoryList"), IID_ILISTBOX);
if (interfacePtr)
{
std::shared_ptr<IListBox> listBox = std::static_pointer_cast<IListBox>(interfacePtr);
@ -53,47 +103,40 @@ bool SystemConfigGameBrowser::OnClick(SCITER_ELEMENT element, SCITER_ELEMENT /*s
}
else if (elementID == "gameDirectoryRemove")
{
RemoveSelectedDirectory();
RemoveSelectedDirectory(m_gamesPage, "gameDirectoryList");
}
else if (elementID == "addOnDirectoryAdd")
{
Path newDirectory = Path().BrowseForDirectory((void *)m_window.GetHandle(), "Select Add-On Directory");
if (newDirectory.DirectoryExists() && m_addOnsPage.IsValid())
{
std::shared_ptr<void> interfacePtr = m_sciterUI.GetElementInterface(m_addOnsPage.GetElementByID("addOnDirectoryList"), IID_ILISTBOX);
if (interfacePtr)
{
std::shared_ptr<IListBox> listBox = std::static_pointer_cast<IListBox>(interfacePtr);
listBox->AddItem(newDirectory, newDirectory);
}
}
}
else if (elementID == "addOnDirectoryRemove")
{
RemoveSelectedDirectory(m_addOnsPage, "addOnDirectoryList");
}
return true;
}
void SystemConfigGameBrowser::SelectDirectoryItem(const SciterElement & gameDirectoryList, SCITER_ELEMENT source)
void SystemConfigGameBrowser::RemoveSelectedDirectory(const SciterElement & page, const char * listId)
{
SciterElement target = source, directoryItem;
while (target.IsValid() && target != gameDirectoryList)
{
std::string className = target.GetAttribute("class");
if (!className.empty())
{
std::string searchIn = " " + className + " ";
if (searchIn.find(" directory-item ") != std::string::npos)
{
directoryItem = target;
break;
}
}
target = target.GetParent();
}
if (target.IsValid())
{
for (uint32_t i = 0, n = gameDirectoryList.GetChildCount(); i < n; i++)
{
SciterElement(gameDirectoryList.GetChild(i)).SetState(0, SciterElement::STATE_CHECKED, true);
}
target.SetState(SciterElement::STATE_CHECKED, 0, true);
}
}
void SystemConfigGameBrowser::RemoveSelectedDirectory()
{
SciterElement gameDirectoryList = m_page.GetElementByID("gameDirectoryList");
if (!gameDirectoryList.IsValid())
if (!page.IsValid())
{
return;
}
std::shared_ptr<void> interfacePtr = m_sciterUI.GetElementInterface(m_page.GetElementByID("gameDirectoryList"), IID_ILISTBOX);
SciterElement directoryList = page.GetElementByID(listId);
if (!directoryList.IsValid())
{
return;
}
std::shared_ptr<void> interfacePtr = m_sciterUI.GetElementInterface(directoryList, IID_ILISTBOX);
if (!interfacePtr)
{
return;
@ -104,5 +147,12 @@ void SystemConfigGameBrowser::RemoveSelectedDirectory()
void SystemConfigGameBrowser::SaveSetting(void)
{
m_config.SavePage(m_page, gameBrowserSettings, sizeof(gameBrowserSettings) / sizeof(gameBrowserSettings[0]));
if (m_gamesPage.IsValid())
{
m_config.SavePage(m_gamesPage, gamesSettings, sizeof(gamesSettings) / sizeof(gamesSettings[0]));
}
if (m_addOnsPage.IsValid())
{
m_config.SavePage(m_addOnsPage, addOnsSettings, sizeof(addOnsSettings) / sizeof(addOnsSettings[0]));
}
}

View File

@ -7,6 +7,7 @@
class SystemConfig;
class SystemConfigGameBrowser :
public IPagesSink,
public IClickSink
{
public:
@ -14,20 +15,31 @@ public:
~SystemConfigGameBrowser() = default;
void SaveSetting(void);
void SetInitialPage(const char * path);
// IPagesSink
bool PageNavChangeFrom(const std::string & pageName, SCITER_ELEMENT pageNav) override;
bool PageNavChangeTo(const std::string & pageName, SCITER_ELEMENT pageNav) override;
void PageNavCreatedPage(const std::string & pageName, SCITER_ELEMENT page) override;
void PageNavPageChanged(const std::string & pageName, SCITER_ELEMENT pageNav) override;
// IClickSink
bool OnClick(SCITER_ELEMENT element, SCITER_ELEMENT source, uint32_t reason) override;
private:
SystemConfigGameBrowser() = delete;
SystemConfigGameBrowser(const SystemConfigGameBrowser&) = delete;
SystemConfigGameBrowser& operator=(const SystemConfigGameBrowser&) = delete;
SystemConfigGameBrowser(const SystemConfigGameBrowser &) = delete;
SystemConfigGameBrowser & operator=(const SystemConfigGameBrowser &) = delete;
void SelectDirectoryItem(const SciterElement & gameDirectoryList, SCITER_ELEMENT source);
void RemoveSelectedDirectory();
void SetupGamesPage(SciterElement page);
void SetupAddOnsPage(SciterElement page);
void RemoveSelectedDirectory(const SciterElement & page, const char * listId);
ISciterUI & m_sciterUI;
SystemConfig & m_config;
ISciterWindow & m_window;
SciterElement m_page;
SciterElement m_gamesPage;
SciterElement m_addOnsPage;
std::shared_ptr<IPageNav> m_pageNav;
};

View File

@ -1,4 +1,3 @@
#include "system_config_game_browser.h"
#include "system_config_general.h"
#include "system_config_hotkeys.h"
#include "system_config_logging.h"
@ -34,10 +33,6 @@ void SystemConfigGeneral::SetInitialPage(const char * path)
void SystemConfigGeneral::SaveSetting(void)
{
if (m_systemConfigGameBrowser)
{
m_systemConfigGameBrowser->SaveSetting();
}
if (m_systemConfigLogging)
{
m_systemConfigLogging->SaveSetting();
@ -64,11 +59,7 @@ bool SystemConfigGeneral::PageNavChangeTo(const std::string & /*pageName*/, SCIT
void SystemConfigGeneral::PageNavCreatedPage(const std::string & pageName, SCITER_ELEMENT page)
{
if (pageName == "GameBrowser")
{
m_systemConfigGameBrowser.reset(new SystemConfigGameBrowser(m_sciterUI, m_config, m_window, page));
}
else if (pageName == "Logging")
if (pageName == "Logging")
{
m_systemConfigLogging.reset(new SystemConfigLogging(m_sciterUI, m_config, m_window, page));
}

View File

@ -4,7 +4,6 @@
#include <widgets/page_nav.h>
class SystemConfig;
class SystemConfigGameBrowser;
class SystemConfigHotkeys;
class SystemConfigLogging;
class SystemConfigSystem;
@ -36,7 +35,6 @@ private:
SciterElement m_page;
std::shared_ptr<IPageNav> m_pageNav;
std::unique_ptr<SystemConfigLogging> m_systemConfigLogging;
std::unique_ptr<SystemConfigGameBrowser> m_systemConfigGameBrowser;
std::unique_ptr<SystemConfigHotkeys> m_systemConfigHotkeys;
std::unique_ptr<SystemConfigSystem> m_systemConfigSystem;
};

View File

@ -249,12 +249,6 @@ bool WidgetRomBrowser::OnEvent(SCITER_ELEMENT element, SCITER_ELEMENT source, ui
}
return false;
}
if (event_code == (uint32_t)SciterBehaviorEvent::MenuItemClick)
{
__debugbreak();
}
return false;
}
@ -273,7 +267,7 @@ bool WidgetRomBrowser::OnClick(SCITER_ELEMENT element, SCITER_ELEMENT source, ui
std::string className = el.GetAttribute("class");
if (strcmp(className.c_str(), "config-button") == 0 && m_window != nullptr)
{
m_window->ShowConfig("General:GameBrowser");
m_window->ShowConfig("GameBrowser");
return true;
}
else if (strcmp(className.c_str(), "rom-card") == 0)