12 Commits

Author SHA1 Message Date
HolographicHat
b76e8e3cd2 Fix 2022-05-31 11:31:00 +08:00
HolographicHat
c12d376e21 Support rel2.7.0 and bump version to 1.7 2022-05-31 11:05:33 +08:00
HolographicHat
d285b1c999 Update message 2022-05-29 22:33:34 +08:00
HolographicHat
89ab4408d6 Update 2022-05-23 12:14:24 +08:00
HolographicHat
bf01214971 Merge pull request #12 from HolographicHat/no-client
Update
2022-05-19 22:07:08 +08:00
HolographicHat
1900937a50 Update 2022-05-19 22:04:23 +08:00
HolographicHat
98a03a0910 Fix #11 2022-04-25 16:16:13 +08:00
HolographicHat
82ba6120e4 snap update 2022-04-23 16:07:13 +08:00
HolographicHat
6cb07d274a UIAF: Update field name 2022-04-21 19:40:17 +08:00
HolographicHat
4acaa516bb UIAF Support 2022-04-21 15:11:05 +08:00
HolographicHat
b5caba2f7a Merge pull request #10 from gaoyifan/master
Fix paimon.moe exporting format
2022-04-18 18:42:43 +08:00
Yifan Gao
ebb11bde9c Fix paimon.moe exporting format 2022-04-18 04:29:34 +08:00
18 changed files with 185 additions and 237 deletions

12
.gitignore vendored
View File

@@ -1,12 +1,6 @@
cache cache
cert
config.json
out.*
node_modules
.idea .idea
YaeAchievement-*.7z generated
YaeAchievement-*.exe node_modules
export*.json config.json
secret.js
package-lock.json package-lock.json
native.node

View File

