mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-09-19 19:07:48 +02:00
Most UMDs carry a firmware updater, and having a real firmware in the NAND is what the LLE modules want. On boot, if the disc's updater is newer than what's installed - or nothing identifiable is installed, which is what a fonts-only NAND looks like - unpack it, with a progress bar on the OSD. Controlled by bAutoUpgradeFirmware, on by default, with a checkbox on the firmware screen. Off in headless, which shouldn't rewrite the NAND during a test run. The install itself is now shared with the install screen, and hardened, since it can run without anyone watching: - It stages into <NAND>/install-staging and only erases the installed firmware once the new one is complete on disk, so a failure leaves what's there alone. - A single entry that didn't unpack fails the whole install. A firmware with holes still looks installed, so nothing would ever replace it. - A truncated archive is rejected instead of unpacking to half a firmware that every stat reports as a clean install. The PSAR header records the length; it was being clamped to the buffer rather than checked against it. Also falls back to the version in the archive's own header when the PARAM.SFO next to the updater is unreadable, which it is on a fair number of discs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
195 lines
7.6 KiB
C++
195 lines
7.6 KiB
C++
// Copyright (c) 2026- PPSSPP Project.
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, version 2.0 or later versions.
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License 2.0 for more details.
|
|
|
|
// A copy of the GPL 2.0 should have been included with the program.
|
|
// If not, see http://www.gnu.org/licenses/
|
|
|
|
// Official git repository and contact information can be found at
|
|
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
|
|
|
#include "Common/Data/Text/I18n.h"
|
|
#include "Common/Data/Text/Parsers.h"
|
|
#include "Common/Log.h"
|
|
#include "Common/StringUtils.h"
|
|
#include "Common/System/Request.h"
|
|
#include "Common/System/System.h"
|
|
#include "Common/UI/Notice.h"
|
|
#include "Common/UI/PopupScreens.h"
|
|
#include "Common/UI/ScreenManager.h"
|
|
#include "Common/UI/View.h"
|
|
#include "Common/UI/ViewGroup.h"
|
|
|
|
#include "Core/Config.h"
|
|
#include "Core/System.h"
|
|
#include "Core/Util/PathUtil.h"
|
|
|
|
#include "UI/FirmwareScreen.h"
|
|
#include "UI/InstallUpdateScreen.h"
|
|
#include "UI/MiscViews.h"
|
|
|
|
std::string_view FirmwareScreen::GetTitle() const {
|
|
auto sy = GetI18NCategory(I18NCat::SYSTEM);
|
|
return sy->T("PSP Firmware");
|
|
}
|
|
|
|
void FirmwareScreen::BeforeCreateViews() {
|
|
nandRoot_ = GetSysDirectory(DIRECTORY_NAND);
|
|
ReadInstalledFirmwareInfo(nandRoot_, &info_);
|
|
}
|
|
|
|
void FirmwareScreen::CreateContentViews(UI::ViewGroup *parent) {
|
|
using namespace UI;
|
|
|
|
auto sy = GetI18NCategory(I18NCat::SYSTEM);
|
|
auto iz = GetI18NCategory(I18NCat::INSTALLZIP);
|
|
auto di = GetI18NCategory(I18NCat::DIALOG);
|
|
auto st = GetI18NCategory(I18NCat::STORE); // Borrow "Size" from here, like GameScreen does.
|
|
|
|
LinearLayout *content = parent->Add(new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)));
|
|
|
|
if (!info_.anythingInstalled) {
|
|
content->Add(new NoticeView(NoticeLevel::INFO, sy->T("No firmware installed"),
|
|
sy->T("FirmwareSources", "PPSSPP has its own replacements for the PSP's system files, so a firmware is optional. Installing one gets you the real fonts, and lets you launch the XMB. Most game discs carry a firmware updater - open a game's info screen to install from it.")));
|
|
content->Add(new TextWithImage(ImageID("I_FOLDER"), GetFriendlyPath(nandRoot_)));
|
|
return;
|
|
}
|
|
|
|
if (!info_.version.empty()) {
|
|
std::string versionLine = std::string(sy->T("Firmware version")) + ": " + info_.version;
|
|
content->Add(new TextView(versionLine, ALIGN_LEFT, false))->SetBig(true);
|
|
if (!info_.buildDate.empty() || !info_.target.empty()) {
|
|
std::string details = info_.buildDate;
|
|
if (!info_.target.empty()) {
|
|
if (!details.empty()) {
|
|
details += " - ";
|
|
}
|
|
details += info_.target;
|
|
}
|
|
content->Add(new TextView(details, ALIGN_LEFT, false));
|
|
}
|
|
} else {
|
|
// No flash0:/vsh/etc/version.txt, so this isn't a full firmware - most likely just the
|
|
// fonts, which is what installing from a running game's disc leaves behind.
|
|
content->Add(new NoticeView(NoticeLevel::INFO, sy->T("Partial firmware installed"),
|
|
sy->T("Some system files are present, but not a complete firmware")));
|
|
}
|
|
|
|
// Without any kernel modules there's no firmware here to speak of - it's the fonts we pulled
|
|
// off a game's disc, and rows reading "Kernel modules: 0 / XMB: No" say nothing useful.
|
|
const bool fullFirmware = info_.kernelModuleCount > 0;
|
|
|
|
content->Add(new ItemHeader(sy->T("Contents")));
|
|
content->Add(new InfoItem(sy->T("Fonts"), info_.fontCount));
|
|
if (fullFirmware) {
|
|
content->Add(new InfoItem(sy->T("Kernel modules"), info_.kernelModuleCount));
|
|
}
|
|
if (fullFirmware || info_.hasVsh) {
|
|
content->Add(new InfoItem(sy->T("XMB (VSH)"), info_.hasVsh ? di->T("Yes") : di->T("No")));
|
|
}
|
|
if (fullFirmware) {
|
|
content->Add(new InfoItem(sy->T("Files"), info_.fileCount));
|
|
}
|
|
content->Add(new InfoItem(st->T("Size"), NiceSizeFormat(info_.totalSize)));
|
|
|
|
content->Add(new ItemHeader(iz->T("Install into folder")));
|
|
content->Add(new TextView(GetFriendlyPath(nandRoot_), ALIGN_LEFT | FLAG_WRAP_TEXT, false));
|
|
}
|
|
|
|
void FirmwareScreen::CreateSettingsViews(UI::ViewGroup *parent) {
|
|
using namespace UI;
|
|
|
|
auto sy = GetI18NCategory(I18NCat::SYSTEM);
|
|
auto iz = GetI18NCategory(I18NCat::INSTALLZIP);
|
|
|
|
// Booting anything replaces the running game, and erasing the NAND out from under a game
|
|
// that has flash0 mounted is worse - so while a game is running, this screen is read-only.
|
|
const bool gameRunning = PSP_IsInited();
|
|
|
|
Choice *launch = parent->Add(new Choice(sy->T("Launch XMB"), ImageID("I_PLAY")));
|
|
launch->OnClick.Add([this](UI::EventParams &) {
|
|
LaunchVSH();
|
|
});
|
|
launch->SetEnabled(!gameRunning && info_.hasVsh && FirmwareVersionSupportsVSH(info_.version));
|
|
|
|
if (info_.hasVsh && !FirmwareVersionSupportsVSH(info_.version)) {
|
|
parent->Add(new NoticeView(NoticeLevel::WARN, sy->T("XMB unsupported"),
|
|
sy->T("XMBUnsupportedVersion", "Only firmware 6.61 can boot the XMB so far")));
|
|
}
|
|
|
|
if (System_GetPropertyBool(SYSPROP_HAS_FILE_BROWSER)) {
|
|
Choice *install = parent->Add(new Choice(iz->T("Install PSP firmware update"), ImageID("I_FOLDER_UPLOAD")));
|
|
install->OnClick.Add([this](UI::EventParams &) {
|
|
BrowseForUpdater();
|
|
});
|
|
install->SetEnabled(!gameRunning);
|
|
}
|
|
|
|
parent->Add(new CheckBox(&g_Config.bAutoUpgradeFirmware, sy->T("Install or upgrade firmware from game discs"),
|
|
sy->T("AutoUpgradeFirmwareDesc", "Most game discs carry a firmware updater. Install it when there's no firmware yet, or when the disc's is newer.")));
|
|
|
|
if (info_.anythingInstalled) {
|
|
Choice *erase = parent->Add(new Choice(sy->T("Erase firmware"), ImageID("I_TRASHCAN")));
|
|
erase->OnClick.Add([this](UI::EventParams &) {
|
|
AskToErase();
|
|
});
|
|
erase->SetEnabled(!gameRunning);
|
|
}
|
|
|
|
if (System_GetPropertyBool(SYSPROP_HAS_OPEN_DIRECTORY)) {
|
|
parent->Add(new Choice(sy->T("Show firmware folder")))->OnClick.Add([this](UI::EventParams &) {
|
|
System_LaunchUrl(LaunchUrlType::LOCAL_FOLDER, nandRoot_.ToString());
|
|
});
|
|
}
|
|
|
|
if (gameRunning) {
|
|
parent->Add(new NoticeView(NoticeLevel::INFO,
|
|
sy->T("FirmwareNeedsNoGame", "Stop the game to change the installed firmware"), ""));
|
|
}
|
|
}
|
|
|
|
void FirmwareScreen::dialogFinished(const Screen *dialog, DialogResult result) {
|
|
// Coming back from the installer (or the erase confirmation), the NAND may well look
|
|
// different than it did when we scanned it. Rescanning is cheap, so just always redo it -
|
|
// BeforeCreateViews does the scan.
|
|
RecreateViews();
|
|
UITwoPaneBaseDialogScreen::dialogFinished(dialog, result);
|
|
}
|
|
|
|
void FirmwareScreen::LaunchVSH() {
|
|
System_PostUIMessage(UIMessage::REQUEST_GAME_BOOT, (nandRoot_ / "flash0/vsh/module/vshmain.prx").ToString());
|
|
}
|
|
|
|
void FirmwareScreen::BrowseForUpdater() {
|
|
auto iz = GetI18NCategory(I18NCat::INSTALLZIP);
|
|
System_BrowseForFile(GetRequesterToken(), iz->T("Install PSP firmware update"), BrowseFileType::BOOTABLE,
|
|
[this](std::string_view value, int) {
|
|
screenManager()->push(new InstallUpdateScreen(Path(value), "", false));
|
|
});
|
|
}
|
|
|
|
void FirmwareScreen::AskToErase() {
|
|
auto sy = GetI18NCategory(I18NCat::SYSTEM);
|
|
auto di = GetI18NCategory(I18NCat::DIALOG);
|
|
|
|
std::string question(sy->T("EraseFirmwareConfirm", "This deletes everything in the NAND folder, including the fonts."));
|
|
screenManager()->push(new UI::MessagePopupScreen(sy->T("Erase firmware"), question, di->T("Delete"), di->T("Cancel"),
|
|
[this](bool erase) {
|
|
if (!erase) {
|
|
return;
|
|
}
|
|
std::string error;
|
|
if (!EraseInstalledFirmware(nandRoot_, &error)) {
|
|
ERROR_LOG(Log::Loader, "Failed to erase the firmware: %s", error.c_str());
|
|
}
|
|
RecreateViews();
|
|
}));
|
|
}
|