niff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 38570 Content-Disposition: inline; filename="intelfsh.cpp" Last-Modified: Fri, 02 May 2025 03:18:10 GMT Expires: Mon, 30 Apr 2035 03:18:10 GMT ETag: "af8e50dc2d04f35ed1167d8ce8a7575f3f89862b" // license:BSD-3-Clause // copyright-holders:Aaron Giles /* Flash ROM emulation Explicitly supports: Intel 28F016S5 (byte-wide) AMD/Fujitsu 29F016 (byte-wide) Sharp LH28F400 (word-wide) Flash ROMs use a standardized command set across manufacturers, so this emulation should work even for non-Intel and non-Sharp chips as long as the game doesn't query the maker ID. */ #include "emu.h" #include "intelfsh.h" //************************************************************************** // CONSTANTS //************************************************************************** enum { FM_NORMAL, // normal read/write FM_READID, // read ID FM_READSTATUS, // read status FM_WRITEPART1, // first half of programming, awaiting second FM_CLEARPART1, // first half of clear, awaiting second FM_SETMASTER, // first half of set master lock, awaiting on/off FM_READAMDID1, // part 1 of alt ID sequence FM_READAMDID2, // part 2 of alt ID sequence FM_READAMDID3, // part 3 of alt ID sequence FM_ERASEAMD1, // part 1 of AMD erase sequence FM_ERASEAMD2, // part 2 of AMD erase sequence FM_ERASEAMD3, // part 3 of AMD erase sequence FM_ERASEAMD4, // part 4 of AMD erase sequence FM_BYTEPROGRAM, FM_BANKSELECT, FM_WRITEPAGEATMEL }; enum { MFG_ALLIANCE = 0x52, MFG_AMD = 0x01, MFG_AMIC = 0x37, MFG_ATMEL = 0x1f, MFG_BRIGHT = 0xad, MFG_CATALYST = 0x31, MFG_EON = 0x1c, MFG_FUJITSU = 0x04, MFG_GIGADEVICE = 0xc8, MFG_HYUNDAI = 0xad, MFG_INTEL = 0x89, MFG_ISSI = 0xd5, MFG_MACRONIX = 0xc2, MFG_PANASONIC = 0x32, MFG_PMC = 0x9d, MFG_SANYO = 0x62, MFG_SHARP = 0xb0, MFG_SPANSION = 0x01, MFG_SST = 0xbf, MFG_ST = 0x20, MFG_SYNCMOS = 0x40, MFG_TI = 0x97, MFG_TI_OLD = 0x01, MFG_WINBOND_NEX = 0xef, MFG_WINBOND = 0xda }; //************************************************************************** // GLOBAL VARIABLES //************************************************************************** // device type definition DEFINE_DEVICE_TYPE(INTEL_28F016S5, intel_28f016s5_device, "intel_28f016s5", "Intel 28F016S5 Flash") DEFINE_DEVICE_TYPE(SHARP_LH28F016S, sharp_lh28f016s_device, "sharp_lh28f016s", "Sharp LH28F016S Flash") DEFINE_DEVICE_TYPE(SHARP_LH28F016S_16BIT, sharp_lh28f016s_16bit_device, "sharp_lh28f016s_16bit", "Sharp LH28F016S Flash (16-bit)") DEFINE_DEVICE_TYPE(ATMEL_29C010, atmel_29c010_device, "atmel_29c010", "Atmel 29C010 Flash") DEFINE_DEVICE_TYPE(AMD_29F010, amd_29f010_device, "amd_29f010", "AMD 29F010 Flash") DEFINE_DEVICE_TYPE(AMD_29F040, amd_29f040_device, "amd_29f040", "AMD 29F040 Flash") DEFINE_DEVICE_TYPE(AMD_29F080, amd_29f080_device, "amd_29f080", "AMD 29F080 Flash") DEFINE_DEVICE_TYPE(AMD_29F400T, amd_29f400t_device, "amd_29f400t", "AMD 29F400T Flash") DEFINE_DEVICE_TYPE(AMD_29F800T, amd_29f800t_device, "amd_29f800t", "AMD 29F800T Flash") DEFINE_DEVICE_TYPE(AMD_29F800B_16BIT, amd_29f800b_16bit_device, "amd_29f800b_16bit", "AMD 29F800B Flash (16-bit)") DEFINE_DEVICE_TYPE(AMD_29LV200T, amd_29lv200t_device, "amd_29lv200t", "AMD 29LV200T Flash") DEFINE_DEVICE_TYPE(FUJITSU_29F160TE, fujitsu_29f160te_device, "mbm29f160te", "Fujitsu MBM29F160TE Flash") DEFINE_DEVICE_TYPE(FUJITSU_29F016A, fujitsu_29f016a_device, "mbm29f016a", "Fujitsu MBM29F016A Flash") DEFINE_DEVICE_TYPE(FUJITSU_29DL164BD, fujitsu_29dl164bd_device, "mbm29dl164bd", "Fujitsu MBM29DL164BD Flash") DEFINE_DEVICE_TYPE(FUJITSU_29LV002TC, fujitsu_29lv002tc_device, "mbm29lv002tc", "Fujitsu MBM29LV002TC Flash") DEFINE_DEVICE_TYPE(FUJITSU_29LV800B, fujitsu_29lv800b_device, "mbm29lv800b", "Fujitsu MBM29LV800B Flash") DEFINE_DEVICE_TYPE(INTEL_E28F400B, intel_e28f400b_device, "intel_e28f400b", "Intel E28F400B Flash") DEFINE_DEVICE_TYPE(MACRONIX_29L001MC, macronix_29l001mc_device, "macronix_29l001mc", "Macronix 29L001MC Flash") DEFINE_DEVICE_TYPE(MACRONIX_29LV160TMC, macronix_29lv160tmc_device, "macronix_29lv160tmc", "Macronix 29LV160TMC Flash") DEFINE_DEVICE_TYPE(TMS_29F040, tms_29f040_device, "tms_29f040", "Texas Instruments 29F040 Flash") DEFINE_DEVICE_TYPE(PANASONIC_MN63F805MNP, panasonic_mn63f805mnp_device, "panasonic_mn63f805mnp", "Panasonic MN63F805MNP Flash") DEFINE_DEVICE_TYPE(SANYO_LE26FV10N1TS, sanyo_le26fv10n1ts_device, "sanyo_le26fv10n1ts", "Sanyo LE26FV10N1TS Flash") DEFINE_DEVICE_TYPE(SST_28SF040, sst_28sf040_device, "sst_28sf040", "SST 28SF040 Flash") DEFINE_DEVICE_TYPE(SST_39SF040, sst_39sf040_device, "sst_39sf040", "SST 39SF040 Flash") DEFINE_DEVICE_TYPE(SST_39VF020, sst_39vf020_device, "sst_39vf020", "SST 39VF020 Flash") DEFINE_DEVICE_TYPE(SST_49LF020, sst_49lf020_device, "sst_49lf020", "SST 49LF020 Flash") DEFINE_DEVICE_TYPE(SHARP_LH28F400, sharp_lh28f400_device, "sharp_lh28f400", "Sharp LH28F400 Flash") DEFINE_DEVICE_TYPE(INTEL_E28F008SA, intel_e28f008sa_device, "intel_e28f008sa", "Intel E28F008SA Flash") DEFINE_DEVICE_TYPE(INTEL_TE28F160, intel_te28f160_device, "intel_te28f160", "Intel TE28F160 Flash") DEFINE_DEVICE_TYPE(SHARP_LH28F160S3, sharp_lh28f160s3_device, "sharp_lh28f160s3", "Sharp LH28F160S3 Flash") DEFINE_DEVICE_TYPE(INTEL_TE28F320, intel_te28f320_device, "intel_te28f320", "Intel TE28F320 Flash") DEFINE_DEVICE_TYPE(SHARP_LH28F320BF, sharp_lh28f320bf_device, "sharp_lh28f320bf", "Sharp LH28F320BFHE-PBTL Flash") DEFINE_DEVICE_TYPE(INTEL_28F320J3D, intel_28f320j3d_device, "intel_28f320j3d", "Intel 28F320J3D Flash") DEFINE_DEVICE_TYPE(SPANSION_S29GL064S, spansion_s29gl064s_device, "spansion_s29gl064s", "Spansion / Cypress S29GL064S Flash" ) DEFINE_DEVICE_TYPE(INTEL_28F320J5, intel_28f320j5_device, "intel_28f320j5", "Intel 28F320J5 Flash") DEFINE_DEVICE_TYPE(SST_39VF400A, sst_39vf400a_device, "sst_39vf400a", "SST 39VF400A Flash") DEFINE_DEVICE_TYPE(ATMEL_49F4096, atmel_49f4096_device, "atmel_49f4096", "Atmel AT49F4096 Flash") DEFINE_DEVICE_TYPE(CAT28F020, cat28f020_device, "cat28f020", "CSI CAT28F020 Flash") //************************************************************************** // LIVE DEVICE //************************************************************************** //------------------------------------------------- // intelfsh_device - constructor //------------------------------------------------- intelfsh_device::intelfsh_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock, uint32_t variant) : device_t(mconfig, type, tag, owner, clock), device_nvram_interface(mconfig, *this), m_region(*this, DEVICE_SELF), m_type(variant), m_size(0), m_bits(8), m_addrmask(0), m_device_id(0), m_maker_id(0), m_sector_is_4k(false), m_sector_is_16k(false), m_top_boot_sector(false), m_bot_boot_sector(false), m_status(0x80), m_erase_sector(0), m_flash_mode(FM_NORMAL), m_flash_master_lock(false), m_timer(nullptr), m_bank(0) { switch( variant ) { case FLASH_INTEL_28F016S5: case FLASH_SHARP_LH28F016S: m_bits = 8; m_size = 0x200000; m_maker_id = MFG_INTEL; m_device_id = 0xaa; break; case FLASH_SHARP_LH28F016S_16BIT: m_bits = 16; m_size = 0x200000; m_maker_id = MFG_INTEL; m_device_id = 0xaa; break; case FLASH_ATMEL_29C010: m_bits = 8; m_size = 0x20000; m_page_size = 0x80; m_maker_id = MFG_ATMEL; m_device_id = 0xd5; break; case FLASH_ATMEL_49F4096: m_bits = 16; m_size = 0x80000; m_maker_id = MFG_ATMEL; m_device_id = 0x92; m_sector_is_16k = true; break; case FLASH_AMD_29F010: m_bits = 8; m_size = 0x20000; m_maker_id = MFG_AMD; m_device_id = 0x20; break; case FLASH_AMD_29F040: m_bits = 8; m_size = 0x80000; m_maker_id = MFG_AMD; m_device_id = 0xa4; break; case FLASH_AMD_29F080: m_bits = 8; m_size = 0x100000; m_addrmask = 0x7ff; m_maker_id = MFG_AMD; m_device_id = 0xd5; break; case FLASH_AMD_29F400T: m_bits = 8; m_size = 0x80000; m_maker_id = MFG_AMD; m_device_id = 0x23; m_top_boot_sector = true; break; case FLASH_AMD_29F800T: m_bits = 8; m_size = 0x100000; m_maker_id = MFG_AMD; m_device_id = 0xda; m_top_boot_sector = true; break; case FLASH_AMD_29F800B_16BIT: m_bits = 16; m_size = 0x100000; m_maker_id = MFG_AMD; m_device_id = 0x2258; m_top_boot_sector = false; break; case FLASH_AMD_29LV200T: m_bits = 8; m_size = 0x40000; m_maker_id = MFG_AMD; m_device_id = 0x3b; break; case FLASH_CAT28F020: m_bits = 8; m_size = 0x40000; m_maker_id = MFG_CATALYST; m_device_id = 0xbd; break; case FLASH_INTEL_28F320J3D: m_bits = 16; m_size = 0x400000; m_maker_id = MFG_INTEL; m_device_id = 0x16; m_sector_is_4k = true; break; case FLASH_SPANSION_S29GL064S: // senbbs m_bits = 16; m_size = 0x800000; m_maker_id = MFG_SPANSION; m_device_id = 0x227e; m_sector_is_4k = false; break; case FLASH_INTEL_28F320J5: // funkball m_bits = 16; m_size = 0x400000; m_maker_id = MFG_INTEL; m_device_id = 0x14; // m_sector_is_4k = true; 128kb? break; case FLASH_SST_39SF040: m_bits = 8; m_size = 0x80000; m_maker_id = MFG_SST; m_device_id = 0xb7; m_sector_is_4k = true; break; case FLASH_SST_39VF020: m_bits = 8; m_size = 0x40000; m_maker_id = MFG_SST; m_device_id = 0xd6; m_sector_is_4k = true; break; case FLASH_SST_49LF020: m_bits = 8; m_size = 0x40000; m_maker_id = MFG_SST; m_device_id = 0x61; m_sector_is_4k = true; break; case FLASH_SST_39VF400A: m_bits = 16; m_size = 0x80000; m_maker_id = MFG_SST; m_device_id = 0xd6; m_sector_is_4k = true; break; case FLASH_SHARP_LH28F400: m_bits = 16; m_size = 0x80000; m_maker_id = MFG_SHARP; m_device_id = 0xed; break; case FLASH_INTEL_E28F400B: m_bits = 16; m_size = 0x80000; m_maker_id = MFG_INTEL; m_device_id = 0x4471; break; case FLASH_FUJITSU_29F160TE: m_bits = 8; m_size = 0x200000; m_maker_id = MFG_FUJITSU; m_device_id = 0xd2; m_top_boot_sector = true; break; case FLASH_FUJITSU_29F016A: m_bits = 8; m_size = 0x200000; m_maker_id = MFG_FUJITSU; m_device_id = 0xad; break; case FLASH_FUJITSU_29DL164BD: m_bits = 8; m_size = 0x200000; m_maker_id = MFG_FUJITSU; m_device_id = 0x35; break; case FLASH_FUJITSU_29LV002TC: m_bits = 8; m_size = 0x40000; m_maker_id = MFG_FUJITSU; m_device_id = 0x40; break; case FLASH_FUJITSU_29LV800B: m_bits = 16; m_size = 0x100000; m_maker_id = MFG_FUJITSU; m_device_id = 0x225b; m_bot_boot_sector = true; break; case FLASH_INTEL_E28F008SA: m_bits = 8; m_size = 0x100000; m_maker_id = MFG_INTEL; m_device_id = 0xa2; break; case FLASH_INTEL_TE28F160: case FLASH_SHARP_LH28F160S3: m_bits = 16; m_size = 0x200000; m_maker_id = MFG_SHARP; m_device_id = 0xd0; break; case FLASH_INTEL_TE28F320: m_bits = 16; m_size = 0x400000; m_maker_id = MFG_INTEL; m_device_id = 0x8896; break; case FLASH_SHARP_LH28F320BF: m_bits = 16; m_size = 0x400000; m_maker_id = MFG_SHARP; m_device_id = 0xb5; break; case FLASH_MACRONIX_29L001MC: m_bits = 8; m_size = 0x20000; m_maker_id = MFG_MACRONIX; m_device_id = 0x51; break; case FLASH_MACRONIX_29LV160TMC: m_bits = 8; m_size = 0x20000; m_maker_id = MFG_MACRONIX; m_device_id = 0x49; m_sector_is_16k = true; break; case FLASH_PANASONIC_MN63F805MNP: m_bits = 8; m_size = 0x10000; m_maker_id = MFG_PANASONIC; m_device_id = 0x1b; m_sector_is_4k = true; break; case FLASH_SANYO_LE26FV10N1TS: m_bits = 8; m_size = 0x20000; m_maker_id = MFG_SANYO; m_device_id = 0x13; m_sector_is_4k = true; break; case FLASH_SST_28SF040: m_bits = 8; m_size = 0x80000; m_maker_id = MFG_SST; m_device_id = 0x04; break; case FLASH_TMS_29F040: m_bits = 8; m_addrmask = 0x7fff; m_size = 0x80000; m_maker_id = MFG_AMD; m_device_id = 0xa4; break; } int addrbits; for (addrbits = 24; addrbits > 0; addrbits--) if ((m_size & (1 << addrbits)) != 0) break; } intelfsh8_device::intelfsh8_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock, uint32_t variant) : intelfsh_device(mconfig, type, tag, owner, clock, variant) { } intelfsh16_device::intelfsh16_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock, uint32_t variant) : intelfsh_device(mconfig, type, tag, owner, clock, variant) { } intel_28f016s5_device::intel_28f016s5_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : intelfsh8_device(mconfig, INTEL_28F016S5, tag, owner, clock, FLASH_INTEL_28F016S5) { } fujitsu_29f160te_device::fujitsu_29f160te_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : intelfsh8_device(mconfig, FUJITSU_29F160TE, tag, owner, clock, FLASH_FUJITSU_29F160TE) { } fujitsu_29f016a_device::fujitsu_29f016a_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : intelfsh8_device(mconfig, FUJITSU_29F016A, tag, owner, clock, FLASH_FUJITSU_29F016A) { } fujitsu_29dl164bd_device::fujitsu_29dl164bd_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : intelfsh8_device(mconfig, FUJITSU_29DL164BD, tag, owner, clock, FLASH_FUJITSU_29DL164BD) { } fujitsu_29lv002tc_device::fujitsu_29lv002tc_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : intelfsh8_device(mconfig, FUJITSU_29LV002TC, tag, owner, clock, FLASH_FUJITSU_29LV002TC) { } fujitsu_29lv800b_device::fujitsu_29lv800b_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : intelfsh16_device(mconfig, FUJITSU_29LV800B, tag, owner, clock, FLASH_FUJITSU_29LV800B) { } sharp_lh28f016s_device::sharp_lh28f016s_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : intelfsh8_device(mconfig, SHARP_LH28F016S, tag, owner, clock, FLASH_SHARP_LH28F016S) { } sharp_lh28f016s_16bit_device::sharp_lh28f016s_16bit_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : intelfsh16_device(mconfig, SHARP_LH28F016S_16BIT, tag, owner, clock, FLASH_SHARP_LH28F016S_16BIT) { } atmel_29c010_device::atmel_29c010_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : intelfsh8_device(mconfig, ATMEL_29C010, tag, owner, clock, FLASH_ATMEL_29C010) { } atmel_49f4096_device::atmel_49f4096_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : intelfsh16_device(mconfig, ATMEL_49F4096, tag, owner, clock, FLASH_ATMEL_49F4096) { } amd_29f010_device::amd_29f010_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : intelfsh8_device(mconfig, AMD_29F010, tag, owner, clock, FLASH_AMD_29F010) { } amd_29f040_device::amd_29f040_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : intelfsh8_device(mconfig, AMD_29F040, tag, owner, clock, FLASH_AMD_29F040) { } amd_29f080_device::amd_29f080_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : intelfsh8_device(mconfig, AMD_29F080, tag, owner, clock, FLASH_AMD_29F080) { } amd_29f400t_device::amd_29f400t_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : intelfsh8_device(mconfig, AMD_29F400T, tag, owner, clock, FLASH_AMD_29F400T) { } amd_29f800t_device::amd_29f800t_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : intelfsh8_device(mconfig, AMD_29F800T, tag, owner, clock, FLASH_AMD_29F800T) { } amd_29f800b_16bit_device::amd_29f800b_16bit_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : intelfsh16_device(mconfig, AMD_29F800B_16BIT, tag, owner, clock, FLASH_AMD_29F800B_16BIT) { } amd_29lv200t_device::amd_29lv200t_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : intelfsh8_device(mconfig, AMD_29LV200T, tag, owner, clock, FLASH_AMD_29LV200T) { } cat28f020_device::cat28f020_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : intelfsh8_device(mconfig, CAT28F020, tag, owner, clock, FLASH_CAT28F020) { } intel_e28f008sa_device::intel_e28f008sa_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : intelfsh8_device(mconfig, INTEL_E28F008SA, tag, owner, clock, FLASH_INTEL_E28F008SA) { } macronix_29l001mc_device::macronix_29l001mc_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : intelfsh8_device(mconfig, MACRONIX_29L001MC, tag, owner, clock, FLASH_MACRONIX_29L001MC) { } macronix_29lv160tmc_device::macronix_29lv160tmc_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : intelfsh8_device(mconfig, MACRONIX_29LV160TMC, tag, owner, clock, FLASH_MACRONIX_29LV160TMC) { } panasonic_mn63f805mnp_device::panasonic_mn63f805mnp_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : intelfsh8_device(mconfig, PANASONIC_MN63F805MNP, tag, owner, clock, FLASH_PANASONIC_MN63F805MNP) { } sanyo_le26fv10n1ts_device::sanyo_le26fv10n1ts_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : intelfsh8_device(mconfig, SANYO_LE26FV10N1TS, tag, owner, clock, FLASH_SANYO_LE26FV10N1TS) { } sst_28sf040_device::sst_28sf040_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : intelfsh8_device(mconfig, SST_28SF040, tag, owner, clock, FLASH_SST_28SF040) { } sst_39sf040_device::sst_39sf040_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : intelfsh8_device(mconfig, SST_39SF040, tag, owner, clock, FLASH_SST_39SF040) { } sst_39vf020_device::sst_39vf020_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : intelfsh8_device(mconfig, SST_39VF020, tag, owner, clock, FLASH_SST_39VF020) { } sst_49lf020_device::sst_49lf020_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : intelfsh8_device(mconfig, SST_49LF020, tag, owner, clock, FLASH_SST_49LF020) { } sharp_lh28f400_device::sharp_lh28f400_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : intelfsh16_device(mconfig, SHARP_LH28F400, tag, owner, clock, FLASH_SHARP_LH28F400) { } intel_te28f160_device::intel_te28f160_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : intelfsh16_device(mconfig, INTEL_TE28F160, tag, owner, clock, FLASH_INTEL_TE28F160) { } sharp_lh28f160s3_device::sharp_lh28f160s3_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : intelfsh16_device(mconfig, SHARP_LH28F160S3, tag, owner, clock, FLASH_SHARP_LH28F160S3) { } intel_te28f320_device::intel_te28f320_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : intelfsh16_device(mconfig, INTEL_TE28F320, tag, owner, clock, FLASH_INTEL_TE28F320) { } spansion_s29gl064s_device::spansion_s29gl064s_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : intelfsh16_device(mconfig, SPANSION_S29GL064S, tag, owner, clock, FLASH_SPANSION_S29GL064S) { } intel_e28f400b_device::intel_e28f400b_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : intelfsh16_device(mconfig, INTEL_E28F400B, tag, owner, clock, FLASH_INTEL_E28F400B) { } sharp_lh28f320bf_device::sharp_lh28f320bf_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : intelfsh16_device(mconfig, SHARP_LH28F320BF, tag, owner, clock, FLASH_SHARP_LH28F320BF) { } intel_28f320j3d_device::intel_28f320j3d_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : intelfsh16_device(mconfig, INTEL_28F320J3D, tag, owner, clock, FLASH_INTEL_28F320J3D) { } intel_28f320j5_device::intel_28f320j5_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : intelfsh16_device(mconfig, INTEL_28F320J5, tag, owner, clock, FLASH_INTEL_28F320J5) { } sst_39vf400a_device::sst_39vf400a_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : intelfsh16_device(mconfig, SST_39VF400A, tag, owner, clock, FLASH_SST_39VF400A) { } tms_29f040_device::tms_29f040_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : intelfsh8_device(mconfig, TMS_29F040, tag, owner, clock, FLASH_TMS_29F040) { } //------------------------------------------------- // device_start - device-specific startup //------------------------------------------------- void intelfsh_device::device_start() { m_data = std::make_unique(m_size); m_timer = timer_alloc(); save_item( NAME(m_status) ); save_item( NAME(m_flash_mode) ); save_item( NAME(m_flash_master_lock) ); save_pointer( &m_data[0], "m_data", m_size); } //------------------------------------------------- // device_timer - handler timer events //------------------------------------------------- void intelfsh_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) { switch( m_flash_mode ) { case FM_READSTATUS: m_status = 0x80; break; case FM_ERASEAMD4: m_flash_mode = FM_NORMAL; break; } } //------------------------------------------------- // nvram_default - called to initialize NVRAM to // its default state //------------------------------------------------- void intelfsh_device::nvram_default() { // region always wins if (m_region.found()) { uint32_t bytes = m_region->bytes(); if (bytes > m_size) bytes = m_size; if (m_bits == 8) { for (offs_t offs = 0; offs < bytes; offs++) m_data[offs] = m_region->as_u8(offs); } else { for (offs_t offs = 0; offs < bytes; offs += 2) { uint16_t v = m_region->as_u16(offs / 2); m_data[offs] = v >> 8; m_data[offs+1] = v; } } return; } // otherwise, default to 0xff memset(&m_data[0], 0xff, m_size); } //------------------------------------------------- // nvram_read - called to read NVRAM from the // .nv file //------------------------------------------------- void intelfsh_device::nvram_read(emu_file &file) { file.read(&m_data[0], m_size); } //------------------------------------------------- // nvram_write - called to write NVRAM to the // .nv file //------------------------------------------------- void intelfsh_device::nvram_write(emu_file &file) { file.write(&m_data[0], m_size); } //------------------------------------------------- // read_full - generic read, called by the // bit-width-specific readers //------------------------------------------------- uint32_t intelfsh_device::read_full(uint32_t address) { uint32_t data = 0; address += m_bank << 16; switch( m_flash_mode ) { default: case FM_NORMAL: switch( m_bits ) { case 8: data = m_data[address]; break; case 16: data = (m_data[address*2] << 8) | m_data[address*2+1]; break; } break; case FM_READSTATUS: data = m_status; break; case FM_READAMDID3: if ((m_maker_id == MFG_FUJITSU && m_device_id == 0x35) || (m_maker_id == MFG_AMD && m_device_id == 0x3b)) { // used in Fujitsu 29DL16X 8bits mode // used in AMD 29LV200 8bits mode switch (address & 0xff) { case 0: data = m_maker_id; break; case 2: data = m_device_id; break; case 4: data = 0; break; } } else { switch (address & 0xff) { case 0: data = m_maker_id; break; case 1: data = m_device_id; break; case 2: data = 0; break; } } break; case FM_READID: if (m_maker_id == MFG_INTEL && m_device_id == 0x16) { switch (address & 0xff) { case 0: data = m_maker_id; break; case 2: data = m_device_id; break; case 4: data = 0; break; } } else { switch (address & 0xff) { case 0: // maker ID data = m_maker_id; break; case 1: // chip ID data = m_device_id; break; case 2: // block lock config data = 0; // we don't support this yet break; case 3: // master lock config if (m_flash_master_lock) { data = 1; } else { data = 0; } break; } } break; case FM_ERASEAMD4: // reads outside of the erasing sector return normal data if ((address < m_erase_sector) || (address >= m_erase_sector+(64*1024))) { switch( m_bits ) { case 8: data = m_data[address]; break; case 16: data = (m_data[address*2] << 8) | m_data[address*2+1]; break; } } else { m_status ^= ( 1 << 6 ) | ( 1 << 2 ); data = m_status; } break; } //if (m_flash_mode != FM_NORMAL) logerror( "intelflash_read( %08x ) %08x\n", address, data ); return data; } //------------------------------------------------- // write_full - generic write, called by the // bit-width-specific writers //------------------------------------------------- void intelfsh_device::write_full(uint32_t address, uint32_t data) { //logerror( "intelflash_write( %u : %08x, %08x )\n", m_flash_mode, address, data ); address += m_bank << 16; switch( m_flash_mode ) { case FM_NORMAL: case FM_READSTATUS: case FM_READID: case FM_READAMDID3: switch( data & 0xff ) { case 0xf0: case 0xff: // reset chip mode m_flash_mode = FM_NORMAL; break; case 0x90: // read ID m_flash_mode = FM_READID; break; case 0x40: case 0x10: // program m_flash_mode = FM_WRITEPART1; break; case 0x50: // clear status reg if ((m_type == FLASH_SST_49LF020) && (m_flash_mode == FM_NORMAL)) logerror("Invalid flash mode byte %x\n", data & 0xff); else { m_status = 0x80; m_flash_mode = FM_READSTATUS; } break; case 0x20: // block erase if (m_type == FLASH_SST_49LF020) logerror("Unknown flash mode byte %x\n", data & 0xff); else m_flash_mode = FM_CLEARPART1; break; case 0x60: // set master lock m_flash_mode = FM_SETMASTER; break; case 0x70: // read status m_flash_mode = FM_READSTATUS; break; case 0xaa: // AMD ID select part 1 if( ( address & 0xfff ) == 0x555 ) { m_flash_mode = FM_READAMDID1; } else if( ( address & 0xfff ) == 0xaaa ) { m_flash_mode = FM_READAMDID1; } break; default: logerror( "Unknown flash mode byte %x\n", data & 0xff ); break; } break; case FM_READAMDID1: if( ( address & 0xffff ) == 0x2aa && ( data & 0xff ) == 0x55 ) { m_flash_mode = FM_READAMDID2; } else if( ( address & 0xffff ) == 0x2aaa && ( data & 0xff ) == 0x55 ) { m_flash_mode = FM_READAMDID2; } else if( ( address & 0xfff ) == 0x555 && ( data & 0xff ) == 0x55 ) { m_flash_mode = FM_READAMDID2; } // for AMD 29F080 address bits A11-A19 don't care, for TMS 29F040 address bits A15-A18 don't care else if( ( address & m_addrmask ) == ( 0xaaaa & m_addrmask ) && ( data & 0xff ) == 0x55 && m_addrmask ) { m_flash_mode = FM_READAMDID2; } else { logerror( "unexpected %08x=%02x in FM_READAMDID1\n", address, data & 0xff ); m_flash_mode = FM_NORMAL; } break; case FM_READAMDID2: if( ( address & 0xffff ) == 0x555 && ( data & 0xff ) == 0x90 ) { m_flash_mode = FM_READAMDID3; } else if( ( address & 0xffff ) == 0x5555 && ( data & 0xff ) == 0x90 ) { m_flash_mode = FM_READAMDID3; } else if( ( address & 0xfff ) == 0xaaa && ( data & 0xff ) == 0x90 ) { m_flash_mode = FM_READAMDID3; } else if( ( address & 0xffff ) == 0x555 && ( data & 0xff ) == 0x80 ) { m_flash_mode = FM_ERASEAMD1; } else if( ( address & 0xffff ) == 0x5555 && ( data & 0xff ) == 0x80 ) { m_flash_mode = FM_ERASEAMD1; } else if( ( address & 0xfff ) == 0xaaa && ( data & 0xff ) == 0x80 ) { m_flash_mode = FM_ERASEAMD1; } else if( ( address & 0xffff ) == 0x555 && ( data & 0xff ) == 0xa0 ) { m_flash_mode = FM_BYTEPROGRAM; } else if( ( address & 0xffff ) == 0x5555 && ( data & 0xff ) == 0xa0 ) { if (m_type == FLASH_ATMEL_29C010) { m_flash_mode = FM_WRITEPAGEATMEL; m_byte_count = 0; } else { m_flash_mode = FM_BYTEPROGRAM; } } else if( ( address & 0xfff ) == 0xaaa && ( data & 0xff ) == 0xa0 ) { m_flash_mode = FM_BYTEPROGRAM; } else if( ( address & 0xffff ) == 0x555 && ( data & 0xff ) == 0xf0 ) { m_flash_mode = FM_NORMAL; } else if( ( address & 0xffff ) == 0x5555 && ( data & 0xff ) == 0xf0 ) { m_flash_mode = FM_NORMAL; } else if( ( address & 0xfff ) == 0xaaa && ( data & 0xff ) == 0xf0 ) { m_flash_mode = FM_NORMAL; } else if( ( address & 0xffff ) == 0x5555 && ( data & 0xff ) == 0xb0 && m_maker_id == MFG_SANYO && m_device_id == 0x13 ) { m_flash_mode = FM_BANKSELECT; } // for AMD 29F080 address bits A11-A19 don't care, for TMS 29F040 address bits A15-A18 don't care else if(( address & m_addrmask ) == ( 0x5555 & m_addrmask ) && ( data & 0xff ) == 0x80 && m_addrmask ) { m_flash_mode = FM_ERASEAMD1; } else if(( address & m_addrmask ) == ( 0x5555 & m_addrmask ) && ( data & 0xff ) == 0x90 && m_addrmask ) { m_flash_mode = FM_READAMDID3; } else if(( address & m_addrmask ) == ( 0x5555 & m_addrmask ) && ( data & 0xff ) == 0xa0 && m_addrmask ) { m_flash_mode = FM_BYTEPROGRAM; } else if(( address & m_addrmask ) == ( 0x5555 & m_addrmask ) && ( data & 0xff ) == 0xf0 && m_addrmask ) { m_flash_mode = FM_NORMAL; } else { logerror( "unexpected %08x=%02x in FM_READAMDID2\n", address, data & 0xff ); m_flash_mode = FM_NORMAL; } break; case FM_ERASEAMD1: if( ( address & 0xfff ) == 0x555 && ( data & 0xff ) == 0xaa ) { m_flash_mode = FM_ERASEAMD2; } else if( ( address & 0xfff ) == 0xaaa && ( data & 0xff ) == 0xaa ) { m_flash_mode = FM_ERASEAMD2; } else { logerror( "unexpected %08x=%02x in FM_ERASEAMD1\n", address, data & 0xff ); } break; case FM_ERASEAMD2: if( ( address & 0xffff ) == 0x2aa && ( data & 0xff ) == 0x55 ) { m_flash_mode = FM_ERASEAMD3; } else if( ( address & 0xffff ) == 0x2aaa && ( data & 0xff ) == 0x55 ) { m_flash_mode = FM_ERASEAMD3; } else if( ( address & 0xfff ) == 0x555 && ( data & 0xff ) == 0x55 ) { m_flash_mode = FM_ERASEAMD3; } else { logerror( "unexpected %08x=%02x in FM_ERASEAMD2\n", address, data & 0xff ); } break; case FM_ERASEAMD3: if( (( address & 0xfff ) == 0x555 && ( data & 0xff ) == 0x10 ) || (( address & 0xfff ) == 0xaaa && ( data & 0xff ) == 0x10 ) ) { // chip erase if (m_maker_id == MFG_FUJITSU && m_device_id == 0x40) { // hardcoded for Dreamcast, TODO properly handle sector protection memset(&m_data[0], 0xff, 0x3A000); memset(&m_data[0x3C000], 0xff, 0x04000); } else memset(&m_data[0], 0xff, m_size); m_status = 1 << 3; m_flash_mode = FM_ERASEAMD4; if (m_sector_is_4k) { m_timer->adjust( attotime::from_seconds( 1 ) ); } else if(m_sector_is_16k) { m_timer->adjust( attotime::from_seconds( 4 ) ); } else { m_timer->adjust( attotime::from_seconds( 16 ) ); } } else if( ( data & 0xff ) == 0x30 ) { // sector erase // clear the 4k/64k block containing the current address to all 0xffs uint32_t base = address * ((m_bits == 16) ? 2 : 1); if (m_sector_is_4k) { memset(&m_data[base & ~0xfff], 0xff, 4 * 1024); m_erase_sector = address & ((m_bits == 16) ? ~0x7ff : ~0xfff); m_timer->adjust( attotime::from_msec( 125 ) ); } else if(m_sector_is_16k) { memset(&m_data[base & ~0x3fff], 0xff, 16 * 1024); m_erase_sector = address & ((m_bits == 16) ? ~0x1fff : ~0x3fff); m_timer->adjust( attotime::from_msec( 500 ) ); } else if(m_top_boot_sector && address >= (m_size - 64*1024)) { if (address >= (m_size - (16*1024))) { memset(&m_data[base & ~0x3fff], 0xff, 16 * 1024); m_erase_sector = address & ((m_bits == 16) ? ~0x1fff : ~0x3fff); m_timer->adjust( attotime::from_msec( 500 ) ); } else if (address >= (m_size - (32*1024))) { memset(&m_data[base & ~0x1fff], 0xff, 8 * 1024); m_erase_sector = address & ((m_bits == 16) ? ~0xfff : ~0x1fff); m_timer->adjust( attotime::from_msec( 250 ) ); } else { memset(&m_data[base & ~0x7fff], 0xff, 32 * 1024); m_erase_sector = address & ((m_bits == 16) ? ~0x3fff : ~0x7fff); m_timer->adjust( attotime::from_msec( 500 ) ); } } else if(m_bot_boot_sector && address < (64*1024)) { if (address < (16*1024)) { memset(&m_data[base & ~0x3fff], 0xff, 16 * 1024); m_erase_sector = address & ((m_bits == 16) ? ~0x1fff : ~0x3fff); m_timer->adjust( attotime::from_msec( 500 ) ); } else if (address < (32*1024)) { memset(&m_data[base & ~0x1fff], 0xff, 8 * 1024); m_erase_sector = address & ((m_bits == 16) ? ~0xfff : ~0x1fff); m_timer->adjust( attotime::from_msec( 250 ) ); } else { memset(&m_data[base & ~0x7fff], 0xff, 32 * 1024); m_erase_sector = address & ((m_bits == 16) ? ~0x3fff : ~0x7fff); m_timer->adjust( attotime::from_msec( 500 ) ); } } else if (m_maker_id == MFG_FUJITSU && m_device_id == 0x40) { constexpr u32 sectors[] = { 0x10000, 0x10000, 0x10000, 0x08000, 0x02000, 0x02000, 0x4000 }; u32 sec_num = 0; u32 toffset = base; while (toffset >= sectors[sec_num]) toffset -= sectors[sec_num++]; u32 sec_len = sectors[sec_num]; if (sec_num != 5) // hardcoded for Dreamcast, TODO properly handle sector protection { memset(&m_data[base & ~(sec_len - 1)], 0xff, sec_len); } m_erase_sector = address & ~(sec_len - 1); m_timer->adjust(attotime::from_seconds(1)); } else { memset(&m_data[base & ~0xffff], 0xff, 64 * 1024); m_erase_sector = address & ((m_bits == 16) ? ~0x7fff : ~0xffff); m_timer->adjust( attotime::from_seconds( 1 ) ); } m_status = 1 << 3; m_flash_mode = FM_ERASEAMD4; } else { logerror( "unexpected %08x=%02x in FM_ERASEAMD3\n", address, data & 0xff ); } break; case FM_BYTEPROGRAM: switch( m_bits ) { case 8: if (m_maker_id == MFG_FUJITSU && m_device_id == 0x40) { if (address < 0x3a000 || address >= 0x3c000) // hardcoded for Dreamcast, TODO properly handle sector protection m_data[address] &= data; } else m_data[address] = data; break; case 16: // senbbs test mode requires this, note, flash type is guessed there based on manufacturer + device ident as markings were erased m_data[address*2] = data >> 8; m_data[address*2+1] = data; break; default: logerror( "FM_BYTEPROGRAM not supported when m_bits == %d (address %08x data %04x)\n", m_bits, address, data ); break; } m_flash_mode = FM_NORMAL; break; case FM_WRITEPART1: switch( m_bits ) { case 8: m_data[address] = data; break; case 16: m_data[address*2] = data >> 8; m_data[address*2+1] = data; break; default: logerror( "FM_WRITEPART1 not supported when m_bits == %d\n", m_bits ); break; } m_status = 0x80; if (m_type == FLASH_SST_28SF040) m_flash_mode = FM_NORMAL; else m_flash_mode = FM_READSTATUS; break; case FM_WRITEPAGEATMEL: switch( m_bits ) { case 8: m_data[address] = data; break; case 16: m_data[address*2] = data >> 8; m_data[address*2+1] = data; break; default: logerror( "FM_WRITEPAGEATMEL not supported when m_bits == %d\n", m_bits ); break; } m_byte_count++; if (m_byte_count == m_page_size) { m_flash_mode = FM_NORMAL; } break; case FM_CLEARPART1: if( ( data & 0xff ) == 0xd0 ) { if (m_type == FLASH_SST_28SF040) { // clear the 256 bytes block containing the current address to all 0xffs uint32_t base = address * ((m_bits == 16) ? 2 : 1); memset(&m_data[base & ~0xff], 0xff, 256); m_timer->adjust( attotime::from_msec( 4 ) ); } else if (m_type == FLASH_INTEL_E28F400B) { // 00000-03fff - 16KB boot block (may be write protected via external pins) // 04000-05fff - 8KB parameter block // 06000-07fff - 8KB parameter block // 08000-1ffff - 96KB main block // 20000-3ffff - 128KB main block // 40000-5ffff - 128KB main block // 60000-7ffff - 128KB main block // erase duration is 0.3s for boot and parameter blocks, and 0.6s for main blocks uint32_t base = (address & 0x3ffff) * 2; int size, duration; if (base < 0x4000) { base = 0; size = 0x4000; duration = 300; } else if (base < 0x8000) { base &= 0x6000; size = 0x2000; duration = 300; } else if (base < 0x20000) { base = 0x8000; size = 0x18000; duration = 600; } else { base &= 0x60000; size = 0x20000; duration = 600; } // clear the block containing the current address to all 0xffffs memset(&m_data[base], 0xff, size); m_timer->adjust( attotime::from_msec( duration ) ); } else { // clear the 64k block containing the current address to all 0xffs uint32_t base = address * ((m_bits == 16) ? 2 : 1); memset(&m_data[base & ~0xffff], 0xff, 64 * 1024); m_timer->adjust( attotime::from_seconds( 1 ) ); } m_status = 0x00; m_flash_mode = FM_READSTATUS; break; } else { logerror( "unexpected %02x in FM_CLEARPART1\n", data & 0xff ); } break; case FM_SETMASTER: switch( data & 0xff ) { case 0xf1: m_flash_master_lock = true; break; case 0xd0: m_flash_master_lock = false; break; default: logerror( "unexpected %08x=%02x in FM_SETMASTER:\n", address, data & 0xff ); break; } m_flash_mode = FM_NORMAL; break; case FM_BANKSELECT: m_bank = data & 0xff; m_flash_mode = FM_NORMAL; break; } } ='n973' href='#n973'>973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153
/*
 * Copyright 2011-2018 Branimir Karadzic. All rights reserved.
 * License: https://github.com/bkaradzic/bgfx#license-bsd-2-clause
 */

