mirror of
https://github.com/open-goal/jak-project.git
synced 2024-11-23 14:20:07 +00:00
decompiler: Extract Jak3 VAGs (#3328)
This commit is contained in:
parent
d4c21c784f
commit
3dc27e37e5
@ -56,11 +56,14 @@ void write_wave_file(const std::vector<s16>& left_samples,
|
||||
writer.write_to_file(name);
|
||||
}
|
||||
|
||||
std::pair<std::vector<s16>, std::vector<s16>> decode_adpcm(BinaryReader& reader, const bool mono) {
|
||||
std::pair<std::vector<s16>, std::vector<s16>> decode_adpcm(BinaryReader& reader,
|
||||
const bool stereo) {
|
||||
std::vector<s16> left_samples;
|
||||
std::vector<s16> right_samples;
|
||||
s32 left_sample_prev[2] = {0, 0};
|
||||
s32 right_sample_prev[2] = {0, 0};
|
||||
bool first_left = true;
|
||||
bool first_right = true;
|
||||
constexpr s32 f1[5] = {0, 60, 115, 98, 122};
|
||||
constexpr s32 f2[5] = {0, 0, -52, -55, -60};
|
||||
|
||||
@ -71,15 +74,29 @@ std::pair<std::vector<s16>, std::vector<s16>> decode_adpcm(BinaryReader& reader,
|
||||
// instead they are partitioned into contiguous 8kb (thats 8192 bytes) chunks
|
||||
// alternating left/right
|
||||
bool processing_left_chunk = true;
|
||||
// We need to skip the vag header for each channel
|
||||
if (first_left) {
|
||||
reader.ffwd(48);
|
||||
bytes_read += 48;
|
||||
first_left = false;
|
||||
}
|
||||
while (true) {
|
||||
if (!reader.bytes_left()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (bytes_read == 0x2000) {
|
||||
if (stereo && bytes_read == 0x2000) {
|
||||
// switch streams
|
||||
processing_left_chunk = !processing_left_chunk;
|
||||
bytes_read = 0;
|
||||
|
||||
// We need to skip the vag header for each channel
|
||||
if (first_right) {
|
||||
// skip the header
|
||||
reader.ffwd(48);
|
||||
bytes_read += 48;
|
||||
first_right = false;
|
||||
}
|
||||
}
|
||||
|
||||
u8 shift_filter = reader.read<u8>();
|
||||
@ -107,7 +124,7 @@ std::pair<std::vector<s16>, std::vector<s16>> decode_adpcm(BinaryReader& reader,
|
||||
s32 sample = (s32)(s16)(nibble << 12);
|
||||
sample >>= shift;
|
||||
|
||||
if (mono || processing_left_chunk) {
|
||||
if (!stereo || processing_left_chunk) {
|
||||
sample += (left_sample_prev[0] * f1[filter] + left_sample_prev[1] * f2[filter] + 32) / 64;
|
||||
|
||||
if (sample > 0x7fff) {
|
||||
|
@ -310,7 +310,15 @@
|
||||
// "audio_dir_file_name": "jak3/VAG",
|
||||
"audio_dir_file_name": "",
|
||||
|
||||
"streamed_audio_file_names": [],
|
||||
"streamed_audio_file_names": [
|
||||
"VAGWAD.ENG",
|
||||
"VAGWAD.FRE",
|
||||
"VAGWAD.GER",
|
||||
"VAGWAD.SPA",
|
||||
"VAGWAD.ITA",
|
||||
"VAGWAD.COM",
|
||||
"VAGWAD.INT"
|
||||
],
|
||||
|
||||
"levels_to_extract": [
|
||||
"LJKDMPK.DGO",
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include "third-party/json.hpp"
|
||||
|
||||
namespace decompiler {
|
||||
using std::string;
|
||||
|
||||
// number of bytes per "audio page" in the VAG directory file.
|
||||
constexpr int AUDIO_PAGE_SIZE = 2048;
|
||||
@ -27,75 +28,67 @@ uint32_t swap32(uint32_t in) {
|
||||
struct AudioDir {
|
||||
struct Entry {
|
||||
std::string name;
|
||||
bool stereo = false;
|
||||
bool international = false;
|
||||
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) {
|
||||
lg::debug("\"{}\" 0x{:07x} - 0x{:07x}", e.name, e.start_byte, e.end_byte);
|
||||
// lg::debug("\"{}\" 0x{:07x} - 0x{:07x}", 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 magic;
|
||||
u32 version;
|
||||
u32 zero;
|
||||
u32 channel_size;
|
||||
u32 size;
|
||||
u32 sample_rate;
|
||||
u32 z[3];
|
||||
char name[16];
|
||||
|
||||
VagFileHeader swapped_endian_jak1() const {
|
||||
VagFileHeader swap_endian() const {
|
||||
VagFileHeader result(*this);
|
||||
result.version = swap32(result.version);
|
||||
result.channel_size = swap32(result.channel_size);
|
||||
result.size = swap32(result.size);
|
||||
result.sample_rate = swap32(result.sample_rate);
|
||||
return result;
|
||||
}
|
||||
|
||||
VagFileHeader swapped_endian_jak2() const {
|
||||
VagFileHeader result(*this);
|
||||
result.version = swap32(result.version);
|
||||
result.channel_size = swap32(result.channel_size);
|
||||
// for some reason, the sample rate is big endian for jak2
|
||||
// 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';
|
||||
lg::debug("{}{}{}{} v {} zero {} chan {} samp {} z {} {} {} name {}", magic[0], magic[1],
|
||||
magic[2], magic[3], version, zero, channel_size, sample_rate, z[0], z[1], z[2],
|
||||
temp_name);
|
||||
lg::debug("{:x} v {} zero {} chan {} samp {} z {} {} {} name {}", magic, version, zero, size,
|
||||
sample_rate, z[0], z[1], z[2], temp_name);
|
||||
}
|
||||
};
|
||||
|
||||
static std::string unpack_vag_name_jak3(u64 compressed) {
|
||||
const char* char_map = " ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-";
|
||||
u32 chars = compressed & 0x1fffff;
|
||||
std::array<char, 9> buf;
|
||||
buf.fill(0);
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if (i == 4) {
|
||||
chars = (compressed >> 21) & 0x1fffff;
|
||||
}
|
||||
buf[7 - i] = char_map[chars % 38];
|
||||
chars /= 38;
|
||||
}
|
||||
|
||||
return {buf.data()};
|
||||
}
|
||||
|
||||
/*!
|
||||
* Read the DIR file into an AudioDir
|
||||
*/
|
||||
@ -103,9 +96,9 @@ AudioDir read_audio_dir(const decompiler::Config& config, const fs::path& path)
|
||||
auto data = file_util::read_binary_file(path);
|
||||
lg::info("Got {} bytes of audio dir.", data.size());
|
||||
auto reader = BinaryReader(data);
|
||||
u32 count = reader.read<u32>();
|
||||
AudioDir result;
|
||||
if (config.game_version == GameVersion::Jak1) {
|
||||
u32 count = reader.read<u32>();
|
||||
// matches the format in file.
|
||||
struct DirEntryJak1 {
|
||||
char name[8];
|
||||
@ -129,20 +122,15 @@ AudioDir read_audio_dir(const decompiler::Config& config, const fs::path& path)
|
||||
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);
|
||||
}
|
||||
} else if (config.game_version == GameVersion::Jak2) {
|
||||
u32 count = reader.read<u32>();
|
||||
// matches the format in file.
|
||||
struct DirEntryJak2 {
|
||||
char name[8];
|
||||
u32 value;
|
||||
// TODO - no idea what this is
|
||||
u32 boolean;
|
||||
u32 stereo;
|
||||
};
|
||||
u32 data_end = sizeof(u32) + sizeof(DirEntryJak2) * count;
|
||||
ASSERT(data_end <= data.size());
|
||||
@ -161,12 +149,45 @@ AudioDir read_audio_dir(const decompiler::Config& config, const fs::path& path)
|
||||
// padded with spaces, no null terminator.
|
||||
e.name.push_back(c);
|
||||
}
|
||||
e.stereo = entries[i].stereo;
|
||||
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);
|
||||
}
|
||||
} else if (config.game_version == GameVersion::Jak3) {
|
||||
struct VagDirJak3 {
|
||||
u32 id[2];
|
||||
u32 version;
|
||||
u32 count;
|
||||
} dir;
|
||||
struct DirEntryJak3 {
|
||||
union {
|
||||
u64 data;
|
||||
struct {
|
||||
u64 name : 42;
|
||||
bool stereo : 1;
|
||||
bool international : 1;
|
||||
u8 param : 4;
|
||||
u64 offset : 16;
|
||||
};
|
||||
};
|
||||
};
|
||||
dir = reader.read<VagDirJak3>();
|
||||
ASSERT(dir.id[0] == 0x41574756);
|
||||
ASSERT(dir.id[1] == 0x52494444);
|
||||
lg::warn("version {} count {}", dir.version, dir.count);
|
||||
|
||||
std::vector<DirEntryJak3> entries;
|
||||
|
||||
for (size_t i = 0; i < dir.count; i++) {
|
||||
entries.push_back(reader.read<DirEntryJak3>());
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < entries.size(); i++) {
|
||||
AudioDir::Entry e;
|
||||
e.name = unpack_vag_name_jak3(entries[i].name);
|
||||
e.stereo = entries[i].stereo;
|
||||
e.international = entries[i].international;
|
||||
e.start_byte = 0x8000 * entries[i].offset;
|
||||
result.entries.push_back(e);
|
||||
}
|
||||
} else {
|
||||
@ -189,27 +210,22 @@ struct AudioFileInfo {
|
||||
};
|
||||
|
||||
AudioFileInfo process_audio_file(const fs::path& output_folder,
|
||||
const std::vector<u8>& data,
|
||||
nonstd::span<const uint8_t> data,
|
||||
const std::string& name,
|
||||
const std::string& suffix) {
|
||||
const std::string& suffix,
|
||||
bool stereo) {
|
||||
BinaryReader reader(data);
|
||||
|
||||
auto header = reader.read<VagFileHeader>();
|
||||
if (header.magic[0] == 'V') {
|
||||
header = header.swapped_endian_jak1();
|
||||
} else if (header.magic[0] == 'p') {
|
||||
header = header.swapped_endian_jak2();
|
||||
} else {
|
||||
if (header.magic == 0x70474156 /* big endian (VAGp)*/) {
|
||||
header = header.swap_endian();
|
||||
} else if (header.magic != 0x56414770 /* little endian (pGAV) */) {
|
||||
ASSERT(false);
|
||||
}
|
||||
header.debug_print();
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
ASSERT(reader.read<u8>() == 0);
|
||||
}
|
||||
|
||||
const auto [left_samples, right_samples] =
|
||||
decode_adpcm(reader, !str_util::starts_with(std::string(header.name), "Stereo"));
|
||||
reader = BinaryReader(data.subspan(0, header.size));
|
||||
const auto [left_samples, right_samples] = decode_adpcm(reader, stereo);
|
||||
|
||||
while (reader.bytes_left()) {
|
||||
ASSERT(reader.read<u8>() == 0);
|
||||
@ -251,12 +267,17 @@ void process_streamed_audio(const decompiler::Config& config,
|
||||
auto& file = audio_files[lang_id];
|
||||
auto wad_data = file_util::read_binary_file(input_dir / "VAG" / file);
|
||||
auto suffix = fs::path(file).extension().u8string().substr(1);
|
||||
bool int_bank_p = suffix.compare("INT") == 0;
|
||||
langs.push_back(suffix);
|
||||
dir_data.set_file_size(wad_data.size());
|
||||
for (int i = 0; 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(output_path, audio_data, dir_data.entries.at(i).name, suffix);
|
||||
auto entry = dir_data.entries.at(i);
|
||||
if (entry.international != int_bank_p) {
|
||||
continue;
|
||||
}
|
||||
|
||||
lg::info("File {}, total {:.2f} minutes", entry.name, audio_len / 60.0);
|
||||
auto data = nonstd::span(wad_data).subspan(entry.start_byte);
|
||||
auto info = process_audio_file(output_path, data, entry.name, suffix, entry.stereo);
|
||||
audio_len += info.length_seconds;
|
||||
filename_data[i][lang_id + 1] = info.filename;
|
||||
}
|
||||
|
@ -1195,7 +1195,7 @@ static u32 ProcessVAGData(IsoMessage* _cmd, IsoBufferHeader* buffer_header) {
|
||||
if (vag->buffer_number == 0) {
|
||||
// first buffer, set stuff up
|
||||
u32* data = (u32*)buffer_header->data;
|
||||
if (data[0] != 0x70474156 /* 'pGAV' */ && data[0] != 0x56414770 /* 'VAGp' */) {
|
||||
if (data[0] != 0x70474156 /* 'VAGp' */ && data[0] != 0x56414770 /* 'pGAV' */) {
|
||||
vag->stop = true;
|
||||
buffer_header->data_size = 0;
|
||||
return CMD_STATUS_IN_PROGRESS;
|
||||
@ -1203,7 +1203,7 @@ static u32 ProcessVAGData(IsoMessage* _cmd, IsoBufferHeader* buffer_header) {
|
||||
|
||||
vag->sample_rate = data[4];
|
||||
vag->data_left = data[3];
|
||||
if (data[0] == 0x70474156 /* 'pGAV' */) {
|
||||
if (data[0] == 0x70474156 /* 'VAGp' */) {
|
||||
vag->sample_rate = bswap(vag->sample_rate);
|
||||
vag->data_left = bswap(vag->data_left);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user