This commit is contained in:
souldbminersmwc
2025-12-23 19:10:41 -05:00
parent 152f92a9a9
commit b5022ea839
43 changed files with 5143 additions and 3939 deletions

View File

@@ -1,5 +1,5 @@
/********************************************************************************
* File: audio_player.cpp
* File: audio.cpp
* Author: ppkantorski
* Description:
* Memory-optimized version with reduced allocation overhead and chunked I/O.
@@ -19,17 +19,17 @@
* Copyright (c) 2025 ppkantorski
********************************************************************************/
#include "audio_player.hpp"
#include "audio.hpp"
namespace ult {
bool AudioPlayer::m_initialized = false;
std::atomic<bool> AudioPlayer::m_enabled{true};
float AudioPlayer::m_masterVolume = 0.6f;
bool AudioPlayer::m_lastDockedState = false;
std::vector<AudioPlayer::CachedSound> AudioPlayer::m_cachedSounds;
std::mutex AudioPlayer::m_audioMutex;
bool Audio::m_initialized = false;
std::atomic<bool> Audio::m_enabled{true};
float Audio::m_masterVolume = 0.6f;
bool Audio::m_lastDockedState = false;
std::vector<Audio::CachedSound> Audio::m_cachedSounds;
std::mutex Audio::m_audioMutex;
bool AudioPlayer::initialize() {
bool Audio::initialize() {
std::lock_guard<std::mutex> lock(m_audioMutex);
if (m_initialized) return true;
@@ -41,13 +41,13 @@ namespace ult {
m_initialized = true;
m_cachedSounds.resize(static_cast<uint32_t>(SoundType::Count));
m_lastDockedState = isDocked();
m_lastDockedState = ult::consoleIsDocked();
reloadAllSounds();
return true;
}
void AudioPlayer::exit() {
void Audio::exit() {
std::lock_guard<std::mutex> lock(m_audioMutex);
// Free all cached sound buffers
@@ -67,24 +67,24 @@ namespace ult {
}
}
void AudioPlayer::reloadAllSounds() {
void Audio::reloadAllSounds() {
for (uint32_t i = 0; i < static_cast<uint32_t>(SoundType::Count); ++i) {
loadSoundFromWav(static_cast<SoundType>(i), m_soundPaths[i]);
}
}
void AudioPlayer::unloadAllSounds(const std::initializer_list<SoundType>& excludeSounds) {
void Audio::unloadAllSounds(const std::initializer_list<SoundType>& excludeSounds) {
std::lock_guard<std::mutex> lock(m_audioMutex);
if (!m_initialized) return;
for (uint32_t i = 0; i < m_cachedSounds.size(); ++i) {
SoundType current = static_cast<SoundType>(i);
// Skip if this sound is in the exclude list
if (std::find(excludeSounds.begin(), excludeSounds.end(), current) != excludeSounds.end()) {
continue;
}
auto& cached = m_cachedSounds[i];
if (cached.buffer) {
free(cached.buffer);
@@ -95,10 +95,10 @@ namespace ult {
}
}
bool AudioPlayer::reloadIfDockedChanged() {
bool Audio::reloadIfDockedChanged() {
if (!m_initialized) return false;
const bool currentDocked = isDocked();
const bool currentDocked = ult::consoleIsDocked();
if (currentDocked == m_lastDockedState) return false;
std::lock_guard<std::mutex> lock(m_audioMutex);
@@ -108,28 +108,28 @@ namespace ult {
return true;
}
bool AudioPlayer::loadSoundFromWav(SoundType type, const char* path) {
bool Audio::loadSoundFromWav(SoundType type, const char* path) {
const uint32_t idx = static_cast<uint32_t>(type);
if (!m_initialized || idx >= static_cast<uint32_t>(SoundType::Count)) return false;
// Free existing buffer
free(m_cachedSounds[idx].buffer);
m_cachedSounds[idx] = { nullptr, 0, 0 };
FILE* f = fopen(path, "rb");
if (!f) return false;
// Parse WAV header
char hdr[12];
if (fread(hdr, 1, 12, f) != 12 || memcmp(hdr, "RIFF", 4) || memcmp(hdr + 8, "WAVE", 4)) {
fclose(f);
return false;
}
u16 fmt = 0, ch = 0, bits = 0;
u32 rate = 0, dSize = 0;
long dPos = 0;
// Find fmt and data chunks
while (fread(hdr, 1, 8, f) == 8) {
const u32 sz = *(u32*)(hdr + 4);
@@ -148,13 +148,13 @@ namespace ult {
fseek(f, sz, SEEK_CUR);
}
}
// Validate format
if (!dSize || fmt != 1 || ch == 0 || ch > 2 || (bits != 8 && bits != 16)) {
fclose(f);
return false;
}
// Calculate buffer sizes
// Note: audout REQUIRES stereo (2 channels), so we must duplicate mono
const bool mono = (ch == 1);
@@ -165,14 +165,14 @@ namespace ult {
// Use smaller alignment to reduce waste (256 bytes instead of 4KB)
const uint32_t align = 0x100;
const uint32_t bufSize = (outSize + align - 1) & ~(align - 1);
// Allocate output buffer
void* buf = aligned_alloc(align, bufSize);
if (!buf) {
fclose(f);
return false;
}
fseek(f, dPos, SEEK_SET);
s16* out = (s16*)buf;
@@ -182,7 +182,7 @@ namespace ult {
effectiveVolume *= 0.5f;
}
const float scale = std::clamp(effectiveVolume, 0.0f, 1.0f);
// Process audio in chunks to minimize memory usage
// This eliminates the need for temporary vectors
constexpr uint32_t CHUNK_SIZE = 512;
@@ -241,25 +241,25 @@ namespace ult {
remaining -= toRead;
}
}
fclose(f);
// Zero-fill any padding
if (outSize < bufSize) {
memset((u8*)buf + outSize, 0, bufSize - outSize);
}
m_cachedSounds[idx] = { buf, bufSize, outSize };
return true;
}
void AudioPlayer::playSound(SoundType type) {
void Audio::playSound(SoundType type) {
// Lock-free check - SAFE with atomic
if (!m_enabled.load(std::memory_order_relaxed)) return;
const uint32_t idx = static_cast<uint32_t>(type);
if (idx >= static_cast<uint32_t>(SoundType::Count)) return;
std::lock_guard<std::mutex> lock(m_audioMutex);
// Check again under lock
@@ -267,12 +267,12 @@ namespace ult {
auto& cached = m_cachedSounds[idx];
if (!cached.buffer) return;
// Release any finished buffers
AudioOutBuffer* releasedBuffers = nullptr;
u32 releasedCount = 0;
audoutGetReleasedAudioOutBuffer(&releasedBuffers, &releasedCount);
// Static buffer is safe with mutex protection
static AudioOutBuffer audioBuffer = {};
audioBuffer = {};
@@ -281,32 +281,32 @@ namespace ult {
audioBuffer.data_size = cached.dataSize;
audioBuffer.data_offset = 0;
audioBuffer.next = nullptr;
AudioOutBuffer* rel = nullptr;
audoutPlayBuffer(&audioBuffer, &rel);
}
void AudioPlayer::setMasterVolume(float v) {
void Audio::setMasterVolume(float v) {
std::lock_guard<std::mutex> lock(m_audioMutex);
m_masterVolume = std::clamp(v, 0.0f, 1.0f);
}
void AudioPlayer::setEnabled(bool e) {
void Audio::setEnabled(bool e) {
m_enabled.store(e, std::memory_order_relaxed);
}
bool AudioPlayer::isEnabled() {
bool Audio::isEnabled() {
return m_enabled.load(std::memory_order_relaxed);
}
bool AudioPlayer::isDocked() {
Result rc = apmInitialize();
if (R_FAILED(rc)) return false;
ApmPerformanceMode perfMode = ApmPerformanceMode_Invalid;
rc = apmGetPerformanceMode(&perfMode);
apmExit();
return R_SUCCEEDED(rc) && (perfMode == ApmPerformanceMode_Boost);
}
//bool Audio::isDocked() {
// Result rc = apmInitialize();
// if (R_FAILED(rc)) return false;
//
// ApmPerformanceMode perfMode = ApmPerformanceMode_Invalid;
// rc = apmGetPerformanceMode(&perfMode);
// apmExit();
//
// return R_SUCCEEDED(rc) && (perfMode == ApmPerformanceMode_Boost);
//}
}

View File

@@ -4,6 +4,15 @@
* Description:
* This source file contains the implementation of debugging functions for 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) 2023-2025 ppkantorski
********************************************************************************/
#include "debug_funcs.hpp"

View File

@@ -14,7 +14,7 @@
* of the project's documentation and must remain intact.
*
* Licensed under both GPLv2 and CC-BY-4.0
* Copyright (c) 2024 ppkantorski
* Copyright (c) 2023-2025 ppkantorski
********************************************************************************/
#include "download_funcs.hpp"
@@ -23,8 +23,8 @@
namespace ult {
// Base loader definitions
size_t DOWNLOAD_READ_BUFFER = 8*1024;//64 * 1024;//4096*10;
size_t DOWNLOAD_WRITE_BUFFER = 8*1024;//64 * 1024;
size_t DOWNLOAD_READ_BUFFER = 32*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;
@@ -131,7 +131,7 @@ int progressCallback(void *ptr, curl_off_t totalToDownload, curl_off_t nowDownlo
// }
//}
std::unique_ptr<char[]> globalWriteBuffer;
//std::unique_ptr<char[]> writeBuffer;
/**
* @brief Downloads a file from a URL to a specified destination.
@@ -192,12 +192,12 @@ bool downloadFile(const std::string& url, const std::string& toDestination, bool
}
// ADD THIS: Set up write buffer for better performance
//std::unique_ptr<char[]> globalWriteBuffer;
std::unique_ptr<char[]> writeBuffer;
if (DOWNLOAD_WRITE_BUFFER > 0) {
//if (!globalWriteBuffer)
globalWriteBuffer = std::make_unique<char[]>(DOWNLOAD_WRITE_BUFFER);
//if (!writeBuffer)
writeBuffer = std::make_unique<char[]>(DOWNLOAD_WRITE_BUFFER);
// _IOFBF = full buffering, _IOLBF = line buffering, _IONBF = no buffering
setvbuf(file.get(), globalWriteBuffer.get(), _IOFBF, DOWNLOAD_WRITE_BUFFER);
setvbuf(file.get(), writeBuffer.get(), _IOFBF, DOWNLOAD_WRITE_BUFFER);
}
//setvbuf(file.get(), NULL, _IOFBF, DOWNLOAD_WRITE_BUFFER);
@@ -224,7 +224,7 @@ bool downloadFile(const std::string& url, const std::string& toDestination, bool
file.close();
#else
file.reset();
globalWriteBuffer.reset();
writeBuffer.reset();
#endif
return false;
}
@@ -277,12 +277,15 @@ bool downloadFile(const std::string& url, const std::string& toDestination, bool
//const bool wasAborted = (result == CURLE_ABORTED_BY_CALLBACK ||
// abortDownload.load(std::memory_order_acquire));
// Check HTTP response code BEFORE closing file/curl
long http_code = 0;
curl_easy_getinfo(curl.get(), CURLINFO_RESPONSE_CODE, &http_code);
#if USING_FSTREAM_DIRECTIVE
file.close();
#else
file.reset();
globalWriteBuffer.reset();
writeBuffer.reset();
#endif
curl.reset();
@@ -309,6 +312,20 @@ bool downloadFile(const std::string& url, const std::string& toDestination, bool
//cleanupCurl();
//socketExit();
// Check for HTTP errors (404, 500, etc.)
if (result == CURLE_OK && (http_code < 200 || http_code >= 300)) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("HTTP error " + std::to_string(http_code) + " downloading: " + url);
#endif
deleteFileOrDirectory(tempFilePath);
if (!noPercentagePolling) {
downloadPercentage.store(-1, std::memory_order_release);
}
return false;
}
if (result != CURLE_OK) {
#if USING_LOGGING_DIRECTIVE
@@ -382,6 +399,7 @@ bool downloadFile(const std::string& url, const std::string& toDestination, bool
}
moveFile(tempFilePath, destination);
return true;
}
@@ -622,7 +640,7 @@ bool unzipFile(const std::string& zipFilePath, const std::string& toDestination)
const size_t bufferSize = UNZIP_WRITE_BUFFER;
//std::unique_ptr<char[]> buffer = std::make_unique<char[]>(bufferSize);
globalWriteBuffer = std::make_unique<char[]>(bufferSize);
std::unique_ptr<char[]> writeBuffer = std::make_unique<char[]>(bufferSize);
char filenameBuffer[512]; // Stack allocated for filename reading
@@ -758,14 +776,14 @@ bool unzipFile(const std::string& zipFilePath, const std::string& toDestination)
fileBytesProcessed = 0;
while ((bytesRead = unzReadCurrentFile(zipFile, globalWriteBuffer.get(), bufferSize)) > 0) {
while ((bytesRead = unzReadCurrentFile(zipFile, writeBuffer.get(), bufferSize)) > 0) {
if (abortUnzip.load(std::memory_order_relaxed)) {
extractSuccess = false;
break; // RAII will handle cleanup
}
// Write data to file
if (outputFile.write(globalWriteBuffer.get(), bytesRead) != static_cast<size_t>(bytesRead)) {
if (outputFile.write(writeBuffer.get(), bytesRead) != static_cast<size_t>(bytesRead)) {
extractSuccess = false;
break;
}
@@ -845,7 +863,7 @@ bool unzipFile(const std::string& zipFilePath, const std::string& toDestination)
result = unzGoToNextFile(zipFile);
}
globalWriteBuffer.reset();
writeBuffer.reset();
// Check final abort state
if (abortUnzip.load(std::memory_order_relaxed)) {

View File

@@ -15,7 +15,7 @@
* of the project's documentation and must remain intact.
*
* Licensed under both GPLv2 and CC-BY-4.0
* Copyright (c) 2024 ppkantorski
* Copyright (c) 2023-2025 ppkantorski
********************************************************************************/
#include "get_funcs.hpp"

View File

@@ -13,7 +13,7 @@
* altered or removed.
*
* Licensed under both GPLv2 and CC-BY-4.0
* Copyright (c) 2024 ppkantorski
* Copyright (c) 2023-2025 ppkantorski
********************************************************************************/
#include "global_vars.hpp"
@@ -26,6 +26,10 @@ namespace ult {
const std::string TESLA_CONFIG_PATH = ROOT_PATH + "config/tesla/";
const std::string SWITCH_PATH = ROOT_PATH + "switch/";
const std::string NX_OVLLOADER_PATH = ROOT_PATH + "config/nx-ovlloader/";
const std::string OVL_HEAP_CONFIG_PATH = NX_OVLLOADER_PATH + "heap_size.bin";
const std::string OVL_EXIT_FLAG_PATH = NX_OVLLOADER_PATH + "exit_flag.bin";
// Filenames
CONSTEXPR_STRING std::string CONFIG_FILENAME = "config.ini";
const std::string BOOT_PACKAGE_FILENAME = "boot_package.ini";
@@ -59,7 +63,8 @@ namespace ult {
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 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/";
@@ -79,12 +84,13 @@ namespace ult {
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 LATEST_UPDATER_INI_URL = ULTRAHAND_REPO_URL + "releases/latest/download/update.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 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";
const std::string SOUND_EFFECTS_URL = GITHUB_RAW_BASE_URL + "Ultrahand-Overlay/main/sounds/sounds.zip";
//const std::string SOUND_EFFECTS_URL = GITHUB_RAW_BASE_URL + "Ultrahand-Overlay/main/sounds/sounds.zip";
// Launch options
const std::string LAUNCH_ARGS_STR = "launch_args";
@@ -140,6 +146,7 @@ namespace ult {
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 HOLD_STR = "hold";
CONSTEXPR_STRING std::string SLOT_STR = "slot";
CONSTEXPR_STRING std::string OPTION_STR = "option";
CONSTEXPR_STRING std::string FORWARDER_STR = "forwarder";
@@ -164,6 +171,7 @@ namespace ult {
CONSTEXPR_STRING std::string INPROGRESS_SYMBOL = "\u25CF";
CONSTEXPR_STRING std::string STAR_SYMBOL = "\u2605";
CONSTEXPR_STRING std::string DIVIDER_SYMBOL = "";
CONSTEXPR_STRING std::string NOTIFY_HEADER = "";
const std::vector<std::string> THROBBER_SYMBOLS = {"", "", "", "", "", "", "", ""};

View File

@@ -30,10 +30,13 @@ namespace ult {
static HidVibrationDeviceHandle vibPlayer1Right;
static u64 rumbleStartTick = 0;
static u64 doubleClickTick = 0;
static u8 doubleClickPulse = 0;
static u8 doubleClickPulse = 0;
static u32 cachedHandheldStyle = 0;
static u32 cachedPlayer1Style = 0;
// ===== Shared flags (accessible globally) =====
std::atomic<bool> rumbleActive{false};
std::atomic<bool> clickActive{false};
std::atomic<bool> doubleClickActive{false};
// ===== Constants =====
@@ -41,144 +44,182 @@ namespace ult {
static constexpr u64 DOUBLE_CLICK_PULSE_DURATION_NS = 30'000'000ULL;
static constexpr u64 DOUBLE_CLICK_GAP_NS = 100'000'000ULL;
static constexpr HidVibrationValue clickDocked = {
//static constexpr HidVibrationValue clickDocked = {
// .amp_low = 0.20f,
// .freq_low = 100.0f,
// .amp_high = 0.80f,
// .freq_high = 300.0f
//};
//
//static constexpr HidVibrationValue clickHandheld = {
// .amp_low = 0.20f,
// .freq_low = 100.0f,
// .amp_high = 0.80f,
// .freq_high = 300.0f
//};
static constexpr HidVibrationValue hapticsPreset = {
.amp_low = 0.20f,
.freq_low = 100.0f,
.amp_high = 0.80f,
.freq_high = 300.0f
};
static constexpr HidVibrationValue clickHandheld = {
.amp_low = 0.25f,
.freq_low = 100.0f,
.amp_high = 1.0f,
.freq_high = 300.0f
};
static constexpr HidVibrationValue vibrationStop{0};
// ===== Internal helpers =====
static void initController(HidNpadIdType npad, HidVibrationDeviceHandle* handles, int count) {
const u32 styleMask = hidGetNpadStyleSet(npad);
if (styleMask)
hidInitializeVibrationDevices(handles, count, npad, static_cast<HidNpadStyleTag>(styleMask));
}
static void sendVibration(const HidVibrationValue* value) {
if (hidGetNpadStyleSet(HidNpadIdType_Handheld))
static inline void sendVibration(const HidVibrationValue* value) {
if (cachedHandheldStyle)
hidSendVibrationValue(vibHandheld, value);
if (hidGetNpadStyleSet(HidNpadIdType_No1)) {
if (cachedPlayer1Style) {
hidSendVibrationValue(vibPlayer1Left, value);
hidSendVibrationValue(vibPlayer1Right, value);
}
}
// ===== Public API =====
void initRumble() {
//if (rumbleInitialized) return;
// Try to initialize whatever is available
// Don't check if controllers exist - let initController handle it
initController(HidNpadIdType_Handheld, &vibHandheld, 1);
HidVibrationDeviceHandle handles[2];
initController(HidNpadIdType_No1, handles, 2);
vibPlayer1Left = handles[0];
vibPlayer1Right = handles[1];
// Only mark as initialized if at least one controller was found
hidGetNpadStyleSet(HidNpadIdType_Handheld);
hidGetNpadStyleSet(HidNpadIdType_No1);
//rumbleInitialized = (handheldStyle || player1Style);
// If neither exist, stay uninitialized so we retry later
static inline void sendVibration2x(const HidVibrationValue* value) {
sendVibration(value);
sendVibration(value);
}
//void deinitRumble() {
// ===== Public API =====
void initHaptics() {
const u32 handheldStyle = hidGetNpadStyleSet(HidNpadIdType_Handheld);
const u32 player1Style = hidGetNpadStyleSet(HidNpadIdType_No1);
// Clear previous handles to avoid using stale handles if controllers were removed
vibHandheld = (HidVibrationDeviceHandle)0;
vibPlayer1Left = (HidVibrationDeviceHandle)0;
vibPlayer1Right = (HidVibrationDeviceHandle)0;
// Handheld
if (handheldStyle) {
hidInitializeVibrationDevices(&vibHandheld, 1,
HidNpadIdType_Handheld,
(HidNpadStyleTag)handheldStyle);
}
// Player 1 (left + right Joy-Con or Pro Controller)
if (player1Style) {
HidVibrationDeviceHandle tmp[2] = { (HidVibrationDeviceHandle)0, (HidVibrationDeviceHandle)0 };
hidInitializeVibrationDevices(tmp, 2,
HidNpadIdType_No1,
(HidNpadStyleTag)player1Style);
vibPlayer1Left = tmp[0];
vibPlayer1Right = tmp[1];
}
// Ensure cache is valid immediately after initHaptics()
cachedHandheldStyle = handheldStyle;
cachedPlayer1Style = player1Style;
}
//void deinitHaptics() {
// rumbleInitialized = false;
//}
void checkAndReinitRumble() {
void checkAndReinitHaptics() {
static u32 lastHandheldStyle = 0;
static u32 lastPlayer1Style = 0;
static u32 lastPlayer1Style = 0;
const u32 currentHandheldStyle = hidGetNpadStyleSet(HidNpadIdType_Handheld);
const u32 currentPlayer1Style = hidGetNpadStyleSet(HidNpadIdType_No1);
const u32 currentPlayer1Style = hidGetNpadStyleSet(HidNpadIdType_No1);
// If not initialized but controllers exist, try to init
// This handles the boot race condition where HID reports controllers
// but vibration subsystem isn't ready yet
//if (!rumbleInitialized && (currentHandheldStyle || currentPlayer1Style)) {
// initRumble();
//}
// Reinitialize only if something changed (appearance/disappearance or style change)
//const bool changed =
// (currentHandheldStyle != lastHandheldStyle) || (currentPlayer1Style != lastPlayer1Style);
// Reinit if controller configuration changed
if (currentHandheldStyle != lastHandheldStyle || currentPlayer1Style != lastPlayer1Style) {
//rumbleInitialized = false;
initRumble();
if ((currentHandheldStyle != lastHandheldStyle) || (currentPlayer1Style != lastPlayer1Style)) {
initHaptics();
}
// Update last style tracking regardless
// Update last-known styles for change detection
lastHandheldStyle = currentHandheldStyle;
lastPlayer1Style = currentPlayer1Style;
lastPlayer1Style = currentPlayer1Style;
// Update cached styles used by sendVibration()/rumble paths
cachedHandheldStyle = currentHandheldStyle;
cachedPlayer1Style = currentPlayer1Style;
}
void rumbleClick() {
//if (!rumbleInitialized) {
// initRumble();
// if (!rumbleInitialized) return;
// Use cached style bit instead of querying hid each call
//const HidVibrationValue* pattern = cachedHandheldStyle ? &clickHandheld : &clickDocked;
sendVibration(&vibrationStop);
//if (cachedHandheldStyle) {
// sendVibration(&clickHandheld);
// sendVibration(&clickHandheld);
//} else {
// sendVibration(&clickDocked);
// sendVibration(&clickDocked);
//}
sendVibration(hidGetNpadStyleSet(HidNpadIdType_Handheld) ? &clickHandheld : &clickDocked);
rumbleActive.store(true, std::memory_order_release);
sendVibration2x(&hapticsPreset);
clickActive.store(true, std::memory_order_release);
rumbleStartTick = armGetSystemTick();
}
void rumbleDoubleClick() {
//if (!rumbleInitialized) {
// initRumble();
// if (!rumbleInitialized) return;
//onst HidVibrationValue* pattern = cachedHandheldStyle ? &clickHandheld : &clickDocked;
sendVibration(&vibrationStop);
//if (cachedHandheldStyle) {
// sendVibration(&clickHandheld);
// sendVibration(&clickHandheld);
//} else {
// sendVibration(&clickDocked);
// sendVibration(&clickDocked);
//}
sendVibration(hidGetNpadStyleSet(HidNpadIdType_Handheld) ? &clickHandheld : &clickDocked);
sendVibration2x(&hapticsPreset);
doubleClickActive.store(true, std::memory_order_release);
doubleClickPulse = 1;
doubleClickTick = armGetSystemTick();
doubleClickTick = armGetSystemTick(); // Set ONCE
}
void processRumbleStop(u64 nowNs) {
if (rumbleActive.load(std::memory_order_acquire) &&
if (clickActive.load(std::memory_order_acquire) &&
nowNs - armTicksToNs(rumbleStartTick) >= RUMBLE_DURATION_NS) {
sendVibration(&vibrationStop);
rumbleActive.store(false, std::memory_order_release);
clickActive.store(false, std::memory_order_release);
}
}
void processRumbleDoubleClick(u64 nowNs) {
if (!doubleClickActive.load(std::memory_order_acquire)) return;
const u64 elapsed = nowNs - armTicksToNs(doubleClickTick);
const u64 elapsed = nowNs - armTicksToNs(doubleClickTick); // Always from original start
switch (doubleClickPulse) {
case 1:
if (elapsed >= DOUBLE_CLICK_PULSE_DURATION_NS) {
sendVibration(&vibrationStop);
doubleClickPulse = 2;
doubleClickTick = armGetSystemTick();
// Don't reset tick!
}
break;
case 2:
if (elapsed >= DOUBLE_CLICK_GAP_NS) {
sendVibration(hidGetNpadStyleSet(HidNpadIdType_Handheld) ? &clickHandheld : &clickDocked);
if (elapsed >= DOUBLE_CLICK_PULSE_DURATION_NS + DOUBLE_CLICK_GAP_NS) {
// Use cached style here too
//if (cachedHandheldStyle) {
// sendVibration(&clickHandheld);
// sendVibration(&clickHandheld);
//} else {
// sendVibration(&clickDocked);
// sendVibration(&clickDocked);
//}
sendVibration2x(&hapticsPreset);
doubleClickPulse = 3;
doubleClickTick = armGetSystemTick();
// Don't reset tick!
}
break;
case 3:
if (elapsed >= DOUBLE_CLICK_PULSE_DURATION_NS) {
if (elapsed >= (DOUBLE_CLICK_PULSE_DURATION_NS * 2) + DOUBLE_CLICK_GAP_NS) {
sendVibration(&vibrationStop);
doubleClickActive.store(false, std::memory_order_release);
doubleClickPulse = 0;
@@ -187,16 +228,34 @@ namespace ult {
}
}
void rumbleDoubleClickStandalone() {
sendVibration(hidGetNpadStyleSet(HidNpadIdType_Handheld) ? &clickHandheld : &clickDocked);
// Standalone uses sleeps, but still use cached style for decision
//const HidVibrationValue* pattern = cachedHandheldStyle ? &clickHandheld : &clickDocked;
sendVibration(&vibrationStop);
//if (cachedHandheldStyle) {
// sendVibration(&clickHandheld);
// sendVibration(&clickHandheld);
//} else {
// sendVibration(&clickDocked);
// sendVibration(&clickDocked);
//}
sendVibration2x(&hapticsPreset);
svcSleepThread(DOUBLE_CLICK_PULSE_DURATION_NS);
sendVibration(&vibrationStop);
svcSleepThread(DOUBLE_CLICK_GAP_NS);
sendVibration(hidGetNpadStyleSet(HidNpadIdType_Handheld) ? &clickHandheld : &clickDocked);
//if (cachedHandheldStyle) {
// sendVibration(&clickHandheld);
// sendVibration(&clickHandheld);
//} else {
// sendVibration(&clickDocked);
// sendVibration(&clickDocked);
//}
sendVibration2x(&hapticsPreset);
svcSleepThread(DOUBLE_CLICK_PULSE_DURATION_NS);
sendVibration(&vibrationStop);
}
}

View File

@@ -15,7 +15,7 @@
* of the project's documentation and must remain intact.
*
* Licensed under both GPLv2 and CC-BY-4.0
* Copyright (c) 2024 ppkantorski
* Copyright (c) 2023-2025 ppkantorski
********************************************************************************/
#include "hex_funcs.hpp"
@@ -229,15 +229,16 @@ namespace ult {
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 size_t patternLen = hexLen / 2;
// Use heap allocation for the buffer to avoid stack overflow with large buffer sizes
std::unique_ptr<unsigned char[]> binaryData(new unsigned char[patternLen]);
const unsigned char* hexPtr = reinterpret_cast<const unsigned char*>(hexData.c_str());
// Unrolled hex conversion loop
@@ -252,17 +253,17 @@ namespace ult {
}
// Optimized search variables
const unsigned char* patternPtr = binaryData.data();
const size_t patternLen = binaryData.size();
const unsigned char* patternPtr = binaryData.get();
const unsigned char firstByte = patternPtr[0];
std::vector<unsigned char> buffer(HEX_BUFFER_SIZE);
// Use heap allocation for the buffer to avoid stack overflow with large buffer sizes
std::unique_ptr<unsigned char[]> buffer(new unsigned char[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();
while ((bytesRead = fread(buffer.get(), 1, HEX_BUFFER_SIZE, file)) > 0) {
const unsigned char* bufPtr = buffer.get();
// Optimized search with first-byte filtering and loop unrolling
i = 0;
@@ -322,15 +323,16 @@ namespace ult {
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 size_t patternLen = hexLen / 2;
// Use heap allocation for the buffer to avoid stack overflow with large buffer sizes
std::unique_ptr<unsigned char[]> binaryData(new unsigned char[patternLen]);
const unsigned char* hexPtr = reinterpret_cast<const unsigned char*>(hexData.c_str());
size_t i = 0;
@@ -342,17 +344,17 @@ namespace ult {
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* patternPtr = binaryData.get();
const unsigned char firstByte = patternPtr[0];
std::vector<unsigned char> buffer(HEX_BUFFER_SIZE);
// Use heap allocation for the buffer to avoid stack overflow with large buffer sizes
std::unique_ptr<unsigned char[]> buffer(new unsigned char[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) {
while (file.read(reinterpret_cast<char*>(buffer.get()), HEX_BUFFER_SIZE) || file.gcount() > 0) {
bytesRead = file.gcount();
const unsigned char* bufPtr = buffer.data();
const unsigned char* bufPtr = buffer.get();
// Same optimized search as FILE* version
i = 0;
@@ -418,7 +420,6 @@ namespace ult {
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
@@ -435,7 +436,6 @@ namespace ult {
// 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
@@ -446,18 +446,37 @@ namespace ult {
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));
// Validate hex data length
const size_t hexLen = hexData.length();
if (hexLen % 2 != 0) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Invalid hex data length.");
#endif
fclose(file);
return;
}
// Convert the hex string to binary data using optimized lookup table
const size_t dataLen = hexLen / 2;
std::unique_ptr<unsigned char[]> binaryData(new unsigned char[dataLen]);
const unsigned char* hexPtr = reinterpret_cast<const unsigned char*>(hexData.c_str());
// Unrolled hex conversion loop (same as findHexDataOffsets)
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]];
}
// 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()) {
const size_t bytesWritten = fwrite(binaryData.get(), sizeof(unsigned char), dataLen, file);
if (bytesWritten != dataLen) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Failed to write data to the file.");
@@ -481,7 +500,6 @@ namespace ult {
// 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
@@ -491,17 +509,34 @@ namespace ult {
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));
// Validate hex data length
const size_t hexLen = hexData.length();
if (hexLen % 2 != 0) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Invalid hex data length.");
#endif
return;
}
// Convert the hex string to binary data using optimized lookup table
const size_t dataLen = hexLen / 2;
std::unique_ptr<unsigned char[]> binaryData(new unsigned char[dataLen]);
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]];
}
for (; i < hexLen; i += 2) {
binaryData[i/2] = (hexTable[hexPtr[i]] << 4) | hexTable[hexPtr[i + 1]];
}
// 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());
file.write(reinterpret_cast<const char*>(binaryData.get()), dataLen);
if (!file) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
@@ -676,8 +711,10 @@ namespace ult {
}
const std::streampos totalOffset = hexSum + std::stoll(offsetStr);
std::vector<char> hexBuffer(length);
std::vector<char> hexStream(length * 2);
// Pre-allocate final string size to avoid reallocation
std::string result;
result.reserve(length * 2);
#if !USING_FSTREAM_DIRECTIVE
FILE* file = fopen(filePath.c_str(), "rb");
@@ -698,23 +735,41 @@ namespace ult {
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 {
// Use heap allocation for the buffer to avoid stack overflow with large buffer sizes
std::unique_ptr<unsigned char[]> buffer(new unsigned char[length]);
const size_t bytesRead = fread(buffer.get(), 1, length, file);
fclose(file);
if (bytesRead != length) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Error reading data from file or end of file reached.");
#endif
fclose(file);
return "";
}
fclose(file);
// Optimized hex conversion - directly build uppercase string
static constexpr char hexDigits[] = "0123456789ABCDEF";
result.resize(length * 2);
// Unrolled loop for better performance
size_t i = 0;
for (; i + 4 <= length; i += 4) {
result[i * 2] = hexDigits[(buffer[i] >> 4) & 0xF];
result[i * 2 + 1] = hexDigits[buffer[i] & 0xF];
result[i * 2 + 2] = hexDigits[(buffer[i + 1] >> 4) & 0xF];
result[i * 2 + 3] = hexDigits[buffer[i + 1] & 0xF];
result[i * 2 + 4] = hexDigits[(buffer[i + 2] >> 4) & 0xF];
result[i * 2 + 5] = hexDigits[buffer[i + 2] & 0xF];
result[i * 2 + 6] = hexDigits[(buffer[i + 3] >> 4) & 0xF];
result[i * 2 + 7] = hexDigits[buffer[i + 3] & 0xF];
}
// Handle remaining bytes
for (; i < length; ++i) {
result[i * 2] = hexDigits[(buffer[i] >> 4) & 0xF];
result[i * 2 + 1] = hexDigits[buffer[i] & 0xF];
}
#else
std::ifstream file(filePath, std::ios::binary);
if (!file) {
@@ -734,14 +789,12 @@ namespace ult {
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 {
// Use heap allocation for the buffer to avoid stack overflow with large buffer sizes
std::unique_ptr<unsigned char[]> buffer(new unsigned char[length]);
file.read(reinterpret_cast<char*>(buffer.get()), length);
file.close();
if (file.gcount() != static_cast<std::streamsize>(length)) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Error reading data from file or end of file reached.");
@@ -749,12 +802,29 @@ namespace ult {
return "";
}
file.close();
// Optimized hex conversion - directly build uppercase string
static constexpr char hexDigits[] = "0123456789ABCDEF";
result.resize(length * 2);
// Unrolled loop for better performance
size_t i = 0;
for (; i + 4 <= length; i += 4) {
result[i * 2] = hexDigits[(buffer[i] >> 4) & 0xF];
result[i * 2 + 1] = hexDigits[buffer[i] & 0xF];
result[i * 2 + 2] = hexDigits[(buffer[i + 1] >> 4) & 0xF];
result[i * 2 + 3] = hexDigits[buffer[i + 1] & 0xF];
result[i * 2 + 4] = hexDigits[(buffer[i + 2] >> 4) & 0xF];
result[i * 2 + 5] = hexDigits[buffer[i + 2] & 0xF];
result[i * 2 + 6] = hexDigits[(buffer[i + 3] >> 4) & 0xF];
result[i * 2 + 7] = hexDigits[buffer[i + 3] & 0xF];
}
// Handle remaining bytes
for (; i < length; ++i) {
result[i * 2] = hexDigits[(buffer[i] >> 4) & 0xF];
result[i * 2 + 1] = hexDigits[buffer[i] & 0xF];
}
#endif
std::string result(hexStream.begin(), hexStream.end());
result = stringToUppercase(result);
return result;
}
@@ -878,4 +948,71 @@ namespace ult {
return ""; // Return empty string if no match is found
}
// 1. Table optimization: Mark as constexpr for compile-time evaluation
static constexpr uint8_t b64_table[256] = {
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,62, 0xFF,0xFF,0xFF,63,
52,53,54,55,56,57,58,59,60,61,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF
};
// 2. Optimized decode: Pre-calculate output size, reduce bounds checking
static size_t base64_decode(const char* src, uint8_t* out) {
size_t outLen = 0;
const char* p = src;
// Process 4 chars at a time (unrolled loop for better instruction pipelining)
while (*p) {
uint8_t a = b64_table[static_cast<uint8_t>(*p++)];
if (a == 0xFF) break;
uint8_t b = b64_table[static_cast<uint8_t>(*p++)];
if (b == 0xFF) break;
out[outLen++] = (a << 2) | (b >> 4);
uint8_t cChar = *p++;
if (cChar == '=' || cChar == '\0') break;
uint8_t c = b64_table[cChar];
if (c == 0xFF) break;
out[outLen++] = (b << 4) | (c >> 2);
uint8_t dChar = *p++;
if (dChar == '=' || dChar == '\0') break;
uint8_t d = b64_table[dChar];
if (d == 0xFF) break;
out[outLen++] = (c << 6) | d;
}
return outLen;
}
// 3. Optimized wrapper: Pre-calculate exact output size, avoid vector overhead
std::string decodeBase64ToString(const std::string& b64) {
// Base64 decodes to ~3/4 original size
const size_t maxOutSize = (b64.size() * 3) / 4 + 3;
std::string result(maxOutSize, '\0');
const size_t len = base64_decode(b64.c_str(), reinterpret_cast<uint8_t*>(result.data()));
result.resize(len);
return result;
}
}

View File

@@ -12,7 +12,7 @@
* of the project's documentation and must remain intact.
*
* Licensed under both GPLv2 and CC-BY-4.0
* Copyright (c) 2024 ppkantorski
* Copyright (c) 2023-2025 ppkantorski
********************************************************************************/
#include "json_funcs.hpp"

View File

@@ -14,7 +14,7 @@
* of the project's documentation and must remain intact.
*
* Licensed under both GPLv2 and CC-BY-4.0
* Copyright (c) 2024 ppkantorski
* Copyright (c) 2023-2025 ppkantorski
********************************************************************************/
#include <list_funcs.hpp>
@@ -84,9 +84,9 @@ namespace ult {
}
}
// Function to read file into a vector of strings with optional cap
std::vector<std::string> readListFromFile(const std::string& filePath, size_t maxLines) {
// Function to read file into a vector of strings with optional cap and newline preservation
std::vector<std::string> readListFromFile(const std::string& filePath, size_t maxLines, bool preserveNewlines) {
std::lock_guard<std::mutex> lock(file_access_mutex);
std::vector<std::string> lines;
@@ -109,18 +109,22 @@ namespace ult {
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') {
if (preserveNewlines) {
// Keep the line as-is, including newlines
lines.emplace_back(buffer);
} else {
// Remove newlines
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);
}
lines.emplace_back(buffer);
}
fclose(file);
@@ -140,12 +144,17 @@ namespace ult {
break;
}
// Remove carriage return if present (getline removes \n but not \r)
if (!line.empty() && line.back() == '\r') {
line.pop_back();
if (preserveNewlines) {
// Add back the newline that getline removed
line += '\n';
lines.emplace_back(std::move(line));
} else {
// 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));
}
lines.emplace_back(std::move(line));
}
file.close();
@@ -282,7 +291,7 @@ namespace ult {
// Function to read file into a set of strings
std::unordered_set<std::string> readSetFromFile(const std::string& filePath) {
std::unordered_set<std::string> readSetFromFile(const std::string& filePath, const std::string& packagePath) {
std::lock_guard<std::mutex> lock(file_access_mutex);
std::unordered_set<std::string> lines;
@@ -304,7 +313,12 @@ namespace ult {
if (len > 0 && buffer[len - 1] == '\n') {
buffer[len - 1] = '\0';
}
lines.insert(buffer);
std::string line = buffer;
if (!packagePath.empty()) {
preprocessPath(line, packagePath);
}
lines.insert(std::move(line));
}
fclose(file);
@@ -319,6 +333,9 @@ namespace ult {
std::string line;
while (std::getline(file, line)) {
if (!packagePath.empty()) {
preprocessPath(line, packagePath);
}
lines.insert(std::move(line));
}

View File

@@ -14,7 +14,7 @@
* of the project's documentation and must remain intact.
*
* Licensed under both GPLv2 and CC-BY-4.0
* Copyright (c) 2024 ppkantorski
* Copyright (c) 2024-2025 ppkantorski
********************************************************************************/
#include <mod_funcs.hpp>

View File

@@ -15,7 +15,7 @@
* of the project's documentation and must remain intact.
*
* Licensed under both GPLv2 and CC-BY-4.0
* Copyright (c) 2024 ppkantorski
* Copyright (c) 2023-2025 ppkantorski
********************************************************************************/
#include "string_funcs.hpp"
@@ -226,12 +226,14 @@ namespace ult {
* @return The string with quotes removed.
*/
void removeQuotes(std::string& str) {
if (str.size() >= 2) {
const size_t len = str.size();
if (len >= 2) {
const char front = str[0];
const char back = str[str.size() - 1];
const char back = str[len - 1];
if ((front == '\'' && back == '\'') || (front == '"' && back == '"')) {
str.erase(0, 1);
str.pop_back();
std::memmove(&str[0], &str[1], len - 2);
str.resize(len - 2);
}
}
}
@@ -245,25 +247,25 @@ namespace ult {
* @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;
}
//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;
//}
@@ -366,7 +368,7 @@ namespace ult {
* @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
const size_t lastDotPos = filename.rfind('.');
if (lastDotPos != std::string::npos) {
filename.resize(lastDotPos);
}
@@ -510,9 +512,12 @@ namespace ult {
* @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 '?'
const char* pos = static_cast<const char*>(
std::memchr(input.data(), '?', input.size())
);
if (pos) {
input.resize(pos - input.data());
}
}