Skip to content

Instantly share code, notes, and snippets.

@remixer-dec
Last active August 26, 2022 12:35
Show Gist options
  • Save remixer-dec/f109b10c72b2796c613b588b8a191ac9 to your computer and use it in GitHub Desktop.
Save remixer-dec/f109b10c72b2796c613b588b8a191ac9 to your computer and use it in GitHub Desktop.
Node script for hashing J2ME JAR files, excluding editable parts
// MULTI-CORE J2ME JAR HASHER | EXCLUDES EVERYTHING INSIDE MANIFEST FOLDER TO IGNORE MANIFEST-ONLY-CHANGES | (c) Remixer Dec 2019 | License: CC BY-NC 4.0
// usage: node jarhasher.js "C:/path/to/folder_with_jar_files_only_with_forward_slashes" CPU_LOGICAL_CORES HASHLIMIT
// output: 2 files hashed.json, corrupted.json
// output format: [["filename","md5","sha1"],...]
const os = require('os')
const JSZip = require("jszip");
const fs = require("fs")
const crypto = require('crypto')
const cluster = require('cluster');
const path = require('path')
// configuration
const DIR = process.argv[2] || 'C:/Users/Admin/Downloads/J2ME'
const HASHLIMIT = process.argv[4] || Infinity //hash only n files in JAR instead of all of them
const CPUCORES = process.argv[3] || os.cpus().length / 2 //It's recommended to set PHYSICAL core count
function zipHash(fn){
return new Promise((rs,rj)=>{
fs.readFile(path.posix.join(DIR,fn),(err,f)=>{
JSZip.loadAsync(f).then(function (zip) {
let files = Object.values(zip.files).sort((a,b)=>a.name.localeCompare(b.name))
let md5 = crypto.createHash("md5");
let sha = crypto.createHash("sha1");
for(let i=0,l=files.length;i<l;i++){
if(files[i].name.slice(0,8).toLowerCase() != 'meta-inf'){
if(i==HASHLIMIT) break
let b = new Buffer(4)
b.writeInt32LE(files[i]._data.crc32)
md5.update(b)
sha.update(b)
}
}
let md5h = md5.digest().toString('hex')
let shah = sha.digest().toString('hex')
rs([fn,md5h,shah])
}).catch(e=>{
corrupted.push(fn)
rj()
});
})
})
}
function arrSplit(arr,chunkSize){
return new Array(Math.ceil(arr.length / chunkSize)).fill().map(_ => arr.splice(0,chunkSize))
}
const progress = []
let allData = []
let corrupted = []
let jobsDone = 0
if (cluster.isMaster) {
//main process
let t1 = Date.now()
let fl = fs.readdirSync(DIR).filter(x=>x.toLowerCase().endsWith('jar'))
let arrs = arrSplit(fl,Math.ceil(fl.length/CPUCORES))
for(let y=0;y<CPUCORES;y++){
let fr = cluster.fork()
progress.push(0)
fr.on('message',(msg)=>{
if('progress' in msg){
progress[msg.id-1] = msg.progress
process.title = 'Hashing: '+progress.map(x=>x+'%').join(' | ')
return
}
console.log(`worker #${msg.id} is done!`)
allData = [].concat(allData,msg.data)
corrupted = [].concat(corrupted, msg.corrupted)
jobsDone++
if(jobsDone == CPUCORES){
let t2 = Date.now()
console.log(`job's done in ${(t2-t1)} ms, hashed ${allData.length} files, found ${corrupted.length} corrupted files`);
fs.writeFileSync('hashed.json',JSON.stringify(allData))
fs.writeFileSync('corrupted.json',JSON.stringify(corrupted))
process.exit()
}
})
fr.send({files:arrs[y]});
}
} else {
process.on('message', async (msg)=>{
console.log(`Worker #${cluster.worker.id} started.`)
let results = []
for(let j=0,l=msg.files.length;j<l;j++){
let file = msg.files[j]
let hash = await zipHash(file).catch(e=>console.error(file+' failed, archive corrupted'))
if(hash){
results.push(hash)
}
if(j % 100 == 0){
process.send({progress:Math.round(j*100/l),id:cluster.worker.id})
}
}
process.send({data: results,corrupted: corrupted,id:cluster.worker.id})
})
}
{
"name": "multicore-jar-hasher",
"version": "1.0.1",
"description": "script for hashing J2ME JAR files, excluding editable parts",
"main": "jarhasher.js",
"dependencies": {
"jszip": "^3.2.2"
},
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Remixer Dec",
"license": "MIT"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment