diff --git a/CMakeLists.txt b/CMakeLists.txt index 75a8d01151..9dc478ce2f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -90,6 +90,7 @@ option(DUSK_ENABLE_SENTRY_NATIVE "Enable sentry-native crash reporting support" option(DUSK_PACKAGE_INSTALL "Install Dusklight with a Linux-native file structure" OFF) option(DUSK_GFX_DEBUG_GROUPS "Report debug groups to the native graphics API" ${DUSK_GFX_DEBUG_GROUPS_DEFAULT}) option(DUSK_ENABLE_CODE_MODS "Enable code mods" OFF) +option(DUSK_ENABLE_OPUS "Enable loading Opus audio files for mods" ON) # Edit & Continue if (MSVC) @@ -219,6 +220,25 @@ FetchContent_Declare(miniz ) set(_fetch_content_deps cxxopts json miniz) + +if (DUSK_ENABLE_OPUS) + message(STATUS "dusklight: Fetching opusfile") + + # Opusfile options + SET(OP_DISABLE_HTTP ON CACHE BOOL "" FORCE) + SET(OP_DISABLE_DOCS ON CACHE BOOL "" FORCE) + SET(OP_DISABLE_EXAMPLES ON CACHE BOOL "" FORCE) + + message(STATUS "dusklight: Fetching opusfile") + FetchContent_Declare( + opusfile + GIT_REPOSITORY https://github.com/xiph/opusfile.git + GIT_TAG 6dfd29e7adb87f2e193575fc3fa88cbf1a0b27df + ) + + list(APPEND _fetch_content_deps opusfile) +endif () + if (DUSK_ENABLE_CODE_MODS) message(STATUS "dusklight: Fetching funchook") # cmake/PatchFunchook.cmake patches funchook's cmake/capstone.cmake.in to inject a @@ -311,6 +331,10 @@ set(GAME_LIBS aurora::core aurora::gx aurora::gd aurora::si aurora::vi aurora::p if (DUSK_ENABLE_CODE_MODS) list(APPEND GAME_LIBS funchook-static) endif () +if (DUSK_ENABLE_OPUS) + list(APPEND GAME_LIBS OpusFile::opusfile) + list(APPEND GAME_COMPILE_DEFS DUSK_OPUS=1) +endif () if (DUSK_ENABLE_SENTRY_NATIVE) list(APPEND GAME_LIBS sentry) @@ -568,6 +592,7 @@ if (DUSK_ENABLE_CODE_MODS AND CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR add_subdirectory(mods/template_mod) add_subdirectory(mods/ao_mod) add_subdirectory(mods/shadow_mod) + add_subdirectory(mods/audio_mod) endif () if (APPLE) diff --git a/files.cmake b/files.cmake index 51b2bdf95e..74e6ef0e69 100644 --- a/files.cmake +++ b/files.cmake @@ -1483,6 +1483,10 @@ set(DUSK_FILES src/dusk/mods/log_buffer.hpp src/dusk/mods/manifest.cpp src/dusk/mods/manifest.hpp + src/dusk/mods/svc/audio_res/audio_res.hpp + src/dusk/mods/svc/audio_res/audio_res.cpp + src/dusk/mods/svc/audio_res/wave.cpp + src/dusk/mods/svc/audio_res/opus.cpp src/dusk/mods/svc/camera.cpp src/dusk/mods/svc/config.cpp src/dusk/mods/svc/config.hpp diff --git a/libs/JSystem/src/JAudio2/JASBank.cpp b/libs/JSystem/src/JAudio2/JASBank.cpp index 69dd05795f..e96b4df174 100644 --- a/libs/JSystem/src/JAudio2/JASBank.cpp +++ b/libs/JSystem/src/JAudio2/JASBank.cpp @@ -1,11 +1,15 @@ #include "JSystem/JSystem.h" // IWYU pragma: keep #include "JSystem/JAudio2/JASBank.h" + +#include "dusk/mods/svc/audio_res/audio_res.hpp" #include "JSystem/JAudio2/JASAiCtrl.h" #include "JSystem/JAudio2/JASBasicInst.h" #include "JSystem/JAudio2/JASBasicWaveBank.h" #include "JSystem/JAudio2/JASChannel.h" +using namespace dusk::mods::svc::audio_res; + // NONMATCHING JASPoolAllocObject_MultiThreaded<_> locations JASChannel* JASBank::noteOn(JASBank const* param_0, int param_1, u8 param_2, u8 param_3, u16 param_4, void (*param_5)(u32, JASChannel*, JASDsp::TChannel*, void*), @@ -28,6 +32,13 @@ JASChannel* JASBank::noteOn(JASBank const* param_0, int param_1, u8 param_2, u8 if (!waveHandle) { return NULL; } +#if TARGET_PC + auto const found_replacement = s_replacements.find(AudioWaveKey(SoundEffects, stack_60.field_0x1a)); + if (found_replacement != s_replacements.end()) { + waveHandle = found_replacement->second.get(); + } +#endif + const JASWaveInfo* waveInfo = waveHandle->getWaveInfo(); if (!waveInfo) { return NULL; diff --git a/sdk/include/mods/svc/audio_res.h b/sdk/include/mods/svc/audio_res.h new file mode 100644 index 0000000000..f5ec3693dc --- /dev/null +++ b/sdk/include/mods/svc/audio_res.h @@ -0,0 +1,65 @@ +#pragma once + +#include + +#define AUDIO_RES_SERVICE_ID "dev.twilitrealm.dusklight.audio_res" +#define AUDIO_RES_SERVICE_MAJOR 1u +#define AUDIO_RES_SERVICE_MINOR 0u + +#define AUDIO_RES_DEFAULT_KEY 0x3C; + +typedef enum AudioWaveBank : uint8_t { + SoundEffects = 0, + MusicSamples = 1, +} AudioWaveBank; + +typedef enum AudioWaveFormat : uint8_t { + Adpcm4 = 0, + Adpcm2 = 1, + Pcm8 = 2, + Pcm16 = 3, +} AudioWaveFormat; + +typedef uint64_t AudioWaveHandle; + +/** + * + */ +typedef struct AudioRawWave { + AudioWaveFormat format; + float sample_rate; + int16_t sample_value_last; + int16_t sample_value_penult; +} AudioRawWave; + +typedef struct AudioWaveInfo { + uint8_t base_key; + bool loop; + uint32_t loop_start_sample; + uint32_t loop_end_sample; + + AudioRawWave const* raw_wave; +} AudioWaveInfo; + +typedef struct AudioResService { + ServiceHeader header; + + ModResult (*replace_wave)( + ModContext* ctx, + AudioWaveBank bank, + uint16_t wave_id, + char const* file_name, + AudioWaveInfo const* wave_info, + AudioWaveHandle* out_handle); +} AudioResService; + +#ifdef __cplusplus +#include "mods/service.hpp" + +template <> +struct mods::ServiceTraits { + static constexpr const char* id = AUDIO_RES_SERVICE_ID; + static constexpr uint16_t major_version = AUDIO_RES_SERVICE_MAJOR; + static constexpr uint16_t minor_version = AUDIO_RES_SERVICE_MINOR; +}; +#endif diff --git a/src/dusk/mods/svc/audio_res/audio_res.cpp b/src/dusk/mods/svc/audio_res/audio_res.cpp new file mode 100644 index 0000000000..20b3e8a95f --- /dev/null +++ b/src/dusk/mods/svc/audio_res/audio_res.cpp @@ -0,0 +1,227 @@ +#include "audio_res.hpp" + +#include "helpers/cast.hpp" +#include "mods/svc/audio_res.h" + +#include "../registry.hpp" +#include "../slot_map.hpp" +#include "aurora/lib/logging.hpp" +#include "dusk/mods/loader/loader.hpp" + +namespace dusk::mods::svc { + +namespace audio_res { +namespace { + +using namespace dusk::helpers::cast; + +aurora::Module Log("dusk::mods::svc::audio_res"); + +SlotMap s_waveReplacements; + +constexpr ContainerLoadFunction s_containerLoadFunctions[] = { + load_wav, +#if DUSK_OPUS + load_opus, +#endif +}; + +bool validate_raw_size(LoadedMod const& mod, std::string const& path, uintptr_t actual_size, AudioRawWave const& raw, u32& sample_count) { + u32 samples_per_block; + u32 bytes_per_block; + switch (raw.format) { + case Adpcm4: + samples_per_block = 16; + bytes_per_block = 9; + break; + case Pcm16: + samples_per_block = 1; + bytes_per_block = 2; + break; + default: + Log.error("[{}] unimplemented wave format (ADPCM2/PCM8): '{}'", mod.metadata.id, path); + return false; + } + + if (actual_size % bytes_per_block != 0) { + Log.error("[{}] raw file is not divisible by format block size: '{}'", mod.metadata.id, path); + return false; + } + + sample_count = (actual_size / bytes_per_block) * samples_per_block; + return true; +} + +ModResult load_raw( + LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, AudioRawWave const& raw) { + auto file_contents = mod.bundle->readFile(slot.bundle_path); + u32 sample_count = 0; + + if (!validate_raw_size(mod, slot.bundle_path, file_contents.size(), raw, sample_count)) { + Log.error( + "[{}] replace_wave: file '{}' is a raw .abf file, but raw_wave data was not specified", + mod.metadata.id, slot.bundle_path); + + return MOD_INVALID_ARGUMENT; + } + + if (slot.format == Pcm16) { + SampleDataPcm16 pcm16; + pcm16.data.resize(sample_count); + memcpy(pcm16.data.data(), file_contents.data(), file_contents.size()); + slot.data = std::make_shared(std::move(pcm16)); + } else { + slot.data = std::make_shared(std::move(file_contents)); + } + + slot.sample_count = sample_count; + slot.sample_rate = raw.sample_rate; + slot.format = raw.format; + slot.sample_value_penult = raw.sample_value_penult; + slot.sample_value_last = raw.sample_value_last; + return MOD_OK; +} + +/** + * Load an audio file stored in some sort of container format. + * File type is determined by looking at contents, not extension. + */ +ModResult load_container(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot) { + auto file_contents = mod.bundle->readFile(slot.bundle_path); + + for (auto const loader : s_containerLoadFunctions) { + auto const result = loader(mod, slot, file_contents); + if (result == MOD_OK) { + return MOD_OK; + } + + if (result != MOD_UNSUPPORTED) { + return result; + } + } + + return MOD_UNSUPPORTED; +} + +JASWaveInfo wave_info_from_slot(RuntimeWaveReplacementSlot const& slot) { + JASWaveInfo info; + info.mWaveFormat = static_cast(slot.format); + info.mBaseKey = slot.base_key; + info.mLoopFlag = slot.loop ? 0xFF : 0; + info.mSampleRate = slot.sample_rate; + info.mOffsetStart = 0; // Unused but just init it ig. + info.mOffsetLength = bounded_cast(slot.data->size()); + info.mLoopStartSample = slot.loop_start_sample; + info.mLoopEndSample = bounded_cast(slot.loop_end_sample); + info.mSampleCount = bounded_cast(slot.sample_count); + info.mpLast = slot.sample_value_last; + info.mpPenult = slot.sample_value_penult; + // mpLoaded is initialized to point to a 1, so we're good there. + return info; +} + +ModResult insert_replace_wave( + ModContext* ctx, + AudioWaveBank bank, + u16 wave_id, + char const* file_name, + AudioWaveInfo const* wave_info, + AudioWaveHandle* out_handle) { + if (out_handle != nullptr) { + *out_handle = 0; + } + + auto mod = mod_from_context(ctx); + if (mod == nullptr || file_name == nullptr || !is_safe_resource_path(file_name)) { + return MOD_INVALID_ARGUMENT; + } + + RuntimeWaveReplacementSlot slot{}; + slot.bundle_path = file_name; + slot.bank = bank; + slot.wave_id = wave_id; + + ModResult result; + if (wave_info && wave_info->raw_wave) { + result = load_raw(*mod, slot, *wave_info->raw_wave); + } else { + result = load_container(*mod, slot); + } + if (result != MOD_OK) { + return result; + } + + if (wave_info != nullptr) { + slot.base_key = wave_info->base_key; + slot.loop = wave_info->loop; + slot.loop_start_sample = wave_info->loop_start_sample; + slot.loop_end_sample = wave_info->loop_end_sample; + } else { + slot.base_key = AUDIO_RES_DEFAULT_KEY; + slot.loop = false; + slot.loop_start_sample = 0; + slot.loop_end_sample = slot.sample_count; + } + + // TODO: Handle conflicts + + audio_res::AudioWaveReplacementValue value; + value.data = slot.data; + value.wave_info = wave_info_from_slot(slot); + audio_res::s_replacements.emplace( + audio_res::AudioWaveKey{.bank = bank, .wave_id = wave_id}, + std::make_unique(std::move(value))); + + const auto handle = s_waveReplacements.emplace(*mod, std::move(slot)); + if (out_handle) { + *out_handle = handle; + } + + return MOD_OK; +} + +constexpr AudioResService s_audioResService{ + .header = SERVICE_HEADER(AudioResService, AUDIO_RES_SERVICE_MAJOR, AUDIO_RES_SERVICE_MINOR), + .replace_wave = &audio_res::insert_replace_wave}; + +} + +absl::flat_hash_map> s_replacements; + +AudioWaveReplacementValue::~AudioWaveReplacementValue() = default; +const JASWaveInfo* AudioWaveReplacementValue::getWaveInfo() const { + return &wave_info; +} + +void const* AudioWaveReplacementValue::getAramBaseAddress() const { + return data->get_data().data(); +} + +intptr_t AudioWaveReplacementValue::getWavePtr() const { + return 0; +} + +void SampleDataPcm16::be_swap() { + for (auto& sample : data) { + ::be_swap(sample); + } +} + +} // namespace audio_res + +constinit const ServiceModule g_audioResModule{ + .id = AUDIO_RES_SERVICE_ID, + .majorVersion = AUDIO_RES_SERVICE_MAJOR, + .minorVersion = AUDIO_RES_SERVICE_MINOR, + .service = &audio_res::s_audioResService, + // .modDetached = overlay_remove_mod, + // .lifecycleApplied = overlay_sync_files, + // .frameEnd = + // [] { + // if (consume_overlays_dirty()) { + // overlay_sync_files(); + // } + // }, +}; + +} diff --git a/src/dusk/mods/svc/audio_res/audio_res.hpp b/src/dusk/mods/svc/audio_res/audio_res.hpp new file mode 100644 index 0000000000..b336dbeda8 --- /dev/null +++ b/src/dusk/mods/svc/audio_res/audio_res.hpp @@ -0,0 +1,93 @@ +#pragma once + +#include +#include "JSystem/JAudio2/JASWaveInfo.h" +#include "absl/container/flat_hash_map.h" +#include "mods/svc/audio_res.h" + +namespace dusk::mods { +struct LoadedMod; +} +namespace dusk::mods::svc::audio_res { + +struct SampleDataBuf { + virtual ~SampleDataBuf() = default; + [[nodiscard]] virtual std::span get_data() const = 0; + [[nodiscard]] size_t size() const { + return get_data().size(); + } +}; + +// Making a separate type for this rather than just using a vector +// because I don't want aliasing or alignment to eat my face. +struct SampleDataPcm16 : SampleDataBuf { + std::vector data; + + [[nodiscard]] std::span get_data() const override { + return as_bytes(std::span(data)); + } + + void be_swap(); +}; + +struct SampleDataU8 : SampleDataBuf { + std::vector data; + + explicit SampleDataU8(std::vector data) : data(std::move(data)) {} + + [[nodiscard]] std::span get_data() const override { + return as_bytes(std::span(data)); + } +}; + +struct AudioWaveKey { + AudioWaveBank bank; + u32 wave_id; + + template + friend H AbslHashValue(H h, const AudioWaveKey& k) { + return H::combine(std::move(h), k.bank, k.wave_id); + } + + [[nodiscard]] bool operator==(const AudioWaveKey& other) const { + return other.bank == bank && other.wave_id == wave_id; + } +}; + +struct AudioWaveReplacementValue : JASWaveHandle { + ~AudioWaveReplacementValue() override; + [[nodiscard]] const JASWaveInfo* getWaveInfo() const override; + [[nodiscard]] intptr_t getWavePtr() const override; + [[nodiscard]] void const* getAramBaseAddress() const override; + + JASWaveInfo wave_info; + std::shared_ptr data; +}; + +extern absl::flat_hash_map> s_replacements; + +struct RuntimeWaveReplacementSlot { + std::string bundle_path; + AudioWaveBank bank; + u16 wave_id; + + u8 base_key; + bool loop; + u32 loop_start_sample; + u32 loop_end_sample; + + AudioWaveFormat format; + f32 sample_rate; + u32 sample_count; + s16 sample_value_last; + s16 sample_value_penult; + + std::shared_ptr data; +}; + +using ContainerLoadFunction = ModResult (*)(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData); + +ModResult load_wav(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData); +ModResult load_opus(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData); + +} diff --git a/src/dusk/mods/svc/audio_res/opus.cpp b/src/dusk/mods/svc/audio_res/opus.cpp new file mode 100644 index 0000000000..3541f6be81 --- /dev/null +++ b/src/dusk/mods/svc/audio_res/opus.cpp @@ -0,0 +1,93 @@ +#if DUSK_OPUS + +#include "audio_res.hpp" +#include "aurora/lib/logging.hpp" +#include "dusk/mod_loader.hpp" +#include "helpers/cast.hpp" +#include "opusfile.h" + +namespace { + +aurora::Module Log("dusk::mods::svc::audio_res::opus"); + +struct OpusHandle { + OggOpusFile* file; + + ~OpusHandle() { + op_free(file); + } + + operator OggOpusFile*() const { + return file; + } +}; + +} + +namespace dusk::mods::svc::audio_res { +ModResult load_opus(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData) { + OpusHead head; + const int result = op_test(&head, fileData.data(), fileData.size()); + if (result != 0) { + return MOD_UNSUPPORTED; + } + + if (head.channel_count != 1) { + Log.error("[{}] replace_wave: file '{}' must be mono but actually has {} channels", mod.metadata.id, slot.bundle_path, head.channel_count); + return MOD_INVALID_ARGUMENT; + } + + if (head.stream_count != 1) { + Log.error("[{}] replace_wave: file '{}' has multiple Opus streams!", mod.metadata.id, slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + int error; + auto const handle_raw = op_open_memory(fileData.data(), fileData.size(), &error); + if (error != 0) { + Log.error("[{}] replace_wave: file '{}': unable to open Opus file: {}", mod.metadata.id, slot.bundle_path, error); + return MOD_ERROR; + } + + const OpusHandle handle(handle_raw); + auto const length = op_pcm_total(handle, -1); + if (length < 0) { + Log.error("[{}] replace_wave: file '{}': failed to check stream length?", mod.metadata.id, slot.bundle_path); + return MOD_ERROR; + } + + SampleDataPcm16 pcm_buffer; + pcm_buffer.data.resize(length); + + std::span span_pcm(pcm_buffer.data); + + while (!span_pcm.empty()) { + auto read_result = op_read( + handle, + span_pcm.data(), helpers::cast::bounded_cast(span_pcm.size()), + nullptr); + + if (read_result <= 0) { + Log.error("[{}] replace_wave: file '{}': failed to read Opus: {}", mod.metadata.id, slot.bundle_path, read_result); + return MOD_ERROR; + } + + if (read_result > span_pcm.size()) { + Log.fatal("What teh fuck?"); + } + + span_pcm = span_pcm.subspan(read_result); + } + + pcm_buffer.be_swap(); + + slot.data = std::make_shared(std::move(pcm_buffer)); + slot.sample_rate = 48000; // Opus always decodes at 48 kHz. + slot.sample_count = length; + slot.format = Pcm16; + + return MOD_OK; +} +} + +#endif diff --git a/src/dusk/mods/svc/audio_res/wave.cpp b/src/dusk/mods/svc/audio_res/wave.cpp new file mode 100644 index 0000000000..69e1e2e10f --- /dev/null +++ b/src/dusk/mods/svc/audio_res/wave.cpp @@ -0,0 +1,145 @@ +#include "audio_res.hpp" +#include "aurora/lib/logging.hpp" +#include "dusk/mod_loader.hpp" +#include "helpers/alignment.hpp" + +namespace { + +aurora::Module Log("dusk::mods::svc::audio_res::wav"); + +struct ChunkHeader { + char magic[4]; + LE(u32) size; +}; + +struct RiffChunk { + ChunkHeader header; // magic "RIFF" + char id[4]; // "WAVE" + // Data after chunk. +}; + +constexpr u16 WAVE_FORMAT_PCM = 0x0001; + +struct FmtPcmChunkData { + ChunkHeader header; + + u16 wFormatTag; + u16 nChannels; + u32 nSamplesPerSec; + u32 nAvgBytesPerSec; + u16 nBlockAlign; + u16 wBitsPerSample; +}; + +bool check_four_cc(char const (&field)[4], char const (&expected)[5]) { + return memcmp(field, expected, 4) == 0; +} + +} + +namespace dusk::mods::svc::audio_res { + +ModResult load_wav(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData) { + using namespace helpers::alignment; + + if (fileData.size() < sizeof(RiffChunk)) { + return MOD_UNSUPPORTED; + } + + auto const riffChunk = read_unaligned(&fileData[0]); + if (!check_four_cc(riffChunk.header.magic, "RIFF")) { + return MOD_UNSUPPORTED; + } + + if (!check_four_cc(riffChunk.id, "WAVE")) { + return MOD_UNSUPPORTED; + } + + if (sizeof(ChunkHeader) + riffChunk.header.size > fileData.size()) { + Log.error("[{}] wav file {}: invalid size!", mod.metadata.id, slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + bool has_read_fmt = false; + SampleDataPcm16 pcmData; + + auto waveData = fileData.subspan(sizeof(RiffChunk), riffChunk.header.size); + while (waveData.size() > sizeof(ChunkHeader)) { + auto const chunkHeader = read_unaligned(&waveData[0]); + + if (check_four_cc(chunkHeader.magic, "fmt ")) { + if (has_read_fmt) { + Log.error("[{}] wav file {}: multiple fmt chunks!", mod.metadata.id, slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + if (waveData.size() < sizeof(FmtPcmChunkData)) { + Log.error("[{}] wav file {}: fmt chunk too small!", mod.metadata.id, slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + auto const fmtChunk = read_unaligned(&waveData[0]); + + if (fmtChunk.nChannels != 1) { + Log.error("[{}] wav file {}: only mono audio is supported", mod.metadata.id, slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + if (fmtChunk.wFormatTag != WAVE_FORMAT_PCM) { + Log.error("[{}] wav file {}: only 16-bit PCM is supported", mod.metadata.id, slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + if (fmtChunk.wBitsPerSample != 16) { + Log.error("[{}] wav file {}: only 16-bit PCM is supported", mod.metadata.id, slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + has_read_fmt = true; + slot.sample_rate = static_cast(fmtChunk.nSamplesPerSec); + + } else if (check_four_cc(chunkHeader.magic, "data")) { + if (sizeof(chunkHeader) + chunkHeader.size > fileData.size()) { + Log.error("[{}] wav file {}: unexpected EOF reading sample data", mod.metadata.id, slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + auto const sampleData = waveData.subspan(sizeof(chunkHeader), chunkHeader.size); + for (size_t i = 0; i + 1 < sampleData.size(); i += 2) { + pcmData.data.push_back(read_unaligned(&sampleData[i])); + } + } + + auto skip = sizeof(chunkHeader) + chunkHeader.size; + if (skip % 2 != 0) { + skip += 1; + } + + if (waveData.size() < skip) { + Log.error("[{}] wav file {}: unexpected EOF", mod.metadata.id, slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + waveData = waveData.subspan(skip); + } + + if (!has_read_fmt) { + Log.error("[{}] wav file {}: missing fmt chunk", mod.metadata.id, slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + if (pcmData.data.empty()) { + Log.error("[{}] wav file {}: missing data chunk", mod.metadata.id, slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + pcmData.be_swap(); + + slot.format = Pcm16; + slot.sample_count = pcmData.size() / sizeof(u16); + slot.data = std::make_unique(std::move(pcmData)); + + return MOD_OK; +} + +} diff --git a/src/dusk/mods/svc/registry.cpp b/src/dusk/mods/svc/registry.cpp index 2033d47475..a9d82e0993 100644 --- a/src/dusk/mods/svc/registry.cpp +++ b/src/dusk/mods/svc/registry.cpp @@ -210,6 +210,7 @@ void ModLoader::init_services() { &svc::g_gameModule, &svc::g_cameraModule, &svc::g_gfxModule, + &svc::g_audioResModule, }) { svc::register_module(*module); diff --git a/src/dusk/mods/svc/registry.hpp b/src/dusk/mods/svc/registry.hpp index 191626e0ef..a2f597def4 100644 --- a/src/dusk/mods/svc/registry.hpp +++ b/src/dusk/mods/svc/registry.hpp @@ -72,5 +72,6 @@ extern const ServiceModule g_uiModule; extern const ServiceModule g_gameModule; extern const ServiceModule g_cameraModule; extern const ServiceModule g_gfxModule; +extern const ServiceModule g_audioResModule; } // namespace dusk::mods::svc