/*H*************************************************************************************************

    \File    netconnwin.c

    \Description
        Provides network setup and teardown support. Does not actually create any
        kind of network connections (see NetPlay).

    \Copyright
        Copyright  (c) 2001-2010 Electronic Arts Inc.

    \Version 03/12/2001 (gschaefer) First version
    \Version 12/16/2001 (sbevan)    Edited config file hack in NetConnDevStart
    \Version 06/23/2009 (mclouatre) _NetConFindAdapter() changed to _NetConnFindMacAddress()

*************************************************************************************************H*/

/*** Include files ********************************************************************************/

#define WIN32_LEAN_AND_MEAN 1           // avoid extra stuff

#pragma warning(push,0)
#include <windows.h>
#pragma warning(pop)

#include <winsock2.h>
#include <iphlpapi.h>        // for GetAdaptersAddresses()
#include <iptypes.h>         // for PIP_ADAPTER_ADDRESSES
#include <stdlib.h>          // for strtol()

#include "DirtySDK/dirtysock.h"
#include "DirtySDK/dirtyvers.h"
#include "DirtySDK/dirtysock/dirtymem.h"
#include "DirtySDK/dirtysock/dirtycert.h"
#include "DirtySDK/dirtylang.h"      // for locality macros and definitions
#include "DirtySDK/dirtysock/netconn.h"
#include "DirtySDK/proto/protossl.h"
#include "DirtySDK/proto/protoupnp.h"
#include "netconncommon.h"

/*** Defines **************************************************************************************/

// LOCALE_SNAME: defined in WinNls.h (WINVER >= 0x0600). This block is just there for the case where the define is absent.
#ifndef LOCALE_SNAME
#define LOCALE_SNAME                    0x0000005c   // locale name (ie: en-us)
#endif

//! GetGeoInfo returns XX for unknown country
#define NETCONNWIN_COUNTRY_UNKNOWN_STR  ("XX")

//! UPNP port
#define NETCONN_DEFAULT_UPNP_PORT       (3659)

/*** Macros ***************************************************************************************/

/*** Type Definitions *****************************************************************************/

//! private module state
typedef struct NetConnRefT
{
    NetConnCommonRefT Common;   //!< cross-platform netconn data (must come first!)
    
    enum
    {
        ST_INIT,
        ST_CONN,
        ST_IDLE,
    } eState;

    int32_t iPeerPort;          //!< peer port to be opened by upnp; if zero, still find upnp router but don't open a port
    ProtoUpnpRefT *pProtoUpnp;  //!< protoupnp module state
    int32_t iThreadCpuAffinity; //!< cpu affinity we use for our internal threads
} NetConnRefT;

/*** Function Prototypes **************************************************************************/

/*** Variables ************************************************************************************/

