Add 32 and 64 bit FSUIPC library projects

Summary:
So far we were linking against the prebuilt FSUIPC user library,
compiled with VS2010 many years ago. The source of this user library is
part of the FSUIPC SDK, so we can benefit from modern compilers and
include it into our source tree.
This version contains some very small wide char fixes compared to the
official FSUIPC SDK source.
It also includes the new 64 bit version.

Reviewers: #swift_pilot_client, msutcliffe

Reviewed By: #swift_pilot_client, msutcliffe

Differential Revision: https://dev.swift-project.org/D29
This commit is contained in:
Roland Winklmeier
2017-06-23 00:04:34 +02:00
parent a00108af35
commit 5a40e86ecf
19 changed files with 1177 additions and 31 deletions

View File

@@ -8,10 +8,7 @@ TEMPLATE = lib
CONFIG += plugin shared
CONFIG += blackmisc blackcore
LIBS += -lsimulatorfscommon -lFSUIPC_User -luuid
# required for FSUIPC
win32:!win32-g++*: QMAKE_LFLAGS += /NODEFAULTLIB:LIBC.lib
LIBS += -lsimulatorfscommon -lfsuipc -luuid
DEPENDPATH += . $$SourceRoot/src
INCLUDEPATH += . $$SourceRoot/src

View File

@@ -14,12 +14,14 @@ INCLUDEPATH += . $$SourceRoot/src
SOURCES += *.cpp
HEADERS += *.h
contains(BLACK_CONFIG, FSUIPC): DEFINES += SWIFT_USING_FSUIPC
LIBS += -lFSUIPC_User
# required for FSUIPC
win32:!win32-g++*: QMAKE_LFLAGS += /NODEFAULTLIB:LIBC.lib
contains(BLACK_CONFIG, FSUIPC) {
equals(WORD_SIZE,32) {
DEFINES += SWIFT_USING_FSUIPC32
}
equals(WORD_SIZE,64) {
DEFINES += SWIFT_USING_FSUIPC64
}
}
DESTDIR = $$DestRoot/lib

View File

@@ -7,7 +7,7 @@
* contained in the LICENSE file.
*/
#ifndef SWIFT_USING_FSUIPC
#if !defined(SWIFT_USING_FSUIPC32) && !defined(SWIFT_USING_FSUIPC64)
#include "fsuipc.h"
@@ -75,4 +75,4 @@ namespace BlackSimPlugin
} // namespace
} // namespace
#endif //SWIFT_USING_FSUIPC
#endif // !defined(SWIFT_USING_FSUIPC32) && !defined(SWIFT_USING_FSUIPC64)

View File

@@ -7,7 +7,7 @@
* contained in the LICENSE file.
*/
#ifdef SWIFT_USING_FSUIPC
#if defined(SWIFT_USING_FSUIPC32) || defined(SWIFT_USING_FSUIPC64)
#ifndef NOMINMAX
#define NOMINMAX
@@ -16,8 +16,16 @@
#include "fsuipc.h"
#include <windows.h>
// bug in FSUIPC_User.h, windows.h not included, so we have to import it first
#include "FSUIPC/FSUIPC_User.h"
#include "FSUIPC/NewWeather.h"
#ifdef SWIFT_USING_FSUIPC32
#include "../fsuipc32/FSUIPC_User.h"
#include "../fsuipc32/NewWeather.h"
#endif
#ifdef SWIFT_USING_FSUIPC64
#include "../fsuipc64/FSUIPC_User64.h"
#include "../fsuipc64/NewWeather.h"
#endif
#include "blackmisc/simulation/fscommon/bcdconversions.h"
#include "blackmisc/logmessage.h"
@@ -500,4 +508,4 @@ namespace BlackSimPlugin
} // namespace
} // namespace
#endif //SWIFT_USING_FSUIPC
#endif // defined(SWIFT_USING_FSUIPC32) || defined(SWIFT_USING_FSUIPC64)

View File

@@ -0,0 +1,60 @@
#ifndef _FSUIPC_H_
#define _FSUIPC_H_
// Supported Sims
#define SIM_ANY 0
#define SIM_FS98 1
#define SIM_FS2K 2
#define SIM_CFS2 3
#define SIM_CFS1 4
#define SIM_FLY 5
#define SIM_FS2K2 6
#define SIM_FS2K4 7
#define SIM_FSX 8
#define SIM_ESP 9
#define SIM_P3D 10
// Error numbers
#define FSUIPC_ERR_OK 0
#define FSUIPC_ERR_OPEN 1 // Attempt to Open when already Open
#define FSUIPC_ERR_NOFS 2 // Cannot link to FSUIPC or WideClient
#define FSUIPC_ERR_REGMSG 3 // Failed to Register common message with Windows
#define FSUIPC_ERR_ATOM 4 // Failed to create Atom for mapping filename
#define FSUIPC_ERR_MAP 5 // Failed to create a file mapping object
#define FSUIPC_ERR_VIEW 6 // Failed to open a view to the file map
#define FSUIPC_ERR_VERSION 7 // Incorrect version of FSUIPC, or not FSUIPC
#define FSUIPC_ERR_WRONGFS 8 // Sim is not version requested
#define FSUIPC_ERR_NOTOPEN 9 // Call cannot execute, link not Open
#define FSUIPC_ERR_NODATA 10 // Call cannot execute: no requests accumulated
#define FSUIPC_ERR_TIMEOUT 11 // IPC timed out all retries
#define FSUIPC_ERR_SENDMSG 12 // IPC sendmessage failed all retries
#define FSUIPC_ERR_DATA 13 // IPC request contains bad data
#define FSUIPC_ERR_RUNNING 14 // Maybe running on WideClient, but FS not running on Server, or wrong FSUIPC
#define FSUIPC_ERR_SIZE 15 // Read or Write request cannot be added, memory for Process is full
#ifdef __cplusplus
extern "C" {
#endif
// Globals accessible from main code
extern DWORD FSUIPC_Version; // HIWORD is 1000 x Version Number, minimum 1998
// LOWORD is build letter, with a = 1 etc. For 1998 this must be at least 5 (1998e)
extern DWORD FSUIPC_FS_Version;
// FS98=1, FS2k=2, CFS2=3. See above.
extern DWORD FSUIPC_Lib_Version;
// HIWORD is 1000 x version, LOWORD is build letter, a = 1 etc.
// Library routines
extern BOOL FSUIPC_Open(DWORD dwFSReq, DWORD *pdwResult); // For use externally (IPCuser.lib)
extern BOOL FSUIPC_Open2(DWORD dwFSReq, DWORD *pdwResult, BYTE *pMem, DWORD dwSize); // For use internally (ModuleUser.lib)
extern void FSUIPC_Close(void);
extern BOOL FSUIPC_Read(DWORD dwOffset, DWORD dwSize, void *pDest, DWORD *pdwResult);
extern BOOL FSUIPC_ReadSpecial(DWORD dwOffset, DWORD dwSize, void *pDest, DWORD *pdwResult);
extern BOOL FSUIPC_Write(DWORD dwOffset, DWORD dwSize, void *pSrce, DWORD *pdwResult);
extern BOOL FSUIPC_Process(DWORD *pdwResult);
#ifdef __cplusplus
}
#endif
#endif // _FSUIPC_H_

