assetdownload.js 9.1 KB

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