using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;

/**
 * reads GENESIS, SMS, SNES HEADER INFORMATION 
**/
namespace TwoMbit
{
    public class Header
    {
        public enum Type {
            SNES_LOROM = 0x20,		//1(0)0000
            SNES_HIROM = 0x21,		//1(0)0001
            SNES_EXLOROM = 0x22,	//110010 = 0x32 StarOcean
            SNES_EXHIROM = 0x25,	//110101 = 0x35 Tales of Phantasia
                                    //111010 = 0x3A Far East Of Eden Zero
            SNES_SPC7110ROM = 0,
            SNES_SUPERFXROM = 1,
            SNES_SA1ROM = 2,

            SNES_BSXROM = 3,
            SNES_BSXROM_BIOS = 4,
            SNES_BSCLOROM = 5,
            SNES_BSCHIROM = 6,

            SNES_STROM_BIOS = 7,
            SNES_STROM = 8,

            SNES_SUPER_GAMEBOY = 9,

            GENESIS_ROM = 10,

            SMS_ROM_GENERIC = 11,
            SMS_ROM_CODEMASTERS = 12,
            SMS_ROM_KOREAN = 13,
            SMS_BIOS = 14,
            SMS_ROM_KOREAN_8K = 15,
            SMS_ROM_NO_BANK_SWITCHING = 16
        };

        public enum Header_Snes { //32 byte rom header + reset vector
            CART_NAME = 0x00,
            MAKEUP = 0x15,
            ROM_TYPE = 0x16,
            ROM_SIZE = 0x17,
            SRAM_SIZE = 0x18,
            REGION = 0x19,
            LICENSE = 0x1a,
            VERSION = 0x1b,
            ICHECKSUM = 0x1c,
            CHECKSUM = 0x1e,
            RESETV = 0x3c,
        };

        public enum Header_Genesis { //240 byte rom beader
            CONSOLE_NAME = 0x00,
            COPYRIGHT_NOTICE = 0x10,
            CART_NAME_ORIGIN = 0x20,
            CART_NAME_OVERSEAS = 0x50,
            PRODUCT_TYPE = 0x80,
            PRODUCT_CODE = 0x82,
            CHECKSUM = 0x8E,
            IO_SUPPORT = 0x90,
            ROM_CAPACITY = 0xA0,
            BACKUP_RAM = 0xA8,
            BACKUP_RAM_ADDRESS_RANGE = 0xB4,
            MODEM_DATA = 0xBC,
            UNKNOWN = 0xC8,
            COUNTRY = 0xF0
        };

        public static Dictionary<string, Region> bios = new Dictionary<string, Region>();
        public static Dictionary<string, Type> customRoms = new Dictionary<string, Type>();

        public enum Index { SNES_LOROM = 0x007fc0, SNES_HIROM = 0x00ffc0, SNES_EXROM = 0x40ffc0
                           ,GENESIS_ROM = 0x100, SMS_ROM_GENERIC = 0x7ff0 };

        
        public static void setHashes() {
            if (bios.Count != 0) return;
            bios["24A519C53F67B00640D0048EF7089105"] = Region.JAPAN; // SMS Bios v2.1 (jap)
            bios["E49E63328C78FE3D24AE32BF903583E0"] = Region.JAPAN; // SMS Bios, Alex Kid built in (korean)

            bios["3153447D155FB8BFC66A4D1C41E399B9"] = Region.USA; // SMS prototype 404 Bios (us)
            bios["840481177270D5642A14CA71EE72844C"] = Region.USA; // SMS Bios 1.3 (ue, eu)
            bios["02CBB2E348945C9AC41E37502A58CA76"] = Region.USA; // SMS Bios v2.4, Hang On and Safari Hunt built in (us, eu)
            bios["5CD8F62CD8786AF0226E6D2248279338"] = Region.USA; // SMS Bios v3.4, Hang On built in (us, eu)
            bios["08B81AA6BE18B92DAEF1B875DEECF824"] = Region.USA; // SMS Bios v4.4, Missle Defense 3D built in (us, eu)
            bios["E8B26871629B938887757A64798DF6DC"] = Region.USA; // SMS Bios, Alex Kid built in (us, eu)
            bios["4187D96BEAF36385E681A3CF3BD1663D"] = Region.EUROPE; //SMS Bios, Sonic built in (eu)
        }
        
