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:
Tim Allen 2015-04-21 21:58:59 +10:00
parent 2eb50fd70b
commit c335ee9d80
10 changed files with 105 additions and 38 deletions

View File

@ -3,7 +3,7 @@
namespace Emulator { namespace Emulator {
static const char Name[] = "higan"; 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 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/";

View File

@ -17,6 +17,10 @@ ConfigurationManager::ConfigurationManager() {
video.append(video.aspectCorrection, "AspectCorrection"); video.append(video.aspectCorrection, "AspectCorrection");
video.append(video.filter, "Filter"); video.append(video.filter, "Filter");
video.append(video.colorEmulation, "ColorEmulation"); 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"); append(video, "Video");
audio.append(audio.driver, "Driver"); audio.append(audio.driver, "Driver");

View File

@ -17,6 +17,12 @@ struct ConfigurationManager : Configuration::Document {
bool aspectCorrection = true; bool aspectCorrection = true;
string filter = "Blur"; string filter = "Blur";
bool colorEmulation = true; bool colorEmulation = true;
struct Overscan : Configuration::Node {
bool mask = false;
unsigned horizontal = 8;
unsigned vertical = 8;
} overscan;
} video; } video;
struct Audio : Configuration::Node { struct Audio : Configuration::Node {

View File

@ -39,6 +39,10 @@ Presentation::Presentation() {
config().video.scale = "Large"; config().video.scale = "Large";
resizeViewport(); resizeViewport();
}); });
aspectCorrection.setText("Aspect Correction").setChecked(config().video.aspectCorrection).onToggle([&] {
config().video.aspectCorrection = aspectCorrection.checked();
resizeViewport();
});
videoFilterMenu.setText("Video Filter"); videoFilterMenu.setText("Video Filter");
MenuRadioItem::group({videoFilterNone, videoFilterBlur}); MenuRadioItem::group({videoFilterNone, videoFilterBlur});
if(config().video.filter == "None") videoFilterNone.setChecked(); if(config().video.filter == "None") videoFilterNone.setChecked();
@ -49,11 +53,8 @@ Presentation::Presentation() {
config().video.colorEmulation = colorEmulation.checked(); config().video.colorEmulation = colorEmulation.checked();
program->updateVideoPalette(); program->updateVideoPalette();
}); });
aspectCorrection.setText("Aspect Correction").setChecked(config().video.aspectCorrection).onToggle([&] { maskOverscan.setText("Mask Overscan").setChecked(config().video.overscan.mask).onToggle([&] {
config().video.aspectCorrection = aspectCorrection.checked(); config().video.overscan.mask = maskOverscan.checked();
resizeViewport();
});
maskOverscan.setText("Mask Overscan").onToggle([&] {
}); });
synchronizeVideo.setText("Synchronize Video").setChecked(config().video.synchronize).onToggle([&] { synchronizeVideo.setText("Synchronize Video").setChecked(config().video.synchronize).onToggle([&] {
config().video.synchronize = synchronizeVideo.checked(); config().video.synchronize = synchronizeVideo.checked();

View File

@ -19,12 +19,12 @@ struct Presentation : Window {
MenuRadioItem videoScaleLarge{&videoScaleMenu}; MenuRadioItem videoScaleLarge{&videoScaleMenu};
MenuSeparator videoScaleSeparator{&videoScaleMenu}; MenuSeparator videoScaleSeparator{&videoScaleMenu};
MenuCheckItem aspectCorrection{&videoScaleMenu}; MenuCheckItem aspectCorrection{&videoScaleMenu};
MenuCheckItem maskOverscan{&videoScaleMenu};
Menu videoFilterMenu{&settingsMenu}; Menu videoFilterMenu{&settingsMenu};
MenuRadioItem videoFilterNone{&videoFilterMenu}; MenuRadioItem videoFilterNone{&videoFilterMenu};
MenuRadioItem videoFilterBlur{&videoFilterMenu}; MenuRadioItem videoFilterBlur{&videoFilterMenu};
MenuSeparator videoFilterSeparator{&videoFilterMenu}; MenuSeparator videoFilterSeparator{&videoFilterMenu};
MenuCheckItem colorEmulation{&videoFilterMenu}; MenuCheckItem colorEmulation{&videoFilterMenu};
MenuCheckItem maskOverscan{&videoFilterMenu};
MenuSeparator settingsMenuSeparator1{&settingsMenu}; MenuSeparator settingsMenuSeparator1{&settingsMenu};
MenuCheckItem synchronizeVideo{&settingsMenu}; MenuCheckItem synchronizeVideo{&settingsMenu};
MenuCheckItem synchronizeAudio{&settingsMenu}; MenuCheckItem synchronizeAudio{&settingsMenu};

View File

@ -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.unlock();
video.refresh(); video.refresh();
} }

View File

@ -29,13 +29,14 @@ auto Program::loadMedia(Emulator::Interface& _emulator, Emulator::Interface::Med
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->cheatEditor.loadCheats();
toolsManager->stateManager.doRefresh(); toolsManager->stateManager.doRefresh();
} }
auto Program::unloadMedia() -> void { auto Program::unloadMedia() -> void {
if(!emulator) return; if(!emulator) return;
toolsManager->cheatEditor.saveCheats();
emulator->unload(); emulator->unload();
emulator = nullptr; emulator = nullptr;
@ -45,4 +46,5 @@ auto Program::unloadMedia() -> void {
presentation->setTitle({"tomoko v", Emulator::Version}); presentation->setTitle({"tomoko v", Emulator::Version});
presentation->systemMenu.setVisible(false); presentation->systemMenu.setVisible(false);
presentation->toolsMenu.setVisible(false); presentation->toolsMenu.setVisible(false);
toolsManager->setVisible(false);
} }

View File

@ -49,4 +49,5 @@ auto CheatDatabase::addCodes() -> void {
} }
} }
setVisible(false); setVisible(false);
toolsManager->cheatEditor.doRefresh();
} }

