/*****************************************************************************\
     Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
                This file is licensed under the Snes9x License.
   For further information, consult the LICENSE file in the root directory.
\*****************************************************************************/

/***********************************************************************************
  SNES9X for Mac OS (c) Copyright John Stiles

  Snes9x for Mac OS X

  (c) Copyright 2001 - 2011  zones
  (c) Copyright 2002 - 2005  107
  (c) Copyright 2002         PB1400c
  (c) Copyright 2004         Alexander and Sander
  (c) Copyright 2004 - 2005  Steven Seeger
  (c) Copyright 2005         Ryan Vogt
 ***********************************************************************************/


#include "snes9x.h"
#include "memmap.h"
#include "cheats.h"

#include "mac-prefix.h"
#include "mac-audio.h"
#include "mac-dialog.h"
#include "mac-os.h"
#include "mac-screenshot.h"
#include "mac-stringtools.h"
#include "mac-cheatfinder.h"

#define	kCFNumBytesPop				'Size'
#define	kCFViewModeRad				'Mode'
#define	kCFCompModePop				'Math'
#define kCFCompStoredRad			'RSto'
#define	kCFCompLastRad				'RLst'
#define	kCFCompThisRad				'RThs'
#define	kCFCompValueTxt				'CTxt'
#define	kCFSearchBtn				'BSea'
#define	kCFStoreValueBtn			'BSto'
#define	kCFWatchBtn					'BWat'
#define kCFDrawerBtn				'Drwr'
#define	kCFWatchAddrTxt				'WTxt'
#define	kCFRestoreBtn				'BRes'
#define	kCFRemoveBtn				'BRem'
#define	kCFAddEntryBtn				'BAdd'
#define kCFUserPane					'Pane'
#define	kCFSheetAddrTxt				'AEad'
#define	kCFSheetCurrentValueTxt		'AEcv'
#define	kCFSheetCheetValueTxt		'AEtx'
#define	kCFSheetDescriptionTxt		'AEde'
#define	kCFSheetAddBtn				'SHTa'
#define	kCFSheetCancelBtn			'SHTc'
#define	kCFListView					'List'
#define	kCFUpperViews				'UI_T'
#define	kCFLowerViews				'UI_B'

#define kEventScrollableScrollThere	'ESST'
#define kEventCheatFinderList		'ECFL'
#define kControlListLinePart		172

#define	MAIN_MEMORY_SIZE			0x20000

#define	kCheatFinderListViewClassID	CFSTR("com.snes9x.macos.snes9x.cheatfinder")

enum
{
	kCFHexadecimal = 1,
	kCFSignedDecimal,
	kCFUnsignedDecimal
};

enum
{
	kCFCompWithStored = 1,
	kCFCompWithLast,
	kCFCompWithThis
};

enum
{
	kCFSearchEqual = 1,
	kCFSearchNotEqual,
	kCFSearchGreater,
	kCFSearchGreaterOrEqual,
	kCFSearchLess,
	kCFSearchLessOrEqual
};

typedef struct
{
	IBNibRef		nibRef;
	WindowRef		main;
	WindowRef		sheet;
	WindowRef		drawer;
	HIViewRef		list;
	HIViewRef		scroll;
	EventHandlerRef	sEref;
	EventHandlerUPP	sUPP;
}	WindowData;

typedef struct
{
	HIViewRef		view;
	HIPoint			originPoint;
	HISize			lineSize;
	Boolean			inFocus;
}	ListViewData;

Boolean	cfIsWatching = false;

extern SCheatData	Cheat;

static UInt8		*cfStoredRAM;
static UInt8		*cfLastRAM;
static UInt8		*cfCurrentRAM;
static UInt8		*cfStatusFlag;
static UInt32		*cfAddress;

static SInt32		cfNumRows;
static SInt32		cfListSelection;

static SInt32		cfViewMode;
static SInt32		cfCompMode;
static SInt32		cfCompWith;
static UInt32		cfViewNumBytes;
static UInt32		cfWatchAddr;
static Boolean		cfIsNewGame;
static Boolean		cfIsStored;
static Boolean		cfDrawerShow;

static int			cfListAddrColumnWidth;
static char			cfWatchTextFormat[32];
static CTFontRef	cfListLineCTFontRef;
#ifdef MAC_TIGER_PANTHER_SUPPORT
static ATSUStyle	cfListLineATSUStyle;
#endif

static HIViewID		kCheatFinderListViewID = { 'CHET', 'FNDR' };

static void CheatFinderSearch (WindowData *);
static void CheatFinderRestoreList (WindowData *);
static void CheatFinderRemoveFromList (WindowData *);
static void CheatFinderAdjustButtons (WindowData *);
static void CheatFinderBuildResultList (void);
static void CheatFinderHandleAddEntryButton (WindowData *);
static void CheatFinderMakeValueFormat (char *);
static void CheatFinderAddEntry (SInt64, char *);
static void CheatFinderBeginAddEntrySheet (WindowData *);
static void CheatFinderEndAddEntrySheet (WindowData *);
static void CheatFinderListViewScrollToThere (float, ListViewData *);
static void CheatFinderListViewDraw (CGContextRef, HIRect *, ListViewData *);
static float CheatFinderListViewSanityCheck (float, ListViewData *);
static SInt64 CheatFinderReadBytes (UInt8 *, UInt32);
static SInt64 CheatFinderGetValueEditText (ControlRef);
static Boolean CheatFinderCompare (SInt64, SInt64);
static HIViewPartCode CheatFinderListViewFindPart (EventRef, ListViewData *, SInt32 *);
static pascal OSStatus CheatFinderListViewHandler (EventHandlerCallRef, EventRef, void *);
static pascal OSStatus CheatFinderSheetEventHandler (EventHandlerCallRef, EventRef, void *);
static pascal OSStatus CheatFinderWindowEventHandler (EventHandlerCallRef, EventRef, void *);
static pascal OSStatus CheatFinderListFrameEventHandler (EventHandlerCallRef, EventRef, void *);


void InitCheatFinder (void)
{
	cfStoredRAM  = new UInt8 [MAIN_MEMORY_SIZE + 10];
	cfLastRAM    = new UInt8 [MAIN_MEMORY_SIZE + 10];
	cfCurrentRAM = new UInt8 [MAIN_MEMORY_SIZE + 10];
	cfStatusFlag = new UInt8 [MAIN_MEMORY_SIZE + 10];
	cfAddress    = new UInt32[MAIN_MEMORY_SIZE + 10];

	if (!cfStoredRAM || !cfLastRAM || !cfCurrentRAM || !cfStatusFlag || !cfAddress)
		QuitWithFatalError(0, "cheatfinder 01");

	memset(cfCurrentRAM, 0x00, MAIN_MEMORY_SIZE + 10);

	cfViewMode     = kCFUnsignedDecimal;
	cfViewNumBytes = 2;
	cfCompMode     = kCFSearchEqual;
	cfCompWith     = kCFCompWithThis;

	if (systemVersion >= 0x1050)
	{
		cfListLineCTFontRef = CTFontCreateWithName(CFSTR("Lucida Sans Typewriter Regular"), 11.0f, NULL);
		if (cfListLineCTFontRef == NULL)
		{
			cfListLineCTFontRef = CTFontCreateWithName(CFSTR("Menlo"), 11.0f, NULL);
			if (cfListLineCTFontRef == NULL)
			{
				cfListLineCTFontRef = CTFontCreateWithName(CFSTR("Monaco"), 11.0f, NULL);
				if (cfListLineCTFontRef == NULL)
					QuitWithFatalError(0, "cheatfinder 02");
			}
		}
	}
#ifdef MAC_TIGER_PANTHER_SUPPORT
	else
	{
		OSStatus	err;

		err = CreateATSUIStyleFromFontFullNameAndSize("Lucida Sans Typewriter Regular", 11, &cfListLineATSUStyle);
		if (err)
		{
			err = CreateATSUIStyleFromFontFullNameAndSize("Menlo", 11, &cfListLineATSUStyle);
			if (err)
			{
				err = CreateATSUIStyleFromFontFullNameAndSize("Monaco", 11, &cfListLineATSUStyle);
				if (err)
					QuitWithFatalError(0, "cheatfinder 02");
			}
		}
	}
#endif
}

