remove files

This commit is contained in:
HolographicHat
2022-07-13 20:42:24 +08:00
parent fb8c941b57
commit 31e23de4d6
24 changed files with 0 additions and 1452 deletions

3
.gitmodules vendored
View File

@@ -1,3 +0,0 @@
[submodule "native/src/wmi"]
path = native/src/wmi
url = https://github.com/HolographicHat/wmi

179
app.js
View File

@@ -1,179 +0,0 @@
const proxy = require("udp-proxy")
const cp = require("child_process")
const appcenter = require("./appcenter")
const regionServer = require("./regionServer")
const cloud = require("./generated/secret")
const {
initConfig, splitPacket, upload, decodeProto, log, setupHost, KPacket, debug, checkUpdate,
brotliCompressSync, brotliDecompressSync, checkGameIsRunning, checkPortIsUsing
} = require("./utils")
const { exportData } = require("./export")
const { enablePrivilege, pause } = require("./generated/native")
const onExit = () => {
setupHost(true)
console.log("按任意键退出")
pause()
};
(async () => {
try {
process.on("SIGHUP", () => setupHost(true))
process.on("unhandledRejection", (reason, promise) => {
console.log("Unhandled Rejection at: ", promise, "\n0Reason:", reason)
})
process.on("uncaughtException", (err, origin) => {
appcenter.uploadError(err, true)
console.log(err)
console.log(`Origin: ${origin}`)
process.exit(1)
})
process.on("exit", onExit)
process.on("SIGINT", onExit)
try {
enablePrivilege()
} catch (e) {
console.log("请使用管理员身份运行此程序")
process.exit(-1)
}
appcenter.startup()
let conf = await initConfig()
cloud.init(conf)
checkPortIsUsing()
checkGameIsRunning()
log("检查更新")
await checkUpdate()
let gameProcess
let unexpectedExit = true
regionServer.create(conf,() => {
setupHost()
gameProcess = cp.execFile(conf.executable, { cwd: conf.path },err => {
if (err !== null && !err.killed) {
throw err
}
})
log("启动原神")
gameProcess.on("exit", () => {
if (unexpectedExit) {
console.log("游戏进程异常退出")
process.exit(0)
}
})
},(ip, port, hServer) => {
let login = false
let cache = new Map()
let lastRecvTimestamp = 0
// noinspection JSUnusedGlobalSymbols
const options = {
address: ip,
port: port,
localaddress: "127.0.0.1",
localport: 45678,
middleware: {
message: (msg, sender, next) => {
const buf = Buffer.from(msg)
if (!(login && buf.readUInt8(8) === 0x51)) {
next(msg, sender)
}
},
proxyMsg: (msg, sender, peer, next) => {
try { next(msg, sender, peer) } catch (e) {}
}
}
}
let monitor;
let stopped = false
const createMonitor = () => {
monitor = setInterval(async () => {
if (login && lastRecvTimestamp + 2 < parseInt(Date.now() / 1000) && !stopped) {
stopped = true
unexpectedExit = false
server.close(() => {})
hServer.close()
gameProcess.kill()
clearInterval(monitor)
setupHost(true)
log("正在处理数据,请稍后...")
let packets = Array.from(cache.values())
cache.clear()
packets.sort((a, b) => a.frg - b.frg)
.sort((a, b) => a.sn - b.sn)
.filter(i => i.data.byteLength !== 0)
.forEach(i => {
const psn = i.sn + i.frg
cache.has(psn) ? (() => {
const arr = cache.get(psn)
arr.push(i.data)
cache.set(psn, arr)
})() : cache.set(psn, [i.data])
})
packets = Array.from(cache.values())
.map(arr => {
const data = Buffer.concat(arr)
const len = Buffer.alloc(4)
len.writeUInt32LE(data.length)
return Buffer.concat([len, data])
})
const merged = Buffer.concat(packets)
const compressed = brotliCompressSync(merged)
const response = await upload(compressed)
const data = brotliDecompressSync(response.data)
if (response.status !== 200) {
log(`发生错误: ${data}`)
log(`请求ID: ${response.headers["x-api-requestid"]}`)
log("请联系开发者以获取帮助")
} else {
const proto = await decodeProto(data, "Notify1")
await exportData(proto)
}
process.exit(0)
}
},1000)
}
const server = proxy.createServer(options)
server.on("message", (msg, _) => {
if (msg.byteLength > 500) {
login = true
}
})
server.on("error", err => console.log(`Proxy error: ${err.message}` + err.message))
server.on("proxyError", err => console.log(`Proxy error: ${err.message}` + err.message))
server.on("proxyMsg", (msg, _) => {
lastRecvTimestamp = parseInt(Date.now() / 1000)
let buf = Buffer.from(msg)
if (buf.byteLength <= 20) {
switch(buf.readUInt32BE(0)) {
case 325:
createMonitor()
debug("Connection established.")
break
case 404:
debug("Connection terminated.")
lastRecvTimestamp = parseInt(Date.now() / 1000) - 2333
break
default:
console.log(`Unhandled: ${buf.toString("hex")}`)
process.exit(2)
break
}
return
}
splitPacket(buf).forEach(sb => {
if (sb.readUInt8(8) === 0x51) {
const p = new KPacket(sb)
if (!cache.has(p.hash)) cache.set(p.hash, p)
}
})
})
return server
}).then(() => log("加载完毕"))
} catch (e) {
console.log(e)
if (e instanceof Error) {
appcenter.uploadError(e, true)
} else {
appcenter.uploadError(Error(e), true)
}
process.exit(1)
}
})()

View File

