grf-file-extractor 1.0.35
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 +33 -0
- package/dist/index.js +32 -0
- package/dist/index.mjs +7 -0
- package/index.js +192 -0
- package/lib/Loaders/GameFile.js +368 -0
- package/lib/Loaders/GameFileDecrypt.js +346 -0
- package/lib/Utils/BinaryReader.js +334 -0
- package/lib/Utils/BinaryWriter.js +326 -0
- package/lib/Utils/Inflate.js +382 -0
- package/lib/Utils/Struct.js +76 -0
- package/lib/Vendors/text-encoding.js +2494 -0
- package/lib/cps.js +72 -0
- package/lib/extractor.js +258 -0
- package/lib/grf.js +41 -0
- package/package.json +24 -0
- package/profile.js +17 -0
- package/src/index.ts +3 -0
- package/tsconfig.json +18 -0
- package/tsup.config.ts +10 -0
package/README.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
Grf-extractor
|
|
2
|
+
=========
|
|
3
|
+
|
|
4
|
+
This is a command line tool for extracting and handling .grf packages.
|
|
5
|
+
|
|
6
|
+
GRF files are a set of packed and compressed files, normally found on Ragnarok's game files.
|
|
7
|
+
|
|
8
|
+
Install
|
|
9
|
+
=========
|
|
10
|
+
```
|
|
11
|
+
npm install -g grf-extractor
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Usage
|
|
15
|
+
====
|
|
16
|
+
```
|
|
17
|
+
Usage: grf-extractor -g data.grf -o output_dir
|
|
18
|
+
Options:
|
|
19
|
+
-g, --grf The grf file to be worked on.
|
|
20
|
+
-s, --search Search a single file on the .grf or a list of files separated by comma. RegExp are supported.
|
|
21
|
+
-l, --list List files inside the grf.
|
|
22
|
+
-o, --output Output directoy to write the extracted files to.
|
|
23
|
+
-e, --extract Extract a single file from the .grf, prints to stdout. Example: grf-extractor -e data/clientinfo.xml > clientinfo.xml
|
|
24
|
+
-c, --concurrency Concurrency rate, how many parallel extractions should we do, set it to higher values for a faster extraction. [default: 100]
|
|
25
|
+
-v, --verbose Enable verbose output, will print debug messages.
|
|
26
|
+
-h, --help Print help and usage information
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
TODO
|
|
30
|
+
======
|
|
31
|
+
* Support writting files to the grf.
|
|
32
|
+
* Standardize the API.
|
|
33
|
+
* Refactor `extractor.js`.
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
sayHello: () => sayHello
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
function sayHello(name = "world") {
|
|
27
|
+
return `Hello, ${name}!`;
|
|
28
|
+
}
|
|
29
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
30
|
+
0 && (module.exports = {
|
|
31
|
+
sayHello
|
|
32
|
+
});
|
package/dist/index.mjs
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Entry point
|
|
3
|
+
// Parse arguments and setup the enviroment
|
|
4
|
+
|
|
5
|
+
// Dependencies
|
|
6
|
+
var argv = require('optimist')
|
|
7
|
+
var _package = require('./package.json')
|
|
8
|
+
var Grf = require('./lib/grf.js')
|
|
9
|
+
var ProgressBar = require('progress')
|
|
10
|
+
var Fs = require('fs')
|
|
11
|
+
|
|
12
|
+
// Setup args
|
|
13
|
+
argv.options('g', {
|
|
14
|
+
alias: 'grf',
|
|
15
|
+
describe: 'The grf file to be worked on.',
|
|
16
|
+
})
|
|
17
|
+
argv.options('s', {
|
|
18
|
+
alias: 'search',
|
|
19
|
+
describe: 'Search a single file on the .grf or a list of files separated by comma. RegExp are supported.',
|
|
20
|
+
})
|
|
21
|
+
argv.options('l', {
|
|
22
|
+
alias: 'list',
|
|
23
|
+
describe: 'List files inside the grf.',
|
|
24
|
+
})
|
|
25
|
+
argv.options('o', {
|
|
26
|
+
alias: 'output',
|
|
27
|
+
describe: 'Output directoy to write the extracted files to.',
|
|
28
|
+
})
|
|
29
|
+
argv.options('e', {
|
|
30
|
+
alias: 'extract',
|
|
31
|
+
describe: 'Extract a single file from the .grf, prints to stdout. Example: grf-extractor -e data/clientinfo.xml > clientinfo.xml',
|
|
32
|
+
})
|
|
33
|
+
argv.options('c', {
|
|
34
|
+
alias: 'concurrency',
|
|
35
|
+
default: 100,
|
|
36
|
+
describe: 'Concurrency rate, how many parallel extractions should we do, set it to higher values for a faster extraction.',
|
|
37
|
+
})
|
|
38
|
+
argv.options('v', {
|
|
39
|
+
alias: 'verbose',
|
|
40
|
+
describe: 'Enable verbose output, will print debug messages.',
|
|
41
|
+
})
|
|
42
|
+
argv.options('version', {
|
|
43
|
+
describe: 'Print package version.',
|
|
44
|
+
})
|
|
45
|
+
argv.options('k', {
|
|
46
|
+
alias: 'key',
|
|
47
|
+
describe: 'Path to a cps.dll for decrypting encrypted GRF files.',
|
|
48
|
+
})
|
|
49
|
+
argv.options('h', {
|
|
50
|
+
alias: 'help',
|
|
51
|
+
describe: 'Print help and usage information',
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
// Parse arguments
|
|
56
|
+
var arg = argv.argv
|
|
57
|
+
|
|
58
|
+
// Print version
|
|
59
|
+
if(arg.version) {
|
|
60
|
+
console.log(_package.version)
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Print help
|
|
65
|
+
if(!arg.g || arg.h) {
|
|
66
|
+
console.error("Usage: grf-extractor -g data.grf -o output_dir [-k cps.dll]")
|
|
67
|
+
argv.showHelp()
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Init working grf
|
|
72
|
+
var fd = Fs.openSync(arg.g, 'r')
|
|
73
|
+
|
|
74
|
+
var grf = new Grf({
|
|
75
|
+
fd: fd
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
// Set debugging mode
|
|
79
|
+
if(typeof arg.v !== 'undefined') {
|
|
80
|
+
grf.printDebug = true
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Search and print information about a single file in the grf
|
|
84
|
+
if(typeof arg.s === 'string') {
|
|
85
|
+
var list = arg.s.split(',')
|
|
86
|
+
|
|
87
|
+
for(var i in list) {
|
|
88
|
+
var pattern = list[i]
|
|
89
|
+
|
|
90
|
+
grf.searchPattern(new RegExp(pattern, 'gi'), function(matches) {
|
|
91
|
+
console.log("Search for %s:", pattern)
|
|
92
|
+
|
|
93
|
+
if(! matches) {
|
|
94
|
+
console.log("- Nothing found matching this pattern.")
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
for(var m in matches) {
|
|
99
|
+
var match = matches[m]
|
|
100
|
+
|
|
101
|
+
console.log("- File path: %s - File size: %d kb", match.filename, match.real_size/1000)
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// List files inside the grf
|
|
110
|
+
if(typeof arg.l !== 'undefined') {
|
|
111
|
+
var list = grf.entries
|
|
112
|
+
|
|
113
|
+
for(var i in list) {
|
|
114
|
+
var file = list[i]
|
|
115
|
+
|
|
116
|
+
console.log("%s - size in bytes: %d", file.filename, file.real_size)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Extract a single file frm the grf
|
|
123
|
+
if(typeof arg.e === 'string') {
|
|
124
|
+
var filename = arg.e
|
|
125
|
+
|
|
126
|
+
// We need to replace our normal slashes for windows forward slash
|
|
127
|
+
filename = filename.replace( /\//g, '\\')
|
|
128
|
+
|
|
129
|
+
var exists = grf.getFile(filename, function(data){
|
|
130
|
+
var buf = Buffer.from(new Uint8Array(data))
|
|
131
|
+
process.stdout.write(buf)
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
if(exists === false) {
|
|
135
|
+
console.error("File '%s' not in the .grf, don't forget to put the data/ folder in front.", filename)
|
|
136
|
+
console.error("Example: -e data/clientinfo.xml")
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Load CPS decryption key if provided
|
|
143
|
+
var Cluster = require('cluster')
|
|
144
|
+
var cpsSbox = null
|
|
145
|
+
if(typeof arg.k === 'string') {
|
|
146
|
+
var Cps = require('./lib/cps.js')
|
|
147
|
+
try {
|
|
148
|
+
cpsSbox = Cps.loadKey(arg.k)
|
|
149
|
+
if(Cluster.isMaster) {
|
|
150
|
+
console.log("Loaded CPS decryption key from %s", arg.k)
|
|
151
|
+
}
|
|
152
|
+
} catch(err) {
|
|
153
|
+
console.error("Failed to load CPS key:", err.message)
|
|
154
|
+
return
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Default action, extract the entire grf
|
|
159
|
+
var extraction = grf.extract({
|
|
160
|
+
output: (typeof arg.o !== 'string' ? '' : arg.o),
|
|
161
|
+
concurrency: arg.c,
|
|
162
|
+
cpsSbox: cpsSbox,
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
extraction.on('start', function() {
|
|
166
|
+
console.log("Extraction has started on %d files.", grf.entries.length)
|
|
167
|
+
|
|
168
|
+
// Pretty progress bar extraction on process
|
|
169
|
+
var bar = new ProgressBar('Progress [:current/:total] [:bar] :percent :elapseds - :etas', {
|
|
170
|
+
width: 100,
|
|
171
|
+
total: grf.entries.length
|
|
172
|
+
})
|
|
173
|
+
var last = 0
|
|
174
|
+
var timer = setInterval(function () {
|
|
175
|
+
var progress = extraction.progress()
|
|
176
|
+
var rate = progress - last
|
|
177
|
+
|
|
178
|
+
last = progress
|
|
179
|
+
|
|
180
|
+
bar.tick(rate)
|
|
181
|
+
|
|
182
|
+
if (bar.complete) {
|
|
183
|
+
console.log("Finished extracting GRF.")
|
|
184
|
+
process.nextTick(process.exit)
|
|
185
|
+
}
|
|
186
|
+
}, 100)
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
extraction.on('error', function(err) {
|
|
190
|
+
console.error("Extraction error", err)
|
|
191
|
+
})
|
|
192
|
+
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loaders/GameFile.js
|
|
3
|
+
*
|
|
4
|
+
* Loaders for Gravity .grf file (Game RO File)
|
|
5
|
+
*
|
|
6
|
+
* This file is part of ROBrowser, Ragnarok Online in the Web Browser (http://www.robrowser.com/).
|
|
7
|
+
*
|
|
8
|
+
* @author Vincent Thibault
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
var GameFileDecrypt = require('./GameFileDecrypt');
|
|
14
|
+
var BinaryReader = require('../Utils/BinaryReader');
|
|
15
|
+
var Inflate = require('../Utils/Inflate');
|
|
16
|
+
var Struct = require('../Utils/Struct');
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* GRF Constructor
|
|
21
|
+
*
|
|
22
|
+
* @param {File} data
|
|
23
|
+
*/
|
|
24
|
+
function GRF( data )
|
|
25
|
+
{
|
|
26
|
+
if (data) {
|
|
27
|
+
this.load( data );
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @var {File System} Nodejs
|
|
34
|
+
*/
|
|
35
|
+
var fs = require('fs');
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* GRF Constants
|
|
40
|
+
*/
|
|
41
|
+
GRF.FILELIST_TYPE_FILE = 0x01; // entry is a file
|
|
42
|
+
GRF.FILELIST_TYPE_ENCRYPT_MIXED = 0x02; // encryption mode 0 (header DES + periodic DES/shuffle)
|
|
43
|
+
GRF.FILELIST_TYPE_ENCRYPT_HEADER = 0x04; // encryption mode 1 (header DES only)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* GRF Structures
|
|
48
|
+
*/
|
|
49
|
+
GRF.struct_header = new Struct(
|
|
50
|
+
'unsigned char signature[15]',
|
|
51
|
+
'unsigned char key[15]',
|
|
52
|
+
'unsigned long file_table_offset',
|
|
53
|
+
'unsigned long skip',
|
|
54
|
+
'unsigned long filecount',
|
|
55
|
+
'unsigned long version'
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
GRF.struct_table = new Struct(
|
|
59
|
+
'unsigned long pack_size',
|
|
60
|
+
'unsigned long real_size'
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
GRF.struct_entry = new Struct(
|
|
64
|
+
'unsigned long pack_size',
|
|
65
|
+
'unsigned long length_aligned',
|
|
66
|
+
'unsigned long real_size',
|
|
67
|
+
'unsigned char type',
|
|
68
|
+
'unsigned long offset'
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* GRF METHODs
|
|
74
|
+
*/
|
|
75
|
+
GRF.prototype.file = null;
|
|
76
|
+
GRF.prototype.reader = null;
|
|
77
|
+
GRF.prototype.header = null;
|
|
78
|
+
GRF.prototype.table = null;
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Loading GRF
|
|
83
|
+
*
|
|
84
|
+
* @param {File} file
|
|
85
|
+
*/
|
|
86
|
+
GRF.prototype.load = function Load( file )
|
|
87
|
+
{
|
|
88
|
+
// Global object
|
|
89
|
+
this.file = file;
|
|
90
|
+
|
|
91
|
+
// node.js
|
|
92
|
+
this.reader = (typeof FileReaderSync !== 'undefined' ? new FileReaderSync() : {});
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
// Local object
|
|
96
|
+
var buffer, fp;
|
|
97
|
+
var header, entries, table;
|
|
98
|
+
var reader = this.reader;
|
|
99
|
+
var data, out;
|
|
100
|
+
var i, count;
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
// Load grf
|
|
104
|
+
file.slice = file.slice || file.webkitSlice || file.mozSlice;
|
|
105
|
+
reader.load = function( start, len ) {
|
|
106
|
+
// node.js
|
|
107
|
+
if (fs && file.fd) {
|
|
108
|
+
var buffer = Buffer.alloc(len);
|
|
109
|
+
fs.readSync(file.fd, buffer, 0, len, start);
|
|
110
|
+
return buffer.buffer;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return reader.readAsArrayBuffer(
|
|
114
|
+
file.slice( start, start+len )
|
|
115
|
+
);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
// Check if file has enought content.
|
|
120
|
+
if (file.size < GRF.struct_header.size) {
|
|
121
|
+
throw new Error('GRF::load() - Not enough bytes to be a valid GRF');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Read the header
|
|
125
|
+
buffer = reader.load( 0, GRF.struct_header.size );
|
|
126
|
+
fp = new BinaryReader(buffer);
|
|
127
|
+
header = fp.readStruct( GRF.struct_header );
|
|
128
|
+
|
|
129
|
+
header.signature = String.fromCharCode.apply( null, header.signature);
|
|
130
|
+
header.filecount -= header.skip + 7;
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
// Check file header
|
|
134
|
+
if (header.signature !== 'Master of Magic') {
|
|
135
|
+
throw new Error('GRF::load() - Incorrect header "' + header.signature + '", must be "Master of Magic".');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (header.version !== 0x200) {
|
|
139
|
+
throw new Error('GRF::load() - Incorrect version "0x' + parseInt(header.version, 10).toString(16) + '", just support version "0x200"');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (header.file_table_offset + GRF.struct_header.size > file.size || header.file_table_offset < 0) {
|
|
143
|
+
throw new Error('GRF::load() - Can\'t jump to table list (' + header.file_table_offset + '), file length: ' + file.size);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Load Table Info
|
|
147
|
+
buffer = reader.load( header.file_table_offset + GRF.struct_header.size, GRF.struct_table.size );
|
|
148
|
+
fp = new BinaryReader( buffer );
|
|
149
|
+
table = fp.readStruct( GRF.struct_table );
|
|
150
|
+
|
|
151
|
+
// Load Table Data
|
|
152
|
+
buffer = reader.load( header.file_table_offset + GRF.struct_header.size + GRF.struct_table.size, table.pack_size );
|
|
153
|
+
data = new Uint8Array(buffer);
|
|
154
|
+
out = new Uint8Array(table.real_size);
|
|
155
|
+
|
|
156
|
+
// Uncompress data
|
|
157
|
+
(new Inflate(data)).getBytes(out);
|
|
158
|
+
|
|
159
|
+
// Load entries
|
|
160
|
+
entries = loadEntries(out, header.filecount);
|
|
161
|
+
|
|
162
|
+
// Store table data (used for regex search in tablelist)
|
|
163
|
+
// Set filename to lowercase (case insensitive in official client)
|
|
164
|
+
table.data = '';
|
|
165
|
+
for (i = 0, count = entries.length; i < count; ++i) {
|
|
166
|
+
table.data += entries[i].filename + '\0';
|
|
167
|
+
entries[i].filename = entries[i].filename.toLowerCase();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Sort entries (for binary search)
|
|
171
|
+
entries.sort(sortEntries);
|
|
172
|
+
|
|
173
|
+
this.header = header;
|
|
174
|
+
this.entries = entries;
|
|
175
|
+
this.table = table;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Load entries
|
|
181
|
+
* Note: this function is quiet intensive, BinaryReader is slowing down
|
|
182
|
+
* the process and generate too much memory to garbage (GC pause of 6sec).
|
|
183
|
+
*
|
|
184
|
+
* @param {Uint8Array} content table
|
|
185
|
+
* @param {number} file count
|
|
186
|
+
*/
|
|
187
|
+
function loadEntries( out, count )
|
|
188
|
+
{
|
|
189
|
+
// Read all entries
|
|
190
|
+
var i, pos, str;
|
|
191
|
+
var entries = new Array(count);
|
|
192
|
+
|
|
193
|
+
for (i = 0, pos = 0; i < count; ++i) {
|
|
194
|
+
str = '';
|
|
195
|
+
while (out[pos]) {
|
|
196
|
+
str += String.fromCharCode(out[pos++]);
|
|
197
|
+
}
|
|
198
|
+
pos++;
|
|
199
|
+
|
|
200
|
+
entries[i] = {
|
|
201
|
+
filename: str,
|
|
202
|
+
pack_size: (out[pos++] | out[pos++] << 8 | out[pos++] << 16 | out[pos++] << 24) >>> 0,
|
|
203
|
+
length_aligned: (out[pos++] | out[pos++] << 8 | out[pos++] << 16 | out[pos++] << 24) >>> 0,
|
|
204
|
+
real_size: (out[pos++] | out[pos++] << 8 | out[pos++] << 16 | out[pos++] << 24) >>> 0,
|
|
205
|
+
type: out[pos++],
|
|
206
|
+
offset: (out[pos++] | out[pos++] << 8 | out[pos++] << 16 | out[pos++] << 24) >>> 0
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return entries;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Sort entries (to find it faster)
|
|
217
|
+
*
|
|
218
|
+
* @param {object} entry 1
|
|
219
|
+
* @param {object} entry 2
|
|
220
|
+
*/
|
|
221
|
+
function sortEntries(a, b)
|
|
222
|
+
{
|
|
223
|
+
if (a.filename > b.filename) {
|
|
224
|
+
return 1;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (a.filename < b.filename) {
|
|
228
|
+
return -1;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return 0;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Decode entry to return its content
|
|
237
|
+
*
|
|
238
|
+
* @param {ArrayBuffer}
|
|
239
|
+
* @param {Entry}
|
|
240
|
+
* @param {function} callback
|
|
241
|
+
*/
|
|
242
|
+
GRF.prototype.decodeEntry = function DecodeEntry( buffer, entry, callback )
|
|
243
|
+
{
|
|
244
|
+
var out;
|
|
245
|
+
|
|
246
|
+
var data = new Uint8Array( buffer );
|
|
247
|
+
|
|
248
|
+
// Decode the file
|
|
249
|
+
if (entry.type & GRF.FILELIST_TYPE_ENCRYPT_MIXED) {
|
|
250
|
+
console.log(11111111)
|
|
251
|
+
GameFileDecrypt.decodeFull( data, entry.length_aligned, entry.pack_size);
|
|
252
|
+
}
|
|
253
|
+
else if (entry.type & GRF.FILELIST_TYPE_ENCRYPT_HEADER) {
|
|
254
|
+
console.log(22222222)
|
|
255
|
+
GameFileDecrypt.decodeHeader( data, entry.length_aligned );
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Uncompress
|
|
259
|
+
out = new Uint8Array(entry.real_size);
|
|
260
|
+
(new Inflate(data)).getBytes(out);
|
|
261
|
+
|
|
262
|
+
callback(out.buffer);
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Binary Search hack to find files in GRF list
|
|
268
|
+
*
|
|
269
|
+
* @param {string} filename
|
|
270
|
+
*/
|
|
271
|
+
GRF.prototype.search = function searchClosure()
|
|
272
|
+
{
|
|
273
|
+
var range = new Uint32Array(2);
|
|
274
|
+
|
|
275
|
+
return function search( filename )
|
|
276
|
+
{
|
|
277
|
+
var entries = this.entries;
|
|
278
|
+
var v = 0;
|
|
279
|
+
var middle = 0;
|
|
280
|
+
|
|
281
|
+
range[1] = 0;
|
|
282
|
+
range[0] = entries.length - 1;
|
|
283
|
+
|
|
284
|
+
while (range[1] < range[0]) {
|
|
285
|
+
middle = range[1] + ((range[0]-range[1]) >> 1);
|
|
286
|
+
v = (entries[middle].filename < filename) | 0;
|
|
287
|
+
range[v] = middle + v;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (range[1] < entries.length && entries[range[1]].filename === filename) {
|
|
291
|
+
return range[1];
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return -1;
|
|
295
|
+
};
|
|
296
|
+
}();
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Find a file in the GRF
|
|
302
|
+
*
|
|
303
|
+
* @param {string} filename
|
|
304
|
+
* @param {function} callback
|
|
305
|
+
*/
|
|
306
|
+
GRF.prototype.getFile = function getFile( filename, callback )
|
|
307
|
+
{
|
|
308
|
+
// Not case sensitive...
|
|
309
|
+
var path = filename.toLowerCase();
|
|
310
|
+
var entry, blob;
|
|
311
|
+
var reader;
|
|
312
|
+
|
|
313
|
+
var pos = this.search(path);
|
|
314
|
+
|
|
315
|
+
// If filename is find in GRF table list
|
|
316
|
+
if (pos !== -1) {
|
|
317
|
+
entry = this.entries[pos];
|
|
318
|
+
|
|
319
|
+
// Directory ?
|
|
320
|
+
if (!(entry.type & GRF.FILELIST_TYPE_FILE)) {
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// node.js
|
|
325
|
+
if (fs && this.file.fd) {
|
|
326
|
+
var buffer = new Buffer(entry.length_aligned);
|
|
327
|
+
var grf = this;
|
|
328
|
+
|
|
329
|
+
fs.read(this.file.fd, buffer, 0, entry.length_aligned, entry.offset + GRF.struct_header.size, function(err, bytesRead, buffer){
|
|
330
|
+
if(err) throw err;
|
|
331
|
+
grf.decodeEntry( (new Uint8Array(buffer)).buffer, entry, callback);
|
|
332
|
+
});
|
|
333
|
+
return true;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
blob = this.file.slice(
|
|
337
|
+
entry.offset + GRF.struct_header.size,
|
|
338
|
+
entry.offset + GRF.struct_header.size + entry.length_aligned
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
// Load into memory
|
|
342
|
+
if (self.FileReader) {
|
|
343
|
+
var grf = this;
|
|
344
|
+
|
|
345
|
+
reader = new FileReader();
|
|
346
|
+
reader.onload = function(){
|
|
347
|
+
grf.decodeEntry( reader.result, entry, callback);
|
|
348
|
+
};
|
|
349
|
+
reader.readAsArrayBuffer(blob);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Firefox doesn't seems to support FileReader in web worker
|
|
353
|
+
else {
|
|
354
|
+
reader = new FileReaderSync();
|
|
355
|
+
this.decodeEntry( reader.readAsArrayBuffer(blob), entry, callback );
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return true;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return false;
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Export
|
|
367
|
+
*/
|
|
368
|
+
module.exports = GRF;
|