void ResetCheatFinder (void)
{
	memset(cfStoredRAM,  0x00, MAIN_MEMORY_SIZE);
	memset(cfLastRAM,    0x00, MAIN_MEMORY_SIZE);
	memset(cfStatusFlag, 0xFF, MAIN_MEMORY_SIZE);

	cfWatchAddr  = 0;
	cfIsNewGame  = true;
	cfIsWatching = false;
	cfIsStored   = false;
	cfDrawerShow = false;

	CheatFinderMakeValueFormat(cfWatchTextFormat);
}

void DeinitCheatFinder (void)
{
	if (systemVersion >= 0x1050)
		CFRelease(cfListLineCTFontRef);
#ifdef MAC_TIGER_PANTHER_SUPPORT
	else
		ATSUDisposeStyle(cfListLineATSUStyle);
#endif

	delete [] cfStoredRAM;
	delete [] cfLastRAM;
	delete [] cfCurrentRAM;
	delete [] cfStatusFlag;
	delete [] cfAddress;
}

void CheatFinder (void)
{
	static HIObjectClassRef	cfListViewClass = NULL;

	OSStatus				err;
	HIViewRef				ctl;
	HIViewID				cid;
	char					num[256];
	WindowData				cf;
	EventHandlerRef			wEref, pEref;
	EventHandlerUPP			wUPP, pUPP;
	EventTypeSpec			wEvents[] = { { kEventClassCommand,    kEventCommandProcess           },
										  { kEventClassCommand,    kEventCommandUpdateStatus      },
										  { kEventClassWindow,     kEventWindowClose              } },
							pEvents[] = { { kEventClassControl,    kEventControlDraw              } },
							cEvents[] = { { kEventClassHIObject,   kEventHIObjectConstruct        },
										  { kEventClassHIObject,   kEventHIObjectInitialize       },
										  { kEventClassHIObject,   kEventHIObjectDestruct         },
										  { kEventClassScrollable, kEventScrollableGetInfo        },
										  { kEventClassScrollable, kEventScrollableScrollTo       },
										  { kEventCheatFinderList, kEventScrollableScrollThere    },
										  { kEventClassControl,    kEventControlHitTest           },
										  { kEventClassControl,    kEventControlTrack             },
										  { kEventClassControl,    kEventControlValueFieldChanged },
										  { kEventClassControl,    kEventControlDraw              } };

	if (!cartOpen)
		return;

	err = CreateNibReference(kMacS9XCFString, &(cf.nibRef));
	if (err == noErr)
	{
		err = CreateWindowFromNib(cf.nibRef, CFSTR("CheatFinder"), &(cf.main));
		if (err == noErr)
		{
			err = CreateWindowFromNib(cf.nibRef, CFSTR("CFDrawer"), &(cf.drawer));
			if (err == noErr)
			{
				memcpy(cfCurrentRAM, Memory.RAM, MAIN_MEMORY_SIZE);
				CheatFinderBuildResultList();

				err = noErr;
				if (!cfListViewClass)
					err = HIObjectRegisterSubclass(kCheatFinderListViewClassID, kHIViewClassID, 0, CheatFinderListViewHandler, GetEventTypeCount(cEvents), cEvents, NULL, &cfListViewClass);
				if (err == noErr)
				{
					HIObjectRef		hiObject;
					HIViewRef		userpane, scrollview, listview, imageview, root;
					HILayoutInfo	layoutinfo;
					HIRect			frame;
					HISize			minSize;
					CGImageRef		image;
					Rect			rct;
					float			pich;

					GetWindowBounds(cf.main, kWindowContentRgn, &rct);

					minSize.width  = (float) (rct.right  - rct.left);
					minSize.height = (float) (rct.bottom - rct.top );
					err = SetWindowResizeLimits(cf.main, &minSize, NULL);

					root = HIViewGetRoot(cf.main);
					cid.id = 0;
					cid.signature = kCFUserPane;
					HIViewFindByID(root, cid, &userpane);

					err = HIScrollViewCreate(kHIScrollViewOptionsVertScroll, &scrollview);
					HIViewAddSubview(userpane, scrollview);
					HIViewGetBounds(userpane, &frame);
					cfListAddrColumnWidth = (int) (frame.size.width * 0.4);
					frame.origin.y    += 16.0f;
					frame.size.height -= 16.0f;
					frame = CGRectInset(frame, 1.0f, 1.0f);
					HIViewSetFrame(scrollview, &frame);
					HIViewSetVisible(scrollview, true);
					cf.scroll = scrollview;

					layoutinfo.version = kHILayoutInfoVersionZero;
					HIViewGetLayoutInfo(scrollview, &layoutinfo);

					layoutinfo.binding.top.toView    = userpane;
					layoutinfo.binding.top.kind      = kHILayoutBindTop;
					layoutinfo.binding.bottom.toView = userpane;
					layoutinfo.binding.bottom.kind   = kHILayoutBindBottom;
					layoutinfo.binding.left.toView   = userpane;
					layoutinfo.binding.left.kind     = kHILayoutBindLeft;
					layoutinfo.binding.right.toView  = userpane;
					layoutinfo.binding.right.kind    = kHILayoutBindRight;
					HIViewSetLayoutInfo(scrollview, &layoutinfo);

					err = HIObjectCreate(kCheatFinderListViewClassID, NULL, &hiObject);
					listview = (HIViewRef) hiObject;
					HIViewAddSubview(scrollview, listview);
					SetControl32BitMinimum(listview, 1);
					SetControl32BitMaximum(listview, cfNumRows);
					SetControl32BitValue(listview, 1);
					HIViewSetVisible(listview, true);
					cf.list = listview;

					cid.signature = kCFNumBytesPop;
					HIViewFindByID(root, cid, &ctl);
					SetControl32BitValue(ctl, cfViewNumBytes);

					cid.signature = kCFViewModeRad;
					HIViewFindByID(root, cid, &ctl);
					SetControl32BitValue(ctl, cfViewMode);

					cid.signature = kCFCompModePop;
					HIViewFindByID(root, cid, &ctl);
					SetControl32BitValue(ctl, cfCompMode);

					if (cfIsNewGame || (!cfIsStored && (cfCompWith == kCFCompWithStored)))
						cfCompWith = kCFCompWithThis;

					cid.signature = kCFCompStoredRad;
					HIViewFindByID(root, cid, &ctl);
					SetControl32BitValue(ctl, cfCompWith == kCFCompWithStored);
					if (cfIsStored)
						ActivateControl(ctl);
					else
						DeactivateControl(ctl);

					cid.signature = kCFCompLastRad;
					HIViewFindByID(root, cid, &ctl);
					SetControl32BitValue(ctl, cfCompWith == kCFCompWithLast);
					if (!cfIsNewGame)
						ActivateControl(ctl);
					else
						DeactivateControl(ctl);

					cid.signature = kCFCompThisRad;
					HIViewFindByID(root, cid, &ctl);
					SetControl32BitValue(ctl, cfCompWith == kCFCompWithThis);

					cid.signature = kCFCompValueTxt;
					HIViewFindByID(root, cid, &ctl);
					SetEditTextCFString(ctl, CFSTR(""), false);
					err = SetKeyboardFocus(cf.main, ctl, kControlFocusNextPart);

					cid.signature = kCFWatchBtn;
					HIViewFindByID(root, cid, &ctl);
					SetControl32BitValue(ctl, cfIsWatching);

					cid.signature = kCFDrawerBtn;
					HIViewFindByID(root, cid, &ctl);
					SetControl32BitValue(ctl, cfDrawerShow);

					cid.signature = kCFWatchAddrTxt;
					HIViewFindByID(root, cid, &ctl);
					if (cfIsWatching)
					{
						sprintf(num, "%06lX", cfWatchAddr + 0x7E0000);
						SetStaticTextCStr(ctl, num, false);
					}
					else
						SetStaticTextCFString(ctl, CFSTR(""), false);

					CheatFinderAdjustButtons(&cf);

					pUPP = NewEventHandlerUPP(CheatFinderListFrameEventHandler);
					err = InstallControlEventHandler(userpane, pUPP, GetEventTypeCount(pEvents), pEvents, (void *) userpane, &pEref);

					wUPP = NewEventHandlerUPP(CheatFinderWindowEventHandler);
					err = InstallWindowEventHandler (cf.main,  wUPP, GetEventTypeCount(wEvents), wEvents, (void *) &cf,      &wEref);

					pich = (float) (IPPU.RenderedScreenHeight >> ((IPPU.RenderedScreenHeight > 256) ? 1 : 0));

					err = SetDrawerParent(cf.drawer, cf.main);
					err = SetDrawerOffsets(cf.drawer, 0.0f, (float) ((rct.bottom - rct.top) - (pich + 37)));

					image = CreateGameScreenCGImage();
					if (image)
					{
						err = HIImageViewCreate(image, &imageview);
						if (err == noErr)
						{
							HIViewFindByID(HIViewGetRoot(cf.drawer), kHIViewWindowContentID, &ctl);

							HIViewAddSubview(ctl, imageview);
							HIImageViewSetOpaque(imageview, false);
							HIImageViewSetScaleToFit(imageview, true);
							HIViewSetVisible(imageview, true);

							frame.origin.x = 8.0f;
							frame.origin.y = 8.0f;
							frame.size.width  = (float) SNES_WIDTH;
							frame.size.height = pich;
							HIViewSetFrame(imageview, &frame);
						}
					}

					MoveWindowPosition(cf.main, kWindowCheatFinder, true);
					ShowWindow(cf.main);

					if (cfDrawerShow)
						err = OpenDrawer(cf.drawer, kWindowEdgeDefault, false);

					err = RunAppModalLoopForWindow(cf.main);

					HideWindow(cf.main);
					SaveWindowPosition(cf.main, kWindowCheatFinder);

					err = RemoveEventHandler(pEref);
					DisposeEventHandlerUPP(pUPP);

					err = RemoveEventHandler(wEref);
					DisposeEventHandlerUPP(wUPP);

					if (image)
						CGImageRelease(image);
				}

				CFRelease(cf.drawer);
			}

			CFRelease(cf.main);
		}

		DisposeNibReference(cf.nibRef);

		memcpy(cfLastRAM, Memory.RAM, MAIN_MEMORY_SIZE);
		cfIsNewGame = false;
	}
}

static SInt64 CheatFinderReadBytes (UInt8 *mem, UInt32 addr)
{
	switch (cfViewMode)
	{
		case kCFSignedDecimal:
		{
			switch (cfViewNumBytes)
			{
				case 1:	return ((SInt64) (SInt8)      mem[addr]);
				case 2:	return ((SInt64) (SInt16)    (mem[addr] | (mem[addr + 1] << 8)));
				case 4:	return ((SInt64) (SInt32)    (mem[addr] | (mem[addr + 1] << 8) | (mem[addr + 2] << 16) | (mem[addr + 3] << 24)));
				case 3:	return ((SInt64) (((SInt32) ((mem[addr] | (mem[addr + 1] << 8) | (mem[addr + 2] << 16)) << 8)) >> 8));
			}

			break;
		}

		case kCFUnsignedDecimal:
		case kCFHexadecimal:
		{
			switch (cfViewNumBytes)
			{
				case 1:	return ((SInt64) (UInt8)      mem[addr]);
				case 2:	return ((SInt64) (UInt16)    (mem[addr] | (mem[addr + 1] << 8)));
				case 3:	return ((SInt64) (UInt32)    (mem[addr] | (mem[addr + 1] << 8) | (mem[addr + 2] << 16)));
				case 4:	return ((SInt64) (UInt32)    (mem[addr] | (mem[addr + 1] << 8) | (mem[addr + 2] << 16) | (mem[addr + 3] << 24)));
			}

			break;
		}
	}

	return (0);
}

static pascal OSStatus CheatFinderWindowEventHandler (EventHandlerCallRef inHandlerCallRef, EventRef inEvent, void *inUserData)
{
	OSStatus	err, result = eventNotHandledErr;
	WindowData	*cf;
	HIViewRef	control, ctl, root;
	HIViewID	controlID, cid;
	Boolean		update = false;
	char		num[256];

	cf = (WindowData *) inUserData;

	switch (GetEventClass(inEvent))
	{
		case kEventClassWindow:
		{
			switch (GetEventKind(inEvent))
			{
				case kEventWindowClose:
				{
					QuitAppModalLoopForWindow(cf->main);
					result = noErr;
					break;
				}
			}

			break;
		}

		case kEventClassCommand:
		{
			switch (GetEventKind(inEvent))
			{
				HICommand	tHICommand;

				case kEventCommandUpdateStatus:
				{
					err = GetEventParameter(inEvent, kEventParamDirectObject, typeHICommand, NULL, sizeof(HICommand), NULL, &tHICommand);
					if (err == noErr && tHICommand.commandID == 'clos')
					{
						UpdateMenuCommandStatus(true);
						result = noErr;
					}

					break;
				}

				case kEventCommandProcess:
				{
					err = GetEventParameter(inEvent, kEventParamDirectObject, typeHICommand, NULL, sizeof(HICommand), NULL, &tHICommand);
					if (err == noErr)
					{
						root = HIViewGetRoot(cf->main);
						controlID.id = 0;
						controlID.signature = tHICommand.commandID;
						HIViewFindByID(root, controlID, &control);

						switch (tHICommand.commandID)
						{
							case kCFNumBytesPop:
							{
								cfViewNumBytes = GetControl32BitValue(control);
								update = true;
								result = noErr;
								break;
							}

							case kCFViewModeRad:
							{
								cfViewMode     = GetControl32BitValue(control);
								update = true;
								result = noErr;
								break;
							}

							case kCFCompModePop:
							{
								cfCompMode     = GetControl32BitValue(control);
								result = noErr;
								break;
							}

							case kCFCompStoredRad:
							case kCFCompLastRad:
							case kCFCompThisRad:
							{
								if (tHICommand.commandID == kCFCompStoredRad)
									cfCompWith = kCFCompWithStored;
								else
								if (tHICommand.commandID == kCFCompLastRad)
									cfCompWith = kCFCompWithLast;
								else
									cfCompWith = kCFCompWithThis;

								cid.id = 0;

								cid.signature = kCFCompStoredRad;
								HIViewFindByID(root, cid, &ctl);
								SetControl32BitValue(ctl, cfCompWith == kCFCompWithStored);

								cid.signature = kCFCompLastRad;
								HIViewFindByID(root, cid, &ctl);
								SetControl32BitValue(ctl, cfCompWith == kCFCompWithLast);

								cid.signature = kCFCompThisRad;
								HIViewFindByID(root, cid, &ctl);
								SetControl32BitValue(ctl, cfCompWith == kCFCompWithThis);

								result = noErr;
								break;
							}

							case kCFStoreValueBtn:
							{
								memcpy(cfStoredRAM, Memory.RAM, MAIN_MEMORY_SIZE);
								cfIsStored = true;

								cid.id = 0;
								cid.signature = kCFCompStoredRad;
								HIViewFindByID(root, cid, &ctl);
								ActivateControl(ctl);

								result = noErr;
								break;
							}

							case kCFSearchBtn:
							{
								CheatFinderSearch(cf);
								update = true;
								result = noErr;
								break;
							}

							case kCFRemoveBtn:
							{
								CheatFinderRemoveFromList(cf);
								update = true;
								result = noErr;
								break;
							}

							case kCFRestoreBtn:
							{
								CheatFinderRestoreList(cf);
								update = true;
								result = noErr;
								break;
							}

							case kCFAddEntryBtn:
							{
								CheatFinderHandleAddEntryButton(cf);
								result = noErr;
								break;
							}

							case kCFDrawerBtn:
							{
								cfDrawerShow = !cfDrawerShow;

								if (cfDrawerShow)
									err = OpenDrawer(cf->drawer, kWindowEdgeDefault, false);
								else
									err = CloseDrawer(cf->drawer, false);

								result = noErr;
								break;
							}

							case kCFWatchBtn:
							{
								cid.id = 0;
								cid.signature = kCFWatchAddrTxt;
								HIViewFindByID(root, cid, &ctl);

								if (!cfIsWatching)
								{
									if (cfAddress[cfListSelection] > (0x20000 - cfViewNumBytes))
									{
										PlayAlertSound();
										SetControl32BitValue(control, 0);
									}
									else
									{
										cfWatchAddr = cfAddress[cfListSelection];
										cfIsWatching = true;

										sprintf(num, "%06lX", cfWatchAddr + 0x7E0000);
										SetStaticTextCStr(ctl, num, true);
									}
								}
								else
								{
									cfWatchAddr = 0;
									cfIsWatching = false;

									SetControl32BitValue(control, 0);
									SetStaticTextCFString(ctl, CFSTR(""), true);

									CheatFinderAdjustButtons(cf);
								}

								result = noErr;
								break;
							}
						}

						if (update)
						{
							HIViewSetNeedsDisplay(cf->list, true);
							CheatFinderAdjustButtons(cf);
						}

						CheatFinderMakeValueFormat(cfWatchTextFormat);
					}

					break;
				}
			}

			break;
		}
	}

	return (result);
}