@@ -1,116 +0,0 @@
const axios = require("axios")
const crypto = require("crypto")
const { version } = require("./version")
const { getDeviceID, getDeviceInfo } = require("./generated/native")
const getTimestamp = (d = new Date()) => {
const p = i => i.toString().padStart(2, "0")
return `${d.getUTCFullYear()}-${p(d.getUTCMonth() + 1)}-${p(d.getUTCDate())}T${p(d.getUTCHours())}:${p(d.getUTCMinutes())}:${p(d.getUTCSeconds())}.${p(d.getUTCMilliseconds())}Z`
}
const queue = []
const session = crypto.randomUUID()
const key = "648b83bf-d439-49bd-97f4-e1e506bdfe39"
const install = (() => {
const id = getDeviceID()
return id === undefined ? crypto.randomUUID() : id
})()
const device = (() => {
const info = getDeviceInfo()
info.appBuild = version.code
info.appVersion = version.name
info.sdkName = "appcenter.wpf.netcore"
info.sdkVersion = "4.5.0"
info.osName = "WINDOWS"
info.appNamespace = "default"
return info
})()
const upload = () => {
if (queue.length > 0) {
const logs = []
for (let i = 0; i <= queue.length; i++) {
logs.push(queue.pop())
}
const data = JSON.stringify({"logs": logs})
axios.post("https://in.appcenter.ms/logs?api-version=1.0.0", data,{
headers: {
"App-Secret": key,
"Install-ID": install
}
}).catch(_ => {}).then()
}
}
const uploadError = (err, fatal) => {
const eid = crypto.randomUUID()
const reportJson = process.report.getReport(err)
const reportAttachment = {
type: "errorAttachment",
device: device,
timestamp: getTimestamp(),
id: crypto.randomUUID(),
sid: session,
errorId: eid,
contentType: "application/json",
fileName: "report.json",
data: Buffer.from(JSON.stringify(reportJson, null, 2), "utf-8").toString("base64")
}
// noinspection JSUnresolvedVariable
const errorContent = {
type: "managedError",
id: eid,
sid: session,
architecture: "AMD64",
userId: install,
fatal: fatal,
processId: process.pid,
processName: process.argv0.replaceAll("\\", "/").split("/").pop(),
timestamp: getTimestamp(),
appLaunchTimestamp: getTimestamp(new Date(Date.now() - process.uptime())),
exception: {
"type": err.name,
"message": err.message,
"stackTrace": err.stack
},
device: device
}
queue.push(errorContent, reportAttachment)
upload()
}
const uploadEvent = (name, prop) => {
const content = {
type: "event",
id: crypto.randomUUID(),
sid: session,
name: name,
properties: prop,
timestamp: getTimestamp(),
device: device
}
queue.push(content)
}
const startup = () => {
queue.push({
type: "startService",
services: [ "Analytics","Crashes" ],
timestamp: getTimestamp(),
device: device
})
queue.push({
type: "startSession",
sid: session,
timestamp: getTimestamp(),
device: device
})
upload()
setInterval(() => upload(), 5000)
}
module.exports = {
startup, upload, uploadError, uploadEvent
}

162
export.js
View File

@@ -1,162 +0,0 @@
const fs = require("fs")
const axios = require("axios")
const readline = require("readline")
const { version } = require("./version")
const { loadCache, log, openUrl } = require("./utils")
const { checkSnapFastcall, copyToClipboard } = require("./generated/native")
const exportToSeelie = proto => {
const out = { achievements: {} }
proto.list.filter(a => a.status === 3 || a.status === 2).forEach(({id}) => {
out.achievements[id === 81222 ? 81219 : id] = { done: true }
})
const fp = `./export-${Date.now()}-seelie.json`
fs.writeFileSync(fp, JSON.stringify(out))
log(`导出为文件: ${fp}`)
}
const exportToPaimon = async proto => {
const out = { achievement: {} }
const data = await loadCache()
proto.list.filter(a => a.status === 3 || a.status === 2).forEach(({id}) => {
const gid = data["a"][id]["g"]
if (out.achievement[gid] === undefined) {
out.achievement[gid] = {}
}
out.achievement[gid][id === 81222 ? 81219 : id] = true
})
const fp = `./export-${Date.now()}-paimon.json`
fs.writeFileSync(fp, JSON.stringify(out))
log(`导出为文件: ${fp}`)
}
const UIAF = proto => {
const out = {
info: {
export_app: "YaeAchievement",
export_timestamp: Date.now(),
export_app_version: version.name,
uiaf_version: "v1.0"
},
list: []
}
proto.list.filter(a => a.status === 3 || a.status === 2).forEach(({id, finishTimestamp, current}) => {
out.list.push({
id: id,
timestamp: finishTimestamp,
current: current
})
})
return out
}
const exportToSnapGenshin = async proto => {
if (checkSnapFastcall()) {
const result = UIAF(proto)
const json = JSON.stringify(result)
copyToClipboard(json)
openUrl(`snapgenshin://achievement/import/uiaf`)
log("在 SnapGenshin 进行下一步操作")
} else {
log("请更新 SnapGenshin 后重试")
}
}
const exportToCocogoat = async proto => {
const result = UIAF(proto)
const response = await axios.post(`https://77.cocogoat.work/v1/memo?source=${encodeURI("全部成就")}`, result).catch(_ => {
console.log("网络错误,请检查网络后重试 (26-1)")
process.exit(261)
})
if (response.status !== 201) {
console.log(`API StatusCode 错误,请联系开发者以获取帮助 (26-2-${response.status})`)
process.exit(262)
}
const retcode = openUrl(`https://cocogoat.work/achievement?memo=${response.data.key}`)
if (retcode > 32) {
log("在浏览器内进行下一步操作")
} else {
log(`打开此链接以进行下一步操作: https://cocogoat.work/achievement?memo=${response.data.key}`)
}
}
const exportToCsv = async proto => {
const data = await loadCache()
const outputLines = ["ID,状态,特辑,名称,描述,当前进度,目标进度,完成时间"]
const getStatusText = i => {
switch (i) {
case 1: return "未完成"
case 2: return "已完成,未领取奖励"
case 3: return "已完成"
default: return "未知"
}
}
const getTime = ts => {
const d = new Date(parseInt(`${ts}000`))
const p = i => i.toString().padStart(2, "0")
return `${d.getFullYear()}/${p(d.getMonth() + 1)}/${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`
}
const bl = [84517]
proto.list.forEach(({current, finishTimestamp, id, status, require}) => {
if (!bl.includes(id)) {
const curAch = data["a"][id] === undefined ? (() => {
console.log(`Error get id ${id} in excel`)
return {
g: "未知",
n: "未知",
d: "未知"
}
})() : data["a"][id]
outputLines.push(`${id},${getStatusText(status)},${data["g"][curAch.g]},${curAch.n},${curAch.d},${status !== 1 ? current === 0 ? require : current : current},${require},${status === 1 ? "" : getTime(finishTimestamp)}`)
}
})
const fp = `./export-${Date.now()}.csv`
fs.writeFileSync(fp, `\uFEFF${outputLines.join("\n")}`)
log(`导出为文件: ${fp}`)
}
const exportData = async proto => {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
})
const question = (query) => new Promise(resolve => {
rl.question(query, resolve)
})
const chosen = await question(
[
"导出至: ",
"[0] 椰羊 (https://cocogoat.work/achievement)",
"[1] SnapGenshin",
"[2] Paimon.moe",
"[3] Seelie.me",
"[4] 表格文件 (默认)",
"输入一个数字(0-4): "
].join("\n")
)
rl.close()
switch (chosen.trim()) {
case "0":
await exportToCocogoat(proto)
break
case "1":
await exportToSnapGenshin(proto)
break
case "2":
await exportToPaimon(proto)
break
case "3":
await exportToSeelie(proto)
break
case "raw":
fs.writeFileSync(`./export-${Date.now()}-raw.json`, JSON.stringify(proto,null,2))
log("OK")
break
default:
await exportToCsv(proto)
}
}
module.exports = {
exportData
}

