assetdownload.js 8.2 KB

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