Init: Migrate SimpleRemoter (Since v1.3.1) to Gitea

This commit is contained in:
yuanyuanxiang
2026-04-19 19:55:01 +02:00
commit 5a325a202b
744 changed files with 235562 additions and 0 deletions

238
common/DateVerify.h Normal file
View File

@@ -0,0 +1,238 @@
#pragma once
#include <iostream>
#include <ctime>
#include <map>
#include <chrono>
#include <cmath>
#include <WinSock2.h>
#include <Windows.h>
#pragma comment(lib, "ws2_32.lib")
// NTP服务器列表按客户端优先级中国50%+ > 港澳台20% > 日新5% > 其他)
static const char* NTP_SERVERS[] = {
// 中国大陆 (50%+)
"ntp.aliyun.com", // 阿里云,国内最快
"ntp1.tencent.com", // 腾讯云
"ntp.tuna.tsinghua.edu.cn", // 清华大学 TUNA
"cn.pool.ntp.org", // 中国 NTP 池
// 亚太区域 (香港20% + 日本/新加坡5%)
"time.asia.apple.com", // Apple 亚太节点
"time.google.com", // Google 亚太有节点
"hk.pool.ntp.org", // 香港 NTP 池
"jp.pool.ntp.org", // 日本 NTP 池
"sg.pool.ntp.org", // 新加坡 NTP 池
// 全球兜底 (~20%其他地区)
"pool.ntp.org", // 全球 NTP 池
"time.cloudflare.com", // Cloudflare 全球 Anycast
};
static const int NTP_SERVER_COUNT = sizeof(NTP_SERVERS) / sizeof(NTP_SERVERS[0]);
static const int NTP_PORT = 123;
static const uint64_t NTP_EPOCH_OFFSET = 2208988800ULL;
// 检测程序是否处于试用期
class DateVerify
{
private:
bool m_hasVerified = false;
bool m_lastTimeTampered = true; // 上次检测结果true=被篡改
time_t m_lastVerifyLocalTime = 0;
time_t m_lastNetworkTime = 0;
static const int VERIFY_INTERVAL = 6 * 3600; // 6小时
// 初始化Winsock
bool initWinsock()
{
WSADATA wsaData;
return WSAStartup(MAKEWORD(2, 2), &wsaData) == 0;
}
// 从指定NTP服务器获取时间
time_t getTimeFromServer(const char* server)
{
SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sock == INVALID_SOCKET) return 0;
sockaddr_in serverAddr{};
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(NTP_PORT);
// 解析主机名
hostent* host = gethostbyname(server);
if (!host) {
closesocket(sock);
return 0;
}
serverAddr.sin_addr.s_addr = *((unsigned long*)host->h_addr_list[0]);
// 设置超时
DWORD timeout = 2000; // 2秒超时
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout));
// 准备NTP请求包
char ntpPacket[48] = { 0 };
ntpPacket[0] = 0x1B; // LI=0, VN=3, Mode=3
// 发送请求
if (sendto(sock, ntpPacket, sizeof(ntpPacket), 0,
(sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
closesocket(sock);
return 0;
}
// 接收响应
if (recv(sock, ntpPacket, sizeof(ntpPacket), 0) <= 0) {
closesocket(sock);
return 0;
}
closesocket(sock);
// 解析NTP时间
uint32_t ntpTime = ntohl(*((uint32_t*)(ntpPacket + 40)));
return ntpTime - NTP_EPOCH_OFFSET;
}
// 获取网络时间(尝试多个服务器)
time_t getNetworkTimeInChina()
{
if (!initWinsock()) return 0;
time_t result = 0;
for (int i = 0; i < NTP_SERVER_COUNT && result == 0; i++) {
result = getTimeFromServer(NTP_SERVERS[i]);
if (result != 0) {
break;
}
}
WSACleanup();
return result;
}
// 将月份缩写转换为数字(1-12)
int monthAbbrevToNumber(const std::string& month)
{
static const std::map<std::string, int> months = {
{"Jan", 1}, {"Feb", 2}, {"Mar", 3}, {"Apr", 4},
{"May", 5}, {"Jun", 6}, {"Jul", 7}, {"Aug", 8},
{"Sep", 9}, {"Oct", 10}, {"Nov", 11}, {"Dec", 12}
};
auto it = months.find(month);
return (it != months.end()) ? it->second : 0;
}
// 解析__DATE__字符串为tm结构
tm parseCompileDate(const char* compileDate)
{
tm tmCompile = { 0 };
std::string monthStr(compileDate, 3);
std::string dayStr(compileDate + 4, 2);
std::string yearStr(compileDate + 7, 4);
tmCompile.tm_year = std::stoi(yearStr) - 1900;
tmCompile.tm_mon = monthAbbrevToNumber(monthStr) - 1;
tmCompile.tm_mday = std::stoi(dayStr);
return tmCompile;
}
// 计算两个日期之间的天数差
int daysBetweenDates(const tm& date1, const tm& date2)
{
auto timeToTimePoint = [](const tm& tmTime) {
std::time_t tt = mktime(const_cast<tm*>(&tmTime));
return std::chrono::system_clock::from_time_t(tt);
};
auto tp1 = timeToTimePoint(date1);
auto tp2 = timeToTimePoint(date2);
auto duration = tp1 > tp2 ? tp1 - tp2 : tp2 - tp1;
return std::chrono::duration_cast<std::chrono::hours>(duration).count() / 24;
}
// 获取当前日期
tm getCurrentDate()
{
std::time_t now = std::time(nullptr);
tm tmNow = *std::localtime(&now);
tmNow.tm_hour = 0;
tmNow.tm_min = 0;
tmNow.tm_sec = 0;
return tmNow;
}
public:
// 检测本地时间是否被篡改(用于授权验证)
// toleranceDays: 允许的时间偏差天数,超过此值视为篡改
// 返回值: true=时间被篡改或无法验证, false=时间正常
bool isTimeTampered(int toleranceDays = 1)
{
time_t currentLocalTime = time(nullptr);
// 检查是否可以使用缓存
if (m_hasVerified) {
time_t localElapsed = currentLocalTime - m_lastVerifyLocalTime;
// 本地时间在合理范围内前进,使用缓存推算
if (localElapsed >= 0 && localElapsed < VERIFY_INTERVAL) {
time_t estimatedNetworkTime = m_lastNetworkTime + localElapsed;
double diffDays = difftime(estimatedNetworkTime, currentLocalTime) / 86400.0;
if (fabs(diffDays) <= toleranceDays) {
return false; // 未篡改
}
}
}
// 执行网络验证
time_t networkTime = getNetworkTimeInChina();
if (networkTime == 0) {
// 网络不可用:如果之前验证通过且本地时间没异常,暂时信任
if (m_hasVerified && !m_lastTimeTampered) {
time_t localElapsed = currentLocalTime - m_lastVerifyLocalTime;
// 允许5分钟回拨和24小时内的前进
if (localElapsed >= -300 && localElapsed < 24 * 3600) {
return false;
}
}
return false; // 无法验证,视为篡改
}
// 更新缓存
m_hasVerified = true;
m_lastVerifyLocalTime = currentLocalTime;
m_lastNetworkTime = networkTime;
double diffDays = difftime(networkTime, currentLocalTime) / 86400.0;
m_lastTimeTampered = fabs(diffDays) > toleranceDays;
return m_lastTimeTampered;
}
// 获取网络时间与本地时间的偏差(秒)
// 返回值: 正数=本地时间落后, 负数=本地时间超前, 0=无法获取网络时间
int getTimeOffset()
{
time_t networkTime = getNetworkTimeInChina();
if (networkTime == 0) return 0;
return static_cast<int>(difftime(networkTime, time(nullptr)));
}
bool isTrial(int trialDays = 7)
{
if (isTimeTampered())
return false;
tm tmCompile = parseCompileDate(__DATE__), tmCurrent = getCurrentDate();
// 计算天数差
int daysDiff = daysBetweenDates(tmCompile, tmCurrent);
return daysDiff <= trialDays;
}
// 保留旧函数名兼容性(已弃用)
bool isTrail(int trailDays = 7) { return isTrial(trailDays); }
};

181
common/IPBlacklist.h Normal file
View File

@@ -0,0 +1,181 @@
// IPBlacklist.h - IP 黑名单管理 (单例)
// 用于拒绝特定 IP 的所有请求
#pragma once
#include <string>
#include <set>
#include <map>
#include <ctime>
#ifdef _WIN32
#include <windows.h>
#else
#include <pthread.h>
#endif
class IPBlacklist {
public:
static IPBlacklist& getInstance() {
static IPBlacklist instance;
return instance;
}
// 从配置字符串加载黑名单 (格式: "192.168.1.1;10.0.0.1")
void Load(const std::string& configValue) {
AutoLock lock(m_Lock);
m_IPs.clear();
if (configValue.empty()) {
return;
}
// 按分号分割
size_t start = 0;
size_t end = 0;
while ((end = configValue.find(';', start)) != std::string::npos) {
AddIPInternal(configValue.substr(start, end - start));
start = end + 1;
}
// 最后一个 IP
AddIPInternal(configValue.substr(start));
}
// 检查 IP 是否在黑名单中
bool IsBlacklisted(const std::string& ip) {
// 本地地址永不加入黑名单
if (ip == "127.0.0.1" || ip == "::1") {
return false;
}
AutoLock lock(m_Lock);
return m_IPs.find(ip) != m_IPs.end();
}
// 添加 IP 到黑名单
void AddIP(const std::string& ip) {
if (ip == "127.0.0.1" || ip == "::1") {
return; // 本地地址不能加入黑名单
}
AutoLock lock(m_Lock);
AddIPInternal(ip);
}
// 从黑名单移除 IP
void RemoveIP(const std::string& ip) {
AutoLock lock(m_Lock);
m_IPs.erase(ip);
}
// 获取黑名单数量
size_t Count() {
AutoLock lock(m_Lock);
return m_IPs.size();
}
// 获取所有黑名单 IP用于显示
std::set<std::string> GetAll() {
AutoLock lock(m_Lock);
return m_IPs;
}
// 清空黑名单
void Clear() {
AutoLock lock(m_Lock);
m_IPs.clear();
}
// 导出为配置字符串
std::string Export() {
AutoLock lock(m_Lock);
std::string result;
for (const auto& ip : m_IPs) {
if (!result.empty()) result += ";";
result += ip;
}
return result;
}
// 检查是否应该记录日志(防刷频,同一 IP 每 300 秒最多记录一次)
bool ShouldLog(const std::string& ip) {
AutoLock lock(m_Lock);
time_t now = time(nullptr);
// 定期清理过期的日志时间记录 (每 100 次检查一次,或条目超过 1000)
if (++m_CleanupCounter >= 100 || m_LastLogTime.size() > 1000) {
m_CleanupCounter = 0;
for (auto it = m_LastLogTime.begin(); it != m_LastLogTime.end(); ) {
if (now - it->second >= 300) { // 5分钟未活动则清理
it = m_LastLogTime.erase(it);
} else {
++it;
}
}
}
auto it = m_LastLogTime.find(ip);
if (it == m_LastLogTime.end() || (now - it->second) >= 300) {
m_LastLogTime[ip] = now;
return true;
}
return false;
}
private:
// RAII 锁,异常安全
#ifdef _WIN32
class AutoLock {
public:
AutoLock(CRITICAL_SECTION& cs) : m_cs(cs) { EnterCriticalSection(&m_cs); }
~AutoLock() { LeaveCriticalSection(&m_cs); }
private:
CRITICAL_SECTION& m_cs;
};
#else
class AutoLock {
public:
AutoLock(pthread_mutex_t& mtx) : m_mtx(mtx) { pthread_mutex_lock(&m_mtx); }
~AutoLock() { pthread_mutex_unlock(&m_mtx); }
private:
pthread_mutex_t& m_mtx;
};
#endif
IPBlacklist() {
#ifdef _WIN32
InitializeCriticalSection(&m_Lock);
#else
pthread_mutex_init(&m_Lock, nullptr);
#endif
}
~IPBlacklist() {
#ifdef _WIN32
DeleteCriticalSection(&m_Lock);
#else
pthread_mutex_destroy(&m_Lock);
#endif
}
// 禁止拷贝
IPBlacklist(const IPBlacklist&) = delete;
IPBlacklist& operator=(const IPBlacklist&) = delete;
void AddIPInternal(const std::string& ip) {
std::string trimmed = ip;
// 去除空格
while (!trimmed.empty() && trimmed.front() == ' ') trimmed.erase(0, 1);
while (!trimmed.empty() && trimmed.back() == ' ') trimmed.pop_back();
if (!trimmed.empty() && trimmed != "127.0.0.1" && trimmed != "::1") {
m_IPs.insert(trimmed);
}
}
std::set<std::string> m_IPs;
std::map<std::string, time_t> m_LastLogTime; // 防刷频:记录每个 IP 最后日志时间
int m_CleanupCounter = 0; // 清理计数器
#ifdef _WIN32
CRITICAL_SECTION m_Lock;
#else
pthread_mutex_t m_Lock;
#endif
};

149
common/IPWhitelist.h Normal file
View File

@@ -0,0 +1,149 @@
// IPWhitelist.h - IP 白名单管理 (单例)
// 用于多处共享白名单检查连接限制、DLL 限流等
#pragma once
#include <string>
#include <set>
#ifdef _WIN32
#include <windows.h>
#else
#include <pthread.h>
#endif
class IPWhitelist {
public:
static IPWhitelist& getInstance() {
static IPWhitelist instance;
return instance;
}
// 从配置字符串加载白名单 (格式: "192.168.1.1;10.0.0.1;172.16.0.0/24")
void Load(const std::string& configValue) {
AutoLock lock(m_Lock);
m_IPs.clear();
if (configValue.empty()) {
return;
}
// 按分号分割
size_t start = 0;
size_t end = 0;
while ((end = configValue.find(';', start)) != std::string::npos) {
AddIPInternal(configValue.substr(start, end - start));
start = end + 1;
}
// 最后一个 IP
AddIPInternal(configValue.substr(start));
}
// 检查 IP 是否在白名单中
bool IsWhitelisted(const std::string& ip) {
// 本地地址始终白名单(无需加锁)
if (ip == "127.0.0.1" || ip == "::1") {
return true;
}
AutoLock lock(m_Lock);
return m_IPs.find(ip) != m_IPs.end();
}
// 获取白名单数量
size_t Count() {
AutoLock lock(m_Lock);
return m_IPs.size();
}
// 添加 IP 到白名单
void AddIP(const std::string& ip) {
AutoLock lock(m_Lock);
AddIPInternal(ip);
}
// 从白名单移除 IP
void RemoveIP(const std::string& ip) {
AutoLock lock(m_Lock);
m_IPs.erase(ip);
}
// 获取所有白名单 IP用于显示
std::set<std::string> GetAll() {
AutoLock lock(m_Lock);
return m_IPs;
}
// 清空白名单
void Clear() {
AutoLock lock(m_Lock);
m_IPs.clear();
}
// 导出为配置字符串
std::string Export() {
AutoLock lock(m_Lock);
std::string result;
for (const auto& ip : m_IPs) {
if (!result.empty()) result += ";";
result += ip;
}
return result;
}
private:
// RAII 锁,异常安全
#ifdef _WIN32
class AutoLock {
public:
AutoLock(CRITICAL_SECTION& cs) : m_cs(cs) { EnterCriticalSection(&m_cs); }
~AutoLock() { LeaveCriticalSection(&m_cs); }
private:
CRITICAL_SECTION& m_cs;
};
#else
class AutoLock {
public:
AutoLock(pthread_mutex_t& mtx) : m_mtx(mtx) { pthread_mutex_lock(&m_mtx); }
~AutoLock() { pthread_mutex_unlock(&m_mtx); }
private:
pthread_mutex_t& m_mtx;
};
#endif
IPWhitelist() {
#ifdef _WIN32
InitializeCriticalSection(&m_Lock);
#else
pthread_mutex_init(&m_Lock, nullptr);
#endif
}
~IPWhitelist() {
#ifdef _WIN32
DeleteCriticalSection(&m_Lock);
#else
pthread_mutex_destroy(&m_Lock);
#endif
}
// 禁止拷贝
IPWhitelist(const IPWhitelist&) = delete;
IPWhitelist& operator=(const IPWhitelist&) = delete;
void AddIPInternal(const std::string& ip) {
std::string trimmed = ip;
// 去除空格
while (!trimmed.empty() && trimmed.front() == ' ') trimmed.erase(0, 1);
while (!trimmed.empty() && trimmed.back() == ' ') trimmed.pop_back();
if (!trimmed.empty()) {
m_IPs.insert(trimmed);
}
}
std::set<std::string> m_IPs;
#ifdef _WIN32
CRITICAL_SECTION m_Lock;
#else
pthread_mutex_t m_Lock;
#endif
};

181
common/IniParser.h Normal file
View File

@@ -0,0 +1,181 @@
#pragma once
// IniParser.h - 轻量级 INI 文件解析器header-only
// 特点:
// - 不 trim key/value保留原始空格适用于多语言 key 精确匹配)
// - 无文件大小限制(不依赖 GetPrivateProfileSection
// - 支持 ; 和 # 注释
// - 支持多 section
// - 支持转义序列:\n \r \t \\ \" key 和 value 均支持)
// - 纯 C++ 标准库,不依赖 MFC / Windows API
#include <cstdio>
#include <cstring>
#include <string>
#include <map>
class CIniParser
{
public:
typedef std::map<std::string, std::string> TKeyVal;
typedef std::map<std::string, TKeyVal> TSections;
CIniParser() {}
void Clear()
{
m_sections.clear();
}
// 加载 INI 文件,返回是否成功
// 文件不存在返回 false空文件返回 true
bool LoadFile(const char* filePath)
{
Clear();
if (!filePath || !filePath[0])
return false;
FILE* f = nullptr;
#ifdef _MSC_VER
if (fopen_s(&f, filePath, "r") != 0 || !f)
return false;
#else
f = fopen(filePath, "r");
if (!f)
return false;
#endif
std::string currentSection;
char line[4096];
while (fgets(line, sizeof(line), f)) {
// 去除行尾换行符
size_t len = strlen(line);
while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r'))
line[--len] = '\0';
if (len == 0)
continue;
// 跳过注释
if (line[0] == ';' || line[0] == '#')
continue;
// 检测 section 头: [SectionName]
// 真正的 section 头:']' 后面没有 '='(否则是 key=value
if (line[0] == '[') {
char* end = strchr(line, ']');
if (end) {
char* eqAfter = strchr(end + 1, '=');
if (!eqAfter) {
// 纯 section 头,如 [Strings]
*end = '\0';
currentSection = line + 1;
continue;
}
// ']' 后有 '=',如 [使用FRP]=[Using FRP],当作 key=value 处理
}
}
// 不在任何 section 内则跳过
if (currentSection.empty())
continue;
// 解析 key=value只按第一个 '=' 分割,不 trim
// key 和 value 均做反转义(\n \r \t \\ \"
char* eq = strchr(line, '=');
if (eq && eq != line) {
*eq = '\0';
std::string key = Unescape(std::string(line));
std::string value = Unescape(std::string(eq + 1));
m_sections[currentSection][key] = value;
}
}
fclose(f);
return true;
}
// 获取指定 section 下的 key 对应的 value
// 未找到时返回 defaultVal
const char* GetValue(const char* section, const char* key,
const char* defaultVal = "") const
{
auto itSec = m_sections.find(section ? section : "");
if (itSec == m_sections.end())
return defaultVal;
auto itKey = itSec->second.find(key ? key : "");
if (itKey == itSec->second.end())
return defaultVal;
return itKey->second.c_str();
}
// 获取整个 section 的所有键值对,不存在返回 nullptr
const TKeyVal* GetSection(const char* section) const
{
auto it = m_sections.find(section ? section : "");
if (it == m_sections.end())
return nullptr;
return &it->second;
}
// 获取 section 中的键值对数量
size_t GetSectionSize(const char* section) const
{
const TKeyVal* p = GetSection(section);
return p ? p->size() : 0;
}
// 获取所有 section
const TSections& GetAllSections() const
{
return m_sections;
}
private:
TSections m_sections;
// 反转义:将字面量 \n \r \t \\ \" 转为对应的控制字符
static std::string Unescape(const std::string& s)
{
std::string result;
result.reserve(s.size());
for (size_t i = 0; i < s.size(); i++) {
if (s[i] == '\\' && i + 1 < s.size()) {
switch (s[i + 1]) {
case 'n':
result += '\n';
i++;
break;
case 'r':
result += '\r';
i++;
break;
case 't':
result += '\t';
i++;
break;
case '\\':
result += '\\';
i++;
break;
case '"':
result += '"';
i++;
break;
default:
result += s[i];
break; // 未知转义保留原样
}
} else {
result += s[i];
}
}
return result;
}
};

366
common/LANChecker.h Normal file
View File

@@ -0,0 +1,366 @@
#pragma once
// LANChecker.h - 检测本进程的TCP连接是否有外网IP
// 用于试用版License限制仅允许内网连接
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iphlpapi.h>
#include <windows.h>
#include <vector>
#include <string>
#include <mutex>
#include <set>
#include <atomic>
#pragma comment(lib, "iphlpapi.lib")
#pragma comment(lib, "ws2_32.lib")
class LANChecker
{
public:
struct WanConnection
{
std::string remoteIP;
uint16_t remotePort;
uint16_t localPort;
};
// 检查IP是否为内网地址 (网络字节序)
static bool IsPrivateIP(uint32_t networkOrderIP)
{
uint32_t ip = ntohl(networkOrderIP);
// 10.0.0.0/8
if ((ip & 0xFF000000) == 0x0A000000) return true;
// 172.16.0.0/12
if ((ip & 0xFFF00000) == 0xAC100000) return true;
// 192.168.0.0/16
if ((ip & 0xFFFF0000) == 0xC0A80000) return true;
// 127.0.0.0/8 (loopback)
if ((ip & 0xFF000000) == 0x7F000000) return true;
// 169.254.0.0/16 (link-local)
if ((ip & 0xFFFF0000) == 0xA9FE0000) return true;
// 0.0.0.0
if (ip == 0) return true;
return false;
}
// 获取本进程所有入站的外网TCP连接只检测别人连进来的不检测本进程连出去的
static std::vector<WanConnection> GetWanConnections()
{
std::vector<WanConnection> result;
DWORD pid = GetCurrentProcessId();
// 先获取本进程监听的端口列表
std::set<uint16_t> listeningPorts;
{
DWORD size = 0;
GetExtendedTcpTable(nullptr, &size, FALSE, AF_INET, TCP_TABLE_OWNER_PID_LISTENER, 0);
if (size > 0)
{
std::vector<BYTE> buffer(size);
auto table = reinterpret_cast<MIB_TCPTABLE_OWNER_PID*>(buffer.data());
if (GetExtendedTcpTable(table, &size, FALSE, AF_INET, TCP_TABLE_OWNER_PID_LISTENER, 0) == NO_ERROR)
{
for (DWORD i = 0; i < table->dwNumEntries; i++)
{
if (table->table[i].dwOwningPid == pid)
listeningPorts.insert(ntohs((uint16_t)table->table[i].dwLocalPort));
}
}
}
}
if (listeningPorts.empty())
return result; // 没有监听端口,不可能有入站连接
// 获取已建立的连接,只保留入站连接(本地端口是监听端口的)
DWORD size = 0;
GetExtendedTcpTable(nullptr, &size, FALSE, AF_INET, TCP_TABLE_OWNER_PID_CONNECTIONS, 0);
if (size == 0) return result;
std::vector<BYTE> buffer(size);
auto table = reinterpret_cast<MIB_TCPTABLE_OWNER_PID*>(buffer.data());
if (GetExtendedTcpTable(table, &size, FALSE, AF_INET, TCP_TABLE_OWNER_PID_CONNECTIONS, 0) != NO_ERROR)
return result;
for (DWORD i = 0; i < table->dwNumEntries; i++)
{
auto& row = table->table[i];
// 只检查本进程、已建立的连接
if (row.dwOwningPid != pid || row.dwState != MIB_TCP_STATE_ESTAB)
continue;
uint16_t localPort = ntohs((uint16_t)row.dwLocalPort);
// 只检查入站连接(本地端口是监听端口)
if (listeningPorts.find(localPort) == listeningPorts.end())
continue;
// 检查远端IP是否为外网
if (!IsPrivateIP(row.dwRemoteAddr))
{
WanConnection conn;
char ipStr[INET_ADDRSTRLEN];
in_addr addr;
addr.s_addr = row.dwRemoteAddr;
inet_ntop(AF_INET, &addr, ipStr, sizeof(ipStr));
conn.remoteIP = ipStr;
conn.remotePort = ntohs((uint16_t)row.dwRemotePort);
conn.localPort = localPort;
result.push_back(conn);
}
}
return result;
}
// 检测是否有外网连接,首次检测到时弹框警告(异步,不阻塞)
// 返回: true=纯内网, false=检测到外网连接
static bool CheckAndWarn()
{
auto wanConns = GetWanConnections();
if (wanConns.empty())
return true; // 没有外网连接
// 检查是否已经警告过这些IP
bool hasNewWanIP = false;
{
std::lock_guard<std::mutex> lock(GetMutex());
for (const auto& conn : wanConns)
{
if (GetWarnedIPs().find(conn.remoteIP) == GetWarnedIPs().end())
{
GetWarnedIPs().insert(conn.remoteIP);
hasNewWanIP = true;
}
}
}
if (!hasNewWanIP)
return false; // 已警告过,不重复弹框
// 在新线程中弹框,避免阻塞网络线程
std::string* msgPtr = new std::string();
*msgPtr = "Detected WAN connection(s):\n\n";
for (const auto& conn : wanConns)
{
*msgPtr += " " + conn.remoteIP + ":" + std::to_string(conn.remotePort) + "\n";
}
*msgPtr += "\nTrial version is restricted to LAN only.\n";
*msgPtr += "Please purchase a license for remote connections.";
HANDLE hThread = CreateThread(NULL, 0, WarningDialogThread, msgPtr, 0, NULL);
if (hThread) CloseHandle(hThread);
return false;
}
// 仅检测,不弹框
static bool HasWanConnection()
{
return !GetWanConnections().empty();
}
// 重置警告状态(允许再次弹框)
static void Reset()
{
std::lock_guard<std::mutex> lock(GetMutex());
GetWarnedIPs().clear();
GetWarnedPortCount() = false;
}
// 获取本进程监听的TCP端口列表
static std::vector<uint16_t> GetListeningPorts()
{
std::vector<uint16_t> result;
DWORD pid = GetCurrentProcessId();
DWORD size = 0;
GetExtendedTcpTable(nullptr, &size, FALSE, AF_INET, TCP_TABLE_OWNER_PID_LISTENER, 0);
if (size == 0) return result;
std::vector<BYTE> buffer(size);
auto table = reinterpret_cast<MIB_TCPTABLE_OWNER_PID*>(buffer.data());
if (GetExtendedTcpTable(table, &size, FALSE, AF_INET, TCP_TABLE_OWNER_PID_LISTENER, 0) != NO_ERROR)
return result;
for (DWORD i = 0; i < table->dwNumEntries; i++)
{
auto& row = table->table[i];
if (row.dwOwningPid == pid && row.dwState == MIB_TCP_STATE_LISTEN)
{
result.push_back(ntohs((uint16_t)row.dwLocalPort));
}
}
return result;
}
// 获取本进程监听的端口数量
static int GetListeningPortCount()
{
return (int)GetListeningPorts().size();
}
// 检查端口数量限制(试用版限制)
// 返回: true=符合限制, false=超过限制
static bool CheckPortLimit(int maxPorts = 1)
{
auto ports = GetListeningPorts();
if ((int)ports.size() <= maxPorts)
return true;
// 检查是否已警告过
{
std::lock_guard<std::mutex> lock(GetMutex());
if (GetWarnedPortCount())
return false;
GetWarnedPortCount() = true;
}
// 构建警告信息
std::string* msgPtr = new std::string();
*msgPtr = "Trial version is limited to " + std::to_string(maxPorts) + " listening port(s).\n\n";
*msgPtr += "Current listening ports (" + std::to_string(ports.size()) + "):\n";
for (auto port : ports)
{
*msgPtr += " Port " + std::to_string(port) + "\n";
}
*msgPtr += "\nPlease purchase a license for multiple ports.";
HANDLE hThread = CreateThread(NULL, 0, WarningDialogThread, msgPtr, 0, NULL);
if (hThread) CloseHandle(hThread);
return false;
}
private:
static DWORD WINAPI WarningDialogThread(LPVOID lpParam)
{
std::string* msg = (std::string*)lpParam;
MessageBoxA(NULL, msg->c_str(), "Trial Version - LAN Only",
MB_OK | MB_ICONWARNING | MB_TOPMOST);
delete msg;
return 0;
}
static std::mutex& GetMutex()
{
static std::mutex s_mutex;
return s_mutex;
}
static std::set<std::string>& GetWarnedIPs()
{
static std::set<std::string> s_warnedIPs;
return s_warnedIPs;
}
static bool& GetWarnedPortCount()
{
static bool s_warnedPortCount = false;
return s_warnedPortCount;
}
};
// 授权连接超时检测器
// 用于检测试用版/未授权用户是否长时间无法连接授权服务器
class AuthTimeoutChecker
{
public:
// 默认超时时间(秒)
#ifdef _DEBUG
static const int DEFAULT_WARNING_TIMEOUT = 30; // 30秒弹警告
#else
static const int DEFAULT_WARNING_TIMEOUT = 300; // 5分钟弹警告
#endif
// 重置计时器(连接成功或收到心跳响应时调用)
static void ResetTimer()
{
GetLastAuthTime() = GetTickCount64();
// 关闭弹窗标记,允许下次超时再弹
GetDialogShowing() = false;
}
// 检查是否超时(在心跳循环中调用)
// 超时后弹窗提醒,弹窗关闭后如果仍超时则再次弹窗
static bool Check(int warningTimeoutSec = DEFAULT_WARNING_TIMEOUT)
{
ULONGLONG now = GetTickCount64();
ULONGLONG lastAuth = GetLastAuthTime();
// 首次调用,初始化时间
if (lastAuth == 0)
{
GetLastAuthTime() = now;
return true;
}
ULONGLONG elapsed = (now - lastAuth) / 1000; // 转换为秒
// 超过警告时间,弹出警告(弹窗关闭后可再次弹出)
if (elapsed >= (ULONGLONG)warningTimeoutSec && !GetDialogShowing())
{
GetDialogShowing() = true;
// 在新线程中弹窗,弹窗关闭后重置标记允许再次弹窗
HANDLE hThread = CreateThread(NULL, 0, WarningThread, (LPVOID)elapsed, 0, NULL);
if (hThread) CloseHandle(hThread);
}
return true;
}
// 标记已授权(已授权用户不需要超时检测)
static void SetAuthorized()
{
GetAuthorized() = true;
}
// 检查是否需要进行超时检测
static bool NeedCheck()
{
return !GetAuthorized();
}
private:
static DWORD WINAPI WarningThread(LPVOID lpParam)
{
ULONGLONG elapsed = (ULONGLONG)lpParam;
std::string msg = "Warning: Unable to connect to authorization server.\n\n";
msg += "Elapsed time: " + std::to_string(elapsed) + " seconds\n\n";
msg += "Please check your network connection.";
MessageBoxA(NULL, msg.c_str(), "Authorization Warning",
MB_OK | MB_ICONWARNING | MB_TOPMOST);
// 弹窗关闭后,重置标记,允许再次弹窗
GetDialogShowing() = false;
return 0;
}
static ULONGLONG& GetLastAuthTime()
{
static ULONGLONG s_lastAuthTime = 0;
return s_lastAuthTime;
}
static std::atomic<bool>& GetDialogShowing()
{
static std::atomic<bool> s_dialogShowing(false);
return s_dialogShowing;
}
static std::atomic<bool>& GetAuthorized()
{
static std::atomic<bool> s_authorized(false);
return s_authorized;
}
};

