cosmetix
[dd2d.git] / wadarc.d
blob8dcd838d63eae8fed9f8623783f935b5cc911138
1 /* DooM2D: Midnight on the Firing Line
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 module wadarc is aliced;
19 private:
21 static import core.sync.mutex;
22 import std.stdio : File;
24 import console;
26 //import iv.encoding;
27 import iv.ziparc;
30 // ////////////////////////////////////////////////////////////////////////// //
31 public string koi8lotranslit (string s) {
32 usize pos = 0;
33 while (pos < s.length && s.ptr[pos] < 0x80) ++pos;
34 if (pos >= s.length) return s; // nothing to do
35 string res = s[0..pos];
36 while (pos < s.length) {
37 char ch = s.ptr[pos++];
38 if (ch == '\xe1' || ch == '\xc1') res ~= "a";
39 else if (ch == '\xe2' || ch == '\xc2') res ~= "b";
40 else if (ch == '\xf7' || ch == '\xd7') res ~= "v";
41 else if (ch == '\xe7' || ch == '\xc7') res ~= "g";
42 else if (ch == '\xe4' || ch == '\xc4') res ~= "d";
43 else if (ch == '\xe5' || ch == '\xc5') res ~= "e";
44 else if (ch == '\xb3' || ch == '\xa3') res ~= "yo";
45 else if (ch == '\xf6' || ch == '\xd6') res ~= "zh";
46 else if (ch == '\xfa' || ch == '\xda') res ~= "z";
47 else if (ch == '\xe9' || ch == '\xc9') res ~= "i";
48 else if (ch == '\xea' || ch == '\xca') res ~= "j";
49 else if (ch == '\xeb' || ch == '\xcb') res ~= "k";
50 else if (ch == '\xec' || ch == '\xcc') res ~= "l";
51 else if (ch == '\xed' || ch == '\xcd') res ~= "m";
52 else if (ch == '\xee' || ch == '\xce') res ~= "n";
53 else if (ch == '\xef' || ch == '\xcf') res ~= "o";
54 else if (ch == '\xf0' || ch == '\xd0') res ~= "p";
55 else if (ch == '\xf2' || ch == '\xd2') res ~= "r";
56 else if (ch == '\xf3' || ch == '\xd3') res ~= "s";
57 else if (ch == '\xf4' || ch == '\xd4') res ~= "t";
58 else if (ch == '\xf5' || ch == '\xd5') res ~= "u";
59 else if (ch == '\xe6' || ch == '\xc6') res ~= "f";
60 else if (ch == '\xe8' || ch == '\xc8') res ~= "h";
61 else if (ch == '\xe3' || ch == '\xc3') res ~= "c";
62 else if (ch == '\xfe' || ch == '\xde') res ~= "ch";
63 else if (ch == '\xfb' || ch == '\xdb') res ~= "sh";
64 else if (ch == '\xfd' || ch == '\xdd') res ~= "sch";
65 else if (ch == '\xff' || ch == '\xdf') res ~= "x";
66 else if (ch == '\xf9' || ch == '\xd9') res ~= "y";
67 else if (ch == '\xf8' || ch == '\xd8') res ~= "w";
68 else if (ch == '\xfc' || ch == '\xdc') res ~= "e";
69 else if (ch == '\xe0' || ch == '\xc0') res ~= "hu";
70 else if (ch == '\xf1' || ch == '\xd1') res ~= "ja";
71 else res ~= ch;
73 return res;
77 // ////////////////////////////////////////////////////////////////////////// //
78 public immutable char[256] koi8from866Table = [
79 '\x00','\x01','\x02','\x03','\x04','\x05','\x06','\x07','\x08','\x09','\x0a','\x0b','\x0c','\x0d','\x0e','\x0f',
80 '\x10','\x11','\x12','\x13','\x14','\x15','\x16','\x17','\x18','\x19','\x1a','\x1b','\x1c','\x1d','\x1e','\x1f',
81 '\x20','\x21','\x22','\x23','\x24','\x25','\x26','\x27','\x28','\x29','\x2a','\x2b','\x2c','\x2d','\x2e','\x2f',
82 '\x30','\x31','\x32','\x33','\x34','\x35','\x36','\x37','\x38','\x39','\x3a','\x3b','\x3c','\x3d','\x3e','\x3f',
83 '\x40','\x41','\x42','\x43','\x44','\x45','\x46','\x47','\x48','\x49','\x4a','\x4b','\x4c','\x4d','\x4e','\x4f',
84 '\x50','\x51','\x52','\x53','\x54','\x55','\x56','\x57','\x58','\x59','\x5a','\x5b','\x5c','\x5d','\x5e','\x5f',
85 '\x60','\x61','\x62','\x63','\x64','\x65','\x66','\x67','\x68','\x69','\x6a','\x6b','\x6c','\x6d','\x6e','\x6f',
86 '\x70','\x71','\x72','\x73','\x74','\x75','\x76','\x77','\x78','\x79','\x7a','\x7b','\x7c','\x7d','\x7e','\x7f',
87 '\xe1','\xe2','\xf7','\xe7','\xe4','\xe5','\xf6','\xfa','\xe9','\xea','\xeb','\xec','\xed','\xee','\xef','\xf0',
88 '\xf2','\xf3','\xf4','\xf5','\xe6','\xe8','\xe3','\xfe','\xfb','\xfd','\xff','\xf9','\xf8','\xfc','\xe0','\xf1',
89 '\xc1','\xc2','\xd7','\xc7','\xc4','\xc5','\xd6','\xda','\xc9','\xca','\xcb','\xcc','\xcd','\xce','\xcf','\xd0',
90 '\x90','\x91','\x92','\x81','\x87','\xb2','\x3f','\x3f','\x3f','\xb5','\xa1','\xa8','\xae','\x3f','\xac','\x83',
91 '\x84','\x89','\x88','\x86','\x80','\x8a','\xaf','\xb0','\xab','\xa5','\xbb','\xb8','\xb1','\xa0','\xbe','\xb9',
92 '\xba','\x3f','\x3f','\xaa','\xa9','\xa2','\x3f','\x3f','\xbc','\x85','\x82','\x8d','\x8c','\x8e','\x8f','\x8b',
93 '\xd2','\xd3','\xd4','\xd5','\xc6','\xc8','\xc3','\xde','\xdb','\xdd','\xdf','\xd9','\xd8','\xdc','\xc0','\xd1',
94 '\xb3','\xa3','\xb4','\xa4','\xb7','\xa7','\x3f','\x3f','\x9c','\x95','\x9e','\x96','\x3f','\x3f','\x94','\x9a',
97 public immutable char[256] koi8from1251Table = [
98 '\x00','\x01','\x02','\x03','\x04','\x05','\x06','\x07','\x08','\x09','\x0a','\x0b','\x0c','\x0d','\x0e','\x0f',
99 '\x10','\x11','\x12','\x13','\x14','\x15','\x16','\x17','\x18','\x19','\x1a','\x1b','\x1c','\x1d','\x1e','\x1f',
100 '\x20','\x21','\x22','\x23','\x24','\x25','\x26','\x27','\x28','\x29','\x2a','\x2b','\x2c','\x2d','\x2e','\x2f',
101 '\x30','\x31','\x32','\x33','\x34','\x35','\x36','\x37','\x38','\x39','\x3a','\x3b','\x3c','\x3d','\x3e','\x3f',
102 '\x40','\x41','\x42','\x43','\x44','\x45','\x46','\x47','\x48','\x49','\x4a','\x4b','\x4c','\x4d','\x4e','\x4f',
103 '\x50','\x51','\x52','\x53','\x54','\x55','\x56','\x57','\x58','\x59','\x5a','\x5b','\x5c','\x5d','\x5e','\x5f',
104 '\x60','\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','\x7b','\x7c','\x7d','\x7e','\x7f',
106 '\x3f','\x3f','\x3f','\x3f','\x3f','\x3f','\x3f','\x3f','\x3f','\x3f','\x3f','\x3f','\x3f','\x3f','\x3f','\x3f',
107 '\x3f','\x3f','\x3f','\x3f','\x3f','\x3f','\x3f','\x3f','\x3f','\x3f','\x3f','\x3f','\x3f','\x3f','\x3f','\x3f',
108 '\x9a','\x3f','\x3f','\x3f','\x3f','\xbd','\x3f','\x3f','\xb3','\xbf','\xb4','\x3f','\x3f','\x3f','\x3f','\xb7',
109 '\x9c','\x3f','\xb6','\xa6','\xad','\x3f','\x3f','\x9e','\xa3','\x3f','\xa4','\x3f','\x3f','\x3f','\x3f','\xa7',
110 '\xe1','\xe2','\xf7','\xe7','\xe4','\xe5','\xf6','\xfa','\xe9','\xea','\xeb','\xec','\xed','\xee','\xef','\xf0',
111 '\xf2','\xf3','\xf4','\xf5','\xe6','\xe8','\xe3','\xfe','\xfb','\xfd','\xff','\xf9','\xf8','\xfc','\xe0','\xf1',
112 '\xc1','\xc2','\xd7','\xc7','\xc4','\xc5','\xd6','\xda','\xc9','\xca','\xcb','\xcc','\xcd','\xce','\xcf','\xd0',
113 '\xd2','\xd3','\xd4','\xd5','\xc6','\xc8','\xc3','\xde','\xdb','\xdd','\xdf','\xd9','\xd8','\xdc','\xc0','\xd1',
116 // char toupper/tolower, koi8
117 public immutable char[256] koi8tolowerTable = [
118 '\x00','\x01','\x02','\x03','\x04','\x05','\x06','\x07','\x08','\x09','\x0a','\x0b','\x0c','\x0d','\x0e','\x0f',
119 '\x10','\x11','\x12','\x13','\x14','\x15','\x16','\x17','\x18','\x19','\x1a','\x1b','\x1c','\x1d','\x1e','\x1f',
120 '\x20','\x21','\x22','\x23','\x24','\x25','\x26','\x27','\x28','\x29','\x2a','\x2b','\x2c','\x2d','\x2e','\x2f',
121 '\x30','\x31','\x32','\x33','\x34','\x35','\x36','\x37','\x38','\x39','\x3a','\x3b','\x3c','\x3d','\x3e','\x3f',
122 '\x40','\x61','\x62','\x63','\x64','\x65','\x66','\x67','\x68','\x69','\x6a','\x6b','\x6c','\x6d','\x6e','\x6f',
123 '\x70','\x71','\x72','\x73','\x74','\x75','\x76','\x77','\x78','\x79','\x7a','\x5b','\x5c','\x5d','\x5e','\x5f',
124 '\x60','\x61','\x62','\x63','\x64','\x65','\x66','\x67','\x68','\x69','\x6a','\x6b','\x6c','\x6d','\x6e','\x6f',
125 '\x70','\x71','\x72','\x73','\x74','\x75','\x76','\x77','\x78','\x79','\x7a','\x7b','\x7c','\x7d','\x7e','\x7f',
126 '\x80','\x81','\x82','\x83','\x84','\x85','\x86','\x87','\x88','\x89','\x8a','\x8b','\x8c','\x8d','\x8e','\x8f',
127 '\x90','\x91','\x92','\x93','\x94','\x95','\x96','\x97','\x98','\x99','\x9a','\x9b','\x9c','\x9d','\x9e','\x9f',
128 '\xa0','\xa1','\xa2','\xa3','\xa4','\xa5','\xa6','\xa7','\xa8','\xa9','\xaa','\xab','\xac','\xad','\xae','\xaf',
129 '\xb0','\xb1','\xb2','\xa3','\xa4','\xb5','\xa6','\xa7','\xb8','\xb9','\xba','\xbb','\xbc','\xad','\xbe','\xbf',
130 '\xc0','\xc1','\xc2','\xc3','\xc4','\xc5','\xc6','\xc7','\xc8','\xc9','\xca','\xcb','\xcc','\xcd','\xce','\xcf',
131 '\xd0','\xd1','\xd2','\xd3','\xd4','\xd5','\xd6','\xd7','\xd8','\xd9','\xda','\xdb','\xdc','\xdd','\xde','\xdf',
132 '\xc0','\xc1','\xc2','\xc3','\xc4','\xc5','\xc6','\xc7','\xc8','\xc9','\xca','\xcb','\xcc','\xcd','\xce','\xcf',
133 '\xd0','\xd1','\xd2','\xd3','\xd4','\xd5','\xd6','\xd7','\xd8','\xd9','\xda','\xdb','\xdc','\xdd','\xde','\xdf',
136 public immutable char[256] koi8toupperTable = [
137 '\x00','\x01','\x02','\x03','\x04','\x05','\x06','\x07','\x08','\x09','\x0a','\x0b','\x0c','\x0d','\x0e','\x0f',
138 '\x10','\x11','\x12','\x13','\x14','\x15','\x16','\x17','\x18','\x19','\x1a','\x1b','\x1c','\x1d','\x1e','\x1f',
139 '\x20','\x21','\x22','\x23','\x24','\x25','\x26','\x27','\x28','\x29','\x2a','\x2b','\x2c','\x2d','\x2e','\x2f',
140 '\x30','\x31','\x32','\x33','\x34','\x35','\x36','\x37','\x38','\x39','\x3a','\x3b','\x3c','\x3d','\x3e','\x3f',
141 '\x40','\x41','\x42','\x43','\x44','\x45','\x46','\x47','\x48','\x49','\x4a','\x4b','\x4c','\x4d','\x4e','\x4f',
142 '\x50','\x51','\x52','\x53','\x54','\x55','\x56','\x57','\x58','\x59','\x5a','\x5b','\x5c','\x5d','\x5e','\x5f',
143 '\x60','\x41','\x42','\x43','\x44','\x45','\x46','\x47','\x48','\x49','\x4a','\x4b','\x4c','\x4d','\x4e','\x4f',
144 '\x50','\x51','\x52','\x53','\x54','\x55','\x56','\x57','\x58','\x59','\x5a','\x7b','\x7c','\x7d','\x7e','\x7f',
145 '\x80','\x81','\x82','\x83','\x84','\x85','\x86','\x87','\x88','\x89','\x8a','\x8b','\x8c','\x8d','\x8e','\x8f',
146 '\x90','\x91','\x92','\x93','\x94','\x95','\x96','\x97','\x98','\x99','\x9a','\x9b','\x9c','\x9d','\x9e','\x9f',
147 '\xa0','\xa1','\xa2','\xb3','\xb4','\xa5','\xb6','\xb7','\xa8','\xa9','\xaa','\xab','\xac','\xbd','\xae','\xaf',
148 '\xb0','\xb1','\xb2','\xb3','\xb4','\xb5','\xb6','\xb7','\xb8','\xb9','\xba','\xbb','\xbc','\xbd','\xbe','\xbf',
149 '\xe0','\xe1','\xe2','\xe3','\xe4','\xe5','\xe6','\xe7','\xe8','\xe9','\xea','\xeb','\xec','\xed','\xee','\xef',
150 '\xf0','\xf1','\xf2','\xf3','\xf4','\xf5','\xf6','\xf7','\xf8','\xf9','\xfa','\xfb','\xfc','\xfd','\xfe','\xff',
151 '\xe0','\xe1','\xe2','\xe3','\xe4','\xe5','\xe6','\xe7','\xe8','\xe9','\xea','\xeb','\xec','\xed','\xee','\xef',
152 '\xf0','\xf1','\xf2','\xf3','\xf4','\xf5','\xf6','\xf7','\xf8','\xf9','\xfa','\xfb','\xfc','\xfd','\xfe','\xff',
155 public immutable ubyte[32] koi8alphaTable = [
156 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0x07,0xfe,0xff,0xff,0x07,
157 0x00,0x00,0x00,0x00,0xd8,0x20,0xd8,0x20,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
160 public char dos2koi8 (char ch) pure nothrow @trusted @nogc {
161 pragma(inline, true);
162 return koi8from866Table.ptr[cast(int)ch];
165 public char koi8lower (char ch) pure nothrow @trusted @nogc {
166 pragma(inline, true);
167 return koi8tolowerTable.ptr[cast(int)ch];
170 public char koi8upper (char ch) pure nothrow @trusted @nogc {
171 pragma(inline, true);
172 return koi8toupperTable.ptr[cast(int)ch];
175 public bool koi8isAlpha (char ch) pure nothrow @trusted @nogc {
176 pragma(inline, true);
177 return ((koi8alphaTable.ptr[ch/8]&(1<<(ch%8))) != 0);
181 // ////////////////////////////////////////////////////////////////////////// //
182 //public __gshared WadArchive[] wadList;
183 __gshared ZipArchive[] pk3List;
184 __gshared core.sync.mutex.Mutex glock;
185 __gshared string dataPath;
188 shared static this () {
189 glock = new core.sync.mutex.Mutex;
193 public void setDataPath (const(char)[] path) {
194 while (path.length > 1 && path[0] == '/' && path[1] == '/') path = path[1..$];
195 if (path != "/") while (path.length && path[$-1] == '/') path = path[0..$-1];
196 dataPath = path.idup~"/";
199 public string getDataPath () { pragma(inline, true); return dataPath; }
203 public void addWad (string fname) {
204 if (glock !is null) glock.lock();
205 scope(exit) if (glock !is null) glock.unlock();
206 conwriteln("adding '", fname, "'...");
207 wadList ~= new WadArchive(fname);
212 public void addPK3 (string fname) {
213 if (glock !is null) glock.lock();
214 scope(exit) if (glock !is null) glock.unlock();
215 conwriteln("; adding '", fname, "'...");
216 pk3List ~= new ZipArchive(fname);
220 public File openFile (string fname) {
221 if (glock !is null) glock.lock();
222 scope(exit) if (glock !is null) glock.unlock();
223 try {
224 return File(dataPath~fname);
225 } catch (Exception) {}
226 // now try pk3
227 foreach_reverse (ZipArchive pk3; pk3List) {
228 try {
229 return pk3.fopen(fname);
230 } catch (Exception) {}
232 return File(fname); // to throw a correct exception, lol
236 public string loadTextFile (string fname) {
237 auto fl = openFile(fname);
238 auto sz = fl.size;
239 if (sz < 0 || sz > 1024*1024) throw new Exception("invalid text file size: '"~fname~"'");
240 if (sz == 0) return "";
241 auto res = new char[](cast(uint)sz);
242 if (fl.rawRead(res[]).length != res.length) throw new Exception("error reading text file '"~fname~"'");
243 import std.exception : assumeUnique;
244 return res.assumeUnique;
248 // ////////////////////////////////////////////////////////////////////////// //
250 final class WadArchive {
251 public:
252 import std.stdio : File;
254 private:
255 static struct FileInfo {
256 uint ofs;
257 uint size;
258 string path;
259 string name;
262 // for dir range
263 public static struct DirEntry {
264 string path;
265 string name;
266 uint size;
269 private:
270 File zfl;
271 ulong flstpos;
272 FileInfo[] dir;
274 public:
275 this (string fname) {
276 import std.stdio : File;
277 initLock();
278 zfl = File(fname);
279 open(zfl);
280 scope(failure) { zfl.close; zfl = zfl.init; }
283 // it now owns the file (if no exception was thrown)
284 this (File fl) {
285 initLock();
286 open(fl);
287 scope(success) zfl = fl;
290 @property auto files () {
291 static struct Range {
292 private:
293 WadArchive me;
294 uint curindex;
296 nothrow @safe @nogc:
297 this (WadArchive ame, uint aidx=0) { me = ame; curindex = aidx; }
299 public:
300 @property bool empty () const { return (curindex >= me.dir.length); }
301 @property DirEntry front () const {
302 return DirEntry(
303 (curindex < me.dir.length ? me.dir[cast(usize)curindex].path : null),
304 (curindex < me.dir.length ? me.dir[cast(usize)curindex].name : null),
305 (curindex < me.dir.length ? me.dir[cast(usize)curindex].size : 0));
307 @property Range save () { return Range(me, curindex); }
308 void popFront () { if (curindex < me.dir.length) ++curindex; }
309 @property uint length () const { return me.dir.length; }
310 @property uint position () const { return curindex; } // current position
311 @property void position (uint np) { curindex = np; }
312 void rewind () { curindex = 0; }
314 return Range(this);
317 File fopen (ref in DirEntry de) {
318 string dname = de.name;
319 usize pos = dname.length;
320 while (pos > 0 && dname.ptr[pos-1] != '.') --pos;
321 if (pos > 1) dname = dname[0..pos-1];
322 foreach (immutable idx, ref fi; dir) {
323 if (/*fi.path == de.path &&*/ fi.name == dname) return openDirEntry(idx, fi.name);
325 throw new NamedException!"WadArchive"("file not found");
328 File fopen (const(char)[] fname) {
329 DirEntry de;
330 auto pos = fname.length;
331 while (pos > 0 && fname[pos-1] != '/') --pos;
332 if (pos) {
333 de.path = cast(string)fname[0..pos]; // it's safe here
334 de.name = cast(string)fname[pos..$]; // it's safe here
335 //conwriteln("[", de.path, "] [", de.name, "]");
336 } else {
337 de.name = cast(string)fname; // it's safe here
339 return fopen(de);
342 private:
343 void cleanup () {
344 dir.length = 0;
345 if (zfl.isOpen) zfl.close;
346 zfl = zfl.init;
349 void open (File fl) {
350 import std.uni : icmp;
352 immutable string[$] msn = [
353 "SARG", "TROO", "POSS", "SPOS", "CYBR", "CPOS", "BOSS", "BOS2", "HEAD", "SKUL",
354 "PAIN", "SPID", "BSPI", "FATT", "SKEL", "VILE", "FISH", "BAR1", "ROBO", "PLAY"
357 string curpath;
359 string fixName (const(char)[] name) {
360 return name.idup;
361 if (name.length >= 4 && name[0..4] == "stcf") return name.idup~".vga";
362 if (name.length >= 4 && name[0..4] == "stbf") return name.idup~".vga";
363 if (name.length >= 5 && name[0..5] == "winum") return name.idup~".vga";
364 if (name == "wicolon" || name == "wiminus" || name == "wipcnt") return name.idup~".vga";
365 if (name.length > 3 && name[0..3] == "map") return name.idup~".d2m";
366 if (name == "playpal") return "playpal.pal";
367 switch (name) {
368 case "endoom": return "endoom.b80";
369 case "endanim": return "endanim.a8";
370 case "end2anim": return "end2anim.a8";
371 case "darts": return "darts.a8";
372 case "colormap": return "colormap.tbl";
373 case "mixmap": return "mixmap.tbl";
374 case "titlepic": return "titlepic.vga";
375 case "interpic": return "interpic.vga";
376 case "cd1pic": return "cd1pic.vga";
377 case "endpic": return "endpic.vraw";
378 case "m_therml": return "m_therml.vga";
379 case "m_thermm": return "m_thermm.vga";
380 case "m_thermo": return "m_thermo.vga";
381 case "m_thermr": return "m_thermr.vga";
382 case "m_lscntr": return "m_lscntr.vga";
383 case "m_lsleft": return "m_lsleft.vga";
384 case "m_lsrght": return "m_lsrght.vga";
385 default:
388 import std.algorithm;
389 if (curpath == "sounds/") return name.idup~".snd";
390 if (curpath == "music/") return (name.length > 3 && name[0..3] == "dmi" ? name.idup~".dmi" : name.idup~".dmm");
391 if (curpath == "tilegfx/") return name.idup~".vga";
392 if (curpath.startsWith("sprites/")) return name.idup~".vga";
393 return name.idup;
396 string fixPath (const(char)[] name) {
397 switch (name) {
398 case "m_therml":
399 case "m_thermm":
400 case "m_thermo":
401 case "m_thermr":
402 case "m_lscntr":
403 case "m_lsleft":
404 case "m_lsrght":
405 return "menugfx/";
406 case "rsky1":
407 case "rsky2":
408 case "rsky3":
409 return "sprites/sky/";
410 default:
412 if (name.length >= 4 && name[0..4] == "stcf") return "fonts/stcf/";
413 if (name.length >= 4 && name[0..4] == "stbf") return "fonts/stbf/";
414 if (name.length >= 5 && name[0..5] == "winum") return "fonts/winum/";
415 if (name == "wicolon" || name == "wiminus" || name == "wipcnt") return "fonts/winum/";
416 if (name.length > 3 && name[0..3] == "map") return "maps/";
417 if (name == "d_start") curpath = "sounds/";
418 if (name == "m_start") curpath = "music/";
419 if (name == "w_start") curpath = "tilegfx/";
420 if (name == "s_start") curpath = "sprites/";
421 if (curpath == "sprites/" && name.length > 4) {
422 switch (name[0..4]) {
423 case "sarg": return "sprites/monsters/demon/";
424 case "troo": return "sprites/monsters/imp/";
425 case "poss": return "sprites/monsters/zombie/";
426 case "spos": return "sprites/monsters/sergeant/";
427 case "cybr": return "sprites/monsters/cyberdemon/";
428 case "cpos": return "sprites/monsters/chaingunner/";
429 case "boss": return "sprites/monsters/baron/";
430 case "bos2": return "sprites/monsters/knight/";
431 case "head": return "sprites/monsters/cacodemon/";
432 case "skul": return "sprites/monsters/soul/";
433 case "pain": return "sprites/monsters/painel/";
434 case "spid": return "sprites/monsters/mastermind/";
435 case "bspi": return "sprites/monsters/arachnotron/";
436 case "fatt": return "sprites/monsters/mancubus/";
437 case "skel": return "sprites/monsters/revenant/";
438 case "vile": return "sprites/monsters/archvile/";
439 case "fish": return "sprites/monsters/fish/";
440 case "bar1": return "sprites/monsters/barrel/";
441 case "robo": return "sprites/monsters/robot/";
442 case "play": return "sprites/monsters/player/";
443 default:
446 return curpath;
449 import core.stdc.stdio : SEEK_CUR, SEEK_END;
450 scope(failure) cleanup();
452 uint readU32 () {
453 ubyte[4] data;
454 if (fl.rawRead(data[]).length != data.length) throw new NamedException!"WadArchive"("reading error");
455 return cast(uint)(data[0]+0x100*data[1]+0x10000*data[2]+0x1000000*data[3]);
458 uint lmpofs;
459 uint lmpsize;
460 char[8] lmpname;
462 flstpos = fl.tell;
464 if (fl.rawRead(lmpname[0..4]).length != 4) throw new NamedException!"WadArchive"("reading error");
465 if (lmpname[0..4] != "PWAD" && lmpname[0..4] != "IWAD") throw new NamedException!"WadArchive"("not a WAD file");
466 auto count = readU32();
467 auto dofs = readU32();
468 if (count == 0) return;
469 if (dofs < 3*4 || count == uint.max) throw new NamedException!"WadArchive"("invalid WAD file");
470 fl.seek(dofs-3*4, SEEK_CUR);
471 while (count-- > 0) {
472 lmpofs = readU32();
473 lmpsize = readU32();
474 if (fl.rawRead(lmpname[]).length != 8) throw new NamedException!"WAD"("reading error");
475 int pos = 0;
476 while (pos < 8 && lmpname[pos] != 0) {
477 if (lmpname[pos] >= 'A' && lmpname[pos] <= 'Z') lmpname[pos] += 32;
478 ++pos;
480 if (pos == 0) continue;
481 auto nm = lmpname[0..pos];
482 if (nm.length > 6 && nm[$-6..$] == "_start") {
483 fixPath(nm);
484 continue;
486 if (nm.length > 4 && nm[$-4..$] == "_end") {
487 curpath = null;
488 continue;
490 uint fidx = uint.max;
491 foreach (immutable idx, ref de; dir) if (de.name == lmpname[0..pos]) { fidx = cast(uint)idx; break; }
492 if (fidx >= dir.length) {
493 fidx = cast(uint)dir.length;
494 dir ~= FileInfo();
496 dir[fidx].ofs = lmpofs;
497 dir[fidx].size = lmpsize;
498 //dir[fidx].name = fixName(nm);
499 //dir[fidx].path = fixPath(nm);
501 import std.uni : toLower;
502 dir[fidx].name = koi8lotranslit(recodeToKOI8(recode(fixName(nm), "utf-8", "cp866").toLower, "utf-8"));
503 dir[fidx].path = koi8lotranslit(recodeToKOI8(recode(fixPath(nm), "utf-8", "cp866").toLower, "utf-8"));
505 //debug conwriteln(dir[fidx].path, " : ", dir[fidx].name);
507 debug conwriteln(dir.length, " files found");
511 // ////////////////////////////////////////////////////////////////////// //
512 static import core.sync.mutex;
514 core.sync.mutex.Mutex lock;
516 void initLock () {
517 lock = new core.sync.mutex.Mutex;
520 auto openDirEntry (uint idx, string filename) {
521 import core.sys.linux.stdio : fopencookie;
522 import core.stdc.stdio : FILE;
523 import core.stdc.stdio : fopen, fclose;
524 import core.stdc.stdlib : calloc, free;
525 import etc.c.zlib;
526 import std.internal.cstring : tempCString;
527 import core.memory : GC;
529 if (!zfl.isOpen) throw new NamedException!"WadArchive"("archive wasn't opened");
530 if (idx >= dir.length) throw new NamedException!"WadArchive"("invalid dir index");
532 // create cookied `FILE*`
533 auto fc = cast(InnerFileCookied*)calloc(1, InnerFileCookied.sizeof);
534 scope(exit) if (fc !is null) free(fc);
535 if (fc is null) {
536 import core.exception : onOutOfMemoryErrorNoGC;
537 onOutOfMemoryErrorNoGC();
539 (*fc) = InnerFileCookied.init;
540 (*fc).stpos = flstpos+dir[idx].ofs;
541 (*fc).size = cast(uint)dir[idx].size;
542 (*fc).lock = lock;
543 GC.addRange(fc, InnerFileCookied.sizeof);
544 (*fc).xfl = zfl;
545 // open `cooked` file
546 FILE* fres = fopencookie(cast(void*)fc, "r", fcdatpkCallbacks);
547 if (fres is null) {
548 // alas
549 if ((*fc).fl !is null) fclose((*fc).fl);
550 try { (*fc).xfl.detach(); } catch (Exception) {}
551 throw new NamedException!"WadArchive"("can't open cookied file");
553 // ok
554 fc = null;
555 return File(fres, filename);
559 // ////////////////////////////////////////////////////////////////////// //
560 // "inner" file processor; processes both packed and unpacked files
561 // can be used as normal disk file processor too
562 static struct InnerFileCookied {
563 private import core.sys.posix.sys.types : ssize_t, off64_t = off_t;
564 private import core.stdc.stdio : FILE;
566 enum ibsize = 32768;
568 core.sync.mutex.Mutex lock;
569 // note that either one of `fl` or `xfl` must be opened and operational
570 FILE* fl; // disk file, can be `null`
571 File xfl; // disk file, can be closed
572 long stpos; // starting position
573 uint size; // unpacked size
574 uint pos; // current file position
575 //uint prpos; // previous file position
576 //uint pkpos; // current position in DAT
577 //ubyte[] pkb; // packed data
578 bool eoz;
580 @disable this (this);
582 nothrow:
583 ~this () { close(); }
585 @property bool isOpen () @safe /*@nogc*/ { return (fl !is null || xfl.isOpen); }
587 void close () {
588 import core.memory : GC;
589 import core.stdc.stdlib : free;
591 if (lock !is null) lock.lock();
592 scope(exit) if (lock !is null) lock.unlock();
593 if (fl !is null) {
594 import core.stdc.stdio : fclose;
595 fclose(fl);
596 fl = null;
598 try { xfl.detach(); } catch (Exception) {} // it's safe to detach closed File
600 eoz = true;
603 ssize_t read (void* buf, size_t count) {
604 if (buf is null) return -1;
605 if (count == 0 || size == 0) return 0;
606 lock.lock();
607 scope(exit) lock.unlock();
608 if (!isOpen) return -1; // read error
609 if (pos >= size) return 0; // EOF
611 import core.stdc.stdio : ferror, fread;
612 import core.sys.posix.stdio : fseeko;
613 if (size-pos < count) count = cast(size_t)(size-pos);
614 if (fl !is null) {
615 // `FILE*`
616 if (fseeko(fl, stpos+pos, 0) < 0) return -1;
617 auto rd = fread(buf, 1, count, fl);
618 if (rd != count && (rd < 0 || ferror(fl))) rd = -1;
619 if (rd > 0) pos += rd;
620 return rd;
621 } else {
622 // std.stdio.File
623 try {
624 xfl.seek(stpos+pos, 0);
625 auto rd = xfl.rawRead(buf[0..count]);
626 pos += rd.length;
627 return (rd.length == count ? rd.length : -1);
628 } catch (Exception) {} //BAD DOGGY!
629 return -1;
634 long seek (long ofs, int whence) {
635 lock.lock();
636 scope(exit) lock.unlock();
637 if (!isOpen) return -1;
638 //TODO: overflow checks
639 switch (whence) {
640 case 0: // SEEK_SET
641 break;
642 case 1: // SEEK_CUR
643 ofs += pos;
644 break;
645 case 2: // SEEK_END
646 if (ofs > 0) ofs = 0;
647 ofs += size;
648 break;
649 default:
650 return -1;
652 if (ofs < 0) return -1;
653 if (ofs > size) ofs = size;
654 pos = cast(uint)ofs;
655 return ofs;
660 static:
661 // ////////////////////////////////////////////////////////////////////// //
662 extern(C) nothrow {
663 import core.sys.linux.stdio : cookie_io_functions_t;
664 import core.sys.posix.sys.types : ssize_t, off64_t = off_t;
666 ssize_t fcdatpkRead (void* cookie, char* buf, size_t count) {
667 //conwriteln("reading ", count, " bytes");
668 import core.stdc.errno;
669 auto fc = cast(InnerFileCookied*)cookie;
670 auto res = fc.read(buf, count);
671 if (res < 0) { errno = EIO; return -1; }
672 return res;
675 ssize_t fcdatpkWrite (void* cookie, const(char)* buf, size_t count) {
676 //conwriteln("writing ", count, " bytes");
677 import core.stdc.errno;
678 errno = EIO; //FIXME: find better code
679 return 0; // error; write should not return `-1`
682 int fcdatpkSeek (void* cookie, off64_t* offset, int whence) {
683 //conwriteln("seeking ", *offset, " bytes, whence=", whence);
684 import core.stdc.errno;
685 auto fc = cast(InnerFileCookied*)cookie;
686 auto res = fc.seek(*offset, whence);
687 if (res < 0) { errno = EIO; return -1; }
688 *offset = cast(off64_t)res;
689 return 0;
692 int fcdatpkClose (void* cookie) {
693 import core.memory : GC;
694 import core.stdc.stdlib : free;
695 //conwriteln("closing");
696 auto fc = cast(InnerFileCookied*)cookie;
697 //fc.close();
698 GC.removeRange(cookie);
699 try { fc.__dtor(); } catch (Exception) {}
700 // no need to run finalizers, we SHOULD NOT have any
701 //try { GC.runFinalizers(cookie[0..InnerFileCookied.sizeof]); } catch (Exception) {}
702 //fc.xfl.__dtor();
703 free(cookie);
704 //conwriteln("closed");
705 return 0;
709 __gshared cookie_io_functions_t fcdatpkCallbacks = cookie_io_functions_t(
710 /*.read =*/ &fcdatpkRead,
711 /*.write =*/ &fcdatpkWrite,
712 /*.seek =*/ &fcdatpkSeek,
713 /*.close =*/ &fcdatpkClose,
716 static:
717 T[] xalloc(T) (size_t len) {
718 import core.stdc.stdlib : malloc;
719 if (len < 1) return null;
720 auto res = cast(T*)malloc(len*T.sizeof);
721 if (res is null) {
722 import core.exception : onOutOfMemoryErrorNoGC;
723 onOutOfMemoryErrorNoGC();
725 res[0..len] = T.init;
726 return res[0..len];
729 void xfree(T) (ref T[] slc) {
730 if (slc.ptr !is null) {
731 import core.stdc.stdlib : free;
732 free(slc.ptr);
734 slc = null;