/*  Copyright 2009 Upthorn, Nitsuja, adelikat

    This file is part of Yabause.

    Yabause is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    Yabause is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Yabause; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
*/

//RamSearch dialog was copied and adapted from GENS11: http://code.google.com/p/gens-rerecording/
//Authors: Upthorn, Nitsuja, adelikat

// A few notes about this implementation of a RAM search window:
//
// Speed of update was one of the highest priories.
// This is because I wanted the RAM search window to be able to
// update every single value in RAM every single frame, and
// keep track of the exact number of frames across which each value has changed,
// without causing the emulation to run noticeably slower than normal.
//
// The data representation was changed from one entry per valid address
// to one entry per contiguous range of uneliminated addresses
// which references uniform pools of per-address properties.
// - This saves time when there are many items because
//   it minimizes the amount of data that needs to be stored and processed per address.
// - It also saves time when there are few items because
//   it ensures that no time is wasted in iterating through
//   addresses that have already been eliminated from the search.
//
// The worst-case scenario is when every other item has been
// eliminated from the search, maximizing the number of regions.
// This implementation manages to handle even that pathological case
// acceptably well. In fact, it still updates faster than the previous implementation.
// The time spent setting up or clearing such a large number of regions
// is somewhat horrendous, but it seems reasonable to have poor worst-case speed
// during these sporadic "setup" steps to achieve an all-around faster per-update speed.
// (You can test this case by performing the search: Modulo 2 Is Specific Address 0)

#include "resource.h"

//#include "common.h"
//#include "NDSSystem.h"
#include "ramwatch.h"
#include "./settings/settings.h"
#include "./cpudebug/yuidebug.h"

#include "ram_search.h"
#include <assert.h>
#include <commctrl.h>
#include "ramwatch.h"
#include <list>
#include <vector>
#ifdef _WIN32
 //  #include "BaseTsd.h"
   typedef INT_PTR intptr_t;
#else
   #include "stdint.h"
#endif

HWND RamSearchHWnd = NULL;

extern "C" HWND YabWin;
extern "C" HINSTANCE y_hInstance;

extern HWND RamWatchHWnd;

static WCHAR Str_Tmp[1024];

int Rom_Size; //TODO
unsigned char* Rom_Data; //TODO

struct MemoryRegion
{
	unsigned int hardwareAddress; // hardware address of the start of this region
	unsigned int size; // number of bytes to the end of this region
	unsigned char* softwareAddress; // pointer to the start of the live emulator source values for this region
	BOOL byteSwapped; // true if this is a byte-swapped region of memory

	unsigned int virtualIndex; // index into s_prevValues, s_curValues, and s_numChanges, valid after being initialized in ResetMemoryRegions()
	unsigned int itemIndex; // index into listbox items, valid when s_itemIndicesInvalid is false
};

static struct Buffers {
	unsigned char s_prevValues [MAX_RAM_SIZE+4]; // values at last search or reset
	unsigned char s_curValues [MAX_RAM_SIZE+4]; // values at last frame update
	unsigned short s_numChanges [MAX_RAM_SIZE+4]; // number of changes of the item starting at this virtual index address
	MemoryRegion* s_itemIndexToRegionPointer [MAX_RAM_SIZE+4]; // used for random access into the memory list (trading memory size to get speed here, too bad it's so much memory), only valid when s_itemIndicesInvalid is false
} *buffers = NULL;
static BOOL s_itemIndicesInvalid = true; // if true, the link from listbox items to memory regions (s_itemIndexToRegionPointer) and the link from memory regions to list box items (MemoryRegion::itemIndex) both need to be recalculated
static BOOL s_prevValuesNeedUpdate = true; // if true, the "prev" values should be updated using the "cur" values on the next frame update signaled
static unsigned int s_maxItemIndex = 0; // max currently valid item index, the listbox sometimes tries to update things past the end of the list so we need to know this to ignore those attempts

u8 *Ram1;
//(unsigned char*)&
static MemoryRegion s_prgRegion;//    = {  0x06000000,    0x10000, Ram1, false};//0x100000
static MemoryRegion s_prgRegion2;
//static const MemoryRegion s_prgRegion2    = {  0x02000000, 0x400000, (unsigned char*)HighWram,     false};

/*
static const MemoryRegion s_prgRegion    = {  0x020000, SEGACD_RAM_PRG_SIZE, (unsigned char*)Ram_Prg,     true};
static const MemoryRegion s_word1MRegion = {  0x200000, SEGACD_1M_RAM_SIZE,  (unsigned char*)Ram_Word_1M, true};
static const MemoryRegion s_word2MRegion = {  0x200000, SEGACD_2M_RAM_SIZE,  (unsigned char*)Ram_Word_2M, true};
static const MemoryRegion s_z80Region    = {  0xA00000, Z80_RAM_SIZE,        (unsigned char*)Ram_Z80,     true};
static const MemoryRegion s_68kRegion    = {  0xFF0000, _68K_RAM_SIZE,       (unsigned char*)Ram_68k,     true};
static const MemoryRegion s_32xRegion    = {0x06000000, _32X_RAM_SIZE,       (unsigned char*)_32X_Ram,    false};
*/
// list of contiguous uneliminated memory regions
typedef std::list<MemoryRegion> MemoryList;
static MemoryList s_activeMemoryRegions;

// for undo support (could be better, but this way was really easy)
static MemoryList s_activeMemoryRegionsBackup;
static int s_undoType = 0; // 0 means can't undo, 1 means can undo, 2 means can redo

void RamSearchSaveUndoStateIfNotTooBig(HWND hDlg);
static const int tooManyRegionsForUndo = 10000;

void InitRamSearch()
{
	if(buffers == NULL)
	{
		buffers = new Buffers;
		memset(buffers,0,sizeof(Buffers));
		//Ram1=HighWram;
		s_prgRegion.byteSwapped=false;
		s_prgRegion.hardwareAddress=0x06000000;
		s_prgRegion.size=0x100000;
		s_prgRegion.softwareAddress=HighWram;

/*		s_prgRegion2.byteSwapped=false;
		s_prgRegion2.hardwareAddress=0x00200000;
		s_prgRegion2.size=0x100000;
		s_prgRegion2.softwareAddress=LowWram;*/

	//		= {  0x06000000,    0x10000, HighWram, false};//0x100000
	}
}

void ResetMemoryRegions()
{
//	Clear_Sound_Buffer();

	s_activeMemoryRegions.clear();
		
	s_activeMemoryRegions.push_back(s_prgRegion);
//	s_activeMemoryRegions.push_back(s_prgRegion2);
	
	/*if(Genesis_Started || _32X_Started || SegaCD_Started)
	{
		s_activeMemoryRegions.push_back(s_68kRegion);
		s_activeMemoryRegions.push_back(s_z80Region);
		if(SegaCD_Started)
		{
			s_activeMemoryRegions.push_back(s_prgRegion);
			s_activeMemoryRegions.push_back((Ram_Word_State & 0x2) ? s_word1MRegion : s_word2MRegion);
		}
		if(_32X_Started)
		{
			s_activeMemoryRegions.push_back(s_32xRegion);
		}
	}*/

	int nextVirtualIndex = 0;
	for(MemoryList::iterator iter = s_activeMemoryRegions.begin(); iter != s_activeMemoryRegions.end(); ++iter)
	{
		MemoryRegion& region = *iter;
		region.virtualIndex = nextVirtualIndex;
		assert(((intptr_t)region.softwareAddress & 1) == 0 && "somebody need to reimplement ReadValueAtSoftwareAddress()");
		nextVirtualIndex = region.virtualIndex + region.size;
	}
//	assert(nextVirtualIndex <= MAX_RAM_SIZE); TODO
}

// eliminates a range of hardware addresses from the search results
// returns 2 if it changed the region and moved the iterator to another region
// returns 1 if it changed the region but didn't move the iterator
// returns 0 if it had no effect
// warning: don't call anything that takes an itemIndex in a loop that calls DeactivateRegion...
//   doing so would be tremendously slow because DeactivateRegion invalidates the index cache
int DeactivateRegion(MemoryRegion& region, MemoryList::iterator& iter, unsigned int hardwareAddress, unsigned int size)
{
	if(hardwareAddress + size <= region.hardwareAddress || hardwareAddress >= region.hardwareAddress + region.size)
	{
		// region is unaffected
		return 0;
	}
	else if(hardwareAddress > region.hardwareAddress && hardwareAddress + size >= region.hardwareAddress + region.size)
	{
		// erase end of region
		region.size = hardwareAddress - region.hardwareAddress;
		return 1;
	}
	else if(hardwareAddress <= region.hardwareAddress && hardwareAddress + size < region.hardwareAddress + region.size)
	{
		// erase start of region
		int eraseSize = (hardwareAddress + size) - region.hardwareAddress;
		region.hardwareAddress += eraseSize;
		region.size -= eraseSize;
		region.softwareAddress += eraseSize;
		region.virtualIndex += eraseSize;
		return 1;
	}
	else if(hardwareAddress <= region.hardwareAddress && hardwareAddress + size >= region.hardwareAddress + region.size)
	{
		// erase entire region
		iter = s_activeMemoryRegions.erase(iter);
		s_itemIndicesInvalid = TRUE;
		return 2;
	}
	else //if(hardwareAddress > region.hardwareAddress && hardwareAddress + size < region.hardwareAddress + region.size)
	{
		// split region
		int eraseSize = (hardwareAddress + size) - region.hardwareAddress;
		MemoryRegion region2 = {region.hardwareAddress + eraseSize, region.size - eraseSize, region.softwareAddress + eraseSize, region.byteSwapped, region.virtualIndex + eraseSize};
		region.size = hardwareAddress - region.hardwareAddress;
		iter = s_activeMemoryRegions.insert(++iter, region2);
		s_itemIndicesInvalid = TRUE;
		return 2;
	}
}

/*
// eliminates a range of hardware addresses from the search results
// this is a simpler but usually slower interface for the above function
void DeactivateRegion(unsigned int hardwareAddress, unsigned int size)
{
	for(MemoryList::iterator iter = s_activeMemoryRegions.begin(); iter != s_activeMemoryRegions.end(); )
	{
		MemoryRegion& region = *iter;
		if(2 != DeactivateRegion(region, iter, hardwareAddress, size))
			++iter;
	}
}
*/

