Cheat System: Standardize memory writes for all cheat types. Most notably, Internal cheats now reset the JIT in the same way as Action Replay cheats do.
This commit is contained in:
parent
12347c7cd9
commit
f240472f5e
|
@ -3216,6 +3216,99 @@ bool validateIORegsRead(u32 addr, u8 size)
|
|||
#define VALIDATE_IO_REGS_READ(PROC, SIZE) ;
|
||||
#endif
|
||||
|
||||
template <typename T, size_t LENGTH>
|
||||
bool MMU_WriteFromExternal(const int targetProc, const u32 targetAddress, T newValue)
|
||||
{
|
||||
u32 oldValue32;
|
||||
|
||||
switch (LENGTH)
|
||||
{
|
||||
case 1:
|
||||
if (sizeof(T) > LENGTH)
|
||||
newValue &= 0x000000FF;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
if (sizeof(T) > LENGTH)
|
||||
newValue &= 0x0000FFFF;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
oldValue32 = _MMU_read32(targetProc, MMU_AT_DEBUG, targetAddress);
|
||||
if (sizeof(T) > LENGTH)
|
||||
newValue = (oldValue32 & 0xFF000000) | (newValue & 0x00FFFFFF);
|
||||
break;
|
||||
|
||||
case 4:
|
||||
oldValue32 = _MMU_read32(targetProc, MMU_AT_DEBUG, targetAddress);
|
||||
if (sizeof(T) > LENGTH)
|
||||
newValue &= 0xFFFFFFFF;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
bool needsJitReset = ( (targetAddress >= 0x02000000) && (targetAddress < 0x02400000) );
|
||||
if (needsJitReset)
|
||||
{
|
||||
bool willValueChange = false;
|
||||
|
||||
switch (LENGTH)
|
||||
{
|
||||
case 1:
|
||||
willValueChange = (_MMU_read08(targetProc, MMU_AT_DEBUG, targetAddress) != (u8)newValue);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
willValueChange = (_MMU_read16(targetProc, MMU_AT_DEBUG, targetAddress) != (u16)newValue);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
case 4:
|
||||
willValueChange = (oldValue32 != (u32)newValue);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (!willValueChange)
|
||||
{
|
||||
needsJitReset = false;
|
||||
return needsJitReset;
|
||||
}
|
||||
}
|
||||
|
||||
switch (LENGTH)
|
||||
{
|
||||
case 1:
|
||||
_MMU_write08(targetProc, MMU_AT_DEBUG, targetAddress, (u8)newValue);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
_MMU_write16(targetProc, MMU_AT_DEBUG, targetAddress, (u16)newValue);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
case 4:
|
||||
_MMU_write32(targetProc, MMU_AT_DEBUG, targetAddress, (u32)newValue);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return needsJitReset;
|
||||
}
|
||||
|
||||
template bool MMU_WriteFromExternal< u8, 1>(const int targetProc, const u32 targetAddress, u8 newValue);
|
||||
template bool MMU_WriteFromExternal<u16, 2>(const int targetProc, const u32 targetAddress, u16 newValue);
|
||||
template bool MMU_WriteFromExternal<u32, 1>(const int targetProc, const u32 targetAddress, u32 newValue);
|
||||
template bool MMU_WriteFromExternal<u32, 2>(const int targetProc, const u32 targetAddress, u32 newValue);
|
||||
template bool MMU_WriteFromExternal<u32, 3>(const int targetProc, const u32 targetAddress, u32 newValue);
|
||||
template bool MMU_WriteFromExternal<u32, 4>(const int targetProc, const u32 targetAddress, u32 newValue);
|
||||
|
||||
//================================================================================================== ARM9 *
|
||||
//=========================================================================================================
|
||||
//=========================================================================================================
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
Copyright (C) 2006 yopyop
|
||||
Copyright (C) 2007 shash
|
||||
Copyright (C) 2007-2017 DeSmuME team
|
||||
Copyright (C) 2007-2023 DeSmuME team
|
||||
|
||||
This file is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
@ -626,6 +626,10 @@ FORCEINLINE void* MMU_gpu_map(const u32 vram_addr)
|
|||
return MMU.ARM9_LCD + (vram_page << 14) + ofs;
|
||||
}
|
||||
|
||||
// Call MMU_WriteFromExternal() when modifying memory outside of the normal execution process, such
|
||||
// as when using cheats or when the client wants to write to memory directly. This function returns
|
||||
// true if memory is modified in such a way that requires the JIT execution be reset.
|
||||
template<typename T, size_t LENGTH> bool MMU_WriteFromExternal(const int targetProc, const u32 targetAddress, T newValue);
|
||||
|
||||
template<int PROCNUM, MMU_ACCESS_TYPE AT> u8 _MMU_read08(u32 addr);
|
||||
template<int PROCNUM, MMU_ACCESS_TYPE AT> u16 _MMU_read16(u32 addr);
|
||||
|
|
|
@ -1454,8 +1454,11 @@ static void execHardware_hstart_vblankStart()
|
|||
|
||||
//for ARM7, cheats process when a vblank IRQ fires. necessary for AR compatibility and to stop cheats from breaking game boot-ups.
|
||||
//note that how we process raw cheats is up to us. so we'll do it the same way we used to, elsewhere
|
||||
if (i==1 && cheats)
|
||||
if ( (i == 1) && (cheats != NULL) )
|
||||
{
|
||||
cheats->process(CHEAT_TYPE_AR);
|
||||
CHEATS::ResetJitIfNeeded();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2197,7 +2200,11 @@ void NDS_exec(s32 nb)
|
|||
}
|
||||
currFrameCounter++;
|
||||
DEBUG_Notify.NextFrame();
|
||||
if(cheats) cheats->process(CHEAT_TYPE_INTERNAL);
|
||||
if (cheats != NULL)
|
||||
{
|
||||
cheats->process(CHEAT_TYPE_INTERNAL);
|
||||
CHEATS::ResetJitIfNeeded();
|
||||
}
|
||||
|
||||
GDBSTUB_MUTEX_UNLOCK();
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ static const char hexValid[23] = {"0123456789ABCDEFabcdef"};
|
|||
CHEATS *cheats = NULL;
|
||||
CHEATSEARCH *cheatSearch = NULL;
|
||||
|
||||
static bool cheatsResetJit;
|
||||
static bool cheatsResetJit = false;
|
||||
|
||||
void CHEATS::clear()
|
||||
{
|
||||
|
@ -143,34 +143,34 @@ bool CHEATS::move(size_t srcPos, size_t dstPos)
|
|||
#define CHEATLOG(...)
|
||||
//#define CHEATLOG(...) printf(__VA_ARGS__)
|
||||
|
||||
static void CheatWrite(int size, int proc, u32 addr, u32 val)
|
||||
template <size_t LENGTH>
|
||||
bool CHEATS::DirectWrite(const int targetProc, const u32 targetAddress, u32 newValue)
|
||||
{
|
||||
bool dirty = true;
|
||||
|
||||
bool isDangerous = false;
|
||||
if(addr >= 0x02000000 && addr < 0x02400000)
|
||||
isDangerous = true;
|
||||
|
||||
if(isDangerous)
|
||||
{
|
||||
//test dirtiness
|
||||
if(size == 8) dirty = _MMU_read08(proc, MMU_AT_DEBUG, addr) != val;
|
||||
if(size == 16) dirty = _MMU_read16(proc, MMU_AT_DEBUG, addr) != val;
|
||||
if(size == 32) dirty = _MMU_read32(proc, MMU_AT_DEBUG, addr) != val;
|
||||
}
|
||||
|
||||
if(!dirty) return;
|
||||
|
||||
if(size == 8) _MMU_write08(proc, MMU_AT_DEBUG, addr, val);
|
||||
if(size == 16) _MMU_write16(proc, MMU_AT_DEBUG, addr, val);
|
||||
if(size == 32) _MMU_write32(proc, MMU_AT_DEBUG, addr, val);
|
||||
|
||||
if(isDangerous)
|
||||
cheatsResetJit = true;
|
||||
return MMU_WriteFromExternal<u32, LENGTH>(targetProc, targetAddress, newValue);
|
||||
}
|
||||
|
||||
template bool CHEATS::DirectWrite<1>(const int targetProc, const u32 targetAddress, u32 newValue);
|
||||
template bool CHEATS::DirectWrite<2>(const int targetProc, const u32 targetAddress, u32 newValue);
|
||||
template bool CHEATS::DirectWrite<3>(const int targetProc, const u32 targetAddress, u32 newValue);
|
||||
template bool CHEATS::DirectWrite<4>(const int targetProc, const u32 targetAddress, u32 newValue);
|
||||
|
||||
void CHEATS::ARparser(const CHEATS_LIST &theList)
|
||||
bool CHEATS::DirectWrite(const size_t newValueLength, const int targetProc, const u32 targetAddress, u32 newValue)
|
||||
{
|
||||
switch (newValueLength)
|
||||
{
|
||||
case 1: return CHEATS::DirectWrite<1>(targetProc, targetAddress, newValue);
|
||||
case 2: return CHEATS::DirectWrite<2>(targetProc, targetAddress, newValue);
|
||||
case 3: return CHEATS::DirectWrite<3>(targetProc, targetAddress, newValue);
|
||||
case 4: return CHEATS::DirectWrite<4>(targetProc, targetAddress, newValue);
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CHEATS::ARparser(const CHEATS_LIST &theList)
|
||||
{
|
||||
//primary organizational source (seems to be referenced by cheaters the most) - http://doc.kodewerx.org/hacking_nds.html
|
||||
//secondary clarification and details (for programmers) - http://problemkaputt.de/gbatek.htm#dscartcheatactionreplayds
|
||||
|
@ -184,6 +184,7 @@ void CHEATS::ARparser(const CHEATS_LIST &theList)
|
|||
|
||||
bool v154 = true; //on advice of power users, v154 is so old, we can assume all cheats use it
|
||||
bool vEmulator = true;
|
||||
bool needsJitReset = false;
|
||||
|
||||
struct {
|
||||
//LSB is
|
||||
|
@ -218,6 +219,7 @@ void CHEATS::ARparser(const CHEATS_LIST &theList)
|
|||
|
||||
for (u32 i = 0; i < theList.num; i++)
|
||||
{
|
||||
bool shouldResetJit = false;
|
||||
const u32 hi = theList.code[i][0];
|
||||
const u32 lo = theList.code[i][1];
|
||||
|
||||
|
@ -273,7 +275,7 @@ void CHEATS::ARparser(const CHEATS_LIST &theList)
|
|||
x = hi & 0x0FFFFFFF;
|
||||
y = lo;
|
||||
addr = x + st.offset;
|
||||
CheatWrite(32,st.proc,addr, y);
|
||||
shouldResetJit = CHEATS::DirectWrite<4>(st.proc, addr, y);
|
||||
break;
|
||||
|
||||
case 0x01:
|
||||
|
@ -283,7 +285,7 @@ void CHEATS::ARparser(const CHEATS_LIST &theList)
|
|||
x = hi & 0x0FFFFFFF;
|
||||
y = lo & 0xFFFF;
|
||||
addr = x + st.offset;
|
||||
CheatWrite(16,st.proc,addr, y);
|
||||
shouldResetJit = CHEATS::DirectWrite<2>(st.proc, addr, y);
|
||||
break;
|
||||
|
||||
case 0x02:
|
||||
|
@ -293,7 +295,7 @@ void CHEATS::ARparser(const CHEATS_LIST &theList)
|
|||
x = hi & 0x0FFFFFFF;
|
||||
y = lo & 0xFF;
|
||||
addr = x + st.offset;
|
||||
CheatWrite(8,st.proc,addr, y);
|
||||
shouldResetJit = CHEATS::DirectWrite<1>(st.proc, addr, y);
|
||||
break;
|
||||
|
||||
case 0x03:
|
||||
|
@ -443,7 +445,7 @@ void CHEATS::ARparser(const CHEATS_LIST &theList)
|
|||
//<gbatek> C6000000 XXXXXXXX [XXXXXXXX]=offset
|
||||
if(!v154) break;
|
||||
x = lo;
|
||||
CheatWrite(32,st.proc,x, st.offset);
|
||||
shouldResetJit = CHEATS::DirectWrite<4>(st.proc, x, st.offset);
|
||||
break;
|
||||
|
||||
case 0xD0:
|
||||
|
@ -524,7 +526,7 @@ void CHEATS::ARparser(const CHEATS_LIST &theList)
|
|||
//<gbatek> word[XXXXXXXX+offset]=datareg, offset=offset+4
|
||||
x = lo;
|
||||
addr = x + st.offset;
|
||||
CheatWrite(32,st.proc,addr, st.data);
|
||||
shouldResetJit = CHEATS::DirectWrite<4>(st.proc, addr, st.data);
|
||||
st.offset += 4;
|
||||
break;
|
||||
|
||||
|
@ -535,7 +537,7 @@ void CHEATS::ARparser(const CHEATS_LIST &theList)
|
|||
//<gbatek> half[XXXXXXXX+offset]=datareg, offset=offset+2
|
||||
x = lo;
|
||||
addr = x + st.offset;
|
||||
CheatWrite(16,st.proc,addr, st.data);
|
||||
shouldResetJit = CHEATS::DirectWrite<2>(st.proc, addr, st.data);
|
||||
st.offset += 2;
|
||||
break;
|
||||
|
||||
|
@ -546,7 +548,7 @@ void CHEATS::ARparser(const CHEATS_LIST &theList)
|
|||
//<gbatek> byte[XXXXXXXX+offset]=datareg, offset=offset+1
|
||||
x = lo;
|
||||
addr = x + st.offset;
|
||||
CheatWrite(8,st.proc,addr, st.data);
|
||||
shouldResetJit = CHEATS::DirectWrite<1>(st.proc, addr, st.data);
|
||||
st.offset += 1;
|
||||
break;
|
||||
|
||||
|
@ -623,7 +625,7 @@ void CHEATS::ARparser(const CHEATS_LIST &theList)
|
|||
u32 tmp = theList.code[i][t];
|
||||
if (t == 1) i++;
|
||||
t ^= 1;
|
||||
CheatWrite(32,st.proc,addr,tmp);
|
||||
shouldResetJit = CHEATS::DirectWrite<4>(st.proc, addr, tmp);
|
||||
addr += 4;
|
||||
y -= 4;
|
||||
}
|
||||
|
@ -631,7 +633,7 @@ void CHEATS::ARparser(const CHEATS_LIST &theList)
|
|||
{
|
||||
if (i == theList.num) break; //if we erroneously went off the end, bail
|
||||
u32 tmp = theList.code[i][t]>>b;
|
||||
CheatWrite(8,st.proc,addr,tmp);
|
||||
shouldResetJit = CHEATS::DirectWrite<1>(st.proc, addr, tmp);
|
||||
addr += 1;
|
||||
y -= 1;
|
||||
b += 4;
|
||||
|
@ -657,7 +659,7 @@ void CHEATS::ARparser(const CHEATS_LIST &theList)
|
|||
{
|
||||
if (i == theList.num) break; //if we erroneously went off the end, bail
|
||||
u32 tmp = _MMU_read32(st.proc,MMU_AT_DEBUG,addr);
|
||||
CheatWrite(32, st.proc,operand,tmp);
|
||||
shouldResetJit = CHEATS::DirectWrite<4>(st.proc, operand, tmp);
|
||||
addr += 4;
|
||||
operand += 4;
|
||||
y -= 4;
|
||||
|
@ -666,7 +668,7 @@ void CHEATS::ARparser(const CHEATS_LIST &theList)
|
|||
{
|
||||
if (i == theList.num) break; //if we erroneously went off the end, bail
|
||||
u8 tmp = _MMU_read08(st.proc,MMU_AT_DEBUG,addr);
|
||||
CheatWrite(8,st.proc,operand,tmp);
|
||||
shouldResetJit = CHEATS::DirectWrite<1>(st.proc, operand, tmp);
|
||||
addr += 1;
|
||||
operand += 1;
|
||||
y -= 1;
|
||||
|
@ -677,8 +679,14 @@ void CHEATS::ARparser(const CHEATS_LIST &theList)
|
|||
printf("AR: ERROR unknown command %08X %08X\n", hi, lo);
|
||||
break;
|
||||
}
|
||||
|
||||
if (shouldResetJit)
|
||||
{
|
||||
needsJitReset = true;
|
||||
}
|
||||
}
|
||||
|
||||
return needsJitReset;
|
||||
}
|
||||
|
||||
size_t CHEATS::add_AR_Direct(const CHEATS_LIST &srcCheat)
|
||||
|
@ -1140,73 +1148,82 @@ bool CHEATS::load()
|
|||
return didLoadAllItems;
|
||||
}
|
||||
|
||||
void CHEATS::process(int targetType) const
|
||||
bool CHEATS::process(int targetType) const
|
||||
{
|
||||
if (CommonSettings.cheatsDisable) return;
|
||||
bool needsJitReset = false;
|
||||
|
||||
if (this->_list.size() == 0) return;
|
||||
|
||||
cheatsResetJit = false;
|
||||
if (CommonSettings.cheatsDisable || (this->_list.size() == 0))
|
||||
return needsJitReset;
|
||||
|
||||
size_t num = this->_list.size();
|
||||
for (size_t i = 0; i < num; i++)
|
||||
{
|
||||
if (this->_list[i].enabled == 0) continue;
|
||||
bool shouldResetJit = false;
|
||||
|
||||
if (this->_list[i].enabled == 0)
|
||||
continue;
|
||||
|
||||
int type = this->_list[i].type;
|
||||
|
||||
if(type != targetType)
|
||||
continue;
|
||||
if (type != targetType)
|
||||
continue;
|
||||
|
||||
switch(type)
|
||||
switch (type)
|
||||
{
|
||||
case 0: // internal cheat system
|
||||
{
|
||||
case CHEAT_TYPE_INTERNAL:
|
||||
//INFO("list at 0x0|%07X value %i (size %i)\n",list[i].code[0], list[i].lo[0], list[i].size);
|
||||
u32 addr = this->_list[i].code[0][0];
|
||||
u32 val = this->_list[i].code[0][1];
|
||||
switch (this->_list[i].size)
|
||||
{
|
||||
case 0:
|
||||
_MMU_write08<ARMCPU_ARM9,MMU_AT_DEBUG>(addr,val);
|
||||
break;
|
||||
case 1:
|
||||
_MMU_write16<ARMCPU_ARM9,MMU_AT_DEBUG>(addr,val);
|
||||
break;
|
||||
case 2:
|
||||
{
|
||||
u32 tmp = _MMU_read32<ARMCPU_ARM9,MMU_AT_DEBUG>(addr);
|
||||
tmp &= 0xFF000000;
|
||||
tmp |= (val & 0x00FFFFFF);
|
||||
_MMU_write32<ARMCPU_ARM9,MMU_AT_DEBUG>(addr,tmp);
|
||||
break;
|
||||
}
|
||||
case 3:
|
||||
_MMU_write32<ARMCPU_ARM9,MMU_AT_DEBUG>(addr,val);
|
||||
break;
|
||||
}
|
||||
shouldResetJit = CHEATS::DirectWrite(this->_list[i].size + 1, ARMCPU_ARM9, this->_list[i].code[0][0], this->_list[i].code[0][1]);
|
||||
break;
|
||||
} //end case 0 internal cheat system
|
||||
|
||||
case 1: // Action Replay
|
||||
CHEATS::ARparser(this->_list[i]);
|
||||
case CHEAT_TYPE_AR:
|
||||
shouldResetJit = CHEATS::ARparser(this->_list[i]);
|
||||
break;
|
||||
case 2: // Codebreaker
|
||||
|
||||
case CHEAT_TYPE_CODEBREAKER:
|
||||
break;
|
||||
default: continue;
|
||||
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
if (shouldResetJit)
|
||||
{
|
||||
needsJitReset = true;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_JIT
|
||||
if(cheatsResetJit)
|
||||
if (needsJitReset)
|
||||
{
|
||||
if(CommonSettings.use_jit)
|
||||
CHEATS::JitNeedsReset();
|
||||
}
|
||||
|
||||
return needsJitReset;
|
||||
}
|
||||
|
||||
void CHEATS::JitNeedsReset()
|
||||
{
|
||||
cheatsResetJit = true;
|
||||
}
|
||||
|
||||
bool CHEATS::ResetJitIfNeeded()
|
||||
{
|
||||
bool didJitReset = false;
|
||||
|
||||
#ifdef HAVE_JIT
|
||||
if (cheatsResetJit)
|
||||
{
|
||||
if (CommonSettings.use_jit)
|
||||
{
|
||||
printf("Cheat code operation potentially not compatible with JIT operations. Resetting JIT...\n");
|
||||
arm_jit_reset(true, true);
|
||||
}
|
||||
|
||||
cheatsResetJit = false;
|
||||
didJitReset = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
return didJitReset;
|
||||
}
|
||||
|
||||
void CHEATS::StringFromXXCode(const CHEATS_LIST &srcCheatItem, char *outCStringBuffer)
|
||||
|
@ -1441,7 +1458,7 @@ u32 CHEATSEARCH::search(u8 comp)
|
|||
|
||||
this->_amount = 0;
|
||||
|
||||
switch (_size)
|
||||
switch (this->_size)
|
||||
{
|
||||
case 0: // 1 byte
|
||||
for (u32 i = 0; i < (4 * 1024 * 1024); i++)
|
||||
|
@ -1561,10 +1578,10 @@ u32 CHEATSEARCH::getAmount()
|
|||
bool CHEATSEARCH::getList(u32 *address, u32 *curVal)
|
||||
{
|
||||
bool didGetValue = false;
|
||||
u8 step = (_size+1);
|
||||
u8 step = this->_size + 1;
|
||||
u8 stepMem = 1;
|
||||
|
||||
switch (_size)
|
||||
switch (this->_size)
|
||||
{
|
||||
case 1: stepMem = 0x3; break;
|
||||
case 2: stepMem = 0x7; break;
|
||||
|
@ -1580,7 +1597,7 @@ bool CHEATSEARCH::getList(u32 *address, u32 *curVal)
|
|||
*address = i;
|
||||
this->_lastRecord = i+step;
|
||||
|
||||
switch (_size)
|
||||
switch (this->_size)
|
||||
{
|
||||
case 0: *curVal = (u32)T1ReadByte(MMU.MMU_MEM[ARMCPU_ARM9][0x20], i); break;
|
||||
case 1: *curVal = (u32)T1ReadWord(MMU.MMU_MEM[ARMCPU_ARM9][0x20], i); break;
|
||||
|
|
|
@ -110,9 +110,15 @@ public:
|
|||
void setDescription(const char *description, const size_t pos);
|
||||
bool save();
|
||||
bool load();
|
||||
void process(int targetType) const;
|
||||
bool process(int targetType) const;
|
||||
|
||||
static void ARparser(const CHEATS_LIST &cheat);
|
||||
static void JitNeedsReset();
|
||||
static bool ResetJitIfNeeded();
|
||||
|
||||
template<size_t LENGTH> static bool DirectWrite(const int targetProc, const u32 targetAddress, u32 newValue);
|
||||
static bool DirectWrite(const size_t newValueLength, const int targetProc, const u32 targetAddress, u32 newValue);
|
||||
|
||||
static bool ARparser(const CHEATS_LIST &cheat);
|
||||
|
||||
static void StringFromXXCode(const CHEATS_LIST &srcCheatItem, char *outCStringBuffer);
|
||||
static bool XXCodeFromString(const std::string codeString, CHEATS_LIST &outCheatItem);
|
||||
|
@ -213,5 +219,3 @@ public:
|
|||
|
||||
extern CHEATS *cheats;
|
||||
extern CHEATSEARCH *cheatSearch;
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue