Files
Project64-Legacy/Cheat.c
2023-08-19 16:29:31 +02:00

2004 lines
58 KiB
C

/*
* Project 64 - A Nintendo 64 emulator.
*
* (c) Copyright 2001 zilmar (zilmar@emulation64.com) and
* Jabo (jabo@emulation64.com).
*
* pj64 homepage: www.pj64.net
*
* Permission to use, copy, modify and distribute Project64 in both binary and
* source form, for non-commercial purposes, is hereby granted without fee,
* providing that this license information and copyright notice appear with
* all copies and any derived work.
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event shall the authors be held liable for any damages
* arising from the use of this software.
*
* Project64 is freeware for PERSONAL USE only. Commercial users should
* seek permission of the copyright holders first. Commercial use includes
* charging money for Project64 or software derived from Project64.
*
* The copyright holders request that bug fixes and improvements to the code
* should be forwarded to them so if they want them.
*
*/
#include <Windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <stdio.h>
#include <winuser.h>
#include <string.h>
#include "main.h"
#include "cheats.h"
#include "cpu.h"
#include "resource.h"
#include "Cheats_Preprocessor.h"
#include "RomTools_Common.h"
#define UM_CLOSE_CHEATS (WM_USER + 132)
#define UM_CHANGEEXTENSION (WM_USER + 101)
#define IDC_MYTREE 0x500
#define SelectCheat 1
#define EditCheat 2
#define NewCheat 3
HWND hManageWindow = NULL;
HWND hSelectCheat, hAddCheat, hCheatTree;
CHEAT_CODES Codes[MaxCheats];
int NoOfCodes;
// *******************************************************************************************
// Functions for Add Cheat and Edit Cheat
// *******************************************************************************************
void ReadDialogInput(CHEAT* cheat, HWND hDlg);
void NormalizeInput(HWND hDlg, DWORD hHandle);
BOOL NeededCRLFChange(char** str);
// *******************************************************************************************
void RefreshCheatManager(void);
void SaveCheat(char* CheatName, BOOL Active);
int _TreeView_GetCheckState(HWND hwndTreeView, HTREEITEM hItem);
BOOL _TreeView_SetCheckState(HWND hwndTreeView, HTREEITEM hItem, int State);
DWORD ConvertXP64Address(DWORD Address); //Witten
WORD ConvertXP64Value(WORD Value); //Witten
LRESULT CALLBACK ManageCheatsProc(HWND, UINT, WPARAM, LPARAM);
enum Dialog_State {
CONTRACTED,
EXPANDED
} DialogState;
enum Cheat_Type {
SIMPLE,
OPTIONS,
RANGE
} CheatType;
enum TV_CHECK_STATE {
TV_STATE_CLEAR,
TV_STATE_CHECKED,
TV_STATE_INDETERMINATE,
} DialogState;
int MinSizeDlg;
int MaxSizeDlg;
/********************************************************************************************
ConvertXP64Address
Purpose: Decode encoded XP64 address to physical address
Parameters:
Returns:
Author: Witten
********************************************************************************************/
DWORD ConvertXP64Address(DWORD Address) {
DWORD tmpAddress;
tmpAddress = (Address ^ 0x68000000) & 0xFF000000;
tmpAddress += ((Address + 0x002B0000) ^ 0x00810000) & 0x00FF0000;
tmpAddress += ((Address + 0x00002B00) ^ 0x00008200) & 0x0000FF00;
tmpAddress += ((Address + 0x0000002B) ^ 0x00000083) & 0x000000FF;
return tmpAddress;
}
/********************************************************************************************
ConvertXP64Value
Purpose: Decode encoded XP64 value
Parameters:
Returns:
Author: Witten
********************************************************************************************/
WORD ConvertXP64Value(WORD Value) {
WORD tmpValue;
tmpValue = ((Value + 0x2B00) ^ 0x8400) & 0xFF00;
tmpValue += ((Value + 0x002B) ^ 0x0085) & 0x00FF;
return tmpValue;
}
void ApplyGSButton(void) {
int count, count2, count3;
DWORD Address;
WORD Memory;
GAMESHARK_CODE Code;
for (count = 0; count < NoOfCodes; count++) {
for (count2 = 0; count2 < MaxGSEntries; count2++) {
Code.Command = Codes[count].Code[count2].Command;
Code.Value = Codes[count].Code[count2].Value;
switch (Code.Command & 0xFF000000) {
case 0x00000000:
count2 = MaxGSEntries;
break;
case 0x40000000: {
int next = count2 + 1;
if (next < MaxGSEntries) {
int byte_count = Codes[count].Code[next].Value + 1;
count2 += (byte_count / 6) + (byte_count % 6 ? 1 : 0) + 1;
}
break;
}
case 0x50000000: {
int numrepeats = (Code.Command & 0x0000FF00) >> 8;
int offset = Code.Command & 0x000000FF;
WORD incr = Code.Value;
if (++count2 >= MaxGSEntries) {
break;
}
Code.Command = Codes[count].Code[count2].Command;
Code.Value = Codes[count].Code[count2].Value;
switch (Code.Command & 0xFF000000) {
// Gameshark / AR
case 0x88000000:
Address = 0x80000000 | (Code.Command & 0xFFFFFF);
Memory = Code.Value;
for (count3 = 0; count3 < numrepeats; count3++) {
r4300i_SB_VAddr_NonCPU(Address, (BYTE)Memory);
Address += offset;
Memory += incr;
}
break;
case 0x89000000:
Address = 0x80000000 | (Code.Command & 0xFFFFFF);
Memory = Code.Value;
for (count3 = 0; count3 < numrepeats; count3++) {
r4300i_SH_VAddr_NonCPU(Address, (WORD)Memory);
Address += offset;
Memory += incr;
}
break;
// Xplorer64
case 0xA8000000:
Address = 0x80000000 | (ConvertXP64Address(Code.Command) & 0xFFFFFF);
Memory = ConvertXP64Value(Code.Value);
for (count3 = 0; count3 < numrepeats; count3++) {
r4300i_SB_VAddr_NonCPU(Address, (BYTE)Memory);
Address += offset;
Memory += incr;
}
break;
case 0xA9000000:
Address = 0x80000000 | (ConvertXP64Address(Code.Command) & 0xFFFFFF);
Memory = ConvertXP64Value(Code.Value);
for (count3 = 0; count3 < numrepeats; count3++) {
r4300i_SH_VAddr_NonCPU(Address, (WORD)Memory);
Address += offset;
Memory += incr;
}
break;
}
break;
}
default:
switch (Code.Command & 0xFF000000) {
// Gameshark / AR
case 0x88000000:
Address = 0x80000000 | (Code.Command & 0xFFFFFF);
r4300i_SB_VAddr_NonCPU(Address, (BYTE)Code.Value);
break;
case 0x89000000:
Address = 0x80000000 | (Code.Command & 0xFFFFFF);
r4300i_SH_VAddr_NonCPU(Address, Code.Value);
break;
// Xplorer64
case 0xA8000000:
Address = 0x80000000 | (ConvertXP64Address(Code.Command) & 0xFFFFFF);
r4300i_SB_VAddr_NonCPU(Address, (BYTE)ConvertXP64Value(Code.Value));
break;
case 0xA9000000:
Address = 0x80000000 | (ConvertXP64Address(Code.Command) & 0xFFFFFF);
r4300i_SH_VAddr_NonCPU(Address, ConvertXP64Value(Code.Value));
break;
default:
break;
}
}
}
}
}
/********************************************************************************************
ApplyCheats
Purpose: Patch codes into memory
Parameters: None
Returns: None
********************************************************************************************/
int ApplyCheatEntry(GAMESHARK_CODE* Code, BOOL Execute) {
DWORD Address;
WORD Memory;
switch (Code->Command & 0xFF000000) {
// Gameshark / AR
case 0x40000000:
{
// Write bytes with virtual addresses (supports TLB mapping)
Address = Code[1].Command;
int count = Code[1].Value + 1;
int i;
if (Execute) {
for (i = 0; i < count; i += 2) {
switch (i % 6) {
case 0:
Memory = Code[(i / 6) + 2].Command >> 16;
break;
case 2:
Memory = Code[(i / 6) + 2].Command;
break;
default:
Memory = Code[(i / 6) + 2].Value;
break;
}
r4300i_SH_VAddr_NonCPU(Address, Memory);
Address += 2;
}
if (count % 2) {
switch (i % 6) {
case 0:
Memory = Code[(i / 6) + 2].Command >> 24;
break;
case 2:
Memory = Code[(i / 6) + 2].Command >> 8;
break;
default:
Memory = Code[(i / 6) + 2].Value >> 8;
break;
}
r4300i_SB_VAddr_NonCPU(Address, (BYTE)Memory);
}
}
return (count / 6) + (count % 6 ? 1 : 0) + 2;
}
case 0x50000000: // Added by Witten (witten@pj64cheats.net)
{
int numrepeats = (Code->Command & 0x0000FF00) >> 8;
int offset = Code->Command & 0x000000FF;
WORD incr = Code->Value;
int count;
switch (Code[1].Command & 0xFF000000) {
case 0x80000000:
if (Execute) {
Address = 0x80000000 | (Code[1].Command & 0xFFFFFF);
Memory = Code[1].Value;
for (count = 0; count < numrepeats; count++) {
r4300i_SB_VAddr_NonCPU(Address, (BYTE)Memory);
Address += offset;
Memory += incr;
}
}
return 2;
case 0x81000000:
if (Execute) {
Address = 0x80000000 | (Code[1].Command & 0xFFFFFF);
Memory = Code[1].Value;
for (count = 0; count < numrepeats; count++) {
r4300i_SH_VAddr_NonCPU(Address, (WORD)Memory);
Address += offset;
Memory += incr;
}
}
return 2;
default: return 1;
}
}
break;
case 0x80000000:
Address = 0x80000000 | (Code->Command & 0xFFFFFF);
if (Execute) { r4300i_SB_VAddr_NonCPU(Address, (BYTE)Code->Value); }
break;
case 0x81000000:
Address = 0x80000000 | (Code->Command & 0xFFFFFF);
if (Execute) { r4300i_SH_VAddr_NonCPU(Address, Code->Value); }
break;
case 0xA0000000:
Address = 0xA0000000 | (Code->Command & 0xFFFFFF);
if (Execute) { r4300i_SB_VAddr_NonCPU(Address, (BYTE)Code->Value); }
break;
case 0xA1000000:
Address = 0xA0000000 | (Code->Command & 0xFFFFFF);
if (Execute) { r4300i_SH_VAddr_NonCPU(Address, Code->Value); }
break;
case 0xD0000000: // Added by Witten (witten@pj64cheats.net)
Address = 0x80000000 | (Code->Command & 0xFFFFFF);
r4300i_LB_VAddr_NonCPU(Address, (BYTE*)&Memory);
Memory &= 0x00FF;
if (Memory != Code->Value) { Execute = FALSE; }
return ApplyCheatEntry(&Code[1], Execute) + 1;
case 0xD1000000: // Added by Witten (witten@pj64cheats.net)
Address = 0x80000000 | (Code->Command & 0xFFFFFF);
r4300i_LH_VAddr_NonCPU(Address, (WORD*)&Memory);
if (Memory != Code->Value) { Execute = FALSE; }
return ApplyCheatEntry(&Code[1], Execute) + 1;
case 0xD2000000: // Added by Witten (witten@pj64cheats.net)
Address = 0x80000000 | (Code->Command & 0xFFFFFF);
r4300i_LB_VAddr_NonCPU(Address, (BYTE*)&Memory);
Memory &= 0x00FF;
if (Memory == Code->Value) { Execute = FALSE; }
return ApplyCheatEntry(&Code[1], Execute) + 1;
case 0xD3000000: // Added by Witten (witten@pj64cheats.net)
Address = 0x80000000 | (Code->Command & 0xFFFFFF);
r4300i_LH_VAddr_NonCPU(Address, (WORD*)&Memory);
if (Memory == Code->Value) { Execute = FALSE; }
return ApplyCheatEntry(&Code[1], Execute) + 1;
// Xplorer64 (Author: Witten)
case 0x30000000:
case 0x82000000:
case 0x84000000:
Address = 0x80000000 | (Code->Command & 0xFFFFFF);
if (Execute) { r4300i_SB_VAddr_NonCPU(Address, (BYTE)Code->Value); }
break;
case 0x31000000:
case 0x83000000:
case 0x85000000:
Address = 0x80000000 | (Code->Command & 0xFFFFFF);
if (Execute) { r4300i_SH_VAddr_NonCPU(Address, Code->Value); }
break;
case 0xE8000000:
Address = 0x80000000 | (ConvertXP64Address(Code->Command) & 0xFFFFFF);
if (Execute) { r4300i_SB_VAddr_NonCPU(Address, (BYTE)ConvertXP64Value(Code->Value)); }
break;
case 0xE9000000:
Address = 0x80000000 | (ConvertXP64Address(Code->Command) & 0xFFFFFF);
if (Execute) { r4300i_SH_VAddr_NonCPU(Address, ConvertXP64Value(Code->Value)); }
break;
case 0xC8000000:
Address = 0xA0000000 | (ConvertXP64Address(Code->Command) & 0xFFFFFF);
if (Execute) { r4300i_SB_VAddr_NonCPU(Address, (BYTE)Code->Value); }
break;
case 0xC9000000:
Address = 0xA0000000 | (ConvertXP64Address(Code->Command) & 0xFFFFFF);
if (Execute) { r4300i_SH_VAddr_NonCPU(Address, ConvertXP64Value(Code->Value)); }
break;
case 0xB8000000:
Address = 0x80000000 | (ConvertXP64Address(Code->Command) & 0xFFFFFF);
r4300i_LB_VAddr_NonCPU(Address, (BYTE*)&Memory);
Memory &= 0x00FF;
if (Memory != ConvertXP64Value(Code->Value)) { Execute = FALSE; }
return ApplyCheatEntry(&Code[1], Execute) + 1;
case 0xB9000000:
Address = 0x80000000 | (ConvertXP64Address(Code->Command) & 0xFFFFFF);
r4300i_LH_VAddr_NonCPU(Address, (WORD*)&Memory);
if (Memory != ConvertXP64Value(Code->Value)) { Execute = FALSE; }
return ApplyCheatEntry(&Code[1], Execute) + 1;
case 0xBA000000:
Address = 0x80000000 | (ConvertXP64Address(Code->Command) & 0xFFFFFF);
r4300i_LB_VAddr_NonCPU(Address, (BYTE*)&Memory);
Memory &= 0x00FF;
if (Memory == ConvertXP64Value(Code->Value)) { Execute = FALSE; }
return ApplyCheatEntry(&Code[1], Execute) + 1;
case 0xBB000000:
Address = 0x80000000 | (ConvertXP64Address(Code->Command) & 0xFFFFFF);
r4300i_LH_VAddr_NonCPU(Address, (WORD*)&Memory);
if (Memory == ConvertXP64Value(Code->Value)) { Execute = FALSE; }
return ApplyCheatEntry(&Code[1], Execute) + 1;
case 0:
return MaxGSEntries;
}
return 1;
}
void ApplyCheats(void) {
int CurrentCheat, CurrentEntry;
for (CurrentCheat = 0; CurrentCheat < NoOfCodes; CurrentCheat++) {
for (CurrentEntry = 0; CurrentEntry < MaxGSEntries;) {
CurrentEntry += ApplyCheatEntry(&Codes[CurrentCheat].Code[CurrentEntry], TRUE);
}
}
}
void ChangeRomCheats(HWND hwndOwner) {
char OrigRomName[sizeof(RomName)], OrigFileName[sizeof(CurrentFileName)], OrigFullName[sizeof(RomFullName)];
BYTE OrigByteHeader[sizeof(RomHeader)];
DWORD OrigFileSize;
// Always remember cheats if the emulator is running
if (CPURunning) {
ManageCheats(hwndOwner);
return;
}
//Load information about target rom and back up current information
memcpy(OrigRomName, RomName, sizeof(OrigRomName));
memcpy(OrigFileName, CurrentFileName, sizeof(OrigFileName));
memcpy(OrigFullName, RomFullName, sizeof(OrigFullName));
memcpy(CurrentFileName, CurrentRBFileName, sizeof(CurrentFileName));
memcpy(OrigByteHeader, RomHeader, sizeof(RomHeader));
OrigFileSize = RomFileSize;
LoadRomHeader();
if (!RememberCheats)
DisableAllCheats();
ManageCheats(hwndOwner);
//Restore details
memcpy(RomName, OrigRomName, sizeof(RomName));
memcpy(CurrentFileName, OrigFileName, sizeof(CurrentFileName));
memcpy(RomFullName, OrigFullName, sizeof(RomFullName));
memcpy(CurrentRBFileName, CurrentFileName, sizeof(CurrentRBFileName));
memcpy(RomHeader, OrigByteHeader, sizeof(RomHeader));
RomFileSize = OrigFileSize;
}
/********************************************************************************************
CheatActive
Purpose: Checks in Application Settings file if cheat is active
Parameters: char*
Name: name of cheat
Returns: Boolean
True: cheat is active
False: cheat isn't active or cheat isn't found in the file
********************************************************************************************/
BOOL CheatActive(char* Name) {
char Identifier[100];
RomID(Identifier, RomHeader);
return Settings_ReadBool(APPS_NAME, Identifier, Name, FALSE);
}
/********************************************************************************************
CheatCodeExProc
Purpose: Message handler for
Parameters:
Returns:
********************************************************************************************/
LRESULT CALLBACK CheatsCodeExProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_INITDIALOG:
{
char option_name[300], * ReadPos;
DWORD len;
BOOL matched;
CHEAT cheat = { 0 };
cheat = *(CHEAT*)lParam;
SetWindowText(hDlg, GS(CHEAT_CODE_EXT_TITLE));
SetDlgItemText(hDlg, IDC_NOTE, GS(CHEAT_CODE_EXT_TXT));
SetDlgItemText(hDlg, IDOK, GS(CHEAT_OK));
SetDlgItemText(hDlg, IDCANCEL, GS(CHEAT_CANCEL));
if (cheat.name != NULL)
SetDlgItemText(hDlg, IDC_CHEAT_NAME, cheat.name);
else
SetDlgItemText(hDlg, IDC_CHEAT_NAME, STR_EMPTY);
// No options
if (cheat.type == NO_REPLACE || cheat.type == BAD_CODE || strcmp(cheat.options, STR_EMPTY) == 0) {
EndDialog(hDlg, 0);
return TRUE;
}
// Parse String for options to display, these will be displayed line by line
ReadPos = cheat.options;
matched = FALSE;
while (ReadPos != NULL) {
int index;
char* name_only;
// Nothing left to parse
if (strlen(ReadPos) == 0)
break;
// The amount to copy, either the entire string (No , detected) or up to the next ,
if (strchr(ReadPos, ',') == NULL)
len = strlen(ReadPos);
else
len = strchr(ReadPos, ',') - ReadPos;
// Set a cap of CheatName's length (remove a 1 for the null character)
// This may be changed later
if (len >= sizeof(option_name))
len = sizeof(option_name) - 1;
// Copy the option name into CheatName
strncpy(option_name, ReadPos, len);
option_name[len] = 0;
name_only = strchr(option_name, ' ');
index = SendMessage(GetDlgItem(hDlg, IDC_CHEAT_LIST), LB_ADDSTRING, 0, (LPARAM)(name_only + 1));
SendMessage(GetDlgItem(hDlg, IDC_CHEAT_FULL), LB_ADDSTRING, 0, (LPARAM)option_name);
if (strcmp(cheat.selected, option_name) == 0) {
matched = TRUE;
SendMessage(GetDlgItem(hDlg, IDC_CHEAT_LIST), LB_SETCURSEL, index, 0);
}
// Find the start of the next code
if (strchr(ReadPos, ',') == NULL)
ReadPos += strlen(ReadPos);
else
ReadPos = strchr(ReadPos, ',') + 1;
}
// There is a selected option that is not in the list, remove it
if (matched == FALSE && strcmp(cheat.selected, STR_EMPTY) != 0) {
char Identifier[100], * tmp;
unsigned int length;
length = strlen(cheat.selected) + strlen(CHT_EXT_O);
tmp = malloc(sizeof(*tmp) * length);
if (tmp) {
RomID(Identifier, RomHeader);
snprintf(tmp, length, CHT_EXT_O, cheat.selected);
Settings_Delete(APPS_NAME, Identifier, tmp);
free(tmp);
}
}
}
break;
case WM_COMMAND:
switch (LOWORD(wParam)) {
case IDC_CHEAT_LIST:
if (HIWORD(wParam) == LBN_DBLCLK) {
PostMessage(hDlg, WM_COMMAND, IDOK, 0);
break;
}
break;
case IDOK:
{
char * name, * ext;
unsigned int index, name_length, ext_length;
index = SendMessage(GetDlgItem(hDlg, IDC_CHEAT_LIST), LB_GETCURSEL, 0, 0);
if (index < 0)
index = 0;
name_length = GetWindowTextLength(GetDlgItem(hDlg, IDC_CHEAT_NAME));
ext_length = SendMessage(GetDlgItem(hDlg, IDC_CHEAT_FULL), LB_GETTEXTLEN, index, 0);
if (name_length == 0 || ext_length == 0) {
EndDialog(hDlg, 0);
return TRUE;
}
// Make these arrays large enough to hold the name and selected option
name_length++;
ext_length++;
name = malloc(sizeof(*name) * name_length);
ext = malloc(sizeof(*ext) * ext_length);
// Could not allocate memory
if (!name || !ext) {
if (name)
free(name);
if (ext)
free(ext);
EndDialog(hDlg, 0);
return TRUE;
}
GetDlgItemText(hDlg, IDC_CHEAT_NAME, name, name_length);
index = SendMessage(GetDlgItem(hDlg, IDC_CHEAT_FULL), LB_GETTEXT, index, (LPARAM)ext);
if (index != LB_ERR) {
char* tmp = malloc(name_length + strlen(CHT_EXT_O));
if (tmp != NULL) {
char Identifier[100];
RomID(Identifier, RomHeader);
snprintf(tmp, name_length + strlen(CHT_EXT_O), CHT_EXT_O, name);
Settings_Write(APPS_NAME, Identifier, tmp, ext);
free(tmp);
}
}
if (name)
free(name);
if (ext)
free(ext);
LoadCheats();
}
EndDialog(hDlg, 0);
break;
case IDCANCEL:
EndDialog(hDlg, 0);
break;
}
default:
return FALSE;
}
return TRUE;
}
// Ensure the input into IDC_CHEAT_CODES and IDC_CHEAT_OPTIONS contains Windows style line endings (Carriage Return Line Feed)
// Depending on the hDlg passed this will either use the Edit Cheat or Add Cheat dialogs
void NormalizeInput(HWND hDlg, DWORD hHandle) {
unsigned int len;
char* str;
len = Edit_GetTextLength(GetDlgItem(hDlg, hHandle));
if (len > 0) {
str = malloc(sizeof(*str) * (len + 1)); // Include NULL character
if (str == NULL)
return;
Edit_GetText(GetDlgItem(hDlg, hHandle), str, len + 1);
// To prevent the caret from being moved unnecessarily (while editing for example) only update if the text needed carriage returns
if (NeededCRLFChange(&str))
Edit_SetText(GetDlgItem(hDlg, hHandle), str);
// Cleanup of allocated memory
free(str);
}
}
BOOL NeededCRLFChange(char** str) {
char* grow, * scan;
unsigned int length, count, replaces;
length = strlen(*str);
// Nothing to change
if (*str == NULL || length <= 0)
return FALSE;
// Prep work for scanning of the amount of carriage returns that must be inserted
replaces = 0;
scan = strchr(*str, '\n');
// Exceptional case, the line starts with a new line character and we cannot check the previous character for a carriage return
if (scan == *str) {
replaces++;
scan = strchr(scan + 1, '\n');
}
while (scan != NULL) {
if ((scan - 1)[0] != '\r')
replaces++;
scan = strchr(scan + 1, '\n');
}
if (replaces == 0)
return FALSE;
grow = malloc(sizeof(*grow) * (length + replaces + 1));
if (!grow)
return FALSE;
strcpy(grow, *str);
free(*str);
*str = grow;
// Scan for and add missing carriage returns before newlines
for (count = length + replaces - 1; replaces != 0 && count > 0; count--) {
(*str)[count] = (*str)[count];
if ((*str)[count] == '\n' && (*str)[count - 1] != '\r') {
replaces--;
(*str)[count] = '\r';
}
}
return TRUE;
}
void ReadDialogInput(CHEAT* cheat, HWND hDlg) {
unsigned int length, count;
// Copy the cheat name
cheat->name = NULL;
length = SendDlgItemMessage(hDlg, IDC_CODE_NAME, WM_GETTEXTLENGTH, 0, 0) + 1; // + 1 to include null pointer
cheat->name = malloc(sizeof(*cheat->name) * length);
if (cheat->name)
GetDlgItemText(hDlg, IDC_CODE_NAME, cheat->name, length);
// Copy the code string and normalize the input (These are in lines but we need them to be separated with commas)
cheat->codestring = NULL;
length = SendDlgItemMessage(hDlg, IDC_CHEAT_CODES, WM_GETTEXTLENGTH, 0, 0);
if (length > 0) {
char* buffer;
unsigned int count2;
length += 2;
buffer = malloc(sizeof(*cheat->codestring) * length);
cheat->codestring = malloc(sizeof(*cheat->codestring) * length);
if (cheat->codestring && buffer) {
buffer[0] = ',';
GetDlgItemText(hDlg, IDC_CHEAT_CODES, (buffer + 1), length);
// Replace all carriage returns with commas and new lines with spaces
for (count = 0, count2 = 0; count < length; count++) {
// Skip carriage returns
if (buffer[count] == '\r')
continue;
// New lines are special, they are replaced with commas unless the previous replacement was a comma
else if (buffer[count] == '\n') {
if (count2 > 0 && cheat->codestring[count2 - 1] == ',')
continue;
else
cheat->codestring[count2] = ',';
}
else
cheat->codestring[count2] = buffer[count];
count2++;
}
// Should not end in a comma
if (cheat->codestring[count2 - 2] == ',')
cheat->codestring[count2 - 2] = '\0';
free(buffer);
}
}
// Copy the options string and normalize the input (These are in lines but we need them to be separated with commas)
cheat->options = NULL;
length = SendDlgItemMessage(hDlg, IDC_CHEAT_OPTIONS, WM_GETTEXTLENGTH, 0, 0);
if (length > 0) {
length += 2;
cheat->options = malloc(sizeof(*cheat->options) * length);
if (cheat->options) {
GetDlgItemText(hDlg, IDC_CHEAT_OPTIONS, (cheat->options + 1), length);
cheat->options[0] = '$';
// Replace all carriage returns with commas and new lines with spaces
for (count = 1; count < length; count++) {
if (cheat->options[count] == '\r')
cheat->options[count] = ',';
if (cheat->options[count] == '\n')
cheat->options[count] = '$';
}
}
}
// Copy the note
cheat->note = NULL;
length = SendDlgItemMessage(hDlg, IDC_NOTES, WM_GETTEXTLENGTH, 0, 0);
if (length > 0) {
length++;
cheat->note = malloc(sizeof(*cheat->note) * length);
if (cheat->note)
GetDlgItemText(hDlg, IDC_NOTES, cheat->note, length);
}
cheat->selected = NULL;
cheat->replacedstring = NULL;
}
/********************************************************************************************
CheatAddProc
Purpose:
Parameters:
Returns:
********************************************************************************************/
LRESULT CALLBACK CheatAddProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_INITDIALOG:
SetWindowText(hDlg, GS(CHEAT_ADDCHEAT_FRAME));
SetWindowText(GetDlgItem(hDlg, IDC_NAME), GS(CHEAT_ADDCHEAT_NAME));
SetWindowText(GetDlgItem(hDlg, IDC_CODE), GS(CHEAT_ADDCHEAT_CODE));
SetWindowText(GetDlgItem(hDlg, IDC_LABEL_OPTIONS), GS(CHEAT_ADDCHEAT_OPT));
SetWindowText(GetDlgItem(hDlg, IDC_CODE_DES), GS(CHEAT_ADDCHEAT_CODEDES));
SetWindowText(GetDlgItem(hDlg, IDC_LABEL_OPTIONS_FORMAT), GS(CHEAT_ADDCHEAT_OPTDES));
SetWindowText(GetDlgItem(hDlg, IDC_CHEATNOTES), GS(CHEAT_ADDCHEAT_NOTES));
SetWindowText(GetDlgItem(hDlg, IDC_NEWCHEAT), GS(CHEAT_ADDCHEAT_NEW));
SetWindowText(GetDlgItem(hDlg, IDC_ADD), GS(CHEAT_ADDCHEAT_ADD));
break;
case WM_COMMAND:
switch (LOWORD(wParam)) {
case IDCANCEL:
EndDialog(hDlg, 0);
break;
case IDC_CODE_NAME:
if (HIWORD(wParam) == EN_CHANGE) {
CHEAT cheat;
ReadDialogInput(&cheat, hDlg);
if (Cheats_VerifyInput(&cheat))
EnableWindow(GetDlgItem(hDlg, IDC_ADD), TRUE);
else
EnableWindow(GetDlgItem(hDlg, IDC_ADD), FALSE);
// This was added because if you write out the code string first and then the name the side screen for options is disabled
// until you go back and adjust one of the codes
switch (cheat.type) {
case O_REPLACE:
case MO_REPLACE:
case SO_REPLACE:
case BAD_REPLACE:
if (!IsWindowEnabled(GetDlgItem(hDlg, IDC_LABEL_OPTIONS))) {
EnableWindow(GetDlgItem(hDlg, IDC_LABEL_OPTIONS), TRUE);
EnableWindow(GetDlgItem(hDlg, IDC_LABEL_OPTIONS_FORMAT), TRUE);
EnableWindow(GetDlgItem(hDlg, IDC_CHEAT_OPTIONS), TRUE);
}
break;
default:
if (IsWindowEnabled(GetDlgItem(hDlg, IDC_LABEL_OPTIONS))) {
EnableWindow(GetDlgItem(hDlg, IDC_LABEL_OPTIONS), FALSE);
EnableWindow(GetDlgItem(hDlg, IDC_LABEL_OPTIONS_FORMAT), FALSE);
EnableWindow(GetDlgItem(hDlg, IDC_CHEAT_OPTIONS), FALSE);
}
break;
}
Cheats_ClearCheat(&cheat);
}
break;
case IDC_CHEAT_CODES:
if (HIWORD(wParam) == EN_CHANGE) {
CHEAT cheat;
NormalizeInput(hDlg, IDC_CHEAT_CODES);
ReadDialogInput(&cheat, hDlg);
if (Cheats_VerifyInput(&cheat))
EnableWindow(GetDlgItem(hDlg, IDC_ADD), TRUE);
else
EnableWindow(GetDlgItem(hDlg, IDC_ADD), FALSE);
switch (cheat.type) {
case O_REPLACE:
case MO_REPLACE:
case SO_REPLACE:
case BAD_REPLACE:
if (!IsWindowEnabled(GetDlgItem(hDlg, IDC_LABEL_OPTIONS))) {
EnableWindow(GetDlgItem(hDlg, IDC_LABEL_OPTIONS), TRUE);
EnableWindow(GetDlgItem(hDlg, IDC_LABEL_OPTIONS_FORMAT), TRUE);
EnableWindow(GetDlgItem(hDlg, IDC_CHEAT_OPTIONS), TRUE);
}
break;
default:
if (IsWindowEnabled(GetDlgItem(hDlg, IDC_LABEL_OPTIONS))) {
EnableWindow(GetDlgItem(hDlg, IDC_LABEL_OPTIONS), FALSE);
EnableWindow(GetDlgItem(hDlg, IDC_LABEL_OPTIONS_FORMAT), FALSE);
EnableWindow(GetDlgItem(hDlg, IDC_CHEAT_OPTIONS), FALSE);
}
break;
}
Cheats_ClearCheat(&cheat);
}
case IDC_CHEAT_OPTIONS:
if (HIWORD(wParam) == EN_CHANGE) {
CHEAT cheat;
NormalizeInput(hDlg, IDC_CHEAT_OPTIONS);
ReadDialogInput(&cheat, hDlg);
if (Cheats_VerifyInput(&cheat))
EnableWindow(GetDlgItem(hDlg, IDC_ADD), TRUE);
else
EnableWindow(GetDlgItem(hDlg, IDC_ADD), FALSE);
Cheats_ClearCheat(&cheat);
}
break;
case IDC_ADD:
{
CHEAT cheat;
ReadDialogInput(&cheat, hDlg);
// This should always be true if the add cheat button is enabled (IDC_ADD case cannot occur if the button is disabled)
// However it doesn't cost much to check again so might as make sure it's good
if (Cheats_VerifyInput(&cheat)) {
// Write the new cheat
Cheats_Write(&cheat);
Cheats_ClearCheat(&cheat);
// Update the cheat list and close the edit dialog box
RefreshCheatManager();
SetDlgItemText(hDlg, IDC_CODE_NAME, STR_EMPTY);
SetDlgItemText(hDlg, IDC_CHEAT_CODES, STR_EMPTY);
SetDlgItemText(hDlg, IDC_CHEAT_OPTIONS, STR_EMPTY);
SetDlgItemText(hDlg, IDC_NOTES, STR_EMPTY);
EnableWindow(GetDlgItem(hDlg, IDC_ADD), FALSE);
EnableWindow(hDlg, FALSE);
EndDialog(hDlg, 0);
}
else
Cheats_ClearCheat(&cheat);
}
break;
case IDC_NEWCHEAT:
{
SetDlgItemText(hDlg, IDC_CODE_NAME, STR_EMPTY);
SetDlgItemText(hDlg, IDC_CHEAT_CODES, STR_EMPTY);
SetDlgItemText(hDlg, IDC_CHEAT_OPTIONS, STR_EMPTY);
SetDlgItemText(hDlg, IDC_NOTES, STR_EMPTY);
EnableWindow(GetDlgItem(hDlg, IDC_ADD), FALSE);
}
break;
}
break;
default:
return FALSE;
}
return TRUE;
}
LRESULT CALLBACK CheatEditProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
static int CheatNo;
switch (uMsg) {
case WM_INITDIALOG:
{
char* ReadPos, * Buffer;
TVITEM item;
CHEAT cheat;
SetWindowText(hDlg, GS(CHEAT_EDITCHEAT_WINDOW));
SetWindowText(GetDlgItem(hDlg, IDC_NAME), GS(CHEAT_ADDCHEAT_NAME));
SetWindowText(GetDlgItem(hDlg, IDC_CODE), GS(CHEAT_ADDCHEAT_CODE));
SetWindowText(GetDlgItem(hDlg, IDC_LABEL_OPTIONS), GS(CHEAT_ADDCHEAT_OPT));
SetWindowText(GetDlgItem(hDlg, IDC_CODE_DES), GS(CHEAT_ADDCHEAT_CODEDES));
SetWindowText(GetDlgItem(hDlg, IDC_LABEL_OPTIONS_FORMAT), GS(CHEAT_ADDCHEAT_OPTDES));
SetWindowText(GetDlgItem(hDlg, IDC_CHEATNOTES), GS(CHEAT_ADDCHEAT_NOTES));
SetWindowText(GetDlgItem(hDlg, IDC_ADD), GS(CHEAT_EDITCHEAT_UPDATE));
item.mask = TVIF_PARAM;
item.hItem = (HTREEITEM)lParam;
TreeView_GetItem(hCheatTree, &item);
CheatNo = item.lParam;
// Read everything related to the cheat
// This data will be used to set the fields for Edit Cheat
Cheats_Read(&cheat, item.lParam);
// Set Cheat Name
SetDlgItemText(hDlg, IDC_CODE_NAME, cheat.name);
// These will be updated with proper values but use these as defaults
SetDlgItemText(hDlg, IDC_CHEAT_CODES, STR_EMPTY);
SetDlgItemText(hDlg, IDC_CHEAT_OPTIONS, STR_EMPTY);
// Add Gameshark codes to screen
if (strcmp(cheat.codestring, STR_EMPTY) != 0) {
ReadPos = cheat.codestring + 1;
Buffer = malloc(sizeof(*Buffer) * (strlen(ReadPos) + MaxGSEntries));
if (Buffer) {
memset(Buffer, 0, sizeof(*Buffer) * (strlen(ReadPos) + MaxGSEntries));
do {
strncat(Buffer, ReadPos, strchr(ReadPos, ',') - ReadPos);
ReadPos = strchr(ReadPos, ',');
if (ReadPos != NULL) {
strcat(Buffer, "\r\n");
ReadPos += 1;
}
} while (ReadPos);
SetDlgItemText(hDlg, IDC_CHEAT_CODES, Buffer);
// Clean up memory
free(Buffer);
Buffer = NULL;
}
}
// Add option values to screen
if (strcmp(cheat.options, STR_EMPTY) != 0) {
ReadPos = strchr(cheat.options, '$') + 1;
Buffer = malloc(sizeof(*Buffer) * (strlen(ReadPos) + 2));
if (Buffer) {
memset(Buffer, 0, sizeof(*Buffer) * (strlen(ReadPos) + 2));
do {
strncat(Buffer, ReadPos, strchr(ReadPos, ',') - ReadPos);
ReadPos = strchr(ReadPos, '$');
if (ReadPos != NULL) {
strcat(Buffer, "\r\n");
ReadPos += 1;
}
} while (ReadPos);
SetDlgItemText(hDlg, IDC_CHEAT_OPTIONS, Buffer);
// Clean up memory
free(Buffer);
Buffer = NULL;
}
}
// Add cheat Notes
SetDlgItemText(hDlg, IDC_NOTES, cheat.note);
// Clean up the cheat structure
Cheats_ClearCheat(&cheat);
// Update Screen
PostMessage(hDlg, WM_COMMAND, MAKEWPARAM(IDC_CHEAT_CODES, EN_CHANGE), (LPARAM)GetDlgItem(hDlg, IDC_CHEAT_CODES));
}
break;
case WM_COMMAND:
switch (LOWORD(wParam)) {
case IDCANCEL:
EndDialog(hDlg, 0);
break;
case IDC_CODE_NAME:
if (HIWORD(wParam) == EN_CHANGE) {
CHEAT cheat;
ReadDialogInput(&cheat, hDlg);
if (Cheats_VerifyInput(&cheat))
EnableWindow(GetDlgItem(hDlg, IDC_ADD), TRUE);
else
EnableWindow(GetDlgItem(hDlg, IDC_ADD), FALSE);
// This was added because if you write out the code string first and then the name the side screen for options is disabled
// until you go back and adjust one of the codes
switch (cheat.type) {
case O_REPLACE:
case MO_REPLACE:
case SO_REPLACE:
case BAD_REPLACE:
if (!IsWindowEnabled(GetDlgItem(hDlg, IDC_LABEL_OPTIONS))) {
EnableWindow(GetDlgItem(hDlg, IDC_LABEL_OPTIONS), TRUE);
EnableWindow(GetDlgItem(hDlg, IDC_LABEL_OPTIONS_FORMAT), TRUE);
EnableWindow(GetDlgItem(hDlg, IDC_CHEAT_OPTIONS), TRUE);
}
break;
default:
if (IsWindowEnabled(GetDlgItem(hDlg, IDC_LABEL_OPTIONS))) {
EnableWindow(GetDlgItem(hDlg, IDC_LABEL_OPTIONS), FALSE);
EnableWindow(GetDlgItem(hDlg, IDC_LABEL_OPTIONS_FORMAT), FALSE);
EnableWindow(GetDlgItem(hDlg, IDC_CHEAT_OPTIONS), FALSE);
}
break;
}
Cheats_ClearCheat(&cheat);
}
break;
case IDC_CHEAT_CODES:
if (HIWORD(wParam) == EN_CHANGE) {
CHEAT cheat;
NormalizeInput(hDlg, IDC_CHEAT_CODES);
ReadDialogInput(&cheat, hDlg);
if (Cheats_VerifyInput(&cheat))
EnableWindow(GetDlgItem(hDlg, IDC_ADD), TRUE);
else
EnableWindow(GetDlgItem(hDlg, IDC_ADD), FALSE);
switch (cheat.type) {
case O_REPLACE:
case MO_REPLACE:
case SO_REPLACE:
case BAD_REPLACE:
if (!IsWindowEnabled(GetDlgItem(hDlg, IDC_LABEL_OPTIONS))) {
EnableWindow(GetDlgItem(hDlg, IDC_LABEL_OPTIONS), TRUE);
EnableWindow(GetDlgItem(hDlg, IDC_LABEL_OPTIONS_FORMAT), TRUE);
EnableWindow(GetDlgItem(hDlg, IDC_CHEAT_OPTIONS), TRUE);
}
break;
default:
if (IsWindowEnabled(GetDlgItem(hDlg, IDC_LABEL_OPTIONS))) {
EnableWindow(GetDlgItem(hDlg, IDC_LABEL_OPTIONS), FALSE);
EnableWindow(GetDlgItem(hDlg, IDC_LABEL_OPTIONS_FORMAT), FALSE);
EnableWindow(GetDlgItem(hDlg, IDC_CHEAT_OPTIONS), FALSE);
}
break;
}
Cheats_ClearCheat(&cheat);
}
break;
case IDC_CHEAT_OPTIONS:
if (HIWORD(wParam) == EN_CHANGE) {
CHEAT cheat;
NormalizeInput(hDlg, IDC_CHEAT_OPTIONS);
ReadDialogInput(&cheat, hDlg);
if (Cheats_VerifyInput(&cheat))
EnableWindow(GetDlgItem(hDlg, IDC_ADD), TRUE);
else
EnableWindow(GetDlgItem(hDlg, IDC_ADD), FALSE);
Cheats_ClearCheat(&cheat);
}
break;
case IDC_ADD:
{
CHEAT cheat;
ReadDialogInput(&cheat, hDlg);
// This should always be true if the update cheat button is enabled (IDC_ADD case cannot occur if the button is disabled)
// However it doesn't cost much to check again so might as make sure it's good
if (Cheats_VerifyInput(&cheat)) {
CHEAT del;
// Delete the current entry being edited and write the new one
// This is done because the edited cheat may have a new name
Cheats_Read(&del, CheatNo);
Cheats_Delete(&cheat);
Cheats_ClearCheat(&del);
// Write the new cheat
Cheats_Write(&cheat);
Cheats_ClearCheat(&cheat);
// Update the cheat list and close the edit dialog box
RefreshCheatManager();
EndDialog(hDlg, 0);
}
else
Cheats_ClearCheat(&cheat);
}
break;
}
break;
default:
return FALSE;
}
return TRUE;
}
void ChangeChildrenStatus(HTREEITEM hParent, BOOL Checked) {
HTREEITEM hItem = TreeView_GetChild(hCheatTree, hParent);
if (hItem == NULL) {
//Save Cheat
CHEAT cheat = { 0 };
TVITEM item;
if (hParent == TVI_ROOT)
return;
item.mask = TVIF_PARAM;
item.hItem = hParent;
TreeView_GetItem(hCheatTree, &item);
Cheats_Read(&cheat, item.lParam);
SaveCheat(cheat.name, Checked);
Cheats_ClearCheat(&cheat);
return;
}
while (hItem != NULL) {
ChangeChildrenStatus(hItem, Checked);
_TreeView_SetCheckState(hCheatTree, hItem, Checked ? TV_STATE_CHECKED : TV_STATE_CLEAR);
hItem = TreeView_GetNextSibling(hCheatTree, hItem);
}
}
void CheckParentStatus(HTREEITEM hParent) {
int CurrentState, InitialState;
HTREEITEM hItem;
if (!hParent) { return; }
hItem = TreeView_GetChild(hCheatTree, hParent);
InitialState = _TreeView_GetCheckState(hCheatTree, hParent);
CurrentState = _TreeView_GetCheckState(hCheatTree, hItem);
while (hItem != NULL) {
if (_TreeView_GetCheckState(hCheatTree, hItem) != CurrentState) {
CurrentState = TV_STATE_INDETERMINATE;
break;
}
hItem = TreeView_GetNextSibling(hCheatTree, hItem);
}
_TreeView_SetCheckState(hCheatTree, hParent, CurrentState);
if (InitialState != CurrentState) {
CheckParentStatus(TreeView_GetParent(hCheatTree, hParent));
}
}
/********************************************************************************************
CheatListProc
Purpose:
Parameters:
Returns:
********************************************************************************************/
LRESULT CALLBACK CheatListProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
static HTREEITEM hSelectedItem;
switch (uMsg) {
case WM_INITDIALOG:
{
DWORD Style;
RECT rcList;
RECT rcButton;
SetWindowText(GetDlgItem(hDlg, IDC_CHEATSFRAME), GS(CHEAT_LIST_FRAME));
SetWindowText(GetDlgItem(hDlg, IDC_NOTESFRAME), GS(CHEAT_NOTES_FRAME));
SetWindowText(GetDlgItem(hDlg, IDC_UNMARK), GS(CHEAT_MARK_NONE));
GetWindowRect(GetDlgItem(hDlg, IDC_CHEATSFRAME), &rcList);
GetWindowRect(GetDlgItem(hDlg, IDC_UNMARK), &rcButton);
hCheatTree = CreateWindowEx(WS_EX_CLIENTEDGE, WC_TREEVIEW, STR_EMPTY,
WS_CHILD | WS_TABSTOP | WS_VISIBLE, 8, 15, rcList.right - rcList.left - 16, rcButton.top - rcList.top - 22, hDlg, (HMENU)IDC_MYTREE, hInst, NULL);
Style = GetWindowLong(hCheatTree, GWL_STYLE);
SetWindowLong(hCheatTree, GWL_STYLE, TVS_HASLINES | TVS_HASBUTTONS | TVS_LINESATROOT | TVS_DISABLEDRAGDROP | TVS_CHECKBOXES | TVS_SHOWSELALWAYS | Style);
{
HIMAGELIST hImageList;
HBITMAP hBmp;
hImageList = ImageList_Create(16, 16, ILC_COLOR | ILC_MASK, 40, 40);
hBmp = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP1));
ImageList_AddMasked(hImageList, hBmp, RGB(255, 0, 255));
DeleteObject(hBmp);
TreeView_SetImageList(hCheatTree, hImageList, TVSIL_STATE);
}
hSelectedItem = NULL;
}
break;
case WM_SIZE:
{
int nWidth = LOWORD(lParam); // width of client area
int nHeight = HIWORD(lParam); // height of client area
SetWindowPos(hCheatTree, HWND_TOP, 0, 0, nWidth - 13, nHeight - 142, SWP_NOMOVE | SWP_NOOWNERZORDER);
SetWindowPos(GetDlgItem(hDlg, IDC_CHEATSFRAME), HWND_TOP, 0, 0, nWidth, nHeight - 100, SWP_NOOWNERZORDER);
SetWindowPos(GetDlgItem(hDlg, IDC_NOTESFRAME), HWND_TOP, 0, nHeight - 96, nWidth, 95, SWP_NOOWNERZORDER);
SetWindowPos(GetDlgItem(hDlg, IDC_NOTES), HWND_TOP, 6, nHeight - 80, nWidth - 12, 72, SWP_NOOWNERZORDER);
SetWindowPos(GetDlgItem(hDlg, IDC_UNMARK), HWND_TOP, nWidth - 110, nHeight - 124, 0, 0, SWP_NOSIZE | SWP_NOOWNERZORDER);
SetWindowPos(GetDlgItem(hDlg, IDC_DELETE), HWND_TOP, nWidth - 165, nHeight - 124, 0, 0, SWP_NOSIZE | SWP_NOOWNERZORDER);
}
break;
case WM_COMMAND:
switch (LOWORD(wParam)) {
case ID_POPUP_ADDNEWCHEAT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_Cheats_Add), hDlg, (DLGPROC)CheatAddProc);
break;
case ID_POPUP_EDIT:
DialogBoxParam(hInst, MAKEINTRESOURCE(IDD_Cheats_Edit), hDlg, (DLGPROC)CheatEditProc, (LPARAM)hSelectedItem);
break;
case ID_POPUP_DELETE:
{
TVITEM item;
CHEAT cheat = { 0 };
int Response = MessageBox(hDlg, GS(MSG_DEL_SURE), GS(MSG_DEL_TITLE), MB_YESNO | MB_ICONQUESTION);
if (Response != IDYES) { break; }
item.hItem = hSelectedItem;
item.mask = TVIF_PARAM;
TreeView_GetItem(hCheatTree, &item);
// Delete the selected cheat and update the Cheat Manager
if (Cheats_Read(&cheat, item.lParam)) {
Cheats_Delete(&cheat);
Cheats_ClearCheat(&cheat);
RefreshCheatManager();
}
}
break;
case IDC_UNMARK:
ChangeChildrenStatus(TVI_ROOT, FALSE);
LoadCheats();
break;
}
break;
case WM_NOTIFY:
{
LPNMHDR lpnmh = (LPNMHDR)lParam;
if ((lpnmh->code == NM_RCLICK) && (lpnmh->idFrom == IDC_MYTREE))
{
{
TVHITTESTINFO ht = { 0 };
DWORD dwpos = GetMessagePos();
// include <windowsx.h> and <windows.h> header files
ht.pt.x = GET_X_LPARAM(dwpos);
ht.pt.y = GET_Y_LPARAM(dwpos);
MapWindowPoints(HWND_DESKTOP, lpnmh->hwndFrom, &ht.pt, 1);
if (BasicMode) { return TRUE; }
TreeView_HitTest(lpnmh->hwndFrom, &ht);
hSelectedItem = ht.hItem;
}
{
HMENU hMenu = LoadMenu(hInst, MAKEINTRESOURCE(IDR_CHEAT_MENU));
HMENU hPopupMenu = GetSubMenu(hMenu, 0);
POINT Mouse;
GetCursorPos(&Mouse);
MenuSetText(hPopupMenu, 0, GS(CHEAT_ADDNEW), NULL);
MenuSetText(hPopupMenu, 1, GS(CHEAT_EDIT), NULL);
MenuSetText(hPopupMenu, 3, GS(CHEAT_DELETE), NULL);
if (hSelectedItem == NULL || TreeView_GetChild(hCheatTree, hSelectedItem) != NULL) {
DeleteMenu(hPopupMenu, 3, MF_BYPOSITION);
DeleteMenu(hPopupMenu, 2, MF_BYPOSITION);
DeleteMenu(hPopupMenu, 1, MF_BYPOSITION);
}
TrackPopupMenu(hPopupMenu, 0, Mouse.x, Mouse.y, 0, hDlg, NULL);
DestroyMenu(hMenu);
}
}
if ((lpnmh->code == NM_CLICK) && (lpnmh->idFrom == IDC_MYTREE))
{
TVHITTESTINFO ht = { 0 };
DWORD dwpos = GetMessagePos();
// include <windowsx.h> and <windows.h> header files
ht.pt.x = GET_X_LPARAM(dwpos);
ht.pt.y = GET_Y_LPARAM(dwpos);
MapWindowPoints(HWND_DESKTOP, lpnmh->hwndFrom, &ht.pt, 1);
TreeView_HitTest(lpnmh->hwndFrom, &ht);
if (TVHT_ONITEMSTATEICON & ht.flags)
{
switch (_TreeView_GetCheckState(hCheatTree, ht.hItem)) {
case TV_STATE_CLEAR:
case TV_STATE_INDETERMINATE:
_TreeView_SetCheckState(hCheatTree, ht.hItem, TV_STATE_CHECKED);
ChangeChildrenStatus(ht.hItem, TRUE);
CheckParentStatus(TreeView_GetParent(hCheatTree, ht.hItem));
_TreeView_SetCheckState(hCheatTree, ht.hItem, TV_STATE_INDETERMINATE);
break;
case TV_STATE_CHECKED:
_TreeView_SetCheckState(hCheatTree, ht.hItem, TV_STATE_CLEAR);
ChangeChildrenStatus(ht.hItem, FALSE);
CheckParentStatus(TreeView_GetParent(hCheatTree, ht.hItem));
_TreeView_SetCheckState(hCheatTree, ht.hItem, TV_STATE_CHECKED);
break;
}
LoadCheats();
}
}
if ((lpnmh->code == NM_DBLCLK) && (lpnmh->idFrom == IDC_MYTREE))
{
TVHITTESTINFO ht = { 0 };
DWORD dwpos = GetMessagePos();
// include <windowsx.h> and <windows.h> header files
ht.pt.x = GET_X_LPARAM(dwpos);
ht.pt.y = GET_Y_LPARAM(dwpos);
MapWindowPoints(HWND_DESKTOP, lpnmh->hwndFrom, &ht.pt, 1);
TreeView_HitTest(lpnmh->hwndFrom, &ht);
if (TVHT_ONITEMLABEL & ht.flags) {
PostMessage(hDlg, UM_CHANGEEXTENSION, 0, (LPARAM)ht.hItem);
}
}
if ((lpnmh->code == TVN_SELCHANGED) && (lpnmh->idFrom == IDC_MYTREE)) {
HTREEITEM hItem;
hItem = TreeView_GetSelection(hCheatTree);
if (TreeView_GetChild(hCheatTree, hItem) == NULL) {
TVITEM item;
CHEAT cheat = { 0 };
item.mask = TVIF_PARAM;
item.hItem = hItem;
TreeView_GetItem(hCheatTree, &item);
Cheats_Read(&cheat, item.lParam);
if (cheat.note != NULL)
SetDlgItemText(hDlg, IDC_NOTES, cheat.note);
else
SetDlgItemText(hDlg, IDC_NOTES, STR_EMPTY);
Cheats_ClearCheat(&cheat);
}
else {
SetDlgItemText(hDlg, IDC_NOTES, STR_EMPTY);
}
}
}
break;
case UM_CHANGEEXTENSION:
{
char CheatName[500];
char Read[500];
char* check;
TVITEM item;
CHEAT cheat = { 0 };
item.mask = TVIF_TEXT;
item.hItem = (HTREEITEM)lParam;
item.cchTextMax = sizeof(Read);
item.pszText = Read;
TreeView_GetItem(hCheatTree, &item);
// On failure go no further, return TRUE because this is still a handled event
if (!Cheats_Read(&cheat, item.lParam) || cheat.type == NO_REPLACE)
return TRUE;
// Check to make sure this is the proper child node being double clicked
check = strrchr(cheat.name, '\\');
if (check != NULL) {
if (strncmp((check + 1), Read, strlen(check + 1)) != 0)
return TRUE;
}
DialogBoxParam(hInst, MAKEINTRESOURCE(IDD_Cheats_CodeEx), hDlg, (DLGPROC)CheatsCodeExProc, (LPARAM)&cheat);
// Re-read the cheat, a new option has been selected
Cheats_Read(&cheat, item.lParam);
Cheats_DisplayName(&cheat, CheatName, sizeof(CheatName));
item.mask = TVIF_PARAM | TVIF_TEXT;
item.pszText = CheatName;
item.cchTextMax = sizeof(CheatName);
// The '\' is used as a special character to separate names into expandable trees so only show the last portion of the name after the '\'
if (strrchr(CheatName, '\\') != NULL) {
strcpy(CheatName, strrchr(CheatName, '\\') + 1);
}
TreeView_SetItem(hCheatTree, &item);
}
break;
default:
return FALSE;
}
return TRUE;
}
void DisableAllCheats(void) {
char Identifier[100];
RomID(Identifier, RomHeader);
Settings_DeleteEntry(APPS_NAME, Identifier);
}
void CloseCheatWindow(void) {
if (!hManageWindow) { return; }
SendMessage(hManageWindow, UM_CLOSE_CHEATS, 0, 0);
}
void LoadCode(CHEAT *cheat)
{
char* ReadPos;
int count2;
if (cheat->replacedstring != NULL && strcmp(cheat->replacedstring, STR_EMPTY) != 0)
ReadPos = cheat->replacedstring;
else
ReadPos = cheat->codestring;
// Nothing to load
if (ReadPos == NULL || strlen(ReadPos) == 0)
return;
if (ReadPos[0] == ',')
ReadPos++;
// Just in case a space was passed -- We are now supporting spaces after the commas
// Because it makes sense to me -- RadeonUser
if (ReadPos[0] == ' ')
ReadPos++;
for (count2 = 0; count2 < MaxGSEntries; count2++) {
// This will always be in "Address Value" format so reject if there is no space
if (strchr(ReadPos, ' ') == NULL)
return;
Codes[NoOfCodes].Code[count2].Command = AsciiToHex(ReadPos);
// There is always a space following the address to separate the value
ReadPos = strchr(ReadPos, ' ') + 1;
Codes[NoOfCodes].Code[count2].Value = (WORD)AsciiToHex(ReadPos);
ReadPos = strchr(ReadPos, ',');
// Finished parsing
if (ReadPos == NULL) {
count2++;
break;
}
// Move on to the next entry, keep in mind there may be an optional space
if (ReadPos[1] == ' ')
ReadPos += 2;
else
ReadPos++;
}
if (count2 == 0)
return;
if (count2 < MaxGSEntries) {
Codes[NoOfCodes].Code[count2].Command = 0;
Codes[NoOfCodes].Code[count2].Value = 0;
}
NoOfCodes += 1;
}
void LoadPermCheats() {
char Identifier[100], CheatName[300];
int count;
CHEAT cheat = { 0 };
RomID(Identifier, RomHeader);
for (count = 0; count < MaxCheats; count++) {
sprintf(CheatName, CHT_ENT, count);
Settings_Read(RDS_NAME, Identifier, CheatName, STR_EMPTY, &cheat.codestring);
if (strcmp(cheat.codestring, STR_EMPTY) != 0)
LoadCode(&cheat);
else
count = MaxCheats;
Cheats_ClearCheat(&cheat);
}
}
/********************************************************************************************
LoadCheats
Purpose:
Parameters:
Returns:
********************************************************************************************/
void LoadCheats(void) {
DWORD count;
CHEAT cheat = { 0 };
NoOfCodes = 0;
LoadPermCheats();
for (count = 0; count < MaxCheats; count++) {
// A return of FALSE means the cheat entry at count does not exist, keep scanning
if (!Cheats_Read(&cheat, count))
continue;
// If the cheat is not active keep scanning
// Also clean up memory usage
if (!CheatActive(cheat.name)) {
Cheats_ClearCheat(&cheat);
continue;
}
LoadCode(&cheat);
Cheats_ClearCheat(&cheat);
}
}
void ManageCheats(HWND hParent) {
#define DefaultWindowWidth 315
#define DefaultWindowHeight 415
DWORD X, Y, WindowWidth, WindowHeight, Style = 0;
if (hManageWindow != NULL) {
SetForegroundWindow(hManageWindow);
RefreshCheatManager();
ShowWindow(hManageWindow, SW_RESTORE);
return;
}
/*if (hParent) {
DialogBox(hInst, MAKEINTRESOURCE(IDD_MANAGECHEATS), hParent, (DLGPROC)ManageCheatsProc);
}else {
CreateDialog(hInst, MAKEINTRESOURCE(IDD_MANAGECHEATS), hParent, (DLGPROC)ManageCheatsProc);
}*/
if (!GetStoredWinSize("Cheat", &WindowWidth, &WindowHeight)) {
WindowWidth = DefaultWindowWidth;
WindowHeight = DefaultWindowHeight;
}
if (!GetStoredWinPos("Cheat", &X, &Y)) {
X = (GetSystemMetrics(SM_CXSCREEN) - WindowWidth) / 2;
Y = (GetSystemMetrics(SM_CYSCREEN) - WindowHeight) / 2;
}
Style = hParent ? WS_SIZEBOX | WS_SYSMENU | WS_MINIMIZEBOX : WS_SIZEBOX | WS_SYSMENU | WS_MINIMIZEBOX;
hManageWindow = CreateWindowEx(NULL, "PJ64.Cheats", GS(CHEAT_TITLE), Style, X, Y, WindowWidth, WindowHeight, hParent, NULL, hInst, NULL);
RefreshCheatManager();
ShowWindow(hManageWindow, SW_SHOW);
SetWindowPos(hManageWindow, // handle to window
HWND_TOPMOST, // placement-order handle
X, // horizontal position
Y, // vertical position
WindowWidth, // width
WindowHeight, // height
SWP_SHOWWINDOW // window-positioning options
);
while (ShowCursor(TRUE) < 0);
if (hParent) {
MSG msg;
EnableWindow(hParent, FALSE);
while (hManageWindow) {
if (!GetMessage(&msg, NULL, 0, 0)) {
DestroyWindow(hManageWindow);
hManageWindow = NULL;
PostMessage(msg.hwnd, msg.message, msg.wParam, msg.lParam);
continue;
}
if (IsDialogMessage(hManageWindow, &msg)) { continue; }
TranslateMessage(&msg);
DispatchMessage(&msg);
}
EnableWindow(hParent, TRUE);
ShowCursor(!inFullScreen);
SetFocus(hParent);
}
}
LRESULT CALLBACK Cheat_Proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
#define MinHeight 260
#define MinWidth 190
switch (uMsg) {
case WM_CREATE:
hSelectCheat = CreateDialog(hInst, MAKEINTRESOURCE(IDD_Cheats_List), hWnd, (DLGPROC)CheatListProc);
SetWindowPos(hSelectCheat, HWND_TOP, 5, 8, 0, 0, SWP_NOSIZE);
ShowWindow(hSelectCheat, SW_SHOW);
break;
/* Move this to WM_DESTROY, this saves the position EVERY time the window is touched, looked at, moved, etc...
case WM_MOVE:
if (IsIconic(hWnd)) { break; }
StoreCurrentWinPos("Cheat", hWnd );
break;*/
case UM_CLOSE_CHEATS:
DestroyWindow(hWnd);
break;
case WM_SIZING:
{
LPRECT lprc = (LPRECT)lParam;
int fwSide = wParam;
if ((lprc->bottom - lprc->top) <= MinHeight) {
switch (fwSide) {
case WMSZ_TOPLEFT:
case WMSZ_TOP:
case WMSZ_TOPRIGHT:
lprc->top = lprc->bottom - MinHeight;
break;
case WMSZ_BOTTOMLEFT:
case WMSZ_BOTTOM:
case WMSZ_BOTTOMRIGHT:
lprc->bottom = lprc->top + MinHeight;
break;
}
}
if ((lprc->right - lprc->left) <= MinWidth) {
switch (fwSide) {
case WMSZ_TOPLEFT:
case WMSZ_LEFT:
case WMSZ_BOTTOMLEFT:
lprc->left = lprc->right - MinWidth;
break;
case WMSZ_TOPRIGHT:
case WMSZ_RIGHT:
case WMSZ_BOTTOMRIGHT:
lprc->right = lprc->left + MinWidth;
break;
}
}
}
break;
case WM_SIZE:
{
int nWidth = LOWORD(lParam); // width of client area
int nHeight = HIWORD(lParam); // height of client area
SetWindowPos(hSelectCheat, HWND_TOP, 5, 5, nWidth - 8, nHeight - 10, SWP_NOOWNERZORDER);
//StoreCurrentWinSize("Cheat",hWnd);
}
break;
case WM_DESTROY:
if (IsIconic(hWnd)) { break; }
StoreCurrentWinPos("Cheat", hWnd);
StoreCurrentWinSize("Cheat", hWnd);
hManageWindow = NULL;
break;
default:
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
return TRUE;
}
/********************************************************************************************
ManageCheatsProc
Purpose:
Parameters:
Returns:
********************************************************************************************/
LRESULT CALLBACK ManageCheatsProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
static int CurrentPanel = SelectCheat;
static RECT rcDisp;
RECT clientrect;
static RECT rcList;
static RECT rcAdd;
HANDLE hIcon;
HWND hStateButton;
switch (uMsg) {
case WM_INITDIALOG:
hManageWindow = hDlg;
{
WINDOWPLACEMENT WndPlac;
RECT* rc;
WndPlac.length = sizeof(WndPlac);
GetWindowPlacement(hDlg, &WndPlac);
rc = &WndPlac.rcNormalPosition;
SetWindowText(hDlg, GS(CHEAT_TITLE));
hSelectCheat = CreateDialog(hInst, MAKEINTRESOURCE(IDD_Cheats_List), hDlg, (DLGPROC)CheatListProc);
SetWindowPos(hSelectCheat, HWND_TOP, 5, 8, 0, 0, SWP_NOSIZE);
ShowWindow(hSelectCheat, SW_SHOW);
hAddCheat = CreateDialog(hInst, MAKEINTRESOURCE(IDD_Cheats_Add), hDlg, (DLGPROC)CheatAddProc);
SetWindowPos(hAddCheat, HWND_TOP, (rc->right - rc->left) / 2, 8, 0, 0, SWP_NOSIZE);
ShowWindow(hAddCheat, SW_HIDE);
GetWindowRect(GetDlgItem(hSelectCheat, IDC_CHEATSFRAME), &rcList);
GetWindowRect(GetDlgItem(hAddCheat, IDC_ADDCHEATSFRAME), &rcAdd);
MinSizeDlg = rcList.right - rcList.left + 32;
MaxSizeDlg = rcAdd.right - rcList.left + 32;
DialogState = CONTRACTED;
WndPlac.rcNormalPosition.right = WndPlac.rcNormalPosition.left + MinSizeDlg;
SetWindowPlacement(hDlg, &WndPlac);
GetClientRect(hDlg, rc);
hStateButton = GetDlgItem(hDlg, IDC_STATE);
SetWindowPos(hStateButton, HWND_TOP, (rc->right - rc->left) - 16, 0, 16, rc->bottom - rc->top, 0);
hIcon = LoadImage(hInst, MAKEINTRESOURCE(IDI_RIGHT), IMAGE_ICON, 0, 0, LR_DEFAULTCOLOR);
SendDlgItemMessage(hDlg, IDC_STATE, BM_SETIMAGE, (WPARAM)IMAGE_ICON, (LPARAM)(HANDLE)hIcon);
}
hManageWindow = hDlg;
RefreshCheatManager();
break;
//case WM_SIZE:
// GetClientRect( hDlg, &rcDisp);
// TabCtrl_AdjustRect( GetDlgItem(hDlg,IDC_TAB), FALSE, &rcDisp );
// break;
/*case WM_NOTIFY:
switch (((NMHDR *)lParam)->code) {
case TCN_SELCHANGE:
{
TC_ITEM item;
HWND hTab;
hTab = GetDlgItem(hDlg,IDC_TAB);
InvalidateRect( hTab, &rcDisp, TRUE );
switch (CurrentPanel) {
case SelectCheat: ShowWindow(hSelectCheat,SW_HIDE); break;
case NewCheat: ShowWindow(hAddCheat,SW_HIDE); break;
}
item.mask = TCIF_PARAM;
TabCtrl_GetItem( hTab, TabCtrl_GetCurSel( hTab ), &item );
CurrentPanel = item.lParam;
switch (CurrentPanel) {
case SelectCheat: ShowWindow(hSelectCheat,SW_SHOW); break;
case NewCheat: ShowWindow(hAddCheat,SW_SHOW); break;
}
break;
}
}
break;*/
case WM_COMMAND:
switch (LOWORD(wParam)) {
case IDCANCEL:
EndDialog(hDlg, 0);
DestroyWindow(hDlg);
break;
case IDC_STATE:
{
WINDOWPLACEMENT WndPlac;
WndPlac.length = sizeof(WndPlac);
GetWindowPlacement(hDlg, &WndPlac);
if (DialogState == CONTRACTED)
{
DialogState = EXPANDED;
WndPlac.rcNormalPosition.right = WndPlac.rcNormalPosition.left + MaxSizeDlg;
SetWindowPlacement(hDlg, &WndPlac);
GetClientRect(hDlg, &clientrect);
hStateButton = GetDlgItem(hDlg, IDC_STATE);
SetWindowPos(hStateButton, HWND_TOP, (clientrect.right - clientrect.left) - 16, 0, 16, clientrect.bottom - clientrect.top, 0);
hIcon = LoadImage(hInst, MAKEINTRESOURCE(IDI_LEFT), IMAGE_ICON, 0, 0, LR_DEFAULTCOLOR);
SendDlgItemMessage(hDlg, IDC_STATE, BM_SETIMAGE, (WPARAM)IMAGE_ICON, (LPARAM)(HANDLE)hIcon);
ShowWindow(hAddCheat, SW_SHOW);
}
else
{
DialogState = CONTRACTED;
WndPlac.rcNormalPosition.right = WndPlac.rcNormalPosition.left + MinSizeDlg;
SetWindowPlacement(hDlg, &WndPlac);
hIcon = LoadImage(hInst, MAKEINTRESOURCE(IDI_RIGHT), IMAGE_ICON, 0, 0, LR_DEFAULTCOLOR);
SendDlgItemMessage(hDlg, IDC_STATE, BM_SETIMAGE, (WPARAM)IMAGE_ICON, (LPARAM)(HANDLE)hIcon);
GetClientRect(hDlg, &clientrect);
hStateButton = GetDlgItem(hDlg, IDC_STATE);
SetWindowPos(hStateButton, HWND_TOP, (clientrect.right - clientrect.left) - 16, 0, 16, clientrect.bottom - clientrect.top, 0);
ShowWindow(hAddCheat, SW_HIDE);
}
}
break;
}
break;
// *** Add in Build 53
case WM_DESTROY:
hManageWindow = NULL;
break;
default:
return FALSE;
}
return TRUE;
}
void AddCodeLayers(int CheatNumber, char* CheatName, HTREEITEM hParent, BOOL CheatActive) {
char Text[500], Item[500];
TV_INSERTSTRUCT tv;
// Work out text to add
strcpy(Text, CheatName);
if (strchr(Text, '\\') > 0)
*strchr(Text, '\\') = 0;
// See if text is already added
tv.item.mask = TVIF_TEXT;
tv.item.pszText = Item;
tv.item.cchTextMax = sizeof(Item);
tv.item.hItem = TreeView_GetChild(hCheatTree, hParent);
while (tv.item.hItem) {
TreeView_GetItem(hCheatTree, &tv.item);
Item[499] = '\0'; // This is already null terminated according to MSDN documentation but Intellisense is complaining about the strcmp below
if (strcmp(Text, Item) == 0) {
// If already exists then just use existing one
int State = _TreeView_GetCheckState(hCheatTree, tv.item.hItem);
if ((CheatActive && State == TV_STATE_CLEAR) || (!CheatActive && State == TV_STATE_CHECKED)) {
_TreeView_SetCheckState(hCheatTree, tv.item.hItem, TV_STATE_INDETERMINATE);
}
AddCodeLayers(CheatNumber, CheatName + strlen(Text) + 1, tv.item.hItem, CheatActive);
return;
}
tv.item.hItem = TreeView_GetNextSibling(hCheatTree, tv.item.hItem);
}
// Add to dialog
tv.hInsertAfter = TVI_SORT;
tv.item.mask = TVIF_TEXT | TVIF_PARAM;
tv.item.pszText = Text;
tv.item.lParam = CheatNumber;
tv.hParent = hParent;
hParent = TreeView_InsertItem(hCheatTree, &tv);
_TreeView_SetCheckState(hCheatTree, hParent, CheatActive ? TV_STATE_CHECKED : TV_STATE_CLEAR);
if (strcmp(Text, CheatName) == 0)
return;
AddCodeLayers(CheatNumber, CheatName + strlen(Text) + 1, hParent, CheatActive);
}
/********************************************************************************************
RefreshCheatManager
Purpose:
Parameters:
Returns:
********************************************************************************************/
void RefreshCheatManager(void) {
char CheatName[500];
BOOL IsCheatActive;
DWORD count;
CHEAT cheat = { 0 };
if (hManageWindow == NULL)
return;
TreeView_DeleteAllItems(hCheatTree);
for (count = 0; count < MaxCheats; count++) {
if (!Cheats_Read(&cheat, count)) {
Cheats_ClearCheat(&cheat);
continue;
}
IsCheatActive = CheatActive(cheat.name);
Cheats_DisplayName(&cheat, CheatName, sizeof(CheatName));
AddCodeLayers(count, CheatName, TVI_ROOT, IsCheatActive);
}
}
void SaveCheat(char* CheatName, BOOL Active) {
char Identifier[100];
RomID(Identifier, RomHeader);
Settings_Write(APPS_NAME, Identifier, CheatName, Active ? STR_TRUE : STR_FALSE);
}
int _TreeView_GetCheckState(HWND hwndTreeView, HTREEITEM hItem)
{
TVITEM tvItem;
// Prepare to receive the desired information.
tvItem.mask = TVIF_HANDLE | TVIF_STATE;
tvItem.hItem = hItem;
tvItem.stateMask = TVIS_STATEIMAGEMASK;
// Request the information.
TreeView_GetItem(hwndTreeView, &tvItem);
// Return zero if it's not checked, or nonzero otherwise.
switch (tvItem.state >> 12) {
case 1: return TV_STATE_CHECKED;
case 2: return TV_STATE_CLEAR;
case 3: return TV_STATE_INDETERMINATE;
}
return ((int)(tvItem.state >> 12) - 1);
}
BOOL _TreeView_SetCheckState(HWND hwndTreeView, HTREEITEM hItem, int state)
{
TVITEM tvItem;
tvItem.mask = TVIF_HANDLE | TVIF_STATE;
tvItem.hItem = hItem;
tvItem.stateMask = TVIS_STATEIMAGEMASK;
/*Image 1 in the tree-view check box image list is the
unchecked box. Image 2 is the checked box.*/
switch (state) {
case TV_STATE_CHECKED: tvItem.state = INDEXTOSTATEIMAGEMASK(1); break;
case TV_STATE_CLEAR: tvItem.state = INDEXTOSTATEIMAGEMASK(2); break;
case TV_STATE_INDETERMINATE: tvItem.state = INDEXTOSTATEIMAGEMASK(3); break;
default: tvItem.state = INDEXTOSTATEIMAGEMASK(0); break;
}
return TreeView_SetItem(hwndTreeView, &tvItem);
}