static SInt64 CheatFinderGetValueEditText (HIViewRef control)
{
	SInt64	result = 0;
	UInt32	uvalue;
	SInt32	svalue;
	char	num[256];

	GetEditTextCStr(control, num);
	if (num[0] == 0)
	{
		SetEditTextCFString(control, CFSTR("0"), true);
		return (0);
	}

	switch (cfViewMode)
	{
		case kCFSignedDecimal:
		{
			if (sscanf(num, "%ld", &svalue) == 1)
			{
				switch (cfViewNumBytes)
				{
					case 1:
					{
						if (svalue >  127)
						{
							svalue =  127;
							SetEditTextCFString(control, CFSTR("127"), true);
						}
						else
						if (svalue < -128)
						{
							svalue = -128;
							SetEditTextCFString(control, CFSTR("-128"), true);
						}

						break;
					}

					case 2:
					{
						if (svalue >  32767)
						{
							svalue =  32767;
							SetEditTextCFString(control, CFSTR("32767"), true);
						}
						else
						if (svalue < -32768)
						{
							svalue = -32768;
							SetEditTextCFString(control, CFSTR("-32768"), true);
						}

						break;
					}

					case 3:
					{
						if (svalue >  8388607)
						{
							svalue =  8388607;
							SetEditTextCFString(control, CFSTR("8388607"), true);
						}
						else
						if (svalue < -8388608)
						{
							svalue = -8388608;
							SetEditTextCFString(control, CFSTR("-8388608"), true);
						}

						break;
					}
				}
			}
			else
			{
				svalue = 0;
				SetEditTextCFString(control, CFSTR("0"), true);
			}

			result = (SInt64) svalue;

			break;
		}

		case kCFUnsignedDecimal:
		{
			if (sscanf(num, "%lu", &uvalue) == 1)
			{
				switch (cfViewNumBytes)
				{
					case 1:
					{
						if (uvalue > 255)
						{
							uvalue = 255;
							SetEditTextCFString(control, CFSTR("255"), true);
						}

						break;
					}

					case 2:
					{
						if (uvalue > 65535)
						{
							uvalue = 65535;
							SetEditTextCFString(control, CFSTR("65535"), true);
						}

						break;
					}

					case 3:
					{
						if (uvalue > 16777215)
						{
							uvalue = 16777215;
							SetEditTextCFString(control, CFSTR("16777215"), true);
						}

						break;
					}
				}
			}
			else
			{
				uvalue = 0;
				SetEditTextCFString(control, CFSTR("0"), true);
			}

			result = (SInt64) uvalue;

			break;
		}

		case kCFHexadecimal:
		{
			if (sscanf(num, "%lx", &uvalue) == 1)
			{
				switch (cfViewNumBytes)
				{
					case 1:
					{
						if (uvalue > 0xFF)
						{
							uvalue = 0xFF;
							SetEditTextCFString(control, CFSTR("FF"), true);
						}

						break;
					}

					case 2:
					{
						if (uvalue > 0xFFFF)
						{
							uvalue = 0xFFFF;
							SetEditTextCFString(control, CFSTR("FFFF"), true);
						}

						break;
					}

					case 3:
					{
						if (uvalue > 0xFFFFFF)
						{
							uvalue = 0xFFFFFF;
							SetEditTextCFString(control, CFSTR("FFFFFF"), true);
						}

						break;
					}
				}
			}
			else
			{
				uvalue = 0;
				SetEditTextCFString(control, CFSTR("0"), true);
			}

			result = (SInt64) uvalue;

			break;
		}
	}

	return (result);
}

static void CheatFinderSearch (WindowData *cf)
{
	SInt64	cmpvalue;
	UInt8	*mem;

	if (cfCompWith == kCFCompWithThis)
	{
		HIViewRef	ctl;
		HIViewID	cid = { kCFCompValueTxt, 0 };

		HIViewFindByID(HIViewGetRoot(cf->main), cid, &ctl);
		cmpvalue = CheatFinderGetValueEditText(ctl);

		for (int i = 0; i < cfNumRows; i++)
			if (!CheatFinderCompare(CheatFinderReadBytes(cfCurrentRAM, cfAddress[i]), cmpvalue))
				cfStatusFlag[cfAddress[i]] = 0;
	}
	else
	{
		mem = (cfCompWith == kCFCompWithStored) ? cfStoredRAM : cfLastRAM;

		for (int i = 0; i < cfNumRows; i++)
			if (!CheatFinderCompare(CheatFinderReadBytes(cfCurrentRAM, cfAddress[i]), CheatFinderReadBytes(mem, cfAddress[i])))
				cfStatusFlag[cfAddress[i]] = 0;
	}

	CheatFinderBuildResultList();

	SetControl32BitMaximum(cf->list, cfNumRows);
	SetControl32BitValue(cf->list, 1);
}

static Boolean CheatFinderCompare (SInt64 ramvalue, SInt64 cmpvalue)
{
	switch (cfCompMode)
	{
		case kCFSearchEqual:			return (ramvalue == cmpvalue);
		case kCFSearchNotEqual:			return (ramvalue != cmpvalue);
		case kCFSearchGreater:			return (ramvalue >  cmpvalue);
		case kCFSearchGreaterOrEqual:	return (ramvalue >= cmpvalue);
		case kCFSearchLess:				return (ramvalue <  cmpvalue);
		case kCFSearchLessOrEqual:		return (ramvalue <= cmpvalue);
	}

	return (false);
}

static void CheatFinderBuildResultList (void)
{
	cfNumRows = 0;

	for (int i = 0; i < MAIN_MEMORY_SIZE; i++)
	{
		if (cfStatusFlag[i] == 0xFF)
		{
			cfAddress[cfNumRows] = i;
			cfNumRows++;
		}
	}

	cfListSelection = 0;
}

static void CheatFinderAdjustButtons (WindowData *cf)
{
	HIViewRef	ctl, root;
	HIViewID	cid;

	cid.id = 0;
	root = HIViewGetRoot(cf->main);

	if (cfNumRows > 0)
	{
		cid.signature = kCFAddEntryBtn;
		HIViewFindByID(root, cid, &ctl);
		ActivateControl(ctl);

		cid.signature = kCFRemoveBtn;
		HIViewFindByID(root, cid, &ctl);
		ActivateControl(ctl);

		cid.signature = kCFWatchBtn;
		HIViewFindByID(root, cid, &ctl);
		ActivateControl(ctl);
	}
	else
	{
		cid.signature = kCFAddEntryBtn;
		HIViewFindByID(root, cid, &ctl);
		DeactivateControl(ctl);

		cid.signature = kCFRemoveBtn;
		HIViewFindByID(root, cid, &ctl);
		DeactivateControl(ctl);

		if (!cfIsWatching)
		{
			cid.signature = kCFWatchBtn;
			HIViewFindByID(root, cid, &ctl);
			DeactivateControl(ctl);
		}
	}
}

static void CheatFinderRemoveFromList (WindowData *cf)
{
	if (cfNumRows > 0)
	{
		cfStatusFlag[cfAddress[cfListSelection]] = 0;

		if (cfNumRows == 1)
		{
			cfNumRows = 0;

			SetControl32BitMaximum(cf->list, 0);
			SetControl32BitValue(cf->list, 1);
		}
		else
		{
			for (int i = cfListSelection; i < cfNumRows - 1; i++)
				cfAddress[i] = cfAddress[i + 1];

			cfNumRows--;
			if (cfListSelection >= cfNumRows)
				cfListSelection = cfNumRows - 1;

			SetControl32BitMaximum(cf->list, cfNumRows);
			SetControl32BitValue(cf->list, cfListSelection + 1);
		}
	}
}

static void CheatFinderRestoreList (WindowData *cf)
{
	memset(cfStatusFlag, 0xFF, MAIN_MEMORY_SIZE);
	CheatFinderBuildResultList();

	SetControl32BitMaximum(cf->list, cfNumRows);
	SetControl32BitValue(cf->list, 1);
}

