Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add core support for using the solar sensor without requiring a Boktai ROM #2221

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 63 additions & 1 deletion src/GBACart.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,57 @@ CartGameSolarSensor::CartGameSolarSensor(std::unique_ptr<u8[]>&& rom, u32 len, s
{
}

std::unique_ptr<CartGameSolarSensor> CreateFakeSolarSensorROM(const char* gamecode, const NDSCart::CartCommon& cart, void* userdata) noexcept
{
return CreateFakeSolarSensorROM(gamecode, cart.GetHeader().NintendoLogo, userdata);
}

std::unique_ptr<CartGameSolarSensor> CreateFakeSolarSensorROM(const char* gamecode, const GBACart::CartGame& cart, void* userdata) noexcept
{
return CreateFakeSolarSensorROM(gamecode, cart.GetHeader().NintendoLogo, userdata);
}

std::unique_ptr<CartGameSolarSensor> CreateFakeSolarSensorROM(const char* gamecode, const u8* logo, void* userdata) noexcept
{
if (!gamecode)
return nullptr;

if (strnlen(gamecode, sizeof(GBAHeader::GameCode)) > sizeof(GBAHeader::GameCode))
return nullptr;

bool solarsensor = false;
for (const char* i : SOLAR_SENSOR_GAMECODES)
{
if (strcmp(gamecode, i) == 0) {
solarsensor = true;
break;
}
}

if (!solarsensor)
return nullptr;

// just 256 bytes; we don't need a whole ROM!
constexpr size_t FAKE_BOKTAI_ROM_LENGTH = 0x100;
std::unique_ptr<u8[]> rom = std::make_unique<u8[]>(FAKE_BOKTAI_ROM_LENGTH);

// create a fake ROM
GBAHeader& header = *reinterpret_cast<GBAHeader*>(rom.get());
memcpy(header.Title, BOKTAI_STUB_TITLE, strnlen(BOKTAI_STUB_TITLE, sizeof(header.Title)));
memcpy(header.GameCode, gamecode, strnlen(gamecode, sizeof(header.GameCode)));
header.FixedValue = 0x96;
if (logo)
{
memcpy(header.NintendoLogo, logo, sizeof(header.NintendoLogo));
}
else
{
memset(header.NintendoLogo, 0xFF, sizeof(header.NintendoLogo));
}

return std::make_unique<CartGameSolarSensor>(std::move(rom), FAKE_BOKTAI_ROM_LENGTH, nullptr, 0, userdata);
}

const int CartGameSolarSensor::kLuxLevels[11] = {0, 5, 11, 18, 27, 42, 62, 84, 109, 139, 183};

void CartGameSolarSensor::Reset()
Expand Down Expand Up @@ -843,7 +894,18 @@ std::unique_ptr<CartCommon> LoadAddon(int type, void* userdata)
case GBAAddon_RumblePak:
cart = std::make_unique<CartRumblePak>(userdata);
break;

case GBAAddon_SolarSensorBoktai1:
// US Boktai 1
cart = CreateFakeSolarSensorROM("U3IE", nullptr, userdata);
break;
case GBAAddon_SolarSensorBoktai2:
// US Boktai 2
cart = CreateFakeSolarSensorROM("U32E", nullptr, userdata);
break;
case GBAAddon_SolarSensorBoktai3:
// JP Boktai 3
cart = CreateFakeSolarSensorROM("U33J", nullptr, userdata);
break;
default:
Log(LogLevel::Warn, "GBACart: !! invalid addon type %d\n", type);
return nullptr;
Expand Down
38 changes: 38 additions & 0 deletions src/GBACart.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,25 @@ enum CartType
RumblePak = 0x202,
};

// See https://problemkaputt.de/gbatek.htm#gbacartridgeheader for details
struct GBAHeader
{
u32 EntryPoint;
u8 NintendoLogo[156]; // must be valid
char Title[12];
char GameCode[4];
char MakerCode[2];
u8 FixedValue; // must be 0x96
u8 MainUnitCode;
u8 DeviceType;
u8 Reserved0[7];
u8 SoftwareVersion;
u8 ComplementCheck;
u8 Reserved1[2];
};

static_assert(sizeof(GBAHeader) == 192, "GBAHeader should be 192 bytes");