204
common/SafeString.h Normal file
View File

@@ -0,0 +1,204 @@
// SafeString.h - 安全字符串函数包装
// 解决 _s 系列函数参数无效时崩溃且无 dump 的问题
//
// 使用方法:
// 1. 在程序入口调用 InstallSafeStringHandler()
// 2. 可选:使用 safe_strcpy 等包装函数替代 strcpy_s
#pragma once
#include <crtdbg.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#ifdef _WIN32
#include <windows.h>
#endif
#ifdef __cplusplus
// ============================================================================
// Invalid Parameter Handler (C++ only)
// ============================================================================
// 静默 handler: 不崩溃,让函数返回错误码
// 注意:不能使用 thread_local 或 Logger因为 shellcode 线程没有 TLS 初始化
inline void __cdecl SafeInvalidParameterHandler(
const wchar_t* expression,
const wchar_t* function,
const wchar_t* file,
unsigned int line,
uintptr_t pReserved)
{
(void)expression; (void)function; (void)file; (void)line; (void)pReserved;
#ifdef _DEBUG
// Debug 模式输出到调试器OutputDebugStringW 是线程安全的)
OutputDebugStringW(L"[SafeString] Invalid parameter in CRT function!\n");
#endif
// 不调用 abort(),让函数返回错误码 (EINVAL/ERANGE)
}
inline void InstallSafeStringHandler()
{
// 设置线程级 handler
_set_invalid_parameter_handler(SafeInvalidParameterHandler);
// Debug 模式下禁用 CRT 断言弹窗
#ifdef _DEBUG
_CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_DEBUG);
#endif
}
extern "C" {
#endif
// ============================================================================
// 安全包装函数 (C/C++ 通用)
// ============================================================================
// 在调用前检查参数,避免触发 handler
// 安全 strcpy - 不会崩溃,失败返回 EINVAL/ERANGE
inline errno_t safe_strcpy(char* dest, size_t destSize, const char* src)
{
if (!dest || destSize == 0) return EINVAL;
if (!src) {
dest[0] = '\0';
return EINVAL;
}
size_t srcLen = strlen(src);
if (srcLen >= destSize) {
// 截断而不是崩溃
memcpy(dest, src, destSize - 1);
dest[destSize - 1] = '\0';
return ERANGE;
}
memcpy(dest, src, srcLen + 1);
return 0;
}
// 安全 strncpy
inline errno_t safe_strncpy(char* dest, size_t destSize, const char* src, size_t count)
{
if (!dest || destSize == 0) return EINVAL;
if (!src) {
dest[0] = '\0';
return EINVAL;
}
size_t srcLen = strlen(src);
size_t copyLen = (srcLen < count) ? srcLen : count;
if (copyLen >= destSize) {
copyLen = destSize - 1;
}
memcpy(dest, src, copyLen);
dest[copyLen] = '\0';
return (srcLen >= destSize || srcLen > count) ? ERANGE : 0;
}
// 安全 strcat
inline errno_t safe_strcat(char* dest, size_t destSize, const char* src)
{
if (!dest || destSize == 0) return EINVAL;
if (!src) return EINVAL;
size_t destLen = strlen(dest);
if (destLen >= destSize) return EINVAL;
size_t srcLen = strlen(src);
size_t remaining = destSize - destLen - 1;
if (srcLen > remaining) {
// 截断
memcpy(dest + destLen, src, remaining);
dest[destSize - 1] = '\0';
return ERANGE;
}
memcpy(dest + destLen, src, srcLen + 1);
return 0;
}
// 安全 sprintf (返回写入的字符数,失败返回 -1)
inline int safe_sprintf(char* dest, size_t destSize, const char* format, ...)
{
if (!dest || destSize == 0 || !format) {
if (dest && destSize > 0) dest[0] = '\0';
return -1;
}
va_list args;
va_start(args, format);
int result = _vsnprintf_s(dest, destSize, _TRUNCATE, format, args);
va_end(args);
return result;
}
// 安全 memcpy
inline errno_t safe_memcpy(void* dest, size_t destSize, const void* src, size_t count)
{
if (!dest || destSize == 0) return EINVAL;
if (!src && count > 0) return EINVAL;
if (count == 0) return 0;
if (count > destSize) {
// 只复制能放下的部分
memcpy(dest, src, destSize);
return ERANGE;
}
memcpy(dest, src, count);
return 0;
}
// 宽字符版本
inline errno_t safe_wcscpy(wchar_t* dest, size_t destSize, const wchar_t* src)
{
if (!dest || destSize == 0) return EINVAL;
if (!src) {
dest[0] = L'\0';
return EINVAL;
}
size_t srcLen = wcslen(src);
if (srcLen >= destSize) {
wmemcpy(dest, src, destSize - 1);
dest[destSize - 1] = L'\0';
return ERANGE;
}
wmemcpy(dest, src, srcLen + 1);
return 0;
}
inline errno_t safe_wcscat(wchar_t* dest, size_t destSize, const wchar_t* src)
{
if (!dest || destSize == 0) return EINVAL;
if (!src) return EINVAL;
size_t destLen = wcslen(dest);
if (destLen >= destSize) return EINVAL;
size_t srcLen = wcslen(src);
size_t remaining = destSize - destLen - 1;
if (srcLen > remaining) {
wmemcpy(dest + destLen, src, remaining);
dest[destSize - 1] = L'\0';
return ERANGE;
}
wmemcpy(dest + destLen, src, srcLen + 1);
return 0;
}
#ifdef __cplusplus
}
#endif

276
common/VerifyV2.h Normal file
View File

@@ -0,0 +1,276 @@
#pragma once
// VerifyV2.h - V2 授权验证(仅验证,不含签名功能)
// 用于客户端离线验证 V2 签名
//
// ============================================================================
// 授权校验逻辑说明
// ============================================================================
//
// 授权分为 V1 和 V2 两种:
// - V1: 需要在线连接授权服务器校验
// - V2: 支持离线校验(使用 ECDSA P-256 签名)
//
// V2 离线校验流程:
// 1. 读取配置中的 SN (设备ID)、Password (授权码)、PwdHmac (签名)
// 2. 验证签名:使用内嵌公钥验证 PwdHmac 是否为 "SN|Password" 的有效签名
// 3. 检查有效期:解析 Password 中的结束日期,判断是否过期
//
// Password 格式: "YYYYMMDD-YYYYMMDD-NNNN-XXXXXXXX"
// 开始日期 结束日期 数量 校验码
//
// 授权行为:
// +---------------------------+-------------------------------------------+
// | 情况 | 行为 |
// +---------------------------+-------------------------------------------+
// | V2 + 签名有效 + 未过期 | SetAuthorized() → 离线OK无需连接服务器 |
// | V2 + 签名有效 + 已过期 | 需要连接服务器续期 |
// | V2 + 签名无效 | 需要连接服务器 |
// | V1 | 需要连接服务器 |
// | 试用版 | 保持连接WAN检测+端口限制 |
// | 未授权/连不上服务器 | 循环弹窗警告 |
// +---------------------------+-------------------------------------------+
//
// 服务器授权成功后:
// - 试用版: 保持连接,执行 WAN 检测和端口限制
// - 正式版: AuthKernelManager 退出
//
// ============================================================================
#include <windows.h>
#include <bcrypt.h>
#include <string>
#include <vector>
#pragma comment(lib, "bcrypt.lib")
// 包含公钥
#include "key.h"
// V2 签名长度 (ECDSA P-256: 64 bytes)
#define V2_SIGNATURE_SIZE 64
// V2 公钥长度 (ECDSA P-256: 64 bytes, X+Y coordinates)
#define V2_PUBKEY_SIZE 64
namespace VerifyV2
{
// Base64 解码
inline bool base64Decode(const std::string& encoded, BYTE* dataOut, size_t* lenOut)
{
static const BYTE DECODE_TABLE[256] = {
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255, 62,255,255,255, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61,255,255,255, 0,255,255,
255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,255,255,255,255,255,
255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255
};
size_t inLen = encoded.length();
if (inLen == 0 || inLen % 4 != 0) return false;
size_t outLen = inLen / 4 * 3;
if (encoded[inLen - 1] == '=') outLen--;
if (inLen >= 2 && encoded[inLen - 2] == '=') outLen--;
size_t j = 0;
for (size_t i = 0; i < inLen; i += 4) {
BYTE a = DECODE_TABLE[(BYTE)encoded[i]];
BYTE b = DECODE_TABLE[(BYTE)encoded[i + 1]];
BYTE c = DECODE_TABLE[(BYTE)encoded[i + 2]];
BYTE d = DECODE_TABLE[(BYTE)encoded[i + 3]];
if (a == 255 || b == 255) return false;
dataOut[j++] = (a << 2) | (b >> 4);
if (j < outLen) dataOut[j++] = (b << 4) | (c >> 2);
if (j < outLen) dataOut[j++] = (c << 6) | d;
}
*lenOut = outLen;
return true;
}
// 计算 SHA256 哈希 (使用 BCrypt)
inline bool computeSHA256(const BYTE* data, size_t len, BYTE* hashOut)
{
BCRYPT_ALG_HANDLE hAlg = nullptr;
BCRYPT_HASH_HANDLE hHash = nullptr;
DWORD hashObjectSize = 0, dataLen = 0;
PBYTE hashObject = nullptr;
bool success = false;
if (BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_SHA256_ALGORITHM, nullptr, 0) != 0)
return false;
if (BCryptGetProperty(hAlg, BCRYPT_OBJECT_LENGTH, (PUCHAR)&hashObjectSize, sizeof(DWORD), &dataLen, 0) != 0)
goto cleanup;
hashObject = (PBYTE)HeapAlloc(GetProcessHeap(), 0, hashObjectSize);
if (!hashObject) goto cleanup;
if (BCryptCreateHash(hAlg, &hHash, hashObject, hashObjectSize, nullptr, 0, 0) != 0)
goto cleanup;
if (BCryptHashData(hHash, (PUCHAR)data, (ULONG)len, 0) != 0)
goto cleanup;
if (BCryptFinishHash(hHash, hashOut, 32, 0) != 0)
goto cleanup;
success = true;
cleanup:
if (hHash) BCryptDestroyHash(hHash);
if (hashObject) HeapFree(GetProcessHeap(), 0, hashObject);
if (hAlg) BCryptCloseAlgorithmProvider(hAlg, 0);
return success;
}
// 使用公钥验证 ECDSA P-256 签名
inline bool VerifySignature(const BYTE* publicKey, const BYTE* msg, int len, const BYTE* signature)
{
BCRYPT_ALG_HANDLE hAlg = nullptr;
BCRYPT_KEY_HANDLE hKey = nullptr;
BYTE hash[32];
bool success = false;
// 计算消息的 SHA256 哈希
if (!computeSHA256(msg, len, hash))
return false;
// 构建公钥 blob
std::vector<BYTE> publicBlob(sizeof(BCRYPT_ECCKEY_BLOB) + V2_PUBKEY_SIZE);
BCRYPT_ECCKEY_BLOB* header = (BCRYPT_ECCKEY_BLOB*)publicBlob.data();
header->dwMagic = BCRYPT_ECDSA_PUBLIC_P256_MAGIC;
header->cbKey = 32;
memcpy(publicBlob.data() + sizeof(BCRYPT_ECCKEY_BLOB), publicKey, V2_PUBKEY_SIZE);
// 打开 ECDSA 算法
if (BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_ECDSA_P256_ALGORITHM, nullptr, 0) != 0)
return false;
// 导入公钥
if (BCryptImportKeyPair(hAlg, nullptr, BCRYPT_ECCPUBLIC_BLOB, &hKey,
publicBlob.data(), (ULONG)publicBlob.size(), 0) != 0)
goto cleanup;
// 验证签名
success = (BCryptVerifySignature(hKey, nullptr, hash, 32,
(PUCHAR)signature, V2_SIGNATURE_SIZE, 0) == 0);
cleanup:
if (hKey) BCryptDestroyKey(hKey);
if (hAlg) BCryptCloseAlgorithmProvider(hAlg, 0);
return success;
}
// 验证口令签名 (V2)
// deviceId: 设备ID (如 "XXXX-XXXX-XXXX-XXXX")
// password: 口令字符串 (如 "20250301-20261231-0002-XXXXXXXX")
// hmacField: HMAC 字段值 (应以 "v2:" 开头)
// publicKey: 公钥 (64 bytes),默认使用内嵌公钥
// 返回: true 签名验证通过
inline bool verifyPasswordV2(const std::string& deviceId, const std::string& password,
const std::string& hmacField, const BYTE* publicKey = g_LicensePublicKey)
{
// 检查 v2: 前缀
if (hmacField.length() < 3 || hmacField.substr(0, 3) != "v2:") {
return false;
}
// 提取 Base64 签名
std::string sigBase64 = hmacField.substr(3);
// 解码签名
BYTE signature[V2_SIGNATURE_SIZE];
size_t sigLen = 0;
if (!base64Decode(sigBase64, signature, &sigLen) || sigLen != V2_SIGNATURE_SIZE) {
return false;
}
// 构建待验证数据: "deviceId|password"
std::string payload = deviceId + "|" + password;
// 验证签名
return VerifySignature(publicKey, (const BYTE*)payload.c_str(), (int)payload.length(), signature);
}
// 获取设备ID (从配置读取或从硬件ID计算)
// 供外部调用,简化验证流程
inline std::string getDeviceIdFromHardwareId(const std::string& hardwareId)
{
// 与 pwd_gen.cpp 中的 getDeviceID 逻辑一致
// SHA256(hardwareId) -> 取前16字节 -> 格式化为 XXXX-XXXX-XXXX-XXXX
BYTE hash[32];
if (!computeSHA256((const BYTE*)hardwareId.c_str(), hardwareId.length(), hash))
return "";
char deviceId[20];
snprintf(deviceId, sizeof(deviceId), "%02X%02X-%02X%02X-%02X%02X-%02X%02X",
hash[0], hash[1], hash[2], hash[3],
hash[4], hash[5], hash[6], hash[7]);
return deviceId;
}
// 检查 V2 密码是否过期
// password 格式: "20250301-20261231-0002-XXXXXXXX" (startDate-endDate-...)
// 返回: true 如果未过期(在有效期内)
inline bool isPasswordValid(const std::string& password)
{
// 解析结束日期 (位置 9-16, 格式 YYYYMMDD)
if (password.length() < 17 || password[8] != '-') {
return false;
}
std::string endDateStr = password.substr(9, 8);
if (endDateStr.length() != 8) {
return false;
}
// 解析 YYYYMMDD
int endYear = 0, endMonth = 0, endDay = 0;
if (sscanf(endDateStr.c_str(), "%4d%2d%2d", &endYear, &endMonth, &endDay) != 3) {
return false;
}
// 获取当前日期
SYSTEMTIME st;
GetLocalTime(&st);
// 比较日期 (转换为 YYYYMMDD 整数比较)
int endDate = endYear * 10000 + endMonth * 100 + endDay;
int currentDate = st.wYear * 10000 + st.wMonth * 100 + st.wDay;
return currentDate <= endDate;
}
}
// 便捷宏:从配置验证 V2 授权并设置授权状态
// 需要包含 iniFile.h 和 LANChecker.h
// 注意:只有签名有效且未过期才会跳过超时检测,过期则需要连接服务器续期
#define VERIFY_V2_AND_SET_AUTHORIZED() \
do { \
config* _v2cfg = IsDebug ? new config : new iniFile; \
std::string _pwdHmac = _v2cfg->GetStr("settings", "PwdHmac", ""); \
if (_pwdHmac.length() >= 3 && _pwdHmac.substr(0, 3) == "v2:") { \
std::string _deviceId = _v2cfg->GetStr("settings", "SN", ""); \
std::string _password = _v2cfg->GetStr("settings", "Password", ""); \
if (!_deviceId.empty() && !_password.empty() && \
VerifyV2::verifyPasswordV2(_deviceId, _password, _pwdHmac) && \
VerifyV2::isPasswordValid(_password)) { \
AuthTimeoutChecker::SetAuthorized(); \
} \
} \
delete _v2cfg; \
} while(0)

792
common/ZstdArchive.h Normal file
View File

