diff --git a/lang/system/en.xml b/lang/system/en.xml index 2a136b11f..948e7ed48 100644 --- a/lang/system/en.xml +++ b/lang/system/en.xml @@ -125,8 +125,6 @@ depending on its size and your hardware. An error occurred. Error code: {} Cancel - Connecting... -Please wait. Close Could not load the file. Could not save the file. @@ -142,10 +140,6 @@ Please wait. Submit Yes - Could not connect to the server. - You must sign in to Vita3K Network to use this feature. - Could not connect to the Internet. - The connection to Vita3K Network has been lost. Sunday Monday @@ -564,27 +558,6 @@ Check vita3k.log to see console output for details. 3. If you want to install or boot Vitamin, it is not supported. - - Saved Data Auto-Upload - Copying... - Download - Eligible - Ineligible - Location - Memory Card - {} Minutes Left - Online Storage - Do you want to overwrite the saved data? - PS Vita System - PS TV System - {} Seconds Left - Size - Trophy Earning - Updated - Upload - Uploaded - - Gamepad Overlay Show gamepad overlay ingame diff --git a/lang/system/fr.xml b/lang/system/fr.xml index ea3231ae1..43c91aeb4 100644 --- a/lang/system/fr.xml +++ b/lang/system/fr.xml @@ -93,8 +93,6 @@ Une erreur est survenue. Code d'erreur: {} Annuler - Connexion en cours... -Merci de patienter. Fermer Le chargement du fichier a échoué. La sauvegarde du fichier a échoué. @@ -108,10 +106,6 @@ Merci de patienter. Sélectionner tout Oui - La connexion au serveur a échoué. - Vous devez vous connecter à Vita3K Network pour utiliser cette fonctionnalité. - La connexion à Internet a échoué. - La connexion à Vita3K Network a été interrompue. dimanche lundi @@ -378,27 +372,6 @@ Appuyez sur 'Autoriser l'accès' pour continuer. - - Téléchargement en amont automatique des données sauvegardées - Copie en cours... - Télécharger - Autorisé - Non autorisé - Emplacement - Carte mémoire - {} minutes restantes - Stockage en ligne - Écraser les données sauvegardées ? - Système PS Vita - Système PS TV - {} secondes restantes - Taille - Obtention de trophées - Mis à jour - Téléchargement en amont - Téléchargée en amont - - Musique système diff --git a/vita3k/CMakeLists.txt b/vita3k/CMakeLists.txt index 4a41d7113..e8aa40a75 100644 --- a/vita3k/CMakeLists.txt +++ b/vita3k/CMakeLists.txt @@ -130,7 +130,6 @@ add_subdirectory(touch) add_subdirectory(util) add_subdirectory(gdbstub) add_subdirectory(packages) -add_subdirectory(v3kn) add_subdirectory(vkutil) if(ANDROID) diff --git a/vita3k/emuenv/CMakeLists.txt b/vita3k/emuenv/CMakeLists.txt index d848d7235..473e9522e 100644 --- a/vita3k/emuenv/CMakeLists.txt +++ b/vita3k/emuenv/CMakeLists.txt @@ -5,4 +5,4 @@ add_library( target_include_directories(emuenv INTERFACE include) target_link_libraries(emuenv PUBLIC mem) -target_link_libraries(emuenv PRIVATE audio camera config ctrl dialog display ime io kernel motion net ngs nids np regmgr renderer touch gdbstub packages http v3kn) +target_link_libraries(emuenv PRIVATE audio camera config ctrl dialog display ime io kernel motion net ngs nids np regmgr renderer touch gdbstub packages http) diff --git a/vita3k/emuenv/include/emuenv/state.h b/vita3k/emuenv/include/emuenv/state.h index d3e23a120..77f3b1f28 100644 --- a/vita3k/emuenv/include/emuenv/state.h +++ b/vita3k/emuenv/include/emuenv/state.h @@ -55,7 +55,6 @@ struct DialogState; struct Ime; struct License; struct RegMgrState; -struct V3KNState; struct SfoFile; struct GDBState; struct HTTPState; @@ -102,7 +101,6 @@ private: std::unique_ptr _ime; std::unique_ptr _license; std::unique_ptr _regmgr; - std::unique_ptr _v3kn; std::unique_ptr _sfo_handle; std::unique_ptr _gdb; std::unique_ptr _http; @@ -169,7 +167,6 @@ public: Ime &ime; License &license; RegMgrState ®mgr; - V3KNState &v3kn; SfoFile &sfo_handle; NIDSet missing_nids; float system_dpi_scale = 1.f; diff --git a/vita3k/emuenv/src/emuenv.cpp b/vita3k/emuenv/src/emuenv.cpp index bd03e3986..a45f43c12 100644 --- a/vita3k/emuenv/src/emuenv.cpp +++ b/vita3k/emuenv/src/emuenv.cpp @@ -38,7 +38,6 @@ #include #include #include -#include #include @@ -86,8 +85,6 @@ EmuEnvState::EmuEnvState() , license(*_license) , _regmgr(new RegMgrState) , regmgr(*_regmgr) - , _v3kn(new V3KNState) - , v3kn(*_v3kn) , _sfo_handle(new SfoFile) , sfo_handle(*_sfo_handle) , _gdb(new GDBState) diff --git a/vita3k/gui/CMakeLists.txt b/vita3k/gui/CMakeLists.txt index 10e109b03..86c77bd5d 100644 --- a/vita3k/gui/CMakeLists.txt +++ b/vita3k/gui/CMakeLists.txt @@ -26,7 +26,6 @@ set(GUI_SOURCES src/main_menubar.cpp src/manual.cpp src/mutexes_dialog.cpp - src/online_storage.cpp src/perf_overlay.cpp src/pkg_install_dialog.cpp src/private.h @@ -45,7 +44,6 @@ set(GUI_SOURCES src/disassembly_dialog.cpp src/trophy_unlocked.cpp src/about_dialog.cpp - src/v3kn_dialog.cpp src/vita3k_update.cpp src/welcome_dialog.cpp ) @@ -61,7 +59,7 @@ endif() add_library(gui STATIC ${GUI_SOURCES}) target_include_directories(gui PUBLIC include ${CMAKE_SOURCE_DIR}/vita3k) -target_link_libraries(gui PUBLIC app bgm_player compat config dialog emuenv ime imgui lang regmgr np v3kn) +target_link_libraries(gui PUBLIC app bgm_player compat config dialog emuenv ime imgui lang regmgr np) target_link_libraries(gui PRIVATE audio camera cppcommon ctrl kernel miniz motion psvpfsparser pugixml::pugixml stb renderer packages SDL3::SDL3 touch vkutil host::dialog concurrentqueue) # Link macOS frameworks for Objective-C++ code if(APPLE) diff --git a/vita3k/gui/include/gui/state.h b/vita3k/gui/include/gui/state.h index 2eaec30ba..99070c4dc 100644 --- a/vita3k/gui/include/gui/state.h +++ b/vita3k/gui/include/gui/state.h @@ -120,13 +120,10 @@ struct VitaAreaState { bool information_bar = false; bool live_area_screen = false; bool manual = false; - bool online_storage = false; bool settings = false; bool start_screen = false; bool trophy_collection = false; bool user_management = false; - bool connecting_please_wait = false; - bool please_wait = false; }; struct FileMenuState { @@ -153,7 +150,6 @@ struct DebugMenuState { struct ConfigurationMenuState { bool custom_settings_dialog = false; bool settings_dialog = false; - bool v3kn_dialog = false; }; struct ControlMenuState { @@ -185,18 +181,6 @@ enum DateTime { HOUR, }; -enum class AvatarSize : uint32_t { - SMALL = 34, - MEDIUM = 130, - LARGE = 160, - V3KN = 80 -}; - -struct AvatarInfo { - ImVec2 pos; - ImVec2 size; -}; - struct User { std::string id; std::string name = "Vita3K"; @@ -303,10 +287,6 @@ struct GuiState { std::map users; std::map users_avatar; - std::map> users_avatar_infos; - - ImGui_Texture v3kn_avatar; - ImGui_Texture v3kn_panel; MemoryEditor memory_editor; MemoryEditor gxp_shader_editor; diff --git a/vita3k/gui/src/app_context_menu.cpp b/vita3k/gui/src/app_context_menu.cpp index 32d0bf5a3..a571309c9 100644 --- a/vita3k/gui/src/app_context_menu.cpp +++ b/vita3k/gui/src/app_context_menu.cpp @@ -33,8 +33,6 @@ #include #include -#include - #include #include #undef main @@ -881,8 +879,6 @@ void draw_app_context_menu(GuiState &gui, EmuEnvState &emuenv, const std::string open_manual(gui, emuenv, app_path); if (ImGui::MenuItem(gui.lang.home_screen["refresh"].c_str())) refresh_app(gui, emuenv, app_path); - if (title_id.starts_with("PCS") && ImGui::MenuItem(gui.lang.online_storage["online_storage"].c_str())) - v3kn::open_online_storage(gui, emuenv, title_id); ImGui::EndMenu(); } if (ImGui::BeginMenu(common["delete"].c_str())) { diff --git a/vita3k/gui/src/gui.cpp b/vita3k/gui/src/gui.cpp index 94435c6d5..45602782d 100644 --- a/vita3k/gui/src/gui.cpp +++ b/vita3k/gui/src/gui.cpp @@ -463,54 +463,6 @@ void init_app_background(GuiState &gui, EmuEnvState &emuenv, const std::string & stbi_image_free(data); } -static void draw_connecting_please_wait(GuiState &gui, EmuEnvState &emuenv) { - 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 auto RES_SCALE = ImVec2(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); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.f); - ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.f); - ImGui::SetNextWindowPos(VIEWPORT_POS); - ImGui::SetNextWindowSize(VIEWPORT_SIZE); - ImGui::Begin("please_wait", &gui.vita_area.connecting_please_wait, ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoDecoration); - const ImVec2 WINDOW_SIZE(520.f * SCALE.x, 120.f * SCALE.y); - ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 15.f * SCALE.x); - ImGui::SetNextWindowPos(ImVec2(VIEWPORT_POS.x + ((VIEWPORT_SIZE.x - WINDOW_SIZE.x) / 2.f), VIEWPORT_POS.y + ((VIEWPORT_SIZE.y - WINDOW_SIZE.y) / 2.f))); - ImGui::BeginChild("please_wait_child", WINDOW_SIZE, ImGuiChildFlags_Borders, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings); - const auto text = emuenv.common_dialog.lang.common["connecting_please_wait"]; - const auto text_size = ImGui::CalcTextSize(text.c_str()); - ImGui::SetCursorPos(ImVec2((WINDOW_SIZE.x - text_size.x) / 2.f, (WINDOW_SIZE.y - text_size.y) / 2.f)); - ImGui::Text("%s", text.c_str()); - ImGui::EndChild(); - ImGui::PopStyleVar(); - ImGui::End(); - ImGui::PopStyleVar(2); -} - -static void draw_please_wait(GuiState &gui, EmuEnvState &emuenv) { - 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 auto RES_SCALE = ImVec2(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); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.f); - ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.f); - ImGui::SetNextWindowPos(VIEWPORT_POS); - ImGui::SetNextWindowSize(VIEWPORT_SIZE); - ImGui::Begin("please_wait", &gui.vita_area.please_wait, ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoDecoration); - const ImVec2 WINDOW_SIZE(520.f * SCALE.x, 120.f * SCALE.y); - ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 15.f * SCALE.x); - ImGui::SetNextWindowPos(ImVec2(VIEWPORT_POS.x + ((VIEWPORT_SIZE.x - WINDOW_SIZE.x) / 2.f), VIEWPORT_POS.y + ((VIEWPORT_SIZE.y - WINDOW_SIZE.y) / 2.f))); - ImGui::BeginChild("please_wait_child", WINDOW_SIZE, ImGuiChildFlags_Borders, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings); - const auto text = emuenv.common_dialog.lang.common["please_wait"]; - const auto text_size = ImGui::CalcTextSize(text.c_str()); - ImGui::SetCursorPos(ImVec2((WINDOW_SIZE.x - text_size.x) / 2.f, (WINDOW_SIZE.y - text_size.y) / 2.f)); - ImGui::Text("%s", text.c_str()); - ImGui::EndChild(); - ImGui::PopStyleVar(); - ImGui::End(); - ImGui::PopStyleVar(2); -} - 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; @@ -975,21 +927,14 @@ void draw_vita_area(GuiState &gui, EmuEnvState &emuenv) { if (gui.vita_area.app_close) draw_app_close(gui, emuenv); - if (gui.vita_area.connecting_please_wait) - draw_connecting_please_wait(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); - if (gui.vita_area.please_wait) - draw_please_wait(gui, emuenv); - // Draw install dialogs if (gui.file_menu.archive_install_dialog) draw_archive_install_dialog(gui, emuenv); @@ -1016,9 +961,6 @@ void draw_vita_area(GuiState &gui, EmuEnvState &emuenv) { if (gui.vita_area.content_manager) draw_content_manager(gui, emuenv); - if (gui.vita_area.online_storage) - draw_online_storage(gui, emuenv); - if (gui.vita_area.settings) draw_settings(gui, emuenv); @@ -1043,9 +985,6 @@ void draw_ui(GuiState &gui, EmuEnvState &emuenv) { 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.v3kn_dialog) - draw_v3kn_dialog(gui, emuenv); - if (gui.configuration_menu.custom_settings_dialog || gui.configuration_menu.settings_dialog) draw_settings_dialog(gui, emuenv); diff --git a/vita3k/gui/src/live_area.cpp b/vita3k/gui/src/live_area.cpp index 3efd5b399..dad8c2b4c 100644 --- a/vita3k/gui/src/live_area.cpp +++ b/vita3k/gui/src/live_area.cpp @@ -35,12 +35,9 @@ #include -#include - #include #include -#include #include #include @@ -69,7 +66,7 @@ void GateAnimation::update() { namespace gui { bool get_sys_apps_state(GuiState &gui) { - return !gui.vita_area.content_manager && !gui.vita_area.online_storage && !gui.vita_area.settings && !gui.vita_area.trophy_collection && !gui.vita_area.manual; + return !gui.vita_area.content_manager && !gui.vita_area.settings && !gui.vita_area.trophy_collection && !gui.vita_area.manual; } struct FRAME { @@ -580,8 +577,7 @@ enum LiveAreaType { GATE, SEARCH, MANUAL, - REFRESH, - CLOUD + REFRESH }; struct ButtonEntry { @@ -916,7 +912,6 @@ void draw_live_area_screen(GuiState &gui, EmuEnvState &emuenv) { const ImVec2 background_pos_max(background_pos_min.x + bg_scal_size.x, background_pos_min.y + bg_scal_size.y); DRAW_LIST->AddImage(gui.live_items[app_path][frame.id]["background"][current_item[app_path][frame.id]], background_pos_min, background_pos_max); } - if (gui.live_items[app_path][frame.id].contains("image")) { const auto image_pos_min = set_screen_pos(img_pos); img_scal_size = apply_zoom_size(img_scal_size); @@ -1243,155 +1238,6 @@ void draw_live_area_screen(GuiState &gui, EmuEnvState &emuenv) { } if (app_device == VitaIoDevice::ux0) { - const auto rotate_point = [](const ImVec2 &point, const ImVec2 ¢er, float cos_angle, float sin_angle) -> ImVec2 { - const ImVec2 offset(point.x - center.x, point.y - center.y); - return ImVec2( - center.x + (offset.x * cos_angle) - (offset.y * sin_angle), - center.y + (offset.x * sin_angle) + (offset.y * cos_angle)); - }; - - const auto draw_cloud_icon = [&](ImDrawList *draw_list, const ImVec2 &pos_min, const ImVec2 &size) { - const float BG_ROUNDING = 12.0f * SCALE.x; - const float BORDER_THICKNESS = 1.5f * SCALE.x; - const float ANGLE = -0.24f; - const float COS_ANGLE = std::cos(ANGLE); - const float SIN_ANGLE = std::sin(ANGLE); - const float BRANCH_SQUARE = size.x * 0.225f; - const float HALF_SQUARE = BRANCH_SQUARE * 0.50f; - const float HALF_EXTENT = BRANCH_SQUARE * 1.50f; - const float SYMBOL_STROKE = 2.0f * SCALE.x; - const float SYMBOL_TRIANGLE_RADIUS = size.x * 0.053f; - const float SYMBOL_SQUARE_RADIUS = size.x * 0.051f; - const float SYMBOL_CIRCLE_RADIUS = size.x * 0.052f; - const float SYMBOL_CROSS_RADIUS = size.x * 0.047f; - const ImVec2 POS_MAX(pos_min.x + size.x, pos_min.y + size.y); - const ImVec2 CENTER(pos_min.x + size.x * 0.53f, pos_min.y + size.y * 0.44f); - const ImVec2 DEPTH(-5.6f * SCALE.x, 6.4f * SCALE.y); - const ImU32 BG_COLOR = IM_COL32(221, 221, 221, 255); - const ImU32 BORDER_COLOR = IM_COL32(192, 193, 196, 255); - const ImU32 FRONT_COLOR = IM_COL32(255, 226, 86, 255); - const ImU32 OUTLINE_COLOR = IM_COL32(220, 164, 20, 255); - const ImU32 SYMBOL_COLOR = IM_COL32(158, 103, 6, 255); - const ImVec2 RAW[12] = { - ImVec2(-HALF_SQUARE, -HALF_EXTENT), ImVec2(HALF_SQUARE, -HALF_EXTENT), - ImVec2(HALF_SQUARE, -HALF_SQUARE), ImVec2(HALF_EXTENT, -HALF_SQUARE), - ImVec2(HALF_EXTENT, HALF_SQUARE), ImVec2(HALF_SQUARE, HALF_SQUARE), - ImVec2(HALF_SQUARE, HALF_EXTENT), ImVec2(-HALF_SQUARE, HALF_EXTENT), - ImVec2(-HALF_SQUARE, HALF_SQUARE), ImVec2(-HALF_EXTENT, HALF_SQUARE), - ImVec2(-HALF_EXTENT, -HALF_SQUARE), ImVec2(-HALF_SQUARE, -HALF_SQUARE) - }; - const ImVec2 FRONT_SQUARES[5] = { - ImVec2(0.0f, 0.0f), - ImVec2(0.0f, -BRANCH_SQUARE), - ImVec2(-BRANCH_SQUARE, 0.0f), - ImVec2(BRANCH_SQUARE, 0.0f), - ImVec2(0.0f, BRANCH_SQUARE) - }; - struct FaceDef { - int first; - int second; - ImU32 color; - ImVec2 offset_scale; - }; - const FaceDef FACES[] = { - { 4, 5, IM_COL32(220, 160, 52, 255), ImVec2(0.22f, 0.88f) }, - { 6, 7, IM_COL32(205, 145, 36, 255), ImVec2(1.00f, 1.00f) }, - { 7, 8, IM_COL32(212, 151, 42, 255), ImVec2(1.00f, 1.00f) }, - { 8, 9, IM_COL32(219, 158, 48, 255), ImVec2(1.00f, 1.00f) }, - { 9, 10, IM_COL32(214, 153, 44, 255), ImVec2(1.00f, 1.00f) }, - { 10, 11, IM_COL32(208, 148, 40, 255), ImVec2(0.92f, 0.92f) }, - { 11, 0, IM_COL32(198, 140, 34, 255), ImVec2(0.70f, 0.78f) } - }; - - draw_list->AddRectFilled(pos_min, POS_MAX, BG_COLOR, BG_ROUNDING, ImDrawFlags_RoundCornersBottom); - draw_list->AddRect(pos_min, POS_MAX, BORDER_COLOR, BG_ROUNDING, ImDrawFlags_RoundCornersBottom, BORDER_THICKNESS); - - const auto to_world = [&](const ImVec2 &point) -> ImVec2 { - return ImVec2(CENTER.x + point.x * COS_ANGLE - point.y * SIN_ANGLE, CENTER.y + point.x * SIN_ANGLE + point.y * COS_ANGLE); - }; - const auto rotate_symbol_point = [&](const ImVec2 &point, const ImVec2 &pivot) -> ImVec2 { - return rotate_point(point, pivot, COS_ANGLE, SIN_ANGLE); - }; - const auto make_vec2 = [](float x, float y) -> ImVec2 { - ImVec2 value; - value.x = x; - value.y = y; - return value; - }; - const auto draw_face = [&](int first, int second, ImU32 color, const ImVec2 &offset_scale) { - const ImVec2 offset = make_vec2(DEPTH.x * offset_scale.x, DEPTH.y * offset_scale.y); - const ImVec2 first_world = to_world(RAW[first]); - const ImVec2 second_world = to_world(RAW[second]); - const ImVec2 quad[4] = { - make_vec2(first_world.x, first_world.y), - make_vec2(second_world.x, second_world.y), - make_vec2(second_world.x + offset.x, second_world.y + offset.y), - make_vec2(first_world.x + offset.x, first_world.y + offset.y) - }; - draw_list->AddConvexPolyFilled(quad, IM_ARRAYSIZE(quad), color); - }; - const auto draw_square = [&](const ImVec2 &square_center, ImU32 color) { - const ImVec2 quad[4] = { - to_world(ImVec2(square_center.x - HALF_SQUARE, square_center.y - HALF_SQUARE)), - to_world(ImVec2(square_center.x + HALF_SQUARE, square_center.y - HALF_SQUARE)), - to_world(ImVec2(square_center.x + HALF_SQUARE, square_center.y + HALF_SQUARE)), - to_world(ImVec2(square_center.x - HALF_SQUARE, square_center.y + HALF_SQUARE)) - }; - draw_list->AddConvexPolyFilled(quad, IM_ARRAYSIZE(quad), color); - }; - const auto symbol_center = [&](const ImVec2 &offset) -> ImVec2 { - return to_world(offset); - }; - - for (const auto &face : FACES) - draw_face(face.first, face.second, face.color, face.offset_scale); - - for (const auto &square_center : FRONT_SQUARES) - draw_square(square_center, FRONT_COLOR); - - ImVec2 TOP[12]; - for (int i = 0; i < IM_ARRAYSIZE(RAW); ++i) - TOP[i] = to_world(RAW[i]); - draw_list->AddPolyline(TOP, IM_ARRAYSIZE(TOP), OUTLINE_COLOR, ImDrawFlags_Closed, 1.8f * SCALE.x); - - { - const ImVec2 TRI_CENTER = symbol_center(ImVec2(0.0f, -BRANCH_SQUARE)); - ImVec2 tri[3] = { - ImVec2(TRI_CENTER.x, TRI_CENTER.y - SYMBOL_TRIANGLE_RADIUS), - ImVec2(TRI_CENTER.x - (SYMBOL_TRIANGLE_RADIUS * 0.87f), TRI_CENTER.y + (SYMBOL_TRIANGLE_RADIUS * 0.50f)), - ImVec2(TRI_CENTER.x + (SYMBOL_TRIANGLE_RADIUS * 0.87f), TRI_CENTER.y + (SYMBOL_TRIANGLE_RADIUS * 0.50f)) - }; - for (auto &point : tri) - point = rotate_symbol_point(point, TRI_CENTER); - draw_list->AddPolyline(tri, IM_ARRAYSIZE(tri), SYMBOL_COLOR, ImDrawFlags_Closed, SYMBOL_STROKE); - } - - { - const ImVec2 SQUARE_CENTER = symbol_center(ImVec2(-BRANCH_SQUARE, 0.0f)); - ImVec2 square[4] = { - ImVec2(SQUARE_CENTER.x - SYMBOL_SQUARE_RADIUS, SQUARE_CENTER.y - SYMBOL_SQUARE_RADIUS), - ImVec2(SQUARE_CENTER.x + SYMBOL_SQUARE_RADIUS, SQUARE_CENTER.y - SYMBOL_SQUARE_RADIUS), - ImVec2(SQUARE_CENTER.x + SYMBOL_SQUARE_RADIUS, SQUARE_CENTER.y + SYMBOL_SQUARE_RADIUS), - ImVec2(SQUARE_CENTER.x - SYMBOL_SQUARE_RADIUS, SQUARE_CENTER.y + SYMBOL_SQUARE_RADIUS) - }; - for (auto &point : square) - point = rotate_symbol_point(point, SQUARE_CENTER); - draw_list->AddPolyline(square, IM_ARRAYSIZE(square), SYMBOL_COLOR, ImDrawFlags_Closed, SYMBOL_STROKE); - } - - draw_list->AddCircle(symbol_center(ImVec2(BRANCH_SQUARE, 0.0f)), SYMBOL_CIRCLE_RADIUS, SYMBOL_COLOR, 0, SYMBOL_STROKE); - - { - const ImVec2 CROSS_CENTER = symbol_center(ImVec2(0.0f, BRANCH_SQUARE)); - const ImVec2 LINE_A = rotate_symbol_point(ImVec2(CROSS_CENTER.x - SYMBOL_CROSS_RADIUS, CROSS_CENTER.y - SYMBOL_CROSS_RADIUS), CROSS_CENTER); - const ImVec2 LINE_B = rotate_symbol_point(ImVec2(CROSS_CENTER.x + SYMBOL_CROSS_RADIUS, CROSS_CENTER.y + SYMBOL_CROSS_RADIUS), CROSS_CENTER); - const ImVec2 LINE_C = rotate_symbol_point(ImVec2(CROSS_CENTER.x - SYMBOL_CROSS_RADIUS, CROSS_CENTER.y + SYMBOL_CROSS_RADIUS), CROSS_CENTER); - const ImVec2 LINE_D = rotate_symbol_point(ImVec2(CROSS_CENTER.x + SYMBOL_CROSS_RADIUS, CROSS_CENTER.y - SYMBOL_CROSS_RADIUS), CROSS_CENTER); - draw_list->AddLine(LINE_A, LINE_B, SYMBOL_COLOR, SYMBOL_STROKE); - draw_list->AddLine(LINE_C, LINE_D, SYMBOL_COLOR, SYMBOL_STROKE); - } - }; - auto APP_INDEX = get_app_index(gui, app_path); const auto widget_scal_size = apply_zoom_size(ImVec2(70.0f * SCALE.x, 70.f * SCALE.y)); @@ -1405,9 +1251,7 @@ void draw_live_area_screen(GuiState &gui, EmuEnvState &emuenv) { if (manual_exist) buttons.emplace_back("Manual", MANUAL, [&]() { open_manual(gui, emuenv, app_path); }); buttons.emplace_back("Refresh", REFRESH, [&]() { refresh_app(gui, emuenv, app_path); }); - if (app_path.find("PCS") != std::string::npos) { - buttons.emplace_back("Cloud", CLOUD, [&]() { v3kn::open_online_storage(gui, emuenv, APP_INDEX->title_id); }); - } + const auto BUTTONS_COUNT = static_cast(buttons.size()); const float spacing = 42.0f * SCALE.x; const ImVec2 BUTTONS_POS = ImVec2( @@ -1429,12 +1273,8 @@ void draw_live_area_screen(GuiState &gui, EmuEnvState &emuenv) { if (TYPE == MANUAL) WIDGET_COLOR = IM_COL32(202, 0, 106, 255); - if (TYPE == CLOUD) - draw_cloud_icon(DRAW_LIST, WIDGET_POS_MIN, widget_scal_size); - else { - DRAW_LIST->AddRectFilled(WIDGET_POS_MIN, ImVec2(WIDGET_POS_MIN.x + widget_scal_size.x, WIDGET_POS_MIN.y + widget_scal_size.y), WIDGET_COLOR, 12.0f * SCALE.x, ImDrawFlags_RoundCornersAll); - DRAW_LIST->AddText(gui.vita_font[emuenv.current_font_level], widget_font_size, WIDGET_STR_POS, IM_COL32(255, 255, 255, 255), WIDGET_STR); - } + DRAW_LIST->AddRectFilled(WIDGET_POS_MIN, ImVec2(WIDGET_POS_MIN.x + widget_scal_size.x, WIDGET_POS_MIN.y + widget_scal_size.y), WIDGET_COLOR, 12.0f * SCALE.x, ImDrawFlags_RoundCornersAll); + DRAW_LIST->AddText(gui.vita_font[emuenv.current_font_level], widget_font_size, WIDGET_STR_POS, IM_COL32(255, 255, 255, 255), WIDGET_STR); ImGui::SetCursorPos(button_pos_scal); if (!is_current_animated) { if (ImGui::Selectable(("##" + TITLE).c_str(), gui.is_nav_button && (live_area_type_selected == TYPE), ImGuiSelectableFlags_None, widget_scal_size)) diff --git a/vita3k/gui/src/main_menubar.cpp b/vita3k/gui/src/main_menubar.cpp index 067bb0a17..9d3872b7e 100644 --- a/vita3k/gui/src/main_menubar.cpp +++ b/vita3k/gui/src/main_menubar.cpp @@ -79,7 +79,7 @@ static void draw_emulation_menu(GuiState &gui, EmuEnvState &emuenv) { } ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.5f, 0.5f)); if (ImGui::Selectable(app.title.c_str(), false, ImGuiSelectableFlags_SpanAllColumns, ImVec2(0, ICON_SIZE.y))) - pre_load_app(gui, emuenv, emuenv.cfg.show_live_area_screen, app.path); + pre_load_app(gui, emuenv, emuenv.cfg.show_live_area_screen, app.title_id); ImGui::PopStyleVar(); ImGui::PopID(); ImGui::PopStyleColor(); @@ -135,7 +135,6 @@ static void draw_config_menu(GuiState &gui, EmuEnvState &emuenv) { init_config(gui, emuenv, emuenv.io.app_path); if (ImGui::MenuItem(lang["user_management"].c_str(), nullptr, &gui.vita_area.user_management, (!gui.vita_area.user_management && emuenv.io.title_id.empty()))) init_user_management(gui, emuenv); - ImGui::MenuItem("Vita3K Network", nullptr, &gui.configuration_menu.v3kn_dialog); ImGui::EndMenu(); } } diff --git a/vita3k/gui/src/online_storage.cpp b/vita3k/gui/src/online_storage.cpp deleted file mode 100644 index 7d0bfe5af..000000000 --- a/vita3k/gui/src/online_storage.cpp +++ /dev/null @@ -1,366 +0,0 @@ -// 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 -#include -#include - -#include -#include - -#include -#include - -namespace gui { - -static std::string get_remaining_str(LangState &lang, const uint64_t remaining) { - if (remaining > 60) - return fmt::format(fmt::runtime(lang.online_storage["minutes_left"]), remaining / 60); - else - return fmt::format(fmt::runtime(lang.online_storage["seconds_left"]), remaining); -} - -void draw_online_storage(GuiState &gui, EmuEnvState &emuenv) { - 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 auto RES_SCALE = ImVec2(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 auto INDICATOR_SIZE = ImVec2(VIEWPORT_SIZE.x, 32.f * SCALE.y); - const ImVec2 WINDOW_SIZE(VIEWPORT_SIZE.x, VIEWPORT_SIZE.y - INDICATOR_SIZE.y); - const ImVec2 WINDOW_POS(VIEWPORT_POS.x, VIEWPORT_POS.y + INDICATOR_SIZE.y); - - const auto &app_path = emuenv.cfg.show_live_area_screen && (gui.live_area_app_current_open >= 0) ? gui.live_area_current_open_apps_list[gui.live_area_app_current_open] : emuenv.app_path; - const auto APP_INDEX = get_app_index(gui, app_path); - const auto &app_titleid = APP_INDEX->title_id; - const auto &app_title = APP_INDEX->title; - const auto &app_icon = gui.app_selector.user_apps_icon[app_path]; - - auto &storage_state = emuenv.v3kn.storage_state; - auto &lang = gui.lang.online_storage; - auto &common_lang = emuenv.common_dialog.lang.common; - const auto is_12_hour_format = emuenv.cfg.sys_time_format == SCE_SYSTEM_PARAM_TIME_FORMAT_12HOUR; - - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.f); - ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.f); - ImGui::SetNextWindowPos(WINDOW_POS, ImGuiCond_Always); - ImGui::SetNextWindowSize(WINDOW_SIZE, ImGuiCond_Always); - ImGui::Begin("##cloud_save", &gui.vita_area.online_storage, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings); - - const auto &draw_list = ImGui::GetWindowDrawList(); - draw_list->AddRectFilled(ImVec2(VIEWPORT_POS.x, VIEWPORT_POS.y + INDICATOR_SIZE.y), ImVec2(VIEWPORT_POS.x + VIEWPORT_SIZE.x, VIEWPORT_POS.y + VIEWPORT_SIZE.y + INDICATOR_SIZE.y), IM_COL32(0, 55, 145, 255)); - - const ImVec2 CLOSE_BUTTON_SIZE(46.f * SCALE.x, 46.f * SCALE.y); - ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 5.f * SCALE.x); - - ImGui::SetCursorPos(ImVec2(10.f * SCALE.x, 10.f * SCALE.y)); - if (ImGui::Button("X", CLOSE_BUTTON_SIZE)) { - gui.vita_area.online_storage = false; - if (gui.live_area_app_current_open >= 0) - gui.vita_area.live_area_screen = true; - else - gui.vita_area.home_screen = true; - } - ImGui::PopStyleVar(); - - ImGui::SetCursorPosX((WINDOW_SIZE.x - CLOSE_BUTTON_SIZE.x) / 2.f); - ImGui::SetWindowFontScale(1.85f * RES_SCALE.y); - const std::string window_title = lang["online_storage"]; - const auto window_title_width = ImGui::CalcTextSize(window_title.c_str()).x; - - ImGui::SetCursorPos(ImVec2((WINDOW_SIZE.x - window_title_width) / 2.f, ((64.f * SCALE.y) - ImGui::GetFontSize()) / 2.f)); - ImGui::Text("%s", window_title.c_str()); - const auto SEPARATOR_CLOUD_POS_Y = 64.f * SCALE.y; - const auto LOCATION_SECTION_SIZE_Y = 92.f * SCALE.y; - ImGui::SetCursorPos(ImVec2(0.f, SEPARATOR_CLOUD_POS_Y)); - ImGui::Separator(); - ImGui::SetCursorPos(ImVec2(54.f * SCALE.x, SEPARATOR_CLOUD_POS_Y + ((LOCATION_SECTION_SIZE_Y - ImGui::GetFontSize()) / 2.f))); - - ImGui::Text("Cloud"); - - const auto has_cloud_save = storage_state.savedata_info.contains(SAVE_DATA_TYPE_CLOUD); - const auto has_local_save = storage_state.savedata_info.contains(SAVE_DATA_TYPE_LOCAL); - - const auto begin_text_pos_x = 182.f * SCALE.x; - - ImGui::SetWindowFontScale(1.68f * RES_SCALE.y); - const auto app_title_height_size = ImGui::CalcTextSize(app_title.c_str()).y; - if (has_cloud_save) { - ImGui::SetCursorPos(ImVec2(begin_text_pos_x, SEPARATOR_CLOUD_POS_Y + (LOCATION_SECTION_SIZE_Y / 2.f) - app_title_height_size)); - ImGui::Text("%s", app_title.c_str()); - const auto &cloud_info = storage_state.savedata_info[SAVE_DATA_TYPE_CLOUD]; - ImGui::SetCursorPos(ImVec2(begin_text_pos_x, SEPARATOR_CLOUD_POS_Y + (LOCATION_SECTION_SIZE_Y / 2.f) + (10.f * SCALE.y))); - tm updated_at_tm = {}; - SAFE_LOCALTIME(&cloud_info.last_updated, &updated_at_tm); - auto UPDATED_AT = get_date_time(gui, emuenv, updated_at_tm); - ImGui::SetWindowFontScale(1.12f * RES_SCALE.y); - ImGui::TextColored(GUI_COLOR_TEXT, "%s", fmt::format("{} {} {} {}", UPDATED_AT[DateTime::DATE_MINI], UPDATED_AT[DateTime::CLOCK], is_12_hour_format ? UPDATED_AT[DateTime::DAY_MOMENT] : "", get_unit_size(cloud_info.total_size)).c_str()); - } else { - ImGui::SetCursorPos(ImVec2(begin_text_pos_x, SEPARATOR_CLOUD_POS_Y + ((LOCATION_SECTION_SIZE_Y - ImGui::GetFontSize()) / 2.f))); - ImGui::Text("-"); - } - - const auto SEPARATOR_BUTTON_POS_Y = SEPARATOR_CLOUD_POS_Y + LOCATION_SECTION_SIZE_Y; - - ImGui::SetCursorPos(ImVec2(0.f, SEPARATOR_BUTTON_POS_Y)); - ImGui::Separator(); - - const auto BUTTONS_SECTION_SIZE_Y = 184.f * SCALE.y; - const auto BUTTONS_SEPARATION = 40.f * SCALE.x; - - ImGui::SetWindowFontScale(1.06f * RES_SCALE.y); - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1, 1, 1, 0.08f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1, 1, 1, 0.12f)); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1, 1, 1, 0.18f)); - ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 15.f * SCALE.x); - const ImVec2 CLOUD_BUTTON_SIZE(284.f * SCALE.x, 116.f * SCALE.y); - const auto BUTTONS_POS_Y = SEPARATOR_BUTTON_POS_Y + ((BUTTONS_SECTION_SIZE_Y - CLOUD_BUTTON_SIZE.y) / 2.f); - ImGui::SetCursorPos(ImVec2((WINDOW_SIZE.x / 2.f) - CLOUD_BUTTON_SIZE.x - (BUTTONS_SEPARATION / 2.f), BUTTONS_POS_Y)); - ImGui::BeginDisabled(!has_local_save); - if (ImGui::Button(lang["upload"].c_str(), CLOUD_BUTTON_SIZE)) { - if (has_cloud_save) { - storage_state.online_storage_state = ONLINE_STORAGE_UPLOAD; - } else { - storage_state.online_storage_state = ONLINE_STORAGE_COPYING; - v3kn::upload_savedata(gui, emuenv, app_titleid); - } - } - ImGui::EndDisabled(); - ImGui::SetCursorPos(ImVec2((WINDOW_SIZE.x / 2.f) + (BUTTONS_SEPARATION / 2.f), BUTTONS_POS_Y)); - ImGui::BeginDisabled(!has_cloud_save); - if (ImGui::Button(lang["download"].c_str(), CLOUD_BUTTON_SIZE)) { - if (has_local_save) { - storage_state.online_storage_state = ONLINE_STORAGE_DOWNLOAD; - } else { - storage_state.online_storage_state = ONLINE_STORAGE_COPYING; - v3kn::download_savedata(gui, emuenv, app_titleid); - } - } - ImGui::EndDisabled(); - ImGui::PopStyleVar(); - ImGui::PopStyleColor(3); - - static const auto get_timestamp_ms = []() { - return duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); - }; - - if (storage_state.online_storage_state != ONLINE_STORAGE_SELECT) { - const ImVec2 OVERLAY_SIZE(VIEWPORT_SIZE.x, VIEWPORT_SIZE.y - INDICATOR_SIZE.y); - const ImVec2 OVERLAY_POS(VIEWPORT_POS.x, VIEWPORT_POS.y + INDICATOR_SIZE.y); - ImGui::SetNextWindowPos(OVERLAY_POS, ImGuiCond_Always); - ImGui::SetNextWindowSize(OVERLAY_SIZE, ImGuiCond_Always); - ImGui::Begin("##cloud_save_overlay", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings); - const auto &window_draw_list = ImGui::GetWindowDrawList(); - - switch (storage_state.online_storage_state) { - case ONLINE_STORAGE_UPLOAD: - case ONLINE_STORAGE_DOWNLOAD: { - const ImVec2 POPUP_SIZE(868.f * SCALE.x, 478.f * SCALE.y); - const ImVec2 POPUP_POS(VIEWPORT_POS.x + ((VIEWPORT_SIZE.x - POPUP_SIZE.x) / 2.f), VIEWPORT_POS.y + ((VIEWPORT_SIZE.y - POPUP_SIZE.y) / 2.f)); - ImGui::SetNextWindowPos(POPUP_POS, ImGuiCond_Always); - ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 15.f * SCALE.x); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.f, 0.f)); - ImGui::BeginChild("##confirm_popup", POPUP_SIZE, ImGuiChildFlags_Borders, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.f, 0.f)); - - const ImVec2 ICON_SIZE(76.f * SCALE.x, 76.f * SCALE.y); - const ImVec2 ICON_POS(50.f * SCALE.x, 26.f * SCALE.y); - if (app_icon) { - const ImVec2 ICON_POS_MIN(POPUP_POS.x + ICON_POS.x, POPUP_POS.y + ICON_POS.y); - const ImVec2 ICON_POS_MAX(ICON_POS_MIN.x + ICON_SIZE.x, ICON_POS_MIN.y + ICON_SIZE.y); - window_draw_list->AddImageRounded(app_icon, ICON_POS_MIN, ICON_POS_MAX, ImVec2(0, 0), ImVec2(1, 1), IM_COL32_WHITE, ICON_SIZE.x); - } - ImGui::SetWindowFontScale(1.52f * RES_SCALE.y); - ImGui::SetCursorPos(ImVec2(ICON_POS.x + ICON_SIZE.x + (22.f * SCALE.x), ICON_POS.y + ((ICON_SIZE.y - app_title_height_size) / 2.f))); - ImGui::Text("%s", app_title.c_str()); - - const bool is_upload = (storage_state.online_storage_state == ONLINE_STORAGE_UPLOAD); - - const auto &src_save = is_upload ? storage_state.savedata_info[SAVE_DATA_TYPE_LOCAL] : storage_state.savedata_info[SAVE_DATA_TYPE_CLOUD]; - const auto &dst_save = is_upload ? storage_state.savedata_info[SAVE_DATA_TYPE_CLOUD] : storage_state.savedata_info[SAVE_DATA_TYPE_LOCAL]; - - const auto ¤t_ps_system = emuenv.cfg.pstv_mode ? lang["ps_tv_system"] : lang["ps_vita_system"]; - const auto &src_title = is_upload ? current_ps_system : lang["online_storage"]; - const auto &dst_title = is_upload ? lang["online_storage"] : current_ps_system; - - const auto draw_save_info = [&](const ImVec2 &begin_pos, const std::string &title, const SaveDataInfo &save_info) { - ImGui::SetCursorPos(begin_pos); - ImGui::SetWindowFontScale(1.1f * RES_SCALE.y); - ImGui::Text("%s", title.c_str()); - ImGui::SetCursorPos(ImVec2(begin_pos.x, ImGui::GetCursorPosY() + (15.f * SCALE.y))); - tm updated_at_tm = {}; - SAFE_LOCALTIME(&save_info.last_updated, &updated_at_tm); - auto UPDATED_AT = get_date_time(gui, emuenv, updated_at_tm); - ImGui::TextColored(GUI_COLOR_TEXT, "%s", fmt::format("{} {} {}", UPDATED_AT[DateTime::DATE_MINI], UPDATED_AT[DateTime::CLOCK], is_12_hour_format ? UPDATED_AT[DateTime::DAY_MOMENT] : "").c_str()); - ImGui::SetCursorPos(ImVec2(begin_pos.x, ImGui::GetCursorPosY() + (5.f * SCALE.y))); - ImGui::TextColored(GUI_COLOR_TEXT, "%s", get_unit_size(save_info.total_size).c_str()); - }; - - const float INFO_START_Y = ICON_POS.y + ICON_SIZE.y + (22.f * SCALE.y); - - // Draw source save info - const auto SRC_POS_X = 50.f * SCALE.x; - draw_save_info(ImVec2(SRC_POS_X, INFO_START_Y), src_title, src_save); - - // Draw arrow - const auto ARROW_COLOR = IM_COL32(161, 162, 165, 255); - const auto ARROW_BAR_SIZE = ImVec2(42.f * SCALE.x, 8.f * SCALE.y); - const auto ARROW_HEAD_SIZE = ImVec2(12.f * SCALE.x, 18.f * SCALE.y); - const ImVec2 ARROW_BAR_POS_MIN(POPUP_POS.x + (POPUP_SIZE.x / 2.f) - (5.f * SCALE.x) - ARROW_BAR_SIZE.x - ARROW_HEAD_SIZE.x, POPUP_POS.y + (132.f * SCALE.y)); - const ImVec2 ARROW_BAR_POS_MAX(ARROW_BAR_POS_MIN.x + ARROW_BAR_SIZE.x, ARROW_BAR_POS_MIN.y + ARROW_BAR_SIZE.y); - window_draw_list->AddRectFilled(ARROW_BAR_POS_MIN, ARROW_BAR_POS_MAX, ARROW_COLOR); - const ImVec2 ARROW_HEAD_POS1(ARROW_BAR_POS_MAX.x, ARROW_BAR_POS_MIN.y - ((ARROW_HEAD_SIZE.y - ARROW_BAR_SIZE.y) / 2.f)); - const ImVec2 ARROW_HEAD_POS2(ARROW_BAR_POS_MAX.x + ARROW_HEAD_SIZE.x, ARROW_BAR_POS_MIN.y + (ARROW_BAR_SIZE.y / 2.f)); - const ImVec2 ARROW_HEAD_POS3(ARROW_BAR_POS_MAX.x, ARROW_BAR_POS_MAX.y + ((ARROW_HEAD_SIZE.y - ARROW_BAR_SIZE.y) / 2.f)); - window_draw_list->AddTriangleFilled(ARROW_HEAD_POS1, ARROW_HEAD_POS2, ARROW_HEAD_POS3, ARROW_COLOR); - - // Draw destination save info - const auto DST_POS_X = (POPUP_SIZE.x / 2.f) + 42.f * SCALE.x; - draw_save_info(ImVec2(DST_POS_X, INFO_START_Y), dst_title, dst_save); - - ImGui::SetCursorPosY(215.f * SCALE.y); - ImGui::Separator(); - ImGui::SetWindowFontScale(1.38f * RES_SCALE.y); - ImGui::SetCursorPos(ImVec2(60.f * SCALE.x, ImGui::GetCursorPosY() + (14.f * SCALE.y))); - ImGui::Text("%s", lang["overwrite_saved_data"].c_str()); - const auto CONFIRM_BUTTON_SIZE = ImVec2(310.f * SCALE.x, 46.f * SCALE.y); - ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 15.f * SCALE.x); - const auto BUTTON_SPACING = 40.f * SCALE.x; - ImGui::SetCursorPos(ImVec2((POPUP_SIZE.x / 2.f) - CONFIRM_BUTTON_SIZE.x - (BUTTON_SPACING / 2.f), POPUP_SIZE.y - CONFIRM_BUTTON_SIZE.y - (24.f * SCALE.y))); - if (ImGui::Button(common_lang["no"].c_str(), CONFIRM_BUTTON_SIZE)) { - storage_state.online_storage_state = ONLINE_STORAGE_SELECT; - } - ImGui::SetCursorPos(ImVec2((POPUP_SIZE.x / 2.f) + (BUTTON_SPACING / 2.f), POPUP_SIZE.y - CONFIRM_BUTTON_SIZE.y - (24.f * SCALE.y))); - if (ImGui::Button(common_lang["yes"].c_str(), CONFIRM_BUTTON_SIZE)) { - if (storage_state.online_storage_state == ONLINE_STORAGE_UPLOAD) - v3kn::upload_savedata(gui, emuenv, app_titleid); - else - v3kn::download_savedata(gui, emuenv, app_titleid); - storage_state.online_storage_state = ONLINE_STORAGE_COPYING; - } - ImGui::PopStyleVar(2); - ImGui::EndChild(); - ImGui::PopStyleVar(2); - break; - } - case ONLINE_STORAGE_COPYING: { - const ImVec2 ICON_SIZE(64.f * SCALE.x, 64.f * SCALE.y); - const ImVec2 POPUP_SIZE(760 * SCALE.x, 436.f * SCALE.y); - ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 15.f * SCALE.x); - const auto PUPUP_POS = ImVec2(VIEWPORT_POS.x + ((VIEWPORT_SIZE.x - POPUP_SIZE.x) / 2.f), VIEWPORT_POS.y + ((VIEWPORT_SIZE.y - POPUP_SIZE.y) / 2.f)); - ImGui::SetNextWindowPos(PUPUP_POS, ImGuiCond_Always); - ImGui::BeginChild("##copying_popup", POPUP_SIZE, ImGuiChildFlags_Borders, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings); - const auto ©_str = lang["copying"]; - ImGui::SetCursorPos(ImVec2((POPUP_SIZE.x - ImGui::CalcTextSize(copy_str.c_str()).x) / 2.f, 82.f * SCALE.y)); - ImGui::Text("%s", copy_str.c_str()); - const auto ICON_POS = ImVec2(100.f * SCALE.x, 132.f * SCALE.y); - if (app_icon) { - const ImVec2 ICON_POS_MIN(PUPUP_POS.x + ICON_POS.x, PUPUP_POS.y + ICON_POS.y); - const ImVec2 ICON_POS_MAX(ICON_POS_MIN.x + ICON_SIZE.x, ICON_POS_MIN.y + ICON_SIZE.y); - window_draw_list->AddImageRounded(app_icon, ICON_POS_MIN, ICON_POS_MAX, ImVec2(0, 0), ImVec2(1, 1), IM_COL32_WHITE, ICON_SIZE.x); - } - ImGui::SetCursorPos(ImVec2(ICON_POS.x + ICON_SIZE.x + (12.f * SCALE.x), ICON_POS.y + ((ICON_SIZE.y - ImGui::GetFontSize()) / 2.f))); - ImGui::Text("%s", app_title.c_str()); - const auto remaining_str = get_remaining_str(gui.lang, storage_state.remaining.load()); - ImGui::SetCursorPos(ImVec2(POPUP_SIZE.x - (100 * SCALE.x) - (ImGui::CalcTextSize(remaining_str.c_str()).x), 214.f * SCALE.y)); - ImGui::Text("%s", remaining_str.c_str()); - const float PROGRESS_BAR_WIDTH = 560.f * SCALE.x; - ImGui::SetCursorPos(ImVec2((ImGui::GetWindowWidth() / 2) - (PROGRESS_BAR_WIDTH / 2.f), 236.f * SCALE.y)); - ImGui::PushStyleColor(ImGuiCol_PlotHistogram, GUI_PROGRESS_BAR); - ImGui::ProgressBar(storage_state.progress.load(), ImVec2(PROGRESS_BAR_WIDTH, 14.f * SCALE.y), ""); - ImGui::SetCursorPosY(ImGui::GetCursorPosY() + (16.f * SCALE.y)); - TextColoredCentered(GUI_COLOR_TEXT, std::to_string(static_cast(storage_state.progress.load() * 100.f)).append("%").c_str()); - ImGui::PopStyleColor(); - ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 15.f * SCALE.x); - const ImVec2 CANCEL_BUTTON_SIZE(320.f * SCALE.x, 46.f * SCALE.y); - ImGui::SetCursorPos(ImVec2((POPUP_SIZE.x - CANCEL_BUTTON_SIZE.x) / 2.f, POPUP_SIZE.y - CANCEL_BUTTON_SIZE.y - (24.f * SCALE.y))); - ImGui::BeginDisabled(storage_state.progress.load() >= 1.f); - if (ImGui::Button(emuenv.common_dialog.lang.common["cancel"].c_str(), CANCEL_BUTTON_SIZE)) { - std::lock_guard lock(storage_state.progress_state.mutex); - storage_state.progress_state.canceled = true; - storage_state.online_storage_state = ONLINE_STORAGE_SELECT; - } - ImGui::EndDisabled(); - - // Hold the dialog at 100% for a short moment to ensure the user perceives the completion state - if (storage_state.progress.load() >= 1.f) { - const auto now = get_timestamp_ms(); - - if (storage_state.progress_done_timestamp == 0) - storage_state.progress_done_timestamp = now; - - if (now - storage_state.progress_done_timestamp >= 900) { - gui.vita_area.please_wait = true; - storage_state.please_wait_timestamp = now; - storage_state.online_storage_state = ONLINE_STORAGE_SELECT; - } - } - - ImGui::PopStyleVar(); - ImGui::EndChild(); - ImGui::PopStyleVar(); - break; - } - default: - break; - } - ImGui::End(); - } - - // Hide please wait after a short delay to improve UX - if (gui.vita_area.please_wait && !storage_state.please_wait_done.load()) { - const auto now = get_timestamp_ms(); - - if (now - storage_state.please_wait_timestamp >= 900) { - storage_state.please_wait_done.store(true); - { - std::lock_guard lck(storage_state.mutex_progress); - storage_state.cv_progress.notify_all(); - } - } - } - - const auto END_SEPARATOR_POS_Y = SEPARATOR_BUTTON_POS_Y + BUTTONS_SECTION_SIZE_Y; - ImGui::SetCursorPos(ImVec2(0.f, END_SEPARATOR_POS_Y)); - ImGui::Separator(); - - ImGui::SetWindowFontScale(1.85f * RES_SCALE.y); - ImGui::SetCursorPos(ImVec2(54.f * SCALE.x, END_SEPARATOR_POS_Y + ((LOCATION_SECTION_SIZE_Y - ImGui::GetFontSize()) / 2.f))); - ImGui::Text("Local"); - - ImGui::SetWindowFontScale(1.68f * RES_SCALE.y); - if (has_local_save) { - const auto &local_info = storage_state.savedata_info[SAVE_DATA_TYPE_LOCAL]; - ImGui::SetCursorPos(ImVec2(begin_text_pos_x, END_SEPARATOR_POS_Y + (LOCATION_SECTION_SIZE_Y / 2.f) - app_title_height_size)); - ImGui::Text("%s", app_title.c_str()); - ImGui::SetCursorPos(ImVec2(begin_text_pos_x, END_SEPARATOR_POS_Y + (LOCATION_SECTION_SIZE_Y / 2.f) + (10.f * SCALE.y))); - tm updated_at_tm = {}; - SAFE_LOCALTIME(&local_info.last_updated, &updated_at_tm); - auto UPDATED_AT = get_date_time(gui, emuenv, updated_at_tm); - ImGui::SetWindowFontScale(1.12f * RES_SCALE.y); - ImGui::TextColored(GUI_COLOR_TEXT, "%s", fmt::format("{} {} {} {}", UPDATED_AT[DateTime::DATE_MINI], UPDATED_AT[DateTime::CLOCK], is_12_hour_format ? UPDATED_AT[DateTime::DAY_MOMENT] : "", get_unit_size(local_info.total_size)).c_str()); - } else { - ImGui::SetCursorPos(ImVec2(begin_text_pos_x, END_SEPARATOR_POS_Y + (LOCATION_SECTION_SIZE_Y - ImGui::GetFontSize()) / 2.f)); - ImGui::Text("-"); - } - - ImGui::End(); - ImGui::PopStyleVar(2); -} - -} // namespace gui diff --git a/vita3k/gui/src/private.h b/vita3k/gui/src/private.h index d8432dc12..72725d8a2 100644 --- a/vita3k/gui/src/private.h +++ b/vita3k/gui/src/private.h @@ -58,7 +58,6 @@ void draw_event_flags_dialog(GuiState &gui, EmuEnvState &emuenv); void draw_allocations_dialog(GuiState &gui, EmuEnvState &emuenv); void draw_disassembly_dialog(GuiState &gui, EmuEnvState &emuenv); void draw_settings_dialog(GuiState &gui, EmuEnvState &emuenv); -void draw_v3kn_dialog(GuiState &gui, EmuEnvState &emuenv); void draw_overlay_dialog(GuiState &gui, EmuEnvState &emuenv); void draw_controls_dialog(GuiState &gui, EmuEnvState &emuenv); void draw_controllers_dialog(GuiState &gui, EmuEnvState &emuenv); @@ -72,7 +71,6 @@ void draw_home_screen(GuiState &gui, EmuEnvState &emuenv); void draw_information_bar(GuiState &gui, EmuEnvState &emuenv); void draw_live_area_screen(GuiState &gui, EmuEnvState &emuenv); void draw_manual(GuiState &gui, EmuEnvState &emuenv); -void draw_online_storage(GuiState &gui, EmuEnvState &emuenv); void draw_settings(GuiState &gui, EmuEnvState &emuenv); void draw_start_screen(GuiState &gui, EmuEnvState &emuenv); void draw_trophy_collection(GuiState &gui, EmuEnvState &emuenv); diff --git a/vita3k/gui/src/user_management.cpp b/vita3k/gui/src/user_management.cpp index d1875763c..a910d3602 100644 --- a/vita3k/gui/src/user_management.cpp +++ b/vita3k/gui/src/user_management.cpp @@ -31,8 +31,6 @@ #include #include -#include - #include #include @@ -40,10 +38,22 @@ namespace gui { +enum AvatarSize { + SMALL = 34, + MEDIUM = 130, + LARGE = 160 +}; + static ImVec2 get_avatar_size(AvatarSize size, const ImVec2 scale = { 1, 1 }) { return ImVec2(static_cast(size) * scale.x, static_cast(size) * scale.y); } +struct AvatarInfo { + ImVec2 pos; + ImVec2 size; +}; + +static std::map> users_avatar_infos; static bool init_avatar(GuiState &gui, EmuEnvState &emuenv, const std::string &user_id, const std::string &avatar_path) { const auto avatar_path_path = avatar_path == "default" ? emuenv.static_assets_path / "data/image/icon.png" : fs_utils::utf8_to_path(avatar_path); @@ -68,11 +78,11 @@ static bool init_avatar(GuiState &gui, EmuEnvState &emuenv, const std::string &u // Calculate avatar size and position based of aspect ratio // Resize for all size of avatar - constexpr std::array sizes = { AvatarSize::SMALL, AvatarSize::MEDIUM, AvatarSize::LARGE, AvatarSize::V3KN }; + constexpr std::array sizes = { SMALL, MEDIUM, LARGE }; for (const auto size : sizes) { const auto avatar_size = get_avatar_size(size); const auto ratio = std::min(avatar_size.x / static_cast(width), avatar_size.y / static_cast(height)); - auto &avatar = gui.users_avatar_infos[user_id][size]; + auto &avatar = users_avatar_infos[user_id][size]; avatar.size = ImVec2(width * ratio, height * ratio); avatar.pos = ImVec2((avatar_size.x / 2.f) - (avatar.size.x / 2.f), (avatar_size.y / 2.f) - (avatar.size.y / 2.f)); } @@ -230,11 +240,6 @@ void init_user(GuiState &gui, EmuEnvState &emuenv, const std::string &user_id) { init_theme(gui, emuenv, gui.users[user_id].theme_id); init_notice_info(gui, emuenv); init_last_time_apps(gui, emuenv); - - // Init V3KN user info asynchronously to avoid blocking the GUI - std::thread([&gui, &emuenv]() { - v3kn::init_v3kn_user_info(gui, emuenv); - }).detach(); } void open_user(GuiState &gui, EmuEnvState &emuenv) { @@ -311,7 +316,7 @@ static void create_and_save_user(GuiState &gui, EmuEnvState &emuenv) { // Move avatar texture + infos gui.users_avatar[user_id_selected] = std::move(gui.users_avatar["temp"]); - gui.users_avatar_infos[user_id_selected] = gui.users_avatar_infos["temp"]; + users_avatar_infos[user_id_selected] = users_avatar_infos["temp"]; current_user_id_selected = string_utils::stoi_def(user_id_selected, 0, "selected user id"); save_user(gui, emuenv, user_id_selected); @@ -512,9 +517,9 @@ void draw_user_management(GuiState &gui, EmuEnvState &emuenv) { const auto user_path{ emuenv.pref_path / "ux0/user" }; - const auto SMALL_AVATAR_SIZE = get_avatar_size(AvatarSize::SMALL, SCALE); - const auto MED_AVATAR_SIZE = get_avatar_size(AvatarSize::MEDIUM, SCALE); - const auto LARGE_AVATAR_SIZE = get_avatar_size(AvatarSize::LARGE, SCALE); + const auto SMALL_AVATAR_SIZE = get_avatar_size(SMALL, SCALE); + const auto MED_AVATAR_SIZE = get_avatar_size(MEDIUM, SCALE); + const auto LARGE_AVATAR_SIZE = get_avatar_size(LARGE, SCALE); const auto USER_NAME_BG_SIZE = ImVec2(MED_AVATAR_SIZE.x, 60.f * SCALE.y); ImGui::SetWindowFontScale(1.4f * RES_SCALE.x); @@ -544,7 +549,7 @@ void draw_user_management(GuiState &gui, EmuEnvState &emuenv) { const auto BG_AVATAR_SIZE = get_avatar_size(size, SCALE); const auto &DRAW_LIST = ImGui::GetWindowDrawList(); DRAW_LIST->AddRectFilled(SCREEN_POS, ImVec2(SCREEN_POS.x + BG_AVATAR_SIZE.x, SCREEN_POS.y + BG_AVATAR_SIZE.y), IM_COL32(19.f, 69.f, 167.f, 150.f)); - if (size == AvatarSize::MEDIUM) { + if (size == MEDIUM) { ImGui::SetCursorPos(ImVec2(origin_pos.x, origin_pos.y + MED_AVATAR_SIZE.y)); DRAW_LIST->AddRectFilled(SCREEN_POS, ImVec2(SCREEN_POS.x + BG_AVATAR_SIZE.x, SCREEN_POS.y + USER_NAME_BG_SIZE.y), IM_COL32(19.f, 69.f, 167.f, 255.f)); } @@ -554,7 +559,7 @@ void draw_user_management(GuiState &gui, EmuEnvState &emuenv) { const auto draw_avatar = [&](const std::string &user_id, const AvatarSize size, const ImVec2 origin_pos) { draw_user_bg(size, origin_pos); if (gui.users_avatar.contains(user_id)) { - const auto &user_avatar_infos = gui.users_avatar_infos[user_id][size]; + const auto &user_avatar_infos = users_avatar_infos[user_id][size]; ImVec2 AVATAR_POS = ImVec2(origin_pos.x + (user_avatar_infos.pos.x * SCALE.x), origin_pos.y + (user_avatar_infos.pos.y * SCALE.y)); ImVec2 AVATAR_SIZE = ImVec2(user_avatar_infos.size.x * SCALE.x, user_avatar_infos.size.y * SCALE.y); ImGui::SetCursorPos(AVATAR_POS); @@ -632,7 +637,7 @@ void draw_user_management(GuiState &gui, EmuEnvState &emuenv) { // Users list title = lang["select_user"]; ImGui::SetWindowFontScale(1.6f); - draw_user_bg(AvatarSize::MEDIUM, CREATE_USER_POS); + draw_user_bg(MEDIUM, CREATE_USER_POS); ImGui::SetCursorPos(CREATE_USER_POS); if (ImGui::InvisibleButton("+", SELECTABLE_USER_SIZE)) { if (menu_selected == CREATE) @@ -680,7 +685,7 @@ void draw_user_management(GuiState &gui, EmuEnvState &emuenv) { if (is_edit_user_selected) draw_frame(EDIT_USER_POS_SEL, EDIT_USER_SIZE); trigger_scroll_to_item(is_current_user_id_selected); - draw_avatar(user.first, AvatarSize::MEDIUM, USER_POS); + draw_avatar(user.first, MEDIUM, USER_POS); ImGui::SetCursorPos(USER_POS); ImGui::PushStyleColor(ImGuiCol_Text, GUI_COLOR_TEXT_TITLE); if (ImGui::InvisibleButton("##avatar", SELECTABLE_USER_SIZE)) { @@ -707,7 +712,7 @@ void draw_user_management(GuiState &gui, EmuEnvState &emuenv) { ImGui::SetCursorPos(ImVec2(USER_POS.x + SPACE_AVATAR, USER_POS.y)); } if (!gui.users.empty()) { - draw_user_bg(AvatarSize::MEDIUM, DELETE_USER_POS); + draw_user_bg(MEDIUM, DELETE_USER_POS); ImGui::SetCursorPos(DELETE_USER_POS); ImGui::SetWindowFontScale(1.6f); if (ImGui::InvisibleButton("-", SELECTABLE_USER_SIZE)) { @@ -740,7 +745,7 @@ void draw_user_management(GuiState &gui, EmuEnvState &emuenv) { temp.avatar = "default"; init_avatar(gui, emuenv, "temp", "default"); } - draw_avatar("temp", AvatarSize::LARGE, AVATAR_POS); + draw_avatar("temp", LARGE, AVATAR_POS); const ImVec2 CHANGE_AVATAR_BTN_SIZE(LARGE_AVATAR_SIZE.x, 36.f * SCALE.y); ImGui::SetCursorPos(ImVec2(AVATAR_POS.x + (LARGE_AVATAR_SIZE.x / 2.f) - (CHANGE_AVATAR_BTN_SIZE.x / 2.f), AVATAR_POS.y + LARGE_AVATAR_SIZE.y + (5.f * SCALE.y))); if (ImGui::Button(lang["choose_avatar"].c_str(), CHANGE_AVATAR_BTN_SIZE)) { @@ -783,7 +788,7 @@ void draw_user_management(GuiState &gui, EmuEnvState &emuenv) { ImGui::SetCursorPosY(44.f * SCALE.y); TextColoredCentered(GUI_COLOR_TEXT, lang["user_created"].c_str()); const auto AVATAR_CONFIRM_POS = ImVec2((SIZE_USER.x / 2) - (MED_AVATAR_SIZE.x / 2.f), 96.f * SCALE.y); - draw_avatar(user_id_selected, AvatarSize::MEDIUM, AVATAR_CONFIRM_POS); + draw_avatar(user_id_selected, MEDIUM, AVATAR_CONFIRM_POS); ImGui::SetWindowFontScale(0.7f); ImGui::SetCursorPos(ImVec2(AVATAR_CONFIRM_POS.x + USER_NAME_PADDING, AVATAR_CONFIRM_POS.y + MED_AVATAR_SIZE.y + (5.f * SCALE.y))); ImGui::PushTextWrapPos(AVATAR_CONFIRM_POS.x + MED_AVATAR_SIZE.x - USER_NAME_PADDING); @@ -817,7 +822,7 @@ void draw_user_management(GuiState &gui, EmuEnvState &emuenv) { ImGui::Separator(); for (const auto &user : gui.users) { users_list_available.push_back(string_utils::stoi_def(user.first, 0, "gui user id")); - draw_avatar(user.first, AvatarSize::SMALL, ImVec2(ImGui::GetCursorPosX(), ImGui::GetCursorPosY() + (SELECT_SIZE.y / 2.f) - (SMALL_AVATAR_SIZE.y / 2.f))); + draw_avatar(user.first, SMALL, ImVec2(ImGui::GetCursorPosX(), ImGui::GetCursorPosY() + (SELECT_SIZE.y / 2.f) - (SMALL_AVATAR_SIZE.y / 2.f))); ImGui::NextColumn(); ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.0f, 0.5f)); if (ImGui::Selectable(user.second.name.c_str(), gui.is_nav_button && (current_user_id_selected == string_utils::stoi_def(user.first, 0, "gui user id")), ImGuiSelectableFlags_SpanAllColumns, SELECT_SIZE)) @@ -908,7 +913,7 @@ void draw_user_management(GuiState &gui, EmuEnvState &emuenv) { config::serialize_config(emuenv.cfg, emuenv.cfg.config_path); } if (!gui.users.empty() && gui.users.contains(emuenv.cfg.user_id)) { - draw_avatar(emuenv.cfg.user_id, AvatarSize::SMALL, ImVec2(WINDOW_SIZE.x - (220.f * SCALE.x), WINDOW_SIZE.y - (POS_SEPARATOR / 2.f) - (SMALL_AVATAR_SIZE.y / 2.f))); + draw_avatar(emuenv.cfg.user_id, SMALL, ImVec2(WINDOW_SIZE.x - (220.f * SCALE.x), WINDOW_SIZE.y - (POS_SEPARATOR / 2.f) - (SMALL_AVATAR_SIZE.y / 2.f))); ImGui::SetCursorPos(ImVec2(WINDOW_SIZE.x - 180.f * SCALE.x, WINDOW_SIZE.y - (POS_SEPARATOR / 2.f) - (ImGui::CalcTextSize(gui.users[emuenv.cfg.user_id].name.c_str()).y / 2.f))); ImGui::TextColored(GUI_COLOR_TEXT, "%s", gui.users[emuenv.cfg.user_id].name.c_str()); } diff --git a/vita3k/gui/src/v3kn_dialog.cpp b/vita3k/gui/src/v3kn_dialog.cpp deleted file mode 100644 index b718e8670..000000000 --- a/vita3k/gui/src/v3kn_dialog.cpp +++ /dev/null @@ -1,1295 +0,0 @@ -// 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 - -#include - -#include -#include - -#include - -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include - -#define STB_IMAGE_RESIZE_IMPLEMENTATION -#include - -#undef ERROR - -struct PanelUploadState { - std::string *result_message; - bool *upload_success; - bool *is_uploading; -}; - -void upload_v3kn_panel(GuiState *gui, EmuEnvState *emuenv, UserInfo *user_info, std::vector panel_source, - int source_w, int source_h, float crop_scroll_x, float crop_scroll_y, float crop_scale, float crop_zoom, PanelUploadState state) { - constexpr int panel_width = 400; - constexpr int panel_height = 80; - - if (panel_source.empty() || source_w <= 0 || source_h <= 0) { - *state.result_message = "Failed to load image for processing."; - *state.is_uploading = false; - return; - } - - constexpr float panel_ratio = static_cast(panel_width) / static_cast(panel_height); - const float max_crop_w = static_cast(source_w) / std::max(crop_zoom, 1.f); - const float max_crop_h = static_cast(source_h) / std::max(crop_zoom, 1.f); - int crop_w = static_cast(std::round(max_crop_w)); - int crop_h = static_cast(std::round(static_cast(crop_w) / panel_ratio)); - if (crop_h > static_cast(std::round(max_crop_h))) { - crop_h = static_cast(std::round(max_crop_h)); - crop_w = static_cast(std::round(static_cast(crop_h) * panel_ratio)); - } - - int crop_x = 0; - int crop_y = 0; - if (crop_w < source_w) { - crop_x = static_cast(std::round(crop_scroll_x / crop_scale)); - crop_x = std::clamp(crop_x, 0, std::max(0, source_w - crop_w)); - } - if (crop_h < source_h) { - crop_y = static_cast(std::round(crop_scroll_y / crop_scale)); - crop_y = std::clamp(crop_y, 0, std::max(0, source_h - crop_h)); - } - - std::vector cropped(crop_w * crop_h * 4); - for (int y = 0; y < crop_h; ++y) { - const auto src_offset = ((crop_y + y) * source_w + crop_x) * 4; - const auto dst_offset = y * crop_w * 4; - std::copy_n(panel_source.data() + src_offset, crop_w * 4, cropped.data() + dst_offset); - } - - std::vector resized(panel_width * panel_height * 4); - stbir_resize_uint8_linear(cropped.data(), crop_w, crop_h, 0, resized.data(), panel_width, panel_height, 0, STBIR_RGBA); - - std::vector png_data; - stbi_write_png_to_func( - [](void *context, void *data, int size) { - auto *vec = static_cast *>(context); - const auto ptr = static_cast(data); - vec->insert(vec->end(), ptr, ptr + size); - }, - &png_data, panel_width, panel_height, 4, resized.data(), panel_width * 4); - - const auto res = v3kn::v3kn_panel_upload(*user_info, png_data); - v3kn::handle_v3kn_status(*emuenv, res); - - if (res.body.starts_with("OK")) { - int w = 0; - int h = 0; - auto *data = stbi_load_from_memory(reinterpret_cast(png_data.data()), static_cast(png_data.size()), &w, &h, nullptr, 4); - if (data) - gui->v3kn_panel = ImGui_Texture(gui->imgui_state.get(), data, w, h); - *state.upload_success = true; - *state.result_message = "Panel uploaded successfully."; - } else { - *state.result_message = v3kn::get_v3kn_error_message(*emuenv, res); - } - *state.is_uploading = false; -} - -enum class V3knPendingAction { - NONE, - CREATE, - LOGIN, - REFRESH_QUOTA, - DELETE_ACCOUNT, - CHANGE_ONLINE_ID, - CHANGE_PASSWORD, - CHANGE_ABOUT_ME, -}; - -struct V3knPendingResult { - V3knPendingAction action = V3knPendingAction::NONE; - net_utils::WebResponse response; - std::string online_id; - std::string password; - std::string token; - std::string message; - uint64_t quota_used = 0; - uint64_t quota_total = 0; - bool has_quota = false; -}; - -namespace gui { - -void draw_v3kn_dialog(GuiState &gui, EmuEnvState &emuenv) { - static std::mutex result_mutex; - static std::optional pending_result; - static std::atomic_bool is_result_ready{ false }; - static std::atomic_bool is_waiting_result{ false }; - - enum class V3knResultState { - NONE, - PENDING_ACK, - CLOSE_PARENT, - }; - - static V3knResultState result_state = V3knResultState::NONE; - - const ImVec2 VIEWPORT_POS(emuenv.logical_viewport_pos.x, emuenv.logical_viewport_pos.y); - const ImVec2 VIEWPORT_SIZE(emuenv.logical_viewport_size.x, emuenv.logical_viewport_size.y); - const auto RES_SCALE = ImVec2(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 float INDICATOR_SIZE(32.f * SCALE.y); - const ImVec2 WINDOW_SIZE(VIEWPORT_SIZE.x / 1.6f, 0.f); - const ImVec2 WINDOW_POS(VIEWPORT_POS.x + (VIEWPORT_SIZE.x - WINDOW_SIZE.x) / 2.f, VIEWPORT_POS.y + (VIEWPORT_SIZE.y - WINDOW_SIZE.y) / 2.f); - - ImGui::SetNextWindowPos(WINDOW_POS, ImGuiCond_Always, ImVec2(0.f, 0.5f)); - ImGui::SetNextWindowSize(WINDOW_SIZE, ImGuiCond_Always); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.f); - ImGui::Begin("##v3kn_dialog", &gui.configuration_menu.v3kn_dialog, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoMove); - - const ImVec2 BUTTON_SIZE(200.f * SCALE.x, 40.f * SCALE.y); - const ImVec2 SMALL_BUTTON_SIZE(100.f * SCALE.x, 40.f * SCALE.y); - - ImGui::SetWindowFontScale(RES_SCALE.y); - const ImVec2 POPUP_SIZE = ImGui::GetWindowSize(); - const ImVec2 POPUP_POS = ImGui::GetWindowPos(); - - const auto title = "V3KN Account Management"; - const auto FONT_SCALE = 0.81f; - const auto title_size = ImGui::CalcTextSize(title); - const auto BLOCK_TITLE_SIZE_HEIGHT = 40.f * SCALE.y; - const ImVec2 title_pos((POPUP_SIZE.x - title_size.x) / 2.f, (BLOCK_TITLE_SIZE_HEIGHT - (title_size.y * FONT_SCALE)) / 2.f); - ImGui::SetCursorPos(title_pos); - ImGui::TextColored(GUI_COLOR_TEXT_TITLE, "%s", title); - ImGui::SetCursorPosY(BLOCK_TITLE_SIZE_HEIGHT); - ImGui::Separator(); - - static std::string result_message; - static char username[16] = ""; - static char password[64] = ""; - static char confirm[64] = ""; - static char about_me[22] = ""; - - auto &common_lang = emuenv.common_dialog.lang.common; - auto &account_state = emuenv.v3kn.account_state; - auto &user_info = account_state.user_info; - - if (is_result_ready.exchange(false)) { - std::optional result; - { - std::lock_guard lock(result_mutex); - result = std::exchange(pending_result, std::nullopt); - } - - if (result) { - v3kn::handle_v3kn_status(emuenv, result->response); - result_state = V3knResultState::NONE; - - const auto rename_avatar_cache = [&](const std::string &old_online_id, const std::string &new_online_id) { - if (old_online_id.empty() || new_online_id.empty() || old_online_id == new_online_id) - return; - - const fs::path server_cache_dir = emuenv.cache_path / "v3kn" / user_info.host; - const fs::path old_avatar_cache_dir = server_cache_dir / old_online_id; - const fs::path new_avatar_cache_dir = server_cache_dir / new_online_id; - - if (!fs::exists(old_avatar_cache_dir)) - return; - - fs::create_directories(server_cache_dir); - fs::rename(old_avatar_cache_dir, new_avatar_cache_dir); - }; - - const auto complete_sign_in = [&]() { - user_info.online_id = result->online_id; - user_info.password = result->password; - user_info.token = result->token; - v3kn::save_v3kn_user_info(emuenv); - std::thread([&gui, &emuenv]() { - v3kn::init_v3kn_user_info(gui, emuenv); - }).detach(); - }; - - switch (result->action) { - case V3knPendingAction::CREATE: - case V3knPendingAction::LOGIN: - if (result->response.body.starts_with("OK:") && !result->token.empty()) { - complete_sign_in(); - result_state = V3knResultState::PENDING_ACK; - } - break; - case V3knPendingAction::REFRESH_QUOTA: - if (result->has_quota) { - user_info.quota_used = result->quota_used; - user_info.quota_total = result->quota_total; - } - break; - case V3knPendingAction::DELETE_ACCOUNT: - if (result->response.body.starts_with("OK:")) { - user_info = {}; - v3kn::save_v3kn_user_info(emuenv); - result_state = V3knResultState::PENDING_ACK; - std::thread([&gui, &emuenv]() { - v3kn::init_v3kn_user_info(gui, emuenv); - }).detach(); - } - break; - case V3knPendingAction::CHANGE_ONLINE_ID: - if (result->response.body.starts_with("OK:")) { - const std::string old_online_id = user_info.online_id; - user_info.online_id = result->online_id; - rename_avatar_cache(old_online_id, user_info.online_id); - v3kn::save_v3kn_user_info(emuenv); - result_state = V3knResultState::PENDING_ACK; - } - break; - case V3knPendingAction::CHANGE_PASSWORD: - if (result->response.body.starts_with("OK:") && !result->token.empty()) { - user_info.password = result->password; - user_info.token = result->token; - v3kn::save_v3kn_user_info(emuenv); - result_state = V3knResultState::PENDING_ACK; - } - break; - case V3knPendingAction::CHANGE_ABOUT_ME: - if (result->response.body.starts_with("OK:")) { - result_state = V3knResultState::PENDING_ACK; - } - break; - case V3knPendingAction::NONE: - break; - } - - result_message = result->message; - is_waiting_result = false; - } - } - - const std::string OK_STR = common_lang["ok"]; - const std::string CANCEL_STR = common_lang["cancel"]; - - const auto popup_flags = ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings; - - static const auto result_popup = [&]() { - const ImVec2 POPUP_SIZE(620.f * SCALE.x, 200.f * SCALE.y); - const ImVec2 POPUP_POS((VIEWPORT_SIZE.x - POPUP_SIZE.x) / 2.f, (VIEWPORT_SIZE.y - POPUP_SIZE.y) / 2.f); - ImGui::SetNextWindowSize(POPUP_SIZE, ImGuiCond_Always); - ImGui::SetNextWindowPos(POPUP_POS, ImGuiCond_Always); - if (ImGui::BeginPopupModal("Result", nullptr, popup_flags)) { - const auto popup_width_size = ImGui::GetWindowWidth(); - const auto text = result_message.empty() ? emuenv.common_dialog.lang.common["please_wait"] : result_message; - const auto text_size = ImGui::CalcTextSize(text.c_str()); - ImGui::SetCursorPosY(((POPUP_SIZE.y - text_size.y) / 2.f) - SMALL_BUTTON_SIZE.y); - TextColoredCentered(GUI_COLOR_TEXT, text.c_str(), 20.f * SCALE.x); - ImGui::SetCursorPos(ImVec2((popup_width_size - SMALL_BUTTON_SIZE.x) / 2.f, POPUP_SIZE.y - SMALL_BUTTON_SIZE.y - (16.f * SCALE.y))); - ImGui::BeginDisabled(is_waiting_result); - if (ImGui::Button("OK", SMALL_BUTTON_SIZE)) { - ImGui::CloseCurrentPopup(); - if (result_state == V3knResultState::PENDING_ACK) { - result_state = V3knResultState::CLOSE_PARENT; - username[0] = '\0'; - password[0] = '\0'; - confirm[0] = '\0'; - about_me[0] = '\0'; - } else { - result_state = V3knResultState::NONE; - result_message.clear(); - } - } - ImGui::EndDisabled(); - ImGui::EndPopup(); - } - }; - - const auto AVATAR_SIZE = ImVec2(static_cast(AvatarSize::V3KN) * SCALE.x, static_cast(AvatarSize::V3KN) * SCALE.y); - const auto BLOCK_INFO_SIZE_HEIGHT = AVATAR_SIZE.y + (16.f * SCALE.y); - const auto INFO_SIZE_HEIGHT = BLOCK_INFO_SIZE_HEIGHT / 4.f; - const auto BEGIN_INFO_POS_Y = BLOCK_TITLE_SIZE_HEIGHT + 1.f; - const auto FIRST_INFO_POS_Y = BEGIN_INFO_POS_Y + ((INFO_SIZE_HEIGHT - (ImGui::GetFontSize() * FONT_SCALE)) / 2.f); - - ImGui::SetCursorPosY(FIRST_INFO_POS_Y); - const auto selected_server = account_state.selected_server_index; - if (selected_server < V3KN_SERVER_LIST_NAME.size()) - ImGui::Text("Server: %s", V3KN_SERVER_LIST_NAME[selected_server]); - - const auto draw_list = ImGui::GetWindowDrawList(); - const auto STATUS_CIRCLE_RADIUS = ImGui::GetFontSize() / 2.f; - const auto circle_pos = ImVec2(POPUP_POS.x + ImGui::GetCursorPosX() + STATUS_CIRCLE_RADIUS, POPUP_POS.y + ImGui::GetCursorPosY() + (INFO_SIZE_HEIGHT / 3.f)); - ImU32 status_color; - std::string status_text; - - if (!user_info.token.empty()) { - if (v3kn::is_v3kn_logged_in()) { - status_color = IM_COL32(14, 138, 22, 255); - status_text = fmt::format("Signed In - ID: {}", user_info.online_id); - } else { - status_color = IM_COL32(224, 138, 30, 255); - status_text = fmt::format("Offline - ID: {}", user_info.online_id); - } - } else { - status_color = IM_COL32(255, 0, 0, 255); - status_text = "Not Signed In - ID: " + (user_info.online_id.empty() ? std::string("-") : user_info.online_id); - } - - draw_list->AddCircleFilled(circle_pos, STATUS_CIRCLE_RADIUS, status_color); - ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + (STATUS_CIRCLE_RADIUS * 2.f) + (8.f * SCALE.x), FIRST_INFO_POS_Y + INFO_SIZE_HEIGHT)); - ImGui::Text("%s", status_text.c_str()); - - std::string created_at = "-"; - if (user_info.created_at > 0) { - tm created_tm = {}; - SAFE_LOCALTIME(&user_info.created_at, &created_tm); - auto CREATED_AT = get_date_time(gui, emuenv, created_tm); - created_at = fmt::format("{} {} {}", CREATED_AT[DateTime::DATE_MINI], CREATED_AT[DateTime::CLOCK], emuenv.cfg.sys_time_format == SCE_SYSTEM_PARAM_TIME_FORMAT_12HOUR ? CREATED_AT[DateTime::DAY_MOMENT] : ""); - } - ImGui::SetCursorPosY(FIRST_INFO_POS_Y + INFO_SIZE_HEIGHT * 2); - ImGui::Text("Created At: %s", created_at.c_str()); - ImGui::SetCursorPosY(FIRST_INFO_POS_Y + (INFO_SIZE_HEIGHT * 3)); - ImGui::Text("Quota Used: %s / %s", - user_info.quota_used == 0 ? "-" : get_unit_size(user_info.quota_used).c_str(), - user_info.quota_total == 0 ? "-" : get_unit_size(user_info.quota_total).c_str()); - - const auto END_INFO_POS_Y = BEGIN_INFO_POS_Y + BLOCK_INFO_SIZE_HEIGHT; - - // Display V3KN avatar if available, otherwise fall back to local user avatar - if (gui.v3kn_avatar) { - const ImVec2 V3KN_AVATAR_SIZE(static_cast(AvatarSize::V3KN) * SCALE.x, static_cast(AvatarSize::V3KN) * SCALE.y); - const ImVec2 V3KN_AVATAR_POS( - POPUP_SIZE.x - V3KN_AVATAR_SIZE.x - (10.f * SCALE.x), - BEGIN_INFO_POS_Y + ((BLOCK_INFO_SIZE_HEIGHT - V3KN_AVATAR_SIZE.y) / 2.f)); - const float BORDER_THICKNESS = 2.f * SCALE.x; - ImGui::GetWindowDrawList()->AddRect( - ImVec2(POPUP_POS.x + V3KN_AVATAR_POS.x - BORDER_THICKNESS, POPUP_POS.y + V3KN_AVATAR_POS.y - BORDER_THICKNESS), - ImVec2(POPUP_POS.x + V3KN_AVATAR_POS.x + V3KN_AVATAR_SIZE.x + BORDER_THICKNESS, POPUP_POS.y + V3KN_AVATAR_POS.y + V3KN_AVATAR_SIZE.y + BORDER_THICKNESS), - IM_COL32(100, 149, 237, 255), 6.f * SCALE.x, 0, BORDER_THICKNESS); - ImGui::SetCursorPos(V3KN_AVATAR_POS); - ImGui::Image(gui.v3kn_avatar, V3KN_AVATAR_SIZE); - } else if (gui.users_avatar[emuenv.io.user_id]) { - const auto &user_avatar_infos = gui.users_avatar_infos[emuenv.io.user_id][AvatarSize::V3KN]; - const ImVec2 AVATAR_FINAL_SIZE = ImVec2(user_avatar_infos.size.x * SCALE.x, user_avatar_infos.size.y * SCALE.y); - const ImVec2 AVATAR_POS = ImVec2( - POPUP_SIZE.x - AVATAR_SIZE.x - (10.f * SCALE.x) + (user_avatar_infos.pos.x * SCALE.x), - BEGIN_INFO_POS_Y + ((BLOCK_INFO_SIZE_HEIGHT - AVATAR_SIZE.y) / 2.f) + (user_avatar_infos.pos.y * SCALE.y)); - const float BORDER_THICKNESS = 2.f * SCALE.x; - ImGui::GetWindowDrawList()->AddRect( - ImVec2(POPUP_POS.x + AVATAR_POS.x - BORDER_THICKNESS, POPUP_POS.y + AVATAR_POS.y - BORDER_THICKNESS), - ImVec2(POPUP_POS.x + AVATAR_POS.x + AVATAR_FINAL_SIZE.x + BORDER_THICKNESS, POPUP_POS.y + AVATAR_POS.y + AVATAR_FINAL_SIZE.y + BORDER_THICKNESS), - IM_COL32(100, 149, 237, 255), 0.f, 0, BORDER_THICKNESS); - ImGui::SetCursorPos(AVATAR_POS); - ImGui::Image(gui.users_avatar[emuenv.io.user_id], AVATAR_FINAL_SIZE); - } - - ImGui::SetCursorPosY(END_INFO_POS_Y); - ImGui::Separator(); - ImGui::Spacing(); - - const auto AFTER_INFO_POS = ImGui::GetCursorPosY(); - - ImGui::BeginDisabled(v3kn::is_v3kn_logged_in() && result_state == V3knResultState::NONE); - - // Create Account Button - ImGui::SetCursorPosX((WINDOW_SIZE.x / 2.f) - BUTTON_SIZE.x - (20.f * SCALE.x)); - if (ImGui::Button("Create Account", BUTTON_SIZE)) { - result_state = V3knResultState::NONE; - ImGui::OpenPopup("V3KN Create"); - } - - if (ImGui::BeginPopupModal("V3KN Create", nullptr, popup_flags)) { - const auto title_popup = "Create V3KN Account"; - ImGui::SetCursorPosX((ImGui::GetWindowWidth() - ImGui::CalcTextSize(title_popup).x) / 2.f); - ImGui::TextColored(GUI_COLOR_TEXT_TITLE, "%s", title_popup); - ImGui::Separator(); - - ImGui::TextColored(GUI_COLOR_TEXT_TITLE, "Please enter a username and password to create your V3KN account."); - ImGui::TextColored(GUI_COLOR_TEXT, "Username must start with a letter and contain 3–16 characters (letters, numbers, _ or -)."); - ImGui::TextColored(GUI_COLOR_TEXT, "Password must be at least 8 characters long."); - - ImGui::InputText("Username", username, sizeof(username), ImGuiInputTextFlags_CharsNoBlank); - ImGui::InputText("Password", password, sizeof(password), ImGuiInputTextFlags_Password); - const auto res = ImGui::InputText("Confirm Password", confirm, sizeof(confirm), ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_Password); - - const std::string username_str(username); - const std::string password_str(password); - const std::string confirm_str(confirm); - ImGui::BeginDisabled(is_waiting_result || username_str.empty() || (username_str.size() < 3) || password_str.empty() || confirm_str.empty() || (password_str.size() < 8)); - ImGui::SetCursorPosX((ImGui::GetWindowWidth() / 2.f) - SMALL_BUTTON_SIZE.x - (20.f * SCALE.x)); - if (ImGui::Button(OK_STR.c_str(), SMALL_BUTTON_SIZE) || res) { - ImGui::OpenPopup("Result"); - if (std::string(password) != std::string(confirm)) { - result_state = V3knResultState::NONE; - result_message = "Password and confirmation do not match."; - } else { - const std::string derived_password = derive_password(password); - const std::string base64_password = base64_encode(derived_password); - const std::string requested_username = username; - const UserInfo current_user_info = user_info; - is_waiting_result = true; - std::thread([&emuenv, requested_username, base64_password, current_user_info]() { - const auto res = v3kn::v3kn_create(current_user_info, requested_username, base64_password); - V3knPendingResult result; - result.action = V3knPendingAction::CREATE; - result.response = res; - result.online_id = requested_username; - result.password = base64_password; - if (res.body.starts_with("OK:")) { - std::smatch match; - std::regex regex_pattern(R"(OK:([^:]+))"); - if (std::regex_search(res.body, match, regex_pattern) && (match.size() == 2)) { - result.token = match[1]; - result.message = fmt::format("Account created successfully for user {}", requested_username); - } else { - result.message = fmt::format("Unexpected response format from server: {}", res.body); - } - } else { - result.message = v3kn::get_v3kn_error_message(emuenv, res); - } - { - std::lock_guard lock(result_mutex); - pending_result = std::move(result); - } - is_result_ready = true; - }).detach(); - } - } - ImGui::EndDisabled(); - ImGui::SameLine(0, 40.f * SCALE.x); - if (ImGui::Button(CANCEL_STR.c_str(), SMALL_BUTTON_SIZE)) { - result_state = V3knResultState::NONE; - username[0] = '\0'; - password[0] = '\0'; - confirm[0] = '\0'; - ImGui::CloseCurrentPopup(); - } - result_popup(); - if (result_state == V3knResultState::CLOSE_PARENT) { - ImGui::CloseCurrentPopup(); - result_message.clear(); - result_state = V3knResultState::NONE; - } - ImGui::EndPopup(); - } - - // Login Button - ImGui::SetCursorPosX((WINDOW_SIZE.x / 2.f) - BUTTON_SIZE.x - (20.f * SCALE.x)); - if (ImGui::Button("Login", BUTTON_SIZE)) { - result_state = V3knResultState::NONE; - ImGui::OpenPopup("V3KN Login"); - } - - if (ImGui::BeginPopupModal("V3KN Login", nullptr, popup_flags)) { - const auto title_popup = "V3KN Login"; - ImGui::SetCursorPosX((ImGui::GetWindowWidth() - ImGui::CalcTextSize(title_popup).x) / 2.f); - ImGui::TextColored(GUI_COLOR_TEXT_TITLE, "%s", title_popup); - ImGui::Separator(); - - ImGui::InputText("Username", username, sizeof(username)); - auto res = ImGui::InputText("Password", password, sizeof(password), ImGuiInputTextFlags_Password | ImGuiInputTextFlags_EnterReturnsTrue); - - ImGui::BeginDisabled(is_waiting_result || std::string(password).empty() || std::string(username).empty()); - ImGui::SetCursorPosX((ImGui::GetWindowWidth() / 2.f) - SMALL_BUTTON_SIZE.x - (20.f * SCALE.x)); - if (ImGui::Button(OK_STR.c_str(), SMALL_BUTTON_SIZE) || res) { - const std::string derived_password = derive_password(password); - const std::string base64_password = base64_encode(derived_password); - const std::string requested_username = username; - const UserInfo current_user_info = user_info; - result_state = V3knResultState::NONE; - is_waiting_result = true; - ImGui::OpenPopup("Result"); - std::thread([&emuenv, requested_username, base64_password, current_user_info]() { - const auto res = v3kn::v3kn_login(current_user_info, requested_username, base64_password); - V3knPendingResult result; - result.action = V3knPendingAction::LOGIN; - result.response = res; - result.password = base64_password; - if (res.body.starts_with("OK:")) { - std::smatch match; - std::regex regex_pattern(R"(OK:([^:]+):([^:]+))"); - if (std::regex_search(res.body, match, regex_pattern) && (match.size() == 3)) { - result.online_id = match[1]; - result.token = match[2]; - result.message = "Login on V3KN successfully for user " + result.online_id; - } else { - result.message = v3kn::get_v3kn_error_message(emuenv, res); - } - } else { - result.message = v3kn::get_v3kn_error_message(emuenv, res); - } - { - std::lock_guard lock(result_mutex); - pending_result = std::move(result); - } - is_result_ready = true; - }).detach(); - } - ImGui::EndDisabled(); - ImGui::SameLine(0, 40.f * SCALE.x); - if (ImGui::Button(CANCEL_STR.c_str(), SMALL_BUTTON_SIZE)) { - result_state = V3knResultState::NONE; - username[0] = '\0'; - password[0] = '\0'; - ImGui::CloseCurrentPopup(); - } - result_popup(); - if (result_state == V3knResultState::CLOSE_PARENT) { - ImGui::CloseCurrentPopup(); - result_message.clear(); - result_state = V3knResultState::NONE; - } - ImGui::EndPopup(); - } - ImGui::EndDisabled(); - - if (!v3kn::is_v3kn_logged_in() && !user_info.token.empty()) { - ImGui::SetCursorPosX((WINDOW_SIZE.x / 2.f) - BUTTON_SIZE.x - (20.f * SCALE.x)); - static bool is_testing_connection = false; - ImGui::BeginDisabled(is_waiting_result || is_testing_connection); - if (ImGui::Button(is_testing_connection ? "Testing..." : "Test Connection", BUTTON_SIZE)) { - is_testing_connection = true; - std::thread([&gui, &emuenv]() { - v3kn::init_v3kn_user_info(gui, emuenv); - is_testing_connection = false; - }).detach(); - } - ImGui::EndDisabled(); - SetTooltipEx("Test connection to V3KN server with saved credentials"); - } - - ImGui::BeginDisabled(!v3kn::is_v3kn_logged_in() && result_state == V3knResultState::NONE); - - // Refresh quota button - ImGui::SetCursorPosX((WINDOW_SIZE.x / 2.f) - BUTTON_SIZE.x - (20.f * SCALE.x)); - ImGui::BeginDisabled(is_waiting_result); - if (ImGui::Button("Refresh Quota", BUTTON_SIZE)) { - const UserInfo current_user_info = user_info; - is_waiting_result = true; - std::thread([&emuenv, current_user_info]() { - const std::string url = v3kn::get_v3kn_server_url(current_user_info.host, "v3kn/quota"); - const auto res = net_utils::get_web_response_ex(url, current_user_info.token); - V3knPendingResult result; - result.action = V3knPendingAction::REFRESH_QUOTA; - result.response = res; - if (res.body.starts_with("OK:")) { - std::smatch match; - std::regex regex_pattern(R"(OK:([^:]+):([^:]+))"); - if (std::regex_search(res.body, match, regex_pattern) && (match.size() == 3)) { - result.quota_used = std::strtoull(match[1].str().c_str(), nullptr, 10); - result.quota_total = std::strtoull(match[2].str().c_str(), nullptr, 10); - result.has_quota = true; - result.message = fmt::format("Quota refreshed: {} / {}", get_unit_size(result.quota_used), get_unit_size(result.quota_total)); - } else { - result.message = v3kn::get_v3kn_error_message(emuenv, res); - } - } else { - result.message = v3kn::get_v3kn_error_message(emuenv, res); - } - { - std::lock_guard lock(result_mutex); - pending_result = std::move(result); - } - is_result_ready = true; - }).detach(); - ImGui::OpenPopup("Result"); - } - ImGui::EndDisabled(); - SetTooltipEx("Refresh quota"); - result_popup(); - - // Delete account button - ImGui::SetCursorPosX((WINDOW_SIZE.x / 2.f) - BUTTON_SIZE.x - (20.f * SCALE.x)); - if (ImGui::Button("Delete Account", BUTTON_SIZE)) - ImGui::OpenPopup("V3KN Delete"); - - if (ImGui::BeginPopupModal("V3KN Delete", nullptr, popup_flags)) { - const auto title_popup = "Delete V3KN Account"; - ImGui::SetCursorPosX((ImGui::GetWindowWidth() - ImGui::CalcTextSize(title_popup).x) / 2.f); - ImGui::TextColored(GUI_COLOR_TEXT_TITLE, "%s", title_popup); - ImGui::Separator(); - - ImGui::InputText("Password", password, sizeof(password), ImGuiInputTextFlags_Password); - ImGui::BeginDisabled(is_waiting_result || std::string(password).empty()); - ImGui::SetCursorPosX((ImGui::GetWindowWidth() / 2.f) - SMALL_BUTTON_SIZE.x - (20.f * SCALE.x)); - if (ImGui::Button(OK_STR.c_str(), SMALL_BUTTON_SIZE)) { - const std::string derived_password = derive_password(password); - const std::string base64_password = base64_encode(derived_password); - const UserInfo current_user_info = user_info; - const std::string current_online_id = user_info.online_id; - is_waiting_result = true; - ImGui::OpenPopup("Result"); - std::thread([&emuenv, current_online_id, base64_password, current_user_info]() { - const auto res = v3kn::v3kn_delete(current_user_info, base64_password); - V3knPendingResult result; - result.action = V3knPendingAction::DELETE_ACCOUNT; - result.response = res; - if (res.body.starts_with("OK:")) - result.message = "Account V3KN deleted successfully for user " + current_online_id; - else - result.message = v3kn::get_v3kn_error_message(emuenv, res); - { - std::lock_guard lock(result_mutex); - pending_result = std::move(result); - } - is_result_ready = true; - }).detach(); - } - ImGui::EndDisabled(); - ImGui::SameLine(0, 40.f * SCALE.x); - if (ImGui::Button(CANCEL_STR.c_str(), SMALL_BUTTON_SIZE)) { - password[0] = '\0'; - ImGui::CloseCurrentPopup(); - } - result_popup(); - if (result_state == V3knResultState::CLOSE_PARENT) { - ImGui::CloseCurrentPopup(); - result_message.clear(); - result_state = V3knResultState::NONE; - } - ImGui::EndPopup(); - } - - ImGui::SetCursorPosX((WINDOW_SIZE.x / 2.f) - BUTTON_SIZE.x - (20.f * SCALE.x)); - if (ImGui::Button("Logout", BUTTON_SIZE)) - ImGui::OpenPopup("Confirm Logout"); - - if (ImGui::BeginPopupModal("Confirm Logout", nullptr, popup_flags)) { - ImGui::Text("Are you sure you want to logout from V3KN?"); - ImGui::SetCursorPosX((ImGui::GetWindowWidth() - (SMALL_BUTTON_SIZE.x * 2.f) - (40.f * SCALE.x)) / 2.f); - if (ImGui::Button(common_lang["no"].c_str(), SMALL_BUTTON_SIZE)) - ImGui::CloseCurrentPopup(); - ImGui::SameLine(0, 40.f * SCALE.x); - if (ImGui::Button(common_lang["yes"].c_str(), SMALL_BUTTON_SIZE)) { - user_info = {}; - v3kn::set_v3kn_logged_in(false); - v3kn::save_v3kn_user_info(emuenv); - gui.v3kn_avatar = {}; - std::thread([&gui, &emuenv]() { - v3kn::init_v3kn_user_info(gui, emuenv); - }).detach(); - result_message = "Logged out successfully."; - ImGui::CloseCurrentPopup(); - } - result_popup(); - if (result_state == V3knResultState::CLOSE_PARENT) { - ImGui::CloseCurrentPopup(); - result_message.clear(); - result_state = V3knResultState::NONE; - } - ImGui::EndPopup(); - } - - ImGui::SetCursorPosY(AFTER_INFO_POS); - ImGui::SetCursorPosX((WINDOW_SIZE.x / 2.f) + (20.f * SCALE.x)); - if (ImGui::Button("Change Online ID", BUTTON_SIZE)) - ImGui::OpenPopup("V3KN Change Online ID"); - - if (ImGui::BeginPopupModal("V3KN Change Online ID", nullptr, popup_flags)) { - const auto title_popup = "Change V3KN Online ID"; - ImGui::SetCursorPosX((ImGui::GetWindowWidth() - ImGui::CalcTextSize(title_popup).x) / 2.f); - ImGui::TextColored(GUI_COLOR_TEXT_TITLE, "%s", title_popup); - ImGui::Separator(); - - ImGui::InputText("New Username", username, sizeof(username), ImGuiInputTextFlags_CharsNoBlank); - ImGui::BeginDisabled(is_waiting_result || std::string(username).empty()); - ImGui::SetCursorPosX((ImGui::GetWindowWidth() / 2.f) - SMALL_BUTTON_SIZE.x - (20.f * SCALE.x)); - if (ImGui::Button(OK_STR.c_str(), SMALL_BUTTON_SIZE)) { - const std::string new_online_id = username; - const UserInfo current_user_info = user_info; - is_waiting_result = true; - ImGui::OpenPopup("Result"); - std::thread([&emuenv, new_online_id, current_user_info]() { - const auto res = v3kn::v3kn_change_online_id(current_user_info, new_online_id); - V3knPendingResult result; - result.action = V3knPendingAction::CHANGE_ONLINE_ID; - result.response = res; - result.online_id = new_online_id; - if (res.body.starts_with("OK:")) - result.message = "Online ID successfully changed to " + new_online_id; - else - result.message = v3kn::get_v3kn_error_message(emuenv, res); - { - std::lock_guard lock(result_mutex); - pending_result = std::move(result); - } - is_result_ready = true; - }).detach(); - } - ImGui::EndDisabled(); - ImGui::SameLine(0, 40.f * SCALE.x); - if (ImGui::Button(CANCEL_STR.c_str(), SMALL_BUTTON_SIZE)) { - username[0] = '\0'; - ImGui::CloseCurrentPopup(); - } - result_popup(); - if (result_state == V3knResultState::CLOSE_PARENT) { - ImGui::CloseCurrentPopup(); - result_message.clear(); - result_state = V3knResultState::NONE; - } - ImGui::EndPopup(); - } - - ImGui::SetCursorPosX((WINDOW_SIZE.x / 2.f) + (20.f * SCALE.x)); - if (ImGui::Button("Change Password", BUTTON_SIZE)) - ImGui::OpenPopup("V3KN Change Password"); - - if (ImGui::BeginPopupModal("V3KN Change Password", nullptr, popup_flags)) { - const auto title_popup = "Change V3KN Password"; - ImGui::SetCursorPosX((ImGui::GetWindowWidth() - ImGui::CalcTextSize(title_popup).x) / 2.f); - ImGui::TextColored(GUI_COLOR_TEXT_TITLE, "%s", title_popup); - ImGui::Separator(); - - ImGui::InputText("Current Password", password, sizeof(password), ImGuiInputTextFlags_Password); - ImGui::InputText("New Password", confirm, sizeof(confirm), ImGuiInputTextFlags_Password); - ImGui::BeginDisabled(is_waiting_result || std::string(password).empty() || std::string(confirm).empty()); - ImGui::SetCursorPosX((ImGui::GetWindowWidth() / 2.f) - SMALL_BUTTON_SIZE.x - (20.f * SCALE.x)); - if (ImGui::Button(OK_STR.c_str(), SMALL_BUTTON_SIZE)) { - const std::string derived_current_password = derive_password(password); - const std::string base64_current_password = base64_encode(derived_current_password); - if (base64_current_password != user_info.password) { - result_message = "Current password is incorrect."; - ImGui::OpenPopup("Result"); - } else { - const std::string derived_new_password = derive_password(confirm); - const std::string base64_new_password = base64_encode(derived_new_password); - const UserInfo current_user_info = user_info; - is_waiting_result = true; - ImGui::OpenPopup("Result"); - std::thread([&emuenv, base64_current_password, base64_new_password, current_user_info]() { - const auto res = v3kn::v3kn_change_password(current_user_info, base64_current_password, base64_new_password); - V3knPendingResult result; - result.action = V3knPendingAction::CHANGE_PASSWORD; - result.response = res; - result.password = base64_new_password; - if (res.body.starts_with("OK:")) { - std::smatch match; - std::regex regex_pattern(R"(OK:([^:]+))"); - if (std::regex_search(res.body, match, regex_pattern) && (match.size() == 2)) { - result.token = match[1]; - result.message = "Password changed successfully for user " + current_user_info.online_id; - } else { - result.message = v3kn::get_v3kn_error_message(emuenv, res); - } - } else { - result.message = v3kn::get_v3kn_error_message(emuenv, res); - } - { - std::lock_guard lock(result_mutex); - pending_result = std::move(result); - } - is_result_ready = true; - }).detach(); - } - } - ImGui::EndDisabled(); - ImGui::SameLine(0, 40.f * SCALE.x); - if (ImGui::Button(CANCEL_STR.c_str(), SMALL_BUTTON_SIZE)) { - password[0] = '\0'; - confirm[0] = '\0'; - ImGui::CloseCurrentPopup(); - } - result_popup(); - if (result_state == V3knResultState::CLOSE_PARENT) { - ImGui::CloseCurrentPopup(); - result_message.clear(); - result_state = V3knResultState::NONE; - } - ImGui::EndPopup(); - } - - // Change About Me Button - ImGui::SetCursorPosX((WINDOW_SIZE.x / 2.f) + (20.f * SCALE.x)); - if (ImGui::Button("Change About Me", BUTTON_SIZE)) - ImGui::OpenPopup("V3KN Change About Me"); - - if (ImGui::BeginPopupModal("V3KN Change About Me", nullptr, popup_flags)) { - const auto title_popup = "Change V3KN About Me"; - ImGui::SetCursorPosX((ImGui::GetWindowWidth() - ImGui::CalcTextSize(title_popup).x) / 2.f); - ImGui::TextColored(GUI_COLOR_TEXT_TITLE, "%s", title_popup); - ImGui::Separator(); - const ImVec2 BOX_TEXT_SIZE = ImVec2(368.f * SCALE.x, 50.f * SCALE.y); - ImGui::SetNextItemWidth(BOX_TEXT_SIZE.x); - const auto res = ImGui::InputText("About Me", about_me, sizeof(about_me), ImGuiInputTextFlags_EnterReturnsTrue); - ImGui::BeginDisabled(is_waiting_result || std::string(about_me).empty()); - if (ImGui::Button(OK_STR.c_str(), SMALL_BUTTON_SIZE) || res) { - const std::string about_me_str(about_me); - const UserInfo current_user_info = user_info; - is_waiting_result = true; - ImGui::OpenPopup("Result"); - std::thread([&emuenv, about_me_str, current_user_info]() { - const auto res = v3kn::v3kn_change_about_me(current_user_info, about_me_str); - V3knPendingResult result; - result.action = V3knPendingAction::CHANGE_ABOUT_ME; - result.response = res; - if (res.body.starts_with("OK:")) - result.message = "About Me changed successfully for user " + current_user_info.online_id; - else - result.message = v3kn::get_v3kn_error_message(emuenv, res); - { - std::lock_guard lock(result_mutex); - pending_result = std::move(result); - } - is_result_ready = true; - }).detach(); - } - ImGui::EndDisabled(); - ImGui::SameLine(0, 40.f * SCALE.x); - if (ImGui::Button(CANCEL_STR.c_str(), SMALL_BUTTON_SIZE)) { - about_me[0] = '\0'; - ImGui::CloseCurrentPopup(); - } - result_popup(); - if (result_state == V3knResultState::CLOSE_PARENT) { - ImGui::CloseCurrentPopup(); - result_message.clear(); - result_state = V3knResultState::NONE; - } - ImGui::EndPopup(); - } - - // Change avatar - ImGui::SetCursorPosX((WINDOW_SIZE.x / 2.f) + (20.f * SCALE.x)); - if (ImGui::Button("Change Avatar", BUTTON_SIZE)) - ImGui::OpenPopup("V3KN Change Avatar"); - - if (ImGui::BeginPopupModal("V3KN Change Avatar", nullptr, popup_flags)) { - const auto title_popup = "Change V3KN Avatar"; - ImGui::SetCursorPosX((ImGui::GetWindowWidth() - ImGui::CalcTextSize(title_popup).x) / 2.f); - ImGui::TextColored(GUI_COLOR_TEXT_TITLE, "%s", title_popup); - ImGui::Separator(); - - static std::string selected_avatar_path; - static ImGui_Texture avatar_preview; - static int preview_w = 0, preview_h = 0; - static bool is_uploading = false; - static std::string avatar_result_message; - static bool avatar_upload_success = false; - - if (avatar_upload_success) { - avatar_upload_success = false; - selected_avatar_path.clear(); - avatar_preview = {}; - preview_w = 0; - preview_h = 0; - avatar_result_message.clear(); - ImGui::CloseCurrentPopup(); - ImGui::EndPopup(); - // skip rest of popup this frame - goto avatar_popup_end; - } - - if (ImGui::Button("Browse...", BUTTON_SIZE)) { - fs::path avatar_path{}; - const auto result = host::dialog::filesystem::open_file(avatar_path, { { "Image file", { "bmp", "gif", "jpg", "jpeg", "png", "tif" } } }); - if (result == host::dialog::filesystem::Result::SUCCESS && fs::exists(avatar_path)) { - selected_avatar_path = avatar_path.string(); - avatar_result_message.clear(); - - int w, h; - stbi_uc *data = stbi_load(selected_avatar_path.c_str(), &w, &h, nullptr, STBI_rgb_alpha); - if (data) { - avatar_preview = ImGui_Texture(gui.imgui_state.get(), data, w, h); - preview_w = w; - preview_h = h; - stbi_image_free(data); - } else { - avatar_result_message = "Failed to load image."; - selected_avatar_path.clear(); - } - } else if (result == host::dialog::filesystem::Result::ERROR) { - avatar_result_message = fmt::format("File dialog error: {}", host::dialog::filesystem::get_error()); - } - } - - if (!selected_avatar_path.empty()) { - ImGui::Text("Selected: %s", fs::path(selected_avatar_path).filename().string().c_str()); - if (avatar_preview && preview_w > 0 && preview_h > 0) { - const float max_preview = 128.f * SCALE.x; - const float ratio = std::min(max_preview / static_cast(preview_w), max_preview / static_cast(preview_h)); - const ImVec2 preview_size(preview_w * ratio, preview_h * ratio); - ImGui::SetCursorPosX((ImGui::GetWindowWidth() - preview_size.x) / 2.f); - ImGui::Image(avatar_preview, preview_size); - } - } - - if (!avatar_result_message.empty()) { - const auto msg_color = avatar_result_message.find("successfully") != std::string::npos - ? ImVec4(0.3f, 1.f, 0.3f, 1.f) - : ImVec4(1.f, 0.3f, 0.3f, 1.f); - ImGui::TextColored(msg_color, "%s", avatar_result_message.c_str()); - } - - ImGui::BeginDisabled(selected_avatar_path.empty() || is_uploading); - ImGui::SetCursorPosX((ImGui::GetWindowWidth() / 2.f) - SMALL_BUTTON_SIZE.x - (20.f * SCALE.x)); - if (ImGui::Button(is_uploading ? "Uploading..." : OK_STR.c_str(), SMALL_BUTTON_SIZE)) { - is_uploading = true; - avatar_result_message.clear(); - std::thread([&gui, &emuenv, &user_info]() { - constexpr int canvas_size = 128; - - int w, h; - stbi_uc *img = stbi_load(selected_avatar_path.c_str(), &w, &h, nullptr, STBI_rgb_alpha); - if (!img) { - avatar_result_message = "Failed to load image for processing."; - is_uploading = false; - return; - } - - const float scale_factor = static_cast(canvas_size) / static_cast(std::max(w, h)); - const int new_w = static_cast(w * scale_factor); - const int new_h = static_cast(h * scale_factor); - - std::vector resized(new_w * new_h * 4); - stbir_resize_uint8_linear(img, w, h, 0, resized.data(), new_w, new_h, 0, STBIR_RGBA); - stbi_image_free(img); - - std::vector canvas(canvas_size * canvas_size * 4, 0); - const int offset_x = (canvas_size - new_w) / 2; - const int offset_y = (canvas_size - new_h) / 2; - - for (int y = 0; y < new_h; y++) { - for (int x = 0; x < new_w; x++) { - const int src = (y * new_w + x) * 4; - const int dst = ((y + offset_y) * canvas_size + (x + offset_x)) * 4; - canvas[dst + 0] = resized[src + 0]; - canvas[dst + 1] = resized[src + 1]; - canvas[dst + 2] = resized[src + 2]; - canvas[dst + 3] = resized[src + 3]; - } - } - - std::vector png_data; - stbi_write_png_to_func( - [](void *context, void *data, int size) { - auto *vec = static_cast *>(context); - const auto ptr = static_cast(data); - vec->insert(vec->end(), ptr, ptr + size); - }, - &png_data, canvas_size, canvas_size, 4, canvas.data(), canvas_size * 4); - - const auto res = v3kn::v3kn_avatar_upload(user_info, png_data); - v3kn::handle_v3kn_status(emuenv, res); - - if (res.body.starts_with("OK")) { - gui.v3kn_avatar = ImGui_Texture(gui.imgui_state.get(), canvas.data(), canvas_size, canvas_size); - const fs::path avatar_cache_path = emuenv.cache_path / "v3kn" / user_info.host / fmt::format("{}.png", user_info.online_id); - fs::create_directories(avatar_cache_path.parent_path()); - fs_utils::dump_data(avatar_cache_path, png_data.data(), static_cast(png_data.size())); - avatar_upload_success = true; - } else { - avatar_result_message = v3kn::get_v3kn_error_message(emuenv, res); - } - is_uploading = false; - }).detach(); - } - ImGui::EndDisabled(); - ImGui::SameLine(0, 40.f * SCALE.x); - if (ImGui::Button(CANCEL_STR.c_str(), SMALL_BUTTON_SIZE)) { - selected_avatar_path.clear(); - avatar_preview = {}; - preview_w = 0; - preview_h = 0; - avatar_result_message.clear(); - ImGui::CloseCurrentPopup(); - } - ImGui::EndPopup(); - } -avatar_popup_end: - - // Change Panel - ImGui::SetCursorPosX((WINDOW_SIZE.x / 2.f) + (20.f * SCALE.x)); - if (ImGui::Button("Change Panel", BUTTON_SIZE)) - ImGui::OpenPopup("V3KN Change Panel"); - ImGui::EndDisabled(); - - static std::string selected_panel_path; - static ImGui_Texture panel_preview; - static int panel_preview_w = 0; - static int panel_preview_h = 0; - static std::vector panel_source_data; - static bool is_panel_uploading = false; - static std::string panel_result_message; - static bool panel_upload_success = false; - static float panel_crop_scroll_x = 0.f; - static float panel_crop_scroll_y = 0.f; - static float panel_crop_scale = 1.f; - static float panel_crop_zoom = 1.f; - static bool show_panel_crop_popup = false; - - if (ImGui::BeginPopupModal("V3KN Change Panel", nullptr, popup_flags)) { - const auto title_popup = "Change V3KN Panel"; - ImGui::SetCursorPosX((ImGui::GetWindowWidth() - ImGui::CalcTextSize(title_popup).x) / 2.f); - ImGui::TextColored(GUI_COLOR_TEXT_TITLE, "%s", title_popup); - ImGui::Separator(); - - if (ImGui::Button("Browse...", BUTTON_SIZE)) { - fs::path panel_path{}; - const auto result = host::dialog::filesystem::open_file(panel_path, { { "Image file", { "bmp", "gif", "jpg", "jpeg", "png", "tif" } } }); - if (result == host::dialog::filesystem::Result::SUCCESS && fs::exists(panel_path)) { - selected_panel_path = panel_path.string(); - panel_result_message.clear(); - - int w = 0; - int h = 0; - stbi_uc *data = stbi_load(selected_panel_path.c_str(), &w, &h, nullptr, STBI_rgb_alpha); - if (data) { - panel_preview = ImGui_Texture(gui.imgui_state.get(), data, w, h); - panel_preview_w = w; - panel_preview_h = h; - panel_source_data.assign(data, data + (w * h * 4)); - stbi_image_free(data); - panel_crop_scroll_x = 0.f; - panel_crop_scroll_y = 0.f; - panel_crop_zoom = 1.f; - show_panel_crop_popup = true; - ImGui::CloseCurrentPopup(); - } else { - panel_result_message = "Failed to load image."; - selected_panel_path.clear(); - } - } else if (result == host::dialog::filesystem::Result::ERROR) { - panel_result_message = fmt::format("File dialog error: {}", host::dialog::filesystem::get_error()); - } - } - ImGui::SetCursorPosX((ImGui::GetWindowWidth() - SMALL_BUTTON_SIZE.x) / 2.f); - if (ImGui::Button(CANCEL_STR.c_str(), SMALL_BUTTON_SIZE)) { - selected_panel_path.clear(); - panel_preview = {}; - panel_preview_w = 0; - panel_preview_h = 0; - panel_source_data.clear(); - panel_result_message.clear(); - ImGui::CloseCurrentPopup(); - } - ImGui::EndPopup(); - } - - if (show_panel_crop_popup && !selected_panel_path.empty() && panel_preview && panel_preview_w > 0 && panel_preview_h > 0) { - constexpr float panel_ratio = 360.f / 72.f; - const float panel_width = 720.f * SCALE.x; - const float panel_height = panel_width / panel_ratio; - const ImVec2 crop_popup_size(panel_width + (80.f * SCALE.x), panel_height + (160.f * SCALE.y)); - ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); - ImGui::SetNextWindowSize(crop_popup_size, ImGuiCond_Appearing); - ImGui::OpenPopup("V3KN Panel Crop"); - show_panel_crop_popup = false; - } - - if (ImGui::BeginPopupModal("V3KN Panel Crop", nullptr, popup_flags)) { - const auto title_popup = "Crop V3KN Panel"; - ImGui::SetCursorPosX((ImGui::GetWindowWidth() - ImGui::CalcTextSize(title_popup).x) / 2.f); - ImGui::TextColored(GUI_COLOR_TEXT_TITLE, "%s", title_popup); - ImGui::Separator(); - - if (panel_upload_success) { - panel_upload_success = false; - selected_panel_path.clear(); - panel_preview = {}; - panel_preview_w = 0; - panel_preview_h = 0; - panel_source_data.clear(); - panel_result_message.clear(); - ImGui::CloseCurrentPopup(); - ImGui::EndPopup(); - } else { - const ImVec2 PANEL_SIZE(720.f * SCALE.x, 144.f * SCALE.y); - const float panel_base_scale = PANEL_SIZE.x / static_cast(panel_preview_w); - panel_crop_scale = panel_base_scale * panel_crop_zoom; - float panel_image_height = panel_preview_h * panel_crop_scale; - const bool needs_scroll_y_for_size = panel_image_height > (PANEL_SIZE.y + 0.5f); - const float panel_extra_width = needs_scroll_y_for_size ? ImGui::GetStyle().ScrollbarSize : 0.f; - const ImVec2 panel_crop_size(PANEL_SIZE.x + panel_extra_width, PANEL_SIZE.y); - static float panel_view_width = 0.f; - static float panel_view_height = 0.f; - if (panel_view_width <= 0.f) - panel_view_width = PANEL_SIZE.x; - if (panel_view_height <= 0.f) - panel_view_height = PANEL_SIZE.y; - - ImGui::Text("Selected: %s", fs::path(selected_panel_path).filename().string().c_str()); - const ImVec2 zoom_button_size(40.f * SCALE.x, 40.f * SCALE.y); - const float panel_crop_start_x = (ImGui::GetWindowWidth() - panel_crop_size.x) / 2.f; - const float panel_crop_start_y = ImGui::GetCursorPosY(); - const float zoom_button_y = panel_crop_start_y + ((panel_crop_size.y - zoom_button_size.y) / 2.f); - - float next_scroll_x = panel_crop_scroll_x; - float next_scroll_y = panel_crop_scroll_y; - bool zoom_changed = false; - - const auto update_zoom = [&](float new_zoom) { - new_zoom = std::clamp(new_zoom, 1.f, 4.f); - if (std::abs(new_zoom - panel_crop_zoom) < 0.001f) - return; - - const float old_scale = panel_base_scale * panel_crop_zoom; - const float new_scale = panel_base_scale * new_zoom; - const float center_x = (panel_crop_scroll_x + (panel_view_width * 0.5f)) / old_scale; - const float center_y = (panel_crop_scroll_y + (panel_view_height * 0.5f)) / old_scale; - const float max_scroll_x = std::max(0.f, (panel_preview_w * new_scale) - panel_view_width); - const float max_scroll_y = std::max(0.f, (panel_preview_h * new_scale) - panel_view_height); - - next_scroll_x = std::clamp((center_x * new_scale) - (panel_view_width * 0.5f), 0.f, max_scroll_x); - next_scroll_y = std::clamp((center_y * new_scale) - (panel_view_height * 0.5f), 0.f, max_scroll_y); - panel_crop_zoom = new_zoom; - zoom_changed = true; - }; - - ImGui::SetCursorPos(ImVec2(panel_crop_start_x - zoom_button_size.x - (10.f * SCALE.x), zoom_button_y)); - if (ImGui::Button("-", zoom_button_size)) - update_zoom(panel_crop_zoom - 0.05f); - - ImGui::SetCursorPos(ImVec2(panel_crop_start_x + panel_crop_size.x + (10.f * SCALE.x), zoom_button_y)); - if (ImGui::Button("+", zoom_button_size)) - update_zoom(panel_crop_zoom + 0.05f); - - if (zoom_changed) { - panel_crop_scale = panel_base_scale * panel_crop_zoom; - panel_image_height = panel_preview_h * panel_crop_scale; - } - - ImGui::SetCursorPos(ImVec2(panel_crop_start_x, panel_crop_start_y)); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.f, 0.f)); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.f, 0.f)); - - const float panel_image_width = panel_preview_w * panel_crop_scale; - const bool needs_scroll_x = panel_image_width > (PANEL_SIZE.x + 0.5f); - const bool needs_scroll_y = panel_image_height > (PANEL_SIZE.y + 0.5f); - ImGuiWindowFlags child_flags = 0; - if (!needs_scroll_x && !needs_scroll_y) - child_flags |= ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings; - else if (needs_scroll_x) - child_flags |= ImGuiWindowFlags_HorizontalScrollbar; - - ImGui::BeginChild("V3KN Panel Crop", panel_crop_size, ImGuiChildFlags_Borders, child_flags); - if (zoom_changed) { - ImGui::SetScrollX(next_scroll_x); - ImGui::SetScrollY(next_scroll_y); - } - const ImVec2 content_min = ImGui::GetWindowContentRegionMin(); - const ImVec2 content_max = ImGui::GetWindowContentRegionMax(); - panel_view_width = std::max(0.f, content_max.x - content_min.x); - panel_view_height = std::max(0.f, content_max.y - content_min.y); - ImGui::Image(panel_preview, ImVec2(panel_preview_w * panel_crop_scale, panel_preview_h * panel_crop_scale)); - panel_crop_scroll_x = ImGui::GetScrollX(); - panel_crop_scroll_y = ImGui::GetScrollY(); - ImGui::EndChild(); - ImGui::PopStyleVar(2); - - ImGui::SetCursorPosY(panel_crop_start_y + panel_crop_size.y + ImGui::GetStyle().ItemSpacing.y); - ImGui::SetCursorPosX(ImGui::GetStyle().WindowPadding.x); - - if (!panel_result_message.empty()) { - const auto msg_color = panel_result_message.find("successfully") != std::string::npos - ? ImVec4(0.3f, 1.f, 0.3f, 1.f) - : ImVec4(1.f, 0.3f, 0.3f, 1.f); - ImGui::TextColored(msg_color, "%s", panel_result_message.c_str()); - } - - ImGui::BeginDisabled(is_panel_uploading); - ImGui::SetCursorPosX((ImGui::GetWindowWidth() / 2.f) - SMALL_BUTTON_SIZE.x - (20.f * SCALE.x)); - if (ImGui::Button(is_panel_uploading ? "Uploading..." : OK_STR.c_str(), SMALL_BUTTON_SIZE)) { - is_panel_uploading = true; - panel_result_message.clear(); - - const auto panel_source = panel_source_data; - const auto source_w = panel_preview_w; - const auto source_h = panel_preview_h; - const auto crop_scroll_x = panel_crop_scroll_x; - const auto crop_scroll_y = panel_crop_scroll_y; - const auto crop_scale = panel_crop_scale; - const auto crop_zoom = panel_crop_zoom; - PanelUploadState panel_state{ &panel_result_message, &panel_upload_success, &is_panel_uploading }; - std::thread(upload_v3kn_panel, &gui, &emuenv, &user_info, panel_source, source_w, source_h, crop_scroll_x, crop_scroll_y, crop_scale, crop_zoom, panel_state).detach(); - } - ImGui::EndDisabled(); - ImGui::SameLine(0, 40.f * SCALE.x); - if (ImGui::Button(CANCEL_STR.c_str(), SMALL_BUTTON_SIZE)) { - selected_panel_path.clear(); - panel_preview = {}; - panel_preview_w = 0; - panel_preview_h = 0; - panel_source_data.clear(); - panel_result_message.clear(); - ImGui::CloseCurrentPopup(); - } - ImGui::EndPopup(); - } - } - - // Change server - static int selected_index = static_cast(account_state.selected_server_index); - ImGui::SetCursorPosX((WINDOW_SIZE.x / 2.f) + (20.f * SCALE.x)); - if (ImGui::Button("Change Server", BUTTON_SIZE)) { - selected_index = static_cast(account_state.selected_server_index); - ImGui::OpenPopup("V3KN Change Server"); - } - - if (ImGui::BeginPopupModal("V3KN Change Server", nullptr, popup_flags)) { - const auto title_popup = "Change V3KN Server"; - ImGui::SetCursorPosX((ImGui::GetWindowWidth() - ImGui::CalcTextSize(title_popup).x) / 2.f); - ImGui::TextColored(GUI_COLOR_TEXT_TITLE, "%s", title_popup); - ImGui::Separator(); - ImGui::Combo("Server", &selected_index, V3KN_SERVER_LIST_NAME.data(), static_cast(V3KN_SERVER_LIST_NAME.size())); - ImGui::SetCursorPosX((ImGui::GetWindowWidth() / 2.f) - SMALL_BUTTON_SIZE.x - (20.f * SCALE.x)); - if (ImGui::Button(OK_STR.c_str(), SMALL_BUTTON_SIZE)) { - if (selected_index != static_cast(account_state.selected_server_index)) { - account_state.selected_server_index = static_cast(selected_index); - user_info = {}; - const auto new_index = account_state.selected_server_index; - if (new_index < V3KN_SERVER_LIST_URL.size()) - user_info.host = V3KN_SERVER_LIST_URL[new_index]; - v3kn::set_v3kn_logged_in(false); - v3kn::save_v3kn_user_info(emuenv); - std::thread([&gui, &emuenv]() { - v3kn::init_v3kn_user_info(gui, emuenv); - }).detach(); - } - ImGui::CloseCurrentPopup(); - } - ImGui::SameLine(0, 40.f * SCALE.x); - if (ImGui::Button(CANCEL_STR.c_str(), SMALL_BUTTON_SIZE)) - ImGui::CloseCurrentPopup(); - ImGui::EndPopup(); - } - - ImGui::Spacing(); - ImGui::Separator(); - ImGui::Spacing(); - - const float close_x_pos = (WINDOW_SIZE.x - BUTTON_SIZE.x) / 2.f; - ImGui::SetCursorPosX(close_x_pos); - if (ImGui::Button("Close", BUTTON_SIZE)) { - gui.configuration_menu.v3kn_dialog = false; - } - - ImGui::End(); - ImGui::PopStyleVar(); -} - -} // namespace gui diff --git a/vita3k/gui/src/vita3k_update.cpp b/vita3k/gui/src/vita3k_update.cpp index be8b3bb80..3b62679dc 100644 --- a/vita3k/gui/src/vita3k_update.cpp +++ b/vita3k/gui/src/vita3k_update.cpp @@ -194,11 +194,10 @@ bool init_vita3k_update(GuiState &gui) { static std::atomic progress(0); static std::atomic remaining(0); -static std::atomic downloaded(0); static net_utils::ProgressState progress_state{}; static void download_update(const fs::path &base_path) { - progress_state.canceled = false; + progress_state.download = true; progress_state.pause = false; std::thread download([base_path]() { std::string download_continuous_link = "https://github.com/Vita3K/Vita3K/releases/download/continuous/"; @@ -273,10 +272,9 @@ static void download_update(const fs::path &base_path) { // Download latest Vita3K version LOG_INFO("Attempting to download and extract the latest Vita3K version {} in progress...", git_version); - static const auto progress_callback = [](float updated_progress, uint64_t updated_remaining, uint64_t updated_downloaded) -> net_utils::ProgressState * { + static const auto progress_callback = [](float updated_progress, uint64_t updated_remaining) { progress = updated_progress; remaining = updated_remaining; - downloaded = updated_downloaded; return &progress_state; }; @@ -324,7 +322,7 @@ static void download_update(const fs::path &base_path) { std::system(vita3K_batch.c_str()); #endif } else { - if (!progress_state.canceled) { + if (progress_state.download) { LOG_WARN("Download failed, please try again later."); state = NOT_COMPLETE_UPDATE; } else @@ -515,7 +513,7 @@ void draw_vita3k_update(GuiState &gui, EmuEnvState &emuenv) { ImGui::SameLine(0, 40.f * SCALE.x); if (ImGui::Button(common["yes"].c_str(), LARGE_BUTTON_SIZE)) { std::unique_lock lock(progress_state.mutex); - progress_state.canceled = true; + progress_state.download = false; progress_state.pause = false; // Unpause so it gets handled by the callback progress_state.cv.notify_one(); diff --git a/vita3k/lang/include/lang/state.h b/vita3k/lang/include/lang/state.h index 7e3672d9b..2eaa8526a 100644 --- a/vita3k/lang/include/lang/state.h +++ b/vita3k/lang/include/lang/state.h @@ -33,7 +33,6 @@ struct DialogLangState { std::map common = { { "an_error_occurred", "An error occurred.\nError code: {}" }, { "cancel", "Cancel" }, - { "connecting_please_wait", "Connecting...\nPlease wait." }, { "close", "Close" }, { "could_not_load", "Could not load the file." }, { "could_not_save", "Could not save the file." }, @@ -48,11 +47,7 @@ struct DialogLangState { { "select_all", "Select All" }, { "select", "Select" }, { "submit", "Submit" }, - { "yes", "Yes" }, - { "could_not_connect_server", "Could not connect to the server." }, - { "must_sign_in_vita3k_network", "You must sign in to Vita3K Network to use this feature." }, - { "could_not_connect_internet", "Could not connect to the Internet." }, - { "vita3k_network_connection_lost", "The connection to Vita3K Network has been lost." } + { "yes", "Yes" } }; std::map message = { { "load_app_failed", "Failed to load \"{}\".\nCheck vita3k.log to see console output for details.\n1. Have you installed the firmware?\n2. Re-dump your own PS Vita app/game and install it on Vita3K.\n3. If you want to install or boot Vitamin, it is not supported." } }; std::map trophy = { { "preparing_start_app", "Preparing to start the application..." } }; @@ -474,26 +469,6 @@ struct LangState { }; }; LiveArea live_area; - std::map online_storage = { - { "save_data_auto_upload", "Saved Data Auto - Upload" }, - { "copying", "Copying..." }, - { "download", "Download" }, - { "eligible", "Eligible" }, - { "ineligible", "Ineligible" }, - { "location", "Location" }, - { "memory_card", "Memory Card" }, - { "minutes_left", "{} Minutes Left" }, - { "online_storage", "Online Storage" }, - { "overwrite_saved_data", "Do you want to overwrite the saved data ?" }, - { "ps_vita_system", "PS Vita System" }, - { "ps_tv_system", "PS TV System" }, - { "seconds_left", "{} Seconds Left" }, - { "size", "Size" }, - { "trophy_earning", "Trophy Earning" }, - { "updated", "Updated" }, - { "upload", "Upload" }, - { "uploaded", "Uploaded" } - }; std::map overlay = { { "title", "Overlay" }, { "gamepad_overlay", "Gamepad Overlay" }, diff --git a/vita3k/lang/src/lang.cpp b/vita3k/lang/src/lang.cpp index f2d3f766e..1efcf8327 100644 --- a/vita3k/lang/src/lang.cpp +++ b/vita3k/lang/src/lang.cpp @@ -313,9 +313,6 @@ void init_lang(LangState &lang, EmuEnvState &emuenv) { // Message set_lang_string(emuenv.common_dialog.lang.message, lang_child.child("message")); - // Online Storage - set_lang_string(lang.online_storage, lang_child.child("online_storage")); - // Overlay set_lang_string(lang.overlay, lang_child.child("overlay")); diff --git a/vita3k/util/include/util/hash.h b/vita3k/util/include/util/hash.h index cd207bf91..08ac280e2 100644 --- a/vita3k/util/include/util/hash.h +++ b/vita3k/util/include/util/hash.h @@ -23,8 +23,6 @@ using Sha256Hash = std::array; -std::string base64_encode(const std::string &input); -std::string derive_password(const std::string_view &password); Sha256Hash sha256(const void *data, size_t size); typedef std::array Sha256HashText; diff --git a/vita3k/util/include/util/net_utils.h b/vita3k/util/include/util/net_utils.h index a2665b3cd..ee9e8be81 100644 --- a/vita3k/util/include/util/net_utils.h +++ b/vita3k/util/include/util/net_utils.h @@ -3,7 +3,6 @@ #include #include -#include #include #include #include @@ -37,7 +36,7 @@ struct parsedUrl { struct ProgressState { std::condition_variable cv; std::mutex mutex; - bool canceled = false; + bool download = true; bool pause = false; }; @@ -52,31 +51,12 @@ struct AssignedAddr { std::string netMask; // Network mask }; -typedef const std::function &ProgressCallback; +typedef const std::function &ProgressCallback; typedef std::pair CallbackData; -struct WebResponse { - int curl_res = 0; - std::string body; -}; - -struct CurlSession { - void *handle = nullptr; - void *headers = nullptr; -}; - bool download_file(const std::string &url, const std::string &output_file_path, ProgressCallback progress_callback = nullptr); -WebResponse download_file_ex(const std::string &url, const std::string &output_file_path, ProgressCallback progress_callback = nullptr, const std::string &token = "", CurlSession *session = nullptr); std::string get_web_response(const std::string &url); -WebResponse get_web_response_ex(const std::string &url, const std::string &token = "", const std::string &post_data = "", CurlSession *session = nullptr, const std::atomic *cancel_flag = nullptr); std::string get_web_regex_result(const std::string &url, const std::regex ®ex); -std::vector get_web_regex_results(const std::string &url, const std::regex ®ex); -WebResponse upload_file(const std::string &url, const std::string &input_file_path, const std::string &token, const std::string &metadata = "", ProgressCallback progress_callback = nullptr, CurlSession *session = nullptr); -WebResponse upload_data(const std::string &url, const std::vector &data, const std::string &filename, const std::string &token, CurlSession *session = nullptr); - -CurlSession init_curl_download_session(const std::string &token, bool is_keep_alive = true); -CurlSession init_curl_upload_session(const std::string &token, bool is_keep_alive = true); -void cleanup_curl_session(CurlSession &session); SceHttpErrorCode parse_url(const std::string &url, parsedUrl &out); const char *int_method_to_char(const int n); diff --git a/vita3k/util/src/hash.cpp b/vita3k/util/src/hash.cpp index cca793d8a..8c66de2c2 100644 --- a/vita3k/util/src/hash.cpp +++ b/vita3k/util/src/hash.cpp @@ -15,67 +15,10 @@ // with this program; if not, write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -#include - #include #include -static const char b64_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - -std::string base64_encode(const std::string &input) { - const unsigned char *data = reinterpret_cast(input.data()); - size_t len = input.size(); - std::string out; - out.reserve(((len + 2) / 3) * 4); - - for (size_t i = 0; i < len; i += 3) { - unsigned int val = data[i] << 16; - if (i + 1 < len) - val |= data[i + 1] << 8; - if (i + 2 < len) - val |= data[i + 2]; - - out.push_back(b64_table[(val >> 18) & 0x3F]); - out.push_back(b64_table[(val >> 12) & 0x3F]); - out.push_back((i + 1 < len) ? b64_table[(val >> 6) & 0x3F] : '='); - out.push_back((i + 2 < len) ? b64_table[val & 0x3F] : '='); - } - - return out; -} - -std::string derive_password(const std::string_view &password) { - const std::string_view salt_str = "No matter where you store, everyone has access somewhere."; - - // Size of SHA3-256 digest - constexpr int digest_len = 32; // 256 bits - - unsigned char derived_password_digest[digest_len]; - - // PBKDF2-HMAC-SHA3-256 - if (!PKCS5_PBKDF2_HMAC( - password.data(), - static_cast(password.size()), - reinterpret_cast(salt_str.data()), - static_cast(salt_str.size()), - 200000, // number of iterations - EVP_sha3_256(), // SHA3-256 algorithm - digest_len, - derived_password_digest)) { - throw std::runtime_error("PBKDF2 failed"); - } - - // Conversion to hex - std::string derived_password; - derived_password.reserve(digest_len * 2); - for (int i = 0; i < digest_len; i++) { - derived_password += fmt::format("{:02X}", derived_password_digest[i]); - } - - return derived_password; -} - Sha256Hash sha256(const void *data, size_t size) { Sha256Hash hash; diff --git a/vita3k/util/src/net_utils.cpp b/vita3k/util/src/net_utils.cpp index 3b1730e79..ac3b9c33b 100644 --- a/vita3k/util/src/net_utils.cpp +++ b/vita3k/util/src/net_utils.cpp @@ -334,89 +334,41 @@ bool socketSetBlocking(int sockfd, bool blocking) { return true; } -static int curl_cancel_callback(void *user_data, curl_off_t, curl_off_t, curl_off_t, curl_off_t) { - const auto *cancel_flag = static_cast *>(user_data); - return (cancel_flag && cancel_flag->load()) ? 1 : 0; -} - -WebResponse get_web_response_ex(const std::string &url, const std::string &token, const std::string &post_data, CurlSession *session, const std::atomic *cancel_flag) { - WebResponse web_response{}; - - const bool has_session_given = session; - - CurlSession local_session; - - if (!has_session_given) { - local_session = init_curl_download_session(token, false); - if (!local_session.handle) { - web_response.curl_res = CURLE_FAILED_INIT; - return web_response; - } - session = &local_session; - } - - CURL *curl = static_cast(session->handle); - curl_slist *headers = static_cast(session->headers); +std::string get_web_response(const std::string &url) { + auto curl = curl_easy_init(); + if (!curl) + return {}; curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); - if (cancel_flag) { - curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); - curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, curl_cancel_callback); - curl_easy_setopt(curl, CURLOPT_XFERINFODATA, cancel_flag); - } else { - curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L); - } + curl_easy_setopt(curl, CURLOPT_USERAGENT, "Vita3K Emulator"); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, true); // Follow redirects + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L); #ifdef __ANDROID__ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); #endif - if (!post_data.empty()) { - curl_easy_setopt(curl, CURLOPT_POST, 1L); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data.c_str()); - curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, post_data.size()); - } - + std::string response_string; const auto writeFunc = +[](void *ptr, size_t size, size_t nmemb, std::string *data) { data->append((char *)ptr, size * nmemb); return size * nmemb; }; curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeFunc); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &web_response.body); - - if (headers) - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); - - web_response.curl_res = curl_easy_perform(curl); - + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_string); long response_code; + curl_easy_perform(curl); curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); - if (!has_session_given) - cleanup_curl_session(local_session); + curl_easy_cleanup(curl); if (response_code / 100 == 2) - return web_response; + return response_string; - web_response.body.clear(); - return web_response; -} - -std::string get_web_response(const std::string &url) { - const auto response = get_web_response_ex(url, ""); - return response.body; + return {}; } std::string get_web_regex_result(const std::string &url, const std::regex ®ex) { - const auto result = get_web_regex_results(url, regex); - if (result.empty()) - return {}; - - return result.back(); -} - -std::vector get_web_regex_results(const std::string &url, const std::regex ®ex) { - std::vector results; + std::string result; // Get the response of the web const auto response = get_web_response(url); @@ -426,13 +378,12 @@ std::vector get_web_regex_results(const std::string &url, const std std::smatch match; // Check if the response matches the regex if (std::regex_search(response, match, regex)) { - for (size_t i = 1; i < match.size(); ++i) - results.push_back(match[i].str()); + result = match[1]; } else LOG_ERROR("No success found regex: {}", response); } - return results; + return result; } static uint64_t get_current_time_ms() { @@ -444,85 +395,56 @@ static size_t write_data(void *ptr, size_t size, size_t nmemb, void *stream) { return written; } -static int curl_callback(void *user_data, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { - auto *data = (CallbackData *)user_data; +typedef int (*CurlDownloadCallback)(void *c, long t, long d); - // Detect mode (download/upload) - const bool is_download = (dltotal > 0); - const bool is_upload = (ultotal > 0); +bool download_file(const std::string &url, const std::string &output_file_path, ProgressCallback progress_callback) { + CURL *curl_download = curl_easy_init(); + if (!curl_download) + return false; - // Skip if curl doesn't have info yet - if (!is_download && !is_upload) - return 0; + CurlDownloadCallback curl_callback = +[](void *user_data, long total_bytes, long downloaded_bytes) { + const auto data = (CallbackData *)user_data; - if (!data->second) - return 0; + if ((total_bytes == 0) || (downloaded_bytes == 0)) + return 0; // Ignore if we dont have the total yet or if we haven't downloaded anything - float progress = 0.f; - double bytes_done = 0; - double bytes_total = 0; + if (!data->second) + return 0; - if (is_download) { - bytes_done = static_cast(dlnow); - bytes_total = static_cast(dltotal); - const auto total_bytes_done = static_cast(dlnow + data->first.bytes_already_downloaded); - const auto file_size = static_cast(dltotal + data->first.bytes_already_downloaded); - progress = static_cast(total_bytes_done) / (static_cast(file_size)); - } else if (is_upload) { - bytes_done = static_cast(ulnow); - bytes_total = static_cast(ultotal); - progress = static_cast(bytes_done) / static_cast(bytes_total); - } + // Calculate progress percentage + const auto total_bytes_downloaded = static_cast(downloaded_bytes + data->first.bytes_already_downloaded); + const auto file_size = total_bytes + data->first.bytes_already_downloaded; + const auto progress_percent = static_cast(total_bytes_downloaded) / static_cast(file_size) * 100.0f; - if (bytes_done == 0) - return 0; + // Calculate elapsed time + const auto current_time = get_current_time_ms(); + const auto elapsed_time_ms = std::difftime(current_time, data->first.time); - // Calculate elapsed time - const auto current_time = get_current_time_ms(); - const auto elapsed_time_ms = std::difftime(current_time, data->first.time); + // Calculate remaining time in seconds + const auto remaining_bytes = static_cast(total_bytes - downloaded_bytes); + const auto remaining_time = static_cast((remaining_bytes / downloaded_bytes) * elapsed_time_ms) / 1000; - // Calculate remaining time in seconds - const auto remaining_bytes = static_cast(bytes_total - bytes_done); - const uint64_t remaining_time = static_cast((remaining_bytes / (double)bytes_done) * elapsed_time_ms) / 1000; + ProgressState *callback_result = data->second(progress_percent, remaining_time); - // Callback UI - ProgressState *callback_result = data->second(progress, remaining_time, bytes_done); + std::unique_lock lock(callback_result->mutex); - std::unique_lock lock(callback_result->mutex); + // Store the current pause state + const auto pause = callback_result->pause; - const bool was_paused = callback_result->pause; + // Wait until the pause state becomes false (unpaused) + callback_result->cv.wait(lock, [&]() { + return !callback_result->pause; + }); - callback_result->cv.wait(lock, [&]() { - return !callback_result->pause; - }); + // When coming out of pause, add the time spent to the start time to keep the time consistent + if (pause) + data->first.time += std::difftime(get_current_time_ms(), current_time); - if (was_paused) - data->first.time += std::difftime(get_current_time_ms(), current_time); - - if (callback_result->canceled) - return 1; - - return 0; -} - -WebResponse download_file_ex(const std::string &url, const std::string &output_file_path, ProgressCallback progress_callback, const std::string &token, CurlSession *session) { - WebResponse web_response{}; - - CurlSession local_session; - - const bool has_session_given = session; - - if (!has_session_given) { - local_session = init_curl_download_session(token); - if (!local_session.handle) { - web_response.curl_res = CURLE_FAILED_INIT; - return web_response; + if (!callback_result->download) { + return 1; // Returning anything that's not 0 aborts the request } - session = &local_session; - } - - CURL *curl_download = static_cast(session->handle); - curl_slist *headers = static_cast(session->headers); + return 0; + }; const auto start_time = get_current_time_ms(); const uint64_t bytes_already_downloaded = fs::exists(output_file_path) ? fs::file_size(output_file_path) : 0; @@ -534,232 +456,33 @@ WebResponse download_file_ex(const std::string &url, const std::string &output_f curl_easy_setopt(curl_download, CURLOPT_RESUME_FROM_LARGE, bytes_already_downloaded); curl_easy_setopt(curl_download, CURLOPT_XFERINFODATA, &callbackData); curl_easy_setopt(curl_download, CURLOPT_XFERINFOFUNCTION, curl_callback); + curl_easy_setopt(curl_download, CURLOPT_FOLLOWLOCATION, true); // Follow redirects - if (headers) - curl_easy_setopt(curl_download, CURLOPT_HTTPHEADER, headers); +#ifdef __ANDROID__ + curl_easy_setopt(curl_download, CURLOPT_SSL_VERIFYPEER, 0L); +#endif auto fp = fopen(output_file_path.c_str(), "ab"); if (!fp) { LOG_CRITICAL("Could not fopen file {}", output_file_path); - if (!has_session_given) - cleanup_curl_session(local_session); + curl_easy_cleanup(curl_download); if (fs::exists(output_file_path)) fs::remove(output_file_path); - web_response.curl_res = CURLE_FAILED_INIT; - return web_response; + return false; } curl_easy_setopt(curl_download, CURLOPT_WRITEDATA, fp); - web_response.curl_res = curl_easy_perform(curl_download); - - long http_code = 0; - curl_easy_getinfo(curl_download, CURLINFO_RESPONSE_CODE, &http_code); - if ((http_code != 200) && (http_code != 206)) { - LOG_WARN("Download failed with HTTP code: {}", http_code); - fclose(fp); - if (!has_session_given) - cleanup_curl_session(local_session); - if (fs::exists(output_file_path)) - fs::remove(output_file_path); - web_response.curl_res = CURLE_HTTP_RETURNED_ERROR; - return web_response; - } + int res = curl_easy_perform(curl_download); fclose(fp); + curl_easy_cleanup(curl_download); + if (progress_callback) + progress_callback(0, 0); - if (!has_session_given) - cleanup_curl_session(local_session); + if (res == CURLE_ABORTED_BY_CALLBACK) + LOG_CRITICAL("Aborted update by user"); - if (web_response.curl_res == CURLE_ABORTED_BY_CALLBACK) - LOG_WARN("Aborted by user"); - - return web_response; -} - -bool download_file(const std::string &url, const std::string &output_file_path, ProgressCallback progress_callback) { - return download_file_ex(url, output_file_path, progress_callback).curl_res == CURLE_OK; -} - -static size_t write_string(void *ptr, size_t size, size_t nmemb, void *userdata) { - std::stringstream *ss = static_cast(userdata); - ss->write(static_cast(ptr), size * nmemb); - return size * nmemb; -} - -static WebResponse upload_impl(const std::string &url, const char *data, size_t data_size, const std::string &filename, const std::string &token, const std::string &metadata, ProgressCallback progress_callback, CurlSession *session) { - WebResponse response{}; - - const bool has_session_given = session; - - CurlSession local_session; - - if (!has_session_given) { - local_session = init_curl_upload_session(token, false); - if (!local_session.handle) { - response.curl_res = CURLE_FAILED_INIT; - return response; - } - session = &local_session; - } - - CURL *curl_upload = static_cast(session->handle); - curl_slist *headers = static_cast(session->headers); - - curl_easy_setopt(curl_upload, CURLOPT_URL, url.c_str()); - - const auto start_time = get_current_time_ms(); - const auto callbackData = CallbackData({ start_time }, progress_callback); - curl_easy_setopt(curl_upload, CURLOPT_NOPROGRESS, progress_callback ? false : true); - if (progress_callback) { - curl_easy_setopt(curl_upload, CURLOPT_XFERINFODATA, &callbackData); - curl_easy_setopt(curl_upload, CURLOPT_XFERINFOFUNCTION, curl_callback); - } - - std::stringstream response_stream; - curl_easy_setopt(curl_upload, CURLOPT_WRITEFUNCTION, write_string); - curl_easy_setopt(curl_upload, CURLOPT_WRITEDATA, &response_stream); - - if (headers) - curl_easy_setopt(curl_upload, CURLOPT_HTTPHEADER, headers); - - curl_mime *mime = curl_mime_init(curl_upload); - curl_mimepart *part = nullptr; - - part = curl_mime_addpart(mime); - curl_mime_name(part, "file"); - curl_mime_filename(part, filename.c_str()); - curl_mime_data(part, data, data_size); - - if (!metadata.empty()) { - part = curl_mime_addpart(mime); - curl_mime_name(part, "xml"); - curl_mime_data(part, metadata.c_str(), CURL_ZERO_TERMINATED); - } - - curl_easy_setopt(curl_upload, CURLOPT_MIMEPOST, mime); - - response.curl_res = curl_easy_perform(curl_upload); - response.body = response_stream.str(); - - curl_mime_free(mime); - - if (!has_session_given) - cleanup_curl_session(local_session); - - return response; -} - -WebResponse upload_file(const std::string &url, const std::string &input_file_path, const std::string &token, const std::string &metadata, ProgressCallback progress_callback, CurlSession *session) { - WebResponse response{}; - - std::ifstream file(input_file_path, std::ios::binary); - if (!file) { - LOG_CRITICAL("Could not open file {}", input_file_path); - response.curl_res = CURLE_READ_ERROR; - return response; - } - - std::vector file_buffer((std::istreambuf_iterator(file)), - std::istreambuf_iterator()); - - return upload_impl(url, file_buffer.data(), file_buffer.size(), fs::path(input_file_path).filename().string(), token, metadata, progress_callback, session); -} - -WebResponse upload_data(const std::string &url, const std::vector &data, const std::string &filename, const std::string &token, CurlSession *session) { - WebResponse response{}; - - if (data.empty()) { - response.curl_res = CURLE_READ_ERROR; - return response; - } - - return upload_impl(url, reinterpret_cast(data.data()), data.size(), filename, token, "", nullptr, session); -} - -CurlSession init_curl_download_session(const std::string &token, bool is_keep_alive) { - CurlSession session; - session.handle = curl_easy_init(); - - if (!session.handle) - return session; - - CURL *curl = static_cast(session.handle); - - std::string os; -#if defined(_WIN32) - os = "Windows"; -#elif defined(__ANDROID__) - os = "Android"; -#elif defined(__linux__) - os = "Linux"; -#elif defined(__APPLE__) - os = "macOS"; -#else - os = "Unknown OS"; -#endif - - curl_easy_setopt(curl, CURLOPT_USERAGENT, ("Vita3K Emulator on (" + os + ")").c_str()); - curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, true); - if (is_keep_alive) { - curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L); - curl_easy_setopt(curl, CURLOPT_TCP_KEEPIDLE, 120L); - curl_easy_setopt(curl, CURLOPT_TCP_KEEPINTVL, 60L); - } - -#ifdef __ANDROID__ - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); -#endif - - if (!token.empty()) { - curl_slist *headers = nullptr; - std::string auth_header = "Authorization: Bearer " + token; - headers = curl_slist_append(headers, auth_header.c_str()); - session.headers = headers; - } - - return session; -} - -CurlSession init_curl_upload_session(const std::string &token, bool is_keep_alive) { - CurlSession session; - session.handle = curl_easy_init(); - - if (!session.handle) - return session; - - CURL *curl = static_cast(session.handle); - - curl_easy_setopt(curl, CURLOPT_USERAGENT, "Vita3K Emulator"); - - if (is_keep_alive) { - curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L); - curl_easy_setopt(curl, CURLOPT_TCP_KEEPIDLE, 120L); - curl_easy_setopt(curl, CURLOPT_TCP_KEEPINTVL, 60L); - } - -#ifdef __ANDROID__ - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); -#endif - - if (!token.empty()) { - curl_slist *headers = nullptr; - std::string auth_header = "Authorization: Bearer " + token; - headers = curl_slist_append(headers, auth_header.c_str()); - session.headers = headers; - } - - return session; -} - -void cleanup_curl_session(CurlSession &session) { - if (session.headers) { - curl_slist_free_all(static_cast(session.headers)); - session.headers = nullptr; - } - if (session.handle) { - curl_easy_cleanup(static_cast(session.handle)); - session.handle = nullptr; - } + return res == CURLE_OK; } std::vector get_all_assigned_addrs() { diff --git a/vita3k/v3kn/CMakeLists.txt b/vita3k/v3kn/CMakeLists.txt deleted file mode 100644 index c80bf86ac..000000000 --- a/vita3k/v3kn/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -add_library( - v3kn - STATIC - include/v3kn/account.h - include/v3kn/state.h - include/v3kn/storage.h - src/account.cpp - src/storage.cpp -) - -target_include_directories(v3kn PUBLIC include) -target_link_libraries(v3kn PUBLIC gui emuenv util packages) -target_link_libraries(v3kn PRIVATE pugixml::pugixml miniz stb) diff --git a/vita3k/v3kn/include/v3kn/account.h b/vita3k/v3kn/include/v3kn/account.h deleted file mode 100644 index b6a0888bb..000000000 --- a/vita3k/v3kn/include/v3kn/account.h +++ /dev/null @@ -1,71 +0,0 @@ -// 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. - -#pragma once - -#include - -#include -#include -#include -#include -#include - -struct EmuEnvState; -struct GuiState; - -inline const std::vector V3KN_SERVER_LIST_NAME = { "Official V3KN Server", "Local V3KN Test Server" }; -inline const std::vector V3KN_SERVER_LIST_URL = { "np.vita3k.org", "localhost:3000" }; -inline const std::map V3KN_SERVER_PROTOCOL = { - { "np.vita3k.org", "https://" }, - { "localhost:3000", "http://" }, -}; - -struct UserInfo { - std::string host = V3KN_SERVER_LIST_URL[0]; - std::string online_id; - std::string password; - std::string token; - time_t created_at = 0; - uint64_t quota_used = 0; - uint64_t quota_total = 0; -}; - -namespace v3kn { - -void set_v3kn_logged_in(bool logged_in); -bool is_v3kn_logged_in(); -bool wait_for_v3kn_login(uint32_t timeout_ms); -void handle_v3kn_status(EmuEnvState &emuenv, const net_utils::WebResponse &response); -std::string get_v3kn_error_message(EmuEnvState &emuenv, const net_utils::WebResponse &response); -std::string get_v3kn_server_url(const std::string &host, const std::string &path); - -net_utils::WebResponse v3kn_create(const UserInfo &user_info, const std::string &online_id, const std::string &password); -net_utils::WebResponse v3kn_login(const UserInfo &user_info, const std::string &online_id, const std::string &password); -net_utils::WebResponse v3kn_delete(const UserInfo &user_info, const std::string &password); -net_utils::WebResponse v3kn_change_online_id(const UserInfo &user_info, const std::string &new_online_id); -net_utils::WebResponse v3kn_change_password(const UserInfo &user_info, const std::string &old_password, const std::string &new_password); -net_utils::WebResponse v3kn_change_about_me(const UserInfo &user_info, const std::string &about_me); -net_utils::WebResponse v3kn_avatar_upload(const UserInfo &user_info, const std::string &file_path); -net_utils::WebResponse v3kn_avatar_upload(const UserInfo &user_info, const std::vector &png_data); -net_utils::WebResponse v3kn_avatar_download(const UserInfo &user_info, const std::string &target_online_id, net_utils::CurlSession *session = nullptr); -net_utils::WebResponse v3kn_panel_upload(const UserInfo &user_info, const std::vector &png_data); -net_utils::WebResponse v3kn_panel_download(const UserInfo &user_info, const std::string &target_online_id, net_utils::CurlSession *session = nullptr); -void save_v3kn_user_info(EmuEnvState &emuenv); -void init_v3kn_user_info(GuiState &gui, EmuEnvState &emuenv); - -} // namespace v3kn diff --git a/vita3k/v3kn/include/v3kn/state.h b/vita3k/v3kn/include/v3kn/state.h deleted file mode 100644 index 908f85853..000000000 --- a/vita3k/v3kn/include/v3kn/state.h +++ /dev/null @@ -1,59 +0,0 @@ -// 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. - -#pragma once - -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -struct AccountState { - UserInfo user_info; - std::atomic is_logged_in{ false }; - uint32_t selected_server_index = 0; -}; - -struct StorageState { - OnlineStorageState online_storage_state = ONLINE_STORAGE_SELECT; - std::map savedata_info; - std::atomic progress{ 0 }; - std::atomic remaining{ 0 }; - std::atomic bytes_done{ 0 }; - net_utils::ProgressState progress_state{}; - - uint64_t progress_done_timestamp = 0; - uint64_t please_wait_timestamp = 0; - std::atomic please_wait_done{ false }; - std::mutex mutex_progress; - std::condition_variable cv_progress; -}; - -struct V3KNState { - AccountState account_state; - StorageState storage_state; -}; diff --git a/vita3k/v3kn/include/v3kn/storage.h b/vita3k/v3kn/include/v3kn/storage.h deleted file mode 100644 index f7c3ec354..000000000 --- a/vita3k/v3kn/include/v3kn/storage.h +++ /dev/null @@ -1,83 +0,0 @@ -// 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. - -#pragma once - -#include - -#include -#include -#include -#include -#include - -struct EmuEnvState; -struct GuiState; - -struct SaveEntry { - std::string rel_path; - time_t last_updated; -}; - -struct SaveDataInfo { - time_t last_updated; - time_t last_uploaded; - uint64_t total_size; - std::vector files; -}; - -struct TrophySync { - time_t last_updated; - std::vector unlocked_ids; - uint32_t platinum = 0; - uint32_t gold = 0; - uint32_t silver = 0; - uint32_t bronze = 0; - uint32_t progress = 0; -}; - -struct TrophyProgressInfo { - std::map progress = {}; - std::map> unlocked_ids = {}; - std::map> unlocked_type_count = {}; - time_t last_updated; -}; - -enum SaveDataType { - SAVE_DATA_TYPE_CLOUD = 0, - SAVE_DATA_TYPE_LOCAL = 1, -}; - -enum OnlineStorageState { - ONLINE_STORAGE_SELECT, - ONLINE_STORAGE_UPLOAD, - ONLINE_STORAGE_DOWNLOAD, - ONLINE_STORAGE_COPYING, -}; - -bool load_and_compute_trophy_stats(EmuEnvState &emuenv, np::trophy::Context &context, const std::string &np_com_id, TrophyProgressInfo &trophy_progress_info); - -namespace v3kn { -void start_trophy_sync(GuiState &gui, EmuEnvState &emuenv, std::map &trophy_progress, std::atomic &sync_progress, std::atomic &sync_cancel, std::vector &sync_downloaded); -void start_trophy_auto_sync(GuiState &gui, EmuEnvState &emuenv); -void add_pending_trophy_sync(EmuEnvState &emuenv, const std::string &np_com_id); -void download_savedata(GuiState &gui, EmuEnvState &emuenv, const std::string &titleid); -void upload_savedata(GuiState &gui, EmuEnvState &emuenv, const std::string &titleid); -void open_online_storage(GuiState &gui, EmuEnvState &emuenv, const std::string &titleid); -void sync_game_stitle(EmuEnvState &emuenv); - -} // namespace v3kn diff --git a/vita3k/v3kn/src/account.cpp b/vita3k/v3kn/src/account.cpp deleted file mode 100644 index 188aad661..000000000 --- a/vita3k/v3kn/src/account.cpp +++ /dev/null @@ -1,386 +0,0 @@ -// 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 -#include - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include - -namespace v3kn { - -static std::atomic is_v3kn_user_logged_in(false); -static std::condition_variable v3kn_login_cv; -static std::mutex v3kn_login_mutex; -static bool v3kn_login_init_completed = false; -constexpr char V3KN_VERSION[] = "0.01"; - -static fs::path get_v3kn_avatar_cache_path(const EmuEnvState &emuenv, const UserInfo &user_info, const std::string &online_id) { - return emuenv.cache_path / "v3kn" / user_info.host / online_id / "avatar.png"; -} - -static bool load_v3kn_avatar_texture(GuiState &gui, const fs::path &avatar_path) { - if (!fs::exists(avatar_path)) - return false; - - int width = 0; - int height = 0; - stbi_uc *data = stbi_load(avatar_path.string().c_str(), &width, &height, nullptr, STBI_rgb_alpha); - if (!data) - return false; - - gui.v3kn_avatar = ImGui_Texture(gui.imgui_state.get(), data, width, height); - stbi_image_free(data); - return true; -} - -static void init_v3kn_avatar(GuiState &gui, EmuEnvState &emuenv, const UserInfo &user_info) { - gui.v3kn_avatar = {}; - - if (user_info.online_id.empty() || user_info.token.empty()) - return; - - const fs::path avatar_cache_path = get_v3kn_avatar_cache_path(emuenv, user_info, user_info.online_id); - if (load_v3kn_avatar_texture(gui, avatar_cache_path)) - return; - - fs::create_directories(avatar_cache_path.parent_path()); - - const auto res = v3kn_avatar_download(user_info, user_info.online_id); - handle_v3kn_status(emuenv, res); - if (!res.body.starts_with("OK") || res.body.empty()) - return; - - fs_utils::dump_data(avatar_cache_path, res.body.data(), static_cast(res.body.size())); - load_v3kn_avatar_texture(gui, avatar_cache_path); -} - -static void set_v3kn_login_init_completed(bool completed) { - { - std::lock_guard lock(v3kn_login_mutex); - v3kn_login_init_completed = completed; - } - - v3kn_login_cv.notify_all(); -} - -void set_v3kn_logged_in(bool logged_in) { - is_v3kn_user_logged_in.store(logged_in); - v3kn_login_cv.notify_all(); -} - -bool is_v3kn_logged_in() { - return is_v3kn_user_logged_in.load(); -} - -bool wait_for_v3kn_login(uint32_t timeout_ms) { - std::unique_lock lock(v3kn_login_mutex); - v3kn_login_cv.wait_for(lock, std::chrono::milliseconds(timeout_ms), [] { - return is_v3kn_user_logged_in.load() || v3kn_login_init_completed; - }); - - return is_v3kn_user_logged_in.load(); -} - -std::string get_v3kn_error_message(EmuEnvState &emuenv, const net_utils::WebResponse &response) { - std::string error_msg; - if (response.body.starts_with("ERR:") || !response.body.starts_with("OK:")) { - if (response.body == "ERR:MissingToken") - error_msg = emuenv.common_dialog.lang.common["must_sign_in_vita3k_network"]; - else if (response.body == "ERR:InvalidToken") - error_msg = "Your session on Vita3K Network has expired. Please sign in again."; - else if (response.body == "ERR:MissingTitleID") - error_msg = "V3KN get error Missing title ID."; - else if (response.body == "ERR:UserExists") - error_msg = "V3KN create account error User already exists."; - else if (response.body == "ERR:MissingPassword") - error_msg = "V3KN get error Missing password."; - else if (response.body == "ERR:InvalidOnlineID") - error_msg = "Invalid Online ID for V3KN.\nMake sure it follows the required format."; - else if (response.body == "ERR:InvalidPassword") - error_msg = "V3KN login/change password/delete error Invalid password."; - else if (response.body == "ERR:ServerError") - error_msg = "V3KN server error."; - else if (response.body == "ERR:QuotaExceeded") - error_msg = "V3KN error Quota exceeded."; - else if (response.body == "ERR:UserNotFound") - error_msg = "V3KN login error User not found."; - else if (response.body == "ERR:InvalidType") - error_msg = "V3KN error Invalid type of download file."; - else if (response.body == "ERR:InvalidID") - error_msg = "V3KN error Invalid ID of App."; - else if (response.body == "ERR:FileNotFound") - error_msg = "V3KN error File not found."; - else if (response.body == "ERR:MissingNewPassword") - error_msg = "V3KN change password error Missing new password."; - else if (response.body == "ERR:MissingOldPassword") - error_msg = "V3KN change password error Missing old password."; - else if (response.body == "ERR:SamePassword") - error_msg = "V3KN change password error New password cannot be same as the old password."; - else if (response.body == "ERR:MissingTargetOnlineId") - error_msg = "V3KN error Missing target online ID."; - else if (response.body == "ERR:AlreadyFriends") - error_msg = "V3KN error Already friends with this user."; - else if (response.body == "ERR:RequestAlreadySent") - error_msg = "V3KN error Friend request already sent to this user."; - else if (response.body == "ERR:NoRequestFound") - error_msg = "V3KN error No friend request found from this user."; - else if (response.body == "ERR:CannotAddYourself") - error_msg = "V3KN error Cannot add yourself as a friend."; - else if (response.body == "ERR:AboutMeTooLong") - error_msg = "V3KN error 'About Me' text is too long."; - else if (response.body == "ERR:OutdatedClient") - error_msg = "Outdated client version. Please update Vita3K to the latest version."; - else if (response.body.empty()) { - if (response.curl_res == 3) - error_msg = "V3KN get error Malformed URL, check server URL."; - else if (response.curl_res == 6) - error_msg = emuenv.common_dialog.lang.common["could_not_connect_internet"]; - else if (response.curl_res == 7) - error_msg = emuenv.common_dialog.lang.common["could_not_connect_server"]; - else if ((response.curl_res == 55) || (response.curl_res == 56)) - error_msg = emuenv.common_dialog.lang.common["vita3k_network_connection_lost"]; - else - error_msg = fmt::format("V3KN get Unexpected server error: {}", response.curl_res); - } else { - error_msg = fmt::format("V3KN: Unexpected server response: {}", response.body); - } - } - - return error_msg; -} - -std::string get_v3kn_server_url(const std::string &host, const std::string &path) { - if (V3KN_SERVER_PROTOCOL.find(host) == V3KN_SERVER_PROTOCOL.end()) { - LOG_WARN("V3KN: Unknown server host {}, defaulting to https", host); - return fmt::format("https://{}/{}", host, path); - } - - return V3KN_SERVER_PROTOCOL.at(host) + host + "/" + path; -} - -void handle_v3kn_status(EmuEnvState &emuenv, const net_utils::WebResponse &response) { - if (response.body.starts_with("OK:")) { - set_v3kn_logged_in(true); - } else if (response.body.starts_with("ERR:")) { - if (response.body == "ERR:InvalidToken") { - set_v3kn_logged_in(false); - emuenv.v3kn.account_state.user_info.token.clear(); - save_v3kn_user_info(emuenv); - } - } else if (!response.body.empty() && response.curl_res == 0) { - set_v3kn_logged_in(true); - } -} - -net_utils::WebResponse v3kn_create(const UserInfo &user_info, const std::string &online_id, const std::string &password) { - const std::string url = get_v3kn_server_url(user_info.host, "v3kn/create"); - const std::string post_data = fmt::format("version={}&online_id={}&password={}", V3KN_VERSION, online_id, password); - - const auto res = net_utils::get_web_response_ex(url, "", post_data); - - return res; -} - -net_utils::WebResponse v3kn_delete(const UserInfo &user_info, const std::string &password) { - const std::string url = get_v3kn_server_url(user_info.host, "v3kn/delete"); - const std::string post_data = fmt::format("password={}", password); - const auto res = net_utils::get_web_response_ex(url, user_info.token, post_data); - - return res; -} - -net_utils::WebResponse v3kn_login(const UserInfo &user_info, const std::string &online_id, const std::string &password) { - const std::string url = get_v3kn_server_url(user_info.host, "v3kn/login"); - const std::string post_data = fmt::format("version={}&online_id={}&password={}", V3KN_VERSION, online_id, password); - - const auto res = net_utils::get_web_response_ex(url, "", post_data); - - return res; -} - -net_utils::WebResponse v3kn_change_online_id(const UserInfo &user_info, const std::string &new_online_id) { - const std::string url = get_v3kn_server_url(user_info.host, "v3kn/change_online_id"); - const std::string post_data = fmt::format("new_online_id={}", new_online_id); - const auto res = net_utils::get_web_response_ex(url, user_info.token, post_data); - - return res; -} - -net_utils::WebResponse v3kn_change_password(const UserInfo &user_info, const std::string &old_password, const std::string &new_password) { - const std::string url = get_v3kn_server_url(user_info.host, "v3kn/change_password"); - const std::string post_data = fmt::format("old_password={}&new_password={}", old_password, new_password); - const auto res = net_utils::get_web_response_ex(url, user_info.token, post_data); - - return res; -} - -net_utils::WebResponse v3kn_change_about_me(const UserInfo &user_info, const std::string &about_me) { - const std::string url = get_v3kn_server_url(user_info.host, "v3kn/change_about_me"); - const std::string post_data = fmt::format("about_me={}", about_me); - const auto res = net_utils::get_web_response_ex(url, user_info.token, post_data); - - return res; -} - -net_utils::WebResponse v3kn_avatar_upload(const UserInfo &user_info, const std::string &file_path) { - const std::string url = get_v3kn_server_url(user_info.host, "v3kn/avatar"); - const auto res = net_utils::upload_file(url, file_path, user_info.token, ""); - - return res; -} - -net_utils::WebResponse v3kn_avatar_upload(const UserInfo &user_info, const std::vector &png_data) { - const std::string url = get_v3kn_server_url(user_info.host, "v3kn/avatar"); - const auto res = net_utils::upload_data(url, png_data, "Avatar.png", user_info.token); - return res; -} - -net_utils::WebResponse v3kn_avatar_download(const UserInfo &user_info, const std::string &target_online_id, net_utils::CurlSession *session) { - const std::string path = fmt::format("v3kn/avatar?target_online_id={}", target_online_id); - const std::string url = get_v3kn_server_url(user_info.host, path); - const auto res = net_utils::get_web_response_ex(url, user_info.token, "", session); - return res; -} - -net_utils::WebResponse v3kn_panel_upload(const UserInfo &user_info, const std::vector &png_data) { - const std::string url = get_v3kn_server_url(user_info.host, "v3kn/panel"); - const auto res = net_utils::upload_data(url, png_data, "Panel.png", user_info.token); - return res; -} - -net_utils::WebResponse v3kn_panel_download(const UserInfo &user_info, const std::string &target_online_id, net_utils::CurlSession *session) { - const std::string path = fmt::format("v3kn/panel?target_online_id={}", target_online_id); - const std::string url = get_v3kn_server_url(user_info.host, path); - const auto res = net_utils::get_web_response_ex(url, user_info.token, "", session); - return res; -} - -void save_v3kn_user_info(EmuEnvState &emuenv) { - const fs::path user_info_path{ emuenv.pref_path / "ux0/user" / emuenv.io.user_id / "v3kn.xml" }; - auto &user_info = emuenv.v3kn.account_state.user_info; - pugi::xml_document doc; - pugi::xml_node user_node = doc.append_child("user"); - user_node.append_attribute("version").set_value(1); - user_node.append_child("host").text().set(user_info.host.c_str()); - user_node.append_child("online_id").text().set(user_info.online_id.c_str()); - user_node.append_child("password").text().set(user_info.password.c_str()); - user_node.append_child("token").text().set(user_info.token.c_str()); - doc.save_file(user_info_path.string().c_str()); -} - -void init_v3kn_user_info(GuiState &gui, EmuEnvState &emuenv) { - auto &account_state = emuenv.v3kn.account_state; - auto &user_info = account_state.user_info; - user_info = {}; - account_state.selected_server_index = 0; - set_v3kn_logged_in(false); - set_v3kn_login_init_completed(false); - - const fs::path user_info_path{ emuenv.pref_path / "ux0/user" / emuenv.io.user_id / "v3kn.xml" }; - pugi::xml_document doc; - pugi::xml_parse_result result = doc.load_file(user_info_path.string().c_str()); - if (!result) { - set_v3kn_login_init_completed(true); - return; - } - pugi::xml_node user_node = doc.child("user"); - if (!user_node) { - LOG_WARN("V3KN user info: missing node"); - set_v3kn_login_init_completed(true); - return; - } - const auto version = user_node.attribute("version").as_uint(); - if (version != 1) { - LOG_WARN("V3KN user info: unsupported version {}", version); - set_v3kn_login_init_completed(true); - return; - } - - user_info.host = user_node.child("host").text().as_string(V3KN_SERVER_LIST_URL[0]); - user_info.online_id = user_node.child("online_id").text().as_string(); - user_info.password = user_node.child("password").text().as_string(); - user_info.token = user_node.child("token").text().as_string(); - - account_state.selected_server_index = static_cast(vector_utils::find_index(V3KN_SERVER_LIST_URL, user_info.host)); - if (account_state.selected_server_index == static_cast(-1)) - account_state.selected_server_index = 0; - - if (user_info.token.empty()) { - set_v3kn_login_init_completed(true); - return; - } - - const std::string url = get_v3kn_server_url(user_info.host, "v3kn/check?version=" + std::string(V3KN_VERSION)); - const auto res = net_utils::get_web_response_ex(url, user_info.token, ""); - - handle_v3kn_status(emuenv, res); - - if (!res.body.starts_with("OK:")) { - gui.info_message.function = "v3kn_check"; - gui.info_message.title = emuenv.common_dialog.lang.common["error"]; - gui.info_message.level = spdlog::level::err; - gui.info_message.msg = get_v3kn_error_message(emuenv, res); - - set_v3kn_login_init_completed(true); - return; - } - - std::smatch match; - std::regex regex_pattern(R"(OK:Connected:([^:]+):([^:]+):([^:]+):([^]+))"); - if (std::regex_search(res.body, match, regex_pattern) && (match.size() == 5)) { - const std::string current_online_id = match[1].str(); - user_info.created_at = std::strtoull(match[2].str().c_str(), nullptr, 10); - user_info.quota_used = std::strtoull(match[3].str().c_str(), nullptr, 10); - user_info.quota_total = std::strtoull(match[4].str().c_str(), nullptr, 10); - if (current_online_id != user_info.online_id) { - LOG_INFO("V3KN user info: online ID on server ({}) does not match local info ({}), updating local info", current_online_id, user_info.online_id); - user_info.online_id = current_online_id; - save_v3kn_user_info(emuenv); - } - } else { - LOG_WARN("V3KN user info: unexpected server response, connecting again will be required"); - set_v3kn_login_init_completed(true); - return; - } - - set_v3kn_logged_in(true); - init_v3kn_avatar(gui, emuenv, user_info); - - set_v3kn_login_init_completed(true); - LOG_INFO("V3KN user info: successfully logged in as {}", user_info.online_id); -} - -} // namespace v3kn diff --git a/vita3k/v3kn/src/storage.cpp b/vita3k/v3kn/src/storage.cpp deleted file mode 100644 index 86d3cb492..000000000 --- a/vita3k/v3kn/src/storage.cpp +++ /dev/null @@ -1,408 +0,0 @@ -// 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 -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include - -namespace v3kn { - -// Online Storage -static bool get_savedata_info(GuiState &gui, EmuEnvState &emuenv, const std::string &titleid) { - emuenv.v3kn.storage_state.savedata_info.clear(); - - // Get local savedata info - const fs::path save_data_path = { emuenv.pref_path / "ux0/user" / emuenv.io.user_id / "savedata" / titleid }; - if (fs::exists(save_data_path) && !fs::is_empty(save_data_path)) { - auto &save_data_info_local = emuenv.v3kn.storage_state.savedata_info[SAVE_DATA_TYPE_LOCAL]; - uint64_t total_size_local = 0; - time_t global_last_updated = 0; - for (auto &file : fs::recursive_directory_iterator(save_data_path)) { - if (!file.is_regular_file()) - continue; - - const auto file_size = fs::file_size(file.path()); - const auto last_updated = fs::last_write_time(file.path()); - SaveEntry entry{ - .rel_path = fs::relative(file.path(), save_data_path).generic_string(), - .last_updated = last_updated - }; - save_data_info_local.files.push_back(entry); - global_last_updated = std::max(global_last_updated, last_updated); - total_size_local += file_size; - } - save_data_info_local.last_updated = global_last_updated; - save_data_info_local.total_size = total_size_local; - } - - const std::string url = get_v3kn_server_url(emuenv.v3kn.account_state.user_info.host, fmt::format("v3kn/save_info?titleid={}", titleid)); - const auto response = net_utils::get_web_response_ex(url, emuenv.v3kn.account_state.user_info.token); - - // Handle response and update connection status - handle_v3kn_status(emuenv, response); - - if (response.body.starts_with("WARN:")) { - LOG_WARN("V3KN get savedata info: server warning: {}", response.body); - return true; - } else if (response.body.starts_with("ERR:")) { - gui.info_message.title = emuenv.common_dialog.lang.common["error"]; - gui.info_message.level = spdlog::level::err; - gui.info_message.msg = get_v3kn_error_message(emuenv, response); - return false; - } - - pugi::xml_document doc; - pugi::xml_parse_result result = doc.load_string(response.body.c_str()); - if (!result) { - LOG_WARN("V3KN get savedata info: failed to parse XML: {}", result.description()); - return false; - } - - pugi::xml_node savedata_node = doc.child("savedata"); - if (!savedata_node) { - LOG_WARN("V3KN get savedata info: missing node"); - return false; - } - - auto &save_data_info_cloud = emuenv.v3kn.storage_state.savedata_info[SAVE_DATA_TYPE_CLOUD]; - save_data_info_cloud.last_updated = static_cast(savedata_node.attribute("last_updated").as_ullong()); - save_data_info_cloud.last_uploaded = static_cast(savedata_node.attribute("last_uploaded").as_ullong()); - save_data_info_cloud.total_size = savedata_node.attribute("total_size").as_ullong(); - for (pugi::xml_node file_node : savedata_node.children("file")) { - SaveEntry entry{ - .rel_path = file_node.attribute("path").as_string(), - .last_updated = static_cast(file_node.attribute("last_updated").as_ullong()) - }; - save_data_info_cloud.files.push_back(entry); - } - - return true; -} - -static bool create_zip_from_directory(const fs::path &directory, const fs::path &zip_path) { - mz_zip_archive zip; - memset(&zip, 0, sizeof(zip)); - - if (!mz_zip_writer_init_file(&zip, zip_path.string().c_str(), 0)) { - LOG_ERROR("Failed to init zip writer"); - return false; - } - - for (auto &entry : fs::recursive_directory_iterator(directory)) { - if (!entry.is_regular_file()) - continue; - - fs::path file_path = entry.path(); - fs::path rel_path = fs::relative(file_path, directory); - - if (!mz_zip_writer_add_file( - &zip, - rel_path.generic_string().c_str(), // path in the zip - file_path.string().c_str(), // source file - nullptr, - 0, - MZ_BEST_SPEED)) { // fast compression - LOG_ERROR("Failed to add file {}", file_path.string()); - mz_zip_writer_end(&zip); - return false; - } - } - - if (!mz_zip_writer_finalize_archive(&zip)) { - LOG_ERROR("Failed to finalize zip"); - mz_zip_writer_end(&zip); - return false; - } - - mz_zip_writer_end(&zip); - return true; -} - -static bool extract_zip_to_directory(const fs::path &zip_path, const fs::path &output_dir) { - mz_zip_archive zip; - memset(&zip, 0, sizeof(zip)); - - if (!mz_zip_reader_init_file(&zip, zip_path.string().c_str(), 0)) { - LOG_ERROR("Failed to init zip reader"); - return false; - } - - int file_count = (int)mz_zip_reader_get_num_files(&zip); - - for (int i = 0; i < file_count; i++) { - mz_zip_archive_file_stat stat; - if (!mz_zip_reader_file_stat(&zip, i, &stat)) { - LOG_ERROR("Failed to stat file {}", i); - continue; - } - - fs::path out_path = output_dir / stat.m_filename; - - if (stat.m_is_directory) { - fs::create_directories(out_path); - continue; - } - - fs::create_directories(out_path.parent_path()); - - if (!mz_zip_reader_extract_to_file(&zip, i, out_path.string().c_str(), 0)) { - LOG_ERROR("Failed to extract {}", stat.m_filename); - continue; - } - } - - mz_zip_reader_end(&zip); - return true; -} - -static void reset_progress(StorageState &storage_state) { - storage_state.progress = 0.f; - storage_state.bytes_done = 0; - storage_state.remaining = 0; - storage_state.progress_done_timestamp = 0; - storage_state.please_wait_timestamp = 0; - storage_state.please_wait_done.store(false); - storage_state.progress_state.canceled = false; -} - -void download_savedata(GuiState &gui, EmuEnvState &emuenv, const std::string &titleid) { - const fs::path savedata_path = emuenv.pref_path / "ux0/user" / emuenv.io.user_id / "savedata" / titleid; - const fs::path savedata_cache_path = emuenv.cache_path / fmt::format("{}.psvimg", titleid); - - const auto &savedata_info_cloud = emuenv.v3kn.storage_state.savedata_info[SAVE_DATA_TYPE_CLOUD]; - - reset_progress(emuenv.v3kn.storage_state); - - std::thread([&gui, &emuenv, titleid, savedata_path, savedata_cache_path, savedata_info_cloud]() mutable { - auto &storage_state = emuenv.v3kn.storage_state; - - auto progress_callback = [&storage_state](float updated_progress, uint64_t updated_remaining, uint64_t updated_bytes_done) { - storage_state.progress = updated_progress; - storage_state.bytes_done = updated_bytes_done; - storage_state.remaining = updated_remaining; - return &storage_state.progress_state; - }; - - const std::string url = get_v3kn_server_url(emuenv.v3kn.account_state.user_info.host, fmt::format("v3kn/download_file?type=savedata&id={}", titleid)); - - if (fs::exists(savedata_cache_path)) - fs::remove(savedata_cache_path); - - const auto savedata_cache_str = fs_utils::path_to_utf8(savedata_cache_path); - const auto res = net_utils::download_file_ex(url, savedata_cache_str, progress_callback, emuenv.v3kn.account_state.user_info.token); - - // Handle response and update connection status - handle_v3kn_status(emuenv, res); - - // Wait for progress to reach 100% and display please wait dialog a minimum time before proceeding for a better UX - { - std::unique_lock lck(storage_state.mutex_progress); - storage_state.cv_progress.wait(lck, [&] { - return storage_state.please_wait_done.load(); - }); - } - - if (res.body.starts_with("ERR:") || (res.curl_res != 0)) { - gui.info_message.function = spdlog::level::err; - gui.info_message.title = emuenv.common_dialog.lang.common["error"]; - gui.info_message.msg = get_v3kn_error_message(emuenv, res); - gui.vita_area.please_wait = false; - LOG_ERROR("Failed to download {}", savedata_cache_str); - return; - } - - // Verify downloaded file size - const auto file_size = fs::file_size(savedata_cache_path); - if ((file_size == 0) || (file_size != savedata_info_cloud.total_size)) { - fs::remove(savedata_cache_path); - gui.info_message.function = spdlog::level::err; - gui.info_message.title = emuenv.common_dialog.lang.common["error"]; - gui.info_message.msg = "Downloaded savedata size mismatch, please try again."; - gui.vita_area.please_wait = false; - LOG_ERROR("Downloaded savedata size mismatch: expected {}, got {}", savedata_info_cloud.total_size, file_size); - return; - } - - // Extract savedata zip in a temp folder first to avoid partial overwrite if extraction fails - const auto save_data_temp_path = fs::path{ savedata_path.parent_path() / (titleid + "_v3kn") }; - if (fs::exists(save_data_temp_path)) - fs::remove_all(save_data_temp_path); - const auto ret = extract_zip_to_directory(savedata_cache_path, save_data_temp_path); - fs::remove(savedata_cache_path); - if (!ret) { - fs::remove_all(save_data_temp_path); - gui.info_message.function = spdlog::level::err; - gui.info_message.title = emuenv.common_dialog.lang.common["error"]; - gui.info_message.msg = "Failed to extract savedata zip, please try again."; - gui.vita_area.please_wait = false; - LOG_ERROR("Failed to extract savedata zip for titleid {}", titleid); - return; - } - - // Remove existing savedata if extracted successfully - if (fs::exists(savedata_path)) - fs::remove_all(savedata_path); - - // Rename temp folder to actual savedata folder - fs::rename(save_data_temp_path, savedata_path); - - auto &savedata_info_local = emuenv.v3kn.storage_state.savedata_info[SAVE_DATA_TYPE_LOCAL]; - savedata_info_local = savedata_info_cloud; - uint64_t total_size_local = 0; - for (auto &entry : savedata_info_cloud.files) { - const fs::path local_file_path = savedata_path / entry.rel_path; - total_size_local += fs::file_size(local_file_path); - fs::last_write_time(local_file_path, entry.last_updated); - } - - savedata_info_local.total_size = total_size_local; - fs::last_write_time(savedata_path, savedata_info_cloud.last_updated); - gui.vita_area.please_wait = false; - LOG_INFO("Download successful for titleid {}", titleid); - }).detach(); -} - -void upload_savedata(GuiState &gui, EmuEnvState &emuenv, const std::string &titleid) { - const fs::path savedata_path = { emuenv.pref_path / "ux0/user" / emuenv.io.user_id / "savedata" / titleid }; - if (!fs::exists(savedata_path) || fs::is_empty(savedata_path)) { - LOG_WARN("No savedata to upload for titleid {}", titleid); - return; - } - - const fs::path savedata_cache_path{ emuenv.cache_path / fmt::format("{}.psvimg", titleid) }; - - reset_progress(emuenv.v3kn.storage_state); - - std::thread([&gui, &emuenv, titleid, savedata_path, savedata_cache_path]() mutable { - auto &storage_state = emuenv.v3kn.storage_state; - - auto progress_callback = [&storage_state](float updated_progress, uint64_t updated_remaining, uint64_t updated_bytes_done) { - storage_state.progress = updated_progress; - storage_state.bytes_done = updated_bytes_done; - storage_state.remaining = updated_remaining; - return &storage_state.progress_state; - }; - - auto &savedata_info_local = emuenv.v3kn.storage_state.savedata_info[SAVE_DATA_TYPE_LOCAL]; - - if (!create_zip_from_directory(savedata_path, savedata_cache_path)) { - LOG_ERROR("Failed to create savedata zip"); - return; - } - - const auto total_size = fs::file_size(savedata_cache_path); - const auto last_uploaded = std::time(0); - - std::stringstream savedata_info_xml; - pugi::xml_document doc; - pugi::xml_node savedata_node = doc.append_child("savedata"); - for (const auto &entry : savedata_info_local.files) { - auto file_node = savedata_node.append_child("file"); - file_node.append_attribute("path").set_value(entry.rel_path.c_str()); - file_node.append_attribute("last_updated").set_value(static_cast(entry.last_updated)); - } - savedata_node.append_attribute("last_uploaded").set_value(static_cast(last_uploaded)); - savedata_node.append_attribute("last_updated").set_value(static_cast(savedata_info_local.last_updated)); - savedata_node.append_attribute("total_size").set_value(total_size); - doc.save(savedata_info_xml); - - const std::string url = get_v3kn_server_url(emuenv.v3kn.account_state.user_info.host, fmt::format("v3kn/upload_file?type=savedata&id={}", titleid)); - - const std::string filepath_str = fs_utils::path_to_utf8(savedata_cache_path); - const auto res = net_utils::upload_file(url, filepath_str, emuenv.v3kn.account_state.user_info.token, savedata_info_xml.str(), progress_callback); - // Handle response and update connection status - handle_v3kn_status(emuenv, res); - - // Wait for progress to reach 100% and display please wait dialog a minimum time before proceeding for a better UX - { - std::unique_lock lck(storage_state.mutex_progress); - storage_state.cv_progress.wait(lck, [&] { - return storage_state.please_wait_done.load(); - }); - } - - fs::remove(savedata_cache_path); - - if (!res.body.starts_with("OK:")) { - gui.info_message.function = spdlog::level::err; - gui.info_message.title = emuenv.common_dialog.lang.common["error"]; - gui.info_message.msg = get_v3kn_error_message(emuenv, res); - gui.vita_area.please_wait = false; - LOG_ERROR("Upload failed for {}", filepath_str); - return; - } - - std::smatch match; - // Expected format: "OK:quota_used:quota_total" - std::regex regex_pattern(R"(OK:(\d+):(\d+))"); - if (std::regex_search(res.body, match, regex_pattern) && match.size() == 3) { - // Update user info of quota - emuenv.v3kn.account_state.user_info.quota_used = std::strtoull(match[1].str().c_str(), nullptr, 10); - emuenv.v3kn.account_state.user_info.quota_total = std::strtoull(match[2].str().c_str(), nullptr, 10); - - // Update cloud savedata info - savedata_info_local.last_uploaded = last_uploaded; - auto &savedata_info_cloud = emuenv.v3kn.storage_state.savedata_info[SAVE_DATA_TYPE_CLOUD]; - savedata_info_cloud = savedata_info_local; - savedata_info_cloud.total_size = total_size; - LOG_INFO("Upload successful for titleid {}", titleid); - } else - LOG_WARN("Unexpected upload response format: {}", res.body); - - gui.vita_area.please_wait = false; - }).detach(); -} - -void open_online_storage(GuiState &gui, EmuEnvState &emuenv, const std::string &titleid) { - gui.vita_area.connecting_please_wait = true; - std::thread([&gui, &emuenv, titleid]() mutable { - if (get_savedata_info(gui, emuenv, titleid)) { - gui.vita_area.online_storage = true; - gui.vita_area.home_screen = false; - gui.vita_area.live_area_screen = false; - } - gui.vita_area.connecting_please_wait = false; - }).detach(); -} - -} // namespace v3kn