13 Commits
1.1 ... 1.3

Author SHA1 Message Date
HolographicHat
b4cd69f303 v1.3 2022-03-27 12:51:41 +08:00
HolographicHat
c76ddd9e3f fix wrong field name 2022-03-27 12:47:25 +08:00
HolographicHat
d40c456494 add default arg for read registry method 2022-03-26 20:51:54 +08:00
HolographicHat
408169da4e Merge remote-tracking branch 'origin/master' 2022-03-26 17:55:57 +08:00
HolographicHat
9de8e957fd add server disconnect handler 2022-03-26 16:59:21 +08:00
HolographicHat
a287e5db43 Update README.md 2022-03-26 16:39:43 +08:00
HolographicHat
589048b912 Update README.md 2022-03-26 16:39:14 +08:00
HolographicHat
79517a37d2 try to fix copy error 2022-03-25 21:53:14 +08:00
HolographicHat
e022f04661 fix achievement data error 2022-03-25 21:33:46 +08:00
HolographicHat
d1a635be7c fix exit hook 2022-03-25 21:32:43 +08:00
HolographicHat
aa853262b7 Merge remote-tracking branch 'origin/master' 2022-03-25 19:23:18 +08:00
HolographicHat
bdf9561dfa fix crash when appcenter error 2022-03-25 19:22:34 +08:00
HolographicHat
a663fddeb0 Update README.md 2022-03-23 20:58:21 +08:00
8 changed files with 2754 additions and 2925 deletions

2
.gitignore vendored
View File

@@ -3,5 +3,7 @@ config.json
out.*
node_modules
.idea
app*.exe
export*.json
secret.js
package-lock.json

View File

@@ -10,7 +10,10 @@
**打开程序前需要关闭正在运行的原神主程序**
第一次打开需要先设置原神主程序所在路径,支持多个路径, 使用符号'*'分隔
![alt](https://upload-bbs.mihoyo.com/upload/2022/03/22/165631158/a1bbf8d0604a29830c09822add53f749_8463600217231045373.png)
- 自定义代理: 配置文件内添加proxy字段详细请参看[Axios-请求配置](https://axios-http.com/zh/docs/req_config)
### Windows7
系统变量添加名为```NODE_SKIP_PLATFORM_CHECK```的变量并将值设为```1```
### 自定义代理
配置文件内添加proxy字段详细请参看[Axios-请求配置](https://axios-http.com/zh/docs/req_config)
```json
{
"path": [],
@@ -31,10 +34,15 @@
[issues](https://github.com/HolographicHat/genshin-achievement-export/issues)或[QQ群: 913777414](https://qm.qq.com/cgi-bin/qm/qr?k=9UGz-chQVTjZa4b82RA_A41vIcBVNpms&jump_from=webapi)
## FAQ
1. Q: 为什么需要管理员权限
0. Q: 程序异常退出或被强行终止后,原神启动时报错: 无法连接网络(4201)
A: 用文本编辑器打开```C:\Windows\System32\drivers\etc\hosts```,删除```127.0.0.1 dispatch**global.yuanshen.com```后保存,重启原神
1. Q: 原神启动时报错: 数据异常(31-4302)
A: 不要把软件和原神主程序放一起
2. Q: 为什么需要管理员权限
A: 临时修改Hosts和启动原神
2. Q: 报毒?
3. Q: 报毒?
A: 执行命令或修改hosts引发相关代码可在./utils.js,./appcenter.js下找到
## TODO
- [ ] GUI

File diff suppressed because it is too large Load Diff

23
app.js
View File

@@ -5,16 +5,24 @@ const rs = require("./regionServer")
const appcenter = require("./appcenter")
const { initConfig, splitPacket, upload, decodeProto, log, setupHost, KPacket, debug, checkCDN, checkUpdate } = require("./utils")
const { exportData } = require("./export")
const { exitHook } = require("./exitHook.js");
const onExit = () => {
setupHost(true)
console.log("按任意键退出")
cp.execSync("pause > nul", { stdio: "inherit" })
};
// TODO: use kotlin rewrite it
(async () => {
try {
exitHook(() => {
setupHost(true)
console.log("按任意键退出")
cp.execSync("pause > nul", { stdio: "inherit" })
process.on("uncaughtException", (err, origin) => {
appcenter.uploadError(err, true)
console.log(err)
console.log(`Origin: ${origin}`)
process.exit(1)
})
process.once("exit", onExit)
process.once("SIGINT", onExit)
appcenter.init()
let conf = await initConfig()
try {
@@ -112,6 +120,8 @@ const { exitHook } = require("./exitHook.js");
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)
@@ -121,6 +131,9 @@ const { exitHook } = require("./exitHook.js");
createMonitor()
debug("服务端握手应答")
break
case 404:
lastRecvTimestamp = parseInt(Date.now() / 1000) - 2333
break
default:
console.log(`Unhandled: ${buf.toString("hex")}`)
process.exit(2)

View File

@@ -8,17 +8,21 @@ const getTimestamp = (d = new Date()) => {
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 readRegistry = (path, key) => {
const i = cp.execSync(`reg query "${path}" /v ${key}`, {
encoding: "utf-8"
}).split("\n")[2].split(" ").filter(s => s.length > 0).map(s => s.trim())
switch (i[1]) {
case "REG_SZ":
return i[2]
case "REG_DWORD":
return parseInt(i[2])
default:
throw "Unsupported"
const readRegistry = (path, key, def) => {
try {
const i = cp.execSync(`reg query "${path}" /v ${key}`, {
encoding: "utf-8"
}).split("\n")[2].split(" ").filter(s => s.length > 0).map(s => s.trim())
switch (i[1]) {
case "REG_SZ":
return i[2]
case "REG_DWORD":
return parseInt(i[2])
default:
return def
}
} catch (e) {
return def
}
}
@@ -27,7 +31,7 @@ const session = crypto.randomUUID()
const key = "648b83bf-d439-49bd-97f4-e1e506bdfe39"
const install = (() => {
const s = readRegistry("HKCU\\SOFTWARE\\miHoYoSDK", "MIHOYOSDK_DEVICE_ID")
const s = readRegistry("HKCU\\SOFTWARE\\miHoYoSDK", "MIHOYOSDK_DEVICE_ID", crypto.randomUUID())
return `${s.substring(0, 8)}-${s.substring(8, 12)}-${s.substring(12, 16)}-${s.substring(16, 20)}-${s.substring(20, 32)}`
})()
@@ -44,8 +48,8 @@ const device = (() => {
timeZoneOffset: parseInt(osi[1]),
osBuild: `${osi[2]}.${readRegistry("HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", "UBR")}`,
osVersion: osi[2],
locale: readRegistry("HKCU\\Control Panel\\International", "LocaleName"),
carrierCountry: readRegistry("HKCU\\Control Panel\\International\\Geo", "Name"),
locale: readRegistry("HKCU\\Control Panel\\International", "LocaleName", "zh-CN"),
carrierCountry: readRegistry("HKCU\\Control Panel\\International\\Geo", "Name", "CN"),
sdkName: "appcenter.wpf.netcore",
sdkVersion: "4.5.0",
osName: "WINDOWS",
@@ -57,17 +61,15 @@ const device = (() => {
const upload = () => {
if (queue.length > 0) {
try {
const data = JSON.stringify({ "logs": queue })
axios.post("https://in.appcenter.ms/logs?api-version=1.0.0", data,{
headers: {
"App-Secret": key,
"Install-ID": install
}
}).then(_ => {
queue.length = 0
})
} catch (e) {}
const data = JSON.stringify({ "logs": queue })
axios.post("https://in.appcenter.ms/logs?api-version=1.0.0", data,{
headers: {
"App-Secret": key,
"Install-ID": install
}
}).then(_ => {
queue.length = 0
}).catch(_ => {})
}
}

View File

@@ -1,50 +0,0 @@
// https://github.com/sindresorhus/exit-hook
const callbacks = new Set();
let isCalled = false;
let isRegistered = false;
function exit(shouldManuallyExit, signal) {
if (isCalled) {
return;
}
isCalled = true;
for (const callback of callbacks) {
callback();
}
if (shouldManuallyExit === true) {
process.exit(128 + signal);
}
}
function exitHook(onExit) {
callbacks.add(onExit);
if (!isRegistered) {
isRegistered = true;
process.once('exit', exit);
process.once('SIGINT', exit.bind(undefined, true, 2));
process.once('SIGTERM', exit.bind(undefined, true, 15));
// PM2 Cluster shutdown message. Caught to support async handlers with pm2, needed because
// explicitly calling process.exit() doesn't trigger the beforeExit event, and the exit
// event cannot support async handlers, since the event loop is never called after it.
process.on('message', message => {
if (message === 'shutdown') {
exit(true, -128);
}
});
}
return () => {
callbacks.delete(onExit);
};
}
module.exports = {
exitHook
}

View File

@@ -1,6 +1,6 @@
const fs = require("fs")
const readline = require("readline")
const { exec } = require("child_process")
const { spawnSync } = require("child_process")
const { loadCache } = require("./utils")
const exportToSeelie = proto => {
@@ -14,7 +14,7 @@ const exportToSeelie = proto => {
}
const exportToPaimon = async proto => {
const out = { achievements: {} }
const out = { achievement: {} }
const achTable = new Map()
const excel = await loadCache("ExcelBinOutput/AchievementExcelConfigData.json")
excel.forEach(({GoalId, Id}) => {
@@ -22,10 +22,10 @@ const exportToPaimon = async proto => {
})
proto.list.filter(achievement => achievement.status === 3).forEach(({id}) => {
const gid = achTable.get(id)
if (out.achievements[gid] === undefined) {
out.achievements[gid] = {}
if (out.achievement[gid] === undefined) {
out.achievement[gid] = {}
}
out.achievements[gid][id] = true
out.achievement[gid][id] = true
})
const fp = `./export-${Date.now()}-paimon.json`
fs.writeFileSync(fp, JSON.stringify(out))
@@ -60,8 +60,12 @@ const exportToCocogoat = async proto => {
date: getDate(finishTimestamp)
})
})
exec("clip").stdin.end(JSON.stringify(out,null,2))
console.log("导出内容已复制到剪贴板")
const ts = Date.now()
const json = JSON.stringify(out,null,2)
spawnSync("clip", { input: json })
const fp = `./export-${ts}-cocogoat.json`
fs.writeFileSync(fp, json)
console.log(`导出内容已复制到剪贴板,若拷贝失败请手动复制 export-${ts}-cocogoat.json 文件内容`)
}
const exportToCsv = async proto => {

View File

@@ -1,6 +1,6 @@
const version = {
code: 2,
name: "1.0.1"
code: 4,
name: "1.3"
}
module.exports = { version }