//	Altirra - Atari 800/800XL/5200 emulator
//	Copyright (C) 2008-2012 Avery Lee
//
//	This program 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.
//
//	This program 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 this program; if not, write to the Free Software
//	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

#include <stdafx.h>
#include "cartridge.h"
#include "ksyms.h"

namespace {
	enum SystemType {
		kType800,
		kType5200
	};

	enum SizeType {
		kSize2K		= 1,
		kSize4K		= 2,
		kSize8K		= 4,
		kSize16K	= 8,
		kSize32K	= 16,
		kSize40K	= 32,
		kSize64K	= 64,
		kSize128K	= 128,
		kSize256K	= 256,
		kSize512K	= 512,
		kSize1M		= 1024,
		kSize2M		= 2048,
		kSize4M		= 0x1000,
		kSize32M	= 0x2000,
		kSize64M	= 0x4000,
		kSize128M	= 0x8000,
	};

	enum WritableStoreType {
		kWrsNone,
		kWrs256B,
		kWrs8K
	};

	enum BankingType {
		kBankNone,
		kBankData,
		kBankDataSw,
		kBankAddr,
		kBankAddrSw,
		kBankAddr7x,
		kBankAddrDx,
		kBankAddrEx,
		kBankAddrEFx,
		kBankAny,
		kBankBF,
		kBankOther
	};

	enum InitRange {
		kInit2K,
		kInit4K,
		kInit8K,
		kInit8KR,
		kInit16K,
		kInit32K
	};

	enum HeaderType {
		kHeaderFirst4K,
		kHeaderFirst8K,
		kHeaderFirst8K_PreferAll8K,
		kHeaderFirst16K_PreferAll16K,
		kHeaderFirst32K,
		kHeaderLast16B,
		kHeaderLast8K_PreferAll8K
	};
}