        public struct Chips //special coprocessor in snes game carts
        {
            public bool sfx;
            public bool spc7110;
            public bool spc7110rtc;
            public bool dsp1;
            public bool dsp2;
            public bool dsp3;
            public bool dsp4;
            public bool st010;
            public bool st011;
            public bool st018;
            public bool obc1;
            public bool srtc;
            public bool sdd1;
            public bool sa1;
            public bool c4;

            public enum Dsp1Type {
                LOROM1MB,
                LOROM2MB,
                HIROM
            };
            public Dsp1Type dsp1Type;

            public void reset()
            {
                dsp1Type = Dsp1Type.LOROM1MB;
                sfx = spc7110 = spc7110rtc = dsp1 = dsp2 = dsp3 = dsp4 = false;
                st010 = st011 = st018 = obc1 = srtc = sdd1 = sa1 = c4 = false;
            }
        };

        public enum Region { USA = 0, JAPAN = 1, EUROPE = 2 };

        //output
        Type type;
        Index index;
        Chips chips;
        uint sramSize;
        Region region;
        string cartName;
        
        //input
        uint size;
        byte[] data;

        public Header(uint size, byte[] data)
        {
            this.size = size;
            this.data = data;
            sramSize = 0;
            region = Region.USA;
            cartName = "";
            chips.reset();
            setHashes();
        }

        public Type getType()
        {
            return type;
        }

        public Chips getChipState()
        {
            return chips;
        }

        public uint getSramSize()
        {
            return sramSize;
        }

        public Region getRegion()
        {
            return region;
        }

        public void setRegion(Region region)
        {
            this.region = region;
        }

        public string getCartName()
        {
            return cartName;
        }

        public void setCartName(string name)
        {
            this.cartName = name;
        }

        public void detect(Cart._System unit)
        {
            switch (unit) {
                case Cart._System.SMS:
                    setSmsType( this.data );
                    readSmsHeader();
                    break;
                case Cart._System.GENESIS:
                    if ( !isGenesis(this.data) ) {
                        if (convertFromSmd() || convertFromMd()) {}
                    }
                    this.index = Index.GENESIS_ROM;
                    this.type = Type.GENESIS_ROM;
                    readGenesisHeader();
                    break;
                case Cart._System.SNES:
                    if (!isSnes()) {
                        this.index = Index.SNES_LOROM;
                        this.type = Type.SNES_LOROM;
                    }
                    readSnesHeader();
                    break;
            }
        }
        //GENESIS
        bool convertFromMd()
        {
            byte singleByte;
            uint halfRomSize = this.size / 2;
            byte[] buffer = new byte[this.size];

            for (uint i = 0; i < this.size; i++) {
                singleByte = data[i];
                if (i < halfRomSize) {
                    buffer[(i * 2) + 1] = singleByte;
                } else {
                    buffer[((i - halfRomSize) * 2)] = singleByte;
                }
            }

            if (!isGenesis(buffer)) {
                buffer = null;
                return false;
            }

            Buffer.BlockCopy(buffer, 0, data, 0, (int)this.size);
            buffer = null;
            return true;
        }

