use std::io::{Write, Seek, SeekFrom, Cursor};
use indexmap::IndexMap;
use byteorder::{LittleEndian, WriteBytesExt};
use crate::error::DxfError;
use crate::types::DxfVersion;
use super::section_definition::{names, PAGE_TYPE_SECTION_MAP, PAGE_TYPE_SECTION_PAGE_MAP};
use super::section_descriptor::{DwgSectionDescriptor, DwgLocalSectionMap};
use crate::io::dwg::checksum::{adler32_checksum, compression_padding, magic_sequence, apply_mask, apply_magic_sequence};
use crate::io::dwg::compression::DwgLZ77AC18Compressor;
use crate::io::dwg::crc;
const FILE_HEADER_SIZE: usize = 0x100;
pub const DEFAULT_DECOMP_SIZE: usize = 0x7400;
const INNER_HEADER_ID: &[u8; 12] = b"AcFssFcAJMB\0";
pub struct DwgFileHeaderWriterAC18 {
version: DxfVersion,
maintenance_version: u8,
descriptors: IndexMap<String, DwgSectionDescriptor>,
local_section_maps: Vec<DwgLocalSectionMap>,
compressor: DwgLZ77AC18Compressor,
next_section_id: i32,
section_array_page_size: u32,
section_page_map_id: u32,
section_map_id: u32,
last_page_id: i32,
last_section_addr: u64,
second_header_addr: u64,
section_amount: u32,
page_map_address: u64,
gap_amount: u32,
}
impl DwgFileHeaderWriterAC18 {
pub fn new<W: Write + Seek>(version: DxfVersion, maintenance_version: u8, output: &mut W) -> Result<Self, DxfError> {
let zeroes = [0u8; FILE_HEADER_SIZE];
output.write_all(&zeroes)?;
Ok(Self {
version,
maintenance_version,
descriptors: IndexMap::new(),
local_section_maps: Vec::new(),
compressor: DwgLZ77AC18Compressor::new(),
next_section_id: 0,
section_array_page_size: 0,
section_page_map_id: 0,
section_map_id: 0,
last_page_id: 0,
last_section_addr: 0,
second_header_addr: 0,
section_amount: 0,
page_map_address: 0,
gap_amount: 0,
})
}
pub fn handle_section_offset(&self) -> usize {
0
}
pub fn add_section<W: Write + Seek>(
&mut self,
output: &mut W,
name: &str,
data: &[u8],
compressed: bool,
decomp_size: usize,
) -> Result<(), DxfError> {
let mut descriptor = DwgSectionDescriptor::new(name);
descriptor.decompressed_size = decomp_size as u64;
descriptor.compressed_size = data.len() as u64;
descriptor.compressed_code = if compressed { 2 } else { 1 };
descriptor.section_id = self.next_section_id;
self.next_section_id += 1;
let n_full_pages = data.len() / decomp_size;
let mut offset: usize = 0;
for _ in 0..n_full_pages {
self.create_local_section(
output,
&mut descriptor,
data,
decomp_size,
offset,
decomp_size,
compressed,
)?;
offset += decomp_size;
}
let remainder = data.len() % decomp_size;
if remainder > 0 && !is_all_zeros(&data[offset..]) {
self.create_local_section(
output,
&mut descriptor,
data,
decomp_size,
offset,
remainder,
compressed,
)?;
}
self.descriptors.insert(name.to_string(), descriptor);
Ok(())
}
pub fn write_file<W: Write + Seek>(&mut self, output: &mut W) -> Result<(), DxfError> {
self.section_array_page_size = (self.local_section_maps.len() + 2) as u32;
self.section_page_map_id = self.section_array_page_size;
self.section_map_id = self.section_array_page_size - 1;
self.write_descriptors(output)?;
self.write_records(output)?;
self.write_file_metadata(output)?;
Ok(())
}
fn create_local_section<W: Write + Seek>(
&mut self,
output: &mut W,
descriptor: &mut DwgSectionDescriptor,
data: &[u8],
decomp_size: usize,
offset: usize,
total_size: usize,
compressed: bool,
) -> Result<(), DxfError> {
let compressed_data = self.apply_compression(data, decomp_size, offset, total_size, compressed)?;
self.write_magic_number(output)?;
let position = output.seek(SeekFrom::Current(0))? as i64;
let mut local_map = DwgLocalSectionMap::new();
local_map.offset = offset as u64;
local_map.seeker = position;
local_map.page_number = self.local_section_maps.len() as i32 + 1;
local_map.oda = adler32_checksum(0, &compressed_data);
let compress_diff = compression_padding(compressed_data.len());
local_map.compressed_size = compressed_data.len() as u64;
local_map.decompressed_size = total_size as u64;
local_map.page_size = local_map.compressed_size as i64 + 32 + compress_diff as i64;
local_map.checksum = 0;
let mut checksum_buf = Vec::with_capacity(32);
Self::write_data_section_header(
&mut checksum_buf,
descriptor,
&local_map,
descriptor.page_type,
)?;
local_map.checksum = adler32_checksum(local_map.oda, &checksum_buf);
checksum_buf.clear();
Self::write_data_section_header(
&mut checksum_buf,
descriptor,
&local_map,
descriptor.page_type,
)?;
apply_mask(&mut checksum_buf, position as u64);
output.write_all(&checksum_buf)?;
output.write_all(&compressed_data)?;
if compressed {
let magic = magic_sequence();
output.write_all(&magic[..compress_diff])?;
} else if compress_diff != 0 {
return Err(DxfError::InvalidFormat(
"Uncompressed page has non-zero compression padding".into(),
));
}
if local_map.page_number > 0 {
descriptor.page_count += 1;
}
local_map.size = output.seek(SeekFrom::Current(0))? as i64 - position;
descriptor.local_sections.push(local_map.clone());
self.local_section_maps.push(local_map);
Ok(())
}
fn apply_compression(
&mut self,
data: &[u8],
decomp_size: usize,
offset: usize,
total_size: usize,
compressed: bool,
) -> Result<Vec<u8>, DxfError> {
if compressed {
let mut padded = vec![0u8; decomp_size];
let copy_len = total_size.min(data.len() - offset);
padded[..copy_len].copy_from_slice(&data[offset..offset + copy_len]);
let compressed_out = self.compressor.compress(&padded, 0, decomp_size);
Ok(compressed_out)
} else {
let mut result = vec![0u8; decomp_size];
let copy_len = total_size.min(data.len() - offset);
result[..copy_len].copy_from_slice(&data[offset..offset + copy_len]);
Ok(result)
}
}
fn write_data_section_header(
buf: &mut Vec<u8>,
descriptor: &DwgSectionDescriptor,
map: &DwgLocalSectionMap,
page_type: i32,
) -> Result<(), DxfError> {
buf.write_i32::<LittleEndian>(page_type)?;
buf.write_i32::<LittleEndian>(descriptor.section_id)?;
buf.write_i32::<LittleEndian>(map.compressed_size as i32)?;
buf.write_i32::<LittleEndian>(map.page_size as i32)?;
buf.write_i64::<LittleEndian>(map.offset as i64)?;
buf.write_u32::<LittleEndian>(map.checksum)?;
buf.write_u32::<LittleEndian>(map.oda)?;
debug_assert_eq!(buf.len() % 32, 0, "Data section header must be 32 bytes");
Ok(())
}
fn write_descriptors<W: Write + Seek>(&mut self, output: &mut W) -> Result<(), DxfError> {
let mut stream = Vec::new();
stream.write_i32::<LittleEndian>(self.descriptors.len() as i32)?;
stream.write_i32::<LittleEndian>(2)?;
stream.write_i32::<LittleEndian>(0x7400)?;
stream.write_i32::<LittleEndian>(0)?;
stream.write_i32::<LittleEndian>(self.descriptors.len() as i32)?;
for descriptor in self.descriptors.values() {
stream.write_u64::<LittleEndian>(descriptor.compressed_size)?;
stream.write_i32::<LittleEndian>(descriptor.page_count)?;
stream.write_i32::<LittleEndian>(descriptor.decompressed_size as i32)?;
stream.write_i32::<LittleEndian>(1)?;
stream.write_i32::<LittleEndian>(descriptor.compressed_code)?;
stream.write_i32::<LittleEndian>(descriptor.section_id)?;
stream.write_i32::<LittleEndian>(descriptor.encrypted)?;
let mut name_buf = [0u8; 64];
let name_bytes = descriptor.name.as_bytes();
let copy_len = name_bytes.len().min(64);
name_buf[..copy_len].copy_from_slice(&name_bytes[..copy_len]);
stream.write_all(&name_buf)?;
for local_map in &descriptor.local_sections {
if local_map.page_number > 0 {
stream.write_i32::<LittleEndian>(local_map.page_number)?;
stream.write_i32::<LittleEndian>(local_map.compressed_size as i32)?;
stream.write_u64::<LittleEndian>(local_map.offset)?;
}
}
}
let section_holder = self.set_seeker(output, PAGE_TYPE_SECTION_MAP, &stream)?;
let padding = compression_padding(
(output.seek(SeekFrom::Current(0))? as i64 - section_holder.seeker) as usize,
);
let magic = magic_sequence();
output.write_all(&magic[..padding])?;
let mut holder = section_holder;
holder.size = output.seek(SeekFrom::Current(0))? as i64 - holder.seeker;
self.add_local_section(holder);
Ok(())
}
fn write_records<W: Write + Seek>(&mut self, output: &mut W) -> Result<(), DxfError> {
self.write_magic_number(output)?;
let mut section = DwgLocalSectionMap::with_section_map(PAGE_TYPE_SECTION_PAGE_MAP);
self.add_local_section(section.clone());
let counter = self.local_section_maps.len() * 8;
section.seeker = output.seek(SeekFrom::Current(0))? as i64;
let size = counter + compression_padding(counter);
section.size = size as i64;
if let Some(last_entry) = self.local_section_maps.last_mut() {
last_entry.seeker = section.seeker;
last_entry.size = section.size;
}
let mut stream = Vec::new();
for item in &self.local_section_maps {
stream.write_i32::<LittleEndian>(item.page_number)?;
stream.write_i32::<LittleEndian>(item.size as i32)?;
}
self.compress_checksum(output, &mut section, &stream)?;
let last = self.local_section_maps.last().unwrap().clone();
self.gap_amount = 0;
self.last_page_id = last.page_number;
self.last_section_addr = ((last.seeker as u64) + (size as u64)).saturating_sub(256);
self.section_amount = (self.local_section_maps.len() - 1) as u32;
self.page_map_address = section.seeker as u64;
Ok(())
}
fn write_file_metadata<W: Write + Seek>(&mut self, output: &mut W) -> Result<(), DxfError> {
self.second_header_addr = output.seek(SeekFrom::Current(0))?;
let inner_header = self.build_inner_file_header()?;
output.write_all(&inner_header)?;
output.seek(SeekFrom::Start(0))?;
let version_str = self.version.as_str();
output.write_all(version_str.as_bytes())?;
output.write_all(&[0u8; 5])?;
output.write_all(&[self.maintenance_version])?;
output.write_all(&[3u8])?;
let preview_addr = self.descriptors.get(names::PREVIEW)
.and_then(|d| d.local_sections.first())
.map_or(0u32, |s| (s.seeker as u32) + 0x20);
output.write_u32::<LittleEndian>(preview_addr)?;
output.write_all(&[33u8])?;
output.write_all(&[self.maintenance_version])?;
output.write_u16::<LittleEndian>(30)?;
output.write_all(&[0u8; 3])?;
output.write_i32::<LittleEndian>(0)?;
output.write_i32::<LittleEndian>(0)?;
let summary_addr = self.descriptors.get(names::SUMMARY_INFO)
.and_then(|d| d.local_sections.first())
.map_or(0u32, |s| (s.seeker as u32) + 0x20);
output.write_u32::<LittleEndian>(summary_addr)?;
output.write_u32::<LittleEndian>(0)?;
output.write_i32::<LittleEndian>(0x80)?;
let app_info_addr = self.descriptors.get(names::APP_INFO)
.and_then(|d| d.local_sections.first())
.map_or(0u32, |s| (s.seeker as u32) + 0x20);
output.write_u32::<LittleEndian>(app_info_addr)?;
output.write_all(&[0u8; 80])?;
output.write_all(&inner_header)?;
let magic = magic_sequence();
output.write_all(&magic[236..256])?;
output.seek(SeekFrom::End(0))?;
Ok(())
}
fn build_inner_file_header(&self) -> Result<Vec<u8>, DxfError> {
let mut buf = vec![0u8; 0x6C];
{
let mut cursor = Cursor::new(&mut buf[..]);
cursor.write_all(INNER_HEADER_ID)?;
cursor.write_i32::<LittleEndian>(0)?;
cursor.write_i32::<LittleEndian>(0x6C)?;
cursor.write_i32::<LittleEndian>(0x04)?;
cursor.write_i32::<LittleEndian>(0)?;
cursor.write_i32::<LittleEndian>(0)?;
cursor.write_i32::<LittleEndian>(0)?;
cursor.write_i32::<LittleEndian>(1)?;
cursor.write_i32::<LittleEndian>(self.last_page_id)?;
cursor.write_u64::<LittleEndian>(self.last_section_addr)?;
cursor.write_u64::<LittleEndian>(self.second_header_addr)?;
cursor.write_u32::<LittleEndian>(self.gap_amount)?;
cursor.write_u32::<LittleEndian>(self.section_amount)?;
cursor.write_i32::<LittleEndian>(0x20)?;
cursor.write_i32::<LittleEndian>(0x80)?;
cursor.write_i32::<LittleEndian>(0x40)?;
cursor.write_u32::<LittleEndian>(self.section_page_map_id)?;
cursor.write_u64::<LittleEndian>(self.page_map_address.saturating_sub(256))?;
cursor.write_u32::<LittleEndian>(self.section_map_id)?;
cursor.write_u32::<LittleEndian>(self.section_array_page_size)?;
cursor.write_u32::<LittleEndian>(0)?;
cursor.write_u32::<LittleEndian>(0)?;
}
let computed_crc = crc::crc32(&buf);
buf[0x68] = computed_crc as u8;
buf[0x69] = (computed_crc >> 8) as u8;
buf[0x6A] = (computed_crc >> 16) as u8;
buf[0x6B] = (computed_crc >> 24) as u8;
apply_magic_sequence(&mut buf);
Ok(buf)
}
fn write_magic_number<W: Write + Seek>(&self, output: &mut W) -> Result<(), DxfError> {
let pos = output.seek(SeekFrom::Current(0))? as usize;
let padding = pos % 0x20;
if padding > 0 {
let magic = magic_sequence();
output.write_all(&magic[..padding])?;
}
Ok(())
}
fn add_local_section(&mut self, mut section: DwgLocalSectionMap) {
section.page_number = self.local_section_maps.len() as i32 + 1;
self.local_section_maps.push(section);
}
fn set_seeker<W: Write + Seek>(
&mut self,
output: &mut W,
section_map_type: i32,
data: &[u8],
) -> Result<DwgLocalSectionMap, DxfError> {
let mut holder = DwgLocalSectionMap::with_section_map(section_map_type);
self.write_magic_number(output)?;
holder.seeker = output.seek(SeekFrom::Current(0))? as i64;
self.compress_checksum(output, &mut holder, data)?;
Ok(holder)
}
fn compress_checksum<W: Write + Seek>(
&mut self,
output: &mut W,
section: &mut DwgLocalSectionMap,
data: &[u8],
) -> Result<(), DxfError> {
section.decompressed_size = data.len() as u64;
let compressed = self.compressor.compress(data, 0, data.len());
section.compressed_size = compressed.len() as u64;
let mut header = Vec::with_capacity(20);
Self::write_page_header_data(&mut header, section)?;
section.checksum = adler32_checksum(0, &header);
section.checksum = adler32_checksum(section.checksum, &compressed);
Self::write_page_header_data(output, section)?;
output.write_all(&compressed)?;
Ok(())
}
fn write_page_header_data<W: Write>(output: &mut W, section: &DwgLocalSectionMap) -> Result<(), DxfError> {
output.write_i32::<LittleEndian>(section.section_map)?;
output.write_i32::<LittleEndian>(section.decompressed_size as i32)?;
output.write_i32::<LittleEndian>(section.compressed_size as i32)?;
output.write_i32::<LittleEndian>(section.compression)?;
output.write_u32::<LittleEndian>(section.checksum)?;
Ok(())
}
}
fn is_all_zeros(data: &[u8]) -> bool {
data.iter().all(|&b| b == 0)
}
#[cfg(test)]
mod tests {
use super::*;
use super::super::section_definition::PAGE_TYPE_DATA_SECTION;
use std::io::Cursor;
#[test]
fn test_new_reserves_header_space() {
let mut output = Cursor::new(Vec::new());
let _writer = DwgFileHeaderWriterAC18::new(DxfVersion::AC1018, 0, &mut output).unwrap();
assert_eq!(output.position(), FILE_HEADER_SIZE as u64);
assert_eq!(output.get_ref().len(), FILE_HEADER_SIZE);
assert!(output.get_ref().iter().all(|&b| b == 0));
}
#[test]
fn test_handle_section_offset_always_zero() {
let mut output = Cursor::new(Vec::new());
let writer = DwgFileHeaderWriterAC18::new(DxfVersion::AC1018, 0, &mut output).unwrap();
assert_eq!(writer.handle_section_offset(), 0);
}
#[test]
fn test_is_all_zeros() {
assert!(is_all_zeros(&[]));
assert!(is_all_zeros(&[0, 0, 0]));
assert!(!is_all_zeros(&[0, 1, 0]));
}
#[test]
fn test_add_section_small_data() {
let mut output = Cursor::new(Vec::new());
let mut writer = DwgFileHeaderWriterAC18::new(DxfVersion::AC1018, 0, &mut output).unwrap();
let data = vec![0xAA; 100];
writer.add_section(&mut output, names::HEADER, &data, true, DEFAULT_DECOMP_SIZE).unwrap();
assert_eq!(writer.descriptors.len(), 1);
assert_eq!(writer.local_section_maps.len(), 1);
let desc = &writer.descriptors[names::HEADER];
assert_eq!(desc.page_count, 1);
assert_eq!(desc.section_id, 0);
assert_eq!(desc.compressed_code, 2);
}
#[test]
fn test_add_section_multi_page() {
let mut output = Cursor::new(Vec::new());
let mut writer = DwgFileHeaderWriterAC18::new(DxfVersion::AC1018, 0, &mut output).unwrap();
let data = vec![0xBB; 0x7400 * 2 + 1000];
writer.add_section(&mut output, names::ACDB_OBJECTS, &data, true, DEFAULT_DECOMP_SIZE).unwrap();
assert_eq!(writer.descriptors.len(), 1);
let desc = &writer.descriptors[names::ACDB_OBJECTS];
assert_eq!(desc.page_count, 3); assert_eq!(writer.local_section_maps.len(), 3);
}
#[test]
fn test_add_multiple_sections() {
let mut output = Cursor::new(Vec::new());
let mut writer = DwgFileHeaderWriterAC18::new(DxfVersion::AC1018, 0, &mut output).unwrap();
writer.add_section(&mut output, names::HEADER, &vec![0xAA; 100], true, DEFAULT_DECOMP_SIZE).unwrap();
writer.add_section(&mut output, names::CLASSES, &vec![0xBB; 200], true, DEFAULT_DECOMP_SIZE).unwrap();
assert_eq!(writer.descriptors.len(), 2);
assert_eq!(writer.descriptors[names::HEADER].section_id, 0);
assert_eq!(writer.descriptors[names::CLASSES].section_id, 1);
}
#[test]
fn test_inner_file_header_size() {
let mut output = Cursor::new(Vec::new());
let writer = DwgFileHeaderWriterAC18::new(DxfVersion::AC1018, 0, &mut output).unwrap();
let header = writer.build_inner_file_header().unwrap();
assert_eq!(header.len(), 0x6C);
}
#[test]
fn test_inner_file_header_magic_xor_roundtrip() {
let mut output = Cursor::new(Vec::new());
let writer = DwgFileHeaderWriterAC18::new(DxfVersion::AC1018, 0, &mut output).unwrap();
let mut header = writer.build_inner_file_header().unwrap();
apply_magic_sequence(&mut header);
assert_eq!(&header[..11], b"AcFssFcAJMB");
assert_eq!(header[11], 0);
}
#[test]
fn test_write_complete_file() {
let mut output = Cursor::new(Vec::new());
let mut writer = DwgFileHeaderWriterAC18::new(DxfVersion::AC1018, 0, &mut output).unwrap();
writer.add_section(&mut output, names::HEADER, &vec![0xAA; 100], true, DEFAULT_DECOMP_SIZE).unwrap();
writer.add_section(&mut output, names::CLASSES, &vec![0xBB; 50], true, DEFAULT_DECOMP_SIZE).unwrap();
writer.add_section(&mut output, names::SUMMARY_INFO, &vec![0; 10], true, DEFAULT_DECOMP_SIZE).unwrap();
writer.add_section(&mut output, names::PREVIEW, &vec![0xCC; 20], true, DEFAULT_DECOMP_SIZE).unwrap();
writer.add_section(&mut output, names::APP_INFO, &vec![0xDD; 30], true, DEFAULT_DECOMP_SIZE).unwrap();
writer.add_section(&mut output, names::AUX_HEADER, &vec![0; 50], true, DEFAULT_DECOMP_SIZE).unwrap();
writer.add_section(&mut output, names::ACDB_OBJECTS, &vec![0xEE; 300], true, DEFAULT_DECOMP_SIZE).unwrap();
writer.add_section(&mut output, names::HANDLES, &vec![0xFF; 100], true, DEFAULT_DECOMP_SIZE).unwrap();
writer.write_file(&mut output).unwrap();
let data = output.into_inner();
assert_eq!(&data[0..6], b"AC1018");
assert!(data.len() > FILE_HEADER_SIZE);
}
#[test]
fn test_write_file_version_strings() {
for version in [DxfVersion::AC1018, DxfVersion::AC1024, DxfVersion::AC1027, DxfVersion::AC1032] {
let mut output = Cursor::new(Vec::new());
let mut writer = DwgFileHeaderWriterAC18::new(version, 0, &mut output).unwrap();
writer.add_section(&mut output, names::HEADER, &vec![0; 10], true, DEFAULT_DECOMP_SIZE).unwrap();
writer.add_section(&mut output, names::CLASSES, &vec![0; 10], true, DEFAULT_DECOMP_SIZE).unwrap();
writer.add_section(&mut output, names::SUMMARY_INFO, &vec![0; 10], true, DEFAULT_DECOMP_SIZE).unwrap();
writer.add_section(&mut output, names::PREVIEW, &vec![0; 10], true, DEFAULT_DECOMP_SIZE).unwrap();
writer.add_section(&mut output, names::APP_INFO, &vec![0; 10], true, DEFAULT_DECOMP_SIZE).unwrap();
writer.add_section(&mut output, names::AUX_HEADER, &vec![0; 10], true, DEFAULT_DECOMP_SIZE).unwrap();
writer.add_section(&mut output, names::ACDB_OBJECTS, &vec![0; 10], true, DEFAULT_DECOMP_SIZE).unwrap();
writer.add_section(&mut output, names::HANDLES, &vec![0; 10], true, DEFAULT_DECOMP_SIZE).unwrap();
writer.write_file(&mut output).unwrap();
let data = output.into_inner();
let expected = version.as_str();
assert_eq!(&data[0..6], expected.as_bytes(), "Version mismatch for {version:?}");
}
}
#[test]
fn test_data_section_header_is_32_bytes() {
let desc = DwgSectionDescriptor::new("test");
let map = DwgLocalSectionMap::new();
let mut buf = Vec::new();
DwgFileHeaderWriterAC18::write_data_section_header(&mut buf, &desc, &map, PAGE_TYPE_DATA_SECTION).unwrap();
assert_eq!(buf.len(), 32);
}
#[test]
fn test_page_header_is_20_bytes() {
let map = DwgLocalSectionMap::new();
let mut buf = Vec::new();
DwgFileHeaderWriterAC18::write_page_header_data(&mut buf, &map).unwrap();
assert_eq!(buf.len(), 20);
}
#[test]
fn test_write_records_page_map_entry_has_nonzero_size() {
let mut output = Cursor::new(Vec::new());
let mut writer = DwgFileHeaderWriterAC18::new(DxfVersion::AC1018, 0, &mut output).unwrap();
writer.add_section(&mut output, names::HEADER, &vec![0xAA; 100], true, DEFAULT_DECOMP_SIZE).unwrap();
writer.add_section(&mut output, names::CLASSES, &vec![0xBB; 50], true, DEFAULT_DECOMP_SIZE).unwrap();
writer.add_section(&mut output, names::SUMMARY_INFO, &vec![0; 10], true, DEFAULT_DECOMP_SIZE).unwrap();
writer.add_section(&mut output, names::PREVIEW, &vec![0xCC; 20], true, DEFAULT_DECOMP_SIZE).unwrap();
writer.add_section(&mut output, names::APP_INFO, &vec![0xDD; 30], true, DEFAULT_DECOMP_SIZE).unwrap();
writer.add_section(&mut output, names::AUX_HEADER, &vec![0; 50], true, DEFAULT_DECOMP_SIZE).unwrap();
writer.add_section(&mut output, names::ACDB_OBJECTS, &vec![0xEE; 300], true, DEFAULT_DECOMP_SIZE).unwrap();
writer.add_section(&mut output, names::HANDLES, &vec![0xFF; 100], true, DEFAULT_DECOMP_SIZE).unwrap();
writer.write_file(&mut output).unwrap();
let page_map_entry = writer.local_section_maps.last().unwrap();
assert_ne!(page_map_entry.seeker, 0, "Page map entry seeker should be non-zero");
assert_ne!(page_map_entry.size, 0, "Page map entry size should be non-zero");
assert!(writer.last_section_addr > 0, "last_section_addr should be > 0");
}
}