diff options
author | BobTheBob9 <for.oliver.kirkham@gmail.com> | 2022-08-16 01:12:59 +0100 |
---|---|---|
committer | BobTheBob9 <for.oliver.kirkham@gmail.com> | 2022-08-16 01:12:59 +0100 |
commit | 082f3893215797268d7ac8c1000ebe371f276af7 (patch) | |
tree | b5cc677a49635a9244559e235d4915c064c9f6c4 /NorthstarDLL/audio.cpp | |
parent | b30a385fc25dad05e568ae8538f5aa3656c5c8d4 (diff) | |
download | NorthstarLauncher-082f3893215797268d7ac8c1000ebe371f276af7.tar.gz NorthstarLauncher-082f3893215797268d7ac8c1000ebe371f276af7.zip |
lots of stuff idk
Diffstat (limited to 'NorthstarDLL/audio.cpp')
-rw-r--r-- | NorthstarDLL/audio.cpp | 1006 |
1 files changed, 503 insertions, 503 deletions
diff --git a/NorthstarDLL/audio.cpp b/NorthstarDLL/audio.cpp index e2f9f3b7..30bf92fb 100644 --- a/NorthstarDLL/audio.cpp +++ b/NorthstarDLL/audio.cpp @@ -1,503 +1,503 @@ -#include "pch.h"
-#include "audio.h"
-#include "dedicated.h"
-#include "convar.h"
-
-#include "rapidjson/error/en.h"
-#include <fstream>
-#include <iostream>
-#include <sstream>
-#include <random>
-
-AUTOHOOK_INIT()
-
-extern "C"
-{
- // should be called only in LoadSampleMetadata_Hook
- extern void* __fastcall Audio_GetParentEvent();
-}
-
-ConVar* Cvar_ns_print_played_sounds;
-
-CustomAudioManager g_CustomAudioManager;
-
-EventOverrideData::EventOverrideData()
-{
- spdlog::warn("Initialised struct EventOverrideData without any data!");
- LoadedSuccessfully = false;
-}
-
-// Empty stereo 48000 WAVE file
-unsigned char EMPTY_WAVE[45] = {0x52, 0x49, 0x46, 0x46, 0x25, 0x00, 0x00, 0x00, 0x57, 0x41, 0x56, 0x45, 0x66, 0x6D, 0x74,
- 0x20, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x44, 0xAC, 0x00, 0x00, 0x88, 0x58,
- 0x01, 0x00, 0x02, 0x00, 0x10, 0x00, 0x64, 0x61, 0x74, 0x61, 0x74, 0x00, 0x00, 0x00, 0x00};
-
-EventOverrideData::EventOverrideData(const std::string& data, const fs::path& path)
-{
- if (data.length() <= 0)
- {
- spdlog::error("Failed reading audio override file {}: file is empty", path.string());
- return;
- }
-
- fs::path samplesFolder = path;
- samplesFolder = samplesFolder.replace_extension();
-
- if (!fs::exists(samplesFolder))
- {
- spdlog::error(
- "Failed reading audio override file {}: samples folder doesn't exist; should be named the same as the definition file without "
- "JSON extension.",
- path.string());
- return;
- }
-
- rapidjson_document dataJson;
- dataJson.Parse<rapidjson::ParseFlag::kParseCommentsFlag | rapidjson::ParseFlag::kParseTrailingCommasFlag>(data);
-
- // fail if parse error
- if (dataJson.HasParseError())
- {
- spdlog::error(
- "Failed reading audio override file {}: encountered parse error \"{}\" at offset {}",
- path.string(),
- GetParseError_En(dataJson.GetParseError()),
- dataJson.GetErrorOffset());
- return;
- }
-
- // fail if it's not a json obj (could be an array, string, etc)
- if (!dataJson.IsObject())
- {
- spdlog::error("Failed reading audio override file {}: file is not a JSON object", path.string());
- return;
- }
-
- // fail if no event ids given
- if (!dataJson.HasMember("EventId"))
- {
- spdlog::error("Failed reading audio override file {}: JSON object does not have the EventId property", path.string());
- return;
- }
-
- // array of event ids
- if (dataJson["EventId"].IsArray())
- {
- for (auto& eventId : dataJson["EventId"].GetArray())
- {
- if (!eventId.IsString())
- {
- spdlog::error(
- "Failed reading audio override file {}: EventId array has a value of invalid type, all must be strings", path.string());
- return;
- }
-
- EventIds.push_back(eventId.GetString());
- }
- }
- // singular event id
- else if (dataJson["EventId"].IsString())
- {
- EventIds.push_back(dataJson["EventId"].GetString());
- }
- // incorrect type
- else
- {
- spdlog::error(
- "Failed reading audio override file {}: EventId property is of invalid type (must be a string or an array of strings)",
- path.string());
- return;
- }
-
- if (dataJson.HasMember("EventIdRegex"))
- {
- // array of event id regex
- if (dataJson["EventIdRegex"].IsArray())
- {
- for (auto& eventId : dataJson["EventIdRegex"].GetArray())
- {
- if (!eventId.IsString())
- {
- spdlog::error(
- "Failed reading audio override file {}: EventIdRegex array has a value of invalid type, all must be strings",
- path.string());
- return;
- }
-
- const std::string& regex = eventId.GetString();
-
- try
- {
- EventIdsRegex.push_back({regex, std::regex(regex)});
- }
- catch (...)
- {
- spdlog::error("Malformed regex \"{}\" in audio override file {}", regex, path.string());
- return;
- }
- }
- }
- // singular event id regex
- else if (dataJson["EventIdRegex"].IsString())
- {
- const std::string& regex = dataJson["EventIdRegex"].GetString();
- try
- {
- EventIdsRegex.push_back({regex, std::regex(regex)});
- }
- catch (...)
- {
- spdlog::error("Malformed regex \"{}\" in audio override file {}", regex, path.string());
- return;
- }
- }
- // incorrect type
- else
- {
- spdlog::error(
- "Failed reading audio override file {}: EventIdRegex property is of invalid type (must be a string or an array of strings)",
- path.string());
- return;
- }
- }
-
- if (dataJson.HasMember("AudioSelectionStrategy"))
- {
- if (!dataJson["AudioSelectionStrategy"].IsString())
- {
- spdlog::error("Failed reading audio override file {}: AudioSelectionStrategy property must be a string", path.string());
- return;
- }
-
- std::string strategy = dataJson["AudioSelectionStrategy"].GetString();
-
- if (strategy == "sequential")
- {
- Strategy = AudioSelectionStrategy::SEQUENTIAL;
- }
- else if (strategy == "random")
- {
- Strategy = AudioSelectionStrategy::RANDOM;
- }
- else
- {
- spdlog::error(
- "Failed reading audio override file {}: AudioSelectionStrategy string must be either \"sequential\" or \"random\"",
- path.string());
- return;
- }
- }
-
- // load samples
- for (fs::directory_entry file : fs::recursive_directory_iterator(samplesFolder))
- {
- if (file.is_regular_file() && file.path().extension().string() == ".wav")
- {
- std::string pathString = file.path().string();
-
- // Open the file.
- std::basic_ifstream<uint8_t> wavStream(pathString, std::ios::binary);
-
- if (wavStream.fail())
- {
- spdlog::error("Failed reading audio sample {}", file.path().string());
- continue;
- }
-
- // Get file size.
- wavStream.seekg(0, std::ios::end);
- size_t fileSize = wavStream.tellg();
- wavStream.close();
-
- // Allocate enough memory for the file.
- // blank out the memory for now, then read it later
- uint8_t* data = new uint8_t[fileSize];
- memcpy(data, EMPTY_WAVE, sizeof(EMPTY_WAVE));
- Samples.push_back({fileSize, std::unique_ptr<uint8_t[]>(data)});
-
- // thread off the file read
- // should we spawn one thread per read? or should there be a cap to the number of reads at once?
- std::thread readThread(
- [pathString, fileSize, data]
- {
- std::shared_lock lock(g_CustomAudioManager.m_loadingMutex);
- std::basic_ifstream<uint8_t> wavStream(pathString, std::ios::binary);
-
- // would be weird if this got hit, since it would've worked previously
- if (wavStream.fail())
- {
- spdlog::error("Failed async read of audio sample {}", pathString);
- return;
- }
-
- // read from after the header first to preserve the empty header, then read the header last
- wavStream.seekg(0, std::ios::beg);
- wavStream.read(data, fileSize);
- wavStream.close();
-
- spdlog::info("Finished async read of audio sample {}", pathString);
- });
-
- readThread.detach();
- }
- }
-
- /*
- if (dataJson.HasMember("EnableOnLoopedSounds"))
- {
- if (!dataJson["EnableOnLoopedSounds"].IsBool())
- {
- spdlog::error("Failed reading audio override file {}: EnableOnLoopedSounds property is of invalid type (must be a bool)",
- path.string()); return;
- }
-
- EnableOnLoopedSounds = dataJson["EnableOnLoopedSounds"].GetBool();
- }
- */
-
- if (Samples.size() == 0)
- spdlog::warn("Audio override {} has no valid samples! Sounds will not play for this event.", path.string());
-
- spdlog::info("Loaded audio override file {}", path.string());
-
- LoadedSuccessfully = true;
-}
-
-bool CustomAudioManager::TryLoadAudioOverride(const fs::path& defPath)
-{
- if (IsDedicatedServer())
- return true; // silently fail
-
- std::ifstream jsonStream(defPath);
- std::stringstream jsonStringStream;
-
- // fail if no audio json
- if (jsonStream.fail())
- {
- spdlog::warn("Unable to read audio override from file {}", defPath.string());
- return false;
- }
-
- while (jsonStream.peek() != EOF)
- jsonStringStream << (char)jsonStream.get();
-
- jsonStream.close();
-
- std::shared_ptr<EventOverrideData> data = std::make_shared<EventOverrideData>(jsonStringStream.str(), defPath);
-
- if (!data->LoadedSuccessfully)
- return false; // no logging, the constructor has probably already logged
-
- for (const std::string& eventId : data->EventIds)
- {
- spdlog::info("Registering sound event {}", eventId);
- m_loadedAudioOverrides.insert({eventId, data});
- }
-
- for (const auto& eventIdRegexData : data->EventIdsRegex)
- {
- spdlog::info("Registering sound event regex {}", eventIdRegexData.first);
- m_loadedAudioOverridesRegex.insert({eventIdRegexData.first, data});
- }
-
- return true;
-}
-
-typedef void (*MilesStopAll_Type)();
-MilesStopAll_Type MilesStopAll;
-
-void CustomAudioManager::ClearAudioOverrides()
-{
- if (IsDedicatedServer())
- return;
-
- if (m_loadedAudioOverrides.size() > 0 || m_loadedAudioOverridesRegex.size() > 0)
- {
- // stop all miles sounds beforehand
- // miles_stop_all
-
- MilesStopAll();
-
- // this is cancer but it works
- Sleep(50);
- }
-
- // slightly (very) bad
- // wait for all audio reads to complete so we don't kill preexisting audio buffers as we're writing to them
- std::unique_lock lock(g_CustomAudioManager.m_loadingMutex);
-
- m_loadedAudioOverrides.clear();
- m_loadedAudioOverridesRegex.clear();
-}
-
-template <typename Iter, typename RandomGenerator> Iter select_randomly(Iter start, Iter end, RandomGenerator& g)
-{
- std::uniform_int_distribution<> dis(0, std::distance(start, end) - 1);
- std::advance(start, dis(g));
- return start;
-}
-
-template <typename Iter> Iter select_randomly(Iter start, Iter end)
-{
- static std::random_device rd;
- static std::mt19937 gen(rd());
- return select_randomly(start, end, gen);
-}
-
-bool ShouldPlayAudioEvent(const char* eventName, const std::shared_ptr<EventOverrideData>& data)
-{
- std::string eventNameString = eventName;
- std::string eventNameStringBlacklistEntry = ("!" + eventNameString);
-
- for (const std::string& name : data->EventIds)
- {
- if (name == eventNameStringBlacklistEntry)
- return false; // event blacklisted
-
- if (name == "*")
- {
- // check for bad sounds I guess?
- // really feel like this should be an option but whatever
- if (!!strstr(eventName, "_amb_") || !!strstr(eventName, "_emit_") || !!strstr(eventName, "amb_"))
- return false; // would play static noise, I hate this
- }
- }
-
- return true; // good to go
-}
-
-// forward declare
-bool __declspec(noinline) __fastcall LoadSampleMetadata_Internal(
- uintptr_t parentEvent, void* sample, void* audioBuffer, unsigned int audioBufferLength, int audioType);
-
-// DO NOT TOUCH THIS FUNCTION
-// The actual logic of it in a separate function (forcefully not inlined) to preserve the r12 register, which holds the event pointer.
-AUTOHOOK(LoadSampleMetadata, mileswin64.dll + 0xF110,
-bool, __fastcall, (void* sample, void* audioBuffer, unsigned int audioBufferLength, int audioType))
-{
- uintptr_t parentEvent = (uintptr_t)Audio_GetParentEvent();
-
- // Raw source, used for voice data only
- if (audioType == 0)
- return LoadSampleMetadata(sample, audioBuffer, audioBufferLength, audioType);
-
- return LoadSampleMetadata_Internal(parentEvent, sample, audioBuffer, audioBufferLength, audioType);
-}
-
-// DO NOT INLINE THIS FUNCTION
-// See comment below.
-bool __declspec(noinline) __fastcall LoadSampleMetadata_Internal(
- uintptr_t parentEvent, void* sample, void* audioBuffer, unsigned int audioBufferLength, int audioType)
-{
- char* eventName = (char*)parentEvent + 0x110;
-
- if (Cvar_ns_print_played_sounds->GetInt() > 0)
- spdlog::info("[AUDIO] Playing event {}", eventName);
-
- auto iter = g_CustomAudioManager.m_loadedAudioOverrides.find(eventName);
- std::shared_ptr<EventOverrideData> overrideData;
-
- if (iter == g_CustomAudioManager.m_loadedAudioOverrides.end())
- {
- // override for that specific event not found, try wildcard
- iter = g_CustomAudioManager.m_loadedAudioOverrides.find("*");
-
- if (iter == g_CustomAudioManager.m_loadedAudioOverrides.end())
- {
- // not found
-
- // try regex
- for (const auto& item : g_CustomAudioManager.m_loadedAudioOverridesRegex)
- for (const auto& regexData : item.second->EventIdsRegex)
- if (std::regex_search(eventName, regexData.second))
- overrideData = item.second;
-
- if (!overrideData)
- // not found either
- return LoadSampleMetadata(sample, audioBuffer, audioBufferLength, audioType);
- else
- {
- // cache found pattern to improve performance
- g_CustomAudioManager.m_loadedAudioOverrides[eventName] = overrideData;
- }
- }
- else
- overrideData = iter->second;
- }
- else
- overrideData = iter->second;
-
- if (!ShouldPlayAudioEvent(eventName, overrideData))
- return LoadSampleMetadata(sample, audioBuffer, audioBufferLength, audioType);
-
- void* data = 0;
- unsigned int dataLength = 0;
-
- if (overrideData->Samples.size() == 0)
- {
- // 0 samples, turn off this particular event.
-
- // using a dummy empty wave file
- data = EMPTY_WAVE;
- dataLength = sizeof(EMPTY_WAVE);
- }
- else
- {
- std::pair<size_t, std::unique_ptr<uint8_t[]>>* dat = NULL;
-
- switch (overrideData->Strategy)
- {
- case AudioSelectionStrategy::RANDOM:
- dat = &*select_randomly(overrideData->Samples.begin(), overrideData->Samples.end());
- break;
- case AudioSelectionStrategy::SEQUENTIAL:
- default:
- dat = &overrideData->Samples[overrideData->CurrentIndex++];
- if (overrideData->CurrentIndex >= overrideData->Samples.size())
- overrideData->CurrentIndex = 0; // reset back to the first sample entry
- break;
- }
-
- if (!dat)
- spdlog::warn("Could not get sample data from override struct for event {}! Shouldn't happen", eventName);
- else
- {
- data = dat->second.get();
- dataLength = dat->first;
- }
- }
-
- if (!data)
- {
- spdlog::warn("Could not fetch override sample data for event {}! Using original data instead.", eventName);
- return LoadSampleMetadata(sample, audioBuffer, audioBufferLength, audioType);
- }
-
- audioBuffer = data;
- audioBufferLength = dataLength;
-
- // most important change: set the sample class buffer so that the correct audio plays
- *(void**)((uintptr_t)sample + 0xE8) = audioBuffer;
- *(unsigned int*)((uintptr_t)sample + 0xF0) = audioBufferLength;
-
- // 64 - Auto-detect sample type
- bool res = LoadSampleMetadata(sample, audioBuffer, audioBufferLength, 64);
- if (!res)
- spdlog::error("LoadSampleMetadata failed! The game will crash :(");
-
- return res;
-}
-
-AUTOHOOK(MilesLog, client.dll + 0x57DAD0,
-void, __fastcall, (int level, const char* string))
-{
- spdlog::info("[MSS] {} - {}", level, string);
-}
-
-ON_DLL_LOAD_CLIENT_RELIESON("client.dll", AudioHooks, ConVar, (CModule module))
-{
- AUTOHOOK_DISPATCH()
-
- Cvar_ns_print_played_sounds = new ConVar("ns_print_played_sounds", "0", FCVAR_NONE, "");
- MilesStopAll = module.Offset(0x580850).As<MilesStopAll_Type>();
-}
\ No newline at end of file +#include "pch.h" +#include "audio.h" +#include "dedicated.h" +#include "convar.h" + +#include "rapidjson/error/en.h" +#include <fstream> +#include <iostream> +#include <sstream> +#include <random> + +AUTOHOOK_INIT() + +extern "C" +{ + // should be called only in LoadSampleMetadata_Hook + extern void* __fastcall Audio_GetParentEvent(); +} + +ConVar* Cvar_ns_print_played_sounds; + +CustomAudioManager g_CustomAudioManager; + +EventOverrideData::EventOverrideData() +{ + spdlog::warn("Initialised struct EventOverrideData without any data!"); + LoadedSuccessfully = false; +} + +// Empty stereo 48000 WAVE file +unsigned char EMPTY_WAVE[45] = {0x52, 0x49, 0x46, 0x46, 0x25, 0x00, 0x00, 0x00, 0x57, 0x41, 0x56, 0x45, 0x66, 0x6D, 0x74, + 0x20, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x44, 0xAC, 0x00, 0x00, 0x88, 0x58, + 0x01, 0x00, 0x02, 0x00, 0x10, 0x00, 0x64, 0x61, 0x74, 0x61, 0x74, 0x00, 0x00, 0x00, 0x00}; + +EventOverrideData::EventOverrideData(const std::string& data, const fs::path& path) +{ + if (data.length() <= 0) + { + spdlog::error("Failed reading audio override file {}: file is empty", path.string()); + return; + } + + fs::path samplesFolder = path; + samplesFolder = samplesFolder.replace_extension(); + + if (!fs::exists(samplesFolder)) + { + spdlog::error( + "Failed reading audio override file {}: samples folder doesn't exist; should be named the same as the definition file without " + "JSON extension.", + path.string()); + return; + } + + rapidjson_document dataJson; + dataJson.Parse<rapidjson::ParseFlag::kParseCommentsFlag | rapidjson::ParseFlag::kParseTrailingCommasFlag>(data); + + // fail if parse error + if (dataJson.HasParseError()) + { + spdlog::error( + "Failed reading audio override file {}: encountered parse error \"{}\" at offset {}", + path.string(), + GetParseError_En(dataJson.GetParseError()), + dataJson.GetErrorOffset()); + return; + } + + // fail if it's not a json obj (could be an array, string, etc) + if (!dataJson.IsObject()) + { + spdlog::error("Failed reading audio override file {}: file is not a JSON object", path.string()); + return; + } + + // fail if no event ids given + if (!dataJson.HasMember("EventId")) + { + spdlog::error("Failed reading audio override file {}: JSON object does not have the EventId property", path.string()); + return; + } + + // array of event ids + if (dataJson["EventId"].IsArray()) + { + for (auto& eventId : dataJson["EventId"].GetArray()) + { + if (!eventId.IsString()) + { + spdlog::error( + "Failed reading audio override file {}: EventId array has a value of invalid type, all must be strings", path.string()); + return; + } + + EventIds.push_back(eventId.GetString()); + } + } + // singular event id + else if (dataJson["EventId"].IsString()) + { + EventIds.push_back(dataJson["EventId"].GetString()); + } + // incorrect type + else + { + spdlog::error( + "Failed reading audio override file {}: EventId property is of invalid type (must be a string or an array of strings)", + path.string()); + return; + } + + if (dataJson.HasMember("EventIdRegex")) + { + // array of event id regex + if (dataJson["EventIdRegex"].IsArray()) + { + for (auto& eventId : dataJson["EventIdRegex"].GetArray()) + { + if (!eventId.IsString()) + { + spdlog::error( + "Failed reading audio override file {}: EventIdRegex array has a value of invalid type, all must be strings", + path.string()); + return; + } + + const std::string& regex = eventId.GetString(); + + try + { + EventIdsRegex.push_back({regex, std::regex(regex)}); + } + catch (...) + { + spdlog::error("Malformed regex \"{}\" in audio override file {}", regex, path.string()); + return; + } + } + } + // singular event id regex + else if (dataJson["EventIdRegex"].IsString()) + { + const std::string& regex = dataJson["EventIdRegex"].GetString(); + try + { + EventIdsRegex.push_back({regex, std::regex(regex)}); + } + catch (...) + { + spdlog::error("Malformed regex \"{}\" in audio override file {}", regex, path.string()); + return; + } + } + // incorrect type + else + { + spdlog::error( + "Failed reading audio override file {}: EventIdRegex property is of invalid type (must be a string or an array of strings)", + path.string()); + return; + } + } + + if (dataJson.HasMember("AudioSelectionStrategy")) + { + if (!dataJson["AudioSelectionStrategy"].IsString()) + { + spdlog::error("Failed reading audio override file {}: AudioSelectionStrategy property must be a string", path.string()); + return; + } + + std::string strategy = dataJson["AudioSelectionStrategy"].GetString(); + + if (strategy == "sequential") + { + Strategy = AudioSelectionStrategy::SEQUENTIAL; + } + else if (strategy == "random") + { + Strategy = AudioSelectionStrategy::RANDOM; + } + else + { + spdlog::error( + "Failed reading audio override file {}: AudioSelectionStrategy string must be either \"sequential\" or \"random\"", + path.string()); + return; + } + } + + // load samples + for (fs::directory_entry file : fs::recursive_directory_iterator(samplesFolder)) + { + if (file.is_regular_file() && file.path().extension().string() == ".wav") + { + std::string pathString = file.path().string(); + + // Open the file. + std::basic_ifstream<uint8_t> wavStream(pathString, std::ios::binary); + + if (wavStream.fail()) + { + spdlog::error("Failed reading audio sample {}", file.path().string()); + continue; + } + + // Get file size. + wavStream.seekg(0, std::ios::end); + size_t fileSize = wavStream.tellg(); + wavStream.close(); + + // Allocate enough memory for the file. + // blank out the memory for now, then read it later + uint8_t* data = new uint8_t[fileSize]; + memcpy(data, EMPTY_WAVE, sizeof(EMPTY_WAVE)); + Samples.push_back({fileSize, std::unique_ptr<uint8_t[]>(data)}); + + // thread off the file read + // should we spawn one thread per read? or should there be a cap to the number of reads at once? + std::thread readThread( + [pathString, fileSize, data] + { + std::shared_lock lock(g_CustomAudioManager.m_loadingMutex); + std::basic_ifstream<uint8_t> wavStream(pathString, std::ios::binary); + + // would be weird if this got hit, since it would've worked previously + if (wavStream.fail()) + { + spdlog::error("Failed async read of audio sample {}", pathString); + return; + } + + // read from after the header first to preserve the empty header, then read the header last + wavStream.seekg(0, std::ios::beg); + wavStream.read(data, fileSize); + wavStream.close(); + + spdlog::info("Finished async read of audio sample {}", pathString); + }); + + readThread.detach(); + } + } + + /* + if (dataJson.HasMember("EnableOnLoopedSounds")) + { + if (!dataJson["EnableOnLoopedSounds"].IsBool()) + { + spdlog::error("Failed reading audio override file {}: EnableOnLoopedSounds property is of invalid type (must be a bool)", + path.string()); return; + } + + EnableOnLoopedSounds = dataJson["EnableOnLoopedSounds"].GetBool(); + } + */ + + if (Samples.size() == 0) + spdlog::warn("Audio override {} has no valid samples! Sounds will not play for this event.", path.string()); + + spdlog::info("Loaded audio override file {}", path.string()); + + LoadedSuccessfully = true; +} + +bool CustomAudioManager::TryLoadAudioOverride(const fs::path& defPath) +{ + if (IsDedicatedServer()) + return true; // silently fail + + std::ifstream jsonStream(defPath); + std::stringstream jsonStringStream; + + // fail if no audio json + if (jsonStream.fail()) + { + spdlog::warn("Unable to read audio override from file {}", defPath.string()); + return false; + } + + while (jsonStream.peek() != EOF) + jsonStringStream << (char)jsonStream.get(); + + jsonStream.close(); + + std::shared_ptr<EventOverrideData> data = std::make_shared<EventOverrideData>(jsonStringStream.str(), defPath); + + if (!data->LoadedSuccessfully) + return false; // no logging, the constructor has probably already logged + + for (const std::string& eventId : data->EventIds) + { + spdlog::info("Registering sound event {}", eventId); + m_loadedAudioOverrides.insert({eventId, data}); + } + + for (const auto& eventIdRegexData : data->EventIdsRegex) + { + spdlog::info("Registering sound event regex {}", eventIdRegexData.first); + m_loadedAudioOverridesRegex.insert({eventIdRegexData.first, data}); + } + + return true; +} + +typedef void (*MilesStopAll_Type)(); +MilesStopAll_Type MilesStopAll; + +void CustomAudioManager::ClearAudioOverrides() +{ + if (IsDedicatedServer()) + return; + + if (m_loadedAudioOverrides.size() > 0 || m_loadedAudioOverridesRegex.size() > 0) + { + // stop all miles sounds beforehand + // miles_stop_all + + MilesStopAll(); + + // this is cancer but it works + Sleep(50); + } + + // slightly (very) bad + // wait for all audio reads to complete so we don't kill preexisting audio buffers as we're writing to them + std::unique_lock lock(g_CustomAudioManager.m_loadingMutex); + + m_loadedAudioOverrides.clear(); + m_loadedAudioOverridesRegex.clear(); +} + +template <typename Iter, typename RandomGenerator> Iter select_randomly(Iter start, Iter end, RandomGenerator& g) +{ + std::uniform_int_distribution<> dis(0, std::distance(start, end) - 1); + std::advance(start, dis(g)); + return start; +} + +template <typename Iter> Iter select_randomly(Iter start, Iter end) +{ + static std::random_device rd; + static std::mt19937 gen(rd()); + return select_randomly(start, end, gen); +} + +bool ShouldPlayAudioEvent(const char* eventName, const std::shared_ptr<EventOverrideData>& data) +{ + std::string eventNameString = eventName; + std::string eventNameStringBlacklistEntry = ("!" + eventNameString); + + for (const std::string& name : data->EventIds) + { + if (name == eventNameStringBlacklistEntry) + return false; // event blacklisted + + if (name == "*") + { + // check for bad sounds I guess? + // really feel like this should be an option but whatever + if (!!strstr(eventName, "_amb_") || !!strstr(eventName, "_emit_") || !!strstr(eventName, "amb_")) + return false; // would play static noise, I hate this + } + } + + return true; // good to go +} + +// forward declare +bool __declspec(noinline) __fastcall LoadSampleMetadata_Internal( + uintptr_t parentEvent, void* sample, void* audioBuffer, unsigned int audioBufferLength, int audioType); + +// DO NOT TOUCH THIS FUNCTION +// The actual logic of it in a separate function (forcefully not inlined) to preserve the r12 register, which holds the event pointer. +AUTOHOOK(LoadSampleMetadata, mileswin64.dll + 0xF110, +bool, __fastcall, (void* sample, void* audioBuffer, unsigned int audioBufferLength, int audioType)) +{ + uintptr_t parentEvent = (uintptr_t)Audio_GetParentEvent(); + + // Raw source, used for voice data only + if (audioType == 0) + return LoadSampleMetadata(sample, audioBuffer, audioBufferLength, audioType); + + return LoadSampleMetadata_Internal(parentEvent, sample, audioBuffer, audioBufferLength, audioType); +} + +// DO NOT INLINE THIS FUNCTION +// See comment below. +bool __declspec(noinline) __fastcall LoadSampleMetadata_Internal( + uintptr_t parentEvent, void* sample, void* audioBuffer, unsigned int audioBufferLength, int audioType) +{ + char* eventName = (char*)parentEvent + 0x110; + + if (Cvar_ns_print_played_sounds->GetInt() > 0) + spdlog::info("[AUDIO] Playing event {}", eventName); + + auto iter = g_CustomAudioManager.m_loadedAudioOverrides.find(eventName); + std::shared_ptr<EventOverrideData> overrideData; + + if (iter == g_CustomAudioManager.m_loadedAudioOverrides.end()) + { + // override for that specific event not found, try wildcard + iter = g_CustomAudioManager.m_loadedAudioOverrides.find("*"); + + if (iter == g_CustomAudioManager.m_loadedAudioOverrides.end()) + { + // not found + + // try regex + for (const auto& item : g_CustomAudioManager.m_loadedAudioOverridesRegex) + for (const auto& regexData : item.second->EventIdsRegex) + if (std::regex_search(eventName, regexData.second)) + overrideData = item.second; + + if (!overrideData) + // not found either + return LoadSampleMetadata(sample, audioBuffer, audioBufferLength, audioType); + else + { + // cache found pattern to improve performance + g_CustomAudioManager.m_loadedAudioOverrides[eventName] = overrideData; + } + } + else + overrideData = iter->second; + } + else + overrideData = iter->second; + + if (!ShouldPlayAudioEvent(eventName, overrideData)) + return LoadSampleMetadata(sample, audioBuffer, audioBufferLength, audioType); + + void* data = 0; + unsigned int dataLength = 0; + + if (overrideData->Samples.size() == 0) + { + // 0 samples, turn off this particular event. + + // using a dummy empty wave file + data = EMPTY_WAVE; + dataLength = sizeof(EMPTY_WAVE); + } + else + { + std::pair<size_t, std::unique_ptr<uint8_t[]>>* dat = NULL; + + switch (overrideData->Strategy) + { + case AudioSelectionStrategy::RANDOM: + dat = &*select_randomly(overrideData->Samples.begin(), overrideData->Samples.end()); + break; + case AudioSelectionStrategy::SEQUENTIAL: + default: + dat = &overrideData->Samples[overrideData->CurrentIndex++]; + if (overrideData->CurrentIndex >= overrideData->Samples.size()) + overrideData->CurrentIndex = 0; // reset back to the first sample entry + break; + } + + if (!dat) + spdlog::warn("Could not get sample data from override struct for event {}! Shouldn't happen", eventName); + else + { + data = dat->second.get(); + dataLength = dat->first; + } + } + + if (!data) + { + spdlog::warn("Could not fetch override sample data for event {}! Using original data instead.", eventName); + return LoadSampleMetadata(sample, audioBuffer, audioBufferLength, audioType); + } + + audioBuffer = data; + audioBufferLength = dataLength; + + // most important change: set the sample class buffer so that the correct audio plays + *(void**)((uintptr_t)sample + 0xE8) = audioBuffer; + *(unsigned int*)((uintptr_t)sample + 0xF0) = audioBufferLength; + + // 64 - Auto-detect sample type + bool res = LoadSampleMetadata(sample, audioBuffer, audioBufferLength, 64); + if (!res) + spdlog::error("LoadSampleMetadata failed! The game will crash :("); + + return res; +} + +AUTOHOOK(MilesLog, client.dll + 0x57DAD0, +void, __fastcall, (int level, const char* string)) +{ + spdlog::info("[MSS] {} - {}", level, string); +} + +ON_DLL_LOAD_CLIENT_RELIESON("client.dll", AudioHooks, ConVar, (CModule module)) +{ + AUTOHOOK_DISPATCH() + + Cvar_ns_print_played_sounds = new ConVar("ns_print_played_sounds", "0", FCVAR_NONE, ""); + MilesStopAll = module.Offset(0x580850).As<MilesStopAll_Type>(); +} |