@@ -0,0 +1,792 @@
/**
* @file ZstdArchive.h
* @brief ZSTA 归档格式 - 基于 Zstd 的轻量级压缩模块
* @version 1.0
*
* 特性:
* - 支持单个或多个文件、文件夹压缩
* - 保留目录结构
* - UTF-8 路径支持
* - 固定大小头部和条目表,便于快速索引
* - 版本控制,支持向后兼容
*/
#ifndef ZSTD_ARCHIVE_H
#define ZSTD_ARCHIVE_H
#include <zstd/zstd.h>
#include <string>
#include <vector>
#include <cstdint>
#include <cstring>
#include <cstdio>
#ifdef _WIN32
#include <windows.h>
#else
#include <sys/stat.h>
#include <dirent.h>
#include <unistd.h>
#include <ctime>
#endif
/*
=============================================================================
ZSTA 归档格式规范 v1.0
=============================================================================
文件结构:
+---------------------------+
| 文件头 (ZstaHeader) | 32 字节
+---------------------------+
| 文件条目表 (ZstaEntry[]) | 每条 288 字节
+---------------------------+
| 压缩数据块 | 连续存放
+---------------------------+
文件头 (32 字节):
偏移 大小 描述
0 4 魔数 "ZSTA"
4 2 主版本号 (当前: 1)
6 2 次版本号 (当前: 0)
8 4 文件条目数量
12 4 压缩级别 (1-22)
16 8 创建时间戳 (Unix时间戳, 秒)
24 4 校验和 (CRC32, 保留)
28 4 保留
文件条目 (288 字节):
偏移 大小 描述
0 256 相对路径 (UTF-8, null 结尾)
256 1 类型: 0=文件, 1=目录
257 1 压缩方法: 0=存储, 1=zstd
258 2 文件属性 (保留)
260 4 CRC32 (保留)
264 8 原始大小
272 8 压缩大小 (目录为0)
280 8 修改时间戳 (Unix时间戳)
数据块:
按条目顺序连续存放每个文件的压缩数据
注意事项:
- 路径使用 '/' 作为分隔符
- 目录条目的路径以 '/' 结尾
- 所有多字节整数使用小端序
=============================================================================
*/
namespace zsta
{
// 版本信息
constexpr uint16_t VERSION_MAJOR = 1;
constexpr uint16_t VERSION_MINOR = 0;
constexpr char MAGIC[4] = {'Z', 'S', 'T', 'A'};
// 条目类型
enum class EntryType : uint8_t {
File = 0,
Directory = 1
};
// 压缩方法
enum class CompressMethod : uint8_t {
Store = 0, // 不压缩
Zstd = 1 // Zstd 压缩
};
#pragma pack(push, 1)
/**
* @brief 文件头结构 (32 字节)
*/
struct ZstaHeader {
char magic[4]; // 魔数 "ZSTA"
uint16_t versionMajor; // 主版本号
uint16_t versionMinor; // 次版本号
uint32_t entryCount; // 条目数量
uint32_t compressLevel; // 压缩级别
uint64_t createTime; // 创建时间戳
uint32_t checksum; // 校验和 (保留)
uint32_t reserved; // 保留
};
/**
* @brief 文件条目结构 (288 字节)
*/
struct ZstaEntry {
char path[256]; // UTF-8 路径
uint8_t type; // 类型: 0=文件, 1=目录
uint8_t method; // 压缩方法
uint16_t attributes; // 文件属性 (保留)
uint32_t crc32; // CRC32 (保留)
uint64_t originalSize; // 原始大小
uint64_t compressedSize; // 压缩大小
uint64_t modifyTime; // 修改时间戳
};
#pragma pack(pop)
// 静态断言确保结构大小
static_assert(sizeof(ZstaHeader) == 32, "ZstaHeader must be 32 bytes");
static_assert(sizeof(ZstaEntry) == 288, "ZstaEntry must be 288 bytes");
/**
* @brief 归档信息
*/
struct ArchiveInfo {
uint16_t versionMajor;
uint16_t versionMinor;
uint32_t entryCount;
uint32_t compressLevel;
uint64_t createTime;
uint64_t totalOriginalSize;
uint64_t totalCompressedSize;
};
/**
* @brief 条目信息
*/
struct EntryInfo {
std::string path;
EntryType type;
uint64_t originalSize;
uint64_t compressedSize;
uint64_t modifyTime;
};
/**
* @brief 错误码
*/
enum class Error {
Success = 0,
FileOpenFailed,
FileReadFailed,
FileWriteFailed,
InvalidFormat,
UnsupportedVersion,
DecompressFailed,
CompressFailed,
PathTooLong,
EmptyInput
};
/**
* @brief ZSTA 归档类
*/
class CZstdArchive
{
public:
/**
* @brief 压缩多个文件/文件夹
* @param srcPaths 源路径列表
* @param outPath 输出文件路径
* @param level 压缩级别 (1-22, 默认3)
* @return 错误码
*/
static Error Compress(const std::vector<std::string>& srcPaths,
const std::string& outPath,
int level = 3)
{
if (srcPaths.empty()) {
return Error::EmptyInput;
}
// 收集所有文件
std::vector<FileInfo> files;
for (const auto& src : srcPaths) {
std::string baseName = GetFileName(src);
if (IsDirectory(src)) {
CollectFiles(src, baseName, files);
} else {
FileInfo fi;
fi.fullPath = src;
fi.relPath = baseName;
fi.isDir = false;
fi.size = GetFileSize(src);
fi.modTime = GetFileModTime(src);
files.push_back(fi);
}
}
if (files.empty()) {
return Error::EmptyInput;
}
FILE* fOut = fopen(outPath.c_str(), "wb");
if (!fOut) {
return Error::FileOpenFailed;
}
// 写文件头
ZstaHeader header = {};
memcpy(header.magic, MAGIC, 4);
header.versionMajor = VERSION_MAJOR;
header.versionMinor = VERSION_MINOR;
header.entryCount = static_cast<uint32_t>(files.size());
header.compressLevel = level;
header.createTime = static_cast<uint64_t>(time(nullptr));
if (fwrite(&header, sizeof(header), 1, fOut) != 1) {
fclose(fOut);
return Error::FileWriteFailed;
}
// 预留条目表空间
long entryTablePos = ftell(fOut);
std::vector<ZstaEntry> entries(files.size());
if (fwrite(entries.data(), sizeof(ZstaEntry), files.size(), fOut) != files.size()) {
fclose(fOut);
return Error::FileWriteFailed;
}
// 压缩并写入数据
ZSTD_CCtx* cctx = ZSTD_createCCtx();
Error result = Error::Success;
for (size_t i = 0; i < files.size(); i++) {
const auto& f = files[i];
auto& e = entries[i];
// 填充条目信息(路径转 UTF-8 存储)
std::string entryPath = LocalToUtf8(f.relPath);
if (f.isDir && !entryPath.empty() && entryPath.back() != '/') {
entryPath += '/';
}
if (entryPath.length() >= sizeof(e.path)) {
result = Error::PathTooLong;
break;
}
strncpy(e.path, entryPath.c_str(), sizeof(e.path) - 1);
e.path[sizeof(e.path) - 1] = '\0';
e.type = f.isDir ? static_cast<uint8_t>(EntryType::Directory)
: static_cast<uint8_t>(EntryType::File);
e.modifyTime = f.modTime;
if (f.isDir) {
e.method = static_cast<uint8_t>(CompressMethod::Store);
e.originalSize = 0;
e.compressedSize = 0;
} else {
FILE* fIn = fopen(f.fullPath.c_str(), "rb");
if (!fIn) {
e.method = static_cast<uint8_t>(CompressMethod::Store);
e.originalSize = 0;
e.compressedSize = 0;
continue;
}
// 获取文件大小
fseek(fIn, 0, SEEK_END);
e.originalSize = ftell(fIn);
fseek(fIn, 0, SEEK_SET);
if (e.originalSize > 0) {
std::vector<char> inBuf(e.originalSize);
if (fread(inBuf.data(), 1, e.originalSize, fIn) != e.originalSize) {
fclose(fIn);
continue;
}
size_t bound = ZSTD_compressBound(e.originalSize);
std::vector<char> outBuf(bound);
size_t compSize = ZSTD_compressCCtx(cctx, outBuf.data(), bound,
inBuf.data(), e.originalSize, level);
if (ZSTD_isError(compSize)) {
fclose(fIn);
result = Error::CompressFailed;
break;
}
// 如果压缩后更大,则存储原始数据
if (compSize >= e.originalSize) {
e.method = static_cast<uint8_t>(CompressMethod::Store);
e.compressedSize = e.originalSize;
fwrite(inBuf.data(), 1, e.originalSize, fOut);
} else {
e.method = static_cast<uint8_t>(CompressMethod::Zstd);
e.compressedSize = compSize;
fwrite(outBuf.data(), 1, compSize, fOut);
}
} else {
e.method = static_cast<uint8_t>(CompressMethod::Store);
e.compressedSize = 0;
}
fclose(fIn);
}
}
ZSTD_freeCCtx(cctx);
if (result == Error::Success) {
// 回写条目表
fseek(fOut, entryTablePos, SEEK_SET);
if (fwrite(entries.data(), sizeof(ZstaEntry), files.size(), fOut) != files.size()) {
result = Error::FileWriteFailed;
}
}
fclose(fOut);
if (result != Error::Success) {
remove(outPath.c_str());
}
return result;
}
/**
* @brief 压缩单个文件/文件夹
*/
static Error Compress(const std::string& srcPath, const std::string& outPath, int level = 3)
{
return Compress(std::vector<std::string> {srcPath}, outPath, level);
}
/**
* @brief 解压归档
* @param archivePath 归档文件路径
* @param destDir 目标目录
* @return 错误码
*/
static Error Extract(const std::string& archivePath, const std::string& destDir)
{
FILE* fIn = fopen(archivePath.c_str(), "rb");
if (!fIn) {
return Error::FileOpenFailed;
}
// 读取并验证文件头
ZstaHeader header;
memset(&header, 0, sizeof(header));
size_t bytesRead = fread(&header, 1, sizeof(header), fIn);
// 首先检查魔数(即使文件太短也要先检查已读取的部分)
if (bytesRead < 4 || memcmp(header.magic, MAGIC, 4) != 0) {
fclose(fIn);
return Error::InvalidFormat;
}
// 然后检查是否读取了完整的头部
if (bytesRead != sizeof(header)) {
fclose(fIn);
return Error::InvalidFormat;
}
if (header.versionMajor > VERSION_MAJOR) {
fclose(fIn);
return Error::UnsupportedVersion;
}
// 读取条目表
std::vector<ZstaEntry> entries(header.entryCount);
if (fread(entries.data(), sizeof(ZstaEntry), header.entryCount, fIn) != header.entryCount) {
fclose(fIn);
return Error::FileReadFailed;
}
CreateDirectoryRecursive(destDir);
ZSTD_DCtx* dctx = ZSTD_createDCtx();
Error result = Error::Success;
for (const auto& e : entries) {
// 路径从 UTF-8 转为本地编码
std::string localPath = Utf8ToLocal(e.path);
std::string fullPath = destDir + "/" + localPath;
NormalizePath(fullPath);
if (e.type == static_cast<uint8_t>(EntryType::Directory)) {
CreateDirectoryRecursive(fullPath);
} else {
// 创建父目录
std::string parent = GetParentPath(fullPath);
if (!parent.empty()) {
CreateDirectoryRecursive(parent);
}
if (e.compressedSize > 0) {
std::vector<char> compBuf(e.compressedSize);
if (fread(compBuf.data(), 1, e.compressedSize, fIn) != e.compressedSize) {
result = Error::FileReadFailed;
break;
}
std::vector<char> origBuf(e.originalSize);
if (e.method == static_cast<uint8_t>(CompressMethod::Zstd)) {
size_t ret = ZSTD_decompressDCtx(dctx, origBuf.data(), e.originalSize,
compBuf.data(), e.compressedSize);
if (ZSTD_isError(ret)) {
result = Error::DecompressFailed;
break;
}
} else {
// Store 方法,直接复制
memcpy(origBuf.data(), compBuf.data(), e.originalSize);
}
FILE* fOut = fopen(fullPath.c_str(), "wb");
if (fOut) {
fwrite(origBuf.data(), 1, e.originalSize, fOut);
fclose(fOut);
}
} else {
// 空文件
FILE* fOut = fopen(fullPath.c_str(), "wb");
if (fOut) fclose(fOut);
}
}
}
ZSTD_freeDCtx(dctx);
fclose(fIn);
return result;
}
/**
* @brief 获取归档信息
*/
static Error GetInfo(const std::string& archivePath, ArchiveInfo& info)
{
FILE* fIn = fopen(archivePath.c_str(), "rb");
if (!fIn) {
return Error::FileOpenFailed;
}
ZstaHeader header;
if (fread(&header, sizeof(header), 1, fIn) != 1 ||
memcmp(header.magic, MAGIC, 4) != 0) {
fclose(fIn);
return Error::InvalidFormat;
}
info.versionMajor = header.versionMajor;
info.versionMinor = header.versionMinor;
info.entryCount = header.entryCount;
info.compressLevel = header.compressLevel;
info.createTime = header.createTime;
info.totalOriginalSize = 0;
info.totalCompressedSize = 0;
std::vector<ZstaEntry> entries(header.entryCount);
if (fread(entries.data(), sizeof(ZstaEntry), header.entryCount, fIn) == header.entryCount) {
for (const auto& e : entries) {
info.totalOriginalSize += e.originalSize;
info.totalCompressedSize += e.compressedSize;
}
}
fclose(fIn);
return Error::Success;
}
/**
* @brief 列出归档内容
*/
static Error List(const std::string& archivePath, std::vector<EntryInfo>& entries)
{
FILE* fIn = fopen(archivePath.c_str(), "rb");
if (!fIn) {
return Error::FileOpenFailed;
}
ZstaHeader header;
if (fread(&header, sizeof(header), 1, fIn) != 1 ||
memcmp(header.magic, MAGIC, 4) != 0) {
fclose(fIn);
return Error::InvalidFormat;
}
std::vector<ZstaEntry> rawEntries(header.entryCount);
if (fread(rawEntries.data(), sizeof(ZstaEntry), header.entryCount, fIn) != header.entryCount) {
fclose(fIn);
return Error::FileReadFailed;
}
fclose(fIn);
entries.clear();
entries.reserve(header.entryCount);
for (const auto& e : rawEntries) {
EntryInfo info;
info.path = Utf8ToLocal(e.path); // 转为本地编码
info.type = static_cast<EntryType>(e.type);
info.originalSize = e.originalSize;
info.compressedSize = e.compressedSize;
info.modifyTime = e.modifyTime;
entries.push_back(info);
}
return Error::Success;
}
/**
* @brief 获取错误描述
*/
static const char* GetErrorString(Error err)
{
switch (err) {
case Error::Success:
return "Success";
case Error::FileOpenFailed:
return "Failed to open file";
case Error::FileReadFailed:
return "Failed to read file";
case Error::FileWriteFailed:
return "Failed to write file";
case Error::InvalidFormat:
return "Invalid archive format";
case Error::UnsupportedVersion:
return "Unsupported archive version";
case Error::DecompressFailed:
return "Decompression failed";
case Error::CompressFailed:
return "Compression failed";
case Error::PathTooLong:
return "Path too long";
case Error::EmptyInput:
return "Empty input";
default:
return "Unknown error";
}
}
private:
struct FileInfo {
std::string fullPath;
std::string relPath;
bool isDir;
uint64_t size;
uint64_t modTime;
};
#ifdef _WIN32
// MBCS -> UTF-8 (压缩时用,存入归档)
static std::string LocalToUtf8(const std::string& local)
{
if (local.empty()) return "";
int wlen = MultiByteToWideChar(CP_ACP, 0, local.c_str(), -1, NULL, 0);
if (wlen <= 0) return local;
std::wstring wide(wlen - 1, 0);
MultiByteToWideChar(CP_ACP, 0, local.c_str(), -1, &wide[0], wlen);
int ulen = WideCharToMultiByte(CP_UTF8, 0, wide.c_str(), -1, NULL, 0, NULL, NULL);
if (ulen <= 0) return local;
std::string utf8(ulen - 1, 0);
WideCharToMultiByte(CP_UTF8, 0, wide.c_str(), -1, &utf8[0], ulen, NULL, NULL);
return utf8;
}
// UTF-8 -> MBCS (解压时用,写入文件系统)
static std::string Utf8ToLocal(const std::string& utf8)
{
if (utf8.empty()) return "";
int wlen = MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, NULL, 0);
if (wlen <= 0) return utf8;
std::wstring wide(wlen - 1, 0);
MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, &wide[0], wlen);
int llen = WideCharToMultiByte(CP_ACP, 0, wide.c_str(), -1, NULL, 0, NULL, NULL);
if (llen <= 0) return utf8;
std::string local(llen - 1, 0);
WideCharToMultiByte(CP_ACP, 0, wide.c_str(), -1, &local[0], llen, NULL, NULL);
return local;
}
#else
// Linux/macOS 默认 UTF-8
static std::string LocalToUtf8(const std::string& s)
{
return s;
}
static std::string Utf8ToLocal(const std::string& s)
{
return s;
}
#endif
static bool IsDirectory(const std::string& path)
{
#ifdef _WIN32
DWORD attr = GetFileAttributesA(path.c_str());
return (attr != INVALID_FILE_ATTRIBUTES) && (attr & FILE_ATTRIBUTE_DIRECTORY);
#else
struct stat st;
if (stat(path.c_str(), &st) != 0) return false;
return S_ISDIR(st.st_mode);
#endif
}
static uint64_t GetFileSize(const std::string& path)
{
#ifdef _WIN32
WIN32_FILE_ATTRIBUTE_DATA fad;
if (!GetFileAttributesExA(path.c_str(), GetFileExInfoStandard, &fad)) return 0;
return (static_cast<uint64_t>(fad.nFileSizeHigh) << 32) | fad.nFileSizeLow;
#else
struct stat st;
if (stat(path.c_str(), &st) != 0) return 0;
return st.st_size;
#endif
}
static uint64_t GetFileModTime(const std::string& path)
{
#ifdef _WIN32
WIN32_FILE_ATTRIBUTE_DATA fad;
if (!GetFileAttributesExA(path.c_str(), GetFileExInfoStandard, &fad)) return 0;
// 转换 FILETIME 到 Unix 时间戳
ULARGE_INTEGER ull;
ull.LowPart = fad.ftLastWriteTime.dwLowDateTime;
ull.HighPart = fad.ftLastWriteTime.dwHighDateTime;
return (ull.QuadPart - 116444736000000000ULL) / 10000000ULL;
#else
struct stat st;
if (stat(path.c_str(), &st) != 0) return 0;
return st.st_mtime;
#endif
}
static std::string GetFileName(const std::string& path)
{
// 先移除末尾的斜杠
std::string p = path;
while (!p.empty() && (p.back() == '/' || p.back() == '\\')) {
p.pop_back();
}
if (p.empty()) return "";
size_t pos = p.find_last_of("/\\");
if (pos != std::string::npos) {
return p.substr(pos + 1);
}
return p;
}
static std::string GetParentPath(const std::string& path)
{
size_t pos = path.find_last_of("/\\");
if (pos != std::string::npos && pos > 0) {
return path.substr(0, pos);
}
return "";
}
static void NormalizePath(std::string& path)
{
for (char& c : path) {
#ifdef _WIN32
if (c == '/') c = '\\';
#else
if (c == '\\') c = '/';
#endif
}
// 移除末尾的分隔符(除非是根目录)
while (path.size() > 1 && (path.back() == '/' || path.back() == '\\')) {
path.pop_back();
}
}
static void CollectFiles(const std::string& dir, const std::string& rel,
std::vector<FileInfo>& files)
{
// 添加目录本身
FileInfo dirInfo;
dirInfo.fullPath = dir;
dirInfo.relPath = rel;
dirInfo.isDir = true;
dirInfo.size = 0;
dirInfo.modTime = GetFileModTime(dir);
files.push_back(dirInfo);
#ifdef _WIN32
WIN32_FIND_DATAA fd;
std::string pattern = dir + "\\*";
HANDLE h = FindFirstFileA(pattern.c_str(), &fd);
if (h == INVALID_HANDLE_VALUE) return;
do {
if (strcmp(fd.cFileName, ".") == 0 || strcmp(fd.cFileName, "..") == 0)
continue;
std::string fullPath = dir + "\\" + fd.cFileName;
std::string relPath = rel + "/" + fd.cFileName;
if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
CollectFiles(fullPath, relPath, files);
} else {
FileInfo fi;
fi.fullPath = fullPath;
fi.relPath = relPath;
fi.isDir = false;
fi.size = (static_cast<uint64_t>(fd.nFileSizeHigh) << 32) | fd.nFileSizeLow;
// 转换 FILETIME
ULARGE_INTEGER ull;
ull.LowPart = fd.ftLastWriteTime.dwLowDateTime;
ull.HighPart = fd.ftLastWriteTime.dwHighDateTime;
fi.modTime = (ull.QuadPart - 116444736000000000ULL) / 10000000ULL;
files.push_back(fi);
}
} while (FindNextFileA(h, &fd));
FindClose(h);
#else
DIR* d = opendir(dir.c_str());
if (!d) return;
struct dirent* entry;
while ((entry = readdir(d)) != nullptr) {
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
std::string fullPath = dir + "/" + entry->d_name;
std::string relPath = rel + "/" + entry->d_name;
struct stat st;
if (stat(fullPath.c_str(), &st) != 0) continue;
if (S_ISDIR(st.st_mode)) {
CollectFiles(fullPath, relPath, files);
} else {
FileInfo fi;
fi.fullPath = fullPath;
fi.relPath = relPath;
fi.isDir = false;
fi.size = st.st_size;
fi.modTime = st.st_mtime;
files.push_back(fi);
}
}
closedir(d);
#endif
}
static void CreateDirectoryRecursive(const std::string& path)
{
if (path.empty()) return;
std::string normalized = path;
NormalizePath(normalized);
size_t pos = 0;
while ((pos = normalized.find_first_of("/\\", pos + 1)) != std::string::npos) {
std::string sub = normalized.substr(0, pos);
#ifdef _WIN32
CreateDirectoryA(sub.c_str(), nullptr);
#else
mkdir(sub.c_str(), 0755);
#endif
}
#ifdef _WIN32
CreateDirectoryA(normalized.c_str(), nullptr);
#else
mkdir(normalized.c_str(), 0755);
#endif
}
};
} // namespace zsta
#endif // ZSTD_ARCHIVE_H

564
common/aes.c Normal file
View File

@@ -0,0 +1,564 @@
/*
This is an implementation of the AES algorithm, specifically ECB, CTR and CBC mode.
Block size can be chosen in aes.h - available choices are AES128, AES192, AES256.
The implementation is verified against the test vectors in:
National Institute of Standards and Technology Special Publication 800-38A 2001 ED
ECB-AES128
----------
plain-text:
6bc1bee22e409f96e93d7e117393172a
ae2d8a571e03ac9c9eb76fac45af8e51
30c81c46a35ce411e5fbc1191a0a52ef
f69f2445df4f9b17ad2b417be66c3710
key:
2b7e151628aed2a6abf7158809cf4f3c
resulting cipher
3ad77bb40d7a3660a89ecaf32466ef97
f5d3d58503b9699de785895a96fdbaaf
43b1cd7f598ece23881b00e3ed030688
7b0c785e27e8ad3f8223207104725dd4
NOTE: String length must be evenly divisible by 16byte (str_len % 16 == 0)
You should pad the end of the string with zeros if this is not the case.
For AES192/256 the key size is proportionally larger.
*/
/*****************************************************************************/
/* Includes: */
/*****************************************************************************/
#include <string.h> // CBC mode, for memset
#include "aes.h"
#pragma intrinsic(memcpy)
/*****************************************************************************/
/* Defines: */
/*****************************************************************************/
// The number of columns comprising a state in AES. This is a constant in AES. Value=4
#define Nb 4
#if defined(AES256) && (AES256 == 1)
#define Nk 8
#define Nr 14
#elif defined(AES192) && (AES192 == 1)
#define Nk 6
#define Nr 12
#else
#define Nk 4 // The number of 32 bit words in a key.
#define Nr 10 // The number of rounds in AES Cipher.
#endif
// jcallan@github points out that declaring Multiply as a function
// reduces code size considerably with the Keil ARM compiler.
// See this link for more information: https://github.com/kokke/tiny-AES-C/pull/3
#ifndef MULTIPLY_AS_A_FUNCTION
#define MULTIPLY_AS_A_FUNCTION 0
#endif
/*****************************************************************************/
/* Private variables: */
/*****************************************************************************/
// state - array holding the intermediate results during decryption.
typedef uint8_t state_t[4][4];
// The lookup-tables are marked const so they can be placed in read-only storage instead of RAM
// The numbers below can be computed dynamically trading ROM for RAM -
// This can be useful in (embedded) bootloader applications, where ROM is often limited.
static const uint8_t sbox[256] = {
//0 1 2 3 4 5 6 7 8 9 A B C D E F
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16
};
#if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1)
static const uint8_t rsbox[256] = {
0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d
};
#endif
// The round constant word array, Rcon[i], contains the values given by
// x to the power (i-1) being powers of x (x is denoted as {02}) in the field GF(2^8)
static const uint8_t Rcon[11] = {
0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36
};
/*
* Jordan Goulder points out in PR #12 (https://github.com/kokke/tiny-AES-C/pull/12),
* that you can remove most of the elements in the Rcon array, because they are unused.
*
* From Wikipedia's article on the Rijndael key schedule @ https://en.wikipedia.org/wiki/Rijndael_key_schedule#Rcon
*
* "Only the first some of these constants are actually used 鈥?up to rcon[10] for AES-128 (as 11 round keys are needed),
* up to rcon[8] for AES-192, up to rcon[7] for AES-256. rcon[0] is not used in AES algorithm."
*/
/*****************************************************************************/
/* Private functions: */
/*****************************************************************************/
/*
static uint8_t getSBoxValue(uint8_t num)
{
return sbox[num];
}
*/
#define getSBoxValue(num) (sbox[(num)])
// This function produces Nb(Nr+1) round keys. The round keys are used in each round to decrypt the states.
static void KeyExpansion(uint8_t* RoundKey, const uint8_t* Key)
{
unsigned i, j, k;
uint8_t tempa[4]; // Used for the column/row operations
// The first round key is the key itself.
for (i = 0; i < Nk; ++i) {
RoundKey[(i * 4) + 0] = Key[(i * 4) + 0];
RoundKey[(i * 4) + 1] = Key[(i * 4) + 1];
RoundKey[(i * 4) + 2] = Key[(i * 4) + 2];
RoundKey[(i * 4) + 3] = Key[(i * 4) + 3];
}
// All other round keys are found from the previous round keys.
for (i = Nk; i < Nb * (Nr + 1); ++i) {
{
k = (i - 1) * 4;
tempa[0]=RoundKey[k + 0];
tempa[1]=RoundKey[k + 1];
tempa[2]=RoundKey[k + 2];
tempa[3]=RoundKey[k + 3];
}
if (i % Nk == 0) {
// This function shifts the 4 bytes in a word to the left once.
// [a0,a1,a2,a3] becomes [a1,a2,a3,a0]
// Function RotWord()
{
const uint8_t u8tmp = tempa[0];
tempa[0] = tempa[1];
tempa[1] = tempa[2];
tempa[2] = tempa[3];
tempa[3] = u8tmp;
}
// SubWord() is a function that takes a four-byte input word and
// applies the S-box to each of the four bytes to produce an output word.
// Function Subword()
{
tempa[0] = getSBoxValue(tempa[0]);
tempa[1] = getSBoxValue(tempa[1]);
tempa[2] = getSBoxValue(tempa[2]);
tempa[3] = getSBoxValue(tempa[3]);
}
tempa[0] = tempa[0] ^ Rcon[i/Nk];
}
#if defined(AES256) && (AES256 == 1)
if (i % Nk == 4) {
// Function Subword()
{
tempa[0] = getSBoxValue(tempa[0]);
tempa[1] = getSBoxValue(tempa[1]);
tempa[2] = getSBoxValue(tempa[2]);
tempa[3] = getSBoxValue(tempa[3]);
}
}
#endif
j = i * 4;
k=(i - Nk) * 4;
RoundKey[j + 0] = RoundKey[k + 0] ^ tempa[0];
RoundKey[j + 1] = RoundKey[k + 1] ^ tempa[1];
RoundKey[j + 2] = RoundKey[k + 2] ^ tempa[2];
RoundKey[j + 3] = RoundKey[k + 3] ^ tempa[3];
}
}
void AES_init_ctx(struct AES_ctx* ctx, const uint8_t* key)
{
KeyExpansion(ctx->RoundKey, key);
}
#if (defined(CBC) && (CBC == 1)) || (defined(CTR) && (CTR == 1))
void AES_init_ctx_iv(struct AES_ctx* ctx, const uint8_t* key, const uint8_t* iv)
{
KeyExpansion(ctx->RoundKey, key);
memcpy (ctx->Iv, iv, AES_BLOCKLEN);
}
void AES_ctx_set_iv(struct AES_ctx* ctx, const uint8_t* iv)
{
memcpy (ctx->Iv, iv, AES_BLOCKLEN);
}
#endif
// This function adds the round key to state.
// The round key is added to the state by an XOR function.
static void AddRoundKey(uint8_t round, state_t* state, const uint8_t* RoundKey)
{
uint8_t i,j;
for (i = 0; i < 4; ++i) {
for (j = 0; j < 4; ++j) {
(*state)[i][j] ^= RoundKey[(round * Nb * 4) + (i * Nb) + j];
}
}
}
// The SubBytes Function Substitutes the values in the
// state matrix with values in an S-box.
static void SubBytes(state_t* state)
{
uint8_t i, j;
for (i = 0; i < 4; ++i) {
for (j = 0; j < 4; ++j) {
(*state)[j][i] = getSBoxValue((*state)[j][i]);
}
}
}
// The ShiftRows() function shifts the rows in the state to the left.
// Each row is shifted with different offset.
// Offset = Row number. So the first row is not shifted.
static void ShiftRows(state_t* state)
{
uint8_t temp;
// Rotate first row 1 columns to left
temp = (*state)[0][1];
(*state)[0][1] = (*state)[1][1];
(*state)[1][1] = (*state)[2][1];
(*state)[2][1] = (*state)[3][1];
(*state)[3][1] = temp;
// Rotate second row 2 columns to left
temp = (*state)[0][2];
(*state)[0][2] = (*state)[2][2];
(*state)[2][2] = temp;
temp = (*state)[1][2];
(*state)[1][2] = (*state)[3][2];
(*state)[3][2] = temp;
// Rotate third row 3 columns to left
temp = (*state)[0][3];
(*state)[0][3] = (*state)[3][3];
(*state)[3][3] = (*state)[2][3];
(*state)[2][3] = (*state)[1][3];
(*state)[1][3] = temp;
}
static uint8_t xtime(uint8_t x)
{
return ((x<<1) ^ (((x>>7) & 1) * 0x1b));
}
// MixColumns function mixes the columns of the state matrix
static void MixColumns(state_t* state)
{
uint8_t i;
uint8_t Tmp, Tm, t;
for (i = 0; i < 4; ++i) {
t = (*state)[i][0];
Tmp = (*state)[i][0] ^ (*state)[i][1] ^ (*state)[i][2] ^ (*state)[i][3] ;
Tm = (*state)[i][0] ^ (*state)[i][1] ;
Tm = xtime(Tm);
(*state)[i][0] ^= Tm ^ Tmp ;
Tm = (*state)[i][1] ^ (*state)[i][2] ;
Tm = xtime(Tm);
(*state)[i][1] ^= Tm ^ Tmp ;
Tm = (*state)[i][2] ^ (*state)[i][3] ;
Tm = xtime(Tm);
(*state)[i][2] ^= Tm ^ Tmp ;
Tm = (*state)[i][3] ^ t ;
Tm = xtime(Tm);
(*state)[i][3] ^= Tm ^ Tmp ;
}
}
// Multiply is used to multiply numbers in the field GF(2^8)
// Note: The last call to xtime() is unneeded, but often ends up generating a smaller binary
// The compiler seems to be able to vectorize the operation better this way.
// See https://github.com/kokke/tiny-AES-c/pull/34
#if MULTIPLY_AS_A_FUNCTION
static uint8_t Multiply(uint8_t x, uint8_t y)
{
return (((y & 1) * x) ^
((y>>1 & 1) * xtime(x)) ^
((y>>2 & 1) * xtime(xtime(x))) ^
((y>>3 & 1) * xtime(xtime(xtime(x)))) ^
((y>>4 & 1) * xtime(xtime(xtime(xtime(x)))))); /* this last call to xtime() can be omitted */
}
#else
#define Multiply(x, y) \
( ((y & 1) * x) ^ \
((y>>1 & 1) * xtime(x)) ^ \
((y>>2 & 1) * xtime(xtime(x))) ^ \
((y>>3 & 1) * xtime(xtime(xtime(x)))) ^ \
((y>>4 & 1) * xtime(xtime(xtime(xtime(x)))))) \
#endif
#if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1)
/*
static uint8_t getSBoxInvert(uint8_t num)
{
return rsbox[num];
}
*/
#define getSBoxInvert(num) (rsbox[(num)])
// MixColumns function mixes the columns of the state matrix.
// The method used to multiply may be difficult to understand for the inexperienced.
// Please use the references to gain more information.
static void InvMixColumns(state_t* state)
{
int i;
uint8_t a, b, c, d;
for (i = 0; i < 4; ++i) {
a = (*state)[i][0];
b = (*state)[i][1];
c = (*state)[i][2];
d = (*state)[i][3];
(*state)[i][0] = Multiply(a, 0x0e) ^ Multiply(b, 0x0b) ^ Multiply(c, 0x0d) ^ Multiply(d, 0x09);
(*state)[i][1] = Multiply(a, 0x09) ^ Multiply(b, 0x0e) ^ Multiply(c, 0x0b) ^ Multiply(d, 0x0d);
(*state)[i][2] = Multiply(a, 0x0d) ^ Multiply(b, 0x09) ^ Multiply(c, 0x0e) ^ Multiply(d, 0x0b);
(*state)[i][3] = Multiply(a, 0x0b) ^ Multiply(b, 0x0d) ^ Multiply(c, 0x09) ^ Multiply(d, 0x0e);
}
}
// The SubBytes Function Substitutes the values in the
// state matrix with values in an S-box.
static void InvSubBytes(state_t* state)
{
uint8_t i, j;
for (i = 0; i < 4; ++i) {
for (j = 0; j < 4; ++j) {
(*state)[j][i] = getSBoxInvert((*state)[j][i]);
}
}
}
static void InvShiftRows(state_t* state)
{
uint8_t temp;
// Rotate first row 1 columns to right
temp = (*state)[3][1];
(*state)[3][1] = (*state)[2][1];
(*state)[2][1] = (*state)[1][1];
(*state)[1][1] = (*state)[0][1];
(*state)[0][1] = temp;
// Rotate second row 2 columns to right
temp = (*state)[0][2];
(*state)[0][2] = (*state)[2][2];
(*state)[2][2] = temp;
temp = (*state)[1][2];
(*state)[1][2] = (*state)[3][2];
(*state)[3][2] = temp;
// Rotate third row 3 columns to right
temp = (*state)[0][3];
(*state)[0][3] = (*state)[1][3];
(*state)[1][3] = (*state)[2][3];
(*state)[2][3] = (*state)[3][3];
(*state)[3][3] = temp;
}
#endif // #if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1)
// Cipher is the main function that encrypts the PlainText.
static void Cipher(state_t* state, const uint8_t* RoundKey)
{
uint8_t round = 0;
// Add the First round key to the state before starting the rounds.
AddRoundKey(0, state, RoundKey);
// There will be Nr rounds.
// The first Nr-1 rounds are identical.
// These Nr rounds are executed in the loop below.
// Last one without MixColumns()
for (round = 1; ; ++round) {
SubBytes(state);
ShiftRows(state);
if (round == Nr) {
break;
}
MixColumns(state);
AddRoundKey(round, state, RoundKey);
}
// Add round key to last round
AddRoundKey(Nr, state, RoundKey);
}
#if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1)
static void InvCipher(state_t* state, const uint8_t* RoundKey)
{
uint8_t round = 0;
// Add the First round key to the state before starting the rounds.
AddRoundKey(Nr, state, RoundKey);
// There will be Nr rounds.
// The first Nr-1 rounds are identical.
// These Nr rounds are executed in the loop below.
// Last one without InvMixColumn()
for (round = (Nr - 1); ; --round) {
InvShiftRows(state);
InvSubBytes(state);
AddRoundKey(round, state, RoundKey);
if (round == 0) {
break;
}
InvMixColumns(state);
}
}
#endif // #if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1)
/*****************************************************************************/
/* Public functions: */
/*****************************************************************************/
#if defined(ECB) && (ECB == 1)
void AES_ECB_encrypt(const struct AES_ctx* ctx, uint8_t* buf)
{
// The next function call encrypts the PlainText with the Key using AES algorithm.
Cipher((state_t*)buf, ctx->RoundKey);
}
void AES_ECB_decrypt(const struct AES_ctx* ctx, uint8_t* buf)
{
// The next function call decrypts the PlainText with the Key using AES algorithm.
InvCipher((state_t*)buf, ctx->RoundKey);
}
#endif // #if defined(ECB) && (ECB == 1)
#if defined(CBC) && (CBC == 1)
static void XorWithIv(uint8_t* buf, const uint8_t* Iv)
{
uint8_t i;
for (i = 0; i < AES_BLOCKLEN; ++i) { // The block in AES is always 128bit no matter the key size
buf[i] ^= Iv[i];
}
}
void AES_CBC_encrypt_buffer(struct AES_ctx *ctx, uint8_t* buf, size_t length)
{
size_t i;
uint8_t *Iv = ctx->Iv;
for (i = 0; i < length; i += AES_BLOCKLEN) {
XorWithIv(buf, Iv);
Cipher((state_t*)buf, ctx->RoundKey);
Iv = buf;
buf += AES_BLOCKLEN;
}
/* store Iv in ctx for next call */
memcpy(ctx->Iv, Iv, AES_BLOCKLEN);
}
void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length)
{
size_t i;
uint8_t storeNextIv[AES_BLOCKLEN];
for (i = 0; i < length; i += AES_BLOCKLEN) {
memcpy(storeNextIv, buf, AES_BLOCKLEN);
InvCipher((state_t*)buf, ctx->RoundKey);
XorWithIv(buf, ctx->Iv);
memcpy(ctx->Iv, storeNextIv, AES_BLOCKLEN);
buf += AES_BLOCKLEN;
}
}
#endif // #if defined(CBC) && (CBC == 1)
#if defined(CTR) && (CTR == 1)
/* Symmetrical operation: same function for encrypting as for decrypting. Note any IV/nonce should never be reused with the same key */
void AES_CTR_xcrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length)
{
uint8_t buffer[AES_BLOCKLEN];
size_t i;
int bi;
for (i = 0, bi = AES_BLOCKLEN; i < length; ++i, ++bi) {
if (bi == AES_BLOCKLEN) { /* we need to regen xor compliment in buffer */
memcpy(buffer, ctx->Iv, AES_BLOCKLEN);
Cipher((state_t*)buffer,ctx->RoundKey);
/* Increment Iv and handle overflow */
for (bi = (AES_BLOCKLEN - 1); bi >= 0; --bi) {
/* inc will overflow */
if (ctx->Iv[bi] == 255) {
ctx->Iv[bi] = 0;
continue;
}
ctx->Iv[bi] += 1;
break;
}
bi = 0;
}
buf[i] = (buf[i] ^ buffer[bi]);
}
}
#endif // #if defined(CTR) && (CTR == 1)

