+ else {
+ dir = path.substr(0, slash + 1);
+ base = path.substr(slash + 1);
+ }
+ }
+};
+
+static void mkdir_p(const std::string &path) {
+ if (path.empty())
+ return;
+#ifdef __WIN32__
+ if (_syscall(mkdir(path.c_str()), EEXIST) == -EEXIST)
+ return;
+#else
+ if (_syscall(mkdir(path.c_str(), 0755), EEXIST) == -EEXIST)
+ return;
+#endif
+ auto slash(path.rfind('/', path.size() - 1));
+ if (slash == std::string::npos)
+ return;
+ mkdir_p(path.substr(0, slash));
+}
+
+static std::string Temporary(std::filebuf &file, const Split &split) {
+ std::string temp(split.dir + ".ldid." + split.base);
+ mkdir_p(split.dir);
+ _assert_(file.open(temp.c_str(), std::ios::out | std::ios::trunc | std::ios::binary) == &file, "open(): %s", temp.c_str());
+ cleanup.push_back(temp);
+ return temp;
+}
+
+static void Commit(const std::string &path, const std::string &temp) {
+ struct stat info;
+ if (_syscall(stat(path.c_str(), &info), ENOENT) == 0) {
+#ifndef __WIN32__
+ _syscall(chown(temp.c_str(), info.st_uid, info.st_gid));
+#endif
+ _syscall(chmod(temp.c_str(), info.st_mode));
+ }
+
+ _syscall(rename(temp.c_str(), path.c_str()));
+ cleanup.erase(std::remove(cleanup.begin(), cleanup.end(), temp), cleanup.end());
+}
+#endif // LDID_NOTOOLS
+
+namespace ldid {
+
+static void get(std::string &value, X509_NAME *name, int nid) {
+ auto index(X509_NAME_get_index_by_NID(name, nid, -1));
+ if (index < 0) {
+ fprintf(stderr, "ldid: An error occursed while parsing the certificate: %s\n", ERR_error_string(ERR_get_error(), NULL));
+ exit(1);
+ }
+ auto next(X509_NAME_get_index_by_NID(name, nid, index));
+ if (next != -1) {
+ fprintf(stderr, "ldid: An error occursed while parsing the certificate: %s\n", ERR_error_string(ERR_get_error(), NULL));
+ exit(1);
+ }
+ auto entry(X509_NAME_get_entry(name, index));
+ if (entry == NULL) {
+ fprintf(stderr, "ldid: An error occursed while parsing the certificate: %s\n", ERR_error_string(ERR_get_error(), NULL));
+ exit(1);
+ }
+ auto asn(X509_NAME_ENTRY_get_data(entry));
+ if (asn == NULL) {
+ fprintf(stderr, "ldid: An error occursed while parsing the certificate: %s\n", ERR_error_string(ERR_get_error(), NULL));
+ exit(1);
+ }
+ value.assign(reinterpret_cast<const char *>(ASN1_STRING_get0_data(asn)), ASN1_STRING_length(asn));
+}
+
+static void req(std::streambuf &buffer, uint32_t value) {
+ value = Swap(value);
+ put(buffer, &value, sizeof(value));
+}
+
+static void req(std::streambuf &buffer, const std::string &value) {
+ req(buffer, value.size());
+ put(buffer, value.data(), value.size());
+ static uint8_t zeros[] = {0,0,0,0};
+ put(buffer, zeros, 3 - (value.size() + 3) % 4);
+}
+
+template <size_t Size_>
+static void req(std::streambuf &buffer, uint8_t (&&data)[Size_]) {
+ req(buffer, Size_);
+ put(buffer, data, Size_);
+ static uint8_t zeros[] = {0,0,0,0};
+ put(buffer, zeros, 3 - (Size_ + 3) % 4);
+}
+
+Hash Sign(const void *idata, size_t isize, std::streambuf &output, const std::string &identifier, const std::string &entitlements, bool merge, const std::string &requirements, const std::string &key, const Slots &slots, uint32_t flags, bool platform, const Progress &progress) {
+ Hash hash;
+
+
+ std::string team;
+ std::string common;
+
+ if (!key.empty()) {
+ Stuff stuff(key);
+ auto name(X509_get_subject_name(stuff));
+ if (name == NULL){
+ fprintf(stderr, "ldid: Your certificate might not be valid: %s\n", ERR_error_string(ERR_get_error(), NULL));
+ exit(1);
+ }
+ get(team, name, NID_organizationalUnitName);
+ get(common, name, NID_commonName);
+ }
+
+
+ std::stringbuf backing;
+
+ if (!requirements.empty()) {
+ put(backing, requirements.data(), requirements.size());
+ } else {
+ Blobs blobs;
+
+ std::stringbuf requirement;
+ req(requirement, exprForm);
+ req(requirement, opAnd);
+ req(requirement, opIdent);
+ req(requirement, identifier);
+ req(requirement, opAnd);
+ req(requirement, opAppleGenericAnchor);
+ req(requirement, opAnd);
+ req(requirement, opCertField);
+ req(requirement, 0);
+ req(requirement, "subject.CN");
+ req(requirement, matchEqual);
+ req(requirement, common);
+ req(requirement, opCertGeneric);
+ req(requirement, 1);
+ req(requirement, (uint8_t []) {APPLE_EXTENSION_OID, 2, 1});
+ req(requirement, matchExists);
+ insert(blobs, 3, CSMAGIC_REQUIREMENT, requirement);
+
+ put(backing, CSMAGIC_REQUIREMENTS, blobs);
+ }
+
+
+ // XXX: this is just a "sufficiently large number"
+ size_t certificate(0x3000);
+
+ Allocate(idata, isize, output, fun([&](const MachHeader &mach_header, Baton &baton, size_t size) -> size_t {
+ size_t alloc(sizeof(struct SuperBlob));
+
+ uint32_t normal((size + PageSize_ - 1) / PageSize_);
+
+ uint32_t special(0);
+
+ _foreach (slot, slots)
+ special = std::max(special, slot.first);
+
+ mach_header.ForSection(fun([&](const char *segment, const char *section, void *data, size_t size) {
+ if (strcmp(segment, "__TEXT") == 0 && section != NULL && strcmp(section, "__info_plist") == 0)
+ special = std::max(special, CSSLOT_INFOSLOT);
+ }));
+
+ special = std::max(special, CSSLOT_REQUIREMENTS);
+ alloc += sizeof(struct BlobIndex);
+ alloc += backing.str().size();
+
+ if (merge)
+ Analyze(mach_header, fun([&](const char *data, size_t size) {
+ baton.entitlements_.assign(data, size);
+ }));
+
+ if (!baton.entitlements_.empty() || !entitlements.empty()) {
+ auto combined(plist(baton.entitlements_));
+ _scope({ plist_free(combined); });
+ if (plist_get_node_type(combined) != PLIST_DICT) {
+ fprintf(stderr, "ldid: Existing entitlements are in wrong format\n");
+ exit(1);
+ };
+
+ auto merging(plist(entitlements));
+ _scope({ plist_free(merging); });
+ if (plist_get_node_type(merging) != PLIST_DICT) {
+ fprintf(stderr, "ldid: Entitlements need a root key of dict\n");
+ exit(1);
+ };
+
+ plist_dict_iter iterator(NULL);
+ plist_dict_new_iter(merging, &iterator);
+ _scope({ free(iterator); });
+
+ for (;;) {
+ char *key(NULL);
+ plist_t value(NULL);
+ plist_dict_next_item(merging, iterator, &key, &value);
+ if (key == NULL)
+ break;
+ _scope({ free(key); });
+ plist_dict_set_item(combined, key, plist_copy(value));
+ }
+
+ baton.derformat_ = der(combined);
+
+ char *xml(NULL);
+ uint32_t size;
+ plist_to_xml(combined, &xml, &size);
+ _scope({ free(xml); });
+
+ baton.entitlements_.assign(xml, size);
+ }
+
+ if (!baton.entitlements_.empty()) {
+ special = std::max(special, CSSLOT_ENTITLEMENTS);
+ alloc += sizeof(struct BlobIndex);
+ alloc += sizeof(struct Blob);
+ alloc += baton.entitlements_.size();
+ }
+
+ if (!baton.derformat_.empty()) {
+ special = std::max(special, CSSLOT_DERFORMAT);
+ alloc += sizeof(struct BlobIndex);
+ alloc += sizeof(struct Blob);
+ alloc += baton.derformat_.size();
+ }
+
+ size_t directory(0);
+
+ directory += sizeof(struct BlobIndex);
+ directory += sizeof(struct Blob);
+ directory += sizeof(struct CodeDirectory);
+ directory += identifier.size() + 1;
+
+ if (!team.empty())
+ directory += team.size() + 1;
+
+ for (Algorithm *algorithm : GetAlgorithms())
+ alloc = Align(alloc + directory + (special + normal) * algorithm->size_, 16);
+
+ if (!key.empty()) {
+ alloc += sizeof(struct BlobIndex);
+ alloc += sizeof(struct Blob);
+ alloc += certificate;
+ }
+
+ return alloc;
+ }), fun([&](const MachHeader &mach_header, const Baton &baton, std::streambuf &output, size_t limit, size_t left, size_t right, const std::string &overlap, const char *top, const Progress &progress) -> size_t {
+ Blobs blobs;
+
+ if (true) {
+ insert(blobs, CSSLOT_REQUIREMENTS, backing);
+ }
+
+ uint64_t execs(0);
+ if (mach_header.Swap(mach_header->filetype) == MH_EXECUTE)
+ execs |= kSecCodeExecSegMainBinary;
+
+ if (!baton.entitlements_.empty()) {
+ std::stringbuf data;
+ put(data, baton.entitlements_.data(), baton.entitlements_.size());
+ insert(blobs, CSSLOT_ENTITLEMENTS, CSMAGIC_EMBEDDED_ENTITLEMENTS, data);
+
+ auto entitlements(plist(baton.entitlements_));
+ _scope({ plist_free(entitlements); });
+ if (plist_get_node_type(entitlements) != PLIST_DICT) {
+ fprintf(stderr, "ldid: Entitlements should be a plist dicionary\n");
+ exit(1);
+ }
+
+ const auto entitled([&](const char *key) {
+ auto item(plist_dict_get_item(entitlements, key));
+ if (plist_get_node_type(item) != PLIST_BOOLEAN)
+ return false;
+ uint8_t value(0);
+ plist_get_bool_val(item, &value);
+ return value != 0;
+ });
+
+ if (entitled("get-task-allow"))
+ execs |= kSecCodeExecSegAllowUnsigned;
+ if (entitled("run-unsigned-code"))
+ execs |= kSecCodeExecSegAllowUnsigned;
+ if (entitled("com.apple.private.cs.debugger"))
+ execs |= kSecCodeExecSegDebugger;
+ if (entitled("dynamic-codesigning"))
+ execs |= kSecCodeExecSegJit;
+ if (entitled("com.apple.private.skip-library-validation"))
+ execs |= kSecCodeExecSegSkipLibraryVal;
+ if (entitled("com.apple.private.amfi.can-load-cdhash"))
+ execs |= kSecCodeExecSegCanLoadCdHash;
+ if (entitled("com.apple.private.amfi.can-execute-cdhash"))
+ execs |= kSecCodeExecSegCanExecCdHash;
+ }
+
+ if (!baton.derformat_.empty()) {
+ std::stringbuf data;
+ put(data, baton.derformat_.data(), baton.derformat_.size());
+ insert(blobs, CSSLOT_DERFORMAT, CSMAGIC_EMBEDDED_DERFORMAT, data);
+ }
+
+ Slots posts(slots);
+
+ mach_header.ForSection(fun([&](const char *segment, const char *section, void *data, size_t size) {
+ if (strcmp(segment, "__TEXT") == 0 && section != NULL && strcmp(section, "__info_plist") == 0) {
+ auto &slot(posts[CSSLOT_INFOSLOT]);
+ for (Algorithm *algorithm : GetAlgorithms())
+ (*algorithm)(slot, data, size);
+ }
+ }));
+
+ unsigned total(0);
+ for (Algorithm *pointer : GetAlgorithms()) {
+ Algorithm &algorithm(*pointer);
+
+ std::stringbuf data;
+
+ uint32_t special(0);
+ _foreach (blob, blobs)
+ special = std::max(special, blob.first);
+ _foreach (slot, posts)
+ special = std::max(special, slot.first);
+ uint32_t normal((limit + PageSize_ - 1) / PageSize_);
+
+ CodeDirectory directory;
+ directory.version = Swap(uint32_t(0x00020400));
+ directory.flags = Swap(uint32_t(flags));
+ directory.nSpecialSlots = Swap(special);
+ directory.codeLimit = Swap(uint32_t(limit > UINT32_MAX ? UINT32_MAX : limit));
+ directory.nCodeSlots = Swap(normal);
+ directory.hashSize = algorithm.size_;
+ directory.hashType = algorithm.type_;
+ directory.platform = platform ? 0x01 : 0x00;
+ directory.pageSize = PageShift_;
+ directory.spare2 = Swap(uint32_t(0));
+ directory.scatterOffset = Swap(uint32_t(0));
+ directory.spare3 = Swap(uint32_t(0));
+ directory.codeLimit64 = Swap(uint64_t(limit > UINT32_MAX ? limit : 0));
+ directory.execSegBase = Swap(uint64_t(left));
+ directory.execSegLimit = Swap(uint64_t(right - left));
+ directory.execSegFlags = Swap(execs);
+
+ uint32_t offset(sizeof(Blob) + sizeof(CodeDirectory));
+
+ directory.identOffset = Swap(uint32_t(offset));
+ offset += identifier.size() + 1;
+
+ if (team.empty())
+ directory.teamIDOffset = Swap(uint32_t(0));
+ else {
+ directory.teamIDOffset = Swap(uint32_t(offset));
+ offset += team.size() + 1;
+ }
+
+ offset += special * algorithm.size_;
+ directory.hashOffset = Swap(uint32_t(offset));
+ offset += normal * algorithm.size_;
+
+ put(data, &directory, sizeof(directory));
+
+ put(data, identifier.c_str(), identifier.size() + 1);
+ if (!team.empty())
+ put(data, team.c_str(), team.size() + 1);
+
+ std::vector<uint8_t> storage((special + normal) * algorithm.size_);
+ auto *hashes(&storage[special * algorithm.size_]);
+
+ memset(storage.data(), 0, special * algorithm.size_);
+
+ _foreach (blob, blobs) {
+ auto local(reinterpret_cast<const Blob *>(&blob.second[0]));
+ algorithm(hashes - blob.first * algorithm.size_, local, Swap(local->length));
+ }
+
+ _foreach (slot, posts)
+ memcpy(hashes - slot.first * algorithm.size_, algorithm[slot.second], algorithm.size_);
+
+ progress(0);
+ if (normal != 1)
+ for (size_t i = 0; i != normal - 1; ++i) {
+ algorithm(hashes + i * algorithm.size_, (PageSize_ * i < overlap.size() ? overlap.data() : top) + PageSize_ * i, PageSize_);
+ progress(double(i) / normal);
+ }
+ if (normal != 0)
+ algorithm(hashes + (normal - 1) * algorithm.size_, top + PageSize_ * (normal - 1), ((limit - 1) % PageSize_) + 1);
+ progress(1);
+
+ put(data, storage.data(), storage.size());
+
+ const auto &save(insert(blobs, total == 0 ? CSSLOT_CODEDIRECTORY : CSSLOT_ALTERNATE + total - 1, CSMAGIC_CODEDIRECTORY, data));
+ algorithm(hash, save.data(), save.size());
+
+ ++total;
+ }
+
+ if (!key.empty()) {
+ auto plist(plist_new_dict());
+ _scope({ plist_free(plist); });
+
+ auto cdhashes(plist_new_array());
+ plist_dict_set_item(plist, "cdhashes", cdhashes);
+
+ std::vector<char> alternateCDSHA256;
+ std::vector<char> alternateCDSHA1;
+
+ unsigned total(0);
+ for (Algorithm *pointer : GetAlgorithms()) {
+ Algorithm &algorithm(*pointer);
+ (void) algorithm;
+
+ const auto &blob(blobs[total == 0 ? CSSLOT_CODEDIRECTORY : CSSLOT_ALTERNATE + total - 1]);
+ ++total;
+
+ std::vector<char> hash;
+ algorithm(hash, blob.data(), blob.size());
+ if (algorithm.type_ == CS_HASHTYPE_SHA256_256)
+ alternateCDSHA256 = hash;
+ else if (algorithm.type_ == CS_HASHTYPE_SHA160_160)
+ alternateCDSHA1 = hash;
+ hash.resize(20);
+
+ plist_array_append_item(cdhashes, plist_new_data(hash.data(), hash.size()));
+ }
+
+ char *xml(NULL);
+ uint32_t size;
+ plist_to_xml(plist, &xml, &size);
+ _scope({ free(xml); });
+
+ std::stringbuf data;
+ const std::string &sign(blobs[CSSLOT_CODEDIRECTORY]);
+
+ Stuff stuff(key);
+ Buffer bio(sign);
+
+ Signature signature(stuff, sign, std::string(xml, size), alternateCDSHA1, alternateCDSHA256);
+ Buffer result(signature);
+ std::string value(result);
+ put(data, value.data(), value.size());
+
+ const auto &save(insert(blobs, CSSLOT_SIGNATURESLOT, CSMAGIC_BLOBWRAPPER, data));
+ _assert(save.size() <= certificate);
+ }
+
+ return put(output, CSMAGIC_EMBEDDED_SIGNATURE, blobs);
+ }), progress);
+
+ return hash;
+}
+
+#ifndef LDID_NOTOOLS
+static void Unsign(void *idata, size_t isize, std::streambuf &output, const Progress &progress) {
+ Allocate(idata, isize, output, fun([](const MachHeader &mach_header, Baton &baton, size_t size) -> size_t {
+ return 0;
+ }), fun([](const MachHeader &mach_header, const Baton &baton, std::streambuf &output, size_t limit, size_t left, size_t right, const std::string &overlap, const char *top, const Progress &progress) -> size_t {
+ return 0;
+ }), progress);
+}
+
+std::string DiskFolder::Path(const std::string &path) const {
+ return path_ + path;
+}
+
+DiskFolder::DiskFolder(const std::string &path) :
+ path_(path)
+{
+ _assert_(path_.size() != 0 && path_[path_.size() - 1] == '/', "missing / on %s", path_.c_str());
+}
+
+DiskFolder::~DiskFolder() {
+ if (!std::uncaught_exception())
+ for (const auto &commit : commit_)
+ Commit(commit.first, commit.second);
+}
+
+#ifndef __WIN32__
+std::string readlink(const std::string &path) {
+ for (size_t size(1024); ; size *= 2) {
+ std::string data;
+ data.resize(size);
+
+ int writ(_syscall(::readlink(path.c_str(), &data[0], data.size())));
+ if (size_t(writ) >= size)
+ continue;
+
+ data.resize(writ);
+ return data;
+ }
+}
+#endif
+
+void DiskFolder::Find(const std::string &root, const std::string &base, const Functor<void (const std::string &)> &code, const Functor<void (const std::string &, const Functor<std::string ()> &)> &link) const {
+ std::string path(Path(root) + base);
+
+ DIR *dir(opendir(path.c_str()));
+ _assert(dir != NULL);
+ _scope({ _syscall(closedir(dir)); });
+
+ while (auto child = readdir(dir)) {
+ std::string name(child->d_name);
+ if (name == "." || name == "..")
+ continue;
+ if (Starts(name, ".ldid."))
+ continue;
+
+ bool directory;
+
+#ifdef __WIN32__
+ struct stat info;
+ _syscall(stat((path + name).c_str(), &info));
+ if (S_ISDIR(info.st_mode))
+ directory = true;
+ else if (S_ISREG(info.st_mode))
+ directory = false;
+ else
+ _assert_(false, "st_mode=%x", info.st_mode);
+#else
+ switch (child->d_type) {
+ case DT_DIR:
+ directory = true;
+ break;
+ case DT_REG:
+ directory = false;
+ break;
+ case DT_LNK:
+ link(base + name, fun([&]() { return readlink(path + name); }));
+ continue;
+ default:
+ _assert_(false, "d_type=%u", child->d_type);
+ }
+#endif
+
+ if (directory)
+ Find(root, base + name + "/", code, link);
+ else
+ code(base + name);
+ }
+}
+
+void DiskFolder::Save(const std::string &path, bool edit, const void *flag, const Functor<void (std::streambuf &)> &code) {
+ if (!edit) {
+ NullBuffer save;
+ code(save);
+ } else {
+ std::filebuf save;
+ auto from(Path(path));
+ commit_[from] = Temporary(save, from);
+ code(save);
+ }
+}
+
+bool DiskFolder::Look(const std::string &path) const {
+ return _syscall(access(Path(path).c_str(), R_OK), ENOENT) == 0;
+}
+
+void DiskFolder::Open(const std::string &path, const Functor<void (std::streambuf &, size_t, const void *)> &code) const {
+ std::filebuf data;
+ auto result(data.open(Path(path).c_str(), std::ios::binary | std::ios::in));
+ _assert_(result == &data, "DiskFolder::Open(%s)", Path(path).c_str());
+
+ auto length(data.pubseekoff(0, std::ios::end, std::ios::in));
+ data.pubseekpos(0, std::ios::in);
+ code(data, length, NULL);
+}
+
+void DiskFolder::Find(const std::string &path, const Functor<void (const std::string &)> &code, const Functor<void (const std::string &, const Functor<std::string ()> &)> &link) const {
+ Find(path, "", code, link);
+}
+#endif // LDID_NOTOOLS
+
+SubFolder::SubFolder(Folder &parent, const std::string &path) :
+ parent_(parent),
+ path_(path)
+{
+ _assert_(path_.size() == 0 || path_[path_.size() - 1] == '/', "missing / on %s", path_.c_str());
+}
+
+std::string SubFolder::Path(const std::string &path) const {
+ return path_ + path;
+}
+
+void SubFolder::Save(const std::string &path, bool edit, const void *flag, const Functor<void (std::streambuf &)> &code) {
+ return parent_.Save(Path(path), edit, flag, code);
+}
+
+bool SubFolder::Look(const std::string &path) const {
+ return parent_.Look(Path(path));
+}
+
+void SubFolder::Open(const std::string &path, const Functor<void (std::streambuf &, size_t, const void *)> &code) const {
+ return parent_.Open(Path(path), code);
+}
+
+void SubFolder::Find(const std::string &path, const Functor<void (const std::string &)> &code, const Functor<void (const std::string &, const Functor<std::string ()> &)> &link) const {
+ return parent_.Find(Path(path), code, link);
+}
+
+std::string UnionFolder::Map(const std::string &path) const {
+ auto remap(remaps_.find(path));
+ if (remap == remaps_.end())
+ return path;
+ return remap->second;
+}
+
+void UnionFolder::Map(const std::string &path, const Functor<void (const std::string &)> &code, const std::string &file, const Functor<void (const Functor<void (std::streambuf &, size_t, const void *)> &)> &save) const {
+ if (file.size() >= path.size() && file.substr(0, path.size()) == path)
+ code(file.substr(path.size()));
+}
+
+UnionFolder::UnionFolder(Folder &parent) :
+ parent_(parent)
+{
+}
+
+void UnionFolder::Save(const std::string &path, bool edit, const void *flag, const Functor<void (std::streambuf &)> &code) {
+ return parent_.Save(Map(path), edit, flag, code);
+}
+
+bool UnionFolder::Look(const std::string &path) const {
+ auto file(resets_.find(path));
+ if (file != resets_.end())
+ return true;
+ return parent_.Look(Map(path));
+}
+
+void UnionFolder::Open(const std::string &path, const Functor<void (std::streambuf &, size_t, const void *)> &code) const {
+ auto file(resets_.find(path));
+ if (file == resets_.end())
+ return parent_.Open(Map(path), code);
+ auto &entry(file->second);
+
+ auto &data(*entry.data_);
+ auto length(data.pubseekoff(0, std::ios::end, std::ios::in));
+ data.pubseekpos(0, std::ios::in);
+ code(data, length, entry.flag_);
+}
+
+void UnionFolder::Find(const std::string &path, const Functor<void (const std::string &)> &code, const Functor<void (const std::string &, const Functor<std::string ()> &)> &link) const {
+ for (auto &reset : resets_)
+ Map(path, code, reset.first, fun([&](const Functor<void (std::streambuf &, size_t, const void *)> &code) {
+ auto &entry(reset.second);
+ auto &data(*entry.data_);
+ auto length(data.pubseekoff(0, std::ios::end, std::ios::in));
+ data.pubseekpos(0, std::ios::in);
+ code(data, length, entry.flag_);
+ }));
+
+ for (auto &remap : remaps_)
+ Map(path, code, remap.first, fun([&](const Functor<void (std::streambuf &, size_t, const void *)> &code) {
+ parent_.Open(remap.second, fun([&](std::streambuf &data, size_t length, const void *flag) {
+ code(data, length, flag);
+ }));
+ }));
+
+ parent_.Find(path, fun([&](const std::string &name) {
+ if (deletes_.find(path + name) == deletes_.end())
+ code(name);
+ }), fun([&](const std::string &name, const Functor<std::string ()> &read) {
+ if (deletes_.find(path + name) == deletes_.end())
+ link(name, read);
+ }));
+}
+
+#ifndef LDID_NOTOOLS
+static void copy(std::streambuf &source, std::streambuf &target, size_t length, const Progress &progress) {
+ progress(0);
+ size_t total(0);
+ for (;;) {
+ char data[4096 * 4];
+ size_t writ(source.sgetn(data, sizeof(data)));
+ if (writ == 0)
+ break;
+ _assert(target.sputn(data, writ) == writ);
+ total += writ;
+ progress(double(total) / length);
+ }
+}
+
+static plist_t plist(const std::string &data) {
+ if (data.empty())
+ return plist_new_dict();
+ plist_t plist(NULL);
+ if (Starts(data, "bplist00"))
+ plist_from_bin(data.data(), data.size(), &plist);
+ else
+ plist_from_xml(data.data(), data.size(), &plist);
+ if (plist == NULL) {
+ fprintf(stderr, "ldid: Failed to parse plist\n");
+ exit(1);
+ }
+ return plist;
+}
+
+static void plist_d(std::streambuf &buffer, size_t length, const Functor<void (plist_t)> &code) {
+ std::stringbuf data;
+ copy(buffer, data, length, dummy_);
+ auto node(plist(data.str()));
+ _scope({ plist_free(node); });
+ if (plist_get_node_type(node) != PLIST_DICT) {
+ fprintf(stderr, "ldid: Unexpected plist type. Expected <dict>\n");
+ exit(1);
+ }
+ code(node);
+}
+
+static std::string plist_s(plist_t node) {
+ if (node == NULL)
+ return NULL;
+ if (plist_get_node_type(node) != PLIST_STRING) {
+ fprintf(stderr, "ldid: Unexpected plist type. Expected <string>\n");
+ exit(1);
+ }
+ char *data;
+ plist_get_string_val(node, &data);
+ _scope({ free(data); });
+ return data;
+}
+
+enum Mode {
+ NoMode,
+ OptionalMode,
+ OmitMode,
+ NestedMode,
+ TopMode,
+};
+
+class Expression {
+ private:
+ regex_t regex_;
+ std::vector<std::string> matches_;
+
+ public:
+ Expression(const std::string &code) {
+ _assert_(regcomp(®ex_, code.c_str(), REG_EXTENDED) == 0, "regcomp()");
+ matches_.resize(regex_.re_nsub + 1);
+ }
+
+ ~Expression() {
+ regfree(®ex_);
+ }
+
+ bool operator ()(const std::string &data) {
+ regmatch_t matches[matches_.size()];
+ auto value(regexec(®ex_, data.c_str(), matches_.size(), matches, 0));
+ if (value == REG_NOMATCH)
+ return false;
+ _assert_(value == 0, "regexec()");
+ for (size_t i(0); i != matches_.size(); ++i)
+ matches_[i].assign(data.data() + matches[i].rm_so, matches[i].rm_eo - matches[i].rm_so);
+ return true;
+ }
+
+ const std::string &operator [](size_t index) const {
+ return matches_[index];
+ }
+};
+
+struct Rule {
+ unsigned weight_;
+ Mode mode_;
+ std::string code_;
+
+ mutable std::unique_ptr<Expression> regex_;
+
+ Rule(unsigned weight, Mode mode, const std::string &code) :
+ weight_(weight),
+ mode_(mode),
+ code_(code)
+ {
+ }
+
+ Rule(const Rule &rhs) :
+ weight_(rhs.weight_),
+ mode_(rhs.mode_),
+ code_(rhs.code_)
+ {
+ }
+
+ void Compile() const {
+ regex_.reset(new Expression(code_));
+ }
+
+ bool operator ()(const std::string &data) const {
+ _assert(regex_.get() != NULL);
+ return (*regex_)(data);
+ }
+
+ bool operator <(const Rule &rhs) const {
+ if (weight_ > rhs.weight_)
+ return true;
+ if (weight_ < rhs.weight_)
+ return false;
+ return mode_ > rhs.mode_;
+ }
+};
+
+struct RuleCode {
+ bool operator ()(const Rule *lhs, const Rule *rhs) const {
+ return lhs->code_ < rhs->code_;
+ }
+};
+
+static Hash Sign(const uint8_t *prefix, size_t size, std::streambuf &buffer, Hash &hash, std::streambuf &save, const std::string &identifier, const std::string &entitlements, bool merge, const std::string &requirements, const std::string &key, const Slots &slots, size_t length, uint32_t flags, bool platform, const Progress &progress) {
+ // XXX: this is a miserable fail
+ std::stringbuf temp;
+ put(temp, prefix, size);
+ copy(buffer, temp, length - size, progress);
+ // XXX: this is a stupid hack
+ pad(temp, 0x10 - (length & 0xf));
+ auto data(temp.str());
+
+ HashProxy proxy(hash, save);
+ return Sign(data.data(), data.size(), proxy, identifier, entitlements, merge, requirements, key, slots, flags, platform, progress);
+}
+
+struct State {
+ std::map<std::string, Hash> files;
+ std::map<std::string, std::string> links;
+
+ void Merge(const std::string &root, const State &state) {
+ for (const auto &entry : state.files)
+ files[root + entry.first] = entry.second;
+ for (const auto &entry : state.links)
+ links[root + entry.first] = entry.second;
+ }
+};
+
+Bundle Sign(const std::string &root, Folder &parent, const std::string &key, State &local, const std::string &requirements, const Functor<std::string (const std::string &, const std::string &)> &alter, const Progress &progress) {
+ std::string executable;
+ std::string identifier;
+
+ bool mac(false);
+
+ std::string info("Info.plist");
+
+ SubFolder folder(parent, [&]() {
+ if (parent.Look(info))
+ return "";
+ mac = true;
+ if (parent.Look("Contents/" + info))
+ return "Contents/";
+ else if (parent.Look("Resources/" + info)) {
+ info = "Resources/" + info;
+ return "";
+ } else {
+ fprintf(stderr, "ldid: Could not find Info.plist\n");
+ exit(1);