diff --git a/Iridium_Icon256.png b/Iridium_Icon256.png deleted file mode 100644 index 5ba7b0c..0000000 Binary files a/Iridium_Icon256.png and /dev/null differ diff --git a/Makefile b/Makefile index 41f8452..9ab25d4 100644 --- a/Makefile +++ b/Makefile @@ -38,8 +38,7 @@ EXEFS_SRC := exefs_src APP_TITLE := NXST APP_AUTHOR := DragonSpirit APP_VERSION := 04.24.2026 -ICON := icon.jpg - +ICON := icon.png #--------------------------------------------------------------------------------- # options for code generation @@ -51,7 +50,7 @@ CFLAGS += -g -O2 -ffunction-sections \ CFLAGS += $(INCLUDE) -D__SWITCH__ -D_GNU_SOURCE=1 -CXXFLAGS:= $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++17 +CXXFLAGS:= $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++17 -g ASFLAGS := -g $(ARCH) LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) @@ -155,6 +154,8 @@ clean: @echo clean ... @rm -fr $(BUILD) $(TARGET).pfs0 $(TARGET).nso $(TARGET).nro $(TARGET).nacp $(TARGET).elf $(TARGET).lst +cleanbuild: clean all + #--------------------------------------------------------------------------------- send: $(BUILD) @nxlink $(TARGET).nro diff --git a/icon.jpg b/icon.jpg deleted file mode 100644 index a060b1d..0000000 Binary files a/icon.jpg and /dev/null differ diff --git a/icon.png b/icon.png new file mode 100644 index 0000000..61dfd58 Binary files /dev/null and b/icon.png differ diff --git a/include/TitlesLayout.hpp b/include/TitlesLayout.hpp index 7443a23..17dd0de 100644 --- a/include/TitlesLayout.hpp +++ b/include/TitlesLayout.hpp @@ -1,12 +1,15 @@ #include #include #include +#include +#include namespace ui { class TitlesLayout : public pu::ui::Layout { private: pu::ui::elm::Menu::Ref titlesMenu; + std::unordered_map menuCache; bool m_inputLocked = false; public: diff --git a/include/TransferOverlay.hpp b/include/TransferOverlay.hpp index ffa2c38..9530224 100644 --- a/include/TransferOverlay.hpp +++ b/include/TransferOverlay.hpp @@ -51,6 +51,20 @@ namespace ui { void SetProgress(double val) { progressBar->SetProgress(val); } + + void SetProgressVisible(bool visible) { + progressBar->SetVisible(visible); + if (visible) { + this->SetHeight(OvlH); + this->SetY(OvlY); + hintText->SetY(195); + } else { + static constexpr int SmallH = 160; + this->SetHeight(SmallH); + this->SetY((720 - SmallH) / 2); + hintText->SetY(120); + } + } }; } diff --git a/include/UsersLayout.hpp b/include/UsersLayout.hpp index f714f6a..25a9168 100644 --- a/include/UsersLayout.hpp +++ b/include/UsersLayout.hpp @@ -7,6 +7,8 @@ namespace ui { private: pu::ui::elm::Menu::Ref usersMenu; + pu::ui::elm::Rectangle::Ref loadingBg; + pu::ui::elm::TextBlock::Ref loadingText; public: diff --git a/include/account.hpp b/include/account.hpp index b22c07b..71f359c 100644 --- a/include/account.hpp +++ b/include/account.hpp @@ -54,7 +54,8 @@ inline bool operator==(const AccountUid& x, u64 y) inline bool operator<(const AccountUid& x, const AccountUid& y) { - return x.uid[0] < y.uid[0] && x.uid[1] == y.uid[1]; + if (x.uid[0] != y.uid[0]) return x.uid[0] < y.uid[0]; + return x.uid[1] < y.uid[1]; } struct User { diff --git a/include/client.hpp b/include/client.hpp index a5b8cef..c46446c 100644 --- a/include/client.hpp +++ b/include/client.hpp @@ -5,6 +5,7 @@ int transfer_files(size_t index, AccountUid uid); bool isClientTransferDone(); bool isClientTransferCancelled(); bool isClientConnectionFailed(); +bool isClientProgressKnown(); void cancelClientTransfer(); double getClientProgress(); std::string getClientStatusText(); diff --git a/include/logger.hpp b/include/logger.hpp index 529ac58..aee29c6 100644 --- a/include/logger.hpp +++ b/include/logger.hpp @@ -48,9 +48,10 @@ public: template void log(const std::string& level, const std::string& format = {}, Args... args) { - // xcbuffer += StringUtils::format(("[" + DateTime::logDateTime() + "] " + level + " " + format + "\n").c_str(), args...); - printf("%s\n", format.c_str()); - // printf("%s\n",StringUtils::format("[" + DateTime::logDateTime() + "] " + level + " " + format + "\n").c_str(), args...); + // buffer += StringUtils::format(("[" + DateTime::logDateTime() + "] " + level + " " + format + "\n").c_str(), args...); + // buffer += StringUtils::format("%s\n", format.c_str()); + // buffer += StringUtils::format("%s\n",StringUtils::format("[" + DateTime::logDateTime() + "] " + level + " " + format + "\n").c_str(), args...); + printf(StringUtils::format("[" + DateTime::logDateTime() + "] " + level + " " + format + "\n").c_str(), args...); } void flush(void) diff --git a/include/title.hpp b/include/title.hpp index bdada8f..31e88b2 100644 --- a/include/title.hpp +++ b/include/title.hpp @@ -82,6 +82,7 @@ private: void getTitle(Title& dst, AccountUid uid, size_t i); size_t getTitleCount(AccountUid uid); void loadTitles(void); +bool areTitlesLoaded(void); void sortTitles(void); void rotateSortMode(void); void refreshDirectories(u64 id); diff --git a/source/Main.cpp b/source/Main.cpp index 2904fcd..ce2b9f9 100644 --- a/source/Main.cpp +++ b/source/Main.cpp @@ -41,25 +41,25 @@ int main() { exit(res); } - loadTitles(); + printf("main"); - // First create our renderer, where one can customize SDL or other stuff's - // initialization. - auto renderer_opts = pu::ui::render::RendererInitOptions( - SDL_INIT_EVERYTHING, pu::ui::render::RendererHardwareFlags); - renderer_opts.UseImage(pu::ui::render::IMGAllFlags); - renderer_opts.UseAudio(pu::ui::render::MixerAllFlags); - renderer_opts.UseTTF(); + // First create our renderer, where one can customize SDL or other stuff's + // initialization. + auto renderer_opts = pu::ui::render::RendererInitOptions( + SDL_INIT_EVERYTHING, pu::ui::render::RendererHardwareFlags); + renderer_opts.UseImage(pu::ui::render::IMGAllFlags); + renderer_opts.UseAudio(pu::ui::render::MixerAllFlags); + renderer_opts.UseTTF(); auto renderer = pu::ui::render::Renderer::New(renderer_opts); - // Create our main application from the renderer - auto main = ui::MainApplication::New(renderer); + // Create our main application from the renderer + auto main = ui::MainApplication::New(renderer); - main->Prepare(); + main->Prepare(); - main->Show(); + main->Show(); - servicesExit(); + servicesExit(); return 0; } diff --git a/source/TitlesLayout.cpp b/source/TitlesLayout.cpp index 557f9a4..94f251d 100644 --- a/source/TitlesLayout.cpp +++ b/source/TitlesLayout.cpp @@ -11,15 +11,25 @@ static std::vector accSids, devSids, bcatSids, cacheSids; namespace ui { extern MainApplication *mainApp; void TitlesLayout::InitTitles() { - this->titlesMenu = pu::ui::elm::Menu::New(0, 0, 1280, COLOR("#67000000"), COLOR("#170909FF"), 94, 7); - for (size_t i = 0; i < getTitleCount(g_currentUId); i++) { - Title title; - getTitle(title, g_currentUId, i); - auto titleItem = pu::ui::elm::MenuItem::New(title.name().c_str()); - titleItem->SetColor(COLOR("#FFFFFFFF")); - this->titlesMenu->AddItem(titleItem); + Logger::getInstance().log(Logger::INFO, "InitTitles"); + + auto it = this->menuCache.find(g_currentUId); + if (it != this->menuCache.end()) { + this->titlesMenu = it->second; + } else { + auto menu = pu::ui::elm::Menu::New(0, 0, 1280, COLOR("#67000000"), COLOR("#170909FF"), 94, 7); + for (size_t i = 0; i < getTitleCount(g_currentUId); i++) { + Title title; + getTitle(title, g_currentUId, i); + auto titleItem = pu::ui::elm::MenuItem::New(title.name().c_str()); + titleItem->SetColor(COLOR("#FFFFFFFF")); + menu->AddItem(titleItem); + } + this->menuCache.emplace(g_currentUId, menu); + this->titlesMenu = menu; } + this->Clear(); this->Add(this->titlesMenu); this->SetBackgroundColor(BACKGROUND_COLOR); @@ -28,6 +38,11 @@ namespace ui { void TitlesLayout::onInput(u64 Down, u64 Up, u64 Held, pu::ui::TouchPoint Pos) { if (m_inputLocked) return; + if (Down & HidNpadButton_B) { + mainApp->LoadLayout(mainApp->usersLayout); + return; + } + if (Down & HidNpadButton_Plus) { cancelClientTransfer(); cancelServerTransfer(); @@ -60,6 +75,7 @@ namespace ui { } while (!isClientTransferDone()) { ovl->SetStatus(getClientStatusText()); + ovl->SetProgressVisible(isClientProgressKnown()); ovl->SetProgress(getClientProgress()); mainApp->CallForRender(); if (mainApp->GetButtonsDown() & HidNpadButton_B) { diff --git a/source/UsersLayout.cpp b/source/UsersLayout.cpp index f4cb43e..5ce5d61 100644 --- a/source/UsersLayout.cpp +++ b/source/UsersLayout.cpp @@ -10,12 +10,22 @@ namespace ui { this->usersMenu->SetScrollbarColor(COLOR("#170909FF")); for (AccountUid const& uid : Account::ids()) { - auto username = pu::ui::elm::MenuItem::New(Account::username(uid) + ": " + std::to_string(getTitleCount(uid))); - username->SetColor(COLOR("#FFFFFFFF")); - this->usersMenu->AddItem(username); + auto item = pu::ui::elm::MenuItem::New(Account::username(uid)); + item->SetColor(COLOR("#FFFFFFFF")); + this->usersMenu->AddItem(item); } + + this->loadingBg = pu::ui::elm::Rectangle::New(0, 0, 1280, 720, pu::ui::Color(30, 30, 30, 220)); + this->loadingBg->SetVisible(false); + + this->loadingText = pu::ui::elm::TextBlock::New(480, 340, "Reading game list..."); + this->loadingText->SetColor(pu::ui::Color(255, 255, 255, 255)); + this->loadingText->SetVisible(false); + this->SetBackgroundColor(BACKGROUND_COLOR); this->Add(this->usersMenu); + this->Add(this->loadingBg); + this->Add(this->loadingText); } int32_t UsersLayout::GetCurrentIndex() { @@ -29,8 +39,21 @@ namespace ui { } if (Down & HidNpadButton_A) { - printf("current index is %i\n", this->usersMenu->GetSelectedIndex()); g_currentUId = Account::ids().at(this->usersMenu->GetSelectedIndex()); + + if (!areTitlesLoaded()) { + this->usersMenu->SetVisible(false); + this->loadingBg->SetVisible(true); + this->loadingText->SetVisible(true); + mainApp->CallForRender(); + + loadTitles(); + + this->loadingBg->SetVisible(false); + this->loadingText->SetVisible(false); + this->usersMenu->SetVisible(true); + } + mainApp->titlesLayout->InitTitles(); mainApp->LoadLayout(mainApp->titlesLayout); } diff --git a/source/account.cpp b/source/account.cpp index 1bca46f..5bf72e7 100644 --- a/source/account.cpp +++ b/source/account.cpp @@ -31,7 +31,16 @@ static std::map mUsers; Result Account::init(void) { - return accountInitialize(AccountServiceType_Application); + Result res = accountInitialize(AccountServiceType_Application); + if (R_FAILED(res)) return res; + + AccountUid uids[8]; + s32 count = 0; + accountListAllUsers(uids, 8, &count); + for (s32 i = 0; i < count; i++) { + Account::username(uids[i]); // populates mUsers as side effect + } + return 0; } void Account::exit(void) @@ -55,11 +64,12 @@ static User getUser(AccountUid id) AccountProfileBase profilebase; memset(&profilebase, 0, sizeof(profilebase)); - if (R_SUCCEEDED(accountGetProfile(&profile, id)) && R_SUCCEEDED(accountProfileGet(&profile, NULL, &profilebase))) { - user.name = std::string(profilebase.nickname); + if (R_SUCCEEDED(accountGetProfile(&profile, id))) { + if (R_SUCCEEDED(accountProfileGet(&profile, NULL, &profilebase))) { + user.name = std::string(profilebase.nickname); + } + accountProfileClose(&profile); } - - accountProfileClose(&profile); return user; } diff --git a/source/client.cpp b/source/client.cpp index 4d52a27..710ca0e 100644 --- a/source/client.cpp +++ b/source/client.cpp @@ -30,6 +30,7 @@ static TransferState g_client_state; bool isClientTransferDone() { return g_client_state.done.load(); } bool isClientTransferCancelled() { return g_client_state.cancelled.load(); } bool isClientConnectionFailed() { return g_client_state.connection_failed.load(); } +bool isClientProgressKnown() { return g_client_state.bytes_total.load() > 0; } void cancelClientTransfer() { g_client_state.cancelled.store(true); } double getClientProgress() { return g_client_state.progress(); } std::string getClientStatusText() { return g_client_state.getStatus(); } diff --git a/source/directory.cpp b/source/directory.cpp index 6fbd197..b6acbe3 100644 --- a/source/directory.cpp +++ b/source/directory.cpp @@ -45,10 +45,9 @@ Directory::Directory(const std::string& root) struct DirectoryEntry de = {name, directory}; mList.push_back(de); } + closedir(dir); + mGood = true; } - - closedir(dir); - mGood = true; } Result Directory::error(void) diff --git a/source/io.cpp b/source/io.cpp index 9316f07..4364d8c 100644 --- a/source/io.cpp +++ b/source/io.cpp @@ -62,6 +62,10 @@ void io::copyFile(const std::string& srcPath, const std::string& dstPath) while (offset < sz) { u32 count = fread((char*)buf, 1, BUFFER_SIZE, src); + if (count == 0) { + Logger::getInstance().log(Logger::ERROR, "fread returned 0 for file {} at offset {}/{} with errno {}. Aborting copy.", srcPath, offset, sz, errno); + break; + } fwrite((char*)buf, 1, count, dst); offset += count; } @@ -161,6 +165,7 @@ std::tuple io::backup(size_t index, AccountUid uid) if (R_SUCCEEDED(res)) { int rc = FileSystem::mount(fileSystem); if (rc == -1) { + fsFsClose(&fileSystem); FileSystem::unmount(); Logger::getInstance().log(Logger::ERROR, "Failed to mount filesystem during backup. Title id: 0x%016lX; User id: 0x%lX%lX.", title.id(), title.userId().uid[1], title.userId().uid[0]); @@ -257,6 +262,7 @@ std::tuple io::restore(size_t index, AccountUid uid, if (R_SUCCEEDED(res)) { int rc = FileSystem::mount(fileSystem); if (rc == -1) { + fsFsClose(&fileSystem); FileSystem::unmount(); Logger::getInstance().log(Logger::ERROR, "Failed to mount filesystem during restore. Title id: 0x%016lX; User id: 0x%lX%lX.", title.id(), uid.uid[1], uid.uid[0]); diff --git a/source/server.cpp b/source/server.cpp index b282077..3e6de02 100644 --- a/source/server.cpp +++ b/source/server.cpp @@ -123,7 +123,7 @@ static void receive_file(int sock, const std::string& relative_path, uint64_t fi static void* handle_client(void* socket_desc) { int client_socket = *(int*)socket_desc; - free(socket_desc); + delete static_cast(socket_desc); while (true) { uint32_t filename_len = 0; @@ -186,7 +186,11 @@ static void* accept_and_handle(void* arg) { if (client_socket >= 0) { g_server_client_sock.store(client_socket); int* pclient = new (std::nothrow) int(client_socket); - if (pclient) handle_client(pclient); + if (pclient) { + handle_client(pclient); + } else { + close(client_socket); + } g_server_client_sock.store(-1); } @@ -203,6 +207,9 @@ static void* broadcast_listener(void* arg) { g_broadcast_sock.store(udp.fd); + struct timeval tv{0, 100000}; // 100ms poll so cancel is detected quickly + setsockopt(udp, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); + sockaddr_in addr{}; addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_ANY); diff --git a/source/title.cpp b/source/title.cpp index dee7ebf..190cb1c 100644 --- a/source/title.cpp +++ b/source/title.cpp @@ -28,6 +28,12 @@ #include "main.hpp" static std::unordered_map> titles; +static bool s_titlesLoaded = false; + +bool areTitlesLoaded(void) +{ + return s_titlesLoaded; +} void Title::init(u8 saveDataType, u64 id, AccountUid userID, const std::string& name, const std::string& author) { @@ -176,25 +182,13 @@ void Title::refreshDirectories(void) else { Logger::getInstance().log(Logger::ERROR, "Couldn't retrieve the extdata directory list for the title " + name()); } - - // save backups from configuration - // std::vector additionalFolders = Configuration::getInstance().additionalSaveFolders(mId); - // for (std::vector::const_iterator it = additionalFolders.begin(); it != additionalFolders.end(); ++it) { - // we have other folders to parse - // Directory list(*it); - // if (list.good()) { - // for (size_t i = 0, sz = list.size(); i < sz; i++) { - // if (list.folder(i)) { - // mSaves.push_back(list.entry(i)); - // mFullSavePaths.push_back(*it + "/" + list.entry(i)); - // } - // } - // } - // } } void loadTitles(void) { + if (s_titlesLoaded) return; + s_titlesLoaded = true; + titles.clear(); FsSaveDataInfoReader reader; diff --git a/source/util.cpp b/source/util.cpp index f6a1198..d27abe2 100644 --- a/source/util.cpp +++ b/source/util.cpp @@ -43,10 +43,8 @@ Result servicesInit(void) io::createDirectory("sdmc:/switch/NXST"); io::createDirectory("sdmc:/switch/NXST/saves"); - Logger::getInstance().log(Logger::INFO, "Starting Checkpoint loading..."); - if (appletGetAppletType() != AppletType_Application) { - Logger::getInstance().log(Logger::WARN, "Please do not run Checkpoint in applet mode."); + Logger::getInstance().log(Logger::WARN, "Please do not run NXST in applet mode."); } // Result socinit = 0; @@ -76,6 +74,19 @@ Result servicesInit(void) return res; } + if (R_FAILED(res = nsInitialize())) { + Logger::getInstance().log(Logger::ERROR, "nsInitialize failed. Result code 0x{:08X}.", res); + return res; + } + + if (R_SUCCEEDED(res = hidsysInitialize())) { + g_notificationLedAvailable = true; + } + else { + Logger::getInstance().log(Logger::INFO, "Notification led not available. Result code 0x{:08X}.", res); + } + + Logger::getInstance().log(Logger::INFO, "NXST loading completed!"); return 0;