add basic support for gamecard installing
This commit is contained in:
@@ -47,6 +47,7 @@ add_executable(sphaira
|
|||||||
source/ui/menus/themezer.cpp
|
source/ui/menus/themezer.cpp
|
||||||
source/ui/menus/ghdl.cpp
|
source/ui/menus/ghdl.cpp
|
||||||
source/ui/menus/usb_menu.cpp
|
source/ui/menus/usb_menu.cpp
|
||||||
|
source/ui/menus/gc_menu.cpp
|
||||||
|
|
||||||
source/ui/error_box.cpp
|
source/ui/error_box.cpp
|
||||||
source/ui/notification.cpp
|
source/ui/notification.cpp
|
||||||
|
|||||||
@@ -454,4 +454,10 @@ struct FsNativeContentStorage final : FsNative {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct FsNativeGameCard final : FsNative {
|
||||||
|
FsNativeGameCard(const FsGameCardHandle* handle, FsGameCardPartition partition, bool ignore_read_only = true) : FsNative{ignore_read_only} {
|
||||||
|
m_open_result = fsOpenGameCardFileSystem(&m_fs, handle, partition);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace fs
|
} // namespace fs
|
||||||
|
|||||||
38
sphaira/include/ui/menus/gc_menu.hpp
Normal file
38
sphaira/include/ui/menus/gc_menu.hpp
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui/menus/menu_base.hpp"
|
||||||
|
#include "yati/container/base.hpp"
|
||||||
|
#include "yati/source/base.hpp"
|
||||||
|
|
||||||
|
namespace sphaira::ui::menu::gc {
|
||||||
|
|
||||||
|
enum class State {
|
||||||
|
// no gamecard inserted.
|
||||||
|
None,
|
||||||
|
// set whilst transfer is in progress.
|
||||||
|
Progress,
|
||||||
|
// set when the transfer is finished.
|
||||||
|
Done,
|
||||||
|
// set when no gamecard is inserted.
|
||||||
|
NotFound,
|
||||||
|
// failed to parse gamecard.
|
||||||
|
Failed,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Menu final : MenuBase {
|
||||||
|
Menu();
|
||||||
|
~Menu();
|
||||||
|
|
||||||
|
void Update(Controller* controller, TouchInfo* touch) override;
|
||||||
|
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||||
|
|
||||||
|
Result ScanGamecard();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<fs::FsNativeGameCard> m_fs{};
|
||||||
|
FsDeviceOperator m_dev_op{};
|
||||||
|
yati::container::Collections m_collections{};
|
||||||
|
State m_state{State::None};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sphaira::ui::menu::gc
|
||||||
187
sphaira/source/ui/menus/gc_menu.cpp
Normal file
187
sphaira/source/ui/menus/gc_menu.cpp
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
#include "ui/menus/gc_menu.hpp"
|
||||||
|
#include "yati/yati.hpp"
|
||||||
|
#include "app.hpp"
|
||||||
|
#include "defines.hpp"
|
||||||
|
#include "log.hpp"
|
||||||
|
#include "ui/nvg_util.hpp"
|
||||||
|
#include "i18n.hpp"
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace sphaira::ui::menu::gc {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
auto InRange(u64 off, u64 offset, u64 size) -> bool {
|
||||||
|
return off < offset + size && off >= offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GcSource final : yati::source::Base {
|
||||||
|
GcSource(const yati::container::Collections& collections, fs::FsNativeGameCard* fs);
|
||||||
|
~GcSource();
|
||||||
|
Result Read(void* buf, s64 off, s64 size, u64* bytes_read);
|
||||||
|
|
||||||
|
const yati::container::Collections& m_collections;
|
||||||
|
fs::FsNativeGameCard* m_fs{};
|
||||||
|
FsFile m_file{};
|
||||||
|
s64 m_offset{};
|
||||||
|
s64 m_size{};
|
||||||
|
};
|
||||||
|
|
||||||
|
GcSource::GcSource(const yati::container::Collections& collections, fs::FsNativeGameCard* fs)
|
||||||
|
: m_collections{collections}
|
||||||
|
, m_fs{fs} {
|
||||||
|
m_offset = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
GcSource::~GcSource() {
|
||||||
|
fsFileClose(&m_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result GcSource::Read(void* buf, s64 off, s64 size, u64* bytes_read) {
|
||||||
|
// check is we need to open a new file.
|
||||||
|
if (!InRange(off, m_offset, m_size)) {
|
||||||
|
fsFileClose(&m_file);
|
||||||
|
m_file = {};
|
||||||
|
|
||||||
|
// find new file based on the offset.
|
||||||
|
bool found = false;
|
||||||
|
for (auto& collection : m_collections) {
|
||||||
|
if (InRange(off, collection.offset, collection.size)) {
|
||||||
|
found = true;
|
||||||
|
m_offset = collection.offset;
|
||||||
|
m_size = collection.size;
|
||||||
|
R_TRY(m_fs->OpenFile(fs::AppendPath("/", collection.name), FsOpenMode_Read, &m_file));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this will never fail, unless i break something in yati.
|
||||||
|
R_UNLESS(found, 0x1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fsFileRead(&m_file, off - m_offset, buf, size, 0, bytes_read);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
Menu::Menu() : MenuBase{"GameCard"_i18n} {
|
||||||
|
SetAction(Button::B, Action{"Back"_i18n, [this](){
|
||||||
|
SetPop();
|
||||||
|
}});
|
||||||
|
|
||||||
|
SetAction(Button::X, Action{"Refresh"_i18n, [this](){
|
||||||
|
m_state = State::None;
|
||||||
|
}});
|
||||||
|
|
||||||
|
fsOpenDeviceOperator(std::addressof(m_dev_op));
|
||||||
|
}
|
||||||
|
|
||||||
|
Menu::~Menu() {
|
||||||
|
// manually close this as it needs(?) to be closed before dev_op.
|
||||||
|
m_fs.reset();
|
||||||
|
fsDeviceOperatorClose(std::addressof(m_dev_op));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Menu::Update(Controller* controller, TouchInfo* touch) {
|
||||||
|
MenuBase::Update(controller, touch);
|
||||||
|
|
||||||
|
switch (m_state) {
|
||||||
|
case State::None: {
|
||||||
|
bool gc_inserted;
|
||||||
|
if (R_FAILED(fsDeviceOperatorIsGameCardInserted(std::addressof(m_dev_op), std::addressof(gc_inserted)))) {
|
||||||
|
m_state = State::Failed;
|
||||||
|
} else {
|
||||||
|
if (!gc_inserted) {
|
||||||
|
m_state = State::NotFound;
|
||||||
|
} else {
|
||||||
|
if (R_FAILED(ScanGamecard())) {
|
||||||
|
m_state = State::Failed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case State::Progress:
|
||||||
|
case State::Done:
|
||||||
|
case State::NotFound:
|
||||||
|
case State::Failed:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
||||||
|
MenuBase::Draw(vg, theme);
|
||||||
|
|
||||||
|
switch (m_state) {
|
||||||
|
case State::None:
|
||||||
|
gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_INFO), "Waiting for connection..."_i18n.c_str());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case State::Progress:
|
||||||
|
gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_INFO), "Transferring data..."_i18n.c_str());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case State::NotFound:
|
||||||
|
gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_INFO), "No GameCard inserted, press X to refresh"_i18n.c_str());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case State::Done:
|
||||||
|
gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_INFO), "Installed GameCard, press B to exit..."_i18n.c_str());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case State::Failed:
|
||||||
|
gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_INFO), "Failed to scan GameCard..."_i18n.c_str());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Result Menu::ScanGamecard() {
|
||||||
|
m_state = State::None;
|
||||||
|
m_fs.reset();
|
||||||
|
m_collections.clear();
|
||||||
|
|
||||||
|
FsGameCardHandle gc_handle;
|
||||||
|
R_TRY(fsDeviceOperatorGetGameCardHandle(std::addressof(m_dev_op), std::addressof(gc_handle)));
|
||||||
|
|
||||||
|
m_fs = std::make_unique<fs::FsNativeGameCard>(std::addressof(gc_handle), FsGameCardPartition_Secure, false);
|
||||||
|
R_TRY(m_fs->GetFsOpenResult());
|
||||||
|
|
||||||
|
FsDir dir;
|
||||||
|
R_TRY(m_fs->OpenDirectory("/", FsDirOpenMode_ReadFiles, std::addressof(dir)));
|
||||||
|
ON_SCOPE_EXIT(fsDirClose(std::addressof(dir)));
|
||||||
|
|
||||||
|
s64 count;
|
||||||
|
R_TRY(m_fs->DirGetEntryCount(std::addressof(dir), std::addressof(count)));
|
||||||
|
|
||||||
|
std::vector<FsDirectoryEntry> buf(count);
|
||||||
|
s64 total_entries;
|
||||||
|
R_TRY(m_fs->DirRead(std::addressof(dir), std::addressof(total_entries), buf.size(), buf.data()));
|
||||||
|
m_collections.reserve(total_entries);
|
||||||
|
|
||||||
|
s64 offset{};
|
||||||
|
for (s64 i = 0; i < total_entries; i++) {
|
||||||
|
yati::container::CollectionEntry entry{};
|
||||||
|
entry.name = buf[i].name;
|
||||||
|
entry.offset = offset;
|
||||||
|
entry.size = buf[i].file_size;
|
||||||
|
m_collections.emplace_back(entry);
|
||||||
|
offset += buf[i].file_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_state = State::Progress;
|
||||||
|
App::Push(std::make_shared<ui::ProgressBox>("Installing App"_i18n, [this](auto pbox) mutable -> bool {
|
||||||
|
auto source = std::make_shared<GcSource>(m_collections, m_fs.get());
|
||||||
|
return R_SUCCEEDED(yati::InstallFromCollections(pbox, source, m_collections));
|
||||||
|
}, [this](bool result){
|
||||||
|
if (result) {
|
||||||
|
App::Notify("Gc install success!"_i18n);
|
||||||
|
m_state = State::Done;
|
||||||
|
} else {
|
||||||
|
App::Notify("Gc install failed!"_i18n);
|
||||||
|
m_state = State::Failed;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sphaira::ui::menu::gc
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "ui/menus/themezer.hpp"
|
#include "ui/menus/themezer.hpp"
|
||||||
#include "ui/menus/ghdl.hpp"
|
#include "ui/menus/ghdl.hpp"
|
||||||
#include "ui/menus/usb_menu.hpp"
|
#include "ui/menus/usb_menu.hpp"
|
||||||
|
#include "ui/menus/gc_menu.hpp"
|
||||||
|
|
||||||
#include "ui/sidebar.hpp"
|
#include "ui/sidebar.hpp"
|
||||||
#include "ui/popup_list.hpp"
|
#include "ui/popup_list.hpp"
|
||||||
@@ -323,6 +324,10 @@ MainMenu::MainMenu() {
|
|||||||
options->Add(std::make_shared<SidebarEntryCallback>("Usb Install"_i18n, [](){
|
options->Add(std::make_shared<SidebarEntryCallback>("Usb Install"_i18n, [](){
|
||||||
App::Push(std::make_shared<menu::usb::Menu>());
|
App::Push(std::make_shared<menu::usb::Menu>());
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
options->Add(std::make_shared<SidebarEntryCallback>("GameCard Install"_i18n, [](){
|
||||||
|
App::Push(std::make_shared<menu::gc::Menu>());
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user