mirror of https://github.com/bsnes-emu/bsnes.git
Update to v094r16 release.
byuu says: Finished the cheat code system, it'll now load and save cheats.bml to disk. Also hooked up overscan masking. But for now you can only configure the amount it clips via the configuration file, since I don't have a video settings dialog anymore. And that's the last of the low-hanging fruit. The remaining items are all going to be a pain in the ass for one reason or another. Short-term: - add input port changing support - add other input types (mouse-based, etc) Long-term: - add slotted cart loader (SGB, BSX, ST) - add DIP switch selection window (NSS) - 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
2eb50fd70b
commit
c335ee9d80
|
@ -3,7 +3,7 @@
|
|||
|
||||
namespace Emulator {
|
||||
static const char Name[] = "higan";
|
||||
static const char Version[] = "094.15";
|
||||
static const char Version[] = "094.16";
|
||||
static const char Author[] = "byuu";
|
||||
static const char License[] = "GPLv3";
|
||||
static const char Website[] = "http://byuu.org/";
|
||||
|
|
|
@ -17,6 +17,10 @@ ConfigurationManager::ConfigurationManager() {
|
|||
video.append(video.aspectCorrection, "AspectCorrection");
|
||||
video.append(video.filter, "Filter");
|
||||
video.append(video.colorEmulation, "ColorEmulation");
|
||||
video.overscan.append(video.overscan.mask, "Mask");
|
||||
video.overscan.append(video.overscan.horizontal, "Horizontal");
|
||||
video.overscan.append(video.overscan.vertical, "Vertical");
|
||||
video.append(video.overscan, "Overscan");
|
||||
append(video, "Video");
|
||||
|
||||
audio.append(audio.driver, "Driver");
|
||||
|
|
|
@ -17,6 +17,12 @@ struct ConfigurationManager : Configuration::Document {
|
|||
bool aspectCorrection = true;
|
||||
string filter = "Blur";
|
||||
bool colorEmulation = true;
|
||||
|
||||
struct Overscan : Configuration::Node {
|
||||
bool mask = false;
|
||||
unsigned horizontal = 8;
|
||||
unsigned vertical = 8;
|
||||
} overscan;
|
||||
} video;
|
||||
|
||||
struct Audio : Configuration::Node {
|
||||
|
|
|
@ -39,6 +39,10 @@ Presentation::Presentation() {
|
|||
config().video.scale = "Large";
|
||||
resizeViewport();
|
||||
});
|
||||
aspectCorrection.setText("Aspect Correction").setChecked(config().video.aspectCorrection).onToggle([&] {
|
||||
config().video.aspectCorrection = aspectCorrection.checked();
|
||||
resizeViewport();
|
||||
});
|
||||
videoFilterMenu.setText("Video Filter");
|
||||
MenuRadioItem::group({videoFilterNone, videoFilterBlur});
|
||||
if(config().video.filter == "None") videoFilterNone.setChecked();
|
||||
|
@ -49,11 +53,8 @@ Presentation::Presentation() {
|
|||
config().video.colorEmulation = colorEmulation.checked();
|
||||
program->updateVideoPalette();
|
||||
});
|
||||
aspectCorrection.setText("Aspect Correction").setChecked(config().video.aspectCorrection).onToggle([&] {
|
||||
config().video.aspectCorrection = aspectCorrection.checked();
|
||||
resizeViewport();
|
||||
});
|
||||
maskOverscan.setText("Mask Overscan").onToggle([&] {
|
||||
maskOverscan.setText("Mask Overscan").setChecked(config().video.overscan.mask).onToggle([&] {
|
||||
config().video.overscan.mask = maskOverscan.checked();
|
||||
});
|
||||
synchronizeVideo.setText("Synchronize Video").setChecked(config().video.synchronize).onToggle([&] {
|
||||
config().video.synchronize = synchronizeVideo.checked();
|
||||
|
|
|
@ -19,12 +19,12 @@ struct Presentation : Window {
|
|||
MenuRadioItem videoScaleLarge{&videoScaleMenu};
|
||||
MenuSeparator videoScaleSeparator{&videoScaleMenu};
|
||||
MenuCheckItem aspectCorrection{&videoScaleMenu};
|
||||
MenuCheckItem maskOverscan{&videoScaleMenu};
|
||||
Menu videoFilterMenu{&settingsMenu};
|
||||
MenuRadioItem videoFilterNone{&videoFilterMenu};
|
||||
MenuRadioItem videoFilterBlur{&videoFilterMenu};
|
||||
MenuSeparator videoFilterSeparator{&videoFilterMenu};
|
||||
MenuCheckItem colorEmulation{&videoFilterMenu};
|
||||
MenuCheckItem maskOverscan{&videoFilterMenu};
|
||||
MenuSeparator settingsMenuSeparator1{&settingsMenu};
|
||||
MenuCheckItem synchronizeVideo{&settingsMenu};
|
||||
MenuCheckItem synchronizeAudio{&settingsMenu};
|
||||
|
|
|
@ -40,6 +40,21 @@ auto Program::videoRefresh(const uint32* palette, const uint32* data, unsigned p
|
|||
}
|
||||
}
|
||||
|
||||
if(emulator->information.overscan && config().video.overscan.mask) {
|
||||
unsigned h = config().video.overscan.horizontal;
|
||||
unsigned v = config().video.overscan.vertical;
|
||||
|
||||
if(h) for(auto y : range(height)) {
|
||||
memory::fill(output + y * length, 4 * h);
|
||||
memory::fill(output + y * length + (width - h), 4 * h);
|
||||
}
|
||||
|
||||
if(v) for(auto y : range(v)) {
|
||||
memory::fill(output + y * length, 4 * width);
|
||||
memory::fill(output + (height - 1 - y) * length, 4 * width);
|
||||
}
|
||||
}
|
||||
|
||||
video.unlock();
|
||||
video.refresh();
|
||||
}
|
||||
|
|
|
@ -29,13 +29,14 @@ auto Program::loadMedia(Emulator::Interface& _emulator, Emulator::Interface::Med
|
|||
presentation->setTitle(emulator->title());
|
||||
presentation->systemMenu.setVisible(true);
|
||||
presentation->toolsMenu.setVisible(true);
|
||||
toolsManager->cheatEditor.doRefresh();
|
||||
toolsManager->cheatEditor.loadCheats();
|
||||
toolsManager->stateManager.doRefresh();
|
||||
}
|
||||
|
||||
auto Program::unloadMedia() -> void {
|
||||
if(!emulator) return;
|
||||
|
||||
toolsManager->cheatEditor.saveCheats();
|
||||
emulator->unload();
|
||||
emulator = nullptr;
|
||||
|
||||
|
@ -45,4 +46,5 @@ auto Program::unloadMedia() -> void {
|
|||
presentation->setTitle({"tomoko v", Emulator::Version});
|
||||
presentation->systemMenu.setVisible(false);
|
||||
presentation->toolsMenu.setVisible(false);
|
||||
toolsManager->setVisible(false);
|
||||
}
|
||||
|
|
|
@ -49,4 +49,5 @@ auto CheatDatabase::addCodes() -> void {
|
|||
}
|
||||
}
|
||||
setVisible(false);
|
||||
toolsManager->cheatEditor.doRefresh();
|
||||
}
|
||||
|
|
|
@ -4,13 +4,16 @@ CheatEditor::CheatEditor(TabFrame* parent) : TabFrameItem(parent) {
|
|||
|
||||
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("Code(s)"));
|
||||
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(); });
|
||||
cheatList.onChange([&] { doChangeSelected(); });
|
||||
cheatList.onToggle([&](sListViewItem item) {
|
||||
cheats[item->offset()].enabled = item->checked();
|
||||
synchronizeCodes();
|
||||
});
|
||||
codeLabel.setText("Code(s):");
|
||||
codeValue.onChange([&] { doModify(); });
|
||||
descriptionLabel.setText("Description:");
|
||||
|
@ -20,11 +23,11 @@ CheatEditor::CheatEditor(TabFrame* parent) : TabFrameItem(parent) {
|
|||
eraseButton.setText("Erase").onActivate([&] { doErase(); });
|
||||
}
|
||||
|
||||
auto CheatEditor::doChange() -> void {
|
||||
auto CheatEditor::doChangeSelected() -> void {
|
||||
if(auto item = cheatList.selected()) {
|
||||
unsigned slot = item->offset();
|
||||
codeValue.setEnabled(true).setText(cheats[slot].code);
|
||||
descriptionValue.setEnabled(true).setText(cheats[slot].description);
|
||||
auto& cheat = cheats[item->offset()];
|
||||
codeValue.setEnabled(true).setText(cheat.code);
|
||||
descriptionValue.setEnabled(true).setText(cheat.description);
|
||||
eraseButton.setEnabled(true);
|
||||
} else {
|
||||
codeValue.setEnabled(false).setText("");
|
||||
|
@ -35,9 +38,9 @@ auto CheatEditor::doChange() -> void {
|
|||
|
||||
auto CheatEditor::doModify() -> void {
|
||||
if(auto item = cheatList.selected()) {
|
||||
unsigned slot = item->offset();
|
||||
cheats[slot].code = codeValue.text();
|
||||
cheats[slot].description = descriptionValue.text();
|
||||
auto& cheat = cheats[item->offset()];
|
||||
cheat.code = codeValue.text();
|
||||
cheat.description = descriptionValue.text();
|
||||
doRefresh();
|
||||
synchronizeCodes();
|
||||
}
|
||||
|
@ -45,26 +48,28 @@ auto CheatEditor::doModify() -> void {
|
|||
|
||||
auto CheatEditor::doRefresh() -> void {
|
||||
for(auto slot : range(Slots)) {
|
||||
if(cheats[slot].code || cheats[slot].description) {
|
||||
lstring codes = cheats[slot].code.split("+");
|
||||
auto& cheat = cheats[slot];
|
||||
if(cheat.code || cheat.description) {
|
||||
lstring codes = cheat.code.split("+");
|
||||
if(codes.size() > 1) codes[0].append("+...");
|
||||
cheatList.item(slot)->setText(1, codes[0]).setText(2, cheats[slot].description);
|
||||
cheatList.item(slot)->setChecked(cheat.enabled).setText(1, codes[0]).setText(2, cheat.description);
|
||||
} else {
|
||||
cheatList.item(slot)->setText(1, "").setText(2, "(empty)");
|
||||
cheatList.item(slot)->setChecked(false).setText(1, "").setText(2, "(empty)");
|
||||
}
|
||||
}
|
||||
|
||||
cheatList.resizeColumns();
|
||||
}
|
||||
|
||||
auto CheatEditor::doReset() -> void {
|
||||
if(MessageDialog().setParent(*toolsManager).setText("Permanently erase all slots?").question() == 0) {
|
||||
auto CheatEditor::doReset(bool force) -> void {
|
||||
if(force || MessageDialog().setParent(*toolsManager).setText("Permanently erase all slots?").question() == 0) {
|
||||
for(auto& cheat : cheats) {
|
||||
cheat.enabled = false;
|
||||
cheat.code = "";
|
||||
cheat.description = "";
|
||||
}
|
||||
cheatList.setSelected(false);
|
||||
doChange();
|
||||
doChangeSelected();
|
||||
doRefresh();
|
||||
synchronizeCodes();
|
||||
}
|
||||
|
@ -72,9 +77,10 @@ auto CheatEditor::doReset() -> void {
|
|||
|
||||
auto CheatEditor::doErase() -> void {
|
||||
if(auto item = cheatList.selected()) {
|
||||
unsigned slot = item->offset();
|
||||
cheats[slot].code = "";
|
||||
cheats[slot].description = "";
|
||||
auto& cheat = cheats[item->offset()];
|
||||
cheat.enabled = false;
|
||||
cheat.code = "";
|
||||
cheat.description = "";
|
||||
codeValue.setText("");
|
||||
descriptionValue.setText("");
|
||||
doRefresh();
|
||||
|
@ -86,10 +92,9 @@ 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);
|
||||
for(auto& cheat : cheats) {
|
||||
if(!cheat.enabled || !cheat.code) continue;
|
||||
codes.append(cheat.code);
|
||||
}
|
||||
|
||||
emulator->cheatSet(codes);
|
||||
|
@ -97,15 +102,45 @@ auto CheatEditor::synchronizeCodes() -> void {
|
|||
|
||||
//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 {
|
||||
auto CheatEditor::addCode(const string& code, const string& description, bool enabled) -> bool {
|
||||
for(auto& cheat : cheats) {
|
||||
if(cheat.code || cheat.description) continue;
|
||||
|
||||
cheat.enabled = enabled;
|
||||
cheat.code = code;
|
||||
cheat.description = description;
|
||||
doRefresh();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto CheatEditor::loadCheats() -> void {
|
||||
doReset(true);
|
||||
auto contents = string::read({program->folderPaths[0], "cheats.bml"});
|
||||
auto document = Markup::Document(contents);
|
||||
for(auto& cheat : document["cartridge"]) {
|
||||
if(cheat.name != "cheat") continue;
|
||||
if(!addCode(cheat["code"].text(), cheat["description"].text(), cheat["enabled"].exists())) break;
|
||||
}
|
||||
doRefresh();
|
||||
synchronizeCodes();
|
||||
}
|
||||
|
||||
auto CheatEditor::saveCheats() -> void {
|
||||
if(!emulator) return;
|
||||
string document = {"cartridge sha256:", emulator->sha256(), "\n"};
|
||||
unsigned count = 0;
|
||||
for(auto& cheat : cheats) {
|
||||
if(!cheat.code && !cheat.description) continue;
|
||||
document.append(" cheat", cheat.enabled ? " enabled" : "", "\n");
|
||||
document.append(" description:", cheat.description, "\n");
|
||||
document.append(" code:", cheat.code, "\n");
|
||||
count++;
|
||||
}
|
||||
if(count) {
|
||||
file::write({program->folderPaths[0], "cheats.bml"}, document);
|
||||
} else {
|
||||
file::remove({program->folderPaths[0], "cheats.bml"});
|
||||
}
|
||||
doReset(true);
|
||||
}
|
||||
|
|
|
@ -18,15 +18,18 @@ struct CheatEditor : TabFrameItem {
|
|||
enum : unsigned { Slots = 128 };
|
||||
|
||||
CheatEditor(TabFrame*);
|
||||
auto doChange() -> void;
|
||||
auto doChangeSelected() -> void;
|
||||
auto doModify() -> void;
|
||||
auto doRefresh() -> void;
|
||||
auto doReset() -> void;
|
||||
auto doReset(bool force = false) -> void;
|
||||
auto doErase() -> void;
|
||||
auto synchronizeCodes() -> void;
|
||||
auto addCode(const string& code, const string& description) -> bool;
|
||||
auto addCode(const string& code, const string& description, bool enabled = false) -> bool;
|
||||
auto loadCheats() -> void;
|
||||
auto saveCheats() -> void;
|
||||
|
||||
struct Cheat {
|
||||
bool enabled = false;
|
||||
string code;
|
||||
string description;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue