bsnes/higan/target-bsnes/program/interface.cpp

276 lines
10 KiB
C++
Raw Normal View History

#include <icarus/heuristics/heuristics.hpp>
#include <icarus/heuristics/heuristics.cpp>
#include <icarus/heuristics/super-famicom.cpp>
#include <icarus/heuristics/game-boy.cpp>
#include <icarus/heuristics/bs-memory.cpp>
#include <icarus/heuristics/sufami-turbo.cpp>
auto Program::open(uint id, string name, vfs::file::mode mode, bool required) -> vfs::shared::file {
//System
if(id == 0 && name == "manifest.bml" && mode == vfs::file::mode::read) {
return vfs::memory::file::open(Resource::System::Manifest.data(), Resource::System::Manifest.size());
}
if(id == 0 && name == "boards.bml" && mode == vfs::file::mode::read) {
return vfs::memory::file::open(Resource::System::Boards.data(), Resource::System::Boards.size());
}
if(id == 0 && name == "ipl.rom" && mode == vfs::file::mode::read) {
return vfs::memory::file::open(Resource::System::IPLROM.data(), Resource::System::IPLROM.size());
}
//Super Famicom
if(id == 1 && name == "manifest.bml" && mode == vfs::file::mode::read) {
return vfs::memory::file::open(superNintendo.manifest.data<uint8_t>(), superNintendo.manifest.size());
}
if(id == 1 && name == "program.rom" && mode == vfs::file::mode::read) {
return vfs::memory::file::open(superNintendo.program.data(), superNintendo.program.size());
}
if(id == 1 && name == "data.rom" && mode == vfs::file::mode::read) {
return vfs::memory::file::open(superNintendo.data.data(), superNintendo.data.size());
}
if(id == 1 && name == "expansion.rom" && mode == vfs::file::mode::read) {
return vfs::memory::file::open(superNintendo.expansion.data(), superNintendo.expansion.size());
}
if(id == 1 && name == "arm6.program.rom" && mode == vfs::file::mode::read) {
if(superNintendo.firmware.size() == 0x28000) {
return vfs::memory::file::open(&superNintendo.firmware.data()[0x00000], 0x20000);
}
if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Program,architecture=ARM6)"]) {
string location = locate({"firmware/", memory["identifier"].text().downcase(), ".program.rom"});
return vfs::fs::file::open(location, mode);
}
}
if(id == 1 && name == "arm6.data.rom" && mode == vfs::file::mode::read) {
if(superNintendo.firmware.size() == 0x28000) {
return vfs::memory::file::open(&superNintendo.firmware.data()[0x20000], 0x08000);
}
if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Data,architecture=ARM6)"]) {
string location = locate({"firmware/", memory["identifier"].text().downcase(), ".data.rom"});
return vfs::fs::file::open(location, mode);
}
}
if(id == 1 && name == "hg51bs169.data.rom" && mode == vfs::file::mode::read) {
if(superNintendo.firmware.size() == 0xc00) {
return vfs::memory::file::open(superNintendo.firmware.data(), superNintendo.firmware.size());
}
if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Data,architecture=HG51BS169)"]) {
string location = locate({"firmware/", memory["identifier"].text().downcase(), ".data.rom"});
return vfs::fs::file::open(location, mode);
}
}
if(id == 1 && name == "lr35902.boot.rom" && mode == vfs::file::mode::read) {
if(superNintendo.firmware.size() == 0x100) {
return vfs::memory::file::open(superNintendo.firmware.data(), superNintendo.firmware.size());
}
if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Boot,architecture=LR35902)"]) {
string location = locate({"firmware/", memory["identifier"].text().downcase(), ".boot.rom"});
return vfs::fs::file::open(location, mode);
}
}
if(id == 1 && name == "upd7725.program.rom" && mode == vfs::file::mode::read) {
if(superNintendo.firmware.size() == 0x2000) {
return vfs::memory::file::open(&superNintendo.firmware.data()[0x0000], 0x1800);
}
if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Program,architecture=uPD7725)"]) {
string location = locate({"firmware/", memory["identifier"].text().downcase(), ".program.rom"});
return vfs::fs::file::open(location, mode);
}
}
if(id == 1 && name == "upd7725.data.rom" && mode == vfs::file::mode::read) {
if(superNintendo.firmware.size() == 0x2000) {
return vfs::memory::file::open(&superNintendo.firmware.data()[0x1800], 0x0800);
}
if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Data,architecture=uPD7725)"]) {
string location = locate({"firmware/", memory["identifier"].text().downcase(), ".data.rom"});
return vfs::fs::file::open(location, mode);
}
}
if(id == 1 && name == "upd96050.program.rom" && mode == vfs::file::mode::read) {
if(superNintendo.firmware.size() == 0xd000) {
return vfs::memory::file::open(&superNintendo.firmware.data()[0x0000], 0xc000);
}
if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Program,architecture=uPD96050)"]) {
string location = locate({"firmware/", memory["identifier"].text().downcase(), ".program.rom"});
return vfs::fs::file::open(location, mode);
}
}
if(id == 1 && name == "upd96050.data.rom" && mode == vfs::file::mode::read) {
if(superNintendo.firmware.size() == 0xd000) {
return vfs::memory::file::open(&superNintendo.firmware.data()[0xc000], 0x1000);
}
if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Data,architecture=uPD96050)"]) {
string location = locate({"firmware/", memory["identifier"].text().downcase(), ".data.rom"});
return vfs::fs::file::open(location, mode);
}
}
if(id == 1 && name == "save.ram") {
return vfs::fs::file::open(path("Saves", superNintendo.location, ".srm"), mode);
}
if(id == 1 && name == "download.ram") {
return vfs::fs::file::open(path("Saves", superNintendo.location, ".psr"), mode);
}
if(id == 1 && name == "time.rtc") {
return vfs::fs::file::open(path("Saves", superNintendo.location, ".rtc"), mode);
}
if(id == 1 && name == "arm6.data.ram") {
return vfs::fs::file::open(path("Saves", superNintendo.location, ".srm"), mode);
}
if(id == 1 && name == "hg51bs169.data.ram") {
return vfs::fs::file::open(path("Saves", superNintendo.location, ".srm"), mode);
}
if(id == 1 && name == "upd7725.data.ram") {
return vfs::fs::file::open(path("Saves", superNintendo.location, ".srm"), mode);
}
if(id == 1 && name == "upd96050.data.ram") {
return vfs::fs::file::open(path("Saves", superNintendo.location, ".srm"), mode);
}
Update to v106r35 release. byuu says: Changelog: - sfc/ppu-fast: fixed overscan crash - sfc/ppu-fast: fixed direct color mode - sfc: reconnected MSU1 support - higan: game.sfc/msu1/data.rom, game.sfc/msu1/track-#.pcm - bsnes: game.msu, game-#.pcm - bsnes: added cheat code editor - bsnes: added cheat code database support - sfc/ppu-fast: clear overscan lines when overscan disabled - sfc: output 223/239 lines instead of 224/240 lines - bsnes: fix aspect correction calculation - bsnes: crop line 224 when overscan masking is enabled - bsnes: exposed Expansion Port menu; but hid “21fx” from the list of devices - bsnes: tools menu is hidden until a game is loaded - ruby/input/keyboard/quartz: fixed compilation error So only bsnes the automated overscan cropping option. In higan, you can crop however many lines you like from the top or bottom of the image. But for bsnes, it automatically eats sixteen lines. My view right now is that if bsnes is meant to be the casual gaming emulator, that it should eat line 224 in this mode. Most games show content here, but because of the way the SNES PPU works, the very last line ends up on its very own tile row (line 0 isn't rendered), if the scroll registers don't account for it. There's a small number of games that will draw junk data to the very last scanline of the frame as a result of this. So I chose, at least for now, to hide it. Users can obviously disable overscan cropping to see this scanline. I'm open to being convinced not to do this, if someone has a compelling reason. We're pretty much screwed one way or the other with no overscan masking. If we output 239 lines, then most games will render 7 blank lines + 224 drawn lines + 8 blank lines, and the black top and bottom aren't centered. But if we output 240 lines to get 8 + 224 + 8, then games that do use overscan will have a blank line at the very bottom of the window. I'm also trying out a modified cheat code file format. It's been forever since I bothered to look at it, and the “cartridge” parent node doesn't match what I'm doing with trying to rename “cartridge” to “game” in manifests. And indeed, the idea of requiring a root node is rather superfluous for a cheat code file. Current format looks like this: cheat description: foo code: 7e2000=20+7e2001=30?40 enabled cheat description: bar code: 7e4000=80 Open to discussing this, and I'd like to sync up with Snes9X before they push out a new release, and I'll agree to finalize and never change this format again. I chose to use .cht for the extension when using game files (eg gamename.cht)
2018-06-03 13:14:42 +00:00
if(id == 1 && name == "msu1/data.rom") {
return vfs::fs::file::open({Location::notsuffix(superNintendo.location), ".msu"}, mode);
}
if(id == 1 && name.match("msu1/track-*.pcm")) {
name.trimLeft("msu1/track-", 1L);
return vfs::fs::file::open({Location::notsuffix(superNintendo.location), name}, mode);
}
//Game Boy
if(id == 2 && name == "manifest.bml" && mode == vfs::file::mode::read) {
return vfs::memory::file::open(gameBoy.manifest.data<uint8_t>(), gameBoy.manifest.size());
}
if(id == 2 && name == "program.rom" && mode == vfs::file::mode::read) {
return vfs::memory::file::open(gameBoy.program.data(), gameBoy.program.size());
}
if(id == 2 && name == "save.ram") {
return vfs::fs::file::open(path("Saves", gameBoy.location, ".sav"), mode);
}
if(id == 2 && name == "time.rtc") {
return vfs::fs::file::open(path("Saves", gameBoy.location, ".sav"), mode);
}
return {};
}
auto Program::load(uint id, string name, string type, string_vector options) -> Emulator::Platform::Load {
if(id == 1 && name == "Super Famicom" && type == "sfc") {
if(gameQueue) {
superNintendo.location = gameQueue.takeLeft();
} else {
BrowserDialog dialog;
dialog.setTitle("Load Super Nintendo");
dialog.setPath(path("Games", settings["Path/Recent/SuperNintendo"].text()));
dialog.setFilters({string{"Super Nintendo Games|*.sfc:*.smc:*.zip"}});
superNintendo.location = dialog.openFile();
}
if(file::exists(superNintendo.location)) {
settings["Path/Recent/SuperNintendo"].setValue(Location::path(superNintendo.location));
loadSuperNintendo(superNintendo.location);
return {id, ""};
}
}
if(id == 2 && name == "Game Boy" && type == "gb") {
if(gameQueue) {
gameBoy.location = gameQueue.takeLeft();
} else {
BrowserDialog dialog;
dialog.setTitle("Load Game Boy");
dialog.setPath(path("Games", settings["Path/Recent/GameBoy"].text()));
dialog.setFilters({string{"Game Boy Games|*.gb:*.gbc:*.zip"}});
gameBoy.location = dialog.openFile();
}
if(file::exists(gameBoy.location)) {
settings["Path/Recent/GameBoy"].setValue(Location::path(gameBoy.location));
loadGameBoy(gameBoy.location);
return {id, ""};
}
}
return {};
}
auto Program::videoRefresh(const uint32* data, uint pitch, uint width, uint height) -> void {
uint32_t* output;
uint length;
pitch >>= 2;
if(presentation->overscanCropping.checked()) {
Update to v106r35 release. byuu says: Changelog: - sfc/ppu-fast: fixed overscan crash - sfc/ppu-fast: fixed direct color mode - sfc: reconnected MSU1 support - higan: game.sfc/msu1/data.rom, game.sfc/msu1/track-#.pcm - bsnes: game.msu, game-#.pcm - bsnes: added cheat code editor - bsnes: added cheat code database support - sfc/ppu-fast: clear overscan lines when overscan disabled - sfc: output 223/239 lines instead of 224/240 lines - bsnes: fix aspect correction calculation - bsnes: crop line 224 when overscan masking is enabled - bsnes: exposed Expansion Port menu; but hid “21fx” from the list of devices - bsnes: tools menu is hidden until a game is loaded - ruby/input/keyboard/quartz: fixed compilation error So only bsnes the automated overscan cropping option. In higan, you can crop however many lines you like from the top or bottom of the image. But for bsnes, it automatically eats sixteen lines. My view right now is that if bsnes is meant to be the casual gaming emulator, that it should eat line 224 in this mode. Most games show content here, but because of the way the SNES PPU works, the very last line ends up on its very own tile row (line 0 isn't rendered), if the scroll registers don't account for it. There's a small number of games that will draw junk data to the very last scanline of the frame as a result of this. So I chose, at least for now, to hide it. Users can obviously disable overscan cropping to see this scanline. I'm open to being convinced not to do this, if someone has a compelling reason. We're pretty much screwed one way or the other with no overscan masking. If we output 239 lines, then most games will render 7 blank lines + 224 drawn lines + 8 blank lines, and the black top and bottom aren't centered. But if we output 240 lines to get 8 + 224 + 8, then games that do use overscan will have a blank line at the very bottom of the window. I'm also trying out a modified cheat code file format. It's been forever since I bothered to look at it, and the “cartridge” parent node doesn't match what I'm doing with trying to rename “cartridge” to “game” in manifests. And indeed, the idea of requiring a root node is rather superfluous for a cheat code file. Current format looks like this: cheat description: foo code: 7e2000=20+7e2001=30?40 enabled cheat description: bar code: 7e4000=80 Open to discussing this, and I'd like to sync up with Snes9X before they push out a new release, and I'll agree to finalize and never change this format again. I chose to use .cht for the extension when using game files (eg gamename.cht)
2018-06-03 13:14:42 +00:00
if(height == 239) data += 8 * pitch, height -= 16;
if(height == 478) data += 16 * pitch, height -= 32;
}
if(video->lock(output, length, width, height)) {
length >>= 2;
for(auto y : range(height)) {
memory::copy<uint32>(output + y * length, data + y * pitch, width);
}
video->unlock();
video->output();
}
static uint frameCounter = 0;
static uint64 previous, current;
frameCounter++;
current = chrono::timestamp();
if(current != previous) {
previous = current;
statusText = {"FPS: ", frameCounter};
frameCounter = 0;
}
}
auto Program::audioSample(const double* samples, uint channels) -> void {
if(presentation->muteAudio.checked()) {
static const double mutedSamples[] = {0.0, 0.0};
audio->output(mutedSamples);
} else {
audio->output(samples);
}
}
auto Program::inputPoll(uint port, uint device, uint input) -> int16 {
if(focused() || settingsWindow->input.allowInput().checked()) {
inputManager->poll();
if(auto mapping = inputManager->mapping(port, device, input)) {
return mapping->poll();
}
}
return 0;
}
auto Program::inputRumble(uint port, uint device, uint input, bool enable) -> void {
if(focused() || settingsWindow->input.allowInput().checked() || !enable) {
if(auto mapping = inputManager->mapping(port, device, input)) {
return mapping->rumble(enable);
}
}
}