diff --git a/Manager/src/cmdparser.cpp b/Manager/src/cmdparser.cpp index f430a0d..4dd7cae 100644 --- a/Manager/src/cmdparser.cpp +++ b/Manager/src/cmdparser.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -61,6 +62,15 @@ namespace AkVCam { std::string helpString; ProgramOptionsFunc func; std::vector flags; + bool advanced {false}; + + CmdParserCommand(); + CmdParserCommand(const std::string &command, + const std::string &arguments, + const std::string &helpString, + const ProgramOptionsFunc &func, + const std::vector flags, + bool advanced); }; class CmdParserPrivate @@ -74,8 +84,8 @@ namespace AkVCam { std::string basename(const std::string &path); void printFlags(const std::vector &cmdFlags, size_t indent); - size_t maxCommandLength(); - size_t maxArgumentsLength(); + size_t maxCommandLength(bool showAdvancedHelp); + size_t maxArgumentsLength(bool showAdvancedHelp); size_t maxFlagsLength(const std::vector &flags); size_t maxFlagsValueLength(const std::vector &flags); size_t maxColumnLength(const StringVector &table, @@ -126,6 +136,9 @@ namespace AkVCam { int setLogLevel(const StringMap &flags, const StringVector &args); int showClients(const StringMap &flags, const StringVector &args); int dumpInfo(const StringMap &flags, const StringVector &args); + int hacks(const StringMap &flags, const StringVector &args); + int hackInfo(const StringMap &flags, const StringVector &args); + int hack(const StringMap &flags, const StringVector &args); void loadGenerals(Settings &settings); VideoFormatMatrix readFormats(Settings &settings); std::vector readFormat(Settings &settings); @@ -154,6 +167,9 @@ AkVCam::CmdParser::CmdParser() this->addFlags("", {"-h", "--help"}, "Show help."); + this->addFlags("", + {"--help-all"}, + "Show advanced help."); this->addFlags("", {"-v", "--version"}, "Show program version."); @@ -236,6 +252,10 @@ AkVCam::CmdParser::CmdParser() "DEVICE FORMAT WIDTH HEIGHT", "Read frames from stdin and send them to the device.", AKVCAM_BIND_FUNC(CmdParserPrivate::stream)); + this->addFlags("stream", + {"-f", "--fps"}, + "FPS", + "Read stream input at a constant frame rate."); this->addCommand("listen-events", "", "Keep the manager running and listening to global events.", @@ -297,6 +317,30 @@ AkVCam::CmdParser::CmdParser() "", "Show all information in a parseable XML format.", AKVCAM_BIND_FUNC(CmdParserPrivate::dumpInfo)); + this->addCommand("hacks", + "", + "List system hacks to make the virtual camera work.", + AKVCAM_BIND_FUNC(CmdParserPrivate::hacks), + true); + this->addCommand("hack-info", + "HACK", + "Show hack information.", + AKVCAM_BIND_FUNC(CmdParserPrivate::hackInfo), + true); + this->addFlags("hack-info", + {"-s", "--issafe"}, + "Is hack safe?"); + this->addFlags("hack-info", + {"-c", "--description"}, + "Show hack description."); + this->addCommand("hack", + "HACK PARAMS...", + "Apply system hack.", + AKVCAM_BIND_FUNC(CmdParserPrivate::hack), + true); + this->addFlags("hack", + {"-y", "--yes"}, + "Accept all risks and continue anyway."); } AkVCam::CmdParser::~CmdParser() @@ -374,7 +418,10 @@ int AkVCam::CmdParser::parse(int argc, char **argv) } } - if (this->d->m_ipcBridge.needsRoot(command->command)) + if (this->d->m_ipcBridge.needsRoot(command->command) + || (command->command == "hack" + && arguments.size() >= 2 + && this->d->m_ipcBridge.hackNeedsRoot(arguments[1]))) return this->d->m_ipcBridge.sudo(argc, argv); return command->func(flags, arguments); @@ -388,7 +435,8 @@ void AkVCam::CmdParser::setDefaultFuntion(const ProgramOptionsFunc &func) void AkVCam::CmdParser::addCommand(const std::string &command, const std::string &arguments, const std::string &helpString, - const ProgramOptionsFunc &func) + const ProgramOptionsFunc &func, + bool advanced) { auto it = std::find_if(this->d->m_commands.begin(), @@ -402,12 +450,14 @@ void AkVCam::CmdParser::addCommand(const std::string &command, arguments, helpString, func, - {}}); + {}, + advanced}); } else { it->command = command; it->arguments = arguments; it->helpString = helpString; it->func = func; + it->advanced = advanced; } } @@ -497,22 +547,24 @@ void AkVCam::CmdParserPrivate::printFlags(const std::vector &cmd } } -size_t AkVCam::CmdParserPrivate::maxCommandLength() +size_t AkVCam::CmdParserPrivate::maxCommandLength(bool showAdvancedHelp) { size_t length = 0; for (auto &cmd: this->m_commands) - length = std::max(cmd.command.size(), length); + if (!cmd.advanced || showAdvancedHelp) + length = std::max(cmd.command.size(), length); return length; } -size_t AkVCam::CmdParserPrivate::maxArgumentsLength() +size_t AkVCam::CmdParserPrivate::maxArgumentsLength(bool showAdvancedHelp) { size_t length = 0; for (auto &cmd: this->m_commands) - length = std::max(cmd.arguments.size(), length); + if (!cmd.advanced || showAdvancedHelp) + length = std::max(cmd.arguments.size(), length); return length; } @@ -674,7 +726,9 @@ std::string AkVCam::CmdParserPrivate::flagValue(const AkVCam::StringMap &flags, int AkVCam::CmdParserPrivate::defaultHandler(const StringMap &flags, const StringVector &args) { - if (flags.empty() || this->containsFlag(flags, "", "-h")) + if (flags.empty() + || this->containsFlag(flags, "", "-h") + || this->containsFlag(flags, "", "--help-all")) return this->showHelp(flags, args); if (this->containsFlag(flags, "", "-v")) { @@ -707,11 +761,13 @@ int AkVCam::CmdParserPrivate::showHelp(const StringMap &flags, std::cout << "Commands:" << std::endl; std::cout << std::endl; - auto maxCmdLen = this->maxCommandLength(); - auto maxArgsLen = this->maxArgumentsLength(); + bool showAdvancedHelp = this->containsFlag(flags, "", "--help-all"); + auto maxCmdLen = this->maxCommandLength(showAdvancedHelp); + auto maxArgsLen = this->maxArgumentsLength(showAdvancedHelp); for (auto &cmd: this->m_commands) { - if (cmd.command.empty()) + if (cmd.command.empty() + || (cmd.advanced && !showAdvancedHelp)) continue; std::cout << " " @@ -1230,6 +1286,30 @@ int AkVCam::CmdParserPrivate::stream(const AkVCam::StringMap &flags, return -1; } + auto fpsStr = this->flagValue(flags, "stream", "-f"); + double fps = std::numeric_limits::quiet_NaN(); + + if (!fpsStr.empty()) { + p = nullptr; + fps = int(strtod(fpsStr.c_str(), &p)); + + if (*p) { + if (!Fraction::isFraction(fpsStr)) { + std::cerr << "The framerate must be a number or a fraction." << std::endl; + + return -1; + } + + fps = Fraction(fpsStr).value(); + } + + if (fps <= 0 || std::isinf(fps)) { + std::cerr << "The framerate is out of range." << std::endl; + + return -1; + } + } + VideoFormat fmt(format, int(width), int(height), {{30, 1}}); if (!this->m_ipcBridge.deviceStart(deviceId, fmt)) { @@ -1253,6 +1333,20 @@ int AkVCam::CmdParserPrivate::stream(const AkVCam::StringMap &flags, _setmode(_fileno(stdin), _O_BINARY); #endif + auto clock = [] (const std::chrono::time_point &since) -> double { + return std::chrono::duration_cast>(std::chrono::high_resolution_clock::now() - since).count(); + }; + + const double minThreshold = 0.04; + const double maxThreshold = 0.1; + const double framedupThreshold = 0.1; + const double nosyncThreshold = 10.0; + + double lastPts = 0.0; + auto t0 = std::chrono::high_resolution_clock::now(); + double drift = 0; + uint64_t i = 0; + do { std::cin.read(reinterpret_cast(frame.data().data() + bufferSize), @@ -1260,7 +1354,46 @@ int AkVCam::CmdParserPrivate::stream(const AkVCam::StringMap &flags, bufferSize += size_t(std::cin.gcount()); if (bufferSize == frame.data().size()) { - this->m_ipcBridge.write(deviceId, frame); + if (fpsStr.empty()) { + this->m_ipcBridge.write(deviceId, frame); + } else { + double pts = double(i) / fps; + + for (;;) { + double clock_pts = clock(t0) + drift; + double diff = pts - clock_pts; + double delay = pts - lastPts; + double syncThreshold = + std::max(minThreshold, + std::min(delay, maxThreshold)); + + if (!std::isnan(diff) + && std::abs(diff) < nosyncThreshold + && delay < framedupThreshold) { + if (diff <= -syncThreshold) { + lastPts = pts; + + break; + } + + if (diff > syncThreshold) { + std::this_thread::sleep_for(std::chrono::duration(diff - syncThreshold)); + + continue; + } + } else { + drift = clock(t0) - pts; + } + + this->m_ipcBridge.write(deviceId, frame); + lastPts = pts; + + break; + } + + i++; + } + bufferSize = 0; } } while (!std::cin.eof() && !exit); @@ -1365,8 +1498,6 @@ int AkVCam::CmdParserPrivate::showControls(const StringMap &flags, int AkVCam::CmdParserPrivate::readControl(const StringMap &flags, const StringVector &args) { - UNUSED(flags); - if (args.size() < 3) { std::cerr << "Not enough arguments." << std::endl; @@ -1389,9 +1520,9 @@ int AkVCam::CmdParserPrivate::readControl(const StringMap &flags, std::cout << control.value << std::endl; } else { if (this->containsFlag(flags, "get-control", "-c")) { - auto typeStr = typeStrMap(); std::cout << control.description << std::endl; } + if (this->containsFlag(flags, "get-control", "-t")) { auto typeStr = typeStrMap(); std::cout << typeStr[control.type] << std::endl; @@ -1871,6 +2002,146 @@ int AkVCam::CmdParserPrivate::dumpInfo(const AkVCam::StringMap &flags, return 0; } +int AkVCam::CmdParserPrivate::hacks(const AkVCam::StringMap &flags, + const AkVCam::StringVector &args) +{ + UNUSED(flags); + UNUSED(args); + + auto hacks = this->m_ipcBridge.hacks(); + + if (hacks.empty()) + return 0; + + if (this->m_parseable) { + for (auto &hack: hacks) + std::cout << hack << std::endl; + } else { + std::cout << "Hacks are intended to fix common problems with the " + "virtual camera, and are intended to be used by developers " + "and advanced users only." << std::endl; + std::cout << std::endl; + std::cout << "WARNING: Unsafe hacks can brick your system, make it " + "unstable, or expose it to a serious security risk. You " + "are solely responsible of whatever happens for using " + "them. You been warned, don't come and cry later." + << std::endl; + std::cout << std::endl; + std::vector table { + "Hack", + "Is safe?", + "Description" + }; + auto columns = table.size(); + + for (auto &hack: hacks) { + table.push_back(hack); + table.push_back(this->m_ipcBridge.hackIsSafe(hack)? "Yes": "No"); + table.push_back(this->m_ipcBridge.hackDescription(hack)); + } + + this->drawTable(table, columns); + } + + return 0; +} + +int AkVCam::CmdParserPrivate::hackInfo(const AkVCam::StringMap &flags, + const AkVCam::StringVector &args) +{ + if (args.size() < 2) { + std::cerr << "Not enough arguments." << std::endl; + + return -1; + } + + auto hack = args[1]; + auto hacks = this->m_ipcBridge.hacks(); + auto dit = std::find(hacks.begin(), hacks.end(), hack); + + if (dit == hacks.end()) { + std::cerr << "Unknown hack: " << hack << "." << std::endl; + + return -1; + } + + if (this->containsFlag(flags, "hack-info", "-c")) + std::cout << this->m_ipcBridge.hackDescription(hack) << std::endl; + + if (this->containsFlag(flags, "hack-info", "-s")) { + if (this->m_ipcBridge.hackIsSafe(hack)) + std::cout << "Yes" << std::endl; + else + std::cout << "No" << std::endl; + } + + return 0; +} + +int AkVCam::CmdParserPrivate::hack(const AkVCam::StringMap &flags, + const AkVCam::StringVector &args) +{ + if (args.size() < 2) { + std::cerr << "Not enough arguments." << std::endl; + + return -1; + } + + auto hack = args[1]; + auto hacks = this->m_ipcBridge.hacks(); + auto dit = std::find(hacks.begin(), hacks.end(), hack); + + if (dit == hacks.end()) { + std::cerr << "Unknown hack: " << hack << "." << std::endl; + + return -1; + } + + bool accepted = this->m_parseable | this->m_ipcBridge.hackIsSafe(hack); + + if (!accepted && !this->m_parseable) { + std::cout << "WARNING: Applying this hack can brick your system, make " + "it unstable, or expose it to a serious security risk. " + "Agreeing to continue, you accept the full responsability " + "of whatever happens from now on." + << std::endl; + std::cout << std::endl; + + if (this->containsFlag(flags, "hack", "-y")) { + std::cout << "You agreed to continue from command line." + << std::endl; + std::cout << std::endl; + accepted = true; + } else { + std::cout << "If you agree to continue write YES: "; + std::string answer; + std::cin >> answer; + std::cout << std::endl; + accepted = answer == "YES"; + } + } + + if (!accepted) { + std::cerr << "Hack not applied." << std::endl; + + return -1; + } + + StringVector hargs; + + for (size_t i = 2; i < args.size(); i++) + hargs.push_back(args[i]); + + auto result = this->m_ipcBridge.execHack(hack, hargs); + + if (result == 0) + std::cout << "Success" << std::endl; + else + std::cout << "Failed" << std::endl; + + return result; +} + void AkVCam::CmdParserPrivate::loadGenerals(Settings &settings) { settings.beginGroup("General"); @@ -2081,3 +2352,22 @@ std::string AkVCam::operator *(size_t n, const std::string &str) return ss.str(); } + +AkVCam::CmdParserCommand::CmdParserCommand() +{ +} + +AkVCam::CmdParserCommand::CmdParserCommand(const std::string &command, + const std::string &arguments, + const std::string &helpString, + const AkVCam::ProgramOptionsFunc &func, + const std::vector flags, + bool advanced): + command(command), + arguments(arguments), + helpString(helpString), + func(func), + flags(flags), + advanced(advanced) +{ +} diff --git a/Manager/src/cmdparser.h b/Manager/src/cmdparser.h index bde97b0..cef0073 100644 --- a/Manager/src/cmdparser.h +++ b/Manager/src/cmdparser.h @@ -42,7 +42,8 @@ namespace AkVCam { void addCommand(const std::string &command, const std::string &arguments, const std::string &helpString, - const ProgramOptionsFunc &func); + const ProgramOptionsFunc &func, + bool advanced=false); void addFlags(const std::string &command, const StringVector &flags, const std::string &value, diff --git a/VCamUtils/src/fraction.cpp b/VCamUtils/src/fraction.cpp index c13af4f..de11b44 100644 --- a/VCamUtils/src/fraction.cpp +++ b/VCamUtils/src/fraction.cpp @@ -17,6 +17,7 @@ * Web-Site: http://webcamoid.github.io/ */ +#include #include #include #include @@ -143,6 +144,42 @@ std::string AkVCam::Fraction::toString() const return ss.str(); } +bool AkVCam::Fraction::isInfinity() const +{ + return this->d->m_num != 0 && this->d->m_den == 0; +} + +int AkVCam::Fraction::sign() const +{ + return std::signbit(this->d->m_num) == std::signbit(this->d->m_den)? 1: -1; +} + +bool AkVCam::Fraction::isFraction(const std::string &str) +{ + auto pos = str.find('/'); + + if (pos == std::string::npos) { + auto strCpy = trimmed(str); + char *p = nullptr; + strtol(strCpy.c_str(), &p, 10); + + if (*p) + return false; + } else { + auto numStr = trimmed(str.substr(0, pos)); + auto denStr = trimmed(str.substr(pos + 1)); + char *p = nullptr; + char *q = nullptr; + strtol(numStr.c_str(), &p, 10); + strtol(denStr.c_str(), &q, 10); + + if (*p || *q) + return false; + } + + return true; +} + std::ostream &operator <<(std::ostream &os, const AkVCam::Fraction &fraction) { os << fraction.toString(); diff --git a/VCamUtils/src/fraction.h b/VCamUtils/src/fraction.h index 55f88be..dc65d95 100644 --- a/VCamUtils/src/fraction.h +++ b/VCamUtils/src/fraction.h @@ -47,6 +47,9 @@ namespace AkVCam int64_t &den(); double value() const; std::string toString() const; + bool isInfinity() const; + int sign() const; + static bool isFraction(const std::string &str); private: FractionPrivate *d; diff --git a/VCamUtils/src/ipcbridge.h b/VCamUtils/src/ipcbridge.h index 3dec9e6..4a73489 100644 --- a/VCamUtils/src/ipcbridge.h +++ b/VCamUtils/src/ipcbridge.h @@ -174,6 +174,15 @@ namespace AkVCam bool needsRoot(const std::string &operation) const; int sudo(int argc, char **argv) const; + /* Hacks */ + + std::vector hacks() const; + std::string hackDescription(const std::string &hack) const; + bool hackIsSafe(const std::string &hack) const; + bool hackNeedsRoot(const std::string &hack) const; + int execHack(const std::string &hack, + const std::vector &args); + private: IpcBridgePrivate *d; diff --git a/VCamUtils/src/utils.cpp b/VCamUtils/src/utils.cpp index e148319..af52fc1 100644 --- a/VCamUtils/src/utils.cpp +++ b/VCamUtils/src/utils.cpp @@ -151,3 +151,11 @@ std::pair AkVCam::splitOnce(const std::string &str, return {first, second}; } + +void AkVCam::move(const std::string &from, const std::string &to) +{ + std::ifstream infile(from, std::ios::in | std::ios::binary); + std::ofstream outfile(to, std::ios::out | std::ios::binary); + outfile << infile.rdbuf(); + std::remove(from.c_str()); +} diff --git a/VCamUtils/src/utils.h b/VCamUtils/src/utils.h index 608cab8..a4c1cbc 100644 --- a/VCamUtils/src/utils.h +++ b/VCamUtils/src/utils.h @@ -154,6 +154,7 @@ namespace AkVCam std::vector split(const std::string &str, char separator); std::pair splitOnce(const std::string &str, const std::string &separator); + void move(const std::string &from, const std::string &to); } #endif // AKVCAMUTILS_UTILS_H diff --git a/appveyor.yml b/appveyor.yml index 138f3bc..6a6c0a7 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,7 +2,7 @@ version: 9.0.0.{build}-{branch} os: MinGW image: Visual Studio 2019 - + platform: - x64 @@ -20,7 +20,7 @@ environment: matrix: - CMAKE_GENERATOR: MSYS Makefiles - - CMAKE_GENERATOR: Visual Studio 16 2019 + - CMAKE_GENERATOR: Visual Studio 16 2019 install: - ports\ci\appveyor\install_deps.bat diff --git a/cmio/VCamIPC/src/ipcbridge.mm b/cmio/VCamIPC/src/ipcbridge.mm index 2152bb3..88d474c 100644 --- a/cmio/VCamIPC/src/ipcbridge.mm +++ b/cmio/VCamIPC/src/ipcbridge.mm @@ -45,8 +45,32 @@ #define AKVCAM_BIND_FUNC(member) \ std::bind(&member, this, std::placeholders::_1, std::placeholders::_2) +#define AKVCAM_BIND_HACK_FUNC(member) \ + std::bind(&member, this, std::placeholders::_1) + namespace AkVCam { + class Hack + { + public: + using HackFunc = std::function &args)>; + + std::string name; + std::string description; + bool isSafe {false}; + bool needsRoot {false}; + HackFunc func; + + Hack(); + Hack(const std::string &name, + const std::string &description, + bool isSafe, + bool needsRoot, + const HackFunc &func); + Hack(const Hack &other); + Hack &operator =(const Hack &other); + }; + class IpcBridgePrivate { public: @@ -81,6 +105,22 @@ namespace AkVCam // Utility methods bool fileExists(const std::string &path) const; static std::string locatePluginPath(); + bool isRoot() const; + std::vector listServices() const; + std::vector listDisabledServices() const; + inline bool isServiceLoaded(const std::string &service) const; + inline bool isServiceDisabled(const std::string &service) const; + bool readEntitlements(const std::string &app, + const std::string &output) const; + + // Hacks + const std::vector &hacks(); + int setServiceUp(const std::vector &args); + int setServiceDown(const std::vector &args); + int disableLibraryValidation(const std::vector &args); + int codeResign(const std::vector &args); + int unsign(const std::vector &args); + int disableSIP(const std::vector &args); private: std::vector m_bridges; @@ -763,15 +803,64 @@ bool AkVCam::IpcBridge::removeListener(const std::string &deviceId) bool AkVCam::IpcBridge::needsRoot(const std::string &operation) const { - UNUSED(operation); + static const std::vector operations; + auto it = std::find(operations.begin(), operations.end(), operation); - return false; + return it != operations.end() && !this->d->isRoot(); } int AkVCam::IpcBridge::sudo(int argc, char **argv) const { UNUSED(argc); UNUSED(argv); + std::cerr << "You must run this command with administrator privileges." << std::endl; + + return -1; +} + +std::vector AkVCam::IpcBridge::hacks() const +{ + std::vector hacks; + + for (auto &hack: this->d->hacks()) + hacks.push_back(hack.name); + + return hacks; +} + +std::string AkVCam::IpcBridge::hackDescription(const std::string &hack) const +{ + for (auto &hck: this->d->hacks()) + if (hck.name == hack) + return hck.description; + + return {}; +} + +bool AkVCam::IpcBridge::hackIsSafe(const std::string &hack) const +{ + for (auto &hck: this->d->hacks()) + if (hck.name == hack) + return hck.isSafe; + + return true; +} + +bool AkVCam::IpcBridge::hackNeedsRoot(const std::string &hack) const +{ + for (auto &hck: this->d->hacks()) + if (hck.name == hack) + return hck.needsRoot && !this->d->isRoot(); + + return false; +} + +int AkVCam::IpcBridge::execHack(const std::string &hack, + const std::vector &args) +{ + for (auto &hck: this->d->hacks()) + if (hck.name == hack) + return hck.func(args); return 0; } @@ -1061,3 +1150,470 @@ std::string AkVCam::IpcBridgePrivate::locatePluginPath() return realPath(dirName + "/../.."); } + +bool AkVCam::IpcBridgePrivate::isRoot() const +{ + AkLogFunction(); + + return getuid() == 0; +} + +std::vector AkVCam::IpcBridgePrivate::listServices() const +{ + std::vector services; + auto proc = popen("launchctl list", "r"); + + if (proc) { + while (!feof(proc)) { + char line[1024]; + + if (!fgets(line, 1024, proc)) + break; + + if (strncmp(line, "PID", 3) == 0) + continue; + + char *pline = strtok(line, " \n\r\t"); + + for (size_t i = 0; i < 3 && pline != nullptr; i++) { + if (i == 2) + services.push_back(pline); + + pline = strtok(nullptr, " \n\r\t"); + } + } + + pclose(proc); + } + + return services; +} + +std::vector AkVCam::IpcBridgePrivate::listDisabledServices() const +{ + std::vector services; + auto proc = popen("launchctl print-disabled system/", "r"); + + if (proc) { + while (!feof(proc)) { + char line[1024]; + + if (!fgets(line, 1024, proc)) + break; + + if (strncmp(line, "\t", 1) != 0) + continue; + + std::string service; + char *pline = strtok(line, " "); + + for (size_t i = 0; i < 3 && pline; i++) { + if (i == 0) + service = std::string(pline).substr(1, strlen(pline) - 2); + + if (i == 2 && strncmp(pline, "true", 4) == 0) + services.push_back(service); + + pline = strtok(nullptr, " "); + } + } + + pclose(proc); + } + + return services; +} + +bool AkVCam::IpcBridgePrivate::isServiceLoaded(const std::string &service) const +{ + auto services = this->listServices(); + + return std::find(services.begin(), services.end(), service) != services.end(); +} + +bool AkVCam::IpcBridgePrivate::isServiceDisabled(const std::string &service) const +{ + auto services = this->listDisabledServices(); + + return std::find(services.begin(), services.end(), service) != services.end(); +} + +bool AkVCam::IpcBridgePrivate::readEntitlements(const std::string &app, + const std::string &output) const +{ + bool writen = false; + std::string cmd = "codesign -d --entitlements - \"" + app + "\""; + auto proc = popen(cmd.c_str(), "r"); + + if (proc) { + auto entitlements = fopen(output.c_str(), "w"); + + if (entitlements) { + for (size_t i = 0; !feof(proc); i++) { + char data[1024]; + auto len = fread(data, 1, 1024, proc); + + if (len < 1) + break; + + size_t offset = 0; + + if (i == 0) + offset = std::string(data, len).find(" &AkVCam::IpcBridgePrivate::hacks() +{ + static const std::vector hacks { + {"set-service-up", + "Setup and start virtual camera service if isn't working", + true, + true, + AKVCAM_BIND_HACK_FUNC(IpcBridgePrivate::setServiceUp)}, + {"set-service-down", + "Stop and unregister virtual camera service", + true, + true, + AKVCAM_BIND_HACK_FUNC(IpcBridgePrivate::setServiceDown)}, + {"disable-library-validation", + "Disable external plugins validation in app bundle", + false, + false, + AKVCAM_BIND_HACK_FUNC(IpcBridgePrivate::disableLibraryValidation)}, + {"code-re-sign", + "Remove app code signature and re-sign it with a developer signature", + false, + false, + AKVCAM_BIND_HACK_FUNC(IpcBridgePrivate::codeResign)}, + {"unsign", + "Remove app code signature", + false, + false, + AKVCAM_BIND_HACK_FUNC(IpcBridgePrivate::unsign)}, + {"disable-sip", + "Disable System Integrity Protection", + false, + false, + AKVCAM_BIND_HACK_FUNC(IpcBridgePrivate::disableSIP)} + }; + + return hacks; +} + +int AkVCam::IpcBridgePrivate::setServiceUp(const std::vector &args) +{ + UNUSED(args); + AkLogFunction(); + std::string pluginPath = this->locatePluginPath(); + static const std::string dstPluginPath = + "/Library/CoreMediaIO/Plug-Ins/DAL/" CMIO_PLUGIN_NAME ".plugin"; + + if (!fileExists(dstPluginPath)) + if (symlink(pluginPath.c_str(), dstPluginPath.c_str()) != 0) { + std::cerr << strerror(errno) << std::endl; + + return -1; + } + + static const std::string daemonPlist = + "/Library/LaunchDaemons/" CMIO_ASSISTANT_NAME ".plist"; + + if (!fileExists(daemonPlist)) { + std::ofstream plist(daemonPlist); + + if (!plist.is_open()) + return -1; + + plist << "" << std::endl; + plist << "" << std::endl; + plist << "" << std::endl; + plist << " " << std::endl; + plist << " Label" << std::endl; + plist << " " CMIO_ASSISTANT_NAME "" << std::endl; + plist << " ProgramArguments" << std::endl; + plist << " " << std::endl; + plist << " "; + plist << pluginPath; + plist << "/Contents/Resources/AkVCamAssistant"; + plist << "" << std::endl; + plist << " --timeout" << std::endl; + plist << " 300.0" << std::endl; + plist << " " << std::endl; + plist << " MachServices" << std::endl; + plist << " " << std::endl; + plist << " " CMIO_ASSISTANT_NAME "" << std::endl; + plist << " " << std::endl; + plist << " " << std::endl; + plist << " StandardOutPath" << std::endl; + plist << " /tmp/AkVCamAssistant.log" << std::endl; + plist << " StandardErrorPath" << std::endl; + plist << " /tmp/AkVCamAssistant.log" << std::endl; + plist << " " << std::endl; + plist << "" << std::endl; + plist.close(); + } + + if (this->isServiceDisabled(CMIO_ASSISTANT_NAME)) { + int result = system("launchctl enable system/" CMIO_ASSISTANT_NAME); + + if (result) + return result; + } + + if (!this->isServiceLoaded(CMIO_ASSISTANT_NAME)) { + auto cmd = "launchctl bootstrap system " + daemonPlist; + int result = system(cmd.c_str()); + + if (result) + return result; + } + + return 0; +} + +int AkVCam::IpcBridgePrivate::setServiceDown(const std::vector &args) +{ + UNUSED(args); + AkLogFunction(); + + static const std::string daemonPlist = + "/Library/LaunchDaemons/" CMIO_ASSISTANT_NAME ".plist"; + + if (fileExists(daemonPlist)) { + if (this->isServiceLoaded(CMIO_ASSISTANT_NAME)) { + auto cmd = "launchctl bootout system " + daemonPlist; + int result = system(cmd.c_str()); + + if (result) + return result; + } + + std::remove(daemonPlist.c_str()); + } + + static const std::string dstPluginPath = + "/Library/CoreMediaIO/Plug-Ins/DAL/" CMIO_PLUGIN_NAME ".plugin"; + + if (this->fileExists(dstPluginPath)) + if (unlink(dstPluginPath.c_str()) != 0) { + std::cerr << strerror(errno) << std::endl; + + return -1; + } + + return 0; +} + +int AkVCam::IpcBridgePrivate::disableLibraryValidation(const std::vector &args) +{ + if (args.size() < 1) { + std::cerr << "Not enough arguments." << std::endl; + + return -1; + } + + if (!this->fileExists(args[0])) { + std::cerr << "No such file or directory." << std::endl; + + return -1; + } + + static const std::string entitlementsXml = "/tmp/entitlements.xml"; + static const std::string dlv = + "com.apple.security.cs.disable-library-validation"; + + if (this->readEntitlements(args[0], entitlementsXml)) { + auto nsdlv = [NSString + stringWithCString: dlv.c_str() + encoding: [NSString defaultCStringEncoding]]; + auto nsEntitlementsXml = [NSString + stringWithCString: entitlementsXml.c_str() + encoding: [NSString defaultCStringEncoding]]; + auto nsEntitlementsXmlUrl = [NSURL + fileURLWithPath: nsEntitlementsXml]; + NSError *error = nil; + auto entitlements = + [[NSXMLDocument alloc] + initWithContentsOfURL: nsEntitlementsXmlUrl + options: 0 + error: &error]; + [nsEntitlementsXmlUrl release]; + const std::string xpath = + "/plist/dict/key[contains(text(), \"" + dlv + "\")]"; + auto nsxpath = [NSString + stringWithCString: xpath.c_str() + encoding: [NSString defaultCStringEncoding]]; + auto nodes = [entitlements nodesForXPath: nsxpath error: &error]; + [nsxpath release]; + + if ([nodes count] < 1) { + auto key = [[NSXMLElement alloc] initWithName: @"key" stringValue: nsdlv]; + [(NSXMLElement *) entitlements.rootElement.children[0] addChild: key]; + [key release]; + auto value = [[NSXMLElement alloc] initWithName: @"true"]; + [(NSXMLElement *) entitlements.rootElement.children[0] addChild: value]; + [value release]; + } else { + for (NSXMLNode *node: nodes) { + auto value = [[NSXMLElement alloc] initWithName: @"true"]; + [(NSXMLElement *) node.parent replaceChildAtIndex: node.nextSibling.index withNode: value]; + [value release]; + } + } + + auto data = [entitlements XMLDataWithOptions: NSXMLNodePrettyPrint + | NSXMLNodeCompactEmptyElement]; + [data writeToFile: nsEntitlementsXml atomically: YES]; + [data release]; + [entitlements release]; + [nsEntitlementsXml release]; + [nsdlv release]; + } else { + std::ofstream entitlements(entitlementsXml); + + if (entitlements.is_open()) { + entitlements << "" << std::endl; + entitlements << "" << std::endl; + entitlements << "" << std::endl; + entitlements << "\t" << std::endl; + entitlements << "\t\t" << dlv << "" << std::endl; + entitlements << "\t\t" << std::endl; + entitlements << "\t" << std::endl; + entitlements << "" << std::endl; + entitlements.close(); + } + } + + auto cmd = "codesign --entitlements \"" + + entitlementsXml + + "\" -f -s - \"" + + args[0] + + "\""; + int result = system(cmd.c_str()); + std::remove(entitlementsXml.c_str()); + + return result; +} + +int AkVCam::IpcBridgePrivate::codeResign(const std::vector &args) +{ + if (args.size() < 1) { + std::cerr << "Not enough arguments." << std::endl; + + return -1; + } + + if (!this->fileExists(args[0])) { + std::cerr << "No such file or directory." << std::endl; + + return -1; + } + + static const std::string entitlementsXml = "/tmp/entitlements.xml"; + std::string cmd; + + if (this->readEntitlements(args[0], entitlementsXml)) + cmd = "codesign --entitlements \"" + + entitlementsXml + + "\" -f -s - \"" + + args[0] + + "\""; + else + cmd = "codesign -f -s - \"" + args[0] + "\""; + + int result = system(cmd.c_str()); + std::remove(entitlementsXml.c_str()); + + return result; +} + +int AkVCam::IpcBridgePrivate::unsign(const std::vector &args) +{ + if (args.size() < 1) { + std::cerr << "Not enough arguments." << std::endl; + + return -1; + } + + if (!this->fileExists(args[0])) { + std::cerr << "No such file or directory." << std::endl; + + return -1; + } + + auto cmd = "codesign --remove-signature \"" + args[0] + "\""; + + return system(cmd.c_str()); +} + +int AkVCam::IpcBridgePrivate::disableSIP(const std::vector &args) +{ + std::cerr << "SIP (System Integrity Protection) can't be disbled from " + "inside the system, you must reboot your system and then " + "press and hold Command + R keys on boot to enter to the " + "recovery mode, then go to Utilities > Terminal and run:" + << std::endl; + std::cerr << std::endl; + std::cerr << "csrutil enable --without fs" << std::endl; + std::cerr << std::endl; + std::cerr << "If that does not works, then run:" << std::endl; + std::cerr << std::endl; + std::cerr << "csrutil disable" << std::endl; + std::cerr << std::endl; + + return -1; +} + +AkVCam::Hack::Hack() +{ + +} + +AkVCam::Hack::Hack(const std::string &name, + const std::string &description, + bool isSafe, + bool needsRoot, + const Hack::HackFunc &func): + name(name), + description(description), + isSafe(isSafe), + needsRoot(needsRoot), + func(func) +{ + +} + +AkVCam::Hack::Hack(const Hack &other): + name(other.name), + description(other.description), + isSafe(other.isSafe), + needsRoot(other.needsRoot), + func(other.func) +{ +} + +AkVCam::Hack &AkVCam::Hack::operator =(const Hack &other) +{ + if (this != &other) { + this->name = other.name; + this->description = other.description; + this->isSafe = other.isSafe; + this->needsRoot = other.needsRoot; + this->func = other.func; + } + + return *this; +} diff --git a/dshow/Assistant/CMakeLists.txt b/dshow/Assistant/CMakeLists.txt index dd55e50..ede8b79 100644 --- a/dshow/Assistant/CMakeLists.txt +++ b/dshow/Assistant/CMakeLists.txt @@ -38,7 +38,7 @@ target_include_directories(Assistant PRIVATE .. PRIVATE ../..) target_link_libraries(Assistant - PlatformUtils + PlatformUtils advapi32 gdi32 ole32 diff --git a/dshow/PlatformUtils/CMakeLists.txt b/dshow/PlatformUtils/CMakeLists.txt index 1822388..ab645ca 100644 --- a/dshow/PlatformUtils/CMakeLists.txt +++ b/dshow/PlatformUtils/CMakeLists.txt @@ -42,7 +42,7 @@ add_library(PlatformUtils STATIC add_dependencies(PlatformUtils VCamUtils) target_compile_definitions(PlatformUtils PRIVATE PLATFORMUTILS_LIBRARY) target_include_directories(PlatformUtils PRIVATE ../..) -target_link_libraries(PlatformUtils +target_link_libraries(PlatformUtils VCamUtils advapi32 kernel32 diff --git a/dshow/PlatformUtils/src/messageserver.cpp b/dshow/PlatformUtils/src/messageserver.cpp index e7e8b96..b6d32a8 100644 --- a/dshow/PlatformUtils/src/messageserver.cpp +++ b/dshow/PlatformUtils/src/messageserver.cpp @@ -31,7 +31,7 @@ #include "VCamUtils/src/logger.h" namespace AkVCam -{ +{ struct PipeThread { std::shared_ptr thread; @@ -400,7 +400,7 @@ void AkVCam::MessageServerPrivate::messagesLoop() for (auto &thread: this->m_clientsThreads) thread->thread->join(); - LocalFree(securityDescriptor); + LocalFree(securityDescriptor); AKVCAM_EMIT(this->self, StateChanged, MessageServer::StateStopped) AkLogDebug() << "Server stopped." << std::endl; } diff --git a/dshow/VCamIPC/CMakeLists.txt b/dshow/VCamIPC/CMakeLists.txt index 4b89e47..ad29bbb 100644 --- a/dshow/VCamIPC/CMakeLists.txt +++ b/dshow/VCamIPC/CMakeLists.txt @@ -35,7 +35,7 @@ target_include_directories(VCamIPC PRIVATE .. PRIVATE ../..) target_link_libraries(VCamIPC - PlatformUtils + PlatformUtils advapi32 kernel32 psapi) diff --git a/dshow/VCamIPC/src/ipcbridge.cpp b/dshow/VCamIPC/src/ipcbridge.cpp index de3ebd5..e7e2ca0 100644 --- a/dshow/VCamIPC/src/ipcbridge.cpp +++ b/dshow/VCamIPC/src/ipcbridge.cpp @@ -49,6 +49,27 @@ namespace AkVCam Mutex mutex; }; + class Hack + { + public: + using HackFunc = std::function &args)>; + + std::string name; + std::string description; + bool isSafe {false}; + bool needsRoot {false}; + HackFunc func; + + Hack(); + Hack(const std::string &name, + const std::string &description, + bool isSafe, + bool needsRoot, + const HackFunc &func); + Hack(const Hack &other); + Hack &operator =(const Hack &other); + }; + class IpcBridgePrivate { public: @@ -87,9 +108,16 @@ namespace AkVCam int sudo(const std::vector ¶meters, const std::string &directory={}, bool show=false); + std::string assistant() const; std::string manager() const; std::string alternativeManager() const; std::string alternativePlugin() const; + std::string service() const; + + // Hacks + const std::vector &hacks(); + int setServiceUp(const std::vector &args); + int setServiceDown(const std::vector &args); }; static const int maxFrameWidth = 1920; @@ -178,7 +206,7 @@ bool AkVCam::IpcBridge::registerPeer() return false; } - AkLogDebug() << "Starting message server." << std::endl; + AkLogDebug() << "Starting message server." << std::endl; auto pipeName = "\\\\.\\pipe\\" + portName; this->d->m_messageServer.setPipeName(pipeName); this->d->m_messageServer.setHandlers(this->d->m_messageHandlers); @@ -244,7 +272,7 @@ void AkVCam::IpcBridge::unregisterPeer() (std::min)(this->d->m_portName.size(), MAX_STRING)); MessageServer::sendMessage("\\\\.\\pipe\\" DSHOW_PLUGIN_ASSISTANT_NAME, &message); - this->d->m_messageServer.stop(); + this->d->m_messageServer.stop(); this->d->m_sharedMemory.setName({}); this->d->m_globalMutex = {}; this->d->m_portName.clear(); @@ -465,7 +493,7 @@ std::vector AkVCam::IpcBridge::clientsPids() const std::vector plugins; // First check for the existence of the main plugin binary. - auto path = pluginPath + "\\" DSHOW_PLUGIN_NAME ".dll"; + auto path = pluginPath + "\\" DSHOW_PLUGIN_NAME ".dll"; AkLogDebug() << "Plugin binary: " << path << std::endl; if (fileExists(path)) @@ -826,15 +854,15 @@ bool AkVCam::IpcBridge::needsRoot(const std::string &operation) const { static const std::vector operations { "add-device", + "add-format", + "load", "remove-device", "remove-devices", - "set-description", - "add-format", "remove-format", "remove-formats", - "update", - "load", + "set-description", "set-loglevel", + "update" }; auto it = std::find(operations.begin(), operations.end(), operation); @@ -853,6 +881,53 @@ int AkVCam::IpcBridge::sudo(int argc, char **argv) const return this->d->sudo(arguments); } +std::vector AkVCam::IpcBridge::hacks() const +{ + std::vector hacks; + + for (auto &hack: this->d->hacks()) + hacks.push_back(hack.name); + + return hacks; +} + +std::string AkVCam::IpcBridge::hackDescription(const std::string &hack) const +{ + for (auto &hck: this->d->hacks()) + if (hck.name == hack) + return hck.description; + + return {}; +} + +bool AkVCam::IpcBridge::hackIsSafe(const std::string &hack) const +{ + for (auto &hck: this->d->hacks()) + if (hck.name == hack) + return hck.isSafe; + + return true; +} + +bool AkVCam::IpcBridge::hackNeedsRoot(const std::string &hack) const +{ + for (auto &hck: this->d->hacks()) + if (hck.name == hack) + return hck.needsRoot && !this->d->isRoot(); + + return false; +} + +int AkVCam::IpcBridge::execHack(const std::string &hack, + const std::vector &args) +{ + for (auto &hck: this->d->hacks()) + if (hck.name == hack) + return hck.func(args); + + return 0; +} + AkVCam::IpcBridgePrivate::IpcBridgePrivate(IpcBridge *self): self(self) { @@ -1180,6 +1255,31 @@ int AkVCam::IpcBridgePrivate::sudo(const std::vector ¶meters, return int(exitCode); } +std::string AkVCam::IpcBridgePrivate::assistant() const +{ + AkLogFunction(); + auto pluginPath = locatePluginPath(); + auto path = realPath(pluginPath + "\\" DSHOW_PLUGIN_ASSISTANT_NAME ".exe"); + + if (fileExists(path)) + return path; + +#ifdef _WIN64 + path = realPath(pluginPath + "\\..\\x86\\" DSHOW_PLUGIN_ASSISTANT_NAME ".exe"); +#else + SYSTEM_INFO info; + memset(&info, 0, sizeof(SYSTEM_INFO)); + GetNativeSystemInfo(&info); + + if (info.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL) + return {}; + + path = realPath(pluginPath + "\\..\\x64\\" DSHOW_PLUGIN_ASSISTANT_NAME ".exe"); +#endif + + return fileExists(path)? path: std::string(); +} + std::string AkVCam::IpcBridgePrivate::manager() const { AkLogFunction(); @@ -1230,3 +1330,188 @@ std::string AkVCam::IpcBridgePrivate::alternativePlugin() const return fileExists(path)? path: std::string(); } + +std::string AkVCam::IpcBridgePrivate::service() const +{ + std::string path; + auto manager = OpenSCManager(nullptr, nullptr, GENERIC_READ); + + if (manager) { + auto service = OpenServiceA(manager, + DSHOW_PLUGIN_ASSISTANT_NAME, + SERVICE_QUERY_CONFIG); + + if (service) { + DWORD bytesNeeded = 0; + QueryServiceConfig(service, nullptr, 0, &bytesNeeded); + auto bufSize = bytesNeeded; + auto serviceConfig = + reinterpret_cast(LocalAlloc(LMEM_FIXED, + bufSize)); + if (serviceConfig) { + if (QueryServiceConfigA(service, + serviceConfig, + bufSize, + &bytesNeeded)) { + path = std::string(serviceConfig->lpBinaryPathName); + } + + LocalFree(serviceConfig); + } + + CloseServiceHandle(service); + } + + CloseServiceHandle(manager); + } + + return path; +} + +const std::vector &AkVCam::IpcBridgePrivate::hacks() +{ + static const std::vector hacks { + {"set-service-up", + "Setup and start virtual camera service if isn't working", + true, + true, + AKVCAM_BIND_FUNC(IpcBridgePrivate::setServiceUp)}, + {"set-service-down", + "Stop and unregister virtual camera service", + true, + true, + AKVCAM_BIND_FUNC(IpcBridgePrivate::setServiceDown)} + }; + + return hacks; +} + +int AkVCam::IpcBridgePrivate::setServiceUp(const std::vector &args) +{ + UNUSED(args); + AkLogFunction(); + + // If the service is not installed, install it. + auto servicePath = this->service(); + + if (servicePath.empty()) { + auto assistant = this->assistant(); + + if (assistant.empty()) + return -1; + + auto result = this->sudo({assistant, "--install"}); + + if (result < 0) + return result; + } + + // Start the service. + bool result = false; + auto manager = OpenSCManager(nullptr, nullptr, SC_MANAGER_CONNECT); + + if (manager) { + auto service = OpenService(manager, + TEXT(DSHOW_PLUGIN_ASSISTANT_NAME), + SERVICE_START); + + if (service) { + result = StartService(service, 0, nullptr); + CloseServiceHandle(service); + } + + CloseServiceHandle(manager); + } + + return result? 0: -1; +} + +int AkVCam::IpcBridgePrivate::setServiceDown(const std::vector &args) +{ + UNUSED(args); + AkLogFunction(); + auto servicePath = this->service(); + + if (servicePath.empty()) + return 0; + + // Stop the service. + bool stopped = false; + auto manager = OpenSCManager(nullptr, nullptr, SC_MANAGER_ALL_ACCESS); + + if (manager) { + auto service = OpenService(manager, + TEXT(DSHOW_PLUGIN_ASSISTANT_NAME), + SERVICE_STOP | SERVICE_QUERY_STATUS); + + if (service) { + SERVICE_STATUS status; + memset(&status, 0, sizeof(SERVICE_STATUS)); + + if (ControlService(service, SERVICE_CONTROL_STOP, &status)) { + memset(&status, 0, sizeof(SERVICE_STATUS)); + + while(QueryServiceStatus(service, &status)) { + if (status.dwCurrentState != SERVICE_STOP_PENDING) + break; + + Sleep(1000); + } + + stopped = status.dwCurrentState == SERVICE_STOPPED; + } + + CloseServiceHandle(service); + } + + CloseServiceHandle(manager); + } + + if (!stopped) + return -1; + + // Unistall the service. + + return this->sudo({servicePath, "--uninstall"}); +} + +AkVCam::Hack::Hack() +{ + +} + +AkVCam::Hack::Hack(const std::string &name, + const std::string &description, + bool isSafe, + bool needsRoot, + const Hack::HackFunc &func): + name(name), + description(description), + isSafe(isSafe), + needsRoot(needsRoot), + func(func) +{ + +} + +AkVCam::Hack::Hack(const Hack &other): + name(other.name), + description(other.description), + isSafe(other.isSafe), + needsRoot(other.needsRoot), + func(other.func) +{ +} + +AkVCam::Hack &AkVCam::Hack::operator =(const Hack &other) +{ + if (this != &other) { + this->name = other.name; + this->description = other.description; + this->isSafe = other.isSafe; + this->needsRoot = other.needsRoot; + this->func = other.func; + } + + return *this; +} diff --git a/dshow/VirtualCamera/src/pin.cpp b/dshow/VirtualCamera/src/pin.cpp index 3f55aa1..e082d8e 100644 --- a/dshow/VirtualCamera/src/pin.cpp +++ b/dshow/VirtualCamera/src/pin.cpp @@ -203,7 +203,9 @@ HRESULT AkVCam::Pin::stateChanged(void *userData, FILTER_STATE state) return VFW_E_NOT_COMMITTED; self->d->updateTestFrame(); + self->d->m_mutex.lock(); self->d->m_currentFrame = self->d->m_testFrameAdapted; + self->d->m_mutex.unlock(); self->d->m_pts = -1; self->d->m_ptsDrift = 0; @@ -241,7 +243,9 @@ HRESULT AkVCam::Pin::stateChanged(void *userData, FILTER_STATE state) CloseHandle(self->d->m_sendFrameEvent); self->d->m_sendFrameEvent = nullptr; self->d->m_memAllocator->Decommit(); + self->d->m_mutex.lock(); self->d->m_currentFrame.clear(); + self->d->m_mutex.unlock(); self->d->m_testFrameAdapted.clear(); } diff --git a/ports/ci/appveyor/build.bat b/ports/ci/appveyor/build.bat old mode 100755 new mode 100644 diff --git a/ports/ci/appveyor/deploy.bat b/ports/ci/appveyor/deploy.bat old mode 100755 new mode 100644 diff --git a/ports/ci/appveyor/install_deps.bat b/ports/ci/appveyor/install_deps.bat old mode 100755 new mode 100644 diff --git a/ports/ci/appveyor/push_artifacts.bat b/ports/ci/appveyor/push_artifacts.bat old mode 100755 new mode 100644 diff --git a/ports/ci/appveyor/upload.bat b/ports/ci/appveyor/upload.bat old mode 100755 new mode 100644