diff --git a/doc/plugins/gta3/std.dmaudio.md b/doc/plugins/gta3/std.dmaudio.md new file mode 100644 index 00000000..77bd415e --- /dev/null +++ b/doc/plugins/gta3/std.dmaudio.md @@ -0,0 +1,13 @@ +gta3.std.dmaudio +========================================================================= + + __Author__: Silent + + __Priority__: 50 + + __Game__: III, Vice City + +************************************************************************* + +__Description__: + This plugin is responsible for DMAudio SFX archives, streamed samples, cutscenes, and radios, that is: + + * SFX archives (sfx.raw/sfx.sdt) + * WAV/MP3/ADF files in the Audio directory diff --git a/doc/readme/Leia-me.md b/doc/readme/Leia-me.md index 0bef4732..faec4008 100644 --- a/doc/readme/Leia-me.md +++ b/doc/readme/Leia-me.md @@ -101,6 +101,6 @@ Finalmente, créditos. #### Muito Obrigado à * ArtututuVidor$, Andryo, Junior_Djjr e JNRois12 por alpha-testarem * Gramps e TJGM pelo suporte emocional. - * SilentPL por muitos fixes e ajudas no desenvolvimento do projeto. + * Silent por muitos fixes e ajudas no desenvolvimento do projeto. * ThirteenAG por me fornecer varios ponteiros (literalmente) para a versão do GTA III / Vice City. diff --git a/doc/readme/Readme.md b/doc/readme/Readme.md index 00714b53..9b02a545 100644 --- a/doc/readme/Readme.md +++ b/doc/readme/Readme.md @@ -102,5 +102,5 @@ Finally, let's go to the credits. #### Special Thanks To * ArtututuVidor$, Andryo, Junior_Djjr, JNRois12 for alpha-testing * Gramps and TJGM for emotional support. - * SilentPL for A LOT of fixes and help with the development of Mod Loader. + * Silent for A LOT of fixes and help with the development of Mod Loader. * ThirteenAG for giving me lots of pointers (literally) for the GTA III / Vice City version. diff --git a/premake5.lua b/premake5.lua index d7fd143c..ad102dbe 100644 --- a/premake5.lua +++ b/premake5.lua @@ -280,6 +280,7 @@ solution "modloader" "std.sprites", "std.fx", "std.text", + "std.dmaudio", "std.tracks", "std.bank", "std.stream", diff --git a/src/plugins/gta3/std.dmaudio/dmaudio.cpp b/src/plugins/gta3/std.dmaudio/dmaudio.cpp new file mode 100644 index 00000000..3e441b36 --- /dev/null +++ b/src/plugins/gta3/std.dmaudio/dmaudio.cpp @@ -0,0 +1,264 @@ +/* + * DMAudio Loader Plugin for Mod Loader + * Copyright (C) 2013-2026 Silent + * Licensed under the MIT License, see LICENSE at top level directory. + * + * std.dmaudio -- Standard DMAudio Loader Plugin for Mod Loader + * + */ +#include +using namespace modloader; + +enum class Type +{ + SfxRaw = 0, // sfx.raw + SfxSdt = 1, // sfx.dat + Sample = 2, // Samples (.wav, .mp3, .adf, .vb) + Max = 3, // Max 2 bits +}; + + +// Basic masks +static const uint64_t hash_mask_base = 0xFFFFFFFF; +static const uint64_t type_mask_base = 0x0003; // Mask for type without any shifting +static const uint32_t type_mask_shf = 32; // Takes 2 bits, starts from 33th bit because first 32th bits is a hash + +// Sets the initial value for a behaviour, by using an filename hash and file type +inline uint64_t SetType(uint32_t hash, Type type) +{ + return modloader::file::set_mask(uint64_t(hash), type_mask_base, type_mask_shf, type); +} + +// Gets the behaviour file type +inline Type GetType(uint64_t mask) +{ + return modloader::file::get_mask(mask, type_mask_base, type_mask_shf); +} + + +/* + * The plugin object + */ +class DMAudioPlugin : public modloader::basic_plugin +{ + private: + file_overrider sfx_raw_overrider; // sfx.raw overrider + file_overrider sfx_sdt_overrider; // sfx.sdt overrider + std::multimap streams; // Stream (hash, file*) map + + public: + // Standard plugin methods + const info& GetInfo() override; + bool OnStartup() override; + bool OnShutdown() override; + int GetBehaviour(modloader::file&) override; + bool InstallFile(const modloader::file&) override; + bool ReinstallFile(const modloader::file&) override; + bool UninstallFile(const modloader::file&) override; + + private: + static char* PatchedStrcat(char* destination, const char* source); + +} dmaudio_plugin; + +REGISTER_ML_PLUGIN(::dmaudio_plugin); + +/* + * DMAudioPlugin::GetInfo + * Returns information about this plugin + */ +const DMAudioPlugin::info& DMAudioPlugin::GetInfo() +{ + static const char* extable[] = { "raw", "sdt", "wav", "mp3", "adf", "vb", 0 }; + static const info xinfo = { "std.dmaudio", get_version_by_date(), "Silent", -1, extable }; + return xinfo; +} + +/* + * DMAudioPlugin::OnStartup + * Startups the plugin + */ +bool DMAudioPlugin::OnStartup() +{ + if(gvm.IsIII() || gvm.IsVC()) + { + // SFX files are used and loaded constantly, and the game doesn't even support closing sfx.raw. + // They are actually fopen, but the signatures are compatible + auto no_reinstall = file_overrider::params(nullptr); + this->sfx_sdt_overrider.SetParams(no_reinstall).SetFileDetour(OpenFileDetour()); + this->sfx_raw_overrider.SetParams(no_reinstall).SetFileDetour(OpenFileDetour()); + + // Patch strcat calls in cSampleManager + using namespace injector; + const auto strcatFunc = raw_ptr(&PatchedStrcat); + + MakeCALL(xVc(0x5D6339), strcatFunc); // cSampleManager::StartStreamedFile + MakeCALL(xVc(0x5D64CD), strcatFunc); // cSampleManager::PreloadStreamedFile + MakeCALL(xVc(0x5D799B), strcatFunc); // cSampleManager::Initialise + MakeCALL(xVc(0x5D7B69), strcatFunc); // cSampleManager::Initialise + //MakeCALL(xVc(0x5D71E3), strcatFunc); // cSampleManager::CheckForAnAudioFileOnCD (pointless) + if (gvm.IsVC()) + { + MakeCALL(xVc(0x5D7C1B), strcatFunc); // cSampleManager::Initialise + MakeCALL(xVc(0x5D7A7B), strcatFunc); // cSampleManager::Initialise + } + + return true; + } + return false; +} + +/* + * DMAudioPlugin::OnShutdown + * Shutdowns the plugin + */ +bool DMAudioPlugin::OnShutdown() +{ + return true; +} + + +/* + * DMAudioPlugin::GetBehaviour + * Gets the relationship between this plugin and the file + */ +int DMAudioPlugin::GetBehaviour(modloader::file& file) +{ + if(!file.is_dir()) + { + static const auto sfx_raw = modloader::hash("sfx.raw"); + static const auto sfx_sdt = modloader::hash("sfx.sdt"); + if(file.hash == sfx_raw) + { + file.behaviour = SetType(file.hash, Type::SfxRaw); + } + else if(file.hash == sfx_sdt) + { + file.behaviour = SetType(file.hash, Type::SfxSdt); + } + else if(file.is_ext("wav") || file.is_ext("mp3") || file.is_ext("adf") || file.is_ext("vb")) + { + // Duplicate file names are allowed + file.behaviour = SetType(modloader::hash(file.filepath()), Type::Sample); + } + else + { + return MODLOADER_BEHAVIOUR_NO; + } + return MODLOADER_BEHAVIOUR_YES; + } + return MODLOADER_BEHAVIOUR_NO; +} + + +/* + * DMAudioPlugin::InstallFile + * Installs a file using this plugin + */ +bool DMAudioPlugin::InstallFile(const modloader::file& file) +{ + switch(GetType(file.behaviour)) + { + case Type::SfxRaw: return sfx_raw_overrider.InstallFile(file); + case Type::SfxSdt: return sfx_sdt_overrider.InstallFile(file); + case Type::Sample: this->streams.emplace(file.hash, &file); return true; + } + return false; +} + +/* + * DMAudioPlugin::ReinstallFile + * Reinstall a file previosly installed that has been updated + */ +bool DMAudioPlugin::ReinstallFile(const modloader::file& file) +{ + switch(GetType(file.behaviour)) + { + case Type::SfxRaw: return sfx_raw_overrider.ReinstallFile(); + case Type::SfxSdt: return sfx_sdt_overrider.ReinstallFile(); + case Type::Sample: return true; // No need to do anything + } + return false; +} + +/* + * DMAudioPlugin::UninstallFile + * Uninstall a previosly installed file + */ +bool DMAudioPlugin::UninstallFile(const modloader::file& file) +{ + switch(GetType(file.behaviour)) + { + case Type::SfxRaw: return sfx_raw_overrider.UninstallFile(); + case Type::SfxSdt: return sfx_sdt_overrider.UninstallFile(); + case Type::Sample: + { + auto range = this->streams.equal_range(file.hash); + for (auto it = range.first; it != range.second; ++it) + { + if (it->second == &file) + { + this->streams.erase(it); + return true; + } + } + break; + } + } + return false; +} + +char* DMAudioPlugin::PatchedStrcat(char* destination, const char* source) +{ + const auto& plugin = plugin_ptr->cast(); + + const char* lookupName; + const char* filename = std::strrchr(source, '\\'); + if (filename != nullptr) + { + lookupName = filename+1; + } + else + { + lookupName = source; + } + auto range = plugin.streams.equal_range(modloader::hash(lookupName, ::tolower)); + if (range.first != range.second) + { + // If we have just one match, return it. Else, find the closest common path suffix and use that. + auto bestMatch = range.first; + if (std::distance(range.first, range.second) > 1) + { + size_t bestScore = 1; + const std::string sourcePath = modloader::NormalizePath(source); + for (auto it = range.first; it != range.second; ++it) + { + const std::string modFilepath(it->second->filepath()); + + size_t currentScore = 2; + size_t sourceSuffixPos; + do + { + sourceSuffixPos = GetLastPathComponent(sourcePath, currentScore); + const size_t modSuffixPos = GetLastPathComponent(modFilepath, currentScore); + if (sourcePath.compare(sourceSuffixPos, std::string::npos, modFilepath, modSuffixPos, std::string::npos) != 0) + { + break; + } + currentScore++; + } + while (sourceSuffixPos != 0); + + if (currentScore > bestScore) + { + bestScore = currentScore; + bestMatch = it; + } + } + } + + std::string path; + return strcpy(destination, bestMatch->second->fullpath(path).c_str()); + } + return strcat(destination, source); +} diff --git a/src/translator/gta3/3/10.hpp b/src/translator/gta3/3/10.hpp index 33694b47..746ba54c 100644 --- a/src/translator/gta3/3/10.hpp +++ b/src/translator/gta3/3/10.hpp @@ -249,6 +249,19 @@ static void III_10(std::map& map) map[xIII(0x54BB7C)] = 0x54BB7C;// call _ZN6CPlane8LoadPathEPKcRiRfb ; "flight4.dat" } + // std.dmaudio + if(true) + { + map[xVc(0x5D5B7B)] = 0x5682E1; // call fopen ; @cSampleManager::InitialiseSampleBanks (sfx.sdt) + map[xVc(0x5D5BC5)] = 0x568301; // call fopen ; @cSampleManager::InitialiseSampleBanks (sfx.raw) + + map[xVc(0x5D6339)] = 0x568066; // call strcat ; cSampleManager::StartStreamedFile + map[xVc(0x5D64CD)] = 0x567CCC; // call strcat ; cSampleManager::PreloadStreamedFile + map[xVc(0x5D799B)] = 0x566A76; // call strcat ; cSampleManager::Initialise + map[xVc(0x5D7B69)] = 0x566AC6; // call strcat ; cSampleManager::Initialise + //map[xVc(0x5D71E3)] = 0x566EE9; // call strcat ; cSampleManager::CheckForAnAudioFileOnCD (pointless) + } + // traits if(true) { diff --git a/src/translator/gta3/vc/10.hpp b/src/translator/gta3/vc/10.hpp index bfeda86b..02223a7e 100644 --- a/src/translator/gta3/vc/10.hpp +++ b/src/translator/gta3/vc/10.hpp @@ -293,6 +293,21 @@ static void vc_10(std::map& map) map[xVc(0x5B24AE)] = 0x5B24AE; // call _ZN6CPlane8LoadPathEPKcRiRfb ; "flight3.dat" } + // std.dmaudio + if(true) + { + map[xVc(0x5D5B7B)] = 0x5D5B7B; // call fopen ; @cSampleManager::InitialiseSampleBanks (sfx.sdt) + map[xVc(0x5D5BC5)] = 0x5D5BC5; // call fopen ; @cSampleManager::InitialiseSampleBanks (sfx.raw) + + map[xVc(0x5D6339)] = 0x5D6339; // call strcat ; cSampleManager::StartStreamedFile + map[xVc(0x5D64CD)] = 0x5D64CD; // call strcat ; cSampleManager::PreloadStreamedFile + map[xVc(0x5D799B)] = 0x5D799B; // call strcat ; cSampleManager::Initialise + map[xVc(0x5D7B69)] = 0x5D7B69; // call strcat ; cSampleManager::Initialise + //map[xVc(0x5D71E3)] = 0x5D71E3; // call strcat ; cSampleManager::CheckForAnAudioFileOnCD (pointless) + map[xVc(0x5D7C1B)] = 0x5D7C1B; // call strcat ; cSampleManager::Initialise + map[xVc(0x5D7A7B)] = 0x5D7A7B; // call strcat ; cSampleManager::Initialise + } + // traits if(true) {