Client/index.js

378 lines
13 KiB
JavaScript
Raw Normal View History

var app = require("app"); // Module to control application life.
var dialog = require("dialog");
var fs = require("fs-extra");
var ipc = require("ipc");
var os = require("os");
var path = require("path");
2023-09-22 23:18:03 +00:00
var url = require("url");
var http = require("http");
var async = require("async");
2023-09-23 21:48:01 +00:00
var createHash = require("crypto").createHash;
var BrowserWindow = require("browser-window");
var mainWindow = null;
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");
}
process.env["UNITY_HOME_DIR"] = unityHomeDir;
process.env["UNITY_DISABLE_PLUGIN_UPDATES"] = "yes";
2023-09-21 15:01:55 +00:00
process.env["WINE_LARGE_ADDRESS_AWARE"] = "1";
app.commandLine.appendSwitch("enable-npapi");
app.commandLine.appendSwitch(
"load-plugin",
path.join(unityHomeDir, "/loader/npUnity3D32.dll")
);
app.commandLine.appendSwitch("no-proxy-server");
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, "hash.txt");
2021-09-18 21:59:15 +00:00
function initialSetup(firstTime) {
if (!firstTime) {
// Migration from pre-1.4
// 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(
path.join(__dirname, "/defaults/servers.json"),
serversPath
);
}
// 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/hash.txt"), hashPath);
console.log("JSON files copied.");
showMainWindow();
}
ipc.on("exit", function (id) {
mainWindow.destroy();
});
// Quit when all windows are closed.
app.on("window-all-closed", function () {
if (process.platform != "darwin") app.quit();
});
app.on("ready", function () {
// Check just in case the user forgot to extract the zip.
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!", errorMessage);
return;
}
// Create the browser window.
mainWindow = new BrowserWindow({
width: 1280,
height: 720,
show: false,
"web-preferences": {
plugins: true,
2023-09-22 23:18:03 +00:00
nodeIntegration: true,
},
});
mainWindow.setMinimumSize(640, 480);
// Check for first run
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.");
initialSetup(false);
} else {
showMainWindow();
}
}
} catch (ex) {
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 (event, url) {
event.preventDefault();
require("shell").openExternal(url);
});
mainWindow.on("closed", function () {
mainWindow = null;
});
2023-09-23 21:48:01 +00:00
ipc.on("download-files", function (event, arg) {
mainWindow.webContents.send("storage-loading-start", {
cacheMode: arg.cacheMode,
versionString: arg.versionString,
resetIntactSize: false,
});
2023-09-23 21:48:01 +00:00
downloadFiles(
arg.cdnDir,
2023-09-23 21:48:01 +00:00
arg.localDir,
arg.hashes,
function (sizes) {
mainWindow.webContents.send("storage-label-update", {
cacheMode: arg.cacheMode,
2023-09-23 21:48:01 +00:00
versionString: arg.versionString,
sizes: sizes,
2023-09-23 21:48:01 +00:00
});
},
function () {
mainWindow.webContents.send("download-complete", {
cacheMode: arg.cacheMode,
versionString: arg.versionString,
});
},
function (err) {
dialog.showErrorBox("Error!", "Download was unsuccessful:\n" + err);
2023-09-23 21:48:01 +00:00
}
);
});
ipc.on("hash-check", function (event, arg) {
mainWindow.webContents.send("storage-loading-start", {
2023-09-23 21:48:01 +00:00
cacheMode: arg.cacheMode,
versionString: arg.versionString,
resetIntactSize: true,
2023-09-23 21:48:01 +00:00
});
checkHashes(
arg.localDir,
arg.hashes,
function (sizes) {
mainWindow.webContents.send("storage-label-update", {
cacheMode: arg.cacheMode,
versionString: arg.versionString,
sizes: sizes,
});
},
function () {
mainWindow.webContents.send("hash-check-complete", {
cacheMode: arg.cacheMode,
versionString: arg.versionString,
});
},
function (err) {
dialog.showErrorBox("Error!", "Could not verify file integrity:\n" + err);
}
);
2023-09-23 21:48:01 +00:00
});
});
function showMainWindow() {
// Load the index.html of the app.
mainWindow.loadUrl("file://" + __dirname + "/index.html");
// Reduces white flash when opening the program
mainWindow.webContents.on("did-finish-load", function () {
mainWindow.webContents.executeJavaScript("setAppVersionText();");
mainWindow.show();
// everything's loaded, tell the renderer process to do its thing
mainWindow.webContents.executeJavaScript("loadConfig();");
mainWindow.webContents.executeJavaScript("loadGameVersions();");
mainWindow.webContents.executeJavaScript("loadServerList();");
mainWindow.webContents.executeJavaScript("loadCacheList();");
});
mainWindow.webContents.on("plugin-crashed", function () {
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 (event, url) {
event.preventDefault();
switch (url) {
case "https://audience.fusionfall.com/ff/regWizard.do?_flowId=fusionfall-registration-flow":
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!", errorMessage);
break;
case "https://audience.fusionfall.com/ff/login.do":
dialog.showErrorBox(
"Sorry!",
"Account management is not available."
);
break;
case "http://forums.fusionfall.com/":
require("shell").openExternal("https://discord.gg/DYavckB");
break;
default:
mainWindow.loadUrl(url);
}
});
}
2023-09-22 23:18:03 +00:00
function downloadFile(cdnDir, localDir, relativePath, fileHash, callback, updateCallback) {
var nginxUrl = path.dirname(cdnDir) + "/" + relativePath;
2023-09-22 23:18:03 +00:00
var localPath = path.join(localDir, relativePath);
// Create directories if they don't exist
var dirName = path.dirname(localPath);
fs.ensureDir(dirName, function (createDirErr) {
if (createDirErr) {
console.log("Could not create path " + dirName + ": " + createDirErr);
callback(createDirErr);
return;
2023-09-22 23:18:03 +00:00
}
// HTTP request to download the file
var fileStream = fs.createWriteStream(localPath);
var urlParse = url.parse(nginxUrl);
var client = http.createClient(80, urlParse.hostname);
var options = {
method: "GET",
url: urlParse.hostname,
port: 80,
path: urlParse.path,
headers: {
"Host": urlParse.hostname,
"Content-Type": "application/octet-stream",
"Referer": cdnDir,
"Connection": "keep-alive",
}
};
2023-09-22 23:18:03 +00:00
var request = client.request("GET", urlParse.path, options);
2023-09-22 23:18:03 +00:00
request.on("response", function (response) {
response.pipe(fileStream);
2023-09-22 23:18:03 +00:00
// When the download is complete, invoke the callback
response.on("end", function() {
fileStream.end();
checkHash(localDir, relativePath, fileHash, callback, updateCallback);
});
2023-09-22 23:18:03 +00:00
// Handle errors
response.on("error", callback);
});
2023-09-22 23:18:03 +00:00
// Handle HTTP errors
request.on("error", callback);
2023-09-22 23:18:03 +00:00
request.end();
});
2023-09-22 23:18:03 +00:00
}
// Function to download multiple files in parallel
function downloadFiles(cdnDir, localDir, hashes, updateCallback, allDoneCallback, errorCallback) {
2023-09-22 23:18:03 +00:00
async.eachLimit(
Object.keys(hashes),
2023-09-22 23:18:03 +00:00
5, // Number of parallel downloads
2023-09-23 21:48:01 +00:00
function (relativePath, callback) {
downloadFile(cdnDir, localDir, relativePath, hashes[relativePath], callback, updateCallback);
2023-09-22 23:18:03 +00:00
},
2023-09-23 21:48:01 +00:00
function (err) {
2023-09-22 23:18:03 +00:00
if (err) {
console.error("Download failed: " + err);
errorCallback(err);
2023-09-22 23:18:03 +00:00
} else {
console.log("All files downloaded successfully.");
allDoneCallback();
}
}
);
}
2023-09-23 21:48:01 +00:00
function checkHash(localDir, relativePath, fileHash, callback, updateCallback) {
var localPath = path.join(localDir, relativePath);
var chunkSize = 1 << 16;
var totalCount = 0;
var buff = new Buffer(chunkSize);
var hash = createHash("sha256");
fs.open(localPath, "r", function (openErr, file) {
if (openErr) {
if (openErr.code === "ENOENT") {
callback();
} else {
2023-09-23 21:48:01 +00:00
console.log("Error opening file for hash check: " + openErr);
callback(openErr);
2023-09-23 21:48:01 +00:00
}
return;
}
var updater;
var reader = function () {
fs.read(file, buff, 0, chunkSize, null, updater);
};
updater = function (readErr, readSize) {
if (readErr) {
console.log("Error reading file for hash check: " + readErr);
callback(readErr);
2023-09-23 21:48:01 +00:00
} else if (readSize > 0) {
hash.update(buff.slice(0, readSize));
totalCount += readSize;
reader();
} else {
var state = (fileHash === hash.digest(encoding="hex")) ? "intact" : "altered";
var sizes = { intact: 0, altered: 0 };
sizes[state] = totalCount;
fs.close(file, function (fileCloseErr) {
if (fileCloseErr) {
console.log("Error closing file for hash check: " + fileCloseErr);
callback(fileCloseErr);
2023-09-23 21:48:01 +00:00
} else {
updateCallback(sizes);
callback();
2023-09-23 21:48:01 +00:00
}
});
}
};
reader();
});
}
function checkHashes(localDir, hashes, updateCallback, allDoneCallback, errorCallback) {
2023-09-23 21:48:01 +00:00
async.eachLimit(
Object.keys(hashes),
20,
function (relativePath, callback) {
checkHash(localDir, relativePath, hashes[relativePath], callback, updateCallback);
2023-09-22 23:18:03 +00:00
},
2023-09-23 21:48:01 +00:00
function (err) {
if (err) {
console.log("Hash check failed: " + err);
errorCallback(err);
} else {
allDoneCallback();
2023-09-23 21:48:01 +00:00
}
2023-09-22 23:18:03 +00:00
}
);
2023-09-23 21:48:01 +00:00
}