-
Notifications
You must be signed in to change notification settings - Fork 4
/
xp3index.tjs
289 lines (243 loc) · 7.74 KB
/
xp3index.tjs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
/**
* XP3のファイル一覧を取得する
*
* @param name 対象のXP3ファイルパス
* @return ファイル名の配列(パス文字も含まれます)
*
* ※要 BinaryStream.dll, memfile.dll プラグイン
* コードの構成は XP3Archive.cpp に習って作ってあります(TJS翻訳のため一部異なる部分あり)
* ファイル名取得のみで,サイズやセグメント情報等は取得しません
* 厳密なチェックをしていないため壊れたXP3ファイルを読ませると例外が発生する場合があります
*/
Storages.getXP3ArchiveIndex = function (name)
{
////////////////////////////////////////////////////////////
// Plugins check
if (typeof global.BinaryStream != "Object") Plugins.link("BinaryStream.dll");
if (typeof Storages.deleteMemoryFile != "Object") Plugins.link("memfile.dll");
////////////////////////////////////////////////////////////
// utilities
var TVPReadError = @"GetXP3ArchiveIndex: ReadError: ${name}";
var TVP_XP3_INDEX_ENCODE_METHOD_MASK = 0x07;
var TVP_XP3_INDEX_ENCODE_RAW = 0;
var TVP_XP3_INDEX_ENCODE_ZLIB = 1;
var TVP_XP3_INDEX_CONTINUE = 0x80;
var ReadI16FromOctet = function (data, ofs) {
return data[ofs+0] | (data[ofs+1]<<8);
};
var ReadStringFromOctet = function (data, ofs, len) {
var r;
for (var i=0, pos=0; i < len; i++,pos+=2)
r += $(data[ofs+pos] | (data[ofs+pos+1]<<8));
return r;
};
var FindXP3ArchiveChunk = function(data, name, elm, error) {
if (typeof name == "String") name = [ #name[0], #name[1], #name[2], #name[3] ];
var start = elm.start;
var size = elm.size;
var pos = 0;
while(pos < size)
{
var found = (data[start+0] == name[0] &&
data[start+1] == name[1] &&
data[start+2] == name[2] &&
data[start+3] == name[3]);
start += 4;
var size_chunk = ((data[start+0] ) |
(data[start+1]<<8 ) |
(data[start+2]<<16) |
(data[start+3]<<24));
if(data[start+4] | data[start+5] | data[start+6] | data[start+7])
throw new Exception(error);
start += 8;
if(found)
{
// found
elm.start = start;
elm.size = size_chunk;
return true;
}
start += size_chunk;
pos += size_chunk + 4 + 8;
}
return false;
} incontextof global;
var GetXP3ArchiveOffset = function (st, name, raise) {
var offset = -1;
st.seek(0, BinaryStream.bsSeekSet);
var XP3Mark = [
0x58/*'X'*/, 0x50/*'P'*/, 0x33/*'3'*/, 0x0d/*'\r'*/,
0x0a/*'\n'*/, 0x20/*' '*/, 0x0a/*'\n'*/, 0x1a/*EOF*/,
0x8b, 0x67, 0x01 ];
var memcmp = function(a, b, ofs, len) {
for (var i = 0; i < len; i++) if (a[i] != b[i+ofs]) return a[i] - b[i+ofs];
return 0;
};
var mark = st.read(11);
if (mark === void || mark.length < 11) mark = <% 00 00 00 00 00 00 00 00 00 00 00 %>; // sentinel
if (mark[0] == 0x4d/*'M'*/ && mark[1] == 0x5a/*'Z'*/)
{
// "MZ" is a mark of Win32/DOS executables,
// TVP searches the first mark of XP3 archive
// in the executeble file.
var found = false;
offset = 16;
st.seek(16, BinaryStream.bsSeekSet);
// XP3 mark must be aligned by a paragraph ( 16 bytes )
var one_read_size = 256*1024;
var buffer; // read 256kbytes at once
while ((buffer = st.read(one_read_size)) !== void)
{
var p = 0, read = buffer.length;
while (p<read)
{
if(!memcmp(XP3Mark, buffer, p, 11))
{
// found the mark
offset += p;
found = true;
break;
}
p+=16;
}
if(found) break;
offset += one_read_size;
}
if(!found)
{
if(raise)
throw new Exception(@"GetXP3ArchiveOffset: CannotUnbindXP3EXE: ${name}");
else
return -1;
}
}
else if(!memcmp(XP3Mark, mark, 0, 11))
{
// XP3 mark found
offset = 0;
}
else
{
if(raise)
throw new Exception(@"GetXP3ArchiveOffset: CannotFindXP3Mark: ${name}");
return -1;
}
return offset;
} incontextof global;
////////////////////////////////////////////////////////////
// main code
var int32mask = 0xFFFFFFFF;
var ItemVector = [];
//var Name = name;
//var Count = 0;
var offset;
var st = new BinaryStream(name, BinaryStream.bsRead);
var indexdata = null;
var cn_File = [ 0x46/*'F'*/, 0x69/*'i'*/, 0x6c/*'l'*/, 0x65/*'e'*/ ];
var cn_info = [ 0x69/*'i'*/, 0x6e/*'n'*/, 0x66/*'f'*/, 0x6f/*'o'*/ ];
// cn_segm, cn_adlr
//Debug.message("(info) Trying to read XP3 virtual file system information from : "+name);
//var segmentcount = 0;
try
{
// retrieve archive offset
offset = GetXP3ArchiveOffset(st, name, true);
// read index position and seek
st.seek(11 + offset, BinaryStream.bsSeekSet);
// read all XP3 indices
while (true)
{
var index_ofs = st.readI64LE();
st.seek(index_ofs + offset, BinaryStream.bsSeekSet);
// read index to memory
var index_flag = st.readI8();
var index_size;
if ((index_flag & TVP_XP3_INDEX_ENCODE_METHOD_MASK) ==
TVP_XP3_INDEX_ENCODE_ZLIB)
{
// compressed index
var compressed_size = st.readI64LE();
index_size = st.readI64LE();
if((compressed_size & int32mask) != compressed_size ||
(index_size & int32mask) != index_size)
throw new Exception(TVPReadError);
// too large to handle, or corrupted
index_size &= int32mask;
var temp = "mem://./"+System.createUUID();
var tempst = new BinaryStream(temp, BinaryStream.bsWrite);
try
{
tempst.decompress(name, %[ offset:st.tell(), length:compressed_size ]);
tempst.close();
tempst.open(temp, BinaryStream.bsRead);
indexdata = tempst.read(index_size);
}
catch (e)
{
if (tempst) invalidate tempst;
Storages.deleteMemoryFile(temp);
throw e;
}
if (tempst) invalidate tempst;
Storages.deleteMemoryFile(temp);
}
else if ((index_flag & TVP_XP3_INDEX_ENCODE_METHOD_MASK) ==
TVP_XP3_INDEX_ENCODE_RAW)
{
// uncompressed index
index_size = st.readI64LE();
if((index_size & int32mask) != index_size)
throw new Exception(TVPReadError);
// too large to handle or corrupted
index_size &= int32mask;
indexdata = st.read(index_size);
}
else
{
throw new Exception(TVPReadError);
}
// check indexdata validation
if (indexdata && indexdata.length != index_size)
{
throw new Exception(TVPReadError);
}
// read index information from memory
var ch_file = %[ start: 0, size: index_size ];
if (indexdata) for(;;)
{
// find 'File' chunk
if(!FindXP3ArchiveChunk(indexdata, cn_File, ch_file, TVPReadError))
break; // not found
// find 'info' sub-chunk
var ch_info = %[ start: ch_file.start, size: ch_file.size ];
if(!FindXP3ArchiveChunk(indexdata, cn_info, ch_info, TVPReadError))
throw new Exception(TVPReadError);
// read info sub-chunk
var len = ReadI16FromOctet(indexdata, ch_info.start + 20);
var name = ReadStringFromOctet(indexdata, ch_info.start + 22, len);
// push information
ItemVector.add(name);
// to next file
ch_file.start += ch_file.size;
ch_file.size = index_size - ch_file.start;
//Count++;
}
if (indexdata) invalidate indexdata;
indexdata = null;
if(!(index_flag & TVP_XP3_INDEX_CONTINUE))
break; // continue reading index when the bit sets
}
// sort item vector by its name
//ItemVector.sort();
}
catch (e)
{
if (indexdata) invalidate indexdata;
invalidate st;
throw e;
}
// if (indexdata) invalidate indexdata;
invalidate st;
//Debug.message(@"(info) Done. (contains ${ItemVector.count} file(s))");
return ItemVector;
} incontextof global;