#include #include #include #include "appStates/saveCreateState.hpp" #include "appStates/taskState.hpp" #include "filesystem/filesystem.hpp" #include "system/input.hpp" #include "system/task.hpp" #include "stringUtil.hpp" #include "ui/popMessage.hpp" #include "jksv.hpp" namespace { // This is to make typing some stuff easier. using titleIDPair = std::pair; // Panel name const std::string PANEL_NAME = "saveCreationPanel"; // Strings used const std::string THREAD_STATUS_CREATING_SAVE_DATA = "threadStatusCreatingSaveData"; const std::string SAVE_DATA_CREATED_FOR_USER = "saveDataCreatedForUser"; const std::string SAVE_DATA_CREATION_FAILED = "saveDataCreationFailed"; const std::string SWKBD_ENTER_SAVE_INDEX = "swkbdSaveIndex"; } static bool sortTitleList(const titleIDPair &titlePairA, const titleIDPair &titlePairB) { // This is bad. I'm assuming there will be a difference before end is hit... int titleLength = titlePairA.second.length(); // Codepoints from title. uint32_t codepointA; uint32_t codepointB; for (int i = 0, j = 0; i < titleLength;) { ssize_t titleAUnitCount = decode_utf8(&codepointA, reinterpret_cast(&titlePairA.second.c_str()[i])); ssize_t titleBUnitCount = decode_utf8(&codepointB, reinterpret_cast(&titlePairB.second.c_str()[j])); if (codepointA != codepointB) { return codepointA < codepointB; } i += titleAUnitCount; j += titleBUnitCount; } return false; } // This is the data needed for the creation task struct saveCreationData : sys::taskData { uint64_t titleID; FsSaveDataType saveDataType; data::user *currentUser; }; // Save creation for single title void createSaveData(sys::task *task, sys::sharedTaskData sharedData) { if (sharedData == nullptr) { task->finished(); return; } // Cast to correct type std::shared_ptr dataIn = static_pointer_cast(sharedData); // Working title data::titleInfo *workingTitleInfo = data::getTitleInfoByTitleID(dataIn->titleID); // Set task status std::string taskStatus = stringUtil::getFormattedString(ui::strings::getCString(THREAD_STATUS_CREATING_SAVE_DATA, 0), workingTitleInfo->getTitle().c_str()); task->setThreadStatus(taskStatus); // This is needed for cache saves uint16_t cacheIndex = 0; if (dataIn->saveDataType == FsSaveDataType_Cache) { std::string defaultCacheText = ui::strings::getString(SWKBD_ENTER_SAVE_INDEX, 0); std::string cacheIndexString = sys::input::getString(SwkbdType_NumPad, "0", defaultCacheText, 2); cacheIndex = std::stoul(cacheIndexString, NULL, 10); } if (fs::createSaveDataFileSystem(dataIn->saveDataType, dataIn->titleID, dataIn->currentUser->getAccountID(), cacheIndex)) { // Reload user data. Hope for no memory corruption. To do: something about that. data::loadUserSaveInfo(); // Pop it std::string successString = stringUtil::getFormattedString(ui::strings::getCString(SAVE_DATA_CREATED_FOR_USER, 0), dataIn->currentUser->getUsername().c_str()); ui::popMessage::newMessage(successString, ui::popMessage::POPMESSAGE_DEFAULT_TICKS); } else { ui::popMessage::newMessage(ui::strings::getString(SAVE_DATA_CREATION_FAILED, 0), ui::popMessage::POPMESSAGE_DEFAULT_TICKS); } task->finished(); } saveCreateState::saveCreateState(data::user *currentUser, FsSaveDataType saveDataType) : m_CurrentUser(currentUser), m_SaveCreatePanel(std::make_unique(PANEL_NAME, 512, ui::PANEL_SIDE_RIGHT)), m_SaveCreateMenu(std::make_unique(8, 8, 492, 20, 6)), m_SaveDataType(saveDataType) { // Get title map data::titleMap titleMap = data::getTitleMap(); // Loop through and make vector for (auto &titleInfo : titleMap) { switch (saveDataType) { case FsSaveDataType_Account: { if (titleInfo.second.hasAccountSaveData()) { m_TitleList.push_back(std::make_pair(titleInfo.first, titleInfo.second.getTitle())); } } break; case FsSaveDataType_Bcat: { if (titleInfo.second.hasBCATSaveData()) { m_TitleList.push_back(std::make_pair(titleInfo.first, titleInfo.second.getTitle())); } } break; case FsSaveDataType_Device: { if (titleInfo.second.hasDeviceSaveData()) { m_TitleList.push_back(std::make_pair(titleInfo.first, titleInfo.second.getTitle())); } } break; case FsSaveDataType_Cache: { if (titleInfo.second.hasCacheSaveData()) { m_TitleList.push_back(std::make_pair(titleInfo.first, titleInfo.second.getTitle())); } } break; default: break; } } // Sort vector std::sort(m_TitleList.begin(), m_TitleList.end(), sortTitleList); // Copy to menu... for (auto &pair : m_TitleList) { m_SaveCreateMenu->addOption(pair.second); } } saveCreateState::~saveCreateState() {} void saveCreateState::update(void) { m_SaveCreatePanel->update(); m_SaveCreateMenu->update(); if (sys::input::buttonDown(HidNpadButton_A)) { // Data we're sending std::shared_ptr creationData = std::make_shared(); creationData->currentUser = m_CurrentUser; creationData->saveDataType = m_SaveDataType; creationData->titleID = m_TitleList.at(m_SaveCreateMenu->getSelected()).first; sharedAppState saveCreationTask = createNewTask(createSaveData, creationData); jksv::pushState(saveCreationTask); } else if (sys::input::buttonDown(HidNpadButton_B)) { m_SaveCreatePanel->closePanel(); } else if (m_SaveCreatePanel->isClosed()) { appState::deactivateState(); } } void saveCreateState::render(void) { // Get render target graphics::sdlTexture renderTarget = m_SaveCreatePanel->getPanelRenderTarget(); // Clear graphics::textureClear(renderTarget.get(), COLOR_SLIDE_PANEL_TARGET); // Render menu to it m_SaveCreateMenu->render(renderTarget.get()); // Render m_SaveCreatePanel->render(); }