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(
16 feature = "tracing",
17 tracing::instrument(level = "debug", skip(writer, wav))
18)]
19pub fn write_wav<W: Write>(writer: &mut W, wav: &Wav) -> Result<(), WavError> {
20 write_wav_with_options(writer, wav, WavWriteOptions::default())
21}
22
23#[cfg_attr(
33 feature = "tracing",
34 tracing::instrument(level = "debug", skip(writer, wav, options))
35)]
36pub fn write_wav_with_options<W: Write>(
37 writer: &mut W,
38 wav: &Wav,
39 options: WavWriteOptions,
40) -> Result<(), WavError> {
41 let clean_payload = match wav.audio_format {
42 WavAudioFormat::Wave => build_clean_wave_payload(wav)?,
43 WavAudioFormat::Mp3 => wav.data.clone(),
44 };
45
46 let out = match options.mode {
47 WavWriteMode::Clean => clean_payload,
48 WavWriteMode::Game => obfuscate_for_game(&clean_payload, wav.wav_type),
49 };
50 writer.write_all(&out)?;
51 Ok(())
52}
53
54#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip(wav)))]
60pub fn write_wav_to_vec(wav: &Wav) -> Result<Vec<u8>, WavError> {
61 write_wav_to_vec_with_options(wav, WavWriteOptions::default())
62}
63
64#[cfg_attr(
71 feature = "tracing",
72 tracing::instrument(level = "debug", skip(wav, options))
73)]
74pub fn write_wav_to_vec_with_options(
75 wav: &Wav,
76 options: WavWriteOptions,
77) -> Result<Vec<u8>, WavError> {
78 let mut out = Vec::new();
79 write_wav_with_options(&mut out, wav, options)?;
80 Ok(out)
81}
82
83fn build_clean_wave_payload(wav: &Wav) -> Result<Vec<u8>, WavError> {
84 let data_size = u32::try_from(wav.data.len())
85 .map_err(|_| WavError::InvalidChunk("audio payload exceeds 4GiB RIFF limit".into()))?;
86
87 let block_align = if wav.block_align != 0 {
88 wav.block_align
89 } else {
90 default_block_align(wav.channels, wav.bits_per_sample)?
91 };
92 let bytes_per_sec = if wav.bytes_per_sec != 0 {
93 wav.bytes_per_sec
94 } else {
95 wav.sample_rate
96 .checked_mul(u32::from(block_align))
97 .ok_or_else(|| {
98 WavError::InvalidChunk("sample_rate * block_align overflows u32".into())
99 })?
100 };
101
102 let fmt_chunk_size: u32 = 16;
103 let riff_size = 4_u32
104 .checked_add(8)
105 .and_then(|value| value.checked_add(fmt_chunk_size))
106 .and_then(|value| value.checked_add(8))
107 .and_then(|value| value.checked_add(data_size))
108 .ok_or_else(|| WavError::InvalidChunk("RIFF size overflows u32".into()))?;
109
110 let mut out = Vec::with_capacity(
111 usize::try_from(8_u64 + u64::from(riff_size))
112 .unwrap_or(0)
113 .max(usize::from(44_u16)),
114 );
115 out.extend_from_slice(&RIFF_MAGIC);
116 out.extend_from_slice(&riff_size.to_le_bytes());
117 out.extend_from_slice(&WAVE_MAGIC);
118 out.extend_from_slice(&FMT_CHUNK_ID);
119 out.extend_from_slice(&fmt_chunk_size.to_le_bytes());
120 out.extend_from_slice(&wav.encoding.raw().to_le_bytes());
121 out.extend_from_slice(&wav.channels.to_le_bytes());
122 out.extend_from_slice(&wav.sample_rate.to_le_bytes());
123 out.extend_from_slice(&bytes_per_sec.to_le_bytes());
124 out.extend_from_slice(&block_align.to_le_bytes());
125 out.extend_from_slice(&wav.bits_per_sample.to_le_bytes());
126 out.extend_from_slice(&DATA_CHUNK_ID);
127 out.extend_from_slice(&data_size.to_le_bytes());
128 out.extend_from_slice(&wav.data);
129
130 Ok(out)
131}
132
133fn default_block_align(channels: u16, bits_per_sample: u16) -> Result<u16, WavError> {
134 let bytes_per_sample = bits_per_sample / 8;
135 channels
136 .checked_mul(bytes_per_sample)
137 .ok_or_else(|| WavError::InvalidChunk("channels * bytes_per_sample overflows u16".into()))
138}
139
140fn obfuscate_for_game(clean_payload: &[u8], wav_type: WavType) -> Vec<u8> {
141 match wav_type {
144 WavType::Standard => clean_payload.to_vec(),
145 WavType::Sfx => {
146 let mut out = Vec::with_capacity(SFX_HEADER_SIZE + clean_payload.len());
152 out.resize(SFX_HEADER_SIZE, 0);
153 out[0..4].copy_from_slice(&SFX_MAGIC);
154 out.extend_from_slice(clean_payload);
155 out
156 }
157 WavType::Vo => {
158 let mut out = Vec::with_capacity(VO_HEADER_SIZE + clean_payload.len());
159 out.resize(VO_HEADER_SIZE, 0);
160 out[0..4].copy_from_slice(&RIFF_MAGIC);
161 out.extend_from_slice(clean_payload);
162 out
163 }
164 }
165}