1use std::fs::File;
2use std::io::{BufWriter, Seek, SeekFrom, Write};
3use std::path::Path;
4
5use std::collections::BTreeMap;
6
7use crate::reader::{S25Image, S25ImageMetadata};
8use crate::{Error, Result};
9
10pub struct S25Writer<A = File>
12where
13 A: Write,
14{
15 file: BufWriter<A>,
16 entries: BTreeMap<i32, S25ImageBuffer>,
17}
18
19#[derive(Clone)]
20struct S25ImageBuffer {
21 metadata: S25ImageMetadata,
22 compressed: (Vec<u32>, Vec<u8>),
23}
24
25impl S25Writer {
26 pub fn create<P: AsRef<Path>>(path: P) -> Result<Self> {
28 Ok(Self {
29 file: BufWriter::new(File::create(path)?),
30 entries: BTreeMap::new(),
31 })
32 }
33}
34
35impl<T> From<T> for S25Writer<T>
36where
37 T: Write,
38{
39 fn from(t: T) -> Self {
40 Self {
41 file: BufWriter::new(t),
42 entries: BTreeMap::new(),
43 }
44 }
45}
46
47use std::io::IntoInnerError;
48
49impl<T> S25Writer<T>
50where
51 T: Write + Seek,
52{
53 pub fn write(&mut self) -> Result<()> {
55 const S25_MAGIC: &[u8; 4] = b"S25\0";
56
57 let mut buf_table: Vec<Option<S25ImageBuffer>> = vec![];
58 let entries = std::mem::take(&mut self.entries);
59
60 for entries in entries {
61 let entry_no = entries.0 as usize;
62
63 if buf_table.len() <= entry_no as usize {
64 buf_table.resize(entry_no + 1, None);
65 }
66
67 buf_table[entry_no] = Some(entries.1);
68 }
69
70 let mut entry_table = vec![0i32; buf_table.len()];
71
72 self.file.seek(SeekFrom::Start(0))?;
74 self.file.write_all(S25_MAGIC)?;
75
76 self.file
78 .write_all(&(entry_table.len() as i32).to_le_bytes())?;
79
80 self.file
82 .seek(SeekFrom::Start(8 + entry_table.len() as u64 * 4))?;
83
84 for (i, entry) in buf_table.into_iter().enumerate() {
85 if let Some(entry) = entry {
86 let head = self.file.seek(SeekFrom::Current(0))?;
87 entry_table[i] = head as i32;
88
89 self.file.write_all(&entry.metadata.width.to_le_bytes())?;
91 self.file.write_all(&entry.metadata.height.to_le_bytes())?;
92 self.file
93 .write_all(&entry.metadata.offset_x.to_le_bytes())?;
94 self.file
95 .write_all(&entry.metadata.offset_y.to_le_bytes())?;
96 self.file.write_all(&(0i32).to_le_bytes())?;
97
98 let img_head = (head + 0x14) as u32;
100 for &offset in &entry.compressed.0 {
101 self.file.write_all(&(offset + img_head).to_le_bytes())?;
102 }
103
104 self.file.write_all(&entry.compressed.1)?;
105 }
106 }
107
108 self.file.seek(SeekFrom::Start(8))?;
110 for pos in entry_table {
111 self.file.write_all(&pos.to_le_bytes())?;
112 }
113
114 Ok(())
115 }
116
117 pub fn into_inner(self) -> std::result::Result<T, IntoInnerError<BufWriter<T>>> {
119 self.file.into_inner()
120 }
121}
122
123impl<T> S25Writer<T>
124where
125 T: Write,
126{
127 pub fn add_entry(&mut self, entry_no: i32, image: &S25Image) -> Result<()> {
129 let metadata = image.metadata.clone();
130
131 assert!(metadata.width >= 0);
132 assert!(metadata.height >= 0);
133
134 let compressed = compress_image(
135 &image.bgra_buffer,
136 metadata.width as usize,
137 metadata.height as usize,
138 )?;
139
140 self.entries.insert(
141 entry_no as i32,
142 S25ImageBuffer {
143 metadata,
144 compressed,
145 },
146 );
147
148 Ok(())
149 }
150}
151
152const METHOD_ABGR: u16 = 4;
155fn compress_image(rgba_image: &[u8], width: usize, height: usize) -> Result<(Vec<u32>, Vec<u8>)> {
158 use crate::utils;
159
160 assert_eq!(rgba_image.len(), width * height * 4);
161
162 let mut row_offsets = vec![0u32; height];
163
164 let mut compress_buf = vec![];
165 let mut row_buf = vec![];
166
167 let row_data_offset = row_offsets.len() * 4;
168
169 for y in 0..height {
170 row_offsets[y] = (row_data_offset + compress_buf.len()) as u32;
171
172 let head = y * width * 4;
173
174 if width <= 0x7ff {
176 utils::io::push_i16(
177 &mut row_buf,
178 encode_count(width as u16, METHOD_ABGR, 0) as i16,
179 );
180 } else if width <= 0xffffffff {
181 utils::io::push_i16(&mut row_buf, encode_count(0, METHOD_ABGR, 0) as i16);
183 utils::io::push_i32(&mut row_buf, width as i32);
184 } else {
185 return Err(Error::CompressionFailed);
186 }
187
188 for i in 0..width {
189 let offset = i * 4 + head;
190
191 row_buf.push(rgba_image[offset + 3]);
192 row_buf.push(rgba_image[offset + 2]);
193 row_buf.push(rgba_image[offset + 1]);
194 row_buf.push(rgba_image[offset + 0]);
195 }
196
197 if row_buf.len() <= 0x7fff {
199 utils::io::push_i16(&mut compress_buf, row_buf.len() as i16);
200 } else {
201 return Err(Error::CompressionFailed);
203 }
204
205 compress_buf.append(&mut row_buf);
207 }
208
209 Ok((row_offsets, compress_buf))
210}
211
212fn encode_count(count: u16, method: u16, skip: u16) -> u16 {
213 assert!(count <= 0x7ff);
214 count & 0x7ff | (method << 13) | ((skip & 0x03) << 11)
215}
216
217#[test]
218fn rewrite_susuko() {
219 use crate::S25Archive;
220
221 let mut writer = S25Writer::create("../test/susuko.s25").unwrap();
222 let mut image = S25Archive::open("../test/SUSUKO_01LL.S25").unwrap();
223
224 for i in 0..image.total_entries() {
225 if let Ok(image) = image.load_image(i) {
226 writer.add_entry(i as i32, &image).unwrap();
227 }
228 }
229
230 writer.write().unwrap();
231 drop(writer);
232
233 let mut image2 = S25Archive::open("../test/susuko.s25").unwrap();
234 for i in 0..image.total_entries() {
235 let meta1 = image.load_image(i);
236 let meta2 = image2.load_image(i);
237
238 if let (Ok(mut meta1), Ok(mut meta2)) = (meta1, meta2) {
239 meta1.metadata.head = 0;
240 meta2.metadata.head = 0;
241
242 assert_eq!(meta1.metadata, meta2.metadata, "metadata wrong at: {}", i);
243 assert_eq!(
244 meta1.bgra_buffer, meta2.bgra_buffer,
245 "decode wrong at: {}",
246 i
247 );
248 }
249 }
250}