Files
Vita3K/vita3k/gui/src/initial_setup.cpp
T

315 lines
15 KiB
C++

// Vita3K emulator project
// Copyright (C) 2026 Vita3K team
//
// 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; either version 2 of the License, or
// (at your option) any later version.
//
// 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 for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#include "private.h"
#include <config/functions.h>
#include <config/state.h>
#include <dialog/state.h>
#include <gui/functions.h>
#include <host/dialog/filesystem.h>
#include <lang/functions.h>
#ifdef __ANDROID__
#include <SDL3/SDL_system.h>
#include <jni.h>
#endif
namespace gui {
void get_firmware_file(EmuEnvState &emuenv) {
const std::map<int, std::string> locale_id = {
{ SCE_SYSTEM_PARAM_LANG_JAPANESE, "ja-jp" },
{ SCE_SYSTEM_PARAM_LANG_ENGLISH_US, "en-us" },
{ SCE_SYSTEM_PARAM_LANG_FRENCH, "fr-fr" },
{ SCE_SYSTEM_PARAM_LANG_SPANISH, "es-es" },
{ SCE_SYSTEM_PARAM_LANG_GERMAN, "de-de" },
{ SCE_SYSTEM_PARAM_LANG_ITALIAN, "it-it" },
{ SCE_SYSTEM_PARAM_LANG_DUTCH, "nl-nl" },
{ SCE_SYSTEM_PARAM_LANG_PORTUGUESE_PT, "pt-pt" },
{ SCE_SYSTEM_PARAM_LANG_RUSSIAN, "ru-ru" },
{ SCE_SYSTEM_PARAM_LANG_KOREAN, "ko-kr" },
{ SCE_SYSTEM_PARAM_LANG_CHINESE_T, "zh-hant-hk" },
{ SCE_SYSTEM_PARAM_LANG_CHINESE_S, "zh-hans-cn" },
{ SCE_SYSTEM_PARAM_LANG_FINNISH, "fi-fi" },
{ SCE_SYSTEM_PARAM_LANG_SWEDISH, "sv-se" },
{ SCE_SYSTEM_PARAM_LANG_DANISH, "da-dk" },
{ SCE_SYSTEM_PARAM_LANG_NORWEGIAN, "no-no" },
{ SCE_SYSTEM_PARAM_LANG_POLISH, "pl-pl" },
{ SCE_SYSTEM_PARAM_LANG_PORTUGUESE_BR, "pt-br" },
{ SCE_SYSTEM_PARAM_LANG_ENGLISH_GB, "en-gb" },
{ SCE_SYSTEM_PARAM_LANG_TURKISH, "tr-tr" },
};
const auto &locale = locale_id.at(emuenv.cfg.sys_lang);
open_path(fmt::format("https://www.playstation.com/{}/support/hardware/psvita/system-software", locale));
}
void draw_initial_setup(GuiState &gui, EmuEnvState &emuenv) {
enum InitialSetup {
SELECT_LANGUAGE,
SELECT_PREF_PATH,
INSTALL_FIRMWARE,
SELECT_INTERFACE_SETTINGS,
FINISHED
};
#ifdef __ANDROID__
static bool has_all_files_access = false;
#endif
static InitialSetup setup = SELECT_LANGUAGE;
static std::string title_str;
const ImVec2 VIEWPORT_SIZE(emuenv.logical_viewport_size.x, emuenv.logical_viewport_size.y);
const ImVec2 VIEWPORT_POS(emuenv.logical_viewport_pos.x, emuenv.logical_viewport_pos.y);
const ImVec2 RES_SCALE(emuenv.gui_scale.x, emuenv.gui_scale.y);
const auto SCALE = ImVec2(RES_SCALE.x * emuenv.manual_dpi_scale, RES_SCALE.y * emuenv.manual_dpi_scale);
const ImVec2 WINDOW_SIZE(756.f * SCALE.x, 418.f * SCALE.y);
const auto SELECT_SIZE(72.f * SCALE.y);
const ImVec2 BUTTON_SIZE(186.f * SCALE.x, 52.f * SCALE.y);
const ImVec2 BUTTON_POS(8.f * SCALE.x, VIEWPORT_SIZE.y - BUTTON_SIZE.y - (6.f * SCALE.y));
const ImVec2 BIG_BUTTON_SIZE(364.f * SCALE.x, 48.f * SCALE.y);
const ImVec2 BIG_BUTTON_POS((WINDOW_SIZE.x / 2.f) - (BIG_BUTTON_SIZE.x / 2.f), WINDOW_SIZE.y - BIG_BUTTON_SIZE.y - (20.f * SCALE.y));
auto &lang = gui.lang.initial_setup;
auto &common = emuenv.common_dialog.lang.common;
auto &welcome = gui.lang.welcome;
const auto is_default_path = emuenv.cfg.pref_path == emuenv.default_path;
const auto FW_PREINST_PATH{ emuenv.pref_path / "pd0" };
const auto FW_PREINST_INSTALLED = fs::exists(FW_PREINST_PATH) && !fs::is_empty(FW_PREINST_PATH);
const auto FW_PATH{ emuenv.pref_path / "vs0" };
const auto FW_INSTALLED = fs::exists(FW_PATH) && !fs::is_empty(FW_PATH);
const auto FW_FONT_PATH{ emuenv.pref_path / "sa0" };
const auto FW_FONT_INSTALLED = fs::exists(FW_FONT_PATH) && !fs::is_empty(FW_FONT_PATH);
ImGui::PushFont(gui.vita_font[emuenv.current_font_level]);
ImGui::SetNextWindowPos(VIEWPORT_POS, ImGuiCond_Always);
ImGui::SetNextWindowSize(VIEWPORT_SIZE, ImGuiCond_Always);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.f);
ImGui::Begin("##initial_setup", &emuenv.cfg.initial_setup, ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings);
ImGui::PopStyleVar(2);
ImGui::GetBackgroundDrawList()->AddRectFilled(VIEWPORT_POS, ImVec2(VIEWPORT_POS.x + VIEWPORT_SIZE.x, VIEWPORT_POS.y + VIEWPORT_SIZE.y), IM_COL32(194.f, 207.f, 241.f, 255.f), 0.f, ImDrawFlags_RoundCornersAll);
ImGui::SetNextWindowPos(ImVec2(VIEWPORT_POS.x + (98.f * SCALE.x), VIEWPORT_POS.y + (30 * SCALE.y)), ImGuiCond_Always);
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 12.f * SCALE.x);
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 6.f * SCALE.x);
ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, 2.f * SCALE.x);
ImGui::PushStyleColor(ImGuiCol_Border, GUI_COLOR_TEXT);
ImGui::SetNextWindowBgAlpha(0.0f);
ImGui::BeginChild("##window_box", WINDOW_SIZE, ImGuiChildFlags_Borders, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings);
ImGui::SetWindowFontScale(1.6f * RES_SCALE.x);
const auto SELECT_COLOR = ImVec4(0.23f, 0.68f, 0.95f, 0.60f);
const auto SELECT_COLOR_HOVERED = ImVec4(0.23f, 0.68f, 0.99f, 0.80f);
const auto SELECT_COLOR_ACTIVE = ImVec4(0.23f, 0.68f, 1.f, 1.f);
ImGui::SetCursorPosY((47 * SCALE.y) - (ImGui::GetFontSize() / 2.f));
TextCentered(title_str.c_str());
ImGui::SetWindowFontScale(1.4f * RES_SCALE.x);
ImGui::SetCursorPosY(94.f * SCALE.y);
ImGui::Separator();
switch (setup) {
case SELECT_LANGUAGE:
title_str = lang["select_language"];
ImGui::SetNextWindowPos(ImVec2(VIEWPORT_POS.x + (198.f * SCALE.x), VIEWPORT_POS.y + (126.f * SCALE.y)), ImGuiCond_Always);
ImGui::BeginChild("##lang_list", ImVec2(WINDOW_SIZE.x - (200.f * SCALE.x), WINDOW_SIZE.y - (108.f * SCALE.y)), ImGuiChildFlags_None, ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoSavedSettings);
ImGui::Columns(3, nullptr, false);
ImGui::SetColumnWidth(0, 90.f * SCALE.x);
ImGui::SetColumnWidth(1, 34.f * SCALE.x);
ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.f, 0.5f));
ImGui::PushStyleColor(ImGuiCol_Header, SELECT_COLOR);
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, SELECT_COLOR_HOVERED);
ImGui::PushStyleColor(ImGuiCol_HeaderActive, SELECT_COLOR_ACTIVE);
for (const auto &sys_lang : LIST_SYS_LANG) {
ImGui::PushID(sys_lang.first);
// Empty Padding
ImGui::NextColumn();
const auto is_current_lang = emuenv.cfg.sys_lang == sys_lang.first;
if (ImGui::Selectable(is_current_lang ? "V" : "##lang_list", false, ImGuiSelectableFlags_SpanAllColumns, ImVec2(WINDOW_SIZE.x, SELECT_SIZE))) {
emuenv.cfg.sys_lang = sys_lang.first;
lang::init_lang(gui.lang, emuenv);
}
ImGui::NextColumn();
ImGui::Selectable(sys_lang.second.c_str(), false, ImGuiSelectableFlags_None, ImVec2(WINDOW_SIZE.x, SELECT_SIZE));
ImGui::Separator();
ImGui::NextColumn();
ImGui::PopID();
}
ImGui::PopStyleVar();
ImGui::PopStyleColor(3);
ImGui::Columns(1);
ImGui::ScrollWhenDragging();
ImGui::EndChild();
break;
case SELECT_PREF_PATH:
title_str = lang["select_pref_path"];
ImGui::SetCursorPosY((WINDOW_SIZE.y / 2.f) - ImGui::GetFontSize());
#ifdef __ANDROID__
has_all_files_access = []() -> bool {
JNIEnv *env = reinterpret_cast<JNIEnv *>(SDL_GetAndroidJNIEnv());
jobject activity = reinterpret_cast<jobject>(SDL_GetAndroidActivity());
jclass clazz = env->GetObjectClass(activity);
jmethodID method_id = env->GetMethodID(clazz, "isStorageManagerEnabled", "()Z");
jboolean result = env->CallBooleanMethod(activity, method_id);
env->DeleteLocalRef(clazz);
env->DeleteLocalRef(activity);
return result == JNI_TRUE;
}();
if (!has_all_files_access) {
TextColoredCentered(ImVec4(0.98f, 0.01f, 0.20f, 1.0f), lang["storage_file_permissions"].c_str(), 20.f * SCALE.x);
ImGui::SetCursorPos(BIG_BUTTON_POS);
if (ImGui::Button(lang["grant_access"].c_str(), BIG_BUTTON_SIZE)) {
JNIEnv *env = reinterpret_cast<JNIEnv *>(SDL_GetAndroidJNIEnv());
jobject activity = reinterpret_cast<jobject>(SDL_GetAndroidActivity());
jclass clazz(env->GetObjectClass(activity));
jmethodID method_id = env->GetMethodID(clazz, "setStoragePermission", "()V");
env->CallVoidMethod(activity, method_id);
env->DeleteLocalRef(activity);
env->DeleteLocalRef(clazz);
}
break;
}
#endif
TextColoredCentered(GUI_COLOR_TEXT_TITLE, lang["current_emu_path"].c_str());
ImGui::Spacing();
TextCentered(emuenv.cfg.pref_path.c_str(), 0);
ImGui::SetCursorPos(!is_default_path ? ImVec2((WINDOW_SIZE.x / 2.f) - BIG_BUTTON_SIZE.x - (20.f * SCALE.x), BIG_BUTTON_POS.y) : BIG_BUTTON_POS);
if (ImGui::Button(lang["change_emu_path"].c_str(), BIG_BUTTON_SIZE)) {
fs::path emulator_path{};
host::dialog::filesystem::Result result = host::dialog::filesystem::pick_folder(emulator_path);
if ((result == host::dialog::filesystem::Result::SUCCESS) && (emulator_path.native() != emuenv.pref_path.native())) {
emuenv.pref_path = fs::path(emulator_path.native()) / "";
emuenv.cfg.set_pref_path(emuenv.pref_path);
} else if (result == host::dialog::filesystem::Result::ERROR) {
LOG_CRITICAL("Error initializing file dialog: {}", host::dialog::filesystem::get_error());
}
}
if (!is_default_path) {
ImGui::SameLine(0, 40.f * SCALE.x);
if (ImGui::Button(lang["reset_emu_path"].c_str(), BIG_BUTTON_SIZE)) {
if (emuenv.default_path != emuenv.pref_path) {
emuenv.pref_path = emuenv.default_path;
emuenv.cfg.set_pref_path(emuenv.default_path);
}
}
}
break;
case INSTALL_FIRMWARE:
title_str = lang["install_firmware"];
ImGui::SetCursorPosY((WINDOW_SIZE.y / 2.f) - (ImGui::GetFontSize() * 3.5f));
TextColoredCentered(GUI_COLOR_TEXT_TITLE, lang["install_highly_recommended"].c_str());
ImGui::Spacing();
if (ImGui::Button(welcome["download_preinst_firmware"].c_str(), BIG_BUTTON_SIZE))
open_path("https://bit.ly/4hlePsX");
ImGui::SameLine(0, 20.f * SCALE.x);
ImGui::Text("%s %s", lang["installed"].c_str(), FW_PREINST_INSTALLED ? "V" : "X");
ImGui::Spacing();
if (ImGui::Button(welcome["download_firmware"].c_str(), BIG_BUTTON_SIZE))
get_firmware_file(emuenv);
ImGui::SameLine(0, 20.f * SCALE.x);
ImGui::Text("%s %s", lang["installed"].c_str(), FW_INSTALLED ? "V" : "X");
ImGui::Spacing();
if (ImGui::Button(lang["download_font_package"].c_str(), BIG_BUTTON_SIZE))
open_path("https://bit.ly/48ouDaa");
ImGui::SameLine(0, 20.f * SCALE.x);
ImGui::Text("%s %s", lang["installed"].c_str(), FW_FONT_INSTALLED ? "V" : "X");
ImGui::SetCursorPos(BIG_BUTTON_POS);
if (ImGui::Button(lang["install_firmware_file"].c_str(), BIG_BUTTON_SIZE))
gui.file_menu.firmware_install_dialog = true;
if (gui.file_menu.firmware_install_dialog) {
ImGui::SetWindowFontScale(RES_SCALE.x);
draw_firmware_install_dialog(gui, emuenv);
}
break;
case SELECT_INTERFACE_SETTINGS:
title_str = lang["select_interface_settings"];
ImGui::SetCursorPosY((WINDOW_SIZE.y / 2.f) - ImGui::GetFontSize());
ImGui::Checkbox(gui.lang.settings_dialog.gui["display_info_message"].c_str(), &emuenv.cfg.display_info_message);
SetTooltipEx(gui.lang.settings_dialog.gui["display_info_message_description"].c_str());
ImGui::SameLine();
ImGui::Checkbox(gui.lang.settings_dialog.gui["display_system_apps"].c_str(), &emuenv.cfg.display_system_apps);
SetTooltipEx(gui.lang.settings_dialog.gui["display_system_apps_description"].c_str());
ImGui::Spacing();
ImGui::Checkbox(lang["show_info_bar"].c_str(), &emuenv.cfg.show_info_bar);
SetTooltipEx(lang["info_bar_description"].c_str());
ImGui::SameLine();
ImGui::Checkbox(lang["show_live_area_screen"].c_str(), &emuenv.cfg.show_live_area_screen);
SetTooltipEx(lang["live_area_screen_description"].c_str());
ImGui::Spacing();
ImGui::Checkbox(lang["apps_list_grid"].c_str(), &emuenv.cfg.apps_list_grid);
SetTooltipEx(lang["apps_list_grid_description"].c_str());
if (!emuenv.cfg.apps_list_grid) {
ImGui::Spacing();
ImGui::SliderInt(lang["icon_size"].c_str(), &emuenv.cfg.icon_size, 64, 128);
SetTooltipEx(lang["select_icon_size"].c_str());
}
break;
case FINISHED:
title_str = lang["completed"];
ImGui::SetCursorPosY((WINDOW_SIZE.y / 2.f) - ImGui::GetFontSize());
TextCentered(lang["completed_setup"].c_str());
ImGui::SetCursorPos(BIG_BUTTON_POS);
if (ImGui::Button(common["ok"].c_str(), BIG_BUTTON_SIZE) || ImGui::IsKeyPressed(static_cast<ImGuiKey>(emuenv.cfg.keyboard_button_cross))) {
emuenv.cfg.initial_setup = true;
config::serialize_config(emuenv.cfg, emuenv.config_path);
}
break;
default:
LOG_ERROR("Unknown initial setup step: {}", static_cast<int>(setup));
break;
}
ImGui::SetWindowFontScale(1.f);
ImGui::EndChild();
ImGui::PopStyleVar(2);
ImGui::PopStyleColor();
ImGui::SetWindowFontScale(1.5f * RES_SCALE.x);
// Draw Button
ImGui::SetCursorPos(BUTTON_POS);
if ((setup > SELECT_LANGUAGE) && ImGui::Button(lang["back"].c_str(), BUTTON_SIZE) || (setup > SELECT_LANGUAGE) && ImGui::IsKeyPressed(static_cast<ImGuiKey>(emuenv.cfg.keyboard_button_circle)))
setup = (InitialSetup)(setup - 1);
ImGui::SetCursorPos(ImVec2(VIEWPORT_SIZE.x - BUTTON_SIZE.x - BUTTON_POS.x, BUTTON_POS.y));
#ifdef __ANDROID__
ImGui::BeginDisabled((setup == SELECT_PREF_PATH) && !has_all_files_access);
#endif
if ((setup < FINISHED) && (ImGui::Button(lang["next"].c_str(), BUTTON_SIZE) || ImGui::IsKeyPressed(static_cast<ImGuiKey>(emuenv.cfg.keyboard_button_cross)))) {
setup = (InitialSetup)(setup + 1);
config::serialize_config(emuenv.cfg, emuenv.config_path);
}
#ifdef __ANDROID__
ImGui::EndDisabled();
#endif
ImGui::SetWindowFontScale(1.f);
ImGui::PopStyleVar();
ImGui::End();
ImGui::PopFont();
}
} // namespace gui