浏览代码

Overhauling file system structure.

Common files such as assets, libraries, and mods have been externalized into a 'common' folder. Each server now has its own instance folder to allow saving per version files. This resolves issues with resourcepacks and mod configurations being overriden, and still preserves our optimizations in storing libraries and mods maven style.
Daniel Scalzi 7 年之前
父节点
当前提交
0cc861f614

+ 1 - 1
app/assets/js/assetexec.js

@@ -1,6 +1,6 @@
 const {AssetGuard} = require('./assetguard.js')
 const {AssetGuard} = require('./assetguard.js')
 
 
-const tracker = new AssetGuard(process.argv[2], process.argv[3], process.argv[4])
+const tracker = new AssetGuard(process.argv[2], process.argv[3], process.argv[4], process.argv[5])
 console.log('AssetExec Started')
 console.log('AssetExec Started')
 
 
 // Temporary for debug purposes.
 // Temporary for debug purposes.

+ 24 - 22
app/assets/js/assetguard.js

@@ -179,12 +179,13 @@ class AssetGuard extends EventEmitter {
      * On creation the object's properties are never-null default
      * On creation the object's properties are never-null default
      * values. Each identifier is resolved to an empty DLTracker.
      * values. Each identifier is resolved to an empty DLTracker.
      * 
      * 
-     * @param {string} basePath The base path for asset validation (game root).
+     * @param {string} commonPath The common path for shared game files.
      * @param {string} launcherPath The root launcher directory.
      * @param {string} launcherPath The root launcher directory.
      * @param {string} javaexec The path to a java executable which will be used
      * @param {string} javaexec The path to a java executable which will be used
      * to finalize installation.
      * to finalize installation.
+     * @param {string} instancePath The path to the instances directory.
      */
      */
-    constructor(basePath, launcherPath, javaexec){
+    constructor(commonPath, launcherPath, javaexec, instancePath){
         super()
         super()
         this.totaldlsize = 0
         this.totaldlsize = 0
         this.progress = 0
         this.progress = 0
@@ -194,9 +195,10 @@ class AssetGuard extends EventEmitter {
         this.forge = new DLTracker([], 0)
         this.forge = new DLTracker([], 0)
         this.java = new DLTracker([], 0)
         this.java = new DLTracker([], 0)
         this.extractQueue = []
         this.extractQueue = []
-        this.basePath = basePath
+        this.commonPath = commonPath
         this.launcherPath = launcherPath
         this.launcherPath = launcherPath
         this.javaexec = javaexec
         this.javaexec = javaexec
+        this.instancePath = instancePath
     }
     }
 
 
     // Static Utility Functions
     // Static Utility Functions
@@ -557,10 +559,10 @@ class AssetGuard extends EventEmitter {
      * in a promise.
      * in a promise.
      * 
      * 
      * @param {Asset} asset The Asset object representing Forge.
      * @param {Asset} asset The Asset object representing Forge.
-     * @param {string} basePath Base path for asset validation (game root).
+     * @param {string} commonPath The common path for shared game files.
      * @returns {Promise.<Object>} A promise which resolves to the contents of forge's version.json.
      * @returns {Promise.<Object>} A promise which resolves to the contents of forge's version.json.
      */
      */
-    static _finalizeForgeAsset(asset, basePath){
+    static _finalizeForgeAsset(asset, commonPath){
         return new Promise((resolve, reject) => {
         return new Promise((resolve, reject) => {
             fs.readFile(asset.to, (err, data) => {
             fs.readFile(asset.to, (err, data) => {
                 const zip = new AdmZip(data)
                 const zip = new AdmZip(data)
@@ -569,7 +571,7 @@ class AssetGuard extends EventEmitter {
                 for(let i=0; i<zipEntries.length; i++){
                 for(let i=0; i<zipEntries.length; i++){
                     if(zipEntries[i].entryName === 'version.json'){
                     if(zipEntries[i].entryName === 'version.json'){
                         const forgeVersion = JSON.parse(zip.readAsText(zipEntries[i]))
                         const forgeVersion = JSON.parse(zip.readAsText(zipEntries[i]))
-                        const versionPath = path.join(basePath, 'versions', forgeVersion.id)
+                        const versionPath = path.join(commonPath, 'versions', forgeVersion.id)
                         const versionFile = path.join(versionPath, forgeVersion.id + '.json')
                         const versionFile = path.join(versionPath, forgeVersion.id + '.json')
                         if(!fs.existsSync(versionFile)){
                         if(!fs.existsSync(versionFile)){
                             mkpath.sync(versionPath)
                             mkpath.sync(versionPath)
@@ -1202,7 +1204,7 @@ class AssetGuard extends EventEmitter {
         return new Promise((resolve, reject) => {
         return new Promise((resolve, reject) => {
             const name = version + '.json'
             const name = version + '.json'
             const url = 'https://s3.amazonaws.com/Minecraft.Download/versions/' + version + '/' + name
             const url = 'https://s3.amazonaws.com/Minecraft.Download/versions/' + version + '/' + name
-            const versionPath = path.join(self.basePath, 'versions', version)
+            const versionPath = path.join(self.commonPath, 'versions', version)
             const versionFile = path.join(versionPath, name)
             const versionFile = path.join(versionPath, name)
             if(!fs.existsSync(versionFile) || force){
             if(!fs.existsSync(versionFile) || force){
                 //This download will never be tracked as it's essential and trivial.
                 //This download will never be tracked as it's essential and trivial.
@@ -1255,7 +1257,7 @@ class AssetGuard extends EventEmitter {
             //Asset index constants.
             //Asset index constants.
             const assetIndex = versionData.assetIndex
             const assetIndex = versionData.assetIndex
             const name = assetIndex.id + '.json'
             const name = assetIndex.id + '.json'
-            const indexPath = path.join(self.basePath, 'assets', 'indexes')
+            const indexPath = path.join(self.commonPath, 'assets', 'indexes')
             const assetIndexLoc = path.join(indexPath, name)
             const assetIndexLoc = path.join(indexPath, name)
 
 
             let data = null
             let data = null
@@ -1291,7 +1293,7 @@ class AssetGuard extends EventEmitter {
 
 
             //Asset constants
             //Asset constants
             const resourceURL = 'http://resources.download.minecraft.net/'
             const resourceURL = 'http://resources.download.minecraft.net/'
-            const localPath = path.join(self.basePath, 'assets')
+            const localPath = path.join(self.commonPath, 'assets')
             const indexPath = path.join(localPath, 'indexes')
             const indexPath = path.join(localPath, 'indexes')
             const objectPath = path.join(localPath, 'objects')
             const objectPath = path.join(localPath, 'objects')
 
 
@@ -1338,7 +1340,7 @@ class AssetGuard extends EventEmitter {
         return new Promise((resolve, reject) => {
         return new Promise((resolve, reject) => {
 
 
             const libArr = versionData.libraries
             const libArr = versionData.libraries
-            const libPath = path.join(self.basePath, 'libraries')
+            const libPath = path.join(self.commonPath, 'libraries')
 
 
             const libDlQueue = []
             const libDlQueue = []
             let dlSize = 0
             let dlSize = 0
@@ -1394,7 +1396,7 @@ class AssetGuard extends EventEmitter {
         return new Promise((resolve, reject) => {
         return new Promise((resolve, reject) => {
             const clientData = versionData.downloads.client
             const clientData = versionData.downloads.client
             const version = versionData.id
             const version = versionData.id
-            const targetPath = path.join(self.basePath, 'versions', version)
+            const targetPath = path.join(self.commonPath, 'versions', version)
             const targetFile = version + '.jar'
             const targetFile = version + '.jar'
 
 
             let client = new Asset(version + ' client', clientData.sha1, clientData.size, clientData.url, path.join(targetPath, targetFile))
             let client = new Asset(version + ' client', clientData.sha1, clientData.size, clientData.url, path.join(targetPath, targetFile))
@@ -1421,7 +1423,7 @@ class AssetGuard extends EventEmitter {
         return new Promise((resolve, reject) => {
         return new Promise((resolve, reject) => {
             const client = versionData.logging.client
             const client = versionData.logging.client
             const file = client.file
             const file = client.file
-            const targetPath = path.join(self.basePath, 'assets', 'log_configs')
+            const targetPath = path.join(self.commonPath, 'assets', 'log_configs')
 
 
             let logConfig = new Asset(file.id, file.sha1, file.size, file.url, path.join(targetPath, file.id))
             let logConfig = new Asset(file.id, file.sha1, file.size, file.url, path.join(targetPath, file.id))
 
 
@@ -1456,13 +1458,13 @@ class AssetGuard extends EventEmitter {
                     console.error('Invalid server pack id:', serverpackid)
                     console.error('Invalid server pack id:', serverpackid)
                 }
                 }
 
 
-                self.forge = self._parseDistroModules(serv.modules, serv.mc_version)
+                self.forge = self._parseDistroModules(serv.modules, serv.mc_version, serv.id)
                 // Correct our workaround here.
                 // Correct our workaround here.
                 let decompressqueue = self.forge.callback
                 let decompressqueue = self.forge.callback
                 self.extractQueue = decompressqueue
                 self.extractQueue = decompressqueue
                 self.forge.callback = (asset, self) => {
                 self.forge.callback = (asset, self) => {
                     if(asset.type === 'forge-hosted' || asset.type === 'forge'){
                     if(asset.type === 'forge-hosted' || asset.type === 'forge'){
-                        AssetGuard._finalizeForgeAsset(asset, self.basePath)
+                        AssetGuard._finalizeForgeAsset(asset, self.commonPath)
                     }
                     }
                 }
                 }
                 resolve(serv)
                 resolve(serv)
@@ -1470,7 +1472,7 @@ class AssetGuard extends EventEmitter {
         })
         })
     }
     }
 
 
-    _parseDistroModules(modules, version){
+    _parseDistroModules(modules, version, servid){
         let alist = []
         let alist = []
         let asize = 0;
         let asize = 0;
         let decompressqueue = []
         let decompressqueue = []
@@ -1483,19 +1485,19 @@ class AssetGuard extends EventEmitter {
                 case 'forge-hosted':
                 case 'forge-hosted':
                 case 'forge':
                 case 'forge':
                 case 'library':
                 case 'library':
-                    obPath = path.join(this.basePath, 'libraries', obPath)
+                    obPath = path.join(this.commonPath, 'libraries', obPath)
                     break
                     break
                 case 'forgemod':
                 case 'forgemod':
                     //obPath = path.join(this.basePath, 'mods', obPath)
                     //obPath = path.join(this.basePath, 'mods', obPath)
-                    obPath = path.join(this.basePath, 'modstore', obPath)
+                    obPath = path.join(this.commonPath, 'modstore', obPath)
                     break
                     break
                 case 'litemod':
                 case 'litemod':
                     //obPath = path.join(this.basePath, 'mods', version, obPath)
                     //obPath = path.join(this.basePath, 'mods', version, obPath)
-                    obPath = path.join(this.basePath, 'modstore', obPath)
+                    obPath = path.join(this.commonPath, 'modstore', obPath)
                     break
                     break
                 case 'file':
                 case 'file':
                 default: 
                 default: 
-                    obPath = path.join(this.basePath, obPath)
+                    obPath = path.join(this.instancePath, servid, obPath)
             }
             }
             let artifact = new DistroModule(ob.id, obArtifact.MD5, obArtifact.size, obArtifact.url, obPath, obType)
             let artifact = new DistroModule(ob.id, obArtifact.MD5, obArtifact.size, obArtifact.url, obPath, obType)
             const validationPath = obPath.toLowerCase().endsWith('.pack.xz') ? obPath.substring(0, obPath.toLowerCase().lastIndexOf('.pack.xz')) : obPath
             const validationPath = obPath.toLowerCase().endsWith('.pack.xz') ? obPath.substring(0, obPath.toLowerCase().lastIndexOf('.pack.xz')) : obPath
@@ -1506,7 +1508,7 @@ class AssetGuard extends EventEmitter {
             }
             }
             //Recursively process the submodules then combine the results.
             //Recursively process the submodules then combine the results.
             if(ob.sub_modules != null){
             if(ob.sub_modules != null){
-                let dltrack = this._parseDistroModules(ob.sub_modules, version)
+                let dltrack = this._parseDistroModules(ob.sub_modules, version, servid)
                 asize += dltrack.dlsize*1
                 asize += dltrack.dlsize*1
                 alist = alist.concat(dltrack.dlqueue)
                 alist = alist.concat(dltrack.dlqueue)
                 decompressqueue = decompressqueue.concat(dltrack.callback)
                 decompressqueue = decompressqueue.concat(dltrack.callback)
@@ -1542,9 +1544,9 @@ class AssetGuard extends EventEmitter {
                 const ob = modules[i]
                 const ob = modules[i]
                 if(ob.type === 'forge-hosted' || ob.type === 'forge'){
                 if(ob.type === 'forge-hosted' || ob.type === 'forge'){
                     let obArtifact = ob.artifact
                     let obArtifact = ob.artifact
-                    let obPath = obArtifact.path == null ? path.join(self.basePath, 'libraries', AssetGuard._resolvePath(ob.id, obArtifact.extension)) : obArtifact.path
+                    let obPath = obArtifact.path == null ? path.join(self.commonPath, 'libraries', AssetGuard._resolvePath(ob.id, obArtifact.extension)) : obArtifact.path
                     let asset = new DistroModule(ob.id, obArtifact.MD5, obArtifact.size, obArtifact.url, obPath, ob.type)
                     let asset = new DistroModule(ob.id, obArtifact.MD5, obArtifact.size, obArtifact.url, obPath, ob.type)
-                    let forgeData = await AssetGuard._finalizeForgeAsset(asset, self.basePath)
+                    let forgeData = await AssetGuard._finalizeForgeAsset(asset, self.commonPath)
                     resolve(forgeData)
                     resolve(forgeData)
                     return
                     return
                 }
                 }

+ 18 - 1
app/assets/js/configmanager.js

@@ -34,7 +34,6 @@ const DEFAULT_CONFIG = {
             ],
             ],
         },
         },
         game: {
         game: {
-            directory: path.join(dataPath, 'game'),
             resWidth: 1280,
             resWidth: 1280,
             resHeight: 720,
             resHeight: 720,
             fullscreen: false,
             fullscreen: false,
@@ -43,6 +42,8 @@ const DEFAULT_CONFIG = {
         },
         },
         launcher: {}
         launcher: {}
     },
     },
+    commonDirectory: path.join(dataPath, 'common'),
+    instanceDirectory: path.join(dataPath, 'instances'),
     clientToken: uuidV4().replace(/-/g, ''),
     clientToken: uuidV4().replace(/-/g, ''),
     selectedServer: null, // Resolved
     selectedServer: null, // Resolved
     selectedAccount: null,
     selectedAccount: null,
@@ -139,6 +140,22 @@ exports.getTempNativeFolder = function(){
 
 
 // System Settings (Unconfigurable on UI)
 // System Settings (Unconfigurable on UI)
 
 
+/**
+ * Retrieve the common directory for shared
+ * game files (assets, libraries, etc).
+ */
+exports.getCommonDirectory = function(){
+    return config.commonDirectory
+}
+
+/**
+ * Retrieve the instance directory for the per
+ * server game directories.
+ */
+exports.getInstanceDirectory = function(){
+    return config.instanceDirectory
+}
+
 /**
 /**
  * Retrieve the launcher's Client Token.
  * Retrieve the launcher's Client Token.
  * There is no default client token.
  * There is no default client token.

+ 11 - 9
app/assets/js/processbuilder.js

@@ -16,14 +16,15 @@ const {URL} = require('url')
 
 
 class ProcessBuilder {
 class ProcessBuilder {
 
 
-    constructor(gameDirectory, distroServer, versionData, forgeData, authUser){
-        this.dir = gameDirectory
+    constructor(distroServer, versionData, forgeData, authUser){
+        this.gameDir = path.join(ConfigManager.getInstanceDirectory(), distroServer.id)
+        this.commonDir = ConfigManager.getCommonDirectory()
         this.server = distroServer
         this.server = distroServer
         this.versionData = versionData
         this.versionData = versionData
         this.forgeData = forgeData
         this.forgeData = forgeData
         this.authUser = authUser
         this.authUser = authUser
-        this.fmlDir = path.join(this.dir, 'versions', this.server.id + '.json')
-        this.libPath = path.join(this.dir, 'libraries')
+        this.fmlDir = path.join(this.commonDir, 'versions', this.server.id + '.json')
+        this.libPath = path.join(this.commonDir, 'libraries')
     }
     }
 
 
     static shouldInclude(mdle){
     static shouldInclude(mdle){
@@ -35,6 +36,7 @@ class ProcessBuilder {
      * Convienence method to run the functions typically used to build a process.
      * Convienence method to run the functions typically used to build a process.
      */
      */
     build(){
     build(){
+        mkpath.sync(this.gameDir)
         const tempNativePath = path.join(os.tmpdir(), ConfigManager.getTempNativeFolder(), crypto.pseudoRandomBytes(16).toString('hex'))
         const tempNativePath = path.join(os.tmpdir(), ConfigManager.getTempNativeFolder(), crypto.pseudoRandomBytes(16).toString('hex'))
         process.throwDeprecation = true
         process.throwDeprecation = true
         const mods = this.resolveDefaultMods()
         const mods = this.resolveDefaultMods()
@@ -44,7 +46,7 @@ class ProcessBuilder {
         console.log(args)
         console.log(args)
 
 
         const child = child_process.spawn(ConfigManager.getJavaExecutable(), args, {
         const child = child_process.spawn(ConfigManager.getJavaExecutable(), args, {
-            cwd: ConfigManager.getGameDirectory(),
+            cwd: this.gameDir,
             detached: ConfigManager.isLaunchDetached()
             detached: ConfigManager.isLaunchDetached()
         })
         })
 
 
@@ -90,7 +92,7 @@ class ProcessBuilder {
 
 
     constructFMLModList(mods, save = false){
     constructFMLModList(mods, save = false){
         const modList = {}
         const modList = {}
-        modList.repositoryRoot = path.join(this.dir, 'modstore')
+        modList.repositoryRoot = path.join(this.commonDir, 'modstore')
         const ids = []
         const ids = []
         for(let i=0; i<mods.length; ++i){
         for(let i=0; i<mods.length; ++i){
             ids.push(mods[i].id)
             ids.push(mods[i].id)
@@ -156,10 +158,10 @@ class ProcessBuilder {
                         val = this.server.id
                         val = this.server.id
                         break
                         break
                     case 'game_directory':
                     case 'game_directory':
-                        val = this.dir
+                        val = this.gameDir
                         break
                         break
                     case 'assets_root':
                     case 'assets_root':
-                        val = path.join(this.dir, 'assets')
+                        val = path.join(this.commonDir, 'assets')
                         break
                         break
                     case 'assets_index_name':
                     case 'assets_index_name':
                         val = this.versionData.assets
                         val = this.versionData.assets
@@ -223,7 +225,7 @@ class ProcessBuilder {
 
 
         // Add the version.jar to the classpath.
         // Add the version.jar to the classpath.
         const version = this.versionData.id
         const version = this.versionData.id
-        cpArgs.push(path.join(this.dir, 'versions', version, version + '.jar'))
+        cpArgs.push(path.join(this.commonDir, 'versions', version, version + '.jar'))
 
 
         // Resolve the Mojang declared libraries.
         // Resolve the Mojang declared libraries.
         const mojangLibs = this._resolveMojangLibraries(tempNativePath)
         const mojangLibs = this._resolveMojangLibraries(tempNativePath)

+ 7 - 5
app/assets/js/scripts/landing.js

@@ -257,9 +257,10 @@ function asyncSystemScan(launchAfter = true){
 
 
     // Fork a process to run validations.
     // Fork a process to run validations.
     sysAEx = cp.fork(path.join(__dirname, 'assets', 'js', 'assetexec.js'), [
     sysAEx = cp.fork(path.join(__dirname, 'assets', 'js', 'assetexec.js'), [
-        ConfigManager.getGameDirectory(),
+        ConfigManager.getCommonDirectory(),
         ConfigManager.getLauncherDirectory(),
         ConfigManager.getLauncherDirectory(),
-        ConfigManager.getJavaExecutable()
+        ConfigManager.getJavaExecutable(),
+        ConfigManager.getInstanceDirectory()
     ], {
     ], {
         stdio: 'pipe'
         stdio: 'pipe'
     })
     })
@@ -436,9 +437,10 @@ function dlAsync(login = true){
 
 
     // Start AssetExec to run validations and downloads in a forked process.
     // Start AssetExec to run validations and downloads in a forked process.
     aEx = cp.fork(path.join(__dirname, 'assets', 'js', 'assetexec.js'), [
     aEx = cp.fork(path.join(__dirname, 'assets', 'js', 'assetexec.js'), [
-        ConfigManager.getGameDirectory(),
+        ConfigManager.getCommonDirectory(),
         ConfigManager.getLauncherDirectory(),
         ConfigManager.getLauncherDirectory(),
-        ConfigManager.getJavaExecutable()
+        ConfigManager.getJavaExecutable(),
+        ConfigManager.getInstanceDirectory()
     ], {
     ], {
         stdio: 'pipe'
         stdio: 'pipe'
     })
     })
@@ -581,7 +583,7 @@ function dlAsync(login = true){
                 //}
                 //}
                 const authUser = ConfigManager.getSelectedAccount()
                 const authUser = ConfigManager.getSelectedAccount()
                 console.log('authu', authUser)
                 console.log('authu', authUser)
-                let pb = new ProcessBuilder(ConfigManager.getGameDirectory(), serv, versionData, forgeData, authUser)
+                let pb = new ProcessBuilder(serv, versionData, forgeData, authUser)
                 setLaunchDetails('Launching game..')
                 setLaunchDetails('Launching game..')
                 try {
                 try {
                     // Build Minecraft process.
                     // Build Minecraft process.