/*
    Created on: Sep 23, 2019

	Credits for cheats: Esppiral, S4pph4rad, yzb37859365, Shenmue_Trilogy, Radaron, Virgin KLM, Joel, Zorlon,
		ELOTROLADO.NET, SEGARETRO.ORG, Sakuragi @ emutalk.net
	Copyright 2019 flyinghead

	This file is part of Flycast.

    Flycast 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 of the License, or
    (at your option) any later version.

    Flycast 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 Flycast.  If not, see <https://www.gnu.org/licenses/>.
 */
#include "cheats.h"
#include "hw/sh4/sh4_mem.h"
#include "reios/reios.h"
#include "cfg/cfg.h"
#include "cfg/ini.h"
#include "cfg/option.h"
#include "emulator.h"

const WidescreenCheat CheatManager::widescreen_cheats[] =
{
		{ "T36803N",	nullptr,	{ 0xBC2CC }, { 0xC00 } },			// 102 Dalmatians (USA)
		{ "T36813D 50",	nullptr,	{ 0xBDE8C }, { 0xC00 } },			// 102 Dalmatians (PAL)
		{ "MK-51064",   nullptr,    { 0x39EFF4 }, { 0x43700000 } },		// 18 wheeler (USA)
		{ "MK-5106450", nullptr,    { 0x39EFF4 }, { 0x43700000 } },		// 18 wheeler (PAL)
		{ "HDR-0080",   nullptr,    { 0x6625C0 }, { 0x43700000 } },		// 18 wheeler (JP)
		{ "T-9708N",    nullptr,    { 0x0AD5B0, 0x112E00 }, { 0x4A2671C4, 0x401CCCCD } },	// 4 Wheel Thunder (USA)
		{ "MK-5119050", nullptr,    { 0x104A34 }, { 0x3F52CCCD } },		// 90 Minutes - Sega Championship Football (PAL)
		{ "T40209D 50", nullptr,    { 0x2A8750 }, { 0x3F400000 } },		// AeroWings 2 (PAL)
		{ "T34101M",    nullptr,    { 0x24CF20, 0x24CEEC }, { 0x3FAAAAAB, 0x43F00000 } },	// Animastar (JP)
		{ "MK-51171",   nullptr,    { 0xCA20B8 }, { 0x43700000 } },		// Alien Front Online (USA)
		{ "T2101M",     nullptr,    { 0x13E8B4, 0x13E8E4 }, { 0x43F00000, 0x3F400000 } },	// Berserk (JP)
		{ "T13001D 18", nullptr,    { 0x2DAC44 }, { 0x3F400000 } },		// Blue Stinger (En, De) (PAL)
		{ "xxxxxxxxxx", nullptr,    { 0x2DAB84 }, { 0x3F400000 } },		// Blue Stinger (Fr) (PAL)
		{ "T13001D-05", nullptr,    { 0x2D9C84 }, { 0x3F400000 } },		// Blue Stinger (Es, It?) (PAL)
		{ "T13001N",    nullptr,    { 0x2D9CA4 }, { 0x3F400000 } },		// Blue Stinger (USA)
		{ "HDR-0003",   nullptr,    { 0x2D6D80 }, { 0x3F400000 } },		// Blue Stinger (JP)
		{ "T42903M",    nullptr,    { 0x286E2C, 0x286E5C }, { 0x43F00000, 0x3F400000 } },	// Bomber Hehhe! (JP)
		{ "MK-51065",   nullptr,    { 0x387B84, 0x387BB4 }, { 0x43F00000, 0x3F400000 } },	// Bomberman Online (USA)
		{ "T6801M",     nullptr,    { 0x042F3C }, { 0x4384BC09 } },		// Buggy Heat (JP)
		{ "T46601D 05", nullptr,    { 0xBB3D14 }, { 0x440A7C9A } },		// Cannon Spike (PAL)
		{ "T1215N",     nullptr,    { 0xBB3C74 }, { 0x440A7C9A } },		// Cannon Spike (USA)
		{ "T44901D 50", nullptr,    { 0xB0109C, 0xB010CC }, { 0x43F00000, 0x3F400000 } },	// Carrier (PAL)
		{ "T5701N",     nullptr,    { 0xAFD93C, 0xAFD96C }, { 0x43F00000, 0x3F400000 } },	// Carrier (USA)
		// Capcom vs. SNK Pro (JP)
		// You can see the end of the backgrounds on each side. The HUD is shifted to the right.
		// Code 2 sets HUD and movements of the characters to a 4:3 zone
		{ "T1247M",     nullptr,    { 0x1E8238, 0x3D3658, 0x3D3628 }, { 0x43F00000, 0x43D20000, 0x43700000 } },
		// Capcom vs. SNK 2 (JP)
		// You can see the end of the backgrounds on each side.
		{ "T1249M",     nullptr,    { 0x39BFD8, 0x34E9E8, 0x34EFBC }, { 0x43700000, 0x3F400000, 0x3F400000 } },
		{ "T44902D 50", nullptr,    { 0x1DBFF8 }, { 0x43700000 } },		// Charge ‘n Blast (PAL)
		{ "T4402M",     nullptr,    { 0x1DA9E0 }, { 0x43700000 } },		// Charge ‘n Blast (JP)
		{ "MK-5104950", nullptr,    { 0x2D2D40, 0x2D2D70 }, { 0x3F400000, 0xC2700000 } },	// ChuChu Rocket! (PAL)
		{ "T44903D 50", nullptr,    { 0x315300, 0x315334 }, { 0x43F00000, 0x3FAAAAAB } },	// Coaster Works (PAL)
		// Confidential Mission (PAL) 022F0D58 43700000 - Only works on real Dreamcast
		{ "T36901M",    nullptr,    { 0x1C8A98 }, { 0x3F400000 } },		// Cool Boarders (JP)
		{ "T3106M",     nullptr,    { 0x60B59C }, { 0x3F400000 } },		// Cool Cool Toon (JP)
		{ "HDR-0176",   nullptr,    { 0x240FAC }, { 0x3F400000 } },		// Cosmic Smash (JP)
		{ "MK-51035",   " U      ", { 0x2B08B0 }, { 0x43700000 } },		// Crazy Taxi (USA)
		{ "MK-51035",   "  E     ", { 0x2B3410 }, { 0x43700000 } },		// Crazy Taxi (PAL)
		{ "MK-5113650", nullptr,    { 0x2BFB70 }, { 0x43700000 } },		// Crazy Taxi 2 (PAL)
		{ "MK-51136",   nullptr,    { 0x2BDDD0 }, { 0x43700000 } },		// Crazy Taxi 2 (USA)
//		{ "HDR-0159",   nullptr,    { 0x2FBBD0 }, { 0x43700000 } },		// Crazy Taxi 2 (JP) not working
		{ "T13004N",    nullptr,    { 0x016D94 }, { 0x44234E73 } },		// Cyber Troopers - Virtual On - Oratorio Tangram (USA)
		// D2 (USA)
		{ "MK-51036",   nullptr,    { 0x4B5CF4, 0x4B5CC4, 0x3E92A0, 0x3E92A8, 0x3E92C0, 0x3E92C8 },
				{ 0x3F400000, 0x43F00000, 0, 0, 0, 0 } },
		// D2 (JP)
		{ "T30006M",    nullptr,    { 0x4CF42C, 0x4CF45C, 0x3E1A36, 0x3E1A34, 0x3E1A3C, 0x3E1A54, 0x3E1A5C },
				{ 0x43F00000, 0x3F400000, 0x08010000, 0, 0, 0, 0 } },
		{ "MK-5103750", nullptr,    { 0x1FE270 }, { 0x43700000 } },		// Daytona USA (PAL)
		{ "MK-51037",   nullptr,    { 0x1FC6D0 }, { 0x43700000 } },		// Daytona USA (USA)
		{ "T9501N-50",  nullptr,    { 0x9821D4 }, { 0x3F400000 } },		// Deadly Skies (PAL)
		{ "T8116D  50", nullptr,    { 0x2E5530 }, { 0x43700000 } },		// Dead or Alive 2 (PAL)
		{ "T3601N",     nullptr,    { 0x2F0670 }, { 0x43700000 } },		// Dead or Alive 2 (USA)
		{ "T3602M",     nullptr,    { 0x2FF798 }, { 0x43700000 } },		// Dead or Alive 2 (JP)
		{ "T3601M",     nullptr,    { 0x2FBBD0 }, { 0x43700000 } },		// Dead or Alive 2: Limited Edition (JP)
		{ "T2401N",     nullptr,    { 0x8BD5B4, 0x8BD5E4 }, { 0x43F00000, 0x3F400000 } },	// Death Crimson OX (USA)
		{ "T23201M",    nullptr,    { 0x819F44, 0x819F74 }, { 0x43F00000, 0x3F400000 } },	// Death Crimson 2 (JP)
		{ "T17714D50",  nullptr,    { 0x0D2ED0, 0x0D2ED4 }, { 0x3FAAAAAB, 0x3F400000 } },	// Donald Duck: Quack Attack (PAL) (Code 1 corrects the HUD)
		{ "T12503D-50", nullptr,    { 0x49CB24 }, { 0x3F0CCCCD } },		// Dragons Blood (PAL)
		{ "T17716D 50", nullptr,    { 0xF97F40, 0x08DCF8 }, { 0x00000168, 0 } },		// Dragonriders: Chronicles of Pern (PAL)
		{ "T17720N",    nullptr,    { 0xF97F40, 0x08DCF4 }, { 0x00000168, 0 } },		// Dragonriders: Chronicles of Pern (USA) (Code 1 removes black bars)
		{ "MK-5101350", nullptr,    { 0x4FCBC0 }, { 0x43700000 } },		// Dynamite Cop  (PAL)
		{ "MK-51013",   nullptr,    { 0x4FCBC0 }, { 0x43700000 } },		// Dynamite Cop  (USA)
		{ "HDR-0020",   nullptr,    { 0x4FA848 }, { 0x43700000 } },		// Dynamite Deka 2  (JP)
		// Draconus: Cult of the Wyrm (USA)
		// Code 1-2 increases drawing distance
		{ "T40203N",    nullptr,    { 0x49D7F4, 0x49D8CC, 0x49D6F8 }, { 0x3F07C3BB, 0x447A0000, 0x3FAAAAAB } },
		// Ecco the Dolphin: Defender of the Future (PAL)
		{ "MK-5103350", nullptr,    { 0x275418, 0x040E68, 0x040D1C }, { 0x49D9A5DA, 0x3F100000, 0x3F100000 } },
		{ "T17705D 05", nullptr,    { 0x304870 }, { 0x3F400000 } },		// Evolution - The World of Sacred Device (PAL)
		{ "T45005D 50", nullptr,    { 0x36CE5C, 0x36CE8C }, { 0x43F00000, 0x3F400000 } },	// Evolution 2 - Far Off Promise (PAL)
		{ "T1711N",     nullptr,    { 0x36C76C, 0x36C73C }, { 0x3F400000, 0x43F00000 } },	// Evolution 2 - Far Off Promise (USA)
		{ "T8118D  50", nullptr,    { 0x2C6B7C }, { 0x00004000 } },		// Ferrari F355 Challenge (PAL) vga mode only
		{ "HDR-0100",   nullptr,    { 0x3235D4 }, { 0x00004000 } },		// Ferrari F355 Challenge (JP) vga mode only
		{ "MK-5115450", nullptr,    { 0x3D3B10 }, { 0x43700000 } },		// Fighting Vipers 2 (PAL)
		{ "HDR-0133",   nullptr,    { 0x3D3AF0 }, { 0x43700000 } },		// Fighting Vipers 2 (JP)
		{ "T18805M",	nullptr,	{ 0x1A39C0 }, { 0x3F400000 } },		// Fire Pro Wrestling D (JP)
		{ "MK-51114",   nullptr,    { 0x132DD8, 0xA26CA8, 0xA26738, 0xA275B8, 0xA26AD8, 0xA26908 },
				{ 0x3F400000, 0x3F400000, 0x3F400000, 0x3F400000, 0x3F400000, 0x3F400000 } },	// Floigan Bros. Ep. 1 (PAL)
		{ "T34201M",    nullptr,    { 0x586290, 0x586260 }, { 0x3F400000, 0x43F00000 } },	// Frame Gride (JP)
		{ "T-8113D-50", nullptr,    { 0x55A354 }, { 0x3FAAAAAB } },		// Fur Fighters (PAL)
		{ "T-8107N",    nullptr,    { 0x55A374 }, { 0x3FAAAAAB } },		// Fur Fighters (USA)
		{ "T9707D  50", nullptr,    { 0x2E7CD0, 0x2E7D04, 0x2E7D10, 0x2E7D0C },
				{ 0x43BEAE39, 0x3DCCCCCD , 0x44A00000, 0xC4200000 } },		// Gauntlet Legends (PAL)
		{ "HDR-0004",   nullptr,    { 0x364A64 }, { 0x43BA0000 } },		// Godzilla Generations (JP)
		{ "HDR-0047",   nullptr,    { 0x7C3F24, 0x7C3F54 }, { 0x43F00000, 0x3F400000 } },	// Godzilla Generations Maximum Impact (JP)
		{ "T41501M",    nullptr,    { 0x282604, 0x282634 }, { 0x43F00000, 0x3F400000 } },	// Golem no Maigo (aka The Lost Golem) (JP)
		{ "T1219M",     nullptr,    { 0xBC3C94 }, { 0x440A7C9A } },		// Gun Spike (JP)
		{ "T13301N",    nullptr,    { 0x88E780 }, { 0x3F400000 } },		// Gundam Side Story (USA)
//		{ "T11001N",    nullptr,    { 0xC3D6BC, 0xD204A8, 0xD32FC8, 0xC7CF84, 0xD20548 },
//				{ 0x00363031, 0x00000356, 0x00000280, 0x00000280, 0x00000280 } },		// Half-Life. Not working
		{ "MK-5104150", nullptr,    { 0x23FCC4 }, { 0x44558000 } },		// Headhunter (PAL)
		// gun coords issue
		//{ "MK-5100250", nullptr,    { 0x4C6708 }, { 0x43700000 } },		// House of the Dead 2, The (PAL)
		//{ "MK-51002",   nullptr,    { 0x4C6088 }, { 0x43700000 } },		// House of the Dead 2, The (USA)
		//not working { "T38706M",    nullptr,    { 0xC0CFA0 }, { 0x3F400000 } },		// Ikaruga (JP)
		{ "T46001N",    nullptr,    { 0x1C8A98 }, { 0x3F400000 } },		// Illbleed (USA)
		{ "T44904D 50", nullptr,    { 0x18C15C, 0x18C18C }, { 0x43F00000, 0x3F400000 } },	// Iron Aces (PAL)
		{ "MK-51058",   nullptr,    { 0x32E0FC, 0x32E12C }, { 0x43F00000, 0x3F400000 } },	// Jet Grind Radio (USA)
		{ "MK-5105850", nullptr,    { 0x32F9EC, 0x32F9BC }, { 0x3F400000, 0x43F00000 } },	// Jet Set Radio (PAL)
		{ "HDR-0078",   nullptr,    { 0x327A8C, 0x327A5C }, { 0x3F400000, 0x43F00000 } },	// Jet Set Radio (De La) (JP)
		{ "T22902D 50", nullptr,    { 0x278508 }, { 0x43700000 } },		// Kao The Kangaroo (PAL)
		{ "T22903N",    nullptr,    { 0x2780A8 }, { 0x43700000 } },		// Kao The Kangaroo (USA)
		{ "T47803M",    nullptr,    { 0x0FDFAC }, { 0x3F400000 } },		// Karous (JP)
//		{ "T41901N",    nullptr,    { 0x53F580, 0xEFB748, 0xEFB750 }, { 0xC4200000, 0x43A00000, 0x43200000 } },	// KISS Psycho Circus – The Nightmare Child (USA)
		{ "T2501M",     nullptr,    { 0x24A878, 0x24A8A8 }, { 0x43F00000, 0x3F400000 } },	// Langrisser Millenium (JP)
		{ "T15111D 50", nullptr,    { 0x29B90C }, { 0x3F400000 } },		// Le Mans 24 Hours (PAL)
		{ "T15116N",    nullptr,    { 0x2198EC }, { 0x3F400000 } },		// Looney Tunes: Space Race (USA)
		{ "MK-5105050", nullptr,    { 0x33818C }, { 0x3FA66666 } },		// Maken X (PAL)
		{ "MK-51050",   nullptr,    { 0x30CB4C }, { 0x3F400000 } },		// Maken X (USA)
		{ "T1212N",     nullptr,    { 0x2D6B18, 0x268390, 0x268ED8, 0x268934, 0x26947C, 0x269A20, 0x269FC4 },
				{ 0x43700000, 0x3F400000, 0x3F400000, 0x3F400000, 0x3F400000, 0x3F400000, 0x3F400000 } },		// Marvel vs. Capcom 2 (USA)
		{ "T1215M",     nullptr,    { 0x2FE2C0, 0x28FB38, 0x290680, 0x2900DC, 0x290C24, 0x2911C8, 0x29176C },
				{ 0x43700000, 0x3F400000, 0x3F400000, 0x3F400000, 0x3F400000, 0x3F400000, 0x3F400000 } },		// Marvel vs. Capcom 2 (JP)
		{ "T41402N",    nullptr,    { 0x551B80 }, { 0x3F400000 } },		// Max Steel - Covert Missions (USA)
		{ "MK-5102250", "V1.001",   { 0x107FDC, 0x11253C }, { 0x3F99999A, 0x3F900000 } },	// Metropolis Street Racer (v1.001) (PAL)
		{ "MK-5102250", "V1.009",   { 0x106B5C, 0x1111F4 }, { 0x3F99999A, 0x3F900000 } },	// Metropolis Street Racer (v1.009) (PAL)
		{ "MK-51012",   nullptr,    { 0x10A01C, 0x1146FC }, { 0x3F99999A, 0x3F900000 } },	// Metropolis Street Racer (USA)
		{ "T0000M", "MILLENNIUM RACER - Y2K FIGHTERS                                                                                                 ",
									{ 0x1CAEAC, 0x1CAEDC }, { 0x43F00000, 0x3F400000 } },	// Millenium Racer Y2K Fighters
		{ "T1221M",     nullptr,    { 0x426B74 }, { 0x43F00000 } },		// Moero! Justice Gauken (JP)
		{ "T9701D",     nullptr,    { 0x31290C }, { 0x3F400000 } },		// Mortal Kombat Gold (PAL)
		{ "T-9701N",    nullptr,    { 0x337B8C }, { 0x3FA66666 } },		// Mortal Kombat Gold (USA)
		{ "T7020D",     nullptr,    { 0x1A5D40, 0x1A5D18, 0x1A5D38, 0x1A5D34, 0x1A5D30, 0x1A5D3C, 0x1A5D0C, 0x1A5D08, 0x1ACB78 },
				{ 0x3F59999A, 0x44084000, 0x3FB1EB85, 0x3FAAAAAB, 0x3F800000, 0x3FA66666, 0xC31B0000, 0x42700000, 0x44160000 } },		// Mr Driller (PAL)
		{ "T7604M",     nullptr,    { 0x43BDA4, 0x43BDD8, 0x4319E8, 0x431A08, 0x431A28, 0x431A48 },
				{ 0x43700000, 0x3F9AF5C2, 0x42200000, 0x42200000, 0x438C0000, 0x438C0000 } },	// Nanatsu no Hikan - Senritsu no Bishou (JP)
		{ "HDR-0079",   nullptr,    { 0x353744, 0x353774, 0x353778 },
				{ 0x43F00000, 0x3F400000, 0x3F800000 } },					// Napple Tale: Arsia in Daydream (JP)
//crash		{ "MK-51178",   nullptr,    { 0x23AF00, 0x23B160, 0x144D40, 0x2105B4, 0x705B40 },
//				{ 0xBFAAAAAB, 0xBFAAAAAB, 0xBFAAAAAB, 0xBFAAAAAB, 0x44558000 } },	// NBA 2K2
		{ "T9504M",     nullptr,    { 0xCDE848, 0xCDE844 }, { 0x3F400000, 0x3FA00000 } },	// Nightmare Creatures II (USA)
		{ "T-9502D-50", nullptr,    { 0xBDE9B0, 0xBDE9C4 }, { 0x3F400000, 0x3FA00000 } },	// Nightmare Creatures II (PAL)
		{ "MK-5110250", nullptr,    { 0x87B5A4 }, { 0x43700000 } },		// Outtrigger (PAL)
		{ "HDR-0118",   nullptr,    { 0x83E284 }, { 0x43700000 } },		// Outtrigger (JP)
		{ "T15103D 50", nullptr,    { 0x1EEE78 }, { 0x3F400000 } },		// PenPen (PAL)
		{ "T17001M",    nullptr,    { 0x1C3828 }, { 0x3F400000 } },		// PenPen TriIcelon (JP)
		{ "MK-5110050", nullptr,    { 0x548E04, 0x0923C0 }, { 0x43E80000, 0x3F966666 } },	// Phantasy Star Online (PAL) TODO
		{ "MK-51100",   nullptr,    { 0x548E84, 0x0925A4 }, { 0x43D20000, 0x3FA66666 } },	// Phantasy Star Online (USA) TODO
		{ "HDR-0129",   nullptr,    { 0x57E344, 0x091794 }, { 0x43D20000, 0x3FA66666 } },	// Phantasy Star Online (JP) TODO
		{ "MK-5119350", nullptr,    { 0x57552C, 0x0A2DD4 }, { 0x43F00000, 0x3F911111 } },	// Phantasy Star Online Ver. 2 (PAL) TODO
		{ "MK-51193",   nullptr,    { 0x0A3138, 0x58C8CC }, { 0x3F911111, 0x43F00000 } },	// Phantasy Star Online Ver. 2 (USA) TODO
		{ "T1207N",     nullptr,    { 0x7D463C }, { 0x3F400000 } },		// Plasma Sword - Nightmare of Bilstein (USA)
		{ "T36801D 50", nullptr,    { 0x7FD3C8 }, { 0x43700000 } },		// Power Stone (PAL)
		{ "T1201N",     nullptr,    { 0x7FCEE8 }, { 0x43700000 } },		// Power Stone (USA)
		{ "T36812D 50", nullptr,    { 0x868BA8 }, { 0x43700000 } },		// Power Stone 2 (PAL)
		{ "T1211N",     nullptr,    { 0x8689A8 }, { 0x43700000 } },		// Power Stone 2 (USA)
		{ "T1218M",     nullptr,    { 0x849AA0 }, { 0x43700000 } },		// Power Stone 2 (JP)
		{ "T7022D  50", nullptr,    { 0x33414C }, { 0x43F00000 } },		// Project Justice (PAL)
		{ "T1219N",     nullptr,    { 0x33374C }, { 0x43F00000 } },		// Project Justice (USA)
		{ "MK-51162",   nullptr,    { 0x204308 }, { 0x3FE38E39 } },		// Propeller Arena
		{ "T-8106D-50", nullptr,    { 0x7DA39C }, { 0x3F400000 } },		// Psychic Force 2012 (PAL)
		{ "T9901M",     nullptr,    { 0x1AD848 }, { 0x3F400000 } },		// Rainbow Cotton (JP)
		{ "T9711D  50", nullptr,    { 0x1574D8 }, { 0x43700000 } },		// Ready 2 Rumble Boxing Round 2 (PAL)
		{ "T7012D  05", nullptr,    { 0x4FF93C, 0x4FF96C }, { 0x43F00000, 0x3F400000 } },	// Record of Lodoss War (En) (PAL)
		{ "xxxxxxxxxx", nullptr,    { 0x4FF25C, 0x4FF28C }, { 0x43F00000, 0x3F400000 } },	// Record of Lodoss War (De) (PAL)
		{ "T7012D  09", nullptr,    { 0x50499C, 0x5049CC }, { 0x43F00000, 0x3F400000 } },	// Record of Lodoss War (Fr) (PAL)
		{ "MK-5102151", nullptr,    { 0x3511A0 }, { 0x3FC58577 } },		// Red Dog (PAL)
		{ "HDR-0074",   nullptr,    { 0x1FF60C, 0x1FF610, 0x1FF5DC }, { 0x3F400000, 0x3F800000, 0x43DC0000 } },	//Rent a Hero n°1
		// Resident Evil: Code Veronica (De) (PAL)
		// Code 1-4 removes the black bars on top and bottom in FMV
		{ "xxxxxxxxxx", nullptr,    { 0x32A380, 0x383E18, 0x383E38, 0x383E58, 0x383E78 },
				{ 0x3F400000, 0x43F00000, 0, 0x43F00000, 0 } },
		// Resident Evil: Code Veronica (Fr) (PAL)
		// Code 1-4 removes the black bars on top and bottom in FMV
		{ "T36806D 09", nullptr,    { 0x32A380, 0x383E18, 0x383E38, 0x383E58, 0x383E78 },
				{ 0x3F400000, 0x43F00000, 0, 0x43F00000, 0 } },
		// Resident Evil: Code Veronica (Sp) (PAL)
		{ "xxxxxxxxxx", nullptr,    { 0x32A3A0 /*, 0x383E18, 0x383E38, 0x383E58, 0x383E78 */ },
				{ 0x3F400000, 0x43F00000, 0, 0x43F00000, 0 } },
		// Resident Evil: Code Veronica (USA)
		// Code 1-4 removes the black bars on top and bottom in FMV
		{ "T1204N", "RESIDENT EVIL CODE VERONICA                                                                                                     ",
				{ 0x329E40, 0x3838D8, 0x3838F8, 0x383918, 0x383938 },
				{ 0x3F400000, 0x43F00000, 0, 0x43F00000, 0 } },
		// Biohazard: Code Veronica: Kanzenban (JP)
		{ "T1240M", "BIOHAZARD CODE VERONICA PLUS                                                                                                    ",
				{ 0x344AE1 }, { 0x003F6000 } },
		// Resident Evil: Code Veronica X Kanzenban (Eng. translation)
		{ "T1204N", "RESIDENT EVIL CODE VERONICA X                                                                                                   ",
				{ 0x344AE1 }, { 0x003F6000 } },
		{ "T8107D  50", nullptr,    { 0x0464FC, 0x046210 }, { 0x3A888889, 0x44200000 } },	// Re-Volt (PAL) Code 1 is a render fix
		{ "MK-5119250", nullptr,    { 0x0C5EB4 }, { 0x3A888889 } },		// Rez (PAL)
		{ "T15122N",    nullptr,    { 0x8E7A80, 0x8E7AB4 }, { 0x43E10000, 0x3FAAAAAB } },	// Ring, The - Terror's Realm (USA)
		{ "MK-51010",   nullptr,    { 0x1A4D2C, 0x1A4D5C }, { 0x43F00000, 0x3F400000 } },	// Rippin' Riders (USA)
		{ "HDR-0044",   nullptr,    { 0x298A94, 0x298AC4 }, { 0x43F00000, 0x3F400000 } },	// Roommania #203 (JP)
		{ "MK-5109250", nullptr,    { 0x29E6C4 }, { 0x3F400000 } },		// Samba De Amigo (PAL)
		{ "T41301N",    nullptr,    { 0x77A178 }, { 0x3F400000 } },		// Seventh Cross Evolution (USA)
		{ "T8104D  58", nullptr,    { 0x2C03F8 }, { 0x44558000 } },		// Shadow Man (PAL)
		{ "T-8106N",    nullptr,    { 0x2C03F4 }, { 0x3F400000 } },		// Shadow Man (USA)
		{ "MK-51048",   nullptr,    { 0x4AA4DC, 0x2B4E30 }, { 0x3F400000, 0x3F400000 } },	// Seaman (USA)
		{ "MK-5105350", nullptr,    { 0x5D613C }, { 0x3F400000 } },		// Sega GT (PAL)
		{ "MK-51096",   nullptr,    { 0x495050 }, { 0x43700000 } },		// Sega Marine Fishing (USA)
//		{ "MK-51019",   nullptr,    { 0xB83A48 }, { 0x3F400000 } },		// Sega Rally 2 (USA) not working?
//		{ "HDR0010",    nullptr,    { 0xBD9BA0 }, { 0x3F400000 } },		// Sega Rally 2 (JP) not working?
		{ "HDR-0151",   nullptr,    { 0xAF57DC, 0xAF580C, 0x2122A0 },
				{ 0x43F00000, 0x3F400000, 0x3F400000 } },					// SGGG – Segagaga (JP)
		{ "MK-5105950", nullptr,    { 0x231EF8, 0x1EF370 }, { 0x43800000, 0x7C1EF400 } },	// Shenmue (PAL) code 1 reduces clipping
		{ "MK-51059",   nullptr,    { 0x230250 }, { 0x43800000 }, { 0x43a00000 } },		// Shenmue (USA) Clipping
		{ "HDR-0016",   nullptr,    { 0x22E8A0, 0x1EBE70 }, { 0x43800000, 0x7C1EBF00 } },	// Shenmue (JP) code 1 reduces clipping
		{ "MK-5118450", nullptr,    { 0x31186C }, { 0x43800000 } },		// Shenmue II (PAL) 01160FF4 0000E100 for black bars in cutscenes
		// Shenmue II (PAL) Alternative code without clipping or black bars. Might be demanding on real hardware.
		// 02311880 C3A00000, 0227E198 35AA359E, 01160FF4 0000E100
		{ "HDR-0164",   nullptr,    { 0x30D67C }, { 0x43700000 } },		// Shenmue II (JP) Clipping
		{ "T9505D",     nullptr,    { 0xD1FB14, 0x096A4C }, { 0x3F400000, 0x3FAAAAAB } },	// Silent Scope (PAL) Choose 60Hz in game options
		{ "MK-51052",   "  E     ", { 0x502A84 }, { 0x3F400000 } },		// Skies of Arcadia (PAL)
		{ "MK-51052",   " U      ", { 0x599158 }, { 0x3F400000 } },		// Skies of Arcadia (USA)
		{ "T15104D 50", nullptr,    { 0x17EF68 }, { 0x43F00010 } },		// Slave Zero (PAL) Widescreen, but a bit zoomed in
		{ "MK-5101050", nullptr,    { 0x1A50EC, 0x1A511C }, { 0x43F00000, 0x3F400000 } },	// Snow Surfers (PAL)
		{ "MK-5100050", nullptr,    { 0x88F528, 0x88F55C }, { 0x43F00000, 0x3FA66666 } },	// Sonic Adventure (PAL)
		{ "MK-51000",   nullptr,    { 0x88F5E8, 0x88F61C }, { 0x43F00000, 0x3FAAAAAB } },	// Sonic Adventure (USA)
		{ "MK-51117",   nullptr,    { 0x28DEF8, 0x28DF28 }, { 0x43F00000, 0x3f400000 } },	// Sonic Adventure 2 (USA)
		{ "HDR-0165",   nullptr,    { 0x28DF28, 0x28DEF8 }, { 0x3F400000, 0x43F00000 } },	// Sonic Adventure 2 (JP)
		{ "MK-51060",   nullptr,    { 0x112A2C }, { 0x3F400000 } },		// Sonic Shuffle (US)
		{ "MK-5106050", nullptr,    { 0x110B4C }, { 0x3F400000 } },		// Sonic Shuffle (PAL)
		{ "T9103M",     nullptr,    { 0x25C714, 0x25C744 }, { 0x43F00000, 0x3F400000 } },	// Sorcerian - Shichisei Mahou no Shito
		{ "T1401D  50", nullptr,    { 0x2D6138 }, { 0x3F400000 } },		// Soul Calibur (PAL)
		{ "T1401N",     nullptr,    { 0x266C28 }, { 0x3F400000 } },		// Soul Calibur (USA)
		{ "T36802N",    "  E     ", { 0x129FA0, 0x12A9BC, 0x1C9FDC },
				{ 0x3EF55555, 0x3EF55555, 0x000000F0 } },					// Soul Reaver (PAL) Code 2 is a Render Fix
		{ "HDR-0190",   nullptr,    { 0x14D3E0 }, { 0x3F400000 } },		// Space Channel 5 Part 2 (JP)
		{ "T1216M",     nullptr, { 0x017C38, 0x17F00 }, { 0x3A99999A, 0x3A99999A } }, // Spawn - In the Demon's Hand v1.003 (JP)
		{ "T1216N",     nullptr, { 0x017C58, 0x17F20 }, { 0x3A99999A, 0x3A99999A } }, // Spawn - In the Demon's Hand v1.000 (US)
		{ "T36816D 50", nullptr, { 0x017C78, 0x17F40 }, { 0x3A99999A, 0x3A99999A } }, // Spawn - In the Demon's Hand v1.000 (EU)
		// Star Wars Episode I Racer (USA)
		// Code 1-4 removes the black bars on top and bottom in FMV
		{ "T23001N",    nullptr,    { 0x17AE20, 0x29A96C, 0x29A98C, 0x29A9AC, 0x29A9CC },
				{ 0x3F400000, 0x42900000, 0x42900000, 0x43CE0000, 0x43CE0000 } },
		{ "T40206N",    nullptr,    { 0x43296C }, { 0x3F400000 } },	// Super Magnetic Neo (US)
		{ "T40206D 50", nullptr,    { 0x43E34C }, { 0x3F400000 } },	// Super Magnetic Neo (EU)
//		{ "T7014D  50", nullptr,    { 0xE2B234 }, { 0x3F800000 } },		// Super Runabout (PAL) doesn't work?
		{ "T17721D 50", nullptr,    { 0x45CED4 }, { 0x3F400000 } },		// Surf Rocket Racers (PAL) alt: 021EBF40 3F400000
		{ "T17703D 50", nullptr,    { 0xCD8950 }, { 0x3F111111 } },		// Suzuki Alstare Extreme Racing
		{ "T36807D 05", nullptr,    { 0x140F74, 0x140FA4 }, { 0x43FA0000, 0x3F400000 } },	// Sword of Bersek (PAL)
		{ "T-36805N",   nullptr,    { 0x13F1C4, 0x13F194 }, { 0x3F400000, 0x43F00000 } },	// Sword of Bersek (USA)
		{ "MK-51186",   nullptr,    { 0x4A19B0 }, { 0x43700000 } },		// Tennis 2K2 (USA)
		{ "T15123N",    nullptr,    { 0x29B7BC }, { 0x3F400000 } },		// Test Drive Le Mans (USA) doesn't work?
		{ "T20801M",    nullptr,    { 0x1AAC80, 0x1AACB0 }, { 0x43F00000, 0x3F400000 } },	// Tetris 4D (JP)
		{ "MK-5101153", nullptr,    { 0x14EFA8, 0x14EFD8 }, { 0x43F00000, 0x3F400000 } },	// Timestalkers (PAL)
		{ "T7009D50",   nullptr,    { 0x39173C }, { 0x3F400000 } },		// Tech Romancer (PAL)
		{ "T35402M",    nullptr,    { 0x315370, 0x3153A0 }, { 0x43F00000, 0x3F400000 } },	// Tokyo Bus Guide (JP) doesn't work?
		{ "T40201D 50", nullptr,    { 0x1D9F10 }, { 0x3F400000 } },		// Tokyo Highway Challenge (PAL)
		{ "T40210D 50", nullptr,    { 0x21E4F8 }, { 0x43700000 } },		// Tokyo Highway Challenge 2 (PAL)
		{ "xxxxxxxxxx", nullptr,    { 0x21DEF8 }, { 0x3F400000 } },		// Tokyo Street Racer 2 (USA)
//		{ "T36804D05",  nullptr,    { 0xB75E28 }, { 0x3EC00000 } },		// Tomb Raider: The Last Revelation (UK) (PAL) clipping, use hex patch instead
		{ "T13008D 05", nullptr,    { 0x1D7C20 }, { 0x3FA66666 } },		// Tony Hawk's Pro Skater 2 (PAL)
		{ "T13006N",    nullptr,    { 0x1D77A0 }, { 0x3FA66666 } },		// Tony Hawk's Pro Skater 2 (USA)
		{ "MK-5102050", nullptr,    { 0x0D592C }, { 0x3FD00000 } },		// Toy Commander (PAL)
		{ "xxxxxxxxxx", nullptr,    { 0x469FCC, 0x469FFC }, { 0x44700000, 0x3F400000 } },	// Toyota Doricatch Series: Land Cruiser 100/Cygnus
		{ "MK-5109505", nullptr,    { 0x1B2718 }, { 0x3F400000 } },		// UEFA Dream Soccer (PAL)
		{ "T40203D 50", nullptr,    { 0x1D74E8, 0x1D7518 }, { 0x43F00000, 0x3F400000 } },	// Ultimate Fighting Championship (PAL)
		{ "T40204N",    nullptr,    { 0x1A9684 }, { 0x3F400000 } },		// Ultimate Fighting Championship (USA) problems with cam in game
		{ "T-8110D-50", nullptr,    { 0x0C6B90, 0x0C6B94 }, { 0x43F00000, 0x43870000 } },	// Vanishing Point (PAL)
//		{ "MK-5109450", nullptr,    { 0x244134, 0x7A73B8, 0x7830D8, 0x7A74B8, 0x783138, 0x7A85A0, 0x6C7928, 0x6C7930, 0x6C7948, 0x6C7950 },
//crash				{ 0x43F00000, 0x3F9C932E, 0x3F9C932E, 0x3F9C932E, 0x3F9C932E, 0x3F400000, 0, 0, 0, 0 } },	// Virtua Athlete 2K (PAL)
		{ "MK-51001",	nullptr,	{ 0x19D518 }, { 0x43700000 } },		// Virtua Fighter 3 TB (NTSC)
		{ "MK-5100150", nullptr,    { 0x19D718 }, { 0x43700000 } },		// Virtua Fighter 3 TB (PAL)
		{ "HDR-0002",   nullptr,    { 0x199FB0 }, { 0x43700000 } },		// Virtua Fighter 3 TB (JP)
		{ "MK-5105450", nullptr,    { 0x456378 }, { 0x43700000 } },		// Virtua Tennis (v1.001) (PAL)
		{ "MK-51054",   nullptr,    { 0x450A90 }, { 0x43700000 } },		// Virtua Tennis (USA)
		{ "MK-5118650", nullptr,    { 0x4A4A20 }, { 0x43700000 } },		// Virtua Tennis 2 (PAL)
//		{ "T0000",      nullptr,    { 0x3A514C, 0x3A6170 }, { 0x3F400000, 0x00000356 } },	// Volgarr the Viking. Not working
		{ "xxxxxxxxxx", nullptr,    { 0x20BB68, 0x1ACBD0, 0x1B9ADC },	// Code 1 reduces clipping.	Code 2 fixes the clock.
				{ 0x43700000, 0x7C1ACC60, 0x3F400000 } },					// What's Shenmue (JP)
		{ "T40504D 50", nullptr,    { 0x75281C }, { 0x3F400000 } },		// Wetrix+ (PAL) not working?
		{ "MK-51152",   nullptr,    { 0x014E90 }, { 0x43700000 } },		// World Series Baseball 2K2 (USA)
		{ "T20401M",    nullptr,    { 0x323CB0, 0x1ACBD0, 0x1B9ADC },	// Code 1 reduces clipping. Code 2 fixes the HUD.
				{ 0x43700000, 0x1ACC60, 0x3F400000 } },						// Zero Gunner 2 (JP)
		{ "MK-5103850", nullptr,    { 0x9484E8 }, { 0x43700000 } },		// Zombie Revenge (PAL)
		{ "MK-51038",   nullptr,    { 0x948058 }, { 0x43700000 } },		// Zombie Revenge (USA)
		{ "HDR-0026",   nullptr,    { 0x948B18 }, { 0x43700000 } },		// Zombie Revenge (JP)
		{ "T43301M",    nullptr,    { 0x4B0218 }, { 0x3F400000 } },		// Zusar Vasar (JP)

		{ nullptr },
};
const WidescreenCheat CheatManager::naomi_widescreen_cheats[] =
{
		{ "KNIGHTS OF VALOUR  THE 7 SPIRITS", nullptr, { 0x475B70, 0x475B40 }, { 0x3F400000, 0x43F00000 } },
		{ "Dolphin Blue", nullptr, { 0x3F2E2C, 0x3F2190, 0x3F2E6C, 0x3F215C },
				{ 0x43B90000, 0x3FAA9FBE, 0x43B90000, 0x43F00000 } },
		{ "METAL SLUG 6", nullptr, { 0xE93478, 0xE9347C }, { 0x3F400000, 0x3F8872B0 } },
		{ "TOY FIGHTER", nullptr, { 0x133E58 }, { 0x43700000 } },
		{ "LUPIN THE THIRD  -THE SHOOTING-", nullptr, { 0x045490 }, { 0x3F400000 } },
		{ "VF4 FINAL TUNED JAPAN", nullptr, { 0x02B834, 0x0AFB90 }, { 0x3FE38E39, 0x3FE38E39 } },
		{ "DYNAMITE DEKA EX", nullptr, { 0x0E3598, 0x0C8E84 }, { 0x3FE38E38, 0x3FE38E38 } },
		{ "DEAD OR ALIVE 2", "doa2m", { 0x085B5C, 0x086AE8 }, { 0x3FE38E39, 0x3FE38E39 } },
		{ "SLASHOUT JAPAN VERSION", nullptr, { 0x3DDBE4 }, { 0x43CFDC86 } },
		{ "SPIKERS BATTLE JAPAN VERSION", nullptr, { 0x3626C4 }, { 0x43A551B0 } },
		{ "  18WHEELER", "18wheelr", { 0x5C64A8 }, { 0x43700000 } },
		{ "BEACH SPIKERS JAPAN", nullptr, { 0x065A7C }, { 0x44558000 } },
		{ "GUN SPIKE", nullptr, { 0x21A94 }, { 0x3FE38E39 } },
		{ "INITIAL D", "initd", { 0x155434 }, { 0x3FE38E39 } },
		{ "INITIAL D", "initdexp", { 0x159674 }, { 0x3FE38E39 } },
		{ "INITIAL D", "initdexpo", { 0x159634 }, { 0x3FE38E39 } },
		{ "INITIAL D", "initdo", { 0x14F5F4 }, { 0x3FE38E39 } },
		{ "INITIAL D Ver.2", "initdv2e", { 0x1B4F74 }, { 0x3FE38E39 } },
		{ "INITIAL D Ver.2", "initdv2j", { 0x1B4F34 }, { 0x3FE38E39 } },
		{ "INITIAL D Ver.2", "initdv2jo", { 0x1AD1F4 }, { 0x3FE38E39 } },
		{ "INITIAL D Ver.3", "initdv3e", { 0x1D0B34 }, { 0x3FE38E39 } },
		{ "INITIAL D Ver.3", "initdv3j", { 0x1D7C74 }, { 0x3FE38E39 } },
		{ "INITIAL D Ver.3", "initdv3jb", { 0x1D7774 }, { 0x3FE38E39 } },
		{ "AIRLINE PILOTS IN JAPAN", "alpilotj", { 0x1D62550 }, { 0x43700000 } },
		{ "MONKEY BALL JAPAN VERSION", nullptr, { 0x345B4, 0x45244, 0x454CC }, { 0x3FE38E39, 0x3FE38E39, 0x3FE38E39 } },
		{ "ZOMBIE REVENGE IN JAPAN", "zombrvn", { 0x7A4808 }, { 0x43700000 } },
		{ "ZOMBIE REVENGE IN JAPAN", "zombrvno", { 0x7A2E50 }, { 0x43700000 } },
		{ " BIOHAZARD  GUN SURVIVOR2", "gunsur2", { 0x42EE80 }, { 0x3F400000 } },
		{ " BIOHAZARD  GUN SURVIVOR2", "gunsur2j", { 0x42EBE0 }, { 0x3F400000 } },
		{ "SPAWN JAPAN", nullptr, { 0x02DEF8, 0x02E1C0 }, { 0x3A99999A, 0x3A99999A } },
		// gun coords problem with these 2 cheats
		// { " CONFIDENTIAL MISSION ---------", nullptr, { 0x24F798 }, { 0x43700000 } },
		// { "hotd2", nullptr, { 0x9C2AD8 }, { 0x43700000 } },
		{ "GUN SPIKE", nullptr, { 0xBACD7C }, { 0x440A7C9A } },
		{ " JAMBO SAFARI ------------", nullptr, { 0x2B1DE0 }, { 0x3FE38E39 } },
		{ "SOUL SURFER IN JAPAN", nullptr, { 0x8962C8 }, { 0x3FE38E39 } },

		{ nullptr },
};
CheatManager cheatManager;

