Add some extra tests for cheats

- Ensure they're persisted across resets
- Test that they're not applied if invalid
- Test that they're cleared when we ask them to be cleared
This commit is contained in:
Jesse Talavera 2024-08-18 18:04:53 -04:00
parent e9b376957a
commit 797d1ae324
8 changed files with 141 additions and 0 deletions

View File

@ -231,6 +231,8 @@ void MelonDsDs::CoreState::Reset() {
memcpy(gbaSram.data(), Console->GetGBASave(), Console->GetGBASaveLength());
}
std::vector<melonDS::ARCode> cheats = std::move(Console->AREngine.Cheats);
Console = nullptr;
melonDS::NDS::Current = nullptr;
Console = CreateConsole(
@ -250,6 +252,8 @@ void MelonDsDs::CoreState::Reset() {
Console->SetGBASave(gbaSram.data(), gbaSram.size());
}
Console->AREngine.Cheats = std::move(cheats);
_ndsSramInstalled = false;
InitFlushFirmwareTask();

View File

@ -182,6 +182,15 @@ extern "C" bool melondsds_is_software_renderer() {
return mode && *mode == RenderMode::Software;
}
extern "C" unsigned melondsds_num_cheats() {
using namespace MelonDsDs;
const auto *console = Core.GetConsole();
if (!console)
return 0;
return console->AREngine.Cheats.size();
}
extern "C" retro_proc_address_t MelonDsDs::GetRetroProcAddress(const char* sym) noexcept {
if (string_is_equal(sym, "libretropy_add_integers"))
return reinterpret_cast<retro_proc_address_t>(libretropy_add_integers);
@ -252,6 +261,9 @@ extern "C" retro_proc_address_t MelonDsDs::GetRetroProcAddress(const char* sym)
if (string_is_equal(sym, "melondsds_is_software_renderer"))
return reinterpret_cast<retro_proc_address_t>(melondsds_is_software_renderer);
if (string_is_equal(sym, "melondsds_num_cheats"))
return reinterpret_cast<retro_proc_address_t>(melondsds_num_cheats);
return nullptr;
}

View File

@ -289,6 +289,7 @@ set(MICRECORD_NDS "${CMAKE_CURRENT_SOURCE_DIR}/nds/micrecord.nds")
include(cmake/Basics.cmake)
include(cmake/Booting.cmake)
include(cmake/Cheats.cmake)
include(cmake/Errors.cmake)
include(cmake/Firmware.cmake)
include(cmake/Reset.cmake)

47
test/cmake/Cheats.cmake Normal file
View File

@ -0,0 +1,47 @@
add_python_test(
NAME "Cheats persist after retro_reset"
TEST_MODULE cheats.persist_after_reset
CONTENT "${NDS_ROM}"
)
add_python_test(
NAME "Resetting cheats clears them"
TEST_MODULE cheats.cleared_on_cheat_reset
CONTENT "${NDS_ROM}"
)
add_python_test(
NAME "Cheats can be updated individually"
TEST_MODULE cheats.can_be_updated_individually
CONTENT "${NDS_ROM}"
DISABLED
)
add_python_test(
NAME "Cheats can be disabled individually"
TEST_MODULE cheats.can_be_disabled_individually
CONTENT "${NDS_ROM}"
DISABLED
)
add_python_test(
NAME "Cheats can be toggled on the error screen"
TEST_MODULE cheats.can_be_toggled_on_error_screen
CONTENT "${NDS_ROM}"
DISABLED
)
add_python_test(
NAME "Invalid cheats are not enabled"
TEST_MODULE cheats.not_enabled_if_invalid
CONTENT "${NDS_ROM}"
)
add_python_test(
NAME "Cheats can be disabled if they're invalid"
TEST_MODULE cheats.can_be_disabled_if_invalid
CONTENT "${NDS_ROM}"
DISABLED
)

View File

View File

@ -0,0 +1,27 @@
from ctypes import CFUNCTYPE, c_uint
from libretro import Session
from libretro.h import RETRO_MEMORY_SYSTEM_RAM
import prelude
session: Session
with prelude.session() as session:
memory = session.core.get_memory(RETRO_MEMORY_SYSTEM_RAM)
assert memory is not None
num_cheats = session.get_proc_address(b"melondsds_num_cheats", CFUNCTYPE(c_uint))
session.core.cheat_set(0, True, b'02000000 DEADBEEF')
cheats = num_cheats()
assert cheats == 1, f"Expected 1 cheat, got {cheats}"
for i in range(60):
session.run()
session.core.cheat_reset()
cheats = num_cheats()
assert cheats == 0, f"Expected 0 cheats, got {cheats}"

View File

@ -0,0 +1,12 @@
from typing import cast
from libretro import Session, LoggerMessageInterface, LogLevel
import prelude
session: Session
with prelude.session() as session:
message = cast(LoggerMessageInterface, session.message)
session.core.cheat_set(0, True, b'fgsfds')
assert message.message_exts[-1].level == LogLevel.WARNING

View File

@ -0,0 +1,38 @@
from ctypes import CFUNCTYPE, c_uint
from libretro import Session
from libretro.h import RETRO_MEMORY_SYSTEM_RAM
import prelude
session: Session
with prelude.session() as session:
memory = session.core.get_memory(RETRO_MEMORY_SYSTEM_RAM)
assert memory is not None
num_cheats = session.get_proc_address(b"melondsds_num_cheats", CFUNCTYPE(c_uint))
session.core.cheat_set(0, True, b'02000000 DEADBEEF')
# A trivial cheat code with one instruction: 0XXXXXXX-YYYYYYYY,
# where XXXXXXX (in this case, 02000000) is the address to write to
# and YYYYYYYY (in this case, DEADBEEF) is the value to write.
# See https://mgba-emu.github.io/gbatek/#dscartcheatactionreplayds for more details
# Slicing reads the bytes in sequence, not as a word
assert memory[0:4].tobytes() != b'\xef\xbe\xad\xde'
# Cheats aren't applied immediately, they're applied when the ARM7 processes a VBlank IRQ
# (so we need to run a few frames)
for i in range(60):
session.run()
session.reset()
num_cheats = num_cheats()
assert num_cheats == 1, f"Expected 1 cheat, got {num_cheats}"
for i in range(60):
session.run()
assert memory[0:4].tobytes() == b'\xef\xbe\xad\xde', f"Expected 0xDEADBEEF, got {memory[0:4].tobytes()}"