6
native/.gitignore vendored
View File

@@ -1,6 +0,0 @@
CMakeLists.txt
.idea
cmake-build-debug
node_modules
build
src/homu.cpp

View File

@@ -1,48 +0,0 @@
{
"targets": [
{
"target_name": "native",
"sources": [
"src/main.cc",
"src/utils.h",
"src/utils.cc",
"src/define.h",
"src/wmi/wmi.cpp",
"src/wmi/wmi.hpp",
"src/wmi/unistd.h",
"src/VMProtectSDK.h",
"src/wmi/wmiresult.cpp",
"src/wmi/wmiresult.hpp",
"src/wmi/wmiclasses.hpp",
"src/wmi/wmiexception.hpp",
"src/registry/registry.hpp"
],
"cflags!": [
"-fno-exceptions"
],
"cflags_cc!": [
"-fno-exceptions"
],
"defines": [
"NAPI_DISABLE_CPP_EXCEPTIONS"
],
"include_dirs": [
"<!(node -p \"require('node-addon-api').include_dir\")"
],
"msvs_settings": {
"VCCLCompilerTool": {
"AdditionalOptions": [
"-std:c++latest",
"-DUNICODE",
"-sdl",
"-O2",
"-Ot",
"-Oi",
"-GL",
"-Gw"
]
}
}
}
]
}

View File

@@ -1,17 +0,0 @@
{
"name": "genshin-export-native",
"version": "1.0.0",
"description": "",
"scripts": {
"build": "node-gyp build && copy .\\build\\Release\\native.node ..\\genshin-export\\generated",
"rebuild": "node-gyp rebuild && copy .\\build\\Release\\native.node ..\\genshin-export\\generated",
"build-for-win7": "node-gyp rebuild --target=v14.17.0 && copy .\\build\\Release\\native.node ..\\genshin-export\\generated"
},
"gypfile": true,
"devDependencies": {
"node-gyp": "^9.0.0"
},
"dependencies": {
"node-addon-api": "^4.3.0"
}
}

View File

@@ -1,15 +0,0 @@
#pragma once
#include <string>
#include <iostream>
#include <napi.h>
#include <Windows.h>
#include <TlHelp32.h>
using std::string, std::wstring, std::cout, std::to_string, std::unique_ptr, std::make_unique;
using Napi::Object, Napi::Env, Napi::Function, Napi::Value, Napi::CallbackInfo, Napi::TypeError, Napi::Error;
typedef unsigned char byte;
typedef unsigned int ui;
typedef unsigned long ul;
typedef unsigned long long ull;

View File

@@ -1,11 +0,0 @@
//
// Created by holog on 2022/5/20.
//
#include "define.h"
Value Initialize(const CallbackInfo &info);
#ifndef GENSHIN_EXPORT_NATIVE_HOMU_H
#define GENSHIN_EXPORT_NATIVE_HOMU_H
#endif //GENSHIN_EXPORT_NATIVE_HOMU_H

View File

@@ -1,250 +0,0 @@
//#include "homu.h"
#include "utils.h"
#include "define.h"
#include "wmi/wmi.hpp"
#include "wmi/wmiclasses.hpp"
#include "registry/registry.hpp"
#include <conio.h>
#include <iphlpapi.h>
#pragma comment(lib,"ws2_32.lib")
#pragma comment(lib,"iphlpapi.lib")
using Wmi::Win32_ComputerSystem, Wmi::Win32_OperatingSystem, Wmi::retrieveWmi;
namespace native {
Value CheckGameIsRunning(const CallbackInfo &info) {
Env env = info.Env();
if (info.Length() != 1 || !info[0].IsString()) {
TypeError::New(env, "Wrong arguments").ThrowAsJavaScriptException();
return env.Undefined();
}
wstring name = StringToWString(info[0].As<Napi::String>().Utf8Value());
bool isRunning = false;
PROCESSENTRY32 entry;
entry.dwSize = sizeof(entry);
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
if (Process32First(snapshot, &entry) == TRUE) {
while (Process32Next(snapshot, &entry) == TRUE) {
if (wstring(entry.szExeFile) == name) {
isRunning = true;
}
}
}
CloseHandle(snapshot);
return Napi::Boolean::New(env, isRunning);
}
Value SelectGameExecutable(const CallbackInfo &info) {
Env env = info.Env();
Napi::String path;
if (OpenFile(env, path) != ERROR_SUCCESS) {
Error::New(env, "Failed to open file: " + to_string(CommDlgExtendedError())).ThrowAsJavaScriptException();
return env.Undefined();
}
return path;
}
Value WhoUseThePort(const CallbackInfo &info) {
Env env = info.Env();
if (info.Length() != 1 || !info[0].IsNumber()) {
TypeError::New(env, "Wrong arguments").ThrowAsJavaScriptException();
return env.Undefined();
}
DWORD dwSize = 0;
PMIB_TCPTABLE_OWNER_PID pTcpTable = nullptr;
GetExtendedTcpTable(pTcpTable, &dwSize, TRUE,AF_INET,TCP_TABLE_OWNER_PID_ALL,0);
pTcpTable = (PMIB_TCPTABLE_OWNER_PID)new byte[dwSize];
if(GetExtendedTcpTable(pTcpTable,&dwSize,TRUE,AF_INET,TCP_TABLE_OWNER_PID_ALL,0) != NO_ERROR) {
Error::New(env, "GetExtendedTcpTable failed").ThrowAsJavaScriptException();
return env.Undefined();
}
int port = info[0].As<Napi::Number>().Int32Value();
auto nNum = (int)pTcpTable->dwNumEntries;
DWORD pid = 0;
for(int i = 0; i < nNum; i++) {
if (htons(pTcpTable->table[i].dwLocalPort) == port) {
pid = pTcpTable->table[i].dwOwningPid;
break;
}
}
delete pTcpTable;
Value ret = env.Undefined();
if (pid != 0) {
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
if (hProcess == nullptr) {
Error::New(env, "OpenProcess error: " + to_string(GetLastError())).ThrowAsJavaScriptException();
return env.Undefined();
}
TCHAR fnBuf[MAX_PATH];
DWORD length = MAX_PATH;
if (QueryFullProcessImageName(hProcess, 0, fnBuf, &length) == 0) {
Error::New(env, "QueryFullProcessImageName error: " + to_string(GetLastError())).ThrowAsJavaScriptException();
return env.Undefined();
}
Object obj = Object::New(env);
obj.Set("pid", Napi::Number::New(env, pid));
obj.Set("path", Napi::String::New(env, WStringToString(fnBuf)));
ret = obj;
}
return ret;
}
Value GetDeviceID(const CallbackInfo &info) {
Env env = info.Env();
wstring wd;
if (RegUtils::GetString(HKEY_CURRENT_USER, L"SOFTWARE\\miHoYoSDK", L"MIHOYOSDK_DEVICE_ID", wd) != ERROR_SUCCESS) {
return env.Undefined();
}
string id = WStringToString(wd);
return Napi::String::New(env, id.substr(0, 8) + "-" + id.substr(8, 4) + "-" + id.substr(12, 4) + "-" + id.substr(16, 4) + "-" + id.substr(20, 12));
}
Value GetDeviceInfo(const CallbackInfo &info) {
Env env = info.Env();
HDC desktop = GetDC(nullptr);
int sw = GetDeviceCaps(desktop, DESKTOPHORZRES);
int sh = GetDeviceCaps(desktop, DESKTOPVERTRES);
ReleaseDC(nullptr, desktop);
DWORD buildNum = RegUtils::GetInt(env, HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", L"UBR", 0);
wstring locale;
if (RegUtils::GetString(HKEY_CURRENT_USER, L"Control Panel\\International", L"LocaleName", locale) != ERROR_SUCCESS) {
locale = L"zh-CN";
}
wstring country;
if (RegUtils::GetString(HKEY_CURRENT_USER, L"Control Panel\\International\\Geo", L"Name", country) != ERROR_SUCCESS) {
country = L"CN";
}
Object obj = Object::New(env);
obj.Set("locale", Napi::String::New(env, WStringToString(locale)));
obj.Set("screenSize", Napi::String::New(env, to_string(sw) + "x" + to_string(sh)));
obj.Set("carrierCountry", Napi::String::New(env, WStringToString(country)));
try {
auto computer = retrieveWmi<Win32_ComputerSystem>();
obj.Set("model", Napi::String::New(env, computer.Model));
obj.Set("oemName", Napi::String::New(env, computer.Manufacturer));
} catch (Wmi::WmiException &e) {
obj.Set("model", Napi::String::New(env, "Unknown"));
obj.Set("oemName", Napi::String::New(env, "Unknown"));
}
try {
auto os = retrieveWmi<Win32_OperatingSystem>();
string osv = os.Version;
obj.Set("osBuild", Napi::String::New(env, osv + "." + to_string(buildNum)));
obj.Set("osVersion", Napi::String::New(env, osv));
obj.Set("timeZoneOffset", Napi::Number::New(env, os.CurrentTimeZone));
} catch (Wmi::WmiException &e) {
obj.Set("osBuild", Napi::String::New(env, "Unknown"));
obj.Set("osVersion", Napi::String::New(env, "Unknown"));
obj.Set("timeZoneOffset", Napi::Number::New(env, 480));
}
return obj;
}
Value EnablePrivilege(const CallbackInfo &info) {
Env env = info.Env();
HANDLE hToken;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) {
Error::New(env, "OpenProcessToken error: " + to_string(GetLastError())).ThrowAsJavaScriptException();
return env.Undefined();
}
TOKEN_PRIVILEGES tp;
ZeroMemory(&tp, sizeof(tp));
tp.PrivilegeCount = 1;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (!LookupPrivilegeValue(nullptr, L"SeDebugPrivilege", &tp.Privileges[0].Luid)) {
Error::New(env, "LookupPrivilegeValue error: " + to_string(GetLastError())).ThrowAsJavaScriptException();
return env.Undefined();
}
if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), nullptr, nullptr)) {
Error::New(env, "AdjustTokenPrivileges error: " + to_string(GetLastError())).ThrowAsJavaScriptException();
return env.Undefined();
}
if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) {
Error::New(env, "The token does not have the specified privilege.").ThrowAsJavaScriptException();
return env.Undefined();
}
CloseHandle(hToken);
return env.Undefined();
}
Value CopyToClipboard(const CallbackInfo &info) {
Env env = info.Env();
string text = info[0].As<Napi::String>().Utf8Value();
if (OpenClipboard(nullptr)) {
EmptyClipboard();
HGLOBAL hg = GlobalAlloc(GMEM_MOVEABLE, text.length() + 1);
if (hg != nullptr) {
memcpy(GlobalLock(hg), text.c_str(), text.length() + 1);
GlobalUnlock(hg);
SetClipboardData(CF_TEXT, hg);
}
CloseClipboard();
}
return env.Undefined();
}
Value Pause(const CallbackInfo &info) {
while(!_kbhit()) {
Sleep(10);
}
return info.Env().Undefined();
}
Value OpenUrl(const CallbackInfo &info) {
Env env = info.Env();
wstring url = StringToWString(info[0].As<Napi::String>().Utf8Value());
HINSTANCE retcode = ShellExecute(GetConsoleWindow(), L"open", url.c_str(), nullptr, nullptr, SW_SHOWNORMAL);
return Napi::Number::New(env, (INT_PTR)retcode); // NOLINT(cppcoreguidelines-narrowing-conversions)
}
Value CheckSnapFastcall(const CallbackInfo &info) {
Env env = info.Env();
wstring queryResult;
RegUtils::GetString(HKEY_CLASSES_ROOT, L"snapgenshin", L"", queryResult);
return Napi::Boolean::New(env, wcscmp(queryResult.c_str(), L"URL:snapgenshin") == 0);
}
Value GetMACAddress(const CallbackInfo &info) {
Env env = info.Env();
ULONG ulOutBufLen = sizeof(IP_ADAPTER_INFO);
auto pAdapterInfo = (IP_ADAPTER_INFO *) malloc(ulOutBufLen);
if (GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) != ERROR_SUCCESS) {
pAdapterInfo = (IP_ADAPTER_INFO *) malloc(ulOutBufLen);
if (GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) != ERROR_SUCCESS) { return env.Undefined(); }
}
PIP_ADAPTER_INFO pAdapter = pAdapterInfo;
while (pAdapter) {
if (pAdapter->Address[0] == 0x00) {
pAdapter = pAdapter->Next;
continue;
}
char *result;
ToHex((char *)pAdapter->Address, 6, &result);
auto ret = Napi::String::New(env, result);
free(result);
free(pAdapterInfo);
return ret;
}
return env.Undefined();
}
Object init(Env env, Object exports) {
exports.Set("pause", Function::New(env, Pause));
exports.Set("openUrl", Function::New(env, OpenUrl));
//exports.Set("homuInit", Function::New(env, Initialize));
exports.Set("getDeviceID", Function::New(env, GetDeviceID));
exports.Set("getMACAddress", Function::New(env, GetMACAddress));
exports.Set("getDeviceInfo", Function::New(env, GetDeviceInfo));
exports.Set("whoUseThePort", Function::New(env, WhoUseThePort));
exports.Set("copyToClipboard", Function::New(env, CopyToClipboard));
exports.Set("enablePrivilege", Function::New(env, EnablePrivilege));
exports.Set("checkSnapFastcall", Function::New(env, CheckSnapFastcall));
exports.Set("checkGameIsRunning", Function::New(env, CheckGameIsRunning));
exports.Set("selectGameExecutable", Function::New(env, SelectGameExecutable));
return exports;
}
NODE_API_MODULE(addon, init)
}

