rakata_formats/wav/
writer.rs1use std::io::Write;
4
5use super::{
6 Wav, WavAudioFormat, WavError, WavType, WavWriteMode, WavWriteOptions, DATA_CHUNK_ID,
7 FMT_CHUNK_ID, RIFF_MAGIC, SFX_HEADER_SIZE, SFX_MAGIC, VO_HEADER_SIZE, WAVE_MAGIC,
8};
9
10#[cfg_attr(
12 feature = "tracing",
13 tracing::instrument(level = "debug", skip(writer, wav))
14)]
15pub fn write_wav<W: Write>(writer: &mut W, wav: &Wav) -> Result<(), WavError> {
16 write_wav_with_options(writer, wav, WavWriteOptions::default())
17}
18
19#[cfg_attr(
21 feature = "tracing",
22 tracing::instrument(level = "debug", skip(writer, wav, options))
23)]
24pub fn write_wav_with_options<W: Write>(
25 writer: &mut W,
26 wav: &Wav,
27 options: WavWriteOptions,
28) -> Result<(), WavError> {
29 let clean_payload = match wav.audio_format {
30 WavAudioFormat::Wave => build_clean_wave_payload(wav)?,
31 WavAudioFormat::Mp3 => wav.data.clone(),
32 };
33
34 let out = match options.mode {
35 WavWriteMode::Clean => clean_payload,
36 WavWriteMode::Game => obfuscate_for_game(&clean_payload, wav.wav_type),
37 };
38 writer.write_all(&out)?;
39 Ok(())
40}
41
42#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip(wav)))]
44pub fn write_wav_to_vec(wav: &Wav) -> Result<Vec<u8>, WavError> {
45 write_wav_to_vec_with_options(wav, WavWriteOptions::default())
46}
47
48#[cfg_attr(
50 feature = "tracing",
51 tracing::instrument(level = "debug", skip(wav, options))
52)]
53pub fn write_wav_to_vec_with_options(
54 wav: &Wav,
55 options: WavWriteOptions,
56) -> Result<Vec<u8>, WavError> {
57 let mut out = Vec::new();
58 write_wav_with_options(&mut out, wav, options)?;
59 Ok(out)
60}
61
62fn build_clean_wave_payload(wav: &Wav) -> Result<Vec<u8>, WavError> {
63 let data_size = u32::try_from(wav.data.len())
64 .map_err(|_| WavError::InvalidChunk("audio payload exceeds 4GiB RIFF limit".into()))?;
65
66 let block_align = if wav.block_align != 0 {
67 wav.block_align
68 } else {
69 default_block_align(wav.channels, wav.bits_per_sample)?
70 };
71 let bytes_per_sec = if wav.bytes_per_sec != 0 {
72 wav.bytes_per_sec
73 } else {
74 wav.sample_rate
75 .checked_mul(u32::from(block_align))
76 .ok_or_else(|| {
77 WavError::InvalidChunk("sample_rate * block_align overflows u32".into())
78 })?
79 };
80
81 let fmt_chunk_size: u32 = 16;
82 let riff_size = 4_u32
83 .checked_add(8)
84 .and_then(|value| value.checked_add(fmt_chunk_size))
85 .and_then(|value| value.checked_add(8))
86 .and_then(|value| value.checked_add(data_size))
87 .ok_or_else(|| WavError::InvalidChunk("RIFF size overflows u32".into()))?;
88
89 let mut out = Vec::with_capacity(
90 usize::try_from(8_u64 + u64::from(riff_size))
91 .unwrap_or(0)
92 .max(usize::from(44_u16)),
93 );
94 out.extend_from_slice(&RIFF_MAGIC);
95 out.extend_from_slice(&riff_size.to_le_bytes());
96 out.extend_from_slice(&WAVE_MAGIC);
97 out.extend_from_slice(&FMT_CHUNK_ID);
98 out.extend_from_slice(&fmt_chunk_size.to_le_bytes());
99 out.extend_from_slice(&wav.encoding.raw().to_le_bytes());
100 out.extend_from_slice(&wav.channels.to_le_bytes());
101 out.extend_from_slice(&wav.sample_rate.to_le_bytes());
102 out.extend_from_slice(&bytes_per_sec.to_le_bytes());
103 out.extend_from_slice(&block_align.to_le_bytes());
104 out.extend_from_slice(&wav.bits_per_sample.to_le_bytes());
105 out.extend_from_slice(&DATA_CHUNK_ID);
106 out.extend_from_slice(&data_size.to_le_bytes());
107 out.extend_from_slice(&wav.data);
108
109 Ok(out)
110}
111
112fn default_block_align(channels: u16, bits_per_sample: u16) -> Result<u16, WavError> {
113 let bytes_per_sample = bits_per_sample / 8;
114 channels
115 .checked_mul(bytes_per_sample)
116 .ok_or_else(|| WavError::InvalidChunk("channels * bytes_per_sample overflows u16".into()))
117}
118
119fn obfuscate_for_game(clean_payload: &[u8], wav_type: WavType) -> Vec<u8> {
120 match wav_type {
123 WavType::Standard => clean_payload.to_vec(),
124 WavType::Sfx => {
125 let mut out = Vec::with_capacity(SFX_HEADER_SIZE + clean_payload.len());
126 out.resize(SFX_HEADER_SIZE, 0);
127 out[0..4].copy_from_slice(&SFX_MAGIC);
128 out.extend_from_slice(clean_payload);
129 out
130 }
131 WavType::Vo => {
132 let mut out = Vec::with_capacity(VO_HEADER_SIZE + clean_payload.len());
133 out.resize(VO_HEADER_SIZE, 0);
134 out[0..4].copy_from_slice(&RIFF_MAGIC);
135 out.extend_from_slice(clean_payload);
136 out
137 }
138 }
139}