From 0b3302af70f4f6209b91c0ece01a3d38ee8d624e Mon Sep 17 00:00:00 2001 From: nmzik Date: Mon, 24 Aug 2026 08:42:07 +0200 Subject: [PATCH] audio: parse NGS2 ATRAC9 waveform metadata --- src/libs/audio.cpp | 155 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 145 insertions(+), 10 deletions(-) diff --git a/src/libs/audio.cpp b/src/libs/audio.cpp index 14d9b09a7..06db7c4aa 100644 --- a/src/libs/audio.cpp +++ b/src/libs/audio.cpp @@ -21,6 +21,8 @@ #include #include +#include "libatrac9.h" + namespace Libs::Audio { namespace { @@ -1360,6 +1362,17 @@ namespace Ngs2 { LIB_NAME("Ngs2", "Ngs2"); +constexpr int32_t NGS2_ERROR_INVALID_OUT_ADDRESS = + static_cast(0x804a8010u); +constexpr int32_t NGS2_ERROR_INVALID_WAVEFORM_DATA = + static_cast(0x804a8430u); +constexpr int32_t NGS2_ERROR_INVALID_WAVEFORM_FORMAT = + static_cast(0x804a8431u); +constexpr int32_t NGS2_ERROR_UNKNOWN_WAVEFORM_FORMAT = + static_cast(0x804a8432u); + +constexpr uint32_t NGS2_WAVEFORM_TYPE_ATRAC9 = 0x40; + struct Ngs2SystemOption { size_t size = 0; char name[64] = {}; @@ -1773,6 +1786,9 @@ static_assert(sizeof(Ngs2VoiceState) == 8); static_assert(sizeof(Ngs2SubmixerVoiceState) == 20); static_assert(sizeof(Ngs2CustomMasteringVoiceState) == 16); static_assert(sizeof(Ngs2SamplerVoiceState) == 56); +static_assert(sizeof(Ngs2WaveformFormat) == 24); +static_assert(sizeof(Ngs2WaveformBlock) == 40); +static_assert(sizeof(Ngs2WaveformInfo) == 232); static uint32_t Ngs2GetStateFlags(const Ngs2VoiceInternal* voice) { switch (voice->state) { @@ -2443,24 +2459,143 @@ int KYTY_SYSV_ABI Ngs2SystemRender(uintptr_t system_handle, const Ngs2RenderBuff return OK; } +static uint16_t Ngs2ReadLe16(const uint8_t* data) { + return static_cast(data[0]) | (static_cast(data[1]) << 8u); +} + +static uint32_t Ngs2ReadLe32(const uint8_t* data) { + return static_cast(data[0]) | (static_cast(data[1]) << 8u) | + (static_cast(data[2]) << 16u) | (static_cast(data[3]) << 24u); +} + +static bool Ngs2FourCcEquals(const uint8_t* data, const char* four_cc) { + return std::memcmp(data, four_cc, 4) == 0; +} + +static int Ngs2ParseAtrac9Riff(const void* data, size_t data_size, Ngs2WaveformInfo* info) { + static constexpr uint8_t ATRAC9_GUID[16] = {0xd2, 0x42, 0xe1, 0x47, 0xba, 0x36, + 0x8d, 0x4d, 0x88, 0xfc, 0x61, 0x65, + 0x4f, 0x8c, 0x83, 0x6c}; + const auto* bytes = static_cast(data); + if (bytes == nullptr || data_size < 12) { + return NGS2_ERROR_INVALID_WAVEFORM_DATA; + } + if (!Ngs2FourCcEquals(bytes, "RIFF") || !Ngs2FourCcEquals(bytes + 8, "WAVE")) { + return NGS2_ERROR_UNKNOWN_WAVEFORM_FORMAT; + } + + const uint64_t riff_end64 = 8ull + Ngs2ReadLe32(bytes + 4); + if (riff_end64 < 12 || riff_end64 > data_size) { + return NGS2_ERROR_INVALID_WAVEFORM_DATA; + } + const auto riff_end = static_cast(riff_end64); + + const uint8_t* format = nullptr; + const uint8_t* fact = nullptr; + size_t waveform_offset = 0; + uint32_t waveform_size = 0; + + for (size_t offset = 12; offset + 8 <= riff_end;) { + const auto* chunk = bytes + offset; + const auto chunk_size = static_cast(Ngs2ReadLe32(chunk + 4)); + const auto payload = offset + 8; + if (chunk_size > riff_end - payload) { + return NGS2_ERROR_INVALID_WAVEFORM_DATA; + } + + if (Ngs2FourCcEquals(chunk, "fmt ")) { + if (chunk_size < 52) { + return NGS2_ERROR_INVALID_WAVEFORM_FORMAT; + } + format = bytes + payload; + } else if (Ngs2FourCcEquals(chunk, "fact")) { + if (chunk_size < 12) { + return NGS2_ERROR_INVALID_WAVEFORM_FORMAT; + } + fact = bytes + payload; + } else if (Ngs2FourCcEquals(chunk, "data")) { + if (payload > std::numeric_limits::max()) { + return NGS2_ERROR_INVALID_WAVEFORM_DATA; + } + waveform_offset = payload; + waveform_size = static_cast(chunk_size); + } + + const uint64_t next = static_cast(payload) + chunk_size + (chunk_size & 1u); + if (next > riff_end) { + return NGS2_ERROR_INVALID_WAVEFORM_DATA; + } + offset = static_cast(next); + } + + if (format == nullptr || fact == nullptr || waveform_offset == 0) { + return NGS2_ERROR_INVALID_WAVEFORM_FORMAT; + } + if (Ngs2ReadLe16(format) != 0xfffe || + std::memcmp(format + 24, ATRAC9_GUID, sizeof(ATRAC9_GUID)) != 0) { + return NGS2_ERROR_UNKNOWN_WAVEFORM_FORMAT; + } + + std::array config {}; + std::memcpy(config.data(), format + 44, config.size()); + if (config[0] != 0xfe || (config[1] & 1u) != 0 || ((config[1] >> 1u) & 7u) >= 6u) { + return NGS2_ERROR_INVALID_WAVEFORM_FORMAT; + } + + Atrac9CodecInfo codec {}; + void* decoder = Atrac9GetHandle(); + const bool valid_codec = + decoder != nullptr && Atrac9InitDecoder(decoder, config.data()) == 0 && + Atrac9GetCodecInfo(decoder, &codec) == 0 && codec.channels > 0 && + codec.samplingRate > 0 && codec.superframeSize > 0 && codec.framesInSuperframe > 0 && + codec.frameSamples > 0 && codec.superframeSize % codec.framesInSuperframe == 0; + if (decoder != nullptr) { + Atrac9ReleaseHandle(decoder); + } + const uint64_t frame_samples = + static_cast(codec.frameSamples) * static_cast(codec.framesInSuperframe); + if (!valid_codec || codec.channels != Ngs2ReadLe16(format + 2) || + codec.samplingRate != static_cast(Ngs2ReadLe32(format + 4)) || + codec.superframeSize != Ngs2ReadLe16(format + 12) || + frame_samples != Ngs2ReadLe16(format + 18) || + frame_samples > std::numeric_limits::max()) { + return NGS2_ERROR_INVALID_WAVEFORM_FORMAT; + } + + info->format.waveform_type = NGS2_WAVEFORM_TYPE_ATRAC9; + info->format.num_channels = static_cast(codec.channels); + info->format.sample_rate = static_cast(codec.samplingRate); + std::memcpy(&info->format.config_data, config.data(), config.size()); + info->data_offset = static_cast(waveform_offset); + info->data_size = waveform_size; + info->num_samples = Ngs2ReadLe32(fact); + info->audio_unit_size = static_cast(codec.superframeSize / + codec.framesInSuperframe); + info->num_audio_unit_samples = static_cast(codec.frameSamples); + info->num_audio_unit_per_frame = static_cast(codec.framesInSuperframe); + info->audio_frame_size = static_cast(codec.superframeSize); + info->num_audio_frame_samples = static_cast(frame_samples); + info->num_delay_samples = Ngs2ReadLe32(fact + 4); + info->num_blocks = 1; + info->blocks[0].data_offset = waveform_offset; + info->blocks[0].data_size = waveform_size; + info->blocks[0].num_skip_samples = Ngs2ReadLe32(fact + 8); + info->blocks[0].num_samples = info->num_samples; + return OK; +} + int KYTY_SYSV_ABI Ngs2ParseWaveformData(const void* data, size_t data_size, Ngs2WaveformInfo* info) { PRINT_NAME(); LOGF("\t data = 0x%016" PRIx64 ", data_size = 0x%016" PRIx64 "\n", reinterpret_cast(data), static_cast(data_size)); - EXIT_NOT_IMPLEMENTED(info == nullptr); + if (info == nullptr) { + return NGS2_ERROR_INVALID_OUT_ADDRESS; + } std::memset(info, 0, sizeof(Ngs2WaveformInfo)); - info->format.waveform_type = 0x80; - info->format.num_channels = 1; - info->format.sample_rate = 48000; - info->data_size = - static_cast(std::min(data_size, std::numeric_limits::max())); - info->num_audio_unit_samples = 1; - info->num_audio_unit_per_frame = 1; - info->num_audio_frame_samples = 1; - return OK; + return Ngs2ParseAtrac9Riff(data, data_size, info); } int KYTY_SYSV_ABI Ngs2CalcWaveformBlock(const Ngs2WaveformFormat* format, uint32_t sample_pos,