90
common/aes.h Normal file
View File

@@ -0,0 +1,90 @@
#ifndef _AES_H_
#define _AES_H_
#include <stdint.h>
#include <stddef.h>
// #define the macros below to 1/0 to enable/disable the mode of operation.
//
// CBC enables AES encryption in CBC-mode of operation.
// CTR enables encryption in counter-mode.
// ECB enables the basic ECB 16-byte block algorithm. All can be enabled simultaneously.
// The #ifndef-guard allows it to be configured before #include'ing or at compile time.
#ifndef CBC
#define CBC 1
#endif
#ifndef ECB
#define ECB 1
#endif
#ifndef CTR
#define CTR 1
#endif
#define AES128 1
//#define AES192 1
//#define AES256 1
#define AES_BLOCKLEN 16 // Block length in bytes - AES is 128b block only
#if defined(AES256) && (AES256 == 1)
#define AES_KEYLEN 32
#define AES_keyExpSize 240
#elif defined(AES192) && (AES192 == 1)
#define AES_KEYLEN 24
#define AES_keyExpSize 208
#else
#define AES_KEYLEN 16 // Key length in bytes
#define AES_keyExpSize 176
#endif
struct AES_ctx {
uint8_t RoundKey[AES_keyExpSize];
#if (defined(CBC) && (CBC == 1)) || (defined(CTR) && (CTR == 1))
uint8_t Iv[AES_BLOCKLEN];
#endif
};
void AES_init_ctx(struct AES_ctx* ctx, const uint8_t* key);
#if (defined(CBC) && (CBC == 1)) || (defined(CTR) && (CTR == 1))
void AES_init_ctx_iv(struct AES_ctx* ctx, const uint8_t* key, const uint8_t* iv);
void AES_ctx_set_iv(struct AES_ctx* ctx, const uint8_t* iv);
#endif
#if defined(ECB) && (ECB == 1)
// buffer size is exactly AES_BLOCKLEN bytes;
// you need only AES_init_ctx as IV is not used in ECB
// NB: ECB is considered insecure for most uses
void AES_ECB_encrypt(const struct AES_ctx* ctx, uint8_t* buf);
void AES_ECB_decrypt(const struct AES_ctx* ctx, uint8_t* buf);
#endif // #if defined(ECB) && (ECB == !)
#if defined(CBC) && (CBC == 1)
// buffer size MUST be mutile of AES_BLOCKLEN;
// Suggest https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7 for padding scheme
// NOTES: you need to set IV in ctx via AES_init_ctx_iv() or AES_ctx_set_iv()
// no IV should ever be reused with the same key
void AES_CBC_encrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length);
void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length);
#endif // #if defined(CBC) && (CBC == 1)
#if defined(CTR) && (CTR == 1)
// Same function for encrypting as for decrypting.
// IV is incremented for every block, and used after encryption as XOR-compliment for output
// Suggesting https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7 for padding scheme
// NOTES: you need to set IV in ctx with AES_init_ctx_iv() or AES_ctx_set_iv()
// no IV should ever be reused with the same key
void AES_CTR_xcrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length);
#endif // #if defined(CTR) && (CTR == 1)
#endif // _AES_H_

1451
common/commands.h Normal file

File diff suppressed because it is too large Load Diff

35
common/dllRunner.h Normal file
View File

@@ -0,0 +1,35 @@
#pragma once
#include <windows.h>
// A DLL runner.
class DllRunner
{
public:
virtual ~DllRunner() {}
virtual void* LoadLibraryA(const char* path, int size = 0) = 0;
virtual FARPROC GetProcAddress(void* mod, const char* lpProcName) = 0;
virtual BOOL FreeLibrary(void* mod) = 0;
};
// Default DLL runner.
class DefaultDllRunner : public DllRunner
{
private:
std::string m_path;
HMODULE m_mod;
public:
DefaultDllRunner(const std::string &path="") :m_path(path), m_mod(nullptr) {}
// Load DLL from the disk.
virtual void* LoadLibraryA(const char* path, int size = 0)
{
return m_mod = ::LoadLibraryA(size ? m_path.c_str() : path);
}
virtual FARPROC GetProcAddress(void* mod, const char* lpProcName)
{
return ::GetProcAddress(m_mod, lpProcName);
}
virtual BOOL FreeLibrary(void* mod)
{
return ::FreeLibrary(m_mod);
}
};

135
common/encfuncs.h Normal file
View File

@@ -0,0 +1,135 @@
#pragma once
// 加密函数
inline void encrypt_v1(unsigned char* data, size_t length, unsigned char key)
{
for (size_t i = 0; i < length; i++) {
if (i % 2 == 0) {
data[i] = data[i] + key; // 偶数索引加 key
} else {
data[i] = data[i] - key; // 奇数索引减 key
}
}
}
// 解密函数
inline void decrypt_v1(unsigned char* data, size_t length, unsigned char key)
{
for (size_t i = 0; i < length; i++) {
if (i % 2 == 0) {
data[i] = data[i] - key; // 偶数索引减 key 还原
} else {
data[i] = data[i] + key; // 奇数索引加 key 还原
}
}
}
// 加密函数 - 使用异或和位旋转
inline void encrypt_v2(unsigned char* data, size_t length, unsigned char key)
{
for (size_t i = 0; i < length; i++) {
// 偶数索引与key异或后左循环移位1位
// 奇数索引与key异或后右循环移位1位
data[i] ^= key;
if (i % 2 == 0) {
data[i] = (data[i] << 1) | (data[i] >> 7); // 左循环移位
} else {
data[i] = (data[i] >> 1) | (data[i] << 7); // 右循环移位
}
}
}
// 解密函数
inline void decrypt_v2(unsigned char* data, size_t length, unsigned char key)
{
for (size_t i = 0; i < length; i++) {
// 加密的逆操作
if (i % 2 == 0) {
data[i] = (data[i] >> 1) | (data[i] << 7); // 右循环移位还原
} else {
data[i] = (data[i] << 1) | (data[i] >> 7); // 左循环移位还原
}
data[i] ^= key; // 再次异或还原
}
}
// 加密函数 V3 - 基于索引和key的动态计算
inline void encrypt_v3(unsigned char* data, size_t length, unsigned char key)
{
for (size_t i = 0; i < length; i++) {
unsigned char dynamic_key = key + (i % 8); // 动态变化的key基于索引
if (i % 3 == 0) {
data[i] = (data[i] + dynamic_key) ^ dynamic_key; // 加法 + 异或
} else if (i % 3 == 1) {
data[i] = (data[i] ^ dynamic_key) - dynamic_key; // 异或 + 减法
} else {
data[i] = ~(data[i] + dynamic_key); // 取反 + 加法
}
}
}
// 解密函数 V3
inline void decrypt_v3(unsigned char* data, size_t length, unsigned char key)
{
for (size_t i = 0; i < length; i++) {
unsigned char dynamic_key = key + (i % 8);
if (i % 3 == 0) {
data[i] = (data[i] ^ dynamic_key) - dynamic_key; // 逆操作:先异或再减
} else if (i % 3 == 1) {
data[i] = (data[i] + dynamic_key) ^ dynamic_key; // 逆操作:先加再异或
} else {
data[i] = ~data[i] - dynamic_key; // 逆操作:取反再减
}
}
}
// 加密函数 V4 - 基于伪随机序列(简单线性同余生成器)
inline void encrypt_v4(unsigned char* data, size_t length, unsigned char key)
{
unsigned char rand = key;
for (size_t i = 0; i < length; i++) {
rand = (rand * 13 + 17) % 256; // 伪随机数生成LCG
data[i] ^= rand; // 用伪随机数异或加密
}
}
// 解密函数 V4与加密完全相同因为异或的自反性
inline void decrypt_v4(unsigned char* data, size_t length, unsigned char key)
{
encrypt_v4(data, length, key); // 异或加密的解密就是再执行一次
}
// 加密函数 V5 - V5 版本(动态密钥派生 + 多重位运算)
inline void encrypt_v5(unsigned char* data, size_t length, unsigned char key)
{
for (size_t i = 0; i < length; i++) {
unsigned char dynamic_key = (key + i) ^ 0x55; // 动态密钥派生
data[i] = ((data[i] + dynamic_key) ^ (dynamic_key << 3)) + (i % 7);
}
}
// 解密函数 V5
inline void decrypt_v5(unsigned char* data, size_t length, unsigned char key)
{
for (size_t i = 0; i < length; i++) {
unsigned char dynamic_key = (key + i) ^ 0x55;
data[i] = (((data[i] - (i % 7)) ^ (dynamic_key << 3)) - dynamic_key);
}
}
// 加密/解密函数 V6自反性 - V6 版本(伪随机流混淆 + 自反性解密)
inline void encrypt_v6(unsigned char* data, size_t length, unsigned char key)
{
unsigned char rand = key;
for (size_t i = 0; i < length; i++) {
rand = (rand * 31 + 17) % 256; // 简单伪随机生成
data[i] ^= rand + i; // 异或动态值
}
}
// 解密函数 V6直接调用 encrypt_v6 即可)
inline void decrypt_v6(unsigned char* data, size_t length, unsigned char key)
{
encrypt_v6(data, length, key); // 异或的自反性
}

163
common/encrypt.h Normal file
View File

@@ -0,0 +1,163 @@
#pragma once
// This file implements a serial of data encoding methods.
#include <vector>
extern "C" {
#include "aes.h"
}
#define ALIGN16(n) ( (( (n) + 15) / 16) * 16 )
// Encoder interface. The default encoder will do nothing.
class Encoder
{
public:
virtual ~Encoder() {}
// Encode data before compress.
virtual void Encode(unsigned char* data, int len, unsigned char* param = 0) {}
// Decode data after uncompress.
virtual void Decode(unsigned char* data, int len, unsigned char* param = 0) {}
};
// XOR Encoder implementation.
class XOREncoder : public Encoder
{
private:
std::vector<char> Keys;
public:
XOREncoder(const std::vector<char>& keys = { 0 }) : Keys(keys) {}
virtual void Encode(unsigned char* data, int len, unsigned char* param = 0)
{
XOR(data, len, Keys);
}
virtual void Decode(unsigned char* data, int len, unsigned char* param = 0)
{
static std::vector<char> reversed(Keys.rbegin(), Keys.rend());
XOR(data, len, reversed);
}
protected:
void XOR(unsigned char* data, int len, const std::vector<char>& keys) const
{
for (char key : keys) {
for (int i = 0; i < len; ++i) {
data[i] ^= key;
}
}
}
};
// XOREncoder16 A simple Encoder for the TCP body. It's using for `HELL` protocol.
// This method is provided by ChatGPT. Encode data according to the 6th and 7th elem.
class XOREncoder16 : public Encoder
{
private:
static uint16_t pseudo_random(uint16_t seed, int index)
{
return ((seed ^ (index * 251 + 97)) * 733) ^ (seed >> 3);
}
void encrypt_internal(unsigned char* data, int len, unsigned char k1, unsigned char k2) const
{
uint16_t key = ((k1 << 8) | k2);
for (int i = 0; i < len; ++i) {
data[i] ^= (k1 + i * 13) ^ (k2 ^ (i << 1));
}
// Two rounds of pseudo-random swaps
for (int round = 0; round < 2; ++round) {
for (int i = 0; i < len; ++i) {
int j = pseudo_random(key, i + round * 100) % len;
std::swap(data[i], data[j]);
}
}
}
void decrypt_internal(unsigned char* data, int len, unsigned char k1, unsigned char k2) const
{
uint16_t key = ((k1 << 8) | k2);
for (int round = 1; round >= 0; --round) {
for (int i = len - 1; i >= 0; --i) {
int j = pseudo_random(key, i + round * 100) % len;
std::swap(data[i], data[j]);
}
}
for (int i = 0; i < len; ++i) {
data[i] ^= (k1 + i * 13) ^ (k2 ^ (i << 1));
}
}
#ifndef NO_AES
void aes_encrypt(unsigned char* data, int len, const unsigned char* key, const unsigned char* iv)
{
if (!data || !key || !iv || len <= 0 || len % 16 != 0) {
return; // AES CBC requires data length to be multiple of 16
}
struct AES_ctx ctx;
AES_init_ctx_iv(&ctx, key, iv);
AES_CBC_encrypt_buffer(&ctx, data, len);
}
void aes_decrypt(unsigned char* data, int len, const unsigned char* key, const unsigned char* iv)
{
if (!data || !key || !iv || len <= 0 || len % 16 != 0)
return;
struct AES_ctx ctx;
AES_init_ctx_iv(&ctx, key, iv);
AES_CBC_decrypt_buffer(&ctx, data, len);
}
#endif
public:
XOREncoder16() {}
void Encode(unsigned char* data, int len, unsigned char* param) override
{
if (param[6] == 0 && param[7] == 0) return;
if (param[7] == 1) {
#ifndef NO_AES
static const unsigned char aes_key[16] = {
0x5A, 0xC3, 0x17, 0xF0, 0x89, 0xB6, 0x4E, 0x7D, 0x1A, 0x22, 0x9F, 0xC8, 0xD3, 0xE6, 0x73, 0xB1
};
return aes_encrypt(data, len, aes_key, param + 8);
#endif
}
encrypt_internal(data, len, param[6], param[7]);
}
void Decode(unsigned char* data, int len, unsigned char* param) override
{
if (param[6] == 0 && param[7] == 0) return;
decrypt_internal(data, len, param[6], param[7]);
}
};
class WinOsEncoder : public Encoder
{
public:
virtual ~WinOsEncoder() {}
// Encode data before compress.
virtual void Encode(unsigned char* data, int len, unsigned char* param = 0)
{
return XOR(data, len, param);
}
// Decode data after uncompress.
virtual void Decode(unsigned char* data, int len, unsigned char* param = 0)
{
return XOR(data, len, param);
}
private:
void XOR(unsigned char* data, int len, unsigned char* password)
{
for (int i = 0, j = 0; i < len; i++) {
((char*)data)[i] ^= (password[j++]) % 456 + 54;
if (i % (10) == 0)
j = 0;
}
}
};

263
common/file_upload.cpp Normal file
View File

@@ -0,0 +1,263 @@
#include "file_upload.h"
#include <Windows.h>
#include <ShlObj.h>
#include <ExDisp.h>
#include <ShlGuid.h>
#include <atlbase.h>
#include <atlcom.h>
#include <string>
#include <vector>
#pragma comment(lib, "shell32.lib")
#pragma comment(lib, "ole32.lib")
#pragma comment(lib, "oleaut32.lib")
void ExpandDirectory(const std::string& dir, std::vector<std::string>& result)
{
std::string searchPath = dir + "\\*";
WIN32_FIND_DATAA fd;
HANDLE hFind = FindFirstFileA(searchPath.c_str(), &fd);
if (hFind == INVALID_HANDLE_VALUE) return;
do {
if (strcmp(fd.cFileName, ".") == 0 || strcmp(fd.cFileName, "..") == 0)
continue;
std::string fullPath = dir + "\\" + fd.cFileName;
result.push_back(fullPath); // 文件和目录都加入
if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
ExpandDirectory(fullPath, result); // 递归
}
} while (FindNextFileA(hFind, &fd));
FindClose(hFind);
}
std::vector<std::string> ExpandDirectories(const std::vector<std::string>& selected)
{
std::vector<std::string> result;
for (const auto& path : selected) {
DWORD attr = GetFileAttributesA(path.c_str());
if (attr == INVALID_FILE_ATTRIBUTES) continue;
result.push_back(path); // 先加入自身
if (attr & FILE_ATTRIBUTE_DIRECTORY) {
ExpandDirectory(path, result);
}
}
return result;
}
static std::vector<std::string> GetDesktopSelectedFiles(int& result)
{
CComPtr<IShellWindows> pShellWindows;
if (FAILED(pShellWindows.CoCreateInstance(CLSID_ShellWindows))) {
result = 101;
return {};
}
CComVariant vLoc(CSIDL_DESKTOP);
CComVariant vEmpty;
long lhwnd;
CComPtr<IDispatch> pDisp;
if (FAILED(pShellWindows->FindWindowSW(&vLoc, &vEmpty, SWC_DESKTOP, &lhwnd, SWFO_NEEDDISPATCH, &pDisp))) {
result = 102;
return {};
}
CComQIPtr<IServiceProvider> pServiceProvider(pDisp);
if (!pServiceProvider) {
result = 103;
return {};
}
CComPtr<IShellBrowser> pShellBrowser;
if (FAILED(pServiceProvider->QueryService(SID_STopLevelBrowser, IID_IShellBrowser, (void**)&pShellBrowser))) {
result = 104;
return {};
}
CComPtr<IShellView> pShellView;
if (FAILED(pShellBrowser->QueryActiveShellView(&pShellView))) {
result = 105;
return {};
}
CComPtr<IDataObject> pDataObject;
if (FAILED(pShellView->GetItemObject(SVGIO_SELECTION, IID_IDataObject, (void**)&pDataObject))) {
result = 106;
return {};
}
FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
STGMEDIUM stg = {};
if (FAILED(pDataObject->GetData(&fmt, &stg))) {
result = 107;
return {};
}
std::vector<std::string> vecFiles;
HDROP hDrop = (HDROP)GlobalLock(stg.hGlobal);
if (hDrop) {
UINT nFiles = DragQueryFileA(hDrop, 0xFFFFFFFF, NULL, 0);
for (UINT i = 0; i < nFiles; i++) {
char szPath[MAX_PATH];
if (DragQueryFileA(hDrop, i, szPath, MAX_PATH)) {
vecFiles.push_back(szPath);
}
}
GlobalUnlock(stg.hGlobal);
}
ReleaseStgMedium(&stg);
vecFiles = ExpandDirectories(vecFiles);
return vecFiles;
}
std::vector<std::string> GetForegroundSelectedFiles(int& result)
{
result = 0;
HRESULT hrInit = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
bool bNeedUninit = SUCCEEDED(hrInit);
HWND hFore = GetForegroundWindow();
// 检查是否是桌面
HWND hDesktop = FindWindow("Progman", NULL);
HWND hWorkerW = NULL;
EnumWindows([](HWND hwnd, LPARAM lParam) -> BOOL {
if (FindWindowEx(hwnd, NULL, "SHELLDLL_DefView", NULL))
{
*(HWND*)lParam = hwnd;
return FALSE;
}
return TRUE;
}, (LPARAM)&hWorkerW);
if (hFore == hDesktop || hFore == hWorkerW) {
if (bNeedUninit) CoUninitialize();
return GetDesktopSelectedFiles(result);
}
// 检查是否是资源管理器窗口
char szClass[256] = {};
GetClassNameA(hFore, szClass, 256);
if (strcmp(szClass, "CabinetWClass") != 0 && strcmp(szClass, "ExploreWClass") != 0) {
if (bNeedUninit) CoUninitialize();
result = 1;
return {};
}
// 获取该窗口的选中项
CComPtr<IShellWindows> pShellWindows;
if (FAILED(pShellWindows.CoCreateInstance(CLSID_ShellWindows))) {
if (bNeedUninit) CoUninitialize();
result = 2;
return {};
}
std::vector<std::string> vecFiles;
long nCount = 0;
pShellWindows->get_Count(&nCount);
for (long i = 0; i < nCount; i++) {
CComVariant vIndex(i);
CComPtr<IDispatch> pDisp;
if (FAILED(pShellWindows->Item(vIndex, &pDisp)) || !pDisp)
continue;
CComQIPtr<IWebBrowserApp> pBrowser(pDisp);
if (!pBrowser)
continue;
SHANDLE_PTR hWnd = 0;
pBrowser->get_HWND(&hWnd);
if ((HWND)hWnd != hFore)
continue;
CComPtr<IDispatch> pDoc;
if (FAILED(pBrowser->get_Document(&pDoc)) || !pDoc) {
result = 3;
break;
}
CComQIPtr<IShellFolderViewDual> pView(pDoc);
if (!pView) {
result = 4;
break;
}
CComPtr<FolderItems> pItems;
if (FAILED(pView->SelectedItems(&pItems)) || !pItems) {
result = 5;
break;
}
long nItems = 0;
pItems->get_Count(&nItems);
for (long j = 0; j < nItems; j++) {
CComVariant vj(j);
CComPtr<FolderItem> pItem;
if (SUCCEEDED(pItems->Item(vj, &pItem)) && pItem) {
CComBSTR bstrPath;
if (SUCCEEDED(pItem->get_Path(&bstrPath))) {
// BSTR (宽字符) 转 多字节
int nLen = WideCharToMultiByte(CP_ACP, 0, bstrPath, -1, NULL, 0, NULL, NULL);
if (nLen > 0) {
std::string strPath(nLen - 1, '\0');
WideCharToMultiByte(CP_ACP, 0, bstrPath, -1, &strPath[0], nLen, NULL, NULL);
vecFiles.push_back(strPath);
}
}
}
}
break;
}
if (bNeedUninit) CoUninitialize();
vecFiles = ExpandDirectories(vecFiles);
return vecFiles;
}
// 将多个路径组合成单\0分隔的char数组
// 格式: "path1\0path2\0path3\0"
std::vector<char> BuildMultiStringPath(const std::vector<std::string>& paths)
{
std::vector<char> result;
for (const auto& path : paths) {
result.insert(result.end(), path.begin(), path.end());
result.push_back('\0');
}
return result;
}
// 从char数组解析出多个路径
std::vector<std::string> ParseMultiStringPath(const char* buffer, size_t size)
{
std::vector<std::string> paths;
const char* p = buffer;
const char* end = buffer + size;
while (p < end) {
size_t len = strlen(p);
if (len > 0) {
paths.emplace_back(p, len);
}
p += len + 1;
}
return paths;
}

331
common/file_upload.h Normal file
View File

@@ -0,0 +1,331 @@
#pragma once
#include <string>
#include <vector>
#include <map>
#pragma pack(push, 1)
// ==================== V1 协议(保持不变)====================
struct FileChunkPacket {
unsigned char cmd; // COMMAND_SEND_FILE = 68
uint32_t fileIndex; // 文件编号
uint32_t totalNum; // 文件总数
uint64_t fileSize; // 整个文件大小
uint64_t offset; // 当前块在文件中的偏移
uint64_t dataLength; // 本块数据长度
uint64_t nameLength; // 文件名长度(不含 '\0'
// char filename[nameLength];
// uint8_t data[dataLength];
}; // 41 bytes
// ==================== V2 协议C2C + 断点续传)====================
// V2 标志位
enum FileFlagsV2 : uint16_t {
FFV2_NONE = 0x0000,
FFV2_LAST_CHUNK = 0x0001, // 最后一块
FFV2_RESUME_REQ = 0x0002, // 请求续传信息
FFV2_RESUME_RESP = 0x0004, // 续传信息响应
FFV2_CANCEL = 0x0008, // 取消传输
FFV2_DIRECTORY = 0x0010, // 目录项(不是文件)
FFV2_COMPRESSED = 0x0020, // 数据已压缩
FFV2_ERROR = 0x0040, // 传输错误
};
// V2 文件传输包
struct FileChunkPacketV2 {
uint8_t cmd; // COMMAND_SEND_FILE_V2 = 85
uint64_t transferID; // 传输会话ID唯一标识一次传输任务
uint64_t srcClientID; // 源客户端 (0=主控端)
uint64_t dstClientID; // 目标客户端 (0=主控端)
uint32_t fileIndex; // 文件编号 (0-based)
uint32_t totalFiles; // 总文件数
uint64_t fileSize; // 当前文件大小
uint64_t offset; // 当前块在文件中的偏移
uint64_t dataLength; // 本块数据长度
uint64_t nameLength; // 文件名长度
uint16_t flags; // FileFlagsV2 组合
uint16_t checksum; // CRC16可选
uint8_t reserved[8]; // 预留扩展
// char filename[nameLength]; // UTF-8 相对路径
// uint8_t data[dataLength]; // 文件数据
}; // 77 bytes
// V2 断点续传区间
struct FileRangeV2 {
uint64_t offset; // 起始偏移
uint64_t length; // 长度
}; // 16 bytes
// V2 断点续传控制包
struct FileResumePacketV2 {
uint8_t cmd; // COMMAND_FILE_RESUME = 86
uint64_t transferID; // 传输会话ID
uint64_t srcClientID; // 源客户端
uint64_t dstClientID; // 目标客户端
uint32_t fileIndex; // 文件编号
uint64_t fileSize; // 文件大小
uint64_t receivedBytes; // 已接收字节数
uint16_t flags; // FFV2_RESUME_REQ 或 FFV2_RESUME_RESP
uint16_t rangeCount; // 已接收区间数量
// FileRangeV2 ranges[rangeCount]; // 已接收的区间列表
}; // 49 bytes + ranges
// V2 剪贴板请求C2C 触发)
struct ClipboardRequestV2 {
uint8_t cmd; // COMMAND_CLIPBOARD_V2 = 87
uint64_t srcClientID; // 源客户端(发送方)
uint64_t dstClientID; // 目标客户端(接收方)
uint64_t transferID; // 传输ID
char hash[64]; // 认证哈希
char hmac[16]; // HMAC
}; // 105 bytes
// C2C 准备接收通知(发给目标客户端,让它捕获当前目录)
struct C2CPreparePacket {
uint8_t cmd; // COMMAND_C2C_PREPARE = 89
uint64_t transferID; // 传输ID与后续文件关联
uint64_t srcClientID; // 源客户端ID发送方用于返回响应
}; // 17 bytes
// C2C 准备响应(返回目标目录给发送方)
struct C2CPrepareRespPacket {
uint8_t cmd; // COMMAND_C2C_PREPARE_RESP = 92
uint64_t transferID; // 传输ID
uint64_t srcClientID; // 原始发送方客户端ID
uint16_t pathLength; // 目录路径长度
// char path[pathLength]; // UTF-8 目标目录
}; // 19 bytes + path
// V2 文件完成校验包
struct FileCompletePacketV2 {
uint8_t cmd; // COMMAND_FILE_COMPLETE_V2 = 91
uint64_t transferID; // 传输ID
uint64_t srcClientID; // 源客户端
uint64_t dstClientID; // 目标客户端
uint32_t fileIndex; // 文件编号
uint64_t fileSize; // 文件大小
uint8_t sha256[32]; // SHA-256 哈希
}; // 69 bytes
// C2C 准备接收:捕获当前目录并存储(由 COMMAND_C2C_PREPARE 调用,接收方使用)
void SetC2CTargetFolder(uint64_t transferID);
// C2C 发送方:存储目标目录(收到 C2C_PREPARE_RESP 后调用)
void SetSenderTargetFolder(uint64_t transferID, const std::wstring& targetDir);
// C2C 发送方:获取目标目录(用于构建完整路径)
std::wstring GetSenderTargetFolder(uint64_t transferID);
// V2 错误码
enum FileErrorV2 : uint8_t {
FEV2_OK = 0,
FEV2_TARGET_OFFLINE = 1, // 目标客户端离线
FEV2_VERSION_MISMATCH = 2, // 版本不匹配
FEV2_FILE_NOT_FOUND = 3, // 文件不存在
FEV2_ACCESS_DENIED = 4, // 访问被拒绝
FEV2_DISK_FULL = 5, // 磁盘空间不足
FEV2_TRANSFER_CANCEL = 6, // 传输被取消
FEV2_CHECKSUM_ERROR = 7, // 校验和错误块级CRC
FEV2_HASH_MISMATCH = 8, // 哈希不匹配文件级SHA-256
};
// V2 续传查询包(发送方在传输前发送)
struct FileQueryResumeV2 {
uint8_t cmd; // COMMAND_FILE_QUERY_RESUME = 88
uint64_t transferID; // 传输会话ID用于C2C目标目录匹配
uint64_t srcClientID; // 源客户端
uint64_t dstClientID; // 目标客户端 (0=主控端)
uint32_t fileCount; // 文件数量
// 后跟 fileCount 个 FileQueryResumeEntryV2
}; // 29 bytes + entries
// 续传查询条目
struct FileQueryResumeEntryV2 {
uint64_t fileSize; // 文件大小
uint16_t nameLength; // 文件名长度
// char filename[nameLength]; // UTF-8 相对路径
}; // 10 bytes + filename
// V2 续传查询响应包
struct FileResumeResponseV2 {
uint8_t cmd; // COMMAND_FILE_RESUME = 86
uint64_t srcClientID; // 原始的源客户端
uint64_t dstClientID; // 原始的目标客户端
uint16_t flags; // FFV2_RESUME_RESP
uint32_t fileCount; // 响应条目数
// 后跟 fileCount 个 FileResumeResponseEntryV2
}; // 23 bytes + entries
// 续传响应条目
struct FileResumeResponseEntryV2 {
uint32_t fileIndex; // 文件编号
uint64_t receivedBytes; // 已接收字节数0=需要完整传输)
}; // 12 bytes
#pragma pack(pop)
typedef void (*LogFunc)(const char* file, int line, const char* format, ...);
int InitFileUpload(const std::string& key, const std::string& msg, const std::string& hmac,
int chunkSizeKb = 64, int sendDurationMs = 50, LogFunc logFunc = NULL, const std::string& pwdHash = {});
int UninitFileUpload();
std::vector<std::string> GetClipboardFiles(int& result);
bool GetCurrentFolderPath(std::string& outDir);
typedef bool (*OnTransform)(void* user, FileChunkPacket* chunk, unsigned char* data, int size);
typedef void (*OnFinish)(void* user);
int FileBatchTransferWorker(const std::vector<std::string>& files, const std::string& targetDir,
void* user, OnTransform f, OnFinish finish, const std::string& hash, const std::string& hmac);
int RecvFileChunk(char* buf, size_t len, void* user, OnFinish f, const std::string& hash, const std::string& hmac);
// ==================== V2 接口 ====================
// V2 传输选项
struct TransferOptionsV2 {
uint64_t transferID; // 传输ID (0=自动生成)
uint64_t srcClientID; // 源客户端ID (0=主控端)
uint64_t dstClientID; // 目标客户端ID (0=主控端)
bool enableResume; // 启用断点续传
std::map<uint32_t, uint64_t> startOffsets; // 每个文件的起始偏移 (fileIndex -> offset)
TransferOptionsV2() : transferID(0), srcClientID(0), dstClientID(0), enableResume(true) {}
};
// V2 发送回调
typedef bool (*OnTransformV2)(void* user, FileChunkPacketV2* chunk, unsigned char* data, int size);
// V2 文件发送
int FileBatchTransferWorkerV2(
const std::vector<std::string>& files,
const std::string& targetDir,
void* user,
OnTransformV2 f,
OnFinish finish,
const std::string& hash,
const std::string& hmac,
const TransferOptionsV2& options
);
// V2 文件接收
int RecvFileChunkV2(
char* buf, size_t len,
void* user,
OnFinish f,
const std::string& hash,
const std::string& hmac,
uint64_t myClientID
);
// 生成传输ID
uint64_t GenerateTransferID();
// 断点续传:保存状态
bool SaveResumeState(uint64_t transferID, const char* targetDir);
// 断点续传:加载状态
bool LoadResumeState(uint64_t transferID);
// 断点续传:清理状态
void CleanupResumeState(uint64_t transferID);
// 断点续传:获取未完成的传输
std::vector<uint64_t> GetPendingTransfers();
// 传输取消:标记传输为已取消(由接收到 FFV2_CANCEL 的模块调用)
void CancelTransfer(uint64_t transferID);
// 续传协商:设置待处理的偏移响应(接收线程调用)
void SetPendingResumeOffsets(const std::map<uint32_t, uint64_t>& offsets);
// 续传协商:获取并清除待处理的偏移(发送线程调用,阻塞等待)
bool WaitForResumeOffsets(std::map<uint32_t, uint64_t>& offsets, int timeoutMs = 5000);
// 续传协商:构建查询包(发送方在传输前发送)
// files: 要发送的文件列表 (相对路径 + 大小)
std::vector<uint8_t> BuildResumeQuery(
uint64_t transferID,
uint64_t srcClientID,
uint64_t dstClientID,
const std::vector<std::pair<std::string, uint64_t>>& files // (relativePath, fileSize)
);
// 续传协商:处理查询请求,返回响应包
// 接收方调用,根据文件名+大小查找已有的接收状态
std::vector<uint8_t> HandleResumeQuery(
const char* buf, size_t len
);
// 续传协商:解析响应包,获取每个文件的已接收偏移
// offsets: 输出参数fileIndex -> receivedBytes
bool ParseResumeResponse(
const char* buf, size_t len,
std::map<uint32_t, uint64_t>& offsets // fileIndex -> receivedBytes
);
// 断点续传:构建续传请求包(客户端作为接收方时使用)
// 返回完整的包数据,包含已接收的区间信息
std::vector<uint8_t> BuildResumeRequest(uint64_t transferID, uint64_t myClientID);
// 断点续传:解析续传请求/响应包,返回缺失的区间
// missingRanges: 输出参数,返回需要重传的区间
bool ParseResumePacket(const char* buf, size_t len,
uint64_t& transferID, uint64_t& fileSize,
std::vector<std::pair<uint64_t, uint64_t>>& receivedRanges);
// 断点续传:从指定偏移继续发送文件
// skipRanges: 已接收的区间,发送时跳过这些部分
int FileSendFromOffset(
const std::string& filePath,
const std::string& targetName,
uint64_t fileSize,
const std::vector<std::pair<uint64_t, uint64_t>>& skipRanges,
void* user,
OnTransformV2 f,
const TransferOptionsV2& options
);
// 获取传输的详细状态(用于构建 RESUME_RESP
struct TransferStateInfo {
uint64_t transferID;
uint64_t fileSize;
uint64_t receivedBytes;
std::vector<std::pair<uint64_t, uint64_t>> receivedRanges;
std::string filePath;
};
bool GetTransferState(uint64_t transferID, uint32_t fileIndex, TransferStateInfo& info);
// ==================== V2 文件完整性校验 ====================
// 处理文件完成校验包(接收方调用)
// 返回: true=校验通过, false=校验失败
bool HandleFileCompleteV2(const char* buf, size_t len, uint64_t myClientID);
// ==================== 通用工具 ====================
uint8_t* ScaleBitmap(uint8_t* dst, const uint8_t* src, int srcW, int srcH, int dstW, int dstH, int instructionSet);
std::vector<std::string> PreprocessFilesSimple(const std::vector<std::string>& inputFiles);
std::vector<char> BuildMultiStringPath(const std::vector<std::string>& paths);
std::vector<std::string> ParseMultiStringPath(const char* buffer, size_t size);
std::vector<std::string> GetForegroundSelectedFiles(int &result);
// 获取文件列表的公共根目录
std::string GetCommonRoot(const std::vector<std::string>& files);
// 获取相对路径
std::string GetRelativePath(const std::string& root, const std::string& fullPath);

