Migrate to citrus.

This commit is contained in:
Steven Smith 2015-08-18 22:19:12 -07:00
parent e64939a12a
commit 558ace6bc5
5 changed files with 897 additions and 112 deletions

View File

@ -4,8 +4,8 @@ endif
#---------------------------------------------------------------------------------
# BUILD_FLAGS: List of extra build flags to add.
# NO_CTRCOMMON: Do not include ctrcommon.
# ENABLE_EXCEPTIONS: Enable C++ exceptions.
# NO_CITRUS: Do not include citrus.
#---------------------------------------------------------------------------------
include $(DEVKITPRO)/ctrcommon/tools/make_base
include $(DEVKITPRO)/citrus/tools/make_base

View File

@ -5,4 +5,4 @@ FBI is an open source CIA (un)installer for the 3DS.
Download: https://github.com/Steveice10/FBI/releases
Requires [devkitARM](http://sourceforge.net/projects/devkitpro/files/devkitARM/) and [ctrcommon](https://github.com/Steveice10/ctrcommon) to build.
Requires [devkitARM](http://sourceforge.net/projects/devkitpro/files/devkitARM/) and [citrus](https://github.com/Steveice10/citrus) to build.

View File

@ -1,12 +1,16 @@
#include <ctrcommon/app.hpp>
#include <ctrcommon/fs.hpp>
#include <ctrcommon/gpu.hpp>
#include <ctrcommon/input.hpp>
#include <ctrcommon/nor.hpp>
#include <ctrcommon/platform.hpp>
#include <ctrcommon/ui.hpp>
#include <citrus/app.hpp>
#include <citrus/battery.hpp>
#include <citrus/core.hpp>
#include <citrus/err.hpp>
#include <citrus/fs.hpp>
#include <citrus/gpu.hpp>
#include <citrus/gput.hpp>
#include <citrus/hid.hpp>
#include <citrus/nor.hpp>
#include <citrus/wifi.hpp>
#include "rop.h"
#include "ui.hpp"
#include <sys/errno.h>
#include <stdio.h>
@ -14,6 +18,9 @@
#include <sstream>
#include <iomanip>
#include <sys/stat.h>
using namespace ctr;
typedef enum {
INSTALL_CIA,
@ -27,7 +34,7 @@ std::vector<std::string> extensions = {"cia"};
bool exit = false;
bool showNetworkPrompts = true;
u64 freeSpace = 0;
MediaType destination = SD;
fs::MediaType destination = fs::SD;
Mode mode = INSTALL_CIA;
int prevProgress = -1;
@ -42,19 +49,17 @@ bool onProgress(u64 pos, u64 totalSize) {
details << installInfo;
details << "Press B to cancel.";
uiDisplayProgress(TOP_SCREEN, "Installing", details.str(), true, progress);
uiDisplayProgress(gpu::SCREEN_TOP, "Installing", details.str(), true, progress);
}
inputPoll();
return !inputIsPressed(BUTTON_B);
hid::poll();
return !hid::pressed(hid::BUTTON_B);
}
void networkInstall() {
while(platformIsRunning()) {
gpuClearScreens();
RemoteFile file = uiAcceptRemoteFile(TOP_SCREEN, [&](std::stringstream& infoStream) {
if(inputIsPressed(BUTTON_A)) {
while(core::running()) {
RemoteFile file = uiAcceptRemoteFile(gpu::SCREEN_TOP, [&](std::stringstream& infoStream) {
if(hid::pressed(hid::BUTTON_A)) {
showNetworkPrompts = !showNetworkPrompts;
}
@ -70,22 +75,22 @@ void networkInstall() {
std::stringstream confirmStream;
confirmStream << "Install the received application?" << "\n";
confirmStream << "Size: " << file.fileSize << " bytes (" << std::fixed << std::setprecision(2) << file.fileSize / 1024.0f / 1024.0f << "MB)";
if(!showNetworkPrompts || uiPrompt(TOP_SCREEN, confirmStream.str(), true)) {
AppResult ret = appInstall(destination, file.fd, file.fileSize, &onProgress);
if(!showNetworkPrompts || uiPrompt(gpu::SCREEN_TOP, confirmStream.str(), true)) {
app::AppResult ret = app::install(destination, file.fd, file.fileSize, &onProgress);
prevProgress = -1;
if(showNetworkPrompts || ret != APP_SUCCESS) {
if(showNetworkPrompts || ret != app::APP_SUCCESS) {
std::stringstream resultMsg;
resultMsg << "Install ";
if(ret == APP_SUCCESS) {
if(ret == app::APP_SUCCESS) {
resultMsg << "succeeded!";
} else {
resultMsg << "failed!" << "\n";
resultMsg << appGetResultString(ret);
resultMsg << app::resultString(ret);
}
uiPrompt(TOP_SCREEN, resultMsg.str(), false);
uiPrompt(gpu::SCREEN_TOP, resultMsg.str(), false);
freeSpace = fsGetFreeSpace(destination);
freeSpace = fs::freeSpace(destination);
}
}
@ -96,20 +101,20 @@ void networkInstall() {
void installROP() {
u32 selected = 0;
bool dirty = true;
while(platformIsRunning()) {
inputPoll();
if(inputIsPressed(BUTTON_B)) {
while(core::running()) {
hid::poll();
if(hid::pressed(hid::BUTTON_B)) {
break;
}
if(inputIsPressed(BUTTON_A)) {
if(hid::pressed(hid::BUTTON_A)) {
std::stringstream stream;
stream << "Install the selected ROP?" << "\n";
stream << ropNames[selected];
if(uiPrompt(TOP_SCREEN, stream.str(), true)) {
if(uiPrompt(gpu::SCREEN_TOP, stream.str(), true)) {
u16 userSettingsOffset = 0;
bool result = norRead(0x20, &userSettingsOffset, 2) && norWrite(userSettingsOffset << 3, rops[selected], ROP_SIZE);
bool result = nor::read(0x20, &userSettingsOffset, 2) && nor::write(userSettingsOffset << 3, rops[selected], ROP_SIZE);
std::stringstream resultMsg;
resultMsg << "ROP installation ";
@ -117,16 +122,16 @@ void installROP() {
resultMsg << "succeeded!";
} else {
resultMsg << "failed!" << "\n";
resultMsg << platformGetErrorString(platformGetError());
resultMsg << err::toString(err::get());
}
uiPrompt(TOP_SCREEN, resultMsg.str(), false);
uiPrompt(gpu::SCREEN_TOP, resultMsg.str(), false);
}
dirty = true;
}
if(inputIsPressed(BUTTON_LEFT)) {
if(hid::pressed(hid::BUTTON_LEFT)) {
if(selected == 0) {
selected = ROP_COUNT - 1;
} else {
@ -136,7 +141,7 @@ void installROP() {
dirty = true;
}
if(inputIsPressed(BUTTON_RIGHT)) {
if(hid::pressed(hid::BUTTON_RIGHT)) {
if(selected >= ROP_COUNT - 1) {
selected = 0;
} else {
@ -151,62 +156,81 @@ void installROP() {
stream << "Select a ROP to install." << "\n";
stream << "< " << ropNames[selected] << " >" << "\n";
stream << "Press A to install, B to cancel.";
uiDisplayMessage(TOP_SCREEN, stream.str());
uiDisplayMessage(gpu::SCREEN_TOP, stream.str());
}
}
}
bool installCIA(MediaType destination, const std::string path, const std::string fileName) {
bool installCIA(fs::MediaType destination, const std::string path, const std::string fileName) {
std::string name = fileName;
if(name.length() > 40) {
name.resize(40);
name += "...";
}
FILE* fd = fopen(path.c_str(), "r");
if(!fd) {
std::stringstream resultMsg;
resultMsg << "Install failed!" << "\n";
resultMsg << name << "\n";
resultMsg << "Could not open file." << "\n";
resultMsg << strerror(errno) << "\n";
uiPrompt(gpu::SCREEN_TOP, resultMsg.str(), false);
return false;
}
struct stat st;
fstat(fileno(fd), &st);
std::stringstream batchInstallStream;
batchInstallStream << name << "\n";
installInfo = batchInstallStream.str();
AppResult ret = appInstallFile(destination, path, &onProgress);
app::AppResult ret = app::install(destination, fd, (u64) st.st_size, &onProgress);
prevProgress = -1;
installInfo = "";
if(ret != APP_SUCCESS && platformHasError()) {
Error error = platformGetError();
if(error.module == MODULE_NN_AM && error.description == DESCRIPTION_ALREADY_EXISTS) {
if(ret != app::APP_SUCCESS && err::has()) {
err::Error error = err::get();
if(error.module == err::MODULE_NN_AM && error.description == err::DESCRIPTION_ALREADY_EXISTS) {
std::stringstream overwriteMsg;
overwriteMsg << "Title already installed, overwrite?" << "\n";
overwriteMsg << name;
if(uiPrompt(TOP_SCREEN, overwriteMsg.str(), true)) {
uiDisplayMessage(TOP_SCREEN, "Deleting title...");
AppResult deleteRet = appDelete(appGetCiaInfo(path, destination));
if(deleteRet != APP_SUCCESS) {
if(uiPrompt(gpu::SCREEN_TOP, overwriteMsg.str(), true)) {
uiDisplayMessage(gpu::SCREEN_TOP, "Deleting title...");
app::App app;
app::ciaInfo(&app, path, destination);
app::AppResult deleteRet = app::uninstall(app);
if(deleteRet != app::APP_SUCCESS) {
std::stringstream resultMsg;
resultMsg << "Delete failed!" << "\n";
resultMsg << name << "\n";
resultMsg << appGetResultString(deleteRet);
uiPrompt(TOP_SCREEN, resultMsg.str(), false);
resultMsg << app::resultString(deleteRet);
uiPrompt(gpu::SCREEN_TOP, resultMsg.str(), false);
return false;
}
ret = appInstallFile(destination, path, &onProgress);
ret = app::install(destination, fd, (u64) st.st_size, &onProgress);
prevProgress = -1;
installInfo = "";
} else {
platformSetError(error);
err::set(error);
}
} else {
platformSetError(error);
err::set(error);
}
}
if(ret != APP_SUCCESS) {
if(ret != app::APP_SUCCESS) {
std::stringstream resultMsg;
resultMsg << "Install failed!" << "\n";
resultMsg << name << "\n";
resultMsg << appGetResultString(ret);
uiPrompt(TOP_SCREEN, resultMsg.str(), false);
resultMsg << app::resultString(ret);
uiPrompt(gpu::SCREEN_TOP, resultMsg.str(), false);
return false;
}
@ -224,14 +248,14 @@ bool deleteCIA(const std::string path, const std::string fileName) {
std::stringstream deleteStream;
deleteStream << "Deleting CIA..." << "\n";
deleteStream << name;
uiDisplayMessage(TOP_SCREEN, deleteStream.str());
uiDisplayMessage(gpu::SCREEN_TOP, deleteStream.str());
if(remove(path.c_str()) != 0) {
std::stringstream resultMsg;
resultMsg << "Delete failed!" << "\n";
resultMsg << name << "\n";
resultMsg << strerror(errno);
uiPrompt(TOP_SCREEN, resultMsg.str(), false);
uiPrompt(gpu::SCREEN_TOP, resultMsg.str(), false);
return false;
}
@ -239,15 +263,15 @@ bool deleteCIA(const std::string path, const std::string fileName) {
return true;
}
bool deleteTitle(App app) {
uiDisplayMessage(TOP_SCREEN, "Deleting title...");
AppResult ret = appDelete(app);
bool deleteTitle(app::App app) {
uiDisplayMessage(gpu::SCREEN_TOP, "Deleting title...");
app::AppResult ret = app::uninstall(app);
if(ret != APP_SUCCESS) {
if(ret != app::APP_SUCCESS) {
std::stringstream resultMsg;
resultMsg << "Delete failed!" << "\n";
resultMsg << appGetResultString(ret);
uiPrompt(TOP_SCREEN, resultMsg.str(), false);
resultMsg << app::resultString(ret);
uiPrompt(gpu::SCREEN_TOP, resultMsg.str(), false);
return false;
}
@ -255,15 +279,15 @@ bool deleteTitle(App app) {
return true;
}
bool launchTitle(App app) {
uiDisplayMessage(TOP_SCREEN, "Launching title...");
AppResult ret = appLaunch(app);
bool launchTitle(app::App app) {
uiDisplayMessage(gpu::SCREEN_TOP, "Launching title...");
app::AppResult ret = app::launch(app);
if(ret != APP_SUCCESS) {
if(ret != app::APP_SUCCESS) {
std::stringstream resultMsg;
resultMsg << "Launch failed!" << "\n";
resultMsg << appGetResultString(ret);
uiPrompt(TOP_SCREEN, resultMsg.str(), false);
resultMsg << app::resultString(ret);
uiPrompt(gpu::SCREEN_TOP, resultMsg.str(), false);
return false;
}
@ -272,28 +296,28 @@ bool launchTitle(App app) {
}
bool onLoop() {
bool launcher = platformHasLauncher();
if(launcher && inputIsPressed(BUTTON_START)) {
bool launcher = core::launcher();
if(launcher && hid::pressed(hid::BUTTON_START)) {
exit = true;
return true;
}
bool breakLoop = false;
if(inputIsPressed(BUTTON_L)) {
if(destination == SD) {
destination = NAND;
if(hid::pressed(hid::BUTTON_L)) {
if(destination == fs::SD) {
destination = fs::NAND;
} else {
destination = SD;
destination = fs::SD;
}
freeSpace = fsGetFreeSpace(destination);
freeSpace = fs::freeSpace(destination);
if(mode == DELETE_TITLE || mode == LAUNCH_TITLE) {
breakLoop = true;
}
}
if(inputIsPressed(BUTTON_R)) {
if(hid::pressed(hid::BUTTON_R)) {
if(mode == INSTALL_CIA) {
mode = DELETE_CIA;
} else if(mode == DELETE_CIA) {
@ -307,29 +331,29 @@ bool onLoop() {
}
}
if(mode == INSTALL_CIA && inputIsPressed(BUTTON_Y)) {
if(mode == INSTALL_CIA && hid::pressed(hid::BUTTON_Y)) {
networkInstall();
}
if(inputIsPressed(BUTTON_SELECT)) {
if(hid::pressed(hid::BUTTON_SELECT)) {
installROP();
}
std::stringstream stream;
stream << "Battery: ";
if(platformIsBatteryCharging()) {
if(battery::charging()) {
stream << "Charging";
} else {
for(u32 i = 0; i < platformGetBatteryLevel(); i++) {
for(u32 i = 0; i < battery::level(); i++) {
stream << "|";
}
}
stream << ", WiFi: ";
if(!platformIsWifiConnected()) {
if(!wifi::connected()) {
stream << "Disconnected";
} else {
for(u32 i = 0; i < platformGetWifiLevel(); i++) {
for(u32 i = 0; i < wifi::strength(); i++) {
stream << "|";
}
}
@ -337,7 +361,7 @@ bool onLoop() {
stream << "\n";
stream << "Free Space: " << freeSpace << " bytes (" << std::fixed << std::setprecision(2) << freeSpace / 1024.0f / 1024.0f << "MB)" << "\n";
stream << "Destination: " << (destination == NAND ? "NAND" : "SD") << ", Mode: " << (mode == INSTALL_CIA ? "Install CIA" : mode == DELETE_CIA ? "Delete CIA" : mode == DELETE_TITLE ? "Delete Title" : "Launch Title") << "\n";
stream << "Destination: " << (destination == fs::NAND ? "NAND" : "SD") << ", Mode: " << (mode == INSTALL_CIA ? "Install CIA" : mode == DELETE_CIA ? "Delete CIA" : mode == DELETE_TITLE ? "Delete Title" : "Launch Title") << "\n";
stream << "L - Switch Destination, R - Switch Mode" << "\n";
if(mode == INSTALL_CIA) {
stream << "X - Install all CIAs in the current directory" << "\n";
@ -352,27 +376,34 @@ bool onLoop() {
stream << "\n" << "START - Exit to launcher";
}
u32 screenWidth;
u32 screenHeight;
gpu::getViewportWidth(&screenWidth);
gpu::getViewportHeight(&screenHeight);
std::string str = stream.str();
const std::string title = "FBI v1.4.8";
gputDrawString(title, (gpuGetViewportWidth() - gputGetStringWidth(title, 16)) / 2, (gpuGetViewportHeight() - gputGetStringHeight(title, 16) + gputGetStringHeight(str, 8)) / 2, 16, 16);
gputDrawString(str, (gpuGetViewportWidth() - gputGetStringWidth(str, 8)) / 2, 4, 8, 8);
const std::string title = "FBI v1.4.9";
gput::drawString(title, (screenWidth - gput::getStringWidth(title, 16)) / 2, (screenHeight - gput::getStringHeight(title, 16) + gput::getStringHeight(str, 8)) / 2, 16, 16);
gput::drawString(str, (screenWidth - gput::getStringWidth(str, 8)) / 2, 4, 8, 8);
return breakLoop;
}
int main(int argc, char **argv) {
if(!platformInit(argc)) {
if(!core::init(argc)) {
return 0;
}
freeSpace = fsGetFreeSpace(destination);
while(platformIsRunning()) {
uiInit();
freeSpace = fs::freeSpace(destination);
while(core::running()) {
if(mode == INSTALL_CIA || mode == DELETE_CIA) {
uiDisplayMessage(BOTTOM_SCREEN, "Loading file list...");
uiDisplayMessage(gpu::SCREEN_BOTTOM, "Loading file list...");
std::string fileTarget;
uiSelectFile(&fileTarget, "/", extensions, [&](const std::string currDirectory, bool inRoot, bool &updateList) {
if(inputIsPressed(BUTTON_X)) {
if(hid::pressed(hid::BUTTON_X)) {
std::stringstream confirmMsg;
if(mode == INSTALL_CIA) {
confirmMsg << "Install ";
@ -381,13 +412,13 @@ int main(int argc, char **argv) {
}
confirmMsg << "all CIAs in the current directory?";
if(uiPrompt(TOP_SCREEN, confirmMsg.str(), true)) {
if(uiPrompt(gpu::SCREEN_TOP, confirmMsg.str(), true)) {
bool failed = false;
std::vector<FileInfo> contents = fsGetDirectoryContents(currDirectory);
for(std::vector<FileInfo>::iterator it = contents.begin(); it != contents.end(); it++) {
std::string path = (*it).path;
std::string name = (*it).name;
if(!fsIsDirectory(name) && fsHasExtensions(name, extensions)) {
std::vector<std::string> contents = fs::contents(currDirectory);
for(std::vector<std::string>::iterator it = contents.begin(); it != contents.end(); it++) {
std::string path = *it;
std::string name = fs::fileName(path);
if(!fs::directory(name) && fs::hasExtensions(name, extensions)) {
if(mode == INSTALL_CIA) {
if(!installCIA(destination, path, name)) {
failed = true;
@ -413,10 +444,10 @@ int main(int argc, char **argv) {
}
successMsg << "succeeded!";
uiPrompt(TOP_SCREEN, successMsg.str(), false);
uiPrompt(gpu::SCREEN_TOP, successMsg.str(), false);
}
freeSpace = fsGetFreeSpace(destination);
freeSpace = fs::freeSpace(destination);
}
}
@ -430,12 +461,12 @@ int main(int argc, char **argv) {
}
confirmMsg << "the selected CIA?";
if(uiPrompt(TOP_SCREEN, confirmMsg.str(), true)) {
if(uiPrompt(gpu::SCREEN_TOP, confirmMsg.str(), true)) {
bool success = false;
if(mode == INSTALL_CIA) {
success = installCIA(destination, path, fsGetFileName(path));
success = installCIA(destination, path, fs::fileName(path));
} else {
success = deleteCIA(path, fsGetFileName(path));
success = deleteCIA(path, fs::fileName(path));
}
if(success) {
@ -447,32 +478,32 @@ int main(int argc, char **argv) {
}
successMsg << "succeeded!";
uiPrompt(TOP_SCREEN, successMsg.str(), false);
uiPrompt(gpu::SCREEN_TOP, successMsg.str(), false);
}
freeSpace = fsGetFreeSpace(destination);
freeSpace = fs::freeSpace(destination);
}
return false;
});
} else if(mode == DELETE_TITLE || mode == LAUNCH_TITLE) {
uiDisplayMessage(BOTTOM_SCREEN, "Loading title list...");
uiDisplayMessage(gpu::SCREEN_BOTTOM, "Loading title list...");
App appTarget;
app::App appTarget;
uiSelectApp(&appTarget, destination, [&](bool &updateList) {
return onLoop();
}, [&](App app, bool &updateList) {
}, [&](app::App app, bool &updateList) {
if(mode == DELETE_TITLE) {
if(uiPrompt(TOP_SCREEN, "Delete the selected title?", true) && (destination != NAND || uiPrompt(TOP_SCREEN, "You are about to delete a title from the NAND.\nTHIS HAS THE POTENTIAL TO BRICK YOUR 3DS!\nAre you sure you wish to continue?", true))) {
if(uiPrompt(gpu::SCREEN_TOP, "Delete the selected title?", true) && (destination != fs::NAND || uiPrompt(gpu::SCREEN_TOP, "You are about to delete a title from the NAND.\nTHIS HAS THE POTENTIAL TO BRICK YOUR 3DS!\nAre you sure you wish to continue?", true))) {
if(deleteTitle(app)) {
uiPrompt(TOP_SCREEN, "Delete succeeded!", false);
uiPrompt(gpu::SCREEN_TOP, "Delete succeeded!", false);
}
updateList = true;
freeSpace = fsGetFreeSpace(destination);
freeSpace = fs::freeSpace(destination);
}
} else if(mode == LAUNCH_TITLE) {
if(uiPrompt(TOP_SCREEN, "Launch the selected title?", true)) {
if(uiPrompt(gpu::SCREEN_TOP, "Launch the selected title?", true)) {
if(launchTitle(app)) {
while(true) {
}
@ -489,6 +520,8 @@ int main(int argc, char **argv) {
}
}
platformCleanup();
uiCleanup();
core::exit();
return 0;
}

720
source/ui.cpp Normal file
View File

@ -0,0 +1,720 @@
#include "ui.hpp"
#include <citrus/core.hpp>
#include <citrus/gput.hpp>
#include <citrus/hid.hpp>
#include <arpa/inet.h>
#include <sys/dirent.h>
#include <sys/errno.h>
#include <sys/unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <iomanip>
#include <sstream>
#include <stack>
using namespace ctr;
struct uiAlphabetize {
inline bool operator()(SelectableElement a, SelectableElement b) {
return strcasecmp(a.name.c_str(), b.name.c_str()) < 0;
}
};
u32 selectorTexture;
u32 selectorVbo;
void uiInit() {
gpu::createTexture(&selectorTexture);
gpu::setTextureInfo(selectorTexture, 64, 64, gpu::PIXEL_RGBA8, TEXTURE_MIN_FILTER(gpu::FILTER_NEAREST) | TEXTURE_MAG_FILTER(gpu::FILTER_NEAREST));
void* textureData;
gpu::getTextureData(selectorTexture, &textureData);
memset(textureData, 0xFF, 64 * 64 * 4);
gpu::createVbo(&selectorVbo);
gpu::setVboAttributes(selectorVbo, ATTRIBUTE(0, 3, gpu::ATTR_FLOAT) | ATTRIBUTE(1, 2, gpu::ATTR_FLOAT) | ATTRIBUTE(2, 4, gpu::ATTR_FLOAT), 3);
const float vboData[] = {
0.0f, 0.0f, -0.1f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f,
320.0f, 0.0f, -0.1f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f,
320.0f, 12.0f, -0.1f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
320.0f, 12.0f, -0.1f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
0.0f, 12.0f, -0.1f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
0.0f, 0.0f, -0.1f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f,
};
gpu::setVboData(selectorVbo, vboData, 6 * 9, gpu::PRIM_TRIANGLES);
}
void uiCleanup() {
if(selectorTexture != 0) {
gpu::freeTexture(selectorTexture);
selectorTexture = 0;
}
if(selectorVbo != 0) {
gpu::freeVbo(selectorVbo);
selectorVbo = 0;
}
}
bool uiSelect(SelectableElement* selected, std::vector<SelectableElement> elements, std::function<bool(std::vector<SelectableElement> &currElements, SelectableElement currElement, bool &elementsDirty, bool &resetCursorIfDirty)> onLoop, std::function<bool(SelectableElement select)> onSelect, bool useTopScreen, bool alphabetize, bool dpadPageScroll) {
int cursor = 0;
int scroll = 0;
u32 selectionScroll = 0;
u64 selectionScrollEndTime = 0;
u64 lastScrollTime = 0;
bool elementsDirty = false;
bool resetCursorIfDirty = true;
if(alphabetize) {
std::sort(elements.begin(), elements.end(), uiAlphabetize());
}
hid::Button leftScrollButton = dpadPageScroll ? hid::BUTTON_LEFT : hid::BUTTON_L;
hid::Button rightScrollButton = dpadPageScroll ? hid::BUTTON_RIGHT : hid::BUTTON_R;
bool canPageUp = false;
bool canPageDown = false;
while(core::running()) {
hid::poll();
if(hid::pressed(hid::BUTTON_A)) {
SelectableElement select = elements.at((u32) cursor);
if(onSelect == NULL || onSelect(select)) {
*selected = select;
return true;
}
}
if(canPageUp) {
canPageUp = !hid::released(leftScrollButton);
} else if(hid::pressed(leftScrollButton)) {
canPageUp = true;
}
if(canPageDown) {
canPageDown = !hid::released(rightScrollButton);
} else if(hid::pressed(rightScrollButton)) {
canPageDown = true;
}
if(hid::held(hid::BUTTON_DOWN) || hid::held(hid::BUTTON_UP) || (hid::held(leftScrollButton) && canPageUp) || (hid::held(rightScrollButton) && canPageDown)) {
if(lastScrollTime == 0 || core::time() - lastScrollTime >= 180) {
if(hid::held(hid::BUTTON_DOWN) && cursor < (int) elements.size() - 1) {
cursor++;
if(cursor >= scroll + 20) {
scroll++;
}
}
if(canPageDown && hid::held(rightScrollButton) && cursor < (int) elements.size() - 1) {
cursor += 20;
if(cursor >= (int) elements.size()) {
cursor = elements.size() - 1;
if(cursor < 0) {
cursor = 0;
}
}
scroll += 20;
if(scroll >= (int) elements.size() - 19) {
scroll = elements.size() - 20;
if(scroll < 0) {
scroll = 0;
}
}
}
if(hid::held(hid::BUTTON_UP) && cursor > 0) {
cursor--;
if(cursor < scroll) {
scroll--;
}
}
if(canPageUp && hid::held(leftScrollButton) && cursor > 0) {
cursor -= 20;
if(cursor < 0) {
cursor = 0;
}
scroll -= 20;
if(scroll < 0) {
scroll = 0;
}
}
selectionScroll = 0;
selectionScrollEndTime = 0;
lastScrollTime = core::time();
}
} else {
lastScrollTime = 0;
}
gpu::setViewport(gpu::SCREEN_BOTTOM, 0, 0, BOTTOM_WIDTH, BOTTOM_HEIGHT);
gput::setOrtho(0, BOTTOM_WIDTH, 0, BOTTOM_HEIGHT, -1, 1);
gpu::clear();
u32 screenWidth;
u32 screenHeight;
gpu::getViewportWidth(&screenWidth);
gpu::getViewportHeight(&screenHeight);
for(std::vector<SelectableElement>::iterator it = elements.begin() + scroll; it != elements.begin() + scroll + 20 && it != elements.end(); it++) {
SelectableElement element = *it;
int index = it - elements.begin();
u8 color = 255;
int offset = 0;
float itemHeight = gput::getStringHeight(element.name, 8) + 4;
if(index == cursor) {
color = 0;
gput::pushModelView();
gput::translate(0, (screenHeight - 1) - ((index - scroll + 1) * itemHeight), 0);
gpu::bindTexture(gpu::TEXUNIT0, selectorTexture);
gpu::drawVbo(selectorVbo);
gput::popModelView();
u32 width = (u32) gput::getStringWidth(element.name, 8);
if(width > screenWidth) {
if(selectionScroll + screenWidth >= width) {
if(selectionScrollEndTime == 0) {
selectionScrollEndTime = core::time();
} else if(core::time() - selectionScrollEndTime >= 4000) {
selectionScroll = 0;
selectionScrollEndTime = 0;
}
} else {
selectionScroll++;
}
}
offset = -selectionScroll;
}
gput::drawString(element.name, offset, (screenHeight - 1) - ((index - scroll + 1) * itemHeight) + 2, 8, 8, color, color, color);
}
gpu::flushCommands();
gpu::flushBuffer();
gpu::setViewport(gpu::SCREEN_TOP, 0, 0, TOP_WIDTH, TOP_HEIGHT);
gput::setOrtho(0, TOP_WIDTH, 0, TOP_HEIGHT, -1, 1);
if(useTopScreen) {
gpu::clear();
SelectableElement currSelected = elements.at((u32) cursor);
if(currSelected.details.size() != 0) {
std::stringstream details;
for(std::vector<std::string>::iterator it = currSelected.details.begin(); it != currSelected.details.end(); it++) {
details << *it << "\n";
}
gput::drawString(details.str(), 0, screenHeight - 1 - gput::getStringHeight(details.str(), 8), 8, 8);
}
}
bool result = onLoop != NULL && onLoop(elements, elements.at((u32) cursor), elementsDirty, resetCursorIfDirty);
if(elementsDirty) {
if(resetCursorIfDirty) {
cursor = 0;
scroll = 0;
} else if(cursor >= (int) elements.size()) {
cursor = elements.size() - 1;
if(cursor < 0) {
cursor = 0;
}
scroll = elements.size() - 20;
if(scroll < 0) {
scroll = 0;
}
}
selectionScroll = 0;
selectionScrollEndTime = 0;
if(alphabetize) {
std::sort(elements.begin(), elements.end(), uiAlphabetize());
}
elementsDirty = false;
resetCursorIfDirty = true;
}
if(useTopScreen) {
gpu::flushCommands();
gpu::flushBuffer();
}
gpu::swapBuffers(true);
if(result) {
break;
}
}
return false;
}
void uiGetDirContents(std::vector<SelectableElement> &elements, const std::string directory, std::vector<std::string> extensions) {
elements.clear();
elements.push_back({".", "."});
elements.push_back({"..", ".."});
std::vector<std::string> contents = fs::contents(directory);
for(std::vector<std::string>::iterator it = contents.begin(); it != contents.end(); it++) {
const std::string path = *it;
const std::string name = fs::fileName(path);
if(fs::directory(path)) {
elements.push_back({path, name});
} else if(fs::hasExtensions(path, extensions)) {
struct stat st;
stat(path.c_str(), &st);
std::vector<std::string> info;
std::stringstream stream;
stream << "File Size: " << ((u32) st.st_size) << " bytes (" << std::fixed << std::setprecision(2) << ((u32) st.st_size) / 1024.0f / 1024.0f << "MB)";
info.push_back(stream.str());
std::string extension = fs::extension(path);
if(extension.compare("cia") == 0) {
app::App app;
app::ciaInfo(&app, path, fs::SD);
std::stringstream titleId;
titleId << "0x" << std::setfill('0') << std::setw(16) << std::hex << app.titleId;
std::stringstream uniqueId;
uniqueId << "0x" << std::setfill('0') << std::setw(8) << std::hex << app.uniqueId;
std::stringstream version;
version << "0x" << std::setfill('0') << std::hex << app.version;
std::stringstream size;
size << "" << app.size << " bytes (" << std::fixed << std::setprecision(2) << app.size / 1024.0f / 1024.0f << "MB)";
info.push_back("Installed Size: " + size.str());
info.push_back("Title ID: " + titleId.str());
info.push_back("Unique ID: " + uniqueId.str());
info.push_back("Product Code: " + std::string(app.productCode));
info.push_back("Platform: " + app::platformString(app.platform));
info.push_back("Category: " + app::categoryString(app.category));
info.push_back("Version: " + version.str());
}
elements.push_back({path, name, info});
}
}
}
bool uiSelectFile(std::string* selectedFile, const std::string rootDirectory, std::vector<std::string> extensions, std::function<bool(const std::string currDirectory, bool inRoot, bool &updateList)> onLoop, std::function<bool(const std::string path, bool &updateList)> onSelect, bool useTopScreen, bool dpadPageScroll) {
std::stack<std::string> directoryStack;
std::string currDirectory = rootDirectory;
std::vector<SelectableElement> elements;
uiGetDirContents(elements, currDirectory, extensions);
bool updateContents = false;
bool resetCursor = true;
SelectableElement selected;
bool result = uiSelect(&selected, elements, [&](std::vector<SelectableElement> &currElements, SelectableElement currElement, bool &elementsDirty, bool &resetCursorIfDirty) {
if(onLoop != NULL && onLoop(currDirectory, directoryStack.empty(), updateContents)) {
return true;
}
if(hid::pressed(hid::BUTTON_B) && !directoryStack.empty()) {
currDirectory = directoryStack.top();
directoryStack.pop();
updateContents = true;
}
if(updateContents) {
uiGetDirContents(currElements, currDirectory, extensions);
elementsDirty = true;
resetCursorIfDirty = resetCursor;
updateContents = false;
resetCursor = true;
}
return false;
}, [&](SelectableElement select) {
if(select.name.compare(".") == 0) {
return false;
} else if(select.name.compare("..") == 0) {
if(!directoryStack.empty()) {
currDirectory = directoryStack.top();
directoryStack.pop();
updateContents = true;
}
return false;
} else if(fs::directory(select.id)) {
directoryStack.push(currDirectory);
currDirectory = select.id;
updateContents = true;
return false;
}
bool updateList = false;
bool ret = onSelect(select.id, updateList);
if(updateList) {
updateContents = true;
resetCursor = false;
}
return ret;
}, useTopScreen, true, dpadPageScroll);
if(result) {
*selectedFile = selected.id;
}
return result;
}
void uiGetApps(std::vector<SelectableElement> &elements, std::vector<app::App> apps) {
elements.clear();
for(std::vector<app::App>::iterator it = apps.begin(); it != apps.end(); it++) {
app::App app = *it;
std::stringstream titleId;
titleId << "0x" << std::setfill('0') << std::setw(16) << std::hex << app.titleId;
std::stringstream uniqueId;
uniqueId << "0x" << std::setfill('0') << std::setw(8) << std::hex << app.uniqueId;
std::stringstream version;
version << "0x" << std::setfill('0') << std::hex << app.version;
std::stringstream size;
size << "" << app.size << " bytes (" << std::fixed << std::setprecision(2) << app.size / 1024.0f / 1024.0f << "MB)";
std::vector<std::string> details;
details.push_back("Title ID: " + titleId.str());
details.push_back("Unique ID: " + uniqueId.str());
details.push_back("Product Code: " + std::string(app.productCode));
details.push_back("Platform: " + app::platformString(app.platform));
details.push_back("Category: " + app::categoryString(app.category));
details.push_back("Version: " + version.str());
details.push_back("Size: " + size.str());
elements.push_back({titleId.str(), app.productCode, details});
}
if(elements.size() == 0) {
elements.push_back({"None", "None"});
}
}
bool uiFindApp(app::App* result, std::string id, std::vector<app::App> apps) {
for(std::vector<app::App>::iterator it = apps.begin(); it != apps.end(); it++) {
app::App app = *it;
if(app.titleId == (u64) strtoll(id.c_str(), NULL, 16)) {
*result = app;
return true;
}
}
return false;
}
bool uiSelectApp(app::App* selectedApp, fs::MediaType mediaType, std::function<bool(bool &updateList)> onLoop, std::function<bool(app::App app, bool &updateList)> onSelect, bool useTopScreen, bool dpadPageScroll) {
std::vector<SelectableElement> elements;
std::vector<app::App> apps;
app::list(&apps, mediaType);
uiGetApps(elements, apps);
bool updateContents = false;
SelectableElement selected;
bool result = uiSelect(&selected, elements, [&](std::vector<SelectableElement> &currElements, SelectableElement currElement, bool &elementsDirty, bool &resetCursorIfDirty) {
if(onLoop != NULL && onLoop(updateContents)) {
return true;
}
if(updateContents) {
app::list(&apps, mediaType);
uiGetApps(currElements, apps);
elementsDirty = true;
resetCursorIfDirty = false;
updateContents = false;
}
return false;
}, [&](SelectableElement select) {
if(select.name.compare("None") != 0) {
app::App app;
if(uiFindApp(&app, select.id, apps)) {
bool updateList = false;
bool ret = onSelect(app, updateList);
if(updateList) {
updateContents = true;
}
return ret;
}
}
return false;
}, useTopScreen, true, dpadPageScroll);
if(result) {
app::App app;
if(uiFindApp(&app, selected.id, apps)) {
*selectedApp = app;
}
}
return result;
}
void uiDisplayMessage(gpu::Screen screen, const std::string message) {
u32 width = screen == gpu::SCREEN_TOP ? TOP_WIDTH : BOTTOM_WIDTH;
u32 height = screen == gpu::SCREEN_TOP ? TOP_HEIGHT : BOTTOM_HEIGHT;
gpu::setViewport(screen, 0, 0, width, height);
gput::setOrtho(0, width, 0, height, -1, 1);
gpu::clear();
gput::drawString(message, (width - gput::getStringWidth(message, 8)) / 2, (height - gput::getStringHeight(message, 8)) / 2, 8, 8);
gpu::flushCommands();
gpu::flushBuffer();
gpu::swapBuffers(true);
gpu::setViewport(gpu::SCREEN_TOP, 0, 0, TOP_WIDTH, TOP_HEIGHT);
gput::setOrtho(0, TOP_WIDTH, 0, TOP_HEIGHT, -1, 1);
}
bool uiPrompt(gpu::Screen screen, const std::string message, bool question) {
std::stringstream stream;
stream << message << "\n";
if(question) {
stream << "Press A to confirm, B to cancel." << "\n";
} else {
stream << "Press Start to continue." << "\n";
}
bool result = false;
std::string str = stream.str();
while(core::running()) {
hid::poll();
if(question) {
if(hid::pressed(hid::BUTTON_A)) {
result = true;
break;
}
if(hid::pressed(hid::BUTTON_B)) {
result = false;
break;
}
} else {
if(hid::pressed(hid::BUTTON_START)) {
result = true;
break;
}
}
uiDisplayMessage(screen, str);
}
hid::poll();
return result;
}
void uiDisplayProgress(gpu::Screen screen, const std::string operation, const std::string details, bool quickSwap, u32 progress) {
std::stringstream stream;
stream << operation << ": [";
u32 progressBars = progress / 4;
for(u32 i = 0; i < 25; i++) {
if(i < progressBars) {
stream << '|';
} else {
stream << ' ';
}
}
stream << "] " << std::setfill(' ') << std::setw(3) << progress << "%" << "\n";
stream << details << "\n";
std::string str = stream.str();
u32 width = screen == gpu::SCREEN_TOP ? TOP_WIDTH : BOTTOM_WIDTH;
u32 height = screen == gpu::SCREEN_TOP ? TOP_HEIGHT : BOTTOM_HEIGHT;
gpu::setViewport(screen, 0, 0, width, height);
gput::setOrtho(0, width, 0, height, -1, 1);
gpu::clear();
gput::drawString(str, (width - gput::getStringWidth(str, 8)) / 2, (height - gput::getStringHeight(str, 8)) / 2, 8, 8);
gpu::flushCommands();
gpu::flushBuffer();
gpu::swapBuffers(!quickSwap);
gpu::setViewport(gpu::SCREEN_TOP, 0, 0, TOP_WIDTH, TOP_HEIGHT);
gput::setOrtho(0, TOP_WIDTH, 0, TOP_HEIGHT, -1, 1);
}
u64 ntohll(u64 value) {
static const int num = 42;
if(*((char*) &num) == num) {
return (((uint64_t) htonl((u32) value)) << 32) + htonl((u32) (value >> 32));
} else {
return value;
}
}
int socketListen(u16 port) {
int fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd < 0) {
return -1;
}
struct sockaddr_in address;
memset(&address, 0, sizeof(address));
address.sin_family = AF_INET;
address.sin_addr.s_addr = htonl(INADDR_ANY);
address.sin_port = htons(port);
if(bind(fd, (struct sockaddr*) &address, sizeof(address)) != 0) {
closesocket(fd);
return -1;
}
int flags = fcntl(fd, F_GETFL);
if(flags == -1) {
closesocket(fd);
return -1;
}
if(fcntl(fd, F_SETFL, flags | O_NONBLOCK) != 0) {
closesocket(fd);
return -1;
}
if(listen(fd, 10) != 0) {
closesocket(fd);
return -1;
}
return fd;
}
FILE* socketAccept(int listeningSocket) {
struct sockaddr_in addr;
socklen_t addrSize = sizeof(addr);
int afd = accept(listeningSocket, (struct sockaddr*) &addr, &addrSize);
if(afd < 0) {
return NULL;
}
int flags = fcntl(afd, F_GETFL);
if(flags == -1) {
closesocket(afd);
return NULL;
}
if(fcntl(afd, F_SETFL, flags | O_NONBLOCK) != 0) {
closesocket(afd);
return NULL;
}
return fdopen(afd, "rw");
}
RemoteFile uiAcceptRemoteFile(gpu::Screen screen, std::function<void(std::stringstream& infoStream)> onWait) {
// meh
for(u32 i = 0; i < 2; i++) {
uiDisplayMessage(screen == gpu::SCREEN_TOP ? gpu::SCREEN_BOTTOM : gpu::SCREEN_TOP, "");
}
uiDisplayMessage(screen, "Initializing socket...");
int listen = socketListen(5000);
if(listen < 0) {
std::stringstream errStream;
errStream << "Failed to initialize socket." << "\n" << strerror(errno) << "\n";
uiPrompt(screen, errStream.str(), false);
return {NULL, 0};
}
std::stringstream baseInfoStream;
baseInfoStream << "Waiting for peer to connect..." << "\n";
baseInfoStream << "IP: " << inet_ntoa({(u32) gethostid()}) << "\n";
baseInfoStream << "Press B to cancel." << "\n";
std::string baseInfo = baseInfoStream.str();
FILE* socket;
while((socket = socketAccept(listen)) == NULL) {
if(!core::running()) {
close(listen);
return {NULL, 0};
}
if(errno != EAGAIN && errno != EWOULDBLOCK && errno != EINPROGRESS) {
close(listen);
std::stringstream errStream;
errStream << "Failed to accept peer." << "\n" << strerror(errno) << "\n";
uiPrompt(screen, errStream.str(), false);
return {NULL, 0};
}
hid::poll();
if(hid::pressed(hid::BUTTON_B)) {
close(listen);
return {NULL, 0};
}
std::stringstream infoStream;
infoStream << baseInfo;
onWait(infoStream);
uiDisplayMessage(screen, infoStream.str());
}
close(listen);
uiDisplayMessage(screen, "Reading info...\nPress B to cancel.");
u64 fileSize = 0;
u64 bytesRead = 0;
while(bytesRead < sizeof(fileSize)) {
if(!core::running()) {
fclose(socket);
return {NULL, 0};
}
size_t currBytesRead = fread(&fileSize + bytesRead, 1, (size_t) (sizeof(fileSize) - bytesRead), socket);
if(currBytesRead > 0) {
bytesRead += currBytesRead;
}
if(ferror(socket) && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINPROGRESS) {
fclose(socket);
std::stringstream errStream;
errStream << "Failed to read info." << "\n" << strerror(errno) << "\n";
uiPrompt(screen, errStream.str(), false);
return {NULL, 0};
}
hid::poll();
if(hid::pressed(hid::BUTTON_B)) {
fclose(socket);
return {NULL, 0};
}
}
fileSize = ntohll(fileSize);
return {socket, fileSize};
}

32
source/ui.hpp Normal file
View File

@ -0,0 +1,32 @@
#pragma once
#include <citrus/app.hpp>
#include <citrus/fs.hpp>
#include <citrus/gpu.hpp>
#include <citrus/types.hpp>
#include <functional>
#include <string>
#include <vector>
typedef struct {
std::string id;
std::string name;
std::vector<std::string> details;
} SelectableElement;
typedef struct {
FILE* fd;
u64 fileSize;
} RemoteFile;
void uiInit();
void uiCleanup();
bool uiSelect(SelectableElement* selected, std::vector<SelectableElement> elements, std::function<bool(std::vector<SelectableElement> &currElements, SelectableElement currElement, bool &elementsDirty, bool &resetCursorIfDirty)> onLoop, std::function<bool(SelectableElement select)> onSelect, bool useTopScreen = true, bool alphabetize = true, bool dpadPageScroll = true);
bool uiSelectFile(std::string* selectedFile, const std::string rootDirectory, std::vector<std::string> extensions, std::function<bool(const std::string currDirectory, bool inRoot, bool &updateList)> onLoop, std::function<bool(const std::string path, bool &updateList)> onSelect, bool useTopScreen = true, bool dpadPageScroll = true);
bool uiSelectApp(ctr::app::App* selectedApp, ctr::fs::MediaType mediaType, std::function<bool(bool &updateList)> onLoop, std::function<bool(ctr::app::App app, bool &updateList)> onSelect, bool useTopScreen = true, bool dpadPageScroll = true);
void uiDisplayMessage(ctr::gpu::Screen screen, const std::string message);
bool uiPrompt(ctr::gpu::Screen screen, const std::string message, bool question);
void uiDisplayProgress(ctr::gpu::Screen screen, const std::string operation, const std::string details, bool quickSwap, u32 progress);
RemoteFile uiAcceptRemoteFile(ctr::gpu::Screen screen, std::function<void(std::stringstream& infoStream)> onWait = [&](std::stringstream& infoStream){});