        bool convertFromSmd()
        {
            byte singleByte;
            uint blockSize = 16384;
            uint halfBlockSize = blockSize / 2;
            uint start = 0;

            if (this.size % blockSize != 0) {
                return false;
            }

            byte[] buffer = new byte[this.size];
            uint blocks = this.size / blockSize;

            for (uint i = 0; i < blocks; i++ ) {
                start = i * blockSize;
                for (uint j = 0; j < blockSize; j++) {
                    singleByte = data[start + j];
                    if (j < halfBlockSize) {
                        buffer[start + (j * 2) + 1] = singleByte;
                    } else {
                        buffer[start + ((j - halfBlockSize) * 2) ] = singleByte;
                    }
                }
            }
            if (!isGenesis(buffer)) {
                buffer = null;
                return false;
            }
            Buffer.BlockCopy(buffer, 0, data, 0, (int)this.size);
            buffer = null;
            return true;
        }

        bool isGenesis(byte[] buffer)
        {
            byte[] temp = new Byte[16];
            Buffer.BlockCopy(buffer, (int)Index.GENESIS_ROM, temp, 0, temp.Count());
            string detectionInfo = System.Text.ASCIIEncoding.ASCII.GetString(temp);

            int checksum = 0;
            for (uint i = 512; i < this.size; i++ ) {
                if ((i % 2) == 0) {
                    checksum += buffer[i] * 256;
                } else {
                    checksum += buffer[i];
                }
            }
            checksum &= 0xFFFF;
            ushort headerChecksum = (ushort)(buffer[(int)Index.GENESIS_ROM + (int)Header_Genesis.CHECKSUM + 1]
                                    | (buffer[(int)Index.GENESIS_ROM + (int)Header_Genesis.CHECKSUM] << 8));

            if (checksum == headerChecksum && detectionInfo.IndexOf("SEGA") != -1) {
                return true;
            }
            return false;
        }

        void readGenesisHeader()
        {
            byte[] temp = new Byte[48];
            Buffer.BlockCopy(data, (int)this.index + (int)Header_Genesis.CART_NAME_ORIGIN, temp, 0, 48);
            string cartNameOrigin = System.Text.ASCIIEncoding.ASCII.GetString(temp);
            cartNameOrigin = cartNameOrigin.Trim();
            temp = null;
            temp = new Byte[48];
            Buffer.BlockCopy(data, (int)this.index + (int)Header_Genesis.CART_NAME_OVERSEAS, temp, 0, 48);
            string cartNameOverseas = System.Text.ASCIIEncoding.ASCII.GetString(temp);
            cartNameOverseas = cartNameOverseas.Trim();
            this.cartName = cartNameOverseas != "" ? cartNameOverseas : cartNameOrigin;
            temp = null;
            temp = new Byte[8];
            Buffer.BlockCopy(data, (int)this.index + (int)Header_Genesis.BACKUP_RAM_ADDRESS_RANGE, temp, 0, 8);

            uint startSram = (uint) ((temp[0] << 24) | (temp[1] << 16) | (temp[2] << 8) | temp[3]);
            uint endSram = (uint)((temp[4] << 24) | (temp[5] << 16) | (temp[6] << 8) | temp[7]);

            if (endSram > startSram) {
                this.sramSize = endSram - startSram + 2;
            }
            temp = null;
            temp = new Byte[1];
            Buffer.BlockCopy(data, (int)this.index + (int)Header_Genesis.COUNTRY, temp, 0, 1);
            string country = System.Text.ASCIIEncoding.ASCII.GetString(temp);
            country = country.Trim();
            int posJ = country.IndexOf('J');
            int posU = country.IndexOf('U');
            if (posJ != -1) {
                this.region = Region.JAPAN;
            } else if (posU != -1 || country == "" || country == "?") {
                this.region = Region.USA;
            } else {
                this.region = Region.EUROPE;
            }
        }
        //SMS
        bool isSmsBios() {
            MD5 md5 = System.Security.Cryptography.MD5.Create();
            byte[] hashBytes = md5.ComputeHash(this.data);
            StringBuilder hashStringBuilder = new StringBuilder();
            for (int i = 0; i < hashBytes.Length; i++) {
                hashStringBuilder.Append(hashBytes[i].ToString("X2"));
            }
            string hashString = hashStringBuilder.ToString();
            foreach (KeyValuePair<string, Region> ident in bios) {
                if (ident.Key == hashString) {
                    this.region = ident.Value;
                    return true;
                }
            }
            return false;
        }