// warning: can be slow
void CalculateItemIndices(int itemSize)
{
	unsigned int itemIndex = 0;
	for(MemoryList::iterator iter = s_activeMemoryRegions.begin(); iter != s_activeMemoryRegions.end(); ++iter)
	{
		MemoryRegion& region = *iter;
		region.itemIndex = itemIndex;
		int startSkipSize = ((unsigned int)(itemSize - region.hardwareAddress)) % itemSize;
		unsigned int start = startSkipSize;
		unsigned int end = region.size;
		for(unsigned int i = start; i < end; i += itemSize)
			buffers->s_itemIndexToRegionPointer[itemIndex++] = &region;
	}
	s_maxItemIndex = itemIndex;
	s_itemIndicesInvalid = FALSE;
}

template<typename stepType, typename compareType, int swapXOR>
void UpdateRegionT(const MemoryRegion& region, const MemoryRegion* nextRegionPtr)
{
	if(s_prevValuesNeedUpdate)
		memcpy(buffers->s_prevValues + region.virtualIndex, buffers->s_curValues + region.virtualIndex, region.size + sizeof(compareType) - sizeof(stepType));

	unsigned int startSkipSize = ((unsigned int)(sizeof(stepType) - region.hardwareAddress)) % sizeof(stepType);

	unsigned char* sourceAddr = region.softwareAddress - region.virtualIndex;
	unsigned int indexStart = region.virtualIndex + startSkipSize;
	unsigned int indexEnd = region.virtualIndex + region.size;

	if(sizeof(compareType) == 1)
	{
		for(unsigned int i = indexStart; i < indexEnd; i++)
		{
			if(buffers->s_curValues[i] != sourceAddr[i^swapXOR]) // if value changed
			{
				buffers->s_curValues[i] = sourceAddr[i^swapXOR]; // update value
				//if(s_numChanges[i] != 0xFFFF)
					buffers->s_numChanges[i]++; // increase change count
			}
		}
	}
	else // it's more complicated for non-byte sizes because:
	{    // - more than one byte can affect a given change count entry
	     // - when more than one of those bytes changes simultaneously the entry's change count should only increase by 1
	     // - a few of those bytes can be outside the region

		unsigned int endSkipSize = ((unsigned int)(startSkipSize - region.size)) % sizeof(stepType);
		unsigned int lastIndexToRead = indexEnd + endSkipSize + sizeof(compareType) - sizeof(stepType);
		unsigned int lastIndexToCopy = lastIndexToRead;
		if(nextRegionPtr)
		{
			const MemoryRegion& nextRegion = *nextRegionPtr;
			int nextStartSkipSize = ((unsigned int)(sizeof(stepType) - nextRegion.hardwareAddress)) % sizeof(stepType);
			unsigned int nextIndexStart = nextRegion.virtualIndex + nextStartSkipSize;
			if(lastIndexToCopy > nextIndexStart)
				lastIndexToCopy = nextIndexStart;
		}

		unsigned int nextValidChange [sizeof(compareType)];
		for(unsigned int i = 0; i < sizeof(compareType); i++)
			nextValidChange[i] = indexStart + i;

		for(unsigned int i = indexStart, j = 0; i < lastIndexToRead; i++, j++)
		{
			if(buffers->s_curValues[i] != sourceAddr[i^swapXOR]) // if value of this byte changed
			{
				if(i < lastIndexToCopy)
					buffers->s_curValues[i] = sourceAddr[i^swapXOR]; // update value
				for(int k = 0; k < sizeof(compareType); k++) // loop through the previous entries that contain this byte
				{
					if(i >= indexEnd+k)
						continue;
					int m = (j-k+sizeof(compareType)) & (sizeof(compareType)-1);
					if(nextValidChange[m]+sizeof(compareType) <= i+sizeof(compareType)) // if we didn't already increase the change count for this entry
					{
						//if(s_numChanges[i-k] != 0xFFFF)
							buffers->s_numChanges[i-k]++; // increase the change count for this entry
						nextValidChange[m] = i+sizeof(compareType); // and remember not to increase it again
					}
				}
			}
		}
	}
}

template<typename stepType, typename compareType>
void UpdateRegionsT()
{
	for(MemoryList::iterator iter = s_activeMemoryRegions.begin(); iter != s_activeMemoryRegions.end();)
	{
		const MemoryRegion& region = *iter;
		++iter;
		const MemoryRegion* nextRegion = (iter == s_activeMemoryRegions.end()) ? NULL : &*iter;

		if(region.byteSwapped)
			UpdateRegionT<stepType, compareType, 1>(region, nextRegion);
		else
			UpdateRegionT<stepType, compareType, 0>(region, nextRegion);
	}

	s_prevValuesNeedUpdate = false;
}

template<typename stepType, typename compareType>
int CountRegionItemsT()
{
	if(sizeof(stepType) == 1)
	{
		if(s_activeMemoryRegions.empty())
			return 0;

		if(s_itemIndicesInvalid)
			CalculateItemIndices(sizeof(stepType));

		MemoryRegion& lastRegion = s_activeMemoryRegions.back();
		return lastRegion.itemIndex + lastRegion.size;
	}
	else // the branch above is faster but won't work if the step size isn't 1
	{
		int total = 0;
		for(MemoryList::iterator iter = s_activeMemoryRegions.begin(); iter != s_activeMemoryRegions.end(); ++iter)
		{
			MemoryRegion& region = *iter;
			int startSkipSize = ((unsigned int)(sizeof(stepType) - region.hardwareAddress)) % sizeof(stepType);
			total += (region.size - startSkipSize + (sizeof(stepType)-1)) / sizeof(stepType);
		}
		return total;
	}
}

// returns information about the item in the form of a "fake" region
// that has the item in it and nothing else
template<typename stepType, typename compareType>
void ItemIndexToVirtualRegion(unsigned int itemIndex, MemoryRegion& virtualRegion)
{
	if(s_itemIndicesInvalid)
		CalculateItemIndices(sizeof(stepType));

	if(itemIndex >= s_maxItemIndex)
	{
		memset(&virtualRegion, 0, sizeof(MemoryRegion));
		return;
	}

	const MemoryRegion* regionPtr = buffers->s_itemIndexToRegionPointer[itemIndex];
	const MemoryRegion& region = *regionPtr;

	int bytesWithinRegion = (itemIndex - region.itemIndex) * sizeof(stepType);
	int startSkipSize = ((unsigned int)(sizeof(stepType) - region.hardwareAddress)) % sizeof(stepType);
	bytesWithinRegion += startSkipSize;
	
	virtualRegion.size = sizeof(compareType);
	virtualRegion.hardwareAddress = region.hardwareAddress + bytesWithinRegion;
	virtualRegion.softwareAddress = region.softwareAddress + bytesWithinRegion;
	virtualRegion.virtualIndex = region.virtualIndex + bytesWithinRegion;
	virtualRegion.byteSwapped = region.byteSwapped;
	virtualRegion.itemIndex = itemIndex;
	return;
}

template<typename stepType, typename compareType>
unsigned int ItemIndexToVirtualIndex(unsigned int itemIndex)
{
	MemoryRegion virtualRegion;
	ItemIndexToVirtualRegion<stepType,compareType>(itemIndex, virtualRegion);
	return virtualRegion.virtualIndex;
}

template<typename T>
T ReadBigEndian(const unsigned char* data)
{
	T rv = 0;
	for(int i = 0; i < sizeof(T); i++)
	{
		rv <<= 8;
		rv |= *data++;
	}
	return rv;
}
template<> signed char ReadBigEndian(const unsigned char* data) { return *data; }
template<> unsigned char ReadBigEndian(const unsigned char* data) { return *data; }


template<typename stepType, typename compareType>
compareType GetPrevValueFromVirtualIndex(unsigned int virtualIndex)
{
//	return ReadBigEndian<compareType>(buffers->s_prevValues + virtualIndex);
	return *(compareType*)(buffers->s_prevValues+virtualIndex);
}
template<typename stepType, typename compareType>
compareType GetCurValueFromVirtualIndex(unsigned int virtualIndex)
{
//	return ReadBigEndian<compareType>(buffers->s_curValues + virtualIndex);
	return *(compareType*)(buffers->s_curValues+virtualIndex);
}
template<typename stepType, typename compareType>
unsigned short GetNumChangesFromVirtualIndex(unsigned int virtualIndex)
{
	unsigned short num = buffers->s_numChanges[virtualIndex];
	//for(unsigned int i = 1; i < sizeof(stepType); i++)
	//	if(num < s_numChanges[virtualIndex+i])
	//		num = s_numChanges[virtualIndex+i];
	return num;
}

template<typename stepType, typename compareType>
compareType GetPrevValueFromItemIndex(unsigned int itemIndex)
{
	int virtualIndex = ItemIndexToVirtualIndex<stepType,compareType>(itemIndex);
	return GetPrevValueFromVirtualIndex<stepType,compareType>(virtualIndex);
}
template<typename stepType, typename compareType>
compareType GetCurValueFromItemIndex(unsigned int itemIndex)
{
	int virtualIndex = ItemIndexToVirtualIndex<stepType,compareType>(itemIndex);
	return GetCurValueFromVirtualIndex<stepType,compareType>(virtualIndex);
}
template<typename stepType, typename compareType>
unsigned short GetNumChangesFromItemIndex(unsigned int itemIndex)
{
	int virtualIndex = ItemIndexToVirtualIndex<stepType,compareType>(itemIndex);
	return GetNumChangesFromVirtualIndex<stepType,compareType>(virtualIndex);
}
template<typename stepType, typename compareType>
unsigned int GetHardwareAddressFromItemIndex(unsigned int itemIndex)
{
	MemoryRegion virtualRegion;
	ItemIndexToVirtualRegion<stepType,compareType>(itemIndex, virtualRegion);
	return virtualRegion.hardwareAddress;
}

// this one might be unreliable, haven't used it much
template<typename stepType, typename compareType>
unsigned int HardwareAddressToItemIndex(unsigned int hardwareAddress)
{
	if(s_itemIndicesInvalid)
		CalculateItemIndices(sizeof(stepType));

	for(MemoryList::iterator iter = s_activeMemoryRegions.begin(); iter != s_activeMemoryRegions.end(); ++iter)
	{
		MemoryRegion& region = *iter;
		if(hardwareAddress >= region.hardwareAddress && hardwareAddress < region.hardwareAddress + region.size)
		{
			int indexWithinRegion = (hardwareAddress - region.hardwareAddress) / sizeof(stepType);
			return region.itemIndex + indexWithinRegion;
		}
	}

	return -1;
}




// workaround for a parser error in MSVC that sometimes deletes a comma preceeding a macro
// this macro takes a type and a signed/unsigned modifier, and returns the same type with that modifier whether or not the compiler decides to delete the comma between them
template<typename T, typename ignored=void>
struct DummyType { typedef T t; };
#define COMMAHACK(sign, type) DummyType<sign type, sign>::t
#ifdef _MSC_VER
#pragma warning(disable : 4114) // disable "same modifier used twice" warning that otherwise would get issued when the compiler bug happens
#endif

