To TS and Beyond !
(Prototype of course, not at all functionnall)
This commit is contained in:
parent
45630c068c
commit
40d0a1cdca
5
src/dto/OpenJDKData.ts
Normal file
5
src/dto/OpenJDKData.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export interface OpenJDKData {
|
||||
uri: string,
|
||||
size: number,
|
||||
name: string
|
||||
}
|
0
src/main.ts
Normal file
0
src/main.ts
Normal file
825
src/manager/ConfigManager.ts
Normal file
825
src/manager/ConfigManager.ts
Normal file
@ -0,0 +1,825 @@
|
||||
import { ensureDirSync, moveSync } from "fs-extra";
|
||||
import { LoggerUtil } from "helios-core/.";
|
||||
import { existsSync, readFileSync, writeFileSync } from "fs-extra";
|
||||
import os from 'os';
|
||||
import { join } from 'path';
|
||||
import { mcVersionAtLeast } from "../util/MinecraftUtil";
|
||||
import { resolveMaxRAM, resolveMinRAM } from "../util/System";
|
||||
|
||||
const logger = LoggerUtil.getLogger("ConfigManager");
|
||||
|
||||
type Config = {
|
||||
settings: {
|
||||
game: {
|
||||
resWidth: number,
|
||||
resHeight: number,
|
||||
fullscreen: boolean,
|
||||
autoConnect: boolean,
|
||||
launchDetached: boolean,
|
||||
},
|
||||
launcher: {
|
||||
allowPrerelease: boolean,
|
||||
dataDirectory: string,
|
||||
},
|
||||
|
||||
},
|
||||
newsCache: {
|
||||
date?: any,
|
||||
content?: any,
|
||||
dismissed: boolean,
|
||||
},
|
||||
clientToken?: string,
|
||||
selectedServer?: any, // Resolved
|
||||
selectedAccount?: any,
|
||||
authenticationDatabase: any,
|
||||
modConfigurations: any[],
|
||||
javaConfig: any,
|
||||
}
|
||||
|
||||
|
||||
export class ConfigManager {
|
||||
|
||||
private static sysRoot = process.env.APPDATA ?? (process.platform == "darwin" ? process.env.HOME + "/Library/Application Support" : process.env.HOME) ?? "";
|
||||
// TODO change
|
||||
private static dataPath = join(this.sysRoot, ".randomia");
|
||||
private static configPath = join(exports.getLauncherDirectory(), "config.json");
|
||||
private static configPathLEGACY = join(this.dataPath, "config.json");
|
||||
private static firstLaunch = !existsSync(this.configPath) && !existsSync(this.configPathLEGACY);
|
||||
// Forked processes do not have access to electron, so we have this workaround.
|
||||
private static launcherDir = process.env.CONFIG_DIRECT_PATH ?? require("@electron/remote").app.getPath("userData");
|
||||
public static readonly DistributionURL = 'http://mc.westeroscraft.com/WesterosCraftLauncher/distribution.json';
|
||||
public static readonly launcherName = 'Helios-Launcher'
|
||||
|
||||
/**
|
||||
* Three types of values:
|
||||
* Static = Explicitly declared.
|
||||
* Dynamic = Calculated by a private static function.
|
||||
* Resolved = Resolved externally, defaults to null.
|
||||
*/
|
||||
private static DEFAULT_CONFIG: Config = {
|
||||
settings: {
|
||||
game: {
|
||||
resWidth: 1280,
|
||||
resHeight: 720,
|
||||
fullscreen: false,
|
||||
autoConnect: true,
|
||||
launchDetached: true,
|
||||
},
|
||||
launcher: {
|
||||
allowPrerelease: false,
|
||||
dataDirectory: this.dataPath,
|
||||
},
|
||||
},
|
||||
newsCache: {
|
||||
date: undefined,
|
||||
content: undefined,
|
||||
dismissed: false,
|
||||
},
|
||||
clientToken: undefined,
|
||||
selectedServer: undefined, // Resolved
|
||||
selectedAccount: undefined,
|
||||
authenticationDatabase: {},
|
||||
modConfigurations: [],
|
||||
javaConfig: {},
|
||||
}
|
||||
|
||||
private static config: Config;
|
||||
|
||||
/**
|
||||
* Retrieve the absolute path of the launcher directory.
|
||||
*
|
||||
* @returns {string} The absolute path of the launcher directory.
|
||||
*/
|
||||
public static getLauncherDirectory() {
|
||||
return this.launcherDir;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the launcher's data directory. This is where all files related
|
||||
* to game launch are installed (common, instances, java, etc).
|
||||
*
|
||||
* @returns {string} The absolute path of the launcher's data directory.
|
||||
*/
|
||||
public static getDataDirectory(def = false) {
|
||||
return !def ? this.config.settings.launcher.dataDirectory : this.DEFAULT_CONFIG.settings.launcher.dataDirectory;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the new data directory.
|
||||
*
|
||||
* @param {string} dataDirectory The new data directory.
|
||||
*/
|
||||
public static setDataDirectory(dataDirectory: string) {
|
||||
this.config.settings.launcher.dataDirectory = dataDirectory;
|
||||
};
|
||||
|
||||
|
||||
|
||||
public static getAbsoluteMinRAM() {
|
||||
const mem = os.totalmem();
|
||||
return mem >= 6000000000 ? 3 : 2;
|
||||
};
|
||||
|
||||
public static getAbsoluteMaxRAM() {
|
||||
const mem = os.totalmem();
|
||||
const gT16 = mem - 16000000000;
|
||||
return Math.floor((mem - 1000000000 - (gT16 > 0 ? gT16 / 8 + 16000000000 / 4 : mem / 4)) / 1000000000);
|
||||
};
|
||||
|
||||
/**
|
||||
* Save the current configuration to a file.
|
||||
*/
|
||||
public static save() {
|
||||
writeFileSync(this.configPath, JSON.stringify(this.config, null, 4), { encoding: "utf-8" });
|
||||
};
|
||||
|
||||
/**
|
||||
* Load the configuration into memory. If a configuration file exists,
|
||||
* that will be read and saved. Otherwise, a default configuration will
|
||||
* be generated. Note that "resolved" values default to null and will
|
||||
* need to be externally assigned.
|
||||
*/
|
||||
public static load() {
|
||||
let doLoad = true;
|
||||
|
||||
if (!existsSync(this.configPath)) {
|
||||
// Create all parent directories.
|
||||
ensureDirSync(join(this.configPath, ".."));
|
||||
if (existsSync(this.configPathLEGACY)) {
|
||||
moveSync(this.configPathLEGACY, this.configPath);
|
||||
} else {
|
||||
doLoad = false;
|
||||
this.config = this.DEFAULT_CONFIG;
|
||||
exports.save();
|
||||
}
|
||||
}
|
||||
if (doLoad) {
|
||||
let doValidate = false;
|
||||
try {
|
||||
this.config = JSON.parse(readFileSync(this.configPath, { encoding: "utf-8" }));
|
||||
doValidate = true;
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
logger.info("Configuration file contains malformed JSON or is corrupt.");
|
||||
logger.info("Generating a new configuration file.");
|
||||
ensureDirSync(join(this.configPath, ".."));
|
||||
this.config = this.DEFAULT_CONFIG;
|
||||
exports.save();
|
||||
}
|
||||
if (doValidate) {
|
||||
this.config = this.validateKeySet(this.DEFAULT_CONFIG, this.config);
|
||||
exports.save();
|
||||
}
|
||||
}
|
||||
logger.info("Successfully Loaded");
|
||||
};
|
||||
|
||||
/**
|
||||
* @returns {boolean} Whether or not the manager has been loaded.
|
||||
*/
|
||||
public static get isLoaded() {
|
||||
return this.config != null;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Check to see if this is the first time the user has launched the
|
||||
* application. This is determined by the existance of the data
|
||||
*
|
||||
* @returns {boolean} True if this is the first launch, otherwise false.
|
||||
*/
|
||||
public static get isFirstLaunch() {
|
||||
return this.firstLaunch;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the name of the folder in the OS temp directory which we
|
||||
* will use to extract and store native dependencies for game launch.
|
||||
*
|
||||
* @returns {string} The name of the folder.
|
||||
*/
|
||||
public static get tempNativeFolder() {
|
||||
return "WCNatives";
|
||||
};
|
||||
|
||||
// System Settings (Unconfigurable on UI)
|
||||
|
||||
/**
|
||||
* Retrieve the news cache to determine
|
||||
* whether or not there is newer news.
|
||||
*
|
||||
* @returns {Object} The news cache object.
|
||||
*/
|
||||
public static get getNewsCache() {
|
||||
return this.config.newsCache;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the new news cache object.
|
||||
*
|
||||
* @param {Object} newsCache The new news cache object.
|
||||
*/
|
||||
public static setNewsCache(newsCache: {
|
||||
date?: any;
|
||||
content?: any;
|
||||
dismissed: boolean;
|
||||
}) {
|
||||
this.config.newsCache = newsCache;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set whether or not the news has been dismissed (checked)
|
||||
*
|
||||
* @param {boolean} dismissed Whether or not the news has been dismissed (checked).
|
||||
*/
|
||||
public static setNewsCacheDismissed(dismissed: boolean) {
|
||||
this.config.newsCache.dismissed = dismissed;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the common directory for shared
|
||||
* game files (assets, libraries, etc).
|
||||
*
|
||||
* @returns {string} The launcher's common directory.
|
||||
*/
|
||||
public static get commonDirectory() {
|
||||
return join(exports.getDataDirectory(), "common");
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the instance directory for the per
|
||||
* server game directories.
|
||||
*
|
||||
* @returns {string} The launcher's instance directory.
|
||||
*/
|
||||
public static get instanceDirectory() {
|
||||
return join(exports.getDataDirectory(), "instances");
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the launcher's Client Token.
|
||||
* There is no default client token.
|
||||
*
|
||||
* @returns {string} The launcher's Client Token.
|
||||
*/
|
||||
public static get clientToken() {
|
||||
return this.config.clientToken;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the launcher's Client Token.
|
||||
*
|
||||
* @param {string} clientToken The launcher's new Client Token.
|
||||
*/
|
||||
public static set clientToken(clientToken) {
|
||||
this.config.clientToken = clientToken;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the ID of the selected serverpack.
|
||||
*
|
||||
* @param {boolean} def Optional. If true, the default value will be returned.
|
||||
* @returns {string} The ID of the selected serverpack.
|
||||
*/
|
||||
public static getSelectedServer(def = false) {
|
||||
return !def ? this.config.selectedServer : this.DEFAULT_CONFIG.clientToken;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the ID of the selected serverpack.
|
||||
*
|
||||
* @param {string} serverID The ID of the new selected serverpack.
|
||||
*/
|
||||
public static set selectedServer(serverID: string) {
|
||||
this.config.selectedServer = serverID;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get an array of each account currently authenticated by the launcher.
|
||||
*
|
||||
* @returns {Array.<Object>} An array of each stored authenticated account.
|
||||
*/
|
||||
public static get authAccounts() {
|
||||
return this.config.authenticationDatabase;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the authenticated account with the given uuid. Value may
|
||||
* be null.
|
||||
*
|
||||
* @param {string} uuid The uuid of the authenticated account.
|
||||
* @returns {Object} The authenticated account with the given uuid.
|
||||
*/
|
||||
public static getAuthAccountByUuid(uuid: string) {
|
||||
return this.config.authenticationDatabase[uuid];
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the access token of an authenticated mojang account.
|
||||
*
|
||||
* @param {string} uuid The uuid of the authenticated account.
|
||||
* @param {string} accessToken The new Access Token.
|
||||
*
|
||||
* @returns {Object} The authenticated account object created by this action.
|
||||
*/
|
||||
public static updateMojangAuthAccount(uuid: string, accessToken: string) {
|
||||
this.config.authenticationDatabase[uuid].accessToken = accessToken;
|
||||
this.config.authenticationDatabase[uuid].type = "mojang"; // For gradual conversion.
|
||||
return this.config.authenticationDatabase[uuid];
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds an authenticated mojang account to the database to be stored.
|
||||
*
|
||||
* @param {string} uuid The uuid of the authenticated account.
|
||||
* @param {string} accessToken The accessToken of the authenticated account.
|
||||
* @param {string} username The username (usually email) of the authenticated account.
|
||||
* @param {string} displayName The in game name of the authenticated account.
|
||||
*
|
||||
* @returns {Object} The authenticated account object created by this action.
|
||||
*/
|
||||
public static addMojangAuthAccount(uuid: string, accessToken: string, username: string, displayName: string) {
|
||||
this.config.selectedAccount = uuid;
|
||||
this.config.authenticationDatabase[uuid] = {
|
||||
type: "mojang",
|
||||
accessToken,
|
||||
username: username.trim(),
|
||||
uuid: uuid.trim(),
|
||||
displayName: displayName.trim(),
|
||||
};
|
||||
return this.config.authenticationDatabase[uuid];
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the tokens of an authenticated microsoft account.
|
||||
*
|
||||
* @param {string} uuid The uuid of the authenticated account.
|
||||
* @param {string} accessToken The new Access Token.
|
||||
* @param {string} msAccessToken The new Microsoft Access Token
|
||||
* @param {string} msRefreshToken The new Microsoft Refresh Token
|
||||
* @param {date} msExpires The date when the microsoft access token expires
|
||||
* @param {date} mcExpires The date when the mojang access token expires
|
||||
*
|
||||
* @returns {Object} The authenticated account object created by this action.
|
||||
*/
|
||||
public static updateMicrosoftAuthAccount(uuid: string, accessToken: string, msAccessToken: string, msRefreshToken: string, msExpires: string, mcExpires: string) {
|
||||
this.config.authenticationDatabase[uuid].accessToken = accessToken;
|
||||
this.config.authenticationDatabase[uuid].expiresAt = mcExpires;
|
||||
this.config.authenticationDatabase[uuid].microsoft.access_token = msAccessToken;
|
||||
this.config.authenticationDatabase[uuid].microsoft.refresh_token = msRefreshToken;
|
||||
this.config.authenticationDatabase[uuid].microsoft.expires_at = msExpires;
|
||||
return this.config.authenticationDatabase[uuid];
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds an authenticated microsoft account to the database to be stored.
|
||||
*
|
||||
* @param {string} uuid The uuid of the authenticated account.
|
||||
* @param {string} accessToken The accessToken of the authenticated account.
|
||||
* @param {string} name The in game name of the authenticated account.
|
||||
* @param {date} mcExpires The date when the mojang access token expires
|
||||
* @param {string} msAccessToken The microsoft access token
|
||||
* @param {string} msRefreshToken The microsoft refresh token
|
||||
* @param {date} msExpires The date when the microsoft access token expires
|
||||
*
|
||||
* @returns {Object} The authenticated account object created by this action.
|
||||
*/
|
||||
public static addMicrosoftAuthAccount(
|
||||
uuid: string,
|
||||
accessToken: string,
|
||||
name: string,
|
||||
mcExpires: string,
|
||||
msAccessToken: string,
|
||||
msRefreshToken: string,
|
||||
msExpires: string
|
||||
) {
|
||||
this.config.selectedAccount = uuid;
|
||||
this.config.authenticationDatabase[uuid] = {
|
||||
type: "microsoft",
|
||||
accessToken,
|
||||
username: name.trim(),
|
||||
uuid: uuid.trim(),
|
||||
displayName: name.trim(),
|
||||
expiresAt: mcExpires,
|
||||
microsoft: {
|
||||
access_token: msAccessToken,
|
||||
refresh_token: msRefreshToken,
|
||||
expires_at: msExpires,
|
||||
},
|
||||
};
|
||||
return this.config.authenticationDatabase[uuid];
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove an authenticated account from the database. If the account
|
||||
* was also the selected account, a new one will be selected. If there
|
||||
* are no accounts, the selected account will be null.
|
||||
*
|
||||
* @param {string} uuid The uuid of the authenticated account.
|
||||
*
|
||||
* @returns {boolean} True if the account was removed, false if it never existed.
|
||||
*/
|
||||
public static removeAuthAccount(uuid: string) {
|
||||
if (this.config.authenticationDatabase[uuid] != null) {
|
||||
delete this.config.authenticationDatabase[uuid];
|
||||
if (this.config.selectedAccount === uuid) {
|
||||
const keys = Object.keys(this.config.authenticationDatabase);
|
||||
if (keys.length > 0) {
|
||||
this.config.selectedAccount = keys[0];
|
||||
} else {
|
||||
this.config.selectedAccount = null;
|
||||
this.config.clientToken = undefined;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the currently selected authenticated account.
|
||||
*
|
||||
* @returns {Object} The selected authenticated account.
|
||||
*/
|
||||
public static getSelectedAccount() {
|
||||
return this.config.authenticationDatabase[this.config.selectedAccount];
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the selected authenticated account.
|
||||
*
|
||||
* @param {string} uuid The UUID of the account which is to be set
|
||||
* as the selected account.
|
||||
*
|
||||
* @returns {Object} The selected authenticated account.
|
||||
*/
|
||||
public static setSelectedAccount(uuid: string) {
|
||||
const authAcc = this.config.authenticationDatabase[uuid];
|
||||
if (authAcc != null) {
|
||||
this.config.selectedAccount = uuid;
|
||||
}
|
||||
return authAcc;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get an array of each mod configuration currently stored.
|
||||
*
|
||||
* @returns {Array.<Object>} An array of each stored mod configuration.
|
||||
*/
|
||||
public static get modConfigurations() {
|
||||
return this.config.modConfigurations;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the array of stored mod configurations.
|
||||
*
|
||||
* @param {Array.<Object>} configurations An array of mod configurations.
|
||||
*/
|
||||
public static set modConfigurations(configurations) {
|
||||
this.config.modConfigurations = configurations;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the mod configuration for a specific server.
|
||||
*
|
||||
* @param {string} serverid The id of the server.
|
||||
* @returns {Object} The mod configuration for the given server.
|
||||
*/
|
||||
public static getModConfigurationForServer(serverid: string) {
|
||||
const cfgs = this.config.modConfigurations;
|
||||
for (let i = 0; i < cfgs.length; i++) {
|
||||
if (cfgs[i].id === serverid) {
|
||||
return cfgs[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the mod configuration for a specific server. This overrides any existing value.
|
||||
*
|
||||
* @param {string} serverid The id of the server for the given mod configuration.
|
||||
* @param {Object} configuration The mod configuration for the given server.
|
||||
*/
|
||||
public static setModConfigurationForServer(serverid: string, configuration) {
|
||||
const cfgs = this.config.modConfigurations;
|
||||
for (let i = 0; i < cfgs.length; i++) {
|
||||
if (cfgs[i].id === serverid) {
|
||||
cfgs[i] = configuration;
|
||||
return;
|
||||
}
|
||||
}
|
||||
cfgs.push(configuration);
|
||||
};
|
||||
|
||||
|
||||
///////////////////////////////////// JAVA CONFIG ////////////////////////////////////////////
|
||||
|
||||
|
||||
// User Configurable Settings
|
||||
|
||||
// Java Settings
|
||||
|
||||
/**
|
||||
* Ensure a java config property is set for the given server.
|
||||
*
|
||||
* @param {string} serverid The server id.
|
||||
* @param {*} mcVersion The minecraft version of the server.
|
||||
*/
|
||||
public static ensureJavaConfig(serverid: string, mcVersion: string) {
|
||||
if (!Object.prototype.hasOwnProperty.call(this.config.javaConfig, serverid)) {
|
||||
this.config.javaConfig[serverid] = this.defaultJavaConfig(mcVersion);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the minimum amount of memory for JVM initialization. This value
|
||||
* contains the units of memory. For example, '5G' = 5 GigaBytes, '1024M' =
|
||||
* 1024 MegaBytes, etc.
|
||||
*
|
||||
* @param {string} serverid The server id.
|
||||
* @returns {string} The minimum amount of memory for JVM initialization.
|
||||
*/
|
||||
public static getMinRAM(serverid: string) {
|
||||
return this.config.javaConfig[serverid].minRAM;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the minimum amount of memory for JVM initialization. This value should
|
||||
* contain the units of memory. For example, '5G' = 5 GigaBytes, '1024M' =
|
||||
* 1024 MegaBytes, etc.
|
||||
*
|
||||
* @param {string} serverid The server id.
|
||||
* @param {string} minRAM The new minimum amount of memory for JVM initialization.
|
||||
*/
|
||||
public static setMinRAM(serverid: string, minRAM: string) {
|
||||
this.config.javaConfig[serverid].minRAM = minRAM;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the maximum amount of memory for JVM initialization. This value
|
||||
* contains the units of memory. For example, '5G' = 5 GigaBytes, '1024M' =
|
||||
* 1024 MegaBytes, etc.
|
||||
*
|
||||
* @param {string} serverid The server id.
|
||||
* @returns {string} The maximum amount of memory for JVM initialization.
|
||||
*/
|
||||
public static getMaxRAM(serverid: string) {
|
||||
return this.config.javaConfig[serverid].maxRAM;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the maximum amount of memory for JVM initialization. This value should
|
||||
* contain the units of memory. For example, '5G' = 5 GigaBytes, '1024M' =
|
||||
* 1024 MegaBytes, etc.
|
||||
*
|
||||
* @param {string} serverid The server id.
|
||||
* @param {string} maxRAM The new maximum amount of memory for JVM initialization.
|
||||
*/
|
||||
public static setMaxRAM(serverid: string, maxRAM: string) {
|
||||
this.config.javaConfig[serverid].maxRAM = maxRAM;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the path of the Java Executable.
|
||||
*
|
||||
* This is a resolved configuration value and defaults to null until externally assigned.
|
||||
*
|
||||
* @param {string} serverid The server id.
|
||||
* @returns {string} The path of the Java Executable.
|
||||
*/
|
||||
public static getJavaExecutable(serverid: string) {
|
||||
return this.config.javaConfig[serverid].executable;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the path of the Java Executable.
|
||||
*
|
||||
* @param {string} serverid The server id.
|
||||
* @param {string} executable The new path of the Java Executable.
|
||||
*/
|
||||
public static setJavaExecutable(serverid: string, executable: string) {
|
||||
this.config.javaConfig[serverid].executable = executable;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the additional arguments for JVM initialization. Required arguments,
|
||||
* such as memory allocation, will be dynamically resolved and will not be included
|
||||
* in this value.
|
||||
*
|
||||
* @param {string} serverid The server id.
|
||||
* @returns {Array.<string>} An array of the additional arguments for JVM initialization.
|
||||
*/
|
||||
public static getJVMOptions(serverid: string) {
|
||||
return this.config.javaConfig[serverid].jvmOptions;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the additional arguments for JVM initialization. Required arguments,
|
||||
* such as memory allocation, will be dynamically resolved and should not be
|
||||
* included in this value.
|
||||
*
|
||||
* @param {string} serverid The server id.
|
||||
* @param {Array.<string>} jvmOptions An array of the new additional arguments for JVM
|
||||
* initialization.
|
||||
*/
|
||||
public static setJVMOptions(serverid: string, jvmOptions: string[]) {
|
||||
this.config.javaConfig[serverid].jvmOptions = jvmOptions;
|
||||
};
|
||||
|
||||
// Game Settings
|
||||
|
||||
/**
|
||||
* Retrieve the width of the game window.
|
||||
*
|
||||
* @param {boolean} def Optional. If true, the default value will be returned.
|
||||
* @returns {number} The width of the game window.
|
||||
*/
|
||||
public static getGameWidth(def = false) {
|
||||
return !def ? this.config.settings.game.resWidth : this.DEFAULT_CONFIG.settings.game.resWidth;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the width of the game window.
|
||||
*
|
||||
* @param {number} resWidth The new width of the game window.
|
||||
*/
|
||||
public static setGameWidth(resWidth: number) {
|
||||
if (typeof resWidth !== "number") throw new Error("Only Accept Number")
|
||||
this.config.settings.game.resWidth = resWidth;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate a potential new width value.
|
||||
*
|
||||
* @param {number} resWidth The width value to validate.
|
||||
* @returns {boolean} Whether or not the value is valid.
|
||||
*/
|
||||
public static validateGameWidth(resWidth: number) {
|
||||
if (typeof resWidth !== "number") throw new Error("Only Accept Number")
|
||||
return Number.isInteger(resWidth) && resWidth >= 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the height of the game window.
|
||||
*
|
||||
* @param {boolean} def Optional. If true, the default value will be returned.
|
||||
* @returns {number} The height of the game window.
|
||||
*/
|
||||
public static getGameHeight(def = false) {
|
||||
return !def ? this.config.settings.game.resHeight : this.DEFAULT_CONFIG.settings.game.resHeight;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the height of the game window.
|
||||
*
|
||||
* @param {number} resHeight The new height of the game window.
|
||||
*/
|
||||
public static setGameHeight(resHeight: number) {
|
||||
if (typeof resHeight !== "number") throw new Error("Only Accept Number")
|
||||
this.config.settings.game.resHeight = resHeight;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate a potential new height value.
|
||||
*
|
||||
* @param {number} resHeight The height value to validate.
|
||||
* @returns {boolean} Whether or not the value is valid.
|
||||
*/
|
||||
public static validateGameHeight(resHeight: number) {
|
||||
if (typeof resHeight !== "number") throw new Error("Only Accept Number")
|
||||
return Number.isInteger(resHeight) && resHeight >= 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the game should be launched in fullscreen mode.
|
||||
*
|
||||
* @param {boolean} def Optional. If true, the default value will be returned.
|
||||
* @returns {boolean} Whether or not the game is set to launch in fullscreen mode.
|
||||
*/
|
||||
public static getFullscreen(def = false) {
|
||||
return !def ? this.config.settings.game.fullscreen : this.DEFAULT_CONFIG.settings.game.fullscreen;
|
||||
};
|
||||
|
||||
/**
|
||||
* Change the status of if the game should be launched in fullscreen mode.
|
||||
*
|
||||
* @param {boolean} fullscreen Whether or not the game should launch in fullscreen mode.
|
||||
*/
|
||||
public static setFullscreen(fullscreen: boolean) {
|
||||
this.config.settings.game.fullscreen = fullscreen;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the game should auto connect to servers.
|
||||
*
|
||||
* @param {boolean} def Optional. If true, the default value will be returned.
|
||||
* @returns {boolean} Whether or not the game should auto connect to servers.
|
||||
*/
|
||||
public static getAutoConnect(def = false) {
|
||||
return !def ? this.config.settings.game.autoConnect : this.DEFAULT_CONFIG.settings.game.autoConnect;
|
||||
};
|
||||
|
||||
/**
|
||||
* Change the status of whether or not the game should auto connect to servers.
|
||||
*
|
||||
* @param {boolean} autoConnect Whether or not the game should auto connect to servers.
|
||||
*/
|
||||
public static setAutoConnect(autoConnect: boolean) {
|
||||
this.config.settings.game.autoConnect = autoConnect;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the game should launch as a detached process.
|
||||
*
|
||||
* @param {boolean} def Optional. If true, the default value will be returned.
|
||||
* @returns {boolean} Whether or not the game will launch as a detached process.
|
||||
*/
|
||||
public static getLaunchDetached(def = false) {
|
||||
return !def ? this.config.settings.game.launchDetached : this.DEFAULT_CONFIG.settings.game.launchDetached;
|
||||
};
|
||||
|
||||
/**
|
||||
* Change the status of whether or not the game should launch as a detached process.
|
||||
*
|
||||
* @param {boolean} launchDetached Whether or not the game should launch as a detached process.
|
||||
*/
|
||||
public static setLaunchDetached(launchDetached: boolean) {
|
||||
this.config.settings.game.launchDetached = launchDetached;
|
||||
};
|
||||
|
||||
// Launcher Settings
|
||||
|
||||
/**
|
||||
* Check if the launcher should download prerelease versions.
|
||||
*
|
||||
* @param {boolean} def Optional. If true, the default value will be returned.
|
||||
* @returns {boolean} Whether or not the launcher should download prerelease versions.
|
||||
*/
|
||||
public static getAllowPrerelease(def = false) {
|
||||
return !def ? this.config.settings.launcher.allowPrerelease : this.DEFAULT_CONFIG.settings.launcher.allowPrerelease;
|
||||
};
|
||||
|
||||
/**
|
||||
* Change the status of Whether or not the launcher should download prerelease versions.
|
||||
*
|
||||
* @param {boolean} launchDetached Whether or not the launcher should download prerelease versions.
|
||||
*/
|
||||
public static setAllowPrerelease(allowPrerelease: boolean) {
|
||||
this.config.settings.launcher.allowPrerelease = allowPrerelease;
|
||||
};
|
||||
|
||||
private static defaultJavaConfig(mcVersion: string) {
|
||||
if (mcVersionAtLeast("1.17", mcVersion)) {
|
||||
return this.defaultJavaConfig117();
|
||||
} else {
|
||||
return this.defaultJavaConfigBelow117();
|
||||
}
|
||||
}
|
||||
|
||||
private static defaultJavaConfigBelow117() {
|
||||
return {
|
||||
minRAM: resolveMinRAM(),
|
||||
maxRAM: resolveMaxRAM(), // Dynamic
|
||||
executable: null,
|
||||
jvmOptions: ["-XX:+UseConcMarkSweepGC", "-XX:+CMSIncrementalMode", "-XX:-UseAdaptiveSizePolicy", "-Xmn128M"],
|
||||
};
|
||||
}
|
||||
|
||||
private static defaultJavaConfig117() {
|
||||
return {
|
||||
minRAM: resolveMinRAM(),
|
||||
maxRAM: resolveMaxRAM(), // Dynamic
|
||||
executable: null,
|
||||
jvmOptions: ["-XX:+UnlockExperimentalVMOptions", "-XX:+UseG1GC", "-XX:G1NewSizePercent=20", "-XX:G1ReservePercent=20", "-XX:MaxGCPauseMillis=50", "-XX:G1HeapRegionSize=32M"],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that the destination object has at least every field
|
||||
* present in the source object. Assign a default value otherwise.
|
||||
*
|
||||
* @param {Object} srcObj The source object to reference against.
|
||||
* @param {Object} destObj The destination object.
|
||||
* @returns {Object} A validated destination object.
|
||||
*/
|
||||
private static validateKeySet(srcObj, destObj) {
|
||||
if (srcObj == null) {
|
||||
srcObj = {};
|
||||
}
|
||||
const validationBlacklist = ["authenticationDatabase", "javaConfig"];
|
||||
const keys = Object.keys(srcObj);
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
if (typeof destObj[keys[i]] === "undefined") {
|
||||
destObj[keys[i]] = srcObj[keys[i]];
|
||||
} else if (typeof srcObj[keys[i]] === "object" && srcObj[keys[i]] != null && !(srcObj[keys[i]] instanceof Array) && validationBlacklist.indexOf(keys[i]) === -1) {
|
||||
destObj[keys[i]] = this.validateKeySet(srcObj[keys[i]], destObj[keys[i]]);
|
||||
}
|
||||
}
|
||||
return destObj;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
94
src/manager/DistroManager.ts
Normal file
94
src/manager/DistroManager.ts
Normal file
@ -0,0 +1,94 @@
|
||||
import { readFile, writeFile } from "fs-extra"
|
||||
import { LoggerUtil } from "helios-core/.";
|
||||
import { DevUtil } from '../util/isDev';
|
||||
import request from "request";
|
||||
import { ConfigManager } from "./ConfigManager";
|
||||
import { join } from 'path';
|
||||
import { DistroIndex } from '../models/DistroIndex';
|
||||
|
||||
const logger = LoggerUtil.getLogger('DistroManager')
|
||||
export enum DistroTypes {
|
||||
Library,
|
||||
ForgeHosted,
|
||||
Forge, // Unimplemented
|
||||
LiteLoader,
|
||||
ForgeMod,
|
||||
LiteMod,
|
||||
File,
|
||||
VersionManifest,
|
||||
}
|
||||
|
||||
export class DistroManager {
|
||||
|
||||
public distribution!: DistroIndex;
|
||||
private readonly DISTRO_PATH = join(ConfigManager.getLauncherDirectory(), 'distribution.json')
|
||||
private readonly DEV_PATH = join(ConfigManager.getLauncherDirectory(), 'dev_distribution.json')
|
||||
|
||||
/**
|
||||
* @returns {Promise.<DistroIndex>}
|
||||
*/
|
||||
public pullRemote() {
|
||||
if (DevUtil.IsDev) {
|
||||
return exports.pullLocal()
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
const opts = {
|
||||
url: ConfigManager.DistributionURL,
|
||||
timeout: 2500
|
||||
}
|
||||
const distroDest = join(ConfigManager.getLauncherDirectory(), 'distribution.json')
|
||||
request(opts, (error: Error, _resp: any, body: string) => {
|
||||
if (!error) {
|
||||
|
||||
try {
|
||||
this.distribution = DistroIndex.fromJSON(JSON.parse(body))
|
||||
} catch (e) {
|
||||
reject(e)
|
||||
return
|
||||
}
|
||||
|
||||
writeFile(distroDest, body, 'utf-8', (err) => {
|
||||
if (!err) {
|
||||
resolve(this.distribution)
|
||||
return
|
||||
} else {
|
||||
reject(err)
|
||||
return
|
||||
}
|
||||
})
|
||||
} else {
|
||||
reject(error)
|
||||
return
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise.<DistroIndex>}
|
||||
*/
|
||||
public pullLocal() {
|
||||
return new Promise((resolve, reject) => {
|
||||
readFile(DevUtil.IsDev ? this.DEV_PATH : this.DISTRO_PATH, 'utf-8', (err, d) => {
|
||||
if (!err) {
|
||||
this.distribution = DistroIndex.fromJSON(JSON.parse(d))
|
||||
resolve(this.distribution)
|
||||
return
|
||||
} else {
|
||||
reject(err)
|
||||
return
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
public setDevMode(value: boolean) {
|
||||
if (value) {
|
||||
logger.info('Developer mode enabled.')
|
||||
logger.info('If you don\'t know what that means, revert immediately.')
|
||||
} else {
|
||||
logger.info('Developer mode disabled.')
|
||||
}
|
||||
DevUtil.IsDev = value
|
||||
}
|
||||
}
|
62
src/models/Artifact.ts
Normal file
62
src/models/Artifact.ts
Normal file
@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Represents the download information
|
||||
* for a specific module.
|
||||
*/
|
||||
export class Artifact {
|
||||
|
||||
/**
|
||||
* Parse a JSON object into an Artifact.
|
||||
*
|
||||
* @param {Object} json A JSON object representing an Artifact
|
||||
*
|
||||
* @returns {Artifact} The parsed Artifact.
|
||||
*/
|
||||
public static fromJSON(json: {
|
||||
MD5: string,
|
||||
size: string,
|
||||
url: string,
|
||||
path: string,
|
||||
}) {
|
||||
return new Artifact(json.MD5, json.size, json.url, json.path)
|
||||
}
|
||||
|
||||
constructor(
|
||||
public MD5: string,
|
||||
public size: string,
|
||||
public url: string,
|
||||
public path: string,
|
||||
) { }
|
||||
|
||||
/**
|
||||
* Get the MD5 hash of the artifact. This value may
|
||||
* be undefined for artifacts which are not to be
|
||||
* validated and updated.
|
||||
*
|
||||
* @returns {string} The MD5 hash of the Artifact or undefined.
|
||||
*/
|
||||
public getHash() {
|
||||
return this.MD5
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number} The download size of the artifact.
|
||||
*/
|
||||
public getSize() {
|
||||
return this.size
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string} The download url of the artifact.
|
||||
*/
|
||||
public getURL() {
|
||||
return this.url
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string} The artifact's destination path.
|
||||
*/
|
||||
public getPath() {
|
||||
return this.path
|
||||
}
|
||||
|
||||
}
|
20
src/models/Asset.ts
Normal file
20
src/models/Asset.ts
Normal file
@ -0,0 +1,20 @@
|
||||
/** Class representing a base asset. */
|
||||
export class Asset {
|
||||
/**
|
||||
* Create an asset.
|
||||
*
|
||||
* @param {any} id The id of the asset.
|
||||
* @param {string} hash The hash value of the asset.
|
||||
* @param {number} size The size in bytes of the asset.
|
||||
* @param {string} from The url where the asset can be found.
|
||||
* @param {string} to The absolute local file path of the asset.
|
||||
*/
|
||||
constructor(
|
||||
public id: any,
|
||||
public hash: string,
|
||||
public size: number,
|
||||
public from: string,
|
||||
public to: string
|
||||
) {
|
||||
}
|
||||
}
|
22
src/models/DLTracker.ts
Normal file
22
src/models/DLTracker.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { Asset } from './Asset';
|
||||
/**
|
||||
* Class representing a download tracker. This is used to store meta data
|
||||
* about a download queue, including the queue itself.
|
||||
*/
|
||||
export class DLTracker {
|
||||
|
||||
/**
|
||||
* Create a DLTracker
|
||||
*
|
||||
* @param {Array.<Asset>} dlqueue An array containing assets queued for download.
|
||||
* @param {number} dlsize The combined size of each asset in the download queue array.
|
||||
* @param {function(Asset)} callback Optional callback which is called when an asset finishes downloading.
|
||||
*/
|
||||
constructor(
|
||||
public dlqueue: Asset[],
|
||||
public dlsize: number,
|
||||
public callback?: (asset: Asset) => void) {
|
||||
|
||||
}
|
||||
|
||||
}
|
24
src/models/DistroAsset.ts
Normal file
24
src/models/DistroAsset.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { Asset } from "./Asset"
|
||||
|
||||
export class DistroAsset extends Asset {
|
||||
|
||||
/**
|
||||
* Create a DistroModule. This is for processing,
|
||||
* not equivalent to the module objects in the
|
||||
* distro index.
|
||||
*
|
||||
* @param {any} id The id of the asset.
|
||||
* @param {string} hash The hash value of the asset.
|
||||
* @param {number} size The size in bytes of the asset.
|
||||
* @param {string} from The url where the asset can be found.
|
||||
* @param {string} to The absolute local file path of the asset.
|
||||
* @param {string} type The the module type.
|
||||
*/
|
||||
constructor(id: any, hash: string, size: number, from: string, to: string,
|
||||
public type
|
||||
) {
|
||||
super(id, hash, size, from, to)
|
||||
this.type = type
|
||||
}
|
||||
|
||||
}
|
47
src/models/DistroIndex.ts
Normal file
47
src/models/DistroIndex.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { IServer, Server } from './Server';
|
||||
|
||||
interface IDistroIndex {
|
||||
version: string,
|
||||
rss: string,
|
||||
servers: IServer[]
|
||||
}
|
||||
|
||||
export class DistroIndex {
|
||||
|
||||
/**
|
||||
* Parse a JSON object into a DistroIndex.
|
||||
*
|
||||
* @param {Object} json A JSON object representing a DistroIndex.
|
||||
*
|
||||
* @returns {DistroIndex} The parsed Server object.
|
||||
*/
|
||||
public static fromJSON(json: IDistroIndex) {
|
||||
return new DistroIndex(
|
||||
json.version,
|
||||
json.rss,
|
||||
json.servers
|
||||
)
|
||||
}
|
||||
|
||||
public servers: Server[] = []
|
||||
public get mainServer() {
|
||||
return this.servers.find(x => x.isMainServer)?.id ?? this.servers[0].id ?? null;
|
||||
}
|
||||
|
||||
constructor(
|
||||
public version: string,
|
||||
public rss: string,
|
||||
servers: IServer[],
|
||||
) {
|
||||
this.resolveServers(servers);
|
||||
}
|
||||
|
||||
private resolveServers(serverJsons: IServer[]) {
|
||||
const servers: Server[] = []
|
||||
for (let serverJson of serverJsons) {
|
||||
servers.push(Server.fromJSON(serverJson))
|
||||
}
|
||||
this.servers = servers
|
||||
}
|
||||
|
||||
}
|
54
src/models/Library.ts
Normal file
54
src/models/Library.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { Asset } from './Asset';
|
||||
export class Library extends Asset {
|
||||
|
||||
/**
|
||||
* Converts the process.platform OS names to match mojang's OS names.
|
||||
*/
|
||||
public static mojangFriendlyOS() {
|
||||
switch (process.platform) {
|
||||
case "darwin":
|
||||
return 'osx';
|
||||
case "linux":
|
||||
return 'linux';
|
||||
case "win32":
|
||||
return 'windows';
|
||||
default:
|
||||
return 'unknown_os'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not a library is valid for download on a particular OS, following
|
||||
* the rule format specified in the mojang version data index. If the allow property has
|
||||
* an OS specified, then the library can ONLY be downloaded on that OS. If the disallow
|
||||
* property has instead specified an OS, the library can be downloaded on any OS EXCLUDING
|
||||
* the one specified.
|
||||
*
|
||||
* If the rules are undefined, the natives property will be checked for a matching entry
|
||||
* for the current OS.
|
||||
*
|
||||
* @param {Array.<Object>} rules The Library's download rules.
|
||||
* @param {Object} natives The Library's natives object.
|
||||
* @returns {boolean} True if the Library follows the specified rules, otherwise false.
|
||||
*/
|
||||
public static validateRules(rules, natives) {
|
||||
if (rules == null) {
|
||||
return natives ? natives[Library.mojangFriendlyOS()] != null : true;
|
||||
}
|
||||
|
||||
for (let rule of rules) {
|
||||
const action = rule.action
|
||||
const osProp = rule.os
|
||||
if (action != null && osProp != null) {
|
||||
const osName = osProp.name
|
||||
const osMoj = Library.mojangFriendlyOS()
|
||||
if (action === 'allow') {
|
||||
return osName === osMoj
|
||||
} else if (action === 'disallow') {
|
||||
return osName !== osMoj
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
141
src/models/Module.ts
Normal file
141
src/models/Module.ts
Normal file
@ -0,0 +1,141 @@
|
||||
import { LoggerUtil } from 'helios-core/.';
|
||||
import { ConfigManager } from '../manager/ConfigManager';
|
||||
import { Artifact } from './Artifact';
|
||||
import { join } from 'path';
|
||||
import { DistroTypes } from '../manager/DistroManager';
|
||||
|
||||
const logger = LoggerUtil.getLogger('Module')
|
||||
|
||||
export class Module {
|
||||
|
||||
/**
|
||||
* Parse a JSON object into a Module.
|
||||
*
|
||||
* @param {Object} json A JSON object representing a Module.
|
||||
* @param {string} serverid The ID of the server to which this module belongs.
|
||||
*
|
||||
* @returns {Module} The parsed Module.
|
||||
*/
|
||||
public static fromJSON(json, serverid) {
|
||||
return new Module(json.id, json.name, json.type, json.classpath, json.required, json.artifact, json.subModules, serverid)
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the default extension for a specific module type.
|
||||
*
|
||||
* @param {string} type The type of the module.
|
||||
*
|
||||
* @return {string} The default extension for the given type.
|
||||
*/
|
||||
private static resolveDefaultExtension(type) {
|
||||
switch (type) {
|
||||
case exports.Types.Library:
|
||||
case exports.Types.ForgeHosted:
|
||||
case exports.Types.LiteLoader:
|
||||
case exports.Types.ForgeMod:
|
||||
return 'jar'
|
||||
case exports.Types.LiteMod:
|
||||
return 'litemod'
|
||||
case exports.Types.File:
|
||||
default:
|
||||
return 'jar' // There is no default extension really.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public artifactExt: string;
|
||||
public artifactClassifier?: string;
|
||||
public artifactVersion: string;
|
||||
public artifactID: string;
|
||||
public artifactGroup: string;
|
||||
|
||||
public subModules: Module[] = []
|
||||
|
||||
/**
|
||||
* @returns {string} The identifier without he version or extension.
|
||||
*/
|
||||
public get versionlessID() {
|
||||
return this.artifactGroup + ':' + this.artifactID
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string} The identifier without the extension.
|
||||
*/
|
||||
public get extensionlessID() {
|
||||
return this.identifier.split('@')[0]
|
||||
}
|
||||
|
||||
public get hasSubModules() {
|
||||
return this.subModules.length > 0;
|
||||
}
|
||||
|
||||
|
||||
constructor(public identifier: string,
|
||||
public name: string,
|
||||
public type: DistroTypes,
|
||||
public classpath: boolean = true,
|
||||
public required = Required.fromJSON(required),
|
||||
public artifact = Artifact.fromJSON(artifact),
|
||||
subModules,
|
||||
serverid
|
||||
) {
|
||||
this.resolveMetaData()
|
||||
this.resolveArtifactPath(artifact.path, serverid)
|
||||
this.resolveSubModules(subModules, serverid)
|
||||
}
|
||||
|
||||
private resolveMetaData() {
|
||||
try {
|
||||
|
||||
const m0 = this.identifier.split('@')
|
||||
|
||||
this.artifactExt = m0[1] || Module.resolveDefaultExtension(this.type)
|
||||
|
||||
const m1 = m0[0].split(':')
|
||||
|
||||
this.artifactClassifier = m1[3] || undefined
|
||||
this.artifactVersion = m1[2] || '???'
|
||||
this.artifactID = m1[1] || '???'
|
||||
this.artifactGroup = m1[0] || '???'
|
||||
|
||||
} catch (err) {
|
||||
// Improper identifier
|
||||
logger.error('Improper ID for module', this.identifier, err)
|
||||
}
|
||||
}
|
||||
|
||||
private resolveArtifactPath(artifactPath: string, serverid) {
|
||||
const pth = artifactPath == null ? join(...this.artifactGroup.split('.'), this.artifactID, this.artifactVersion, `${this.artifactID}-${this.artifactVersion}${this.artifactClassifier != undefined ? `-${this.artifactClassifier}` : ''}.${this.artifactExt}`) : artifactPath
|
||||
|
||||
switch (this.type) {
|
||||
case exports.Types.Library:
|
||||
case exports.Types.ForgeHosted:
|
||||
case exports.Types.LiteLoader:
|
||||
this.artifact.path = join(ConfigManager.commonDirectory, 'libraries', pth)
|
||||
break
|
||||
case exports.Types.ForgeMod:
|
||||
case exports.Types.LiteMod:
|
||||
this.artifact.path = join(ConfigManager.commonDirectory, 'modstore', pth)
|
||||
break
|
||||
case exports.Types.VersionManifest:
|
||||
this.artifact.path = join(ConfigManager.commonDirectory, 'versions', this.identifier, `${this.identifier}.json`)
|
||||
break
|
||||
case exports.Types.File:
|
||||
default:
|
||||
this.artifact.path = join(ConfigManager.instanceDirectory, serverid, pth)
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private resolveSubModules(json, serverid) {
|
||||
if (json == null) return;
|
||||
|
||||
const subModules: Module[] = []
|
||||
for (let sm of json) {
|
||||
subModules.push(Module.fromJSON(sm, serverid))
|
||||
}
|
||||
this.subModules = subModules
|
||||
}
|
||||
|
||||
}
|
54
src/models/Required.ts
Normal file
54
src/models/Required.ts
Normal file
@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Represents a the requirement status
|
||||
* of a module.
|
||||
*/
|
||||
|
||||
export interface IRequired {
|
||||
value: any,
|
||||
def: any,
|
||||
}
|
||||
|
||||
export class Required {
|
||||
|
||||
/**
|
||||
* Parse a JSON object into a Required object.
|
||||
*
|
||||
* @param {Object} json A JSON object representing a Required object.
|
||||
*
|
||||
* @returns {Required} The parsed Required object.
|
||||
*/
|
||||
static fromJSON(json: IRequired) {
|
||||
if (json == null) {
|
||||
return new Required(true, true)
|
||||
} else {
|
||||
return new Required(json.value == null ? true : json.value, json.def == null ? true : json.def)
|
||||
}
|
||||
}
|
||||
|
||||
public default: any;
|
||||
constructor(
|
||||
public value: any,
|
||||
def: any
|
||||
) {
|
||||
this.default = def;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default value for a required object. If a module
|
||||
* is not required, this value determines whether or not
|
||||
* it is enabled by default.
|
||||
*
|
||||
* @returns {boolean} The default enabled value.
|
||||
*/
|
||||
public get isDefault() {
|
||||
return this.default
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean} Whether or not the module is required.
|
||||
*/
|
||||
public get isRequired(): boolean {
|
||||
return this.value
|
||||
}
|
||||
|
||||
}
|
73
src/models/Server.ts
Normal file
73
src/models/Server.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import { Module } from "./Module"
|
||||
|
||||
export interface IServer {
|
||||
id: string,
|
||||
name: string,
|
||||
description: string,
|
||||
icon: string,
|
||||
version: string,
|
||||
address: string,
|
||||
minecraftVersion: string,
|
||||
isMainServer: boolean,
|
||||
autoconnect: boolean,
|
||||
modules: Module[],
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a server configuration.
|
||||
*/
|
||||
export class Server {
|
||||
/**
|
||||
* Parse a JSON object into a Server.
|
||||
*
|
||||
* @param {Object} json A JSON object representing a Server.
|
||||
*
|
||||
* @returns {Server} The parsed Server object.
|
||||
*/
|
||||
public static fromJSON(json: IServer) {
|
||||
|
||||
const mdls = json.modules
|
||||
json.modules = []
|
||||
|
||||
const serv = new Server(
|
||||
json.id,
|
||||
json.name,
|
||||
json.description,
|
||||
json.icon,
|
||||
json.version,
|
||||
json.address,
|
||||
json.minecraftVersion,
|
||||
json.isMainServer,
|
||||
json.autoconnect,
|
||||
json.modules
|
||||
)
|
||||
serv.resolveModules(mdls)
|
||||
|
||||
return serv
|
||||
}
|
||||
|
||||
|
||||
constructor(
|
||||
public id: string,
|
||||
public name: string,
|
||||
public description: string,
|
||||
public icon: string,
|
||||
public version: string,
|
||||
public address: string,
|
||||
public minecraftVersion: string,
|
||||
public isMainServer: boolean,
|
||||
public autoconnect: boolean,
|
||||
public modules: Module[] = [],
|
||||
) { }
|
||||
|
||||
|
||||
private resolveModules(json) {
|
||||
const modules: Module[] = []
|
||||
for (let m of json) {
|
||||
modules.push(Module.fromJSON(m, this.id))
|
||||
}
|
||||
this.modules = modules
|
||||
}
|
||||
|
||||
|
||||
}
|
5
src/services/JavaGuard.ts
Normal file
5
src/services/JavaGuard.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import * as EventEmitter from "events";
|
||||
|
||||
export class JavaGuard extends EventEmitter {
|
||||
|
||||
}
|
2
src/services/Logger.ts
Normal file
2
src/services/Logger.ts
Normal file
@ -0,0 +1,2 @@
|
||||
import { LoggerUtil } from "helios-core/."
|
||||
export const logger = LoggerUtil.getLogger('AssetExec')
|
874
src/services/ProcessBuilder.ts
Normal file
874
src/services/ProcessBuilder.ts
Normal file
@ -0,0 +1,874 @@
|
||||
import { ensureDirSync, remove, writeFile, writeFileSync } from "fs-extra";
|
||||
import { join, basename } from "path";
|
||||
import { pseudoRandomBytes } from "crypto";
|
||||
import os from "os";
|
||||
import { existsSync } from "fs-extra";
|
||||
import AdmZip from "adm-zip";
|
||||
import { spawn } from "child_process";
|
||||
import { LoggerUtil } from "helios-core/.";
|
||||
import { ConfigManager } from "../manager/ConfigManager";
|
||||
import { MinecraftUtil } from "../util/MinecraftUtil";
|
||||
import { DistroTypes } from "../manager/DistroManager";
|
||||
import { Library } from "../models/Library";
|
||||
import { Module } from "../models/Module";
|
||||
import { Required } from "../models/Required";
|
||||
|
||||
const logger = LoggerUtil.getLogger('ProcessBuilder')
|
||||
export default class ProcessBuilder {
|
||||
|
||||
/**
|
||||
* Get the platform specific classpath separator. On windows, this is a semicolon.
|
||||
* On Unix, this is a colon.
|
||||
*
|
||||
* @returns {string} The classpath separator for the current operating system.
|
||||
*/
|
||||
public static get classpathSeparator() {
|
||||
return process.platform === 'win32' ? ';' : ':'
|
||||
}
|
||||
|
||||
public static isModEnabled(modCfg, required?: Required) {
|
||||
return modCfg != null ? ((typeof modCfg === 'boolean' && modCfg) || (typeof modCfg === 'object' && (typeof modCfg.value !== 'undefined' ? modCfg.value : true))) : required != null ? required.isDefault() : true
|
||||
}
|
||||
|
||||
|
||||
public gameDir: string;
|
||||
public commonDir: string;
|
||||
public forgeModListFile: string;
|
||||
public fmlDir: string;
|
||||
public llDir: string;
|
||||
public libPath: string;
|
||||
|
||||
public usingLiteLoader = false;
|
||||
public llPath?: string;
|
||||
|
||||
constructor(
|
||||
public server,
|
||||
public versionData,
|
||||
public forgeData,
|
||||
public authUser,
|
||||
public launcherVersion
|
||||
) {
|
||||
|
||||
this.gameDir = join(ConfigManager.instanceDirectory, server.getID())
|
||||
this.commonDir = ConfigManager.commonDirectory
|
||||
this.versionData = versionData
|
||||
this.forgeData = forgeData
|
||||
this.authUser = authUser
|
||||
this.launcherVersion = launcherVersion
|
||||
this.forgeModListFile = join(this.gameDir, 'forgeMods.list') // 1.13+
|
||||
this.fmlDir = join(this.gameDir, 'forgeModList.json')
|
||||
this.llDir = join(this.gameDir, 'liteloaderModList.json')
|
||||
this.libPath = join(this.commonDir, 'libraries')
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convienence method to run the functions typically used to build a process.
|
||||
*/
|
||||
public build() {
|
||||
ensureDirSync(this.gameDir)
|
||||
const tempNativePath = join(os.tmpdir(), ConfigManager.tempNativeFolder, pseudoRandomBytes(16).toString('hex'))
|
||||
process.throwDeprecation = true
|
||||
this.setupLiteLoader()
|
||||
logger.info('Using liteloader:', this.usingLiteLoader)
|
||||
const modObj = this.resolveModConfiguration(ConfigManager.getModConfigurationForServer(this.server.getID()).mods, this.server.getModules())
|
||||
|
||||
// Mod list below 1.13
|
||||
if (!MinecraftUtil.mcVersionAtLeast('1.13', this.server.getMinecraftVersion())) {
|
||||
this.constructJSONModList('forge', modObj.forgeMods, true)
|
||||
if (this.usingLiteLoader) {
|
||||
this.constructJSONModList('liteloader', modObj.liteMods, true)
|
||||
}
|
||||
}
|
||||
|
||||
const uberModArr = modObj.forgeMods.concat(modObj.liteMods)
|
||||
let args = this.constructJVMArguments(uberModArr, tempNativePath)
|
||||
|
||||
if (MinecraftUtil.mcVersionAtLeast('1.13', this.server.getMinecraftVersion())) {
|
||||
//args = args.concat(this.constructModArguments(modObj.forgeMods))
|
||||
args = args.concat(this.constructModList(modObj.forgeMods))
|
||||
}
|
||||
|
||||
logger.info('Launch Arguments:', args)
|
||||
|
||||
const child = spawn(ConfigManager.getJavaExecutable(this.server.getID()), args, {
|
||||
cwd: this.gameDir,
|
||||
detached: ConfigManager.getLaunchDetached()
|
||||
})
|
||||
|
||||
if (ConfigManager.getLaunchDetached()) {
|
||||
child.unref()
|
||||
}
|
||||
|
||||
child.stdout.setEncoding('utf8')
|
||||
child.stderr.setEncoding('utf8')
|
||||
|
||||
child.stdout.on('data', (data) => {
|
||||
data.trim().split('\n').forEach(x => console.log(`\x1b[32m[Minecraft]\x1b[0m ${x}`))
|
||||
|
||||
})
|
||||
child.stderr.on('data', (data) => {
|
||||
data.trim().split('\n').forEach(x => console.log(`\x1b[31m[Minecraft]\x1b[0m ${x}`))
|
||||
})
|
||||
child.on('close', (code, signal) => {
|
||||
logger.info('Exited with code', code)
|
||||
remove(tempNativePath, (err) => {
|
||||
if (err) {
|
||||
logger.warn('Error while deleting temp dir', err)
|
||||
} else {
|
||||
logger.info('Temp dir deleted successfully.')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return child
|
||||
}
|
||||
|
||||
/**
|
||||
* Function which performs a preliminary scan of the top level
|
||||
* mods. If liteloader is present here, we setup the special liteloader
|
||||
* launch options. Note that liteloader is only allowed as a top level
|
||||
* mod. It must not be declared as a submodule.
|
||||
*/
|
||||
public setupLiteLoader() {
|
||||
for (let ll of this.server.getModules()) {
|
||||
if (ll.getType() === DistroTypes.LiteLoader) {
|
||||
if (!ll.getRequired().isRequired()) {
|
||||
const modCfg = ConfigManager.getModConfigurationForServer(this.server.getID()).mods
|
||||
if (ProcessBuilder.isModEnabled(modCfg[ll.getVersionlessID()], ll.getRequired())) {
|
||||
if (existsSync(ll.getArtifact().getPath())) {
|
||||
this.usingLiteLoader = true
|
||||
this.llPath = ll.getArtifact().getPath()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (existsSync(ll.getArtifact().getPath())) {
|
||||
this.usingLiteLoader = true
|
||||
this.llPath = ll.getArtifact().getPath()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve an array of all enabled mods. These mods will be constructed into
|
||||
* a mod list format and enabled at launch.
|
||||
*
|
||||
* @param {Object} modConfig The mod configuration object.
|
||||
* @param {Array.<Object>} modules An array of modules to parse.
|
||||
* @returns {{forgeMods: Array.<Object>, liteMods: Array.<Object>}} An object which contains
|
||||
* a list of enabled forge mods and litemods.
|
||||
*/
|
||||
public resolveModConfiguration(modConfig, modules: Module[]) {
|
||||
let forgeMods: Module[] = []
|
||||
let liteMods: Module[] = []
|
||||
|
||||
for (let module of modules) {
|
||||
const type = module.type;
|
||||
if (type === DistroTypes.ForgeMod || type === DistroTypes.LiteMod || type === DistroTypes.LiteLoader) {
|
||||
const isRequired = !module.required.isRequired()
|
||||
const isEnabled = ProcessBuilder.isModEnabled(modConfig[module.versionlessID], module.required)
|
||||
if (!isRequired || (isRequired && isEnabled)) {
|
||||
if (module.hasSubModules) {
|
||||
const v = this.resolveModConfiguration(modConfig[module.versionlessID].mods, module.subModules)
|
||||
forgeMods = forgeMods.concat(v.forgeMods)
|
||||
liteMods = liteMods.concat(v.liteMods)
|
||||
if (module.type === DistroTypes.LiteLoader) continue;
|
||||
}
|
||||
if (type === DistroTypes.ForgeMod) {
|
||||
forgeMods.push(module)
|
||||
} else {
|
||||
liteMods.push(module)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
forgeMods,
|
||||
liteMods
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Construct a mod list json object.
|
||||
*
|
||||
* @param {'forge' | 'liteloader'} type The mod list type to construct.
|
||||
* @param {Array.<Object>} mods An array of mods to add to the mod list.
|
||||
* @param {boolean} save Optional. Whether or not we should save the mod list file.
|
||||
*/
|
||||
public constructJSONModList(type: 'forge' | 'liteloader', mods: Module[], save = false) {
|
||||
let modList: {
|
||||
repositoryRoot: string,
|
||||
modRef: string[]
|
||||
} = {
|
||||
repositoryRoot: ((type === 'forge' && this.requiresAbsolute()) ? 'absolute:' : '') + join(this.commonDir, 'modstore'),
|
||||
modRef: []
|
||||
}
|
||||
|
||||
const ids = []
|
||||
if (type === 'forge') {
|
||||
for (let mod of mods) {
|
||||
ids.push(mod.extensionlessID)
|
||||
}
|
||||
} else {
|
||||
for (let mod of mods) {
|
||||
ids.push(mod.extensionlessID + '@' + mod.artifactExt)
|
||||
}
|
||||
}
|
||||
|
||||
modList.modRef = ids
|
||||
|
||||
if (save) {
|
||||
const json = JSON.stringify(modList, null, 4)
|
||||
writeFileSync(type === 'forge' ? this.fmlDir : this.llDir, json, { encoding: 'utf-8' })
|
||||
}
|
||||
|
||||
return modList
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct the argument array that will be passed to the JVM process.
|
||||
*
|
||||
* @param {Array.<Object>} mods An array of enabled mods which will be launched with this process.
|
||||
* @param {string} tempNativePath The path to store the native libraries.
|
||||
* @returns {Array.<string>} An array containing the full JVM arguments for this process.
|
||||
*/
|
||||
public constructJVMArguments(mods: Module, tempNativePath: string) {
|
||||
if (MinecraftUtil.mcVersionAtLeast('1.13', this.server.getMinecraftVersion())) {
|
||||
return this.constructJVMArguments113(mods, tempNativePath)
|
||||
} else {
|
||||
return this.constructJVMArguments112(mods, tempNativePath)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Resolve the full classpath argument list for this process. This method will resolve all Mojang-declared
|
||||
* libraries as well as the libraries declared by the server. Since mods are permitted to declare libraries,
|
||||
* this method requires all enabled mods as an input
|
||||
*
|
||||
* @param {Array.<Object>} mods An array of enabled mods which will be launched with this process.
|
||||
* @param {string} tempNativePath The path to store the native libraries.
|
||||
* @returns {Array.<string>} An array containing the paths of each library required by this process.
|
||||
*/
|
||||
public classpathArg(mods: Module[], tempNativePath: string) {
|
||||
let cpArgs: string[] = []
|
||||
|
||||
if (!MinecraftUtil.mcVersionAtLeast('1.17', this.server.getMinecraftVersion())) {
|
||||
// Add the version.jar to the classpath.
|
||||
// Must not be added to the classpath for Forge 1.17+.
|
||||
const version = this.versionData.id
|
||||
cpArgs.push(join(this.commonDir, 'versions', version, version + '.jar'))
|
||||
}
|
||||
|
||||
|
||||
if (this.usingLiteLoader && this.llPath) {
|
||||
cpArgs.push(this.llPath)
|
||||
}
|
||||
|
||||
// Resolve the Mojang declared libraries.
|
||||
const mojangLibs = this.resolveMojangLibraries(tempNativePath)
|
||||
|
||||
// Resolve the server declared libraries.
|
||||
const servLibs = this.resolveServerLibraries(mods)
|
||||
|
||||
// Merge libraries, server libs with the same
|
||||
// maven identifier will override the mojang ones.
|
||||
// Ex. 1.7.10 forge overrides mojang's guava with newer version.
|
||||
const finalLibs = { ...mojangLibs, ...servLibs }
|
||||
cpArgs = cpArgs.concat(Object.values(finalLibs))
|
||||
|
||||
this.processClassPathList(cpArgs)
|
||||
|
||||
return cpArgs
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct the mod argument list for forge 1.13
|
||||
*
|
||||
* @param {Array.<Object>} mods An array of mods to add to the mod list.
|
||||
*/
|
||||
public constructModList(mods: Module[]) {
|
||||
const writeBuffer = mods.map(mod => {
|
||||
return mod.extensionlessID
|
||||
}).join('\n')
|
||||
|
||||
if (writeBuffer) {
|
||||
writeFileSync(this.forgeModListFile, writeBuffer, { encoding: 'utf-8' })
|
||||
return [
|
||||
'--fml.mavenRoots',
|
||||
join('..', '..', 'common', 'modstore'),
|
||||
'--fml.modLists',
|
||||
this.forgeModListFile
|
||||
]
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Ensure that the classpath entries all point to jar files.
|
||||
*
|
||||
* //TODO: WTF WHY MATE WHY ?????
|
||||
* @param {Array.<String>} classpathEntries Array of classpath entries.
|
||||
*/
|
||||
private processClassPathList(classpathEntries: string[]) {
|
||||
const ext = '.jar'
|
||||
const extLen = ext.length
|
||||
for (let i = 0; i < classpathEntries.length; i++) {
|
||||
const extIndex = classpathEntries[i].indexOf(ext)
|
||||
if (extIndex > -1 && extIndex !== classpathEntries[i].length - extLen) {
|
||||
classpathEntries[i] = classpathEntries[i].substring(0, extIndex + extLen)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Resolve the libraries declared by this server in order to add them to the classpath.
|
||||
* This method will also check each enabled mod for libraries, as mods are permitted to
|
||||
* declare libraries.
|
||||
*
|
||||
* @param {Array.<Object>} mods An array of enabled mods which will be launched with this process.
|
||||
* @returns {{[id: string]: string}} An object containing the paths of each library this server requires.
|
||||
*/
|
||||
private resolveServerLibraries(mods: Module[]) {
|
||||
const modules: Module[] = this.server.getModules();
|
||||
let libs: Record<string, string> = {}
|
||||
|
||||
// Locate Forge/Libraries
|
||||
for (let module of modules) {
|
||||
const type = module.type
|
||||
if (type === DistroTypes.ForgeHosted || type === DistroTypes.Library) {
|
||||
libs[module.versionlessID] = module.artifact.path;
|
||||
if (!module.hasSubModules) continue;
|
||||
const res = this.resolveModuleLibraries(module)
|
||||
if (res.length > 0) {
|
||||
libs = { ...libs, ...res }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Check for any libraries in our mod list.
|
||||
for (let i = 0; i < mods.length; i++) {
|
||||
const mod = mods[i];
|
||||
if (!mod.hasSubModules) continue;
|
||||
const res = this.resolveModuleLibraries(mods[i])
|
||||
if (res.length > 0) {
|
||||
libs = { ...libs, ...res }
|
||||
}
|
||||
}
|
||||
|
||||
return libs
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively resolve the path of each library required by this module.
|
||||
*
|
||||
* @param {Object} module A module object from the server distro index.
|
||||
* @returns {Array.<string>} An array containing the paths of each library this module requires.
|
||||
*/
|
||||
private resolveModuleLibraries(module: Module) {
|
||||
if (!module.hasSubModules) return []
|
||||
|
||||
let libs: string[] = []
|
||||
|
||||
for (let subModule of module.subModules) {
|
||||
if (subModule.type === DistroTypes.Library) {
|
||||
|
||||
if (subModule.classpath) {
|
||||
libs.push(subModule.artifact.path)
|
||||
}
|
||||
}
|
||||
|
||||
// If this module has submodules, we need to resolve the libraries for those.
|
||||
// To avoid unnecessary recursive calls, base case is checked here.
|
||||
if (module.hasSubModules) {
|
||||
const res = this.resolveModuleLibraries(subModule)
|
||||
if (res.length > 0) {
|
||||
libs = libs.concat(res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return libs
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Resolve the libraries defined by Mojang's version data. This method will also extract
|
||||
* native libraries and point to the correct location for its classpath.
|
||||
*
|
||||
* TODO - clean up function
|
||||
*
|
||||
* @param {string} tempNativePath The path to store the native libraries.
|
||||
* @returns {{[id: string]: string}} An object containing the paths of each library mojang declares.
|
||||
*/
|
||||
private resolveMojangLibraries(tempNativePath: string) {
|
||||
const nativesRegex = /.+:natives-([^-]+)(?:-(.+))?/
|
||||
let libs: Record<string, string> = {}
|
||||
|
||||
const libArr = this.versionData.libraries
|
||||
ensureDirSync(tempNativePath)
|
||||
for (let i = 0; i < libArr.length; i++) {
|
||||
const lib = libArr[i]
|
||||
if (Library.validateRules(lib.rules, lib.natives)) {
|
||||
|
||||
// Pre-1.19 has a natives object.
|
||||
if (lib.natives != null) {
|
||||
// Extract the native library.
|
||||
const exclusionArr: string[] = lib.extract != null ? lib.extract.exclude : ['META-INF/']
|
||||
const artifact = lib.downloads.classifiers[lib.natives[Library.mojangFriendlyOS()].replace('${arch}', process.arch.replace('x', ''))]
|
||||
|
||||
// Location of native zip.
|
||||
const to = join(this.libPath, artifact.path)
|
||||
|
||||
const zip = new AdmZip(to)
|
||||
const zipEntries = zip.getEntries()
|
||||
|
||||
// Unzip the native zip.
|
||||
for (let i = 0; i < zipEntries.length; i++) {
|
||||
const fileName = zipEntries[i].entryName
|
||||
|
||||
let shouldExclude = false
|
||||
|
||||
// Exclude noted files.
|
||||
exclusionArr.forEach(exclusion => {
|
||||
if (fileName.indexOf(exclusion) > -1) {
|
||||
shouldExclude = true
|
||||
}
|
||||
})
|
||||
|
||||
// Extract the file.
|
||||
if (shouldExclude) continue;
|
||||
writeFile(join(tempNativePath, fileName), zipEntries[i].getData(), (err) => {
|
||||
if (err) {
|
||||
logger.error('Error while extracting native library:', err)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
// 1.19+ logic
|
||||
else if (lib.name.includes('natives-')) {
|
||||
|
||||
const regexTest: RegExpExecArray | null = nativesRegex.exec(lib.name)
|
||||
// const os = regexTest[1]
|
||||
if (!regexTest) throw new Error("No RegexTest - Processor Builder");
|
||||
|
||||
const arch = regexTest[2] ?? 'x64'
|
||||
|
||||
if (arch != process.arch) continue;
|
||||
|
||||
// Extract the native library.
|
||||
const exclusionArr: string[] = lib.extract != null ? lib.extract.exclude : ['META-INF/', '.git', '.sha1']
|
||||
const artifact = lib.downloads.artifact
|
||||
|
||||
// Location of native zip.
|
||||
const to = join(this.libPath, artifact.path)
|
||||
|
||||
let zip = new AdmZip(to)
|
||||
let zipEntries = zip.getEntries()
|
||||
|
||||
// Unzip the native zip.
|
||||
for (let i = 0; i < zipEntries.length; i++) {
|
||||
if (zipEntries[i].isDirectory) continue;
|
||||
|
||||
const fileName = zipEntries[i].entryName;
|
||||
|
||||
let shouldExclude = false;
|
||||
|
||||
// Exclude noted files.
|
||||
exclusionArr.forEach(exclusion => {
|
||||
if (fileName.indexOf(exclusion) > -1) {
|
||||
shouldExclude = true
|
||||
}
|
||||
})
|
||||
|
||||
const extractName = fileName.includes('/') ? fileName.substring(fileName.lastIndexOf('/')) : fileName
|
||||
|
||||
// Extract the file.
|
||||
if (shouldExclude) continue;
|
||||
|
||||
writeFile(join(tempNativePath, extractName), zipEntries[i].getData(), (err) => {
|
||||
if (err) {
|
||||
logger.error('Error while extracting native library:', err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
// No natives
|
||||
else {
|
||||
const dlInfo = lib.downloads
|
||||
const artifact = dlInfo.artifact
|
||||
const to = join(this.libPath, artifact.path)
|
||||
const versionIndependentId = lib.name.substring(0, lib.name.lastIndexOf(':'))
|
||||
libs[versionIndependentId] = to
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return libs
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct the argument array that will be passed to the JVM process.
|
||||
* This function is for 1.12 and below.
|
||||
*
|
||||
* @param {Array.<Object>} mods An array of enabled mods which will be launched with this process.
|
||||
* @param {string} tempNativePath The path to store the native libraries.
|
||||
* @returns {Array.<string>} An array containing the full JVM arguments for this process.
|
||||
*/
|
||||
private constructJVMArguments112(mods: Module[], tempNativePath: string) {
|
||||
|
||||
let args: string[] = []
|
||||
|
||||
// Classpath Argument
|
||||
args.push('-cp')
|
||||
args.push(this.classpathArg(mods, tempNativePath).join(ProcessBuilder.classpathSeparator))
|
||||
|
||||
// Java Arguments
|
||||
if (process.platform === 'darwin') {
|
||||
args.push(`-Xdock:name=${ConfigManager.launcherName.replace(" ", "")}`)
|
||||
args.push('-Xdock:icon=' + join(__dirname, '..', 'images', 'minecraft.icns'))
|
||||
}
|
||||
args.push('-Xmx' + ConfigManager.getMaxRAM(this.server.getID()))
|
||||
args.push('-Xms' + ConfigManager.getMinRAM(this.server.getID()))
|
||||
args = args.concat(ConfigManager.getJVMOptions(this.server.getID()))
|
||||
args.push('-Djava.library.path=' + tempNativePath)
|
||||
|
||||
// Main Java Class
|
||||
args.push(this.forgeData.mainClass)
|
||||
|
||||
// Forge Arguments
|
||||
args = args.concat(this.resolveForgeArgs())
|
||||
|
||||
return args
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct the argument array that will be passed to the JVM process.
|
||||
* This function is for 1.13+
|
||||
*
|
||||
* Note: Required Libs https://github.com/MinecraftForge/MinecraftForge/blob/af98088d04186452cb364280340124dfd4766a5c/src/fmllauncher/java/net/minecraftforge/fml/loading/LibraryFinder.java#L82
|
||||
*
|
||||
* @param {Array.<Object>} mods An array of enabled mods which will be launched with this process.
|
||||
* @param {string} tempNativePath The path to store the native libraries.
|
||||
* @returns {Array.<string>} An array containing the full JVM arguments for this process.
|
||||
*/
|
||||
private constructJVMArguments113(mods: Module, tempNativePath: string) {
|
||||
|
||||
const argDiscovery = /\${*(.*)}/
|
||||
|
||||
// JVM Arguments First
|
||||
let args: string[] = this.versionData.arguments.jvm
|
||||
|
||||
// Debug securejarhandler
|
||||
// args.push('-Dbsl.debug=true')
|
||||
|
||||
if (this.forgeData.arguments.jvm != null) {
|
||||
for (const argStr of this.forgeData.arguments.jvm) {
|
||||
args.push(argStr
|
||||
.replaceAll('${library_directory}', this.libPath)
|
||||
.replaceAll('${classpath_separator}', ProcessBuilder.classpathSeparator)
|
||||
.replaceAll('${version_name}', this.forgeData.id)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
//args.push('-Dlog4j.configurationFile=D:\\WesterosCraft\\game\\common\\assets\\log_configs\\client-1.12.xml')
|
||||
|
||||
// Java Arguments
|
||||
if (process.platform === 'darwin') {
|
||||
args.push(`-Xdock:name=${ConfigManager.launcherName.replace(" ", "")}`)
|
||||
args.push('-Xdock:icon=' + join(__dirname, '..', 'images', 'minecraft.icns'))
|
||||
}
|
||||
args.push('-Xmx' + ConfigManager.getMaxRAM(this.server.getID()))
|
||||
args.push('-Xms' + ConfigManager.getMinRAM(this.server.getID()))
|
||||
args = args.concat(ConfigManager.getJVMOptions(this.server.getID()))
|
||||
|
||||
// Main Java Class
|
||||
args.push(this.forgeData.mainClass)
|
||||
|
||||
// Vanilla Arguments
|
||||
args = args.concat(this.versionData.arguments.game)
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (typeof args[i] === 'object' && args[i].rules != null) {
|
||||
|
||||
let checksum = 0
|
||||
for (let rule of args[i].rules) {
|
||||
if (rule.os != null) {
|
||||
if (rule.os.name === Library.mojangFriendlyOS()
|
||||
&& (rule.os.version == null || new RegExp(rule.os.version).test(os.release()))) {
|
||||
if (rule.action === 'allow') {
|
||||
checksum++
|
||||
}
|
||||
} else {
|
||||
if (rule.action === 'disallow') {
|
||||
checksum++
|
||||
}
|
||||
}
|
||||
} else if (rule.features != null) {
|
||||
// We don't have many 'features' in the index at the moment.
|
||||
// This should be fine for a while.
|
||||
if (rule.features.has_custom_resolution != null && rule.features.has_custom_resolution === true) {
|
||||
if (ConfigManager.getFullscreen()) {
|
||||
args[i].value = [
|
||||
'--fullscreen',
|
||||
'true'
|
||||
]
|
||||
}
|
||||
checksum++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO splice not push
|
||||
if (checksum === args[i].rules.length) {
|
||||
if (typeof args[i].value === 'string') {
|
||||
args[i] = args[i].value
|
||||
} else if (typeof args[i].value === 'object') {
|
||||
//args = args.concat(args[i].value)
|
||||
args.splice(i, 1, ...args[i].value)
|
||||
}
|
||||
|
||||
// Decrement i to reprocess the resolved value
|
||||
i--
|
||||
} else {
|
||||
args[i] = null
|
||||
}
|
||||
|
||||
} else if (typeof args[i] === 'string') {
|
||||
if (argDiscovery.test(args[i])) {
|
||||
const identifier = args[i].match(argDiscovery)[1]
|
||||
let val = null
|
||||
switch (identifier) {
|
||||
case 'auth_player_name':
|
||||
val = this.authUser.displayName.trim()
|
||||
break
|
||||
case 'version_name':
|
||||
//val = versionData.id
|
||||
val = this.server.getID()
|
||||
break
|
||||
case 'game_directory':
|
||||
val = this.gameDir
|
||||
break
|
||||
case 'assets_root':
|
||||
val = join(this.commonDir, 'assets')
|
||||
break
|
||||
case 'assets_index_name':
|
||||
val = this.versionData.assets
|
||||
break
|
||||
case 'auth_uuid':
|
||||
val = this.authUser.uuid.trim()
|
||||
break
|
||||
case 'auth_access_token':
|
||||
val = this.authUser.accessToken
|
||||
break
|
||||
case 'user_type':
|
||||
val = this.authUser.type === 'microsoft' ? 'msa' : 'mojang'
|
||||
break
|
||||
case 'version_type':
|
||||
val = this.versionData.type
|
||||
break
|
||||
case 'resolution_width':
|
||||
val = ConfigManager.getGameWidth()
|
||||
break
|
||||
case 'resolution_height':
|
||||
val = ConfigManager.getGameHeight()
|
||||
break
|
||||
case 'natives_directory':
|
||||
val = args[i].replace(argDiscovery, tempNativePath)
|
||||
break
|
||||
case 'launcher_name':
|
||||
val = args[i].replace(argDiscovery, ConfigManager.launcherName)
|
||||
break
|
||||
case 'launcher_version':
|
||||
val = args[i].replace(argDiscovery, this.launcherVersion)
|
||||
break
|
||||
case 'classpath':
|
||||
val = this.classpathArg(mods, tempNativePath).join(ProcessBuilder.classpathSeparator)
|
||||
break
|
||||
}
|
||||
if (val != null) {
|
||||
args[i] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Autoconnect
|
||||
let isAutoconnectBroken
|
||||
try {
|
||||
isAutoconnectBroken = MinecraftUtil.isAutoconnectBroken(this.forgeData.id.split('-')[2])
|
||||
} catch (err) {
|
||||
logger.error(err)
|
||||
logger.error('Forge version format changed.. assuming autoconnect works.')
|
||||
logger.debug('Forge version:', this.forgeData.id)
|
||||
}
|
||||
|
||||
if (isAutoconnectBroken) {
|
||||
logger.error('Server autoconnect disabled on Forge 1.15.2 for builds earlier than 31.2.15 due to OpenGL Stack Overflow issue.')
|
||||
logger.error('Please upgrade your Forge version to at least 31.2.15!')
|
||||
} else {
|
||||
this.processAutoConnectArg(args)
|
||||
}
|
||||
|
||||
|
||||
// Forge Specific Arguments
|
||||
args = args.concat(this.forgeData.arguments.game)
|
||||
|
||||
// Filter null values
|
||||
args = args.filter(arg => {
|
||||
return arg != null
|
||||
})
|
||||
|
||||
return args
|
||||
}
|
||||
|
||||
private lteMinorVersion(version: number) {
|
||||
return Number(this.forgeData.id.split('-')[0].split('.')[1]) <= version;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test to see if this version of forge requires the absolute: prefix
|
||||
* on the modListFile repository field.
|
||||
*/
|
||||
private requiresAbsolute() {
|
||||
try {
|
||||
if (this.lteMinorVersion(9)) {
|
||||
return false
|
||||
}
|
||||
|
||||
const ver = this.forgeData.id.split('-')[2]
|
||||
const pts = ver.split('.')
|
||||
const min = [14, 23, 3, 2655]
|
||||
for (let i = 0; i < pts.length; i++) {
|
||||
const parsed = Number.parseInt(pts[i])
|
||||
if (parsed < min[i]) {
|
||||
return false
|
||||
} else if (parsed > min[i]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
// We know old forge versions follow this format.
|
||||
// Error must be caused by newer version.
|
||||
}
|
||||
|
||||
// Equal or errored
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the arguments required by forge.
|
||||
*
|
||||
* @returns {Array.<string>} An array containing the arguments required by forge.
|
||||
*/
|
||||
private resolveForgeArgs() {
|
||||
const mcArgs = this.forgeData.minecraftArguments.split(' ')
|
||||
const argDiscovery = /\${*(.*)}/
|
||||
|
||||
// Replace the declared variables with their proper values.
|
||||
for (let i = 0; i < mcArgs.length; ++i) {
|
||||
if (argDiscovery.test(mcArgs[i])) {
|
||||
const identifier = mcArgs[i].match(argDiscovery)[1]
|
||||
let val = null
|
||||
switch (identifier) {
|
||||
case 'auth_player_name':
|
||||
val = this.authUser.displayName.trim()
|
||||
break
|
||||
case 'version_name':
|
||||
//val = versionData.id
|
||||
val = this.server.getID()
|
||||
break
|
||||
case 'game_directory':
|
||||
val = this.gameDir
|
||||
break
|
||||
case 'assets_root':
|
||||
val = join(this.commonDir, 'assets')
|
||||
break
|
||||
case 'assets_index_name':
|
||||
val = this.versionData.assets
|
||||
break
|
||||
case 'auth_uuid':
|
||||
val = this.authUser.uuid.trim()
|
||||
break
|
||||
case 'auth_access_token':
|
||||
val = this.authUser.accessToken
|
||||
break
|
||||
case 'user_type':
|
||||
val = this.authUser.type === 'microsoft' ? 'msa' : 'mojang'
|
||||
break
|
||||
case 'user_properties': // 1.8.9 and below.
|
||||
val = '{}'
|
||||
break
|
||||
case 'version_type':
|
||||
val = this.versionData.type
|
||||
break
|
||||
}
|
||||
if (val != null) {
|
||||
mcArgs[i] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Autoconnect to the selected server.
|
||||
this.processAutoConnectArg(mcArgs)
|
||||
|
||||
// Prepare game resolution
|
||||
if (ConfigManager.getFullscreen()) {
|
||||
mcArgs.push('--fullscreen')
|
||||
mcArgs.push(true)
|
||||
} else {
|
||||
mcArgs.push('--width')
|
||||
mcArgs.push(ConfigManager.getGameWidth())
|
||||
mcArgs.push('--height')
|
||||
mcArgs.push(ConfigManager.getGameHeight())
|
||||
}
|
||||
|
||||
// Mod List File Argument
|
||||
mcArgs.push('--modListFile')
|
||||
if (this.lteMinorVersion(9)) {
|
||||
mcArgs.push(basename(this.fmlDir))
|
||||
} else {
|
||||
mcArgs.push('absolute:' + this.fmlDir)
|
||||
}
|
||||
|
||||
|
||||
// LiteLoader
|
||||
if (this.usingLiteLoader) {
|
||||
mcArgs.push('--modRepo')
|
||||
mcArgs.push(this.llDir)
|
||||
|
||||
// Set first arg to liteloader tweak class
|
||||
mcArgs.unshift('com.mumfrey.liteloader.launch.LiteLoaderTweaker')
|
||||
mcArgs.unshift('--tweakClass')
|
||||
}
|
||||
|
||||
return mcArgs
|
||||
}
|
||||
|
||||
|
||||
private processAutoConnectArg(args: string[]) {
|
||||
if (ConfigManager.getAutoConnect() && this.server.isAutoConnect()) {
|
||||
const serverURL = new URL('my://' + this.server.getAddress())
|
||||
args.push('--server')
|
||||
args.push(serverURL.hostname)
|
||||
if (serverURL.port) {
|
||||
args.push('--port')
|
||||
args.push(serverURL.port)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
66
src/services/ServerStatus.ts
Normal file
66
src/services/ServerStatus.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import * as net from "net";
|
||||
|
||||
export class ServerStatus {
|
||||
|
||||
/**
|
||||
* Retrieves the status of a minecraft server.
|
||||
*
|
||||
* @param {string} address The server address.
|
||||
* @param {number} port Optional. The port of the server. Defaults to 25565.
|
||||
* @returns {Promise.<Object>} A promise which resolves to an object containing
|
||||
* status information.
|
||||
*/
|
||||
public static getStatus(address: string, port: number = 25565) {
|
||||
|
||||
if (port === null || typeof port !== 'number') {
|
||||
port = 25565
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const socket = net.connect(port, address, () => {
|
||||
let buff = Buffer.from([0xFE, 0x01])
|
||||
socket.write(buff)
|
||||
})
|
||||
|
||||
socket.setTimeout(2500, () => {
|
||||
socket.end()
|
||||
reject({
|
||||
code: 'ETIMEDOUT',
|
||||
errno: 'ETIMEDOUT',
|
||||
address,
|
||||
port
|
||||
})
|
||||
})
|
||||
|
||||
socket.on('data', (data) => {
|
||||
if (data != null && data.length > 0) {
|
||||
let server_info = data.toString().split('\x00\x00\x00')
|
||||
const NUM_FIELDS = 6
|
||||
if (server_info != null && server_info.length >= NUM_FIELDS) {
|
||||
resolve({
|
||||
online: true,
|
||||
version: server_info[2].replace(/\u0000/g, ''),
|
||||
motd: server_info[3].replace(/\u0000/g, ''),
|
||||
onlinePlayers: server_info[4].replace(/\u0000/g, ''),
|
||||
maxPlayers: server_info[5].replace(/\u0000/g, '')
|
||||
})
|
||||
} else {
|
||||
resolve({
|
||||
online: false
|
||||
})
|
||||
}
|
||||
}
|
||||
socket.end()
|
||||
})
|
||||
|
||||
socket.on('error', (err) => {
|
||||
socket.destroy()
|
||||
reject(err)
|
||||
// ENOTFOUND = Unable to resolve.
|
||||
// ECONNREFUSED = Unable to connect to port.
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
}
|
0
src/util/DistroTypes.ts
Normal file
0
src/util/DistroTypes.ts
Normal file
68
src/util/MinecraftUtil.ts
Normal file
68
src/util/MinecraftUtil.ts
Normal file
@ -0,0 +1,68 @@
|
||||
export class MinecraftUtil {
|
||||
|
||||
/**
|
||||
* Returns true if the actual version is greater than
|
||||
* or equal to the desired version.
|
||||
*
|
||||
* @param {string} desired The desired version.
|
||||
* @param {string} actual The actual version.
|
||||
*/
|
||||
public static mcVersionAtLeast(desired: string, actual: string) {
|
||||
const des = desired.split(".");
|
||||
const act = actual.split(".");
|
||||
|
||||
for (let i = 0; i < des.length; i++) {
|
||||
if (!(parseInt(act[i]) >= parseInt(des[i]))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static isForgeGradle3(mcVersion: string, forgeVersion: string) {
|
||||
|
||||
if (this.mcVersionAtLeast('1.13', mcVersion)) {
|
||||
return true
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
const forgeVer = forgeVersion.split('-')[1]
|
||||
|
||||
const maxFG2 = [14, 23, 5, 2847]
|
||||
const verSplit = forgeVer.split('.').map(v => Number(v))
|
||||
|
||||
for (let i = 0; i < maxFG2.length; i++) {
|
||||
if (verSplit[i] > maxFG2[i]) {
|
||||
return true
|
||||
} else if (verSplit[i] < maxFG2[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
|
||||
} catch (err) {
|
||||
throw new Error('Forge version is complex (changed).. launcher requires a patch.')
|
||||
}
|
||||
}
|
||||
|
||||
public static isAutoconnectBroken(forgeVersion: string) {
|
||||
|
||||
const minWorking = [31, 2, 15]
|
||||
const verSplit = forgeVersion.split('.').map(v => Number(v))
|
||||
|
||||
if (verSplit[0] === 31) {
|
||||
for (let i = 0; i < minWorking.length; i++) {
|
||||
if (verSplit[i] > minWorking[i]) {
|
||||
return false
|
||||
} else if (verSplit[i] < minWorking[i]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
10
src/util/System.ts
Normal file
10
src/util/System.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import os from 'os';
|
||||
|
||||
export function resolveMaxRAM() {
|
||||
const mem = os.totalmem();
|
||||
return mem >= 8000000000 ? "4G" : mem >= 6000000000 ? "3G" : "2G";
|
||||
}
|
||||
|
||||
export function resolveMinRAM() {
|
||||
return resolveMaxRAM();
|
||||
}
|
15
src/util/isDev.ts
Normal file
15
src/util/isDev.ts
Normal file
@ -0,0 +1,15 @@
|
||||
const getFromEnv = parseInt(process.env.ELECTRON_IS_DEV ?? "", 10) === 1
|
||||
const isEnvSet = 'ELECTRON_IS_DEV' in process.env
|
||||
|
||||
export class DevUtil {
|
||||
private static enforceDevMode = false;
|
||||
|
||||
public static get IsDev() {
|
||||
//@ts-ignore
|
||||
return this.enforceDevMode ?? isEnvSet ? getFromEnv : (process.defaultApp || /node_modules[\\/]electron[\\/]/.test(process.execPath))
|
||||
}
|
||||
|
||||
public static set IsDev(value) {
|
||||
this.enforceDevMode = value;
|
||||
}
|
||||
}
|
55
src/views/app.ejs
Normal file
55
src/views/app.ejs
Normal file
@ -0,0 +1,55 @@
|
||||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta charset="utf-8" http-equiv="Content-Security-Policy" content="script-src 'self' 'sha256-In6B8teKZQll5heMl9bS7CESTbGvuAt3VVV86BUQBDk='"/>
|
||||
<title>Helios Launcher</title>
|
||||
<script src="./assets/js/scripts/uicore.js"></script>
|
||||
<script src="./assets/js/scripts/uibinder.js"></script>
|
||||
<link type="text/css" rel="stylesheet" href="./assets/css/launcher.css">
|
||||
<style>
|
||||
body {
|
||||
/*background: url('assets/images/backgrounds/<%=bkid%>.jpg') no-repeat center center fixed;*/
|
||||
transition: background-image 1s ease;
|
||||
background-image: url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD//gA+Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBkZWZhdWx0IHF1YWxpdHkK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAPwBwAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A8VooopDCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/9k=');
|
||||
background-size: cover;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
#main {
|
||||
display: none;
|
||||
height: calc(100% - 22px);
|
||||
background: linear-gradient(to top, rgba(0, 0, 0, 0.75) 0%, rgba(0, 0, 0, 0) 100%);
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
}
|
||||
#main[overlay] {
|
||||
filter: blur(3px) contrast(0.9) brightness(1.0);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body bkid="<%=bkid%>">
|
||||
<%- include('frame') %>
|
||||
<div id="main">
|
||||
<%- include('welcome') %>
|
||||
<%- include('login') %>
|
||||
<%- include('waiting') %>
|
||||
<%- include('loginOptions') %>
|
||||
<%- include('settings') %>
|
||||
<%- include('landing') %>
|
||||
</div>
|
||||
<%- include('overlay') %>
|
||||
<div id="loadingContainer">
|
||||
<div id="loadingContent">
|
||||
<div id="loadSpinnerContainer">
|
||||
<img id="loadCenterImage" src="assets/images/LoadingSeal.png">
|
||||
<img id="loadSpinnerImage" class="rotating" src="assets/images/LoadingText.png">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
// Load language
|
||||
for(let key of Object.keys(Lang.query('html'))){
|
||||
document.getElementById(key).innerHTML = Lang.query(`html.${key}`)
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
33
src/views/frame.ejs
Normal file
33
src/views/frame.ejs
Normal file
@ -0,0 +1,33 @@
|
||||
<div id="frameBar">
|
||||
<div id="frameResizableTop" class="frameDragPadder"></div>
|
||||
<div id="frameMain">
|
||||
<div class="frameResizableVert frameDragPadder"></div>
|
||||
<%if (process.platform === 'darwin') { %>
|
||||
<div id="frameContentDarwin">
|
||||
<div id="frameButtonDockDarwin">
|
||||
<button class="frameButtonDarwin fCb" id="frameButtonDarwin_close" tabIndex="-1"></button>
|
||||
<button class="frameButtonDarwin fMb" id="frameButtonDarwin_minimize" tabIndex="-1"></button>
|
||||
<button class="frameButtonDarwin fRb" id="frameButtonDarwin_restoredown" tabIndex="-1"></button>
|
||||
</div>
|
||||
</div>
|
||||
<% } else{ %>
|
||||
<div id="frameContentWin">
|
||||
<div id="frameTitleDock">
|
||||
<span id="frameTitleText">Helios Launcher</span>
|
||||
</div>
|
||||
<div id="frameButtonDockWin">
|
||||
<button class="frameButton fMb" id="frameButton_minimize" tabIndex="-1">
|
||||
<svg name="TitleBarMinimize" width="10" height="10" viewBox="0 0 12 12"><rect stroke="#ffffff" fill="#ffffff" width="10" height="1" x="1" y="6"></rect></svg>
|
||||
</button>
|
||||
<button class="frameButton fRb" id="frameButton_restoredown" tabIndex="-1">
|
||||
<svg name="TitleBarMaximize" width="10" height="10" viewBox="0 0 12 12"><rect width="9" height="9" x="1.5" y="1.5" fill="none" stroke="#ffffff" stroke-width="1.4px"></rect></svg>
|
||||
</button>
|
||||
<button class="frameButton fCb" id="frameButton_close" tabIndex="-1">
|
||||
<svg name="TitleBarClose" width="10" height="10" viewBox="0 0 12 12"><polygon stroke="#ffffff" fill="#ffffff" fill-rule="evenodd" points="11 1.576 6.583 6 11 10.424 10.424 11 6 6.583 1.576 11 1 10.424 5.417 6 1 1.576 1.576 1 6 5.417 10.424 1"></polygon></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
<div class="frameResizableVert frameDragPadder"></div>
|
||||
</div>
|
||||
</div>
|
220
src/views/landing.ejs
Normal file
220
src/views/landing.ejs
Normal file
@ -0,0 +1,220 @@
|
||||
<div id="landingContainer" style="display: none;">
|
||||
<div id="upper">
|
||||
<div id="left">
|
||||
<div id="image_seal_container">
|
||||
<img id="image_seal" src="assets/images/SealCircle.png"/>
|
||||
<div id="updateAvailableTooltip">Update Available</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="content">
|
||||
</div>
|
||||
<div id="right">
|
||||
<div id="rightContainer">
|
||||
<div id="user_content">
|
||||
<span id="user_text">Username</span>
|
||||
<div id="avatarContainer">
|
||||
<button id="avatarOverlay">Edit</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="mediaContent">
|
||||
<div id="internalMedia">
|
||||
<div class="mediaContainer" id="settingsMediaContainer">
|
||||
<button class="mediaButton" id="settingsMediaButton">
|
||||
<svg id="settingsSVG" class="mediaSVG" viewBox="0 0 141.36 137.43">
|
||||
<path d="M70.70475616319865,83.36934004916053 a15.320781354859122,15.320781354859122 0 1 1 14.454501310561755,-15.296030496450625 A14.850515045097694,14.850515045097694 0 0 1 70.70475616319865,83.36934004916053 M123.25082856443602,55.425620905968366 h-12.375429204248078 A45.54157947163293,45.54157947163293 0 0 0 107.21227231573047,46.243052436416285 l8.613298726156664,-9.108315894326587 a9.727087354538993,9.727087354538993 0 0 0 0,-13.167456673319956 l-3.465120177189462,-3.6631270444574313 a8.489544434114185,8.489544434114185 0 0 0 -12.375429204248078,0 l-8.613298726156664,9.108315894326587 A40.442902639482725,40.442902639482725 0 0 0 81.99114759747292,25.427580514871032 V12.532383284044531 a9.108315894326587,9.108315894326587 0 0 0 -8.811305593424633,-9.306322761594556 h-4.950171681699231 a9.108315894326587,9.108315894326587 0 0 0 -8.811305593424633,9.306322761594556 v12.895197230826497 a40.17064319698927,40.17064319698927 0 0 0 -9.331073620003052,4.0591407789933704 l-8.613298726156664,-9.108315894326587 a8.489544434114185,8.489544434114185 0 0 0 -12.375429204248078,0 L25.58394128451018,23.967279868769744 a9.727087354538993,9.727087354538993 0 0 0 0,13.167456673319956 L34.19724001066683,46.243052436416285 a45.07131316187151,45.07131316187151 0 0 0 -3.6631270444574313,9.083565035918088 h-12.375429204248078 a9.083565035918088,9.083565035918088 0 0 0 -8.811305593424633,9.306322761594556 v5.197680265784193 a9.108315894326587,9.108315894326587 0 0 0 8.811305593424633,9.306322761594556 h11.979415469712139 a45.69008462208391,45.69008462208391 0 0 0 4.0591407789933704,10.642869115653347 l-8.613298726156664,9.108315894326587 a9.727087354538993,9.727087354538993 0 0 0 0,13.167456673319956 l3.465120177189462,3.6631270444574313 a8.489544434114185,8.489544434114185 0 0 0 12.375429204248078,0 l8.613298726156664,-9.108315894326587 a40.49240435629971,40.49240435629971 0 0 0 9.331073620003052,4.0591407789933704 v12.895197230826497 a9.083565035918088,9.083565035918088 0 0 0 8.811305593424633,9.306322761594556 h4.950171681699231 A9.083565035918088,9.083565035918088 0 0 0 81.99114759747292,123.68848839660077 V110.79329116577425 a40.78941465720167,40.78941465720167 0 0 0 9.331073620003052,-4.0591407789933704 l8.613298726156664,9.108315894326587 a8.489544434114185,8.489544434114185 0 0 0 12.375429204248078,0 l3.465120177189462,-3.6631270444574313 a9.727087354538993,9.727087354538993 0 0 0 0,-13.167456673319956 l-8.613298726156664,-9.108315894326587 a45.665333763675406,45.665333763675406 0 0 0 4.034389920584874,-10.642869115653347 h12.004166328120636 a9.108315894326587,9.108315894326587 0 0 0 8.811305593424633,-9.306322761594556 v-5.197680265784193 a9.083565035918088,9.083565035918088 0 0 0 -8.811305593424633,-9.306322761594556 " id="svg_3" class=""/>
|
||||
</svg>
|
||||
<div id="settingsTooltip">Settings</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mediaDivider"></div>
|
||||
<div id="externalMedia">
|
||||
<div class="mediaContainer">
|
||||
<a href="https://github.com/dscalzi/HeliosLauncher" class="mediaURL" id="linkURL">
|
||||
<svg id="linkSVG" class="mediaSVG" viewBox="35.34 34.3575 70.68 68.71500">
|
||||
<g>
|
||||
<path d="M75.37,65.51a3.85,3.85,0,0,0-1.73.42,8.22,8.22,0,0,1,.94,3.76A8.36,8.36,0,0,1,66.23,78H46.37a8.35,8.35,0,1,1,0-16.7h9.18a21.51,21.51,0,0,1,6.65-8.72H46.37a17.07,17.07,0,1,0,0,34.15H66.23A17,17,0,0,0,82.77,65.51Z"/>
|
||||
<path d="M66,73.88a3.85,3.85,0,0,0,1.73-.42,8.22,8.22,0,0,1-.94-3.76,8.36,8.36,0,0,1,8.35-8.35H95A8.35,8.35,0,1,1,95,78H85.8a21.51,21.51,0,0,1-6.65,8.72H95a17.07,17.07,0,0,0,0-34.15H75.13A17,17,0,0,0,58.59,73.88Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<div class="mediaContainer">
|
||||
<a href="#" class="mediaURL" id="twitterURL" disabled>
|
||||
<svg id="twitterSVG" class="mediaSVG" viewBox="0 0 5000 4060" preserveAspectRatio="xMidYMid meet">
|
||||
<g>
|
||||
<path d="M1210 4048 c-350 -30 -780 -175 -1124 -378 -56 -33 -86 -57 -86 -68 0 -16 7 -17 83 -9 114 12 349 1 493 -22 295 -49 620 -180 843 -341 l54 -38 -49 -7 c-367 -49 -660 -256 -821 -582 -30 -61 -53 -120 -51 -130 3 -16 12 -17 73 -13 97 7 199 5 270 -4 l60 -9 -65 -22 c-341 -117 -609 -419 -681 -769 -18 -88 -26 -226 -13 -239 4 -3 32 7 63 22 68 35 198 77 266 86 28 4 58 9 68 12 10 2 -22 -34 -72 -82 -240 -232 -353 -532 -321 -852 15 -149 79 -347 133 -418 16 -20 17 -19 49 20 377 455 913 795 1491 945 160 41 346 74 485 86 l82 7 -7 -59 c-5 -33 -7 -117 -6 -189 2 -163 31 -286 103 -430 141 -285 422 -504 708 -550 112 -19 333 -19 442 0 180 30 335 108 477 239 l58 54 95 -24 c143 -36 286 -89 427 -160 70 -35 131 -60 135 -56 19 19 -74 209 -151 312 -50 66 -161 178 -216 217 l-30 22 73 -14 c111 -21 257 -63 353 -101 99 -39 99 -39 99 -19 0 57 -237 326 -412 468 l-88 71 6 51 c4 28 1 130 -5 226 -30 440 -131 806 -333 1202 -380 745 -1036 1277 -1823 1477 -243 62 -430 81 -786 78 -134 0 -291 -5 -349 -10z"/>
|
||||
</g>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<div class="mediaContainer">
|
||||
<a href="#" class="mediaURL" id="instagramURL" disabled>
|
||||
<svg id="instagramSVG" class="mediaSVG" viewBox="0 0 5040 5040">
|
||||
<defs>
|
||||
<radialGradient id="instaFill" cx="30%" cy="107%" r="150%">
|
||||
<stop offset="0%" stop-color="#fdf497"/>
|
||||
<stop offset="5%" stop-color="#fdf497"/>
|
||||
<stop offset="45%" stop-color="#fd5949"/>
|
||||
<stop offset="60%" stop-color="#d6249f"/>
|
||||
<stop offset="90%" stop-color="#285AEB"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<g>
|
||||
<path d="M1390 5024 c-163 -9 -239 -19 -315 -38 -281 -70 -477 -177 -660 -361 -184 -184 -292 -380 -361 -660 -43 -171 -53 -456 -53 -1445 0 -989 10 -1274 53 -1445 69 -280 177 -476 361 -660 184 -184 380 -292 660 -361 171 -43 456 -53 1445 -53 989 0 1274 10 1445 53 280 69 476 177 660 361 184 184 292 380 361 660 43 171 53 456 53 1445 0 989 -10 1274 -53 1445 -69 280 -177 476 -361 660 -184 184 -380 292 -660 361 -174 44 -454 53 -1470 52 -599 0 -960 -5 -1105 -14z m2230 -473 c58 -6 141 -18 185 -27 397 -78 638 -318 719 -714 37 -183 41 -309 41 -1290 0 -981 -4 -1107 -41 -1290 -81 -395 -319 -633 -714 -714 -183 -37 -309 -41 -1290 -41 -981 0 -1107 4 -1290 41 -397 81 -636 322 -714 719 -33 166 -38 296 -43 1100 -5 796 3 1203 27 1380 67 489 338 758 830 825 47 7 162 15 255 20 250 12 1907 4 2035 -9z"/>
|
||||
<path d="M2355 3819 c-307 -42 -561 -172 -780 -400 -244 -253 -359 -543 -359 -899 0 -361 116 -648 367 -907 262 -269 563 -397 937 -397 374 0 675 128 937 397 251 259 367 546 367 907 0 361 -116 648 -367 907 -197 203 -422 326 -690 378 -101 20 -317 27 -412 14z m400 -509 c275 -88 470 -284 557 -560 20 -65 23 -95 23 -230 0 -135 -3 -165 -23 -230 -88 -278 -284 -474 -562 -562 -65 -20 -95 -23 -230 -23 -135 0 -165 3 -230 23 -278 88 -474 284 -562 562 -20 65 -23 95 -23 230 0 135 3 165 23 230 73 230 219 403 427 507 134 67 212 83 390 79 111 -3 155 -8 210 -26z"/>
|
||||
<path d="M3750 1473 c-29 -11 -66 -38 -106 -77 -70 -71 -94 -126 -94 -221 0 -95 24 -150 94 -221 72 -71 126 -94 225 -94 168 0 311 143 311 311 0 99 -23 154 -94 225 -43 42 -76 66 -110 77 -61 21 -166 21 -226 0z"/>
|
||||
</g>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<div class="mediaContainer">
|
||||
<a href="#" class="mediaURL" id="youtubeURL" disabled>
|
||||
<svg id="youtubeSVG" class="mediaSVG" viewBox="35.34 34.3575 70.68 68.71500">
|
||||
<g>
|
||||
<path d="M84.8,69.52,65.88,79.76V59.27Zm23.65.59c0-5.14-.79-17.63-3.94-20.57S99,45.86,73.37,45.86s-28,.73-31.14,3.68S38.29,65,38.29,70.11s.79,17.63,3.94,20.57,5.52,3.68,31.14,3.68,28-.74,31.14-3.68,3.94-15.42,3.94-20.57"/>
|
||||
</g>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<div class="mediaContainer">
|
||||
<a href="https://discord.gg/zNWUXdt" class="mediaURL" id="discordURL">
|
||||
<svg id="discordSVG" class="mediaSVG" viewBox="35.34 34.3575 70.68 68.71500">
|
||||
<g>
|
||||
<path d="M81.23,78.48a6.14,6.14,0,1,1,6.14-6.14,6.14,6.14,0,0,1-6.14,6.14M60,78.48a6.14,6.14,0,1,1,6.14-6.14A6.14,6.14,0,0,1,60,78.48M104.41,73c-.92-7.7-8.24-22.9-8.24-22.9A43,43,0,0,0,88,45.59a17.88,17.88,0,0,0-8.38-1.27l-.13,1.06a23.52,23.52,0,0,1,5.8,1.95,87.59,87.59,0,0,1,8.17,4.87s-10.32-5.63-22.27-5.63a51.32,51.32,0,0,0-23.2,5.63,87.84,87.84,0,0,1,8.17-4.87,23.57,23.57,0,0,1,5.8-1.95l-.13-1.06a17.88,17.88,0,0,0-8.38,1.27,42.84,42.84,0,0,0-8.21,4.56S37.87,65.35,37,73s-.37,11.54-.37,11.54,4.22,5.68,9.9,7.14,7.7,1.47,7.7,1.47l3.75-4.68a21.22,21.22,0,0,1-4.65-2A24.47,24.47,0,0,1,47.93,82S61.16,88.4,70.68,88.4c10,0,22.75-6.44,22.75-6.44a24.56,24.56,0,0,1-5.35,4.56,21.22,21.22,0,0,1-4.65,2l3.75,4.68s2,0,7.7-1.47,9.89-7.14,9.89-7.14.55-3.85-.37-11.54"/>
|
||||
</g>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="lower">
|
||||
<div id="left">
|
||||
<div class="bot_wrapper">
|
||||
<div id="content">
|
||||
<div id="server_status_wrapper">
|
||||
<span class="bot_label" id="landingPlayerLabel">SERVER</span>
|
||||
<span id="player_count">OFFLINE</span>
|
||||
</div>
|
||||
<div class="bot_divider"></div>
|
||||
<div id="mojangStatusWrapper">
|
||||
<span class="bot_label">MOJANG STATUS</span>
|
||||
<span id="mojang_status_icon">•</span>
|
||||
<div id="mojangStatusTooltip">
|
||||
<div id="mojangStatusTooltipTitle">Services</div>
|
||||
<div id="mojangStatusEssentialContainer">
|
||||
<!-- Essential Mojang services are populated here. -->
|
||||
</div>
|
||||
<div id="mojangStatusNEContainer">
|
||||
<div class="mojangStatusNEBar"></div>
|
||||
<div id="mojangStatusNETitle">Non Essential</div>
|
||||
<div class="mojangStatusNEBar"></div>
|
||||
</div>
|
||||
<div id="mojangStatusNonEssentialContainer">
|
||||
<!-- Non Essential Mojang services are populated here. -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="center">
|
||||
<div class="bot_wrapper">
|
||||
<div id="content">
|
||||
<button id="newsButton">
|
||||
<!--<img src="assets/images/icons/arrow.svg" id="newsButtonSVG"/>-->
|
||||
<div id="newsButtonAlert" style="display: none;"></div>
|
||||
<svg id="newsButtonSVG" viewBox="0 0 24.87 13.97">
|
||||
<defs>
|
||||
<style>.arrowLine{fill:none;stroke:#FFF;stroke-width:2px;}</style>
|
||||
</defs>
|
||||
<polyline class="arrowLine" points="0.71 13.26 12.56 1.41 24.16 13.02"/>
|
||||
</svg>
|
||||
<span id="newsButtonText">NEWS</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="right">
|
||||
<div class="bot_wrapper">
|
||||
<div id="launch_content">
|
||||
<button id="launch_button">PLAY</button>
|
||||
<div class="bot_divider"></div>
|
||||
<button id="server_selection_button" class="bot_label">• No Server Selected</button>
|
||||
</div>
|
||||
<div id="launch_details">
|
||||
<div id="launch_details_left">
|
||||
<span id="launch_progress_label">0%</span>
|
||||
<div class="bot_divider"></div>
|
||||
</div>
|
||||
<div id="launch_details_right">
|
||||
<progress id="launch_progress" value="22" max="100"></progress>
|
||||
<span id="launch_details_text" class="bot_label">Please wait..</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="newsContainer">
|
||||
<div id="newsContent" article="-1" style="display: none;">
|
||||
<div id="newsStatusContainer">
|
||||
<div id="newsStatusContent">
|
||||
<div id="newsTitleContainer">
|
||||
<a id="newsArticleTitle" href="#">Lorem Ipsum</a>
|
||||
</div>
|
||||
<div id="newsMetaContainer">
|
||||
<div id="newsArticleDateWrapper">
|
||||
<span id="newsArticleDate">Mar 15, 44 BC, 9:14 AM</span>
|
||||
</div>
|
||||
<div id="newsArticleAuthorWrapper">
|
||||
<span id="newsArticleAuthor">by Cicero</span>
|
||||
</div>
|
||||
<a href="#" id="newsArticleComments">0 Comments</a>
|
||||
</div>
|
||||
</div>
|
||||
<div id="newsNavigationContainer">
|
||||
<button id="newsNavigateLeft">
|
||||
<svg id="newsNavigationLeftSVG" viewBox="0 0 24.87 13.97">
|
||||
<defs>
|
||||
<style>.arrowLine{fill:none;stroke:#FFF;stroke-width:2px;transition: 0.25s ease;}</style>
|
||||
</defs>
|
||||
<polyline class="arrowLine" points="0.71 13.26 12.56 1.41 24.16 13.02"/>
|
||||
</svg>
|
||||
</button>
|
||||
<span id="newsNavigationStatus">1 of 1</span>
|
||||
<button id="newsNavigateRight">
|
||||
<svg id="newsNavigationRightSVG" viewBox="0 0 24.87 13.97">
|
||||
<defs>
|
||||
<style>.arrowLine{fill:none;stroke:#FFF;stroke-width:2px;transition: 0.25s ease;}</style>
|
||||
</defs>
|
||||
<polyline class="arrowLine" points="0.71 13.26 12.56 1.41 24.16 13.02"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="newsArticleContainer">
|
||||
<div id="newsArticleContent">
|
||||
<div id="newsArticleContentScrollable">
|
||||
<!-- Article Content -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="newsErrorContainer">
|
||||
<div id="newsErrorLoading">
|
||||
<span id="nELoadSpan" class="newsErrorContent">Checking for News..</span>
|
||||
</div>
|
||||
<div id="newsErrorFailed" style="display: none;">
|
||||
<span id="nEFailedSpan" class="newsErrorContent">Failed to Load News</span>
|
||||
<button id="newsErrorRetry">Try Again</button>
|
||||
</div>
|
||||
<div id="newsErrorNone" style="display: none;">
|
||||
<span id="nENoneSpan" class="newsErrorContent">No News</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="./assets/js/scripts/landing.js"></script>
|
||||
</div>
|
65
src/views/login.ejs
Normal file
65
src/views/login.ejs
Normal file
@ -0,0 +1,65 @@
|
||||
<div id="loginContainer" style="display: none;">
|
||||
<div id="loginCancelContainer" style="display: none;">
|
||||
<button id="loginCancelButton">
|
||||
<div id="loginCancelIcon">X</div>
|
||||
<span id="loginCancelText">Cancel</span>
|
||||
</button>
|
||||
</div>
|
||||
<div id="loginContent">
|
||||
<form id="loginForm">
|
||||
<img id="loginImageSeal" src="assets/images/SealCircle.png"/>
|
||||
<span id="loginSubheader">MINECRAFT LOGIN</span>
|
||||
<div class="loginFieldContainer">
|
||||
<svg id="profileSVG" class="loginSVG" viewBox="40 37 65.36 61.43">
|
||||
<g>
|
||||
<path d="M86.77,58.12A13.79,13.79,0,1,0,73,71.91,13.79,13.79,0,0,0,86.77,58.12M97,103.67a3.41,3.41,0,0,0,3.39-3.84,27.57,27.57,0,0,0-54.61,0,3.41,3.41,0,0,0,3.39,3.84Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
<span class="loginErrorSpan" id="loginEmailError">* Invalid Value</span>
|
||||
<input id="loginUsername" class="loginField" type="text" placeholder="EMAIL OR USERNAME"/>
|
||||
</div>
|
||||
<div class="loginFieldContainer">
|
||||
<svg id="lockSVG" class="loginSVG" viewBox="40 32 60.36 70.43">
|
||||
<g>
|
||||
<path d="M86.16,54a16.38,16.38,0,1,0-32,0H44V102.7H96V54Zm-25.9-3.39a9.89,9.89,0,1,1,19.77,0A9.78,9.78,0,0,1,79.39,54H60.89A9.78,9.78,0,0,1,60.26,50.59ZM70,96.2a6.5,6.5,0,0,1-6.5-6.5,6.39,6.39,0,0,1,3.1-5.4V67h6.5V84.11a6.42,6.42,0,0,1,3.39,5.6A6.5,6.5,0,0,1,70,96.2Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
<span class="loginErrorSpan" id="loginPasswordError">* Required</span>
|
||||
<input id="loginPassword" class="loginField" type="password" placeholder="PASSWORD"/>
|
||||
</div>
|
||||
<div id="loginOptions">
|
||||
<span class="loginSpanDim">
|
||||
<a href="https://minecraft.net/password/forgot/">forgot password?</a>
|
||||
</span>
|
||||
<label id="checkmarkContainer">
|
||||
<input id="loginRememberOption" type="checkbox" checked>
|
||||
<span id="loginRememberText" class="loginSpanDim">remember me?</span>
|
||||
<span class="loginCheckmark"></span>
|
||||
</label>
|
||||
</div>
|
||||
<button id="loginButton" disabled>
|
||||
<div id="loginButtonContent">
|
||||
LOGIN
|
||||
<svg id="loginSVG" viewBox="0 0 24.87 13.97">
|
||||
<defs>
|
||||
<style>.arrowLine{fill:none;stroke:#FFF;stroke-width:2px;transition: 0.25s ease;}</style>
|
||||
</defs>
|
||||
<polyline class="arrowLine" points="0.71 13.26 12.56 1.41 24.16 13.02"/>
|
||||
</svg>
|
||||
<div class="circle-loader">
|
||||
<div class="checkmark draw"></div>
|
||||
</div>
|
||||
<!--<div class="spinningCircle" id="loginSpinner"></div>-->
|
||||
</div>
|
||||
</button>
|
||||
<div id="loginDisclaimer">
|
||||
<span class="loginSpanDim" id="loginRegisterSpan">
|
||||
<a href="https://minecraft.net/store/minecraft-java-edition/">Need an Account?</a>
|
||||
</span>
|
||||
<p class="loginDisclaimerText">Your password is sent directly to mojang and never stored.</p>
|
||||
<p class="loginDisclaimerText">Helios Launcher is not affiliated with Mojang AB.</p>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<script src="./assets/js/scripts/login.js"></script>
|
||||
</div>
|
34
src/views/loginOptions.ejs
Normal file
34
src/views/loginOptions.ejs
Normal file
@ -0,0 +1,34 @@
|
||||
<div id="loginOptionsContainer" style="display: none;">
|
||||
<div id="loginOptionsContent">
|
||||
<div class="loginOptionsMainContent">
|
||||
<h2>Login Options</h2>
|
||||
<div class="loginOptionActions">
|
||||
<div class="loginOptionButtonContainer">
|
||||
<button id="loginOptionMicrosoft" class="loginOptionButton">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 23 23">
|
||||
<path fill="#f35325" d="M1 1h10v10H1z" />
|
||||
<path fill="#81bc06" d="M12 1h10v10H12z" />
|
||||
<path fill="#05a6f0" d="M1 12h10v10H1z" />
|
||||
<path fill="#ffba08" d="M12 12h10v10H12z" />
|
||||
</svg>
|
||||
<span>Login with Microsoft</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="loginOptionButtonContainer">
|
||||
<button id="loginOptionMojang" class="loginOptionButton">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 9.677 9.667">
|
||||
<path d="M-26.332-12.098h2.715c-1.357.18-2.574 1.23-2.715 2.633z" fill="#fff" />
|
||||
<path d="M2.598.022h7.07L9.665 7c-.003 1.334-1.113 2.46-2.402 2.654H0V2.542C.134 1.2 1.3.195 2.598.022z" fill="#db2331" />
|
||||
<path d="M1.54 2.844c.314-.76 1.31-.46 1.954-.528.785-.083 1.503.272 2.1.758l.164-.9c.327.345.587.756.964 1.052.28.254.655-.342.86-.013.42.864.408 1.86.54 2.795l-.788-.373C6.9 4.17 5.126 3.052 3.656 3.685c-1.294.592-1.156 2.65.06 3.255 1.354.703 2.953.51 4.405.292-.07.42-.34.87-.834.816l-4.95.002c-.5.055-.886-.413-.838-.89l.04-4.315z" fill="#fff" />
|
||||
</svg>
|
||||
<span>Login with Mojang</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="loginOptionCancelContainer" style="display: none;">
|
||||
<button id="loginOptionCancelButton">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="./assets/js/scripts/loginOptions.js"></script>
|
||||
</div>
|
41
src/views/overlay.ejs
Normal file
41
src/views/overlay.ejs
Normal file
@ -0,0 +1,41 @@
|
||||
<div id="overlayContainer" style="display: none;">
|
||||
<div id="serverSelectContent" style="display: none;">
|
||||
<span id="serverSelectHeader">Available Servers</span>
|
||||
<div id="serverSelectList">
|
||||
<div id="serverSelectListScrollable">
|
||||
<!-- Server listings populated here. -->
|
||||
</div>
|
||||
</div>
|
||||
<div id="serverSelectActions">
|
||||
<button id="serverSelectConfirm" class="overlayKeybindEnter" type="submit">Select</button>
|
||||
<div id="serverSelectCancelWrapper">
|
||||
<button id="serverSelectCancel" class="overlayKeybindEsc">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="accountSelectContent" style="display: none;">
|
||||
<span id="accountSelectHeader">Select an Account</span>
|
||||
<div id="accountSelectList">
|
||||
<div id="accountSelectListScrollable">
|
||||
<!-- Accounts populated here. -->
|
||||
</div>
|
||||
</div>
|
||||
<div id="accountSelectActions">
|
||||
<button id="accountSelectConfirm" class="overlayKeybindEnter" type="submit">Select</button>
|
||||
<div id="accountSelectCancelWrapper">
|
||||
<button id="accountSelectCancel" class="overlayKeybindEsc">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="overlayContent">
|
||||
<span id="overlayTitle">Lorem Ipsum:<br>Finis Illud</span>
|
||||
<span id="overlayDesc">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud..</span>
|
||||
<div id="overlayActionContainer">
|
||||
<button id="overlayAcknowledge" class="overlayKeybindEnter">Conare Iterum</button>
|
||||
<div id="overlayDismissWrapper">
|
||||
<button id="overlayDismiss" style="display: none;" class="overlayKeybindEsc">Dismiss</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="./assets/js/scripts/overlay.js"></script>
|
||||
</div>
|
393
src/views/settings.ejs
Normal file
393
src/views/settings.ejs
Normal file
@ -0,0 +1,393 @@
|
||||
<div id="settingsContainer" style="display: none;">
|
||||
<div id="settingsContainerLeft">
|
||||
<div id="settingsNavContainer">
|
||||
<div id="settingsNavHeader">
|
||||
<span id="settingsNavHeaderText">Settings</span>
|
||||
</div>
|
||||
<div id="settingsNavItemsContainer">
|
||||
<div id="settingsNavItemsContent">
|
||||
<button class="settingsNavItem" rSc="settingsTabAccount" id="settingsNavAccount" selected>Account</button>
|
||||
<button class="settingsNavItem" rSc="settingsTabMinecraft">Minecraft</button>
|
||||
<button class="settingsNavItem" rSc="settingsTabMods">Mods</button>
|
||||
<button class="settingsNavItem" rSc="settingsTabJava">Java</button>
|
||||
<button class="settingsNavItem" rSc="settingsTabLauncher">Launcher</button>
|
||||
<div class="settingsNavSpacer"></div>
|
||||
<button class="settingsNavItem" rSc="settingsTabAbout">About</button>
|
||||
<button class="settingsNavItem" rSc="settingsTabUpdate" id="settingsNavUpdate">Updates</button>
|
||||
<div id="settingsNavContentBottom">
|
||||
<div class="settingsNavDivider"></div>
|
||||
<button id="settingsNavDone">Done</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="settingsContainerRight">
|
||||
<div id="settingsTabAccount" class="settingsTab">
|
||||
<div class="settingsTabHeader">
|
||||
<span class="settingsTabHeaderText">Account Settings</span>
|
||||
<span class="settingsTabHeaderDesc">Add new accounts or manage existing ones.</span>
|
||||
</div>
|
||||
<div class="settingsAuthAccountTypeContainer">
|
||||
<div class="settingsAuthAccountTypeHeader">
|
||||
<div class="settingsAuthAccountTypeHeaderLeft">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 23 23">
|
||||
<path fill="#f35325" d="M1 1h10v10H1z" />
|
||||
<path fill="#81bc06" d="M12 1h10v10H12z" />
|
||||
<path fill="#05a6f0" d="M1 12h10v10H1z" />
|
||||
<path fill="#ffba08" d="M12 12h10v10H12z" />
|
||||
</svg>
|
||||
<span>Microsoft</span>
|
||||
</div>
|
||||
<div class="settingsAuthAccountTypeHeaderRight">
|
||||
<button class="settingsAddAuthAccount" id="settingsAddMicrosoftAccount">+ Add Microsoft Account</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settingsCurrentAccounts" id="settingsCurrentMicrosoftAccounts">
|
||||
<!-- Microsoft auth accounts populated here. -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settingsAuthAccountTypeContainer">
|
||||
<div class="settingsAuthAccountTypeHeader">
|
||||
<div class="settingsAuthAccountTypeHeaderLeft">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 9.677 9.667">
|
||||
<path d="M-26.332-12.098h2.715c-1.357.18-2.574 1.23-2.715 2.633z" fill="#fff" />
|
||||
<path d="M2.598.022h7.07L9.665 7c-.003 1.334-1.113 2.46-2.402 2.654H0V2.542C.134 1.2 1.3.195 2.598.022z" fill="#db2331" />
|
||||
<path d="M1.54 2.844c.314-.76 1.31-.46 1.954-.528.785-.083 1.503.272 2.1.758l.164-.9c.327.345.587.756.964 1.052.28.254.655-.342.86-.013.42.864.408 1.86.54 2.795l-.788-.373C6.9 4.17 5.126 3.052 3.656 3.685c-1.294.592-1.156 2.65.06 3.255 1.354.703 2.953.51 4.405.292-.07.42-.34.87-.834.816l-4.95.002c-.5.055-.886-.413-.838-.89l.04-4.315z" fill="#fff" />
|
||||
</svg>
|
||||
<span>Mojang</span>
|
||||
</div>
|
||||
<div class="settingsAuthAccountTypeHeaderRight">
|
||||
<button class="settingsAddAuthAccount" id="settingsAddMojangAccount">+ Add Mojang Account</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settingsCurrentAccounts" id="settingsCurrentMojangAccounts">
|
||||
<!-- Mojang auth accounts populated here. -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="settingsTabMinecraft" class="settingsTab" style="display: none;">
|
||||
<div class="settingsTabHeader">
|
||||
<span class="settingsTabHeaderText">Minecraft Settings</span>
|
||||
<span class="settingsTabHeaderDesc">Options related to game launch.</span>
|
||||
</div>
|
||||
<div id="settingsGameResolutionContainer">
|
||||
<span class="settingsFieldTitle">Game Resolution</span>
|
||||
<div id="settingsGameResolutionContent">
|
||||
<input type="number" id="settingsGameWidth" min="0" cValue="GameWidth">
|
||||
<div id="settingsGameResolutionCross">✖</div>
|
||||
<input type="number" id="settingsGameHeight" min="0" cValue="GameHeight">
|
||||
</div>
|
||||
</div>
|
||||
<div class="settingsFieldContainer">
|
||||
<div class="settingsFieldLeft">
|
||||
<span class="settingsFieldTitle">Launch in fullscreen.</span>
|
||||
</div>
|
||||
<div class="settingsFieldRight">
|
||||
<label class="toggleSwitch">
|
||||
<input type="checkbox" cValue="Fullscreen">
|
||||
<span class="toggleSwitchSlider"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settingsFieldContainer">
|
||||
<div class="settingsFieldLeft">
|
||||
<span class="settingsFieldTitle">Automatically connect to the server on launch.</span>
|
||||
</div>
|
||||
<div class="settingsFieldRight">
|
||||
<label class="toggleSwitch">
|
||||
<input type="checkbox" cValue="AutoConnect">
|
||||
<span class="toggleSwitchSlider"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settingsFieldContainer">
|
||||
<div class="settingsFieldLeft">
|
||||
<span class="settingsFieldTitle">Launch game process detached from launcher.</span>
|
||||
<span class="settingsFieldDesc">If the game is not detached, closing the launcher will also close the game.</span>
|
||||
</div>
|
||||
<div class="settingsFieldRight">
|
||||
<label class="toggleSwitch">
|
||||
<input type="checkbox" cValue="LaunchDetached">
|
||||
<span class="toggleSwitchSlider"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="settingsTabMods" class="settingsTab" style="display: none;">
|
||||
<div class="settingsTabHeader">
|
||||
<span class="settingsTabHeaderText">Mod Settings</span>
|
||||
<span class="settingsTabHeaderDesc">Enable or disable mods.</span>
|
||||
</div>
|
||||
<div class="settingsSelServContainer">
|
||||
<div class="settingsSelServContent">
|
||||
|
||||
</div>
|
||||
<div class="settingsSwitchServerContainer">
|
||||
<div class="settingsSwitchServerContent">
|
||||
<button class="settingsSwitchServerButton">Switch</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="settingsModsContainer">
|
||||
<div id="settingsReqModsContainer">
|
||||
<div class="settingsModsHeader">Required Mods</div>
|
||||
<div id="settingsReqModsContent">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div id="settingsOptModsContainer">
|
||||
<div class="settingsModsHeader">Optional Mods</div>
|
||||
<div id="settingsOptModsContent">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div id="settingsDropinModsContainer">
|
||||
<div class="settingsModsHeader">Drop-in Mods</div>
|
||||
<button id="settingsDropinFileSystemButton">+ Add Mods <span id="settingsDropinRefreshNote">(F5 to Refresh)</span></button>
|
||||
<div id="settingsDropinModsContent">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div id="settingsShadersContainer">
|
||||
<div class="settingsModsHeader">Shaderpacks</div>
|
||||
<div id="settingsShaderpackDesc">Enable or disable shaders. Please note, shaders will only run smoothly on powerful setups. You may add custom packs here.</div>
|
||||
<div id="settingsShaderpackWrapper">
|
||||
<button id="settingsShaderpackButton"> + </button>
|
||||
<div class="settingsSelectContainer">
|
||||
<div class="settingsSelectSelected" id="settingsShadersSelected">Select Shaderpack</div>
|
||||
<div class="settingsSelectOptions" id="settingsShadersOptions" hidden>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="settingsTabJava" class="settingsTab" style="display: none;">
|
||||
<div class="settingsTabHeader">
|
||||
<span class="settingsTabHeaderText">Java Settings</span>
|
||||
<span class="settingsTabHeaderDesc">Manage the Java configuration (advanced).</span>
|
||||
</div>
|
||||
<div class="settingsSelServContainer">
|
||||
<div class="settingsSelServContent">
|
||||
|
||||
</div>
|
||||
<div class="settingsSwitchServerContainer">
|
||||
<div class="settingsSwitchServerContent">
|
||||
<button class="settingsSwitchServerButton">Switch</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="settingsMemoryContainer">
|
||||
<div id="settingsMemoryTitle">Memory</div>
|
||||
<div id="settingsMemoryContent">
|
||||
<div id="settingsMemoryContentLeft">
|
||||
<div class="settingsMemoryContentItem">
|
||||
<span class="settingsMemoryHeader">Maximum RAM</span>
|
||||
<div class="settingsMemoryActionContainer">
|
||||
<div id="settingsMaxRAMRange" class="rangeSlider" cValue="MaxRAM" serverDependent min="3" max="8" value="3" step="0.5">
|
||||
<div class="rangeSliderBar"></div>
|
||||
<div class="rangeSliderTrack"></div>
|
||||
</div>
|
||||
<span id="settingsMaxRAMLabel" class="settingsMemoryLabel">3G</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settingsMemoryContentItem">
|
||||
<span class="settingsMemoryHeader">Minimum RAM</span>
|
||||
<div class="settingsMemoryActionContainer">
|
||||
<div id="settingsMinRAMRange" class="rangeSlider" cValue="MinRAM" serverDependent min="3" max="8" value="3" step="0.5">
|
||||
<div class="rangeSliderBar"></div>
|
||||
<div class="rangeSliderTrack"></div>
|
||||
</div>
|
||||
<span id="settingsMinRAMLabel" class="settingsMemoryLabel">3G</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="settingsMemoryDesc">The recommended minimum RAM is 3 gigabytes. Setting the minimum and maximum values to the same value may reduce lag.</div>
|
||||
</div>
|
||||
<div id="settingsMemoryContentRight">
|
||||
<div id="settingsMemoryStatus">
|
||||
<div class="settingsMemoryStatusContainer">
|
||||
<span class="settingsMemoryStatusTitle">Total</span>
|
||||
<span id="settingsMemoryTotal" class="settingsMemoryStatusValue">16G</span>
|
||||
</div>
|
||||
<div class="settingsMemoryStatusContainer">
|
||||
<span class="settingsMemoryStatusTitle">Available</span>
|
||||
<span id="settingsMemoryAvail" class="settingsMemoryStatusValue">7.3G</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settingsFileSelContainer">
|
||||
<div class="settingsFileSelTitle">Java Executable</div>
|
||||
<div class="settingsFileSelContent">
|
||||
<div id="settingsJavaExecDetails">Selected: Java 8 Update 172 (x64)</div>
|
||||
<div class="settingsFileSelActions">
|
||||
<div class="settingsFileSelIcon">
|
||||
<svg class="settingsFileSelSVG" x="0px" y="0px" viewBox="0 0 305.001 305.001">
|
||||
<g>
|
||||
<path d="M150.99,56.513c-14.093,9.912-30.066,21.147-38.624,39.734c-14.865,32.426,30.418,67.798,32.353,69.288c0.45,0.347,0.988,0.519,1.525,0.519c0.57,0,1.141-0.195,1.605-0.583c0.899-0.752,1.154-2.029,0.614-3.069c-0.164-0.316-16.418-31.888-15.814-54.539c0.214-7.888,11.254-16.837,22.942-26.312c10.705-8.678,22.839-18.514,29.939-30.02c15.586-25.327-1.737-50.231-1.914-50.479c-0.688-0.966-1.958-1.317-3.044-0.84c-1.085,0.478-1.686,1.652-1.438,2.811c0.035,0.164,3.404,16.633-5.97,33.6C169.301,43.634,160.816,49.603,150.99,56.513z"></path>
|
||||
<path d="M210.365,67.682c0.994-0.749,1.286-2.115,0.684-3.205c-0.602-1.09-1.913-1.571-3.077-1.129c-2.394,0.91-58.627,22.585-58.627,48.776c0,18.053,7.712,27.591,13.343,34.556c2.209,2.731,4.116,5.09,4.744,7.104c1.769,5.804-2.422,16.294-4.184,19.846c-0.508,1.022-0.259,2.259,0.605,3.005c0.467,0.403,1.05,0.607,1.634,0.607c0.497,0,0.996-0.148,1.427-0.448c0.967-0.673,23.63-16.696,19.565-36.001c-1.514-7.337-5.12-12.699-8.302-17.43c-4.929-7.329-8.489-12.624-3.088-22.403C181.419,89.556,210.076,67.899,210.365,67.682z"></path>
|
||||
<path d="M63.99,177.659c-0.964,2.885-0.509,5.75,1.315,8.283c6.096,8.462,27.688,13.123,60.802,13.123c0.002,0,0.003,0,0.004,0c4.487,0,9.224-0.088,14.076-0.262c52.943-1.896,72.58-18.389,73.39-19.09c0.883-0.764,1.119-2.037,0.57-3.067c-0.549-1.029-1.733-1.546-2.864-1.235c-18.645,5.091-53.463,6.898-77.613,6.898c-27.023,0-40.785-1.946-44.154-3.383c1.729-2.374,12.392-6.613,25.605-9.212c1.263-0.248,2.131-1.414,2.006-2.695c-0.125-1.281-1.201-2.258-2.488-2.258C106.893,164.762,68.05,165.384,63.99,177.659z"></path>
|
||||
<path d="M241.148,160.673c-10.92,0-21.275,5.472-21.711,5.705c-1.01,0.541-1.522,1.699-1.245,2.811c0.278,1.111,1.277,1.892,2.423,1.893c0.232,0.001,23.293,0.189,25.382,13.365c1.85,11.367-21.82,29.785-31.097,35.923c-1.002,0.663-1.391,1.945-0.926,3.052c0.395,0.943,1.314,1.533,2.304,1.533c0.173,0,0.348-0.018,0.522-0.056c2.202-0.47,53.855-11.852,48.394-41.927C261.862,164.541,250.278,160.673,241.148,160.673z"></path>
|
||||
<path d="M205.725,216.69c0.18-0.964-0.221-1.944-1.023-2.506l-12.385-8.675c-0.604-0.423-1.367-0.556-2.076-0.368c-0.129,0.034-13.081,3.438-31.885,5.526c-7.463,0.837-15.822,1.279-24.175,1.279c-18.799,0-31.091-2.209-32.881-3.829c-0.237-0.455-0.162-0.662-0.12-0.777c0.325-0.905,2.068-1.98,3.192-2.405c1.241-0.459,1.91-1.807,1.524-3.073c-0.385-1.266-1.69-2.012-2.978-1.702c-12.424,2.998-18.499,7.191-18.057,12.461c0.785,9.343,22.428,14.139,40.725,15.408c2.631,0.18,5.477,0.272,8.456,0.272c0.002,0,0.003,0,0.005,0c30.425,0,69.429-9.546,69.819-9.643C204.818,218.423,205.544,217.654,205.725,216.69z"></path>
|
||||
<path d="M112.351,236.745c0.938-0.611,1.354-1.77,1.021-2.838c-0.332-1.068-1.331-1.769-2.453-1.755c-1.665,0.044-16.292,0.704-17.316,10.017c-0.31,2.783,0.487,5.325,2.37,7.556c5.252,6.224,19.428,9.923,43.332,11.31c2.828,0.169,5.7,0.254,8.539,0.254c30.39,0,50.857-9.515,51.714-9.92c0.831-0.393,1.379-1.209,1.428-2.127c0.049-0.917-0.409-1.788-1.193-2.267l-15.652-9.555c-0.543-0.331-1.193-0.441-1.813-0.314c-0.099,0.021-10.037,2.082-25.035,4.119c-2.838,0.385-6.392,0.581-10.562,0.581c-14.982,0-31.646-2.448-34.842-4.05C111.843,237.455,111.902,237.075,112.351,236.745z"></path>
|
||||
<path d="M133.681,290.018c69.61-0.059,106.971-12.438,114.168-20.228c2.548-2.757,2.823-5.366,2.606-7.07c-0.535-4.194-4.354-6.761-4.788-7.04c-1.045-0.672-2.447-0.496-3.262,0.444c-0.813,0.941-0.832,2.314-0.016,3.253c0.439,0.565,0.693,1.51-0.591,2.795c-2.877,2.687-31.897,10.844-80.215,13.294c-6.619,0.345-13.561,0.519-20.633,0.52c-43.262,0-74.923-5.925-79.079-9.379c1.603-2.301,12.801-5.979,24.711-8.058c1.342-0.234,2.249-1.499,2.041-2.845c-0.208-1.346-1.449-2.273-2.805-2.096c-0.336,0.045-1.475,0.115-2.796,0.195c-19.651,1.2-42.36,3.875-43.545,13.999c-0.36,3.086,0.557,5.886,2.726,8.324c5.307,5.963,20.562,13.891,91.475,13.891C133.68,290.018,133.68,290.018,133.681,290.018z"></path>
|
||||
<path d="M261.522,271.985c-0.984-0.455-2.146-0.225-2.881,0.567c-0.103,0.11-10.568,11.054-42.035,17.48c-12.047,2.414-34.66,3.638-67.211,3.638c-32.612,0-63.643-1.283-63.953-1.296c-1.296-0.063-2.405,0.879-2.581,2.155c-0.177,1.276,0.645,2.477,1.897,2.775c0.323,0.077,32.844,7.696,77.31,7.696c21.327,0,42.08-1.733,61.684-5.151c36.553-6.408,39.112-24.533,39.203-25.301C263.082,273.474,262.504,272.44,261.522,271.985z"></path>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<input class="settingsFileSelVal" id="settingsJavaExecVal" type="text" value="null" cValue="JavaExecutable" serverDependent disabled>
|
||||
<button class="settingsFileSelButton" id="settingsJavaExecSel" dialogTitle="Select Java Executable" dialogDirectory="false">Choose File</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settingsFileSelDesc">The Java executable is validated before game launch. <strong id="settingsJavaReqDesc">Requires Java 8 x64.</strong><br>The path should end with <strong>bin<%= process.platform === 'win32' ? '\\javaw.exe' : '/java' %></strong>.</div>
|
||||
</div>
|
||||
<div id="settingsJVMOptsContainer">
|
||||
<div id="settingsJVMOptsTitle">Additional JVM Options</div>
|
||||
<div id="settingsJVMOptsContent">
|
||||
<div class="settingsFileSelIcon">
|
||||
<svg class="settingsFileSelSVG" x="0px" y="0px" viewBox="0 0 305.001 305.001">
|
||||
<g>
|
||||
<path d="M150.99,56.513c-14.093,9.912-30.066,21.147-38.624,39.734c-14.865,32.426,30.418,67.798,32.353,69.288c0.45,0.347,0.988,0.519,1.525,0.519c0.57,0,1.141-0.195,1.605-0.583c0.899-0.752,1.154-2.029,0.614-3.069c-0.164-0.316-16.418-31.888-15.814-54.539c0.214-7.888,11.254-16.837,22.942-26.312c10.705-8.678,22.839-18.514,29.939-30.02c15.586-25.327-1.737-50.231-1.914-50.479c-0.688-0.966-1.958-1.317-3.044-0.84c-1.085,0.478-1.686,1.652-1.438,2.811c0.035,0.164,3.404,16.633-5.97,33.6C169.301,43.634,160.816,49.603,150.99,56.513z"></path>
|
||||
<path d="M210.365,67.682c0.994-0.749,1.286-2.115,0.684-3.205c-0.602-1.09-1.913-1.571-3.077-1.129c-2.394,0.91-58.627,22.585-58.627,48.776c0,18.053,7.712,27.591,13.343,34.556c2.209,2.731,4.116,5.09,4.744,7.104c1.769,5.804-2.422,16.294-4.184,19.846c-0.508,1.022-0.259,2.259,0.605,3.005c0.467,0.403,1.05,0.607,1.634,0.607c0.497,0,0.996-0.148,1.427-0.448c0.967-0.673,23.63-16.696,19.565-36.001c-1.514-7.337-5.12-12.699-8.302-17.43c-4.929-7.329-8.489-12.624-3.088-22.403C181.419,89.556,210.076,67.899,210.365,67.682z"></path>
|
||||
<path d="M63.99,177.659c-0.964,2.885-0.509,5.75,1.315,8.283c6.096,8.462,27.688,13.123,60.802,13.123c0.002,0,0.003,0,0.004,0c4.487,0,9.224-0.088,14.076-0.262c52.943-1.896,72.58-18.389,73.39-19.09c0.883-0.764,1.119-2.037,0.57-3.067c-0.549-1.029-1.733-1.546-2.864-1.235c-18.645,5.091-53.463,6.898-77.613,6.898c-27.023,0-40.785-1.946-44.154-3.383c1.729-2.374,12.392-6.613,25.605-9.212c1.263-0.248,2.131-1.414,2.006-2.695c-0.125-1.281-1.201-2.258-2.488-2.258C106.893,164.762,68.05,165.384,63.99,177.659z"></path>
|
||||
<path d="M241.148,160.673c-10.92,0-21.275,5.472-21.711,5.705c-1.01,0.541-1.522,1.699-1.245,2.811c0.278,1.111,1.277,1.892,2.423,1.893c0.232,0.001,23.293,0.189,25.382,13.365c1.85,11.367-21.82,29.785-31.097,35.923c-1.002,0.663-1.391,1.945-0.926,3.052c0.395,0.943,1.314,1.533,2.304,1.533c0.173,0,0.348-0.018,0.522-0.056c2.202-0.47,53.855-11.852,48.394-41.927C261.862,164.541,250.278,160.673,241.148,160.673z"></path>
|
||||
<path d="M205.725,216.69c0.18-0.964-0.221-1.944-1.023-2.506l-12.385-8.675c-0.604-0.423-1.367-0.556-2.076-0.368c-0.129,0.034-13.081,3.438-31.885,5.526c-7.463,0.837-15.822,1.279-24.175,1.279c-18.799,0-31.091-2.209-32.881-3.829c-0.237-0.455-0.162-0.662-0.12-0.777c0.325-0.905,2.068-1.98,3.192-2.405c1.241-0.459,1.91-1.807,1.524-3.073c-0.385-1.266-1.69-2.012-2.978-1.702c-12.424,2.998-18.499,7.191-18.057,12.461c0.785,9.343,22.428,14.139,40.725,15.408c2.631,0.18,5.477,0.272,8.456,0.272c0.002,0,0.003,0,0.005,0c30.425,0,69.429-9.546,69.819-9.643C204.818,218.423,205.544,217.654,205.725,216.69z"></path>
|
||||
<path d="M112.351,236.745c0.938-0.611,1.354-1.77,1.021-2.838c-0.332-1.068-1.331-1.769-2.453-1.755c-1.665,0.044-16.292,0.704-17.316,10.017c-0.31,2.783,0.487,5.325,2.37,7.556c5.252,6.224,19.428,9.923,43.332,11.31c2.828,0.169,5.7,0.254,8.539,0.254c30.39,0,50.857-9.515,51.714-9.92c0.831-0.393,1.379-1.209,1.428-2.127c0.049-0.917-0.409-1.788-1.193-2.267l-15.652-9.555c-0.543-0.331-1.193-0.441-1.813-0.314c-0.099,0.021-10.037,2.082-25.035,4.119c-2.838,0.385-6.392,0.581-10.562,0.581c-14.982,0-31.646-2.448-34.842-4.05C111.843,237.455,111.902,237.075,112.351,236.745z"></path>
|
||||
<path d="M133.681,290.018c69.61-0.059,106.971-12.438,114.168-20.228c2.548-2.757,2.823-5.366,2.606-7.07c-0.535-4.194-4.354-6.761-4.788-7.04c-1.045-0.672-2.447-0.496-3.262,0.444c-0.813,0.941-0.832,2.314-0.016,3.253c0.439,0.565,0.693,1.51-0.591,2.795c-2.877,2.687-31.897,10.844-80.215,13.294c-6.619,0.345-13.561,0.519-20.633,0.52c-43.262,0-74.923-5.925-79.079-9.379c1.603-2.301,12.801-5.979,24.711-8.058c1.342-0.234,2.249-1.499,2.041-2.845c-0.208-1.346-1.449-2.273-2.805-2.096c-0.336,0.045-1.475,0.115-2.796,0.195c-19.651,1.2-42.36,3.875-43.545,13.999c-0.36,3.086,0.557,5.886,2.726,8.324c5.307,5.963,20.562,13.891,91.475,13.891C133.68,290.018,133.68,290.018,133.681,290.018z"></path>
|
||||
<path d="M261.522,271.985c-0.984-0.455-2.146-0.225-2.881,0.567c-0.103,0.11-10.568,11.054-42.035,17.48c-12.047,2.414-34.66,3.638-67.211,3.638c-32.612,0-63.643-1.283-63.953-1.296c-1.296-0.063-2.405,0.879-2.581,2.155c-0.177,1.276,0.645,2.477,1.897,2.775c0.323,0.077,32.844,7.696,77.31,7.696c21.327,0,42.08-1.733,61.684-5.151c36.553-6.408,39.112-24.533,39.203-25.301C263.082,273.474,262.504,272.44,261.522,271.985z"></path>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<input id="settingsJVMOptsVal" cValue="JVMOptions" serverDependent type="text">
|
||||
</div>
|
||||
<div id="settingsJVMOptsDesc">Options to be provided to the JVM at runtime. <em>-Xms</em> and <em>-Xmx</em> should not be included.<br><a href="https://docs.oracle.com/javase/8/docs/technotes/tools/<%= process.platform === 'win32' ? 'windows' : 'unix' %>/java.html" id="settingsJvmOptsLink">Available Options for Java 8</a>.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="settingsTabLauncher" class="settingsTab" style="display: none;">
|
||||
<div class="settingsTabHeader">
|
||||
<span class="settingsTabHeaderText">Launcher Settings</span>
|
||||
<span class="settingsTabHeaderDesc">Options related to the launcher itself.</span>
|
||||
</div>
|
||||
<div class="settingsFieldContainer">
|
||||
<div class="settingsFieldLeft">
|
||||
<span class="settingsFieldTitle">Allow Pre-Release Updates.</span>
|
||||
<span class="settingsFieldDesc">Pre-Releases include new features which may have not been fully tested or integrated.<br>This will always be true if you are using a pre-release version.</span>
|
||||
</div>
|
||||
<div class="settingsFieldRight">
|
||||
<label class="toggleSwitch">
|
||||
<input type="checkbox" cValue="AllowPrerelease">
|
||||
<span class="toggleSwitchSlider"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settingsFileSelContainer">
|
||||
<div class="settingsFileSelContent">
|
||||
<div class="settingsFieldTitle" id="settingsDataDirTitle">Data Directory</div>
|
||||
<div class="settingsFileSelActions">
|
||||
<div class="settingsFileSelIcon">
|
||||
<svg class="settingsFileSelSVG">
|
||||
<g>
|
||||
<path fill="gray" d="m10.044745,5c0,0.917174 -0.746246,1.667588 -1.667588,1.667588l-4.168971,0l-2.501382,0c-0.921009,0 -1.667588,0.750415 -1.667588,1.667588l0,6.670353l0,2.501382c0,0.917174 0.746604,1.667588 1.667588,1.667588l16.675882,0c0.921342,0 1.667588,-0.750415 1.667588,-1.667588l0,-2.501382l0,-8.337941c0,-0.917174 -0.746246,-1.667588 -1.667588,-1.667588l-8.337941,0z"/>
|
||||
<path fill="gray" d="m1.627815,1.6c-0.921009,0 -1.667588,0.746579 -1.667588,1.667588l0,4.168971l8.337941,0l0,0.833794l11.673118,0l0,-4.168971c0,-0.921009 -0.746246,-1.667588 -1.667588,-1.667588l-8.572237,0c-0.288493,-0.497692 -0.816284,-0.833794 -1.433292,-0.833794l-6.670353,0z"/>
|
||||
<path fill="lightgray" d="m10.025276,4c0,0.918984 -0.747719,1.670879 -1.670879,1.670879l-4.177198,0l-2.506319,0c-0.922827,0 -1.670879,0.751896 -1.670879,1.670879l0,6.683517l0,2.506319c0,0.918984 0.748078,1.670879 1.670879,1.670879l16.708794,0c0.923161,0 1.670879,-0.751896 1.670879,-1.670879l0,-2.506319l0,-8.354397c0,-0.918984 -0.747719,-1.670879 -1.670879,-1.670879l-8.354397,0z"/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<input class="settingsFileSelVal" type="text" value="null" cValue="DataDirectory" disabled>
|
||||
<button class="settingsFileSelButton" dialogTitle="Select Data Directory" dialogDirectory="true">Choose Folder</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settingsFileSelDesc">All game files and local Java installations will be stored in the data directory.<br>Screenshots and world saves are stored in the instance folder for the corresponding server configuration.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="settingsTabAbout" class="settingsTab" style="display: none;">
|
||||
<div class="settingsTabHeader">
|
||||
<span class="settingsTabHeaderText">About</span>
|
||||
<span class="settingsTabHeaderDesc">View information and release notes for the current version.</span>
|
||||
</div>
|
||||
<div id="settingsAboutCurrentContainer">
|
||||
<div id="settingsAboutCurrentContent">
|
||||
<div id="settingsAboutCurrentHeadline">
|
||||
<img id="settingsAboutLogo" src="./assets/images/SealCircle.png">
|
||||
<span id="settingsAboutTitle">Helios Launcher</span>
|
||||
</div>
|
||||
<div id="settingsAboutCurrentVersion">
|
||||
<div id="settingsAboutCurrentVersionCheck">✓</div>
|
||||
<div id="settingsAboutCurrentVersionDetails">
|
||||
<span id="settingsAboutCurrentVersionTitle">Stable Release</span>
|
||||
<div id="settingsAboutCurrentVersionLine">
|
||||
<span id="settingsAboutCurrentVersionText">Version </span>
|
||||
<span id="settingsAboutCurrentVersionValue">0.0.1-alpha.12</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="settingsAboutButtons">
|
||||
<a href="https://github.com/dscalZi/HeliosLauncher" id="settingsAboutSourceButton" class="settingsAboutButton">Source (GitHub)</a>
|
||||
<!-- The following must be included in third-party usage. -->
|
||||
<!-- <a href="https://github.com/dscalzi/HeliosLauncher" id="settingsAboutSourceButton" class="settingsAboutButton">Original Source</a> -->
|
||||
<a href="https://github.com/dscalZi/HeliosLauncher/issues" id="settingsAboutSupportButton" class="settingsAboutButton">Support</a>
|
||||
<a href="#" id="settingsAboutDevToolsButton" class="settingsAboutButton">DevTools Console</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settingsChangelogContainer">
|
||||
<div class="settingsChangelogContent">
|
||||
<div class="settingsChangelogHeadline">
|
||||
<div class="settingsChangelogLabel">Release Notes</div>
|
||||
<div class="settingsChangelogTitle">Changelog</div>
|
||||
</div>
|
||||
<div class="settingsChangelogText">
|
||||
No Release Notes
|
||||
</div>
|
||||
</div>
|
||||
<div class="settingsChangelogActions">
|
||||
<a class="settingsChangelogButton settingsAboutButton" href="#">View Release Notes on GitHub</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="settingsTabUpdate" class="settingsTab" style="display: none;">
|
||||
<div class="settingsTabHeader">
|
||||
<span class="settingsTabHeaderText">Launcher Updates</span>
|
||||
<span class="settingsTabHeaderDesc">Download, install, and review updates for the launcher.</span>
|
||||
</div>
|
||||
<div id="settingsUpdateStatusContainer">
|
||||
<div id="settingsUpdateStatusContent">
|
||||
<div id="settingsUpdateStatusHeadline">
|
||||
<span id="settingsUpdateTitle">You Are Running the Latest Version</span>
|
||||
</div>
|
||||
<div id="settingsUpdateVersion">
|
||||
<div id="settingsUpdateVersionCheck">✓</div>
|
||||
<div id="settingsUpdateVersionDetails">
|
||||
<span id="settingsUpdateVersionTitle">Stable Release</span>
|
||||
<div id="settingsUpdateVersionLine">
|
||||
<span id="settingsUpdateVersionText">Version </span>
|
||||
<span id="settingsUpdateVersionValue">0.0.1-alpha.18</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="settingsUpdateActionContainer">
|
||||
<button id="settingsUpdateActionButton">Check for Updates</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settingsChangelogContainer">
|
||||
<div class="settingsChangelogContent">
|
||||
<div class="settingsChangelogHeadline">
|
||||
<div class="settingsChangelogLabel">What's New</div>
|
||||
<div class="settingsChangelogTitle">Update Release Notes</div>
|
||||
</div>
|
||||
<div class="settingsChangelogText">
|
||||
No Release Notes
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="./assets/js/scripts/settings.js"></script>
|
||||
</div>
|
8
src/views/waiting.ejs
Normal file
8
src/views/waiting.ejs
Normal file
@ -0,0 +1,8 @@
|
||||
<div id="waitingContainer" style="display: none;">
|
||||
<div id="waitingContent">
|
||||
<div class="waitingSpinner"></div>
|
||||
<div id="waitingTextContainer">
|
||||
<h2>Waiting for Microsoft..</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
25
src/views/welcome.ejs
Normal file
25
src/views/welcome.ejs
Normal file
@ -0,0 +1,25 @@
|
||||
<div id="welcomeContainer" style="display: none;">
|
||||
<!--<div class="cloudDiv">
|
||||
<div class="cloudTop"></div>
|
||||
<div class="cloudBottom"></div>
|
||||
</div>-->
|
||||
<div id="welcomeContent">
|
||||
<img id="welcomeImageSeal" src="assets/images/SealCircle.png"/>
|
||||
<span id="welcomeHeader">WELCOME TO WESTEROSCRAFT</span>
|
||||
<span id="welcomeDescription">Our mission is to recreate the universe imagined by author George RR Martin in his fantasy series, A Song of Ice and Fire. Through the collaborative effort of thousands of community members, we have sought to create Westeros as accurately and precisely as possible within Minecraft. The world we are creating is yours to explore. Journey from Dorne to Castle Black, and if you aren’t afraid, beyond the Wall itself, but best not delay. As the words of House Stark ominously warn: Winter is Coming.</span>
|
||||
<br>
|
||||
<span id="welcomeDescCTA">You are just a few clicks away from Westeros.</span>
|
||||
<button id="welcomeButton">
|
||||
<div id="welcomeButtonContent">
|
||||
CONTINUE
|
||||
<svg id="welcomeSVG" viewBox="0 0 24.87 13.97">
|
||||
<defs>
|
||||
<style>.arrowLine{fill:none;stroke:#FFF;stroke-width:2px;transition: 0.25s ease;}</style>
|
||||
</defs>
|
||||
<polyline class="arrowLine" points="0.71 13.26 12.56 1.41 24.16 13.02"/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<script src="./assets/js/scripts/welcome.js"></script>
|
||||
</div>
|
9
src/views/welcome.ts
Normal file
9
src/views/welcome.ts
Normal file
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Script for welcome.ejs
|
||||
*/
|
||||
document.getElementById('welcomeButton')?.addEventListener('click', e => {
|
||||
loginOptionsCancelEnabled(false) // False by default, be explicit.
|
||||
loginOptionsViewOnLoginSuccess = VIEWS.landing
|
||||
loginOptionsViewOnLoginCancel = VIEWS.loginOptions
|
||||
switchView(VIEWS.welcome, VIEWS.loginOptions)
|
||||
})
|
17
tsconfig.json
Normal file
17
tsconfig.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ES2022",
|
||||
"sourceMap": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"stripInternal": true,
|
||||
"declaration": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"outDir": "./dist",
|
||||
"lib": ["ES2022", "dom"],
|
||||
"moduleResolution": "node"
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user