        void setSmsType(byte[] buffer)
        {
            if (isSmsBios()) {
                this.type = Type.SMS_BIOS;
                return;
            }

            Dictionary<Type, uint> mappers = new Dictionary<Type, uint>();
            mappers.Add(Type.SMS_ROM_GENERIC, 0);
            mappers.Add(Type.SMS_ROM_CODEMASTERS, 0);
            mappers.Add(Type.SMS_ROM_KOREAN, 0);
            mappers.Add(Type.SMS_ROM_KOREAN_8K, 0);
            mappers.Add(Type.SMS_ROM_NO_BANK_SWITCHING, 0);

            //this part is mainly from Meka
            for (uint i = 0; i < Math.Min(0x8000, this.size); i++) {
                if (buffer[i] == 0x32) { //LD (xxxx), A
                    uint addr = (uint)(buffer[i+2] << 8) | buffer[i+1];
                    if (addr == 0xffff) {
                        i += 2; mappers[Type.SMS_ROM_GENERIC]++;
                    } else if (addr == 0x8000) {
                        i += 2; mappers[Type.SMS_ROM_CODEMASTERS]++;
                    } else if (addr == 0xa000) {
                        i += 2; mappers[Type.SMS_ROM_KOREAN]++;
                    } else if ( (addr == 0x0001) || (addr == 0x0002) || (addr == 0x0003) ) {
                        i += 2; mappers[Type.SMS_ROM_KOREAN_8K]++;
                    } 
                }
            }
            if (mappers[Type.SMS_ROM_GENERIC] == 0 && mappers[Type.SMS_ROM_CODEMASTERS] == 0 && mappers[Type.SMS_ROM_KOREAN] == 0 && mappers[Type.SMS_ROM_KOREAN_8K] == 0) {
                if (this.size <= (32 * 1024)) mappers[Type.SMS_ROM_NO_BANK_SWITCHING]++;
            }
            var sortedMappers = (from entry in mappers orderby entry.Value descending select entry).ToDictionary(pair => pair.Key, pair => pair.Value);

            this.type = Type.SMS_ROM_GENERIC;

            foreach (KeyValuePair<Type, uint> mapper in sortedMappers) {
                if ((mapper.Value > (mappers[Type.SMS_ROM_GENERIC] + 2))
                    || ((mapper.Value > 0) && (mappers[Type.SMS_ROM_GENERIC] == 0))) {
                        this.type = mapper.Key;
                        break;
                }
            }
        }

        void readSmsHeader()
        {
            this.sramSize = 8 * 1024; //no sram detection by header possible, asume 8 k, writes only if at least one value > 0
            switch ( this.type ) {
                case Type.SMS_BIOS:
                    break;
                case Type.SMS_ROM_KOREAN:
                case Type.SMS_ROM_KOREAN_8K:
                case Type.SMS_ROM_NO_BANK_SWITCHING:
                    this.region = Region.JAPAN; break;
                case Type.SMS_ROM_CODEMASTERS:
                    this.region = Region.EUROPE; break;
                case Type.SMS_ROM_GENERIC:
                default:
                    this.region = Region.USA;
                    if (this.size < (32 * 1024)) break;
                    byte[] temp = new Byte[1];
                    Buffer.BlockCopy(data, (int)Index.SMS_ROM_GENERIC + 0xf, temp, 0, temp.Count());
                    if ((temp[0] >> 4) == 3) {
                        this.region = Region.JAPAN;
                    }
                    break;
            }
        }
        //SNES
        bool isSnes()
        {
            if (isSuperGameBoy()) return true;
            if (isSufamiTurbo()) return true;

            byte hintLo = hintForRomType(Index.SNES_LOROM);
            byte hintHi = hintForRomType(Index.SNES_HIROM);
            byte hintEx = hintForRomType(Index.SNES_EXROM);

            if (hintLo == 0 && hintHi == 0 && hintEx == 0) return false;

            if (hintEx > 0) hintEx += 4; // > 32 MBit -> favour ex rom

            if (hintLo >= hintHi && hintLo >= hintEx) {
                this.index = Index.SNES_LOROM;
                if (size >= 0x401000) {
                    this.type = Type.SNES_EXLOROM;
                } else {
                    this.type = Type.SNES_LOROM;
                }
            } else if (hintHi >= hintEx) {
                this.index = Index.SNES_HIROM;
                this.type = Type.SNES_HIROM;
            } else {
                this.index = Index.SNES_EXROM;
                this.type = Type.SNES_EXHIROM;
            }

            if (checkBsxFlashCart()) return true;
            checkBsxFlashCartConnector();

            detectChip();
            return true;
        }

        void readSnesHeader()
        {
            if (this.size < (uint)(this.index + 64)) {
                this.sramSize = 0;
                this.region = Region.USA;
                this.cartName = "";
                return;
            }
            byte sram = data[(uint)this.index + (uint)Header_Snes.SRAM_SIZE];
            this.sramSize = (sram & 7) > 0 ? (uint) (1024 << (sram & 7)) : 0;

            if (this.chips.sfx) {
                this.sramSize = (uint) 1024 << (data[(uint)this.index - 3] & 7);
                if (this.sramSize == 1024) this.sramSize = 0;
            }

            byte[] temp = new Byte[21];
            Buffer.BlockCopy(data, (int)this.index + (int)Header_Snes.CART_NAME, temp, 0, 21);
            this.cartName = System.Text.ASCIIEncoding.ASCII.GetString(temp);
            this.cartName = this.cartName.Trim();

            byte _region = (byte)(data[(uint)this.index + (uint)Header_Snes.REGION] & 0x7f);
            this.region = (_region <= 1 || _region >= 13) ? Region.USA : Region.EUROPE;
        }

        void detectChip()
        {
            byte mu = data[(uint)this.index + (uint)Header_Snes.MAKEUP];
            byte rt = data[(uint)this.index + (uint)Header_Snes.ROM_TYPE];
            byte li = data[(uint)this.index + (uint)Header_Snes.LICENSE];

            if (mu == 0x3A && (rt == 0xF5 || rt == 0xF9)) {
                this.chips.spc7110 = true;
                //FEOEZ needs Realtimeclock
                if (rt == 0xF9) this.chips.spc7110rtc = true;
                this.type = Type.SNES_SPC7110ROM;
            }

            if (mu == 0x20 && (rt == 0x13 || rt == 0x14 || rt == 0x15 || rt == 0x1A)) {
                this.chips.sfx = true;
                this.type = Type.SNES_SUPERFXROM;
            }

            if ((mu == 0x20 || mu == 0x21) && rt == 0x03) this.chips.dsp1 = true;

            if (mu == 0x30 && rt == 0x05) { // SD Gundam GX
                if (li == 0xB2) this.chips.dsp3 = true;
                else this.chips.dsp1 = true;
            }
            if (mu == 0x31 && (rt == 0x03 || rt == 0x05)) this.chips.dsp1 = true;

            if (mu == 0x20 && rt == 0x05) this.chips.dsp2 = true;

            if (mu == 0x30 && rt == 0x03) this.chips.dsp4 = true;

            if (mu == 0x30 && rt == 0xf6) {
                if (this.size > 0x80000) this.chips.st010 = true;
                else this.chips.st011 = true;
            }
            if (mu == 0x30 && rt == 0xf5) this.chips.st018 = true;

            if (mu == 0x35 && rt == 0x55) this.chips.srtc = true;
            if (mu == 0x30 && rt == 0x25) this.chips.obc1 = true;
            if (mu == 0x32 && (rt == 0x43 || rt == 0x45)) this.chips.sdd1 = true;
            if (mu == 0x23 && (rt == 0x34 || rt == 0x35)) {
                this.chips.sa1 = true;
                this.type = Type.SNES_SA1ROM;
            }
            if (mu == 0x20 && rt == 0xf3) this.chips.c4 = true;

            if (this.chips.dsp1) {
                if (((mu & 0x2f) == (byte)Type.SNES_LOROM) && (this.size <= 0x100000)) {
                    this.chips.dsp1Type = Chips.Dsp1Type.LOROM1MB;
                } else if ((mu & 0x2f) == (byte)Type.SNES_LOROM) {
                    this.chips.dsp1Type = Chips.Dsp1Type.LOROM2MB;
                } else if ((mu & 0x2f) == (byte)Type.SNES_HIROM) {
                    this.chips.dsp1Type = Chips.Dsp1Type.HIROM;
                }
            }
        }

