mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-11-22 02:06:41 +00:00
Add DataTransferTaskFrame and GameCardImageDumpTaskFrame classes
DataTransferTaskFrame is a template class that's derived from brls::AppletFrame, which automatically starts a background task using an internal object belonging to a class derived from DataTransferTask. A DataTransferProgressDisplay view is used to show progress updates. If the background task hits an error, the class takes care of switching to an ErrorFrame view with the corresponding error message. GameCardImageDumpTaskFrame is a derived class of DataTransferTaskFrame that uses a GameCardImageDumpTask object as its background task. In layman's terms, this provides a way to fully dump gamecard images using the new UI. DataTransferTaskFrame depends on the newly added is_base_template helper from goneskiing to check if the class for the provided task is derived from DataTransferTask. Other changes include: * DataTransferProgressDisplay: rename setProgress() method to SetProgress(). * DataTransferTask: move post-task-execution code into its own new private method, PostExecutionCallback(), and update both OnCancelled() and OnPostExecute() callbacks to invoke it. * DataTransferTask: update OnProgressUpdate() to allow sending a last progress update to all event subscribers even if the background task was cancelled. * DataTransferTask: update OnProgressUpdate() to allow sending a first progress update if no data has been transferred but the total transfer size is already known. * DataTransferTask: update OnProgressUpdate() to avoid calculating the ETA if the speed isn't greater than 0. * DumpOptionsFrame: remove UpdateOutputStorages() method. * DumpOptionsFrame: update class to use the cached output storage value from our RootView. * DumpOptionsFrame: add GenerateOutputStoragesVector() method, which is used to avoid setting dummy options while initializing the output storages SelectListItem. * DumpOptionsFrame: update UMS task callback to add the rest of the leftover logic from UpdateOutputStorages(). * DumpOptionsFrame: update RegisterButtonListener() to use a wrapper callback around the user-provided callback to check if the USB host was selected as the output storage but no USB host connection is available. * ErrorFrame: use const references for all input string arguments. * FileWriter: fix a localization stirng name typo. * FileWriter: fix an exception that was previously being thrown by a fmt::format() call because of a wrong format specifier. * FocusableItem: add a static assert to check if the provided ViewType is derived from brls::View. * gamecard: redefine global gamecard status variable as an atomic unsigned 8-bit integer, which fixes a "status-hopping" issue previously experienced by repeating tasks running under other threads that periodically call gamecardGetStatus(). * GameCardImageDumpOptionsFrame: define GAMECARD_TOGGLE_ITEM macro, which is used to initialize all ToggleListItem elements from the view. * GameCardImageDumpOptionsFrame: update button callback to push a GameCardImageDumpTaskFrame view onto the borealis stack. * GameCardImageDumpTask: move class into its own header and module files. * GameCardImageDumpTask: update class to also take in the checksum lookup method (not yet implemented). * GameCardImageDumpTask: update class to send its first progress update as soon as the gamecard image size is known. * GameCardImageDumpTask: update class to avoid returning a string if the task was cancelled -- DataTransferTaskFrame offers logic to display the appropiate cancel message on its own. * GameCardTab: update PopulateList() method to display the new version information available in TitleGameCardApplicationMetadataEntry elements as part of the generated TitlesTabItem objects. * i18n: add new localization strings. * OptionsTab: update background task callback logic to handle task cancellation, reflecting the changes made to DataTransferTask. * OptionsTab: reflect changes made to DataTransferProgressDisplay. * RootView: cache the currently selected output storage value at all times, which is propagated throughout different parts of the UI. Getter and setter helpers have been added to operate with this value. * RootView: add GetUsbHostSpeed() helper, which can be used by child views to retrieve the USB host speed on demand. * RootView: update UMS task callback to automatically reset the cached output storage value back to the SD card if a UMS device was previously selected. * title: define TitleGameCardApplicationMetadataEntry struct, which also holds version-specific information retrieved from the gamecard titles. * title: refactor titleGetGameCardApplicationMetadataEntries() to return a dynamically allocated array of TitleGameCardApplicationMetadataEntry elements. * usb: redefine global endpoint max packet size variable as an atomic unsigned 16-bit integer, which fixes a "status-hopping" issue previously experienced by repeating tasks running under other threads that periodically call usbIsReady(). * UsbHostTask: add GetUsbHostSpeed() method.
This commit is contained in:
parent
32c097c055
commit
3e10421ec9
32 changed files with 639 additions and 268 deletions
|
@ -113,7 +113,7 @@ namespace nxdt::tasks
|
|||
if (!this->m_future.valid()) return;
|
||||
|
||||
/* Wait until a result is provided by the task thread. */
|
||||
/* Avoid rethrowing any exceptions here - program execution could end if another exception has already been rethrown. */
|
||||
/* Avoid rethrowing any exceptions here -- program execution could end if another exception has already been rethrown. */
|
||||
m_future.wait();
|
||||
}
|
||||
|
||||
|
|
|
@ -111,7 +111,7 @@ void logFlushLogFile(void);
|
|||
void logCloseLogFile(void);
|
||||
|
||||
/// Returns a pointer to a dynamically allocated buffer that holds the last error message string, or NULL if there's none.
|
||||
/// The allocated buffer must be freed by the calling function using free().
|
||||
/// The allocated buffer must be freed by the caller using free().
|
||||
char *logGetLastMessage(void);
|
||||
|
||||
/// (Un)locks the log mutex. Can be used to block other threads and prevent them from writing data to the logfile.
|
||||
|
|
|
@ -179,7 +179,7 @@ bool utilsDeleteDirectoryRecursively(const char *path);
|
|||
/// A path separator is automatically placed between the provided prefix and the filename if the prefix doesn't end with one.
|
||||
/// A dot *isn't* automatically placed between the filename and the provided extension -- if required, it must be provided as part of the extension string.
|
||||
/// Furthermore, if the full length for the generated path is >= FS_MAX_PATH, NULL will be returned.
|
||||
/// The allocated buffer must be freed by the calling function using free().
|
||||
/// The allocated buffer must be freed by the caller using free().
|
||||
char *utilsGeneratePath(const char *prefix, const char *filename, const char *extension);
|
||||
|
||||
/// Prints an error message using the standard console output and waits for the user to press a button.
|
||||
|
|
|
@ -46,6 +46,13 @@ typedef struct {
|
|||
u8 *icon; ///< JPEG icon data.
|
||||
} TitleApplicationMetadata;
|
||||
|
||||
/// Used to display gamecard-specific title information.
|
||||
typedef struct {
|
||||
TitleApplicationMetadata *app_metadata; ///< User application metadata.
|
||||
Version version; ///< Reflects the title version stored in the inserted gamecard.
|
||||
char display_version[32]; ///< Reflects the title display version stored in its NACP.
|
||||
} TitleGameCardApplicationMetadataEntry;
|
||||
|
||||
/// Generated using ncm calls.
|
||||
/// User applications: the previous/next pointers reference other user applications with the same ID.
|
||||
/// Patches: the previous/next pointers reference other patches with the same ID.
|
||||
|
@ -102,12 +109,13 @@ NcmContentStorage *titleGetNcmStorageByStorageId(u8 storage_id);
|
|||
/// Returns a pointer to a dynamically allocated array of pointers to TitleApplicationMetadata entries, as well as their count. Returns NULL if an error occurs.
|
||||
/// If 'is_system' is true, TitleApplicationMetadata entries from available system titles (NcmStorageId_BuiltInSystem) will be returned.
|
||||
/// Otherwise, TitleApplicationMetadata entries from user applications with available content data (NcmStorageId_BuiltInUser, NcmStorageId_SdCard, NcmStorageId_GameCard) will be returned.
|
||||
/// The allocated buffer must be freed by the calling function using free().
|
||||
/// The allocated buffer must be freed by the caller using free().
|
||||
TitleApplicationMetadata **titleGetApplicationMetadataEntries(bool is_system, u32 *out_count);
|
||||
|
||||
/// Returns a pointer to a dynamically allocated array of pointers to TitleApplicationMetadata entries with matching gamecard user titles, as well as their count. Returns NULL if an error occurs.
|
||||
/// The allocated buffer must be freed by the calling function using free().
|
||||
TitleApplicationMetadata **titleGetGameCardApplicationMetadataEntries(u32 *out_count);
|
||||
/// Returns a pointer to a dynamically allocated array of TitleGameCardApplicationMetadataEntry elements generated from gamecard user titles, as well as their count.
|
||||
/// Returns NULL if an error occurs.
|
||||
/// The allocated buffer must be freed by the caller using free().
|
||||
TitleGameCardApplicationMetadataEntry *titleGetGameCardApplicationMetadataEntries(u32 *out_count);
|
||||
|
||||
/// Returns a pointer to a dynamically allocated TitleInfo element with a matching storage ID and title ID. Returns NULL if an error occurs.
|
||||
/// If NcmStorageId_Any is used, the first entry with a matching title ID is returned.
|
||||
|
|
|
@ -39,7 +39,7 @@ void umsExit(void);
|
|||
/// Returns true if USB Mass Storage device info has been updated.
|
||||
bool umsIsDeviceInfoUpdated(void);
|
||||
|
||||
/// Returns a pointer to a dynamically allocated array of UsbHsFsDevice elements. The allocated buffer must be freed by the calling function.
|
||||
/// Returns a pointer to a dynamically allocated array of UsbHsFsDevice elements. The allocated buffer must be freed by the caller.
|
||||
/// Returns NULL if an error occurs.
|
||||
UsbHsFsDevice *umsGetDevices(u32 *out_count);
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ namespace nxdt::views
|
|||
DataTransferProgressDisplay();
|
||||
~DataTransferProgressDisplay();
|
||||
|
||||
void setProgress(const nxdt::tasks::DataTransferProgress& progress);
|
||||
void SetProgress(const nxdt::tasks::DataTransferProgress& progress);
|
||||
|
||||
void willAppear(bool resetState = false) override;
|
||||
void willDisappear(bool resetState = false) override;
|
||||
|
|
|
@ -87,12 +87,32 @@ namespace nxdt::tasks
|
|||
|
||||
SteadyTimePoint start_time{}, prev_time{}, end_time{};
|
||||
size_t prev_xfer_size = 0;
|
||||
bool first_publish_progress = true;
|
||||
|
||||
ALWAYS_INLINE std::string FormatTimeString(double seconds)
|
||||
{
|
||||
return fmt::format("{:02.0F}H{:02.0F}M{:02.0F}S", std::fmod(seconds, 86400.0) / 3600.0, std::fmod(seconds, 3600.0) / 60.0, std::fmod(seconds, 60.0));
|
||||
}
|
||||
|
||||
void PostExecutionCallback(void)
|
||||
{
|
||||
/* Set end time. */
|
||||
this->end_time = CurrentSteadyTimePoint();
|
||||
|
||||
/* Fire task handler immediately to make it store the last result from AsyncTask::LoopCallback(). */
|
||||
/* We do this here because all subscribers to our progress event will most likely call IsFinished() to check if the task is complete. */
|
||||
/* That being the case, if the `finished` flag returned by the task handler isn't updated before the progress event subscribers receive the last progress update, */
|
||||
/* they won't be able to determine if the task has already finished, leading to unsuspected consequences. */
|
||||
this->task_handler->fireNow();
|
||||
|
||||
/* Update progress one last time. */
|
||||
/* This will effectively invoke the callbacks from all of our progress event subscribers. */
|
||||
this->OnProgressUpdate(this->GetProgress());
|
||||
|
||||
/* Unset long running process state. */
|
||||
utilsSetLongRunningProcessState(false);
|
||||
}
|
||||
|
||||
protected:
|
||||
/* Set class as non-copyable and non-moveable. */
|
||||
NON_COPYABLE(DataTransferTask);
|
||||
|
@ -103,11 +123,8 @@ namespace nxdt::tasks
|
|||
{
|
||||
NX_IGNORE_ARG(result);
|
||||
|
||||
/* Set end time. */
|
||||
this->end_time = CurrentSteadyTimePoint();
|
||||
|
||||
/* Unset long running process state. */
|
||||
utilsSetLongRunningProcessState(false);
|
||||
/* Run post execution callback. */
|
||||
this->PostExecutionCallback();
|
||||
}
|
||||
|
||||
/* Runs on the calling thread. */
|
||||
|
@ -115,20 +132,8 @@ namespace nxdt::tasks
|
|||
{
|
||||
NX_IGNORE_ARG(result);
|
||||
|
||||
/* Set end time. */
|
||||
this->end_time = CurrentSteadyTimePoint();
|
||||
|
||||
/* Fire task handler immediately to make it store the last result from AsyncTask::LoopCallback(). */
|
||||
/* We do this here because all subscriptors to our progress event will most likely call IsFinished() to check if the task is complete. */
|
||||
/* That being the case, if the `finished` flag returned by the task handler isn't updated before the progress event subscriptors receive the last progress update, */
|
||||
/* they won't be able to determine if the task has already finished, leading to unsuspected consequences. */
|
||||
this->task_handler->fireNow();
|
||||
|
||||
/* Update progress one last time. */
|
||||
this->OnProgressUpdate(this->GetProgress());
|
||||
|
||||
/* Unset long running process state. */
|
||||
utilsSetLongRunningProcessState(false);
|
||||
/* Run post execution callback. */
|
||||
this->PostExecutionCallback();
|
||||
}
|
||||
|
||||
/* Runs on the calling thread. */
|
||||
|
@ -147,17 +152,18 @@ namespace nxdt::tasks
|
|||
/* Runs on the calling thread. */
|
||||
void OnProgressUpdate(const DataTransferProgress& progress) override final
|
||||
{
|
||||
AsyncTaskStatus status = this->GetStatus();
|
||||
|
||||
/* Return immediately if there has been no progress at all, or if it the task has been cancelled. */
|
||||
bool proceed = (progress.xfer_size > prev_xfer_size || (progress.xfer_size == prev_xfer_size && (!progress.total_size || progress.xfer_size >= progress.total_size)));
|
||||
if (!proceed || this->IsCancelled()) return;
|
||||
/* Return immediately if there has been no progress at all. */
|
||||
bool proceed = (progress.xfer_size > prev_xfer_size || (progress.xfer_size == prev_xfer_size && (!progress.total_size || progress.xfer_size >= progress.total_size ||
|
||||
this->first_publish_progress)));
|
||||
if (!proceed) return;
|
||||
|
||||
/* Calculate time difference between the last progress update and the current one. */
|
||||
/* Return immediately if it's less than 1 second, but only if this isn't the last chunk; or if we don't know the total size and the task is still running . */
|
||||
/* Return immediately if the task hasn't been cancelled and less than 1 second has passed since the last progress update -- but only if */
|
||||
/* this isn't the last chunk *or* if we don't know the total size and the task is still running . */
|
||||
AsyncTaskStatus status = this->GetStatus();
|
||||
SteadyTimePoint cur_time = std::chrono::steady_clock::now();
|
||||
double diff_time = std::chrono::duration<double>(cur_time - this->prev_time).count();
|
||||
if (diff_time < 1.0 && ((progress.total_size && progress.xfer_size < progress.total_size) || status == AsyncTaskStatus::RUNNING)) return;
|
||||
if (!this->IsCancelled() && diff_time < 1.0 && ((progress.total_size && progress.xfer_size < progress.total_size) || status == AsyncTaskStatus::RUNNING)) return;
|
||||
|
||||
/* Calculate transferred data size difference between the last progress update and the current one. */
|
||||
double diff_xfer_size = static_cast<double>(progress.xfer_size - prev_xfer_size);
|
||||
|
@ -169,14 +175,14 @@ namespace nxdt::tasks
|
|||
DataTransferProgress new_progress = progress;
|
||||
new_progress.speed = speed;
|
||||
|
||||
if (progress.total_size)
|
||||
if (progress.total_size && speed > 0.0)
|
||||
{
|
||||
/* Calculate remaining data size and ETA if we know the total size. */
|
||||
double remaining = static_cast<double>(progress.total_size - progress.xfer_size);
|
||||
double eta = (remaining / speed);
|
||||
new_progress.eta = this->FormatTimeString(eta);
|
||||
} else {
|
||||
/* No total size means no ETA calculation, sadly. */
|
||||
/* No total size nor speed means no ETA calculation, sadly. */
|
||||
new_progress.eta = "";
|
||||
}
|
||||
|
||||
|
@ -190,6 +196,7 @@ namespace nxdt::tasks
|
|||
/* Update class variables. */
|
||||
this->prev_time = cur_time;
|
||||
this->prev_xfer_size = progress.xfer_size;
|
||||
if (this->first_publish_progress) this->first_publish_progress = false;
|
||||
|
||||
/* Send updated progress to all listeners. */
|
||||
this->progress_event.fire(new_progress);
|
||||
|
|
177
include/data_transfer_task_frame.hpp
Normal file
177
include/data_transfer_task_frame.hpp
Normal file
|
@ -0,0 +1,177 @@
|
|||
/*
|
||||
* data_transfer_task_frame.hpp
|
||||
*
|
||||
* Copyright (c) 2020-2024, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||
*
|
||||
* This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool).
|
||||
*
|
||||
* nxdumptool 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* nxdumptool 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, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef __DATA_TRANSFER_TASK_FRAME_HPP__
|
||||
#define __DATA_TRANSFER_TASK_FRAME_HPP__
|
||||
|
||||
#include "is_base_of_template.hpp"
|
||||
#include "error_frame.hpp"
|
||||
#include "data_transfer_progress_display.hpp"
|
||||
|
||||
namespace nxdt::views
|
||||
{
|
||||
template<typename Task>
|
||||
class DataTransferTaskFrame: public brls::AppletFrame
|
||||
{
|
||||
static_assert(nxdt::utils::is_base_of_template_v<nxdt::tasks::DataTransferTask, Task>, "Task must inherit from DataTransferTask");
|
||||
|
||||
protected:
|
||||
Task task;
|
||||
|
||||
private:
|
||||
DataTransferProgressDisplay *task_progress = nullptr;
|
||||
ErrorFrame *error_frame = nullptr;
|
||||
bool progress_displayed = false;
|
||||
std::string notification{};
|
||||
|
||||
void DisplayProgress(void)
|
||||
{
|
||||
this->setContentView(this->task_progress);
|
||||
this->progress_displayed = true;
|
||||
}
|
||||
|
||||
void DisplayError(const std::string& msg)
|
||||
{
|
||||
this->error_frame->SetMessage(msg);
|
||||
this->setContentView(this->error_frame);
|
||||
this->progress_displayed = false;
|
||||
}
|
||||
|
||||
template<typename... Params>
|
||||
void Initialize(const std::string& title, brls::Image *icon, const Params&... params)
|
||||
{
|
||||
/* Set UI properties. */
|
||||
this->setTitle(title);
|
||||
this->setIcon(icon);
|
||||
|
||||
/* Update B button label. */
|
||||
this->updateActionHint(brls::Key::B, brls::i18n::getStr("generic/cancel"));
|
||||
|
||||
/* Initialize progress display. */
|
||||
this->task_progress = new DataTransferProgressDisplay();
|
||||
|
||||
/* Initialize error frame. */
|
||||
this->error_frame = new ErrorFrame();
|
||||
|
||||
/* Subscribe to the background task. */
|
||||
this->task.RegisterListener([this](const nxdt::tasks::DataTransferProgress& progress) {
|
||||
/* Store notification message and return immediately if the background task was cancelled. */
|
||||
if (this->task.IsCancelled())
|
||||
{
|
||||
this->notification = brls::i18n::getStr("generic/process_cancelled");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Update progress. */
|
||||
this->task_progress->SetProgress(progress);
|
||||
|
||||
/* Check if the background task has finished. */
|
||||
if (this->task.IsFinished())
|
||||
{
|
||||
/* Get background task result and error reason. */
|
||||
std::string error_msg{};
|
||||
bool ret = this->GetTaskResult(error_msg);
|
||||
|
||||
if (ret)
|
||||
{
|
||||
/* Store notification message. */
|
||||
this->notification = brls::i18n::getStr("generic/process_complete");
|
||||
|
||||
/* Pop view. */
|
||||
this->onCancel();
|
||||
} else {
|
||||
/* Update B button label. */
|
||||
this->updateActionHint(brls::Key::B, brls::i18n::getStr("brls/hints/back"));
|
||||
|
||||
/* Display error frame. */
|
||||
this->DisplayError(error_msg);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/* Start background task. */
|
||||
this->task.Execute(params...);
|
||||
|
||||
/* Set content view. */
|
||||
this->DisplayProgress();
|
||||
}
|
||||
|
||||
protected:
|
||||
/* Set class as non-copyable and non-moveable. */
|
||||
NON_COPYABLE(DataTransferTaskFrame);
|
||||
NON_MOVEABLE(DataTransferTaskFrame);
|
||||
|
||||
bool onCancel(void) override final
|
||||
{
|
||||
/* Cancel background task. This will have no effect if the background task already finished or if it was already cancelled. */
|
||||
this->task.Cancel();
|
||||
|
||||
/* Pop view. This will invoke this class' destructor. */
|
||||
brls::Application::popView(brls::ViewAnimation::SLIDE_RIGHT);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Must be implemented by derived classes to determine if the background task succeeded or not by calling GetResult() on their own. */
|
||||
/* If the task failed, false shall be returned and `error_msg` shall be updated to reflect the error reason. */
|
||||
virtual bool GetTaskResult(std::string& error_msg) = 0;
|
||||
|
||||
public:
|
||||
template<typename... Params>
|
||||
DataTransferTaskFrame(const std::string& title, const Params&... params) : brls::AppletFrame(true, true)
|
||||
{
|
||||
/* Generate icon using the default image. */
|
||||
brls::Image *icon = new brls::Image();
|
||||
icon->setImage(BOREALIS_ASSET("icon/" APP_TITLE ".jpg"));
|
||||
icon->setScaleType(brls::ImageScaleType::SCALE);
|
||||
|
||||
/* Initialize the rest of the elements. */
|
||||
this->Initialize(title, icon, params...);
|
||||
}
|
||||
|
||||
template<typename... Params>
|
||||
DataTransferTaskFrame(const std::string& title, brls::Image *icon, const Params&... params) : brls::AppletFrame(true, true)
|
||||
{
|
||||
/* Initialize the rest of the elements. */
|
||||
this->Initialize(title, icon, params...);
|
||||
}
|
||||
|
||||
~DataTransferTaskFrame()
|
||||
{
|
||||
/* Delete the view that's not currently being displayed. */
|
||||
/* The other one will be taken care of by brls::AppletFrame's destructor. */
|
||||
if (this->progress_displayed)
|
||||
{
|
||||
delete this->error_frame;
|
||||
} else {
|
||||
delete this->task_progress;
|
||||
}
|
||||
|
||||
/* Show relevant notification, if needed. */
|
||||
/* This is done here to avoid a slowdown issue while attempting to pop the current view and display a notification at the same time. */
|
||||
if (!this->notification.empty()) brls::Application::notify(this->notification);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif /* __DATA_TRANSFER_TASK_FRAME_HPP__ */
|
|
@ -89,7 +89,7 @@ namespace nxdt::tasks
|
|||
};
|
||||
|
||||
/* Asynchronous task used to store downloaded data into a dynamically allocated buffer using a URL. */
|
||||
/* The buffer returned by std::pair::first() must be manually freed by the calling function using free(). */
|
||||
/* The buffer returned by std::pair::first() must be manually freed by the caller using free(). */
|
||||
class DownloadDataTask: public DownloadTask<DownloadDataResult, std::string, bool>
|
||||
{
|
||||
protected:
|
||||
|
|
|
@ -51,7 +51,8 @@ namespace nxdt::views
|
|||
|
||||
std::string SanitizeUserFileName(void);
|
||||
|
||||
void UpdateOutputStorages(const nxdt::tasks::UmsDeviceVector& ums_devices);
|
||||
std::vector<std::string> GenerateOutputStoragesVector(const nxdt::tasks::UmsDeviceVector& ums_devices);
|
||||
|
||||
void UpdateStoragePrefix(u32 selected);
|
||||
|
||||
protected:
|
||||
|
@ -67,7 +68,18 @@ namespace nxdt::views
|
|||
|
||||
ALWAYS_INLINE brls::GenericEvent::Subscription RegisterButtonListener(brls::GenericEvent::Callback cb)
|
||||
{
|
||||
return this->button_click_event->subscribe(cb);
|
||||
return this->button_click_event->subscribe([this, cb](brls::View *view){
|
||||
/* Check if the USB host is currently selected as the output storage. */
|
||||
/* If so, we'll prevent the provided callback from running. */
|
||||
if (this->output_storage->getSelectedValue() == ConfigOutputStorage_UsbHost && this->root_view->GetUsbHostSpeed() == UsbHostSpeed_None)
|
||||
{
|
||||
brls::Application::notify(brls::i18n::getStr("dump_options/notifications/usb_host_unavailable"));
|
||||
return;
|
||||
}
|
||||
|
||||
/* Run the provided callback. */
|
||||
cb(view);
|
||||
});
|
||||
}
|
||||
|
||||
ALWAYS_INLINE void UnregisterButtonListener(brls::GenericEvent::Subscription subscription)
|
||||
|
|
|
@ -40,10 +40,10 @@ namespace nxdt::views
|
|||
void layout(NVGcontext* vg, brls::Style* style, brls::FontStash* stash) override;
|
||||
|
||||
public:
|
||||
ErrorFrame(std::string msg = "");
|
||||
ErrorFrame(const std::string& msg = "");
|
||||
~ErrorFrame();
|
||||
|
||||
void SetMessage(std::string msg);
|
||||
void SetMessage(const std::string& msg);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -25,12 +25,15 @@
|
|||
#define __FOCUSABLE_ITEM_HPP__
|
||||
|
||||
#include <borealis.hpp>
|
||||
#include <type_traits>
|
||||
|
||||
namespace nxdt::views
|
||||
{
|
||||
template<typename ViewType>
|
||||
class FocusableItem: public ViewType
|
||||
{
|
||||
static_assert(std::is_base_of_v<brls::View, ViewType>, "ViewType must inherit from brls::View");
|
||||
|
||||
private:
|
||||
bool highlight, highlight_bg;
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* gamecard_dump_tasks.hpp
|
||||
* gamecard_image_dump_task.hpp
|
||||
*
|
||||
* Copyright (c) 2020-2024, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||
*
|
||||
|
@ -21,8 +21,8 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#ifndef __GAMECARD_DUMP_TASKS_HPP__
|
||||
#define __GAMECARD_DUMP_TASKS_HPP__
|
||||
#ifndef __GAMECARD_IMAGE_DUMP_TASK_HPP__
|
||||
#define __GAMECARD_IMAGE_DUMP_TASK_HPP__
|
||||
|
||||
#include <optional>
|
||||
#include <mutex>
|
||||
|
@ -33,12 +33,14 @@ namespace nxdt::tasks
|
|||
{
|
||||
typedef std::optional<std::string> GameCardDumpTaskError;
|
||||
|
||||
class GameCardImageDumpTask: public DataTransferTask<GameCardDumpTaskError, std::string, bool, bool, bool, bool>
|
||||
/* Generates an image dump out of the inserted gamecard. */
|
||||
class GameCardImageDumpTask: public DataTransferTask<GameCardDumpTaskError, std::string, bool, bool, bool, bool, int>
|
||||
{
|
||||
private:
|
||||
std::mutex task_mtx;
|
||||
bool calculate_checksum = false;
|
||||
int checksum_lookup_method = ConfigChecksumLookupMethod_None;
|
||||
u32 gc_img_crc = 0, full_gc_img_crc = 0;
|
||||
std::mutex crc_mtx;
|
||||
|
||||
protected:
|
||||
/* Set class as non-copyable and non-moveable. */
|
||||
|
@ -47,7 +49,7 @@ namespace nxdt::tasks
|
|||
|
||||
/* Runs in the background thread. */
|
||||
GameCardDumpTaskError DoInBackground(const std::string& output_path, const bool& prepend_key_area, const bool& keep_certificate, const bool& trim_dump,
|
||||
const bool& calculate_checksum) override final;
|
||||
const bool& calculate_checksum, const int& checksum_lookup_method) override final;
|
||||
|
||||
public:
|
||||
GameCardImageDumpTask() = default;
|
||||
|
@ -56,7 +58,7 @@ namespace nxdt::tasks
|
|||
/* Returns zero if checksum calculation wasn't enabled, if the task hasn't finished yet or if the task was cancelled. */
|
||||
ALWAYS_INLINE u32 GetImageChecksum(void)
|
||||
{
|
||||
std::scoped_lock lock(this->crc_mtx);
|
||||
std::scoped_lock lock(this->task_mtx);
|
||||
return ((this->calculate_checksum && this->IsFinished() && !this->IsCancelled()) ? this->gc_img_crc : 0);
|
||||
}
|
||||
|
||||
|
@ -64,10 +66,10 @@ namespace nxdt::tasks
|
|||
/* Returns zero if checksum calculation wasn't enabled, if the task hasn't finished yet or if the task was cancelled. */
|
||||
ALWAYS_INLINE u32 GetFullImageChecksum(void)
|
||||
{
|
||||
std::scoped_lock lock(this->crc_mtx);
|
||||
std::scoped_lock lock(this->task_mtx);
|
||||
return ((this->calculate_checksum && this->IsFinished() && !this->IsCancelled()) ? this->full_gc_img_crc : 0);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif /* __GAMECARD_DUMP_TASKS_HPP__ */
|
||||
#endif /* __GAMECARD_IMAGE_DUMP_TASK_HPP__ */
|
58
include/gamecard_image_dump_task_frame.hpp
Normal file
58
include/gamecard_image_dump_task_frame.hpp
Normal file
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* gamecard_image_dump_task_frame.hpp
|
||||
*
|
||||
* Copyright (c) 2020-2024, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||
*
|
||||
* This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool).
|
||||
*
|
||||
* nxdumptool 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* nxdumptool 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, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef __GAMECARD_IMAGE_DUMP_TASK_FRAME_HPP__
|
||||
#define __GAMECARD_IMAGE_DUMP_TASK_FRAME_HPP__
|
||||
|
||||
#include "data_transfer_task_frame.hpp"
|
||||
#include "gamecard_image_dump_task.hpp"
|
||||
|
||||
namespace nxdt::views
|
||||
{
|
||||
class GameCardImageDumpTaskFrame: public DataTransferTaskFrame<nxdt::tasks::GameCardImageDumpTask>
|
||||
{
|
||||
protected:
|
||||
/* Set class as non-copyable and non-moveable. */
|
||||
NON_COPYABLE(GameCardImageDumpTaskFrame);
|
||||
NON_MOVEABLE(GameCardImageDumpTaskFrame);
|
||||
|
||||
bool GetTaskResult(std::string& error_msg) override final
|
||||
{
|
||||
auto res = this->task.GetResult();
|
||||
if (res.has_value())
|
||||
{
|
||||
error_msg = res.value();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public:
|
||||
template<typename... Params>
|
||||
GameCardImageDumpTaskFrame(Params... params) :
|
||||
DataTransferTaskFrame<nxdt::tasks::GameCardImageDumpTask>(brls::i18n::getStr("gamecard_tab/list/dump_card_image/label"), params...) { }
|
||||
};
|
||||
}
|
||||
|
||||
#endif /* __GAMECARD_IMAGE_DUMP_TASK_FRAME_HPP__ */
|
55
include/is_base_of_template.hpp
Normal file
55
include/is_base_of_template.hpp
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* is_base_of_template.hpp
|
||||
*
|
||||
* Copyright (c) 2020-2024, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||
*
|
||||
* Based on goneskiing's C++ implementation at:
|
||||
* https://stackoverflow.com/a/63562826
|
||||
*
|
||||
* This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool).
|
||||
*
|
||||
* nxdumptool 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* nxdumptool 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, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef __IS_BASE_OF_TEMPLATE_HPP__
|
||||
#define __IS_BASE_OF_TEMPLATE_HPP__
|
||||
|
||||
#include <variant>
|
||||
#include <experimental/type_traits>
|
||||
|
||||
namespace nxdt::utils
|
||||
{
|
||||
/* Can be used as part of static asserts to check if any given class was derived from a base template class. */
|
||||
template <template <typename...> class Base, typename Derived>
|
||||
struct is_base_of_template
|
||||
{
|
||||
template <typename... Ts> static constexpr std::variant<Ts...> is_callable(Base<Ts...>*);
|
||||
|
||||
template <typename T> using is_callable_t = decltype(is_callable(std::declval<T*>()));
|
||||
|
||||
static inline constexpr bool value = std::experimental::is_detected_v<is_callable_t, Derived>;
|
||||
|
||||
using type = std::experimental::detected_or_t<void, is_callable_t, Derived>;
|
||||
};
|
||||
|
||||
template <template <typename...> class Base, typename Derived>
|
||||
using is_base_of_template_t = typename is_base_of_template<Base, Derived>::type;
|
||||
|
||||
template <template <typename...> class Base, typename Derived>
|
||||
inline constexpr bool is_base_of_template_v = is_base_of_template<Base, Derived>::value;
|
||||
}
|
||||
|
||||
#endif /* __IS_BASE_OF_TEMPLATE_HPP__ */
|
|
@ -37,6 +37,8 @@ namespace nxdt::views
|
|||
private:
|
||||
bool applet_mode = false;
|
||||
|
||||
int output_storage = ConfigOutputStorage_SdCard;
|
||||
|
||||
brls::Label *applet_mode_lbl = nullptr;
|
||||
brls::Label *time_lbl = nullptr;
|
||||
brls::Label *battery_icon = nullptr, *battery_percentage = nullptr;
|
||||
|
@ -65,7 +67,19 @@ namespace nxdt::views
|
|||
|
||||
static std::string GetFormattedDateString(const struct tm& timeinfo);
|
||||
|
||||
/* Wrappers for task functions. */
|
||||
/* Helpers used to propagate the selected output storage throughout different parts of the UI. */
|
||||
|
||||
ALWAYS_INLINE int GetOutputStorage(void)
|
||||
{
|
||||
return this->output_storage;
|
||||
}
|
||||
|
||||
ALWAYS_INLINE void SetOutputStorage(int value)
|
||||
{
|
||||
this->output_storage = value;
|
||||
}
|
||||
|
||||
/* Wrappers for task methods. */
|
||||
|
||||
ALWAYS_INLINE bool IsInternetConnectionAvailable(void)
|
||||
{
|
||||
|
@ -82,6 +96,11 @@ namespace nxdt::views
|
|||
return this->ums_task->GetUmsDevices();
|
||||
}
|
||||
|
||||
ALWAYS_INLINE const UsbHostSpeed& GetUsbHostSpeed(void)
|
||||
{
|
||||
return this->usb_host_task->GetUsbHostSpeed();
|
||||
}
|
||||
|
||||
EVENT_SUBSCRIPTION(StatusInfoTask, StatusInfoEvent, status_info_task);
|
||||
EVENT_SUBSCRIPTION(GameCardTask, GameCardStatusEvent, gc_status_task);
|
||||
EVENT_SUBSCRIPTION(TitleTask, UserTitleEvent, title_task);
|
||||
|
|
|
@ -170,6 +170,9 @@ namespace nxdt::tasks
|
|||
UsbHostTask();
|
||||
~UsbHostTask();
|
||||
|
||||
/* Intentionally left here to let views retrieve USB host connection speed on-demand. */
|
||||
const UsbHostSpeed& GetUsbHostSpeed(void);
|
||||
|
||||
EVENT_SUBSCRIPTION(UsbHostEvent, usb_host_event);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
"start_dump": "Start dump",
|
||||
|
||||
"notifications": {
|
||||
"usb_host_unavailable": "Please connect the console to a PC and start the host server program.",
|
||||
"get_output_path_error": "Failed to generate output path."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
"mem_alloc_failed": "Failed to allocate memory buffer.",
|
||||
|
||||
"process_cancelled": "Process cancelled.",
|
||||
"process_complete": "Process complete!",
|
||||
|
||||
"read": "read",
|
||||
"write": "write"
|
||||
|
|
|
@ -70,7 +70,7 @@ static Thread g_gameCardDetectionThread = {0};
|
|||
static UEvent g_gameCardDetectionThreadExitEvent = {0}, g_gameCardStatusChangeEvent = {0};
|
||||
static bool g_gameCardDetectionThreadCreated = false;
|
||||
|
||||
static GameCardStatus g_gameCardStatus = GameCardStatus_NotInserted;
|
||||
static atomic_uchar g_gameCardStatus = GameCardStatus_NotInserted;
|
||||
|
||||
static FsGameCardHandle g_gameCardHandle = {0};
|
||||
static FsStorage g_gameCardStorage = {0};
|
||||
|
@ -285,14 +285,7 @@ UEvent *gamecardGetStatusChangeUserEvent(void)
|
|||
|
||||
u8 gamecardGetStatus(void)
|
||||
{
|
||||
u8 status = GameCardStatus_Processing;
|
||||
|
||||
SCOPED_TRY_LOCK(&g_gameCardMutex)
|
||||
{
|
||||
if (g_gameCardInterfaceInit) status = g_gameCardStatus;
|
||||
}
|
||||
|
||||
return status;
|
||||
return atomic_load(&g_gameCardStatus);
|
||||
}
|
||||
|
||||
/* Read full FS program memory to retrieve the GameCardSecurityInformation block. */
|
||||
|
@ -311,7 +304,7 @@ bool gamecardGetCardIdSet(FsGameCardIdSet *out)
|
|||
|
||||
SCOPED_LOCK(&g_gameCardMutex)
|
||||
{
|
||||
if (!g_gameCardInterfaceInit || g_gameCardStatus != GameCardStatus_InsertedAndInfoLoaded || !out) break;
|
||||
if (!g_gameCardInterfaceInit || atomic_load(&g_gameCardStatus) != GameCardStatus_InsertedAndInfoLoaded || !out) break;
|
||||
|
||||
Result rc = fsDeviceOperatorGetGameCardIdSet(&g_deviceOperator, out, sizeof(FsGameCardIdSet), (s64)sizeof(FsGameCardIdSet));
|
||||
if (R_FAILED(rc)) LOG_MSG_ERROR("fsDeviceOperatorGetGameCardIdSet failed! (0x%X)", rc);
|
||||
|
@ -355,7 +348,7 @@ bool gamecardGetHeader(GameCardHeader *out)
|
|||
|
||||
SCOPED_LOCK(&g_gameCardMutex)
|
||||
{
|
||||
ret = (g_gameCardInterfaceInit && g_gameCardStatus == GameCardStatus_InsertedAndInfoLoaded && out);
|
||||
ret = (g_gameCardInterfaceInit && atomic_load(&g_gameCardStatus) == GameCardStatus_InsertedAndInfoLoaded && out);
|
||||
if (ret) memcpy(out, &g_gameCardHeader, sizeof(GameCardHeader));
|
||||
}
|
||||
|
||||
|
@ -368,7 +361,7 @@ bool gamecardGetPlaintextCardInfoArea(GameCardInfo *out)
|
|||
|
||||
SCOPED_LOCK(&g_gameCardMutex)
|
||||
{
|
||||
ret = (g_gameCardInterfaceInit && g_gameCardStatus == GameCardStatus_InsertedAndInfoLoaded && out);
|
||||
ret = (g_gameCardInterfaceInit && atomic_load(&g_gameCardStatus) == GameCardStatus_InsertedAndInfoLoaded && out);
|
||||
if (ret) memcpy(out, &g_gameCardInfoArea, sizeof(GameCardInfo));
|
||||
}
|
||||
|
||||
|
@ -381,7 +374,7 @@ bool gamecardGetCertificate(FsGameCardCertificate *out)
|
|||
|
||||
SCOPED_LOCK(&g_gameCardMutex)
|
||||
{
|
||||
if (!g_gameCardInterfaceInit || g_gameCardStatus != GameCardStatus_InsertedAndInfoLoaded || !g_gameCardHandle.value || !out) break;
|
||||
if (!g_gameCardInterfaceInit || atomic_load(&g_gameCardStatus) != GameCardStatus_InsertedAndInfoLoaded || !g_gameCardHandle.value || !out) break;
|
||||
|
||||
/* Read the gamecard certificate using the official IPC call. */
|
||||
Result rc = fsDeviceOperatorGetGameCardDeviceCertificate(&g_deviceOperator, &g_gameCardHandle, out, sizeof(FsGameCardCertificate), (s64)sizeof(FsGameCardCertificate));
|
||||
|
@ -399,7 +392,7 @@ bool gamecardGetTotalSize(u64 *out)
|
|||
|
||||
SCOPED_LOCK(&g_gameCardMutex)
|
||||
{
|
||||
ret = (g_gameCardInterfaceInit && g_gameCardStatus == GameCardStatus_InsertedAndInfoLoaded && out);
|
||||
ret = (g_gameCardInterfaceInit && atomic_load(&g_gameCardStatus) == GameCardStatus_InsertedAndInfoLoaded && out);
|
||||
if (ret) *out = g_gameCardTotalSize;
|
||||
}
|
||||
|
||||
|
@ -412,7 +405,7 @@ bool gamecardGetTrimmedSize(u64 *out)
|
|||
|
||||
SCOPED_LOCK(&g_gameCardMutex)
|
||||
{
|
||||
ret = (g_gameCardInterfaceInit && g_gameCardStatus == GameCardStatus_InsertedAndInfoLoaded && out);
|
||||
ret = (g_gameCardInterfaceInit && atomic_load(&g_gameCardStatus) == GameCardStatus_InsertedAndInfoLoaded && out);
|
||||
if (ret) *out = (sizeof(GameCardHeader) + GAMECARD_PAGE_OFFSET(g_gameCardHeader.valid_data_end_page));
|
||||
}
|
||||
|
||||
|
@ -425,7 +418,7 @@ bool gamecardGetRomCapacity(u64 *out)
|
|||
|
||||
SCOPED_LOCK(&g_gameCardMutex)
|
||||
{
|
||||
ret = (g_gameCardInterfaceInit && g_gameCardStatus == GameCardStatus_InsertedAndInfoLoaded && out);
|
||||
ret = (g_gameCardInterfaceInit && atomic_load(&g_gameCardStatus) == GameCardStatus_InsertedAndInfoLoaded && out);
|
||||
if (ret) *out = g_gameCardCapacity;
|
||||
}
|
||||
|
||||
|
@ -438,7 +431,7 @@ bool gamecardGetBundledFirmwareUpdateVersion(Version *out)
|
|||
|
||||
SCOPED_LOCK(&g_gameCardMutex)
|
||||
{
|
||||
if (!g_gameCardInterfaceInit || g_gameCardStatus != GameCardStatus_InsertedAndInfoLoaded || !g_gameCardHandle.value || !out) break;
|
||||
if (!g_gameCardInterfaceInit || atomic_load(&g_gameCardStatus) != GameCardStatus_InsertedAndInfoLoaded || !g_gameCardHandle.value || !out) break;
|
||||
|
||||
u64 update_id = 0;
|
||||
u32 update_version = 0;
|
||||
|
@ -739,14 +732,14 @@ NX_INLINE bool gamecardIsInserted(void)
|
|||
|
||||
static void gamecardLoadInfo(void)
|
||||
{
|
||||
if (g_gameCardStatus == GameCardStatus_InsertedAndInfoLoaded) return;
|
||||
if (atomic_load(&g_gameCardStatus) == GameCardStatus_InsertedAndInfoLoaded) return;
|
||||
|
||||
HashFileSystemContext *root_hfs_ctx = NULL;
|
||||
u32 root_hfs_entry_count = 0, root_hfs_name_table_size = 0;
|
||||
char *root_hfs_name_table = NULL;
|
||||
|
||||
/* Set initial gamecard status. */
|
||||
g_gameCardStatus = GameCardStatus_InsertedAndInfoNotLoaded;
|
||||
atomic_store(&g_gameCardStatus, GameCardStatus_Processing);
|
||||
|
||||
/* Read gamecard header. */
|
||||
/* This step *will* fail if the running CFW enabled the "nogc" patch. */
|
||||
|
@ -760,7 +753,7 @@ static void gamecardLoadInfo(void)
|
|||
if (g_lafwVersion < g_gameCardInfoArea.fw_version)
|
||||
{
|
||||
LOG_MSG_ERROR("LAFW version doesn't meet gamecard requirement! (%lu < %lu).", g_lafwVersion, g_gameCardInfoArea.fw_version);
|
||||
g_gameCardStatus = GameCardStatus_LotusAsicFirmwareUpdateRequired;
|
||||
atomic_store(&g_gameCardStatus, GameCardStatus_LotusAsicFirmwareUpdateRequired);
|
||||
goto end;
|
||||
}
|
||||
|
||||
|
@ -828,11 +821,14 @@ static void gamecardLoadInfo(void)
|
|||
}
|
||||
|
||||
/* Update gamecard status. */
|
||||
g_gameCardStatus = GameCardStatus_InsertedAndInfoLoaded;
|
||||
atomic_store(&g_gameCardStatus, GameCardStatus_InsertedAndInfoLoaded);
|
||||
|
||||
end:
|
||||
if (g_gameCardStatus != GameCardStatus_InsertedAndInfoLoaded)
|
||||
u8 status = atomic_load(&g_gameCardStatus);
|
||||
if (status != GameCardStatus_InsertedAndInfoLoaded)
|
||||
{
|
||||
if (status == GameCardStatus_Processing) atomic_store(&g_gameCardStatus, GameCardStatus_InsertedAndInfoNotLoaded);
|
||||
|
||||
if (!g_gameCardHfsCtx && root_hfs_ctx)
|
||||
{
|
||||
hfsFreeContext(root_hfs_ctx);
|
||||
|
@ -875,7 +871,7 @@ static void gamecardFreeInfo(bool clear_status)
|
|||
|
||||
gamecardCloseStorageArea();
|
||||
|
||||
if (clear_status) g_gameCardStatus = GameCardStatus_NotInserted;
|
||||
if (clear_status) atomic_store(&g_gameCardStatus, GameCardStatus_NotInserted);
|
||||
}
|
||||
|
||||
static bool gamecardReadHeader(void)
|
||||
|
@ -977,7 +973,7 @@ static bool _gamecardGetPlaintextCardInfoArea(void)
|
|||
|
||||
static bool gamecardReadSecurityInformation(GameCardSecurityInformation *out)
|
||||
{
|
||||
if (!g_gameCardInterfaceInit || g_gameCardStatus != GameCardStatus_InsertedAndInfoLoaded || !out)
|
||||
if (!g_gameCardInterfaceInit || atomic_load(&g_gameCardStatus) != GameCardStatus_InsertedAndInfoLoaded || !out)
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid parameters!");
|
||||
return false;
|
||||
|
@ -1030,7 +1026,10 @@ static bool gamecardReadSecurityInformation(GameCardSecurityInformation *out)
|
|||
|
||||
static bool gamecardGetHandleAndStorage(u32 partition)
|
||||
{
|
||||
if (g_gameCardStatus < GameCardStatus_InsertedAndInfoNotLoaded || partition > 1)
|
||||
u8 status = atomic_load(&g_gameCardStatus);
|
||||
|
||||
if (partition > 1 || (status < GameCardStatus_LotusAsicFirmwareUpdateRequired && status != GameCardStatus_Processing) || \
|
||||
(status == GameCardStatus_LotusAsicFirmwareUpdateRequired && partition == 1))
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid parameters!");
|
||||
return false;
|
||||
|
@ -1068,7 +1067,7 @@ static bool gamecardGetHandleAndStorage(u32 partition)
|
|||
if (R_FAILED(rc))
|
||||
{
|
||||
LOG_MSG_ERROR("fsDeviceOperatorGetGameCardHandle / fsOpenGameCardStorage failed! (0x%X).", rc);
|
||||
if (g_gameCardStatus == GameCardStatus_InsertedAndInfoNotLoaded && partition == 0) g_gameCardStatus = GameCardStatus_NoGameCardPatchEnabled;
|
||||
if (status == GameCardStatus_Processing && partition == 0) atomic_store(&g_gameCardStatus, GameCardStatus_NoGameCardPatchEnabled);
|
||||
}
|
||||
|
||||
return R_SUCCEEDED(rc);
|
||||
|
@ -1076,7 +1075,10 @@ static bool gamecardGetHandleAndStorage(u32 partition)
|
|||
|
||||
static bool gamecardOpenStorageArea(u8 area)
|
||||
{
|
||||
if (g_gameCardStatus < GameCardStatus_InsertedAndInfoNotLoaded || (area != GameCardStorageArea_Normal && area != GameCardStorageArea_Secure))
|
||||
u8 status = atomic_load(&g_gameCardStatus);
|
||||
|
||||
if ((area != GameCardStorageArea_Normal && area != GameCardStorageArea_Secure) || (status < GameCardStatus_LotusAsicFirmwareUpdateRequired && \
|
||||
status != GameCardStatus_Processing) || (status == GameCardStatus_LotusAsicFirmwareUpdateRequired && area == GameCardStorageArea_Secure))
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid parameters!");
|
||||
return false;
|
||||
|
@ -1103,7 +1105,10 @@ static bool gamecardOpenStorageArea(u8 area)
|
|||
|
||||
static bool gamecardReadStorageArea(void *out, u64 read_size, u64 offset)
|
||||
{
|
||||
if (g_gameCardStatus < GameCardStatus_InsertedAndInfoNotLoaded || !g_gameCardNormalAreaSize || !g_gameCardSecureAreaSize || !out || !read_size || (offset + read_size) > g_gameCardTotalSize)
|
||||
u8 status = atomic_load(&g_gameCardStatus);
|
||||
|
||||
if ((status < GameCardStatus_LotusAsicFirmwareUpdateRequired && status != GameCardStatus_Processing) || !g_gameCardNormalAreaSize || !g_gameCardSecureAreaSize || \
|
||||
!out || !read_size || (offset + read_size) > g_gameCardTotalSize)
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid parameters!");
|
||||
return false;
|
||||
|
@ -1399,7 +1404,7 @@ static HashFileSystemContext *_gamecardGetHashFileSystemContext(u8 hfs_partition
|
|||
{
|
||||
HashFileSystemContext *hfs_ctx = NULL;
|
||||
|
||||
if (!g_gameCardInterfaceInit || g_gameCardStatus != GameCardStatus_InsertedAndInfoLoaded || !g_gameCardHfsCount || !g_gameCardHfsCtx || \
|
||||
if (!g_gameCardInterfaceInit || atomic_load(&g_gameCardStatus) != GameCardStatus_InsertedAndInfoLoaded || !g_gameCardHfsCount || !g_gameCardHfsCtx || \
|
||||
hfs_partition_type < HashFileSystemPartitionType_Root || hfs_partition_type >= HashFileSystemPartitionType_Count)
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid parameters!");
|
||||
|
|
|
@ -741,45 +741,74 @@ TitleApplicationMetadata **titleGetApplicationMetadataEntries(bool is_system, u3
|
|||
return app_metadata;
|
||||
}
|
||||
|
||||
TitleApplicationMetadata **titleGetGameCardApplicationMetadataEntries(u32 *out_count)
|
||||
TitleGameCardApplicationMetadataEntry *titleGetGameCardApplicationMetadataEntries(u32 *out_count)
|
||||
{
|
||||
u32 app_count = 0;
|
||||
TitleApplicationMetadata **app_metadata = NULL, **tmp_app_metadata = NULL;
|
||||
TitleGameCardApplicationMetadataEntry *gc_app_metadata = NULL, *tmp_gc_app_metadata = NULL;
|
||||
|
||||
SCOPED_LOCK(&g_titleMutex)
|
||||
{
|
||||
if (!g_titleInterfaceInit || !g_userMetadata || !g_userMetadataCount || !g_titleGameCardAvailable || !out_count)
|
||||
TitleStorage *title_storage = &(g_titleStorage[TITLE_STORAGE_INDEX(NcmStorageId_GameCard)]);
|
||||
TitleInfo **titles = title_storage->titles;
|
||||
u32 title_count = title_storage->title_count;
|
||||
bool error = false;
|
||||
|
||||
if (!g_titleInterfaceInit || !g_titleGameCardAvailable || !out_count || !titles || !title_count)
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid parameters!");
|
||||
break;
|
||||
}
|
||||
|
||||
bool error = false;
|
||||
|
||||
for(u32 i = 0; i < g_userMetadataCount; i++)
|
||||
/* Loop through our gamecard TitleInfo entries. */
|
||||
for(u32 i = 0; i < title_count; i++)
|
||||
{
|
||||
TitleApplicationMetadata *cur_app_metadata = g_userMetadata[i];
|
||||
if (!cur_app_metadata) continue;
|
||||
/* Skip current entry if it's not a user application. */
|
||||
TitleInfo *app_info = titles[i], *patch_info = NULL;
|
||||
if (!app_info || app_info->meta_key.type != NcmContentMetaType_Application) continue;
|
||||
|
||||
/* Skip current metadata entry if content data for this title isn't available on the inserted gamecard. */
|
||||
if (!_titleGetInfoFromStorageByTitleId(NcmStorageId_GameCard, cur_app_metadata->title_id)) continue;
|
||||
u32 app_version = app_info->meta_key.version;
|
||||
|
||||
/* Check if the inserted gamecard holds any bundled patches for the current user application. */
|
||||
/* If so, we'll use the highest patch version available as part of the filename. */
|
||||
for(u32 j = 0; j < title_count; j++)
|
||||
{
|
||||
if (j == i) continue;
|
||||
|
||||
TitleInfo *cur_title_info = titles[j];
|
||||
if (!cur_title_info || cur_title_info->meta_key.type != NcmContentMetaType_Patch || \
|
||||
!titleCheckIfPatchIdBelongsToApplicationId(app_info->meta_key.id, cur_title_info->meta_key.id) || cur_title_info->meta_key.version <= app_version) continue;
|
||||
|
||||
patch_info = cur_title_info;
|
||||
app_version = cur_title_info->meta_key.version;
|
||||
}
|
||||
|
||||
/* Reallocate application metadata pointer array. */
|
||||
tmp_app_metadata = realloc(app_metadata, (app_count + 1) * sizeof(TitleApplicationMetadata*));
|
||||
if (!tmp_app_metadata)
|
||||
tmp_gc_app_metadata = realloc(gc_app_metadata, (app_count + 1) * sizeof(TitleGameCardApplicationMetadataEntry));
|
||||
if (!tmp_gc_app_metadata)
|
||||
{
|
||||
LOG_MSG_ERROR("Failed to reallocate application metadata pointer array!");
|
||||
if (app_metadata) free(app_metadata);
|
||||
app_metadata = NULL;
|
||||
if (gc_app_metadata) free(gc_app_metadata);
|
||||
gc_app_metadata = NULL;
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
|
||||
app_metadata = tmp_app_metadata;
|
||||
tmp_app_metadata = NULL;
|
||||
gc_app_metadata = tmp_gc_app_metadata;
|
||||
tmp_gc_app_metadata = NULL;
|
||||
|
||||
/* Set current pointer and increase counter. */
|
||||
app_metadata[app_count++] = cur_app_metadata;
|
||||
/* Fill current entry and increase counter. */
|
||||
tmp_gc_app_metadata = &(gc_app_metadata[app_count++]);
|
||||
memset(tmp_gc_app_metadata, 0, sizeof(TitleGameCardApplicationMetadataEntry));
|
||||
tmp_gc_app_metadata->app_metadata = app_info->app_metadata;
|
||||
tmp_gc_app_metadata->version.value = app_version;
|
||||
|
||||
/* Try to retrieve the display version. */
|
||||
char *version_str = titleGetPatchVersionString(patch_info ? patch_info : app_info);
|
||||
if (version_str)
|
||||
{
|
||||
snprintf(tmp_gc_app_metadata->display_version, MAX_ELEMENTS(tmp_gc_app_metadata->display_version), "%s", version_str);
|
||||
free(version_str);
|
||||
}
|
||||
}
|
||||
|
||||
if (error) break;
|
||||
|
@ -787,10 +816,10 @@ TitleApplicationMetadata **titleGetGameCardApplicationMetadataEntries(u32 *out_c
|
|||
/* Update output counter. */
|
||||
*out_count = app_count;
|
||||
|
||||
if (!app_metadata || !app_count) LOG_MSG_ERROR("No gamecard content data found for user applications!");
|
||||
if (!gc_app_metadata || !app_count) LOG_MSG_ERROR("No gamecard content data found for user applications!");
|
||||
}
|
||||
|
||||
return app_metadata;
|
||||
return gc_app_metadata;
|
||||
}
|
||||
|
||||
TitleInfo *titleGetInfoFromStorageByTitleId(u8 storage_id, u64 title_id)
|
||||
|
|
|
@ -208,7 +208,7 @@ static atomic_bool g_usbDetectionThreadCreated = false;
|
|||
|
||||
static u8 *g_usbTransferBuffer = NULL;
|
||||
static u64 g_usbTransferRemainingSize = 0, g_usbTransferWrittenSize = 0;
|
||||
static u16 g_usbEndpointMaxPacketSize = 0;
|
||||
static atomic_ushort g_usbEndpointMaxPacketSize = 0;
|
||||
|
||||
/* Function prototypes. */
|
||||
|
||||
|
@ -326,25 +326,21 @@ void *usbAllocatePageAlignedBuffer(size_t size)
|
|||
u8 usbIsReady(void)
|
||||
{
|
||||
u8 ret = UsbHostSpeed_None;
|
||||
u16 max_packet_size = atomic_load(&g_usbEndpointMaxPacketSize);
|
||||
|
||||
SCOPED_TRY_LOCK(&g_usbInterfaceMutex)
|
||||
switch(max_packet_size)
|
||||
{
|
||||
if (!g_usbHostAvailable || !g_usbSessionStarted) break;
|
||||
|
||||
switch(g_usbEndpointMaxPacketSize)
|
||||
{
|
||||
case USB_FS_EP_MAX_PACKET_SIZE: /* USB 1.x. */
|
||||
ret = UsbHostSpeed_FullSpeed;
|
||||
break;
|
||||
case USB_HS_EP_MAX_PACKET_SIZE: /* USB 2.0. */
|
||||
ret = UsbHostSpeed_HighSpeed;
|
||||
break;
|
||||
case USB_SS_EP_MAX_PACKET_SIZE: /* USB 3.0. */
|
||||
ret = UsbHostSpeed_SuperSpeed;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
case USB_FS_EP_MAX_PACKET_SIZE: /* USB 1.x. */
|
||||
ret = UsbHostSpeed_FullSpeed;
|
||||
break;
|
||||
case USB_HS_EP_MAX_PACKET_SIZE: /* USB 2.0. */
|
||||
ret = UsbHostSpeed_HighSpeed;
|
||||
break;
|
||||
case USB_SS_EP_MAX_PACKET_SIZE: /* USB 3.0. */
|
||||
ret = UsbHostSpeed_SuperSpeed;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
@ -395,7 +391,7 @@ bool usbSendFileData(const void *data, u64 data_size)
|
|||
if ((g_usbTransferRemainingSize - data_size) == 0)
|
||||
{
|
||||
/* Enable ZLT if the last chunk size is aligned to the USB endpoint max packet size. */
|
||||
if (IS_ALIGNED(data_size, g_usbEndpointMaxPacketSize))
|
||||
if (IS_ALIGNED(data_size, atomic_load(&g_usbEndpointMaxPacketSize)))
|
||||
{
|
||||
zlt_required = true;
|
||||
usbSetZltPacket(true);
|
||||
|
@ -593,7 +589,7 @@ static void usbDetectionThreadFunc(void *arg)
|
|||
g_usbHostAvailable = usbIsHostAvailable();
|
||||
g_usbSessionStarted = false;
|
||||
g_usbTransferRemainingSize = g_usbTransferWrittenSize = 0;
|
||||
g_usbEndpointMaxPacketSize = 0;
|
||||
atomic_store(&g_usbEndpointMaxPacketSize, 0);
|
||||
|
||||
/* Start a USB session if we're connected to a host device. */
|
||||
/* This will essentially hang this thread and all other threads that call USB-related functions until: */
|
||||
|
@ -606,7 +602,7 @@ static void usbDetectionThreadFunc(void *arg)
|
|||
g_usbSessionStarted = usbStartSession();
|
||||
if (g_usbSessionStarted)
|
||||
{
|
||||
LOG_MSG_INFO("USB session successfully established. Endpoint max packet size: 0x%04X.", g_usbEndpointMaxPacketSize);
|
||||
LOG_MSG_INFO("USB session successfully established. Endpoint max packet size: 0x%04X.", atomic_load(&g_usbEndpointMaxPacketSize));
|
||||
} else {
|
||||
/* Update exit flag. */
|
||||
exit_flag = g_usbDetectionThreadExitFlag;
|
||||
|
@ -624,7 +620,7 @@ static void usbDetectionThreadFunc(void *arg)
|
|||
if (g_usbHostAvailable && g_usbSessionStarted) usbEndSession();
|
||||
g_usbHostAvailable = g_usbSessionStarted = g_usbDetectionThreadExitFlag = false;
|
||||
g_usbTransferRemainingSize = g_usbTransferWrittenSize = 0;
|
||||
g_usbEndpointMaxPacketSize = 0;
|
||||
atomic_store(&g_usbEndpointMaxPacketSize, 0);
|
||||
}
|
||||
|
||||
threadExit();
|
||||
|
@ -658,14 +654,15 @@ static bool usbStartSession(void)
|
|||
/* Get the endpoint max packet size from the response sent by the USB host. */
|
||||
/* This is done to accurately know when and where to enable Zero Length Termination (ZLT) packets during bulk transfers. */
|
||||
/* As much as I'd like to avoid this, the GetUsbDeviceSpeed cmd from usb:ds is only available in HOS 8.0.0+ -- and we definitely want to provide USB comms under older versions. */
|
||||
g_usbEndpointMaxPacketSize = ((UsbStatus*)g_usbTransferBuffer)->max_packet_size;
|
||||
if (g_usbEndpointMaxPacketSize != USB_FS_EP_MAX_PACKET_SIZE && g_usbEndpointMaxPacketSize != USB_HS_EP_MAX_PACKET_SIZE && g_usbEndpointMaxPacketSize != USB_SS_EP_MAX_PACKET_SIZE)
|
||||
u16 max_packet_size = ((UsbStatus*)g_usbTransferBuffer)->max_packet_size;
|
||||
if (max_packet_size != USB_FS_EP_MAX_PACKET_SIZE && max_packet_size != USB_HS_EP_MAX_PACKET_SIZE && max_packet_size != USB_SS_EP_MAX_PACKET_SIZE)
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid endpoint max packet size value received from USB host: 0x%04X.", g_usbEndpointMaxPacketSize);
|
||||
LOG_MSG_ERROR("Invalid endpoint max packet size value received from USB host: 0x%04X.", max_packet_size);
|
||||
|
||||
/* Reset flags. */
|
||||
ret = false;
|
||||
g_usbEndpointMaxPacketSize = 0;
|
||||
} else {
|
||||
atomic_store(&g_usbEndpointMaxPacketSize, max_packet_size);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -739,7 +736,7 @@ static bool usbSendCommand(void)
|
|||
memmove(g_usbTransferBuffer, g_usbTransferBuffer + sizeof(UsbCommandHeader), cmd_block_size);
|
||||
|
||||
/* Determine if we'll need to set a Zero Length Termination (ZLT) packet after sending the command block. */
|
||||
zlt_required = IS_ALIGNED(cmd_block_size, g_usbEndpointMaxPacketSize);
|
||||
zlt_required = IS_ALIGNED(cmd_block_size, atomic_load(&g_usbEndpointMaxPacketSize));
|
||||
if (zlt_required) usbSetZltPacket(true);
|
||||
|
||||
/* Write command block. */
|
||||
|
|
|
@ -90,7 +90,7 @@ namespace nxdt::views
|
|||
this->speed_eta_lbl->getHeight());
|
||||
}
|
||||
|
||||
void DataTransferProgressDisplay::setProgress(const nxdt::tasks::DataTransferProgress& progress)
|
||||
void DataTransferProgressDisplay::SetProgress(const nxdt::tasks::DataTransferProgress& progress)
|
||||
{
|
||||
/* Update progress percentage. */
|
||||
this->progress_display->setProgress(progress.percentage, 100);
|
||||
|
|
|
@ -66,7 +66,8 @@ namespace nxdt::views
|
|||
this->list->setMarginBottom(20);
|
||||
|
||||
/* Filename. */
|
||||
this->filename = new brls::InputListItem("dump_options/filename/label"_i18n, this->SanitizeUserFileName(), "", "dump_options/filename/description"_i18n, FS_MAX_FILENAME_LENGTH);
|
||||
this->filename = new brls::InputListItem("dump_options/filename/label"_i18n, this->SanitizeUserFileName(), "", "dump_options/filename/description"_i18n,
|
||||
FS_MAX_FILENAME_LENGTH);
|
||||
|
||||
this->filename->getClickEvent()->subscribe([this](brls::View *view) {
|
||||
/* Sanitize the string entered by the user. */
|
||||
|
@ -77,7 +78,9 @@ namespace nxdt::views
|
|||
this->list->addView(this->filename);
|
||||
|
||||
/* Output storage. */
|
||||
this->output_storage = new brls::SelectListItem("dump_options/output_storage/label"_i18n, { "dummy0", "dummy1" }, configGetInteger("output_storage"), i18n::getStr("dump_options/output_storage/description", GITHUB_REPOSITORY_URL));
|
||||
this->output_storage = new brls::SelectListItem("dump_options/output_storage/label"_i18n, this->GenerateOutputStoragesVector(this->root_view->GetUmsDevices()),
|
||||
static_cast<u32>(this->root_view->GetOutputStorage()),
|
||||
i18n::getStr("dump_options/output_storage/description", GITHUB_REPOSITORY_URL));
|
||||
|
||||
this->output_storage->getValueSelectedEvent()->subscribe([this](int selected) {
|
||||
/* Make sure the current value isn't out of bounds. */
|
||||
|
@ -86,6 +89,9 @@ namespace nxdt::views
|
|||
/* Update configuration. */
|
||||
if (selected == ConfigOutputStorage_SdCard || selected == ConfigOutputStorage_UsbHost) configSetInteger("output_storage", selected);
|
||||
|
||||
/* Update cached output storage value. */
|
||||
this->root_view->SetOutputStorage(selected);
|
||||
|
||||
/* Sanitize output filename for the selected storage. */
|
||||
this->filename->setValue(this->SanitizeUserFileName());
|
||||
|
||||
|
@ -93,9 +99,6 @@ namespace nxdt::views
|
|||
this->UpdateStoragePrefix(static_cast<u32>(selected));
|
||||
});
|
||||
|
||||
/* Manually update the output storages vector. */
|
||||
this->UpdateOutputStorages(this->root_view->GetUmsDevices());
|
||||
|
||||
/* Manually update the storage prefix. */
|
||||
this->UpdateStoragePrefix(this->output_storage->getSelectedValue());
|
||||
|
||||
|
@ -103,8 +106,24 @@ namespace nxdt::views
|
|||
|
||||
/* Subscribe to the UMS device event. */
|
||||
this->ums_task_sub = this->root_view->RegisterUmsTaskListener([this](const nxdt::tasks::UmsDeviceVector& ums_devices) {
|
||||
/* Update output storages vector. */
|
||||
this->UpdateOutputStorages(ums_devices);
|
||||
/* Update SelectListItem values. */
|
||||
/* If the dropdown menu currently is being displayed, it'll be reloaded. */
|
||||
this->output_storage->updateValues(this->GenerateOutputStoragesVector(ums_devices));
|
||||
|
||||
/* Get selected output storage. */
|
||||
u32 selected = this->output_storage->getSelectedValue();
|
||||
if (selected > ConfigOutputStorage_UsbHost)
|
||||
{
|
||||
/* Switch the current output storage to the SD card if a UMS device was previously selected. */
|
||||
this->output_storage->setSelectedValue(ConfigOutputStorage_SdCard);
|
||||
|
||||
/* Manually trigger selection event. */
|
||||
/* This will take care of updating the JSON configuration, updating the cached output storage value and saniziting the filename provided by the user. */
|
||||
this->output_storage->getValueSelectedEvent()->fire(ConfigOutputStorage_SdCard);
|
||||
} else {
|
||||
/* Set the current output storage once more. This will make sure the string for the selected storage gets updated. */
|
||||
this->output_storage->setSelectedValue(selected);
|
||||
}
|
||||
});
|
||||
|
||||
/* Start dump button. */
|
||||
|
@ -124,7 +143,7 @@ namespace nxdt::views
|
|||
|
||||
if (this->raw_filename.empty() || !(raw_filename_dup = strdup(this->raw_filename.c_str()))) return "dummy";
|
||||
|
||||
u8 selected = static_cast<u8>(this->output_storage ? this->output_storage->getSelectedValue() : configGetInteger("output_storage"));
|
||||
int selected = this->root_view->GetOutputStorage();
|
||||
utilsReplaceIllegalCharacters(raw_filename_dup, selected == ConfigOutputStorage_SdCard);
|
||||
|
||||
std::string output = std::string(raw_filename_dup);
|
||||
|
@ -133,14 +152,10 @@ namespace nxdt::views
|
|||
return output;
|
||||
}
|
||||
|
||||
void DumpOptionsFrame::UpdateOutputStorages(const nxdt::tasks::UmsDeviceVector& ums_devices)
|
||||
std::vector<std::string> DumpOptionsFrame::GenerateOutputStoragesVector(const nxdt::tasks::UmsDeviceVector& ums_devices)
|
||||
{
|
||||
if (!this->output_storage) return;
|
||||
|
||||
std::vector<std::string> storages{};
|
||||
|
||||
size_t elem_count = (ConfigOutputStorage_Count + ums_devices.size());
|
||||
u32 selected = this->output_storage->getSelectedValue();
|
||||
|
||||
/* Fill storages vector. */
|
||||
for(size_t i = 0; i < elem_count; i++)
|
||||
|
@ -170,22 +185,7 @@ namespace nxdt::views
|
|||
}
|
||||
}
|
||||
|
||||
/* Update SelectListItem values. */
|
||||
/* If the dropdown menu currently is being displayed, it'll be reloaded. */
|
||||
this->output_storage->updateValues(storages);
|
||||
|
||||
if (selected > ConfigOutputStorage_UsbHost)
|
||||
{
|
||||
/* Set the SD card as the current output storage. */
|
||||
this->output_storage->setSelectedValue(ConfigOutputStorage_SdCard);
|
||||
|
||||
/* Manually trigger selection event. */
|
||||
/* This will take care of both updating the JSON configuration and saniziting the filename provided by the user. */
|
||||
this->output_storage->getValueSelectedEvent()->fire(ConfigOutputStorage_SdCard);
|
||||
} else {
|
||||
/* Set the current output storage once more. This will make sure the selected device string gets updated. */
|
||||
this->output_storage->setSelectedValue(selected);
|
||||
}
|
||||
return storages;
|
||||
}
|
||||
|
||||
void DumpOptionsFrame::UpdateStoragePrefix(u32 selected)
|
||||
|
@ -209,7 +209,7 @@ namespace nxdt::views
|
|||
|
||||
bool DumpOptionsFrame::onCancel(void)
|
||||
{
|
||||
/* Pop view. */
|
||||
/* Pop view. This will invoke this class' destructor. */
|
||||
brls::Application::popView(brls::ViewAnimation::SLIDE_RIGHT);
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
namespace nxdt::views
|
||||
{
|
||||
ErrorFrame::ErrorFrame(std::string msg) : brls::View()
|
||||
ErrorFrame::ErrorFrame(const std::string& msg) : brls::View()
|
||||
{
|
||||
this->label = new brls::Label(brls::LabelStyle::REGULAR, msg, true);
|
||||
this->label->setHorizontalAlign(NVG_ALIGN_CENTER);
|
||||
|
@ -89,7 +89,7 @@ namespace nxdt::views
|
|||
this->label->getHeight());
|
||||
}
|
||||
|
||||
void ErrorFrame::SetMessage(std::string msg)
|
||||
void ErrorFrame::SetMessage(const std::string& msg)
|
||||
{
|
||||
this->label->setText(msg);
|
||||
this->invalidate();
|
||||
|
|
|
@ -102,7 +102,7 @@ namespace nxdt::utils
|
|||
{
|
||||
char needed_size_str[0x40] = {0};
|
||||
utilsGenerateFormattedSizeString(static_cast<double>(this->total_size), needed_size_str, sizeof(needed_size_str));
|
||||
return i18n::getStr("utils/file_writer/free_space_check/insufficiente_space_error", needed_size_str);
|
||||
return i18n::getStr("utils/file_writer/free_space_check/insufficient_space_error", needed_size_str);
|
||||
}
|
||||
|
||||
return {};
|
||||
|
@ -139,7 +139,7 @@ namespace nxdt::utils
|
|||
this->fp = fopen(this->output_path.c_str(), "wb");
|
||||
} else {
|
||||
/* Open current part file. */
|
||||
std::string part_file_path = fmt::format("{}/{:02u}", this->output_path, this->split_file_part_idx);
|
||||
std::string part_file_path = fmt::format("{}/{:02d}", this->output_path, this->split_file_part_idx);
|
||||
this->fp = fopen(part_file_path.c_str(), "wb");
|
||||
if (this->fp)
|
||||
{
|
||||
|
|
|
@ -20,10 +20,23 @@
|
|||
*/
|
||||
|
||||
#include <gamecard_image_dump_options_frame.hpp>
|
||||
#include <gamecard_image_dump_task_frame.hpp>
|
||||
|
||||
namespace i18n = brls::i18n; /* For getStr(). */
|
||||
using namespace i18n::literals; /* For _i18n. */
|
||||
|
||||
#define GAMECARD_TOGGLE_ITEM(name) \
|
||||
do { \
|
||||
this->name = new brls::ToggleListItem("dump_options/gamecard/image/" #name "/label"_i18n, configGetBoolean("gamecard/" #name), \
|
||||
"dump_options/gamecard/image/" #name "/description"_i18n, "generic/value_enabled"_i18n, "generic/value_disabled"_i18n); \
|
||||
this->name->getClickEvent()->subscribe([](brls::View *view) { \
|
||||
brls::ToggleListItem *item = static_cast<brls::ToggleListItem*>(view); \
|
||||
configSetBoolean("gamecard/" #name, item->getToggleState()); \
|
||||
LOG_MSG_DEBUG("\"" #name "\" setting changed by user."); \
|
||||
}); \
|
||||
this->addView(this->name); \
|
||||
} while(0)
|
||||
|
||||
namespace nxdt::views
|
||||
{
|
||||
GameCardImageDumpOptionsFrame::GameCardImageDumpOptionsFrame(RootView *root_view, std::string raw_filename) :
|
||||
|
@ -41,74 +54,19 @@ namespace nxdt::views
|
|||
brls::Application::popView();
|
||||
});
|
||||
|
||||
/* Prepend KeyArea data. */
|
||||
this->prepend_key_area = new brls::ToggleListItem("dump_options/gamecard/image/prepend_key_area/label"_i18n, configGetBoolean("gamecard/prepend_key_area"),
|
||||
"dump_options/gamecard/image/prepend_key_area/description"_i18n, "generic/value_enabled"_i18n, "generic/value_disabled"_i18n);
|
||||
/* "Prepend KeyArea data" toggle. */
|
||||
GAMECARD_TOGGLE_ITEM(prepend_key_area);
|
||||
|
||||
this->prepend_key_area->getClickEvent()->subscribe([](brls::View* view) {
|
||||
/* Get current value. */
|
||||
brls::ToggleListItem *item = static_cast<brls::ToggleListItem*>(view);
|
||||
bool value = item->getToggleState();
|
||||
/* "Keep certificate" toggle. */
|
||||
GAMECARD_TOGGLE_ITEM(keep_certificate);
|
||||
|
||||
/* Update configuration. */
|
||||
configSetBoolean("gamecard/prepend_key_area", value);
|
||||
/* "Trim dump" toggle. */
|
||||
GAMECARD_TOGGLE_ITEM(trim_dump);
|
||||
|
||||
LOG_MSG_DEBUG("Prepend Key Area setting changed by user.");
|
||||
});
|
||||
/* "Calculate checksum" toggle. */
|
||||
GAMECARD_TOGGLE_ITEM(calculate_checksum);
|
||||
|
||||
this->addView(this->prepend_key_area);
|
||||
|
||||
/* Keep certificate. */
|
||||
this->keep_certificate = new brls::ToggleListItem("dump_options/gamecard/image/keep_certificate/label"_i18n, configGetBoolean("gamecard/keep_certificate"),
|
||||
"dump_options/gamecard/image/keep_certificate/description"_i18n, "generic/value_enabled"_i18n, "generic/value_disabled"_i18n);
|
||||
|
||||
this->keep_certificate->getClickEvent()->subscribe([](brls::View* view) {
|
||||
/* Get current value. */
|
||||
brls::ToggleListItem *item = static_cast<brls::ToggleListItem*>(view);
|
||||
bool value = item->getToggleState();
|
||||
|
||||
/* Update configuration. */
|
||||
configSetBoolean("gamecard/keep_certificate", value);
|
||||
|
||||
LOG_MSG_DEBUG("Keep certificate setting changed by user.");
|
||||
});
|
||||
|
||||
this->addView(this->keep_certificate);
|
||||
|
||||
/* Trim dump. */
|
||||
this->trim_dump = new brls::ToggleListItem("dump_options/gamecard/image/trim_dump/label"_i18n, configGetBoolean("gamecard/trim_dump"), "dump_options/gamecard/image/trim_dump/description"_i18n,
|
||||
"generic/value_enabled"_i18n, "generic/value_disabled"_i18n);
|
||||
|
||||
this->trim_dump->getClickEvent()->subscribe([](brls::View* view) {
|
||||
/* Get current value. */
|
||||
brls::ToggleListItem *item = static_cast<brls::ToggleListItem*>(view);
|
||||
bool value = item->getToggleState();
|
||||
|
||||
/* Update configuration. */
|
||||
configSetBoolean("gamecard/trim_dump", value);
|
||||
|
||||
LOG_MSG_DEBUG("Trim dump setting changed by user.");
|
||||
});
|
||||
|
||||
this->addView(this->trim_dump);
|
||||
|
||||
this->calculate_checksum = new brls::ToggleListItem("dump_options/gamecard/image/calculate_checksum/label"_i18n, configGetBoolean("gamecard/calculate_checksum"),
|
||||
"dump_options/gamecard/image/calculate_checksum/description"_i18n, "generic/value_enabled"_i18n, "generic/value_disabled"_i18n);
|
||||
|
||||
this->calculate_checksum->getClickEvent()->subscribe([](brls::View* view) {
|
||||
/* Get current value. */
|
||||
brls::ToggleListItem *item = static_cast<brls::ToggleListItem*>(view);
|
||||
bool value = item->getToggleState();
|
||||
|
||||
/* Update configuration. */
|
||||
configSetBoolean("gamecard/calculate_checksum", value);
|
||||
|
||||
LOG_MSG_DEBUG("Calculate checksum setting changed by user.");
|
||||
});
|
||||
|
||||
this->addView(this->calculate_checksum);
|
||||
|
||||
/* Checksum lookup method. */
|
||||
/* "Checksum lookup method" dropdown. */
|
||||
this->checksum_lookup_method = new brls::SelectListItem("dump_options/gamecard/image/checksum_lookup_method/label"_i18n, {
|
||||
"dump_options/gamecard/image/checksum_lookup_method/value_00"_i18n,
|
||||
"NSWDB",
|
||||
|
@ -133,8 +91,8 @@ namespace nxdt::views
|
|||
bool prepend_key_area_val = this->prepend_key_area->getToggleState();
|
||||
bool keep_certificate_val = this->keep_certificate->getToggleState();
|
||||
bool trim_dump_val = this->trim_dump->getToggleState();
|
||||
//bool calculate_checksum_val = this->calculate_checksum->getToggleState();
|
||||
//int checksum_lookup_method_val = static_cast<int>(this->checksum_lookup_method->getSelectedValue());
|
||||
bool calculate_checksum_val = this->calculate_checksum->getToggleState();
|
||||
int checksum_lookup_method_val = static_cast<int>(this->checksum_lookup_method->getSelectedValue());
|
||||
|
||||
/* Generate file extension. */
|
||||
std::string extension = fmt::format(" [{}][{}][{}].xci", prepend_key_area_val ? "KA" : "NKA", keep_certificate_val ? "C" : "NC", trim_dump_val ? "T" : "NT");
|
||||
|
@ -143,10 +101,9 @@ namespace nxdt::views
|
|||
std::string output_path{};
|
||||
if (!this->GetOutputFilePath(extension, output_path)) return;
|
||||
|
||||
/* Display update frame. */
|
||||
//brls::Application::pushView(new OptionsTabUpdateApplicationFrame(), brls::ViewAnimation::SLIDE_LEFT, false);
|
||||
|
||||
LOG_MSG_DEBUG("Output file path: %s", output_path.c_str());
|
||||
/* Display task frame. */
|
||||
brls::Application::pushView(new GameCardImageDumpTaskFrame(output_path, prepend_key_area_val, keep_certificate_val, trim_dump_val, calculate_checksum_val,
|
||||
checksum_lookup_method_val), brls::ViewAnimation::SLIDE_LEFT, false);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* gamecard_dump_tasks.cpp
|
||||
* gamecard_image_dump_task.cpp
|
||||
*
|
||||
* Copyright (c) 2020-2024, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||
*
|
||||
|
@ -19,7 +19,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <gamecard_dump_tasks.hpp>
|
||||
#include <gamecard_image_dump_task.hpp>
|
||||
#include <core/gamecard.h>
|
||||
#include <scope_guard.hpp>
|
||||
#include <file_writer.hpp>
|
||||
|
@ -27,14 +27,12 @@
|
|||
namespace i18n = brls::i18n; /* For getStr(). */
|
||||
using namespace i18n::literals; /* For _i18n. */
|
||||
|
||||
#define BLOCK_SIZE 0x800000
|
||||
|
||||
namespace nxdt::tasks
|
||||
{
|
||||
GameCardDumpTaskError GameCardImageDumpTask::DoInBackground(const std::string& output_path, const bool& prepend_key_area, const bool& keep_certificate, const bool& trim_dump,
|
||||
const bool& calculate_checksum)
|
||||
const bool& calculate_checksum, const int& checksum_lookup_method)
|
||||
{
|
||||
std::scoped_lock lock(this->crc_mtx);
|
||||
std::scoped_lock lock(this->task_mtx);
|
||||
|
||||
GameCardKeyArea gc_key_area{};
|
||||
GameCardSecurityInformation gc_security_information{};
|
||||
|
@ -47,10 +45,12 @@ namespace nxdt::tasks
|
|||
|
||||
DataTransferProgress progress{};
|
||||
|
||||
/* Update private variables. */
|
||||
this->calculate_checksum = calculate_checksum;
|
||||
this->checksum_lookup_method = checksum_lookup_method;
|
||||
|
||||
LOG_MSG_DEBUG("Starting dump with parameters:\n- Output path: \"%s\".\n- Prepend key area: %u.\n- Keep certificate: %u.\n- Trim dump: %u.\n- Calculate checksum: %u.", \
|
||||
output_path.c_str(), prepend_key_area, keep_certificate, trim_dump, calculate_checksum);
|
||||
LOG_MSG_DEBUG("Starting dump with parameters:\n- Output path: \"%s\".\n- Prepend key area: %u.\n- Keep certificate: %u.\n- Trim dump: %u.\n- Calculate checksum: %u.\n- Checksum lookup method: %d.", \
|
||||
output_path.c_str(), prepend_key_area, keep_certificate, trim_dump, calculate_checksum, checksum_lookup_method);
|
||||
|
||||
/* Retrieve gamecard image size. */
|
||||
if ((!trim_dump && !gamecardGetTotalSize(&gc_img_size)) || (trim_dump && !gamecardGetTrimmedSize(&gc_img_size)) || !gc_img_size) return "tasks/gamecard/image/get_size_failed"_i18n;
|
||||
|
@ -75,6 +75,10 @@ namespace nxdt::tasks
|
|||
}
|
||||
}
|
||||
|
||||
/* Push progress onto the class. */
|
||||
progress.total_size = gc_img_size;
|
||||
this->PublishProgress(progress);
|
||||
|
||||
/* Open output file. */
|
||||
try {
|
||||
file = new nxdt::utils::FileWriter(output_path, gc_img_size);
|
||||
|
@ -84,31 +88,27 @@ namespace nxdt::tasks
|
|||
|
||||
ON_SCOPE_EXIT { delete file; };
|
||||
|
||||
/* Push progress onto the class. */
|
||||
progress.total_size = gc_img_size;
|
||||
this->PublishProgress(progress);
|
||||
|
||||
if (prepend_key_area)
|
||||
{
|
||||
/* Write GameCardKeyArea object. */
|
||||
if (!file->Write(&gc_key_area, sizeof(GameCardKeyArea))) return "tasks/gamecard/image/write_key_area_failed"_i18n;
|
||||
|
||||
/* Push progress onto the class. */
|
||||
progress.xfer_size = sizeof(GameCardKeyArea);
|
||||
progress.xfer_size += sizeof(GameCardKeyArea);
|
||||
this->PublishProgress(progress);
|
||||
}
|
||||
|
||||
/* Allocate memory buffer for the dump process. */
|
||||
buf = usbAllocatePageAlignedBuffer(BLOCK_SIZE);
|
||||
buf = usbAllocatePageAlignedBuffer(USB_TRANSFER_BUFFER_SIZE);
|
||||
if (!buf) return "generic/mem_alloc_failed"_i18n;
|
||||
|
||||
ON_SCOPE_EXIT { free(buf); };
|
||||
|
||||
/* Dump gamecard image. */
|
||||
for(size_t offset = 0, blksize = BLOCK_SIZE; offset < gc_img_size; offset += blksize)
|
||||
for(size_t offset = 0, blksize = USB_TRANSFER_BUFFER_SIZE; offset < gc_img_size; offset += blksize)
|
||||
{
|
||||
/* Don't proceed if the task has been cancelled. */
|
||||
if (this->IsCancelled()) return "generic/process_cancelled"_i18n;
|
||||
if (this->IsCancelled()) return {};
|
||||
|
||||
/* Adjust current block size, if needed. */
|
||||
if (blksize > (gc_img_size - offset)) blksize = (gc_img_size - offset);
|
|
@ -113,8 +113,8 @@ namespace nxdt::views
|
|||
|
||||
void GameCardTab::PopulateList(void)
|
||||
{
|
||||
TitleApplicationMetadata **app_metadata = nullptr;
|
||||
u32 app_metadata_count = 0;
|
||||
TitleGameCardApplicationMetadataEntry *gc_app_metadata = nullptr;
|
||||
u32 gc_app_metadata_count = 0;
|
||||
GameCardHeader card_header = {0};
|
||||
GameCardInfo card_info = {0};
|
||||
FsGameCardIdSet card_id_set = {0};
|
||||
|
@ -134,8 +134,8 @@ namespace nxdt::views
|
|||
this->list->addView(launch_error_info);
|
||||
|
||||
/* Retrieve gamecard application metadata. */
|
||||
app_metadata = titleGetGameCardApplicationMetadataEntries(&app_metadata_count);
|
||||
if (app_metadata)
|
||||
gc_app_metadata = titleGetGameCardApplicationMetadataEntries(&gc_app_metadata_count);
|
||||
if (gc_app_metadata)
|
||||
{
|
||||
/* Display the applications that are part of the inserted gamecard. */
|
||||
this->list->addView(new brls::Header("gamecard_tab/list/user_titles/header"_i18n));
|
||||
|
@ -147,15 +147,33 @@ namespace nxdt::views
|
|||
this->list->addView(user_titles_info);
|
||||
|
||||
/* Populate list. */
|
||||
for(u32 i = 0; i < app_metadata_count; i++)
|
||||
for(u32 i = 0; i < gc_app_metadata_count; i++)
|
||||
{
|
||||
TitlesTabItem *title = new TitlesTabItem(app_metadata[i], false, false);
|
||||
TitleGameCardApplicationMetadataEntry *cur_gc_app_metadata = &(gc_app_metadata[i]);
|
||||
|
||||
/* Create item. */
|
||||
TitlesTabItem *title = new TitlesTabItem(cur_gc_app_metadata->app_metadata, false, false);
|
||||
|
||||
/* Unregister A button action. */
|
||||
title->unregisterAction(brls::Key::A);
|
||||
|
||||
/* Set version information as the item sublabel instead of the title author. */
|
||||
std::string sublabel{};
|
||||
if (cur_gc_app_metadata->display_version[0])
|
||||
{
|
||||
sublabel = fmt::format("v{} ({})", cur_gc_app_metadata->version.value, cur_gc_app_metadata->display_version);
|
||||
} else {
|
||||
sublabel = fmt::format("v{}", cur_gc_app_metadata->version.value);
|
||||
}
|
||||
|
||||
title->setSubLabel(sublabel);
|
||||
|
||||
/* Add view to item list. */
|
||||
this->list->addView(title);
|
||||
}
|
||||
|
||||
/* Free application metadata array. */
|
||||
free(app_metadata);
|
||||
/* Free gamecard application metadata array. */
|
||||
free(gc_app_metadata);
|
||||
}
|
||||
|
||||
/* Populate gamecard properties table. */
|
||||
|
|
|
@ -49,8 +49,11 @@ namespace nxdt::views
|
|||
|
||||
/* Subscribe to the download task. */
|
||||
this->download_task.RegisterListener([&](const nxdt::tasks::DataTransferProgress& progress) {
|
||||
/* Return immediately if the download task was cancelled. */
|
||||
if (this->download_task.IsCancelled()) return;
|
||||
|
||||
/* Update progress. */
|
||||
this->update_progress->setProgress(progress);
|
||||
this->update_progress->SetProgress(progress);
|
||||
|
||||
/* Check if the download task has finished. */
|
||||
if (this->download_task.IsFinished())
|
||||
|
@ -167,7 +170,7 @@ namespace nxdt::views
|
|||
/* Cancel JSON task. */
|
||||
this->json_task.Cancel();
|
||||
|
||||
/* Pop view. */
|
||||
/* Pop view. This will invoke this class' destructor. */
|
||||
brls::Application::popView(brls::ViewAnimation::SLIDE_RIGHT);
|
||||
|
||||
return true;
|
||||
|
@ -245,19 +248,23 @@ namespace nxdt::views
|
|||
/* Subscribe to the NRO task. */
|
||||
this->nro_task.RegisterListener([&](const nxdt::tasks::DataTransferProgress& progress) {
|
||||
/* Update progress. */
|
||||
this->update_progress->setProgress(progress);
|
||||
this->update_progress->SetProgress(progress);
|
||||
|
||||
/* Check if the download task has finished. */
|
||||
if (this->nro_task.IsFinished())
|
||||
{
|
||||
/* Get NRO task result and immediately set application updated state if the task succeeded. */
|
||||
bool ret = this->nro_task.GetResult();
|
||||
if (ret) utilsSetApplicationUpdatedState();
|
||||
/* Check if the download task was cancelled. */
|
||||
if (!this->nro_task.IsCancelled())
|
||||
{
|
||||
/* Get NRO task result and immediately set application updated state if the task succeeded. */
|
||||
bool ret = this->nro_task.GetResult();
|
||||
if (ret) utilsSetApplicationUpdatedState();
|
||||
|
||||
/* Display notification. */
|
||||
brls::Application::notify(ret ? "options_tab/notifications/app_updated"_i18n : "options_tab/notifications/update_failed"_i18n);
|
||||
/* Display notification. */
|
||||
brls::Application::notify(ret ? "options_tab/notifications/app_updated"_i18n : "options_tab/notifications/update_failed"_i18n);
|
||||
}
|
||||
|
||||
/* Pop view */
|
||||
/* Pop view. */
|
||||
this->onCancel();
|
||||
}
|
||||
});
|
||||
|
@ -356,13 +363,13 @@ namespace nxdt::views
|
|||
});
|
||||
});
|
||||
|
||||
/* Update UMS devices vector. */
|
||||
/* Manually update UMS devices vector. */
|
||||
this->ums_devices = this->root_view->GetUmsDevices();
|
||||
|
||||
/* Subscribe to the UMS device event. */
|
||||
this->ums_task_sub = this->root_view->RegisterUmsTaskListener([this, unmount_ums_device](const nxdt::tasks::UmsDeviceVector& ums_devices) {
|
||||
/* Update UMS devices vector. */
|
||||
this->ums_devices = this->root_view->GetUmsDevices();
|
||||
this->ums_devices = ums_devices;
|
||||
|
||||
/* Generate values vector for the dropdown. */
|
||||
std::vector<std::string> values{};
|
||||
|
|
|
@ -33,6 +33,7 @@ namespace nxdt::views
|
|||
{
|
||||
RootView::RootView() : brls::TabFrame()
|
||||
{
|
||||
/* Get Material font ID. */
|
||||
int material = brls::Application::getFontStash()->material;
|
||||
|
||||
/* Set UI properties. */
|
||||
|
@ -42,6 +43,9 @@ namespace nxdt::views
|
|||
/* Check if we're running under applet mode. */
|
||||
this->applet_mode = utilsIsAppletMode();
|
||||
|
||||
/* Cache output storage configuration value. */
|
||||
this->output_storage = configGetInteger("output_storage");
|
||||
|
||||
/* Create labels. */
|
||||
this->applet_mode_lbl = new brls::Label(brls::LabelStyle::HINT, "root_view/applet_mode"_i18n);
|
||||
this->applet_mode_lbl->setColor(nvgRGB(255, 0, 0));
|
||||
|
@ -159,6 +163,9 @@ namespace nxdt::views
|
|||
this->ums_task_sub = this->ums_task->RegisterListener([this](const nxdt::tasks::UmsDeviceVector& ums_devices) {
|
||||
/* Update UMS counter label. */
|
||||
this->ums_counter_lbl->setText(i18n::getStr("root_view/ums_counter"_i18n, usbHsFsGetPhysicalDeviceCount()));
|
||||
|
||||
/* Update cached output storage value, if needed. */
|
||||
if (this->output_storage > ConfigOutputStorage_UsbHost) this->output_storage = ConfigOutputStorage_SdCard;
|
||||
});
|
||||
|
||||
/* Subscribe to USB host event. */
|
||||
|
|
|
@ -308,4 +308,9 @@ namespace nxdt::tasks
|
|||
this->usb_host_event.fire(this->cur_usb_host_speed);
|
||||
}
|
||||
}
|
||||
|
||||
const UsbHostSpeed& UsbHostTask::GetUsbHostSpeed(void)
|
||||
{
|
||||
return this->cur_usb_host_speed;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue