Update to v094r15 release.

byuu says:

Implemented the cheat database dialog, and most of the cheat editor
dialog. I still have to handle loading and saving the cheats.bml file
for each game. I wanted to finish it today, but I burned out. It's a ton
of really annoying work to support cheat codes. There's also some issue
with the width calculation for the "code(s)" column in hiro/GTK.

Short-term:
- add input port changing support
- add other input types (mouse-based, etc)
- finish cheat codes

Long-term:
- add slotted cart loader (SGB, BSX, ST)
- add DIP switch selection window (NSS)
- add overscan masking
- add timing configuration (video/audio sync)

Not planned:
- video color adjustments (will allow emulated color vs raw color; but
  no more sliders)
- pixel shaders
- ananke integration (will need to make a command-line version to get my
  games in)
- fancy audio adjustment controls (resampler, latency, volume)
- input focus settings
- localization support (not enough users)
- window geometry memory
- anything else not in higan v094
This commit is contained in:
Tim Allen 2015-04-21 21:51:57 +10:00
parent 89d578bc7f
commit 2eb50fd70b
29 changed files with 568 additions and 74 deletions

View File

@ -3,7 +3,7 @@
namespace Emulator {
static const char Name[] = "higan";
static const char Version[] = "094.14";
static const char Version[] = "094.15";
static const char Author[] = "byuu";
static const char License[] = "GPLv3";
static const char Website[] = "http://byuu.org/";

View File

@ -49,7 +49,12 @@ auto pListView::destruct() -> void {
auto pListView::append(sListViewColumn column) -> void {
gtk_tree_view_append_column(gtkTreeView, column->self()->gtkColumn);
gtk_widget_show_all(column->self()->gtkHeader);
column->setBackgroundColor(column->backgroundColor());
column->setEditable(column->editable());
column->setFont(column->font());
column->setForegroundColor(column->foregroundColor());
column->setHorizontalAlignment(column->horizontalAlignment());
column->setVerticalAlignment(column->verticalAlignment());
setCheckable(state().checkable);
_createModel();
gtk_tree_view_set_rules_hint(gtkTreeView, self().columns() >= 2); //two or more columns + checkbutton column
@ -118,7 +123,7 @@ auto pListView::resizeColumns() -> void {
}
for(auto row : range(self().items())) {
maximumWidth = max(maximumWidth, 8 //margin
+ (row == 0 && state().checkable ? 24 : 0) //check box
+ (row == 0 && state().checkable ? 32 : 0) //check box
+ state().items[row]->state.icon(column, {}).width
+ Font::size(state().columns[column]->font(true), state().items[row]->state.text(column, "")).width()
);

View File

@ -93,6 +93,7 @@ inline auto realpath(rstring name) -> string;
inline auto programpath() -> string;
inline auto userpath() -> string;
inline auto configpath() -> string;
inline auto localpath() -> string;
inline auto sharedpath() -> string;
inline auto temppath() -> string;

View File

@ -71,6 +71,24 @@ auto configpath() -> string {
return result;
}
// /home/username/.local/
// c:/users/username/appdata/local/
auto localpath() -> string {
#if defined(PLATFORM_WINDOWS)
whcar_t path[PATH_MAX] = L"";
SHGetFolderPathW(nullptr, CSIDL_LOCAL_APPDATA | CSIDL_FLAG_CREATE, nullptr, 0, path);
string result = (const char*)utf8_t(path);
result.transform("\\", "/");
#elif defined(PLATFORM_MACOSX)
string result = {userpath(), "Library/Application Support/"};
#else
string result = {userpath(), ".local/"};
#endif
if(result.empty()) result = ".";
if(result.endsWith("/") == false) result.append("/");
return result;
}
// /usr/share
// /Library/Application Support/
// c:/ProgramData/

View File

@ -9,7 +9,7 @@ include gb/GNUmakefile
include gba/GNUmakefile
ui_objects := ui-tomoko ui-program ui-configuration ui-input
ui_objects += ui-library ui-settings ui-presentation
ui_objects += ui-library ui-settings ui-tools ui-presentation
ui_objects += ruby hiro
# platform
@ -55,6 +55,7 @@ obj/ui-configuration.o: $(ui)/configuration/configuration.cpp $(call rwildcard,$
obj/ui-input.o: $(ui)/input/input.cpp $(call rwildcard,$(ui)/)
obj/ui-library.o: $(ui)/library/library.cpp $(call rwildcard,$(ui)/)
obj/ui-settings.o: $(ui)/settings/settings.cpp $(call rwildcard,$(ui)/)
obj/ui-tools.o: $(ui)/tools/tools.cpp $(call rwildcard,$(ui)/)
obj/ui-presentation.o: $(ui)/presentation/presentation.cpp $(call rwildcard,$(ui)/)
# targets
@ -67,9 +68,13 @@ ifeq ($(shell id -un),root)
else ifeq ($(platform),windows)
else ifeq ($(platform),macosx)
else
mkdir -p $(prefix)/bin/
mkdir -p $(prefix)/share/icons/
mkdir -p $(prefix)/$(name)/
mkdir -p ~/Emulation/System/
cp out/$(name) $(prefix)/bin/$(name)
cp data/higan.png $(prefix)/share/icons/$(name).png
mkdir -p ~/Emulation/System/
cp data/cheats.bml $(prefix)/$(name)/cheats.bml
cp -R profile/* ~/Emulation/System/
endif

View File

@ -8,6 +8,9 @@ ConfigurationManager::ConfigurationManager() {
userInterface.append(userInterface.showStatusBar, "ShowStatusBar");
append(userInterface, "UserInterface");
library.append(library.location, "Location");
append(library, "Library");
video.append(video.driver, "Driver");
video.append(video.synchronize, "Synchronize");
video.append(video.scale, "Scale");
@ -25,6 +28,7 @@ ConfigurationManager::ConfigurationManager() {
append(input, "Input");
load({configpath(), "tomoko/settings.bml"});
if(!library.location) library.location = {userpath(), "Emulation/"};
if(!video.driver) video.driver = ruby::video.safestDriver();
if(!audio.driver) audio.driver = ruby::audio.safestDriver();
if(!input.driver) input.driver = ruby::input.safestDriver();

View File

@ -6,6 +6,10 @@ struct ConfigurationManager : Configuration::Document {
bool showStatusBar = true;
} userInterface;
struct Library : Configuration::Node {
string location;
} library;
struct Video : Configuration::Node {
string driver;
bool synchronize = false;

View File

@ -1,6 +1,5 @@
auto InputManager::appendHotkeys() -> void {
{
auto hotkey = new InputHotkey;
{ auto hotkey = new InputHotkey;
hotkey->name = "Toggle Fullscreen";
hotkey->action = [] {
presentation->toggleFullScreen();
@ -8,6 +7,46 @@ auto InputManager::appendHotkeys() -> void {
hotkeys.append(hotkey);
}
{ auto hotkey = new InputHotkey;
hotkey->name = "Save State";
hotkey->action = [] {
program->saveState(0);
};
hotkeys.append(hotkey);
}
{ auto hotkey = new InputHotkey;
hotkey->name = "Load State";
hotkey->action = [] {
program->loadState(0);
};
hotkeys.append(hotkey);
}
{ auto hotkey = new InputHotkey;
hotkey->name = "Pause Emulation";
hotkey->action = [] {
program->pause = !program->pause;
};
hotkeys.append(hotkey);
}
{ auto hotkey = new InputHotkey;
hotkey->name = "Power Cycle";
hotkey->action = [] {
program->powerCycle();
};
hotkeys.append(hotkey);
}
{ auto hotkey = new InputHotkey;
hotkey->name = "Soft Reset";
hotkey->action = [] {
program->softReset();
};
hotkeys.append(hotkey);
}
Configuration::Node nodeHotkeys;
for(auto& hotkey : hotkeys) {
nodeHotkeys.append(hotkey->assignment, string{hotkey->name}.replace(" ", ""));

View File

@ -4,12 +4,12 @@ LibraryBrowser::LibraryBrowser(TabFrame& parent, Emulator::Interface::Media& med
layout.setMargin(5);
gameList.onActivate([&] {
libraryManager->setVisible(false);
program->loadMedia({userpath(), "Emulation/", this->media.name, "/", gameList.selected()->text(), ".", this->media.type, "/"});
program->loadMedia({config().library.location, this->media.name, "/", gameList.selected()->text(), ".", this->media.type, "/"});
});
}
auto LibraryBrowser::reload() -> void {
string path = {userpath(), "Emulation/", media.name};
string path = {config().library.location, media.name};
directory::create(path);
gameList.reset();

View File

@ -13,7 +13,6 @@ LibraryManager::LibraryManager() {
}
}
setTitle("Library");
setSize({640, 800});
setPlacement(0.0, 0.0);
}
@ -24,6 +23,7 @@ auto LibraryManager::show(const string& type) -> void {
browser->select();
}
setTitle({"Library (", config().library.location, ")"});
setVisible();
setFocused();
}

View File

@ -17,8 +17,8 @@ Presentation::Presentation() {
}
systemMenu.setText("System").setVisible(false);
powerSystem.setText("Power");
resetSystem.setText("Reset");
powerSystem.setText("Power").onActivate([&] { program->powerCycle(); });
resetSystem.setText("Reset").onActivate([&] { program->softReset(); });
unloadSystem.setText("Unload").onActivate([&] { program->unloadMedia(); drawSplashScreen(); });
settingsMenu.setText("Settings");
@ -72,10 +72,7 @@ Presentation::Presentation() {
statusBar.setVisible(config().userInterface.showStatusBar);
if(visible()) resizeViewport();
});
showConfiguration.setText("Configuration ...").onActivate([&] {
settingsManager->setVisible();
settingsManager->setFocused();
});
showConfiguration.setText("Configuration ...").onActivate([&] { settingsManager->show(0); });
toolsMenu.setText("Tools").setVisible(false);
saveStateMenu.setText("Save State");
@ -90,8 +87,8 @@ Presentation::Presentation() {
loadSlot3.setText("Slot 3").onActivate([&] { program->loadState(3); });
loadSlot4.setText("Slot 4").onActivate([&] { program->loadState(4); });
loadSlot5.setText("Slot 5").onActivate([&] { program->loadState(5); });
stateManager.setText("State Manager").onActivate([&] {});
cheatEditor.setText("Cheat Editor").onActivate([&] {});
cheatEditor.setText("Cheat Editor").onActivate([&] { toolsManager->show(0); });
stateManager.setText("State Manager").onActivate([&] { toolsManager->show(1); });
statusBar.setFont(Font::sans(8, "Bold"));
statusBar.setVisible(config().userInterface.showStatusBar);
@ -108,15 +105,13 @@ auto Presentation::resizeViewport() -> void {
signed width = 256;
signed height = 240;
if(program->activeEmulator) {
width = program->emulator().information.width;
height = program->emulator().information.height;
if(emulator) {
width = emulator->information.width;
height = emulator->information.height;
}
if(fullScreen() == false) {
bool arc = config().video.aspectCorrection
&& program->activeEmulator
&& program->emulator().information.aspectRatio != 1.0;
bool arc = config().video.aspectCorrection && emulator && emulator->information.aspectRatio != 1.0;
if(config().video.scale == "Small" ) width *= 1, height *= 1;
if(config().video.scale == "Normal") width *= 2, height *= 2;
@ -149,7 +144,7 @@ auto Presentation::resizeViewport() -> void {
viewport.setGeometry({x, y, width, height});
}
if(!program->activeEmulator) drawSplashScreen();
if(!emulator) drawSplashScreen();
}
auto Presentation::toggleFullScreen() -> void {

View File

@ -46,8 +46,8 @@ struct Presentation : Window {
MenuItem loadSlot4{&loadStateMenu};
MenuItem loadSlot5{&loadStateMenu};
MenuSeparator toolsMenuSeparator{&toolsMenu};
MenuItem stateManager{&toolsMenu};
MenuItem cheatEditor{&toolsMenu};
MenuItem cheatEditor{&toolsMenu};
MenuItem stateManager{&toolsMenu};
FixedLayout layout{this};
Viewport viewport{&layout, Geometry{0, 0, 1, 1}};

View File

@ -4,17 +4,17 @@ auto Program::loadRequest(unsigned id, string name, string type) -> void {
//request from emulation core to load non-volatile media file
auto Program::loadRequest(unsigned id, string path) -> void {
string location = {mediaPaths(emulator().group(id)), path};
string location = {mediaPaths(emulator->group(id)), path};
if(!file::exists(location)) return;
mmapstream stream{location};
return emulator().load(id, stream);
return emulator->load(id, stream);
}
//request from emulation core to save non-volatile media file
auto Program::saveRequest(unsigned id, string path) -> void {
string location = {mediaPaths(emulator().group(id)), path};
string location = {mediaPaths(emulator->group(id)), path};
filestream stream{location, file::mode::write};
return emulator().save(id, stream);
return emulator->save(id, stream);
}
auto Program::videoColor(unsigned source, uint16 alpha, uint16 red, uint16 green, uint16 blue) -> uint32 {
@ -67,7 +67,7 @@ auto Program::audioSample(int16 lsample, int16 rsample) -> void {
auto Program::inputPoll(unsigned port, unsigned device, unsigned input) -> int16 {
if(presentation->focused()) {
auto guid = emulator().port[port].device[device].input[input].guid;
auto guid = emulator->port[port].device[device].input[input].guid;
auto mapping = (InputMapping*)guid;
if(mapping) return mapping->poll();
}

View File

@ -20,22 +20,25 @@ auto Program::loadMedia(Emulator::Interface& _emulator, Emulator::Interface::Med
mediaPaths(media.id) = location;
folderPaths.append(location);
setEmulator(&_emulator);
emulator = &_emulator;
updateVideoPalette();
emulator().load(media.id);
emulator().power();
emulator->load(media.id);
emulator->power();
presentation->resizeViewport();
presentation->setTitle(emulator().title());
presentation->setTitle(emulator->title());
presentation->systemMenu.setVisible(true);
presentation->toolsMenu.setVisible(true);
toolsManager->cheatEditor.doRefresh();
toolsManager->stateManager.doRefresh();
}
auto Program::unloadMedia() -> void {
if(activeEmulator == nullptr) return;
emulator().unload();
if(!emulator) return;
emulator->unload();
emulator = nullptr;
setEmulator(nullptr);
mediaPaths.reset();
folderPaths.reset();

View File

@ -24,6 +24,8 @@ Program::Program() {
new InputManager;
new LibraryManager;
new SettingsManager;
new CheatDatabase;
new ToolsManager;
new Presentation;
presentation->setVisible();
@ -56,22 +58,17 @@ Program::Program() {
updateVideoFilter();
}
auto Program::emulator() -> Emulator::Interface& {
if(!activeEmulator) throw;
return *activeEmulator;
}
auto Program::main() -> void {
updateStatusText();
inputManager->poll();
if(!activeEmulator || emulator().loaded() == false) {
if(!emulator || !emulator->loaded() || pause) {
audio.clear();
usleep(20 * 1000);
return;
}
emulator().run();
emulator->run();
}
auto Program::quit() -> void {
@ -83,7 +80,3 @@ auto Program::quit() -> void {
input.term();
Application::quit();
}
auto Program::setEmulator(Emulator::Interface* emulator) -> void {
activeEmulator = emulator;
}

View File

@ -1,10 +1,8 @@
struct Program : Emulator::Interface::Bind {
//program.cpp
Program();
auto emulator() -> Emulator::Interface&;
auto main() -> void;
auto quit() -> void;
auto setEmulator(Emulator::Interface*) -> void;
//interface.cpp
auto loadRequest(unsigned id, string name, string type) -> void override;
@ -25,19 +23,22 @@ struct Program : Emulator::Interface::Bind {
auto unloadMedia() -> void;
//state.cpp
auto loadState(unsigned slot) -> bool;
auto saveState(unsigned slot) -> bool;
auto stateName(unsigned slot, bool manager = false) -> string;
auto loadState(unsigned slot, bool manager = false) -> bool;
auto saveState(unsigned slot, bool manager = false) -> bool;
//utility.cpp
auto powerCycle() -> void;
auto softReset() -> void;
auto showMessage(const string& text) -> void;
auto updateStatusText() -> void;
auto updateVideoFilter() -> void;
auto updateVideoPalette() -> void;
DSP dsp;
bool pause = false;
vector<Emulator::Interface*> emulators;
Emulator::Interface* activeEmulator = nullptr;
vector<string> mediaPaths;
vector<string> folderPaths;

View File

@ -1,18 +1,28 @@
auto Program::loadState(unsigned slot) -> bool {
if(!activeEmulator) return false;
auto memory = file::read({folderPaths[0], "higan/state-", slot, ".bst"});
auto Program::stateName(unsigned slot, bool manager) -> string {
return {
folderPaths[0], "higan/states/",
manager ? "managed/" : "quick/",
"slot-", decimal<2>(slot), ".bst"
};
}
auto Program::loadState(unsigned slot, bool manager) -> bool {
if(!emulator) return false;
auto location = stateName(slot, manager);
auto memory = file::read(location);
if(memory.size() == 0) return showMessage({"Slot ", slot, " does not exist"}), false;
serializer s(memory.data(), memory.size());
if(emulator().unserialize(s) == false) return showMessage({"Slot ", slot, " state incompatible"}), false;
if(emulator->unserialize(s) == false) return showMessage({"Slot ", slot, " state incompatible"}), false;
return showMessage({"Loaded from slot ", slot}), true;
}
auto Program::saveState(unsigned slot) -> bool {
if(!activeEmulator) return false;
serializer s = emulator().serialize();
auto Program::saveState(unsigned slot, bool manager) -> bool {
if(!emulator) return false;
auto location = stateName(slot, manager);
serializer s = emulator->serialize();
if(s.size() == 0) return showMessage({"Failed to save state to slot ", slot}), false;
directory::create({folderPaths[0], "higan/"});
if(file::write({folderPaths[0], "higan/state-", slot, ".bst"}, s.data(), s.size()) == false) {
directory::create(location.pathname());
if(file::write(location, s.data(), s.size()) == false) {
return showMessage({"Unable to write to slot ", slot}), false;
}
return showMessage({"Saved to slot ", slot}), true;

View File

@ -1,3 +1,16 @@
auto Program::powerCycle() -> void {
if(!emulator) return;
emulator->power();
showMessage("Power cycled");
}
auto Program::softReset() -> void {
if(!emulator) return;
if(!emulator->information.resettable) return powerCycle();
emulator->reset();
showMessage("System reset");
}
auto Program::showMessage(const string& text) -> void {
statusTime = time(0);
statusMessage = text;
@ -9,9 +22,9 @@ auto Program::updateStatusText() -> void {
string text;
if((currentTime - statusTime) <= 2) {
text = statusMessage;
} else if(!activeEmulator || emulator().loaded() == false) {
} else if(!emulator || emulator->loaded() == false) {
text = "No cartridge loaded";
} else if(0) {
} else if(pause) {
text = "Paused";
} else {
text = statusText;
@ -28,8 +41,8 @@ auto Program::updateVideoFilter() -> void {
}
auto Program::updateVideoPalette() -> void {
if(!activeEmulator) return;
emulator().paletteUpdate(config().video.colorEmulation
if(!emulator) return;
emulator->paletteUpdate(config().video.colorEmulation
? Emulator::Interface::PaletteMode::Emulation
: Emulator::Interface::PaletteMode::Standard
);

View File

@ -3,6 +3,7 @@ AdvancedSettings::AdvancedSettings(TabFrame* parent) : TabFrameItem(parent) {
setText("Advanced");
layout.setMargin(5);
driverLabel.setText("Driver Selection").setFont(Font::sans(8, "Bold"));
videoLabel.setText("Video:");
videoDriver.onChange([&] { config().video.driver = videoDriver.selected()->text(); });
@ -28,4 +29,13 @@ AdvancedSettings::AdvancedSettings(TabFrame* parent) : TabFrameItem(parent) {
inputDriver.append(item);
if(config().input.driver == driver) item.setSelected();
}
libraryLabel.setText("Game Library").setFont(Font::sans(8, "Bold"));
libraryPrefix.setText("Location:");
libraryLocation.setEditable(false).setText(config().library.location);
libraryChange.setText("Change ...").onActivate([&] {
if(auto location = BrowserDialog().setParent(*presentation).selectFolder()) {
libraryLocation.setText(config().library.location = location);
}
});
}

View File

@ -63,7 +63,7 @@ auto InputSettings::reloadMappings() -> void {
mappingList.reset();
mappingList.append(ListViewColumn().setText("Name"));
mappingList.append(ListViewColumn().setText("Mapping").setWidth(~0));
mappingList.append(ListViewColumn().setText("Device"));
mappingList.append(ListViewColumn().setText("Device").setForegroundColor({0, 128, 0}));
for(auto& mapping : activeDevice().mappings) {
mappingList.append(ListViewItem().setText(0, mapping->name));
}

View File

@ -14,6 +14,15 @@ SettingsManager::SettingsManager() {
setSize({600, 400});
setPlacement(0.0, 1.0);
input.mappingList.resizeColumns();
hotkeys.mappingList.resizeColumns();
onSize([&] {
input.mappingList.resizeColumns();
hotkeys.mappingList.resizeColumns();
});
}
auto SettingsManager::show(unsigned setting) -> void {
panel.item(setting)->setSelected();
setVisible();
setFocused();
doSize();
}

View File

@ -52,16 +52,22 @@ struct AdvancedSettings : TabFrameItem {
ComboButton audioDriver{&driverLayout, Size{~0, 0}};
Label inputLabel{&driverLayout, Size{0, 0}};
ComboButton inputDriver{&driverLayout, Size{~0, 0}};
Label libraryLabel{&layout, Size{~0, 0}, 2};
HorizontalLayout libraryLayout{&layout, Size{~0, 0}};
Label libraryPrefix{&libraryLayout, Size{0, 0}};
LineEdit libraryLocation{&libraryLayout, Size{~0, 0}};
Button libraryChange{&libraryLayout, Size{0, 0}};
};
struct SettingsManager : Window {
SettingsManager();
auto show(unsigned setting) -> void;
VerticalLayout layout{this};
TabFrame panelLayout{&layout, Size{~0, ~0}};
InputSettings input{&panelLayout};
HotkeySettings hotkeys{&panelLayout};
AdvancedSettings advanced{&panelLayout};
TabFrame panel{&layout, Size{~0, ~0}};
InputSettings input{&panel};
HotkeySettings hotkeys{&panel};
AdvancedSettings advanced{&panel};
StatusBar statusBar{this};
};

View File

@ -1,4 +1,5 @@
#include "tomoko.hpp"
Emulator::Interface* emulator = nullptr;
#include <nall/main.hpp>
auto nall::main(lstring args) -> void {

View File

@ -1,4 +1,5 @@
#include <emulator/emulator.hpp>
extern Emulator::Interface* emulator;
#include <nall/nall.hpp>
#include <ruby/ruby.hpp>
@ -12,4 +13,5 @@ using namespace hiro;
#include "input/input.hpp"
#include "library/library.hpp"
#include "settings/settings.hpp"
#include "tools/tools.hpp"
#include "presentation/presentation.hpp"

View File

@ -0,0 +1,52 @@
CheatDatabase::CheatDatabase() {
cheatDatabase = this;
layout.setMargin(5);
cheatList.setCheckable();
selectAllButton.setText("Select All").onActivate([&] { cheatList.setChecked(true); });
unselectAllButton.setText("Unselect All").onActivate([&] { cheatList.setChecked(false); });
addCodesButton.setText("Add Codes").onActivate([&] { addCodes(); });
setSize({800, 400});
setPlacement(0.5, 1.0);
}
auto CheatDatabase::findCodes() -> void {
if(!emulator) return;
auto sha256 = emulator->sha256();
auto contents = string::read({localpath(), "tomoko/cheats.bml"});
auto document = Markup::Document(contents);
for(auto& cartridge : document) {
if(cartridge.name != "cartridge") continue;
if(cartridge["sha256"].text() != sha256) continue;
codes.reset();
cheatList.reset();
cheatList.append(ListViewColumn().setWidth(~0));
for(auto& cheat : cartridge) {
if(cheat.name != "cheat") continue;
codes.append(cheat["code"].text());
cheatList.append(ListViewItem().setText(0, cheat["description"].text()));
}
setTitle(cartridge["name"].text());
setVisible();
return;
}
MessageDialog().setParent(*toolsManager).setText("Sorry, no cheats were found for this game.").information();
}
auto CheatDatabase::addCodes() -> void {
for(auto item : cheatList.checked()) {
string code = codes(item->offset(), "");
string description = item->text(0);
if(toolsManager->cheatEditor.addCode(code, description) == false) {
MessageDialog().setParent(*this).setText("Free slots exhausted. Not all codes could be added.").warning();
break;
}
}
setVisible(false);
}

View File

@ -0,0 +1,111 @@
CheatEditor::CheatEditor(TabFrame* parent) : TabFrameItem(parent) {
setIcon(Icon::Edit::Replace);
setText("Cheat Editor");
layout.setMargin(5);
cheatList.append(ListViewColumn().setText("Slot").setForegroundColor({0, 128, 0}).setHorizontalAlignment(1.0));
cheatList.append(ListViewColumn().setText("Code(s)").setWidth(0));
cheatList.append(ListViewColumn().setText("Description").setWidth(~0));
for(auto slot : range(Slots)) cheatList.append(ListViewItem().setText(0, 1 + slot));
cheatList.setCheckable();
cheatList.setHeaderVisible();
cheatList.onChange([&] { doChange(); });
cheatList.onToggle([&](sListViewItem) { synchronizeCodes(); });
codeLabel.setText("Code(s):");
codeValue.onChange([&] { doModify(); });
descriptionLabel.setText("Description:");
descriptionValue.onChange([&] { doModify(); });
findCodesButton.setText("Find Codes ...").onActivate([&] { cheatDatabase->findCodes(); });
resetButton.setText("Reset").onActivate([&] { doReset(); });
eraseButton.setText("Erase").onActivate([&] { doErase(); });
}
auto CheatEditor::doChange() -> void {
if(auto item = cheatList.selected()) {
unsigned slot = item->offset();
codeValue.setEnabled(true).setText(cheats[slot].code);
descriptionValue.setEnabled(true).setText(cheats[slot].description);
eraseButton.setEnabled(true);
} else {
codeValue.setEnabled(false).setText("");
descriptionValue.setEnabled(false).setText("");
eraseButton.setEnabled(false);
}
}
auto CheatEditor::doModify() -> void {
if(auto item = cheatList.selected()) {
unsigned slot = item->offset();
cheats[slot].code = codeValue.text();
cheats[slot].description = descriptionValue.text();
doRefresh();
synchronizeCodes();
}
}
auto CheatEditor::doRefresh() -> void {
for(auto slot : range(Slots)) {
if(cheats[slot].code || cheats[slot].description) {
lstring codes = cheats[slot].code.split("+");
if(codes.size() > 1) codes[0].append("+...");
cheatList.item(slot)->setText(1, codes[0]).setText(2, cheats[slot].description);
} else {
cheatList.item(slot)->setText(1, "").setText(2, "(empty)");
}
}
cheatList.resizeColumns();
}
auto CheatEditor::doReset() -> void {
if(MessageDialog().setParent(*toolsManager).setText("Permanently erase all slots?").question() == 0) {
for(auto& cheat : cheats) {
cheat.code = "";
cheat.description = "";
}
cheatList.setSelected(false);
doChange();
doRefresh();
synchronizeCodes();
}
}
auto CheatEditor::doErase() -> void {
if(auto item = cheatList.selected()) {
unsigned slot = item->offset();
cheats[slot].code = "";
cheats[slot].description = "";
codeValue.setText("");
descriptionValue.setText("");
doRefresh();
synchronizeCodes();
}
}
auto CheatEditor::synchronizeCodes() -> void {
if(!emulator) return;
lstring codes;
for(auto slot : range(Slots)) {
if(!cheatList.item(slot)->checked()) continue;
if(!cheats[slot].code) continue;
codes.append(cheats[slot].code);
}
emulator->cheatSet(codes);
}
//returns true if code was added
//returns false if there are no more free slots for additional codes
auto CheatEditor::addCode(const string& code, const string& description) -> bool {
for(auto& cheat : cheats) {
if(cheat.code || cheat.description) continue;
cheat.code = code;
cheat.description = description;
doRefresh();
return true;
}
return false;
}

View File

@ -0,0 +1,98 @@
StateManager::StateManager(TabFrame* parent) : TabFrameItem(parent) {
setIcon(Icon::Application::FileManager);
setText("State Manager");
layout.setMargin(5);
stateList.append(ListViewColumn().setText("Slot").setForegroundColor({0, 128, 0}).setHorizontalAlignment(1.0));
stateList.append(ListViewColumn().setText("Description").setWidth(~0));
for(unsigned slot = 0; slot < Slots; slot++) {
stateList.append(ListViewItem().setText(0, 1 + slot));
}
stateList.setHeaderVisible();
stateList.onActivate([&] { doLoad(); });
stateList.onChange([&] { doChange(); });
descriptionLabel.setText("Description:");
descriptionValue.onChange([&] { doLabel(); });
saveButton.setText("Save").onActivate([&] { doSave(); });
loadButton.setText("Load").onActivate([&] { doLoad(); });
resetButton.setText("Reset").onActivate([&] { doReset(); });
eraseButton.setText("Erase").onActivate([&] { doErase(); });
}
auto StateManager::doChange() -> void {
vector<uint8> buffer;
if(auto item = stateList.selected()) {
buffer = file::read(program->stateName(1 + item->offset(), true));
}
if(buffer.size() >= 584) {
string description;
description.reserve(512);
memory::copy(description.pointer(), buffer.data() + 72, 512);
description.resize(description.length());
descriptionValue.setEnabled(true).setText(description);
loadButton.setEnabled(true);
eraseButton.setEnabled(true);
} else {
descriptionValue.setEnabled(false).setText("");
loadButton.setEnabled(false);
eraseButton.setEnabled(false);
}
}
auto StateManager::doRefresh() -> void {
for(unsigned slot = 0; slot < Slots; slot++) {
auto buffer = file::read(program->stateName(1 + slot, true));
if(buffer.size() >= 584) {
string description;
description.reserve(512);
memory::copy(description.pointer(), buffer.data() + 72, 512);
description.resize(description.length());
stateList.item(slot)->setText(1, description);
} else {
stateList.item(slot)->setText(1, "(empty)");
}
}
doChange();
}
auto StateManager::doLabel() -> void {
if(auto item = stateList.selected()) {
auto buffer = file::read(program->stateName(1 + item->offset(), true));
if(buffer.size() >= 584) {
string description = descriptionValue.text();
description.reserve(512);
memory::copy(buffer.data() + 72, description.data(), 512);
file::write(program->stateName(1 + item->offset(), true), buffer);
doRefresh();
}
}
}
auto StateManager::doLoad() -> void {
if(auto item = stateList.selected()) {
program->loadState(1 + item->offset(), true);
}
}
auto StateManager::doSave() -> void {
if(auto item = stateList.selected()) {
program->saveState(1 + item->offset(), true);
doRefresh();
descriptionValue.setFocused();
}
}
auto StateManager::doReset() -> void {
if(MessageDialog().setParent(*toolsManager).setText("Permanently erase all slots?").question() == 0) {
for(auto slot : range(Slots)) file::remove(program->stateName(1 + slot, true));
doRefresh();
}
}
auto StateManager::doErase() -> void {
if(auto item = stateList.selected()) {
file::remove(program->stateName(1 + item->offset(), true));
doRefresh();
}
}

View File

@ -0,0 +1,28 @@
#include "../tomoko.hpp"
#include "cheat-database.cpp"
#include "cheat-editor.cpp"
#include "state-manager.cpp"
CheatDatabase* cheatDatabase = nullptr;
ToolsManager* toolsManager = nullptr;
ToolsManager::ToolsManager() {
toolsManager = this;
layout.setMargin(5);
setTitle("Tools");
setSize({600, 400});
setPlacement(1.0, 1.0);
onSize([&] {
cheatEditor.cheatList.resizeColumns();
stateManager.stateList.resizeColumns();
});
}
auto ToolsManager::show(unsigned tool) -> void {
panel.item(tool)->setSelected();
setVisible();
setFocused();
doSize();
}

View File

@ -0,0 +1,86 @@
struct CheatDatabase : Window {
CheatDatabase();
auto findCodes() -> void;
auto addCodes() -> void;
vector<string> codes;
VerticalLayout layout{this};
ListView cheatList{&layout, Size{~0, ~0}};
HorizontalLayout controlLayout{&layout, Size{~0, 0}};
Button selectAllButton{&controlLayout, Size{100, 0}};
Button unselectAllButton{&controlLayout, Size{100, 0}};
Widget spacer{&controlLayout, Size{~0, 0}};
Button addCodesButton{&controlLayout, Size{80, 0}};
};
struct CheatEditor : TabFrameItem {
enum : unsigned { Slots = 128 };
CheatEditor(TabFrame*);
auto doChange() -> void;
auto doModify() -> void;
auto doRefresh() -> void;
auto doReset() -> void;
auto doErase() -> void;
auto synchronizeCodes() -> void;
auto addCode(const string& code, const string& description) -> bool;
struct Cheat {
string code;
string description;
};
Cheat cheats[Slots];
VerticalLayout layout{this};
ListView cheatList{&layout, Size{~0, ~0}};
HorizontalLayout codeLayout{&layout, Size{~0, 0}};
Label codeLabel{&codeLayout, Size{70, 0}};
LineEdit codeValue{&codeLayout, Size{~0, 0}};
HorizontalLayout descriptionLayout{&layout, Size{~0, 0}};
Label descriptionLabel{&descriptionLayout, Size{70, 0}};
LineEdit descriptionValue{&descriptionLayout, Size{~0, 0}};
HorizontalLayout controlLayout{&layout, Size{~0, 0}};
Button findCodesButton{&controlLayout, Size{120, 0}};
Widget spacer{&controlLayout, Size{~0, 0}};
Button resetButton{&controlLayout, Size{80, 0}};
Button eraseButton{&controlLayout, Size{80, 0}};
};
struct StateManager : TabFrameItem {
enum : unsigned { Slots = 32 };
StateManager(TabFrame*);
auto doChange() -> void;
auto doRefresh() -> void;
auto doLabel() -> void;
auto doLoad() -> void;
auto doSave() -> void;
auto doReset() -> void;
auto doErase() -> void;
VerticalLayout layout{this};
ListView stateList{&layout, Size{~0, ~0}};
HorizontalLayout descriptionLayout{&layout, Size{~0, 0}};
Label descriptionLabel{&descriptionLayout, Size{70, 0}};
LineEdit descriptionValue{&descriptionLayout, Size{~0, 0}};
HorizontalLayout controlLayout{&layout, Size{~0, 0}};
Button saveButton{&controlLayout, Size{80, 0}};
Button loadButton{&controlLayout, Size{80, 0}};
Widget spacer{&controlLayout, Size{~0, 0}};
Button resetButton{&controlLayout, Size{80, 0}};
Button eraseButton{&controlLayout, Size{80, 0}};
};
struct ToolsManager : Window {
ToolsManager();
auto show(unsigned tool) -> void;
VerticalLayout layout{this};
TabFrame panel{&layout, Size{~0, ~0}};
CheatEditor cheatEditor{&panel};
StateManager stateManager{&panel};
};
extern CheatDatabase* cheatDatabase;
extern ToolsManager* toolsManager;