// it's ugly but I can't think of a better way to call these functions that isn't also slower, since
// I need the current values of these arguments to determine which primitive types are used within the function
#define CALL_WITH_T_SIZE_TYPES(functionName, sizeTypeID, isSigned, requireAligned, ...) \
	(sizeTypeID == 'b' \
		? (isSigned \
			? functionName<char, COMMAHACK(signed,char)>(__VA_ARGS__) \
			: functionName<char, COMMAHACK(unsigned,char)>(__VA_ARGS__)) \
	: sizeTypeID == 'w' \
		? (isSigned \
			? (requireAligned \
				? functionName<short, COMMAHACK(signed,short)>(__VA_ARGS__) \
				: functionName<char, COMMAHACK(signed,short)>(__VA_ARGS__)) \
			: (requireAligned \
				? functionName<short, COMMAHACK(unsigned,short)>(__VA_ARGS__) \
				: functionName<char, COMMAHACK(unsigned,short)>(__VA_ARGS__))) \
	: sizeTypeID == 'd' \
		? (isSigned \
			? (requireAligned \
				? functionName<short, COMMAHACK(signed,long)>(__VA_ARGS__) \
				: functionName<char, COMMAHACK(signed,long)>(__VA_ARGS__)) \
			: (requireAligned \
				? functionName<short, COMMAHACK(unsigned,long)>(__VA_ARGS__) \
				: functionName<char, COMMAHACK(unsigned,long)>(__VA_ARGS__))) \
	: functionName<char, COMMAHACK(signed,char)>(__VA_ARGS__))

// version that takes a forced comparison type
#define CALL_WITH_T_STEP(functionName, sizeTypeID, sign,type, requireAligned, ...) \
	(sizeTypeID == 'b' \
		? functionName<char, COMMAHACK(sign,type)>(__VA_ARGS__) \
	: sizeTypeID == 'w' \
		? (requireAligned \
			? functionName<short, COMMAHACK(sign,type)>(__VA_ARGS__) \
			: functionName<char, COMMAHACK(sign,type)>(__VA_ARGS__)) \
	: sizeTypeID == 'd' \
		? (requireAligned \
			? functionName<short, COMMAHACK(sign,type)>(__VA_ARGS__) \
			: functionName<char, COMMAHACK(sign,type)>(__VA_ARGS__)) \
	: functionName<char, COMMAHACK(sign,type)>(__VA_ARGS__))


// basic comparison functions:
template <typename T> inline bool LessCmp (T x, T y, T i)        { return x < y; }
template <typename T> inline bool MoreCmp (T x, T y, T i)        { return x > y; }
template <typename T> inline bool LessEqualCmp (T x, T y, T i)   { return x <= y; }
template <typename T> inline bool MoreEqualCmp (T x, T y, T i)   { return x >= y; }
template <typename T> inline bool EqualCmp (T x, T y, T i)       { return x == y; }
template <typename T> inline bool UnequalCmp (T x, T y, T i)     { return x != y; }
template <typename T> inline bool DiffByCmp (T x, T y, T p)      { return x - y == p || y - x == p; }
template <typename T> inline bool ModIsCmp (T x, T y, T p)       { return p && x % p == y; }

// compare-to type functions:
template<typename stepType, typename T>
void SearchRelative (bool(*cmpFun)(T,T,T), T ignored, T param)
{
	for(MemoryList::iterator iter = s_activeMemoryRegions.begin(); iter != s_activeMemoryRegions.end(); )
	{
		MemoryRegion& region = *iter;
		int startSkipSize = ((unsigned int)(sizeof(stepType) - region.hardwareAddress)) % sizeof(stepType);
		unsigned int start = region.virtualIndex + startSkipSize;
		unsigned int end = region.virtualIndex + region.size;
		for(unsigned int i = start, hwaddr = region.hardwareAddress; i < end; i += sizeof(stepType), hwaddr += sizeof(stepType))
			if(!cmpFun(GetCurValueFromVirtualIndex<stepType,T>(i), GetPrevValueFromVirtualIndex<stepType,T>(i), param))
				if(2 == DeactivateRegion(region, iter, hwaddr, sizeof(stepType)))
					goto outerContinue;
		++iter;
outerContinue:
		continue;
	}
}
template<typename stepType, typename T>
void SearchSpecific (bool(*cmpFun)(T,T,T), T value, T param)
{
	for(MemoryList::iterator iter = s_activeMemoryRegions.begin(); iter != s_activeMemoryRegions.end(); )
	{
		MemoryRegion& region = *iter;
		int startSkipSize = ((unsigned int)(sizeof(stepType) - region.hardwareAddress)) % sizeof(stepType);
		unsigned int start = region.virtualIndex + startSkipSize;
		unsigned int end = region.virtualIndex + region.size;
		for(unsigned int i = start, hwaddr = region.hardwareAddress; i < end; i += sizeof(stepType), hwaddr += sizeof(stepType))
			if(!cmpFun(GetCurValueFromVirtualIndex<stepType,T>(i), value, param))
				if(2 == DeactivateRegion(region, iter, hwaddr, sizeof(stepType)))
					goto outerContinue;
		++iter;
outerContinue:
		continue;
	}
}
template<typename stepType, typename T>
void SearchAddress (bool(*cmpFun)(T,T,T), T address, T param)
{
	for(MemoryList::iterator iter = s_activeMemoryRegions.begin(); iter != s_activeMemoryRegions.end(); )
	{
		MemoryRegion& region = *iter;
		int startSkipSize = ((unsigned int)(sizeof(stepType) - region.hardwareAddress)) % sizeof(stepType);
		unsigned int start = region.virtualIndex + startSkipSize;
		unsigned int end = region.virtualIndex + region.size;
		for(unsigned int i = start, hwaddr = region.hardwareAddress; i < end; i += sizeof(stepType), hwaddr += sizeof(stepType))
			if(!cmpFun(hwaddr, address, param))
				if(2 == DeactivateRegion(region, iter, hwaddr, sizeof(stepType)))
					goto outerContinue;
		++iter;
outerContinue:
		continue;
	}
}
template<typename stepType, typename T>
void SearchChanges (bool(*cmpFun)(T,T,T), T changes, T param)
{
	for(MemoryList::iterator iter = s_activeMemoryRegions.begin(); iter != s_activeMemoryRegions.end(); )
	{
		MemoryRegion& region = *iter;
		int startSkipSize = ((unsigned int)(sizeof(stepType) - region.hardwareAddress)) % sizeof(stepType);
		unsigned int start = region.virtualIndex + startSkipSize;
		unsigned int end = region.virtualIndex + region.size;
		for(unsigned int i = start, hwaddr = region.hardwareAddress; i < end; i += sizeof(stepType), hwaddr += sizeof(stepType))
			if(!cmpFun(GetNumChangesFromVirtualIndex<stepType,T>(i), changes, param))
				if(2 == DeactivateRegion(region, iter, hwaddr, sizeof(stepType)))
					goto outerContinue;
		++iter;
outerContinue:
		continue;
	}
}

char rs_c='s';
char rs_o='=';
char rs_t='s';
int rs_param=0, rs_val=0, rs_val_valid=0;
char rs_type_size = 'b', rs_last_type_size = rs_type_size;
bool noMisalign = true, rs_last_no_misalign = noMisalign;
bool littleEndian = false;
int last_rs_possible = -1;
int last_rs_regions = -1;

void prune(char c,char o,char t,int v,int p)
{
	// repetition-reducing macros
	#define DO_SEARCH(sf) \
	switch (o) \
	{ \
		case '<': DO_SEARCH_2(LessCmp,sf); break; \
		case '>': DO_SEARCH_2(MoreCmp,sf); break; \
		case '=': DO_SEARCH_2(EqualCmp,sf); break; \
		case '!': DO_SEARCH_2(UnequalCmp,sf); break; \
		case 'l': DO_SEARCH_2(LessEqualCmp,sf); break; \
		case 'm': DO_SEARCH_2(MoreEqualCmp,sf); break; \
		case 'd': DO_SEARCH_2(DiffByCmp,sf); break; \
		case '%': DO_SEARCH_2(ModIsCmp,sf); break; \
		default: assert(!"Invalid operator for this search type."); break; \
	}

	// perform the search, eliminating nonmatching values
	switch (c)
	{
		#define DO_SEARCH_2(CmpFun,sf) CALL_WITH_T_SIZE_TYPES(sf, rs_type_size, t, noMisalign, CmpFun,v,p)
		case 'r': DO_SEARCH(SearchRelative); break;
		case 's': DO_SEARCH(SearchSpecific); break;

		#undef DO_SEARCH_2
		#define DO_SEARCH_2(CmpFun,sf) CALL_WITH_T_STEP(sf, rs_type_size, unsigned,int, noMisalign, CmpFun,v,p);
		case 'a': DO_SEARCH(SearchAddress); break;

		#undef DO_SEARCH_2
		#define DO_SEARCH_2(CmpFun,sf) CALL_WITH_T_STEP(sf, rs_type_size, unsigned,short, noMisalign, CmpFun,v,p);
		case 'n': DO_SEARCH(SearchChanges); break;

		default: assert(!"Invalid search comparison type."); break;
	}

	s_prevValuesNeedUpdate = true;

	int prevNumItems = last_rs_possible;

	CompactAddrs();

	if(prevNumItems == last_rs_possible)
	{
		SetRamSearchUndoType(RamSearchHWnd, 0); // nothing to undo
	}
}




template<typename stepType, typename T>
bool CompareRelativeAtItem (bool(*cmpFun)(T,T,T), int itemIndex, T ignored, T param)
{
	return cmpFun(GetCurValueFromItemIndex<stepType,T>(itemIndex), GetPrevValueFromItemIndex<stepType,T>(itemIndex), param);
}
template<typename stepType, typename T>
bool CompareSpecificAtItem (bool(*cmpFun)(T,T,T), int itemIndex, T value, T param)
{
	return cmpFun(GetCurValueFromItemIndex<stepType,T>(itemIndex), value, param);
}
template<typename stepType, typename T>
bool CompareAddressAtItem (bool(*cmpFun)(T,T,T), int itemIndex, T address, T param)
{
	return cmpFun(GetHardwareAddressFromItemIndex<stepType,T>(itemIndex), address, param);
}
template<typename stepType, typename T>
bool CompareChangesAtItem (bool(*cmpFun)(T,T,T), int itemIndex, T changes, T param)
{
	return cmpFun(GetNumChangesFromItemIndex<stepType,T>(itemIndex), changes, param);
}

