diff --git a/CMakeLists.txt b/CMakeLists.txt index 95fe883..995cfe2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -464,6 +464,7 @@ set(SOURCE_FILES ${SOURCE_FILES} hooks/audio/implementations/wave_out.cpp hooks/audio/implementations/none.cpp hooks/audio/implementations/pipewire.cpp + hooks/bmsbhook.cpp hooks/avshook.cpp hooks/cfgmgr32hook.cpp hooks/debughook.cpp @@ -634,7 +635,7 @@ add_executable(spicetools_spice64 ${SOURCE_FILES} ${RESOURCE_FILES}) # do NOT link against: mf, mfplat, mfreadwrite; otherwise unity games will break target_link_libraries(spicetools_spice64 PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winscard winhttp mfuuid strmiids dxva2 - PRIVATE fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features) + PRIVATE fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features mf mfplat mfreadwrite) set_target_properties(spicetools_spice64 PROPERTIES PREFIX "") set_target_properties(spicetools_spice64 PROPERTIES OUTPUT_NAME "spice64") diff --git a/games/iidx/iidx.cpp b/games/iidx/iidx.cpp index 32cc1f5..08ca2b6 100644 --- a/games/iidx/iidx.cpp +++ b/games/iidx/iidx.cpp @@ -17,6 +17,7 @@ #endif #include "hooks/setupapihook.h" #include "hooks/sleephook.h" +#include "hooks/bmsbhook.h" #include "launcher/options.h" #include "touch/touch.h" #include "misc/wintouchemu.h" @@ -373,6 +374,10 @@ namespace games::iidx { // init cfgmgr32 hooks cfgmgr32hook_init(avs::game::DLL_INSTANCE); + + // wine fixes + hooks::bmsb::init(avs::game::DLL_INSTANCE); + } void IIDXGame::pre_attach() { @@ -422,6 +427,8 @@ namespace games::iidx { } void IIDXGame::detach() { + hooks::bmsb::deinit(avs::game::DLL_INSTANCE); + Game::detach(); devicehook_dispose(); diff --git a/hooks/bmsbhook.cpp b/hooks/bmsbhook.cpp new file mode 100644 index 0000000..1455bf5 --- /dev/null +++ b/hooks/bmsbhook.cpp @@ -0,0 +1,688 @@ +#include "bmsbhook.h" +#include "util/logging.h" +#include "util/memutils.h" +#include "util/sigscan.h" +#include "util/detour.h" +#include "util/libutils.h" +#include "avs/core.h" +#include + + +struct wav_hdr +{ + char sig_riff[4] = {'R', 'I', 'F', 'F'}; + uint32_t filesz = -1; + char sig_wave[4] = {'W', 'A', 'V', 'E'}; + char sig_fmt[4] = {'f', 'm', 't', ' '}; + uint32_t chunksize = 16; + uint16_t format = 1; + uint16_t channels = 2; + uint32_t samplerate = 44100; + uint32_t byterate = 44100 * 2 * 2; + uint16_t stride = 2 * 2; + uint16_t bitdepth = 16; + char sig_data[4] = {'d', 'a', 't', 'a'}; + uint32_t datasz = -1; +}; + +void dump(const char *path, const char *src, size_t sz) +{ + FILE *fh = fopen(path, "wb"); + fwrite(src, sz, 1, fh); + fclose(fh); +} + +// This implementation replicates wine MF wma=>wav playback bug 1:1, follows https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/Win7Samples/multimedia/mediafoundation/AudioClip/main.cpp +namespace hooks::bmsb::mf_broken +{ +//_BUG: manual symbol lookup, should not link against: mf, mfplat, mfreadwrite +#include +#include +#include +#include +#include +typedef HRESULT (*MFCreateMFByteStreamOnStream_t)(IStream *, IMFByteStream **); +MFCreateMFByteStreamOnStream_t MFCreateMFByteStreamOnStream; + +HRESULT asf_unpack_pcm_s16le(BYTE *dst, IMFSourceReader *srcreader, uint32_t szmax, uint32_t *sz) +{ + HRESULT hr = S_OK; + size_t srcpos = 0; + size_t srcchunk = 0; + BYTE *audiobuf = nullptr; + IMFSample *samples = nullptr; + IMFMediaBuffer *mediabuf = nullptr; + + while (true) + { + DWORD flags = 0; + + // Get next samples chunk, in memcpy capable format + hr = srcreader->ReadSample((DWORD) MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, nullptr, &flags, nullptr, &samples); + if (FAILED(hr)) + { + log_warning("hooks::bmsb", "ReadSample() failure, skipping.."); + break; + } + if (flags & MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED) + { + log_warning("hooks::bmsb", "Unsupported WAVE format, skipping.."); + samples->Release(); + break; + } + if (flags & MF_SOURCE_READERF_ENDOFSTREAM) + { + break; + } + if (samples == nullptr) + { + log_warning("hooks::bmsb", "Missing samples"); + continue; + } + hr = samples->ConvertToContiguousBuffer(&mediabuf); + if (FAILED(hr)) + { + log_warning("hooks::bmsb", "ConvertToContiguousBuffer() failure, skipping.."); + samples->Release(); + break; + } + + // Store to destination (szmax as overflow check) + mediabuf->Lock(&audiobuf, nullptr, reinterpret_cast(&srcchunk)); + if (szmax - srcpos < srcchunk) + { + srcchunk = (signed int) szmax - srcpos; + } + memcpy(dst + srcpos, audiobuf, srcchunk); + srcpos += srcchunk; + hr = mediabuf->Unlock(); + + // Necessary per samples chunk + audiobuf = nullptr; + samples->Release(); + mediabuf->Release(); + if (srcpos >= szmax) + { + break; + } + } + + *sz = srcpos; + return hr; +} + +HRESULT asf_to_wav(const BYTE *srcasf, uint32_t srcsz, BYTE **dstwav, uint32_t *dstsz) +{ + HRESULT hr = S_OK; + + // Reuse raw asf container into a byte stream https://stackoverflow.com/questions/46493191/create-imfbytestream-from-byte-array + IStream *shstream = SHCreateMemStream(srcasf, srcsz); + IMFByteStream *bstream; + MFCreateMFByteStreamOnStream(shstream, &bstream); + + // Create preconfigured from source buffer reader + IMFSourceReader *srcreader; + hr = MFCreateSourceReaderFromByteStream(bstream, nullptr, &srcreader); + + // Setup wav metadata/header (s16le@44.1kHz 2ch) + UINT32 a, b, c; + IMFMediaType *mediatype; + PROPVARIANT duration; + srcreader->GetCurrentMediaType(MF_SOURCE_READER_FIRST_AUDIO_STREAM, &mediatype); + srcreader->GetPresentationAttribute(MF_SOURCE_READER_MEDIASOURCE, MF_PD_DURATION, &duration); + mediatype->GetUINT32(MF_MT_AUDIO_NUM_CHANNELS, &a); + mediatype->GetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, &b); + mediatype->GetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, &c); + wav_hdr dsthdr = { + .channels=static_cast(a), + .samplerate=b, + .bitdepth=static_cast(c) + }; + + // ASF reader => PCM data + if (!*dstwav) *dstwav = (BYTE *) malloc(size_t(((double) duration.uhVal.QuadPart / 10000000.0 * dsthdr.samplerate + 4096) * dsthdr.bitdepth * dsthdr.channels) + sizeof(struct wav_hdr)); + asf_unpack_pcm_s16le(*dstwav + sizeof(struct wav_hdr), srcreader, 0xFFFFFFFF, dstsz); //_INFO: overflow handled elsewhere + + // Finalize and store header + dsthdr.datasz = *dstsz; + dsthdr.filesz = *dstsz + sizeof(struct wav_hdr) - 8; + memcpy(*dstwav, &dsthdr, sizeof(struct wav_hdr)); + *dstsz += sizeof(wav_hdr); + + // Cleanup without releasing srcasf + PropVariantClear(&duration); + mediatype->Release(); + srcreader->Release(); + bstream->Close(); + shstream->Release(); + return hr; +} + +bool init() +{ + HMODULE mfdll = libutils::try_library("Mfplat.dll"); + if (!mfdll) return false; + + // Missing in includes + MFCreateMFByteStreamOnStream = (MFCreateMFByteStreamOnStream_t) GetProcAddress(mfdll, "MFCreateMFByteStreamOnStream"); + + // MF init checks + if (!MFCreateMFByteStreamOnStream || FAILED(MFStartup(MF_VERSION))) + return false; + + return true; +} + +void deinit() +{ + MFShutdown(); +} + +} + + +namespace hooks::bmsb +{ +constexpr char SNDPATHFMT[] = "%05d/%05d"; +constexpr size_t SNDPATHFMTMAX = sizeof(SNDPATHFMT) - 1; +constexpr size_t SNDPATHMAX = sizeof("AABBB/AABBBC_pre.2dx"); +constexpr size_t CACHEMAX = 32; +typedef struct soundbank soundbank; +typedef struct snd_s3p snd_s3p; +typedef struct snd_2dx snd_2dx; +typedef void (*BmsbEnumValidSoundbanks_t)(void *, int32_t, int32_t); + +struct snd_2dx +{ +public: + struct bank + { + struct voice_hdr + { + char sig[4]; + uint32_t fbegin; + uint32_t sz; + int16_t unk0; + int16_t type; + int16_t unk1; + int16_t volume; + int32_t unk2; + [[nodiscard]] constexpr BYTE *wave() noexcept + { + return ((BYTE *) this) + this->fbegin; + } + }; + struct voice_ptr + { + uint32_t fbegin; + [[nodiscard]] constexpr void sz(voice_hdr *val) noexcept + { + (this + 1)->fbegin = this->fbegin + val->fbegin + val->sz; // see constructor + } + [[nodiscard]] constexpr size_t sz() const noexcept + { + return (this + 1)->fbegin > 0 ? (this + 1)->fbegin - this->fbegin : 0; // see constructor + } + }; + struct + { + char name[16]; + uint32_t sz; + uint32_t voice_cnt; + char unk[48]; + } header; + voice_ptr table; + + [[nodiscard]] constexpr size_t sz() const noexcept + { + return (&table + header.voice_cnt - 1)->fbegin + (&table + header.voice_cnt - 1)->sz(); + } + }; + + [[nodiscard]] static constexpr size_t assertsz_asf(size_t sz) noexcept + { + return static_cast(static_cast(sz) * 8.81875) + sizeof(wav_hdr); // 0.wma is consistent 180 therefore VBR Q90, assuming worst case: x8.81875@~160kb/s + } + + [[nodiscard]] constexpr const decltype(bank::header) *header() const noexcept + { + return &this->bank_->header; + } + + [[nodiscard]] constexpr bank::voice_ptr *voice_ptr(size_t i) const noexcept + { + return &bank_->table + i; + } + + [[nodiscard]] constexpr bank::voice_hdr *voice(size_t i) const noexcept + { + return (bank::voice_hdr *) ((char *) this->bank_ + this->voice_ptr(i)->fbegin); + } + + //_INFO: assertsz guaranteed until serialize(true), overridden by voice_ptr::sz(v), compute value with assertsz_*() + [[nodiscard]] constexpr size_t voice_emplace(size_t assertsz) noexcept + { + // memory space assertion + if (this->voice_ptr(this->bank_->header.voice_cnt)->fbegin + assertsz >= banksz_) + { + log_info("hooks:bmsb", "snd_2dx::voice_emplace({}=>{})", banksz_, banksz_ + assertsz + ALLOCSIZE); + banksz_ += assertsz + ALLOCSIZE; + bank_ = (bank *) realloc(bank_, banksz_); + } + + // voice_ptr updating works through voice_ptr::sz() + //_REV: table and header.sz resizing event if voice_cnt == 4077, as voice_ptr::sz() needs to read 1 ahead + + // voice_hdr setup + bank::voice_hdr *voice_hdr = this->voice(this->bank_->header.voice_cnt); + std::copy("2DX9", &"2DX9"[4], voice_hdr->sig); + voice_hdr->fbegin = sizeof(bank::voice_hdr); + voice_hdr->sz = 0; + voice_hdr->unk0 = (bank_->header.name[1] - '0') % 2 == 1 ? 0x3231 : 0x3230; // 0x3230/0x3231 for even/odd style + voice_hdr->type = this->bank_->header.voice_cnt == 0 ? 0 : -1; // special case for Voice[0] + voice_hdr->unk1 = 0x40; + voice_hdr->volume = 1; + voice_hdr->unk2 = 0x0; + return this->bank_->header.voice_cnt++; + } + + static snd_2dx *unpack(char *refbank) = delete; + static snd_2dx *pack(int id); + bank *serialize(bool trim); + ~snd_2dx(); + +private: + static constexpr size_t ALLOCSIZE = 4 * 1024 * 1024; + struct bank *bank_; + size_t banksz_; + explicit snd_2dx(char *bindbank); + +}; +snd_2dx::snd_2dx(char *bindbank) : bank_(reinterpret_cast(bindbank)), banksz_(0) +{ + if (bank_) + return; + + // Necessary bank header initialization + banksz_ = ALLOCSIZE; + bank_ = (bank *) malloc(ALLOCSIZE); + memset(&bank_->header, 0, sizeof(bank_->header) + 4078 * sizeof(bank::voice_ptr)); // initializes bank::voice_ptr::sz and bank::header.unk + bank_->header.name[0] = '\0'; + bank_->header.voice_cnt = 0; + bank_->header.sz = sizeof(bank_->header) + 4078 * sizeof(bank::voice_ptr); // 4078 keysounds limit (16KB header), refer to voice_emplace() + + // Necessary voice pointer table initialization (refer to voice_ptr::sz()) + (&bank_->table)[0].fbegin = bank_->header.sz; +} +snd_2dx *snd_2dx::pack(int id) +{ + snd_2dx *r = new snd_2dx(nullptr); + snprintf(r->bank_->header.name, sizeof(r->bank_->header.name), "%05d.2dx", id); + return r; +} +snd_2dx::bank *snd_2dx::serialize(bool trim) +{ + if (trim) + { + //_REV: prune voice_ptr for unused voice slots + move data segment up adjusting voice_ptr->fbegin + } + return bank_; +} +snd_2dx::~snd_2dx() +{ + free(bank_); +} + + +struct snd_s3p +{ +public: + struct bank + { + struct voice_hdr + { + char sig[4]; // S3V0 + uint32_t fbegin; + uint32_t sz; + uint8_t unk[20]; + [[nodiscard]] constexpr BYTE *asf() const noexcept + { + return ((BYTE *) this) + this->fbegin; + } + }; + struct voice_ptr + { + uint32_t fbegin; + uint32_t sz; + }; + struct + { + const char sig[4]; // S3P0 + uint32_t voice_cnt; + } header; + voice_ptr table; + }; + + [[nodiscard]] constexpr const decltype(bank::header) *header() const noexcept + { + return &this->bank_->header; + } + [[nodiscard]] constexpr bank::voice_ptr *voice_ptr(size_t i) const noexcept + { + return &bank_->table + i; + } + [[nodiscard]] constexpr bank::voice_hdr *voice(size_t i) const noexcept + { + return (bank::voice_hdr *) ((char *) this->bank_ + this->voice_ptr(i)->fbegin); + } + + static snd_s3p *unpack(char *refbank); + static snd_s3p *pack(int id) = delete; + bank *serialize(bool trim) = delete; + ~snd_s3p(); + +private: + struct bank *bank_; + size_t banksz_; //_TODO: keep track of allocated memory + explicit snd_s3p(char *bindbank); +}; + +snd_s3p::snd_s3p(char *bindbank) : bank_(reinterpret_cast(bindbank)), banksz_(0) +{ + +} +snd_s3p::~snd_s3p() +{ + free(bank_); +} +snd_s3p *snd_s3p::unpack(char *refbank) +{ + return refbank ? new snd_s3p(refbank) : nullptr; +} + +struct soundbank +{ +public: + static soundbank *new_instance(int id); + static void flush_cache(); + static void pop_cache(); + static soundbank *get_bank(int id); + static char *read_data(const char *sndpath, const char *ext, size_t *sz); + static bool has_format(const char *sndpath, const char *ext); + + int id; + char sndpath[SNDPATHMAX]; +private: + static soundbank *cache_; + static size_t cachesz_; + + soundbank *next_; + soundbank(); + ~soundbank(); +}; +static struct offset_t +{ + intptr_t RD_05d_s3p; // %05d/%05d.s3p + intptr_t RD_05d_c_s3p; // %05d/%05d%c.s3p + intptr_t RD_05d_2dx; // %05d/%05d.2dx + intptr_t RD_05d_c_2dx; // %05d/%05d%c.2dx + intptr_t TX_BmsbEnumValidSoundbanks; // enumerates above databank paths, simple exist check (lstat()/OPEN -> CLOSE_NOWRITE only) + memutils::VProtectGuard *guard; +} offset_; + +soundbank *soundbank::cache_; +size_t soundbank::cachesz_; +static BmsbEnumValidSoundbanks_t BmsbEnumValidSoundbanks; + +soundbank *soundbank::new_instance(int id) +{ + soundbank *prev, *instance; + + // Latest entry should always be head + instance = new soundbank(); + instance->id = id; + instance->next_ = cache_; + cache_ = instance; + cachesz_ += 1; + + // Clean cache (last entry indexing target excluded, cache limit reached) + if (instance->next_ && instance->next_->sndpath[0] != 's') + { + prev = instance->next_; + instance->next_ = prev->next_; + delete prev; + cachesz_ -= 1; + } + if (cachesz_ >= CACHEMAX) + { + for (prev = instance; instance; (instance = instance->next_) && (prev = instance)); + prev->next_ = nullptr; + delete instance; + cachesz_ -= 1; + } + + return cache_; +} +soundbank::soundbank() : id(0), sndpath(""), next_(nullptr) +{ + +} +soundbank::~soundbank() +{ + +} + +soundbank *soundbank::get_bank(int id) +{ + // Find in cache (latest=>oldest) + for (soundbank *it = soundbank::cache_; it; it = it->next_) + if (it->id == id) return it; + + // Find in avs (has valid s3p/.2dx resource at data/sound/AABBB/AABBB.2dx) + soundbank *bank = new_instance(id); + snprintf(bank->sndpath, SNDPATHMAX, "%05d/%05d", id, id); + if (!(soundbank::has_format(bank->sndpath, ".s3p") || soundbank::has_format(bank->sndpath, ".2dx"))) + { + soundbank::pop_cache(); + bank = nullptr; + } + + return bank; +} +void soundbank::flush_cache() +{ + soundbank *prev; + while (cache_) + { + prev = cache_; + cache_ = cache_->next_; + delete prev; + } +} +void soundbank::pop_cache() +{ + soundbank *prev = cache_; + cache_ = cache_->next_; + delete prev; +} +char *soundbank::read_data(const char *sndpath, const char *ext, size_t *sz) +{ + constexpr uint16_t O_RDONLY = 1; // rw:12 consistent across versions with s3p support + constexpr avs::core::avs_file_t E_NOT_FOUND = 0x80070002; + + char *r = nullptr; + char avspath[64] = ""; + snprintf(avspath, sizeof(avspath), "/data/sound/%s%s", sndpath, ext); + + avs::core::avs_file_t fd = avs::core::avs_fs_open(avspath, O_RDONLY, 420); + if (fd != E_NOT_FOUND) + { + { + avs::core::avs_stat st; // NOLINT(cppcoreguidelines-pro-type-member-init) + avs::core::avs_fs_fstat(fd, &st); + *sz = st.filesize; + } + r = (char *) malloc(*sz); + if (r) avs::core::avs_fs_read(fd, reinterpret_cast(r), *sz); + avs::core::avs_fs_close(fd); + } + return r; +} +bool soundbank::has_format(const char *sndpath, const char *ext) +{ + char avspath[64] = ""; + snprintf(avspath, sizeof(avspath), "/data/sound/%s%s", sndpath, ext); + avs::core::avs_stat st; // NOLINT(cppcoreguidelines-pro-type-member-init) + return static_cast(avs::core::avs_fs_lstat(avspath, &st)); // lstat should be faster than open +} + + +void on_enum_valid_soundbanks(void *song, int32_t difficulty, int32_t unk2) +{ + // Keysounds populated into CtrlSound::Bank[2][X] = [0:s3p][1:2dx][2:_pre.2dx], always uses lowest i databank for resource confirmed valid by this function + // Struct memory layout 1:1 with data/info/music_*.bin _BUG: therefore differs a bit between some styles + soundbank *keysounds = nullptr; + const char *titlel = (const char *) song; + const char *artistl = (const char *) song + 0xc0; + const int32_t id = *(int32_t *) ((const char *) song + 0x3b0); + + log_info("hooks::bmsb", "Processing s3p->2dx detour routine for [{}]({} - {})", id, artistl, titlel); + if (!(keysounds = soundbank::get_bank(id))) + { + log_info("hooks::bmsb", "soundbank::get_bank() was 0x0, trying to recover"); + memcpy(reinterpret_cast(offset_.RD_05d_2dx), "%05d/%05d", SNDPATHFMTMAX); + BmsbEnumValidSoundbanks(song, difficulty, unk2); + return; + } + else if (!soundbank::has_format(keysounds->sndpath, ".2dx")) + { + // Get s3p + size_t szs3p; + char *bufs3p = soundbank::read_data(keysounds->sndpath, ".s3p", &szs3p); + + // Process s3p into wma + snd_s3p *snds3p = snd_s3p::unpack(bufs3p); + + // Process wma into wav + + // Process wav into 2dx + + // Output 2dx into /var/tmp (z:\var\tmp) + + // symlink system/00.2dx -> /tmp/output + + // Store to cache (skip transcoding on consecutive runs) + + } + + // Resume execution + memcpy(reinterpret_cast(offset_.RD_05d_2dx), strlen(keysounds->sndpath) == SNDPATHFMTMAX ? keysounds->sndpath : "%05d/%05d", SNDPATHFMTMAX); + log_info("hooks::bmsb", "RD_05d_2dx set as '{}'", reinterpret_cast(offset_.RD_05d_2dx)); //_REM: tests + log_info("hooks::bmsb", "Processing completed"); + BmsbEnumValidSoundbanks(song, difficulty, unk2); +} + +void run_tests() +{ + size_t szs3p; + char sig[5]; + sig[4] = '\0'; + + char *bufs3p = soundbank::read_data("25073/25073", ".s3p", &szs3p); + snd_s3p *snds3p = snd_s3p::unpack(bufs3p); + if (snds3p) + { + // S3P/S3V + log_info("hooks:bmsb", "s3p(25073/25006)? => ({}/{})", soundbank::has_format("25073/25073", ".s3p"), soundbank::has_format("25006/25006", ".s3p")); + memcpy(sig, snds3p->header()->sig, 4); + log_info("hooks:bmsb", "25073::Bank[S3P]::Signature {}", sig); + log_info("hooks:bmsb", "25073::Bank[S3P]::Size {}", szs3p); + log_info("hooks:bmsb", "25073::Bank[S3P]::VoiceCount {}", snds3p->header()->voice_cnt); + log_info("hooks:bmsb", "25073::Bank[S3P]::Voice[0]::Size {}", snds3p->voice_ptr(0)->sz); + memcpy(sig, snds3p->voice(0)->sig, 4); + log_info("hooks:bmsb", "25073::Bank[S3P]::Voice[0]::Signature {}", sig); + + // MF codec (tainted PCM data, bad) + BYTE *wav = nullptr; + uint32_t wavsz; + dump("mf_raw.wma", reinterpret_cast(snds3p->voice(0)->asf()), snds3p->voice(0)->sz); + mf_broken::asf_to_wav(snds3p->voice(0)->asf(), snds3p->voice(0)->sz, &wav, &wavsz); + dump("mf_wav.wav", reinterpret_cast(wav), wavsz); + + // S3P=>2DX (max 4076 voices) + size_t i, j; + snd_2dx *snd2dx = snd_2dx::pack(25073); + log_info("hooks:bmsb", "25073::Bank[2DX]::Name {}", snd2dx->header()->name); + for (i = 0; i < snds3p->header()->voice_cnt; i++) + { + // WMA=>WAV transcode + snd_s3p::bank::voice_hdr *voicesrc = snds3p->voice(i); + j = snd2dx->voice_emplace(snd_2dx::assertsz_asf(voicesrc->sz)); + snd_2dx::bank::voice_hdr *voicedst = snd2dx->voice(j); + auto *voicedst_wave = voicedst->wave(); + mf_broken::asf_to_wav(voicesrc->asf(), voicesrc->sz, &voicedst_wave, &voicedst->sz); + snd2dx->voice_ptr(j)->sz(voicedst); // updates voice_ptr table, do not skip + + } + + // Valid 2DX (trim is not necessary) + log_info("hooks:bmsb", "25073::Bank[2DX]::dump()"); + snd_2dx::bank *buf2dx = snd2dx->serialize(false); + dump("mf_25073.2dx", reinterpret_cast(buf2dx), buf2dx->sz()); + + } + else + { + log_info("hooks:bmsb", "Tests failed"); + } + +} + +void init(HINSTANCE hmodule) +{ + if (false && IsDebuggerPresent()) + { + log_warning("hooks::bmsb", "Debugger detected, skipping.."); + return; + } + + if (!mf_broken::init()) + { + log_warning("hooks::bmsb", "MF initialization failed, skipping.."); + } + + // Tests _REM: tests + run_tests(); + return; + + // Override .text + offset_.TX_BmsbEnumValidSoundbanks = (intptr_t) hmodule + 0xafd780; //_TODO: Hardcoded for iidx30 for now, requires asm pattern lookup + + // Override .rdata + offset_.RD_05d_s3p = replace_pattern(hmodule, "253035642f253035642e73337000", "003035642f253035642e73337000", 0, 0); + offset_.RD_05d_c_s3p = replace_pattern(hmodule, "253035642f2530356425632e73337000", "003035642f2530356425632e73337000", 0, 0); + offset_.RD_05d_2dx = replace_pattern(hmodule, "253035642F253035642E32647800", "73797374656D2F30302E32647800", 0, 0); + offset_.RD_05d_c_2dx = replace_pattern(hmodule, "253035642f2530356425632e32647800", "253035642f2530356425632e32647800", 0, 0); + if (!(offset_.TX_BmsbEnumValidSoundbanks && offset_.RD_05d_s3p && offset_.RD_05d_c_s3p && offset_.RD_05d_2dx && offset_.RD_05d_c_2dx)) + { + log_warning("hooks::bmsb", "Could not find valid offsets, skipping.."); + return; + } + + // Setup on_enum_valid_ksbd + offset_.guard = new memutils::VProtectGuard((void *) offset_.RD_05d_2dx, SNDPATHFMTMAX); + detour::trampoline(reinterpret_cast(offset_.TX_BmsbEnumValidSoundbanks), reinterpret_cast(on_enum_valid_soundbanks), reinterpret_cast(&BmsbEnumValidSoundbanks)); + + log_info("hooks::bmsb", "Soundbank preprocessor ready"); +} + +void deinit(HINSTANCE hmodule) +{ + log_info("hooks::bmsb", "Releasing soundbank preprocessor"); + + soundbank::flush_cache(); + delete offset_.guard; + mf_broken::deinit(); +} + +} \ No newline at end of file diff --git a/hooks/bmsbhook.h b/hooks/bmsbhook.h new file mode 100644 index 0000000..e8df3de --- /dev/null +++ b/hooks/bmsbhook.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +namespace hooks::bmsb +{ +void init(HINSTANCE hmodule); +void deinit(HINSTANCE hmodule); +}