shader optimization; won some frames on big lights
[dd2d.git] / wadarc.d
blob2c96f067dc918abd20ade4eb1e59819a4e22c94b
1 module wadarc is aliced;
2 private:
4 static import core.sync.mutex;
5 import std.stdio : File;
8 // ////////////////////////////////////////////////////////////////////////// //
9 __gshared WadArchive[] wadList;
10 __gshared core.sync.mutex.Mutex glock;
11 __gshared string dataPath;
14 shared static this () {
15 glock = new core.sync.mutex.Mutex;
19 public void setDataPath (const(char)[] path) {
20 while (path.length > 1 && path[0] == '/' && path[1] == '/') path = path[1..$];
21 if (path != "/") while (path.length && path[$-1] == '/') path = path[0..$-1];
22 dataPath = path.idup~"/";
26 public void addWad (string fname) {
27 if (glock !is null) glock.lock();
28 scope(exit) if (glock !is null) glock.unlock();
29 { import std.stdio; writeln("adding '", fname, "'..."); }
30 wadList ~= new WadArchive(fname);
34 public File openFile (string fname) {
35 try {
36 return File(fname);
37 } catch (Exception) {}
38 // now try wads
39 if (glock !is null) glock.lock();
40 scope(exit) if (glock !is null) glock.unlock();
41 foreach_reverse (WadArchive wad; wadList) {
42 try {
43 return wad.fopen(fname);
44 } catch (Exception) {}
46 return File(fname); // to throw a correct exception, lol
48 string ofname = fname;
49 import std.path;
50 fname = fname.baseName.setExtension("");
51 foreach_reverse (WadArchive wad; wadList) {
52 try {
53 return wad.fopen(fname);
54 } catch (Exception) {}
56 return File(ofname); // to throw a correct exception, lol
61 // ////////////////////////////////////////////////////////////////////////// //
62 final class WadArchive {
63 public:
64 import std.stdio : File;
66 private:
67 static struct FileInfo {
68 uint ofs;
69 uint size;
70 string path;
71 string name;
74 // for dir range
75 public static struct DirEntry {
76 string path;
77 string name;
78 uint size;
81 private:
82 File zfl;
83 ulong flstpos;
84 FileInfo[] dir;
86 public:
87 this (string fname) {
88 import std.stdio : File;
89 initLock();
90 zfl = File(fname);
91 open(zfl);
92 scope(failure) { zfl.close; zfl = zfl.init; }
95 // it now owns the file (if no exception was thrown)
96 this (File fl) {
97 initLock();
98 open(fl);
99 scope(success) zfl = fl;
102 @property auto files () {
103 static struct Range {
104 private:
105 WadArchive me;
106 uint curindex;
108 nothrow @safe @nogc:
109 this (WadArchive ame, uint aidx=0) { me = ame; curindex = aidx; }
111 public:
112 @property bool empty () const { return (curindex >= me.dir.length); }
113 @property DirEntry front () const {
114 return DirEntry(
115 (curindex < me.dir.length ? me.dir[cast(usize)curindex].path : null),
116 (curindex < me.dir.length ? me.dir[cast(usize)curindex].name : null),
117 (curindex < me.dir.length ? me.dir[cast(usize)curindex].size : 0));
119 @property Range save () { return Range(me, curindex); }
120 void popFront () { if (curindex < me.dir.length) ++curindex; }
121 @property uint length () const { return me.dir.length; }
122 @property uint position () const { return curindex; } // current position
123 @property void position (uint np) { curindex = np; }
124 void rewind () { curindex = 0; }
126 return Range(this);
129 File fopen (ref in DirEntry de) {
130 foreach (immutable idx, ref fi; dir) {
131 if (fi.path == de.path && fi.name == de.name) return openDirEntry(idx, fi.name);
133 throw new NamedException!"WadArchive"("file not found");
136 File fopen (const(char)[] fname) {
137 DirEntry de;
138 auto pos = fname.length;
139 while (pos > 0 && fname[pos-1] != '/') --pos;
140 if (pos) {
141 de.path = cast(string)fname[0..pos]; // it's safe here
142 de.name = cast(string)fname[pos..$]; // it's safe here
143 //{ import std.stdio; writeln("[", de.path, "] [", de.name, "]"); }
144 } else {
145 de.name = cast(string)fname; // it's safe here
147 return fopen(de);
150 private:
151 void cleanup () {
152 dir.length = 0;
153 if (zfl.isOpen) zfl.close;
154 zfl = zfl.init;
157 void open (File fl) {
158 string curpath;
160 string fixName (const(char)[] name) {
161 if (name.length >= 4 && name[0..4] == "stcf") return name.idup~".vga";
162 if (name.length >= 4 && name[0..4] == "stbf") return name.idup~".vga";
163 if (name.length >= 5 && name[0..5] == "winum") return name.idup~".vga";
164 if (name == "wicolon" || name == "wiminus" || name == "wipcnt") return name.idup~".vga";
165 if (name.length > 3 && name[0..3] == "map") return name.idup~".d2m";
166 if (name == "playpal") return "playpal.pal";
167 switch (name) {
168 case "endoom": return "endoom.b80";
169 case "endanim": return "endanim.a8";
170 case "end2anim": return "end2anim.a8";
171 case "darts": return "darts.a8";
172 case "colormap": return "colormap.tbl";
173 case "mixmap": return "mixmap.tbl";
174 case "titlepic": return "titlepic.vga";
175 case "interpic": return "interpic.vga";
176 case "cd1pic": return "cd1pic.vga";
177 case "endpic": return "endpic.vraw";
178 case "m_therml": return "m_therml.vga";
179 case "m_thermm": return "m_thermm.vga";
180 case "m_thermo": return "m_thermo.vga";
181 case "m_thermr": return "m_thermr.vga";
182 case "m_lscntr": return "m_lscntr.vga";
183 case "m_lsleft": return "m_lsleft.vga";
184 case "m_lsrght": return "m_lsrght.vga";
185 default:
188 if (curpath == "sounds/") return name.idup~".snd";
189 if (curpath == "music/") return (name.length > 3 && name[0..3] == "dmi" ? name.idup~".dmi" : name.idup~".dmm");
190 if (curpath == "tilegfx/") return name.idup~".vga";
191 if (curpath == "sprites/") return name.idup~".vga";
192 return name.idup;
195 string fixPath (const(char)[] name) {
196 switch (name) {
197 case "m_therml":
198 case "m_thermm":
199 case "m_thermo":
200 case "m_thermr":
201 case "m_lscntr":
202 case "m_lsleft":
203 case "m_lsrght":
204 return "menugfx/";
205 default:
207 if (name.length >= 4 && name[0..4] == "stcf") return "fonts/stcf/";
208 if (name.length >= 4 && name[0..4] == "stbf") return "fonts/stbf/";
209 if (name.length >= 5 && name[0..5] == "winum") return "fonts/winum/";
210 if (name == "wicolon" || name == "wiminus" || name == "wipcnt") return "fonts/winum/";
211 if (name.length > 3 && name[0..3] == "map") return "maps/";
212 if (name == "d_start") curpath = "sounds/";
213 if (name == "m_start") curpath = "music/";
214 if (name == "w_start") curpath = "tilegfx/";
215 if (name == "s_start") curpath = "sprites/";
216 return curpath;
219 import core.stdc.stdio : SEEK_CUR, SEEK_END;
220 debug import std.stdio : writeln, writefln;
221 scope(failure) cleanup();
223 uint readU32 () {
224 ubyte[4] data;
225 if (fl.rawRead(data[]).length != data.length) throw new NamedException!"WadArchive"("reading error");
226 return cast(uint)(data[0]+0x100*data[1]+0x10000*data[2]+0x1000000*data[3]);
229 uint lmpofs;
230 uint lmpsize;
231 char[8] lmpname;
233 flstpos = fl.tell;
235 if (fl.rawRead(lmpname[0..4]).length != 4) throw new NamedException!"WadArchive"("reading error");
236 if (lmpname[0..4] != "PWAD" && lmpname[0..4] != "IWAD") throw new NamedException!"WadArchive"("not a WAD file");
237 auto count = readU32();
238 auto dofs = readU32();
239 if (count == 0) return;
240 if (dofs < 3*4 || count == uint.max) throw new NamedException!"WadArchive"("invalid WAD file");
241 fl.seek(dofs-3*4, SEEK_CUR);
242 while (count-- > 0) {
243 lmpofs = readU32();
244 lmpsize = readU32();
245 if (fl.rawRead(lmpname[]).length != 8) throw new NamedException!"WAD"("reading error");
246 int pos = 0;
247 while (pos < 8 && lmpname[pos] != 0) {
248 if (lmpname[pos] >= 'A' && lmpname[pos] <= 'Z') lmpname[pos] += 32;
249 ++pos;
251 if (pos == 0) continue;
252 auto nm = lmpname[0..pos];
253 if (nm.length > 6 && nm[$-6..$] == "_start") {
254 fixPath(nm);
255 continue;
257 if (nm.length > 4 && nm[$-4..$] == "_end") {
258 curpath = null;
259 continue;
261 uint fidx = uint.max;
262 foreach (immutable idx, ref de; dir) if (de.name == lmpname[0..pos]) { fidx = cast(uint)idx; break; }
263 if (fidx >= dir.length) {
264 fidx = cast(uint)dir.length;
265 dir ~= FileInfo();
267 dir[fidx].ofs = lmpofs;
268 dir[fidx].size = lmpsize;
269 dir[fidx].name = fixName(nm);
270 dir[fidx].path = fixPath(nm);
271 //debug writeln(dir[fidx].path, " : ", dir[fidx].name);
273 debug writeln(dir.length, " files found");
277 // ////////////////////////////////////////////////////////////////////// //
278 static import core.sync.mutex;
280 core.sync.mutex.Mutex lock;
282 void initLock () {
283 lock = new core.sync.mutex.Mutex;
286 auto openDirEntry (uint idx, string filename) {
287 import core.sys.linux.stdio : fopencookie;
288 import core.stdc.stdio : FILE;
289 import core.stdc.stdio : fopen, fclose;
290 import core.stdc.stdlib : calloc, free;
291 import etc.c.zlib;
292 import std.internal.cstring : tempCString;
293 import core.memory : GC;
295 if (!zfl.isOpen) throw new NamedException!"WadArchive"("archive wasn't opened");
296 if (idx >= dir.length) throw new NamedException!"WadArchive"("invalid dir index");
298 // create cookied `FILE*`
299 auto fc = cast(InnerFileCookied*)calloc(1, InnerFileCookied.sizeof);
300 scope(exit) if (fc !is null) free(fc);
301 if (fc is null) {
302 import core.exception : onOutOfMemoryErrorNoGC;
303 onOutOfMemoryErrorNoGC();
305 (*fc) = InnerFileCookied.init;
306 (*fc).stpos = flstpos+dir[idx].ofs;
307 (*fc).size = cast(uint)dir[idx].size;
308 (*fc).lock = lock;
309 GC.addRange(fc, InnerFileCookied.sizeof);
310 (*fc).xfl = zfl;
311 // open `cooked` file
312 FILE* fres = fopencookie(cast(void*)fc, "r", fcdatpkCallbacks);
313 if (fres is null) {
314 // alas
315 if ((*fc).fl !is null) fclose((*fc).fl);
316 try { (*fc).xfl.detach(); } catch (Exception) {}
317 throw new NamedException!"WadArchive"("can't open cookied file");
319 // ok
320 fc = null;
321 return File(fres, filename);
325 // ////////////////////////////////////////////////////////////////////// //
326 // "inner" file processor; processes both packed and unpacked files
327 // can be used as normal disk file processor too
328 static struct InnerFileCookied {
329 private import core.sys.posix.sys.types : ssize_t, off64_t = off_t;
330 private import core.stdc.stdio : FILE;
332 enum ibsize = 32768;
334 core.sync.mutex.Mutex lock;
335 // note that either one of `fl` or `xfl` must be opened and operational
336 FILE* fl; // disk file, can be `null`
337 File xfl; // disk file, can be closed
338 long stpos; // starting position
339 uint size; // unpacked size
340 uint pos; // current file position
341 //uint prpos; // previous file position
342 //uint pkpos; // current position in DAT
343 //ubyte[] pkb; // packed data
344 bool eoz;
346 @disable this (this);
348 nothrow:
349 ~this () { close(); }
351 @property bool isOpen () @safe /*@nogc*/ { return (fl !is null || xfl.isOpen); }
353 void close () {
354 import core.memory : GC;
355 import core.stdc.stdlib : free;
357 if (lock !is null) lock.lock();
358 scope(exit) if (lock !is null) lock.unlock();
359 if (fl !is null) {
360 import core.stdc.stdio : fclose;
361 fclose(fl);
362 fl = null;
364 try { xfl.detach(); } catch (Exception) {} // it's safe to detach closed File
366 eoz = true;
369 ssize_t read (void* buf, size_t count) {
370 if (buf is null) return -1;
371 if (count == 0 || size == 0) return 0;
372 lock.lock();
373 scope(exit) lock.unlock();
374 if (!isOpen) return -1; // read error
375 if (pos >= size) return 0; // EOF
377 import core.stdc.stdio : ferror, fread;
378 import core.sys.posix.stdio : fseeko;
379 if (size-pos < count) count = cast(size_t)(size-pos);
380 if (fl !is null) {
381 // `FILE*`
382 if (fseeko(fl, stpos+pos, 0) < 0) return -1;
383 auto rd = fread(buf, 1, count, fl);
384 if (rd != count && (rd < 0 || ferror(fl))) rd = -1;
385 if (rd > 0) pos += rd;
386 return rd;
387 } else {
388 // std.stdio.File
389 try {
390 xfl.seek(stpos+pos, 0);
391 auto rd = xfl.rawRead(buf[0..count]);
392 pos += rd.length;
393 return (rd.length == count ? rd.length : -1);
394 } catch (Exception) {} //BAD DOGGY!
395 return -1;
400 long seek (long ofs, int whence) {
401 lock.lock();
402 scope(exit) lock.unlock();
403 if (!isOpen) return -1;
404 //TODO: overflow checks
405 switch (whence) {
406 case 0: // SEEK_SET
407 break;
408 case 1: // SEEK_CUR
409 ofs += pos;
410 break;
411 case 2: // SEEK_END
412 if (ofs > 0) ofs = 0;
413 ofs += size;
414 break;
415 default:
416 return -1;
418 if (ofs < 0) return -1;
419 if (ofs > size) ofs = size;
420 pos = cast(uint)ofs;
421 return ofs;
426 static:
427 // ////////////////////////////////////////////////////////////////////// //
428 extern(C) nothrow {
429 import core.sys.linux.stdio : cookie_io_functions_t;
430 import core.sys.posix.sys.types : ssize_t, off64_t = off_t;
432 ssize_t fcdatpkRead (void* cookie, char* buf, size_t count) {
433 //{ import iv.writer; writeln("reading ", count, " bytes"); }
434 import core.stdc.errno;
435 auto fc = cast(InnerFileCookied*)cookie;
436 auto res = fc.read(buf, count);
437 if (res < 0) { errno = EIO; return -1; }
438 return res;
441 ssize_t fcdatpkWrite (void* cookie, const(char)* buf, size_t count) {
442 //{ import iv.writer; writeln("writing ", count, " bytes"); }
443 import core.stdc.errno;
444 errno = EIO; //FIXME: find better code
445 return 0; // error; write should not return `-1`
448 int fcdatpkSeek (void* cookie, off64_t* offset, int whence) {
449 //{ import iv.writer; writeln("seeking ", *offset, " bytes, whence=", whence); }
450 import core.stdc.errno;
451 auto fc = cast(InnerFileCookied*)cookie;
452 auto res = fc.seek(*offset, whence);
453 if (res < 0) { errno = EIO; return -1; }
454 *offset = cast(off64_t)res;
455 return 0;
458 int fcdatpkClose (void* cookie) {
459 import core.memory : GC;
460 import core.stdc.stdlib : free;
461 //{ import iv.writer; writeln("closing"); }
462 auto fc = cast(InnerFileCookied*)cookie;
463 //fc.close();
464 GC.removeRange(cookie);
465 try { fc.__dtor(); } catch (Exception) {}
466 // no need to run finalizers, we SHOULD NOT have any
467 //try { GC.runFinalizers(cookie[0..InnerFileCookied.sizeof]); } catch (Exception) {}
468 //fc.xfl.__dtor();
469 free(cookie);
470 //{ import iv.writer; writeln("closed"); }
471 return 0;
475 __gshared cookie_io_functions_t fcdatpkCallbacks = cookie_io_functions_t(
476 /*.read =*/ &fcdatpkRead,
477 /*.write =*/ &fcdatpkWrite,
478 /*.seek =*/ &fcdatpkSeek,
479 /*.close =*/ &fcdatpkClose,
482 static:
483 T[] xalloc(T) (size_t len) {
484 import core.stdc.stdlib : malloc;
485 if (len < 1) return null;
486 auto res = cast(T*)malloc(len*T.sizeof);
487 if (res is null) {
488 import core.exception : onOutOfMemoryErrorNoGC;
489 onOutOfMemoryErrorNoGC();
491 res[0..len] = T.init;
492 return res[0..len];
495 void xfree(T) (ref T[] slc) {
496 if (slc.ptr !is null) {
497 import core.stdc.stdlib : free;
498 free(slc.ptr);
500 slc = null;