2022-04-15 12:54:49 -04:00
|
|
|
#include <std_include.hpp>
|
|
|
|
#include "loader/component_loader.hpp"
|
|
|
|
|
2022-07-31 17:59:01 -04:00
|
|
|
#include "game/dvars.hpp"
|
|
|
|
#include "game/game.hpp"
|
|
|
|
|
2022-07-22 13:51:26 -04:00
|
|
|
#include "filesystem.hpp"
|
|
|
|
#include "console.hpp"
|
|
|
|
#include "command.hpp"
|
2022-04-15 12:54:49 -04:00
|
|
|
|
2022-05-02 19:00:08 -04:00
|
|
|
#include <utils/io.hpp>
|
2022-04-15 12:54:49 -04:00
|
|
|
#include <utils/hook.hpp>
|
2022-07-22 13:51:26 -04:00
|
|
|
#include <utils/string.hpp>
|
2022-07-31 17:59:01 -04:00
|
|
|
#include <utils/flags.hpp>
|
2022-04-15 12:54:49 -04:00
|
|
|
|
|
|
|
namespace database
|
|
|
|
{
|
|
|
|
namespace
|
|
|
|
{
|
2022-07-22 13:51:26 -04:00
|
|
|
struct bnet_file_handle_t
|
|
|
|
{
|
|
|
|
std::unique_ptr<std::ifstream> stream;
|
|
|
|
uint64_t offset{};
|
2022-08-21 22:38:53 -04:00
|
|
|
std::string path;
|
2022-07-22 13:51:26 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
std::unordered_map<game::DB_IFileSysFile*, bnet_file_handle_t> bnet_file_handles{};
|
|
|
|
utils::hook::detour bnet_fs_open_file_hook;
|
|
|
|
utils::hook::detour bnet_fs_read_hook;
|
|
|
|
utils::hook::detour bnet_fs_tell_hook;
|
|
|
|
utils::hook::detour bnet_fs_size_hook;
|
|
|
|
utils::hook::detour bnet_fs_close_hook;
|
|
|
|
utils::hook::detour bnet_fs_exists_hook;
|
|
|
|
utils::hook::detour bink_io_read_hook;
|
|
|
|
utils::hook::detour bink_io_seek_hook;
|
|
|
|
|
|
|
|
utils::hook::detour open_sound_handle_hook;
|
|
|
|
|
|
|
|
utils::memory::allocator handle_allocator;
|
|
|
|
|
|
|
|
using sound_file_t = std::unordered_map<uint64_t, std::string>;
|
|
|
|
std::unordered_map<unsigned short, sound_file_t> sound_files = {};
|
|
|
|
std::unordered_map<std::string, uint64_t> sound_sizes = {};
|
|
|
|
|
2022-04-15 12:54:49 -04:00
|
|
|
game::dvar_t* db_filesysImpl = nullptr;
|
2022-05-02 19:41:05 -04:00
|
|
|
utils::hook::detour db_fs_initialize_hook;
|
2022-04-15 12:54:49 -04:00
|
|
|
|
2022-08-21 19:42:13 -04:00
|
|
|
std::unordered_map<std::string, std::string> file_search_folders =
|
|
|
|
{
|
|
|
|
{".flac", "sound/"},
|
|
|
|
{".bik", "video/"},
|
|
|
|
};
|
|
|
|
|
2022-05-02 19:41:05 -04:00
|
|
|
game::DB_FileSysInterface* db_fs_initialize_stub()
|
2022-04-15 12:54:49 -04:00
|
|
|
{
|
|
|
|
switch (db_filesysImpl->current.integer)
|
|
|
|
{
|
|
|
|
case 0:
|
|
|
|
return reinterpret_cast<game::DB_FileSysInterface*>(0x140BE82F8); // ptr to vtable of BnetTACTVFSManager (implements DB_FileSysInterface)
|
|
|
|
case 1:
|
|
|
|
return reinterpret_cast<game::DB_FileSysInterface*>(0x140BEFDC0); // ptr to vtable of DiskFS (implements DB_FileSysInterface)
|
|
|
|
default:
|
|
|
|
return nullptr; // this should not happen
|
|
|
|
}
|
|
|
|
}
|
2022-07-22 13:51:26 -04:00
|
|
|
|
2022-08-21 21:04:53 -04:00
|
|
|
// kinda sucks but whatever
|
|
|
|
std::optional<std::string> find_fastfile(const std::string& name)
|
2022-07-22 13:51:26 -04:00
|
|
|
{
|
2022-08-21 21:04:53 -04:00
|
|
|
std::string name_ = name;
|
|
|
|
|
|
|
|
if (game::DB_IsLocalized(name.data()))
|
2022-07-22 13:51:26 -04:00
|
|
|
{
|
2022-08-21 21:04:53 -04:00
|
|
|
const auto language = game::SEH_GetCurrentLanguageName();
|
|
|
|
if (filesystem::exists(name))
|
|
|
|
{
|
|
|
|
return {name};
|
|
|
|
}
|
|
|
|
|
|
|
|
name_ = language + "/"s + name;
|
|
|
|
if (filesystem::exists(name_))
|
|
|
|
{
|
|
|
|
return {name_};
|
|
|
|
}
|
|
|
|
|
|
|
|
name_ = "zone/" + name;
|
|
|
|
if (filesystem::exists(name_))
|
|
|
|
{
|
|
|
|
return {name_};
|
|
|
|
}
|
|
|
|
|
|
|
|
name_ = "zone/"s + language + "/" + name;
|
|
|
|
if (filesystem::exists(name_))
|
|
|
|
{
|
|
|
|
return {name_};
|
|
|
|
}
|
2022-07-22 13:51:26 -04:00
|
|
|
}
|
2022-08-21 21:04:53 -04:00
|
|
|
else
|
|
|
|
{
|
|
|
|
if (filesystem::exists(name))
|
|
|
|
{
|
|
|
|
return {name};
|
|
|
|
}
|
2022-07-22 13:51:26 -04:00
|
|
|
|
2022-08-21 21:04:53 -04:00
|
|
|
name_ = "zone/" + name;
|
|
|
|
if (filesystem::exists(name_))
|
|
|
|
{
|
|
|
|
return {name_};
|
|
|
|
}
|
|
|
|
}
|
2022-07-22 13:51:26 -04:00
|
|
|
|
2022-08-21 21:04:53 -04:00
|
|
|
return {};
|
2022-07-22 13:51:26 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
game::DB_IFileSysFile* bnet_fs_open_file_stub(game::DB_FileSysInterface* this_, int folder, const char* file)
|
|
|
|
{
|
|
|
|
std::string name = file;
|
|
|
|
|
2022-08-21 19:42:13 -04:00
|
|
|
const auto search_path = [&](const std::string& ext, const std::string& path)
|
2022-07-22 13:51:26 -04:00
|
|
|
{
|
2022-08-21 19:42:13 -04:00
|
|
|
if (name.ends_with(ext) && !filesystem::exists(name))
|
|
|
|
{
|
|
|
|
name = path + name;
|
|
|
|
}
|
|
|
|
};
|
2022-07-22 13:51:26 -04:00
|
|
|
|
2022-08-21 21:04:53 -04:00
|
|
|
if (name.ends_with(".ff"))
|
2022-07-22 13:51:26 -04:00
|
|
|
{
|
2022-08-21 21:04:53 -04:00
|
|
|
const auto found = find_fastfile(name);
|
|
|
|
if (found.has_value())
|
|
|
|
{
|
|
|
|
name = found.value();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
for (const auto& [ext, path] : file_search_folders)
|
|
|
|
{
|
|
|
|
search_path(ext, path);
|
|
|
|
}
|
2022-07-22 13:51:26 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
std::string path{};
|
|
|
|
if (!filesystem::find_file(name, &path))
|
|
|
|
{
|
|
|
|
return bnet_fs_open_file_hook.invoke<game::DB_IFileSysFile*>(this_, folder, file);
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto handle = handle_allocator.allocate<game::DB_IFileSysFile>();
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
#ifdef DEBUG
|
|
|
|
console::info("[Database] Opening file %s\n", path.data());
|
|
|
|
#endif
|
|
|
|
|
|
|
|
auto stream = std::make_unique<std::ifstream>();
|
|
|
|
stream->open(path, std::ios::binary);
|
|
|
|
|
|
|
|
bnet_file_handle_t bnet_handle{};
|
|
|
|
bnet_handle.stream = std::move(stream);
|
2022-08-21 22:38:53 -04:00
|
|
|
bnet_handle.path = path;
|
2022-07-22 13:51:26 -04:00
|
|
|
bnet_file_handles[handle] = std::move(bnet_handle);
|
|
|
|
return handle;
|
|
|
|
}
|
|
|
|
catch (const std::exception& e)
|
|
|
|
{
|
|
|
|
console::error("[Database] Error opening file %s: %s\n", path.data(), e.what());
|
|
|
|
}
|
|
|
|
|
|
|
|
return handle;
|
|
|
|
}
|
|
|
|
|
|
|
|
game::FileSysResult bnet_fs_read_stub(game::DB_FileSysInterface* this_, game::DB_IFileSysFile* handle,
|
|
|
|
unsigned __int64 offset, unsigned __int64 size, void* dest)
|
|
|
|
{
|
|
|
|
if (bnet_file_handles.find(handle) == bnet_file_handles.end())
|
|
|
|
{
|
|
|
|
return bnet_fs_read_hook.invoke<game::FileSysResult>(this_, handle, offset, size, dest);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto& handle_ = bnet_file_handles[handle];
|
|
|
|
if (!handle_.stream->is_open())
|
|
|
|
{
|
|
|
|
return game::FILESYSRESULT_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
const auto start_pos = offset - handle_.offset;
|
|
|
|
handle_.stream->seekg(0, std::ios::end);
|
|
|
|
const auto end_pos = static_cast<uint64_t>(handle_.stream->tellg());
|
|
|
|
handle_.stream->seekg(start_pos);
|
|
|
|
|
|
|
|
const auto len = end_pos - start_pos;
|
|
|
|
const auto bytes_to_read = len <= size ? len : size;
|
|
|
|
|
|
|
|
const auto& res = handle_.stream->read(reinterpret_cast<char*>(dest), bytes_to_read);
|
|
|
|
if (res.bad())
|
|
|
|
{
|
|
|
|
return game::FILESYSRESULT_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto bytes_read = static_cast<uint64_t>(res.gcount());
|
|
|
|
handle->bytes_read += bytes_read;
|
|
|
|
handle->last_read = bytes_read;
|
|
|
|
|
|
|
|
return game::FILESYSRESULT_SUCCESS;
|
|
|
|
}
|
|
|
|
catch (const std::exception& e)
|
|
|
|
{
|
|
|
|
console::error("[Database] bnet_fs_read_stub: %s\n", e.what());
|
|
|
|
return game::FILESYSRESULT_ERROR;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
game::FileSysResult bnet_fs_tell_stub(game::DB_FileSysInterface* this_, game::DB_IFileSysFile* handle, uint64_t* bytes_read)
|
|
|
|
{
|
|
|
|
if (bnet_file_handles.find(handle) == bnet_file_handles.end())
|
|
|
|
{
|
|
|
|
return bnet_fs_tell_hook.invoke<game::FileSysResult>(this_, handle, bytes_read);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto& handle_ = bnet_file_handles[handle];
|
|
|
|
if (!handle_.stream->is_open())
|
|
|
|
{
|
|
|
|
return game::FILESYSRESULT_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
*bytes_read = handle->last_read;
|
|
|
|
return game::FILESYSRESULT_SUCCESS;
|
|
|
|
}
|
|
|
|
catch (const std::exception& e)
|
|
|
|
{
|
|
|
|
console::error("[Database] bnet_fs_tell_stub: %s\n", e.what());
|
|
|
|
return game::FILESYSRESULT_ERROR;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
uint64_t bnet_fs_size_stub(game::DB_FileSysInterface* this_, game::DB_IFileSysFile* handle)
|
|
|
|
{
|
|
|
|
if (bnet_file_handles.find(handle) == bnet_file_handles.end())
|
|
|
|
{
|
|
|
|
return bnet_fs_size_hook.invoke<uint64_t>(this_, handle);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto& handle_ = bnet_file_handles[handle];
|
|
|
|
try
|
|
|
|
{
|
|
|
|
handle_.stream->seekg(0, std::ios::end);
|
|
|
|
const std::streamsize size = handle_.stream->tellg();
|
|
|
|
handle_.stream->seekg(0, std::ios::beg);
|
|
|
|
return static_cast<uint64_t>(size);
|
|
|
|
}
|
|
|
|
catch (const std::exception& e)
|
|
|
|
{
|
|
|
|
console::error("[Database] bnet_fs_size_stub: %s\n", e.what());
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void bnet_fs_close_stub(game::DB_FileSysInterface* this_, game::DB_IFileSysFile* handle)
|
|
|
|
{
|
|
|
|
if (bnet_file_handles.find(handle) == bnet_file_handles.end())
|
|
|
|
{
|
|
|
|
bnet_fs_close_hook.invoke<uint64_t>(this_, handle);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
handle_allocator.free(handle);
|
|
|
|
bnet_file_handles.erase(handle);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool bnet_fs_exists_stub(game::DB_FileSysInterface* this_, game::DB_IFileSysFile* handle, const char* filename)
|
|
|
|
{
|
2022-08-21 19:42:13 -04:00
|
|
|
std::string name = filename;
|
|
|
|
const auto search_path = [&](const std::string& ext, const std::string& path)
|
|
|
|
{
|
|
|
|
if (!name.ends_with(ext))
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return filesystem::exists(name) || filesystem::exists(path + name);
|
|
|
|
};
|
|
|
|
|
|
|
|
if (filesystem::exists(filename))
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-08-21 21:04:53 -04:00
|
|
|
if (name.ends_with(".ff"))
|
2022-08-21 19:42:13 -04:00
|
|
|
{
|
2022-08-21 21:04:53 -04:00
|
|
|
const auto found = find_fastfile(name);
|
|
|
|
if (found.has_value())
|
2022-08-21 19:42:13 -04:00
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
2022-08-21 21:04:53 -04:00
|
|
|
else
|
|
|
|
{
|
|
|
|
for (const auto& [ext, path] : file_search_folders)
|
|
|
|
{
|
|
|
|
if (search_path(ext, path))
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-08-21 19:42:13 -04:00
|
|
|
|
|
|
|
return bnet_fs_exists_hook.invoke<bool>(this_, handle, filename);
|
2022-07-22 13:51:26 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
uint64_t bink_io_read_stub(game::DB_IFileSysFile** handle, void* dest, uint64_t bytes)
|
|
|
|
{
|
|
|
|
const auto handle_ptr = *handle;
|
|
|
|
if (bnet_file_handles.find(handle_ptr) == bnet_file_handles.end())
|
|
|
|
{
|
|
|
|
return bink_io_read_hook.invoke<uint64_t>(handle, dest, bytes);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto& handle_ = bnet_file_handles[handle_ptr];
|
|
|
|
if (!handle_.stream->is_open())
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
const auto& res = handle_.stream->read(reinterpret_cast<char*>(dest), bytes);
|
|
|
|
return static_cast<uint64_t>(res.gcount());
|
|
|
|
}
|
|
|
|
catch (const std::exception& e)
|
|
|
|
{
|
|
|
|
console::error("[Database] bink_io_read_stub: %s\n", e.what());
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool bink_io_seek_stub(game::DB_IFileSysFile** handle, uint64_t pos)
|
|
|
|
{
|
|
|
|
const auto handle_ptr = *handle;
|
|
|
|
if (bnet_file_handles.find(handle_ptr) == bnet_file_handles.end())
|
|
|
|
{
|
|
|
|
return bink_io_seek_hook.invoke<bool>(handle, pos);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto& handle_ = bnet_file_handles[handle_ptr];
|
|
|
|
if (!handle_.stream->is_open())
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
const auto& res = handle_.stream->seekg(pos);
|
|
|
|
return !(res.fail() || res.bad());
|
|
|
|
}
|
|
|
|
catch (const std::exception& e)
|
|
|
|
{
|
|
|
|
console::error("[Database] bink_io_seek_stub: %s\n", e.what());
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-04-15 12:54:49 -04:00
|
|
|
}
|
|
|
|
|
2022-08-21 22:38:53 -04:00
|
|
|
void close_fastfile_handles()
|
|
|
|
{
|
|
|
|
for (const auto& handle : bnet_file_handles)
|
|
|
|
{
|
|
|
|
if (handle.second.path.ends_with(".ff"))
|
|
|
|
{
|
|
|
|
handle.second.stream->close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-15 12:54:49 -04:00
|
|
|
class component final : public component_interface
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
void post_unpack() override
|
|
|
|
{
|
2022-05-02 19:41:05 -04:00
|
|
|
static const char* values[] =
|
|
|
|
{
|
2022-04-15 12:54:49 -04:00
|
|
|
"BnetTACTVFSManager", // (load files from CASC)
|
|
|
|
"DiskFS", // (load files from disk)
|
|
|
|
nullptr
|
|
|
|
};
|
|
|
|
|
2022-05-02 19:41:05 -04:00
|
|
|
const auto default_value = static_cast<int>(!utils::io::directory_exists("Data/data")
|
|
|
|
|| !utils::io::directory_exists("Data/config")
|
|
|
|
|| !utils::io::directory_exists("Data/indices"));
|
|
|
|
|
2022-06-17 14:00:39 -04:00
|
|
|
db_filesysImpl = dvars::register_enum("db_filesysImpl", values, default_value, game::DVAR_FLAG_READ, "Filesystem implementation");
|
2022-04-15 12:54:49 -04:00
|
|
|
|
|
|
|
if (default_value == 1)
|
|
|
|
{
|
|
|
|
utils::hook::nop(0x1405A4868, 22); // TACT related stuff that's pointless if we're using DiskFS
|
2022-05-02 19:00:08 -04:00
|
|
|
utils::hook::nop(0x14071AF83, 45); // Skip setting Bink file OS callbacks (not necessary since we're loading from disk)
|
2022-04-15 12:54:49 -04:00
|
|
|
}
|
|
|
|
|
2022-05-02 19:41:05 -04:00
|
|
|
db_fs_initialize_hook.create(game::DB_FSInitialize, db_fs_initialize_stub);
|
2022-07-22 13:51:26 -04:00
|
|
|
|
|
|
|
// Allow bnet filesystem to also load files from disk
|
|
|
|
if (db_filesysImpl->current.integer == 0)
|
|
|
|
{
|
|
|
|
const auto bnet_interface = reinterpret_cast<game::DB_FileSysInterface*>(0x140BE82F8);
|
|
|
|
|
|
|
|
bnet_fs_open_file_hook.create(bnet_interface->vftbl->OpenFile, bnet_fs_open_file_stub);
|
|
|
|
bnet_fs_read_hook.create(bnet_interface->vftbl->Read, bnet_fs_read_stub);
|
|
|
|
bnet_fs_tell_hook.create(bnet_interface->vftbl->Tell, bnet_fs_tell_stub);
|
|
|
|
bnet_fs_size_hook.create(bnet_interface->vftbl->Size, bnet_fs_size_stub);
|
|
|
|
bnet_fs_close_hook.create(bnet_interface->vftbl->Close, bnet_fs_close_stub);
|
|
|
|
bnet_fs_exists_hook.create(bnet_interface->vftbl->Exists, bnet_fs_exists_stub);
|
|
|
|
|
|
|
|
bink_io_read_hook.create(0x1407191B0, bink_io_read_stub);
|
|
|
|
bink_io_seek_hook.create(0x140719200, bink_io_seek_stub);
|
|
|
|
}
|
2022-04-15 12:54:49 -04:00
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
REGISTER_COMPONENT(database::component)
|