int ReadControlInt(int controlID, bool forceHex, BOOL& success)
{
	int rv = 0;
	BOOL ok = false;

	if(!forceHex)
	{
		rv = GetDlgItemInt(RamSearchHWnd,controlID,&ok,(rs_t == 's'));
	}

	if(!ok)
	{
		if(GetDlgItemText(RamSearchHWnd,controlID,Str_Tmp,16))
		{
			for(int i = 0; Str_Tmp[i]; i++) {if(toupper(Str_Tmp[i]) == 'O') Str_Tmp[i] = '0';}
			const char* strPtr = (const char*)Str_Tmp;
			bool negate = false;
			while(strPtr[0] == '-')
				strPtr++, negate = !negate;
			if(strPtr[0] == '+')
				strPtr++;
			if(strPtr[0] == '0' && tolower(strPtr[1]) == 'x')
				strPtr += 2, forceHex = true;
			if(strPtr[0] == '$')
				strPtr++, forceHex = true;
			if(!forceHex)
			{
				const char* strSearchPtr = strPtr;
				while(*strSearchPtr)
				{
					int c = tolower(*strSearchPtr++);
					if(c >= 'a' && c <= 'f')
						forceHex = true;
				}
			}
			const char* formatString = forceHex ? "%X" : ((rs_t=='s') ? "%d" : "%u");
			if(sscanf(strPtr, formatString, &rv) > 0)
				ok = true;
			if(negate)
				rv = -rv;
		}
	}

	success = ok;
	return rv;
}


bool Set_RS_Val()
{
	BOOL success;

	// update rs_val
	switch(rs_c)
	{
		case 'r':
		default:
			rs_val = 0;
			break;
		case 's':
			rs_val = ReadControlInt(IDC_EDIT_COMPAREVALUE, rs_t == 'h', success);
			if(!success)
				return false;
			if((rs_type_size == 'b' && rs_t == 's' && (rs_val < -128 || rs_val > 127)) ||
			   (rs_type_size == 'b' && rs_t != 's' && (rs_val < 0 || rs_val > 255)) ||
			   (rs_type_size == 'w' && rs_t == 's' && (rs_val < -32768 || rs_val > 32767)) ||
			   (rs_type_size == 'w' && rs_t != 's' && (rs_val < 0 || rs_val > 65535)))
			   return false;
			break;
		case 'a':
			rs_val = ReadControlInt(IDC_EDIT_COMPAREADDRESS, true, success);
			if(!success || rs_val < 0 || rs_val > 0x06040000)
				return false;
			break;
		case 'n': {
			rs_val = ReadControlInt(IDC_EDIT_COMPARECHANGES, false, success);
			if(!success || rs_val < 0 || rs_val > 0xFFFF)
				return false;
		}	break;
	}

	// also update rs_param
	switch(rs_o)
	{
		default:
			rs_param = 0;
			break;
		case 'd':
			rs_param = ReadControlInt(IDC_EDIT_DIFFBY, false, success);
			if(!success)
				return false;
			if(rs_param < 0)
				rs_param = -rs_param;
			break;
		case '%':
			rs_param = ReadControlInt(IDC_EDIT_MODBY, false, success);
			if(!success || rs_param == 0)
				return false;
			break;
	}

	// validate that rs_param fits in the comparison data type
	{
		int appliedSize = rs_type_size;
		int appliedSign = rs_t;
		if(rs_c == 'n')
			appliedSize = 'w', appliedSign = 'u';
		if(rs_c == 'a')
			appliedSize = 'd', appliedSign = 'u';
		if((appliedSize == 'b' && appliedSize == 's' && (rs_param < -128 || rs_param > 127)) ||
		   (appliedSize == 'b' && appliedSize != 's' && (rs_param < 0 || rs_param > 255)) ||
		   (appliedSize == 'w' && appliedSize == 's' && (rs_param < -32768 || rs_param > 32767)) ||
		   (appliedSize == 'w' && appliedSize != 's' && (rs_param < 0 || rs_param > 65535)))
		   return false;
	}

	return true;
}

bool IsSatisfied(int itemIndex)
{
	if(!rs_val_valid)
		return true;
	int o = rs_o;
	switch (rs_c)
	{
		#undef DO_SEARCH_2
		#define DO_SEARCH_2(CmpFun,sf) return CALL_WITH_T_SIZE_TYPES(sf, rs_type_size,(rs_t=='s'),noMisalign, CmpFun,itemIndex,rs_val,rs_param);
		case 'r': DO_SEARCH(CompareRelativeAtItem); break;
		case 's': DO_SEARCH(CompareSpecificAtItem); break;

		#undef DO_SEARCH_2
		#define DO_SEARCH_2(CmpFun,sf) return CALL_WITH_T_STEP(sf, rs_type_size, unsigned,int, noMisalign, CmpFun,itemIndex,rs_val,rs_param);
		case 'a': DO_SEARCH(CompareAddressAtItem); break;

		#undef DO_SEARCH_2
		#define DO_SEARCH_2(CmpFun,sf) return CALL_WITH_T_STEP(sf, rs_type_size, unsigned,short, noMisalign, CmpFun,itemIndex,rs_val,rs_param);
		case 'n': DO_SEARCH(CompareChangesAtItem); break;
	}
	return false;
}



// this is mainly for the RAM watch window
unsigned int ReadValueAtSoftwareAddress(const unsigned char* address, unsigned int size, int byteSwapped = false)
{
	unsigned int value = 0;
	if(!byteSwapped)
	{
		// convert to current endianness
		for(unsigned int i = 0; i < size; i++)
		{
			value <<= 8;
			value |= *address++;
		}
	}
	else
	{
		// byte-swap and convert to current endianness at the same time
		for(unsigned int i = 0; i < size; i++)
		{
			value <<= 8;
			value |= *((unsigned char*)((intptr_t)address++^1));
		}
	}
	return value;
}
void WriteValueAtSoftwareAddress(unsigned char* address, unsigned int value, unsigned int size, int byteSwapped = false)
{
	if(!byteSwapped)
	{
		// write as big endian
		for(int i = size-1; i >= 0; i--)
		{
			address[i] = value & 0xFF;
			value >>= 8;
		}
	}
	else
	{
		// byte-swap and write as big endian at the same time
		for(int i = size-1; i >= 0; i--)
		{
			*((unsigned char*)((intptr_t)(address+i)^1)) = value & 0xFF;
			value >>= 8;
		}
	}
}
inline bool IsInRange(unsigned int x, unsigned int min, unsigned int size)
{
	x -= min;
	return x < size;
}
unsigned int ReadValueAtHardwareAddress(unsigned int address, unsigned int size)
{
	if((address & ~0xFFFFFF) == ~0xFFFFFF)
		address &= 0xFFFFFF;


	char buf[4];
//	MMU_DumpMemBlock(0, address, 4, (u8*)buf);

	u32 val_u32 = *(u32*)buf;
	u16 val_u16 = *(u16*)buf;
	u8 val_u8 = *(u8*)buf;

	switch (size)
	{
	case 1: return val_u8;
	case 2: return val_u16;
	case 4: return val_u32;
	}
//	if(IsInRange(address, 0xFF0000, _68K_RAM_SIZE))
//		return ReadValueAtSoftwareAddress(Ram_68k + address - 0xFF0000, size, true);	
/*	if(IsInRange(address, 0xFF0000, _68K_RAM_SIZE)) TODO
		return ReadValueAtSoftwareAddress(Ram_68k + address - 0xFF0000, size, true);
	if(IsInRange(address, 0xA00000, Z80_RAM_SIZE))
		return ReadValueAtSoftwareAddress(Ram_Z80 + address - 0xA00000, size, true);
	if(SegaCD_Started && IsInRange(address, 0x020000, SEGACD_RAM_PRG_SIZE))
		return ReadValueAtSoftwareAddress(Ram_Prg + address - 0x020000, size, true);
	if(SegaCD_Started && IsInRange(address, 0x200000, SEGACD_1M_RAM_SIZE))
		return ReadValueAtSoftwareAddress(((Ram_Word_State & 0x2) ? Ram_Word_1M : Ram_Word_2M) + address - 0x200000, size, true);
	if(IsInRange(address, 0x0, Rom_Size))
		return ReadValueAtSoftwareAddress(Rom_Data + address, size, true);
	if(_32X_Started && IsInRange(address, 0x06000000, _32X_RAM_SIZE))
		return ReadValueAtSoftwareAddress(_32X_Ram + address - 0x06000000, size, false);*/
	return 0;
}
bool WriteValueAtHardwareRAMAddress(unsigned int address, unsigned int value, unsigned int size, bool hookless)
{
	if((address & ~0xFFFFFF) == ~0xFFFFFF)
		address &= 0xFFFFFF;
/*	if(IsInRange(address, 0xFF0000, _68K_RAM_SIZE)) TODO
		WriteValueAtSoftwareAddress(Ram_68k + address - 0xFF0000, value, size, true);
	else if(IsInRange(address, 0xA00000, Z80_RAM_SIZE))
		WriteValueAtSoftwareAddress(Ram_Z80 + address - 0xA00000, value, size, true);
	else if(SegaCD_Started && IsInRange(address, 0x020000, SEGACD_RAM_PRG_SIZE))
		WriteValueAtSoftwareAddress(Ram_Prg + address - 0x020000, value, size, true);
	else if(SegaCD_Started && IsInRange(address, 0x200000, SEGACD_1M_RAM_SIZE))
		WriteValueAtSoftwareAddress(((Ram_Word_State & 0x2) ? Ram_Word_1M : Ram_Word_2M) + address - 0x200000, value, size, true);
	else if(_32X_Started && IsInRange(address, 0x06000000, _32X_RAM_SIZE))
		WriteValueAtSoftwareAddress(_32X_Ram + address - 0x06000000, value, size, false);*/
//	else return false;
//	if(!hookless) // a script that calls e.g. memory.writebyte() should trigger write hooks
//		CallRegisteredLuaMemHook(address, size, value, LUAMEMHOOK_WRITE);
	return true;
}
bool WriteValueAtHardwareROMAddress(unsigned int address, unsigned int value, unsigned int size)
{
	if(IsInRange(address, 0x0, Rom_Size))
		WriteValueAtSoftwareAddress(Rom_Data + address, value, size, true);
	else return false;
	return true;
}
bool WriteValueAtHardwareAddress(unsigned int address, unsigned int value, unsigned int size, bool hookless=false)
{
	return WriteValueAtHardwareRAMAddress(address, value, size, hookless) ||
	       WriteValueAtHardwareROMAddress(address, value, size);
}
bool IsHardwareRAMAddressValid(unsigned int address)
{
	if((address & ~0xFFFFFF) == ~0xFFFFFF)
		address &= 0xFFFFFF;
//	if(IsInRange(address, 0xFF0000, _68K_RAM_SIZE))
//		return true;
//	if(IsInRange(address, 0xA00000, Z80_RAM_SIZE))
//		return true;

	return true; //TODO
/*	if(SegaCD_Started && IsInRange(address, 0x020000, SEGACD_RAM_PRG_SIZE)) TODO
		return true;
	if(SegaCD_Started && IsInRange(address, 0x200000, SEGACD_1M_RAM_SIZE))
		return true;
	if(_32X_Started && IsInRange(address, 0x06000000, _32X_RAM_SIZE))
		return true;*/
	return false;
}
bool IsHardwareROMAddressValid(unsigned int address)
{
	return IsInRange(address, 0x0, Rom_Size);
}
bool IsHardwareAddressValid(unsigned int address)
{
	return IsHardwareROMAddressValid(address) || IsHardwareRAMAddressValid(address);
}