static void vblankCallback(Event event, void *param)
{
	((CheatManager *)param)->apply();
}

void CheatManager::setActive(bool active)
{
	this->active = active;
	if (active || widescreen_cheat != nullptr)
		EventManager::listen(Event::VBlank, vblankCallback, this);
	else
		EventManager::unlisten(Event::VBlank, vblankCallback, this);
}

void CheatManager::loadCheatFile(const std::string& filename)
{
#ifndef LIBRETRO
	FILE* cheatfile = nowide::fopen(filename.c_str(), "r");
	if (cheatfile == nullptr)
	{
		WARN_LOG(COMMON, "Cannot open cheat file '%s'", filename.c_str());
		return;
	}
	emucfg::ConfigFile cfg;
	cfg.parse(cheatfile);
	fclose(cheatfile);

	int count = cfg.get_int("", "cheats", 0);
	cheats.clear();
	for (int i = 0; count == 0 || i < count; i++)
	{
		std::string prefix = "cheat" + std::to_string(i) + "_";
		Cheat cheat{};
		cheat.description = cfg.get("", prefix + "desc", "Cheat " + std::to_string(i + 1));
		cheat.address = cfg.get_int("", prefix + "address", -1);
		if (count == 0 && cheat.address == (u32)-1)
			break;
		if (cheat.address >= RAM_SIZE)
		{
			WARN_LOG(COMMON, "Invalid address %x", cheat.address);
			continue;
		}
		cheat.type = (Cheat::Type)cfg.get_int("", prefix + "cheat_type", (int)Cheat::Type::disabled);
		cheat.size = 1 << cfg.get_int("", prefix + "memory_search_size", 0);
		cheat.value = cfg.get_int("", prefix + "value", cheat.value);
		cheat.repeatCount = cfg.get_int("", prefix + "repeat_count", cheat.repeatCount);
		cheat.repeatValueIncrement = cfg.get_int("", prefix + "repeat_add_to_value", cheat.repeatValueIncrement);
		cheat.repeatAddressIncrement = cfg.get_int("", prefix + "repeat_add_to_address", cheat.repeatAddressIncrement);
		cheat.enabled = cfg.get_bool("", prefix + "enable", false);
		cheat.destAddress = cfg.get_int("", prefix + "dest_address", 0);
		if (cheat.destAddress >= RAM_SIZE)
		{
			WARN_LOG(COMMON, "Invalid address %x", cheat.destAddress);
			continue;
		}
		cheat.valueMask = cfg.get_int("", prefix + "address_bit_position", 0);
		if (cheat.type != Cheat::Type::disabled)
			cheats.push_back(cheat);
	}
	setActive(!cheats.empty());
	INFO_LOG(COMMON, "%d cheats loaded", (int)cheats.size());
	cfgSaveStr("cheats", gameId, filename);
#endif
}

