// VisualBoyAdvance - Nintendo Gameboy/GameboyAdvance (TM) emulator. // Copyright (C) 1999-2003 Forgotten // Copyright (C) 2004 Forgotten and the VBA development team // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2, or(at your option) // any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software Foundation, // Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // MapView.cpp : implementation file // #include "stdafx.h" #include "vba.h" #include "FileDlg.h" #include "MapView.h" #include "Reg.h" #include "WinResUtil.h" #include "../System.h" #include "../GBA.h" #include "../Globals.h" #include "../NLS.h" #include "../Util.h" extern "C" { #include } #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // MapView dialog MapView::MapView(CWnd* pParent /*=NULL*/) : ResizeDlg(MapView::IDD, pParent) { //{{AFX_DATA_INIT(MapView) //}}AFX_DATA_INIT autoUpdate = false; memset(&bmpInfo.bmiHeader, 0, sizeof(bmpInfo.bmiHeader)); bmpInfo.bmiHeader.biSize = sizeof(bmpInfo.bmiHeader); bmpInfo.bmiHeader.biWidth = 1024; bmpInfo.bmiHeader.biHeight = -1024; bmpInfo.bmiHeader.biPlanes = 1; bmpInfo.bmiHeader.biBitCount = 24; bmpInfo.bmiHeader.biCompression = BI_RGB; data = (u8 *)calloc(1, 3 * 1024 * 1024); mapView.setData(data); mapView.setBmpInfo(&bmpInfo); control = BG0CNT; bg = 0; frame = 0; } MapView::~MapView() { free(data); data = NULL; } void MapView::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(MapView) DDX_Control(pDX, IDC_NUMCOLORS, m_numcolors); DDX_Control(pDX, IDC_MODE, m_mode); DDX_Control(pDX, IDC_OVERFLOW, m_overflow); DDX_Control(pDX, IDC_MOSAIC, m_mosaic); DDX_Control(pDX, IDC_PRIORITY, m_priority); DDX_Control(pDX, IDC_DIM, m_dim); DDX_Control(pDX, IDC_CHARBASE, m_charbase); DDX_Control(pDX, IDC_MAPBASE, m_mapbase); //}}AFX_DATA_MAP DDX_Control(pDX, IDC_MAP_VIEW, mapView); DDX_Control(pDX, IDC_MAP_VIEW_ZOOM, mapViewZoom); DDX_Control(pDX, IDC_COLOR, color); } BEGIN_MESSAGE_MAP(MapView, CDialog) //{{AFX_MSG_MAP(MapView) ON_BN_CLICKED(IDC_REFRESH, OnRefresh) ON_BN_CLICKED(IDC_FRAME_0, OnFrame0) ON_BN_CLICKED(IDC_FRAME_1, OnFrame1) ON_BN_CLICKED(IDC_BG0, OnBg0) ON_BN_CLICKED(IDC_BG1, OnBg1) ON_BN_CLICKED(IDC_BG2, OnBg2) ON_BN_CLICKED(IDC_BG3, OnBg3) ON_BN_CLICKED(IDC_STRETCH, OnStretch) ON_BN_CLICKED(IDC_AUTO_UPDATE, OnAutoUpdate) ON_BN_CLICKED(IDC_CLOSE, OnClose) ON_BN_CLICKED(IDC_SAVE, OnSave) //}}AFX_MSG_MAP ON_MESSAGE(WM_MAPINFO, OnMapInfo) ON_MESSAGE(WM_COLINFO, OnColInfo) END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // MapView message handlers void MapView::renderTextScreen(u16 control) { u16 *palette = (u16 *)paletteRAM; u8 *charBase = &vram[((control >> 2) & 0x03) * 0x4000]; u16 *screenBase = (u16 *)&vram[((control >> 8) & 0x1f) * 0x800]; u8 *bmp = data; int sizeX = 256; int sizeY = 256; switch((control >> 14) & 3) { case 0: break; case 1: sizeX = 512; break; case 2: sizeY = 512; break; case 3: sizeX = 512; sizeY = 512; break; } w = sizeX; h = sizeY; if(control & 0x80) { for(int y = 0; y < sizeY; y++) { int yy = y & 255; if(y == 256 && sizeY > 256) { screenBase += 0x400; if(sizeX > 256) screenBase += 0x400; } u16 *screenSource = screenBase + ((yy>>3)*32); for(int x = 0; x < sizeX; x++) { u16 data = *screenSource; int tile = data & 0x3FF; int tileX = (x & 7); int tileY = y & 7; if(data & 0x0400) tileX = 7 - tileX; if(data & 0x0800) tileY = 7 - tileY; u8 c = charBase[tile * 64 + tileY * 8 + tileX]; u16 color = palette[c]; *bmp++ = ((color >> 10) & 0x1f) << 3; *bmp++ = ((color >> 5) & 0x1f) << 3; *bmp++ = (color & 0x1f) << 3; if(data & 0x0400) { if(tileX == 0) screenSource++; } else if(tileX == 7) screenSource++; if(x == 255 && sizeX > 256) { screenSource = screenBase + 0x400 + ((yy>>3)*32); } } } } else { for(int y = 0; y < sizeY; y++) { int yy = y & 255; if(y == 256 && sizeY > 256) { screenBase += 0x400; if(sizeX > 256) screenBase += 0x400; } u16 *screenSource = screenBase + ((yy>>3)*32); for(int x = 0; x < sizeX; x++) { u16 data = *screenSource; int tile = data & 0x3FF; int tileX = (x & 7); int tileY = y & 7; if(data & 0x0400) tileX = 7 - tileX; if(data & 0x0800) tileY = 7 - tileY; u8 color = charBase[tile * 32 + tileY * 4 + (tileX>>1)]; if(tileX & 1) { color = (color >> 4); } else { color &= 0x0F; } int pal = (*screenSource>>8) & 0xF0; u16 color2 = palette[pal + color]; *bmp++ = ((color2 >> 10) & 0x1f) << 3; *bmp++ = ((color2 >> 5) & 0x1f) << 3; *bmp++ = (color2 & 0x1f) << 3; if(data & 0x0400) { if(tileX == 0) screenSource++; } else if(tileX == 7) screenSource++; if(x == 255 && sizeX > 256) { screenSource = screenBase + 0x400 + ((yy>>3)*32); } } } } /* switch(bg) { case 0: renderView(BG0HOFS<<8, BG0VOFS<<8, 0x100, 0x000, 0x000, 0x100, (sizeX -1) <<8, (sizeY -1) << 8, true); break; case 1: renderView(BG1HOFS<<8, BG1VOFS<<8, 0x100, 0x000, 0x000, 0x100, (sizeX -1) <<8, (sizeY -1) << 8, true); break; case 2: renderView(BG2HOFS<<8, BG2VOFS<<8, 0x100, 0x000, 0x000, 0x100, (sizeX -1) <<8, (sizeY -1) << 8, true); break; case 3: renderView(BG3HOFS<<8, BG3VOFS<<8, 0x100, 0x000, 0x000, 0x100, (sizeX -1) <<8, (sizeY -1) << 8, true); break; } */ } void MapView::renderRotScreen(u16 control) { u16 *palette = (u16 *)paletteRAM; u8 *charBase = &vram[((control >> 2) & 0x03) * 0x4000]; u8 *screenBase = (u8 *)&vram[((control >> 8) & 0x1f) * 0x800]; u8 *bmp = data; int sizeX = 128; int sizeY = 128; switch((control >> 14) & 3) { case 0: break; case 1: sizeX = sizeY = 256; break; case 2: sizeX = sizeY = 512; break; case 3: sizeX = sizeY = 1024; break; } w = sizeX; h = sizeY; if(control & 0x80) { for(int y = 0; y < sizeY; y++) { for(int x = 0; x < sizeX; x++) { int tile = screenBase[(x>>3) + (y>>3)*(w>>3)]; int tileX = (x & 7); int tileY = y & 7; u8 color = charBase[tile * 64 + tileY * 8 + tileX]; u16 color2 = palette[color]; *bmp++ = ((color2 >> 10) & 0x1f) << 3; *bmp++ = ((color2 >> 5) & 0x1f) << 3; *bmp++ = (color2 & 0x1f) << 3; } } } else { for(int y = 0; y < sizeY; y++) { for(int x = 0; x < sizeX; x++) { int tile = screenBase[(x>>3) + (y>>3)*(w>>3)]; int tileX = (x & 7); int tileY = y & 7; u8 color = charBase[tile * 64 + tileY * 8 + tileX]; u16 color2 = palette[color]; *bmp++ = ((color2 >> 10) & 0x1f) << 3; *bmp++ = ((color2 >> 5) & 0x1f) << 3; *bmp++ = (color2 & 0x1f) << 3; } } } u32 xx; u32 yy; switch(bg) { case 2: xx = BG2X_L | BG2X_H << 16; yy = BG2Y_L | BG2Y_H << 16; /* renderView(xx, yy, BG2PA, BG2PC, BG2PB, BG2PD, (sizeX -1) <<8, (sizeY -1) << 8, (control & 0x2000) != 0); */ break; case 3: xx = BG3X_L | BG3X_H << 16; yy = BG3Y_L | BG3Y_H << 16; /* renderView(xx, yy, BG3PA, BG3PC, BG3PB, BG3PD, (sizeX -1) <<8, (sizeY -1) << 8, (control & 0x2000) != 0); */ break; } } void MapView::renderMode0() { renderTextScreen(control); } void MapView::renderMode1() { switch(bg) { case 0: case 1: renderTextScreen(control); break; case 2: renderRotScreen(control); break; default: bg = 0; control = BG0CNT; renderTextScreen(control); break; } } void MapView::renderMode2() { switch(bg) { case 2: case 3: renderRotScreen(control); break; default: bg = 2; control = BG2CNT; renderRotScreen(control); break; } } void MapView::renderMode3() { u8 *bmp = data; u16 *src = (u16 *)&vram[0]; w = 240; h = 160; for(int y = 0; y < 160; y++) { for(int x = 0; x < 240; x++) { u16 data = *src++; *bmp++ = ((data >> 10) & 0x1f) << 3; *bmp++ = ((data >> 5) & 0x1f) << 3; *bmp++ = (data & 0x1f) << 3; } } bg = 2; } void MapView::renderMode4() { u8 *bmp = data; u8 *src = frame ? &vram[0xa000] : &vram[0]; u16 *pal = (u16 *)&paletteRAM[0]; w = 240; h = 160; for(int y = 0; y < 160; y++) { for(int x = 0; x < 240; x++) { u8 c = *src++; u16 data = pal[c]; *bmp++ = ((data >> 10) & 0x1f) << 3; *bmp++ = ((data >> 5) & 0x1f) << 3; *bmp++ = (data & 0x1f) << 3; } } bg = 2; } void MapView::renderMode5() { u8 *bmp = data; u16 *src = (u16 *)(frame ? &vram[0xa000] : &vram[0]); w = 160; h = 128; for(int y = 0; y < 128; y++) { for(int x = 0; x < 160; x++) { u16 data = *src++; *bmp++ = ((data >> 10) & 0x1f) << 3; *bmp++ = ((data >> 5) & 0x1f) << 3; *bmp++ = (data & 0x1f) << 3; } } bg = 2; } void MapView::OnRefresh() { paint(); } void MapView::paint() { if(vram == NULL) return; int mode = DISPCNT & 7; switch(bg) { default: case 0: control = BG0CNT; break; case 1: control = BG1CNT; break; case 2: control = BG2CNT; break; case 3: control = BG3CNT; break; } switch(mode) { case 0: renderMode0(); break; case 1: renderMode1(); break; case 2: renderMode2(); break; case 3: renderMode3(); break; case 4: renderMode4(); break; case 5: renderMode5(); break; } enableButtons(mode); SIZE s; if(mapView.getStretch()) { mapView.setSize(w, h); s.cx = s.cy = 1; mapView.SetScrollSizes(MM_TEXT, s); } else { mapView.setSize(w, h); s.cx = w; s.cy = h; mapView.SetScrollSizes(MM_TEXT, s); } mapView.refresh(); CString buffer; u32 charBase = ((control >> 2) & 0x03) * 0x4000 + 0x6000000; u32 screenBase = ((control >> 8) & 0x1f) * 0x800 + 0x6000000; buffer.Format("%d", mode); m_mode.SetWindowText(buffer); if(mode >= 3) { m_mapbase.SetWindowText(""); m_charbase.SetWindowText(""); } else { buffer.Format("0x%08X", screenBase); m_mapbase.SetWindowText(buffer); buffer.Format("0x%08X", charBase); m_charbase.SetWindowText(buffer); } buffer.Format("%dx%d", w, h); m_dim.SetWindowText(buffer); m_numcolors.SetWindowText(control & 0x80 ? "256" : "16"); buffer.Format("%d", control & 3); m_priority.SetWindowText(buffer); m_mosaic.SetWindowText(control & 0x40 ? "1" : "0"); m_overflow.SetWindowText(bg <= 1 ? "" : control & 0x2000 ? "1" : "0"); } BOOL MapView::OnInitDialog() { CDialog::OnInitDialog(); DIALOG_SIZER_START( sz ) DIALOG_SIZER_ENTRY( IDC_MAP_VIEW, DS_SizeX | DS_SizeY ) DIALOG_SIZER_ENTRY( IDC_REFRESH, DS_MoveY) DIALOG_SIZER_ENTRY( IDC_CLOSE, DS_MoveY) DIALOG_SIZER_ENTRY( IDC_SAVE, DS_MoveY) DIALOG_SIZER_ENTRY( IDC_COLOR, DS_MoveY) DIALOG_SIZER_ENTRY( IDC_R, DS_MoveY) DIALOG_SIZER_ENTRY( IDC_G, DS_MoveY) DIALOG_SIZER_ENTRY( IDC_B, DS_MoveY) DIALOG_SIZER_END() SetData(sz, TRUE, HKEY_CURRENT_USER, "Software\\Emulators\\VisualBoyAdvance\\Viewer\\MapView", NULL); SIZE size; size.cx = 1; size.cy = 1; mapView.SetScrollSizes(MM_TEXT,size); int s = regQueryDwordValue("mapViewStretch", 0); if(s) mapView.setStretch(true); ((CButton *)GetDlgItem(IDC_STRETCH))->SetCheck(s); paint(); return TRUE; // return TRUE unless you set the focus to a control // EXCEPTION: OCX Property Pages should return FALSE } void MapView::PostNcDestroy() { delete this; } void MapView::enableButtons(int mode) { bool enable[6] = { true, true, true, true, true, true }; switch(mode) { case 0: enable[4] = false; enable[5] = false; break; case 1: enable[3] = false; enable[4] = false; enable[5] = false; break; case 2: enable[0] = false; enable[1] = false; enable[4] = false; enable[5] = false; break; case 3: enable[0] = false; enable[1] = false; enable[2] = false; enable[3] = false; enable[4] = false; enable[5] = false; break; case 4: enable[0] = false; enable[1] = false; enable[2] = false; enable[3] = false; break; case 5: enable[0] = false; enable[1] = false; enable[2] = false; enable[3] = false; break; } GetDlgItem(IDC_BG0)->EnableWindow(enable[0]); GetDlgItem(IDC_BG1)->EnableWindow(enable[1]); GetDlgItem(IDC_BG2)->EnableWindow(enable[2]); GetDlgItem(IDC_BG3)->EnableWindow(enable[3]); GetDlgItem(IDC_FRAME_0)->EnableWindow(enable[4]); GetDlgItem(IDC_FRAME_1)->EnableWindow(enable[5]); int id = IDC_BG0; switch(bg) { case 1: id = IDC_BG1; break; case 2: id = IDC_BG2; break; case 3: id = IDC_BG3; break; } CheckRadioButton(IDC_BG0, IDC_BG3, id); id = IDC_FRAME_0; if(frame != 0) id = IDC_FRAME_1; CheckRadioButton(IDC_FRAME_0, IDC_FRAME_1, id); } void MapView::OnFrame0() { frame = 0; paint(); } void MapView::OnFrame1() { frame = 1; paint(); } void MapView::OnBg0() { bg = 0; control = BG0CNT; paint(); } void MapView::OnBg1() { bg = 1; control = BG1CNT; paint(); } void MapView::OnBg2() { bg = 2; control = BG2CNT; paint(); } void MapView::OnBg3() { bg = 3; control = BG3CNT; paint(); } void MapView::OnStretch() { mapView.setStretch(!mapView.getStretch()); paint(); regSetDwordValue("mapViewStretch", mapView.getStretch()); } void MapView::OnAutoUpdate() { autoUpdate = !autoUpdate; if(autoUpdate) { theApp.winAddUpdateListener(this); } else { theApp.winRemoveUpdateListener(this); } } void MapView::update() { paint(); } void MapView::OnClose() { theApp.winRemoveUpdateListener(this); DestroyWindow(); } u32 MapView::GetTextClickAddress(u32 base, int x, int y) { if(y > 255 && h > 256) { base += 0x800; if(w > 256) base += 0x800; } if(x >= 256) base += 0x800; x &= 255; y &= 255; base += (x>>3)*2 + 64*(y>>3); return base; } u32 MapView::GetClickAddress(int x, int y) { int mode = DISPCNT & 7; u32 base = ((control >> 8) & 0x1f) * 0x800 + 0x6000000; // all text bgs (16 bits) if(mode == 0 ||(mode < 3 && bg < 2)) { return GetTextClickAddress(base, x, y); } // rot bgs (8 bits) if(mode < 3) { return base + (x>>3) + (w>>3)*(y>>3); } // mode 3/5 (16 bits) if(mode != 4) { return 0x6000000 + 0xa000*frame + 2*x + w*y*2; } // mode 4 (8 bits) return 0x6000000 + 0xa000*frame + x + w*y; } LRESULT MapView::OnMapInfo(WPARAM wParam, LPARAM lParam) { u8 *colors = (u8 *)lParam; mapViewZoom.setColors(colors); int x = wParam & 0xffff; int y = (wParam >> 16); CString buffer; buffer.Format("(%d,%d)", x, y); GetDlgItem(IDC_XY)->SetWindowText(buffer); u32 address = GetClickAddress(x,y); buffer.Format("0x%08X", address); GetDlgItem(IDC_ADDRESS)->SetWindowText(buffer); int mode = DISPCNT & 7; if(mode >= 3) { // bitmap modes GetDlgItem(IDC_TILE_NUM)->SetWindowText("---"); GetDlgItem(IDC_FLIP)->SetWindowText("--"); GetDlgItem(IDC_PALETTE_NUM)->SetWindowText("---"); } else if(mode == 0 || bg < 2) { // text bgs u16 value = *((u16 *)&vram[address - 0x6000000]); int tile = value & 1023; buffer.Format("%d", tile); GetDlgItem(IDC_TILE_NUM)->SetWindowText(buffer); buffer.Empty(); buffer += value & 1024 ? 'H' : '-'; buffer += value & 2048 ? 'V' : '-'; GetDlgItem(IDC_FLIP)->SetWindowText(buffer); if(!(control & 0x80)) { buffer.Format("%d", (value >> 12) & 15); } else buffer = "---"; GetDlgItem(IDC_PALETTE_NUM)->SetWindowText(buffer); } else { // rot bgs GetDlgItem(IDC_TILE_NUM)->SetWindowText("---"); GetDlgItem(IDC_FLIP)->SetWindowText("--"); GetDlgItem(IDC_PALETTE_NUM)->SetWindowText("---"); } return TRUE; } LRESULT MapView::OnColInfo(WPARAM wParam, LPARAM lParam) { u16 c = (u16)wParam; color.setColor(c); int r = (c & 0x1f); int g = (c & 0x3e0) >> 5; int b = (c & 0x7c00) >> 10; CString buffer; buffer.Format("R: %d", r); GetDlgItem(IDC_R)->SetWindowText(buffer); buffer.Format("G: %d", g); GetDlgItem(IDC_G)->SetWindowText(buffer); buffer.Format("B: %d", b); GetDlgItem(IDC_B)->SetWindowText(buffer); return TRUE; } void MapView::saveBMP(const char *name) { u8 writeBuffer[1024 * 3]; FILE *fp = fopen(name,"wb"); if(!fp) { systemMessage(MSG_ERROR_CREATING_FILE, "Error creating file %s", name); return; } struct { u8 ident[2]; u8 filesize[4]; u8 reserved[4]; u8 dataoffset[4]; u8 headersize[4]; u8 width[4]; u8 height[4]; u8 planes[2]; u8 bitsperpixel[2]; u8 compression[4]; u8 datasize[4]; u8 hres[4]; u8 vres[4]; u8 colors[4]; u8 importantcolors[4]; u8 pad[2]; } bmpheader; memset(&bmpheader, 0, sizeof(bmpheader)); bmpheader.ident[0] = 'B'; bmpheader.ident[1] = 'M'; u32 fsz = sizeof(bmpheader) + w*h*3; utilPutDword(bmpheader.filesize, fsz); utilPutDword(bmpheader.dataoffset, 0x38); utilPutDword(bmpheader.headersize, 0x28); utilPutDword(bmpheader.width, w); utilPutDword(bmpheader.height, h); utilPutDword(bmpheader.planes, 1); utilPutDword(bmpheader.bitsperpixel, 24); utilPutDword(bmpheader.datasize, 3*w*h); fwrite(&bmpheader, 1, sizeof(bmpheader), fp); u8 *b = writeBuffer; int sizeX = w; int sizeY = h; u8 *pixU8 = (u8 *)data+3*w*(h-1); for(int y = 0; y < sizeY; y++) { for(int x = 0; x < sizeX; x++) { *b++ = *pixU8++; // B *b++ = *pixU8++; // G *b++ = *pixU8++; // R } pixU8 -= 2*3*w; fwrite(writeBuffer, 1, 3*w, fp); b = writeBuffer; } fclose(fp); } void MapView::savePNG(const char *name) { u8 writeBuffer[1024 * 3]; FILE *fp = fopen(name,"wb"); if(!fp) { systemMessage(MSG_ERROR_CREATING_FILE, "Error creating file %s", name); return; } png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if(!png_ptr) { fclose(fp); return; } png_infop info_ptr = png_create_info_struct(png_ptr); if(!info_ptr) { png_destroy_write_struct(&png_ptr,NULL); fclose(fp); return; } if(setjmp(png_ptr->jmpbuf)) { png_destroy_write_struct(&png_ptr,NULL); fclose(fp); return; } png_init_io(png_ptr,fp); png_set_IHDR(png_ptr, info_ptr, w, h, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); png_write_info(png_ptr,info_ptr); u8 *b = writeBuffer; int sizeX = w; int sizeY = h; u8 *pixU8 = (u8 *)data; for(int y = 0; y < sizeY; y++) { for(int x = 0; x < sizeX; x++) { int blue = *pixU8++; int green = *pixU8++; int red = *pixU8++; *b++ = red; *b++ = green; *b++ = blue; } png_write_row(png_ptr,writeBuffer); b = writeBuffer; } png_write_end(png_ptr, info_ptr); png_destroy_write_struct(&png_ptr, &info_ptr); fclose(fp); } void MapView::OnSave() { CString filename; if(theApp.captureFormat == 0) filename = "map.png"; else filename = "map.bmp"; LPCTSTR exts[] = {".png", ".bmp" }; CString filter = theApp.winLoadFilter(IDS_FILTER_PNG); CString title = winResLoadString(IDS_SELECT_CAPTURE_NAME); FileDlg dlg(this, filename, filter, theApp.captureFormat ? 2 : 1, theApp.captureFormat ? "BMP" : "PNG", exts, "", title, true); if(dlg.DoModal() == IDCANCEL) { return; } if(dlg.getFilterIndex() == 2) saveBMP(dlg.GetPathName()); else savePNG(dlg.GetPathName()); }