Vita3K/vita3k/gui/src/gui.cpp
Pedro Montes Alcalde 34565e3ef4
Vita2024K: Happy 2024
2024-01-01 12:36:32 -03:00

882 lines
37 KiB
C++

// Vita3K emulator project
// Copyright (C) 2024 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 <gui/functions.h>
#include <gui/imgui_impl_sdl.h>
#include <gui/state.h>
#include <boost/algorithm/string/trim.hpp>
#include <config/state.h>
#include <display/state.h>
#include <glutil/gl.h>
#include <io/VitaIoDevice.h>
#include <io/state.h>
#include <io/vfs.h>
#include <lang/functions.h>
#include <packages/sfo.h>
#include <regmgr/functions.h>
#include <touch/functions.h>
#include <util/fs.h>
#include <util/log.h>
#include <util/string_utils.h>
#include <SDL_video.h>
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
#include <fstream>
#include <string>
#include <vector>
namespace gui {
void draw_info_message(GuiState &gui, EmuEnvState &emuenv) {
if (emuenv.io.title_id.empty() && emuenv.cfg.display_info_message) {
const ImVec2 display_size(emuenv.viewport_size.x, emuenv.viewport_size.y);
const ImVec2 RES_SCALE(display_size.x / emuenv.res_width_dpi_scale, display_size.y / emuenv.res_height_dpi_scale);
const ImVec2 SCALE(RES_SCALE.x * emuenv.dpi_scale, RES_SCALE.y * emuenv.dpi_scale);
const ImVec2 WINDOW_SIZE(680.0f * SCALE.x, 320.0f * SCALE.y);
const ImVec2 BUTTON_SIZE(160.f * SCALE.x, 46.f * SCALE.y);
ImGui::SetNextWindowPos(ImVec2(emuenv.viewport_pos.x, emuenv.viewport_pos.y), ImGuiCond_Always);
ImGui::SetNextWindowSize(display_size, ImGuiCond_Always);
ImGui::Begin("##information", nullptr, ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoDecoration);
ImGui::SetNextWindowPos(ImVec2(emuenv.viewport_pos.x + (display_size.x / 2) - (WINDOW_SIZE.x / 2.f), emuenv.viewport_pos.y + (display_size.y / 2.f) - (WINDOW_SIZE.y / 2.f)), ImGuiCond_Always);
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 10.f * SCALE.x);
ImGui::BeginChild("##info", WINDOW_SIZE, true, ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoDecoration);
const auto title = gui.info_message.title;
ImGui::SetWindowFontScale(RES_SCALE.x);
ImGui::SetCursorPosX((ImGui::GetWindowWidth() - ImGui::CalcTextSize(title.c_str()).x) / 2);
ImGui::TextColored(GUI_COLOR_TEXT_TITLE, "%s", title.c_str());
ImGui::Spacing();
ImGui::Separator();
const auto text_size = ImGui::CalcTextSize(gui.info_message.msg.c_str(), 0, false, WINDOW_SIZE.x - (24.f * SCALE.x));
const auto text_pos = ImVec2((WINDOW_SIZE.x / 2.f) - (text_size.x / 2.f), (WINDOW_SIZE.y / 2.f) - (text_size.y / 2.f) - (24 * SCALE.y));
ImGui::SetCursorPos(text_pos);
ImGui::TextWrapped("%s", gui.info_message.msg.c_str());
ImGui::SetCursorPosY(WINDOW_SIZE.y - BUTTON_SIZE.y - (42.0f * SCALE.y));
ImGui::Separator();
ImGui::SetCursorPos(ImVec2((ImGui::GetWindowWidth() / 2.f) - (BUTTON_SIZE.x / 2.f), WINDOW_SIZE.y - BUTTON_SIZE.y - (24.0f * SCALE.y)));
if (ImGui::Button(emuenv.common_dialog.lang.common["ok"].c_str(), BUTTON_SIZE) || ImGui::IsKeyPressed(static_cast<ImGuiKey>(emuenv.cfg.keyboard_button_cross)))
gui.info_message = {};
ImGui::EndChild();
ImGui::PopStyleVar();
ImGui::End();
} else {
spdlog::log(gui.info_message.level, "[{}] {}", gui.info_message.function, gui.info_message.msg);
gui.info_message = {};
}
}
static void init_style(EmuEnvState &emuenv) {
ImGui::StyleColorsDark();
ImGuiStyle *style = &ImGui::GetStyle();
style->WindowPadding = ImVec2(11, 11);
style->WindowRounding = 4.0f;
style->FramePadding = ImVec2(4, 4);
style->FrameRounding = 3.0f;
style->ItemSpacing = ImVec2(10, 5);
style->ItemInnerSpacing = ImVec2(6, 5);
style->IndentSpacing = 20.0f;
style->ScrollbarSize = 12.0f;
style->ScrollbarRounding = 8.0f;
style->GrabMinSize = 4.0f;
style->GrabRounding = 2.5f;
style->ScaleAllSizes(emuenv.dpi_scale);
style->Colors[ImGuiCol_Text] = ImVec4(0.95f, 0.95f, 0.95f, 1.00f);
style->Colors[ImGuiCol_TextDisabled] = ImVec4(0.24f, 0.23f, 0.29f, 1.00f);
style->Colors[ImGuiCol_WindowBg] = ImVec4(0.07f, 0.08f, 0.10f, 0.80f);
style->Colors[ImGuiCol_ChildBg] = ImVec4(0.15f, 0.16f, 0.18f, 1.00f);
style->Colors[ImGuiCol_PopupBg] = ImVec4(0.15f, 0.16f, 0.18f, 1.00f);
style->Colors[ImGuiCol_Border] = ImVec4(0.80f, 0.80f, 0.80f, 0.88f);
style->Colors[ImGuiCol_BorderShadow] = ImVec4(0.92f, 0.91f, 0.88f, 0.00f);
style->Colors[ImGuiCol_FrameBg] = ImVec4(0.10f, 0.09f, 0.12f, 0.80f);
style->Colors[ImGuiCol_FrameBgHovered] = ImVec4(0.24f, 0.23f, 0.29f, 0.40f);
style->Colors[ImGuiCol_FrameBgActive] = ImVec4(0.56f, 0.56f, 0.58f, 0.70f);
style->Colors[ImGuiCol_TitleBg] = ImVec4(0.10f, 0.09f, 0.12f, 1.00f);
style->Colors[ImGuiCol_TitleBgCollapsed] = ImVec4(1.00f, 0.98f, 0.95f, 0.75f);
style->Colors[ImGuiCol_TitleBgActive] = ImVec4(0.07f, 0.07f, 0.09f, 1.00f);
style->Colors[ImGuiCol_MenuBarBg] = ImVec4(0.10f, 0.09f, 0.12f, 1.00f);
style->Colors[ImGuiCol_ScrollbarBg] = ImVec4(0.10f, 0.09f, 0.12f, 0.90f);
style->Colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.80f, 0.80f, 0.83f, 0.31f);
style->Colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.46f, 0.56f, 0.58f, 1.00f);
style->Colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.06f, 0.05f, 0.07f, 1.00f);
style->Colors[ImGuiCol_CheckMark] = ImVec4(1.00f, 0.55f, 0.00f, 1.00f);
style->Colors[ImGuiCol_SliderGrab] = ImVec4(1.00f, 0.55f, 0.00f, 1.00f);
style->Colors[ImGuiCol_SliderGrabActive] = ImVec4(0.06f, 0.05f, 0.07f, 1.00f);
style->Colors[ImGuiCol_Button] = ImVec4(0.20f, 0.21f, 0.23f, 1.00f);
style->Colors[ImGuiCol_ButtonHovered] = ImVec4(0.08f, 0.66f, 0.87f, 0.50f);
style->Colors[ImGuiCol_ButtonActive] = ImVec4(0.08f, 0.66f, 0.87f, 1.00f);
style->Colors[ImGuiCol_Header] = ImVec4(1.00f, 1.00f, 0.00f, 0.50f);
style->Colors[ImGuiCol_HeaderHovered] = ImVec4(1.00f, 1.00f, 0.00f, 0.30f);
style->Colors[ImGuiCol_HeaderActive] = ImVec4(1.00f, 1.00f, 0.00f, 0.70f);
style->Colors[ImGuiCol_Separator] = ImVec4(0.10f, 0.10f, 0.10f, 1.00f);
style->Colors[ImGuiCol_SeparatorHovered] = ImVec4(0.24f, 0.23f, 0.29f, 1.00f);
style->Colors[ImGuiCol_SeparatorActive] = ImVec4(0.56f, 0.56f, 0.58f, 1.00f);
style->Colors[ImGuiCol_ResizeGrip] = ImVec4(0.18f, 0.18f, 0.18f, 0.20f);
style->Colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.22f, 0.22f, 0.22f, 1.00f);
style->Colors[ImGuiCol_ResizeGripActive] = ImVec4(0.32f, 0.32f, 0.32f, 1.00f);
style->Colors[ImGuiCol_Tab] = ImVec4(0.80f, 0.80f, 0.83f, 0.31f);
style->Colors[ImGuiCol_TabHovered] = ImVec4(0.32f, 0.30f, 0.23f, 1.00f);
style->Colors[ImGuiCol_TabActive] = ImVec4(0.06f, 0.05f, 0.07f, 1.00f);
style->Colors[ImGuiCol_PlotLines] = ImVec4(1.f, 0.49f, 0.f, 1.f);
style->Colors[ImGuiCol_PlotLinesHovered] = ImVec4(0.25f, 1.00f, 0.00f, 1.00f);
style->Colors[ImGuiCol_PlotHistogram] = ImVec4(0.40f, 0.39f, 0.38f, 0.63f);
style->Colors[ImGuiCol_PlotHistogramHovered] = ImVec4(0.25f, 1.00f, 0.00f, 1.00f);
style->Colors[ImGuiCol_TextSelectedBg] = ImVec4(1.00f, 1.00f, 0.00f, 0.50f);
style->Colors[ImGuiCol_ModalWindowDimBg] = ImVec4(1.00f, 0.98f, 0.95f, 0.73f);
}
static void init_font(GuiState &gui, EmuEnvState &emuenv) {
ImGuiIO &io = ImGui::GetIO();
ImFontConfig mono_font_config{};
mono_font_config.SizePixels = 13.f;
#ifdef _WIN32
const auto monospaced_font_path = "C:\\Windows\\Fonts\\consola.ttf";
gui.monospaced_font = io.Fonts->AddFontFromFileTTF(monospaced_font_path, mono_font_config.SizePixels, &mono_font_config, io.Fonts->GetGlyphRangesJapanese());
#else
gui.monospaced_font = io.Fonts->AddFontDefault(&mono_font_config);
#endif
// Set Large Font
static const ImWchar large_font_chars[] = { L'0', L'1', L'2', L'3', L'4', L'5', L'6', L'7', L'8', L'9', L':', L'A', L'M', L'P', 0 };
// Set Fw font paths
const auto fw_font_path{ emuenv.pref_path / "sa0/data/font/pvf" };
const auto latin_fw_font_path{ fw_font_path / "ltn0.pvf" };
// clang-format off
static const ImWchar latin_range[] = {
0x0020, 0x017F, // Basic Latin + Latin Supplement
0x0370, 0x03FF, // Greek and Coptic
0x0400, 0x052F, // Cyrillic + Cyrillic Supplement
0x20A0, 0x20CF, // Currency Symbols
0x2100, 0x214F, // Letter type symbols
0x2DE0, 0x2DFF, // Cyrillic Extended-A
0xA640, 0xA69F, // Cyrillic Extended-B
0,
};
static const ImWchar extra_range[] = {
0x0100, 0x017F, // Latin Extended A
0x2000, 0x206F, // General Punctuation
0x2150, 0x218F, // Numeral forms
0x2190, 0x21FF, // Arrows
0x2200, 0x22FF, // Math operators
0x2460, 0x24FF, // Enclosed Alphanumerics
0x25A0, 0x26FF, // Miscellaneous symbols
0x4E00, 0x9FFF, // Unified ideograms CJK
0,
};
static const ImWchar korean_range[] = {
0x3131, 0x3163, // Korean alphabets
0xAC00, 0xD79D, // Korean characters
0,
};
static const ImWchar chinese_range[] = {
0x2000, 0x206F, // General Punctuation
0x4E00, 0x9FAF, // CJK Ideograms
0,
};
// clang-format on
// Merge Japanese and Extra ranges
ImFontGlyphRangesBuilder builder;
builder.AddRanges(io.Fonts->GetGlyphRangesJapanese());
builder.AddRanges(extra_range);
ImVector<ImWchar> japanese_and_extra_ranges;
builder.BuildRanges(&japanese_and_extra_ranges);
ImFontConfig font_config{};
ImFontConfig large_font_config{};
// Check existence of fw font file
if (fs::exists(latin_fw_font_path)) {
// Add fw font to imgui
gui.fw_font = true;
font_config.SizePixels = 19.2f;
gui.vita_font = io.Fonts->AddFontFromFileTTF(latin_fw_font_path.string().c_str(), font_config.SizePixels, &font_config, latin_range);
font_config.MergeMode = true;
io.Fonts->AddFontFromFileTTF((fw_font_path / "jpn0.pvf").string().c_str(), font_config.SizePixels, &font_config, japanese_and_extra_ranges.Data);
const auto sys_lang = static_cast<SceSystemParamLang>(emuenv.cfg.sys_lang);
if (emuenv.cfg.asia_font_support || (sys_lang == SCE_SYSTEM_PARAM_LANG_KOREAN))
io.Fonts->AddFontFromFileTTF((fw_font_path / "kr0.pvf").string().c_str(), font_config.SizePixels, &font_config, korean_range);
if (emuenv.cfg.asia_font_support || (sys_lang == SCE_SYSTEM_PARAM_LANG_CHINESE_T) || (sys_lang == SCE_SYSTEM_PARAM_LANG_CHINESE_S))
io.Fonts->AddFontFromFileTTF((fw_font_path / "cn0.pvf").string().c_str(), font_config.SizePixels, &font_config, chinese_range);
font_config.MergeMode = false;
large_font_config.SizePixels = 116.f;
gui.large_font = io.Fonts->AddFontFromFileTTF(latin_fw_font_path.string().c_str(), large_font_config.SizePixels, &large_font_config, large_font_chars);
} else {
LOG_WARN("Could not find firmware font file at \"{}\", install firmware fonts package to fix this.", latin_fw_font_path.string());
font_config.SizePixels = 22.f;
// Set up default font path
fs::path default_font_path = emuenv.static_assets_path / "data/fonts";
// Check existence of default font file
if (fs::exists(default_font_path)) {
gui.vita_font = io.Fonts->AddFontFromFileTTF((default_font_path / "mplus-1mn-bold.ttf").string().c_str(), font_config.SizePixels, &font_config, latin_range);
font_config.MergeMode = true;
io.Fonts->AddFontFromFileTTF((default_font_path / "mplus-1mn-bold.ttf").string().c_str(), font_config.SizePixels, &font_config, japanese_and_extra_ranges.Data);
const auto sys_lang = static_cast<SceSystemParamLang>(emuenv.cfg.sys_lang);
if (sys_lang == SCE_SYSTEM_PARAM_LANG_CHINESE_S)
io.Fonts->AddFontFromFileTTF((default_font_path / "SourceHanSansSC-Bold-Min.ttf").string().c_str(), font_config.SizePixels, &font_config, japanese_and_extra_ranges.Data);
font_config.MergeMode = false;
large_font_config.SizePixels = 134.f;
gui.large_font = io.Fonts->AddFontFromFileTTF((default_font_path / "mplus-1mn-bold.ttf").string().c_str(), large_font_config.SizePixels, &large_font_config, large_font_chars);
LOG_INFO("Using default Vita3K font.");
} else
LOG_WARN("Could not find default Vita3K font, using default ImGui font.", default_font_path.string());
}
// Build font atlas loaded and upload to GPU
io.Fonts->Build();
// DPI scaling
io.FontGlobalScale = emuenv.dpi_scale;
io.DisplayFramebufferScale = { emuenv.dpi_scale, emuenv.dpi_scale };
}
vfs::FileBuffer init_default_icon(GuiState &gui, EmuEnvState &emuenv) {
vfs::FileBuffer buffer;
const auto default_fw_icon{ emuenv.pref_path / "vs0/data/internal/livearea/default/sce_sys/icon0.png" };
fs::path default_icon = emuenv.static_assets_path / "data/image/icon.png";
if (fs::exists(default_fw_icon) || fs::exists(default_icon)) {
auto icon_path = fs::exists(default_fw_icon) ? default_fw_icon.string() : default_icon.string();
std::ifstream image_stream(icon_path, std::ios::binary | std::ios::ate);
const std::size_t fsize = image_stream.tellg();
buffer.resize(fsize);
image_stream.seekg(0, std::ios::beg);
image_stream.read(reinterpret_cast<char *>(buffer.data()), fsize);
}
return buffer;
}
static IconData load_app_icon(GuiState &gui, EmuEnvState &emuenv, const std::string &app_path) {
IconData image;
vfs::FileBuffer buffer;
const auto APP_INDEX = get_app_index(gui, app_path);
if (!vfs::read_app_file(buffer, emuenv.pref_path.wstring(), app_path, "sce_sys/icon0.png")) {
buffer = init_default_icon(gui, emuenv);
if (buffer.empty()) {
LOG_WARN("Default icon not found for title {}, [{}] in path {}.",
APP_INDEX->title_id, APP_INDEX->title, app_path);
return {};
} else
LOG_INFO("Default icon found for App {}, [{}] in path {}.", APP_INDEX->title_id, APP_INDEX->title, app_path);
}
image.data.reset(stbi_load_from_memory(
buffer.data(), static_cast<int>(buffer.size()),
&image.width, &image.height, nullptr, STBI_rgb_alpha));
if (!image.data || image.width != 128 || image.height != 128) {
LOG_ERROR("Invalid icon for title {}, [{}] in path {}.",
APP_INDEX->title_id, APP_INDEX->title, app_path);
return {};
}
return image;
}
void init_app_icon(GuiState &gui, EmuEnvState &emuenv, const std::string &app_path) {
IconData data = load_app_icon(gui, emuenv, app_path);
if (data.data) {
gui.app_selector.user_apps_icon[app_path].init(gui.imgui_state.get(), data.data.get(), data.width, data.height);
}
}
IconData::IconData()
: data(nullptr, stbi_image_free) {}
void IconAsyncLoader::commit(GuiState &gui) {
std::lock_guard<std::mutex> lock(mutex);
for (const auto &pair : icon_data) {
if (pair.second.data) {
gui.app_selector.user_apps_icon[pair.first].init(gui.imgui_state.get(), pair.second.data.get(), pair.second.width, pair.second.height);
}
}
icon_data.clear();
}
IconAsyncLoader::IconAsyncLoader(GuiState &gui, EmuEnvState &emuenv, const std::vector<gui::App> &app_list) {
// I don't feel comfortable passing app_list down to be iterated by thread.
// Methods like delete_app might mutate it, so I'd like to copy what I need now.
auto paths = [&app_list]() {
std::vector<std::string> copy(app_list.size());
std::transform(app_list.begin(), app_list.end(), copy.begin(), [](const auto &a) { return a.path; });
return copy;
};
quit = false;
thread = std::thread([&, paths = paths()]() {
for (const auto &path : paths) {
if (quit)
return;
// load the actual texture
IconData data = load_app_icon(gui, emuenv, path);
// Duplicate code here from init_app_icon
{
std::lock_guard<std::mutex> lock(mutex);
icon_data[path] = std::move(data);
}
}
});
}
IconAsyncLoader::~IconAsyncLoader() {
quit = true;
thread.join();
}
void init_apps_icon(GuiState &gui, EmuEnvState &emuenv, const std::vector<gui::App> &app_list) {
gui.app_selector.icon_async_loader.emplace(gui, emuenv, app_list);
}
void init_app_background(GuiState &gui, EmuEnvState &emuenv, const std::string &app_path) {
if (gui.apps_background.contains(app_path))
return;
const auto APP_INDEX = get_app_index(gui, app_path);
int32_t width = 0;
int32_t height = 0;
vfs::FileBuffer buffer;
const auto is_sys = app_path.starts_with("NPXS") && (app_path != "NPXS10007");
if (is_sys)
vfs::read_file(VitaIoDevice::vs0, buffer, emuenv.pref_path.wstring(), "app/" + app_path + "/sce_sys/pic0.png");
else
vfs::read_app_file(buffer, emuenv.pref_path.wstring(), app_path, "sce_sys/pic0.png");
const auto title = APP_INDEX ? APP_INDEX->title : app_path;
if (buffer.empty()) {
LOG_WARN("Background not found for application {} [{}].", title, app_path);
return;
}
stbi_uc *data = stbi_load_from_memory(&buffer[0], static_cast<int>(buffer.size()), &width, &height, nullptr, STBI_rgb_alpha);
if (!data) {
LOG_ERROR("Invalid background for application {} [{}].", title, app_path);
return;
}
gui.apps_background[app_path].init(gui.imgui_state.get(), data, width, height);
stbi_image_free(data);
}
std::string get_sys_lang_name(uint32_t lang_id) {
const auto current_sys_lang = std::find_if(LIST_SYS_LANG.begin(), LIST_SYS_LANG.end(), [&](const auto &l) {
return l.first == lang_id;
});
return current_sys_lang->second;
}
static bool get_user_apps(GuiState &gui, EmuEnvState &emuenv) {
const auto apps_cache_path{ emuenv.pref_path / "ux0/temp/apps.dat" };
fs::ifstream apps_cache(apps_cache_path, std::ios::in | std::ios::binary);
if (apps_cache.is_open()) {
gui.app_selector.user_apps.clear();
// Read size of apps list
size_t size;
apps_cache.read((char *)&size, sizeof(size));
// Check version of cache
uint32_t versionInFile;
apps_cache.read((char *)&versionInFile, sizeof(uint32_t));
if (versionInFile != 1) {
LOG_WARN("Current version of cache: {}, is outdated, recreate it.", versionInFile);
return false;
}
// Read language of cache
apps_cache.read((char *)&gui.app_selector.apps_cache_lang, sizeof(uint32_t));
if (gui.app_selector.apps_cache_lang != emuenv.cfg.sys_lang) {
LOG_WARN("Current lang of cache: {}, is diferent config: {}, recreate it.", get_sys_lang_name(gui.app_selector.apps_cache_lang), get_sys_lang_name(emuenv.cfg.sys_lang));
return false;
}
// Read App info value
for (size_t a = 0; a < size; a++) {
auto read = [&apps_cache]() {
size_t size;
apps_cache.read((char *)&size, sizeof(size));
std::vector<char> buffer(size); // dont trust std::string to hold buffer enough
apps_cache.read(buffer.data(), size);
return std::string(buffer.begin(), buffer.end());
};
App app;
app.app_ver = read();
app.category = read();
app.content_id = read();
app.addcont = read();
app.savedata = read();
app.parental_level = read();
app.stitle = read();
app.title = read();
app.title_id = read();
app.path = read();
gui.app_selector.user_apps.push_back(app);
}
init_apps_icon(gui, emuenv, gui.app_selector.user_apps);
load_and_update_compat_user_apps(gui, emuenv);
}
return !gui.app_selector.user_apps.empty();
}
void save_apps_cache(GuiState &gui, EmuEnvState &emuenv) {
const auto temp_path{ emuenv.pref_path / "ux0/temp" };
if (!fs::exists(temp_path))
fs::create_directory(temp_path);
fs::ofstream apps_cache(temp_path / "apps.dat", std::ios::out | std::ios::binary);
if (apps_cache.is_open()) {
// Write Size of apps list
const auto size = gui.app_selector.user_apps.size();
apps_cache.write((char *)&size, sizeof(size));
// Write version of cache
const uint32_t versionInFile = 1;
apps_cache.write((char *)&versionInFile, sizeof(uint32_t));
// Write language of cache
gui.app_selector.apps_cache_lang = emuenv.cfg.sys_lang;
apps_cache.write((char *)&gui.app_selector.apps_cache_lang, sizeof(uint32_t));
// Write Apps list
for (const App &app : gui.app_selector.user_apps) {
auto write = [&apps_cache](const std::string &i) {
const auto size = i.length();
apps_cache.write((char *)&size, sizeof(size));
apps_cache.write(i.c_str(), size);
};
write(app.app_ver);
write(app.category);
write(app.content_id);
write(app.addcont);
write(app.savedata);
write(app.parental_level);
write(app.stitle);
write(app.title);
write(app.title_id);
write(app.path);
}
apps_cache.close();
}
}
void init_home(GuiState &gui, EmuEnvState &emuenv) {
if (gui.app_selector.user_apps.empty() && (emuenv.cfg.load_app_list || !emuenv.cfg.run_app_path)) {
if (!get_user_apps(gui, emuenv))
init_user_apps(gui, emuenv);
}
init_app_background(gui, emuenv, "NPXS10015");
regmgr::init_regmgr(emuenv.regmgr, emuenv.pref_path.wstring());
const auto is_cmd = emuenv.cfg.run_app_path || emuenv.cfg.content_path;
if (!gui.users.empty() && gui.users.contains(emuenv.cfg.user_id) && (is_cmd || emuenv.cfg.auto_user_login)) {
init_user(gui, emuenv, emuenv.cfg.user_id);
if (!is_cmd && emuenv.cfg.auto_user_login) {
gui.vita_area.information_bar = true;
open_user(gui, emuenv);
}
} else
init_user_management(gui, emuenv);
}
void init_user_app(GuiState &gui, EmuEnvState &emuenv, const std::string &app_path) {
auto &user_apps = gui.app_selector.user_apps;
auto it = std::find_if(user_apps.begin(), user_apps.end(), [&](const App &a) {
return a.path == app_path;
});
if (it != user_apps.end()) {
user_apps.erase(it);
gui.app_selector.user_apps_icon.erase(app_path);
}
get_app_param(gui, emuenv, app_path);
init_app_icon(gui, emuenv, app_path);
const auto TIME_APP_INDEX = get_time_app_index(gui, emuenv, app_path);
if (TIME_APP_INDEX != gui.time_apps[emuenv.io.user_id].end())
get_app_index(gui, app_path)->last_time = TIME_APP_INDEX->last_time_used;
gui.app_selector.is_app_list_sorted = false;
}
std::map<std::string, ImGui_Texture>::const_iterator get_app_icon(GuiState &gui, const std::string &app_path) {
const auto &app_type = app_path.starts_with("NPXS") && (app_path != "NPXS10007") ? gui.app_selector.sys_apps_icon : gui.app_selector.user_apps_icon;
const auto app_icon = std::find_if(app_type.begin(), app_type.end(), [&](const auto &i) {
return i.first == app_path;
});
return app_icon;
}
App *get_app_index(GuiState &gui, const std::string &app_path) {
auto &app_type = app_path.starts_with("NPXS") && (app_path != "NPXS10007") ? gui.app_selector.sys_apps : gui.app_selector.user_apps;
const auto app_index = std::find_if(app_type.begin(), app_type.end(), [&](const App &a) {
return a.path == app_path;
});
return (app_index != app_type.end()) ? &(*app_index) : nullptr;
}
void get_app_param(GuiState &gui, EmuEnvState &emuenv, const std::string &app_path) {
emuenv.app_path = app_path;
vfs::FileBuffer param;
if (vfs::read_app_file(param, emuenv.pref_path.wstring(), app_path, "sce_sys/param.sfo")) {
sfo::get_param_info(emuenv.app_info, param, emuenv.cfg.sys_lang);
} else {
emuenv.app_info.app_addcont = emuenv.app_info.app_savedata = emuenv.app_info.app_short_title = emuenv.app_info.app_title = emuenv.app_info.app_title_id = emuenv.app_path; // Use app path as TitleID, addcont, Savedata, Short title and Title
emuenv.app_info.app_version = emuenv.app_info.app_category = emuenv.app_info.app_parental_level = "N/A";
}
gui.app_selector.user_apps.push_back({ emuenv.app_info.app_version, emuenv.app_info.app_category, emuenv.app_info.app_content_id, emuenv.app_info.app_addcont, emuenv.app_info.app_savedata, emuenv.app_info.app_parental_level, emuenv.app_info.app_short_title, emuenv.app_info.app_title, emuenv.app_info.app_title_id, emuenv.app_path });
}
void get_user_apps_title(GuiState &gui, EmuEnvState &emuenv) {
const fs::path app_path{ emuenv.pref_path / "ux0/app" };
if (!fs::exists(app_path))
return;
gui.app_selector.user_apps.clear();
for (const auto &app : fs::directory_iterator(app_path)) {
if (!app.path().empty() && fs::is_directory(app.path())
&& !app.path().filename_is_dot() && !app.path().filename_is_dot_dot()) {
const auto app_path = app.path().stem().generic_string();
get_app_param(gui, emuenv, app_path);
}
}
save_apps_cache(gui, emuenv);
}
void get_sys_apps_title(GuiState &gui, EmuEnvState &emuenv) {
gui.app_selector.sys_apps.clear();
const std::array<std::string, 4> sys_apps_list = { "NPXS10003", "NPXS10008", "NPXS10015", "NPXS10026" };
for (const auto &app : sys_apps_list) {
vfs::FileBuffer params;
if (vfs::read_file(VitaIoDevice::vs0, params, emuenv.pref_path.wstring(), "app/" + app + "/sce_sys/param.sfo")) {
SfoFile sfo_handle;
sfo::load(sfo_handle, params);
sfo::get_data_by_key(emuenv.app_info.app_version, sfo_handle, "APP_VER");
if (emuenv.app_info.app_version[0] == '0')
emuenv.app_info.app_version.erase(emuenv.app_info.app_version.begin());
sfo::get_data_by_key(emuenv.app_info.app_category, sfo_handle, "CATEGORY");
sfo::get_data_by_key(emuenv.app_info.app_short_title, sfo_handle, fmt::format("STITLE_{:0>2d}", emuenv.cfg.sys_lang));
sfo::get_data_by_key(emuenv.app_info.app_title, sfo_handle, fmt::format("TITLE_{:0>2d}", emuenv.cfg.sys_lang));
boost::trim(emuenv.app_info.app_title);
sfo::get_data_by_key(emuenv.app_info.app_title_id, sfo_handle, "TITLE_ID");
} else {
emuenv.app_info.app_version = "1.00";
emuenv.app_info.app_category = "gda";
emuenv.app_info.app_title_id = app;
if (app == "NPXS10003") {
emuenv.app_info.app_short_title = "Browser";
emuenv.app_info.app_title = "Internet Browser";
} else if (app == "NPXS10008") {
emuenv.app_info.app_short_title = "Trophies";
emuenv.app_info.app_title = "Trophy Collection";
} else if (app == "NPXS10015")
emuenv.app_info.app_short_title = emuenv.app_info.app_title = "Settings";
else
emuenv.app_info.app_short_title = emuenv.app_info.app_title = "Content Manager";
}
gui.app_selector.sys_apps.push_back({ emuenv.app_info.app_version, emuenv.app_info.app_category, {}, {}, {}, {}, emuenv.app_info.app_short_title, emuenv.app_info.app_title, emuenv.app_info.app_title_id, app });
}
std::sort(gui.app_selector.sys_apps.begin(), gui.app_selector.sys_apps.end(), [](const App &lhs, const App &rhs) {
return string_utils::toupper(lhs.title) < string_utils::toupper(rhs.title);
});
}
std::map<DateTime, std::string> get_date_time(GuiState &gui, EmuEnvState &emuenv, const tm &date_time) {
std::map<DateTime, std::string> date_time_str;
if (!emuenv.io.user_id.empty()) {
const auto day_str = gui.lang.common.wday[date_time.tm_wday];
const auto month_str = gui.lang.common.ymonth[date_time.tm_mon];
const auto days_str = gui.lang.common.mday[date_time.tm_mday];
const auto year = date_time.tm_year + 1900;
const auto month = date_time.tm_mon + 1;
const auto day = date_time.tm_mday;
switch (emuenv.cfg.sys_date_format) {
case SCE_SYSTEM_PARAM_DATE_FORMAT_YYYYMMDD:
date_time_str[DateTime::DATE_DETAIL] = fmt::format("{} {} ({})", month_str, days_str, day_str);
date_time_str[DateTime::DATE_MINI] = fmt::format("{}/{}/{}", year, month, day);
break;
case SCE_SYSTEM_PARAM_DATE_FORMAT_DDMMYYYY: {
const auto small_month_str = gui.lang.common.small_ymonth[date_time.tm_mon];
const auto small_days_str = gui.lang.common.small_mday[day];
date_time_str[DateTime::DATE_DETAIL] = fmt::format("{} {} ({})", small_days_str, small_month_str, day_str);
date_time_str[DateTime::DATE_MINI] = fmt::format("{}/{}/{}", day, month, year);
break;
}
case SCE_SYSTEM_PARAM_DATE_FORMAT_MMDDYYYY:
date_time_str[DateTime::DATE_DETAIL] = fmt::format("{} {} ({})", month_str, days_str, day_str);
date_time_str[DateTime::DATE_MINI] = fmt::format("{}/{}/{}", month, day, year);
break;
}
}
const auto clock_12h = emuenv.io.user_id.empty() || (emuenv.cfg.sys_time_format == SCE_SYSTEM_PARAM_TIME_FORMAT_12HOUR);
if (clock_12h && date_time.tm_hour == 0)
date_time_str[DateTime::HOUR] = std::to_string(12);
else
date_time_str[DateTime::HOUR] = std::to_string(clock_12h && date_time.tm_hour > 12 ? (date_time.tm_hour - 12) : date_time.tm_hour);
date_time_str[DateTime::CLOCK] = fmt::format("{}:{:0>2d}", date_time_str[DateTime::HOUR], date_time.tm_min);
date_time_str[DateTime::DAY_MOMENT] = date_time.tm_hour >= 12 ? "PM" : "AM";
return date_time_str;
}
ImTextureID load_image(GuiState &gui, const char *data, const std::uint32_t size) {
int width;
int height;
stbi_uc *img_data = stbi_load_from_memory(reinterpret_cast<const stbi_uc *>(data), size, &width, &height,
nullptr, STBI_rgb_alpha);
if (!data)
return nullptr;
const auto handle = ImGui_ImplSdl_CreateTexture(gui.imgui_state.get(), img_data, width, height);
stbi_image_free(img_data);
return handle;
}
void pre_init(GuiState &gui, EmuEnvState &emuenv) {
if (ImGui::GetCurrentContext() == NULL) {
ImGui::CreateContext();
}
gui.imgui_state.reset(ImGui_ImplSdl_Init(emuenv.renderer.get(), emuenv.window.get()));
assert(gui.imgui_state);
init_style(emuenv);
init_font(gui, emuenv);
lang::init_lang(gui.lang, emuenv);
bool result = ImGui_ImplSdl_CreateDeviceObjects(gui.imgui_state.get());
assert(result);
}
void init(GuiState &gui, EmuEnvState &emuenv) {
get_modules_list(gui, emuenv);
get_notice_list(emuenv);
get_users_list(gui, emuenv);
get_time_apps(gui, emuenv);
if (emuenv.cfg.show_welcome)
gui.help_menu.welcome_dialog = true;
get_sys_apps_title(gui, emuenv);
init_home(gui, emuenv);
// Initialize trophy callback
emuenv.np.trophy_state.trophy_unlock_callback = [&gui](NpTrophyUnlockCallbackData &callback_data) {
const std::lock_guard<std::mutex> guard(gui.trophy_unlock_display_requests_access_mutex);
gui.trophy_unlock_display_requests.insert(gui.trophy_unlock_display_requests.begin(), callback_data);
};
}
void draw_begin(GuiState &gui, EmuEnvState &emuenv) {
ImGui_ImplSdl_NewFrame(gui.imgui_state.get());
emuenv.renderer_focused = !ImGui::GetIO().WantCaptureMouse;
// async loading, renderer texture creation needs to be synchronous
// cant bind opengl context outside main thread on macos now
if (gui.app_selector.icon_async_loader)
gui.app_selector.icon_async_loader->commit(gui);
}
void draw_end(GuiState &gui, SDL_Window *window) {
ImGui::Render();
ImGui_ImplSdl_RenderDrawData(gui.imgui_state.get());
}
void draw_touchpad_cursor(EmuEnvState &emuenv) {
SceTouchPortType port;
const auto touchpad_fingers_pos = get_touchpad_fingers_pos(port);
if (touchpad_fingers_pos.empty())
return;
const ImVec2 RES_SCALE(emuenv.viewport_size.x / emuenv.res_width_dpi_scale, emuenv.viewport_size.y / emuenv.res_height_dpi_scale);
const ImVec2 SCALE(RES_SCALE.x * emuenv.dpi_scale, RES_SCALE.y * emuenv.dpi_scale);
const auto color = (port == SCE_TOUCH_PORT_FRONT) ? IM_COL32(0.f, 102.f, 204.f, 255.f) : IM_COL32(255.f, 0.f, 0.f, 255.f);
for (const auto &pos : touchpad_fingers_pos) {
auto x = emuenv.viewport_pos.x + (pos.x * emuenv.viewport_size.x);
auto y = emuenv.viewport_pos.y + (pos.y * emuenv.viewport_size.y);
ImGui::GetForegroundDrawList()->AddCircle(ImVec2(x, y), 20.f * SCALE.x, color, 0, 4.f * SCALE.x);
}
}
void draw_vita_area(GuiState &gui, EmuEnvState &emuenv) {
if (gui.vita_area.start_screen)
draw_start_screen(gui, emuenv);
ImGui::PushFont(gui.vita_font);
if (gui.vita_area.app_close)
draw_app_close(gui, emuenv);
if (gui.vita_area.home_screen)
draw_home_screen(gui, emuenv);
if (gui.vita_area.live_area_screen)
draw_live_area_screen(gui, emuenv);
if (gui.vita_area.manual)
draw_manual(gui, emuenv);
// Draw install dialogs
if (gui.file_menu.archive_install_dialog)
draw_archive_install_dialog(gui, emuenv);
if (gui.file_menu.firmware_install_dialog)
draw_firmware_install_dialog(gui, emuenv);
if (gui.file_menu.license_install_dialog)
draw_license_install_dialog(gui, emuenv);
if (gui.file_menu.pkg_install_dialog)
draw_pkg_install_dialog(gui, emuenv);
if (gui.vita_area.user_management)
draw_user_management(gui, emuenv);
if (emuenv.cfg.show_compile_shaders && gui.shaders_compiled_display_count > 0)
draw_shaders_count_compiled(gui, emuenv);
if (!gui.trophy_unlock_display_requests.empty())
draw_trophies_unlocked(gui, emuenv);
if (emuenv.ime.state && !gui.vita_area.home_screen && !gui.vita_area.live_area_screen && !gui.vita_area.user_management && get_sys_apps_state(gui))
draw_ime(emuenv.ime, emuenv);
// System App
if (gui.vita_area.content_manager)
draw_content_manager(gui, emuenv);
if (gui.vita_area.settings)
draw_settings(gui, emuenv);
if (gui.vita_area.trophy_collection)
draw_trophy_collection(gui, emuenv);
if (gui.help_menu.vita3k_update)
draw_vita3k_update(gui, emuenv);
if ((emuenv.cfg.show_info_bar || !emuenv.display.imgui_render || !gui.vita_area.home_screen) && gui.vita_area.information_bar)
draw_information_bar(gui, emuenv);
// Info Message
if (!gui.info_message.msg.empty())
draw_info_message(gui, emuenv);
ImGui::PopFont();
}
void draw_ui(GuiState &gui, EmuEnvState &emuenv) {
ImGui::PushFont(gui.vita_font);
if ((gui.vita_area.home_screen || !emuenv.io.app_path.empty()) && get_sys_apps_state(gui) && !gui.vita_area.live_area_screen && !gui.vita_area.user_management && (!emuenv.cfg.show_info_bar || !gui.vita_area.information_bar))
draw_main_menu_bar(gui, emuenv);
if (gui.configuration_menu.custom_settings_dialog || gui.configuration_menu.settings_dialog)
draw_settings_dialog(gui, emuenv);
if (gui.controls_menu.controls_dialog)
draw_controls_dialog(gui, emuenv);
if (gui.controls_menu.controllers_dialog)
draw_controllers_dialog(gui, emuenv);
if (gui.help_menu.about_dialog)
draw_about_dialog(gui, emuenv);
if (gui.help_menu.welcome_dialog)
draw_welcome_dialog(gui, emuenv);
ImGui::PopFont();
ImGui::PushFont(gui.monospaced_font);
if (gui.debug_menu.threads_dialog)
draw_threads_dialog(gui, emuenv);
if (gui.debug_menu.thread_details_dialog)
draw_thread_details_dialog(gui, emuenv);
if (gui.debug_menu.semaphores_dialog)
draw_semaphores_dialog(gui, emuenv);
if (gui.debug_menu.mutexes_dialog)
draw_mutexes_dialog(gui, emuenv);
if (gui.debug_menu.lwmutexes_dialog)
draw_lw_mutexes_dialog(gui, emuenv);
if (gui.debug_menu.condvars_dialog)
draw_condvars_dialog(gui, emuenv);
if (gui.debug_menu.lwcondvars_dialog)
draw_lw_condvars_dialog(gui, emuenv);
if (gui.debug_menu.eventflags_dialog)
draw_event_flags_dialog(gui, emuenv);
if (gui.debug_menu.allocations_dialog)
draw_allocations_dialog(gui, emuenv);
if (gui.debug_menu.disassembly_dialog)
draw_disassembly_dialog(gui, emuenv);
ImGui::PopFont();
}
} // namespace gui