@@ -1,9 +1,13 @@
# 原神成就导出工具 <div align="center">
# YaeAchievement
![GitHub](https://img.shields.io/badge/License-GPL--3.0-brightgreen?style=flat-square) ![GitHub release (latest by date)](https://img.shields.io/github/v/release/HolographicHat/genshin-achievement-export?color=brightgreen&label=Release&style=flat-square) ![GitHub issues](https://img.shields.io/github/issues/HolographicHat/genshin-achievement-export?label=Issues&style=flat-square) ![Downloads](https://img.shields.io/github/downloads/HolographicHat/genshin-achievement-export/total?color=brightgreen&label=Downloads&style=flat-square) ![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square) ![GitHub](https://img.shields.io/badge/License-GPL--3.0-brightgreen?style=flat-square) ![GitHub release (latest by date)](https://img.shields.io/github/v/release/HolographicHat/genshin-achievement-export?color=brightgreen&label=Release&style=flat-square) ![GitHub issues](https://img.shields.io/github/issues/HolographicHat/genshin-achievement-export?label=Issues&style=flat-square) ![Downloads](https://img.shields.io/github/downloads/HolographicHat/genshin-achievement-export/total?color=brightgreen&label=Downloads&style=flat-square) ![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)
- 支持导出所有成就 </div>
- 支持官服B服与国际服
- 支持导出所有类别的成就
- 支持官服,渠道服与国际服
- 支持导出至[椰羊](https://cocogoat.work/achievement)、[SnapGenshin](https://github.com/DGP-Studio/Snap.Genshin)、[Paimon.moe](https://paimon.moe/achievement/)、[Seelie.me](https://seelie.me/achievements)和表格文件(csv) - 支持导出至[椰羊](https://cocogoat.work/achievement)、[SnapGenshin](https://github.com/DGP-Studio/Snap.Genshin)、[Paimon.moe](https://paimon.moe/achievement/)、[Seelie.me](https://seelie.me/achievements)和表格文件(csv)
- 没有窗口大小、游戏语言等要求 - 没有窗口大小、游戏语言等要求

9
app.js
View File

@@ -1,14 +1,14 @@
const proxy = require("udp-proxy") const proxy = require("udp-proxy")
const cp = require("child_process") const cp = require("child_process")
const cloud = require("./secret")
const appcenter = require("./appcenter") const appcenter = require("./appcenter")
const regionServer = require("./regionServer") const regionServer = require("./regionServer")
const cloud = require("./generated/secret")
const { const {
initConfig, splitPacket, upload, decodeProto, log, setupHost, KPacket, debug, checkUpdate, initConfig, splitPacket, upload, decodeProto, log, setupHost, KPacket, debug, checkUpdate,
brotliCompressSync, brotliDecompressSync, checkGameIsRunning, checkPortIsUsing brotliCompressSync, brotliDecompressSync, checkGameIsRunning, checkPortIsUsing
} = require("./utils") } = require("./utils")
const { exportData } = require("./export") const { exportData } = require("./export")
const { enablePrivilege, pause } = require("./native") const { enablePrivilege, pause } = require("./generated/native")
const onExit = () => { const onExit = () => {
setupHost(true) setupHost(true)
@@ -123,7 +123,7 @@ const onExit = () => {
log(`请求ID: ${response.headers["x-api-requestid"]}`) log(`请求ID: ${response.headers["x-api-requestid"]}`)
log("请联系开发者以获取帮助") log("请联系开发者以获取帮助")
} else { } else {
const proto = await decodeProto(data,"AllAchievement") const proto = await decodeProto(data, "Notify1")
await exportData(proto) await exportData(proto)
} }
process.exit(0) process.exit(0)
@@ -145,9 +145,10 @@ const onExit = () => {
switch(buf.readUInt32BE(0)) { switch(buf.readUInt32BE(0)) {
case 325: case 325:
createMonitor() createMonitor()
debug("服务端握手应答") debug("Connection established.")
break break
case 404: case 404:
debug("Connection terminated.")
lastRecvTimestamp = parseInt(Date.now() / 1000) - 2333 lastRecvTimestamp = parseInt(Date.now() / 1000) - 2333
break break
default: default:

View File

@@ -1,7 +1,7 @@
const axios = require("axios") const axios = require("axios")
const crypto = require("crypto") const crypto = require("crypto")
const { version } = require("./version") const { version } = require("./version")
const { getDeviceID, getDeviceInfo } = require("./native") const { getDeviceID, getDeviceInfo } = require("./generated/native")
const getTimestamp = (d = new Date()) => { const getTimestamp = (d = new Date()) => {
const p = i => i.toString().padStart(2, "0") const p = i => i.toString().padStart(2, "0")

View File

@@ -1,14 +1,14 @@
const fs = require("fs") const fs = require("fs")
const axios = require("axios") const axios = require("axios")
const readline = require("readline") const readline = require("readline")
const { randomUUID } = require("crypto") const { version } = require("./version")
const { loadCache, log, openUrl } = require("./utils") const { loadCache, log, openUrl } = require("./utils")
const { checkSnapFastcall, copyToClipboard } = require("./native") const { checkSnapFastcall, copyToClipboard } = require("./generated/native")
const exportToSeelie = proto => { const exportToSeelie = proto => {
const out = { achievements: {} } const out = { achievements: {} }
proto.list.filter(a => a.status === 3 || a.status === 2).forEach(({id}) => { proto.list.filter(a => a.status === 3 || a.status === 2).forEach(({id}) => {
out.achievements[id] = { done: true } out.achievements[id === 81222 ? 81219 : id] = { done: true }
}) })
const fp = `./export-${Date.now()}-seelie.json` const fp = `./export-${Date.now()}-seelie.json`
fs.writeFileSync(fp, JSON.stringify(out)) fs.writeFileSync(fp, JSON.stringify(out))
@@ -19,58 +19,52 @@ const exportToPaimon = async proto => {
const out = { achievement: {} } const out = { achievement: {} }
const data = await loadCache() const data = await loadCache()
proto.list.filter(a => a.status === 3 || a.status === 2).forEach(({id}) => { proto.list.filter(a => a.status === 3 || a.status === 2).forEach(({id}) => {
const gid = data["a"][id] const gid = data["a"][id]["g"]
if (out.achievement[gid] === undefined) { if (out.achievement[gid] === undefined) {
out.achievement[gid] = {} out.achievement[gid] = {}
} }
out.achievement[gid][id] = true out.achievement[gid][id === 81222 ? 81219 : id] = true
}) })
const fp = `./export-${Date.now()}-paimon.json` const fp = `./export-${Date.now()}-paimon.json`
fs.writeFileSync(fp, JSON.stringify(out)) fs.writeFileSync(fp, JSON.stringify(out))
log(`导出为文件: ${fp}`) log(`导出为文件: ${fp}`)
} }
const exportToSnapGenshin = async proto => { const UIAF = proto => {
const out = [] const out = {
proto.list.filter(a => a.status === 3 || a.status === 2).forEach(({id, finishTimestamp}) => { info: {
out.push({ 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, id: id,
timestamp: finishTimestamp timestamp: finishTimestamp,
current: current
}) })
}) })
return out
}
const exportToSnapGenshin = async proto => {
if (checkSnapFastcall()) { if (checkSnapFastcall()) {
const json = JSON.stringify(out) const result = UIAF(proto)
const path = `${process.env.TMP}/YaeAchievement-export-${randomUUID()}` const json = JSON.stringify(result)
fs.writeFileSync(path, json) copyToClipboard(json)
openUrl(`snapgenshin://achievement/import/file?path=\"${path}\"`) openUrl(`snapgenshin://achievement/import/uiaf`)
log("在 SnapGenshin 进行下一步操作") log("在 SnapGenshin 进行下一步操作")
} else { } else {
const json = JSON.stringify(out, null, 2) log("请更新 SnapGenshin 后重试")
copyToClipboard(json)
log("导出内容已复制到剪贴板")
} }
} }
const exportToCocogoat = async proto => { const exportToCocogoat = async proto => {
const out = { const result = UIAF(proto)
achievements: [] const response = await axios.post(`https://77.cocogoat.work/v1/memo?source=${encodeURI("全部成就")}`, result).catch(_ => {
}
const data = await loadCache()
const p = i => i.toString().padStart(2, "0")
const getDate = ts => {
const d = new Date(parseInt(`${ts}000`))
return `${d.getFullYear()}/${p(d.getMonth()+1)}/${p(d.getDate())}`
}
proto.list.filter(a => a.status === 3 || a.status === 2).forEach(({current, finishTimestamp, id, require}) => {
const curAch = data["a"][id]
out.achievements.push({
id: id,
status: current === undefined || current === 0 || curAch["p"] === undefined ? `${require}/${require}` : `${current}/${require}`,
categoryId: curAch["g"],
date: getDate(finishTimestamp)
})
})
const response = await axios.post(`https://77.cocogoat.work/v1/memo?source=${encodeURI("全部成就")}`, out).catch(_ => {
console.log("网络错误,请检查网络后重试 (26-1)") console.log("网络错误,请检查网络后重试 (26-1)")
process.exit(261) process.exit(261)
}) })
@@ -82,7 +76,7 @@ const exportToCocogoat = async proto => {
if (retcode > 32) { if (retcode > 32) {
log("在浏览器内进行下一步操作") log("在浏览器内进行下一步操作")
} else { } else {
log(`导出失败,请联系开发者以获取帮助 (26-3-${retcode})`) log(`打开此链接以进行下一步操作: https://cocogoat.work/achievement?memo=${response.data.key}`)
} }
} }
@@ -129,7 +123,17 @@ const exportData = async proto => {
const question = (query) => new Promise(resolve => { const question = (query) => new Promise(resolve => {
rl.question(query, resolve) rl.question(query, resolve)
}) })
const chosen = await question("导出至: \n[0] 椰羊 (https://cocogoat.work/achievement)\n[1] SnapGenshin\n[2] Paimon.moe\n[3] Seelie.me\n[4] 表格文件 (默认)\n输入一个数字(0-4): ") const chosen = await question(
[
"导出至: ",
"[0] 椰羊 (https://cocogoat.work/achievement)",
"[1] SnapGenshin",
"[2] Paimon.moe",
"[3] Seelie.me",
"[4] 表格文件 (默认)",
"输入一个数字(0-4): "
].join("\n")
)
rl.close() rl.close()
switch (chosen.trim()) { switch (chosen.trim()) {
case "0": case "0":

10
native.d.ts vendored
View File

@@ -1,10 +0,0 @@
export function selectGameExecutable(): string
export function checkGameIsRunning(processName: string): boolean
export function whoUseThePort(port: number): object
export function copyToClipboard(value: string): any
export function checkSnapFastcall(): boolean
export function enablePrivilege(): any
export function getDeviceInfo(): any
export function getDeviceID(): string
export function openUrl(url: string): number
export function pause(): any

1
native/.gitignore vendored
View File

@@ -3,3 +3,4 @@ CMakeLists.txt
cmake-build-debug cmake-build-debug
node_modules node_modules
build build
src/homu.cpp

View File

@@ -10,6 +10,7 @@
"src/wmi/wmi.cpp", "src/wmi/wmi.cpp",
"src/wmi/wmi.hpp", "src/wmi/wmi.hpp",
"src/wmi/unistd.h", "src/wmi/unistd.h",
"src/VMProtectSDK.h",
"src/wmi/wmiresult.cpp", "src/wmi/wmiresult.cpp",
"src/wmi/wmiresult.hpp", "src/wmi/wmiresult.hpp",
"src/wmi/wmiclasses.hpp", "src/wmi/wmiclasses.hpp",
@@ -33,7 +34,12 @@
"AdditionalOptions": [ "AdditionalOptions": [
"-std:c++latest", "-std:c++latest",
"-DUNICODE", "-DUNICODE",
"-sdl" "-sdl",
"-O2",
"-Ot",
"-Oi",
"-GL",
"-Gw"
] ]
} }
} }

View File

@@ -3,8 +3,9 @@
"version": "1.0.0", "version": "1.0.0",
"description": "", "description": "",
"scripts": { "scripts": {
"build": "node-gyp rebuild && copy .\\build\\Release\\native.node ..\\genshin-export\\", "build": "node-gyp build && 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\\" "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, "gypfile": true,
"devDependencies": { "devDependencies": {

View File

@@ -6,9 +6,10 @@
#include <Windows.h> #include <Windows.h>
#include <TlHelp32.h> #include <TlHelp32.h>
using std::string, std::wstring, std::cout, std::to_string; 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; using Napi::Object, Napi::Env, Napi::Function, Napi::Value, Napi::CallbackInfo, Napi::TypeError, Napi::Error;
typedef unsigned char byte; typedef unsigned char byte;
typedef unsigned int ui;
typedef unsigned long ul; typedef unsigned long ul;
typedef unsigned long long ull; typedef unsigned long long ull;

11
native/src/homu.h Normal file
View File

@@ -0,0 +1,11 @@
//
// 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,3 +1,4 @@
//#include "homu.h"
#include "utils.h" #include "utils.h"
#include "define.h" #include "define.h"
#include "wmi/wmi.hpp" #include "wmi/wmi.hpp"
@@ -12,7 +13,7 @@ using Wmi::Win32_ComputerSystem, Wmi::Win32_OperatingSystem, Wmi::retrieveWmi;
namespace native { namespace native {
Value checkGameIsRunning(const CallbackInfo &info) { Value CheckGameIsRunning(const CallbackInfo &info) {
Env env = info.Env(); Env env = info.Env();
if (info.Length() != 1 || !info[0].IsString()) { if (info.Length() != 1 || !info[0].IsString()) {
TypeError::New(env, "Wrong arguments").ThrowAsJavaScriptException(); TypeError::New(env, "Wrong arguments").ThrowAsJavaScriptException();
@@ -34,7 +35,7 @@ namespace native {
return Napi::Boolean::New(env, isRunning); return Napi::Boolean::New(env, isRunning);
} }
Value selectGameExecutable(const CallbackInfo &info) { Value SelectGameExecutable(const CallbackInfo &info) {
Env env = info.Env(); Env env = info.Env();
Napi::String path; Napi::String path;
if (OpenFile(env, path) != ERROR_SUCCESS) { if (OpenFile(env, path) != ERROR_SUCCESS) {
@@ -44,7 +45,7 @@ namespace native {
return path; return path;
} }
Value whoUseThePort(const CallbackInfo &info) { Value WhoUseThePort(const CallbackInfo &info) {
Env env = info.Env(); Env env = info.Env();
if (info.Length() != 1 || !info[0].IsNumber()) { if (info.Length() != 1 || !info[0].IsNumber()) {
TypeError::New(env, "Wrong arguments").ThrowAsJavaScriptException(); TypeError::New(env, "Wrong arguments").ThrowAsJavaScriptException();
@@ -89,7 +90,7 @@ namespace native {
return ret; return ret;
} }
Value getDeviceID(const CallbackInfo &info) { Value GetDeviceID(const CallbackInfo &info) {
Env env = info.Env(); Env env = info.Env();
wstring wd; wstring wd;
if (RegUtils::GetString(HKEY_CURRENT_USER, L"SOFTWARE\\miHoYoSDK", L"MIHOYOSDK_DEVICE_ID", wd) != ERROR_SUCCESS) { if (RegUtils::GetString(HKEY_CURRENT_USER, L"SOFTWARE\\miHoYoSDK", L"MIHOYOSDK_DEVICE_ID", wd) != ERROR_SUCCESS) {
@@ -99,7 +100,7 @@ namespace native {
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)); 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) { Value GetDeviceInfo(const CallbackInfo &info) {
Env env = info.Env(); Env env = info.Env();
HDC desktop = GetDC(nullptr); HDC desktop = GetDC(nullptr);
int sw = GetDeviceCaps(desktop, DESKTOPHORZRES); int sw = GetDeviceCaps(desktop, DESKTOPHORZRES);
@@ -140,13 +141,34 @@ namespace native {
return obj; return obj;
} }
Value enablePrivilege(const CallbackInfo &info) { Value EnablePrivilege(const CallbackInfo &info) {
Env env = info.Env(); Env env = info.Env();
EnablePrivilege(env, L"SeDebugPrivilege"); 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(); return env.Undefined();
} }
Value copyToClipboard(const CallbackInfo &info) { Value CopyToClipboard(const CallbackInfo &info) {
Env env = info.Env(); Env env = info.Env();
string text = info[0].As<Napi::String>().Utf8Value(); string text = info[0].As<Napi::String>().Utf8Value();
if (OpenClipboard(nullptr)) { if (OpenClipboard(nullptr)) {
@@ -162,38 +184,64 @@ namespace native {
return env.Undefined(); return env.Undefined();
} }
Value pause(const CallbackInfo &info) { Value Pause(const CallbackInfo &info) {
while(!_kbhit()) { while(!_kbhit()) {
Sleep(10); Sleep(10);
} }
return info.Env().Undefined(); return info.Env().Undefined();
} }
Value openUrl(const CallbackInfo &info) { Value OpenUrl(const CallbackInfo &info) {
Env env = info.Env(); Env env = info.Env();
wstring url = StringToWString(info[0].As<Napi::String>().Utf8Value()); wstring url = StringToWString(info[0].As<Napi::String>().Utf8Value());
HINSTANCE retcode = ShellExecute(GetConsoleWindow(), L"open", url.c_str(), nullptr, nullptr, SW_SHOWNORMAL); 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) return Napi::Number::New(env, (INT_PTR)retcode); // NOLINT(cppcoreguidelines-narrowing-conversions)
} }
Value checkSnapFastcall(const CallbackInfo &info) { Value CheckSnapFastcall(const CallbackInfo &info) {
Env env = info.Env(); Env env = info.Env();
wstring queryResult; wstring queryResult;
RegUtils::GetString(HKEY_CLASSES_ROOT, L"snapgenshin", L"", queryResult); RegUtils::GetString(HKEY_CLASSES_ROOT, L"snapgenshin", L"", queryResult);
return Napi::Boolean::New(env, wcscmp(queryResult.c_str(), L"URL:snapgenshin") == 0); 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) { Object init(Env env, Object exports) {
exports.Set("pause", Function::New(env, pause)); exports.Set("pause", Function::New(env, Pause));
exports.Set("openUrl", Function::New(env, openUrl)); exports.Set("openUrl", Function::New(env, OpenUrl));
exports.Set("getDeviceID", Function::New(env, getDeviceID)); //exports.Set("homuInit", Function::New(env, Initialize));
exports.Set("getDeviceInfo", Function::New(env, getDeviceInfo)); exports.Set("getDeviceID", Function::New(env, GetDeviceID));
exports.Set("whoUseThePort", Function::New(env, whoUseThePort)); exports.Set("getMACAddress", Function::New(env, GetMACAddress));
exports.Set("copyToClipboard", Function::New(env, copyToClipboard)); exports.Set("getDeviceInfo", Function::New(env, GetDeviceInfo));
exports.Set("enablePrivilege", Function::New(env, enablePrivilege)); exports.Set("whoUseThePort", Function::New(env, WhoUseThePort));
exports.Set("checkSnapFastcall", Function::New(env, checkSnapFastcall)); exports.Set("copyToClipboard", Function::New(env, CopyToClipboard));
exports.Set("checkGameIsRunning", Function::New(env, checkGameIsRunning)); exports.Set("enablePrivilege", Function::New(env, EnablePrivilege));
exports.Set("selectGameExecutable", Function::New(env, selectGameExecutable)); exports.Set("checkSnapFastcall", Function::New(env, CheckSnapFastcall));
exports.Set("checkGameIsRunning", Function::New(env, CheckGameIsRunning));
exports.Set("selectGameExecutable", Function::New(env, SelectGameExecutable));
return exports; return exports;
} }

View File

@@ -29,6 +29,18 @@ void Log(Env env, const wstring &msg) {
logFunc.Call({ Napi::String::New(env, WStringToString(msg)) }); 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) { LSTATUS OpenFile(Env env, Napi::String &result, HWND parent) {
OPENFILENAME open; OPENFILENAME open;
ZeroMemory(&open, sizeof(open)); ZeroMemory(&open, sizeof(open));
@@ -48,29 +60,3 @@ LSTATUS OpenFile(Env env, Napi::String &result, HWND parent) {
return ERROR_ERRORS_ENCOUNTERED; return ERROR_ERRORS_ENCOUNTERED;
} }
} }
BOOL EnablePrivilege(Env env, const wstring &name) {
HANDLE hToken;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) {
Error::New(env, "OpenProcessToken error: " + to_string(GetLastError())).ThrowAsJavaScriptException();
return FALSE;
}
TOKEN_PRIVILEGES tp;
ZeroMemory(&tp, sizeof(tp));
tp.PrivilegeCount = 1;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (!LookupPrivilegeValue(nullptr, name.c_str(), &tp.Privileges[0].Luid)) {
Error::New(env, "LookupPrivilegeValue error: " + to_string(GetLastError())).ThrowAsJavaScriptException();
return FALSE;
}
if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), nullptr, nullptr)) {
Error::New(env, "AdjustTokenPrivileges error: " + to_string(GetLastError())).ThrowAsJavaScriptException();
return FALSE;
}
if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) {
Error::New(env, "The token does not have the specified privilege.").ThrowAsJavaScriptException();
return FALSE;
}
CloseHandle(hToken);
return TRUE;
}

View File

@@ -5,7 +5,7 @@
string WStringToString(const wstring &src, UINT codePage = CP_ACP); string WStringToString(const wstring &src, UINT codePage = CP_ACP);
wstring StringToWString(const string &src, UINT codePage = CP_ACP); wstring StringToWString(const string &src, UINT codePage = CP_ACP);
LSTATUS OpenFile(Env env, Napi::String &result, HWND parent = GetConsoleWindow()); LSTATUS OpenFile(Env env, Napi::String &result, HWND parent = GetConsoleWindow());
BOOL EnablePrivilege(Env env, const wstring &name); char* ToHex(const char *bin, int binsz, char **result);
void Log(Env env, const string &msg); void Log(Env env, const string &msg);
void Log(Env env, const wstring &msg); void Log(Env env, const wstring &msg);

View File

@@ -1,102 +0,0 @@
syntax = "proto3";
message AllAchievement {
repeated Achievement list = 1;
}
message Achievement {
enum Status {
INVALID = 0;
UNFINISHED = 1;
FINISHED = 2;
REWARD_TAKEN = 3;
}
uint32 id = 1;
Status status = 2;
uint32 current = 3;
uint32 require = 4;
uint32 finish_timestamp = 5;
}
message QueryCurRegion {
int32 retcode = 1;
string message = 2;
msg0 info = 3;
oneof group {
msg3 field2 = 4;
msg4 field3 = 5;
}
bytes field4 = 11;
bytes field5 = 12;
bytes field6 = 13;
}
message QueryRegionList {
int32 retcode = 1;
bytes field0 = 5;
bytes field1 = 6;
bool field2 = 7;
repeated msg2 list = 2;
}
message msg0 {
string ip = 1;
uint32 port = 2;
string field0 = 3;
string field1 = 7;
string field2 = 8;
string field3 = 9;
string field4 = 10;
string field5 = 11;
string field6 = 12;
string field7 = 13;
uint32 field8 = 14;
string field9 = 16;
uint32 fieldA = 18;
string fieldB = 19;
string fieldC = 20;
msg1 fieldD = 22;
bytes fieldE = 23;
string fieldF = 24;
string fieldG = 26;
string fieldH = 27;
bool fieldI = 28;
string fieldJ = 29;
string fieldK = 30;
string fieldL = 31;
string fieldM = 32;
string fieldN = 33;
string fieldO = 34;
msg1 fieldP = 35;
}
message msg1 {
uint32 field0 = 1;
bool field1 = 2;
string field2 = 3;
string field3 = 4;
string field4 = 5;
string field5 = 6;
string field6 = 7;
}
message msg2 {
string field0 = 1;
string field1 = 2;
string field2 = 3;
string url = 4;
}
message msg3 {
string field0 = 1;
}
message msg4 {
uint32 field0 = 1;
uint32 field1 = 2;
string field2 = 3;
string field3 = 4;
}

View File

@@ -3,7 +3,7 @@ const https = require("https")
const axios = require("axios") const axios = require("axios")
const { decodeProto, encodeProto, debug } = require("./utils") const { decodeProto, encodeProto, debug } = require("./utils")
const path = require("path") const path = require("path")
const cert = path.join(__dirname, "./cert/root.p12") const cert = path.join(__dirname, "./cache/cert/root.p12")
const preparedRegions = {} const preparedRegions = {}
let currentProxy = undefined let currentProxy = undefined

View File

@@ -2,8 +2,8 @@ const fs = require("fs")
const dns = require("dns") const dns = require("dns")
const ini = require("ini") const ini = require("ini")
const zlib = require("zlib") const zlib = require("zlib")
const cloud = require("./secret") const cloud = require("./generated/secret")
const native = require("./native") const native = require("./generated/native")
const readline = require("readline") const readline = require("readline")
const protobuf = require("protobufjs") const protobuf = require("protobufjs")
const { version } = require("./version") const { version } = require("./version")
@@ -11,17 +11,16 @@ const { promisify } = require("util")
const { createHash } = require("crypto") const { createHash } = require("crypto")
const path = require("path") const path = require("path")
const { uploadEvent } = require("./appcenter") const { uploadEvent } = require("./appcenter")
const messages = path.join(__dirname, "./proto/Messages.proto")
const encodeProto = (object, name) => protobuf.load(messages).then(r => { const messages = protobuf.loadSync(path.join(__dirname, "./generated/Messages.proto"))
const msgType = r.lookupType(name)
const encodeProto = (object, name) => {
const msgType = messages.lookupType(name)
const msgInst = msgType.create(object) const msgInst = msgType.create(object)
return msgType.encode(msgInst).finish() return msgType.encode(msgInst).finish()
}) }
const decodeProto = (buf, name) => protobuf.load(messages).then(r => { const decodeProto = (buf, name) => messages.lookupType(name).decode(buf)
return r.lookupType(name).decode(buf)
})
const checkPath = path => new Promise((resolve, reject) => { const checkPath = path => new Promise((resolve, reject) => {
if (!fs.existsSync(`${path}/UnityPlayer.dll`) || !fs.existsSync(`${path}/pkg_version`)) { if (!fs.existsSync(`${path}/UnityPlayer.dll`) || !fs.existsSync(`${path}/pkg_version`)) {
@@ -206,11 +205,15 @@ const upload = async data => {
} }
const checkUpdate = async () => { const checkUpdate = async () => {
const data = (await cloud.get("/latest-version")).data const data = (await cloud.fetchBucket("/latest.json")).data
if (data["vc"] !== version.code) { if (data["vc"] !== version.code) {
log(`有可用更新: ${version.name} => ${data["vn"]}`) console.log(`有可用更新: ${version.name} => ${data["vn"]}`)
log(`更新内容: \n${data["ds"]}`) console.log(`更新内容: \n${data["ds"]}`)
log("下载地址: https://github.com/HolographicHat/genshin-achievement-export/releases\n") console.log("下载地址: https://github.com/HolographicHat/YaeAchievement/releases")
if (data["fc"] === true) {
console.log(" * 这是一次强制更新")
process.exit(410)
}
} }
} }

View File

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