#include "entry_p.h"

#if ENTRY_CONFIG_USE_SDL

#if BX_PLATFORM_WINDOWS
#	define SDL_MAIN_HANDLED
#endif // BX_PLATFORM_WINDOWS

#include <bx/os.h>

#include <SDL2/SDL.h>

BX_PRAGMA_DIAGNOSTIC_PUSH()
BX_PRAGMA_DIAGNOSTIC_IGNORED_CLANG("-Wextern-c-compat")
#include <SDL2/SDL_syswm.h>
BX_PRAGMA_DIAGNOSTIC_POP()

#include <bgfx/platform.h>
#if defined(None) // X11 defines this...
#	undef None
#endif // defined(None)

#include <bx/mutex.h>
#include <bx/thread.h>
#include <bx/handlealloc.h>
#include <bx/readerwriter.h>
#include <tinystl/allocator.h>
#include <tinystl/string.h>

namespace entry
{
	inline bool sdlSetWindow(SDL_Window* _window)
	{
		SDL_SysWMinfo wmi;
		SDL_VERSION(&wmi.version);
		if (!SDL_GetWindowWMInfo(_window, &wmi) )
		{
			return false;
		}

		bgfx::PlatformData pd;
#	if BX_PLATFORM_LINUX || BX_PLATFORM_BSD
		pd.ndt          = wmi.info.x11.display;
		pd.nwh          = (void*)(uintptr_t)wmi.info.x11.window;
#	elif BX_PLATFORM_OSX
		pd.ndt          = NULL;
		pd.nwh          = wmi.info.cocoa.window;
#	elif BX_PLATFORM_WINDOWS
		pd.ndt          = NULL;
		pd.nwh          = wmi.info.win.window;
#	elif BX_PLATFORM_STEAMLINK
		pd.ndt          = wmi.info.vivante.display;
		pd.nwh          = wmi.info.vivante.window;
#	endif // BX_PLATFORM_
		pd.context      = NULL;
		pd.backBuffer   = NULL;
		pd.backBufferDS = NULL;
		bgfx::setPlatformData(pd);

		return true;
	}

