2022-07-18 18:19:22 +00:00
|
|
|
var app = require("app"); // Module to control application life.
|
2023-09-17 01:51:33 +00:00
|
|
|
var dialog = require("dialog");
|
2022-07-18 18:19:22 +00:00
|
|
|
var fs = require("fs-extra");
|
2023-09-17 01:51:33 +00:00
|
|
|
var ipc = require("ipc");
|
2022-07-18 18:19:22 +00:00
|
|
|
var os = require("os");
|
2023-09-17 01:51:33 +00:00
|
|
|
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;
|
2021-05-17 02:00:27 +00:00
|
|
|
|
2023-09-17 01:51:33 +00:00
|
|
|
var BrowserWindow = require("browser-window");
|
2021-05-17 02:00:27 +00:00
|
|
|
var mainWindow = null;
|
|
|
|
|
2023-09-17 01:51:33 +00:00
|
|
|
var unityHomeDir = path.join(__dirname, "../../WebPlayer");
|
2023-09-19 13:51:19 +00:00
|
|
|
// If running in non-packaged / development mode, this dir will be slightly different
|
2023-09-14 07:13:46 +00:00
|
|
|
if (process.env.npm_node_execpath) {
|
2023-09-17 01:51:33 +00:00
|
|
|
unityHomeDir = path.join(app.getAppPath(), "/build/WebPlayer");
|
2023-09-03 09:56:24 +00:00
|
|
|
}
|
|
|
|
|
2023-09-14 07:13:46 +00:00
|
|
|
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";
|
2023-02-08 21:21:35 +00:00
|
|
|
|
2023-09-17 01:51:33 +00:00
|
|
|
app.commandLine.appendSwitch("enable-npapi");
|
2023-09-19 12:29:56 +00:00
|
|
|
app.commandLine.appendSwitch(
|
|
|
|
"load-plugin",
|
|
|
|
path.join(unityHomeDir, "/loader/npUnity3D32.dll")
|
|
|
|
);
|
2023-09-17 01:51:33 +00:00
|
|
|
app.commandLine.appendSwitch("no-proxy-server");
|
|
|
|
|
2023-09-19 12:29:56 +00:00
|
|
|
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");
|
2023-09-18 21:49:31 +00:00
|
|
|
var hashPath = path.join(userData, "hash.txt");
|
2023-09-17 01:51:33 +00:00
|
|
|
|
2021-09-18 21:59:15 +00:00
|
|
|
function initialSetup(firstTime) {
|
2023-09-14 07:13:46 +00:00
|
|
|
if (!firstTime) {
|
2023-09-19 13:51:19 +00:00
|
|
|
// Migration from pre-1.4
|
2023-09-14 07:13:46 +00:00
|
|
|
// Back everything up, just in case
|
2023-09-17 01:51:33 +00:00
|
|
|
fs.copySync(configPath, configPath + ".bak");
|
|
|
|
fs.copySync(serversPath, serversPath + ".bak");
|
|
|
|
fs.copySync(versionsPath, versionsPath + ".bak");
|
2023-09-18 21:49:31 +00:00
|
|
|
fs.copySync(hashPath, hashPath + ".bak");
|
2023-09-14 07:13:46 +00:00
|
|
|
} else {
|
2023-09-19 13:51:19 +00:00
|
|
|
// First-time setup
|
2023-09-14 07:13:46 +00:00
|
|
|
// Copy default servers
|
2022-07-26 14:41:07 +00:00
|
|
|
fs.copySync(
|
2023-09-17 01:51:33 +00:00
|
|
|
path.join(__dirname, "/defaults/servers.json"),
|
|
|
|
serversPath
|
2022-07-26 14:41:07 +00:00
|
|
|
);
|
2023-09-14 07:13:46 +00:00
|
|
|
}
|
2022-07-26 14:41:07 +00:00
|
|
|
|
2023-09-14 07:13:46 +00:00
|
|
|
// Copy default versions and config
|
2023-09-17 01:51:33 +00:00
|
|
|
fs.copySync(path.join(__dirname, "/defaults/versions.json"), versionsPath);
|
|
|
|
fs.copySync(path.join(__dirname, "/defaults/config.json"), configPath);
|
2023-09-18 21:49:31 +00:00
|
|
|
fs.copySync(path.join(__dirname, "/defaults/hash.txt"), hashPath);
|
2023-09-14 07:13:46 +00:00
|
|
|
|
|
|
|
console.log("JSON files copied.");
|
|
|
|
showMainWindow();
|
2021-05-17 02:00:27 +00:00
|
|
|
}
|
|
|
|
|
2022-07-18 18:19:22 +00:00
|
|
|
ipc.on("exit", function (id) {
|
2022-07-26 14:41:07 +00:00
|
|
|
mainWindow.destroy();
|
2021-05-17 02:00:27 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
// Quit when all windows are closed.
|
2022-07-18 18:19:22 +00:00
|
|
|
app.on("window-all-closed", function () {
|
2022-07-26 14:41:07 +00:00
|
|
|
if (process.platform != "darwin") app.quit();
|
2021-05-17 02:00:27 +00:00
|
|
|
});
|
|
|
|
|
2022-07-18 18:19:22 +00:00
|
|
|
app.on("ready", function () {
|
2022-07-26 14:41:07 +00:00
|
|
|
// Check just in case the user forgot to extract the zip.
|
2023-09-03 09:56:24 +00:00
|
|
|
zipCheck = app.getPath("exe").includes(os.tmpdir());
|
|
|
|
if (zipCheck) {
|
|
|
|
var errorMessage =
|
2022-07-26 14:41:07 +00:00
|
|
|
"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.";
|
2023-09-03 09:56:24 +00:00
|
|
|
dialog.showErrorBox("Error!", errorMessage);
|
2022-07-26 14:41:07 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Create the browser window.
|
|
|
|
mainWindow = new BrowserWindow({
|
|
|
|
width: 1280,
|
|
|
|
height: 720,
|
|
|
|
show: false,
|
2023-09-14 07:13:46 +00:00
|
|
|
"web-preferences": {
|
2023-09-19 12:29:56 +00:00
|
|
|
plugins: true,
|
2023-09-22 23:18:03 +00:00
|
|
|
nodeIntegration: true,
|
2023-09-14 07:13:46 +00:00
|
|
|
},
|
2022-07-26 14:41:07 +00:00
|
|
|
});
|
|
|
|
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 {
|
2023-09-14 07:13:46 +00:00
|
|
|
showMainWindow();
|
2022-07-26 14:41:07 +00:00
|
|
|
}
|
|
|
|
}
|
2023-02-08 20:06:54 +00:00
|
|
|
} catch (ex) {
|
2023-09-03 09:56:24 +00:00
|
|
|
dialog.showErrorBox(
|
|
|
|
"Error!",
|
|
|
|
"An error occurred while checking for the config. Make sure you have sufficent permissions."
|
|
|
|
);
|
|
|
|
app.quit();
|
2022-07-26 14:41:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Makes it so external links are opened in the system browser, not Electron
|
2023-09-03 09:56:24 +00:00
|
|
|
mainWindow.webContents.on("new-window", function (event, url) {
|
|
|
|
event.preventDefault();
|
2022-07-26 14:41:07 +00:00
|
|
|
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) {
|
|
|
|
downloadFiles(
|
|
|
|
arg.nginxDir,
|
|
|
|
arg.localDir,
|
|
|
|
arg.fileRelativePaths,
|
|
|
|
function (size) {
|
|
|
|
mainWindow.webContents.send("download-update", {
|
|
|
|
size: size,
|
|
|
|
versionString: arg.versionString,
|
|
|
|
});
|
|
|
|
},
|
|
|
|
function () {
|
|
|
|
mainWindow.webContents.send("download-success", arg.versionString);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
ipc.on("hash-check", function (event, arg) {
|
|
|
|
mainWindow.webContents.send("hash-update", {
|
|
|
|
cacheMode: arg.cacheMode,
|
|
|
|
versionString: arg.versionString,
|
|
|
|
// no size sent, reset sizes
|
|
|
|
});
|
|
|
|
|
|
|
|
checkHashes(arg.localDir, arg.hashes, function (sizes) {
|
|
|
|
mainWindow.webContents.send("hash-update", {
|
|
|
|
cacheMode: arg.cacheMode,
|
|
|
|
versionString: arg.versionString,
|
|
|
|
sizes: sizes,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2021-05-17 02:00:27 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
function showMainWindow() {
|
2022-07-26 14:41:07 +00:00
|
|
|
// 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 () {
|
2023-02-09 11:32:21 +00:00
|
|
|
mainWindow.webContents.executeJavaScript("setAppVersionText();");
|
2022-07-26 14:41:07 +00:00
|
|
|
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();");
|
2023-09-18 21:49:31 +00:00
|
|
|
mainWindow.webContents.executeJavaScript("loadCacheList();");
|
2022-07-26 14:41:07 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
mainWindow.webContents.on("plugin-crashed", function () {
|
2023-09-03 09:56:24 +00:00
|
|
|
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();
|
2022-07-26 14:41:07 +00:00
|
|
|
});
|
|
|
|
|
2023-09-03 09:56:24 +00:00
|
|
|
mainWindow.webContents.on("will-navigate", function (event, url) {
|
|
|
|
event.preventDefault();
|
2022-07-26 14:41:07 +00:00
|
|
|
switch (url) {
|
|
|
|
case "https://audience.fusionfall.com/ff/regWizard.do?_flowId=fusionfall-registration-flow":
|
2023-09-03 09:56:24 +00:00
|
|
|
var errorMessage =
|
2022-07-26 14:41:07 +00:00
|
|
|
"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!";
|
2023-09-03 09:56:24 +00:00
|
|
|
dialog.showErrorBox("Sorry!", errorMessage);
|
2022-07-26 14:41:07 +00:00
|
|
|
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:
|
2023-09-03 04:03:00 +00:00
|
|
|
mainWindow.loadUrl(url);
|
2022-07-26 14:41:07 +00:00
|
|
|
}
|
|
|
|
});
|
2021-05-17 02:00:27 +00:00
|
|
|
}
|
2023-09-22 23:18:03 +00:00
|
|
|
|
|
|
|
function downloadFile(nginxDir, localDir, relativePath, callback, updateCallback) {
|
|
|
|
var nginxUrl = path.dirname(nginxDir) + "/" + relativePath;
|
|
|
|
var localPath = path.join(localDir, relativePath);
|
|
|
|
|
|
|
|
// Create directories if they don't exist
|
|
|
|
var dirName = path.dirname(localPath);
|
|
|
|
fs.ensureDirSync(dirName);
|
|
|
|
|
|
|
|
// 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": nginxDir,
|
|
|
|
"Connection": "keep-alive",
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
var request = client.request("GET", urlParse.path, options);
|
|
|
|
|
|
|
|
request.on("response", function(response) {
|
|
|
|
response.pipe(fileStream);
|
|
|
|
|
|
|
|
// When the download is complete, invoke the callback
|
|
|
|
response.on("end", function() {
|
|
|
|
fileStream.end();
|
|
|
|
updateCallback(fs.statSync(localPath).size);
|
|
|
|
callback(null, relativePath);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Handle errors
|
|
|
|
response.on("error", function(err) {
|
|
|
|
console.error("Error downloading " + relativePath + ": " + err.message);
|
|
|
|
retryDownload(nginxDir, localDir, relativePath, callback, updateCallback); // Retry download
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
// Handle HTTP errors
|
|
|
|
request.on("error", function(err) {
|
|
|
|
console.error("Error downloading " + relativePath + ": " + err.message);
|
|
|
|
retryDownload(nginxDir, localDir, relativePath, callback, updateCallback); // Retry download
|
|
|
|
});
|
|
|
|
|
|
|
|
request.end();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Function to retry downloading a file after a delay
|
|
|
|
function retryDownload(nginxDir, localDir, relativePath, callback, updateCallback) {
|
|
|
|
setTimeout(function() {
|
|
|
|
downloadFile(nginxDir, localDir, relativePath, callback, updateCallback);
|
|
|
|
}, 1000); // Retry after 1 second
|
|
|
|
}
|
|
|
|
|
|
|
|
// Function to download multiple files in parallel
|
|
|
|
function downloadFiles(nginxDir, localDir, fileRelativePaths, updateCallback, allDoneCallback) {
|
|
|
|
async.eachLimit(
|
|
|
|
fileRelativePaths,
|
|
|
|
5, // Number of parallel downloads
|
2023-09-23 21:48:01 +00:00
|
|
|
function (relativePath, callback) {
|
2023-09-22 23:18:03 +00:00
|
|
|
downloadFile(nginxDir, localDir, relativePath, callback, updateCallback);
|
|
|
|
},
|
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);
|
|
|
|
} 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") {
|
|
|
|
console.log("Error opening file for hash check: " + openErr);
|
|
|
|
}
|
|
|
|
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);
|
|
|
|
} 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);
|
|
|
|
} else {
|
|
|
|
callback(null, relativePath);
|
|
|
|
updateCallback(sizes);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
reader();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function checkHashes(localDir, hashes, updateCallback) {
|
|
|
|
console.log(hashes);
|
|
|
|
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);
|
|
|
|
}
|
2023-09-22 23:18:03 +00:00
|
|
|
}
|
|
|
|
);
|
2023-09-23 21:48:01 +00:00
|
|
|
}
|