diff --git a/.gitignore b/.gitignore index 259148f..7170c9a 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,5 @@ *.exe *.out *.app + +build/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e10b2a0 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,44 @@ +cmake_minimum_required(VERSION 3.25) + +project(xray-serialization VERSION 1.0.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_AUTOUIC ON) + +# Setup for shared library creation +set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) +if(WIN32) + set(CMAKE_SHARED_LIBRARY_PREFIX "") +endif() + +find_package(Qt6 REQUIRED COMPONENTS Core Core5Compat) + +set(SOURCES + serialization.h + transfer.h + inbound.cpp + outbound.cpp + ss.cpp + ssd.cpp + trojan.cpp + vless.cpp + vmess.cpp + vmess_new.cpp +) + +add_library(${PROJECT_NAME} SHARED ${SOURCES}) + +target_include_directories(${PROJECT_NAME} + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/3rd/QJsonStruct +) + +target_link_libraries(${PROJECT_NAME} + PRIVATE + Qt6::Core + Qt6::Core5Compat +) \ No newline at end of file diff --git a/utilities.cpp b/utilities.cpp new file mode 100755 index 0000000..61944e5 --- /dev/null +++ b/utilities.cpp @@ -0,0 +1,360 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utilities.h" + +#ifdef Q_OS_WINDOWS +QString printErrorMessage(DWORD errorCode) { + LPVOID lpMsgBuf; + + DWORD dwFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS; + + DWORD dwLanguageId = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US); + + FormatMessageW( + dwFlags, + NULL, + errorCode, + dwLanguageId, + (LPWSTR)&lpMsgBuf, + 0, + NULL + ); + + QString errorMsg = QString::fromWCharArray((LPCWSTR)lpMsgBuf); + LocalFree(lpMsgBuf); + return errorMsg.trimmed(); +} + +QString Utils::getNextDriverLetter() +{ + DWORD drivesBitmask = GetLogicalDrives(); + if (drivesBitmask == 0) { + DWORD error = GetLastError(); + qDebug() << "GetLogicalDrives failed. Error code:" << error; + return ""; + } + + QString letters = "FGHIJKLMNOPQRSTUVWXYZ"; + QString availableLetter; + + for (int i = letters.size() - 1; i >= 0; --i) { + QChar letterChar = letters.at(i); + int driveIndex = letterChar.toLatin1() - 'A'; + + if ((drivesBitmask & (1 << driveIndex)) == 0) { + availableLetter = letterChar; + break; + } + } + + if (availableLetter.isEmpty()) { + qDebug() << "Can't find free drive letter"; + return ""; + } + + return availableLetter; +} +#endif + +QString Utils::getRandomString(int len) +{ + const QString possibleCharacters = QStringLiteral("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); + QString randomString; + + for (int i = 0; i < len; ++i) { + randomString.append(possibleCharacters.at(QRandomGenerator::system()->bounded(possibleCharacters.length()))); + } + + return randomString; +} + +QString Utils::VerifyJsonString(const QString &source) +{ + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(source.toUtf8(), &error); + Q_UNUSED(doc) + + if (error.error == QJsonParseError::NoError) { + return ""; + } else { + qDebug() << "WARNING: Json parse returns: " + error.errorString(); + return error.errorString(); + } +} + +QJsonObject Utils::JsonFromString(const QString &string) +{ + auto removeComment = string.trimmed(); + if (removeComment != string.trimmed()) { + qDebug() << "Some comments have been removed from the json."; + } + QJsonDocument doc = QJsonDocument::fromJson(removeComment.toUtf8()); + return doc.object(); +} + +QString Utils::SafeBase64Decode(QString string) +{ + QByteArray ba = string.replace(QChar('-'), QChar('+')).replace(QChar('_'), QChar('/')).toUtf8(); + return QByteArray::fromBase64(ba, QByteArray::Base64Option::OmitTrailingEquals); +} + +QString Utils::JsonToString(const QJsonObject &json, QJsonDocument::JsonFormat format) +{ + QJsonDocument doc; + doc.setObject(json); + return doc.toJson(format); +} + +QString Utils::JsonToString(const QJsonArray &array, QJsonDocument::JsonFormat format) +{ + QJsonDocument doc; + doc.setArray(array); + return doc.toJson(format); +} + +bool Utils::initializePath(const QString &path) +{ + QDir dir; + if (!dir.mkpath(path)) { + qWarning().noquote() << QString("Cannot initialize path: '%1'").arg(path); + return false; + } + return true; +} + +bool Utils::createEmptyFile(const QString &path) +{ + QFile f(path); + return f.open(QIODevice::WriteOnly | QIODevice::Truncate); +} + +QString Utils::executable(const QString &baseName, bool absPath) +{ + QString ext; +#ifdef Q_OS_WIN + ext = ".exe"; +#endif + const QString fileName = baseName + ext; + if (!absPath) { + return fileName; + } + return QCoreApplication::applicationDirPath() + "/" + fileName; +} + +QString Utils::usrExecutable(const QString &baseName) +{ + if (QFileInfo::exists("/usr/sbin/" + baseName)) + return ("/usr/sbin/" + baseName); + else + return ("/usr/bin/" + baseName); +} + +bool Utils::processIsRunning(const QString &fileName, const bool fullFlag) +{ +#ifdef Q_OS_WIN + HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (hSnapshot == INVALID_HANDLE_VALUE) { + qWarning() << "Utils::processIsRunning error CreateToolhelp32Snapshot"; + return false; + } + + PROCESSENTRY32W pe32; + pe32.dwSize = sizeof(PROCESSENTRY32W); + + if (!Process32FirstW(hSnapshot, &pe32)) { + CloseHandle(hSnapshot); + qWarning() << "Utils::processIsRunning error Process32FirstW"; + return false; + } + + do { + QString exeFile = QString::fromWCharArray(pe32.szExeFile); + + if (exeFile.compare(fileName, Qt::CaseInsensitive) == 0) { + CloseHandle(hSnapshot); + return true; + } + } while (Process32NextW(hSnapshot, &pe32)); + + CloseHandle(hSnapshot); + return false; + +#elif defined(Q_OS_IOS) || defined(Q_OS_ANDROID) + return false; +#else + QProcess process; + QStringList arguments; + if (fullFlag) { + arguments << "-f"; + } + arguments << fileName; + process.setProcessChannelMode(QProcess::MergedChannels); + process.start("pgrep", arguments); + process.waitForFinished(); + if (process.exitStatus() == QProcess::NormalExit) { + if (fullFlag) { + return (process.readLine().toUInt() > 0); + } else { + return (process.readAll().toUInt() > 0); + } + } + return false; +#endif +} + +bool Utils::killProcessByName(const QString &name) +{ + qDebug().noquote() << "Kill process" << name; +#ifdef Q_OS_WIN + HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (hSnapshot == INVALID_HANDLE_VALUE) + return false; + + PROCESSENTRY32W pe32; + pe32.dwSize = sizeof(PROCESSENTRY32W); + + bool success = false; + + if (Process32FirstW(hSnapshot, &pe32)) { + do { + QString exeFile = QString::fromWCharArray(pe32.szExeFile); + + if (exeFile.compare(name, Qt::CaseInsensitive) == 0) { + HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pe32.th32ProcessID); + if (hProcess != NULL) { + if (TerminateProcess(hProcess, 0)) { + success = true; + } else { + DWORD error = GetLastError(); + qCritical() << "Can't terminate process" << exeFile << "(PID:" << pe32.th32ProcessID << "). Error:" << printErrorMessage(error); + } + CloseHandle(hProcess); + } else { + DWORD error = GetLastError(); + qCritical() << "Can't open process for termination" << exeFile << "(PID:" << pe32.th32ProcessID << "). Error:" << printErrorMessage(error); + } + } + } while (Process32NextW(hSnapshot, &pe32)); + } + + CloseHandle(hSnapshot); + return success; +#elif defined Q_OS_IOS || defined(Q_OS_ANDROID) + return false; +#else + return QProcess::execute("pkill", { name }) == 0; +#endif +} + +QString Utils::openVpnExecPath() +{ +#ifdef Q_OS_WIN + return Utils::executable("openvpn/openvpn", true); +#elif defined Q_OS_LINUX + // We have service that runs OpenVPN on Linux. We need to make same + // path for client and service. + return Utils::executable("../../client/bin/openvpn", true); +#else + return Utils::executable("/openvpn", true); +#endif +} + +QString Utils::wireguardExecPath() +{ +#ifdef Q_OS_WIN + return Utils::executable("wireguard/wireguard-service", true); +#elif defined Q_OS_LINUX + return Utils::usrExecutable("wg-quick"); +#elif defined Q_OS_MAC + return Utils::executable("/wireguard", true); +#else + return {}; +#endif +} + +QString Utils::certUtilPath() +{ +#ifdef Q_OS_WIN + QString winPath = QString::fromUtf8(qgetenv("windir")); + return winPath + "\\system32\\certutil.exe"; +#else + return ""; +#endif +} + +QString Utils::tun2socksPath() +{ +#ifdef Q_OS_WIN + return Utils::executable("xray/tun2socks", true); +#elif defined Q_OS_LINUX + // We have service that runs OpenVPN on Linux. We need to make same + // path for client and service. + return Utils::executable("../../client/bin/tun2socks", true); +#else + return Utils::executable("/tun2socks", true); +#endif +} + +#ifdef Q_OS_WIN +// Inspired from http://stackoverflow.com/a/15281070/1529139 +// and http://stackoverflow.com/q/40059902/1529139 +bool Utils::signalCtrl(DWORD dwProcessId, DWORD dwCtrlEvent) +{ + bool success = false; + DWORD thisConsoleId = GetCurrentProcessId(); + // Leave current console if it exists + // (otherwise AttachConsole will return ERROR_ACCESS_DENIED) + bool consoleDetached = (FreeConsole() != FALSE); + + if (AttachConsole(dwProcessId) != FALSE) { + // Add a fake Ctrl-C handler for avoid instant kill is this console + // WARNING: do not revert it or current program will be also killed + SetConsoleCtrlHandler(nullptr, true); + success = (GenerateConsoleCtrlEvent(dwCtrlEvent, 0) != FALSE); + FreeConsole(); + } + + if (consoleDetached) { + // Create a new console if previous was deleted by OS + if (AttachConsole(thisConsoleId) == FALSE) { + int errorCode = GetLastError(); + if (errorCode == 31) // 31=ERROR_GEN_FAILURE + { + AllocConsole(); + } + } + } + return success; +} + +#endif + +void Utils::logException(const std::exception &e) +{ + qCritical() << e.what(); + try { + std::rethrow_if_nested(e); + } catch (const std::exception &nested) { + logException(nested); + } catch (...) {} +} + +void Utils::logException(const std::exception_ptr &eptr) +{ + try { + if (eptr) std::rethrow_exception(eptr); + } catch (const std::exception &e) { + logException(e); + } catch (...) {} +} diff --git a/utilities.h b/utilities.h new file mode 100755 index 0000000..4a1985b --- /dev/null +++ b/utilities.h @@ -0,0 +1,47 @@ +#ifndef UTILITIES_H +#define UTILITIES_H + +#include +#include +#include +#include + +#ifdef Q_OS_WIN +#include +#include +#endif + +class Utils : public QObject +{ + Q_OBJECT + +public: + static QString getRandomString(int len); + static QString SafeBase64Decode(QString string); + static QString VerifyJsonString(const QString &source); + static QString JsonToString(const QJsonObject &json, QJsonDocument::JsonFormat format); + static QString JsonToString(const QJsonArray &array, QJsonDocument::JsonFormat format); + static QJsonObject JsonFromString(const QString &string); + static QString executable(const QString &baseName, bool absPath); + static QString usrExecutable(const QString &baseName); + static bool createEmptyFile(const QString &path); + static bool initializePath(const QString &path); + + static bool processIsRunning(const QString &fileName, const bool fullFlag = false); + static bool killProcessByName(const QString &name); + + static QString openVpnExecPath(); + static QString wireguardExecPath(); + static QString certUtilPath(); + static QString tun2socksPath(); + + static void logException(const std::exception &e); + static void logException(const std::exception_ptr &eptr = std::current_exception()); + +#ifdef Q_OS_WIN + static bool signalCtrl(DWORD dwProcessId, DWORD dwCtrlEvent); + static QString getNextDriverLetter(); +#endif +}; + +#endif // UTILITIES_H