//////////////////////////////////////////////////////////////////////////////////////// // // 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 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(genie.GetValue()).Ptr(), nesCode ))) continue; } else if (const Xml::Node rocky=node.GetChild( L"rocky" )) { if (NES_FAILED(Nes::Cheats::ProActionRockyDecode( String::Heap(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 messages[]; static const MsgHandler::Entry commands[]; }; const MsgHandler::Entry Cheats::MainDialog::Handlers::messages[] = { { WM_INITDIALOG, &MainDialog::OnInitDialog } }; const MsgHandler::Entry 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
notifications[]; }; const Control::NotificationHandler::Entry 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
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
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(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 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(static_cast(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(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(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(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(reinterpret_cast(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(nmhdr).iSubItem; NST_VERIFY( column <= NUM_COLUMNS ); if (column <= NUM_COLUMNS) { sortColumns[0] = static_cast(column); for (uint i=1, next=0; i < NUM_COLUMNS; ++i, ++next) { if (next == column) next++; sortColumns[i] = static_cast(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( obj1 ); const Code& b = *static_cast( 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(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 result( codes.insert( context.code ) ); if (result.second) { AddToListView( *result.first ); RefreshListView(); } } } struct Cheats::MainDialog::Section::CodeDialog::Handlers { static const MsgHandler::Entry messages[]; static const MsgHandler::Entry commands[]; }; const MsgHandler::Entry Cheats::MainDialog::Section::CodeDialog::Handlers::messages[] = { { WM_INITDIALOG, &CodeDialog::OnInitDialog } }; const MsgHandler::Entry 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 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(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 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 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( uint(context.searcher.ram[address]) ).Ptr(); list[index].Text(2) << String::Num( 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) {} } }