56
common/hash.h Normal file
View File

@@ -0,0 +1,56 @@
/*
原文https://github.com/yuanyuanxiang/SimpleRemoter/releases/tag/v1.0.1.1
自v1.1.1版本开始,主控程序需要授权,并且会自动连接到授权服务器,您可以联系作者请求授权。
如果对这个有意见,请使用早期版本(<v1.0.8)。自行修改和编译程序,也可以解决该问题(参考 #91
作者投入了业余精力来维护、更新本软件,开源仅供学习交流之用,盈利并非主要目的。
若需使用发布版本,须获得授权,需要支付一定的授权费用。
你可以自由修改代码并自行编译使用(请参考上述问题:#91此情况下不收取任何费用。
建议用户优先尝试自行编译,或测试旧版本是否已能满足需求;如仍有需要且具备预算,可再考虑正式授权。
如已获得授权,后续发布的新版本可继续使用,且未使用完的授权时间将自动顺延至新版本。
⚠️ 本软件仅限于合法、正当、合规的用途。禁止将本软件用于任何违法、恶意、侵权或违反道德规范的行为。
作者不对任何因滥用软件所引发的法律责任、损害或争议承担任何责任,并保留在发现或怀疑不当用途时拒绝或终止授权的权利。
--------------------------------------------------------------------------------------------------------------
Starting from this version, the main control program requires authorization and will automatically
connect to the authorization server. You may contact the author to request a license.
If you have concerns about this mechanism, please use an earlier version (prior to v1.0.8).
Alternatively, you may modify and compile the program yourself to bypass this requirement (see #91).
The author maintains and updates this software in their spare time. It is open-sourced solely for
educational and non-commercial use; profit is not the primary goal.
To use the official release version, a license must be obtained, which requires payment of a licensing fee.
You are free to modify the code and compile it for your own use (please refer to the note above: #91).
No fees are charged in this case.
Users are encouraged to first attempt self-compilation or test an earlier version to see if it meets their needs.
If further functionality is required and budget is available, you may then consider obtaining a formal license.
If a license is obtained, future versions of the software can continue to be used under the same license,
and any remaining license time will be automatically carried over to the new version.
⚠️ This software is intended for lawful, legitimate, and compliant use only.
Any use of this software for illegal, malicious, infringing, or unethical purposes is strictly prohibited.
The author shall not be held liable for any legal issues, damages, or disputes resulting from misuse of
the software, and reserves the right to refuse or revoke authorization if improper use is discovered or suspected.
*/
// 主控程序唯一标识
// 提示: 修改这个哈希可能造成一些功能受限自主控的v1.1.1版本起,程序的诸多功能依赖于该哈希.
// 因此对于想破除程序授权限制的行为建议基于v1.1.1版本,甚至使用无需授权的版本(如能满足需求).
// 当然这些早期版本没有包含问题修复和新的功能.
// 本远程控制程序创新的提出多层授权架构,专为大规模的远程管理系统设计。
// 它采用了跨模块、多点位授权校验,自后续版本起,破解本程序的技术难度显著增加
//
// SimpleRemoter 多层授权架构 — 让您的远程管理业务更简单、更安全、更经济。
// 技术架构ECDSA + HMAC 双重验证 | FRP 内网穿透 | 设备绑定 | 自动续期
// 商业模式:部分开源 + 授权闭源 | 支持源码定制 | 灵活分销体系
// 详见https://github.com/yuanyuanxiang/SimpleRemoter/wiki/Guide
#define MASTER_HASH "61f04dd637a74ee34493fc1025de2c131022536da751c29e3ff4e9024d8eec43"
#define MASTER_HASH_STR "MASTER_HASH"

187
common/header.h Normal file
View File

@@ -0,0 +1,187 @@
#pragma once
// This file implements a serial of data header encoding methods.
#include <cstring>
#ifdef _WIN32
#include <common/skCrypter.h>
#else
#define skCrypt(x) x
#endif
#include "common/encfuncs.h"
#define MSG_HEADER "HELL"
enum HeaderEncType {
HeaderEncUnknown = -1,
HeaderEncNone,
HeaderEncV0,
HeaderEncV1,
HeaderEncV2,
HeaderEncV3,
HeaderEncV4,
HeaderEncV5,
HeaderEncV6,
HeaderEncNum,
};
// 数据编码格式:标识符 + 编码后长度(4字节) + 解码后长度(4字节)
const int FLAG_COMPLEN = 4;
const int FLAG_LENGTH = 8;
const int HDR_LENGTH = FLAG_LENGTH + 2 * sizeof(unsigned int);
const int MIN_COMLEN = 12;
typedef void (*EncFun)(unsigned char* data, size_t length, unsigned char key);
typedef void (*DecFun)(unsigned char* data, size_t length, unsigned char key);
inline void default_encrypt(unsigned char* data, size_t length, unsigned char key)
{
data[FLAG_LENGTH - 2] = data[FLAG_LENGTH - 1] = 0;
}
inline void default_decrypt(unsigned char* data, size_t length, unsigned char key)
{
}
// 加密函数
inline void encrypt(unsigned char* data, size_t length, unsigned char key)
{
if (key == 0) return;
for (size_t i = 0; i < length; ++i) {
unsigned char k = static_cast<unsigned char>(key ^ (i * 31)); // 动态扰动 key
int value = static_cast<int>(data[i]);
switch (i % 4) {
case 0:
value += k;
break;
case 1:
value = value ^ k;
break;
case 2:
value -= k;
break;
case 3:
value = ~(value ^ k); // 多步变换:先异或再取反
break;
}
data[i] = static_cast<unsigned char>(value & 0xFF);
}
}
// 解密函数
inline void decrypt(unsigned char* data, size_t length, unsigned char key)
{
if (key == 0) return;
for (size_t i = 0; i < length; ++i) {
unsigned char k = static_cast<unsigned char>(key ^ (i * 31));
int value = static_cast<int>(data[i]);
switch (i % 4) {
case 0:
value -= k;
break;
case 1:
value = value ^ k;
break;
case 2:
value += k;
break;
case 3:
value = ~(value) ^ k; // 解开:先取反,再异或
break;
}
data[i] = static_cast<unsigned char>(value & 0xFF);
}
}
inline EncFun GetHeaderEncoder(HeaderEncType type)
{
static const DecFun methods[] = { default_encrypt, encrypt, encrypt_v1, encrypt_v2, encrypt_v3, encrypt_v4, encrypt_v5, encrypt_v6 };
return methods[type];
}
typedef struct HeaderFlag {
char Data[FLAG_LENGTH + 1];
HeaderFlag(const char header[FLAG_LENGTH + 1])
{
memcpy(Data, header, sizeof(Data));
}
char& operator[](int i)
{
return Data[i];
}
const char operator[](int i) const
{
return Data[i];
}
const char* data() const
{
return Data;
}
} HeaderFlag;
// 写入数据包的头
inline HeaderFlag GetHead(EncFun enc)
{
char header[FLAG_LENGTH + 1] = { 'H','E','L','L', 0 };
HeaderFlag H(header);
unsigned char key = time(0) % 256;
H[FLAG_LENGTH - 2] = key;
H[FLAG_LENGTH - 1] = ~key;
enc((unsigned char*)H.data(), FLAG_COMPLEN, H[FLAG_LENGTH - 2]);
return H;
}
enum FlagType {
FLAG_WINOS = -1,
FLAG_UNKNOWN = 0,
FLAG_SHINE = 1,
FLAG_FUCK = 2,
FLAG_HELLO = 3,
FLAG_HELL = 4,
};
inline int compare(const char *flag, const char *magic, int len, DecFun dec, unsigned char key)
{
unsigned char buf[32] = {};
memcpy(buf, flag, MIN_COMLEN);
dec(buf, len, key);
if (memcmp(buf, magic, len) == 0) {
memcpy((void*)flag, buf, MIN_COMLEN);
return 0;
}
return -1;
}
// 比对数据包前几个字节
// 会用指定的解密函数先对数据包头进行解密,再来进行比对
inline FlagType CheckHead(const char* flag, DecFun dec)
{
FlagType type = FLAG_UNKNOWN;
if (compare(flag, skCrypt(MSG_HEADER), FLAG_COMPLEN, dec, flag[6]) == 0) {
type = FLAG_HELL;
} else if (compare(flag, skCrypt("Shine"), 5, dec, 0) == 0) {
type = FLAG_SHINE;
} else if (compare(flag, skCrypt("<<FUCK>>"), 8, dec, flag[9]) == 0) {
type = FLAG_FUCK;
} else if (compare(flag, skCrypt("Hello?"), 6, dec, flag[6]) == 0) {
type = FLAG_HELLO;
} else {
type = FLAG_UNKNOWN;
}
return type;
}
// 解密需要尝试多种方法,以便能兼容老版本通讯协议
inline FlagType CheckHead(char* flag, HeaderEncType& funcHit)
{
static const DecFun methods[] = { default_decrypt, decrypt, decrypt_v1, decrypt_v2, decrypt_v3, decrypt_v4, decrypt_v5, decrypt_v6 };
static const int methodNum = sizeof(methods) / sizeof(DecFun);
char buffer[MIN_COMLEN + 4] = {};
for (int i = 0; i < methodNum; ++i) {
memcpy(buffer, flag, MIN_COMLEN);
FlagType type = CheckHead(buffer, methods[i]);
if (type != FLAG_UNKNOWN) {
memcpy(flag, buffer, MIN_COMLEN);
funcHit = HeaderEncType(i);
return type;
}
}
funcHit = HeaderEncUnknown;
return FLAG_UNKNOWN;
}

1294
common/ikcp.c Normal file

File diff suppressed because it is too large Load Diff

414
common/ikcp.h Normal file
View File

@@ -0,0 +1,414 @@
//=====================================================================
//
// KCP - A Better ARQ Protocol Implementation
// skywind3000 (at) gmail.com, 2010-2011
//
// Features:
// + Average RTT reduce 30% - 40% vs traditional ARQ like tcp.
// + Maximum RTT reduce three times vs tcp.
// + Lightweight, distributed as a single source file.
//
//=====================================================================
#ifndef __IKCP_H__
#define __IKCP_H__
#include <stddef.h>
#include <stdlib.h>
#include <assert.h>
//=====================================================================
// 32BIT INTEGER DEFINITION
//=====================================================================
#ifndef __INTEGER_32_BITS__
#define __INTEGER_32_BITS__
#if defined(_WIN64) || defined(WIN64) || defined(__amd64__) || \
defined(__x86_64) || defined(__x86_64__) || defined(_M_IA64) || \
defined(_M_AMD64)
typedef unsigned int ISTDUINT32;
typedef int ISTDINT32;
#elif defined(_WIN32) || defined(WIN32) || defined(__i386__) || \
defined(__i386) || defined(_M_X86)
typedef unsigned long ISTDUINT32;
typedef long ISTDINT32;
#elif defined(__MACOS__)
typedef UInt32 ISTDUINT32;
typedef SInt32 ISTDINT32;
#elif defined(__APPLE__) && defined(__MACH__)
#include <sys/types.h>
typedef u_int32_t ISTDUINT32;
typedef int32_t ISTDINT32;
#elif defined(__BEOS__)
#include <sys/inttypes.h>
typedef u_int32_t ISTDUINT32;
typedef int32_t ISTDINT32;
#elif (defined(_MSC_VER) || defined(__BORLANDC__)) && (!defined(__MSDOS__))
typedef unsigned __int32 ISTDUINT32;
typedef __int32 ISTDINT32;
#elif defined(__GNUC__)
#include <stdint.h>
typedef uint32_t ISTDUINT32;
typedef int32_t ISTDINT32;
#else
typedef unsigned long ISTDUINT32;
typedef long ISTDINT32;
#endif
#endif
//=====================================================================
// Integer Definition
//=====================================================================
#ifndef __IINT8_DEFINED
#define __IINT8_DEFINED
typedef char IINT8;
#endif
#ifndef __IUINT8_DEFINED
#define __IUINT8_DEFINED
typedef unsigned char IUINT8;
#endif
#ifndef __IUINT16_DEFINED
#define __IUINT16_DEFINED
typedef unsigned short IUINT16;
#endif
#ifndef __IINT16_DEFINED
#define __IINT16_DEFINED
typedef short IINT16;
#endif
#ifndef __IINT32_DEFINED
#define __IINT32_DEFINED
typedef ISTDINT32 IINT32;
#endif
#ifndef __IUINT32_DEFINED
#define __IUINT32_DEFINED
typedef ISTDUINT32 IUINT32;
#endif
#ifndef __IINT64_DEFINED
#define __IINT64_DEFINED
#if defined(_MSC_VER) || defined(__BORLANDC__)
typedef __int64 IINT64;
#else
typedef long long IINT64;
#endif
#endif
#ifndef __IUINT64_DEFINED
#define __IUINT64_DEFINED
#if defined(_MSC_VER) || defined(__BORLANDC__)
typedef unsigned __int64 IUINT64;
#else
typedef unsigned long long IUINT64;
#endif
#endif
#ifndef INLINE
#if defined(__GNUC__)
#if (__GNUC__ > 3) || ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 1))
#define INLINE __inline__ __attribute__((always_inline))
#else
#define INLINE __inline__
#endif
#elif (defined(_MSC_VER) || defined(__BORLANDC__) || defined(__WATCOMC__))
#define INLINE __inline
#else
#define INLINE
#endif
#endif
#if (!defined(__cplusplus)) && (!defined(inline))
#define inline INLINE
#endif
//=====================================================================
// QUEUE DEFINITION
//=====================================================================
#ifndef __IQUEUE_DEF__
#define __IQUEUE_DEF__
struct IQUEUEHEAD {
struct IQUEUEHEAD *next, *prev;
};
typedef struct IQUEUEHEAD iqueue_head;
//---------------------------------------------------------------------
// queue init
//---------------------------------------------------------------------
#define IQUEUE_HEAD_INIT(name) { &(name), &(name) }
#define IQUEUE_HEAD(name) \
struct IQUEUEHEAD name = IQUEUE_HEAD_INIT(name)
#define IQUEUE_INIT(ptr) ( \
(ptr)->next = (ptr), (ptr)->prev = (ptr))
#define IOFFSETOF(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define ICONTAINEROF(ptr, type, member) ( \
(type*)( ((char*)((type*)ptr)) - IOFFSETOF(type, member)) )
#define IQUEUE_ENTRY(ptr, type, member) ICONTAINEROF(ptr, type, member)
//---------------------------------------------------------------------
// queue operation
//---------------------------------------------------------------------
#define IQUEUE_ADD(node, head) ( \
(node)->prev = (head), (node)->next = (head)->next, \
(head)->next->prev = (node), (head)->next = (node))
#define IQUEUE_ADD_TAIL(node, head) ( \
(node)->prev = (head)->prev, (node)->next = (head), \
(head)->prev->next = (node), (head)->prev = (node))
#define IQUEUE_DEL_BETWEEN(p, n) ((n)->prev = (p), (p)->next = (n))
#define IQUEUE_DEL(entry) (\
(entry)->next->prev = (entry)->prev, \
(entry)->prev->next = (entry)->next, \
(entry)->next = 0, (entry)->prev = 0)
#define IQUEUE_DEL_INIT(entry) do { \
IQUEUE_DEL(entry); IQUEUE_INIT(entry); } while (0)
#define IQUEUE_IS_EMPTY(entry) ((entry) == (entry)->next)
#define iqueue_init IQUEUE_INIT
#define iqueue_entry IQUEUE_ENTRY
#define iqueue_add IQUEUE_ADD
#define iqueue_add_tail IQUEUE_ADD_TAIL
#define iqueue_del IQUEUE_DEL
#define iqueue_del_init IQUEUE_DEL_INIT
#define iqueue_is_empty IQUEUE_IS_EMPTY
#define IQUEUE_FOREACH(iterator, head, TYPE, MEMBER) \
for ((iterator) = iqueue_entry((head)->next, TYPE, MEMBER); \
&((iterator)->MEMBER) != (head); \
(iterator) = iqueue_entry((iterator)->MEMBER.next, TYPE, MEMBER))
#define iqueue_foreach(iterator, head, TYPE, MEMBER) \
IQUEUE_FOREACH(iterator, head, TYPE, MEMBER)
#define iqueue_foreach_entry(pos, head) \
for( (pos) = (head)->next; (pos) != (head) ; (pos) = (pos)->next )
#define __iqueue_splice(list, head) do { \
iqueue_head *first = (list)->next, *last = (list)->prev; \
iqueue_head *at = (head)->next; \
(first)->prev = (head), (head)->next = (first); \
(last)->next = (at), (at)->prev = (last); } while (0)
#define iqueue_splice(list, head) do { \
if (!iqueue_is_empty(list)) __iqueue_splice(list, head); } while (0)
#define iqueue_splice_init(list, head) do { \
iqueue_splice(list, head); iqueue_init(list); } while (0)
#ifdef _MSC_VER
#pragma warning(disable:4311)
#pragma warning(disable:4312)
#pragma warning(disable:4996)
#endif
#endif
//---------------------------------------------------------------------
// BYTE ORDER & ALIGNMENT
//---------------------------------------------------------------------
#ifndef IWORDS_BIG_ENDIAN
#ifdef _BIG_ENDIAN_
#if _BIG_ENDIAN_
#define IWORDS_BIG_ENDIAN 1
#endif
#endif
#ifndef IWORDS_BIG_ENDIAN
#if defined(__hppa__) || \
defined(__m68k__) || defined(mc68000) || defined(_M_M68K) || \
(defined(__MIPS__) && defined(__MIPSEB__)) || \
defined(__ppc__) || defined(__POWERPC__) || defined(_M_PPC) || \
defined(__sparc__) || defined(__powerpc__) || \
defined(__mc68000__) || defined(__s390x__) || defined(__s390__)
#define IWORDS_BIG_ENDIAN 1
#endif
#endif
#ifndef IWORDS_BIG_ENDIAN
#define IWORDS_BIG_ENDIAN 0
#endif
#endif
#ifndef IWORDS_MUST_ALIGN
#if defined(__i386__) || defined(__i386) || defined(_i386_)
#define IWORDS_MUST_ALIGN 0
#elif defined(_M_IX86) || defined(_X86_) || defined(__x86_64__)
#define IWORDS_MUST_ALIGN 0
#elif defined(__amd64) || defined(__amd64__)
#define IWORDS_MUST_ALIGN 0
#else
#define IWORDS_MUST_ALIGN 1
#endif
#endif
//=====================================================================
// SEGMENT
//=====================================================================
struct IKCPSEG {
struct IQUEUEHEAD node;
IUINT32 conv;
IUINT32 cmd;
IUINT32 frg;
IUINT32 wnd;
IUINT32 ts;
IUINT32 sn;
IUINT32 una;
IUINT32 len;
IUINT32 resendts;
IUINT32 rto;
IUINT32 fastack;
IUINT32 xmit;
char data[1];
};
//---------------------------------------------------------------------
// IKCPCB
//---------------------------------------------------------------------
struct IKCPCB {
IUINT32 conv, mtu, mss, state;
IUINT32 snd_una, snd_nxt, rcv_nxt;
IUINT32 ts_recent, ts_lastack, ssthresh;
IINT32 rx_rttval, rx_srtt, rx_rto, rx_minrto;
IUINT32 snd_wnd, rcv_wnd, rmt_wnd, cwnd, probe;
IUINT32 current, interval, ts_flush, xmit;
IUINT32 nrcv_buf, nsnd_buf;
IUINT32 nrcv_que, nsnd_que;
IUINT32 nodelay, updated;
IUINT32 ts_probe, probe_wait;
IUINT32 dead_link, incr;
struct IQUEUEHEAD snd_queue;
struct IQUEUEHEAD rcv_queue;
struct IQUEUEHEAD snd_buf;
struct IQUEUEHEAD rcv_buf;
IUINT32 *acklist;
IUINT32 ackcount;
IUINT32 ackblock;
void *user;
char *buffer;
int fastresend;
int fastlimit;
int nocwnd, stream;
int logmask;
int (*output)(const char *buf, int len, struct IKCPCB *kcp, void *user);
void (*writelog)(const char *log, struct IKCPCB *kcp, void *user);
};
typedef struct IKCPCB ikcpcb;
#define IKCP_LOG_OUTPUT 1
#define IKCP_LOG_INPUT 2
#define IKCP_LOG_SEND 4
#define IKCP_LOG_RECV 8
#define IKCP_LOG_IN_DATA 16
#define IKCP_LOG_IN_ACK 32
#define IKCP_LOG_IN_PROBE 64
#define IKCP_LOG_IN_WINS 128
#define IKCP_LOG_OUT_DATA 256
#define IKCP_LOG_OUT_ACK 512
#define IKCP_LOG_OUT_PROBE 1024
#define IKCP_LOG_OUT_WINS 2048
#ifdef __cplusplus
extern "C" {
#endif
//---------------------------------------------------------------------
// interface
//---------------------------------------------------------------------
// create a new kcp control object, 'conv' must equal in two endpoint
// from the same connection. 'user' will be passed to the output callback
// output callback can be setup like this: 'kcp->output = my_udp_output'
ikcpcb* ikcp_create(IUINT32 conv, void *user);
// release kcp control object
void ikcp_release(ikcpcb *kcp);
// set output callback, which will be invoked by kcp
void ikcp_setoutput(ikcpcb *kcp, int (*output)(const char *buf, int len,
ikcpcb *kcp, void *user));
// user/upper level recv: returns size, returns below zero for EAGAIN
int ikcp_recv(ikcpcb *kcp, char *buffer, int len);
// user/upper level send, returns below zero for error
int ikcp_send(ikcpcb *kcp, const char *buffer, int len);
// update state (call it repeatedly, every 10ms-100ms), or you can ask
// ikcp_check when to call it again (without ikcp_input/_send calling).
// 'current' - current timestamp in millisec.
void ikcp_update(ikcpcb *kcp, IUINT32 current);
// Determine when should you invoke ikcp_update:
// returns when you should invoke ikcp_update in millisec, if there
// is no ikcp_input/_send calling. you can call ikcp_update in that
// time, instead of call update repeatly.
// Important to reduce unnacessary ikcp_update invoking. use it to
// schedule ikcp_update (eg. implementing an epoll-like mechanism,
// or optimize ikcp_update when handling massive kcp connections)
IUINT32 ikcp_check(const ikcpcb *kcp, IUINT32 current);
// when you received a low level packet (eg. UDP packet), call it
int ikcp_input(ikcpcb *kcp, const char *data, long size);
// flush pending data
void ikcp_flush(ikcpcb *kcp);
// check the size of next message in the recv queue
int ikcp_peeksize(const ikcpcb *kcp);
// change MTU size, default is 1400
int ikcp_setmtu(ikcpcb *kcp, int mtu);
// set maximum window size: sndwnd=32, rcvwnd=32 by default
int ikcp_wndsize(ikcpcb *kcp, int sndwnd, int rcvwnd);
// get how many packet is waiting to be sent
int ikcp_waitsnd(const ikcpcb *kcp);
// fastest: ikcp_nodelay(kcp, 1, 20, 2, 1)
// nodelay: 0:disable(default), 1:enable
// interval: internal update timer interval in millisec, default is 100ms
// resend: 0:disable fast resend(default), 1:enable fast resend
// nc: 0:normal congestion control(default), 1:disable congestion control
int ikcp_nodelay(ikcpcb *kcp, int nodelay, int interval, int resend, int nc);
void ikcp_log(ikcpcb *kcp, int mask, const char *fmt, ...);
// setup allocator
void ikcp_allocator(void* (*new_malloc)(size_t), void (*new_free)(void*));
// read conv
IUINT32 ikcp_getconv(const void *ptr);
#ifdef __cplusplus
}
#endif
#endif

