exc_bad_access

NextStep __ICON resources

preserving a twitter thread from 20/06/2020 located here

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