diff --git a/lang/system/en.xml b/lang/system/en.xml
index 948e7ed48..2a136b11f 100644
--- a/lang/system/en.xml
+++ b/lang/system/en.xml
@@ -125,6 +125,8 @@ 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.
@@ -140,6 +142,10 @@ Error code: {}
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
@@ -558,6 +564,27 @@ 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 43c91aeb4..ea3231ae1 100644
--- a/lang/system/fr.xml
+++ b/lang/system/fr.xml
@@ -93,6 +93,8 @@
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é.
@@ -106,6 +108,10 @@ Code d'erreur: {}
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
@@ -372,6 +378,27 @@ 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 e8aa40a75..4a41d7113 100644
--- a/vita3k/CMakeLists.txt
+++ b/vita3k/CMakeLists.txt
@@ -130,6 +130,7 @@ 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 473e9522e..d848d7235 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)
+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)
diff --git a/vita3k/emuenv/include/emuenv/state.h b/vita3k/emuenv/include/emuenv/state.h
index 77f3b1f28..d3e23a120 100644
--- a/vita3k/emuenv/include/emuenv/state.h
+++ b/vita3k/emuenv/include/emuenv/state.h
@@ -55,6 +55,7 @@ struct DialogState;
struct Ime;
struct License;
struct RegMgrState;
+struct V3KNState;
struct SfoFile;
struct GDBState;
struct HTTPState;
@@ -101,6 +102,7 @@ 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;
@@ -167,6 +169,7 @@ 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 a45f43c12..bd03e3986 100644
--- a/vita3k/emuenv/src/emuenv.cpp
+++ b/vita3k/emuenv/src/emuenv.cpp
@@ -38,6 +38,7 @@
#include
#include
#include
+#include
#include
@@ -85,6 +86,8 @@ 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 86c77bd5d..10e109b03 100644
--- a/vita3k/gui/CMakeLists.txt
+++ b/vita3k/gui/CMakeLists.txt
@@ -26,6 +26,7 @@ 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
@@ -44,6 +45,7 @@ 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
)
@@ -59,7 +61,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)
+target_link_libraries(gui PUBLIC app bgm_player compat config dialog emuenv ime imgui lang regmgr np v3kn)
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 99070c4dc..2eaec30ba 100644
--- a/vita3k/gui/include/gui/state.h
+++ b/vita3k/gui/include/gui/state.h
@@ -120,10 +120,13 @@ 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 {
@@ -150,6 +153,7 @@ struct DebugMenuState {
struct ConfigurationMenuState {
bool custom_settings_dialog = false;
bool settings_dialog = false;
+ bool v3kn_dialog = false;
};
struct ControlMenuState {
@@ -181,6 +185,18 @@ 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";
@@ -287,6 +303,10 @@ 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 a571309c9..32d0bf5a3 100644
--- a/vita3k/gui/src/app_context_menu.cpp
+++ b/vita3k/gui/src/app_context_menu.cpp
@@ -33,6 +33,8 @@
#include
#include
+#include
+
#include
#include
#undef main
@@ -879,6 +881,8 @@ 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 45602782d..94435c6d5 100644
--- a/vita3k/gui/src/gui.cpp
+++ b/vita3k/gui/src/gui.cpp
@@ -463,6 +463,54 @@ 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;
@@ -927,14 +975,21 @@ 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);
@@ -961,6 +1016,9 @@ 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);
@@ -985,6 +1043,9 @@ 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 dad8c2b4c..3efd5b399 100644
--- a/vita3k/gui/src/live_area.cpp
+++ b/vita3k/gui/src/live_area.cpp
@@ -35,9 +35,12 @@
#include
+#include
+
#include
#include
+#include
#include
#include
@@ -66,7 +69,7 @@ void GateAnimation::update() {
namespace gui {
bool get_sys_apps_state(GuiState &gui) {
- return !gui.vita_area.content_manager && !gui.vita_area.settings && !gui.vita_area.trophy_collection && !gui.vita_area.manual;
+ return !gui.vita_area.content_manager && !gui.vita_area.online_storage && !gui.vita_area.settings && !gui.vita_area.trophy_collection && !gui.vita_area.manual;
}
struct FRAME {
@@ -577,7 +580,8 @@ enum LiveAreaType {
GATE,
SEARCH,
MANUAL,
- REFRESH
+ REFRESH,
+ CLOUD
};
struct ButtonEntry {
@@ -912,6 +916,7 @@ 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);
@@ -1238,6 +1243,155 @@ 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));
@@ -1251,7 +1405,9 @@ 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(
@@ -1273,8 +1429,12 @@ void draw_live_area_screen(GuiState &gui, EmuEnvState &emuenv) {
if (TYPE == MANUAL)
WIDGET_COLOR = IM_COL32(202, 0, 106, 255);
- 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);
+ 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);
+ }
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 9d3872b7e..067bb0a17 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.title_id);
+ pre_load_app(gui, emuenv, emuenv.cfg.show_live_area_screen, app.path);
ImGui::PopStyleVar();
ImGui::PopID();
ImGui::PopStyleColor();
@@ -135,6 +135,7 @@ 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
new file mode 100644
index 000000000..7d0bfe5af
--- /dev/null
+++ b/vita3k/gui/src/online_storage.cpp
@@ -0,0 +1,366 @@
+// 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