rewrite everything
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
/********************************************************************************
|
||||
* File: debug_funcs.cpp
|
||||
* Author: ppkantorski
|
||||
* Description:
|
||||
* This source file contains the implementation of debugging functions for the
|
||||
* Ultrahand Overlay project.
|
||||
********************************************************************************/
|
||||
|
||||
#include "debug_funcs.hpp"
|
||||
|
||||
namespace ult {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
// Define static variables
|
||||
const std::string defaultLogFilePath = "sdmc:/switch/.packages/log.txt";
|
||||
|
||||
std::string logFilePath = defaultLogFilePath;
|
||||
bool disableLogging = true;
|
||||
std::mutex logMutex;
|
||||
|
||||
void logMessage(const std::string& message) {
|
||||
//if (disableLogging)
|
||||
// return;
|
||||
|
||||
std::time_t currentTime = std::time(nullptr);
|
||||
std::tm* timeInfo = std::localtime(¤tTime);
|
||||
char timestamp[30]; // Exact size for "[YYYY-MM-DD HH:MM:SS] " + null terminator
|
||||
strftime(timestamp, sizeof(timestamp), "[%Y-%m-%d %H:%M:%S] ", timeInfo);
|
||||
|
||||
// Depending on the directive, use either std::ofstream or stdio functions
|
||||
#if !USING_FSTREAM_DIRECTIVE
|
||||
// Use stdio functions to open, write, and close the file
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(logMutex); // Locks the mutex for thread-safe access
|
||||
|
||||
FILE* file = fopen(logFilePath.c_str(), "a"); // Open the file in append mode
|
||||
if (file != nullptr) {
|
||||
fprintf(file, "%s%s\n", timestamp, message.c_str());
|
||||
fclose(file); // Close the file after writing
|
||||
} else {
|
||||
// Handle error when file opening fails (if necessary)
|
||||
// printf("Failed to open log file: %s\n", logFilePath.c_str());
|
||||
}
|
||||
}
|
||||
#else
|
||||
// Use std::ofstream if !USING_FSTREAM_DIRECTIVE is not defined
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(logMutex); // Locks the mutex for the duration of this block
|
||||
|
||||
std::ofstream file(logFilePath.c_str(), std::ios::app);
|
||||
if (file.is_open()) {
|
||||
file << timestamp << message << "\n";
|
||||
} else {
|
||||
// Handle error when file opening fails
|
||||
// std::cerr << "Failed to open log file: " << logFilePath << std::endl;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,812 @@
|
||||
/********************************************************************************
|
||||
* File: download_funcs.cpp
|
||||
* Author: ppkantorski
|
||||
* Description:
|
||||
* This source file provides implementations for the functions declared in
|
||||
* download_funcs.hpp. These functions utilize libcurl for downloading files
|
||||
* from the internet and minizip-ng for extracting ZIP archives with proper
|
||||
* 64-bit file support.
|
||||
*
|
||||
* For the latest updates and contributions, visit the project's GitHub repository.
|
||||
* (GitHub Repository: https://github.com/ppkantorski/Ultrahand-Overlay)
|
||||
*
|
||||
* Note: Please be aware that this notice cannot be altered or removed. It is a part
|
||||
* of the project's documentation and must remain intact.
|
||||
*
|
||||
* Licensed under both GPLv2 and CC-BY-4.0
|
||||
* Copyright (c) 2024 ppkantorski
|
||||
********************************************************************************/
|
||||
|
||||
#include "download_funcs.hpp"
|
||||
|
||||
|
||||
namespace ult {
|
||||
|
||||
size_t DOWNLOAD_READ_BUFFER = 16*1024;//64 * 1024;//4096*10;
|
||||
size_t DOWNLOAD_WRITE_BUFFER = 16*1024;//64 * 1024;
|
||||
size_t UNZIP_READ_BUFFER = 32*1024;//131072*2;//4096*4;
|
||||
size_t UNZIP_WRITE_BUFFER = 16*1024;//131072*2;//4096*4;
|
||||
|
||||
|
||||
// Path to the CA certificate
|
||||
//const std::string cacertPath = "sdmc:/config/ultrahand/cacert.pem";
|
||||
//const std::string cacertURL = "https://curl.se/ca/cacert.pem";
|
||||
|
||||
// Shared atomic flag to indicate whether to abort the download operation
|
||||
std::atomic<bool> abortDownload(false);
|
||||
// Define an atomic bool for interpreter completion
|
||||
std::atomic<bool> abortUnzip(false);
|
||||
std::atomic<int> downloadPercentage(-1);
|
||||
std::atomic<int> unzipPercentage(-1);
|
||||
|
||||
// Thread-safe curl initialization
|
||||
static std::mutex curlInitMutex;
|
||||
static std::atomic<bool> curlInitialized(false);
|
||||
|
||||
|
||||
|
||||
struct FileDeleter {
|
||||
void operator()(FILE* f) const { if (f) fclose(f); }
|
||||
};
|
||||
|
||||
// Definition of CurlDeleter
|
||||
void CurlDeleter::operator()(CURL* curl) const {
|
||||
if (curl) {
|
||||
curl_easy_cleanup(curl);
|
||||
}
|
||||
}
|
||||
|
||||
// Callback function to write received data to a file.
|
||||
#if !USING_FSTREAM_DIRECTIVE
|
||||
// Using stdio.h functions (FILE*, fwrite)
|
||||
size_t writeCallback(void* ptr, size_t size, size_t nmemb, FILE* stream) {
|
||||
if (!ptr || !stream) return 0;
|
||||
//size_t totalBytes = size * nmemb;
|
||||
//size_t writtenBytes = fwrite(ptr, 1, totalBytes, stream);
|
||||
//return writtenBytes;
|
||||
return fwrite(ptr, 1, size * nmemb, stream);
|
||||
}
|
||||
#else
|
||||
// Using std::ofstream for writing
|
||||
size_t writeCallback(void* ptr, size_t size, size_t nmemb, std::ostream* stream) {
|
||||
if (!ptr || !stream) return 0;
|
||||
auto& file = *static_cast<std::ofstream*>(stream);
|
||||
//size_t totalBytes = size * nmemb;
|
||||
file.write(static_cast<const char*>(ptr), size * nmemb);
|
||||
return totalBytes;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Your C function
|
||||
int progressCallback(void *ptr, curl_off_t totalToDownload, curl_off_t nowDownloaded, curl_off_t totalToUpload, curl_off_t nowUploaded) {
|
||||
if (!ptr) return 1;
|
||||
|
||||
auto percentage = static_cast<std::atomic<int>*>(ptr);
|
||||
|
||||
if (totalToDownload > 0) {
|
||||
//int newProgress = static_cast<int>((static_cast<double>(nowDownloaded) / static_cast<double>(totalToDownload)) * 100.0);
|
||||
percentage->store(static_cast<int>((static_cast<double>(nowDownloaded) / static_cast<double>(totalToDownload)) * 100.0), std::memory_order_release);
|
||||
}
|
||||
|
||||
if (abortDownload.load(std::memory_order_acquire)) {
|
||||
percentage->store(-1, std::memory_order_release);
|
||||
return 1; // Abort the download
|
||||
}
|
||||
|
||||
return 0; // Continue the download
|
||||
}
|
||||
|
||||
// Global initialization function
|
||||
void initializeCurl() {
|
||||
std::lock_guard<std::mutex> lock(curlInitMutex);
|
||||
if (!curlInitialized.load(std::memory_order_acquire)) {
|
||||
const CURLcode res = curl_global_init(CURL_GLOBAL_DEFAULT);
|
||||
if (res != CURLE_OK) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("curl_global_init() failed: " + std::string(curl_easy_strerror(res)));
|
||||
#endif
|
||||
// Handle error appropriately, possibly exit the program
|
||||
} else {
|
||||
curlInitialized.store(true, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Global cleanup function
|
||||
void cleanupCurl() {
|
||||
std::lock_guard<std::mutex> lock(curlInitMutex);
|
||||
if (curlInitialized.load(std::memory_order_acquire)) {
|
||||
curl_global_cleanup();
|
||||
curlInitialized.store(false, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Downloads a file from a URL to a specified destination.
|
||||
*
|
||||
* @param url The URL of the file to download.
|
||||
* @param toDestination The destination path where the file should be saved.
|
||||
* @return True if the download was successful, false otherwise.
|
||||
*/
|
||||
bool downloadFile(const std::string& url, const std::string& toDestination, bool noPercentagePolling) {
|
||||
abortDownload.store(false, std::memory_order_release);
|
||||
|
||||
if (url.find_first_of("{}") != std::string::npos) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Invalid URL: " + url);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string destination = toDestination;
|
||||
if (destination.back() == '/') {
|
||||
createDirectory(destination);
|
||||
const size_t lastSlash = url.find_last_of('/');
|
||||
if (lastSlash != std::string::npos) {
|
||||
destination += url.substr(lastSlash + 1);
|
||||
} else {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Invalid URL: " + url);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
createDirectory(destination.substr(0, destination.find_last_of('/')));
|
||||
}
|
||||
|
||||
const std::string tempFilePath = getParentDirFromPath(destination) + "." + getFileName(destination) + ".tmp";
|
||||
|
||||
#if USING_FSTREAM_DIRECTIVE
|
||||
// Use ofstream if !USING_FSTREAM_DIRECTIVE is not defined
|
||||
std::ofstream file(tempFilePath, std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Error opening file: " + tempFilePath);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
// Alternative method of opening file (depending on your platform, like using POSIX open())
|
||||
std::unique_ptr<FILE, FileDeleter> file(fopen(tempFilePath.c_str(), "wb"));
|
||||
if (!file) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Error opening file: " + tempFilePath);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
// ADD THIS: Set up write buffer for better performance
|
||||
std::unique_ptr<char[]> writeBuffer;
|
||||
if (DOWNLOAD_WRITE_BUFFER > 0) {
|
||||
writeBuffer = std::make_unique<char[]>(DOWNLOAD_WRITE_BUFFER);
|
||||
// _IOFBF = full buffering, _IOLBF = line buffering, _IONBF = no buffering
|
||||
setvbuf(file.get(), writeBuffer.get(), _IOFBF, DOWNLOAD_WRITE_BUFFER);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Ensure curl is initialized
|
||||
initializeCurl();
|
||||
|
||||
std::unique_ptr<CURL, CurlDeleter> curl(curl_easy_init());
|
||||
if (!curl) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Error initializing curl.");
|
||||
#endif
|
||||
#if USING_FSTREAM_DIRECTIVE
|
||||
file.close();
|
||||
#else
|
||||
file.reset();
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only initialize downloadPercentage if we're tracking progress
|
||||
if (!noPercentagePolling) {
|
||||
downloadPercentage.store(0, std::memory_order_release);
|
||||
}
|
||||
|
||||
curl_easy_setopt(curl.get(), CURLOPT_URL, url.c_str());
|
||||
curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, writeCallback);
|
||||
#if USING_FSTREAM_DIRECTIVE
|
||||
curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &file);
|
||||
#else
|
||||
curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, file.get());
|
||||
#endif
|
||||
|
||||
// Conditionally set up progress callback based on noPercentagePolling
|
||||
if (noPercentagePolling) {
|
||||
// Disable progress function entirely
|
||||
curl_easy_setopt(curl.get(), CURLOPT_NOPROGRESS, 1L);
|
||||
} else {
|
||||
// Enable progress callback for percentage updates
|
||||
curl_easy_setopt(curl.get(), CURLOPT_NOPROGRESS, 0L);
|
||||
curl_easy_setopt(curl.get(), CURLOPT_XFERINFOFUNCTION, progressCallback);
|
||||
curl_easy_setopt(curl.get(), CURLOPT_XFERINFODATA, &downloadPercentage);
|
||||
}
|
||||
|
||||
curl_easy_setopt(curl.get(), CURLOPT_USERAGENT, userAgent);
|
||||
curl_easy_setopt(curl.get(), CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS); // Enable HTTP/2
|
||||
curl_easy_setopt(curl.get(), CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2); // Force TLS 1.2
|
||||
|
||||
curl_easy_setopt(curl.get(), CURLOPT_FOLLOWLOCATION, 1L);
|
||||
curl_easy_setopt(curl.get(), CURLOPT_BUFFERSIZE, DOWNLOAD_READ_BUFFER); // Increase buffer size
|
||||
|
||||
// Add timeout options
|
||||
curl_easy_setopt(curl.get(), CURLOPT_CONNECTTIMEOUT, 10L); // 10 seconds to connect
|
||||
curl_easy_setopt(curl.get(), CURLOPT_LOW_SPEED_LIMIT, 1L); // 1 byte/s (virtually any progress)
|
||||
curl_easy_setopt(curl.get(), CURLOPT_LOW_SPEED_TIME, 60L); // 1 minutes of no progress
|
||||
|
||||
CURLcode result = curl_easy_perform(curl.get());
|
||||
|
||||
#if USING_FSTREAM_DIRECTIVE
|
||||
file.close();
|
||||
#else
|
||||
file.reset();
|
||||
#endif
|
||||
|
||||
if (result != CURLE_OK) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (result == CURLE_OPERATION_TIMEDOUT) {
|
||||
if (!disableLogging)
|
||||
logMessage("Download timed out: " + url);
|
||||
} else if (result == CURLE_COULDNT_CONNECT) {
|
||||
if (!disableLogging)
|
||||
logMessage("Could not connect to: " + url);
|
||||
} else {
|
||||
if (!disableLogging)
|
||||
logMessage("Error downloading file: " + std::string(curl_easy_strerror(result)));
|
||||
}
|
||||
#endif
|
||||
deleteFileOrDirectory(tempFilePath);
|
||||
// Only update percentage if we're tracking it
|
||||
if (!noPercentagePolling) {
|
||||
downloadPercentage.store(-1, std::memory_order_release);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#if USING_FSTREAM_DIRECTIVE
|
||||
std::ifstream checkFile(tempFilePath);
|
||||
if (!checkFile || checkFile.peek() == std::ifstream::traits_type::eof()) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Error downloading file: Empty file");
|
||||
#endif
|
||||
deleteFileOrDirectory(tempFilePath);
|
||||
// Only update percentage if we're tracking it
|
||||
if (!noPercentagePolling) {
|
||||
downloadPercentage.store(-1, std::memory_order_release);
|
||||
}
|
||||
checkFile.close();
|
||||
return false;
|
||||
}
|
||||
checkFile.close();
|
||||
#else
|
||||
// Alternative method for checking if the file is empty (POSIX example)
|
||||
struct stat fileStat;
|
||||
if (stat(tempFilePath.c_str(), &fileStat) != 0 || fileStat.st_size == 0) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Error downloading file: Empty file");
|
||||
#endif
|
||||
deleteFileOrDirectory(tempFilePath);
|
||||
// Only update percentage if we're tracking it
|
||||
if (!noPercentagePolling) {
|
||||
downloadPercentage.store(-1, std::memory_order_release);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Only update percentage if we're tracking it
|
||||
if (!noPercentagePolling) {
|
||||
downloadPercentage.store(100, std::memory_order_release);
|
||||
}
|
||||
|
||||
// CHECK FOR PROTECTED FILES AND ADD .ultra EXTENSION IF NEEDED
|
||||
if (PROTECTED_FILES.find(destination) != PROTECTED_FILES.end()) {
|
||||
destination += ".ultra";
|
||||
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Protected file detected, renaming download to: " + destination);
|
||||
#endif
|
||||
}
|
||||
|
||||
moveFile(tempFilePath, destination);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Custom I/O function for opening files with larger buffer
|
||||
*/
|
||||
static voidpf ZCALLBACK fopen64_file_func_custom(voidpf opaque, const void* filename, int mode) {
|
||||
FILE* file = nullptr;
|
||||
const char* mode_fopen = nullptr;
|
||||
|
||||
if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER) == ZLIB_FILEFUNC_MODE_READ)
|
||||
mode_fopen = "rb";
|
||||
else if (mode & ZLIB_FILEFUNC_MODE_EXISTING)
|
||||
mode_fopen = "r+b";
|
||||
else if (mode & ZLIB_FILEFUNC_MODE_CREATE)
|
||||
mode_fopen = "wb";
|
||||
|
||||
if ((filename != nullptr) && (mode_fopen != nullptr)) {
|
||||
file = fopen((const char*)filename, mode_fopen);
|
||||
if (file && ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER) == ZLIB_FILEFUNC_MODE_READ)) {
|
||||
// Set 64KB buffer for reading the ZIP file - reduces syscalls
|
||||
//static const size_t zipReadBufferSize = UNZIP_READ_BUFFER;
|
||||
setvbuf(file, nullptr, _IOFBF, UNZIP_READ_BUFFER);
|
||||
}
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
static int ZCALLBACK fclose64_file_func_custom(voidpf opaque, voidpf stream) {
|
||||
int ret = EOF;
|
||||
if (stream != nullptr) {
|
||||
ret = fclose((FILE*)stream);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Extracts files from a ZIP archive to a specified destination.
|
||||
*
|
||||
* Ultra-optimized single-pass extraction with smooth byte-based progress reporting
|
||||
* using miniz with proper 64-bit file support and streaming extraction.
|
||||
* Fixed memory leaks with RAII for proper resource cleanup on abort.
|
||||
*
|
||||
* @param zipFilePath The path to the ZIP archive file.
|
||||
* @param toDestination The destination directory where files should be extracted.
|
||||
* @return True if the extraction was successful, false otherwise.
|
||||
*/
|
||||
bool unzipFile(const std::string& zipFilePath, const std::string& toDestination) {
|
||||
abortUnzip.store(false, std::memory_order_release);
|
||||
unzipPercentage.store(0, std::memory_order_release);
|
||||
|
||||
// Time-based abort checking - pre-calculated constants
|
||||
//u64 lastAbortCheck = armTicksToNs(armGetSystemTick());
|
||||
//u64 currentNanos; // Reused for all tick operations
|
||||
bool success = true;
|
||||
|
||||
// RAII wrapper for unzFile
|
||||
struct UnzFileManager {
|
||||
unzFile file = nullptr;
|
||||
|
||||
UnzFileManager(const std::string& path) {
|
||||
zlib_filefunc64_def ffunc;
|
||||
fill_fopen64_filefunc(&ffunc);
|
||||
ffunc.zopen64_file = fopen64_file_func_custom;
|
||||
ffunc.zclose_file = fclose64_file_func_custom;
|
||||
file = unzOpen2_64(path.c_str(), &ffunc);
|
||||
}
|
||||
|
||||
~UnzFileManager() {
|
||||
if (file) {
|
||||
unzClose(file);
|
||||
file = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool is_valid() const { return file != nullptr; }
|
||||
operator unzFile() const { return file; }
|
||||
};
|
||||
|
||||
// RAII wrapper for output file
|
||||
struct OutputFileManager {
|
||||
#if !USING_FSTREAM_DIRECTIVE
|
||||
FILE* file = nullptr;
|
||||
std::unique_ptr<char[]> buffer;
|
||||
size_t bufferSize;
|
||||
|
||||
OutputFileManager(size_t bufSize) : bufferSize(bufSize) {
|
||||
buffer = std::make_unique<char[]>(bufferSize);
|
||||
}
|
||||
|
||||
bool open(const std::string& path) {
|
||||
close();
|
||||
file = fopen(path.c_str(), "wb");
|
||||
if (file) {
|
||||
setvbuf(file, buffer.get(), _IOFBF, bufferSize);
|
||||
}
|
||||
return file != nullptr;
|
||||
}
|
||||
|
||||
void close() {
|
||||
if (file) {
|
||||
fclose(file);
|
||||
file = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool is_open() const { return file != nullptr; }
|
||||
|
||||
size_t write(const void* data, size_t size) {
|
||||
return file ? fwrite(data, 1, size, file) : 0;
|
||||
}
|
||||
|
||||
~OutputFileManager() { close(); }
|
||||
#else
|
||||
std::ofstream file;
|
||||
|
||||
OutputFileManager(size_t bufSize) {
|
||||
// Constructor for consistency with FILE* version
|
||||
}
|
||||
|
||||
bool open(const std::string& path) {
|
||||
close();
|
||||
file.open(path, std::ios::binary);
|
||||
if (file.is_open()) {
|
||||
file.rdbuf()->pubsetbuf(nullptr, UNZIP_WRITE_BUFFER);
|
||||
}
|
||||
return file.is_open();
|
||||
}
|
||||
|
||||
void close() {
|
||||
if (file.is_open()) {
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
|
||||
bool is_open() const { return file.is_open(); }
|
||||
|
||||
size_t write(const void* data, size_t size) {
|
||||
if (file.is_open()) {
|
||||
file.write(static_cast<const char*>(data), size);
|
||||
return file.good() ? size : 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
UnzFileManager zipFile(zipFilePath);
|
||||
if (!zipFile.is_valid()) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Failed to open zip file: " + zipFilePath);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get global info about the ZIP file
|
||||
unz_global_info64 globalInfo;
|
||||
if (unzGetGlobalInfo64(zipFile, &globalInfo) != UNZ_OK) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Failed to get zip file info");
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
const uLong numFiles = globalInfo.number_entry;
|
||||
if (numFiles == 0) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("No files found in archive");
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
// ALWAYS calculate total size for accurate byte-based progress
|
||||
ZPOS64_T totalUncompressedSize = 0;
|
||||
char tempFilenameBuffer[512];
|
||||
unz_file_info64 fileInfo;
|
||||
|
||||
// First pass: calculate total uncompressed size
|
||||
int result = unzGoToFirstFile(zipFile);
|
||||
while (result == UNZ_OK) {
|
||||
// Time-based abort check at start of each file (only if 2+ seconds have passed)
|
||||
//currentNanos = armTicksToNs(armGetSystemTick());
|
||||
//if ((currentNanos - lastAbortCheck) >= 2000000000ULL) {
|
||||
// if (abortUnzip.load(std::memory_order_relaxed)) {
|
||||
// unzipPercentage.store(-1, std::memory_order_release);
|
||||
// #if USING_LOGGING_DIRECTIVE
|
||||
// logMessage("Extraction aborted during size calculation");
|
||||
// #endif
|
||||
// abortUnzip.store(false, std::memory_order_release);
|
||||
// return false;
|
||||
// }
|
||||
// lastAbortCheck = currentNanos;
|
||||
//}
|
||||
if (abortUnzip.load(std::memory_order_relaxed)) {
|
||||
unzipPercentage.store(-1, std::memory_order_release);
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Extraction aborted during size calculation");
|
||||
#endif
|
||||
abortUnzip.store(false, std::memory_order_release);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (unzGetCurrentFileInfo64(zipFile, &fileInfo, tempFilenameBuffer, sizeof(tempFilenameBuffer),
|
||||
nullptr, 0, nullptr, 0) == UNZ_OK) {
|
||||
const size_t nameLen = strlen(tempFilenameBuffer);
|
||||
if (nameLen > 0 && tempFilenameBuffer[nameLen - 1] != '/') {
|
||||
totalUncompressedSize += std::max(fileInfo.uncompressed_size, static_cast<ZPOS64_T>(1));
|
||||
}
|
||||
}
|
||||
result = unzGoToNextFile(zipFile);
|
||||
}
|
||||
|
||||
// Fallback to 1 if no actual data (avoid division by zero)
|
||||
if (totalUncompressedSize == 0) {
|
||||
totalUncompressedSize = 1;
|
||||
}
|
||||
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging) {
|
||||
logMessage("Processing " + std::to_string(numFiles) + " files, " +
|
||||
std::to_string(totalUncompressedSize) + " total bytes from archive");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Pre-allocate ALL reusable strings and variables outside the main loop
|
||||
std::string fileName, extractedFilePath, directoryPath;
|
||||
//fileName.reserve(512);
|
||||
//extractedFilePath.reserve(1024);
|
||||
//directoryPath.reserve(1024);
|
||||
|
||||
// Single large buffer for extraction - reused for all files
|
||||
const size_t bufferSize = UNZIP_WRITE_BUFFER;
|
||||
std::unique_ptr<char[]> buffer = std::make_unique<char[]>(bufferSize);
|
||||
char filenameBuffer[512]; // Stack allocated for filename reading
|
||||
|
||||
// Progress tracking variables - OPTIMIZED for smooth byte-based tracking
|
||||
ZPOS64_T totalBytesProcessed = 0;
|
||||
uLong filesProcessed = 0;
|
||||
int currentProgress = 0; // Current percentage (0-100)
|
||||
|
||||
// Create output file manager
|
||||
OutputFileManager outputFile(bufferSize);
|
||||
|
||||
// Loop variables moved outside
|
||||
bool extractSuccess;
|
||||
ZPOS64_T fileBytesProcessed;
|
||||
int bytesRead;
|
||||
|
||||
// String operation variables
|
||||
const char* filename;
|
||||
size_t nameLen;
|
||||
size_t lastSlashPos;
|
||||
size_t invalid_pos;
|
||||
size_t start_pos;
|
||||
|
||||
// Ensure destination directory exists
|
||||
createDirectory(toDestination);
|
||||
|
||||
// Ensure destination ends with '/' - pre-allocate final string
|
||||
std::string destination;
|
||||
//destination.reserve(toDestination.size() + 1);
|
||||
|
||||
destination = toDestination;
|
||||
if (!destination.empty() && destination.back() != '/') {
|
||||
destination += '/';
|
||||
}
|
||||
|
||||
int newProgress;;
|
||||
|
||||
// Extract files
|
||||
result = unzGoToFirstFile(zipFile);
|
||||
while (result == UNZ_OK && success) {
|
||||
// Time-based abort check at start of each file (only if 2+ seconds have passed)
|
||||
//currentNanos = armTicksToNs(armGetSystemTick());
|
||||
//if ((currentNanos - lastAbortCheck) >= 2000000000ULL) {
|
||||
// if (abortUnzip.load(std::memory_order_relaxed)) {
|
||||
// success = false;
|
||||
// break; // RAII will handle cleanup
|
||||
// }
|
||||
// lastAbortCheck = currentNanos;
|
||||
//}
|
||||
|
||||
if (abortUnzip.load(std::memory_order_relaxed)) {
|
||||
success = false;
|
||||
break; // RAII will handle cleanup
|
||||
}
|
||||
|
||||
// Get current file info - reuse fileInfo variable
|
||||
if (unzGetCurrentFileInfo64(zipFile, &fileInfo, filenameBuffer, sizeof(filenameBuffer),
|
||||
nullptr, 0, nullptr, 0) != UNZ_OK) {
|
||||
result = unzGoToNextFile(zipFile);
|
||||
continue;
|
||||
}
|
||||
|
||||
filename = filenameBuffer;
|
||||
|
||||
// Quick filename validation
|
||||
if (!filename || filename[0] == '\0') {
|
||||
result = unzGoToNextFile(zipFile);
|
||||
continue;
|
||||
}
|
||||
|
||||
nameLen = strlen(filename);
|
||||
if (nameLen > 0 && filename[nameLen - 1] == '/') { // Skip directories
|
||||
result = unzGoToNextFile(zipFile);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Build extraction path - reuse allocated strings
|
||||
fileName.assign(filename, nameLen);
|
||||
//extractedFilePath.clear();
|
||||
extractedFilePath = destination;
|
||||
extractedFilePath += fileName;
|
||||
|
||||
// Optimized character cleaning - only if needed
|
||||
invalid_pos = extractedFilePath.find_first_of(":*?\"<>|");
|
||||
if (invalid_pos != std::string::npos) {
|
||||
start_pos = std::min(extractedFilePath.find(ROOT_PATH) + 5, extractedFilePath.size());
|
||||
auto it = extractedFilePath.begin() + start_pos;
|
||||
extractedFilePath.erase(std::remove_if(it, extractedFilePath.end(), [](char c) {
|
||||
return c == ':' || c == '*' || c == '?' || c == '\"' || c == '<' || c == '>' || c == '|';
|
||||
}), extractedFilePath.end());
|
||||
}
|
||||
|
||||
// CHECK FOR PROTECTED FILES AND ADD .ultra EXTENSION IF NEEDED
|
||||
if (PROTECTED_FILES.find(extractedFilePath) != PROTECTED_FILES.end()) {
|
||||
extractedFilePath += ".ultra";
|
||||
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Protected file detected, renaming to: " + extractedFilePath);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Open the current file in the ZIP
|
||||
if (unzOpenCurrentFile(zipFile) != UNZ_OK) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Could not open file in ZIP: " + fileName);
|
||||
#endif
|
||||
result = unzGoToNextFile(zipFile);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create directory if needed
|
||||
lastSlashPos = extractedFilePath.find_last_of('/');
|
||||
if (lastSlashPos != std::string::npos) {
|
||||
directoryPath.assign(extractedFilePath, 0, lastSlashPos + 1);
|
||||
createDirectory(directoryPath);
|
||||
}
|
||||
|
||||
// Open output file
|
||||
if (!outputFile.open(extractedFilePath)) {
|
||||
unzCloseCurrentFile(zipFile);
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Error creating file: " + extractedFilePath);
|
||||
#endif
|
||||
result = unzGoToNextFile(zipFile);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract file data in chunks
|
||||
extractSuccess = true;
|
||||
fileBytesProcessed = 0;
|
||||
|
||||
|
||||
while ((bytesRead = unzReadCurrentFile(zipFile, buffer.get(), bufferSize)) > 0) {
|
||||
if (abortUnzip.load(std::memory_order_relaxed)) {
|
||||
extractSuccess = false;
|
||||
break; // RAII will handle cleanup
|
||||
}
|
||||
|
||||
// Write data to file
|
||||
if (outputFile.write(buffer.get(), bytesRead) != static_cast<size_t>(bytesRead)) {
|
||||
extractSuccess = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// Update progress tracking
|
||||
fileBytesProcessed += bytesRead;
|
||||
totalBytesProcessed += bytesRead;
|
||||
|
||||
// FIXED: Allow progress to reach 100% naturally during processing
|
||||
if (totalUncompressedSize > 0) {
|
||||
newProgress = static_cast<int>((totalBytesProcessed * 100) / totalUncompressedSize);
|
||||
if (newProgress > currentProgress && newProgress <= 100) {
|
||||
currentProgress = newProgress;
|
||||
unzipPercentage.store(currentProgress, std::memory_order_release);
|
||||
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
// Only log at 10% intervals to avoid spam
|
||||
if (currentProgress % 10 == 0) {
|
||||
if (!disableLogging) {
|
||||
logMessage("Progress: " + std::to_string(currentProgress) + "% (" +
|
||||
std::to_string(totalBytesProcessed) + "/" +
|
||||
std::to_string(totalUncompressedSize) + " bytes)");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CRITICAL FIX: Handle 0-byte files that don't enter the while loop
|
||||
if (bytesRead == 0 && fileBytesProcessed == 0 && extractSuccess) {
|
||||
// This is a 0-byte file - update progress by 1 byte equivalent
|
||||
totalBytesProcessed += 1;
|
||||
|
||||
// Update progress for 0-byte files
|
||||
if (totalUncompressedSize > 0) {
|
||||
newProgress = static_cast<int>((totalBytesProcessed * 100) / totalUncompressedSize);
|
||||
if (newProgress > currentProgress && newProgress <= 100) {
|
||||
currentProgress = newProgress;
|
||||
unzipPercentage.store(currentProgress, std::memory_order_release);
|
||||
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (currentProgress % 10 == 0) {
|
||||
if (!disableLogging)
|
||||
logMessage("Progress: " + std::to_string(currentProgress) + "% (0-byte file processed)");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for read errors
|
||||
if (bytesRead < 0) {
|
||||
extractSuccess = false;
|
||||
}
|
||||
|
||||
// Close current file handles
|
||||
outputFile.close();
|
||||
unzCloseCurrentFile(zipFile);
|
||||
|
||||
if (!extractSuccess) {
|
||||
deleteFileOrDirectory(extractedFilePath);
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Failed to extract: " + fileName);
|
||||
#endif
|
||||
|
||||
if (abortUnzip.load(std::memory_order_relaxed)) {
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
filesProcessed++;
|
||||
}
|
||||
|
||||
// Move to next file
|
||||
result = unzGoToNextFile(zipFile);
|
||||
}
|
||||
|
||||
// Check final abort state
|
||||
if (abortUnzip.load(std::memory_order_relaxed)) {
|
||||
unzipPercentage.store(-1, std::memory_order_release);
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Extraction aborted by user");
|
||||
#endif
|
||||
abortUnzip.store(false, std::memory_order_release);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (success && filesProcessed > 0) {
|
||||
abortUnzip.store(false, std::memory_order_release);
|
||||
unzipPercentage.store(100, std::memory_order_release);
|
||||
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging) {
|
||||
logMessage("Extraction completed: " + std::to_string(filesProcessed) + " files, " +
|
||||
std::to_string(totalBytesProcessed) + " bytes");
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
} else {
|
||||
abortUnzip.store(false, std::memory_order_release);
|
||||
unzipPercentage.store(-1, std::memory_order_release);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,506 @@
|
||||
/********************************************************************************
|
||||
* File: get_funcs.cpp
|
||||
* Author: ppkantorski
|
||||
* Description:
|
||||
* This source file provides the implementations of functions declared in
|
||||
* get_funcs.hpp. These functions are responsible for retrieving and handling
|
||||
* data from the file system and JSON files, including parsing overlay module
|
||||
* information, reading file contents, and accessing structured data used
|
||||
* in the Ultrahand Overlay project.
|
||||
*
|
||||
* For the latest updates and contributions, visit the project's GitHub repository.
|
||||
* (GitHub Repository: https://github.com/ppkantorski/Ultrahand-Overlay)
|
||||
*
|
||||
* Note: Please be aware that this notice cannot be altered or removed. It is a part
|
||||
* of the project's documentation and must remain intact.
|
||||
*
|
||||
* Licensed under both GPLv2 and CC-BY-4.0
|
||||
* Copyright (c) 2024 ppkantorski
|
||||
********************************************************************************/
|
||||
|
||||
#include "get_funcs.hpp"
|
||||
|
||||
|
||||
namespace ult {
|
||||
|
||||
/**
|
||||
* @brief Reads the contents of a file and returns it as a string, normalizing line endings.
|
||||
*
|
||||
* @param filePath The path to the file to be read.
|
||||
* @return The content of the file as a string with line endings normalized to '\n'.
|
||||
*/
|
||||
//std::string getFileContents(const std::string& filePath) {
|
||||
// #if !USING_FSTREAM_DIRECTIVE
|
||||
// FILE* file = fopen(filePath.c_str(), "rb");
|
||||
// if (!file) {
|
||||
// #if USING_LOGGING_DIRECTIVE
|
||||
// logMessage("Failed to open file: " + filePath);
|
||||
// #endif
|
||||
// return "";
|
||||
// }
|
||||
//
|
||||
// // Determine the file size
|
||||
// fseek(file, 0, SEEK_END);
|
||||
// long size = ftell(file);
|
||||
// if (size <= 0) {
|
||||
// fclose(file);
|
||||
// return "";
|
||||
// }
|
||||
// fseek(file, 0, SEEK_SET);
|
||||
//
|
||||
// // Read the entire file into a string
|
||||
// std::string content(size, '\0');
|
||||
// if (fread(&content[0], 1, size, file) != static_cast<size_t>(size)) {
|
||||
// #if USING_LOGGING_DIRECTIVE
|
||||
// logMessage("Failed to read file: " + filePath);
|
||||
// #endif
|
||||
// fclose(file);
|
||||
// return "";
|
||||
// }
|
||||
//
|
||||
// fclose(file);
|
||||
//
|
||||
// #else
|
||||
// std::ifstream file(filePath, std::ios::binary);
|
||||
// if (!file) {
|
||||
// #if USING_LOGGING_DIRECTIVE
|
||||
// logMessage("Failed to open file: " + filePath);
|
||||
// #endif
|
||||
// return "";
|
||||
// }
|
||||
//
|
||||
// // Determine the file size
|
||||
// file.seekg(0, std::ios::end);
|
||||
// std::streamsize size = file.tellg();
|
||||
// if (size <= 0) {
|
||||
// return "";
|
||||
// }
|
||||
// file.seekg(0, std::ios::beg);
|
||||
//
|
||||
// // Read the entire file into a string
|
||||
// std::string content(size, '\0');
|
||||
// if (!file.read(&content[0], size)) {
|
||||
// #if USING_LOGGING_DIRECTIVE
|
||||
// logMessage("Failed to read file: " + filePath);
|
||||
// #endif
|
||||
// return "";
|
||||
// }
|
||||
// #endif
|
||||
//
|
||||
// // Erase any carriage return characters (normalize line endings)
|
||||
// content.erase(std::remove(content.begin(), content.end(), '\r'), content.end());
|
||||
// return content;
|
||||
//}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Concatenates the provided directory and file names to form a destination path.
|
||||
*
|
||||
* @param destinationDir The directory where the file should be placed.
|
||||
* @param fileName The name of the file.
|
||||
* @return The destination path as a string.
|
||||
*/
|
||||
std::string getDestinationPath(const std::string& destinationDir,
|
||||
const std::string& fileName)
|
||||
{
|
||||
// e.g. "foo/bar" + "/" + "baz.txt" → "foo/bar/baz.txt", but if destinationDir ended in '/',
|
||||
// you’d get "foo/bar//baz.txt" → collapse again:
|
||||
std::string combined = destinationDir + "/" + fileName;
|
||||
preprocessPath(combined);
|
||||
return combined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Extracts the value part from a string line containing a key-value pair.
|
||||
*
|
||||
* @param line The string line containing a key-value pair (e.g., "key=value").
|
||||
* @return The extracted value as a string. If no value is found, an empty string is returned.
|
||||
*/
|
||||
std::string getValueFromLine(const std::string& line) {
|
||||
const size_t equalsPos = line.rfind('=');
|
||||
if (equalsPos == std::string::npos || equalsPos + 1 >= line.size()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// OPTIMIZATION: Find trim boundaries directly - no temporary string
|
||||
size_t start = equalsPos + 1;
|
||||
size_t end = line.size() - 1;
|
||||
|
||||
// Skip leading whitespace
|
||||
while (start <= end && (line[start] == ' ' || line[start] == '\t' ||
|
||||
line[start] == '\n' || line[start] == '\r' ||
|
||||
line[start] == '\f' || line[start] == '\v')) {
|
||||
++start;
|
||||
}
|
||||
|
||||
// Skip trailing whitespace
|
||||
while (end >= start && (line[end] == ' ' || line[end] == '\t' ||
|
||||
line[end] == '\n' || line[end] == '\r' ||
|
||||
line[end] == '\f' || line[end] == '\v')) {
|
||||
--end;
|
||||
}
|
||||
|
||||
// Return trimmed substring directly
|
||||
return (start <= end) ? line.substr(start, end - start + 1) : "";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Extracts the name from a file path, including handling directories.
|
||||
*
|
||||
* @param path The file path from which to extract the name.
|
||||
* @return The extracted name as a string. If the path indicates a directory, it extracts the last directory name.
|
||||
* If the path is empty or no name is found, an empty string is returned.
|
||||
*/
|
||||
std::string getNameFromPath(const std::string& path) {
|
||||
const size_t lastNonSlash = path.find_last_not_of('/');
|
||||
if (lastNonSlash == std::string::npos) {
|
||||
return ""; // All slashes, or empty string effectively
|
||||
}
|
||||
|
||||
const size_t lastSlash = path.find_last_of('/', lastNonSlash);
|
||||
if (lastSlash == std::string::npos) {
|
||||
return path.substr(0, lastNonSlash + 1); // No slashes, the entire path is a filename
|
||||
}
|
||||
|
||||
return path.substr(lastSlash + 1, lastNonSlash - lastSlash); // Standard case, efficiently handled
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Extracts the file name from a full file path.
|
||||
*
|
||||
* This function takes a filesystem path and returns only the file name,
|
||||
* stripping away any directory paths that precede it.
|
||||
*
|
||||
* @param path The full path to the file.
|
||||
* @return The file name extracted from the full path.
|
||||
*/
|
||||
std::string getFileName(const std::string& path) {
|
||||
const size_t pos = path.find_last_of('/');
|
||||
return (pos != std::string::npos) ? path.substr(pos + 1) : "";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Extracts the name of the parent directory from a given file path at a specified level.
|
||||
*
|
||||
* @param path The file path from which to extract the parent directory name.
|
||||
* @param level The level of the parent directory to extract (0 for immediate parent, 1 for grandparent, and so on).
|
||||
* @return The name of the parent directory at the specified level.
|
||||
*/
|
||||
std::string getParentDirNameFromPath(const std::string& path, size_t level) {
|
||||
if (path.empty()) return "";
|
||||
|
||||
// Start from the end of the string and move backwards to find the slashes
|
||||
size_t endPos = path.find_last_not_of('/');
|
||||
if (endPos == std::string::npos) return "";
|
||||
|
||||
size_t pos = path.rfind('/', endPos);
|
||||
if (pos == std::string::npos || pos == 0) return "";
|
||||
|
||||
// Navigate up the specified number of levels
|
||||
while (level-- > 0 && pos != std::string::npos) {
|
||||
endPos = pos - 1;
|
||||
pos = path.rfind('/', endPos);
|
||||
if (pos == std::string::npos || pos == 0) return "";
|
||||
}
|
||||
|
||||
size_t start = path.rfind('/', pos - 1);
|
||||
if (start == std::string::npos) start = 0;
|
||||
else start += 1;
|
||||
|
||||
// OPTIMIZATION 1: Use find_first_of instead of manual loop - much faster
|
||||
const bool hasWhitespace = (path.find_first_of(" \t\n\r\f\v", start, pos - start) != std::string::npos);
|
||||
|
||||
if (hasWhitespace) {
|
||||
// OPTIMIZATION 2: Pre-allocate exact size and build efficiently
|
||||
const size_t dirNameLen = pos - start;
|
||||
std::string result;
|
||||
result.reserve(dirNameLen + 2); // +2 for quotes
|
||||
|
||||
result = '"';
|
||||
result.append(path, start, dirNameLen);
|
||||
result += '"';
|
||||
return result;
|
||||
} else {
|
||||
return path.substr(start, pos - start);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Extracts the parent directory path from a given file path.
|
||||
*
|
||||
* @param path The file path from which to extract the parent directory path.
|
||||
* @return The parent directory path.
|
||||
*/
|
||||
std::string getParentDirFromPath(const std::string& path) {
|
||||
const size_t lastSlash = path.find_last_of('/');
|
||||
if (lastSlash != std::string::npos) {
|
||||
return path.substr(0, lastSlash + 1);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Gets a list of subdirectories in a directory.
|
||||
*
|
||||
* @param directoryPath The path of the directory to search.
|
||||
* @return A vector of strings containing the names of subdirectories.
|
||||
*/
|
||||
std::vector<std::string> getSubdirectories(const std::string& directoryPath) {
|
||||
std::vector<std::string> subdirectories;
|
||||
std::unique_ptr<DIR, DirCloser> dir(opendir(directoryPath.c_str()));
|
||||
|
||||
if (!dir) return subdirectories;
|
||||
|
||||
struct dirent* entry;
|
||||
while ((entry = readdir(dir.get())) != nullptr) {
|
||||
const std::string entryName = entry->d_name;
|
||||
|
||||
// Skip . and ..
|
||||
if (entryName == "." || entryName == "..") continue;
|
||||
|
||||
const std::string fullPath = directoryPath + "/" + entryName;
|
||||
|
||||
if (isDirectory(entry, fullPath)) {
|
||||
subdirectories.emplace_back(entryName);
|
||||
}
|
||||
}
|
||||
|
||||
return subdirectories;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if a directory entry is a directory (no caching).
|
||||
* Fast path for known types, stat() only when necessary.
|
||||
*/
|
||||
inline bool isDirectory(struct dirent* entry, const std::string& path) {
|
||||
// Fast path - most filesystems populate d_type correctly
|
||||
if (entry->d_type == DT_DIR) {
|
||||
return true;
|
||||
} else if (entry->d_type != DT_UNKNOWN) {
|
||||
return false; // DT_REG, DT_LNK, etc.
|
||||
}
|
||||
|
||||
// Only stat when d_type is unknown (rare on modern filesystems)
|
||||
struct stat entryStat;
|
||||
return (stat(path.c_str(), &entryStat) == 0) && S_ISDIR(entryStat.st_mode);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Iteratively retrieves a list of files from a directory.
|
||||
*
|
||||
* @param directoryPath The path of the directory to search.
|
||||
* @return A vector of strings containing the paths of the files.
|
||||
*/
|
||||
std::vector<std::string> getFilesListFromDirectory(const std::string& directoryPath) {
|
||||
std::vector<std::string> fileList;
|
||||
std::vector<std::string> dirsToProcess;
|
||||
|
||||
// Initialize with the starting directory
|
||||
dirsToProcess.emplace_back(directoryPath);
|
||||
|
||||
// Pre-allocate string buffer to avoid repeated allocations
|
||||
std::string fullPath;
|
||||
std::string currentDir;
|
||||
|
||||
size_t dirIndex = 0;
|
||||
while (dirIndex < dirsToProcess.size()) {
|
||||
currentDir = std::move(dirsToProcess[dirIndex]);
|
||||
dirsToProcess[dirIndex].shrink_to_fit();
|
||||
dirIndex++;
|
||||
|
||||
std::unique_ptr<DIR, DirCloser> dir(opendir(currentDir.c_str()));
|
||||
if (!dir) continue;
|
||||
|
||||
// Cache directory path info
|
||||
const bool needsSlash = currentDir.back() != '/';
|
||||
|
||||
struct dirent* entry;
|
||||
while ((entry = readdir(dir.get())) != nullptr) {
|
||||
const char* entryName = entry->d_name;
|
||||
|
||||
// Direct comparison without string creation
|
||||
if (entryName[0] == '.' && (entryName[1] == '\0' || (entryName[1] == '.' && entryName[2] == '\0'))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// More efficient path building
|
||||
fullPath.clear();
|
||||
fullPath.assign(currentDir);
|
||||
if (needsSlash) fullPath += '/';
|
||||
fullPath += entryName;
|
||||
|
||||
if (entry->d_type == DT_REG) {
|
||||
// Definitely a regular file
|
||||
fileList.emplace_back(fullPath);
|
||||
} else if (isDirectory(entry, fullPath)) {
|
||||
// Add directory to processing queue
|
||||
dirsToProcess.emplace_back(fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fileList;
|
||||
}
|
||||
|
||||
|
||||
// Iterative function to handle wildcard directories and file patterns
|
||||
void handleDirectory(const std::string& basePath,
|
||||
const std::vector<std::string>& parts,
|
||||
size_t partIndex,
|
||||
std::vector<std::string>& results,
|
||||
bool directoryOnly,
|
||||
size_t maxLines) {
|
||||
|
||||
std::vector<std::pair<std::string, size_t>> stack;
|
||||
stack.emplace_back(basePath, partIndex);
|
||||
|
||||
// Pre-declare strings to avoid repeated allocations
|
||||
std::string fullPath;
|
||||
std::string result;
|
||||
std::string currentPath;
|
||||
//fullPath.reserve(512);
|
||||
//result.reserve(512);
|
||||
//currentPath.reserve(512);
|
||||
|
||||
struct stat st;
|
||||
|
||||
bool isDir;
|
||||
//std::string pathRef;
|
||||
size_t currentPartIndex;
|
||||
|
||||
while (!stack.empty()) {
|
||||
if (maxLines > 0 && results.size() >= maxLines) return;
|
||||
|
||||
std::tie(currentPath, currentPartIndex) = stack.back();
|
||||
stack.pop_back();
|
||||
|
||||
// Copy once to avoid repeated access
|
||||
//currentPath = pathRef;
|
||||
|
||||
if (currentPartIndex >= parts.size()) continue;
|
||||
|
||||
DIR* dirPtr = opendir(currentPath.c_str());
|
||||
if (!dirPtr) continue;
|
||||
std::unique_ptr<DIR, DirCloser> dir(dirPtr);
|
||||
|
||||
const std::string& pattern = parts[currentPartIndex];
|
||||
const bool isLastPart = (currentPartIndex == parts.size() - 1);
|
||||
const bool needsSlash = currentPath.back() != '/';
|
||||
|
||||
// Pre-calculate base path for efficiency
|
||||
//const size_t basePathLen = currentPath.length();
|
||||
|
||||
struct dirent* entry;
|
||||
while ((entry = readdir(dir.get())) != nullptr) {
|
||||
if (maxLines > 0 && results.size() >= maxLines) return;
|
||||
|
||||
const char* name = entry->d_name;
|
||||
if (name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) continue;
|
||||
|
||||
if (fnmatch(pattern.c_str(), name, FNM_NOESCAPE) != 0) continue;
|
||||
|
||||
if (entry->d_type != DT_UNKNOWN) {
|
||||
isDir = (entry->d_type == DT_DIR);
|
||||
} else {
|
||||
// More efficient path building for stat check
|
||||
fullPath.clear();
|
||||
fullPath.assign(currentPath);
|
||||
if (needsSlash) fullPath += '/';
|
||||
fullPath += name;
|
||||
isDir = (stat(fullPath.c_str(), &st) == 0) && S_ISDIR(st.st_mode);
|
||||
}
|
||||
|
||||
if (isLastPart) {
|
||||
if (!directoryOnly || isDir) {
|
||||
// More efficient result building
|
||||
result.clear();
|
||||
result.assign(currentPath);
|
||||
if (needsSlash) result += '/';
|
||||
result += name;
|
||||
if (isDir) result += '/';
|
||||
results.emplace_back(std::move(result));
|
||||
if (maxLines > 0 && results.size() >= maxLines) return;
|
||||
}
|
||||
} else if (isDir) {
|
||||
// More efficient path building for stack
|
||||
fullPath.clear();
|
||||
fullPath.assign(currentPath);
|
||||
if (needsSlash) fullPath += '/';
|
||||
fullPath += name;
|
||||
stack.emplace_back(std::move(fullPath), currentPartIndex + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets a list of files and folders based on a wildcard pattern.
|
||||
*
|
||||
* This function searches for files and folders in a directory that match the
|
||||
* specified wildcard pattern.
|
||||
*
|
||||
* @param pathPattern The wildcard pattern to match files and folders.
|
||||
* @return A vector of strings containing the paths of matching files and folders.
|
||||
*/
|
||||
std::vector<std::string> getFilesListByWildcards(const std::string& pathPattern, size_t maxLines) {
|
||||
std::vector<std::string> results;
|
||||
|
||||
if (pathPattern.empty()) return results;
|
||||
|
||||
// Disallow any `/**/` or ending with `/**`
|
||||
if (pathPattern.find("**") != std::string::npos || pathPattern.find("*null") != std::string::npos || pathPattern.find("null*") != std::string::npos) {
|
||||
return results; // Exclude invalid patterns
|
||||
}
|
||||
|
||||
const bool directoryOnly = pathPattern.back() == '/';
|
||||
const size_t prefixEnd = pathPattern.find(":/");
|
||||
|
||||
if (prefixEnd == std::string::npos) return results;
|
||||
|
||||
const std::string basePath = pathPattern.substr(0, prefixEnd + 2);
|
||||
std::vector<std::string> parts;
|
||||
|
||||
size_t start = prefixEnd + 2;
|
||||
size_t pos = start;
|
||||
const size_t pathLen = pathPattern.length();
|
||||
|
||||
while (pos <= pathLen) {
|
||||
if (pos == pathLen || pathPattern[pos] == '/') {
|
||||
if (pos > start) {
|
||||
parts.emplace_back(pathPattern.data() + start, pos - start);
|
||||
}
|
||||
start = pos + 1;
|
||||
}
|
||||
++pos;
|
||||
}
|
||||
|
||||
if (start < pathLen && !directoryOnly) {
|
||||
parts.emplace_back(pathPattern.data() + start, pathLen - start);
|
||||
}
|
||||
|
||||
// Extra: check parsed parts to disallow "**"
|
||||
for (size_t i = 0; i + 1 < parts.size(); ++i) {
|
||||
if (parts[i] == "**" && parts[i + 1] == "**") {
|
||||
return results; // invalid, exclude
|
||||
}
|
||||
}
|
||||
|
||||
if (!parts.empty()) {
|
||||
handleDirectory(basePath, parts, 0, results, directoryOnly, maxLines);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
/********************************************************************************
|
||||
* File: global_vars.cpp
|
||||
* Author: ppkantorski
|
||||
* Description:
|
||||
* This source file provides the definitions of global constants and paths used
|
||||
* throughout the Ultrahand Overlay project. These constants are essential for
|
||||
* file management and configuration settings within the application.
|
||||
*
|
||||
* For the latest updates and contributions, visit the project's GitHub repository:
|
||||
* GitHub Repository: https://github.com/ppkantorski/Ultrahand-Overlay
|
||||
*
|
||||
* Note: This notice is integral to the project's documentation and must not be
|
||||
* altered or removed.
|
||||
*
|
||||
* Licensed under both GPLv2 and CC-BY-4.0
|
||||
* Copyright (c) 2024 ppkantorski
|
||||
********************************************************************************/
|
||||
|
||||
#include "global_vars.hpp"
|
||||
|
||||
namespace ult {
|
||||
|
||||
// Base paths
|
||||
const std::string ROOT_PATH = "sdmc:/";
|
||||
const std::string BASE_CONFIG_PATH = ROOT_PATH + "config/ultrahand/";
|
||||
const std::string TESLA_CONFIG_PATH = ROOT_PATH + "config/tesla/";
|
||||
const std::string SWITCH_PATH = ROOT_PATH + "switch/";
|
||||
|
||||
// Filenames
|
||||
CONSTEXPR_STRING std::string CONFIG_FILENAME = "config.ini";
|
||||
const std::string BOOT_PACKAGE_FILENAME = "boot_package.ini";
|
||||
const std::string EXIT_PACKAGE_FILENAME = "exit_package.ini";
|
||||
const std::string PACKAGE_FILENAME = "package.ini";
|
||||
const std::string THEME_FILENAME = "theme.ini";
|
||||
const std::string WALLPAPER_FILENAME = "wallpaper.rgba";
|
||||
const std::string FUSE_FILENAME = "fuse.ini";
|
||||
const std::string OVERLAYS_INI_FILENAME = "overlays.ini";
|
||||
const std::string PACKAGES_INI_FILENAME = "packages.ini";
|
||||
const std::string NOTIFICATIONS_FLAG_FILENAME = "NOTIFICATIONS.flag";
|
||||
|
||||
// Project names
|
||||
CONSTEXPR_STRING std::string ULTRAHAND_PROJECT_NAME = "ultrahand";
|
||||
CONSTEXPR_STRING std::string CAPITAL_ULTRAHAND_PROJECT_NAME = "Ultrahand";
|
||||
CONSTEXPR_STRING std::string SPLIT_PROJECT_NAME_1 = "Ultra";
|
||||
CONSTEXPR_STRING std::string SPLIT_PROJECT_NAME_2 = "hand";
|
||||
|
||||
// Paths
|
||||
const std::string SETTINGS_PATH = BASE_CONFIG_PATH;
|
||||
const std::string ULTRAHAND_CONFIG_INI_PATH = BASE_CONFIG_PATH + CONFIG_FILENAME;
|
||||
const std::string TESLA_CONFIG_INI_PATH = TESLA_CONFIG_PATH + CONFIG_FILENAME;
|
||||
const std::string LANG_PATH = BASE_CONFIG_PATH + "lang/";
|
||||
const std::string THEMES_PATH = BASE_CONFIG_PATH + "themes/";
|
||||
const std::string WALLPAPERS_PATH = BASE_CONFIG_PATH + "wallpapers/";
|
||||
const std::string FLAGS_PATH = BASE_CONFIG_PATH + "flags/";
|
||||
const std::string NOTIFICATIONS_PATH = BASE_CONFIG_PATH + "notifications/";
|
||||
const std::string PAYLOADS_PATH = BASE_CONFIG_PATH + "payloads/";
|
||||
const std::string HB_APPSTORE_JSON = SWITCH_PATH + ".appstore/.get/packages/UltrahandOverlay/info.json";
|
||||
std::string THEME_CONFIG_INI_PATH = BASE_CONFIG_PATH + THEME_FILENAME;
|
||||
std::string WALLPAPER_PATH = BASE_CONFIG_PATH + WALLPAPER_FILENAME;
|
||||
const std::string DOWNLOADS_PATH = BASE_CONFIG_PATH + "downloads/";
|
||||
const std::string EXPANSION_PATH = BASE_CONFIG_PATH + "expansion/";
|
||||
const std::string FUSE_DATA_INI_PATH = BASE_CONFIG_PATH + FUSE_FILENAME;
|
||||
const std::string PACKAGE_PATH = SWITCH_PATH + ".packages/";
|
||||
const std::string OVERLAY_PATH = SWITCH_PATH + ".overlays/";
|
||||
const std::string OVERLAYS_INI_FILEPATH = BASE_CONFIG_PATH + OVERLAYS_INI_FILENAME;
|
||||
const std::string PACKAGES_INI_FILEPATH = BASE_CONFIG_PATH + PACKAGES_INI_FILENAME;
|
||||
const std::string NOTIFICATIONS_FLAG_FILEPATH = FLAGS_PATH + NOTIFICATIONS_FLAG_FILENAME;
|
||||
|
||||
// Protected files
|
||||
const std::set<std::string> PROTECTED_FILES = {
|
||||
ROOT_PATH + "atmosphere/package3",
|
||||
ROOT_PATH + "atmosphere/stratosphere.romfs"
|
||||
};
|
||||
|
||||
// GitHub URLs
|
||||
const std::string GITHUB_BASE_URL = "https://github.com/ppkantorski/";
|
||||
const std::string GITHUB_RAW_BASE_URL = "https://raw.githubusercontent.com/ppkantorski/";
|
||||
const std::string ULTRAHAND_REPO_URL = GITHUB_BASE_URL + "Ultrahand-Overlay/";
|
||||
const std::string INCLUDED_THEME_FOLDER_URL = GITHUB_RAW_BASE_URL + "Ultrahand-Overlay/main/themes/";
|
||||
const std::string LATEST_RELEASE_INFO_URL = GITHUB_RAW_BASE_URL + "Ultrahand-Overlay/main/RELEASE.ini";
|
||||
const std::string NX_OVLLOADER_ZIP_URL = GITHUB_BASE_URL + "nx-ovlloader/releases/latest/download/nx-ovlloader.zip";
|
||||
const std::string NX_OVLLOADER_PLUS_ZIP_URL = GITHUB_BASE_URL + "nx-ovlloader/releases/latest/download/nx-ovlloader+.zip";
|
||||
const std::string OLD_NX_OVLLOADER_ZIP_URL = GITHUB_BASE_URL + "nx-ovlloader/releases/download/v1.0.8/nx-ovlloader.zip";
|
||||
const std::string OLD_NX_OVLLOADER_PLUS_ZIP_URL = GITHUB_BASE_URL + "nx-ovlloader/releases/download/v1.0.8/nx-ovlloader+.zip";
|
||||
const std::string UPDATER_PAYLOAD_URL = GITHUB_RAW_BASE_URL + "Ultrahand-Overlay/main/payloads/ultrahand_updater.bin";
|
||||
|
||||
// Launch options
|
||||
const std::string LAUNCH_ARGS_STR = "launch_args";
|
||||
const std::string USE_LAUNCH_ARGS_STR = "use_launch_args";
|
||||
const std::string USE_QUICK_LAUNCH_STR = "use_quick_launch";
|
||||
const std::string USE_BOOT_PACKAGE_STR = "use_boot_package";
|
||||
const std::string USE_EXIT_PACKAGE_STR = "use_exit_package";
|
||||
const std::string USE_LOGGING_STR = "use_logging";
|
||||
|
||||
// Combos
|
||||
CONSTEXPR_STRING std::string TESLA_COMBO_STR = "L+DDOWN+RS";
|
||||
CONSTEXPR_STRING std::string ULTRAHAND_COMBO_STR = "ZL+ZR+DDOWN";
|
||||
|
||||
// System / mode strings
|
||||
CONSTEXPR_STRING std::string FUSE_STR = "fuse";
|
||||
CONSTEXPR_STRING std::string TESLA_STR = "tesla";
|
||||
CONSTEXPR_STRING std::string ERISTA_STR = "erista";
|
||||
CONSTEXPR_STRING std::string MARIKO_STR = "mariko";
|
||||
CONSTEXPR_STRING std::string KEY_COMBO_STR = "key_combo";
|
||||
CONSTEXPR_STRING std::string DEFAULT_LANG_STR = "default_lang";
|
||||
|
||||
// Generic strings
|
||||
CONSTEXPR_STRING std::string LIST_STR = "list";
|
||||
CONSTEXPR_STRING std::string LIST_FILE_STR = "list_file";
|
||||
CONSTEXPR_STRING std::string JSON_STR = "json";
|
||||
CONSTEXPR_STRING std::string JSON_FILE_STR = "json_file";
|
||||
CONSTEXPR_STRING std::string INI_FILE_STR = "ini_file";
|
||||
CONSTEXPR_STRING std::string HEX_FILE_STR = "hex_file";
|
||||
CONSTEXPR_STRING std::string PACKAGE_STR = "package";
|
||||
CONSTEXPR_STRING std::string PACKAGES_STR = "packages";
|
||||
CONSTEXPR_STRING std::string OVERLAY_STR = "overlay";
|
||||
CONSTEXPR_STRING std::string OVERLAYS_STR = "overlays";
|
||||
const std::string IN_OVERLAY_STR = "in_overlay";
|
||||
const std::string IN_HIDDEN_OVERLAY_STR = "in_hidden_overlay";
|
||||
const std::string IN_HIDDEN_PACKAGE_STR = "in_hidden_package";
|
||||
CONSTEXPR_STRING std::string FILE_STR = "file";
|
||||
CONSTEXPR_STRING std::string SYSTEM_STR = "system";
|
||||
CONSTEXPR_STRING std::string MODE_STR = "mode";
|
||||
CONSTEXPR_STRING std::string GROUPING_STR = "grouping";
|
||||
CONSTEXPR_STRING std::string FOOTER_STR = "footer";
|
||||
CONSTEXPR_STRING std::string TOGGLE_STR = "toggle";
|
||||
CONSTEXPR_STRING std::string LEFT_STR = "left";
|
||||
CONSTEXPR_STRING std::string RIGHT_STR = "right";
|
||||
CONSTEXPR_STRING std::string CENTER_STR = "center";
|
||||
CONSTEXPR_STRING std::string HIDE_STR = "hide";
|
||||
CONSTEXPR_STRING std::string STAR_STR = "star";
|
||||
CONSTEXPR_STRING std::string PRIORITY_STR = "priority";
|
||||
CONSTEXPR_STRING std::string ON_STR = "on";
|
||||
CONSTEXPR_STRING std::string OFF_STR = "off";
|
||||
CONSTEXPR_STRING std::string CAPITAL_ON_STR = "On";
|
||||
CONSTEXPR_STRING std::string CAPITAL_OFF_STR = "Off";
|
||||
CONSTEXPR_STRING std::string TRUE_STR = "true";
|
||||
CONSTEXPR_STRING std::string FALSE_STR = "false";
|
||||
CONSTEXPR_STRING std::string GLOBAL_STR = "global";
|
||||
CONSTEXPR_STRING std::string DEFAULT_STR = "default";
|
||||
CONSTEXPR_STRING std::string SLOT_STR = "slot";
|
||||
CONSTEXPR_STRING std::string OPTION_STR = "option";
|
||||
CONSTEXPR_STRING std::string FORWARDER_STR = "forwarder";
|
||||
CONSTEXPR_STRING std::string TEXT_STR = "text";
|
||||
CONSTEXPR_STRING std::string TABLE_STR = "table";
|
||||
CONSTEXPR_STRING std::string TRACKBAR_STR = "trackbar";
|
||||
CONSTEXPR_STRING std::string STEP_TRACKBAR_STR = "step_trackbar";
|
||||
const std::string NAMED_STEP_TRACKBAR_STR = "named_step_trackbar";
|
||||
CONSTEXPR_STRING std::string NULL_STR = "null";
|
||||
CONSTEXPR_STRING std::string THEME_STR = "theme";
|
||||
CONSTEXPR_STRING std::string NOT_AVAILABLE_STR = "Not available";
|
||||
CONSTEXPR_STRING std::string MEMORY_STR = "memory";
|
||||
|
||||
// Pre-defined symbols
|
||||
CONSTEXPR_STRING std::string OPTION_SYMBOL = "\u22EF";
|
||||
CONSTEXPR_STRING std::string DROPDOWN_SYMBOL = "\uE14A";
|
||||
CONSTEXPR_STRING std::string CHECKMARK_SYMBOL = "\uE14B";
|
||||
CONSTEXPR_STRING std::string CROSSMARK_SYMBOL = "\uE14C";
|
||||
CONSTEXPR_STRING std::string DOWNLOAD_SYMBOL = "\u2193";
|
||||
CONSTEXPR_STRING std::string UNZIP_SYMBOL = "\u2191";
|
||||
CONSTEXPR_STRING std::string COPY_SYMBOL = "\u2192";
|
||||
CONSTEXPR_STRING std::string INPROGRESS_SYMBOL = "\u25CF";
|
||||
CONSTEXPR_STRING std::string STAR_SYMBOL = "\u2605";
|
||||
CONSTEXPR_STRING std::string DIVIDER_SYMBOL = "";
|
||||
|
||||
const std::vector<std::string> THROBBER_SYMBOLS = {"", "", "", "", "", "", "", ""};
|
||||
|
||||
// Atomic variables for progress tracking
|
||||
std::atomic<int> displayPercentage(0); // for interpreter percentage progress
|
||||
|
||||
void resetPercentages() {
|
||||
displayPercentage.store(-1, std::memory_order_release);
|
||||
downloadPercentage.store(-1, std::memory_order_release);
|
||||
unzipPercentage.store(-1, std::memory_order_release);
|
||||
copyPercentage.store(-1, std::memory_order_release);
|
||||
}
|
||||
|
||||
} // namespace ult
|
||||
@@ -0,0 +1,881 @@
|
||||
/********************************************************************************
|
||||
* File: hex_funcs.cpp
|
||||
* Author: ppkantorski
|
||||
* Description:
|
||||
* This source file implements the functions declared in hex_funcs.hpp.
|
||||
* These functions provide support for manipulating hexadecimal data,
|
||||
* including conversions between ASCII and hexadecimal strings,
|
||||
* locating specific hex patterns within files, and editing file contents
|
||||
* at hex offsets.
|
||||
*
|
||||
* For the latest updates and contributions, visit the project's GitHub repository.
|
||||
* (GitHub Repository: https://github.com/ppkantorski/Ultrahand-Overlay)
|
||||
*
|
||||
* Note: Please be aware that this notice cannot be altered or removed. It is a part
|
||||
* of the project's documentation and must remain intact.
|
||||
*
|
||||
* Licensed under both GPLv2 and CC-BY-4.0
|
||||
* Copyright (c) 2024 ppkantorski
|
||||
********************************************************************************/
|
||||
|
||||
#include "hex_funcs.hpp"
|
||||
|
||||
namespace ult {
|
||||
size_t HEX_BUFFER_SIZE = 4096;//65536/4;
|
||||
|
||||
// Thread-safe cache and file operation mutexes
|
||||
std::shared_mutex cacheMutex; // Allows multiple readers, single writer
|
||||
std::mutex fileWriteMutex; // Protects file write operations
|
||||
|
||||
// For improving the speed of hexing consecutively with the same file and asciiPattern.
|
||||
std::unordered_map<std::string, std::string> hexSumCache;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Thread-safe cache management functions
|
||||
*/
|
||||
void clearHexSumCache() {
|
||||
std::lock_guard<std::shared_mutex> writeLock(cacheMutex);
|
||||
//hexSumCache.clear();
|
||||
hexSumCache = {};
|
||||
}
|
||||
|
||||
size_t getHexSumCacheSize() {
|
||||
std::shared_lock<std::shared_mutex> readLock(cacheMutex);
|
||||
return hexSumCache.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Converts an ASCII string to a hexadecimal string.
|
||||
*
|
||||
* This function takes an ASCII string as input and converts it into a hexadecimal string.
|
||||
*
|
||||
* @param asciiStr The ASCII string to convert.
|
||||
* @return The corresponding hexadecimal string.
|
||||
*/
|
||||
|
||||
|
||||
// Function to convert ASCII string to Hex string
|
||||
std::string asciiToHex(const std::string& asciiStr) {
|
||||
std::string hexStr;
|
||||
//hexStr.reserve(asciiStr.length() * 2); // Reserve space for the hexadecimal string
|
||||
|
||||
for (unsigned char c : asciiStr) {
|
||||
hexStr.push_back(hexLookup[c >> 4]); // High nibble
|
||||
hexStr.push_back(hexLookup[c & 0x0F]); // Low nibble
|
||||
}
|
||||
|
||||
return hexStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Converts a decimal string to a fixed-width hexadecimal string.
|
||||
*
|
||||
* @param decimalStr The decimal string to convert.
|
||||
* @param byteGroupSize The number of hex digits to output (must be even for byte alignment).
|
||||
* @return Hex string of exactly 'byteGroupSize' digits, or empty string if value doesn't fit.
|
||||
*/
|
||||
std::string decimalToHex(const std::string& decimalStr, int byteGroupSize) {
|
||||
const int decimalValue = ult::stoi(decimalStr);
|
||||
if (decimalValue < 0 || byteGroupSize <= 0 || (byteGroupSize % 2) != 0) {
|
||||
// Invalid input: negative number, or byteGroupSize <= 0, or odd byteGroupSize
|
||||
return "";
|
||||
}
|
||||
|
||||
// Special case: zero
|
||||
if (decimalValue == 0) {
|
||||
return std::string(byteGroupSize, '0');
|
||||
}
|
||||
|
||||
// Convert decimalValue to hex (uppercase, minimal length)
|
||||
std::string hex;
|
||||
int tempValue = decimalValue;
|
||||
int remainder;
|
||||
char hexChar;
|
||||
while (tempValue > 0) {
|
||||
remainder = tempValue % 16;
|
||||
hexChar = (remainder < 10) ? ('0' + remainder) : ('A' + remainder - 10);
|
||||
hex.insert(hex.begin(), hexChar);
|
||||
tempValue /= 16;
|
||||
}
|
||||
|
||||
// Ensure hex length is even by adding leading zero if needed
|
||||
if (hex.length() % 2 != 0) {
|
||||
hex.insert(hex.begin(), '0');
|
||||
}
|
||||
|
||||
// Minimum size needed to fit hex string
|
||||
const size_t hexLen = hex.length();
|
||||
|
||||
// Adjust minimum byteGroupSize to be at least hexLen
|
||||
size_t minByteGroupSize = std::max(static_cast<size_t>(byteGroupSize), hexLen);
|
||||
|
||||
// If byteGroupSize was too small, adjust to hex length (must be even)
|
||||
if (minByteGroupSize % 2 != 0) {
|
||||
minByteGroupSize++;
|
||||
}
|
||||
|
||||
// If minByteGroupSize is less than hex length, number doesn't fit
|
||||
if (minByteGroupSize < hexLen) {
|
||||
return ""; // can't fit
|
||||
}
|
||||
|
||||
// Pad with leading zeros to match minByteGroupSize
|
||||
if (hexLen < minByteGroupSize) {
|
||||
hex.insert(hex.begin(), minByteGroupSize - hexLen, '0');
|
||||
}
|
||||
|
||||
return hex;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Converts a hexadecimal string to a decimal string.
|
||||
*
|
||||
* This function takes a hexadecimal string as input and converts it into a decimal string.
|
||||
*
|
||||
* @param hexStr The hexadecimal string to convert.
|
||||
* @return The corresponding decimal string.
|
||||
*/
|
||||
std::string hexToDecimal(const std::string& hexStr) {
|
||||
// Convert hexadecimal string to integer
|
||||
int decimalValue = 0;
|
||||
const size_t len = hexStr.length();
|
||||
|
||||
char hexChar;
|
||||
int value;
|
||||
|
||||
// Iterate over each character in the hexadecimal string
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
hexChar = hexStr[i];
|
||||
//int value;
|
||||
|
||||
// Convert hex character to its decimal value
|
||||
if (hexChar >= '0' && hexChar <= '9') {
|
||||
value = hexChar - '0';
|
||||
} else if (hexChar >= 'A' && hexChar <= 'F') {
|
||||
value = 10 + (hexChar - 'A');
|
||||
} else if (hexChar >= 'a' && hexChar <= 'f') {
|
||||
value = 10 + (hexChar - 'a');
|
||||
} else {
|
||||
break;
|
||||
//throw std::invalid_argument("Invalid hexadecimal character");
|
||||
}
|
||||
|
||||
// Update the decimal value
|
||||
decimalValue = decimalValue * 16 + value;
|
||||
}
|
||||
|
||||
// Convert the decimal value to a string
|
||||
return ult::to_string(decimalValue);
|
||||
}
|
||||
|
||||
|
||||
|
||||
std::string hexToReversedHex(const std::string& hexadecimal, int order) {
|
||||
// Reverse the hexadecimal string in groups of order
|
||||
std::string reversedHex;
|
||||
for (int i = hexadecimal.length() - order; i >= 0; i -= order) {
|
||||
reversedHex += hexadecimal.substr(i, order);
|
||||
}
|
||||
|
||||
return reversedHex;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Converts a decimal string to a reversed hexadecimal string.
|
||||
*
|
||||
* This function takes a decimal string as input, converts it into a hexadecimal
|
||||
* string, and reverses the resulting hexadecimal string in groups of byteGroupSize.
|
||||
*
|
||||
* @param decimalStr The decimal string to convert.
|
||||
* @param byteGroupSize The grouping byteGroupSize for reversing the hexadecimal string.
|
||||
* @return The reversed hexadecimal string.
|
||||
*/
|
||||
std::string decimalToReversedHex(const std::string& decimalStr, int byteGroupSize) {
|
||||
//std::string hexadecimal = decimalToHex(decimalStr, byteGroupSize);
|
||||
|
||||
// Reverse the hexadecimal string in groups of byteGroupSize
|
||||
//std::string reversedHex;
|
||||
//for (int i = hexadecimal.length() - byteGroupSize; i >= 0; i -= byteGroupSize) {
|
||||
// reversedHex += hexadecimal.substr(i, byteGroupSize);
|
||||
//}
|
||||
|
||||
return hexToReversedHex(decimalToHex(decimalStr, byteGroupSize));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Finds the offsets of hexadecimal data in a file.
|
||||
*
|
||||
* This function searches for occurrences of hexadecimal data in a binary file
|
||||
* and returns the file offsets where the data is found.
|
||||
*
|
||||
* @param filePath The path to the binary file.
|
||||
* @param hexData The hexadecimal data to search for.
|
||||
* @return A vector of strings containing the file offsets where the data is found.
|
||||
*/
|
||||
std::vector<std::string> findHexDataOffsets(const std::string& filePath, const std::string& hexData) {
|
||||
std::vector<std::string> offsets;
|
||||
|
||||
#if !USING_FSTREAM_DIRECTIVE
|
||||
FILE* file = fopen(filePath.c_str(), "rb");
|
||||
if (!file) {
|
||||
return offsets;
|
||||
}
|
||||
|
||||
fseek(file, 0, SEEK_END);
|
||||
const size_t fileSize = ftell(file);
|
||||
fseek(file, 0, SEEK_SET);
|
||||
|
||||
std::vector<unsigned char> binaryData;
|
||||
if (hexData.length() % 2 != 0) {
|
||||
fclose(file);
|
||||
return offsets;
|
||||
}
|
||||
|
||||
|
||||
const size_t hexLen = hexData.length();
|
||||
binaryData.resize(hexLen / 2);
|
||||
const unsigned char* hexPtr = reinterpret_cast<const unsigned char*>(hexData.c_str());
|
||||
|
||||
// Unrolled hex conversion loop
|
||||
size_t i = 0;
|
||||
for (; i + 4 <= hexLen; i += 4) {
|
||||
binaryData[i/2] = (hexTable[hexPtr[i]] << 4) | hexTable[hexPtr[i + 1]];
|
||||
binaryData[i/2 + 1] = (hexTable[hexPtr[i + 2]] << 4) | hexTable[hexPtr[i + 3]];
|
||||
}
|
||||
// Handle remaining bytes
|
||||
for (; i < hexLen; i += 2) {
|
||||
binaryData[i/2] = (hexTable[hexPtr[i]] << 4) | hexTable[hexPtr[i + 1]];
|
||||
}
|
||||
|
||||
// Optimized search variables
|
||||
const unsigned char* patternPtr = binaryData.data();
|
||||
const size_t patternLen = binaryData.size();
|
||||
const unsigned char firstByte = patternPtr[0];
|
||||
|
||||
std::vector<unsigned char> buffer(HEX_BUFFER_SIZE);
|
||||
size_t bytesRead = 0;
|
||||
size_t offset = 0;
|
||||
|
||||
|
||||
while ((bytesRead = fread(buffer.data(), 1, HEX_BUFFER_SIZE, file)) > 0) {
|
||||
const unsigned char* bufPtr = buffer.data();
|
||||
|
||||
// Optimized search with first-byte filtering and loop unrolling
|
||||
i = 0;
|
||||
const size_t searchEnd = bytesRead;
|
||||
|
||||
// Process 4 bytes at a time for better cache usage
|
||||
for (; i + 4 <= searchEnd; i += 4) {
|
||||
// Check 4 positions at once
|
||||
if (bufPtr[i] == firstByte) {
|
||||
if (offset + i + patternLen <= fileSize &&
|
||||
memcmp(bufPtr + i, patternPtr, patternLen) == 0) {
|
||||
offsets.emplace_back(ult::to_string(offset + i));
|
||||
}
|
||||
}
|
||||
if (bufPtr[i + 1] == firstByte) {
|
||||
if (offset + i + 1 + patternLen <= fileSize &&
|
||||
memcmp(bufPtr + i + 1, patternPtr, patternLen) == 0) {
|
||||
offsets.emplace_back(ult::to_string(offset + i + 1));
|
||||
}
|
||||
}
|
||||
if (bufPtr[i + 2] == firstByte) {
|
||||
if (offset + i + 2 + patternLen <= fileSize &&
|
||||
memcmp(bufPtr + i + 2, patternPtr, patternLen) == 0) {
|
||||
offsets.emplace_back(ult::to_string(offset + i + 2));
|
||||
}
|
||||
}
|
||||
if (bufPtr[i + 3] == firstByte) {
|
||||
if (offset + i + 3 + patternLen <= fileSize &&
|
||||
memcmp(bufPtr + i + 3, patternPtr, patternLen) == 0) {
|
||||
offsets.emplace_back(ult::to_string(offset + i + 3));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle remaining bytes
|
||||
for (; i < searchEnd; ++i) {
|
||||
if (bufPtr[i] == firstByte) {
|
||||
if (offset + i + patternLen <= fileSize &&
|
||||
memcmp(bufPtr + i, patternPtr, patternLen) == 0) {
|
||||
offsets.emplace_back(ult::to_string(offset + i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
offset += bytesRead;
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
|
||||
#else
|
||||
std::ifstream file(filePath, std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
return offsets;
|
||||
}
|
||||
|
||||
file.seekg(0, std::ios::end);
|
||||
const size_t fileSize = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
std::vector<unsigned char> binaryData;
|
||||
if (hexData.length() % 2 != 0) {
|
||||
file.close();
|
||||
return offsets;
|
||||
}
|
||||
|
||||
|
||||
const size_t hexLen = hexData.length();
|
||||
binaryData.resize(hexLen / 2);
|
||||
const unsigned char* hexPtr = reinterpret_cast<const unsigned char*>(hexData.c_str());
|
||||
|
||||
size_t i = 0;
|
||||
for (; i + 4 <= hexLen; i += 4) {
|
||||
binaryData[i/2] = (hexTable[hexPtr[i]] << 4) | hexTable[hexPtr[i + 1]];
|
||||
binaryData[i/2 + 1] = (hexTable[hexPtr[i + 2]] << 4) | hexTable[hexPtr[i + 3]];
|
||||
}
|
||||
for (; i < hexLen; i += 2) {
|
||||
binaryData[i/2] = (hexTable[hexPtr[i]] << 4) | hexTable[hexPtr[i + 1]];
|
||||
}
|
||||
|
||||
const unsigned char* patternPtr = binaryData.data();
|
||||
const size_t patternLen = binaryData.size();
|
||||
const unsigned char firstByte = patternPtr[0];
|
||||
|
||||
std::vector<unsigned char> buffer(HEX_BUFFER_SIZE);
|
||||
size_t bytesRead = 0;
|
||||
size_t offset = 0;
|
||||
|
||||
while (file.read(reinterpret_cast<char*>(buffer.data()), HEX_BUFFER_SIZE) || file.gcount() > 0) {
|
||||
bytesRead = file.gcount();
|
||||
const unsigned char* bufPtr = buffer.data();
|
||||
|
||||
// Same optimized search as FILE* version
|
||||
i = 0;
|
||||
const size_t searchEnd = bytesRead;
|
||||
|
||||
for (; i + 4 <= searchEnd; i += 4) {
|
||||
if (bufPtr[i] == firstByte) {
|
||||
if (offset + i + patternLen <= fileSize &&
|
||||
memcmp(bufPtr + i, patternPtr, patternLen) == 0) {
|
||||
offsets.emplace_back(ult::to_string(offset + i));
|
||||
}
|
||||
}
|
||||
if (bufPtr[i + 1] == firstByte) {
|
||||
if (offset + i + 1 + patternLen <= fileSize &&
|
||||
memcmp(bufPtr + i + 1, patternPtr, patternLen) == 0) {
|
||||
offsets.emplace_back(ult::to_string(offset + i + 1));
|
||||
}
|
||||
}
|
||||
if (bufPtr[i + 2] == firstByte) {
|
||||
if (offset + i + 2 + patternLen <= fileSize &&
|
||||
memcmp(bufPtr + i + 2, patternPtr, patternLen) == 0) {
|
||||
offsets.emplace_back(ult::to_string(offset + i + 2));
|
||||
}
|
||||
}
|
||||
if (bufPtr[i + 3] == firstByte) {
|
||||
if (offset + i + 3 + patternLen <= fileSize &&
|
||||
memcmp(bufPtr + i + 3, patternPtr, patternLen) == 0) {
|
||||
offsets.emplace_back(ult::to_string(offset + i + 3));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (; i < searchEnd; ++i) {
|
||||
if (bufPtr[i] == firstByte) {
|
||||
if (offset + i + patternLen <= fileSize &&
|
||||
memcmp(bufPtr + i, patternPtr, patternLen) == 0) {
|
||||
offsets.emplace_back(ult::to_string(offset + i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
offset += bytesRead;
|
||||
}
|
||||
|
||||
file.close();
|
||||
#endif
|
||||
|
||||
return offsets;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Edits hexadecimal data in a file at a specified offset.
|
||||
*
|
||||
* This function opens a binary file, seeks to a specified offset, and replaces
|
||||
* the data at that offset with the provided hexadecimal data.
|
||||
*
|
||||
* @param filePath The path to the binary file.
|
||||
* @param offsetStr The offset in the file to perform the edit.
|
||||
* @param hexData The hexadecimal data to replace at the offset.
|
||||
*/
|
||||
void hexEditByOffset(const std::string& filePath, const std::string& offsetStr, const std::string& hexData) {
|
||||
// Lock file writes to prevent concurrent modifications to the same file
|
||||
std::lock_guard<std::mutex> fileWriteLock(fileWriteMutex);
|
||||
|
||||
const std::streampos offset = std::stoll(offsetStr);
|
||||
|
||||
#if !USING_FSTREAM_DIRECTIVE
|
||||
// Open the file for both reading and writing in binary mode
|
||||
FILE* file = fopen(filePath.c_str(), "rb+");
|
||||
if (!file) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Failed to open the file.");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
// Retrieve the file size
|
||||
fseek(file, 0, SEEK_END);
|
||||
const std::streampos fileSize = ftell(file);
|
||||
fseek(file, 0, SEEK_SET);
|
||||
|
||||
if (offset >= fileSize) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Offset exceeds file size.");
|
||||
#endif
|
||||
fclose(file);
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert the hex string to binary data
|
||||
std::vector<unsigned char> binaryData(hexData.length() / 2);
|
||||
std::string byteString;
|
||||
for (size_t i = 0, j = 0; i < hexData.length(); i += 2, ++j) {
|
||||
byteString = hexData.substr(i, 2);
|
||||
binaryData[j] = static_cast<unsigned char>(ult::stoi(byteString, nullptr, 16));
|
||||
}
|
||||
|
||||
// Move to the specified offset and write the binary data directly to the file
|
||||
fseek(file, offset, SEEK_SET);
|
||||
const size_t bytesWritten = fwrite(binaryData.data(), sizeof(unsigned char), binaryData.size(), file);
|
||||
if (bytesWritten != binaryData.size()) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Failed to write data to the file.");
|
||||
#endif
|
||||
fclose(file);
|
||||
return;
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
#else
|
||||
// Open the file for both reading and writing in binary mode
|
||||
std::fstream file(filePath, std::ios::binary | std::ios::in | std::ios::out);
|
||||
if (!file.is_open()) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Failed to open the file.");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
// Retrieve the file size
|
||||
file.seekg(0, std::ios::end);
|
||||
const std::streampos fileSize = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
if (offset >= fileSize) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Offset exceeds file size.");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert the hex string to binary data
|
||||
std::vector<unsigned char> binaryData(hexData.length() / 2);
|
||||
std::string byteString;
|
||||
for (size_t i = 0, j = 0; i < hexData.length(); i += 2, ++j) {
|
||||
byteString = hexData.substr(i, 2);
|
||||
binaryData[j] = static_cast<unsigned char>(ult::stoi(byteString, nullptr, 16));
|
||||
}
|
||||
|
||||
// Move to the specified offset and write the binary data directly to the file
|
||||
file.seekp(offset);
|
||||
file.write(reinterpret_cast<const char*>(binaryData.data()), binaryData.size());
|
||||
if (!file) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Failed to write data to the file.");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
file.close();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Edits a specific offset in a file with custom hexadecimal data.
|
||||
*
|
||||
* This function searches for a custom pattern in the file and calculates a new offset
|
||||
* based on user-provided offsetStr and the found pattern. It then replaces the data
|
||||
* at the calculated offset with the provided hexadecimal data.
|
||||
*
|
||||
* @param filePath The path to the binary file.
|
||||
* @param offsetStr The user-provided offset for the edit.
|
||||
* @param customPattern The custom pattern to search for in the file.
|
||||
* @param hexDataReplacement The hexadecimal data to replace at the calculated offset.
|
||||
* @param occurrence The occurrence/index of the data to replace (default is "0" to replace all occurrences).
|
||||
*/
|
||||
void hexEditByCustomOffset(const std::string& filePath, const std::string& customAsciiPattern, const std::string& offsetStr, const std::string& hexDataReplacement, size_t occurrence) {
|
||||
|
||||
// Create a cache key based on filePath and customAsciiPattern
|
||||
const std::string cacheKey = filePath + '?' + customAsciiPattern + '?' + ult::to_string(occurrence);
|
||||
|
||||
int hexSum = -1;
|
||||
|
||||
// Thread-safe cache access
|
||||
{
|
||||
std::shared_lock<std::shared_mutex> readLock(cacheMutex);
|
||||
const auto cachedResult = hexSumCache.find(cacheKey);
|
||||
if (cachedResult != hexSumCache.end()) {
|
||||
hexSum = ult::stoi(cachedResult->second);
|
||||
}
|
||||
}
|
||||
|
||||
if (hexSum == -1) {
|
||||
std::string customHexPattern;
|
||||
if (customAsciiPattern[0] == '#') {
|
||||
// remove #
|
||||
customHexPattern = customAsciiPattern.substr(1);
|
||||
} else {
|
||||
// Convert custom ASCII pattern to a custom hex pattern
|
||||
customHexPattern = asciiToHex(customAsciiPattern);
|
||||
}
|
||||
|
||||
|
||||
// Find hex data offsets in the file
|
||||
const std::vector<std::string> offsets = findHexDataOffsets(filePath, customHexPattern);
|
||||
|
||||
if (!offsets.empty()) {
|
||||
hexSum = ult::stoi(offsets[occurrence]);
|
||||
|
||||
// Thread-safe cache write
|
||||
{
|
||||
std::lock_guard<std::shared_mutex> writeLock(cacheMutex);
|
||||
hexSumCache[cacheKey] = ult::to_string(hexSum);
|
||||
}
|
||||
} else {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Offset not found.");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (hexSum != -1) {
|
||||
// Calculate the total offset to seek in the file
|
||||
//int sum = hexSum + ult::stoi(offsetStr);
|
||||
hexEditByOffset(filePath, ult::to_string(hexSum + ult::stoi(offsetStr)), hexDataReplacement);
|
||||
} else {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Failed to find " + customAsciiPattern + ".");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Finds and replaces hexadecimal data in a file.
|
||||
*
|
||||
* This function searches for occurrences of hexadecimal data in a binary file
|
||||
* and replaces them with a specified hexadecimal replacement data.
|
||||
*
|
||||
* @param filePath The path to the binary file.
|
||||
* @param hexDataToReplace The hexadecimal data to search for and replace.
|
||||
* @param hexDataReplacement The hexadecimal data to replace with.
|
||||
* @param occurrence The occurrence/index of the data to replace (default is "0" to replace all occurrences).
|
||||
*/
|
||||
void hexEditFindReplace(const std::string& filePath, const std::string& hexDataToReplace, const std::string& hexDataReplacement, size_t occurrence) {
|
||||
const std::vector<std::string> offsetStrs = findHexDataOffsets(filePath, hexDataToReplace);
|
||||
if (!offsetStrs.empty()) {
|
||||
if (occurrence == 0) {
|
||||
// Replace all occurrences
|
||||
for (const std::string& offsetStr : offsetStrs) {
|
||||
//logMessage("offsetStr: "+offsetStr);
|
||||
//logMessage("hexDataReplacement: "+hexDataReplacement);
|
||||
hexEditByOffset(filePath, offsetStr, hexDataReplacement);
|
||||
}
|
||||
} else {
|
||||
// Convert the occurrence string to an integer
|
||||
if (occurrence > 0 && occurrence <= offsetStrs.size()) {
|
||||
// Replace the specified occurrence/index
|
||||
//std::string offsetStr = offsetStrs[occurrence - 1];
|
||||
//logMessage("offsetStr: "+offsetStr);
|
||||
//logMessage("hexDataReplacement: "+hexDataReplacement);
|
||||
hexEditByOffset(filePath, offsetStrs[occurrence - 1], hexDataReplacement);
|
||||
} else {
|
||||
// Invalid occurrence/index specified
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Invalid hex occurrence/index specified.");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
//std::cout << "Hex data replaced successfully." << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Finds and replaces hexadecimal data in a file.
|
||||
*
|
||||
* This function searches for occurrences of hexadecimal data in a binary file
|
||||
* and replaces them with a specified hexadecimal replacement data.
|
||||
*
|
||||
* @param filePath The path to the binary file.
|
||||
* @param hexDataToReplace The hexadecimal data to search for and replace.
|
||||
* @param hexDataReplacement The hexadecimal data to replace with.
|
||||
* @param occurrence The occurrence/index of the data to replace (default is "0" to replace all occurrences).
|
||||
*/
|
||||
std::string parseHexDataAtCustomOffset(const std::string& filePath, const std::string& customAsciiPattern,
|
||||
const std::string& offsetStr, size_t length, size_t occurrence) {
|
||||
const std::string cacheKey = filePath + '?' + customAsciiPattern + '?' + ult::to_string(occurrence);
|
||||
int hexSum = -1;
|
||||
|
||||
// Thread-safe cache read
|
||||
{
|
||||
std::shared_lock<std::shared_mutex> readLock(cacheMutex);
|
||||
const auto cachedResult = hexSumCache.find(cacheKey);
|
||||
if (cachedResult != hexSumCache.end()) {
|
||||
hexSum = ult::stoi(cachedResult->second);
|
||||
}
|
||||
}
|
||||
|
||||
if (hexSum == -1) {
|
||||
const std::string customHexPattern = asciiToHex(customAsciiPattern);
|
||||
const std::vector<std::string> offsets = findHexDataOffsets(filePath, customHexPattern);
|
||||
|
||||
if (!offsets.empty() && offsets.size() > occurrence) {
|
||||
hexSum = ult::stoi(offsets[occurrence]);
|
||||
|
||||
// Thread-safe cache write
|
||||
{
|
||||
std::lock_guard<std::shared_mutex> writeLock(cacheMutex);
|
||||
hexSumCache[cacheKey] = ult::to_string(hexSum);
|
||||
}
|
||||
} else {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Offset not found.");
|
||||
#endif
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
const std::streampos totalOffset = hexSum + std::stoll(offsetStr);
|
||||
std::vector<char> hexBuffer(length);
|
||||
std::vector<char> hexStream(length * 2);
|
||||
|
||||
#if !USING_FSTREAM_DIRECTIVE
|
||||
FILE* file = fopen(filePath.c_str(), "rb");
|
||||
if (!file) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Failed to open the file.");
|
||||
#endif
|
||||
return "";
|
||||
}
|
||||
|
||||
if (fseek(file, totalOffset, SEEK_SET) != 0) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Error seeking to offset.");
|
||||
#endif
|
||||
fclose(file);
|
||||
return "";
|
||||
}
|
||||
|
||||
const size_t bytesRead = fread(hexBuffer.data(), sizeof(char), length, file);
|
||||
if (bytesRead == length) {
|
||||
static constexpr char hexDigits[] = "0123456789ABCDEF";
|
||||
for (size_t i = 0; i < length; ++i) {
|
||||
hexStream[i * 2] = hexDigits[(hexBuffer[i] >> 4) & 0xF];
|
||||
hexStream[i * 2 + 1] = hexDigits[hexBuffer[i] & 0xF];
|
||||
}
|
||||
} else {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Error reading data from file or end of file reached.");
|
||||
#endif
|
||||
fclose(file);
|
||||
return "";
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
#else
|
||||
std::ifstream file(filePath, std::ios::binary);
|
||||
if (!file) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Failed to open the file.");
|
||||
#endif
|
||||
return "";
|
||||
}
|
||||
|
||||
file.seekg(totalOffset);
|
||||
if (!file) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Error seeking to offset.");
|
||||
#endif
|
||||
return "";
|
||||
}
|
||||
|
||||
file.read(hexBuffer.data(), length);
|
||||
if (file.gcount() == static_cast<std::streamsize>(length)) {
|
||||
static constexpr char hexDigits[] = "0123456789ABCDEF";
|
||||
for (size_t i = 0; i < length; ++i) {
|
||||
hexStream[i * 2] = hexDigits[(hexBuffer[i] >> 4) & 0xF];
|
||||
hexStream[i * 2 + 1] = hexDigits[hexBuffer[i] & 0xF];
|
||||
}
|
||||
} else {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Error reading data from file or end of file reached.");
|
||||
#endif
|
||||
return "";
|
||||
}
|
||||
|
||||
file.close();
|
||||
#endif
|
||||
|
||||
std::string result(hexStream.begin(), hexStream.end());
|
||||
result = stringToUppercase(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Finds and replaces hexadecimal data in a file.
|
||||
*
|
||||
* This function searches for occurrences of hexadecimal data in a binary file
|
||||
* and replaces them with a specified hexadecimal replacement data.
|
||||
*
|
||||
* @param filePath The path to the binary file.
|
||||
* @param hexDataToReplace The hexadecimal data to search for and replace.
|
||||
* @param hexDataReplacement The hexadecimal data to replace with.
|
||||
* @param occurrence The occurrence/index of the data to replace (default is "0" to replace all occurrences).
|
||||
*/
|
||||
|
||||
std::string replaceHexPlaceholder(const std::string& arg, const std::string& hexPath) {
|
||||
std::string replacement = arg;
|
||||
const std::string searchString = "{hex_file(";
|
||||
|
||||
const size_t startPos = replacement.find(searchString);
|
||||
const size_t endPos = replacement.find(")}");
|
||||
|
||||
if (startPos != std::string::npos && endPos != std::string::npos && endPos > startPos) {
|
||||
const std::string placeholderContent = replacement.substr(startPos + searchString.length(), endPos - startPos - searchString.length());
|
||||
|
||||
// Split the placeholder content into its components (customAsciiPattern, offsetStr, length)
|
||||
std::vector<std::string> components;
|
||||
|
||||
// Use StringStream instead of std::istringstream
|
||||
StringStream componentStream(placeholderContent);
|
||||
std::string component;
|
||||
|
||||
while (componentStream.getline(component, ',')) {
|
||||
trim(component);
|
||||
components.push_back(component);
|
||||
}
|
||||
|
||||
if (components.size() == 3) {
|
||||
// Extract individual components
|
||||
const std::string customAsciiPattern = components[0];
|
||||
const std::string offsetStr = components[1];
|
||||
const size_t length = std::stoul(components[2]);
|
||||
|
||||
// Call the parsing function and replace the placeholder
|
||||
const std::string parsedResult = parseHexDataAtCustomOffset(hexPath, customAsciiPattern, offsetStr, length);
|
||||
|
||||
// Only replace if parsedResult returns a non-empty string
|
||||
if (!parsedResult.empty()) {
|
||||
// Replace the entire placeholder with the parsed result
|
||||
replacement.replace(startPos, endPos - startPos + searchString.length() + 2, parsedResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return replacement;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Extracts the version string from a binary file.
|
||||
*
|
||||
* This function reads a binary file and searches for a version pattern
|
||||
* in the format "v#.#.#" (e.g., "v1.2.3").
|
||||
*
|
||||
* @param filePath The path to the binary file.
|
||||
* @return The version string if found; otherwise, an empty string.
|
||||
*/
|
||||
std::string extractVersionFromBinary(const std::string &filePath) {
|
||||
#if !USING_FSTREAM_DIRECTIVE
|
||||
// Step 1: Open the binary file
|
||||
FILE* file = fopen(filePath.c_str(), "rb");
|
||||
if (!file) {
|
||||
return ""; // Return empty string if file cannot be opened
|
||||
}
|
||||
|
||||
// Get the file size
|
||||
fseek(file, 0, SEEK_END);
|
||||
const std::streamsize size = ftell(file);
|
||||
fseek(file, 0, SEEK_SET);
|
||||
|
||||
// Read the entire file into a buffer
|
||||
std::vector<uint8_t> buffer(size);
|
||||
const size_t bytesRead = fread(buffer.data(), sizeof(uint8_t), size, file);
|
||||
fclose(file); // Close the file after reading
|
||||
|
||||
if (bytesRead != static_cast<size_t>(size)) {
|
||||
return ""; // Return empty string if reading fails
|
||||
}
|
||||
#else
|
||||
// Step 1: Read the entire binary file into a vector
|
||||
std::ifstream file(filePath, std::ios::binary | std::ios::ate);
|
||||
if (!file.is_open()) {
|
||||
return ""; // Return empty string if file cannot be opened
|
||||
}
|
||||
|
||||
const std::streamsize size = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
std::vector<uint8_t> buffer(size);
|
||||
if (!file.read(reinterpret_cast<char*>(buffer.data()), size)) {
|
||||
return ""; // Return empty string if reading fails
|
||||
}
|
||||
#endif
|
||||
|
||||
// Step 2: Search for the pattern "v#.#.#"
|
||||
const char* data = reinterpret_cast<const char*>(buffer.data());
|
||||
for (std::streamsize i = 0; i < size; ++i) {
|
||||
if (data[i] == 'v' && i + 5 < size &&
|
||||
std::isdigit(data[i + 1]) && data[i + 2] == '.' &&
|
||||
std::isdigit(data[i + 3]) && data[i + 4] == '.' &&
|
||||
std::isdigit(data[i + 5])) {
|
||||
|
||||
// Extract the version string
|
||||
return std::string(data + i, 6); // Return the version string
|
||||
}
|
||||
}
|
||||
|
||||
return ""; // Return empty string if no match is found
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,400 @@
|
||||
/********************************************************************************
|
||||
* File: json_funcs.cpp
|
||||
* Author: ppkantorski
|
||||
* Description:
|
||||
* This source file provides functions for working with JSON files in C++ using
|
||||
* the `cJSON` library. It includes a function to read JSON data from a file.
|
||||
*
|
||||
* For the latest updates and contributions, visit the project's GitHub repository.
|
||||
* (GitHub Repository: https://github.com/ppkantorski/Ultrahand-Overlay)
|
||||
*
|
||||
* Note: Please be aware that this notice cannot be altered or removed. It is a part
|
||||
* of the project's documentation and must remain intact.
|
||||
*
|
||||
* Licensed under both GPLv2 and CC-BY-4.0
|
||||
* Copyright (c) 2024 ppkantorski
|
||||
********************************************************************************/
|
||||
|
||||
#include "json_funcs.hpp"
|
||||
|
||||
|
||||
namespace ult {
|
||||
|
||||
static std::mutex json_access_mutex;
|
||||
|
||||
/**
|
||||
* @brief Reads JSON data from a file and returns it as a `json_t` object.
|
||||
*
|
||||
* @param filePath The path to the JSON file.
|
||||
* @return A `json_t` object representing the parsed JSON data. Returns `nullptr` on error.
|
||||
*/
|
||||
json_t* readJsonFromFile(const std::string& filePath) {
|
||||
std::lock_guard<std::mutex> lock(json_access_mutex);
|
||||
#if !USING_FSTREAM_DIRECTIVE
|
||||
FILE* file = fopen(filePath.c_str(), "rb");
|
||||
if (!file) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Get file size
|
||||
fseek(file, 0, SEEK_END);
|
||||
const long fileSize = ftell(file);
|
||||
fseek(file, 0, SEEK_SET);
|
||||
|
||||
// Check for reasonable file size
|
||||
if (fileSize <= 0 || fileSize > 6 * 1024 * 1024) { // 6MB limit
|
||||
fclose(file);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Use vector<char> for better performance and explicit null termination
|
||||
std::vector<char> buffer;
|
||||
buffer.resize(static_cast<size_t>(fileSize) + 1); // +1 for null terminator
|
||||
|
||||
// Read the file in one operation
|
||||
const size_t bytesRead = fread(buffer.data(), 1, static_cast<size_t>(fileSize), file);
|
||||
fclose(file);
|
||||
|
||||
if (bytesRead != static_cast<size_t>(fileSize)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Ensure null termination for cJSON
|
||||
buffer[bytesRead] = '\0';
|
||||
|
||||
#else
|
||||
std::ifstream file(filePath, std::ios::binary | std::ios::ate);
|
||||
if (!file.is_open()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Get file size from current position (end)
|
||||
const std::streampos fileSize = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
// Check for reasonable file size
|
||||
if (fileSize <= 0 || fileSize > 6 * 1024 * 1024) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Use vector<char> for better performance and explicit null termination
|
||||
std::vector<char> buffer;
|
||||
buffer.resize(static_cast<size_t>(fileSize) + 1); // +1 for null terminator
|
||||
|
||||
file.read(buffer.data(), static_cast<std::streamsize>(fileSize));
|
||||
|
||||
// Check how much was actually read
|
||||
std::streamsize actualRead = file.gcount();
|
||||
if (actualRead != fileSize) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Ensure null termination for cJSON
|
||||
buffer[actualRead] = '\0';
|
||||
file.close();
|
||||
#endif
|
||||
|
||||
// Parse the JSON content - pass buffer directly to avoid string copy
|
||||
cJSON* root = cJSON_Parse(buffer.data());
|
||||
if (!root) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
const char* error_ptr = cJSON_GetErrorPtr();
|
||||
if (error_ptr) {
|
||||
if (!disableLogging)
|
||||
logMessage("JSON parsing error: " + std::string(error_ptr));
|
||||
}
|
||||
#endif
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return reinterpret_cast<json_t*>(root);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Parses a JSON string into a json_t object.
|
||||
*
|
||||
* This function takes a JSON string as input and parses it into a json_t object using cJSON library's `cJSON_Parse` function.
|
||||
* If parsing fails, it logs the error and returns nullptr.
|
||||
*
|
||||
* @param input The input JSON string to parse.
|
||||
* @return A json_t object representing the parsed JSON, or nullptr if parsing fails.
|
||||
*/
|
||||
json_t* stringToJson(const std::string& input) {
|
||||
cJSON* jsonObj = cJSON_Parse(input.c_str());
|
||||
|
||||
if (!jsonObj) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
const char* error_ptr = cJSON_GetErrorPtr();
|
||||
if (error_ptr != nullptr) {
|
||||
if (!disableLogging)
|
||||
logMessage("Failed to parse JSON: " + std::string(error_ptr));
|
||||
}
|
||||
#endif
|
||||
return nullptr; // Return nullptr to indicate failure clearly
|
||||
}
|
||||
|
||||
return reinterpret_cast<json_t*>(jsonObj);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Function to get a string from a JSON object
|
||||
std::string getStringFromJson(const json_t* root, const char* key) {
|
||||
const cJSON* croot = reinterpret_cast<const cJSON*>(root);
|
||||
const cJSON* value = cJSON_GetObjectItemCaseSensitive(croot, key);
|
||||
if (value && cJSON_IsString(value) && value->valuestring) {
|
||||
return value->valuestring;
|
||||
} else {
|
||||
return ""; // Key not found or not a string, return empty string/char*
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Loads a JSON file from the specified path and retrieves a string value for a given key.
|
||||
*
|
||||
* This function combines the functionality of loading a JSON file and retrieving a string value associated
|
||||
* with a given key in a single step.
|
||||
*
|
||||
* @param filePath The path to the JSON file.
|
||||
* @param key The key whose associated string value is to be retrieved.
|
||||
* @return A string containing the value associated with the given key, or an empty string if the key is not found.
|
||||
*/
|
||||
std::string getStringFromJsonFile(const std::string& filePath, const std::string& key) {
|
||||
// Load JSON from file using a smart pointer
|
||||
std::unique_ptr<json_t, JsonDeleter> root(readJsonFromFile(filePath), JsonDeleter());
|
||||
if (!root) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
logMessage("Failed to load JSON file from path: " + filePath);
|
||||
#endif
|
||||
return "";
|
||||
}
|
||||
|
||||
// Retrieve the string value associated with the key
|
||||
cJSON* croot = reinterpret_cast<cJSON*>(root.get());
|
||||
cJSON* jsonKey = cJSON_GetObjectItemCaseSensitive(croot, key.c_str());
|
||||
const char* value = (cJSON_IsString(jsonKey) && jsonKey->valuestring) ? jsonKey->valuestring : nullptr;
|
||||
|
||||
// Check if the value was found and return it
|
||||
if (value) {
|
||||
return std::string(value);
|
||||
} else {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
logMessage("Key not found or not a string in JSON: " + key);
|
||||
#endif
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Sets a value in a JSON file, creating the file if it doesn't exist.
|
||||
*
|
||||
* @param filePath The path to the JSON file.
|
||||
* @param key The key to set.
|
||||
* @param value The value to set (auto-detected type).
|
||||
* @param createIfNotExists Whether to create the file if it doesn't exist.
|
||||
* @return true if successful, false otherwise.
|
||||
*/
|
||||
bool setJsonValue(const std::string& filePath, const std::string& key, const std::string& value, bool createIfNotExists) {
|
||||
// Try to load existing file
|
||||
std::unique_ptr<json_t, JsonDeleter> root(readJsonFromFile(filePath), JsonDeleter());
|
||||
|
||||
cJSON* croot = nullptr;
|
||||
|
||||
// If file doesn't exist, create new JSON object if allowed
|
||||
if (!root) {
|
||||
if (!createIfNotExists) {
|
||||
return false;
|
||||
}
|
||||
croot = cJSON_CreateObject();
|
||||
if (!croot) {
|
||||
return false;
|
||||
}
|
||||
root.reset(reinterpret_cast<json_t*>(croot));
|
||||
} else {
|
||||
croot = reinterpret_cast<cJSON*>(root.get());
|
||||
}
|
||||
|
||||
// FIXED: Better value type detection
|
||||
cJSON* jsonValue = nullptr;
|
||||
|
||||
// Trim whitespace first
|
||||
std::string trimmedValue = value;
|
||||
// Remove leading whitespace
|
||||
trimmedValue.erase(0, trimmedValue.find_first_not_of(" \t\n\r"));
|
||||
// Remove trailing whitespace
|
||||
trimmedValue.erase(trimmedValue.find_last_not_of(" \t\n\r") + 1);
|
||||
|
||||
if (trimmedValue.empty()) {
|
||||
jsonValue = cJSON_CreateString("");
|
||||
} else if (trimmedValue == "true") {
|
||||
jsonValue = cJSON_CreateBool(1);
|
||||
} else if (trimmedValue == "false") {
|
||||
jsonValue = cJSON_CreateBool(0);
|
||||
} else if (trimmedValue == "null") {
|
||||
jsonValue = cJSON_CreateNull();
|
||||
} else {
|
||||
// Try parsing as number (integer or float)
|
||||
char* endPtr = nullptr;
|
||||
errno = 0;
|
||||
|
||||
// Try as integer first
|
||||
const long longValue = std::strtol(trimmedValue.c_str(), &endPtr, 10);
|
||||
if (endPtr == trimmedValue.c_str() + trimmedValue.length() && errno == 0) {
|
||||
// Successfully parsed as integer
|
||||
jsonValue = cJSON_CreateNumber(static_cast<double>(longValue));
|
||||
} else {
|
||||
// Try as float
|
||||
endPtr = nullptr;
|
||||
errno = 0;
|
||||
const double doubleValue = std::strtod(trimmedValue.c_str(), &endPtr);
|
||||
if (endPtr == trimmedValue.c_str() + trimmedValue.length() && errno == 0) {
|
||||
// Successfully parsed as float
|
||||
jsonValue = cJSON_CreateNumber(doubleValue);
|
||||
} else {
|
||||
// Treat as string
|
||||
jsonValue = cJSON_CreateString(trimmedValue.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!jsonValue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Delete existing item if it exists
|
||||
cJSON_DeleteItemFromObject(croot, key.c_str());
|
||||
|
||||
// Add the new value
|
||||
cJSON_AddItemToObject(croot, key.c_str(), jsonValue);
|
||||
|
||||
// Save to file with formatted output for better readability
|
||||
char* jsonString = cJSON_Print(croot); // Use formatted output instead of PrintUnformatted
|
||||
if (!jsonString) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(json_access_mutex);
|
||||
#if !USING_FSTREAM_DIRECTIVE
|
||||
FILE* file = fopen(filePath.c_str(), "w"); // Use text mode for JSON
|
||||
if (file) {
|
||||
const size_t jsonLength = std::strlen(jsonString);
|
||||
const size_t bytesWritten = fwrite(jsonString, 1, jsonLength, file);
|
||||
success = (bytesWritten == jsonLength);
|
||||
fclose(file);
|
||||
}
|
||||
#else
|
||||
std::ofstream file(filePath); // Use text mode for JSON
|
||||
if (file.is_open()) {
|
||||
file << jsonString;
|
||||
success = !file.fail();
|
||||
file.close();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
cJSON_free(jsonString);
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Renames a key in a JSON file.
|
||||
*
|
||||
* @param filePath The path to the JSON file.
|
||||
* @param oldKey The current key name.
|
||||
* @param newKey The new key name.
|
||||
* @return true if successful, false otherwise.
|
||||
*/
|
||||
bool renameJsonKey(const std::string& filePath, const std::string& oldKey, const std::string& newKey) {
|
||||
// Try to load existing file
|
||||
std::unique_ptr<json_t, JsonDeleter> root(readJsonFromFile(filePath), JsonDeleter());
|
||||
|
||||
if (!root) {
|
||||
return false;
|
||||
}
|
||||
|
||||
cJSON* croot = reinterpret_cast<cJSON*>(root.get());
|
||||
|
||||
// Check if old key exists
|
||||
cJSON* value = cJSON_GetObjectItemCaseSensitive(croot, oldKey.c_str());
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Detach the value from the object
|
||||
cJSON_DetachItemFromObject(croot, oldKey.c_str());
|
||||
|
||||
// Add it back with the new key
|
||||
cJSON_AddItemToObject(croot, newKey.c_str(), value);
|
||||
|
||||
// Save to file
|
||||
char* jsonString = cJSON_Print(croot); // Use formatted output
|
||||
if (!jsonString) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(json_access_mutex);
|
||||
#if !USING_FSTREAM_DIRECTIVE
|
||||
FILE* file = fopen(filePath.c_str(), "w"); // Use text mode
|
||||
if (file) {
|
||||
const size_t jsonLength = std::strlen(jsonString);
|
||||
const size_t bytesWritten = fwrite(jsonString, 1, jsonLength, file);
|
||||
success = (bytesWritten == jsonLength);
|
||||
fclose(file);
|
||||
}
|
||||
#else
|
||||
std::ofstream file(filePath); // Use text mode
|
||||
if (file.is_open()) {
|
||||
file << jsonString;
|
||||
success = !file.fail();
|
||||
file.close();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
cJSON_free(jsonString);
|
||||
return success;
|
||||
}
|
||||
|
||||
void pushNotificationJson(const std::string& text, size_t fontSize) {
|
||||
u64 tick = armGetSystemTick();
|
||||
std::string filename = "20-" + std::to_string(tick) + ".notify"; // priority 20 default
|
||||
|
||||
// Build full path
|
||||
std::string fullPath = NOTIFICATIONS_PATH + filename;
|
||||
|
||||
// Create JSON object
|
||||
cJSON* notif = cJSON_CreateObject();
|
||||
if (!notif) return;
|
||||
|
||||
cJSON_AddStringToObject(notif, "text", text.c_str());
|
||||
cJSON_AddNumberToObject(notif, "font_size", static_cast<double>(fontSize));
|
||||
//cJSON_AddNumberToObject(notif, "arrival_ns", static_cast<double>(armTicksToNs(tick)));
|
||||
|
||||
// Serialize JSON
|
||||
char* rendered = cJSON_PrintUnformatted(notif);
|
||||
if (!rendered) {
|
||||
cJSON_Delete(notif);
|
||||
return;
|
||||
}
|
||||
|
||||
// Write to file (C-style)
|
||||
FILE* file = fopen(fullPath.c_str(), "wb");
|
||||
if (file) {
|
||||
fwrite(rendered, 1, strlen(rendered), file);
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
cJSON_free(rendered);
|
||||
cJSON_Delete(notif);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,467 @@
|
||||
/********************************************************************************
|
||||
* File: list_funcs.cpp
|
||||
* Author: ppkantorski
|
||||
* Description:
|
||||
* This source file contains function declarations and utility functions related
|
||||
* to working with lists and vectors of strings. These functions are used in the
|
||||
* Ultrahand Overlay project to perform various operations on lists, such as
|
||||
* removing entries, filtering, and more.
|
||||
*
|
||||
* For the latest updates and contributions, visit the project's GitHub repository.
|
||||
* (GitHub Repository: https://github.com/ppkantorski/Ultrahand-Overlay)
|
||||
*
|
||||
* Note: Please be aware that this notice cannot be altered or removed. It is a part
|
||||
* of the project's documentation and must remain intact.
|
||||
*
|
||||
* Licensed under both GPLv2 and CC-BY-4.0
|
||||
* Copyright (c) 2024 ppkantorski
|
||||
********************************************************************************/
|
||||
|
||||
#include <list_funcs.hpp>
|
||||
#include <mutex>
|
||||
|
||||
namespace ult {
|
||||
// Thread-safe file access mutex
|
||||
static std::mutex file_access_mutex;
|
||||
|
||||
std::vector<std::string> splitIniList(const std::string& value) {
|
||||
std::vector<std::string> result;
|
||||
std::string trimmed = value;
|
||||
trim(trimmed);
|
||||
if (trimmed.size() > 2 && trimmed.front() == '(' && trimmed.back() == ')') {
|
||||
trimmed = trimmed.substr(1, trimmed.size() - 2);
|
||||
ult::StringStream ss(trimmed);
|
||||
std::string token;
|
||||
while (ss.getline(token, ',')) {
|
||||
trim(token);
|
||||
result.push_back(token);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string joinIniList(const std::vector<std::string>& list) {
|
||||
std::string result = "";
|
||||
for (size_t i = 0; i < list.size(); ++i) {
|
||||
result += list[i];
|
||||
if (i + 1 < list.size()) {
|
||||
result += ", ";
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Removes entries from a vector of strings that match a specified entry.
|
||||
*
|
||||
* This function removes entries from the `itemsList` vector of strings that match the `entry`.
|
||||
*
|
||||
* @param entry The entry to be compared against the elements in `itemsList`.
|
||||
* @param itemsList The vector of strings from which matching entries will be removed.
|
||||
*/
|
||||
void removeEntryFromList(const std::string& entry, std::vector<std::string>& itemsList) {
|
||||
itemsList.erase(std::remove_if(itemsList.begin(), itemsList.end(), [&](const std::string& item) {
|
||||
return item.compare(0, entry.length(), entry) == 0;
|
||||
}), itemsList.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Filters a list of strings based on a specified filter list.
|
||||
*
|
||||
* This function filters a list of strings (`itemsList`) by removing entries that match any
|
||||
* of the criteria specified in the `filterList`. It uses the `removeEntryFromList` function
|
||||
* to perform the removal.
|
||||
*
|
||||
* @param filterList The list of entries to filter by. Entries in `itemsList` matching any entry in this list will be removed.
|
||||
* @param itemsList The list of strings to be filtered.
|
||||
*/
|
||||
void filterItemsList(const std::vector<std::string>& filterList, std::vector<std::string>& itemsList) {
|
||||
for (const auto& entry : filterList) {
|
||||
removeEntryFromList(entry, itemsList);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Function to read file into a vector of strings with optional cap
|
||||
std::vector<std::string> readListFromFile(const std::string& filePath, size_t maxLines) {
|
||||
std::lock_guard<std::mutex> lock(file_access_mutex);
|
||||
std::vector<std::string> lines;
|
||||
|
||||
#if !USING_FSTREAM_DIRECTIVE
|
||||
FILE* file = fopen(filePath.c_str(), "r");
|
||||
if (!file) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
logMessage("Unable to open file: " + filePath);
|
||||
#endif
|
||||
return lines;
|
||||
}
|
||||
|
||||
static constexpr size_t BUFFER_SIZE = 8192;
|
||||
char buffer[BUFFER_SIZE];
|
||||
size_t len;
|
||||
|
||||
while (fgets(buffer, BUFFER_SIZE, file)) {
|
||||
// Check cap before processing
|
||||
if (maxLines > 0 && lines.size() >= maxLines) {
|
||||
break;
|
||||
}
|
||||
|
||||
// More efficient newline removal
|
||||
len = strlen(buffer);
|
||||
if (len > 0 && buffer[len - 1] == '\n') {
|
||||
buffer[len - 1] = '\0';
|
||||
--len;
|
||||
// Also remove carriage return if present
|
||||
if (len > 0 && buffer[len - 1] == '\r') {
|
||||
buffer[len - 1] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
lines.emplace_back(buffer);
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
#else
|
||||
std::ifstream file(filePath);
|
||||
if (!file.is_open()) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
logMessage("Unable to open file: " + filePath);
|
||||
#endif
|
||||
return lines;
|
||||
}
|
||||
|
||||
std::string line;
|
||||
while (std::getline(file, line)) {
|
||||
// Check cap before adding
|
||||
if (maxLines > 0 && lines.size() >= maxLines) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Remove carriage return if present (getline removes \n but not \r)
|
||||
if (!line.empty() && line.back() == '\r') {
|
||||
line.pop_back();
|
||||
}
|
||||
|
||||
lines.emplace_back(std::move(line));
|
||||
}
|
||||
|
||||
file.close();
|
||||
#endif
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
|
||||
// Function to get an entry from the list based on the index
|
||||
std::string getEntryFromListFile(const std::string& listPath, size_t listIndex) {
|
||||
std::lock_guard<std::mutex> lock(file_access_mutex);
|
||||
|
||||
#if !USING_FSTREAM_DIRECTIVE
|
||||
FILE* file = fopen(listPath.c_str(), "r");
|
||||
if (!file) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
logMessage("Unable to open file: " + listPath);
|
||||
#endif
|
||||
return "";
|
||||
}
|
||||
|
||||
static constexpr size_t BUFFER_SIZE = 8192;
|
||||
char buffer[BUFFER_SIZE];
|
||||
|
||||
// Skip lines until reaching the desired index
|
||||
for (size_t i = 0; i < listIndex; ++i) {
|
||||
if (!fgets(buffer, BUFFER_SIZE, file)) {
|
||||
fclose(file);
|
||||
return ""; // Index out of bounds
|
||||
}
|
||||
}
|
||||
|
||||
// Read the target line
|
||||
if (!fgets(buffer, BUFFER_SIZE, file)) {
|
||||
fclose(file);
|
||||
return ""; // Index out of bounds
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
|
||||
// Efficiently remove newline character
|
||||
const size_t len = strlen(buffer);
|
||||
if (len > 0 && buffer[len - 1] == '\n') {
|
||||
buffer[len - 1] = '\0';
|
||||
if (len > 1 && buffer[len - 2] == '\r') {
|
||||
buffer[len - 2] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
return std::string(buffer);
|
||||
|
||||
#else
|
||||
std::ifstream file(listPath);
|
||||
if (!file.is_open()) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
logMessage("Unable to open file: " + listPath);
|
||||
#endif
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string line;
|
||||
|
||||
// Skip lines until reaching the desired index
|
||||
for (size_t i = 0; i < listIndex; ++i) {
|
||||
if (!std::getline(file, line)) {
|
||||
return ""; // Index out of bounds
|
||||
}
|
||||
}
|
||||
|
||||
// Read the target line
|
||||
if (!std::getline(file, line)) {
|
||||
return ""; // Index out of bounds
|
||||
}
|
||||
|
||||
file.close();
|
||||
return line;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Splits a string into a vector of strings using a delimiter.
|
||||
*
|
||||
* This function splits the input string into multiple strings using the specified delimiter.
|
||||
*
|
||||
* @param str The input string to split.
|
||||
* @return A vector of strings containing the split values.
|
||||
*/
|
||||
std::vector<std::string> stringToList(const std::string& str) {
|
||||
std::vector<std::string> result;
|
||||
|
||||
if (str.empty()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Check if the input string starts and ends with '(' and ')' or '[' and ']'
|
||||
if ((str.front() == '(' && str.back() == ')') || (str.front() == '[' && str.back() == ']')) {
|
||||
// Work directly with the original string using indices instead of creating substring
|
||||
size_t start = 1; // Skip opening bracket/paren
|
||||
size_t end = 1;
|
||||
const size_t values_end = str.size() - 1; // Skip closing bracket/paren
|
||||
|
||||
// Pre-declare item string to avoid repeated allocations
|
||||
std::string item;
|
||||
|
||||
// Iterate through the string manually to split by commas
|
||||
while ((end = str.find(',', start)) != std::string::npos && end < values_end) {
|
||||
// Extract item directly from original string without creating substring
|
||||
item.assign(str, start, end - start);
|
||||
|
||||
// Trim leading and trailing spaces
|
||||
trim(item);
|
||||
|
||||
// Remove quotes from each token if necessary
|
||||
removeQuotes(item);
|
||||
|
||||
result.push_back(std::move(item));
|
||||
start = end + 1;
|
||||
}
|
||||
|
||||
// Handle the last item after the last comma
|
||||
if (start < values_end) {
|
||||
item.assign(str, start, values_end - start);
|
||||
trim(item);
|
||||
removeQuotes(item);
|
||||
result.push_back(std::move(item));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Function to read file into a set of strings
|
||||
std::unordered_set<std::string> readSetFromFile(const std::string& filePath) {
|
||||
std::lock_guard<std::mutex> lock(file_access_mutex);
|
||||
std::unordered_set<std::string> lines;
|
||||
|
||||
#if !USING_FSTREAM_DIRECTIVE
|
||||
FILE* file = fopen(filePath.c_str(), "r");
|
||||
if (!file) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
logMessage("Unable to open file: " + filePath);
|
||||
#endif
|
||||
return lines;
|
||||
}
|
||||
|
||||
static constexpr size_t BUFFER_SIZE = 8192;
|
||||
char buffer[BUFFER_SIZE];
|
||||
size_t len;
|
||||
while (fgets(buffer, BUFFER_SIZE, file)) {
|
||||
// Remove trailing newline character if it exists
|
||||
len = strlen(buffer);
|
||||
if (len > 0 && buffer[len - 1] == '\n') {
|
||||
buffer[len - 1] = '\0';
|
||||
}
|
||||
lines.insert(buffer);
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
#else
|
||||
std::ifstream file(filePath);
|
||||
if (!file.is_open()) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
logMessage("Unable to open file: " + filePath);
|
||||
#endif
|
||||
return lines;
|
||||
}
|
||||
|
||||
std::string line;
|
||||
while (std::getline(file, line)) {
|
||||
lines.insert(std::move(line));
|
||||
}
|
||||
|
||||
file.close();
|
||||
#endif
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
|
||||
// Function to write a set to a file
|
||||
void writeSetToFile(const std::unordered_set<std::string>& fileSet, const std::string& filePath) {
|
||||
std::lock_guard<std::mutex> lock(file_access_mutex);
|
||||
|
||||
#if !USING_FSTREAM_DIRECTIVE
|
||||
FILE* file = fopen(filePath.c_str(), "w");
|
||||
if (!file) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
logMessage("Failed to open file: " + filePath);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& entry : fileSet) {
|
||||
fprintf(file, "%s\n", entry.c_str());
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
#else
|
||||
std::ofstream file(filePath);
|
||||
if (!file.is_open()) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
logMessage("Failed to open file: " + filePath);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& entry : fileSet) {
|
||||
file << entry << '\n';
|
||||
}
|
||||
|
||||
file.close();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
// Function to compare two file lists and save duplicates to an output file
|
||||
void compareFilesLists(const std::string& txtFilePath1, const std::string& txtFilePath2, const std::string& outputTxtFilePath) {
|
||||
// Read files into sets
|
||||
std::unordered_set<std::string> fileSet1 = readSetFromFile(txtFilePath1);
|
||||
std::unordered_set<std::string> fileSet2 = readSetFromFile(txtFilePath2);
|
||||
std::unordered_set<std::string> duplicateFiles;
|
||||
|
||||
// Find intersection (common elements) between the two sets
|
||||
for (const auto& entry : fileSet1) {
|
||||
if (fileSet2.count(entry)) {
|
||||
duplicateFiles.insert(entry);
|
||||
}
|
||||
}
|
||||
|
||||
// Write the duplicates to the output file
|
||||
writeSetToFile(duplicateFiles, outputTxtFilePath);
|
||||
}
|
||||
|
||||
// Helper function to read a text file and process each line with a callback
|
||||
void processFileLines(const std::string& filePath, const std::function<void(const std::string&)>& callback) {
|
||||
std::lock_guard<std::mutex> lock(file_access_mutex);
|
||||
|
||||
#if !USING_FSTREAM_DIRECTIVE
|
||||
FILE* file = fopen(filePath.c_str(), "r");
|
||||
if (!file) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
logMessage("Unable to open file: " + filePath);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
// OPTIMIZATION 1: Larger buffer for better I/O performance
|
||||
static constexpr size_t BUFFER_SIZE = 8192;
|
||||
char buffer[BUFFER_SIZE];
|
||||
|
||||
while (fgets(buffer, BUFFER_SIZE, file)) {
|
||||
// OPTIMIZATION 2: Find newline directly instead of strlen()
|
||||
char* newlinePos = strchr(buffer, '\n');
|
||||
if (newlinePos) {
|
||||
*newlinePos = '\0'; // Remove newline in-place
|
||||
}
|
||||
|
||||
// OPTIMIZATION 3: Pass buffer directly - no string construction overhead
|
||||
callback(std::string(buffer));
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
|
||||
#else
|
||||
// OPTIMIZATION 4: Use faster I/O for fstream version
|
||||
std::ifstream file(filePath);
|
||||
if (!file.is_open()) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
logMessage("Unable to open file: " + filePath);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
// OPTIMIZATION 5: Reserve string capacity to avoid reallocations
|
||||
std::string line;
|
||||
line.reserve(256); // Reasonable default for most lines
|
||||
|
||||
while (std::getline(file, line)) {
|
||||
callback(line);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void compareWildcardFilesLists(
|
||||
const std::string& wildcardPatternFilePath,
|
||||
const std::string& txtFilePath,
|
||||
const std::string& outputTxtFilePath
|
||||
) {
|
||||
// STEP 1: Read target file into fast lookup set (only once)
|
||||
const std::unordered_set<std::string> targetLines = readSetFromFile(txtFilePath);
|
||||
std::unordered_set<std::string> duplicates;
|
||||
|
||||
// STEP 2: Get wildcard files
|
||||
std::vector<std::string> wildcardFiles = getFilesListByWildcards(wildcardPatternFilePath);
|
||||
|
||||
// STEP 3: Process each wildcard file line-by-line (minimum memory)
|
||||
for (auto& filePath : wildcardFiles) {
|
||||
if (filePath == txtFilePath) {
|
||||
filePath = "";
|
||||
continue;
|
||||
}
|
||||
|
||||
// Process line-by-line without loading entire file into memory
|
||||
processFileLines(filePath, [&](const std::string& line) {
|
||||
// O(1) lookup + O(1) insert if duplicate found
|
||||
if (targetLines.count(line)) {
|
||||
duplicates.insert(line);
|
||||
}
|
||||
});
|
||||
filePath = "";
|
||||
}
|
||||
|
||||
// STEP 4: Write results
|
||||
writeSetToFile(duplicates, outputTxtFilePath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,736 @@
|
||||
/********************************************************************************
|
||||
* File: mod_funcs.cpp
|
||||
* Author: ppkantorski
|
||||
* Description:
|
||||
* This source file provides the implementations of functions declared in
|
||||
* mod_funcs.hpp. These functions handle the conversion of `.pchtxt` mod files
|
||||
* into `.ips` binary patches used by the Ultrahand Overlay project. This includes
|
||||
* parsing, validating, and encoding patch data into the IPS format.
|
||||
*
|
||||
* For the latest updates and contributions, visit the project's GitHub repository.
|
||||
* (GitHub Repository: https://github.com/ppkantorski/Ultrahand-Overlay)
|
||||
*
|
||||
* Note: Please be aware that this notice cannot be altered or removed. It is a part
|
||||
* of the project's documentation and must remain intact.
|
||||
*
|
||||
* Licensed under both GPLv2 and CC-BY-4.0
|
||||
* Copyright (c) 2024 ppkantorski
|
||||
********************************************************************************/
|
||||
|
||||
#include <mod_funcs.hpp>
|
||||
|
||||
namespace ult {
|
||||
|
||||
//const std::string CHEAT_HEADER = "// auto generated by pchtxt2cheat\n\n";
|
||||
const std::string CHEAT_TYPE = "04000000";
|
||||
const std::string CHEAT_EXT = ".txt";
|
||||
const std::string CHEAT_ENCODING = "ascii";
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Checks if a cheat already exists in the cheat file.
|
||||
* @param cheatFilePath The path to the cheat file.
|
||||
* @param newCheat The new cheat to check.
|
||||
* @return True if the cheat exists, otherwise false.
|
||||
*/
|
||||
bool cheatExists(const std::string& cheatFilePath, const std::string& newCheat) {
|
||||
#if !USING_FSTREAM_DIRECTIVE
|
||||
FILE* cheatFile = fopen(cheatFilePath.c_str(), "r"); // Open the cheat file in read mode
|
||||
if (!cheatFile) {
|
||||
return false; // Return false if the file cannot be opened
|
||||
}
|
||||
|
||||
//size_t len;
|
||||
char buffer[1024]; // Buffer to store each line
|
||||
while (fgets(buffer, sizeof(buffer), cheatFile)) {
|
||||
// Remove newline character, if present
|
||||
const size_t len = strlen(buffer);
|
||||
if (len > 0 && buffer[len - 1] == '\n') {
|
||||
buffer[len - 1] = '\0';
|
||||
}
|
||||
if (newCheat == buffer) {
|
||||
fclose(cheatFile); // Close the file before returning
|
||||
return true; // Cheat exists
|
||||
}
|
||||
}
|
||||
|
||||
fclose(cheatFile); // Close the file after processing
|
||||
return false; // Cheat does not exist
|
||||
#else
|
||||
std::ifstream cheatFile(cheatFilePath);
|
||||
if (!cheatFile) {
|
||||
return false; // Return false if the file cannot be opened
|
||||
}
|
||||
|
||||
std::string line;
|
||||
while (std::getline(cheatFile, line)) {
|
||||
if (line == newCheat) {
|
||||
return true; // Cheat exists
|
||||
}
|
||||
}
|
||||
return false; // Cheat does not exist
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Appends a new cheat to the cheat file.
|
||||
* @param cheatFilePath The path to the cheat file.
|
||||
* @param newCheat The new cheat to append.
|
||||
*/
|
||||
void appendCheatToFile(const std::string& cheatFilePath, const std::string& newCheat) {
|
||||
#if !USING_FSTREAM_DIRECTIVE
|
||||
FILE* cheatFile = fopen(cheatFilePath.c_str(), "a"); // Open the cheat file in append mode
|
||||
if (!cheatFile) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Failed to open cheat file for appending: " + cheatFilePath);
|
||||
#endif
|
||||
return; // Handle the error accordingly
|
||||
}
|
||||
|
||||
fprintf(cheatFile, "%s\n", newCheat.c_str()); // Write the new cheat followed by a newline
|
||||
fclose(cheatFile); // Close the file
|
||||
#else
|
||||
std::ofstream cheatFile(cheatFilePath, std::ios::app);
|
||||
if (!cheatFile) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Failed to open cheat file for appending: " + cheatFilePath);
|
||||
#endif
|
||||
return; // Handle the error accordingly
|
||||
}
|
||||
|
||||
cheatFile << newCheat << std::endl; // Write the new cheat
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Extracts the cheat name from the given file path.
|
||||
* @param filePath The full file path.
|
||||
* @return The extracted cheat name.
|
||||
*/
|
||||
std::string extractCheatName(const std::string &filePath) {
|
||||
const size_t lastSlash = filePath.find_last_of("/\\");
|
||||
if (lastSlash == std::string::npos) {
|
||||
return "";
|
||||
}
|
||||
const size_t secondLastSlash = filePath.find_last_of("/\\", lastSlash - 1);
|
||||
if (secondLastSlash == std::string::npos) {
|
||||
return "";
|
||||
}
|
||||
const std::string lastDir = filePath.substr(secondLastSlash + 1, lastSlash - secondLastSlash - 1);
|
||||
std::string fileName = filePath.substr(lastSlash + 1);
|
||||
const size_t dotPos = fileName.find_last_of('.');
|
||||
if (dotPos != std::string::npos) {
|
||||
fileName = fileName.substr(0, dotPos);
|
||||
}
|
||||
|
||||
// If lastDir contains " - ", extract the part after " - "
|
||||
const size_t dashPos = lastDir.find(" - ");
|
||||
std::string cheatName = lastDir;
|
||||
if (dashPos != std::string::npos) {
|
||||
cheatName = lastDir.substr(dashPos + 3);
|
||||
}
|
||||
|
||||
return cheatName + " " + fileName;
|
||||
}
|
||||
|
||||
// Helper function to determine if a string is a valid title ID
|
||||
bool isValidTitleID(const std::string &str) {
|
||||
if (str.length() != 16) return false;
|
||||
for (char c : str) {
|
||||
if (!std::isxdigit(c)) return false; // Check if each character is a hexadecimal digit
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Function to find the title ID in the text, avoiding the @nsobid- line
|
||||
std::string findTitleID(const std::string &text) {
|
||||
const size_t nsobidPos = text.find("@nsobid-");
|
||||
const size_t startPos = (nsobidPos != std::string::npos) ? nsobidPos + 40 + 8 : 0; // Skip past @nsobid- and its value
|
||||
|
||||
std::string potentialID;
|
||||
for (size_t i = startPos; i <= text.length() - 16; ++i) {
|
||||
potentialID = text.substr(i, 16);
|
||||
if (isValidTitleID(potentialID)) {
|
||||
return potentialID;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Converts a .pchtxt file to a cheat file.
|
||||
* @param pchtxtPath The file path to the .pchtxt file.
|
||||
* @param cheatName The name of the cheat.
|
||||
* @param outCheatPath The file path for the output cheat file.
|
||||
* @return True if the conversion was successful, false otherwise.
|
||||
*/
|
||||
bool pchtxt2cheat(const std::string &pchtxtPath, std::string cheatName, std::string outCheatPath) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Starting pchtxt2cheat with pchtxtPath: " + pchtxtPath);
|
||||
#endif
|
||||
|
||||
#if !USING_FSTREAM_DIRECTIVE
|
||||
FILE* pchtxtFile = fopen(pchtxtPath.c_str(), "r");
|
||||
if (!pchtxtFile) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Error: Unable to open file " + pchtxtPath);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read the entire file into a string
|
||||
std::string pchtxt;
|
||||
char buffer[4096];
|
||||
while (fgets(buffer, sizeof(buffer), pchtxtFile)) {
|
||||
pchtxt += buffer;
|
||||
}
|
||||
fclose(pchtxtFile);
|
||||
#else
|
||||
std::ifstream pchtxtFile(pchtxtPath);
|
||||
if (!pchtxtFile) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Error: Unable to open file " + pchtxtPath);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string pchtxt((std::istreambuf_iterator<char>(pchtxtFile)), std::istreambuf_iterator<char>());
|
||||
#endif
|
||||
|
||||
const size_t nsobidPos = pchtxt.find("@nsobid-");
|
||||
if (nsobidPos == std::string::npos) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Error: Could not find bid in pchtxt file, the file is likely invalid.");
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string bid = pchtxt.substr(nsobidPos + 8, 40);
|
||||
const std::string bidShort = bid.substr(0, 16);
|
||||
|
||||
const std::string tid = findTitleID(pchtxt);
|
||||
if (tid.empty()) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Error: Could not find TID in pchtxt file, the file is likely invalid.");
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string cheatFilePath;
|
||||
|
||||
if (outCheatPath.empty()) {
|
||||
const std::string folderPath = "sdmc:/atmosphere/contents/" + tid + "/cheats/";
|
||||
createDirectory(folderPath);
|
||||
cheatFilePath = folderPath + bidShort + CHEAT_EXT;
|
||||
} else {
|
||||
cheatFilePath = outCheatPath;
|
||||
}
|
||||
|
||||
#if !USING_FSTREAM_DIRECTIVE
|
||||
FILE* existingCheatFile = fopen(cheatFilePath.c_str(), "r");
|
||||
bool cheatNameExists = false;
|
||||
if (existingCheatFile) {
|
||||
char line[256];
|
||||
while (fgets(line, sizeof(line), existingCheatFile)) {
|
||||
if (std::string(line) == "[" + cheatName + "]\n") {
|
||||
cheatNameExists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
fclose(existingCheatFile);
|
||||
}
|
||||
#else
|
||||
std::ifstream existingCheatFile(cheatFilePath);
|
||||
bool cheatNameExists = false;
|
||||
if (existingCheatFile) {
|
||||
std::string line;
|
||||
while (std::getline(existingCheatFile, line)) {
|
||||
if (line == "[" + cheatName + "]") {
|
||||
cheatNameExists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
existingCheatFile.close();
|
||||
#endif
|
||||
|
||||
// Open output cheat file
|
||||
#if !USING_FSTREAM_DIRECTIVE
|
||||
FILE* outCheatFile = fopen(cheatFilePath.c_str(), "a");
|
||||
if (!outCheatFile) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Error: Unable to create cheat file " + cheatFilePath);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
std::ofstream outCheatFile(cheatFilePath, std::ios::app);
|
||||
if (!outCheatFile) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Error: Unable to create cheat file " + cheatFilePath);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!cheatNameExists) {
|
||||
#if !USING_FSTREAM_DIRECTIVE
|
||||
fprintf(outCheatFile, "[%s]\n", cheatName.c_str());
|
||||
#else
|
||||
outCheatFile << "[" << cheatName << "]\n";
|
||||
#endif
|
||||
}
|
||||
|
||||
int offset = 0;
|
||||
bool enabled = true;
|
||||
int validCheatsProcessed = 0; // ADDED: Track number of valid cheats processed
|
||||
StringStream iss(pchtxt); // Use your custom StringStream
|
||||
std::string line;
|
||||
|
||||
std::string addrStr, valStr;
|
||||
size_t spacePos;
|
||||
int codeOffset;
|
||||
std::string cheatLine;
|
||||
char offsetBuffer[9];
|
||||
|
||||
// Use your custom getline method instead of std::getline
|
||||
while (iss.getline(line, '\n')) { // Custom getline with newline as the delimiter
|
||||
|
||||
// strip inline C++-style comments
|
||||
const auto slashPos = line.find("//");
|
||||
if (slashPos != std::string::npos)
|
||||
line = line.substr(0, slashPos);
|
||||
|
||||
// strip inline hash comments (but leave full-line # for headers)
|
||||
const auto hashPos = line.find('#');
|
||||
if (hashPos != std::string::npos && hashPos > 0)
|
||||
line = line.substr(0, hashPos);
|
||||
|
||||
trim(line);
|
||||
if (line.empty() || line[0] == '#') continue;
|
||||
|
||||
if (line.find("@flag offset_shift ") == 0) {
|
||||
const std::string offsetStr = line.substr(19);
|
||||
offset = (offsetStr.find("0x") == 0 ? std::strtol(offsetStr.c_str(), nullptr, 16) : std::strtol(offsetStr.c_str(), nullptr, 10)) - 0x100;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.find("@enabled") == 0) {
|
||||
enabled = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.find("@disabled") == 0) {
|
||||
enabled = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.find("@stop") == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!enabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
spacePos = line.find(' ');
|
||||
if (spacePos == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
|
||||
addrStr = line.substr(0, spacePos);
|
||||
valStr = line.substr(spacePos + 1);
|
||||
|
||||
if (addrStr.find_first_not_of("0123456789abcdefABCDEF") != std::string::npos || valStr.find_first_not_of("0123456789abcdefABCDEF") != std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
|
||||
codeOffset = std::strtol(valStr.c_str(), nullptr, 16) + offset;
|
||||
snprintf(offsetBuffer, sizeof(offsetBuffer), "%08X", codeOffset);
|
||||
cheatLine = CHEAT_TYPE + " " + addrStr + " " + hexToReversedHex(offsetBuffer);
|
||||
|
||||
#if !USING_FSTREAM_DIRECTIVE
|
||||
// Check if cheat already exists
|
||||
FILE* checkFile = fopen(cheatFilePath.c_str(), "r");
|
||||
bool exists = false;
|
||||
if (checkFile) {
|
||||
char checkLine[256];
|
||||
while (fgets(checkLine, sizeof(checkLine), checkFile)) {
|
||||
if (std::string(checkLine) == cheatLine + "\n") {
|
||||
exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
fclose(checkFile);
|
||||
}
|
||||
|
||||
if (!exists) {
|
||||
fprintf(outCheatFile, "%s\n", cheatLine.c_str());
|
||||
validCheatsProcessed++; // ADDED: Increment counter for new cheats
|
||||
}
|
||||
#else
|
||||
if (!cheatExists(cheatFilePath, cheatLine)) {
|
||||
outCheatFile << cheatLine << "\n";
|
||||
validCheatsProcessed++; // ADDED: Increment counter for new cheats
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !USING_FSTREAM_DIRECTIVE
|
||||
fclose(outCheatFile);
|
||||
#else
|
||||
outCheatFile.close();
|
||||
#endif
|
||||
|
||||
// ADDED: Check if any valid cheats were processed
|
||||
if (validCheatsProcessed == 0) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Warning: No valid patch data found to convert to cheats in " + pchtxtPath);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Corrected helper function to convert values to big-endian format
|
||||
uint32_t toBigEndian(uint32_t value) {
|
||||
return ((value & 0x000000FF) << 24) |
|
||||
((value & 0x0000FF00) << 8) |
|
||||
((value & 0x00FF0000) >> 8) |
|
||||
((value & 0xFF000000) >> 24);
|
||||
}
|
||||
|
||||
uint16_t toBigEndian(uint16_t value) {
|
||||
return ((value & 0x00FF) << 8) |
|
||||
((value & 0xFF00) >> 8);
|
||||
}
|
||||
|
||||
// Helper function to convert a vector of bytes to a hex string for logging
|
||||
|
||||
std::string hexToString(const std::vector<uint8_t>& bytes) {
|
||||
StringStream oss; // Use your custom StringStream
|
||||
oss.hex(); // Enable hex mode for the stream
|
||||
|
||||
for (uint8_t byte : bytes) {
|
||||
if (byte < 0x10) {
|
||||
oss << "0"; // Append leading zero for single-digit hex values
|
||||
}
|
||||
oss << static_cast<int>(byte); // Convert byte to int and then append it
|
||||
}
|
||||
|
||||
return oss.str(); // Return the final hex string
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Converts a .pchtxt file to an IPS file using fstream.
|
||||
*
|
||||
* This function reads the contents of a .pchtxt file, extracts the address-value pairs,
|
||||
* and generates an IPS file with the provided output folder.
|
||||
*
|
||||
* @param pchtxtPath The file path to the .pchtxt file.
|
||||
* @param outputFolder The folder path for the output IPS file.
|
||||
* @return True if the conversion was successful, false otherwise.
|
||||
*/
|
||||
bool pchtxt2ips(const std::string& pchtxtPath, const std::string& outputFolder) {
|
||||
#if !USING_FSTREAM_DIRECTIVE
|
||||
// Use FILE* for reading
|
||||
FILE* pchtxtFile = fopen(pchtxtPath.c_str(), "r");
|
||||
if (!pchtxtFile) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Error: Unable to open file " + pchtxtPath);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::pair<uint32_t, std::vector<uint8_t>>> patches;
|
||||
char lineBuffer[512]; // Fixed: Use char buffer for fgets
|
||||
std::string line;
|
||||
uint32_t lineNum = 0;
|
||||
std::string nsobid;
|
||||
int offset = 0; // Default offset
|
||||
bool enabled = true;
|
||||
|
||||
uint32_t address;
|
||||
uint8_t byte;
|
||||
std::vector<uint8_t> valueBytes;
|
||||
std::string offsetStr;
|
||||
|
||||
while (fgets(lineBuffer, sizeof(lineBuffer), pchtxtFile) != nullptr) {
|
||||
line = lineBuffer; // Convert to string
|
||||
++lineNum;
|
||||
if (line.empty() || line.front() == '@' || !enabled) {
|
||||
if (line.find("@nsobid-") == 0) {
|
||||
nsobid = line.substr(8);
|
||||
}
|
||||
if (line.find("@flag offset_shift ") == 0) {
|
||||
offsetStr = line.substr(19);
|
||||
offset = (offsetStr.find("0x") == 0 ? std::strtol(offsetStr.c_str(), nullptr, 16) : std::strtol(offsetStr.c_str(), nullptr, 10));
|
||||
}
|
||||
if (line.find("@enabled") == 0) {
|
||||
enabled = true;
|
||||
continue;
|
||||
}
|
||||
if (line.find("@disabled") == 0) {
|
||||
enabled = false;
|
||||
continue;
|
||||
}
|
||||
if (line.find("@stop") == 0) {
|
||||
break;
|
||||
}
|
||||
continue; // Skip empty lines and lines starting with '@'
|
||||
}
|
||||
|
||||
StringStream iss(line);
|
||||
std::string addressStr, valueStr;
|
||||
|
||||
if (!(iss >> addressStr >> valueStr)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
char* endPtr;
|
||||
address = std::strtoul(addressStr.c_str(), &endPtr, 16) + offset; // Adjust address by offset
|
||||
if (*endPtr != '\0') {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < valueStr.length(); i += 2) {
|
||||
byte = ult::stoi(valueStr.substr(i, 2), nullptr, 16);
|
||||
valueBytes.push_back(byte);
|
||||
}
|
||||
|
||||
if (valueBytes.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
patches.push_back(std::make_pair(address, valueBytes));
|
||||
valueBytes.clear();
|
||||
}
|
||||
|
||||
fclose(pchtxtFile);
|
||||
|
||||
#else
|
||||
// Use fstream for reading
|
||||
std::ifstream pchtxtFile(pchtxtPath);
|
||||
if (!pchtxtFile) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Error: Unable to open file " + pchtxtPath);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::pair<uint32_t, std::vector<uint8_t>>> patches;
|
||||
std::string line;
|
||||
uint32_t lineNum = 0;
|
||||
std::string nsobid;
|
||||
int offset = 0; // Default offset
|
||||
|
||||
uint32_t address;
|
||||
uint8_t byte;
|
||||
std::vector<uint8_t> valueBytes;
|
||||
std::string offsetStr;
|
||||
|
||||
while (std::getline(pchtxtFile, line)) {
|
||||
++lineNum;
|
||||
if (line.empty() || line.front() == '@') {
|
||||
if (line.find("@nsobid-") == 0) {
|
||||
nsobid = line.substr(8);
|
||||
}
|
||||
if (line.find("@flag offset_shift ") == 0) {
|
||||
offsetStr = line.substr(19);
|
||||
offset = (offsetStr.find("0x") == 0 ? std::strtol(offsetStr.c_str(), nullptr, 16) : std::strtol(offsetStr.c_str(), nullptr, 10));
|
||||
}
|
||||
if (line.find("@stop") == 0) {
|
||||
break;
|
||||
}
|
||||
continue; // Skip empty lines and lines starting with '@'
|
||||
}
|
||||
|
||||
StringStream iss(line);
|
||||
std::string addressStr, valueStr;
|
||||
|
||||
if (!(iss >> addressStr >> valueStr)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
char* endPtr;
|
||||
address = std::strtoul(addressStr.c_str(), &endPtr, 16) + offset; // Adjust address by offset
|
||||
if (*endPtr != '\0') {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < valueStr.length(); i += 2) {
|
||||
byte = ult::stoi(valueStr.substr(i, 2), nullptr, 16);
|
||||
valueBytes.push_back(byte);
|
||||
}
|
||||
|
||||
if (valueBytes.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
patches.push_back(std::make_pair(address, valueBytes));
|
||||
valueBytes.clear();
|
||||
}
|
||||
|
||||
pchtxtFile.close();
|
||||
#endif
|
||||
|
||||
// CHECK: Return false if no patches were found
|
||||
if (patches.empty()) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Warning: No valid patches found in " + pchtxtPath);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
if (nsobid.empty()) {
|
||||
nsobid = pchtxtPath.substr(pchtxtPath.find_last_of("/\\") + 1);
|
||||
nsobid = nsobid.substr(0, nsobid.find_last_of("."));
|
||||
}
|
||||
|
||||
// Trim any newline characters from nsobid
|
||||
trim(nsobid);
|
||||
trimNewline(nsobid);
|
||||
|
||||
const std::string ipsFileName = nsobid + ".ips";
|
||||
const std::string ipsFilePath = outputFolder + ipsFileName;
|
||||
|
||||
#if !USING_FSTREAM_DIRECTIVE
|
||||
// Use FILE* for writing
|
||||
FILE* ipsFile = fopen(ipsFilePath.c_str(), "wb");
|
||||
if (!ipsFile) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Error: Unable to create IPS file " + ipsFilePath);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
fwrite(IPS32_HEAD_MAGIC, sizeof(char), std::strlen(IPS32_HEAD_MAGIC), ipsFile);
|
||||
|
||||
uint16_t valueLength;
|
||||
uint32_t bigEndianAddress;
|
||||
|
||||
for (const auto& patch : patches) {
|
||||
bigEndianAddress = toBigEndian(patch.first); // Convert address to big-endian
|
||||
fwrite(&bigEndianAddress, sizeof(bigEndianAddress), 1, ipsFile); // Write address
|
||||
|
||||
valueLength = toBigEndian(static_cast<uint16_t>(patch.second.size())); // Convert length to big-endian
|
||||
fwrite(&valueLength, sizeof(valueLength), 1, ipsFile); // Write length of value
|
||||
|
||||
fwrite(patch.second.data(), sizeof(uint8_t), patch.second.size(), ipsFile); // Write value
|
||||
}
|
||||
|
||||
fwrite(IPS32_FOOT_MAGIC, sizeof(char), std::strlen(IPS32_FOOT_MAGIC), ipsFile);
|
||||
fclose(ipsFile);
|
||||
|
||||
|
||||
FILE* _pchtxtFile = fopen(pchtxtPath.c_str(), "r");
|
||||
if (!_pchtxtFile) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Error: Unable to open file " + pchtxtPath);
|
||||
#endif
|
||||
return false; // Changed: Return false if we can't read the file for title ID
|
||||
}
|
||||
|
||||
// Read the entire file into a string
|
||||
std::string pchtxt;
|
||||
char buffer[4096];
|
||||
while (fgets(buffer, sizeof(buffer), _pchtxtFile)) {
|
||||
pchtxt += buffer;
|
||||
}
|
||||
fclose(_pchtxtFile);
|
||||
|
||||
|
||||
const std::string tid = findTitleID(pchtxt);
|
||||
if (!tid.empty()) {
|
||||
const std::string tidFilePath = outputFolder + tid;
|
||||
FILE* tidFile = fopen(tidFilePath.c_str(), "w");
|
||||
if (tidFile) {
|
||||
fclose(tidFile); // Creates an empty file
|
||||
}
|
||||
} else {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Warning: Could not find Title ID in " + pchtxtPath);
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
// Use fstream for writing
|
||||
std::ofstream ipsFile(ipsFilePath, std::ios::binary);
|
||||
if (!ipsFile) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Error: Unable to create IPS file " + ipsFilePath);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
ipsFile.write(IPS32_HEAD_MAGIC, std::strlen(IPS32_HEAD_MAGIC));
|
||||
|
||||
uint16_t valueLength;
|
||||
uint32_t bigEndianAddress;
|
||||
for (const auto& patch : patches) {
|
||||
bigEndianAddress = toBigEndian(patch.first); // Convert address to big-endian
|
||||
ipsFile.write(reinterpret_cast<const char*>(&bigEndianAddress), sizeof(bigEndianAddress)); // Write address
|
||||
|
||||
valueLength = toBigEndian(static_cast<uint16_t>(patch.second.size())); // Convert length to big-endian
|
||||
ipsFile.write(reinterpret_cast<const char*>(&valueLength), sizeof(valueLength)); // Write length of value
|
||||
|
||||
ipsFile.write(reinterpret_cast<const char*>(patch.second.data()), patch.second.size()); // Write value
|
||||
}
|
||||
|
||||
ipsFile.write(IPS32_FOOT_MAGIC, std::strlen(IPS32_FOOT_MAGIC));
|
||||
ipsFile.close();
|
||||
|
||||
|
||||
std::ifstream _pchtxtFile(pchtxtPath);
|
||||
if (!_pchtxtFile) {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Error: Unable to open file " + pchtxtPath);
|
||||
#endif
|
||||
return false; // Changed: Return false if we can't read the file for title ID
|
||||
}
|
||||
|
||||
std::string pchtxt((std::istreambuf_iterator<char>(_pchtxtFile)), std::istreambuf_iterator<char>());
|
||||
|
||||
const std::string tid = findTitleID(pchtxt);
|
||||
if (!tid.empty()) {
|
||||
const std::string tidFilePath = outputFolder + tid;
|
||||
std::ofstream tidFile(tidFilePath);
|
||||
tidFile.close(); // Creates an empty file
|
||||
} else {
|
||||
#if USING_LOGGING_DIRECTIVE
|
||||
if (!disableLogging)
|
||||
logMessage("Warning: Could not find Title ID in " + pchtxtPath);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,609 @@
|
||||
/********************************************************************************
|
||||
* File: string_funcs.cpp
|
||||
* Author: ppkantorski
|
||||
* Description:
|
||||
* This source file provides implementations for the string manipulation
|
||||
* functions declared in string_funcs.hpp. These utility functions support
|
||||
* operations such as trimming whitespace, removing quotes, normalizing
|
||||
* slashes, and performing other string cleanup tasks used throughout
|
||||
* the Ultrahand Overlay project.
|
||||
*
|
||||
* For the latest updates and contributions, visit the project's GitHub repository.
|
||||
* (GitHub Repository: https://github.com/ppkantorski/Ultrahand-Overlay)
|
||||
*
|
||||
* Note: Please be aware that this notice cannot be altered or removed. It is a part
|
||||
* of the project's documentation and must remain intact.
|
||||
*
|
||||
* Licensed under both GPLv2 and CC-BY-4.0
|
||||
* Copyright (c) 2024 ppkantorski
|
||||
********************************************************************************/
|
||||
|
||||
#include "string_funcs.hpp"
|
||||
|
||||
namespace ult {
|
||||
|
||||
|
||||
// Custom string conversion methods in place of std::
|
||||
std::string to_string(int value) {
|
||||
char buffer[12]; // Sufficient for 32-bit int
|
||||
snprintf(buffer, sizeof(buffer), "%d", value);
|
||||
return std::string(buffer);
|
||||
}
|
||||
|
||||
int stoi(const std::string& str, std::size_t* pos, int base) {
|
||||
char* end;
|
||||
const long result = std::strtol(str.c_str(), &end, base);
|
||||
|
||||
if (pos) {
|
||||
*pos = end - str.c_str(); // Set the position to the last character processed
|
||||
}
|
||||
|
||||
// Handle out-of-range or conversion issues here if needed
|
||||
return static_cast<int>(result);
|
||||
}
|
||||
|
||||
|
||||
float stof(const std::string& str) {
|
||||
return strtof(str.c_str(), nullptr);
|
||||
}
|
||||
|
||||
|
||||
// Mimics std::getline() with a delimiter
|
||||
bool StringStream::getline(std::string& output, char delimiter) {
|
||||
if (position >= data.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const size_t nextPos = data.find(delimiter, position);
|
||||
|
||||
if (nextPos != std::string::npos) {
|
||||
output.assign(data, position, nextPos - position); // No temporary string creation
|
||||
position = nextPos + 1;
|
||||
} else {
|
||||
output.assign(data, position, data.size() - position); // No temporary string creation
|
||||
position = data.size();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Mimics operator >> to split by whitespace
|
||||
StringStream& StringStream::operator>>(std::string& output) {
|
||||
// Skip leading whitespace
|
||||
while (position < data.size() && std::isspace(data[position])) {
|
||||
++position;
|
||||
}
|
||||
|
||||
if (position >= data.size()) {
|
||||
output.clear();
|
||||
validState = false;
|
||||
return *this;
|
||||
}
|
||||
|
||||
size_t nextPos = position;
|
||||
while (nextPos < data.size() && !std::isspace(data[nextPos])) {
|
||||
++nextPos;
|
||||
}
|
||||
|
||||
output.assign(data, position, nextPos - position); // Replace substr() with assign()
|
||||
position = nextPos;
|
||||
|
||||
validState = true;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Overload << operator for std::string
|
||||
StringStream& StringStream::operator<<(const std::string& input) {
|
||||
data += input;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Overload << operator for const char*
|
||||
StringStream& StringStream::operator<<(const char* input) {
|
||||
data += input;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Overload << operator for char
|
||||
StringStream& StringStream::operator<<(char input) {
|
||||
data += input;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Overload << operator for int (handles hex mode)
|
||||
StringStream& StringStream::operator<<(int input) {
|
||||
if (hexMode) {
|
||||
char buffer[20]; // Buffer large enough for hex conversion
|
||||
sprintf(buffer, "%x", input); // Convert integer to hex string
|
||||
data += buffer;
|
||||
} else {
|
||||
data += ult::to_string(input);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Define the new overload for long long
|
||||
StringStream& StringStream::operator<<(long long input) {
|
||||
data += std::to_string(input);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Return the current buffer content
|
||||
std::string StringStream::str() const {
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Trims leading and trailing whitespaces from a string.
|
||||
*
|
||||
* This function removes leading and trailing whitespaces, tabs, newlines, carriage returns, form feeds,
|
||||
* and vertical tabs from the input string.
|
||||
*
|
||||
* @param str The input string to trim.
|
||||
* @return The trimmed string.
|
||||
*/
|
||||
void trim(std::string& str) {
|
||||
const size_t first = str.find_first_not_of(" \t\n\r\f\v");
|
||||
if (first == std::string::npos) {
|
||||
str.clear(); // Fix: clear all-whitespace strings
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t last = str.find_last_not_of(" \t\n\r\f\v");
|
||||
|
||||
// True in-place modification - no temporary string creation
|
||||
if (last + 1 < str.length()) {
|
||||
str.erase(last + 1); // Remove trailing whitespace
|
||||
}
|
||||
if (first > 0) {
|
||||
str.erase(0, first); // Remove leading whitespace
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Function to trim newline characters from the end of a string
|
||||
void trimNewline(std::string& str) {
|
||||
const size_t end = str.find_last_not_of("\n");
|
||||
if (end == std::string::npos) {
|
||||
str.clear(); // If the string consists entirely of newlines, clear it
|
||||
} else {
|
||||
str.erase(end + 1); // Remove all characters after the last non-newline character
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Removes all white spaces from a string.
|
||||
*
|
||||
* This function removes all white spaces, including spaces, tabs, newlines, carriage returns, form feeds,
|
||||
* and vertical tabs from the input string.
|
||||
*
|
||||
* @param str The input string to remove white spaces from.
|
||||
* @return The string with white spaces removed.
|
||||
*/
|
||||
std::string removeWhiteSpaces(const std::string& str) {
|
||||
std::string result;
|
||||
result.reserve(str.size()); // Reserve space for the result to avoid reallocations
|
||||
|
||||
std::remove_copy_if(str.begin(), str.end(), std::back_inserter(result), [](unsigned char c) {
|
||||
return std::isspace(c);
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Removes quotes from a string.
|
||||
*
|
||||
* This function removes single and double quotes from the beginning and end of the input string.
|
||||
*
|
||||
* @param str The input string to remove quotes from.
|
||||
* @return The string with quotes removed.
|
||||
*/
|
||||
void removeQuotes(std::string& str) {
|
||||
if (str.size() >= 2) {
|
||||
const char front = str[0];
|
||||
const char back = str[str.size() - 1];
|
||||
if ((front == '\'' && back == '\'') || (front == '"' && back == '"')) {
|
||||
str.erase(0, 1);
|
||||
str.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Replaces multiple consecutive slashes with a single slash in a string.
|
||||
*
|
||||
* This function replaces sequences of two or more consecutive slashes with a single slash in the input string.
|
||||
*
|
||||
* @param input The input string to process.
|
||||
* @return The string with multiple slashes replaced.
|
||||
*/
|
||||
std::string replaceMultipleSlashes(const std::string& input) {
|
||||
std::string output;
|
||||
output.reserve(input.size()); // Reserve space for the output string
|
||||
|
||||
bool previousSlash = false;
|
||||
for (char c : input) {
|
||||
if (c == '/') {
|
||||
if (!previousSlash) {
|
||||
output.push_back(c);
|
||||
}
|
||||
previousSlash = true;
|
||||
} else {
|
||||
output.push_back(c);
|
||||
previousSlash = false;
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Preprocesses a path string by replacing multiple slashes and adding "sdmc:" prefix.
|
||||
*
|
||||
* This function preprocesses a path string by removing multiple consecutive slashes,
|
||||
* resolving relative path components (. and ..), adding the "sdmc:" prefix if not present,
|
||||
* and modifying the input string in place.
|
||||
*
|
||||
* @param path The input path string to preprocess, passed by reference.
|
||||
* @param packagePath The base package path to resolve relative paths against.
|
||||
*/
|
||||
void preprocessPath(std::string& path, const std::string& packagePath) {
|
||||
removeQuotes(path);
|
||||
|
||||
// In-place multiple slash removal - no temporary string creation
|
||||
if (!path.empty()) {
|
||||
size_t writePos = 0;
|
||||
bool previousSlash = false;
|
||||
|
||||
for (size_t i = 0; i < path.length(); ++i) {
|
||||
if (path[i] == '/') {
|
||||
if (!previousSlash) {
|
||||
path[writePos++] = path[i];
|
||||
}
|
||||
previousSlash = true;
|
||||
} else {
|
||||
path[writePos++] = path[i];
|
||||
previousSlash = false;
|
||||
}
|
||||
}
|
||||
path.resize(writePos);
|
||||
}
|
||||
|
||||
// First handle "./" replacement if present
|
||||
if (!packagePath.empty() && path.length() >= 2 && path[0] == '.' && path[1] == '/') {
|
||||
// Handle "./" - replace with packagePath
|
||||
path.replace(0, 2, packagePath);
|
||||
}
|
||||
|
||||
size_t dotDotPos, lastSlash;
|
||||
std::string beforeDotDot;
|
||||
|
||||
// Then handle any "../" sequences that may exist anywhere in the path
|
||||
while (!path.empty()) {
|
||||
dotDotPos = path.find("../");
|
||||
if (dotDotPos == std::string::npos) {
|
||||
break; // No more "../" sequences found
|
||||
}
|
||||
|
||||
// Found "../" sequence at dotDotPos
|
||||
beforeDotDot = path.substr(0, dotDotPos);
|
||||
|
||||
// Remove trailing slash from the part before "../"
|
||||
if (!beforeDotDot.empty() && beforeDotDot.back() == '/') {
|
||||
beforeDotDot.pop_back();
|
||||
}
|
||||
|
||||
// Go up one level
|
||||
lastSlash = beforeDotDot.find_last_of('/');
|
||||
if (lastSlash != std::string::npos) {
|
||||
beforeDotDot = beforeDotDot.substr(0, lastSlash + 1);
|
||||
} else {
|
||||
// No slash found, we're at root level
|
||||
beforeDotDot = "/";
|
||||
}
|
||||
|
||||
// Replace the path up to and including this "../" with the resolved path
|
||||
path = beforeDotDot + path.substr(dotDotPos + 3);
|
||||
}
|
||||
|
||||
// Direct character comparison instead of substr() for sdmc: prefix
|
||||
if (path.length() < 5 ||
|
||||
path[0] != 's' || path[1] != 'd' || path[2] != 'm' || path[3] != 'c' || path[4] != ':') {
|
||||
path.insert(0, "sdmc:");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Preprocesses a URL string by adding "https://" prefix.
|
||||
*
|
||||
* This function preprocesses a URL string by adding the "https://" prefix if not already present.
|
||||
*
|
||||
* @param path The input URL string to preprocess, passed by reference and modified in-place.
|
||||
*/
|
||||
void preprocessUrl(std::string& path) {
|
||||
removeQuotes(path);
|
||||
if ((path.compare(0, 7, "http://") == 0) || (path.compare(0, 8, "https://") == 0)) {
|
||||
return; // No need to modify the string if it already has a prefix
|
||||
} else {
|
||||
path = "https://" + path; // Prepend "https://"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Drops the file extension from a filename.
|
||||
*
|
||||
* This function removes the file extension (characters after the last dot) from the input filename string.
|
||||
*
|
||||
* @param filename The input filename from which to drop the extension, passed by reference and modified in-place.
|
||||
*/
|
||||
void dropExtension(std::string& filename) {
|
||||
const size_t lastDotPos = filename.find_last_of('.'); // Single char instead of string
|
||||
if (lastDotPos != std::string::npos) {
|
||||
filename.resize(lastDotPos);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if a string starts with a given prefix.
|
||||
*
|
||||
* This function checks if the input string starts with the specified prefix.
|
||||
*
|
||||
* @param str The input string to check.
|
||||
* @param prefix The prefix to check for.
|
||||
* @return True if the string starts with the prefix, false otherwise.
|
||||
*/
|
||||
bool startsWith(const std::string& str, const std::string& prefix) {
|
||||
return str.compare(0, prefix.length(), prefix) == 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Helper function to check if a string is a valid integer
|
||||
bool isValidNumber(const std::string& str) {
|
||||
if (str.empty() || ((str[0] != '-') && !std::isdigit(str[0])) || (str[0] == '-' && str.size() == 1)) {
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 1; i < str.size(); ++i) {
|
||||
if (!std::isdigit(str[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string returnOrNull(const std::string& value) {
|
||||
return value.empty() ? NULL_STR : value;
|
||||
}
|
||||
|
||||
|
||||
// Function to slice a string from start to end index
|
||||
std::string sliceString(const std::string& str, size_t start, size_t end) {
|
||||
if (start < 0) start = 0;
|
||||
if (end > static_cast<size_t>(str.length())) end = str.length();
|
||||
if (start > end) start = end;
|
||||
return str.substr(start, end - start);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//std::string addQuotesIfNeeded(const std::string& str) {
|
||||
// if (str.find(' ') != std::string::npos) {
|
||||
// return "\"" + str + "\"";
|
||||
// }
|
||||
// return str;
|
||||
//}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Converts a string to lowercase.
|
||||
*
|
||||
* This function takes a string as input and returns a lowercase version of that string.
|
||||
*
|
||||
* @param str The input string to convert to lowercase.
|
||||
* @return The lowercase version of the input string.
|
||||
*/
|
||||
|
||||
std::string stringToLowercase(const std::string& str) {
|
||||
std::string result = str;
|
||||
//std::transform(result.begin(), result.end(), result.begin(),
|
||||
// [](unsigned char c) { return std::tolower(c); });
|
||||
//return result;
|
||||
for (char& c : result) {
|
||||
if (c >= 'A' && c <= 'Z') {
|
||||
c += 32;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Converts a string to uppercase.
|
||||
*
|
||||
* This function takes a string as input and returns an uppercase version of that string.
|
||||
*
|
||||
* @param str The input string to convert to uppercase.
|
||||
* @return The uppercase version of the input string.
|
||||
*/
|
||||
|
||||
std::string stringToUppercase(const std::string& str) {
|
||||
std::string result = str;
|
||||
for (char& c : result) {
|
||||
if (c >= 'a' && c <= 'z') {
|
||||
c -= 32;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Formats a priority string to a desired width.
|
||||
*
|
||||
* This function takes a priority string and formats it to a specified desired width by padding with '0's if it's shorter
|
||||
* or truncating with '9's if it's longer.
|
||||
*
|
||||
* @param priority The input priority string to format.
|
||||
* @param desiredWidth The desired width of the formatted string (default is 4).
|
||||
* @return A formatted priority string.
|
||||
*/
|
||||
std::string formatPriorityString(const std::string& priority, int desiredWidth) {
|
||||
const int priorityLength = priority.length();
|
||||
|
||||
if (priorityLength > desiredWidth) {
|
||||
// FASTEST: Single allocation with direct fill
|
||||
return std::string(desiredWidth, '9');
|
||||
} else {
|
||||
// FASTEST: Single allocation + direct memory copy
|
||||
std::string result(desiredWidth, '0'); // Pre-fill with zeros
|
||||
memcpy(&result[desiredWidth - priorityLength], priority.data(), priorityLength);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Removes the part of the string after the first occurrence of '?' character.
|
||||
*
|
||||
* This function takes a string and removes the portion of the string that appears after
|
||||
* the first '?' character, if found. If no '?' character is present, the string remains unchanged.
|
||||
*
|
||||
* @param input The input string from which to remove the tag, passed by reference and modified in-place.
|
||||
*/
|
||||
void removeTag(std::string &input) {
|
||||
const size_t pos = input.find('?');
|
||||
if (pos != std::string::npos) {
|
||||
input.resize(pos); // Modify the string in-place to remove everything after the '?'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::string getFirstLongEntry(const std::string& input, size_t minLength) {
|
||||
StringStream iss(input); // Use custom StringStream
|
||||
std::string word;
|
||||
|
||||
// Split the input string based on spaces and get the first word
|
||||
if (iss >> word) {
|
||||
// Check if the first word's length is greater than the specified length
|
||||
if (word.length() > minLength) {
|
||||
return word;
|
||||
}
|
||||
}
|
||||
|
||||
// Return an empty string if the first word is not longer than minLength
|
||||
return input;
|
||||
}
|
||||
|
||||
|
||||
// This will take a string like "v1.3.5-abasdfasdfa" and output "1.3.5". string could also look like "test-1.3.5-1" or "v1.3.5" and we will only want "1.3.5"
|
||||
std::string cleanVersionLabel(const std::string& input) {
|
||||
std::string result;
|
||||
result.reserve(input.size());
|
||||
|
||||
size_t start = 0;
|
||||
|
||||
// Find the start of the version number (first digit)
|
||||
while (start < input.size() && !std::isdigit(input[start])) {
|
||||
start++;
|
||||
}
|
||||
|
||||
if (start == input.size()) {
|
||||
return ""; // No digits found
|
||||
}
|
||||
|
||||
|
||||
// Extract version number with dots and plus signs
|
||||
for (size_t i = start; i < input.size(); ++i) {
|
||||
const char c = input[i];
|
||||
if (std::isdigit(c) || c == '.' || c == '+') {
|
||||
result += c;
|
||||
} else {
|
||||
break; // Stop at first character that's not digit, dot, or plus
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
std::string extractTitle(const std::string& input) {
|
||||
const size_t spacePos = input.find(' '); // Find the position of the first space
|
||||
|
||||
if (spacePos != std::string::npos) {
|
||||
// Extract the substring before the first space
|
||||
return input.substr(0, spacePos);
|
||||
} else {
|
||||
// If no space is found, return the original string
|
||||
return input;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::vector<std::string> splitString(const std::string& str, const std::string& delimiter) {
|
||||
std::vector<std::string> tokens;
|
||||
|
||||
// OPTIMIZATION: Pre-allocate space to avoid reallocations
|
||||
tokens.reserve(str.length() / (delimiter.length() + 1) + 1);
|
||||
|
||||
size_t start = 0;
|
||||
size_t end = str.find(delimiter);
|
||||
|
||||
while (end != std::string::npos) {
|
||||
// OPTIMIZATION: Direct construction instead of substr() - no temporary string
|
||||
tokens.emplace_back(str, start, end - start);
|
||||
start = end + delimiter.length();
|
||||
end = str.find(delimiter, start);
|
||||
}
|
||||
|
||||
// OPTIMIZATION: Direct construction for last token
|
||||
tokens.emplace_back(str, start);
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
|
||||
// Function to split a string by a delimiter and return a specific index
|
||||
std::string splitStringAtIndex(const std::string& str, const std::string& delimiter, size_t index) {
|
||||
const std::vector<std::string> tokens = splitString(str, delimiter);
|
||||
|
||||
if (index < tokens.size()) {
|
||||
return tokens[index];
|
||||
} else {
|
||||
return ""; // Return empty string if index is out of bounds
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::string customAlign(int number) {
|
||||
const std::string numStr = ult::to_string(number);
|
||||
const int paddingSpaces = (4 - numStr.length()) * 2;
|
||||
|
||||
// FASTEST: Single allocation + direct memory operations
|
||||
std::string result(paddingSpaces + numStr.length(), ' ');
|
||||
memcpy(&result[paddingSpaces], numStr.data(), numStr.length());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//#if IS_LAUNCHER_DIRECTIVE
|
||||
//std::string inputExists(const std::string& input) {
|
||||
// std::string e;
|
||||
// for (char c : input) {
|
||||
// e += (c + 5);
|
||||
// }
|
||||
// return e;
|
||||
//}
|
||||
//#endif
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user