int CheatManager::enabledCheatCount()
{
	int enabledCheatCount = 0;
	for (int i = 0; i < cheatCount(); i++)
	{
		if (cheatEnabled(i))
		{
			enabledCheatCount++;
		}
	}
	return enabledCheatCount;
}

void CheatManager::reset()
{
	cheats.clear();
	setActive(false);
	cfgSaveStr("cheats", gameId, "");
}

void CheatManager::reset(const std::string& gameId)
{
	widescreen_cheat = nullptr;
	if (this->gameId != gameId)
	{
		cheats.clear();
		setActive(false);
		this->gameId = gameId;
#ifndef LIBRETRO
		if (!settings.network.online || settings.dojo.training)
		{
			std::string cheatFile = cfgLoadStr("cheats", gameId, "");
			if (!cheatFile.empty())
				loadCheatFile(cheatFile);
		}
#endif
	}

	// disable all loaded cheats outside of training mode
	if (!settings.dojo.training)
		for (size_t i = 0; i < cheatManager.cheatCount(); i++)
			cheatManager.enableCheat(i, false);

	// enable skip dimm version check for vf4 final tuned
	if (gameId == "VF4 FINAL TUNED JAPAN")
	{
		setActive(true);
		cheats.emplace_back(Cheat::Type::setValue, "Skip DIMM version check", true, 16, 0x0007f486, 0xe001); // mov #1, r0
		cheats.back().builtIn = true;
	}
	else if (gameId == "Fixed BOOT strapper")	// Extreme Hunting 2
	{
		setActive(true);
		cheats.emplace_back(Cheat::Type::runNextIfEq, "skip netbd check ifeq", true, 32, 0x00067b04, 0);
		cheats.back().builtIn = true;
		cheats.emplace_back(Cheat::Type::setValue, "skip netbd check", true, 32, 0x00067b04, 1); // 1 skips initNetwork()
		cheats.back().builtIn = true;
		cheats.emplace_back(Cheat::Type::setValue, "skip netbd check 2", true, 16, 0x0009acc8, 0x0009); // not acceptable by main board
		cheats.back().builtIn = true;
		// ac010000 should be d202 9302, but is changed to 78c0 8c93
		cheats.emplace_back(Cheat::Type::runNextIfEq, "fix boot ifeq", true, 32, 0x00010000, 0x8c9378c0);
		cheats.back().builtIn = true;
		cheats.emplace_back(Cheat::Type::setValue, "fix boot", true, 32, 0x00010000, 0x9302d202);
		cheats.back().builtIn = true;
	}

	if (config::WidescreenGameHacks)
	{
		if (settings.platform.isConsole())
		{
			for (int i = 0; widescreen_cheats[i].game_id != nullptr; i++)
			{
				if (!strcmp(gameId.c_str(), widescreen_cheats[i].game_id)
						&& (widescreen_cheats[i].area_or_version == nullptr
								|| !strncmp(ip_meta.area_symbols, widescreen_cheats[i].area_or_version, sizeof(ip_meta.area_symbols))
								|| !strncmp(ip_meta.product_version, widescreen_cheats[i].area_or_version, sizeof(ip_meta.product_version))
								|| !strncmp(ip_meta.software_name, widescreen_cheats[i].area_or_version, sizeof(ip_meta.software_name))))
				{
					widescreen_cheat = &widescreen_cheats[i];
					NOTICE_LOG(COMMON, "Applying widescreen hack to game %s", gameId.c_str());
					break;
				}
			}
		}
		else
		{
			std::string romName = get_file_basename(settings.content.path);
			size_t folder_pos = get_last_slash_pos(romName);
			if (folder_pos != std::string::npos)
				romName = romName.substr(folder_pos + 1);

			for (int i = 0; naomi_widescreen_cheats[i].game_id != nullptr; i++)
			{
				if (!strcmp(gameId.c_str(), naomi_widescreen_cheats[i].game_id)
						&& (naomi_widescreen_cheats[i].area_or_version == nullptr
								|| !strcmp(romName.c_str(), naomi_widescreen_cheats[i].area_or_version)))
				{
					widescreen_cheat = &naomi_widescreen_cheats[i];
					NOTICE_LOG(COMMON, "Applying widescreen hack to game %s", gameId.c_str());
					break;
				}
			}
		}
		if (widescreen_cheat != nullptr)
			for (size_t i = 0; i < ARRAY_SIZE(widescreen_cheat->addresses) && widescreen_cheat->addresses[i] != 0; i++)
				verify(widescreen_cheat->addresses[i] < RAM_SIZE);
	}
	setActive(active);
}

u32 CheatManager::readRam(u32 addr, u32 bits)
{
	switch (bits)
	{
	case 8:
	default:
		return ReadMem8_nommu(0x8C000000 + addr);
	case 16:
		return ReadMem16_nommu(0x8C000000 + addr);
	case 32:
		return ReadMem32_nommu(0x8C000000 + addr);
	}
}

void CheatManager::writeRam(u32 addr, u32 value, u32 bits)
{
	switch (bits)
	{
	case 8:
	default:
		WriteMem8_nommu(0x8C000000 + addr, (u8)value);
		break;
	case 16:
		WriteMem16_nommu(0x8C000000 + addr, (u16)value);
		break;
	case 32:
		WriteMem32_nommu(0x8C000000 + addr, value);
		break;
	}
}

void CheatManager::apply()
{
	if (widescreen_cheat != nullptr)
	{
		for (size_t i = 0; i < ARRAY_SIZE(widescreen_cheat->addresses) && widescreen_cheat->addresses[i] != 0; i++)
		{
			u32 address = widescreen_cheat->addresses[i];
			u32 curVal = readRam(address, 32);
			if (curVal != widescreen_cheat->values[i] && (widescreen_cheat->origValues[i] == 0 || widescreen_cheat->origValues[i] == curVal))
				writeRam(address, widescreen_cheat->values[i], 32);
		}
	}
	if (active && !settings.network.online)
	{
		bool skipCheat = false;
		for (const Cheat& cheat : cheats)
		{
			if (skipCheat) {
				skipCheat = false;
				continue;
			}
			if (!cheat.enabled)
				continue;

			bool setValue = false;
			u32 valueToSet = 0;
			switch (cheat.type)
			{
			case Cheat::Type::disabled:
			default:
				break;
			case Cheat::Type::setValue:
				setValue = true;
				valueToSet = cheat.value;
				break;
			case Cheat::Type::increase:
				setValue = true;
				valueToSet = readRam(cheat.address, cheat.size) + cheat.value;
				break;
			case Cheat::Type::decrease:
				setValue = true;
				valueToSet = readRam(cheat.address, cheat.size) - cheat.value;
				break;
			case Cheat::Type::runNextIfEq:
				skipCheat = readRam(cheat.address, cheat.size) != cheat.value;
				break;
			case Cheat::Type::runNextIfNeq:
				skipCheat = readRam(cheat.address, cheat.size) == cheat.value;
				break;
			case Cheat::Type::runNextIfGt:
				skipCheat = readRam(cheat.address, cheat.size) <= cheat.value;
				break;
			case Cheat::Type::runNextIfLt:
				skipCheat = readRam(cheat.address, cheat.size) >= cheat.value;
				break;
			case Cheat::Type::copy:
				for (u32 i = 0; i < cheat.repeatCount; i++)
					writeRam(cheat.destAddress + i, readRam(cheat.address + i, cheat.size), cheat.size);
				break;
			}
			if (setValue)
			{
				u32 address = cheat.address;
				for (u32 repeat = 0; repeat < cheat.repeatCount; repeat++)
				{
					u32 curVal = readRam(address, cheat.size);
					if (cheat.size < 8)
					{
						for (int i = 0; i < 8; i++)
						{
							int bitmask = 1 << i;
							if ((cheat.valueMask & bitmask) == 0)
								// keep current bit value
								valueToSet = (valueToSet & ~bitmask) | (curVal & bitmask);
						}
					}
					if (curVal != valueToSet)
						writeRam(address, valueToSet, cheat.size);
					address += cheat.repeatAddressIncrement * cheat.size / 8;
					valueToSet += cheat.repeatValueIncrement;
				}
			}
		}
	}
}

static std::vector<u32> parseCodes(const std::string& s)
{
	std::vector<u32> codes;
	std::string curCode;
	for (u8 c : s)
	{
		if (std::isxdigit(c))
		{
			curCode += c;
			if (curCode.length() == 8)
			{
				codes.push_back(strtol(curCode.c_str(), nullptr, 16));
				curCode.clear();
			}
		}
		else if (!curCode.empty())
			throw FlycastException("Invalid cheat code");
	}
	if (!curCode.empty())
	{
		if (curCode.length() != 8)
			throw FlycastException("Invalid cheat code");
		codes.push_back(strtol(curCode.c_str(), nullptr, 16));
	}

	return codes;
}

void CheatManager::addGameSharkCheat(const std::string& name, const std::string& s)
{
	std::vector<u32> codes = parseCodes(s);
	Cheat conditionCheat;
	unsigned conditionLimit = 0;

	const size_t prevSize = cheats.size();
	try {
		for (unsigned i = 0; i < codes.size(); i++)
		{
			if (i < conditionLimit)
				cheats.push_back(conditionCheat);
			Cheat cheat{};
			cheat.description = name;
			u32 code = (codes[i] & 0xff000000) >> 24;
			switch (code)
			{
			case 0:
			case 1:
			case 2:
				{
					// 8/16/32-bit write
					if (i + 1 >= codes.size())
						throw FlycastException("Missing value");
					cheat.type = Cheat::Type::setValue;
					cheat.size = code == 0 ? 8 : code == 1 ? 16 : 32;
					cheat.address = codes[i] & 0x00ffffff;
					cheat.value = codes[++i];
					cheats.push_back(cheat);
				}
				break;
			case 3:
				{
					u32 subcode = (codes[i] & 0x00ff0000) >> 16;
					switch (subcode)
					{
					case 0:
						{
							// Group write
							int count = codes[i] & 0xffff;
							if (i + count + 1 >= codes.size())
								throw FlycastException("Missing values");
							cheat.type = Cheat::Type::setValue;
							cheat.size = 32;
							cheat.address = codes[++i] & 0x00ffffff;
							for (int j = 0; j < count; j++)
							{
								if (j == 1)
									cheat.description += " (cont'd)";
								cheat.value = codes[++i];
								cheats.push_back(cheat);
								cheat.address += 4;
								if (j < count - 1 && i < conditionLimit)
									cheats.push_back(conditionCheat);
							}
						}
						break;
					case 1:
					case 2:
						{
							// 8-bit inc/decrement
							if (i + 1 >= codes.size())
								throw FlycastException("Missing value");
							cheat.type = subcode == 1 ? Cheat::Type::increase : Cheat::Type::decrease;
							cheat.size = 8;
							cheat.value = codes[i] & 0xff;
							cheat.address = codes[++i] & 0x00ffffff;
							cheats.push_back(cheat);
						}
						break;
					case 3:
					case 4:
						{
							// 16-bit inc/decrement
							if (i + 1 >= codes.size())
								throw FlycastException("Missing value");
							cheat.type = subcode == 3 ? Cheat::Type::increase : Cheat::Type::decrease;
							cheat.size = 16;
							cheat.value = codes[i] & 0xffff;
							cheat.address = codes[++i] & 0x00ffffff;
							cheats.push_back(cheat);
						}
						break;
					case 5:
					case 6:
						{
							// 32-bit inc/decrement
							if (i + 2 >= codes.size())
								throw FlycastException("Missing address or value");
							cheat.type = subcode == 5 ? Cheat::Type::increase : Cheat::Type::decrease;
							cheat.size = 32;
							cheat.address = codes[++i] & 0x00ffffff;
							cheat.value = codes[++i];
							cheats.push_back(cheat);
						}
						break;
					default:
						throw FlycastException("Unsupported cheat type");
					}
				}
				break;
			case 4:
				{
					// 32-bit repeat write
					if (i + 2 >= codes.size())
						throw FlycastException("Missing count or value");
					cheat.type = Cheat::Type::setValue;
					cheat.size = 32;
					cheat.address = codes[i] & 0x00ffffff;
					cheat.repeatCount = codes[++i] >> 16;
					cheat.repeatAddressIncrement = codes[i] & 0xffff;
					cheat.value = codes[++i];
					cheats.push_back(cheat);
				}
				break;
			case 5:
				{
					// copy bytes
					if (i + 2 >= codes.size())
						throw FlycastException("Missing count or destination address");
					cheat.type = Cheat::Type::copy;
					cheat.size = 8;
					cheat.address = codes[i] & 0x00ffffff;
					cheat.destAddress = codes[++i] & 0x00ffffff;
					cheat.repeatCount = codes[++i];
					cheats.push_back(cheat);
				}
				break;
			// TODO 7 change decryption type
			// TODO 0xb delay applying codes
			// TODO 0xc global enable test
			case 0xd:
				{
					// enable next code if eq/neq/lt/gt
					if (i + 1 >= codes.size())
						throw FlycastException("Missing count or destination address");
					cheat.size = 16;
					cheat.address = codes[i] & 0x00ffffff;
					switch (codes[++i] >> 16)
					{
					case 0:
						cheat.type = Cheat::Type::runNextIfEq;
						break;
					case 1:
						cheat.type = Cheat::Type::runNextIfNeq;
						break;
					case 2:
						cheat.type = Cheat::Type::runNextIfLt;
						break;
					case 3:
						cheat.type = Cheat::Type::runNextIfGt;
						break;
					default:
						throw FlycastException("Unsupported conditional code");
					}
					cheat.value = codes[i] & 0xffff;
					cheats.push_back(cheat);
				}
				break;
			case 0xe:
				{
					// multiline enable codes if eq/neq/lt/gt
					if (i + 1 >= codes.size())
						throw FlycastException("Missing test address");
					cheat.size = 16;
					cheat.value = codes[i] & 0xffff;
					conditionLimit = i + 1 + ((codes[i] >> 16) & 0xff);
					switch (codes[++i] >> 24)
					{
					case 0:
						cheat.type = Cheat::Type::runNextIfEq;
						break;
					case 1:
						cheat.type = Cheat::Type::runNextIfNeq;
						break;
					case 2:
						cheat.type = Cheat::Type::runNextIfLt;
						break;
					case 3:
						cheat.type = Cheat::Type::runNextIfGt;
						break;
					default:
						throw FlycastException("Unsupported conditional code");
					}
					cheat.address = codes[i] & 0x00ffffff;
					conditionCheat = cheat;
				}
				break;
			default:
				throw FlycastException("Unsupported cheat type");
			}
		}
#ifndef LIBRETRO
		std::string path = cfgLoadStr("cheats", gameId, "");
		if (path == "")
		{
			path = get_game_save_prefix() + ".cht";
			cfgSaveStr("cheats", gameId, path);
		}
		saveCheatFile(path);
#endif
		setActive(!cheats.empty());
	} catch (...) {
		cheats.erase(cheats.begin() + prevSize, cheats.end());
		throw;
	}
}