View File

@@ -1,39 +0,0 @@
#pragma once
#ifndef REGISTRY_HPP
#define REGISTRY_HPP
#include "../define.h"
namespace RegUtils {
DWORD GetInt(Env env, HKEY hKey, const wstring &path, const wstring &value, DWORD defaultValue) {
DWORD data = 0;
DWORD size = sizeof(DWORD);
LSTATUS retcode = RegGetValue(hKey, path.c_str(), value.c_str(), RRF_RT_REG_DWORD, nullptr, &data, &size);
if (retcode != ERROR_SUCCESS) {
return defaultValue;
}
return data;
}
LSTATUS GetString(HKEY hKey, const wstring &path, const wstring &value, wstring &result) {
DWORD size = 0;
LSTATUS retcode = RegGetValue(hKey, path.c_str(), value.c_str(), RRF_RT_REG_SZ, nullptr, nullptr, &size);
if (retcode != ERROR_SUCCESS) {
return retcode;
}
wstring data;
DWORD len = size / sizeof(WCHAR);
data.resize(len);
retcode = RegGetValue(hKey, path.c_str(), value.c_str(), RRF_RT_REG_SZ, nullptr, &data[0], &size);
if (retcode != ERROR_SUCCESS) {
return retcode;
}
data.resize((len-1));
result = data;
return ERROR_SUCCESS;
}
}
#endif //REGISTRY_HPP

View File

@@ -1,62 +0,0 @@
#include "utils.h"
#include "define.h"
wstring StringToWString(const string &src, UINT codePage) {
int len = MultiByteToWideChar(codePage, 0, src.c_str(), -1, nullptr, 0);
auto *buffer = new WCHAR[len];
MultiByteToWideChar(codePage, 0, src.c_str(), -1, buffer, len);
wstring strTemp(buffer);
delete[] buffer;
return strTemp;
}
string WStringToString(const wstring &src, UINT codePage) {
int len = WideCharToMultiByte(codePage, 0, src.c_str(), -1, nullptr, 0, nullptr, nullptr);
auto *buffer = new CHAR[len];
WideCharToMultiByte(codePage, 0, src.c_str(), -1, buffer, len, nullptr, nullptr);
string strTemp(buffer);
delete[] buffer;
return strTemp;
}
void Log(Env env, const string &msg) {
auto logFunc = env.Global().Get("console").As<Object>().Get("log").As<Function>();
logFunc.Call({ Napi::String::New(env, msg) });
}
void Log(Env env, const wstring &msg) {
auto logFunc = env.Global().Get("console").As<Object>().Get("log").As<Function>();
logFunc.Call({ Napi::String::New(env, WStringToString(msg)) });
}
char* ToHex(const char *bin, int binsz, char **result) {
char hexStr[] = "0123456789ABCDEF";
if (!(*result = (char *) malloc(binsz * 2 + 1))) return nullptr;
(*result)[binsz * 2] = 0;
if (!binsz) return nullptr;
for (int i = 0; i < binsz; i++) {
(*result)[i * 2 + 0] = hexStr[(bin[i] >> 4) & 0x0F];
(*result)[i * 2 + 1] = hexStr[(bin[i]) & 0x0F];
}
return *result;
}
LSTATUS OpenFile(Env env, Napi::String &result, HWND parent) {
OPENFILENAME open;
ZeroMemory(&open, sizeof(open));
WCHAR file[32768];
file[0]=L'\0';
open.Flags = OFN_FILEMUSTEXIST | OFN_NONETWORKBUTTON | OFN_PATHMUSTEXIST | OFN_EXPLORER;
open.hwndOwner = parent;
open.nMaxFile = 32768;
open.lpstrFile = file;
open.lpstrTitle = L"选择原神主程序";
open.lpstrFilter = L"国服/国际服主程序 (YuanShen/GenshinImpact.exe)\0YuanShen.exe;GenshinImpact.exe\0";
open.lStructSize = sizeof(open);
if(GetOpenFileName(&open)) {
result = GetACP() == 936 ? Napi::String::New(env, WStringToString(file, CP_UTF8)) : Napi::String::New(env, WStringToString(file));
return ERROR_SUCCESS;
} else {
return ERROR_ERRORS_ENCOUNTERED;
}
}

View File

@@ -1,15 +0,0 @@
#pragma once
#include "define.h"
string WStringToString(const wstring &src, UINT codePage = CP_ACP);
wstring StringToWString(const string &src, UINT codePage = CP_ACP);
LSTATUS OpenFile(Env env, Napi::String &result, HWND parent = GetConsoleWindow());
char* ToHex(const char *bin, int binsz, char **result);
void Log(Env env, const string &msg);
void Log(Env env, const wstring &msg);
#ifndef GENSHIN_EXPORT_NATIVE_UTILS_H
#define GENSHIN_EXPORT_NATIVE_UTILS_H
#endif //GENSHIN_EXPORT_NATIVE_UTILS_H

Submodule native/src/wmi deleted from aab36ea1e1

View File

@@ -1,19 +0,0 @@
{
"name": "genshin-export",
"version": "1.0.0",
"description": "",
"main": "app.js",
"keywords": [],
"author": "",
"license": "ISC",
"private": true,
"dependencies": {
"axios": "^0.26.1",
"ini": "^2.0.0",
"protobufjs": "^6.11.2",
"udp-proxy": "^1.2.0"
},
"devDependencies": {
"pkg": "file:C:/Users/holog/Desktop/pkg-main"
}
}

View File

@@ -1,2 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"><trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"><security><requestedPrivileges><requestedExecutionLevel level="requireAdministrator" uiAccess="false"/></requestedPrivileges></security></trustInfo><application xmlns="urn:schemas-microsoft-com:asm.v3"><windowsSettings><dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware><longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware></windowsSettings></application><compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"><application><supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/><supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/><supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/><supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/></application></compatibility></assembly>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 353 KiB

View File

@@ -1,57 +0,0 @@
const fs = require("fs")
const pkg = require("pkg")
const { log } = require("../utils")
const { version } = require("../version")
const { execSync } = require("child_process")
const name = `YaeAchievement-${ version.isDev ? "DevBuild" : "Release" }-${((t = new Date()) => {
const p = i => i.toString().padStart(2, "0")
return `${p(t.getMonth()+1)}${p(t.getDate())}${p(t.getHours())}${p(t.getMinutes())}`
})()}`
const generateAndCompileVersionInfo = () => {
const vci = version.name.split(".")
let content = fs.readFileSync("./version.rc", "utf-8")
content = content.replaceAll("${BID}" , vci[2] === undefined ? "0" : vci[2])
content = content.replaceAll("${YEAR}" , (new Date()).getFullYear())
content = content.replaceAll("${MAJOR_VERSION}", vci[0])
content = content.replaceAll("${MINOR_VERSION}", vci[1])
content = content.replaceAll("${ORIGINAL_NAME}", `${name}.exe`)
fs.writeFileSync("./tmp.rc", content, "utf-8")
execSync(`rh.exe -open tmp.rc -save tmp.res -action compile -log NUL`)
fs.rm("./tmp.rc", () => {})
}
const generateScript = (path) => {
let content = fs.readFileSync("./rh.script", "utf-8")
content = content.replaceAll("${PATH}", path)
fs.writeFileSync("./tmp.script", content, "utf-8")
}
const cleanUp = () => {
fs.rm("./rh.ini", () => {})
fs.rm("./tmp.res", () => {})
fs.rm("./tmp.script", () => {})
}
const c = {
forWin7: false,
sevenZipPath: "\"C:/Program Files/7-Zip/7z.exe\""
};
(async () => {
const fn = c.forWin7 ? `${name}-Win7` : name
const nv = c.forWin7 ? "14.19.1" : "16.14.2"
log("Generate and compile version info")
generateAndCompileVersionInfo()
log("Modify executable file resources")
const path = `C:/Users/holog/.pkg-cache/v3.3/built-v${nv}-win-x64`
generateScript(path)
execSync(`rh.exe -script tmp.script`)
cleanUp()
log("Build and compress package")
await pkg.exec(`../app.js -t node${nv.split(".")[0]}-win-x64 -C Brotli --build -o ${fn}.exe`.split(" "))
execSync(`${c.sevenZipPath} a ${fn}.7z ${fn}.exe -mx=9 -myx=9 -mmt=4 -sdel -stl`)
log("Done")
})()

Binary file not shown.

View File

@@ -1,10 +0,0 @@
[FILENAMES]
Exe=${PATH}
SaveAs=${PATH}
Log=NUL
[COMMANDS]
-delete MANIFEST,,
-delete ICONGROUP,,
-addoverwrite icon.ico, ICONGROUP,1,0
-addoverwrite tmp.res, VERSIONINFO,1,0
-addoverwrite app.manifest MANIFEST,1,0

View File

@@ -1,23 +0,0 @@
1 VERSIONINFO
FILEVERSION ${MAJOR_VERSION},${MINOR_VERSION},${BID},0
PRODUCTVERSION ${MAJOR_VERSION},${MINOR_VERSION},${BID},0
FILEOS 0x4
FILETYPE 0x1
{
BLOCK "StringFileInfo"
{
BLOCK "000004B0"
{
VALUE "ProductName", "YaeAchievement"
VALUE "ProductVersion", "${MAJOR_VERSION}.${MINOR_VERSION}.${BID}"
VALUE "FileDescription", "YaeAchievement"
VALUE "FileVersion", "${MAJOR_VERSION}.${MINOR_VERSION}.${BID}"
VALUE "OriginalFilename", "${ORIGINAL_NAME}"
VALUE "LegalCopyright", "Copyright (c) ${YEAR} HolographicHat"
}
}
BLOCK "VarFileInfo"
{
VALUE "Translation", 0x0000 0x04B0
}
}

View File