	static uint8_t translateKeyModifiers(uint16_t _sdl)
	{
		uint8_t modifiers = 0;
		modifiers |= _sdl & KMOD_LALT   ? Modifier::LeftAlt    : 0;
		modifiers |= _sdl & KMOD_RALT   ? Modifier::RightAlt   : 0;
		modifiers |= _sdl & KMOD_LCTRL  ? Modifier::LeftCtrl   : 0;
		modifiers |= _sdl & KMOD_RCTRL  ? Modifier::RightCtrl  : 0;
		modifiers |= _sdl & KMOD_LSHIFT ? Modifier::LeftShift  : 0;
		modifiers |= _sdl & KMOD_RSHIFT ? Modifier::RightShift : 0;
		modifiers |= _sdl & KMOD_LGUI   ? Modifier::LeftMeta   : 0;
		modifiers |= _sdl & KMOD_RGUI   ? Modifier::RightMeta  : 0;
		return modifiers;
	}

	static uint8_t translateKeyModifierPress(uint16_t _key)
	{
		uint8_t modifier;
		switch (_key)
		{
			case SDL_SCANCODE_LALT:   { modifier = Modifier::LeftAlt;    } break;
			case SDL_SCANCODE_RALT:   { modifier = Modifier::RightAlt;   } break;
			case SDL_SCANCODE_LCTRL:  { modifier = Modifier::LeftCtrl;   } break;
			case SDL_SCANCODE_RCTRL:  { modifier = Modifier::RightCtrl;  } break;
			case SDL_SCANCODE_LSHIFT: { modifier = Modifier::LeftShift;  } break;
			case SDL_SCANCODE_RSHIFT: { modifier = Modifier::RightShift; } break;
			case SDL_SCANCODE_LGUI:   { modifier = Modifier::LeftMeta;   } break;
			case SDL_SCANCODE_RGUI:   { modifier = Modifier::RightMeta;  } break;
			default:                  { modifier = 0;                    } break;
		}

		return modifier;
	}

