mirror of https://github.com/bsnes-emu/bsnes.git
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:
parent
89d578bc7f
commit
2eb50fd70b
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
namespace Emulator {
|
namespace Emulator {
|
||||||
static const char Name[] = "higan";
|
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 Author[] = "byuu";
|
||||||
static const char License[] = "GPLv3";
|
static const char License[] = "GPLv3";
|
||||||
static const char Website[] = "http://byuu.org/";
|
static const char Website[] = "http://byuu.org/";
|
||||||
|
|
|
@ -49,7 +49,12 @@ auto pListView::destruct() -> void {
|
||||||
auto pListView::append(sListViewColumn column) -> void {
|
auto pListView::append(sListViewColumn column) -> void {
|
||||||
gtk_tree_view_append_column(gtkTreeView, column->self()->gtkColumn);
|
gtk_tree_view_append_column(gtkTreeView, column->self()->gtkColumn);
|
||||||
gtk_widget_show_all(column->self()->gtkHeader);
|
gtk_widget_show_all(column->self()->gtkHeader);
|
||||||
|
column->setBackgroundColor(column->backgroundColor());
|
||||||
|
column->setEditable(column->editable());
|
||||||
column->setFont(column->font());
|
column->setFont(column->font());
|
||||||
|
column->setForegroundColor(column->foregroundColor());
|
||||||
|
column->setHorizontalAlignment(column->horizontalAlignment());
|
||||||
|
column->setVerticalAlignment(column->verticalAlignment());
|
||||||
setCheckable(state().checkable);
|
setCheckable(state().checkable);
|
||||||
_createModel();
|
_createModel();
|
||||||
gtk_tree_view_set_rules_hint(gtkTreeView, self().columns() >= 2); //two or more columns + checkbutton column
|
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())) {
|
for(auto row : range(self().items())) {
|
||||||
maximumWidth = max(maximumWidth, 8 //margin
|
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
|
+ state().items[row]->state.icon(column, {}).width
|
||||||
+ Font::size(state().columns[column]->font(true), state().items[row]->state.text(column, "")).width()
|
+ Font::size(state().columns[column]->font(true), state().items[row]->state.text(column, "")).width()
|
||||||
);
|
);
|
||||||
|
|
|
@ -93,6 +93,7 @@ inline auto realpath(rstring name) -> string;
|
||||||
inline auto programpath() -> string;
|
inline auto programpath() -> string;
|
||||||
inline auto userpath() -> string;
|
inline auto userpath() -> string;
|
||||||
inline auto configpath() -> string;
|
inline auto configpath() -> string;
|
||||||
|
inline auto localpath() -> string;
|
||||||
inline auto sharedpath() -> string;
|
inline auto sharedpath() -> string;
|
||||||
inline auto temppath() -> string;
|
inline auto temppath() -> string;
|
||||||
|
|
||||||
|
|
|
@ -71,6 +71,24 @@ auto configpath() -> string {
|
||||||
return result;
|
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
|
// /usr/share
|
||||||
// /Library/Application Support/
|
// /Library/Application Support/
|
||||||
// c:/ProgramData/
|
// c:/ProgramData/
|
||||||
|
|
|
@ -9,7 +9,7 @@ include gb/GNUmakefile
|
||||||
include gba/GNUmakefile
|
include gba/GNUmakefile
|
||||||
|
|
||||||
ui_objects := ui-tomoko ui-program ui-configuration ui-input
|
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
|
ui_objects += ruby hiro
|
||||||
|
|
||||||
# platform
|
# 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-input.o: $(ui)/input/input.cpp $(call rwildcard,$(ui)/)
|
||||||
obj/ui-library.o: $(ui)/library/library.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-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)/)
|
obj/ui-presentation.o: $(ui)/presentation/presentation.cpp $(call rwildcard,$(ui)/)
|
||||||
|
|
||||||
# targets
|
# targets
|
||||||
|
@ -67,9 +68,13 @@ ifeq ($(shell id -un),root)
|
||||||
else ifeq ($(platform),windows)
|
else ifeq ($(platform),windows)
|
||||||
else ifeq ($(platform),macosx)
|
else ifeq ($(platform),macosx)
|
||||||
else
|
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 out/$(name) $(prefix)/bin/$(name)
|
||||||
cp data/higan.png $(prefix)/share/icons/$(name).png
|
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/
|
cp -R profile/* ~/Emulation/System/
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,9 @@ ConfigurationManager::ConfigurationManager() {
|
||||||
userInterface.append(userInterface.showStatusBar, "ShowStatusBar");
|
userInterface.append(userInterface.showStatusBar, "ShowStatusBar");
|
||||||
append(userInterface, "UserInterface");
|
append(userInterface, "UserInterface");
|
||||||
|
|
||||||
|
library.append(library.location, "Location");
|
||||||
|
append(library, "Library");
|
||||||
|
|
||||||
video.append(video.driver, "Driver");
|
video.append(video.driver, "Driver");
|
||||||
video.append(video.synchronize, "Synchronize");
|
video.append(video.synchronize, "Synchronize");
|
||||||
video.append(video.scale, "Scale");
|
video.append(video.scale, "Scale");
|
||||||
|
@ -25,6 +28,7 @@ ConfigurationManager::ConfigurationManager() {
|
||||||
append(input, "Input");
|
append(input, "Input");
|
||||||
|
|
||||||
load({configpath(), "tomoko/settings.bml"});
|
load({configpath(), "tomoko/settings.bml"});
|
||||||
|
if(!library.location) library.location = {userpath(), "Emulation/"};
|
||||||
if(!video.driver) video.driver = ruby::video.safestDriver();
|
if(!video.driver) video.driver = ruby::video.safestDriver();
|
||||||
if(!audio.driver) audio.driver = ruby::audio.safestDriver();
|
if(!audio.driver) audio.driver = ruby::audio.safestDriver();
|
||||||
if(!input.driver) input.driver = ruby::input.safestDriver();
|
if(!input.driver) input.driver = ruby::input.safestDriver();
|
||||||
|
|
|
@ -6,6 +6,10 @@ struct ConfigurationManager : Configuration::Document {
|
||||||
bool showStatusBar = true;
|
bool showStatusBar = true;
|
||||||
} userInterface;
|
} userInterface;
|
||||||
|
|
||||||
|
struct Library : Configuration::Node {
|
||||||
|
string location;
|
||||||
|
} library;
|
||||||
|
|
||||||
struct Video : Configuration::Node {
|
struct Video : Configuration::Node {
|
||||||
string driver;
|
string driver;
|
||||||
bool synchronize = false;
|
bool synchronize = false;
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
auto InputManager::appendHotkeys() -> void {
|
auto InputManager::appendHotkeys() -> void {
|
||||||
{
|
{ auto hotkey = new InputHotkey;
|
||||||
auto hotkey = new InputHotkey;
|
|
||||||
hotkey->name = "Toggle Fullscreen";
|
hotkey->name = "Toggle Fullscreen";
|
||||||
hotkey->action = [] {
|
hotkey->action = [] {
|
||||||
presentation->toggleFullScreen();
|
presentation->toggleFullScreen();
|
||||||
|
@ -8,6 +7,46 @@ auto InputManager::appendHotkeys() -> void {
|
||||||
hotkeys.append(hotkey);
|
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;
|
Configuration::Node nodeHotkeys;
|
||||||
for(auto& hotkey : hotkeys) {
|
for(auto& hotkey : hotkeys) {
|
||||||
nodeHotkeys.append(hotkey->assignment, string{hotkey->name}.replace(" ", ""));
|
nodeHotkeys.append(hotkey->assignment, string{hotkey->name}.replace(" ", ""));
|
||||||
|
|
|
@ -4,12 +4,12 @@ LibraryBrowser::LibraryBrowser(TabFrame& parent, Emulator::Interface::Media& med
|
||||||
layout.setMargin(5);
|
layout.setMargin(5);
|
||||||
gameList.onActivate([&] {
|
gameList.onActivate([&] {
|
||||||
libraryManager->setVisible(false);
|
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 {
|
auto LibraryBrowser::reload() -> void {
|
||||||
string path = {userpath(), "Emulation/", media.name};
|
string path = {config().library.location, media.name};
|
||||||
directory::create(path);
|
directory::create(path);
|
||||||
|
|
||||||
gameList.reset();
|
gameList.reset();
|
||||||
|
|
|
@ -13,7 +13,6 @@ LibraryManager::LibraryManager() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setTitle("Library");
|
|
||||||
setSize({640, 800});
|
setSize({640, 800});
|
||||||
setPlacement(0.0, 0.0);
|
setPlacement(0.0, 0.0);
|
||||||
}
|
}
|
||||||
|
@ -24,6 +23,7 @@ auto LibraryManager::show(const string& type) -> void {
|
||||||
browser->select();
|
browser->select();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setTitle({"Library (", config().library.location, ")"});
|
||||||
setVisible();
|
setVisible();
|
||||||
setFocused();
|
setFocused();
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,8 @@ Presentation::Presentation() {
|
||||||
}
|
}
|
||||||
|
|
||||||
systemMenu.setText("System").setVisible(false);
|
systemMenu.setText("System").setVisible(false);
|
||||||
powerSystem.setText("Power");
|
powerSystem.setText("Power").onActivate([&] { program->powerCycle(); });
|
||||||
resetSystem.setText("Reset");
|
resetSystem.setText("Reset").onActivate([&] { program->softReset(); });
|
||||||
unloadSystem.setText("Unload").onActivate([&] { program->unloadMedia(); drawSplashScreen(); });
|
unloadSystem.setText("Unload").onActivate([&] { program->unloadMedia(); drawSplashScreen(); });
|
||||||
|
|
||||||
settingsMenu.setText("Settings");
|
settingsMenu.setText("Settings");
|
||||||
|
@ -72,10 +72,7 @@ Presentation::Presentation() {
|
||||||
statusBar.setVisible(config().userInterface.showStatusBar);
|
statusBar.setVisible(config().userInterface.showStatusBar);
|
||||||
if(visible()) resizeViewport();
|
if(visible()) resizeViewport();
|
||||||
});
|
});
|
||||||
showConfiguration.setText("Configuration ...").onActivate([&] {
|
showConfiguration.setText("Configuration ...").onActivate([&] { settingsManager->show(0); });
|
||||||
settingsManager->setVisible();
|
|
||||||
settingsManager->setFocused();
|
|
||||||
});
|
|
||||||
|
|
||||||
toolsMenu.setText("Tools").setVisible(false);
|
toolsMenu.setText("Tools").setVisible(false);
|
||||||
saveStateMenu.setText("Save State");
|
saveStateMenu.setText("Save State");
|
||||||
|
@ -90,8 +87,8 @@ Presentation::Presentation() {
|
||||||
loadSlot3.setText("Slot 3").onActivate([&] { program->loadState(3); });
|
loadSlot3.setText("Slot 3").onActivate([&] { program->loadState(3); });
|
||||||
loadSlot4.setText("Slot 4").onActivate([&] { program->loadState(4); });
|
loadSlot4.setText("Slot 4").onActivate([&] { program->loadState(4); });
|
||||||
loadSlot5.setText("Slot 5").onActivate([&] { program->loadState(5); });
|
loadSlot5.setText("Slot 5").onActivate([&] { program->loadState(5); });
|
||||||
stateManager.setText("State Manager").onActivate([&] {});
|
cheatEditor.setText("Cheat Editor").onActivate([&] { toolsManager->show(0); });
|
||||||
cheatEditor.setText("Cheat Editor").onActivate([&] {});
|
stateManager.setText("State Manager").onActivate([&] { toolsManager->show(1); });
|
||||||
|
|
||||||
statusBar.setFont(Font::sans(8, "Bold"));
|
statusBar.setFont(Font::sans(8, "Bold"));
|
||||||
statusBar.setVisible(config().userInterface.showStatusBar);
|
statusBar.setVisible(config().userInterface.showStatusBar);
|
||||||
|
@ -108,15 +105,13 @@ auto Presentation::resizeViewport() -> void {
|
||||||
signed width = 256;
|
signed width = 256;
|
||||||
signed height = 240;
|
signed height = 240;
|
||||||
|
|
||||||
if(program->activeEmulator) {
|
if(emulator) {
|
||||||
width = program->emulator().information.width;
|
width = emulator->information.width;
|
||||||
height = program->emulator().information.height;
|
height = emulator->information.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(fullScreen() == false) {
|
if(fullScreen() == false) {
|
||||||
bool arc = config().video.aspectCorrection
|
bool arc = config().video.aspectCorrection && emulator && emulator->information.aspectRatio != 1.0;
|
||||||
&& program->activeEmulator
|
|
||||||
&& program->emulator().information.aspectRatio != 1.0;
|
|
||||||
|
|
||||||
if(config().video.scale == "Small" ) width *= 1, height *= 1;
|
if(config().video.scale == "Small" ) width *= 1, height *= 1;
|
||||||
if(config().video.scale == "Normal") width *= 2, height *= 2;
|
if(config().video.scale == "Normal") width *= 2, height *= 2;
|
||||||
|
@ -149,7 +144,7 @@ auto Presentation::resizeViewport() -> void {
|
||||||
viewport.setGeometry({x, y, width, height});
|
viewport.setGeometry({x, y, width, height});
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!program->activeEmulator) drawSplashScreen();
|
if(!emulator) drawSplashScreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Presentation::toggleFullScreen() -> void {
|
auto Presentation::toggleFullScreen() -> void {
|
||||||
|
|
|
@ -46,8 +46,8 @@ struct Presentation : Window {
|
||||||
MenuItem loadSlot4{&loadStateMenu};
|
MenuItem loadSlot4{&loadStateMenu};
|
||||||
MenuItem loadSlot5{&loadStateMenu};
|
MenuItem loadSlot5{&loadStateMenu};
|
||||||
MenuSeparator toolsMenuSeparator{&toolsMenu};
|
MenuSeparator toolsMenuSeparator{&toolsMenu};
|
||||||
MenuItem stateManager{&toolsMenu};
|
MenuItem cheatEditor{&toolsMenu};
|
||||||
MenuItem cheatEditor{&toolsMenu};
|
MenuItem stateManager{&toolsMenu};
|
||||||
|
|
||||||
FixedLayout layout{this};
|
FixedLayout layout{this};
|
||||||
Viewport viewport{&layout, Geometry{0, 0, 1, 1}};
|
Viewport viewport{&layout, Geometry{0, 0, 1, 1}};
|
||||||
|
|
|
@ -4,17 +4,17 @@ auto Program::loadRequest(unsigned id, string name, string type) -> void {
|
||||||
|
|
||||||
//request from emulation core to load non-volatile media file
|
//request from emulation core to load non-volatile media file
|
||||||
auto Program::loadRequest(unsigned id, string path) -> void {
|
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;
|
if(!file::exists(location)) return;
|
||||||
mmapstream stream{location};
|
mmapstream stream{location};
|
||||||
return emulator().load(id, stream);
|
return emulator->load(id, stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
//request from emulation core to save non-volatile media file
|
//request from emulation core to save non-volatile media file
|
||||||
auto Program::saveRequest(unsigned id, string path) -> void {
|
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};
|
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 {
|
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 {
|
auto Program::inputPoll(unsigned port, unsigned device, unsigned input) -> int16 {
|
||||||
if(presentation->focused()) {
|
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;
|
auto mapping = (InputMapping*)guid;
|
||||||
if(mapping) return mapping->poll();
|
if(mapping) return mapping->poll();
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,22 +20,25 @@ auto Program::loadMedia(Emulator::Interface& _emulator, Emulator::Interface::Med
|
||||||
mediaPaths(media.id) = location;
|
mediaPaths(media.id) = location;
|
||||||
folderPaths.append(location);
|
folderPaths.append(location);
|
||||||
|
|
||||||
setEmulator(&_emulator);
|
emulator = &_emulator;
|
||||||
updateVideoPalette();
|
updateVideoPalette();
|
||||||
emulator().load(media.id);
|
emulator->load(media.id);
|
||||||
emulator().power();
|
emulator->power();
|
||||||
|
|
||||||
presentation->resizeViewport();
|
presentation->resizeViewport();
|
||||||
presentation->setTitle(emulator().title());
|
presentation->setTitle(emulator->title());
|
||||||
presentation->systemMenu.setVisible(true);
|
presentation->systemMenu.setVisible(true);
|
||||||
presentation->toolsMenu.setVisible(true);
|
presentation->toolsMenu.setVisible(true);
|
||||||
|
toolsManager->cheatEditor.doRefresh();
|
||||||
|
toolsManager->stateManager.doRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Program::unloadMedia() -> void {
|
auto Program::unloadMedia() -> void {
|
||||||
if(activeEmulator == nullptr) return;
|
if(!emulator) return;
|
||||||
emulator().unload();
|
|
||||||
|
emulator->unload();
|
||||||
|
emulator = nullptr;
|
||||||
|
|
||||||
setEmulator(nullptr);
|
|
||||||
mediaPaths.reset();
|
mediaPaths.reset();
|
||||||
folderPaths.reset();
|
folderPaths.reset();
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,8 @@ Program::Program() {
|
||||||
new InputManager;
|
new InputManager;
|
||||||
new LibraryManager;
|
new LibraryManager;
|
||||||
new SettingsManager;
|
new SettingsManager;
|
||||||
|
new CheatDatabase;
|
||||||
|
new ToolsManager;
|
||||||
new Presentation;
|
new Presentation;
|
||||||
|
|
||||||
presentation->setVisible();
|
presentation->setVisible();
|
||||||
|
@ -56,22 +58,17 @@ Program::Program() {
|
||||||
updateVideoFilter();
|
updateVideoFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Program::emulator() -> Emulator::Interface& {
|
|
||||||
if(!activeEmulator) throw;
|
|
||||||
return *activeEmulator;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Program::main() -> void {
|
auto Program::main() -> void {
|
||||||
updateStatusText();
|
updateStatusText();
|
||||||
inputManager->poll();
|
inputManager->poll();
|
||||||
|
|
||||||
if(!activeEmulator || emulator().loaded() == false) {
|
if(!emulator || !emulator->loaded() || pause) {
|
||||||
audio.clear();
|
audio.clear();
|
||||||
usleep(20 * 1000);
|
usleep(20 * 1000);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
emulator().run();
|
emulator->run();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Program::quit() -> void {
|
auto Program::quit() -> void {
|
||||||
|
@ -83,7 +80,3 @@ auto Program::quit() -> void {
|
||||||
input.term();
|
input.term();
|
||||||
Application::quit();
|
Application::quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Program::setEmulator(Emulator::Interface* emulator) -> void {
|
|
||||||
activeEmulator = emulator;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
struct Program : Emulator::Interface::Bind {
|
struct Program : Emulator::Interface::Bind {
|
||||||
//program.cpp
|
//program.cpp
|
||||||
Program();
|
Program();
|
||||||
auto emulator() -> Emulator::Interface&;
|
|
||||||
auto main() -> void;
|
auto main() -> void;
|
||||||
auto quit() -> void;
|
auto quit() -> void;
|
||||||
auto setEmulator(Emulator::Interface*) -> void;
|
|
||||||
|
|
||||||
//interface.cpp
|
//interface.cpp
|
||||||
auto loadRequest(unsigned id, string name, string type) -> void override;
|
auto loadRequest(unsigned id, string name, string type) -> void override;
|
||||||
|
@ -25,19 +23,22 @@ struct Program : Emulator::Interface::Bind {
|
||||||
auto unloadMedia() -> void;
|
auto unloadMedia() -> void;
|
||||||
|
|
||||||
//state.cpp
|
//state.cpp
|
||||||
auto loadState(unsigned slot) -> bool;
|
auto stateName(unsigned slot, bool manager = false) -> string;
|
||||||
auto saveState(unsigned slot) -> bool;
|
auto loadState(unsigned slot, bool manager = false) -> bool;
|
||||||
|
auto saveState(unsigned slot, bool manager = false) -> bool;
|
||||||
|
|
||||||
//utility.cpp
|
//utility.cpp
|
||||||
|
auto powerCycle() -> void;
|
||||||
|
auto softReset() -> void;
|
||||||
auto showMessage(const string& text) -> void;
|
auto showMessage(const string& text) -> void;
|
||||||
auto updateStatusText() -> void;
|
auto updateStatusText() -> void;
|
||||||
auto updateVideoFilter() -> void;
|
auto updateVideoFilter() -> void;
|
||||||
auto updateVideoPalette() -> void;
|
auto updateVideoPalette() -> void;
|
||||||
|
|
||||||
DSP dsp;
|
DSP dsp;
|
||||||
|
bool pause = false;
|
||||||
|
|
||||||
vector<Emulator::Interface*> emulators;
|
vector<Emulator::Interface*> emulators;
|
||||||
Emulator::Interface* activeEmulator = nullptr;
|
|
||||||
|
|
||||||
vector<string> mediaPaths;
|
vector<string> mediaPaths;
|
||||||
vector<string> folderPaths;
|
vector<string> folderPaths;
|
||||||
|
|
|
@ -1,18 +1,28 @@
|
||||||
auto Program::loadState(unsigned slot) -> bool {
|
auto Program::stateName(unsigned slot, bool manager) -> string {
|
||||||
if(!activeEmulator) return false;
|
return {
|
||||||
auto memory = file::read({folderPaths[0], "higan/state-", slot, ".bst"});
|
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;
|
if(memory.size() == 0) return showMessage({"Slot ", slot, " does not exist"}), false;
|
||||||
serializer s(memory.data(), memory.size());
|
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;
|
return showMessage({"Loaded from slot ", slot}), true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Program::saveState(unsigned slot) -> bool {
|
auto Program::saveState(unsigned slot, bool manager) -> bool {
|
||||||
if(!activeEmulator) return false;
|
if(!emulator) return false;
|
||||||
serializer s = emulator().serialize();
|
auto location = stateName(slot, manager);
|
||||||
|
serializer s = emulator->serialize();
|
||||||
if(s.size() == 0) return showMessage({"Failed to save state to slot ", slot}), false;
|
if(s.size() == 0) return showMessage({"Failed to save state to slot ", slot}), false;
|
||||||
directory::create({folderPaths[0], "higan/"});
|
directory::create(location.pathname());
|
||||||
if(file::write({folderPaths[0], "higan/state-", slot, ".bst"}, s.data(), s.size()) == false) {
|
if(file::write(location, s.data(), s.size()) == false) {
|
||||||
return showMessage({"Unable to write to slot ", slot}), false;
|
return showMessage({"Unable to write to slot ", slot}), false;
|
||||||
}
|
}
|
||||||
return showMessage({"Saved to slot ", slot}), true;
|
return showMessage({"Saved to slot ", slot}), true;
|
||||||
|
|
|
@ -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 {
|
auto Program::showMessage(const string& text) -> void {
|
||||||
statusTime = time(0);
|
statusTime = time(0);
|
||||||
statusMessage = text;
|
statusMessage = text;
|
||||||
|
@ -9,9 +22,9 @@ auto Program::updateStatusText() -> void {
|
||||||
string text;
|
string text;
|
||||||
if((currentTime - statusTime) <= 2) {
|
if((currentTime - statusTime) <= 2) {
|
||||||
text = statusMessage;
|
text = statusMessage;
|
||||||
} else if(!activeEmulator || emulator().loaded() == false) {
|
} else if(!emulator || emulator->loaded() == false) {
|
||||||
text = "No cartridge loaded";
|
text = "No cartridge loaded";
|
||||||
} else if(0) {
|
} else if(pause) {
|
||||||
text = "Paused";
|
text = "Paused";
|
||||||
} else {
|
} else {
|
||||||
text = statusText;
|
text = statusText;
|
||||||
|
@ -28,8 +41,8 @@ auto Program::updateVideoFilter() -> void {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Program::updateVideoPalette() -> void {
|
auto Program::updateVideoPalette() -> void {
|
||||||
if(!activeEmulator) return;
|
if(!emulator) return;
|
||||||
emulator().paletteUpdate(config().video.colorEmulation
|
emulator->paletteUpdate(config().video.colorEmulation
|
||||||
? Emulator::Interface::PaletteMode::Emulation
|
? Emulator::Interface::PaletteMode::Emulation
|
||||||
: Emulator::Interface::PaletteMode::Standard
|
: Emulator::Interface::PaletteMode::Standard
|
||||||
);
|
);
|
||||||
|
|
|
@ -3,6 +3,7 @@ AdvancedSettings::AdvancedSettings(TabFrame* parent) : TabFrameItem(parent) {
|
||||||
setText("Advanced");
|
setText("Advanced");
|
||||||
|
|
||||||
layout.setMargin(5);
|
layout.setMargin(5);
|
||||||
|
|
||||||
driverLabel.setText("Driver Selection").setFont(Font::sans(8, "Bold"));
|
driverLabel.setText("Driver Selection").setFont(Font::sans(8, "Bold"));
|
||||||
videoLabel.setText("Video:");
|
videoLabel.setText("Video:");
|
||||||
videoDriver.onChange([&] { config().video.driver = videoDriver.selected()->text(); });
|
videoDriver.onChange([&] { config().video.driver = videoDriver.selected()->text(); });
|
||||||
|
@ -28,4 +29,13 @@ AdvancedSettings::AdvancedSettings(TabFrame* parent) : TabFrameItem(parent) {
|
||||||
inputDriver.append(item);
|
inputDriver.append(item);
|
||||||
if(config().input.driver == driver) item.setSelected();
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,7 +63,7 @@ auto InputSettings::reloadMappings() -> void {
|
||||||
mappingList.reset();
|
mappingList.reset();
|
||||||
mappingList.append(ListViewColumn().setText("Name"));
|
mappingList.append(ListViewColumn().setText("Name"));
|
||||||
mappingList.append(ListViewColumn().setText("Mapping").setWidth(~0));
|
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) {
|
for(auto& mapping : activeDevice().mappings) {
|
||||||
mappingList.append(ListViewItem().setText(0, mapping->name));
|
mappingList.append(ListViewItem().setText(0, mapping->name));
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,15 @@ SettingsManager::SettingsManager() {
|
||||||
setSize({600, 400});
|
setSize({600, 400});
|
||||||
setPlacement(0.0, 1.0);
|
setPlacement(0.0, 1.0);
|
||||||
|
|
||||||
input.mappingList.resizeColumns();
|
onSize([&] {
|
||||||
hotkeys.mappingList.resizeColumns();
|
input.mappingList.resizeColumns();
|
||||||
|
hotkeys.mappingList.resizeColumns();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
auto SettingsManager::show(unsigned setting) -> void {
|
||||||
|
panel.item(setting)->setSelected();
|
||||||
|
setVisible();
|
||||||
|
setFocused();
|
||||||
|
doSize();
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,16 +52,22 @@ struct AdvancedSettings : TabFrameItem {
|
||||||
ComboButton audioDriver{&driverLayout, Size{~0, 0}};
|
ComboButton audioDriver{&driverLayout, Size{~0, 0}};
|
||||||
Label inputLabel{&driverLayout, Size{0, 0}};
|
Label inputLabel{&driverLayout, Size{0, 0}};
|
||||||
ComboButton inputDriver{&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 {
|
struct SettingsManager : Window {
|
||||||
SettingsManager();
|
SettingsManager();
|
||||||
|
auto show(unsigned setting) -> void;
|
||||||
|
|
||||||
VerticalLayout layout{this};
|
VerticalLayout layout{this};
|
||||||
TabFrame panelLayout{&layout, Size{~0, ~0}};
|
TabFrame panel{&layout, Size{~0, ~0}};
|
||||||
InputSettings input{&panelLayout};
|
InputSettings input{&panel};
|
||||||
HotkeySettings hotkeys{&panelLayout};
|
HotkeySettings hotkeys{&panel};
|
||||||
AdvancedSettings advanced{&panelLayout};
|
AdvancedSettings advanced{&panel};
|
||||||
|
|
||||||
StatusBar statusBar{this};
|
StatusBar statusBar{this};
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#include "tomoko.hpp"
|
#include "tomoko.hpp"
|
||||||
|
Emulator::Interface* emulator = nullptr;
|
||||||
|
|
||||||
#include <nall/main.hpp>
|
#include <nall/main.hpp>
|
||||||
auto nall::main(lstring args) -> void {
|
auto nall::main(lstring args) -> void {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#include <emulator/emulator.hpp>
|
#include <emulator/emulator.hpp>
|
||||||
|
extern Emulator::Interface* emulator;
|
||||||
|
|
||||||
#include <nall/nall.hpp>
|
#include <nall/nall.hpp>
|
||||||
#include <ruby/ruby.hpp>
|
#include <ruby/ruby.hpp>
|
||||||
|
@ -12,4 +13,5 @@ using namespace hiro;
|
||||||
#include "input/input.hpp"
|
#include "input/input.hpp"
|
||||||
#include "library/library.hpp"
|
#include "library/library.hpp"
|
||||||
#include "settings/settings.hpp"
|
#include "settings/settings.hpp"
|
||||||
|
#include "tools/tools.hpp"
|
||||||
#include "presentation/presentation.hpp"
|
#include "presentation/presentation.hpp"
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
|
@ -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;
|
Loading…
Reference in New Issue