mirror of
https://github.com/open-goal/jak-project
synced 2026-07-07 14:13:45 -04:00
add some really old vag file unpacking stuff (#708)
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
add_library(common
|
||||
SHARED
|
||||
audio/audio_formats.cpp
|
||||
cross_os_debug/xdbg.cpp
|
||||
cross_sockets/xsocket.cpp
|
||||
goos/Interpreter.cpp
|
||||
|
||||
@@ -0,0 +1,300 @@
|
||||
#include "audio_formats.h"
|
||||
#include "common/util/BinaryWriter.h"
|
||||
#include "third-party/fmt/core.h"
|
||||
|
||||
/*!
|
||||
* Write a wave file from a vector of samples.
|
||||
*/
|
||||
void write_wave_file_mono(const std::vector<s16>& samples,
|
||||
s32 sample_rate,
|
||||
const std::string& name) {
|
||||
WaveFileHeader header;
|
||||
memcpy(header.chunk_id, "RIFF", 4);
|
||||
header.chunk_size = 36 + samples.size() * sizeof(s16);
|
||||
memcpy(header.format, "WAVE", 4);
|
||||
|
||||
// now the format
|
||||
memcpy(header.subchunk1_id, "fmt ", 4);
|
||||
header.subchunk1_size = 16;
|
||||
header.aud_format = 1;
|
||||
header.num_channels = 1; // mono
|
||||
header.sample_rate = sample_rate;
|
||||
header.byte_rate = sample_rate * header.num_channels * sizeof(s16);
|
||||
header.block_align = header.num_channels * sizeof(s16);
|
||||
header.bits_per_sample = 16;
|
||||
|
||||
memcpy(header.subchunk2_id, "data", 4);
|
||||
header.subchunk2_size = samples.size() * sizeof(s16);
|
||||
|
||||
BinaryWriter writer;
|
||||
writer.add(header);
|
||||
|
||||
for (auto& samp : samples) {
|
||||
writer.add(samp);
|
||||
}
|
||||
|
||||
writer.write_to_file(name);
|
||||
}
|
||||
|
||||
std::vector<s16> decode_adpcm(BinaryReader& reader) {
|
||||
std::vector<s16> decoded_samples;
|
||||
s32 sample_prev[2] = {0, 0};
|
||||
constexpr s32 f1[5] = {0, 60, 115, 98, 122};
|
||||
constexpr s32 f2[5] = {0, 0, -52, -55, -60};
|
||||
|
||||
int block_idx = 0;
|
||||
while (true) {
|
||||
if (!reader.bytes_left()) {
|
||||
break;
|
||||
}
|
||||
u8 shift_filter = reader.read<u8>();
|
||||
u8 flags = reader.read<u8>();
|
||||
u8 shift = shift_filter & 0b1111;
|
||||
u8 filter = shift_filter >> 4;
|
||||
|
||||
if (shift > 12) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
if (filter > 4) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
if (flags == 7) {
|
||||
break;
|
||||
}
|
||||
|
||||
u8 input_buffer[14];
|
||||
|
||||
for (int i = 0; i < 14; i++) {
|
||||
input_buffer[i] = reader.read<u8>();
|
||||
}
|
||||
|
||||
for (int i = 0; i < 28; i++) {
|
||||
int16_t nibble = input_buffer[i / 2];
|
||||
if (i % 2 == 0) {
|
||||
nibble = (nibble & 0x0f);
|
||||
} else {
|
||||
nibble = (nibble & 0xf0) >> 4;
|
||||
}
|
||||
|
||||
s32 sample = (s32)(s16)(nibble << 12);
|
||||
sample >>= shift;
|
||||
sample += (sample_prev[0] * f1[filter] + sample_prev[1] * f2[filter] + 32) / 64;
|
||||
|
||||
if (sample > 0x7fff) {
|
||||
sample = 0x7fff;
|
||||
}
|
||||
|
||||
if (sample < -0x8000) {
|
||||
sample = -0x8000;
|
||||
}
|
||||
|
||||
sample_prev[1] = sample_prev[0];
|
||||
sample_prev[0] = sample;
|
||||
|
||||
decoded_samples.push_back(sample);
|
||||
}
|
||||
block_idx++;
|
||||
}
|
||||
|
||||
return decoded_samples;
|
||||
}
|
||||
|
||||
// I attempted to write an encoder below, which works, but has some limitations.
|
||||
// - In some cases we can't recover the original data exactly because the decode saturates the
|
||||
// the output to fit in a signed 16-bit integer.
|
||||
// - There are some cases when there are multiple ways to encode the same data.
|
||||
// The break_filter_ties function attempts to handle this, but doesn't work 100% of the time.
|
||||
|
||||
template <typename T>
|
||||
T saturate(T in, T minimum, T maximum) {
|
||||
if (in < minimum) {
|
||||
return minimum;
|
||||
}
|
||||
if (in > maximum) {
|
||||
return maximum;
|
||||
}
|
||||
return in;
|
||||
}
|
||||
|
||||
constexpr int SAMPLES_PER_BLOCK = 28;
|
||||
|
||||
void encode_block_with_filter(int filter_idx,
|
||||
const s16* samples_in,
|
||||
s32* out,
|
||||
const s32* prev_samples_in) {
|
||||
constexpr s32 f1[5] = {0, 60, 115, 98, 122};
|
||||
constexpr s32 f2[5] = {0, 0, -52, -55, -60};
|
||||
s32 prev_samples[2] = {prev_samples_in[0], prev_samples_in[1]};
|
||||
|
||||
for (int sample_idx = 0; sample_idx < SAMPLES_PER_BLOCK; sample_idx++) {
|
||||
s32 sample = samples_in[sample_idx];
|
||||
s32 delta =
|
||||
sample - (prev_samples[0] * f1[filter_idx] + prev_samples[1] * f2[filter_idx] + 32) / 64;
|
||||
out[sample_idx] = delta;
|
||||
prev_samples[1] = prev_samples[0];
|
||||
prev_samples[0] = sample;
|
||||
}
|
||||
}
|
||||
|
||||
int get_shift_error(int shift, const s32* samples, bool /*debug*/) {
|
||||
int result = 0;
|
||||
|
||||
for (int sample_idx = 0; sample_idx < SAMPLES_PER_BLOCK; sample_idx++) {
|
||||
int left_shift = 32 - (12 + 4 - shift);
|
||||
assert(left_shift >= 0);
|
||||
s32 sample_left = samples[sample_idx] << left_shift;
|
||||
s32 sample_right = sample_left >> (32 - 4);
|
||||
s32 sample_compressed = sample_right << (12 - shift);
|
||||
|
||||
s32 err = std::abs(sample_compressed - samples[sample_idx]);
|
||||
|
||||
result += err;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int get_max_bits(s32 value) {
|
||||
int result = 0;
|
||||
if (value >= 0) {
|
||||
int last = 1;
|
||||
while (value) {
|
||||
result++;
|
||||
last = value & 1;
|
||||
value >>= 1;
|
||||
}
|
||||
if (last) {
|
||||
result++;
|
||||
}
|
||||
} else {
|
||||
int last = 0;
|
||||
while (value != -1) {
|
||||
result++;
|
||||
last = value & 1;
|
||||
value >>= 1;
|
||||
}
|
||||
if (!last) {
|
||||
result++;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int break_filter_ties(s32* errors, s32* filter_shifts) {
|
||||
s32 best_error = INT32_MAX;
|
||||
|
||||
for (int filter_idx = 0; filter_idx < 5; filter_idx++) {
|
||||
if (errors[filter_idx] < best_error) {
|
||||
best_error = errors[filter_idx];
|
||||
}
|
||||
}
|
||||
|
||||
s32 best_shift = INT32_MAX;
|
||||
int best_filter = -1;
|
||||
for (int filter_idx = 5; filter_idx-- > 0;) {
|
||||
if (errors[filter_idx] == best_error) {
|
||||
if (filter_shifts[filter_idx] <= best_shift) {
|
||||
best_shift = filter_shifts[filter_idx];
|
||||
best_filter = filter_idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return best_filter;
|
||||
}
|
||||
|
||||
void test_encode_adpcm(const std::vector<s16>& samples,
|
||||
const std::vector<u8>& filter_debug,
|
||||
const std::vector<u8>& shift_debug) {
|
||||
// the data is made of blocks.
|
||||
// Each block decodes to 28 samples.
|
||||
// each block has a shift and FIR filter.
|
||||
// the window is continuous across blocks.
|
||||
|
||||
// we could try all combinations of filters / shifts and pick the best, but that's slow and
|
||||
// we don't know how to break ties if multiple are the same.
|
||||
// we will try all 5 filters, then be smart about picking the best shift from there.
|
||||
|
||||
// filter coefficients.
|
||||
// there are 5x FIR filters that you can pick between.
|
||||
|
||||
// last two samples from chosen encoding of the previous block
|
||||
// init to 0, like the decoder
|
||||
s32 prev_block_samples[2] = {0, 0};
|
||||
|
||||
// TODO - this will drop some samples at the end, if we don't use a multiple of 28.
|
||||
// probably best to go back and pad with zeros or something.
|
||||
int block_count = samples.size() / SAMPLES_PER_BLOCK;
|
||||
|
||||
for (int block_idx = 0; block_idx < block_count; block_idx++) {
|
||||
// try each filter
|
||||
s32 pre_shift_samples_per_filter[5][SAMPLES_PER_BLOCK];
|
||||
for (int filter_idx = 0; filter_idx < 5; filter_idx++) {
|
||||
encode_block_with_filter(filter_idx, samples.data() + SAMPLES_PER_BLOCK * block_idx,
|
||||
pre_shift_samples_per_filter[filter_idx], prev_block_samples);
|
||||
}
|
||||
|
||||
// this is somewhat arbitrary, but we will require that the largest delta in the previous encode
|
||||
// can be represented.
|
||||
|
||||
s32 filter_errors[5] = {0, 0, 0, 0, 0};
|
||||
s32 filter_shifts[5] = {-1, -1, -1, -1};
|
||||
for (int filter_idx = 0; filter_idx < 5; filter_idx++) {
|
||||
// find the largest value
|
||||
s32 max_sample = INT32_MIN;
|
||||
s32 min_sample = INT32_MAX;
|
||||
|
||||
bool debug = block_idx == 10966 && filter_idx == 4;
|
||||
|
||||
for (int sample_idx = 0; sample_idx < SAMPLES_PER_BLOCK; sample_idx++) {
|
||||
s32 s = pre_shift_samples_per_filter[filter_idx][sample_idx];
|
||||
max_sample = std::max(s, max_sample);
|
||||
min_sample = std::min(s, min_sample);
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
fmt::print("Range: {}\n", max_sample - min_sample);
|
||||
}
|
||||
|
||||
// see how many bits we need and pick shift.
|
||||
auto bits_for_max = std::max(4, std::max(get_max_bits(min_sample), get_max_bits(max_sample)));
|
||||
|
||||
filter_shifts[filter_idx] = 4 + 12 - bits_for_max;
|
||||
|
||||
filter_errors[filter_idx] = get_shift_error(filter_shifts[filter_idx],
|
||||
pre_shift_samples_per_filter[filter_idx], debug);
|
||||
|
||||
if (filter_errors[filter_idx] == 0) {
|
||||
while (filter_shifts[filter_idx] >= 0) {
|
||||
int next_error = get_shift_error(filter_shifts[filter_idx] - 1,
|
||||
pre_shift_samples_per_filter[filter_idx], false);
|
||||
if (next_error == 0) {
|
||||
filter_shifts[filter_idx]--;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int best_filter = break_filter_ties(filter_errors, filter_shifts);
|
||||
s32 best_shift = filter_shifts[best_filter];
|
||||
|
||||
if (filter_errors[best_filter] || best_filter != filter_debug[block_idx] ||
|
||||
best_shift != shift_debug[block_idx]) {
|
||||
fmt::print("Block {} me {}, {} : answer {} {}: ERR {}\n", block_idx, best_filter, best_shift,
|
||||
filter_debug[block_idx], shift_debug[block_idx], filter_errors[best_filter]);
|
||||
fmt::print("filter errors:\n");
|
||||
for (int i = 0; i < 5; i++) {
|
||||
fmt::print(" [{}] {} {}\n", i, filter_errors[i], filter_shifts[i]);
|
||||
}
|
||||
fmt::print("prev: {} {}\n", prev_block_samples[0], prev_block_samples[1]);
|
||||
assert(false);
|
||||
}
|
||||
|
||||
prev_block_samples[0] = samples.at(block_idx * 28 + 27);
|
||||
prev_block_samples[1] = samples.at(block_idx * 28 + 26);
|
||||
|
||||
} // end loop over blocks
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "common/util/BinaryReader.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
// The header data for a simple wave file
|
||||
struct WaveFileHeader {
|
||||
// wave file header
|
||||
char chunk_id[4];
|
||||
s32 chunk_size;
|
||||
char format[4];
|
||||
|
||||
// format chunk
|
||||
char subchunk1_id[4];
|
||||
s32 subchunk1_size;
|
||||
s16 aud_format;
|
||||
s16 num_channels;
|
||||
s32 sample_rate;
|
||||
s32 byte_rate;
|
||||
s16 block_align;
|
||||
s16 bits_per_sample;
|
||||
|
||||
// data chunk
|
||||
char subchunk2_id[4];
|
||||
s32 subchunk2_size;
|
||||
};
|
||||
|
||||
void write_wave_file_mono(const std::vector<s16>& samples,
|
||||
s32 sample_rate,
|
||||
const std::string& name);
|
||||
|
||||
std::vector<s16> decode_adpcm(BinaryReader& reader);
|
||||
|
||||
std::vector<u8> encode_adpcm(const std::vector<s16>& samples);
|
||||
+15
-11
@@ -6,31 +6,35 @@
|
||||
*/
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include "common/util/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include <vector>
|
||||
|
||||
class BinaryReader {
|
||||
public:
|
||||
explicit BinaryReader(const std::vector<uint8_t>& _buffer) : buffer(_buffer) {}
|
||||
explicit BinaryReader(const std::vector<uint8_t>& _buffer) : m_buffer(_buffer) {}
|
||||
|
||||
template <typename T>
|
||||
T read() {
|
||||
assert(seek + sizeof(T) <= buffer.size());
|
||||
T& obj = *(T*)(buffer.data() + seek);
|
||||
seek += sizeof(T);
|
||||
assert(m_seek + sizeof(T) <= m_buffer.size());
|
||||
T obj;
|
||||
memcpy(&obj, m_buffer.data() + m_seek, sizeof(T));
|
||||
m_seek += sizeof(T);
|
||||
return obj;
|
||||
}
|
||||
|
||||
void ffwd(int amount) {
|
||||
seek += amount;
|
||||
assert(seek <= buffer.size());
|
||||
m_seek += amount;
|
||||
assert(m_seek <= m_buffer.size());
|
||||
}
|
||||
|
||||
uint32_t bytes_left() const { return buffer.size() - seek; }
|
||||
uint8_t* here() { return buffer.data() + seek; }
|
||||
uint32_t get_seek() const { return seek; }
|
||||
uint32_t bytes_left() const { return m_buffer.size() - m_seek; }
|
||||
uint8_t* here() { return m_buffer.data() + m_seek; }
|
||||
uint32_t get_seek() const { return m_seek; }
|
||||
void set_seek(u32 seek) { m_seek = seek; }
|
||||
|
||||
private:
|
||||
std::vector<u8> buffer;
|
||||
uint32_t seek = 0;
|
||||
std::vector<u8> m_buffer;
|
||||
uint32_t m_seek = 0;
|
||||
};
|
||||
|
||||
@@ -18,6 +18,7 @@ add_library(
|
||||
data/dir_tpages.cpp
|
||||
data/game_count.cpp
|
||||
data/game_text.cpp
|
||||
data/streamed_audio.cpp
|
||||
data/StrFileReader.cpp
|
||||
data/tpage.cpp
|
||||
|
||||
|
||||
@@ -157,7 +157,7 @@ ObjectFileDB::ObjectFileDB(const std::vector<std::string>& _dgos,
|
||||
|
||||
lg::info("ObjectFileDB Initialized\n");
|
||||
if (obj_files_by_name.empty()) {
|
||||
lg::die(
|
||||
lg::error(
|
||||
"No object files have been added. Check that there are input files and the allowed_objects "
|
||||
"list.");
|
||||
}
|
||||
|
||||
@@ -33,6 +33,9 @@ Config read_config_file(const std::string& path_to_config_file) {
|
||||
config.dgo_names = inputs_json.at("dgo_names").get<std::vector<std::string>>();
|
||||
config.object_file_names = inputs_json.at("object_file_names").get<std::vector<std::string>>();
|
||||
config.str_file_names = inputs_json.at("str_file_names").get<std::vector<std::string>>();
|
||||
config.audio_dir_file_name = inputs_json.at("audio_dir_file_name").get<std::string>();
|
||||
config.streamed_audio_file_names =
|
||||
inputs_json.at("streamed_audio_file_names").get<std::vector<std::string>>();
|
||||
|
||||
if (cfg.contains("obj_file_name_map_file")) {
|
||||
config.obj_file_name_map_file = cfg.at("obj_file_name_map_file").get<std::string>();
|
||||
|
||||
@@ -68,6 +68,9 @@ struct Config {
|
||||
std::vector<std::string> object_file_names;
|
||||
std::vector<std::string> str_file_names;
|
||||
|
||||
std::string audio_dir_file_name;
|
||||
std::vector<std::string> streamed_audio_file_names;
|
||||
|
||||
std::string obj_file_name_map_file;
|
||||
|
||||
bool disassemble_code = false;
|
||||
|
||||
@@ -256,5 +256,18 @@
|
||||
"TEXT/4COMMON.TXT",
|
||||
"TEXT/5COMMON.TXT",
|
||||
"TEXT/6COMMON.TXT"
|
||||
],
|
||||
|
||||
// uncomment the next line to extract audio to wave files.
|
||||
//"audio_dir_file_name": "VAG/VAGDIR.AYB",
|
||||
"audio_dir_file_name" : "",
|
||||
|
||||
"streamed_audio_file_names": [
|
||||
"VAG/VAGWAD.ENG",
|
||||
"VAG/VAGWAD.FRE",
|
||||
"VAG/VAGWAD.GER",
|
||||
"VAG/VAGWAD.ITA",
|
||||
"VAG/VAGWAD.JAP",
|
||||
"VAG/VAGWAD.SPA"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
|
||||
#include "common/log/log.h"
|
||||
#include "common/util/FileUtil.h"
|
||||
#include "common/util/BinaryReader.h"
|
||||
#include "common/audio/audio_formats.h"
|
||||
#include "third-party/fmt/core.h"
|
||||
#include "third-party/json.hpp"
|
||||
|
||||
#include "streamed_audio.h"
|
||||
|
||||
namespace decompiler {
|
||||
|
||||
// number of bytes per "audio page" in the VAG directory file.
|
||||
constexpr int AUDIO_PAGE_SIZE = 2048;
|
||||
|
||||
// Swap endian of 32-bit value.
|
||||
uint32_t swap32(uint32_t in) {
|
||||
return ((in << 24) | ((in & 0xff00) << 8) | ((in & 0xff0000) >> 8) | (in >> 24));
|
||||
}
|
||||
|
||||
/*!
|
||||
* A processed version of the VAGDIR file containing a map from 8-char name to location in the
|
||||
* WAD files.
|
||||
*/
|
||||
struct AudioDir {
|
||||
struct Entry {
|
||||
std::string name;
|
||||
s64 start_byte = -1;
|
||||
s64 end_byte = -1;
|
||||
};
|
||||
|
||||
std::vector<Entry> entries;
|
||||
|
||||
void set_file_size(u64 size) {
|
||||
if (!entries.empty()) {
|
||||
entries.back().end_byte = size;
|
||||
}
|
||||
}
|
||||
|
||||
int entry_count() const { return entries.size(); }
|
||||
|
||||
void debug_print() const {
|
||||
for (auto& e : entries) {
|
||||
fmt::print("\"{}\" 0x{:07x} - 0x{:07x}\n", e.name, e.start_byte, e.end_byte);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* Read an entry from a WAD and return the binary data.
|
||||
*/
|
||||
std::vector<u8> read_entry(const AudioDir& dir, const std::vector<u8>& data, int entry_idx) {
|
||||
const auto& entry = dir.entries.at(entry_idx);
|
||||
assert(entry.end_byte > 0);
|
||||
return std::vector<u8>(data.begin() + entry.start_byte, data.begin() + entry.end_byte);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Matches the format in file.
|
||||
*/
|
||||
struct VagFileHeader {
|
||||
char magic[4];
|
||||
u32 version;
|
||||
u32 zero;
|
||||
u32 channel_size;
|
||||
u32 sample_rate;
|
||||
u32 z[3];
|
||||
char name[16];
|
||||
|
||||
VagFileHeader swapped_endian() const {
|
||||
VagFileHeader result(*this);
|
||||
result.version = swap32(result.version);
|
||||
result.channel_size = swap32(result.channel_size);
|
||||
result.sample_rate = swap32(result.sample_rate);
|
||||
return result;
|
||||
}
|
||||
|
||||
void debug_print() {
|
||||
char temp_name[17];
|
||||
memcpy(temp_name, name, 16);
|
||||
temp_name[16] = '\0';
|
||||
fmt::print("{}{}{}{} v {} zero {} chan {} samp {} z {} {} {} name {}\n", magic[0], magic[1],
|
||||
magic[2], magic[3], version, zero, channel_size, sample_rate, z[0], z[1], z[2],
|
||||
temp_name);
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* Read the DIR file into an AudioDir
|
||||
*/
|
||||
AudioDir read_audio_dir(const std::string& path) {
|
||||
// matches the format in file.
|
||||
struct DirEntry {
|
||||
char name[8];
|
||||
u32 value;
|
||||
};
|
||||
auto data = file_util::read_binary_file(path);
|
||||
lg::info("Got {} bytes of audio dir.\n", data.size());
|
||||
auto reader = BinaryReader(data);
|
||||
|
||||
u32 count = reader.read<u32>();
|
||||
u32 data_end = sizeof(u32) + sizeof(DirEntry) * count;
|
||||
assert(data_end <= data.size());
|
||||
std::vector<DirEntry> entries;
|
||||
for (u32 i = 0; i < count; i++) {
|
||||
entries.push_back(reader.read<DirEntry>());
|
||||
}
|
||||
|
||||
while (reader.bytes_left()) {
|
||||
assert(reader.read<u8>() == 0);
|
||||
}
|
||||
|
||||
AudioDir result;
|
||||
|
||||
assert(!entries.empty());
|
||||
for (size_t i = 0; i < entries.size(); i++) {
|
||||
AudioDir::Entry e;
|
||||
for (auto c : entries[i].name) {
|
||||
// padded with spaces, no null terminator.
|
||||
e.name.push_back(c);
|
||||
e.start_byte = AUDIO_PAGE_SIZE * entries[i].value;
|
||||
if (i + 1 < (entries.size())) {
|
||||
e.end_byte = AUDIO_PAGE_SIZE * entries[i + 1].value;
|
||||
} else {
|
||||
e.end_byte = -1;
|
||||
}
|
||||
}
|
||||
result.entries.push_back(e);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string remove_trailing_spaces(const std::string& in) {
|
||||
auto short_name = in;
|
||||
while (!short_name.empty() && short_name.back() == ' ') {
|
||||
short_name.pop_back();
|
||||
}
|
||||
return short_name;
|
||||
}
|
||||
|
||||
struct AudioFileInfo {
|
||||
std::string filename;
|
||||
double length_seconds;
|
||||
};
|
||||
|
||||
AudioFileInfo process_audio_file(const std::vector<u8>& data,
|
||||
const std::string& name,
|
||||
const std::string& suffix) {
|
||||
BinaryReader reader(data);
|
||||
|
||||
auto header = reader.read<VagFileHeader>();
|
||||
if (header.magic[0] == 'V') {
|
||||
header = header.swapped_endian();
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
header.debug_print();
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
assert(reader.read<u8>() == 0);
|
||||
}
|
||||
|
||||
std::vector<s16> decoded_samples = decode_adpcm(reader);
|
||||
|
||||
while (reader.bytes_left()) {
|
||||
assert(reader.read<u8>() == 0);
|
||||
}
|
||||
|
||||
auto file_name = fmt::format("{}_{}.wav", remove_trailing_spaces(name), suffix);
|
||||
write_wave_file_mono(decoded_samples, header.sample_rate,
|
||||
file_util::get_file_path({"assets", "streaming_audio", file_name}));
|
||||
|
||||
std::string vag_filename;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
if (header.name[i]) {
|
||||
vag_filename.push_back(header.name[i]);
|
||||
}
|
||||
}
|
||||
return {vag_filename, (double)decoded_samples.size() / header.sample_rate};
|
||||
}
|
||||
|
||||
void process_streamed_audio(const std::string& dir, const std::vector<std::string>& audio_files) {
|
||||
lg::info("Streaming audio: {}\n", dir);
|
||||
file_util::create_dir_if_needed(file_util::get_file_path({"assets", "streaming_audio"}));
|
||||
auto dir_data = read_audio_dir(file_util::get_file_path({"iso_data", dir}));
|
||||
double audio_len = 0.f;
|
||||
|
||||
std::vector<std::string> langs;
|
||||
std::vector<std::vector<std::string>> filename_data;
|
||||
for (auto& e : dir_data.entries) {
|
||||
std::vector<std::string> placeholders = {remove_trailing_spaces(e.name)};
|
||||
for (size_t i = 0; i < audio_files.size(); i++) {
|
||||
placeholders.push_back("????");
|
||||
}
|
||||
filename_data.push_back(placeholders);
|
||||
}
|
||||
|
||||
for (size_t lang_id = 0; lang_id < audio_files.size(); lang_id++) {
|
||||
auto& file = audio_files[lang_id];
|
||||
auto wad_data = file_util::read_binary_file(file_util::get_file_path({"iso_data", file}));
|
||||
auto suffix = std::filesystem::path(file).extension().u8string().substr(1);
|
||||
langs.push_back(suffix);
|
||||
dir_data.set_file_size(wad_data.size());
|
||||
for (int i = 3; i < dir_data.entry_count(); i++) {
|
||||
auto audio_data = read_entry(dir_data, wad_data, i);
|
||||
lg::info("File {}, total {:.2f} minutes", dir_data.entries.at(i).name, audio_len / 60.0);
|
||||
auto info = process_audio_file(audio_data, dir_data.entries.at(i).name, suffix);
|
||||
audio_len += info.length_seconds;
|
||||
filename_data[i][lang_id + 1] = info.filename;
|
||||
}
|
||||
}
|
||||
|
||||
nlohmann::json file_list;
|
||||
file_list["names"] = filename_data;
|
||||
file_list["languages"] = langs;
|
||||
|
||||
file_util::write_text_file(
|
||||
file_util::get_file_path({"assets", "streaming_audio", "file_list.txt"}), file_list.dump(2));
|
||||
}
|
||||
|
||||
} // namespace decompiler
|
||||
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace decompiler {
|
||||
void process_streamed_audio(const std::string& dir, const std::vector<std::string>& audio_files);
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "config.h"
|
||||
#include "common/util/FileUtil.h"
|
||||
#include "common/versions.h"
|
||||
#include "decompiler/data/streamed_audio.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
using namespace decompiler;
|
||||
@@ -119,6 +120,10 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!config.audio_dir_file_name.empty()) {
|
||||
process_streamed_audio(config.audio_dir_file_name, config.streamed_audio_file_names);
|
||||
}
|
||||
|
||||
lg::info("Disassembly has completed successfully.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user