19 Commits

Author SHA1 Message Date
Alessandro Autiero
e1df46efd9 10.0 2024-12-09 12:42:49 +01:00
Alessandro Autiero
eb7745cc4d 10.0 2024-12-09 12:14:41 +01:00
Alessandro Autiero
6f91ad0404 Switched to starfall.dll 2024-12-08 20:41:31 +01:00
Alessandro Autiero
dfebe74518 Switched to sinum 2024-10-21 20:32:23 +02:00
Alessandro Autiero
bfe15e43d9 Released 9.2.7 2024-09-14 12:37:56 +02:00
Alessandro Autiero
a9af28273a Released 9.2.6 2024-09-12 15:46:24 +02:00
Alessandro Autiero
4c3fe9bc65 Released 9.2.5 2024-08-18 20:29:09 +02:00
Alessandro Autiero
582270849e Released 9.2.4 2024-07-10 15:40:52 +02:00
Alessandro Autiero
1ef4e76768 Small fix to display errors and warnings from backend 2024-07-10 15:19:20 +02:00
Alessandro Autiero
cd8c8e6dd9 Release 9.2.3 2024-07-10 15:11:49 +02:00
Alessandro Autiero
a2505011d9 Release 9.2.2 2024-07-09 20:38:01 +02:00
Alessandro Autiero
3e2c2e96b1 Release 9.2.1 2024-07-07 10:17:07 +02:00
Alessandro Autiero
e3b8d7d182 Release 9.2.0 2024-07-06 18:43:52 +02:00
Alessandro Autiero
45b8629207 Port Forwarding documentation 2024-07-06 18:34:42 +02:00
Alessandro Autiero
2df1b81485 Port Forwarding documentation 2024-07-06 18:33:22 +02:00
Alessandro Autiero
0d64251623 Reversed logging 2024-06-15 18:48:42 +02:00
Alessandro Autiero
47adb572ea Added back logging 2024-06-15 18:40:46 +02:00
Alessandro Autiero
e3a42d6b81 Fixed small bug 2024-06-15 18:23:43 +02:00
Alessandro Autiero
e24f4e97b3 9.1.4 2024-06-15 17:57:17 +02:00
215 changed files with 7221 additions and 4361 deletions

4
backend/README.md Normal file
View File

@@ -0,0 +1,4 @@
# Backend
Fork of LawinV1
Awaiting rewrite in Dart
Use build.bat to generate the executable

View File