View File

@@ -0,0 +1,338 @@
/* IPCUSER.C User interface library for FSUIPC
*******************************************************************************
Started: 28th November 2000
With acknowledgements to Adam Szofran (author of original FS6IPC).
******************************************************************************/
#define LIB_VERSION 2002 // 2.002
#define MAX_SIZE 0x7F00 // Largest data (kept below 32k to avoid
// any possible 16-bit sign problems)
#include "IPCuser.h"
/******************************************************************************
IPC client stuff
******************************************************************************/
DWORD FSUIPC_Version = 0;
DWORD FSUIPC_FS_Version = 0;
DWORD FSUIPC_Lib_Version = LIB_VERSION;
static HWND m_hWnd = 0; // FS6 window handle
static UINT m_msg = 0; // id of registered window message
static ATOM m_atom = 0; // global atom containing name of file-mapping object
static HANDLE m_hMap = 0; // handle of file-mapping object
static BYTE* m_pView = 0; // pointer to view of file-mapping object
static BYTE* m_pNext = 0;
/******************************************************************************
FSUIPC_Close
******************************************************************************/
// Stop the client
void FSUIPC_Close(void)
{ m_hWnd = 0;
m_msg = 0;
if (m_atom)
{ GlobalDeleteAtom(m_atom);
m_atom = 0;
}
if (m_pView)
{ UnmapViewOfFile((LPVOID)m_pView);
m_pView = 0;
}
if (m_hMap)
{ CloseHandle(m_hMap);
m_hMap = 0;
}
}
/******************************************************************************
FSUIPC_Open
******************************************************************************/
// Start the client
// return: TRUE if successful, FALSE otherwise
BOOL FSUIPC_Open(DWORD dwFSReq, DWORD *pdwResult)
{ wchar_t szName[MAX_PATH];
static int nTry = 0;
BOOL fWideFS = FALSE;
int i = 0;
// abort if already started
if (m_pView)
{ *pdwResult = FSUIPC_ERR_OPEN;
return FALSE;
}
// Clear version information, so know when connected
FSUIPC_Version = FSUIPC_FS_Version = 0;
// Connect via FSUIPC, which is known to be FSUIPC's own
// and isn't subject to user modificiation
m_hWnd = FindWindowEx(NULL, NULL, L"UIPCMAIN", NULL);
if (!m_hWnd)
{ // If there's no UIPCMAIN, we may be using WideClient
// which only simulates FS98
m_hWnd = FindWindowEx(NULL, NULL, L"FS98MAIN", NULL);
fWideFS = TRUE;
if (!m_hWnd)
{ *pdwResult = FSUIPC_ERR_NOFS;
return FALSE;
}
}
// register the window message
m_msg = RegisterWindowMessage(FS6IPC_MSGNAME1);
if (m_msg == 0)
{ *pdwResult = FSUIPC_ERR_REGMSG;
return FALSE;
}
// create the name of our file-mapping object
nTry++; // Ensures a unique string is used in case user closes and reopens
wsprintf(szName, FS6IPC_MSGNAME1 ":%X:%X", GetCurrentProcessId(), nTry);
// stuff the name into a global atom
m_atom = GlobalAddAtom(szName);
if (m_atom == 0)
{ *pdwResult = FSUIPC_ERR_ATOM;
FSUIPC_Close();
return FALSE;
}
// create the file-mapping object
m_hMap = CreateFileMapping(
(HANDLE)0xFFFFFFFF, // use system paging file
NULL, // security
PAGE_READWRITE, // protection
0, MAX_SIZE+256, // size
szName); // name
if ((m_hMap == 0) || (GetLastError() == ERROR_ALREADY_EXISTS))
{ *pdwResult = FSUIPC_ERR_MAP;
FSUIPC_Close();
return FALSE;
}
// get a view of the file-mapping object
m_pView = (BYTE*)MapViewOfFile(m_hMap, FILE_MAP_WRITE, 0, 0, 0);
if (m_pView == NULL)
{ *pdwResult = FSUIPC_ERR_VIEW;
FSUIPC_Close();
return FALSE;
}
// Okay, now determine FSUIPC version AND FS type
m_pNext = m_pView;
// Try up to 5 times with a 100mSec rest between each
// Note that WideClient returns zeroes initially, whilst waiting
// for the Server to get the data
while ((i++ < 5) && ((FSUIPC_Version == 0) || (FSUIPC_FS_Version == 0)))
{ // Read FSUIPC version
if (!FSUIPC_Read(0x3304, 4, &FSUIPC_Version, pdwResult))
{ FSUIPC_Close();
return FALSE;
}
// and FS version and validity check pattern
if (!FSUIPC_Read(0x3308, 4, &FSUIPC_FS_Version, pdwResult))
{ FSUIPC_Close();
return FALSE;
}
// Write our Library version number to a special read-only offset
// This is to assist diagnosis from FSUIPC logging
// But only do this on first try
if ((i < 2) && !FSUIPC_Write(0x330a, 2, &FSUIPC_Lib_Version, pdwResult))
{ FSUIPC_Close();
return FALSE;
}
// Actually send the requests and get the responses ("process")
if (!FSUIPC_Process(pdwResult))
{ FSUIPC_Close();
return FALSE;
}
// Maybe running on WideClient, and need another try
Sleep(100); // Give it a chance
}
// Only allow running on FSUIPC 1.998e or later
// with correct check pattern 0xFADE
if ((FSUIPC_Version < 0x19980005) || ((FSUIPC_FS_Version & 0xFFFF0000L) != 0xFADE0000))
{ *pdwResult = fWideFS ? FSUIPC_ERR_RUNNING : FSUIPC_ERR_VERSION;
FSUIPC_Close();
return FALSE;
}
FSUIPC_FS_Version &= 0xffff; // Isolates the FS version number
if (dwFSReq && (dwFSReq != FSUIPC_FS_Version)) // Optional user specific FS request
{ *pdwResult = FSUIPC_ERR_WRONGFS;
FSUIPC_Close();
return FALSE;
}
*pdwResult = FSUIPC_ERR_OK;
return TRUE;
}
/******************************************************************************
FSUIPC_Process
******************************************************************************/
BOOL FSUIPC_Process(DWORD *pdwResult)
{ DWORD dwError;
DWORD *pdw;
FS6IPC_READSTATEDATA_HDR *pHdrR;
FS6IPC_WRITESTATEDATA_HDR *pHdrW;
int i = 0;
if (!m_pView)
{ *pdwResult = FSUIPC_ERR_NOTOPEN;
return FALSE;
}
if (m_pView == m_pNext)
{ *pdwResult = FSUIPC_ERR_NODATA;
return FALSE;
}
ZeroMemory(m_pNext, 4); // Terminator
m_pNext = m_pView;
// send the request (allow up to 9 tries)
while ((++i < 10) && !SendMessageTimeout(
m_hWnd, // FS6 window handle
m_msg, // our registered message id
m_atom, // wParam: name of file-mapping object
0, // lParam: offset of request into file-mapping obj
SMTO_BLOCK, // halt this thread until we get a response
2000, // time out interval
&dwError)) // return value
{ Sleep(100); // Allow for things to happen
}
if (i >= 10) // Failed all tries?
{ *pdwResult = GetLastError() == 0 ? FSUIPC_ERR_TIMEOUT : FSUIPC_ERR_SENDMSG;
return FALSE;
}
if (dwError != FS6IPC_MESSAGE_SUCCESS)
{ *pdwResult = FSUIPC_ERR_DATA; // FSUIPC didn't like something in the data!
return FALSE;
}
// Decode and store results of Read requests
pdw = (DWORD *) m_pView;
while (*pdw)
{ switch (*pdw)
{ case FS6IPC_READSTATEDATA_ID:
pHdrR = (FS6IPC_READSTATEDATA_HDR *) pdw;
m_pNext += sizeof(FS6IPC_READSTATEDATA_HDR);
if (pHdrR->pDest && pHdrR->nBytes)
CopyMemory(pHdrR->pDest, m_pNext, pHdrR->nBytes);
m_pNext += pHdrR->nBytes;
break;
case FS6IPC_WRITESTATEDATA_ID:
// This is a write, so there's no returned data to store
pHdrW = (FS6IPC_WRITESTATEDATA_HDR *) pdw;
m_pNext += sizeof(FS6IPC_WRITESTATEDATA_HDR) + pHdrW->nBytes;
break;
default:
// Error! So terminate the scan
*pdw = 0;
break;
}
pdw = (DWORD *) m_pNext;
}
m_pNext = m_pView;
*pdwResult = FSUIPC_ERR_OK;
return TRUE;
}
/******************************************************************************
FSUIPC_Read
******************************************************************************/
BOOL FSUIPC_Read(DWORD dwOffset, DWORD dwSize, void *pDest, DWORD *pdwResult)
{ FS6IPC_READSTATEDATA_HDR *pHdr = (FS6IPC_READSTATEDATA_HDR *) m_pNext;
// Check link is open
if (!m_pView)
{ *pdwResult = FSUIPC_ERR_NOTOPEN;
return FALSE;
}
// Check have space for this request (including terminator)
if (((m_pNext - m_pView) + 4 + (dwSize + sizeof(FS6IPC_READSTATEDATA_HDR))) > MAX_SIZE)
{ *pdwResult = FSUIPC_ERR_SIZE;
return FALSE;
}
// Initialise header for read request
pHdr->dwId = FS6IPC_READSTATEDATA_ID;
pHdr->dwOffset = dwOffset;
pHdr->nBytes = dwSize;
pHdr->pDest = (BYTE *) pDest;
// Zero the reception area, so rubbish won't be returned
if (dwSize) ZeroMemory(&m_pNext[sizeof(FS6IPC_READSTATEDATA_HDR)], dwSize);
// Update the pointer ready for more data
m_pNext += sizeof(FS6IPC_READSTATEDATA_HDR) + dwSize;
*pdwResult = FSUIPC_ERR_OK;
return TRUE;
}
/******************************************************************************
FSUIPC_Write
******************************************************************************/
BOOL FSUIPC_Write(DWORD dwOffset, DWORD dwSize, void *pSrce, DWORD *pdwResult)
{ FS6IPC_WRITESTATEDATA_HDR *pHdr = (FS6IPC_WRITESTATEDATA_HDR *) m_pNext;
// check link is open
if (!m_pView)
{ *pdwResult = FSUIPC_ERR_NOTOPEN;
return FALSE;
}
// Check have spce for this request (including terminator)
if (((m_pNext - m_pView) + 4 + (dwSize + sizeof(FS6IPC_WRITESTATEDATA_HDR))) > MAX_SIZE)
{ *pdwResult = FSUIPC_ERR_SIZE;
return FALSE;
}
// Initialise header for write request
pHdr->dwId = FS6IPC_WRITESTATEDATA_ID;
pHdr->dwOffset = dwOffset;
pHdr->nBytes = dwSize;
// Copy in the data to be written
if (dwSize) CopyMemory(&m_pNext[sizeof(FS6IPC_WRITESTATEDATA_HDR)], pSrce, dwSize);
// Update the pointer ready for more data
m_pNext += sizeof(FS6IPC_WRITESTATEDATA_HDR) + dwSize;
*pdwResult = FSUIPC_ERR_OK;
return TRUE;
}
/******************************************************************************
End of IPCuser module
******************************************************************************/

View File

@@ -0,0 +1,29 @@
#include <windows.h>
#include "FSUIPC_User.h"
#define FS6IPC_MSGNAME1 L"FsasmLib:IPC"
#define FS6IPC_MESSAGE_SUCCESS 1
#define FS6IPC_MESSAGE_FAILURE 0
// IPC message types
#define FS6IPC_READSTATEDATA_ID 1
#define FS6IPC_WRITESTATEDATA_ID 2
// read request structure
typedef struct tagFS6IPC_READSTATEDATA_HDR
{
DWORD dwId; // FS6IPC_READSTATEDATA_ID
DWORD dwOffset; // state table offset
DWORD nBytes; // number of bytes of state data to read
void* pDest; // destination buffer for data (client use only)
} FS6IPC_READSTATEDATA_HDR;
// write request structure
typedef struct tagFS6IPC_WRITESTATEDATA_HDR
{
DWORD dwId; // FS6IPC_WRITESTATEDATA_ID
DWORD dwOffset; // state table offset
DWORD nBytes; // number of bytes of state data to write
} FS6IPC_WRITESTATEDATA_HDR;

View File

@@ -0,0 +1,120 @@
// Wind structure (size 16 bytes)
typedef struct _NewWind
{ unsigned short UpperAlt; // Metres
unsigned short Speed; // Knots
unsigned short Gust; // Max gust speed difference, knots
unsigned short Direction; // usual 65536 = 360 units
unsigned char Turbulence; // 0-4
unsigned char Shear; // 0-3
unsigned short Variance; // direction variability, same units as Direction
unsigned short SpeedFract; // 1/65536ths of Knots, for more accurate vector calcs
unsigned short GapAbove; // Metres
// This is the gap from the top of surface altitude to first layer's base
// GapAbove is ignored in layers other than the surface layer, and needs
// FSUIC 4.748 or 3.998k minimum.
} NewWind;
// Visibility structure (size 8 bytes)
typedef struct _NewVis
{ unsigned short UpperAlt; // Metres
signed short LowerAlt; // Metres
unsigned short Range; // in 1/100ths sm
unsigned short Spare;
} NewVis;
// Cloud structure (size 16 bytes)
typedef struct _NewCloud
{ unsigned short UpperAlt; // Metres
unsigned short LowerAlt; // Metres
unsigned short Deviation; // Metres
unsigned char Coverage; // Octas, 0-8
unsigned char Type; // 1-10
unsigned char Turbulence; // 0-4
unsigned char Icing; // 0-4
signed short PrecipBase; // Metres
unsigned char PrecipType; // 0-2
unsigned char PrecipRate; // 0-5
unsigned char TopShape; // ?
unsigned char Spare;
} NewCloud;
// Temperature structure (size 8 bytes)
typedef struct _NewTemp
{ unsigned short Alt; // Metres
short Day; // Degrees C
short DayNightVar; // Degrees C
short DewPoint; // Degrees C
} NewTemp;
// Pressure structure (size 4 bytes)
typedef struct _NewPress
{ unsigned short Pressure; // 16 x mb
short Drift; // ? Maybe
} NewPress;
// Structure mapped to FSUIPC offsets
// C000, C400, C800 and CC00
typedef struct _NewWeather
{ unsigned short uCommand; // C000 C400 C800 CC00
unsigned short uFlags; // C002 C402 C802 CC02 // Not used until FSX - see below
unsigned int ulSignature; // C004 C404 C804 CC04
char chICAO[4]; // C008 C408 C808 CC08
unsigned short uDynamics; // C00C C40C C80C CC0C // 0=none, 4=extreme
unsigned short uSeconds; // C00E C40E C80E CC0E // FSX only -- see below
double dLatitude; // C010 C410 C810 CC10 // LLA zero for GLOB or unknown ICAO
double dLongitude; // C018 C418 C818 CC18
int nElevation; // C020 C420 C820 CC20 // metres * 65536
unsigned int ulTimeStamp; // C024 C424 C824 CC24 // mSecs since start of session
NewPress Press; // C028 C428 C828 CC28
NewVis Vis; // C02C C42C C82C CC2C // Base Vis -- for upper Vis layers see
int nTempCtr; // C034 C434 C834 CC34 // Number of temperature layers
NewTemp Temp[24]; // C038 C438 C838 CC38
int nWindsCtr; // C0F8 C4F8 C8F8 CCF8 // Number of wind layers
NewWind Wind[24]; // C0FC C4FC C8FC CCFC
int nCloudsCtr; // C27C C67C CA7C CE7C // Number of Cloud layers (max 16 in FSX, was 24
NewCloud Cloud[16]; // C280 C680 CA80 CE80
char chUTCstamp[6]; // C380 C780 CB80 CF80 // UTC time stamp on METAR (internal use)
unsigned short fWriteback; // C386 C786 CB86 CF86 // Flags for writing back to FSX (internal use only)
unsigned long ulSettingTimeStamp; // C388 c788 CB88 CF88 // Timestamp METAR sent (internal use)
int nUpperVisCtr; // C38C C78C CB8C CF8C // Number of upper Vis layer (new in FSX)
NewVis UpperVis[12]; // C390 C790 CB90 CF90
unsigned short uSpare[8]; // C3F0 C7F0 CBF0 CFF0
} NewWeather;
#define MAXVISLAYERS 12
#define MAXTEMPLAYERS 24
#define MAXWINDLAYERS 24
#define MAXCLOUDLAYERS 16
// Commands (for set weather area only)
#define NW_SET 1 // Set weather via FSUIPC user filters
#define NW_SETEXACT 2 // Set weather bypassing user filters
#define NW_CLEAR 3 // Clear all weather, but leave dynamic setting alone
#define NW_DYNAMICS 4 // Set weather dynamics (from uDynamics value)
#define NW_GLOBAL 5 // (FSX) Put FS into Global weather mode
// You sohuld write only 'GLOB' weather after this, which will be applied everywhere.
#define NW_METAR 128 // DON'T USE! (Internal setting when weather written via string at B000. (FSX))
#define NW_SET_PENDING 257 // Set weather using filters, but don't activate in FS yet
#define NW_SETEXACT_PENDING 258 // Set weather bypassing filters, but don't activate in FS yet
#define NW_ACTIVATE 256 // Activate pending weather settings
// Flags for FSX weather setting
#define NWF_SECONDS 1
/* Set this flag if "Seconds" value is to be used in setting weather in FSX
This applies to weather set via NWI values or via the NEXT Metar string
written to B000. It's a "one shot" flag, cleared after use.
The Seconds value determines whether the weather change is effective
immediately or after a delay, gradually "blending" into the existing
weather.
*/
// The 4 distinct areas are used as follows:
// C000 - read only, maintains current interpolated weather at aircraft
// C400 - read only, maintains last written global weather values
// C800 - write area to set weather according to Command, flags, etc etc.
// CC00 - selected read (via signature + ICAO), reads weather at weather station or GLOB