// CartCommon -- base code shared by all cart types
class CartCommon
{
Expand Down Expand Up @@ -91,6 +110,8 @@ class CartGame : public CartCommon

[[nodiscard]] const u8* GetROM() const override { return ROM.get(); }
[[nodiscard]] u32 GetROMLength() const override { return ROMLength; }
[[nodiscard]] const GBAHeader& GetHeader() const noexcept { return *reinterpret_cast<const GBAHeader*>(ROM.get()); }
[[nodiscard]] GBAHeader& GetHeader() noexcept { return *reinterpret_cast<GBAHeader*>(ROM.get()); }

u8* GetSaveMemory() const override;
u32 GetSaveMemoryLength() const override;
Expand Down Expand Up @@ -309,6 +330,23 @@ std::unique_ptr<CartCommon> ParseROM(std::unique_ptr<u8[]>&& romdata, u32 romlen

std::unique_ptr<CartCommon> LoadAddon(int type, void* userdata);

/// Creates a solar sensor-enabled GBA cart without needing a real Boktai ROM.
/// This enables the solar sensor to be used in supported games.
/// Will not contain any SRAM.
/// @param gamecode
/// @param logo The Nintendo logo data embedded in the headers for GBA ROMs and NDS ROMs.
/// Required for the cart to be recognized as a valid GBA cart.
/// Overloads that accept cart objects directly exist as well.
/// If not provided, then it will have to be patched with equivalent data
/// from a real ROM (NDS or GBA) before booting the emulator.
/// @param userdata Optional user data to associate with the cart.
/// @return A CartGameSolarSensor if the ROM was created successfully,
/// or nullptr if any argument is wrong (e.g. an incorrect game code).
std::unique_ptr<CartGameSolarSensor> CreateFakeSolarSensorROM(const char* gamecode, const u8* logo, void* userdata = nullptr) noexcept;
std::unique_ptr<CartGameSolarSensor> CreateFakeSolarSensorROM(const char* gamecode, const NDSCart::CartCommon& cart, void* userdata = nullptr) noexcept;
std::unique_ptr<CartGameSolarSensor> CreateFakeSolarSensorROM(const char* gamecode, const GBACart::CartGame& cart, void* userdata = nullptr) noexcept;

constexpr const char* BOKTAI_STUB_TITLE = "BOKTAI STUB";
}

#endif // GBACART_H
20 changes: 20 additions & 0 deletions src/NDS.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,26 @@ void NDS::Reset()
void NDS::Start()
{
Running = true;

if (ConsoleType != 0)
return;

auto* ndscart = NDSCartSlot.GetCart();
if (!ndscart)
return;

if (auto* cart = GBACartSlot.GetCart(); cart && cart->Type() == GBACart::CartType::GameSolarSensor)
{ // If we have a solar sensor cart inserted...
auto& solarcart = *static_cast<GBACart::CartGameSolarSensor*>(cart);
GBACart::GBAHeader& header = solarcart.GetHeader();
if (strncmp(header.Title, GBACart::BOKTAI_STUB_TITLE, sizeof(header.Title)) == 0) {
// If this is a stub Boktai cart (so we can use the sensor without a full ROM)...

// ...then copy the Nintendo logo data from the NDS ROM into the stub GBA ROM.
// Otherwise, the GBA cart won't be recognized.
memcpy(header.NintendoLogo, ndscart->GetHeader().NintendoLogo, sizeof(header.NintendoLogo));
}
}
}

static const char* StopReasonName(Platform::StopReason reason)
Expand Down
6 changes: 6 additions & 0 deletions src/NDS.h
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,12 @@ enum
{
GBAAddon_RAMExpansion = 1,
GBAAddon_RumblePak = 2,
// Each game in the GBA Boktai trilogy uses the same solar sensor,
// but Lunar Knights (the only NDS game to use the solar sensor)
// applies slightly different effects depending on the game.
GBAAddon_SolarSensorBoktai1 = 3,
GBAAddon_SolarSensorBoktai2 = 4,
GBAAddon_SolarSensorBoktai3 = 5,
};

class SPU;
Expand Down
6 changes: 6 additions & 0 deletions src/frontend/qt_sdl/EmuInstance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1082,7 +1082,7 @@
auto firmcfg = localCfg.GetTable("Firmware");

// we store relevant strings as UTF-8, so we need to convert them to UTF-16
auto converter = wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>{};

Check warning on line 1085 in src/frontend/qt_sdl/EmuInstance.cpp

View workflow job for this annotation

GitHub Actions / x86_64

'codecvt_utf8_utf16<char16_t>' is deprecated [-Wdeprecated-declarations]

Check warning on line 1085 in src/frontend/qt_sdl/EmuInstance.cpp

View workflow job for this annotation

GitHub Actions / x86_64

'wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>' is deprecated [-Wdeprecated-declarations]

Check warning on line 1085 in src/frontend/qt_sdl/EmuInstance.cpp

View workflow job for this annotation

GitHub Actions / arm64

'codecvt_utf8_utf16<char16_t>' is deprecated [-Wdeprecated-declarations]

Check warning on line 1085 in src/frontend/qt_sdl/EmuInstance.cpp

View workflow job for this annotation

GitHub Actions / arm64

'wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>' is deprecated [-Wdeprecated-declarations]

// setting up username
std::u16string username = converter.from_bytes(firmcfg.GetString("Username"));
Expand Down Expand Up @@ -1673,7 +1673,7 @@
std::string orig_username = firmcfg.GetString("Username");
if (!orig_username.empty())
{ // If the frontend defines a username, take it. If not, leave the existing one.
std::u16string username = std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>{}.from_bytes(

Check warning on line 1676 in src/frontend/qt_sdl/EmuInstance.cpp

View workflow job for this annotation

GitHub Actions / x86_64

'codecvt_utf8_utf16<char16_t>' is deprecated [-Wdeprecated-declarations]
orig_username);
size_t usernameLength = std::min(username.length(), (size_t) 10);
currentData.NameLength = usernameLength;
Expand Down Expand Up @@ -2146,6 +2146,12 @@
return "Rumble Pak";
case GBAAddon_RAMExpansion:
return "Memory expansion";
case GBAAddon_SolarSensorBoktai1:
return "Solar Sensor (Boktai 1)";
case GBAAddon_SolarSensorBoktai2:
return "Solar Sensor (Boktai 2)";
case GBAAddon_SolarSensorBoktai3:
return "Solar Sensor (Boktai 3)";
}

return "???";
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/qt_sdl/Window.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ MainWindow::MainWindow(int id, EmuInstance* inst, QWidget* parent) :
QMenu * submenu = menu->addMenu("Insert add-on cart");
QAction *act;

int addons[] = {GBAAddon_RAMExpansion, GBAAddon_RumblePak, -1};
int addons[] = {GBAAddon_RAMExpansion, GBAAddon_RumblePak, GBAAddon_SolarSensorBoktai1, GBAAddon_SolarSensorBoktai2, GBAAddon_SolarSensorBoktai3, -1};
for (int i = 0; addons[i] != -1; i++)
{
int addon = addons[i];
Expand Down
Loading