	static uint8_t s_translateKey[256];

	static void initTranslateKey(uint16_t _sdl, Key::Enum _key)
	{
		BX_CHECK(_sdl < BX_COUNTOF(s_translateKey), "Out of bounds %d.", _sdl);
		s_translateKey[_sdl&0xff] = (uint8_t)_key;
	}

	static Key::Enum translateKey(SDL_Scancode _sdl)
	{
		return (Key::Enum)s_translateKey[_sdl&0xff];
	}

	static uint8_t s_translateGamepad[256];

	static void initTranslateGamepad(uint8_t _sdl, Key::Enum _button)
	{
		s_translateGamepad[_sdl] = _button;
	}

	static Key::Enum translateGamepad(uint8_t _sdl)
	{
		return Key::Enum(s_translateGamepad[_sdl]);
	}

	static uint8_t s_translateGamepadAxis[256];

	static void initTranslateGamepadAxis(uint8_t _sdl, GamepadAxis::Enum _axis)
	{
		s_translateGamepadAxis[_sdl] = uint8_t(_axis);
	}

	static GamepadAxis::Enum translateGamepadAxis(uint8_t _sdl)
	{
		return GamepadAxis::Enum(s_translateGamepadAxis[_sdl]);
	}

	struct AxisDpadRemap
	{
		Key::Enum first;
		Key::Enum second;
	};

