502 lines
17 KiB
C++
502 lines
17 KiB
C++
#include "l_squirrel.h"
|
||
#include "l_squirrel_register.hpp"
|
||
#include <spdlog/spdlog.h>
|
||
#include <spdlog/sinks/stdout_color_sinks.h>
|
||
#include <spdlog/sinks/basic_file_sink.h>
|
||
#include <iostream>
|
||
#include <ctime>
|
||
#include <sstream>
|
||
#include <chrono>
|
||
#include "SqrReqScript.hpp"
|
||
#include "Dio.hpp"
|
||
#include <sys/wait.h>
|
||
#include <openssl/aes.h>
|
||
#include <openssl/evp.h>
|
||
|
||
#define WEBSCRIPT true;
|
||
|
||
using asio::ip::tcp;
|
||
|
||
static char szGamePathA[256];
|
||
void setupLogger()
|
||
{
|
||
getConfigPath(szGamePathA, sizeof(szGamePathA));
|
||
std::string Path = std::string(szGamePathA);
|
||
std::string log_filename = "log/dps_log/" + Path.substr(Path.find("cfg") + 4) + "/log_";
|
||
char time_buffer[100];
|
||
std::time_t now = std::time(nullptr);
|
||
std::tm* local_time = std::localtime(&now);
|
||
strftime(time_buffer, sizeof(time_buffer), "%Y-%m-%d_%H-%M-%S", local_time);
|
||
log_filename += std::string(time_buffer) + ".txt";
|
||
|
||
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
|
||
console_sink->set_level(spdlog::level::info);
|
||
console_sink->set_pattern("[multi_sink_example] [%^%l%$] %v");
|
||
|
||
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(log_filename, true);
|
||
file_sink->set_level(spdlog::level::info);
|
||
file_sink->set_pattern("[%Y-%m-%d %H:%M:%S] [%^%l%$] %v");
|
||
|
||
auto logger = std::make_shared<spdlog::logger>("logger", spdlog::sinks_init_list{ console_sink, file_sink });
|
||
logger->set_level(spdlog::level::info);
|
||
logger->flush_on(spdlog::level::info);
|
||
|
||
// logger->info("欢迎使用Dps插件");
|
||
|
||
spdlog::register_logger(logger);
|
||
spdlog::set_default_logger(logger);
|
||
}
|
||
|
||
// 虚拟机对象
|
||
HSQUIRRELVM v;
|
||
// Lock
|
||
std::recursive_mutex SqMtx;
|
||
|
||
static void printfunc(HSQUIRRELVM v, const SQChar* s, ...)
|
||
{
|
||
va_list vl;
|
||
va_start(vl, s);
|
||
char buf[1024];
|
||
vsnprintf(buf, sizeof(buf), s, vl);
|
||
va_end(vl);
|
||
|
||
spdlog::info(buf);
|
||
}
|
||
|
||
static void errorfunc(HSQUIRRELVM v, const SQChar* s, ...)
|
||
{
|
||
va_list vl;
|
||
va_start(vl, s);
|
||
char buf[1024];
|
||
vsnprintf(buf, sizeof(buf), s, vl);
|
||
va_end(vl);
|
||
|
||
spdlog::error(buf);
|
||
}
|
||
|
||
// 加密函数
|
||
static std::string encryptDecrypt(const std::string& input, const std::string& key)
|
||
{
|
||
std::string output = input;
|
||
for (size_t i = 0; i < input.size(); i++)
|
||
{
|
||
output[i] = input[i] ^ key[i % key.size()]; // 使用异或运算进行加密
|
||
}
|
||
return output;
|
||
}
|
||
|
||
// 判断是否处理加密
|
||
static std::string IsencryptDecrypt(const std::string& input, const std::string& FileName)
|
||
{
|
||
if (FileName.find(".nut") != std::string::npos)
|
||
return input;
|
||
else
|
||
return encryptDecrypt(input, "Rindro-Aurora");
|
||
}
|
||
|
||
static void ReloadingScript(HSQUIRRELVM v, std::string FilePath)
|
||
{
|
||
// 爬取出所有的脚本文件
|
||
std::vector<std::string> vec = Tool::GetListFilesR(FilePath);
|
||
std::vector<std::pair<std::string, std::string>> SquirrelFilePath;
|
||
// std::map<std::string, std::string>SquirrelFilePath;
|
||
|
||
for (auto it = vec.cbegin(); it != vec.cend(); ++it)
|
||
{
|
||
std::string FileName = FilePath + *it;
|
||
// std::cout << FileName << std::endl;
|
||
if (FileName.find(".nut") == std::string::npos && FileName.find(".sut") == std::string::npos)
|
||
continue;
|
||
std::fstream F;
|
||
F.open((FileName).c_str(), std::ios::in);
|
||
std::stringstream ContentStringStream;
|
||
ContentStringStream << F.rdbuf();
|
||
std::string ContentString(ContentStringStream.str());
|
||
F.close();
|
||
std::string RealContentString = IsencryptDecrypt(ContentString, FileName);
|
||
SquirrelFilePath.push_back({ FileName, RealContentString });
|
||
}
|
||
|
||
std::map<std::string, std::string> SquirrelLastFilePath;
|
||
|
||
for (auto it = SquirrelFilePath.begin(); it != SquirrelFilePath.end(); it++)
|
||
{
|
||
std::string Sourcename = it->first;
|
||
std::string ContentString = it->second;
|
||
if (SQ_SUCCEEDED(sq_compilebuffer(v, (SQChar*)(ContentString.c_str()), ContentString.length(), (SQChar*)(Sourcename.c_str()), true)))
|
||
{
|
||
sq_pushroottable(v);
|
||
sq_call(v, 1, 1, 1);
|
||
sq_pop(v, 1);
|
||
}
|
||
else
|
||
{
|
||
SquirrelLastFilePath[Sourcename] = ContentString;
|
||
}
|
||
}
|
||
|
||
while (SquirrelLastFilePath.size() > 0)
|
||
{
|
||
std::map<std::string, std::string> FailMapBuffer;
|
||
for (auto it = SquirrelLastFilePath.begin(); it != SquirrelLastFilePath.end(); it++)
|
||
{
|
||
std::string Sourcename = it->first;
|
||
std::string ContentString = it->second;
|
||
|
||
if (SQ_SUCCEEDED(sq_compilebuffer(v, (SQChar*)(ContentString.c_str()), ContentString.length(), (SQChar*)(Sourcename.c_str()), true)))
|
||
{
|
||
sq_pushroottable(v);
|
||
if (SQ_FAILED(sq_call(v, 1, 1, 1)))
|
||
{
|
||
FailMapBuffer[Sourcename] = ContentString;
|
||
};
|
||
sq_pop(v, 1);
|
||
};
|
||
}
|
||
|
||
SquirrelLastFilePath.clear();
|
||
if (FailMapBuffer.size() > 0)
|
||
{
|
||
for (auto it = FailMapBuffer.begin(); it != FailMapBuffer.end(); it++)
|
||
{
|
||
std::string Sourcename = it->first;
|
||
std::string ContentString = it->second;
|
||
SquirrelLastFilePath[Sourcename] = ContentString;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
// 十六进制字符串转字节数组(完整实现)
|
||
std::vector<unsigned char> hex_to_bytes(const std::string& hex_str) {
|
||
std::vector<unsigned char> bytes;
|
||
// 检查Hex字符串长度是否为偶数
|
||
if (hex_str.size() % 2 != 0) {
|
||
throw std::invalid_argument("Hex string length must be even");
|
||
}
|
||
// 逐字节转换
|
||
for (size_t i = 0; i < hex_str.size(); i += 2) {
|
||
std::string byte_str = hex_str.substr(i, 2);
|
||
char* end_ptr;
|
||
unsigned long byte_val = strtol(byte_str.c_str(), &end_ptr, 16);
|
||
// 验证转换有效性
|
||
if (*end_ptr != '\0' || byte_val > 0xFF) {
|
||
throw std::invalid_argument("Invalid hex character: " + byte_str);
|
||
}
|
||
bytes.push_back(static_cast<unsigned char>(byte_val));
|
||
}
|
||
return bytes;
|
||
}
|
||
|
||
// 移除PKCS5Padding填充
|
||
std::string unpad_pkcs5(const std::string& data) {
|
||
if (data.empty()) return "";
|
||
unsigned char pad_len = data.back();
|
||
// 验证填充合法性:填充长度需≤块大小,且所有填充字节一致
|
||
if (pad_len > AES_BLOCK_SIZE || pad_len == 0) return data;
|
||
for (size_t i = data.size() - pad_len; i < data.size(); ++i) {
|
||
if (static_cast<unsigned char>(data[i]) != pad_len) {
|
||
return data; // 填充非法,返回原数据
|
||
}
|
||
}
|
||
return data.substr(0, data.size() - pad_len);
|
||
}
|
||
|
||
// AES-ECB解密(输入:二进制数据;密钥:固定字节数组)
|
||
std::string aes_ecb_decrypt(const std::vector<unsigned char>& encrypted_data) {
|
||
// 用户提供的密钥(16字节,AES-128)
|
||
const signed char keyBytes[] = { 92, -2, -65, 10, 118, 103, -68, 72, -23, 124, -103, -39, 0, -128, 31, -107 };
|
||
const int key_len = sizeof(keyBytes);
|
||
|
||
// 初始化AES解密密钥
|
||
AES_KEY aes_key;
|
||
if (AES_set_decrypt_key(reinterpret_cast<const unsigned char*>(keyBytes),
|
||
key_len * 8, // 16*8=128位
|
||
&aes_key) != 0) {
|
||
throw std::runtime_error("Invalid AES key");
|
||
}
|
||
|
||
// ECB模式分块解密(AES块大小16字节)
|
||
size_t data_len = encrypted_data.size();
|
||
std::vector<unsigned char> decrypt_buf(data_len);
|
||
for (size_t i = 0; i < data_len; i += AES_BLOCK_SIZE) {
|
||
// 最后一块不足16字节时补0(ECB要求块对齐,实际加密数据应为块大小整数倍)
|
||
unsigned char block[AES_BLOCK_SIZE] = { 0 };
|
||
memcpy(block, encrypted_data.data() + i,
|
||
std::min(static_cast<size_t>(AES_BLOCK_SIZE), data_len - i));
|
||
|
||
AES_ecb_encrypt(block, decrypt_buf.data() + i, &aes_key, AES_DECRYPT);
|
||
}
|
||
|
||
// 移除PKCS5填充并返回字符串
|
||
return unpad_pkcs5(std::string(reinterpret_cast<char*>(decrypt_buf.data()), data_len));
|
||
}
|
||
|
||
static void LoadScript(HSQUIRRELVM v, bool Flag) {
|
||
|
||
std::ifstream f("/dp_s/_DPS_/FileConfig.json");
|
||
nlohmann::json Jso = nlohmann::json::parse(f);
|
||
f.close();
|
||
|
||
//加载基础脚本
|
||
for (const auto& elem : Jso["BaseScript"]) {
|
||
if (elem.is_string()) {
|
||
std::string line = elem.get<std::string>();
|
||
std::string file_path = "/dp_s/_DPS_/_Core/" + line;
|
||
|
||
// 以二进制模式打开加密文件
|
||
std::ifstream F(file_path, std::ios::binary | std::ios::in);
|
||
if (!F.is_open()) {
|
||
spdlog::error("无法打开脚本文件: {}", file_path);
|
||
continue;
|
||
}
|
||
|
||
// 直接读取二进制数据到vector<unsigned char>
|
||
std::vector<unsigned char> encrypted_data(
|
||
(std::istreambuf_iterator<char>(F)),
|
||
std::istreambuf_iterator<char>()
|
||
);
|
||
F.close();
|
||
|
||
std::string ContentString;
|
||
//外网解密版本
|
||
if (!Flag) {
|
||
try {
|
||
// 直接解密二进制数据
|
||
ContentString = aes_ecb_decrypt(encrypted_data);
|
||
if (ContentString.empty()) {
|
||
spdlog::warn("解密结果为空: {}", file_path);
|
||
continue;
|
||
}
|
||
}
|
||
catch (const std::runtime_error& e) {
|
||
spdlog::error("AES解密失败 {}: {}", file_path, e.what());
|
||
continue;
|
||
}
|
||
}
|
||
//本地不解密版本
|
||
else {
|
||
ContentString = std::string(reinterpret_cast<const char*>(encrypted_data.data()), encrypted_data.size());
|
||
}
|
||
|
||
|
||
if (SQ_SUCCEEDED(sq_compilebuffer(v, (SQChar*)(ContentString.c_str()), ContentString.length(), (SQChar*)(line.c_str()), true)))
|
||
{
|
||
sq_pushroottable(v);
|
||
sq_call(v, 1, SQTrue, SQFalse);
|
||
sq_pop(v, 1);
|
||
}
|
||
}
|
||
}
|
||
//加载项目脚本
|
||
for (const auto& elem : Jso["ProjectScript"]) {
|
||
for (const auto& path : elem["Script"]) {
|
||
if (path.is_string()) {
|
||
std::string line = path.get<std::string>();
|
||
std::string ContentString = "";
|
||
std::fstream F;
|
||
F.open(("/dp_s/_DPS_/_BuiltProject/" + line).c_str(), std::ios::in);
|
||
if (F.is_open()) {
|
||
std::stringstream ContentStringStream;
|
||
ContentStringStream << F.rdbuf();
|
||
ContentString = (ContentStringStream.str());
|
||
F.close();
|
||
}
|
||
if (SQ_SUCCEEDED(sq_compilebuffer(v, (SQChar*)(ContentString.c_str()), ContentString.length(), (SQChar*)(line.c_str()), true)))
|
||
{
|
||
sq_pushroottable(v);
|
||
sq_call(v, 1, SQTrue, SQFalse);
|
||
sq_pop(v, 1);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
static SQInteger SqReloadScript(HSQUIRRELVM v)
|
||
{
|
||
|
||
if ((access("/dp_s/lib/db.ini", F_OK) != -1))
|
||
{
|
||
LoadScript(v, true);
|
||
}
|
||
else
|
||
{
|
||
LoadScript(v, false);
|
||
//读取网络单发脚本
|
||
ReqSquirrelScript(v);
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static std::string GetLocalScriptVersion() {
|
||
std::ifstream file("/dp_s/_DPS_/version"); // 打开文件
|
||
if (!file.is_open()) { // 检查文件是否成功打开
|
||
return "-1";
|
||
}
|
||
std::string content((std::istreambuf_iterator<char>(file)),
|
||
std::istreambuf_iterator<char>());
|
||
content.pop_back();
|
||
return content;
|
||
}
|
||
|
||
static std::string GetRemoteScriptVersion() {
|
||
|
||
try {
|
||
asio::io_context io_context;
|
||
HttpsClient client(io_context);
|
||
// 配置GET请求
|
||
std::map<std::string, std::string> headers = {
|
||
{"User-Agent", "AsioHttpsClient/Sync"},
|
||
{"Accept", "application/json"},
|
||
{"Content-Type", "application/json"} // POST必须的头
|
||
};
|
||
client.setRequest("dps.senzo.online", "/script/getBaseUpdateTime", "POST", "", headers);
|
||
|
||
// 同步发送请求(阻塞等待结果)
|
||
HttpsClient::SyncResponse res = client.sendRequestSync();
|
||
|
||
// 处理结果
|
||
if (res.error.empty()) {
|
||
spdlog::info("获取到服务器最新脚本,版本为:{}", res.response);
|
||
return res.response;
|
||
}
|
||
else {
|
||
spdlog::info("请求服务器最新脚本失败,使用本地缓存脚本! 错误原因: {}", res.error);
|
||
return "error";
|
||
}
|
||
}
|
||
catch (const std::exception& e) {
|
||
spdlog::info("请求服务器最新脚本失败,使用本地缓存脚本! 错误原因: {}", e.what());
|
||
return "error";
|
||
}
|
||
}
|
||
|
||
int executeCommand(const std::string& cmd) {
|
||
pid_t pid = fork();
|
||
if (pid == -1) return -1;
|
||
|
||
if (pid == 0) { // 子进程
|
||
// 清除LD_PRELOAD
|
||
unsetenv("LD_PRELOAD");
|
||
// 执行命令(需拆分命令和参数)
|
||
execl("/bin/sh", "sh", "-c", cmd.c_str(), nullptr);
|
||
exit(1); // 若exec失败
|
||
}
|
||
|
||
// 父进程等待子进程结束
|
||
int status;
|
||
waitpid(pid, &status, 0);
|
||
return WEXITSTATUS(status);
|
||
}
|
||
|
||
static void DownLoadScript(std::string RemoteVersion) {
|
||
try {
|
||
asio::io_context io_context;
|
||
HttpsClient client(io_context);
|
||
|
||
// 设置下载请求
|
||
client.setRequest("dps.senzo.online", "/script/getBaseSutZip", "GET");
|
||
|
||
// 下载文件
|
||
HttpsClient::FileDownloadResult result = client.downloadFileSync("/dp_s/_DPS_/cache.tar.gz");
|
||
|
||
if (!result.error.empty()) {
|
||
spdlog::info("下载服务器最新脚本失败,使用本地缓存脚本! 错误原因: {}", result.error);
|
||
}
|
||
else {
|
||
spdlog::info("服务器最新脚本下载完成! 正在解压...");
|
||
std::string command = "rm -rf \"" + std::string("/dp_s/_DPS_/_Core") + "\"";
|
||
int Res1 = executeCommand(command.c_str());
|
||
|
||
std::string tarPath = "/dp_s/_DPS_/cache.tar.gz";
|
||
std::string destDir = "/dp_s/_DPS_";
|
||
std::string tarCmd = " tar -xzf \"" + tarPath + "\" -C \"" + destDir + "\"";
|
||
int Res2 = executeCommand(tarCmd.c_str());
|
||
|
||
if (Res2 == 0) {
|
||
spdlog::info("解压成功!");
|
||
}
|
||
else {
|
||
spdlog::info("在解压过程中发生了一个预期之外的错误,请联系开发者!");
|
||
}
|
||
|
||
std::ofstream file("/dp_s/_DPS_/version", std::ios::out);
|
||
if (!file.is_open()) {
|
||
spdlog::info("发生了一个预期之外的错误,请联系开发者!");
|
||
}
|
||
file << RemoteVersion << std::endl;
|
||
file.close();
|
||
}
|
||
}
|
||
catch (const std::exception& e) {
|
||
spdlog::info("下载服务器最新脚本失败,使用本地缓存脚本! 错误原因: {}", e.what());
|
||
}
|
||
}
|
||
|
||
void InitSquirrel()
|
||
{
|
||
v = sq_open(8192); // 创建虚拟机,其栈的初始大小为1024
|
||
|
||
sq_pushroottable(v);
|
||
sqstd_register_bloblib(v);
|
||
sqstd_register_iolib(v);
|
||
sqstd_register_systemlib(v);
|
||
sqstd_register_mathlib(v);
|
||
sqstd_register_stringlib(v);
|
||
// sqstd_register_dio(v);
|
||
|
||
sqstd_seterrorhandlers(v);
|
||
|
||
// 初始化日志器
|
||
setupLogger();
|
||
sq_setprintfunc(v, printfunc, errorfunc); // sets the print function
|
||
|
||
// 获取根表(全局作用域)
|
||
sq_pushroottable(v);
|
||
|
||
// 将DP_S_VERSION添加到全局表
|
||
sq_pushstring(v, "DP_S_VERSION", -1); // 键
|
||
sq_pushfloat(v, 25.332); // 值
|
||
sq_newslot(v, -3, SQFalse); // 在根表中创建slot
|
||
|
||
// 弹出根表,恢复堆栈
|
||
sq_pop(v, 1);
|
||
|
||
// 注册全局NutApi
|
||
GlobaRegisterSquirrel(v);
|
||
|
||
//非本地模式
|
||
if ((access("/dp_s/lib/db.ini", F_OK) == -1)) {
|
||
//获取本地脚本版本
|
||
std::string LocalVersion = GetLocalScriptVersion();
|
||
if (LocalVersion == "-1") {
|
||
spdlog::info("本地未检测到DP-S核心脚本,正在尝试联网获取最新脚本...");
|
||
}
|
||
else {
|
||
spdlog::info("本地DP-S核心脚本版本为{},正在检测是否有可用更新...", LocalVersion);
|
||
}
|
||
|
||
//获取服务器最新脚本版本
|
||
std::string RemoteVersion = GetRemoteScriptVersion();
|
||
if (RemoteVersion != "error") {
|
||
if (RemoteVersion != LocalVersion) {
|
||
spdlog::info("正在下载服务器最新脚本...");
|
||
//下载最新脚本
|
||
DownLoadScript(RemoteVersion);
|
||
}
|
||
else {
|
||
spdlog::info("当前服务器脚本已是最新版本无需更新.");
|
||
}
|
||
}
|
||
}
|
||
|
||
SqReloadScript(v);
|
||
// sq_pushroottable(v);
|
||
// sq_pushstring(v, "sq_ReloadScript", -1);
|
||
// sq_newclosure(v, SqReloadScript, 0); // create a new function
|
||
// sq_newslot(v, -3, SQFalse);
|
||
// sq_pop(v, 1); // pops the root table
|
||
}
|