/* FCE Ultra - NES/Famicom Emulator
 *
 * Copyright notice for this file:
 *  Copyright (C) 2020 mjbudd77
 *
 * 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 of the License, 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
// iNesHeaderEditor.cpp
//
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>

#include <SDL.h>
#include <QHeaderView>
#include <QCloseEvent>
#include <QMessageBox>
#include <QFileDialog>
#include <QSettings>

#include "../../types.h"
#include "../../fceu.h"
#include "../../cart.h"
#include "../../ines.h"

#include "Qt/main.h"
#include "Qt/dface.h"
#include "Qt/input.h"
#include "Qt/config.h"
#include "Qt/keyscan.h"
#include "Qt/fceuWrapper.h"
#include "Qt/iNesHeaderEditor.h"
#include "Qt/ConsoleUtilities.h"

extern BMAPPINGLocal bmap[];

// VS System type
static const char* vsSysList[] = {
	"Normal",
	"RBI Baseball",
	"TKO Boxing",
	"Super Xevious",
	"Ice Climber",
	"Dual Normal",
	"Dual Raid on Bungeling Bay",
	0
};

// VS PPU type
static const char* vsPPUList[] = {
	"RP2C03B",
	"RP2C03G",
	"RP2C04-0001",
	"RP2C04-0002",
	"RP2C04-0003",
	"RP2C04-0004",
	"RC2C03B",
	"RC2C03C",
	"RC2C05-01 ($2002 AND $\?\?=$1B)",
	"RC2C05-02 ($2002 AND $3F=$3D)",
	"RC2C05-03 ($2002 AND $1F=$1C)",
	"RC2C05-04 ($2002 AND $1F=$1B)",
	"RC2C05-05 ($2002 AND $1F=$\?\?)",
	"Reserved",
	"Reserved",
	"Reserved",
	0
};

// Extend console type
static const char* extConsoleList[] = {
	"Normal",
	"VS. System",
	"Playchoice 10",
	"Bit Corp. Creator",
	"V.R. Tech. VT01 monochrome",
	"V.R. Tech. VT01 red / cyan",
	"V.R. Tech. VT02",
	"V.R. Tech. VT03",
	"V.R. Tech. VT09",
	"V.R. Tech. VT32",
	"V.R. Tech. VT369",
	"Reserved",
	"Reserved",
	"Reserved",
	"Reserved",
	"Reserved",
	0
};

// Default input device list
static const char* inputDevList[] = {
	"Unspecified",
	"Standard NES / Famicom Controllers",
	"Four-score (NES)",
	"Four-score (Famicom)",
	"VS. System",
	"VS. System (controllers swapped)",
	"VS. Pinball (J)",
	"VS. Zapper",
	"Zapper",
	"Double Zappers",
	"Bandai Hyper Shot",
	"Power Pad Side A",
	"Power Pad Side B",
	"Family Trainer Side A",
	"Family Trainer Side B",
	"Arkanoid Paddle (NES)",
	"Arkanoid Paddle (Famicom)",
	"Double Arkanoid Paddle",
	"Konami Hyper Shot",
	"Pachinko",
	"Exciting Boxing Punching Bag",
	"Jissen Mahjong",
	"Party Tap",
	"Oeka Kids Tablet",
	"Barcode Reader",
	"Miracle Piano",
	"Pokkun Moguraa",
	"Top Rider",
	"Double-Fisted",
	"Famicom 3D",
	"Doremikko Keyboard",
	"R.O.B. Gyro Set",
	"Famicom Data Recorder",
	"ASCII Turbo File",
	"IGS Storage Battle Box",
	"Family BASIC Keyboard",
	"PEC-586 Keyboard",
	"Bit Corp. Keyboard",
	"Subor Keyboard",
	"Subor Keyboard + Mouse A",
	"Subor Keyboard + Mouse B",
	"SNES Mouse",
	"Multicart",
	"Double SNES controllers",
	"RacerMate Bicycle",
	"U-Force",
	"R.O.B. Stack-Up",
	"City Patrolman Lightgun",
	"Sharp C1 Cassette Interface",
	"Standard Controller with swapped Left-Right/Up-Down/B-A",
	"Excalibor Sudoku Pad",
	"ABL Pinball",
	"Golden Nugget Casino extra buttons",
	0
};
//----------------------------------------------------------------------------
iNesHeaderEditor_t::iNesHeaderEditor_t(QWidget *parent)
	: QDialog( parent )
{
	int i;
	int fontCharWidth;
	fceuDecIntValidtor *validator; 
	QVBoxLayout *mainLayout, *hdrLayout;
	QVBoxLayout *vbox, *vbox1, *vbox2, *vbox3, *vbox4, *vbox5;
	QHBoxLayout *hbox, *hbox1, *hbox2, *hbox3;
	QGroupBox *box, *hdrBox;
	QGridLayout *grid;
	QStyle      *style;
	QSettings    settings;
	char stmp[128];

	style = this->style();

	initOK = false;

	font.setFamily("Courier New");
	font.setStyle( QFont::StyleNormal );
	font.setStyleHint( QFont::Monospace );
	QFontMetrics fm(font);

#if QT_VERSION > QT_VERSION_CHECK(5, 11, 0)
    fontCharWidth = fm.horizontalAdvance(QLatin1Char('2'));
#else
    fontCharWidth = fm.width(QLatin1Char('2'));
#endif

	setWindowTitle("NES Header Editor");

	//resize( 512, 512 );

	mainLayout = new QVBoxLayout();
	hdrLayout  = new QVBoxLayout();
	hbox1      = new QHBoxLayout();
	hbox       = new QHBoxLayout();
	hdrBox     = new QGroupBox( tr("NES Header") );
	box        = new QGroupBox( tr("Version:") );

	mainLayout->addWidget( hdrBox );
	hdrBox->setLayout( hdrLayout );
	hbox1->addWidget( box );
	box->setLayout( hbox );

	iNes1Btn = new QRadioButton( tr("iNES") );
	iNes2Btn = new QRadioButton( tr("NES 2.0") );

	connect( iNes1Btn, SIGNAL(clicked(bool)), this, SLOT(iNes1Clicked(bool)) );
	connect( iNes2Btn, SIGNAL(clicked(bool)), this, SLOT(iNes2Clicked(bool)) );

	hbox->addWidget( iNes1Btn );
	hbox->addWidget( iNes2Btn );

	hbox       = new QHBoxLayout();
	box        = new QGroupBox( tr("Mapper:") );

	hbox1->addWidget( box );
	box->setLayout( hbox );

	mapperComboBox = new QComboBox();
	mapperSubEdit  = new QLineEdit();

	hbox->addWidget( new QLabel( tr("Mapper #:") ) );
	hbox->addWidget( mapperComboBox );
	hbox->addWidget( mapperSubLbl = new QLabel( tr("Sub #:") ) );
	hbox->addWidget( mapperSubEdit );

	validator = new fceuDecIntValidtor(0, 15, this);

	mapperSubEdit->setFont( font );
	mapperSubEdit->setMaxLength( 2 );
	mapperSubEdit->setValidator( validator );
	mapperSubEdit->setAlignment(Qt::AlignCenter);
	mapperSubEdit->setMaximumWidth( 4 * fontCharWidth );

	for (i = 0; bmap[i].init; ++i)
	{
		snprintf(stmp, sizeof(stmp), "%d %s", bmap[i].number, bmap[i].name);
		
		mapperComboBox->addItem( tr(stmp), bmap[i].number );
	}
	hdrLayout->addLayout( hbox1 );

	box   = new QGroupBox( tr("PRG") );
	grid  = new QGridLayout();

	box->setLayout( grid );
	hdrLayout->addWidget( box );

	prgRomBox   = new QComboBox();
	prgRamBox   = new QComboBox();
	prgNvRamBox = new QComboBox();
	battNvRamBox= new QCheckBox( tr("Battery-backed NVRAM") );

	hbox  = new QHBoxLayout();
	grid->addLayout( hbox, 0, 0 );
	hbox->addWidget( new QLabel( tr("PRG ROM:") ), 0, Qt::AlignRight );
	hbox->addWidget( prgRomBox, 0, Qt::AlignLeft );
	hbox  = new QHBoxLayout();
	grid->addLayout( hbox, 0, 1 );
	hbox->addWidget( prgRamLbl = new QLabel( tr("PRG RAM:") ), 0, Qt::AlignRight );
	hbox->addWidget( prgRamBox, 0, Qt::AlignLeft );
	hbox  = new QHBoxLayout();
	grid->addLayout( hbox, 0, 2 );
	hbox->addWidget( prgNvRamLbl = new QLabel( tr("PRG NVRAM:") ), 0, Qt::AlignRight );
	hbox->addWidget( prgNvRamBox, 0, Qt::AlignLeft );
	hbox->addWidget( battNvRamBox, 0, Qt::AlignRight );

	box   = new QGroupBox( tr("CHR") );
	grid  = new QGridLayout();

	box->setLayout( grid );
	hdrLayout->addWidget( box );

	chrRomBox   = new QComboBox();
	chrRamBox   = new QComboBox();
	chrNvRamBox = new QComboBox();

	hbox  = new QHBoxLayout();
	grid->addLayout( hbox, 0, 0 );
	hbox->addWidget( new QLabel( tr("CHR ROM:") ), 0, Qt::AlignRight );
	hbox->addWidget( chrRomBox, 0, Qt::AlignLeft );
	hbox  = new QHBoxLayout();
	grid->addLayout( hbox, 0, 1 );
	hbox->addWidget( chrRamLbl = new QLabel( tr("CHR RAM:") ), 0, Qt::AlignRight );
	hbox->addWidget( chrRamBox, 0, Qt::AlignLeft );
	hbox  = new QHBoxLayout();
	grid->addLayout( hbox, 0, 2 );
	hbox->addWidget( chrNvRamLbl = new QLabel( tr("CHR NVRAM:") ), 0, Qt::AlignRight );
	hbox->addWidget( chrNvRamBox, 0, Qt::AlignLeft );

	// add usually used size strings
	strcpy(stmp, "0 B");
	prgRomBox->addItem( tr(stmp), 0 );
	prgRamBox->addItem( tr(stmp), 0 );
	prgNvRamBox->addItem( tr(stmp), 0 );
	chrRomBox->addItem( tr(stmp), 0 );
	chrRamBox->addItem( tr(stmp), 0 );
	chrNvRamBox->addItem( tr(stmp), 0 );

	int size = 128;
	while (size <= 2048 * 1024)
	{
		if (size >= 1024 << 3)
		{
			// The size of CHR ROM must be multiple of 8KB
			snprintf( stmp, sizeof(stmp), "%d KB", size / 1024);
			chrRomBox->addItem( tr(stmp), size );

			// The size of PRG ROM must be multiple of 16KB
			if (size >= 1024 * 16)
			{
				// PRG ROM
				snprintf(stmp, sizeof(stmp), "%d KB", size / 1024);
				prgRomBox->addItem( tr(stmp), size );
			}
		}
		
		if (size >= 1024)
		{
			snprintf( stmp, sizeof(stmp), "%d KB", size / 1024);
		}
		else
		{
			snprintf( stmp, sizeof(stmp), "%d B", size);
		}

		prgRamBox->addItem( tr(stmp), size );
		chrRamBox->addItem( tr(stmp), size );
		prgNvRamBox->addItem( tr(stmp), size );
		chrNvRamBox->addItem( tr(stmp), size );

		size <<= 1;
	}

	hbox2 = new QHBoxLayout();
	hbox3 = new QHBoxLayout();
	vbox1 = new QVBoxLayout();
	vbox2 = new QVBoxLayout();
	vbox3 = new QVBoxLayout();
	vbox4 = new QVBoxLayout();
	vbox5 = new QVBoxLayout();
	vbox  = new QVBoxLayout();

	hdrLayout->addLayout( hbox2 );
	hbox2->addLayout( vbox1 );
	hbox3->addLayout( vbox5 );
	hbox3->addLayout( vbox2 );
	vbox1->addLayout( hbox3 );

	trainerCBox = new QCheckBox( tr("Trainer") );
	iNesUnOfBox = new QCheckBox( tr("iNES 1.0 Unofficial Properties") );

	connect( iNesUnOfBox, SIGNAL(stateChanged(int)), this, SLOT(unofficialStateChange(int)) );

	box = new QGroupBox( tr("Mirroring:") );
	box->setLayout( vbox );
	vbox5->addWidget( box );
	vbox5->addWidget( trainerCBox );

	horzMirrorBtn = new QRadioButton( tr("Horizontal") );
	vertMirrorBtn = new QRadioButton( tr("Vertical") );
	fourMirrorBtn = new QRadioButton( tr("Four Screen") );

	vbox->addWidget( horzMirrorBtn );
	vbox->addWidget( vertMirrorBtn );
	vbox->addWidget( fourMirrorBtn );

	vbox = new QVBoxLayout();
	box  = new QGroupBox( tr("Region:") );
	box->setLayout( vbox );
	vbox2->addWidget( box );

	ntscRegionBtn  = new QRadioButton( tr("NTSC") );
	palRegionBtn   = new QRadioButton( tr("PAL") );
	dendyRegionBtn = new QRadioButton( tr("Dendy") );
	dualRegionBtn  = new QRadioButton( tr("Dual") );

	vbox->addWidget( ntscRegionBtn  );
	vbox->addWidget( palRegionBtn   );
	vbox->addWidget( dendyRegionBtn );
	vbox->addWidget( dualRegionBtn  );

	iNesDualRegBox = new QCheckBox( tr("Dual Region") );
	iNesBusCfltBox = new QCheckBox( tr("Bus Conflict") );
	iNesPrgRamBox  = new QCheckBox( tr("PRG RAM Exists") );

	connect( iNesPrgRamBox , SIGNAL(stateChanged(int)), this, SLOT(unofficialPrgRamStateChange(int)) );
	connect( iNesDualRegBox, SIGNAL(stateChanged(int)), this, SLOT(unofficialDualRegionStateChange(int)) );

	iNesUnOfGroupBox = new QGroupBox( tr("iNES 1.0 Unofficial Properties:") );
	vbox = new QVBoxLayout();

	vbox->addWidget( iNesDualRegBox );
	vbox->addWidget( iNesBusCfltBox );
	vbox->addWidget( iNesPrgRamBox  );

	iNesUnOfGroupBox->setLayout( vbox );
	vbox1->addWidget( iNesUnOfBox );
	vbox1->addWidget( iNesUnOfGroupBox );

	hbox = new QHBoxLayout();
	sysGroupBox = new QGroupBox( tr("System") );
	hbox2->addWidget( sysGroupBox );
	sysGroupBox->setLayout( vbox3 );
	vbox3->addLayout( hbox );

	normSysbtn  = new QRadioButton( tr("Normal") );
	vsSysbtn    = new QRadioButton( tr("VS. Sys") );
	plySysbtn   = new QRadioButton( tr("PlayChoice-10") );
	extSysbtn   = new QRadioButton( tr("Extend") );

	connect( normSysbtn, SIGNAL(clicked(bool)), this, SLOT(normSysClicked(bool)) );
	connect( vsSysbtn  , SIGNAL(clicked(bool)), this, SLOT(vsSysClicked(bool)) );
	connect( plySysbtn , SIGNAL(clicked(bool)), this, SLOT(plySysClicked(bool)) );
	connect( extSysbtn , SIGNAL(clicked(bool)), this, SLOT(extSysClicked(bool)) );

	hbox->addWidget( normSysbtn );
	hbox->addWidget( vsSysbtn   );
	hbox->addWidget( plySysbtn  );
	hbox->addWidget( extSysbtn  );

	hbox = new QHBoxLayout();
	vsGroupBox = new QGroupBox( tr("VS. System") );
	vsGroupBox->setLayout( vbox4 );
	vbox3->addWidget( vsGroupBox );
	vbox4->addLayout( hbox );

	vsHwBox   = new QComboBox();
	vsPpuBox  = new QComboBox();
	extCslBox = new QComboBox();

	hbox->addWidget( new QLabel( tr("Hardware:") ) );
	hbox->addWidget( vsHwBox );

	hbox = new QHBoxLayout();
	vbox4->addLayout( hbox );

	hbox->addWidget( new QLabel( tr("PPU:") ) );
	hbox->addWidget( vsPpuBox );

	hbox = new QHBoxLayout();
	extGroupBox  = new QGroupBox( tr("Extend System") );
	vbox3->addWidget( extGroupBox );
	extGroupBox->setLayout( hbox );

	hbox->addWidget( new QLabel( tr("Console:") ) );
	hbox->addWidget( extCslBox );

	hbox = new QHBoxLayout();
	inputDevBox  = new QComboBox();
	miscRomsEdit = new QLineEdit();
	hdrLayout->addLayout( hbox );
	hbox->addWidget( miscRomsEdit, 1, Qt::AlignLeft );
	hbox->addWidget( miscRomsLbl = new QLabel( tr("Misc. ROM(s)") ), 10, Qt::AlignLeft);
	hbox->addWidget( inputDevLbl = new QLabel( tr("Input Device:") ), 1, Qt::AlignRight );
	hbox->addWidget( inputDevBox, 10, Qt::AlignLeft );

	validator = new fceuDecIntValidtor(0, 3, this);

	miscRomsEdit->setFont( font );
	miscRomsEdit->setMaxLength( 1 );
	miscRomsEdit->setValidator( validator );
	miscRomsEdit->setAlignment(Qt::AlignCenter);
	miscRomsEdit->setMinimumWidth( 3 * fontCharWidth );
	miscRomsEdit->setMaximumWidth( 3 * fontCharWidth );

	grid = new QGridLayout();
	restoreBtn = new QPushButton( tr("Restore") );
	saveAsBtn  = new QPushButton( tr("Save As") );
	closeBtn   = new QPushButton( tr("Close") );

	restoreBtn->setIcon( style->standardIcon( QStyle::SP_DialogResetButton ) );
	saveAsBtn->setIcon( style->standardIcon( QStyle::SP_DialogSaveButton ) );
	closeBtn->setIcon( style->standardIcon( QStyle::SP_DialogCloseButton ) );

	grid->addWidget( restoreBtn, 0, 0, Qt::AlignLeft );
	grid->addWidget( saveAsBtn , 0, 1, Qt::AlignRight );
	grid->addWidget( closeBtn  , 0, 2, Qt::AlignRight );

	mainLayout->addLayout( grid );

	setLayout( mainLayout );

	connect( restoreBtn, SIGNAL(clicked(void)), this, SLOT(restoreHeader(void)) );
	connect( saveAsBtn , SIGNAL(clicked(void)), this, SLOT(saveHeader(void)) );
	connect( closeBtn  , SIGNAL(clicked(void)), this, SLOT(closeWindow(void)) );

	i=0;
	while ( vsSysList[i] != NULL )
	{
		vsHwBox->addItem( vsSysList[i], i ); i++;
	}

	i=0;
	while ( vsPPUList[i] != NULL )
	{
		vsPpuBox->addItem( vsPPUList[i], i ); i++;
	}

	i=0;
	while ( extConsoleList[i] != NULL )
	{
		extCslBox->addItem( extConsoleList[i], i ); i++;
	}

	i=0;
	while ( inputDevList[i] != NULL )
	{
		inputDevBox->addItem( inputDevList[i], i ); i++;
	}

	iNesHdr = (iNES_HEADER*)::malloc( sizeof(iNES_HEADER) );

	::memset( iNesHdr, 0, sizeof(iNES_HEADER) );

	if (GameInfo == NULL)
	{
		if ( openFile() == false )
		{
			return;
		}
	}
	if ( loadHeader( iNesHdr ) == false )
	{
		return;
	}

	setHeaderData( iNesHdr );

	initOK = true;

	restoreGeometry(settings.value("iNesHeaderWindow/geometry").toByteArray());
}
//----------------------------------------------------------------------------
iNesHeaderEditor_t::~iNesHeaderEditor_t(void)
{
	QSettings settings;
	//printf("Destroy Header Editor Config Window\n");

	if ( iNesHdr )
	{
		free( iNesHdr ); iNesHdr = NULL;
	}
	settings.setValue("iNesHeaderWindow/geometry", saveGeometry());
}
//----------------------------------------------------------------------------
void iNesHeaderEditor_t::closeEvent(QCloseEvent *event)
{
	//printf("NES Header Editor Close Window Event\n");
	done(0);
	deleteLater();
	event->accept();
}
//----------------------------------------------------------------------------
void iNesHeaderEditor_t::closeWindow(void)
{
   //printf("Close Window\n");
   done(0);
	deleteLater();
}
//----------------------------------------------------------------------------
void iNesHeaderEditor_t::restoreHeader(void)
{
	if ( iNesHdr != NULL )
	{
		setHeaderData( iNesHdr );
	}
}
//----------------------------------------------------------------------------
void iNesHeaderEditor_t::saveHeader(void)
{
	saveFileAs();
}
//----------------------------------------------------------------------------
void iNesHeaderEditor_t::iNes1Clicked(bool checked)
{
	//printf("INES 1.0 State: %i \n", checked);
	ToggleINES20(false);
}
//----------------------------------------------------------------------------
void iNesHeaderEditor_t::iNes2Clicked(bool checked)
{
	//printf("INES 2.0 State: %i \n", checked);
	ToggleINES20(true);
}
//----------------------------------------------------------------------------
void iNesHeaderEditor_t::normSysClicked(bool checked)
{
	ToggleVSSystemGroup(false);
	ToggleExtendSystemList(false);
}
//----------------------------------------------------------------------------
void iNesHeaderEditor_t::vsSysClicked(bool checked)
{

	ToggleVSSystemGroup(true);
	ToggleExtendSystemList(false);
}
//----------------------------------------------------------------------------
void iNesHeaderEditor_t::plySysClicked(bool checked)
{

	ToggleVSSystemGroup(false);
	ToggleExtendSystemList(false);
}
//----------------------------------------------------------------------------
void iNesHeaderEditor_t::extSysClicked(bool checked)
{

	ToggleVSSystemGroup(false);
	ToggleExtendSystemList(true);
}
//----------------------------------------------------------------------------
void iNesHeaderEditor_t::unofficialStateChange(int state)
{
	ToggleUnofficialPropertiesEnabled( iNes2Btn->isChecked(), state != Qt::Unchecked );
}
//----------------------------------------------------------------------------
void iNesHeaderEditor_t::unofficialPrgRamStateChange(int state)
{
	ToggleUnofficialPrgRamPresent( false, true, state != Qt::Unchecked );
}
//----------------------------------------------------------------------------
void iNesHeaderEditor_t::unofficialDualRegionStateChange(int state)
{
	ToggleUnofficialExtraRegionCode( false, true, state != Qt::Unchecked );
}
//----------------------------------------------------------------------------
void iNesHeaderEditor_t::showErrorMsgWindow(const char *str)
{
	QMessageBox msgBox(this);

	msgBox.setIcon( QMessageBox::Critical );
	msgBox.setText( tr(str) );
	msgBox.show();
	msgBox.exec();
}
//----------------------------------------------------------------------------
bool iNesHeaderEditor_t::loadHeader(iNES_HEADER* header)
{
	int error = 0;
	enum errors {
		OK,
		OPEN_FAILED,
		INVALID_HEADER,
		FDS_HEADER,
		UNIF_HEADER,
		NSF_HEADER//,
//		NSFE_HEADER,
//		NSF2_HEADER
	};

	FCEUFILE* fp = FCEU_fopen(LoadedRomFName, NULL, "rb", NULL);

	if (fp)
	{
		if (FCEU_fread(header, 1, sizeof(iNES_HEADER), fp) == sizeof(iNES_HEADER) && !memcmp(header, "NES\x1A", 4))
		{
			header->cleanup();
		}
		else if (!memcmp(header, "FDS\x1A", 4))
		{
			error = errors::FDS_HEADER;
		}
		else if (!memcmp(header, "UNIF", 4))
		{
			error = errors::UNIF_HEADER;
		}
		else if (!memcmp(header, "NESM", 4))
		{
			error = errors::NSF_HEADER;
		}
/*		else if (!memcmp(header, "NSFE", 4))
			error = errors::NSFE_HEADER;
		else if (!memcmp(header, "NESM\2", 4))
			error = errors::NSF2_HEADER;
*/		else
		{
			error = errors::INVALID_HEADER;
		}
		FCEU_fclose(fp);
	}
	else
	{
		error = errors::OPEN_FAILED;
	}

	if (error)
	{
		switch (error)
		{
			case errors::OPEN_FAILED:
			{
				char buf[5120];
				snprintf(buf, sizeof(buf), "Error opening %s!", LoadedRomFName);
				showErrorMsgWindow( buf );
				break;
			}
			case errors::INVALID_HEADER:
				//MessageBox(parent, "Invalid NES header.", "NES Header Editor", MB_OK | MB_ICONERROR);
				showErrorMsgWindow( "Invalid NES header." );
				break;
			case errors::FDS_HEADER:
				//MessageBox(parent, "Editing header of an FDS file is not supported.", "NES Header Editor", MB_OK | MB_ICONERROR);
				showErrorMsgWindow("Editing header of an FDS file is not supported.");
				break;
			case errors::UNIF_HEADER:
				//MessageBox(parent, "Editing header of a UNIF file is not supported.", "NES Header Editor", MB_OK | MB_ICONERROR);
				showErrorMsgWindow("Editing header of a UNIF file is not supported.");
				break;
			case errors::NSF_HEADER:
//			case errors::NSF2_HEADER:
//			case errors::NSFE_HEADER:
				//MessageBox(parent, "Editing header of an NSF file is not supported.", "NES Header Editor", MB_OK | MB_ICONERROR);
				showErrorMsgWindow("Editing header of an NSF file is not supported.");
				break;
		}
		return false;
	}

	printf("Opened File: '%s'\n", LoadedRomFName );


	return true;
}
//----------------------------------------------------------------------------
bool iNesHeaderEditor_t::SaveINESFile(const char* path, iNES_HEADER* header)
{
	char buf[4096];
	const char* ext[] = { "nes", 0 };

	// Source file
	FCEUFILE* source = FCEU_fopen(LoadedRomFName, NULL, "rb", 0, -1, ext);
	if (!source)
	{
		char msg[5120];
		snprintf(msg, sizeof(msg), "Opening source file %s failed.", LoadedRomFName);
		showErrorMsgWindow(msg);
		return false;
	}

	// Destination file
	FILE* target = FCEUD_UTF8fopen(path, "wb");
	if (!target)
	{
		char msg[5120];
		snprintf(msg, sizeof(msg), "Creating target file %s failed.", path);
		showErrorMsgWindow(msg);
		return false;
	}

	memset(buf, 0, sizeof(buf));

	// Write the header first
	fwrite(header, 1, sizeof(iNES_HEADER), target);
	// Skip the original header
	FCEU_fseek(source, sizeof(iNES_HEADER), SEEK_SET);
	int len;
	while (len = FCEU_fread(buf, 1, sizeof(buf), source))
	{
		fwrite(buf, len, 1, target);
	}

	FCEU_fclose(source);
	fclose(target);

	return true;

}