static void CheatFinderMakeValueFormat (char *text)
{
	switch (cfViewMode)
	{
		case kCFSignedDecimal:
		case kCFUnsignedDecimal:
		{
			strcpy(text, "%lld");
			break;
		}

		case kCFHexadecimal:
		{
			sprintf(text, "%%0%lullX", cfViewNumBytes * 2);
			break;
		}
	}
}

void CheatFinderDrawWatchAddr (void)
{
	static char	code[256];

	uint16		*basePtr;
	int			len;

	sprintf(code, cfWatchTextFormat, CheatFinderReadBytes(Memory.RAM, cfWatchAddr));

	basePtr = GFX.Screen + 1;
	len = strlen(code);

	for (int i = 0; i < len; i++)
	{
		S9xDisplayChar(basePtr, code[i]);
		basePtr += (8 - 1);
    }
}

static void CheatFinderHandleAddEntryButton (WindowData *cf)
{
	if (cfAddress[cfListSelection] > (0x20000 - cfViewNumBytes))
		PlayAlertSound();
	else
	if (Cheat.g.size() + cfViewNumBytes > MAC_MAX_CHEATS)
		AppearanceAlert(kAlertCautionAlert, kS9xMacAlertCFCantAddEntry, kS9xMacAlertCFCantAddEntryHint);
	else
		CheatFinderBeginAddEntrySheet(cf);
}

static void CheatFinderBeginAddEntrySheet (WindowData *cf)
{
	OSStatus		err;
	HIViewRef		ctl, root;
	HIViewID		cid;
	UInt32			addr;
	char			str[256], form[256];
	EventTypeSpec	sEvents[] = { { kEventClassCommand, kEventCommandProcess      },
								  { kEventClassCommand, kEventCommandUpdateStatus } };

	err = CreateWindowFromNib(cf->nibRef, CFSTR("CFAddEntry"), &(cf->sheet));
	if (err == noErr)
	{
		addr = cfAddress[cfListSelection];

		root = HIViewGetRoot(cf->sheet);
		cid.id = 0;

		cid.signature = kCFSheetAddrTxt;
		HIViewFindByID(root, cid, &ctl);
		sprintf(str, "%06lX", addr + 0x7E0000);
		SetStaticTextCStr(ctl, str, false);

		cid.signature = kCFSheetCurrentValueTxt;
		HIViewFindByID(root, cid, &ctl);
		CheatFinderMakeValueFormat(form);
		sprintf(str, form, CheatFinderReadBytes(cfCurrentRAM, addr));
		SetStaticTextCStr(ctl, str, false);

		cid.signature = kCFSheetCheetValueTxt;
		HIViewFindByID(root, cid, &ctl);
		SetEditTextCStr(ctl, str, false);

		err = ClearKeyboardFocus(cf->sheet);
		err = SetKeyboardFocus(cf->sheet, ctl, kControlFocusNextPart);

		cid.signature = kCFSheetDescriptionTxt;
		HIViewFindByID(root, cid, &ctl);
		sprintf(str, "%06lX-%06lX", addr + 0x7E0000, addr + cfViewNumBytes - 1 + 0x7E0000);
		SetStaticTextCStr(ctl, str, false);

		cf->sUPP = NewEventHandlerUPP(CheatFinderSheetEventHandler);
		err = InstallWindowEventHandler(cf->sheet, cf->sUPP, GetEventTypeCount(sEvents), sEvents, (void *) cf, &(cf->sEref));

		err = ShowSheetWindow(cf->sheet, cf->main);
	}
}

static void CheatFinderEndAddEntrySheet (WindowData *cf)
{
	if (cf->sheet)
	{
		OSStatus	err;

		err = HideSheetWindow(cf->sheet);

		err = RemoveEventHandler(cf->sEref);
		DisposeEventHandlerUPP(cf->sUPP);

		CFRelease(cf->sheet);
	}
}

static pascal OSStatus CheatFinderSheetEventHandler (EventHandlerCallRef inHandlerCallRef, EventRef inEvent, void *inUserData)
{
	OSStatus	err, result = eventNotHandledErr;
	WindowData	*cf = (WindowData *) inUserData;

	switch (GetEventClass(inEvent))
	{
		case kEventClassCommand:
		{
			switch (GetEventKind(inEvent))
			{
				HICommand	tHICommand;

				case kEventCommandUpdateStatus:
				{
					err = GetEventParameter(inEvent, kEventParamDirectObject, typeHICommand, NULL, sizeof(HICommand), NULL, &tHICommand);
					if (err == noErr && tHICommand.commandID == 'clos')
					{
						UpdateMenuCommandStatus(false);
						result = noErr;
					}

					break;
				}

				case kEventCommandProcess:
				{
					err = GetEventParameter(inEvent, kEventParamDirectObject, typeHICommand, NULL, sizeof(HICommand), NULL, &tHICommand);
					if (err == noErr)
					{
						switch (tHICommand.commandID)
						{
							case kCFSheetAddBtn:
							{
								HIViewRef	ctl, root;
								HIViewID	cid;
								CFStringRef	str;
								SInt64		rv;
								char		buf[256];

								root = HIViewGetRoot(cf->sheet);
								cid.id = 0;

								cid.signature = kCFSheetCheetValueTxt;
								HIViewFindByID(root, cid, &ctl);
								rv = CheatFinderGetValueEditText(ctl);

								cid.signature = kCFSheetDescriptionTxt;
								HIViewFindByID(root, cid, &ctl);

								buf[0] = 0;
								CopyEditTextCFString(ctl, &str);
								if (str)
								{
									strcpy(buf, GetMultiByteCharacters(str, 19));
									CFRelease(str);
								}

								if (buf[0] == 0)
								{
									buf[0] = ' ';
									buf[1] = 0;
								}

								CheatFinderAddEntry(rv, buf);

								CheatFinderEndAddEntrySheet(cf);

								result = noErr;
								break;
							}

							case kCFSheetCancelBtn:
							{
								CheatFinderEndAddEntrySheet(cf);
								result = noErr;
								break;
							}
						}
					}

					break;
				}
			}

			break;
		}
	}

	return (result);
}

static void CheatFinderAddEntry (SInt64 value, char *description)
{
	UInt32	addr, v;

	addr = cfAddress[cfListSelection];
	v = (UInt32) (SInt32) value;

	for (unsigned int i = 0; i < cfViewNumBytes; i++)
	{
		char code[10];
		snprintf(code, 10, "%x=%x", addr + i + 0x7E0000, (UInt8) ((v & (0x000000FF << (i * 8))) >> (i * 8)));
		int index = S9xAddCheatGroup(description, code);
		if(index >= 0)
			S9xEnableCheatGroup(index);
	}
}

