mirror of
https://github.com/Auties00/Reboot-Launcher.git
synced 2026-01-13 11:12:23 +01:00
Compare commits
19 Commits
9.1.4
...
_onLoggedI
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e1df46efd9 | ||
|
|
eb7745cc4d | ||
|
|
6f91ad0404 | ||
|
|
dfebe74518 | ||
|
|
bfe15e43d9 | ||
|
|
a9af28273a | ||
|
|
4c3fe9bc65 | ||
|
|
582270849e | ||
|
|
1ef4e76768 | ||
|
|
cd8c8e6dd9 | ||
|
|
a2505011d9 | ||
|
|
3e2c2e96b1 | ||
|
|
e3b8d7d182 | ||
|
|
45b8629207 | ||
|
|
2df1b81485 | ||
|
|
0d64251623 | ||
|
|
47adb572ea | ||
|
|
e3a42d6b81 | ||
|
|
e24f4e97b3 |
4
backend/README.md
Normal file
4
backend/README.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# Backend
|
||||
Fork of LawinV1
|
||||
Awaiting rewrite in Dart
|
||||
Use build.bat to generate the executable
|
||||
@@ -4,6 +4,16 @@ const fs = require("fs");
|
||||
const path = require("path");
|
||||
const cookieParser = require("cookie-parser");
|
||||
|
||||
const audit = require('express-requests-logger')
|
||||
express.use(audit({
|
||||
request: {
|
||||
maxBodyLength: 150
|
||||
},
|
||||
response: {
|
||||
maxBodyLength: 150
|
||||
}
|
||||
}));
|
||||
|
||||
express.use(Express.json());
|
||||
express.use(Express.urlencoded({ extended: true }));
|
||||
express.use(Express.static('public'));
|
||||
@@ -25,7 +35,7 @@ express.use(require("./structure/matchmaking.js"));
|
||||
express.use(require("./structure/cloudstorage.js"));
|
||||
express.use(require("./structure/mcp.js"));
|
||||
|
||||
const port = process.env.PORT || 3551;
|
||||
const port = 3551;
|
||||
express.listen(port, () => {
|
||||
console.log("LawinServer started listening on port", port);
|
||||
|
||||
0
backend/install_packages.bat
Normal file
0
backend/install_packages.bat
Normal file
248
lawin/package-lock.json → backend/package-lock.json
generated
248
lawin/package-lock.json → backend/package-lock.json
generated
@@ -11,6 +11,7 @@
|
||||
"dependencies": {
|
||||
"cookie-parser": "^1.4.6",
|
||||
"express": "^4.18.2",
|
||||
"express-requests-logger": "^4.0.0",
|
||||
"ini": "^2.0.0",
|
||||
"nexe": "^4.0.0-rc.6",
|
||||
"path": "^0.12.7",
|
||||
@@ -388,6 +389,23 @@
|
||||
"node": ">v0.4.12"
|
||||
}
|
||||
},
|
||||
"node_modules/bunyan": {
|
||||
"version": "1.8.15",
|
||||
"resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.15.tgz",
|
||||
"integrity": "sha512-0tECWShh6wUysgucJcBAoYegf3JJoZWibxdqhTm7OHPeT42qdjkZ29QCMcKwbgU1kiH+auSIasNRXMLWXafXig==",
|
||||
"engines": [
|
||||
"node >=0.10.0"
|
||||
],
|
||||
"bin": {
|
||||
"bunyan": "bin/bunyan"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"dtrace-provider": "~0.8",
|
||||
"moment": "^2.19.3",
|
||||
"mv": "~2",
|
||||
"safe-json-stringify": "~1"
|
||||
}
|
||||
},
|
||||
"node_modules/bytes": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||
@@ -1063,6 +1081,19 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/dtrace-provider": {
|
||||
"version": "0.8.8",
|
||||
"resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.8.tgz",
|
||||
"integrity": "sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==",
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"nan": "^2.14.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/duplexer3": {
|
||||
"version": "0.1.5",
|
||||
"resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz",
|
||||
@@ -1168,6 +1199,16 @@
|
||||
"node": ">= 0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/express-requests-logger": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/express-requests-logger/-/express-requests-logger-4.0.0.tgz",
|
||||
"integrity": "sha512-NHQptnDY0fceiTSWLnW0dbJSFlrvbFpCGHmY6LsTMmJLgkyO3x8qAJ+EsryQRMga20YH8Ynt/vnmg23QP07h1Q==",
|
||||
"dependencies": {
|
||||
"bunyan": "^1.8.14",
|
||||
"flat": "^5.0.2",
|
||||
"lodash": "^4.17.14"
|
||||
}
|
||||
},
|
||||
"node_modules/express/node_modules/cookie": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
|
||||
@@ -1292,6 +1333,14 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/flat": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
|
||||
"integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
|
||||
"bin": {
|
||||
"flat": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/fn.name": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz",
|
||||
@@ -1788,6 +1837,11 @@
|
||||
"resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz",
|
||||
"integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A=="
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"node_modules/log-symbols": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz",
|
||||
@@ -1973,6 +2027,15 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/moment": {
|
||||
"version": "2.30.1",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
|
||||
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/moo-server": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/moo-server/-/moo-server-1.3.0.tgz",
|
||||
@@ -2009,6 +2072,77 @@
|
||||
"readable-stream": "^3.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mv": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz",
|
||||
"integrity": "sha512-at/ZndSy3xEGJ8i0ygALh8ru9qy7gWW1cmkaqBN29JmMlIvM//MEO9y1sk/avxuwnPcfhkejkLsuPxH81BrkSg==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"mkdirp": "~0.5.1",
|
||||
"ncp": "~2.0.0",
|
||||
"rimraf": "~2.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mv/node_modules/glob": {
|
||||
"version": "6.0.4",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz",
|
||||
"integrity": "sha512-MKZeRNyYZAVVVG1oZeLaWie1uweH40m9AZwIwxyPbTSX4hHrVYSzLg0Ro5Z5R7XKkIX+Cc6oD1rqeDJnwsB8/A==",
|
||||
"deprecated": "Glob versions prior to v9 are no longer supported",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "2 || 3",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/mv/node_modules/mkdirp": {
|
||||
"version": "0.5.6",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
|
||||
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.6"
|
||||
},
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
}
|
||||
},
|
||||
"node_modules/mv/node_modules/rimraf": {
|
||||
"version": "2.4.5",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz",
|
||||
"integrity": "sha512-J5xnxTyqaiw06JjMftq7L9ouA448dw/E7dKghkP9WpKNuwmARNNg+Gk8/u5ryb9N/Yo2+z3MCwuqFK/+qPOPfQ==",
|
||||
"deprecated": "Rimraf versions prior to v4 are no longer supported",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"glob": "^6.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"rimraf": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/nan": {
|
||||
"version": "2.20.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.20.0.tgz",
|
||||
"integrity": "sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/ncp": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz",
|
||||
"integrity": "sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA==",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
"ncp": "bin/ncp"
|
||||
}
|
||||
},
|
||||
"node_modules/negotiator": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||
@@ -2534,6 +2668,12 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/safe-json-stringify": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz",
|
||||
"integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/safe-stable-stringify": {
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz",
|
||||
@@ -3405,6 +3545,17 @@
|
||||
"wrench": "1.3.x"
|
||||
}
|
||||
},
|
||||
"bunyan": {
|
||||
"version": "1.8.15",
|
||||
"resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.15.tgz",
|
||||
"integrity": "sha512-0tECWShh6wUysgucJcBAoYegf3JJoZWibxdqhTm7OHPeT42qdjkZ29QCMcKwbgU1kiH+auSIasNRXMLWXafXig==",
|
||||
"requires": {
|
||||
"dtrace-provider": "~0.8",
|
||||
"moment": "^2.19.3",
|
||||
"mv": "~2",
|
||||
"safe-json-stringify": "~1"
|
||||
}
|
||||
},
|
||||
"bytes": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||
@@ -3941,6 +4092,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"dtrace-provider": {
|
||||
"version": "0.8.8",
|
||||
"resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.8.tgz",
|
||||
"integrity": "sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"nan": "^2.14.0"
|
||||
}
|
||||
},
|
||||
"duplexer3": {
|
||||
"version": "0.1.5",
|
||||
"resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz",
|
||||
@@ -4038,6 +4198,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"express-requests-logger": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/express-requests-logger/-/express-requests-logger-4.0.0.tgz",
|
||||
"integrity": "sha512-NHQptnDY0fceiTSWLnW0dbJSFlrvbFpCGHmY6LsTMmJLgkyO3x8qAJ+EsryQRMga20YH8Ynt/vnmg23QP07h1Q==",
|
||||
"requires": {
|
||||
"bunyan": "^1.8.14",
|
||||
"flat": "^5.0.2",
|
||||
"lodash": "^4.17.14"
|
||||
}
|
||||
},
|
||||
"ext-list": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz",
|
||||
@@ -4130,6 +4300,11 @@
|
||||
"unpipe": "~1.0.0"
|
||||
}
|
||||
},
|
||||
"flat": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
|
||||
"integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ=="
|
||||
},
|
||||
"fn.name": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz",
|
||||
@@ -4496,6 +4671,11 @@
|
||||
"resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz",
|
||||
"integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A=="
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"log-symbols": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz",
|
||||
@@ -4626,6 +4806,12 @@
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
|
||||
},
|
||||
"moment": {
|
||||
"version": "2.30.1",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
|
||||
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
|
||||
"optional": true
|
||||
},
|
||||
"moo-server": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/moo-server/-/moo-server-1.3.0.tgz",
|
||||
@@ -4645,6 +4831,62 @@
|
||||
"readable-stream": "^3.6.0"
|
||||
}
|
||||
},
|
||||
"mv": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz",
|
||||
"integrity": "sha512-at/ZndSy3xEGJ8i0ygALh8ru9qy7gWW1cmkaqBN29JmMlIvM//MEO9y1sk/avxuwnPcfhkejkLsuPxH81BrkSg==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"mkdirp": "~0.5.1",
|
||||
"ncp": "~2.0.0",
|
||||
"rimraf": "~2.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"glob": {
|
||||
"version": "6.0.4",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz",
|
||||
"integrity": "sha512-MKZeRNyYZAVVVG1oZeLaWie1uweH40m9AZwIwxyPbTSX4hHrVYSzLg0Ro5Z5R7XKkIX+Cc6oD1rqeDJnwsB8/A==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "2 || 3",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "0.5.6",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
|
||||
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"minimist": "^1.2.6"
|
||||
}
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "2.4.5",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz",
|
||||
"integrity": "sha512-J5xnxTyqaiw06JjMftq7L9ouA448dw/E7dKghkP9WpKNuwmARNNg+Gk8/u5ryb9N/Yo2+z3MCwuqFK/+qPOPfQ==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"glob": "^6.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"nan": {
|
||||
"version": "2.20.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.20.0.tgz",
|
||||
"integrity": "sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==",
|
||||
"optional": true
|
||||
},
|
||||
"ncp": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz",
|
||||
"integrity": "sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA==",
|
||||
"optional": true
|
||||
},
|
||||
"negotiator": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||
@@ -5000,6 +5242,12 @@
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
|
||||
},
|
||||
"safe-json-stringify": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz",
|
||||
"integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==",
|
||||
"optional": true
|
||||
},
|
||||
"safe-stable-stringify": {
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz",
|
||||
@@ -12,7 +12,8 @@
|
||||
"uuid": "^8.3.2",
|
||||
"ws": "^8.5.0",
|
||||
"xml-parser": "^1.2.1",
|
||||
"xmlbuilder": "^15.1.1"
|
||||
"xmlbuilder": "^15.1.1",
|
||||
"express-requests-logger": "^4.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node index.js",
|
||||
File diff suppressed because it is too large
Load Diff
0
backend/public/images/discord-s.png
Normal file
0
backend/public/images/discord-s.png
Normal file
0
backend/public/images/discord.png
Normal file
0
backend/public/images/discord.png
Normal file
0
backend/public/images/lawin-s.png
Normal file
0
backend/public/images/lawin-s.png
Normal file
0
backend/public/images/lawin.jpg
Normal file
0
backend/public/images/lawin.jpg
Normal file
0
backend/public/images/motd-s.png
Normal file
0
backend/public/images/motd-s.png
Normal file
0
backend/public/images/motd.png
Normal file
0
backend/public/images/motd.png
Normal file
0
backend/public/images/seasonx.png
Normal file
0
backend/public/images/seasonx.png
Normal file
0
backend/responses/CloudDir/LawinServer.chunk
Normal file
0
backend/responses/CloudDir/LawinServer.chunk
Normal file
0
backend/responses/CloudDir/LawinServer.manifest
Normal file
0
backend/responses/CloudDir/LawinServer.manifest
Normal file
@@ -75,5 +75,12 @@
|
||||
"direction": "OUTBOUND",
|
||||
"created": "2024-05-31T19:50:04.738Z",
|
||||
"favorite": false
|
||||
},
|
||||
{
|
||||
"accountId": "Player724",
|
||||
"status": "ACCEPTED",
|
||||
"direction": "OUTBOUND",
|
||||
"created": "2024-06-24T20:15:48.062Z",
|
||||
"favorite": false
|
||||
}
|
||||
]
|
||||
@@ -98,6 +98,15 @@
|
||||
"note": "",
|
||||
"favorite": false,
|
||||
"created": "2024-05-31T19:50:04.738Z"
|
||||
},
|
||||
{
|
||||
"accountId": "Player724",
|
||||
"groups": [],
|
||||
"mutual": 0,
|
||||
"alias": "",
|
||||
"note": "",
|
||||
"favorite": false,
|
||||
"created": "2024-06-24T20:15:48.062Z"
|
||||
}
|
||||
],
|
||||
"incoming": [],
|
||||
@@ -51,7 +51,7 @@ void main(List<String> args) async {
|
||||
}
|
||||
|
||||
stdout.writeln("Launching game...");
|
||||
var executable = version.gameExecutable;
|
||||
var executable = version.shippingExecutable;
|
||||
if(executable == null){
|
||||
throw Exception("Missing game executable at: ${version.location.path}");
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ Future<void> startGame() async {
|
||||
await _startLauncherProcess(version);
|
||||
await _startEacProcess(version);
|
||||
|
||||
var executable = await version.gameExecutable;
|
||||
var executable = await version.shippingExecutable;
|
||||
if (executable == null) {
|
||||
throw Exception("${version.location.path} no longer contains a Fortnite executable, did you delete or move it?");
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ Future<bool> startServerCli(String? host, int? port, ServerType type) async {
|
||||
stdout.writeln("Starting backend server...");
|
||||
switch(type){
|
||||
case ServerType.local:
|
||||
var result = await pingBackend(host ?? kDefaultBackendHost, port ?? kDefaultBackendPort);
|
||||
final result = await pingBackend(host ?? kDefaultBackendHost, port ?? kDefaultBackendPort);
|
||||
if(result == null){
|
||||
throw Exception("Local backend server is not running");
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ export 'package:reboot_common/src/model/server_result.dart';
|
||||
export 'package:reboot_common/src/model/server_type.dart';
|
||||
export 'package:reboot_common/src/model/update_status.dart';
|
||||
export 'package:reboot_common/src/model/update_timer.dart';
|
||||
export 'package:reboot_common/src/model/fortnite_server.dart';
|
||||
export 'package:reboot_common/src/model/dll.dart';
|
||||
export 'package:reboot_common/src/util/backend.dart';
|
||||
export 'package:reboot_common/src/util/build.dart';
|
||||
@@ -17,4 +18,5 @@ export 'package:reboot_common/src/util/dll.dart';
|
||||
export 'package:reboot_common/src/util/network.dart';
|
||||
export 'package:reboot_common/src/util/patcher.dart';
|
||||
export 'package:reboot_common/src/util/path.dart';
|
||||
export 'package:reboot_common/src/util/process.dart';
|
||||
export 'package:reboot_common/src/util/process.dart';
|
||||
export 'package:reboot_common/src/util/log.dart';
|
||||
@@ -1,2 +1,3 @@
|
||||
const String kDefaultBackendHost = "127.0.0.1";
|
||||
const int kDefaultBackendPort = 3551;
|
||||
const int kDefaultBackendPort = 3551;
|
||||
const int kDefaultXmppPort = 80;
|
||||
@@ -1,4 +1,5 @@
|
||||
const String kDefaultPlayerName = "Player";
|
||||
const String kDefaultHostName = "Host";
|
||||
const String kDefaultGameServerHost = "127.0.0.1";
|
||||
const String kDefaultGameServerPort = "7777";
|
||||
const String kInitializedLine = "Game Engine Initialized";
|
||||
@@ -11,7 +12,7 @@ const List<String> kCorruptedBuildErrors = [
|
||||
"Critical error",
|
||||
"when 0 bytes remain",
|
||||
"Pak chunk signature verification failed!",
|
||||
"Couldn't find pak signature file"
|
||||
"LogWindows:Error: Fatal error!"
|
||||
];
|
||||
const List<String> kCannotConnectErrors = [
|
||||
"port 3551 failed: Connection refused",
|
||||
|
||||
@@ -5,38 +5,50 @@ import 'package:path/path.dart' as path;
|
||||
import 'package:reboot_common/common.dart';
|
||||
|
||||
extension FortniteVersionExtension on FortniteVersion {
|
||||
static File? findExecutable(Directory directory, String name) {
|
||||
static String _marker = "FortniteClient.mod";
|
||||
|
||||
static File? findFile(Directory directory, String name) {
|
||||
try{
|
||||
final result = directory.listSync(recursive: true)
|
||||
.firstWhere((element) => path.basename(element.path) == name);
|
||||
return File(result.path);
|
||||
for(final child in directory.listSync()) {
|
||||
if(child is Directory) {
|
||||
if(!path.basename(child.path).startsWith("\.")) {
|
||||
final result = findFile(child, name);
|
||||
if(result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}else if(child is File) {
|
||||
if(path.basename(child.path) == name) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}catch(_){
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
File? get gameExecutable => findExecutable(location, "FortniteClient-Win64-Shipping.exe");
|
||||
|
||||
Future<File?> get headlessGameExecutable async {
|
||||
final result = findExecutable(location, "FortniteClient-Win64-Shipping-Headless.exe");
|
||||
if(result != null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
final original = findExecutable(location, "FortniteClient-Win64-Shipping.exe");
|
||||
if(original == null) {
|
||||
Future<File?> get shippingExecutable async {
|
||||
final result = findFile(location, "FortniteClient-Win64-Shipping.exe");
|
||||
if(result == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final output = File("${original.parent.path}\\FortniteClient-Win64-Shipping-Headless.exe");
|
||||
await original.copy(output.path);
|
||||
await Isolate.run(() => patchHeadless(output));
|
||||
return output;
|
||||
final marker = findFile(location, _marker);
|
||||
if(marker != null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
await Isolate.run(() => patchHeadless(result));
|
||||
await File("${location.path}\\$_marker").create();
|
||||
return result;
|
||||
}
|
||||
|
||||
File? get launcherExecutable => findExecutable(location, "FortniteLauncher.exe");
|
||||
File? get launcherExecutable => findFile(location, "FortniteLauncher.exe");
|
||||
|
||||
File? get eacExecutable => findExecutable(location, "FortniteClient-Win64-Shipping_EAC.exe");
|
||||
File? get eacExecutable => findFile(location, "FortniteClient-Win64-Shipping_EAC.exe");
|
||||
|
||||
File? get splashBitmap => findExecutable(location, "Splash.bmp");
|
||||
File? get splashBitmap => findFile(location, "Splash.bmp");
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
enum InjectableDll {
|
||||
console,
|
||||
cobalt,
|
||||
starfall,
|
||||
reboot,
|
||||
memory
|
||||
}
|
||||
|
||||
extension InjectableDllVersionAware on InjectableDll {
|
||||
bool get isVersionDependent => this == InjectableDll.reboot;
|
||||
}
|
||||
|
||||
@@ -1,27 +1,31 @@
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:version/version.dart';
|
||||
|
||||
class FortniteBuild {
|
||||
final String identifier;
|
||||
final String version;
|
||||
final Version version;
|
||||
final String link;
|
||||
final bool available;
|
||||
|
||||
FortniteBuild({
|
||||
required this.identifier,
|
||||
required this.version,
|
||||
required this.link
|
||||
required this.link,
|
||||
required this.available
|
||||
});
|
||||
}
|
||||
|
||||
class FortniteBuildDownloadProgress {
|
||||
final double progress;
|
||||
final int? minutesLeft;
|
||||
final int? timeLeft;
|
||||
final bool extracting;
|
||||
final int speed;
|
||||
|
||||
FortniteBuildDownloadProgress({
|
||||
required this.progress,
|
||||
required this.extracting,
|
||||
this.minutesLeft,
|
||||
required this.timeLeft,
|
||||
required this.speed
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
47
common/lib/src/model/fortnite_server.dart
Normal file
47
common/lib/src/model/fortnite_server.dart
Normal file
@@ -0,0 +1,47 @@
|
||||
class FortniteServer {
|
||||
final String id;
|
||||
final String name;
|
||||
final String description;
|
||||
final String author;
|
||||
final String ip;
|
||||
final String version;
|
||||
final String? password;
|
||||
final DateTime timestamp;
|
||||
final bool discoverable;
|
||||
|
||||
FortniteServer({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.description,
|
||||
required this.author,
|
||||
required this.ip,
|
||||
required this.version,
|
||||
required this.password,
|
||||
required this.timestamp,
|
||||
required this.discoverable
|
||||
});
|
||||
|
||||
factory FortniteServer.fromJson(json) => FortniteServer(
|
||||
id: json["id"],
|
||||
name: json["name"],
|
||||
description: json["description"],
|
||||
author: json["author"],
|
||||
ip: json["ip"],
|
||||
version: json["version"],
|
||||
password: json["password"],
|
||||
timestamp: json.containsKey("json") ? DateTime.parse(json["timestamp"]) : DateTime.now(),
|
||||
discoverable: json["discoverable"] ?? false
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"id": id,
|
||||
"name": name,
|
||||
"description": description,
|
||||
"author": author,
|
||||
"ip": ip,
|
||||
"version": version,
|
||||
"password": password,
|
||||
"timestamp": timestamp.toString(),
|
||||
"discoverable": discoverable
|
||||
};
|
||||
}
|
||||
@@ -1,17 +1,22 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:version/version.dart';
|
||||
|
||||
class FortniteVersion {
|
||||
String name;
|
||||
Version content;
|
||||
Directory location;
|
||||
|
||||
FortniteVersion.fromJson(json)
|
||||
: name = json["name"],
|
||||
: content = Version.parse(json["content"]),
|
||||
location = Directory(json["location"]);
|
||||
|
||||
FortniteVersion({required this.name, required this.location});
|
||||
FortniteVersion({required this.content, required this.location});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'name': name,
|
||||
'content': content.toString(),
|
||||
'location': location.path
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => other is FortniteVersion && this.content == other.content;
|
||||
}
|
||||
@@ -1,30 +1,42 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:reboot_common/common.dart';
|
||||
import 'package:version/version.dart';
|
||||
|
||||
|
||||
class GameInstance {
|
||||
final String versionName;
|
||||
final Version version;
|
||||
final int gamePid;
|
||||
final int? launcherPid;
|
||||
final int? eacPid;
|
||||
final List<InjectableDll> injectedDlls;
|
||||
bool hosting;
|
||||
final GameServerType? serverType;
|
||||
bool launched;
|
||||
bool movedToVirtualDesktop;
|
||||
bool tokenError;
|
||||
bool killed;
|
||||
GameInstance? child;
|
||||
|
||||
GameInstance({
|
||||
required this.versionName,
|
||||
required this.version,
|
||||
required this.gamePid,
|
||||
required this.launcherPid,
|
||||
required this.eacPid,
|
||||
required this.hosting,
|
||||
required this.serverType,
|
||||
required this.child
|
||||
}): tokenError = false, launched = false, movedToVirtualDesktop = false, injectedDlls = [];
|
||||
}): tokenError = false, killed = false, launched = false, movedToVirtualDesktop = false, injectedDlls = [];
|
||||
|
||||
void kill() {
|
||||
GameInstance? child = this;
|
||||
while(child != null) {
|
||||
child._kill();
|
||||
child = child.child;
|
||||
}
|
||||
}
|
||||
|
||||
void _kill() {
|
||||
launched = true;
|
||||
killed = true;
|
||||
Process.killPid(gamePid, ProcessSignal.sigabrt);
|
||||
if(launcherPid != null) {
|
||||
Process.killPid(launcherPid!, ProcessSignal.sigabrt);
|
||||
@@ -33,17 +45,10 @@ class GameInstance {
|
||||
Process.killPid(eacPid!, ProcessSignal.sigabrt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool get nestedHosting {
|
||||
GameInstance? child = this;
|
||||
while(child != null) {
|
||||
if(child.hosting) {
|
||||
return true;
|
||||
}
|
||||
|
||||
child = child.child;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
enum GameServerType {
|
||||
headless,
|
||||
virtualWindow,
|
||||
window
|
||||
}
|
||||
@@ -4,6 +4,11 @@ class ServerResult {
|
||||
final StackTrace? stackTrace;
|
||||
|
||||
ServerResult(this.type, {this.error, this.stackTrace});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ServerResult{type: $type, error: $error, stackTrace: $stackTrace}';
|
||||
}
|
||||
}
|
||||
|
||||
enum ServerResultType {
|
||||
|
||||
@@ -15,10 +15,19 @@ final Semaphore _semaphore = Semaphore();
|
||||
String? _lastIp;
|
||||
String? _lastPort;
|
||||
|
||||
Future<Process> startEmbeddedBackend(bool detached) async => startProcess(
|
||||
Future<Process> startEmbeddedBackend(bool detached, {void Function(String)? onError}) async {
|
||||
final process = await startProcess(
|
||||
executable: backendStartExecutable,
|
||||
window: detached,
|
||||
);
|
||||
);
|
||||
process.stdOutput.listen((message) => log("[BACKEND] Message: $message"));
|
||||
process.stdError.listen((error) {
|
||||
log("[BACKEND] Error: $error");
|
||||
onError?.call(error);
|
||||
});
|
||||
process.exitCode.then((exitCode) => log("[BACKEND] Exit code: $exitCode"));
|
||||
return process;
|
||||
}
|
||||
|
||||
Future<HttpServer> startRemoteBackendProxy(Uri uri) async => await serve(proxyHandler(uri), kDefaultBackendHost, kDefaultBackendPort);
|
||||
|
||||
@@ -26,6 +35,7 @@ Future<bool> isBackendPortFree() async => await pingBackend(kDefaultBackendHost,
|
||||
|
||||
Future<bool> freeBackendPort() async {
|
||||
await killProcessByPort(kDefaultBackendPort);
|
||||
await killProcessByPort(kDefaultXmppPort);
|
||||
final standardResult = await isBackendPortFree();
|
||||
if(standardResult) {
|
||||
return true;
|
||||
@@ -35,21 +45,24 @@ Future<bool> freeBackendPort() async {
|
||||
}
|
||||
|
||||
Future<Uri?> pingBackend(String host, int port, [bool https=false]) async {
|
||||
var hostName = host.replaceFirst("http://", "").replaceFirst("https://", "");
|
||||
var declaredScheme = host.startsWith("http://") ? "http" : host.startsWith("https://") ? "https" : null;
|
||||
final hostName = host.replaceFirst("http://", "").replaceFirst("https://", "");
|
||||
final declaredScheme = host.startsWith("http://") ? "http" : host.startsWith("https://") ? "https" : null;
|
||||
try{
|
||||
var uri = Uri(
|
||||
final uri = Uri(
|
||||
scheme: declaredScheme ?? (https ? "https" : "http"),
|
||||
host: hostName,
|
||||
port: port,
|
||||
path: "unknown"
|
||||
);
|
||||
var client = HttpClient()
|
||||
..connectionTimeout = const Duration(seconds: 5);
|
||||
var request = await client.getUrl(uri);
|
||||
var response = await request.close();
|
||||
return response.statusCode == 200 || response.statusCode == 404 ? uri : null;
|
||||
}catch(_){
|
||||
log("[BACKEND] Pinging $uri...");
|
||||
final client = HttpClient()
|
||||
..connectionTimeout = const Duration(seconds: 10);
|
||||
final request = await client.getUrl(uri);
|
||||
await request.close().timeout(const Duration(seconds: 10));
|
||||
log("[BACKEND] Ping successful");
|
||||
return uri;
|
||||
}catch(error){
|
||||
log("[BACKEND] Cannot ping backend: $error");
|
||||
return https || declaredScheme != null || isLocalHost(host) ? null : await pingBackend(host, port, true);
|
||||
}
|
||||
}
|
||||
@@ -59,16 +72,16 @@ Stream<String?> watchMatchmakingIp() async* {
|
||||
return;
|
||||
}
|
||||
|
||||
var observer = matchmakerConfigFile.parent.watch(events: FileSystemEvent.modify);
|
||||
final observer = matchmakerConfigFile.parent.watch(events: FileSystemEvent.modify);
|
||||
yield* observer.where((event) => event.path == matchmakerConfigFile.path).asyncMap((event) async {
|
||||
try {
|
||||
var config = Config.fromString(await matchmakerConfigFile.readAsString());
|
||||
var ip = config.get("GameServer", "ip");
|
||||
final config = Config.fromString(await matchmakerConfigFile.readAsString());
|
||||
final ip = config.get("GameServer", "ip");
|
||||
if(ip == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var port = config.get("GameServer", "port");
|
||||
final port = config.get("GameServer", "port");
|
||||
if(port == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -89,14 +102,14 @@ Stream<String?> watchMatchmakingIp() async* {
|
||||
}
|
||||
|
||||
Future<void> writeMatchmakingIp(String text) async {
|
||||
var exists = await matchmakerConfigFile.exists();
|
||||
final exists = await matchmakerConfigFile.exists();
|
||||
if(!exists) {
|
||||
return;
|
||||
}
|
||||
|
||||
_semaphore.acquire();
|
||||
var splitIndex = text.indexOf(":");
|
||||
var ip = splitIndex != -1 ? text.substring(0, splitIndex) : text;
|
||||
final splitIndex = text.indexOf(":");
|
||||
final ip = splitIndex != -1 ? text.substring(0, splitIndex) : text;
|
||||
var port = splitIndex != -1 ? text.substring(splitIndex + 1) : kDefaultGameServerPort;
|
||||
if(port.isBlank) {
|
||||
port = kDefaultGameServerPort;
|
||||
@@ -104,7 +117,7 @@ Future<void> writeMatchmakingIp(String text) async {
|
||||
|
||||
_lastIp = ip;
|
||||
_lastPort = port;
|
||||
var config = Config.fromString(await matchmakerConfigFile.readAsString());
|
||||
final config = Config.fromString(await matchmakerConfigFile.readAsString());
|
||||
config.set("GameServer", "ip", ip);
|
||||
config.set("GameServer", "port", port);
|
||||
await matchmakerConfigFile.writeAsString(config.toString(), flush: true);
|
||||
|
||||
@@ -3,136 +3,243 @@ import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:dio/io.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:reboot_common/common.dart';
|
||||
import 'package:reboot_common/src/extension/types.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
import 'package:version/version.dart';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
const String kStopBuildDownloadSignal = "kill";
|
||||
|
||||
final Dio _dio = _buildDioInstance();
|
||||
Dio _buildDioInstance() {
|
||||
final dio = Dio();
|
||||
final httpClientAdapter = dio.httpClientAdapter as IOHttpClientAdapter;
|
||||
httpClientAdapter.createHttpClient = () {
|
||||
final client = HttpClient();
|
||||
client.badCertificateCallback = (X509Certificate cert, String host, int port) => true;
|
||||
return client;
|
||||
};
|
||||
return dio;
|
||||
}
|
||||
|
||||
final String _archiveSourceUrl = "http://185.203.216.3/versions.json";
|
||||
final Uri _archiveSourceUrl = Uri.parse("https://builds.rebootfn.org/versions.json");
|
||||
final int _ariaPort = 6800;
|
||||
final Uri _ariaEndpoint = Uri.parse('http://localhost:$_ariaPort/jsonrpc');
|
||||
final Duration _ariaMaxSpawnTime = const Duration(seconds: 10);
|
||||
final String _ariaSecret = "RebootLauncher";
|
||||
final RegExp _rarProgressRegex = RegExp("^((100)|(\\d{1,2}(.\\d*)?))%\$");
|
||||
const String _deniedConnectionError = "The connection was denied: your firewall might be blocking the download";
|
||||
const String _unavailableError = "The build downloader is not available right now";
|
||||
const String _genericError = "The build downloader is not working correctly";
|
||||
const int _maxErrors = 100;
|
||||
|
||||
Future<List<FortniteBuild>> fetchBuilds(ignored) async {
|
||||
final response = await _dio.get<String>(
|
||||
_archiveSourceUrl,
|
||||
options: Options(
|
||||
responseType: ResponseType.plain
|
||||
)
|
||||
);
|
||||
final response = await http.get(_archiveSourceUrl);
|
||||
if (response.statusCode != 200) {
|
||||
return [];
|
||||
}
|
||||
|
||||
final data = jsonDecode(response.data ?? "{}");
|
||||
var results = <FortniteBuild>[];
|
||||
for(final entry in data.entries) {
|
||||
results.add(FortniteBuild(
|
||||
identifier: entry.key,
|
||||
version: "${entry.value["title"]} (${entry.key})",
|
||||
link: entry.value["url"]
|
||||
));
|
||||
}
|
||||
return results;
|
||||
return jsonDecode(response.body)
|
||||
.map((entry) {
|
||||
try {
|
||||
final fileUrl = entry as String;
|
||||
final fileName = Uri.parse(fileUrl).pathSegments.last;
|
||||
final fileNameWithoutExtension = path.basenameWithoutExtension(fileName);
|
||||
return FortniteBuild(
|
||||
version: Version.parse(fileNameWithoutExtension),
|
||||
link: entry,
|
||||
available: true
|
||||
);
|
||||
}catch(_) {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.whereType<FortniteBuild>()
|
||||
.toList();
|
||||
}
|
||||
|
||||
|
||||
Future<void> downloadArchiveBuild(FortniteBuildDownloadOptions options) async {
|
||||
final fileName = options.build.link.substring(options.build.link.lastIndexOf("/") + 1);
|
||||
final outputFile = File("${options.destination.path}\\.build\\$fileName");
|
||||
try {
|
||||
final stopped = _setupLifecycle(options);
|
||||
final outputDir = Directory("${options.destination.path}\\.build");
|
||||
await outputDir.create(recursive: true);
|
||||
final fileName = options.build.link.substring(options.build.link.lastIndexOf("/") + 1);
|
||||
final extension = path.extension(fileName);
|
||||
final tempFile = File("${outputDir.path}\\$fileName");
|
||||
if(await tempFile.exists()) {
|
||||
await tempFile.delete(recursive: true);
|
||||
}
|
||||
await outputFile.parent.create(recursive: true);
|
||||
|
||||
final startTime = DateTime.now().millisecondsSinceEpoch;
|
||||
final response = _downloadArchive(options, tempFile, startTime);
|
||||
await Future.any([stopped.future, response]);
|
||||
final downloadItemCompleter = Completer<File>();
|
||||
|
||||
await _startAriaServer();
|
||||
final downloadId = await _startAriaDownload(options, outputFile);
|
||||
Timer.periodic(const Duration(seconds: 5), (Timer timer) async {
|
||||
try {
|
||||
final statusRequestId = Uuid().toString().replaceAll("-", "");
|
||||
final statusRequest = {
|
||||
"jsonrcp": "2.0",
|
||||
"id": statusRequestId,
|
||||
"method": "aria2.tellStatus",
|
||||
"params": [
|
||||
"token:${_ariaSecret}",
|
||||
downloadId
|
||||
]
|
||||
};
|
||||
final statusResponse = await http.post(_ariaEndpoint, body: jsonEncode(statusRequest));
|
||||
final statusResponseJson = jsonDecode(statusResponse.body) as Map?;
|
||||
if(statusResponseJson == null) {
|
||||
downloadItemCompleter.completeError("Invalid download status (invalid JSON)");
|
||||
timer.cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
final result = statusResponseJson["result"];
|
||||
final files = result["files"] as List?;
|
||||
if(files == null || files.isEmpty) {
|
||||
downloadItemCompleter.completeError("Download aborted");
|
||||
timer.cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
final error = result["errorCode"];
|
||||
if(error != null) {
|
||||
final errorCode = int.tryParse(error);
|
||||
if(errorCode == 0) {
|
||||
final path = File(files[0]["path"]);
|
||||
downloadItemCompleter.complete(path);
|
||||
}else if(errorCode == 3) {
|
||||
downloadItemCompleter.completeError("This build is not available yet");
|
||||
}else {
|
||||
final errorMessage = result["errorMessage"];
|
||||
downloadItemCompleter.completeError("$errorMessage (error code $errorCode)");
|
||||
}
|
||||
|
||||
timer.cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
final speed = int.parse(result["downloadSpeed"] ?? "0");
|
||||
final completedLength = int.parse(files[0]["completedLength"] ?? "0");
|
||||
final totalLength = int.parse(files[0]["length"] ?? "0");
|
||||
|
||||
final percentage = completedLength * 100 / totalLength;
|
||||
final minutesLeft = speed == 0 ? -1 : ((totalLength - completedLength) / speed / 60).round();
|
||||
_onProgress(
|
||||
options.port,
|
||||
percentage,
|
||||
speed,
|
||||
minutesLeft,
|
||||
false
|
||||
);
|
||||
}catch(error) {
|
||||
throw "Invalid download status (${error})";
|
||||
}
|
||||
});
|
||||
|
||||
await Future.any([stopped.future, downloadItemCompleter.future]);
|
||||
if(!stopped.isCompleted) {
|
||||
await _extractArchive(stopped, extension, tempFile, options);
|
||||
final extension = path.extension(fileName);
|
||||
await _extractArchive(stopped, extension, await downloadItemCompleter.future, options);
|
||||
}else {
|
||||
await _stopAriaDownload(downloadId);
|
||||
}
|
||||
|
||||
delete(outputDir);
|
||||
}catch(error) {
|
||||
_onError(error, options);
|
||||
}finally {
|
||||
delete(outputFile);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _downloadArchive(FortniteBuildDownloadOptions options, File tempFile, int startTime, [int? byteStart = null, int errorsCount = 0]) async {
|
||||
var received = byteStart ?? 0;
|
||||
try {
|
||||
await _dio.download(
|
||||
options.build.link,
|
||||
tempFile.path,
|
||||
onReceiveProgress: (data, length) {
|
||||
received = data;
|
||||
final percentage = (received / length) * 100;
|
||||
_onProgress(startTime, percentage < 1 ? null : DateTime.now().millisecondsSinceEpoch, percentage, false, options);
|
||||
},
|
||||
deleteOnError: false,
|
||||
options: Options(
|
||||
validateStatus: (statusCode) {
|
||||
if(statusCode == 200) {
|
||||
return true;
|
||||
}
|
||||
Future<void> _startAriaServer() async {
|
||||
final running = await _isAriaRunning();
|
||||
if(running) {
|
||||
await killProcessByPort(_ariaPort);
|
||||
}
|
||||
|
||||
if(statusCode == 403 || statusCode == 503) {
|
||||
throw _deniedConnectionError;
|
||||
}
|
||||
final aria2c = File("${assetsDirectory.path}\\build\\aria2c.exe");
|
||||
if(!aria2c.existsSync()) {
|
||||
throw "Missing aria2c.exe";
|
||||
}
|
||||
|
||||
if(statusCode == 404) {
|
||||
throw _unavailableError;
|
||||
}
|
||||
|
||||
throw _genericError;
|
||||
},
|
||||
headers: byteStart == null || byteStart <= 0 ? {
|
||||
"Cookie": "_c_t_c=1"
|
||||
} : {
|
||||
"Cookie": "_c_t_c=1",
|
||||
"Range": "bytes=${byteStart}-"
|
||||
},
|
||||
)
|
||||
);
|
||||
}catch(error) {
|
||||
if(errorsCount > _maxErrors || error.toString().contains(_deniedConnectionError) || error.toString().contains(_unavailableError)) {
|
||||
_onError(error, options);
|
||||
await startProcess(
|
||||
executable: aria2c,
|
||||
args: [
|
||||
"--max-connection-per-server=${Platform.numberOfProcessors}",
|
||||
"--split=${Platform.numberOfProcessors}",
|
||||
"--enable-rpc",
|
||||
"--rpc-listen-all=true",
|
||||
"--rpc-allow-origin-all",
|
||||
"--rpc-secret=$_ariaSecret",
|
||||
"--rpc-listen-port=$_ariaPort"
|
||||
],
|
||||
window: false
|
||||
);
|
||||
for(var i = 0; i < _ariaMaxSpawnTime.inSeconds; i++) {
|
||||
if(await _isAriaRunning()) {
|
||||
return;
|
||||
}
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
}
|
||||
throw "cannot start download server (timeout exceeded)";
|
||||
}
|
||||
|
||||
await _downloadArchive(options, tempFile, startTime, received, errorsCount + 1);
|
||||
Future<bool> _isAriaRunning() async {
|
||||
try {
|
||||
final statusRequestId = Uuid().toString().replaceAll("-", "");
|
||||
final statusRequest = {
|
||||
"jsonrcp": "2.0",
|
||||
"id": statusRequestId,
|
||||
"method": "aria2.getVersion",
|
||||
"params": [
|
||||
"token:${_ariaSecret}"
|
||||
]
|
||||
};
|
||||
await http.post(_ariaEndpoint, body: jsonEncode(statusRequest));
|
||||
return true;
|
||||
}catch(_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> _startAriaDownload(FortniteBuildDownloadOptions options, File outputFile) async {
|
||||
http.Response? addDownloadResponse;
|
||||
try {
|
||||
final addDownloadRequestId = Uuid().toString().replaceAll("-", "");
|
||||
final addDownloadRequest = {
|
||||
"jsonrcp": "2.0",
|
||||
"id": addDownloadRequestId,
|
||||
"method": "aria2.addUri",
|
||||
"params": [
|
||||
"token:${_ariaSecret}",
|
||||
[options.build.link],
|
||||
{
|
||||
"dir": outputFile.parent.path,
|
||||
"out": path.basename(outputFile.path)
|
||||
}
|
||||
]
|
||||
};
|
||||
addDownloadResponse = await http.post(_ariaEndpoint, body: jsonEncode(addDownloadRequest));
|
||||
final addDownloadResponseJson = jsonDecode(addDownloadResponse.body);
|
||||
final downloadId = addDownloadResponseJson is Map ? addDownloadResponseJson['result'] : null;
|
||||
if(downloadId == null) {
|
||||
throw "Start failed (${addDownloadResponse.body})";
|
||||
}
|
||||
|
||||
return downloadId;
|
||||
}catch(error) {
|
||||
throw "Start failed (${addDownloadResponse?.body ?? error})";
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _stopAriaDownload(String downloadId) async {
|
||||
try {
|
||||
final addDownloadRequestId = Uuid().toString().replaceAll("-", "");
|
||||
final addDownloadRequest = {
|
||||
"jsonrcp": "2.0",
|
||||
"id": addDownloadRequestId,
|
||||
"method": "aria2.forceRemove",
|
||||
"params": [
|
||||
"token:${_ariaSecret}",
|
||||
downloadId
|
||||
]
|
||||
};
|
||||
await http.post(_ariaEndpoint, body: jsonEncode(addDownloadRequest));
|
||||
}catch(error) {
|
||||
throw "Stop failed (${error})";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Future<void> _extractArchive(Completer<dynamic> stopped, String extension, File tempFile, FortniteBuildDownloadOptions options) async {
|
||||
final startTime = DateTime.now().millisecondsSinceEpoch;
|
||||
Process? process;
|
||||
switch (extension.toLowerCase()) {
|
||||
case ".zip":
|
||||
final sevenZip = File("${assetsDirectory.path}\\build\\7zip.exe");
|
||||
if(!sevenZip.existsSync()) {
|
||||
throw "Corrupted installation: missing 7zip.exe";
|
||||
throw "Missing 7zip.exe";
|
||||
}
|
||||
|
||||
process = await startProcess(
|
||||
@@ -147,10 +254,15 @@ Future<void> _extractArchive(Completer<dynamic> stopped, String extension, File
|
||||
);
|
||||
var completed = false;
|
||||
process.stdOutput.listen((data) {
|
||||
final now = DateTime.now().millisecondsSinceEpoch;
|
||||
if(data.toLowerCase().contains("everything is ok")) {
|
||||
completed = true;
|
||||
_onProgress(startTime, now, 100, true, options);
|
||||
_onProgress(
|
||||
options.port,
|
||||
100,
|
||||
0,
|
||||
-1,
|
||||
true
|
||||
);
|
||||
process?.kill(ProcessSignal.sigabrt);
|
||||
return;
|
||||
}
|
||||
@@ -161,7 +273,13 @@ Future<void> _extractArchive(Completer<dynamic> stopped, String extension, File
|
||||
}
|
||||
|
||||
final percentage = int.parse(element.substring(0, element.length - 1)).toDouble();
|
||||
_onProgress(startTime, now, percentage, true, options);
|
||||
_onProgress(
|
||||
options.port,
|
||||
percentage,
|
||||
0,
|
||||
-1,
|
||||
true
|
||||
);
|
||||
});
|
||||
process.stdError.listen((data) {
|
||||
if(!data.isBlank) {
|
||||
@@ -177,7 +295,7 @@ Future<void> _extractArchive(Completer<dynamic> stopped, String extension, File
|
||||
case ".rar":
|
||||
final winrar = File("${assetsDirectory.path}\\build\\winrar.exe");
|
||||
if(!winrar.existsSync()) {
|
||||
throw "Corrupted installation: missing winrar.exe";
|
||||
throw "Missing winrar.exe";
|
||||
}
|
||||
|
||||
process = await startProcess(
|
||||
@@ -192,11 +310,16 @@ Future<void> _extractArchive(Completer<dynamic> stopped, String extension, File
|
||||
);
|
||||
var completed = false;
|
||||
process.stdOutput.listen((data) {
|
||||
final now = DateTime.now().millisecondsSinceEpoch;
|
||||
data = data.replaceAll("\r", "").replaceAll("\b", "").trim();
|
||||
if(data == "All OK") {
|
||||
completed = true;
|
||||
_onProgress(startTime, now, 100, true, options);
|
||||
_onProgress(
|
||||
options.port,
|
||||
100,
|
||||
0,
|
||||
-1,
|
||||
true
|
||||
);
|
||||
process?.kill(ProcessSignal.sigabrt);
|
||||
return;
|
||||
}
|
||||
@@ -207,7 +330,13 @@ Future<void> _extractArchive(Completer<dynamic> stopped, String extension, File
|
||||
}
|
||||
|
||||
final percentage = int.parse(element).toDouble();
|
||||
_onProgress(startTime, now, percentage, true, options);
|
||||
_onProgress(
|
||||
options.port,
|
||||
percentage,
|
||||
0,
|
||||
-1,
|
||||
true
|
||||
);
|
||||
});
|
||||
process.stdError.listen((data) {
|
||||
if(!data.isBlank) {
|
||||
@@ -225,23 +354,25 @@ Future<void> _extractArchive(Completer<dynamic> stopped, String extension, File
|
||||
}
|
||||
|
||||
await Future.any([stopped.future, process.exitCode]);
|
||||
process.kill(ProcessSignal.sigabrt);
|
||||
}
|
||||
|
||||
void _onProgress(int startTime, int? now, double percentage, bool extracting, FortniteBuildDownloadOptions options) {
|
||||
void _onProgress(SendPort port, double percentage, int speed, int minutesLeft, bool extracting) {
|
||||
if(percentage == 0) {
|
||||
options.port.send(FortniteBuildDownloadProgress(
|
||||
port.send(FortniteBuildDownloadProgress(
|
||||
progress: percentage,
|
||||
extracting: extracting
|
||||
extracting: extracting,
|
||||
timeLeft: null,
|
||||
speed: speed
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
final msLeft = now == null ? null : startTime + (now - startTime) * 100 / percentage - now;
|
||||
final minutesLeft = msLeft == null ? null : (msLeft / 1000 / 60).round();
|
||||
options.port.send(FortniteBuildDownloadProgress(
|
||||
port.send(FortniteBuildDownloadProgress(
|
||||
progress: percentage,
|
||||
extracting: extracting,
|
||||
minutesLeft: minutesLeft
|
||||
timeLeft: minutesLeft,
|
||||
speed: speed
|
||||
));
|
||||
}
|
||||
|
||||
@@ -261,4 +392,5 @@ Completer<dynamic> _setupLifecycle(FortniteBuildDownloadOptions options) {
|
||||
});
|
||||
options.port.send(lifecyclePort.sendPort);
|
||||
return stopped;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,19 +6,21 @@ import 'package:path/path.dart' as path;
|
||||
import 'package:reboot_common/common.dart';
|
||||
|
||||
bool _watcher = false;
|
||||
final File rebootDllFile = File("${dllsDirectory.path}\\reboot.dll");
|
||||
const String kRebootDownloadUrl =
|
||||
"http://nightly.link/Milxnor/Project-Reboot-3.0/workflows/msbuild/master/Release.zip";
|
||||
final File rebootBeforeS20DllFile = File("${dllsDirectory.path}\\reboot.dll");
|
||||
final File rebootAboveS20DllFile = File("${dllsDirectory.path}\\rebootS20.dll");
|
||||
const String kRebootBelowS20DownloadUrl =
|
||||
"https://nightly.link/Milxnor/Project-Reboot-3.0/workflows/msbuild/master/Reboot.zip";
|
||||
const String kRebootAboveS20DownloadUrl =
|
||||
"https://nightly.link/Milxnor/Project-Reboot-3.0/workflows/msbuild/master/RebootS20.zip";
|
||||
|
||||
Future<bool> hasRebootDllUpdate(int? lastUpdateMs, {int hours = 24, bool force = false}) async {
|
||||
final lastUpdate = await _getLastUpdate(lastUpdateMs);
|
||||
final exists = await rebootDllFile.exists();
|
||||
final exists = await rebootBeforeS20DllFile.exists() && await rebootAboveS20DllFile.exists();
|
||||
final now = DateTime.now();
|
||||
return force || !exists || (hours > 0 && lastUpdate != null && now.difference(lastUpdate).inHours > hours);
|
||||
}
|
||||
|
||||
Future<void> downloadCriticalDll(String name, String outputPath) async {
|
||||
print("https://github.com/Auties00/reboot_launcher/raw/master/gui/dependencies/dlls/$name");
|
||||
final response = await http.get(Uri.parse("https://github.com/Auties00/reboot_launcher/raw/master/gui/dependencies/dlls/$name"));
|
||||
if(response.statusCode != 200) {
|
||||
throw Exception("Cannot download $name: status code ${response.statusCode}");
|
||||
@@ -29,9 +31,8 @@ Future<void> downloadCriticalDll(String name, String outputPath) async {
|
||||
await output.writeAsBytes(response.bodyBytes, flush: true);
|
||||
}
|
||||
|
||||
Future<int> downloadRebootDll(String url) async {
|
||||
Future<void> downloadRebootDll(File file, String url) async {
|
||||
Directory? outputDir;
|
||||
final now = DateTime.now();
|
||||
try {
|
||||
final response = await http.get(Uri.parse(url));
|
||||
if(response.statusCode != 200) {
|
||||
@@ -43,8 +44,7 @@ Future<int> downloadRebootDll(String url) async {
|
||||
await tempZip.writeAsBytes(response.bodyBytes, flush: true);
|
||||
await extractFileToDisk(tempZip.path, outputDir.path);
|
||||
final rebootDll = File(outputDir.listSync().firstWhere((element) => path.extension(element.path) == ".dll").path);
|
||||
await rebootDllFile.writeAsBytes(await rebootDll.readAsBytes(), flush: true);
|
||||
return now.millisecondsSinceEpoch;
|
||||
await file.writeAsBytes(await rebootDll.readAsBytes(), flush: true);
|
||||
} finally{
|
||||
if(outputDir != null) {
|
||||
delete(outputDir);
|
||||
@@ -64,7 +64,7 @@ Stream<String> watchDlls() async* {
|
||||
}
|
||||
|
||||
_watcher = true;
|
||||
await for(final event in rebootDllFile.parent.watch(events: FileSystemEvent.delete | FileSystemEvent.move)) {
|
||||
await for(final event in dllsDirectory.watch(events: FileSystemEvent.delete | FileSystemEvent.move)) {
|
||||
if (event.path.endsWith(".dll")) {
|
||||
yield event.path;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user