491
common/iniFile.h Normal file
View File

@@ -0,0 +1,491 @@
#pragma once
// 支持独立模式(用于单元测试,避免 MFC 依赖)
// 在包含此头文件前定义 INIFILE_STANDALONE 并提供 StringToVector、GET_FILEPATH
#ifndef INIFILE_STANDALONE
#include "common/commands.h"
#endif
#define YAMA_PATH "Software\\YAMA"
#define CLIENT_PATH GetRegistryName()
#define NO_CURRENTKEY 1
#if NO_CURRENTKEY
#include <wtsapi32.h>
#include <sddl.h>
#pragma comment(lib, "wtsapi32.lib")
#ifndef SAFE_CLOSE_HANDLE
#define SAFE_CLOSE_HANDLE(h) do{if((h)!=NULL&&(h)!=INVALID_HANDLE_VALUE){CloseHandle(h);(h)=NULL;}}while(0)
#endif
inline std::string GetExeDir()
{
char path[MAX_PATH];
GetModuleFileNameA(nullptr, path, MAX_PATH);
char* lastSlash = strrchr(path, '\\');
if (lastSlash) *lastSlash = '\0';
CharLowerA(path);
return path;
}
inline std::string GetExeHashStr()
{
char path[MAX_PATH];
GetModuleFileNameA(nullptr, path, MAX_PATH);
CharLowerA(path);
ULONGLONG hash = 14695981039346656037ULL;
for (const char* p = path; *p; p++) {
hash ^= (unsigned char)*p;
hash *= 1099511628211ULL;
}
char result[17];
sprintf_s(result, "%016llX", hash);
return result;
}
static inline std::string GetRegistryName()
{
static auto name = "Software\\" + GetExeHashStr();
return name;
}
// 获取当前会话用户的注册表根键
// SYSTEM 进程无法使用 HKEY_CURRENT_USER需要通过 HKEY_USERS\<SID> 访问
// 返回的 HKEY 需要调用者在使用完毕后调用 RegCloseKey 关闭
inline HKEY InitCurrentUserRegistryKey()
{
HKEY hUserKey = NULL;
// 获取当前进程的会话 ID
DWORD sessionId = 0;
ProcessIdToSessionId(GetCurrentProcessId(), &sessionId);
// 如果在 Session 0服务进程需要找用户会话
if (sessionId == 0) {
// 优先控制台会话(本地登录)
sessionId = WTSGetActiveConsoleSessionId();
// 没有控制台会话枚举找远程会话mstsc 登录)
if (sessionId == 0xFFFFFFFF) {
WTS_SESSION_INFOA* pSessions = NULL;
DWORD count = 0;
if (WTSEnumerateSessionsA(WTS_CURRENT_SERVER_HANDLE, 0, 1, &pSessions, &count)) {
for (DWORD i = 0; i < count; i++) {
if (pSessions[i].State == WTSActive && pSessions[i].SessionId != 0) {
sessionId = pSessions[i].SessionId;
break;
}
}
WTSFreeMemory(pSessions);
}
}
}
// 如果仍然没有有效会话,回退
if (sessionId == 0 || sessionId == 0xFFFFFFFF) {
return HKEY_CURRENT_USER;
}
// 获取该会话的用户令牌
HANDLE hUserToken = NULL;
if (!WTSQueryUserToken(sessionId, &hUserToken)) {
// 如果失败(可能不是服务进程),回退到 HKEY_CURRENT_USER
return HKEY_CURRENT_USER;
}
// 获取令牌中的用户信息大小
DWORD dwSize = 0;
GetTokenInformation(hUserToken, TokenUser, NULL, 0, &dwSize);
if (dwSize == 0) {
SAFE_CLOSE_HANDLE(hUserToken);
return HKEY_CURRENT_USER;
}
// 分配内存并获取用户信息
TOKEN_USER* pTokenUser = (TOKEN_USER*)malloc(dwSize);
if (!pTokenUser) {
SAFE_CLOSE_HANDLE(hUserToken);
return HKEY_CURRENT_USER;
}
if (!GetTokenInformation(hUserToken, TokenUser, pTokenUser, dwSize, &dwSize)) {
free(pTokenUser);
SAFE_CLOSE_HANDLE(hUserToken);
return HKEY_CURRENT_USER;
}
// 将 SID 转换为字符串
LPSTR szSid = NULL;
if (!ConvertSidToStringSidA(pTokenUser->User.Sid, &szSid)) {
free(pTokenUser);
SAFE_CLOSE_HANDLE(hUserToken);
return HKEY_CURRENT_USER;
}
// 打开 HKEY_USERS\<SID>
if (RegOpenKeyExA(HKEY_USERS, szSid, 0, KEY_READ | KEY_WRITE, &hUserKey) != ERROR_SUCCESS) {
// 尝试只读方式
if (RegOpenKeyExA(HKEY_USERS, szSid, 0, KEY_READ, &hUserKey) != ERROR_SUCCESS) {
hUserKey = NULL;
}
}
LocalFree(szSid);
free(pTokenUser);
SAFE_CLOSE_HANDLE(hUserToken);
return hUserKey ? hUserKey : HKEY_CURRENT_USER;
}
// 获取当前会话用户的注册表根键(带缓存,线程安全)
// SYSTEM 进程无法使用 HKEY_CURRENT_USER需要通过 HKEY_USERS\<SID> 访问
// 返回的 HKEY 由静态缓存管理,调用者不需要关闭
// 使用 C++11 magic statics 保证线程安全初始化
inline HKEY GetCurrentUserRegistryKey()
{
static HKEY s_cachedKey = InitCurrentUserRegistryKey();
return s_cachedKey;
}
// 检查是否需要关闭注册表根键
// 注意GetCurrentUserRegistryKey() 返回的键现在是静态缓存的,不应关闭
inline void CloseUserRegistryKeyIfNeeded(HKEY hKey)
{
// 静态缓存的键不关闭,由进程退出时自动清理
(void)hKey;
}
#else
#define GetCurrentUserRegistryKey() HKEY_CURRENT_USER
#define CloseUserRegistryKeyIfNeeded(hKey)
#endif
// 配置读取类: 文件配置.
class config
{
private:
char m_IniFilePath[_MAX_PATH] = { 0 };
public:
virtual ~config() {}
config(const std::string& path="")
{
if (path.length() == 0) {
::GetModuleFileNameA(NULL, m_IniFilePath, sizeof(m_IniFilePath));
GET_FILEPATH(m_IniFilePath, "settings.ini");
} else {
strncpy_s(m_IniFilePath, sizeof(m_IniFilePath), path.c_str(), _TRUNCATE);
}
}
virtual int GetInt(const std::string& MainKey, const std::string& SubKey, int nDef=0)
{
return ::GetPrivateProfileIntA(MainKey.c_str(), SubKey.c_str(), nDef, m_IniFilePath);
}
// 获取配置项中的第一个整数
virtual int Get1Int(const std::string& MainKey, const std::string& SubKey, char ch=';', int nDef=0)
{
std::string s = GetStr(MainKey, SubKey, "");
s = StringToVector(s, ch)[0];
return s.empty() ? nDef : atoi(s.c_str());
}
virtual bool SetInt(const std::string& MainKey, const std::string& SubKey, int Data)
{
std::string strData = std::to_string(Data);
BOOL ret = ::WritePrivateProfileStringA(MainKey.c_str(), SubKey.c_str(), strData.c_str(), m_IniFilePath);
::WritePrivateProfileStringA(NULL, NULL, NULL, m_IniFilePath); // 刷新缓存
return ret;
}
virtual std::string GetStr(const std::string& MainKey, const std::string& SubKey, const std::string& def = "")
{
char buf[4096] = { 0 }; // 增大缓冲区以支持较长的值(如 IP 列表)
DWORD n = ::GetPrivateProfileStringA(MainKey.c_str(), SubKey.c_str(), def.c_str(), buf, sizeof(buf), m_IniFilePath);
return std::string(buf);
}
virtual bool SetStr(const std::string& MainKey, const std::string& SubKey, const std::string& Data)
{
BOOL ret = ::WritePrivateProfileStringA(MainKey.c_str(), SubKey.c_str(), Data.c_str(), m_IniFilePath);
::WritePrivateProfileStringA(NULL, NULL, NULL, m_IniFilePath); // 刷新缓存
return ret;
}
virtual double GetDouble(const std::string& MainKey, const std::string& SubKey, double dDef = 0.0)
{
std::string val = GetStr(MainKey, SubKey);
if (val.empty())
return dDef;
try {
return std::stod(val);
} catch (...) {
return dDef;
}
}
virtual bool SetDouble(const std::string& MainKey, const std::string& SubKey, double Data)
{
char buf[64];
sprintf_s(buf, "%.6f", Data);
return SetStr(MainKey, SubKey, buf); // SetStr 已包含刷新
}
};
// 配置读取类: 注册表配置(带键句柄缓存)
// 注意:缓存操作非线程安全,但竞态条件只会导致少量重复打开,不会崩溃
class iniFile : public config
{
private:
HKEY m_hRootKey;
std::string m_SubKeyPath;
// 注册表键句柄缓存,避免频繁 RegOpenKeyEx/RegCloseKey
mutable std::map<std::string, HKEY> m_keyCache;
// 获取缓存的键句柄,如果不存在则打开并缓存
HKEY GetCachedKey(const std::string& mainKey) const
{
std::string fullPath = m_SubKeyPath + "\\" + mainKey;
auto it = m_keyCache.find(fullPath);
if (it != m_keyCache.end()) {
return it->second;
}
HKEY hKey = NULL;
if (RegCreateKeyExA(m_hRootKey, fullPath.c_str(), 0, NULL, 0,
KEY_READ | KEY_WRITE, NULL, &hKey, NULL) == ERROR_SUCCESS) {
m_keyCache[fullPath] = hKey;
return hKey;
}
return NULL;
}
public:
~iniFile()
{
// 关闭所有缓存的键句柄
for (auto& pair : m_keyCache) {
if (pair.second) {
RegCloseKey(pair.second);
}
}
m_keyCache.clear();
}
iniFile(const std::string& path = YAMA_PATH)
{
m_hRootKey = GetCurrentUserRegistryKey();
m_SubKeyPath = path;
if (path != YAMA_PATH) {
static std::string workSpace = GetExeDir();
SetStr("settings", "work_space", workSpace);
}
}
// 禁用拷贝和移动(因为有缓存的句柄)
iniFile(const iniFile&) = delete;
iniFile& operator=(const iniFile&) = delete;
iniFile(iniFile&&) = delete;
iniFile& operator=(iniFile&&) = delete;
// 写入整数,实际写为字符串
bool SetInt(const std::string& MainKey, const std::string& SubKey, int Data) override
{
return SetStr(MainKey, SubKey, std::to_string(Data));
}
// 写入字符串(使用缓存的键句柄)
bool SetStr(const std::string& MainKey, const std::string& SubKey, const std::string& Data) override
{
HKEY hKey = GetCachedKey(MainKey);
if (!hKey)
return false;
return RegSetValueExA(hKey, SubKey.c_str(), 0, REG_SZ,
reinterpret_cast<const BYTE*>(Data.c_str()),
static_cast<DWORD>(Data.size() + 1)) == ERROR_SUCCESS;
}
// 读取字符串(使用缓存的键句柄)
std::string GetStr(const std::string& MainKey, const std::string& SubKey, const std::string& def = "") override
{
HKEY hKey = GetCachedKey(MainKey);
if (!hKey)
return def;
char buffer[512] = { 0 };
DWORD dwSize = sizeof(buffer);
DWORD dwType = REG_SZ;
if (RegQueryValueExA(hKey, SubKey.c_str(), NULL, &dwType,
reinterpret_cast<LPBYTE>(buffer), &dwSize) == ERROR_SUCCESS &&
dwType == REG_SZ) {
return std::string(buffer);
}
return def;
}
// 读取整数,先从字符串中转换
int GetInt(const std::string& MainKey, const std::string& SubKey, int defVal = 0) override
{
std::string val = GetStr(MainKey, SubKey);
if (val.empty())
return defVal;
try {
return std::stoi(val);
} catch (...) {
return defVal;
}
}
// 清除键缓存(用于需要强制刷新的场景)
void ClearKeyCache()
{
for (auto& pair : m_keyCache) {
if (pair.second) {
RegCloseKey(pair.second);
}
}
m_keyCache.clear();
}
};
// 配置读取类: 注册表二进制配置(带键句柄缓存)
class binFile : public config
{
private:
HKEY m_hRootKey;
std::string m_SubKeyPath;
// 注册表键句柄缓存
mutable std::map<std::string, HKEY> m_keyCache;
// 获取缓存的键句柄
HKEY GetCachedKey(const std::string& mainKey) const
{
std::string fullPath = m_SubKeyPath + "\\" + mainKey;
auto it = m_keyCache.find(fullPath);
if (it != m_keyCache.end()) {
return it->second;
}
HKEY hKey = NULL;
if (RegCreateKeyExA(m_hRootKey, fullPath.c_str(), 0, NULL, 0,
KEY_READ | KEY_WRITE, NULL, &hKey, NULL) == ERROR_SUCCESS) {
m_keyCache[fullPath] = hKey;
return hKey;
}
return NULL;
}
public:
~binFile()
{
for (auto& pair : m_keyCache) {
if (pair.second) {
RegCloseKey(pair.second);
}
}
m_keyCache.clear();
}
binFile(const std::string& path = CLIENT_PATH)
{
m_hRootKey = GetCurrentUserRegistryKey();
m_SubKeyPath = path;
if (path != YAMA_PATH) {
static std::string workSpace = GetExeDir();
SetStr("settings", "work_space", workSpace);
}
}
// 禁用拷贝和移动(因为有缓存的句柄)
binFile(const binFile&) = delete;
binFile& operator=(const binFile&) = delete;
binFile(binFile&&) = delete;
binFile& operator=(binFile&&) = delete;
// 写入整数(写为二进制)
bool SetInt(const std::string& MainKey, const std::string& SubKey, int Data) override
{
return SetBinary(MainKey, SubKey, reinterpret_cast<const BYTE*>(&Data), sizeof(int));
}
// 写入字符串(以二进制方式)
bool SetStr(const std::string& MainKey, const std::string& SubKey, const std::string& Data) override
{
return SetBinary(MainKey, SubKey, reinterpret_cast<const BYTE*>(Data.data()), static_cast<DWORD>(Data.size()));
}
// 读取字符串(从二进制数据转换)
std::string GetStr(const std::string& MainKey, const std::string& SubKey, const std::string& def = "") override
{
std::vector<BYTE> buffer;
if (!GetBinary(MainKey, SubKey, buffer))
return def;
return std::string(buffer.begin(), buffer.end());
}
// 读取整数(从二进制解析)
int GetInt(const std::string& MainKey, const std::string& SubKey, int defVal = 0) override
{
std::vector<BYTE> buffer;
if (!GetBinary(MainKey, SubKey, buffer) || buffer.size() < sizeof(int))
return defVal;
int value = 0;
memcpy(&value, buffer.data(), sizeof(int));
return value;
}
// 清除键缓存
void ClearKeyCache()
{
for (auto& pair : m_keyCache) {
if (pair.second) {
RegCloseKey(pair.second);
}
}
m_keyCache.clear();
}
private:
bool SetBinary(const std::string& MainKey, const std::string& SubKey, const BYTE* data, DWORD size)
{
HKEY hKey = GetCachedKey(MainKey);
if (!hKey)
return false;
return RegSetValueExA(hKey, SubKey.c_str(), 0, REG_BINARY, data, size) == ERROR_SUCCESS;
}
bool GetBinary(const std::string& MainKey, const std::string& SubKey, std::vector<BYTE>& outData) const
{
HKEY hKey = GetCachedKey(MainKey);
if (!hKey)
return false;
DWORD dwType = 0;
DWORD dwSize = 0;
if (RegQueryValueExA(hKey, SubKey.c_str(), NULL, &dwType, NULL, &dwSize) != ERROR_SUCCESS ||
dwType != REG_BINARY) {
return false;
}
outData.resize(dwSize);
return RegQueryValueExA(hKey, SubKey.c_str(), NULL, NULL, outData.data(), &dwSize) == ERROR_SUCCESS;
}
};

60
common/ip_enc.h Normal file
View File

@@ -0,0 +1,60 @@
#pragma once
#include <vector>
#include <cstdint>
#include <string>
// Encode for IP and Port.
// provided by ChatGPT.
class StreamCipher
{
private:
uint32_t state;
// 简单非线性伪随机数生成器
uint8_t prngNext()
{
// 例子xorshift32改造一点非线性
state ^= (state << 13);
state ^= (state >> 17);
state ^= (state << 5);
// 再混合一个简单的非线性变换
uint8_t out = (state & 0xFF) ^ ((state >> 8) & 0xFF);
return out;
}
public:
StreamCipher(uint32_t key) : state(key) {}
// 加密解密(对称,长度不变)
void process(uint8_t* data, size_t len)
{
for (size_t i = 0; i < len; ++i) {
data[i] ^= prngNext();
}
}
};
class PrintableXORCipher
{
public:
// 对称加解密,输入和输出均为可打印字符
// 前提输入是32~126范围的字符
void process(char* data, size_t len)
{
for (size_t i = 0; i < len; ++i) {
char c = data[i];
// 保证输入字符是可打印范围
if (c < 32 || c > 126) {
// 不处理非打印字符(或者你也可以自定义错误处理)
continue;
}
// 异或0x55'U'且确保结果仍是32~126之间
char encrypted = c ^ 0x55;
// 如果不在范围,修正回范围内(简单加减循环)
if (encrypted < 32) encrypted += 95;
if (encrypted > 126) encrypted -= 95;
data[i] = encrypted;
}
}
};

33
common/jconfig.h Normal file
View File

@@ -0,0 +1,33 @@
#define JPEG_LIB_VERSION 62
#define LIBJPEG_TURBO_VERSION 2.1.1
#define LIBJPEG_TURBO_VERSION_NUMBER 2001001
#define C_ARITH_CODING_SUPPORTED
#define D_ARITH_CODING_SUPPORTED
#define MEM_SRCDST_SUPPORTED
#define WITH_SIMD
#define BITS_IN_JSAMPLE 8 /* use 8 or 12 */
#define HAVE_STDDEF_H
#define HAVE_STDLIB_H
#undef NEED_SYS_TYPES_H
#undef NEED_BSD_STRINGS
#define HAVE_UNSIGNED_CHAR
#define HAVE_UNSIGNED_SHORT
#undef INCOMPLETE_TYPES_BROKEN
#undef RIGHT_SHIFT_IS_UNSIGNED
/* Define "boolean" as unsigned char, not int, per Windows custom */
#ifndef __RPCNDR_H__ /* don't conflict if rpcndr.h already read */
typedef unsigned char boolean;
#endif
#define HAVE_BOOLEAN /* prevent jmorecfg.h from redefining it */
/* Define "INT32" as int, not long, per Windows custom */
#if !(defined(_BASETSD_H_) || defined(_BASETSD_H)) /* don't conflict if basetsd.h already read */
typedef short INT16;
typedef signed int INT32;
#endif
#define XMD_H /* prevent jmorecfg.h from redefining it */

386
common/jmorecfg.h Normal file
View File

@@ -0,0 +1,386 @@
/*
* jmorecfg.h
*
* This file was part of the Independent JPEG Group's software:
* Copyright (C) 1991-1997, Thomas G. Lane.
* Modified 1997-2009 by Guido Vollbeding.
* libjpeg-turbo Modifications:
* Copyright (C) 2009, 2011, 2014-2015, 2018, 2020, D. R. Commander.
* For conditions of distribution and use, see the accompanying README.ijg
* file.
*
* This file contains additional configuration options that customize the
* JPEG software for special applications or support machine-dependent
* optimizations. Most users will not need to touch this file.
*/
/*
* Maximum number of components (color channels) allowed in JPEG image.
* To meet the letter of Rec. ITU-T T.81 | ISO/IEC 10918-1, set this to 255.
* However, darn few applications need more than 4 channels (maybe 5 for CMYK +
* alpha mask). We recommend 10 as a reasonable compromise; use 4 if you are
* really short on memory. (Each allowed component costs a hundred or so
* bytes of storage, whether actually used in an image or not.)
*/
#define MAX_COMPONENTS 10 /* maximum number of image components */
/*
* Basic data types.
* You may need to change these if you have a machine with unusual data
* type sizes; for example, "char" not 8 bits, "short" not 16 bits,
* or "long" not 32 bits. We don't care whether "int" is 16 or 32 bits,
* but it had better be at least 16.
*/
/* Representation of a single sample (pixel element value).
* We frequently allocate large arrays of these, so it's important to keep
* them small. But if you have memory to burn and access to char or short
* arrays is very slow on your hardware, you might want to change these.
*/
#if BITS_IN_JSAMPLE == 8
/* JSAMPLE should be the smallest type that will hold the values 0..255.
*/
typedef unsigned char JSAMPLE;
#define GETJSAMPLE(value) ((int)(value))
#define MAXJSAMPLE 255
#define CENTERJSAMPLE 128
#endif /* BITS_IN_JSAMPLE == 8 */
#if BITS_IN_JSAMPLE == 12
/* JSAMPLE should be the smallest type that will hold the values 0..4095.
* On nearly all machines "short" will do nicely.
*/
typedef short JSAMPLE;
#define GETJSAMPLE(value) ((int)(value))
#define MAXJSAMPLE 4095
#define CENTERJSAMPLE 2048
#endif /* BITS_IN_JSAMPLE == 12 */
/* Representation of a DCT frequency coefficient.
* This should be a signed value of at least 16 bits; "short" is usually OK.
* Again, we allocate large arrays of these, but you can change to int
* if you have memory to burn and "short" is really slow.
*/
typedef short JCOEF;
/* Compressed datastreams are represented as arrays of JOCTET.
* These must be EXACTLY 8 bits wide, at least once they are written to
* external storage. Note that when using the stdio data source/destination
* managers, this is also the data type passed to fread/fwrite.
*/
typedef unsigned char JOCTET;
#define GETJOCTET(value) (value)
/* These typedefs are used for various table entries and so forth.
* They must be at least as wide as specified; but making them too big
* won't cost a huge amount of memory, so we don't provide special
* extraction code like we did for JSAMPLE. (In other words, these
* typedefs live at a different point on the speed/space tradeoff curve.)
*/
/* UINT8 must hold at least the values 0..255. */
typedef unsigned char UINT8;
/* UINT16 must hold at least the values 0..65535. */
#ifdef HAVE_UNSIGNED_SHORT
typedef unsigned short UINT16;
#else /* not HAVE_UNSIGNED_SHORT */
typedef unsigned int UINT16;
#endif /* HAVE_UNSIGNED_SHORT */
/* INT16 must hold at least the values -32768..32767. */
#ifndef XMD_H /* X11/xmd.h correctly defines INT16 */
typedef short INT16;
#endif
/* INT32 must hold at least signed 32-bit values.
*
* NOTE: The INT32 typedef dates back to libjpeg v5 (1994.) Integers were
* sometimes 16-bit back then (MS-DOS), which is why INT32 is typedef'd to
* long. It also wasn't common (or at least as common) in 1994 for INT32 to be
* defined by platform headers. Since then, however, INT32 is defined in
* several other common places:
*
* Xmd.h (X11 header) typedefs INT32 to int on 64-bit platforms and long on
* 32-bit platforms (i.e always a 32-bit signed type.)
*
* basetsd.h (Win32 header) typedefs INT32 to int (always a 32-bit signed type
* on modern platforms.)
*
* qglobal.h (Qt header) typedefs INT32 to int (always a 32-bit signed type on
* modern platforms.)
*
* This is a recipe for conflict, since "long" and "int" aren't always
* compatible types. Since the definition of INT32 has technically been part
* of the libjpeg API for more than 20 years, we can't remove it, but we do not
* use it internally any longer. We instead define a separate type (JLONG)
* for internal use, which ensures that internal behavior will always be the
* same regardless of any external headers that may be included.
*/
#ifndef XMD_H /* X11/xmd.h correctly defines INT32 */
#ifndef _BASETSD_H_ /* Microsoft defines it in basetsd.h */
#ifndef _BASETSD_H /* MinGW is slightly different */
#ifndef QGLOBAL_H /* Qt defines it in qglobal.h */
typedef long INT32;
#endif
#endif
#endif
#endif
/* Datatype used for image dimensions. The JPEG standard only supports
* images up to 64K*64K due to 16-bit fields in SOF markers. Therefore
* "unsigned int" is sufficient on all machines. However, if you need to
* handle larger images and you don't mind deviating from the spec, you
* can change this datatype. (Note that changing this datatype will
* potentially require modifying the SIMD code. The x86-64 SIMD extensions,
* in particular, assume a 32-bit JDIMENSION.)
*/
typedef unsigned int JDIMENSION;
#define JPEG_MAX_DIMENSION 65500L /* a tad under 64K to prevent overflows */
/* These macros are used in all function definitions and extern declarations.
* You could modify them if you need to change function linkage conventions;
* in particular, you'll need to do that to make the library a Windows DLL.
* Another application is to make all functions global for use with debuggers
* or code profilers that require it.
*/
/* a function called through method pointers: */
#define METHODDEF(type) static type
/* a function used only in its module: */
#define LOCAL(type) static type
/* a function referenced thru EXTERNs: */
#define GLOBAL(type) type
/* a reference to a GLOBAL function: */
#define EXTERN(type) extern type
/* Originally, this macro was used as a way of defining function prototypes
* for both modern compilers as well as older compilers that did not support
* prototype parameters. libjpeg-turbo has never supported these older,
* non-ANSI compilers, but the macro is still included because there is some
* software out there that uses it.
*/
#define JMETHOD(type, methodname, arglist) type (*methodname) arglist
/* libjpeg-turbo no longer supports platforms that have far symbols (MS-DOS),
* but again, some software relies on this macro.
*/
#undef FAR
#define FAR
/*
* On a few systems, type boolean and/or its values FALSE, TRUE may appear
* in standard header files. Or you may have conflicts with application-
* specific header files that you want to include together with these files.
* Defining HAVE_BOOLEAN before including jpeglib.h should make it work.
*/
#ifndef HAVE_BOOLEAN
typedef int boolean;
#endif
#ifndef FALSE /* in case these macros already exist */
#define FALSE 0 /* values of boolean */
#endif
#ifndef TRUE
#define TRUE 1
#endif
/*
* The remaining options affect code selection within the JPEG library,
* but they don't need to be visible to most applications using the library.
* To minimize application namespace pollution, the symbols won't be
* defined unless JPEG_INTERNALS or JPEG_INTERNAL_OPTIONS has been defined.
*/
#ifdef JPEG_INTERNALS
#define JPEG_INTERNAL_OPTIONS
#endif
#ifdef JPEG_INTERNAL_OPTIONS
/*
* These defines indicate whether to include various optional functions.
* Undefining some of these symbols will produce a smaller but less capable
* library. Note that you can leave certain source files out of the
* compilation/linking process if you've #undef'd the corresponding symbols.
* (You may HAVE to do that if your compiler doesn't like null source files.)
*/
/* Capability options common to encoder and decoder: */
#define DCT_ISLOW_SUPPORTED /* accurate integer method */
#define DCT_IFAST_SUPPORTED /* less accurate int method [legacy feature] */
#define DCT_FLOAT_SUPPORTED /* floating-point method [legacy feature] */
/* Encoder capability options: */
#define C_MULTISCAN_FILES_SUPPORTED /* Multiple-scan JPEG files? */
#define C_PROGRESSIVE_SUPPORTED /* Progressive JPEG? (Requires MULTISCAN)*/
#define ENTROPY_OPT_SUPPORTED /* Optimization of entropy coding parms? */
/* Note: if you selected 12-bit data precision, it is dangerous to turn off
* ENTROPY_OPT_SUPPORTED. The standard Huffman tables are only good for 8-bit
* precision, so jchuff.c normally uses entropy optimization to compute
* usable tables for higher precision. If you don't want to do optimization,
* you'll have to supply different default Huffman tables.
* The exact same statements apply for progressive JPEG: the default tables
* don't work for progressive mode. (This may get fixed, however.)
*/
#define INPUT_SMOOTHING_SUPPORTED /* Input image smoothing option? */
/* Decoder capability options: */
#define D_MULTISCAN_FILES_SUPPORTED /* Multiple-scan JPEG files? */
#define D_PROGRESSIVE_SUPPORTED /* Progressive JPEG? (Requires MULTISCAN)*/
#define SAVE_MARKERS_SUPPORTED /* jpeg_save_markers() needed? */
#define BLOCK_SMOOTHING_SUPPORTED /* Block smoothing? (Progressive only) */
#define IDCT_SCALING_SUPPORTED /* Output rescaling via IDCT? */
#undef UPSAMPLE_SCALING_SUPPORTED /* Output rescaling at upsample stage? */
#define UPSAMPLE_MERGING_SUPPORTED /* Fast path for sloppy upsampling? */
#define QUANT_1PASS_SUPPORTED /* 1-pass color quantization? */
#define QUANT_2PASS_SUPPORTED /* 2-pass color quantization? */
/* more capability options later, no doubt */
/*
* The RGB_RED, RGB_GREEN, RGB_BLUE, and RGB_PIXELSIZE macros are a vestigial
* feature of libjpeg. The idea was that, if an application developer needed
* to compress from/decompress to a BGR/BGRX/RGBX/XBGR/XRGB buffer, they could
* change these macros, rebuild libjpeg, and link their application statically
* with it. In reality, few people ever did this, because there were some
* severe restrictions involved (cjpeg and djpeg no longer worked properly,
* compressing/decompressing RGB JPEGs no longer worked properly, and the color
* quantizer wouldn't work with pixel sizes other than 3.) Furthermore, since
* all of the O/S-supplied versions of libjpeg were built with the default
* values of RGB_RED, RGB_GREEN, RGB_BLUE, and RGB_PIXELSIZE, many applications
* have come to regard these values as immutable.
*
* The libjpeg-turbo colorspace extensions provide a much cleaner way of
* compressing from/decompressing to buffers with arbitrary component orders
* and pixel sizes. Thus, we do not support changing the values of RGB_RED,
* RGB_GREEN, RGB_BLUE, or RGB_PIXELSIZE. In addition to the restrictions
* listed above, changing these values will also break the SIMD extensions and
* the regression tests.
*/
#define RGB_RED 0 /* Offset of Red in an RGB scanline element */
#define RGB_GREEN 1 /* Offset of Green */
#define RGB_BLUE 2 /* Offset of Blue */
#define RGB_PIXELSIZE 3 /* JSAMPLEs per RGB scanline element */
#define JPEG_NUMCS 17
#define EXT_RGB_RED 0
#define EXT_RGB_GREEN 1
#define EXT_RGB_BLUE 2
#define EXT_RGB_PIXELSIZE 3
#define EXT_RGBX_RED 0
#define EXT_RGBX_GREEN 1
#define EXT_RGBX_BLUE 2
#define EXT_RGBX_PIXELSIZE 4
#define EXT_BGR_RED 2
#define EXT_BGR_GREEN 1
#define EXT_BGR_BLUE 0
#define EXT_BGR_PIXELSIZE 3
#define EXT_BGRX_RED 2
#define EXT_BGRX_GREEN 1
#define EXT_BGRX_BLUE 0
#define EXT_BGRX_PIXELSIZE 4
#define EXT_XBGR_RED 3
#define EXT_XBGR_GREEN 2
#define EXT_XBGR_BLUE 1
#define EXT_XBGR_PIXELSIZE 4
#define EXT_XRGB_RED 1
#define EXT_XRGB_GREEN 2
#define EXT_XRGB_BLUE 3
#define EXT_XRGB_PIXELSIZE 4
static const int rgb_red[JPEG_NUMCS] = {
-1, -1, RGB_RED, -1, -1, -1, EXT_RGB_RED, EXT_RGBX_RED,
EXT_BGR_RED, EXT_BGRX_RED, EXT_XBGR_RED, EXT_XRGB_RED,
EXT_RGBX_RED, EXT_BGRX_RED, EXT_XBGR_RED, EXT_XRGB_RED,
-1
};
static const int rgb_green[JPEG_NUMCS] = {
-1, -1, RGB_GREEN, -1, -1, -1, EXT_RGB_GREEN, EXT_RGBX_GREEN,
EXT_BGR_GREEN, EXT_BGRX_GREEN, EXT_XBGR_GREEN, EXT_XRGB_GREEN,
EXT_RGBX_GREEN, EXT_BGRX_GREEN, EXT_XBGR_GREEN, EXT_XRGB_GREEN,
-1
};
static const int rgb_blue[JPEG_NUMCS] = {
-1, -1, RGB_BLUE, -1, -1, -1, EXT_RGB_BLUE, EXT_RGBX_BLUE,
EXT_BGR_BLUE, EXT_BGRX_BLUE, EXT_XBGR_BLUE, EXT_XRGB_BLUE,
EXT_RGBX_BLUE, EXT_BGRX_BLUE, EXT_XBGR_BLUE, EXT_XRGB_BLUE,
-1
};
static const int rgb_pixelsize[JPEG_NUMCS] = {
-1, -1, RGB_PIXELSIZE, -1, -1, -1, EXT_RGB_PIXELSIZE, EXT_RGBX_PIXELSIZE,
EXT_BGR_PIXELSIZE, EXT_BGRX_PIXELSIZE, EXT_XBGR_PIXELSIZE, EXT_XRGB_PIXELSIZE,
EXT_RGBX_PIXELSIZE, EXT_BGRX_PIXELSIZE, EXT_XBGR_PIXELSIZE, EXT_XRGB_PIXELSIZE,
-1
};
/* Definitions for speed-related optimizations. */
/* On some machines (notably 68000 series) "int" is 32 bits, but multiplying
* two 16-bit shorts is faster than multiplying two ints. Define MULTIPLIER
* as short on such a machine. MULTIPLIER must be at least 16 bits wide.
*/
#ifndef MULTIPLIER
#ifndef WITH_SIMD
#define MULTIPLIER int /* type for fastest integer multiply */
#else
#define MULTIPLIER short /* prefer 16-bit with SIMD for parellelism */
#endif
#endif
/* FAST_FLOAT should be either float or double, whichever is done faster
* by your compiler. (Note that this type is only used in the floating point
* DCT routines, so it only matters if you've defined DCT_FLOAT_SUPPORTED.)
*/
#ifndef FAST_FLOAT
#define FAST_FLOAT float
#endif
#endif /* JPEG_INTERNAL_OPTIONS */