@@ -4,6 +4,16 @@ const fs = require("fs");
const path = require("path"); const path = require("path");
const cookieParser = require("cookie-parser"); 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.json());
express.use(Express.urlencoded({ extended: true })); express.use(Express.urlencoded({ extended: true }));
express.use(Express.static('public')); 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/cloudstorage.js"));
express.use(require("./structure/mcp.js")); express.use(require("./structure/mcp.js"));
const port = process.env.PORT || 3551; const port = 3551;
express.listen(port, () => { express.listen(port, () => {
console.log("LawinServer started listening on port", port); console.log("LawinServer started listening on port", port);

View File

View File

@@ -11,6 +11,7 @@
"dependencies": { "dependencies": {
"cookie-parser": "^1.4.6", "cookie-parser": "^1.4.6",
"express": "^4.18.2", "express": "^4.18.2",
"express-requests-logger": "^4.0.0",
"ini": "^2.0.0", "ini": "^2.0.0",
"nexe": "^4.0.0-rc.6", "nexe": "^4.0.0-rc.6",
"path": "^0.12.7", "path": "^0.12.7",
@@ -388,6 +389,23 @@
"node": ">v0.4.12" "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": { "node_modules/bytes": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@@ -1063,6 +1081,19 @@
"node": ">=4" "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": { "node_modules/duplexer3": {
"version": "0.1.5", "version": "0.1.5",
"resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz",
@@ -1168,6 +1199,16 @@
"node": ">= 0.10.0" "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": { "node_modules/express/node_modules/cookie": {
"version": "0.5.0", "version": "0.5.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
@@ -1292,6 +1333,14 @@
"node": ">= 0.8" "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": { "node_modules/fn.name": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", "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", "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz",
"integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" "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": { "node_modules/log-symbols": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz",
@@ -1973,6 +2027,15 @@
"node": ">=10" "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": { "node_modules/moo-server": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/moo-server/-/moo-server-1.3.0.tgz", "resolved": "https://registry.npmjs.org/moo-server/-/moo-server-1.3.0.tgz",
@@ -2009,6 +2072,77 @@
"readable-stream": "^3.6.0" "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": { "node_modules/negotiator": {
"version": "0.6.3", "version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "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": { "node_modules/safe-stable-stringify": {
"version": "2.4.3", "version": "2.4.3",
"resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz",
@@ -3405,6 +3545,17 @@
"wrench": "1.3.x" "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": { "bytes": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "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": { "duplexer3": {
"version": "0.1.5", "version": "0.1.5",
"resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz", "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": { "ext-list": {
"version": "2.2.2", "version": "2.2.2",
"resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz",
@@ -4130,6 +4300,11 @@
"unpipe": "~1.0.0" "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": { "fn.name": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", "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", "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz",
"integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" "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": { "log-symbols": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", "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", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" "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": { "moo-server": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/moo-server/-/moo-server-1.3.0.tgz", "resolved": "https://registry.npmjs.org/moo-server/-/moo-server-1.3.0.tgz",
@@ -4645,6 +4831,62 @@
"readable-stream": "^3.6.0" "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": { "negotiator": {
"version": "0.6.3", "version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "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", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" "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": { "safe-stable-stringify": {
"version": "2.4.3", "version": "2.4.3",
"resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz",

View File

@@ -12,7 +12,8 @@
"uuid": "^8.3.2", "uuid": "^8.3.2",
"ws": "^8.5.0", "ws": "^8.5.0",
"xml-parser": "^1.2.1", "xml-parser": "^1.2.1",
"xmlbuilder": "^15.1.1" "xmlbuilder": "^15.1.1",
"express-requests-logger": "^4.0.0"
}, },
"scripts": { "scripts": {
"start": "node index.js", "start": "node index.js",

File diff suppressed because it is too large Load Diff

View File

View File

View File

View File

View File

View File

View File

View File

@@ -75,5 +75,12 @@
"direction": "OUTBOUND", "direction": "OUTBOUND",
"created": "2024-05-31T19:50:04.738Z", "created": "2024-05-31T19:50:04.738Z",
"favorite": false "favorite": false
},
{
"accountId": "Player724",
"status": "ACCEPTED",
"direction": "OUTBOUND",
"created": "2024-06-24T20:15:48.062Z",
"favorite": false
} }
] ]

View File

@@ -98,6 +98,15 @@
"note": "", "note": "",
"favorite": false, "favorite": false,
"created": "2024-05-31T19:50:04.738Z" "created": "2024-05-31T19:50:04.738Z"
},
{
"accountId": "Player724",
"groups": [],
"mutual": 0,
"alias": "",
"note": "",
"favorite": false,
"created": "2024-06-24T20:15:48.062Z"
} }
], ],
"incoming": [], "incoming": [],

View File

@@ -51,7 +51,7 @@ void main(List<String> args) async {
} }
stdout.writeln("Launching game..."); stdout.writeln("Launching game...");
var executable = version.gameExecutable; var executable = version.shippingExecutable;
if(executable == null){ if(executable == null){
throw Exception("Missing game executable at: ${version.location.path}"); throw Exception("Missing game executable at: ${version.location.path}");
} }

View File

@@ -12,7 +12,7 @@ Future<void> startGame() async {
await _startLauncherProcess(version); await _startLauncherProcess(version);
await _startEacProcess(version); await _startEacProcess(version);
var executable = await version.gameExecutable; var executable = await version.shippingExecutable;
if (executable == null) { if (executable == null) {
throw Exception("${version.location.path} no longer contains a Fortnite executable, did you delete or move it?"); throw Exception("${version.location.path} no longer contains a Fortnite executable, did you delete or move it?");
} }

View File

@@ -7,7 +7,7 @@ Future<bool> startServerCli(String? host, int? port, ServerType type) async {
stdout.writeln("Starting backend server..."); stdout.writeln("Starting backend server...");
switch(type){ switch(type){
case ServerType.local: case ServerType.local:
var result = await pingBackend(host ?? kDefaultBackendHost, port ?? kDefaultBackendPort); final result = await pingBackend(host ?? kDefaultBackendHost, port ?? kDefaultBackendPort);
if(result == null){ if(result == null){
throw Exception("Local backend server is not running"); throw Exception("Local backend server is not running");
} }

View File

@@ -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/server_type.dart';
export 'package:reboot_common/src/model/update_status.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/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/model/dll.dart';
export 'package:reboot_common/src/util/backend.dart'; export 'package:reboot_common/src/util/backend.dart';
export 'package:reboot_common/src/util/build.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/network.dart';
export 'package:reboot_common/src/util/patcher.dart'; export 'package:reboot_common/src/util/patcher.dart';
export 'package:reboot_common/src/util/path.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';

View File

@@ -1,2 +1,3 @@
const String kDefaultBackendHost = "127.0.0.1"; const String kDefaultBackendHost = "127.0.0.1";
const int kDefaultBackendPort = 3551; const int kDefaultBackendPort = 3551;
const int kDefaultXmppPort = 80;

View File

@@ -1,4 +1,5 @@
const String kDefaultPlayerName = "Player"; const String kDefaultPlayerName = "Player";
const String kDefaultHostName = "Host";
const String kDefaultGameServerHost = "127.0.0.1"; const String kDefaultGameServerHost = "127.0.0.1";
const String kDefaultGameServerPort = "7777"; const String kDefaultGameServerPort = "7777";
const String kInitializedLine = "Game Engine Initialized"; const String kInitializedLine = "Game Engine Initialized";
@@ -11,7 +12,7 @@ const List<String> kCorruptedBuildErrors = [
"Critical error", "Critical error",
"when 0 bytes remain", "when 0 bytes remain",
"Pak chunk signature verification failed!", "Pak chunk signature verification failed!",
"Couldn't find pak signature file" "LogWindows:Error: Fatal error!"
]; ];
const List<String> kCannotConnectErrors = [ const List<String> kCannotConnectErrors = [
"port 3551 failed: Connection refused", "port 3551 failed: Connection refused",

View File

@@ -5,38 +5,50 @@ import 'package:path/path.dart' as path;
import 'package:reboot_common/common.dart'; import 'package:reboot_common/common.dart';
extension FortniteVersionExtension on FortniteVersion { extension FortniteVersionExtension on FortniteVersion {
static File? findExecutable(Directory directory, String name) { static String _marker = "FortniteClient.mod";
static File? findFile(Directory directory, String name) {
try{ try{
final result = directory.listSync(recursive: true) for(final child in directory.listSync()) {
.firstWhere((element) => path.basename(element.path) == name); if(child is Directory) {
return File(result.path); 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(_){ }catch(_){
return null; return null;
} }
} }
File? get gameExecutable => findExecutable(location, "FortniteClient-Win64-Shipping.exe"); Future<File?> get shippingExecutable async {
final result = findFile(location, "FortniteClient-Win64-Shipping.exe");
Future<File?> get headlessGameExecutable async { if(result == null) {
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) {
return null; return null;
} }
final output = File("${original.parent.path}\\FortniteClient-Win64-Shipping-Headless.exe"); final marker = findFile(location, _marker);
await original.copy(output.path); if(marker != null) {
await Isolate.run(() => patchHeadless(output)); return result;
return output; }
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");
} }

View File

@@ -1,6 +1,9 @@
enum InjectableDll { enum InjectableDll {
console, console,
cobalt, starfall,
reboot, reboot,
memory }
extension InjectableDllVersionAware on InjectableDll {
bool get isVersionDependent => this == InjectableDll.reboot;
} }

View File

@@ -1,27 +1,31 @@
import 'dart:io'; import 'dart:io';
import 'dart:isolate'; import 'dart:isolate';
import 'package:version/version.dart';
class FortniteBuild { class FortniteBuild {
final String identifier; final Version version;
final String version;
final String link; final String link;
final bool available;
FortniteBuild({ FortniteBuild({
required this.identifier,
required this.version, required this.version,
required this.link required this.link,
required this.available
}); });
} }
class FortniteBuildDownloadProgress { class FortniteBuildDownloadProgress {
final double progress; final double progress;
final int? minutesLeft; final int? timeLeft;
final bool extracting; final bool extracting;
final int speed;
FortniteBuildDownloadProgress({ FortniteBuildDownloadProgress({
required this.progress, required this.progress,
required this.extracting, required this.extracting,
this.minutesLeft, required this.timeLeft,
required this.speed
}); });
} }

View 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
};
}

View File

@@ -1,17 +1,22 @@
import 'dart:io'; import 'dart:io';
import 'package:version/version.dart';
class FortniteVersion { class FortniteVersion {
String name; Version content;
Directory location; Directory location;
FortniteVersion.fromJson(json) FortniteVersion.fromJson(json)
: name = json["name"], : content = Version.parse(json["content"]),
location = Directory(json["location"]); location = Directory(json["location"]);
FortniteVersion({required this.name, required this.location}); FortniteVersion({required this.content, required this.location});
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
'name': name, 'content': content.toString(),
'location': location.path 'location': location.path
}; };
}
@override
bool operator ==(Object other) => other is FortniteVersion && this.content == other.content;
}

View File

@@ -1,30 +1,42 @@
import 'dart:io'; import 'dart:io';
import 'package:reboot_common/common.dart'; import 'package:reboot_common/common.dart';
import 'package:version/version.dart';
class GameInstance { class GameInstance {
final String versionName; final Version version;
final int gamePid; final int gamePid;
final int? launcherPid; final int? launcherPid;
final int? eacPid; final int? eacPid;
final List<InjectableDll> injectedDlls; final List<InjectableDll> injectedDlls;
bool hosting; final GameServerType? serverType;
bool launched; bool launched;
bool movedToVirtualDesktop; bool movedToVirtualDesktop;
bool tokenError; bool tokenError;
bool killed;
GameInstance? child; GameInstance? child;
GameInstance({ GameInstance({
required this.versionName, required this.version,
required this.gamePid, required this.gamePid,
required this.launcherPid, required this.launcherPid,
required this.eacPid, required this.eacPid,
required this.hosting, required this.serverType,
required this.child required this.child
}): tokenError = false, launched = false, movedToVirtualDesktop = false, injectedDlls = []; }): tokenError = false, killed = false, launched = false, movedToVirtualDesktop = false, injectedDlls = [];
void kill() { void kill() {
GameInstance? child = this;
while(child != null) {
child._kill();
child = child.child;
}
}
void _kill() {
launched = true;
killed = true;
Process.killPid(gamePid, ProcessSignal.sigabrt); Process.killPid(gamePid, ProcessSignal.sigabrt);
if(launcherPid != null) { if(launcherPid != null) {
Process.killPid(launcherPid!, ProcessSignal.sigabrt); Process.killPid(launcherPid!, ProcessSignal.sigabrt);
@@ -33,17 +45,10 @@ class GameInstance {
Process.killPid(eacPid!, ProcessSignal.sigabrt); Process.killPid(eacPid!, ProcessSignal.sigabrt);
} }
} }
}
bool get nestedHosting { enum GameServerType {
GameInstance? child = this; headless,
while(child != null) { virtualWindow,
if(child.hosting) { window
return true;
}
child = child.child;
}
return false;
}
} }

View File

@@ -4,6 +4,11 @@ class ServerResult {
final StackTrace? stackTrace; final StackTrace? stackTrace;
ServerResult(this.type, {this.error, this.stackTrace}); ServerResult(this.type, {this.error, this.stackTrace});
@override
String toString() {
return 'ServerResult{type: $type, error: $error, stackTrace: $stackTrace}';
}
} }
enum ServerResultType { enum ServerResultType {

View File

@@ -15,10 +15,19 @@ final Semaphore _semaphore = Semaphore();
String? _lastIp; String? _lastIp;
String? _lastPort; 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, executable: backendStartExecutable,
window: detached, 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); 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 { Future<bool> freeBackendPort() async {
await killProcessByPort(kDefaultBackendPort); await killProcessByPort(kDefaultBackendPort);
await killProcessByPort(kDefaultXmppPort);
final standardResult = await isBackendPortFree(); final standardResult = await isBackendPortFree();
if(standardResult) { if(standardResult) {
return true; return true;
@@ -35,21 +45,24 @@ Future<bool> freeBackendPort() async {
} }
Future<Uri?> pingBackend(String host, int port, [bool https=false]) async { Future<Uri?> pingBackend(String host, int port, [bool https=false]) async {
var hostName = host.replaceFirst("http://", "").replaceFirst("https://", ""); final hostName = host.replaceFirst("http://", "").replaceFirst("https://", "");
var declaredScheme = host.startsWith("http://") ? "http" : host.startsWith("https://") ? "https" : null; final declaredScheme = host.startsWith("http://") ? "http" : host.startsWith("https://") ? "https" : null;
try{ try{
var uri = Uri( final uri = Uri(
scheme: declaredScheme ?? (https ? "https" : "http"), scheme: declaredScheme ?? (https ? "https" : "http"),
host: hostName, host: hostName,
port: port, port: port,
path: "unknown" path: "unknown"
); );
var client = HttpClient() log("[BACKEND] Pinging $uri...");
..connectionTimeout = const Duration(seconds: 5); final client = HttpClient()
var request = await client.getUrl(uri); ..connectionTimeout = const Duration(seconds: 10);
var response = await request.close(); final request = await client.getUrl(uri);
return response.statusCode == 200 || response.statusCode == 404 ? uri : null; await request.close().timeout(const Duration(seconds: 10));
}catch(_){ 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); return https || declaredScheme != null || isLocalHost(host) ? null : await pingBackend(host, port, true);
} }
} }
@@ -59,16 +72,16 @@ Stream<String?> watchMatchmakingIp() async* {
return; 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 { yield* observer.where((event) => event.path == matchmakerConfigFile.path).asyncMap((event) async {
try { try {
var config = Config.fromString(await matchmakerConfigFile.readAsString()); final config = Config.fromString(await matchmakerConfigFile.readAsString());
var ip = config.get("GameServer", "ip"); final ip = config.get("GameServer", "ip");
if(ip == null) { if(ip == null) {
return null; return null;
} }
var port = config.get("GameServer", "port"); final port = config.get("GameServer", "port");
if(port == null) { if(port == null) {
return null; return null;
} }
@@ -89,14 +102,14 @@ Stream<String?> watchMatchmakingIp() async* {
} }
Future<void> writeMatchmakingIp(String text) async { Future<void> writeMatchmakingIp(String text) async {
var exists = await matchmakerConfigFile.exists(); final exists = await matchmakerConfigFile.exists();
if(!exists) { if(!exists) {
return; return;
} }
_semaphore.acquire(); _semaphore.acquire();
var splitIndex = text.indexOf(":"); final splitIndex = text.indexOf(":");
var ip = splitIndex != -1 ? text.substring(0, splitIndex) : text; final ip = splitIndex != -1 ? text.substring(0, splitIndex) : text;
var port = splitIndex != -1 ? text.substring(splitIndex + 1) : kDefaultGameServerPort; var port = splitIndex != -1 ? text.substring(splitIndex + 1) : kDefaultGameServerPort;
if(port.isBlank) { if(port.isBlank) {
port = kDefaultGameServerPort; port = kDefaultGameServerPort;
@@ -104,7 +117,7 @@ Future<void> writeMatchmakingIp(String text) async {
_lastIp = ip; _lastIp = ip;
_lastPort = port; _lastPort = port;
var config = Config.fromString(await matchmakerConfigFile.readAsString()); final config = Config.fromString(await matchmakerConfigFile.readAsString());
config.set("GameServer", "ip", ip); config.set("GameServer", "ip", ip);
config.set("GameServer", "port", port); config.set("GameServer", "port", port);
await matchmakerConfigFile.writeAsString(config.toString(), flush: true); await matchmakerConfigFile.writeAsString(config.toString(), flush: true);

View File

@@ -3,136 +3,243 @@ import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:isolate'; import 'dart:isolate';
import 'package:dio/dio.dart';
import 'package:dio/io.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import 'package:reboot_common/common.dart'; import 'package:reboot_common/common.dart';
import 'package:reboot_common/src/extension/types.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"; const String kStopBuildDownloadSignal = "kill";
final Dio _dio = _buildDioInstance(); final Uri _archiveSourceUrl = Uri.parse("https://builds.rebootfn.org/versions.json");
Dio _buildDioInstance() { final int _ariaPort = 6800;
final dio = Dio(); final Uri _ariaEndpoint = Uri.parse('http://localhost:$_ariaPort/jsonrpc');
final httpClientAdapter = dio.httpClientAdapter as IOHttpClientAdapter; final Duration _ariaMaxSpawnTime = const Duration(seconds: 10);
httpClientAdapter.createHttpClient = () { final String _ariaSecret = "RebootLauncher";
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 RegExp _rarProgressRegex = RegExp("^((100)|(\\d{1,2}(.\\d*)?))%\$"); 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 { Future<List<FortniteBuild>> fetchBuilds(ignored) async {
final response = await _dio.get<String>( final response = await http.get(_archiveSourceUrl);
_archiveSourceUrl,
options: Options(
responseType: ResponseType.plain
)
);
if (response.statusCode != 200) { if (response.statusCode != 200) {
return []; return [];
} }
final data = jsonDecode(response.data ?? "{}"); return jsonDecode(response.body)
var results = <FortniteBuild>[]; .map((entry) {
for(final entry in data.entries) { try {
results.add(FortniteBuild( final fileUrl = entry as String;
identifier: entry.key, final fileName = Uri.parse(fileUrl).pathSegments.last;
version: "${entry.value["title"]} (${entry.key})", final fileNameWithoutExtension = path.basenameWithoutExtension(fileName);
link: entry.value["url"] return FortniteBuild(
)); version: Version.parse(fileNameWithoutExtension),
} link: entry,
return results; available: true
);
}catch(_) {
return null;
}
})
.whereType<FortniteBuild>()
.toList();
} }
Future<void> downloadArchiveBuild(FortniteBuildDownloadOptions options) async { 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 { try {
final stopped = _setupLifecycle(options); final stopped = _setupLifecycle(options);
final outputDir = Directory("${options.destination.path}\\.build"); await outputFile.parent.create(recursive: true);
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);
}
final startTime = DateTime.now().millisecondsSinceEpoch; final downloadItemCompleter = Completer<File>();
final response = _downloadArchive(options, tempFile, startTime);
await Future.any([stopped.future, response]); 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) { 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) { }catch(error) {
_onError(error, options); _onError(error, options);
}finally {
delete(outputFile);
} }
} }
Future<void> _downloadArchive(FortniteBuildDownloadOptions options, File tempFile, int startTime, [int? byteStart = null, int errorsCount = 0]) async { Future<void> _startAriaServer() async {
var received = byteStart ?? 0; final running = await _isAriaRunning();
try { if(running) {
await _dio.download( await killProcessByPort(_ariaPort);
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;
}
if(statusCode == 403 || statusCode == 503) { final aria2c = File("${assetsDirectory.path}\\build\\aria2c.exe");
throw _deniedConnectionError; if(!aria2c.existsSync()) {
} throw "Missing aria2c.exe";
}
if(statusCode == 404) { await startProcess(
throw _unavailableError; executable: aria2c,
} args: [
"--max-connection-per-server=${Platform.numberOfProcessors}",
throw _genericError; "--split=${Platform.numberOfProcessors}",
}, "--enable-rpc",
headers: byteStart == null || byteStart <= 0 ? { "--rpc-listen-all=true",
"Cookie": "_c_t_c=1" "--rpc-allow-origin-all",
} : { "--rpc-secret=$_ariaSecret",
"Cookie": "_c_t_c=1", "--rpc-listen-port=$_ariaPort"
"Range": "bytes=${byteStart}-" ],
}, window: false
) );
); for(var i = 0; i < _ariaMaxSpawnTime.inSeconds; i++) {
}catch(error) { if(await _isAriaRunning()) {
if(errorsCount > _maxErrors || error.toString().contains(_deniedConnectionError) || error.toString().contains(_unavailableError)) {
_onError(error, options);
return; 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 { Future<void> _extractArchive(Completer<dynamic> stopped, String extension, File tempFile, FortniteBuildDownloadOptions options) async {
final startTime = DateTime.now().millisecondsSinceEpoch;
Process? process; Process? process;
switch (extension.toLowerCase()) { switch (extension.toLowerCase()) {
case ".zip": case ".zip":
final sevenZip = File("${assetsDirectory.path}\\build\\7zip.exe"); final sevenZip = File("${assetsDirectory.path}\\build\\7zip.exe");
if(!sevenZip.existsSync()) { if(!sevenZip.existsSync()) {
throw "Corrupted installation: missing 7zip.exe"; throw "Missing 7zip.exe";
} }
process = await startProcess( process = await startProcess(
@@ -147,10 +254,15 @@ Future<void> _extractArchive(Completer<dynamic> stopped, String extension, File
); );
var completed = false; var completed = false;
process.stdOutput.listen((data) { process.stdOutput.listen((data) {
final now = DateTime.now().millisecondsSinceEpoch;
if(data.toLowerCase().contains("everything is ok")) { if(data.toLowerCase().contains("everything is ok")) {
completed = true; completed = true;
_onProgress(startTime, now, 100, true, options); _onProgress(
options.port,
100,
0,
-1,
true
);
process?.kill(ProcessSignal.sigabrt); process?.kill(ProcessSignal.sigabrt);
return; 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(); 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) { process.stdError.listen((data) {
if(!data.isBlank) { if(!data.isBlank) {
@@ -177,7 +295,7 @@ Future<void> _extractArchive(Completer<dynamic> stopped, String extension, File
case ".rar": case ".rar":
final winrar = File("${assetsDirectory.path}\\build\\winrar.exe"); final winrar = File("${assetsDirectory.path}\\build\\winrar.exe");
if(!winrar.existsSync()) { if(!winrar.existsSync()) {
throw "Corrupted installation: missing winrar.exe"; throw "Missing winrar.exe";
} }
process = await startProcess( process = await startProcess(
@@ -192,11 +310,16 @@ Future<void> _extractArchive(Completer<dynamic> stopped, String extension, File
); );
var completed = false; var completed = false;
process.stdOutput.listen((data) { process.stdOutput.listen((data) {
final now = DateTime.now().millisecondsSinceEpoch;
data = data.replaceAll("\r", "").replaceAll("\b", "").trim(); data = data.replaceAll("\r", "").replaceAll("\b", "").trim();
if(data == "All OK") { if(data == "All OK") {
completed = true; completed = true;
_onProgress(startTime, now, 100, true, options); _onProgress(
options.port,
100,
0,
-1,
true
);
process?.kill(ProcessSignal.sigabrt); process?.kill(ProcessSignal.sigabrt);
return; return;
} }
@@ -207,7 +330,13 @@ Future<void> _extractArchive(Completer<dynamic> stopped, String extension, File
} }
final percentage = int.parse(element).toDouble(); final percentage = int.parse(element).toDouble();
_onProgress(startTime, now, percentage, true, options); _onProgress(
options.port,
percentage,
0,
-1,
true
);
}); });
process.stdError.listen((data) { process.stdError.listen((data) {
if(!data.isBlank) { if(!data.isBlank) {
@@ -225,23 +354,25 @@ Future<void> _extractArchive(Completer<dynamic> stopped, String extension, File
} }
await Future.any([stopped.future, process.exitCode]); 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) { if(percentage == 0) {
options.port.send(FortniteBuildDownloadProgress( port.send(FortniteBuildDownloadProgress(
progress: percentage, progress: percentage,
extracting: extracting extracting: extracting,
timeLeft: null,
speed: speed
)); ));
return; return;
} }
final msLeft = now == null ? null : startTime + (now - startTime) * 100 / percentage - now; port.send(FortniteBuildDownloadProgress(
final minutesLeft = msLeft == null ? null : (msLeft / 1000 / 60).round();
options.port.send(FortniteBuildDownloadProgress(
progress: percentage, progress: percentage,
extracting: extracting, extracting: extracting,
minutesLeft: minutesLeft timeLeft: minutesLeft,
speed: speed
)); ));
} }
@@ -261,4 +392,5 @@ Completer<dynamic> _setupLifecycle(FortniteBuildDownloadOptions options) {
}); });
options.port.send(lifecyclePort.sendPort); options.port.send(lifecyclePort.sendPort);
return stopped; return stopped;
} }

View File

@@ -6,19 +6,21 @@ import 'package:path/path.dart' as path;
import 'package:reboot_common/common.dart'; import 'package:reboot_common/common.dart';
bool _watcher = false; bool _watcher = false;
final File rebootDllFile = File("${dllsDirectory.path}\\reboot.dll"); final File rebootBeforeS20DllFile = File("${dllsDirectory.path}\\reboot.dll");
const String kRebootDownloadUrl = final File rebootAboveS20DllFile = File("${dllsDirectory.path}\\rebootS20.dll");
"http://nightly.link/Milxnor/Project-Reboot-3.0/workflows/msbuild/master/Release.zip"; 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 { Future<bool> hasRebootDllUpdate(int? lastUpdateMs, {int hours = 24, bool force = false}) async {
final lastUpdate = await _getLastUpdate(lastUpdateMs); final lastUpdate = await _getLastUpdate(lastUpdateMs);
final exists = await rebootDllFile.exists(); final exists = await rebootBeforeS20DllFile.exists() && await rebootAboveS20DllFile.exists();
final now = DateTime.now(); final now = DateTime.now();
return force || !exists || (hours > 0 && lastUpdate != null && now.difference(lastUpdate).inHours > hours); return force || !exists || (hours > 0 && lastUpdate != null && now.difference(lastUpdate).inHours > hours);
} }
Future<void> downloadCriticalDll(String name, String outputPath) async { 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")); final response = await http.get(Uri.parse("https://github.com/Auties00/reboot_launcher/raw/master/gui/dependencies/dlls/$name"));
if(response.statusCode != 200) { if(response.statusCode != 200) {
throw Exception("Cannot download $name: status code ${response.statusCode}"); 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); await output.writeAsBytes(response.bodyBytes, flush: true);
} }
Future<int> downloadRebootDll(String url) async { Future<void> downloadRebootDll(File file, String url) async {
Directory? outputDir; Directory? outputDir;
final now = DateTime.now();
try { try {
final response = await http.get(Uri.parse(url)); final response = await http.get(Uri.parse(url));
if(response.statusCode != 200) { if(response.statusCode != 200) {
@@ -43,8 +44,7 @@ Future<int> downloadRebootDll(String url) async {
await tempZip.writeAsBytes(response.bodyBytes, flush: true); await tempZip.writeAsBytes(response.bodyBytes, flush: true);
await extractFileToDisk(tempZip.path, outputDir.path); await extractFileToDisk(tempZip.path, outputDir.path);
final rebootDll = File(outputDir.listSync().firstWhere((element) => path.extension(element.path) == ".dll").path); final rebootDll = File(outputDir.listSync().firstWhere((element) => path.extension(element.path) == ".dll").path);
await rebootDllFile.writeAsBytes(await rebootDll.readAsBytes(), flush: true); await file.writeAsBytes(await rebootDll.readAsBytes(), flush: true);
return now.millisecondsSinceEpoch;
} finally{ } finally{
if(outputDir != null) { if(outputDir != null) {
delete(outputDir); delete(outputDir);
@@ -64,7 +64,7 @@ Stream<String> watchDlls() async* {
} }
_watcher = true; _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")) { if (event.path.endsWith(".dll")) {
yield event.path; yield event.path;
} }

Some files were not shown because too many files have changed in this diff Show More