assetdownload.js 9.3 KB

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