1176
common/jpeglib.h Normal file

File diff suppressed because it is too large Load Diff

13
common/key.h Normal file
View File

@@ -0,0 +1,13 @@
// V2 License Public Key
// Generated: 2026-03-05 00:48:25
const BYTE g_LicensePublicKey[64] = {
0xa9, 0x5d, 0x1d, 0x44, 0x35, 0x86, 0x85, 0xdd,
0xbe, 0x27, 0x26, 0x6d, 0xe7, 0x33, 0x27, 0xf8,
0xbe, 0x2d, 0x87, 0xdd, 0xc1, 0x47, 0x18, 0xbf,
0xc6, 0x32, 0xfd, 0xce, 0xec, 0x25, 0x1b, 0xf5,
0x9b, 0x8a, 0x26, 0xa9, 0x85, 0x42, 0x72, 0x9f,
0x68, 0x79, 0x9b, 0x83, 0x5e, 0x2b, 0xd6, 0x59,
0x86, 0x64, 0x85, 0xe1, 0xf3, 0xa3, 0x18, 0x95,
0x5d, 0xd6, 0x3f, 0x2f, 0x55, 0x0b, 0x76, 0xbd
};

430
common/location.h Normal file
View File

@@ -0,0 +1,430 @@
#pragma once
#include <string>
#include <winsock2.h>
#include <iphlpapi.h>
#include <ws2tcpip.h>
#include <WinInet.h>
#include "logger.h"
#include "jsoncpp/json.h"
#ifndef _WIN64
#ifdef _DEBUG
#pragma comment(lib, "jsoncpp/jsoncppd.lib")
#else
#pragma comment(lib, "jsoncpp/jsoncpp.lib")
#endif
#else
#ifdef _DEBUG
#pragma comment(lib, "jsoncpp/jsoncpp_x64d.lib")
#else
#pragma comment(lib, "jsoncpp/jsoncpp_x64.lib")
#endif
#endif
#pragma comment(lib, "wininet.lib")
#pragma comment(lib, "Iphlpapi.lib")
// UTF-8 转 ANSI当前系统代码页
inline std::string Utf8ToAnsi(const std::string& utf8)
{
if (utf8.empty()) return "";
// UTF-8 -> UTF-16
int wideLen = MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, NULL, 0);
if (wideLen <= 0) return utf8;
std::wstring wideStr(wideLen, 0);
MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, &wideStr[0], wideLen);
// UTF-16 -> ANSI
int ansiLen = WideCharToMultiByte(CP_ACP, 0, wideStr.c_str(), -1, NULL, 0, NULL, NULL);
if (ansiLen <= 0) return utf8;
std::string ansiStr(ansiLen, 0);
WideCharToMultiByte(CP_ACP, 0, wideStr.c_str(), -1, &ansiStr[0], ansiLen, NULL, NULL);
// 去掉末尾的 null 字符
if (!ansiStr.empty() && ansiStr.back() == '\0') {
ansiStr.pop_back();
}
return ansiStr;
}
// 检测字符串是否可能是 UTF-8 编码(包含多字节序列)
inline bool IsLikelyUtf8(const std::string& str)
{
for (size_t i = 0; i < str.size(); i++) {
unsigned char c = str[i];
if (c >= 0x80) {
// 2字节序列: 110xxxxx 10xxxxxx
if ((c & 0xE0) == 0xC0 && i + 1 < str.size()) {
if ((str[i+1] & 0xC0) == 0x80) return true;
}
// 3字节序列: 1110xxxx 10xxxxxx 10xxxxxx
else if ((c & 0xF0) == 0xE0 && i + 2 < str.size()) {
if ((str[i+1] & 0xC0) == 0x80 && (str[i+2] & 0xC0) == 0x80) return true;
}
// 4字节序列: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
else if ((c & 0xF8) == 0xF0 && i + 3 < str.size()) {
if ((str[i+1] & 0xC0) == 0x80 && (str[i+2] & 0xC0) == 0x80 && (str[i+3] & 0xC0) == 0x80) return true;
}
}
}
return false;
}
// 安全转换:检测到 UTF-8 才转换,否则原样返回
inline std::string SafeUtf8ToAnsi(const std::string& str)
{
if (str.empty()) return str;
return IsLikelyUtf8(str) ? Utf8ToAnsi(str) : str;
}
inline void splitIpPort(const std::string& input, std::string& ip, std::string& port)
{
size_t pos = input.find(':');
if (pos != std::string::npos) {
ip = input.substr(0, pos);
port = input.substr(pos + 1);
} else {
ip = input;
port = "";
}
}
/**
* IPConverter: IP 操作类用于获取公网IP获取IP对应的地理位置等.
* 目前是通过调用公开的互联网API完成假如该查询网站不可访问则需要重新适配.
*/
class IPConverter
{
public:
virtual ~IPConverter() {}
virtual std::string IPtoAddress(const std::string& ip)
{
return "implement me";
}
/**
* 判断给定的 IP 地址是否是局域网内网IP
* @param ipAddress IP 地址字符串(如 "192.168.1.1"
* @return 如果是局域网 IP返回 true; 否则返回 false
*/
bool IsPrivateIP(const std::string& ipAddress)
{
// 将 IP 地址字符串转换为二进制格式
in_addr addr;
if (inet_pton(AF_INET, ipAddress.c_str(), &addr) != 1) {
Mprintf("Invalid IP address: %s\n", ipAddress.c_str());
return false;
}
// 将二进制 IP 地址转换为无符号整数
unsigned long ip = ntohl(addr.s_addr);
// 检查 IP 地址是否在局域网范围内
if ((ip >= 0x0A000000 && ip <= 0x0AFFFFFF) || // 10.0.0.0/8
(ip >= 0xAC100000 && ip <= 0xAC1FFFFF) || // 172.16.0.0/12
(ip >= 0xC0A80000 && ip <= 0xC0A8FFFF) || // 192.168.0.0/16
(ip >= 0x7F000000 && ip <= 0x7FFFFFFF)) { // 127.0.0.0/8
return true;
}
return false;
}
/**
* 判断给定的 IP 地址是否是公网 IP
*/
bool IsPublicIP(const std::string& ipAddress)
{
in_addr addr;
if (inet_pton(AF_INET, ipAddress.c_str(), &addr) != 1) return false;
unsigned long h = ntohl(addr.s_addr);
if ((h & 0xFF000000) == 0x0A000000) return false; // 10.0.0.0/8
if ((h & 0xFFF00000) == 0xAC100000) return false; // 172.16.0.0/12
if ((h & 0xFFFF0000) == 0xC0A80000) return false; // 192.168.0.0/16
if ((h & 0xFF000000) == 0x7F000000) return false; // 127.0.0.0/8
if ((h & 0xFF000000) == 0x00000000) return false; // 0.0.0.0/8
if ((h & 0xFFFF0000) == 0xA9FE0000) return false; // 169.254.0.0/16
if ((h & 0xFFC00000) == 0x64400000) return false; // 100.64.0.0/10
if ((h & 0xF0000000) == 0xE0000000) return false; // 224.0.0.0/4
if ((h & 0xF0000000) == 0xF0000000) return false; // 240.0.0.0/4
return true;
}
/**
* 获取本机网卡的公网 IP 和私有 IP
*/
void GetLocalIPs(std::string& publicIP, std::string& privateIP)
{
publicIP.clear();
privateIP.clear();
ULONG outBufLen = 15000;
IP_ADAPTER_ADDRESSES* pAddresses = (IP_ADAPTER_ADDRESSES*)malloc(outBufLen);
if (GetAdaptersAddresses(AF_INET, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen) == ERROR_BUFFER_OVERFLOW) {
free(pAddresses);
pAddresses = (IP_ADAPTER_ADDRESSES*)malloc(outBufLen);
}
if (GetAdaptersAddresses(AF_INET, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen) == NO_ERROR) {
for (IP_ADAPTER_ADDRESSES* pCurr = pAddresses; pCurr; pCurr = pCurr->Next) {
if (pCurr->OperStatus != IfOperStatusUp) continue;
if (pCurr->IfType == IF_TYPE_SOFTWARE_LOOPBACK) continue;
for (IP_ADAPTER_UNICAST_ADDRESS* pUnicast = pCurr->FirstUnicastAddress; pUnicast; pUnicast = pUnicast->Next) {
if (pUnicast->Address.lpSockaddr->sa_family != AF_INET) continue;
char addrBuf[INET_ADDRSTRLEN] = { 0 };
sockaddr_in* sa_in = (sockaddr_in*)pUnicast->Address.lpSockaddr;
inet_ntop(AF_INET, &sa_in->sin_addr, addrBuf, sizeof(addrBuf));
std::string ip = addrBuf;
if (ip.substr(0, 4) == "127.") continue;
if (IsPublicIP(ip)) { if (publicIP.empty()) publicIP = ip; }
else { if (privateIP.empty()) privateIP = ip; }
if (!publicIP.empty() && !privateIP.empty()) break;
}
if (!publicIP.empty() && !privateIP.empty()) break;
}
}
free(pAddresses);
}
// 获取本机地理位置
std::string GetLocalLocation()
{
return GetGeoLocation(getPublicIP());
}
// 获取 IP 地址地理位置 (多API备用)
virtual std::string GetGeoLocation(const std::string& IP)
{
if (IP.empty()) return "";
std::string ip = IP;
if (isLocalIP(ip)) {
ip = getPublicIP();
if (ip.empty())
return "";
}
// 初始化 WinINet
HINTERNET hInternet = InternetOpen("IP Geolocation", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
if (hInternet == NULL) {
Mprintf("InternetOpen failed! %d\n", GetLastError());
return "";
}
// 设置超时
DWORD timeout = 5000;
InternetSetOptionA(hInternet, INTERNET_OPTION_CONNECT_TIMEOUT, &timeout, sizeof(timeout));
InternetSetOptionA(hInternet, INTERNET_OPTION_SEND_TIMEOUT, &timeout, sizeof(timeout));
InternetSetOptionA(hInternet, INTERNET_OPTION_RECEIVE_TIMEOUT, &timeout, sizeof(timeout));
// API配置: {url格式, 城市字段, 国家字段, 成功条件字段, 成功条件值, 是否HTTPS}
struct GeoApiConfig {
const char* urlFmt; // URL格式 (%s = IP)
const char* cityField; // 城市字段名
const char* countryField;// 国家字段名
const char* checkField; // 校验字段 (空=不校验)
const char* checkValue; // 校验值 (空=检查非error)
bool useHttps;
};
static const GeoApiConfig apis[] = {
// ip-api.com: 45次/分钟
{"http://ip-api.com/json/%s?fields=status,country,city", "city", "country", "status", "success", false},
// ipinfo.io: 1000次/月
{"http://ipinfo.io/%s/json", "city", "country", "", "", false},
// ipapi.co: 1000次/天
{"https://ipapi.co/%s/json/", "city", "country_name", "error", "", true},
};
std::string location;
for (const auto& api : apis) {
location = TryGeoApi(hInternet, ip, api.urlFmt, api.cityField, api.countryField,
api.checkField, api.checkValue, api.useHttps);
if (!location.empty()) break;
}
InternetCloseHandle(hInternet);
if (location.empty() && IsPrivateIP(ip)) {
return "Local Area Network";
}
return location;
}
private:
// 通用API查询函数
std::string TryGeoApi(HINTERNET hInternet, const std::string& ip,
const char* urlFmt, const char* cityField, const char* countryField,
const char* checkField, const char* checkValue, bool useHttps)
{
char urlBuf[256];
sprintf_s(urlBuf, urlFmt, ip.c_str());
DWORD flags = INTERNET_FLAG_RELOAD;
if (useHttps) flags |= INTERNET_FLAG_SECURE;
HINTERNET hConnect = InternetOpenUrlA(hInternet, urlBuf, NULL, 0, flags, 0);
if (!hConnect) return "";
// 检查HTTP状态码
DWORD statusCode = 0;
DWORD statusSize = sizeof(statusCode);
if (HttpQueryInfo(hConnect, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &statusCode, &statusSize, NULL)) {
if (statusCode == 429 || statusCode >= 400) {
InternetCloseHandle(hConnect);
return ""; // 429或其他错误尝试下一个API
}
}
std::string readBuffer;
char buffer[4096];
DWORD bytesRead;
while (InternetReadFile(hConnect, buffer, sizeof(buffer), &bytesRead) && bytesRead > 0) {
readBuffer.append(buffer, bytesRead);
}
InternetCloseHandle(hConnect);
// 备用: 检查响应体中的错误信息
if (readBuffer.find("Rate limit") != std::string::npos ||
readBuffer.find("rate limit") != std::string::npos) {
return "";
}
Json::Value json;
Json::Reader reader;
if (!reader.parse(readBuffer, json)) return "";
// 校验条件
if (checkField && checkField[0]) {
if (checkValue && checkValue[0]) {
// 检查字段==值
if (json[checkField].asString() != checkValue) return "";
} else {
// 检查字段不为true (error字段)
if (json[checkField].asBool()) return "";
}
}
std::string city = Utf8ToAnsi(json[cityField].asString());
std::string country = Utf8ToAnsi(json[countryField].asString());
if (!city.empty() && !country.empty()) return city + ", " + country;
if (!city.empty()) return city;
if (!country.empty()) return country;
return "";
}
public:
bool isLoopbackAddress(const std::string& ip)
{
return (ip == "127.0.0.1" || ip == "::1");
}
bool isLocalIP(const std::string& ip)
{
if (isLoopbackAddress(ip)) return true; // 先检查回环地址
ULONG outBufLen = 15000;
IP_ADAPTER_ADDRESSES* pAddresses = (IP_ADAPTER_ADDRESSES*)malloc(outBufLen);
if (GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen) == ERROR_BUFFER_OVERFLOW) {
free(pAddresses);
pAddresses = (IP_ADAPTER_ADDRESSES*)malloc(outBufLen);
}
if (GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen) == NO_ERROR) {
for (IP_ADAPTER_ADDRESSES* pCurrAddresses = pAddresses; pCurrAddresses; pCurrAddresses = pCurrAddresses->Next) {
for (IP_ADAPTER_UNICAST_ADDRESS* pUnicast = pCurrAddresses->FirstUnicastAddress; pUnicast; pUnicast = pUnicast->Next) {
char addressBuffer[INET6_ADDRSTRLEN] = { 0 };
getnameinfo(pUnicast->Address.lpSockaddr, pUnicast->Address.iSockaddrLength, addressBuffer, sizeof(addressBuffer), nullptr, 0, NI_NUMERICHOST);
if (ip == addressBuffer) {
free(pAddresses);
return true;
}
}
}
}
free(pAddresses);
return false;
}
// 获取公网IP, 获取失败返回空
std::string getPublicIP()
{
clock_t t = clock();
// 多个候选查询源
static const std::vector<std::string> urls = {
"https://checkip.amazonaws.com", // 全球最稳
"https://api.ipify.org", // 主流高可用
"https://ipinfo.io/ip", // 备用方案
"https://icanhazip.com", // 轻量快速
"https://ifconfig.me/ip" // 末位兜底
};
// 打开 WinINet 会话
HINTERNET hInternet = InternetOpenA("Mozilla/5.0", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
if (!hInternet) {
Mprintf("InternetOpen failed. cost %d ms.\n", clock() - t);
return "";
}
// 设置超时 (毫秒)
DWORD timeout = 3000; // 3 秒
InternetSetOptionA(hInternet, INTERNET_OPTION_CONNECT_TIMEOUT, &timeout, sizeof(timeout));
InternetSetOptionA(hInternet, INTERNET_OPTION_SEND_TIMEOUT, &timeout, sizeof(timeout));
InternetSetOptionA(hInternet, INTERNET_OPTION_RECEIVE_TIMEOUT, &timeout, sizeof(timeout));
std::string result;
char buffer[2048];
DWORD bytesRead = 0;
// 轮询不同 IP 查询源
for (const auto& url : urls) {
HINTERNET hConnect = InternetOpenUrlA(
hInternet, url.c_str(), NULL, 0,
INTERNET_FLAG_RELOAD | INTERNET_FLAG_SECURE | INTERNET_FLAG_NO_CACHE_WRITE,
0
);
if (!hConnect) {
continue; // 当前源失败,尝试下一个
}
memset(buffer, 0, sizeof(buffer));
if (InternetReadFile(hConnect, buffer, sizeof(buffer) - 1, &bytesRead) && bytesRead > 0) {
result.assign(buffer, bytesRead);
// 去除换行符和空格
while (!result.empty() && (result.back() == '\n' || result.back() == '\r' || result.back() == ' '))
result.pop_back();
InternetCloseHandle(hConnect);
break; // 成功获取,停止尝试
}
InternetCloseHandle(hConnect);
}
InternetCloseHandle(hInternet);
Mprintf("getPublicIP %s cost %d ms.\n", result.empty() ? "failed" : "succeed", clock() - t);
return result;
}
};
enum IPDatabaseType {
NoneDB = 0,
QQWry = 1,
Ip2Region = 2,
};
IPConverter* LoadFileQQWry(const char* datPath);
IPConverter* LoadFileIp2Region(const char* xdbPath);

173
common/locker.h Normal file
View File

@@ -0,0 +1,173 @@
#pragma once
#pragma warning(disable: 4996)
#pragma warning(disable: 4819)
// 互斥锁、睡眠函数、自动锁、自动计时、自动日志等
#include "logger.h"
// 自动日志
class CAutoLog
{
private:
CRITICAL_SECTION* m_cs;
const char* name;
public:
CAutoLog(const char* _name, CRITICAL_SECTION* cs = NULL) : name(_name), m_cs(cs)
{
Mprintf(">>> Enter thread %s: [%d]\n", name ? name : "", GetCurrentThreadId());
if (m_cs)EnterCriticalSection(m_cs);
}
~CAutoLog()
{
if (m_cs)LeaveCriticalSection(m_cs);
Mprintf(">>> Leave thread %s: [%d]\n", name ? name : "", GetCurrentThreadId());
}
};
class CLock
{
public:
CLock(CRITICAL_SECTION& cs) : m_cs(&cs)
{
Lock();
}
CLock() : m_cs(nullptr)
{
InitializeCriticalSection(&i_cs);
}
~CLock()
{
m_cs ? Unlock() : DeleteCriticalSection(&i_cs);
}
void Unlock()
{
LeaveCriticalSection(m_cs ? m_cs : &i_cs);
}
void Lock()
{
EnterCriticalSection(m_cs ? m_cs : &i_cs);
}
void unlock()
{
LeaveCriticalSection(m_cs ? m_cs : &i_cs);
}
void lock()
{
EnterCriticalSection(m_cs ? m_cs : &i_cs);
}
protected:
CRITICAL_SECTION* m_cs; // 外部锁
CRITICAL_SECTION i_cs; // 内部锁
};
typedef CLock CLocker;
class CAutoLock
{
private:
CRITICAL_SECTION& m_cs;
public:
CAutoLock(CRITICAL_SECTION& cs) : m_cs(cs)
{
EnterCriticalSection(&m_cs);
}
~CAutoLock()
{
LeaveCriticalSection(&m_cs);
}
};
class CAutoCLock
{
private:
CLock& m_cs;
public:
CAutoCLock(CLock& cs) : m_cs(cs)
{
m_cs.Lock();
}
~CAutoCLock()
{
m_cs.Unlock();
}
};
// 智能计时器,计算函数的耗时
class auto_tick
{
private:
const char* file;
const char* func;
int line;
int span;
clock_t tick;
std::string tag;
__inline clock_t now() const
{
return clock();
}
__inline int time() const
{
return now() - tick;
}
public:
auto_tick(const char* file_name, const char* func_name, int line_no, int th = 5, const std::string& tag = "") :
file(file_name), func(func_name), line(line_no), span(th), tick(now()), tag(tag) { }
~auto_tick()
{
stop();
}
__inline void stop()
{
if (span != 0) {
int s(this->time());
if (s > span) {
char buf[1024];
tag.empty() ? sprintf_s(buf, "%s(%d) : [%s] cost [%d]ms.\n", file, line, func, s) :
sprintf_s(buf, "%s(%d) : [%s] cost [%d]ms. Tag= %s. \n", file, line, func, s, tag.c_str());
OutputDebugStringA(buf);
}
span = 0;
}
}
};
#if defined (_DEBUG) || defined (WINDOWS)
// 智能计算当前函数的耗时,超时会打印
#define AUTO_TICK(thresh, tag) auto_tick TICK(__FILE__, __FUNCTION__, __LINE__, thresh, tag)
#define STOP_TICK TICK.stop()
#else
#define AUTO_TICK(thresh, tag)
#define STOP_TICK
#endif
#define AUTO_TICK_C AUTO_TICK
#include <MMSystem.h>
#pragma comment(lib, "winmm.lib")
// 高精度的睡眠函数
#define Sleep_m(ms) { Sleep(ms); }
// 以步长n毫秒在条件C下等待T秒(n是步长必须能整除1000)
#define WAIT_n(C, T, n) { int s=(1000*(T))/(n); s=max(s,1); while((C)&&(s--))Sleep(n); }
// 在条件C成立时等待T秒(步长10ms)
#define WAIT(C, T) { WAIT_n(C, T, 10); }
// 在条件C成立时等待T秒(步长1ms)
#define WAIT_1(C, T) { WAIT_n(C, T, 1); }

345
common/logger.h Normal file
View File