int ResultCount=0;
bool AutoSearch=false;
bool AutoSearchAutoRetry=false;
LRESULT CALLBACK PromptWatchNameProc(HWND, UINT, WPARAM, LPARAM);
void UpdatePossibilities(int rs_possible, int regions);


void CompactAddrs()
{
	int size = (rs_type_size=='b' || !noMisalign) ? 1 : 2;
	int prevResultCount = ResultCount;

	CalculateItemIndices(size);

	ResultCount = CALL_WITH_T_SIZE_TYPES(CountRegionItemsT, rs_type_size,rs_t=='s',noMisalign);
	UpdatePossibilities(ResultCount, (int)s_activeMemoryRegions.size());

	if(ResultCount != prevResultCount)
		ListView_SetItemCount(GetDlgItem(RamSearchHWnd,IDC_RAMLIST),ResultCount);
}

void soft_reset_address_info ()
{
	ResetMemoryRegions();
	memset(buffers->s_numChanges, 0, sizeof(buffers->s_numChanges));
	CompactAddrs();
}
void reset_address_info ()
{
	SetRamSearchUndoType(RamSearchHWnd, 0);
	s_activeMemoryRegionsBackup.clear(); // not necessary, but we'll take the time hit here instead of at the next thing that sets up an undo
	memcpy(buffers->s_prevValues, buffers->s_curValues, sizeof(buffers->s_prevValues));
	s_prevValuesNeedUpdate = false;
	ResetMemoryRegions();
	if(!RamSearchHWnd)
	{
		s_activeMemoryRegions.clear();
		ResultCount = 0;
	}
	else
	{
		// force s_prevValues to be valid
		signal_new_frame();
		s_prevValuesNeedUpdate = true;
		signal_new_frame();
	}
	memset(buffers->s_numChanges, 0, sizeof(buffers->s_numChanges));
	CompactAddrs();
}

void signal_new_frame ()
{
	CALL_WITH_T_SIZE_TYPES(UpdateRegionsT, rs_type_size,rs_t=='s',noMisalign);
}







void RefreshRamListSelectedCountControlStatus(HWND hDlg)
{
	static int prevSelCount=-1;
	int selCount = ListView_GetSelectedCount(GetDlgItem(hDlg,IDC_RAMLIST));
	if(selCount != prevSelCount)
	{
		if(selCount < 2 || prevSelCount < 2)
		{
			EnableWindow(GetDlgItem(hDlg, IDC_C_WATCH), (selCount == 1 && WatchCount < MAX_WATCH_COUNT) ? TRUE : FALSE);
			EnableWindow(GetDlgItem(hDlg, IDC_C_ADDCHEAT), (selCount == 1) ? /*TRUE*/FALSE : FALSE);
			EnableWindow(GetDlgItem(hDlg, IDC_C_ELIMINATE), (selCount >= 1) ? TRUE : FALSE);
		}
		prevSelCount = selCount;
	}
}




struct AddrRange
{
	unsigned int addr;
	unsigned int size;
	unsigned int End() const { return addr + size; }
	AddrRange(unsigned int a, unsigned int s) : addr(a),size(s){}
};

void signal_new_size ()
{
	HWND lv = GetDlgItem(RamSearchHWnd,IDC_RAMLIST);

	int oldSize = (rs_last_type_size=='b' || !rs_last_no_misalign) ? 1 : 2;
	int newSize = (rs_type_size=='b' || !noMisalign) ? 1 : 2;
	bool numberOfItemsChanged = (oldSize != newSize);

	unsigned int itemsPerPage = ListView_GetCountPerPage(lv);
	unsigned int oldTopIndex = ListView_GetTopIndex(lv);
	unsigned int oldSelectionIndex = ListView_GetSelectionMark(lv);
	unsigned int oldTopAddr = CALL_WITH_T_SIZE_TYPES(GetHardwareAddressFromItemIndex, rs_last_type_size,rs_t=='s',rs_last_no_misalign, oldTopIndex);
	unsigned int oldSelectionAddr = CALL_WITH_T_SIZE_TYPES(GetHardwareAddressFromItemIndex, rs_last_type_size,rs_t=='s',rs_last_no_misalign, oldSelectionIndex);

	std::vector<AddrRange> selHardwareAddrs;
	if(numberOfItemsChanged)
	{
		// store selection ranges
		// unfortunately this can take a while if the user has a huge range of items selected
//		Clear_Sound_Buffer();
		int selCount = ListView_GetSelectedCount(lv);
		int size = (rs_last_type_size=='b' || !rs_last_no_misalign) ? 1 : 2;
		int watchIndex = -1;
		for(int i = 0; i < selCount; ++i)
		{
			watchIndex = ListView_GetNextItem(lv, watchIndex, LVNI_SELECTED);
			int addr = CALL_WITH_T_SIZE_TYPES(GetHardwareAddressFromItemIndex, rs_last_type_size,rs_t=='s',rs_last_no_misalign, watchIndex);
			if(!selHardwareAddrs.empty() && addr == selHardwareAddrs.back().End())
				selHardwareAddrs.back().size += size;
			else
				selHardwareAddrs.push_back(AddrRange(addr,size));
		}
	}

	CompactAddrs();

	rs_last_type_size = rs_type_size;
	rs_last_no_misalign = noMisalign;

	if(numberOfItemsChanged)
	{
		// restore selection ranges
		unsigned int newTopIndex = CALL_WITH_T_SIZE_TYPES(HardwareAddressToItemIndex, rs_type_size,rs_t=='s',noMisalign, oldTopAddr);
		unsigned int newBottomIndex = newTopIndex + itemsPerPage - 1;
		SendMessage(lv, WM_SETREDRAW, FALSE, 0);
		ListView_SetItemState(lv, -1, 0, LVIS_SELECTED|LVIS_FOCUSED); // deselect all
		for(unsigned int i = 0; i < selHardwareAddrs.size(); i++)
		{
			// calculate index ranges of this selection
			const AddrRange& range = selHardwareAddrs[i];
			int selRangeTop = CALL_WITH_T_SIZE_TYPES(HardwareAddressToItemIndex, rs_type_size,rs_t=='s',noMisalign, range.addr);
			int selRangeBottom = -1;
			for(int endAddr = range.End()-1; endAddr >= selRangeTop && selRangeBottom == -1; endAddr--)
				selRangeBottom = CALL_WITH_T_SIZE_TYPES(HardwareAddressToItemIndex, rs_type_size,rs_t=='s',noMisalign, endAddr);
			if(selRangeBottom == -1)
				selRangeBottom = selRangeTop;
			if(selRangeTop == -1)
				continue;

			// select the entire range at once without deselecting the other ranges
			// looks hacky but it works, and the only documentation I found on how to do this was blatantly false and equally hacky anyway
			POINT pos;
			ListView_EnsureVisible(lv, selRangeTop, 0);
			ListView_GetItemPosition(lv, selRangeTop, &pos);
			SendMessage(lv, WM_LBUTTONDOWN, MK_LBUTTON|MK_CONTROL, MAKELONG(pos.x,pos.y));
			ListView_EnsureVisible(lv, selRangeBottom, 0);
			ListView_GetItemPosition(lv, selRangeBottom, &pos);
			SendMessage(lv, WM_LBUTTONDOWN, MK_LBUTTON|MK_CONTROL|MK_SHIFT, MAKELONG(pos.x,pos.y));
		}

		// restore previous scroll position
		if(newBottomIndex != -1)
			ListView_EnsureVisible(lv, newBottomIndex, 0);
		if(newTopIndex != -1)
			ListView_EnsureVisible(lv, newTopIndex, 0);

		SendMessage(lv, WM_SETREDRAW, TRUE, 0);

		RefreshRamListSelectedCountControlStatus(RamSearchHWnd);

		EnableWindow(GetDlgItem(RamSearchHWnd,IDC_MISALIGN), rs_type_size != 'b');
	}
	else
	{
		ListView_Update(lv, -1);
	}
}




LRESULT CustomDraw (LPARAM lParam)
{
	LPNMLVCUSTOMDRAW lplvcd = (LPNMLVCUSTOMDRAW)lParam;

	switch(lplvcd->nmcd.dwDrawStage) 
	{
		case CDDS_PREPAINT :
			return CDRF_NOTIFYITEMDRAW;

		case CDDS_ITEMPREPAINT:
		{
			int rv = CDRF_DODEFAULT;

			if(lplvcd->nmcd.dwItemSpec % 2)
			{
				// alternate the background color slightly
				lplvcd->clrTextBk = RGB(248,248,255);
				rv = CDRF_NEWFONT;
			}

			if(!IsSatisfied(lplvcd->nmcd.dwItemSpec))
			{
				// tint red any items that would be eliminated if a search were to run now
				lplvcd->clrText = RGB(192,64,64);
				rv = CDRF_NEWFONT;
			}

			return rv;
		}	break;
	}
	return CDRF_DODEFAULT;
}