	static AxisDpadRemap s_axisDpad[] =
	{
		{ Key::GamepadLeft, Key::GamepadRight },
		{ Key::GamepadUp,   Key::GamepadDown  },
		{ Key::None,        Key::None         },
		{ Key::GamepadLeft, Key::GamepadRight },
		{ Key::GamepadUp,   Key::GamepadDown  },
		{ Key::None,        Key::None         },
	};

	struct GamepadSDL
	{
		GamepadSDL()
			: m_controller(NULL)
			, m_jid(INT32_MAX)
		{
			bx::memSet(m_value, 0, sizeof(m_value) );

			// Deadzone values from xinput.h
			m_deadzone[GamepadAxis::LeftX ] =
			m_deadzone[GamepadAxis::LeftY ] = 7849;
			m_deadzone[GamepadAxis::RightX] =
			m_deadzone[GamepadAxis::RightY] = 8689;
			m_deadzone[GamepadAxis::LeftZ ] =
			m_deadzone[GamepadAxis::RightZ] = 30;
		}

		void create(const SDL_JoyDeviceEvent& _jev)
		{
			m_joystick = SDL_JoystickOpen(_jev.which);
			SDL_Joystick* joystick = m_joystick;
			m_jid = SDL_JoystickInstanceID(joystick);
		}

		void create(const SDL_ControllerDeviceEvent& _cev)
		{
			m_controller = SDL_GameControllerOpen(_cev.which);
			SDL_Joystick* joystick = SDL_GameControllerGetJoystick(m_controller);
			m_jid = SDL_JoystickInstanceID(joystick);
		}

		void update(EventQueue& _eventQueue, WindowHandle _handle, GamepadHandle _gamepad, GamepadAxis::Enum _axis, int32_t _value)
		{
			if (filter(_axis, &_value) )
			{
				_eventQueue.postAxisEvent(_handle, _gamepad, _axis, _value);

				if (Key::None != s_axisDpad[_axis].first)
				{
					if (_value == 0)
					{
						_eventQueue.postKeyEvent(_handle, s_axisDpad[_axis].first,  0, false);
						_eventQueue.postKeyEvent(_handle, s_axisDpad[_axis].second, 0, false);
					}
					else
					{
						_eventQueue.postKeyEvent(_handle
								, 0 > _value ? s_axisDpad[_axis].first : s_axisDpad[_axis].second
								, 0
								, true
								);
					}
				}
			}
		}

		void destroy()
		{
			if (NULL != m_controller)
			{
				SDL_GameControllerClose(m_controller);
				m_controller = NULL;
			}

			if (NULL != m_joystick)
			{
				SDL_JoystickClose(m_joystick);
				m_joystick = NULL;
			}

			m_jid = INT32_MAX;
		}

		bool filter(GamepadAxis::Enum _axis, int32_t* _value)
		{
			const int32_t old = m_value[_axis];
			const int32_t deadzone = m_deadzone[_axis];
			int32_t value = *_value;
			value = value > deadzone || value < -deadzone ? value : 0;
			m_value[_axis] = value;
			*_value = value;
			return old != value;
		}

		int32_t m_value[GamepadAxis::Count];
		int32_t m_deadzone[GamepadAxis::Count];

		SDL_Joystick*       m_joystick;
		SDL_GameController* m_controller;
//		SDL_Haptic*         m_haptic;
		SDL_JoystickID      m_jid;
	};

	struct MainThreadEntry
	{
		int m_argc;
		char** m_argv;

		static int32_t threadFunc(bx::Thread* _thread, void* _userData);
	};

	///
	static void* sdlNativeWindowHandle(SDL_Window* _window)
	{
		SDL_SysWMinfo wmi;
		SDL_VERSION(&wmi.version);
		if (!SDL_GetWindowWMInfo(_window, &wmi) )
		{
			return NULL;
		}

#	if BX_PLATFORM_LINUX || BX_PLATFORM_BSD
		return (void*)wmi.info.x11.window;
#	elif BX_PLATFORM_OSX
		return wmi.info.cocoa.window;
#	elif BX_PLATFORM_WINDOWS
		return wmi.info.win.window;
#	elif BX_PLATFORM_STEAMLINK
		return wmi.info.vivante.window;
#	endif // BX_PLATFORM_
	}

	struct Msg
	{
		Msg()
			: m_x(0)
			, m_y(0)
			, m_width(0)
			, m_height(0)
			, m_flags(0)
			, m_flagsEnabled(false)
		{
		}

		int32_t  m_x;
		int32_t  m_y;
		uint32_t m_width;
		uint32_t m_height;
		uint32_t m_flags;
		tinystl::string m_title;
		bool m_flagsEnabled;
	};

	static uint32_t s_userEventStart;

	enum SDL_USER_WINDOW
	{
		SDL_USER_WINDOW_CREATE,
		SDL_USER_WINDOW_DESTROY,
		SDL_USER_WINDOW_SET_TITLE,
		SDL_USER_WINDOW_SET_FLAGS,
		SDL_USER_WINDOW_SET_POS,
		SDL_USER_WINDOW_SET_SIZE,
		SDL_USER_WINDOW_TOGGLE_FRAME,
		SDL_USER_WINDOW_TOGGLE_FULL_SCREEN,
		SDL_USER_WINDOW_MOUSE_LOCK,
	};

	static void sdlPostEvent(SDL_USER_WINDOW _type, WindowHandle _handle, Msg* _msg = NULL, uint32_t _code = 0)
	{
		SDL_Event event;
		SDL_UserEvent& uev = event.user;
		uev.type = s_userEventStart + _type;

		union { void* p; WindowHandle h; } cast;
		cast.h = _handle;
		uev.data1 = cast.p;

		uev.data2 = _msg;
		uev.code = _code;
		SDL_PushEvent(&event);
	}

	static WindowHandle getWindowHandle(const SDL_UserEvent& _uev)
	{
		union { void* p; WindowHandle h; } cast;
		cast.p = _uev.data1;
		return cast.h;
	}

	struct Context
	{
		Context()
			: m_width(ENTRY_DEFAULT_WIDTH)
			, m_height(ENTRY_DEFAULT_HEIGHT)
			, m_aspectRatio(16.0f/9.0f)
			, m_mx(0)
			, m_my(0)
			, m_mz(0)
			, m_mouseLock(false)
			, m_fullscreen(false)
		{
			bx::memSet(s_translateKey, 0, sizeof(s_translateKey) );
			initTranslateKey(SDL_SCANCODE_ESCAPE,       Key::Esc);
			initTranslateKey(SDL_SCANCODE_RETURN,       Key::Return);
			initTranslateKey(SDL_SCANCODE_TAB,          Key::Tab);
			initTranslateKey(SDL_SCANCODE_BACKSPACE,    Key::Backspace);
			initTranslateKey(SDL_SCANCODE_SPACE,        Key::Space);
			initTranslateKey(SDL_SCANCODE_UP,           Key::Up);
			initTranslateKey(SDL_SCANCODE_DOWN,         Key::Down);
			initTranslateKey(SDL_SCANCODE_LEFT,         Key::Left);
			initTranslateKey(SDL_SCANCODE_RIGHT,        Key::Right);
			initTranslateKey(SDL_SCANCODE_PAGEUP,       Key::PageUp);
			initTranslateKey(SDL_SCANCODE_PAGEDOWN,     Key::PageDown);
			initTranslateKey(SDL_SCANCODE_HOME,         Key::Home);
			initTranslateKey(SDL_SCANCODE_END,          Key::End);
			initTranslateKey(SDL_SCANCODE_PRINTSCREEN,  Key::Print);
			initTranslateKey(SDL_SCANCODE_KP_PLUS,      Key::Plus);
			initTranslateKey(SDL_SCANCODE_EQUALS,       Key::Plus);
			initTranslateKey(SDL_SCANCODE_KP_MINUS,     Key::Minus);
			initTranslateKey(SDL_SCANCODE_MINUS,        Key::Minus);
			initTranslateKey(SDL_SCANCODE_GRAVE,        Key::Tilde);
			initTranslateKey(SDL_SCANCODE_KP_COMMA,     Key::Comma);
			initTranslateKey(SDL_SCANCODE_COMMA,        Key::Comma);
			initTranslateKey(SDL_SCANCODE_KP_PERIOD,    Key::Period);
			initTranslateKey(SDL_SCANCODE_PERIOD,       Key::Period);
			initTranslateKey(SDL_SCANCODE_SLASH,        Key::Slash);
			initTranslateKey(SDL_SCANCODE_F1,           Key::F1);
			initTranslateKey(SDL_SCANCODE_F2,           Key::F2);
			initTranslateKey(SDL_SCANCODE_F3,           Key::F3);
			initTranslateKey(SDL_SCANCODE_F4,           Key::F4);
			initTranslateKey(SDL_SCANCODE_F5,           Key::F5);
			initTranslateKey(SDL_SCANCODE_F6,           Key::F6);
			initTranslateKey(SDL_SCANCODE_F7,           Key::F7);
			initTranslateKey(SDL_SCANCODE_F8,           Key::F8);
			initTranslateKey(SDL_SCANCODE_F9,           Key::F9);
			initTranslateKey(SDL_SCANCODE_F10,          Key::F10);
			initTranslateKey(SDL_SCANCODE_F11,          Key::F11);
			initTranslateKey(SDL_SCANCODE_F12,          Key::F12);
			initTranslateKey(SDL_SCANCODE_KP_0,         Key::NumPad0);
			initTranslateKey(SDL_SCANCODE_KP_1,         Key::NumPad1);
			initTranslateKey(SDL_SCANCODE_KP_2,         Key::NumPad2);
			initTranslateKey(SDL_SCANCODE_KP_3,         Key::NumPad3);
			initTranslateKey(SDL_SCANCODE_KP_4,         Key::NumPad4);
			initTranslateKey(SDL_SCANCODE_KP_5,         Key::NumPad5);
			initTranslateKey(SDL_SCANCODE_KP_6,         Key::NumPad6);
			initTranslateKey(SDL_SCANCODE_KP_7,         Key::NumPad7);
			initTranslateKey(SDL_SCANCODE_KP_8,         Key::NumPad8);
			initTranslateKey(SDL_SCANCODE_KP_9,         Key::NumPad9);
			initTranslateKey(SDL_SCANCODE_0,            Key::Key0);
			initTranslateKey(SDL_SCANCODE_1,            Key::Key1);
			initTranslateKey(SDL_SCANCODE_2,            Key::Key2);
			initTranslateKey(SDL_SCANCODE_3,            Key::Key3);
			initTranslateKey(SDL_SCANCODE_4,            Key::Key4);
			initTranslateKey(SDL_SCANCODE_5,            Key::Key5);
			initTranslateKey(SDL_SCANCODE_6,            Key::Key6);
			initTranslateKey(SDL_SCANCODE_7,            Key::Key7);
			initTranslateKey(SDL_SCANCODE_8,            Key::Key8);
			initTranslateKey(SDL_SCANCODE_9,            Key::Key9);
			initTranslateKey(SDL_SCANCODE_A,            Key::KeyA);
			initTranslateKey(SDL_SCANCODE_B,            Key::KeyB);
			initTranslateKey(SDL_SCANCODE_C,            Key::KeyC);
			initTranslateKey(SDL_SCANCODE_D,            Key::KeyD);
			initTranslateKey(SDL_SCANCODE_E,            Key::KeyE);
			initTranslateKey(SDL_SCANCODE_F,            Key::KeyF);
			initTranslateKey(SDL_SCANCODE_G,            Key::KeyG);
			initTranslateKey(SDL_SCANCODE_H,            Key::KeyH);
			initTranslateKey(SDL_SCANCODE_I,            Key::KeyI);
			initTranslateKey(SDL_SCANCODE_J,            Key::KeyJ);
			initTranslateKey(SDL_SCANCODE_K,            Key::KeyK);
			initTranslateKey(SDL_SCANCODE_L,            Key::KeyL);
			initTranslateKey(SDL_SCANCODE_M,            Key::KeyM);
			initTranslateKey(SDL_SCANCODE_N,            Key::KeyN);
			initTranslateKey(SDL_SCANCODE_O,            Key::KeyO);
			initTranslateKey(SDL_SCANCODE_P,            Key::KeyP);
			initTranslateKey(SDL_SCANCODE_Q,            Key::KeyQ);
			initTranslateKey(SDL_SCANCODE_R,            Key::KeyR);
			initTranslateKey(SDL_SCANCODE_S,            Key::KeyS);
			initTranslateKey(SDL_SCANCODE_T,            Key::KeyT);
			initTranslateKey(SDL_SCANCODE_U,            Key::KeyU);
			initTranslateKey(SDL_SCANCODE_V,            Key::KeyV);
			initTranslateKey(SDL_SCANCODE_W,            Key::KeyW);
			initTranslateKey(SDL_SCANCODE_X,            Key::KeyX);
			initTranslateKey(SDL_SCANCODE_Y,            Key::KeyY);
			initTranslateKey(SDL_SCANCODE_Z,            Key::KeyZ);

			bx::memSet(s_translateGamepad, uint8_t(Key::Count), sizeof(s_translateGamepad) );
			initTranslateGamepad(SDL_CONTROLLER_BUTTON_A,             Key::GamepadA);
			initTranslateGamepad(SDL_CONTROLLER_BUTTON_B,             Key::GamepadB);
			initTranslateGamepad(SDL_CONTROLLER_BUTTON_X,             Key::GamepadX);
			initTranslateGamepad(SDL_CONTROLLER_BUTTON_Y,             Key::GamepadY);
			initTranslateGamepad(SDL_CONTROLLER_BUTTON_LEFTSTICK,     Key::GamepadThumbL);
			initTranslateGamepad(SDL_CONTROLLER_BUTTON_RIGHTSTICK,    Key::GamepadThumbR);
			initTranslateGamepad(SDL_CONTROLLER_BUTTON_LEFTSHOULDER,  Key::GamepadShoulderL);
			initTranslateGamepad(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, Key::GamepadShoulderR);
			initTranslateGamepad(SDL_CONTROLLER_BUTTON_DPAD_UP,       Key::GamepadUp);
			initTranslateGamepad(SDL_CONTROLLER_BUTTON_DPAD_DOWN,     Key::GamepadDown);
			initTranslateGamepad(SDL_CONTROLLER_BUTTON_DPAD_LEFT,     Key::GamepadLeft);
			initTranslateGamepad(SDL_CONTROLLER_BUTTON_DPAD_RIGHT,    Key::GamepadRight);
			initTranslateGamepad(SDL_CONTROLLER_BUTTON_BACK,          Key::GamepadBack);
			initTranslateGamepad(SDL_CONTROLLER_BUTTON_START,         Key::GamepadStart);
			initTranslateGamepad(SDL_CONTROLLER_BUTTON_GUIDE,         Key::GamepadGuide);

			bx::memSet(s_translateGamepadAxis, uint8_t(GamepadAxis::Count), sizeof(s_translateGamepadAxis) );
			initTranslateGamepadAxis(SDL_CONTROLLER_AXIS_LEFTX,        GamepadAxis::LeftX);
			initTranslateGamepadAxis(SDL_CONTROLLER_AXIS_LEFTY,        GamepadAxis::LeftY);
			initTranslateGamepadAxis(SDL_CONTROLLER_AXIS_TRIGGERLEFT,  GamepadAxis::LeftZ);
			initTranslateGamepadAxis(SDL_CONTROLLER_AXIS_RIGHTX,       GamepadAxis::RightX);
			initTranslateGamepadAxis(SDL_CONTROLLER_AXIS_RIGHTY,       GamepadAxis::RightY);
			initTranslateGamepadAxis(SDL_CONTROLLER_AXIS_TRIGGERRIGHT, GamepadAxis::RightZ);
		}

