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