1use crate::checksum::{boot_sum, normal_sum, read_i32_be, read_u32_be, read_u32_be_slice};
4use crate::constants::*;
5use crate::date::AmigaDate;
6use crate::error::{AffsError, Result};
7use crate::types::{EntryType, FsFlags, FsType};
8
9#[derive(Debug, Clone)]
11pub struct BootBlock {
12 pub dos_type: [u8; 4],
14 pub checksum: u32,
16 pub root_block: u32,
18}
19
20impl BootBlock {
21 pub fn parse(buf: &[u8; BOOT_BLOCK_SIZE]) -> Result<Self> {
23 let dos_type = [buf[0], buf[1], buf[2], buf[3]];
24
25 if &dos_type[0..3] != b"DOS" {
27 return Err(AffsError::InvalidDosType);
28 }
29
30 let checksum = read_u32_be_slice(buf, 4);
31 let root_block = read_u32_be_slice(buf, 8);
32
33 if buf[12] != 0 {
35 let calculated = boot_sum(buf);
36 if checksum != calculated {
37 return Err(AffsError::ChecksumMismatch);
38 }
39 }
40
41 Ok(Self {
42 dos_type,
43 checksum,
44 root_block,
45 })
46 }
47
48 #[inline]
50 pub const fn fs_type(&self) -> FsType {
51 if (self.dos_type[3] & DOSFS_FFS) != 0 {
52 FsType::Ffs
53 } else {
54 FsType::Ofs
55 }
56 }
57
58 #[inline]
60 pub const fn fs_flags(&self) -> FsFlags {
61 FsFlags::from_dos_type(self.dos_type[3])
62 }
63}
64
65#[derive(Debug, Clone)]
67pub struct RootBlock {
68 pub block_type: i32,
70 pub hash_table_size: i32,
72 pub checksum: u32,
74 pub hash_table: [u32; HASH_TABLE_SIZE],
76 pub bm_flag: i32,
78 pub bm_pages: [u32; BM_PAGES_ROOT_SIZE],
80 pub bm_ext: u32,
82 pub creation_date: AmigaDate,
84 pub name_len: u8,
86 pub disk_name: [u8; MAX_NAME_LEN],
88 pub last_modified: AmigaDate,
90 pub extension: u32,
92 pub sec_type: i32,
94}
95
96impl RootBlock {
97 pub fn parse(buf: &[u8; BLOCK_SIZE]) -> Result<Self> {
99 let block_type = read_i32_be(buf, 0);
100 if block_type != T_HEADER {
101 return Err(AffsError::InvalidBlockType);
102 }
103
104 let sec_type = read_i32_be(buf, 508);
105 if sec_type != ST_ROOT {
106 return Err(AffsError::InvalidSecType);
107 }
108
109 let checksum = read_u32_be(buf, 20);
110 let calculated = normal_sum(buf, 20);
111 if checksum != calculated {
112 return Err(AffsError::ChecksumMismatch);
113 }
114
115 let hash_table_size = read_i32_be(buf, 12);
116
117 let mut hash_table = [0u32; HASH_TABLE_SIZE];
118 for (i, entry) in hash_table.iter_mut().enumerate() {
119 *entry = read_u32_be(buf, 24 + i * 4);
120 }
121
122 let bm_flag = read_i32_be(buf, 0x138);
123
124 let mut bm_pages = [0u32; BM_PAGES_ROOT_SIZE];
125 for (i, page) in bm_pages.iter_mut().enumerate() {
126 *page = read_u32_be(buf, 0x13C + i * 4);
127 }
128
129 let bm_ext = read_u32_be(buf, 0x1A0);
130
131 let creation_date = AmigaDate::new(
132 read_i32_be(buf, 0x1A4),
133 read_i32_be(buf, 0x1A8),
134 read_i32_be(buf, 0x1AC),
135 );
136
137 let name_len = buf[0x1B0].min(MAX_NAME_LEN as u8);
138 let mut disk_name = [0u8; MAX_NAME_LEN];
139 disk_name[..name_len as usize].copy_from_slice(&buf[0x1B1..0x1B1 + name_len as usize]);
140
141 let last_modified = AmigaDate::new(
142 read_i32_be(buf, 0x1D8),
143 read_i32_be(buf, 0x1DC),
144 read_i32_be(buf, 0x1E0),
145 );
146
147 let extension = read_u32_be(buf, 0x1F8);
148
149 Ok(Self {
150 block_type,
151 hash_table_size,
152 checksum,
153 hash_table,
154 bm_flag,
155 bm_pages,
156 bm_ext,
157 creation_date,
158 name_len,
159 disk_name,
160 last_modified,
161 extension,
162 sec_type,
163 })
164 }
165
166 #[inline]
168 pub fn name(&self) -> &[u8] {
169 &self.disk_name[..self.name_len as usize]
170 }
171
172 #[inline]
174 pub const fn bitmap_valid(&self) -> bool {
175 self.bm_flag == BM_VALID
176 }
177}
178
179#[derive(Debug, Clone)]
181pub struct EntryBlock {
182 pub block_type: i32,
184 pub header_key: u32,
186 pub high_seq: i32,
188 pub first_data: u32,
190 pub checksum: u32,
192 pub hash_table: [u32; HASH_TABLE_SIZE],
194 pub access: u32,
196 pub byte_size: u32,
198 pub comment_len: u8,
200 pub comment: [u8; MAX_COMMENT_LEN],
202 pub date: AmigaDate,
204 pub name_len: u8,
206 pub name: [u8; MAX_NAME_LEN],
208 pub real_entry: u32,
210 pub next_link: u32,
212 pub next_same_hash: u32,
214 pub parent: u32,
216 pub extension: u32,
218 pub sec_type: i32,
220}
221
222impl EntryBlock {
223 pub fn parse(buf: &[u8; BLOCK_SIZE]) -> Result<Self> {
225 let block_type = read_i32_be(buf, 0);
226 if block_type != T_HEADER {
227 return Err(AffsError::InvalidBlockType);
228 }
229
230 let checksum = read_u32_be(buf, 20);
231 let calculated = normal_sum(buf, 20);
232 if checksum != calculated {
233 return Err(AffsError::ChecksumMismatch);
234 }
235
236 let header_key = read_u32_be(buf, 4);
237 let high_seq = read_i32_be(buf, 8);
238 let first_data = read_u32_be(buf, 16);
239
240 let mut hash_table = [0u32; HASH_TABLE_SIZE];
241 for (i, entry) in hash_table.iter_mut().enumerate() {
242 *entry = read_u32_be(buf, 24 + i * 4);
243 }
244
245 let access = read_u32_be(buf, 0x140);
246 let byte_size = read_u32_be(buf, 0x144);
247
248 let comment_len = buf[0x148].min(MAX_COMMENT_LEN as u8);
249 let mut comment = [0u8; MAX_COMMENT_LEN];
250 comment[..comment_len as usize].copy_from_slice(&buf[0x149..0x149 + comment_len as usize]);
251
252 let date = AmigaDate::new(
253 read_i32_be(buf, 0x1A4),
254 read_i32_be(buf, 0x1A8),
255 read_i32_be(buf, 0x1AC),
256 );
257
258 let name_len = buf[0x1B0].min(MAX_NAME_LEN as u8);
259 let mut name = [0u8; MAX_NAME_LEN];
260 name[..name_len as usize].copy_from_slice(&buf[0x1B1..0x1B1 + name_len as usize]);
261
262 let real_entry = read_u32_be(buf, 0x1D4);
263 let next_link = read_u32_be(buf, 0x1D8);
264 let next_same_hash = read_u32_be(buf, 0x1F0);
265 let parent = read_u32_be(buf, 0x1F4);
266 let extension = read_u32_be(buf, 0x1F8);
267 let sec_type = read_i32_be(buf, 0x1FC);
268
269 Ok(Self {
270 block_type,
271 header_key,
272 high_seq,
273 first_data,
274 checksum,
275 hash_table,
276 access,
277 byte_size,
278 comment_len,
279 comment,
280 date,
281 name_len,
282 name,
283 real_entry,
284 next_link,
285 next_same_hash,
286 parent,
287 extension,
288 sec_type,
289 })
290 }
291
292 #[inline]
294 pub fn name(&self) -> &[u8] {
295 &self.name[..self.name_len as usize]
296 }
297
298 #[inline]
300 pub fn comment(&self) -> &[u8] {
301 &self.comment[..self.comment_len as usize]
302 }
303
304 #[inline]
306 pub fn entry_type(&self) -> Option<EntryType> {
307 EntryType::from_sec_type(self.sec_type)
308 }
309
310 #[inline]
312 pub const fn is_dir(&self) -> bool {
313 self.sec_type == ST_DIR || self.sec_type == ST_LDIR
314 }
315
316 #[inline]
318 pub const fn is_file(&self) -> bool {
319 self.sec_type == ST_FILE || self.sec_type == ST_LFILE
320 }
321
322 #[inline]
325 pub const fn data_block(&self, index: usize) -> u32 {
326 if index < MAX_DATABLK {
327 self.hash_table[MAX_DATABLK - 1 - index]
329 } else {
330 0
331 }
332 }
333}
334
335#[derive(Debug, Clone)]
337pub struct FileExtBlock {
338 pub block_type: i32,
340 pub header_key: u32,
342 pub high_seq: i32,
344 pub checksum: u32,
346 pub data_blocks: [u32; MAX_DATABLK],
348 pub parent: u32,
350 pub extension: u32,
352 pub sec_type: i32,
354}
355
356impl FileExtBlock {
357 pub fn parse(buf: &[u8; BLOCK_SIZE]) -> Result<Self> {
359 let block_type = read_i32_be(buf, 0);
360 if block_type != T_LIST {
361 return Err(AffsError::InvalidBlockType);
362 }
363
364 let checksum = read_u32_be(buf, 20);
365 let calculated = normal_sum(buf, 20);
366 if checksum != calculated {
367 return Err(AffsError::ChecksumMismatch);
368 }
369
370 let header_key = read_u32_be(buf, 4);
371 let high_seq = read_i32_be(buf, 8);
372
373 let mut data_blocks = [0u32; MAX_DATABLK];
374 for (i, block) in data_blocks.iter_mut().enumerate() {
375 *block = read_u32_be(buf, 24 + i * 4);
376 }
377
378 let parent = read_u32_be(buf, 0x1F4);
379 let extension = read_u32_be(buf, 0x1F8);
380 let sec_type = read_i32_be(buf, 0x1FC);
381
382 Ok(Self {
383 block_type,
384 header_key,
385 high_seq,
386 checksum,
387 data_blocks,
388 parent,
389 extension,
390 sec_type,
391 })
392 }
393
394 #[inline]
396 pub const fn data_block(&self, index: usize) -> u32 {
397 if index < MAX_DATABLK {
398 self.data_blocks[MAX_DATABLK - 1 - index]
400 } else {
401 0
402 }
403 }
404}
405
406#[derive(Debug, Clone, Copy)]
408pub struct OfsDataBlock {
409 pub block_type: i32,
411 pub header_key: u32,
413 pub seq_num: u32,
415 pub data_size: u32,
417 pub next_data: u32,
419 pub checksum: u32,
421}
422
423impl OfsDataBlock {
424 pub const HEADER_SIZE: usize = 24;
426
427 pub fn parse(buf: &[u8; BLOCK_SIZE]) -> Result<Self> {
429 let block_type = read_i32_be(buf, 0);
430 if block_type != T_DATA {
431 return Err(AffsError::InvalidBlockType);
432 }
433
434 let checksum = read_u32_be(buf, 20);
435 let calculated = normal_sum(buf, 20);
436 if checksum != calculated {
437 return Err(AffsError::ChecksumMismatch);
438 }
439
440 Ok(Self {
441 block_type,
442 header_key: read_u32_be(buf, 4),
443 seq_num: read_u32_be(buf, 8),
444 data_size: read_u32_be(buf, 12),
445 next_data: read_u32_be(buf, 16),
446 checksum,
447 })
448 }
449
450 #[inline]
452 pub fn data(buf: &[u8; BLOCK_SIZE]) -> &[u8] {
453 &buf[Self::HEADER_SIZE..]
454 }
455}
456
457#[inline]
461pub fn hash_name(name: &[u8], intl: bool) -> usize {
462 let mut hash = name.len() as u32;
463 for &c in name {
464 let upper = if intl {
465 intl_to_upper(c)
466 } else {
467 c.to_ascii_uppercase()
468 };
469 hash = (hash.wrapping_mul(13).wrapping_add(upper as u32)) & 0x7FF;
470 }
471 (hash % HASH_TABLE_SIZE as u32) as usize
472}
473
474#[inline]
476const fn intl_to_upper(c: u8) -> u8 {
477 if (c >= b'a' && c <= b'z') || (c >= 224 && c <= 254 && c != 247) {
478 c - (b'a' - b'A')
479 } else {
480 c
481 }
482}
483
484#[inline]
486pub fn names_equal(a: &[u8], b: &[u8], intl: bool) -> bool {
487 if a.len() != b.len() {
488 return false;
489 }
490 for (&ca, &cb) in a.iter().zip(b.iter()) {
491 let ua = if intl {
492 intl_to_upper(ca)
493 } else {
494 ca.to_ascii_uppercase()
495 };
496 let ub = if intl {
497 intl_to_upper(cb)
498 } else {
499 cb.to_ascii_uppercase()
500 };
501 if ua != ub {
502 return false;
503 }
504 }
505 true
506}
507
508#[cfg(test)]
509mod tests {
510 use super::*;
511
512 #[test]
513 fn test_hash_name() {
514 assert!(hash_name(b"test", false) < HASH_TABLE_SIZE);
516 assert!(hash_name(b"", false) == 0);
517 }
518
519 #[test]
520 fn test_intl_to_upper() {
521 assert_eq!(intl_to_upper(b'a'), b'A');
522 assert_eq!(intl_to_upper(b'z'), b'Z');
523 assert_eq!(intl_to_upper(b'A'), b'A');
524 assert_eq!(intl_to_upper(224), 192); }
526
527 #[test]
528 fn test_names_equal() {
529 assert!(names_equal(b"Test", b"test", false));
530 assert!(names_equal(b"TEST", b"test", false));
531 assert!(!names_equal(b"Test", b"test2", false));
532 }
533}