use alloc::vec::Vec; use core::mem; use crate::endian::{LittleEndian as LE, U16Bytes, U32Bytes, U16, U32}; use crate::pe as coff; use crate::write::string::*; use crate::write::util::*; use crate::write::*; #[derive(Default, Clone, Copy)] struct SectionOffsets { offset: usize, str_id: Option, reloc_offset: usize, selection: u8, associative_section: u16, } #[derive(Default, Clone, Copy)] struct SymbolOffsets { index: usize, str_id: Option, aux_count: u8, } /// Internal format to use for the `.drectve` section containing linker /// directives for symbol exports. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum CoffExportStyle { /// MSVC format supported by link.exe and LLD. Msvc, /// Gnu format supported by GNU LD and LLD. Gnu, } impl<'a> Object<'a> { pub(crate) fn coff_section_info( &self, section: StandardSection, ) -> (&'static [u8], &'static [u8], SectionKind) { match section { StandardSection::Text => (&[], &b".text"[..], SectionKind::Text), StandardSection::Data => (&[], &b".data"[..], SectionKind::Data), StandardSection::ReadOnlyData | StandardSection::ReadOnlyDataWithRel | StandardSection::ReadOnlyString => (&[], &b".rdata"[..], SectionKind::ReadOnlyData), StandardSection::UninitializedData => { (&[], &b".bss"[..], SectionKind::UninitializedData) } // TLS sections are data sections with a special name. StandardSection::Tls => (&[], &b".tls$"[..], SectionKind::Data), StandardSection::UninitializedTls => { // Unsupported section. (&[], &[], SectionKind::UninitializedTls) } StandardSection::TlsVariables => { // Unsupported section. (&[], &[], SectionKind::TlsVariables) } StandardSection::Common => { // Unsupported section. (&[], &[], SectionKind::Common) } } } pub(crate) fn coff_subsection_name(&self, section: &[u8], value: &[u8]) -> Vec { let mut name = section.to_vec(); name.push(b'$'); name.extend_from_slice(value); name } pub(crate) fn coff_fixup_relocation(&mut self, mut relocation: &mut Relocation) -> i64 { if relocation.kind == RelocationKind::GotRelative { // Use a stub symbol for the relocation instead. // This isn't really a GOT, but it's a similar purpose. // TODO: need to handle DLL imports differently? relocation.kind = RelocationKind::Relative; relocation.symbol = self.coff_add_stub_symbol(relocation.symbol); } else if relocation.kind == RelocationKind::PltRelative { // Windows doesn't need a separate relocation type for // references to functions in import libraries. // For convenience, treat this the same as Relative. relocation.kind = RelocationKind::Relative; } let constant = match self.architecture { Architecture::I386 | Architecture::Arm | Architecture::Aarch64 => match relocation.kind { RelocationKind::Relative => { // IMAGE_REL_I386_REL32, IMAGE_REL_ARM_REL32, IMAGE_REL_ARM64_REL32 relocation.addend + 4 } _ => relocation.addend, }, Architecture::X86_64 => match relocation.kind { RelocationKind::Relative => { // IMAGE_REL_AMD64_REL32 through to IMAGE_REL_AMD64_REL32_5 if relocation.addend <= -4 && relocation.addend >= -9 { 0 } else { relocation.addend + 4 } } _ => relocation.addend, }, _ => unimplemented!(), }; relocation.addend -= constant; constant } fn coff_add_stub_symbol(&mut self, symbol_id: SymbolId) -> SymbolId { if let Some(stub_id) = self.stub_symbols.get(&symbol_id) { return *stub_id; } let stub_size = self.architecture.address_size().unwrap().bytes(); let name = b".rdata$.refptr".to_vec(); let section_id = self.add_section(Vec::new(), name, SectionKind::ReadOnlyData); let section = self.section_mut(section_id); section.set_data(vec![0; stub_size as usize], u64::from(stub_size)); section.relocations = vec![Relocation { offset: 0, size: stub_size * 8, kind: RelocationKind::Absolute, encoding: RelocationEncoding::Generic, symbol: symbol_id, addend: 0, }]; let mut name = b".refptr.".to_vec(); name.extend_from_slice(&self.symbol(symbol_id).name); let stub_id = self.add_raw_symbol(Symbol { name, value: 0, size: u64::from(stub_size), kind: SymbolKind::Data, scope: SymbolScope::Compilation, weak: false, section: SymbolSection::Section(section_id), flags: SymbolFlags::None, }); self.stub_symbols.insert(symbol_id, stub_id); stub_id } /// Appends linker directives to the `.drectve` section to tell the linker /// to export all symbols with `SymbolScope::Dynamic`. /// /// This must be called after all symbols have been defined. pub fn add_coff_exports(&mut self, style: CoffExportStyle) { assert_eq!(self.format, BinaryFormat::Coff); let mut directives = vec![]; for symbol in &self.symbols { if symbol.scope == SymbolScope::Dynamic { match style { CoffExportStyle::Msvc => directives.extend(b" /EXPORT:\""), CoffExportStyle::Gnu => directives.extend(b" -export:\""), } directives.extend(&symbol.name); directives.extend(b"\""); if symbol.kind != SymbolKind::Text { match style { CoffExportStyle::Msvc => directives.extend(b",DATA"), CoffExportStyle::Gnu => directives.extend(b",data"), } } } } let drectve = self.add_section(vec![], b".drectve".to_vec(), SectionKind::Linker); self.append_section_data(drectve, &directives, 1); } pub(crate) fn coff_write(&self, buffer: &mut dyn WritableBuffer) -> Result<()> { // Calculate offsets of everything, and build strtab. let mut offset = 0; let mut strtab = StringTable::default(); // COFF header. offset += mem::size_of::(); // Section headers. offset += self.sections.len() * mem::size_of::(); // Calculate size of section data and add section strings to strtab. let mut section_offsets = vec![SectionOffsets::default(); self.sections.len()]; for (index, section) in self.sections.iter().enumerate() { if section.name.len() > 8 { section_offsets[index].str_id = Some(strtab.add(§ion.name)); } let len = section.data.len(); if len != 0 { // TODO: not sure what alignment is required here, but this seems to match LLVM offset = align(offset, 4); section_offsets[index].offset = offset; offset += len; } else { section_offsets[index].offset = 0; } // Calculate size of relocations. let mut count = section.relocations.len(); if count != 0 { section_offsets[index].reloc_offset = offset; if count > 0xffff { count += 1; } offset += count * mem::size_of::(); } } // Set COMDAT flags. for comdat in &self.comdats { let symbol = &self.symbols[comdat.symbol.0]; let comdat_section = match symbol.section { SymbolSection::Section(id) => id.0, _ => { return Err(Error(format!( "unsupported COMDAT symbol `{}` section {:?}", symbol.name().unwrap_or(""), symbol.section ))); } }; section_offsets[comdat_section].selection = match comdat.kind { ComdatKind::NoDuplicates => coff::IMAGE_COMDAT_SELECT_NODUPLICATES, ComdatKind::Any => coff::IMAGE_COMDAT_SELECT_ANY, ComdatKind::SameSize => coff::IMAGE_COMDAT_SELECT_SAME_SIZE, ComdatKind::ExactMatch => coff::IMAGE_COMDAT_SELECT_EXACT_MATCH, ComdatKind::Largest => coff::IMAGE_COMDAT_SELECT_LARGEST, ComdatKind::Newest => coff::IMAGE_COMDAT_SELECT_NEWEST, ComdatKind::Unknown => { return Err(Error(format!( "unsupported COMDAT symbol `{}` kind {:?}", symbol.name().unwrap_or(""), comdat.kind ))); } }; for id in &comdat.sections { let section = &self.sections[id.0]; if section.symbol.is_none() { return Err(Error(format!( "missing symbol for COMDAT section `{}`", section.name().unwrap_or(""), ))); } if id.0 != comdat_section { section_offsets[id.0].selection = coff::IMAGE_COMDAT_SELECT_ASSOCIATIVE; section_offsets[id.0].associative_section = comdat_section as u16 + 1; } } } // Calculate size of symbols and add symbol strings to strtab. let mut symbol_offsets = vec![SymbolOffsets::default(); self.symbols.len()]; let mut symtab_count = 0; for (index, symbol) in self.symbols.iter().enumerate() { symbol_offsets[index].index = symtab_count; symtab_count += 1; match symbol.kind { SymbolKind::File => { // Name goes in auxilary symbol records. let aux_count = (symbol.name.len() + coff::IMAGE_SIZEOF_SYMBOL - 1) / coff::IMAGE_SIZEOF_SYMBOL; symbol_offsets[index].aux_count = aux_count as u8; symtab_count += aux_count; // Don't add name to strtab. continue; } SymbolKind::Section => { symbol_offsets[index].aux_count = 1; symtab_count += 1; } _ => {} } if symbol.name.len() > 8 { symbol_offsets[index].str_id = Some(strtab.add(&symbol.name)); } } // Calculate size of symtab. let symtab_offset = offset; let symtab_len = symtab_count * coff::IMAGE_SIZEOF_SYMBOL; offset += symtab_len; // Calculate size of strtab. let strtab_offset = offset; let mut strtab_data = Vec::new(); // First 4 bytes of strtab are the length. strtab.write(4, &mut strtab_data); let strtab_len = strtab_data.len() + 4; offset += strtab_len; // Start writing. buffer .reserve(offset) .map_err(|_| Error(String::from("Cannot allocate buffer")))?; // Write file header. let header = coff::ImageFileHeader { machine: U16::new( LE, match self.architecture { Architecture::Arm => coff::IMAGE_FILE_MACHINE_ARMNT, Architecture::Aarch64 => coff::IMAGE_FILE_MACHINE_ARM64, Architecture::I386 => coff::IMAGE_FILE_MACHINE_I386, Architecture::X86_64 => coff::IMAGE_FILE_MACHINE_AMD64, _ => { return Err(Error(format!( "unimplemented architecture {:?}", self.architecture ))); } }, ), number_of_sections: U16::new(LE, self.sections.len() as u16), time_date_stamp: U32::default(), pointer_to_symbol_table: U32::new(LE, symtab_offset as u32), number_of_symbols: U32::new(LE, symtab_count as u32), size_of_optional_header: U16::default(), characteristics: match self.flags { FileFlags::Coff { characteristics } => U16::new(LE, characteristics), _ => U16::default(), }, }; buffer.write(&header); // Write section headers. for (index, section) in self.sections.iter().enumerate() { let mut characteristics = if let SectionFlags::Coff { characteristics, .. } = section.flags { characteristics } else { match section.kind { SectionKind::Text => { coff::IMAGE_SCN_CNT_CODE | coff::IMAGE_SCN_MEM_EXECUTE | coff::IMAGE_SCN_MEM_READ } SectionKind::Data => { coff::IMAGE_SCN_CNT_INITIALIZED_DATA | coff::IMAGE_SCN_MEM_READ | coff::IMAGE_SCN_MEM_WRITE } SectionKind::UninitializedData => { coff::IMAGE_SCN_CNT_UNINITIALIZED_DATA | coff::IMAGE_SCN_MEM_READ | coff::IMAGE_SCN_MEM_WRITE } SectionKind::ReadOnlyData | SectionKind::ReadOnlyString => { coff::IMAGE_SCN_CNT_INITIALIZED_DATA | coff::IMAGE_SCN_MEM_READ } SectionKind::Debug | SectionKind::Other | SectionKind::OtherString => { coff::IMAGE_SCN_CNT_INITIALIZED_DATA | coff::IMAGE_SCN_MEM_READ | coff::IMAGE_SCN_MEM_DISCARDABLE } SectionKind::Linker => coff::IMAGE_SCN_LNK_INFO | coff::IMAGE_SCN_LNK_REMOVE, SectionKind::Common | SectionKind::Tls | SectionKind::UninitializedTls | SectionKind::TlsVariables | SectionKind::Note | SectionKind::Unknown | SectionKind::Metadata | SectionKind::Elf(_) => { return Err(Error(format!( "unimplemented section `{}` kind {:?}", section.name().unwrap_or(""), section.kind ))); } } }; if section_offsets[index].selection != 0 { characteristics |= coff::IMAGE_SCN_LNK_COMDAT; }; if section.relocations.len() > 0xffff { characteristics |= coff::IMAGE_SCN_LNK_NRELOC_OVFL; } characteristics |= match section.align { 1 => coff::IMAGE_SCN_ALIGN_1BYTES, 2 => coff::IMAGE_SCN_ALIGN_2BYTES, 4 => coff::IMAGE_SCN_ALIGN_4BYTES, 8 => coff::IMAGE_SCN_ALIGN_8BYTES, 16 => coff::IMAGE_SCN_ALIGN_16BYTES, 32 => coff::IMAGE_SCN_ALIGN_32BYTES, 64 => coff::IMAGE_SCN_ALIGN_64BYTES, 128 => coff::IMAGE_SCN_ALIGN_128BYTES, 256 => coff::IMAGE_SCN_ALIGN_256BYTES, 512 => coff::IMAGE_SCN_ALIGN_512BYTES, 1024 => coff::IMAGE_SCN_ALIGN_1024BYTES, 2048 => coff::IMAGE_SCN_ALIGN_2048BYTES, 4096 => coff::IMAGE_SCN_ALIGN_4096BYTES, 8192 => coff::IMAGE_SCN_ALIGN_8192BYTES, _ => { return Err(Error(format!( "unimplemented section `{}` align {}", section.name().unwrap_or(""), section.align ))); } }; let mut coff_section = coff::ImageSectionHeader { name: [0; 8], virtual_size: U32::default(), virtual_address: U32::default(), size_of_raw_data: U32::new(LE, section.size as u32), pointer_to_raw_data: U32::new(LE, section_offsets[index].offset as u32), pointer_to_relocations: U32::new(LE, section_offsets[index].reloc_offset as u32), pointer_to_linenumbers: U32::default(), number_of_relocations: if section.relocations.len() > 0xffff { U16::new(LE, 0xffff) } else { U16::new(LE, section.relocations.len() as u16) }, number_of_linenumbers: U16::default(), characteristics: U32::new(LE, characteristics), }; if section.name.len() <= 8 { coff_section.name[..section.name.len()].copy_from_slice(§ion.name); } else { let mut str_offset = strtab.get_offset(section_offsets[index].str_id.unwrap()); if str_offset <= 9_999_999 { let mut name = [0; 7]; let mut len = 0; if str_offset == 0 { name[6] = b'0'; len = 1; } else { while str_offset != 0 { let rem = (str_offset % 10) as u8; str_offset /= 10; name[6 - len] = b'0' + rem; len += 1; } } coff_section.name = [0; 8]; coff_section.name[0] = b'/'; coff_section.name[1..][..len].copy_from_slice(&name[7 - len..]); } else if str_offset as u64 <= 0xf_ffff_ffff { coff_section.name[0] = b'/'; coff_section.name[1] = b'/'; for i in 0..6 { let rem = (str_offset % 64) as u8; str_offset /= 64; let c = match rem { 0..=25 => b'A' + rem, 26..=51 => b'a' + rem - 26, 52..=61 => b'0' + rem - 52, 62 => b'+', 63 => b'/', _ => unreachable!(), }; coff_section.name[7 - i] = c; } } else { return Err(Error(format!("invalid section name offset {}", str_offset))); } } buffer.write(&coff_section); } // Write section data and relocations. for (index, section) in self.sections.iter().enumerate() { let len = section.data.len(); if len != 0 { write_align(buffer, 4); debug_assert_eq!(section_offsets[index].offset, buffer.len()); buffer.write_bytes(§ion.data); } if !section.relocations.is_empty() { debug_assert_eq!(section_offsets[index].reloc_offset, buffer.len()); if section.relocations.len() > 0xffff { let coff_relocation = coff::ImageRelocation { virtual_address: U32Bytes::new(LE, section.relocations.len() as u32 + 1), symbol_table_index: U32Bytes::new(LE, 0), typ: U16Bytes::new(LE, 0), }; buffer.write(&coff_relocation); } for reloc in §ion.relocations { //assert!(reloc.implicit_addend); let typ = match self.architecture { Architecture::I386 => match (reloc.kind, reloc.size, reloc.addend) { (RelocationKind::Absolute, 16, 0) => coff::IMAGE_REL_I386_DIR16, (RelocationKind::Relative, 16, 0) => coff::IMAGE_REL_I386_REL16, (RelocationKind::Absolute, 32, 0) => coff::IMAGE_REL_I386_DIR32, (RelocationKind::ImageOffset, 32, 0) => coff::IMAGE_REL_I386_DIR32NB, (RelocationKind::SectionIndex, 16, 0) => coff::IMAGE_REL_I386_SECTION, (RelocationKind::SectionOffset, 32, 0) => coff::IMAGE_REL_I386_SECREL, (RelocationKind::SectionOffset, 7, 0) => coff::IMAGE_REL_I386_SECREL7, (RelocationKind::Relative, 32, -4) => coff::IMAGE_REL_I386_REL32, (RelocationKind::Coff(x), _, _) => x, _ => { return Err(Error(format!("unimplemented relocation {:?}", reloc))); } }, Architecture::X86_64 => match (reloc.kind, reloc.size, reloc.addend) { (RelocationKind::Absolute, 64, 0) => coff::IMAGE_REL_AMD64_ADDR64, (RelocationKind::Absolute, 32, 0) => coff::IMAGE_REL_AMD64_ADDR32, (RelocationKind::ImageOffset, 32, 0) => coff::IMAGE_REL_AMD64_ADDR32NB, (RelocationKind::Relative, 32, -4) => coff::IMAGE_REL_AMD64_REL32, (RelocationKind::Relative, 32, -5) => coff::IMAGE_REL_AMD64_REL32_1, (RelocationKind::Relative, 32, -6) => coff::IMAGE_REL_AMD64_REL32_2, (RelocationKind::Relative, 32, -7) => coff::IMAGE_REL_AMD64_REL32_3, (RelocationKind::Relative, 32, -8) => coff::IMAGE_REL_AMD64_REL32_4, (RelocationKind::Relative, 32, -9) => coff::IMAGE_REL_AMD64_REL32_5, (RelocationKind::SectionIndex, 16, 0) => coff::IMAGE_REL_AMD64_SECTION, (RelocationKind::SectionOffset, 32, 0) => coff::IMAGE_REL_AMD64_SECREL, (RelocationKind::SectionOffset, 7, 0) => coff::IMAGE_REL_AMD64_SECREL7, (RelocationKind::Coff(x), _, _) => x, _ => { return Err(Error(format!("unimplemented relocation {:?}", reloc))); } }, Architecture::Arm => match (reloc.kind, reloc.size, reloc.addend) { (RelocationKind::Absolute, 32, 0) => coff::IMAGE_REL_ARM_ADDR32, (RelocationKind::ImageOffset, 32, 0) => coff::IMAGE_REL_ARM_ADDR32NB, (RelocationKind::Relative, 32, -4) => coff::IMAGE_REL_ARM_REL32, (RelocationKind::SectionIndex, 16, 0) => coff::IMAGE_REL_ARM_SECTION, (RelocationKind::SectionOffset, 32, 0) => coff::IMAGE_REL_ARM_SECREL, (RelocationKind::Coff(x), _, _) => x, _ => { return Err(Error(format!("unimplemented relocation {:?}", reloc))); } }, Architecture::Aarch64 => match (reloc.kind, reloc.size, reloc.addend) { (RelocationKind::Absolute, 32, 0) => coff::IMAGE_REL_ARM64_ADDR32, (RelocationKind::ImageOffset, 32, 0) => coff::IMAGE_REL_ARM64_ADDR32NB, (RelocationKind::SectionIndex, 16, 0) => coff::IMAGE_REL_ARM64_SECTION, (RelocationKind::SectionOffset, 32, 0) => coff::IMAGE_REL_ARM64_SECREL, (RelocationKind::Absolute, 64, 0) => coff::IMAGE_REL_ARM64_ADDR64, (RelocationKind::Relative, 32, -4) => coff::IMAGE_REL_ARM64_REL32, (RelocationKind::Coff(x), _, _) => x, _ => { return Err(Error(format!("unimplemented relocation {:?}", reloc))); } }, _ => { return Err(Error(format!( "unimplemented architecture {:?}", self.architecture ))); } }; let coff_relocation = coff::ImageRelocation { virtual_address: U32Bytes::new(LE, reloc.offset as u32), symbol_table_index: U32Bytes::new( LE, symbol_offsets[reloc.symbol.0].index as u32, ), typ: U16Bytes::new(LE, typ), }; buffer.write(&coff_relocation); } } } // Write symbols. debug_assert_eq!(symtab_offset, buffer.len()); for (index, symbol) in self.symbols.iter().enumerate() { let mut name = &symbol.name[..]; let section_number = match symbol.section { SymbolSection::None => { debug_assert_eq!(symbol.kind, SymbolKind::File); coff::IMAGE_SYM_DEBUG } SymbolSection::Undefined => coff::IMAGE_SYM_UNDEFINED, SymbolSection::Absolute => coff::IMAGE_SYM_ABSOLUTE, SymbolSection::Common => coff::IMAGE_SYM_UNDEFINED, SymbolSection::Section(id) => id.0 as u16 + 1, }; let typ = if symbol.kind == SymbolKind::Text { coff::IMAGE_SYM_DTYPE_FUNCTION << coff::IMAGE_SYM_DTYPE_SHIFT } else { coff::IMAGE_SYM_TYPE_NULL }; let storage_class = match symbol.kind { SymbolKind::File => { // Name goes in auxilary symbol records. name = b".file"; coff::IMAGE_SYM_CLASS_FILE } SymbolKind::Section => coff::IMAGE_SYM_CLASS_STATIC, SymbolKind::Label => coff::IMAGE_SYM_CLASS_LABEL, SymbolKind::Text | SymbolKind::Data | SymbolKind::Tls => { match symbol.section { SymbolSection::None => { return Err(Error(format!( "missing section for symbol `{}`", symbol.name().unwrap_or("") ))); } SymbolSection::Undefined | SymbolSection::Common => { coff::IMAGE_SYM_CLASS_EXTERNAL } SymbolSection::Absolute | SymbolSection::Section(_) => { match symbol.scope { // TODO: does this need aux symbol records too? _ if symbol.weak => coff::IMAGE_SYM_CLASS_WEAK_EXTERNAL, SymbolScope::Unknown => { return Err(Error(format!( "unimplemented symbol `{}` scope {:?}", symbol.name().unwrap_or(""), symbol.scope ))); } SymbolScope::Compilation => coff::IMAGE_SYM_CLASS_STATIC, SymbolScope::Linkage | SymbolScope::Dynamic => { coff::IMAGE_SYM_CLASS_EXTERNAL } } } } } SymbolKind::Unknown | SymbolKind::Null => { return Err(Error(format!( "unimplemented symbol `{}` kind {:?}", symbol.name().unwrap_or(""), symbol.kind ))); } }; let number_of_aux_symbols = symbol_offsets[index].aux_count; let value = if symbol.section == SymbolSection::Common { symbol.size as u32 } else { symbol.value as u32 }; let mut coff_symbol = coff::ImageSymbol { name: [0; 8], value: U32Bytes::new(LE, value), section_number: U16Bytes::new(LE, section_number as u16), typ: U16Bytes::new(LE, typ), storage_class, number_of_aux_symbols, }; if name.len() <= 8 { coff_symbol.name[..name.len()].copy_from_slice(name); } else { let str_offset = strtab.get_offset(symbol_offsets[index].str_id.unwrap()); coff_symbol.name[4..8].copy_from_slice(&u32::to_le_bytes(str_offset as u32)); } buffer.write(&coff_symbol); // Write auxiliary symbols. match symbol.kind { SymbolKind::File => { let aux_len = number_of_aux_symbols as usize * coff::IMAGE_SIZEOF_SYMBOL; debug_assert!(aux_len >= symbol.name.len()); let old_len = buffer.len(); buffer.write_bytes(&symbol.name); buffer.resize(old_len + aux_len); } SymbolKind::Section => { debug_assert_eq!(number_of_aux_symbols, 1); let section_index = symbol.section.id().unwrap().0; let section = &self.sections[section_index]; let aux = coff::ImageAuxSymbolSection { length: U32Bytes::new(LE, section.size as u32), number_of_relocations: if section.relocations.len() > 0xffff { U16Bytes::new(LE, 0xffff) } else { U16Bytes::new(LE, section.relocations.len() as u16) }, number_of_linenumbers: U16Bytes::default(), check_sum: U32Bytes::new(LE, checksum(section.data())), number: U16Bytes::new( LE, section_offsets[section_index].associative_section, ), selection: section_offsets[section_index].selection, reserved: 0, // TODO: bigobj high_number: U16Bytes::default(), }; buffer.write(&aux); } _ => { debug_assert_eq!(number_of_aux_symbols, 0); } } } // Write strtab section. debug_assert_eq!(strtab_offset, buffer.len()); buffer.write_bytes(&u32::to_le_bytes(strtab_len as u32)); buffer.write_bytes(&strtab_data); debug_assert_eq!(offset, buffer.len()); Ok(()) } } // JamCRC fn checksum(data: &[u8]) -> u32 { let mut hasher = crc32fast::Hasher::new_with_initial(0xffff_ffff); hasher.update(data); !hasher.finalize() }