int main(int argc, char * argv[])
{
    KXKextManagerError	err;
    int			fd;
    const char *	output_name = NULL;
    uint32_t		i, zero = 0, num_files = 0;
    uint32_t		filenum;
    uint32_t		strx, strtabsize, strtabpad;
    struct symbol *	import_symbols;
    struct symbol *	export_symbols;
    uint32_t		num_import_syms, num_export_syms, num_removed_syms;
    uint32_t		import_idx, export_idx;
    const NXArchInfo *	host_arch;
    const NXArchInfo *	target_arch;
    boolean_t		require_imports = true;
    boolean_t		diff = false;

    struct {
	struct mach_header    hdr;
	struct symtab_command symcmd;
    } load_cmds;

    struct file {
        vm_offset_t  mapped;
        vm_size_t    mapped_size;
	uint32_t     nsyms;
	boolean_t    import;
	const char * path;
    };
    struct file files[64];
    
    host_arch = NXGetLocalArchInfo();
    target_arch = host_arch;

    for( i = 1; i < argc; i += 2)
    {
	boolean_t import;

        if (!strcmp("-sect", argv[i]))
        {
	    require_imports = false;
	    i--;
	    continue;
        }
        if (!strcmp("-diff", argv[i]))
        {
	    require_imports = false;
	    diff = true;
	    i--;
	    continue;
        }

	if (i == (argc - 1))
	{
	    fprintf(stderr, "bad arguments: %s\n", argv[i]);
	    exit(1);
	}

        if (!strcmp("-arch", argv[i]))
        {
            target_arch = NXGetArchInfoFromName(argv[i + 1]);
	    if (!target_arch)
	    {
		fprintf(stderr, "unknown architecture name: %s\n", argv[i+1]);
		exit(1);
	    }
            continue;
        }
        if (!strcmp("-output", argv[i]))
        {
	    output_name = argv[i+1];
            continue;
        }

        if (!strcmp("-import", argv[i]))
	    import = true;
	else if (!strcmp("-export", argv[i]))
	    import = false;
	else
	{
	    fprintf(stderr, "unknown option: %s\n", argv[i]);
	    exit(1);
	}

        err = readFile(argv[i+1], &files[num_files].mapped, &files[num_files].mapped_size);
        if (kKXKextManagerErrorNone != err)
            exit(1);

        if (files[num_files].mapped && files[num_files].mapped_size)
	{
	    files[num_files].import = import;
	    files[num_files].path   = argv[i+1];
            num_files++;
	}
    }

    if (!output_name)
    {
	fprintf(stderr, "no output file\n");
	exit(1);
    }

    num_import_syms = 0;
    num_export_syms = 0;
    for (filenum = 0; filenum < num_files; filenum++)
    {
        files[filenum].nsyms = count_symbols((char *) files[filenum].mapped);
	if (files[filenum].import)
	    num_import_syms += files[filenum].nsyms;
	else
	    num_export_syms += files[filenum].nsyms;
    }
    if (!num_export_syms)
    {
	fprintf(stderr, "no export names\n");
	exit(1);
    }

    import_symbols = calloc(num_import_syms, sizeof(struct symbol));
    export_symbols = calloc(num_export_syms, sizeof(struct symbol));

    strtabsize = 4;
    import_idx = 0;
    export_idx = 0;

    for (filenum = 0; filenum < num_files; filenum++)
    {
	if (files[filenum].import)
	{
	    store_symbols((char *) files[filenum].mapped,
					import_symbols, import_idx, num_import_syms);
	    import_idx += files[filenum].nsyms;
	}
	else
	{
	    strtabsize += store_symbols((char *) files[filenum].mapped,
					export_symbols, export_idx, num_export_syms);
	    export_idx += files[filenum].nsyms;
	}
	if (!files[filenum].nsyms)
	{
	    fprintf(stderr, "warning: file %s contains no names\n", files[filenum].path);
	}
    }


    qsort(import_symbols, num_import_syms, sizeof(struct symbol), &qsort_cmp);
    qsort(export_symbols, num_export_syms, sizeof(struct symbol), &qsort_cmp);

    num_removed_syms = 0;
    if (num_import_syms)
    {
	for (export_idx = 0; export_idx < num_export_syms; export_idx++)
	{
	    boolean_t found = true;
	    if (!bsearch(export_symbols[export_idx].name, import_symbols, 
			    num_import_syms, sizeof(struct symbol), &bsearch_cmp))
	    {
		if (require_imports)
		    fprintf(stderr, "exported name not in import list: %s\n", 
				export_symbols[export_idx].name);
		found = false;
	    }
    
	    if (export_symbols[export_idx].indirect)
	    {
		if (!bsearch(export_symbols[export_idx].indirect, import_symbols, 
				num_import_syms, sizeof(struct symbol), &bsearch_cmp))
		{
		    if (require_imports)
			fprintf(stderr, "exported name not in import list: %s\n", 
				    export_symbols[export_idx].indirect);
		    found = false;
		}
	    }
	    if (found && !diff)
		continue;
	    if (!found && diff)
		continue;

	    num_removed_syms++;
	    strtabsize -= (export_symbols[export_idx].name_len + export_symbols[export_idx].indirect_len);
	    export_symbols[export_idx].name = 0;
	}
    }

    if (require_imports && num_removed_syms)
    {
	err = kKXKextManagerErrorUnspecified;
	goto finish;
    }

    fd = open(output_name, O_WRONLY|O_CREAT|O_TRUNC, 0755);
    if (-1 == fd)
    {
	perror("couldn't write output");
	err = kKXKextManagerErrorFileAccess;
	goto finish;
    }

    strtabpad = (strtabsize + 3) & ~3;

    load_cmds.hdr.magic		= MH_MAGIC;
    load_cmds.hdr.cputype	= target_arch->cputype;
    load_cmds.hdr.cpusubtype	= target_arch->cpusubtype;
    load_cmds.hdr.filetype	= MH_OBJECT;
    load_cmds.hdr.ncmds		= 1;
    load_cmds.hdr.sizeofcmds	= sizeof(load_cmds.symcmd);
    load_cmds.hdr.flags		= MH_INCRLINK;

    load_cmds.symcmd.cmd	= LC_SYMTAB;
    load_cmds.symcmd.cmdsize	= sizeof(load_cmds.symcmd);
    load_cmds.symcmd.symoff	= sizeof(load_cmds);
    load_cmds.symcmd.nsyms	= (num_export_syms - num_removed_syms);
    load_cmds.symcmd.stroff	= (num_export_syms - num_removed_syms) * sizeof(struct nlist) 
				+ load_cmds.symcmd.symoff;
    load_cmds.symcmd.strsize	= strtabpad;

    if (target_arch->byteorder != host_arch->byteorder)
    {
	swap_mach_header(&load_cmds.hdr, target_arch->byteorder);
	swap_symtab_command(&load_cmds.symcmd, target_arch->byteorder);
    }

    err = writeFile(fd, &load_cmds, sizeof(load_cmds));
    if (kKXKextManagerErrorNone != err)
	goto finish;

    strx = 4;
    for (export_idx = 0; export_idx < num_export_syms; export_idx++)
    {
	struct nlist nl;

	if (!export_symbols[export_idx].name)
	    continue;

	nl.n_sect  = 0;
	nl.n_desc  = 0;

	nl.n_un.n_strx = strx;
	strx += export_symbols[export_idx].name_len;

	if (export_symbols[export_idx].indirect)
	{
	    nl.n_type  = N_INDR | N_EXT;
	    nl.n_value = strx;
	    strx += export_symbols[export_idx].indirect_len;
	}
	else
	{
	    nl.n_type  = N_UNDF | N_EXT;
	    nl.n_value = 0;
	}

	if (target_arch->byteorder != host_arch->byteorder)
	    swap_nlist(&nl, 1, target_arch->byteorder);

	err = writeFile(fd, &nl, sizeof(nl));
	if (kKXKextManagerErrorNone != err)
	    goto finish;
    }

    strx = sizeof(uint32_t);
    err = writeFile(fd, &zero, strx);
    if (kKXKextManagerErrorNone != err)
	goto finish;

    for (export_idx = 0; export_idx < num_export_syms; export_idx++)
    {
	if (!export_symbols[export_idx].name)
	    continue;
	err = writeFile(fd, export_symbols[export_idx].name, 
		    export_symbols[export_idx].name_len + export_symbols[export_idx].indirect_len);
	if (kKXKextManagerErrorNone != err)
	    goto finish;
    }

    err = writeFile(fd, &zero, strtabpad - strtabsize);
    if (kKXKextManagerErrorNone != err)
	goto finish;
	
    close(fd);


finish:
    if (kKXKextManagerErrorNone != err)
    {
	if (output_name)
	    unlink(output_name);
        exit(1);
    }
    else
        exit(0);
    return(0);
}
int main(int argc, char * argv[])
{
    ToolError	err;
    int			i, fd;
    const char *	output_name = NULL;
    uint32_t		zero = 0, num_files = 0;
    uint32_t		filenum;
    uint32_t		strx, strtabsize, strtabpad;
    struct symbol *	import_symbols;
    struct symbol *	export_symbols;
    uint32_t		num_import_syms, num_export_syms;
    uint32_t		result_count, num_removed_syms;
    uint32_t		import_idx, export_idx;
    const NXArchInfo *	host_arch;
    const NXArchInfo *	target_arch;
    boolean_t		require_imports = true;
    boolean_t		diff = false;


    struct file {
        vm_offset_t  mapped;
        vm_size_t    mapped_size;
	uint32_t     nsyms;
	boolean_t    import;
	const char * path;
    };
    struct file files[64];
    
    host_arch = NXGetLocalArchInfo();
    target_arch = host_arch;

    for( i = 1; i < argc; i += 2)
    {
	boolean_t import;

        if (!strcmp("-sect", argv[i]))
        {
	    require_imports = false;
	    i--;
	    continue;
        }
        if (!strcmp("-diff", argv[i]))
        {
	    require_imports = false;
	    diff = true;
	    i--;
	    continue;
        }

	if (i == (argc - 1))
	{
	    fprintf(stderr, "bad arguments: %s\n", argv[i]);
	    exit(1);
	}

        if (!strcmp("-arch", argv[i]))
        {
            target_arch = DL_NXGetArchInfoFromName(argv[i + 1]);
	    if (!target_arch)
	    {
		fprintf(stderr, "unknown architecture name: %s\n", argv[i+1]);
		exit(1);
	    }
            continue;
        }
        if (!strcmp("-output", argv[i]))
        {
	    output_name = argv[i+1];
            continue;
        }

        if (!strcmp("-import", argv[i]))
	    import = true;
	else if (!strcmp("-export", argv[i]))
	    import = false;
	else
	{
	    fprintf(stderr, "unknown option: %s\n", argv[i]);
	    exit(1);
	}

        err = readFile(argv[i+1], &files[num_files].mapped, &files[num_files].mapped_size);
        if (kErrorNone != err)
            exit(1);

        if (files[num_files].mapped && files[num_files].mapped_size)
	{
	    files[num_files].import = import;
	    files[num_files].path   = argv[i+1];
            num_files++;
	}
    }

    if (!output_name)
    {
	fprintf(stderr, "no output file\n");
	exit(1);
    }

    num_import_syms = 0;
    num_export_syms = 0;
    for (filenum = 0; filenum < num_files; filenum++)
    {
        files[filenum].nsyms = count_symbols((char *) files[filenum].mapped, files[filenum].mapped_size);
	if (files[filenum].import)
	    num_import_syms += files[filenum].nsyms;
	else
	    num_export_syms += files[filenum].nsyms;
    }
    if (!num_export_syms)
    {
	fprintf(stderr, "no export names\n");
	exit(1);
    }

    import_symbols = calloc(num_import_syms, sizeof(struct symbol));
    export_symbols = calloc(num_export_syms, sizeof(struct symbol));

    import_idx = 0;
    export_idx = 0;

    for (filenum = 0; filenum < num_files; filenum++)
    {
	if (files[filenum].import)
	{
	    store_symbols((char *) files[filenum].mapped, files[filenum].mapped_size,
					import_symbols, import_idx, num_import_syms);
	    import_idx += files[filenum].nsyms;
	}
	else
	{
	    store_symbols((char *) files[filenum].mapped, files[filenum].mapped_size,
					export_symbols, export_idx, num_export_syms);
	    export_idx += files[filenum].nsyms;
	}
	if (false && !files[filenum].nsyms)
	{
	    fprintf(stderr, "warning: file %s contains no names\n", files[filenum].path);
	}
    }


    qsort(import_symbols, num_import_syms, sizeof(struct symbol), &qsort_cmp);
    qsort(export_symbols, num_export_syms, sizeof(struct symbol), &qsort_cmp);

    result_count = 0;
    num_removed_syms = 0;
    strtabsize = 4;
    if (num_import_syms)
    {
	for (export_idx = 0; export_idx < num_export_syms; export_idx++)
	{
	    struct symbol * result;
	    char * name;
	    size_t len;
	    boolean_t wild;

	    name = export_symbols[export_idx].indirect;
	    len  = export_symbols[export_idx].indirect_len;
	    if (!name)
	    {
		name = export_symbols[export_idx].name;
		len  = export_symbols[export_idx].name_len;
	    }
	    wild = ((len > 2) && ('*' == name[len-=2]));
	    if (wild)
	    {
		struct bsearch_key key;
		key.name = name;
		key.name_len = len;
		result = bsearch(&key, import_symbols, 
				    num_import_syms, sizeof(struct symbol), &bsearch_cmp_prefix);

		if (result)
		{
		    struct symbol * first;
		    struct symbol * last;

		    strtabsize += (result->name_len + result->indirect_len);

		    first = result;
		    while (--first >= &import_symbols[0])
		    {
			if (bsearch_cmp_prefix(&key, first))
			    break;
			strtabsize += (first->name_len + first->indirect_len);
		    }
		    first++;

		    last = result;
		    while (++last < (&import_symbols[0] + num_import_syms))
		    {
			if (bsearch_cmp_prefix(&key, last))
			    break;
			strtabsize += (last->name_len + last->indirect_len);
		    }
		    result_count += last - first;
		    result = first;
		    export_symbols[export_idx].list = first;
		    export_symbols[export_idx].list_count = last - first;
		    export_symbols[export_idx].flags |= kExported;
		}
	    }
	    else
		result = bsearch(name, import_symbols, 
				    num_import_syms, sizeof(struct symbol), &bsearch_cmp);

	    if (!result && require_imports)
	    {
		int status;
		char * demangled_result = 
			__cxa_demangle(export_symbols[export_idx].name + 1, NULL, NULL, &status);
		fprintf(stderr, "exported name not in import list: %s\n",
					demangled_result ? demangled_result : export_symbols[export_idx].name);
//		fprintf(stderr, "                                : %s\n", export_symbols[export_idx].name);
		if (demangled_result) {
			free(demangled_result);
		}
		num_removed_syms++;
	    }
	    if (diff)
	    {
		if (!result)
		    result = &export_symbols[export_idx];
		else
		    result = NULL;
	    }
	    if (result && !wild)
	    {
		export_symbols[export_idx].flags |= kExported;
		strtabsize += (export_symbols[export_idx].name_len + export_symbols[export_idx].indirect_len);
		result_count++;
		export_symbols[export_idx].list = &export_symbols[export_idx];
		export_symbols[export_idx].list_count = 1;
	    }
	}
    }
    strtabpad = (strtabsize + 3) & ~3;

    if (require_imports && num_removed_syms)
    {
	err = kError;
	goto finish;
    }

    fd = open(output_name, O_WRONLY|O_CREAT|O_TRUNC, 0755);
    if (-1 == fd)
    {
	perror("couldn't write output");
	err = kErrorFileAccess;
	goto finish;
    }

    struct symtab_command symcmd;
    struct uuid_command uuidcmd;

    symcmd.cmd		= LC_SYMTAB;
    symcmd.cmdsize	= sizeof(symcmd);
    symcmd.symoff	= sizeof(symcmd) + sizeof(uuidcmd);
    symcmd.nsyms	= result_count;
    symcmd.strsize	= strtabpad;

    uuidcmd.cmd         = LC_UUID;
    uuidcmd.cmdsize     = sizeof(uuidcmd);
    uuid_generate(uuidcmd.uuid);

    if (CPU_ARCH_ABI64 & target_arch->cputype)
    {
	struct mach_header_64 hdr;
	hdr.magic	= MH_MAGIC_64;
	hdr.cputype	= target_arch->cputype;
	hdr.cpusubtype	= target_arch->cpusubtype;
	hdr.filetype	= MH_KEXT_BUNDLE;
	hdr.ncmds	= 2;
	hdr.sizeofcmds	= sizeof(symcmd) + sizeof(uuidcmd);
	hdr.flags	= MH_INCRLINK;

	symcmd.symoff	+= sizeof(hdr);
	symcmd.stroff	= result_count * sizeof(struct nlist_64) 
				+ symcmd.symoff;

	if (target_arch->byteorder != host_arch->byteorder)
	    swap_mach_header_64(&hdr, target_arch->byteorder);
	err = writeFile(fd, &hdr, sizeof(hdr));
    }
    else
    {
	struct mach_header    hdr;
	hdr.magic	= MH_MAGIC;
	hdr.cputype	= target_arch->cputype;
	hdr.cpusubtype	= target_arch->cpusubtype;
	hdr.filetype	= (target_arch->cputype == CPU_TYPE_I386) ? MH_OBJECT : MH_KEXT_BUNDLE;
	hdr.ncmds	= 2;
	hdr.sizeofcmds	= sizeof(symcmd) + sizeof(uuidcmd);
	hdr.flags	= MH_INCRLINK;

	symcmd.symoff	+= sizeof(hdr);
	symcmd.stroff	= result_count * sizeof(struct nlist) 
				+ symcmd.symoff;

	if (target_arch->byteorder != host_arch->byteorder)
	    swap_mach_header(&hdr, target_arch->byteorder);
	err = writeFile(fd, &hdr, sizeof(hdr));
    }

    if (kErrorNone != err)
	goto finish;

    if (target_arch->byteorder != host_arch->byteorder) {
        swap_symtab_command(&symcmd, target_arch->byteorder);
        swap_uuid_command(&uuidcmd, target_arch->byteorder);
    }
    err = writeFile(fd, &symcmd, sizeof(symcmd));
    if (kErrorNone != err)
	goto finish;
    err = writeFile(fd, &uuidcmd, sizeof(uuidcmd));
    if (kErrorNone != err)
        goto finish;

    strx = 4;
    for (export_idx = 0; export_idx < num_export_syms; export_idx++)
    {
	if (!export_symbols[export_idx].name)
	    continue;
	if (!(kExported & export_symbols[export_idx].flags))
	    continue;

	if (export_idx
	  && export_symbols[export_idx - 1].name
	  && !strcmp(export_symbols[export_idx - 1].name, export_symbols[export_idx].name))
	{
	    fprintf(stderr, "duplicate export: %s\n", export_symbols[export_idx - 1].name);
	    err = kErrorDuplicate;
	    goto finish;
	}

	for (import_idx = 0; import_idx < export_symbols[export_idx].list_count; import_idx++)
	{

	    if (export_symbols[export_idx].list != &export_symbols[export_idx])
	    {
		printf("wild: %s, %s\n", export_symbols[export_idx].name, 
			export_symbols[export_idx].list[import_idx].name);
	    }
	    if (CPU_ARCH_ABI64 & target_arch->cputype)
	    {
		struct nlist_64 nl;

		nl.n_sect  = 0;
                nl.n_desc  = 0;
		nl.n_un.n_strx = strx;
		strx += export_symbols[export_idx].list[import_idx].name_len;
                
                if (export_symbols[export_idx].flags & kObsolete) {
                    nl.n_desc |= N_DESC_DISCARDED;
                }

		if (export_symbols[export_idx].list[import_idx].indirect)
		{
		    nl.n_type  = N_INDR | N_EXT;
		    nl.n_value = strx;
		    strx += export_symbols[export_idx].list[import_idx].indirect_len;
		}
		else
		{
		    nl.n_type  = N_UNDF | N_EXT;
		    nl.n_value = 0;
		}

		if (target_arch->byteorder != host_arch->byteorder)
		    swap_nlist_64(&nl, 1, target_arch->byteorder);

		err = writeFile(fd, &nl, sizeof(nl));
	    }
	    else
	    {
		struct nlist nl;

		nl.n_sect  = 0;
		nl.n_desc  = 0;
		nl.n_un.n_strx = strx;
		strx += export_symbols[export_idx].list[import_idx].name_len;
 
                if (export_symbols[export_idx].flags & kObsolete) {
                    nl.n_desc |= N_DESC_DISCARDED;
                }

		if (export_symbols[export_idx].list[import_idx].indirect)
		{
		    nl.n_type  = N_INDR | N_EXT;
		    nl.n_value = strx;
		    strx += export_symbols[export_idx].list[import_idx].indirect_len;
		}
		else
		{
		    nl.n_type  = N_UNDF | N_EXT;
		    nl.n_value = 0;
		}

		if (target_arch->byteorder != host_arch->byteorder)
		    swap_nlist(&nl, 1, target_arch->byteorder);

		err = writeFile(fd, &nl, sizeof(nl));
	    }
	}

	if (kErrorNone != err)
	    goto finish;
    }

    strx = sizeof(uint32_t);
    err = writeFile(fd, &zero, strx);
    if (kErrorNone != err)
	goto finish;

    for (export_idx = 0; export_idx < num_export_syms; export_idx++)
    {
	if (!export_symbols[export_idx].name)
	    continue;

	for (import_idx = 0; import_idx < export_symbols[export_idx].list_count; import_idx++)
	{
	    err = writeFile(fd, export_symbols[export_idx].list[import_idx].name, 
			export_symbols[export_idx].list[import_idx].name_len);
	    if (kErrorNone != err)
		goto finish;
	    if (export_symbols[export_idx].list[import_idx].indirect)
	    {
		err = writeFile(fd, export_symbols[export_idx].list[import_idx].indirect, 
			    export_symbols[export_idx].list[import_idx].indirect_len);
		if (kErrorNone != err)
		    goto finish;
	    }
	}
    }

    err = writeFile(fd, &zero, strtabpad - strtabsize);
    if (kErrorNone != err)
	goto finish;
	
    close(fd);


finish:
    for (filenum = 0; filenum < num_files; filenum++) {
        // unmap file
        if (files[filenum].mapped_size)
        {
            munmap((caddr_t)files[filenum].mapped, files[filenum].mapped_size);
            files[filenum].mapped     = 0;
            files[filenum].mapped_size = 0;
        }

    }

    if (kErrorNone != err)
    {
	if (output_name)
	    unlink(output_name);
        exit(1);
    }
    else
        exit(0);
    return(0);
}