adding audio_host project for hooking noita FMOD
This commit is contained in:
1 parent
00a9c1c26c
commit
06bb712a46
2 files changed
+223
No files matched your search
@@ -0,0 +1,13 @@
|
|||||||
|
# The 32-bit audio host (the game's FMOD DLLs are x86). Built on its own:
|
||||||
|
# cmake -S tools/audio_host -B build_audio32 -A Win32 && cmake --build build_audio32 --config Release
|
||||||
|
# then audio_host.exe goes next to noita_gpu.exe (and into demo/).
|
||||||
|
cmake_minimum_required(VERSION 3.20)
|
||||||
|
project(audio_host CXX)
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
add_executable(audio_host audio_host.cpp)
|
||||||
|
target_include_directories(audio_host PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../src)
|
||||||
|
if(MSVC)
|
||||||
|
target_compile_options(audio_host PRIVATE /W4)
|
||||||
|
target_compile_definitions(audio_host PRIVATE _CRT_SECURE_NO_WARNINGS)
|
||||||
|
set_property(TARGET audio_host PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
|
||||||
|
endif()
|
||||||
@@ -0,0 +1,210 @@
|
|||||||
|
// audio_host: a 32-bit helper that plays the game's sounds with the FMOD Studio of the Noita install (fmodstudio.dll / fmod.dll 2.1.5 are x86 and the
|
||||||
|
// game is 64-bit). It loads those DLLs at run time from the Noita folder (they are never copied or shipped), loads the banks of data/audio/Desktop and runs
|
||||||
|
// the commands the game writes into the shared memory of src/audio/audio_ipc.h. It ends with the game.
|
||||||
|
// "Made with FMOD Studio by Firelight Technologies Pty Ltd."
|
||||||
|
// usage: audio_host <noita dir> <shared memory name> <game pid>
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "audio/audio_ipc.h"
|
||||||
|
|
||||||
|
using namespace ng::audio_ipc;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
struct FVec { float x, y, z; };
|
||||||
|
struct F3D { FVec position, velocity, forward, up; };
|
||||||
|
typedef int(__stdcall* PCreate)(void**, unsigned);
|
||||||
|
typedef int(__stdcall* PInit)(void*, int, unsigned, unsigned, void*);
|
||||||
|
typedef int(__stdcall* PSys)(void*);
|
||||||
|
typedef int(__stdcall* PLoadBank)(void*, const char*, unsigned, void**);
|
||||||
|
typedef int(__stdcall* PGetEvent)(void*, const char*, void**);
|
||||||
|
typedef int(__stdcall* PListener)(void*, int, const F3D*, const FVec*);
|
||||||
|
typedef int(__stdcall* PCreateInst)(void*, void**);
|
||||||
|
typedef int(__stdcall* PInst)(void*);
|
||||||
|
typedef int(__stdcall* PStop)(void*, int);
|
||||||
|
typedef int(__stdcall* PSet3D)(void*, const F3D*);
|
||||||
|
typedef int(__stdcall* PSetParam)(void*, const char*, float, int);
|
||||||
|
typedef int(__stdcall* PGetState)(void*, int*);
|
||||||
|
typedef int(__stdcall* PSetFloat)(void*, float);
|
||||||
|
typedef int(__stdcall* PBankCount)(void*, int*);
|
||||||
|
typedef int(__stdcall* PBankList)(void*, void**, int, int*);
|
||||||
|
typedef int(__stdcall* PDescPath)(void*, char*, int, int*);
|
||||||
|
struct FParamId { unsigned d1, d2; };
|
||||||
|
struct FParamDesc { const char* name; FParamId id; float minimum, maximum, defaultvalue; int type; unsigned flags; unsigned guid[4]; };
|
||||||
|
typedef int(__stdcall* PParamCount)(void*, int*);
|
||||||
|
typedef int(__stdcall* PParamByIndex)(void*, int, FParamDesc*);
|
||||||
|
typedef int(__stdcall* PParamLabel)(void*, int, int, char*, int, int*);
|
||||||
|
|
||||||
|
PCreate fCreate; PInit fInit; PSys fUpdate, fRelease; PLoadBank fLoadBank; PGetEvent fGetEvent; PListener fListener;
|
||||||
|
typedef int(__stdcall* PSetBool)(void*, int);
|
||||||
|
PCreateInst fCreateInst; PInst fStart, fRelInst; PStop fStop; PSet3D fSet3D; PSetParam fSetParam; PGetState fGetState; PSetFloat fSetVolume; PSetBool fSetPaused;
|
||||||
|
PBankCount fBankCount; PBankList fBankList; PDescPath fDescPath;
|
||||||
|
|
||||||
|
F3D at(float x, float y, float z, float vx, float vy) { // 0047b600: position in pixels, velocity = the movement since the last update, forward +z, up +y
|
||||||
|
F3D a{};
|
||||||
|
a.position = {x, y, z};
|
||||||
|
a.velocity = {vx, vy, 0.0f};
|
||||||
|
a.forward = {0.0f, 0.0f, 1.0f};
|
||||||
|
a.up = {0.0f, 1.0f, 0.0f};
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
if (argc < 4) return 2;
|
||||||
|
const std::string dir = argv[1];
|
||||||
|
HANDLE map = OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, argv[2]);
|
||||||
|
if (!map) return 3;
|
||||||
|
auto* sh = (Shared*)MapViewOfFile(map, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(Shared));
|
||||||
|
if (!sh || sh->magic != kMagic) return 4;
|
||||||
|
HANDLE game = OpenProcess(SYNCHRONIZE, FALSE, (DWORD)std::strtoul(argv[3], nullptr, 10));
|
||||||
|
auto fail = [&](const char* m) { std::snprintf(sh->error, sizeof sh->error, "%s", m); sh->ready = -1; return 5; };
|
||||||
|
|
||||||
|
SetDllDirectoryA(dir.c_str()); // fmodstudio.dll needs fmod.dll next to it
|
||||||
|
HMODULE core = LoadLibraryA((dir + "\\fmod.dll").c_str());
|
||||||
|
HMODULE studio = LoadLibraryA((dir + "\\fmodstudio.dll").c_str());
|
||||||
|
if (!core || !studio) return fail("fmod.dll / fmodstudio.dll not found in the Noita folder");
|
||||||
|
#define GET(v, T, n) v = (T)GetProcAddress(studio, n); if (!v) return fail("missing " n);
|
||||||
|
GET(fCreate, PCreate, "FMOD_Studio_System_Create");
|
||||||
|
GET(fInit, PInit, "FMOD_Studio_System_Initialize");
|
||||||
|
GET(fUpdate, PSys, "FMOD_Studio_System_Update");
|
||||||
|
GET(fRelease, PSys, "FMOD_Studio_System_Release");
|
||||||
|
GET(fLoadBank, PLoadBank, "FMOD_Studio_System_LoadBankFile");
|
||||||
|
GET(fGetEvent, PGetEvent, "FMOD_Studio_System_GetEvent");
|
||||||
|
GET(fListener, PListener, "FMOD_Studio_System_SetListenerAttributes");
|
||||||
|
GET(fCreateInst, PCreateInst, "FMOD_Studio_EventDescription_CreateInstance");
|
||||||
|
GET(fStart, PInst, "FMOD_Studio_EventInstance_Start");
|
||||||
|
GET(fRelInst, PInst, "FMOD_Studio_EventInstance_Release");
|
||||||
|
GET(fStop, PStop, "FMOD_Studio_EventInstance_Stop");
|
||||||
|
GET(fSet3D, PSet3D, "FMOD_Studio_EventInstance_Set3DAttributes");
|
||||||
|
GET(fSetParam, PSetParam, "FMOD_Studio_EventInstance_SetParameterByName");
|
||||||
|
GET(fGetState, PGetState, "FMOD_Studio_EventInstance_GetPlaybackState");
|
||||||
|
GET(fSetVolume, PSetFloat, "FMOD_Studio_EventInstance_SetVolume");
|
||||||
|
GET(fSetPaused, PSetBool, "FMOD_Studio_EventInstance_SetPaused");
|
||||||
|
GET(fBankCount, PBankCount, "FMOD_Studio_Bank_GetEventCount");
|
||||||
|
GET(fBankList, PBankList, "FMOD_Studio_Bank_GetEventList");
|
||||||
|
GET(fDescPath, PDescPath, "FMOD_Studio_EventDescription_GetPath");
|
||||||
|
#undef GET
|
||||||
|
void* sys = nullptr;
|
||||||
|
if (fCreate(&sys, 0x00020105) != 0 || !sys) return fail("FMOD_Studio_System_Create failed");
|
||||||
|
if (fInit(sys, 512, 0, 0, nullptr) != 0) return fail("FMOD_Studio_System_Initialize failed");
|
||||||
|
std::vector<void*> banks;
|
||||||
|
{ // every bank of data/audio/Desktop (the strings bank gives the event paths)
|
||||||
|
WIN32_FIND_DATAA fd;
|
||||||
|
const std::string bdir = dir + "\\data\\audio\\Desktop\\";
|
||||||
|
HANDLE fh = FindFirstFileA((bdir + "*.bank").c_str(), &fd);
|
||||||
|
if (fh == INVALID_HANDLE_VALUE) return fail("no banks in data/audio/Desktop");
|
||||||
|
do { void* b = nullptr; if (fLoadBank(sys, (bdir + fd.cFileName).c_str(), 0, &b) == 0) banks.push_back(b); } while (FindNextFileA(fh, &fd));
|
||||||
|
FindClose(fh);
|
||||||
|
}
|
||||||
|
if (std::getenv("NG_AUDIO_LIST")) { // every event path, for the tables
|
||||||
|
FILE* f = std::fopen(std::getenv("NG_AUDIO_LIST"), "w");
|
||||||
|
for (void* b : banks) {
|
||||||
|
int n = 0; fBankCount(b, &n);
|
||||||
|
std::vector<void*> d(n > 0 ? n : 1); int got = 0; fBankList(b, d.data(), n, &got);
|
||||||
|
for (int i = 0; i < got; ++i) { char p[512]; int r = 0; if (fDescPath(d[i], p, sizeof p, &r) == 0 && f) std::fprintf(f, "%s\n", p); }
|
||||||
|
}
|
||||||
|
if (f) std::fclose(f);
|
||||||
|
}
|
||||||
|
if (const char* dn = std::getenv("NG_AUDIO_DESCRIBE")) { // the parameters of some events (path;path;...), for the tables: NG_AUDIO_DESCRIBE_OUT
|
||||||
|
auto pc = (PParamCount)GetProcAddress(studio, "FMOD_Studio_EventDescription_GetParameterDescriptionCount");
|
||||||
|
auto pb = (PParamByIndex)GetProcAddress(studio, "FMOD_Studio_EventDescription_GetParameterDescriptionByIndex");
|
||||||
|
auto pl = (PParamLabel)GetProcAddress(studio, "FMOD_Studio_EventDescription_GetParameterLabelByIndex");
|
||||||
|
FILE* f = std::fopen(std::getenv("NG_AUDIO_DESCRIBE_OUT") ? std::getenv("NG_AUDIO_DESCRIBE_OUT") : "describe.txt", "w");
|
||||||
|
std::string list = dn;
|
||||||
|
size_t p0 = 0;
|
||||||
|
while (f && p0 <= list.size()) {
|
||||||
|
size_t e = list.find(';', p0); if (e == std::string::npos) e = list.size();
|
||||||
|
const std::string path = list.substr(p0, e - p0); p0 = e + 1;
|
||||||
|
void* d = nullptr;
|
||||||
|
if (path.empty() || fGetEvent(sys, path.c_str(), &d) != 0) { std::fprintf(f, "%s: missing\n", path.c_str()); continue; }
|
||||||
|
int n = 0; if (pc) pc(d, &n);
|
||||||
|
std::fprintf(f, "%s: %d parameters\n", path.c_str(), n);
|
||||||
|
for (int i = 0; i < n && pb; ++i) {
|
||||||
|
FParamDesc pdsc{}; pb(d, i, &pdsc);
|
||||||
|
std::fprintf(f, " %s [%g .. %g] default %g type %d flags %u\n", pdsc.name ? pdsc.name : "?", pdsc.minimum, pdsc.maximum, pdsc.defaultvalue, pdsc.type, pdsc.flags);
|
||||||
|
if (pl && (pdsc.flags & 0x20)) for (int k = 0; k < 64; ++k) { char lb[128]; int r = 0; if (pl(d, i, k, lb, sizeof lb, &r) != 0) break; std::fprintf(f, " label %d: %s\n", k, lb); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (f) std::fclose(f);
|
||||||
|
}
|
||||||
|
{ // every event path, for the game (the music's track lists)
|
||||||
|
size_t pos = 0;
|
||||||
|
for (void* b : banks) {
|
||||||
|
int n = 0; fBankCount(b, &n);
|
||||||
|
std::vector<void*> d(n > 0 ? n : 1); int got = 0; fBankList(b, d.data(), n, &got);
|
||||||
|
for (int i = 0; i < got; ++i) {
|
||||||
|
char pth[512]; int r = 0;
|
||||||
|
if (fDescPath(d[i], pth, sizeof pth, &r) != 0) continue;
|
||||||
|
const size_t l = std::strlen(pth);
|
||||||
|
if (pos + l + 2 >= sizeof sh->events) break;
|
||||||
|
std::memcpy(sh->events + pos, pth, l); pos += l; sh->events[pos++] = 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sh->events[pos] = 0;
|
||||||
|
}
|
||||||
|
sh->ready = 1;
|
||||||
|
|
||||||
|
FILE* alog = std::getenv("NG_AUDIOLOG") ? std::fopen(std::getenv("NG_AUDIOLOG"), "w") : nullptr; // (tests: what was played, what was missing)
|
||||||
|
std::map<std::string, void*> descs;
|
||||||
|
auto describe = [&](const char* path) -> void* {
|
||||||
|
auto it = descs.find(path);
|
||||||
|
if (it != descs.end()) return it->second;
|
||||||
|
void* d = nullptr;
|
||||||
|
if (fGetEvent(sys, path, &d) != 0) d = nullptr;
|
||||||
|
descs[path] = d;
|
||||||
|
return d;
|
||||||
|
};
|
||||||
|
struct Kept { void* inst = nullptr; float x = 0, y = 0; };
|
||||||
|
std::vector<Kept> kept(kHandles);
|
||||||
|
std::vector<void*> oneshots;
|
||||||
|
for (;;) {
|
||||||
|
if (game && WaitForSingleObject(game, 0) == WAIT_OBJECT_0) break;
|
||||||
|
bool quit = false;
|
||||||
|
while (sh->tail != sh->head) {
|
||||||
|
const Cmd c = sh->ring[sh->tail % kRing];
|
||||||
|
MemoryBarrier();
|
||||||
|
sh->tail = sh->tail + 1;
|
||||||
|
const int h = ((c.handle % kHandles) + kHandles) % kHandles;
|
||||||
|
switch (c.op) {
|
||||||
|
case PlayOneShot: case Start: {
|
||||||
|
void* d = describe(c.name);
|
||||||
|
void* inst = nullptr;
|
||||||
|
if (alog) { std::fprintf(alog, "%s %s (%.0f, %.0f)\n", d ? (c.op == Start ? "start" : "play") : "MISSING", c.name, c.x, c.y); std::fflush(alog); }
|
||||||
|
if (!d || fCreateInst(d, &inst) != 0 || !inst) break;
|
||||||
|
const F3D a = at(c.x, c.y, c.z, 0, 0);
|
||||||
|
fSet3D(inst, &a);
|
||||||
|
if (c.value > 0.0f && c.value != 1.0f) fSetVolume(inst, c.value);
|
||||||
|
fStart(inst);
|
||||||
|
if (c.op == PlayOneShot) fRelInst(inst); // (released: it frees itself when it ends)
|
||||||
|
else { if (kept[h].inst) { fStop(kept[h].inst, 0); fRelInst(kept[h].inst); } kept[h] = {inst, c.x, c.y}; sh->playing[h] = 1; }
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Move: if (kept[h].inst) { const F3D a = at(c.x, c.y, c.z, c.x - kept[h].x, c.y - kept[h].y); fSet3D(kept[h].inst, &a); kept[h].x = c.x; kept[h].y = c.y; } break;
|
||||||
|
case SetParam: if (kept[h].inst) fSetParam(kept[h].inst, c.name, c.value, 0); break;
|
||||||
|
case SetVolume: if (kept[h].inst) fSetVolume(kept[h].inst, c.value); break;
|
||||||
|
case Pause: for (int k = 0; k < kHandles; ++k) if (kept[k].inst) fSetPaused(kept[k].inst, c.value != 0.0f ? 1 : 0); break; // (the pause menu: the loops and the music hold)
|
||||||
|
case Stop: case StopNow: if (kept[h].inst) { fStop(kept[h].inst, c.op == StopNow ? 1 : 0); fRelInst(kept[h].inst); kept[h].inst = nullptr; sh->playing[h] = 0; } break;
|
||||||
|
case Listener: { const F3D a = at(c.x, c.y, c.z, 0, 0); const FVec p{c.x, c.y, c.z}; fListener(sys, 0, &a, &p); break; }
|
||||||
|
case Release: if (kept[h].inst) { fRelInst(kept[h].inst); kept[h].inst = nullptr; sh->playing[h] = 0; } break; // (it plays to its end on its own)
|
||||||
|
case Quit: quit = true; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (quit) break;
|
||||||
|
for (int h = 0; h < kHandles; ++h)
|
||||||
|
if (kept[h].inst) { int st = 2; fGetState(kept[h].inst, &st); if (st == 2) { fRelInst(kept[h].inst); kept[h].inst = nullptr; sh->playing[h] = 0; } }
|
||||||
|
fUpdate(sys);
|
||||||
|
Sleep(8);
|
||||||
|
}
|
||||||
|
fRelease(sys);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in new issue
Block a user