52 Commits

Author SHA1 Message Date
FinnHornhoover
e11023df54 added linguist-vendored 2023-11-15 10:01:36 +03:00
FinnHornhoover
51f0c06092 fixed config json read, limited hash check 2023-11-13 23:06:38 +03:00
FinnHornhoover
695fcaebb6 fixed valdator function name 2023-11-13 22:43:58 +03:00
FinnHornhoover
01a4318db5 fixed hash check timing on config change 2023-11-13 20:05:02 +03:00
FinnHornhoover
4105472d3c added hash check when cache location is changed 2023-11-13 01:46:23 +03:00
FinnHornhoover
8f05ace344 added config modal, bumped version 2023-11-13 00:15:45 +03:00
FinnHornhoover
b51e49d703 added more proper initial check handling 2023-11-12 16:27:11 +03:00
FinnHornhoover
6f3f3290d7 added default ip, regex for version names 2023-11-12 15:38:33 +03:00
FinnHornhoover
f6ae8f0b6c added fixes for validation 2023-11-12 15:00:11 +03:00
FinnHornhoover
3dc1c1ae45 fixed version url and path bugs, added validation 2023-11-11 23:55:07 +03:00
FinnHornhoover
490d335336 WIP custom build adjustments 2023-11-11 23:55:06 +03:00
FinnHornhoover
cf0737cb60 added build management, WIP custom cache checks 2023-11-11 23:55:05 +03:00
FinnHornhoover
ea1715e27c by default, always use cdn 2023-11-11 23:55:04 +03:00
FinnHornhoover
59f0e155dd removed async, adjusted load logic, and hash check 2023-11-11 23:55:04 +03:00
FinnHornhoover
d391faefb7 edited gitignore for extra files 2023-11-11 23:55:04 +03:00
FinnHornhoover
9c3219f203 removed redundant function 2023-11-11 23:55:04 +03:00
FinnHornhoover
e980ca5d65 do not immediately start checking hashes 2023-11-11 23:55:04 +03:00
FinnHornhoover
9183d3338d reverted version sizes 2023-11-11 23:55:04 +03:00
FinnHornhoover
096fcee49f added script itself and migration logic 2023-11-11 23:55:04 +03:00
FinnHornhoover
e74106e137 added measure against half-flushed objects 2023-11-11 23:55:04 +03:00
FinnHornhoover
9ee982b53c switched to ipc through localhost 2023-11-11 23:55:04 +03:00
FinnHornhoover
b2ba0ba1b6 use python child process to handle cache ops 2023-11-11 23:55:03 +03:00
FinnHornhoover
60aba00ad5 WIP added offline cache pointing logic 2023-11-11 23:55:02 +03:00
FinnHornhoover
1c3e3f81b0 delegated downloads to wget exe due to memory leak 2023-11-11 23:55:02 +03:00
FinnHornhoover
f7d1b73806 implemented delete as ipc, adjusted for cache swap 2023-11-11 23:55:02 +03:00
FinnHornhoover
a1678cb1e9 adjusted download and hash check logic and data 2023-11-11 23:55:02 +03:00
FinnHornhoover
3f56895d3b integrated download and hash functionality 2023-11-11 23:55:02 +03:00
FinnHornhoover
2c5337938a added async hash checking 2023-11-11 23:55:02 +03:00
FinnHornhoover
b644edb774 WIP ipc downloads 2023-11-11 23:55:02 +03:00
FinnHornhoover
ee309319c9 WIP download bugfix 2023-11-11 23:55:02 +03:00
FinnHornhoover
01e329e621 WIP button functionality (download, delete) 2023-11-11 23:55:02 +03:00
CakeLancelot
ec0fc87dcd Bump version number to 1.5.1 2023-11-09 13:31:44 -06:00
CakeLancelot
cef8c1de93 Revert readJsonSync() change for modifying servers
readJsonSync() apparently produces an immutable JSON, which made it to where you couldn't add or remove servers
2023-10-18 12:47:41 -05:00
CakeLancelot
b73329c6de Update version numbers to 1.5 2023-10-07 18:20:10 -05:00
CakeLancelot
2e7dc51aa1 Fix issue where cache swapping wouldn't work if record wasn't already present
Not sure why I re-ordered this before
2023-09-27 05:01:22 -05:00
CakeLancelot
3fc6cabe33 Disable telemetry in player dll
Co-authored-by: gsemaj <gsemaj@proton.me>
2023-09-19 08:59:13 -05:00
CakeLancelot
71e4694ff6 Misc comment cleanup, correct holiday date range, simplify JSON loading 2023-09-19 08:57:50 -05:00
CakeLancelot
76f4a05287 Move server-selector over to path.join(), fix issues with cache swapping
Before, swapping would continually fail if a cache tried to be stored at a directory that already existed. The skipping mechanism likely also didn't work as intended. Both have been fixed now.
Additionally:
* Added .editorconfig file 
* Ran prettier format
* Misc. refactoring
2023-09-19 08:57:29 -05:00
CakeLancelot
97144aad59 Remove initial setup window
Now that we only copy a few json files for the initial setup, the process is so fast the progress window isn't really needed anymore
2023-09-17 13:02:19 -05:00
CakeLancelot
4a465ca689 README: include preleases for release badge 2023-09-17 09:42:41 -05:00
CakeLancelot
43f0c8c684 Revise loader DLL to work under Wine, loading screen beta text, cleanup 2023-09-16 20:57:13 -05:00
CakeLancelot
d4dc403e90 Remove now unnecessary attributes from game-client.js 2023-09-14 15:13:23 -05:00
CakeLancelot
ab5de9488d Fix DNS resolution
not sure why this works but resolve4 doesn't
2023-09-14 06:44:11 -05:00
CakeLancelot
dabb8bf484 Use extra-plugin-dirs in Electron to load plugin, add fusion manifest.
This has the advantage of being self contained (doesn't conflict with Unity 3.x - 5.x loader, so we can use the original one) and **disabling auto updates actually works**.

We can also eliminate any code used to install and verify unity, since it's all self contained.

Also added fusion manifest file to set process as DPI aware, and set ACP to UTF-8 on Windows 10 1903 and above.

Bumped version to 1.4.2
2023-09-14 05:29:34 -05:00
CakeLancelot
043bf219e7 Consistency pass and small tweaks
**Formatting going forward**
Variables: camelCase (capitalization of two letter abbreviations is OK, e.g. playerID)
Classes: PascalCase
CSS: kebab-case
Files: kebab-case

**Other miscellaneous changes**
* The WebPlayer crashing as well as failing to load the config file are now fatal  errors and will quit the app
* Moved some style attributes from index.html into openfusion.css
2023-09-03 05:12:30 -05:00
CakeLancelot
7131026b2f Misc tweaks
loadURL() -> loadUrl() in index.js
Move Bootstrap files
2023-09-02 23:03:00 -05:00
7dd018e336 Use local fonts for Bootstrap
bootstrap.min.css was fetching a css snippet for the Roboto font
remotely. Inlined the snippet and modified it to load the font files
locally.
2023-09-03 01:44:09 +02:00
CakeLancelot
e19098a13e README update 2023-07-11 19:40:51 -05:00
CakeLancelot
48e2052748 Fix call to nonexistent function in URL redirection
Gotta love runtime errors
2023-04-23 15:37:44 -05:00
CakeLancelot
7b3de1fe52 Bump fs-extra version to 2.0.0
This is the latest version that supports ES5
2023-02-17 20:07:52 -06:00
CakeLancelot
675a5fce50 Update repo URL in package.json 2023-02-11 16:08:25 +00:00
CakeLancelot
2bf916ea58 Fix bug in WebPlayer installation logic
Also run prettier
2023-02-09 12:52:57 -06:00
63 changed files with 38040 additions and 677 deletions

19
.editorconfig Normal file
View File

@@ -0,0 +1,19 @@
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
# 4 space indentation
[*.js, *.css, *.html, *.json]
indent_style = space
indent_size = 4
# Don't enforce anything in vendored code
[*.min.*]
end_of_line = unset
insert_final_newline = unset
indent_style = unset
indent_style = unset

3
.gitattributes vendored
View File

@@ -3,3 +3,6 @@
*.js text eol=lf
*.json text eol=lf
*.md text eol=lf
defaults/* linguist-vendored
lib/* linguist-vendored

6
.gitignore vendored
View File

@@ -8,4 +8,8 @@ version
resources/app/files/rankurl.txt
node_modules/
dist/
UnityBugReporter.exe
UnityBugReporter.exe
cache_handler/*/
extra/
*.sh
yarn.lock

View File

@@ -1,3 +1,52 @@
# OpenFusionClient
Electron app for joining OpenFusion servers
[![Current Release](https://img.shields.io/github/v/release/OpenFusionProject/OpenFusionClient?include_prereleases)](https://github.com/OpenFusionProject/OpenFusionClient/releases/latest) [![Discord](https://img.shields.io/badge/chat-on%20discord-7289da.svg?logo=discord)](https://discord.gg/DYavckB)[![License](https://img.shields.io/github/license/OpenFusionProject/OpenFusionClient)](https://github.com/OpenFusionProject/OpenFusionClient/blob/master/LICENSE.md)
An Electron app that allows you to easily join FusionFall servers.
It automatically installs FF's custom build of Unity Web Player, manages text files such as `assetInfo.php`/`loginInfo.php`, and embeds the game, all in a few clicks!
For an overview of how the game client worked originally, please see [this section in the OpenFusion README](https://github.com/OpenFusionProject/OpenFusion#architecture).
## Disclaimer
This repository does not contain any code from the actual FusionFall game client. **Think of it more as a launcher:** it abstracts away having to use a NPAPI plugin capable web browser, along with having to host a HTTP server for it to connect to.
In addition, if you are interested in contributing: do note that **this project likely cannot utilize more modern Javascript techniques**. In order to use NPAPI plugins, a very old version of Electron was needed (0.31.0). This limits the project to only a portion of ES5 in non-strict mode, and a reduced subset of Node/Electron APIs.
## Usage
Provided that you have npm installed, clone the repository, then run install like so:
```
git clone https://github.com/OpenFusionProject/OpenFusionClient.git
npm install
```
After that has completed you can then test OpenFusionClient:
```
npm run start
```
If you would like to package it as a standalone win32 application:
```
npm run pack
```
You can then compress the application directory into a zip file and installer for distribution:
```
npm run dist
```
Before opening a PR or running pack/dist, please do a code formatting pass:
```
npm run prettier
```
## License
MIT unless specified otherwise

File diff suppressed because one or more lines are too long

30
assets/css/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -47,6 +47,11 @@ img {
}
body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
clear: both;
user-select: none;
background-color: #000;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
@@ -75,6 +80,11 @@ body {
background-repeat: repeat;
}
.btn-warning {
background-image: url("../../assets/img/btn-warning-bg.png");
background-repeat: repeat;
}
#of-aboutmodal > .modal-dialog > .modal-content {
background-color: #093363;
border-color: #6699ff;
@@ -100,6 +110,36 @@ body {
border-color: #6699ff;
}
#of-editcacheconfigmodal > .modal-dialog > .modal-content {
background-color: #093363;
border-color: #6699ff;
}
#of-editconfigmodal > .modal-dialog > .modal-content {
background-color: #093363;
border-color: #6699ff;
}
#of-addversionmodal > .modal-dialog > .modal-content {
background-color: #093363;
border-color: #6699ff;
}
#of-editversionmodal > .modal-dialog > .modal-content {
background-color: #093363;
border-color: #6699ff;
}
#of-deleteversionmodal > .modal-dialog > .modal-content {
background-color: #093363;
border-color: #6699ff;
}
#of-restoreversionsmodal > .modal-dialog > .modal-content {
background-color: #093363;
border-color: #6699ff;
}
.form-control,
.form-control:focus {
border-color: #0099ff;
@@ -113,6 +153,14 @@ select {
color: #fff;
}
.modal {
overflow-y: auto;
}
.invalidinput {
border-color: #ff0000;
}
button:disabled {
cursor: not-allowed;
pointer-events: all !important;
@@ -129,3 +177,12 @@ button > i {
#server-table .server-listing-entry {
cursor: pointer;
}
#client {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
background: #000;
clear: both;
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 514 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

View File

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

View File

Before

Width:  |  Height:  |  Size: 176 B

After

Width:  |  Height:  |  Size: 176 B

View File

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -1,10 +1,8 @@
// You're kind of ruining the surprise by reading this, but whatever
var today = new Date();
// Check Christmas season: Date constructor in Javascript uses an index
// so 11 is Dec. of this year, and 12 is Jan. of the next
var christmasBegin = new Date(today.getFullYear(), 11, 23);
var christmasEnd = new Date(today.getFullYear(), 12, 8);
var christmasBegin = new Date(today.getFullYear(), 11, 21);
var christmasEnd = new Date(today.getFullYear(), 11, 31);
var sf;
if (today >= christmasBegin && today <= christmasEnd) {

View File

@@ -5,7 +5,7 @@ var gameRunning = false;
// Uncomment and enter credentials to skip login screen
function authDoCallback(param) {
/*var unity = document.getElementById('Unity_embed');
/*var unity = document.getElementById('unityEmbed');
unity.SendMessage("GlobalManager", "SetTEGid", "player");
unity.SendMessage("GlobalManager", "SetAuthid", "0");
unity.SendMessage("GlobalManager", "DoAuth", 0);*/
@@ -33,7 +33,7 @@ function PayPage(param) {
function onResize() {
if (gameRunning == true) {
var unity = document.getElementById("Unity_embed");
var unity = document.getElementById("unityEmbed");
unity.style.width = window.innerWidth + "px";
unity.style.height = window.innerHeight + "px";
}
@@ -56,38 +56,29 @@ function launchGame() {
"codebase",
"undefined/UnityWebPlayer.cab#version=2,0,0,0"
);
object.setAttribute("id", "Unity_object");
object.setAttribute("id", "unityObject");
object.setAttribute("width", "1264");
object.setAttribute("height", "661");
var embed = document.createElement("embed");
embed.setAttribute("type", "application/vnd.unity");
embed.setAttribute("type", "application/vnd.ffuwp");
embed.setAttribute(
"pluginspage",
"http://www.unity3d.com/unity-web-player-2.x"
);
embed.setAttribute("id", "Unity_embed");
embed.setAttribute("id", "unityEmbed");
embed.setAttribute("width", "1280");
embed.setAttribute("height", "680");
embed.setAttribute("src", window.asseturl + "main.unity3d");
embed.setAttribute("disablecontdparaextmenu", "true");
embed.setAttribute("src", window.assetUrl + "main.unity3d");
embed.setAttribute("bordercolor", "000000");
embed.setAttribute("backgroundcolor", "000000");
embed.setAttribute("disableContextMenu", true);
embed.setAttribute("textcolor", "ccffff");
embed.setAttribute("logoimage", "assets/img/unity_dexlabs.png");
embed.setAttribute("progressbarimage", "assets/img/unity_loadingbar.png");
embed.setAttribute("logoimage", "assets/img/unity-dexlabs.png");
embed.setAttribute("progressbarimage", "assets/img/unity-loadingbar.png");
embed.setAttribute(
"progressframeimage",
"assets/img/unity_loadingframe.png"
);
embed.setAttribute(
"autoupdateurlsignature",
"42180ee5edc4e3d4dd706bcc17cedd8d6ec7b7ac463071fd34ab97fe181f1a78df31db5feb4526677e4f69ef53acaff44471591e68b87f041c80fd54765f0d5725b08aa28f5acf7716ffb2a04e971269f35925c7e38d57dd78f6a206530caaa3da7e32f07f19810efc0ebf29a4eae976a925ad9cc5beb4dd51564c67dc489033"
);
embed.setAttribute(
"autoupdateurl",
"http://wp-cartoonnetwork.unity3d.com/ff/big/beta-20111013/autodownload_webplugin_beta"
"assets/img/unity-loadingframe.png"
);
var div = document.getElementById("client");

View File

@@ -0,0 +1,951 @@
var remote = require("remote");
var remotefs = remote.require("fs-extra");
var dns = remote.require("dns");
var path = remote.require("path");
var dialog = remote.require("dialog");
var net = remote.require("net");
var spawn = require("child_process").spawn;
var userData = remote.require("app").getPath("userData");
var configPath = path.join(userData, "config.json");
var serversPath = path.join(userData, "servers.json");
var versionsPath = path.join(userData, "versions.json");
var cacheRoot = path.join(
userData,
"/../../LocalLow/Unity/Web Player/Cache"
);
var offlineRootDefault = path.join(cacheRoot, "Offline");
var offlineRoot = offlineRootDefault;
var cdnString = "http://cdn.dexlabs.systems/ff/big";
var versionArray;
var serverArray;
var cacheSizes;
var defaultHashes;
var config;
function enableServerListButtons() {
$("#of-connect-button").removeClass("disabled");
$("#of-connect-button").prop("disabled", false);
$("#of-editserver-button").removeClass("disabled");
$("#of-editserver-button").prop("disabled", false);
$("#of-deleteserver-button").removeClass("disabled");
$("#of-deleteserver-button").prop("disabled", false);
}
function disableServerListButtons() {
$("#of-connect-button").addClass("disabled");
$("#of-connect-button").prop("disabled", true);
$("#of-editserver-button").addClass("disabled");
$("#of-editserver-button").prop("disabled", true);
$("#of-deleteserver-button").addClass("disabled");
$("#of-deleteserver-button").prop("disabled", true);
}
function enableVersionAddButton() {
$("#of-addversion-button").removeClass("disabled");
$("#of-addversion-button").prop("disabled", false);
}
function enableVersionListButtons() {
$("#of-editversion-button").removeClass("disabled");
$("#of-editversion-button").prop("disabled", false);
$("#of-deleteversion-button").removeClass("disabled");
$("#of-deleteversion-button").prop("disabled", false);
}
function disableVersionAddButton() {
$("#of-addversion-button").addClass("disabled");
$("#of-addversion-button").prop("disabled", true);
}
function disableVersionListButtons() {
$("#of-editversion-button").addClass("disabled");
$("#of-editversion-button").prop("disabled", true);
$("#of-deleteversion-button").addClass("disabled");
$("#of-deleteversion-button").prop("disabled", true);
}
function getAppVersion() {
appVersion = remote.require("app").getVersion();
// Simplify version, ex. 1.4.0 -> 1.4,
// but only if a revision number isn't present
if (appVersion.endsWith(".0")) {
return appVersion.substr(0, appVersion.length - 2);
} else {
return appVersion;
}
}
function setAppVersionText() {
$("#of-aboutversionnumber").text("Version " + getAppVersion());
$("#of-versionnumber").text("v" + getAppVersion());
}
function validateServerSave(modalName) {
// works everytime a key is entered into the server save form
var descInput = document.getElementById(modalName + "server-descinput");
var ipInput = document.getElementById(modalName + "server-ipinput");
var button = document.getElementById(modalName + "server-savebutton");
var valid = true;
descInput.classList.remove("invalidinput");
ipInput.classList.remove("invalidinput");
if (
descInput.value.length < parseInt(descInput.getAttribute("minlength")) ||
descInput.value.length > parseInt(descInput.getAttribute("maxlength"))
) {
descInput.classList.add("invalidinput");
valid = false;
}
if (!(new RegExp(ipInput.getAttribute("pattern"))).test(ipInput.value)) {
ipInput.classList.add("invalidinput");
valid = false;
}
if (valid) {
button.removeAttribute("disabled");
} else {
button.setAttribute("disabled", "");
}
}
function addServer() {
var jsonToModify = JSON.parse(remotefs.readFileSync(serversPath));
var server = {};
server["uuid"] = uuidv4();
server["description"] =
$("#addserver-descinput").val().length == 0
? "My OpenFusion Server"
: $("#addserver-descinput").val();
server["ip"] =
$("#addserver-ipinput").val().length == 0
? "127.0.0.1:23000"
: $("#addserver-ipinput").val();
server["version"] = $("#addserver-versionselect option:selected").text();
//server['endpoint'] =
jsonToModify["servers"].push(server);
remotefs.writeFileSync(serversPath, JSON.stringify(jsonToModify, null, 4));
loadServerList();
}
function editServer() {
var jsonToModify = JSON.parse(remotefs.readFileSync(serversPath));
$.each(jsonToModify["servers"], function (key, value) {
if (value["uuid"] == getSelectedServer()) {
value["description"] =
$("#editserver-descinput").val().length == 0
? value["description"]
: $("#editserver-descinput").val();
value["ip"] =
$("#editserver-ipinput").val().length == 0
? value["ip"]
: $("#editserver-ipinput").val();
value["version"] = $(
"#editserver-versionselect option:selected"
).text();
}
});
remotefs.writeFileSync(serversPath, JSON.stringify(jsonToModify, null, 4));
loadServerList();
}
function deleteServer() {
var jsonToModify = JSON.parse(remotefs.readFileSync(serversPath));
var result = jsonToModify["servers"].filter(function (obj) {
return obj.uuid === getSelectedServer();
})[0];
var resultindex = jsonToModify["servers"].indexOf(result);
jsonToModify["servers"].splice(resultindex, 1);
remotefs.writeFileSync(serversPath, JSON.stringify(jsonToModify, null, 4));
loadServerList();
}
function restoreDefaultServers() {
remotefs.copySync(
path.join(__dirname, "/defaults/servers.json"),
serversPath
);
loadServerList();
}
function validateVersionSave(modalName) {
// works everytime a key is entered into the version save form
var nameInput = document.getElementById(modalName + "version-nameinput");
var urlInput = document.getElementById(modalName + "version-urlinput");
var button = document.getElementById(modalName + "version-savebutton");
var valid = true;
nameInput.classList.remove("invalidinput");
urlInput.classList.remove("invalidinput");
var matchingVersions = versionArray.filter(function (obj) {
return obj.name === nameInput.value;
});
var allowedMatches = (modalName === "edit") ? 1 : 0;
if (
matchingVersions.length > allowedMatches ||
!(new RegExp(nameInput.getAttribute("pattern"))).test(nameInput.value)
) {
nameInput.classList.add("invalidinput");
valid = false;
}
if (!(new RegExp(urlInput.getAttribute("pattern"))).test(urlInput.value)) {
urlInput.classList.add("invalidinput");
valid = false;
}
if (valid) {
button.removeAttribute("disabled");
} else {
button.setAttribute("disabled", "");
}
}
function addVersion() {
var jsonToModify = JSON.parse(remotefs.readFileSync(versionsPath));
var version = {};
version["name"] =
$("#addversion-nameinput").val().length == 0
? "custom-build-" + uuidv4().substring(0, 8)
: $("#addversion-nameinput").val();
version["url"] =
$("#addversion-urlinput").val().length == 0
? cdnString + "/" + version["name"] + "/"
: $("#addversion-urlinput").val();
var matchingVersions = jsonToModify["versions"].filter(function (obj) {
return obj.name === version["name"];
});
if (matchingVersions.length > 0) return;
jsonToModify["versions"].unshift(version);
remotefs.writeFileSync(versionsPath, JSON.stringify(jsonToModify, null, 4));
loadCacheList();
handleCache("hash-check", version["name"]);
}
function editVersion() {
var jsonToModify = JSON.parse(remotefs.readFileSync(versionsPath));
var editedVersionString = null;
$.each(jsonToModify["versions"], function (key, value) {
if (value["name"] == getSelectedVersion() && !defaultHashes.hasOwnProperty(value["name"])) {
value["name"] =
$("#editversion-nameinput").val().length == 0
? value["name"]
: $("#editversion-nameinput").val();
value["url"] =
$("#editversion-urlinput").val().length == 0
? value["url"]
: $("#editversion-urlinput").val();
editedVersionString = value["name"];
}
});
if (!editedVersionString) return;
remotefs.writeFileSync(versionsPath, JSON.stringify(jsonToModify, null, 4));
loadCacheList();
handleCache("hash-check", editedVersionString);
}
function deleteVersion() {
var jsonToModify = JSON.parse(remotefs.readFileSync(versionsPath));
var result = jsonToModify["versions"].filter(function (obj) {
return obj.name === getSelectedVersion();
})[0];
if (defaultHashes.hasOwnProperty(result.name)) return;
var resultindex = jsonToModify["versions"].indexOf(result);
jsonToModify["versions"].splice(resultindex, 1);
remotefs.writeFileSync(versionsPath, JSON.stringify(jsonToModify, null, 4));
loadCacheList();
delete cacheSizes[result.name];
}
function restoreDefaultVersions() {
remotefs.copySync(
path.join(__dirname, "/defaults/versions.json"),
versionsPath
);
loadCacheList();
handleCache("hash-check");
}
function editConfig() {
var jsonToModify = JSON.parse(remotefs.readFileSync(configPath));
jsonToModify["autoupdate-check"] = $("#editconfig-autoupdate").prop("checked");
jsonToModify["cache-swapping"] = $("#editconfig-cacheswapping").prop("checked");
jsonToModify["enable-offline-cache"] = $("#editconfig-enableofflinecache").prop("checked");
jsonToModify["verify-offline-cache"] = $("#editconfig-verifyofflinecache").prop("checked");
var dirInput = $("#editconfig-offlinecachelocation:text").val();
var shouldChangeRoot = (
remotefs.existsSync(dirInput) &&
remotefs.statSync(dirInput).isDirectory()
);
jsonToModify["offline-cache-location"] = shouldChangeRoot ? dirInput : offlineRoot;
remotefs.writeFileSync(configPath, JSON.stringify(jsonToModify, null, 4));
loadConfig();
if (shouldChangeRoot) handleCache("hash-check", null, "offline");
}
function validateCacheLocation() {
var input = document.getElementById("editconfig-offlinecachelocation");
var button = document.getElementById("editconfig-savebutton");
input.classList.remove("invalidinput");
button.removeAttribute("disabled");
if (!remotefs.existsSync(input.value) || !remotefs.statSync(input.value).isDirectory()) {
input.classList.add("invalidinput");
button.setAttribute("disabled", "");
}
}
function loadGameVersions() {
var versionJson = remotefs.readJsonSync(versionsPath);
versionArray = versionJson["versions"];
$.each(versionArray, function (key, value) {
$(new Option(value.name, "val")).appendTo("#addserver-versionselect");
$(new Option(value.name, "val")).appendTo("#editserver-versionselect");
});
}
function loadConfig() {
// Load config object globally
config = remotefs.readJsonSync(configPath);
$("#editconfig-autoupdate").prop("checked", config["autoupdate-check"]);
$("#editconfig-cacheswapping").prop("checked", config["cache-swapping"]);
$("#editconfig-enableofflinecache").prop("checked", config["enable-offline-cache"]);
$("#editconfig-verifyofflinecache").prop("checked", config["verify-offline-cache"]);
offlineRoot = config["offline-cache-location"] || offlineRootDefault;
$("#editconfig-offlinecachelocation:text").val(offlineRoot);
validateCacheLocation();
}
function loadServerList() {
var serverJson = remotefs.readJsonSync(serversPath);
serverArray = serverJson["servers"];
deselectServer(); // Disable buttons until another server is selected
$(".server-listing-entry").remove(); // Clear out old stuff, if any
if (serverArray.length > 0) {
// Servers were found in the JSON
$("#server-listing-placeholder").attr("hidden", true);
$.each(serverArray, function (key, value) {
// Create the row, and populate the cells
var row = document.createElement("tr");
row.className = "server-listing-entry";
row.setAttribute("id", value.uuid);
var cellName = document.createElement("td");
cellName.textContent = value.description;
var cellVersion = document.createElement("td");
cellVersion.textContent = value.version;
cellVersion.className = "text-monospace";
row.appendChild(cellName);
row.appendChild(cellVersion);
$("#server-tablebody").append(row);
});
} else {
// No servers are added, make sure placeholder is visible
$("#server-listing-placeholder").attr("hidden", false);
}
}
function loadCacheList() {
var versionjson = remotefs.readJsonSync(versionsPath);
versionArray = versionjson["versions"];
if (!defaultHashes) {
defaultHashes = remotefs.readJsonSync(path.join(
__dirname,
"/defaults/hashes.json"
));
}
deselectVersion();
$(".cache-listing-entry").remove();
$.each(versionArray, function (key, value) {
var row = document.createElement("tr");
row.className = "cache-listing-entry"
row.setAttribute("id", value.name);
var cellVersion = document.createElement("td");
cellVersion.textContent = value.name;
cellVersion.className = "text-monospace";
var cellPlayableCache = getCacheInfoCell(value.name, "playable");
var cellOfflineCache = getCacheInfoCell(value.name, "offline");
row.appendChild(cellVersion);
row.appendChild(cellPlayableCache);
row.appendChild(cellOfflineCache);
$("#cache-tablebody").append(row);
});
storageLoadingStart();
storageLoadingUpdate(cacheSizes);
storageLoadingComplete(cacheSizes);
}
function getCacheElemID(versionString, cacheMode, elementName) {
return [versionString, cacheMode, "cache", elementName].filter(function (value) {
return typeof value !== "undefined";
}).join("-");
}
function getCacheButtonID(versionString, cacheMode, buttonMode) {
return [getCacheElemID(versionString, cacheMode), buttonMode, "button"].join("-");
}
function getCacheLabelText(sizes) {
if (!sizes || sizes.total === 0)
return "?.?? GB / ?.?? GB";
var gb = 1 << 30;
var labelText = (sizes.intact / gb).toFixed(2) + " / " + (sizes.total / gb).toFixed(2) + " GB";
if (sizes.altered > 0) {
labelText += "<br/>(" + (sizes.altered / gb).toFixed(2) + " GB Altered)";
}
return labelText;
}
function getCacheInfoCell(versionString, cacheMode) {
var divID = getCacheElemID(versionString, cacheMode, "div");
var labelID = getCacheElemID(versionString, cacheMode, "label");
var settings = {
download: {
icon: "fas fa-download",
class: "btn btn-success mr-1",
tooltip: "Download Cache"
},
fix: {
icon: "fas fa-hammer",
class: "btn btn-warning mr-1",
tooltip: "Fix Altered Files in Cache"
},
delete: {
icon: "fas fa-trash-alt",
class: "btn btn-danger mr-1",
tooltip: "Delete Cache"
}
};
var cellCache = document.createElement("td");
var divCacheAll = document.createElement("div");
var labelCache = document.createElement("label");
labelCache.setAttribute("id", labelID);
labelCache.setAttribute("for", divID);
labelCache.innerHTML = getCacheLabelText(
(cacheSizes && cacheSizes[versionString]) ?
cacheSizes[versionString][cacheMode] :
null
);
var divCacheButtons = document.createElement("div");
divCacheButtons.setAttribute("id", labelID);
$.each(settings, function (buttonMode, config) {
if (cacheMode === "playable" && buttonMode !== "delete") {
return;
}
var buttonID = getCacheButtonID(versionString, cacheMode, buttonMode);
var iconItalic = document.createElement("i");
iconItalic.setAttribute("class", config.icon);
var buttonCache = document.createElement("button");
buttonCache.setAttribute("id", buttonID);
buttonCache.setAttribute("class", config.class);
buttonCache.setAttribute("title", config.tooltip);
buttonCache.setAttribute("type", "button");
buttonCache.setAttribute("onclick", "handleCache(\"" + buttonMode + "\", \"" + versionString + "\", \"" + cacheMode + "\");");
buttonCache.appendChild(iconItalic);
divCacheButtons.appendChild(buttonCache);
});
divCacheAll.appendChild(labelCache);
divCacheAll.appendChild(divCacheButtons);
cellCache.appendChild(divCacheAll);
return cellCache;
}
function storageLoadingStart(vString, cMode) {
var versionStrings = [];
$.each(versionArray, function (key, value) {
if (vString) {
if (vString === value.name)
versionStrings.push(value.name);
} else {
versionStrings.push(value.name);
}
});
var cacheModes = (cMode) ? [cMode] : ["offline", "playable"];
deselectVersion();
disableVersionAddButton();
$.each(versionStrings, function (vKey, versionString) {
$.each(cacheModes, function (cKey, cacheMode) {
var buttonDelete = document.getElementById(getCacheButtonID(versionString, cacheMode, "delete"));
var buttonDownload = document.getElementById(getCacheButtonID(versionString, cacheMode, "download"));
var buttonFix = document.getElementById(getCacheButtonID(versionString, cacheMode, "fix"));
if (!buttonDelete) return;
buttonDelete.setAttribute("disabled", "");
buttonDelete.children[0].setAttribute("class", "fas fa-spinner fa-spin fa-fw");
if (cacheMode === "offline") {
buttonDownload.setAttribute("disabled", "");
buttonDownload.children[0].setAttribute("class", "fas fa-spinner fa-spin fa-fw");
buttonFix.setAttribute("disabled", "");
buttonFix.children[0].setAttribute("class", "fas fa-spinner fa-spin fa-fw");
}
});
});
}
function storageLoadingUpdate(allSizes) {
$.each(allSizes, function (versionString, vSizes) {
$.each(vSizes, function (cacheMode, sizes) {
var label = document.getElementById(getCacheElemID(versionString, cacheMode, "label"));
cacheSizes = cacheSizes || {};
cacheSizes[versionString] = cacheSizes[versionString] || {};
cacheSizes[versionString][cacheMode] = sizes || {};
if (!label) return;
label.innerHTML = getCacheLabelText(sizes);
});
});
}
function storageLoadingComplete(allSizes) {
$.each(allSizes, function (versionString, vSizes) {
$.each(vSizes, function (cacheMode, sizes) {
var buttonDelete = document.getElementById(getCacheButtonID(versionString, cacheMode, "delete"));
var buttonDownload = document.getElementById(getCacheButtonID(versionString, cacheMode, "download"));
var buttonFix = document.getElementById(getCacheButtonID(versionString, cacheMode, "fix"));
if (!buttonDelete) return;
buttonDelete.children[0].setAttribute("class", "fas fa-trash-alt");
if (cacheMode === "offline") {
buttonDownload.children[0].setAttribute("class", "fas fa-download");
buttonFix.children[0].setAttribute("class", "fas fa-hammer");
}
if (sizes.intact > 0 || sizes.altered > 0) {
buttonDelete.removeAttribute("disabled");
if (cacheMode === "offline") {
buttonDownload.setAttribute("disabled", "");
if (sizes.altered > 0 || sizes.intact < sizes.total) {
buttonFix.removeAttribute("disabled");
}
}
} else {
buttonDelete.setAttribute("disabled", "");
if (cacheMode === "offline") {
buttonDownload.removeAttribute("disabled");
buttonFix.setAttribute("disabled", "");
}
}
});
});
enableVersionAddButton();
}
function handleCache(operation, versionString, cacheMode, callback) {
var versions = versionArray.filter(function (obj) {
return obj.name === versionString;
});
var cdnRoot = (versions.length === 0) ? cdnString : versions[0].url;
var lastSizes = { intact: 0, altered: 0, total: 0 };
var buf = "";
storageLoadingStart(versionString, cacheMode);
var server = net.createServer(function (sock) {
sock.setEncoding("utf8");
sock.on("data", function (data) {
buf += data;
var end = buf.indexOf("\n");
while (end > 0) {
var sub = buf.substring(0, end);
buf = buf.substring(end + 1);
lastSizes = JSON.parse(sub);
storageLoadingUpdate(lastSizes);
end = buf.indexOf("\n");
}
});
});
server.listen(0, "localhost", function () {
spawn(
path.join(__dirname, "lib", "cache_handler.exe"),
[
"--operation", operation,
// roots below contain version-agnostic main directories for caches
"--playable-root", cacheRoot,
"--offline-root", offlineRoot,
"--user-dir", userData,
// CDN root contains version-specific directory, unless cacheMode is "all"
"--cdn-root", cdnRoot,
"--cache-mode", cacheMode || "all",
"--cache-version", versionString || "all",
"--port", server.address().port,
"--official-caches"
].concat(Object.keys(defaultHashes)),
{
stdio: "inherit"
}
).on("exit", function (code, signal) {
if (code !== 0 || signal) {
dialog.showErrorBox(
"Sorry!",
"Process \"" + operation + "\" failed with code " + code + " and signal " + signal + "."
);
}
server.close();
storageLoadingComplete(lastSizes);
if (callback)
callback(lastSizes);
});
});
}
function performCacheSwap(newVersion) {
var currentCache = path.join(cacheRoot, "FusionFall");
var newCache = path.join(cacheRoot, newVersion);
var record = path.join(userData, ".lastver");
// If cache renaming would result in a no-op (ex. launching the same version
// two times), then skip it. This avoids permissions errors with multiple clients
// (file/folder is already open in another process)
var skip = false;
if (remotefs.existsSync(currentCache)) {
// Cache already exists, find out what version it belongs to
if (remotefs.existsSync(record)) {
var lastVersion = remotefs.readFileSync(record, (encoding = "utf8"));
if (lastVersion != newVersion) {
// Remove the directory we're trying to store the
// existing cache to if it already exists for whatever
// reason, as it would cause an EPERM error otherwise.
// This is a no-op if the directory doesn't exist
remotefs.removeSync(path.join(cacheRoot, lastVersion));
// Store old cache to named directory
remotefs.renameSync(
currentCache,
path.join(cacheRoot, lastVersion)
);
} else {
console.log("Cached version unchanged, skipping rename");
skip = true;
}
console.log("Current cache is " + lastVersion);
}
}
// Make note of what version we are launching for next launch
remotefs.writeFileSync(record, newVersion);
if (remotefs.existsSync(newCache) && !skip) {
// Rename saved cache to FusionFall
remotefs.renameSync(newCache, currentCache);
console.log("Current cache swapped to " + newVersion);
}
}
function prepGameInfo(serverUUID) {
var serverInfo = serverArray.filter(function (obj) {
return obj.uuid === serverUUID;
})[0];
var versionInfo = versionArray.filter(function (obj) {
return obj.name === serverInfo.version;
})[0];
// If cache swapping property exists AND is `true`, run cache swapping logic
if (config["cache-swapping"]) {
try {
performCacheSwap(versionInfo.name);
} catch (ex) {
console.log(
"Error when swapping cache, it may get overwritten:\n" + ex
);
}
}
if (!config["enable-offline-cache"]) {
// if we always ignore the offline cache, just use the URL
setGameInfo(serverInfo, versionInfo.url);
return;
}
var offlinePath = path.join(offlineRoot, versionInfo.name);
var offlineURL = "file:///" + offlinePath.replace(/\\/g, "/") + "/";
if (config["verify-offline-cache"]) {
// if required, do a full hash check, and use the offline cache only if it is fully intact
handleCache("hash-check", versionInfo.name, "offline", function (sizes) {
var versionURL = (sizes.intact < sizes.total) ? versionInfo.url : offlineURL;
setGameInfo(serverInfo, versionURL);
});
return;
}
// if main.unity3d is present, use the offline cache
var mainPath = path.join(offlinePath, "main.unity3d");
var versionURL = remotefs.existsSync(mainPath) ? versionInfo.url : offlineURL;
setGameInfo(serverInfo, versionURL);
}
// For writing loginInfo.php, assetInfo.php, etc.
function setGameInfo(serverInfo, versionURL) {
var versionURLRoot = versionURL.endsWith("/") ? versionURL : versionURL + "/";
window.assetUrl = versionURLRoot; // game-client.js needs to access this
console.log("Cache will expand from " + versionURLRoot);
remotefs.writeFileSync(path.join(__dirname, "assetInfo.php"), assetUrl);
if (serverInfo.hasOwnProperty("endpoint")) {
var httpEndpoint = serverInfo.endpoint.replace("https://", "http://");
remotefs.writeFileSync(
path.join(__dirname, "rankurl.txt"),
httpEndpoint + "getranks"
);
// Write these out too
remotefs.writeFileSync(
path.join(__dirname, "sponsor.php"),
httpEndpoint + "upsell/sponsor.png"
);
remotefs.writeFileSync(
path.join(__dirname, "images.php"),
httpEndpoint + "upsell/"
);
} else {
// Remove/default the endpoint related stuff, this server won't be using it
if (remotefs.existsSync(path.join(__dirname, "rankurl.txt"))) {
remotefs.unlinkSync(path.join(__dirname, "rankurl.txt"));
remotefs.writeFileSync(
path.join(__dirname, "sponsor.php"),
"assets/img/welcome.png"
);
remotefs.writeFileSync(
path.join(__dirname, "images.php"),
"assets/img/"
);
}
}
// Server address parsing
var address;
var port;
var sepPos = serverInfo.ip.indexOf(":");
if (sepPos > -1) {
address = serverInfo.ip.substr(0, sepPos);
port = serverInfo.ip.substr(sepPos + 1);
} else {
address = serverInfo.ip;
port = 23000; // default
}
// DNS resolution. there is no synchronous version for some stupid reason
if (!address.match(/^[0-9.]+$/)) {
dns.lookup(address, (family = 4), function (err, resolvedAddress) {
if (!err) {
console.log("Resolved " + address + " to " + resolvedAddress);
address = resolvedAddress;
} else {
console.log("Err: " + err.code);
}
prepConnection(address, port);
});
} else {
console.log(address + " is an IP; skipping DNS lookup");
prepConnection(address, port);
}
}
function prepConnection(address, port) {
var full = address + ":" + port;
console.log("Will connect to " + full);
remotefs.writeFileSync(path.join(__dirname, "loginInfo.php"), full);
launchGame();
}
// Returns the UUID of the server with the selected background color.
// Yes, there are probably better ways to go about this, but it works well enough.
function getSelectedServer() {
return $("#server-tablebody > tr.bg-primary").prop("id");
}
function getSelectedVersion() {
return $("#cache-tablebody > tr.bg-primary").prop("id");
}
function connectToServer() {
// Get ID of the selected server, which corresponds to its UUID in the json
console.log("Connecting to server with UUID of " + getSelectedServer());
// Prevent the user from clicking anywhere else during the transition
$("body,html").css("pointer-events", "none");
stopEasterEggs();
$("#of-serverselector").fadeOut("slow", function () {
setTimeout(function () {
$("body,html").css("pointer-events", "");
prepGameInfo(getSelectedServer());
}, 200);
});
}
// If applicable, deselect currently selected server.
function deselectServer() {
disableServerListButtons();
$(".server-listing-entry").removeClass("bg-primary");
}
function deselectVersion() {
disableVersionListButtons();
$(".cache-listing-entry").removeClass("bg-primary");
}
$("#server-table").on("click", ".server-listing-entry", function (event) {
enableServerListButtons();
$(this).addClass("bg-primary").siblings().removeClass("bg-primary");
});
$("#cache-table").on("click", ".cache-listing-entry", function (event) {
// wait for the add button to be re-enabled first
if ($("#of-addversion-button").prop("disabled")) return;
// do not select default builds
if (defaultHashes.hasOwnProperty($(this).attr("id"))) return;
enableVersionListButtons();
$(this).addClass("bg-primary").siblings().removeClass("bg-primary");
});
// QoL feature: if you double click on a server it will connect
$("#server-table").on("dblclick", ".server-listing-entry", function (event) {
$(this).addClass("bg-primary").siblings().removeClass("bg-primary");
connectToServer();
});
$("#of-addservermodal").on("show.bs.modal", function (e) {
validateServerSave("add");
});
$("#of-addversionmodal").on("show.bs.modal", function (e) {
validateVersionSave("add");
});
$("#of-editservermodal").on("show.bs.modal", function (e) {
var jsonToModify = remotefs.readJsonSync(serversPath);
$.each(jsonToModify["servers"], function (key, value) {
if (value["uuid"] == getSelectedServer()) {
$("#editserver-descinput")[0].value = value["description"];
$("#editserver-ipinput")[0].value = value["ip"];
var versionIndex = -1;
$.each($("#editserver-versionselect")[0], function (key, val) {
if (val.text === value["version"]) {
versionIndex = key;
}
});
$("#editserver-versionselect")[0].selectedIndex = versionIndex;
}
});
validateServerSave("edit");
});
$("#of-editversionmodal").on("show.bs.modal", function (e) {
var jsonToModify = remotefs.readJsonSync(versionsPath);
$.each(jsonToModify["versions"], function (key, value) {
if (value["name"] == getSelectedVersion()) {
$("#editversion-nameinput")[0].value = value["name"];
$("#editversion-urlinput")[0].value = value["url"];
}
});
validateVersionSave("edit");
});
$("#of-deleteservermodal").on("show.bs.modal", function (e) {
var result = serverArray.filter(function (obj) {
return obj.uuid === getSelectedServer();
})[0];
$("#deleteserver-servername").html(result.description);
});
$("#of-deleteversionmodal").on("show.bs.modal", function (e) {
var result = versionArray.filter(function (obj) {
return obj.name === getSelectedVersion();
})[0];
$("#deleteversion-versionname").html(result.name);
});
$("#of-editcacheconfigmodal").on("show.bs.modal", function (e) {
if (!cacheSizes) handleCache("hash-check");
});
$("#of-editconfigmodal").on("show.bs.modal", function (e) {
// best to keep this synced on modal show
loadConfig();
});

View File

@@ -1,368 +0,0 @@
var remote = require("remote");
var remotefs = remote.require("fs-extra");
var dns = remote.require("dns");
var userdir = remote.require("app").getPath("userData");
var versionarray;
var serverarray;
var config;
function enableServerListButtons() {
$("#of-connect-button").removeClass("disabled");
$("#of-connect-button").prop("disabled", false);
$("#of-editserver-button").removeClass("disabled");
$("#of-editserver-button").prop("disabled", false);
$("#of-deleteserver-button").removeClass("disabled");
$("#of-deleteserver-button").prop("disabled", false);
}
function disableServerListButtons() {
$("#of-connect-button").addClass("disabled");
$("#of-connect-button").prop("disabled", true);
$("#of-editserver-button").addClass("disabled");
$("#of-editserver-button").prop("disabled", true);
$("#of-deleteserver-button").addClass("disabled");
$("#of-deleteserver-button").prop("disabled", true);
}
function getAppVersion() {
appversion = remote.require("app").getVersion();
// simplify version, ex. 1.4.0 -> 1.4,
// but only if a revision number isn't present
if(appversion.endsWith(".0")){
return appversion.substr(0, appversion.length - 2)
} else {
return appversion
}
}
function setAppVersionText() {
$("#of-aboutversionnumber").text("Version " + getAppVersion());
$("#of-versionnumber").text("v" + getAppVersion());
}
function addServer() {
var jsontomodify = JSON.parse(
remotefs.readFileSync(userdir + "\\servers.json")
);
var server = {};
server["uuid"] = uuidv4();
server["description"] =
$("#addserver-descinput").val().length == 0
? "My OpenFusion Server"
: $("#addserver-descinput").val();
server["ip"] =
$("#addserver-ipinput").val().length == 0
? "127.0.0.1:23000"
: $("#addserver-ipinput").val();
server["version"] = $("#addserver-versionselect option:selected").text();
//server['endpoint'] =
jsontomodify["servers"].push(server);
remotefs.writeFileSync(
userdir + "\\servers.json",
JSON.stringify(jsontomodify, null, 4)
);
loadServerList();
}
function editServer() {
var jsontomodify = JSON.parse(
remotefs.readFileSync(userdir + "\\servers.json")
);
$.each(jsontomodify["servers"], function (key, value) {
if (value["uuid"] == getSelectedServer()) {
value["description"] =
$("#editserver-descinput").val().length == 0
? value["description"]
: $("#editserver-descinput").val();
value["ip"] =
$("#editserver-ipinput").val().length == 0
? value["ip"]
: $("#editserver-ipinput").val();
value["version"] = $(
"#editserver-versionselect option:selected"
).text();
}
});
remotefs.writeFileSync(
userdir + "\\servers.json",
JSON.stringify(jsontomodify, null, 4)
);
loadServerList();
}
function deleteServer() {
var jsontomodify = JSON.parse(
remotefs.readFileSync(userdir + "\\servers.json")
);
var result = jsontomodify["servers"].filter(function (obj) {
return obj.uuid === getSelectedServer();
})[0];
var resultindex = jsontomodify["servers"].indexOf(result);
jsontomodify["servers"].splice(resultindex, 1);
remotefs.writeFileSync(
userdir + "\\servers.json",
JSON.stringify(jsontomodify, null, 4)
);
loadServerList();
}
function restoreDefaultServers() {
remotefs.copySync(
__dirname + "\\defaults\\servers.json",
userdir + "\\servers.json"
);
loadServerList();
}
function loadGameVersions() {
var versionjson = JSON.parse(
remotefs.readFileSync(userdir + "\\versions.json")
);
versionarray = versionjson["versions"];
$.each(versionarray, function (key, value) {
$(new Option(value.name, "val")).appendTo("#addserver-versionselect");
$(new Option(value.name, "val")).appendTo("#editserver-versionselect");
});
}
function loadConfig() {
// load config object globally
config = JSON.parse(remotefs.readFileSync(userdir + "\\config.json"));
}
function loadServerList() {
var serverjson = JSON.parse(
remotefs.readFileSync(userdir + "\\servers.json")
);
serverarray = serverjson["servers"];
$(".server-listing-entry").remove(); // Clear out old stuff, if any
disableServerListButtons(); // Disable buttons until another server is selected
if (serverarray.length > 0) {
// Servers were found in the JSON
$("#server-listing-placeholder").attr("hidden", true);
$.each(serverarray, function (key, value) {
// Create the row, and populate the cells
var row = document.createElement("tr");
row.className = "server-listing-entry";
row.setAttribute("id", value.uuid);
var cellName = document.createElement("td");
cellName.textContent = value.description;
var cellVersion = document.createElement("td");
cellVersion.textContent = value.version;
cellVersion.className = "text-monospace";
row.appendChild(cellName);
row.appendChild(cellVersion);
$("#server-tablebody").append(row);
});
} else {
// No servers are added, make sure placeholder is visible
$("#server-listing-placeholder").attr("hidden", false);
}
}
function performCacheSwap(newversion) {
var cacheroot = userdir + "\\..\\..\\LocalLow\\Unity\\Web Player\\Cache";
var currentcache = cacheroot + "\\Fusionfall";
var newcache = cacheroot + "\\" + newversion;
var record = userdir + "\\.lastver";
// if cache renaming would result in a no-op (ex. launching the same version
// two times), then skip it. this avoids permissions errors with multiple clients
// (file/folder is already open in another process)
var skip = false;
if (remotefs.existsSync(currentcache)) {
// cache already exists, find out what version it belongs to
if (remotefs.existsSync(record)) {
lastversion = remotefs.readFileSync(record);
if (lastversion != newversion) {
remotefs.renameSync(
currentcache,
cacheroot + "\\" + lastversion
);
} else {
console.log(
"Cached version unchanged, renaming will be skipped"
);
skip = true;
}
console.log("Current cache is " + lastversion);
} else {
console.log(
"Couldn't find last version record; cache may get overwritten"
);
}
}
if (remotefs.existsSync(newcache) || !skip) {
// rename saved cache to FusionFall
remotefs.renameSync(newcache, currentcache);
console.log("Current cache swapped to " + newversion);
}
// make note of what version we are launching for next launch
remotefs.writeFileSync(record, newversion);
}
// For writing loginInfo.php, assetInfo.php, etc.
function setGameInfo(serverUUID) {
var result = serverarray.filter(function (obj) {
return obj.uuid === serverUUID;
})[0];
var gameversion = versionarray.filter(function (obj) {
return obj.name === result.version;
})[0];
// if cache swapping property exists AND is `true`, run cache swapping logic
if (config["cache-swapping"]) {
try {
performCacheSwap(gameversion.name);
} catch (ex) {
console.log(
"Error when swapping cache, it may get overwritten:\n" + ex
);
}
}
window.asseturl = gameversion.url; // gameclient.js needs to access this
remotefs.writeFileSync(__dirname + "\\assetInfo.php", asseturl);
if (result.hasOwnProperty("endpoint")) {
var httpendpoint = result.endpoint.replace("https://", "http://");
remotefs.writeFileSync(
__dirname + "\\rankurl.txt",
httpendpoint + "getranks"
);
// Write these out too
remotefs.writeFileSync(
__dirname + "\\sponsor.php",
httpendpoint + "upsell/sponsor.png"
);
remotefs.writeFileSync(
__dirname + "\\images.php",
httpendpoint + "upsell/"
);
} else {
// Remove/default the endpoint related stuff, this server won't be using it
if (remotefs.existsSync(__dirname + "\\rankurl.txt")) {
remotefs.unlinkSync(__dirname + "\\rankurl.txt");
remotefs.writeFileSync(
__dirname + "\\sponsor.php",
"assets/img/welcome.png"
);
remotefs.writeFileSync(__dirname + "\\images.php", "assets/img/");
}
}
// Server address parsing
var address;
var port;
var sepPos = result.ip.indexOf(":");
if (sepPos > -1) {
address = result.ip.substr(0, sepPos);
port = result.ip.substr(sepPos + 1);
} else {
address = result.ip;
port = 23000; // default
}
// DNS resolution. there is no synchronous version for some stupid reason
if (!address.match(/^[0-9.]+$/))
dns.resolve4(address, function (err, res) {
if (!err) {
console.log("Resolved " + address + " to " + res[0]);
address = res[0];
} else {
console.log("Err: " + err.code);
}
prepConnection(address, port);
});
else {
console.log(address + " is an IP; skipping DNS lookup");
prepConnection(address, port);
}
}
function prepConnection(address, port) {
var full = address + ":" + port;
console.log("Will connect to " + full);
remotefs.writeFileSync(__dirname + "\\loginInfo.php", full);
launchGame();
}
// Returns the UUID of the server with the selected background color.
// Yes, there are probably better ways to go about this, but it works well enough.
function getSelectedServer() {
return $("#server-tablebody > tr.bg-primary").prop("id");
}
function connectToServer() {
// Get ID of the selected server, which corresponds to its UUID in the json
console.log("Connecting to server with UUID of " + getSelectedServer());
// Prevent the user from clicking anywhere else during the transition
$("body,html").css("pointer-events", "none");
stopEasterEggs();
$("#of-serverselector").fadeOut("slow", function () {
setTimeout(function () {
$("body,html").css("pointer-events", "");
setGameInfo(getSelectedServer());
}, 200);
});
}
// If applicable, deselect currently selected server.
function deselectServer() {
disableServerListButtons();
$(".server-listing-entry").removeClass("bg-primary");
}
$("#server-table").on("click", ".server-listing-entry", function (event) {
enableServerListButtons();
$(this).addClass("bg-primary").siblings().removeClass("bg-primary");
});
// QoL feature: if you double click on a server it will connect
$("#server-table").on("dblclick", ".server-listing-entry", function (event) {
$(this).addClass("bg-primary").siblings().removeClass("bg-primary");
connectToServer();
});
$("#of-editservermodal").on("show.bs.modal", function (e) {
var jsontomodify = JSON.parse(
remotefs.readFileSync(userdir + "\\servers.json")
);
$.each(jsontomodify["servers"], function (key, value) {
if (value["uuid"] == getSelectedServer()) {
$("#editserver-descinput")[0].value = value["description"];
$("#editserver-ipinput")[0].value = value["ip"];
var versionIndex = -1;
$.each($("#editserver-versionselect")[0], function (key, val) {
if (val.text === value["version"]) {
versionIndex = key;
}
});
$("#editserver-versionselect")[0].selectedIndex = versionIndex;
}
});
});
$("#of-deleteservermodal").on("show.bs.modal", function (e) {
var result = serverarray.filter(function (obj) {
return obj.uuid === getSelectedServer();
})[0];
$("#deleteserver-servername").html(result.description);
});

View File

@@ -0,0 +1,8 @@
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<asmv3:application>
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">
<activeCodePage>UTF-8</activeCodePage>
<dpiAware>true</dpiAware>
</asmv3:windowsSettings>
</asmv3:application>
</assembly>

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<plist>
<dict>
<key>CFBundleVersion</key>
<string>2.5.5b4</string>
<key>UnityBuildNumber</key>
<string>50</string>
</dict>
<?xml version="1.0" encoding="UTF-8"?>
<plist>
<dict>
<key>CFBundleVersion</key>
<string>2.5.5b4</string>
<key>UnityBuildNumber</key>
<string>50</string>
</dict>
</plist>

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<plist>
<dict>
<key>CFBundleVersion</key>
<string>3.5.2f2</string>
<key>UnityBuildNumber</key>
<string>4c6ee796dacc</string>
</dict>
<?xml version="1.0" encoding="UTF-8"?>
<plist>
<dict>
<key>CFBundleVersion</key>
<string>2.5.5b4</string>
<key>UnityBuildNumber</key>
<string>50</string>
</dict>
</plist>

View File

@@ -1,13 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<plist>
<dict>
<key>CFBundleVersion</key>
<string>2.5.5b4</string>
<key>UnityBuildNumber</key>
<string>50</string>
<key>monoVersion</key>
<string>fusion-2.x.x</string>
<key>monoMinimumRevision</key>
<string>2.0.0f6</string>
</dict>
<?xml version="1.0" encoding="UTF-8"?>
<plist>
<dict>
<key>CFBundleVersion</key>
<string>2.5.5b4</string>
<key>UnityBuildNumber</key>
<string>50</string>
<key>monoVersion</key>
<string>fusion-2.x.x</string>
<key>monoMinimumRevision</key>
<string>2.0.0f6</string>
</dict>
</plist>

View File

@@ -1,16 +1,16 @@
const fs = require('fs');
const defaultdir = './dist/win-ia32-unpacked/resources/default_app'
const exefile = './dist/win-ia32-unpacked/OpenFusionClient.exe'
const defaultDir = './dist/win-ia32-unpacked/resources/default_app'
const exeFile = './dist/win-ia32-unpacked/OpenFusionClient.exe'
exports.default = function() {
// remove leftover files from default electron app
fs.rm(dir, { recursive: true }, (err) => {
fs.rm(defaultDir, { recursive: true }, (err) => {
if (err) {
throw err;
}
});
// patch executable for large address awareness
fs.open(exefile, "r+", (err, fd) => {
fs.open(exeFile, "r+", (err, fd) => {
if(!err) {
fs.write(
fd, new Uint8Array([0x22]), 0, 1, 0x166,

Binary file not shown.

View File

@@ -0,0 +1,560 @@
import json
import random
import shutil
import asyncio
import hashlib
from pathlib import Path
from dataclasses import dataclass
from typing import Any, Dict, List, Tuple
from argparse import Namespace, ArgumentParser
import httpx
import aiofiles
from bs4 import BeautifulSoup
# hack to get pyinstaller 3.5 to work
if False:
import anyio._backends._asyncio
# Definitions
BUF_SIZE = 1 << 16
VMDict = Dict[str, Dict[str, Dict[str, Any]]]
size_dict: VMDict = {}
hash_dict: VMDict = {}
hash_dict_updated: bool = False
# Helper Classes
@dataclass
class FileInfo:
version: str
mode: str
local_root: Path
url_root: str
current_local_path: Path
current_url: str
sha256: str
def resolve(self, suffix: str, sha256: str = ''):
return FileInfo(
version=self.version,
mode=self.mode,
local_root=self.local_root,
url_root=self.url_root,
current_local_path=(self.current_local_path / suffix),
current_url=(self.current_url.rstrip('/') + '/' + suffix.lstrip('/')),
sha256=(sha256 or self.sha256),
)
def resolve_full(self, full_path: Path, sha256: str = ''):
return self.resolve(full_path.relative_to(self.local_root).as_posix(), sha256=sha256)
def relative_path(self) -> str:
return self.current_local_path.relative_to(self.local_root).as_posix()
@dataclass
class FileInfoGroup:
version: str
mode: str
is_official: bool
local_root: Path
url_root: str
file_info_list: List[FileInfo]
# IPC
async def send_message(writer: asyncio.StreamWriter) -> None:
message = (json.dumps(size_dict) + '\n').encode('utf-8')
writer.write(message)
await writer.drain()
# Hash Helpers
async def get_file_size_and_hash(file_path: Path) -> Tuple[int, str]:
size = 0
sha256 = hashlib.sha256()
try:
async with aiofiles.open(file_path, mode='rb') as rb:
while True:
data = await rb.read(BUF_SIZE)
if not data:
break
sha256.update(data)
size += len(data)
except:
pass
return size, sha256.hexdigest()
async def check_file_hash(file_info: FileInfo) -> bool:
size, hash_str = await get_file_size_and_hash(file_info.current_local_path)
file_intact = (hash_str == file_info.sha256)
state = 'intact' if file_intact else 'altered'
size_dict[file_info.version][file_info.mode][state] += size
return file_intact
async def register_size_and_hash(file_info: FileInfo) -> None:
global hash_dict_updated
size, hash_str = await get_file_size_and_hash(file_info.current_local_path)
size_dict[file_info.version][file_info.mode]['intact'] += size
size_dict[file_info.version][file_info.mode]['total'] += size
hash_dict[file_info.version][file_info.mode + '_size'] += size
hash_dict[file_info.version][file_info.mode][file_info.relative_path()] = hash_str
hash_dict_updated = True
async def unregister_size_and_hash(file_info: FileInfo) -> None:
global hash_dict_updated
size_dict[file_info.version][file_info.mode]['intact'] = 0
size_dict[file_info.version][file_info.mode]['altered'] = 0
size_dict[file_info.version][file_info.mode]['total'] = 0
hash_dict[file_info.version][file_info.mode + '_size'] = 0
hash_dict[file_info.version][file_info.mode].clear()
hash_dict_updated = True
# Hash High-Level Helpers
async def hash_check_registered(writer: asyncio.StreamWriter, file_info_groups: List[FileInfoGroup], update_freq: int = 50) -> None:
file_info_list = [file_info
for file_info_group in file_info_groups
for file_info in file_info_group.file_info_list]
coroutines = [check_file_hash(file_info) for file_info in file_info_list]
random.shuffle(coroutines)
for i in range(0, len(coroutines), update_freq):
await asyncio.gather(*coroutines[i:i+update_freq])
await send_message(writer)
async def hash_check_unregistered(writer: asyncio.StreamWriter, file_info_groups: List[FileInfoGroup]) -> None:
for file_info_group in file_info_groups:
file_info = FileInfo(
version=file_info_group.version,
mode=file_info_group.mode,
local_root=file_info_group.local_root,
url_root=file_info_group.url_root,
current_local_path=file_info_group.local_root,
current_url=file_info_group.url_root,
sha256='',
)
path_set = {str(fi.current_local_path.resolve())
for fi in file_info_group.file_info_list}
for file_path in file_info_group.local_root.glob('**/*'):
if file_path.is_dir() or str(file_path.resolve()) in path_set:
continue
# assume file is intact
await register_size_and_hash(file_info.resolve_full(file_path))
await send_message(writer)
# Download Helpers
async def download_unregistered_file_all(writer: asyncio.StreamWriter, file_info: FileInfo) -> None:
remote_path = Path(file_info.current_url.replace('file:', '', 1).lstrip('/'))
for file_path in remote_path.glob('**/*'):
if file_path.is_dir():
continue
new_file_info = file_info.resolve(file_path.relative_to(remote_path).as_posix())
new_file_info.current_local_path.parent.mkdir(parents=True, exist_ok=True)
shutil.copy(file_path, new_file_info.current_local_path)
# assume file is intact
await register_size_and_hash(new_file_info)
await send_message(writer)
async def download_unregistered_http_all(
writer: asyncio.StreamWriter,
client: httpx.AsyncClient,
file_info: FileInfo,
retries: int = 5,
depth: int = 3,
) -> None:
if depth == 0:
return
file_info.current_local_path.mkdir(exist_ok=True)
response = await client.get(file_info.current_url)
response.raise_for_status()
page = response.content
bs = BeautifulSoup(page, 'html.parser')
links = bs.find_all('a', href=True)
for link in links:
file_str = str(link['href'])
new_file_info = file_info.resolve(file_str)
if file_str == '../':
continue
if file_str.endswith('/'):
await download_unregistered_http_all(
writer, client, new_file_info, retries=retries, depth=(depth - 1))
continue
for i in range(retries):
try:
async with client.stream('GET', new_file_info.current_url) as stream:
stream.raise_for_status()
async with aiofiles.open(new_file_info.current_local_path, mode='wb') as wb:
async for chunk in stream.aiter_bytes(chunk_size=BUF_SIZE):
await wb.write(chunk)
break
except:
await asyncio.sleep(i + 1)
# assume file is intact
await register_size_and_hash(new_file_info)
await send_message(writer)
async def download_registered_single(writer: asyncio.StreamWriter, client: httpx.AsyncClient, file_info: FileInfo, retries: int = 5) -> None:
if (await check_file_hash(file_info)):
await send_message(writer)
return
for i in range(retries):
try:
async with client.stream('GET', file_info.current_url) as stream:
stream.raise_for_status()
async with aiofiles.open(file_info.current_local_path, mode='wb') as wb:
async for chunk in stream.aiter_bytes(chunk_size=BUF_SIZE):
await wb.write(chunk)
except:
await asyncio.sleep(i + 1)
if (await check_file_hash(file_info)):
break
await send_message(writer)
# Download High-Level Helpers
async def download_unregistered(writer: asyncio.StreamWriter, client: httpx.AsyncClient, file_info_groups: List[FileInfoGroup]) -> None:
for file_info_group in file_info_groups:
file_info = FileInfo(
version=file_info_group.version,
mode=file_info_group.mode,
local_root=file_info_group.local_root,
url_root=file_info_group.url_root,
current_local_path=file_info_group.local_root,
current_url=file_info_group.url_root,
sha256='',
)
if file_info_group.url_root.startswith('http'):
await download_unregistered_http_all(writer, client, file_info)
else:
await download_unregistered_file_all(writer, file_info)
async def download_registered(writer: asyncio.StreamWriter, client: httpx.AsyncClient, file_info_groups: List[FileInfoGroup]) -> None:
coroutines = []
for file_info_group in file_info_groups:
for file_info in file_info_group.file_info_list:
file_info.current_local_path.parent.mkdir(parents=True, exist_ok=True)
coroutines.append(download_registered_single(writer, client, file_info))
random.shuffle(coroutines)
await asyncio.gather(*coroutines)
# Delete High-Level Helpers
async def delete_unregistered(writer: asyncio.StreamWriter, file_info_groups: List[FileInfoGroup]) -> None:
for file_info_group in file_info_groups:
file_info = FileInfo(
version=file_info_group.version,
mode=file_info_group.mode,
local_root=file_info_group.local_root,
url_root=file_info_group.url_root,
current_local_path=file_info_group.local_root,
current_url=file_info_group.url_root,
sha256='',
)
shutil.rmtree(file_info.current_local_path)
await unregister_size_and_hash(file_info)
await send_message(writer)
async def delete_registered(writer: asyncio.StreamWriter, file_info_groups: List[FileInfoGroup]) -> None:
roots = set()
for file_info_group in file_info_groups:
for file_info in file_info_group.file_info_list:
if file_info.current_local_path.parent.is_dir():
roots.add(file_info.current_local_path.parent)
if file_info.current_local_path.is_file():
file_info.current_local_path.unlink()
await send_message(writer)
roots_list: List[Path] = sorted(roots, key=lambda p: len(p.parts), reverse=True)
for root_dir in roots_list:
if not any(root_dir.iterdir()):
root_dir.rmdir()
# Operations
async def hash_check(writer: asyncio.StreamWriter, file_info_groups: List[FileInfoGroup], update_freq: int = 50) -> None:
"""
Behavior:
- get info group, if permanent, then only check hashes of files in
hashes.json (either default or current)
- if not permanent but has hashes registered, check the files in hashes.json
then run a tree search for more files. If file new, add it into the hashes
(assuming intact).
- if not permanent and new, run a tree search for more files. If file new, add
it into the hashes (assuming intact).
"""
registered_groups = [file_info_group
for file_info_group in file_info_groups
if file_info_group.file_info_list]
unregistered_groups = [file_info_group
for file_info_group in file_info_groups
if not file_info_group.is_official]
if registered_groups:
await hash_check_registered(writer, registered_groups, update_freq=update_freq)
if unregistered_groups:
await hash_check_unregistered(writer, unregistered_groups)
async def download(writer: asyncio.StreamWriter, file_info_groups: List[FileInfoGroup], max_connections: int = 5) -> None:
"""
Behavior:
- get info group, if permanent, download checked with hashes.json
- if not permanent but has hashes registered, download checked for the registered
files. Run a recursive http or file download for the others, skipping registered
files. If file new, add it into hashes (assuming intact).
- if not permanent and new, run a recursive http or file download for the others.
If file new, add it into hashes (assuming intact).
"""
registered_groups = [file_info_group
for file_info_group in file_info_groups
if file_info_group.file_info_list]
unregistered_groups = [file_info_group
for file_info_group in file_info_groups
if not file_info_group.is_official]
async with httpx.AsyncClient(limits=httpx.Limits(max_connections=max_connections),
timeout=httpx.Timeout(None)) as client:
if registered_groups:
await download_registered(writer, client, registered_groups)
if unregistered_groups:
await download_unregistered(writer, client, unregistered_groups)
async def delete(writer: asyncio.StreamWriter, file_info_groups: List[FileInfoGroup]) -> None:
"""
Behavior:
- get info group, if permanent, erase files listed in hashes.json, and remove dirs
from the innermost dir to the outermost, checking if they're empty.
- if not permanent but has hashed registered, tree-remove the local directory, erase
the entries in hashes.json
- if not permanent and new, tree-remove the local directory
"""
registered_groups = [file_info_group
for file_info_group in file_info_groups
if file_info_group.is_official]
unregistered_groups = [file_info_group
for file_info_group in file_info_groups
if not file_info_group.is_official]
if registered_groups:
await delete_registered(writer, registered_groups)
if unregistered_groups:
await delete_unregistered(writer, unregistered_groups)
# Main & Helpers
def swapped_path(local_root: str, user_dir: str, cache_version: str, cache_mode: str) -> Path:
current_cache = Path(local_root) / 'FusionFall'
named_cache = Path(local_root) / cache_version
record_path = Path(user_dir) / '.lastver'
if (
cache_mode == 'playable' and
not named_cache.is_dir() and
current_cache.is_dir() and
record_path.is_file() and
cache_version == record_path.read_text(encoding='utf-8')
):
return current_cache
return named_cache
def compile_file_lists(args: Namespace) -> List[FileInfoGroup]:
global hash_dict_updated
with open(Path(args.user_dir) / 'hashes.json') as r:
hash_dict.update(json.load(r))
with open(Path(args.user_dir) / 'versions.json') as r:
versions = json.load(r)['versions']
for version in versions:
if version['name'] not in hash_dict:
hash_dict[version['name']] = {
'playable_size': 0,
'offline_size': 0,
'playable': {},
'offline': {},
}
hash_dict_updated = True
cache_modes = ['offline', 'playable'] if args.cache_mode == 'all' else [args.cache_mode]
cache_versions = list(hash_dict) if args.cache_version == 'all' else [args.cache_version]
file_info_groups = []
for cache_version in cache_versions:
for cache_mode in cache_modes:
file_info_list = []
local_root = args.offline_root if cache_mode == 'offline' else args.playable_root
local_dir = swapped_path(local_root, args.user_dir, cache_version, cache_mode)
url_dir = (
args.cdn_root.rstrip('/') + '/' + cache_version.lstrip('/')
if args.cache_version == 'all' else
args.cdn_root
)
file_info_version = FileInfo(
version=cache_version,
mode=cache_mode,
local_root=local_dir,
url_root=url_dir,
current_local_path=local_dir,
current_url=url_dir,
sha256='',
)
if cache_version not in size_dict:
size_dict[cache_version] = {}
size_dict[cache_version][cache_mode] = {
'intact': 0,
'altered': 0,
'total': hash_dict[cache_version][cache_mode + '_size'],
}
file_info_list.extend([
file_info_version.resolve(rel_path, sha256=file_hash)
for rel_path, file_hash in hash_dict[cache_version][cache_mode].items()
])
file_info_groups.append(FileInfoGroup(
version=cache_version,
mode=cache_mode,
is_official=(cache_version in args.official_caches),
local_root=local_dir,
url_root=url_dir,
file_info_list=file_info_list,
))
return file_info_groups
def write_hash_updates(args: Namespace) -> None:
if not hash_dict_updated:
return
for version_name in hash_dict:
if version_name in args.official_caches:
continue
for cache_mode in ['playable', 'offline']:
hash_dict[version_name][cache_mode] = dict(sorted(hash_dict[version_name][cache_mode].items()))
with open(Path(args.user_dir) / 'hashes.json', 'w') as w:
json.dump(hash_dict, w, indent=4)
async def prep_and_run_coroutine(args: Namespace) -> None:
file_info_groups = compile_file_lists(args)
_, writer = await asyncio.open_connection('localhost', args.port)
coroutines = {
'hash-check': hash_check,
'download': download,
'fix': download,
'delete': delete,
}
try:
await coroutines[args.operation](writer, file_info_groups)
finally:
await send_message(writer)
writer.close()
await writer.wait_closed()
write_hash_updates(args)
def parse_args() -> Namespace:
parser = ArgumentParser('Python executable for tasks relating to OpenFusionClient.')
parser.add_argument('--operation', type=str, required=True, choices=['hash-check', 'download', 'delete'])
parser.add_argument('--playable-root', dest='playable_root', type=str)
parser.add_argument('--offline-root', dest='offline_root', type=str)
parser.add_argument('--user-dir', dest='user_dir', type=str, required=True)
parser.add_argument('--cdn-root', dest='cdn_root', type=str, default='http://cdn.dexlabs.systems/ff/big')
parser.add_argument('--cache-mode', dest='cache_mode', type=str, default='all', choices=['all', 'offline', 'playable'])
parser.add_argument('--cache-version', dest='cache_version', type=str, default='all')
parser.add_argument('--port', type=str, required=True)
parser.add_argument('--official-caches', dest='official_caches', nargs='*', type=str, default=[])
return parser.parse_args()
if __name__ == '__main__':
asyncio.run(prep_and_run_coroutine(parse_args()))

View File

@@ -0,0 +1,33 @@
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(['cache_handler.py'],
pathex=['Z:\\src'],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='cache_handler',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True )

View File

@@ -0,0 +1,4 @@
aiofiles
httpx
beautifulsoup4
pyinstaller==3.5

View File

@@ -1,5 +1,7 @@
{
"autoupdate-check": true,
"cache-swapping": true,
"last-version-initialized": "1.4"
"enable-offline-cache": false,
"verify-offline-cache": false,
"last-version-initialized": "1.6"
}

35695
defaults/hashes.json vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -3,27 +3,18 @@
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, shrink-to-fit=no"
http-equiv="Content-Type"
contentType="text/html; charset=UTF-8"
/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>OpenFusion: Server Selector</title>
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css" />
<link rel="stylesheet" href="assets/css/bootstrap.min.css" />
<link rel="stylesheet" href="assets/fonts/fontawesome-all.min.css" />
<link rel="stylesheet" href="assets/css/openfusion.css" />
<link rel="stylesheet" href="assets/css/styles.css" />
</head>
<body
style="
background: rgb(0, 0, 0);
width: 100%;
height: 100%;
margin: 0;
padding: 0;
clear: both;
"
onresize="onResize()"
>
<body onresize="onResize()">
<section id="of-serverselector">
<div class="container" id="serverselector-container">
<div class="row text-center mt-3" id="of-logoheader">
@@ -117,6 +108,30 @@
</button>
</div>
<div class="col-4 d-inline-flex justify-content-end">
<button
class="btn btn-primary mr-1"
data-toggle="modal"
data-bs-tooltip=""
data-placement="bottom"
id="of-editconfig-button"
type="button"
title="Edit Configuration"
data-target="#of-editconfigmodal"
>
<i class="fas fa-cog"></i>
</button>
<button
class="btn btn-primary mr-1"
data-toggle="modal"
data-bs-tooltip=""
data-placement="bottom"
id="of-editcache-button"
type="button"
title="Edit Game Builds"
data-target="#of-editcacheconfigmodal"
>
<i class="fas fa-database"></i>
</button>
<button
class="btn btn-primary disabled"
id="of-connect-button"
@@ -151,7 +166,12 @@
</button>
</div>
<div class="modal-body">
<p id="of-aboutversionnumber" class="text-monospace">APP_VERSION_NUMBER</p>
<p
id="of-aboutversionnumber"
class="text-monospace"
>
APP_VERSION_NUMBER
</p>
<p>
©2020-2023 OpenFusion Contributors<br />OpenFusion
is licensed under MIT.<br />
@@ -163,6 +183,13 @@
data-target="#of-restoreserversmodal"
>Reset to Default Servers</a
>
<a
href="#of-restoreversionsmodal"
onclick="$('#of-aboutmodal').modal('toggle')"
data-toggle="modal"
data-target="#of-restoreversionsmodal"
>Reset to Default Game Builds</a
>
</div>
<div class="modal-footer">
<div class="row flex-fill">
@@ -173,7 +200,7 @@
data-bs-tooltip=""
type="button"
title="Github Page"
onclick="window.open(&#39;https://github.com/OpenFusionProject/OpenFusion&#39;,&#39;_blank&#39;);"
onclick="window.open('https://github.com/OpenFusionProject/OpenFusion','_blank');"
>
<i
class="fab fa-github"
@@ -185,7 +212,7 @@
data-bs-tooltip=""
type="button"
title="Discord Chat"
onclick="window.open(&#39;https://discord.gg/DYavckB&#39;,&#39;_blank&#39;);"
onclick="window.open('https://discord.gg/DYavckB','_blank');"
>
<i
class="fab fa-discord"
@@ -242,6 +269,7 @@
required=""
minlength="1"
maxlength="70"
oninput="validateServerSave('add')"
/><label for="addserver-ipinput"
>Server IP</label
><input
@@ -249,8 +277,10 @@
type="text"
id="addserver-ipinput"
placeholder="127.0.0.1:23000"
value="127.0.0.1:23000"
required=""
pattern="^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]):[0-9]+$"
pattern="^([-a-zA-Z0-9]+\.)+[-a-zA-Z0-9]+:[0-9]+$"
oninput="validateServerSave('add')"
/><label for="addserver-versionselect"
>Game Version:&nbsp;</label
><select
@@ -258,6 +288,7 @@
id="addserver-versionselect"
required=""
style="margin-left: -5px"
oninput="validateServerSave('add')"
></select>
</form>
</div>
@@ -314,7 +345,8 @@
required=""
minlength="1"
maxlength="70"
/><label for="addserver-ipinput"
oninput="validateServerSave('edit')"
/><label for="editserver-ipinput"
>Server IP</label
><input
class="form-control form-row w-75"
@@ -322,7 +354,8 @@
id="editserver-ipinput"
placeholder="127.0.0.1:23000"
required=""
pattern="^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]):[0-9]+$"
pattern="^([-a-zA-Z0-9]+\.)+[-a-zA-Z0-9]+:[0-9]+$"
oninput="validateServerSave('edit')"
/><label for="editserver-versionselect"
>Game Version:&nbsp;</label
><select
@@ -330,6 +363,7 @@
id="editserver-versionselect"
required=""
style="margin-left: -5px"
oninput="validateServerSave('edit')"
></select>
</form>
</div>
@@ -343,7 +377,7 @@
Cancel</button
><button
class="btn btn-primary border rounded border-primary btn-success border-success"
id="addserver-savebutton"
id="editserver-savebutton"
type="submit"
data-dismiss="modal"
form="editserver-form"
@@ -436,7 +470,7 @@
Cancel</button
><button
class="btn btn-primary border rounded border-primary btn-danger border-danger"
id="deleteserver-button"
id="restoreservers-button"
type="button"
data-dismiss="modal"
onclick="restoreDefaultServers();"
@@ -447,6 +481,401 @@
</div>
</div>
</div>
<div
class="modal fade"
role="dialog"
tabindex="-1"
id="of-editconfigmodal"
>
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Edit Configuration</h4>
<button
type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<form id="editconfig-form" class="needs-validation">
<label for="editconfig-autoupdate"
>Automatically update the client:</label
><input
class="form-control form-row w-75"
type="checkbox"
id="editconfig-autoupdate"
/>
<label for="editconfig-cacheswapping"
>Swap game caches to avoid unnecessary downloads:</label
><input
class="form-control form-row w-75"
type="checkbox"
id="editconfig-cacheswapping"
/>
<label for="editconfig-enableofflinecache"
>Use offline caches when they are available:</label
><input
class="form-control form-row w-75"
type="checkbox"
id="editconfig-enableofflinecache"
/>
<label for="editconfig-verifyofflinecache"
>Verify offline caches every time they are loaded:</label
><input
class="form-control form-row w-75"
type="checkbox"
id="editconfig-verifyofflinecache"
/>
<label for="editconfig-offlinecachelocation"
>Select Offline Cache Location:</label
><input
class="form-control form-row w-75"
id="editconfig-offlinecachelocation"
type="text"
oninput="validateCacheLocation()"
/>
</form>
</div>
<div class="modal-footer">
<button
class="btn btn-primary border rounded border-primary btn-danger border-danger"
id="editconfig-cancel"
type="button"
data-dismiss="modal"
>
Cancel</button
><button
class="btn btn-primary border rounded border-primary btn-success border-success"
id="editconfig-savebutton"
type="submit"
data-dismiss="modal"
form="editconfig-form"
onclick="editConfig();"
>
Save
</button>
</div>
</div>
</div>
</div>
<div
class="modal fade"
role="dialog"
tabindex="-1"
id="of-editcacheconfigmodal"
>
<div class="modal-dialog modal-dialog-centered modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Edit Game Builds</h4>
<button
type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<div
class="row row-cols-2 d-xl-flex justify-content-center justify-content-xl-start"
id="of-versionbuttons"
style="padding-bottom: 16px"
>
<div
class="col-4 text-left d-inline-flex justify-content-xl-start"
id="cache-buttons"
>
<button
class="btn btn-success mr-1"
data-toggle="modal"
data-bs-tooltip=""
data-placement="bottom"
id="of-addversion-button"
type="button"
title="Add Version"
data-target="#of-addversionmodal"
onclick="deselectVersion()"
>
<i class="fas fa-plus"></i>
</button>
<button
class="btn btn-primary mr-1 disabled"
data-toggle="modal"
data-bs-tooltip=""
data-placement="bottom"
id="of-editversion-button"
type="button"
title="Edit Version"
data-target="#of-editversionmodal"
disabled=""
>
<i class="fas fa-edit"></i>
</button>
<button
class="btn btn-danger mr-1 disabled"
data-toggle="modal"
data-bs-tooltip=""
data-placement="bottom"
id="of-deleteversion-button"
type="button"
title="Delete Version"
data-target="#of-deleteversionmodal"
disabled=""
>
<i class="fas fa-trash-alt"></i>
</button>
</div>
</div>
<div
class="table-responsive text-center border rounded border-primary"
id="cache-table"
>
<table class="table table-striped table-hover mb-0">
<thead>
<tr>
<th>Game Version</th>
<th>Game Cache</th>
<th>Offline Cache</th>
</tr>
</thead>
<tbody id="cache-tablebody">
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div
class="modal fade"
role="dialog"
tabindex="-1"
id="of-addversionmodal"
>
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Add Server</h4>
<button
type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<form id="addversion-form" class="needs-validation">
<label for="addversion-nameinput"
>Version Name</label
><input
class="form-control form-row w-75"
type="text"
id="addversion-nameinput"
placeholder="custom-build-000"
required=""
pattern="^[-a-zA-Z0-9_]{1,70}$"
oninput="validateVersionSave('add')"
/><label for="addversion-urlinput"
>Version URL</label
><input
class="form-control form-row w-75"
type="text"
id="addversion-urlinput"
placeholder="http://cdn.dexlabs.systems/custom-build-000/"
required=""
pattern="^(https?|file):\/\/\/?([-a-zA-Z0-9@:%._\+~#= ]{1,256}\/){1,64}$"
oninput="validateVersionSave('add')"
/>
</form>
</div>
<div class="modal-footer">
<button
class="btn btn-primary border rounded border-primary btn-danger border-danger"
id="addversion-cancel"
type="button"
data-dismiss="modal"
>
Cancel</button
><button
class="btn btn-primary border rounded border-primary btn-success border-success"
id="addversion-savebutton"
type="submit"
data-dismiss="modal"
form="addversion-form"
onclick="addVersion();"
>
Save
</button>
</div>
</div>
</div>
</div>
<div
class="modal fade"
role="dialog"
tabindex="-1"
id="of-editversionmodal"
>
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Edit Version</h4>
<button
type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<form id="editversion-form" class="needs-validation">
<label for="editversion-nameinput"
>Version Name</label
><input
class="form-control form-row w-75"
type="text"
id="editversion-nameinput"
placeholder="custom-build-000"
required=""
pattern="^[-a-zA-Z0-9_]{1,70}$"
oninput="validateVersionSave('edit')"
/><label for="editversion-urlinput"
>Version URL</label
><input
class="form-control form-row w-75"
type="text"
id="editversion-urlinput"
placeholder="http://cdn.dexlabs.systems/custom-build-000/"
required=""
pattern="^(https?|file):\/\/\/?([-a-zA-Z0-9@:%._\+~#= ]{1,256}\/){1,64}$"
oninput="validateVersionSave('edit')"
/>
</form>
</div>
<div class="modal-footer">
<button
class="btn btn-primary border rounded border-primary btn-danger border-danger"
id="editversion-cancel"
type="button"
data-dismiss="modal"
>
Cancel</button
><button
class="btn btn-primary border rounded border-primary btn-success border-success"
id="editversion-savebutton"
type="submit"
data-dismiss="modal"
form="editversion-form"
onclick="editVersion();"
>
Save
</button>
</div>
</div>
</div>
</div>
<div
class="modal fade"
role="dialog"
tabindex="-1"
id="of-deleteversionmodal"
>
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Are you sure?</h4>
<button
type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<p class="lead">
Do you really want to delete<br />"<a
id="deleteversion-versionname"
>VERSION_NAME</a
>"?<br /><br />You could always re-add it later.
</p>
</div>
<div class="modal-footer">
<button
class="btn btn-primary border rounded border-primary"
type="button"
data-dismiss="modal"
>
Cancel</button
><button
class="btn btn-primary border rounded border-primary btn-danger border-danger"
id="deleteversion-button"
type="button"
data-dismiss="modal"
onclick="deleteVersion();"
>
Yes, Delete
</button>
</div>
</div>
</div>
</div>
<div
class="modal fade"
role="dialog"
tabindex="-1"
id="of-restoreversionsmodal"
>
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Are you sure?</h4>
<button
type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<p class="lead">
Do you really want to restore the default
versions?
</p>
</div>
<div class="modal-footer">
<button
class="btn btn-primary border rounded border-primary"
type="button"
data-dismiss="modal"
>
Cancel</button
><button
class="btn btn-primary border rounded border-primary btn-danger border-danger"
id="restoreversions-button"
type="button"
data-dismiss="modal"
onclick="restoreDefaultVersions();"
>
Yes, Restore
</button>
</div>
</div>
</div>
</div>
<div id="of-versionnumberdiv">
<a
id="of-versionnumber"
@@ -459,28 +888,18 @@
</div>
</section>
<section>
<div
id="client"
style="
width: 100%;
height: 100%;
margin: 0;
padding: 0;
background: #000;
clear: both;
"
></div>
<div id="client"></div>
</section>
<script
src="assets/js/jquery.min.js"
onload="window.$ = window.jQuery = module.exports;"
></script>
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
<script src="assets/js/bootstrap.min.js"></script>
<script src="assets/js/bs-init.js"></script>
<script src="assets/js/uuidv4.min.js"></script>
<script src="assets/js/serverselector.js"></script>
<script src="assets/js/gameclient.js"></script>
<script src="assets/js/server-selector.js"></script>
<script src="assets/js/game-client.js"></script>
<script src="assets/js/snowflakes.min.js"></script>
<script src="assets/js/eastereggs.js"></script>
<script src="assets/js/easter-eggs.js"></script>
</body>
</html>

186
index.js
View File

@@ -1,108 +1,59 @@
var app = require("app"); // Module to control application life.
var ipc = require("ipc");
var fs = require("fs-extra");
var os = require("os");
var dialog = require("dialog");
var BrowserWindow = require("browser-window");
var fs = require("fs-extra");
var ipc = require("ipc");
var os = require("os");
var path = require("path");
var BrowserWindow = require("browser-window");
var mainWindow = null;
app.commandLine.appendSwitch("--enable-npapi");
function verifyUnity() {
var dllpath =
app.getPath("appData") +
"\\..\\LocalLow\\Unity\\WebPlayer\\player\\fusion-2.x.x\\webplayer_win.dll";
if (fs.existsSync(dllpath)) {
var buff = fs.readFileSync(dllpath);
var hash = require("crypto")
.createHash("md5")
.update(buff)
.digest("hex");
if (hash == "e5028405b4483de9e5e5fe9cd5f1e98f") {
return true;
}
}
return false;
var unityHomeDir = path.join(__dirname, "../../WebPlayer");
// If running in non-packaged / development mode, this dir will be slightly different
if (process.env.npm_node_execpath) {
unityHomeDir = path.join(app.getAppPath(), "/build/WebPlayer");
}
function installUnity(callback) {
var utilsdir = __dirname + "\\..\\..\\utils";
process.env["UNITY_HOME_DIR"] = unityHomeDir;
process.env["UNITY_DISABLE_PLUGIN_UPDATES"] = "yes";
// if running in non-packaged / development mode, this dir will be slightly different
if (process.env.npm_node_execpath) {
utilsdir = app.getAppPath() + "\\build\\utils";
}
app.commandLine.appendSwitch("enable-npapi");
app.commandLine.appendSwitch(
"load-plugin",
path.join(unityHomeDir, "/loader/npUnity3D32.dll")
);
app.commandLine.appendSwitch("no-proxy-server");
// run the installer silently
var child = require("child_process").spawn(
utilsdir + "\\UnityWebPlayer.exe",
["/quiet", "/S"]
);
child.on("exit", function () {
// overwrite 3.5.2 loader/player with FF's custom version
var dstfolder =
app.getPath("appData") + "..\\LocalLow\\Unity\\WebPlayer";
fs.copySync(utilsdir + "\\WebPlayer", dstfolder, {
clobber: true,
});
// avoids error reporter popping up when closing Electron
fs.removeSync(dstfolder + "\\UnityBugReporter.exe");
console.log("Unity Web Player installed successfully.");
callback();
});
}
var userData = app.getPath("userData");
var configPath = path.join(userData, "config.json");
var serversPath = path.join(userData, "servers.json");
var versionsPath = path.join(userData, "versions.json");
var hashPath = path.join(userData, "hashes.json");
function initialSetup(firstTime) {
// Display a small window to inform the user that the app is working
setupWindow = new BrowserWindow({
width: 275,
height: 450,
resizable: false,
center: true,
frame: false,
});
setupWindow.loadUrl("file://" + __dirname + "/initialsetup.html");
installUnity(function () {
if (!firstTime) {
// migration from pre-1.4
// Back everything up, just in case
fs.copySync(
app.getPath("userData") + "\\config.json",
app.getPath("userData") + "\\config.json.bak"
);
fs.copySync(
app.getPath("userData") + "\\servers.json",
app.getPath("userData") + "\\servers.json.bak"
);
fs.copySync(
app.getPath("userData") + "\\versions.json",
app.getPath("userData") + "\\versions.json.bak"
);
} else {
// first-time setup
// Copy default servers
fs.copySync(
__dirname + "\\defaults\\servers.json",
app.getPath("userData") + "\\servers.json"
);
}
// Copy default versions and config
if (!firstTime) {
// Migration from pre-1.6
// Back everything up, just in case
fs.copySync(configPath, configPath + ".bak");
fs.copySync(serversPath, serversPath + ".bak");
fs.copySync(versionsPath, versionsPath + ".bak");
fs.copySync(hashPath, hashPath + ".bak");
} else {
// First-time setup
// Copy default servers
fs.copySync(
__dirname + "\\defaults\\versions.json",
app.getPath("userData") + "\\versions.json"
);
fs.copySync(
__dirname + "\\defaults\\config.json",
app.getPath("userData") + "\\config.json"
path.join(__dirname, "/defaults/servers.json"),
serversPath
);
}
console.log("JSON files copied.");
setupWindow.destroy();
showMainWindow();
});
// Copy default versions and config
fs.copySync(path.join(__dirname, "/defaults/versions.json"), versionsPath);
fs.copySync(path.join(__dirname, "/defaults/config.json"), configPath);
fs.copySync(path.join(__dirname, "/defaults/hashes.json"), hashPath);
console.log("JSON files copied.");
showMainWindow();
}
ipc.on("exit", function (id) {
@@ -116,50 +67,50 @@ app.on("window-all-closed", function () {
app.on("ready", function () {
// Check just in case the user forgot to extract the zip.
zip_check = app.getPath("exe").includes(os.tmpdir());
if (zip_check) {
errormsg =
zipCheck = app.getPath("exe").includes(os.tmpdir());
if (zipCheck) {
var errorMessage =
"It has been detected that OpenFusionClient is running from the TEMP folder.\n\n" +
"Please extract the entire Client folder to a location of your choice before starting OpenFusionClient.";
dialog.showErrorBox("Error!", errormsg);
dialog.showErrorBox("Error!", errorMessage);
return;
}
// Create the browser window.
mainWindow = new BrowserWindow({
width: 1280,
height: 720,
show: false,
"web-preferences": { plugins: true },
"web-preferences": {
plugins: true,
},
});
mainWindow.setMinimumSize(640, 480);
// Check for first run
var configPath = app.getPath("userData") + "\\config.json";
try {
if (!fs.existsSync(configPath)) {
console.log("Config file not found. Running initial setup.");
initialSetup(true);
} else {
var config = fs.readJsonSync(configPath);
if (!config["last-version-initialized"]) {
console.log("Pre-1.4 config detected. Running migration.");
if (config["last-version-initialized"] !== "1.6") {
console.log("Pre-1.6 config detected. Running migration.");
initialSetup(false);
} else {
if (verifyUnity()) {
showMainWindow();
} else {
installUnity(showMainWindow);
}
showMainWindow();
}
}
} catch (ex) {
console.log("An error occurred while checking for the config");
dialog.showErrorBox(
"Error!",
"An error occurred while checking for the config. Make sure you have sufficent permissions."
);
app.quit();
}
// Makes it so external links are opened in the system browser, not Electron
mainWindow.webContents.on("new-window", function (e, url) {
e.preventDefault();
mainWindow.webContents.on("new-window", function (event, url) {
event.preventDefault();
require("shell").openExternal(url);
});
@@ -180,22 +131,27 @@ function showMainWindow() {
mainWindow.webContents.executeJavaScript("loadConfig();");
mainWindow.webContents.executeJavaScript("loadGameVersions();");
mainWindow.webContents.executeJavaScript("loadServerList();");
mainWindow.webContents.executeJavaScript("loadCacheList();");
});
mainWindow.webContents.on("plugin-crashed", function () {
console.log("Unity Web Player crashed.");
var errorMessage =
"Unity Web Player has crashed - please re-open the application.\n" +
"If this error persists, please read the FAQ or ask for support in our Discord server.";
dialog.showErrorBox("Error!", errorMessage);
mainWindow.destroy();
app.quit();
});
mainWindow.webContents.on("will-navigate", function (evt, url) {
evt.preventDefault();
// TODO: showMessageBox rather than showErrorBox?
mainWindow.webContents.on("will-navigate", function (event, url) {
event.preventDefault();
switch (url) {
case "https://audience.fusionfall.com/ff/regWizard.do?_flowId=fusionfall-registration-flow":
errormsg =
var errorMessage =
"The register page is currently unimplemented.\n\n" +
'You can still create an account: type your desired username and password into the provided boxes and click "Log In". ' +
"Your account will then be automatically created on the server. \nBe sure to remember these details!";
dialog.showErrorBox("Sorry!", errormsg);
dialog.showErrorBox("Sorry!", errorMessage);
break;
case "https://audience.fusionfall.com/ff/login.do":
dialog.showErrorBox(
@@ -207,7 +163,7 @@ function showMainWindow() {
require("shell").openExternal("https://discord.gg/DYavckB");
break;
default:
mainWindow.webContents.loadURL(url);
mainWindow.loadUrl(url);
}
});
}

View File

@@ -1,40 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>OpenFusion: Initial Setup</title>
</head>
<body
style="
overflow: hidden;
background-color: #000;
user-select: none;
-webkit-user-select: none;
"
>
<center>
<div>
<img src="assets/img/of-3.png" width="256" />
<div>
<img src="assets/img/spinner.gif" width="50px" />
</div>
<div style="margin-top: 15px">
<p
style="
text-shadow: 1px 1px 8px #4349c4;
color: #4a76b7;
font-size: 18px;
font-family: -apple-system, BlinkMacSystemFont,
'Segoe UI', Roboto, 'Helvetica Neue', Arial,
'Noto Sans', sans-serif, 'Apple Color Emoji',
'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji';
"
>
OpenFusion is setting up...<br />Please wait - this
should take <br />less than a minute.
</p>
</div>
</div>
</center>
</body>
</html>

BIN
lib/cache_handler.exe vendored Executable file

Binary file not shown.

84
package-lock.json generated
View File

@@ -1,16 +1,16 @@
{
"name": "OpenFusionClient",
"version": "1.4.0",
"version": "1.5.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "OpenFusionClient",
"version": "1.4.0",
"version": "1.5.1",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"fs-extra": "^0.30.0"
"fs-extra": "2.0.0"
},
"devDependencies": {
"electron-builder": "^22.14.13",
@@ -746,7 +746,8 @@
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
"node_modules/base64-js": {
"version": "1.5.1",
@@ -830,6 +831,7 @@
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -1333,7 +1335,8 @@
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
},
"node_modules/concat-stream": {
"version": "1.6.2",
@@ -2021,15 +2024,12 @@
}
},
"node_modules/fs-extra": {
"version": "0.30.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz",
"integrity": "sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A=",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-2.0.0.tgz",
"integrity": "sha512-DERmRq6uGnu7I4uFJiWQBe5pYy67v2oKowEi8jYA/52u/ZO9xXBP2HAGacD9Nus0UT/WhJFZTq8cWbxZqOHVUg==",
"dependencies": {
"graceful-fs": "^4.1.2",
"jsonfile": "^2.1.0",
"klaw": "^1.0.0",
"path-is-absolute": "^1.0.0",
"rimraf": "^2.2.8"
"jsonfile": "^2.1.0"
}
},
"node_modules/fs-extra/node_modules/jsonfile": {
@@ -2095,6 +2095,7 @@
"version": "6.0.4",
"resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz",
"integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=",
"dev": true,
"dependencies": {
"inflight": "^1.0.4",
"inherits": "2",
@@ -2406,6 +2407,7 @@
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"dependencies": {
"once": "^1.3.0",
"wrappy": "1"
@@ -2414,7 +2416,8 @@
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
"node_modules/ini": {
"version": "1.3.8",
@@ -2711,14 +2714,6 @@
"json-buffer": "3.0.0"
}
},
"node_modules/klaw": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz",
"integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=",
"optionalDependencies": {
"graceful-fs": "^4.1.9"
}
},
"node_modules/klaw-sync": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz",
@@ -2915,6 +2910,7 @@
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"dependencies": {
"brace-expansion": "^1.1.7"
},
@@ -3052,6 +3048,7 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"dependencies": {
"wrappy": "1"
}
@@ -3215,6 +3212,7 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -3603,6 +3601,7 @@
"version": "2.4.5",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz",
"integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=",
"dev": true,
"dependencies": {
"glob": "^6.0.1"
},
@@ -4379,7 +4378,8 @@
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
},
"node_modules/write-file-atomic": {
"version": "3.0.3",
@@ -5049,7 +5049,8 @@
"balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
"base64-js": {
"version": "1.5.1",
@@ -5109,6 +5110,7 @@
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -5485,7 +5487,8 @@
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
},
"concat-stream": {
"version": "1.6.2",
@@ -6054,15 +6057,12 @@
}
},
"fs-extra": {
"version": "0.30.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz",
"integrity": "sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A=",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-2.0.0.tgz",
"integrity": "sha512-DERmRq6uGnu7I4uFJiWQBe5pYy67v2oKowEi8jYA/52u/ZO9xXBP2HAGacD9Nus0UT/WhJFZTq8cWbxZqOHVUg==",
"requires": {
"graceful-fs": "^4.1.2",
"jsonfile": "^2.1.0",
"klaw": "^1.0.0",
"path-is-absolute": "^1.0.0",
"rimraf": "^2.2.8"
"jsonfile": "^2.1.0"
},
"dependencies": {
"jsonfile": {
@@ -6121,6 +6121,7 @@
"version": "6.0.4",
"resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz",
"integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=",
"dev": true,
"requires": {
"inflight": "^1.0.4",
"inherits": "2",
@@ -6349,6 +6350,7 @@
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"requires": {
"once": "^1.3.0",
"wrappy": "1"
@@ -6357,7 +6359,8 @@
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
"ini": {
"version": "1.3.8",
@@ -6580,14 +6583,6 @@
"json-buffer": "3.0.0"
}
},
"klaw": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz",
"integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=",
"requires": {
"graceful-fs": "^4.1.9"
}
},
"klaw-sync": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz",
@@ -6738,6 +6733,7 @@
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@@ -6848,6 +6844,7 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"requires": {
"wrappy": "1"
}
@@ -6971,7 +6968,8 @@
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true
},
"path-key": {
"version": "2.0.1",
@@ -7272,6 +7270,7 @@
"version": "2.4.5",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz",
"integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=",
"dev": true,
"requires": {
"glob": "^6.0.1"
}
@@ -7882,7 +7881,8 @@
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
},
"write-file-atomic": {
"version": "3.0.3",

View File

@@ -1,6 +1,6 @@
{
"name": "OpenFusionClient",
"version": "1.4.1",
"version": "1.5.1",
"description": "OpenFusionClient",
"main": "index.js",
"scripts": {
@@ -20,7 +20,7 @@
},
"repository": {
"type": "git",
"url": "https://github.com/OpenFusionProject/Client.git"
"url": "https://github.com/OpenFusionProject/OpenFusionClient.git"
},
"build": {
"appId": "xyz.openfusion.client",
@@ -63,13 +63,17 @@
"extraFiles": [
"LICENSE.md",
{
"from": "build/utils",
"to": "utils"
"from": "build/OpenFusionClient.exe.manifest",
"to": "OpenFusionClient.exe.manifest"
},
{
"from": "build/WebPlayer",
"to": "WebPlayer"
}
],
"afterPack": "./build/afterpack.js"
"afterPack": "./build/after-pack.js"
},
"dependencies": {
"fs-extra": "^0.30.0"
"fs-extra": "2.0.0"
}
}