void CheatManager::saveCheatFile(const std::string& filename)
{
#ifndef LIBRETRO
	emucfg::ConfigFile cfg;

	int i = 0;
	for (const Cheat& cheat : cheats)
	{
		if (cheat.builtIn)
			continue;
		std::string prefix = "cheat" + std::to_string(i) + "_";
		cfg.set_int("", prefix + "address", cheat.address);
		cfg.set_int("", prefix + "address_bit_position", cheat.valueMask);
		cfg.set_bool("", prefix + "big_endian", false);
		cfg.set_int("", prefix + "cheat_type", (int)cheat.type);
		cfg.set("", prefix + "code", "");
		cfg.set("", prefix + "desc", cheat.description);
		cfg.set_int("", prefix + "dest_address", cheat.destAddress);
		cfg.set_bool("", prefix + "enable", false);	// force all cheats disabled at start
		cfg.set_int("", prefix + "handler", 1);
		int memSize;
		switch (cheat.size) {
		case 1:
			memSize = 0;
			break;
		case 2:
			memSize = 1;
			break;
		case 4:
			memSize = 2;
			break;
		case 8:
			memSize = 3;
			break;
		case 16:
			memSize = 4;
			break;
		case 32:
		default:
			memSize = 5;
			break;
		}
		cfg.set_int("", prefix + "memory_search_size", memSize);
		cfg.set_int("", prefix + "value", cheat.value);
		cfg.set_int("", prefix + "repeat_count", cheat.repeatCount);
		cfg.set_int("", prefix + "repeat_add_to_value", cheat.repeatValueIncrement);
		cfg.set_int("", prefix + "repeat_add_to_address", cheat.repeatAddressIncrement);
		i++;
	}
	cfg.set_int("", "cheats", i);
	FILE *fp = nowide::fopen(filename.c_str(), "w");
	if (fp == nullptr)
		throw FlycastException("Can't save cheat file");
	cfg.save(fp);
	fclose(fp);
#endif
}