		int run(int _argc, char** _argv)
		{
			m_mte.m_argc = _argc;
			m_mte.m_argv = _argv;

			SDL_Init(0
				| SDL_INIT_GAMECONTROLLER
				);

			m_windowAlloc.alloc();
			m_window[0] = SDL_CreateWindow("bgfx"
							, SDL_WINDOWPOS_UNDEFINED
							, SDL_WINDOWPOS_UNDEFINED
							, m_width
							, m_height
							, SDL_WINDOW_SHOWN
							| SDL_WINDOW_RESIZABLE
							);

			m_flags[0] = 0
				| ENTRY_WINDOW_FLAG_ASPECT_RATIO
				| ENTRY_WINDOW_FLAG_FRAME
				;

			s_userEventStart = SDL_RegisterEvents(7);

			sdlSetWindow(m_window[0]);
			bgfx::renderFrame();

			m_thread.init(MainThreadEntry::threadFunc, &m_mte);

			// Force window resolution...
			WindowHandle defaultWindow = { 0 };
			setWindowSize(defaultWindow, m_width, m_height, true);

			SDL_EventState(SDL_DROPFILE, SDL_ENABLE);

			bx::FileReaderI* reader = NULL;
			while (NULL == reader)
			{
				reader = getFileReader();
				bx::sleep(100);
			}

			if (bx::open(reader, "gamecontrollerdb.txt") )
			{
				bx::AllocatorI* allocator = getAllocator();
				uint32_t size = (uint32_t)bx::getSize(reader);
				void* data = BX_ALLOC(allocator, size + 1);
				bx::read(reader, data, size);
				bx::close(reader);
				((char*)data)[size] = '\0';

				if (SDL_GameControllerAddMapping( (char*)data) < 0) {
					DBG("SDL game controller add mapping failed: %s", SDL_GetError());
				}

				BX_FREE(allocator, data);
			}

			bool exit = false;
			SDL_Event event;
			while (!exit)
			{
				bgfx::renderFrame();

				while (SDL_PollEvent(&event) )
				{
					switch (event.type)
					{
					case SDL_QUIT:
						m_eventQueue.postExitEvent();
						exit = true;
						break;

					case SDL_MOUSEMOTION:
						{
							const SDL_MouseMotionEvent& mev = event.motion;
							m_mx = mev.x;
							m_my = mev.y;

							WindowHandle handle = findHandle(mev.windowID);
							if (isValid(handle) )
							{
								m_eventQueue.postMouseEvent(handle, m_mx, m_my, m_mz);
							}
						}
						break;

					case SDL_MOUSEBUTTONDOWN:
					case SDL_MOUSEBUTTONUP:
						{
							const SDL_MouseButtonEvent& mev = event.button;
							WindowHandle handle = findHandle(mev.windowID);
							if (isValid(handle) )
							{
								MouseButton::Enum button;
								switch (mev.button)
								{
								default:
								case SDL_BUTTON_LEFT:   button = MouseButton::Left;   break;
								case SDL_BUTTON_MIDDLE: button = MouseButton::Middle; break;
								case SDL_BUTTON_RIGHT:  button = MouseButton::Right;  break;
								}

								m_eventQueue.postMouseEvent(handle
									, mev.x
									, mev.y
									, m_mz
									, button
									, mev.type == SDL_MOUSEBUTTONDOWN
									);
							}
						}
						break;

					case SDL_MOUSEWHEEL:
						{
							const SDL_MouseWheelEvent& mev = event.wheel;
							m_mz += mev.y;

							WindowHandle handle = findHandle(mev.windowID);
							if (isValid(handle) )
							{
								m_eventQueue.postMouseEvent(handle, m_mx, m_my, m_mz);
							}
						}
						break;

					case SDL_TEXTINPUT:
						{
							const SDL_TextInputEvent& tev = event.text;
							WindowHandle handle = findHandle(tev.windowID);
							if (isValid(handle) )
							{
								m_eventQueue.postCharEvent(handle, 1, (const uint8_t*)tev.text);
							}
						}
						break;

					case SDL_KEYDOWN:
						{
							const SDL_KeyboardEvent& kev = event.key;
							WindowHandle handle = findHandle(kev.windowID);
							if (isValid(handle) )
							{
								uint8_t modifiers = translateKeyModifiers(kev.keysym.mod);
								Key::Enum key = translateKey(kev.keysym.scancode);

#if 0
								DBG("SDL scancode %d, key %d, name %s, key name %s"
									, kev.keysym.scancode
									, key
									, SDL_GetScancodeName(kev.keysym.scancode)
									, SDL_GetKeyName(kev.keysym.scancode)
									);
#endif // 0

								/// If you only press (e.g.) 'shift' and nothing else, then key == 'shift', modifier == 0.
								/// Further along, pressing 'shift' + 'ctrl' would be: key == 'shift', modifier == 'ctrl.
								if (0 == key && 0 == modifiers)
								{
									modifiers = translateKeyModifierPress(kev.keysym.scancode);
								}

								if (Key::Esc == key)
								{
									uint8_t pressedChar[4];
									pressedChar[0] = 0x1b;
									m_eventQueue.postCharEvent(handle, 1, pressedChar);
								}
								else if (Key::Return == key)
								{
									uint8_t pressedChar[4];
									pressedChar[0] = 0x0d;
									m_eventQueue.postCharEvent(handle, 1, pressedChar);
								}
								else if (Key::Backspace == key)
								{
									uint8_t pressedChar[4];
									pressedChar[0] = 0x08;
									m_eventQueue.postCharEvent(handle, 1, pressedChar);
								}

								m_eventQueue.postKeyEvent(handle, key, modifiers, kev.state == SDL_PRESSED);
							}
						}
						break;

					case SDL_KEYUP:
						{
							const SDL_KeyboardEvent& kev = event.key;
							WindowHandle handle = findHandle(kev.windowID);
							if (isValid(handle) )
							{
								uint8_t modifiers = translateKeyModifiers(kev.keysym.mod);
								Key::Enum key = translateKey(kev.keysym.scancode);
								m_eventQueue.postKeyEvent(handle, key, modifiers, kev.state == SDL_PRESSED);
							}
						}
						break;

					case SDL_WINDOWEVENT:
						{
							const SDL_WindowEvent& wev = event.window;
							switch (wev.event)
							{
							case SDL_WINDOWEVENT_RESIZED:
							case SDL_WINDOWEVENT_SIZE_CHANGED:
								{
									WindowHandle handle = findHandle(wev.windowID);
									setWindowSize(handle, wev.data1, wev.data2);
								}
								break;

							case SDL_WINDOWEVENT_SHOWN:
							case SDL_WINDOWEVENT_HIDDEN:
							case SDL_WINDOWEVENT_EXPOSED:
							case SDL_WINDOWEVENT_MOVED:
							case SDL_WINDOWEVENT_MINIMIZED:
							case SDL_WINDOWEVENT_MAXIMIZED:
							case SDL_WINDOWEVENT_RESTORED:
							case SDL_WINDOWEVENT_ENTER:
							case SDL_WINDOWEVENT_LEAVE:
							case SDL_WINDOWEVENT_FOCUS_GAINED:
							case SDL_WINDOWEVENT_FOCUS_LOST:
								break;

							case SDL_WINDOWEVENT_CLOSE:
								{
									WindowHandle handle = findHandle(wev.windowID);
									if (0 == handle.idx)
									{
										m_eventQueue.postExitEvent();
										exit = true;
									}
								}
								break;
							}
						}
						break;

					case SDL_JOYAXISMOTION:
						{
							const SDL_JoyAxisEvent& jev = event.jaxis;
							GamepadHandle handle = findGamepad(jev.which);
							if (isValid(handle) )
							{
								GamepadAxis::Enum axis = translateGamepadAxis(jev.axis);
								m_gamepad[handle.idx].update(m_eventQueue, defaultWindow, handle, axis, jev.value);
							}
						}
						break;

					case SDL_CONTROLLERAXISMOTION:
						{
							const SDL_ControllerAxisEvent& aev = event.caxis;
							GamepadHandle handle = findGamepad(aev.which);
							if (isValid(handle) )
							{
								GamepadAxis::Enum axis = translateGamepadAxis(aev.axis);
								m_gamepad[handle.idx].update(m_eventQueue, defaultWindow, handle, axis, aev.value);
							}
						}
						break;

					case SDL_JOYBUTTONDOWN:
					case SDL_JOYBUTTONUP:
						{
							const SDL_JoyButtonEvent& bev = event.jbutton;
							GamepadHandle handle = findGamepad(bev.which);

							if (isValid(handle) )
							{
								Key::Enum key = translateGamepad(bev.button);
								if (Key::Count != key)
								{
									m_eventQueue.postKeyEvent(defaultWindow, key, 0, event.type == SDL_JOYBUTTONDOWN);
								}
							}
						}
						break;

					case SDL_CONTROLLERBUTTONDOWN:
					case SDL_CONTROLLERBUTTONUP:
						{
							const SDL_ControllerButtonEvent& bev = event.cbutton;
							GamepadHandle handle = findGamepad(bev.which);
							if (isValid(handle) )
							{
								Key::Enum key = translateGamepad(bev.button);
								if (Key::Count != key)
								{
									m_eventQueue.postKeyEvent(defaultWindow, key, 0, event.type == SDL_CONTROLLERBUTTONDOWN);
								}
							}
						}
						break;

					case SDL_JOYDEVICEADDED:
						{
							GamepadHandle handle = { m_gamepadAlloc.alloc() };
							if (isValid(handle) )
							{
								const SDL_JoyDeviceEvent& jev = event.jdevice;
								m_gamepad[handle.idx].create(jev);
								m_eventQueue.postGamepadEvent(defaultWindow, handle, true);
							}
						}
						break;

					case SDL_JOYDEVICEREMOVED:
						{
							const SDL_JoyDeviceEvent& jev = event.jdevice;
							GamepadHandle handle = findGamepad(jev.which);
							if (isValid(handle) )
							{
								m_gamepad[handle.idx].destroy();
								m_gamepadAlloc.free(handle.idx);
								m_eventQueue.postGamepadEvent(defaultWindow, handle, false);
							}
						}
						break;

					case SDL_CONTROLLERDEVICEADDED:
						{
							GamepadHandle handle = { m_gamepadAlloc.alloc() };
							if (isValid(handle) )
							{
								const SDL_ControllerDeviceEvent& cev = event.cdevice;
								m_gamepad[handle.idx].create(cev);
								m_eventQueue.postGamepadEvent(defaultWindow, handle, true);
							}
						}
						break;

					case SDL_CONTROLLERDEVICEREMAPPED:
						{

						}
						break;

					case SDL_CONTROLLERDEVICEREMOVED:
						{
							const SDL_ControllerDeviceEvent& cev = event.cdevice;
							GamepadHandle handle = findGamepad(cev.which);
							if (isValid(handle) )
							{
								m_gamepad[handle.idx].destroy();
								m_gamepadAlloc.free(handle.idx);
								m_eventQueue.postGamepadEvent(defaultWindow, handle, false);
							}
						}
						break;

					case SDL_DROPFILE:
						{
							const SDL_DropEvent& dev = event.drop;
							WindowHandle handle = defaultWindow; //findHandle(dev.windowID);
							if (isValid(handle) )
							{
								m_eventQueue.postDropFileEvent(handle, dev.file);
								SDL_free(dev.file);
							}
						}
						break;

					default:
						{
							const SDL_UserEvent& uev = event.user;
							switch (uev.type - s_userEventStart)
							{
							case SDL_USER_WINDOW_CREATE:
								{
									WindowHandle handle = getWindowHandle(uev);
									Msg* msg = (Msg*)uev.data2;

									m_window[handle.idx] = SDL_CreateWindow(msg->m_title.c_str()
										, msg->m_x
										, msg->m_y
										, msg->m_width
										, msg->m_height
										, SDL_WINDOW_SHOWN
										| SDL_WINDOW_RESIZABLE
										);

									m_flags[handle.idx] = msg->m_flags;

									void* nwh = sdlNativeWindowHandle(m_window[handle.idx]);
									if (NULL != nwh)
									{
										m_eventQueue.postSizeEvent(handle, msg->m_width, msg->m_height);
										m_eventQueue.postWindowEvent(handle, nwh);
									}

									delete msg;
								}
								break;

							case SDL_USER_WINDOW_DESTROY:
								{
									WindowHandle handle = getWindowHandle(uev);
									if (isValid(handle) )
									{
										m_eventQueue.postWindowEvent(handle);
										SDL_DestroyWindow(m_window[handle.idx]);
										m_window[handle.idx] = NULL;
									}
								}
								break;

							case SDL_USER_WINDOW_SET_TITLE:
								{
									WindowHandle handle = getWindowHandle(uev);
									Msg* msg = (Msg*)uev.data2;
									if (isValid(handle) )
									{
										SDL_SetWindowTitle(m_window[handle.idx], msg->m_title.c_str() );
									}
									delete msg;
								}
								break;

							case SDL_USER_WINDOW_SET_FLAGS:
								{
									WindowHandle handle = getWindowHandle(uev);
									Msg* msg = (Msg*)uev.data2;

									if (msg->m_flagsEnabled)
									{
										m_flags[handle.idx] |= msg->m_flags;
									}
									else
									{
										m_flags[handle.idx] &= ~msg->m_flags;
									}

									delete msg;
								}
								break;

							case SDL_USER_WINDOW_SET_POS:
								{
									WindowHandle handle = getWindowHandle(uev);
									Msg* msg = (Msg*)uev.data2;
									SDL_SetWindowPosition(m_window[handle.idx], msg->m_x, msg->m_y);
									delete msg;
								}
								break;

							case SDL_USER_WINDOW_SET_SIZE:
								{
									WindowHandle handle = getWindowHandle(uev);
									Msg* msg = (Msg*)uev.data2;
									if (isValid(handle) )
									{
										setWindowSize(handle, msg->m_width, msg->m_height);
									}
									delete msg;
								}
								break;

							case SDL_USER_WINDOW_TOGGLE_FRAME:
								{
									WindowHandle handle = getWindowHandle(uev);
									if (isValid(handle) )
									{
										m_flags[handle.idx] ^= ENTRY_WINDOW_FLAG_FRAME;
										SDL_SetWindowBordered(m_window[handle.idx], (SDL_bool)!!(m_flags[handle.idx] & ENTRY_WINDOW_FLAG_FRAME) );
									}
								}
								break;

							case SDL_USER_WINDOW_TOGGLE_FULL_SCREEN:
								{
									WindowHandle handle = getWindowHandle(uev);
									m_fullscreen = !m_fullscreen;
									SDL_SetWindowFullscreen(m_window[handle.idx], m_fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
								}
								break;

							case SDL_USER_WINDOW_MOUSE_LOCK:
								{
									SDL_SetRelativeMouseMode(!!uev.code ? SDL_TRUE : SDL_FALSE);
								}
								break;

							default:
								break;
							}
						}
						break;
					}
				}
			}

			while (bgfx::RenderFrame::NoContext != bgfx::renderFrame() ) {};
			m_thread.shutdown();

			SDL_DestroyWindow(m_window[0]);
			SDL_Quit();

			return m_thread.getExitCode();
		}

		WindowHandle findHandle(uint32_t _windowId)
		{
			SDL_Window* window = SDL_GetWindowFromID(_windowId);
			return findHandle(window);
		}

		WindowHandle findHandle(SDL_Window* _window)
		{
			bx::MutexScope scope(m_lock);
			for (uint32_t ii = 0, num = m_windowAlloc.getNumHandles(); ii < num; ++ii)
			{
				uint16_t idx = m_windowAlloc.getHandleAt(ii);
				if (_window == m_window[idx])
				{
					WindowHandle handle = { idx };
					return handle;
				}
			}

			WindowHandle invalid = { UINT16_MAX };
			return invalid;
		}

		void setWindowSize(WindowHandle _handle, uint32_t _width, uint32_t _height, bool _force = false)
		{
			if (_width  != m_width
			||  _height != m_height
			||  _force)
			{
				m_width  = _width;
				m_height = _height;

				SDL_SetWindowSize(m_window[_handle.idx], m_width, m_height);
				m_eventQueue.postSizeEvent(_handle, m_width, m_height);
			}
		}

		GamepadHandle findGamepad(SDL_JoystickID _jid)
		{
			for (uint32_t ii = 0, num = m_gamepadAlloc.getNumHandles(); ii < num; ++ii)
			{
				uint16_t idx = m_gamepadAlloc.getHandleAt(ii);
				if (_jid == m_gamepad[idx].m_jid)
				{
					GamepadHandle handle = { idx };
					return handle;
				}
			}

			GamepadHandle invalid = { UINT16_MAX };
			return invalid;
		}

		MainThreadEntry m_mte;
		bx::Thread m_thread;

		EventQueue m_eventQueue;
		bx::Mutex m_lock;

		bx::HandleAllocT<ENTRY_CONFIG_MAX_WINDOWS> m_windowAlloc;
		SDL_Window* m_window[ENTRY_CONFIG_MAX_WINDOWS];
		uint32_t m_flags[ENTRY_CONFIG_MAX_WINDOWS];

		bx::HandleAllocT<ENTRY_CONFIG_MAX_GAMEPADS> m_gamepadAlloc;
		GamepadSDL m_gamepad[ENTRY_CONFIG_MAX_GAMEPADS];

		uint32_t m_width;
		uint32_t m_height;
		float m_aspectRatio;

		int32_t m_mx;
		int32_t m_my;
		int32_t m_mz;
		bool m_mouseLock;
		bool m_fullscreen;
	};

