Remove old unused stuff
This commit is contained in:
parent
d3af34572d
commit
60f4a6e98f
@ -7,7 +7,6 @@
|
||||
#include "filesystem.hpp"
|
||||
#include "console.hpp"
|
||||
#include "command.hpp"
|
||||
#include "sound.hpp"
|
||||
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/hook.hpp>
|
||||
|
@ -5,7 +5,6 @@
|
||||
#include "command.hpp"
|
||||
#include "console.hpp"
|
||||
#include "localized_strings.hpp"
|
||||
#include "sound.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/concurrency.hpp>
|
||||
@ -32,15 +31,6 @@ namespace fastfiles
|
||||
|
||||
game::XAssetHeader db_find_xasset_header_stub(game::XAssetType type, const char* name, int allow_create_default)
|
||||
{
|
||||
if (type == game::ASSET_TYPE_SOUND)
|
||||
{
|
||||
const auto res = sound::find_sound(name);
|
||||
if (res.sound != nullptr)
|
||||
{
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
const auto start = game::Sys_Milliseconds();
|
||||
const auto result = db_find_xasset_header.invoke<game::XAssetHeader>(type, name, allow_create_default);
|
||||
const auto diff = game::Sys_Milliseconds() - start;
|
||||
|
@ -4,19 +4,14 @@
|
||||
#include "game/dvars.hpp"
|
||||
|
||||
#include "fastfiles.hpp"
|
||||
#include "filesystem.hpp"
|
||||
#include "console.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include "mapents.hpp"
|
||||
#include "command.hpp"
|
||||
#include "game/scripting/functions.hpp"
|
||||
|
||||
#include <xsk/gsc/types.hpp>
|
||||
#include <xsk/resolver.hpp>
|
||||
#include <xsk/utils/compression.hpp>
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/concurrency.hpp>
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/io.hpp>
|
||||
|
||||
@ -24,296 +19,6 @@ namespace mapents
|
||||
{
|
||||
namespace
|
||||
{
|
||||
game::dvar_t* addon_mapname = nullptr;
|
||||
utils::memory::allocator allocator;
|
||||
|
||||
std::unordered_map<unsigned int, game::scriptType_e> custom_fields;
|
||||
|
||||
unsigned int token_id_start = 0x16000;
|
||||
|
||||
// zonetool/iw4/addonmapents.cpp
|
||||
class asset_reader
|
||||
{
|
||||
public:
|
||||
asset_reader(char* data)
|
||||
: data_(data)
|
||||
{
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T read()
|
||||
{
|
||||
const auto value = *reinterpret_cast<T*>(this->data_);
|
||||
this->data_ += sizeof(T);
|
||||
return value;
|
||||
}
|
||||
|
||||
std::uint32_t read_int()
|
||||
{
|
||||
const auto type = this->read<char>();
|
||||
if (type != 0)
|
||||
{
|
||||
printf("asset_reader: invalid type %i for int\n", type);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return this->read<int>();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* read_array()
|
||||
{
|
||||
const auto type = this->read<char>();
|
||||
if (type != 3)
|
||||
{
|
||||
printf("asset_reader: invalid type %i for array\n", type);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto size = this->read<int>();
|
||||
if (size <= 0)
|
||||
{
|
||||
printf("asset_reader: array size <= 0\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto array_ = allocator.allocate_array<T>(size);
|
||||
const auto total_size = sizeof(T) * size;
|
||||
std::memcpy(array_, this->data_, total_size);
|
||||
this->data_ += total_size;
|
||||
|
||||
return array_;
|
||||
}
|
||||
|
||||
private:
|
||||
char* data_ = nullptr;
|
||||
};
|
||||
|
||||
std::string parse_mapents(const std::string& source)
|
||||
{
|
||||
std::string out_buffer{};
|
||||
const auto lines = utils::string::split(source, '\n');
|
||||
|
||||
bool in_map_ent = false;
|
||||
bool empty = false;
|
||||
int line_index{};
|
||||
for (const auto& line : lines)
|
||||
{
|
||||
const auto _0 = gsl::finally([&]()
|
||||
{
|
||||
line_index++;
|
||||
});
|
||||
|
||||
if (line[0] == '{' && !in_map_ent)
|
||||
{
|
||||
in_map_ent = true;
|
||||
out_buffer.append("{\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line[0] == '{' && in_map_ent)
|
||||
{
|
||||
throw std::runtime_error(utils::string::va("[addon_map_ents parser] '{' on line %i", line_index));
|
||||
}
|
||||
|
||||
if (line[0] == '}' && in_map_ent)
|
||||
{
|
||||
if (empty)
|
||||
{
|
||||
out_buffer.append("\n}\n");
|
||||
}
|
||||
else if (line_index < static_cast<int>(lines.size()) - 1)
|
||||
{
|
||||
out_buffer.append("}\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
out_buffer.append("}\0");
|
||||
}
|
||||
|
||||
in_map_ent = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line[0] == '}' && !in_map_ent)
|
||||
{
|
||||
throw std::runtime_error(utils::string::va("[addon_map_ents parser] Unexpected '}' on line %i", line_index));
|
||||
}
|
||||
|
||||
std::regex expr(R"~((.+) "(.*)")~");
|
||||
std::smatch match{};
|
||||
if (!std::regex_search(line, match, expr))
|
||||
{
|
||||
console::warn("[addon_map_ents parser] Failed to parse line %i", line_index);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto key = utils::string::to_lower(match[1].str());
|
||||
const auto value = match[2].str();
|
||||
|
||||
if (key.size() <= 0 || value.size() <= 0)
|
||||
{
|
||||
console::warn("[addon_map_ents parser] Invalid key/value ('%s' '%s') pair on line %i", key.data(), value.data(), line_index);
|
||||
continue;
|
||||
}
|
||||
|
||||
empty = false;
|
||||
|
||||
auto key_id = std::atoi(key.data());
|
||||
if (key_id != 0)
|
||||
{
|
||||
out_buffer.append(utils::string::va("%i \"%s\"\n", key_id, value.data()));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key.size() < 3 || (!key.starts_with("\"") || !key.ends_with("\"")))
|
||||
{
|
||||
console::warn("[addon_map_ents parser] Bad key '%s' on line %i", key.data(), line_index);
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto key_ = key.substr(1, key.size() - 2);
|
||||
const auto id = xsk::gsc::h2::resolver::token_id(key_);
|
||||
if (id == 0)
|
||||
{
|
||||
console::warn("[addon_map_ents parser] Key '%s' not found, on line %i", key_.data(), line_index);
|
||||
continue;
|
||||
}
|
||||
|
||||
out_buffer.append(utils::string::va("%i \"%s\"\n", id, value.data()));
|
||||
}
|
||||
|
||||
return out_buffer;
|
||||
}
|
||||
|
||||
std::optional<std::string> get_mapents_data(std::string* real_path = nullptr)
|
||||
{
|
||||
std::string data{};
|
||||
if (addon_mapname->current.string != ""s &&
|
||||
filesystem::read_file("addon_map_ents/"s + addon_mapname->current.string + ".mapents"s, &data, real_path))
|
||||
{
|
||||
return {data};
|
||||
}
|
||||
|
||||
static const auto mapname = game::Dvar_FindVar("mapname");
|
||||
|
||||
if (filesystem::read_file("addon_map_ents/"s + mapname->current.string + ".mapents"s, &data, real_path))
|
||||
{
|
||||
return {data};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
bool should_load_addon_mapents()
|
||||
{
|
||||
return get_mapents_data().has_value();
|
||||
}
|
||||
|
||||
bool should_load_addon_mapents_stub(const char* a1, void* a2)
|
||||
{
|
||||
if (should_load_addon_mapents())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return utils::hook::invoke<bool>(0x140609570, a1, a2); // Com_IsAddonMap
|
||||
}
|
||||
|
||||
void try_parse_mapents(const std::string& path, const std::string& data, game::AddonMapEnts* mapents)
|
||||
{
|
||||
const auto parsed = parse_mapents(data);
|
||||
utils::io::write_file("parsed_mapents.txt", parsed, false);
|
||||
|
||||
mapents->entityString = allocator.duplicate_string(parsed.data());
|
||||
mapents->numEntityChars = static_cast<int>(parsed.size()) + 1;
|
||||
|
||||
std::string triggers{};
|
||||
if (utils::io::read_file(path + ".triggers", &triggers))
|
||||
{
|
||||
asset_reader reader(triggers.data());
|
||||
|
||||
mapents->trigger.modelCount = reader.read_int();
|
||||
mapents->trigger.models = reader.read_array<game::TriggerModel>();
|
||||
|
||||
mapents->trigger.hullCount = reader.read_int();
|
||||
mapents->trigger.hulls = reader.read_array<game::TriggerHull>();
|
||||
|
||||
mapents->trigger.slabCount = reader.read_int();
|
||||
mapents->trigger.slabs = reader.read_array<game::TriggerSlab>();
|
||||
}
|
||||
}
|
||||
|
||||
game::XAssetHeader db_find_xasset_header_stub(game::XAssetType type, const char* name, int allow_create_default)
|
||||
{
|
||||
if (!should_load_addon_mapents())
|
||||
{
|
||||
return game::DB_FindXAssetHeader(type, name, allow_create_default);
|
||||
}
|
||||
|
||||
const auto _0 = gsl::finally(&mapents::clear_dvars);
|
||||
|
||||
const auto mapents = allocator.allocate<game::AddonMapEnts>();
|
||||
mapents->name = allocator.duplicate_string(name);
|
||||
mapents->entityString = allocator.duplicate_string("{\n}");
|
||||
mapents->numEntityChars = 4;
|
||||
|
||||
std::string real_path{};
|
||||
const auto data = get_mapents_data(&real_path);
|
||||
if (!data.has_value())
|
||||
{
|
||||
return static_cast<game::XAssetHeader>(mapents);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
try_parse_mapents(real_path, data.value(), mapents);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
console::error("Failed to parse addon_map_ents file %s: %s\n", name, e.what());
|
||||
}
|
||||
|
||||
return static_cast<game::XAssetHeader>(mapents);
|
||||
}
|
||||
|
||||
void cm_trigger_model_bounds_stub(void* a1, void* a2)
|
||||
{
|
||||
__try
|
||||
{
|
||||
utils::hook::invoke<void>(0x14058BEB0, a1, a2);
|
||||
}
|
||||
__except (EXCEPTION_EXECUTE_HANDLER)
|
||||
{
|
||||
game::Com_Error(game::ERR_DROP, "CM_TriggerModelBounds: you are probably missing a mapents.triggers file");
|
||||
}
|
||||
}
|
||||
|
||||
void add_field(const std::string& name, game::scriptType_e type)
|
||||
{
|
||||
const auto id = token_id_start++;
|
||||
custom_fields[id] = type;
|
||||
xsk::gsc::h2::resolver::add_token(name, static_cast<std::uint16_t>(id));
|
||||
}
|
||||
|
||||
void add_field(const std::string& name, game::scriptType_e type, unsigned int id)
|
||||
{
|
||||
custom_fields[id] = type;
|
||||
xsk::gsc::h2::resolver::add_token(name, static_cast<std::uint16_t>(id));
|
||||
}
|
||||
|
||||
utils::hook::detour scr_find_field_hook;
|
||||
unsigned int scr_find_field_stub(unsigned int name, game::scriptType_e* type)
|
||||
{
|
||||
if (custom_fields.find(name) != custom_fields.end())
|
||||
{
|
||||
*type = custom_fields[name];
|
||||
return name;
|
||||
}
|
||||
|
||||
return scr_find_field_hook.invoke<unsigned int>(name, type);
|
||||
}
|
||||
|
||||
std::string replace_mapents_keys(const std::string& data)
|
||||
{
|
||||
std::string buffer{};
|
||||
@ -350,28 +55,11 @@ namespace mapents
|
||||
}
|
||||
}
|
||||
|
||||
void clear_dvars()
|
||||
{
|
||||
game::Dvar_SetString(addon_mapname, "");
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
allocator.clear();
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
scr_find_field_hook.create(0x1405C5240, scr_find_field_stub);
|
||||
|
||||
scheduler::once([]()
|
||||
{
|
||||
addon_mapname = dvars::register_string("addon_mapname", "", 0, "");
|
||||
}, scheduler::pipeline::main);
|
||||
|
||||
command::add("dumpMapEnts", []()
|
||||
{
|
||||
if (!game::SV_Loaded())
|
||||
@ -395,12 +83,6 @@ namespace mapents
|
||||
console::info("Mapents dumped to %s\n", dest);
|
||||
}, true);
|
||||
});
|
||||
|
||||
utils::hook::call(0x14058BDD3, db_find_xasset_header_stub);
|
||||
utils::hook::call(0x14058BD6B, should_load_addon_mapents_stub);
|
||||
utils::hook::call(0x1406B3384, cm_trigger_model_bounds_stub);
|
||||
|
||||
add_field("script_specialops", game::SCRIPT_INTEGER, 0xEFFF);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,7 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace mapents
|
||||
{
|
||||
void clear();
|
||||
void clear_dvars();
|
||||
}
|
@ -1,269 +0,0 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "materials.hpp"
|
||||
#include "console.hpp"
|
||||
#include "filesystem.hpp"
|
||||
#include "command.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include "game/dvars.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/memory.hpp>
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/image.hpp>
|
||||
#include <utils/concurrency.hpp>
|
||||
|
||||
namespace materials
|
||||
{
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour db_material_streaming_fail_hook;
|
||||
utils::hook::detour material_register_handle_hook;
|
||||
utils::hook::detour db_get_material_index_hook;
|
||||
|
||||
struct material_data_t
|
||||
{
|
||||
std::unordered_map<std::string, game::Material*> materials;
|
||||
std::unordered_map<std::string, std::string> images;
|
||||
};
|
||||
|
||||
char constant_table[0x20] = {};
|
||||
|
||||
utils::concurrency::container<material_data_t> material_data;
|
||||
|
||||
game::GfxImage* setup_image(game::GfxImage* image, const utils::image& raw_image)
|
||||
{
|
||||
image->imageFormat = 0x1000003;
|
||||
image->resourceSize = -1;
|
||||
|
||||
D3D11_SUBRESOURCE_DATA data{};
|
||||
data.SysMemPitch = raw_image.get_width() * 4;
|
||||
data.SysMemSlicePitch = data.SysMemPitch * raw_image.get_height();
|
||||
data.pSysMem = raw_image.get_buffer();
|
||||
|
||||
game::Image_Setup(image, raw_image.get_width(), raw_image.get_height(), image->depth, image->numElements,
|
||||
image->imageFormat, DXGI_FORMAT_R8G8B8A8_UNORM, 0, image->name, &data);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
game::Material* create_material(const std::string& name, const utils::image& raw_image)
|
||||
{
|
||||
const auto white = *reinterpret_cast<game::Material**>(0x141B09208);
|
||||
|
||||
const auto material = utils::memory::get_allocator()->allocate<game::Material>();
|
||||
const auto texture_table = utils::memory::get_allocator()->allocate<game::MaterialTextureDef>();
|
||||
const auto image = utils::memory::get_allocator()->allocate<game::GfxImage>();
|
||||
|
||||
std::memcpy(material, white, sizeof(game::Material));
|
||||
std::memcpy(texture_table, white->textureTable, sizeof(game::MaterialTextureDef));
|
||||
std::memcpy(image, white->textureTable->u.image, sizeof(game::GfxImage));
|
||||
|
||||
material->constantTable = &constant_table;
|
||||
material->name = utils::memory::get_allocator()->duplicate_string(name);
|
||||
image->name = material->name;
|
||||
|
||||
material->textureTable = texture_table;
|
||||
material->textureTable->u.image = setup_image(image, raw_image);
|
||||
|
||||
return material;
|
||||
}
|
||||
|
||||
void free_material(game::Material* material)
|
||||
{
|
||||
material->textureTable->u.image->textures.___u0.map->Release();
|
||||
material->textureTable->u.image->textures.shaderView->Release();
|
||||
utils::memory::get_allocator()->free(material->textureTable->u.image);
|
||||
utils::memory::get_allocator()->free(material->textureTable);
|
||||
utils::memory::get_allocator()->free(material->name);
|
||||
utils::memory::get_allocator()->free(material);
|
||||
}
|
||||
|
||||
game::Material* load_material(const std::string& name)
|
||||
{
|
||||
return material_data.access<game::Material*>([&](material_data_t& data_) -> game::Material*
|
||||
{
|
||||
if (const auto i = data_.materials.find(name); i != data_.materials.end())
|
||||
{
|
||||
return i->second;
|
||||
}
|
||||
|
||||
std::string data{};
|
||||
if (const auto i = data_.images.find(name); i != data_.images.end())
|
||||
{
|
||||
data = i->second;
|
||||
}
|
||||
|
||||
if (!data.empty())
|
||||
{
|
||||
const auto material = create_material(name, data);
|
||||
data_.materials[name] = material;
|
||||
return material;
|
||||
}
|
||||
|
||||
if (filesystem::read_file(utils::string::va("materials/%s.stbi_img", name.data()), &data))
|
||||
{
|
||||
const auto buffer = data.data();
|
||||
const auto width = *reinterpret_cast<int*>(buffer);
|
||||
const auto height = *reinterpret_cast<int*>(buffer + 4);
|
||||
const auto image_data = std::string(reinterpret_cast<char*>(buffer + 8), data.size() - 8);
|
||||
|
||||
const auto image = utils::image(image_data, width, height);
|
||||
|
||||
const auto material = create_material(name, image);
|
||||
data_.materials[name] = material;
|
||||
|
||||
return material;
|
||||
}
|
||||
|
||||
if (filesystem::read_file(utils::string::va("materials/%s.png", name.data()), &data))
|
||||
{
|
||||
const auto material = create_material(name, data);
|
||||
data_.materials[name] = material;
|
||||
return material;
|
||||
}
|
||||
|
||||
data_.materials[name] = nullptr;
|
||||
return nullptr;
|
||||
});
|
||||
}
|
||||
|
||||
game::Material* try_load_material(const std::string& name)
|
||||
{
|
||||
if (name == "white")
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return load_material(name);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
console::error("Failed to load material %s: %s\n", name.data(), e.what());
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
game::Material* material_register_handle_stub(const char* name)
|
||||
{
|
||||
auto result = try_load_material(name);
|
||||
if (result == nullptr)
|
||||
{
|
||||
result = material_register_handle_hook.invoke<game::Material*>(name);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int db_material_streaming_fail_stub(game::Material* material)
|
||||
{
|
||||
if (material->constantTable == &constant_table)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return db_material_streaming_fail_hook.invoke<int>(material);
|
||||
}
|
||||
|
||||
unsigned int db_get_material_index_stub(game::Material* material)
|
||||
{
|
||||
if (material->constantTable == &constant_table)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return db_get_material_index_hook.invoke<unsigned int>(material);
|
||||
}
|
||||
}
|
||||
|
||||
void add(const std::string& name, const std::string& data)
|
||||
{
|
||||
material_data.access([&](material_data_t& data_)
|
||||
{
|
||||
data_.images[name] = data;
|
||||
});
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
material_data.access([&](material_data_t& data_)
|
||||
{
|
||||
for (auto& material : data_.materials)
|
||||
{
|
||||
if (material.second == nullptr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
free_material(material.second);
|
||||
}
|
||||
|
||||
data_.materials.clear();
|
||||
});
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
material_register_handle_hook.create(game::Material_RegisterHandle.get(), material_register_handle_stub);
|
||||
db_material_streaming_fail_hook.create(0x14041D140, db_material_streaming_fail_stub);
|
||||
db_get_material_index_hook.create(0x140413BC0, db_get_material_index_stub);
|
||||
|
||||
command::add("preloadImage", [](const command::params& params)
|
||||
{
|
||||
if (params.size() < 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto image_name = params.join(1);
|
||||
if (!utils::io::file_exists(image_name))
|
||||
{
|
||||
console::error("Image file not found\n");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto data = utils::io::read_file(image_name);
|
||||
|
||||
try
|
||||
{
|
||||
const auto image = utils::image{ data };
|
||||
const auto last_of = image_name.find_last_of('.');
|
||||
const auto new_name = image_name.substr(0, last_of) + ".stbi_img";
|
||||
|
||||
auto width = image.get_width();
|
||||
auto height = image.get_height();
|
||||
|
||||
/*
|
||||
int width;
|
||||
int height;
|
||||
char* data;
|
||||
*/
|
||||
|
||||
std::string buffer{};
|
||||
buffer.append(reinterpret_cast<char*>(&width), 4);
|
||||
buffer.append(reinterpret_cast<char*>(&height), 4);
|
||||
buffer.append(image.get_data());
|
||||
|
||||
utils::io::write_file(new_name, buffer, false);
|
||||
|
||||
console::info("Image saved to %s\n", new_name.data());
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
console::error("Error processing image: %s\n", e.what());
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(materials::component)
|
@ -1,7 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace materials
|
||||
{
|
||||
void add(const std::string& name, const std::string& data);
|
||||
void clear();
|
||||
}
|
@ -7,13 +7,10 @@
|
||||
#include "console.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include "filesystem.hpp"
|
||||
#include "materials.hpp"
|
||||
#include "fonts.hpp"
|
||||
#include "mods.hpp"
|
||||
#include "mapents.hpp"
|
||||
#include "localized_strings.hpp"
|
||||
#include "loadscreen.hpp"
|
||||
#include "sound.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/io.hpp>
|
||||
@ -31,15 +28,11 @@ namespace mods
|
||||
{
|
||||
if (release_assets)
|
||||
{
|
||||
materials::clear();
|
||||
fonts::clear();
|
||||
mapents::clear_dvars();
|
||||
loadscreen::clear();
|
||||
}
|
||||
|
||||
mapents::clear();
|
||||
localized_strings::clear();
|
||||
sound::clear();
|
||||
|
||||
db_release_xassets_hook.invoke<void>();
|
||||
}
|
||||
|
@ -4,578 +4,15 @@
|
||||
#include "game/game.hpp"
|
||||
#include "game/dvars.hpp"
|
||||
|
||||
#include "sound.hpp"
|
||||
#include "filesystem.hpp"
|
||||
#include "console.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include "command.hpp"
|
||||
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/memory.hpp>
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/concurrency.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
// 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<size_t, game::snd_alias_list_t*>;
|
||||
utils::concurrency::container<loaded_sound_map, std::recursive_mutex> loaded_sounds;
|
||||
std::hash<std::string_view> hasher;
|
||||
|
||||
#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<char>(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<cast_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_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<int>(value.Size()); i++)
|
||||
{
|
||||
buffer += static_cast<char>(value[i].GetInt());
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
rapidjson::Value rapidjson_bytes_to_object(const std::vector<std::uint8_t>& 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<game::LoadedSound>();
|
||||
result->name = sound_allocator.duplicate_string(name);
|
||||
result->info.loadedSize = static_cast<int>(data.size());
|
||||
result->info.data = sound_allocator.allocate_array<char>(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<game::LoadedSound>();
|
||||
|
||||
unsigned int chunk_id_buffer{};
|
||||
unsigned int chunk_size{};
|
||||
|
||||
file.read(reinterpret_cast<char*>(&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<char*>(&chunk_size), 4);
|
||||
file.read(reinterpret_cast<char*>(&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<char*>(&chunk_id_buffer), 4);
|
||||
file.read(reinterpret_cast<char*>(&chunk_size), 4);
|
||||
|
||||
switch (chunk_id_buffer)
|
||||
{
|
||||
case 0x20746D66: // fmt
|
||||
if (chunk_size >= 16)
|
||||
{
|
||||
short format{};
|
||||
file.read(reinterpret_cast<char*>(&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<char*>(&num_channels), 2);
|
||||
result->info.channels = static_cast<char>(num_channels);
|
||||
|
||||
int sample_rate{};
|
||||
file.read(reinterpret_cast<char*>(&sample_rate), 4);
|
||||
result->info.sampleRate = sample_rate;
|
||||
|
||||
int byte_rate{};
|
||||
file.read(reinterpret_cast<char*>(&byte_rate), 4);
|
||||
|
||||
short block_align{};
|
||||
file.read(reinterpret_cast<char*>(&block_align), 2);
|
||||
result->info.blockAlign = static_cast<char>(block_align);
|
||||
|
||||
short bit_per_sample{};
|
||||
file.read(reinterpret_cast<char*>(&bit_per_sample), 2);
|
||||
result->info.numBits = static_cast<char>(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<char>(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<game::SoundFile>();
|
||||
asset->soundFile->type = static_cast<game::snd_alias_type_t>(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<game::SpeakerMap>();
|
||||
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<unsigned int>((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<unsigned int>(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<char>(JSON_GET_OPTIONAL(snd_unknown, "u18", Int, 0));
|
||||
asset->u20 = static_cast<char>(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<game::snd_alias_list_t>();
|
||||
|
||||
SOUND_STRING(aliasName, false);
|
||||
|
||||
asset->count = JSON_GET_CAST(j, "count", Int, char, "sound");
|
||||
asset->head = sound_allocator.allocate_array<game::snd_alias_t>(asset->count);
|
||||
|
||||
JSON_CHECK(j, "head", Array, "sound");
|
||||
|
||||
const auto head = j["head"].GetArray();
|
||||
for (auto i = 0; i < static_cast<int>(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<unsigned char>(unk.Size());
|
||||
asset->unk = sound_allocator.allocate_array<short>(asset->unkCount);
|
||||
|
||||
for (unsigned char i = 0; i < asset->unkCount && unk.Size(); i++)
|
||||
{
|
||||
asset->unk[i] = static_cast<short>(unk[i].GetInt());
|
||||
}
|
||||
}
|
||||
|
||||
return asset;
|
||||
}
|
||||
|
||||
bool sound_exists(const char* name)
|
||||
{
|
||||
return sound::find_sound(name).sound != nullptr;
|
||||
}
|
||||
|
||||
utils::hook::detour db_is_xasset_default_hook;
|
||||
int db_is_xasset_default_stub(game::XAssetType type, const char* name)
|
||||
{
|
||||
if (type != game::ASSET_TYPE_SOUND)
|
||||
{
|
||||
return db_is_xasset_default_hook.invoke<bool>(type, name);
|
||||
}
|
||||
|
||||
const auto res = db_is_xasset_default_hook.invoke<bool>(type, name);
|
||||
if (!res)
|
||||
{
|
||||
return res;
|
||||
}
|
||||
|
||||
return !sound_exists(name);
|
||||
}
|
||||
|
||||
utils::hook::detour db_xasset_exists_hook;
|
||||
int db_xasset_exists_stub(game::XAssetType type, const char* name)
|
||||
{
|
||||
const auto res = utils::hook::invoke<bool>(0x140417FD0, type, name);
|
||||
if (res)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return sound_exists(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<void>();
|
||||
}
|
||||
|
||||
std::optional<int> new_value{};
|
||||
loaded_sounds.access([&](loaded_sound_map& map)
|
||||
{
|
||||
const auto i = map.find(hasher(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>();
|
||||
}
|
||||
}
|
||||
|
||||
void com_sprintf_raw_sound_localized_stub(char* buffer, int size, const char* fmt,
|
||||
const char* lang, const char* name, const char* extension)
|
||||
{
|
||||
@ -598,344 +35,6 @@ namespace sound
|
||||
|
||||
return snd_is_music_playing_hook.invoke<bool>(a1);
|
||||
}
|
||||
|
||||
void load_sound(const std::string& name, const std::string& path)
|
||||
{
|
||||
try
|
||||
{
|
||||
const auto data = utils::io::read_file(path);
|
||||
|
||||
rapidjson::Document j;
|
||||
j.Parse(data.data());
|
||||
|
||||
console::info("[Sound] Loading sound %s\n", name.data());
|
||||
const auto sound = parse_sound_alias_list(j);
|
||||
|
||||
const auto h = hasher(name.data());
|
||||
loaded_sounds.access([&](loaded_sound_map& map)
|
||||
{
|
||||
map[h] = sound;
|
||||
});
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
console::error("[Sound] Error loading sound %s: %s\n", name.data(), e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void load_sounds()
|
||||
{
|
||||
const auto paths = filesystem::get_search_paths();
|
||||
for (const auto& path : paths)
|
||||
{
|
||||
const auto dir = path + "/sounds";
|
||||
if (!utils::io::directory_exists(dir))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto sound_files = utils::io::list_files(dir);
|
||||
for (const auto& file : sound_files)
|
||||
{
|
||||
const auto last = file.find_last_of("\\/");
|
||||
std::string name = file;
|
||||
if (last != std::string::npos)
|
||||
{
|
||||
name = file.substr(last + 1);
|
||||
}
|
||||
|
||||
load_sound(name, file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool dump_sound(game::snd_alias_list_t* asset)
|
||||
{
|
||||
if (asset == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
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<std::uint8_t>(snd_head->__pad0, snd_head->__pad0 + sizeof(snd_head->__pad0));
|
||||
const auto pad1 = std::vector<std::uint8_t>(snd_head->__pad1, snd_head->__pad1 + sizeof(snd_head->__pad1));
|
||||
const auto pad2 = std::vector<std::uint8_t>(snd_head->__pad2, snd_head->__pad2 + sizeof(snd_head->__pad2));
|
||||
const auto pad3 = std::vector<std::uint8_t>(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<rapidjson::StringBuffer, rapidjson::Document::EncodingType, rapidjson::ASCII<>>
|
||||
writer(buffer);
|
||||
writer.SetIndent(' ', 4);
|
||||
j.Accept(writer);
|
||||
|
||||
utils::io::write_file(path, std::string{buffer.GetString(), buffer.GetLength()}, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
game::XAssetHeader find_sound(const char* name)
|
||||
{
|
||||
const auto hash = hasher(name);
|
||||
return loaded_sounds.access<game::XAssetHeader>([&](loaded_sound_map& map)
|
||||
{
|
||||
const auto i = map.find(hash);
|
||||
if (i != map.end())
|
||||
{
|
||||
return static_cast<game::XAssetHeader>(i->second);
|
||||
}
|
||||
|
||||
return static_cast<game::XAssetHeader>(nullptr);
|
||||
});
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
sound_allocator.clear();
|
||||
loaded_sounds.access([](loaded_sound_map& map)
|
||||
{
|
||||
map.clear();
|
||||
});
|
||||
|
||||
load_sounds();
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
@ -943,44 +42,12 @@ namespace sound
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
db_is_xasset_default_hook.create(0x1404143C0, db_is_xasset_default_stub);
|
||||
utils::hook::call(0x140616DD1, 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);
|
||||
|
||||
scheduler::once(clear, scheduler::pipeline::main);
|
||||
|
||||
command::add("dumpSoundAlias", [](const command::params& params)
|
||||
{
|
||||
if (params.size() < 2)
|
||||
{
|
||||
console::info("Usage: dumpSoundAlias <name>\n");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto name = params.get(1);
|
||||
const auto sound = game::DB_FindXAssetHeader(game::ASSET_TYPE_SOUND, name, false).sound;
|
||||
if (sound == nullptr)
|
||||
{
|
||||
console::error("Sound %s does not exist\n", name);
|
||||
return;
|
||||
}
|
||||
|
||||
if (dump_sound(sound))
|
||||
{
|
||||
console::info("Sound dumped to dumps/sound/%s\n", name);
|
||||
}
|
||||
else
|
||||
{
|
||||
console::error("Failed to dump sound %s\n", name);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,9 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace sound
|
||||
{
|
||||
bool dump_sound(game::snd_alias_list_t* asset);
|
||||
game::XAssetHeader find_sound(const char* name);
|
||||
|
||||
void clear();
|
||||
}
|
Loading…
Reference in New Issue
Block a user