mirror of
https://github.com/dborth/vbagx.git
synced 2024-12-27 11:11:50 +01:00
523 lines
13 KiB
C++
523 lines
13 KiB
C++
// VisualBoyAdvance - Nintendo Gameboy/GameboyAdvance (TM) emulator.
|
|
// Copyright (C) 1999-2003 Forgotten
|
|
// Copyright (C) 2005 Forgotten and the VBA development team
|
|
|
|
// 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; either version 2, or(at your option)
|
|
// any later version.
|
|
//
|
|
// 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 for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program; if not, write to the Free Software Foundation,
|
|
// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <ctype.h>
|
|
|
|
#include "../System.h"
|
|
#include "../NLS.h"
|
|
#include "../Util.h"
|
|
|
|
#include "gbCheats.h"
|
|
#include "gbGlobals.h"
|
|
#include "gb.h"
|
|
|
|
gbCheat gbCheatList[100];
|
|
int gbCheatNumber = 0;
|
|
int gbNextCheat = 0;
|
|
bool gbCheatMap[0x10000];
|
|
|
|
extern bool cheatsEnabled;
|
|
|
|
#define GBCHEAT_IS_HEX(a) ( ((a)>='A' && (a) <='F') || ((a) >='0' && (a) <= '9'))
|
|
#define GBCHEAT_HEX_VALUE(a) ( (a) >= 'A' ? (a) - 'A' + 10 : (a) - '0')
|
|
|
|
void gbCheatUpdateMap()
|
|
{
|
|
memset(gbCheatMap, 0, 0x10000);
|
|
|
|
for(int i = 0; i < gbCheatNumber; i++) {
|
|
if(gbCheatList[i].enabled)
|
|
gbCheatMap[gbCheatList[i].address] = true;
|
|
}
|
|
}
|
|
|
|
void gbCheatsSaveGame(gzFile gzFile)
|
|
{
|
|
utilWriteInt(gzFile, gbCheatNumber);
|
|
if(gbCheatNumber>0)
|
|
utilGzWrite(gzFile, &gbCheatList[0], sizeof(gbCheat)*gbCheatNumber);
|
|
}
|
|
|
|
void gbCheatsReadGame(gzFile gzFile, int version)
|
|
{
|
|
if(version <= 8) {
|
|
int gbGgOn = utilReadInt(gzFile);
|
|
|
|
if(gbGgOn) {
|
|
int n = utilReadInt(gzFile);
|
|
gbXxCheat tmpCheat;
|
|
for(int i = 0; i < n; i++) {
|
|
utilGzRead(gzFile,&tmpCheat, sizeof(gbXxCheat));
|
|
gbAddGgCheat(tmpCheat.cheatCode, tmpCheat.cheatDesc);
|
|
}
|
|
}
|
|
|
|
int gbGsOn = utilReadInt(gzFile);
|
|
|
|
if(gbGsOn) {
|
|
int n = utilReadInt(gzFile);
|
|
gbXxCheat tmpCheat;
|
|
for(int i = 0; i < n; i++) {
|
|
utilGzRead(gzFile,&tmpCheat, sizeof(gbXxCheat));
|
|
gbAddGsCheat(tmpCheat.cheatCode, tmpCheat.cheatDesc);
|
|
}
|
|
}
|
|
} else {
|
|
gbCheatNumber = utilReadInt(gzFile);
|
|
|
|
if(gbCheatNumber>0) {
|
|
utilGzRead(gzFile, &gbCheatList[0], sizeof(gbCheat)*gbCheatNumber);
|
|
}
|
|
}
|
|
|
|
gbCheatUpdateMap();
|
|
}
|
|
|
|
void gbCheatsSaveCheatList(const char *file)
|
|
{
|
|
if(gbCheatNumber == 0)
|
|
return;
|
|
FILE *f = fopen(file, "wb");
|
|
if(f == NULL)
|
|
return;
|
|
int version = 1;
|
|
fwrite(&version, 1, sizeof(version), f);
|
|
int type = 1;
|
|
fwrite(&type, 1, sizeof(type), f);
|
|
fwrite(&gbCheatNumber, 1, sizeof(gbCheatNumber), f);
|
|
fwrite(gbCheatList, 1, sizeof(gbCheatList), f);
|
|
fclose(f);
|
|
}
|
|
|
|
bool gbCheatsLoadCheatList(const char *file)
|
|
{
|
|
gbCheatNumber = 0;
|
|
|
|
gbCheatUpdateMap();
|
|
|
|
int count = 0;
|
|
|
|
FILE *f = fopen(file, "rb");
|
|
|
|
if(f == NULL)
|
|
return false;
|
|
|
|
int version = 0;
|
|
|
|
if(fread(&version, 1, sizeof(version), f) != sizeof(version)) {
|
|
fclose(f);
|
|
return false;
|
|
}
|
|
|
|
if(version != 1) {
|
|
systemMessage(MSG_UNSUPPORTED_CHEAT_LIST_VERSION,
|
|
N_("Unsupported cheat list version %d"), version);
|
|
fclose(f);
|
|
return false;
|
|
}
|
|
|
|
int type = 0;
|
|
if(fread(&type, 1, sizeof(type), f) != sizeof(type)) {
|
|
fclose(f);
|
|
return false;
|
|
}
|
|
|
|
if(type != 1) {
|
|
systemMessage(MSG_UNSUPPORTED_CHEAT_LIST_TYPE,
|
|
N_("Unsupported cheat list type %d"), type);
|
|
fclose(f);
|
|
return false;
|
|
}
|
|
|
|
if(fread(&count, 1, sizeof(count), f) != sizeof(count)) {
|
|
fclose(f);
|
|
return false;
|
|
}
|
|
|
|
if(fread(gbCheatList, 1, sizeof(gbCheatList), f) != sizeof(gbCheatList)) {
|
|
fclose(f);
|
|
return false;
|
|
}
|
|
|
|
gbCheatNumber = count;
|
|
gbCheatUpdateMap();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool gbVerifyGsCode(const char *code)
|
|
{
|
|
size_t len = strlen(code);
|
|
|
|
if(len == 0)
|
|
return true;
|
|
|
|
if(len != 8)
|
|
return false;
|
|
|
|
for(int i = 0; i < 8; i++)
|
|
if(!GBCHEAT_IS_HEX(code[i]))
|
|
return false;
|
|
|
|
int address = GBCHEAT_HEX_VALUE(code[6]) << 12 |
|
|
GBCHEAT_HEX_VALUE(code[7]) << 8 |
|
|
GBCHEAT_HEX_VALUE(code[4]) << 4 |
|
|
GBCHEAT_HEX_VALUE(code[5]);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool gbAddGsCheat(const char *code, const char *desc)
|
|
{
|
|
if(gbCheatNumber > 99) {
|
|
systemMessage(MSG_MAXIMUM_NUMBER_OF_CHEATS,
|
|
N_("Maximum number of cheats reached."));
|
|
return false;
|
|
}
|
|
|
|
if(!gbVerifyGsCode(code)) {
|
|
systemMessage(MSG_INVALID_GAMESHARK_CODE,
|
|
N_("Invalid GameShark code: %s"), code);
|
|
return false;
|
|
}
|
|
|
|
int i = gbCheatNumber;
|
|
|
|
strcpy(gbCheatList[i].cheatCode, code);
|
|
strcpy(gbCheatList[i].cheatDesc, desc);
|
|
|
|
gbCheatList[i].code = GBCHEAT_HEX_VALUE(code[0]) << 4 |
|
|
GBCHEAT_HEX_VALUE(code[1]);
|
|
|
|
gbCheatList[i].value = GBCHEAT_HEX_VALUE(code[2]) << 4 |
|
|
GBCHEAT_HEX_VALUE(code[3]);
|
|
|
|
gbCheatList[i].address = GBCHEAT_HEX_VALUE(code[6]) << 12 |
|
|
GBCHEAT_HEX_VALUE(code[7]) << 8 |
|
|
GBCHEAT_HEX_VALUE(code[4]) << 4 |
|
|
GBCHEAT_HEX_VALUE(code[5]);
|
|
|
|
gbCheatList[i].compare = 0;
|
|
|
|
gbCheatList[i].enabled = true;
|
|
|
|
int gsCode = gbCheatList[i].code;
|
|
|
|
if ((gsCode !=1) && ((gsCode & 0xF0) !=0x80) && ((gsCode & 0xF0) !=0x90) &&
|
|
((gsCode & 0xF0) !=0xA0) && ((gsCode) !=0xF0) && ((gsCode) !=0xF1))
|
|
systemMessage(MSG_WRONG_GAMESHARK_CODE,
|
|
N_("Wrong GameShark code type : %s"), code);
|
|
else if (((gsCode & 0xF0) ==0xA0) || ((gsCode) ==0xF0) || ((gsCode) ==0xF1))
|
|
systemMessage(MSG_UNSUPPORTED_GAMESHARK_CODE,
|
|
N_("Unsupported GameShark code type : %s"), code);
|
|
|
|
gbCheatNumber++;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool gbVerifyGgCode(const char *code)
|
|
{
|
|
size_t len = strlen(code);
|
|
|
|
if(len != 11 &&
|
|
len != 7 &&
|
|
len != 6 &&
|
|
len != 0)
|
|
return false;
|
|
|
|
if(len == 0)
|
|
return true;
|
|
|
|
if(!GBCHEAT_IS_HEX(code[0]))
|
|
return false;
|
|
if(!GBCHEAT_IS_HEX(code[1]))
|
|
return false;
|
|
if(!GBCHEAT_IS_HEX(code[2]))
|
|
return false;
|
|
if(code[3] != '-')
|
|
return false;
|
|
if(!GBCHEAT_IS_HEX(code[4]))
|
|
return false;
|
|
if(!GBCHEAT_IS_HEX(code[5]))
|
|
return false;
|
|
if(!GBCHEAT_IS_HEX(code[6]))
|
|
return false;
|
|
if(code[7] != 0) {
|
|
if(code[7] != '-')
|
|
return false;
|
|
if(code[8] != 0) {
|
|
if(!GBCHEAT_IS_HEX(code[8]))
|
|
return false;
|
|
if(!GBCHEAT_IS_HEX(code[9]))
|
|
return false;
|
|
if(!GBCHEAT_IS_HEX(code[10]))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// int replace = (GBCHEAT_HEX_VALUE(code[0]) << 4) +
|
|
// GBCHEAT_HEX_VALUE(code[1]);
|
|
|
|
int address = (GBCHEAT_HEX_VALUE(code[2]) << 8) +
|
|
(GBCHEAT_HEX_VALUE(code[4]) << 4) +
|
|
(GBCHEAT_HEX_VALUE(code[5])) +
|
|
((GBCHEAT_HEX_VALUE(code[6]) ^ 0x0f) << 12);
|
|
|
|
if(address >= 0x8000 && address <= 0x9fff)
|
|
return false;
|
|
|
|
if(address >= 0xc000)
|
|
return false;
|
|
|
|
if(code[7] == 0 || code[8] == '0')
|
|
return true;
|
|
|
|
int compare = (GBCHEAT_HEX_VALUE(code[8]) << 4) +
|
|
(GBCHEAT_HEX_VALUE(code[10]));
|
|
compare = compare ^ 0xff;
|
|
compare = (compare >> 2) | ( (compare << 6) & 0xc0);
|
|
compare ^= 0x45;
|
|
|
|
int cloak = (GBCHEAT_HEX_VALUE(code[8])) ^ (GBCHEAT_HEX_VALUE(code[9]));
|
|
|
|
if(cloak >=1 && cloak <= 7)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool gbAddGgCheat(const char *code, const char *desc)
|
|
{
|
|
if(gbCheatNumber > 99) {
|
|
systemMessage(MSG_MAXIMUM_NUMBER_OF_CHEATS,
|
|
N_("Maximum number of cheats reached."));
|
|
return false;
|
|
}
|
|
|
|
if(!gbVerifyGgCode(code)) {
|
|
systemMessage(MSG_INVALID_GAMEGENIE_CODE,
|
|
N_("Invalid GameGenie code: %s"), code);
|
|
return false;
|
|
}
|
|
|
|
int i = gbCheatNumber;
|
|
|
|
size_t len = strlen(code);
|
|
|
|
strcpy(gbCheatList[i].cheatCode, code);
|
|
strcpy(gbCheatList[i].cheatDesc, desc);
|
|
|
|
gbCheatList[i].code = 0x101;
|
|
gbCheatList[i].value = (GBCHEAT_HEX_VALUE(code[0]) << 4) +
|
|
GBCHEAT_HEX_VALUE(code[1]);
|
|
|
|
gbCheatList[i].address = (GBCHEAT_HEX_VALUE(code[2]) << 8) +
|
|
(GBCHEAT_HEX_VALUE(code[4]) << 4) +
|
|
(GBCHEAT_HEX_VALUE(code[5])) +
|
|
((GBCHEAT_HEX_VALUE(code[6]) ^ 0x0f) << 12);
|
|
|
|
gbCheatList[i].compare = 0;
|
|
|
|
if(len != 7 && len != 8) {
|
|
|
|
int compare = (GBCHEAT_HEX_VALUE(code[8]) << 4) +
|
|
(GBCHEAT_HEX_VALUE(code[10]));
|
|
compare = compare ^ 0xff;
|
|
compare = (compare >> 2) | ( (compare << 6) & 0xc0);
|
|
compare ^= 0x45;
|
|
|
|
gbCheatList[i].compare = compare;
|
|
//gbCheatList[i].code = 0;
|
|
gbCheatList[i].code = 0x100; // fix for compare value
|
|
|
|
}
|
|
|
|
|
|
gbCheatList[i].enabled = true;
|
|
|
|
gbCheatMap[gbCheatList[i].address] = true;
|
|
|
|
gbCheatNumber++;
|
|
|
|
return true;
|
|
}
|
|
|
|
void gbCheatRemove(int i)
|
|
{
|
|
if(i < 0 || i >= gbCheatNumber) {
|
|
systemMessage(MSG_INVALID_CHEAT_TO_REMOVE,
|
|
N_("Invalid cheat to remove %d"), i);
|
|
return;
|
|
}
|
|
|
|
if((i+1) < gbCheatNumber) {
|
|
memcpy(&gbCheatList[i], &gbCheatList[i+1], sizeof(gbCheat)*
|
|
(gbCheatNumber-i-1));
|
|
}
|
|
|
|
gbCheatNumber--;
|
|
|
|
gbCheatUpdateMap();
|
|
}
|
|
|
|
void gbCheatRemoveAll()
|
|
{
|
|
gbCheatNumber = 0;
|
|
gbCheatUpdateMap();
|
|
}
|
|
|
|
void gbCheatEnable(int i)
|
|
{
|
|
if(i >=0 && i < gbCheatNumber) {
|
|
if(!gbCheatList[i].enabled) {
|
|
gbCheatList[i].enabled = true;
|
|
gbCheatUpdateMap();
|
|
}
|
|
}
|
|
}
|
|
|
|
void gbCheatDisable(int i)
|
|
{
|
|
if(i >=0 && i < gbCheatNumber) {
|
|
if(gbCheatList[i].enabled) {
|
|
gbCheatList[i].enabled = false;
|
|
gbCheatUpdateMap();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool gbCheatReadGSCodeFile(const char *fileName)
|
|
{
|
|
FILE *file = fopen(fileName, "rb");
|
|
|
|
if(!file) {
|
|
systemMessage(MSG_CANNOT_OPEN_FILE, N_("Cannot open file %s"), fileName);
|
|
return false;
|
|
}
|
|
|
|
fseek(file, 0x18, SEEK_SET);
|
|
int count = 0;
|
|
fread(&count, 1, 2, file);
|
|
int dummy = 0;
|
|
gbCheatRemoveAll();
|
|
char desc[13];
|
|
char code[9];
|
|
int i;
|
|
for(i = 0; i < count; i++) {
|
|
fread(&dummy, 1, 2, file);
|
|
fread(desc, 1, 12, file);
|
|
desc[12] = 0;
|
|
fread(code, 1, 8, file);
|
|
code[8] = 0;
|
|
gbAddGsCheat(code, desc);
|
|
}
|
|
|
|
for(i = 0; i < gbCheatNumber; i++)
|
|
gbCheatDisable(i);
|
|
|
|
fclose(file);
|
|
return true;
|
|
}
|
|
|
|
// Used to emulated GG codes
|
|
u8 gbCheatRead(u16 address)
|
|
{
|
|
if(!cheatsEnabled)
|
|
return gbMemoryMap[address>>12][address & 0xFFF];
|
|
|
|
for(int i = 0; i < gbCheatNumber; i++) {
|
|
if(gbCheatList[i].enabled && gbCheatList[i].address == address) {
|
|
switch(gbCheatList[i].code) {
|
|
case 0x100: // GameGenie support
|
|
if(gbMemoryMap[address>>12][address&0xFFF] == gbCheatList[i].compare)
|
|
return gbCheatList[i].value;
|
|
break;
|
|
case 0x101: // GameGenie 6 digits code support
|
|
return gbCheatList[i].value;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return gbMemoryMap[address>>12][address&0xFFF];
|
|
}
|
|
|
|
|
|
// Used to emulate GS codes.
|
|
void gbCheatWrite(bool reboot)
|
|
{
|
|
if(cheatsEnabled)
|
|
{
|
|
u16 address = 0;
|
|
|
|
if (gbNextCheat >= gbCheatNumber)
|
|
gbNextCheat = 0;
|
|
|
|
for(int i = gbNextCheat; i < gbCheatNumber; i++) {
|
|
if(gbCheatList[i].enabled) {
|
|
address = gbCheatList[i].address;
|
|
if ((!reboot) && (address >= 0x8000) && !((address>=0xA000) && (address<0xC000)))
|
|
{ // These codes are executed one per one, at each Vblank
|
|
switch(gbCheatList[i].code) {
|
|
case 0x01:
|
|
gbWriteMemory(address, gbCheatList[i].value);
|
|
gbNextCheat = i+1;
|
|
return;
|
|
case 0x90:
|
|
case 0x91:
|
|
case 0x92:
|
|
case 0x93:
|
|
case 0x94:
|
|
case 0x95:
|
|
case 0x96:
|
|
case 0x97:
|
|
case 0x98:
|
|
case 0x99:
|
|
case 0x9A:
|
|
case 0x9B:
|
|
case 0x9C:
|
|
case 0x9D:
|
|
case 0x9E:
|
|
case 0x9F:
|
|
int oldbank = gbMemory[0xff70];
|
|
gbWriteMemory(0xff70, gbCheatList[i].code & 0xf);
|
|
gbWriteMemory(address, gbCheatList[i].value);
|
|
gbWriteMemory(0xff70, oldbank);
|
|
gbNextCheat = i+1;
|
|
return;
|
|
}
|
|
}
|
|
else // These codes are only executed when the game is booted
|
|
{
|
|
switch(gbCheatList[i].code & 0xF0) {
|
|
case 0x80:
|
|
gbWriteMemory(0x0000, 0x0A);
|
|
gbWriteMemory(0x4000, gbCheatList[i].value & 0xF);
|
|
gbWriteMemory(address, gbCheatList[i].value);
|
|
gbNextCheat = i+1;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|