@@ -0,0 +1,345 @@
#pragma once
#ifdef _MSC_VER
#pragma warning(disable: 4996)
#endif
#ifdef _WIN32
#ifdef _WINDOWS
#include <afxwin.h>
#else
#include <windows.h>
#endif
#include "skCrypter.h"
#else
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#endif
#include <iostream>
#include <fstream>
#include <mutex>
#include <queue>
#include <string>
#include <thread>
#include <condition_variable>
#include <atomic>
#include <chrono>
#include <sstream>
#include <cstdarg>
#include <iomanip>
#include <algorithm>
inline bool stringToBool(const std::string& str)
{
std::string lower = str;
std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
return (lower == "true" || lower == "1");
}
class Logger
{
public:
enum LogLevel {
InfoLevel, WarningLevel, ErrorLevel
};
// 单例模式
static Logger& getInstance()
{
static Logger instance;
if (instance.pid.empty()) {
#ifdef _WIN32
char buf[16] = {};
sprintf_s(buf, "%d", GetCurrentProcessId());
instance.pid = buf;
// SYSTEM | C:\Windows\Temp
// 普通用户 | C:\Users\用户名\AppData\Local\Temp
char logPath[MAX_PATH] = { 0 };
GetEnvironmentVariableA("TEMP", logPath, MAX_PATH);
instance.InitLogFile(logPath, instance.pid);
#ifdef _WINDOWS
instance.enable = true; // 主控日志默认打开
#else
char var[32] = {};
const char* name = skCrypt("ENABLE_LOG");
DWORD size = GetEnvironmentVariableA(name, var, sizeof(var));
instance.enable = stringToBool(var);
instance.log("logger.h", __LINE__, "GetEnvironmentVariable: %s=%s\n", name, var);
#endif
#else // Linux
char buf[16] = {};
snprintf(buf, sizeof(buf), "%d", (int)getpid());
instance.pid = buf;
// ~/.config/ghost/ 目录写日志
std::string logDir;
const char* xdg = getenv("XDG_CONFIG_HOME");
if (xdg && xdg[0]) {
logDir = std::string(xdg) + "/ghost";
} else {
const char* home = getenv("HOME");
if (home && home[0]) {
logDir = std::string(home) + "/.config/ghost";
} else {
logDir = "/tmp";
}
}
mkdir(logDir.c_str(), 0755); // 确保日志目录存在
instance.InitLogFile(logDir, instance.pid);
// daemon 模式默认打开日志,可通过 ENABLE_LOG=0 关闭
const char* envLog = getenv("ENABLE_LOG");
if (envLog) {
instance.enable = stringToBool(envLog);
} else {
instance.enable = true;
}
#endif
}
return instance;
}
// 禁止拷贝和赋值
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
// 设置日志文件名
void setLogFile(const std::string& filename)
{
std::lock_guard<std::mutex> lock(fileMutex);
logFileName = filename;
}
// 启用日志
void usingLog(bool b = true)
{
enable = b;
}
// 刷新日志队列(同步等待所有日志写入完成)
void flush()
{
// 等待队列清空
while (true) {
{
std::lock_guard<std::mutex> lock(queueMutex);
if (logQueue.empty()) break;
}
cv.notify_one();
#ifdef _WIN32
Sleep(1);
#else
usleep(1000);
#endif
}
}
// 写日志,支持 printf 格式化
void log(const char* file, int line, const char* format, ...)
{
va_list args;
va_start(args, format);
std::string message = formatString(format, args);
va_end(args);
auto timestamp = getCurrentTimestamp();
std::string id = pid.empty() ? "" : "[" + pid + "]";
std::string logEntry = file && line ?
id + "[" + timestamp + "] [" + file + ":" + std::to_string(line) + "] " + message:
id + "[" + timestamp + "] " + message;
if (enable) {
if (running) {
std::lock_guard<std::mutex> lock(queueMutex);
logQueue.push(logEntry);
} else {
writeToFile(logEntry);
}
}
#ifdef _DEBUG
#ifndef _WINDOWS
printf("%s", logEntry.c_str());
#else
OutputDebugStringA(logEntry.c_str());
#endif
#endif
cv.notify_one(); // 通知写线程
}
// 停止日志系统
void stop()
{
if (!running) return;
{
std::lock_guard<std::mutex> lock(queueMutex);
running = false; // 设置运行状态
}
cv.notify_one();
if (workerThread.joinable()) {
try {
workerThread.join();
} catch (const std::system_error& e) {
printf("Join failed: %s [%d]\n", e.what(), e.code().value());
}
}
#ifdef _WIN32
for (int i = 0; threadRun && i++ < 1000; Sleep(1));
#else
for (int i = 0; threadRun && i++ < 1000; usleep(1000));
#endif
}
private:
// 日志按月份起名
void InitLogFile(const std::string & dir, const std::string& pid)
{
time_t currentTime = time(nullptr);
tm* localTime = localtime(&currentTime);
char timeString[32];
strftime(timeString, sizeof(timeString), "%Y-%m", localTime);
char fileName[100];
#ifdef _WINDOWS
sprintf_s(fileName, "\\YAMA_%s_%s.txt", timeString, pid.c_str());
#elif defined(_WIN32)
sprintf_s(fileName, "\\log_%s_%s.txt", timeString, pid.c_str());
#else
snprintf(fileName, sizeof(fileName), "/log_%s_%s.txt", timeString, pid.c_str());
#endif
logFileName = dir + fileName;
}
std::string logFileName; // 日志文件名
bool enable; // 是否启用
bool threadRun; // 日志线程状态
std::queue<std::string> logQueue; // 日志队列
std::mutex queueMutex; // 队列互斥锁
std::condition_variable cv; // 条件变量
std::atomic<bool> running; // 是否运行
std::thread workerThread; // 后台线程
std::mutex fileMutex; // 文件写入锁
std::string pid; // 进程ID
Logger() : enable(false), threadRun(false), running(true), workerThread(&Logger::processLogs, this) {}
~Logger()
{
stop();
}
// 后台线程处理日志
void processLogs()
{
threadRun = true;
while (running) {
std::unique_lock<std::mutex> lock(queueMutex);
cv.wait(lock, [this]() {
return !running || !logQueue.empty();
});
while (running && !logQueue.empty()) {
std::string logEntry = logQueue.front();
logQueue.pop();
lock.unlock();
// 写入日志文件
writeToFile(logEntry);
lock.lock();
}
lock.unlock();
}
threadRun = false;
}
// 写入文件
void writeToFile(const std::string& logEntry)
{
std::lock_guard<std::mutex> lock(fileMutex);
std::ofstream logFile(logFileName, std::ios::app);
if (logFile.is_open()) {
logFile << logEntry << std::endl;
}
}
// 获取当前时间戳
std::string getCurrentTimestamp()
{
auto now = std::chrono::system_clock::now();
auto in_time_t = std::chrono::system_clock::to_time_t(now);
std::tm tm;
#ifdef _WIN32
localtime_s(&tm, &in_time_t); // Windows 安全版本
#else
localtime_r(&in_time_t, &tm); // POSIX 安全版本
#endif
std::stringstream ss;
ss << std::put_time(&tm, "%Y-%m-%d %H:%M:%S");
return ss.str();
}
// 将日志级别转换为字符串
std::string logLevelToString(LogLevel level)
{
switch (level) {
case InfoLevel:
return "INFO";
case WarningLevel:
return "WARNING";
case ErrorLevel:
return "ERROR";
default:
return "UNKNOWN";
}
}
// 格式化字符串
std::string formatString(const char* format, va_list args)
{
char buffer[1024];
vsnprintf(buffer, sizeof(buffer), format, args);
return std::string(buffer);
}
};
inline const char* getFileName(const char* path)
{
const char* fileName = strrchr(path, '\\');
if (!fileName) {
fileName = strrchr(path, '/');
}
return fileName ? fileName + 1 : path;
}
#ifdef _WINDOWS
#ifdef _DEBUG
#define Mprintf(format, ...) TRACE(format, __VA_ARGS__)
#else
#define Mprintf(format, ...) Logger::getInstance().log(getFileName(__FILE__), __LINE__, format, __VA_ARGS__)
#endif
#elif defined(_WIN32)
#define Mprintf(format, ...) Logger::getInstance().log(getFileName((__FILE__)), __LINE__, (format), __VA_ARGS__)
#else
// Linux: 覆盖 commands.h 中的 printf 回退定义,改用 Logger 写文件
#ifdef Mprintf
#undef Mprintf
#endif
#define Mprintf(format, ...) Logger::getInstance().log(getFileName(__FILE__), __LINE__, format, ##__VA_ARGS__)
#endif
inline void Log(const char* message)
{
return Logger::getInstance().log(NULL, 0, "%s", message);
}
inline void Logf(const char* file, int line, const char* format, ...)
{
va_list args;
va_start(args, format);
char message[1024];
vsnprintf(message, sizeof(message), format, args);
va_end(args);
return Logger::getInstance().log(getFileName(file), line, "%s", message);
}

196
common/mask.h Normal file
View File

@@ -0,0 +1,196 @@
#pragma once
#include "header.h"
#ifndef IMAGE_FILE_MACHINE_ARM64
#define IMAGE_FILE_MACHINE_ARM64 0xAA64
#endif
#ifndef PROCESSOR_ARCHITECTURE_ARM64
#define PROCESSOR_ARCHITECTURE_ARM64 12
#endif
// 数据包协议封装格式
// Copy left: 962914132@qq.com & ChatGPT
enum PkgMaskType {
MaskTypeUnknown = -1,
MaskTypeNone,
MaskTypeHTTP,
MaskTypeNum,
};
#define DEFAULT_HOST "example.com"
inline ULONG UnMaskHttp(char* src, ULONG srcSize)
{
const char* header_end_mark = "\r\n\r\n";
const ULONG mark_len = 4;
// 查找 HTTP 头部结束标记
for (ULONG i = 0; i + mark_len <= srcSize; ++i) {
if (memcmp(src + i, header_end_mark, mark_len) == 0) {
return i + mark_len; // 返回 Body 起始位置
}
}
return 0; // 无效数据
}
// TryUnMask 尝试去掉伪装的协议头.
inline ULONG TryUnMask(char* src, ULONG srcSize, PkgMaskType& maskHit)
{
if (srcSize >= 5 && memcmp(src, "POST ", 5) == 0) {
maskHit = MaskTypeHTTP;
return UnMaskHttp(src, srcSize);
}
maskHit = MaskTypeNone;
return 0;
}
// PkgMask 针对消息进一步加密、混淆或伪装.
class PkgMask
{
protected:
virtual ~PkgMask() {}
public:
virtual void Destroy()
{
delete this;
}
virtual void Mask(char*& dst, ULONG& dstSize, char* src, ULONG srcSize, int cmd = -1)
{
dst = src;
dstSize = srcSize;
}
virtual ULONG UnMask(char* src, ULONG srcSize)
{
return 0;
}
virtual PkgMask* SetServer(const std::string& addr)
{
return this;
}
virtual PkgMaskType GetMaskType() const
{
return MaskTypeNone;
}
};
class HttpMask : public PkgMask
{
public:
virtual PkgMaskType GetMaskType() const override
{
return MaskTypeHTTP;
}
/**
* @brief 构造函数
* @param host HTTP Host 头字段
*/
explicit HttpMask(const std::string& host, const std::map<std::string, std::string>& headers = {}) :
product_(GenerateRandomString()), host_(host)
{
// 初始化随机数生成器
srand(static_cast<unsigned>(time(nullptr)));
char buf[32];
sprintf_s(buf, "V%d.%d.%d", rand() % 10, rand() % 10, rand() % 10);
version_ = buf;
user_agent_ = GetEnhancedSystemUA(product_, version_);
for (std::map<std::string, std::string>::const_iterator it = headers.begin(); it != headers.end(); ++it) {
headers_ += it->first + ": " + it->second + "\r\n";
}
}
/**
* @brief 将原始数据伪装为 HTTP 请求
* @param dst [输出] 伪装后的数据缓冲区(需调用者释放)
* @param dstSize [输出] 伪装后数据长度
* @param src 原始数据指针
* @param srcSize 原始数据长度
* @param cmd 命令号
*/
void Mask(char*& dst, ULONG& dstSize, char* src, ULONG srcSize, int cmd = -1)
{
// 生成动态 HTTP 头部
std::string http_header =
"POST " + GeneratePath(cmd) + " HTTP/1.1\r\n"
"Host: " + host_ + "\r\n"
"User-Agent: " + user_agent_ + "\r\n"
"Content-Type: application/octet-stream\r\n"
"Content-Length: " + std::to_string(srcSize) + "\r\n" + headers_ +
"Connection: keep-alive\r\n"
"\r\n"; // 空行分隔头部和 Body
// 分配输出缓冲区
dstSize = static_cast<ULONG>(http_header.size()) + srcSize;
dst = new char[dstSize];
// 拷贝数据HTTP 头部 + 原始数据
memcpy(dst, http_header.data(), http_header.size());
memcpy(dst + http_header.size(), src, srcSize);
}
/**
* @brief 从 HTTP 数据中提取原始数据起始位置
* @param src 收到的 HTTP 数据
* @param srcSize 数据长度
* @return ULONG 原始数据在 src 中的起始偏移量(失败返回 0
*/
ULONG UnMask(char* src, ULONG srcSize)
{
return UnMaskHttp(src, srcSize);
}
PkgMask* SetServer(const std::string& addr) override
{
host_ = addr;
return this;
}
private:
static std::string GetEnhancedSystemUA(const std::string& appName, const std::string& appVersion)
{
#ifdef _WIN32
OSVERSIONINFOEX osvi = {};
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
GetVersionEx((OSVERSIONINFO*)&osvi);
// 获取系统架构
SYSTEM_INFO si;
GetNativeSystemInfo(&si);
std::string arch = (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) ? "Win64; x64" :
(si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_ARM64) ? "Win64; ARM64" :
"WOW64";
return "Mozilla/5.0 (" +
std::string("Windows NT ") +
std::to_string(osvi.dwMajorVersion) + "." +
std::to_string(osvi.dwMinorVersion) + "; " +
arch + ") " +
appName + "/" + appVersion;
#else
return "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36";
#endif
}
std::string host_; // 目标主机
std::string product_; // 产品名称
std::string version_; // 产品版本
std::string user_agent_;// 代理名称
std::string headers_; // 自定义请求头
/** 生成随机 URL 路径 */
std::string GenerateRandomString(int size = 8) const
{
static const char charset[] = "abcdefghijklmnopqrstuvwxyz0123456789";
char path[32];
for (int i = 0; i < size; ++i) {
path[i] = charset[rand() % (sizeof(charset) - 1)];
}
path[size] = 0;
return path;
}
std::string GeneratePath(int cmd) const
{
static std::string root = "/" + product_ + "/" + GenerateRandomString() + "/";
return root + (cmd == -1 ? GenerateRandomString() : std::to_string(cmd));
}
};

43
common/md5.h Normal file
View File

@@ -0,0 +1,43 @@
#pragma once
#include <wincrypt.h>
inline std::string CalcMD5FromBytes(const BYTE* data, DWORD length)
{
HCRYPTPROV hProv = 0;
HCRYPTHASH hHash = 0;
BYTE hash[16]; // MD5 输出长度是 16 字节
DWORD hashLen = sizeof(hash);
std::ostringstream oss;
if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
return "";
}
if (!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash)) {
CryptReleaseContext(hProv, 0);
return "";
}
if (!CryptHashData(hHash, data, length, 0)) {
CryptDestroyHash(hHash);
CryptReleaseContext(hProv, 0);
return "";
}
if (!CryptGetHashParam(hHash, HP_HASHVAL, hash, &hashLen, 0)) {
CryptDestroyHash(hHash);
CryptReleaseContext(hProv, 0);
return "";
}
// 转换为十六进制字符串
for (DWORD i = 0; i < hashLen; ++i) {
oss << std::hex << std::setw(2) << std::setfill('0') << (int)hash[i];
}
CryptDestroyHash(hHash);
CryptReleaseContext(hProv, 0);
return oss.str();
}

128
common/obfs.h Normal file
View File

@@ -0,0 +1,128 @@
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include "aes.h"
#pragma once
class ObfsBase
{
public:
bool m_bGenCArray;
ObfsBase(bool genCArray = true) : m_bGenCArray(genCArray) { }
virtual ~ObfsBase() { }
// 对称混淆函数:用于加密和解密
virtual void ObfuscateBuffer(uint8_t* buf, size_t len, uint32_t seed) {}
// 解混淆:与加密顺序相反
virtual void DeobfuscateBuffer(uint8_t* buf, size_t len, uint32_t seed) {}
virtual bool WriteFile(const char* filename, uint8_t* data, size_t length, const char* arrayName)
{
return m_bGenCArray ? WriteBinaryAsCArray(filename, data, length, arrayName) : WriteBinaryFile(filename, data, length);
}
// 将二进制数据以 C 数组格式写入文件
virtual bool WriteBinaryAsCArray(const char* filename, uint8_t* data, size_t length, const char* arrayName)
{
FILE* file = fopen(filename, "w");
if (!file) return false;
fprintf(file, "unsigned char %s[] = {\n", arrayName);
for (size_t i = 0; i < length; ++i) {
if (i % 24 == 0) fprintf(file, " ");
fprintf(file, "0x%02X", data[i]);
if (i != length - 1) fprintf(file, ",");
if ((i + 1) % 24 == 0 || i == length - 1) fprintf(file, "\n");
else fprintf(file, " ");
}
fprintf(file, "};\n");
fprintf(file, "unsigned int %s_len = %zu;\n", arrayName, length);
fclose(file);
return true;
}
// 使用 "wb" 二进制写入模式
virtual bool WriteBinaryFile(const char* filename, const uint8_t* data, size_t length)
{
FILE* file = fopen(filename, "wb");
if (!file) return false;
size_t written = fwrite(data, 1, length, file);
fclose(file);
return written == length;
}
};
class Obfs : public ObfsBase
{
private:
// 左旋8位整数
static inline uint8_t rol8(uint8_t val, int shift)
{
return (val << shift) | (val >> (8 - shift));
}
// 右旋8位整数
static inline uint8_t ror8(uint8_t val, int shift)
{
return (val >> shift) | (val << (8 - shift));
}
public:
Obfs(bool genCArray = true) : ObfsBase(genCArray) { }
// 对称混淆函数:用于加密和解密
virtual void ObfuscateBuffer(uint8_t* buf, size_t len, uint32_t seed)
{
uint32_t state = seed;
for (size_t i = 0; i < len; ++i) {
uint8_t mask = (uint8_t)((state >> 16) & 0xFF);
buf[i] = rol8(buf[i] ^ mask, 3); // 异或+旋转扰乱特征
state = state * 2654435761u + buf[i]; // LCG + 数据扰动
}
}
// 解混淆:与加密顺序相反
virtual void DeobfuscateBuffer(uint8_t* buf, size_t len, uint32_t seed)
{
uint32_t state = seed;
for (size_t i = 0; i < len; ++i) {
uint8_t mask = (uint8_t)((state >> 16) & 0xFF);
uint8_t orig = buf[i];
buf[i] = ror8(buf[i], 3) ^ mask;
state = state * 2654435761u + orig; // 必须用混淆前的原字节更新 state
}
}
};
class ObfsAes : public ObfsBase
{
private:
// Please change `aes_key` and `aes_iv`.
unsigned char aes_key[16] = "It is a example";
unsigned char aes_iv[16] = "It is a example";
public:
ObfsAes(bool genCArray = true) : ObfsBase(genCArray) { }
virtual void ObfuscateBuffer(uint8_t* buf, size_t len, uint32_t seed)
{
struct AES_ctx ctx;
AES_init_ctx_iv(&ctx, aes_key, aes_iv);
AES_CBC_encrypt_buffer(&ctx, buf, len);
}
virtual void DeobfuscateBuffer(uint8_t* buf, size_t len, uint32_t seed)
{
struct AES_ctx ctx;
AES_init_ctx_iv(&ctx, aes_key, aes_iv);
AES_CBC_decrypt_buffer(&ctx, buf, len);
}
};

161
common/skCrypter.h Normal file
View File

@@ -0,0 +1,161 @@
#pragma once
/*____________________________________________________________________________________________________________
Original Author: skadro
Github: https://github.com/skadro-official
License: See end of file
skCrypter
Compile-time, Usermode + Kernelmode, safe and lightweight string crypter library for C++11+
*Not removing this part is appreciated*
____________________________________________________________________________________________________________*/
#ifdef _KERNEL_MODE
namespace std
{
// STRUCT TEMPLATE remove_reference
template <class _Ty>
struct remove_reference {
using type = _Ty;
};
template <class _Ty>
struct remove_reference<_Ty&> {
using type = _Ty;
};
template <class _Ty>
struct remove_reference<_Ty&&> {
using type = _Ty;
};
template <class _Ty>
using remove_reference_t = typename remove_reference<_Ty>::type;
// STRUCT TEMPLATE remove_const
template <class _Ty>
struct remove_const { // remove top-level const qualifier
using type = _Ty;
};
template <class _Ty>
struct remove_const<const _Ty> {
using type = _Ty;
};
template <class _Ty>
using remove_const_t = typename remove_const<_Ty>::type;
}
#else
#include <type_traits>
#endif
namespace skc
{
template<class _Ty>
using clean_type = typename std::remove_const_t<std::remove_reference_t<_Ty>>;
template <int _size, char _key1, char _key2, typename T>
class skCrypter
{
public:
__forceinline constexpr skCrypter(T* data)
{
crypt(data);
}
__forceinline T* get()
{
return _storage;
}
__forceinline int size() // (w)char count
{
return _size;
}
__forceinline char key()
{
return _key1;
}
__forceinline T* encrypt()
{
if (!isEncrypted())
crypt(_storage);
return _storage;
}
__forceinline T* decrypt()
{
if (isEncrypted())
crypt(_storage);
return _storage;
}
__forceinline bool isEncrypted()
{
return _storage[_size - 1] != 0;
}
__forceinline void clear() // set full storage to 0
{
for (int i = 0; i < _size; i++) {
_storage[i] = 0;
}
}
__forceinline operator T* ()
{
decrypt();
return _storage;
}
private:
__forceinline constexpr void crypt(T* data)
{
for (int i = 0; i < _size; i++) {
_storage[i] = data[i] ^ (_key1 + i % (1 + _key2));
}
}
T _storage[_size] {};
};
}
#define skCrypt(str) skCrypt_key(str, __TIME__[4], __TIME__[7])
#define skCrypt_key(str, key1, key2) []() { \
constexpr static auto crypted = skc::skCrypter \
<sizeof(str) / sizeof(str[0]), key1, key2, skc::clean_type<decltype(str[0])>>((skc::clean_type<decltype(str[0])>*)str); \
return crypted; }()
/*________________________________________________________________________________
MIT License
Copyright (c) 2020 skadro
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
________________________________________________________________________________*/

2809
common/turbojpeg.h Normal file

File diff suppressed because it is too large Load Diff

120
common/wallet.h Normal file
View File

@@ -0,0 +1,120 @@
#include <iostream>
#include <string>
#include <regex>
// Powered by ChatGPT.
enum WalletType {
WALLET_UNKNOWN = 0,
WALLET_BTC_P2PKH,
WALLET_BTC_P2SH,
WALLET_BTC_BECH32,
WALLET_ETH_ERC20, // ETH、ERC20含 USDT-ERC20
WALLET_USDT_OMNI, // USDT OmniBTC 网络,格式同 BTC
WALLET_USDT_TRC20, // USDT TRC20
WALLET_TRON,
WALLET_SOLANA,
WALLET_XRP,
WALLET_POLKADOT,
WALLET_CARDANO_SHELLEY,
WALLET_CARDANO_BYRON,
WALLET_DOGE // Dogecoin
};
enum AddressType {
ADDR_BTC = 0,
ADDR_ERC20,
ADDR_OMNI,
ADDR_TRC20,
ADDR_SOL,
ADDR_XRP,
ADDR_ADA,
ADDR_DOGE,
ADDR_DOT,
ADDR_TRON,
MAX_WALLET_NUM,
};
inline WalletType detectWalletType(const std::string& address_raw)
{
std::string address = address_raw;
address.erase(0, address.find_first_not_of(" \t\n\r"));
address.erase(address.find_last_not_of(" \t\n\r") + 1);
// 1. ETH/ERC200x 开头)
static const std::regex eth_regex("^0x[a-fA-F0-9]{40}$");
if (std::regex_match(address, eth_regex)) return WALLET_ETH_ERC20;
// 2. TRC20T 开头)
static const std::regex trc20_regex("^T[1-9A-HJ-NP-Za-km-z]{33}$");
if (std::regex_match(address, trc20_regex)) return WALLET_USDT_TRC20;
// 3. BTC Bech32bc1 开头)
static const std::regex btc_bech32_regex("^bc1[0-9a-z]{6,}$");
if (std::regex_match(address, btc_bech32_regex)) return WALLET_BTC_BECH32;
// 4. BTC P2PKH1 开头)
static const std::regex btc_p2pkh_regex("^1[1-9A-HJ-NP-Za-km-z]{25,34}$");
if (std::regex_match(address, btc_p2pkh_regex)) return WALLET_BTC_P2PKH;
// 5. BTC P2SH3 开头)
static const std::regex btc_p2sh_regex("^3[1-9A-HJ-NP-Za-km-z]{25,34}$");
if (std::regex_match(address, btc_p2sh_regex)) return WALLET_BTC_P2SH;
// 6. XRPr 开头Base58
static const std::regex xrp_regex("^r[1-9A-HJ-NP-Za-km-z]{24,34}$");
if (std::regex_match(address, xrp_regex)) return WALLET_XRP;
// 7. DogecoinD 开头Base58
static const std::regex doge_regex("^D[5-9A-HJ-NP-Ua-km-z]{33}$");
if (std::regex_match(address, doge_regex)) return WALLET_DOGE;
// 8. Cardano Shelleyaddr1 开头)
static const std::regex ada_shelley_regex("^addr1[0-9a-z]{20,}$");
if (std::regex_match(address, ada_shelley_regex)) return WALLET_CARDANO_SHELLEY;
// 9. Cardano ByronDdzFF 开头)
if (address.find("DdzFF") == 0) return WALLET_CARDANO_BYRON;
// 10. Polkadot长度 4748Base58
static const std::regex dot_regex("^[1-9A-HJ-NP-Za-km-z]{47,48}$");
if (std::regex_match(address, dot_regex)) return WALLET_POLKADOT;
// 11. Solana3244无前缀Base58→ 容易误判,必须放最后
static const std::regex solana_regex("^[1-9A-HJ-NP-Za-km-z]{32,44}$");
if (std::regex_match(address, solana_regex)) return WALLET_SOLANA;
return WALLET_UNKNOWN;
}
inline std::string walletTypeToString(WalletType type)
{
switch (type) {
case WALLET_BTC_P2PKH:
return "Bitcoin P2PKH (includes USDT-OMNI)";
case WALLET_BTC_P2SH:
return "Bitcoin P2SH (includes USDT-OMNI)";
case WALLET_BTC_BECH32:
return "Bitcoin Bech32";
case WALLET_ETH_ERC20:
return "Ethereum / ERC20 (includes USDT-ERC20)";
case WALLET_USDT_TRC20:
return "USDT TRC20";
case WALLET_TRON:
return "TRON (same as USDT-TRC20)";
case WALLET_SOLANA:
return "Solana";
case WALLET_XRP:
return "XRP";
case WALLET_POLKADOT:
return "Polkadot";
case WALLET_CARDANO_SHELLEY:
return "Cardano Shelley";
case WALLET_CARDANO_BYRON:
return "Cardano Byron";
case WALLET_DOGE:
return "Dogecoin";
default:
return "Unknown or Unsupported";
}
}

7457
common/xxhash.h Normal file

File diff suppressed because it is too large Load Diff

43
common/zstd_wrapper.c Normal file
View File

@@ -0,0 +1,43 @@
#include "zstd_wrapper.h"
#include <string.h> // memcpy
size_t zstd_compress_auto(
ZSTD_CCtx* cctx,
void* dst, size_t dstCapacity,
const void* src, size_t srcSize,
size_t threshold
)
{
// 检查输入有效性
if (!cctx || !dst || !src) {
return ZSTD_error_GENERIC;
}
// --- 小数据或库不支持多线程 → 退回到单线程 ZSTD_compress2 ---
if (srcSize < threshold) {
return ZSTD_compress2(cctx, dst, dstCapacity, src, srcSize);
}
// --- 多线程流式压缩 ---
ZSTD_inBuffer input = { src, srcSize, 0 };
ZSTD_outBuffer output = { dst, dstCapacity, 0 };
// 循环压缩输入数据
size_t ret = 0;
while (input.pos < input.size) {
ret = ZSTD_compressStream2(cctx, &output, &input, ZSTD_e_continue);
if (ZSTD_isError(ret)) break;
// 输出缓冲区已满(理论上不应发生,因 dstCapacity >= ZSTD_compressBound
if (output.pos == output.size) {
return ZSTD_error_dstSize_tooSmall;
}
}
// 结束压缩(确保所有线程完成)
if (!ZSTD_isError(ret)) {
ret = ZSTD_compressStream2(cctx, &output, &input, ZSTD_e_end);
}
return ZSTD_isError(ret) ? ret : output.pos;
}

31
common/zstd_wrapper.h Normal file
View File

@@ -0,0 +1,31 @@
#ifndef ZSTD_WRAPPER_H
#define ZSTD_WRAPPER_H
#include "zstd/zstd.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* 智能压缩函数(自动选择单线程/多线程)
* @param cctx 压缩上下文(需提前创建)
* @param dst 输出缓冲区
* @param dstCapacity 输出缓冲区大小
* @param src 输入数据
* @param srcSize 输入数据大小
* @param threshold 触发多线程的最小数据大小(建议 >= 1MB
* @return 压缩后的数据大小(错误码通过 ZSTD_isError() 检查)
*/
size_t zstd_compress_auto(
ZSTD_CCtx* cctx,
void* dst, size_t dstCapacity,
const void* src, size_t srcSize,
size_t threshold
);
#ifdef __cplusplus
}
#endif
#endif // ZSTD_WRAPPER_H