View File

@@ -0,0 +1,20 @@
load(common_pre)
requires(equals(WORD_SIZE,32))
QT += core dbus xml network
TARGET = fsuipc
TEMPLATE = lib
CONFIG += staticlib
DEPENDPATH += . $$SourceRoot/src
INCLUDEPATH += . $$SourceRoot/src
HEADERS += *.h
SOURCES += *.c
DESTDIR = $$DestRoot/lib
load(common_post)

View File

@@ -0,0 +1,62 @@
#ifndef _FSUIPC_H_
#define _FSUIPC_H_
// Supported Sims
#define SIM_ANY 0
#define SIM_FS98 1
#define SIM_FS2K 2
#define SIM_CFS2 3
#define SIM_CFS1 4
#define SIM_FLY 5
#define SIM_FS2K2 6
#define SIM_FS2K4 7
#define SIM_FSX 8
#define SIM_ESP 9
#define SIM_P3D 10
#define SIM_FSX64 11
#define SIM_P3D64 12
// Error numbers
#define FSUIPC_ERR_OK 0
#define FSUIPC_ERR_OPEN 1 // Attempt to Open when already Open
#define FSUIPC_ERR_NOFS 2 // Cannot link to FSUIPC or WideClient
#define FSUIPC_ERR_REGMSG 3 // Failed to Register common message with Windows
#define FSUIPC_ERR_ATOM 4 // Failed to create Atom for mapping filename
#define FSUIPC_ERR_MAP 5 // Failed to create a file mapping object
#define FSUIPC_ERR_VIEW 6 // Failed to open a view to the file map
#define FSUIPC_ERR_VERSION 7 // Incorrect version of FSUIPC, or not FSUIPC
#define FSUIPC_ERR_WRONGFS 8 // Sim is not version requested
#define FSUIPC_ERR_NOTOPEN 9 // Call cannot execute, link not Open
#define FSUIPC_ERR_NODATA 10 // Call cannot execute: no requests accumulated
#define FSUIPC_ERR_TIMEOUT 11 // IPC timed out all retries
#define FSUIPC_ERR_SENDMSG 12 // IPC sendmessage failed all retries
#define FSUIPC_ERR_DATA 13 // IPC request contains bad data
#define FSUIPC_ERR_RUNNING 14 // Maybe running on WideClient, but FS not running on Server, or wrong FSUIPC
#define FSUIPC_ERR_SIZE 15 // Read or Write request cannot be added, memory for Process is full
#ifdef __cplusplus
extern "C" {
#endif
// Globals accessible from main code
extern DWORD FSUIPC_Version; // HIWORD is 1000 x Version Number, minimum 1998
// LOWORD is build letter, with a = 1 etc. For 1998 this must be at least 5 (1998e)
extern DWORD FSUIPC_FS_Version;
// FS98=1, FS2k=2, CFS2=3. See above.
extern DWORD FSUIPC_Lib_Version;
// HIWORD is 1000 x version, LOWORD is build letter, a = 1 etc.
// Library routines
extern BOOL FSUIPC_Open(DWORD dwFSReq, DWORD *pdwResult); // For use externally (IPCuser.lib)
extern BOOL FSUIPC_Open2(DWORD dwFSReq, DWORD *pdwResult, BYTE *pMem, DWORD dwSize); // For use internally (ModuleUser.lib)
extern void FSUIPC_Close(void);
extern BOOL FSUIPC_Read(DWORD dwOffset, DWORD dwSize, void *pDest, DWORD *pdwResult);
extern BOOL FSUIPC_ReadSpecial(DWORD dwOffset, DWORD dwSize, void *pDest, DWORD *pdwResult);
extern BOOL FSUIPC_Write(DWORD dwOffset, DWORD dwSize, void *pSrce, DWORD *pdwResult);
extern BOOL FSUIPC_Process(DWORD *pdwResult);
#ifdef __cplusplus
};
#endif
#endif // _FSUIPC_H_

View File

@@ -0,0 +1,347 @@
/* IPCUSER64.C User interface library for FSUIPC
*******************************************************************************
Started: 28th November 2000
With acknowledgements to Adam Szofran (author of original FS6IPC).
******************************************************************************/
#define LIB_VERSION 2002 // 2.002
#define MAX_SIZE 0x7F00 // Largest data (kept below 32k to avoid
// any possible 16-bit sign problems)
#include "IPCuser64.h"
#include "FSUIPC_User64.h"
#define FS6IPC_MSGNAME1 L"FsasmLib:IPC"
/******************************************************************************
IPC client stuff
******************************************************************************/
DWORD FSUIPC_Version = 0;
DWORD FSUIPC_FS_Version = 0;
DWORD FSUIPC_Lib_Version = LIB_VERSION;
static HWND m_hWnd = 0; // FS6 window handle
static UINT m_msg = 0; // id of registered window message
static ATOM m_atom = 0; // global atom containing name of file-mapping object
static HANDLE m_hMap = 0; // handle of file-mapping object
static BYTE* m_pView = 0; // pointer to view of file-mapping object
static BYTE* m_pNext = 0;
/******************************************************************************
FSUIPC_Close
******************************************************************************/
// Stop the client
void FSUIPC_Close(void)
{ m_hWnd = 0;
m_msg = 0;
if (m_atom)
{ GlobalDeleteAtom(m_atom);
m_atom = 0;
}
if (m_pView)
{ UnmapViewOfFile((LPVOID)m_pView);
m_pView = 0;
}
if (m_hMap)
{ CloseHandle(m_hMap);
m_hMap = 0;
}
}
/******************************************************************************
FSUIPC_Open
******************************************************************************/
// Start the client
// return: TRUE if successful, FALSE otherwise
BOOL FSUIPC_Open(DWORD dwFSReq, DWORD *pdwResult)
{ wchar_t szName[MAX_PATH];
static int nTry = 0;
int i = 0;
// abort if already started
if (m_pView)
{ *pdwResult = FSUIPC_ERR_OPEN;
return FALSE;
}
// Clear version information, so know when connected
FSUIPC_Version = FSUIPC_FS_Version = 0;
// Connect via FSUIPC, which is known to be FSUIPC's own
// and isn't subject to user modificiation
m_hWnd = FindWindowEx(NULL, NULL, L"UIPCMAIN", NULL);
if (!m_hWnd)
{ *pdwResult = FSUIPC_ERR_NOFS;
return FALSE;
}
// register the window message
m_msg = RegisterWindowMessage(FS6IPC_MSGNAME1);
if (m_msg == 0)
{ *pdwResult = FSUIPC_ERR_REGMSG;
return FALSE;
}
// create the name of our file-mapping object
nTry++; // Ensures a unique string is used in case user closes and reopens
wsprintf(szName, FS6IPC_MSGNAME1 ":%X:%X", GetCurrentProcessId(), nTry);
// stuff the name into a global atom
m_atom = GlobalAddAtom(szName);
if (m_atom == 0)
{ *pdwResult = FSUIPC_ERR_ATOM;
FSUIPC_Close();
return FALSE;
}
// create the file-mapping object
m_hMap = CreateFileMapping(
INVALID_HANDLE_VALUE, // use system paging file
NULL, // security
PAGE_READWRITE, // protection
0, MAX_SIZE+256, // size
szName); // name
if ((m_hMap == 0) || (GetLastError() == ERROR_ALREADY_EXISTS))
{ *pdwResult = FSUIPC_ERR_MAP;
FSUIPC_Close();
return FALSE;
}
// get a view of the file-mapping object
m_pView = (BYTE*)MapViewOfFile(m_hMap, FILE_MAP_WRITE, 0, 0, 0);
if (m_pView == NULL)
{ *pdwResult = FSUIPC_ERR_VIEW;
FSUIPC_Close();
return FALSE;
}
// Okay, now determine FSUIPC version AND FS type
m_pNext = m_pView;
// Try up to 5 times with a 100mSec rest between each
while ((i++ < 5) && ((FSUIPC_Version == 0) || (FSUIPC_FS_Version == 0)))
{ // Read FSUIPC version
if (!FSUIPC_Read(0x3304, 4, &FSUIPC_Version, pdwResult))
{ FSUIPC_Close();
return FALSE;
}
// and FS version and validity check pattern
if (!FSUIPC_Read(0x3308, 4, &FSUIPC_FS_Version, pdwResult))
{ FSUIPC_Close();
return FALSE;
}
// Write our Library version number to a special read-only offset
// This is to assist diagnosis from FSUIPC logging
// But only do this on first try
if ((i < 2) && !FSUIPC_Write(0x330a, 2, &FSUIPC_Lib_Version, pdwResult))
{ FSUIPC_Close();
return FALSE;
}
// Actually send the requests and get the responses ("process")
if (!FSUIPC_Process(pdwResult))
{ FSUIPC_Close();
return FALSE;
}
// Maybe running on WideClient, and need another try
Sleep(100); // Give it a chance
}
// Only allow running on FSUIPC 1.998e or later
// with correct check pattern 0xFADE
if ((FSUIPC_Version < 0x19980005) || ((FSUIPC_FS_Version & 0xFFFF0000L) != 0xFADE0000))
{ *pdwResult = FSUIPC_ERR_VERSION;
FSUIPC_Close();
return FALSE;
}
FSUIPC_FS_Version &= 0xffff; // Isolates the FS version number
if (dwFSReq && (dwFSReq != FSUIPC_FS_Version)) // Optional user specific FS request
{ *pdwResult = FSUIPC_ERR_WRONGFS;
FSUIPC_Close();
return FALSE;
}
*pdwResult = FSUIPC_ERR_OK;
return TRUE;
}
/******************************************************************************
FSUIPC_Process
******************************************************************************/
BOOL FSUIPC_Process(DWORD *pdwResult)
{ DWORD_PTR dwError;
DWORD *pdw;
F64IPC_READSTATEDATA_HDR *pHdrR;
FS6IPC_WRITESTATEDATA_HDR *pHdrW;
int i = 0;
if (!m_pView)
{ *pdwResult = FSUIPC_ERR_NOTOPEN;
return FALSE;
}
if (m_pView == m_pNext)
{ *pdwResult = FSUIPC_ERR_NODATA;
return FALSE;
}
ZeroMemory(m_pNext, 4); // Terminator
m_pNext = m_pView;
// send the request (allow up to 9 tries)
while ((++i < 10) && !SendMessageTimeout(
m_hWnd, // FS6 window handle
m_msg, // our registered message id
m_atom, // wParam: name of file-mapping object
0, // lParam: offset of request into file-mapping obj
SMTO_BLOCK, // halt this thread until we get a response
2000, // time out interval
&dwError)) // return value
{ Sleep(100); // Allow for things to happen
}
if (i >= 10) // Failed all tries?
{ *pdwResult = GetLastError() == 0 ? FSUIPC_ERR_TIMEOUT : FSUIPC_ERR_SENDMSG;
return FALSE;
}
if (dwError != FS6IPC_MESSAGE_SUCCESS)
{ *pdwResult = FSUIPC_ERR_DATA; // FSUIPC didn't like something in the data!
return FALSE;
}
// Decode and store results of Read requests
pdw = (DWORD *) m_pView;
while (*pdw)
{ switch (*pdw)
{ case F64IPC_READSTATEDATA_ID:
pHdrR = (F64IPC_READSTATEDATA_HDR *) pdw;
m_pNext += sizeof(F64IPC_READSTATEDATA_HDR);
if (pHdrR->pDest && pHdrR->nBytes)
CopyMemory(pHdrR->pDest, m_pNext, pHdrR->nBytes);
m_pNext += pHdrR->nBytes;
break;
case FS6IPC_WRITESTATEDATA_ID:
// This is a write, so there's no returned data to store
pHdrW = (FS6IPC_WRITESTATEDATA_HDR *) pdw;
m_pNext += sizeof(FS6IPC_WRITESTATEDATA_HDR) + pHdrW->nBytes;
break;
default:
// Error! So terminate the scan
*pdw = 0;
break;
}
pdw = (DWORD *) m_pNext;
}
m_pNext = m_pView;
*pdwResult = FSUIPC_ERR_OK;
return TRUE;
}
/******************************************************************************
FSUIPC_ReadCommon
******************************************************************************/
BOOL FSUIPC_ReadCommon(BOOL fSpecial, DWORD dwOffset, DWORD dwSize, void *pDest, DWORD *pResult)
{ F64IPC_READSTATEDATA_HDR *pHdr = (F64IPC_READSTATEDATA_HDR *) m_pNext;
if (!m_pView)
{ *pResult = FSUIPC_ERR_NOTOPEN;
return FALSE;
}
if (((m_pNext - m_pView) + (dwSize + sizeof(F64IPC_READSTATEDATA_HDR))) > MAX_SIZE)
{ *pResult = FSUIPC_ERR_SIZE;
return FALSE;
}
pHdr->dwId = F64IPC_READSTATEDATA_ID;
pHdr->dwOffset = dwOffset;
pHdr->nBytes = dwSize;
pHdr->pDest = (BYTE *) pDest;
// Initialise the reception area, so rubbish won't be returned
if (dwSize)
{ if (fSpecial) CopyMemory(&m_pNext[sizeof(F64IPC_READSTATEDATA_HDR)], pDest, dwSize);
else ZeroMemory(&m_pNext[sizeof(F64IPC_READSTATEDATA_HDR)], dwSize);
}
m_pNext += sizeof(F64IPC_READSTATEDATA_HDR) + dwSize;
*pResult = FSUIPC_ERR_OK;
return TRUE;
}
/******************************************************************************
FSUIPC_Read
******************************************************************************/
BOOL FSUIPC_Read(DWORD dwOffset, DWORD dwSize, void *pDest, DWORD *pResult)
{ return FSUIPC_ReadCommon(FALSE, dwOffset, dwSize, pDest, pResult);
}
/******************************************************************************
FSUIPC_ReadSpecial
******************************************************************************/
BOOL FSUIPC_ReadSpecial(DWORD dwOffset, DWORD dwSize, void *pDest, DWORD *pResult)
{ return FSUIPC_ReadCommon(TRUE, dwOffset, dwSize, pDest, pResult);
}
/******************************************************************************
FSUIPC_Write
******************************************************************************/
BOOL FSUIPC_Write(DWORD dwOffset, DWORD dwSize, void *pSrce, DWORD *pdwResult)
{ FS6IPC_WRITESTATEDATA_HDR *pHdr = (FS6IPC_WRITESTATEDATA_HDR *) m_pNext;
// check link is open
if (!m_pView)
{ *pdwResult = FSUIPC_ERR_NOTOPEN;
return FALSE;
}
// Check have spce for this request (including terminator)
if (((m_pNext - m_pView) + 4 + (dwSize + sizeof(FS6IPC_WRITESTATEDATA_HDR))) > MAX_SIZE)
{ *pdwResult = FSUIPC_ERR_SIZE;
return FALSE;
}
// Initialise header for write request
pHdr->dwId = FS6IPC_WRITESTATEDATA_ID;
pHdr->dwOffset = dwOffset;
pHdr->nBytes = dwSize;
// Copy in the data to be written
if (dwSize) CopyMemory(&m_pNext[sizeof(FS6IPC_WRITESTATEDATA_HDR)], pSrce, dwSize);
// Update the pointer ready for more data
m_pNext += sizeof(FS6IPC_WRITESTATEDATA_HDR) + dwSize;
*pdwResult = FSUIPC_ERR_OK;
return TRUE;
}
/******************************************************************************
End of IPCuser64 module
******************************************************************************/

