#include <architecture/byte_order.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <mach-o/arch.h>
#include <mach-o/fat.h>
#include <mach-o/loader.h>
#include <mach-o/swap.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#ifndef PLATFORM_DRIVERKIT
#define PLATFORM_DRIVERKIT 10
#endif
enum command {
kCommandUnset = 0,
kCommandShow,
kCommandSet,
kCommandRemove,
kCommandHelp,
};
enum show_command {
kShowAll = 0,
kShowBuild,
kShowSource,
kShowSpace,
};
enum set_command {
kSetBuild,
kSetSource,
kAddTool,
};
enum remove_command {
kRemoveBuild,
kRemoveSource,
kRemoveTool,
};
struct set_item {
enum set_command type;
uint32_t platform;
uint32_t tool;
uint32_t version;
uint32_t sdk_vers;
uint64_t src_vers;
bool versmin;
};
struct remove_item {
enum remove_command type;
uint32_t platform;
uint32_t tool;
};
struct lcmds {
struct load_command** items;
uint32_t count;
};
struct file {
unsigned char* buf;
off_t len;
mode_t mode;
uint32_t nfat_arch;
struct fat_arch* fat_archs;
uint32_t fat_arch_idx;
struct mach_header_64 mh;
size_t mh_size;
unsigned char* lcs; struct lcmds* lcmds; bool swap;
};
static struct options {
enum command command;
const char* inPath;
const char* outPath;
const NXArchInfo **archs;
uint32_t narch;
enum show_command show;
bool replace;
struct set_item* setItems;
uint32_t numSetItems;
struct remove_item* removeItems;
uint32_t numRemoveItems;
} gOptions;
static const char* gProgramName;
static enum NXByteOrder gByteOrder;
static int process(void);
static int command_remove(struct file* fb);
static int command_set(struct file* fb);
static int command_show(struct file* fb);
static int file_read(const char* path, struct file* fb);
static int file_select_macho(const char* path, struct file* fb, uint32_t index);
static int file_write(const char* path, struct file* fb);
static struct lcmds* lcmds_alloc(struct file* fb);
static void lcmds_free(struct lcmds* lcmds);
static int parse_version_abcde(const char* rostr, uint64_t *version);
static int parse_version_xyz(const char* rostr, uint32_t *version);
static uint32_t platform_id_for_name(const char* name);
static uint32_t platform_id_for_vmlc(uint32_t vmlc);
static const char* platform_name_for_id(uint32_t platform_id);
static uint32_t platform_vmlc_for_id(uint32_t platform_id);
static void print_version_xyz(const char* label, uint32_t version);
static void print_version_min_command(struct version_min_command *vd);
static void print_build_version_command(struct build_version_command *bv);
static void print_build_tool_version(uint32_t tool, uint32_t version);
static void print_source_version_command(struct source_version_command *sv);
static uint32_t tool_id_for_name(const char* name);
static const char* tool_name_for_id(uint32_t tool_id);
static void usage(const char * __restrict format, ...);
int main(int argc, const char * argv[])
{
bool read_options = true;
gByteOrder = NXHostByteOrder();
gProgramName = *argv++;
argc--;
if (argc == 0)
usage(NULL);
while (argc > 0)
{
if (read_options && *argv && '-' == **argv)
{
if (0 == strcmp("-arch", *argv))
{
argv++; argc--;
if (!*argv) {
usage("missing arch");
}
const NXArchInfo *archInfo = NXGetArchInfoFromName(*argv);
if (!archInfo) {
usage("unknown arch: %s", *argv);
}
gOptions.archs = reallocf(gOptions.archs,
sizeof(*gOptions.archs) *
(gOptions.narch + 1));
gOptions.archs[gOptions.narch++] = archInfo;
}
else if (0 == strcmp("-h", *argv) ||
0 == strcmp("-help", *argv))
{
gOptions.command = kCommandHelp;
usage(NULL);
}
else if (0 == strcmp("-o", *argv) ||
0 == strcmp("-output", *argv))
{
argv++; argc--;
if (gOptions.outPath) {
usage("only one output file must be specified");
}
if (!*argv) {
usage("one output file must be specified");
}
gOptions.outPath = *argv;
}
else if (0 == strcmp("-r", *argv) ||
0 == strcmp("-replace", *argv))
{
gOptions.replace = true;
}
else if (0 == strcmp("-remove-build-tool", *argv) ||
0 == strcmp("-remove-tool", *argv))
{
const char* option = *argv;
argv++; argc--;
if (kCommandUnset != gOptions.command &&
kCommandRemove != gOptions.command)
{
usage("option %s cannot be used with -show or -set "
"commands", option);
}
gOptions.command = kCommandRemove;
gOptions.removeItems = reallocf(gOptions.removeItems,
sizeof(*gOptions.removeItems) *
(gOptions.numRemoveItems + 1));
struct remove_item* item =
&gOptions.removeItems[gOptions.numRemoveItems++];
memset(item, 0, sizeof(*item));
item->type = kRemoveTool;
if (!*argv) {
usage("missing %s platform", option);
}
item->platform = platform_id_for_name(*argv);
if (0 == item->platform) {
item->platform = (uint32_t)strtol(*argv, NULL, 0);
}
if (0 == item->platform) {
usage("unknown platform: %s", *argv);
}
argv++; argc--;
if (!*argv) {
usage("missing %s tool", option);
}
item->tool = tool_id_for_name(*argv);
if (0 == item->tool) {
item->tool = (uint32_t)strtol(*argv, NULL, 0);
}
if (0 == item->tool) {
usage("unknown tool: %s", *argv);
}
}
else if (0 == strcmp("-remove-build-version", *argv))
{
const char* option = *argv;
argv++; argc--;
if (kCommandUnset != gOptions.command &&
kCommandRemove != gOptions.command)
{
usage("option %s cannot be used with -show or -set "
"commands", option);
}
gOptions.command = kCommandRemove;
gOptions.removeItems = reallocf(gOptions.removeItems,
sizeof(*gOptions.removeItems) *
(gOptions.numRemoveItems + 1));
struct remove_item* item =
&gOptions.removeItems[gOptions.numRemoveItems++];
memset(item, 0, sizeof(*item));
item->type = kRemoveBuild;
if (!*argv) {
usage("missing %s platform", option);
}
item->platform = platform_id_for_name(*argv);
if (0 == item->platform) {
item->platform = (uint32_t)strtol(*argv, NULL, 0);
}
if (0 == item->platform) {
usage("unknown platform: %s", *argv);
}
}
else if (0 == strcmp("-remove-source-version", *argv))
{
const char* option = *argv;
if (kCommandUnset != gOptions.command &&
kCommandRemove != gOptions.command)
{
usage("option %s cannot be used with -show or -set "
"commands", option);
}
gOptions.command = kCommandRemove;
gOptions.removeItems = reallocf(gOptions.removeItems,
sizeof(*gOptions.removeItems) *
(gOptions.numRemoveItems + 1));
struct remove_item* item =
&gOptions.removeItems[gOptions.numRemoveItems++];
memset(item, 0, sizeof(*item));
item->type = kRemoveSource;
}
else if (0 == strcmp("-set-tool", *argv) ||
0 == strcmp("-set-build-tool", *argv))
{
const char* option = *argv;
argv++; argc--;
if (kCommandUnset != gOptions.command &&
kCommandSet != gOptions.command)
{
usage("option %s cannot be used with -show or -remove "
"commands", option);
}
gOptions.command = kCommandSet;
gOptions.setItems = reallocf(gOptions.setItems,
sizeof(*gOptions.setItems) *
(gOptions.numSetItems + 1));
struct set_item* item =
&gOptions.setItems[gOptions.numSetItems++];
memset(item, 0, sizeof(*item));
item->type = kAddTool;
if (!*argv) {
usage("missing %s platform", option);
}
item->platform = platform_id_for_name(*argv);
if (0 == item->platform) {
item->platform = (uint32_t)strtol(*argv, NULL, 0);
}
if (0 == item->platform) {
usage("unknown platform: %s", *argv);
}
argv++; argc--;
if (!*argv) {
usage("missing %s tool", option);
}
item->tool = tool_id_for_name(*argv);
if (0 == item->tool) {
item->tool = (uint32_t)strtol(*argv, NULL, 0);
}
if (0 == item->tool) {
usage("unknown tool: %s", *argv);
}
argv++; argc--;
if (!*argv) {
usage("missing %s version", option);
}
if (parse_version_xyz(*argv, &item->version)) {
usage("bad version: %s", gProgramName, *argv);
}
}
else if (0 == strcmp("-set-build", *argv) ||
0 == strcmp("-set-build-version", *argv) ||
0 == strcmp("-set-version-min", *argv))
{
const char* option = *argv;
argv++; argc--;
if (kCommandUnset != gOptions.command &&
kCommandSet != gOptions.command)
{
usage("option %s cannot be used with -show or -remove "
"commands", option);
}
gOptions.command = kCommandSet;
gOptions.setItems = reallocf(gOptions.setItems,
sizeof(*gOptions.setItems) *
(gOptions.numSetItems + 1));
struct set_item* item =
&gOptions.setItems[gOptions.numSetItems++];
memset(item, 0, sizeof(*item));
item->type = kSetBuild;
item->versmin = (0 == strcmp("-set-version-min", option));
if (!*argv) {
usage("missing %s platform", option);
}
item->platform = platform_id_for_name(*argv);
if (0 == item->platform) {
item->platform = (uint32_t)strtol(*argv, NULL, 0);
}
if (0 == item->platform) {
usage("unknown platform: %s", *argv);
}
if (item->versmin && 0 == platform_vmlc_for_id(item->platform)){
usage("version min unsupported for platform: %s", *argv);
}
argv++; argc--;
if (!*argv) {
usage("missing %s min_version", option);
}
if (parse_version_xyz(*argv, &item->version)) {
usage("bad min_version: %s", gProgramName, *argv);
}
argv++; argc--;
if (!*argv) {
usage("missing %s sdk_version", option);
}
if (parse_version_xyz(*argv, &item->sdk_vers)) {
usage("bad sdk_version: %s", gProgramName, *argv);
}
while (*(argv + 1) && 0 == strcmp("-tool", *(argv + 1)))
{
uint32_t platform = item->platform;
argv++; argc--;
argv++; argc--;
if (item->versmin) {
usage("-tool cannot be used with %s", option);
}
gOptions.setItems = reallocf(gOptions.setItems,
sizeof(*gOptions.setItems) *
(gOptions.numSetItems + 1));
item = &gOptions.setItems[gOptions.numSetItems++];
memset(item, 0, sizeof(*item));
item->type = kAddTool;
item->platform = platform;
if (!*argv) {
usage("missing %s -tool tool", option);
}
item->tool = tool_id_for_name(*argv);
if (0 == item->tool) {
item->tool = (uint32_t)strtol(*argv, NULL, 0);
}
if (0 == item->tool) {
usage("unknown tool: %s", *argv);
}
argv++; argc--;
if (!*argv) {
usage("missing %s -tool version", option);
}
if (parse_version_xyz(*argv, &item->version)) {
usage("bad version: %s", gProgramName, *argv);
}
} } else if (0 == strcmp("-set-source", *argv) ||
0 == strcmp("-set-source-version", *argv))
{
const char* option = *argv;
argv++; argc--;
if (kCommandUnset != gOptions.command &&
kCommandSet != gOptions.command)
{
usage("option %s cannot be used with -show or -remove "
"commands", *argv);
}
gOptions.command = kCommandSet;
gOptions.setItems = reallocf(gOptions.setItems,
sizeof(*gOptions.setItems) *
(gOptions.numSetItems + 1));
struct set_item* item =
&gOptions.setItems[gOptions.numSetItems++];
memset(item, 0, sizeof(*item));
item->type = kSetSource;
if (!*argv) {
usage("missing %s version", option);
}
if (parse_version_abcde(*argv, &item->src_vers)) {
usage("bad version: %s", gProgramName, *argv);
}
} else if (0 == strcmp("-show", *argv) ||
0 == strcmp("-show-all", *argv))
{
if (gOptions.command)
usage("option %s cannot be used with other commands",
*argv);
gOptions.command = kCommandShow;
gOptions.show = kShowAll;
}
else if (0 == strcmp("-show-build", *argv) ||
0 == strcmp("-show-build-version", *argv))
{
if (gOptions.command)
usage("option %s cannot be used with other commands",
*argv);
gOptions.command = kCommandShow;
gOptions.show = kShowBuild;
}
else if (0 == strcmp("-show-source", *argv) ||
0 == strcmp("-show-source-version", *argv))
{
if (gOptions.command)
usage("option %s cannot be used with other commands",
*argv);
gOptions.command = kCommandShow;
gOptions.show = kShowSource;
}
else if (0 == strcmp("-show-space", *argv))
{
if (gOptions.command)
usage("option %s cannot be used with other commands",
*argv);
gOptions.command = kCommandShow;
gOptions.show = kShowSpace;
}
else if (0 == strcmp("-", *argv))
{
read_options = false;
}
else
{
usage("unknown option: %s", *argv);
}
} else
{
if (gOptions.inPath)
usage("only one input file must be specified");
gOptions.inPath = *argv;
}
argv++; argc--;
}
if (!gOptions.inPath)
usage("one input file must be specified");
if (gOptions.command == kCommandUnset)
usage("a -show, -set, or -remove command must be specified");
if ((gOptions.command==kCommandSet || gOptions.command==kCommandRemove) &&
!gOptions.outPath)
usage("one output file must be specified");
if (gOptions.command==kCommandShow && gOptions.outPath)
usage("-show commands do not write output");
for (uint32_t iitem = 0; iitem < gOptions.numSetItems; ++iitem)
{
if (kSetBuild != gOptions.setItems[iitem].type)
continue;
for (uint32_t jitem = 0; jitem < iitem; ++jitem)
{
if (kSetBuild != gOptions.setItems[jitem].type)
continue;
if (gOptions.setItems[iitem].platform ==
gOptions.setItems[jitem].platform)
{
uint32_t plid = gOptions.setItems[iitem].platform;
const char* plname = platform_name_for_id(plid);
if (plname)
usage("more than one build version specified for "
"platform %s", plname);
else
usage("more than one build version specified for "
"platform #%u", plid);
}
}
}
bool foundsrc = false;
for (uint32_t iitem = 0; iitem < gOptions.numSetItems; ++iitem)
{
if (kSetSource != gOptions.setItems[iitem].type)
continue;
if (foundsrc) {
usage("more than one source version specified");
}
foundsrc = true;
}
for (uint32_t iitem = 0; iitem < gOptions.numSetItems; ++iitem)
{
if (kAddTool != gOptions.setItems[iitem].type)
continue;
for (uint32_t jitem = 0; jitem < iitem; ++jitem)
{
if (kAddTool != gOptions.setItems[jitem].type)
continue;
if (gOptions.setItems[iitem].platform ==
gOptions.setItems[jitem].platform &&
gOptions.setItems[iitem].tool ==
gOptions.setItems[jitem].tool)
{
uint32_t plid = gOptions.setItems[iitem].platform;
const char* plname = platform_name_for_id(plid);
uint32_t tlid = gOptions.setItems[iitem].tool;
const char* tlname = tool_name_for_id(tlid);
if (plname && tlname)
usage("more than one tool version specified for "
"platform %s, tool %s", plname, tlname);
else if (plname)
usage("more than one tool version specified for "
"platform %s, tool #%u", plname, tlid);
if (tlname)
usage("more than one tool version specified for "
"platform #%u, tool %s", plid, tlname);
else
usage("more than one tool version specified for "
"platform #%u, tool #%u", plid, tlid);
}
}
}
int res = process();
return res ? EXIT_FAILURE : EXIT_SUCCESS;
}
int process(void)
{
struct file file;
int res = 0;
if (0 == res)
res = file_read(gOptions.inPath, &file);
if (0 == res && gOptions.narch)
{
for (uint32_t iopt = 0; iopt < gOptions.narch; ++iopt)
{
const NXArchInfo* a = gOptions.archs[iopt];
bool found = false;
for (uint32_t iarch = 0; false == found && iarch < file.nfat_arch;
++iarch)
{
struct fat_arch* b = &file.fat_archs[iarch];
if (a->cputype == b->cputype &&
(a->cpusubtype & ~CPU_SUBTYPE_MASK) ==
(b->cpusubtype & ~CPU_SUBTYPE_MASK))
found = true;
}
if (false == found) {
fprintf(stderr, "%s error: %s file does not contain "
"architecture: %s\n", gProgramName, gOptions.inPath,
a->name);
res = -1;
}
}
}
for (uint32_t iarch = 0; 0 == res && iarch < file.nfat_arch; ++iarch)
{
res = file_select_macho(gOptions.inPath, &file, iarch);
if (0 == res && gOptions.narch)
{
bool found = false;
for (uint32_t j = 0; false == found && j < gOptions.narch; ++j)
{
if (gOptions.archs[j]->cputype == file.mh.cputype &&
(gOptions.archs[j]->cpusubtype & ~CPU_SUBTYPE_MASK) ==
(file.mh.cpusubtype & ~CPU_SUBTYPE_MASK))
{
found = true;
}
}
if (!found)
continue;
}
if (0 == res) {
if (kCommandShow == gOptions.command) {
res = command_show(&file);
} else if (kCommandSet == gOptions.command) {
res = command_set(&file);
} else if (kCommandRemove == gOptions.command) {
res = command_remove(&file);
}
}
}
if (0 == res &&
(gOptions.command == kCommandSet || gOptions.command == kCommandRemove))
{
res = file_write(gOptions.outPath, &file);
}
return res;
}
int command_remove(struct file* fb)
{
struct lcmds* lcmds = fb->lcmds;
struct set_item* items = NULL;
uint32_t nitem = 0;
for (uint32_t iitem = 0; iitem < gOptions.numRemoveItems; ++iitem)
{
if (kRemoveTool != gOptions.removeItems[iitem].type)
continue;
uint32_t platform = gOptions.removeItems[iitem].platform;
struct build_version_command* bv = NULL;
bool found = false;
if (!found)
{
for (uint32_t jitem = 0; !found &&
jitem < gOptions.numRemoveItems; ++jitem)
{
if (kRemoveBuild == gOptions.removeItems[jitem].type &&
platform == gOptions.removeItems[jitem].platform) {
found = true;
}
}
}
if (!found)
{
for (uint32_t icmd = 0; !found && icmd < lcmds->count; ++icmd)
{
struct load_command* lc = lcmds->items[icmd];
if (lc->cmd == LC_BUILD_VERSION)
{
bv = (struct build_version_command*)lc;
found = (platform == bv->platform);
}
else if (lc->cmd == LC_VERSION_MIN_MACOSX ||
lc->cmd == LC_VERSION_MIN_IPHONEOS ||
lc->cmd == LC_VERSION_MIN_WATCHOS ||
lc->cmd == LC_VERSION_MIN_TVOS)
{
if (platform_id_for_vmlc(lc->cmd) == platform) {
fprintf(stderr, "%s error: %s version min load "
"commands do not support tool versions\n",
gProgramName, gOptions.inPath);
return -1;
}
}
}
}
if (!found)
{
const char* plname = platform_name_for_id(platform);
if (plname)
fprintf(stderr, "%s error: no build verion load command "
"found for platform %s\n", gProgramName, plname);
else
fprintf(stderr, "%s error: no build verion load command "
"found for platform #%u\n", gProgramName, platform);
return -1;
}
if (bv)
{
items = reallocf(items, sizeof(*items) * (nitem+1));
struct set_item* item = &items[nitem++];
memset(item, 0, sizeof(*item));
item->type = kSetBuild;
item->platform = platform;
item->version = bv->minos;
item->sdk_vers = bv->sdk;
for (uint32_t itool = 0; itool < bv->ntools; ++itool)
{
struct build_tool_version* bt =
(struct build_tool_version*)
(((unsigned char*)(bv+1)) +
(itool * sizeof(struct build_tool_version)));
found = false;
for (uint32_t jitem = 0; !found &&
jitem < gOptions.numRemoveItems; ++jitem)
{
if (platform == gOptions.removeItems[jitem].platform &&
bt->tool == gOptions.removeItems[jitem].tool) {
found = true;
}
}
if (!found) {
items = reallocf(items, sizeof(*items) * (nitem+1));
item = &items[nitem++];
memset(item, 0, sizeof(*item));
item->type = kAddTool;
item->platform = platform;
item->tool = bt->tool;
item->version = bt->version;
}
}
} }
off_t sectoffset = fb->fat_archs[fb->fat_arch_idx].size;
uint32_t modvlcsize = 0;
for (uint32_t icmd = 0; icmd < lcmds->count; ++icmd)
{
struct load_command* lc = lcmds->items[icmd];
if (lc->cmd == LC_SEGMENT)
{
struct segment_command* sg = (struct segment_command*)lc;
for (uint32_t isect = 0; isect < sg->nsects; ++isect)
{
struct section* sc = (struct section*)
(((unsigned char*)(sg+1)) + isect * sizeof(*sc));
if (sc->flags & S_ZEROFILL)
continue;
if (sc->offset < sectoffset)
sectoffset = sc->offset;
}
}
else if (lc->cmd == LC_SEGMENT_64)
{
struct segment_command_64* sg = (struct segment_command_64*)lc;
for (uint32_t isect = 0; isect < sg->nsects; ++isect)
{
struct section_64* sc = (struct section_64*)
(((unsigned char*)(sg+1)) + isect * sizeof(*sc));
if (sc->flags & S_ZEROFILL)
continue;
if (sc->offset < sectoffset)
sectoffset = sc->offset;
}
}
else if (lc->cmd == LC_VERSION_MIN_MACOSX ||
lc->cmd == LC_VERSION_MIN_IPHONEOS ||
lc->cmd == LC_VERSION_MIN_WATCHOS ||
lc->cmd == LC_VERSION_MIN_TVOS ||
lc->cmd == LC_BUILD_VERSION)
{
bool modify = false;
uint32_t platform;
if (lc->cmd == LC_BUILD_VERSION) {
struct build_version_command* bv =
(struct build_version_command*)lc;
platform = bv->platform;
}
else {
platform = platform_id_for_vmlc(lc->cmd);
}
for (uint32_t iitem=0; !modify &&
iitem < gOptions.numRemoveItems; ++iitem)
{
if (gOptions.removeItems[iitem].type == kRemoveBuild &&
gOptions.removeItems[iitem].platform == platform)
modify = true;
}
for (uint32_t iitem=0; !modify && iitem < nitem; ++iitem)
{
if (items[iitem].type == kSetBuild &&
items[iitem].platform == platform)
modify = true;
}
if (modify)
modvlcsize += lc->cmdsize;
}
else if (lc->cmd == LC_SOURCE_VERSION)
{
bool modify = false;
for (uint32_t iitem=0; !modify &&
iitem < gOptions.numRemoveItems; ++iitem)
{
if (gOptions.removeItems[iitem].type == kRemoveSource)
modify = true;
}
if (modify)
modvlcsize += lc->cmdsize;
}
}
uint32_t sizeofnewcmds = fb->mh.sizeofcmds;
sizeofnewcmds -= modvlcsize;
for (uint32_t iitem=0; iitem < nitem; ++iitem)
{
if (items[iitem].type == kAddTool)
sizeofnewcmds += sizeof(struct build_tool_version);
if (items[iitem].type == kSetBuild && !items[iitem].versmin)
sizeofnewcmds += sizeof(struct build_version_command);
}
uint32_t totalcmdspace = (uint32_t)(sectoffset - fb->mh_size);
if (totalcmdspace < sizeofnewcmds)
{
if (fb->nfat_arch > 1 || gOptions.narch) {
const NXArchInfo* archInfo = NULL;
archInfo = NXGetArchInfoFromCpuType(fb->mh.cputype,
fb->mh.cpusubtype);
if (archInfo)
fprintf(stderr, "%s error: %s (%s) not enough space to "
"hold load commands\n", gProgramName,
gOptions.inPath, archInfo->name);
else
fprintf(stderr, "%s error: %s (%u, %u) not enough space to "
"hold load commands\n", gProgramName,
gOptions.inPath, fb->mh.cputype, fb->mh.cpusubtype);
}
else {
fprintf(stderr, "%s error: %s not enough space to hold load "
"commands\n", gProgramName, gOptions.inPath);
}
return -1;
}
unsigned char* newcmds = calloc(1, totalcmdspace);
unsigned char* p = fb->lcs;
unsigned char* q = newcmds;
uint32_t ncmds = 0;
for (uint32_t icmd = 0; icmd < lcmds->count; ++icmd)
{
struct load_command* lc = lcmds->items[icmd];
bool copy = true;
if (lc->cmd == LC_VERSION_MIN_MACOSX ||
lc->cmd == LC_VERSION_MIN_IPHONEOS ||
lc->cmd == LC_VERSION_MIN_WATCHOS ||
lc->cmd == LC_VERSION_MIN_TVOS ||
lc->cmd == LC_BUILD_VERSION)
{
uint32_t platform;
if (lc->cmd == LC_BUILD_VERSION) {
struct build_version_command* bv =
(struct build_version_command*)lc;
platform = bv->platform;
}
else {
platform = platform_id_for_vmlc(lc->cmd);
}
for (uint32_t iitem=0; copy &&
iitem < gOptions.numRemoveItems; ++iitem)
{
if (gOptions.removeItems[iitem].type == kRemoveBuild &&
gOptions.removeItems[iitem].platform == platform)
copy = false;
}
for (uint32_t iitem=0; copy && iitem < nitem; ++iitem)
{
if (items[iitem].type == kSetBuild &&
items[iitem].platform == platform)
copy = false;
}
}
else if (lc->cmd == LC_SOURCE_VERSION)
{
for (uint32_t iitem=0; copy &&
iitem < gOptions.numRemoveItems; ++iitem)
{
if (gOptions.removeItems[iitem].type == kRemoveSource)
copy = false;
}
}
if (copy) {
memcpy(q, p, lc->cmdsize);
q += lc->cmdsize;
ncmds += 1;
}
p += lc->cmdsize;
}
for (uint32_t iitem=0; iitem < nitem; ++iitem)
{
if (items[iitem].type == kSetBuild) {
struct build_version_command* bv =
(struct build_version_command*)q;
bv->cmd = LC_BUILD_VERSION;
bv->cmdsize = sizeof(*bv); bv->platform = items[iitem].platform;
bv->minos = items[iitem].version;
bv->sdk = items[iitem].sdk_vers;
bv->ntools = 0;
q += bv->cmdsize;
struct build_tool_version* tools =
(struct build_tool_version*)q;
for (uint32_t itool=iitem + 1; itool < nitem; ++itool) {
if (items[itool].type != kAddTool)
continue;
if (items[itool].platform != bv->platform)
continue;
struct build_tool_version* bt =
(struct build_tool_version*)q;
bt->tool = items[itool].tool;
bt->version = items[itool].version;
q += sizeof(*bt);
bv->ntools += 1;
bv->cmdsize += sizeof(*bt);
}
if (fb->swap) {
swap_build_version_command(bv, gByteOrder);
swap_build_tool_version(tools, bv->ntools, gByteOrder);
}
ncmds += 1;
}
}
struct mach_header_64 mh;
memcpy(&mh, &fb->mh, sizeof(mh));
mh.sizeofcmds = sizeofnewcmds;
mh.ncmds = ncmds;
if (fb->swap)
swap_mach_header_64(&mh, gByteOrder);
q = fb->buf + fb->fat_archs[fb->fat_arch_idx].offset;
memcpy(q, &mh, fb->mh_size);
q += fb->mh_size;
memcpy(q, newcmds, totalcmdspace);
free(newcmds);
free(items);
return 0;
}
int command_set(struct file* fb)
{
struct lcmds* lcmds = fb->lcmds;
struct set_item* items = NULL;
uint32_t nitem = 0;
for (uint32_t iitem = 0; iitem < gOptions.numSetItems; ++iitem)
{
if (kAddTool == gOptions.setItems[iitem].type)
continue;
items = reallocf(items, sizeof(*items) * (nitem+1));
memcpy(&items[nitem++], &gOptions.setItems[iitem], sizeof(*items));
}
for (uint32_t iitem = 0; iitem < gOptions.numSetItems; ++iitem)
{
if (kAddTool != gOptions.setItems[iitem].type)
continue;
uint32_t platform = gOptions.setItems[iitem].platform;
struct build_version_command* bv = NULL;
bool found = false;
if (!found)
{
for (uint32_t jitem = 0; !found && jitem < nitem; ++jitem)
{
if (platform == items[jitem].platform &&
kAddTool != items[jitem].type)
{
if (items[jitem].versmin) {
fprintf(stderr, "%s error: version min load commands "
"do not support tool versions\n", gProgramName);
return -1;
}
found = true;
}
}
}
if (!found)
{
for (uint32_t icmd = 0; !found && icmd < lcmds->count; ++icmd)
{
struct load_command* lc = lcmds->items[icmd];
if (lc->cmd == LC_BUILD_VERSION)
{
bv = (struct build_version_command*)lc;
found = (platform == bv->platform);
}
else if (lc->cmd == LC_VERSION_MIN_MACOSX ||
lc->cmd == LC_VERSION_MIN_IPHONEOS ||
lc->cmd == LC_VERSION_MIN_WATCHOS ||
lc->cmd == LC_VERSION_MIN_TVOS)
{
if (platform_id_for_vmlc(lc->cmd) == platform) {
fprintf(stderr, "%s error: %s version min load "
"commands do not support tool versions\n",
gProgramName, gOptions.inPath);
return -1;
}
}
}
}
if (!found)
{
const char* plname = platform_name_for_id(platform);
if (plname)
fprintf(stderr, "%s error: no build verion load command found "
"for platform %s\n", gProgramName, plname);
else
fprintf(stderr, "%s error: no build verion load command found "
"for platform #%u\n", gProgramName, platform);
return -1;
}
if (bv)
{
items = reallocf(items, sizeof(*items) * (nitem+1));
struct set_item* item = &items[nitem++];
memset(item, 0, sizeof(*item));
item->type = kSetBuild;
item->platform = platform;
item->version = bv->minos;
item->sdk_vers = bv->sdk;
if (!gOptions.replace) {
for (uint32_t itool = 0; itool < bv->ntools; ++itool)
{
struct build_tool_version* bt =
(struct build_tool_version*)
(((unsigned char*)(bv+1)) +
(itool * sizeof(struct build_tool_version)));
found = false;
for (uint32_t jitem = 0; !found &&
jitem < gOptions.numSetItems; ++jitem)
{
if (platform == gOptions.setItems[jitem].platform &&
bt->tool == gOptions.setItems[jitem].tool) {
found = true;
}
}
if (!found) {
items = reallocf(items, sizeof(*items) * (nitem+1));
item = &items[nitem++];
memset(item, 0, sizeof(*item));
item->type = kAddTool;
item->platform = platform;
item->tool = bt->tool;
item->version = bt->version;
}
}
}
}
items = reallocf(items, sizeof(*items) * (nitem+1));
memcpy(&items[nitem++], &gOptions.setItems[iitem], sizeof(*items));
}
off_t sectoffset = fb->fat_archs[fb->fat_arch_idx].size;
uint32_t modvlcsize = 0;
for (uint32_t icmd = 0; icmd < lcmds->count; ++icmd)
{
struct load_command* lc = lcmds->items[icmd];
if (lc->cmd == LC_SEGMENT)
{
struct segment_command* sg = (struct segment_command*)lc;
for (uint32_t isect = 0; isect < sg->nsects; ++isect)
{
struct section* sc = (struct section*)
(((unsigned char*)(sg+1)) + isect * sizeof(*sc));
if (sc->flags & S_ZEROFILL)
continue;
if (sc->offset < sectoffset)
sectoffset = sc->offset;
}
}
else if (lc->cmd == LC_SEGMENT_64)
{
struct segment_command_64* sg = (struct segment_command_64*)lc;
for (uint32_t isect = 0; isect < sg->nsects; ++isect)
{
struct section_64* sc = (struct section_64*)
(((unsigned char*)(sg+1)) + isect * sizeof(*sc));
if (sc->flags & S_ZEROFILL)
continue;
if (sc->offset < sectoffset)
sectoffset = sc->offset;
}
}
else if (lc->cmd == LC_VERSION_MIN_MACOSX ||
lc->cmd == LC_VERSION_MIN_IPHONEOS ||
lc->cmd == LC_VERSION_MIN_WATCHOS ||
lc->cmd == LC_VERSION_MIN_TVOS ||
lc->cmd == LC_BUILD_VERSION)
{
bool modify = gOptions.replace;
uint32_t platform;
if (lc->cmd == LC_BUILD_VERSION) {
struct build_version_command* bv =
(struct build_version_command*)lc;
platform = bv->platform;
}
else {
platform = platform_id_for_vmlc(lc->cmd);
}
for (uint32_t iitem=0; !modify && iitem < nitem; ++iitem)
{
if (items[iitem].type == kSetBuild &&
items[iitem].platform == platform)
modify = true;
}
if (modify)
modvlcsize += lc->cmdsize;
}
else if (lc->cmd == LC_SOURCE_VERSION)
{
bool modify = false;
for (uint32_t iitem=0; !modify && iitem < nitem; ++iitem)
{
if (items[iitem].type == kSetSource)
modify = true;
}
if (modify)
modvlcsize += lc->cmdsize;
}
}
uint32_t sizeofnewcmds = fb->mh.sizeofcmds;
sizeofnewcmds -= modvlcsize;
for (uint32_t iitem=0; iitem < nitem; ++iitem)
{
if (items[iitem].type == kSetSource)
sizeofnewcmds += sizeof(struct source_version_command);
if (items[iitem].type == kAddTool)
sizeofnewcmds += sizeof(struct build_tool_version);
if (items[iitem].type == kSetBuild && items[iitem].versmin)
sizeofnewcmds += sizeof(struct version_min_command);
if (items[iitem].type == kSetBuild && !items[iitem].versmin)
sizeofnewcmds += sizeof(struct build_version_command);
}
uint32_t totalcmdspace = (uint32_t)(sectoffset - fb->mh_size);
if (totalcmdspace < sizeofnewcmds)
{
if (fb->nfat_arch > 1 || gOptions.narch) {
const NXArchInfo* archInfo = NULL;
archInfo = NXGetArchInfoFromCpuType(fb->mh.cputype,
fb->mh.cpusubtype);
if (archInfo)
fprintf(stderr, "%s error: %s (%s) not enough space to "
"hold load commands\n", gProgramName,
gOptions.inPath, archInfo->name);
else
fprintf(stderr, "%s error: %s (%u, %u) not enough space to "
"hold load commands\n", gProgramName,
gOptions.inPath, fb->mh.cputype, fb->mh.cpusubtype);
}
else {
fprintf(stderr, "%s error: %s not enough space to hold load "
"commands\n", gProgramName, gOptions.inPath);
}
return -1;
}
unsigned char* newcmds = calloc(1, totalcmdspace);
unsigned char* p = fb->lcs;
unsigned char* q = newcmds;
uint32_t ncmds = 0;
for (uint32_t icmd = 0; icmd < lcmds->count; ++icmd)
{
struct load_command* lc = lcmds->items[icmd];
bool copy = true;
if (lc->cmd == LC_VERSION_MIN_MACOSX ||
lc->cmd == LC_VERSION_MIN_IPHONEOS ||
lc->cmd == LC_VERSION_MIN_WATCHOS ||
lc->cmd == LC_VERSION_MIN_TVOS ||
lc->cmd == LC_BUILD_VERSION)
{
copy = !gOptions.replace;
uint32_t platform;
if (lc->cmd == LC_BUILD_VERSION) {
struct build_version_command* bv =
(struct build_version_command*)lc;
platform = bv->platform;
}
else {
platform = platform_id_for_vmlc(lc->cmd);
}
for (uint32_t iitem=0; copy && iitem < nitem; ++iitem)
{
if (items[iitem].type == kSetBuild &&
items[iitem].platform == platform)
copy = false;
}
}
else if (lc->cmd == LC_SOURCE_VERSION)
{
copy = true;
for (uint32_t iitem=0; copy && iitem < nitem; ++iitem)
{
if (items[iitem].type == kSetSource)
copy = false;
}
}
if (copy) {
memcpy(q, p, lc->cmdsize);
q += lc->cmdsize;
ncmds += 1;
}
p += lc->cmdsize;
}
for (uint32_t iitem=0; iitem < nitem; ++iitem)
{
if (items[iitem].type == kSetBuild) {
if (items[iitem].versmin) {
struct version_min_command* vmc =
(struct version_min_command*)q;
vmc->cmd = platform_vmlc_for_id(items[iitem].platform);
vmc->cmdsize = sizeof(*vmc);
vmc->version = items[iitem].version;
vmc->sdk = items[iitem].sdk_vers;
if (fb->swap)
swap_version_min_command(vmc, gByteOrder);
q += vmc->cmdsize;
}
else {
struct build_version_command* bv =
(struct build_version_command*)q;
bv->cmd = LC_BUILD_VERSION;
bv->cmdsize = sizeof(*bv); bv->platform = items[iitem].platform;
bv->minos = items[iitem].version;
bv->sdk = items[iitem].sdk_vers;
bv->ntools = 0;
q += bv->cmdsize;
struct build_tool_version* tools =
(struct build_tool_version*)q;
for (uint32_t itool=iitem + 1; itool < nitem; ++itool) {
if (items[itool].type != kAddTool)
continue;
if (items[itool].platform != bv->platform)
continue;
struct build_tool_version* bt =
(struct build_tool_version*)q;
bt->tool = items[itool].tool;
bt->version = items[itool].version;
q += sizeof(*bt);
bv->ntools += 1;
bv->cmdsize += sizeof(*bt);
}
if (fb->swap) {
swap_build_version_command(bv, gByteOrder);
swap_build_tool_version(tools, bv->ntools, gByteOrder);
}
}
ncmds += 1;
}
if (items[iitem].type == kSetSource) {
struct source_version_command* sv =
(struct source_version_command*)q;
sv->cmd = LC_SOURCE_VERSION;
sv->cmdsize = sizeof(*sv);
sv->version = items[iitem].src_vers;
if (fb->swap)
swap_source_version_command(sv, gByteOrder);
q += sv->cmdsize;
ncmds += 1;
}
}
struct mach_header_64 mh;
memcpy(&mh, &fb->mh, sizeof(mh));
mh.sizeofcmds = sizeofnewcmds;
mh.ncmds = ncmds;
if (fb->swap)
swap_mach_header_64(&mh, gByteOrder);
q = fb->buf + fb->fat_archs[fb->fat_arch_idx].offset;
memcpy(q, &mh, fb->mh_size);
q += fb->mh_size;
memcpy(q, newcmds, totalcmdspace);
free(newcmds);
free(items);
return 0;
}
int command_show(struct file* fb)
{
const NXArchInfo* archInfo = NULL;
if (fb->nfat_arch > 1 || gOptions.narch) {
archInfo = NXGetArchInfoFromCpuType(fb->mh.cputype,
fb->mh.cpusubtype);
}
printf("%s", gOptions.inPath);
if (archInfo) {
printf(" (architecture %s)", archInfo->name);
}
printf(":\n");
struct lcmds* lcmds = fb->lcmds;
off_t sectoffset = fb->fat_archs[fb->fat_arch_idx].size;
for (uint32_t icmd = 0; icmd < lcmds->count; ++icmd)
{
struct load_command* lc = lcmds->items[icmd];
if (lc->cmd == LC_SEGMENT)
{
struct segment_command* sg = (struct segment_command*)lc;
for (uint32_t isect = 0; isect < sg->nsects; ++isect)
{
struct section* sc = (struct section*)
(((unsigned char*)(sg+1)) + isect * sizeof(*sc));
if (sc->flags & S_ZEROFILL)
continue;
if (sc->offset < sectoffset)
sectoffset = sc->offset;
}
}
else if (lc->cmd == LC_SEGMENT_64)
{
struct segment_command_64* sg = (struct segment_command_64*)lc;
for (uint32_t isect = 0; isect < sg->nsects; ++isect)
{
struct section_64* sc = (struct section_64*)
(((unsigned char*)(sg+1)) + isect * sizeof(*sc));
if (sc->flags & S_ZEROFILL)
continue;
if (sc->offset < sectoffset)
sectoffset = sc->offset;
}
}
else if (lc->cmd == LC_VERSION_MIN_MACOSX ||
lc->cmd == LC_VERSION_MIN_IPHONEOS ||
lc->cmd == LC_VERSION_MIN_WATCHOS ||
lc->cmd == LC_VERSION_MIN_TVOS)
{
if (gOptions.show == kShowAll || gOptions.show == kShowBuild)
{
struct version_min_command* vmc =
(struct version_min_command*)lc;
printf("Load command %u\n", icmd);
print_version_min_command(vmc);
}
}
else if (lc->cmd == LC_BUILD_VERSION)
{
if (gOptions.show == kShowAll || gOptions.show == kShowBuild)
{
struct build_version_command* bv =
(struct build_version_command*)lc;
printf("Load command %u\n", icmd);
print_build_version_command(bv);
unsigned char* q = (unsigned char*)(bv+1);
for (uint32_t itool = 0; itool < bv->ntools; ++itool) {
struct build_tool_version* tv =
(struct build_tool_version*)q;
print_build_tool_version(tv->tool, tv->version);
q += sizeof(*tv);
}
}
}
else if (lc->cmd == LC_SOURCE_VERSION)
{
if (gOptions.show == kShowAll ||
gOptions.show == kShowSource)
{
struct source_version_command* sv =
(struct source_version_command*)lc;
printf("Load command %u\n", icmd);
print_source_version_command(sv);
}
}
}
if (gOptions.show == kShowSpace ) {
printf(" Mach header size: %5d\n", (int)fb->mh_size);
printf(" Load command size: %5d\n", (int)fb->mh.sizeofcmds);
printf(" Available space: %5d\n",
(int)(sectoffset - fb->mh_size - fb->mh.sizeofcmds));
printf(" Total: %5d\n", (int)sectoffset);
}
return 0;
}
int file_read(const char* path, struct file* fb)
{
memset(fb, 0, sizeof(*fb));
int fd = open(path, O_RDONLY);
if (-1 == fd) {
fprintf(stderr, "%s error: %s: can't open file: %s\n",
gProgramName, path, strerror(errno));
return -1;
}
struct stat sb;
if (fstat(fd, &sb)) {
fprintf(stderr, "%s error: %s: can't stat file: %s\n",
gProgramName, path, strerror(errno));
close(fd);
return -1;
}
fb->len = sb.st_size;
fb->mode = sb.st_mode;
fb->buf = calloc(1, fb->len);
ssize_t readed = read(fd, fb->buf, fb->len); if (-1 == readed) {
fprintf(stderr, "%s error: %s: can't read file: %s\n",
gProgramName, path, strerror(errno));
close(fd);
free(fb->buf);
return -1;
}
else if (readed != fb->len) {
fprintf(stderr, "%s error: %s: partial read (0x%zx of 0x%llx)\n",
gProgramName, path, readed, fb->len);
close(fd);
free(fb->buf);
return -1;
}
if (close(fd)) {
fprintf(stderr, "%s warning: %s: can't close file: %s\n",
gProgramName, path, strerror(errno));
}
uint32_t magic;
if (fb->len < sizeof(magic)) {
fprintf(stderr, "%s error: %s file is not mach-o\n",
gProgramName, path);
free(fb->buf);
return -1;
}
magic = *(uint32_t*)(fb->buf);
if (magic == MH_MAGIC || magic == MH_CIGAM ||
magic == MH_MAGIC_64 || magic == MH_CIGAM_64)
{
if (magic == MH_MAGIC || magic == MH_CIGAM) {
fb->mh_size = sizeof(struct mach_header);
} else {
fb->mh_size = sizeof(struct mach_header_64);
}
if (fb->len < fb->mh_size) {
fprintf(stderr, "%s error: %s file is not mach-o\n",
gProgramName, path);
free(fb->buf);
return -1;
}
if (magic == MH_MAGIC || magic == MH_CIGAM) {
memcpy(&fb->mh, fb->buf, fb->mh_size);
fb->mh.reserved = 0;
}
else if (magic == MH_MAGIC_64 || magic == MH_CIGAM_64) {
memcpy(&fb->mh, fb->buf, fb->mh_size);
}
if (magic == MH_CIGAM || magic == MH_CIGAM_64) {
fb->swap = true;
swap_mach_header_64(&fb->mh, NXHostByteOrder());
} else {
fb->swap = false;
}
fb->nfat_arch = 1;
fb->fat_archs = calloc(1, sizeof(struct fat_arch));
fb->fat_archs[0].cputype = fb->mh.cputype;
fb->fat_archs[0].cpusubtype = fb->mh.cpusubtype;
fb->fat_archs[0].offset = 0;
fb->fat_archs[0].size = (uint32_t)fb->len;
fb->fat_archs[0].align = 0;
}
else if (magic == FAT_MAGIC || magic == FAT_CIGAM) {
struct fat_header fh;
if (fb->len < sizeof(fh)) {
fprintf(stderr, "%s error: %s file is not mach-o\n",
gProgramName, path);
free(fb->buf);
return -1;
}
memcpy(&fh, fb->buf, sizeof(fh));
swap_fat_header(&fh, NXHostByteOrder());
if (fb->len < sizeof(fh) + sizeof(struct fat_arch) * (fh.nfat_arch+1)) {
fprintf(stderr, "%s error: %s file is not mach-o\n",
gProgramName, path);
free(fb->buf);
return -1;
}
fb->nfat_arch = fh.nfat_arch;
fb->fat_archs = malloc(sizeof(*fb->fat_archs) * (fb->nfat_arch + 1));
memcpy(fb->fat_archs, ((struct fat_header*)fb->buf) + 1,
sizeof(*fb->fat_archs) * (fb->nfat_arch + 1));
swap_fat_arch(fb->fat_archs, fb->nfat_arch + 1, NXHostByteOrder());
bool foundARM32 = false;
bool foundARM64 = false;
for (uint32_t i = 0; i < fb->nfat_arch; ++i)
{
if (CPU_TYPE_ARM == fb->fat_archs[i].cputype)
foundARM32 = true;
if (CPU_TYPE_ARM64 == fb->fat_archs[i].cputype)
foundARM64 = true;
}
if (foundARM32 && !foundARM64 &&
CPU_TYPE_ARM64 == fb->fat_archs[fb->nfat_arch].cputype)
{
fb->nfat_arch += 1;
}
for (uint32_t i = 0; i < fb->nfat_arch; ++i)
{
if (fb->len < fb->fat_archs[i].offset + fb->fat_archs[i].size) {
fprintf(stderr, "%s error: %s file #%d for cputype (%u, %u) "
"extends beyond file boundaries (%llu < %u + %u)\n",
gProgramName, path, i, fb->fat_archs[i].cputype,
fb->fat_archs[i].cpusubtype, fb->len,
fb->fat_archs[i].offset, fb->fat_archs[i].size);
free(fb->buf);
free(fb->fat_archs);
return -1;
}
}
}
else {
fprintf(stderr, "%s error: %s file is not mach-o\n",
gProgramName, path);
free(fb->buf);
return -1;
}
return 0;
}
int file_select_macho(const char* path, struct file* fb, uint32_t index)
{
if (index >= fb->nfat_arch) {
fprintf(stderr, "%s internal error: reading beyond fat_arch array\n",
gProgramName);
return -1;
}
fb->fat_arch_idx = index;
uint32_t offset = fb->fat_archs[index].offset;
const unsigned char* buf = fb->buf + offset;
uint32_t magic = *(uint32_t*)(buf);
if (magic == MH_MAGIC || magic == MH_CIGAM ||
magic == MH_MAGIC_64 || magic == MH_CIGAM_64)
{
if (magic == MH_MAGIC || magic == MH_CIGAM) {
fb->mh_size = sizeof(struct mach_header);
} else {
fb->mh_size = sizeof(struct mach_header_64);
}
if (fb->len < offset + fb->mh_size) {
fprintf(stderr, "%s error: %s file #%d for cputype (%u, %u) "
"is not mach-o\n",
gProgramName, path, index, fb->fat_archs[index].cputype,
fb->fat_archs[index].cpusubtype);
return -1;
}
if (magic == MH_MAGIC || magic == MH_CIGAM) {
memcpy(&fb->mh, buf, fb->mh_size);
fb->mh.reserved = 0;
}
else if (magic == MH_MAGIC_64 || magic == MH_CIGAM_64) {
memcpy(&fb->mh, buf, fb->mh_size);
}
if (magic == MH_CIGAM || magic == MH_CIGAM_64) {
fb->swap = true;
swap_mach_header_64(&fb->mh, NXHostByteOrder());
} else {
fb->swap = false;
}
if (fb->len < offset + fb->mh_size + fb->mh.sizeofcmds) {
fprintf(stderr, "%s error: %s file #%d for cputype (%u, %u) "
"load command extend beyond length of file\n",
gProgramName, path, index, fb->fat_archs[index].cputype,
fb->fat_archs[index].cpusubtype);
return -1;
}
fb->lcs = (unsigned char*)&buf[fb->mh_size];
if (fb->lcmds)
lcmds_free(fb->lcmds);
fb->lcmds = lcmds_alloc(fb);
if (!fb->lcmds)
return -1;
}
else {
fprintf(stderr, "%s error: %s file #%d for cputype (%u, %u) "
"is not mach-o\n",
gProgramName, path, index, fb->fat_archs[index].cputype,
fb->fat_archs[index].cpusubtype);
return -1;
}
return 0;
}
int file_write(const char* path, struct file* fb)
{
int res = 0;
bool warn = true;
for (uint32_t iarch = 0; 0 == res && warn && iarch < fb->nfat_arch; ++iarch)
{
res = file_select_macho(path, fb, iarch);
if (res)
continue;
struct lcmds* lcmds = fb->lcmds;
for (uint32_t icmd = 0; warn && icmd < lcmds->count; ++icmd) {
struct load_command* lc = lcmds->items[icmd];
if (lc->cmd == LC_CODE_SIGNATURE) {
fprintf(stderr, "%s warning: code signature will be invalid "
"for %s\n", gProgramName, path);
warn = false;
}
}
}
size_t pathlen = strlen(path);
const char* prefix = ".XXXXXX";
size_t tmpsize = pathlen + strlen(prefix) + 1;
char* tmppath = calloc(1, tmpsize);
snprintf(tmppath, tmpsize, "%s%s", path, prefix);
int fd = -1;
if (0 == res) {
fd = mkstemp(tmppath);
if (-1 == fd) {
fprintf(stderr, "%s error: ", gProgramName);
perror("mkstemp");
res = -1;
}
}
if (0 == res) {
ssize_t wrote = write(fd, fb->buf, fb->len);
if (wrote == -1) {
fprintf(stderr, "%s error: %s: write: %s\n", gProgramName, tmppath,
strerror(errno));
res = -1;
}
else if (wrote != fb->len) {
fprintf(stderr, "%s error: %s: partial write (0x%zx of 0x%llx)\n",
gProgramName, tmppath, wrote, fb->len);
res = -1;
}
}
if (0 == res && close(fd)) {
fprintf(stderr, "%s error: %s: can't close file: %s\n",
gProgramName, tmppath, strerror(errno));
res = -1;
}
if (0 == res && chmod(tmppath, fb->mode)) {
fprintf(stderr, "%s error: %s: can't change file permissions: %s\n",
gProgramName, tmppath, strerror(errno));
res = -1;
}
if (0 == res && rename(tmppath, path)) {
fprintf(stderr, "%s error: %s: can't rename file: %s\n", gProgramName,
tmppath, strerror(errno));
res = -1;
}
if (res) {
unlink(tmppath);
}
free(tmppath);
return res;
}
struct lcmds* lcmds_alloc(struct file* fb)
{
struct lcmds* lcmds = calloc(1, sizeof(*lcmds));
if (!lcmds)
return NULL;
lcmds->count = fb->mh.ncmds;
lcmds->items = calloc(lcmds->count, sizeof(*lcmds->items));
if (!lcmds->items) {
lcmds_free(lcmds);
return NULL;
}
unsigned char* p = fb->lcs;
for (uint32_t icmd = 0; icmd < fb->mh.ncmds; ++icmd)
{
if (fb->mh.sizeofcmds < p - fb->lcs + sizeof(struct load_command)) {
fprintf(stderr, "%s error: load command %d extends beyond "
"range\n", gProgramName, icmd);
lcmds_free(lcmds);
return NULL;
}
struct load_command lc;
memcpy(&lc, p, sizeof(lc));
if (fb->swap) {
swap_load_command(&lc, gByteOrder);
}
if (fb->mh.sizeofcmds < p - fb->lcs + lc.cmdsize) {
fprintf(stderr, "%s error: load command %d (0x%X) extends "
"beyond range\n", gProgramName, icmd, lc.cmd);
lcmds_free(lcmds);
return NULL;
}
lcmds->items[icmd] = calloc(1, lc.cmdsize);
memcpy(lcmds->items[icmd], p, lc.cmdsize);
if (LC_SEGMENT == lc.cmd)
{
if (lc.cmdsize < sizeof(struct segment_command)) {
fprintf(stderr,
"%s error: %s file for cputype (%u, %u) load "
"command %d (0x%x) has incorrect size (%d)\n",
gProgramName, gOptions.inPath, fb->mh.cputype,
fb->mh.cpusubtype, icmd, lc.cmd, lc.cmdsize);
lcmds_free(lcmds);
return NULL;
}
struct segment_command* sg =
(struct segment_command*)lcmds->items[icmd];
if (fb->swap) {
swap_segment_command(sg, gByteOrder);
}
if (lc.cmdsize != sizeof(struct segment_command) +
sizeof(struct section) * sg->nsects) {
fprintf(stderr,
"%s error: %s file for cputype (%u, %u) load "
"command %d (0x%x) has incorrect size (%d)\n",
gProgramName, gOptions.inPath, fb->mh.cputype,
fb->mh.cpusubtype, icmd, lc.cmd, lc.cmdsize);
lcmds_free(lcmds);
return NULL;
}
struct section* sc = (struct section*)(sg+1);
if (fb->swap) {
swap_section(sc, sg->nsects, gByteOrder);
}
}
else if (LC_SEGMENT_64 == lc.cmd)
{
if (lc.cmdsize < sizeof(struct segment_command_64)) {
fprintf(stderr,
"%s error: %s file for cputype (%u, %u) load "
"command %d (0x%x) has incorrect size (%d)\n",
gProgramName, gOptions.inPath, fb->mh.cputype,
fb->mh.cpusubtype, icmd, lc.cmd, lc.cmdsize);
lcmds_free(lcmds);
return NULL;
}
struct segment_command_64* sg =
(struct segment_command_64*)lcmds->items[icmd];
if (fb->swap) {
swap_segment_command_64(sg, gByteOrder);
}
if (lc.cmdsize != sizeof(struct segment_command_64) +
sizeof(struct section_64) * sg->nsects) {
fprintf(stderr,
"%s error: %s file for cputype (%u, %u) load "
"command %d (0x%x) has incorrect size (%d)\n",
gProgramName, gOptions.inPath, fb->mh.cputype,
fb->mh.cpusubtype, icmd, lc.cmd, lc.cmdsize);
lcmds_free(lcmds);
return NULL;
}
struct section_64* sc = (struct section_64*)(sg + 1);
if (fb->swap) {
swap_section_64(sc, sg->nsects, gByteOrder);
}
}
else if (LC_VERSION_MIN_TVOS == lc.cmd ||
LC_VERSION_MIN_MACOSX == lc.cmd ||
LC_VERSION_MIN_WATCHOS == lc.cmd ||
LC_VERSION_MIN_IPHONEOS == lc.cmd)
{
if (sizeof(struct version_min_command) != lc.cmdsize) {
fprintf(stderr,
"%s error: %s file for cputype (%u, %u) load "
"command %d (0x%x) has incorrect size (%d)\n",
gProgramName, gOptions.inPath, fb->mh.cputype,
fb->mh.cpusubtype, icmd, lc.cmd, lc.cmdsize);
lcmds_free(lcmds);
return NULL;
}
struct version_min_command* vm =
(struct version_min_command*)lcmds->items[icmd];
if (fb->swap) {
swap_version_min_command(vm, gByteOrder);
}
}
else if (LC_BUILD_VERSION == lc.cmd)
{
if (lc.cmdsize < sizeof(struct build_version_command)) {
fprintf(stderr,
"%s error: %s file for cputype (%u, %u) load "
"command %d (0x%x) has incorrect size (%d)\n",
gProgramName, gOptions.inPath, fb->mh.cputype,
fb->mh.cpusubtype, icmd, lc.cmd, lc.cmdsize);
lcmds_free(lcmds);
return NULL;
}
struct build_version_command* bv =
(struct build_version_command*)lcmds->items[icmd];
if (fb->swap) {
swap_build_version_command(bv, gByteOrder);
}
if (lc.cmdsize < sizeof(struct build_version_command) +
sizeof(struct build_tool_version) * bv->ntools) {
fprintf(stderr,
"%s error: %s file for cputype (%u, %u) load "
"command %d (0x%x) has incorrect size (%d)\n",
gProgramName, gOptions.inPath, fb->mh.cputype,
fb->mh.cpusubtype, icmd, lc.cmd, lc.cmdsize);
lcmds_free(lcmds);
return NULL;
}
struct build_tool_version* tv = (struct build_tool_version*)(bv+1);
if (fb->swap) {
swap_build_tool_version(tv, bv->ntools, gByteOrder);
}
}
else if (LC_SOURCE_VERSION == lc.cmd)
{
if (lc.cmdsize != sizeof(struct source_version_command)) {
fprintf(stderr,
"%s error: %s file for cputype (%u, %u) load "
"command %d (0x%x) has incorrect size (%d)\n",
gProgramName, gOptions.inPath, fb->mh.cputype,
fb->mh.cpusubtype, icmd, lc.cmd, lc.cmdsize);
lcmds_free(lcmds);
return NULL;
}
struct source_version_command* sv =
(struct source_version_command*)lcmds->items[icmd];
if (fb->swap)
swap_source_version_command(sv, gByteOrder);
}
else {
memcpy(lcmds->items[icmd], &lc, sizeof(lc));
}
p += lc.cmdsize;
} return lcmds;
}
void lcmds_free(struct lcmds* lcmds)
{
for (uint32_t icmd = 0; icmd < lcmds->count; ++icmd) {
if (lcmds->items[icmd])
free(lcmds->items[icmd]);
}
if (lcmds->items)
free(lcmds->items);
free(lcmds);
}
int parse_version_abcde(const char* rostr, uint64_t *version)
{
uint64_t a, b, c, d, e;
char* str = strdup(rostr);
char* start = str;
char* end = strchr(start, '.');
if (end) *end = 0;
a = (uint64_t)strtoull(start, NULL, 10);
if (end) {
start = end + 1;
end = strchr(start, '.');
if (end) *end = 0;
b = (uint64_t)strtoull(start, NULL, 10);
} else {
b = 0;
}
if (end) {
start = end + 1;
end = strchr(start, '.');
if (end) *end = 0;
c = (uint64_t)strtoull(start, NULL, 10);
} else {
c = 0;
}
if (end) {
start = end + 1;
end = strchr(start, '.');
if (end) *end = 0;
d = (uint64_t)strtoull(start, NULL, 10);
} else {
d = 0;
}
if (end) {
start = end + 1;
end = strchr(start, '.');
if (end) *end = 0;
e = (uint64_t)strtoull(start, NULL, 10);
} else {
e = 0;
}
free(str);
if (end) {
fprintf(stderr, "%s error: version has more than 5 components: %s\n",
gProgramName, rostr);
return -1;
}
if (a > 0xFFFFFF) {
fprintf(stderr, "%s error: major version %llu is too large\n",
gProgramName, a);
return -1;
}
if (b > 0x3FF) {
fprintf(stderr, "%s error: minor version %llu is too large\n",
gProgramName, b);
return -1;
}
if (c > 0x3FF) {
fprintf(stderr, "%s error: revision version %llu is too large\n",
gProgramName, c);
return -1;
}
if (d > 0x3FF) {
fprintf(stderr, "%s error: penultimate version %llu is too large\n",
gProgramName, d);
return -1;
}
if (e > 0x3FF) {
fprintf(stderr, "%s error: ultimate version %llu is too large\n",
gProgramName, e);
return -1;
}
if (version) {
*version = (a << 40) | (b << 30) | (c << 20) | (d << 10) | e;
}
return 0;
}
int parse_version_xyz(const char* rostr, uint32_t *version)
{
uint32_t x, y, z;
char* str = strdup(rostr);
char* start = str;
char* end = strchr(start, '.');
if (end) *end = 0;
x = (uint32_t)strtoul(start, NULL, 10);
if (end) {
start = end + 1;
end = strchr(start, '.');
if (end) *end = 0;
y = (uint32_t)strtoul(start, NULL, 10);
} else {
y = 0;
}
if (end) {
start = end + 1;
end = strchr(start, '.');
if (end) *end = 0;
z = (uint32_t)strtoul(start, NULL, 10);
} else {
z = 0;
}
free(str);
if (end) {
fprintf(stderr, "%s error: version has more than 3 components: %s\n",
gProgramName, rostr);
return -1;
}
if (x > 0xFFFF) {
fprintf(stderr, "%s error: major version is too large\n", gProgramName);
return -1;
}
if (y > 0xFF) {
fprintf(stderr, "%s error: minor version is too large\n", gProgramName);
return -1;
}
if (z > 0xFF) {
fprintf(stderr, "%s error: revision version is too large\n",
gProgramName);
return -1;
}
if (version) {
*version = (x << 16) | (y << 8) | z;
}
return 0;
}
struct platform_entry {
uint32_t platformid;
const char* name;
uint32_t vmlc;
};
static const struct platform_entry kPlatforms[] = {
{ PLATFORM_MACOS, "macos", LC_VERSION_MIN_MACOSX },
{ PLATFORM_IOS, "ios", LC_VERSION_MIN_IPHONEOS },
{ PLATFORM_WATCHOS, "watchos", LC_VERSION_MIN_WATCHOS },
{ PLATFORM_TVOS, "tvos", LC_VERSION_MIN_TVOS },
{ PLATFORM_BRIDGEOS, "bridgeos", 0 },
{ PLATFORM_MACCATALYST, "maccatalyst", 0 },
{ PLATFORM_MACCATALYST, "uikitformac", 0 }, { PLATFORM_IOSSIMULATOR, "iossim", 0 },
{ PLATFORM_WATCHOSSIMULATOR, "watchossim", 0 },
{ PLATFORM_DRIVERKIT, "driverkit", 0 },
};
uint32_t platform_id_for_name(const char* name)
{
int count = sizeof(kPlatforms) / sizeof(*kPlatforms);
for (int i = 0; i < count; ++i)
{
if (name && 0 == strcmp(name, kPlatforms[i].name))
return kPlatforms[i].platformid;
}
return 0;
}
uint32_t platform_id_for_vmlc(uint32_t vmlc)
{
int count = sizeof(kPlatforms) / sizeof(*kPlatforms);
for (int i = 0; i < count; ++i)
{
if (kPlatforms[i].vmlc == vmlc)
return kPlatforms[i].platformid;
}
return 0;
}
const char* platform_name_for_id(uint32_t platform_id)
{
int count = sizeof(kPlatforms) / sizeof(*kPlatforms);
for (int i = 0; i < count; ++i)
{
if (platform_id == kPlatforms[i].platformid)
return kPlatforms[i].name;
}
return NULL;
}
uint32_t platform_vmlc_for_id(uint32_t platform_id)
{
int count = sizeof(kPlatforms) / sizeof(*kPlatforms);
for (int i = 0; i < count; ++i)
{
if (platform_id == kPlatforms[i].platformid)
return kPlatforms[i].vmlc;
}
return 0;
}
void print_version_xyz(const char* label, uint32_t version)
{
const char* space = " ";
const char* nl = "\n";
if (label == NULL)
label = space = nl = "";
if((version & 0xff) == 0)
printf("%s%s%u.%u%s",
label,
space,
version >> 16,
(version >> 8) & 0xff,
nl);
else
printf("%s%s%u.%u.%u%s",
label,
space,
version >> 16,
(version >> 8) & 0xff,
version & 0xff,
nl);
}
void print_version_min_command(struct version_min_command *vd)
{
if(vd->cmd == LC_VERSION_MIN_MACOSX)
printf(" cmd LC_VERSION_MIN_MACOSX\n");
else if(vd->cmd == LC_VERSION_MIN_IPHONEOS)
printf(" cmd LC_VERSION_MIN_IPHONEOS\n");
else if(vd->cmd == LC_VERSION_MIN_WATCHOS)
printf(" cmd LC_VERSION_MIN_WATCHOS\n");
else if(vd->cmd == LC_VERSION_MIN_TVOS)
printf(" cmd LC_VERSION_MIN_TVOS\n");
else
printf(" cmd %u (?)\n", vd->cmd);
printf(" cmdsize %u", vd->cmdsize);
if(vd->cmdsize != sizeof(struct version_min_command))
printf(" Incorrect size\n");
else
printf("\n");
print_version_xyz(" version", vd->version);
if(vd->sdk == 0)
printf(" sdk n/a\n");
else
print_version_xyz(" sdk", vd->sdk);
}
void print_build_version_command(struct build_version_command *bv)
{
if(bv->cmd == LC_BUILD_VERSION)
printf(" cmd LC_BUILD_VERSION\n");
else
printf(" cmd %u (?)\n", bv->cmd);
printf(" cmdsize %u", bv->cmdsize);
if(bv->cmdsize != sizeof(struct build_version_command) +
bv->ntools * sizeof(struct build_tool_version))
printf(" Incorrect size\n");
else
printf("\n");
printf(" platform ");
switch(bv->platform){
case PLATFORM_MACOS:
printf("MACOS\n");
break;
case PLATFORM_IOS:
printf("IOS\n");
break;
case PLATFORM_TVOS:
printf("TVOS\n");
break;
case PLATFORM_WATCHOS:
printf("WATCHOS\n");
break;
case PLATFORM_BRIDGEOS:
printf("BRIDGEOS\n");
break;
case PLATFORM_MACCATALYST:
printf("MACCATALYST\n");
break;
case PLATFORM_IOSSIMULATOR:
printf("IOSSIMULATOR\n");
break;
case PLATFORM_TVOSSIMULATOR:
printf("TVOSSIMULATOR\n");
break;
case PLATFORM_WATCHOSSIMULATOR:
printf("WATCHOSSIMULATOR\n");
break;
case PLATFORM_DRIVERKIT:
printf("DRIVERKIT\n");
break;
default:
printf("%u\n", bv->platform);
break;
}
print_version_xyz(" minos", bv->minos);
if(bv->sdk == 0)
printf(" sdk n/a\n");
else{
print_version_xyz(" sdk", bv->sdk);
}
printf(" ntools %u\n", bv->ntools);
}
void print_build_tool_version(uint32_t tool, uint32_t version)
{
printf(" tool ");
switch(tool){
case TOOL_CLANG:
printf("CLANG\n");
break;
case TOOL_SWIFT:
printf("SWIFT\n");
break;
case TOOL_LD:
printf("LD\n");
break;
default:
printf("%u\n", tool);
break;
}
print_version_xyz(" version", version);
}
void print_source_version_command(struct source_version_command *sv)
{
uint64_t a, b, c, d, e;
printf(" cmd LC_SOURCE_VERSION\n");
printf(" cmdsize %u", sv->cmdsize);
if(sv->cmdsize != sizeof(struct source_version_command))
printf(" Incorrect size\n");
else
printf("\n");
a = (sv->version >> 40) & 0xffffff;
b = (sv->version >> 30) & 0x3ff;
c = (sv->version >> 20) & 0x3ff;
d = (sv->version >> 10) & 0x3ff;
e = sv->version & 0x3ff;
if(e != 0)
printf(" version %llu.%llu.%llu.%llu.%llu\n", a, b, c, d, e);
else if(d != 0)
printf(" version %llu.%llu.%llu.%llu\n", a, b, c, d);
else if(c != 0)
printf(" version %llu.%llu.%llu\n", a, b, c);
else
printf(" version %llu.%llu\n", a, b);
}
struct tool_entry {
uint32_t toolid;
const char* name;
};
static const struct tool_entry kTools[] = {
{ TOOL_CLANG, "clang" },
{ TOOL_SWIFT, "swift" },
{ TOOL_LD, "ld" },
};
uint32_t tool_id_for_name(const char* name)
{
int count = sizeof(kTools) / sizeof(*kTools);
for (int i = 0; i < count; ++i)
{
if (name && 0 == strcmp(name, kTools[i].name))
return kTools[i].toolid;
}
return 0;
}
const char* tool_name_for_id(uint32_t tool_id)
{
int count = sizeof(kTools) / sizeof(*kTools);
for (int i = 0; i < count; ++i)
{
if (tool_id == kTools[i].toolid)
return kTools[i].name;
}
return NULL;
}
void usage(const char * __restrict format, ...)
{
const char* basename = strrchr(gProgramName, '/');
if (basename)
basename++;
else
basename = gProgramName;
va_list args;
va_start(args, format);
if (format) {
fprintf(stderr, "error: ");
vfprintf(stderr, format, args);;
fprintf(stderr, "\n");
}
va_end(args);
int size = snprintf(NULL, 0, "%s", basename);
char* spaces = calloc(1, size + 1);
memset(spaces, ' ', size);
fprintf(stderr,
"usage: %s [-arch <arch>] ... <show_command> <file>\n", basename);
fprintf(stderr,
" %s [-arch <arch>] ... <set_command> ... [-replace] [-output <output>] "
"<file>\n", basename);
fprintf(stderr,
" %s [-arch <arch>] ... <remove_command> ... [-output <output>] <file>\n",
basename);
fprintf(stderr,
" %s -help\n", basename);
fprintf(stderr, " show_command is exactly one of:\n");
fprintf(stderr, " -show\n");
fprintf(stderr, " -show-build\n");
fprintf(stderr, " -show-source\n");
fprintf(stderr, " -show-space\n");
fprintf(stderr, " set_command is one or more of:\n");
fprintf(stderr, " -set-build-version <platform> <minos> <sdk> [-tool "
"<tool> <version>] ...\n");
fprintf(stderr, " -set-build-tool <platform> <tool> <version>\n");
fprintf(stderr, " -set-version-min <platform> <minos> <sdk>\n");
fprintf(stderr, " -set-source-version <version>\n");
fprintf(stderr, " remove_command is one or more of:\n");
fprintf(stderr, " -remove-build-version <platform>\n");
fprintf(stderr, " -remove-build-tool <platform> <tool>\n");
fprintf(stderr, " -remove-source-version\n");
if (gOptions.command == kCommandHelp) {
#if 0
fprintf(stderr, " arch is one of:\n");
for (const NXArchInfo* archInfo = NXGetAllArchInfos();
archInfo->name;
++archInfo)
{
fprintf(stderr, " %s\n", archInfo->name);
}
#endif
int count = sizeof(kPlatforms) / sizeof(*kPlatforms);
fprintf(stderr, " platform is one of:\n");
for (uint32_t i = 0; i < count; ++i) {
fprintf(stderr, " %s\n", kPlatforms[i].name);
}
count = sizeof(kTools) / sizeof(*kTools);
fprintf(stderr, " tool is one of:\n");
for (uint32_t i = 0; i < count; ++i) {
fprintf(stderr, " %s\n", kTools[i].name);
}
fprintf(stderr,
" platform and tool can also be specified by number\n");
}
exit(EXIT_FAILURE);
}