UI: update how install firmware works

This commit is contained in:
N3xoX1 2026-05-30 10:02:12 +09:30
parent d41b232322
commit e4713965c3
6 changed files with 292 additions and 9 deletions

View File

@ -284,6 +284,17 @@ rombrowser .games-grid[data-size="4"] .rom-card
margin-bottom: *;
}
#LoadingPanel .loading-main.no-game-icon .loading-text-column
{
margin-left: *;
text-align: center;
}
#LoadingPanel .loading-main.no-game-icon .loading-progress-wrap
{
width: 20rem;
}
#LoadingCornerBanner
{
horizontal-align: right;

View File

@ -13,12 +13,14 @@ namespace
enum class SettingType
{
BooleanValue,
IntegerValue,
};
class LoaderSetting
{
public:
LoaderSetting(const char * id, const char * path, bool * val, bool defaultValue);
LoaderSetting(const char * id, int defaultValue);
const char * identifier;
const char * json_path;
@ -26,15 +28,19 @@ namespace
union
{
bool * boolValue;
int * intValue;
} setting;
union
{
bool boolValue;
int intValue;
} defaultValue;
};
static LoaderSetting settings[] = {
{NXLoaderSetting::CheckForUpdatedFirmware, "CheckForUpdatedFirmware", &loaderSettings.checkForUpdatedFirmware, true},
{NXLoaderSetting::FirmwareInstallCurrent, 0},
{NXLoaderSetting::FirmwareInstallTotal, 0},
};
};
@ -56,6 +62,12 @@ void LoaderSettingChanged(const char * setting, void * /*userData*/)
case SettingType::BooleanValue:
*loaderSetting.setting.boolValue = g_settings->GetBool(setting);
break;
case SettingType::IntegerValue:
if (loaderSetting.setting.intValue != nullptr)
{
*loaderSetting.setting.intValue = g_settings->GetInt(setting);
}
break;
default:
UNIMPLEMENTED();
}
@ -71,6 +83,12 @@ void SetupLoaderSetting(void)
case SettingType::BooleanValue:
*loaderSetting.setting.boolValue = loaderSetting.defaultValue.boolValue;
break;
case SettingType::IntegerValue:
if (loaderSetting.setting.intValue != nullptr)
{
*loaderSetting.setting.intValue = loaderSetting.defaultValue.intValue;
}
break;
default:
UNIMPLEMENTED();
}
@ -84,6 +102,11 @@ void SetupLoaderSetting(void)
{
for (const LoaderSetting & loaderSetting : settings)
{
if (loaderSetting.json_path == nullptr)
{
continue;
}
const JsonValue value = JsonGetNestedValue(root, loaderSetting.json_path);
switch (loaderSetting.settingType)
{
@ -93,6 +116,8 @@ void SetupLoaderSetting(void)
*loaderSetting.setting.boolValue = value.asBool();
}
break;
case SettingType::IntegerValue:
break;
default:
UNIMPLEMENTED();
}
@ -112,6 +137,10 @@ void SetupLoaderSetting(void)
g_settings->SetDefaultBool(loaderSetting.identifier, loaderSetting.defaultValue.boolValue);
g_settings->SetBool(loaderSetting.identifier, *loaderSetting.setting.boolValue);
break;
case SettingType::IntegerValue:
g_settings->SetDefaultInt(loaderSetting.identifier, loaderSetting.defaultValue.intValue);
g_settings->SetInt(loaderSetting.identifier, loaderSetting.setting.intValue != nullptr ? *loaderSetting.setting.intValue : loaderSetting.defaultValue.intValue);
break;
default:
UNIMPLEMENTED();
}
@ -133,6 +162,8 @@ void SaveLoaderSettings(void)
JsonSetNestedValue(root, loaderSetting.json_path, *loaderSetting.setting.boolValue != 0);
}
break;
case SettingType::IntegerValue:
break;
default:
UNIMPLEMENTED();
}
@ -148,3 +179,12 @@ LoaderSetting::LoaderSetting(const char * id, const char * path, bool * val, boo
setting.boolValue = val;
defaultValue.boolValue = defaultValue_;
}
LoaderSetting::LoaderSetting(const char * id, int defaultValue_) :
identifier(id),
json_path(nullptr),
settingType(SettingType::IntegerValue)
{
setting.intValue = nullptr;
defaultValue.intValue = defaultValue_;
}