View File

@@ -0,0 +1,32 @@
#include <windows.h>
#include "FSUIPC_User64.h"
#define FS6IPC_MESSAGE_SUCCESS 1
#define FS6IPC_MESSAGE_FAILURE 0
// IPC message types
#define F64IPC_READSTATEDATA_ID 4
#define FS6IPC_WRITESTATEDATA_ID 2
#pragma pack (push, r1, 1)
// read request structure
typedef struct tagF64IPC_READSTATEDATA_HDR
{
DWORD dwId; // F64IPC_READSTATEDATA_ID
DWORD dwOffset; // state table offset
DWORD nBytes; // number of bytes of state data to read
void* pDest; // destination buffer for data (client use only)
} F64IPC_READSTATEDATA_HDR;
// write request structure
typedef struct tagFS6IPC_WRITESTATEDATA_HDR
{
DWORD dwId; // FS6IPC_WRITESTATEDATA_ID
DWORD dwOffset; // state table offset
DWORD nBytes; // number of bytes of state data to write
} FS6IPC_WRITESTATEDATA_HDR;
#pragma pack (pop, r1)

View File

@@ -0,0 +1,120 @@
// Wind structure (size 16 bytes)
typedef struct _NewWind
{ unsigned short UpperAlt; // Metres
unsigned short Speed; // Knots
unsigned short Gust; // Max gust speed difference, knots
unsigned short Direction; // usual 65536 = 360 units
unsigned char Turbulence; // 0-4
unsigned char Shear; // 0-3
unsigned short Variance; // direction variability, same units as Direction
unsigned short SpeedFract; // 1/65536ths of Knots, for more accurate vector calcs
unsigned short GapAbove; // Metres
// This is the gap from the top of surface altitude to first layer's base
// GapAbove is ignored in layers other than the surface layer, and needs
// FSUIC 4.748 or 3.998k minimum.
} NewWind;
// Visibility structure (size 8 bytes)
typedef struct _NewVis
{ unsigned short UpperAlt; // Metres
signed short LowerAlt; // Metres
unsigned short Range; // in 1/100ths sm
unsigned short Spare;
} NewVis;
// Cloud structure (size 16 bytes)
typedef struct _NewCloud
{ unsigned short UpperAlt; // Metres
unsigned short LowerAlt; // Metres
unsigned short Deviation; // Metres
unsigned char Coverage; // Octas, 0-8
unsigned char Type; // 1-10
unsigned char Turbulence; // 0-4
unsigned char Icing; // 0-4
signed short PrecipBase; // Metres
unsigned char PrecipType; // 0-2
unsigned char PrecipRate; // 0-5
unsigned char TopShape; // ?
unsigned char Spare;
} NewCloud;
// Temperature structure (size 8 bytes)
typedef struct _NewTemp
{ unsigned short Alt; // Metres
short Day; // Degrees C
short DayNightVar; // Degrees C
short DewPoint; // Degrees C
} NewTemp;
// Pressure structure (size 4 bytes)
typedef struct _NewPress
{ unsigned short Pressure; // 16 x mb
short Drift; // ? Maybe
} NewPress;
// Structure mapped to FSUIPC offsets
// C000, C400, C800 and CC00
typedef struct _NewWeather
{ unsigned short uCommand; // C000 C400 C800 CC00
unsigned short uFlags; // C002 C402 C802 CC02 // Not used until FSX - see below
unsigned int ulSignature; // C004 C404 C804 CC04
char chICAO[4]; // C008 C408 C808 CC08
unsigned short uDynamics; // C00C C40C C80C CC0C // 0=none, 4=extreme
unsigned short uSeconds; // C00E C40E C80E CC0E // FSX only -- see below
double dLatitude; // C010 C410 C810 CC10 // LLA zero for GLOB or unknown ICAO
double dLongitude; // C018 C418 C818 CC18
int nElevation; // C020 C420 C820 CC20 // metres * 65536
unsigned int ulTimeStamp; // C024 C424 C824 CC24 // mSecs since start of session
NewPress Press; // C028 C428 C828 CC28
NewVis Vis; // C02C C42C C82C CC2C // Base Vis -- for upper Vis layers see
int nTempCtr; // C034 C434 C834 CC34 // Number of temperature layers
NewTemp Temp[24]; // C038 C438 C838 CC38
int nWindsCtr; // C0F8 C4F8 C8F8 CCF8 // Number of wind layers
NewWind Wind[24]; // C0FC C4FC C8FC CCFC
int nCloudsCtr; // C27C C67C CA7C CE7C // Number of Cloud layers (max 16 in FSX, was 24
NewCloud Cloud[16]; // C280 C680 CA80 CE80
char chUTCstamp[6]; // C380 C780 CB80 CF80 // UTC time stamp on METAR (internal use)
unsigned short fWriteback; // C386 C786 CB86 CF86 // Flags for writing back to FSX (internal use only)
unsigned long ulSettingTimeStamp; // C388 c788 CB88 CF88 // Timestamp METAR sent (internal use)
int nUpperVisCtr; // C38C C78C CB8C CF8C // Number of upper Vis layer (new in FSX)
NewVis UpperVis[12]; // C390 C790 CB90 CF90
unsigned short uSpare[8]; // C3F0 C7F0 CBF0 CFF0
} NewWeather;
#define MAXVISLAYERS 12
#define MAXTEMPLAYERS 24
#define MAXWINDLAYERS 24
#define MAXCLOUDLAYERS 16
// Commands (for set weather area only)
#define NW_SET 1 // Set weather via FSUIPC user filters
#define NW_SETEXACT 2 // Set weather bypassing user filters
#define NW_CLEAR 3 // Clear all weather, but leave dynamic setting alone
#define NW_DYNAMICS 4 // Set weather dynamics (from uDynamics value)
#define NW_GLOBAL 5 // (FSX) Put FS into Global weather mode
// You sohuld write only 'GLOB' weather after this, which will be applied everywhere.
#define NW_METAR 128 // DON'T USE! (Internal setting when weather written via string at B000. (FSX))
#define NW_SET_PENDING 257 // Set weather using filters, but don't activate in FS yet
#define NW_SETEXACT_PENDING 258 // Set weather bypassing filters, but don't activate in FS yet
#define NW_ACTIVATE 256 // Activate pending weather settings
// Flags for FSX weather setting
#define NWF_SECONDS 1
/* Set this flag if "Seconds" value is to be used in setting weather in FSX
This applies to weather set via NWI values or via the NEXT Metar string
written to B000. It's a "one shot" flag, cleared after use.
The Seconds value determines whether the weather change is effective
immediately or after a delay, gradually "blending" into the existing
weather.
*/
// The 4 distinct areas are used as follows:
// C000 - read only, maintains current interpolated weather at aircraft
// C400 - read only, maintains last written global weather values
// C800 - write area to set weather according to Command, flags, etc etc.
// CC00 - selected read (via signature + ICAO), reads weather at weather station or GLOB

