Dump SoundBank asset data files

This commit is contained in:
Jan 2021-04-05 18:50:42 +02:00
parent 05303313be
commit 3cda71d1e7
10 changed files with 670 additions and 2 deletions

View File

@ -5755,6 +5755,14 @@ namespace T6
MaterialArgumentDef u;
};
enum SndAliasType
{
SAT_UNKNOWN = 0x0,
SAT_LOADED = 0x1,
SAT_STREAMED = 0x2,
SAT_PRIMED = 0x3,
SAT_COUNT = 0x4,
};
struct SndAlias
{
@ -5764,7 +5772,7 @@ namespace T6
const char* secondaryname;
unsigned int assetId;
const char* assetFileName;
unsigned int flags0;
unsigned int flags0; // Bits 15/16 are SndAliasType
unsigned int flags1;
unsigned int duck;
unsigned int contextType;

View File

@ -1,5 +1,7 @@
#include "ObjLoaderT6.h"
#include <sstream>
#include "Game/T6/GameT6.h"
#include "Game/T6/GameAssetPoolT6.h"
#include "ObjContainer/IPak/IPak.h"
@ -92,6 +94,115 @@ namespace T6
return zone->m_game == &g_GameT6;
}
bool ObjLoader::VerifySoundBankChecksum(const SoundBank* soundBank, const SndRuntimeAssetBank& sndRuntimeAssetBank)
{
SndAssetBankChecksum checksum{};
static_assert(sizeof(SndAssetBankChecksum::checksumBytes) == sizeof(SndRuntimeAssetBank::linkTimeChecksum));
for (auto i = 0u; i < sizeof(SndAssetBankChecksum::checksumBytes); i++)
checksum.checksumBytes[i] = sndRuntimeAssetBank.linkTimeChecksum[i];
return soundBank->VerifyChecksum(checksum);
}
SoundBank* ObjLoader::LoadSoundBankForZone(ISearchPath* searchPath, const std::string& soundBankFileName, Zone* zone)
{
if (ObjLoading::Configuration.Verbose)
std::cout << "Trying to load sound bank '" << soundBankFileName << "' for zone '" << zone->m_name << "'" << std::endl;
auto* existingSoundBank = SoundBank::Repository.GetContainerByName(soundBankFileName);
if (existingSoundBank != nullptr)
{
if (ObjLoading::Configuration.Verbose)
std::cout << "Referencing loaded sound bank '" << soundBankFileName << "'." << std::endl;
SoundBank::Repository.AddContainerReference(existingSoundBank, zone);
return existingSoundBank;
}
auto file = searchPath->Open(soundBankFileName);
if (file.IsOpen())
{
auto sndBank = std::make_unique<SoundBank>(soundBankFileName, std::move(file.m_stream), file.m_length);
auto* sndBankPtr = sndBank.get();
if (!sndBank->Initialize())
{
std::cout << "Failed to load sound bank '" << soundBankFileName << "'" << std::endl;
return nullptr;
}
SoundBank::Repository.AddContainer(std::move(sndBank), zone);
if (ObjLoading::Configuration.Verbose)
std::cout << "Found and loaded sound bank '" << soundBankFileName << "'" << std::endl;
return sndBankPtr;
}
std::cout << "Failed to load sound bank '" << soundBankFileName << "'" << std::endl;
return nullptr;
}
void ObjLoader::LoadSoundBankFromLinkedInfo(ISearchPath* searchPath, const std::string& soundBankFileName, const SndRuntimeAssetBank* sndBankLinkedInfo, Zone* zone, std::set<std::string>& loadedBanksForZone, std::stack<std::string>& dependenciesToLoad)
{
if (loadedBanksForZone.find(soundBankFileName) == loadedBanksForZone.end())
{
auto* soundBank = LoadSoundBankForZone(searchPath, soundBankFileName, zone);
if (soundBank)
{
if (!VerifySoundBankChecksum(soundBank, *sndBankLinkedInfo))
{
std::cout << "Checksum of sound bank does not match link time checksum for '" << soundBankFileName << "'" << std::endl;
}
loadedBanksForZone.emplace(soundBankFileName);
for (const auto& dependency : soundBank->GetDependencies())
{
dependenciesToLoad.emplace(dependency);
}
}
}
}
void ObjLoader::LoadSoundBanksFromAsset(ISearchPath* searchPath, const SndBank* sndBank, Zone* zone, std::set<std::string>& loadedBanksForZone)
{
std::stack<std::string> dependenciesToLoad;
if (sndBank->streamAssetBank.zone)
{
const auto soundBankFileName = SoundBank::GetFileNameForDefinition(true, sndBank->streamAssetBank.zone, sndBank->streamAssetBank.language);
LoadSoundBankFromLinkedInfo(searchPath, soundBankFileName, &sndBank->streamAssetBank, zone, loadedBanksForZone, dependenciesToLoad);
}
if (sndBank->runtimeAssetLoad && sndBank->loadAssetBank.zone)
{
const auto soundBankFileName = SoundBank::GetFileNameForDefinition(false, sndBank->loadAssetBank.zone, sndBank->loadAssetBank.language);
LoadSoundBankFromLinkedInfo(searchPath, soundBankFileName, &sndBank->loadAssetBank, zone, loadedBanksForZone, dependenciesToLoad);
}
while(!dependenciesToLoad.empty())
{
auto dependencyFileName = dependenciesToLoad.top();
dependenciesToLoad.pop();
if (loadedBanksForZone.find(dependencyFileName) == loadedBanksForZone.end())
{
auto* soundBank = LoadSoundBankForZone(searchPath, dependencyFileName, zone);
if (soundBank)
{
loadedBanksForZone.emplace(dependencyFileName);
for (const auto& dependency : soundBank->GetDependencies())
{
dependenciesToLoad.emplace(dependency);
}
}
}
}
}
void ObjLoader::LoadIPakForZone(ISearchPath* searchPath, const std::string& ipakName, Zone* zone)
{
if (ObjLoading::Configuration.Verbose)
@ -199,6 +310,16 @@ namespace T6
}
}
}
if (assetPoolT6->m_sound_bank != nullptr)
{
std::set<std::string> loadedSoundBanksForZone;
for (auto* sndBankAssetInfo : *assetPoolT6->m_sound_bank)
{
LoadSoundBanksFromAsset(searchPath, sndBankAssetInfo->Asset(), zone, loadedSoundBanksForZone);
}
}
}
void ObjLoader::UnloadContainersOfZone(Zone* zone) const