static pascal OSStatus CheatFinderListFrameEventHandler (EventHandlerCallRef inHandlerCallRef, EventRef inEvent, void *userData)
{
	OSStatus	err, result = eventNotHandledErr;
	HIViewRef	view = (HIViewRef) userData;

	switch (GetEventClass(inEvent))
	{
		case kEventClassControl:
		{
			switch (GetEventKind(inEvent))
			{
				case kEventControlDraw:
				{
					CGContextRef	ctx;
					HIRect			bounds;

					HIViewGetBounds(view, &bounds);
					cfListAddrColumnWidth = (int) (bounds.size.width * 0.4);

					err = GetEventParameter(inEvent, kEventParamCGContextRef, typeCGContextRef, NULL, sizeof(CGContextRef), NULL, &ctx);
					if (err == noErr)
					{
						HIRect	drawBounds;

						if (systemVersion >= 0x1040)
						{
							HIShapeRef	shape = NULL;

							err = GetEventParameter(inEvent, kEventParamShape, typeHIShapeRef, NULL, sizeof(HIShapeRef), NULL, &shape);
							if (err == noErr)
							{
								if (shape)
									HIShapeGetBounds(shape, &drawBounds);
								else
									drawBounds = bounds;
							}
						}
					#ifdef MAC_PANTHER_SUPPORT
						else
						{
							RgnHandle	rgn = NULL;

							err = GetEventParameter(inEvent, kEventParamRgnHandle, typeQDRgnHandle, NULL, sizeof(RgnHandle), NULL, &rgn);
							if (err == noErr)
							{
								if (rgn)
								{
									Rect	rgnBounds;

									GetRegionBounds(rgn, &rgnBounds);
									drawBounds.origin.x    = (float)  rgnBounds.left;
									drawBounds.origin.y    = (float)  rgnBounds.top;
									drawBounds.size.width  = (float) (rgnBounds.right  - rgnBounds.left);
									drawBounds.size.height = (float) (rgnBounds.bottom - rgnBounds.top );
								}
								else
									drawBounds = bounds;
							}
						}
					#endif

						if (err == noErr)
						{
							if (CGRectContainsRect(drawBounds, bounds))
							{
								HIRect					rct;
								HIThemeFrameDrawInfo	frameinfo;
								HIThemeButtonDrawInfo	buttoninfo;
								HIThemeTextInfo			textinfo;
								CFStringRef				str;

								buttoninfo.version          = 0;
								buttoninfo.state            = kThemeStateActive;
								buttoninfo.kind             = kThemeListHeaderButton;
								buttoninfo.value            = kThemeButtonOff;
								buttoninfo.adornment        = kThemeAdornmentNone;

								frameinfo.version           = 0;
								frameinfo.state             = kThemeStateActive;
								frameinfo.kind              = kHIThemeFrameListBox;
								frameinfo.isFocused         = false;

								textinfo.version            = 0;
								textinfo.state              = kThemeStateActive;
								textinfo.fontID             = kThemeSmallSystemFont;
								textinfo.verticalFlushness  = kHIThemeTextVerticalFlushCenter;
								textinfo.options            = 0;
								textinfo.truncationPosition = kHIThemeTextTruncationNone;
								textinfo.truncationMaxLines = 0;
								textinfo.truncationHappened = false;

								CGContextSaveGState(ctx);

								rct = CGRectInset(bounds, 1.0f, 1.0f);
								err = HIThemeDrawFrame(&rct, &frameinfo, ctx, kHIThemeOrientationNormal);

								rct = bounds;
								rct.size.height = 16.0f;
								rct.size.width  = (float) cfListAddrColumnWidth + 1.0f;
								err = HIThemeDrawButton(&rct, &buttoninfo, ctx, kHIThemeOrientationNormal, NULL);

								CGContextSetRGBFillColor(ctx, 0.0f, 0.0f, 0.0f, 1.0f);

								rct.size.width  = (float) cfListAddrColumnWidth;
								rct.origin.y++;
								textinfo.horizontalFlushness = kHIThemeTextHorizontalFlushCenter;
								str = CFCopyLocalizedString(CFSTR("Address"), "address");
								if (str)
								{
									err = HIThemeDrawTextBox(str, &rct, &textinfo, ctx, kHIThemeOrientationNormal);
									CFRelease(str);
								}

								rct = bounds;
								rct.size.height = 16.0f;
								rct.size.width -= (float) cfListAddrColumnWidth;
								rct.origin.x   += (float) cfListAddrColumnWidth;
								err = HIThemeDrawButton(&rct, &buttoninfo, ctx, kHIThemeOrientationNormal, NULL);

								CGContextSetRGBFillColor(ctx, 0.0f, 0.0f, 0.0f, 1.0f);

								rct.size.width -= 28.0f;
								rct.origin.y++;
								textinfo.horizontalFlushness = kHIThemeTextHorizontalFlushRight;
								str = CFCopyLocalizedString(CFSTR("Value"), "value");
								if (str)
								{
									err = HIThemeDrawTextBox(str, &rct, &textinfo, ctx, kHIThemeOrientationNormal);
									CFRelease(str);
								}

								rct = bounds;
								rct.size.height = 16.0f;
								rct.size.width  = 16.0f;
								rct.origin.x    = bounds.size.width - 16.0f;
								err = HIThemeDrawButton(&rct, &buttoninfo, ctx, kHIThemeOrientationNormal, NULL);

								CGContextRestoreGState(ctx);
							}

							result = noErr;
						}
					}

					break;
				}
			}

			break;
		}
	}

	return (result);
}