	static Context s_ctx;

	const Event* poll()
	{
		return s_ctx.m_eventQueue.poll();
	}

	const Event* poll(WindowHandle _handle)
	{
		return s_ctx.m_eventQueue.poll(_handle);
	}

	void release(const Event* _event)
	{
		s_ctx.m_eventQueue.release(_event);
	}

	WindowHandle createWindow(int32_t _x, int32_t _y, uint32_t _width, uint32_t _height, uint32_t _flags, const char* _title)
	{
		bx::MutexScope scope(s_ctx.m_lock);
		WindowHandle handle = { s_ctx.m_windowAlloc.alloc() };

		if (UINT16_MAX != handle.idx)
		{
			Msg* msg = new Msg;
			msg->m_x      = _x;
			msg->m_y      = _y;
			msg->m_width  = _width;
			msg->m_height = _height;
			msg->m_title  = _title;
			msg->m_flags  = _flags;

			sdlPostEvent(SDL_USER_WINDOW_CREATE, handle, msg);
		}

		return handle;
	}

	void destroyWindow(WindowHandle _handle)
	{
		if (UINT16_MAX != _handle.idx)
		{
			sdlPostEvent(SDL_USER_WINDOW_DESTROY, _handle);

			bx::MutexScope scope(s_ctx.m_lock);
			s_ctx.m_windowAlloc.free(_handle.idx);
		}
	}

	void setWindowPos(WindowHandle _handle, int32_t _x, int32_t _y)
	{
		Msg* msg = new Msg;
		msg->m_x = _x;
		msg->m_y = _y;

		sdlPostEvent(SDL_USER_WINDOW_SET_POS, _handle, msg);
	}

	void setWindowSize(WindowHandle _handle, uint32_t _width, uint32_t _height)
	{
		Msg* msg = new Msg;
		msg->m_width  = _width;
		msg->m_height = _height;

		sdlPostEvent(SDL_USER_WINDOW_SET_SIZE, _handle, msg);
	}

	void setWindowTitle(WindowHandle _handle, const char* _title)
	{
		Msg* msg = new Msg;
		msg->m_title = _title;

		sdlPostEvent(SDL_USER_WINDOW_SET_TITLE, _handle, msg);
	}

	void setWindowFlags(WindowHandle _handle, uint32_t _flags, bool _enabled)
	{
		Msg* msg = new Msg;
		msg->m_flags = _flags;
		msg->m_flagsEnabled = _enabled;
		sdlPostEvent(SDL_USER_WINDOW_SET_FLAGS, _handle, msg);
	}

	void toggleFullscreen(WindowHandle _handle)
	{
		sdlPostEvent(SDL_USER_WINDOW_TOGGLE_FULL_SCREEN, _handle);
	}

	void setMouseLock(WindowHandle _handle, bool _lock)
	{
		sdlPostEvent(SDL_USER_WINDOW_MOUSE_LOCK, _handle, NULL, _lock);
	}

	int32_t MainThreadEntry::threadFunc(bx::Thread* _thread, void* _userData)
	{
		BX_UNUSED(_thread);

		MainThreadEntry* self = (MainThreadEntry*)_userData;
		int32_t result = main(self->m_argc, self->m_argv);

		SDL_Event event;
		SDL_QuitEvent& qev = event.quit;
		qev.type = SDL_QUIT;
		SDL_PushEvent(&event);
		return result;
	}

} // namespace entry

int main(int _argc, char** _argv)
{
	using namespace entry;
	return s_ctx.run(_argc, _argv);
}

#endif // ENTRY_CONFIG_USE_SDL