mirror of
https://github.com/HolographicHat/Yae.git
synced 2025-12-11 00:48:12 +08:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4417feab53 | ||
|
|
28080dbafd | ||
|
|
a8b6419157 | ||
|
|
7f8296c3dc | ||
|
|
37382b28e0 | ||
|
|
a46e49722f | ||
|
|
700cbbb86d | ||
|
|
6a23153f70 | ||
|
|
75e3cd848f | ||
|
|
96912d3da7 | ||
|
|
02b034cc48 | ||
|
|
00898d11cb | ||
|
|
7cf03ad905 | ||
|
|
7d9e5bc218 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -4,7 +4,8 @@ config.json
|
||||
out.*
|
||||
node_modules
|
||||
.idea
|
||||
app*.exe
|
||||
YaeAchievement-*.7z
|
||||
YaeAchievement-*.exe
|
||||
export*.json
|
||||
secret.js
|
||||
package-lock.json
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 原神成就导出工具
|
||||
|
||||
   
|
||||
    
|
||||
|
||||
- 支持导出所有成就
|
||||
- 支持官服,B服与国际服
|
||||
@@ -13,6 +13,7 @@
|
||||

|
||||
### Windows7
|
||||
系统变量添加名为```NODE_SKIP_PLATFORM_CHECK```的变量并将值设为```1```
|
||||
[[?]如何添加环境变量](https://www.bing.com/search?q=windows+7+环境变量)
|
||||
|
||||
## 下载地址
|
||||
[releases/latest](https://github.com/HolographicHat/genshin-achievement-export/releases/latest)
|
||||
@@ -35,3 +36,6 @@
|
||||
|
||||
4. Q: 原神进门后没有自动退出,程序输出停留在“加载完成”
|
||||
A: 关闭代理后重试
|
||||
|
||||
5. Q: 网络错误,请检查网络后重试 (21-x)
|
||||
A: 如果你是校园网用户,使用流量上网并重试;如果你是海外用户,打开config.json,将oversea_api的值设置为true并重试
|
||||
|
||||
6
app.js
6
app.js
@@ -1,7 +1,8 @@
|
||||
const proxy = require("udp-proxy")
|
||||
const cp = require("child_process")
|
||||
const rs = require("./regionServer")
|
||||
const cloud = require("./secret")
|
||||
const appcenter = require("./appcenter")
|
||||
const regionServer = require("./regionServer")
|
||||
const {
|
||||
initConfig, splitPacket, upload, decodeProto, log, setupHost, KPacket, debug, checkUpdate,
|
||||
brotliCompressSync, brotliDecompressSync, checkGameIsRunning, checkPortIsUsing
|
||||
@@ -37,13 +38,14 @@ const onExit = () => {
|
||||
}
|
||||
appcenter.startup()
|
||||
let conf = await initConfig()
|
||||
cloud.init(conf)
|
||||
checkPortIsUsing()
|
||||
checkGameIsRunning()
|
||||
log("检查更新")
|
||||
await checkUpdate()
|
||||
let gameProcess
|
||||
let unexpectedExit = true
|
||||
rs.create(conf,() => {
|
||||
regionServer.create(conf,() => {
|
||||
setupHost()
|
||||
gameProcess = cp.execFile(conf.executable, { cwd: conf.path },err => {
|
||||
if (err !== null && !err.killed) {
|
||||
|
||||
23
export.js
23
export.js
@@ -1,8 +1,9 @@
|
||||
const fs = require("fs")
|
||||
const axios = require("axios")
|
||||
const readline = require("readline")
|
||||
const { loadCache, log } = require("./utils")
|
||||
const { openUrl, copyToClipboard } = require("./native")
|
||||
const { randomUUID } = require("crypto")
|
||||
const { loadCache, log, openUrl } = require("./utils")
|
||||
const { checkSnapFastcall, copyToClipboard } = require("./native")
|
||||
|
||||
const exportToSeelie = proto => {
|
||||
const out = { achievements: {} }
|
||||
@@ -34,12 +35,20 @@ const exportToSnapGenshin = async proto => {
|
||||
proto.list.filter(a => a.status === 3 || a.status === 2).forEach(({id, finishTimestamp}) => {
|
||||
out.push({
|
||||
id: id,
|
||||
ts: finishTimestamp
|
||||
timestamp: finishTimestamp
|
||||
})
|
||||
})
|
||||
const json = JSON.stringify(out, null, 2)
|
||||
copyToClipboard(json)
|
||||
log("导出内容已复制到剪贴板")
|
||||
if (checkSnapFastcall()) {
|
||||
const json = JSON.stringify(out)
|
||||
const path = `${process.env.TMP}/YaeAchievement-export-${randomUUID()}`
|
||||
fs.writeFileSync(path, json)
|
||||
openUrl(`snapgenshin://achievement/import/file?path=\"${path}\"`)
|
||||
log("在 SnapGenshin 进行下一步操作")
|
||||
} else {
|
||||
const json = JSON.stringify(out, null, 2)
|
||||
copyToClipboard(json)
|
||||
log("导出内容已复制到剪贴板")
|
||||
}
|
||||
}
|
||||
|
||||
const exportToCocogoat = async proto => {
|
||||
@@ -122,7 +131,7 @@ const exportData = async proto => {
|
||||
})
|
||||
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): ")
|
||||
rl.close()
|
||||
switch (chosen) {
|
||||
switch (chosen.trim()) {
|
||||
case "0":
|
||||
await exportToCocogoat(proto)
|
||||
break
|
||||
|
||||
1
native.d.ts
vendored
1
native.d.ts
vendored
@@ -2,6 +2,7 @@ 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
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"build": "node-gyp rebuild && copy .\\build\\Release\\native.node ..\\genshin-export\\"
|
||||
"build": "node-gyp rebuild && copy .\\build\\Release\\native.node ..\\genshin-export\\",
|
||||
"build-for-win7": "node-gyp rebuild --target=v14.17.0 && copy .\\build\\Release\\native.node ..\\genshin-export\\"
|
||||
},
|
||||
"gypfile": true,
|
||||
"devDependencies": {
|
||||
|
||||
@@ -105,7 +105,7 @@ namespace native {
|
||||
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");
|
||||
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";
|
||||
@@ -176,6 +176,13 @@ namespace native {
|
||||
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);
|
||||
}
|
||||
|
||||
Object init(Env env, Object exports) {
|
||||
exports.Set("pause", Function::New(env, pause));
|
||||
exports.Set("openUrl", Function::New(env, openUrl));
|
||||
@@ -184,6 +191,7 @@ namespace native {
|
||||
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;
|
||||
|
||||
@@ -7,13 +7,12 @@
|
||||
|
||||
namespace RegUtils {
|
||||
|
||||
DWORD GetInt(Env env, HKEY hKey, const wstring &path, const wstring &value) {
|
||||
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) {
|
||||
Error::New(env, "RegGetValue error: " + to_string(retcode)).ThrowAsJavaScriptException();
|
||||
return 0;
|
||||
return defaultValue;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -1,26 +1,22 @@
|
||||
#include "utils.h"
|
||||
#include "define.h"
|
||||
|
||||
wstring StringToWString(const string &src) {
|
||||
wstring result;
|
||||
int len = MultiByteToWideChar(CP_ACP, 0, src.c_str(), src.size(), nullptr, 0); // NOLINT(cppcoreguidelines-narrowing-conversions)
|
||||
auto *buffer = new WCHAR[len + 1];
|
||||
MultiByteToWideChar(CP_ACP, 0, src.c_str(), src.size(), buffer, len); // NOLINT(cppcoreguidelines-narrowing-conversions)
|
||||
buffer[len] = '\0';
|
||||
result.append(buffer);
|
||||
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 result;
|
||||
return strTemp;
|
||||
}
|
||||
|
||||
string WStringToString(const wstring &src) {
|
||||
string result;
|
||||
int len = WideCharToMultiByte(CP_ACP, 0, src.c_str(), src.size(), nullptr, 0, nullptr, nullptr); // NOLINT(cppcoreguidelines-narrowing-conversions)
|
||||
char *buffer = new char[len + 1];
|
||||
WideCharToMultiByte(CP_ACP, 0, src.c_str(), src.size(), buffer, len, nullptr, nullptr); // NOLINT(cppcoreguidelines-narrowing-conversions)
|
||||
buffer[len] = '\0';
|
||||
result.append(buffer);
|
||||
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 result;
|
||||
return strTemp;
|
||||
}
|
||||
|
||||
void Log(Env env, const string &msg) {
|
||||
@@ -46,7 +42,7 @@ LSTATUS OpenFile(Env env, Napi::String &result, HWND parent) {
|
||||
open.lpstrFilter = L"国服/国际服主程序 (YuanShen/GenshinImpact.exe)\0YuanShen.exe;GenshinImpact.exe\0";
|
||||
open.lStructSize = sizeof(open);
|
||||
if(GetOpenFileName(&open)) {
|
||||
result = Napi::String::New(env, WStringToString(file));
|
||||
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;
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
#include "define.h"
|
||||
|
||||
string WStringToString(const wstring &src);
|
||||
wstring StringToWString(const string &src);
|
||||
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());
|
||||
BOOL EnablePrivilege(Env env, const wstring &name);
|
||||
void Log(Env env, const string &msg);
|
||||
|
||||
13
package.json
13
package.json
@@ -3,18 +3,17 @@
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "app.js",
|
||||
"scripts": {
|
||||
"pkg": "pkg -t node16-win-x64 -C Brotli app.js --build -o app.exe",
|
||||
"pkg-for-windows7": "pkg -t node14-win-x64 -C Brotli app.js --build -o app-win7.exe"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"ini": "^2.0.0",
|
||||
"axios": "^0.26.1",
|
||||
"udp-proxy": "^1.2.0",
|
||||
"protobufjs": "^6.11.2"
|
||||
"ini": "^2.0.0",
|
||||
"protobufjs": "^6.11.2",
|
||||
"udp-proxy": "^1.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"pkg": "file:C:/Users/holog/Desktop/pkg-main"
|
||||
}
|
||||
}
|
||||
|
||||
2
package/app.manifest
Normal file
2
package/app.manifest
Normal file
@@ -0,0 +1,2 @@
|
||||
<?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>
|
||||
BIN
package/icon.ico
Normal file
BIN
package/icon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 353 KiB |
57
package/package.js
Normal file
57
package/package.js
Normal file
@@ -0,0 +1,57 @@
|
||||
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")
|
||||
})()
|
||||
BIN
package/rh.exe
Normal file
BIN
package/rh.exe
Normal file
Binary file not shown.
10
package/rh.script
Normal file
10
package/rh.script
Normal file
@@ -0,0 +1,10 @@
|
||||
[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
|
||||
23
package/version.rc
Normal file
23
package/version.rc
Normal file
@@ -0,0 +1,23 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
18
utils.js
18
utils.js
@@ -45,9 +45,15 @@ const initConfig = async () => {
|
||||
}
|
||||
} else {
|
||||
conf = {
|
||||
path: []
|
||||
path: [],
|
||||
oversea_api: false
|
||||
}
|
||||
let p = ""
|
||||
try {
|
||||
p = path.dirname(native.selectGameExecutable())
|
||||
} catch (e) {
|
||||
process.exit(1)
|
||||
}
|
||||
const p = path.dirname(native.selectGameExecutable())
|
||||
await checkPath(p).catch(reason => {
|
||||
console.log(reason)
|
||||
process.exit(1)
|
||||
@@ -55,6 +61,10 @@ const initConfig = async () => {
|
||||
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)
|
||||
@@ -112,6 +122,8 @@ const checkPortIsUsing = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const openUrl = url => native.openUrl(encodeURI(url))
|
||||
|
||||
const splitPacket = buf => {
|
||||
let offset = 0
|
||||
let arr = []
|
||||
@@ -280,5 +292,5 @@ class KPacket {
|
||||
|
||||
module.exports = {
|
||||
log, encodeProto, decodeProto, initConfig, splitPacket, upload, brotliCompressSync, brotliDecompressSync,
|
||||
setupHost, loadCache, debug, checkUpdate, KPacket, checkGameIsRunning, checkPortIsUsing
|
||||
setupHost, loadCache, debug, checkUpdate, KPacket, checkGameIsRunning, checkPortIsUsing, openUrl
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const version = {
|
||||
code: 6,
|
||||
name: "1.5"
|
||||
code: 7,
|
||||
name: "1.6.50",
|
||||
isDev: true
|
||||
}
|
||||
|
||||
module.exports = { version }
|
||||
|
||||
Reference in New Issue
Block a user