//! mapping table to map Windows Language IDs to DirtySock encodings
/*
    Language Identifier Constants and Strings: http://msdn.microsoft.com/en-us/library/dd318693(v=vs.85).aspx

1.  In order to reduce the size of the mapping table:
    if locality(Primary Id, SUBLANG_NEUTRAL) == locality(Primary Id, SUBLANG_DEFAULT),
    then only locality(Primary Id, SUBLANG_DEFAULT) is included by the table.
    That means if we can't find locality(Primary Id, SUBLANG_NEUTRAL) in the table,
    we should search the table again with langid(Primary Id, SUBLANG_DEFAULT).
    Defined in WinNT.h:
    #define SUBLANG_NEUTRAL                             0x00    // language neutral
    #define SUBLANG_DEFAULT                             0x01    // user default
    #define SUBLANG_ARABIC_SAUDI_ARABIA                 0x01    // Arabic (Saudi Arabia)
    // normally, specific sublangs (ex. SUBLANG_ARABIC_SAUDI_ARABIA) are used by the table instead of using SUBLANG_DEFAULT.

    Another reason why we don't define all locality(Primary Id, SUBLANG_NEUTRAL) is:
    Normally Windows API returns langid(Primary Id, specific_sublang) instead of langid(Primary Id, SUBLANG_NEUTRAL).

2.  The reason why we use a mapping table is because Windows API GetLocaleInfo(LOCALE_SNAME) is only available under Vista (or later).

3.  The result of GetLocaleInfo(LOCALE_SNAME) will be used to verify if the mapping table is good by _NetConnVerifyAllLocales.
    (debug version only, Vista / Windows7 only)

4.  NetConnStatus('locl', 0, NULL, 0) can be used to obtain locality for local system.
    The return value of NetConnStatus('locl', 0, NULL, 0) is similar to the return value of "locl" on the XBOX 360, ex. 'enUS'.
*/
static uint16_t _NetConn_WindowsLocaleMap[214][3] =
{
    { MAKELANGID(LANG_AFRIKAANS, SUBLANG_AFRIKAANS_SOUTH_AFRICA), LOBBYAPI_LANGUAGE_AFRIKAANS, LOBBYAPI_COUNTRY_SOUTH_AFRICA },
    { MAKELANGID(LANG_ALBANIAN, SUBLANG_ALBANIAN_ALBANIA), LOBBYAPI_LANGUAGE_ALBANIAN, LOBBYAPI_COUNTRY_ALBANIA },
    { MAKELANGID(LANG_AMHARIC, SUBLANG_AMHARIC_ETHIOPIA), LOBBYAPI_LANGUAGE_AMHARIC, LOBBYAPI_COUNTRY_ETHIOPIA },
    { MAKELANGID(LANG_ARABIC, SUBLANG_ARABIC_ALGERIA), LOBBYAPI_LANGUAGE_ARABIC, LOBBYAPI_COUNTRY_ALGERIA },
    { MAKELANGID(LANG_ARABIC, SUBLANG_ARABIC_BAHRAIN), LOBBYAPI_LANGUAGE_ARABIC, LOBBYAPI_COUNTRY_BAHRAIN },
    { MAKELANGID(LANG_ARABIC, SUBLANG_ARABIC_EGYPT), LOBBYAPI_LANGUAGE_ARABIC, LOBBYAPI_COUNTRY_EGYPT },
    { MAKELANGID(LANG_ARABIC, SUBLANG_ARABIC_IRAQ), LOBBYAPI_LANGUAGE_ARABIC, LOBBYAPI_COUNTRY_IRAQ },
    { MAKELANGID(LANG_ARABIC, SUBLANG_ARABIC_JORDAN), LOBBYAPI_LANGUAGE_ARABIC, LOBBYAPI_COUNTRY_JORDAN },
    { MAKELANGID(LANG_ARABIC, SUBLANG_ARABIC_KUWAIT), LOBBYAPI_LANGUAGE_ARABIC, LOBBYAPI_COUNTRY_KUWAIT },
    { MAKELANGID(LANG_ARABIC, SUBLANG_ARABIC_LEBANON), LOBBYAPI_LANGUAGE_ARABIC, LOBBYAPI_COUNTRY_LEBANON },
    { MAKELANGID(LANG_ARABIC, SUBLANG_ARABIC_LIBYA), LOBBYAPI_LANGUAGE_ARABIC, LOBBYAPI_COUNTRY_LIBYAN_ARAB_JAMAHIRIYA },
    { MAKELANGID(LANG_ARABIC, SUBLANG_ARABIC_MOROCCO), LOBBYAPI_LANGUAGE_ARABIC, LOBBYAPI_COUNTRY_MOROCCO },
    { MAKELANGID(LANG_ARABIC, SUBLANG_ARABIC_OMAN), LOBBYAPI_LANGUAGE_ARABIC, LOBBYAPI_COUNTRY_OMAN },
    { MAKELANGID(LANG_ARABIC, SUBLANG_ARABIC_QATAR), LOBBYAPI_LANGUAGE_ARABIC, LOBBYAPI_COUNTRY_QATAR },
    { MAKELANGID(LANG_ARABIC, SUBLANG_ARABIC_SAUDI_ARABIA), LOBBYAPI_LANGUAGE_ARABIC, LOBBYAPI_COUNTRY_SAUDI_ARABIA },
    { MAKELANGID(LANG_ARABIC, SUBLANG_ARABIC_SYRIA), LOBBYAPI_LANGUAGE_ARABIC, LOBBYAPI_COUNTRY_SYRIAN_ARAB_REPUBLIC },
    { MAKELANGID(LANG_ARABIC, SUBLANG_ARABIC_TUNISIA), LOBBYAPI_LANGUAGE_ARABIC, LOBBYAPI_COUNTRY_TUNISIA },
    { MAKELANGID(LANG_ARABIC, SUBLANG_ARABIC_UAE), LOBBYAPI_LANGUAGE_ARABIC, LOBBYAPI_COUNTRY_UNITED_ARAB_EMIRATES },
    { MAKELANGID(LANG_ARABIC, SUBLANG_ARABIC_YEMEN), LOBBYAPI_LANGUAGE_ARABIC, LOBBYAPI_COUNTRY_YEMEN },
    { MAKELANGID(LANG_ARMENIAN, SUBLANG_ARMENIAN_ARMENIA), LOBBYAPI_LANGUAGE_ARMENIAN, LOBBYAPI_COUNTRY_ARMENIA },
    { MAKELANGID(LANG_ASSAMESE, SUBLANG_ASSAMESE_INDIA), LOBBYAPI_LANGUAGE_ASSAMESE, LOBBYAPI_COUNTRY_INDIA },
    { MAKELANGID(LANG_AZERI, SUBLANG_AZERI_CYRILLIC), LOBBYAPI_LANGUAGE_AZERBAIJANI, LOBBYAPI_COUNTRY_AZERBAIJAN },
    { MAKELANGID(LANG_AZERI, SUBLANG_AZERI_LATIN), LOBBYAPI_LANGUAGE_AZERBAIJANI, LOBBYAPI_COUNTRY_AZERBAIJAN },
    { MAKELANGID(LANG_BASHKIR, SUBLANG_BASHKIR_RUSSIA), LOBBYAPI_LANGUAGE_BASHKIR, LOBBYAPI_COUNTRY_RUSSIAN_FEDERATION },
    { MAKELANGID(LANG_BASQUE, SUBLANG_BASQUE_BASQUE), LOBBYAPI_LANGUAGE_BASQUE, LOBBYAPI_COUNTRY_SPAIN },
    { MAKELANGID(LANG_BELARUSIAN, SUBLANG_BELARUSIAN_BELARUS), LOBBYAPI_LANGUAGE_BYELORUSSIAN, LOBBYAPI_COUNTRY_BELARUS },
    { MAKELANGID(LANG_BENGALI, SUBLANG_BENGALI_BANGLADESH), LOBBYAPI_LANGUAGE_BENGALI, LOBBYAPI_COUNTRY_BANGLADESH },
    { MAKELANGID(LANG_BENGALI, SUBLANG_BENGALI_INDIA), LOBBYAPI_LANGUAGE_BENGALI, LOBBYAPI_COUNTRY_INDIA },
    { MAKELANGID(LANG_BOSNIAN, SUBLANG_BOSNIAN_BOSNIA_HERZEGOVINA_CYRILLIC), LOBBYAPI_LANGUAGE_BOSNIAN, LOBBYAPI_COUNTRY_BOSNIA_HERZEGOVINA },
    { MAKELANGID(LANG_BOSNIAN, SUBLANG_BOSNIAN_BOSNIA_HERZEGOVINA_LATIN), LOBBYAPI_LANGUAGE_BOSNIAN, LOBBYAPI_COUNTRY_BOSNIA_HERZEGOVINA },
    { MAKELANGID(LANG_BRETON, SUBLANG_BRETON_FRANCE), LOBBYAPI_LANGUAGE_BRETON, LOBBYAPI_COUNTRY_FRANCE },
    { MAKELANGID(LANG_BULGARIAN, SUBLANG_BULGARIAN_BULGARIA), LOBBYAPI_LANGUAGE_BULGARIAN, LOBBYAPI_COUNTRY_BULGARIA },
    { MAKELANGID(LANG_CATALAN, SUBLANG_CATALAN_CATALAN), LOBBYAPI_LANGUAGE_CATALAN, LOBBYAPI_COUNTRY_SPAIN },
    { MAKELANGID(LANG_CHINESE, SUBLANG_NEUTRAL), LOBBYAPI_LANGUAGE_CHINESE, LOBBYAPI_COUNTRY_CHINA },
    { MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_HONGKONG), LOBBYAPI_LANGUAGE_CHINESE, LOBBYAPI_COUNTRY_HONG_KONG },
    { MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_MACAU), LOBBYAPI_LANGUAGE_CHINESE, LOBBYAPI_COUNTRY_MACAU },
    { MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_SINGAPORE), LOBBYAPI_LANGUAGE_CHINESE, LOBBYAPI_COUNTRY_SINGAPORE },
    { MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED), LOBBYAPI_LANGUAGE_CHINESE, LOBBYAPI_COUNTRY_CHINA },
    { MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL), LOBBYAPI_LANGUAGE_CHINESE, LOBBYAPI_COUNTRY_TAIWAN },
    { MAKELANGID(LANG_CORSICAN, SUBLANG_CORSICAN_FRANCE), LOBBYAPI_LANGUAGE_CORSICAN, LOBBYAPI_COUNTRY_FRANCE },
    { MAKELANGID(LANG_CROATIAN, SUBLANG_CROATIAN_BOSNIA_HERZEGOVINA_LATIN), LOBBYAPI_LANGUAGE_CROATIAN, LOBBYAPI_COUNTRY_BOSNIA_HERZEGOVINA },
    { MAKELANGID(LANG_CROATIAN, SUBLANG_CROATIAN_CROATIA), LOBBYAPI_LANGUAGE_CROATIAN, LOBBYAPI_COUNTRY_CROATIA },
    { MAKELANGID(LANG_CZECH, SUBLANG_CZECH_CZECH_REPUBLIC), LOBBYAPI_LANGUAGE_CZECH, LOBBYAPI_COUNTRY_CZECH_REPUBLIC },
    { MAKELANGID(LANG_DANISH, SUBLANG_DANISH_DENMARK), LOBBYAPI_LANGUAGE_DANISH, LOBBYAPI_COUNTRY_DENMARK },
    { MAKELANGID(LANG_DIVEHI, SUBLANG_DIVEHI_MALDIVES), LOBBYAPI_LANGUAGE_DIVEHI, LOBBYAPI_COUNTRY_MALDIVES },
    { MAKELANGID(LANG_DUTCH, SUBLANG_DUTCH_BELGIAN), LOBBYAPI_LANGUAGE_DUTCH, LOBBYAPI_COUNTRY_BELGIUM },
    { MAKELANGID(LANG_DUTCH, SUBLANG_DUTCH), LOBBYAPI_LANGUAGE_DUTCH, LOBBYAPI_COUNTRY_NETHERLANDS },
    { MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_AUS), LOBBYAPI_LANGUAGE_ENGLISH, LOBBYAPI_COUNTRY_AUSTRALIA },
    { MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_BELIZE), LOBBYAPI_LANGUAGE_ENGLISH, LOBBYAPI_COUNTRY_BELIZE },
    { MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_CAN), LOBBYAPI_LANGUAGE_ENGLISH, LOBBYAPI_COUNTRY_CANADA },
    { MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_CARIBBEAN), LOBBYAPI_LANGUAGE_ENGLISH, LOBBYAPI_COUNTRY_UNKNOWN },
    { MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_INDIA), LOBBYAPI_LANGUAGE_ENGLISH, LOBBYAPI_COUNTRY_INDIA },
    { MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_EIRE), LOBBYAPI_LANGUAGE_ENGLISH, LOBBYAPI_COUNTRY_IRELAND },
    { MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_JAMAICA), LOBBYAPI_LANGUAGE_ENGLISH, LOBBYAPI_COUNTRY_JAMAICA },
    { MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_MALAYSIA), LOBBYAPI_LANGUAGE_ENGLISH, LOBBYAPI_COUNTRY_MALAYSIA },
    { MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_NZ), LOBBYAPI_LANGUAGE_ENGLISH, LOBBYAPI_COUNTRY_NEW_ZEALAND },
    { MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_PHILIPPINES), LOBBYAPI_LANGUAGE_ENGLISH, LOBBYAPI_COUNTRY_PHILIPPINES },
    { MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_SINGAPORE), LOBBYAPI_LANGUAGE_ENGLISH, LOBBYAPI_COUNTRY_SINGAPORE },
    { MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_SOUTH_AFRICA), LOBBYAPI_LANGUAGE_ENGLISH, LOBBYAPI_COUNTRY_SOUTH_AFRICA },
    { MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_TRINIDAD), LOBBYAPI_LANGUAGE_ENGLISH, LOBBYAPI_COUNTRY_TRINIDAD_AND_TOBAGO },
    { MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_UK), LOBBYAPI_LANGUAGE_ENGLISH, LOBBYAPI_COUNTRY_UNITED_KINGDOM },
    { MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), LOBBYAPI_LANGUAGE_ENGLISH, LOBBYAPI_COUNTRY_UNITED_STATES },
    { MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_ZIMBABWE), LOBBYAPI_LANGUAGE_ENGLISH, LOBBYAPI_COUNTRY_ZIMBABWE },
    { MAKELANGID(LANG_ESTONIAN, SUBLANG_ESTONIAN_ESTONIA), LOBBYAPI_LANGUAGE_ESTONIAN, LOBBYAPI_COUNTRY_ESTONIA },
    { MAKELANGID(LANG_FAEROESE, SUBLANG_FAEROESE_FAROE_ISLANDS), LOBBYAPI_LANGUAGE_FAEROESE, LOBBYAPI_COUNTRY_FAEROE_ISLANDS },
    { MAKELANGID(LANG_FINNISH, SUBLANG_FINNISH_FINLAND), LOBBYAPI_LANGUAGE_FINNISH, LOBBYAPI_COUNTRY_FINLAND },
    { MAKELANGID(LANG_FRENCH, SUBLANG_FRENCH_BELGIAN), LOBBYAPI_LANGUAGE_FRENCH, LOBBYAPI_COUNTRY_BELGIUM },
    { MAKELANGID(LANG_FRENCH, SUBLANG_FRENCH_CANADIAN), LOBBYAPI_LANGUAGE_FRENCH, LOBBYAPI_COUNTRY_CANADA },
    { MAKELANGID(LANG_FRENCH, SUBLANG_FRENCH), LOBBYAPI_LANGUAGE_FRENCH, LOBBYAPI_COUNTRY_FRANCE },
    { MAKELANGID(LANG_FRENCH, SUBLANG_FRENCH_LUXEMBOURG), LOBBYAPI_LANGUAGE_FRENCH, LOBBYAPI_COUNTRY_LUXEMBOURG },
    { MAKELANGID(LANG_FRENCH, SUBLANG_FRENCH_MONACO), LOBBYAPI_LANGUAGE_FRENCH, LOBBYAPI_COUNTRY_MONACO },
    { MAKELANGID(LANG_FRENCH, SUBLANG_FRENCH_SWISS), LOBBYAPI_LANGUAGE_FRENCH, LOBBYAPI_COUNTRY_SWITZERLAND },
    { MAKELANGID(LANG_FRISIAN, SUBLANG_FRISIAN_NETHERLANDS), LOBBYAPI_LANGUAGE_FRISIAN, LOBBYAPI_COUNTRY_NETHERLANDS },
    { MAKELANGID(LANG_GALICIAN, SUBLANG_GALICIAN_GALICIAN), LOBBYAPI_LANGUAGE_GALICIAN, LOBBYAPI_COUNTRY_SPAIN },
    { MAKELANGID(LANG_GEORGIAN, SUBLANG_GEORGIAN_GEORGIA), LOBBYAPI_LANGUAGE_GEORGIAN, LOBBYAPI_COUNTRY_GEORGIA },
    { MAKELANGID(LANG_GERMAN, SUBLANG_GERMAN_AUSTRIAN), LOBBYAPI_LANGUAGE_GERMAN, LOBBYAPI_COUNTRY_AUSTRIA },
    { MAKELANGID(LANG_GERMAN, SUBLANG_GERMAN), LOBBYAPI_LANGUAGE_GERMAN, LOBBYAPI_COUNTRY_GERMANY },
    { MAKELANGID(LANG_GERMAN, SUBLANG_GERMAN_LIECHTENSTEIN), LOBBYAPI_LANGUAGE_GERMAN, LOBBYAPI_COUNTRY_LIECHTENSTEIN },
    { MAKELANGID(LANG_GERMAN, SUBLANG_GERMAN_LUXEMBOURG), LOBBYAPI_LANGUAGE_GERMAN, LOBBYAPI_COUNTRY_LUXEMBOURG },
    { MAKELANGID(LANG_GERMAN, SUBLANG_GERMAN_SWISS), LOBBYAPI_LANGUAGE_GERMAN, LOBBYAPI_COUNTRY_SWITZERLAND },
    { MAKELANGID(LANG_GREEK, SUBLANG_GREEK_GREECE), LOBBYAPI_LANGUAGE_GREEK, LOBBYAPI_COUNTRY_GREECE },
    { MAKELANGID(LANG_GREENLANDIC, SUBLANG_GREENLANDIC_GREENLAND), LOBBYAPI_LANGUAGE_GREENLANDIC, LOBBYAPI_COUNTRY_GREENLAND },
    { MAKELANGID(LANG_GUJARATI, SUBLANG_GUJARATI_INDIA), LOBBYAPI_LANGUAGE_GUJARATI, LOBBYAPI_COUNTRY_INDIA },
    { MAKELANGID(LANG_HAUSA, SUBLANG_HAUSA_NIGERIA_LATIN), LOBBYAPI_LANGUAGE_HAUSA, LOBBYAPI_COUNTRY_NIGERIA },
    { MAKELANGID(LANG_HEBREW, SUBLANG_HEBREW_ISRAEL), LOBBYAPI_LANGUAGE_HEBREW, LOBBYAPI_COUNTRY_ISRAEL },
    { MAKELANGID(LANG_HINDI, SUBLANG_HINDI_INDIA), LOBBYAPI_LANGUAGE_HINDI, LOBBYAPI_COUNTRY_INDIA },
    { MAKELANGID(LANG_HUNGARIAN, SUBLANG_HUNGARIAN_HUNGARY), LOBBYAPI_LANGUAGE_HUNGARIAN, LOBBYAPI_COUNTRY_HUNGARY },
    { MAKELANGID(LANG_ICELANDIC, SUBLANG_ICELANDIC_ICELAND), LOBBYAPI_LANGUAGE_ICELANDIC, LOBBYAPI_COUNTRY_ICELAND },
    { MAKELANGID(LANG_IGBO, SUBLANG_IGBO_NIGERIA), LOBBYAPI_LANGUAGE_IGBO, LOBBYAPI_COUNTRY_NIGERIA },
    { MAKELANGID(LANG_INDONESIAN, SUBLANG_INDONESIAN_INDONESIA), LOBBYAPI_LANGUAGE_INDONESIAN, LOBBYAPI_COUNTRY_INDONESIA },
    { MAKELANGID(LANG_INUKTITUT, SUBLANG_INUKTITUT_CANADA_LATIN), LOBBYAPI_LANGUAGE_INUKTITUT, LOBBYAPI_COUNTRY_CANADA },
    { MAKELANGID(LANG_INUKTITUT, SUBLANG_INUKTITUT_CANADA), LOBBYAPI_LANGUAGE_INUKTITUT, LOBBYAPI_COUNTRY_CANADA },
    { MAKELANGID(LANG_IRISH, SUBLANG_NEUTRAL), LOBBYAPI_LANGUAGE_IRISH, LOBBYAPI_COUNTRY_IRELAND },
    { MAKELANGID(LANG_IRISH, SUBLANG_IRISH_IRELAND), LOBBYAPI_LANGUAGE_IRISH, LOBBYAPI_COUNTRY_IRELAND },
    { MAKELANGID(LANG_ITALIAN, SUBLANG_ITALIAN), LOBBYAPI_LANGUAGE_ITALIAN, LOBBYAPI_COUNTRY_ITALY },
    { MAKELANGID(LANG_ITALIAN, SUBLANG_ITALIAN_SWISS), LOBBYAPI_LANGUAGE_ITALIAN, LOBBYAPI_COUNTRY_SWITZERLAND },
    { MAKELANGID(LANG_JAPANESE, SUBLANG_JAPANESE_JAPAN), LOBBYAPI_LANGUAGE_JAPANESE, LOBBYAPI_COUNTRY_JAPAN },
    { MAKELANGID(LANG_KANNADA, SUBLANG_KANNADA_INDIA), LOBBYAPI_LANGUAGE_KANNADA, LOBBYAPI_COUNTRY_INDIA },
    { MAKELANGID(LANG_KAZAK, SUBLANG_KAZAK_KAZAKHSTAN), LOBBYAPI_LANGUAGE_KAZAKH, LOBBYAPI_COUNTRY_KAZAKHSTAN },
    { MAKELANGID(LANG_KHMER, SUBLANG_KHMER_CAMBODIA), LOBBYAPI_LANGUAGE_CAMBODIAN, LOBBYAPI_COUNTRY_CAMBODIA },
    { MAKELANGID(LANG_KINYARWANDA, SUBLANG_KINYARWANDA_RWANDA), LOBBYAPI_LANGUAGE_KINYARWANDA, LOBBYAPI_COUNTRY_RWANDA },
    { MAKELANGID(LANG_KOREAN, SUBLANG_KOREAN), LOBBYAPI_LANGUAGE_KOREAN, LOBBYAPI_COUNTRY_KOREA_REPUBLIC_OF },
    { MAKELANGID(LANG_KYRGYZ, SUBLANG_KYRGYZ_KYRGYZSTAN), LOBBYAPI_LANGUAGE_KIRGHIZ, LOBBYAPI_COUNTRY_KYRGYZSTAN },
    { MAKELANGID(LANG_LAO, SUBLANG_LAO_LAO), LOBBYAPI_LANGUAGE_LAOTHIAN, LOBBYAPI_COUNTRY_LAO_PEOPLES_DEMOCRATIC_REPUBLIC },
    { MAKELANGID(LANG_LATVIAN, SUBLANG_LATVIAN_LATVIA), LOBBYAPI_LANGUAGE_LATVIAN_LETTISH, LOBBYAPI_COUNTRY_LATVIA },
    { MAKELANGID(LANG_LITHUANIAN, SUBLANG_LITHUANIAN), LOBBYAPI_LANGUAGE_LITHUANIAN, LOBBYAPI_COUNTRY_LITHUANIA },
    { MAKELANGID(LANG_LUXEMBOURGISH, SUBLANG_LUXEMBOURGISH_LUXEMBOURG), LOBBYAPI_LANGUAGE_LUXEMBOURGISH, LOBBYAPI_COUNTRY_LUXEMBOURG },
    { MAKELANGID(LANG_MACEDONIAN, SUBLANG_MACEDONIAN_MACEDONIA), LOBBYAPI_LANGUAGE_MACEDONIAN, LOBBYAPI_COUNTRY_MACEDONIA_THE_FORMER_YUGOSLAV_REPUBLIC_OF },
    { MAKELANGID(LANG_MALAY, SUBLANG_MALAY_BRUNEI_DARUSSALAM), LOBBYAPI_LANGUAGE_MALAY, LOBBYAPI_COUNTRY_BRUNEI_DARUSSALAM },
    { MAKELANGID(LANG_MALAY, SUBLANG_MALAY_MALAYSIA), LOBBYAPI_LANGUAGE_MALAY, LOBBYAPI_COUNTRY_MALAYSIA },
    { MAKELANGID(LANG_MALAYALAM, SUBLANG_MALAYALAM_INDIA), LOBBYAPI_LANGUAGE_MALAYALAM, LOBBYAPI_COUNTRY_INDIA },
    { MAKELANGID(LANG_MALTESE, SUBLANG_MALTESE_MALTA), LOBBYAPI_LANGUAGE_MALTESE, LOBBYAPI_COUNTRY_MALTA },
    { MAKELANGID(LANG_MAORI, SUBLANG_MAORI_NEW_ZEALAND), LOBBYAPI_LANGUAGE_MAORI, LOBBYAPI_COUNTRY_NEW_ZEALAND },
    { MAKELANGID(LANG_MARATHI, SUBLANG_MARATHI_INDIA), LOBBYAPI_LANGUAGE_MARATHI, LOBBYAPI_COUNTRY_INDIA },
    { MAKELANGID(LANG_MONGOLIAN, SUBLANG_MONGOLIAN_CYRILLIC_MONGOLIA), LOBBYAPI_LANGUAGE_MONGOLIAN, LOBBYAPI_COUNTRY_MONGOLIA },
    { MAKELANGID(LANG_MONGOLIAN, SUBLANG_MONGOLIAN_PRC), LOBBYAPI_LANGUAGE_MONGOLIAN, LOBBYAPI_COUNTRY_CHINA },
    { MAKELANGID(LANG_NEPALI, SUBLANG_NEPALI_NEPAL), LOBBYAPI_LANGUAGE_NEPALI, LOBBYAPI_COUNTRY_NEPAL },
    { MAKELANGID(LANG_NORWEGIAN, SUBLANG_NORWEGIAN_BOKMAL), LOBBYAPI_LANGUAGE_NORWEGIAN_BOKMAL, LOBBYAPI_COUNTRY_NORWAY },
    { MAKELANGID(LANG_NORWEGIAN, SUBLANG_NORWEGIAN_NYNORSK), LOBBYAPI_LANGUAGE_NORWEGIAN_NYNORSK, LOBBYAPI_COUNTRY_NORWAY },
    { MAKELANGID(LANG_OCCITAN, SUBLANG_OCCITAN_FRANCE), LOBBYAPI_LANGUAGE_OCCITAN, LOBBYAPI_COUNTRY_FRANCE },
    { MAKELANGID(LANG_ORIYA, SUBLANG_ORIYA_INDIA), LOBBYAPI_LANGUAGE_ORIYA, LOBBYAPI_COUNTRY_INDIA },
    { MAKELANGID(LANG_PASHTO, SUBLANG_PASHTO_AFGHANISTAN), LOBBYAPI_LANGUAGE_PASHTO_PUSHTO, LOBBYAPI_COUNTRY_AFGHANISTAN },
    { MAKELANGID(LANG_PERSIAN, SUBLANG_PERSIAN_IRAN), LOBBYAPI_LANGUAGE_PERSIAN, LOBBYAPI_COUNTRY_IRAN },
    { MAKELANGID(LANG_POLISH, SUBLANG_POLISH_POLAND), LOBBYAPI_LANGUAGE_POLISH, LOBBYAPI_COUNTRY_POLAND },
    { MAKELANGID(LANG_PORTUGUESE, SUBLANG_PORTUGUESE_BRAZILIAN), LOBBYAPI_LANGUAGE_PORTUGUESE, LOBBYAPI_COUNTRY_BRAZIL },
    { MAKELANGID(LANG_PORTUGUESE, SUBLANG_PORTUGUESE), LOBBYAPI_LANGUAGE_PORTUGUESE, LOBBYAPI_COUNTRY_PORTUGAL },
    { MAKELANGID(LANG_PUNJABI, SUBLANG_PUNJABI_INDIA), LOBBYAPI_LANGUAGE_PUNJABI, LOBBYAPI_COUNTRY_INDIA },
    { MAKELANGID(LANG_QUECHUA, SUBLANG_QUECHUA_BOLIVIA), LOBBYAPI_LANGUAGE_QUECHUA, LOBBYAPI_COUNTRY_BOLIVIA },
    { MAKELANGID(LANG_QUECHUA, SUBLANG_QUECHUA_ECUADOR), LOBBYAPI_LANGUAGE_QUECHUA, LOBBYAPI_COUNTRY_ECUADOR },
    { MAKELANGID(LANG_QUECHUA, SUBLANG_QUECHUA_PERU), LOBBYAPI_LANGUAGE_QUECHUA, LOBBYAPI_COUNTRY_PERU },
    { MAKELANGID(LANG_ROMANIAN, SUBLANG_ROMANIAN_ROMANIA), LOBBYAPI_LANGUAGE_ROMANIAN, LOBBYAPI_COUNTRY_ROMANIA },
    { MAKELANGID(LANG_ROMANSH, SUBLANG_ROMANSH_SWITZERLAND), LOBBYAPI_LANGUAGE_RHAETO_ROMANCE, LOBBYAPI_COUNTRY_SWITZERLAND },
    { MAKELANGID(LANG_RUSSIAN, SUBLANG_RUSSIAN_RUSSIA), LOBBYAPI_LANGUAGE_RUSSIAN, LOBBYAPI_COUNTRY_RUSSIAN_FEDERATION },
    { MAKELANGID(LANG_SAMI, SUBLANG_SAMI_NORTHERN_FINLAND), LOBBYAPI_LANGUAGE_SAMI, LOBBYAPI_COUNTRY_FINLAND },
    { MAKELANGID(LANG_SAMI, SUBLANG_SAMI_NORTHERN_NORWAY), LOBBYAPI_LANGUAGE_SAMI, LOBBYAPI_COUNTRY_NORWAY },
    { MAKELANGID(LANG_SAMI, SUBLANG_SAMI_NORTHERN_SWEDEN), LOBBYAPI_LANGUAGE_SAMI, LOBBYAPI_COUNTRY_SWEDEN },
    { MAKELANGID(LANG_SANSKRIT, SUBLANG_SANSKRIT_INDIA), LOBBYAPI_LANGUAGE_SANSKRIT, LOBBYAPI_COUNTRY_INDIA },
    { MAKELANGID(LANG_SERBIAN, SUBLANG_SERBIAN_BOSNIA_HERZEGOVINA_CYRILLIC), LOBBYAPI_LANGUAGE_SERBIAN, LOBBYAPI_COUNTRY_BOSNIA_HERZEGOVINA },
    { MAKELANGID(LANG_SERBIAN, SUBLANG_SERBIAN_BOSNIA_HERZEGOVINA_LATIN),LOBBYAPI_LANGUAGE_SERBIAN, LOBBYAPI_COUNTRY_BOSNIA_HERZEGOVINA },
    { MAKELANGID(LANG_SERBIAN, SUBLANG_SERBIAN_CYRILLIC), LOBBYAPI_LANGUAGE_SERBIAN, LOBBYAPI_COUNTRY_SERBIA_AND_MONTENEGRO },
    { MAKELANGID(LANG_SERBIAN, SUBLANG_SERBIAN_LATIN), LOBBYAPI_LANGUAGE_SERBIAN, LOBBYAPI_COUNTRY_SERBIA_AND_MONTENEGRO },
    { MAKELANGID(LANG_SERBIAN, 0x0C), LOBBYAPI_LANGUAGE_SERBIAN, LOBBYAPI_COUNTRY_MONTENEGRO },
    { MAKELANGID(LANG_SERBIAN, 0x0B), LOBBYAPI_LANGUAGE_SERBIAN, LOBBYAPI_COUNTRY_MONTENEGRO },
    { MAKELANGID(LANG_SERBIAN, 0x0A), LOBBYAPI_LANGUAGE_SERBIAN, LOBBYAPI_COUNTRY_SERBIA },
    { MAKELANGID(LANG_SERBIAN, 0x09), LOBBYAPI_LANGUAGE_SERBIAN, LOBBYAPI_COUNTRY_SERBIA },
    { MAKELANGID(LANG_SINHALESE, SUBLANG_SINHALESE_SRI_LANKA), LOBBYAPI_LANGUAGE_SINGHALESE, LOBBYAPI_COUNTRY_SRI_LANKA },
    { MAKELANGID(LANG_SLOVAK, SUBLANG_SLOVAK_SLOVAKIA), LOBBYAPI_LANGUAGE_SLOVAK, LOBBYAPI_COUNTRY_SLOVAKIA },
    { MAKELANGID(LANG_SLOVENIAN, SUBLANG_SLOVENIAN_SLOVENIA), LOBBYAPI_LANGUAGE_SLOVENIAN, LOBBYAPI_COUNTRY_SLOVENIA },
    { MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_ARGENTINA), LOBBYAPI_LANGUAGE_SPANISH, LOBBYAPI_COUNTRY_ARGENTINA },
    { MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_BOLIVIA), LOBBYAPI_LANGUAGE_SPANISH, LOBBYAPI_COUNTRY_BOLIVIA },
    { MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_CHILE), LOBBYAPI_LANGUAGE_SPANISH, LOBBYAPI_COUNTRY_CHILE },
    { MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_COLOMBIA), LOBBYAPI_LANGUAGE_SPANISH, LOBBYAPI_COUNTRY_COLOMBIA },
    { MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_COSTA_RICA), LOBBYAPI_LANGUAGE_SPANISH, LOBBYAPI_COUNTRY_COSTA_RICA },
    { MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_DOMINICAN_REPUBLIC), LOBBYAPI_LANGUAGE_SPANISH, LOBBYAPI_COUNTRY_DOMINICAN_REPUBLIC },
    { MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_ECUADOR), LOBBYAPI_LANGUAGE_SPANISH, LOBBYAPI_COUNTRY_ECUADOR },
    { MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_EL_SALVADOR), LOBBYAPI_LANGUAGE_SPANISH, LOBBYAPI_COUNTRY_EL_SALVADOR },
    { MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_GUATEMALA), LOBBYAPI_LANGUAGE_SPANISH, LOBBYAPI_COUNTRY_GUATEMALA },
    { MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_HONDURAS), LOBBYAPI_LANGUAGE_SPANISH, LOBBYAPI_COUNTRY_HONDURAS },
    { MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_MEXICAN), LOBBYAPI_LANGUAGE_SPANISH, LOBBYAPI_COUNTRY_MEXICO },
    { MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_NICARAGUA), LOBBYAPI_LANGUAGE_SPANISH, LOBBYAPI_COUNTRY_NICARAGUA },
    { MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_PANAMA), LOBBYAPI_LANGUAGE_SPANISH, LOBBYAPI_COUNTRY_PANAMA },
    { MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_PARAGUAY), LOBBYAPI_LANGUAGE_SPANISH, LOBBYAPI_COUNTRY_PARAGUAY },
    { MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_PERU), LOBBYAPI_LANGUAGE_SPANISH, LOBBYAPI_COUNTRY_PERU },
    { MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_PUERTO_RICO), LOBBYAPI_LANGUAGE_SPANISH, LOBBYAPI_COUNTRY_PUERTO_RICO },
    { MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_MODERN), LOBBYAPI_LANGUAGE_SPANISH, LOBBYAPI_COUNTRY_SPAIN },
    { MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH), LOBBYAPI_LANGUAGE_SPANISH, LOBBYAPI_COUNTRY_SPAIN },
    { MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_US), LOBBYAPI_LANGUAGE_SPANISH, LOBBYAPI_COUNTRY_UNITED_STATES },
    { MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_URUGUAY), LOBBYAPI_LANGUAGE_SPANISH, LOBBYAPI_COUNTRY_URUGUAY },
    { MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_VENEZUELA), LOBBYAPI_LANGUAGE_SPANISH, LOBBYAPI_COUNTRY_VENEZUELA },
    { MAKELANGID(LANG_SWAHILI, SUBLANG_DEFAULT), LOBBYAPI_LANGUAGE_SWAHILI, LOBBYAPI_COUNTRY_KENYA },
    { MAKELANGID(LANG_SWEDISH, SUBLANG_SWEDISH_FINLAND), LOBBYAPI_LANGUAGE_SWEDISH, LOBBYAPI_COUNTRY_FINLAND },
    { MAKELANGID(LANG_SWEDISH, SUBLANG_SWEDISH), LOBBYAPI_LANGUAGE_SWEDISH, LOBBYAPI_COUNTRY_SWEDEN },
    { MAKELANGID(LANG_TAJIK, SUBLANG_TAJIK_TAJIKISTAN), LOBBYAPI_LANGUAGE_TAJIK, LOBBYAPI_COUNTRY_TAJIKISTAN },
    { MAKELANGID(LANG_TAMIL, SUBLANG_TAMIL_INDIA), LOBBYAPI_LANGUAGE_TAMIL, LOBBYAPI_COUNTRY_INDIA },
    { MAKELANGID(LANG_TATAR, SUBLANG_TATAR_RUSSIA), LOBBYAPI_LANGUAGE_TATAR, LOBBYAPI_COUNTRY_RUSSIAN_FEDERATION },
    { MAKELANGID(LANG_TELUGU, SUBLANG_TELUGU_INDIA), LOBBYAPI_LANGUAGE_TELUGU, LOBBYAPI_COUNTRY_INDIA },
    { MAKELANGID(LANG_THAI, SUBLANG_THAI_THAILAND), LOBBYAPI_LANGUAGE_THAI, LOBBYAPI_COUNTRY_THAILAND },
    { MAKELANGID(LANG_TIBETAN, SUBLANG_TIBETAN_PRC), LOBBYAPI_LANGUAGE_TIBETAN, LOBBYAPI_COUNTRY_CHINA },
    { MAKELANGID(LANG_TSWANA, SUBLANG_TSWANA_SOUTH_AFRICA), LOBBYAPI_LANGUAGE_SETSWANA, LOBBYAPI_COUNTRY_SOUTH_AFRICA },
    { MAKELANGID(LANG_TURKISH, SUBLANG_TURKISH_TURKEY), LOBBYAPI_LANGUAGE_TURKISH, LOBBYAPI_COUNTRY_TURKEY },
    { MAKELANGID(LANG_TURKMEN, SUBLANG_TURKMEN_TURKMENISTAN), LOBBYAPI_LANGUAGE_TURKMEN, LOBBYAPI_COUNTRY_TURKMENISTAN },
    { MAKELANGID(LANG_UIGHUR, SUBLANG_UIGHUR_PRC), LOBBYAPI_LANGUAGE_UIGHUR, LOBBYAPI_COUNTRY_CHINA },
    { MAKELANGID(LANG_UKRAINIAN, SUBLANG_UKRAINIAN_UKRAINE), LOBBYAPI_LANGUAGE_UKRAINIAN, LOBBYAPI_COUNTRY_UKRAINE },
    { MAKELANGID(LANG_URDU, SUBLANG_URDU_PAKISTAN), LOBBYAPI_LANGUAGE_URDU, LOBBYAPI_COUNTRY_PAKISTAN },
    { MAKELANGID(LANG_UZBEK, SUBLANG_UZBEK_CYRILLIC), LOBBYAPI_LANGUAGE_UZBEK, LOBBYAPI_COUNTRY_UZBEKISTAN },
    { MAKELANGID(LANG_UZBEK, SUBLANG_UZBEK_LATIN), LOBBYAPI_LANGUAGE_UZBEK, LOBBYAPI_COUNTRY_UZBEKISTAN },
    { MAKELANGID(LANG_VIETNAMESE, SUBLANG_VIETNAMESE_VIETNAM), LOBBYAPI_LANGUAGE_VIETNAMESE, LOBBYAPI_COUNTRY_VIETNAM },
    { MAKELANGID(LANG_WELSH, SUBLANG_WELSH_UNITED_KINGDOM), LOBBYAPI_LANGUAGE_WELSH, LOBBYAPI_COUNTRY_UNITED_KINGDOM },
    { MAKELANGID(LANG_WOLOF, SUBLANG_WOLOF_SENEGAL), LOBBYAPI_LANGUAGE_WOLOF, LOBBYAPI_COUNTRY_SENEGAL },
    { MAKELANGID(LANG_XHOSA, SUBLANG_XHOSA_SOUTH_AFRICA), LOBBYAPI_LANGUAGE_XHOSA, LOBBYAPI_COUNTRY_SOUTH_AFRICA },
    { MAKELANGID(LANG_YI, SUBLANG_YI_PRC), LOBBYAPI_LANGUAGE_YI, LOBBYAPI_COUNTRY_CHINA },
    { MAKELANGID(LANG_YORUBA, SUBLANG_YORUBA_NIGERIA), LOBBYAPI_LANGUAGE_YORUBA, LOBBYAPI_COUNTRY_NIGERIA },
    { MAKELANGID(LANG_ZULU, SUBLANG_ZULU_SOUTH_AFRICA), LOBBYAPI_LANGUAGE_ZULU, LOBBYAPI_COUNTRY_SOUTH_AFRICA },
    { MAKELANGID(0x91, SUBLANG_DEFAULT), LOBBYAPI_LANGUAGE_SCOTS_GAELIC, LOBBYAPI_COUNTRY_UNITED_KINGDOM },
    // the following are valid Windows locales, but not covered by ISO639-1, LOBBYAPI_LANGUAGE_UNKNOWN will be returned for them.
    { MAKELANGID(LANG_SAMI, SUBLANG_SAMI_INARI_FINLAND), LOBBYAPI_LANGUAGE_UNKNOWN, LOBBYAPI_COUNTRY_FINLAND },
    { MAKELANGID(LANG_SAMI, SUBLANG_SAMI_LULE_NORWAY), LOBBYAPI_LANGUAGE_UNKNOWN, LOBBYAPI_COUNTRY_NORWAY },
    { MAKELANGID(LANG_SAMI, SUBLANG_SAMI_LULE_SWEDEN), LOBBYAPI_LANGUAGE_UNKNOWN, LOBBYAPI_COUNTRY_SWEDEN },
    { MAKELANGID(LANG_SAMI, SUBLANG_SAMI_SKOLT_FINLAND), LOBBYAPI_LANGUAGE_UNKNOWN, LOBBYAPI_COUNTRY_FINLAND },
    { MAKELANGID(LANG_SAMI, SUBLANG_SAMI_SOUTHERN_NORWAY), LOBBYAPI_LANGUAGE_UNKNOWN, LOBBYAPI_COUNTRY_NORWAY },
    { MAKELANGID(LANG_SAMI, SUBLANG_SAMI_SOUTHERN_SWEDEN), LOBBYAPI_LANGUAGE_UNKNOWN, LOBBYAPI_COUNTRY_SWEDEN },
    { MAKELANGID(LANG_UPPER_SORBIAN, SUBLANG_UPPER_SORBIAN_GERMANY), LOBBYAPI_LANGUAGE_UNKNOWN, LOBBYAPI_COUNTRY_GERMANY },
    { MAKELANGID(LANG_LOWER_SORBIAN, SUBLANG_LOWER_SORBIAN_GERMANY), LOBBYAPI_LANGUAGE_UNKNOWN, LOBBYAPI_COUNTRY_GERMANY },
    { MAKELANGID(LANG_KONKANI, SUBLANG_KONKANI_INDIA), LOBBYAPI_LANGUAGE_UNKNOWN, LOBBYAPI_COUNTRY_INDIA },
    { MAKELANGID(LANG_SYRIAC, SUBLANG_DEFAULT), LOBBYAPI_LANGUAGE_UNKNOWN, LOBBYAPI_COUNTRY_SYRIAN_ARAB_REPUBLIC },
    { MAKELANGID(LANG_TAMAZIGHT, SUBLANG_NEUTRAL), LOBBYAPI_LANGUAGE_UNKNOWN, LOBBYAPI_COUNTRY_ALGERIA },
    { MAKELANGID(LANG_TAMAZIGHT, SUBLANG_TAMAZIGHT_ALGERIA_LATIN), LOBBYAPI_LANGUAGE_UNKNOWN, LOBBYAPI_COUNTRY_ALGERIA },
    { MAKELANGID(LANG_FILIPINO, SUBLANG_FILIPINO_PHILIPPINES), LOBBYAPI_LANGUAGE_UNKNOWN, LOBBYAPI_COUNTRY_PHILIPPINES },
    { MAKELANGID(LANG_SOTHO, SUBLANG_SOTHO_NORTHERN_SOUTH_AFRICA), LOBBYAPI_LANGUAGE_UNKNOWN, LOBBYAPI_COUNTRY_SOUTH_AFRICA },
    { MAKELANGID(LANG_MAPUDUNGUN, SUBLANG_MAPUDUNGUN_CHILE), LOBBYAPI_LANGUAGE_UNKNOWN, LOBBYAPI_COUNTRY_CHILE },
    { MAKELANGID(LANG_MOHAWK, SUBLANG_MOHAWK_MOHAWK), LOBBYAPI_LANGUAGE_UNKNOWN, LOBBYAPI_COUNTRY_CANADA },
    { MAKELANGID(LANG_ALSATIAN, SUBLANG_ALSATIAN_FRANCE), LOBBYAPI_LANGUAGE_UNKNOWN, LOBBYAPI_COUNTRY_FRANCE },
    { MAKELANGID(LANG_YAKUT, SUBLANG_YAKUT_RUSSIA), LOBBYAPI_LANGUAGE_UNKNOWN, LOBBYAPI_COUNTRY_RUSSIAN_FEDERATION },
    { MAKELANGID(LANG_KICHE, SUBLANG_KICHE_GUATEMALA), LOBBYAPI_LANGUAGE_UNKNOWN, LOBBYAPI_COUNTRY_GUATEMALA },
    { MAKELANGID(LANG_DARI, SUBLANG_DARI_AFGHANISTAN), LOBBYAPI_LANGUAGE_UNKNOWN, LOBBYAPI_COUNTRY_AFGHANISTAN }
};

//! global module ref
static NetConnRefT *_NetConn_pRef = NULL;

/*** Private Functions ****************************************************************************/

/*F********************************************************************************/
/*!
    \Function _NetConnGetLocaleInfo

    \Description
        To obtain locale information for specified lang id.

    \Input uLangId  - the lang id used to extract locale info from

    \Output
        int32_t     - the same output to NetConnStatus('locl'), ex. enUS

    \Version 04/14/2011 (szhu)
*/
/********************************************************************************F*/
static int32_t _NetConnGetLocaleInfo(uint16_t uLangId)
{
    uint16_t uLanguage, uCountry;
    uint32_t uIndex;

    // search the pre-defined mapping table
    for (uIndex = 0; uIndex < (sizeof(_NetConn_WindowsLocaleMap)/sizeof(_NetConn_WindowsLocaleMap[0])); uIndex++)
    {
        // _NetConn_WindowsLocaleMap[uIndex][0]: LangId, [1]: language, [2]: COUNTRY
        if (uLangId == _NetConn_WindowsLocaleMap[uIndex][0])
        {
            uLanguage = _NetConn_WindowsLocaleMap[uIndex][1];
            uCountry = _NetConn_WindowsLocaleMap[uIndex][2];
            return(LOBBYAPI_LocalizerTokenCreate(uLanguage, uCountry));
        }
    }

    // not found in pre-defined mapping table, search again with SUBLANG_DEFAULT (only for SUBLANG_NEUTRAL)
    if ((SUBLANGID(uLangId) == SUBLANG_NEUTRAL) && (SUBLANG_NEUTRAL != SUBLANG_DEFAULT))
    {
        uLangId = PRIMARYLANGID(uLangId);
        return(_NetConnGetLocaleInfo(MAKELANGID(uLangId, SUBLANG_DEFAULT)));
    }

    NetPrintf(("netconnwin: failed to obtain locale info for localid (0x%04x), %s is returned.\n", (uint32_t)uLangId, LOBBYAPI_LOCALITY_UNKNOWN_STR));
    // not found, zzZZ will be returned
    return(LOBBYAPI_LOCALITY_UNKNOWN);
}


#if DIRTYCODE_LOGGING
/*F********************************************************************************/
/*!
    \Function _NetConnVerifyLocale

    \Description
        Check if a _NetConn_WindowsLocaleMap entry is good by comparing with Windows API (GetLocaleInfo)'s result.

    \Input *pLocale         - a _NetConn_WindowsLocaleMap entry -- uint16_t[3]
    \Input iAlwaysPrint     - (boolean) TRUE, always prints debug trace; FALSE, prints debug trace upon error

    \Output
        int32_t             - (boolean) TRUE=success, FALSE=failure

    \Notes
        For debugging purpose only.

    \Version 03/25/2011 (szhu)
*/
/********************************************************************************F*/
static int32_t _NetConnVerifyLocale(uint16_t *pLocale, int32_t iAlwaysPrint)
{
    int32_t iResult = FALSE;
    char strLocale[LOCALE_NAME_MAX_LENGTH], strDefined[32];
    char *pTemp1, *pTemp2;
    uint32_t uToken;

    // convert array to string like enUS
    uToken = LOBBYAPI_LocalizerTokenCreate(pLocale[1], pLocale[2]);
    LOBBYAPI_LocalizerTokenCreateLocalityString(strDefined, uToken);

    // compare between mapping table value and Windows API GetLocaleInfo(LOCALE_SNAME) to verify whether the mapping table value is good.
    ds_memclr(strLocale, sizeof(strLocale));
    if (GetLocaleInfo(pLocale[0], LOCALE_SNAME, strLocale, LOCALE_NAME_MAX_LENGTH) > 0)
    {
        // GetLocaleInfo may return name-script-COUNTRY. If so, we remove the script: name-script-COUNTRY --> nameCOUNTRY
        if ((pTemp1 = strchr(strLocale, '-')) != NULL)
        {
            // name-script-COUNTRY --> nameCOUNTRY
            if ((pTemp2 = strchr(pTemp1 + 1, '-')) != NULL)
            {
                pTemp2++; // skip '-'
                memmove(pTemp1, pTemp2, strlen(pTemp2) + 1); // plus '\0'
            }
            else // name-COUNTRY --> nameCOUNTRY
            {
                memmove(pTemp1, pTemp1 + 1, strlen(pTemp1 + 1) + 1);
            }
        }

        // GetLocaleInfo may return name-COUNTRY_xxxx. If so, we remove _xxxx: nameCOUNTRY_xxxx --> nameCOUNTRY
        if ((pTemp1 = strchr(strLocale, '_')) != NULL)
        {
            pTemp1[0] = 0;
        }

        // the language name should be in lowercase while the country name should be in uppercase.
        // 1, enUS == enUS (perfect); 2, zzCA == xxxCA (not covered by ISO639-1); 3, xxCA == xxyCA (not covered by ISO639-1); 4, enZZ == enxxx;
        if((strcmp(strDefined, strLocale) == 0)
            || (strncmp(strDefined, LOBBYAPI_LANGUAGE_UNKNOWN_STR, 2) == 0 && strncmp(strDefined + 2, strLocale + 3, 2) == 0)
            || (strncmp(strDefined, strLocale, 2) == 0 && strncmp(strDefined + 2, strLocale + 3, 2) == 0)
            || (strncmp(strDefined, strLocale, 2) == 0 && strncmp(strDefined + 2, LOBBYAPI_COUNTRY_UNKNOWN_STR, 2) == 0 && strlen(strLocale) != 4))
        {
            // check if _NetConnGetLocaleInfo behaves correctly
            if ((uint32_t)_NetConnGetLocaleInfo(pLocale[0]) == uToken)
            {
                iResult = TRUE;
                if (iAlwaysPrint != FALSE)
                {
                    NetPrintf(("netconnwin: passed -- GetLocaleInfo(0x%04x) returned (%s)!\n", (uint32_t)pLocale[0], strLocale));
                }
                // if not perfect (enUS == enUS), print debug trace
                if (strcmp(strDefined, strLocale) != 0)
                {
                    NetPrintf(("netconnwin: notice -- GetLocaleInfo(0x%04x) returned (%s) - (%s)!\n", (uint32_t)pLocale[0], strLocale, strDefined));
                }
            }
        }
    }

    if (iResult == FALSE)
    {
        NetPrintf(("netconnwin: ERROR -- GetLocaleInfo(0x%04x) returned (%s) - (%s)!\n", (uint32_t)pLocale[0], strLocale, strDefined));
    }

    return(iResult);
}