@@ -1,111 +0,0 @@
const fs = require("fs")
const https = require("https")
const axios = require("axios")
const { decodeProto, encodeProto, debug } = require("./utils")
const path = require("path")
const cert = path.join(__dirname, "./cache/cert/root.p12")
const preparedRegions = {}
let currentProxy = undefined
const getModifiedRegionList = async (conf) => {
const d = await axios.get(`https://${conf.dispatchUrl}/query_region_list`, {
responseType: "text",
params: {
version: conf.version,
channel_id: conf.channel,
sub_channel_id: conf.subChannel
}
}).catch(_ => {
console.log("网络错误,请检查网络后重试 (22-1)")
process.exit(221)
})
const regions = await decodeProto(Buffer.from(d.data,"base64"),"QueryRegionList")
if (regions["retcode"] !== 0) {
console.log(`系统错误,请稍后重试 (${regions["retcode"]}-23)`)
process.exit(23)
}
regions.list = regions.list.map(item => {
const host = new URL(item.url).host
if (regions.list.length === 1) {
preparedRegions[host] = true
}
item.url = `https://localdispatch.yuanshen.com/query_cur_region/${host}`
return item
})
return (await encodeProto(regions,"QueryRegionList")).toString("base64")
}
const getModifiedRegionInfo = async (url, uc, hs) => {
const splitUrl = url.split("?")
const host = splitUrl[0].split("/")[2]
const noQueryRequest = splitUrl[1] === undefined
const query = noQueryRequest ? "" : `?${splitUrl[1]}`
const d = await axios.get(`https://${host}/query_cur_region${query}`, {
responseType: "text"
}).catch(_ => {
console.log("网络错误,请检查网络后重试 (22-2)")
process.exit(222)
})
if (noQueryRequest) {
preparedRegions[host] = true
return d.data
} else {
const region = await decodeProto(Buffer.from(d.data,"base64"),"QueryCurRegion")
if (region["retcode"] !== 0) {
console.log(`${region["message"]} (${region["retcode"]}-24)`)
process.exit(24)
}
const info = region.info
if (preparedRegions[host]) {
if (currentProxy !== undefined) {
currentProxy.close()
}
debug("Create udp proxy: %s:%d", info.ip, info.port)
currentProxy = uc(info.ip, info.port, hs)
} else {
preparedRegions[host] = true
}
info.ip = "127.0.0.1"
info.port = 45678
return (await encodeProto(region,"QueryCurRegion")).toString("base64")
}
}
const agent = new https.Agent({
rejectUnauthorized: false
})
const create = async (conf, regionListLoadedCallback, regionSelectCallback) => {
const regions = await getModifiedRegionList(conf)
const hServer = https.createServer({
pfx: fs.readFileSync(cert),
passphrase: ""
}, async (request, response) => {
const url = request.url
debug("HTTP: %s", url)
response.writeHead(200, { "Content-Type": "text/html" })
if (url.startsWith("/query_region_list")) {
response.end(regions)
} else if (url.startsWith("/query_cur_region")) {
const regionInfo = await getModifiedRegionInfo(url, regionSelectCallback, hServer)
response.end(regionInfo)
} else {
const frontResponse = await axios.get(`https://${conf.dispatchIP}${url}`, {
responseType: "arraybuffer",
httpsAgent: agent
}).catch(err => {
console.log("网络错误,请检查网络后重试 (22-3)")
console.log(err.message)
process.exit(223)
})
response.end(frontResponse.data)
}
}).listen(443, "127.0.0.1", () => {
regionListLoadedCallback()
})
}
module.exports = {
create
}

299
utils.js
View File

