Skip to content

Commit

Permalink
mia, desktop-ui: Add basic error handling to ROM loading (#1785)
Browse files Browse the repository at this point in the history
> [!NOTE]
> Seeking testers on this to validate that errors appear correctly, are
accurate, and we didn't break anything.

### Description

Adds the `LoadResult` type, used to identify and propagate upwards
errors encountered when loading ROMs and systems.

Modifies the `mia` and `desktop-ui` load routines to use this type
instead of generic booleans, so that if we have a load failure, we can
propagate a precise and useful error to the user rather than a vague and
possibly mismatched one.

Currently the scope of the error handling is just `mia` and
`desktop-ui`; errors encountered when loading in `ares` itself are
currently generic "`otherError`"s. `LoadResult` is placed in
`mia/mia.hpp` so it is accessible to both `mia` and `desktop-ui`. If
LoadResult is added to ares loading in the future, the LoadResult type
may need to be moved.
  • Loading branch information
jcm93 authored Jan 27, 2025
1 parent 4c367fd commit af5fa1a
Show file tree
Hide file tree
Showing 104 changed files with 889 additions and 550 deletions.
18 changes: 11 additions & 7 deletions desktop-ui/emulator/arcade.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
struct Arcade : Emulator {
Arcade();
auto load() -> bool override;
auto load() -> LoadResult override;
auto save() -> bool override;
auto pak(ares::Node::Object) -> shared_pointer<vfs::directory> override;
auto group() -> string override { return "Arcade"; }
Expand Down Expand Up @@ -33,29 +33,33 @@ Arcade::Arcade() {
}
}

auto Arcade::load() -> bool {
auto Arcade::load() -> LoadResult {
game = mia::Medium::create("Arcade");
if(!game->load(Emulator::load(game, configuration.game))) return false;
string location = Emulator::load(game, configuration.game);
if(!location) return noFileSelected;
LoadResult result = game->load(location);
if(result != successful) return result;

system = mia::System::create("Arcade");
if(!system->load()) return false;
result = system->load();
if(result != successful) return result;

//Determine from the game manifest which core to use for the given arcade rom
#ifdef CORE_SG
if(game->pak->attribute("board") == "sega/sg1000a") {
if(!ares::SG1000::load(root, {"[Sega] SG-1000A"})) return false;
if(!ares::SG1000::load(root, {"[Sega] SG-1000A"})) return otherError;
systemPakName = "SG-1000A";
gamePakName = "Arcade Cartridge";

if(auto port = root->find<ares::Node::Port>("Cartridge Slot")) {
port->allocate();
port->connect();
}
return true;
return successful;
}
#endif

return false;
return otherError;
}

auto Arcade::save() -> bool {
Expand Down
16 changes: 10 additions & 6 deletions desktop-ui/emulator/atari-2600.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
struct Atari2600 : Emulator {
Atari2600();
auto load() -> bool override;
auto load() -> LoadResult override;
auto save() -> bool override;
auto pak(ares::Node::Object) -> shared_pointer<vfs::directory> override;
};
Expand Down Expand Up @@ -38,15 +38,19 @@ Atari2600::Atari2600() {
}
}

auto Atari2600::load() -> bool {
auto Atari2600::load() -> LoadResult {
game = mia::Medium::create("Atari 2600");
if(!game->load(Emulator::load(game, configuration.game))) return false;
string location = Emulator::load(game, configuration.game);
if(!location) return couldNotParseManifest;
LoadResult result = game->load(location);
if(result != successful) return result;

system = mia::System::create("Atari 2600");
if(!system->load()) return false;
result = system->load();
if(result != successful) return result;

auto region = Emulator::region();
if(!ares::Atari2600::load(root, {"[Atari] Atari 2600 (", region, ")"})) return false;
if(!ares::Atari2600::load(root, {"[Atari] Atari 2600 (", region, ")"})) return otherError;

if(auto port = root->find<ares::Node::Port>("Cartridge Slot")) {
port->allocate();
Expand All @@ -63,7 +67,7 @@ auto Atari2600::load() -> bool {
port->connect();
}

return true;
return successful;
}

auto Atari2600::save() -> bool {
Expand Down
21 changes: 15 additions & 6 deletions desktop-ui/emulator/colecovision.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
struct ColecoVision : Emulator {
ColecoVision();
auto load() -> bool override;
auto load() -> LoadResult override;
auto save() -> bool override;
auto pak(ares::Node::Object) -> shared_pointer<vfs::directory> override;
};
Expand Down Expand Up @@ -39,15 +39,24 @@ ColecoVision::ColecoVision() {
}
}

auto ColecoVision::load() -> bool {
auto ColecoVision::load() -> LoadResult {
game = mia::Medium::create("ColecoVision");
if(!game->load(Emulator::load(game, configuration.game))) return false;
string location = Emulator::load(game, configuration.game);
if(!location) return noFileSelected;
LoadResult result = game->load(location);
if(result != successful) return result;

system = mia::System::create("ColecoVision");
if(!system->load(firmware[0].location)) return errorFirmware(firmware[0]), false;
if(system->load(firmware[0].location) != successful) {
result.firmwareSystemName = "ColecoVision";
result.firmwareType = firmware[0].type;
result.firmwareRegion = firmware[0].region;
result.result = noFirmware;
return result;
}

auto region = Emulator::region();
if(!ares::ColecoVision::load(root, {"[Coleco] ColecoVision (", region, ")"})) return false;
if(!ares::ColecoVision::load(root, {"[Coleco] ColecoVision (", region, ")"})) return otherError;

if(auto port = root->find<ares::Node::Port>("Cartridge Slot")) {
port->allocate();
Expand All @@ -64,7 +73,7 @@ auto ColecoVision::load() -> bool {
port->connect();
}

return true;
return successful;
}

auto ColecoVision::save() -> bool {
Expand Down
75 changes: 59 additions & 16 deletions desktop-ui/emulator/emulator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,67 @@ auto Emulator::region() -> string {
return {};
}

auto Emulator::handleLoadResult(LoadResult result) -> void {
string errorText;

switch (result.result) {
case successful:
return;
case noFileSelected:
return;
case invalidROM:
errorText = { "There was an error trying to parse the selected ROM. \n",
"Your ROM may be corrupt or contain a bad dump." };
break;
case couldNotParseManifest:
errorText = { "An error occurred while parsing the database file. You \n",
"may need to reinstall ares." };
break;
case databaseNotFound:
errorText = { "The database file for the system was not found. \n",
"Make sure that you have installed or packaged ares correctly. \n",
"Missing database file: " };
break;
case noFirmware:
errorText = { "Error: firmware is missing or invalid.\n",
result.firmwareSystemName, " - ", result.firmwareType, " (", result.firmwareRegion, ") is required to play this game.\n",
"Would you like to configure firmware settings now?" };
break;
case romNotFound:
errorText = "The selected ROM file was not found or could not be opened.";
break;
case romNotFoundInDatabase:
errorText = { "The required manifest for this ROM was not found in the database. \n",
"This title may not be currently supported by ares." };
break;
case otherError:
errorText = "An internal error occurred when initializing the emulator core.";
break;
}

if(result.info) {
errorText = { errorText, result.info };
}

switch (result.result) {
case noFirmware:
if(MessageDialog().setText({
errorText
}).question() == "Yes") {
settingsWindow.show("Firmware");
firmwareSettings.select(emulator->name, result.firmwareType, result.firmwareRegion);
}
default:
error(errorText);
}
}

auto Emulator::load(const string& location) -> bool {
if(inode::exists(location)) locationQueue.append(location);

if(!load()) {
error("Failed to load system! Database files may have been incorrectly \n"
"installed. Make sure you have packaged or installed ares correctly.");
LoadResult result = load();
handleLoadResult(result);
if(result != successful) {
return false;
}
setBoolean("Color Emulation", settings.video.colorEmulation);
Expand Down Expand Up @@ -178,18 +233,6 @@ auto Emulator::error(const string& text) -> void {
MessageDialog().setTitle("Error").setText(text).setAlignment(presentation).error();
}

auto Emulator::errorFirmware(const Firmware& firmware, string system) -> void {
if(!system) system = emulator->name;
if(MessageDialog().setText({
"Error: firmware is missing or invalid.\n",
system, " - ", firmware.type, " (", firmware.region, ") is required to play this game.\n"
"Would you like to configure firmware settings now?"
}).question() == "Yes") {
settingsWindow.show("Firmware");
firmwareSettings.select(system, firmware.type, firmware.region);
}
}

auto Emulator::input(ares::Node::Input::Input input) -> void {
//looking up inputs is very time-consuming; skip call if input was called too recently
//note: allow rumble to be polled at full speed to prevent missed motor events
Expand Down
4 changes: 2 additions & 2 deletions desktop-ui/emulator/emulator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ struct Emulator {
auto setOverscan(bool value) -> bool;
auto setColorBleed(bool value) -> bool;
auto error(const string& text) -> void;
auto errorFirmware(const Firmware&, string system = "") -> void;
auto load(mia::Pak& node, string name) -> bool;
auto save(mia::Pak& node, string name) -> bool;
virtual auto input(ares::Node::Input::Input) -> void;
auto inputKeyboard(string name) -> bool;
auto handleLoadResult(LoadResult result) -> void;
virtual auto load(Menu) -> void {}
virtual auto load() -> bool = 0;
virtual auto load() -> LoadResult = 0;
virtual auto save() -> bool { return true; }
virtual auto pak(ares::Node::Object) -> shared_pointer<vfs::directory> = 0;
virtual auto notify(const string& message) -> void {}
Expand Down
25 changes: 18 additions & 7 deletions desktop-ui/emulator/famicom-disk-system.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
struct FamicomDiskSystem : Emulator {
FamicomDiskSystem();
auto load(Menu) -> void override;
auto load() -> bool override;
auto load() -> LoadResult override;
auto save() -> bool override;
auto pak(ares::Node::Object) -> shared_pointer<vfs::directory> override;
auto notify(const string& message) -> void override;
Expand Down Expand Up @@ -65,17 +65,28 @@ auto FamicomDiskSystem::load(Menu menu) -> void {
return (void)disk1sideA.setChecked();
}

auto FamicomDiskSystem::load() -> bool {
auto FamicomDiskSystem::load() -> LoadResult {
game = mia::Medium::create("Famicom Disk System");
if(!game->load(Emulator::load(game, configuration.game))) return false;
string location = Emulator::load(game, configuration.game);
if(!location) return noFileSelected;
LoadResult result = game->load(location);
if(result != successful) return result;

bios = mia::Medium::create("Famicom");
if(!bios->load(firmware[0].location)) return errorFirmware(firmware[0]), false;
result = bios->load(firmware[0].location);
if(result != successful) {
result.firmwareSystemName = "Famicom";
result.firmwareType = firmware[0].type;
result.firmwareRegion = firmware[0].region;
result.result = noFirmware;
return result;
}

system = mia::System::create("Famicom");
if(!system->load()) return false;
result = system->load();
if(result != successful) return result;

if(!ares::Famicom::load(root, "[Nintendo] Famicom (NTSC-J)")) return false;
if(!ares::Famicom::load(root, "[Nintendo] Famicom (NTSC-J)")) return otherError;

if(auto port = root->find<ares::Node::Port>("Cartridge Slot")) {
port->allocate();
Expand All @@ -101,7 +112,7 @@ auto FamicomDiskSystem::load() -> bool {
port->connect();
}

return true;
return successful;
}

auto FamicomDiskSystem::save() -> bool {
Expand Down
16 changes: 10 additions & 6 deletions desktop-ui/emulator/famicom.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
struct Famicom : Emulator {
Famicom();
auto load() -> bool override;
auto load() -> LoadResult override;
auto save() -> bool override;
auto pak(ares::Node::Object) -> shared_pointer<vfs::directory> override;
};
Expand Down Expand Up @@ -35,15 +35,19 @@ Famicom::Famicom() {
}
}

auto Famicom::load() -> bool {
auto Famicom::load() -> LoadResult {
game = mia::Medium::create("Famicom");
if(!game->load(Emulator::load(game, configuration.game))) return false;
string location = Emulator::load(game, configuration.game);
if(!location) return noFileSelected;
LoadResult result = game->load(location);
if(result != successful) return result;

system = mia::System::create("Famicom");
if(!system->load()) return false;
result = system->load();
if(result != successful) return result;

auto region = Emulator::region();
if(!ares::Famicom::load(root, {"[Nintendo] Famicom (", region, ")"})) return false;
if(!ares::Famicom::load(root, {"[Nintendo] Famicom (", region, ")"})) return otherError;

if(auto port = root->find<ares::Node::Port>("Cartridge Slot")) {
port->allocate();
Expand All @@ -67,7 +71,7 @@ auto Famicom::load() -> bool {
}
}

return true;
return successful;
}

auto Famicom::save() -> bool {
Expand Down
21 changes: 15 additions & 6 deletions desktop-ui/emulator/game-boy-advance.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
struct GameBoyAdvance : Emulator {
GameBoyAdvance();
auto load(Menu) -> void override;
auto load() -> bool override;
auto load() -> LoadResult override;
auto save() -> bool override;
auto pak(ares::Node::Object) -> shared_pointer<vfs::directory> override;
};
Expand Down Expand Up @@ -50,23 +50,32 @@ auto GameBoyAdvance::load(Menu menu) -> void {
}
}

auto GameBoyAdvance::load() -> bool {
auto GameBoyAdvance::load() -> LoadResult {
game = mia::Medium::create("Game Boy Advance");
if(!game->load(Emulator::load(game, configuration.game))) return false;
string location = Emulator::load(game, configuration.game);
if(!location) return noFileSelected;
LoadResult result = game->load(location);
if(result != successful) return result;

system = mia::System::create("Game Boy Advance");
if(!system->load(firmware[0].location)) return errorFirmware(firmware[0]), false;
if(system->load(firmware[0].location) != successful) {
result.firmwareSystemName = "Game Boy Advance";
result.firmwareType = firmware[0].type;
result.firmwareRegion = firmware[0].region;
result.result = noFirmware;
return result;
}

ares::GameBoyAdvance::option("Pixel Accuracy", settings.video.pixelAccuracy);

if(!ares::GameBoyAdvance::load(root, "[Nintendo] Game Boy Advance")) return false;
if(!ares::GameBoyAdvance::load(root, "[Nintendo] Game Boy Advance")) return otherError;

if(auto port = root->find<ares::Node::Port>("Cartridge Slot")) {
port->allocate();
port->connect();
}

return true;
return successful;
}

auto GameBoyAdvance::save() -> bool {
Expand Down
Loading

0 comments on commit af5fa1a

Please sign in to comment.