        void checkBsxFlashCartConnector()
        {
            byte[] area = new Byte[11];
            Buffer.BlockCopy(data, (int)this.index - 14, area, 0, area.Count());

            byte licence = data[(uint)this.index + (uint)Header_Snes.LICENSE];

            bool bsxSlot = false;

            if ((area[0] == 'Z') && (area[3] == 'J')) {
                if ((area[1] >= 'A' && area[1] <= 'Z') || (area[1] >= '0' && area[1] <= '9')) {
                    if (licence == 0x33 || (area[4] == 0x00 && area[10] == 0x00)) {
                        bsxSlot = true;
		            }
	            }
            }

            if (bsxSlot) {
                string bsxHeader = "Satellaview BS-X     ";
                if (data.Skip((int)this.index).Take(21).SequenceEqual(Encoding.ASCII.GetBytes(bsxHeader))) {
                    this.type = Type.SNES_BSXROM_BIOS;
                } else {
                    this.type = this.index == Index.SNES_LOROM ? Type.SNES_BSCLOROM : Type.SNES_BSCHIROM;
                }
            }
        }

        private bool checkBsxFlashCart()
        {
            byte[] area = new Byte[0xFF];
            Buffer.BlockCopy(data, (int)this.index, area, 0, area.Count());

            if ((area[0x13] == 0x00 || area[0x13] == 0xff) && area[0x14] == 0x00) {
                byte n15 = area[0x15];
                if (n15 == 0x00 || n15 == 0x80 || n15 == 0x84 || n15 == 0x9c || n15 == 0xbc || n15 == 0xfc) {
                    if (area[0x1a] == 0x33 || area[0x1a] == 0xff) {
                        this.type = Type.SNES_BSXROM;
                        return true;
                    }
                }
            }
            return false;
        }

