mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-20 17:59:08 +02:00
The cheat tests left their ini files in the working directory: on Windows the delete failed while CheatFileParser still held the file open. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
228 lines
7.3 KiB
C++
228 lines
7.3 KiB
C++
// Copyright (c) 2012- PPSSPP Project.
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, version 2.0 or later versions.
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License 2.0 for more details.
|
|
|
|
// A copy of the GPL 2.0 should have been included with the program.
|
|
// If not, see http://www.gnu.org/licenses/
|
|
|
|
// Official git repository and contact information can be found at
|
|
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
|
|
|
#include <cstdio>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "Common/File/FileUtil.h"
|
|
#include "Common/File/Path.h"
|
|
#include "Core/CwCheat.h"
|
|
#include "Core/MemMap.h"
|
|
#include "Core/MemMapHelpers.h"
|
|
#include "Core/HLE/sceCtrl.h"
|
|
#include "Core/MIPS/MIPS.h"
|
|
#include "UnitTest.h"
|
|
|
|
// Most of these are the PSPAR tests from TempAR's docs/tests/tests.db, with their expected results.
|
|
static const char *const g_cheatFile = R"(_S TEST00000
|
|
_G Cheat tests
|
|
_C1 Loop
|
|
_M 0xD5000000 0x11223344
|
|
_M 0xC0000000 0x00000002
|
|
_M 0xD6000000 0x08802000
|
|
_M 0xD4000000 0x44444444
|
|
_M 0xD2000000 0x00000000
|
|
_C1 Loop without terminator
|
|
_M 0xD5000000 0x11223344
|
|
_M 0xC0000000 0x00000002
|
|
_M 0xD6000000 0x08802000
|
|
_M 0xD4000000 0x44444444
|
|
_C1 Patch
|
|
_M 0xE8802000 0x0000000C
|
|
_M 0x11223344 0x55667788
|
|
_M 0x99AABBCC 0x00000000
|
|
_C1 Patch with extra lines
|
|
_M 0xE8802000 0x0000000C # Expected Result:
|
|
_M 0x11223344 0x55667788 # 0x08802000 = 0x11223344; 0x08802004 = 0x55667788
|
|
_M 0x99AABBCC 0x00000000 # 0x08802008 = 0x99AABBCC; 0x0880200C = 0xDDEEFF00
|
|
_M 0x0880200C 0xDDEEFF00 #
|
|
_C1 Memory copy
|
|
_M 0xD3000000 0x08802000
|
|
_M 0x00000000 0x11223344
|
|
_M 0xF8802008 0x00000004
|
|
_C1 Write offset
|
|
_M 0x08802000 0x08802004
|
|
_M 0x08802004 0x08802020
|
|
_M 0x68802000 0x00000000
|
|
_M 0xB8802000 0x00000000
|
|
_M 0xB0000000 0x00000000
|
|
_M 0xC6000000 0x08802008
|
|
_M 0xD2000000 0x00000000
|
|
_C1 Multi-level pointer
|
|
_M 0x08802000 0x08802004
|
|
_M 0x08802004 0x08802020
|
|
_M 0x68802000 0x00000000
|
|
_M 0xB8802000 0x00000000
|
|
_M 0xB0000000 0x00000000
|
|
_M 0x00000000 0x12345678
|
|
_M 0xD2000000 0x00000000
|
|
_C1 Data register add
|
|
_M 0x08802000 0x11111111
|
|
_M 0xD9000000 0x08802000
|
|
_M 0xD4000000 0x11111111
|
|
_M 0xD6000000 0x08802004
|
|
_M 0xD2000000 0x00000000
|
|
_C1 Data register sub
|
|
_M 0x08802000 0x22222222
|
|
_M 0xD9000000 0x08802000
|
|
_M 0xD4000000 0xEEEEEEEF
|
|
_M 0xD6000000 0x08802004
|
|
_M 0xD2000000 0x00000000
|
|
_C1 Safe data store
|
|
_M 0xC4000000 0x00000000
|
|
_M 0x00000020 0x08802000
|
|
_M 0x00000024 0x12345678
|
|
_M 0xD2000000 0x00000000
|
|
_M 0x00000000 0x00000000
|
|
_C1 Conditions and terminator
|
|
_M 0x08802000 0x00001234
|
|
_M 0x58802000 0x00001234
|
|
_M 0x08802004 0x11111111
|
|
_M 0x58802000 0x00005678
|
|
_M 0x08802008 0x22222222
|
|
_M 0xD0000000 0x00000000
|
|
_M 0x0880200C 0x33333333
|
|
_M 0xD0000000 0x00000000
|
|
_M 0x88802000 0xFF0F0005
|
|
_M 0x08802010 0x44444444
|
|
_M 0xD2000000 0x00000000
|
|
_M 0x28802014 0x00000055
|
|
_C1 Pad (PSPAR address)
|
|
_M 0x99F00000 0x00000200
|
|
_M 0x08802000 0x11111111
|
|
_M 0xD0000000 0x00000000
|
|
_M 0xA9F00000 0x00000200
|
|
_M 0x08802004 0x22222222
|
|
_C1 Pad (TempAR address)
|
|
_M 0x9A000000 0xFDFF0200
|
|
_M 0x08802000 0x33333333
|
|
_C1 CwCheat with a comment
|
|
_L 0x20002000 0x11111111 # Comment
|
|
)";
|
|
|
|
struct ExpectedWord {
|
|
u32 addr;
|
|
u32 value;
|
|
};
|
|
|
|
static bool RunCheatTest(CWCheatEngine &engine, const CheatCode &cheat, const std::vector<ExpectedWord> &expected) {
|
|
Memory::Memset(0x08801F00, 0, 0x400, "CheatTest");
|
|
engine.SetCheats({ cheat });
|
|
engine.Run();
|
|
for (const ExpectedWord &e : expected) {
|
|
u32 actual = Memory::ReadUnchecked_U32(e.addr);
|
|
if (actual != e.value) {
|
|
printf("Cheat '%s': %08x = %08x, expected %08x\n", cheat.name.c_str(), e.addr, actual, e.value);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool RunCheatTests() {
|
|
const Path filename = UnitTestTempPath("unittest_cheats.ini");
|
|
if (!File::WriteStringToFile(true, g_cheatFile, filename)) {
|
|
printf("Can't write %s\n", filename.c_str());
|
|
return false;
|
|
}
|
|
bool parsed;
|
|
std::vector<CheatCode> cheats;
|
|
{
|
|
// The parser holds the file open, which keeps it from being deleted on Windows.
|
|
CheatFileParser parser(filename, "TEST00000");
|
|
parsed = parser.Parse();
|
|
for (const std::string &error : parser.GetErrors()) {
|
|
printf("%s\n", error.c_str());
|
|
}
|
|
cheats = parser.GetCheats();
|
|
}
|
|
File::Delete(filename);
|
|
EXPECT_TRUE(parsed);
|
|
EXPECT_EQ_INT((int)cheats.size(), 14);
|
|
|
|
const std::vector<std::vector<ExpectedWord>> expected = {
|
|
{ { 0x08802000, 0x11223344 }, { 0x08802004, 0x55667788 }, { 0x08802008, 0x99AABBCC } },
|
|
{ { 0x08802000, 0x11223344 }, { 0x08802004, 0x55667788 }, { 0x08802008, 0x99AABBCC } },
|
|
{ { 0x08802000, 0x11223344 }, { 0x08802004, 0x55667788 }, { 0x08802008, 0x99AABBCC }, { 0x0880200C, 0 } },
|
|
{ { 0x08802000, 0x11223344 }, { 0x08802004, 0x55667788 }, { 0x08802008, 0x99AABBCC }, { 0x0880200C, 0xDDEEFF00 } },
|
|
{ { 0x08802000, 0x11223344 }, { 0x08802008, 0x11223344 } },
|
|
{ { 0x08802000, 0x08802004 }, { 0x08802004, 0x08802020 }, { 0x08802008, 0x08802020 } },
|
|
{ { 0x08802000, 0x08802004 }, { 0x08802004, 0x08802020 }, { 0x08802020, 0x12345678 } },
|
|
{ { 0x08802004, 0x22222222 } },
|
|
{ { 0x08802004, 0x11111111 } },
|
|
{ { 0x08802000, 0x12345678 } },
|
|
{ { 0x08802004, 0x11111111 }, { 0x08802008, 0 }, { 0x0880200C, 0x33333333 }, { 0x08802010, 0x44444444 }, { 0x08802014, 0x55 } },
|
|
};
|
|
|
|
CWCheatEngine engine("TEST00000");
|
|
bool success = cheats.size() == 14;
|
|
for (size_t i = 0; success && i < expected.size(); i++) {
|
|
success = RunCheatTest(engine, cheats[i], expected[i]);
|
|
}
|
|
|
|
if (success) {
|
|
__CtrlUpdateButtons(0, 0xFFFFFFFF);
|
|
success = RunCheatTest(engine, cheats[11], { { 0x08802000, 0 }, { 0x08802004, 0x22222222 } });
|
|
success = success && RunCheatTest(engine, cheats[12], { { 0x08802000, 0 } });
|
|
__CtrlUpdateButtons(0x00000200 | 0x00004000, 0);
|
|
success = success && RunCheatTest(engine, cheats[11], { { 0x08802000, 0x11111111 }, { 0x08802004, 0 } });
|
|
success = success && RunCheatTest(engine, cheats[12], { { 0x08802000, 0x33333333 } });
|
|
__CtrlUpdateButtons(0, 0xFFFFFFFF);
|
|
success = success && RunCheatTest(engine, cheats[13], { { 0x08802000, 0x11111111 } });
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
static bool TestCheatParseErrors() {
|
|
const Path filename = UnitTestTempPath("unittest_cheats_bad.ini");
|
|
const char *content = "_S TEST00000\n_C1 Mixed\n_L 0x20002000 0x00000001\n_M 0x08802004 0x00000002\n_C1 Extended\n_N 0xD4000001 0x00000001\n";
|
|
if (!File::WriteStringToFile(true, content, filename)) {
|
|
return false;
|
|
}
|
|
bool parsed;
|
|
size_t errorCount;
|
|
std::vector<CheatCode> cheats;
|
|
{
|
|
CheatFileParser parser(filename, "TEST00000");
|
|
parsed = parser.Parse();
|
|
errorCount = parser.GetErrors().size();
|
|
cheats = parser.GetCheats();
|
|
}
|
|
File::Delete(filename);
|
|
EXPECT_FALSE(parsed);
|
|
EXPECT_EQ_INT((int)errorCount, 2);
|
|
EXPECT_EQ_INT((int)cheats.size(), 1);
|
|
EXPECT_EQ_INT((int)cheats[0].lines.size(), 1);
|
|
EXPECT_TRUE(cheats[0].format == CheatCodeFormat::CwCheat);
|
|
return true;
|
|
}
|
|
|
|
bool TestCwCheat() {
|
|
// Other tests may leave currentMIPS pointing elsewhere; the cheat engine needs the real one.
|
|
currentMIPS = &mipsr4k;
|
|
Memory::g_MemorySize = Memory::RAM_NORMAL_SIZE;
|
|
if (!Memory::Init(Memory::MemMapSetupFlags::Default)) {
|
|
printf("Memory::Init failed\n");
|
|
return false;
|
|
}
|
|
const bool success = RunCheatTests() && TestCheatParseErrors();
|
|
Memory::Shutdown();
|
|
return success;
|
|
}
|