//----------------------------------------------------------------------------
bool iNesHeaderEditor_t::openFile(void)
{
	int ret, useNativeFileDialogVal;
	QString filename;
	std::string last;
	std::string dir;
	QFileDialog  dialog(this, tr("Open NES File") );

	const QStringList filters(
			{ 
           "NES files (*.nes *.NES)",
           "Any files (*)"
         });

	dialog.setFileMode(QFileDialog::ExistingFile);

	dialog.setNameFilters( filters );

	dialog.setViewMode(QFileDialog::List);
	dialog.setFilter( QDir::AllEntries | QDir::AllDirs | QDir::Hidden );
	dialog.setLabelText( QFileDialog::Accept, tr("Open") );

	g_config->getOption ("SDL.LastOpenFile", &last );

	getDirFromFile( last.c_str(), dir );

	dialog.setDirectory( tr(dir.c_str()) );

	// Check config option to use native file dialog or not
	g_config->getOption ("SDL.UseNativeFileDialog", &useNativeFileDialogVal);

	dialog.setOption(QFileDialog::DontUseNativeDialog, !useNativeFileDialogVal);

	ret = dialog.exec();

	if ( ret )
	{
		QStringList fileList;
		fileList = dialog.selectedFiles();

		if ( fileList.size() > 0 )
		{
			filename = fileList[0];
		}
	}

   if ( filename.isNull() )
   {
      return false;
   }
	qDebug() << "selected file path : " << filename.toLocal8Bit();

	if ( GameInfo == NULL )
	{
		strncpy( LoadedRomFName, filename.toLocal8Bit().constData(), sizeof(LoadedRomFName)-1 );
		LoadedRomFName[sizeof(LoadedRomFName)-1] = 0;
	}

   return true;
}
//----------------------------------------------------------------------------
void iNesHeaderEditor_t::saveFileAs(void)
{
	int ret, useNativeFileDialogVal;
	QString filename;
	std::string last;
	std::string dir;
	QFileDialog  dialog(this, tr("Save iNES File") );

	dialog.setFileMode(QFileDialog::AnyFile);

	dialog.setNameFilter(tr("NES Files (*.nes *.NES) ;; All files (*)"));

	dialog.setViewMode(QFileDialog::List);
	dialog.setFilter( QDir::AllEntries | QDir::AllDirs | QDir::Hidden );
	dialog.setLabelText( QFileDialog::Accept, tr("Save") );
	dialog.setDefaultSuffix( tr(".nes") );

	getDirFromFile( LoadedRomFName, dir );

	dialog.setDirectory( tr(dir.c_str()) );

	// Check config option to use native file dialog or not
	g_config->getOption ("SDL.UseNativeFileDialog", &useNativeFileDialogVal);

	dialog.setOption(QFileDialog::DontUseNativeDialog, !useNativeFileDialogVal);

	ret = dialog.exec();

	if ( ret )
	{
		QStringList fileList;
		fileList = dialog.selectedFiles();

		if ( fileList.size() > 0 )
		{
			filename = fileList[0];
		}
	}

	if ( filename.isNull() )
   {
      return;
   }
	qDebug() << "selected file path : " << filename.toLocal8Bit();

	WriteHeaderData( iNesHdr );

	if ( SaveINESFile( filename.toLocal8Bit().constData(), iNesHdr ) )
	{
		// Error
	}
}
//----------------------------------------------------------------------------
void iNesHeaderEditor_t::setHeaderData(iNES_HEADER* header)
{
	int i;
	// Temporary buffers
	char buf[256];

	bool ines20 = (header->ROM_type2 & 0xC) == 8;
	bool unofficial = false;

	ToggleINES20( ines20 );

	iNes1Btn->setChecked( !ines20 );
	iNes2Btn->setChecked(  ines20 );

	// Mapper#
	int mapper = header->ROM_type >> 4 | header->ROM_type2 & 0xF0;
	if (ines20)
	{
		mapper |= (header->ROM_type3 & 0x0F) << 8;
	}
	for (i=0; i<mapperComboBox->count(); i++)
	{
		if ( mapperComboBox->itemData(i).toInt() > mapper )
		{
			snprintf( buf, sizeof(buf), "%i Unknown/unsupported", i);
			mapperComboBox->insertItem( i, tr(buf), mapper);
			mapperComboBox->setCurrentIndex(i);
			break;
		}
		else if ( mapperComboBox->itemData(i).toInt() == mapper )
		{
			mapperComboBox->setCurrentIndex(i);
			break;
		}
	}

	// Sub Mapper
	snprintf(buf, sizeof(buf), "%d", ines20 ? header->ROM_type3 >> 4 : 0);

	mapperSubEdit->setText( tr(buf) );

	// PRG ROM
	int prg_rom = header->ROM_size;
	if (ines20) 
	{
		if ((header->Upper_ROM_VROM_size & 0xF) == 0xF)
			prg_rom = pow(2, header->ROM_size >> 2) * ((header->ROM_size & 3) * 2 + 1);
		else {
			prg_rom |= (header->Upper_ROM_VROM_size & 0xF) << 8;
			prg_rom *= 1024 * 16;
		}
	}
	else
	{
		prg_rom *= 1024 * 16;
	}

	for (i=0; i<prgRomBox->count(); i++)
	{
		if ( prgRomBox->itemData(i).toInt() == prg_rom )
		{
			prgRomBox->setCurrentIndex(i); break;
		}
	}

	// PRG RAM
	int prg_ram = 0;
	if (ines20)
	{
		int shift = header->RAM_size & 0xF;
		if (shift)
		{
			prg_ram = 64 << shift;
		}

	} 
	else
	{
		if (!(header->RAM_size & 0x10) && header->ROM_type3)
		{
			prg_ram = (header->ROM_type3 ? 1 : header->ROM_type3 * 8) * 1024;
		}
	}

	for (i=0; i<prgRamBox->count(); i++)
	{
		if ( prgRamBox->itemData(i).toInt() == prg_ram )
		{
			prgRamBox->setCurrentIndex(i); break;
		}
	}

	// PRG NVRAM
	int prg_nvram = 0;
	if (ines20)
	{
		int shift = header->RAM_size >> 4;
		if (shift)
		{
			prg_nvram = 64 << shift;
		}
		battNvRamBox->hide();
		prgNvRamLbl->show();
		prgNvRamBox->show();
	} 
	else
	{
		prgNvRamLbl->hide();
		prgNvRamBox->hide();
		battNvRamBox->show();
		battNvRamBox->setChecked( header->ROM_type & 0x2 ? true : false );
	}

	for (i=0; i<prgNvRamBox->count(); i++)
	{
		if ( prgNvRamBox->itemData(i).toInt() == prg_nvram )
		{
			prgNvRamBox->setCurrentIndex(i); break;
		}
	}

	// CHR ROM
	int chr_rom = header->VROM_size;
	if (ines20)
	{
		if ((header->Upper_ROM_VROM_size & 0xF0) == 0xF0)
		{
			chr_rom = pow(2, header->VROM_size >> 2) * (((header->VROM_size & 3) * 2) + 1);
		}
		else
		{
			chr_rom |= (header->Upper_ROM_VROM_size & 0xF0) << 4;
			chr_rom *= 8 * 1024;
		}
	}
	else
	{
		chr_rom *= 8 * 1024;
	}

	for (i=0; i<chrRomBox->count(); i++)
	{
		if ( chrRomBox->itemData(i).toInt() == chr_rom )
		{
			chrRomBox->setCurrentIndex(i); break;
		}
	}

	// CHR RAM
	int chr_ram = 0;
	if (ines20)
	{
		int shift = header->VRAM_size & 0xF;
		if (shift)
		{
			chr_ram = 64 << shift;
		}
	}

	for (i=0; i<chrRamBox->count(); i++)
	{
		if ( chrRamBox->itemData(i).toInt() == chr_ram )
		{
			chrRamBox->setCurrentIndex(i); break;
		}
	}

	// CHR NVRAM
	int chr_nvram = 0;
	if (ines20)
	{
		int shift = header->VRAM_size >> 4;
		if (shift)
		{
			chr_nvram = 64 << shift;
		}
	}

	for (i=0; i<chrNvRamBox->count(); i++)
	{
		if ( chrNvRamBox->itemData(i).toInt() == chr_nvram )
		{
			chrNvRamBox->setCurrentIndex(i); break;
		}
	}

	// Mirroring
	if ( header->ROM_type & 8 )
	{
		horzMirrorBtn->setChecked(false);
		vertMirrorBtn->setChecked(false);
		fourMirrorBtn->setChecked(true);
	}
	else if ( header->ROM_type & 1 )
	{
		horzMirrorBtn->setChecked(false);
		vertMirrorBtn->setChecked(true);
		fourMirrorBtn->setChecked(false);
	}
	else 
	{
		horzMirrorBtn->setChecked(true);
		vertMirrorBtn->setChecked(false);
		fourMirrorBtn->setChecked(false);
	}

	// Region
	if (ines20)
	{
		int region = header->TV_system & 3;
		switch (region) 
		{
			case 0:
				// NTSC
				ntscRegionBtn->setChecked(true);
				break;
			case 1:
				// PAL
				palRegionBtn->setChecked(true);
				break;
			case 2:
				// Dual
				dualRegionBtn->setChecked(true);
				break;
			case 3:
				// Dendy
				dendyRegionBtn->setChecked(true);
				break;
		}
	}
	else 
	{
		// Only the unofficial 10th byte has a dual region, we must check it first.
		int region = header->RAM_size & 3;
		if (region == 3 || region == 1)
		{
			// Check the unofficial checkmark and the region code
			dualRegionBtn->setChecked(true);
			unofficial = true;
		}
		else
		{
			// When the region in unofficial byte is inconsistent with the official byte, based on the official byte
			if ( header->Upper_ROM_VROM_size & 1 )
			{
				palRegionBtn->setChecked(true);
			}
			else
			{
				ntscRegionBtn->setChecked(true);
			}
		}
	}

	// System
	int system = header->ROM_type2 & 3;
	switch (system)
	{
		default:
		// Normal
		case 0:
			normSysbtn->setChecked(true);
			break;
		// VS. System
		case 1:
			vsSysbtn->setChecked(true);
			break;
		// PlayChoice-10
		case 2:
			// PlayChoice-10 is an unofficial setting for ines 1.0
			plySysbtn->setChecked(true);
			unofficial = !ines20;
			break;
		// Extend
		case 3:
			// The 7th byte is different between ines 1.0 and 2.0, so we need to check it
			if (ines20) 
			{
				extSysbtn->setChecked(true);
			}
			break;
	}

	// it's quite ambiguous to put them here, but it's better to have a default selection than leave the dropdown blank, because user might switch to ines 2.0 anytime
	// Hardware type
	int hardware = header->VS_hardware >> 4;
	for (i=0; i<vsHwBox->count(); i++)
	{
		if ( vsHwBox->itemData(i).toInt() == hardware )
		{
			vsHwBox->setCurrentIndex(i); break;
		}
	}

	// PPU type
	int ppu = header->VS_hardware & 0xF;
	for (i=0; i<vsPpuBox->count(); i++)
	{
		if ( vsPpuBox->itemData(i).toInt() == ppu )
		{
			vsPpuBox->setCurrentIndex(i); break;
		}
	}
	// Extend Console
	for (i=0; i<extCslBox->count(); i++)
	{
		if ( extCslBox->itemData(i).toInt() == ppu )
		{
			extCslBox->setCurrentIndex(i); break;
		}
	}

	// Input Device:
	int input = header->expansion & 0x3F;
	for (i=0; i<inputDevBox->count(); i++)
	{
		if ( inputDevBox->itemData(i).toInt() == input )
		{
			inputDevBox->setCurrentIndex(i); break;
		}
	}

	// Miscellaneous ROM Area(s)
	snprintf(buf, sizeof(buf), "%d", header->misc_roms & 3);
	miscRomsEdit->setText( tr(buf) );

	// Trainer
	trainerCBox->setChecked( header->ROM_type & 4 ? true : false );

	// Unofficial Properties Checkmark
	iNesUnOfBox->setChecked( unofficial );

	// Switch the UI to the proper version
	ToggleINES20( ines20 );

}
//----------------------------------------------------------------------------
void iNesHeaderEditor_t::ToggleINES20(bool ines20)
{
	// only ines 2.0 has these values
	// these are always have when in 2.0
	// Submapper#
	mapperSubLbl->setEnabled(ines20);
	mapperSubEdit->setEnabled(ines20);
	// PRG NVRAM
	prgNvRamLbl->setEnabled(ines20);
	prgNvRamBox->setEnabled(ines20);
	// CHR RAM
	chrRamLbl->setEnabled(ines20);
	chrRamBox->setEnabled(ines20);
	// CHR NVRAM
	chrNvRamBox->setEnabled(ines20);
	chrNvRamLbl->setEnabled(ines20);
	// Dendy in Region Groupbox
	dendyRegionBtn->setEnabled(ines20);
	// Multiple in Regtion Groupbox
	dualRegionBtn->setEnabled(ines20);
	// Extend in System Groupbox
	extGroupBox->setEnabled(ines20);
	extSysbtn->setEnabled(ines20);
	// Input Device
	inputDevLbl->setEnabled(ines20);
	inputDevBox->setEnabled(ines20);
	// Miscellaneous ROMs
	miscRomsLbl->setEnabled(ines20);
	miscRomsEdit->setEnabled(ines20);
	//
	
	if ( ines20 )
	{
		battNvRamBox->hide();
		prgNvRamLbl->show();
		prgNvRamBox->show();
	}
	else
	{
		battNvRamBox->show();
		prgNvRamLbl->hide();
		prgNvRamBox->hide();
	}

	if ( ines20 )
	{
		if ( dendyRegionBtn->isChecked() )
		{
			dendyRegionBtn->setChecked(false);
			palRegionBtn->setChecked(true);
		}

		if ( extSysbtn->isChecked() )
		{
			extSysbtn->setChecked(false);
			normSysbtn->setChecked(true);
		}

	}

	// enable extend dialog only when ines 2.0 and extend button is checked
	ToggleExtendSystemList( ines20 && extSysbtn->isChecked() );

	// enable vs dialog only when ines 2.0 and vs button is checked
	ToggleVSSystemGroup( ines20 && vsSysbtn->isChecked() );

	iNesUnOfBox->setEnabled(!ines20);
	ToggleUnofficialPropertiesEnabled( ines20, iNesUnOfBox->isChecked() );

}
//----------------------------------------------------------------------------
void iNesHeaderEditor_t::ToggleUnofficialPropertiesEnabled(bool ines20, bool check)
{
	bool sub_enable = !ines20 && check;

	// Unofficial Properties only available in ines 1.0
	iNesUnOfGroupBox->setEnabled(sub_enable);
	// when unofficial properties is enabled or in ines 2.0 standard, Playchoice-10 in System groupbox is available
	plySysbtn->setEnabled(ines20 || sub_enable);

	ToggleUnofficialPrgRamPresent(ines20, check, iNesPrgRamBox->isChecked() );
	ToggleUnofficialExtraRegionCode(ines20, check, iNesDualRegBox->isChecked() );

	// Playchoice-10 is not available in ines 1.0 and unofficial is not checked, switch back to normal
	if (!ines20 && !check && plySysbtn->isChecked() )
	{
		normSysbtn->setChecked(true);
	}
}
//----------------------------------------------------------------------------
void iNesHeaderEditor_t::ToggleUnofficialExtraRegionCode(bool ines20, bool unofficial_check, bool check)
{
	// The unofficial byte to determine whether multiple region is valid

	// Multiple in Region Groupbox
	dualRegionBtn->setEnabled(ines20 || unofficial_check && check);

	// Dual region is not avalable when in ines 1.0 and extra region in unofficial is not checked, switch it back to NTSC 
	if (!ines20 && (!unofficial_check || !check) && dualRegionBtn->isChecked() )
	{
		ntscRegionBtn->setChecked(true);
	}
}
//----------------------------------------------------------------------------

