@chicowall/grf-loader
Version:
A loader for GRF files (Ragnarok Online game file)
2 lines • 14.7 kB
JavaScript
var J=(n=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(n,{get:(e,t)=>(typeof require<"u"?require:e)[t]}):n)(function(n){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+n+'" is not supported')});import K from"pako";import me from"jdataview";var E=new Uint8Array([128,64,32,16,8,4,2,1]),c=new Uint8Array(8),p=new Uint8Array(8),y=new Uint8Array(8),ee=new Uint8Array([58,50,42,34,26,18,10,2,60,52,44,36,28,20,12,4,62,54,46,38,30,22,14,6,64,56,48,40,32,24,16,8,57,49,41,33,25,17,9,1,59,51,43,35,27,19,11,3,61,53,45,37,29,21,13,5,63,55,47,39,31,23,15,7]),te=new Uint8Array([40,8,48,16,56,24,64,32,39,7,47,15,55,23,63,31,38,6,46,14,54,22,62,30,37,5,45,13,53,21,61,29,36,4,44,12,52,20,60,28,35,3,43,11,51,19,59,27,34,2,42,10,50,18,58,26,33,1,41,9,49,17,57,25]),ne=new Uint8Array([16,7,20,21,29,12,28,17,1,15,23,26,5,18,31,10,2,8,24,14,32,27,3,9,19,13,30,6,22,11,4,25]),M=[new Uint8Array([239,3,65,253,216,116,30,71,38,239,251,34,179,216,132,30,57,172,167,96,98,193,205,186,92,150,144,89,5,59,122,133,64,253,30,200,231,138,139,33,218,67,100,159,45,20,177,114,245,91,200,182,156,55,118,236,57,160,163,5,82,110,15,217]),new Uint8Array([167,221,13,120,158,11,227,149,96,54,54,79,249,96,90,163,17,36,210,135,200,82,117,236,187,193,76,186,36,254,143,25,218,19,102,175,73,208,144,6,140,106,251,145,55,141,13,120,191,73,17,244,35,229,206,59,85,188,162,87,232,34,116,206]),new Uint8Array([44,234,193,191,74,36,31,194,121,71,162,124,182,217,104,21,128,86,93,1,51,253,244,174,222,48,7,155,229,131,155,104,73,180,46,131,31,194,181,124,162,25,216,229,124,47,131,218,247,107,144,254,196,1,90,151,97,166,61,64,11,88,230,61]),new Uint8Array([77,209,178,15,40,189,228,120,246,74,15,147,139,23,209,164,58,236,201,53,147,86,126,203,85,32,160,254,108,137,23,98,23,98,75,177,180,222,209,135,201,20,60,74,126,168,226,125,160,159,246,92,106,9,141,240,15,227,83,37,149,54,40,203])];function re(n,e){for(let t=0;t<64;++t){let r=ee[t]-1;n[e+(r>>3&7)]&E[r&7]&&(c[t>>3&7]|=E[t&7])}n.set(c,e),c.set(y)}function ie(n,e){for(let t=0;t<64;++t){let r=te[t]-1;n[e+(r>>3&7)]&E[r&7]&&(c[t>>3&7]|=E[t&7])}n.set(c,e),c.set(y)}function oe(n,e){for(let t=0;t<32;++t){let r=ne[t]-1;n[e+(r>>3)]&E[r&7]&&(c[(t>>3)+4]|=E[t&7])}n.set(c,e),c.set(y)}function se(n,e){c[0]=(n[e+7]<<5|n[e+4]>>3)&63,c[1]=(n[e+4]<<1|n[e+5]>>7)&63,c[2]=(n[e+4]<<5|n[e+5]>>3)&63,c[3]=(n[e+5]<<1|n[e+6]>>7)&63,c[4]=(n[e+5]<<5|n[e+6]>>3)&63,c[5]=(n[e+6]<<1|n[e+7]>>7)&63,c[6]=(n[e+6]<<5|n[e+7]>>3)&63,c[7]=(n[e+7]<<1|n[e+4]>>7)&63,n.set(c,e),c.set(y)}function ae(n,e){for(let t=0;t<4;++t)c[t]=M[t][n[t*2+0+e]]&240|M[t][n[t*2+1+e]]&15;n.set(c,e),c.set(y)}function ce(n,e){for(let t=0;t<8;t++)p[t]=n[e+t];se(p,0),ae(p,0),oe(p,0),n[e+0]^=p[4],n[e+1]^=p[5],n[e+2]^=p[6],n[e+3]^=p[7]}function B(n,e){re(n,e),ce(n,e),ie(n,e)}function k(n,e,t){let r=t.toString().length,o=r<3?1:r<5?r+1:r<7?r+9:r+15,a=e>>3;for(let i=0;i<20&&i<a;++i)B(n,i*8);for(let i=20,l=-1;i<a;++i){if(i%o===0){B(n,i*8);continue}++l&&l%7===0&&le(n,i*8)}}function j(n,e){let t=e>>3;for(let r=0;r<20&&r<t;++r)B(n,r*8)}function le(n,e){c[0]=n[e+3],c[1]=n[e+4],c[2]=n[e+6],c[3]=n[e+0],c[4]=n[e+1],c[5]=n[e+2],c[6]=n[e+5],c[7]=fe[n[e+7]],n.set(c,e),c.set(y)}var fe=(()=>{let n=new Uint8Array([0,43,108,128,1,104,72,119,96,255,185,192,254,235]),e=new Uint8Array(Array.from({length:256},(r,o)=>o)),t=n.length;for(let r=0;r<t;r+=2)e[n[r+0]]=n[r+1],e[n[r+1]]=n[r+0];return e})();var d=null;try{typeof process<"u"&&process.versions?.node&&(d=J("iconv-lite"))}catch{d=null}function ue(){return d!==null}function O(n){let e=0;for(let t of n){let r=t.charCodeAt(0);r>=128&&r<=159&&e++}return e}function I(n){let e=0;for(let t of n)t==="\uFFFD"&&e++;return e}function C(n){return I(n)+O(n)}function D(n,e){let t=e.toLowerCase();if((t==="cp949"||t==="euc-kr")&&d)try{let r=Buffer.from(n);return d.decode(r,"cp949")}catch{}try{let r=t==="cp949"?"euc-kr":t;return new TextDecoder(r,{fatal:!1}).decode(n)}catch{return Array.from(n).map(r=>String.fromCharCode(r)).join("")}}function H(n,e){let t=D(n,e),r=O(t),o=I(t),a=r+o;return{text:t,badChars:a,c1Chars:r,replacementChars:o}}var xe=[/[ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞß][¡-þ]/,/À¯/,/Àú/,/ÀÎ/,/Ÿ/,/Æä/,/ÀÌ/,/½º/,/¾Æ/,/¸ð/,/¸®/,/¿¡/,/Áö/,/µ¥/,/ÅØ/,/½ºÆ®/,/¸ÁÅä/];function $(n){if(!n||n.length===0||/[\uAC00-\uD7AF]/.test(n))return!1;for(let r of xe)if(r.test(n))return!0;let e=0;for(let r of n){let o=r.charCodeAt(0);o>=128&&o<=255&&e++}return e/n.length>.3}function Y(n){if(!d)return n;try{let e=d.encode(n,"windows-1252"),t=d.decode(e,"cp949"),r=/[\uAC00-\uD7AF]/.test(t),o=C(t),a=C(n);return r&&o<=a?t:n}catch{return n}}function de(n){if(!d)return n;try{let e=d.encode(n,"cp949");return d.decode(e,"windows-1252")}catch{return n}}function V(n){return $(n)?Y(n):n}function he(n){let t=n.split(/[\\/]/).map(o=>V(o)),r=n.includes("\\")?"\\":"/";return t.join(r)}function W(n,e=.01){if(n.length===0)return"utf-8";let t=0,r=0,o=0,a=0;for(let u of n){if(!u.some(b=>b>127))continue;a++,o+=u.length;let s=H(u,"utf-8"),m=H(u,"cp949");t+=s.badChars,r+=m.badChars}if(a===0)return"utf-8";let i=o>0?t/o:0,l=o>0?r/o:0;return i<e?"utf-8":l<i?"cp949":"utf-8"}var pe={INVALID_MAGIC:"GRF_INVALID_MAGIC",UNSUPPORTED_VERSION:"GRF_UNSUPPORTED_VERSION",NOT_LOADED:"GRF_NOT_LOADED",FILE_NOT_FOUND:"GRF_FILE_NOT_FOUND",AMBIGUOUS_PATH:"GRF_AMBIGUOUS_PATH",DECOMPRESS_FAIL:"GRF_DECOMPRESS_FAIL",CORRUPT_TABLE:"GRF_CORRUPT_TABLE",LIMIT_EXCEEDED:"GRF_LIMIT_EXCEEDED",INVALID_OFFSET:"GRF_INVALID_OFFSET",DECRYPT_REQUIRED:"GRF_DECRYPT_REQUIRED"},h=class extends Error{constructor(t,r,o){super(r);this.code=t;this.context=o;this.name="GrfError"}},ge=1,be=2,Ee=4,ye="Master of Magic",_=46,X=Uint32Array.BYTES_PER_ELEMENT*2,Ue=256*1024*1024,Ae=5e5,Fe=.01;function U(n){return n.toLowerCase().replace(/\\/g,"/")}function Q(n){let e=n.lastIndexOf(".");return e===-1||e===n.length-1?"":n.substring(e+1).toLowerCase()}function Ce(n,e){return D(n,e==="euc-kr"||e==="cp949"?"cp949":e)}var A=class{constructor(e,t){this.fd=e;this.version=512;this.fileCount=0;this.loaded=!1;this.files=new Map;this.normalizedIndex=new Map;this.extensionIndex=new Map;this.fileTableOffset=0;this.cache=new Map;this.cacheMaxSize=50;this.cacheOrder=[];this._stats={fileCount:0,badNameCount:0,collisionCount:0,extensionStats:new Map,detectedEncoding:"utf-8"};this.options={filenameEncoding:t?.filenameEncoding??"auto",autoDetectThreshold:t?.autoDetectThreshold??Fe,maxFileUncompressedBytes:t?.maxFileUncompressedBytes??Ue,maxEntries:t?.maxEntries??Ae}}async getStreamReader(e,t){let r=await this.getStreamBuffer(this.fd,e,t);return new me(r,void 0,void 0,!0)}async load(){this.loaded||(await this.parseHeader(),await this.parseFileList(),this.loaded=!0)}async parseHeader(){let e=await this.getStreamReader(0,_),t=e.getString(15);if(t!==ye)throw new h("INVALID_MAGIC","Not a GRF file (invalid signature)",{signature:t});e.skip(15);let r=e.tell();if(e.seek(42),this.version=e.getUint32(),this.version!==512&&this.version!==768)throw new h("UNSUPPORTED_VERSION",`Unsupported version "0x${this.version.toString(16)}"`,{version:this.version});if(e.seek(r),this.version===512){this.fileTableOffset=e.getUint32()+_;let o=e.getUint32();this.fileCount=e.getUint32()-o-7}else{let o=e.getUint32(),a=e.getUint32();if(a>>>8){this.version=512,e.seek(r),this.fileTableOffset=e.getUint32()+_;let i=e.getUint32();this.fileCount=e.getUint32()-i-7}else this.fileTableOffset=a*4294967296+o+_,this.fileCount=e.getUint32()}if(this.fileCount>this.options.maxEntries)throw new h("LIMIT_EXCEEDED",`File count ${this.fileCount} exceeds limit ${this.options.maxEntries}`,{fileCount:this.fileCount,maxEntries:this.options.maxEntries})}async parseFileList(){let e=this.version===768?4:0,t=await this.getStreamReader(this.fileTableOffset+e,X),r=t.getUint32(),o=t.getUint32(),a=await this.getStreamBuffer(this.fd,this.fileTableOffset+e+X,r),i;try{i=K.inflate(a)}catch(f){throw new h("CORRUPT_TABLE","Failed to decompress file table",{compressedSize:r,realSize:o,error:f instanceof Error?f.message:String(f)})}if(i.length!==o)throw new h("CORRUPT_TABLE",`File table size mismatch: expected ${o}, got ${i.length}`,{expected:o,actual:i.length});let l=this.options.filenameEncoding;if(this.options.filenameEncoding==="auto"){let f=[],s=0,m=Math.min(200,this.fileCount),b=this.version===768?21:17;for(let x=0;x<m&&s<i.length;x++){let g=s;for(;i[g]!==0&&g<i.length;)g++;let S=i.subarray(s,g);f.push(S),s=g+1+b}l=W(f,this.options.autoDetectThreshold)}this._stats.detectedEncoding=l,this._stats.badNameCount=0,this._stats.collisionCount=0,this._stats.extensionStats.clear();let u=this.version===768?21:17;for(let f=0,s=0;f<this.fileCount;++f){if(s>=i.length)throw new h("CORRUPT_TABLE",`Unexpected end of file table at entry ${f}`,{position:s,dataLength:i.length,entryIndex:f});let m=s;for(;i[m]!==0&&m<i.length;)m++;let b=i.slice(s,m),x=Ce(b,l);if(C(x)>0&&this._stats.badNameCount++,s=m+1,s+u>i.length)throw new h("CORRUPT_TABLE",`Incomplete entry data at entry ${f}`,{position:s,dataLength:i.length,entryIndex:f});let g=i[s++]|i[s++]<<8|i[s++]<<16|i[s++]<<24,S=i[s++]|i[s++]<<8|i[s++]<<16|i[s++]<<24,q=i[s++]|i[s++]<<8|i[s++]<<16|i[s++]<<24,Z=i[s++],T;if(this.version===768){let R=(i[s++]|i[s++]<<8|i[s++]<<16|i[s++]<<24)>>>0;T=((i[s++]|i[s++]<<8|i[s++]<<16|i[s++]<<24)>>>0)*4294967296+R}else T=(i[s++]|i[s++]<<8|i[s++]<<16|i[s++]<<24)>>>0;let w={compressedSize:g,lengthAligned:S,realSize:q,type:Z,offset:T,rawNameBytes:b};if(!(w.realSize>this.options.maxFileUncompressedBytes)&&w.type&ge){this.files.set(x,w);let R=U(x),P=this.normalizedIndex.get(R);P?(P.push(x),this._stats.collisionCount++):this.normalizedIndex.set(R,[x]);let F=Q(x);if(F){let N=this.extensionIndex.get(F);N?N.push(x):this.extensionIndex.set(F,[x]),this._stats.extensionStats.set(F,(this._stats.extensionStats.get(F)||0)+1)}}}this._stats.fileCount=this.files.size}decodeEntry(e,t){return t.type&be?k(e,t.lengthAligned,t.compressedSize):t.type&Ee&&j(e,t.lengthAligned),t.realSize===t.compressedSize?e:K.inflate(e)}addToCache(e,t){if(this.cacheOrder.length>=this.cacheMaxSize){let r=this.cacheOrder.shift();r&&this.cache.delete(r)}this.cache.set(e,t),this.cacheOrder.push(e)}getFromCache(e){let t=this.cache.get(e);if(t){let r=this.cacheOrder.indexOf(e);r>-1&&(this.cacheOrder.splice(r,1),this.cacheOrder.push(e))}return t}clearCache(){this.cache.clear(),this.cacheOrder=[]}async getFile(e){if(!this.loaded)return Promise.resolve({data:null,error:"GRF not loaded yet"});let t=this.resolvePath(e);if(t.status==="not_found")return Promise.resolve({data:null,error:`File "${e}" not found`});if(t.status==="ambiguous")return Promise.resolve({data:null,error:`Ambiguous path "${e}": ${t.candidates?.length} matches found. Use exact path: ${t.candidates?.slice(0,5).join(", ")}${(t.candidates?.length||0)>5?"...":""}`});let r=t.matchedPath,o=this.getFromCache(r);if(o)return Promise.resolve({data:o,error:null});let a=this.files.get(r);if(!a)return{data:null,error:`File "${r}" not found`};let i=await this.getStreamBuffer(this.fd,a.offset+_,a.lengthAligned);try{let l=this.decodeEntry(i,a);return this.addToCache(r,l),Promise.resolve({data:l,error:null})}catch(l){return{data:null,error:l instanceof Error?l.message:String(l)}}}resolvePath(e){if(this.files.has(e))return{status:"found",matchedPath:e};let t=U(e),r=this.normalizedIndex.get(t);return!r||r.length===0?{status:"not_found"}:r.length===1?{status:"found",matchedPath:r[0]}:{status:"ambiguous",candidates:r}}hasFile(e){return this.resolvePath(e).status==="found"}getEntry(e){let t=this.resolvePath(e);return t.status!=="found"||!t.matchedPath?null:this.files.get(t.matchedPath)||null}find(e={}){let{ext:t,contains:r,endsWith:o,regex:a,limit:i}=e,l=[];if(t&&!r&&!o&&!a){let u=t.toLowerCase().replace(/^\./,"");l=this.extensionIndex.get(u)||[]}else for(let u of this.files.keys()){if(t){let f=t.toLowerCase().replace(/^\./,"");if(Q(u)!==f)continue}if(r){let f=U(u),s=U(r);if(!f.includes(s))continue}if(o){let f=U(u),s=U(o);if(!f.endsWith(s))continue}if(!(a&&!a.test(u))&&(l.push(u),i&&l.length>=i))break}return i&&l.length>i&&(l=l.slice(0,i)),l}getFilesByExtension(e){let t=e.toLowerCase().replace(/^\./,"");return this.extensionIndex.get(t)||[]}listExtensions(){return Array.from(this.extensionIndex.keys()).sort()}listFiles(){return Array.from(this.files.keys())}getStats(){return{...this._stats,extensionStats:new Map(this._stats.extensionStats)}}getDetectedEncoding(){return this._stats.detectedEncoding}async reloadWithEncoding(e){this.options.filenameEncoding=e,this.files.clear(),this.normalizedIndex.clear(),this.extensionIndex.clear(),this.clearCache(),this.loaded=!1,await this.load()}};var L=class extends A{constructor(e,t){super(e,t)}async getStreamBuffer(e,t,r){return new Promise((o,a)=>{let i=new FileReader;i.onerror=a,i.onload=()=>o(new Uint8Array(i.result)),i.readAsArrayBuffer(e.slice(t,t+r))})}};import{fstatSync as _e,read as Re}from"fs";import{promisify as ve}from"util";var z=class{constructor(){this.pools=new Map;this.maxPoolSize=10;this.poolSizes=[1024,4096,8192,16384,32768,65536,131072,262144];for(let e of this.poolSizes)this.pools.set(e,[])}getPoolSize(e){for(let t of this.poolSizes)if(e<=t)return t;return null}acquire(e){let t=this.getPoolSize(e);if(t===null)return Buffer.allocUnsafe(e);let r=this.pools.get(t);if(r){let o=r.find(a=>!a.inUse);if(o)return o.inUse=!0,o.buffer.subarray(0,e);if(r.length<this.maxPoolSize){let a=Buffer.allocUnsafe(t);return r.push({buffer:a,inUse:!0}),a.subarray(0,e)}}return Buffer.allocUnsafe(e)}release(e){let t=e.buffer.byteLength,r=this.pools.get(t);if(r){let o=r.find(a=>a.buffer===e||a.buffer.buffer===e.buffer);o&&(o.inUse=!1)}}clear(){for(let e of this.pools.values())e.length=0}stats(){let e=[];for(let[t,r]of this.pools.entries())e.push({size:t,total:r.length,inUse:r.filter(o=>o.inUse).length});return e}},v=new z;var Se=ve(Re),G=class extends A{constructor(e,t){super(e,t),this.useBufferPool=t?.useBufferPool??!0;try{if(!_e(e).isFile())throw new Error("GRFNode: file descriptor must point to a regular file")}catch{throw new Error("GRFNode: invalid file descriptor")}}async getStreamBuffer(e,t,r){let o=this.useBufferPool?v.acquire(r):Buffer.allocUnsafe(r),{bytesRead:a}=await Se(e,o,0,r,t);if(a!==r)throw this.useBufferPool&&v.release(o),new Error("Not a GRF file (invalid signature)");return o}};export{pe as GRF_ERROR_CODES,L as GrfBrowser,h as GrfError,G as GrfNode,v as bufferPool,C as countBadChars,O as countC1ControlChars,I as countReplacementChars,Y as fixMojibake,ue as hasIconvLite,$ as isMojibake,he as normalizeEncodingPath,V as normalizeFilename,de as toMojibake};
//# sourceMappingURL=index.js.map