/*F********************************************************************************/
/*!
    \Function _NetConnVerifyAllLocales

    \Description
        Verify if _NetConn_WindowsLocaleMap items are good. For debugging purpose only.

    \Notes
        For unknown language, zz will be returned;
        For unknown country, ZZ will be returned;
        For unknown locality, zzZZ will be returned.

    \Version 03/25/2011 (szhu)
*/
/********************************************************************************F*/
static void _NetConnVerifyAllLocales(void)
{
    uint16_t aNeutral[3];
    uint32_t uIndex, uToken;
    char strLocale[LOCALE_NAME_MAX_LENGTH];

    for (uIndex = 0; uIndex < (sizeof(_NetConn_WindowsLocaleMap)/sizeof(_NetConn_WindowsLocaleMap[0])); uIndex++)
    {
        if (_NetConnVerifyLocale(_NetConn_WindowsLocaleMap[uIndex], FALSE) == FALSE)
        {
            NetPrintf(("netconnwin: error found in _NetConn_WindowsLocaleMap[%u] (0x%04x)!\n", uIndex, (uint32_t)_NetConn_WindowsLocaleMap[uIndex][0]));
            DebugBreak();
        }

        // verify if langid(Primary ID, SUBLANG_NEUTRAL) works out
        // exception 0x0404: 0x0404 --> ZH_TW, 0x0004 --> ZH_CN
        if ((_NetConn_WindowsLocaleMap[uIndex][0] != 0x0404) && (SUBLANGID(_NetConn_WindowsLocaleMap[uIndex][0]) == SUBLANG_DEFAULT))
        {
            aNeutral[0] = MAKELANGID(PRIMARYLANGID(_NetConn_WindowsLocaleMap[uIndex][0]), SUBLANG_NEUTRAL);
            aNeutral[1] = _NetConn_WindowsLocaleMap[uIndex][1];
            aNeutral[2] = _NetConn_WindowsLocaleMap[uIndex][2];

            if (_NetConnVerifyLocale(aNeutral, FALSE) == FALSE)
            {
                NetPrintf(("netconnwin: error found in _NetConn_WindowsLocaleMap[%u](Neutral) (0x%04x)!\n", uIndex, (uint32_t)aNeutral[0]));
                DebugBreak();
            }
        }
    }
    NetPrintf(("netconnwin: checked _NetConn_WindowsLocaleMap -- %u entries in total.\n", uIndex));

    // print locale information
    uToken = NetConnStatus('locl', 0, NULL, 0);
    NetPrintf(("netconnwin: locality returned by NetConnStatus(locl,0): (%c%c%c%c).\n", LOBBYAPI_LocalizerTokenPrintCharArray(uToken)));

    // test if _NetConnGetLocaleInfo works fine when given an invalid langid
    uToken = _NetConnGetLocaleInfo(0x9FFF);
    NetPrintf(("netconnwin: _NetConnGetLocaleInfo(0x9FFF) returned: (%c%c%c%c) - (%s).\n", LOBBYAPI_LocalizerTokenPrintCharArray(uToken), LOBBYAPI_LOCALITY_UNKNOWN_STR));

    // list locales that not covered by the mapping table
    for (uIndex = 1; uIndex <= 0x8200; uIndex++)
    {
        if ((PRIMARYLANGID(uIndex) == LANG_NEUTRAL)
            || (uIndex == MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL))
            || (uIndex == MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT)))
        {
            continue;
        }

        if ((GetLocaleInfo(uIndex, LOCALE_SNAME, strLocale, LOCALE_NAME_MAX_LENGTH) > 0)
            && (_NetConnGetLocaleInfo(uIndex) == 'zzZZ'))
        {
            NetPrintf(("netconnwin: not covered (0x%04x): %s!\n", uIndex, strLocale));
        }
    }
}
#endif //DIRTYCODE_LOGGING

/*F********************************************************************************/
/*!
    \Function _NetConnIsValidMacAddress

    \Description
        Checks to make sure the address is valid

    \Input *pAddr   - The current address we are checking for validity
    \Input bCheckIP - Should we check the IP Address of the NIC to make sure it is active

    \Output
        uint8_t     - TRUE=valid, FALSE=invalid 
*/
/********************************************************************************F*/
static uint8_t _NetConnIsValidMacAddress(PIP_ADAPTER_ADDRESSES pAddr, uint8_t bCheckIP)
{
    PIP_ADAPTER_UNICAST_ADDRESS pUnicastAddr;

    // If the address is not 6-byte long it is invalid
    if (pAddr->PhysicalAddressLength != 6)
    {
        return(FALSE);
    }

    // If we are not checking the IP address then the MAC is valid
    // as far as we are concerned
    if (bCheckIP == FALSE)
    {
        return(TRUE);
    }

    // Make sure the address has a valid IP in multiple NIC situation
    for (pUnicastAddr = pAddr->FirstUnicastAddress; pUnicastAddr != NULL; pUnicastAddr = pUnicastAddr->Next)
    {
        if ((pUnicastAddr->Flags & IP_ADAPTER_ADDRESS_DNS_ELIGIBLE) == IP_ADAPTER_ADDRESS_DNS_ELIGIBLE)
        {
            return(TRUE);
        }
    }

    return(FALSE);
}