View File

@ -3,4 +3,6 @@
namespace NXLoaderSetting
{
constexpr const char * CheckForUpdatedFirmware = "nxloader:CheckForUpdatedFirmware";
constexpr const char * FirmwareInstallCurrent = "nxloader:FirmwareInstallCurrent";
constexpr const char * FirmwareInstallTotal = "nxloader:FirmwareInstallTotal";
} // namespace NXLoaderSetting

View File

@ -17,6 +17,7 @@
#include "core/loader/loader.h"
#include "firmware_zip.h"
#include "loader_settings.h"
#include "loader_settings_identifiers.h"
#include "rom_info.h"
#include <algorithm>
#include <cctype>
@ -257,6 +258,12 @@ std::filesystem::path CreateFirmwareExtractDirectory()
return {};
}
void ClearFirmwareInstallProgress()
{
g_settings->SetInt(NXLoaderSetting::FirmwareInstallCurrent, 0);
g_settings->SetInt(NXLoaderSetting::FirmwareInstallTotal, 0);
}
void CollectFirmwareSourcesFromPath(const std::filesystem::path & path, const FileSys::VirtualFilesystem & vfs, std::vector<FirmwareInstallSource> & sources)
{
std::error_code ec;
@ -376,10 +383,15 @@ bool InstallFirmwareFilesToRegistered(::FileSystemController & fs_controller, co
return false;
}
int32_t copied = 0;
g_settings->SetInt(NXLoaderSetting::FirmwareInstallTotal, static_cast<int32_t>(sources.size()));
g_settings->SetInt(NXLoaderSetting::FirmwareInstallCurrent, 0);
for (const FirmwareInstallSource & source : sources)
{
if (!source.file)
{
ClearFirmwareInstallProgress();
g_notify->DisplayError("Copying one or more firmware files failed. See the log for details.", "Firmware install failed");
return false;
}
@ -389,11 +401,14 @@ bool InstallFirmwareFilesToRegistered(::FileSystemController & fs_controller, co
if (!dst_file || !FileSys::VfsRawCopy(source.file, dst_file))
{
LOG_ERROR(Core, "Firmware install: copy failed for {}", filename);
ClearFirmwareInstallProgress();
g_notify->DisplayError("Copying one or more firmware files failed. See the log for details.", "Firmware install failed");
return false;
}
g_settings->SetInt(NXLoaderSetting::FirmwareInstallCurrent, ++copied);
}
ClearFirmwareInstallProgress();
return true;
}
@ -1192,6 +1207,8 @@ bool Systemloader::InstallFirmwarePackage(const char * utf8_path)
return false;
}
ClearFirmwareInstallProgress();
FirmwareVersionFormat package_firmware{};
if (!impl->QueryFirmwarePackage(utf8_path, &package_firmware))
{
@ -1232,5 +1249,7 @@ bool Systemloader::InstallFirmwarePackage(const char * utf8_path)
{
g_notify->DisplayError("Firmware installed successfully.", "Firmware installed");
}
ClearFirmwareInstallProgress();
return installed;
}

View File

