Last active
December 17, 2025 17:19
-
-
Save PhoenixIllusion/5e5cad5d469f64d33b676fd92ca0e0c4 to your computer and use it in GitHub Desktop.
Minimal ZipReader that uses js decompression stream and supports Zip64 Central Directory
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| interface ZipEntry<T> { | |
| name: string; | |
| path: string; | |
| entry?: T; | |
| children?: ZipEntry<T>[] | |
| } | |
| function splitPath(path: string) { | |
| if(path.indexOf('/')<0) { | |
| return { parent: '', name: path} | |
| } | |
| const parent = path.substring(0, path.lastIndexOf('/')); | |
| const name = path.substring(path.lastIndexOf('/')+1); | |
| return { parent, name }; | |
| } | |
| export function parseFolders<T>(records: T[], getFilename: (r: T)=>string): ZipEntry<T> { | |
| const exists = new Map<string, ZipEntry<T>>(); | |
| const root = { name: '', path: '/', children: []}; | |
| exists.set('', root); | |
| function createDirIfNeeded(path: string): ZipEntry<T> { | |
| const dir = exists.get(path); | |
| if(!dir) { | |
| const { parent, name } = splitPath(path); | |
| const parentEntry = createDirIfNeeded(parent); | |
| const result: ZipEntry<T> = { name, path, children: []}; | |
| exists.set(path, result); | |
| parentEntry.children?.push(result); | |
| return result; | |
| } | |
| return dir; | |
| } | |
| records.forEach(record => { | |
| const filename = getFilename(record); | |
| if(!filename.endsWith('/')) { | |
| const { parent, name } = splitPath(filename); | |
| const folder = createDirIfNeeded(parent); | |
| folder.children?.push({ name, path: filename, entry: record }) | |
| } | |
| }); | |
| return root; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { parseFolders } from './folder'; | |
| import './style.css' | |
| import { ZipReader } from './MinZipReader'; | |
| const zipFile = document.getElementById('zipFile') as HTMLInputElement; | |
| zipFile.addEventListener('change', async () => { | |
| const files = zipFile.files; | |
| if(files && files[0]) { | |
| const zip = new ZipReader(files[0]); | |
| await zip.parse(); | |
| const folders = parseFolders(zip.r, (R) => R[5]); | |
| const innerZipBlob = await zip.getFile(zip.r.find(x => x[5] == "Archive/Isometric Road Assets/Isometric Road Assets.zip")!); | |
| const innerZip = new ZipReader(innerZipBlob); | |
| await innerZip.parse(); | |
| const innerZipFolders = parseFolders(innerZip.r, (R) => R[5]); | |
| debugger; | |
| } | |
| }) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| export class ZipReader { | |
| /** [Method, CRC, CSize, USize, Offset, Name] */ | |
| r:any[][]=[]; | |
| b: Blob; | |
| constructor(b:Blob){this.b=b;} | |
| async parse(){ | |
| const V=(b:Blob)=>b.arrayBuffer().then(a=>new DataView(a)), | |
| g=(v:DataView,i: number,b: number)=>Number((v as any)[(b>32?'getBig':'get')+'Uint'+b](i,1)), | |
| td=new TextDecoder(); | |
| let t=this.b,d=await V(t.slice(-65536)),l=65532; | |
| while(l--&&g(d,l,32)!=0x6054b50); | |
| if(l<0)throw 0; | |
| let n=g(d,l+10,16),s=g(d,l+12,32),o=g(d,l+16,32); | |
| if(l>20&&g(d,l-20,32)==0x7064b50){ | |
| d=await V(t.slice(g(d,l-12,64))); | |
| n=g(d,32,64);s=g(d,40,64);o=g(d,48,64); | |
| } | |
| d=await V(t.slice(o,o+s)); | |
| for(let i=0;n--;){ | |
| if(g(d,i,32)!=0x2014b50)throw 1; | |
| let nl=g(d,i+28,16),el=g(d,i+30,16),cl=g(d,i+32,16); | |
| this.r.push([ | |
| g(d,i+10,16), g(d,i+16,32),g(d,i+20,32),g(d,i+24,32),g(d,i+42,32), | |
| td.decode(new Uint8Array(d.buffer,d.byteOffset+i+46,nl)) | |
| ]); | |
| i+=46+nl+el+cl; | |
| } | |
| } | |
| async getFile(x:number[]){ | |
| let b=this.b,h=new DataView(await b.slice(x[4],x[4]+30).arrayBuffer()), | |
| st=x[4]+30+h.getUint16(26,true)+h.getUint16(28,true), | |
| c=b.slice(st,st+x[2]); | |
| if(!x[0])return c; | |
| return new Response(new Blob([ | |
| new Uint8Array([31,139,8,0,0,0,0,0,0,255]), c, new Uint32Array([x[1],x[3]]) | |
| ]).stream().pipeThrough(new DecompressionStream('gzip'))).blob(); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment