const fs = require('fs-extra') const path = require('path') const { ipcRenderer, shell } = require('electron') const { SHELL_OPCODE } = require('./ipcconstants') // Group #1: File Name (without .disabled, if any) // Group #2: File Extension (jar, zip, or litemod) // Group #3: If it is disabled (if string 'disabled' is present) const MOD_REGEX = /^(.+(jar|zip|litemod))(?:\.(disabled))?$/ const DISABLED_EXT = '.disabled' const SHADER_REGEX = /^(.+)\.zip$/ const SHADER_OPTION = /shaderPack=(.+)/ const SHADER_DIR = 'shaderpacks' const SHADER_CONFIG = 'optionsshaders.txt' /** * Validate that the given directory exists. If not, it is * created. * * @param {string} modsDir The path to the mods directory. */ exports.validateDir = function(dir) { fs.ensureDirSync(dir) } /** * Scan for drop-in mods in both the mods folder and version * safe mods folder. * * @param {string} modsDir The path to the mods directory. * @param {string} version The minecraft version of the server configuration. * * @returns {{fullName: string, name: string, ext: string, disabled: boolean}[]} * An array of objects storing metadata about each discovered mod. */ exports.scanForDropinMods = function(modsDir, version) { const modsDiscovered = [] if(fs.existsSync(modsDir)){ let modCandidates = fs.readdirSync(modsDir) let verCandidates = [] const versionDir = path.join(modsDir, version) if(fs.existsSync(versionDir)){ verCandidates = fs.readdirSync(versionDir) } for(let file of modCandidates){ const match = MOD_REGEX.exec(file) if(match != null){ modsDiscovered.push({ fullName: match[0], name: match[1], ext: match[2], disabled: match[3] != null }) } } for(let file of verCandidates){ const match = MOD_REGEX.exec(file) if(match != null){ modsDiscovered.push({ fullName: path.join(version, match[0]), name: match[1], ext: match[2], disabled: match[3] != null }) } } } return modsDiscovered } /** * Add dropin mods. * * @param {FileList} files The files to add. * @param {string} modsDir The path to the mods directory. */ exports.addDropinMods = function(files, modsdir) { exports.validateDir(modsdir) for(let f of files) { if(MOD_REGEX.exec(f.name) != null) { fs.moveSync(f.path, path.join(modsdir, f.name)) } } } /** * Delete a drop-in mod from the file system. * * @param {string} modsDir The path to the mods directory. * @param {string} fullName The fullName of the discovered mod to delete. * * @returns {Promise.} True if the mod was deleted, otherwise false. */ exports.deleteDropinMod = async function(modsDir, fullName){ const res = await ipcRenderer.invoke(SHELL_OPCODE.TRASH_ITEM, path.join(modsDir, fullName)) if(!res.result) { shell.beep() console.error('Error deleting drop-in mod.', res.error) return false } return true } /** * Toggle a discovered mod on or off. This is achieved by either * adding or disabling the .disabled extension to the local file. * * @param {string} modsDir The path to the mods directory. * @param {string} fullName The fullName of the discovered mod to toggle. * @param {boolean} enable Whether to toggle on or off the mod. * * @returns {Promise.} A promise which resolves when the mod has * been toggled. If an IO error occurs the promise will be rejected. */ exports.toggleDropinMod = function(modsDir, fullName, enable){ return new Promise((resolve, reject) => { const oldPath = path.join(modsDir, fullName) const newPath = path.join(modsDir, enable ? fullName.substring(0, fullName.indexOf(DISABLED_EXT)) : fullName + DISABLED_EXT) fs.rename(oldPath, newPath, (err) => { if(err){ reject(err) } else { resolve() } }) }) } /** * Check if a drop-in mod is enabled. * * @param {string} fullName The fullName of the discovered mod to toggle. * @returns {boolean} True if the mod is enabled, otherwise false. */ exports.isDropinModEnabled = function(fullName){ return !fullName.endsWith(DISABLED_EXT) } /** * Scan for shaderpacks inside the shaderpacks folder. * * @param {string} instanceDir The path to the server instance directory. * * @returns {{fullName: string, name: string}[]} * An array of objects storing metadata about each discovered shaderpack. */ exports.scanForShaderpacks = function(instanceDir){ const shaderDir = path.join(instanceDir, SHADER_DIR) const packsDiscovered = [{ fullName: 'OFF', name: 'Off (Default)' }] if(fs.existsSync(shaderDir)){ let modCandidates = fs.readdirSync(shaderDir) for(let file of modCandidates){ const match = SHADER_REGEX.exec(file) if(match != null){ packsDiscovered.push({ fullName: match[0], name: match[1] }) } } } return packsDiscovered } /** * Read the optionsshaders.txt file to locate the current * enabled pack. If the file does not exist, OFF is returned. * * @param {string} instanceDir The path to the server instance directory. * * @returns {string} The file name of the enabled shaderpack. */ exports.getEnabledShaderpack = function(instanceDir){ exports.validateDir(instanceDir) const optionsShaders = path.join(instanceDir, SHADER_CONFIG) if(fs.existsSync(optionsShaders)){ const buf = fs.readFileSync(optionsShaders, {encoding: 'utf-8'}) const match = SHADER_OPTION.exec(buf) if(match != null){ return match[1] } else { console.warn('WARNING: Shaderpack regex failed.') } } return 'OFF' } /** * Set the enabled shaderpack. * * @param {string} instanceDir The path to the server instance directory. * @param {string} pack the file name of the shaderpack. */ exports.setEnabledShaderpack = function(instanceDir, pack){ exports.validateDir(instanceDir) const optionsShaders = path.join(instanceDir, SHADER_CONFIG) let buf if(fs.existsSync(optionsShaders)){ buf = fs.readFileSync(optionsShaders, {encoding: 'utf-8'}) buf = buf.replace(SHADER_OPTION, `shaderPack=${pack}`) } else { buf = `shaderPack=${pack}` } fs.writeFileSync(optionsShaders, buf, {encoding: 'utf-8'}) } /** * Add shaderpacks. * * @param {FileList} files The files to add. * @param {string} instanceDir The path to the server instance directory. */ exports.addShaderpacks = function(files, instanceDir) { const p = path.join(instanceDir, SHADER_DIR) exports.validateDir(p) for(let f of files) { if(SHADER_REGEX.exec(f.name) != null) { fs.moveSync(f.path, path.join(p, f.name)) } } }