/* tile editor 1.1 */ #include #include #include #define ID_Exit 1 #define ID_About 2 #define ID_Selector 3 #define ID_Open 4 #define ID_Save 5 #define ID_Palette 6 char *tileset_name[] = { "0 - Underworld", "1 - Onett", "2 - Twoson", "3 - Threed", "4 - Fourside", "5 - Magicant", "6 - Outdoors", "7 - Summers", "8 - Desert", "9 - Dalaam", "10 - Indoors 1", "11 - Indoors 2", "12 - Stores 1", "13 - Caves 1", "14 - Indoors 3", "15 - Stores 2", "16 - Indoors 4", "17 - Winters", "18 - Scaraba", "19 - Caves 2" }; char tileset[20][897][32]; char rom[4194816]; int rom_loaded; int tileset_changed[20]; int selected_tileset, selected_tile, selected_color, selected_palette; int preview[7][7]; int palstart[20] = { 0x1A7EA7, 0x1A81A7, 0x1A83E7, 0x1A86E7, 0x1A8867, 0x1A89E7, 0x1A8CE7, 0x1A9227, 0x1A92E7, 0x1A9467, 0x1A98E7, 0x1ACEE7, 0x1AC8E7, 0x1AE7A7, 0x1ADD27, 0x1AD967, 0x1AC3A7, 0x1AAAE7, 0x1AC2E7, 0x1AE327 }; int pallen[20] = { 24,18,24,12,12, 24,42,6,12,36, 336,114,48,104,48, 30,42,24,6,36 }; #define tileset_pointers ((int *)&rom[0x2F125B]) /* Used internally by comp */ void encode(unsigned char **bpos, int length, int type) { if(length > 32) { *(*bpos)++ = 0xE0 + 4 * type + ((length - 1) >> 8); *(*bpos)++ = (length - 1) & 0xFF; } else *(*bpos)++ = 0x20 * type + length - 1; } void rencode(unsigned char **bpos, unsigned char *pos, int length) { if(length <= 0) return; encode(bpos, length, 0); memcpy(*bpos, pos, length); *bpos += length; } /* The compressor function. * Takes a pointer to the uncompressed block, a pointer to 65536 bytes * which it compresses into, and the size of the uncompressed block. * Returns the number of bytes compressed. */ int comp(unsigned char *udata, unsigned char *buffer, int length) { unsigned char *bpos = buffer, *limit = &udata[length]; unsigned char *pos = udata, *pos2, *pos3, *pos4; int tmp; static unsigned char bitrevs[256]; for(tmp = 0; tmp < 256; tmp++) { unsigned char x = tmp; x = ((x >> 1) & 0x55) | ((x << 1) & 0xAA); x = ((x >> 2) & 0x33) | ((x << 2) & 0xCC); bitrevs[tmp] = ((x >> 4) & 0x0F) | ((x << 4) & 0xF0); } while(pos < limit) { /*printf("%d\t%d\n", pos - udata, bpos - buffer);*/ /* Look for patterns */ for(pos2 = pos; pos2 < limit && pos2 < pos + 1024; pos2++) { for(pos3 = pos2; pos3 < limit && pos3 < pos2 + 1024 && *pos2 == *pos3; pos3++); if(pos3 - pos2 >= 3) { rencode(&bpos, pos, pos2 - pos); encode(&bpos, pos3 - pos2, 1); *bpos++ = *pos2; pos = pos3; break; } for(pos3 = pos2; pos3 < limit && pos3 < pos2 + 2048 && *pos3 == *pos2 && pos3[1] == pos2[1]; pos3 += 2); if(pos3 - pos2 >= 6) { rencode(&bpos, pos, pos2 - pos); encode(&bpos, (pos3 - pos2) / 2, 2); *bpos++ = pos2[0]; *bpos++ = pos2[1]; pos = pos3; break; } for(tmp = 0, pos3 = pos2; pos3 < limit && pos3 < pos2 + 1024 && *pos3 == *pos2 + tmp; pos3++, tmp++); if(pos3 - pos2 >= 4) { rencode(&bpos, pos, pos2 - pos); encode(&bpos, pos3 - pos2, 3); *bpos++ = *pos2; pos = pos3; break; } for(pos3 = udata; pos3 < pos2; pos3++) { for(tmp = 0, pos4 = pos3; pos4 < pos2 && tmp < 1024 && *pos4 == pos2[tmp]; pos4++, tmp++); if(tmp >= 5) { rencode(&bpos, pos, pos2 - pos); encode(&bpos, tmp, 4); *bpos++ = (pos3 - udata) >> 8; *bpos++ = (pos3 - udata) & 0xFF; pos = pos2 + tmp; goto DONE; } for(tmp = 0, pos4 = pos3; pos4 < pos2 && tmp < 1024 && *pos4 == bitrevs[pos2[tmp]]; pos4++, tmp++); if(tmp >= 5) { rencode(&bpos, pos, pos2 - pos); encode(&bpos, tmp, 5); *bpos++ = (pos3 - udata) >> 8; *bpos++ = (pos3 - udata) & 0xFF; pos = pos2 + tmp; goto DONE; } for(tmp = 0, pos4 = pos3; pos4 >= udata && tmp < 1024 && *pos4 == pos2[tmp]; pos4--, tmp++); if(tmp >= 5) { rencode(&bpos, pos, pos2 - pos); encode(&bpos, tmp, 6); *bpos++ = (pos3 - udata) >> 8; *bpos++ = (pos3 - udata) & 0xFF; pos = pos2 + tmp; goto DONE; } } } DONE: /* Can't compress, so just use 0 (raw) */ rencode(&bpos, pos, pos2 - pos); if(pos < pos2) pos = pos2; } *bpos++ = 0xFF; return bpos - buffer; } /* The decompressor function. * Takes a pointer to the compressed block, a pointer to the buffer * which it decompresses into, . Returns the number of bytes uncompressed, * or -1 if decompression failed. */ int decomp(unsigned char *cdata, unsigned char *buffer, int maxlen) { unsigned char *bpos = buffer, *bpos2, tmp; while(*cdata != 0xFF) { int cmdtype = *cdata >> 5; int len = (*cdata & 0x1F) + 1; if(cmdtype == 7) { cmdtype = (*cdata & 0x1C) >> 2; len = ((*cdata & 3) << 8) + *(cdata + 1) + 1; cdata++; } if(bpos + len > &buffer[maxlen]) return -1; cdata++; if(cmdtype >= 4) { bpos2 = &buffer[(*cdata << 8) + *(cdata + 1)]; if(bpos2 >= &buffer[maxlen]) return -1; cdata += 2; } switch(cmdtype) { case 0: memcpy(bpos, cdata, len); cdata += len; bpos += len; break; case 1: memset(bpos, *cdata++, len); bpos += len; break; case 2: if(bpos + 2 * len > &buffer[maxlen]) return -1; while(len--) { *(short *)bpos = *(short *)cdata; bpos += 2; } cdata += 2; break; case 3: tmp = *cdata++; while(len--) *bpos++ = tmp++; break; case 4: if(bpos2 + len > &buffer[maxlen]) return -1; memcpy(bpos, bpos2, len); bpos += len; break; case 5: if(bpos2 + len > &buffer[maxlen]) return -1; while(len--) { tmp = *bpos2++; /* reverse the bits */ tmp = ((tmp >> 1) & 0x55) | ((tmp << 1) & 0xAA); tmp = ((tmp >> 2) & 0x33) | ((tmp << 2) & 0xCC); tmp = ((tmp >> 4) & 0x0F) | ((tmp << 4) & 0xF0); *bpos++ = tmp; } break; case 6: if(bpos2 - len + 1 < buffer) return -1; while(len--) *bpos++ = *bpos2--; break; case 7: return -1; } } return bpos - buffer; } /* window event handler */ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { case WM_COMMAND: switch(LOWORD(wParam)) { case ID_Exit: PostMessage(hWnd, WM_CLOSE, 0, 0); break; case ID_Open: { OPENFILENAME ofn; FILE *f; char filename[MAX_PATH] = ""; int i; ZeroMemory(&ofn, sizeof(ofn)); ofn.lStructSize = sizeof(ofn); ofn.hwndOwner = hWnd; ofn.lpstrFilter = "SNES Rom files (*.smc)\0*.smc\0All Files (*.*)\0*.*\0"; ofn.lpstrFile = filename; ofn.nMaxFile = MAX_PATH; ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; ofn.lpstrDefExt = "smc"; if(GetOpenFileName(&ofn)) { f = fopen(filename, "rb"); if(f) { fread(rom, 4194816, 1, f); fclose(f); rom_loaded = 1; for(i = 0; i < 20; i++) decomp(&rom[tileset_pointers[i] - 0xBFFE00], tileset[i], 28673); tileset_changed[i] = 0; InvalidateRgn(hWnd, NULL, 0); } else { MessageBox(hWnd, "rb", NULL, MB_OK); } } } break; case ID_Save: { OPENFILENAME ofn; FILE *f; char filename[MAX_PATH] = ""; int i; ZeroMemory(&ofn, sizeof(ofn)); ofn.lStructSize = sizeof(ofn); ofn.hwndOwner = hWnd; ofn.lpstrFilter = "SNES Rom files (*.smc)\0*.smc\0All Files (*.*)\0*.*\0"; ofn.lpstrFile = filename; ofn.nMaxFile = MAX_PATH; ofn.Flags = OFN_EXPLORER | OFN_HIDEREADONLY; ofn.lpstrDefExt = "smc"; if(GetSaveFileName(&ofn)) { f = fopen(filename, "wb"); if(f) { for(i = 0; i < 20; i++) { if(tileset_changed[i]) { tileset_changed[i] = 0; tileset_pointers[i] = 0xFA0000 + 16384 * i; comp(tileset[i], &rom[0x3A0200 + 16384 * i], 28673); } } fwrite(rom, 4194816, 1, f); fclose(f); } else { MessageBox(hWnd, "wb", NULL, MB_OK); } } } break; case ID_About: MessageBox(hWnd, "EarthBound Tile Editor version 1.1\n" "Created by cabbage, a.k.a. mrnobo1024\n" "Visit http://pkhack.starmen.net/ for more EB hacking programs and info", "About", MB_OK); break; case ID_Selector: if(HIWORD(wParam) == CBN_SELCHANGE) { int i; selected_tileset = SendDlgItemMessage(hWnd, ID_Selector, CB_GETCURSEL, 0, 0); SendDlgItemMessage(hWnd, ID_Palette, CB_RESETCONTENT, 0, 0); for(i = 0; i < pallen[selected_tileset]; i++) { char buf[3]; sprintf(buf, "%d", i); SendDlgItemMessage(hWnd, ID_Palette, CB_ADDSTRING, 0, (LPARAM)buf); } SendDlgItemMessage(hWnd, ID_Palette, CB_SELECTSTRING, 0, (LPARAM)"0"); selected_palette = 0; if(rom_loaded) InvalidateRgn(hWnd, NULL, 0); } break; case ID_Palette: if(HIWORD(wParam) == CBN_SELCHANGE) { selected_palette = SendDlgItemMessage(hWnd, ID_Palette, CB_GETCURSEL, 0, 0); if(rom_loaded) InvalidateRgn(hWnd, NULL, 0); } break; } break; case WM_CLOSE: DestroyWindow(hWnd); break; case WM_DESTROY: PostQuitMessage(0); break; case WM_LBUTTONDOWN: case WM_MOUSEMOVE: { int x = LOWORD(lParam); int y = HIWORD(lParam); if(!LOWORD(wParam)) break; if(!rom_loaded) break; if(x < 512) { selected_tile = (y / 16) * 32 + (x / 16); InvalidateRgn(hWnd, NULL, 0); } else if(x >= 520 && y >= 35 && x <= 623 && y <= 138) { int tx = (x-520)/13; int ty = (y-35)/13; char *b = &tileset[selected_tileset][selected_tile][ty * 2]; b[ 0] &= ~(128 >> tx); if(selected_color & 1) b[ 0] |= (128 >> tx); b[ 1] &= ~(128 >> tx); if(selected_color & 2) b[ 1] |= (128 >> tx); b[16] &= ~(128 >> tx); if(selected_color & 4) b[16] |= (128 >> tx); b[17] &= ~(128 >> tx); if(selected_color & 8) b[17] |= (128 >> tx); tileset_changed[selected_tileset] = 1; InvalidateRgn(hWnd, NULL, 0); } else if(x >= 520 && y >= 175 && x <= 623 && y <= 278) selected_color = (y-175)/26 * 4 + (x-520)/26; else if(x >= 515 && y >= 284 && x <= 626 && y <= 395) { preview[(y - 284) / 16][(x - 515) / 16] = selected_tile; InvalidateRgn(hWnd, NULL, 0); } } break; case WM_PAINT: { BITMAPINFO bmi; HBITMAP bm; int *data; PAINTSTRUCT ps; HDC hdc = BeginPaint(hWnd, &ps); int x, y, tx, ty, palette[16]; if(!rom_loaded) return DefWindowProc(hWnd, msg, wParam, lParam); bmi.bmiHeader.biSize = 40; bmi.bmiHeader.biWidth = 640; bmi.bmiHeader.biHeight = -400; bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 32; bmi.bmiHeader.biCompression = BI_RGB; bmi.bmiHeader.biSizeImage = 0; bmi.bmiHeader.biXPelsPerMeter = 0; bmi.bmiHeader.biYPelsPerMeter = 0; bmi.bmiHeader.biClrUsed = 0; bmi.bmiHeader.biClrImportant = 0; bm = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, (void **)&data, NULL, 0); memset(data, 255, 640 * 400 * sizeof(int)); for(x = 0; x < 16; x++) { y = *(unsigned short *)&rom[palstart[selected_tileset] + 32*selected_palette + 2*x]; palette[x] = (((y & 0x1F) >> 0) * 8) << 16; palette[x] |= (((y & 0x3E0) >> 5) * 8) << 8; palette[x] |= (((y & 0x7C00) >> 10) * 8) << 0; } /* tiles */ for(y = 0; y < 25; y++) for(x = 0; x < 32; x++) { for(ty = 0; ty < 8; ty++) { char *b = &tileset[selected_tileset][y * 32 + x][ty * 2]; for(tx = 0; tx < 8; tx++) { int c = 0; if(b[ 0] & (128 >> tx)) c = 1; if(b[ 1] & (128 >> tx)) c |= 2; if(b[16] & (128 >> tx)) c |= 4; if(b[17] & (128 >> tx)) c |= 8; data[16*x + 2*tx + 640 * (16*y + 2*ty)] = data[16*x + 2*tx + 1 + 640 * (16*y + 2*ty)] = data[16*x + 2*tx + 640 * (16*y + 2*ty + 1)] = data[16*x + 2*tx + 1 + 640 * (16*y + 2*ty + 1)] = palette[c]; if(y * 32 + x == selected_tile) { int xx, yy; for(yy = 0; yy < 12; yy++) for(xx = 0; xx < 12; xx++) data[520 + 13 * tx + xx + 640 * (35 + 13 * ty + yy)] = palette[c]; } } } } /* palette */ for(y = 0; y < 4; y++) for(x = 0; x < 4; x++) for(ty = 0; ty < 26; ty++) for(tx = 0; tx < 26; tx++) data[520 + 26 * x + tx + 640 * (175 + 26 * y + ty)] = palette[y*4+x]; /* preview */ for(y = 0; y < 7; y++) for(x = 0; x < 7; x++) { for(ty = 0; ty < 8; ty++) { char *b = &tileset[selected_tileset][preview[y][x]][ty * 2]; for(tx = 0; tx < 8; tx++) { int c = 0; if(b[ 0] & (128 >> tx)) c = 1; if(b[ 1] & (128 >> tx)) c |= 2; if(b[16] & (128 >> tx)) c |= 4; if(b[17] & (128 >> tx)) c |= 8; data[515 + 16*x + 2*tx + 640 * (284 + 16*y + 2*ty)] = data[515 + 16*x + 2*tx + 1 + 640 * (284 + 16*y + 2*ty)] = data[515 + 16*x + 2*tx + 640 * (284 + 16*y + 2*ty + 1)] = data[515 + 16*x + 2*tx + 1 + 640 * (284 + 16*y + 2*ty + 1)] = palette[c]; } } } SetDIBitsToDevice(hdc, 0, 0, 640, 400, 0, 0, 0, 400, data, &bmi, DIB_RGB_COLORS); DeleteObject(bm); EndPaint(hWnd, &ps); } default: return DefWindowProc(hWnd, msg, wParam, lParam); } return 0; } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { WNDCLASS wc; HWND window, listbox; MSG msg; int i; HMENU menu, submenu; wc.style = 0; wc.lpfnWndProc = WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)COLOR_WINDOWFRAME; wc.lpszMenuName = NULL; wc.lpszClassName = "window"; RegisterClass(&wc); menu = CreateMenu(); submenu = CreatePopupMenu(); AppendMenu(submenu, MF_STRING, ID_Open, "&Open"); AppendMenu(submenu, MF_STRING, ID_Save, "&Save"); AppendMenu(submenu, MF_SEPARATOR, 0, 0); AppendMenu(submenu, MF_STRING, ID_Exit, "E&xit"); AppendMenu(menu, MF_STRING | MF_POPUP, (UINT)submenu, "&File"); AppendMenu(menu, MF_STRING, ID_About, "&About"); window = CreateWindowEx( WS_EX_CLIENTEDGE, "window", "EarthBound Tile Editor", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 640, 450, NULL, menu, hInstance, NULL); listbox = CreateWindow( "COMBOBOX", "", WS_CHILD | WS_VISIBLE | WS_VSCROLL | CBS_DROPDOWNLIST, 517, 5, 108, 150, window, (HMENU)ID_Selector, hInstance, NULL); for(i = 0; i < 20; i++) SendMessage(listbox, CB_ADDSTRING, 0, (LPARAM)tileset_name[i]); SendMessage(listbox, CB_SELECTSTRING, 0, (LPARAM)tileset_name[0]); CreateWindow( "COMBOBOX", "", WS_CHILD | WS_VISIBLE | WS_VSCROLL | CBS_DROPDOWNLIST, 517, 145, 108, 150, window, (HMENU)ID_Palette, hInstance, NULL); ShowWindow(window, nCmdShow); UpdateWindow(window); while(GetMessage(&msg, NULL, 0, 0) > 0) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; }