static void CheatFinderListViewDraw (CGContextRef ctx, HIRect *bounds, ListViewData *myData)
{
	static Boolean	init = true;

	if (systemVersion >= 0x1050)
	{
		static CGRect			aRct, vRct;

		CTLineRef				line;
		CFDictionaryRef			attr;
		CFAttributedStringRef	astr;
		CFStringRef				str;
		HIRect					lineBounds;
		SInt32					start, end, val, max;
		float					ax, vx, y, f;
		char					format[32], t1[64], t2[64];

		CFStringRef				keys[] = { kCTFontAttributeName, kCTForegroundColorAttributeName        };
		CFTypeRef				bval[] = { cfListLineCTFontRef,  CGColorGetConstantColor(kCGColorBlack) },
								wval[] = { cfListLineCTFontRef,  CGColorGetConstantColor(kCGColorWhite) };

		CheatFinderMakeValueFormat(format);

		start = (SInt32)  (myData->originPoint.y / myData->lineSize.height);
		end   = (SInt32) ((myData->originPoint.y + bounds->size.height) / myData->lineSize.height) + 1;

		y = start * myData->lineSize.height - myData->originPoint.y;

		lineBounds = *bounds;
		lineBounds.size.height = myData->lineSize.height;
		lineBounds.origin.y = y;

		val = GetControl32BitValue(myData->view) - 1;
		max = GetControl32BitMaximum(myData->view);

		attr = CFDictionaryCreate(kCFAllocatorDefault, (const void **) &keys, (const void **) &bval, sizeof(keys) / sizeof(keys[0]), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);

		CGContextSetTextMatrix(ctx, CGAffineTransformIdentity);

		if (init)
		{
			CGContextSetTextPosition(ctx, 0.0f, 0.0f);

			astr = CFAttributedStringCreate(kCFAllocatorDefault, CFSTR("FFFFFF"), attr);
			line = CTLineCreateWithAttributedString(astr);
			aRct = CTLineGetImageBounds(line, ctx);
			CFRelease(line);
			CFRelease(astr);

			astr = CFAttributedStringCreate(kCFAllocatorDefault, CFSTR("FFFFFFFFFFF"), attr);
			line = CTLineCreateWithAttributedString(astr);
			vRct = CTLineGetImageBounds(line, ctx);
			CFRelease(line);
			CFRelease(astr);

			init = false;
		}

		ax = (float) (int) (((float) cfListAddrColumnWidth - 2.0 - aRct.size.width) / 2.0);
		vx = (float) (int) (lineBounds.origin.x + lineBounds.size.width - vRct.size.width - 12.0);

		for (int i = start; i <= end; i++)
		{
			if ((i == val) && cfNumRows)
				CGContextSetRGBFillColor(ctx,  59.0f / 256.0f, 124.0f / 256.0f, 212.0f / 256.0f, 1.0f);
			else
			if ((i - start) % 2 == 0)
				CGContextSetRGBFillColor(ctx, 256.0f / 256.0f, 256.0f / 256.0f, 256.0f / 256.0f, 1.0f);
			else
				CGContextSetRGBFillColor(ctx, 237.0f / 256.0f, 244.0f / 256.0f, 254.0f / 256.0f, 1.0f);

			CGContextFillRect(ctx, lineBounds);

			if (i < max)
			{
				CGContextScaleCTM(ctx, 1, -1);

				if (i == val)
				{
					CFRelease(attr);
					attr = CFDictionaryCreate(kCFAllocatorDefault, (const void **) &keys, (const void **) &wval, sizeof(keys) / sizeof(keys[0]), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
				}

				f = -(y + 12.0f);

				sprintf(t1, "%06lX", cfAddress[i] + 0x7E0000);
				str = CFStringCreateWithCString(kCFAllocatorDefault, t1, kCFStringEncodingUTF8);
				astr = CFAttributedStringCreate(kCFAllocatorDefault, str, attr);
				line = CTLineCreateWithAttributedString(astr);
				CGContextSetTextPosition(ctx, ax, f);
				CTLineDraw(line, ctx);
				CFRelease(line);
				CFRelease(astr);
				CFRelease(str);

				sprintf(t2, format, CheatFinderReadBytes(cfCurrentRAM, cfAddress[i]));
				strcpy(t1, "            ");
				t1[11 - strlen(t2)] = 0;
				strcat(t1, t2);
				str = CFStringCreateWithCString(kCFAllocatorDefault, t1, kCFStringEncodingUTF8);
				astr = CFAttributedStringCreate(kCFAllocatorDefault, str, attr);
				line = CTLineCreateWithAttributedString(astr);
				CGContextSetTextPosition(ctx, vx, f);
				CTLineDraw(line, ctx);
				CFRelease(line);
				CFRelease(astr);
				CFRelease(str);

				CGContextScaleCTM(ctx, 1, -1);

				if (i == val)
				{
					CFRelease(attr);
					attr = CFDictionaryCreate(kCFAllocatorDefault, (const void **) &keys, (const void **) &bval, sizeof(keys) / sizeof(keys[0]), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
				}
			}

			y += myData->lineSize.height;
			lineBounds.origin.y += myData->lineSize.height;
		}

		CFRelease(attr);
	}
#ifdef MAC_TIGER_PANTHER_SUPPORT
	else
	{
		static Rect		aRect = { 0, 0, 0, 0 }, vRect = { 0, 0, 0, 0 };

		OSStatus		err;
		ATSUTextLayout	layout;
		HIRect			lineBounds;
		UniCharCount	runLength[1], len;
		SInt32			start, end, val, max;
		Fixed			ax, vx, f;
		float			y;
		UniChar			unistr[64];
		char			format[32], t1[64], t2[64];

		ATSUAttributeTag		theTags[]   = { kATSUCGContextTag    };
		ByteCount				theSizes[]  = { sizeof(CGContextRef) };
		ATSUAttributeValuePtr	theValues[] = { &ctx                 };

		CheatFinderMakeValueFormat(format);

		start = (SInt32)  (myData->originPoint.y / myData->lineSize.height);
		end   = (SInt32) ((myData->originPoint.y + bounds->size.height) / myData->lineSize.height) + 1;

		y = start * myData->lineSize.height - myData->originPoint.y;

		lineBounds = *bounds;
		lineBounds.size.height = myData->lineSize.height;
		lineBounds.origin.y = y;

		val = GetControl32BitValue(myData->view) - 1;
		max = GetControl32BitMaximum(myData->view);

		if (init)
		{
			f = Long2Fix(0);
			for (unsigned int n = 0; n < 11; n++)
				unistr[n] = 'F';

			len = runLength[0] = 6;
			err = ATSUCreateTextLayoutWithTextPtr(unistr, kATSUFromTextBeginning, kATSUToTextEnd, len, 1, runLength, &cfListLineATSUStyle, &layout);
			err = ATSUSetLayoutControls(layout, sizeof(theTags) / sizeof(theTags[0]), theTags, theSizes, theValues);
			err = ATSUMeasureTextImage(layout, kATSUFromTextBeginning, kATSUToTextEnd, f, f, &aRect);
			err = ATSUDisposeTextLayout(layout);

			len = runLength[0] = 11;
			err = ATSUCreateTextLayoutWithTextPtr(unistr, kATSUFromTextBeginning, kATSUToTextEnd, len, 1, runLength, &cfListLineATSUStyle, &layout);
			err = ATSUSetLayoutControls(layout, sizeof(theTags) / sizeof(theTags[0]), theTags, theSizes, theValues);
			err = ATSUMeasureTextImage(layout, kATSUFromTextBeginning, kATSUToTextEnd, f, f, &vRect);
			err = ATSUDisposeTextLayout(layout);

			init = false;
		}

		ax = Long2Fix((cfListAddrColumnWidth - 2 - (aRect.right - aRect.left)) >> 1);
		vx = Long2Fix((int) (lineBounds.origin.x + lineBounds.size.width) - (vRect.right - vRect.left) - 13);

		for (int i = start; i <= end; i++)
		{
			if ((i == val) && cfNumRows)
				CGContextSetRGBFillColor(ctx,  59.0f / 256.0f, 124.0f / 256.0f, 212.0f / 256.0f, 1.0f);
			else
			if ((i - start) % 2 == 0)
				CGContextSetRGBFillColor(ctx, 256.0f / 256.0f, 256.0f / 256.0f, 256.0f / 256.0f, 1.0f);
			else
				CGContextSetRGBFillColor(ctx, 237.0f / 256.0f, 244.0f / 256.0f, 254.0f / 256.0f, 1.0f);

			CGContextFillRect(ctx, lineBounds);

			if (i < max)
			{
				CGContextScaleCTM(ctx, 1, -1);

				if (i == val)
					CGContextSetRGBFillColor(ctx, 1.0f, 1.0f, 1.0f, 1.0f);
				else
					CGContextSetRGBFillColor(ctx, 0.0f, 0.0f, 0.0f, 1.0f);

				f = Long2Fix(-((int) y + 12));

				sprintf(t1, "%06lX", cfAddress[i] + 0x7E0000);
				len = runLength[0] = strlen(t1);
				for (unsigned int n = 0; n < len; n++)
					unistr[n] = t1[n];
				err = ATSUCreateTextLayoutWithTextPtr(unistr, kATSUFromTextBeginning, kATSUToTextEnd, len, 1, runLength, &cfListLineATSUStyle, &layout);
				err = ATSUSetLayoutControls(layout, sizeof(theTags) / sizeof(theTags[0]), theTags, theSizes, theValues);
				err = ATSUDrawText(layout, kATSUFromTextBeginning, kATSUToTextEnd, ax, f);
				err = ATSUDisposeTextLayout(layout);

				sprintf(t2, format, CheatFinderReadBytes(cfCurrentRAM, cfAddress[i]));
				strcpy(t1, "            ");
				t1[11 - strlen(t2)] = 0;
				strcat(t1, t2);
				len = runLength[0] = strlen(t1);
				for (unsigned int n = 0; n < len; n++)
					unistr[n] = t1[n];
				err = ATSUCreateTextLayoutWithTextPtr(unistr, kATSUFromTextBeginning, kATSUToTextEnd, len, 1, runLength, &cfListLineATSUStyle, &layout);
				err = ATSUSetLayoutControls(layout, sizeof(theTags) / sizeof(theTags[0]), theTags, theSizes, theValues);
				err = ATSUDrawText(layout, kATSUFromTextBeginning, kATSUToTextEnd, vx, f);
				err = ATSUDisposeTextLayout(layout);

				CGContextScaleCTM(ctx, 1, -1);
			}

			y += myData->lineSize.height;
			lineBounds.origin.y += myData->lineSize.height;
		}
	}
#endif
}

static HIViewPartCode CheatFinderListViewFindPart (EventRef inEvent, ListViewData *myData, SInt32 *whichLine)
{
	OSStatus		err;
	HIViewPartCode	part;
	HIPoint			hipt;
	SInt32			start, line;
	float			y;

	part = kControlNoPart;

	start = (SInt32) (myData->originPoint.y / myData->lineSize.height);
	y = start * myData->lineSize.height - myData->originPoint.y;

	err = GetEventParameter(inEvent, kEventParamMouseLocation, typeHIPoint, NULL, sizeof(hipt), NULL, &hipt);
	if (err == noErr)
	{
		line = start + (SInt32) ((hipt.y - y - 1) / myData->lineSize.height) + 1;

		if (line <= GetControl32BitMaximum(myData->view))
			part = kControlListLinePart;

		if (whichLine != NULL)
			*whichLine = line;
	}

	return (part);
}

static float CheatFinderListViewSanityCheck (float where, ListViewData *myData)
{
	HIRect	bounds;
	HISize	imageSize;

	HIViewGetBounds(myData->view, &bounds);
	imageSize = myData->lineSize;
	imageSize.height *= GetControl32BitMaximum(myData->view);

	if (where + bounds.size.height > imageSize.height)
		where = imageSize.height - bounds.size.height;
	if (where < 0)
		where = 0;

	return (where);
}

static void CheatFinderListViewScrollToThere (float where, ListViewData *myData)
{
	OSStatus	err;
	EventRef	theEvent;
	HIPoint		whereP = { 0.0f, where };

	err = CreateEvent(kCFAllocatorDefault, kEventCheatFinderList, kEventScrollableScrollThere, GetCurrentEventTime(), kEventAttributeUserEvent, &theEvent);
	if (err == noErr)
	{
		err = SetEventParameter(theEvent, kEventParamOrigin, typeHIPoint, sizeof(whereP), &whereP);
		if (err == noErr)
			err = SendEventToEventTarget(theEvent, GetControlEventTarget(myData->view));

		ReleaseEvent(theEvent);
	}
}

static pascal OSStatus CheatFinderListViewHandler (EventHandlerCallRef inHandlerCallRef, EventRef inEvent, void *inUserData)
{
	OSStatus		err, result = eventNotHandledErr;
	ListViewData	*myData = (ListViewData *) inUserData;

	switch (GetEventClass(inEvent))
	{
		case kEventClassHIObject:
		{
			switch (GetEventKind(inEvent))
			{
				case kEventHIObjectConstruct:
				{
					ListViewData	*data;

					data = (ListViewData *) calloc(1, sizeof(ListViewData));
					if (data)
					{
						HIViewRef	epView;

						err = GetEventParameter(inEvent, kEventParamHIObjectInstance, typeHIObjectRef, NULL, sizeof(epView), NULL, &epView);
						if (err == noErr)
						{
							data->view = epView;
							data->originPoint.x   =  0.0f;
							data->originPoint.y   =  0.0f;
							data->lineSize.width  =  0.0f;
							data->lineSize.height = 16.0f;
							data->inFocus = false;

							SetControlID(epView, &kCheatFinderListViewID);

							result = SetEventParameter(inEvent, kEventParamHIObjectInstance, typeVoidPtr, sizeof(data), &data);
						}
					}

					break;
				}

				case kEventHIObjectDestruct:
				{
					if (myData)
						free(myData);

					result = noErr;
					break;
				}

				case kEventHIObjectInitialize:
				{
					result = CallNextEventHandler(inHandlerCallRef, inEvent);
					break;
				}
			}

			break;
		}

		case kEventClassScrollable:
		{
			switch (GetEventKind(inEvent))
			{
				case kEventScrollableGetInfo:
				{
					HIRect	bounds;
					HISize	imageSize;

					HIViewGetBounds(myData->view, &bounds);
					imageSize = myData->lineSize;
					imageSize.height *= GetControl32BitMaximum(myData->view);

					err = SetEventParameter(inEvent, kEventParamImageSize, typeHISize,  sizeof(imageSize),           &imageSize);
					err = SetEventParameter(inEvent, kEventParamViewSize,  typeHISize,  sizeof(bounds.size),         &bounds.size);
					err = SetEventParameter(inEvent, kEventParamLineSize,  typeHISize,  sizeof(myData->lineSize),    &myData->lineSize);
					err = SetEventParameter(inEvent, kEventParamOrigin,    typeHIPoint, sizeof(myData->originPoint), &myData->originPoint);

					result = noErr;
					break;
				}

				case kEventScrollableScrollTo:
				{
					HIPoint	where;

					err = GetEventParameter(inEvent, kEventParamOrigin, typeHIPoint, NULL, sizeof(where), NULL, &where);
					if (err == noErr)
					{
						where.y = CheatFinderListViewSanityCheck(where.y, myData);
						if ((int) myData->originPoint.y == (int) where.y)
							break;
						myData->originPoint.y = where.y;

						HIViewSetNeedsDisplay(myData->view, true);
					}

					break;
				}
			}

			break;
		}

		case kEventCheatFinderList:
		{
			switch (GetEventKind(inEvent))
			{
				case kEventScrollableScrollThere:
				{
					HIPoint	where;

					err = GetEventParameter(inEvent, kEventParamOrigin, typeHIPoint, NULL, sizeof(where), NULL, &where);
					if (err == noErr)
					{
						where.y = CheatFinderListViewSanityCheck(where.y, myData);
						if ((int) myData->originPoint.y != (int) where.y)
						{
							myData->originPoint.y = where.y;
							SetControl32BitValue(HIViewGetNextView(myData->view), (SInt32) myData->originPoint.y);

							HIViewSetNeedsDisplay(myData->view, true);
						}
					}

					break;
				}
			}

			break;
		}

		case kEventClassControl:
		{
			switch (GetEventKind(inEvent))
			{
				case kEventControlDraw:
				{
					CGContextRef	ctx;

					err = GetEventParameter(inEvent, kEventParamCGContextRef, typeCGContextRef, NULL, sizeof(ctx), NULL, &ctx);
					if (err == noErr)
					{
						HIRect	bounds;

						HIViewGetBounds(myData->view, &bounds);

						CGContextSaveGState(ctx);
						CheatFinderListViewDraw(ctx, &bounds, myData);
						CGContextRestoreGState(ctx);

						result = noErr;
					}

					break;
				}

				case kEventControlHitTest:
				{
					HIViewPartCode	part;

					part = CheatFinderListViewFindPart(inEvent, myData, NULL);

					result = SetEventParameter(inEvent, kEventParamControlPart, typeControlPartCode, sizeof(part), &part);

					break;
				}

				case kEventControlTrack:
				{
					MouseTrackingResult	trackResult;
					WindowRef			window;
					HIViewRef			contentView;
					HIViewPartCode		part;
					HIRect				frame;
					SInt32				whichLine = 0;

					part = CheatFinderListViewFindPart(inEvent, myData, &whichLine);
					if (part == kControlListLinePart)
						SetControl32BitValue(myData->view, whichLine);

					window = GetControlOwner(myData->view);

					HIViewFindByID(HIViewGetRoot(window), kHIViewWindowContentID, &contentView);
					HIViewGetFrame(myData->view, &frame);
					HIViewConvertRect(&frame, myData->view, contentView);

					HIPoint		hipt;
				#ifdef MAC_TIGER_PANTHER_SUPPORT
					CGrafPtr	oldPort;
					Point		qdpt;
					Boolean		portChanged = false;

					if (systemVersion < 0x1050)
						portChanged = QDSwapPort(GetWindowPort(window), &oldPort);
				#endif

					if (systemVersion >= 0x1050)
						err = HIViewTrackMouseLocation(contentView, 0, kEventDurationForever, 0, NULL, &hipt, NULL, NULL, &trackResult);
				#ifdef MAC_TIGER_PANTHER_SUPPORT
					else
						TrackMouseLocation(NULL, &qdpt, &trackResult);
				#endif

					while (trackResult != kMouseTrackingMouseUp)
					{
						SInt32	start, line = 0;
						float	y;

						start = (SInt32) (myData->originPoint.y / myData->lineSize.height);
						y = start * myData->lineSize.height - myData->originPoint.y;

						if (systemVersion >= 0x1050)
						{
							line = (SInt32) ((hipt.y - frame.origin.y - y - 1) / myData->lineSize.height) + 1;
							if (hipt.y < frame.origin.y)
								line--;
						}
					#ifdef MAC_TIGER_PANTHER_SUPPORT
						else
						{
							line = (SInt32) ((qdpt.v - frame.origin.y - y - 1) / myData->lineSize.height) + 1;
							if (qdpt.v < frame.origin.y)
								line--;
						}
					#endif
						line += start;

						if (line != whichLine)
						{
							whichLine = line;
							SetControl32BitValue(myData->view, whichLine);

							SInt32	goThere = -1;

							if (whichLine <= start)
								goThere = whichLine - 1;
							else
							if (whichLine >=  start + (SInt32) (frame.size.height / myData->lineSize.height) + 1)
								goThere = whichLine - (SInt32) (frame.size.height / myData->lineSize.height) - 1;

					 		if (goThere != -1)
							{
								float	where;

								where = goThere * myData->lineSize.height;
								where = CheatFinderListViewSanityCheck(where, myData);
								if ((int) where != (int) myData->originPoint.y)
									CheatFinderListViewScrollToThere(where, myData);
							}
						}

						if (systemVersion >= 0x1050)
							err = HIViewTrackMouseLocation(contentView, 0, kEventDurationForever, 0, NULL, &hipt, NULL, NULL, &trackResult);
					#ifdef MAC_TIGER_PANTHER_SUPPORT
						else
							TrackMouseLocation(NULL, &qdpt, &trackResult);
					#endif
					}

				#ifdef MAC_TIGER_PANTHER_SUPPORT
					if (systemVersion < 0x1050)
					{
						if (portChanged)
							QDSwapPort(oldPort, NULL);
					}
				#endif

					err = SetEventParameter(inEvent, kEventParamControlPart, typeControlPartCode, sizeof(part), &part);

					cfListSelection = GetControl32BitValue(myData->view) - 1;

					result = noErr;
					break;
				}

				case kEventControlValueFieldChanged:
				{
					EventRef	theEvent;

					myData->originPoint.y = CheatFinderListViewSanityCheck(myData->originPoint.y, myData);

					err = CreateEvent(kCFAllocatorDefault, kEventClassScrollable, kEventScrollableInfoChanged, GetCurrentEventTime(), kEventAttributeUserEvent, &theEvent);
					if (err == noErr)
					{
						err = SendEventToEventTarget(theEvent, GetControlEventTarget(myData->view));
						ReleaseEvent(theEvent);

						HIViewSetNeedsDisplay(myData->view, true);
					}

					break;
				}
			}

			break;
		}
	}

	return (result);
}
