PageRenderTime 67ms CodeModel.GetById 16ms app.highlight 46ms RepoModel.GetById 1ms app.codeStats 0ms

/resource/resource.d

http://github.com/wilkie/djehuty
D | 534 lines | 265 code | 150 blank | 119 comment | 55 complexity | ecbe240d03836862ac85d8a467b5569c MD5 | raw file
  1/*
  2 * resource.d
  3 *
  4 * This file has the logic behind loading a resource from a resource file or
  5 * a resource stream.
  6 *
  7 * Author: Dave Wilkinson
  8 *
  9 */
 10
 11module resource.resource;
 12
 13import core.string;
 14import core.endian;
 15import core.stream;
 16import core.unicode;
 17import core.definitions;
 18
 19import resource.image;
 20import resource.menu;
 21
 22import io.file;
 23import io.console;
 24
 25static const bool flagCheckForMenuRecursion = true;
 26
 27// Section: Core/Resources
 28
 29// Description: This class facilitates the retreival of compiled resources from a Djehuty resource file (rsc).  The file allows multiple languages and is designed to be efficient and fast.  The resources include Strings and Images at this time.  The strings are stored with a language specific Unicode format.  The resource file allows an application to support multiple locales which can be switched on the fly.  The default language will always be the current locale unless it is missing from the resource file, in which case the first language listed in the file will be the default language upon a file load.
 30
 31// Feature: You can have more than one resource file in use. (Flexibility)
 32// Feature: You can either have a resource file for each language, or one file for all languages. (Flexibility)
 33// Feature: All languages can be supported eventually. (Scalablity)
 34// Feature: Supports a resource file up to 4 gigabytes, supports a file with at least 1000 languages! (Scalablity)
 35class Resource {
 36
 37	// Description: Will create the object and then load the file specified.
 38	// filename: The path and filename of the resource file.
 39	this(string filename) {
 40		open(filename);
 41	}
 42
 43	// Description: Will create the object using the specified array as the resource database. This is for static resource information stored within the executable.
 44	// fromArray: The byte array to stream from.
 45	this(ubyte[] fromArray) {
 46		_file = new Stream(fromArray);
 47
 48		_stream();
 49	}
 50
 51	// Methods //
 52
 53	// Description: Will open the filename specified.
 54	// filename: The path and filename of the resource file.
 55	void open(string filename) {
 56		_filename = filename.dup;
 57
 58		_open();
 59	}
 60
 61	// Description: Will close the file.
 62	void close() {
 63		_file = null;
 64	}
 65
 66	// Description: Will set the current language to the one given by the parameter if it exists within the file.
 67	// langID: The standard language ID for the language you wish to use.
 68	void language(uint langID) {
 69		// check for validity
 70		if (_file is null) { return; }
 71
 72		if (_langTable is null || _langTable.length == 0) { return; }
 73
 74		foreach(int i, uint lang; _langTable) {
 75			if (lang == langID) {
 76				_langIndex = i;
 77				return;
 78			}
 79		}
 80
 81		// error: unknown language
 82		return;
 83	}
 84
 85	// -- //
 86
 87	// Description: Will traverse the resource stream and grab the String from the file.
 88	// resourceID: The specific String resource to grab.
 89	// Returns: The String resource.
 90	string loadString(uint resourceID) {
 91		Console.putln("loading string...");
 92
 93		// get the string from the file for the current language
 94		if (_file is null) { return null; }
 95
 96		if (resourceID > _stringOffsets.length) { return null; }
 97
 98		if (_stringAccessed[resourceID] !is null) { return _stringAccessed[resourceID]; }
 99
100		_file.rewind();
101		//Console.putln("cur pos: ", _file.getPosition());
102
103		ulong skipLen = void;
104		uint langOffset = void;
105
106		// Get the language offset we need!
107
108		if (_langIndex > 0) {
109			skipLen = _stringOffsets[resourceID];
110			skipLen += uint.sizeof * (_langIndex-1);
111			//Console.putln("skip: ", skipLen);
112			_file.skip(skipLen);
113
114			_file.read(langOffset);
115			skipLen = langOffset - skipLen - 4;
116			//Console.putln("skip: ", skipLen);
117		}
118		else {
119			skipLen = _stringOffsets[resourceID];
120			skipLen += uint.sizeof * (_numLang-1);
121			//Console.putln("skip: ", skipLen);
122		}
123
124		_file.skip(skipLen);
125
126		// read in string length
127
128		uint stringLen;
129
130		//Console.putln("cur pos: ", _file.getPosition());
131
132		_file.read(stringLen);
133
134		//Console.putln("string length: ", stringLen);
135
136		dchar stringarr[] = new dchar[stringLen];
137
138		_file.read(stringarr.ptr, stringLen * 4);
139
140		string s = Unicode.toUtf8(stringarr);
141
142		_stringAccessed[resourceID] = s;
143
144		return s;
145	}
146
147	// Description: Will traverse the resource stream and grab the Image from the file.
148	// resourceID: The specific Image resource to grab.
149	// Returns: The decoded Image resource.
150	Image loadImage(uint resourceID) {
151		Console.putln("loading image...");
152
153		// get the image from the file
154		if (_file is null) { return null; }
155
156		if (resourceID > _imageOffsets.length) { return null; }
157
158		if (_imageAccessed[resourceID] !is null) { return _imageAccessed[resourceID]; }
159
160		_file.rewind();
161		//Console.putln("cur pos: ", _file.getPosition());
162
163		ulong skipLen = void;
164		uint langOffset = void;
165
166		// Get the language offset we need!
167
168		if (_langIndex > 0) {
169			skipLen = _imageOffsets[resourceID];
170			skipLen += uint.sizeof * (_langIndex-1);
171			//Console.putln("skip: ", skipLen);
172			_file.skip(skipLen);
173
174			_file.read(langOffset);
175			skipLen = langOffset - skipLen - 4;
176			//Console.putln("skip: ", skipLen);
177		}
178		else {
179			skipLen = _imageOffsets[resourceID];
180			skipLen += uint.sizeof * (_numLang-1);
181			//Console.putln("skip: ", skipLen);
182		}
183
184		_file.skip(skipLen);
185
186		// read in string length
187
188		uint fileLen;
189
190		//Console.putln("cur pos: ", _file.getPosition());
191
192		_file.read(fileLen);
193
194		//Console.putln("image file length: ", fileLen);
195
196		Image img = new Image();
197
198		img.load(_file);
199
200		_imageAccessed[resourceID] = img;
201
202		return img;
203	}
204
205	// Description: Will traverse the resource stream and grab the Menu and any associated Menu classes from the file.
206	// resourceID: The specific Menu resource to grab.
207	// Returns: The Menu resource with any sub menus it needs allocated and appended.
208	Menu loadMenu(uint resourceID) {
209		Console.putln("loading menu...");
210
211		return _loadMenu(resourceID, null);
212	}
213
214private:
215
216	string _filename;
217
218	Stream _file;
219
220	uint _langIndex;
221	uint _curVersion;
222	uint _numLang;
223
224	uint _langTable[]; // the languages supported by the file
225
226	uint _sectionOffsets[3]; // position in stream of the resource section
227	uint _sectionCounts[3]; // number of resources of each resource section
228
229	uint _stringOffsets[]; // position in the stream for the string resources
230	uint _imageOffsets[]; // position in the stream for the string resources
231	uint _menuOffsets[]; // position in the stream for the menu resources
232
233	string _stringAccessed[];	// stores menus already pulled
234	Image _imageAccessed[];	// stores menus already pulled
235	Menu _menuAccessed[];	// stores menus already pulled
236
237	// hidden function for grabbing a menu
238	Menu _loadMenu(uint resourceID, uint[] subMenuIds) {
239		// get the menu from the file
240		if (_file is null) { return null; }
241
242		if (resourceID > _menuOffsets.length) { return null; }
243
244		if (_menuAccessed[resourceID] !is null) { return _menuAccessed[resourceID]; }
245
246		_file.rewind();
247		//Console.putln("cur pos: ", _file.getPosition());
248
249		ulong skipLen = void;
250		uint langOffset = void;
251
252		// Get the language offset we need!
253
254		if (_langIndex > 0) {
255			skipLen = _menuOffsets[resourceID];
256			skipLen += uint.sizeof * (_langIndex-1);
257			//Console.putln("skip: ", skipLen);
258			_file.skip(skipLen);
259
260			_file.read(langOffset);
261			skipLen = langOffset - skipLen - 4;
262			//Console.putln("skip: ", skipLen);
263		}
264		else {
265			skipLen = _menuOffsets[resourceID];
266			skipLen += uint.sizeof * (_numLang-1);
267			//Console.putln("skip: ", skipLen);
268		}
269
270		_file.skip(skipLen);
271
272		// read in string length
273
274		//Console.putln("cur pos: ", _file.getPosition());
275
276		uint stringID;
277		uint flags;
278		uint imgID;
279		uint x, y, w, h;
280		uint numSubMenus;
281
282		uint[] subMenuIDs;
283		Menu[] subMenus;
284
285		_file.read(stringID);
286		_file.read(flags);
287		_file.read(imgID);
288		_file.read(x);
289		_file.read(y);
290		_file.read(w);
291		_file.read(h);
292
293		_file.read(numSubMenus);
294
295		//Console.putln("menu string id: ", stringID);
296		//Console.putln("menu flags: ", flags);
297		//Console.putln("menu imgID: ", imgID, " ( ", x, ", ", y, " : ", w, " x ", h, " ) ");
298		//Console.putln("menu submenu count: ", numSubMenus);
299
300		// for every submenu, read in the id
301		// then call this function recursively
302		// when it is called recursively, the
303		// stream's current position is invalidated
304		// so we do this AFTER we read in the menu
305		// IDs.
306
307		uint mnuCount = 0;
308
309		if (numSubMenus > 0) {
310			// Do not allow recursive reading. (Security)
311			// There is a lot of code devoted to avoiding this DoS style
312			// attack from editing a resource file.
313
314			subMenuIDs = new uint[numSubMenus];
315			subMenus = new Menu[numSubMenus];
316
317			_file.read(subMenuIDs.ptr, uint.sizeof * numSubMenus);
318
319			// now for each, call this function
320			foreach(uint i, mnuID; subMenuIDs) {
321				if (subMenuIds !is null && flagCheckForMenuRecursion) {
322					uint j = subMenuIds.length;
323
324					foreach(uint z, mnuID_old; subMenuIds) {
325						if (mnuID_old == subMenuIDs[i]) {
326							j = z;
327							break;
328						}
329					}
330
331					if (j == subMenuIds.length) {
332						subMenus[mnuCount] = _loadMenu(mnuID, subMenuIDs ~ subMenuIds);
333						mnuCount++;
334					}
335					// else: recursive menu found
336				}
337				else {
338					subMenus[mnuCount] = _loadMenu(mnuID, subMenuIDs);
339					mnuCount++;
340				}
341			}
342		}
343
344		// getting the string invalidates the file
345		// stream's current location
346		string mnuString = loadString(stringID);
347
348		Menu mnu;
349
350		if (mnuCount > 0) {
351			if (subMenus.length > mnuCount) {
352				subMenus = subMenus[0..mnuCount];
353			}
354			mnu = new Menu(mnuString, subMenus);
355		}
356		else {
357			mnu = new Menu(mnuString);
358		}
359
360		_menuAccessed[resourceID] = mnu;
361
362		return mnu;
363	}
364
365	// hidden function, opens and parses always relevant data
366	void _open() {
367		_file = new File(_filename);
368
369		_stream();
370	}
371
372	void _stream() {
373		// read in the headers
374		uint magic;
375
376		_file.read(magic);
377		if (FromLittleEndian32(magic) != 0x53524a44) {
378			// error: not a valid resource file
379			Console.putln("error: not a valid resource file");
380			return;
381		}
382
383		//Console.putln("magic: DJRS");
384
385		// read in version information
386		if (!_file.read(_curVersion)) {
387			// error: version expected, EOF found
388			Console.putln("error: version expected, EOF found");
389			return;
390		}
391
392		//Console.putln("version: ", _curVersion);
393
394		// read in version information
395		if (!_file.read(_numLang)) {
396			// error: number of languages expected, EOF found
397			Console.putln("error: number of languages expected, EOF found");
398			return;
399		}
400
401		//Console.putln("num languages: ", _numLang);
402
403		_file.skip(4);
404
405		if (_numLang > 1000) {
406			// error: over maximum language limit
407			Console.putln("error: over maximum language limit");
408			return;
409		}
410
411		_langTable = new uint[_numLang];
412
413		// read in the language table
414		if (!_file.read(_langTable.ptr, uint.sizeof * _numLang)) {
415			// error: language table expected, EOF found
416			Console.putln("error: language table expected, EOF found");
417			return;
418		}
419
420		//Console.putln("languages: ", _langTable);
421
422		// read in the section counts
423		if (!_file.read(_sectionCounts.ptr, uint.sizeof * _sectionOffsets.length)) {
424			// error: section count table expected, EOF found
425			Console.putln("error: section count table expected, EOF found");
426			return;
427		}
428
429		//Console.putln("section counts: ", _sectionCounts);
430
431		// read in the section offsets
432		if (!_file.read(_sectionOffsets.ptr, uint.sizeof * _sectionOffsets.length)) {
433			// error: section offset table expected, EOF found
434			Console.putln("error: section offset table expected, EOF found");
435			return;
436		}
437
438		//Console.putln("section offsets: ", _sectionOffsets);
439
440
441
442		/* STRINGS */
443
444
445
446		// load string section information
447		if (_sectionCounts[0] > 0) {
448			_stringOffsets = new uint[_sectionCounts[0]];
449			_stringAccessed = new string[_sectionCounts[0]];
450		}
451
452		// read in the string offsets
453		if (_sectionCounts[0] > 1 || _sectionOffsets[0] > 0) {
454			if (!_file.read((_stringOffsets.ptr + 1), uint.sizeof * (_sectionCounts[0]-1))) {
455				// error: string offset table expected, EOF found
456				Console.putln("error: string offset table expected, EOF found");
457				return;
458			}
459		}
460
461		if (_sectionCounts[0] > 0) {
462			_stringOffsets[0] = cast(uint)_file.position;
463		}
464
465		// skip to the image section
466
467		_file.skip(_sectionOffsets[1] - _file.position);
468
469
470
471
472		/* IMAGES */
473
474
475
476
477		// load image section information
478		if (_sectionCounts[1] > 0) {
479			_imageOffsets = new uint[_sectionCounts[1]];
480			_imageAccessed = new Image[_sectionCounts[1]];
481		}
482
483		// read in the image offsets
484		if (_sectionCounts[1] > 0 || _sectionOffsets[1] > 0) {
485			if (!_file.read((_imageOffsets.ptr + 1), uint.sizeof * (_sectionCounts[1]-1))) {
486				// error: image offset table expected, EOF found
487				Console.putln("error: image offset table expected, EOF found");
488				return;
489			}
490		}
491
492		if (_sectionCounts[1] > 0) {
493			_imageOffsets[0] = cast(uint)_file.position;
494		}
495
496		//Console.putln(_imageOffsets);
497		// skip to the menu section
498
499		_file.skip(_sectionOffsets[2] - _file.position);
500
501		//Console.putln(_sectionOffsets[2] - _file.getPosition());
502
503
504		/* MENUS */
505
506
507
508		// load menu section information
509		if (_sectionCounts[2] > 0) {
510			_menuOffsets = new uint[_sectionCounts[2]];
511			_menuAccessed = new Menu[_sectionCounts[2]];
512		}
513
514		// read in the menu offsets
515		if (_sectionCounts[2] > 0 || _sectionOffsets[2] > 0) {
516			if (!_file.read((_menuOffsets.ptr + 1), uint.sizeof * (_sectionCounts[2]-1))) {
517				// error: image offset table expected, EOF found
518				Console.putln("error: image offset table expected, EOF found");
519				return;
520			}
521		}
522
523		if (_sectionCounts[2] > 0) {
524			_menuOffsets[0] = cast(uint)_file.position;
525		}
526
527
528		//Console.putln(_menuOffsets);
529
530
531		// set _langID to the first language (default)
532		_langIndex = 0;
533	}
534}