// VisualBoyAdvance - Nintendo Gameboy/GameboyAdvance (TM) emulator.
// Copyright (C) 1999-2003 Forgotten
// Copyright (C) 2005 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 "../agb/GBA.h"
#include "../Globals.h"
#include "../NLS.h"
#include "../Util.h"

extern "C" {
#include <png.h>
}

#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;
  case 6:
    renderMode5();
    break;
  case 7:
    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) || mode == 6 || mode == 7) {
    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 = (int)(wParam & 0xffff);
  int y = (int)(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 && mode <=5) {
    // 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()
{
  if(rom != NULL)
  {
    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());
  }
}