View File

@ -2,11 +2,15 @@
#include <unordered_map>
#include <memory>
#include <set>
#include <string>
#include <stack>
#include "IObjLoader.h"
#include "AssetLoading/IAssetLoader.h"
#include "SearchPath/ISearchPath.h"
#include "Game/T6/T6.h"
#include "ObjContainer/SoundBank/SoundBank.h"
namespace T6
{
@ -17,6 +21,12 @@ namespace T6
std::unordered_map<asset_type_t, std::unique_ptr<IAssetLoader>> m_asset_loaders_by_type;
static bool VerifySoundBankChecksum(const SoundBank* soundBank, const SndRuntimeAssetBank& sndRuntimeAssetBank);
static SoundBank* LoadSoundBankForZone(ISearchPath* searchPath, const std::string& soundBankFileName, Zone* zone);
static void LoadSoundBankFromLinkedInfo(ISearchPath* searchPath, const std::string& soundBankFileName, const SndRuntimeAssetBank* sndBankLinkedInfo, Zone* zone,
std::set<std::string>& loadedBanksForZone, std::stack<std::string>& dependenciesToLoad);
static void LoadSoundBanksFromAsset(ISearchPath* searchPath, const SndBank* sndBank, Zone* zone, std::set<std::string>& loadedBanksForZone);
static void LoadIPakForZone(ISearchPath* searchPath, const std::string& ipakName, Zone* zone);
static void LoadImageFromIwi(GfxImage* image, ISearchPath* searchPath, Zone* zone);

View File

@ -0,0 +1,299 @@
#include "SoundBank.h"
#include <sstream>
#include <vector>
#include <memory>
#include "zlib.h"
#include "Utils/FileUtils.h"
ObjContainerRepository<SoundBank, Zone> SoundBank::Repository;
class SoundBankInputBuffer final : public objbuf
{
std::istream& m_stream;
int64_t m_base_offset;
size_t m_size;
size_t m_offset;
bool m_open;
protected:
std::streamsize showmanyc() override
{
return m_size - m_offset;
}
int_type underflow() override
{
if (m_offset >= m_size)
return EOF;
return m_stream.peek();
}
int_type uflow() override
{
if (m_offset >= m_size)
return EOF;
m_offset++;
return m_stream.get();
}
std::streamsize xsgetn(char* ptr, std::streamsize count) override
{
if (m_offset + count > m_size)
count = m_size - m_offset;
if (count > 0)
{
m_stream.read(ptr, count);
const auto readSize = m_stream.gcount();
m_offset += readSize;
return readSize;
}
return 0;
}
pos_type seekoff(const off_type off, const std::ios_base::seekdir dir, const std::ios_base::openmode mode) override
{
if(dir == std::ios_base::beg)
{
return seekpos(off, mode);
}
if(dir == std::ios_base::end)
{
if (off > m_size)
return pos_type(-1);
return seekpos(m_size - off, mode);
}
return seekpos(m_offset + off, mode);
}
pos_type seekpos(const pos_type pos, std::ios_base::openmode mode) override
{
if (pos < 0 || pos >= m_size)
return pos_type(-1);
m_stream.seekg(m_base_offset + pos);
m_offset = static_cast<size_t>(pos);
return pos;
}
public:
SoundBankInputBuffer(std::istream& stream, const int64_t baseOffset, const size_t size)
: m_stream(stream),
m_base_offset(baseOffset),
m_size(size),
m_offset(0),
m_open(true)
{
}
_NODISCARD bool is_open() const override
{
return m_open;
}
bool close() override
{
const auto result = m_open;
m_open = false;
return result;
}
};
bool SoundBank::ReadHeader()
{
m_stream->read(reinterpret_cast<char*>(&m_header), sizeof(m_header));
if (m_stream->gcount() != sizeof(m_header))
{
printf("Unexpected eof when trying to load sndbank header.\n");
return false;
}
if (m_header.magic != MAGIC)
{
std::cout << "Invalid sndbank magic 0x" << std::hex << m_header.magic << std::endl;
return false;
}
if (m_header.version != VERSION)
{
std::cout << "Unsupported sndbank version " << m_header.version << " (should be " << VERSION << ")" << std::endl;
return false;
}
if (m_header.entrySize != sizeof(SndAssetBankEntry))
{
std::cout << "Invalid sndbank entry size 0x" << std::hex << m_header.entrySize << " (should be 0x" << std::hex << sizeof(SndAssetBankEntry) << ")" << std::endl;
return false;
}
if (m_header.fileSize != m_file_size)
{
std::cout << "Invalid sndbank " << m_file_size << " (header expects " << m_header.fileSize << ")" << std::endl;
return false;
}
if (m_header.entryCount
&& (m_header.entryOffset <= 0 || m_header.entryOffset + sizeof(SndAssetBankEntry) * m_header.entryCount > m_file_size))
{
std::cout << "Invalid sndbank entry offset " << m_header.entryOffset << " (filesize is " << m_file_size << ")" << std::endl;
return false;
}
if (m_header.checksumOffset <= 0 || m_header.checksumOffset + sizeof(SndAssetBankChecksum) * m_header.entryCount > m_file_size)
{
std::cout << "Invalid sndbank checksum offset " << m_header.checksumOffset << " (filesize is " << m_file_size << ")" << std::endl;
return false;
}
if (m_header.dependencyCount * m_header.dependencySize > sizeof(SndAssetBankHeader::dependencies))
{
std::cout << "Invalid sndbank dependency sizes (count is " << m_header.dependencyCount << "; size is " << m_header.dependencySize << ")" << std::endl;
return false;
}
for (auto i = 0u; i < m_header.dependencyCount; i++)
{
const auto dependencyLen = strnlen(&m_header.dependencies[i * m_header.dependencySize], m_header.dependencySize);
std::string dependencyName(&m_header.dependencies[i * m_header.dependencySize], dependencyLen);
if (dependencyName.empty())
continue;
m_dependencies.emplace_back(std::move(dependencyName));
}
return true;
}
bool SoundBank::ReadEntries()
{
m_stream->seekg(m_header.entryOffset);
for (auto i = 0u; i < m_header.entryCount; i++)
{
SndAssetBankEntry entry{};
m_stream->read(reinterpret_cast<char*>(&entry), sizeof(entry));
if (m_stream->gcount() != sizeof(entry))
{
std::cout << "Failed to read sound bank entry at index " << i << std::endl;
return false;
}
if (entry.offset == 0 || entry.offset + entry.size >= m_file_size)
{
std::cout << "Invalid sound bank entry data offset " << entry.offset << " (filesize is " << m_header.fileSize << ")" << std::endl;
return false;
}
m_entries.push_back(entry);
m_entries_by_id.emplace(std::make_pair(entry.id, i));
}
return true;
}
bool SoundBank::ReadChecksums()
{
m_stream->seekg(m_header.entryOffset);
for (auto i = 0u; i < m_header.entryCount; i++)
{
SndAssetBankChecksum checksum{};
m_stream->read(reinterpret_cast<char*>(&checksum), sizeof(checksum));
if (m_stream->gcount() != sizeof(checksum))
{
std::cout << "Failed to read sound bank checksum at index " << i << std::endl;
return false;
}
m_checksums.push_back(checksum);
}
return true;
}
std::string SoundBank::GetFileNameForDefinition(const bool streamed, const char* zone, const char* language)
{
std::ostringstream str;
assert(zone != nullptr);
if (zone)
str << zone;
if (language)
str << "." << language;
if (streamed)
str << ".sabs";
else
str << ".sabl";
return str.str();
}
SoundBank::SoundBank(std::string fileName, std::unique_ptr<std::istream> stream, const int64_t fileSize)
: m_file_name(std::move(fileName)),
m_stream(std::move(stream)),
m_file_size(fileSize),
m_initialized(false),
m_header{}
{
}
std::string SoundBank::GetName()
{
return m_file_name;
}
bool SoundBank::Initialize()
{
if (m_initialized)
return true;
if (!ReadHeader()
|| !ReadEntries()
|| !ReadChecksums())
return false;
m_initialized = true;
return true;
}
const std::vector<std::string>& SoundBank::GetDependencies() const
{
return m_dependencies;
}
bool SoundBank::VerifyChecksum(const SndAssetBankChecksum& checksum) const
{
return m_initialized && memcmp(checksum.checksumBytes, m_header.checksumChecksum.checksumBytes, sizeof(SndAssetBankChecksum)) == 0;
}
SearchPathOpenFile SoundBank::GetEntryStream(const unsigned id) const
{
const auto foundEntry = m_entries_by_id.find(id);
if (foundEntry != m_entries_by_id.end())
{
const auto& entry = m_entries[foundEntry->second];
m_stream->seekg(entry.offset);
return SearchPathOpenFile(std::make_unique<iobjstream>(std::make_unique<SoundBankInputBuffer>(*m_stream, entry.offset, entry.size)), entry.size);
}
return SearchPathOpenFile();
}

View File

@ -0,0 +1,52 @@
#pragma once
#include <istream>
#include "Utils/ClassUtils.h"
#include "ObjContainer/ObjContainerReferenceable.h"
#include "ObjContainer/ObjContainerRepository.h"
#include "ObjContainer/SoundBank/SoundBankTypes.h"
#include "SearchPath/ISearchPath.h"
#include "Utils/FileUtils.h"
#include "Utils/ObjStream.h"
#include "Zone/Zone.h"
class SoundBank final : public ObjContainerReferenceable
{
static constexpr uint32_t MAGIC = FileUtils::MakeMagic32('2', 'U', 'X', '#');
static constexpr uint32_t VERSION = 14u;
std::string m_file_name;
std::unique_ptr<std::istream> m_stream;
int64_t m_file_size;
bool m_initialized;
SndAssetBankHeader m_header;
std::vector<std::string> m_dependencies;
std::vector<SndAssetBankEntry> m_entries;
std::vector<SndAssetBankChecksum> m_checksums;
std::unordered_map<unsigned int, size_t> m_entries_by_id;
bool ReadHeader();
bool ReadEntries();
bool ReadChecksums();
public:
static ObjContainerRepository<SoundBank, Zone> Repository;
static std::string GetFileNameForDefinition(bool streamed, const char* zone, const char* language);
SoundBank(std::string fileName, std::unique_ptr<std::istream> stream, int64_t fileSize);
SoundBank(const SoundBank& other) = delete;
SoundBank(SoundBank&& other) noexcept = default;
SoundBank& operator=(const SoundBank& other) = delete;
SoundBank& operator=(SoundBank&& other) noexcept = default;
std::string GetName() override;
bool Initialize();
_NODISCARD const std::vector<std::string>& GetDependencies() const;
_NODISCARD bool VerifyChecksum(const SndAssetBankChecksum& checksum) const;
_NODISCARD SearchPathOpenFile GetEntryStream(unsigned int id) const;
};

View File

@ -0,0 +1,45 @@
#pragma once
#include <cstdint>
class SoundBankConsts
{
SoundBankConsts() = default;
public:
static constexpr unsigned OFFSET_DATA_START = 0x800;
};
struct SndAssetBankChecksum
{
char checksumBytes[16];
};
struct SndAssetBankHeader
{
unsigned int magic; // + 0x0
unsigned int version; // + 0x4
unsigned int entrySize; // + 0x8
unsigned int checksumSize; // + 0xC
unsigned int dependencySize; // + 0x10
unsigned int entryCount; // + 0x14
unsigned int dependencyCount; // + 0x18
unsigned int pad32; // + 0x1C
int64_t fileSize; // + 0x20
int64_t entryOffset; // + 0x28
int64_t checksumOffset; // + 0x30
SndAssetBankChecksum checksumChecksum; // + 0x38
char dependencies[512]; // + 0x48
};
struct SndAssetBankEntry
{
unsigned int id;
unsigned int size;
unsigned int offset;
unsigned int frameCount;
char frameRateIndex;
char channelCount;
char looping;
char format;
};

View File

@ -3,6 +3,7 @@ ObjWriting = {}
function ObjWriting:include(includes)
if includes:handle(self:name()) then
ObjCommon:include(includes)
ObjLoading:include(includes)
ZoneCommon:include(includes)
includedirs {
path.join(ProjectFolder(), "ObjWriting")
@ -14,6 +15,7 @@ function ObjWriting:link(links)
links:add(self:name())
links:linkto(Utils)
links:linkto(ObjCommon)
links:linkto(ObjLoading)
links:linkto(ZoneCommon)
links:linkto(minilzo)
links:linkto(minizip)

View File

@ -0,0 +1,111 @@
#include "AssetDumperSndBank.h"
#include <filesystem>
#include "Csv/CsvStream.h"
#include "ObjContainer/SoundBank/SoundBank.h"
using namespace T6;
namespace fs = std::filesystem;
std::unique_ptr<std::ostream> AssetDumperSndBank::OpenOutputFile(AssetDumpingContext& context, const std::string& outputFileName) const
{
fs::path assetPath(context.m_base_path);
fs::path assetName(outputFileName);
auto firstPart = true;
for(const auto& part : assetName)
{
if(firstPart)
{
firstPart = false;
if(part.string() == "raw"
|| part.string() == "devraw")
continue;
}
assetPath.append(part.string());
}
auto assetDir(assetPath);
assetDir.remove_filename();
create_directories(assetDir);
auto outputStream = std::make_unique<std::ofstream>(assetPath, std::ios_base::out | std::ios_base::binary);
if(outputStream->is_open())
{
return std::move(outputStream);
}
return nullptr;
}
void AssetDumperSndBank::DumpSndBank(AssetDumpingContext& context, const XAssetInfo<SndBank>* sndBankInfo)
{
const auto* sndBank = sndBankInfo->Asset();
// auto* streamAssetBank = SoundBank::Repository.GetContainerByName(SoundBank::GetFileNameForDefinition(true, sndBank->streamAssetBank.zone, sndBank->streamAssetBank.language));
// SoundBank* loadedAssetBank = nullptr;
// if (sndBank->runtimeAssetLoad && sndBank->loadAssetBank.zone)
// loadedAssetBank = SoundBank::Repository.GetContainerByName(SoundBank::GetFileNameForDefinition(false, sndBank->loadAssetBank.zone, sndBank->loadAssetBank.language));
std::map<unsigned int, std::string> aliasMappings;
for (auto i = 0u; i < sndBank->aliasCount; i++)
{
const auto& aliasList = sndBank->alias[i];
for (auto j = 0; j < aliasList.count; j++)
{
const auto& alias = aliasList.head[j];
if (alias.assetId && alias.assetFileName)
aliasMappings[alias.assetId] = alias.assetFileName;
}
}
for (const auto& [id, filename] : aliasMappings)
{
auto foundEntry = false;
for (const auto* soundBank : SoundBank::Repository)
{
auto soundFile = soundBank->GetEntryStream(id);
if (soundFile.IsOpen())
{
auto outFile = OpenOutputFile(context, filename);
if (!outFile)
{
std::cout << "Failed to open sound outputfile: \"" << filename << "\"" << std::endl;
break;
}
char buffer[2048];
while (!soundFile.m_stream->eof())
{
soundFile.m_stream->read(buffer, sizeof(buffer));
const auto readSize = soundFile.m_stream->gcount();
outFile->write(buffer, readSize);
}
foundEntry = true;
break;
}
}
if(!foundEntry)
{
std::cout << "Could not find data for sound \"" << filename << "\"" << std::endl;
}
}
}
void AssetDumperSndBank::DumpPool(AssetDumpingContext& context, AssetPool<SndBank>* pool)
{
for(const auto* assetInfo : *pool)
{
if (!assetInfo->m_name.empty() && assetInfo->m_name[0] == ',')
continue;
DumpSndBank(context, assetInfo);
}
}

View File

@ -0,0 +1,19 @@
#pragma once
#include <memory>
#include <ostream>
#include "Dumping/AbstractAssetDumper.h"
#include "Game/T6/T6.h"
namespace T6
{
class AssetDumperSndBank final : public IAssetDumper<SndBank>
{
std::unique_ptr<std::ostream> OpenOutputFile(AssetDumpingContext& context, const std::string& outputFileName) const;
void DumpSndBank(AssetDumpingContext& context, const XAssetInfo<SndBank>* sndBankInfo);
public:
void DumpPool(AssetDumpingContext& context, AssetPool<SndBank>* pool) override;
};
}

View File

@ -13,6 +13,7 @@
#include "AssetDumpers/AssetDumperFontIcon.h"
#include "AssetDumpers/AssetDumperPhysConstraints.h"
#include "AssetDumpers/AssetDumperPhysPreset.h"
#include "AssetDumpers/AssetDumperSndBank.h"
#include "AssetDumpers/AssetDumperTracer.h"
#include "AssetDumpers/AssetDumperVehicle.h"
#include "AssetDumpers/AssetDumperWeapon.h"
@ -46,7 +47,7 @@ bool ZoneDumper::DumpZone(AssetDumpingContext& context) const
// DUMP_ASSET_POOL(AssetDumperMaterial, m_material);
// DUMP_ASSET_POOL(AssetDumperTechniqueSet, m_technique_set);
DUMP_ASSET_POOL(AssetDumperGfxImage, m_image);
// DUMP_ASSET_POOL(AssetDumperSndBank, m_sound_bank);
DUMP_ASSET_POOL(AssetDumperSndBank, m_sound_bank);
// DUMP_ASSET_POOL(AssetDumperSndPatch, m_sound_patch);
// DUMP_ASSET_POOL(AssetDumperClipMap, m_clip_map);
// DUMP_ASSET_POOL(AssetDumperComWorld, m_com_world);