// 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 #define WIN32_LEAN_AND_MEAN #include #include #include #include #include #include #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 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 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 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 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(kHandles); std::vector 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; }