mirror of
https://github.com/0ldsk00l/nestopia.git
synced 2025-04-02 10:31:51 -04:00
1723 lines
44 KiB
C++
1723 lines
44 KiB
C++
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Nestopia - NES/Famicom emulator written in C++
|
|
//
|
|
// Copyright (C) 2003-2008 Martin Freij
|
|
//
|
|
// This file is part of Nestopia.
|
|
//
|
|
// Nestopia 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.
|
|
//
|
|
// Nestopia 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 Nestopia; if not, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include "NstIoStream.hpp"
|
|
#include "NstResourceString.hpp"
|
|
#include "NstWindowParam.hpp"
|
|
#include "NstWindowDropFiles.hpp"
|
|
#include "NstWindowUser.hpp"
|
|
#include "NstManagerPaths.hpp"
|
|
#include "NstDialogCheats.hpp"
|
|
#include "NstApplicationInstance.hpp"
|
|
#include "../core/NstXml.hpp"
|
|
#include "../core/api/NstApiCartridge.hpp"
|
|
#include <CommCtrl.h>
|
|
|
|
namespace Nestopia
|
|
{
|
|
namespace Window
|
|
{
|
|
class Cheats::MainDialog
|
|
{
|
|
public:
|
|
|
|
MainDialog(Codes&,Codes&,bool&,bool&,Searcher&,Managers::Emulator&,const Managers::Paths&);
|
|
|
|
private:
|
|
|
|
struct Handlers;
|
|
|
|
class Section
|
|
{
|
|
public:
|
|
|
|
Section(uint,Dialog&,Codes&,const bool&,bool&,Searcher&,Managers::Emulator&,const Managers::Paths&);
|
|
|
|
void UpdateHexView() const;
|
|
|
|
private:
|
|
|
|
struct Handlers;
|
|
|
|
class CodeDialog
|
|
{
|
|
public:
|
|
|
|
struct Context
|
|
{
|
|
Code code;
|
|
bool edit;
|
|
bool& hex;
|
|
Searcher& searcher;
|
|
|
|
Context(bool& h,Searcher& s)
|
|
: edit(false), hex(h), searcher(s) {}
|
|
};
|
|
|
|
CodeDialog(Managers::Emulator&,const Managers::Paths&,Context&);
|
|
|
|
private:
|
|
|
|
struct Handlers;
|
|
|
|
ibool OnInitDialog(Param&);
|
|
ibool OnDestroy(Param&);
|
|
|
|
ibool OnCmdSubmit (Param&);
|
|
ibool OnCmdValidate (Param&);
|
|
ibool OnCmdGameCurrent (Param&);
|
|
ibool OnCmdGameBrowse (Param&);
|
|
ibool OnCmdHex (Param&);
|
|
ibool OnCmdType (Param&);
|
|
ibool OnSearchType (Param&);
|
|
ibool OnCmdReset (Param&);
|
|
|
|
void OnItemChanged(const NMHDR&);
|
|
|
|
void UpdateInput() const;
|
|
void UpdateHexView(bool) const;
|
|
void UpdateSearchList() const;
|
|
|
|
bool GetRawCode (NesCode&) const;
|
|
bool GetGenieCode (NesCode&) const;
|
|
bool GetRockyCode (NesCode&) const;
|
|
|
|
void SetRawCode (const NesCode&) const;
|
|
void SetGenieCode (const NesCode&) const;
|
|
void SetRockyCode (const NesCode&) const;
|
|
|
|
uint GetSearchValue(uint) const;
|
|
void SetSearchValue(uint,uint) const;
|
|
|
|
void AddSearchEntry(Control::ListView,uint) const;
|
|
|
|
Context& context;
|
|
Managers::Emulator& emulator;
|
|
const Managers::Paths& paths;
|
|
Dialog dialog;
|
|
Control::NotificationHandler notificationHandler;
|
|
|
|
public:
|
|
|
|
bool Open()
|
|
{
|
|
return dialog.Open();
|
|
}
|
|
};
|
|
|
|
enum
|
|
{
|
|
ADD,
|
|
EDIT,
|
|
REMOVE,
|
|
IMPORT,
|
|
EXPORT,
|
|
CLEAR,
|
|
NUM_CONTROLS
|
|
};
|
|
|
|
enum Column
|
|
{
|
|
COLUMN_CODE,
|
|
COLUMN_ADDRESS,
|
|
COLUMN_VALUE,
|
|
COLUMN_COMPARE,
|
|
COLUMN_GAME,
|
|
COLUMN_DESCRIPTION
|
|
};
|
|
|
|
enum
|
|
{
|
|
NUM_COLUMNS = 6
|
|
};
|
|
|
|
int Sorter(const void*,const void*);
|
|
void RefreshListView();
|
|
void SetCode(int=-1);
|
|
void AddCodes(const Codes&);
|
|
void AddToListView(const Code&) const;
|
|
|
|
void OnInitDialog (Param&);
|
|
void OnDropFiles (Param&);
|
|
|
|
ibool OnCmdAdd (Param&);
|
|
ibool OnCmdEdit (Param&);
|
|
ibool OnCmdRemove (Param&);
|
|
ibool OnCmdExport (Param&);
|
|
ibool OnCmdImport (Param&);
|
|
ibool OnCmdClear (Param&);
|
|
|
|
void OnKeyDown (const NMHDR&);
|
|
void OnItemChanged (const NMHDR&);
|
|
void OnInsertItem (const NMHDR&);
|
|
void OnDeleteItem (const NMHDR&);
|
|
void OnColumnClick (const NMHDR&);
|
|
|
|
const uint id;
|
|
Codes& codes;
|
|
const bool& showHexMainDialog;
|
|
bool& showHexSubDialogs;
|
|
Searcher& searcher;
|
|
const Dialog& dialog;
|
|
Control::NotificationHandler notificationHandler;
|
|
Control::ListView listView;
|
|
Control::Generic controls[NUM_CONTROLS];
|
|
Column sortColumns[NUM_COLUMNS];
|
|
Managers::Emulator& emulator;
|
|
const Managers::Paths& paths;
|
|
};
|
|
|
|
ibool OnInitDialog (Param&);
|
|
ibool OnCmdShowHex (Param&);
|
|
|
|
Dialog dialog;
|
|
Section permanentView;
|
|
Section temporaryView;
|
|
bool& showHex;
|
|
|
|
public:
|
|
|
|
void Open()
|
|
{
|
|
dialog.Open();
|
|
}
|
|
};
|
|
|
|
Cheats::Cheats(Managers::Emulator& e,const Configuration& cfg,const Managers::Paths& p)
|
|
:
|
|
paths ( p ),
|
|
emulator ( e )
|
|
{
|
|
Configuration::ConstSection cheats( cfg["cheats"] );
|
|
|
|
showHexMainDialog = cheats["show-hex-main-view"].Yes();
|
|
showHexSubDialogs = cheats["show-hex-code-view"].Yes();
|
|
|
|
for (uint i=0; i < MAX_CODES; ++i)
|
|
{
|
|
if (Configuration::ConstSection cheat=cheats["cheat"][i])
|
|
{
|
|
Code code;
|
|
|
|
uint data = cheat["address"].Int();
|
|
|
|
if (data > 0xFFFF)
|
|
continue;
|
|
|
|
code.address = data;
|
|
|
|
data = cheat["value"].Int();
|
|
|
|
if (data > 0xFF)
|
|
continue;
|
|
|
|
code.value = data;
|
|
|
|
if (Configuration::ConstSection compare=cheat["compare"])
|
|
{
|
|
data = compare.Int();
|
|
|
|
if (data > 0xFF)
|
|
continue;
|
|
|
|
code.compare = data;
|
|
}
|
|
|
|
code.enabled = !cheat["enabled"].No();
|
|
|
|
code.crc = cheat["game"].Int();
|
|
|
|
code.description = cheat["description"].Str();
|
|
code.description.Trim();
|
|
|
|
codes[PERMANENT_CODES].insert( code );
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
Cheats::~Cheats()
|
|
{
|
|
}
|
|
|
|
void Cheats::Save(Configuration& cfg) const
|
|
{
|
|
Configuration::Section cheats( cfg["cheats"] );
|
|
|
|
cheats["show-hex-main-view"].YesNo() = showHexMainDialog;
|
|
cheats["show-hex-code-view"].YesNo() = showHexSubDialogs;
|
|
|
|
uint i = 0;
|
|
for (Codes::const_iterator it(codes[PERMANENT_CODES].begin()), end(codes[PERMANENT_CODES].end()); it != end; ++it)
|
|
{
|
|
Configuration::Section cheat( cheats["cheat"][i++] );
|
|
|
|
cheat[ "enabled" ].YesNo() = it->enabled;
|
|
|
|
if (it->crc)
|
|
cheat[ "game" ].Str() = HexString( 32, it->crc ).Ptr();
|
|
|
|
cheat[ "address" ].Str() = HexString( 16, it->address ).Ptr();
|
|
cheat[ "value" ].Str() = HexString( 8, it->value ).Ptr();
|
|
|
|
if (it->compare != Code::NO_COMPARE)
|
|
cheat["compare"].Str() = HexString( 8, it->compare ).Ptr();
|
|
|
|
if (it->description.Length())
|
|
cheat["description"].Str() = it->description.Ptr();
|
|
}
|
|
}
|
|
|
|
void Cheats::Flush()
|
|
{
|
|
searcher.filter = Searcher::NO_FILTER;
|
|
searcher.a = 0;
|
|
searcher.b = 0;
|
|
|
|
codes[TEMPORARY_CODES].clear();
|
|
}
|
|
|
|
void Cheats::Open()
|
|
{
|
|
MainDialog( codes[PERMANENT_CODES], codes[TEMPORARY_CODES], showHexMainDialog, showHexSubDialogs, searcher, emulator, paths ).Open();
|
|
}
|
|
|
|
bool Cheats::Load(const Path& path)
|
|
{
|
|
return Import( codes[TEMPORARY_CODES], path );
|
|
}
|
|
|
|
bool Cheats::Save(const Path& path) const
|
|
{
|
|
return Export( codes[TEMPORARY_CODES], path );
|
|
}
|
|
|
|
bool Cheats::Import(Codes& codes,const Path& path)
|
|
{
|
|
try
|
|
{
|
|
typedef Nes::Core::Xml Xml;
|
|
Xml xml;
|
|
|
|
{
|
|
Io::Stream::In stream( path );
|
|
xml.Read( stream );
|
|
}
|
|
|
|
if (!xml.GetRoot().IsType( L"cheats" ))
|
|
return false;
|
|
|
|
for (Xml::Node node(xml.GetRoot().GetFirstChild()); node && codes.size() < MAX_CODES; node=node.GetNextSibling())
|
|
{
|
|
if (!node.IsType( L"cheat" ))
|
|
continue;
|
|
|
|
Code code;
|
|
|
|
if (const Xml::Node address=node.GetChild( L"address" ))
|
|
{
|
|
uint v;
|
|
|
|
if (0xFFFF < (v=address.GetUnsignedValue()))
|
|
continue;
|
|
|
|
code.address = v;
|
|
|
|
if (const Xml::Node value=node.GetChild( L"value" ))
|
|
{
|
|
if (0xFF < (v=value.GetUnsignedValue()))
|
|
continue;
|
|
|
|
code.value = v;
|
|
}
|
|
|
|
if (const Xml::Node compare=node.GetChild( L"compare" ))
|
|
{
|
|
if (0xFF < (v=compare.GetUnsignedValue()))
|
|
continue;
|
|
|
|
code.compare = v;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NesCode nesCode;
|
|
|
|
if (const Xml::Node genie=node.GetChild( L"genie" ))
|
|
{
|
|
if (NES_FAILED(Nes::Cheats::GameGenieDecode( String::Heap<char>(genie.GetValue()).Ptr(), nesCode )))
|
|
continue;
|
|
}
|
|
else if (const Xml::Node rocky=node.GetChild( L"rocky" ))
|
|
{
|
|
if (NES_FAILED(Nes::Cheats::ProActionRockyDecode( String::Heap<char>(rocky.GetValue()).Ptr(), nesCode )))
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
|
|
code.FromNesCode( nesCode );
|
|
}
|
|
|
|
code.description = node.GetChild( L"description" ).GetValue();
|
|
code.enabled = !node.GetAttribute( L"enabled" ).IsValue( L"0" );
|
|
code.crc = node.GetAttribute( L"game" ).GetUnsignedValue();
|
|
|
|
codes.insert( code );
|
|
}
|
|
}
|
|
catch (...)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Cheats::Export(const Codes& codes,const Path& path)
|
|
{
|
|
if (!path.Length() || codes.empty())
|
|
return false;
|
|
|
|
try
|
|
{
|
|
typedef Nes::Core::Xml Xml;
|
|
|
|
Xml xml;
|
|
Xml::Node root( xml.GetRoot() );
|
|
|
|
root = xml.Create( L"cheats" );
|
|
root.AddAttribute( L"version", L"1.0" );
|
|
|
|
for (Codes::const_iterator it(codes.begin()), end(codes.end()); it != end; ++it)
|
|
{
|
|
Xml::Node node( root.AddChild( L"cheat" ) );
|
|
node.AddAttribute( L"enabled", it->enabled ? L"1" : L"0" );
|
|
|
|
if (it->crc)
|
|
node.AddAttribute( L"game", HexString( 32, it->crc ).Ptr() );
|
|
|
|
char buffer[9];
|
|
|
|
if (NES_SUCCEEDED(Nes::Cheats::GameGenieEncode( it->ToNesCode(), buffer )))
|
|
node.AddChild( L"genie", HeapString(buffer).Ptr() );
|
|
|
|
if (NES_SUCCEEDED(Nes::Cheats::ProActionRockyEncode( it->ToNesCode(), buffer )))
|
|
node.AddChild( L"rocky", HeapString(buffer).Ptr() );
|
|
|
|
node.AddChild( L"address", HexString( 16, it->address ).Ptr() );
|
|
node.AddChild( L"value", HexString( 8, it->value ).Ptr() );
|
|
|
|
if (it->compare != Code::NO_COMPARE)
|
|
node.AddChild( L"compare", HexString( 8, it->compare ).Ptr() );
|
|
|
|
if (it->description.Length())
|
|
node.AddChild( L"description", it->description.Ptr() );
|
|
}
|
|
|
|
Io::Stream::Out stream( path );
|
|
xml.Write( root, stream );
|
|
}
|
|
catch (...)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
struct Cheats::MainDialog::Handlers
|
|
{
|
|
static const MsgHandler::Entry<MainDialog> messages[];
|
|
static const MsgHandler::Entry<MainDialog> commands[];
|
|
};
|
|
|
|
const MsgHandler::Entry<Cheats::MainDialog> Cheats::MainDialog::Handlers::messages[] =
|
|
{
|
|
{ WM_INITDIALOG, &MainDialog::OnInitDialog }
|
|
};
|
|
|
|
const MsgHandler::Entry<Cheats::MainDialog> Cheats::MainDialog::Handlers::commands[] =
|
|
{
|
|
{ IDC_CHEATS_SHOW_HEX, &MainDialog::OnCmdShowHex }
|
|
};
|
|
|
|
Cheats::MainDialog::MainDialog
|
|
(
|
|
Codes& permanentCodes,
|
|
Codes& temporaryCodes,
|
|
bool& showHexMainDialog,
|
|
bool& showHexSubDialogs,
|
|
Searcher& searcher,
|
|
Managers::Emulator& emulator,
|
|
const Managers::Paths& paths
|
|
)
|
|
:
|
|
dialog ( IDD_CHEATS, this, Handlers::messages, Handlers::commands ),
|
|
permanentView ( 0, dialog, permanentCodes, showHexMainDialog, showHexSubDialogs, searcher, emulator, paths ),
|
|
temporaryView ( 1, dialog, temporaryCodes, showHexMainDialog, showHexSubDialogs, searcher, emulator, paths ),
|
|
showHex ( showHexMainDialog )
|
|
{
|
|
}
|
|
|
|
ibool Cheats::MainDialog::OnInitDialog(Param&)
|
|
{
|
|
dialog.CheckBox( IDC_CHEATS_SHOW_HEX ).Check( showHex );
|
|
return true;
|
|
}
|
|
|
|
ibool Cheats::MainDialog::OnCmdShowHex(Param& param)
|
|
{
|
|
if (param.Button().Clicked())
|
|
{
|
|
showHex = dialog.CheckBox( IDC_CHEATS_SHOW_HEX ).Checked();
|
|
permanentView.UpdateHexView();
|
|
temporaryView.UpdateHexView();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
struct Cheats::MainDialog::Section::Handlers
|
|
{
|
|
static const Control::NotificationHandler::Entry<Section> notifications[];
|
|
};
|
|
|
|
const Control::NotificationHandler::Entry<Cheats::MainDialog::Section> Cheats::MainDialog::Section::Handlers::notifications[] =
|
|
{
|
|
{ LVN_KEYDOWN, &Section::OnKeyDown },
|
|
{ LVN_ITEMCHANGED, &Section::OnItemChanged },
|
|
{ LVN_INSERTITEM, &Section::OnInsertItem },
|
|
{ LVN_DELETEITEM, &Section::OnDeleteItem },
|
|
{ LVN_COLUMNCLICK, &Section::OnColumnClick }
|
|
};
|
|
|
|
Cheats::MainDialog::Section::Section
|
|
(
|
|
uint i,
|
|
Dialog& d,
|
|
Codes& c,
|
|
const bool& h,
|
|
bool& a,
|
|
Searcher& s,
|
|
Managers::Emulator& e,
|
|
const Managers::Paths& p
|
|
)
|
|
:
|
|
id ( i ),
|
|
codes ( c ),
|
|
showHexMainDialog ( h ),
|
|
showHexSubDialogs ( a ),
|
|
searcher ( s ),
|
|
dialog ( d ),
|
|
notificationHandler ( i == 0 ? IDC_CHEATS_STATIC_CODES : IDC_CHEATS_TEMP_CODES, d.Messages(), this, Handlers::notifications ),
|
|
emulator ( e ),
|
|
paths ( p )
|
|
{
|
|
static const MsgHandler::Entry<Section> commands[2][6] =
|
|
{
|
|
{
|
|
{ IDC_CHEATS_STATIC_ADD, &Section::OnCmdAdd },
|
|
{ IDC_CHEATS_STATIC_EDIT, &Section::OnCmdEdit },
|
|
{ IDC_CHEATS_STATIC_REMOVE, &Section::OnCmdRemove },
|
|
{ IDC_CHEATS_STATIC_EXPORT, &Section::OnCmdExport },
|
|
{ IDC_CHEATS_STATIC_IMPORT, &Section::OnCmdImport },
|
|
{ IDC_CHEATS_STATIC_CLEAR, &Section::OnCmdClear }
|
|
},
|
|
{
|
|
{ IDC_CHEATS_TEMP_ADD, &Section::OnCmdAdd },
|
|
{ IDC_CHEATS_TEMP_EDIT, &Section::OnCmdEdit },
|
|
{ IDC_CHEATS_TEMP_REMOVE, &Section::OnCmdRemove },
|
|
{ IDC_CHEATS_TEMP_EXPORT, &Section::OnCmdExport },
|
|
{ IDC_CHEATS_TEMP_IMPORT, &Section::OnCmdImport },
|
|
{ IDC_CHEATS_TEMP_CLEAR, &Section::OnCmdClear }
|
|
}
|
|
};
|
|
|
|
d.Commands().Add( this, commands[i != 0] );
|
|
|
|
static const MsgHandler::HookEntry<Section> hooks[] =
|
|
{
|
|
{ WM_INITDIALOG, &Section::OnInitDialog },
|
|
{ WM_DROPFILES, &Section::OnDropFiles }
|
|
};
|
|
|
|
d.Messages().Hooks().Add( this, hooks );
|
|
|
|
for (uint i=0; i < NUM_COLUMNS; ++i)
|
|
sortColumns[i] = static_cast<Column>(i);
|
|
}
|
|
|
|
void Cheats::MainDialog::Section::OnInitDialog(Param& param)
|
|
{
|
|
listView = Control::ListView( param.hWnd, id == 0 ? IDC_CHEATS_STATIC_CODES : IDC_CHEATS_TEMP_CODES );
|
|
listView.StyleEx() = LVS_EX_FULLROWSELECT|LVS_EX_CHECKBOXES;
|
|
|
|
listView.Columns().Clear();
|
|
|
|
for (uint i=0; i < NUM_COLUMNS; ++i)
|
|
{
|
|
static const ushort columns[NUM_COLUMNS] =
|
|
{
|
|
IDS_CHEAT_CODE,
|
|
IDS_CHEAT_ADDRESS,
|
|
IDS_CHEAT_VALUE,
|
|
IDS_CHEAT_COMPARE,
|
|
IDS_CHEAT_GAME,
|
|
IDS_CHEAT_DESCRIPTION
|
|
};
|
|
|
|
listView.Columns().Insert( i, Resource::String(columns[i]) );
|
|
}
|
|
|
|
controls[ ADD ] = Control::Generic( param.hWnd, id ? IDC_CHEATS_TEMP_ADD : IDC_CHEATS_STATIC_ADD );
|
|
controls[ EDIT ] = Control::Generic( param.hWnd, id ? IDC_CHEATS_TEMP_EDIT : IDC_CHEATS_STATIC_EDIT );
|
|
controls[ REMOVE ] = Control::Generic( param.hWnd, id ? IDC_CHEATS_TEMP_REMOVE : IDC_CHEATS_STATIC_REMOVE );
|
|
controls[ IMPORT ] = Control::Generic( param.hWnd, id ? IDC_CHEATS_TEMP_IMPORT : IDC_CHEATS_STATIC_IMPORT );
|
|
controls[ EXPORT ] = Control::Generic( param.hWnd, id ? IDC_CHEATS_TEMP_EXPORT : IDC_CHEATS_STATIC_EXPORT );
|
|
controls[ CLEAR ] = Control::Generic( param.hWnd, id ? IDC_CHEATS_TEMP_CLEAR : IDC_CHEATS_STATIC_CLEAR );
|
|
|
|
controls[ EDIT ].Disable();
|
|
controls[ REMOVE ].Disable();
|
|
controls[ EXPORT ].Disable();
|
|
controls[ CLEAR ].Disable();
|
|
|
|
if (!codes.empty())
|
|
{
|
|
listView.Reserve( codes.size() );
|
|
|
|
for (Codes::iterator it(codes.begin()), end(codes.end()); it != end; ++it)
|
|
AddToListView( *it );
|
|
}
|
|
|
|
RefreshListView();
|
|
}
|
|
|
|
ibool Cheats::MainDialog::Section::OnCmdAdd(Param& param)
|
|
{
|
|
if (param.Button().Clicked())
|
|
SetCode();
|
|
|
|
return true;
|
|
}
|
|
|
|
ibool Cheats::MainDialog::Section::OnCmdEdit(Param& param)
|
|
{
|
|
if (param.Button().Clicked())
|
|
SetCode( listView.Selection().GetIndex() );
|
|
|
|
return true;
|
|
}
|
|
|
|
ibool Cheats::MainDialog::Section::OnCmdRemove(Param& param)
|
|
{
|
|
if (param.Button().Clicked())
|
|
{
|
|
NST_VERIFY( !codes.empty() );
|
|
listView.Selection().Delete();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
ibool Cheats::MainDialog::Section::OnCmdExport(Param& param)
|
|
{
|
|
if (param.Button().Clicked() && !codes.empty())
|
|
{
|
|
Path path( paths.BrowseSave( Managers::Paths::File::XML, Managers::Paths::SUGGEST, paths.GetCheatPath() ) );
|
|
paths.FixFile( Managers::Paths::File::XML, path );
|
|
|
|
if (path.Length())
|
|
{
|
|
if (path.FileExists() && User::Confirm( IDS_CHEATS_EXPORTEXISTING ))
|
|
{
|
|
Codes combinedCodes( codes );
|
|
|
|
if (!Import( combinedCodes, path ))
|
|
User::Warn( IDS_CHEATS_EXPORTEXISTING_ERROR );
|
|
|
|
if (!Export( combinedCodes, path ))
|
|
User::Fail( IDS_FILE_ERR_INVALID );
|
|
}
|
|
else
|
|
{
|
|
if (!Export( codes, path ))
|
|
User::Fail( IDS_FILE_ERR_INVALID );
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
ibool Cheats::MainDialog::Section::OnCmdImport(Param& param)
|
|
{
|
|
if (param.Button().Clicked())
|
|
{
|
|
const Path path( paths.BrowseLoad( Managers::Paths::File::XML, paths.GetCheatPath() ) );
|
|
|
|
if (path.Length())
|
|
{
|
|
Codes newCodes;
|
|
|
|
if (Import( newCodes, path ))
|
|
AddCodes( newCodes );
|
|
else
|
|
User::Fail( IDS_FILE_ERR_INVALID );
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
ibool Cheats::MainDialog::Section::OnCmdClear(Param& param)
|
|
{
|
|
if (param.Button().Clicked())
|
|
{
|
|
NST_VERIFY( !codes.empty() );
|
|
listView.Clear();
|
|
RefreshListView();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Cheats::MainDialog::Section::AddCodes(const Codes& newCodes)
|
|
{
|
|
bool refresh = false;
|
|
|
|
for (Codes::const_iterator it(newCodes.begin()), end(newCodes.end()); it != end; ++it)
|
|
{
|
|
std::pair<Codes::iterator,bool> result( codes.insert( *it ) );
|
|
|
|
if (result.second)
|
|
{
|
|
refresh = true;
|
|
AddToListView( *result.first );
|
|
}
|
|
}
|
|
|
|
if (refresh)
|
|
RefreshListView();
|
|
}
|
|
|
|
void Cheats::MainDialog::Section::UpdateHexView() const
|
|
{
|
|
for (uint i=0, n=listView.Size(); i < n; ++i)
|
|
{
|
|
const Code* const code = static_cast<Code*>(static_cast<void*>(listView[i].Data()));
|
|
NST_VERIFY( code );
|
|
|
|
if (code)
|
|
{
|
|
listView[i].Text( COLUMN_VALUE ) << (showHexMainDialog ? HexString( 8, code->value ).Ptr() : (String::Stack<8>() << code->value).Ptr());
|
|
|
|
if (code->compare != Code::NO_COMPARE)
|
|
listView[i].Text( COLUMN_COMPARE ) << (showHexMainDialog ? HexString( 8, code->compare ).Ptr() : (String::Stack<8>() << code->compare).Ptr());
|
|
}
|
|
}
|
|
}
|
|
|
|
void Cheats::MainDialog::Section::OnKeyDown(const NMHDR& nmhdr)
|
|
{
|
|
switch (reinterpret_cast<const NMLVKEYDOWN&>(nmhdr).wVKey)
|
|
{
|
|
case VK_INSERT:
|
|
|
|
SetCode();
|
|
break;
|
|
|
|
case VK_DELETE:
|
|
|
|
listView.Selection().Delete();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Cheats::MainDialog::Section::OnItemChanged(const NMHDR& nmhdr)
|
|
{
|
|
const NMLISTVIEW& nm = reinterpret_cast<const NMLISTVIEW&>(nmhdr);
|
|
|
|
if ((nm.uOldState ^ nm.uNewState) & LVIS_SELECTED)
|
|
{
|
|
controls[ REMOVE ].Enable( nm.uNewState & LVIS_SELECTED );
|
|
controls[ EDIT ].Enable( nm.uNewState & LVIS_SELECTED );
|
|
}
|
|
|
|
if ((nm.uOldState ^ nm.uNewState) & LVIS_STATEIMAGEMASK)
|
|
{
|
|
NST_VERIFY( nm.lParam );
|
|
|
|
if (nm.lParam)
|
|
{
|
|
// As documented on MSDN the image index for the checked box is 2 (unchecked is 1)
|
|
reinterpret_cast<Code*>(nm.lParam)->enabled = ((nm.uNewState & LVIS_STATEIMAGEMASK) == INDEXTOSTATEIMAGEMASK( 2 ));
|
|
}
|
|
}
|
|
}
|
|
|
|
void Cheats::MainDialog::Section::OnInsertItem(const NMHDR&)
|
|
{
|
|
if (listView.Size() == 1)
|
|
{
|
|
controls[ CLEAR ].Enable();
|
|
controls[ EXPORT ].Enable();
|
|
}
|
|
}
|
|
|
|
void Cheats::MainDialog::Section::OnDeleteItem(const NMHDR& nmhdr)
|
|
{
|
|
const Code* const code = reinterpret_cast<Code*>(reinterpret_cast<const NMLISTVIEW&>(nmhdr).lParam);
|
|
NST_VERIFY( code );
|
|
|
|
if (code)
|
|
{
|
|
Codes::iterator it(codes.find( *code ));
|
|
NST_VERIFY( it != codes.end() );
|
|
|
|
if (it != codes.end())
|
|
codes.erase( it );
|
|
}
|
|
|
|
if (listView.Size() == 1)
|
|
{
|
|
controls[ CLEAR ].Disable();
|
|
controls[ EXPORT ].Disable();
|
|
controls[ REMOVE ].Disable();
|
|
}
|
|
}
|
|
|
|
void Cheats::MainDialog::Section::OnColumnClick(const NMHDR& nmhdr)
|
|
{
|
|
uint column = reinterpret_cast<const NMLISTVIEW&>(nmhdr).iSubItem;
|
|
NST_VERIFY( column <= NUM_COLUMNS );
|
|
|
|
if (column <= NUM_COLUMNS)
|
|
{
|
|
sortColumns[0] = static_cast<Column>(column);
|
|
|
|
for (uint i=1, next=0; i < NUM_COLUMNS; ++i, ++next)
|
|
{
|
|
if (next == column)
|
|
next++;
|
|
|
|
sortColumns[i] = static_cast<Column>(next);
|
|
}
|
|
|
|
if (!codes.empty())
|
|
listView.Sort( this, &Section::Sorter );
|
|
}
|
|
}
|
|
|
|
void Cheats::MainDialog::Section::AddToListView(const Code& code) const
|
|
{
|
|
const uint index = listView.Add( code.ToGenieCode(), &code, code.enabled );
|
|
|
|
listView[index].Text( COLUMN_ADDRESS ) << HexString( 16, code.address ).Ptr();
|
|
listView[index].Text( COLUMN_VALUE ) << (showHexMainDialog ? HexString( 8, code.value ).Ptr() : (String::Stack<8>() << code.value).Ptr());
|
|
listView[index].Text( COLUMN_COMPARE ) << (code.compare != Code::NO_COMPARE ? showHexMainDialog ? HexString( 8, code.compare ).Ptr() : (String::Stack<8>() << code.compare).Ptr() : L"-");
|
|
listView[index].Text( COLUMN_GAME ) << (code.crc ? HexString( 32, code.crc ).Ptr() : L"-");
|
|
listView[index].Text( COLUMN_DESCRIPTION ) << (code.description.Length() ? code.description.Ptr() : L"-");
|
|
}
|
|
|
|
void Cheats::MainDialog::Section::OnDropFiles(Param& param)
|
|
{
|
|
DropFiles dropFiles( param );
|
|
|
|
if (dropFiles.Inside( listView.GetHandle() ))
|
|
{
|
|
Codes newCodes;
|
|
|
|
for (uint i=0, n=dropFiles.Size(); i < n; ++i)
|
|
Import( newCodes, dropFiles[i] );
|
|
|
|
AddCodes( newCodes );
|
|
}
|
|
}
|
|
|
|
void Cheats::MainDialog::Section::RefreshListView()
|
|
{
|
|
if (!codes.empty())
|
|
listView.Sort( this, &Section::Sorter );
|
|
|
|
listView.Columns().Align();
|
|
}
|
|
|
|
int Cheats::MainDialog::Section::Sorter(const void* obj1,const void* obj2)
|
|
{
|
|
const Code& a = *static_cast<const Code*>( obj1 );
|
|
const Code& b = *static_cast<const Code*>( obj2 );
|
|
|
|
for (uint i=0; i < NUM_COLUMNS; ++i)
|
|
{
|
|
switch (sortColumns[i])
|
|
{
|
|
case COLUMN_CODE:
|
|
{
|
|
const Code::GenieCode ga( a.ToGenieCode() );
|
|
const Code::GenieCode gb( b.ToGenieCode() );
|
|
|
|
if (gb == L"-")
|
|
{
|
|
if (ga != L"-")
|
|
return -1;
|
|
}
|
|
else if (ga == L"-")
|
|
{
|
|
if (gb != L"-")
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
if (ga < gb)
|
|
return -1;
|
|
|
|
if (ga > gb)
|
|
return 1;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
case COLUMN_ADDRESS:
|
|
|
|
if (a.address < b.address)
|
|
return -1;
|
|
|
|
if (a.address > b.address)
|
|
return 1;
|
|
|
|
continue;
|
|
|
|
case COLUMN_VALUE:
|
|
|
|
if (a.value < b.value)
|
|
return -1;
|
|
|
|
if (a.value > b.value)
|
|
return 1;
|
|
|
|
continue;
|
|
|
|
case COLUMN_COMPARE:
|
|
|
|
if (a.compare < b.compare)
|
|
return -1;
|
|
|
|
if (a.compare > b.compare)
|
|
return 1;
|
|
|
|
continue;
|
|
|
|
case COLUMN_GAME:
|
|
|
|
if ((a.crc ? a.crc : ~0U) < (b.crc ? b.crc : ~0U))
|
|
return -1;
|
|
|
|
if ((a.crc ? a.crc : ~0U) > (b.crc ? b.crc : ~0U))
|
|
return 1;
|
|
|
|
continue;
|
|
|
|
case COLUMN_DESCRIPTION:
|
|
|
|
if (!b.description.Length())
|
|
{
|
|
if (a.description.Length())
|
|
return -1;
|
|
}
|
|
else if (!a.description.Length())
|
|
{
|
|
if (b.description.Length())
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
if (a.description < b.description)
|
|
return -1;
|
|
|
|
if (a.description > b.description)
|
|
return 1;
|
|
}
|
|
continue;
|
|
|
|
default: NST_UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void Cheats::MainDialog::Section::SetCode(int index)
|
|
{
|
|
CodeDialog::Context context( showHexSubDialogs, searcher );
|
|
|
|
if (index >= 0)
|
|
{
|
|
if (const void* const existing = listView[index].Data())
|
|
{
|
|
const Codes::const_iterator it( codes.find( *static_cast<const Code*>(existing) ) );
|
|
|
|
if (it != codes.end())
|
|
{
|
|
context.code = *it;
|
|
context.edit = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (CodeDialog( emulator, paths, context ).Open())
|
|
{
|
|
if (index >= 0)
|
|
listView[index].Delete();
|
|
|
|
const std::pair<Codes::iterator,bool> result( codes.insert( context.code ) );
|
|
|
|
if (result.second)
|
|
{
|
|
AddToListView( *result.first );
|
|
RefreshListView();
|
|
}
|
|
}
|
|
}
|
|
|
|
struct Cheats::MainDialog::Section::CodeDialog::Handlers
|
|
{
|
|
static const MsgHandler::Entry<CodeDialog> messages[];
|
|
static const MsgHandler::Entry<CodeDialog> commands[];
|
|
};
|
|
|
|
const MsgHandler::Entry<Cheats::MainDialog::Section::CodeDialog> Cheats::MainDialog::Section::CodeDialog::Handlers::messages[] =
|
|
{
|
|
{ WM_INITDIALOG, &CodeDialog::OnInitDialog }
|
|
};
|
|
|
|
const MsgHandler::Entry<Cheats::MainDialog::Section::CodeDialog> Cheats::MainDialog::Section::CodeDialog::Handlers::commands[] =
|
|
{
|
|
{ IDC_CHEATS_ADDCODE_SUBMIT, &CodeDialog::OnCmdSubmit },
|
|
{ IDC_CHEATS_ADDCODE_VALIDATE, &CodeDialog::OnCmdValidate },
|
|
{ IDC_CHEATS_ADDCODE_GAME_CURRENT, &CodeDialog::OnCmdGameCurrent },
|
|
{ IDC_CHEATS_ADDCODE_GAME_BROWSE, &CodeDialog::OnCmdGameBrowse },
|
|
{ IDC_CHEATS_ADDCODE_USE_HEX, &CodeDialog::OnCmdHex },
|
|
{ IDC_CHEATS_ADDCODE_USE_RAW, &CodeDialog::OnCmdType },
|
|
{ IDC_CHEATS_ADDCODE_USE_GENIE, &CodeDialog::OnCmdType },
|
|
{ IDC_CHEATS_ADDCODE_USE_ROCKY, &CodeDialog::OnCmdType },
|
|
{ IDC_CHEATS_ADDCODE_SEARCH_NONE, &CodeDialog::OnSearchType },
|
|
{ IDC_CHEATS_ADDCODE_SEARCH_R0_A_R1_B, &CodeDialog::OnSearchType },
|
|
{ IDC_CHEATS_ADDCODE_SEARCH_R0_A_R0R1_B, &CodeDialog::OnSearchType },
|
|
{ IDC_CHEATS_ADDCODE_SEARCH_R0R1_B, &CodeDialog::OnSearchType },
|
|
{ IDC_CHEATS_ADDCODE_SEARCH_R0_L_R1, &CodeDialog::OnSearchType },
|
|
{ IDC_CHEATS_ADDCODE_SEARCH_R0_G_R1, &CodeDialog::OnSearchType },
|
|
{ IDC_CHEATS_ADDCODE_SEARCH_R0_N_R1, &CodeDialog::OnSearchType },
|
|
{ IDC_CHEATS_ADDCODE_SEARCH_RESET, &CodeDialog::OnCmdReset }
|
|
};
|
|
|
|
Cheats::MainDialog::Section::CodeDialog::CodeDialog(Managers::Emulator& e,const Managers::Paths& p,Context& c)
|
|
:
|
|
context (c),
|
|
emulator (e),
|
|
paths (p),
|
|
dialog (IDD_CHEATS_ADDCODE,this,Handlers::messages,Handlers::commands),
|
|
notificationHandler (IDC_CHEATS_ADDCODE_SEARCH_LIST,dialog.Messages())
|
|
{
|
|
static const Control::NotificationHandler::Entry<CodeDialog> notifications[] =
|
|
{
|
|
{ LVN_ITEMCHANGED, &CodeDialog::OnItemChanged }
|
|
};
|
|
|
|
notificationHandler.Add( this, notifications );
|
|
}
|
|
|
|
ibool Cheats::MainDialog::Section::CodeDialog::OnInitDialog(Param&)
|
|
{
|
|
dialog.Edit( IDC_CHEATS_ADDCODE_ADDRESS ).Limit( 4 );
|
|
dialog.Edit( IDC_CHEATS_ADDCODE_DESC ).Limit( 256 );
|
|
dialog.Edit( IDC_CHEATS_ADDCODE_GENIE ).Limit( 8 );
|
|
dialog.Edit( IDC_CHEATS_ADDCODE_ROCKY ).Limit( 8 );
|
|
dialog.Edit( IDC_CHEATS_ADDCODE_GAME ).Limit( 8 );
|
|
|
|
dialog.RadioButton( IDC_CHEATS_ADDCODE_USE_RAW ).Check( true );
|
|
dialog.RadioButton( IDC_CHEATS_ADDCODE_USE_GENIE ).Check( false );
|
|
dialog.RadioButton( IDC_CHEATS_ADDCODE_USE_ROCKY ).Check( false );
|
|
|
|
dialog.Control( IDC_CHEATS_ADDCODE_GAME_CURRENT ).Enable( Nes::Cartridge(emulator).GetProfile() );
|
|
|
|
UpdateInput();
|
|
|
|
if (emulator.IsGameOn())
|
|
{
|
|
Control::ListView listView( dialog.ListView(IDC_CHEATS_ADDCODE_SEARCH_LIST) );
|
|
|
|
listView.StyleEx() = LVS_EX_FULLROWSELECT;
|
|
|
|
static wcstring const columns[] =
|
|
{
|
|
L"Index", L"R0", L"R1"
|
|
};
|
|
|
|
listView.Columns().Set( columns );
|
|
|
|
if (context.searcher.filter == Searcher::NO_FILTER)
|
|
{
|
|
context.searcher.filter = IDC_CHEATS_ADDCODE_SEARCH_NONE;
|
|
std::memcpy( context.searcher.ram, Nes::Cheats(emulator).GetRam(), Nes::Cheats::RAM_SIZE );
|
|
|
|
dialog.Control( IDC_CHEATS_ADDCODE_SEARCH_RESET ).Disable();
|
|
}
|
|
else if (std::memcmp( context.searcher.ram, Nes::Cheats(emulator).GetRam(), Nes::Cheats::RAM_SIZE ) == 0)
|
|
{
|
|
dialog.Control( IDC_CHEATS_ADDCODE_SEARCH_RESET ).Disable();
|
|
}
|
|
|
|
dialog.RadioButton( context.searcher.filter ).Check();
|
|
}
|
|
else
|
|
{
|
|
NST_COMPILE_ASSERT
|
|
(
|
|
IDC_CHEATS_ADDCODE_SEARCH_TEXT_B - IDC_CHEATS_ADDCODE_SEARCH_TEXT_A == 1 &&
|
|
IDC_CHEATS_ADDCODE_SEARCH_A - IDC_CHEATS_ADDCODE_SEARCH_TEXT_A == 2 &&
|
|
IDC_CHEATS_ADDCODE_SEARCH_B - IDC_CHEATS_ADDCODE_SEARCH_TEXT_A == 3 &&
|
|
IDC_CHEATS_ADDCODE_SEARCH_LIST - IDC_CHEATS_ADDCODE_SEARCH_TEXT_A == 4 &&
|
|
IDC_CHEATS_ADDCODE_SEARCH_NONE - IDC_CHEATS_ADDCODE_SEARCH_TEXT_A == 5 &&
|
|
IDC_CHEATS_ADDCODE_SEARCH_R0_A_R1_B - IDC_CHEATS_ADDCODE_SEARCH_TEXT_A == 6 &&
|
|
IDC_CHEATS_ADDCODE_SEARCH_R0_A_R0R1_B - IDC_CHEATS_ADDCODE_SEARCH_TEXT_A == 7 &&
|
|
IDC_CHEATS_ADDCODE_SEARCH_R0R1_B - IDC_CHEATS_ADDCODE_SEARCH_TEXT_A == 8 &&
|
|
IDC_CHEATS_ADDCODE_SEARCH_R0_L_R1 - IDC_CHEATS_ADDCODE_SEARCH_TEXT_A == 9 &&
|
|
IDC_CHEATS_ADDCODE_SEARCH_R0_G_R1 - IDC_CHEATS_ADDCODE_SEARCH_TEXT_A == 10 &&
|
|
IDC_CHEATS_ADDCODE_SEARCH_R0_N_R1 - IDC_CHEATS_ADDCODE_SEARCH_TEXT_A == 11 &&
|
|
IDC_CHEATS_ADDCODE_SEARCH_RESET - IDC_CHEATS_ADDCODE_SEARCH_TEXT_A == 12
|
|
);
|
|
|
|
for (uint i=IDC_CHEATS_ADDCODE_SEARCH_TEXT_A; i <= IDC_CHEATS_ADDCODE_SEARCH_RESET; ++i)
|
|
dialog.Control( i ).Disable();
|
|
}
|
|
|
|
dialog.CheckBox( IDC_CHEATS_ADDCODE_USE_HEX ).Check( context.hex );
|
|
UpdateHexView( false );
|
|
|
|
if (context.edit)
|
|
{
|
|
const NesCode nesCode( context.code.ToNesCode() );
|
|
|
|
SetRawCode( nesCode );
|
|
SetGenieCode( nesCode );
|
|
SetRockyCode( nesCode );
|
|
|
|
if (context.code.crc)
|
|
dialog.Edit( IDC_CHEATS_ADDCODE_GAME ) << HexString( 32, context.code.crc, true ).Ptr();
|
|
|
|
if (context.code.description.Length())
|
|
dialog.Edit( IDC_CHEATS_ADDCODE_DESC ) << context.code.description.Ptr();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
ibool Cheats::MainDialog::Section::CodeDialog::OnDestroy(Param&)
|
|
{
|
|
context.searcher.a = GetSearchValue( IDC_CHEATS_ADDCODE_SEARCH_A );
|
|
context.searcher.b = GetSearchValue( IDC_CHEATS_ADDCODE_SEARCH_B );
|
|
|
|
return true;
|
|
}
|
|
|
|
ibool Cheats::MainDialog::Section::CodeDialog::OnCmdSubmit(Param& param)
|
|
{
|
|
if (param.Button().Clicked())
|
|
{
|
|
bool result;
|
|
NesCode nesCode;
|
|
|
|
if (dialog.RadioButton( IDC_CHEATS_ADDCODE_USE_RAW ).Checked())
|
|
{
|
|
result = GetRawCode( nesCode );
|
|
}
|
|
else if (dialog.RadioButton( IDC_CHEATS_ADDCODE_USE_GENIE ).Checked())
|
|
{
|
|
result = GetGenieCode( nesCode );
|
|
}
|
|
else
|
|
{
|
|
result = GetRockyCode( nesCode );
|
|
}
|
|
|
|
if (result)
|
|
{
|
|
context.code.FromNesCode( nesCode );
|
|
|
|
HeapString crc;
|
|
|
|
if (dialog.Edit( IDC_CHEATS_ADDCODE_GAME ) >> crc)
|
|
{
|
|
crc.Insert( 0, "0x" );
|
|
crc >> context.code.crc;
|
|
}
|
|
else
|
|
{
|
|
context.code.crc = 0;
|
|
}
|
|
|
|
dialog.Edit( IDC_CHEATS_ADDCODE_DESC ) >> context.code.description;
|
|
|
|
dialog.Close( true );
|
|
}
|
|
else
|
|
{
|
|
User::Warn( IDS_CHEATS_INVALID_CODE, IDS_CHEATS );
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
ibool Cheats::MainDialog::Section::CodeDialog::OnCmdValidate(Param& param)
|
|
{
|
|
if (param.Button().Clicked())
|
|
{
|
|
uint id;
|
|
NesCode code;
|
|
|
|
if (dialog.RadioButton( IDC_CHEATS_ADDCODE_USE_RAW ).Checked())
|
|
{
|
|
id = GetRawCode( code ) ? IDC_CHEATS_ADDCODE_USE_RAW : 0;
|
|
}
|
|
else if (dialog.RadioButton( IDC_CHEATS_ADDCODE_USE_GENIE ).Checked())
|
|
{
|
|
id = GetGenieCode( code ) ? IDC_CHEATS_ADDCODE_USE_GENIE : 0;
|
|
}
|
|
else
|
|
{
|
|
id = GetRockyCode( code ) ? IDC_CHEATS_ADDCODE_USE_ROCKY : 0;
|
|
}
|
|
|
|
if (id)
|
|
{
|
|
if (id != IDC_CHEATS_ADDCODE_USE_RAW)
|
|
SetRawCode( code );
|
|
|
|
if (id != IDC_CHEATS_ADDCODE_USE_GENIE)
|
|
SetGenieCode( code );
|
|
|
|
if (id != IDC_CHEATS_ADDCODE_USE_ROCKY)
|
|
SetRockyCode( code );
|
|
}
|
|
|
|
dialog.Edit( IDC_CHEATS_ADDCODE_RESULT ) << Resource::String(id ? IDS_CHEATS_RESULT_VALID : IDS_CHEATS_RESULT_INVALID);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
ibool Cheats::MainDialog::Section::CodeDialog::OnCmdGameCurrent(Param& param)
|
|
{
|
|
if (param.Button().Clicked())
|
|
{
|
|
if (const Nes::Cartridge::Profile* const profile = Nes::Cartridge(emulator).GetProfile())
|
|
dialog.Edit( IDC_CHEATS_ADDCODE_GAME ) << HexString( 32, profile->hash.GetCrc32(), true ).Ptr();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
ibool Cheats::MainDialog::Section::CodeDialog::OnCmdGameBrowse(Param& param)
|
|
{
|
|
if (param.Button().Clicked())
|
|
{
|
|
Managers::Paths::File file;
|
|
|
|
if (paths.Load( file, Managers::Paths::File::CARTRIDGE|Managers::Paths::File::ARCHIVE ))
|
|
{
|
|
bool loaded = false;
|
|
|
|
Io::Stream::In stream( file.data );
|
|
Nes::Cartridge::Profile profile;
|
|
|
|
switch (file.type)
|
|
{
|
|
case Managers::Paths::File::INES: loaded = NES_SUCCEEDED(Nes::Cartridge::ReadInes( stream, Nes::Machine::FAVORED_NES_NTSC, profile )); break;
|
|
case Managers::Paths::File::UNIF: loaded = NES_SUCCEEDED(Nes::Cartridge::ReadUnif( stream, Nes::Machine::FAVORED_NES_NTSC, profile )); break;
|
|
case Managers::Paths::File::XML: loaded = NES_SUCCEEDED(Nes::Cartridge::ReadRomset( stream, Nes::Machine::FAVORED_NES_NTSC, false, profile )); break;
|
|
}
|
|
|
|
if (loaded)
|
|
{
|
|
NST_VERIFY( profile.hash.GetCrc32() );
|
|
|
|
if (const uint crc = profile.hash.GetCrc32())
|
|
dialog.Edit( IDC_CHEATS_ADDCODE_GAME ) << HexString( 32, crc, true ).Ptr();
|
|
else
|
|
dialog.Edit( IDC_CHEATS_ADDCODE_GAME ).Clear();
|
|
}
|
|
else
|
|
{
|
|
User::Fail( IDS_FILE_ERR_INVALID );
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
ibool Cheats::MainDialog::Section::CodeDialog::OnCmdHex(Param& param)
|
|
{
|
|
if (param.Button().Clicked())
|
|
UpdateHexView( true );
|
|
|
|
return true;
|
|
}
|
|
|
|
ibool Cheats::MainDialog::Section::CodeDialog::OnCmdType(Param& param)
|
|
{
|
|
if (param.Button().Clicked())
|
|
{
|
|
const uint cmd = param.Button().GetId();
|
|
|
|
dialog.RadioButton( IDC_CHEATS_ADDCODE_USE_RAW ).Check( cmd == IDC_CHEATS_ADDCODE_USE_RAW );
|
|
dialog.RadioButton( IDC_CHEATS_ADDCODE_USE_GENIE ).Check( cmd == IDC_CHEATS_ADDCODE_USE_GENIE );
|
|
dialog.RadioButton( IDC_CHEATS_ADDCODE_USE_ROCKY ).Check( cmd == IDC_CHEATS_ADDCODE_USE_ROCKY );
|
|
|
|
UpdateInput();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
ibool Cheats::MainDialog::Section::CodeDialog::OnSearchType(Param& param)
|
|
{
|
|
if (param.Button().Clicked())
|
|
{
|
|
context.searcher.filter = param.Button().GetId();
|
|
UpdateSearchList();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
ibool Cheats::MainDialog::Section::CodeDialog::OnCmdReset(Param& param)
|
|
{
|
|
if (param.Button().Clicked())
|
|
{
|
|
dialog.Control( IDC_CHEATS_ADDCODE_SEARCH_RESET ).Disable();
|
|
std::memcpy( context.searcher.ram, Nes::Cheats(emulator).GetRam(), Nes::Cheats::RAM_SIZE );
|
|
UpdateSearchList();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Cheats::MainDialog::Section::CodeDialog::OnItemChanged(const NMHDR& nmhdr)
|
|
{
|
|
const NMLISTVIEW& nm = reinterpret_cast<const NMLISTVIEW&>(nmhdr);
|
|
|
|
if ((nm.uNewState & LVIS_SELECTED) > (nm.uOldState & LVIS_SELECTED))
|
|
{
|
|
NST_VERIFY( nm.lParam <= 0xFFFF );
|
|
|
|
if (nm.lParam <= 0xFFFF)
|
|
{
|
|
dialog.Edit( IDC_CHEATS_ADDCODE_ADDRESS ) << HexString( 16, nm.lParam, true ).Ptr();
|
|
|
|
if (dialog.RadioButton( IDC_CHEATS_ADDCODE_USE_RAW ).Unchecked())
|
|
{
|
|
dialog.RadioButton( IDC_CHEATS_ADDCODE_USE_RAW ).Check( true );
|
|
dialog.RadioButton( IDC_CHEATS_ADDCODE_USE_GENIE ).Check( false );
|
|
dialog.RadioButton( IDC_CHEATS_ADDCODE_USE_ROCKY ).Check( false );
|
|
|
|
UpdateInput();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Cheats::MainDialog::Section::CodeDialog::UpdateInput() const
|
|
{
|
|
const bool raw = dialog.RadioButton( IDC_CHEATS_ADDCODE_USE_RAW ).Checked();
|
|
const bool genie = !raw && dialog.RadioButton( IDC_CHEATS_ADDCODE_USE_GENIE ).Checked();
|
|
const bool rocky = !raw && !genie;
|
|
|
|
dialog.Control( IDC_CHEATS_ADDCODE_VALUE ).Enable( raw );
|
|
dialog.Control( IDC_CHEATS_ADDCODE_COMPARE ).Enable( raw );
|
|
dialog.Control( IDC_CHEATS_ADDCODE_ADDRESS ).Enable( raw );
|
|
dialog.Control( IDC_CHEATS_ADDCODE_GENIE ).Enable( genie );
|
|
dialog.Control( IDC_CHEATS_ADDCODE_ROCKY ).Enable( rocky );
|
|
}
|
|
|
|
void Cheats::MainDialog::Section::CodeDialog::UpdateHexView(bool changed) const
|
|
{
|
|
NesCode code;
|
|
|
|
if (changed)
|
|
{
|
|
changed = GetRawCode( code );
|
|
|
|
if (emulator.IsGameOn())
|
|
{
|
|
context.searcher.a = GetSearchValue( IDC_CHEATS_ADDCODE_SEARCH_A );
|
|
context.searcher.b = GetSearchValue( IDC_CHEATS_ADDCODE_SEARCH_B );
|
|
}
|
|
|
|
context.hex = dialog.CheckBox( IDC_CHEATS_ADDCODE_USE_HEX ).Checked();
|
|
}
|
|
|
|
const uint digits = context.hex ? 2 : 3;
|
|
|
|
dialog.Edit( IDC_CHEATS_ADDCODE_VALUE ).Limit( digits );
|
|
dialog.Edit( IDC_CHEATS_ADDCODE_COMPARE ).Limit( digits );
|
|
dialog.Edit( IDC_CHEATS_ADDCODE_VALUE ).SetNumberOnly( digits == 3 );
|
|
dialog.Edit( IDC_CHEATS_ADDCODE_COMPARE ).SetNumberOnly( digits == 3 );
|
|
|
|
if (changed)
|
|
{
|
|
SetRawCode( code );
|
|
}
|
|
else
|
|
{
|
|
dialog.Edit( IDC_CHEATS_ADDCODE_VALUE ).Clear();
|
|
dialog.Edit( IDC_CHEATS_ADDCODE_COMPARE ).Clear();
|
|
}
|
|
|
|
if (emulator.IsGameOn())
|
|
{
|
|
dialog.Edit( IDC_CHEATS_ADDCODE_SEARCH_A ).Limit( digits );
|
|
dialog.Edit( IDC_CHEATS_ADDCODE_SEARCH_B ).Limit( digits );
|
|
dialog.Edit( IDC_CHEATS_ADDCODE_SEARCH_A ).SetNumberOnly( digits == 3 );
|
|
dialog.Edit( IDC_CHEATS_ADDCODE_SEARCH_B ).SetNumberOnly( digits == 3 );
|
|
|
|
SetSearchValue( IDC_CHEATS_ADDCODE_SEARCH_A, context.searcher.a );
|
|
SetSearchValue( IDC_CHEATS_ADDCODE_SEARCH_B, context.searcher.b );
|
|
|
|
UpdateSearchList();
|
|
}
|
|
}
|
|
|
|
void Cheats::MainDialog::Section::CodeDialog::UpdateSearchList() const
|
|
{
|
|
Application::Instance::Waiter wait;
|
|
|
|
Control::ListView list( dialog.ListView(IDC_CHEATS_ADDCODE_SEARCH_LIST) );
|
|
|
|
const uchar values[] =
|
|
{
|
|
GetSearchValue( IDC_CHEATS_ADDCODE_SEARCH_A ),
|
|
GetSearchValue( IDC_CHEATS_ADDCODE_SEARCH_B )
|
|
};
|
|
|
|
list.Clear();
|
|
list.Reserve( Nes::Cheats::RAM_SIZE );
|
|
|
|
Nes::Cheats::Ram ram = Nes::Cheats(emulator).GetRam();
|
|
|
|
switch (context.searcher.filter)
|
|
{
|
|
case IDC_CHEATS_ADDCODE_SEARCH_NONE:
|
|
|
|
for (uint i=0; i < Nes::Cheats::RAM_SIZE; ++i)
|
|
AddSearchEntry( list, i );
|
|
|
|
break;
|
|
|
|
case IDC_CHEATS_ADDCODE_SEARCH_R0_N_R1:
|
|
|
|
for (uint i=0; i < Nes::Cheats::RAM_SIZE; ++i)
|
|
{
|
|
if (context.searcher.ram[i] != ram[i])
|
|
AddSearchEntry( list, i );
|
|
}
|
|
break;
|
|
|
|
case IDC_CHEATS_ADDCODE_SEARCH_R0_L_R1:
|
|
|
|
for (uint i=0; i < Nes::Cheats::RAM_SIZE; ++i)
|
|
{
|
|
if (context.searcher.ram[i] < ram[i])
|
|
AddSearchEntry( list, i );
|
|
}
|
|
break;
|
|
|
|
case IDC_CHEATS_ADDCODE_SEARCH_R0_G_R1:
|
|
|
|
for (uint i=0; i < Nes::Cheats::RAM_SIZE; ++i)
|
|
{
|
|
if (context.searcher.ram[i] > ram[i])
|
|
AddSearchEntry( list, i );
|
|
}
|
|
break;
|
|
|
|
case IDC_CHEATS_ADDCODE_SEARCH_R0_A_R1_B:
|
|
|
|
for (uint i=0; i < Nes::Cheats::RAM_SIZE; ++i)
|
|
{
|
|
if (context.searcher.ram[i] == values[0] && ram[i] == values[1])
|
|
AddSearchEntry( list, i );
|
|
}
|
|
break;
|
|
|
|
case IDC_CHEATS_ADDCODE_SEARCH_R0_A_R0R1_B:
|
|
|
|
for (uint i=0; i < Nes::Cheats::RAM_SIZE; ++i)
|
|
{
|
|
if (context.searcher.ram[i] == values[0] && ((context.searcher.ram[i] - ram[i]) & 0xFFU) == values[1])
|
|
AddSearchEntry( list, i );
|
|
}
|
|
break;
|
|
|
|
case IDC_CHEATS_ADDCODE_SEARCH_R0R1_B:
|
|
|
|
for (uint i=0; i < Nes::Cheats::RAM_SIZE; ++i)
|
|
{
|
|
if (((context.searcher.ram[i] - ram[i]) & 0xFFU) == values[1])
|
|
AddSearchEntry( list, i );
|
|
}
|
|
break;
|
|
}
|
|
|
|
list.Columns().Align();
|
|
}
|
|
|
|
bool Cheats::MainDialog::Section::CodeDialog::GetRawCode(NesCode& code) const
|
|
{
|
|
HeapString string;
|
|
|
|
if (!(dialog.Edit( IDC_CHEATS_ADDCODE_ADDRESS ) >> string))
|
|
return false;
|
|
|
|
string.Insert( 0, "0x" );
|
|
|
|
uint value;
|
|
|
|
if (!(string >> value) || value > 0xFFFF)
|
|
return false;
|
|
|
|
code.address = value;
|
|
|
|
if (!(dialog.Edit( IDC_CHEATS_ADDCODE_VALUE ) >> string))
|
|
return false;
|
|
|
|
if (context.hex)
|
|
string.Insert( 0, "0x" );
|
|
|
|
if (!(string >> value) || value > 0xFF)
|
|
return false;
|
|
|
|
code.value = value;
|
|
|
|
if (dialog.Edit( IDC_CHEATS_ADDCODE_COMPARE ) >> string)
|
|
{
|
|
if (context.hex)
|
|
string.Insert( 0, "0x" );
|
|
|
|
if (!(string >> value) || value > 0xFF)
|
|
return false;
|
|
|
|
code.compare = value;
|
|
code.useCompare = true;
|
|
}
|
|
else
|
|
{
|
|
code.compare = 0;
|
|
code.useCompare = false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Cheats::MainDialog::Section::CodeDialog::SetRawCode(const NesCode& code) const
|
|
{
|
|
dialog.Edit( IDC_CHEATS_ADDCODE_ADDRESS ) << HexString( 16, code.address, true ).Ptr();
|
|
|
|
if (context.hex)
|
|
dialog.Edit( IDC_CHEATS_ADDCODE_VALUE ) << HexString( 8, code.value, true ).Ptr();
|
|
else
|
|
dialog.Edit( IDC_CHEATS_ADDCODE_VALUE ) << uint(code.value);
|
|
|
|
if (!code.useCompare)
|
|
{
|
|
dialog.Edit( IDC_CHEATS_ADDCODE_COMPARE ).Clear();
|
|
}
|
|
else if (context.hex)
|
|
{
|
|
dialog.Edit( IDC_CHEATS_ADDCODE_COMPARE ) << HexString( 8, code.compare, true ).Ptr();
|
|
}
|
|
else
|
|
{
|
|
dialog.Edit( IDC_CHEATS_ADDCODE_COMPARE ) << uint(code.compare);
|
|
}
|
|
}
|
|
|
|
bool Cheats::MainDialog::Section::CodeDialog::GetGenieCode(NesCode& code) const
|
|
{
|
|
String::Heap<char> string;
|
|
dialog.Edit( IDC_CHEATS_ADDCODE_GENIE ) >> string;
|
|
return NES_SUCCEEDED(Nes::Cheats::GameGenieDecode( string.Ptr(), code ));
|
|
}
|
|
|
|
void Cheats::MainDialog::Section::CodeDialog::SetGenieCode(const NesCode& code) const
|
|
{
|
|
char characters[8+1];
|
|
|
|
if (NES_SUCCEEDED(Nes::Cheats::GameGenieEncode( code, characters )))
|
|
dialog.Edit( IDC_CHEATS_ADDCODE_GENIE ) << characters;
|
|
else
|
|
dialog.Edit( IDC_CHEATS_ADDCODE_GENIE ).Clear();
|
|
}
|
|
|
|
bool Cheats::MainDialog::Section::CodeDialog::GetRockyCode(NesCode& code) const
|
|
{
|
|
String::Heap<char> string;
|
|
dialog.Edit( IDC_CHEATS_ADDCODE_ROCKY ) >> string;
|
|
return NES_SUCCEEDED(Nes::Cheats::ProActionRockyDecode( string.Ptr(), code ));
|
|
}
|
|
|
|
void Cheats::MainDialog::Section::CodeDialog::SetRockyCode(const NesCode& code) const
|
|
{
|
|
char characters[8+1];
|
|
|
|
if (NES_SUCCEEDED(Nes::Cheats::ProActionRockyEncode( code, characters )))
|
|
dialog.Edit( IDC_CHEATS_ADDCODE_ROCKY ) << characters;
|
|
else
|
|
dialog.Edit( IDC_CHEATS_ADDCODE_ROCKY ).Clear();
|
|
}
|
|
|
|
uint Cheats::MainDialog::Section::CodeDialog::GetSearchValue(const uint id) const
|
|
{
|
|
uint value = 0;
|
|
|
|
if (context.hex)
|
|
{
|
|
HeapString string;
|
|
|
|
if (dialog.Edit( id ) >> string)
|
|
{
|
|
string.Insert( 0, "0x" );
|
|
|
|
if (!(string >> value))
|
|
value = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!(dialog.Edit( id ) >> value) || value > 0xFF)
|
|
value = 0;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
void Cheats::MainDialog::Section::CodeDialog::SetSearchValue(const uint id,const uint value) const
|
|
{
|
|
if (context.hex)
|
|
dialog.Edit( id ) << HexString( 8, value, true ).Ptr();
|
|
else
|
|
dialog.Edit( id ) << uint(value);
|
|
}
|
|
|
|
void Cheats::MainDialog::Section::CodeDialog::AddSearchEntry(Control::ListView list,const uint address) const
|
|
{
|
|
const int index = list.Add( HexString( 16, address, true ), address );
|
|
|
|
if (context.hex)
|
|
{
|
|
list[index].Text(1) << HexString( 8, context.searcher.ram[address], true ).Ptr();
|
|
list[index].Text(2) << HexString( 8, Nes::Cheats(emulator).GetRam()[address], true ).Ptr();
|
|
}
|
|
else
|
|
{
|
|
list[index].Text(1) << String::Num<wchar_t>( uint(context.searcher.ram[address]) ).Ptr();
|
|
list[index].Text(2) << String::Num<wchar_t>( uint(Nes::Cheats(emulator).GetRam()[address]) ).Ptr();
|
|
}
|
|
}
|
|
|
|
Cheats::Code::Code()
|
|
:
|
|
crc (0),
|
|
address (0),
|
|
compare (NO_COMPARE),
|
|
value (0),
|
|
enabled (true)
|
|
{
|
|
}
|
|
|
|
bool Cheats::Code::operator < (const Code& code) const
|
|
{
|
|
if (address < code.address)
|
|
return true;
|
|
|
|
if (address > code.address)
|
|
return false;
|
|
|
|
if (value < code.value)
|
|
return true;
|
|
|
|
if (value > code.value)
|
|
return false;
|
|
|
|
if (compare < code.compare)
|
|
return true;
|
|
|
|
if (compare > code.compare)
|
|
return false;
|
|
|
|
if ((crc ? crc : ~0U) < (code.crc ? code.crc : ~0U))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
Cheats::NesCode Cheats::Code::ToNesCode() const
|
|
{
|
|
return NesCode
|
|
(
|
|
address,
|
|
value,
|
|
compare != NO_COMPARE ? compare : 0,
|
|
compare != NO_COMPARE
|
|
);
|
|
}
|
|
|
|
void Cheats::Code::FromNesCode(const NesCode& code)
|
|
{
|
|
address = code.address;
|
|
value = code.value;
|
|
compare = (code.useCompare ? code.compare : NO_COMPARE);
|
|
}
|
|
|
|
Cheats::Code::GenieCode Cheats::Code::ToGenieCode() const
|
|
{
|
|
char characters[8+1];
|
|
|
|
if (NES_SUCCEEDED(Nes::Cheats::GameGenieEncode( ToNesCode(), characters )))
|
|
return characters;
|
|
else
|
|
return L"-";
|
|
}
|
|
|
|
Cheats::Searcher::Searcher()
|
|
: filter(NO_FILTER), a(0), b(0) {}
|
|
}
|
|
}
|