View File

@@ -0,0 +1,20 @@
load(common_pre)
requires(equals(WORD_SIZE,64))
QT += core dbus xml network
TARGET = fsuipc
TEMPLATE = lib
CONFIG += staticlib
DEPENDPATH += . $$SourceRoot/src
INCLUDEPATH += . $$SourceRoot/src
HEADERS += *.h
SOURCES += *.c
DESTDIR = $$DestRoot/lib
load(common_post)

View File

@@ -10,10 +10,7 @@ TEMPLATE = lib
CONFIG += plugin shared
CONFIG += blackmisc blackcore
LIBS += -lsimulatorfscommon -lsimulatorfsxcommon -lSimConnect -lFSUIPC_User
# required for FSUIPC
win32:!win32-g++*: QMAKE_LFLAGS += /NODEFAULTLIB:LIBC.lib
LIBS += -lsimulatorfscommon -lsimulatorfsxcommon -lfsuipc -lSimConnect
DEPENDPATH += . $$SourceRoot/src
INCLUDEPATH += . $$SourceRoot/src

View File

@@ -8,11 +8,6 @@ TEMPLATE = lib
CONFIG += staticlib
CONFIG += blackmisc blackcore blackgui
LIBS += -lsimulatorfscommon -lSimConnect -lFSUIPC_User
# required for FSUIPC
win32:!win32-g++*: QMAKE_LFLAGS += /NODEFAULTLIB:LIBC.lib
DEPENDPATH += . $$SourceRoot/src
INCLUDEPATH += . $$SourceRoot/src