static const struct ATCartDetectInfo {
	ATCartridgeMode		mMode;
	SystemType			mSystemType;
	uint32				mSizeTypes;
	WritableStoreType	mWritableStoreType;
	BankingType			mBankingType;
	InitRange			mInitRange;
	HeaderType			mHeaderType;
} kATCartDetectInfo[] = {
{	kATCartridgeMode_2K,						kType800,	kSize2K,	kWrsNone,	kBankNone,		kInit2K,	kHeaderLast16B,					},
{	kATCartridgeMode_4K,						kType800,	kSize4K,	kWrsNone,	kBankNone,		kInit4K,	kHeaderLast16B,					},
{	kATCartridgeMode_8K,						kType800,	kSize2K |
															kSize4K |
															kSize8K,	kWrsNone,	kBankNone,		kInit8K,	kHeaderLast16B,					},
{	kATCartridgeMode_16K,						kType800,	kSize16K,	kWrsNone,	kBankNone,		kInit16K,	kHeaderLast16B,					},
{	kATCartridgeMode_XEGS_32K,					kType800,	kSize32K,	kWrsNone,	kBankData,		kInit8K,	kHeaderLast16B,					},
{	kATCartridgeMode_XEGS_64K,					kType800,	kSize64K,	kWrsNone,	kBankData,		kInit8K,	kHeaderLast16B,					},
{	kATCartridgeMode_XEGS_128K,					kType800,	kSize128K,	kWrsNone,	kBankData,		kInit8K,	kHeaderLast16B,					},
{	kATCartridgeMode_XEGS_256K,					kType800,	kSize256K,	kWrsNone,	kBankData,		kInit8K,	kHeaderLast16B,					},
{	kATCartridgeMode_XEGS_512K,					kType800,	kSize512K,	kWrsNone,	kBankData,		kInit8K,	kHeaderLast16B,					},
{	kATCartridgeMode_XEGS_1M,					kType800,	kSize1M,	kWrsNone,	kBankData,		kInit8K,	kHeaderLast16B,					},
{	kATCartridgeMode_Switchable_XEGS_32K,		kType800,	kSize32K,	kWrsNone,	kBankDataSw,	kInit16K,	kHeaderLast16B,					},
{	kATCartridgeMode_Switchable_XEGS_64K,		kType800,	kSize64K,	kWrsNone,	kBankDataSw,	kInit16K,	kHeaderLast16B,					},
{	kATCartridgeMode_Switchable_XEGS_128K,		kType800,	kSize128K,	kWrsNone,	kBankDataSw,	kInit16K,	kHeaderLast16B,					},
{	kATCartridgeMode_Switchable_XEGS_256K,		kType800,	kSize256K,	kWrsNone,	kBankDataSw,	kInit16K,	kHeaderLast16B,					},
{	kATCartridgeMode_Switchable_XEGS_512K,		kType800,	kSize512K,	kWrsNone,	kBankDataSw,	kInit16K,	kHeaderLast16B,					},
{	kATCartridgeMode_Switchable_XEGS_1M,		kType800,	kSize1M,	kWrsNone,	kBankDataSw,	kInit16K,	kHeaderLast16B,					},
{	kATCartridgeMode_MaxFlash_128K,				kType800,	kSize128K,	kWrsNone,	kBankAddrSw,	kInit8K,	kHeaderFirst8K_PreferAll8K,		},
{	kATCartridgeMode_MaxFlash_128K_MyIDE,		kType800,	kSize128K,	kWrsNone,	kBankAddrSw,	kInit8K,	kHeaderFirst8K_PreferAll8K,		},
{	kATCartridgeMode_MaxFlash_1024K,			kType800,	kSize1M,	kWrsNone,	kBankAddrSw,	kInit8K,	kHeaderLast8K_PreferAll8K,		},
{	kATCartridgeMode_MaxFlash_1024K_Bank0,		kType800,	kSize256K |
															kSize512K |
															kSize1M,	kWrsNone,	kBankAddrSw,	kInit8K,	kHeaderFirst8K_PreferAll8K,		},
{	kATCartridgeMode_MegaCart_16K,				kType800,	kSize16K,	kWrsNone,	kBankDataSw,	kInit16K,	kHeaderFirst16K_PreferAll16K,	},
{	kATCartridgeMode_MegaCart_32K,				kType800,	kSize32K,	kWrsNone,	kBankDataSw,	kInit16K,	kHeaderFirst16K_PreferAll16K,	},
{	kATCartridgeMode_MegaCart_64K,				kType800,	kSize64K,	kWrsNone,	kBankDataSw,	kInit16K,	kHeaderFirst16K_PreferAll16K,	},
{	kATCartridgeMode_MegaCart_128K,				kType800,	kSize128K,	kWrsNone,	kBankDataSw,	kInit16K,	kHeaderFirst16K_PreferAll16K,	},
{	kATCartridgeMode_MegaCart_256K,				kType800,	kSize256K,	kWrsNone,	kBankDataSw,	kInit16K,	kHeaderFirst16K_PreferAll16K,	},
{	kATCartridgeMode_MegaCart_512K,				kType800,	kSize512K,	kWrsNone,	kBankDataSw,	kInit16K,	kHeaderFirst16K_PreferAll16K,	},
{	kATCartridgeMode_MegaCart_1M,				kType800,	kSize1M,	kWrsNone,	kBankDataSw,	kInit16K,	kHeaderFirst16K_PreferAll16K,	},
{	kATCartridgeMode_BountyBob800,				kType800,	kSize40K,	kWrsNone,	kBankOther,		kInit16K,	kHeaderLast16B,					},
{	kATCartridgeMode_OSS_034M,					kType800,	kSize16K,	kWrsNone,	kBankAddr,		kInit8K,	kHeaderLast16B,					},
{	kATCartridgeMode_OSS_M091,					kType800,	kSize16K,	kWrsNone,	kBankAddr,		kInit8K,	kHeaderFirst4K,					},
{	kATCartridgeMode_OSS_043M,					kType800,	kSize16K,	kWrsNone,	kBankAddr,		kInit8K,	kHeaderLast16B,					},
{	kATCartridgeMode_OSS_8K,					kType800,	kSize8K,	kWrsNone,	kBankAddr,		kInit8K,	kHeaderFirst4K,					},
{	kATCartridgeMode_Corina_1M_EEPROM,			kType800,	kSize1M,	kWrs8K,		kBankData,		kInit8K,	kHeaderFirst8K,					},
{	kATCartridgeMode_Corina_512K_SRAM_EEPROM,	kType800,	kSize512K,	kWrs8K,		kBankData,		kInit8K,	kHeaderFirst8K,					},
{	kATCartridgeMode_BountyBob5200,				kType5200,	kSize40K,	kWrsNone,	kBankOther,		kInit32K,	kHeaderLast16B,					},
{	kATCartridgeMode_Williams_32K,				kType800,	kSize32K,	kWrsNone,	kBankAddr,		kInit8K,	kHeaderFirst8K,					},
{	kATCartridgeMode_Williams_64K,				kType800,	kSize64K,	kWrsNone,	kBankAddr,		kInit8K,	kHeaderFirst8K,					},
{	kATCartridgeMode_Diamond_64K,				kType800,	kSize64K,	kWrsNone,	kBankAddrDx,	kInit8K,	kHeaderFirst8K,					},
{	kATCartridgeMode_Express_64K,				kType800,	kSize64K,	kWrsNone,	kBankAddr7x,	kInit8K,	kHeaderFirst8K,					},
{	kATCartridgeMode_SpartaDosX_64K,			kType800,	kSize64K,	kWrsNone,	kBankAddrEx,	kInit8K,	kHeaderFirst8K,					},
{	kATCartridgeMode_SpartaDosX_128K,			kType800,	kSize128K,	kWrsNone,	kBankAddrEFx,	kInit8K,	kHeaderFirst8K,					},
{	kATCartridgeMode_Atrax_SDX_128K,			kType800,	kSize128K,	kWrsNone,	kBankAddrEFx,	kInit8K,	kHeaderFirst8K,					},
{	kATCartridgeMode_Atrax_SDX_64K,				kType800,	kSize64K,	kWrsNone,	kBankAddrEx,	kInit8K,	kHeaderFirst8K,					},
{	kATCartridgeMode_TelelinkII,				kType800,	kSize8K,	kWrs256B,	kBankNone,		kInit8K,	kHeaderFirst8K,					},
{	kATCartridgeMode_RightSlot_4K,				kType800,	kSize4K,	kWrsNone,	kBankNone,		kInit8KR,	kHeaderLast16B,					},
{	kATCartridgeMode_RightSlot_8K,				kType800,	kSize8K,	kWrsNone,	kBankNone,		kInit8KR,	kHeaderLast16B,					},
{	kATCartridgeMode_DB_32K,					kType800,	kSize32K,	kWrsNone,	kBankAddr,		kInit8K,	kHeaderLast16B,					},
{	kATCartridgeMode_Atrax_128K,				kType800,	kSize128K,	kWrsNone,	kBankData,		kInit8K,	kHeaderFirst8K,					},
{	kATCartridgeMode_Phoenix_8K,				kType800,	kSize8K,	kWrsNone,	kBankAny,		kInit8K,	kHeaderLast16B,					},
{	kATCartridgeMode_Blizzard_16K,				kType800,	kSize16K,	kWrsNone,	kBankAny,		kInit16K,	kHeaderLast16B,					},
{	kATCartridgeMode_Blizzard_4K,				kType800,	kSize4K,	kWrsNone,	kBankAny,		kInit8K,	kHeaderLast16B,					},
{	kATCartridgeMode_SIC,						kType800,	kSize128K |
															kSize256K |
															kSize512K,	kWrsNone,	kBankAddr,		kInit8K,	kHeaderFirst16K_PreferAll16K,	},
{	kATCartridgeMode_AST_32K,					kType800,	kSize32K,	kWrsNone,	kBankAny,		kInit8K,	kHeaderFirst8K,					},
{	kATCartridgeMode_Turbosoft_64K,				kType800,	kSize64K,	kWrsNone,	kBankAddr,		kInit8K,	kHeaderFirst8K,					},
{	kATCartridgeMode_Turbosoft_128K,			kType800,	kSize128K,	kWrsNone,	kBankAddr,		kInit8K,	kHeaderFirst8K,					},
{	kATCartridgeMode_MegaCart_1M_2,				kType800,	kSize256K |
															kSize512K |
															kSize1M,	kWrsNone,	kBankDataSw,	kInit8K,	kHeaderFirst8K,					},
{	kATCartridgeMode_MegaCart_512K_3,			kType800,	kSize512K,	kWrsNone,	kBankDataSw,	kInit8K,	kHeaderFirst8K,					},
{	kATCartridgeMode_MegaCart_2M_3,				kType800,	kSize2M,	kWrsNone,	kBankDataSw,	kInit8K,	kHeaderLast16B,					},
{	kATCartridgeMode_MegaCart_4M_3,				kType800,	kSize4M,	kWrsNone,	kBankDataSw,	kInit8K,	kHeaderLast16B,					},
{	kATCartridgeMode_MicroCalc,					kType800,	kSize32K,	kWrsNone,	kBankAny,		kInit8K,	kHeaderFirst8K,					},
{	kATCartridgeMode_MegaMax_2M,				kType800,	kSize2M,	kWrsNone,	kBankAny,		kInit16K,	kHeaderFirst16K_PreferAll16K,	},
{	kATCartridgeMode_TheCart_32M,				kType800,	kSize32M,	kWrsNone,	kBankAny,		kInit8K,	kHeaderFirst8K,					},
{	kATCartridgeMode_TheCart_64M,				kType800,	kSize64M,	kWrsNone,	kBankAny,		kInit8K,	kHeaderFirst8K,					},
{	kATCartridgeMode_TheCart_128M,				kType800,	kSize128M,	kWrsNone,	kBankAny,		kInit8K,	kHeaderFirst8K,					},

// 5200 carts
//
// Quite a few carts make use of address mirroring, so the entire cart address range
// is allowed for init.

{	kATCartridgeMode_5200_32K,					kType5200,	kSize32K,	kWrsNone,	kBankNone,		kInit32K,	kHeaderLast16B,					},
{	kATCartridgeMode_5200_16K_TwoChip,			kType5200,	kSize16K,	kWrsNone,	kBankNone,		kInit32K,	kHeaderLast16B,					},
{	kATCartridgeMode_5200_16K_OneChip,			kType5200,	kSize16K,	kWrsNone,	kBankNone,		kInit32K,	kHeaderLast16B,					},
{	kATCartridgeMode_5200_8K,					kType5200,	kSize8K,	kWrsNone,	kBankNone,		kInit32K,	kHeaderLast16B,					},
{	kATCartridgeMode_5200_4K,					kType5200,	kSize8K,	kWrsNone,	kBankNone,		kInit32K,	kHeaderLast16B,					},
{	kATCartridgeMode_5200_64K_32KBanks,			kType5200,	kSize64K,	kWrsNone,	kBankBF,		kInit32K,	kHeaderFirst32K,				},
};

