1 /*****************************************************************************\
2      Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
3                 This file is licensed under the Snes9x License.
4    For further information, consult the LICENSE file in the root directory.
5 \*****************************************************************************/
6 
7 /***********************************************************************************
8   SNES9X for Mac OS (c) Copyright John Stiles
9 
10   Snes9x for Mac OS X
11 
12   (c) Copyright 2001 - 2011  zones
13   (c) Copyright 2002 - 2005  107
14   (c) Copyright 2002         PB1400c
15   (c) Copyright 2004         Alexander and Sander
16   (c) Copyright 2004 - 2005  Steven Seeger
17   (c) Copyright 2005         Ryan Vogt
18  ***********************************************************************************/
19 
20 
21 #include "port.h"
22 #include "cheats.h"
23 
24 #define __STDC_FORMAT_MACROS
25 #include <inttypes.h>
26 
27 #include "mac-prefix.h"
28 #include "mac-dialog.h"
29 #include "mac-os.h"
30 #include "mac-stringtools.h"
31 #include "mac-cheat.h"
32 
33 #define	kDataBrowser	'BRSR'
34 #define	kCmCheckBox		'CHK_'
35 #define	kCmAddress		'ADDR'
36 #define	kCmValue		'VALU'
37 #define	kCmDescription	'DESC'
38 #define	kNewButton		'NEW_'
39 #define	kDelButton		'DEL_'
40 #define	kAllButton		'ALL_'
41 
42 extern SCheatData	Cheat;
43 
44 typedef struct
45 {
46 	uint32	id;
47 	uint32	address;
48 	uint8	value;
49 	bool8	valid;
50 	bool8	enabled;
51 	char	description[22];
52 }	CheatItem;
53 
54 static WindowRef	wRef;
55 static HIViewRef	dbRef;
56 static CheatItem	citem[MAC_MAX_CHEATS];
57 static uint32		numofcheats;
58 
59 static void InitCheatItems (void);
60 static void ImportCheatItems (void);
61 static void DetachCheatItems (void);
62 static void AddCheatItem (void);
63 static void DeleteCheatItem (void);
64 static void EnableAllCheatItems (void);
65 static pascal void DBItemNotificationCallBack (HIViewRef, DataBrowserItemID, DataBrowserItemNotification);
66 static pascal Boolean DBCompareCallBack (HIViewRef, DataBrowserItemID, DataBrowserItemID, DataBrowserPropertyID);
67 static pascal OSStatus DBClientDataCallback (HIViewRef, DataBrowserItemID, DataBrowserPropertyID, DataBrowserItemDataRef, Boolean);
68 static pascal OSStatus CheatEventHandler (EventHandlerCallRef, EventRef, void *);
69 
70 
InitCheatItems(void)71 static void InitCheatItems (void)
72 {
73 	for (unsigned int i = 0; i < MAC_MAX_CHEATS; i++)
74 	{
75 		citem[i].id      = i + 1;
76 		citem[i].valid   = false;
77 		citem[i].enabled = false;
78 		citem[i].address = 0;
79 		citem[i].value   = 0;
80 		sprintf(citem[i].description, "Cheat %03" PRIu32, citem[i].id);
81 	}
82 }
83 
ImportCheatItems(void)84 static void ImportCheatItems (void)
85 {
86     int cheat_num = std::min((int)Cheat.g.size(), MAC_MAX_CHEATS);
87 	for (unsigned int i = 0; i < cheat_num; i++)
88 	{
89 		citem[i].valid   = true;
90 		citem[i].enabled = Cheat.g[i].enabled;
91 		citem[i].address = Cheat.g[i].c[0].address; // mac dialog only supports one cheat per group at the moment
92 		citem[i].value   = Cheat.g[i].c[0].byte;
93 		strncpy(citem[i].description, Cheat.g[i].name, 21);
94 		citem[i].description[21] = '\0';
95 	}
96 }
97 
DetachCheatItems(void)98 static void DetachCheatItems (void)
99 {
100 	S9xDeleteCheats();
101 
102 	for (unsigned int i = 0; i < MAC_MAX_CHEATS; i++)
103 	{
104 		if (citem[i].valid)
105 		{
106 			char code[10];
107 			snprintf(code, 10, "%x=%x", citem[i].address, citem[i].value);
108 			int index = S9xAddCheatGroup(citem[i].description, code);
109 			if(citem[i].enabled && index >= 0)
110 				S9xEnableCheatGroup(index);
111 		}
112 	}
113 }
114 
ConfigureCheat(void)115 void ConfigureCheat (void)
116 {
117 	if (!cartOpen)
118 		return;
119 
120 	OSStatus	err;
121 	IBNibRef	nibRef;
122 
123 	err = CreateNibReference(kMacS9XCFString, &nibRef);
124 	if (err == noErr)
125 	{
126 		err = CreateWindowFromNib(nibRef, CFSTR("CheatEntry"), &wRef);
127 		if (err == noErr)
128 		{
129 			DataBrowserCallbacks	callbacks;
130 			EventHandlerRef			eref;
131 			EventHandlerUPP			eUPP;
132 			EventTypeSpec			events[] = { { kEventClassCommand, kEventCommandProcess      },
133 												 { kEventClassCommand, kEventCommandUpdateStatus },
134 												 { kEventClassWindow,  kEventWindowClose         } };
135 			HIViewRef				ctl, root;
136 			HIViewID				cid;
137 
138 			root = HIViewGetRoot(wRef);
139 			cid.id = 0;
140 			cid.signature = kDataBrowser;
141 			HIViewFindByID(root, cid, &dbRef);
142 
143 		#ifdef MAC_PANTHER_SUPPORT
144 			if (systemVersion < 0x1040)
145 			{
146 				HISize	minSize;
147 				Rect	rct;
148 
149 				GetWindowBounds(wRef, kWindowContentRgn, &rct);
150 				minSize.width  = (float) (rct.right  - rct.left);
151 				minSize.height = (float) (rct.bottom - rct.top );
152 				err = SetWindowResizeLimits(wRef, &minSize, NULL);
153 			}
154 		#endif
155 
156 			callbacks.version = kDataBrowserLatestCallbacks;
157 			err = InitDataBrowserCallbacks(&callbacks);
158 			callbacks.u.v1.itemDataCallback = NewDataBrowserItemDataUPP(DBClientDataCallback);
159 			callbacks.u.v1.itemCompareCallback = NewDataBrowserItemCompareUPP(DBCompareCallBack);
160 			callbacks.u.v1.itemNotificationCallback = NewDataBrowserItemNotificationUPP(DBItemNotificationCallBack);
161 			err = SetDataBrowserCallbacks(dbRef, &callbacks);
162 
163 			if (systemVersion >= 0x1040)
164 				err = DataBrowserChangeAttributes(dbRef, kDataBrowserAttributeListViewAlternatingRowColors, kDataBrowserAttributeNone);
165 
166 			InitCheatItems();
167 			ImportCheatItems();
168 
169 			DataBrowserItemID	*id;
170 
171 			id = new DataBrowserItemID[MAC_MAX_CHEATS];
172 			if (!id)
173 				QuitWithFatalError(0, "cheat 01");
174 
175 			numofcheats = 0;
176 
177 			for (unsigned int i = 0; i < MAC_MAX_CHEATS; i++)
178 			{
179 				if (citem[i].valid)
180 				{
181 					id[numofcheats] = citem[i].id;
182 					numofcheats++;
183 				}
184 			}
185 
186 			if (numofcheats)
187 				err = AddDataBrowserItems(dbRef, kDataBrowserNoItem, numofcheats, id, kDataBrowserItemNoProperty);
188 
189 			delete [] id;
190 
191 			cid.signature = kNewButton;
192 			HIViewFindByID(root, cid, &ctl);
193 			if (numofcheats == MAC_MAX_CHEATS)
194 				err = DeactivateControl(ctl);
195 			else
196 				err = ActivateControl(ctl);
197 
198 			cid.signature = kAllButton;
199 			HIViewFindByID(root, cid, &ctl);
200 			if (numofcheats == 0)
201 				err = DeactivateControl(ctl);
202 			else
203 				err = ActivateControl(ctl);
204 
205 			cid.signature = kDelButton;
206 			HIViewFindByID(root, cid, &ctl);
207 			err = DeactivateControl(ctl);
208 
209 			eUPP = NewEventHandlerUPP(CheatEventHandler);
210 			err = InstallWindowEventHandler(wRef, eUPP, GetEventTypeCount(events), events, (void *) wRef, &eref);
211 
212 			err = SetKeyboardFocus(wRef, dbRef, kControlFocusNextPart);
213 
214 			MoveWindowPosition(wRef, kWindowCheatEntry, true);
215 			ShowWindow(wRef);
216 			err = RunAppModalLoopForWindow(wRef);
217 			HideWindow(wRef);
218 			SaveWindowPosition(wRef, kWindowCheatEntry);
219 
220 			err = RemoveEventHandler(eref);
221 			DisposeEventHandlerUPP(eUPP);
222 
223 			DisposeDataBrowserItemNotificationUPP(callbacks.u.v1.itemNotificationCallback);
224 			DisposeDataBrowserItemCompareUPP(callbacks.u.v1.itemCompareCallback);
225 			DisposeDataBrowserItemDataUPP(callbacks.u.v1.itemDataCallback);
226 
227 			CFRelease(wRef);
228 
229 			DetachCheatItems();
230 		}
231 
232 		DisposeNibReference(nibRef);
233 	}
234 }
235 
AddCheatItem(void)236 static void AddCheatItem (void)
237 {
238 	OSStatus			err;
239 	HIViewRef			ctl, root;
240 	HIViewID			cid;
241 	DataBrowserItemID	id[1];
242 	unsigned int		i;
243 
244 	if (numofcheats == MAC_MAX_CHEATS)
245 		return;
246 
247 	for (i = 0; i < MAC_MAX_CHEATS; i++)
248 		if (citem[i].valid == false)
249 			break;
250 
251 	if (i == MAC_MAX_CHEATS)
252 		return;
253 
254 	numofcheats++;
255 	citem[i].valid   = true;
256 	citem[i].enabled = false;
257 	citem[i].address = 0;
258 	citem[i].value   = 0;
259 	sprintf(citem[i].description, "Cheat %03" PRIu32, citem[i].id);
260 
261 	id[0] = citem[i].id;
262 	err = AddDataBrowserItems(dbRef, kDataBrowserNoItem, 1, id, kDataBrowserItemNoProperty);
263 	err = RevealDataBrowserItem(dbRef, id[0], kCmAddress, true);
264 
265 	root = HIViewGetRoot(wRef);
266 	cid.id = 0;
267 
268 	if (numofcheats == MAC_MAX_CHEATS)
269 	{
270 		cid.signature = kNewButton;
271 		HIViewFindByID(root, cid, &ctl);
272 		err = DeactivateControl(ctl);
273 	}
274 
275 	if (numofcheats)
276 	{
277 		cid.signature = kAllButton;
278 		HIViewFindByID(root, cid, &ctl);
279 		err = ActivateControl(ctl);
280 	}
281 }
282 
DeleteCheatItem(void)283 static void DeleteCheatItem (void)
284 {
285 	OSStatus	err;
286 	HIViewRef	ctl, root;
287 	HIViewID	cid;
288 	Handle		selectedItems;
289 	ItemCount	selectionCount;
290 
291 	selectedItems = NewHandle(0);
292 	if (!selectedItems)
293 		return;
294 
295 	err = GetDataBrowserItems(dbRef, kDataBrowserNoItem, true, kDataBrowserItemIsSelected, selectedItems);
296 	selectionCount = (GetHandleSize(selectedItems) / sizeof(DataBrowserItemID));
297 
298 	if (selectionCount == 0)
299 	{
300 		DisposeHandle(selectedItems);
301 		return;
302 	}
303 
304 	err = RemoveDataBrowserItems(dbRef, kDataBrowserNoItem, selectionCount, (DataBrowserItemID *) *selectedItems, kDataBrowserItemNoProperty);
305 
306 	for (unsigned int i = 0; i < selectionCount; i++)
307 	{
308 		citem[((DataBrowserItemID *) (*selectedItems))[i] - 1].valid   = false;
309 		citem[((DataBrowserItemID *) (*selectedItems))[i] - 1].enabled = false;
310 		numofcheats--;
311 	}
312 
313 	DisposeHandle(selectedItems);
314 
315 	root = HIViewGetRoot(wRef);
316 	cid.id = 0;
317 
318 	if (numofcheats < MAC_MAX_CHEATS)
319 	{
320 		cid.signature = kNewButton;
321 		HIViewFindByID(root, cid, &ctl);
322 		err = ActivateControl(ctl);
323 	}
324 
325 	if (numofcheats == 0)
326 	{
327 		cid.signature = kAllButton;
328 		HIViewFindByID(root, cid, &ctl);
329 		err = DeactivateControl(ctl);
330 	}
331 }
332 
EnableAllCheatItems(void)333 static void EnableAllCheatItems (void)
334 {
335 	OSStatus	err;
336 
337 	for (unsigned int i = 0; i < MAC_MAX_CHEATS; i++)
338 		if (citem[i].valid)
339 			citem[i].enabled = true;
340 
341 	err = UpdateDataBrowserItems(dbRef, kDataBrowserNoItem, kDataBrowserNoItem, NULL, kDataBrowserItemNoProperty, kCmCheckBox);
342 }
343 
DBClientDataCallback(HIViewRef browser,DataBrowserItemID itemID,DataBrowserPropertyID property,DataBrowserItemDataRef itemData,Boolean changeValue)344 static pascal OSStatus DBClientDataCallback (HIViewRef browser, DataBrowserItemID itemID, DataBrowserPropertyID property, DataBrowserItemDataRef itemData, Boolean changeValue)
345 {
346 	OSStatus 	err, result;
347 	CFStringRef	str;
348 	Boolean		r;
349 	uint32		address;
350 	uint8		value;
351 	char		code[256];
352 
353 	result = noErr;
354 
355 	switch (property)
356 	{
357 		case kCmCheckBox:
358 			ThemeButtonValue	buttonValue;
359 
360             if (changeValue)
361 			{
362 				err = GetDataBrowserItemDataButtonValue(itemData, &buttonValue);
363 				citem[itemID - 1].enabled = (buttonValue == kThemeButtonOn) ? true : false;
364  	        }
365 			else
366 				err = SetDataBrowserItemDataButtonValue(itemData, citem[itemID - 1].enabled ? kThemeButtonOn : kThemeButtonOff);
367 
368 			break;
369 
370 		case kCmAddress:
371 			if (changeValue)
372 			{
373 				err = GetDataBrowserItemDataText(itemData, &str);
374 				r = CFStringGetCString(str, code, 256, CFStringGetSystemEncoding());
375 				CFRelease(str);
376 				if (r)
377 				{
378 					Boolean	translated;
379 
380 					if (S9xProActionReplayToRaw(code, address, value) == NULL)
381 						translated = true;
382 					else
383 					if (S9xGameGenieToRaw(code, address, value) == NULL)
384 						translated = true;
385 					else
386 					{
387 						translated = false;
388 						if (sscanf(code, "%" SCNx32, &address) != 1)
389 							address = 0;
390 						else
391 							address &= 0xFFFFFF;
392 					}
393 
394 					citem[itemID - 1].address = address;
395 					sprintf(code, "%06" PRIX32, address);
396 					str = CFStringCreateWithCString(kCFAllocatorDefault, code, CFStringGetSystemEncoding());
397 					err = SetDataBrowserItemDataText(itemData, str);
398 					CFRelease(str);
399 
400 					if (translated)
401 					{
402 						DataBrowserItemID	id[1];
403 
404 						citem[itemID - 1].value = value;
405 						id[0] = itemID;
406 						err = UpdateDataBrowserItems(browser, kDataBrowserNoItem, 1, id, kDataBrowserItemNoProperty, kCmValue);
407 					}
408 				}
409 			}
410 			else
411 			{
412 				sprintf(code, "%06" PRIX32, citem[itemID - 1].address);
413 				str = CFStringCreateWithCString(kCFAllocatorDefault, code, CFStringGetSystemEncoding());
414 				err = SetDataBrowserItemDataText(itemData, str);
415 				CFRelease(str);
416 			}
417 
418 			break;
419 
420 		case kCmValue:
421 			if (changeValue)
422 			{
423 				err = GetDataBrowserItemDataText(itemData, &str);
424 				r = CFStringGetCString(str, code, 256, CFStringGetSystemEncoding());
425 				CFRelease(str);
426 				if (r)
427 				{
428 					uint32	byte;
429 
430 					if (sscanf(code, "%" SCNx32, &byte) == 1)
431 						citem[itemID - 1].value = (uint8) byte;
432 					else
433 					{
434 						citem[itemID - 1].value = 0;
435 						err = SetDataBrowserItemDataText(itemData, CFSTR("00"));
436 					}
437 				}
438 			}
439 			else
440 			{
441 				sprintf(code, "%02" PRIX8, citem[itemID - 1].value);
442 				str = CFStringCreateWithCString(kCFAllocatorDefault, code, CFStringGetSystemEncoding());
443 				err = SetDataBrowserItemDataText(itemData, str);
444 				CFRelease(str);
445 			}
446 
447 			break;
448 
449 		case kCmDescription:
450 			if (changeValue)
451 			{
452 				code[0] = 0;
453 				err = GetDataBrowserItemDataText(itemData, &str);
454 				strcpy(code, GetMultiByteCharacters(str, 19));
455 				CFRelease(str);
456 
457 				if (code[0] == 0)
458 				{
459 					code[0] = ' ';
460 					code[1] = 0;
461 				}
462 
463 				strcpy(citem[itemID - 1].description, code);
464 			}
465 			else
466 			{
467 				str = CFStringCreateWithCString(kCFAllocatorDefault, citem[itemID - 1].description, CFStringGetSystemEncoding());
468 				err = SetDataBrowserItemDataText(itemData, str);
469 				CFRelease(str);
470 			}
471 
472 			break;
473 
474 		case kDataBrowserItemIsActiveProperty:
475 			err = SetDataBrowserItemDataBooleanValue(itemData, true);
476 			break;
477 
478 		case kDataBrowserItemIsEditableProperty:
479 			err = SetDataBrowserItemDataBooleanValue(itemData, true);
480 			break;
481 
482 		default:
483 			result = errDataBrowserPropertyNotSupported;
484 	}
485 
486 	return (result);
487 }
488 
DBCompareCallBack(HIViewRef browser,DataBrowserItemID itemOne,DataBrowserItemID itemTwo,DataBrowserPropertyID sortProperty)489 static pascal Boolean DBCompareCallBack (HIViewRef browser, DataBrowserItemID itemOne, DataBrowserItemID itemTwo, DataBrowserPropertyID sortProperty)
490 {
491 	Boolean	result = false;
492 
493 	switch (sortProperty)
494 	{
495 		case kCmCheckBox:
496 			result = (citem[itemOne - 1].enabled && !(citem[itemTwo - 1].enabled)) ? true : false;
497 			break;
498 
499 		case kCmAddress:
500 			result = (citem[itemOne - 1].address <    citem[itemTwo - 1].address)  ? true : false;
501 			break;
502 
503 		case kCmValue:
504 			result = (citem[itemOne - 1].value   <    citem[itemTwo - 1].value)    ? true : false;
505 			break;
506 
507 		case kCmDescription:
508 			result = (strcmp(citem[itemOne - 1].description, citem[itemTwo - 1].description) < 0) ? true : false;
509 	}
510 
511 	return (result);
512 }
513 
DBItemNotificationCallBack(HIViewRef browser,DataBrowserItemID itemID,DataBrowserItemNotification message)514 static pascal void DBItemNotificationCallBack (HIViewRef browser, DataBrowserItemID itemID, DataBrowserItemNotification message)
515 {
516 	OSStatus	err;
517 	HIViewRef	ctl;
518 	HIViewID	cid = { kDelButton, 0 };
519 	ItemCount	selectionCount;
520 
521 	switch (message)
522 	{
523 		case kDataBrowserSelectionSetChanged:
524 			HIViewFindByID(HIViewGetRoot(wRef), cid, &ctl);
525 
526 			err = GetDataBrowserItemCount(browser, kDataBrowserNoItem, true, kDataBrowserItemIsSelected, &selectionCount);
527 			if (selectionCount == 0)
528 				err = DeactivateControl(ctl);
529 			else
530 				err = ActivateControl(ctl);
531 	}
532 }
533 
CheatEventHandler(EventHandlerCallRef inHandlerCallRef,EventRef inEvent,void * inUserData)534 static pascal OSStatus CheatEventHandler (EventHandlerCallRef inHandlerCallRef, EventRef inEvent, void *inUserData)
535 {
536 	OSStatus	err, result = eventNotHandledErr;
537 	WindowRef	tWindowRef;
538 
539 	tWindowRef = (WindowRef) inUserData;
540 
541 	switch (GetEventClass(inEvent))
542 	{
543 		case kEventClassWindow:
544 			switch (GetEventKind(inEvent))
545 			{
546 				case kEventWindowClose:
547 					QuitAppModalLoopForWindow(tWindowRef);
548 					result = noErr;
549 					break;
550 			}
551 
552 			break;
553 
554 		case kEventClassCommand:
555 			switch (GetEventKind(inEvent))
556 			{
557 				HICommand	tHICommand;
558 
559 				case kEventCommandUpdateStatus:
560 					err = GetEventParameter(inEvent, kEventParamDirectObject, typeHICommand, NULL, sizeof(HICommand), NULL, &tHICommand);
561 					if (err == noErr && tHICommand.commandID == 'clos')
562 					{
563 						UpdateMenuCommandStatus(true);
564 						result = noErr;
565 					}
566 
567 					break;
568 
569 				case kEventCommandProcess:
570 					err = GetEventParameter(inEvent, kEventParamDirectObject, typeHICommand, NULL, sizeof(HICommand), NULL, &tHICommand);
571 					if (err == noErr)
572 					{
573 						switch (tHICommand.commandID)
574 						{
575 							case kNewButton:
576 								AddCheatItem();
577 								result = noErr;
578 								break;
579 
580 							case kDelButton:
581 								DeleteCheatItem();
582 								result = noErr;
583 								break;
584 
585 							case kAllButton:
586 								EnableAllCheatItems();
587 								result = noErr;
588 						}
589 					}
590 			}
591 	}
592 
593 	return (result);
594 }
595