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()
}
}
}