@ -7,6 +7,7 @@
#include <common/std_string.h>
#include <nxemu-core/notification.h>
#include <nxemu-core/settings/identifiers.h>
#include <nxemu-loader/loader_settings_identifiers.h>
#include <nxemu-core/settings/settings.h>
#include <nxemu-core/version.h>
#include <nxemu-module-spec/operating_system.h>
@ -45,6 +46,9 @@ enum
EVENT_EMULATION_STOPPED = 0x2002,
EVENT_EMULATION_FIRST_FRAME = 0x2003,
EVENT_DISK_CACHE_STATUS = 0x2004,
EVENT_FIRMWARE_INSTALL_DONE = 0x2005,
EVENT_FIRMWARE_INSTALL_ACTIVE = 0x2006,
EVENT_FIRMWARE_INSTALL_FINISHED = 0x2007,
};
const uint32_t kKeyboardStateControl = 0x0040u | 0x0080u;
@ -196,8 +200,7 @@ std::string GetGameTitleLoadingHtml(ISystemloader & loader, const char * verb)
return stdstr_f("<span class=\"loading-verb\">%s</span> <span class=\"loading-game-name\">...</span>", verb);
}
const std::string titleEsc = HtmlEscapeForHtmlContent(std::string(buf.data()));
return stdstr_f("<span class=\"loading-verb\">%s</span> <span class=\"loading-game-name\">%s</span>", verb,
titleEsc.c_str());
return stdstr_f("<span class=\"loading-verb\">%s</span> <span class=\"loading-game-name\">%s</span>", verb, titleEsc.c_str());
}
} // namespace
@ -213,7 +216,9 @@ SciterMainWindow::SciterMainWindow(ISciterUI & sciterUI, const char * windowTitl
m_lastDiskCacheStatusPostMs(0),
m_lastPostedDiskCacheStage(0),
m_shownFirstFrame(false),
m_win32Fullscreen(std::make_unique<Win32FullscreenState>())
m_win32Fullscreen(std::make_unique<Win32FullscreenState>()),
m_firmwareInstallInProgress(false),
m_firmwareInstallUiActive(false)
{
SettingsStore & settings = SettingsStore::GetInstance();
settings.RegisterCallback(NXCoreSetting::EmulationRunning, SciterMainWindow::EmulationRunning, this);
@ -222,6 +227,7 @@ SciterMainWindow::SciterMainWindow(ISciterUI & sciterUI, const char * windowTitl
settings.RegisterCallback(NXCoreSetting::GameName, SciterMainWindow::GameNameChanged, this);
settings.RegisterCallback(NXCoreSetting::DisplayedFrames, SciterMainWindow::DisplayedFramesChanged, this);
settings.RegisterCallback(NXCoreSetting::DiskCacheLoadTick, SciterMainWindow::DiskCacheLoadChanged, this);
settings.RegisterCallback(NXLoaderSetting::FirmwareInstallTotal, SciterMainWindow::FirmwareInstallTotalChanged, this);
settings.RegisterCallback(NXOsSetting::AudioMuted, SciterMainWindow::SettingChanged, this);
settings.RegisterCallback(NXOsSetting::AudioVolume, SciterMainWindow::SettingChanged, this);
settings.RegisterCallback(NXOsSetting::ResolutionUpFactor, SciterMainWindow::SettingChanged, this);
@ -307,6 +313,7 @@ SciterMainWindow::~SciterMainWindow()
settings.UnregisterCallback(NXCoreSetting::GameName, SciterMainWindow::GameNameChanged, this);
settings.UnregisterCallback(NXCoreSetting::DisplayedFrames, SciterMainWindow::DisplayedFramesChanged, this);
settings.UnregisterCallback(NXCoreSetting::DiskCacheLoadTick, SciterMainWindow::DiskCacheLoadChanged, this);
settings.UnregisterCallback(NXLoaderSetting::FirmwareInstallTotal, SciterMainWindow::FirmwareInstallTotalChanged, this);
settings.UnregisterCallback(NXOsSetting::AudioMuted, SciterMainWindow::SettingChanged, this);
settings.UnregisterCallback(NXOsSetting::AudioVolume, SciterMainWindow::SettingChanged, this);
settings.UnregisterCallback(NXOsSetting::ResolutionUpFactor, SciterMainWindow::SettingChanged, this);
@ -316,6 +323,11 @@ SciterMainWindow::~SciterMainWindow()
settings.UnregisterCallback(NXOsSetting::DockedMode, SciterMainWindow::SettingChanged, this);
settings.UnregisterCallback(NXUISetting::Hotkeys, SciterMainWindow::HotKeysChanged, this);
m_rootElement.SetTimer(0, (uint32_t *)TIMER_UPDATE_INSTALL_FIRMWARE);
if (m_firmwareInstallThread.joinable())
{
m_firmwareInstallThread.join();
}
m_systemConfig.reset(nullptr);
m_inputConfig.reset(nullptr);
@ -406,7 +418,7 @@ void SciterMainWindow::ResetMenu()
optionsMenu.push_back(MenuBarItem(static_cast<int32_t>(GuiAction::OpenControllersDialog), "&Controllers...", nullptr, HotkeyAccelerator(Hotkey::Controllers), MenuBarItem::CheckState::None, MenuIconSvg(GuiAction::OpenControllersDialog)));
optionsMenu.push_back(MenuBarItem(static_cast<int32_t>(GuiAction::OpenSystemConfiguration), "Confi&gure...", nullptr, HotkeyAccelerator(Hotkey::Configure), MenuBarItem::CheckState::None, MenuIconSvg(GuiAction::OpenSystemConfiguration)));
MenuBarItemList installFirmwareMenu;
if (!m_emulationRunning && m_modules.IsValid())
if (!m_emulationRunning && !m_firmwareInstallInProgress && m_modules.IsValid())
{
optionsMenu.push_back(MenuBarItem(MenuBarItem::SPLITER));
const std::string * installFirmwareSvg = MenuIconSvg(GuiAction::InstallFirmwareFromFile);
@ -642,6 +654,12 @@ void SciterMainWindow::EmulationStateChanged(const char * /*setting*/, void * us
if (state == EmulationState::RomLoaded)
{
impl->UpdateLoadingScreenDetails();
SciterElement loadingMain(impl->m_rootElement.FindFirst("#LoadingPanel .loading-main"));
if (loadingMain.IsValid())
{
loadingMain.RemoveClassName("no-game-icon");
}
impl->RefreshDiskCacheLoadingText();
impl->m_shownFirstFrame = false;
impl->ShowPanel(Panel::Loading);
}
@ -837,7 +855,7 @@ void SciterMainWindow::OnOpenFile()
void SciterMainWindow::OnInstallFirmwareFromFile()
{
if (m_window == nullptr || m_emulationRunning || !m_modules.IsValid())
if (m_window == nullptr || m_emulationRunning || !m_modules.IsValid() || m_firmwareInstallInProgress)
{
return;
}
@ -850,13 +868,12 @@ void SciterMainWindow::OnInstallFirmwareFromFile()
return;
}
m_modules.Modules().Systemloader().InstallFirmwarePackage(file);
UpdateEmulationStatusText();
BeginFirmwareInstall(file);
}
void SciterMainWindow::OnInstallFirmwareFromFolder()
{
if (m_window == nullptr || m_emulationRunning || !m_modules.IsValid())
if (m_window == nullptr || m_emulationRunning || !m_modules.IsValid() || m_firmwareInstallInProgress)
{
return;
}
@ -868,8 +885,166 @@ void SciterMainWindow::OnInstallFirmwareFromFolder()
return;
}
m_modules.Modules().Systemloader().InstallFirmwarePackage((const char *)folder);
BeginFirmwareInstall((const char *)folder);
}
void SciterMainWindow::RefreshFirmwareInstallLoading()
{
SettingsStore & settings = SettingsStore::GetInstance();
const uint32_t current = static_cast<uint32_t>(settings.GetInt(NXLoaderSetting::FirmwareInstallCurrent));
const uint32_t total = static_cast<uint32_t>(settings.GetInt(NXLoaderSetting::FirmwareInstallTotal));
SciterElement fillEl(m_rootElement.GetElementByID("LoadingProgressFill"));
SciterElement status(m_rootElement.GetElementByID("loadingText"));
std::string detail;
if (total > 0)
{
detail = stdstr_f("%u / %u files", current, total);
if (fillEl.IsValid())
{
const int pct = static_cast<int>((100.0 * static_cast<double>(current)) / static_cast<double>(total));
UpdateLoadingProgressBar(fillEl, false, pct, false);
}
}
else if (fillEl.IsValid())
{
UpdateLoadingProgressBar(fillEl, true, 0, false);
}
if (status.IsValid())
{
const std::string text = stdstr_f("<span class=\"loading-verb\">Installing firmware</span> <span class=\"loading-game-name\">%s</span>",HtmlEscapeForHtmlContent(detail).c_str());
status.SetHTML(reinterpret_cast<const uint8_t *>(text.c_str()), text.size());
}
m_sciterUI.UpdateWindow(m_rootElement.GetElementHwnd(true));
}
void SciterMainWindow::StartFirmwareInstallUi()
{
if (m_firmwareInstallUiActive)
{
return;
}
m_firmwareInstallUiActive = true;
SciterElement fillEl(m_rootElement.GetElementByID("LoadingProgressFill"));
SciterElement status(m_rootElement.GetElementByID("loadingText"));
if (fillEl.IsValid())
{
UpdateLoadingProgressBar(fillEl, true, 0, false);
}
if (status.IsValid())
{
const std::string text = "<span class=\"loading-verb\">Installing firmware</span>";
status.SetHTML(reinterpret_cast<const uint8_t *>(text.c_str()), text.size());
}
LoadImageToElement(m_rootElement.GetElementByID("LoadingCornerLogo"), {});
LoadImageToElement(m_rootElement.GetElementByID("LoadingCornerBanner"), {});
LoadImageToElement(m_rootElement.GetElementByID("LoadingGameIcon"), {});
SciterElement loadingMain(m_rootElement.FindFirst("#LoadingPanel .loading-main"));
if (loadingMain.IsValid())
{
loadingMain.AddClassName("no-game-icon");
}
ShowPanel(Panel::Loading);
m_sciterUI.UpdateWindow(m_rootElement.GetElementHwnd(true));
if (!m_emulationRunning)
{
ResetMenu();
}
m_rootElement.SetTimer(25, (uint32_t *)TIMER_UPDATE_INSTALL_FIRMWARE);
RefreshFirmwareInstallLoading();
}
void SciterMainWindow::StopFirmwareInstallUi()
{
if (!m_firmwareInstallUiActive)
{
return;
}
m_firmwareInstallUiActive = false;
m_rootElement.SetTimer(0, (uint32_t *)TIMER_UPDATE_INSTALL_FIRMWARE);
SciterElement loadingMain(m_rootElement.FindFirst("#LoadingPanel .loading-main"));
if (loadingMain.IsValid())
{
loadingMain.RemoveClassName("no-game-icon");
}
const EmulationState state = static_cast<EmulationState>(SettingsStore::GetInstance().GetInt(NXCoreSetting::EmulationState));
if (state == EmulationState::Running && m_shownFirstFrame)
{
ShowPanel(Panel::Renderer);
}
else if (state == EmulationState::Paused)
{
ShowPanel(Panel::Pause);
}
else if (state == EmulationState::RomLoaded)
{
ShowPanel(Panel::Loading);
RefreshDiskCacheLoadingText();
}
else
{
ShowPanel(Panel::RomBrowser);
}
UpdateEmulationStatusText();
if (!m_emulationRunning)
{
ResetMenu();
}
}
void SciterMainWindow::FinishFirmwareInstall()
{
if (m_firmwareInstallThread.joinable())
{
m_firmwareInstallThread.join();
}
m_firmwareInstallInProgress = false;
StopFirmwareInstallUi();
UpdateEmulationStatusText();
if (!m_emulationRunning)
{
ResetMenu();
}
}
void SciterMainWindow::BeginFirmwareInstall(const char * utf8_path)
{
if (utf8_path == nullptr || utf8_path[0] == '\0' || m_firmwareInstallInProgress || !m_modules.IsValid())
{
return;
}
m_firmwareInstallInProgress = true;
m_rootElement.PostEvent(EVENT_FIRMWARE_INSTALL_ACTIVE);
const std::string path = utf8_path;
m_firmwareInstallThread = std::thread([this, path]() {
m_modules.Modules().Systemloader().InstallFirmwarePackage(path.c_str());
m_rootElement.PostEvent(EVENT_FIRMWARE_INSTALL_DONE);
});
}
void SciterMainWindow::FirmwareInstallTotalChanged(const char * /*setting*/, void * userData)
{
SciterMainWindow * impl = static_cast<SciterMainWindow *>(userData);
const int32_t total = SettingsStore::GetInstance().GetInt(NXLoaderSetting::FirmwareInstallTotal);
if (total > 0)
{
impl->m_rootElement.PostEvent(EVENT_FIRMWARE_INSTALL_ACTIVE);
}
else if (impl->m_firmwareInstallUiActive)
{
impl->m_rootElement.PostEvent(EVENT_FIRMWARE_INSTALL_FINISHED);
}
}
void SciterMainWindow::OnFileExit()
@ -1314,6 +1489,12 @@ void SciterMainWindow::UpdateLoadingScreenDetails()
LoadImageToElement(m_rootElement.GetElementByID("LoadingCornerLogo"), logoData);
LoadImageToElement(m_rootElement.GetElementByID("LoadingCornerBanner"), bannerData);
LoadImageToElement(m_rootElement.GetElementByID("LoadingGameIcon"), iconData);
SciterElement loadingMain(m_rootElement.FindFirst("#LoadingPanel .loading-main"));
if (loadingMain.IsValid())
{
loadingMain.RemoveClassName("no-game-icon");
}
}
void SciterMainWindow::UpdateUIVisibility()
@ -1618,6 +1799,13 @@ bool SciterMainWindow::OnTimer(SCITER_ELEMENT /*element*/, uint32_t * timerId)
{
UpdateEmulationStatusText();
}
else if (timerId == (uint32_t *)TIMER_UPDATE_INSTALL_FIRMWARE)
{
if (m_firmwareInstallUiActive)
{
RefreshFirmwareInstallLoading();
}
}
return true;
}
@ -1670,5 +1858,17 @@ bool SciterMainWindow::OnEvent(SCITER_ELEMENT element, SCITER_ELEMENT /*source*/
{
RefreshDiskCacheLoadingText();
}
else if (event_code == EVENT_FIRMWARE_INSTALL_ACTIVE)
{
StartFirmwareInstallUi();
}
else if (event_code == EVENT_FIRMWARE_INSTALL_FINISHED)
{
StopFirmwareInstallUi();
}
else if (event_code == EVENT_FIRMWARE_INSTALL_DONE)
{
FinishFirmwareInstall();
}
return false;
}

View File

@ -5,6 +5,7 @@
#include <map>
#include <memory>
#include <string>
#include <thread>
#include <nxemu-core/modules/system_modules.h>
#include <nxemu-module-spec/base.h>
#include <nxemu-module-spec/system_loader.h>
@ -66,6 +67,7 @@ class SciterMainWindow :
TIMER_UPDATE_UI = 5000,
TIMER_UPDATE_INPUT,
TIMER_UPDATE_STATUS,
TIMER_UPDATE_INSTALL_FIRMWARE,
};
public:
@ -94,6 +96,7 @@ private:
static void GameNameChanged(const char * setting, void * userData);
static void DisplayedFramesChanged(const char * setting, void * userData);
static void DiskCacheLoadChanged(const char * setting, void * userData);
static void FirmwareInstallTotalChanged(const char * setting, void * userData);
static void HotKeysChanged(const char * setting, void * userData);
void UpdateStatusWidgets();
void UpdateInputDrivers();
@ -108,6 +111,11 @@ private:
void OnInputConfig();
void OnInstallFirmwareFromFile();
void OnInstallFirmwareFromFolder();
void BeginFirmwareInstall(const char * utf8_path);
void StartFirmwareInstallUi();
void StopFirmwareInstallUi();
void RefreshFirmwareInstallLoading();
void FinishFirmwareInstall();
void OnRecetGame(uint32_t fileIndex);
void OnToggleDockedMode();
void OnToggleStartGamesInFullscreen();
@ -187,4 +195,7 @@ private:
int m_lastPostedDiskCacheStage;
bool m_shownFirstFrame;
std::unique_ptr<Win32FullscreenState> m_win32Fullscreen;
bool m_firmwareInstallInProgress;
bool m_firmwareInstallUiActive;
std::thread m_firmwareInstallThread;
};