Files
spice2x-r3d/hooks/wmischook.cpp
T
[ ] bb0fd62ec3 feat: Crude SDVX 5 support
> disable lobby video (cause for crashes in lobby), see #9
> suppress dmoeffector log messages, see #9
2026-01-20 08:31:17 +09:00

245 lines
8.1 KiB
C++

#include "wmischook.h"
#include "util/logging.h"
#include "util/detour.h"
#include "util/sigscan.h"
#include "util/libutils.h"
#include "util/utils.h"
#include <mmsystem.h>
namespace hooks::wmisc::msacm
{
typedef MMRESULT (__stdcall *acmFormatSuggest_t)(HANDLE had, LPWAVEFORMATEX pwfxSrc, LPWAVEFORMATEX pwfxDst, DWORD cbwfxDst, DWORD fdwSuggest);
static acmFormatSuggest_t acmFormatSuggest;
static uint64_t hash_waveformatex(const WAVEFORMATEX *pwfx, DWORD fdwSuggest)
{
//_INFO: no clashes, likely faster than hashing sizeof(WAVEFORMATEX) buffer
uint64_t h = 0;
h ^= (uint64_t) pwfx->nSamplesPerSec << 0;
h ^= (uint64_t) pwfx->nAvgBytesPerSec << 1;
h ^= (uint64_t) pwfx->nBlockAlign << 2;
h ^= (uint64_t) pwfx->wBitsPerSample << 3;
h ^= (uint64_t) pwfx->cbSize << 4;
h ^= (uint64_t) fdwSuggest << 5;
h ^= (uint64_t) pwfx->wFormatTag << 6;
h ^= (uint64_t) pwfx->nChannels << 7;
return h;
}
MMRESULT __stdcall on_acmFormatSuggest(HANDLE had, LPWAVEFORMATEX pwfxSrc, LPWAVEFORMATEX pwfxDst, DWORD cbwfxDst, DWORD fdwSuggest)
{
static std::unordered_map<uint64_t, std::vector<char>> acmformats;
static std::mutex mutex;
// Find cached formats
size_t hash = hash_waveformatex(pwfxSrc, fdwSuggest);
{
std::lock_guard lock(mutex);
auto it = acmformats.find(hash);
if (it != acmformats.end())
{
// Copy cached result
memcpy(pwfxDst, it->second.data(), std::min(cbwfxDst, (DWORD) (sizeof(WAVEFORMATEX) + ((WAVEFORMATEX *) it->second.data())->cbSize)));
return MMSYSERR_NOERROR;
}
}
// Cache unique formats
log_info("hooks::wmisc::msacm", "Forwarding acmFormatSuggest({}) request due to cache miss", reinterpret_cast<void *>(hash));
MMRESULT r = acmFormatSuggest(had, pwfxSrc, pwfxDst, cbwfxDst, fdwSuggest);
if (r == MMSYSERR_NOERROR)
{
std::lock_guard lock(mutex);
acmformats[hash].resize(sizeof(WAVEFORMATEX) + pwfxDst->cbSize);
memcpy(acmformats[hash].data(), pwfxDst, sizeof(WAVEFORMATEX) + pwfxDst->cbSize);
}
return r;
}
void attach(HINSTANCE hmodule)
{
// Override acmFormatSuggest
HMODULE msacm = nullptr;
if ((msacm = libutils::try_library("msacm32.dll")))
{
acmFormatSuggest = (acmFormatSuggest_t) GetProcAddress(msacm, "acmFormatSuggest");
}
log_info("hooks::wmisc::msacm", "Hooking acmFormatSuggest({})", reinterpret_cast<void *>(acmFormatSuggest));
detour::trampoline_try(reinterpret_cast<void *>(acmFormatSuggest), reinterpret_cast<void *>(on_acmFormatSuggest), reinterpret_cast<void **>(&acmFormatSuggest));
}
}
namespace hooks::wmisc::thumbnail
{
typedef bool (*ThumExists_t)(void *titleUTF_getter, int32_t id, char flag);
static ThumExists_t ThumExists;
bool on_thum_exists_(void *titleUTF_getter, int32_t id, char flag)
{
static std::unordered_map<int32_t, bool> thumids;
auto it = thumids.find(id);
if (it != thumids.end())
{
return it->second;
}
else
{
// log_misc("hooks::wmisc::thumbnail", "Caching state for [{}]", id);
it = thumids.insert({id, ThumExists(titleUTF_getter, id, flag)}).first;
}
return it->second;
}
void attach(HINSTANCE hmodule)
{
// Reverse lookup function head (PUSH RDI) for ThumExists() specific signature, quietly fail if invalid
intptr_t beg, match, TX_ThumExists;
TX_ThumExists = beg = match = find_pattern_from(hmodule, "83f801750c4084ff750732c0", "XXXXXXXXXXXX", 0, 0, 0);
while (beg > sizeof(void *) && match >= TX_ThumExists)
{
beg -= sizeof(void *);
match = find_pattern_from(hmodule, "4057", "XX", 0, 0, beg - reinterpret_cast<intptr_t>(hmodule));
}
TX_ThumExists = match;
log_info("hooks::wmisc::thumbnail", "Hooking ThumExists({})", reinterpret_cast<void *>(TX_ThumExists));
detour::trampoline_try(reinterpret_cast<void *>(TX_ThumExists), reinterpret_cast<void *>(on_thum_exists_), reinterpret_cast<void **>(&ThumExists));
}
}
namespace hooks::wmisc::bmsound
{
typedef void (*BmswLogInit_t)(void *cb);
extern "C" void __cdecl on_log_msg(char level, const char *scope, const char *msg)
{
switch (level)
{
case 'I':
log_info(scope, "{}", msg);
break;
case 'W':
log_warning(scope, "{}", msg);
break;
case 'E':
log_fatal(scope, "{}", msg);
break;
case 'M':
default:
log_misc(scope, "{}", msg);
break;
}
}
void attach(HINSTANCE hmodule)
{
BmswLogInit_t BmswLogInit = nullptr;
HMODULE bmsw;
if ((bmsw = libutils::try_library(MODULE_PATH / "bmsound-wine.dll")))
{
BmswLogInit = (BmswLogInit_t) GetProcAddress(bmsw, "BmswLogInit");
}
if (BmswLogInit) //_REM: keep 0.2.x backward compatibility until 0.3.0
{
BmswLogInit(reinterpret_cast<void *>(on_log_msg));
}
}
}
namespace hooks::wmisc::cmovieplayer
{
typedef void (*GetWmvChunk_t)(uint8_t *unk);
static GetWmvChunk_t GetWmvChunk_;
void on_get_wmv_chunk_(uint8_t *unk)
{
size_t chunksz = *(uint32_t *) (unk + 0x10);
void *readbuf = (void *) *(intptr_t *) (unk + 0x08);
int *readsz = (int *) (unk + 0x14);
//_REV: For now simply disable broken video stream
*readsz = 0;
}
void attach(HINSTANCE hmodule)
{
// Remove crashing movie
intptr_t TX_ThumExists = find_pattern_from(hmodule, "40534883ec20448b4110488bd9488b51088b09ff150000000089431485c0791d448bc0488d1500000000488d0d000000004883c4205b48ff25000000004883c4205bc3", "XXXXXXXXXXXXXXXXXXXXX????XXXXXXXXXXXXX????XXX????XXXXXXXX????XXXXXX", 0, 0, 0);
log_info("hooks::wmisc::cmovieplayer", "Hooking GetWmvChunk({})", reinterpret_cast<void *>(TX_ThumExists));
if (detour::trampoline_try(reinterpret_cast<void *>(TX_ThumExists), reinterpret_cast<void *>(on_get_wmv_chunk_), reinterpret_cast<void **>(&GetWmvChunk_)))
{
log_warning("hooks::wmisc::cmovieplayer", "Video playback for wmv won't work");
}
}
}
namespace hooks::wmisc::dmoeffector
{
void attach(HINSTANCE hmodule)
{
//_REM: Disable dmoeffector error printing for line55, could not find unique pattern (9 matches, but patching all of them is harmless)
intptr_t next = (intptr_t) hmodule;
while (next)
{
next = replace_pattern_from(hmodule, "793ae8????????895424??4c8d05????????488bc8488d15????????41b937000000e8????????ff15????????", "eb3ae8????????895424??4c8d05????????488bc8488d15????????41b937000000e8????????ff15????????", 0, 0, next - reinterpret_cast<intptr_t>(hmodule));
if (next) log_info("hooks::wmisc::dmoeffector", "Muting error message({})", reinterpret_cast<void *>(next));
}
}
}
namespace hooks::wmisc
{
static bool patchmodel_ = true;
void skip_patching_by_model(bool val)
{
patchmodel_ = !val;
}
void apply_performance_hacks(HINSTANCE hmodule, const char *model, const char *ext)
{
if (!patchmodel_ && strcmp(model, "FORCE") != 0) return;
log_info("hooks::wmisc", "Enabling performance patches for {}-{}", model, ext);
long extl = strtol(ext, nullptr, 10);
switch (to_hash(model))
{
case to_hash("LDJ"):
thumbnail::attach(hmodule);
msacm::attach(hmodule);
bmsound::attach(hmodule);
break;
case to_hash("M39"):
msacm::attach(hmodule);
bmsound::attach(hmodule);
break;
case to_hash("KFC"):
msacm::attach(hmodule);
bmsound::attach(hmodule);
if (extl > 2019020600 && extl < 2021011800) // SDVX5
{
cmovieplayer::attach(hmodule);
dmoeffector::attach(hmodule);
}
break;
case to_hash("FORCE"):
switch (to_hash(ext))
{
case to_hash("thumbnail"):
thumbnail::attach(hmodule);
break;
case to_hash("msacm"):
msacm::attach(hmodule);
break;
case to_hash("bmsound"):
bmsound::attach(hmodule);
break;
case to_hash("cmovieplayer"):
cmovieplayer::attach(hmodule);
break;
case to_hash("dmoeffector"):
dmoeffector::attach(hmodule);
break;
default:
break;
}
break;
default:
break;
}
}
}