PageRenderTime 45ms CodeModel.GetById 8ms RepoModel.GetById 0ms 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. module resource.resource;
  11. import core.string;
  12. import core.endian;
  13. import core.stream;
  14. import core.unicode;
  15. import core.definitions;
  16. import resource.image;
  17. import resource.menu;
  18. import io.file;
  19. import io.console;
  20. static const bool flagCheckForMenuRecursion = true;
  21. // Section: Core/Resources
  22. // 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.
  23. // Feature: You can have more than one resource file in use. (Flexibility)
  24. // Feature: You can either have a resource file for each language, or one file for all languages. (Flexibility)
  25. // Feature: All languages can be supported eventually. (Scalablity)
  26. // Feature: Supports a resource file up to 4 gigabytes, supports a file with at least 1000 languages! (Scalablity)
  27. class Resource {
  28. // Description: Will create the object and then load the file specified.
  29. // filename: The path and filename of the resource file.
  30. this(string filename) {
  31. open(filename);
  32. }
  33. // Description: Will create the object using the specified array as the resource database. This is for static resource information stored within the executable.
  34. // fromArray: The byte array to stream from.
  35. this(ubyte[] fromArray) {
  36. _file = new Stream(fromArray);
  37. _stream();
  38. }
  39. // Methods //
  40. // Description: Will open the filename specified.
  41. // filename: The path and filename of the resource file.
  42. void open(string filename) {
  43. _filename = filename.dup;
  44. _open();
  45. }
  46. // Description: Will close the file.
  47. void close() {
  48. _file = null;
  49. }
  50. // Description: Will set the current language to the one given by the parameter if it exists within the file.
  51. // langID: The standard language ID for the language you wish to use.
  52. void language(uint langID) {
  53. // check for validity
  54. if (_file is null) { return; }
  55. if (_langTable is null || _langTable.length == 0) { return; }
  56. foreach(int i, uint lang; _langTable) {
  57. if (lang == langID) {
  58. _langIndex = i;
  59. return;
  60. }
  61. }
  62. // error: unknown language
  63. return;
  64. }
  65. // -- //
  66. // Description: Will traverse the resource stream and grab the String from the file.
  67. // resourceID: The specific String resource to grab.
  68. // Returns: The String resource.
  69. string loadString(uint resourceID) {
  70. Console.putln("loading string...");
  71. // get the string from the file for the current language
  72. if (_file is null) { return null; }
  73. if (resourceID > _stringOffsets.length) { return null; }
  74. if (_stringAccessed[resourceID] !is null) { return _stringAccessed[resourceID]; }
  75. _file.rewind();
  76. //Console.putln("cur pos: ", _file.getPosition());
  77. ulong skipLen = void;
  78. uint langOffset = void;
  79. // Get the language offset we need!
  80. if (_langIndex > 0) {
  81. skipLen = _stringOffsets[resourceID];
  82. skipLen += uint.sizeof * (_langIndex-1);
  83. //Console.putln("skip: ", skipLen);
  84. _file.skip(skipLen);
  85. _file.read(langOffset);
  86. skipLen = langOffset - skipLen - 4;
  87. //Console.putln("skip: ", skipLen);
  88. }
  89. else {
  90. skipLen = _stringOffsets[resourceID];
  91. skipLen += uint.sizeof * (_numLang-1);
  92. //Console.putln("skip: ", skipLen);
  93. }
  94. _file.skip(skipLen);
  95. // read in string length
  96. uint stringLen;
  97. //Console.putln("cur pos: ", _file.getPosition());
  98. _file.read(stringLen);
  99. //Console.putln("string length: ", stringLen);
  100. dchar stringarr[] = new dchar[stringLen];
  101. _file.read(stringarr.ptr, stringLen * 4);
  102. string s = Unicode.toUtf8(stringarr);
  103. _stringAccessed[resourceID] = s;
  104. return s;
  105. }
  106. // Description: Will traverse the resource stream and grab the Image from the file.
  107. // resourceID: The specific Image resource to grab.
  108. // Returns: The decoded Image resource.
  109. Image loadImage(uint resourceID) {
  110. Console.putln("loading image...");
  111. // get the image from the file
  112. if (_file is null) { return null; }
  113. if (resourceID > _imageOffsets.length) { return null; }
  114. if (_imageAccessed[resourceID] !is null) { return _imageAccessed[resourceID]; }
  115. _file.rewind();
  116. //Console.putln("cur pos: ", _file.getPosition());
  117. ulong skipLen = void;
  118. uint langOffset = void;
  119. // Get the language offset we need!
  120. if (_langIndex > 0) {
  121. skipLen = _imageOffsets[resourceID];
  122. skipLen += uint.sizeof * (_langIndex-1);
  123. //Console.putln("skip: ", skipLen);
  124. _file.skip(skipLen);
  125. _file.read(langOffset);
  126. skipLen = langOffset - skipLen - 4;
  127. //Console.putln("skip: ", skipLen);
  128. }
  129. else {
  130. skipLen = _imageOffsets[resourceID];
  131. skipLen += uint.sizeof * (_numLang-1);
  132. //Console.putln("skip: ", skipLen);
  133. }
  134. _file.skip(skipLen);
  135. // read in string length
  136. uint fileLen;
  137. //Console.putln("cur pos: ", _file.getPosition());
  138. _file.read(fileLen);
  139. //Console.putln("image file length: ", fileLen);
  140. Image img = new Image();
  141. img.load(_file);
  142. _imageAccessed[resourceID] = img;
  143. return img;
  144. }
  145. // Description: Will traverse the resource stream and grab the Menu and any associated Menu classes from the file.
  146. // resourceID: The specific Menu resource to grab.
  147. // Returns: The Menu resource with any sub menus it needs allocated and appended.
  148. Menu loadMenu(uint resourceID) {
  149. Console.putln("loading menu...");
  150. return _loadMenu(resourceID, null);
  151. }
  152. private:
  153. string _filename;
  154. Stream _file;
  155. uint _langIndex;
  156. uint _curVersion;
  157. uint _numLang;
  158. uint _langTable[]; // the languages supported by the file
  159. uint _sectionOffsets[3]; // position in stream of the resource section
  160. uint _sectionCounts[3]; // number of resources of each resource section
  161. uint _stringOffsets[]; // position in the stream for the string resources
  162. uint _imageOffsets[]; // position in the stream for the string resources
  163. uint _menuOffsets[]; // position in the stream for the menu resources
  164. string _stringAccessed[]; // stores menus already pulled
  165. Image _imageAccessed[]; // stores menus already pulled
  166. Menu _menuAccessed[]; // stores menus already pulled
  167. // hidden function for grabbing a menu
  168. Menu _loadMenu(uint resourceID, uint[] subMenuIds) {
  169. // get the menu from the file
  170. if (_file is null) { return null; }
  171. if (resourceID > _menuOffsets.length) { return null; }
  172. if (_menuAccessed[resourceID] !is null) { return _menuAccessed[resourceID]; }
  173. _file.rewind();
  174. //Console.putln("cur pos: ", _file.getPosition());
  175. ulong skipLen = void;
  176. uint langOffset = void;
  177. // Get the language offset we need!
  178. if (_langIndex > 0) {
  179. skipLen = _menuOffsets[resourceID];
  180. skipLen += uint.sizeof * (_langIndex-1);
  181. //Console.putln("skip: ", skipLen);
  182. _file.skip(skipLen);
  183. _file.read(langOffset);
  184. skipLen = langOffset - skipLen - 4;
  185. //Console.putln("skip: ", skipLen);
  186. }
  187. else {
  188. skipLen = _menuOffsets[resourceID];
  189. skipLen += uint.sizeof * (_numLang-1);
  190. //Console.putln("skip: ", skipLen);
  191. }
  192. _file.skip(skipLen);
  193. // read in string length
  194. //Console.putln("cur pos: ", _file.getPosition());
  195. uint stringID;
  196. uint flags;
  197. uint imgID;
  198. uint x, y, w, h;
  199. uint numSubMenus;
  200. uint[] subMenuIDs;
  201. Menu[] subMenus;
  202. _file.read(stringID);
  203. _file.read(flags);
  204. _file.read(imgID);
  205. _file.read(x);
  206. _file.read(y);
  207. _file.read(w);
  208. _file.read(h);
  209. _file.read(numSubMenus);
  210. //Console.putln("menu string id: ", stringID);
  211. //Console.putln("menu flags: ", flags);
  212. //Console.putln("menu imgID: ", imgID, " ( ", x, ", ", y, " : ", w, " x ", h, " ) ");
  213. //Console.putln("menu submenu count: ", numSubMenus);
  214. // for every submenu, read in the id
  215. // then call this function recursively
  216. // when it is called recursively, the
  217. // stream's current position is invalidated
  218. // so we do this AFTER we read in the menu
  219. // IDs.
  220. uint mnuCount = 0;
  221. if (numSubMenus > 0) {
  222. // Do not allow recursive reading. (Security)
  223. // There is a lot of code devoted to avoiding this DoS style
  224. // attack from editing a resource file.
  225. subMenuIDs = new uint[numSubMenus];
  226. subMenus = new Menu[numSubMenus];
  227. _file.read(subMenuIDs.ptr, uint.sizeof * numSubMenus);
  228. // now for each, call this function
  229. foreach(uint i, mnuID; subMenuIDs) {
  230. if (subMenuIds !is null && flagCheckForMenuRecursion) {
  231. uint j = subMenuIds.length;
  232. foreach(uint z, mnuID_old; subMenuIds) {
  233. if (mnuID_old == subMenuIDs[i]) {
  234. j = z;
  235. break;
  236. }
  237. }
  238. if (j == subMenuIds.length) {
  239. subMenus[mnuCount] = _loadMenu(mnuID, subMenuIDs ~ subMenuIds);
  240. mnuCount++;
  241. }
  242. // else: recursive menu found
  243. }
  244. else {
  245. subMenus[mnuCount] = _loadMenu(mnuID, subMenuIDs);
  246. mnuCount++;
  247. }
  248. }
  249. }
  250. // getting the string invalidates the file
  251. // stream's current location
  252. string mnuString = loadString(stringID);
  253. Menu mnu;
  254. if (mnuCount > 0) {
  255. if (subMenus.length > mnuCount) {
  256. subMenus = subMenus[0..mnuCount];
  257. }
  258. mnu = new Menu(mnuString, subMenus);
  259. }
  260. else {
  261. mnu = new Menu(mnuString);
  262. }
  263. _menuAccessed[resourceID] = mnu;
  264. return mnu;
  265. }
  266. // hidden function, opens and parses always relevant data
  267. void _open() {
  268. _file = new File(_filename);
  269. _stream();
  270. }
  271. void _stream() {
  272. // read in the headers
  273. uint magic;
  274. _file.read(magic);
  275. if (FromLittleEndian32(magic) != 0x53524a44) {
  276. // error: not a valid resource file
  277. Console.putln("error: not a valid resource file");
  278. return;
  279. }
  280. //Console.putln("magic: DJRS");
  281. // read in version information
  282. if (!_file.read(_curVersion)) {
  283. // error: version expected, EOF found
  284. Console.putln("error: version expected, EOF found");
  285. return;
  286. }
  287. //Console.putln("version: ", _curVersion);
  288. // read in version information
  289. if (!_file.read(_numLang)) {
  290. // error: number of languages expected, EOF found
  291. Console.putln("error: number of languages expected, EOF found");
  292. return;
  293. }
  294. //Console.putln("num languages: ", _numLang);
  295. _file.skip(4);
  296. if (_numLang > 1000) {
  297. // error: over maximum language limit
  298. Console.putln("error: over maximum language limit");
  299. return;
  300. }
  301. _langTable = new uint[_numLang];
  302. // read in the language table
  303. if (!_file.read(_langTable.ptr, uint.sizeof * _numLang)) {
  304. // error: language table expected, EOF found
  305. Console.putln("error: language table expected, EOF found");
  306. return;
  307. }
  308. //Console.putln("languages: ", _langTable);
  309. // read in the section counts
  310. if (!_file.read(_sectionCounts.ptr, uint.sizeof * _sectionOffsets.length)) {
  311. // error: section count table expected, EOF found
  312. Console.putln("error: section count table expected, EOF found");
  313. return;
  314. }
  315. //Console.putln("section counts: ", _sectionCounts);
  316. // read in the section offsets
  317. if (!_file.read(_sectionOffsets.ptr, uint.sizeof * _sectionOffsets.length)) {
  318. // error: section offset table expected, EOF found
  319. Console.putln("error: section offset table expected, EOF found");
  320. return;
  321. }
  322. //Console.putln("section offsets: ", _sectionOffsets);
  323. /* STRINGS */
  324. // load string section information
  325. if (_sectionCounts[0] > 0) {
  326. _stringOffsets = new uint[_sectionCounts[0]];
  327. _stringAccessed = new string[_sectionCounts[0]];
  328. }
  329. // read in the string offsets
  330. if (_sectionCounts[0] > 1 || _sectionOffsets[0] > 0) {
  331. if (!_file.read((_stringOffsets.ptr + 1), uint.sizeof * (_sectionCounts[0]-1))) {
  332. // error: string offset table expected, EOF found
  333. Console.putln("error: string offset table expected, EOF found");
  334. return;
  335. }
  336. }
  337. if (_sectionCounts[0] > 0) {
  338. _stringOffsets[0] = cast(uint)_file.position;
  339. }
  340. // skip to the image section
  341. _file.skip(_sectionOffsets[1] - _file.position);
  342. /* IMAGES */
  343. // load image section information
  344. if (_sectionCounts[1] > 0) {
  345. _imageOffsets = new uint[_sectionCounts[1]];
  346. _imageAccessed = new Image[_sectionCounts[1]];
  347. }
  348. // read in the image offsets
  349. if (_sectionCounts[1] > 0 || _sectionOffsets[1] > 0) {
  350. if (!_file.read((_imageOffsets.ptr + 1), uint.sizeof * (_sectionCounts[1]-1))) {
  351. // error: image offset table expected, EOF found
  352. Console.putln("error: image offset table expected, EOF found");
  353. return;
  354. }
  355. }
  356. if (_sectionCounts[1] > 0) {
  357. _imageOffsets[0] = cast(uint)_file.position;
  358. }
  359. //Console.putln(_imageOffsets);
  360. // skip to the menu section
  361. _file.skip(_sectionOffsets[2] - _file.position);
  362. //Console.putln(_sectionOffsets[2] - _file.getPosition());
  363. /* MENUS */
  364. // load menu section information
  365. if (_sectionCounts[2] > 0) {
  366. _menuOffsets = new uint[_sectionCounts[2]];
  367. _menuAccessed = new Menu[_sectionCounts[2]];
  368. }
  369. // read in the menu offsets
  370. if (_sectionCounts[2] > 0 || _sectionOffsets[2] > 0) {
  371. if (!_file.read((_menuOffsets.ptr + 1), uint.sizeof * (_sectionCounts[2]-1))) {
  372. // error: image offset table expected, EOF found
  373. Console.putln("error: image offset table expected, EOF found");
  374. return;
  375. }
  376. }
  377. if (_sectionCounts[2] > 0) {
  378. _menuOffsets[0] = cast(uint)_file.position;
  379. }
  380. //Console.putln(_menuOffsets);
  381. // set _langID to the first language (default)
  382. _langIndex = 0;
  383. }
  384. }