Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 144 additions & 0 deletions game_patch/misc/g_solid.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#include <algorithm>
#include <cmath>
#include <unordered_map>
#include <patch_common/FunHook.h>
#include <patch_common/FunPrePostHook.h>
#include <patch_common/CallHook.h>
#include <patch_common/CodeInjection.h>
#include <patch_common/AsmWriter.h>
Expand Down Expand Up @@ -699,12 +701,147 @@ CodeInjection sky_room_eye_position_patch{
},
};

// The stock face lists (intrusive singly-linked lists with next pointers at face+0x54, +0x58,
// +0x5C) append by walking for the tail and remove by walking for the predecessor - O(n^2) on
// face-heavy maps. Shadow each list with a tail cache and a face->predecessor map for O(1)
// append/remove; every shadow lookup is validated against the real links and falls back to the
// stock walk, so stale entries degrade to stock behavior instead of corrupting the list.
template<int NextOffset>
struct VListShadow
{
struct VListRaw
{
void* head;
int count;
};

std::unordered_map<void*, void*> tail; // list -> last face
std::unordered_map<void*, void*> prev; // face -> predecessor face (nullptr = list head)

static void*& next_of(void* f)
{
return *reinterpret_cast<void**>(static_cast<char*>(f) + NextOffset);
}

void append(void* list_ptr, void* face)
{
auto* list = static_cast<VListRaw*>(list_ptr);
list->count++;
next_of(face) = nullptr;
if (!list->head) {
list->head = face;
prev[face] = nullptr;
}
else {
void* t = nullptr;
auto it = tail.find(list_ptr);
if (it != tail.end() && it->second && !next_of(it->second)) {
t = it->second;
}
else {
t = list->head;
while (next_of(t)) {
t = next_of(t);
}
}
next_of(t) = face;
prev[face] = t;
}
tail[list_ptr] = face;
}

void remove(void* list_ptr, void* face)
{
auto* list = static_cast<VListRaw*>(list_ptr);
list->count--; // stock decrements unconditionally, even when the face is not found
if (list->head == face) {
list->head = next_of(face); // stock leaves the removed face's next pointer intact here
if (list->head) {
prev[list->head] = nullptr;
}
else {
tail.erase(list_ptr);
}
prev.erase(face);
return;
}
if (!list->head) {
return;
}
void* p;
auto it = prev.find(face);
if (it != prev.end() && it->second && next_of(it->second) == face) {
p = it->second;
}
else {
p = list->head;
while (p && next_of(p) != face) {
p = next_of(p);
}
if (!p) {
return; // not in this list (stock bails the same way)
}
}
void* nxt = next_of(face);
next_of(face) = nullptr; // stock zeroes next in the non-head case
next_of(p) = nxt;
if (nxt) {
prev[nxt] = p;
}
else {
auto tit = tail.find(list_ptr);
if (tit != tail.end()) {
tit->second = p;
}
}
prev.erase(face);
}

void clear()
{
tail.clear();
prev.clear();
}
};

static VListShadow<0x54> g_vlist54; // GSolid face list (GSolid__create_face / VList__remove_face)
static VListShadow<0x58> g_vlist58; // room face list (solid_recompute_normals rebuilds; no remover)
static VListShadow<0x5C> g_vlist5c; // bbox face list (FUN_004ccec0 add / FUN_004ce240 remove)

FunHook<void __fastcall(void*, int, void*)> vlist54_add_hook{
0x004D3160,
[](void* list_ptr, int, void* face) { g_vlist54.append(list_ptr, face); },
};

FunHook<void __fastcall(void*, int, void*)> vlist54_remove_hook{
0x004CE2A0,
[](void* list_ptr, int, void* face) { g_vlist54.remove(list_ptr, face); },
};

FunHook<void __fastcall(void*, int, void*)> vlist58_add_hook{
0x004D30E0,
[](void* list_ptr, int, void* face) { g_vlist58.append(list_ptr, face); },
};

FunHook<void __fastcall(void*, int, void*)> vlist5c_add_hook{
0x004CE200,
[](void* list_ptr, int, void* face) { g_vlist5c.append(list_ptr, face); },
};

FunHook<void __fastcall(void*, int, void*)> vlist5c_remove_hook{
0x004CE240,
[](void* list_ptr, int, void* face) { g_vlist5c.remove(list_ptr, face); },
};

// clean up sky room overrides and destruction state when shutting down level
CodeInjection level_release_sky_room_shutdown_patch{
0x0045CAF9,
[](auto& regs) {
set_sky_room_uid_override(-1, -1, false, -1);
destruction_level_cleanup();
g_vlist54.clear();
g_vlist58.clear();
g_vlist5c.clear();
},
};

Expand Down Expand Up @@ -776,6 +913,13 @@ void g_solid_do_patch()
// Set PPM for geo crater texture based on its resolution instead of static value of 32.0
levelmod_do_blast_autotexture_ppm_patch.install();

// O(1) face list appends and removals (shadow tail caches + predecessor maps)
vlist54_add_hook.install();
vlist54_remove_hook.install();
vlist58_add_hook.install();
vlist5c_add_hook.install();
vlist5c_remove_hook.install();