View File

@ -4,13 +4,16 @@ CheatEditor::CheatEditor(TabFrame* parent) : TabFrameItem(parent) {
layout.setMargin(5); layout.setMargin(5);
cheatList.append(ListViewColumn().setText("Slot").setForegroundColor({0, 128, 0}).setHorizontalAlignment(1.0)); 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)); cheatList.append(ListViewColumn().setText("Description").setWidth(~0));
for(auto slot : range(Slots)) cheatList.append(ListViewItem().setText(0, 1 + slot)); for(auto slot : range(Slots)) cheatList.append(ListViewItem().setText(0, 1 + slot));
cheatList.setCheckable(); cheatList.setCheckable();
cheatList.setHeaderVisible(); cheatList.setHeaderVisible();
cheatList.onChange([&] { doChange(); }); cheatList.onChange([&] { doChangeSelected(); });
cheatList.onToggle([&](sListViewItem) { synchronizeCodes(); }); cheatList.onToggle([&](sListViewItem item) {
cheats[item->offset()].enabled = item->checked();
synchronizeCodes();
});
codeLabel.setText("Code(s):"); codeLabel.setText("Code(s):");
codeValue.onChange([&] { doModify(); }); codeValue.onChange([&] { doModify(); });
descriptionLabel.setText("Description:"); descriptionLabel.setText("Description:");
@ -20,11 +23,11 @@ CheatEditor::CheatEditor(TabFrame* parent) : TabFrameItem(parent) {
eraseButton.setText("Erase").onActivate([&] { doErase(); }); eraseButton.setText("Erase").onActivate([&] { doErase(); });
} }
auto CheatEditor::doChange() -> void { auto CheatEditor::doChangeSelected() -> void {
if(auto item = cheatList.selected()) { if(auto item = cheatList.selected()) {
unsigned slot = item->offset(); auto& cheat = cheats[item->offset()];
codeValue.setEnabled(true).setText(cheats[slot].code); codeValue.setEnabled(true).setText(cheat.code);
descriptionValue.setEnabled(true).setText(cheats[slot].description); descriptionValue.setEnabled(true).setText(cheat.description);
eraseButton.setEnabled(true); eraseButton.setEnabled(true);
} else { } else {
codeValue.setEnabled(false).setText(""); codeValue.setEnabled(false).setText("");
@ -35,9 +38,9 @@ auto CheatEditor::doChange() -> void {
auto CheatEditor::doModify() -> void { auto CheatEditor::doModify() -> void {
if(auto item = cheatList.selected()) { if(auto item = cheatList.selected()) {
unsigned slot = item->offset(); auto& cheat = cheats[item->offset()];
cheats[slot].code = codeValue.text(); cheat.code = codeValue.text();
cheats[slot].description = descriptionValue.text(); cheat.description = descriptionValue.text();
doRefresh(); doRefresh();
synchronizeCodes(); synchronizeCodes();
} }
@ -45,26 +48,28 @@ auto CheatEditor::doModify() -> void {
auto CheatEditor::doRefresh() -> void { auto CheatEditor::doRefresh() -> void {
for(auto slot : range(Slots)) { for(auto slot : range(Slots)) {
if(cheats[slot].code || cheats[slot].description) { auto& cheat = cheats[slot];
lstring codes = cheats[slot].code.split("+"); if(cheat.code || cheat.description) {
lstring codes = cheat.code.split("+");
if(codes.size() > 1) codes[0].append("+..."); 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 { } else {
cheatList.item(slot)->setText(1, "").setText(2, "(empty)"); cheatList.item(slot)->setChecked(false).setText(1, "").setText(2, "(empty)");
} }
} }
cheatList.resizeColumns(); cheatList.resizeColumns();
} }
auto CheatEditor::doReset() -> void { auto CheatEditor::doReset(bool force) -> void {
if(MessageDialog().setParent(*toolsManager).setText("Permanently erase all slots?").question() == 0) { if(force || MessageDialog().setParent(*toolsManager).setText("Permanently erase all slots?").question() == 0) {
for(auto& cheat : cheats) { for(auto& cheat : cheats) {
cheat.enabled = false;
cheat.code = ""; cheat.code = "";
cheat.description = ""; cheat.description = "";
} }
cheatList.setSelected(false); cheatList.setSelected(false);
doChange(); doChangeSelected();
doRefresh(); doRefresh();
synchronizeCodes(); synchronizeCodes();
} }
@ -72,9 +77,10 @@ auto CheatEditor::doReset() -> void {
auto CheatEditor::doErase() -> void { auto CheatEditor::doErase() -> void {
if(auto item = cheatList.selected()) { if(auto item = cheatList.selected()) {
unsigned slot = item->offset(); auto& cheat = cheats[item->offset()];
cheats[slot].code = ""; cheat.enabled = false;
cheats[slot].description = ""; cheat.code = "";
cheat.description = "";
codeValue.setText(""); codeValue.setText("");
descriptionValue.setText(""); descriptionValue.setText("");
doRefresh(); doRefresh();
@ -86,10 +92,9 @@ auto CheatEditor::synchronizeCodes() -> void {
if(!emulator) return; if(!emulator) return;
lstring codes; lstring codes;
for(auto slot : range(Slots)) { for(auto& cheat : cheats) {
if(!cheatList.item(slot)->checked()) continue; if(!cheat.enabled || !cheat.code) continue;
if(!cheats[slot].code) continue; codes.append(cheat.code);
codes.append(cheats[slot].code);
} }
emulator->cheatSet(codes); emulator->cheatSet(codes);
@ -97,15 +102,45 @@ auto CheatEditor::synchronizeCodes() -> void {
//returns true if code was added //returns true if code was added
//returns false if there are no more free slots for additional codes //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) { for(auto& cheat : cheats) {
if(cheat.code || cheat.description) continue; if(cheat.code || cheat.description) continue;
cheat.enabled = enabled;
cheat.code = code; cheat.code = code;
cheat.description = description; cheat.description = description;
doRefresh();
return true; return true;
} }
return false; 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);
}

View File

@ -18,15 +18,18 @@ struct CheatEditor : TabFrameItem {
enum : unsigned { Slots = 128 }; enum : unsigned { Slots = 128 };
CheatEditor(TabFrame*); CheatEditor(TabFrame*);
auto doChange() -> void; auto doChangeSelected() -> void;
auto doModify() -> void; auto doModify() -> void;
auto doRefresh() -> void; auto doRefresh() -> void;
auto doReset() -> void; auto doReset(bool force = false) -> void;
auto doErase() -> void; auto doErase() -> void;
auto synchronizeCodes() -> 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 { struct Cheat {
bool enabled = false;
string code; string code;
string description; string description;
}; };