mirror of
https://github.com/izzy2lost/Torch.git
synced 2026-07-06 00:18:35 -07:00
Merge branch 'main' into audio_extraction
This commit is contained in:
@@ -24,5 +24,5 @@ jobs:
|
||||
- name: Publish packaged artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: torch-mac-x64
|
||||
path: torch-release
|
||||
name: torch-mac-bin
|
||||
path: torch-release
|
||||
|
||||
+44
-5
@@ -25,6 +25,7 @@
|
||||
#include "factories/Vec3sFactory.h"
|
||||
#include "factories/AssetArrayFactory.h"
|
||||
#include "factories/ViewportFactory.h"
|
||||
#include "factories/CompressedTextureFactory.h"
|
||||
|
||||
#include "factories/sm64/AnimationFactory.h"
|
||||
#include "factories/sm64/BehaviorScriptFactory.h"
|
||||
@@ -78,6 +79,8 @@
|
||||
#include "factories/naudio/v1/BookFactory.h"
|
||||
#include "factories/naudio/v1/SequenceFactory.h"
|
||||
|
||||
#include "preprocess/CompTool.h"
|
||||
|
||||
using namespace std::chrono;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
@@ -105,6 +108,7 @@ void Companion::Init(const ExportType type) {
|
||||
this->RegisterFactory("ARRAY", std::make_shared<GenericArrayFactory>());
|
||||
this->RegisterFactory("ASSET_ARRAY", std::make_shared<AssetArrayFactory>());
|
||||
this->RegisterFactory("VP", std::make_shared<ViewportFactory>());
|
||||
this->RegisterFactory("COMPRESSED_TEXTURE", std::make_shared<CompressedTextureFactory>());
|
||||
|
||||
// SM64 specific
|
||||
this->RegisterFactory("SM64:DIALOG", std::make_shared<SM64::DialogFactory>());
|
||||
@@ -559,13 +563,12 @@ void Companion::ProcessFile(YAML::Node root) {
|
||||
for(auto asset = root.begin(); asset != root.end(); ++asset){
|
||||
auto node = asset->second;
|
||||
auto entryName = asset->first.as<std::string>();
|
||||
|
||||
auto output = (this->gCurrentDirectory / entryName).string();
|
||||
std::replace(output.begin(), output.end(), '\\', '/');
|
||||
|
||||
if(node["type"]){
|
||||
const auto type = GetSafeNode<std::string>(node, "type");
|
||||
if(type == "SAMPLE"){
|
||||
if(type == "NAUDIO:V0:SAMPLE"){
|
||||
AudioManager::Instance->bind_sample(node, output);
|
||||
}
|
||||
}
|
||||
@@ -845,7 +848,7 @@ void Companion::ProcessFile(YAML::Node root) {
|
||||
if(gap < 0) {
|
||||
stream << "// WARNING: Overlap detected between 0x" << std::hex << startptr << " and 0x" << end << " with size 0x" << std::abs(gap) << "\n";
|
||||
SPDLOG_WARN("Overlap detected between 0x{:X} and 0x{:X} with size 0x{:X} on file {}", startptr, end, gap, this->gCurrentFile);
|
||||
} else if(gap < 0x10 && gap >= alignment && end % 0x10 == 0 && this->gEnablePadGen) {
|
||||
} else if(gap < 0x10 && gap >= alignment && end % alignment == 0 && this->gEnablePadGen) {
|
||||
SPDLOG_WARN("Gap detected between 0x{:X} and 0x{:X} with size 0x{:X} on file {}", startptr, end, gap, this->gCurrentFile);
|
||||
SPDLOG_WARN("Creating pad of 0x{:X} bytes", gap);
|
||||
const auto padfile = this->gCurrentDirectory.filename().string();
|
||||
@@ -863,7 +866,7 @@ void Companion::ProcessFile(YAML::Node root) {
|
||||
} else {
|
||||
stream << "\n";
|
||||
}
|
||||
} else if(gap > 0x10) {
|
||||
} else if(gap >= 0x10) {
|
||||
stream << "// WARNING: Gap detected between 0x" << std::hex << startptr << " and 0x" << end << " with size 0x" << gap << "\n";
|
||||
}
|
||||
}
|
||||
@@ -970,8 +973,44 @@ void Companion::Process() {
|
||||
}
|
||||
|
||||
auto rom = !isDirectoryMode ? config[this->gCartridge->GetHash()] : config;
|
||||
auto cfg = rom["config"];
|
||||
|
||||
if(rom["preprocess"]) {
|
||||
auto preprocess = rom["preprocess"];
|
||||
for(auto job = preprocess.begin(); job != preprocess.end(); job++) {
|
||||
auto name = job->first.as<std::string>();
|
||||
auto item = job->second;
|
||||
auto method = GetSafeNode<std::string>(item, "method");
|
||||
if (method == "mio0-comptool") {
|
||||
auto type = GetSafeNode<std::string>(item, "type");
|
||||
auto target = GetSafeNode<std::string>(item, "target");
|
||||
auto restart = GetSafeNode<bool>(item, "restart");
|
||||
|
||||
if (type == "decompress") {
|
||||
this->gRomData = CompTool::Decompress(this->gRomData);
|
||||
this->gCartridge = std::make_shared<N64::Cartridge>(this->gRomData);
|
||||
this->gCartridge->Initialize();
|
||||
|
||||
auto hash = this->gCartridge->GetHash();
|
||||
|
||||
SPDLOG_INFO("ROM decompressed to {}", hash);
|
||||
|
||||
if (hash != target) {
|
||||
throw std::runtime_error("Hash mismatch");
|
||||
}
|
||||
|
||||
if(restart){
|
||||
rom = config[this->gCartridge->GetHash()];
|
||||
}
|
||||
} else {
|
||||
throw std::runtime_error("Only decompression is supported");
|
||||
}
|
||||
} else {
|
||||
throw std::runtime_error("Invalid preprocess method");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto cfg = rom["config"];
|
||||
if(!cfg) {
|
||||
SPDLOG_ERROR("No config found for {}", !isDirectoryMode ? this->gCartridge->GetHash() : GetSafeNode<std::string>(config, "folder"));
|
||||
return;
|
||||
|
||||
@@ -56,10 +56,12 @@ std::optional<T> GetNode(YAML::Node& node, const std::string& key) {
|
||||
template<typename T>
|
||||
T GetSafeNode(YAML::Node& node, const std::string& key) {
|
||||
if(!node[key]) {
|
||||
auto dump = YAML::Dump(node);
|
||||
|
||||
if (node["symbol"]) {
|
||||
throw std::runtime_error("Yaml asset missing the '" + key + "' node for '" + node["symbol"].as<std::string>() + "'");
|
||||
throw std::runtime_error("Yaml asset missing the '" + key + "' node for '" + node["symbol"].as<std::string>() + "'\nProblematic YAML:\n" + dump);
|
||||
} else {
|
||||
throw std::runtime_error("Yaml asset missing the '" + key + "' node");
|
||||
throw std::runtime_error("Yaml asset missing the '" + key + "' node\nProblematic YAML:\n" + dump);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include "BaseFactory.h"
|
||||
#include "utils/Decompressor.h"
|
||||
#include "utils/TextureUtils.h"
|
||||
|
||||
class CompressedTextureData : public IParsedData {
|
||||
public:
|
||||
TextureFormat mFormat;
|
||||
uint32_t mWidth;
|
||||
uint32_t mHeight;
|
||||
std::vector<uint8_t> mBuffer;
|
||||
CompressionType mCompressionType;
|
||||
|
||||
CompressedTextureData(TextureFormat format, uint32_t width, uint32_t height, std::vector<uint8_t>& buffer, CompressionType compressionType) : mFormat(format), mWidth(width), mHeight(height), mBuffer(std::move(buffer)), mCompressionType(compressionType) {}
|
||||
};
|
||||
|
||||
class CompressedTextureHeaderExporter : public BaseExporter {
|
||||
ExportResult Export(std::ostream& write, std::shared_ptr<IParsedData> data, std::string& entryName, YAML::Node& node, std::string* replacement) override;
|
||||
};
|
||||
|
||||
class CompressedTextureCodeExporter : public BaseExporter {
|
||||
ExportResult Export(std::ostream& write, std::shared_ptr<IParsedData> data, std::string& entryName, YAML::Node& node, std::string* replacement) override;
|
||||
};
|
||||
|
||||
class CompressedTextureBinaryExporter : public BaseExporter {
|
||||
ExportResult Export(std::ostream& write, std::shared_ptr<IParsedData> data, std::string& entryName, YAML::Node& node, std::string* replacement) override;
|
||||
};
|
||||
|
||||
class CompressedTextureModdingExporter : public BaseExporter {
|
||||
ExportResult Export(std::ostream& write, std::shared_ptr<IParsedData> data, std::string& entryName, YAML::Node& node, std::string* replacement) override;
|
||||
};
|
||||
|
||||
class CompressedTextureFactory : public BaseFactory {
|
||||
public:
|
||||
std::optional<std::shared_ptr<IParsedData>> parse(std::vector<uint8_t>& buffer, YAML::Node& data) override;
|
||||
std::optional<std::shared_ptr<IParsedData>> parse_modding(std::vector<uint8_t>& buffer, YAML::Node& data) override;
|
||||
inline std::unordered_map<ExportType, std::shared_ptr<BaseExporter>> GetExporters() override {
|
||||
return {
|
||||
REGISTER(Header, CompressedTextureHeaderExporter)
|
||||
REGISTER(Binary, CompressedTextureBinaryExporter)
|
||||
REGISTER(Code, CompressedTextureCodeExporter)
|
||||
REGISTER(Modding, CompressedTextureModdingExporter)
|
||||
};
|
||||
}
|
||||
bool SupportModdedAssets() override { return true; }
|
||||
};
|
||||
@@ -10,10 +10,10 @@ extern "C" {
|
||||
#include "BaseFactory.h"
|
||||
}
|
||||
|
||||
bool isTable = false;
|
||||
std::vector<std::string> tableEntries;
|
||||
static bool isTable = false;
|
||||
static std::vector<std::string> tableEntries;
|
||||
|
||||
static const std::unordered_map <std::string, TextureFormat> gTextureFormats = {
|
||||
static const std::unordered_map <std::string, TextureFormat> sTextureFormats = {
|
||||
{ "RGBA16", { TextureType::RGBA16bpp, 16 } },
|
||||
{ "RGBA32", { TextureType::RGBA32bpp, 32 } },
|
||||
{ "CI4", { TextureType::Palette4bpp, 4 } },
|
||||
@@ -27,60 +27,6 @@ static const std::unordered_map <std::string, TextureFormat> gTextureFormats = {
|
||||
{ "TLUT", { TextureType::TLUT, 16 } },
|
||||
};
|
||||
|
||||
size_t CalculateTextureSize(TextureType type, uint32_t width, uint32_t height) {
|
||||
switch (type) {
|
||||
// 4 bytes per pixel
|
||||
case TextureType::RGBA32bpp:
|
||||
return width * height * 4;
|
||||
// 2 bytes per pixel
|
||||
case TextureType::TLUT:
|
||||
case TextureType::RGBA16bpp:
|
||||
case TextureType::GrayscaleAlpha16bpp:
|
||||
return width * height * 2;
|
||||
// 1 byte per pixel
|
||||
case TextureType::Grayscale8bpp:
|
||||
case TextureType::Palette8bpp:
|
||||
case TextureType::GrayscaleAlpha8bpp:
|
||||
// TODO: We need to validate this MegaMech
|
||||
case TextureType::GrayscaleAlpha1bpp:
|
||||
return width * height;
|
||||
// 1/2 byte per pixel
|
||||
case TextureType::Palette4bpp:
|
||||
case TextureType::Grayscale4bpp:
|
||||
case TextureType::GrayscaleAlpha4bpp:
|
||||
return (width * height) / 2;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint8_t> alloc_ia8_text_from_i1(uint16_t *in, int16_t width, int16_t height) {
|
||||
int32_t inPos;
|
||||
uint16_t bitMask;
|
||||
int16_t outPos = 0;
|
||||
const auto out = new uint8_t[width * height];
|
||||
|
||||
for (int32_t inPos = 0; inPos < (width * height) / 16; inPos++) {
|
||||
uint16_t bitMask = 0x8000;
|
||||
|
||||
while (bitMask != 0) {
|
||||
if (BSWAP16(in[inPos]) & bitMask) {
|
||||
out[outPos] = 0xFF;
|
||||
} else {
|
||||
out[outPos] = 0x00;
|
||||
}
|
||||
|
||||
bitMask /= 2;
|
||||
outPos++;
|
||||
}
|
||||
}
|
||||
|
||||
auto result = std::vector(out, out + width * height);
|
||||
delete[] out;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
ExportResult TextureHeaderExporter::Export(std::ostream &write, std::shared_ptr<IParsedData> raw, std::string& entryName, YAML::Node &node, std::string* replacement) {
|
||||
const auto symbol = GetSafeNode(node, "symbol", entryName);
|
||||
const auto offset = GetSafeNode<uint32_t>(node, "offset");
|
||||
@@ -239,7 +185,7 @@ ExportResult TextureBinaryExporter::Export(std::ostream &write, std::shared_ptr<
|
||||
ExportResult TextureModdingExporter::Export(std::ostream&write, std::shared_ptr<IParsedData> data, std::string&entryName, YAML::Node&node, std::string* replacement) {
|
||||
auto texture = std::static_pointer_cast<TextureData>(data);
|
||||
auto format = texture->mFormat;
|
||||
uint8_t* raw = new uint8_t[CalculateTextureSize(format.type, texture->mWidth, texture->mHeight) * 2];
|
||||
uint8_t* raw = new uint8_t[TextureUtils::CalculateTextureSize(format.type, texture->mWidth, texture->mHeight) * 2];
|
||||
int size = 0;
|
||||
|
||||
auto ext = GetSafeNode<std::string>(node, "format");
|
||||
@@ -332,11 +278,11 @@ std::optional<std::shared_ptr<IParsedData>> TextureFactory::parse(std::vector<ui
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if(!gTextureFormats.contains(format)) {
|
||||
if(!sTextureFormats.contains(format)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
TextureFormat fmt = gTextureFormats.at(format);
|
||||
TextureFormat fmt = sTextureFormats.at(format);
|
||||
|
||||
if(fmt.type == TextureType::TLUT){
|
||||
width = GetSafeNode<uint32_t>(node, "colors");
|
||||
@@ -363,12 +309,12 @@ std::optional<std::shared_ptr<IParsedData>> TextureFactory::parse(std::vector<ui
|
||||
}
|
||||
Companion::Instance->AddAsset(tlutNode);
|
||||
}
|
||||
size = GetSafeNode<uint32_t>(node, "size", CalculateTextureSize(gTextureFormats.at(format).type, width, height));
|
||||
size = GetSafeNode<uint32_t>(node, "size", TextureUtils::CalculateTextureSize(sTextureFormats.at(format).type, width, height));
|
||||
auto [_, segment] = Decompressor::AutoDecode(node, buffer, size);
|
||||
std::vector<uint8_t> result;
|
||||
|
||||
if(fmt.type == TextureType::GrayscaleAlpha1bpp){
|
||||
result = alloc_ia8_text_from_i1(reinterpret_cast<uint16_t*>(segment.data), 8, 16);
|
||||
result = TextureUtils::alloc_ia8_text_from_i1(reinterpret_cast<uint16_t*>(segment.data), 8, 16);
|
||||
} else {
|
||||
result = std::vector(segment.data, segment.data + segment.size);
|
||||
}
|
||||
@@ -408,11 +354,11 @@ std::optional<std::shared_ptr<IParsedData>> TextureFactory::parse_modding(std::v
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if(!gTextureFormats.contains(format)) {
|
||||
if(!sTextureFormats.contains(format)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
TextureFormat fmt = gTextureFormats.at(format);
|
||||
TextureFormat fmt = sTextureFormats.at(format);
|
||||
if(fmt.type == TextureType::TLUT){
|
||||
width = GetSafeNode<uint32_t>(node, "colors");
|
||||
height = 1;
|
||||
|
||||
@@ -1,26 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "BaseFactory.h"
|
||||
|
||||
enum class TextureType {
|
||||
Error,
|
||||
RGBA32bpp,
|
||||
RGBA16bpp,
|
||||
Palette4bpp,
|
||||
Palette8bpp,
|
||||
Grayscale4bpp,
|
||||
Grayscale8bpp,
|
||||
GrayscaleAlpha4bpp,
|
||||
GrayscaleAlpha8bpp,
|
||||
GrayscaleAlpha16bpp,
|
||||
GrayscaleAlpha1bpp,
|
||||
TLUT
|
||||
};
|
||||
|
||||
struct TextureFormat {
|
||||
TextureType type;
|
||||
uint32_t depth;
|
||||
};
|
||||
#include "utils/TextureUtils.h"
|
||||
|
||||
class TextureData : public IParsedData {
|
||||
public:
|
||||
|
||||
@@ -1,7 +1,42 @@
|
||||
#include "AudioHeaderFactory.h"
|
||||
|
||||
#include <vector>
|
||||
#include "AudioManager.h"
|
||||
#include "Companion.h"
|
||||
#include "AIFCDecode.h"
|
||||
#include "spdlog/spdlog.h"
|
||||
#include <factories/naudio/v1/AudioConverter.h>
|
||||
|
||||
/*
|
||||
ExportResult AudioAIFCExporter::Export(std::ostream& write, std::shared_ptr<IParsedData> data, std::string& entryName, YAML::Node& node, std::string* replacement) {
|
||||
|
||||
auto samples = AudioManager::Instance->get_samples();
|
||||
|
||||
int temp = 0;
|
||||
for(auto& sample : samples){
|
||||
std::string dpath = Companion::Instance->GetOutputPath() + "/" + (*replacement);
|
||||
if(!exists(fs::path(dpath).parent_path())){
|
||||
create_directories(fs::path(dpath).parent_path());
|
||||
}
|
||||
std::ofstream file(dpath + "_bank_" + std::to_string(++temp) + ".aiff", std::ios::binary);
|
||||
|
||||
LUS::BinaryWriter aifc = LUS::BinaryWriter();
|
||||
AudioConverter::SampleV0ToAIFC(sample, aifc);
|
||||
|
||||
LUS::BinaryWriter aiff = LUS::BinaryWriter();
|
||||
write_aiff(aifc.ToVector(), aiff);
|
||||
aifc.Close();
|
||||
aiff.Finish(file);
|
||||
file.close();
|
||||
// SPDLOG_INFO("Exported {}", dpath + "_bank_" + std::to_string(temp) + ".aiff");
|
||||
|
||||
SPDLOG_INFO("sample_{}:", temp);
|
||||
SPDLOG_INFO(" type: NAUDIO:V0:SAMPLE");
|
||||
SPDLOG_INFO(" id: {}\n", temp);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
*/
|
||||
|
||||
std::optional<std::shared_ptr<IParsedData>> AudioHeaderFactory::parse(std::vector<uint8_t>& buffer, YAML::Node& data) {
|
||||
AudioManager::Instance->initialize(buffer, data);
|
||||
|
||||
@@ -2,6 +2,13 @@
|
||||
|
||||
#include <factories/BaseFactory.h>
|
||||
|
||||
/*
|
||||
class AudioAIFCExporter : public BaseExporter {
|
||||
public:
|
||||
ExportResult Export(std::ostream& write, std::shared_ptr<IParsedData> data, std::string& entryName, YAML::Node& node, std::string* replacement);
|
||||
};
|
||||
*/
|
||||
|
||||
class AudioDummyExporter : public BaseExporter {
|
||||
public:
|
||||
ExportResult Export(std::ostream& write, std::shared_ptr<IParsedData> data, std::string& entryName, YAML::Node& node, std::string* replacement) override {
|
||||
@@ -17,11 +24,11 @@ public:
|
||||
}
|
||||
std::unordered_map<ExportType, std::shared_ptr<BaseExporter>> GetExporters() override {
|
||||
return {
|
||||
REGISTER(Modding, AudioDummyExporter)
|
||||
// REGISTER(Modding, AudioGenericAIFCExporter)
|
||||
REGISTER(Header, AudioDummyExporter)
|
||||
REGISTER(Binary, AudioDummyExporter)
|
||||
REGISTER(Code, AudioDummyExporter)
|
||||
};
|
||||
}
|
||||
bool SupportModdedAssets() override { return true; }
|
||||
bool HasModdedDependencies() override { return true; }
|
||||
};
|
||||
@@ -128,9 +128,11 @@ Bank AudioManager::parse_ctl(CTLHeader header, std::vector<uint8_t> data, Sample
|
||||
for (size_t i = 0; i < numDrums; ++i) {
|
||||
uint32_t drumOffset;
|
||||
memcpy(&drumOffset, rawData + drumBaseAddr + i * 4, 4);
|
||||
drumOffset = BSWAP32(drumOffset);
|
||||
assert(drumOffset != 0);
|
||||
drumOffsets.push_back(drumOffset);
|
||||
if(drumOffset == 0){
|
||||
continue;
|
||||
}
|
||||
|
||||
drumOffsets.push_back(BSWAP32(drumOffset));
|
||||
}
|
||||
} else {
|
||||
assert(drumBaseAddr == 0);
|
||||
@@ -382,7 +384,14 @@ AudioBankSample* AudioManager::parse_sample(std::vector<uint8_t>& data, std::vec
|
||||
uint32_t loop = reader.ReadUInt32();
|
||||
uint32_t book = reader.ReadUInt32();
|
||||
uint32_t sampleSize = reader.ReadUInt32();
|
||||
assert(zero == 0);
|
||||
|
||||
SPDLOG_INFO("Zero: 0x{:X}", zero);
|
||||
SPDLOG_INFO("Addr: 0x{:X}", addr);
|
||||
SPDLOG_INFO("Loop: 0x{:X}", loop);
|
||||
SPDLOG_INFO("Book: 0x{:X}", book);
|
||||
SPDLOG_INFO("Sample Size: {}", sampleSize);
|
||||
|
||||
// assert(zero == 0);
|
||||
assert(loop != 0);
|
||||
assert(book != 0);
|
||||
|
||||
@@ -479,154 +488,6 @@ void AudioManager::initialize(std::vector<uint8_t>& buffer, YAML::Node& data) {
|
||||
}
|
||||
}
|
||||
|
||||
void serialize_f80(double num, LUS::BinaryWriter &writer) {
|
||||
// Convert the input double to an uint64_t
|
||||
std::uint64_t f64;
|
||||
std::memcpy(&f64, &num, sizeof(double));
|
||||
|
||||
std::uint64_t f64_sign_bit = f64 & (std::uint64_t) pow(2, 63);
|
||||
if (num == 0.0) {
|
||||
if (f64_sign_bit) {
|
||||
writer.Write(0x80000000);
|
||||
} else {
|
||||
writer.Write(0x00000000);
|
||||
}
|
||||
}
|
||||
|
||||
std::uint64_t exponent = ((f64 ^ f64_sign_bit) >> 52);
|
||||
|
||||
assert(exponent != 0);
|
||||
assert(exponent != 0x7FF);
|
||||
|
||||
exponent -= 1023;
|
||||
uint64_t f64_mantissa_bits = f64 & (uint64_t) pow(2, 52) - 1;
|
||||
uint64_t f80_sign_bit = f64_sign_bit << (80 - 64);
|
||||
uint64_t f80_exponent = (exponent + 0x3FFF) << 64;
|
||||
uint64_t f80_mantissa_bits = (uint64_t) pow(2, 63) | (f64_mantissa_bits << (63 - 52));
|
||||
uint64_t f80 = f80_sign_bit | f80_exponent | f80_mantissa_bits;
|
||||
|
||||
// Split the f80 representation into two parts (high and low)
|
||||
uint16_t high = BSWAP16((uint16_t) f80 >> 64);
|
||||
writer.Write((char*) &high, 2);
|
||||
uint64_t low = BSWAP64(f80 & ((uint64_t) pow(2, 64) - 1));
|
||||
writer.Write((char*) &low, 8);
|
||||
}
|
||||
|
||||
#define START_SECTION(section) \
|
||||
{ \
|
||||
out.Write((uint32_t) BSWAP32(section)); \
|
||||
LUS::BinaryWriter tmp = LUS::BinaryWriter(); \
|
||||
tmp.SetEndianness(Torch::Endianness::Big); \
|
||||
|
||||
#define START_CUSTOM_SECTION(section) \
|
||||
{ \
|
||||
LUS::BinaryWriter tmp = LUS::BinaryWriter(); \
|
||||
tmp.SetEndianness(Torch::Endianness::Big); \
|
||||
out.Write((uint32_t) BSWAP32(AIFC::MagicValues::AAPL)); \
|
||||
tmp.Write(AIFC::MagicValues::stoc); \
|
||||
tmp.Write(section, false); \
|
||||
|
||||
#define END_SECTION() \
|
||||
auto odata = tmp.ToVector(); \
|
||||
size_t size = odata.size(); \
|
||||
len += ALIGN(size, 2) + 8; \
|
||||
out.Write((uint32_t) BSWAP32((uint32_t) size)); \
|
||||
out.Write(odata.data(), odata.size()); \
|
||||
if(size % 2){ \
|
||||
out.WriteByte(0); \
|
||||
} \
|
||||
} \
|
||||
|
||||
void AudioManager::write_aifc(AudioBankSample* entry, LUS::BinaryWriter &out) {
|
||||
int16_t num_channels = 1;
|
||||
auto data = entry->data;
|
||||
size_t len = 0;
|
||||
assert(data.size() % 9 == 0);
|
||||
if(data.size() % 2 == 1){
|
||||
data.push_back('\0');
|
||||
}
|
||||
uint32_t num_frames = data.size() * 16 / 9;
|
||||
int16_t sample_size = 16;
|
||||
|
||||
uint32_t sample_rate = -1;
|
||||
if(entry->tunings.size() == 1){
|
||||
sample_rate = 32000 * entry->tunings[0];
|
||||
} else {
|
||||
float tmin = PyUtils::min(entry->tunings);
|
||||
float tmax = PyUtils::max(entry->tunings);
|
||||
|
||||
if(tmin <= 0.5f <= tmax){
|
||||
sample_rate = 16000;
|
||||
} else if(tmin <= 1.0f <= tmax){
|
||||
sample_rate = 32000;
|
||||
} else if(tmin <= 1.5f <= tmax){
|
||||
sample_rate = 48000;
|
||||
} else if(tmin <= 2.5f <= tmax){
|
||||
sample_rate = 80000;
|
||||
} else {
|
||||
sample_rate = 16000 * (tmin + tmax);
|
||||
}
|
||||
}
|
||||
|
||||
out.Write((uint32_t) BSWAP32(AIFC::MagicValues::FORM));
|
||||
// This should be where the size is, but we need to write it later
|
||||
out.Write((uint32_t) 0);
|
||||
out.Write((uint32_t) BSWAP32(AIFC::MagicValues::AIFC));
|
||||
|
||||
START_SECTION(AIFC::MagicValues::COMM);
|
||||
|
||||
tmp.Write((uint16_t) num_channels);
|
||||
tmp.Write((uint32_t) num_frames);
|
||||
|
||||
tmp.Write((uint16_t) sample_size);
|
||||
serialize_f80(sample_rate, tmp);
|
||||
tmp.Write(AIFC::MagicValues::VAPC);
|
||||
tmp.Write("\x0bVADPCM ~4-1", false);
|
||||
|
||||
END_SECTION();
|
||||
|
||||
START_SECTION(AIFC::MagicValues::INST)
|
||||
tmp.Write(std::string(20, '\0'), false);
|
||||
END_SECTION();
|
||||
|
||||
START_CUSTOM_SECTION("\x0bVADPCMCODES")
|
||||
tmp.Write((uint16_t) 1);
|
||||
tmp.Write((uint16_t) entry->book.order);
|
||||
tmp.Write((uint16_t) entry->book.npredictors);
|
||||
|
||||
for(auto x : entry->book.table){
|
||||
tmp.Write((int16_t) x);
|
||||
}
|
||||
END_SECTION();
|
||||
|
||||
START_SECTION(AIFC::MagicValues::SSND)
|
||||
uint32_t zero = 0;
|
||||
tmp.Write((char*) &zero, 4);
|
||||
tmp.Write((char*) &zero, 4);
|
||||
tmp.Write((char*) data.data(), data.size());
|
||||
END_SECTION();
|
||||
|
||||
if(entry->loop.count != 0){
|
||||
START_CUSTOM_SECTION("\x0bVADPCMLOOPS")
|
||||
uint16_t one = BSWAP16(1);
|
||||
tmp.Write(reinterpret_cast<char*>(&one), 2);
|
||||
tmp.Write(reinterpret_cast<char*>(&one), 2);
|
||||
tmp.Write(entry->loop.start);
|
||||
tmp.Write(entry->loop.end);
|
||||
tmp.Write(entry->loop.count);
|
||||
for(size_t i = 0; i < 16; i++){
|
||||
int16_t loop = BSWAP16(entry->loop.state.value()[i]);
|
||||
tmp.Write(reinterpret_cast<char*>(&loop), 2);
|
||||
}
|
||||
END_SECTION();
|
||||
}
|
||||
|
||||
len += 4;
|
||||
out.Seek(4, LUS::SeekOffsetType::Start);
|
||||
out.Write((uint32_t) BSWAP32(len));
|
||||
|
||||
}
|
||||
|
||||
void AudioManager::bind_sample(YAML::Node& node, const std::string& path){
|
||||
auto id = GetSafeNode<uint32_t>(node, "id");
|
||||
sample_table[id] = path;
|
||||
@@ -639,6 +500,7 @@ std::string& AudioManager::get_sample(uint32_t id) {
|
||||
return sample_table[id];
|
||||
}
|
||||
|
||||
/*
|
||||
void AudioManager::create_aifc(int32_t index, LUS::BinaryWriter &out) {
|
||||
int32_t idx = -1;
|
||||
for(auto &sample_bank : this->loaded_tbl.banks){
|
||||
@@ -653,6 +515,7 @@ void AudioManager::create_aifc(int32_t index, LUS::BinaryWriter &out) {
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
AudioBankSample AudioManager::get_aifc(int32_t index) {
|
||||
int32_t idx = 0;
|
||||
@@ -680,4 +543,21 @@ uint32_t AudioManager::get_index(AudioBankSample* entry) {
|
||||
|
||||
std::map<uint32_t, Bank> AudioManager::get_banks() {
|
||||
return this->banks;
|
||||
}
|
||||
|
||||
std::vector<SampleBank*> AudioManager::get_loaded_banks() {
|
||||
return this->loaded_tbl.banks;
|
||||
}
|
||||
|
||||
std::vector<AudioBankSample*> AudioManager::get_samples() {
|
||||
std::vector<AudioBankSample*> samples;
|
||||
for(auto &bank : this->loaded_tbl.banks){
|
||||
for(auto &entry : bank->entries){
|
||||
// Avoid duplicates
|
||||
if(std::find(samples.begin(), samples.end(), entry.second) == samples.end()){
|
||||
samples.push_back(entry.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
return samples;
|
||||
}
|
||||
@@ -12,19 +12,6 @@
|
||||
#define NONE 0xFFFF
|
||||
#define ALIGN(val, al) (size_t) ((val + (al - 1)) & -al)
|
||||
|
||||
namespace AIFC {
|
||||
enum MagicValues {
|
||||
FORM = 0x464f524d,
|
||||
AIFC = 0x41494643,
|
||||
COMM = 0x434f4d4d,
|
||||
INST = 0x494e5354,
|
||||
VAPC = 0x56415043,
|
||||
SSND = 0x53534e44,
|
||||
AAPL = 0x4150504c,
|
||||
stoc = 0x73746f63,
|
||||
};
|
||||
}
|
||||
|
||||
struct Entry {
|
||||
uint32_t offset;
|
||||
uint32_t length;
|
||||
@@ -139,12 +126,12 @@ public:
|
||||
static AudioManager* Instance;
|
||||
void initialize(std::vector<uint8_t>& buffer, YAML::Node& data);
|
||||
void bind_sample(YAML::Node& node, const std::string& path);
|
||||
void create_aifc(int32_t index, LUS::BinaryWriter& writer);
|
||||
std::string& get_sample(uint32_t id);
|
||||
AudioBankSample get_aifc(int32_t index);
|
||||
std::map<uint32_t, Bank> get_banks();
|
||||
std::vector<SampleBank*> get_loaded_banks();
|
||||
std::vector<AudioBankSample*> get_samples();
|
||||
uint32_t get_index(AudioBankSample* bank);
|
||||
|
||||
private:
|
||||
std::map<uint32_t, Bank> banks;
|
||||
std::map<AudioBankSample*, uint32_t> sampleMap;
|
||||
@@ -161,6 +148,4 @@ private:
|
||||
static std::vector<AdsrEnvelope> parse_envelope(uint32_t addr, std::vector<uint8_t>& dataBank);
|
||||
static Bank parse_ctl(CTLHeader header, std::vector<uint8_t> data, SampleBank* bank, uint32_t index);
|
||||
static TBLFile parse_tbl(std::vector<uint8_t>& data, std::vector<Entry>& entries);
|
||||
|
||||
static void write_aifc(AudioBankSample* entry, LUS::BinaryWriter& writer);
|
||||
};
|
||||
@@ -1,5 +1,25 @@
|
||||
#include "SampleFactory.h"
|
||||
|
||||
#include <vector>
|
||||
#include "Companion.h"
|
||||
#include "AIFCDecode.h"
|
||||
#include "spdlog/spdlog.h"
|
||||
#include <factories/naudio/v1/AudioConverter.h>
|
||||
|
||||
ExportResult SampleModdingExporter::Export(std::ostream& writer, std::shared_ptr<IParsedData> raw, std::string& entryName, YAML::Node& node, std::string* replacement) {
|
||||
auto sample = std::static_pointer_cast<SampleData>(raw);
|
||||
*replacement += ".aiff";
|
||||
|
||||
LUS::BinaryWriter aifc = LUS::BinaryWriter();
|
||||
AudioConverter::SampleV0ToAIFC(&sample->mSample, aifc);
|
||||
|
||||
LUS::BinaryWriter aiff = LUS::BinaryWriter();
|
||||
write_aiff(aifc.ToVector(), aiff);
|
||||
aifc.Close();
|
||||
aiff.Finish(writer);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
ExportResult SampleBinaryExporter::Export(std::ostream &write, std::shared_ptr<IParsedData> raw, std::string& entryName, YAML::Node &node, std::string* replacement ) {
|
||||
auto writer = LUS::BinaryWriter();
|
||||
auto sample = std::static_pointer_cast<SampleData>(raw)->mSample;
|
||||
|
||||
@@ -5,6 +5,11 @@
|
||||
#include <factories/BaseFactory.h>
|
||||
#include "AudioManager.h"
|
||||
|
||||
class SampleModdingExporter : public BaseExporter {
|
||||
public:
|
||||
ExportResult Export(std::ostream& write, std::shared_ptr<IParsedData> data, std::string& entryName, YAML::Node& node, std::string* replacement);
|
||||
};
|
||||
|
||||
class SampleData : public IParsedData {
|
||||
public:
|
||||
AudioBankSample mSample;
|
||||
@@ -21,7 +26,10 @@ public:
|
||||
std::optional<std::shared_ptr<IParsedData>> parse(std::vector<uint8_t>& buffer, YAML::Node& data) override;
|
||||
std::unordered_map<ExportType, std::shared_ptr<BaseExporter>> GetExporters() override {
|
||||
return {
|
||||
REGISTER(Modding, SampleModdingExporter)
|
||||
REGISTER(Binary, SampleBinaryExporter)
|
||||
};
|
||||
}
|
||||
|
||||
bool SupportModdedAssets() override { return true; }
|
||||
};
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <Companion.h>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include "hj/pyutils.h"
|
||||
|
||||
void AIFCWriter::End(std::string chunk, LUS::BinaryWriter& writer) {
|
||||
auto buffer = writer.ToVector();
|
||||
@@ -79,7 +80,92 @@ void SerializeF80(double num, LUS::BinaryWriter &writer) {
|
||||
writer.Write(low);
|
||||
}
|
||||
|
||||
void AudioConverter::SampleToAIFC(NSampleData* sample, LUS::BinaryWriter &out) {
|
||||
void AudioConverter::SampleV0ToAIFC(AudioBankSample* sample, LUS::BinaryWriter &out) {
|
||||
auto aifc = AIFCWriter();
|
||||
auto data = sample->data;
|
||||
|
||||
uint32_t num_frames = data.size() * 16 / 9;
|
||||
uint32_t sample_rate = -1;
|
||||
|
||||
if(sample->tunings.size() == 1){
|
||||
sample_rate = 32000 * sample->tunings[0];
|
||||
} else {
|
||||
float tmin = PyUtils::min(sample->tunings);
|
||||
float tmax = PyUtils::max(sample->tunings);
|
||||
|
||||
if(tmin <= 0.5f <= tmax){
|
||||
sample_rate = 16000;
|
||||
} else if(tmin <= 1.0f <= tmax){
|
||||
sample_rate = 32000;
|
||||
} else if(tmin <= 1.5f <= tmax){
|
||||
sample_rate = 48000;
|
||||
} else if(tmin <= 2.5f <= tmax){
|
||||
sample_rate = 80000;
|
||||
} else {
|
||||
sample_rate = 16000 * (tmin + tmax);
|
||||
}
|
||||
}
|
||||
|
||||
int16_t num_channels = 1;
|
||||
int16_t sample_size = 16;
|
||||
|
||||
// COMM Chunk
|
||||
auto comm = aifc.Start();
|
||||
comm.Write(num_channels);
|
||||
comm.Write(num_frames);
|
||||
comm.Write(sample_size);
|
||||
SerializeF80(sample_rate, comm);
|
||||
comm.Write(AIFCMagicValues::VAPC);
|
||||
comm.Write((char*) "\x0bVADPCM ~4-1", 12);
|
||||
aifc.End("COMM", comm);
|
||||
|
||||
// INST Chunk
|
||||
auto inst = aifc.Start();
|
||||
for(size_t i = 0; i < 5; i++){
|
||||
inst.Write((int32_t) 0);
|
||||
}
|
||||
aifc.End("INST", inst);
|
||||
|
||||
// VADPCMCODES Chunk
|
||||
auto vcodes = aifc.Start();
|
||||
vcodes.Write((char*) "stoc\x0bVADPCMCODES", 16);
|
||||
vcodes.Write((int16_t) 1);
|
||||
vcodes.Write((int16_t) sample->book.order);
|
||||
vcodes.Write((int16_t) sample->book.npredictors);
|
||||
|
||||
for(auto page : sample->book.table){
|
||||
vcodes.Write(page);
|
||||
}
|
||||
aifc.End("APPL", vcodes);
|
||||
|
||||
// SSND Chunk
|
||||
auto ssnd = aifc.Start();
|
||||
ssnd.Write((uint64_t) 0);
|
||||
ssnd.Write((char*) data.data(), data.size());
|
||||
aifc.End("SSND", ssnd);
|
||||
|
||||
// VADPCMLOOPS
|
||||
if(sample->loop.count != 0){
|
||||
auto vloops = aifc.Start();
|
||||
vloops.Write((char*) "stoc\x0bVADPCMLOOPS", 16);
|
||||
vloops.Write((uint16_t) 1);
|
||||
vloops.Write((uint16_t) 1);
|
||||
vloops.Write(sample->loop.start);
|
||||
vloops.Write(sample->loop.end);
|
||||
vloops.Write(sample->loop.count);
|
||||
|
||||
if(sample->loop.state.has_value()){
|
||||
for(auto state : sample->loop.state.value()){
|
||||
vcodes.Write(state);
|
||||
}
|
||||
}
|
||||
aifc.End("APPL", vloops);
|
||||
}
|
||||
|
||||
aifc.Close(out);
|
||||
}
|
||||
|
||||
void AudioConverter::SampleV1ToAIFC(NSampleData* sample, LUS::BinaryWriter &out) {
|
||||
auto loop = std::static_pointer_cast<ADPCMLoopData>(Companion::Instance->GetParseDataByAddr(sample->loop)->data.value());
|
||||
auto book = std::static_pointer_cast<ADPCMBookData>(Companion::Instance->GetParseDataByAddr(sample->book)->data.value());
|
||||
auto entry = AudioContext::tableData[AudioTableType::SAMPLE_TABLE]->entries[sample->sampleBankId];
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <factories/BaseFactory.h>
|
||||
#include <factories/naudio/v1/SampleFactory.h>
|
||||
#include <factories/naudio/v0/AudioManager.h>
|
||||
|
||||
enum AIFCMagicValues {
|
||||
FORM = (uint32_t) 0x464f524d,
|
||||
@@ -15,7 +16,8 @@ enum AIFCMagicValues {
|
||||
|
||||
class AudioConverter {
|
||||
public:
|
||||
static void SampleToAIFC(NSampleData* tSample, LUS::BinaryWriter &out);
|
||||
static void SampleV0ToAIFC(AudioBankSample* entry, LUS::BinaryWriter &out);
|
||||
static void SampleV1ToAIFC(NSampleData* tSample, LUS::BinaryWriter &out);
|
||||
};
|
||||
|
||||
struct AIFCChunk {
|
||||
|
||||
@@ -51,7 +51,7 @@ ExportResult NSampleModdingExporter::Export(std::ostream &write, std::shared_ptr
|
||||
*replacement += ".aiff";
|
||||
|
||||
auto aifc = LUS::BinaryWriter();
|
||||
AudioConverter::SampleToAIFC(data.get(), aifc);
|
||||
AudioConverter::SampleV1ToAIFC(data.get(), aifc);
|
||||
auto cnv = aifc.ToVector();
|
||||
|
||||
if(!cnv.empty()){
|
||||
|
||||
@@ -111,9 +111,8 @@ ExportResult SF64::SkeletonBinaryExporter::Export(std::ostream &write, std::shar
|
||||
|
||||
auto limbWriter = LUS::BinaryWriter();
|
||||
WriteHeader(limbWriter, Torch::ResourceType::Limb, 0);
|
||||
bool hasDList = limb.mDList != 0 && (SEGMENT_NUMBER(limb.mDList) == SEGMENT_NUMBER(limb.mAddr));
|
||||
|
||||
if(hasDList){
|
||||
if(limb.mDList != 0){
|
||||
auto dec = Companion::Instance->GetNodeByAddr(limb.mDList);
|
||||
if (dec.has_value()){
|
||||
std::string path = std::get<0>(dec.value());
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
#include "CompTool.h"
|
||||
#include "utils/Decompressor.h"
|
||||
#include "lib/binarytools/BinaryWriter.h"
|
||||
#include "lib/binarytools/BinaryReader.h"
|
||||
#include <fstream>
|
||||
#include <cstring>
|
||||
|
||||
uint32_t CompTool::FindFileTable(std::vector<uint8_t>& rom) {
|
||||
uint8_t query_one[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x50, 0x00, 0x00, 0x00, 0x00 };
|
||||
uint8_t query_two[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x60, 0x00, 0x00, 0x00, 0x00 };
|
||||
|
||||
for(size_t i = 0; i < rom.size() - sizeof(query_one); i++) {
|
||||
if(memcmp(rom.data() + i, query_one, sizeof(query_one)) == 0){
|
||||
return i;
|
||||
}
|
||||
|
||||
if(memcmp(rom.data() + i, query_two, sizeof(query_two)) == 0){
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
throw std::runtime_error("Failed to find file table");
|
||||
}
|
||||
|
||||
std::vector<uint8_t> CompTool::Decompress(std::vector<uint8_t> rom){
|
||||
LUS::BinaryReader basefile((char*) rom.data(), rom.size());
|
||||
basefile.SetEndianness(Torch::Endianness::Big);
|
||||
|
||||
LUS::BinaryWriter decompfile;
|
||||
decompfile.SetEndianness(Torch::Endianness::Big);
|
||||
decompfile.Write((uint8_t)0x80);
|
||||
|
||||
uint32_t table = CompTool::FindFileTable(rom);
|
||||
uint32_t count = 0;
|
||||
while (true){
|
||||
auto entry = table + 0x10 * count;
|
||||
basefile.Seek(entry, LUS::SeekOffsetType::Start);
|
||||
|
||||
auto v_begin = basefile.ReadInt32();
|
||||
auto p_begin = basefile.ReadInt32();
|
||||
auto p_end = basefile.ReadInt32();
|
||||
auto comp_flag = basefile.ReadInt32();
|
||||
|
||||
auto p_size = p_end - p_begin;
|
||||
auto v_size = (int32_t) 0;
|
||||
DataChunk* decoded = nullptr;
|
||||
|
||||
if(v_begin == 0 && p_end == 0){
|
||||
break;
|
||||
}
|
||||
|
||||
basefile.Seek(p_begin, LUS::SeekOffsetType::Start);
|
||||
|
||||
auto bytes = new uint8_t[p_size];
|
||||
basefile.Read((char*) bytes, p_size);
|
||||
|
||||
switch ((CompType) comp_flag) {
|
||||
case CompType::UNCOMPRESSED:
|
||||
v_size = p_size;
|
||||
break;
|
||||
case CompType::COMPRESSED:
|
||||
decoded = Decompressor::Decode(std::vector(bytes, bytes + p_size), 0, CompressionType::MIO0, true);
|
||||
bytes = decoded->data;
|
||||
v_size = decoded->size;
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error("Invalid compression flag. There may be a problem with your ROM.");
|
||||
}
|
||||
|
||||
decompfile.Seek(v_begin, LUS::SeekOffsetType::Start);
|
||||
decompfile.Write((char*) bytes, v_size);
|
||||
auto v_end = v_begin + v_size;
|
||||
|
||||
decompfile.Seek(entry + 4, LUS::SeekOffsetType::Start);
|
||||
decompfile.Write(v_begin);
|
||||
decompfile.Write(v_end);
|
||||
decompfile.Write((uint32_t) CompType::UNCOMPRESSED);
|
||||
count++;
|
||||
}
|
||||
|
||||
decompfile.Seek(0x10, LUS::SeekOffsetType::Start);
|
||||
decompfile.Write(0xA7D5F194); // CRC1
|
||||
decompfile.Write(0xFE3DF761); // CRC2
|
||||
|
||||
auto result = decompfile.ToVector();
|
||||
return { (uint8_t*) result.data(), (uint8_t*) result.data() + result.size() };
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
enum class CompType {
|
||||
UNCOMPRESSED,
|
||||
COMPRESSED,
|
||||
UNKNOWN
|
||||
};
|
||||
|
||||
class CompTool {
|
||||
public:
|
||||
static std::vector<uint8_t> Decompress(std::vector<uint8_t> rom);
|
||||
private:
|
||||
static uint32_t FindFileTable(std::vector<uint8_t>& rom);
|
||||
};
|
||||
@@ -12,9 +12,9 @@ extern "C" {
|
||||
|
||||
std::unordered_map<uint32_t, DataChunk*> gCachedChunks;
|
||||
|
||||
DataChunk* Decompressor::Decode(const std::vector<uint8_t>& buffer, const uint32_t offset, const CompressionType type) {
|
||||
DataChunk* Decompressor::Decode(const std::vector<uint8_t>& buffer, const uint32_t offset, const CompressionType type, bool ignoreCache) {
|
||||
|
||||
if(gCachedChunks.contains(offset)){
|
||||
if(!ignoreCache && gCachedChunks.contains(offset)){
|
||||
return gCachedChunks[offset];
|
||||
}
|
||||
|
||||
@@ -208,4 +208,4 @@ void Decompressor::ClearCache() {
|
||||
delete value->data;
|
||||
}
|
||||
gCachedChunks.clear();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user