#include #include "loader/component_loader.hpp" #include "game/game.hpp" #include "game/dvars.hpp" #include "sound.hpp" #include "filesystem.hpp" #include "console.hpp" #include #include #include #include #include // https://github.com/skkuull/h1-zonetool/blob/main/src/client/zonetool/assets/sound.cpp // https://github.com/skkuull/h1-zonetool/blob/main/src/client/zonetool/assets/loadedsound.cpp namespace sound { namespace { utils::memory::allocator sound_allocator; using loaded_sound_map = std::unordered_map; utils::concurrency::container loaded_sounds; #define FATAL(...) \ throw std::runtime_error(utils::string::va(__VA_ARGS__)); \ #define SOUND_STRING(entry, optional) \ if (j.HasMember(#entry) && j[#entry].IsString()) \ { \ asset->entry = sound_allocator.duplicate_string(j[#entry].GetString()); \ } \ else if (!optional) \ { \ FATAL("member '%s' does not exist or isn't of type 'String'\n", #entry); \ } \ #define SOUND_FLOAT(entry, optional) \ if (j.HasMember(#entry) && (j[#entry].IsFloat() || j[#entry].IsInt())) \ { \ asset->entry = j[#entry].GetFloat(); \ } \ else if (!optional) \ { \ FATAL("member '%s' does not exist or isn't of type 'Float'\n", #entry); \ } \ #define SOUND_INT(entry, optional) \ if (j.HasMember(#entry) && j[#entry].IsInt()) \ { \ asset->entry = j[#entry].GetInt(); \ } \ else if (!optional) \ { \ FATAL("member '%s' does not exist or isn't of type 'Int'\n", #entry); \ } \ #define SOUND_CHAR(entry, optional) \ if (j.HasMember(#entry) && j[#entry].IsInt()) \ { \ asset->entry = static_cast(j[#entry].GetInt()); \ } \ else if (!optional) \ { \ FATAL("member '%s' does not exist or isn't of type 'Char'\n", #entry); \ } \ #define JSON_GET(parent, name, type, parent_name) \ (parent.HasMember(name) && parent[name].Is##type()) \ ? parent[name].Get##type() \ : FATAL("'%s' member '%s' does not exist or it isn't of type '%s'", parent_name, name, #type) \ #define JSON_GET_OPTIONAL(parent, name, type, default_value) \ (parent.HasMember(name) && parent[name].Is##type()) \ ? parent[name].Get##type() \ : default_value \ #define JSON_GET_CAST(parent, name, type, cast_type, parent_name) \ (parent.HasMember(name) && parent[name].Is##type()) \ ? static_cast(parent[name].Get##type()) \ : FATAL("'%s' member '%s' does not exist or it isn't of type '%s'", parent_name, name, #type) \ #define JSON_CHECK(parent, name, type, parent_name) \ if (!parent.HasMember(name) || !parent[name].Is##type()) \ { \ FATAL("'%s' member '%s' does not exist or it isn't of type '%s'", parent_name, name, #type) \ } \ std::string rapidjson_get_object_bytes(const rapidjson::Value& value) { std::string buffer{}; if (!value.IsArray()) { return buffer; } for (auto i = 0; i < static_cast(value.Size()); i++) { buffer += static_cast(value[i].GetInt()); } return buffer; } rapidjson::Value rapidjson_bytes_to_object(const std::vector& bytes, rapidjson::Document& j) { rapidjson::Value arr{rapidjson::kArrayType}; for (const auto& byte : bytes) { arr.PushBack(byte, j.GetAllocator()); } return arr; } game::LoadedSound* parse_flac(const std::string& name) { const auto path = "loaded_sound/"s + name + ".flac"; std::string data{}; if (!filesystem::read_file(path, &data)) { return nullptr; } console::info("[Sound] Parsing flac %s\n", path.data()); auto* result = sound_allocator.allocate(); result->name = sound_allocator.duplicate_string(name); result->info.loadedSize = static_cast(data.size()); result->info.data = sound_allocator.allocate_array(result->info.loadedSize); std::memcpy(result->info.data, data.data(), data.size()); return result; } game::LoadedSound* parse_wav(const std::string& name) { const auto path = "loaded_sound/"s + name + ".wav"; std::string full_path{}; if (!filesystem::find_file(path, &full_path)) { return nullptr; } console::info("[Sound] Parsing wav %s\n", path.data()); std::ifstream file; file.open(full_path, std::ios::binary); if (!file.is_open()) { FATAL("failed to open loaded sound file: %s\n", name.data()); } const auto result = sound_allocator.allocate(); unsigned int chunk_id_buffer{}; unsigned int chunk_size{}; file.read(reinterpret_cast(&chunk_id_buffer), 4); if (chunk_id_buffer != 0x46464952) // RIFF { FATAL("%s: Invalid RIFF Header 0x%lX.", name.data(), chunk_id_buffer); } file.read(reinterpret_cast(&chunk_size), 4); file.read(reinterpret_cast(&chunk_id_buffer), 4); if (chunk_id_buffer != 0x45564157) // WAVE { FATAL("%s: Invalid WAVE Header 0x%lX.", name.data(), chunk_id_buffer); } while (!result->info.data && !file.eof()) { file.read(reinterpret_cast(&chunk_id_buffer), 4); file.read(reinterpret_cast(&chunk_size), 4); switch (chunk_id_buffer) { case 0x20746D66: // fmt if (chunk_size >= 16) { short format{}; file.read(reinterpret_cast(&format), 2); if (format != 1 && format != 17) { FATAL("%s: Invalid wave format %i.", name.data(), format); } result->info.format = format; short num_channels{}; file.read(reinterpret_cast(&num_channels), 2); result->info.channels = static_cast(num_channels); int sample_rate{}; file.read(reinterpret_cast(&sample_rate), 4); result->info.sampleRate = sample_rate; int byte_rate{}; file.read(reinterpret_cast(&byte_rate), 4); short block_align{}; file.read(reinterpret_cast(&block_align), 2); result->info.blockAlign = static_cast(block_align); short bit_per_sample{}; file.read(reinterpret_cast(&bit_per_sample), 2); result->info.numBits = static_cast(bit_per_sample); if (chunk_size > 16) { file.seekg(chunk_size - 16, std::ios::cur); } } break; case 0x61746164: // data result->info.data = sound_allocator.allocate_array(chunk_size); file.read(result->info.data, chunk_size); result->info.loadedSize = chunk_size; result->info.dataByteCount = result->info.loadedSize; result->info.numSamples = result->info.dataByteCount / (result->info.channels * result->info.numBits / 8); break; default: if (chunk_size > 0) { file.seekg(chunk_size, std::ios::cur); } break; } } if (!result->info.data) { FATAL("%s: Could not read sounddata.", name.data()); return nullptr; } result->name = sound_allocator.duplicate_string(name); return result; } game::LoadedSound* parse_loaded_sound(const std::string& name) { std::string full_path{}; auto path = "loaded_sound/"s + name; if (filesystem::find_file(path + ".wav", &full_path)) { return parse_wav(name); } if (filesystem::find_file(path + ".flac", &full_path)) { return parse_flac(name); } console::warn("Sound %s not found, falling back to default sound\n", path.data()); return game::DB_FindXAssetHeader(game::ASSET_TYPE_LOADED_SOUND, name.data(), true).loaded_sound; } void parse_sound_alias(const rapidjson::Value& j, game::snd_alias_t* asset) { SOUND_STRING(aliasName, false); SOUND_STRING(secondaryAliasName, true); SOUND_STRING(chainAliasName, true); SOUND_STRING(subtitle, true); SOUND_STRING(mixerGroup, true); if (!j.HasMember("soundfile") || !j["soundfile"].IsObject()) { FATAL("missing 'soundfile' object"); } const auto& sound_file = j["soundfile"]; const auto sound_file_type = JSON_GET(sound_file, "type", Int, "soundfile"); asset->soundFile = sound_allocator.allocate(); asset->soundFile->type = static_cast(sound_file_type); asset->soundFile->exists = true; if (asset->soundFile->type == game::SAT_LOADED) { const auto name = JSON_GET(sound_file, "name", String, "soundfile is missing 'name'"); asset->soundFile->u.loadSnd = parse_loaded_sound(name); } else if (asset->soundFile->type == game::SAT_STREAMED) { asset->soundFile->u.streamSnd.totalMsec = JSON_GET(sound_file, "totalMsec", Uint, "soundfile"); asset->soundFile->u.streamSnd.filename.isLocalized = JSON_GET(sound_file, "isLocalized", Bool, "soundfile"); asset->soundFile->u.streamSnd.filename.isStreamed = JSON_GET(sound_file, "isStreamed", Bool, "soundfile"); asset->soundFile->u.streamSnd.filename.fileIndex = JSON_GET_CAST(sound_file, "fileIndex", Int, unsigned short, "soundfile"); if (asset->soundFile->u.streamSnd.filename.fileIndex) { JSON_CHECK(sound_file, "packed", Object, "soundfile"); asset->soundFile->u.streamSnd.filename.info.packed.offset = JSON_GET(sound_file["packed"], "offset", Uint64, "soundfile.raw"); asset->soundFile->u.streamSnd.filename.info.packed.length = JSON_GET(sound_file["packed"], "length", Uint64, "soundfile.raw"); } else { JSON_CHECK(sound_file, "raw", Object, "soundfile"); const auto dir = JSON_GET(sound_file["raw"], "dir", String, "soundfile.raw"); const auto name = JSON_GET(sound_file["raw"], "name", String, "soundfile.raw"); asset->soundFile->u.streamSnd.filename.info.raw.dir = sound_allocator.duplicate_string(dir); asset->soundFile->u.streamSnd.filename.info.raw.name = sound_allocator.duplicate_string(name); } } else { FATAL("Sound alias has invalid soundFile type %i", asset->soundFile->type); } SOUND_INT(flags, false); SOUND_INT(sequence, false); SOUND_FLOAT(volMin, false); SOUND_FLOAT(volMax, false); SOUND_INT(volModIndex, false); SOUND_FLOAT(pitchMin, false); SOUND_FLOAT(pitchMax, false); SOUND_FLOAT(distMin, false); SOUND_FLOAT(distMax, false); SOUND_FLOAT(velocityMin, false); SOUND_CHAR(masterPriority, false); SOUND_FLOAT(masterPercentage, false); SOUND_FLOAT(slavePercentage, false); SOUND_FLOAT(probability, false); SOUND_INT(startDelay, false); if (j.HasMember("sndContext") && j["sndContext"].IsString()) { asset->sndContext = game::DB_FindXAssetHeader(game::ASSET_TYPE_SNDCONTEXT, j["sndContext"].GetString(), false).snd_context; } if (j.HasMember("sndCurve") && j["sndCurve"].IsString()) { asset->sndCurve = game::DB_FindXAssetHeader(game::ASSET_TYPE_SNDCURVE, j["sndCurve"].GetString(), false).snd_curve; } if (j.HasMember("lpfCurve") && j["lpfCurve"].IsString()) { asset->lpfCurve = game::DB_FindXAssetHeader(game::ASSET_TYPE_LPFCURVE, j["lpfCurve"].GetString(), false).snd_curve; } if (j.HasMember("hpfCurve") && j["hpfCurve"].IsString()) { asset->hpfCurve = game::DB_FindXAssetHeader(game::ASSET_TYPE_LPFCURVE, j["hpfCurve"].GetString(), false).snd_curve; } if (j.HasMember("reverbSendCurve") && j["reverbSendCurve"].IsString()) { asset->reverbSendCurve = game::DB_FindXAssetHeader(game::ASSET_TYPE_REVERBSENDCURVE, j["reverbSendCurve"].GetString(), false).snd_curve; } if (j.HasMember("speakerMap") && j["speakerMap"].IsObject()) { asset->speakerMap = sound_allocator.allocate(); const auto& speaker_map = j["speakerMap"]; const auto speaker_map_name = JSON_GET(speaker_map, "name", String, "speakerMap"); const auto is_default = JSON_GET(speaker_map, "isDefault", Bool, "speakerMap"); asset->speakerMap->name = sound_allocator.duplicate_string(speaker_map_name); asset->speakerMap->isDefault = is_default; if (speaker_map.HasMember("channelMaps") && speaker_map["channelMaps"].IsArray()) { const auto& channel_maps = speaker_map["channelMaps"]; for (char x = 0; x < 2; x++) { for (char y = 0; y < 2; y++) { const auto index = static_cast((x & 0x01) << 1 | y & 0x01); if (index >= channel_maps.Size() || !channel_maps[index].IsObject()) { FATAL("channelMaps at index %i does not exist", index); } const auto& channel_map = channel_maps[index]; asset->speakerMap->channelMaps[x][y].speakerCount = JSON_GET(channel_map, "speakerCount", Int, "speakerMap.channelMaps[]"); if (!channel_map.HasMember("speakers") || !channel_map["speakers"].IsArray()) { FATAL("channelMap does not have a 'speakers' member or it isn't an array"); } const auto& speakers = channel_map["speakers"]; for (auto speaker = 0; speaker < asset->speakerMap->channelMaps[x][y].speakerCount; speaker++) { if (static_cast(speaker) < speakers.Size() && speakers[speaker].IsObject()) { const auto& jspeaker = speakers[speaker]; asset->speakerMap->channelMaps[x][y].speakers[speaker].speaker = JSON_GET_CAST(jspeaker, "speaker", Int, char, "speakerMap.channelMaps.speakers[]"); asset->speakerMap->channelMaps[x][y].speakers[speaker].numLevels = JSON_GET_CAST(jspeaker, "numLevels", Int, char, "speakerMap.channelMaps.speakers[]"); asset->speakerMap->channelMaps[x][y].speakers[speaker].levels[0] = JSON_GET(jspeaker, "levels0", Float, "speakerMap.channelMaps.speakers[]"); asset->speakerMap->channelMaps[x][y].speakers[speaker].levels[1] = JSON_GET(jspeaker, "levels1", Float, "speakerMap.channelMaps.speakers[]"); } else { FATAL("speaker at index %i does not exist or is not an object", speaker); } } } } } } /*SOUND_CHAR(allowDoppler); if (j.HasMember("dopplerPreset") && !j["dopplerPreset"].IsNull()) { asset->dopplerPreset = game::DB_FindXAssetHeader(game::ASSET_TYPE_DOPPLERPRESET, j["dopplerPreset"].GetString(), false).doppler_preset; }*/ if (j.HasMember("unknown") && j["unknown"].IsObject()) { const auto& snd_unknown = j["unknown"]; const auto& pad0 = rapidjson_get_object_bytes(snd_unknown["pad"][0]); const auto& pad1 = rapidjson_get_object_bytes(snd_unknown["pad"][1]); const auto& pad2 = rapidjson_get_object_bytes(snd_unknown["pad"][2]); const auto& pad3 = rapidjson_get_object_bytes(snd_unknown["pad"][3]); std::memcpy(asset->__pad0, pad0.data(), pad0.size()); std::memcpy(asset->__pad1, pad1.data(), pad1.size()); std::memcpy(asset->__pad2, pad2.data(), pad2.size()); std::memcpy(asset->__pad3, pad3.data(), pad3.size()); asset->u4 = JSON_GET_OPTIONAL(snd_unknown, "u4", Int, 0); asset->u5 = JSON_GET_OPTIONAL(snd_unknown, "u5", Int, 0); asset->u18 = static_cast(JSON_GET_OPTIONAL(snd_unknown, "u18", Int, 0)); asset->u20 = static_cast(JSON_GET_OPTIONAL(snd_unknown, "u20", Int, 0)); asset->u34 = JSON_GET_OPTIONAL(snd_unknown, "u34", Float, 0.f); } } game::snd_alias_list_t* parse_sound_alias_list(const rapidjson::Document& j) { const auto asset = sound_allocator.allocate(); SOUND_STRING(aliasName, false); asset->count = JSON_GET_CAST(j, "count", Int, char, "sound"); asset->head = sound_allocator.allocate_array(asset->count); JSON_CHECK(j, "head", Array, "sound"); const auto head = j["head"].GetArray(); for (auto i = 0; i < static_cast(asset->count) && head.Size(); i++) { parse_sound_alias(head[i], &asset->head[i]); } if (j.HasMember("unknownArray") && j["unknownArray"].IsArray()) { const auto& unk = j["unknownArray"]; asset->unkCount = static_cast(unk.Size()); asset->unk = sound_allocator.allocate_array(asset->unkCount); for (unsigned char i = 0; i < asset->unkCount && unk.Size(); i++) { asset->unk[i] = static_cast(unk[i].GetInt()); } } return asset; } bool sound_exists(const std::string& name) { return loaded_sounds.access([&](loaded_sound_map& map) { const auto i = map.find(name); if (i != map.end()) { return true; } return find_sound(name.data()).sound != nullptr; }); } utils::hook::detour db_is_xasset_default_hook; bool db_is_xasset_default_stub(game::XAssetType type, const char* name) { if (type == game::ASSET_TYPE_SOUND) { const auto res = db_is_xasset_default_hook.invoke(type, name); if (!res) { return res; } return !sound_exists(name); } return db_is_xasset_default_hook.invoke(type, name); } utils::hook::detour db_xasset_exists_hook; bool db_xasset_exists_stub(game::XAssetType type, const char* name) { if (type == game::ASSET_TYPE_SOUND) { const auto res = db_xasset_exists_hook.invoke(type, name); if (res) { return true; } return sound_exists(name); } return db_xasset_exists_hook.invoke(type, name); } utils::hook::detour scr_table_lookup_hook; void scr_table_lookup_stub() { const auto table = game::Scr_GetString(0); const auto search_column = game::Scr_GetInt(1); const auto search_value = game::Scr_GetString(2); const auto return_row = game::Scr_GetInt(3); if (table != "mp/sound/soundlength.csv"s || search_column != 0 || return_row != 1) { return scr_table_lookup_hook.invoke(); } std::optional new_value{}; loaded_sounds.access([&](loaded_sound_map& map) { const auto i = map.find(search_value); if (i == map.end()) { return; } const auto sound_list = i->second; if (sound_list->count) { const auto sound = &sound_list->head[0]; if (sound->soundFile && sound->soundFile->type == game::SAT_STREAMED) { new_value = sound->soundFile->u.streamSnd.totalMsec; } } }); if (new_value.has_value()) { game::Scr_AddString(utils::string::va("%i\n", new_value.value())); } else { scr_table_lookup_hook.invoke(); } } void com_sprintf_raw_sound_localized_stub(char* buffer, int size, const char* fmt, const char* lang, const char* name, const char* extension) { sprintf_s(buffer, size, "%s%s", name, extension); } void com_sprintf_raw_sound_stub(char* buffer, int size, const char* fmt, const char* name, const char* extension) { sprintf_s(buffer, size, "%s%s", name, extension); } utils::hook::detour snd_is_music_playing_hook; bool snd_is_music_playing_stub(void* a1) { if (a1 == nullptr) { return true; // dont play music } return snd_is_music_playing_hook.invoke(a1); } } void dump_sound(game::snd_alias_list_t* asset) { if (asset == nullptr) { return; } rapidjson::Document j; j.SetObject(); j.AddMember("aliasName", rapidjson::StringRef(asset->aliasName), j.GetAllocator()); j.AddMember("count", asset->count, j.GetAllocator()); rapidjson::Value head{rapidjson::kArrayType}; for (auto i = 0; i < asset->count; i++) { const auto snd_head = &asset->head[i]; rapidjson::Value entry{rapidjson::kObjectType}; entry.AddMember("aliasName", rapidjson::StringRef(snd_head->aliasName), j.GetAllocator()); if (snd_head->secondaryAliasName) { entry.AddMember("secondaryAliasName", rapidjson::StringRef(snd_head->secondaryAliasName), j.GetAllocator()); } else { entry.AddMember("secondaryAliasName", rapidjson::Value{rapidjson::kNullType}, j.GetAllocator()); } if (snd_head->chainAliasName) { entry.AddMember("chainAliasName", rapidjson::StringRef(snd_head->chainAliasName), j.GetAllocator()); } else { entry.AddMember("chainAliasName", rapidjson::Value{ rapidjson::kNullType }, j.GetAllocator()); } if (snd_head->subtitle) { entry.AddMember("subtitle", rapidjson::StringRef(snd_head->subtitle), j.GetAllocator()); } else { entry.AddMember("subtitle", rapidjson::Value{ rapidjson::kNullType }, j.GetAllocator()); } if (snd_head->mixerGroup) { entry.AddMember("mixerGroup", rapidjson::StringRef(snd_head->mixerGroup), j.GetAllocator()); } else { entry.AddMember("mixerGroup", rapidjson::Value{ rapidjson::kNullType }, j.GetAllocator()); } if (snd_head->soundFile) { rapidjson::Value sound_file{rapidjson::kObjectType}; sound_file.AddMember("type", snd_head->soundFile->type, j.GetAllocator()); if (snd_head->soundFile->exists) { if (snd_head->soundFile->type == game::SAT_LOADED) { sound_file.AddMember("name", rapidjson::StringRef(snd_head->soundFile->u.loadSnd->name), j.GetAllocator()); } else if (snd_head->soundFile->type == game::SAT_STREAMED) { sound_file.AddMember("totalMsec", snd_head->soundFile->u.streamSnd.totalMsec, j.GetAllocator()); sound_file.AddMember("isLocalized", snd_head->soundFile->u.streamSnd.filename.isLocalized, j.GetAllocator()); sound_file.AddMember("isStreamed", snd_head->soundFile->u.streamSnd.filename.isStreamed, j.GetAllocator()); sound_file.AddMember("fileIndex", snd_head->soundFile->u.streamSnd.filename.fileIndex, j.GetAllocator()); if (snd_head->soundFile->u.streamSnd.filename.fileIndex) { rapidjson::Value packed{rapidjson::kObjectType}; packed.AddMember("offset", snd_head->soundFile->u.streamSnd.filename.info.packed.offset, j.GetAllocator()); packed.AddMember("length", snd_head->soundFile->u.streamSnd.filename.info.packed.length, j.GetAllocator()); sound_file.AddMember("packed", packed, j.GetAllocator()); } else { rapidjson::Value raw{rapidjson::kObjectType}; raw.AddMember("dir", rapidjson::StringRef(snd_head->soundFile->u.streamSnd.filename.info.raw.dir), j.GetAllocator()); raw.AddMember("name", rapidjson::StringRef(snd_head->soundFile->u.streamSnd.filename.info.raw.name), j.GetAllocator()); sound_file.AddMember("packed", raw, j.GetAllocator()); } } } entry.AddMember("soundfile", sound_file, j.GetAllocator()); } entry.AddMember("flags", snd_head->flags, j.GetAllocator()); entry.AddMember("sequence", snd_head->sequence, j.GetAllocator()); entry.AddMember("volMin", snd_head->volMin, j.GetAllocator()); entry.AddMember("volMax", snd_head->volMax, j.GetAllocator()); entry.AddMember("volModIndex", snd_head->volModIndex, j.GetAllocator()); entry.AddMember("pitchMin", snd_head->pitchMin, j.GetAllocator()); entry.AddMember("pitchMax", snd_head->pitchMax, j.GetAllocator()); entry.AddMember("distMin", snd_head->distMin, j.GetAllocator()); entry.AddMember("distMax", snd_head->distMax, j.GetAllocator()); entry.AddMember("velocityMin", snd_head->velocityMin, j.GetAllocator()); entry.AddMember("masterPriority", snd_head->masterPriority, j.GetAllocator()); entry.AddMember("masterPercentage", snd_head->masterPercentage, j.GetAllocator()); entry.AddMember("slavePercentage", snd_head->slavePercentage, j.GetAllocator()); entry.AddMember("probability", snd_head->probability, j.GetAllocator()); entry.AddMember("startDelay", snd_head->startDelay, j.GetAllocator()); if (snd_head->sndContext) { entry.AddMember("sndContext", rapidjson::StringRef(snd_head->sndContext->name), j.GetAllocator()); } else { entry.AddMember("sndContext", rapidjson::Value{rapidjson::kNullType}, j.GetAllocator()); } if (snd_head->sndCurve) { entry.AddMember("sndCurve", rapidjson::StringRef(snd_head->sndCurve->name), j.GetAllocator()); } else { entry.AddMember("sndCurve", rapidjson::Value{rapidjson::kNullType}, j.GetAllocator()); } if (snd_head->lpfCurve) { entry.AddMember("lpfCurve", rapidjson::StringRef(snd_head->lpfCurve->name), j.GetAllocator()); } else { entry.AddMember("lpfCurve", rapidjson::Value{rapidjson::kNullType}, j.GetAllocator()); } if (snd_head->hpfCurve) { entry.AddMember("hpfCurve", rapidjson::StringRef(snd_head->hpfCurve->name), j.GetAllocator()); } else { entry.AddMember("hpfCurve", rapidjson::Value{rapidjson::kNullType}, j.GetAllocator()); } if (snd_head->reverbSendCurve) { entry.AddMember("reverbSendCurve", rapidjson::StringRef(snd_head->reverbSendCurve->name), j.GetAllocator()); } else { entry.AddMember("reverbSendCurve", rapidjson::Value{rapidjson::kNullType}, j.GetAllocator()); } if (snd_head->speakerMap) { rapidjson::Value speaker_map{rapidjson::kObjectType}; rapidjson::Value channel_maps{rapidjson::kArrayType}; for (char x = 0; x < 2; x++) { for (char y = 0; y < 2; y++) { rapidjson::Value channel_map{rapidjson::kObjectType}; channel_map.AddMember("speakerCount", snd_head->speakerMap->channelMaps[x][y].speakerCount, j.GetAllocator()); rapidjson::Value speakers{rapidjson::kArrayType}; for (int speaker = 0; speaker < snd_head->speakerMap->channelMaps[x][y].speakerCount; speaker++) { rapidjson::Value jspeaker{rapidjson::kObjectType}; jspeaker.AddMember("speaker", snd_head->speakerMap->channelMaps[x][y].speakers[speaker].speaker, j.GetAllocator()); jspeaker.AddMember("numLevels", snd_head->speakerMap->channelMaps[x][y].speakers[speaker].numLevels, j.GetAllocator()); jspeaker.AddMember("levels0", snd_head->speakerMap->channelMaps[x][y].speakers[speaker].levels[0], j.GetAllocator()); jspeaker.AddMember("levels1", snd_head->speakerMap->channelMaps[x][y].speakers[speaker].levels[1], j.GetAllocator()); speakers.PushBack(jspeaker, j.GetAllocator()); } channel_map.AddMember("speakers", speakers, j.GetAllocator()); channel_maps.PushBack(channel_map, j.GetAllocator()); } } speaker_map.AddMember("name", rapidjson::StringRef(snd_head->speakerMap->name), j.GetAllocator()); speaker_map.AddMember("isDefault", snd_head->speakerMap->isDefault, j.GetAllocator()); speaker_map.AddMember("channelMaps", channel_maps, j.GetAllocator()); entry.AddMember("speakerMap", speaker_map, j.GetAllocator()); } else { j.AddMember("speakerMap", rapidjson::Value{rapidjson::kNullType}, j.GetAllocator()); } /*entry.AddMember("allowDoppler", snd_head->allowDoppler, j.GetAllocator()); if (snd_head->dopplerPreset) { entry.AddMember("dopplerPreset", rapidjson::StringRef(snd_head->dopplerPreset->name), j.GetAllocator()); } else { entry.AddMember("dopplerPreset", rapidjson::Value{rapidjson::kNullType}, j.GetAllocator()); }*/ rapidjson::Value unknown{rapidjson::kObjectType}; rapidjson::Value pad{rapidjson::kArrayType}; const auto pad0 = std::vector(snd_head->__pad0, snd_head->__pad0 + sizeof(snd_head->__pad0)); const auto pad1 = std::vector(snd_head->__pad1, snd_head->__pad1 + sizeof(snd_head->__pad1)); const auto pad2 = std::vector(snd_head->__pad2, snd_head->__pad2 + sizeof(snd_head->__pad2)); const auto pad3 = std::vector(snd_head->__pad3, snd_head->__pad3 + sizeof(snd_head->__pad3)); auto rpad0 = rapidjson_bytes_to_object(pad0, j); auto rpad1 = rapidjson_bytes_to_object(pad1, j); auto rpad2 = rapidjson_bytes_to_object(pad2, j); auto rpad3 = rapidjson_bytes_to_object(pad3, j); pad.PushBack(rpad0, j.GetAllocator()); pad.PushBack(rpad1, j.GetAllocator()); pad.PushBack(rpad2, j.GetAllocator()); pad.PushBack(rpad3, j.GetAllocator()); unknown.AddMember("pad", pad, j.GetAllocator()); unknown.AddMember("u4", snd_head->u4, j.GetAllocator()); unknown.AddMember("u5", snd_head->u5, j.GetAllocator()); unknown.AddMember("u18", snd_head->u18, j.GetAllocator()); unknown.AddMember("u20", snd_head->u20, j.GetAllocator()); unknown.AddMember("u34", snd_head->u34, j.GetAllocator()); entry.AddMember("unknown", unknown, j.GetAllocator()); head.PushBack(entry, j.GetAllocator()); } j.AddMember("head", head, j.GetAllocator()); rapidjson::Value unknown_array{rapidjson::kArrayType}; for (unsigned char i = 0; i < asset->unkCount; i++) { unknown_array.PushBack(asset->unk[i], j.GetAllocator()); } j.AddMember("unknownArray", unknown_array, j.GetAllocator()); std::string path = "dumps/sounds/"s + asset->aliasName; rapidjson::StringBuffer buffer{}; rapidjson::PrettyWriter> writer(buffer); writer.SetIndent(' ', 4); j.Accept(writer); utils::io::write_file(path, std::string{buffer.GetString(), buffer.GetLength()}, false); } game::XAssetHeader find_sound(const char* name) { bool found = false; const auto res = loaded_sounds.access([&](loaded_sound_map& map) { const auto i = map.find(name); if (i != map.end()) { found = true; return static_cast(i->second); } return static_cast(nullptr); }); if (found) { return res; } std::string path{}; if (!filesystem::find_file("sounds/"s + name, &path))// { return static_cast(nullptr); } const auto data = utils::io::read_file(path); rapidjson::Document j; j.Parse(data.data()); try { console::info("[Sound] Loading sound %s\n", name); const auto sound = parse_sound_alias_list(j); loaded_sounds.access([&](loaded_sound_map& map) { map[name] = sound; }); return static_cast(sound); } catch (const std::exception& e) { console::error("[Sound] Error loading sound %s: %s\n", name, e.what()); return static_cast(nullptr); } } void clear() { sound_allocator.clear(); loaded_sounds.access([](loaded_sound_map& map) { map.clear(); }); } class component final : public component_interface { public: void post_unpack() override { db_is_xasset_default_hook.create(0x1404143C0, db_is_xasset_default_stub); db_xasset_exists_hook.create(0x140417FD0, db_xasset_exists_stub); scr_table_lookup_hook.create(0x1404EFD40, scr_table_lookup_stub); // remove raw/sound or raw/language/sound prefix when loading raw sounds utils::hook::call(0x140622FEF, com_sprintf_raw_sound_localized_stub); utils::hook::call(0x14062306C, com_sprintf_raw_sound_stub); // fix playing non-existing music crashing snd_is_music_playing_hook.create(0x1407C58A0, snd_is_music_playing_stub); } }; } REGISTER_COMPONENT(sound::component)