assetdownload.js 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. const fs = require('fs')
  2. const request = require('request')
  3. const path = require('path')
  4. const mkpath = require('mkdirp');
  5. const async = require('async')
  6. const crypto = require('crypto')
  7. const Library = require('./library.js')
  8. const {BrowserWindow} = require('electron')
  9. /**
  10. * PHASING THIS OUT, WILL BE REMOVED WHEN ASSET GUARD MODULE IS COMPLETE!
  11. */
  12. function Asset(from, to, size, hash){
  13. this.from = from
  14. this.to = to
  15. this.size = size
  16. this.hash = hash
  17. }
  18. function AssetIndex(id, sha1, size, url, totalSize){
  19. this.id = id
  20. this.sha1 = sha1
  21. this.size = size
  22. this.url = url
  23. this.totalSize = totalSize
  24. }
  25. /**
  26. * This function will download the version index data and read it into a Javascript
  27. * Object. This object will then be returned.
  28. */
  29. parseVersionData = function(version, basePath){
  30. const name = version + '.json'
  31. const baseURL = 'https://s3.amazonaws.com/Minecraft.Download/versions/' + version + '/' + name
  32. const versionPath = path.join(basePath, 'versions', version)
  33. return new Promise(function(fulfill, reject){
  34. request.head(baseURL, function(err, res, body){
  35. console.log('Preparing download of ' + version + ' assets.')
  36. mkpath.sync(versionPath)
  37. const stream = request(baseURL).pipe(fs.createWriteStream(path.join(versionPath, name)))
  38. stream.on('finish', function(){
  39. fulfill(JSON.parse(fs.readFileSync(path.join(versionPath, name))))
  40. })
  41. })
  42. })
  43. }
  44. /**
  45. * Download the client for version. This file is 'client.jar' although
  46. * it must be renamed to '{version}'.jar.
  47. */
  48. downloadClient = function(versionData, basePath){
  49. const dls = versionData['downloads']
  50. const clientData = dls['client']
  51. const url = clientData['url']
  52. const size = clientData['size']
  53. const version = versionData['id']
  54. const sha1 = clientData['sha1']
  55. const targetPath = path.join(basePath, 'versions', version)
  56. const targetFile = version + '.jar'
  57. if(!validateLocalIntegrity(path.join(targetPath, targetFile), 'sha1', sha1)){
  58. request.head(url, function(err, res, body){
  59. console.log('Downloading ' + version + ' client..')
  60. mkpath.sync(targetPath)
  61. const stream = request(url).pipe(fs.createWriteStream(path.join(targetPath, targetFile)))
  62. stream.on('finish', function(){
  63. console.log('Finished downloading ' + version + ' client.')
  64. })
  65. })
  66. }
  67. }
  68. downloadLogConfig = function(versionData, basePath){
  69. const logging = versionData['logging']
  70. const client = logging['client']
  71. const file = client['file']
  72. const version = versionData['id']
  73. const sha1 = file['sha1']
  74. const targetPath = path.join(basePath, 'assets', 'log_configs')
  75. const name = file['id']
  76. const url = file['url']
  77. if(!validateLocalIntegrity(path.join(targetPath, name), 'sha1', sha1)){
  78. request.head(url, function(err, res, body){
  79. console.log('Downloading ' + version + ' log config..')
  80. mkpath.sync(targetPath)
  81. const stream = request(url).pipe(fs.createWriteStream(path.join(targetPath, name)))
  82. stream.on('finish', function(){
  83. console.log('Finished downloading ' + version + ' log config..')
  84. })
  85. })
  86. }
  87. }
  88. downloadLibraries = function(versionData, basePath){
  89. const libArr = versionData['libraries']
  90. const libPath = path.join(basePath, 'libraries')
  91. let win = BrowserWindow.getFocusedWindow()
  92. const libDlQueue = []
  93. let dlSize = 0
  94. //Check validity of each library. If the hashs don't match, download the library.
  95. libArr.forEach(function(lib, index){
  96. if(Library.validateRules(lib.rules)){
  97. let artifact = null
  98. if(lib.natives == null){
  99. artifact = lib.downloads.artifact
  100. } else {
  101. artifact = lib.downloads.classifiers[lib.natives[Library.mojangFriendlyOS()]]
  102. }
  103. const libItm = new Library(lib.name, artifact.sha1, artifact.size, artifact.url, path.join(libPath, artifact.path))
  104. if(!validateLocalIntegrity(libItm.to, 'sha1', libItm.sha1)){
  105. dlSize += libItm.size
  106. libDlQueue.push(libItm)
  107. }
  108. }
  109. })
  110. let acc = 0;
  111. //Download all libraries that failed validation.
  112. async.eachLimit(libDlQueue, 1, function(lib, cb){
  113. mkpath.sync(path.join(lib.to, '..'))
  114. let req = request(lib.from)
  115. let writeStream = fs.createWriteStream(lib.to)
  116. req.pipe(writeStream)
  117. req.on('data', function(chunk){
  118. acc += chunk.length
  119. //console.log('Progress', acc/dlSize)
  120. win.setProgressBar(acc/dlSize)
  121. })
  122. writeStream.on('close', cb)
  123. }, function(err){
  124. if(err){
  125. console.log('A library failed to process');
  126. } else {
  127. console.log('All libraries have been processed successfully');
  128. }
  129. win.setProgressBar(-1)
  130. })
  131. }
  132. /**
  133. * Given an index url, this function will asynchonously download the
  134. * assets associated with that version.
  135. */
  136. downloadAssets = function(versionData, basePath){
  137. //Asset index constants.
  138. const assetIndex = versionData.assetIndex
  139. const indexURL = assetIndex.url
  140. const gameVersion = versionData.id
  141. const assetVersion = assetIndex.id
  142. const name = assetVersion + '.json'
  143. //Asset constants
  144. const resourceURL = 'http://resources.download.minecraft.net/'
  145. const localPath = path.join(basePath, 'assets')
  146. const indexPath = path.join(localPath, 'indexes')
  147. const objectPath = path.join(localPath, 'objects')
  148. let win = BrowserWindow.getFocusedWindow()
  149. const assetIndexLoc = path.join(indexPath, name)
  150. /*if(!fs.existsSync(assetIndexLoc)){
  151. }*/
  152. console.log('Downloading ' + gameVersion + ' asset index.')
  153. mkpath.sync(indexPath)
  154. const stream = request(indexURL).pipe(fs.createWriteStream(assetIndexLoc))
  155. stream.on('finish', function() {
  156. const data = JSON.parse(fs.readFileSync(assetIndexLoc, 'utf-8'))
  157. const assetDlQueue = []
  158. let dlSize = 0;
  159. Object.keys(data.objects).forEach(function(key, index){
  160. const ob = data.objects[key]
  161. const hash = ob.hash
  162. const assetName = path.join(hash.substring(0, 2), hash)
  163. const urlName = hash.substring(0, 2) + "/" + hash
  164. const ast = new Asset(resourceURL + urlName, path.join(objectPath, assetName), ob.size, String(ob.hash))
  165. if(!validateLocalIntegrity(ast.to, 'sha1', ast.hash)){
  166. dlSize += ast.size
  167. assetDlQueue.push(ast)
  168. }
  169. })
  170. let acc = 0;
  171. async.eachLimit(assetDlQueue, 5, function(asset, cb){
  172. mkpath.sync(path.join(asset.to, ".."))
  173. let req = request(asset.from)
  174. let writeStream = fs.createWriteStream(asset.to)
  175. req.pipe(writeStream)
  176. req.on('data', function(chunk){
  177. acc += chunk.length
  178. console.log('Progress', acc/dlSize)
  179. win.setProgressBar(acc/dlSize)
  180. })
  181. writeStream.on('close', cb)
  182. }, function(err){
  183. if(err){
  184. console.log('An asset failed to process');
  185. } else {
  186. console.log('All assets have been processed successfully');
  187. }
  188. win.setProgressBar(-1)
  189. })
  190. })
  191. }
  192. validateLocalIntegrity = function(filePath, algo, hash){
  193. if(fs.existsSync(filePath)){
  194. let fileName = path.basename(filePath)
  195. console.log('Validating integrity of local file', fileName)
  196. let shasum = crypto.createHash(algo)
  197. let content = fs.readFileSync(filePath)
  198. shasum.update(content)
  199. let localhash = shasum.digest('hex')
  200. if(localhash === hash){
  201. console.log('Hash value of ' + fileName + ' matches the index hash, woo!')
  202. return true
  203. } else {
  204. console.log('Hash value of ' + fileName + ' (' + localhash + ')' + ' does not match the index hash. Redownloading..')
  205. return false
  206. }
  207. }
  208. return false;
  209. }
  210. module.exports = {
  211. parseVersionData,
  212. downloadClient,
  213. downloadLogConfig,
  214. downloadLibraries,
  215. downloadAssets
  216. }