1 module wadarc
is aliced
;
4 static import core
.sync
.mutex
;
5 import std
.stdio
: File
;
12 // ////////////////////////////////////////////////////////////////////////// //
13 public string
koi8lotranslit (string s
) {
15 while (pos
< s
.length
&& s
.ptr
[pos
] < 0x80) ++pos
;
16 if (pos
>= s
.length
) return s
; // nothing to do
17 string res
= s
[0..pos
];
18 while (pos
< s
.length
) {
19 char ch
= s
.ptr
[pos
++];
20 if (ch
== '\xe1' || ch
== '\xc1') res
~= "a";
21 else if (ch
== '\xe2' || ch
== '\xc2') res
~= "b";
22 else if (ch
== '\xf7' || ch
== '\xd7') res
~= "v";
23 else if (ch
== '\xe7' || ch
== '\xc7') res
~= "g";
24 else if (ch
== '\xe4' || ch
== '\xc4') res
~= "d";
25 else if (ch
== '\xe5' || ch
== '\xc5') res
~= "e";
26 else if (ch
== '\xb3' || ch
== '\xa3') res
~= "yo";
27 else if (ch
== '\xf6' || ch
== '\xd6') res
~= "zh";
28 else if (ch
== '\xfa' || ch
== '\xda') res
~= "z";
29 else if (ch
== '\xe9' || ch
== '\xc9') res
~= "i";
30 else if (ch
== '\xea' || ch
== '\xca') res
~= "j";
31 else if (ch
== '\xeb' || ch
== '\xcb') res
~= "k";
32 else if (ch
== '\xec' || ch
== '\xcc') res
~= "l";
33 else if (ch
== '\xed' || ch
== '\xcd') res
~= "m";
34 else if (ch
== '\xee' || ch
== '\xce') res
~= "n";
35 else if (ch
== '\xef' || ch
== '\xcf') res
~= "o";
36 else if (ch
== '\xf0' || ch
== '\xd0') res
~= "p";
37 else if (ch
== '\xf2' || ch
== '\xd2') res
~= "r";
38 else if (ch
== '\xf3' || ch
== '\xd3') res
~= "s";
39 else if (ch
== '\xf4' || ch
== '\xd4') res
~= "t";
40 else if (ch
== '\xf5' || ch
== '\xd5') res
~= "u";
41 else if (ch
== '\xe6' || ch
== '\xc6') res
~= "f";
42 else if (ch
== '\xe8' || ch
== '\xc8') res
~= "h";
43 else if (ch
== '\xe3' || ch
== '\xc3') res
~= "c";
44 else if (ch
== '\xfe' || ch
== '\xde') res
~= "ch";
45 else if (ch
== '\xfb' || ch
== '\xdb') res
~= "sh";
46 else if (ch
== '\xfd' || ch
== '\xdd') res
~= "sch";
47 else if (ch
== '\xff' || ch
== '\xdf') res
~= "x";
48 else if (ch
== '\xf9' || ch
== '\xd9') res
~= "y";
49 else if (ch
== '\xf8' || ch
== '\xd8') res
~= "w";
50 else if (ch
== '\xfc' || ch
== '\xdc') res
~= "e";
51 else if (ch
== '\xe0' || ch
== '\xc0') res
~= "hu";
52 else if (ch
== '\xf1' || ch
== '\xd1') res
~= "ja";
59 // ////////////////////////////////////////////////////////////////////////// //
60 public immutable char[256] koi8from866Table
= [
61 '\x00','\x01','\x02','\x03','\x04','\x05','\x06','\x07','\x08','\x09','\x0a','\x0b','\x0c','\x0d','\x0e','\x0f',
62 '\x10','\x11','\x12','\x13','\x14','\x15','\x16','\x17','\x18','\x19','\x1a','\x1b','\x1c','\x1d','\x1e','\x1f',
63 '\x20','\x21','\x22','\x23','\x24','\x25','\x26','\x27','\x28','\x29','\x2a','\x2b','\x2c','\x2d','\x2e','\x2f',
64 '\x30','\x31','\x32','\x33','\x34','\x35','\x36','\x37','\x38','\x39','\x3a','\x3b','\x3c','\x3d','\x3e','\x3f',
65 '\x40','\x41','\x42','\x43','\x44','\x45','\x46','\x47','\x48','\x49','\x4a','\x4b','\x4c','\x4d','\x4e','\x4f',
66 '\x50','\x51','\x52','\x53','\x54','\x55','\x56','\x57','\x58','\x59','\x5a','\x5b','\x5c','\x5d','\x5e','\x5f',
67 '\x60','\x61','\x62','\x63','\x64','\x65','\x66','\x67','\x68','\x69','\x6a','\x6b','\x6c','\x6d','\x6e','\x6f',
68 '\x70','\x71','\x72','\x73','\x74','\x75','\x76','\x77','\x78','\x79','\x7a','\x7b','\x7c','\x7d','\x7e','\x7f',
69 '\xe1','\xe2','\xf7','\xe7','\xe4','\xe5','\xf6','\xfa','\xe9','\xea','\xeb','\xec','\xed','\xee','\xef','\xf0',
70 '\xf2','\xf3','\xf4','\xf5','\xe6','\xe8','\xe3','\xfe','\xfb','\xfd','\xff','\xf9','\xf8','\xfc','\xe0','\xf1',
71 '\xc1','\xc2','\xd7','\xc7','\xc4','\xc5','\xd6','\xda','\xc9','\xca','\xcb','\xcc','\xcd','\xce','\xcf','\xd0',
72 '\x90','\x91','\x92','\x81','\x87','\xb2','\x3f','\x3f','\x3f','\xb5','\xa1','\xa8','\xae','\x3f','\xac','\x83',
73 '\x84','\x89','\x88','\x86','\x80','\x8a','\xaf','\xb0','\xab','\xa5','\xbb','\xb8','\xb1','\xa0','\xbe','\xb9',
74 '\xba','\x3f','\x3f','\xaa','\xa9','\xa2','\x3f','\x3f','\xbc','\x85','\x82','\x8d','\x8c','\x8e','\x8f','\x8b',
75 '\xd2','\xd3','\xd4','\xd5','\xc6','\xc8','\xc3','\xde','\xdb','\xdd','\xdf','\xd9','\xd8','\xdc','\xc0','\xd1',
76 '\xb3','\xa3','\xb4','\xa4','\xb7','\xa7','\x3f','\x3f','\x9c','\x95','\x9e','\x96','\x3f','\x3f','\x94','\x9a',
79 public immutable char[256] koi8from1251Table
= [
80 '\x00','\x01','\x02','\x03','\x04','\x05','\x06','\x07','\x08','\x09','\x0a','\x0b','\x0c','\x0d','\x0e','\x0f',
81 '\x10','\x11','\x12','\x13','\x14','\x15','\x16','\x17','\x18','\x19','\x1a','\x1b','\x1c','\x1d','\x1e','\x1f',
82 '\x20','\x21','\x22','\x23','\x24','\x25','\x26','\x27','\x28','\x29','\x2a','\x2b','\x2c','\x2d','\x2e','\x2f',
83 '\x30','\x31','\x32','\x33','\x34','\x35','\x36','\x37','\x38','\x39','\x3a','\x3b','\x3c','\x3d','\x3e','\x3f',
84 '\x40','\x41','\x42','\x43','\x44','\x45','\x46','\x47','\x48','\x49','\x4a','\x4b','\x4c','\x4d','\x4e','\x4f',
85 '\x50','\x51','\x52','\x53','\x54','\x55','\x56','\x57','\x58','\x59','\x5a','\x5b','\x5c','\x5d','\x5e','\x5f',
86 '\x60','\x61','\x62','\x63','\x64','\x65','\x66','\x67','\x68','\x69','\x6a','\x6b','\x6c','\x6d','\x6e','\x6f',
87 '\x70','\x71','\x72','\x73','\x74','\x75','\x76','\x77','\x78','\x79','\x7a','\x7b','\x7c','\x7d','\x7e','\x7f',
88 '\x3f','\x3f','\x3f','\x3f','\x3f','\x3f','\x3f','\x3f','\x3f','\x3f','\x3f','\x3f','\x3f','\x3f','\x3f','\x3f',
89 '\x3f','\x3f','\x3f','\x3f','\x3f','\x3f','\x3f','\x3f','\x3f','\x3f','\x3f','\x3f','\x3f','\x3f','\x3f','\x3f',
90 '\x9a','\x3f','\x3f','\x3f','\x3f','\xbd','\x3f','\x3f','\xb3','\xbf','\xb4','\x3f','\x3f','\x3f','\x3f','\xb7',
91 '\x9c','\x3f','\xb6','\xa6','\xad','\x3f','\x3f','\x9e','\xa3','\x3f','\xa4','\x3f','\x3f','\x3f','\x3f','\xa7',
92 '\xe1','\xe2','\xf7','\xe7','\xe4','\xe5','\xf6','\xfa','\xe9','\xea','\xeb','\xec','\xed','\xee','\xef','\xf0',
93 '\xf2','\xf3','\xf4','\xf5','\xe6','\xe8','\xe3','\xfe','\xfb','\xfd','\xff','\xf9','\xf8','\xfc','\xe0','\xf1',
94 '\xc1','\xc2','\xd7','\xc7','\xc4','\xc5','\xd6','\xda','\xc9','\xca','\xcb','\xcc','\xcd','\xce','\xcf','\xd0',
95 '\xd2','\xd3','\xd4','\xd5','\xc6','\xc8','\xc3','\xde','\xdb','\xdd','\xdf','\xd9','\xd8','\xdc','\xc0','\xd1',
98 // char toupper/tolower, koi8
99 public immutable char[256] koi8tolowerTable
= [
100 '\x00','\x01','\x02','\x03','\x04','\x05','\x06','\x07','\x08','\x09','\x0a','\x0b','\x0c','\x0d','\x0e','\x0f',
101 '\x10','\x11','\x12','\x13','\x14','\x15','\x16','\x17','\x18','\x19','\x1a','\x1b','\x1c','\x1d','\x1e','\x1f',
102 '\x20','\x21','\x22','\x23','\x24','\x25','\x26','\x27','\x28','\x29','\x2a','\x2b','\x2c','\x2d','\x2e','\x2f',
103 '\x30','\x31','\x32','\x33','\x34','\x35','\x36','\x37','\x38','\x39','\x3a','\x3b','\x3c','\x3d','\x3e','\x3f',
104 '\x40','\x61','\x62','\x63','\x64','\x65','\x66','\x67','\x68','\x69','\x6a','\x6b','\x6c','\x6d','\x6e','\x6f',
105 '\x70','\x71','\x72','\x73','\x74','\x75','\x76','\x77','\x78','\x79','\x7a','\x5b','\x5c','\x5d','\x5e','\x5f',
106 '\x60','\x61','\x62','\x63','\x64','\x65','\x66','\x67','\x68','\x69','\x6a','\x6b','\x6c','\x6d','\x6e','\x6f',
107 '\x70','\x71','\x72','\x73','\x74','\x75','\x76','\x77','\x78','\x79','\x7a','\x7b','\x7c','\x7d','\x7e','\x7f',
108 '\x80','\x81','\x82','\x83','\x84','\x85','\x86','\x87','\x88','\x89','\x8a','\x8b','\x8c','\x8d','\x8e','\x8f',
109 '\x90','\x91','\x92','\x93','\x94','\x95','\x96','\x97','\x98','\x99','\x9a','\x9b','\x9c','\x9d','\x9e','\x9f',
110 '\xa0','\xa1','\xa2','\xa3','\xa4','\xa5','\xa6','\xa7','\xa8','\xa9','\xaa','\xab','\xac','\xad','\xae','\xaf',
111 '\xb0','\xb1','\xb2','\xa3','\xa4','\xb5','\xa6','\xa7','\xb8','\xb9','\xba','\xbb','\xbc','\xad','\xbe','\xbf',
112 '\xc0','\xc1','\xc2','\xc3','\xc4','\xc5','\xc6','\xc7','\xc8','\xc9','\xca','\xcb','\xcc','\xcd','\xce','\xcf',
113 '\xd0','\xd1','\xd2','\xd3','\xd4','\xd5','\xd6','\xd7','\xd8','\xd9','\xda','\xdb','\xdc','\xdd','\xde','\xdf',
114 '\xc0','\xc1','\xc2','\xc3','\xc4','\xc5','\xc6','\xc7','\xc8','\xc9','\xca','\xcb','\xcc','\xcd','\xce','\xcf',
115 '\xd0','\xd1','\xd2','\xd3','\xd4','\xd5','\xd6','\xd7','\xd8','\xd9','\xda','\xdb','\xdc','\xdd','\xde','\xdf',
118 public immutable char[256] koi8toupperTable
= [
119 '\x00','\x01','\x02','\x03','\x04','\x05','\x06','\x07','\x08','\x09','\x0a','\x0b','\x0c','\x0d','\x0e','\x0f',
120 '\x10','\x11','\x12','\x13','\x14','\x15','\x16','\x17','\x18','\x19','\x1a','\x1b','\x1c','\x1d','\x1e','\x1f',
121 '\x20','\x21','\x22','\x23','\x24','\x25','\x26','\x27','\x28','\x29','\x2a','\x2b','\x2c','\x2d','\x2e','\x2f',
122 '\x30','\x31','\x32','\x33','\x34','\x35','\x36','\x37','\x38','\x39','\x3a','\x3b','\x3c','\x3d','\x3e','\x3f',
123 '\x40','\x41','\x42','\x43','\x44','\x45','\x46','\x47','\x48','\x49','\x4a','\x4b','\x4c','\x4d','\x4e','\x4f',
124 '\x50','\x51','\x52','\x53','\x54','\x55','\x56','\x57','\x58','\x59','\x5a','\x5b','\x5c','\x5d','\x5e','\x5f',
125 '\x60','\x41','\x42','\x43','\x44','\x45','\x46','\x47','\x48','\x49','\x4a','\x4b','\x4c','\x4d','\x4e','\x4f',
126 '\x50','\x51','\x52','\x53','\x54','\x55','\x56','\x57','\x58','\x59','\x5a','\x7b','\x7c','\x7d','\x7e','\x7f',
127 '\x80','\x81','\x82','\x83','\x84','\x85','\x86','\x87','\x88','\x89','\x8a','\x8b','\x8c','\x8d','\x8e','\x8f',
128 '\x90','\x91','\x92','\x93','\x94','\x95','\x96','\x97','\x98','\x99','\x9a','\x9b','\x9c','\x9d','\x9e','\x9f',
129 '\xa0','\xa1','\xa2','\xb3','\xb4','\xa5','\xb6','\xb7','\xa8','\xa9','\xaa','\xab','\xac','\xbd','\xae','\xaf',
130 '\xb0','\xb1','\xb2','\xb3','\xb4','\xb5','\xb6','\xb7','\xb8','\xb9','\xba','\xbb','\xbc','\xbd','\xbe','\xbf',
131 '\xe0','\xe1','\xe2','\xe3','\xe4','\xe5','\xe6','\xe7','\xe8','\xe9','\xea','\xeb','\xec','\xed','\xee','\xef',
132 '\xf0','\xf1','\xf2','\xf3','\xf4','\xf5','\xf6','\xf7','\xf8','\xf9','\xfa','\xfb','\xfc','\xfd','\xfe','\xff',
133 '\xe0','\xe1','\xe2','\xe3','\xe4','\xe5','\xe6','\xe7','\xe8','\xe9','\xea','\xeb','\xec','\xed','\xee','\xef',
134 '\xf0','\xf1','\xf2','\xf3','\xf4','\xf5','\xf6','\xf7','\xf8','\xf9','\xfa','\xfb','\xfc','\xfd','\xfe','\xff',
137 public immutable ubyte[32] koi8alphaTable
= [
138 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0x07,0xfe,0xff,0xff,0x07,
139 0x00,0x00,0x00,0x00,0xd8,0x20,0xd8,0x20,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
142 public char dos2koi8 (char ch
) pure nothrow @trusted @nogc {
143 pragma(inline
, true);
144 return koi8from866Table
.ptr
[cast(int)ch
];
147 public char koi8lower (char ch
) pure nothrow @trusted @nogc {
148 pragma(inline
, true);
149 return koi8tolowerTable
.ptr
[cast(int)ch
];
152 public char koi8upper (char ch
) pure nothrow @trusted @nogc {
153 pragma(inline
, true);
154 return koi8toupperTable
.ptr
[cast(int)ch
];
157 public bool koi8isAlpha (char ch
) pure nothrow @trusted @nogc {
158 pragma(inline
, true);
159 return ((koi8alphaTable
.ptr
[ch
/8]&(1<<(ch
%8))) != 0);
163 // ////////////////////////////////////////////////////////////////////////// //
164 //public __gshared WadArchive[] wadList;
165 __gshared core
.sync
.mutex
.Mutex glock
;
166 __gshared string dataPath
;
169 shared static this () {
170 glock
= new core
.sync
.mutex
.Mutex
;
174 public void setDataPath (const(char)[] path
) {
175 while (path
.length
> 1 && path
[0] == '/' && path
[1] == '/') path
= path
[1..$];
176 if (path
!= "/") while (path
.length
&& path
[$-1] == '/') path
= path
[0..$-1];
177 dataPath
= path
.idup
~"/";
182 public void addWad (string fname) {
183 if (glock !is null) glock.lock();
184 scope(exit) if (glock !is null) glock.unlock();
185 conwriteln("adding '", fname, "'...");
186 wadList ~= new WadArchive(fname);
191 public File
openFile (string fname
) {
192 if (glock
!is null) glock
.lock();
193 scope(exit
) if (glock
!is null) glock
.unlock();
195 return File(dataPath
~fname
);
196 } catch (Exception
) {}
199 foreach_reverse (WadArchive wad; wadList) {
201 return wad.fopen(fname);
202 } catch (Exception) {}
205 return File(fname
); // to throw a correct exception, lol
207 string ofname = fname;
209 fname = fname.baseName.setExtension("");
210 foreach_reverse (WadArchive wad; wadList) {
212 return wad.fopen(fname);
213 } catch (Exception) {}
215 return File(ofname); // to throw a correct exception, lol
220 public string
loadTextFile (string fname
) {
221 auto fl
= openFile(fname
);
223 if (sz
< 0 || sz
> 1024*1024) throw new Exception("invalid text file size: '"~fname
~"'");
224 if (sz
== 0) return "";
225 auto res
= new char[](cast(uint)sz
);
226 if (fl
.rawRead(res
[]).length
!= res
.length
) throw new Exception("error reading text file '"~fname
~"'");
227 import std
.exception
: assumeUnique
;
228 return res
.assumeUnique
;
232 // ////////////////////////////////////////////////////////////////////////// //
234 final class WadArchive {
236 import std.stdio : File;
239 static struct FileInfo {
247 public static struct DirEntry {
259 this (string fname) {
260 import std.stdio : File;
264 scope(failure) { zfl.close; zfl = zfl.init; }
267 // it now owns the file (if no exception was thrown)
271 scope(success) zfl = fl;
274 @property auto files () {
275 static struct Range {
281 this (WadArchive ame, uint aidx=0) { me = ame; curindex = aidx; }
284 @property bool empty () const { return (curindex >= me.dir.length); }
285 @property DirEntry front () const {
287 (curindex < me.dir.length ? me.dir[cast(usize)curindex].path : null),
288 (curindex < me.dir.length ? me.dir[cast(usize)curindex].name : null),
289 (curindex < me.dir.length ? me.dir[cast(usize)curindex].size : 0));
291 @property Range save () { return Range(me, curindex); }
292 void popFront () { if (curindex < me.dir.length) ++curindex; }
293 @property uint length () const { return me.dir.length; }
294 @property uint position () const { return curindex; } // current position
295 @property void position (uint np) { curindex = np; }
296 void rewind () { curindex = 0; }
301 File fopen (ref in DirEntry de) {
302 string dname = de.name;
303 usize pos = dname.length;
304 while (pos > 0 && dname.ptr[pos-1] != '.') --pos;
305 if (pos > 1) dname = dname[0..pos-1];
306 foreach (immutable idx, ref fi; dir) {
307 if (/*fi.path == de.path &&*/ fi.name == dname) return openDirEntry(idx, fi.name);
309 throw new NamedException!"WadArchive"("file not found");
312 File fopen (const(char)[] fname) {
314 auto pos = fname.length;
315 while (pos > 0 && fname[pos-1] != '/') --pos;
317 de.path = cast(string)fname[0..pos]; // it's safe here
318 de.name = cast(string)fname[pos..$]; // it's safe here
319 //conwriteln("[", de.path, "] [", de.name, "]");
321 de.name = cast(string)fname; // it's safe here
329 if (zfl.isOpen) zfl.close;
333 void open (File fl) {
334 import std.uni : icmp;
336 immutable string[$] msn = [
337 "SARG", "TROO", "POSS", "SPOS", "CYBR", "CPOS", "BOSS", "BOS2", "HEAD", "SKUL",
338 "PAIN", "SPID", "BSPI", "FATT", "SKEL", "VILE", "FISH", "BAR1", "ROBO", "PLAY"
343 string fixName (const(char)[] name) {
345 if (name.length >= 4 && name[0..4] == "stcf") return name.idup~".vga";
346 if (name.length >= 4 && name[0..4] == "stbf") return name.idup~".vga";
347 if (name.length >= 5 && name[0..5] == "winum") return name.idup~".vga";
348 if (name == "wicolon" || name == "wiminus" || name == "wipcnt") return name.idup~".vga";
349 if (name.length > 3 && name[0..3] == "map") return name.idup~".d2m";
350 if (name == "playpal") return "playpal.pal";
352 case "endoom": return "endoom.b80";
353 case "endanim": return "endanim.a8";
354 case "end2anim": return "end2anim.a8";
355 case "darts": return "darts.a8";
356 case "colormap": return "colormap.tbl";
357 case "mixmap": return "mixmap.tbl";
358 case "titlepic": return "titlepic.vga";
359 case "interpic": return "interpic.vga";
360 case "cd1pic": return "cd1pic.vga";
361 case "endpic": return "endpic.vraw";
362 case "m_therml": return "m_therml.vga";
363 case "m_thermm": return "m_thermm.vga";
364 case "m_thermo": return "m_thermo.vga";
365 case "m_thermr": return "m_thermr.vga";
366 case "m_lscntr": return "m_lscntr.vga";
367 case "m_lsleft": return "m_lsleft.vga";
368 case "m_lsrght": return "m_lsrght.vga";
372 import std.algorithm;
373 if (curpath == "sounds/") return name.idup~".snd";
374 if (curpath == "music/") return (name.length > 3 && name[0..3] == "dmi" ? name.idup~".dmi" : name.idup~".dmm");
375 if (curpath == "tilegfx/") return name.idup~".vga";
376 if (curpath.startsWith("sprites/")) return name.idup~".vga";
380 string fixPath (const(char)[] name) {
393 return "sprites/sky/";
396 if (name.length >= 4 && name[0..4] == "stcf") return "fonts/stcf/";
397 if (name.length >= 4 && name[0..4] == "stbf") return "fonts/stbf/";
398 if (name.length >= 5 && name[0..5] == "winum") return "fonts/winum/";
399 if (name == "wicolon" || name == "wiminus" || name == "wipcnt") return "fonts/winum/";
400 if (name.length > 3 && name[0..3] == "map") return "maps/";
401 if (name == "d_start") curpath = "sounds/";
402 if (name == "m_start") curpath = "music/";
403 if (name == "w_start") curpath = "tilegfx/";
404 if (name == "s_start") curpath = "sprites/";
405 if (curpath == "sprites/" && name.length > 4) {
406 switch (name[0..4]) {
407 case "sarg": return "sprites/monsters/demon/";
408 case "troo": return "sprites/monsters/imp/";
409 case "poss": return "sprites/monsters/zombie/";
410 case "spos": return "sprites/monsters/sergeant/";
411 case "cybr": return "sprites/monsters/cyberdemon/";
412 case "cpos": return "sprites/monsters/chaingunner/";
413 case "boss": return "sprites/monsters/baron/";
414 case "bos2": return "sprites/monsters/knight/";
415 case "head": return "sprites/monsters/cacodemon/";
416 case "skul": return "sprites/monsters/soul/";
417 case "pain": return "sprites/monsters/painel/";
418 case "spid": return "sprites/monsters/mastermind/";
419 case "bspi": return "sprites/monsters/arachnotron/";
420 case "fatt": return "sprites/monsters/mancubus/";
421 case "skel": return "sprites/monsters/revenant/";
422 case "vile": return "sprites/monsters/archvile/";
423 case "fish": return "sprites/monsters/fish/";
424 case "bar1": return "sprites/monsters/barrel/";
425 case "robo": return "sprites/monsters/robot/";
426 case "play": return "sprites/monsters/player/";
433 import core.stdc.stdio : SEEK_CUR, SEEK_END;
434 scope(failure) cleanup();
438 if (fl.rawRead(data[]).length != data.length) throw new NamedException!"WadArchive"("reading error");
439 return cast(uint)(data[0]+0x100*data[1]+0x10000*data[2]+0x1000000*data[3]);
448 if (fl.rawRead(lmpname[0..4]).length != 4) throw new NamedException!"WadArchive"("reading error");
449 if (lmpname[0..4] != "PWAD" && lmpname[0..4] != "IWAD") throw new NamedException!"WadArchive"("not a WAD file");
450 auto count = readU32();
451 auto dofs = readU32();
452 if (count == 0) return;
453 if (dofs < 3*4 || count == uint.max) throw new NamedException!"WadArchive"("invalid WAD file");
454 fl.seek(dofs-3*4, SEEK_CUR);
455 while (count-- > 0) {
458 if (fl.rawRead(lmpname[]).length != 8) throw new NamedException!"WAD"("reading error");
460 while (pos < 8 && lmpname[pos] != 0) {
461 if (lmpname[pos] >= 'A' && lmpname[pos] <= 'Z') lmpname[pos] += 32;
464 if (pos == 0) continue;
465 auto nm = lmpname[0..pos];
466 if (nm.length > 6 && nm[$-6..$] == "_start") {
470 if (nm.length > 4 && nm[$-4..$] == "_end") {
474 uint fidx = uint.max;
475 foreach (immutable idx, ref de; dir) if (de.name == lmpname[0..pos]) { fidx = cast(uint)idx; break; }
476 if (fidx >= dir.length) {
477 fidx = cast(uint)dir.length;
480 dir[fidx].ofs = lmpofs;
481 dir[fidx].size = lmpsize;
482 //dir[fidx].name = fixName(nm);
483 //dir[fidx].path = fixPath(nm);
485 import std.uni : toLower;
486 dir[fidx].name = koi8lotranslit(recodeToKOI8(recode(fixName(nm), "utf-8", "cp866").toLower, "utf-8"));
487 dir[fidx].path = koi8lotranslit(recodeToKOI8(recode(fixPath(nm), "utf-8", "cp866").toLower, "utf-8"));
489 //debug conwriteln(dir[fidx].path, " : ", dir[fidx].name);
491 debug conwriteln(dir.length, " files found");
495 // ////////////////////////////////////////////////////////////////////// //
496 static import core.sync.mutex;
498 core.sync.mutex.Mutex lock;
501 lock = new core.sync.mutex.Mutex;
504 auto openDirEntry (uint idx, string filename) {
505 import core.sys.linux.stdio : fopencookie;
506 import core.stdc.stdio : FILE;
507 import core.stdc.stdio : fopen, fclose;
508 import core.stdc.stdlib : calloc, free;
510 import std.internal.cstring : tempCString;
511 import core.memory : GC;
513 if (!zfl.isOpen) throw new NamedException!"WadArchive"("archive wasn't opened");
514 if (idx >= dir.length) throw new NamedException!"WadArchive"("invalid dir index");
516 // create cookied `FILE*`
517 auto fc = cast(InnerFileCookied*)calloc(1, InnerFileCookied.sizeof);
518 scope(exit) if (fc !is null) free(fc);
520 import core.exception : onOutOfMemoryErrorNoGC;
521 onOutOfMemoryErrorNoGC();
523 (*fc) = InnerFileCookied.init;
524 (*fc).stpos = flstpos+dir[idx].ofs;
525 (*fc).size = cast(uint)dir[idx].size;
527 GC.addRange(fc, InnerFileCookied.sizeof);
529 // open `cooked` file
530 FILE* fres = fopencookie(cast(void*)fc, "r", fcdatpkCallbacks);
533 if ((*fc).fl !is null) fclose((*fc).fl);
534 try { (*fc).xfl.detach(); } catch (Exception) {}
535 throw new NamedException!"WadArchive"("can't open cookied file");
539 return File(fres, filename);
543 // ////////////////////////////////////////////////////////////////////// //
544 // "inner" file processor; processes both packed and unpacked files
545 // can be used as normal disk file processor too
546 static struct InnerFileCookied {
547 private import core.sys.posix.sys.types : ssize_t, off64_t = off_t;
548 private import core.stdc.stdio : FILE;
552 core.sync.mutex.Mutex lock;
553 // note that either one of `fl` or `xfl` must be opened and operational
554 FILE* fl; // disk file, can be `null`
555 File xfl; // disk file, can be closed
556 long stpos; // starting position
557 uint size; // unpacked size
558 uint pos; // current file position
559 //uint prpos; // previous file position
560 //uint pkpos; // current position in DAT
561 //ubyte[] pkb; // packed data
564 @disable this (this);
567 ~this () { close(); }
569 @property bool isOpen () @safe /*@nogc*/ { return (fl !is null || xfl.isOpen); }
572 import core.memory : GC;
573 import core.stdc.stdlib : free;
575 if (lock !is null) lock.lock();
576 scope(exit) if (lock !is null) lock.unlock();
578 import core.stdc.stdio : fclose;
582 try { xfl.detach(); } catch (Exception) {} // it's safe to detach closed File
587 ssize_t read (void* buf, size_t count) {
588 if (buf is null) return -1;
589 if (count == 0 || size == 0) return 0;
591 scope(exit) lock.unlock();
592 if (!isOpen) return -1; // read error
593 if (pos >= size) return 0; // EOF
595 import core.stdc.stdio : ferror, fread;
596 import core.sys.posix.stdio : fseeko;
597 if (size-pos < count) count = cast(size_t)(size-pos);
600 if (fseeko(fl, stpos+pos, 0) < 0) return -1;
601 auto rd = fread(buf, 1, count, fl);
602 if (rd != count && (rd < 0 || ferror(fl))) rd = -1;
603 if (rd > 0) pos += rd;
608 xfl.seek(stpos+pos, 0);
609 auto rd = xfl.rawRead(buf[0..count]);
611 return (rd.length == count ? rd.length : -1);
612 } catch (Exception) {} //BAD DOGGY!
618 long seek (long ofs, int whence) {
620 scope(exit) lock.unlock();
621 if (!isOpen) return -1;
622 //TODO: overflow checks
630 if (ofs > 0) ofs = 0;
636 if (ofs < 0) return -1;
637 if (ofs > size) ofs = size;
645 // ////////////////////////////////////////////////////////////////////// //
647 import core.sys.linux.stdio : cookie_io_functions_t;
648 import core.sys.posix.sys.types : ssize_t, off64_t = off_t;
650 ssize_t fcdatpkRead (void* cookie, char* buf, size_t count) {
651 //conwriteln("reading ", count, " bytes");
652 import core.stdc.errno;
653 auto fc = cast(InnerFileCookied*)cookie;
654 auto res = fc.read(buf, count);
655 if (res < 0) { errno = EIO; return -1; }
659 ssize_t fcdatpkWrite (void* cookie, const(char)* buf, size_t count) {
660 //conwriteln("writing ", count, " bytes");
661 import core.stdc.errno;
662 errno = EIO; //FIXME: find better code
663 return 0; // error; write should not return `-1`
666 int fcdatpkSeek (void* cookie, off64_t* offset, int whence) {
667 //conwriteln("seeking ", *offset, " bytes, whence=", whence);
668 import core.stdc.errno;
669 auto fc = cast(InnerFileCookied*)cookie;
670 auto res = fc.seek(*offset, whence);
671 if (res < 0) { errno = EIO; return -1; }
672 *offset = cast(off64_t)res;
676 int fcdatpkClose (void* cookie) {
677 import core.memory : GC;
678 import core.stdc.stdlib : free;
679 //conwriteln("closing");
680 auto fc = cast(InnerFileCookied*)cookie;
682 GC.removeRange(cookie);
683 try { fc.__dtor(); } catch (Exception) {}
684 // no need to run finalizers, we SHOULD NOT have any
685 //try { GC.runFinalizers(cookie[0..InnerFileCookied.sizeof]); } catch (Exception) {}
688 //conwriteln("closed");
693 __gshared cookie_io_functions_t fcdatpkCallbacks = cookie_io_functions_t(
694 /*.read =*/ &fcdatpkRead,
695 /*.write =*/ &fcdatpkWrite,
696 /*.seek =*/ &fcdatpkSeek,
697 /*.close =*/ &fcdatpkClose,
701 T[] xalloc(T) (size_t len) {
702 import core.stdc.stdlib : malloc;
703 if (len < 1) return null;
704 auto res = cast(T*)malloc(len*T.sizeof);
706 import core.exception : onOutOfMemoryErrorNoGC;
707 onOutOfMemoryErrorNoGC();
709 res[0..len] = T.init;
713 void xfree(T) (ref T[] slc) {
714 if (slc.ptr !is null) {
715 import core.stdc.stdlib : free;