//extern "C" int disableRamSearchUpdate;
extern "C" void Update_RAM_Search() //keeps RAM values up to date in the search and watch windows
{
	
	if (!RamSearchHWnd) return;

	//	if(disableRamSearchUpdate)
//		return;

	if (AutoSearch && !ResultCount)
	{
		if(!AutoSearchAutoRetry)
		{
//			Clear_Sound_Buffer();
			int answer = MessageBox(RamSearchHWnd,(LPCWSTR)_16("Choosing Retry will reset the search once and continue autosearching.\nChoose Ignore will reset the search whenever necessary and continue autosearching.\nChoosing Abort will reset the search once and stop autosearching."),(LPCWSTR) _16("Autosearch - out of results."),MB_ABORTRETRYIGNORE|MB_DEFBUTTON2|MB_ICONINFORMATION);
			if(answer == IDABORT)
			{
				SendDlgItemMessage(RamSearchHWnd, IDC_C_AUTOSEARCH, BM_SETCHECK, BST_UNCHECKED, 0);
				SendMessage(RamSearchHWnd, WM_COMMAND, IDC_C_AUTOSEARCH, 0);
			}
			if(answer == IDIGNORE)
				AutoSearchAutoRetry = true;
		}
		reset_address_info();
	}

	int prevValuesNeededUpdate = s_prevValuesNeedUpdate;
	if (RamSearchHWnd)
	{
		// update active RAM values
		signal_new_frame();
	}

	if (AutoSearch && ResultCount)
	{
		//Clear_Sound_Buffer();
		if(!rs_val_valid)
			rs_val_valid = Set_RS_Val();
		if(rs_val_valid)
			prune(rs_c,rs_o,rs_t=='s',rs_val,rs_param);
	}

	if(RamSearchHWnd)
	{
		HWND lv = GetDlgItem(RamSearchHWnd,IDC_RAMLIST);
		if(prevValuesNeededUpdate != s_prevValuesNeedUpdate)
		{
			// previous values got updated, refresh everything visible
			ListView_Update(lv, -1);
		}
		else
		{
			// refresh any visible parts of the listview box that changed
			static int changes[128];
			int top = ListView_GetTopIndex(lv);
			int count = ListView_GetCountPerPage(lv);
			int start = -1;
			for(int i = top; i <= top+count; i++)
			{
				int changeNum = CALL_WITH_T_SIZE_TYPES(GetNumChangesFromItemIndex, rs_type_size,rs_t=='s',noMisalign, i); //s_numChanges[i];
				int changed = changeNum != changes[i-top];
				if(changed)
					changes[i-top] = changeNum;

				if(start == -1)
				{
					if(i != top+count && changed)
					{
						start = i;
						//somethingChanged = true;
					}
				}
				else
				{
					if(i == top+count || !changed)
					{
						ListView_RedrawItems(lv, start, i-1);
						start = -1;
					}
				}
			}
		}
	}

	if(RamWatchHWnd)
	{
		Update_RAM_Watch();
	}
}

static int rs_lastPercent = -1;
inline void UpdateRamSearchProgressBar(int percent)
{
	if(rs_lastPercent != percent)
	{
		rs_lastPercent = percent;
		UpdateRamSearchTitleBar(percent);
	}
}

static void SelectEditControl(int controlID)
{
	HWND hEdit = GetDlgItem(RamSearchHWnd,controlID);
	SetFocus(hEdit);
	SendMessage(hEdit, EM_SETSEL, 0, -1);
}

static BOOL SelectingByKeyboard()
{
	int a = GetKeyState(VK_LEFT);
	int b = GetKeyState(VK_RIGHT);
	int c = GetKeyState(VK_UP);
	int d = GetKeyState(VK_DOWN); // space and tab are intentionally omitted
	return (a | b | c | d) & 0x80;
}

extern void init_list_box(HWND Box, const char* Strs[], int numColumns, int *columnWidths);