View File

@@ -10,10 +10,7 @@ TEMPLATE = lib
CONFIG += plugin shared
CONFIG += blackmisc blackcore
LIBS += -lsimulatorfscommon -lsimulatorfsxcommon -lSimConnect -lFSUIPC_User
# required for FSUIPC
win32:!win32-g++*: QMAKE_LFLAGS += /NODEFAULTLIB:LIBC.lib
LIBS += -lsimulatorfscommon -lsimulatorfsxcommon -lfsuipc -lSimConnect
DEPENDPATH += . $$SourceRoot/src
INCLUDEPATH += . $$SourceRoot/src

View File

@@ -4,6 +4,8 @@ TEMPLATE = subdirs
CONFIG += ordered
contains(BLACK_CONFIG,FSX|FS9|P3D) {
SUBDIRS += fsuipc32
SUBDIRS += fsuipc64
SUBDIRS += fscommon
}
contains(BLACK_CONFIG,FSX|P3D) {
@@ -17,7 +19,7 @@ contains(BLACK_CONFIG,FSX) {
SUBDIRS += fsx
SUBDIRS += fsxconfig
}
contains(BLACK_CONFIG,FS9) {
contains(BLACK_CONFIG,FS9):contains(BLACK_CONFIG,FSUIPC) {
SUBDIRS += fs9
}
contains(BLACK_CONFIG,XPlane) {