From 2d4e7c826422fdbfa7e0ba837ab65f58801e3251 Mon Sep 17 00:00:00 2001
From: Narr the Reg <juangerman-13@hotmail.com>
Date: Fri, 24 Nov 2023 11:53:31 -0600
Subject: [PATCH] yuzu: Display firmware version

---
 src/core/hle/service/set/set_sys.cpp | 66 +++++++++++++++-------------
 src/core/hle/service/set/set_sys.h   | 24 ++++++++++
 src/yuzu/main.cpp                    | 39 +++++++++++++++-
 src/yuzu/main.h                      |  2 +
 4 files changed, 98 insertions(+), 33 deletions(-)

diff --git a/src/core/hle/service/set/set_sys.cpp b/src/core/hle/service/set/set_sys.cpp
index 19c667b42..f5edfdc8b 100644
--- a/src/core/hle/service/set/set_sys.cpp
+++ b/src/core/hle/service/set/set_sys.cpp
@@ -19,19 +19,8 @@
 
 namespace Service::Set {
 
-namespace {
-constexpr u64 SYSTEM_VERSION_FILE_MINOR_REVISION_OFFSET = 0x05;
-
-enum class GetFirmwareVersionType {
-    Version1,
-    Version2,
-};
-
-void GetFirmwareVersionImpl(Core::System& system, HLERequestContext& ctx,
-                            GetFirmwareVersionType type) {
-    ASSERT_MSG(ctx.GetWriteBufferSize() == 0x100,
-               "FirmwareVersion output buffer must be 0x100 bytes in size!");
-
+Result GetFirmwareVersionImpl(FirmwareVersionFormat& out_firmware, Core::System& system,
+                              GetFirmwareVersionType type) {
     constexpr u64 FirmwareVersionSystemDataId = 0x0100000000000809;
     auto& fsc = system.GetFileSystemController();
 
@@ -52,39 +41,34 @@ void GetFirmwareVersionImpl(Core::System& system, HLERequestContext& ctx,
             FileSys::SystemArchive::SynthesizeSystemArchive(FirmwareVersionSystemDataId));
     }
 
-    const auto early_exit_failure = [&ctx](std::string_view desc, Result code) {
+    const auto early_exit_failure = [](std::string_view desc, Result code) {
         LOG_ERROR(Service_SET, "General failure while attempting to resolve firmware version ({}).",
                   desc);
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(code);
+        return code;
     };
 
     const auto ver_file = romfs->GetFile("file");
     if (ver_file == nullptr) {
-        early_exit_failure("The system version archive didn't contain the file 'file'.",
-                           FileSys::ERROR_INVALID_ARGUMENT);
-        return;
+        return early_exit_failure("The system version archive didn't contain the file 'file'.",
+                                  FileSys::ERROR_INVALID_ARGUMENT);
     }
 
     auto data = ver_file->ReadAllBytes();
-    if (data.size() != 0x100) {
-        early_exit_failure("The system version file 'file' was not the correct size.",
-                           FileSys::ERROR_OUT_OF_BOUNDS);
-        return;
+    if (data.size() != sizeof(FirmwareVersionFormat)) {
+        return early_exit_failure("The system version file 'file' was not the correct size.",
+                                  FileSys::ERROR_OUT_OF_BOUNDS);
     }
 
+    std::memcpy(&out_firmware, data.data(), sizeof(FirmwareVersionFormat));
+
     // If the command is GetFirmwareVersion (as opposed to GetFirmwareVersion2), hardware will
     // zero out the REVISION_MINOR field.
     if (type == GetFirmwareVersionType::Version1) {
-        data[SYSTEM_VERSION_FILE_MINOR_REVISION_OFFSET] = 0;
+        out_firmware.revision_minor = 0;
     }
 
-    ctx.WriteBuffer(data);
-
-    IPC::ResponseBuilder rb{ctx, 2};
-    rb.Push(ResultSuccess);
+    return ResultSuccess;
 }
-} // Anonymous namespace
 
 void SET_SYS::SetLanguageCode(HLERequestContext& ctx) {
     IPC::RequestParser rp{ctx};
@@ -98,12 +82,32 @@ void SET_SYS::SetLanguageCode(HLERequestContext& ctx) {
 
 void SET_SYS::GetFirmwareVersion(HLERequestContext& ctx) {
     LOG_DEBUG(Service_SET, "called");
-    GetFirmwareVersionImpl(system, ctx, GetFirmwareVersionType::Version1);
+
+    FirmwareVersionFormat firmware_data{};
+    const auto result =
+        GetFirmwareVersionImpl(firmware_data, system, GetFirmwareVersionType::Version1);
+
+    if (result.IsSuccess()) {
+        ctx.WriteBuffer(firmware_data);
+    }
+
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(result);
 }
 
 void SET_SYS::GetFirmwareVersion2(HLERequestContext& ctx) {
     LOG_DEBUG(Service_SET, "called");
-    GetFirmwareVersionImpl(system, ctx, GetFirmwareVersionType::Version2);
+
+    FirmwareVersionFormat firmware_data{};
+    const auto result =
+        GetFirmwareVersionImpl(firmware_data, system, GetFirmwareVersionType::Version2);
+
+    if (result.IsSuccess()) {
+        ctx.WriteBuffer(firmware_data);
+    }
+
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(result);
 }
 
 void SET_SYS::GetAccountSettings(HLERequestContext& ctx) {
diff --git a/src/core/hle/service/set/set_sys.h b/src/core/hle/service/set/set_sys.h
index 93023c6dd..5f770fd32 100644
--- a/src/core/hle/service/set/set_sys.h
+++ b/src/core/hle/service/set/set_sys.h
@@ -4,6 +4,7 @@
 #pragma once
 
 #include "common/uuid.h"
+#include "core/hle/result.h"
 #include "core/hle/service/service.h"
 #include "core/hle/service/time/clock_types.h"
 
@@ -12,6 +13,29 @@ class System;
 }
 
 namespace Service::Set {
+enum class LanguageCode : u64;
+enum class GetFirmwareVersionType {
+    Version1,
+    Version2,
+};
+
+struct FirmwareVersionFormat {
+    u8 major;
+    u8 minor;
+    u8 micro;
+    INSERT_PADDING_BYTES(1);
+    u8 revision_major;
+    u8 revision_minor;
+    INSERT_PADDING_BYTES(2);
+    std::array<char, 0x20> platform;
+    std::array<u8, 0x40> version_hash;
+    std::array<char, 0x18> display_version;
+    std::array<char, 0x80> display_title;
+};
+static_assert(sizeof(FirmwareVersionFormat) == 0x100, "FirmwareVersionFormat is an invalid size");
+
+Result GetFirmwareVersionImpl(FirmwareVersionFormat& out_firmware, Core::System& system,
+                              GetFirmwareVersionType type);
 
 class SET_SYS final : public ServiceFramework<SET_SYS> {
 public:
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index f22db233b..1848ac89f 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -47,6 +47,7 @@
 #include "core/hle/service/am/applet_ae.h"
 #include "core/hle/service/am/applet_oe.h"
 #include "core/hle/service/am/applets/applets.h"
+#include "core/hle/service/set/set_sys.h"
 #include "yuzu/multiplayer/state.h"
 #include "yuzu/util/controller_navigation.h"
 
@@ -1047,7 +1048,12 @@ void GMainWindow::InitializeWidgets() {
         statusBar()->addPermanentWidget(label);
     }
 
-    // TODO (flTobi): Add the widget when multiplayer is fully implemented
+    firmware_label = new QLabel();
+    firmware_label->setObjectName(QStringLiteral("FirmwareLabel"));
+    firmware_label->setVisible(false);
+    firmware_label->setFocusPolicy(Qt::NoFocus);
+    statusBar()->addPermanentWidget(firmware_label);
+
     statusBar()->addPermanentWidget(multiplayer_state->GetStatusText(), 0);
     statusBar()->addPermanentWidget(multiplayer_state->GetStatusIcon(), 0);
 
@@ -2161,6 +2167,10 @@ void GMainWindow::OnEmulationStopped() {
     emu_frametime_label->setVisible(false);
     renderer_status_button->setEnabled(!UISettings::values.has_broken_vulkan);
 
+    if (!firmware_label->text().isEmpty()) {
+        firmware_label->setVisible(true);
+    }
+
     current_game_path.clear();
 
     // When closing the game, destroy the GLWindow to clear the context after the game is closed
@@ -4586,6 +4596,7 @@ void GMainWindow::UpdateStatusBar() {
     emu_speed_label->setVisible(!Settings::values.use_multi_core.GetValue());
     game_fps_label->setVisible(true);
     emu_frametime_label->setVisible(true);
+    firmware_label->setVisible(false);
 }
 
 void GMainWindow::UpdateGPUAccuracyButton() {
@@ -4803,6 +4814,8 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
                "games."));
     }
 
+    SetFirmwareVersion();
+
     if (behavior == ReinitializeKeyBehavior::Warning) {
         game_list->PopulateAsync(UISettings::values.game_dirs);
     }
@@ -4830,7 +4843,7 @@ bool GMainWindow::CheckSystemArchiveDecryption() {
 }
 
 bool GMainWindow::CheckFirmwarePresence() {
-    constexpr u64 MiiEditId = 0x0100000000001009ull;
+    constexpr u64 MiiEditId = static_cast<u64>(Service::AM::Applets::AppletProgramId::MiiEdit);
 
     auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
     if (!bis_system) {
@@ -4845,6 +4858,28 @@ bool GMainWindow::CheckFirmwarePresence() {
     return true;
 }
 
+void GMainWindow::SetFirmwareVersion() {
+    Service::Set::FirmwareVersionFormat firmware_data{};
+    const auto result = Service::Set::GetFirmwareVersionImpl(
+        firmware_data, *system, Service::Set::GetFirmwareVersionType::Version2);
+
+    if (result.IsError() || !CheckFirmwarePresence()) {
+        LOG_INFO(Frontend, "Installed firmware: No firmware available");
+        firmware_label->setVisible(false);
+        return;
+    }
+
+    firmware_label->setVisible(true);
+
+    const std::string display_version(firmware_data.display_version.data());
+    const std::string display_title(firmware_data.display_title.data());
+
+    LOG_INFO(Frontend, "Installed firmware: {}", display_title);
+
+    firmware_label->setText(QString::fromStdString(display_version));
+    firmware_label->setToolTip(QString::fromStdString(display_title));
+}
+
 bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, u64 program_id,
                                         u64* selected_title_id, u8* selected_content_record_type) {
     using ContentInfo = std::tuple<u64, FileSys::TitleType, FileSys::ContentRecordType>;
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 49ee1e1d2..0c8b7606d 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -457,6 +457,7 @@ private:
     bool CheckDarkMode();
     bool CheckSystemArchiveDecryption();
     bool CheckFirmwarePresence();
+    void SetFirmwareVersion();
     void ConfigureFilesystemProvider(const std::string& filepath);
     /**
      * Open (or not) the right confirm dialog based on current setting and game exit lock
@@ -511,6 +512,7 @@ private:
     QLabel* game_fps_label = nullptr;
     QLabel* emu_frametime_label = nullptr;
     QLabel* tas_label = nullptr;
+    QLabel* firmware_label = nullptr;
     QPushButton* gpu_accuracy_button = nullptr;
     QPushButton* renderer_status_button = nullptr;
     QPushButton* dock_status_button = nullptr;