/* This file is part of Darling. Copyright (C) 2017 Lubos Dolezel Darling is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Darling is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Darling. If not, see . */ #include #include #include #include bool processArgs(int argc, const char** argv, const char** command); void resolvePlistEntry(const char* whatStr, CFPropertyListRef* parent, CFPropertyListRef* entry, char** last, bool autoCreate); void setEntry(const char* entryName, const char* entryValue); void printUsage(void); void printHelp(void); bool revertToFile(void); bool saveToFile(void); void runCommand(const char* cmd); void doPrint(const char* what); enum PropertyType { typeUnknown = 0, typeString, typeArray, typeDict, typeBool, typeReal, typeInteger, typeDate, typeData }; void addEntry(const char* entry, enum PropertyType type, const char* value); CFPropertyListRef parseValue(enum PropertyType type, const char* string); void deleteEntry(const char* entry); void copyEntry(const char* src, const char* dst); void mergeFile(const char* path, const char* entry); void importFile(const char* entry, const char* path); // Forces XML output when printing to screen bool forceXML = false; CFPropertyListRef plist = NULL; const char* outputFile = NULL; int main(int argc, const char **argv) { const char* command = NULL; if (!processArgs(argc, argv, &command)) { printUsage(); return 1; } if (!outputFile) return 0; if (!revertToFile()) return 1; if (command != NULL) { runCommand(command); if (!saveToFile()) return 1; } else { while (true) { char buffer[200]; printf("Command: "); if (!fgets(buffer, sizeof(buffer), stdin)) break; size_t len = strlen(buffer); if (buffer[len-1] == '\n') buffer[len-1] = '\0'; runCommand(buffer); } } return EXIT_SUCCESS; } void printUsage() { puts("Usage: PlistBuddy [-cxh] \n" " -c \"\" execute command, otherwise run in interactive mode\n" " -x output will be in the form of an xml plist where appropriate\n" " -h print the complete help info, with command guide\n"); } void printHelp() { puts("Command Format:\n\n" " Help - Prints this information\n" " Exit - Exits the program, changes are not saved to the file\n" " Save - Saves the current changes to the file\n" " Revert - Reloads the last saved version of the file\n" " Clear [] - Clears out all existing entries, and creates root of Type\n" " Print [] - Prints value of Entry. Otherwise, prints file\n" " Set - Sets the value at Entry to Value\n" " Add [] - Adds Entry to the plist, with value Value\n" " Copy - Copies the EntrySrc property to EntryDst\n" " Delete - Deletes Entry from the plist\n" " Merge [] - Adds the contents of file.plist to Entry\n" " Import - Creates or sets Entry the contents of file\n" "\n" "Entry Format:\n" " Entries consist of property key names delimited by colons. Array items\n" " are specified by a zero-based integer index. Examples:\n" " :CFBundleShortVersionString\n" " :CFBundleDocumentTypes:2:CFBundleTypeExtensions\n" "\n" "Types:\n" " string\n" " array\n" " dict\n" " bool\n" " real\n" " integer\n" " date\n" " data\n" "\n" "Examples:\n" " Set :CFBundleIdentifier com.apple.plistbuddy\n" " Sets the CFBundleIdentifier property to com.apple.plistbuddy\n" " Add :CFBundleGetInfoString string \"App version 1.0.1\"\n" " Adds the CFBundleGetInfoString property to the plist\n" " Add :CFBundleDocumentTypes: dict\n" " Adds a new item of type dict to the CFBundleDocumentTypes array\n" " Add :CFBundleDocumentTypes:0 dict\n" " Adds the new item to the beginning of the array\n" " Delete :CFBundleDocumentTypes:0 dict\n" " Deletes the FIRST item in the array\n" " Delete :CFBundleDocumentTypes\n" " Deletes the ENTIRE CFBundleDocumentTypes array\n"); } bool processArgs(int argc, const char** argv, const char** command) { if (argc <= 1) return false; for (int i = 1; i < argc; i++) { if (strcmp(argv[i], "-c") == 0) { if (i+1 >= argc) return false; *command = argv[++i]; } else if (strcmp(argv[i], "-x") == 0) { forceXML = true; } else if (strcmp(argv[i], "-h") == 0) { printHelp(); return true; } else if (i+1 >= argc) { outputFile = argv[i]; } else { puts("Invalid Arguments"); exit(1); } } return outputFile != NULL; } CFPropertyListRef loadPlist(const char* filePath) { SInt32 errorCode = 0; CFStringRef errorString = NULL; CFDataRef data = NULL; CFPropertyListRef plist; CFStringRef path = CFStringCreateWithCString(kCFAllocatorDefault, filePath, kCFStringEncodingUTF8); CFURLRef url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path, kCFURLPOSIXPathStyle, false); CFRelease(path); CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault, url, &data, NULL, NULL, &errorCode); CFRelease(url); if (errorCode != 0) { printf("Error Reading File: %s\n", filePath); return false; } plist = CFPropertyListCreateFromXMLData(kCFAllocatorDefault, data, kCFPropertyListMutableContainers, &errorString); if (errorString != NULL) { CFShow(errorString); CFRelease(errorString); printf("Error Reading File: %s\n", filePath); } return plist; } bool revertToFile(void) { if (plist != NULL) CFRelease(plist); if (access(outputFile, F_OK) != 0) { printf("File Doesn't Exist, Will Create: %s\n", outputFile); plist = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); return true; } else { bool rv; plist = loadPlist(outputFile); rv = plist != NULL; if (plist == NULL) plist = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); return rv; } } bool saveToFile(void) { bool rv = true; CFDataRef data; CFStringRef path = CFStringCreateWithCString(kCFAllocatorDefault, outputFile, kCFStringEncodingUTF8); CFURLRef url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path, kCFURLPOSIXPathStyle, false); CFRelease(path); data = CFPropertyListCreateXMLData(kCFAllocatorDefault, plist); if (data != NULL) { SInt32 errorCode = 0; CFURLWriteDataAndPropertiesToResource(url, data, NULL, &errorCode); CFRelease(data); if (errorCode != 0) { puts("Cannot Write File"); rv = false; } } else { puts("Cannot Format Plist"); rv = false; } CFRelease(url); return rv; } static const char* nextWord(const char* input) { int i; for (i = 0; isspace(input[i]) && input[i] != '\0'; i++) ; if (input[i]) return &input[i]; else return NULL; } bool isCommand(const char* input, const char* cmd, const char** next) { size_t len = strlen(cmd); if (strncasecmp(input, cmd, len) == 0 && (input[len] == ' ' || input[len] == '\0')) { if (next != NULL) *next = nextWord(input + len); return true; } else return false; } static enum PropertyType parseType(const char* type) { if (strcasecmp(type, "string") == 0) return typeString; else if (strcasecmp(type, "array") == 0) return typeArray; else if (strcasecmp(type, "dict") == 0) return typeDict; else if (strcasecmp(type, "bool") == 0) return typeBool; else if (strcasecmp(type, "real") == 0) return typeReal; else if (strcasecmp(type, "integer") == 0) return typeInteger; else if (strcasecmp(type, "date") == 0) return typeDate; else if (strcasecmp(type, "data") == 0) return typeData; else { printf("Unrecognized Type: %s\n", type); return typeUnknown; } } static enum PropertyType inferType(CFPropertyListRef obj) { CFTypeID typeId = CFGetTypeID(obj); if (typeId == CFStringGetTypeID()) return typeString; else if (typeId == CFArrayGetTypeID()) return typeArray; else if (typeId == CFDictionaryGetTypeID()) return typeDict; else if (typeId == CFBooleanGetTypeID()) return typeBool; else if (typeId == CFNumberGetTypeID()) { if (CFNumberIsFloatType((CFNumberRef) obj)) return typeReal; else return typeInteger; } else if (typeId == CFDateGetTypeID()) return typeDate; else if (typeId == CFDataGetTypeID()) return typeData; else return typeUnknown; } char* getWord(const char* cmd, const char** next) { if (cmd[0] == '"') { int i = 1, j = 0; char* out = strdup(cmd); for (; cmd[i] != '\0'; i++) { if (cmd[i] == '\\' && cmd[i+1] != '\0') { i++; switch (cmd[i]) { case '"': out[j++] = '"'; break; case 'n': out[j++] = '\n'; break; case 't': out[j++] = '\t'; break; default: out[j++] = '\\'; out[j++] = cmd[i]; } } else if (cmd[i] == '"') { out[j++] = '\0'; break; } else { out[j++] = cmd[i]; } } if (j > 0 && out[j-1] != '\0') { free(out); puts("Unterminated Quotes"); return NULL; } if (next) *next = nextWord(cmd + i + 1); return out; } else { char* p = strchr(cmd, ' '); if (p == NULL) { if (next) *next = NULL; return strdup(cmd); } else { char* rv = strndup(cmd, p-cmd); if (next) *next = nextWord(p); return rv; } } } void runCommand(const char* cmd) { const char* next = NULL; if (isCommand(cmd, "Exit", NULL) || isCommand(cmd, "Quit", NULL) || isCommand(cmd, "Bye", NULL)) { exit(0); } else if (isCommand(cmd, "Help", NULL)) { printHelp(); } else if (isCommand(cmd, "Save", &next)) { puts("Saving..."); saveToFile(); } else if (isCommand(cmd, "Revert", NULL)) { puts("Reverting to last saved state..."); revertToFile(); } else if (isCommand(cmd, "Print", &next) || isCommand(cmd, "ls", &next)) { doPrint(next); } else if (isCommand(cmd, "Clear", &next)) { enum PropertyType type = typeUnknown; if (next != NULL) type = parseType(next); if (type == typeUnknown) type = typeDict; } else if (isCommand(cmd, "Set", &next) || isCommand(cmd, "=", &next)) { // entry value if (!next) { puts("Missing arguments"); return; } char* entry = getWord(next, &next); if (!next) next = ""; setEntry(entry, next); free(entry); } else if (isCommand(cmd, "Add", &next) || isCommand(cmd, "+", &next)) { // entry type [value] if (!next) { puts("Missing arguments"); return; } char* entry = getWord(next, &next); if (!next) { free(entry); puts("Missing arguments"); return; } char* type = getWord(next, &next); enum PropertyType typeV = parseType(type); if (typeV == typeUnknown) { free(entry); free(type); return; } if (!next) next = ""; addEntry(entry, typeV, next); free(entry); free(type); } else if (isCommand(cmd, "Copy", &next) || isCommand(cmd, "cp", &next)) { // entrySrc entryDst if (!next) { puts("Missing arguments"); return; } char* src = getWord(next, &next); if (!next) { puts("Missing arguments"); return; } char* dst = getWord(next, NULL); copyEntry(src, dst); free(src); free(dst); } else if (isCommand(cmd, "Delete", &next) || isCommand(cmd, "rm", &next) || isCommand(cmd, "-", &next)) { // entry if (!next) { puts("Missing arguments"); return; } deleteEntry(next); } else if (isCommand(cmd, "Merge", &next)) { // file.plist [entryDst] if (!next) { puts("Missing arguments"); return; } char* path = getWord(next, &next); mergeFile(path, next); free(path); } else if (isCommand(cmd, "Import", &next)) { // entry file.plist if (!next) { puts("Missing arguments"); return; } char* entry = getWord(next, &next); if (!next) { puts("Missing arguments"); free(entry); return; } char* path = getWord(next, &next); importFile(entry, path); free(path); free(entry); } else { puts("Unrecognized Command"); } } void addEntry(const char* entry, enum PropertyType type, const char* value) { CFPropertyListRef parent, existing; CFPropertyListRef newValue; char* leafName = NULL; CFTypeID parentType; // printf("add entry (type %d) with value: %s\n", type, value); if (type == typeArray) { newValue = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); } else if (type == typeDict) { newValue = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); } else { newValue = parseValue(type, value); if (newValue == NULL) return; } resolvePlistEntry(entry, &parent, &existing, &leafName, true); if (parent == NULL) { printf("Add: Entry, \"%s\", Does Not Exist\n", entry); free(leafName); return; } parentType = CFGetTypeID(parent); // in case of arrays, we just insert at given position if (existing != NULL && parentType != CFArrayGetTypeID()) { printf("Add: \"%s\" Entry Already Exists\n", entry); goto out; } if (parentType == CFArrayGetTypeID()) { CFMutableArrayRef array = (CFMutableArrayRef) parent; CFIndex index = atoi(leafName); if (index < 0) index = 0; else if (index > CFArrayGetCount(array)) index = CFArrayGetCount(array); CFArrayInsertValueAtIndex(array, index, newValue); } else if (parentType == CFDictionaryGetTypeID()) { // printf("Add new entry named %s into dict %p, root is %p\n", leafName, parent, plist); CFStringRef key = CFStringCreateWithCString(kCFAllocatorDefault, leafName, kCFStringEncodingUTF8); CFDictionaryAddValue((CFMutableDictionaryRef) parent, key, newValue); CFRelease(key); } else { printf("Add: Can't Add Entry, \"%s\", to Parent\n", entry); } out: free(leafName); CFRelease(newValue); } CFPropertyListRef parseValue(enum PropertyType type, const char* string) { switch (type) { case typeString: case typeData: { char* stringOut; bool hasQuotes = string[0] == '"'; size_t len = strlen(string); size_t i = 0, j = 0; if (hasQuotes) { if (string[len-1] != '"') { puts("Parse Error: Unclosed Quotes"); return NULL; } i++; len--; } stringOut = (char*) malloc(len+1); for (; i < len; i++) { if (string[i] != '\\') { stringOut[j] = string[i]; j++; } else { if (i+1 == len) { free(stringOut); puts("Parse Error: Unclosed Quotes"); return NULL; } switch (string[++i]) { case '"': stringOut[j++] = '"'; break; case 'n': stringOut[j++] = '\n'; break; case 't': stringOut[j++] = '\t'; break; default: stringOut[j++] = string[i]; } } } stringOut[j] = '\0'; CFPropertyListRef rv; if (type == typeString) rv = CFStringCreateWithCString(kCFAllocatorDefault, stringOut, kCFStringEncodingUTF8); else rv = CFDataCreate(kCFAllocatorDefault, (const UInt8*) string, strlen(string)); free(stringOut); return rv; } case typeBool: if (strcasecmp(string, "true") == 0 || strcasecmp(string, "yes") == 0 || strcmp(string, "1") == 0) return kCFBooleanTrue; return kCFBooleanFalse; case typeInteger: { char* endptr; long long v; v = strtoll(string, &endptr, 0); if (endptr == string) { puts("Unrecognized Integer Format"); return NULL; } return CFNumberCreate(kCFAllocatorDefault, kCFNumberLongLongType, &v); } case typeReal: { char* endptr; double v; v = strtod(string, &endptr); if (endptr == string) { puts("Unrecognized Real Format"); return NULL; } return CFNumberCreate(kCFAllocatorDefault, kCFNumberFloat64Type, &v); } case typeDate: { struct tm tm; if (!strptime(string, "%a %b %d %H:%M:%S %Z %Y", &tm) && !strptime(string, "%c", &tm) && !strptime(string, "%D", &tm)) { puts("Unrecognized Date Format"); return NULL; } CFTimeZoneRef tz = CFTimeZoneCopyDefault(); CFAbsoluteTime at; CFGregorianDate date; date.day = tm.tm_mday; date.hour = tm.tm_hour; date.minute = tm.tm_min; date.month = tm.tm_mon + 1; date.second = tm.tm_sec; date.year = tm.tm_year + 1900; at = CFGregorianDateGetAbsoluteTime(date, tz); CFDateRef rv = CFDateCreate(kCFAllocatorDefault, at); CFRelease(tz); return rv; } default: { puts("Cannot parse this entry type"); return NULL; } } } void setEntry(const char* entry, const char* entryValue) { CFPropertyListRef parent, existing; CFPropertyListRef newValue; char* leafName = NULL; enum PropertyType type; resolvePlistEntry(entry, &parent, &existing, &leafName, false); if (existing == NULL) { free(leafName); printf("Set: Entry, \"%s\", Does Not Exist\n", entry); return; } type = inferType(existing); if (type == typeArray || type == typeDict) { free(leafName); puts("Set: Cannot Perform Set On Containers"); return; } newValue = parseValue(type, entryValue); if (newValue == NULL) { free(leafName); return; } if (CFGetTypeID(parent) == CFDictionaryGetTypeID()) { CFStringRef leafNameStr = CFStringCreateWithCString(kCFAllocatorDefault, leafName, kCFStringEncodingUTF8); CFDictionaryReplaceValue((CFMutableDictionaryRef) parent, leafNameStr, newValue); CFRelease(leafNameStr); } else { int index = atoi(leafName); CFArraySetValueAtIndex((CFMutableArrayRef) parent, index, newValue); } free(leafName); CFRelease(newValue); } static const char* strtok_empty(char** pstr, char delim) { char* p; char* str = *pstr; if (str == NULL) return NULL; while (str[0] == delim) str++; if ((p = strchr (str, delim)) != NULL) { *p = '\0'; *pstr = p + 1; } else { *pstr = NULL; } return str; } void resolvePlistEntry(const char* whatStr, CFPropertyListRef* parent, CFPropertyListRef* entry, char** last, bool autoCreate) { CFPropertyListRef pos = plist; CFPropertyListRef lastPos = NULL; const char *tok, *lastTok = ""; // trim head while (whatStr[0] == ':') whatStr++; // trim tail size_t len = strlen(whatStr); while(len > 0 && whatStr[len - 1] == ':') { len--; } char* whatStr2 = strndup(whatStr, len); char* p = whatStr2; while ((tok = strtok_empty(&p, ':')) && pos != NULL) { lastPos = pos; if (*tok) { CFTypeID typeId = CFGetTypeID(pos); if (typeId == CFDictionaryGetTypeID()) { CFStringRef key = CFStringCreateWithCString(kCFAllocatorDefault, tok, kCFStringEncodingUTF8); pos = (CFPropertyListRef) CFDictionaryGetValue((CFDictionaryRef) pos, key); if (pos == NULL && autoCreate && p != NULL) { pos = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFDictionaryAddValue((CFMutableDictionaryRef) lastPos, key, pos); } CFRelease(key); } else if (typeId == CFArrayGetTypeID()) { int index = atoi(tok); if (index < 0 || index >= CFArrayGetCount((CFArrayRef) pos)) { pos = NULL; } else { pos = (CFPropertyListRef) CFArrayGetValueAtIndex((CFArrayRef) pos, index); } } else { pos = NULL; } } lastTok = tok; } if (last != NULL) *last = strdup(lastTok); if (entry != NULL) *entry = pos; if (parent != NULL) *parent = (!tok) ? lastPos : NULL; free(whatStr2); } void prettyPrintPlist(CFPropertyListRef what, int indentNum) { CFTypeID typeId = CFGetTypeID(what); char* indent = __builtin_alloca(indentNum + 1); memset(indent, ' ', indentNum); indent[indentNum] = '\0'; if (typeId == CFStringGetTypeID()) { const char* str = CFStringGetCStringPtr((CFStringRef) what, kCFStringEncodingUTF8); printf("%s", str); } else if (typeId == CFArrayGetTypeID()) { CFArrayRef array = (CFArrayRef) what; CFIndex count = CFArrayGetCount(array); printf("Array {\n"); for (CFIndex i = 0; i < count; i++) { CFPropertyListRef elem = (CFPropertyListRef) CFArrayGetValueAtIndex(array, i); printf("%s ", indent); prettyPrintPlist(elem, indentNum + 4); printf("\n"); } printf("%s}", indent); } else if (typeId == CFDictionaryGetTypeID()) { CFDictionaryRef dict = (CFDictionaryRef) what; CFStringRef* keys; CFPropertyListRef* values; CFIndex count = CFDictionaryGetCount(dict); keys = malloc(sizeof(CFStringRef*) * count); values = malloc(sizeof(CFStringRef*) * count); CFDictionaryGetKeysAndValues(dict, (const void**) keys, (const void**) values); printf("Dict {\n"); for (CFIndex i = 0; i < count; i++) { printf("%s ", indent); prettyPrintPlist(keys[i], 0); printf(" = "); prettyPrintPlist(values[i], indentNum + 4); printf("\n"); } printf("%s}", indent); free(keys); free(values); } else if (typeId == CFBooleanGetTypeID()) { if (what == kCFBooleanTrue) printf("true"); else printf("false"); } else if (typeId == CFNumberGetTypeID()) { CFNumberRef num = (CFNumberRef) what; if (CFNumberIsFloatType(num)) { Float64 d; CFNumberGetValue(num, kCFNumberFloat64Type, &d); printf("%f", d); } else { long long l; CFNumberGetValue(num, kCFNumberLongLongType, &l); printf("%lld", l); } } else if (typeId == CFDateGetTypeID()) { CFAbsoluteTime t = CFDateGetAbsoluteTime((CFDateRef) what); CFTimeZoneRef tz = CFTimeZoneCopyDefault(); CFGregorianDate d = CFAbsoluteTimeGetGregorianDate(t, tz); struct tm tm; char buf[150]; tm.tm_mday = d.day; tm.tm_mon = d.month - 1; tm.tm_year = d.year - 1900; tm.tm_hour = d.hour; tm.tm_min = d.minute; tm.tm_sec = d.second; tm.tm_wday = CFAbsoluteTimeGetDayOfWeek(t, tz); if (tm.tm_wday == 7) tm.tm_wday = 0; tm.tm_zone = 0; tm.tm_isdst = 0; strftime(buf, sizeof(buf), "%a %b %d %H:%M:%S %Z %Y", &tm); printf("%s", buf); CFRelease(tz); } else if (typeId == CFDataGetTypeID()) { CFDataRef data = (CFDataRef) what; const UInt8* bytes = CFDataGetBytePtr(data); CFIndex len = CFDataGetLength(data); fwrite(bytes, 1, len, stdout); } } void doPrint(const char* whatStr) { CFPropertyListRef what = plist; CFPropertyListRef entry = plist; if (whatStr != NULL) { resolvePlistEntry(whatStr, &what, &entry, NULL, false); if (entry == NULL) { printf("Print: Entry, \"%s\", Does Not Exist\n", whatStr); return; } } if (!forceXML) { prettyPrintPlist(entry, 0); printf("\n"); } else { CFDataRef data = CFPropertyListCreateXMLData(kCFAllocatorDefault, entry); if (data != NULL) { prettyPrintPlist(data, 0); printf("\n"); CFRelease(data); } } } void deleteEntry(const char* entry) { CFPropertyListRef parent, existing; CFPropertyListRef newValue; char* leafName = NULL; resolvePlistEntry(entry, &parent, &existing, &leafName, false); if (existing == NULL) { printf("Delete: Entry, \"%s\", Does Not Exist\n", entry); goto out; } CFTypeID typeID = CFGetTypeID(parent); if (typeID == CFDictionaryGetTypeID()) { CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, leafName, kCFStringEncodingUTF8); CFDictionaryRemoveValue((CFMutableDictionaryRef) parent, str); CFRelease(str); } else { CFIndex idx = atoi(leafName); CFArrayRemoveValueAtIndex((CFMutableArrayRef) parent, idx); } out: free(leafName); } void copyEntry(const char* src, const char* dst) { CFPropertyListRef existing, entry, newParent, entryCopy; char* leafName = NULL; resolvePlistEntry(src, NULL, &entry, NULL, false); resolvePlistEntry(dst, &newParent, &existing, &leafName, true); if (entry == NULL) { printf("Copy: Entry, \"%s\", Does Not Exist\n", src); goto out; } if (existing != NULL) { printf("Copy: \"%s\" Entry Already Exists\n", dst); goto out; } entryCopy = CFPropertyListCreateDeepCopy(kCFAllocatorDefault, entry, kCFPropertyListMutableContainers); CFTypeID typeID = CFGetTypeID(newParent); if (typeID == CFDictionaryGetTypeID()) { CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, leafName, kCFStringEncodingUTF8); CFDictionaryAddValue((CFMutableDictionaryRef) newParent, str, entryCopy); CFRelease(str); } else { CFIndex idx = atoi(leafName); CFArrayInsertValueAtIndex((CFMutableArrayRef) newParent, idx, entryCopy); } CFRelease(entryCopy); out: free(leafName); } void mergeFile(const char* path, const char* entry) { CFPropertyListRef dest, fileContents; fileContents = loadPlist(path); if (fileContents == NULL) return; if (entry != NULL && *entry) { resolvePlistEntry(entry, NULL, &dest, NULL, true); if (dest == NULL) { printf("Merge: Entry, \"%s\", Does Not Exist\n", entry); CFRelease(fileContents); return; } } else dest = plist; CFTypeID typeID = CFGetTypeID(dest); CFTypeID sourceTypeID = CFGetTypeID(fileContents); if (typeID == CFArrayGetTypeID()) { if (sourceTypeID == CFArrayGetTypeID()) { CFArrayAppendArray((CFMutableArrayRef) dest, fileContents, CFRangeMake(0, CFArrayGetCount(fileContents))); } else if (sourceTypeID == CFDictionaryGetTypeID()) { CFIndex count = CFDictionaryGetCount((CFDictionaryRef) fileContents); void** values = malloc(sizeof(void*) * count); CFDictionaryGetKeysAndValues((CFDictionaryRef) fileContents, NULL, (const void**) values); CFArrayReplaceValues((CFMutableArrayRef) dest, CFRangeMake(CFArrayGetCount((CFArrayRef) dest) - 1, 0), (const void**) values, count); free(values); } else { CFArrayAppendValue((CFMutableArrayRef) dest, fileContents); } } else if (typeID == CFDictionaryGetTypeID()) { if (sourceTypeID == CFDictionaryGetTypeID()) { CFIndex count = CFDictionaryGetCount((CFDictionaryRef) fileContents); void** values = malloc(sizeof(void*) * count); void** keys = malloc(sizeof(void*) * count); CFDictionaryGetKeysAndValues((CFDictionaryRef) fileContents, (const void**) keys, (const void**) values); for (CFIndex i = 0; i < count; i++) CFDictionarySetValue((CFMutableDictionaryRef) dest, keys[i], values[i]); free(keys); free(values); } else if (sourceTypeID == CFArrayGetTypeID()) { puts("Merge: Can't Add array Entries to dict"); } else { CFDictionarySetValue((CFMutableDictionaryRef) dest, CFSTR(""), fileContents); } } else { puts("Merge: Specified Entry Must Be a Container"); } CFRelease(fileContents); } // NOTE: This function doesn't seem to work at all in the original program void importFile(const char* entry, const char* path) { CFPropertyListRef dest, fileContents; char* leafName; fileContents = loadPlist(path); if (fileContents == NULL) return; resolvePlistEntry(entry, &dest, NULL, &leafName, true); if (dest == NULL) { printf("Import: Entry, \"%s\", Does Not Exist\n", entry); CFRelease(fileContents); return; } CFTypeID typeID = CFGetTypeID(dest); if (typeID == CFDictionaryGetTypeID()) { CFStringRef key = CFStringCreateWithCString(kCFAllocatorDefault, leafName, kCFStringEncodingUTF8); CFDictionarySetValue((CFMutableDictionaryRef) dest, key, fileContents); CFRelease(key); } else if (typeID == CFArrayGetTypeID()) { CFIndex index = atoi(leafName); if (index < 0) index = 0; if (index >= CFArrayGetCount((CFArrayRef) dest)) CFArrayAppendValue((CFMutableArrayRef) dest, fileContents); else CFArraySetValueAtIndex((CFMutableArrayRef) dest, index, fileContents); } free(leafName); CFRelease(fileContents); }