Skip to content

Commit cd44423

Browse files
feat: create std.dmaudio for III/VC audios (#135)
* std.dmaudio: Support overriding sfx.raw/sfx.sdt sfx.raw is permanently opened by the game, so any attempts to reinstall are pointless. Loose files cannot be loaded at the moment, but they are likely theoretically possible for replacements. Loose addon SFX files are meaningfless, as the game accesses them exclusively through an ID enum, and thus there is no way to make sure new sounds sit where mods expect them to. * std.dmaudio: Support overriding and adding audio samples * std.audio: Match duplicate stream names on the longest path suffix
1 parent e040e7e commit cd44423

7 files changed

Lines changed: 308 additions & 2 deletions

File tree

doc/plugins/gta3/std.dmaudio.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
gta3.std.dmaudio
2+
=========================================================================
3+
+ __Author__: Silent
4+
+ __Priority__: 50
5+
+ __Game__: III, Vice City
6+
7+
*************************************************************************
8+
9+
__Description__:
10+
This plugin is responsible for DMAudio SFX archives, streamed samples, cutscenes, and radios, that is:
11+
12+
* SFX archives (sfx.raw/sfx.sdt)
13+
* WAV/MP3/ADF files in the Audio directory

doc/readme/Leia-me.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,6 @@ Finalmente, créditos.
101101
#### Muito Obrigado à
102102
* ArtututuVidor$, Andryo, Junior_Djjr e JNRois12 por alpha-testarem
103103
* Gramps e TJGM pelo suporte emocional.
104-
* SilentPL por muitos fixes e ajudas no desenvolvimento do projeto.
104+
* Silent por muitos fixes e ajudas no desenvolvimento do projeto.
105105
* ThirteenAG por me fornecer varios ponteiros (literalmente) para a versão do GTA III / Vice City.
106106

doc/readme/Readme.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,5 +102,5 @@ Finally, let's go to the credits.
102102
#### Special Thanks To
103103
* ArtututuVidor$, Andryo, Junior_Djjr, JNRois12 for alpha-testing
104104
* Gramps and TJGM for emotional support.
105-
* SilentPL for A LOT of fixes and help with the development of Mod Loader.
105+
* Silent for A LOT of fixes and help with the development of Mod Loader.
106106
* ThirteenAG for giving me lots of pointers (literally) for the GTA III / Vice City version.

premake5.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,7 @@ solution "modloader"
280280
"std.sprites",
281281
"std.fx",
282282
"std.text",
283+
"std.dmaudio",
283284
"std.tracks",
284285
"std.bank",
285286
"std.stream",
Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
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+
}

src/translator/gta3/3/10.hpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,19 @@ static void III_10(std::map<memory_pointer_raw, memory_pointer_raw>& map)
250250
map[xIII(0x54BB7C)] = 0x54BB7C;// call _ZN6CPlane8LoadPathEPKcRiRfb ; "flight4.dat"
251251
}
252252

253+
// std.dmaudio
254+
if(true)
255+
{
256+
map[xVc(0x5D5B7B)] = 0x5682E1; // call fopen ; @cSampleManager::InitialiseSampleBanks (sfx.sdt)
257+
map[xVc(0x5D5BC5)] = 0x568301; // call fopen ; @cSampleManager::InitialiseSampleBanks (sfx.raw)
258+
259+
map[xVc(0x5D6339)] = 0x568066; // call strcat ; cSampleManager::StartStreamedFile
260+
map[xVc(0x5D64CD)] = 0x567CCC; // call strcat ; cSampleManager::PreloadStreamedFile
261+
map[xVc(0x5D799B)] = 0x566A76; // call strcat ; cSampleManager::Initialise
262+
map[xVc(0x5D7B69)] = 0x566AC6; // call strcat ; cSampleManager::Initialise
263+
//map[xVc(0x5D71E3)] = 0x566EE9; // call strcat ; cSampleManager::CheckForAnAudioFileOnCD (pointless)
264+
}
265+
253266
// traits
254267
if(true)
255268
{

src/translator/gta3/vc/10.hpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,21 @@ static void vc_10(std::map<memory_pointer_raw, memory_pointer_raw>& map)
294294
map[xVc(0x5B24AE)] = 0x5B24AE; // call _ZN6CPlane8LoadPathEPKcRiRfb ; "flight3.dat"
295295
}
296296

297+
// std.dmaudio
298+
if(true)
299+
{
300+
map[xVc(0x5D5B7B)] = 0x5D5B7B; // call fopen ; @cSampleManager::InitialiseSampleBanks (sfx.sdt)
301+
map[xVc(0x5D5BC5)] = 0x5D5BC5; // call fopen ; @cSampleManager::InitialiseSampleBanks (sfx.raw)
302+
303+
map[xVc(0x5D6339)] = 0x5D6339; // call strcat ; cSampleManager::StartStreamedFile
304+
map[xVc(0x5D64CD)] = 0x5D64CD; // call strcat ; cSampleManager::PreloadStreamedFile
305+
map[xVc(0x5D799B)] = 0x5D799B; // call strcat ; cSampleManager::Initialise
306+
map[xVc(0x5D7B69)] = 0x5D7B69; // call strcat ; cSampleManager::Initialise
307+
//map[xVc(0x5D71E3)] = 0x5D71E3; // call strcat ; cSampleManager::CheckForAnAudioFileOnCD (pointless)
308+
map[xVc(0x5D7C1B)] = 0x5D7C1B; // call strcat ; cSampleManager::Initialise
309+
map[xVc(0x5D7A7B)] = 0x5D7A7B; // call strcat ; cSampleManager::Initialise
310+
}
311+
297312
// traits
298313
if(true)
299314
{

0 commit comments

Comments
 (0)