import ky from 'ky' ; import { createPool } from './threadPool' ; type EOCD = [ number , number , number ] ; interface Entry { name : string ; size : number ; begin : number ; } const pool = createPool ( 3 ) ; export const loadZip = async ( ugoira ) : Promise < Pixiv . FrameAndImage [ ] > => { const { originalSrc , frames } = ugoira ; const buffer = await ky . get ( originalSrc , { credentials : 'same-origin' , cache : 'force-cache' , retry : 1 } ) . arrayBuffer ( ) ; const eocd = getEOCD ( buffer ) ; const entries = getEntries ( buffer , eocd ) ; return Promise . all ( entries . map ( ( entry , i ) => pool . execute ( async ( ) => { const frame = frames [ i ] ; const image = await getSrc ( buffer , entry , ugoira . mime_type ) ; return { ... frame , image } ; } ) ) ) ; } ; const getEOCD = ( buffer ) : EOCD => { const length = buffer . byteLength ; const view = new DataView ( buffer , length - 22 , 22 ) ; if ( view . getUint32 ( 0 , true ) !== 0x06054b50 ) throw new Error ( 'End of Central Directory signature not found' ) ; const cdCount = view . getUint16 ( 10 , true ) ; const cdSize = view . getUint32 ( 12 , true ) ; const cdOffset = view . getUint32 ( 16 , true ) ; return [ cdCount , cdOffset , cdSize ] ; } ; const getEntries = ( buffer , [ count , offset , size ] ) => { const view = new DataView ( buffer , offset , size ) ; const entries = [ ] ; let p = 0 ; for ( var i = 0 ; i < count ; i ++ ) { if ( view . getUint32 ( p , true ) === 0x02014b50 ) { const length = view . getUint32 ( p + 24 , true ) ; const begin = view . getUint32 ( p + 42 , true ) ; const extraLength = view . getUint16 ( p + 30 , true ) ; const cmtLength = view . getUint16 ( p + 32 , true ) ; const nameLength = view . getUint16 ( p + 28 , true ) ; const nameView = new Uint8Array ( buffer , offset + p + 46 , nameLength ) ; const name = String . fromCharCode ( ... nameView ) ; const size = length + nameLength + extraLength + 30 ; entries . push ( { name , size , begin } ) ; p += nameLength + extraLength + cmtLength + 46 ; } else { throw new Error ( `インデックス ${ i } のエントリが取得できませんでした` ) ; } } return entries ; } ; const getSrc = ( buffer , entry , type ) => { return new Promise < HTMLImageElement > ( ( resolve , reject ) => { const view = new DataView ( buffer , entry . begin , entry . size ) ; const length = view . getUint32 ( 22 , true ) ; const nameLength = view . getUint16 ( 26 , true ) ; const extraLength = view . getUint16 ( 28 , true ) ; const begin = entry . begin + nameLength + extraLength + 30 ; const end = begin + length ; const blob = new Blob ( [ buffer . slice ( begin , end ) ] , { type } ) ; const src = URL . createObjectURL ( blob ) ; const img = new Image ( ) ; img . addEventListener ( 'load' , ( ) => { URL . revokeObjectURL ( src ) ; resolve ( img ) ; } ) ; img . addEventListener ( 'error' , ( ) => { URL . revokeObjectURL ( src ) ; reject ( img ) ; } ) ; img . src = src ; } ) ; } ;