1 /* ***** BEGIN LICENSE BLOCK *****
2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 * The contents of this file are subject to the Mozilla Public License Version
5 * 1.1 (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
7 * http://www.mozilla.org/MPL/
9 * Software distributed under the License is distributed on an "AS IS" basis,
10 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 * for the specific language governing rights and limitations under the
14 * The Original Code is Syndrome.
16 * The Initial Developer of the Original Code is
17 * Jason Oster <parasyte at kodewerx org>.
18 * Portions created by the Initial Developer are Copyright (C) 2009
19 * the Initial Developer. All Rights Reserved.
23 * Alternatively, the contents of this file may be used under the terms of
24 * either the GNU General Public License Version 2 or later (the "GPL"), or
25 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
26 * in which case the provisions of the GPL or the LGPL are applicable instead
27 * of those above. If you wish to allow use of your version of this file only
28 * under the terms of either the GPL or the LGPL, and not to allow others to
29 * use your version of this file under the terms of the MPL, indicate your
30 * decision by deleting the provisions above and replace them with the notice
31 * and other provisions required by the GPL or the LGPL. If you do not delete
32 * the provisions above, a recipient may use your version of this file under
33 * the terms of any one of the MPL, the GPL or the LGPL.
35 * ***** END LICENSE BLOCK ***** */
39 * defaultTiles is an array of SNES CHR tiles;
40 * one solid tile for each of the SNES palette's 16 colors
41 * The stage graphics set is appended to this array after decompressing
43 const defaultTiles = [
44 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
45 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
46 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
47 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
48 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
49 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
50 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
51 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
52 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
53 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
54 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
55 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
56 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
57 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
58 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
59 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
60 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
61 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
62 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
63 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
64 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
65 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
66 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
67 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
68 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
69 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
70 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
71 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
72 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
73 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
74 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
75 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
76 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
77 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
78 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
79 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
80 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
81 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
82 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
83 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
84 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
85 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
86 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
87 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
88 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
89 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
90 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
91 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
92 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
93 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
94 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
95 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
96 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
97 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
98 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
99 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
100 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
101 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
102 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
103 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
104 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
105 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
106 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
107 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
110 // Mega Man 7 Editor constructor
111 function mm7Editor(filepath, data, canvas) {
112 this.snesROM(filepath, data);
116 this.canvas = canvas;
120 // Mega Man 7 pointer constants
121 mm7Editor.PTR_PALDATA = 0xC08456; // Pointers to palette data
122 mm7Editor.PTR_ROOMDATA = 0xC0914F; // Pointers to room data
124 mm7Editor.PTR_ROOMSIZE = 0xC09179; // Pointers to room data sizes
126 mm7Editor.PTR_FGMAPDATA = 0xC09195; // Pointers to foreground map data
127 mm7Editor.PTR_BGMAPDATA = 0xC091B1; // Pointers to background map data
128 mm7Editor.PTR_BLOCKDATA = 0xC091CD; // Pointers to block data
130 mm7Editor.PTR_TYPEDATA = 0xC091F7; // Pointers to "block\structure type" data
132 mm7Editor.PTR_CMPDATA = 0xC0E993; // Pointers to compressed data
133 mm7Editor.PTR_STAGEHEAD = 0xC0ED2B; // Pointers to stage headers
134 mm7Editor.PTR_STAGEPAL = 0xC0FB04; // Pointers to stage palette headers
135 mm7Editor.PTR_STRUCTDATA = 0xCA5600; // Pointers to structure data
139 mm7Editor.ERR_DECOMP = -2; // Decompression error
140 mm7Editor.ERR_GEN = -1; // General error
141 mm7Editor.ERR_NONE = 0; // Success/no error
144 // Mega Man 7 Editor class
145 mm7Editor.prototype = {
146 // SNES tile map: Background
149 // SNES tile map: Foreground
155 // Destination canvas
159 // Data decompression
160 decompress: function(index, length) {
161 jsdump('decompress(' + index.toHex() + ', ' + length.toHex() + ')');
162 if ((index < 0) || (index > 183)) {
163 jsdump('decompress(): Index out of range: ' + index);
164 return mm7Editor.ERR_DECOMP;
167 var ptr = (mm7Editor.PTR_CMPDATA + (index * 5));
169 var source = this.getData24(ptr + 0);
170 var size = this.getData16(ptr + 3);
172 // Error if the size we are given does not match the size in the compressed data header.
173 if (size != length) {
174 jsdump('decompress(): Size error: ' + length + ', ' + size);
175 return mm7Editor.ERR_DECOMP;
178 var output = new Array(size);
179 var overflow = new Array();
183 var ctrl = this.getData8(source++);
185 for (var i = 0; i < 8; i++) {
187 var len = (this.getData8(source) >> 2); // Length of data to copy from moving window
188 var win = (((this.getData8(source) & 3) << 8) | this.getData8(source + 1)); // LZ window source
191 if (out < size) output[out] = output[out - win];
192 else overflow[out - size] = output[out - win];
197 if (out < size) output[out] = this.getData8(source);
198 else overflow[out - size] = this.getData8(source);
205 jsdump('decompress(): Decompressed data size: ' + size.toHex());
208 for (i = 0; i < overflow.length; i++) {
209 overstr += overflow[i].toHex() + ' ';
211 jsdump('decompress(): overflow[' + overflow.length.toHex() + ']: ' + overstr);
217 // Load a palette index
218 loadPal: function(index) {
219 var pal = new Array(128);
221 var ptr = (0x00C00000 | this.getData16(mm7Editor.PTR_PALDATA + (index * 2)));
222 /* Note: ptr now points to palette headers
224 * byte-1 & byte-2: pointer to palette data
225 * byte-3: destination for this palette
229 while ((len = this.getData8(ptr++))) {
230 var src = (0x00C40000 | this.getData16(ptr));
233 var dst = this.getData8(ptr++);
234 for (var i = dst; i < (dst + len); i++) {
235 pal[i] = this.getData16(src);
243 // Load stage palette with sub-palette (used for swapping the palette mid-stage)
244 loadStagePal: function(subpal) {
245 if ((this.stage < 0) || (this.stage > 13)) return mm7Editor.ERR_GEN;
247 var ptr = (mm7Editor.PTR_STAGEPAL + this.getData16(mm7Editor.PTR_STAGEPAL + (this.stage * 2)) + (subpal * 3));
248 /* Note: ptr now points to the sub-palette header
249 * byte-0: palette index
250 * byte-1 & byte-2: background color (transparency)
252 var index = this.getData8(ptr++);
253 var pal = this.loadPal(index);
254 pal[0] = this.getData16(ptr);
259 // Load a tile set index
260 loadTileSet: function(index) {
261 ptr = (0x00C00000 | this.getData16(mm7Editor.PTR_STAGEHEAD + ((this.stage + 0x10) * 2)));
262 /* Note: ptr now points to the stage graphics header:
263 * byte-0: Compression index
264 * byte-1 & byte-2: Decompressed data size
269 var cmp_index = this.getData8 (ptr + 0);
270 var cmp_length = this.getData16(ptr + 1);
271 var cmp_dest = this.getData16(ptr + 3);
273 // FIXME: Verify this is the correct way to handle this case ... works for Spring Man's stage
274 var tiles = defaultTiles.slice(0);
275 if (cmp_dest == 0x1100) {
279 jsdump('buildStage(): Adjusting tile size to ' + cmp_dest.toHex() + ' bytes');
280 for (var i = 0x0200; i < cmp_dest; i++) {
284 tiles.merge(cmp_dest, this.decompress(cmp_index, cmp_length));
289 // Build stage images, tile maps, palettes, etc.
290 buildStage: function(stage) {
291 jsdump('buildStage(' + stage + ')');
292 if ((stage < 0) || (stage > 13)) {
293 jsdump('buildstage(): Stage error: ' + stage);
294 return mm7Editor.ERR_GEN;
301 // Get foreground width and height
302 ptr = (0x00C10000 | this.getData16(mm7Editor.PTR_FGMAPDATA + (this.stage * 2)));
303 /* Note: ptr now points to the stage room data
304 * byte-0: stage width (in rooms)
305 * byte-1: stage height (in rooms)
306 * byte-2 to byte-n: room numbers (where n = (width * height) + 1)
308 var w = (this.getData8(ptr++) * 32); // Each room is 32 tiles wide
309 var h = (this.getData8(ptr++) * 32); // and 32 tiles high
312 this.fgmap = new snesTileMap(this.canvas, w, h, this.loadTileSet(0), this.loadStagePal(0));
315 for (var y = 0; y < h; y += 32) { // Each room is 32 tiles high
316 for (var x = 0; x < w; x += 32) { // and 32 tiles wide
317 this.buildRoom(this.fgmap, x, y, this.getData8(ptr++));
322 // Get background width and height
323 ptr = (0x00C10000 | this.getData16(mm7Editor.PTR_BGMAPDATA + (this.stage * 2)));
324 /* Note: ptr now points to the stage room data
325 * byte-0: stage width (in rooms)
326 * byte-1: stage height (in rooms)
327 * byte-2 to byte-n: room numbers (where n = (width * height) + 1)
329 w = (this.getData8(ptr++) * 32); // Each room is 32 tiles wide
330 h = (this.getData8(ptr++) * 32); // and 32 tiles high
333 this.bgmap = new snesTileMap(this.canvas, w, h, this.loadTileSet(0), this.loadStagePal(0));
336 for (var y = 0; y < h; y += 32) { // Each room is 32 tiles high
337 for (var x = 0; x < w; x += 32) { // and 32 tiles wide
338 this.buildRoom(this.bgmap, x, y, this.getData8(ptr++));
342 // Render background, then foreground
343 // this.bgmap.render(this.canvas);
344 // this.fgmap.render(this.canvas, true);
346 // Resize canvas element
347 this.canvas.setAttribute('width', this.fgmap.w * 8);
348 this.canvas.setAttribute('height', this.fgmap.h * 8);
350 // Render only foreground
351 // FIXME: Try doing putImageData after drawing every 256x256 pixel 'room': save memory, and probably feel like it's running faster?
352 var ctx = this.canvas.getContext('2d');
353 ctx.putImageData(this.fgmap.render(), 0, 0, 0, 0, (this.fgmap.w * 8), (this.fgmap.h * 8));
356 return mm7Editor.ERR_NONE;
359 // Build a single room (made of 8x8 structures)
360 buildRoom: function(map, x, y, num) {
361 var ptr = (this.getData24(mm7Editor.PTR_ROOMDATA + (this.stage * 3)) + (num * 128)); // 128: each room is 8*8 blocks (each block is 2 bytes)
369 this.buildStruct(map, x, y, this.getData16(ptr));
377 // Build a single structure (made of 2x2 blocks)
378 buildStruct: function(map, x, y, num) {
379 var ptr = (mm7Editor.PTR_STRUCTDATA + this.getData24(mm7Editor.PTR_STRUCTDATA + (this.stage * 3)) + num);
387 this.buildBlock(map, x, y, this.getData16(ptr));
395 // Build a single block (made of 2x2 tiles)
396 buildBlock: function(map, x, y, num) {
397 var ptr = (this.getData24(mm7Editor.PTR_BLOCKDATA + (this.stage * 3)) + (num * 8)); // 8: each block is 2*2 tiles (each tile is 2 bytes)
405 map.map[x + (y * map.w)] = this.getData16(ptr);
415 // Voodoo to inherit from the snesROM object
416 mm7Editor.inherit(snesROM);