LRESULT CALLBACK RamSearchProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	RECT r;
	RECT r2;
	int dx1, dy1, dx2, dy2;
	static int watchIndex=0;

	switch(uMsg)
	{
		case WM_INITDIALOG: {
			RamSearchHWnd = hDlg;
/*			if (Full_Screen)
			{
				while (ShowCursor(false) >= 0);
				while (ShowCursor(true) < 0);
			}*/

			GetWindowRect(YabWin, &r);
			dx1 = (r.right - r.left) / 2;
			dy1 = (r.bottom - r.top) / 2;

			GetWindowRect(hDlg, &r2);
			dx2 = (r2.right - r2.left) / 2;
			dy2 = (r2.bottom - r2.top) / 2;

			// push it away from the main window if we can
			const int width = (r.right-r.left); 
			const int width2 = (r2.right-r2.left); 
			if(r.left+width2 + width < GetSystemMetrics(SM_CXSCREEN))
			{
				r.right += width;
				r.left += width;
			}
			else if((int)r.left - (int)width2 > 0)
			{
				r.right -= width2;
				r.left -= width2;
			}

			SetWindowPos(hDlg, NULL, r.left, r.top, NULL, NULL, SWP_NOSIZE | SWP_NOZORDER | SWP_SHOWWINDOW);
			switch(rs_o)
			{
				case '<':
					SendDlgItemMessage(hDlg, IDC_LESSTHAN, BM_SETCHECK, BST_CHECKED, 0);
					break;
				case '>':
					SendDlgItemMessage(hDlg, IDC_MORETHAN, BM_SETCHECK, BST_CHECKED, 0);
					break;
				case 'l':
					SendDlgItemMessage(hDlg, IDC_NOMORETHAN, BM_SETCHECK, BST_CHECKED, 0);
					break;
				case 'm':
					SendDlgItemMessage(hDlg, IDC_NOLESSTHAN, BM_SETCHECK, BST_CHECKED, 0);
					break;
				case '=': 
					SendDlgItemMessage(hDlg, IDC_EQUALTO, BM_SETCHECK, BST_CHECKED, 0);
					break;
				case '!':
					SendDlgItemMessage(hDlg, IDC_DIFFERENTFROM, BM_SETCHECK, BST_CHECKED, 0);
					break;
				case 'd':
					SendDlgItemMessage(hDlg, IDC_DIFFERENTBY, BM_SETCHECK, BST_CHECKED, 0);
					EnableWindow(GetDlgItem(hDlg,IDC_EDIT_DIFFBY),true);
					break;
				case '%':
					SendDlgItemMessage(hDlg, IDC_MODULO, BM_SETCHECK, BST_CHECKED, 0);
					EnableWindow(GetDlgItem(hDlg,IDC_EDIT_MODBY),true);
					break;
			}
			switch (rs_c)
			{
				case 'r':
					SendDlgItemMessage(hDlg, IDC_PREVIOUSVALUE, BM_SETCHECK, BST_CHECKED, 0);
					break;
				case 's':
					SendDlgItemMessage(hDlg, IDC_SPECIFICVALUE, BM_SETCHECK, BST_CHECKED, 0);
					EnableWindow(GetDlgItem(hDlg,IDC_EDIT_COMPAREVALUE),true);
					break;
				case 'a':
					SendDlgItemMessage(hDlg, IDC_SPECIFICADDRESS, BM_SETCHECK, BST_CHECKED, 0);
					EnableWindow(GetDlgItem(hDlg,IDC_EDIT_COMPAREADDRESS),true);
					break;
				case 'n':
					SendDlgItemMessage(hDlg, IDC_NUMBEROFCHANGES, BM_SETCHECK, BST_CHECKED, 0);
					EnableWindow(GetDlgItem(hDlg,IDC_EDIT_COMPARECHANGES),true);
					break;
			}
			switch (rs_t)
			{
				case 's':
					SendDlgItemMessage(hDlg, IDC_SIGNED, BM_SETCHECK, BST_CHECKED, 0);
					break;
				case 'u':
					SendDlgItemMessage(hDlg, IDC_UNSIGNED, BM_SETCHECK, BST_CHECKED, 0);
					break;
				case 'h':
					SendDlgItemMessage(hDlg, IDC_HEX, BM_SETCHECK, BST_CHECKED, 0);
					break;
			}
			switch (rs_type_size)
			{
				case 'b':
					SendDlgItemMessage(hDlg, IDC_1_BYTE, BM_SETCHECK, BST_CHECKED, 0);
					break;
				case 'w':
					SendDlgItemMessage(hDlg, IDC_2_BYTES, BM_SETCHECK, BST_CHECKED, 0);
					break;
				case 'd':
					SendDlgItemMessage(hDlg, IDC_4_BYTES, BM_SETCHECK, BST_CHECKED, 0);
					break;
			}

			s_prevValuesNeedUpdate = true;

			SendDlgItemMessage(hDlg,IDC_C_AUTOSEARCH,BM_SETCHECK,AutoSearch?BST_CHECKED:BST_UNCHECKED,0);
			//const char* names[5] = {"Address","Value","Previous","Changes","Notes"};
			//int widths[5] = {62,64,64,55,55};
			const char* names[] = {"Address","Value","Previous","Changes"};
			int widths[4] = {68,76,76,68};
			if (!ResultCount)
				reset_address_info();
			else
			{
				signal_new_frame();
				CompactAddrs();
			}
			init_list_box(GetDlgItem(hDlg,IDC_RAMLIST),names,4,widths);
			//ListView_SetItemCount(GetDlgItem(hDlg,IDC_RAMLIST),ResultCount);
			if (!noMisalign) SendDlgItemMessage(hDlg, IDC_MISALIGN, BM_SETCHECK, BST_CHECKED, 0);
//			if (littleEndian) SendDlgItemMessage(hDlg, IDC_ENDIAN, BM_SETCHECK, BST_CHECKED, 0);
			last_rs_possible = -1;
			RefreshRamListSelectedCountControlStatus(hDlg);

			// force misalign checkbox to refresh
			signal_new_size();

			// force undo button to refresh
			int undoType = s_undoType;
			SetRamSearchUndoType(hDlg, -2);
			SetRamSearchUndoType(hDlg, undoType);

			// force possibility count to refresh
			last_rs_possible--;
			UpdatePossibilities(ResultCount, (int)s_activeMemoryRegions.size());
			
			rs_val_valid = Set_RS_Val();

			ListView_SetCallbackMask(GetDlgItem(hDlg,IDC_RAMLIST), LVIS_FOCUSED|LVIS_SELECTED);

			return true;
		}	break;

		case WM_NOTIFY:
		{
			LPNMHDR lP = (LPNMHDR) lParam;
			switch (lP->code)
			{
				case LVN_ITEMCHANGED: // selection changed event
				{
					NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)lP;
					if(pNMListView->uNewState & LVIS_FOCUSED)
					{
						// disable buttons that we don't have the right number of selected items for
						RefreshRamListSelectedCountControlStatus(hDlg);
					}
				}	break;

				case LVN_GETDISPINFO:
				{
					LV_DISPINFO *Item = (LV_DISPINFO *)lParam;
					Item->item.mask = LVIF_TEXT;
					Item->item.state = 0;
					Item->item.iImage = 0;
					const unsigned int iNum = Item->item.iItem;
					static char num[11];
					switch (Item->item.iSubItem)
					{
						case 0:
						{
							int addr = CALL_WITH_T_SIZE_TYPES(GetHardwareAddressFromItemIndex, rs_type_size,rs_t=='s',noMisalign, iNum);
							sprintf(num,"%08X",addr);
							Item->item.pszText = (LPWSTR)_16(num);
						}	return true;
						case 1:
						{
							int i = CALL_WITH_T_SIZE_TYPES(GetCurValueFromItemIndex, rs_type_size,rs_t=='s',noMisalign, iNum);
							const char* formatString = ((rs_t=='s') ? "%d" : (rs_t=='u') ? "%u" : (rs_type_size=='d' ? "%08X" : rs_type_size=='w' ? "%04X" : "%02X"));
							switch (rs_type_size)
							{
								case 'b':
								default: sprintf(num, formatString, rs_t=='s' ? (char)(i&0xff) : (unsigned char)(i&0xff)); break;
								case 'w': sprintf(num, formatString, rs_t=='s' ? (short)(i&0xffff) : (unsigned short)(i&0xffff)); break;
								case 'd': sprintf(num, formatString, rs_t=='s' ? (long)(i&0xffffffff) : (unsigned long)(i&0xffffffff)); break;
							}
							Item->item.pszText = (LPWSTR)_16(num);
						}	return true;
						case 2:
						{
							int i = CALL_WITH_T_SIZE_TYPES(GetPrevValueFromItemIndex, rs_type_size,rs_t=='s',noMisalign, iNum);
							const char* formatString = ((rs_t=='s') ? "%d" : (rs_t=='u') ? "%u" : (rs_type_size=='d' ? "%08X" : rs_type_size=='w' ? "%04X" : "%02X"));
							switch (rs_type_size)
							{
								case 'b':
								default: sprintf(num, formatString, rs_t=='s' ? (char)(i&0xff) : (unsigned char)(i&0xff)); break;
								case 'w': sprintf(num, formatString, rs_t=='s' ? (short)(i&0xffff) : (unsigned short)(i&0xffff)); break;
								case 'd': sprintf(num, formatString, rs_t=='s' ? (long)(i&0xffffffff) : (unsigned long)(i&0xffffffff)); break;
							}
							Item->item.pszText = (LPWSTR)_16(num);
						}	return true;
						case 3:
						{
							int i = CALL_WITH_T_SIZE_TYPES(GetNumChangesFromItemIndex, rs_type_size,rs_t=='s',noMisalign, iNum);
							sprintf(num,"%d",i);

							Item->item.pszText = (LPWSTR)_16(num);
						}	return true;
						//case 4:
						//	Item->item.pszText = rsaddrs[rsresults[iNum].Index].comment ? rsaddrs[rsresults[iNum].Index].comment : "";
						//	return true;
						default:
							return false;
					}
				}

				case NM_CUSTOMDRAW:
				{
					SetWindowLong(hDlg, DWL_MSGRESULT, CustomDraw(lParam));
					return TRUE;
				}	break;

				//case LVN_ODCACHEHINT: //Copied this bit from the MSDN virtual listbox code sample. Eventually it should probably do something.
				//{
				//	LPNMLVCACHEHINT   lpCacheHint = (LPNMLVCACHEHINT)lParam;
				//	return 0;
				//}
				//case LVN_ODFINDITEM: //Copied this bit from the MSDN virtual listbox code sample. Eventually it should probably do something.
				//{	
				//	LPNMLVFINDITEM lpFindItem = (LPNMLVFINDITEM)lParam;
				//	return 0;
				//}
			}
		}	break;

		case WM_COMMAND:
		{
			int rv = false;
			switch(LOWORD(wParam))
			{
				case IDC_SIGNED:
					rs_t='s';
					signal_new_size();
					{rv = true; break;}
				case IDC_UNSIGNED:
					rs_t='u';
					signal_new_size();
					{rv = true; break;}
				case IDC_HEX:
					rs_t='h';
					signal_new_size();
					{rv = true; break;}
				case IDC_1_BYTE:
					rs_type_size = 'b';
					signal_new_size();
					{rv = true; break;}
				case IDC_2_BYTES:
					rs_type_size = 'w';
					signal_new_size();
					{rv = true; break;}
				case IDC_4_BYTES:
					rs_type_size = 'd';
					signal_new_size();
					{rv = true; break;}
				case IDC_MISALIGN:
					noMisalign = !noMisalign;
					//CompactAddrs();
					signal_new_size();
					{rv = true; break;}
//				case IDC_ENDIAN:
//					littleEndian = !littleEndian;
//					signal_new_size();
					{rv = true; break;}				
				case IDC_LESSTHAN:
					EnableWindow(GetDlgItem(hDlg,IDC_EDIT_DIFFBY),false);
					EnableWindow(GetDlgItem(hDlg,IDC_EDIT_MODBY),false);
					rs_o = '<';
					{rv = true; break;}
				case IDC_MORETHAN:
					EnableWindow(GetDlgItem(hDlg,IDC_EDIT_DIFFBY),false);
					EnableWindow(GetDlgItem(hDlg,IDC_EDIT_MODBY),false);
					rs_o = '>';
					{rv = true; break;}
				case IDC_NOMORETHAN:
					EnableWindow(GetDlgItem(hDlg,IDC_EDIT_DIFFBY),false);
					EnableWindow(GetDlgItem(hDlg,IDC_EDIT_MODBY),false);
					rs_o = 'l';
					{rv = true; break;}
				case IDC_NOLESSTHAN:
					EnableWindow(GetDlgItem(hDlg,IDC_EDIT_DIFFBY),false);
					EnableWindow(GetDlgItem(hDlg,IDC_EDIT_MODBY),false);
					rs_o = 'm';
					{rv = true; break;}
				case IDC_EQUALTO:
					EnableWindow(GetDlgItem(hDlg,IDC_EDIT_DIFFBY),false);
					EnableWindow(GetDlgItem(hDlg,IDC_EDIT_MODBY),false);
					rs_o = '=';
					{rv = true; break;}
				case IDC_DIFFERENTFROM:
					EnableWindow(GetDlgItem(hDlg,IDC_EDIT_DIFFBY),false);
					EnableWindow(GetDlgItem(hDlg,IDC_EDIT_MODBY),false);
					rs_o = '!';
					{rv = true; break;}
				case IDC_DIFFERENTBY:
				{
					rs_o = 'd';
					EnableWindow(GetDlgItem(hDlg,IDC_EDIT_DIFFBY),true);
					EnableWindow(GetDlgItem(hDlg,IDC_EDIT_MODBY),false);
					if(!SelectingByKeyboard())
						SelectEditControl(IDC_EDIT_DIFFBY);
				}	{rv = true; break;}
				case IDC_MODULO:
				{
					rs_o = '%';
					EnableWindow(GetDlgItem(hDlg,IDC_EDIT_DIFFBY),false);
					EnableWindow(GetDlgItem(hDlg,IDC_EDIT_MODBY),true);
					if(!SelectingByKeyboard())
						SelectEditControl(IDC_EDIT_MODBY);
				}	{rv = true; break;}
				case IDC_PREVIOUSVALUE:
					rs_c='r';
					EnableWindow(GetDlgItem(hDlg,IDC_EDIT_COMPAREVALUE),false);
					EnableWindow(GetDlgItem(hDlg,IDC_EDIT_COMPAREADDRESS),false);
					EnableWindow(GetDlgItem(hDlg,IDC_EDIT_COMPARECHANGES),false);
					{rv = true; break;}
				case IDC_SPECIFICVALUE:
				{
					rs_c = 's';
					EnableWindow(GetDlgItem(hDlg,IDC_EDIT_COMPAREVALUE),true);
					EnableWindow(GetDlgItem(hDlg,IDC_EDIT_COMPAREADDRESS),false);
					EnableWindow(GetDlgItem(hDlg,IDC_EDIT_COMPARECHANGES),false);
					if(!SelectingByKeyboard())
						SelectEditControl(IDC_EDIT_COMPAREVALUE);
					{rv = true; break;}
				}
				case IDC_SPECIFICADDRESS:
				{
					rs_c = 'a';
					EnableWindow(GetDlgItem(hDlg,IDC_EDIT_COMPAREADDRESS),true);
					EnableWindow(GetDlgItem(hDlg,IDC_EDIT_COMPAREVALUE),false);
					EnableWindow(GetDlgItem(hDlg,IDC_EDIT_COMPARECHANGES),false);
					if(!SelectingByKeyboard())
						SelectEditControl(IDC_EDIT_COMPAREADDRESS);
				}	{rv = true; break;}
				case IDC_NUMBEROFCHANGES:
				{
					rs_c = 'n';
					EnableWindow(GetDlgItem(hDlg,IDC_EDIT_COMPARECHANGES),true);
					EnableWindow(GetDlgItem(hDlg,IDC_EDIT_COMPAREVALUE),false);
					EnableWindow(GetDlgItem(hDlg,IDC_EDIT_COMPAREADDRESS),false);
					if(!SelectingByKeyboard())
						SelectEditControl(IDC_EDIT_COMPARECHANGES);
				}	{rv = true; break;}
				case IDC_C_ADDCHEAT:
				{
//					watchIndex = ListView_GetSelectionMark(GetDlgItem(hDlg,IDC_RAMLIST));
//					Liste_GG[CheatCount].restore = Liste_GG[CheatCount].data = rsresults[watchIndex].cur;
//					Liste_GG[CheatCount].addr = rsresults[watchIndex].Address;
//					Liste_GG[CheatCount].size = rs_type_size;
//					Liste_GG[CheatCount].Type = rs_t;
//					Liste_GG[CheatCount].oper = '=';
//					Liste_GG[CheatCount].mode = 0;
//					DialogBoxParam(ghInstance, MAKEINTRESOURCE(IDD_EDITCHEAT), hDlg, (DLGPROC) EditCheatProc,(LPARAM) 0);
				}
				case IDC_C_RESET:
				{
					RamSearchSaveUndoStateIfNotTooBig(RamSearchHWnd);
					int prevNumItems = last_rs_possible;

					soft_reset_address_info();

					if(prevNumItems == last_rs_possible)
						SetRamSearchUndoType(RamSearchHWnd, 0); // nothing to undo

					ListView_SetItemState(GetDlgItem(hDlg,IDC_RAMLIST), -1, 0, LVIS_SELECTED); // deselect all
					//ListView_SetItemCount(GetDlgItem(hDlg,IDC_RAMLIST),ResultCount);
					ListView_SetSelectionMark(GetDlgItem(hDlg,IDC_RAMLIST), 0);
					RefreshRamListSelectedCountControlStatus(hDlg);
					{rv = true; break;}
				}
				case IDC_C_RESET_CHANGES:
					memset(buffers->s_numChanges, 0, sizeof(buffers->s_numChanges));
					ListView_Update(GetDlgItem(hDlg,IDC_RAMLIST), -1);
					//SetRamSearchUndoType(hDlg, 0);
					{rv = true; break;}
				case IDC_C_UNDO:
					if(s_undoType>0)
					{
//						Clear_Sound_Buffer();
						if(s_activeMemoryRegions.size() < tooManyRegionsForUndo)
						{
							MemoryList tempMemoryList = s_activeMemoryRegions;
							s_activeMemoryRegions = s_activeMemoryRegionsBackup;
							s_activeMemoryRegionsBackup = tempMemoryList;
							SetRamSearchUndoType(hDlg, 3 - s_undoType);
						}
						else
						{
							s_activeMemoryRegions = s_activeMemoryRegionsBackup;
							SetRamSearchUndoType(hDlg, -1);
						}
						CompactAddrs();
						ListView_SetItemState(GetDlgItem(hDlg,IDC_RAMLIST), -1, 0, LVIS_SELECTED); // deselect all
						ListView_SetSelectionMark(GetDlgItem(hDlg,IDC_RAMLIST), 0);
						RefreshRamListSelectedCountControlStatus(hDlg);
					}
					{rv = true; break;}
				case IDC_C_AUTOSEARCH:
					AutoSearch = SendDlgItemMessage(hDlg, IDC_C_AUTOSEARCH, BM_GETCHECK, 0, 0) != 0;
					AutoSearchAutoRetry = false;
					if (!AutoSearch) {rv = true; break;}
				case IDC_C_SEARCH:
				{
//					Clear_Sound_Buffer();

					if(!rs_val_valid && !(rs_val_valid = Set_RS_Val()))
						goto invalid_field;

					if(ResultCount)
					{
						RamSearchSaveUndoStateIfNotTooBig(hDlg);

						prune(rs_c,rs_o,rs_t=='s',rs_val,rs_param);

						RefreshRamListSelectedCountControlStatus(hDlg);
					}

					if(!ResultCount)
					{

						MessageBox(RamSearchHWnd,(LPCWSTR) _16("Resetting search."),(LPCWSTR) _16("Out of results."),MB_OK|MB_ICONINFORMATION);
						soft_reset_address_info();
					}

					{rv = true; break;}

invalid_field:
					MessageBox(RamSearchHWnd,(LPCWSTR) _16("Invalid or out-of-bound entered value."),(LPCWSTR) _16("Error"),MB_OK|MB_ICONSTOP);
					if(AutoSearch) // stop autosearch if it just started
					{
						SendDlgItemMessage(hDlg, IDC_C_AUTOSEARCH, BM_SETCHECK, BST_UNCHECKED, 0);
						SendMessage(hDlg, WM_COMMAND, IDC_C_AUTOSEARCH, 0);
					}
					{rv = true; break;}
				}
				case IDC_C_WATCH:
				{
					int watchItemIndex = ListView_GetSelectionMark(GetDlgItem(hDlg,IDC_RAMLIST));
					if(watchItemIndex >= 0)
					{
						AddressWatcher tempWatch;
						tempWatch.Address = CALL_WITH_T_SIZE_TYPES(GetHardwareAddressFromItemIndex, rs_type_size,rs_t=='s',noMisalign, watchItemIndex);
						tempWatch.Size = rs_type_size;
						tempWatch.Type = rs_t;
						tempWatch.WrongEndian = 0; //Replace when I get little endian working
						tempWatch.comment = NULL;
						
						bool inserted = InsertWatchHwnd(&tempWatch, hDlg);
						ListView_Update(GetDlgItem(hDlg,IDC_RAMLIST), -1);

						//bring up the ram watch window if it's not already showing so the user knows where the watch went
						if(inserted && !RamWatchHWnd)
							SendMessage(YabWin, WM_COMMAND, ID_RAM_WATCH, 0);
						SetForegroundWindow(RamSearchHWnd);
					}
					{rv = true; break;}
				}

				// eliminate all selected items
				case IDC_C_ELIMINATE:
				{
					RamSearchSaveUndoStateIfNotTooBig(hDlg);

					HWND ramListControl = GetDlgItem(hDlg,IDC_RAMLIST);
					int size = (rs_type_size=='b' || !noMisalign) ? 1 : 2;
					int selCount = ListView_GetSelectedCount(ramListControl);
					int watchIndex = -1;

					// time-saving trick #1:
					// condense the selected items into an array of address ranges
					std::vector<AddrRange> selHardwareAddrs;
					for(int i = 0, j = 1024; i < selCount; ++i, --j)
					{
						watchIndex = ListView_GetNextItem(ramListControl, watchIndex, LVNI_SELECTED);
						int addr = CALL_WITH_T_SIZE_TYPES(GetHardwareAddressFromItemIndex, rs_type_size,rs_t=='s',noMisalign, watchIndex);
						if(!selHardwareAddrs.empty() && addr == selHardwareAddrs.back().End())
							selHardwareAddrs.back().size += size;
						else
							selHardwareAddrs.push_back(AddrRange(addr,size));

						if(!j) UpdateRamSearchProgressBar(i * 50 / selCount), j = 1024;
					}

					// now deactivate the ranges

					// time-saving trick #2:
					// take advantage of the fact that the listbox items must be in the same order as the regions
					MemoryList::iterator iter = s_activeMemoryRegions.begin();
					int numHardwareAddrRanges = selHardwareAddrs.size();
					for(int i = 0, j = 16; i < numHardwareAddrRanges; ++i, --j)
					{
						int addr = selHardwareAddrs[i].addr;
						int size = selHardwareAddrs[i].size;
						bool affected = false;
						while(iter != s_activeMemoryRegions.end())
						{
							MemoryRegion& region = *iter;
							int affNow = DeactivateRegion(region, iter, addr, size);
							if(affNow)
								affected = true;
							else if(affected)
								break;
							if(affNow != 2)
								++iter;
						}

						if(!j) UpdateRamSearchProgressBar(50 + (i * 50 / selCount)), j = 16;
					}
					UpdateRamSearchTitleBar();

					// careful -- if the above two time-saving tricks aren't working,
					// the runtime can absolutely explode (seconds -> hours) when there are lots of regions

					ListView_SetItemState(ramListControl, -1, 0, LVIS_SELECTED); // deselect all
					signal_new_size();
					{rv = true; break;}
				}
				//case IDOK:
				case IDCANCEL:
/*					if (Full_Screen)
					{
						while (ShowCursor(true) < 0);
						while (ShowCursor(false) >= 0);
					}*/
//					DialogsOpen--;
					RamSearchHWnd = NULL;
					EndDialog(hDlg, true);
					{rv = true; break;}
			}

			// check refresh for comparison preview color update
			// also, update rs_val if needed
			bool needRefresh = false;
			switch(LOWORD(wParam))
			{
				case IDC_LESSTHAN:
				case IDC_MORETHAN:
				case IDC_NOMORETHAN:
				case IDC_NOLESSTHAN:
				case IDC_EQUALTO:
				case IDC_DIFFERENTFROM:
				case IDC_DIFFERENTBY:
				case IDC_MODULO:
				case IDC_PREVIOUSVALUE:
				case IDC_SPECIFICVALUE:
				case IDC_SPECIFICADDRESS:
				case IDC_NUMBEROFCHANGES:
				case IDC_SIGNED:
				case IDC_UNSIGNED:
				case IDC_HEX:
					rs_val_valid = Set_RS_Val();
					needRefresh = true;
					break;
				case IDC_EDIT_COMPAREVALUE:
				case IDC_EDIT_COMPAREADDRESS:
				case IDC_EDIT_COMPARECHANGES:
				case IDC_EDIT_DIFFBY:
				case IDC_EDIT_MODBY:
					if(HIWORD(wParam) == EN_CHANGE)
					{
						rs_val_valid = Set_RS_Val();
						needRefresh = true;
					}
					break;
			}
			if(needRefresh)
				ListView_Update(GetDlgItem(hDlg,IDC_RAMLIST), -1);


			return rv;
		}	break;

		case WM_CLOSE:
/*			if (Full_Screen)
			{
				while (ShowCursor(true) < 0);
				while (ShowCursor(false) >= 0);
			}
			DialogsOpen--;*/
			RamSearchHWnd = NULL;
			EndDialog(hDlg, true);
			return true;
	}

	return false;
}

void UpdateRamSearchTitleBar(int percent)
{
#define HEADER_STR " RAM Search - "
#define PROGRESS_STR " %d%% ... "
#define STATUS_STR "%d Possibilit%s (%d Region%s)"

	int poss = last_rs_possible;
	int regions = last_rs_regions;
	if(poss <= 0)
		strcpy((char*)Str_Tmp, "RAM Search");
	else if(percent <= 0)
		sprintf((char*)Str_Tmp, HEADER_STR STATUS_STR, poss, poss==1?"y":"ies", regions, regions==1?"":"s");
	else
		sprintf((char*)Str_Tmp, PROGRESS_STR STATUS_STR, percent, poss, poss==1?"y":"ies", regions, regions==1?"":"s");
	SetWindowText(RamSearchHWnd, (LPCWSTR)_16((const char*)Str_Tmp));
}

void UpdatePossibilities(int rs_possible, int regions)
{
	if(rs_possible != last_rs_possible)
	{
		last_rs_possible = rs_possible;
		last_rs_regions = regions;
		UpdateRamSearchTitleBar();
	}
}

void SetRamSearchUndoType(HWND hDlg, int type)
{
	if(s_undoType != type)
	{
		if((s_undoType!=2 && s_undoType!=-1)!=(type!=2 && type!=-1))
			SendDlgItemMessage(hDlg,IDC_C_UNDO,WM_SETTEXT,0,(LPARAM)((type == 2 || type == -1) ? "Redo" : "Undo"));
		if((s_undoType>0)!=(type>0))
			EnableWindow(GetDlgItem(hDlg,IDC_C_UNDO),type>0);
		s_undoType = type;
	}
}

void RamSearchSaveUndoStateIfNotTooBig(HWND hDlg)
{
	if(s_activeMemoryRegions.size() < tooManyRegionsForUndo)
	{
		s_activeMemoryRegionsBackup = s_activeMemoryRegions;
		SetRamSearchUndoType(hDlg, 1);
	}
	else
	{
		SetRamSearchUndoType(hDlg, 0);
	}
}
