diff --git a/include/async_task.hpp b/include/async_task.hpp index 2bda542..57c08fb 100644 --- a/include/async_task.hpp +++ b/include/async_task.hpp @@ -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(); } diff --git a/include/core/nxdt_log.h b/include/core/nxdt_log.h index 48504a8..d87a4a8 100644 --- a/include/core/nxdt_log.h +++ b/include/core/nxdt_log.h @@ -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. diff --git a/include/core/nxdt_utils.h b/include/core/nxdt_utils.h index dab2ecc..9730bcc 100644 --- a/include/core/nxdt_utils.h +++ b/include/core/nxdt_utils.h @@ -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. diff --git a/include/core/title.h b/include/core/title.h index cbeb435..4efe63d 100644 --- a/include/core/title.h +++ b/include/core/title.h @@ -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. diff --git a/include/core/ums.h b/include/core/ums.h index ac7a2d8..f85a33c 100644 --- a/include/core/ums.h +++ b/include/core/ums.h @@ -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); diff --git a/include/data_transfer_progress_display.hpp b/include/data_transfer_progress_display.hpp index 10b4f83..5e66447 100644 --- a/include/data_transfer_progress_display.hpp +++ b/include/data_transfer_progress_display.hpp @@ -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; diff --git a/include/data_transfer_task.hpp b/include/data_transfer_task.hpp index efd8c3b..13c0c35 100644 --- a/include/data_transfer_task.hpp +++ b/include/data_transfer_task.hpp @@ -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(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(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(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); diff --git a/include/data_transfer_task_frame.hpp b/include/data_transfer_task_frame.hpp new file mode 100644 index 0000000..b68f174 --- /dev/null +++ b/include/data_transfer_task_frame.hpp @@ -0,0 +1,177 @@ +/* + * data_transfer_task_frame.hpp + * + * Copyright (c) 2020-2024, DarkMatterCore . + * + * 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 . + */ + +#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 + class DataTransferTaskFrame: public brls::AppletFrame + { + static_assert(nxdt::utils::is_base_of_template_v, "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 + 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 + 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 + 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__ */ diff --git a/include/download_task.hpp b/include/download_task.hpp index d7dd756..e8e3caa 100644 --- a/include/download_task.hpp +++ b/include/download_task.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 { protected: diff --git a/include/dump_options_frame.hpp b/include/dump_options_frame.hpp index 3e159fb..e82b0c6 100644 --- a/include/dump_options_frame.hpp +++ b/include/dump_options_frame.hpp @@ -51,7 +51,8 @@ namespace nxdt::views std::string SanitizeUserFileName(void); - void UpdateOutputStorages(const nxdt::tasks::UmsDeviceVector& ums_devices); + std::vector 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) diff --git a/include/error_frame.hpp b/include/error_frame.hpp index 71ab0b7..b767799 100644 --- a/include/error_frame.hpp +++ b/include/error_frame.hpp @@ -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); }; } diff --git a/include/focusable_item.hpp b/include/focusable_item.hpp index 1ac171a..5d4589a 100644 --- a/include/focusable_item.hpp +++ b/include/focusable_item.hpp @@ -25,12 +25,15 @@ #define __FOCUSABLE_ITEM_HPP__ #include +#include namespace nxdt::views { template class FocusableItem: public ViewType { + static_assert(std::is_base_of_v, "ViewType must inherit from brls::View"); + private: bool highlight, highlight_bg; diff --git a/include/gamecard_dump_tasks.hpp b/include/gamecard_image_dump_task.hpp similarity index 81% rename from include/gamecard_dump_tasks.hpp rename to include/gamecard_image_dump_task.hpp index af03fbb..3a610e9 100644 --- a/include/gamecard_dump_tasks.hpp +++ b/include/gamecard_image_dump_task.hpp @@ -1,5 +1,5 @@ /* - * gamecard_dump_tasks.hpp + * gamecard_image_dump_task.hpp * * Copyright (c) 2020-2024, DarkMatterCore . * @@ -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 #include @@ -33,12 +33,14 @@ namespace nxdt::tasks { typedef std::optional GameCardDumpTaskError; - class GameCardImageDumpTask: public DataTransferTask + /* Generates an image dump out of the inserted gamecard. */ + class GameCardImageDumpTask: public DataTransferTask { 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__ */ diff --git a/include/gamecard_image_dump_task_frame.hpp b/include/gamecard_image_dump_task_frame.hpp new file mode 100644 index 0000000..c5b52b7 --- /dev/null +++ b/include/gamecard_image_dump_task_frame.hpp @@ -0,0 +1,58 @@ +/* + * gamecard_image_dump_task_frame.hpp + * + * Copyright (c) 2020-2024, DarkMatterCore . + * + * 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 . + */ + +#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 + { + 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 + GameCardImageDumpTaskFrame(Params... params) : + DataTransferTaskFrame(brls::i18n::getStr("gamecard_tab/list/dump_card_image/label"), params...) { } + }; +} + +#endif /* __GAMECARD_IMAGE_DUMP_TASK_FRAME_HPP__ */ diff --git a/include/is_base_of_template.hpp b/include/is_base_of_template.hpp new file mode 100644 index 0000000..0600eec --- /dev/null +++ b/include/is_base_of_template.hpp @@ -0,0 +1,55 @@ +/* + * is_base_of_template.hpp + * + * Copyright (c) 2020-2024, DarkMatterCore . + * + * 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 . + */ + +#pragma once + +#ifndef __IS_BASE_OF_TEMPLATE_HPP__ +#define __IS_BASE_OF_TEMPLATE_HPP__ + +#include +#include + +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