|
| 1 | +/* |
| 2 | + * DMAudio Loader Plugin for Mod Loader |
| 3 | + * Copyright (C) 2013-2026 Silent |
| 4 | + * Licensed under the MIT License, see LICENSE at top level directory. |
| 5 | + * |
| 6 | + * std.dmaudio -- Standard DMAudio Loader Plugin for Mod Loader |
| 7 | + * |
| 8 | + */ |
| 9 | +#include <stdinc.hpp> |
| 10 | +using namespace modloader; |
| 11 | + |
| 12 | +enum class Type |
| 13 | +{ |
| 14 | + SfxRaw = 0, // sfx.raw |
| 15 | + SfxSdt = 1, // sfx.dat |
| 16 | + Sample = 2, // Samples (.wav, .mp3, .adf, .vb) |
| 17 | + Max = 3, // Max 2 bits |
| 18 | +}; |
| 19 | + |
| 20 | + |
| 21 | +// Basic masks |
| 22 | +static const uint64_t hash_mask_base = 0xFFFFFFFF; |
| 23 | +static const uint64_t type_mask_base = 0x0003; // Mask for type without any shifting |
| 24 | +static const uint32_t type_mask_shf = 32; // Takes 2 bits, starts from 33th bit because first 32th bits is a hash |
| 25 | + |
| 26 | +// Sets the initial value for a behaviour, by using an filename hash and file type |
| 27 | +inline uint64_t SetType(uint32_t hash, Type type) |
| 28 | +{ |
| 29 | + return modloader::file::set_mask(uint64_t(hash), type_mask_base, type_mask_shf, type); |
| 30 | +} |
| 31 | + |
| 32 | +// Gets the behaviour file type |
| 33 | +inline Type GetType(uint64_t mask) |
| 34 | +{ |
| 35 | + return modloader::file::get_mask<Type>(mask, type_mask_base, type_mask_shf); |
| 36 | +} |
| 37 | + |
| 38 | + |
| 39 | +/* |
| 40 | + * The plugin object |
| 41 | + */ |
| 42 | +class DMAudioPlugin : public modloader::basic_plugin |
| 43 | +{ |
| 44 | + private: |
| 45 | + file_overrider sfx_raw_overrider; // sfx.raw overrider |
| 46 | + file_overrider sfx_sdt_overrider; // sfx.sdt overrider |
| 47 | + std::multimap<uint32_t, const modloader::file*> streams; // Stream (hash, file*) map |
| 48 | + |
| 49 | + public: |
| 50 | + // Standard plugin methods |
| 51 | + const info& GetInfo() override; |
| 52 | + bool OnStartup() override; |
| 53 | + bool OnShutdown() override; |
| 54 | + int GetBehaviour(modloader::file&) override; |
| 55 | + bool InstallFile(const modloader::file&) override; |
| 56 | + bool ReinstallFile(const modloader::file&) override; |
| 57 | + bool UninstallFile(const modloader::file&) override; |
| 58 | + |
| 59 | + private: |
| 60 | + static char* PatchedStrcat(char* destination, const char* source); |
| 61 | + |
| 62 | +} dmaudio_plugin; |
| 63 | + |
| 64 | +REGISTER_ML_PLUGIN(::dmaudio_plugin); |
| 65 | + |
| 66 | +/* |
| 67 | + * DMAudioPlugin::GetInfo |
| 68 | + * Returns information about this plugin |
| 69 | + */ |
| 70 | +const DMAudioPlugin::info& DMAudioPlugin::GetInfo() |
| 71 | +{ |
| 72 | + static const char* extable[] = { "raw", "sdt", "wav", "mp3", "adf", "vb", 0 }; |
| 73 | + static const info xinfo = { "std.dmaudio", get_version_by_date(), "Silent", -1, extable }; |
| 74 | + return xinfo; |
| 75 | +} |
| 76 | + |
| 77 | +/* |
| 78 | + * DMAudioPlugin::OnStartup |
| 79 | + * Startups the plugin |
| 80 | + */ |
| 81 | +bool DMAudioPlugin::OnStartup() |
| 82 | +{ |
| 83 | + if(gvm.IsIII() || gvm.IsVC()) |
| 84 | + { |
| 85 | + // SFX files are used and loaded constantly, and the game doesn't even support closing sfx.raw. |
| 86 | + // They are actually fopen, but the signatures are compatible |
| 87 | + auto no_reinstall = file_overrider::params(nullptr); |
| 88 | + this->sfx_sdt_overrider.SetParams(no_reinstall).SetFileDetour(OpenFileDetour<xVc(0x5D5B7B)>()); |
| 89 | + this->sfx_raw_overrider.SetParams(no_reinstall).SetFileDetour(OpenFileDetour<xVc(0x5D5BC5)>()); |
| 90 | + |
| 91 | + // Patch strcat calls in cSampleManager |
| 92 | + using namespace injector; |
| 93 | + const auto strcatFunc = raw_ptr(&PatchedStrcat); |
| 94 | + |
| 95 | + MakeCALL(xVc(0x5D6339), strcatFunc); // cSampleManager::StartStreamedFile |
| 96 | + MakeCALL(xVc(0x5D64CD), strcatFunc); // cSampleManager::PreloadStreamedFile |
| 97 | + MakeCALL(xVc(0x5D799B), strcatFunc); // cSampleManager::Initialise |
| 98 | + MakeCALL(xVc(0x5D7B69), strcatFunc); // cSampleManager::Initialise |
| 99 | + //MakeCALL(xVc(0x5D71E3), strcatFunc); // cSampleManager::CheckForAnAudioFileOnCD (pointless) |
| 100 | + if (gvm.IsVC()) |
| 101 | + { |
| 102 | + MakeCALL(xVc(0x5D7C1B), strcatFunc); // cSampleManager::Initialise |
| 103 | + MakeCALL(xVc(0x5D7A7B), strcatFunc); // cSampleManager::Initialise |
| 104 | + } |
| 105 | + |
| 106 | + return true; |
| 107 | + } |
| 108 | + return false; |
| 109 | +} |
| 110 | + |
| 111 | +/* |
| 112 | + * DMAudioPlugin::OnShutdown |
| 113 | + * Shutdowns the plugin |
| 114 | + */ |
| 115 | +bool DMAudioPlugin::OnShutdown() |
| 116 | +{ |
| 117 | + return true; |
| 118 | +} |
| 119 | + |
| 120 | + |
| 121 | +/* |
| 122 | + * DMAudioPlugin::GetBehaviour |
| 123 | + * Gets the relationship between this plugin and the file |
| 124 | + */ |
| 125 | +int DMAudioPlugin::GetBehaviour(modloader::file& file) |
| 126 | +{ |
| 127 | + if(!file.is_dir()) |
| 128 | + { |
| 129 | + static const auto sfx_raw = modloader::hash("sfx.raw"); |
| 130 | + static const auto sfx_sdt = modloader::hash("sfx.sdt"); |
| 131 | + if(file.hash == sfx_raw) |
| 132 | + { |
| 133 | + file.behaviour = SetType(file.hash, Type::SfxRaw); |
| 134 | + } |
| 135 | + else if(file.hash == sfx_sdt) |
| 136 | + { |
| 137 | + file.behaviour = SetType(file.hash, Type::SfxSdt); |
| 138 | + } |
| 139 | + else if(file.is_ext("wav") || file.is_ext("mp3") || file.is_ext("adf") || file.is_ext("vb")) |
| 140 | + { |
| 141 | + // Duplicate file names are allowed |
| 142 | + file.behaviour = SetType(modloader::hash(file.filepath()), Type::Sample); |
| 143 | + } |
| 144 | + else |
| 145 | + { |
| 146 | + return MODLOADER_BEHAVIOUR_NO; |
| 147 | + } |
| 148 | + return MODLOADER_BEHAVIOUR_YES; |
| 149 | + } |
| 150 | + return MODLOADER_BEHAVIOUR_NO; |
| 151 | +} |
| 152 | + |
| 153 | + |
| 154 | +/* |
| 155 | + * DMAudioPlugin::InstallFile |
| 156 | + * Installs a file using this plugin |
| 157 | + */ |
| 158 | +bool DMAudioPlugin::InstallFile(const modloader::file& file) |
| 159 | +{ |
| 160 | + switch(GetType(file.behaviour)) |
| 161 | + { |
| 162 | + case Type::SfxRaw: return sfx_raw_overrider.InstallFile(file); |
| 163 | + case Type::SfxSdt: return sfx_sdt_overrider.InstallFile(file); |
| 164 | + case Type::Sample: this->streams.emplace(file.hash, &file); return true; |
| 165 | + } |
| 166 | + return false; |
| 167 | +} |
| 168 | + |
| 169 | +/* |
| 170 | + * DMAudioPlugin::ReinstallFile |
| 171 | + * Reinstall a file previosly installed that has been updated |
| 172 | + */ |
| 173 | +bool DMAudioPlugin::ReinstallFile(const modloader::file& file) |
| 174 | +{ |
| 175 | + switch(GetType(file.behaviour)) |
| 176 | + { |
| 177 | + case Type::SfxRaw: return sfx_raw_overrider.ReinstallFile(); |
| 178 | + case Type::SfxSdt: return sfx_sdt_overrider.ReinstallFile(); |
| 179 | + case Type::Sample: return true; // No need to do anything |
| 180 | + } |
| 181 | + return false; |
| 182 | +} |
| 183 | + |
| 184 | +/* |
| 185 | + * DMAudioPlugin::UninstallFile |
| 186 | + * Uninstall a previosly installed file |
| 187 | + */ |
| 188 | +bool DMAudioPlugin::UninstallFile(const modloader::file& file) |
| 189 | +{ |
| 190 | + switch(GetType(file.behaviour)) |
| 191 | + { |
| 192 | + case Type::SfxRaw: return sfx_raw_overrider.UninstallFile(); |
| 193 | + case Type::SfxSdt: return sfx_sdt_overrider.UninstallFile(); |
| 194 | + case Type::Sample: |
| 195 | + { |
| 196 | + auto range = this->streams.equal_range(file.hash); |
| 197 | + for (auto it = range.first; it != range.second; ++it) |
| 198 | + { |
| 199 | + if (it->second == &file) |
| 200 | + { |
| 201 | + this->streams.erase(it); |
| 202 | + return true; |
| 203 | + } |
| 204 | + } |
| 205 | + break; |
| 206 | + } |
| 207 | + } |
| 208 | + return false; |
| 209 | +} |
| 210 | + |
| 211 | +char* DMAudioPlugin::PatchedStrcat(char* destination, const char* source) |
| 212 | +{ |
| 213 | + const auto& plugin = plugin_ptr->cast<DMAudioPlugin>(); |
| 214 | + |
| 215 | + const char* lookupName; |
| 216 | + const char* filename = std::strrchr(source, '\\'); |
| 217 | + if (filename != nullptr) |
| 218 | + { |
| 219 | + lookupName = filename+1; |
| 220 | + } |
| 221 | + else |
| 222 | + { |
| 223 | + lookupName = source; |
| 224 | + } |
| 225 | + auto range = plugin.streams.equal_range(modloader::hash(lookupName, ::tolower)); |
| 226 | + if (range.first != range.second) |
| 227 | + { |
| 228 | + // If we have just one match, return it. Else, find the closest common path suffix and use that. |
| 229 | + auto bestMatch = range.first; |
| 230 | + if (std::distance(range.first, range.second) > 1) |
| 231 | + { |
| 232 | + size_t bestScore = 1; |
| 233 | + const std::string sourcePath = modloader::NormalizePath(source); |
| 234 | + for (auto it = range.first; it != range.second; ++it) |
| 235 | + { |
| 236 | + const std::string modFilepath(it->second->filepath()); |
| 237 | + |
| 238 | + size_t currentScore = 2; |
| 239 | + size_t sourceSuffixPos; |
| 240 | + do |
| 241 | + { |
| 242 | + sourceSuffixPos = GetLastPathComponent(sourcePath, currentScore); |
| 243 | + const size_t modSuffixPos = GetLastPathComponent(modFilepath, currentScore); |
| 244 | + if (sourcePath.compare(sourceSuffixPos, std::string::npos, modFilepath, modSuffixPos, std::string::npos) != 0) |
| 245 | + { |
| 246 | + break; |
| 247 | + } |
| 248 | + currentScore++; |
| 249 | + } |
| 250 | + while (sourceSuffixPos != 0); |
| 251 | + |
| 252 | + if (currentScore > bestScore) |
| 253 | + { |
| 254 | + bestScore = currentScore; |
| 255 | + bestMatch = it; |
| 256 | + } |
| 257 | + } |
| 258 | + } |
| 259 | + |
| 260 | + std::string path; |
| 261 | + return strcpy(destination, bestMatch->second->fullpath(path).c_str()); |
| 262 | + } |
| 263 | + return strcat(destination, source); |
| 264 | +} |
0 commit comments