uint32 ATCartridgeAutodetectMode(const void *data, uint32 size, vdfastvector<int>& cartModes) {
	WritableStoreType wrsType = kWrsNone;

	if (size >= 262144 && (size & 8192)) {
		size -= 8192;
		wrsType = kWrs8K;
	} else if (size >= 8192 && (size & 256)) {
		size -= 256;
		wrsType = kWrs256B;
	}

	// map cartridge size
	uint32 sizeType;

		 if (size == 4194304) sizeType = kSize4M;
	else if (size == 2097152) sizeType = kSize2M;
	else if (size == 1048576) sizeType = kSize1M;
	else if (size ==  524288) sizeType = kSize512K;
	else if (size ==  262144) sizeType = kSize256K;
	else if (size ==  131072) sizeType = kSize128K;
	else if (size ==   65536) sizeType = kSize64K;
	else if (size ==   40960) sizeType = kSize40K;
	else if (size ==   32768) sizeType = kSize32K;
	else if (size ==   16384) sizeType = kSize16K;
	else if (size ==    8192) sizeType = kSize8K;
	else if (size ==    4096) sizeType = kSize4K;
	else if (size ==    2048) sizeType = kSize2K;
	else return 0;

	// Attempt to detect 5200 vs. 800 by POKEY and GTIA accesses.
	//
	// 5200: GTIA @ Cxxx, POKEY @ E800-EBFF
	// 800: GTIA @ D0xx, POKEY @ D2xx
	//
	// We just check STA abs instructions for simplicity, as these are the most
	// common.

	const uint8 *const data8 = (const uint8 *)data;

	uint32 count800 = 0;
	uint32 count5200 = 0;

	if (data) {
		for(uint32 i=2; i<size; ++i) {
			uint8 c = data8[i-2];

			if (c == 0x8D && data8[i-1] < 0x20) {
				uint8 hiaddr = data8[i];

				if ((hiaddr & 0xF0) == 0xC0 || (hiaddr & 0xFC) == 0xE8)
					++count5200;
				else if (hiaddr == 0xD0 || hiaddr == 0xD2)
					++count800;
			} else if (c == 0x20) {
				uint32 target = data8[i-1] + 256*data8[i];

				switch(target) {
					case ATKernelSymbols::CIOV:
					case ATKernelSymbols::SIOV:
					case ATKernelSymbols::SETVBV:
					case ATKernelSymbols::XITVBV:
					case ATKernelSymbols::AFP:
					case ATKernelSymbols::FASC:
					case ATKernelSymbols::FPI:
						++count800;
						break;
				}
			}
		}
	}

	// Consider it a hit if we have at least some accesses and at least a 3:1
	// ratio over the other mode.
	const uint32 countAll = count800 + count5200;
	const bool reject800 = (count800 * 3 < count5200) && (countAll >= 10);
	const bool reject5200 = (count5200 * 3 < count800) && (countAll >= 10);

	// Loop over all of the modes and filter.
	for(size_t i=0; i<sizeof(kATCartDetectInfo)/sizeof(kATCartDetectInfo[0]); ++i) {
		const ATCartDetectInfo& detInfo = kATCartDetectInfo[i];

		// check size type
		if (!(detInfo.mSizeTypes & sizeType))
			continue;

		// check system type
		switch(detInfo.mSystemType) {
			case kType800:
				if (reject800)
					goto not_recommended;
				break;

			case kType5200:
				if (reject5200)
					goto not_recommended;
				break;
		}

		// check writable store type (optional, but must be absent if not expected)
		if (wrsType && wrsType != detInfo.mWritableStoreType)
			continue;

		// special casing for troublesome and rare types
		switch(detInfo.mMode) {
			case kATCartridgeMode_TelelinkII:
			case kATCartridgeMode_Atrax_SDX_64K:
			case kATCartridgeMode_Atrax_SDX_128K:
				goto not_recommended;
		}

		bool passAllHeaders = true;
		sint32 score = 0;

		if (data) {
			sint32 headerOffset = -1;
			sint32 headerStep = 0;

			switch(detInfo.mHeaderType) {
				case kHeaderFirst4K:
					headerOffset = 0xFF0;
					break;

				case kHeaderFirst8K:
					headerOffset = 0x1FF0;
					break;

				case kHeaderFirst8K_PreferAll8K:
					headerOffset = 0x1FF0;
					headerStep = 0x2000;
					break;

				case kHeaderFirst16K_PreferAll16K:
					headerOffset = 0x3FF0;
					headerStep = 0x4000;
					break;

				case kHeaderFirst32K:
					headerOffset = 0x7FF0;
					break;

				case kHeaderLast16B:
					headerOffset = size - 0x10;
					break;

				case kHeaderLast8K_PreferAll8K:
					headerOffset = size - 0x10;
					headerStep = -0x2000;
					break;
			}

			bool firstHeader = true;

			while(headerOffset >= 0 && headerOffset + 16 <= (sint32)size) {
				const uint8 *header = data8 + headerOffset;
				const uint8 hibyte = header[15];

				bool ok = true;

				switch(detInfo.mInitRange) {
					case kInit2K:
						if ((hibyte & 0xF8) != 0xB8)
							ok = false;
						break;

					case kInit4K:
						if ((hibyte & 0xF0) != 0xB0)
							ok = false;
						break;

					case kInit8K:
						if ((hibyte & 0xE0) != 0xA0)
							ok = false;
						break;

					case kInit8KR:
						if ((hibyte & 0xE0) != 0x80)
							ok = false;
						break;

					case kInit16K:
						if ((hibyte & 0xC0) != 0x80)
							ok = false;
						break;

					case kInit32K:
						if ((uint8)(hibyte - 0x40) >= 0x80)
							ok = false;
						break;
				}

				if (!ok) {
					passAllHeaders = false;

					if (firstHeader)
						goto not_recommended;
				}

				firstHeader = false;

				if (!headerStep)
					break;

				headerOffset += headerStep;
			}

			score += 10;

			if (headerStep && passAllHeaders) {
				score += 10;

				if (abs(headerStep) > 0x2000)
					score += 10;
			}

			// Now scan looking for bank instructions -- we are looking for:
			//		[AC,AD,AE] xx D5	(D5 read)
			//		[8C,8D,8E] xx D5	(D5 write)
			//		A9 yy 8D xx D5		(load A, D5 write)
			//		A2 yy 8E xx D5		(load X, D5 write)
			//		A0 yy 8C xx D5		(load Y, D5 write)
			//
			uint32 goodBank = 0;
			uint32 badBank = 0;

			for(uint32 i=4; i<size; ++i) {
				uint8 c = data8[i-2];
				int val = -1;

				switch(c) {
					case 0xAC:	// LDY
						break;

					case 0xAD:	// LDA
						break;

					case 0xAE:	// LDX
						break;

					case 0x8C:	// STY
						if (data8[i-4] == 0xA0)
							val = data8[i-3];
						break;

					case 0x8D:	// STA
						if (data8[i-4] == 0xA9)
							val = data8[i-3];
						break;

					case 0x8E:	// STX
						if (data8[i-4] == 0xA2)
							val = data8[i-3];
						break;

					default:
						continue;
				}

				const uint8 hiaddr = data8[i];
				const bool write = !(c & 0x20);

				switch(detInfo.mBankingType) {
					case kBankNone:
						if (hiaddr == 0xD5)
							++badBank;
						break;

					case kBankData:
						if (hiaddr == 0xD5) {
							if (write && (val >= 0 && val < 0x80))
								++goodBank;
							else
								++badBank;
						}
						break;

					case kBankDataSw:
						if (hiaddr == 0xD5) {
							if (val >= 0x80)
								++goodBank;
							else if (!write)
								++badBank;
						}
						break;

					case kBankAddr:
						if (hiaddr == 0xD5) {
							if (data8[i-1] < 0x80)
								++goodBank;
							else
								++badBank;
						}
						break;

					case kBankAddrSw:
						if (hiaddr == 0xD5 && data8[i-1] >= 0x80)
							++goodBank;
						break;

					case kBankAddr7x:
						if (hiaddr == 0xD5) {
							if ((data8[i-1] & 0xF0) == 0x70)
								++goodBank;
							else
								++badBank;
						}
						break;

					case kBankAddrDx:
						if (hiaddr == 0xD5) {
							if ((data8[i-1] & 0xF0) == 0xD0)
								++goodBank;
							else
								++badBank;
						}
						break;

					case kBankAddrEx:
						if (hiaddr == 0xD5) {
							if ((data8[i-1] & 0xF0) == 0xE0)
								++goodBank;
							else
								++badBank;
						}
						break;

					case kBankAddrEFx:
						if (hiaddr == 0xD5) {
							if ((data8[i-1] & 0xE0) == 0xE0)
								++goodBank;
							else
								++badBank;
						}
						break;

					case kBankAny:
						if (hiaddr == 0xD5)
							++goodBank;
						break;

					case kBankBF:
						if (hiaddr == 0xBF)
							++goodBank;
						else if (hiaddr == 0xD5)
							++badBank;
						break;
				}
			}

			// if we expected banking and didn't find much, reject
			if (detInfo.mBankingType && goodBank < 2)
				goto not_recommended;

			// if we had significantly more bad banks, reject
			if (badBank >= 32 && badBank * 3 >= goodBank)
				goto not_recommended;

			// apply a bonus based on banking type
			switch(detInfo.mBankingType) {
				case kBankNone:
					break;

				case kBankData:
					score += 10;
					break;

				case kBankDataSw:
					score += 20;
					break;

				case kBankAddr:
					score += 5;
					break;

				case kBankAddrSw:
					score += 15;
					break;

				case kBankAddr7x:
					score += 30;
					break;

				case kBankAddrDx:
					score += 30;
					break;

				case kBankAddrEx:
					score += 40;
					break;

				case kBankAddrEFx:
					score += 30;
					break;

				case kBankAny:
					break;

				case kBankBF:
					score += 20;
					break;
			}
		}

		cartModes.push_back(detInfo.mMode - (score << 16));
		continue;

not_recommended:
		cartModes.push_back(detInfo.mMode);
	}

	if (cartModes.empty())
		return 0;

	// reverse sort
	std::sort(cartModes.begin(), cartModes.end());

	// count recommended types
	int hiscore = cartModes.front() >> 16;

	uint32 recommendedCount = 0;

	for(vdfastvector<int>::iterator it(cartModes.begin()), itEnd(cartModes.end());
		it != itEnd;
		++it)
	{
		if ((*it >> 16) == hiscore)
			++recommendedCount;

		*it &= 0xffff;
	}

	return recommendedCount;
}