        private byte hintForRomType(Index index)
        {
            //this part in mainly from bsnes
            sbyte hint = 0;
            if ( size < (uint) (index + 64) ) return 0; //image too small to contain header at this location

            ushort resetvector =(ushort)(data[(uint)index + (uint)Header_Snes.RESETV] |
                                        (data[(uint)index + (uint)Header_Snes.RESETV + 1] << 8));
            ushort checksum =   (ushort)(data[(uint)index + (uint)Header_Snes.CHECKSUM] |
                                        (data[(uint)index + (uint)Header_Snes.CHECKSUM + 1] << 8));
            ushort ichecksum =  (ushort)(data[(uint)index + (uint)Header_Snes.ICHECKSUM] |
                                        (data[(uint)index + (uint)Header_Snes.ICHECKSUM + 1] << 8));

            if (resetvector < 0x8000) return 0;  //must point to rom at $00:[8000-ffff]

            byte resetop = data[ ( (uint) index & ~0x7fff) | (uint) (resetvector & 0x7fff) ];
            byte mapper = (byte)(data[(uint)index + (uint)Header_Snes.MAKEUP] & ~0x10); // Bit 3 variable, rom speed

            //most likely opcodes
            if (resetop == 0x78 //sei
            || resetop == 0x18 //clc (clc; xce)
            || resetop == 0x38 //sec (sec; xce)
            || resetop == 0x9c //stz $nnnn (stz $4200)
            || resetop == 0x4c //jmp $nnnn
            || resetop == 0x5c //jml $nnnnnn
            ) hint += 8;

            //plausible opcodes
            if (resetop == 0xc2 //rep #$nn
            || resetop == 0xe2 //sep #$nn
            || resetop == 0xad //lda $nnnn
            || resetop == 0xae //ldx $nnnn
            || resetop == 0xac //ldy $nnnn
            || resetop == 0xaf //lda $nnnnnn
            || resetop == 0xa9 //lda #$nn
            || resetop == 0xa2 //ldx #$nn
            || resetop == 0xa0 //ldy #$nn
            || resetop == 0x20 //jsr $nnnn
            || resetop == 0x22 //jsl $nnnnnn
            ) hint += 4;

            //implausible opcodes
            if (resetop == 0x40 //rti
            || resetop == 0x60 //rts
            || resetop == 0x6b //rtl
            || resetop == 0xcd //cmp $nnnn
            || resetop == 0xec //cpx $nnnn
            || resetop == 0xcc //cpy $nnnn
            ) hint -= 4;

            //least likely opcodes
            if (resetop == 0x00 //brk #$nn
            || resetop == 0x02 //cop #$nn
            || resetop == 0xdb //stp
            || resetop == 0x42 //wdm
            || resetop == 0xff //sbc $nnnnnn,x
            ) hint -= 8;

            if ((checksum + ichecksum) == 0xffff && (checksum != 0) && (ichecksum != 0)) hint += 4;

            if (index == Index.SNES_LOROM && mapper == (byte)Type.SNES_LOROM) hint += 2; //0x20 is usually LoROM
            if (index == Index.SNES_HIROM && mapper == (byte)Type.SNES_HIROM) hint += 2; //0x21 is usually HiROM
            if (index == Index.SNES_LOROM && mapper == (byte)Type.SNES_EXLOROM) hint += 2; //0x22 is usually ExLoROM
            if (index == Index.SNES_EXROM && mapper == (byte)Type.SNES_EXHIROM) hint += 2; //0x25 is usually ExHiROM

            if (data[(uint)index + (uint)Header_Snes.LICENSE] == 0x33) hint += 2; //0x33 indicates extended header
            if (data[(uint)index + (uint)Header_Snes.ROM_TYPE] < 0x08) hint++;
            if (data[(uint)index + (uint)Header_Snes.ROM_SIZE] < 0x0E) hint++; //max 64 mbit
            if (data[(uint)index + (uint)Header_Snes.SRAM_SIZE] < 0x08) hint++; //max 1024 kbit
            if (data[(uint)index + (uint)Header_Snes.REGION] < 14) hint++;

            return (byte) ((hint < 0) ? 0 : hint);
        }

        private bool isSufamiTurbo()
        {
            string stHeader = "BANDAI SFC-ADX";
            string stBios = "SFC-ADX BACKUP";

            if ( !data.Take(14).SequenceEqual(Encoding.ASCII.GetBytes(stHeader)) ) return false;

            if ( data.Skip(16).Take(14).SequenceEqual(Encoding.ASCII.GetBytes(stBios)) ) {
                type = Type.SNES_STROM_BIOS;
            } else {
                type = Type.SNES_STROM;
            }
            return true;
        }

        bool isSuperGameBoy()
        {
            if (this.size >= 0x0140) {
                if (data[0x0104] == 0xce && data[0x0105] == 0xed && data[0x0106] == 0x66 && data[0x0107] == 0x66
                && data[0x0108] == 0xcc && data[0x0109] == 0x0d && data[0x010a] == 0x00 && data[0x010b] == 0x0b) {
                    this.type = Type.SNES_SUPER_GAMEBOY;
                    return true;
                }
            }
            return false;
        }
    }
}
