@chicowall/grf-loader 1.0.12 → 1.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +248 -37
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +205 -5
- package/dist/index.d.ts +205 -5
- package/dist/index.global.js +6 -1
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -1,85 +1,296 @@
|
|
|
1
1
|
# GRF Loader
|
|
2
2
|
|
|
3
|
-
**GRF** is an archive file format that
|
|
3
|
+
**GRF** is an archive file format that supports lossless data compression used on **Ragnarok Online** to store game assets. A GRF file may contain one or more files or directories that may have been compressed (deflate) and encrypted (variant of DES).
|
|
4
4
|
|
|
5
5
|
[](https://github.com/vthibault/roBrowser) [](https://opensource.org/licenses/MIT)
|
|
6
6
|
  
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## Features
|
|
9
9
|
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
10
|
+
- ✅ GRF version 0x200 support
|
|
11
|
+
- ✅ Works in both Node.js and browser environments
|
|
12
|
+
- ✅ DES decryption support
|
|
13
|
+
- ✅ **Korean filename encoding (CP949/EUC-KR)** with auto-detection
|
|
14
|
+
- ✅ **Mojibake detection and fixing**
|
|
15
|
+
- ✅ **Case-insensitive path resolution**
|
|
16
|
+
- ✅ **Collision-safe indexing** (no lost files)
|
|
17
|
+
- ✅ Memory efficient (streams data without loading entire file)
|
|
18
|
+
- ❌ Custom encryption not supported
|
|
15
19
|
|
|
16
20
|
## Installation
|
|
17
21
|
|
|
18
|
-
```
|
|
22
|
+
```bash
|
|
19
23
|
npm install @chicowall/grf-loader
|
|
20
24
|
```
|
|
21
25
|
|
|
22
|
-
##
|
|
23
|
-
|
|
24
|
-
- Load a grf file on node.js
|
|
25
|
-
- Load a grf from the browser
|
|
26
|
-
- List all files content
|
|
27
|
-
- Extract a file from the GRF
|
|
26
|
+
## Quick Start
|
|
28
27
|
|
|
29
|
-
###
|
|
28
|
+
### Node.js
|
|
30
29
|
|
|
31
30
|
```ts
|
|
32
|
-
import {GrfNode} from 'grf-loader';
|
|
33
|
-
import {openSync} from 'fs';
|
|
31
|
+
import { GrfNode } from '@chicowall/grf-loader';
|
|
32
|
+
import { openSync } from 'fs';
|
|
34
33
|
|
|
35
34
|
const fd = openSync('path/to/data.grf', 'r');
|
|
36
35
|
const grf = new GrfNode(fd);
|
|
37
36
|
|
|
38
|
-
// Start parsing the grf.
|
|
39
37
|
await grf.load();
|
|
38
|
+
|
|
39
|
+
// Get file
|
|
40
|
+
const { data, error } = await grf.getFile('data\\sprite\\monster.spr');
|
|
40
41
|
```
|
|
41
42
|
|
|
42
|
-
###
|
|
43
|
+
### Browser
|
|
43
44
|
|
|
44
45
|
```ts
|
|
45
|
-
import {GrfBrowser} from 'grf-loader';
|
|
46
|
+
import { GrfBrowser } from '@chicowall/grf-loader';
|
|
46
47
|
|
|
47
|
-
const
|
|
48
|
-
const grf = new GrfBrowser(
|
|
48
|
+
const file = document.querySelector('input[type="file"]').files[0];
|
|
49
|
+
const grf = new GrfBrowser(file);
|
|
49
50
|
|
|
50
|
-
// Start parsing the grf
|
|
51
51
|
await grf.load();
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
## Configuration Options
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
const grf = new GrfNode(fd, {
|
|
58
|
+
// Filename encoding: 'auto' | 'cp949' | 'euc-kr' | 'utf-8' | 'latin1'
|
|
59
|
+
filenameEncoding: 'auto',
|
|
60
|
+
|
|
61
|
+
// Auto-detection threshold for bad characters (default: 1%)
|
|
62
|
+
autoDetectThreshold: 0.01,
|
|
63
|
+
|
|
64
|
+
// Maximum uncompressed file size (default: 256MB)
|
|
65
|
+
maxFileUncompressedBytes: 256 * 1024 * 1024,
|
|
66
|
+
|
|
67
|
+
// Maximum entries allowed (default: 500,000)
|
|
68
|
+
maxEntries: 500000
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## API Reference
|
|
73
|
+
|
|
74
|
+
### File Operations
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
// Get file data
|
|
78
|
+
const { data, error } = await grf.getFile('data\\clientinfo.xml');
|
|
79
|
+
|
|
80
|
+
// Check if file exists (case-insensitive)
|
|
81
|
+
grf.hasFile('DATA\\CLIENTINFO.XML'); // true
|
|
82
|
+
|
|
83
|
+
// Get file entry metadata
|
|
84
|
+
const entry = grf.getEntry('data\\clientinfo.xml');
|
|
85
|
+
// { type, offset, realSize, compressedSize, lengthAligned, rawNameBytes }
|
|
86
|
+
|
|
87
|
+
// Resolve path (handles case-insensitivity and collisions)
|
|
88
|
+
const result = grf.resolvePath('DATA\\Sprite\\Test.spr');
|
|
89
|
+
// { status: 'found' | 'not_found' | 'ambiguous', matchedPath?, candidates? }
|
|
90
|
+
```
|
|
55
91
|
|
|
56
|
-
|
|
92
|
+
### Search API
|
|
57
93
|
|
|
58
94
|
```ts
|
|
59
|
-
|
|
60
|
-
|
|
95
|
+
// Find files with multiple filters
|
|
96
|
+
const files = grf.find({
|
|
97
|
+
ext: 'spr', // Filter by extension
|
|
98
|
+
contains: 'monster', // Filter by substring (case-insensitive)
|
|
99
|
+
endsWith: 'poring.spr', // Filter by path ending
|
|
100
|
+
regex: /^data\\sprite/, // Filter by regex
|
|
101
|
+
limit: 100 // Max results
|
|
61
102
|
});
|
|
103
|
+
|
|
104
|
+
// Get all files by extension (fast, uses index)
|
|
105
|
+
const sprites = grf.getFilesByExtension('spr');
|
|
106
|
+
const textures = grf.getFilesByExtension('bmp');
|
|
107
|
+
|
|
108
|
+
// List all unique extensions
|
|
109
|
+
const extensions = grf.listExtensions();
|
|
110
|
+
// ['spr', 'act', 'bmp', 'wav', ...]
|
|
111
|
+
|
|
112
|
+
// List all files
|
|
113
|
+
const allFiles = grf.listFiles();
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Statistics
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
const stats = grf.getStats();
|
|
120
|
+
// {
|
|
121
|
+
// fileCount: 203092,
|
|
122
|
+
// badNameCount: 4, // Files with encoding issues
|
|
123
|
+
// collisionCount: 0, // Normalized path collisions
|
|
124
|
+
// extensionStats: Map, // Extension -> count
|
|
125
|
+
// detectedEncoding: 'cp949'
|
|
126
|
+
// }
|
|
127
|
+
|
|
128
|
+
// Get detected encoding
|
|
129
|
+
const encoding = grf.getDetectedEncoding(); // 'cp949' | 'utf-8' | ...
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Encoding Utilities
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
import {
|
|
136
|
+
isMojibake,
|
|
137
|
+
fixMojibake,
|
|
138
|
+
normalizeFilename,
|
|
139
|
+
normalizeEncodingPath,
|
|
140
|
+
countBadChars,
|
|
141
|
+
hasIconvLite
|
|
142
|
+
} from '@chicowall/grf-loader';
|
|
143
|
+
|
|
144
|
+
// Detect mojibake (CP949 misread as Windows-1252)
|
|
145
|
+
isMojibake('À¯ÀúÀÎÅÍÆäÀ̽º'); // true
|
|
146
|
+
isMojibake('유저인터페이스'); // false
|
|
147
|
+
|
|
148
|
+
// Fix mojibake
|
|
149
|
+
fixMojibake('À¯ÀúÀÎÅÍÆäÀ̽º'); // '유저인터페이스'
|
|
150
|
+
|
|
151
|
+
// Normalize entire path
|
|
152
|
+
normalizeEncodingPath('data\\texture\\À¯ÀúÀÎÅÍÆäÀ̽º\\test.bmp');
|
|
153
|
+
// 'data\\texture\\유저인터페이스\\test.bmp'
|
|
154
|
+
|
|
155
|
+
// Count problematic characters
|
|
156
|
+
countBadChars('test�file.txt'); // 1 (U+FFFD replacement char)
|
|
157
|
+
|
|
158
|
+
// Check if iconv-lite is available (Node.js only)
|
|
159
|
+
hasIconvLite(); // true in Node.js, false in browser
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Korean Encoding Support
|
|
163
|
+
|
|
164
|
+
GRF files from Korean Ragnarok Online clients use CP949 encoding for filenames. This library automatically detects and handles Korean encoding:
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
// Auto-detection (default)
|
|
168
|
+
const grf = new GrfNode(fd, { filenameEncoding: 'auto' });
|
|
169
|
+
|
|
170
|
+
// Force CP949
|
|
171
|
+
const grf = new GrfNode(fd, { filenameEncoding: 'cp949' });
|
|
172
|
+
|
|
173
|
+
// Reload with different encoding
|
|
174
|
+
await grf.reloadWithEncoding('euc-kr');
|
|
62
175
|
```
|
|
63
176
|
|
|
64
|
-
###
|
|
177
|
+
### Encoding Detection Results
|
|
178
|
+
|
|
179
|
+
| Scenario | Detection | Result |
|
|
180
|
+
|----------|-----------|--------|
|
|
181
|
+
| Korean GRF | `cp949` | ✅ Proper Korean display |
|
|
182
|
+
| English GRF | `utf-8` | ✅ ASCII preserved |
|
|
183
|
+
| Mixed content | `cp949` | ✅ Both work |
|
|
65
184
|
|
|
66
|
-
|
|
185
|
+
## Error Handling
|
|
67
186
|
|
|
68
187
|
```ts
|
|
69
|
-
|
|
188
|
+
import { GrfError, GRF_ERROR_CODES } from '@chicowall/grf-loader';
|
|
70
189
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
190
|
+
try {
|
|
191
|
+
await grf.load();
|
|
192
|
+
} catch (e) {
|
|
193
|
+
if (e instanceof GrfError) {
|
|
194
|
+
switch (e.code) {
|
|
195
|
+
case 'INVALID_MAGIC':
|
|
196
|
+
console.log('Not a GRF file');
|
|
197
|
+
break;
|
|
198
|
+
case 'UNSUPPORTED_VERSION':
|
|
199
|
+
console.log('Only version 0x200 supported');
|
|
200
|
+
break;
|
|
201
|
+
case 'CORRUPT_TABLE':
|
|
202
|
+
console.log('File table is corrupted');
|
|
203
|
+
break;
|
|
204
|
+
case 'LIMIT_EXCEEDED':
|
|
205
|
+
console.log('File exceeds size limit');
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
74
210
|
```
|
|
75
211
|
|
|
76
|
-
###
|
|
212
|
+
### Error Codes
|
|
213
|
+
|
|
214
|
+
| Code | Description |
|
|
215
|
+
|------|-------------|
|
|
216
|
+
| `INVALID_MAGIC` | File is not a GRF (invalid signature) |
|
|
217
|
+
| `UNSUPPORTED_VERSION` | GRF version not 0x200 |
|
|
218
|
+
| `NOT_LOADED` | GRF not loaded yet |
|
|
219
|
+
| `FILE_NOT_FOUND` | Requested file not in archive |
|
|
220
|
+
| `AMBIGUOUS_PATH` | Multiple files match (collision) |
|
|
221
|
+
| `DECOMPRESS_FAIL` | Decompression failed |
|
|
222
|
+
| `CORRUPT_TABLE` | File table is corrupted |
|
|
223
|
+
| `LIMIT_EXCEEDED` | Size/count limit exceeded |
|
|
77
224
|
|
|
78
|
-
|
|
79
|
-
|
|
225
|
+
## Validation Tools
|
|
226
|
+
|
|
227
|
+
### Validate a Single GRF
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
npm run validate:grf -- path/to/data.grf auto 100
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Validate All GRFs in a Folder
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
npm run validate:all -- path/to/grf/folder auto
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Output example:
|
|
240
|
+
```
|
|
241
|
+
================================================================================
|
|
242
|
+
SUMMARY
|
|
243
|
+
================================================================================
|
|
244
|
+
GRFs loaded: 3/3
|
|
245
|
+
Total files: 655,144
|
|
246
|
+
Bad U+FFFD: 12
|
|
247
|
+
Bad C1 Control: 40
|
|
248
|
+
Read tests passed: 300
|
|
249
|
+
Read tests failed: 0
|
|
250
|
+
|
|
251
|
+
Encoding Health: 99.99% (655,092/655,144 clean)
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Examples
|
|
255
|
+
|
|
256
|
+
### Extract All Files
|
|
80
257
|
|
|
81
258
|
```bash
|
|
82
259
|
npx ts-node examples/extract-all.ts path/to/data.grf output-directory
|
|
83
260
|
```
|
|
84
261
|
|
|
85
|
-
|
|
262
|
+
### List All Files by Extension
|
|
263
|
+
|
|
264
|
+
```ts
|
|
265
|
+
const grf = new GrfNode(fd);
|
|
266
|
+
await grf.load();
|
|
267
|
+
|
|
268
|
+
// Get all sprite files
|
|
269
|
+
const sprites = grf.getFilesByExtension('spr');
|
|
270
|
+
console.log(`Found ${sprites.length} sprite files`);
|
|
271
|
+
|
|
272
|
+
// Get extension statistics
|
|
273
|
+
const stats = grf.getStats();
|
|
274
|
+
for (const [ext, count] of stats.extensionStats) {
|
|
275
|
+
console.log(`${ext}: ${count} files`);
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Handle Case-Insensitive Lookups
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
// All of these resolve to the same file:
|
|
283
|
+
await grf.getFile('data\\sprite\\monster.spr');
|
|
284
|
+
await grf.getFile('DATA\\SPRITE\\MONSTER.SPR');
|
|
285
|
+
await grf.getFile('data/sprite/monster.spr');
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## Browser Limitations
|
|
289
|
+
|
|
290
|
+
- **iconv-lite** is not available in browsers
|
|
291
|
+
- CP949 extended characters may show as C1 control characters
|
|
292
|
+
- Use `hasIconvLite()` to check availability
|
|
293
|
+
|
|
294
|
+
## License
|
|
295
|
+
|
|
296
|
+
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var O=Object.create;var h=Object.defineProperty;var I=Object.getOwnPropertyDescriptor;var C=Object.getOwnPropertyNames;var D=Object.getPrototypeOf,M=Object.prototype.hasOwnProperty;var N=(r,e)=>{for(var t in e)h(r,t,{get:e[t],enumerable:!0})},S=(r,e,t,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let a of C(e))!M.call(r,a)&&a!==t&&h(r,a,{get:()=>e[a],enumerable:!(o=I(e,a))||o.enumerable});return r};var P=(r,e,t)=>(t=r!=null?O(D(r)):{},S(e||!r||!r.__esModule?h(t,"default",{value:r,enumerable:!0}):t,r)),j=r=>S(h({},"__esModule",{value:!0}),r);var oe={};N(oe,{GrfBrowser:()=>p,GrfNode:()=>y,bufferPool:()=>m});module.exports=j(oe);var A=P(require("pako"),1),R=P(require("jdataview"),1);var c=new Uint8Array([128,64,32,16,8,4,2,1]),n=new Uint8Array(8),f=new Uint8Array(8),u=new Uint8Array(8),G=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]),H=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]),k=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]),T=[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 Y(r,e){for(let t=0;t<64;++t){let o=G[t]-1;r[e+(o>>3&7)]&c[o&7]&&(n[t>>3&7]|=c[t&7])}r.set(n,e),n.set(u)}function $(r,e){for(let t=0;t<64;++t){let o=H[t]-1;r[e+(o>>3&7)]&c[o&7]&&(n[t>>3&7]|=c[t&7])}r.set(n,e),n.set(u)}function q(r,e){for(let t=0;t<32;++t){let o=k[t]-1;r[e+(o>>3)]&c[o&7]&&(n[(t>>3)+4]|=c[t&7])}r.set(n,e),n.set(u)}function Z(r,e){n[0]=(r[e+7]<<5|r[e+4]>>3)&63,n[1]=(r[e+4]<<1|r[e+5]>>7)&63,n[2]=(r[e+4]<<5|r[e+5]>>3)&63,n[3]=(r[e+5]<<1|r[e+6]>>7)&63,n[4]=(r[e+5]<<5|r[e+6]>>3)&63,n[5]=(r[e+6]<<1|r[e+7]>>7)&63,n[6]=(r[e+6]<<5|r[e+7]>>3)&63,n[7]=(r[e+7]<<1|r[e+4]>>7)&63,r.set(n,e),n.set(u)}function X(r,e){for(let t=0;t<4;++t)n[t]=T[t][r[t*2+0+e]]&240|T[t][r[t*2+1+e]]&15;r.set(n,e),n.set(u)}function J(r,e){for(let t=0;t<8;t++)f[t]=r[e+t];Z(f,0),X(f,0),q(f,0),r[e+0]^=f[4],r[e+1]^=f[5],r[e+2]^=f[6],r[e+3]^=f[7]}function U(r,e){Y(r,e),J(r,e),$(r,e)}function F(r,e,t){let o=t.toString().length,a=o<3?1:o<5?o+1:o<7?o+9:o+15,i=e>>3;for(let x=0;x<20&&x<i;++x)U(r,x*8);for(let x=20,l=-1;x<i;++x){if(x%a===0){U(r,x*8);continue}++l&&l%7===0&&K(r,x*8)}}function B(r,e){let t=e>>3;for(let o=0;o<20&&o<t;++o)U(r,o*8)}function K(r,e){n[0]=r[e+3],n[1]=r[e+4],n[2]=r[e+6],n[3]=r[e+0],n[4]=r[e+1],n[5]=r[e+2],n[6]=r[e+5],n[7]=Q[r[e+7]],r.set(n,e),n.set(u)}var Q=(()=>{let r=new Uint8Array([0,43,108,128,1,104,72,119,96,255,185,192,254,235]),e=new Uint8Array(Array.from({length:256},(o,a)=>a)),t=r.length;for(let o=0;o<t;o+=2)e[r[o+0]]=r[o+1],e[r[o+1]]=r[o+0];return e})();var V=1,W=2,ee=4,te="Master of Magic",v=46,z=Uint32Array.BYTES_PER_ELEMENT*2,d=class{constructor(e){this.fd=e;this.version=512;this.fileCount=0;this.loaded=!1;this.files=new Map;this.fileTableOffset=0;this.cache=new Map;this.cacheMaxSize=50;this.cacheOrder=[]}async getStreamReader(e,t){let o=await this.getStreamBuffer(this.fd,e,t);return new R.default(o,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,v);if(e.getString(15)!==te)throw new Error("Not a GRF file (invalid signature)");e.skip(15),this.fileTableOffset=e.getUint32()+v;let o=e.getUint32();if(this.fileCount=e.getUint32()-o-7,this.version=e.getUint32(),this.version!==512)throw new Error(`Unsupported version "0x${this.version.toString(16)}"`)}async parseFileList(){let e=await this.getStreamReader(this.fileTableOffset,z),t=e.getUint32(),o=e.getUint32(),a=await this.getStreamBuffer(this.fd,this.fileTableOffset+z,t),i=A.default.inflate(a),x=new TextDecoder("utf-8");for(let l=0,s=0;l<this.fileCount;++l){let b=s;for(;i[b]!==0&&b<i.length;)b++;let L=x.decode(i.subarray(s,b));s=b+1;let E={compressedSize:i[s++]|i[s++]<<8|i[s++]<<16|i[s++]<<24,lengthAligned:i[s++]|i[s++]<<8|i[s++]<<16|i[s++]<<24,realSize:i[s++]|i[s++]<<8|i[s++]<<16|i[s++]<<24,type:i[s++],offset:(i[s++]|i[s++]<<8|i[s++]<<16|i[s++]<<24)>>>0};E.type&V&&this.files.set(L,E)}}decodeEntry(e,t){return t.type&W?F(e,t.lengthAligned,t.compressedSize):t.type&ee&&B(e,t.lengthAligned),t.realSize===t.compressedSize?e:A.default.inflate(e)}addToCache(e,t){if(this.cacheOrder.length>=this.cacheMaxSize){let o=this.cacheOrder.shift();o&&this.cache.delete(o)}this.cache.set(e,t),this.cacheOrder.push(e)}getFromCache(e){let t=this.cache.get(e);if(t){let o=this.cacheOrder.indexOf(e);o>-1&&(this.cacheOrder.splice(o,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=e;if(!this.files.has(t))return Promise.resolve({data:null,error:`File "${t}" not found`});let o=this.getFromCache(t);if(o)return Promise.resolve({data:o,error:null});let a=this.files.get(t);if(!a)return{data:null,error:`File "${t}" not found`};let i=await this.getStreamBuffer(this.fd,a.offset+v,a.lengthAligned);try{let x=this.decodeEntry(i,a);return this.addToCache(t,x),Promise.resolve({data:x,error:null})}catch(x){return{data:null,error:x instanceof Error?x.message:String(x)}}}};var p=class extends d{async getStreamBuffer(e,t,o){return new Promise((a,i)=>{let x=new FileReader;x.onerror=i,x.onload=()=>a(new Uint8Array(x.result)),x.readAsArrayBuffer(e.slice(t,t+o))})}};var g=require("fs"),_=require("util");var w=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 o=this.pools.get(t);if(o){let a=o.find(i=>!i.inUse);if(a)return a.inUse=!0,a.buffer.subarray(0,e);if(o.length<this.maxPoolSize){let i=Buffer.allocUnsafe(t);return o.push({buffer:i,inUse:!0}),i.subarray(0,e)}}return Buffer.allocUnsafe(e)}release(e){let t=e.buffer.byteLength,o=this.pools.get(t);if(o){let a=o.find(i=>i.buffer===e||i.buffer.buffer===e.buffer);a&&(a.inUse=!1)}}clear(){for(let e of this.pools.values())e.length=0}stats(){let e=[];for(let[t,o]of this.pools.entries())e.push({size:t,total:o.length,inUse:o.filter(a=>a.inUse).length});return e}},m=new w;var re=(0,_.promisify)(g.read),y=class extends d{constructor(e,t){super(e),this.useBufferPool=t?.useBufferPool??!0;try{if(!(0,g.fstatSync)(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,o){let a=this.useBufferPool?m.acquire(o):Buffer.allocUnsafe(o),{bytesRead:i}=await re(e,a,0,o,t);if(i!==o)throw this.useBufferPool&&m.release(a),new Error("Not a GRF file (invalid signature)");return a}};0&&(module.exports={GrfBrowser,GrfNode,bufferPool});
|
|
1
|
+
"use strict";var ne=Object.create;var C=Object.defineProperty;var re=Object.getOwnPropertyDescriptor;var oe=Object.getOwnPropertyNames;var ie=Object.getPrototypeOf,se=Object.prototype.hasOwnProperty;var ae=(n,e)=>{for(var t in e)C(n,t,{get:e[t],enumerable:!0})},k=(n,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of oe(e))!se.call(n,i)&&i!==t&&C(n,i,{get:()=>e[i],enumerable:!(r=re(e,i))||r.enumerable});return n};var j=(n,e,t)=>(t=n!=null?ne(ie(n)):{},k(e||!n||!n.__esModule?C(t,"default",{value:n,enumerable:!0}):t,n)),ce=n=>k(C({},"__esModule",{value:!0}),n);var Pe={};ae(Pe,{GRF_ERROR_CODES:()=>ee,GrfBrowser:()=>S,GrfError:()=>h,GrfNode:()=>T,bufferPool:()=>U,countBadChars:()=>E,countC1ControlChars:()=>_,countReplacementChars:()=>R,fixMojibake:()=>I,hasIconvLite:()=>W,isMojibake:()=>B,normalizeEncodingPath:()=>K,normalizeFilename:()=>O,toMojibake:()=>X});module.exports=ce(Pe);var L=j(require("pako"),1),J=j(require("jdataview"),1);var g=new Uint8Array([128,64,32,16,8,4,2,1]),c=new Uint8Array(8),m=new Uint8Array(8),b=new Uint8Array(8),le=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]),fe=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]),ue=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]),H=[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 xe(n,e){for(let t=0;t<64;++t){let r=le[t]-1;n[e+(r>>3&7)]&g[r&7]&&(c[t>>3&7]|=g[t&7])}n.set(c,e),c.set(b)}function de(n,e){for(let t=0;t<64;++t){let r=fe[t]-1;n[e+(r>>3&7)]&g[r&7]&&(c[t>>3&7]|=g[t&7])}n.set(c,e),c.set(b)}function he(n,e){for(let t=0;t<32;++t){let r=ue[t]-1;n[e+(r>>3)]&g[r&7]&&(c[(t>>3)+4]|=g[t&7])}n.set(c,e),c.set(b)}function me(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(b)}function pe(n,e){for(let t=0;t<4;++t)c[t]=H[t][n[t*2+0+e]]&240|H[t][n[t*2+1+e]]&15;n.set(c,e),c.set(b)}function ge(n,e){for(let t=0;t<8;t++)m[t]=n[e+t];me(m,0),pe(m,0),he(m,0),n[e+0]^=m[4],n[e+1]^=m[5],n[e+2]^=m[6],n[e+3]^=m[7]}function P(n,e){xe(n,e),ge(n,e),de(n,e)}function $(n,e,t){let r=t.toString().length,i=r<3?1:r<5?r+1:r<7?r+9:r+15,o=e>>3;for(let l=0;l<20&&l<o;++l)P(n,l*8);for(let l=20,a=-1;l<o;++l){if(l%i===0){P(n,l*8);continue}++a&&a%7===0&&be(n,l*8)}}function Y(n,e){let t=e>>3;for(let r=0;r<20&&r<t;++r)P(n,r*8)}function be(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]=Ee[n[e+7]],n.set(c,e),c.set(b)}var Ee=(()=>{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,i)=>i)),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=require("iconv-lite"))}catch{d=null}function W(){return d!==null}function _(n){let e=0;for(let t of n){let r=t.charCodeAt(0);r>=128&&r<=159&&e++}return e}function R(n){let e=0;for(let t of n)t==="\uFFFD"&&e++;return e}function E(n){return R(n)+_(n)}function v(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 V(n,e){let t=v(n,e),r=_(t),i=R(t),o=r+i;return{text:t,badChars:o,c1Chars:r,replacementChars:i}}var ye=[/[ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞß][¡-þ]/,/À¯/,/Àú/,/ÀÎ/,/Ÿ/,/Æä/,/ÀÌ/,/½º/,/¾Æ/,/¸ð/,/¸®/,/¿¡/,/Áö/,/µ¥/,/ÅØ/,/½ºÆ®/,/¸ÁÅä/];function B(n){if(!n||n.length===0||/[\uAC00-\uD7AF]/.test(n))return!1;for(let r of ye)if(r.test(n))return!0;let e=0;for(let r of n){let i=r.charCodeAt(0);i>=128&&i<=255&&e++}return e/n.length>.3}function I(n){if(!d)return n;try{let e=d.encode(n,"windows-1252"),t=d.decode(e,"cp949"),r=/[\uAC00-\uD7AF]/.test(t),i=E(t),o=E(n);return r&&i<=o?t:n}catch{return n}}function X(n){if(!d)return n;try{let e=d.encode(n,"cp949");return d.decode(e,"windows-1252")}catch{return n}}function O(n){return B(n)?I(n):n}function K(n){let t=n.split(/[\\/]/).map(i=>O(i)),r=n.includes("\\")?"\\":"/";return t.join(r)}function Q(n,e=.01){if(n.length===0)return"utf-8";let t=0,r=0,i=0,o=0;for(let s of n){if(!s.some(p=>p>127))continue;o++,i+=s.length;let x=V(s,"utf-8"),f=V(s,"cp949");t+=x.badChars,r+=f.badChars}if(o===0)return"utf-8";let l=i>0?t/i:0,a=i>0?r/i:0;return l<e?"utf-8":a<l?"cp949":"utf-8"}var ee={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,i){super(r);this.code=t;this.context=i;this.name="GrfError"}},Ae=1,Fe=2,Ue=4,Ce="Master of Magic",D=46,q=Uint32Array.BYTES_PER_ELEMENT*2,_e=256*1024*1024,Re=5e5,Se=.01;function y(n){return n.toLowerCase().replace(/\\/g,"/")}function Z(n){let e=n.lastIndexOf(".");return e===-1||e===n.length-1?"":n.substring(e+1).toLowerCase()}function Te(n,e){return v(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??Se,maxFileUncompressedBytes:t?.maxFileUncompressedBytes??_e,maxEntries:t?.maxEntries??Re}}async getStreamReader(e,t){let r=await this.getStreamBuffer(this.fd,e,t);return new J.default(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,D),t=e.getString(15);if(t!==Ce)throw new h("INVALID_MAGIC","Not a GRF file (invalid signature)",{signature:t});e.skip(15),this.fileTableOffset=e.getUint32()+D;let r=e.getUint32();if(this.fileCount=e.getUint32()-r-7,this.version=e.getUint32(),this.version!==512)throw new h("UNSUPPORTED_VERSION",`Unsupported version "0x${this.version.toString(16)}"`,{version:this.version});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=await this.getStreamReader(this.fileTableOffset,q),t=e.getUint32(),r=e.getUint32(),i=await this.getStreamBuffer(this.fd,this.fileTableOffset+q,t),o;try{o=L.default.inflate(i)}catch(a){throw new h("CORRUPT_TABLE","Failed to decompress file table",{compressedSize:t,realSize:r,error:a instanceof Error?a.message:String(a)})}if(o.length!==r)throw new h("CORRUPT_TABLE",`File table size mismatch: expected ${r}, got ${o.length}`,{expected:r,actual:o.length});let l=this.options.filenameEncoding;if(this.options.filenameEncoding==="auto"){let a=[],s=0,u=Math.min(200,this.fileCount);for(let x=0;x<u&&s<o.length;x++){let f=s;for(;o[f]!==0&&f<o.length;)f++;let p=o.subarray(s,f);a.push(p),s=f+1+17}l=Q(a,this.options.autoDetectThreshold)}this._stats.detectedEncoding=l,this._stats.badNameCount=0,this._stats.collisionCount=0,this._stats.extensionStats.clear();for(let a=0,s=0;a<this.fileCount;++a){if(s>=o.length)throw new h("CORRUPT_TABLE",`Unexpected end of file table at entry ${a}`,{position:s,dataLength:o.length,entryIndex:a});let u=s;for(;o[u]!==0&&u<o.length;)u++;let x=o.slice(s,u),f=Te(x,l);if(E(f)>0&&this._stats.badNameCount++,s=u+1,s+17>o.length)throw new h("CORRUPT_TABLE",`Incomplete entry data at entry ${a}`,{position:s,dataLength:o.length,entryIndex:a});let p={compressedSize:o[s++]|o[s++]<<8|o[s++]<<16|o[s++]<<24,lengthAligned:o[s++]|o[s++]<<8|o[s++]<<16|o[s++]<<24,realSize:o[s++]|o[s++]<<8|o[s++]<<16|o[s++]<<24,type:o[s++],offset:(o[s++]|o[s++]<<8|o[s++]<<16|o[s++]<<24)>>>0,rawNameBytes:x};if(!(p.realSize>this.options.maxFileUncompressedBytes)&&p.type&Ae){this.files.set(f,p);let G=y(f),N=this.normalizedIndex.get(G);N?(N.push(f),this._stats.collisionCount++):this.normalizedIndex.set(G,[f]);let F=Z(f);if(F){let M=this.extensionIndex.get(F);M?M.push(f):this.extensionIndex.set(F,[f]),this._stats.extensionStats.set(F,(this._stats.extensionStats.get(F)||0)+1)}}}this._stats.fileCount=this.files.size}decodeEntry(e,t){return t.type&Fe?$(e,t.lengthAligned,t.compressedSize):t.type&Ue&&Y(e,t.lengthAligned),t.realSize===t.compressedSize?e:L.default.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,i=this.getFromCache(r);if(i)return Promise.resolve({data:i,error:null});let o=this.files.get(r);if(!o)return{data:null,error:`File "${r}" not found`};let l=await this.getStreamBuffer(this.fd,o.offset+D,o.lengthAligned);try{let a=this.decodeEntry(l,o);return this.addToCache(r,a),Promise.resolve({data:a,error:null})}catch(a){return{data:null,error:a instanceof Error?a.message:String(a)}}}resolvePath(e){if(this.files.has(e))return{status:"found",matchedPath:e};let t=y(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:i,regex:o,limit:l}=e,a=[];if(t&&!r&&!i&&!o){let s=t.toLowerCase().replace(/^\./,"");a=this.extensionIndex.get(s)||[]}else for(let s of this.files.keys()){if(t){let u=t.toLowerCase().replace(/^\./,"");if(Z(s)!==u)continue}if(r){let u=y(s),x=y(r);if(!u.includes(x))continue}if(i){let u=y(s),x=y(i);if(!u.endsWith(x))continue}if(!(o&&!o.test(s))&&(a.push(s),l&&a.length>=l))break}return l&&a.length>l&&(a=a.slice(0,l)),a}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 S=class extends A{constructor(e,t){super(e,t)}async getStreamBuffer(e,t,r){return new Promise((i,o)=>{let l=new FileReader;l.onerror=o,l.onload=()=>i(new Uint8Array(l.result)),l.readAsArrayBuffer(e.slice(t,t+r))})}};var w=require("fs"),te=require("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 i=r.find(o=>!o.inUse);if(i)return i.inUse=!0,i.buffer.subarray(0,e);if(r.length<this.maxPoolSize){let o=Buffer.allocUnsafe(t);return r.push({buffer:o,inUse:!0}),o.subarray(0,e)}}return Buffer.allocUnsafe(e)}release(e){let t=e.buffer.byteLength,r=this.pools.get(t);if(r){let i=r.find(o=>o.buffer===e||o.buffer.buffer===e.buffer);i&&(i.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(i=>i.inUse).length});return e}},U=new z;var we=(0,te.promisify)(w.read),T=class extends A{constructor(e,t){super(e,t),this.useBufferPool=t?.useBufferPool??!0;try{if(!(0,w.fstatSync)(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 i=this.useBufferPool?U.acquire(r):Buffer.allocUnsafe(r),{bytesRead:o}=await we(e,i,0,r,t);if(o!==r)throw this.useBufferPool&&U.release(i),new Error("Not a GRF file (invalid signature)");return i}};0&&(module.exports={GRF_ERROR_CODES,GrfBrowser,GrfError,GrfNode,bufferPool,countBadChars,countC1ControlChars,countReplacementChars,fixMojibake,hasIconvLite,isMojibake,normalizeEncodingPath,normalizeFilename,toMojibake});
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|