void iNesHeaderEditor_t::ToggleUnofficialPrgRamPresent(bool ines20, bool unofficial_check, bool check)
{
	// The unofficial byte to determine wheter PRGRAM exists
	// PRG RAM
	bool enable = ines20 || !unofficial_check || check;
	// When unofficial 10th byte was not set, the PRG RAM is defaultly enabled
	prgRamLbl->setEnabled( enable );
	prgRamBox->setEnabled( enable );
}
//----------------------------------------------------------------------------
void iNesHeaderEditor_t::ToggleExtendSystemList(bool enable)
{
	// Extend combo box only enabled when in iNES 2.0 and Extend System was chosen
	// Extend combo box
	extGroupBox->setEnabled( enable );
	//EnableWindow(GetDlgItem(hwnd, IDC_EXTEND_SYSTEM_GROUP), enable);
	//EnableWindow(GetDlgItem(hwnd, IDC_SYSTEM_EXTEND_COMBO), enable);
	//EnableWindow(GetDlgItem(hwnd, IDC_EXTEND_SYSTEM_TEXT), enable);
}
//----------------------------------------------------------------------------
void iNesHeaderEditor_t::ToggleVSSystemGroup(bool enable)
{
	// VS System Groupbox only enabled when in iNES 2.0 and VS System in System groupbox is chosen
	// VS System Groupbox
	vsGroupBox->setEnabled(enable);
	// System
	//EnableWindow(GetDlgItem(hwnd, IDC_VS_SYSTEM_COMBO), enable);
	//EnableWindow(GetDlgItem(hwnd, IDC_VS_SYSTEM_TEXT), enable);
	// PPU
	//EnableWindow(GetDlgItem(hwnd, IDC_VS_PPU_COMBO), enable);
	//EnableWindow(GetDlgItem(hwnd, IDC_VS_PPU_TEXT), enable);
}
//----------------------------------------------------------------------------
bool iNesHeaderEditor_t::WriteHeaderData(iNES_HEADER* header)
{
	// Temporary buffers
	int idx;
	char buf[256];

	iNES_HEADER _header;
	memset(&_header, 0, sizeof(iNES_HEADER));

	// Check iNES 2.0
	bool ines20 = iNes2Btn->isChecked();
	// iNES 1.0 unofficial byte available
	bool unofficial = !ines20 && iNesUnOfBox->isChecked();

	// iNES 2.0 signature
	if (ines20)
	{
		_header.ROM_type2 |= 8;
	}

	// Mapper
	idx = mapperComboBox->currentIndex();
	int mapper = mapperComboBox->itemData(idx).toInt();

	if (mapper < 4096)
	{
		_header.ROM_type |= (mapper & 0xF) << 4;
		_header.ROM_type2 |= (mapper & 0xF0);
		if (mapper >= 256)
		{
			if (ines20)
			{
				_header.ROM_type3 |= mapper >> 8;
			}
			else
			{
				snprintf(buf, sizeof(buf), "Error: Mapper# should be less than %d in iNES %d.0 format.", 256, 1);
				showErrorMsgWindow(buf);
				return false;
			}
		}	
	}
	else 
	{
		snprintf(buf, sizeof(buf), "Mapper# should be less than %d in iNES %d.0 format.", 4096, 2);
		showErrorMsgWindow(buf);
		return false;
	}

	// Sub mapper
	if (ines20) 
	{
		strcpy( buf, mapperSubEdit->text().toLocal8Bit().constData() );
		int submapper;
		if (sscanf(buf, "%d", &submapper) > 0)
		{
			if (submapper < 16)
			{
				_header.ROM_type3 |= submapper << 4;
			}
			else
			{
				showErrorMsgWindow("Error: The sub mapper# should less than 16 in iNES 2.0 format.");
				return false;
			}
		} 
		else
		{
			showErrorMsgWindow("Error: The sub mapper# you have entered is invalid. Please enter a decimal number.");
			return false;
		}
	}

	// PRG ROM
	idx = prgRomBox->currentIndex();
	int prg_rom = prgRomBox->itemData(idx).toInt();

	if (prg_rom >= 0)
	{
		// max value which a iNES 2.0 header can hold
		if (prg_rom < 16 * 1024 * 0xEFF)
		{
			// try to fit the irregular value with the alternative way
			if (prg_rom % (16 * 1024) != 0)
			{
				if (ines20)
				{
					// try to calculate the nearest value
					bool fit = false;
					int result = 0x7FFFFFFF;
					for (int multiplier = 0; multiplier < 4; ++multiplier)
					{
						for (int exponent = 0; exponent < 64; ++exponent)
						{
							int new_result = pow(2, exponent) * (multiplier * 2 + 1);
							if (new_result == prg_rom)
							{
								_header.Upper_ROM_VROM_size |= 0xF;
								_header.ROM_size |= exponent << 2;
								_header.ROM_size |= multiplier & 3;
								fit = true;
								break;
							}
							if (new_result > prg_rom && result > new_result)
							{
								result = new_result;
							}
						}
						if (fit) break;
					}

					if (!fit)
					{
						int result10 = (prg_rom / 16 / 1024 + 1) * 16 * 1024;
						if (result10 < result)
						{
							result = result10;
						}
						char buf2[64];
						if (result % 1024 != 0)
						{
							snprintf(buf2, sizeof(buf2), "%dB", result);
						}
						else
						{
							snprintf(buf2, sizeof(buf2), "%dKB", result / 1024);
						}
						snprintf(buf, sizeof(buf), "PRG ROM size you entered is invalid in iNES 2.0, do you want to set to its nearest value %s?", buf2);
						showErrorMsgWindow(buf);
						//if (MessageBox(hwnd, buf, "Error", MB_YESNO | MB_ICONERROR) == IDYES)
						//{
						//	SetDlgItemText(hwnd, IDC_PRGROM_COMBO, buf2);
						//}
						//else
						//{
						//	SetFocus(GetDlgItem(hwnd, IDC_PRGROM_COMBO));
						//	return false;
						//}
						return false;
					}
				}
				else 
				{
					// ines 1.0 can't handle this kind of value
					showErrorMsgWindow("PRG ROM size must be multiple of 16KB in iNES 1.0");
					return false;
				}
			}
			// it's multiple size of 16KB
			else {
				// it can be fitted in iNES 1.0
				if (prg_rom < 16 * 1024 * 0xFF)
				{
					_header.ROM_size |= prg_rom / 16 / 1024 & 0xFF;
				}
				else
				{
					if (ines20)
					{
						_header.Upper_ROM_VROM_size |= prg_rom / 16 / 1024 >> 8 & 0xF;
					}
					else 
					{
						showErrorMsgWindow("PRG ROM size exceeded the limit of iNES 1.0 (4080KB).");
						return false;
					}
				}
			}
		}
		// A too large size
		else 
		{
			showErrorMsgWindow("PRG ROM size you entered is too large to fit into a cartridge, by the way this is an NES emulator, not for XBOX360 or PlayStation2.");
			return false;
		}
	} 
	else
	{
		return false;
	}

	// PRG RAM
	if (ines20 || !unofficial || iNesPrgRamBox->isChecked() )
	{
		idx = prgRamBox->currentIndex();
		int prg_ram = prgRamBox->itemData(idx).toInt();

		if (prg_ram >= 0)
		{
			if (ines20)
			{
				if (prg_ram < 64 << 0xF)
				{
					if (prg_ram % 64 == 0)
					{
						_header.RAM_size |= (int)log2(prg_ram / 64);
					}
					else
					{
						showErrorMsgWindow("Invalid PRG RAM size");
						return false;
					}
				}
				else 
				{
					showErrorMsgWindow("PRG RAM size exceeded the limit (4096KB)");
					return false;
				}
			}
			else 
			{
				if (prg_ram < 8 * 1024 * 255)
				{
					if (prg_ram % (8 * 1024) == 0)
					{
						_header.ROM_type3 |= prg_ram / 8 / 1024;
					}
					else 
					{
						showErrorMsgWindow("PRG RAM size must be multiple of 8KB in iNES 1.0");
						return false;
					}
				}
				else {
					showErrorMsgWindow("PRG RAM size exceeded the limit (2040KB)");
					return false;
				}
			}
		}
		else
		{
			return false;
		}
	}

	// PRG NVRAM
	if (ines20)
	{
		// only iNES 2.0 has value for PRG VMRAM
		idx = prgNvRamBox->currentIndex();
		int prg_nvram = prgNvRamBox->itemData(idx).toInt();

		if (prg_nvram >= 0)
		{
			if (prg_nvram < 64 << 0xF)
			{
				if (prg_nvram % 64 == 0)
				{
					_header.RAM_size |= (int)log2(prg_nvram / 64) << 4;
				}
				else
				{
					showErrorMsgWindow("Invalid PRG NVRAM size");
					return false;
				}
			}
			else
			{
				showErrorMsgWindow("PRG NVRAM size exceeded the limit (4096KB)");
				return false;
			}

			if (prg_nvram != 0)
			{
				_header.ROM_type |= 2;
			}
		}
		else
		{
			return false;
		}
	}
	else 
	{
		// iNES 1.0 is much simpler, it has only 1 bit for check
		if ( battNvRamBox->isChecked() )
		{
			_header.ROM_type |= 2;
		}
	}

	// CHR ROM
	idx = chrRomBox->currentIndex();
	int chr_rom = chrRomBox->itemData(idx).toInt();

	if (chr_rom >= 0)
	{
		// max value which a iNES 2.0 header can hold
		if (chr_rom < 8 * 1024 * 0xEFF)
		{
			// try to fit the irregular value with the alternative way
			if (chr_rom % (8 * 1024) != 0)
			{
				if (ines20)
				{
					// try to calculate the nearest value
					bool fit = false;
					int result = 0;
					for (int multiplier = 0; multiplier < 4; ++multiplier)
					{
						for (int exponent = 0; exponent < 64; ++exponent)
						{
							int new_result = pow(2, exponent) * (multiplier * 2 + 1);
							if (new_result == chr_rom)
							{
								_header.Upper_ROM_VROM_size |= 0xF0;
								_header.VROM_size |= exponent << 2;
								_header.VROM_size |= multiplier & 3;
								fit = true;
								break;
							}
							if (result > new_result && new_result > chr_rom)
							{
								result = new_result;
							}
						}
						if (fit) break;
					}

					if (!fit)
					{
						int result10 = (chr_rom / 1024 / 8 + 1) * 8 * 1024;
						if (result10 < result)
						{
							result = result10;
						}
						char buf2[64];
						if (result % 1024 != 0)
						{
							snprintf(buf2, sizeof(buf2), "%dB", result);
						}
						else
						{
							snprintf(buf2, sizeof(buf2), "%dKB", result / 1024);
						}
						snprintf(buf, sizeof(buf), "CHR ROM size you entered is invalid in iNES 2.0, do you want to set to its nearest value %s?", buf2);
						showErrorMsgWindow(buf);
						//if (MessageBox(hwnd, buf, "Error", MB_YESNO | MB_ICONERROR) == IDYES)
						//	SetDlgItemText(hwnd, IDC_CHRROM_COMBO, buf2);
						//else
						//{
						//	SetFocus(GetDlgItem(hwnd, IDC_CHRROM_COMBO));
						//	return false;
						//}
						return false;
					}
				}
				else 
				{
					// ines 1.0 can't handle this kind of value
					showErrorMsgWindow("CHR ROM size must be multiple of 8KB in iNES 1.0");
					return false;
				}
			}
			// it's multiple size of 8KB
			else {
				// it can be fitted in iNES 1.0
				if (chr_rom < 8 * 1024 * 0xFF)
				{
					_header.VROM_size |= chr_rom / 8 / 1024 & 0xFF;
				}
				else
				{
					if (ines20)
					{
						_header.Upper_ROM_VROM_size |= chr_rom / 8 / 1024 >> 4 & 0xF0;
					}
					else
					{
						showErrorMsgWindow("CHR ROM size exceeded the limit of iNES 1.0 (2040KB).");
						return false;
					}
				}
			}
		}
		// A too large size
		else 
		{
			showErrorMsgWindow("CHR ROM size you entered cannot be fitted in iNES 2.0.");
			return false;
		}
	}
	else
	{
		return false;
	}

	// CHR RAM
	if (ines20)
	{
		// only iNES 2.0 has value for CHR RAM
		idx = chrRamBox->currentIndex();
		int chr_ram = chrRamBox->itemData(idx).toInt();

		if (chr_ram >= 0)
		{
			if (chr_ram < 64 << 0xF)
			{
				if (chr_ram % 64 == 0)
				{
					_header.VRAM_size |= (int)log2(chr_ram / 64);
				}
				else
				{
					showErrorMsgWindow("Invalid CHR RAM size");
					return false;
				}
			}
			else 
			{
				showErrorMsgWindow("CHR RAM size exceeded the limit (4096KB)");
				return false;
			}
		}
		else
		{
			return false;
		}
	}

	// CHR NVRAM
	if (ines20)
	{
		// only iNES 2.0 has value for CHR NVRAM
		idx = chrNvRamBox->currentIndex();
		int chr_nvram = chrNvRamBox->itemData(idx).toInt();

		if (chr_nvram >= 0)
		{
			if (chr_nvram < 64 << 0xF)
			{
				if (chr_nvram % 64 == 0)
				{
					_header.VRAM_size |= (int)log2(chr_nvram / 64) << 4;
				}
				else 
				{
					showErrorMsgWindow("Invalid CHR NVRAM size");
					return false;
				}
			}
			else 
			{
				showErrorMsgWindow("CHR NVRAM size exceeded the limit (4096KB)");
				return false;
			}

			if (chr_nvram != 0)
			{
				_header.ROM_type |= 2;
			}
		}
		else
		{
			return false;
		}
	}

	// Mirroring
	if ( fourMirrorBtn->isChecked() )
	{
		_header.ROM_type |= 8;
	}
	else if ( vertMirrorBtn->isChecked() )
	{
		_header.ROM_type |= 1;
	}

	// Region
	if ( palRegionBtn->isChecked() )
	{
		if (ines20)
		{
			_header.TV_system |= 1;
		}
		else 
		{
			_header.Upper_ROM_VROM_size |= 1;
			if ( unofficial && iNesDualRegBox->isChecked() )
			{
				_header.RAM_size |= 2;
			}
		}
	}
	else if ( dualRegionBtn->isChecked() )
	{
		if (ines20)
		{
			_header.TV_system |= 2;
		}
		else
		{
			_header.RAM_size |= 3;
		}
	}
	else if ( dendyRegionBtn->isChecked() )
	{
		_header.TV_system |= 3;
	}

	// System
	if ( vsSysbtn->isChecked() )
	{
		_header.ROM_type2 |= 1;
		if (ines20) {
			// VS System type
			idx = vsHwBox->currentIndex();
			int system = vsHwBox->itemData(idx).toInt();

			if (system <= 0xF)
			{
				_header.VS_hardware |= (system & 0xF) << 4;
			}
			else
			{
				showErrorMsgWindow("Invalid VS System hardware type.");
				return false;
			}
			// VS PPU type
			idx = vsPpuBox->currentIndex();
			int ppu = vsPpuBox->itemData(idx).toInt();
			if ( (ppu >= 0) && (system <= 0xF) )
			{
				_header.VS_hardware |= ppu & 0xF;
			}
			else
			{
				showErrorMsgWindow("Invalid VS System PPU type.");
				return false;
			}
		}
	}
	else if ( plySysbtn->isChecked() )
	{
		_header.ROM_type2 |= 2;
	}
	else if ( extSysbtn->isChecked() )
	{
		// Extend System
		_header.ROM_type2 |= 3;
		idx = extCslBox->currentIndex();
		int extend = extCslBox->itemData(idx).toInt();

		if (extend >= 0)
		{
			_header.VS_hardware |= extend & 0x3F;
		}
		else
		{
			showErrorMsgWindow("Invalid extend system type");
			return false;
		}			
	}

	// Input device
	if (ines20)
	{
		idx = inputDevBox->currentIndex();
		int input = inputDevBox->itemData(idx).toInt();
		if (input <= 0x3F)
		{
			_header.expansion |= input & 0x3F;
		}
		else
		{
			showErrorMsgWindow("Invalid input device.");
			return false;
		}
	}

	// Miscellanous ROM(s)
	if (ines20)
	{
		strcpy( buf, miscRomsEdit->text().toLocal8Bit().constData() );
		int misc_roms = 0;
		if (sscanf(buf, "%d", &misc_roms) < 1)
		{
			showErrorMsgWindow("Invalid miscellaneous ROM(s) count. If you don't know what value should be, we recommend to set it to 0.");
			return false;
		}
		if (misc_roms > 3)
		{
			showErrorMsgWindow("Miscellaneous ROM(s) count has exceeded the limit of iNES 2.0 (3)");
			return false;
		}
		_header.misc_roms |= misc_roms & 3;
	}

	// iNES 1.0 unofficial properties
	if ( !ines20 && unofficial )
	{
		// bus conflict configure in unoffcial bit of iNES 1.0
		if ( iNesBusCfltBox->isChecked() )
		{
			_header.RAM_size |= 0x20;
		}
		// PRG RAM non exist bit flag
		if ( iNesPrgRamBox->isChecked() )
		{
			_header.RAM_size |= 0x10;
		}
	}

	// Trainer
	if ( trainerCBox->isChecked() )
	{
		_header.ROM_type |= 4;
	}

	bool fceux_support = false;
	for (int i = 0; bmap[i].init; ++i)
	{
		if (mapper == bmap[i].number)
		{
			fceux_support = true;
			break;
		}
	}

	if (!fceux_support)
	{
		int ret;
		QMessageBox msgBox(this);

		snprintf(buf, sizeof(buf), "FCEUX doesn't support iNES Mapper# %d, this is not a serious problem, but the ROM will not be run in FCEUX properly.\nDo you want to continue?", mapper);

		msgBox.setIcon( QMessageBox::Warning );
		msgBox.setText( tr(buf) );
		ret = msgBox.exec();

		if (ret == 0)
		{
			return false;
		}
	}

	memcpy(_header.ID, "NES\x1A", 4);

	if (header)
	{
		memcpy(header, &_header, sizeof(iNES_HEADER));
	}

	return true;
}
//----------------------------------------------------------------------------
void iNesHeaderEditor_t::printHeader(iNES_HEADER* _header)
{
	printf("header: ");
	printf("%02X ", _header->ID[0]);
	printf("%02X ", _header->ID[1]);
	printf("%02X ", _header->ID[2]);
	printf("%02X ", _header->ID[3]);
	printf("%02X ", _header->ROM_size);
	printf("%02X ", _header->VROM_size);
	printf("%02X ", _header->ROM_type);
	printf("%02X ", _header->ROM_type2);
	printf("%02X ", _header->ROM_type3);
	printf("%02X ", _header->Upper_ROM_VROM_size);
	printf("%02X ", _header->RAM_size);
	printf("%02X ", _header->VRAM_size);
	printf("%02X ", _header->TV_system);
	printf("%02X ", _header->VS_hardware);
	printf("%02X ", _header->misc_roms);
	printf("%02X\n", _header->expansion);
}
//----------------------------------------------------------------------------