@@ -1,299 +0,0 @@
const fs = require("fs")
const dns = require("dns")
const ini = require("ini")
const zlib = require("zlib")
const cloud = require("./generated/secret")
const native = require("./generated/native")
const readline = require("readline")
const protobuf = require("protobufjs")
const { version } = require("./version")
const { promisify } = require("util")
const { createHash } = require("crypto")
const path = require("path")
const { uploadEvent } = require("./appcenter")
const messages = protobuf.loadSync(path.join(__dirname, "./generated/Messages.proto"))
const encodeProto = (object, name) => {
const msgType = messages.lookupType(name)
const msgInst = msgType.create(object)
return msgType.encode(msgInst).finish()
}
const decodeProto = (buf, name) => messages.lookupType(name).decode(buf)
const checkPath = path => new Promise((resolve, reject) => {
if (!fs.existsSync(`${path}/UnityPlayer.dll`) || !fs.existsSync(`${path}/pkg_version`)) {
reject(`路径有误 ${path}`)
} else {
resolve(path)
}
})
let conf
const initConfig = async () => {
const configFileName = "./config.json"
if (fs.existsSync(configFileName)) {
conf = JSON.parse(fs.readFileSync(configFileName, "utf-8"))
if (conf.offlineResource !== undefined || conf.customCDN !== undefined || conf.proxy !== undefined) {
conf.proxy = undefined
conf.customCDN = undefined
conf.offlineResource = undefined
fs.writeFileSync(configFileName, JSON.stringify(conf, null, 2))
}
} else {
conf = {
path: [],
oversea_api: false
}
let p = ""
try {
p = path.dirname(native.selectGameExecutable())
} catch (e) {
process.exit(1)
}
await checkPath(p).catch(reason => {
console.log(reason)
process.exit(1)
})
conf.path.push(p)
fs.writeFileSync(configFileName, JSON.stringify(conf, null, 2))
}
if (conf.oversea_api === undefined) {
conf.oversea_api = false
fs.writeFileSync(configFileName, JSON.stringify(conf, null, 2))
}
if (conf.path.length === 1) {
await checkPath(conf.path[0]).catch(reason => {
console.log(reason)
process.exit(1)
}).then(p => {
conf.path = p
})
} else {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
})
const question = (query) => new Promise(resolve => {
rl.question(query, resolve)
})
const idx = await question(`选择客户端: \n${conf.path.map((s, i) => {
const fp = fs.existsSync(`${s}/GenshinImpact.exe`) ? `${s}\\GenshinImpact.exe` : `${s}\\YuanShen.exe`
return `[${i}] ${fp}`
}).join("\n")}\n> `)
await checkPath(conf.path[parseInt(idx)]).catch(reason => {
console.log(reason)
process.exit(1)
}).then(p => {
conf.path = p
})
rl.close()
}
conf.isOversea = fs.existsSync(conf.path + "/GenshinImpact.exe")
conf.dataDir = conf.isOversea ? conf.path + "/GenshinImpact_Data" : conf.path + "/YuanShen_Data"
const readGameRes = (path) => fs.readFileSync(conf.dataDir + path, "utf-8")
const genshinConf = ini.parse(fs.readFileSync(`${conf.path}/config.ini`, "utf-8"))["General"]
conf.channel = genshinConf["channel"]
conf.subChannel = genshinConf["sub_channel"]
conf.version = readGameRes("/Persistent/ChannelName") + readGameRes("/Persistent/ScriptVersion")
conf.executable = conf.isOversea ? `${conf.path}/GenshinImpact.exe` : `${conf.path}/YuanShen.exe`
conf.dispatchUrl = `dispatch${conf.isOversea ? "os" : "cn"}global.yuanshen.com`
conf.dispatchIP = (await promisify(dns.lookup).bind(dns)(conf.dispatchUrl, 4)).address
uploadEvent("AppInitialize", { ClientVersion: version.name, GameVersion: conf.version })
return conf
}
const checkGameIsRunning = () => {
const name = path.basename(conf.executable)
if (native.checkGameIsRunning(name)) {
console.log("原神正在运行,请关闭后重试")
process.exit(301)
}
}
const checkPortIsUsing = () => {
const portUsage = native.whoUseThePort(443)
if (portUsage !== undefined) {
console.log(`443端口被 ${path.basename(portUsage["path"])}(${portUsage["pid"]}) 占用,结束该进程后重试`)
process.exit(14)
}
}
const openUrl = url => native.openUrl(encodeURI(url))
const splitPacket = buf => {
let offset = 0
let arr = []
while (offset < buf.length) {
let dataLength = buf.readUInt32LE(offset + 24)
arr.push(buf.subarray(offset, offset + 28 + dataLength))
offset += dataLength + 28
}
return arr
}
const md5 = str => {
const h = createHash("md5")
h.update(str)
return h.digest("hex")
}
/*
* Structure:
* UInt16 = 0x4321: magic num
* UInt8 = 0x2 : version
* Char[32] : data md5
* ... : compressed data
*/
const loadCache = async (fp = "latest-data") => {
log(`正在加载资源: ${fp}`)
const startAt = Date.now()
fs.mkdirSync("./cache", { recursive: true })
// remove old cache file
fs.readdir("./cache/", (err, files) => {
if (err === null) files.forEach(s => {
const fn = `./cache/${s}`
if (!fn.endsWith(".ch")) fs.rm(fn,_ => {})
})
})
const localPath = `./cache/${md5(fp)}.ch`
const headers = {
"x-content-hash": "0".repeat(32)
}
let fd = Buffer.alloc(0)
if (fs.existsSync(localPath)) {
fd = fs.readFileSync(localPath)
if (fd.readUInt16BE(0) === 0x4321 && fd.readUInt8(2) === 2) {
headers["x-content-hash"] = fd.subarray(3, 35).toString("utf-8")
}
}
const resp = await cloud.get(`/${fp}`, "application/json", headers, "arraybuffer")
if (resp.status === 304) {
log(`完成 (Cache, ${Date.now() - startAt}ms)`)
return JSON.parse(brotliDecompressSync(fd.subarray(35)).toString())
} else {
const compressedData = resp.data
const decompressedData = brotliDecompressSync(compressedData)
const buf = Buffer.allocUnsafe(compressedData.length + 35)
buf.writeUInt16BE(0x4321, 0)
buf.writeUInt8(0x2, 2)
buf.fill(md5(decompressedData), 3)
buf.fill(compressedData, 35)
fs.writeFileSync(localPath, buf)
log(`完成 (Network, ${Date.now() - startAt}ms)`)
return JSON.parse(decompressedData)
}
}
const isDebug = false
const debug = (msg, ...params) => {
if (isDebug) log(msg, ...params)
}
const log = (msg, ...params) => {
const time = new Date()
const timeStr = time.getHours().toString().padStart(2, "0") + ":" + time.getMinutes().toString().padStart(2, "0") + ":" + time.getSeconds().toString().padStart(2, "0")
console.log(`${timeStr} ${msg}`, ...params)
}
const upload = async data => {
return await cloud.post("/achievement-export", data)
}
const checkUpdate = async () => {
const data = (await cloud.fetchBucket("/latest.json")).data
if (data["vc"] !== version.code) {
console.log(`有可用更新: ${version.name} => ${data["vn"]}`)
console.log(`更新内容: \n${data["ds"]}`)
console.log("下载地址: https://github.com/HolographicHat/YaeAchievement/releases")
if (data["fc"] === true) {
console.log(" * 这是一次强制更新")
process.exit(410)
}
}
}
const brotliCompressSync = data => zlib.brotliCompressSync(data,{
params: {
[zlib.constants.BROTLI_PARAM_QUALITY]: zlib.constants.BROTLI_MAX_QUALITY,
[zlib.constants.BROTLI_PARAM_SIZE_HINT]: data.length
}
})
const brotliDecompressSync = data => zlib.brotliDecompressSync(data)
let hostsContent = undefined
const setupHost = (restore = false) => {
if (restore && hostsContent === undefined) return
const path = `${process.env.windir}\\System32\\drivers\\etc\\hosts`
if (!fs.existsSync(path)) {
fs.writeFileSync(path, "")
}
fs.chmodSync(path, 0o777)
if (restore) {
fs.writeFileSync(path, hostsContent)
} else {
hostsContent = fs.readFileSync(path, "utf-8")
const requireHosts = new Map()
requireHosts.set(conf.dispatchUrl, "127.0.0.1")
requireHosts.set("localdispatch.yuanshen.com", "127.0.0.1")
const currentHosts = new Map()
hostsContent.split("\n").map(l => l.trim()).filter(l => !l.startsWith("#") && l.length > 0).forEach(value => {
const pair = value.trim().split(" ").filter(v => v.trim().length !== 0)
currentHosts.set(pair[1], pair[0])
})
requireHosts.forEach((value, key) => {
if (currentHosts.has(key)) {
if (currentHosts.get(key) === value) {
requireHosts.delete(key)
} else {
currentHosts.delete(key)
}
}
})
requireHosts.forEach((ip, host) => {
currentHosts.set(host, ip)
})
const newContent = Array.from(currentHosts.entries()).map(pair => {
return `${pair[1]} ${pair[0]}`
}).join("\n")
fs.writeFileSync(path, newContent)
}
debug("修改SystemHosts")
process.on("exit", () => {
fs.writeFileSync(path, hostsContent)
})
}
// noinspection JSUnusedGlobalSymbols
class KPacket {
constructor(data) {
this.origin = data
this.conv = data.readUInt32BE(0)
this.token = data.readUInt32BE(4)
this.cmd = data.readUInt8(8)
this.frg = data.readUInt8(9)
this.wnd = data.readUInt16LE(10)
this.ts = data.readUInt32LE(12)
this.sn = data.readUInt32LE(16)
this.una = data.readUInt32LE(20)
this.length = data.readUInt32LE(24)
this.data = data.subarray(28)
this.hash = (() => {
const h = createHash("sha256")
h.update(Buffer.concat([Buffer.of(this.sn, this.frg), this.data]))
return h.digest("hex")
})()
}
}
module.exports = {
log, encodeProto, decodeProto, initConfig, splitPacket, upload, brotliCompressSync, brotliDecompressSync,
setupHost, loadCache, debug, checkUpdate, KPacket, checkGameIsRunning, checkPortIsUsing, openUrl
}

View File

@@ -1,7 +0,0 @@
const version = {
code: 8,
name: "1.7.0",
isDev: false
}
module.exports = { version }