/*F********************************************************************************/
/*!
    \Function _NetConnFindMacAddress

    \Description
        Scans through adapter list and finds an active adapter.

    \Input *pMacAddr    - user-provided buffer to be filled with MAC address
    \Input iAddrSize    - The size of the buffer
    \Input bCheckIP     - If we should check for a valid IP on the NIC

    \Output
        int32_t         - Negative=error, zero=success

    \Version 06/23/2009 (mclouatre)
*/
/********************************************************************************F*/
static int32_t _NetConnFindMacAddress(uint8_t *pMacAddr, int32_t iAddrSize, uint8_t bCheckIP)
{
    NetConnRefT *pRef = _NetConn_pRef;
    DWORD dwMsRetVal;
    ULONG ulOutBufLen;
    ULONG ulDummyBuffer;
    PIP_ADAPTER_ADDRESSES pAdaptersAddrBuf;
    PIP_ADAPTER_ADDRESSES pCurrAddr;

#if DIRTYCODE_LOGGING
    uint32_t uByteIndex;
    char strPrintBuf[32];
    int32_t iCurrLen;
#endif

    int32_t iRetVal = 0;

    // make an initial call to GetAdaptersAddresses() to get the
    // size needed into the ulOutBufLen variable
    ulOutBufLen = 1;   // make sure that size provided is too small to store the info.
    dwMsRetVal = GetAdaptersAddresses(AF_UNSPEC, 0, NULL, (PIP_ADAPTER_ADDRESSES) &ulDummyBuffer, &ulOutBufLen);
    if (dwMsRetVal != ERROR_BUFFER_OVERFLOW)
    {
        // if windows does not return an overflow error, something is wrong...
        NetPrintf(("netconnwin: GetAdaptersAddresses() failed with err %d when querying for output buffer size\n", dwMsRetVal));
        return(-1);
    }

    /*
    allocate a big enough buffer.
    note:
        * it is not a good idea to preallocate this as we will have to seriously overallocate to support rare cases
          with a long list of IP_ADAPTER_ADDRESSES returned.   MS doc states that Windows will "typically" not
          require a buffer larger than 15 Kbytes http://msdn.microsoft.com/en-us/library/aa365915(VS.85).aspx
        * it is not a good idea to allocate this buffer on the stack as it can be quite huge and could lead to a
          stack overflow.
    */
    if ((pAdaptersAddrBuf = DirtyMemAlloc(ulOutBufLen, NETCONN_MEMID, pRef->Common.iMemGroup, pRef->Common.pMemGroupUserData)) == NULL)
    {
        NetPrintf(("netconnwin: unable to allocate output buffer required to call GetAdaptersAddresses()\n"));
        return(-2);
    }

    // make a second call to GetAdaptersAddresses() to get the actual data we want
    dwMsRetVal = GetAdaptersAddresses(AF_UNSPEC, 0, NULL,  pAdaptersAddrBuf, &ulOutBufLen);
    if (dwMsRetVal == NO_ERROR)
    {
        // loop through the adapters and match first entry with valid MAC address
        pCurrAddr = pAdaptersAddrBuf;
        while (pCurrAddr)
        {
            // make sure interface type can have a MAC address
            if ((pCurrAddr->IfType == IF_TYPE_ETHERNET_CSMACD) || (pCurrAddr->IfType == IF_TYPE_IEEE80211))
            {
                // make sure physical address is 6-byte long and has a valid ip address; if not, it is not a MAC address
                if (_NetConnIsValidMacAddress(pCurrAddr, bCheckIP) == TRUE)
                {
                    ds_memcpy_s(pMacAddr, iAddrSize, pCurrAddr->PhysicalAddress, pCurrAddr->PhysicalAddressLength);

                    #if DIRTYCODE_LOGGING
                    // We need to use sprintf here because if we try to NetPrintf we will
                    // have timestamps inbetween where we are printing out the mac address

                    ds_memclr(strPrintBuf, sizeof(strPrintBuf));
                    iCurrLen = 0;

                    for (uByteIndex = 0; uByteIndex < (int32_t) pCurrAddr->PhysicalAddressLength; uByteIndex++)
                    {
                        if (uByteIndex == (pCurrAddr->PhysicalAddressLength - 1))
                        {
                            iCurrLen += ds_snzprintf(strPrintBuf + iCurrLen, sizeof(strPrintBuf) - iCurrLen, "%.2X", (int32_t)pCurrAddr->PhysicalAddress[uByteIndex]); 
                        }
                        else
                        {
                            iCurrLen += ds_snzprintf(strPrintBuf + iCurrLen, sizeof(strPrintBuf) - iCurrLen, "%.2X-", (int32_t)pCurrAddr->PhysicalAddress[uByteIndex]);
                        }
                    }

                    NetPrintf(("netconnwin: valid MAC address found --> %s\n", strPrintBuf));
                    #endif

                    // valid MAC address found! exit the function with success
                    break;
                }
            }

            pCurrAddr = pCurrAddr->Next;
        }

        if (pCurrAddr == NULL)
        {
            NetPrintf(("netconnwin: could not find any valid adapters to get a MAC address from\n"));
            iRetVal = -3;
        }
    }
    else
    {
        NetPrintf(("netconnwin: GetAdaptersAddresses() failed with err %d when querying for adapters data size\n", dwMsRetVal));
        iRetVal = -4;
    }

    // free buffer used with GetAdaptersAddresses
    DirtyMemFree(pAdaptersAddrBuf, NETCONN_MEMID, pRef->Common.iMemGroup, pRef->Common.pMemGroupUserData);

    return(iRetVal);
}

/*F********************************************************************************/
/*!
    \Function    _NetConnShutdownInternal

    \Description
        Shutdown the network code and return to idle state for internal use

    \Input  pRef       - netconn ref
    \Input  bStopEE    - ignored in PC implementation

    \Output
        int32_t        - negative=error, else zero

    \Version 1/13/2020 (tcho)
*/
/********************************************************************************F*/
int32_t _NetConnShutdownInternal(NetConnRefT *pRef, uint32_t bStopEE)
{
    int32_t iResult = 0;

    // decrement and check the refcount
    if ((iResult = NetConnCommonCheckRef((NetConnCommonRefT*)pRef)) < 0)
    {
        return(iResult);
    }

    if (_NetConn_pRef != NULL)
    {
        NetConnDisconnect();
    }

    // destroy the upnp ref
    if (pRef->pProtoUpnp != NULL)
    {
        ProtoUpnpDestroy(pRef->pProtoUpnp);
        pRef->pProtoUpnp = NULL;
    }

    // shut down protossl
    ProtoSSLShutdown();

    // shut down dirtycert
    DirtyCertDestroy();

    // shut down Idle handler
    NetConnIdleShutdown();

    // shut down dirtysock
    SocketDestroy(0);

    // common shutdown (must come last as this frees the memory)
    NetConnCommonShutdown(&pRef->Common);
    _NetConn_pRef = NULL;

    return(0);
}

/*** Public functions *****************************************************************************/

/*F********************************************************************************/
/*!
    \Function NetConnStartup

    \Description
        Bring the network connection module to life. Does not actually start any
        network activity.

    \Input *pParams - startup parameters

    \Output
        int32_t     - zero=success, negative=failure

    \Notes
        NetConnRefT::iRefCount serves as a counter for the number of times 
        NetConnStartup has been called. This allows us to track how many modules 
        are using it and how many times we expect NetConnShutdown to the called. 
        In the past we only allowed a single call to NetConnStartup but some 
        libraries may need to networking without a guarentee that the game has 
        already started it.

        pParams can contain the following terms:

        \verbatim
            -noupnp                           : disable UPNP
            -servicename=<game-year-platform> : set servicename required for SSL use
            -affinity=<mask as hex>           : cpu affinity we set for our internal threads
        \endverbatim

    \Version 3/10/01 (GWS)
*/
/********************************************************************************F*/
int32_t NetConnStartup(const char *pParams)
{
    NetConnRefT *pRef = _NetConn_pRef;
    int32_t iResult = 0;
    char strThreadCpuAffinity[16];

    // allow NULL params
    if (pParams == NULL)
    {
        pParams = "";
    }

    // debug display of input params
    NetPrintf(("netconnwin: startup params='%s'\n", pParams));

    // common startup
    // pRef shall hold the address of the NetConnRefT after completion if no error occured
    iResult = NetConnCommonStartup(sizeof(*pRef), pParams, (NetConnCommonRefT**)(&pRef));

    // treat the result of the common startup, if already started simply early out
    if (iResult == NETCONN_ERROR_ALREADY_STARTED)
    {
        return(0);
    }
    // otherwise, if an error occured report it
    else if (iResult < 0)
    {
        return(iResult);
    }

    pRef->eState = ST_INIT;
    pRef->iPeerPort = NETCONN_DEFAULT_UPNP_PORT;

    // get the cpu affinity string from our startup params, defaulting to 0x0
    ds_memclr(strThreadCpuAffinity, sizeof(strThreadCpuAffinity));
    NetConnCopyParam(strThreadCpuAffinity, sizeof(strThreadCpuAffinity), "-affinity=", pParams, "0x0");
    pRef->iThreadCpuAffinity = strtol(strThreadCpuAffinity, NULL, 16);

    // start up dirtysock
    if (SocketCreate(THREAD_PRIORITY_HIGHEST, 0, pRef->iThreadCpuAffinity) != 0)
    {
        _NetConnShutdownInternal(pRef, FALSE);
        NetPrintf(("netconnwin: unable to start up dirtysock\n"));
        return(NETCONN_ERROR_SOCKET_CREATE);
    }

    // create and configure dirtycert
    if (NetConnDirtyCertCreate(pParams))
    {
        _NetConnShutdownInternal(pRef, FALSE);
        NetPrintf(("netconnwin: unable to create dirtycert\n"));
        return(NETCONN_ERROR_DIRTYCERT_CREATE);
    }

    // start up protossl
    if (ProtoSSLStartup() < 0)
    {
        _NetConnShutdownInternal(pRef, FALSE);
        NetPrintf(("netconnwin: unable to start up protossl\n"));
        return(NETCONN_ERROR_PROTOSSL_CREATE);
    }

    if (strstr(pParams, "-noupnp") == NULL)
    {
        pRef->pProtoUpnp = ProtoUpnpCreate();
        if (pRef->pProtoUpnp == NULL)
        {
            _NetConnShutdownInternal(pRef, FALSE);
            NetPrintf(("netconnwin: unable to start up protoupnp\n"));
            return(NETCONN_ERROR_PROTOUPNP_CREATE);
        }
    }

    // save ref
    _NetConn_pRef = pRef;

    #if DIRTYCODE_LOGGING
    // if we made changes to the mapping table (_NetConn_WindowsLocaleMap),
    // uncomment the following line and build-debug-run to verify if the changes are good.
    //_NetConnVerifyAllLocales();
    #endif

    return(0);
}

/*F*************************************************************************************************/
/*!
    \Function    NetConnQuery

    \Description
        Query the list of available connection configurations. This list is loaded
        from the specified device. The list is returned in a simple fixed width
        array with one string per array element. The caller can find the user portion
        of the config name via strchr(item, '#')+1.

    \Input *pDevice - device to scan (mc0:, mc1:, pfs0:, pfs1:)
    \Input *pList   - buffer to store output array in
    \Input iSize    - length of buffer in bytes

    \Output
        int32_t       - negative=error, else number of configurations

    \Version  01/18/02 (GWS)
*/
/*************************************************************************************************F*/
int32_t NetConnQuery(const char *pDevice, NetConfigRecT *pList, int32_t iSize)
{
    return(0);
}

/*F*************************************************************************************************/
/*!
    \Function    NetConnConnect

    \Description
        Used to bring the networking online with a specific configuration. Uses the
        configuration returned by NetConnQuery.

    \Input *pConfig - the configuration entry from NetConnQuery
    \Input *pOption - asciiz list of config parameters
                      "peerport=<port>" to specify peer port to be opened by upnp.
    \Input iData    - platform-specific

    \Output
        int32_t     - negative=error, zero=success

    \Version 01/18/02 (GWS)
*/
/*************************************************************************************************F*/
int32_t NetConnConnect(const NetConfigRecT *pConfig, const char *pOption, int32_t iData)
{
    NetConnRefT *pRef = _NetConn_pRef;

    // check connection options, if present
    if (pOption != NULL)
    {
        const char *pOpt;
        // check for specification of peer port
        if ((pOpt = strstr(pOption, "peerport=")) != NULL)
        {
            pRef->iPeerPort = strtol(pOpt+9, NULL, 10);
        }
    }
    NetPrintf(("netconnwin: upnp peerport=%d %s\n",
        pRef->iPeerPort, (pRef->iPeerPort == NETCONN_DEFAULT_UPNP_PORT ? "(default)" : "(selected via netconnconnect param)")));

    // make sure we aren't already connected
    if (pRef->eState == ST_INIT)
    {
        // connecting
        pRef->eState = ST_CONN;

        // discover upnp router information
        if (pRef->pProtoUpnp != NULL)
        {
            if (pRef->iPeerPort != 0)
            {
                ProtoUpnpControl(pRef->pProtoUpnp, 'port', pRef->iPeerPort, 0, NULL);
                ProtoUpnpControl(pRef->pProtoUpnp, 'macr', 'upnp', 0, NULL);
            }
            else
            {
                ProtoUpnpControl(pRef->pProtoUpnp, 'macr', 'dscg', 0, NULL);
            }
        }
    }
    else
    {
        NetPrintf(("netconnwin: NetConnConnect() ignored because already connected!\n"));
    }
    return(0);
}

/*F*************************************************************************************************/
/*!
    \Function    NetConnDisconnect

    \Description
        Used to bring down the network connection. After calling this, it would
        be necessary to call NetConnConnect to bring the connection back up or
        NetConnShutdown to completely shutdown all network support.

    \Output
        int32_t     - negative=error, zero=success

    \Version 02/09/02 (GWS)
*/
/*************************************************************************************************F*/
int32_t NetConnDisconnect(void)
{
    NetConnRefT *pRef = _NetConn_pRef;

    if (pRef->eState == ST_CONN)
    {
        // shutdown the interfaces (but leave API running)
        pRef->eState = ST_INIT;
    }

    // abort upnp operations
    if (pRef->pProtoUpnp != NULL)
    {
        ProtoUpnpControl(pRef->pProtoUpnp, 'abrt', 0, 0, NULL);
    }

    return(0);
}

/*F********************************************************************************/
/*!
    \Function NetConnControl

    \Description
        Set module behavior based on input selector.

    \Input  iControl    - input selector
    \Input  iValue      - selector input
    \Input  iValue2     - selector input
    \Input *pValue      - selector input
    \Input *pValue2     - selector input

    \Output
        int32_t         - selector result

    \Notes
        iControl can be one of the following:

        \verbatim
            snam: set DirtyCert service name
        \endverbatim

        Unhandled selectors are passed through to NetConnCommonControl()

    \Version 04/27/2006 (jbrookes)
*/
/********************************************************************************F*/
int32_t NetConnControl(int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue, void *pValue2)
{
    NetConnRefT *pRef = _NetConn_pRef;

    // make sure module is started before allowing any other control calls
    if (pRef == NULL)
    {
        NetPrintf(("netconnwin: warning - calling NetConnControl() while module is not initialized\n"));
        return(-1);
    }

    // set dirtycert service name
    if (iControl == 'snam')
    {
        return(DirtyCertControl('snam', 0, 0, pValue));
    }

    // pass through unhandled selectors to NetConnCommon
    return(NetConnCommonControl(&pRef->Common, iControl, iValue, iValue2, pValue, pValue2));
}

/*F*************************************************************************************************/
/*!
    \Function NetConnStatus

    \Description
        Check general network connection status. Different selectors return
        different status attributes.

    \Input iKind    - status selector
    \Input iData    - (optional) selector specific
    \Input *pBuf    - (optional) pointer to output buffer
    \Input iBufSize - (optional) size of output buffer

    \Output
        int32_t     - selector specific

    \Notes
        iKind can be one of the following:

        \verbatim
            addr: ip address of client
            affn: get the thread cpu affinity setting
            bbnd: broadband true or false
            conn: true/false indication of whether connection in progress
            ethr: mac address of adapter based on iData (0=first, otherwise=first active) (returned in pBuf), 0=success, negative=error
            locl: return locality (Windows locale) for local system, ex. 'enUS'
                  For unrecognized locale, 'zzZZ' will be returned.
            locn: return location (Windows location) for local system, ex. 'zzCA'
                  The language part of the return value should be ignored by the caller.
                  If local system has 'no location' set, 'zzZZ' will be returned.
            macx: mac address of adapter based on iData (0=first, otherwise=first active) (returned in pBuf), 0=success, negative=error
            onln: true/false indication of whether network is operational
            open: true/false indication of whether network code running
            type: NETCONN_IFTYPE_*
            upnp: return protoupnp port info, if available
            vers: return DirtySDK version
        \endverbatim

        Unhandled selectors are passed through to NetConnCommonStatus()

    \Version 03/10/2001 (gschaefer)
*/
/*************************************************************************************************F*/
int32_t NetConnStatus(int32_t iKind, int32_t iData, void *pBuf, int32_t iBufSize)
{
    NetConnRefT *pRef = _NetConn_pRef;

    // see if network code is initialized
    if (iKind == 'open')
    {
        return(pRef != NULL);
    }
    // return DirtySDK version
    if (iKind == 'vers')
    {
        return(DIRTYSDK_VERSION);
    }

    // make sure module is started before allowing any other status calls
    if (pRef == NULL)
    {
        NetPrintf(("netconn: warning - calling NetConnStatus() while module is not initialized\n"));
        return(-1);
    }

    // return client ip address
    if (iKind == 'addr')
    {
        return(SocketGetLocalAddr());
    }

    // return the thread cpu affinity
    if (iKind == 'affn')
    {
        return(pRef->iThreadCpuAffinity);
    }

    // return whether we are broadband or not
    if (iKind == 'bbnd')
    {
        // assume broadband
        return(TRUE);
    }

    // return connection status
    if (iKind == 'conn')
    {
        return((pRef->eState == ST_CONN) ? '+onl' : '-dsc');
    }

    // return host mac address
    if ((iKind == 'ethr') || (iKind == 'macx'))
    {
        if (pBuf != NULL)
        {
            // clear out the buffer
            ds_memclr(pBuf, iBufSize);

            // find a valid MAC address
            return(_NetConnFindMacAddress(pBuf, iBufSize, iData == 0 ? FALSE : TRUE));
        }
    }

    // see if connected to ISP/LAN
    if (iKind == 'onln')
    {
        return(pRef->eState == ST_CONN);
    }

    // return what type of interface we are connected with
    if (iKind == 'type')
    {
        // assume broadband
        return(NETCONN_IFTYPE_ETHER);
    }

    // return upnp addportmap info, if available
    if (iKind == 'upnp')
    {
        // if protoupnp is available, and we've added a port map, return the external port for the port mapping
        if ((pRef->pProtoUpnp != NULL) && (ProtoUpnpStatus(pRef->pProtoUpnp, 'stat', NULL, 0) & PROTOUPNP_STATUS_ADDPORTMAP))
        {
            return(ProtoUpnpStatus(pRef->pProtoUpnp, 'extp', NULL, 0));
        }
    }

    // return Windows locale information
    if (iKind == 'locl')
    {
        return(_NetConnGetLocaleInfo(GetUserDefaultLangID()));
    }

    // return Windows location information
    if (iKind == 'locn')
    {
        GEOID iGeoID;
        char strCountry[8]; // ISO2, [8] is enough
        uint16_t uLanguage = LOBBYAPI_LANGUAGE_UNKNOWN, // language is always set to zz
                 uCountry = LOBBYAPI_COUNTRY_UNKNOWN; // default to ZZ

        // check if local system has location set
        if ((iGeoID = GetUserGeoID(GEOCLASS_NATION)) == GEOID_NOT_AVAILABLE)
        {
            return(LOBBYAPI_LOCALITY_UNKNOWN);
        }

        ds_memclr(strCountry, sizeof(strCountry));
        if ((GetGeoInfoA(iGeoID, GEO_ISO2, strCountry, sizeof(strCountry), 0) > 0)
            && (strlen(strCountry) == sizeof(uint16_t))
            && (ds_stricmp(strCountry, NETCONNWIN_COUNTRY_UNKNOWN_STR) != 0))
        {
            // a valid country code: 2-letter, and not equal to XX
            uCountry = LOBBYAPI_LocalizerTokenGetShortFromString(strCountry);
        }

        return(LOBBYAPI_LocalizerTokenCreate(uLanguage, uCountry));
    }

    // pass unrecognized options to NetConnCommon
    return(NetConnCommonStatus(&pRef->Common, iKind, iData, pBuf, iBufSize));
}

/*F*************************************************************************************************/
/*!
    \Function     NetConnShutdown

    \Description
        Shutdown the network code and return to idle state.

    \Input bStopEE     - ignored in PC implementation

    \Output
        negative=error, zero=success

    \Version 3/10/01 (GWS)
*/
/*************************************************************************************************F*/
int32_t NetConnShutdown(uint32_t bStopEE)
{
    NetConnRefT *pRef = _NetConn_pRef;

    // error see if already stopped
    if (pRef == NULL)
    {
        return(NETCONN_ERROR_NOTACTIVE);
    }

    return(_NetConnShutdownInternal(pRef, bStopEE));
}

/*F*************************************************************************************************/
/*!
    \Function    NetConnSleep

    \Description
        Sleep the application (yield thread) for some number of milliseconds.

    \Input iMilliSecs    - number of milliseconds to block for

    \Output
        None

    \Version 04/30/02 (GWS)
*/
/*************************************************************************************************F*/
void NetConnSleep(int32_t iMilliSecs)
{
    Sleep(iMilliSecs);
}