// Commands
max_decals_cmd.register_cmd();
dbg_room_clip_wnd_cmd.register_cmd();
Expand Down
43 changes: 43 additions & 0 deletions game_patch/sound/sound.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
#include <patch_common/ShortTypes.h>
#include <patch_common/StaticBufferResizePatch.h>
#include <algorithm>
#include <vector>
#include "sound.h"
#include "../rf/sound/sound.h"
#include "../rf/sound/sound_ds.h"
#include "../rf/entity.h"
#include "../rf/multi.h"
#include "../rf/os/frametime.h"
#include "../rf/os/timestamp.h"
#include "../multi/multi.h"
#include "../misc/alpine_settings.h"
#include "../main/main.h"
Expand Down Expand Up @@ -420,6 +422,32 @@ void sound_test_do_frame()

#endif // DEBUG

// Defer sound preloads issued while reading level events: sound-heavy maps preload many MB
// of ogg/wav synchronously during load. Queue the ids and warm them one per 10 ms after the
// level is up; a sound played before it is warmed just loads on demand like in the stock game.
static std::vector<int> g_deferred_snd_loads;
static bool g_defer_snd_loads = false;

FunHook<int(int)> snd_load_hint_defer_hook{
0x005054D0,
[](int snd_id) {
if (g_defer_snd_loads && snd_id >= 0) {
g_deferred_snd_loads.push_back(snd_id);
return 0;
}
return snd_load_hint_defer_hook.call_target(snd_id);
},
};

FunHook<void(void*)> level_read_events_snd_defer_hook{
0x00462150,
[](void* file) {
g_defer_snd_loads = true;
level_read_events_snd_defer_hook.call_target(file);
g_defer_snd_loads = false;
},
};

FunHook<void(const rf::Vector3&, const rf::Vector3&, const rf::Matrix3&)> snd_update_sounds_hook{
0x00505EC0,
[](const rf::Vector3& camera_pos, const rf::Vector3& camera_vel, const rf::Matrix3& camera_orient) {
Expand All @@ -437,6 +465,17 @@ FunHook<void(const rf::Vector3&, const rf::Vector3&, const rf::Matrix3&)> snd_up
rf::sound_listener_pos = camera_pos;
rf::sound_listener_rvec = camera_orient.rvec;

// Warm one deferred level sound per 10 ms
if (!g_deferred_snd_loads.empty()) {
static rf::Timestamp warm_timer;
if (!warm_timer.valid() || warm_timer.elapsed()) {
warm_timer.set(10);
int snd_id = g_deferred_snd_loads.back();
g_deferred_snd_loads.pop_back();
snd_load_hint_defer_hook.call_target(snd_id);
}
}

// Update DirectSound 3D listener parameters
rf::snd_pc_change_listener(camera_pos, camera_vel, camera_orient);

Expand Down Expand Up @@ -858,6 +897,10 @@ void apply_sound_patches()
snd_change_3d_hook.install();
snd_update_sounds_hook.install();

// Defer level-event sound preloads to after level load
snd_load_hint_defer_hook.install();
level_read_events_snd_defer_hook.install();

// Apply patch for DirectSound specific code
snd_ds_apply_patch();

Expand Down
23 changes: 21 additions & 2 deletions game_patch/sound/sound_ds.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#include <patch_common/AsmOpcodes.h>
#include <stb_vorbis.h>
#include <algorithm>
#include <cstring>
#include <memory>
#include "../rf/sound/sound.h"
#include "../rf/sound/sound_ds.h"
#include "../rf/crt.h"
Expand Down Expand Up @@ -331,6 +333,23 @@ FunHook<int(HMMIO*, LPMMCKINFO, const MMCKINFO*)> snd_mmio_find_data_chunk_hook{
},
};

// Bulk replacement for the stock chunk reader at 0x00563620: the original copies the wav data
// into the destination buffer one byte at a time through the mmio buffered-IO window, which
// costs several ms per sound during level load. One mmioRead call is semantically identical
// (same clamp to mmcki->cksize, same error path) but memcpy-chunked internally.
static int snd_mmio_read_chunk_bulk(HMMIO hmmio, unsigned buf_size, BYTE* buf, MMCKINFO* mmcki, int* bytes_read)
{
unsigned len = std::min<unsigned>(buf_size, mmcki->cksize);
mmcki->cksize -= len;
LONG read = len ? mmioRead(hmmio, reinterpret_cast<HPSTR>(buf), len) : 0;
if (read < 0 || static_cast<unsigned>(read) != len) {
*bytes_read = 0;
return -1;
}
*bytes_read = read;
return 0;
}

FunHook<int(HMMIO, unsigned, BYTE*, MMCKINFO*, int*)> snd_mmio_read_chunk_hook{
0x00563620,
[](HMMIO hmmio, unsigned buf_size, BYTE *buf, MMCKINFO *mmcki, int *bytes_read) {
Expand All @@ -351,11 +370,11 @@ FunHook<int(HMMIO, unsigned, BYTE*, MMCKINFO*, int*)> snd_mmio_read_chunk_hook{
xlog::info("Ogg Vorbis: read {} samples", samples_read);
return 0;
}
return snd_mmio_read_chunk_hook.call_target(wrapper->hmmio, buf_size, buf, mmcki, bytes_read);
return snd_mmio_read_chunk_bulk(wrapper->hmmio, buf_size, buf, mmcki, bytes_read);
}
else {
// fallback: not a wrapper, treat as native
return snd_mmio_read_chunk_hook.call_target(hmmio, buf_size, buf, mmcki, bytes_read);
return snd_mmio_read_chunk_bulk(hmmio, buf_size, buf, mmcki, bytes_read);
}
},
};
Expand Down
Loading