Init: Migrate SimpleRemoter (Since v1.3.1) to Gitea
This commit is contained in:
238
common/DateVerify.h
Normal file
238
common/DateVerify.h
Normal 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
181
common/IPBlacklist.h
Normal 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
149
common/IPWhitelist.h
Normal 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
181
common/IniParser.h
Normal 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
366
common/LANChecker.h
Normal 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
204
common/SafeString.h
Normal 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
276
common/VerifyV2.h
Normal 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
792
common/ZstdArchive.h
Normal 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
564
common/aes.c
Normal 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
90
common/aes.h
Normal 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
1451
common/commands.h
Normal file
File diff suppressed because it is too large
Load Diff
35
common/dllRunner.h
Normal file
35
common/dllRunner.h
Normal 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
135
common/encfuncs.h
Normal 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
163
common/encrypt.h
Normal 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
263
common/file_upload.cpp
Normal 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
331
common/file_upload.h
Normal 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
56
common/hash.h
Normal 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
187
common/header.h
Normal 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
1294
common/ikcp.c
Normal file
File diff suppressed because it is too large
Load Diff
414
common/ikcp.h
Normal file
414
common/ikcp.h
Normal 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
491
common/iniFile.h
Normal 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
60
common/ip_enc.h
Normal 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
33
common/jconfig.h
Normal 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
386
common/jmorecfg.h
Normal 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
1176
common/jpeglib.h
Normal file
File diff suppressed because it is too large
Load Diff
13
common/key.h
Normal file
13
common/key.h
Normal 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
430
common/location.h
Normal 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
173
common/locker.h
Normal 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
345
common/logger.h
Normal 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(¤tTime);
|
||||
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
196
common/mask.h
Normal 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
43
common/md5.h
Normal 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
128
common/obfs.h
Normal 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
161
common/skCrypter.h
Normal 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
2809
common/turbojpeg.h
Normal file
File diff suppressed because it is too large
Load Diff
120
common/wallet.h
Normal file
120
common/wallet.h
Normal 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 Omni,BTC 网络,格式同 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/ERC20(0x 开头)
|
||||
static const std::regex eth_regex("^0x[a-fA-F0-9]{40}$");
|
||||
if (std::regex_match(address, eth_regex)) return WALLET_ETH_ERC20;
|
||||
|
||||
// 2. TRC20(T 开头)
|
||||
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 Bech32(bc1 开头)
|
||||
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 P2PKH(1 开头)
|
||||
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 P2SH(3 开头)
|
||||
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. XRP(r 开头,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. Dogecoin(D 开头,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 Shelley(addr1 开头)
|
||||
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 Byron(DdzFF 开头)
|
||||
if (address.find("DdzFF") == 0) return WALLET_CARDANO_BYRON;
|
||||
|
||||
// 10. Polkadot(长度 47–48,Base58)
|
||||
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. Solana(32–44,无前缀,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
7457
common/xxhash.h
Normal file
File diff suppressed because it is too large
Load Diff
43
common/zstd_wrapper.c
Normal file
43
common/zstd_wrapper.c
Normal 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
31
common/zstd_wrapper.h
Normal 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
|
||||
Reference in New Issue
Block a user