Write some info in README.md, design titlebar, make downloader and very basic instance checker
This commit is contained in:
		
							
								
								
									
										11
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								README.md
									
									
									
									
									
								
							@@ -1,3 +1,12 @@
 | 
				
			|||||||
# OpenModLauncher-Base
 | 
					# OpenModLauncher-Base
 | 
				
			||||||
 | 
					
 | 
				
			||||||
A small Minecraft launcher codebase
 | 
					A small Minecraft launcher codebase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This is currently a work in progress for another project of mine but it can be used in other projects too as I try to be the least project-specific as I can be.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Please note I reused and rearranged a lot of code from a launcher I've programmed earlier this year. 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Features
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					At the time of writing this README, this launcher can only download a Minecraft version specified in meta.json into a given directory.
 | 
				
			||||||
 | 
					Launching Minecraft, installing a mod loader on top of many versions and downloading custom files are the main planned features.
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										10
									
								
								lib/checker.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								lib/checker.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					const path = require("node:path");
 | 
				
			||||||
 | 
					const fs = require("node:fs");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = {
 | 
				
			||||||
 | 
						"check_install": function (directory) {
 | 
				
			||||||
 | 
							return new Promise(function (resolve, reject) {
 | 
				
			||||||
 | 
								resolve(fs.existsSync(path.join(directory, "versions")));
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										146
									
								
								lib/downloader.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								lib/downloader.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,146 @@
 | 
				
			|||||||
 | 
					const path = require("node:path");
 | 
				
			||||||
 | 
					const fs = require("node:fs");
 | 
				
			||||||
 | 
					const fsPromises = require("node:fs/promises");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const httpsWrapper = require("./util/https.js");
 | 
				
			||||||
 | 
					const utilities = require("./util/functions.js");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let maxConcurrentDownloads = 16;
 | 
				
			||||||
 | 
					let downloadQueue = [];
 | 
				
			||||||
 | 
					let totalDownloads = 0;
 | 
				
			||||||
 | 
					let isDownloading = false;
 | 
				
			||||||
 | 
					let ongoingDownloads = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function waitUntilDownloadsFinish() {
 | 
				
			||||||
 | 
						return new Promise(async function (resolve, reject) {
 | 
				
			||||||
 | 
							var downloadCountChecker = setInterval(function () {
 | 
				
			||||||
 | 
								if (ongoingDownloads < 1 && downloadQueue.length < 1 && !isDownloading) {
 | 
				
			||||||
 | 
									resolve(clearInterval(downloadCountChecker));
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}, 256);
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = {
 | 
				
			||||||
 | 
						"download_file": function (url, filePath) {
 | 
				
			||||||
 | 
							return new Promise(async function (resolve, reject) {
 | 
				
			||||||
 | 
								var directory = path.dirname(filePath);
 | 
				
			||||||
 | 
								if (!(await fs.existsSync(directory))) await fsPromises.mkdir(directory, {"recursive": true});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								var writeStream = fs.createWriteStream(filePath);
 | 
				
			||||||
 | 
								writeStream.on("finish", resolve);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								httpsWrapper.getStream(url).then(function (response) {
 | 
				
			||||||
 | 
									response.pipe(writeStream);
 | 
				
			||||||
 | 
								}).catch(reject);
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						"process_queue": function (progressCB) {
 | 
				
			||||||
 | 
							return new Promise(async function (resolve, reject) {
 | 
				
			||||||
 | 
								totalDownloads = downloadQueue.length;
 | 
				
			||||||
 | 
								if (downloadQueue.length > 0) isDownloading = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (progressCB) {
 | 
				
			||||||
 | 
									progressCB(0.0, 0, downloadQueue.length, totalDownloads);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								while (downloadQueue.length > 0) {
 | 
				
			||||||
 | 
									if (ongoingDownloads < maxConcurrentDownloads) {
 | 
				
			||||||
 | 
										setImmediate(function (downloadData) {
 | 
				
			||||||
 | 
											module.exports.download_file(downloadData.url, downloadData.path).then(function () {
 | 
				
			||||||
 | 
												ongoingDownloads--;
 | 
				
			||||||
 | 
												if (downloadQueue.length < 1) {
 | 
				
			||||||
 | 
													isDownloading = false;
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
											}).catch(function (error) {
 | 
				
			||||||
 | 
												ongoingDownloads--;
 | 
				
			||||||
 | 
												downloadQueue.push(downloadData);
 | 
				
			||||||
 | 
											});
 | 
				
			||||||
 | 
										}, downloadQueue.shift());
 | 
				
			||||||
 | 
										ongoingDownloads++;
 | 
				
			||||||
 | 
										let progress = (totalDownloads - (downloadQueue.length + ongoingDownloads)) / totalDownloads;
 | 
				
			||||||
 | 
										if (progressCB) {
 | 
				
			||||||
 | 
											progressCB(progress, ongoingDownloads, downloadQueue.length, totalDownloads);
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										await utilities.sleep(256);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								waitUntilDownloadsFinish().then(function () {
 | 
				
			||||||
 | 
									if (progressCB) {
 | 
				
			||||||
 | 
										progressCB(1.0, 0, 0, totalDownloads);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									resolve();
 | 
				
			||||||
 | 
								}).catch(reject);
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						"download_minecraft": function (directory, meta, manifestSource, progressCB) {
 | 
				
			||||||
 | 
							return new Promise(async function (resolve, reject) {
 | 
				
			||||||
 | 
								let gameManifest = JSON.parse(await httpsWrapper.get(manifestSource["versions_manifest"]));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								let versionInManifest = gameManifest.versions.find(ver => ver.id == meta["version"]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (!versionInManifest) {
 | 
				
			||||||
 | 
									return reject(new Error("Minecraft version \"" + meta["version"] + "\" could not be found in given source. "));
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (!fs.existsSync(directory)) await fsPromises.mkdir(directory, {"recursive": true});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								let versionDirectory = path.join(directory, "versions", meta["version"]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (!fs.existsSync(versionDirectory)) await fsPromises.mkdir(versionDirectory, {"recursive": true});
 | 
				
			||||||
 | 
								let versionManifest = JSON.parse(await httpsWrapper.get(versionInManifest.url));
 | 
				
			||||||
 | 
								fs.writeFileSync(path.join(versionDirectory, meta["version"] + ".json"), JSON.stringify(versionManifest, null, "\t"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								let librariesDirectory = path.join(directory, "libraries");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								for (let l = 0; l < versionManifest.libraries.length; l++) {
 | 
				
			||||||
 | 
									let library = versionManifest.libraries[l];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if ("classifiers" in library.downloads) continue; // Attempt at skipping platform-specific code, hoping the game still starts.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									let libraryFilePath = path.join(librariesDirectory, ...(library.downloads.artifact.path.split("/")));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									downloadQueue.push({
 | 
				
			||||||
 | 
										"url": library.downloads.artifact.url,
 | 
				
			||||||
 | 
										"path": libraryFilePath
 | 
				
			||||||
 | 
									});
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								let assetsDirectory = path.join(directory, "assets");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								let assetIndexesDirectory = path.join(assetsDirectory, "indexes");
 | 
				
			||||||
 | 
								if (!fs.existsSync(assetIndexesDirectory)) await fsPromises.mkdir(assetIndexesDirectory, {"recursive": true});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								let assetIndexPath = path.join(assetIndexesDirectory, versionManifest.assetIndex.id + ".json");
 | 
				
			||||||
 | 
								let assetIndex = JSON.parse(await httpsWrapper.get(versionManifest.assetIndex.url));
 | 
				
			||||||
 | 
								fs.writeFileSync(assetIndexPath, JSON.stringify(assetIndex, null, "\t"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								let assetObjectsDirectory = path.join(assetsDirectory, "objects");
 | 
				
			||||||
 | 
								for (const assetLocation in assetIndex.objects) {
 | 
				
			||||||
 | 
									if (assetIndex.objects.hasOwnProperty(assetLocation)) {
 | 
				
			||||||
 | 
										let asset = assetIndex.objects[assetLocation];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										let assetPath = [
 | 
				
			||||||
 | 
											asset.hash.substring(0, 2), asset.hash
 | 
				
			||||||
 | 
										];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										downloadQueue.push({
 | 
				
			||||||
 | 
											"url": manifestSource["asset_base"] + assetPath.join("/"),
 | 
				
			||||||
 | 
											"path": path.join(assetObjectsDirectory, ...assetPath)
 | 
				
			||||||
 | 
										});
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								downloadQueue.push({
 | 
				
			||||||
 | 
									"url": versionManifest.downloads.client.url,
 | 
				
			||||||
 | 
									"path": path.join(versionDirectory, meta["version"] + ".jar")
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (!isDownloading) await module.exports.process_queue(progressCB);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								resolve(true);
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										7
									
								
								lib/util/functions.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								lib/util/functions.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					module.exports = {
 | 
				
			||||||
 | 
						"sleep": function (delay) {
 | 
				
			||||||
 | 
							return new Promise(function (resolve, reject) {
 | 
				
			||||||
 | 
								setTimeout(resolve, delay);
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										35
									
								
								lib/util/https.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								lib/util/https.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					const https = require("https");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = {
 | 
				
			||||||
 | 
						"get": function (url) {
 | 
				
			||||||
 | 
							return new Promise(function (resolve, reject) {
 | 
				
			||||||
 | 
								var request = https.get(url, function (response) {
 | 
				
			||||||
 | 
									if (response.statusCode != 200) return reject(new Error("Response status code wasn't 200. "));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									response.setEncoding("utf-8");
 | 
				
			||||||
 | 
									let body = "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									response.on("data", function (chunk) {
 | 
				
			||||||
 | 
										body += chunk;
 | 
				
			||||||
 | 
									});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									response.on("end", function () {
 | 
				
			||||||
 | 
										resolve(body);
 | 
				
			||||||
 | 
									});
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								request.on("error", reject);
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						"getStream": function (url) {
 | 
				
			||||||
 | 
							return new Promise(function (resolve, reject) {
 | 
				
			||||||
 | 
								var request = https.get(url, function (response) {
 | 
				
			||||||
 | 
									if (response.statusCode != 200) return reject(new Error("Response status code wasn't 200"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									resolve(response);
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								request.on("error", reject);
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										60
									
								
								main.js
									
									
									
									
									
								
							
							
						
						
									
										60
									
								
								main.js
									
									
									
									
									
								
							@@ -1,8 +1,49 @@
 | 
				
			|||||||
const path = require("node:path");
 | 
					const path = require("node:path");
 | 
				
			||||||
const child_process = require("node:child_process");
 | 
					const fs = require("node:fs");
 | 
				
			||||||
 | 
					const os = require("node:os");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const electron = require("electron");
 | 
					const electron = require("electron");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const downloader = require("./lib/downloader.js");
 | 
				
			||||||
 | 
					const checker = require("./lib/checker.js");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let meta = JSON.parse(fs.readFileSync(path.join(__dirname, "meta.json"), "utf-8"));
 | 
				
			||||||
 | 
					let sources = JSON.parse(fs.readFileSync(path.join(__dirname, "sources.json"), "utf-8"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let osPlatform = os.platform();
 | 
				
			||||||
 | 
					let userHome = os.homedir();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let gameDirectory;
 | 
				
			||||||
 | 
					switch (osPlatform) {
 | 
				
			||||||
 | 
						case "win32":
 | 
				
			||||||
 | 
							gameDirectory = path.join(userHome, "AppData", "Roaming", ...(meta["install_directory"]).split("/"));
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case "darwin":
 | 
				
			||||||
 | 
							gameDirectory = path.join(userHome, "Library", "Application Support", ...(meta["install_directory"]).split("/"));
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case "linux":
 | 
				
			||||||
 | 
							gameDirectory = path.join(userHome, ...(meta["install_directory"]).split("/"));
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							gameDirectory = path.join(userHome, ...(meta["install_directory"]).split("/"));
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let window;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function showProgress(progress, ongoing, remaining, total) {
 | 
				
			||||||
 | 
						window.setProgressBar(progress);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						window.webContents.send("event", {
 | 
				
			||||||
 | 
							"type": "download-progress",
 | 
				
			||||||
 | 
							"data": {
 | 
				
			||||||
 | 
								"ongoing": ongoing,
 | 
				
			||||||
 | 
								"remaining": remaining,
 | 
				
			||||||
 | 
								"total": total
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
electron.app.on("ready", function () {
 | 
					electron.app.on("ready", function () {
 | 
				
			||||||
	window = new electron.BrowserWindow({
 | 
						window = new electron.BrowserWindow({
 | 
				
			||||||
		"width": 960,
 | 
							"width": 960,
 | 
				
			||||||
@@ -14,7 +55,7 @@ electron.app.on("ready", function () {
 | 
				
			|||||||
		},
 | 
							},
 | 
				
			||||||
		"title": "OpenModLauncher",
 | 
							"title": "OpenModLauncher",
 | 
				
			||||||
		"icon": path.join(__dirname, "res", "img", "logo.png"),
 | 
							"icon": path.join(__dirname, "res", "img", "logo.png"),
 | 
				
			||||||
		"frame": true,
 | 
							"frame": false,
 | 
				
			||||||
		"transparent": true,
 | 
							"transparent": true,
 | 
				
			||||||
		"resizable": false,
 | 
							"resizable": false,
 | 
				
			||||||
		"show": false
 | 
							"show": false
 | 
				
			||||||
@@ -25,6 +66,21 @@ electron.app.on("ready", function () {
 | 
				
			|||||||
	window.on("ready-to-show", window.show);
 | 
						window.on("ready-to-show", window.show);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					electron.ipcMain.handle("minimize", function (event) {
 | 
				
			||||||
 | 
						return window.minimize();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					electron.ipcMain.handle("close", function (event) {
 | 
				
			||||||
 | 
						return window.close();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					electron.ipcMain.handle("is-installed", function (event) {
 | 
				
			||||||
 | 
						return checker.check_install(gameDirectory);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					electron.ipcMain.handle("install", function (event) {
 | 
				
			||||||
 | 
						return downloader.download_minecraft(gameDirectory, meta["minecraft"], sources["minecraft"], showProgress);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
electron.app.on("window-all-closed", function () {
 | 
					electron.app.on("window-all-closed", function () {
 | 
				
			||||||
	electron.app.quit();
 | 
						electron.app.quit();
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
 | 
						"install_directory": "games/.openmod",
 | 
				
			||||||
	"minecraft": {
 | 
						"minecraft": {
 | 
				
			||||||
		"version": "${MINECRAFT_VERSION}"
 | 
							"version": "1.7.10"
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										18
									
								
								preload.js
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								preload.js
									
									
									
									
									
								
							@@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					const electron = require("electron");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					electron.contextBridge.exposeInMainWorld("minimizeWindow", function () {
 | 
				
			||||||
 | 
						return electron.ipcRenderer.invoke("minimize");
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					electron.contextBridge.exposeInMainWorld("closeWindow", function () {
 | 
				
			||||||
 | 
						return electron.ipcRenderer.invoke("close");
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					electron.contextBridge.exposeInMainWorld("game", {
 | 
				
			||||||
 | 
						"isInstalled": function () {
 | 
				
			||||||
 | 
							return electron.ipcRenderer.invoke("is-installed");
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						"install": function () {
 | 
				
			||||||
 | 
							return electron.ipcRenderer.invoke("install");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										47
									
								
								res/css/main.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								res/css/main.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
				
			|||||||
 | 
					:root {
 | 
				
			||||||
 | 
						color: rgba(248, 248, 248, 1.0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					body {
 | 
				
			||||||
 | 
						margin: 0px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#titlebar {
 | 
				
			||||||
 | 
						display: flex;
 | 
				
			||||||
 | 
						flex-direction: row;
 | 
				
			||||||
 | 
						justify-content: space-between;
 | 
				
			||||||
 | 
						align-items: center;
 | 
				
			||||||
 | 
						-webkit-app-region: drag;
 | 
				
			||||||
 | 
						width: 100%;
 | 
				
			||||||
 | 
						height: 24px;
 | 
				
			||||||
 | 
						position: absolute;
 | 
				
			||||||
 | 
						top: 0px;
 | 
				
			||||||
 | 
						left: 0px;
 | 
				
			||||||
 | 
						background-color: rgba(16, 16, 16, 1.0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#window-actions {
 | 
				
			||||||
 | 
						display: flex;
 | 
				
			||||||
 | 
						flex-direction: row;
 | 
				
			||||||
 | 
						height: 100%;
 | 
				
			||||||
 | 
						-webkit-app-region: no-drag;
 | 
				
			||||||
 | 
						pointer-events: all;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.window-action {
 | 
				
			||||||
 | 
						background-color: rgba(24, 24, 32, 1.0);
 | 
				
			||||||
 | 
						width: 32px;
 | 
				
			||||||
 | 
						height: 100%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.window-action:hover {
 | 
				
			||||||
 | 
						background-color: rgba(48, 48, 64, 1.0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#window-close {
 | 
				
			||||||
 | 
						background-color: rgba(128, 0, 0, 1.0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#window-close:hover {
 | 
				
			||||||
 | 
						background-color: rgba(248, 0, 0, 1.0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,5 +1,9 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
	"minecraft": {
 | 
						"minecraft": {
 | 
				
			||||||
		"versions_manifest": "https://launchermeta.mojang.com/mc/game/version_manifest_v2.json"
 | 
							"versions_manifest": "https://launchermeta.mojang.com/mc/game/version_manifest_v2.json",
 | 
				
			||||||
 | 
							"asset_base": "https://resources.download.minecraft.net/"
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						"custom": {
 | 
				
			||||||
 | 
							"files_manifest": "${CUSTOM_MANIFEST_URL}"
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,10 +3,20 @@
 | 
				
			|||||||
	<head>
 | 
						<head>
 | 
				
			||||||
		<meta charset="utf-8">
 | 
							<meta charset="utf-8">
 | 
				
			||||||
		<title>OpenModLauncher</title>
 | 
							<title>OpenModLauncher</title>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							<link rel="stylesheet" href="../res/css/main.css">
 | 
				
			||||||
	</head>
 | 
						</head>
 | 
				
			||||||
	<body>
 | 
						<body>
 | 
				
			||||||
		<div id="titlebar">
 | 
							<div id="titlebar">
 | 
				
			||||||
			
 | 
								<span>OpenModLauncher</span>
 | 
				
			||||||
 | 
								<div id="window-actions">
 | 
				
			||||||
 | 
									<div class="window-action" id="window-minimize" onclick="minimizeWindow();">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									</div>
 | 
				
			||||||
 | 
									<div class="window-action" id="window-close" onclick="closeWindow();">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									</div>
 | 
				
			||||||
 | 
								</div>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	</body>
 | 
						</body>
 | 
				
			||||||
</html>
 | 
					</html>
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user