NextStep __ICON resources
While working on a NextStep icon archive I've discovered that
early NextStep versions didn't keep the app icon as a file in the .app
bundle (like NextStep descendants macOS and iOS), but rather as a resource embedded in the binary.
NextStep Mach-O binaries contained a segment called __ICON
which stored a TIFF
image,
similar to how Windows PE binaries contain icon resources.
Extracting the icon is a matter of copying the __ICON
segment to a file.
A small C tool to extract the icons:
#include <stdio.h> #include <stdbool.h> #include <stdlib.h> #include <string.h> #include <byteswap.h> #define MH_MAGIC 0xfeedface #define MH_CIGAM 0xcefaedfe struct mach_header { int magic; int cputype; int cpusubtype; int filetype; int ncmds; int sizeofcmds; int flags; }; struct segment_command { int cmd; int cmdsize; char segname[16]; int vmaddr; int vmsize; int fileoff; int filesize; int maxprot; int initprot; int nsects; int flags; }; struct section { char sectname[16]; char segname[16]; int addr; int size; int offset; int align; int reloff; int nreloc; int flags; int reserved1; int reserved2; }; void * load_bytes (FILE * obj_file, int offset, int size) { void *buf = calloc (1, size); fseek (obj_file, offset, SEEK_SET); fread (buf, size, 1, obj_file); return buf; } int main (int argc, char *argv[]) { const char *filename = argv[1]; FILE *obj_file = fopen (filename, "rb"); int offset = 0; size_t header_size = sizeof (struct mach_header); struct mach_header *header = load_bytes (obj_file, offset, header_size); offset += header_size; bool found = false; bool swap = (header->magic == MH_CIGAM); struct segment_command *cmd; if (swap) { header->ncmds = bswap_32 (header->ncmds); } size_t cmd_size = sizeof (struct segment_command); int i; for (i = 0; i <= header->ncmds; i++) { cmd = load_bytes (obj_file, offset, cmd_size); if (swap) { cmd->cmdsize = bswap_32 (cmd->cmdsize); cmd->nsects = bswap_32 (cmd->nsects); } if (strcmp (cmd->segname, "__ICON") == 0) { found = true; break; } offset += cmd->cmdsize; } size_t sect_size = sizeof (struct section); if (found) { printf ("\n%s:\n", filename); offset += sizeof (struct segment_command); int i; for (i = 0; i < cmd->nsects; i++) { struct section *sect = load_bytes (obj_file, offset, sect_size); if (sect->sectname[0] == '_') { offset += sect_size; continue; } if (swap) { sect->offset = bswap_32 (sect->offset); sect->size = bswap_32 (sect->size); } char *icon_data = load_bytes (obj_file, sect->offset, sect->size); char icon_name[100]; sprintf (icon_name, "%s.tiff", sect->sectname); FILE *f = fopen (icon_name, "wb"); if (f == NULL) { printf ("Could not open %s file", icon_name); return 1; } fwrite (icon_data, 1, sect->size, f); fclose (f); offset += sect_size; printf ("Section at %x is called %.16s\n", sect->offset, sect->sectname); } } fclose (obj_file); return 0; }The previous rust implementation is here:
use std::env; use std::io::{Read, Cursor, Write}; use std::fs::File; use mach_object::{OFile, MachCommand, LoadCommand}; fn dump_icon(path: &String) { let mut f = File::open(&path).unwrap(); let mut buf = Vec::new(); let size = f.read_to_end(&mut buf).unwrap(); let mut cur = Cursor::new(&buf[..size]); if let OFile::MachFile { ref header, ref commands } = OFile::parse(&mut cur).unwrap() { assert_eq!(header.ncmds as usize, commands.len()); for &MachCommand(ref cmd, _cmdsize) in commands { if let &LoadCommand::Segment { ref segname, ref sections, .. } = cmd { if segname == "__ICON" { for ref sect in sections { if sect.sectname == "__header" { continue; } println!("dumping {}.tiff...", sect.sectname); let offset = sect.offset as usize; let size = offset+sect.size; let part = &buf[offset..size]; let mut f = File::create(format!("{}.tiff", sect.sectname)).expect("Unable to create file"); f.write_all(part).expect("Unable to write data"); } } } } } } fn help() { println!("Usage: next_icon"); } fn main() { let args: Vec = env::args().collect(); match args.len() { 1 => { help() }, 2 => { dump_icon(&args[1]); } _ => { help() } } }