Proton/lsteamclient/gen_wrapper.py

1737 lines
59 KiB
Python
Executable File

#!/usr/bin/env python3
#NOTE: If you make modifications here, consider whether they should
#be duplicated in ../vrclient/gen_wrapper.py
CLANG_PATH='/usr/lib/clang/15'
from clang.cindex import Cursor, CursorKind, Index, TypeKind
import concurrent.futures
import os
import re
SDK_VERSIONS = [
"158",
"157",
"156",
"155",
"154",
"153a",
"152",
"151",
"150",
"149",
"148a",
"147",
"146",
"145",
"144",
"143y",
"143x",
"143",
"142",
"141",
"140",
"139",
"138a",
"138",
"137",
"136",
"135a",
"135",
"134",
"133x",
"133b",
"133a",
"133",
"132x",
"132",
"131",
"130x",
"130",
"129a",
"129",
"128x",
"128",
"127",
"126a",
"126",
"125",
"124",
"123a",
"123",
"122",
"121x",
"121",
"120",
"119x",
"119",
"118",
"117",
"116x",
"116",
"115",
"114",
"113",
"112x",
"112",
"111x",
"111",
"110",
"109",
"108",
"107",
"106",
"105",
"104",
"103",
"102x",
"102",
"101x",
"101",
"100",
"099y",
"099x",
"099w",
"099v",
"099u",
]
ABIS = ['u32', 'u64', 'w32', 'w64']
SDK_SOURCES = {
"steam_api.h": [
"ISteamApps",
"ISteamAppList",
"ISteamClient",
"ISteamController",
"ISteamGameSearch",
"ISteamFriends",
"ISteamHTMLSurface",
"ISteamHTTP",
"ISteamInput",
"ISteamInventory",
"ISteamMatchmaking",
"ISteamMatchmakingServers",
"ISteamMusic",
"ISteamMusicRemote",
"ISteamNetworking",
"ISteamParties",
"ISteamRemotePlay",
"ISteamRemoteStorage",
"ISteamScreenshots",
"ISteamUGC",
"ISteamUnifiedMessages",
"ISteamUser",
"ISteamUserStats",
"ISteamUtils",
"ISteamVideo"
],
"isteamappticket.h": [
"ISteamAppTicket"
],
"isteamgameserver.h": [
"ISteamGameServer"
],
"isteamgameserverstats.h": [
"ISteamGameServerStats"
],
"isteamgamestats.h": [
"ISteamGameStats"
],
"isteammasterserverupdater.h": [
"ISteamMasterServerUpdater"
],
"isteamgamecoordinator.h": [
"ISteamGameCoordinator"
],
"isteamparentalsettings.h": [
"ISteamParentalSettings"
],
"isteamnetworkingmessages.h": [
"ISteamNetworkingMessages"
],
"isteamnetworkingsockets.h": [
"ISteamNetworkingSockets"
],
"isteamnetworkingsocketsserialized.h": [
"ISteamNetworkingSocketsSerialized"
],
"isteamnetworkingutils.h": [
"ISteamNetworkingUtils"
],
"steamnetworkingfakeip.h": [
"ISteamNetworkingFakeUDPPort"
],
}
SDK_CLASSES = {klass: source for source, klasses in SDK_SOURCES.items()
for klass in klasses}
VERSION_ALIASES = {
#these interfaces are undocumented and binary compatible
#"target interface": ["alias 1", "alias 2"],
"SteamUtils004":["SteamUtils003"],
"SteamUtils002":["SteamUtils001"],
"SteamGameServer008":["SteamGameServer007","SteamGameServer006"],
"SteamNetworkingSocketsSerialized002":["SteamNetworkingSocketsSerialized001"],
"STEAMAPPS_INTERFACE_VERSION001":["SteamApps001"],
"SteamNetworkingSockets002":["SteamNetworkingSockets003"],
}
# these structs are manually confirmed to be equivalent
EXEMPT_STRUCTS = {
"CSteamID",
"CGameID",
"CCallbackBase",
"SteamIPAddress_t",
"SteamPS3Params_t",
"ValvePackingSentinel_t"
}
# structs for which the size is important, either because of arrays or size parameters
SIZED_STRUCTS = {
"SteamPartyBeaconLocation_t",
"SteamItemDetails_t",
}
# we have converters for these written by hand because they're too complicated to generate
MANUAL_STRUCTS = [
"SteamNetworkingMessage_t"
]
UNIX_FUNCS = [
'steamclient_init',
'steamclient_next_callback',
'steamclient_CreateInterface',
'steamclient_Steam_GetAPICallResult',
'steamclient_Steam_BGetCallback',
'steamclient_callback_message_receive',
'steamclient_Steam_FreeLastCallback',
'steamclient_Steam_ReleaseThreadLocalMemory',
'steamclient_Steam_IsKnownInterface',
'steamclient_Steam_NotifyMissingInterface',
'steamclient_networking_messages_receive_144',
'steamclient_networking_messages_receive_147',
'steamclient_networking_messages_receive_153a',
'steamclient_networking_message_release_147',
'steamclient_networking_message_release_153a',
]
MANUAL_METHODS = {
#TODO: 001 005 007
#NOTE: 003 never appeared in a public SDK, but is an alias for 002 (the version in SDK 1.45 is actually 004 but incorrectly versioned as 003)
"ISteamNetworkingSockets_ReceiveMessagesOnConnection": True,
"ISteamNetworkingSockets_ReceiveMessagesOnListenSocket": True,
"ISteamNetworkingSockets_ReceiveMessagesOnPollGroup": True,
"ISteamNetworkingSockets_SendMessages": True,
"ISteamNetworkingSockets_ConnectP2PCustomSignaling": lambda ver, abi: abi == 'u' and ver <= 8,
"ISteamNetworkingSockets_ReceivedP2PCustomSignal": lambda ver, abi: abi == 'u' and ver <= 8,
"ISteamMatchmakingServers_PingServer": lambda ver, abi: abi == 'u',
"ISteamMatchmakingServers_PlayerDetails": lambda ver, abi: abi == 'u',
"ISteamMatchmakingServers_ReleaseRequest": lambda ver, abi: abi == 'w',
"ISteamMatchmakingServers_RequestFavoritesServerList": lambda ver, abi: abi == 'u',
"ISteamMatchmakingServers_RequestFriendsServerList": lambda ver, abi: abi == 'u',
"ISteamMatchmakingServers_RequestHistoryServerList": lambda ver, abi: abi == 'u',
"ISteamMatchmakingServers_RequestInternetServerList": lambda ver, abi: abi == 'u',
"ISteamMatchmakingServers_RequestLANServerList": lambda ver, abi: abi == 'u',
"ISteamMatchmakingServers_RequestSpectatorServerList": lambda ver, abi: abi == 'u',
"ISteamMatchmakingServers_ServerRules": lambda ver, abi: abi == 'u',
"ISteamNetworkingUtils_AllocateMessage": True,
"ISteamNetworkingUtils_SetConfigValue": lambda ver, abi: abi == 'u' and ver >= 3,
"ISteamNetworkingMessages_ReceiveMessagesOnChannel": True,
"ISteamInput_EnableActionEventCallbacks": lambda ver, abi: abi == 'u',
"ISteamInput_GetGlyphForActionOrigin": True,
"ISteamInput_GetGlyphPNGForActionOrigin": True,
"ISteamInput_GetGlyphSVGForActionOrigin": True,
"ISteamInput_GetGlyphForActionOrigin_Legacy": True,
"ISteamInput_GetGlyphForXboxOrigin": True,
"ISteamController_GetGlyphForActionOrigin": True,
"ISteamController_GetGlyphForXboxOrigin": True,
"ISteamNetworkingFakeUDPPort_DestroyFakeUDPPort": lambda ver, abi: abi == 'w',
"ISteamNetworkingFakeUDPPort_ReceiveMessages": True,
"ISteamClient_Set_SteamAPI_CCheckCallbackRegisteredInProcess": lambda ver, abi: abi == 'u' and ver >= 20,
"ISteamUtils_GetAPICallResult": lambda ver, abi: abi == 'u',
}
DEFINE_INTERFACE_VERSION = re.compile(r'^#define\s*(?P<name>STEAM(?:\w*)_VERSION(?:\w*))\s*"(?P<version>.*)"')
def is_manual_method(klass, method, abi):
version = re.search(r'(\d+)$', klass.version)
key = f'{klass.name}_{method.name}'
needs_manual = MANUAL_METHODS.get(key, False)
if callable(needs_manual) and version:
return needs_manual(int(version[0]), abi)
return needs_manual
# manual converters for simple types (function pointers)
MANUAL_TYPES = [
"FSteamNetworkingSocketsDebugOutput",
"SteamAPIWarningMessageHook_t",
"SteamAPI_CheckCallbackRegistered_t",
"SteamAPI_PostAPIResultInProcess_t",
"void ()",
]
# manual converters for specific parameters
MANUAL_PARAMS = [
"nNativeKeyCode"
]
converted_structs = []
all_classes = {}
all_records = {}
all_sources = {}
all_structs = {}
all_versions = {}
unique_structs = []
PATH_CONV_METHODS_UTOW = {
"ISteamAppList_GetAppInstallDir": {
"pchDirectory": {"len": "cchNameMax", "url": False},
"ret_size": True,
},
"ISteamApps_GetAppInstallDir": {
"pchFolder": {"len": "cchFolderBufferSize", "url": False},
"ret_size": True,
},
"ISteamUGC_GetQueryUGCAdditionalPreview": {
"pchURLOrVideoID": {"len": "cchURLSize", "url": True},
},
"ISteamUGC_GetItemInstallInfo": {
"pchFolder": {"len": "cchFolderSize", "url": False},
},
"ISteamUser_GetUserDataFolder": {
"pchBuffer": {"len": "cubBuffer", "url": False},
},
}
PATH_CONV_METHODS_WTOU = {
"ISteamApps_GetFileDetails": {
"pszFileName": {"array": False, "url": False},
},
### ISteamGameServer::SetModDir - "Just the folder name, not the whole path. I.e. "Spacewar"."
"ISteamHTMLSurface_LoadURL": {
"pchURL": {"array": False, "url": True},
},
"ISteamHTMLSurface_FileLoadDialogResponse": {
"pchSelectedFiles": {"array": True, "url": False},
},
"ISteamRemoteStorage_PublishWorkshopFile": {
"pchFile": {"array": False, "url": False},
"pchPreviewFile": {"array": False, "url": False},
},
"ISteamRemoteStorage_UpdatePublishedFileFile": {
"pchFile": {"array": False, "url": False},
},
"ISteamRemoteStorage_UpdatePublishedFilePreviewFile": {
"pchPreviewFile": {"array": False, "url": False},
},
"ISteamRemoteStorage_PublishVideo": {
"pchPreviewFile": {"array": False, "url": False},
},
"ISteamScreenshots_AddScreenshotToLibrary": {
"pchFilename": {"array": False, "url": False},
"pchThumbnailFilename": {"array": False, "url": False},
},
"ISteamScreenshots_AddVRScreenshotToLibrary": {
"pchFilename": {"array": False, "url": False},
"pchVRFilename": {"array": False, "url": False},
},
"ISteamRemoteStorage_UGCDownloadToLocation": {
"pchLocation": {"array": False, "url": False},
},
"ISteamUGC_SetItemContent": {
"pszContentFolder": {"array": False, "url": False},
},
"ISteamUGC_SetItemPreview": {
"pszPreviewFile": {"array": False, "url": False},
},
"ISteamUGC_AddItemPreviewFile": {
"pszPreviewFile": {"array": False, "url": False},
},
"ISteamUGC_UpdateItemPreviewFile": {
"pszPreviewFile": {"array": False, "url": False},
},
"ISteamUGC_BInitWorkshopForGameServer": {
"pszFolder": {"array": False, "url": False},
},
"ISteamUtils_CheckFileSignature": {
"szFileName": {"array": False, "url": False},
},
"ISteamController_Init": {
"pchAbsolutePathToControllerConfigVDF": {"array": False, "url": False},
},
"ISteamInput_SetInputActionManifestFilePath": {
"pchInputActionManifestAbsolutePath": {"array": False, "url": False},
},
}
PATH_CONV_STRUCTS = {
"HTML_StartRequest_t": {
"pchURL": True,
},
"HTML_URLChanged_t": {
"pchURL": True,
},
"HTML_FinishedRequest_t": {
"pchURL": True,
},
"HTML_OpenLinkInNewTab_t": {
"pchURL": True,
},
"HTML_LinkAtPosition_t": {
"pchURL": True,
},
"HTML_FileOpenDialog_t": {
"pchInitialFile": True,
},
"HTML_NewWindow_t": {
"pchURL": True,
},
}
class Padding:
def __init__(self, offset, size):
self.offset = offset
self.size = size
class Field:
def __init__(self, cursor, struct, type, offset, name=None):
self._cursor = cursor
self._type = type
self.name = cursor.spelling if not name else name
self.type = cursor.type
self.size = self.type.get_size()
self.offset = offset
def needs_conversion(self, other):
return self._type.needs_conversion(other._type)
class BasicType:
def __init__(self, type, abi):
self._type = type.get_canonical()
self._abi = abi
self._decl_order = 0
self._conv_cache = {}
self.size = self._type.get_size()
self.id = self._type.spelling
@property
def order(self):
return self._decl_order
def set_used(self, order=-1):
if self._decl_order <= order:
return
self._decl_order = order
def needs_conversion(self, other):
if self._type.kind == TypeKind.POINTER and self._type.get_pointee().kind == TypeKind.FUNCTIONPROTO:
return self._abi != other._abi
return False
class Struct:
def __init__(self, sdkver, abi, cursor):
self._cursor = cursor
self._sdkver = sdkver
self._abi = abi
self._fields = None
self._decl_order = 0
self._conv_cache = {}
self.name = canonical_typename(self._cursor)
self.type = self._cursor.type.get_canonical()
self.size = self.type.get_size()
self.align = self.type.get_align()
self.id = f'{abi}_{self.name}_{sdkver}'
if self.name in EXEMPT_STRUCTS:
self._fields = [Padding(0, self.size)]
@property
def order(self):
return self._decl_order
def set_used(self, order=-1):
if self._decl_order <= order:
return
self._decl_order = order
[f._type.set_used(order - 1) for f in self.fields]
@property
def callback_id(self):
is_enum = lambda c: c.kind == CursorKind.ENUM_DECL
enums = filter(is_enum, self._cursor.get_children())
is_callback = lambda c: c.displayname == "k_iCallback"
callbacks = [k for c in enums for k in filter(is_callback, c.get_children())]
if len(callbacks): return int(callbacks[0].enum_value)
return None
@property
def padded_fields(self):
if self._fields: return self._fields
size, self._fields = 0, []
for cursor in self._cursor.get_children():
if cursor.kind == CursorKind.CXX_BASE_SPECIFIER \
and len(list(cursor.type.get_fields())) > 0:
base_type = Type(cursor.type, self._sdkver, self._abi)
self._fields += base_type.padded_fields
size = cursor.type.get_size()
break
for cursor in self.type.get_fields():
assert not cursor.is_bitfield()
offset = self.type.get_offset(cursor.spelling)
assert offset % 8 == 0
offset = offset // 8
# assert offset >= size or type(self) is Union
if size < offset: self._fields.append(Padding(size, offset - size))
field_type = Type(cursor.type, self._sdkver, self._abi)
self._fields.append(Field(cursor, self, field_type, offset))
size = max(size, offset + cursor.type.get_size())
if size < self.size: self._fields.append(Padding(size, self.size - size))
return self._fields
@property
def fields(self):
return [f for f in self.padded_fields if type(f) is not Padding]
def write_definition(self, out, prefix, converters):
version = all_versions[sdkver][self.name]
kind = 'union' if type(self) is Union else 'struct'
wrapped = len(prefix) > 0
out(f'#pragma pack( push, {self.align} )\n')
out(f'{kind} {prefix}{version}\n')
out(u'{\n')
for f in self.padded_fields:
if type(f) is Field:
out(f' {declspec(f._cursor, f.name, prefix, wrapped)};\n')
else:
out(f' uint8_t __pad_{f.offset}[{f.size}];\n')
for conv in converters:
out(u'#ifdef __cplusplus\n')
out(f' operator {conv}{version}() const;\n')
out(u'#endif /* __cplusplus */\n')
out(u'};\n')
out(u'#pragma pack( pop )\n\n')
def write_checks(self, out, prefix):
version = all_versions[sdkver][self.name]
out(f'C_ASSERT( sizeof({prefix}{version}) >= {self.size} );\n')
for f in self.fields:
out(f'C_ASSERT( offsetof({prefix}{version}, {f.name}) == {f.offset} );\n')
out(f'C_ASSERT( sizeof({prefix}{version}().{f.name}) >= {f.size} );\n')
out(u'\n')
def write_converter(self, prefix, path_conv_fields):
version = all_versions[sdkver][self.name]
out(f'{self._abi}_{version}::operator {prefix}{version}() const\n')
out(u'{\n')
out(f' {prefix}{version} ret;\n')
for field in self.fields:
if field.name not in path_conv_fields:
out(f' ret.{field.name} = this->{field.name};\n')
else:
out(f' steamclient_unix_path_to_dos_path(1, this->{field.name}, g_tmppath, sizeof(g_tmppath), 1);\n')
out(f' ret.{field.name} = g_tmppath;\n')
out(u' return ret;\n')
out(u'}\n')
def needs_conversion(self, other):
if other.id in self._conv_cache:
return self._conv_cache[other.id]
self._conv_cache[other.id] = other._conv_cache[self.id] = True
if self.name in PATH_CONV_STRUCTS and self._abi[0] != other._abi[0]:
return True
if self.name in SIZED_STRUCTS and self.size != other.size:
return True
if len(self.fields) != len(other.fields):
return True
if any([a.offset != b.offset or a.needs_conversion(b)
for a, b in zip(self.fields, other.fields)]):
return True
self._conv_cache[other.id] = other._conv_cache[self.id] = False
return False
def get_children(self):
return self._cursor.get_children()
class Union(Struct):
def __init__(self, sdkver, abi, cursor):
super().__init__(sdkver, abi, cursor)
def needs_conversion(self, other):
return True
class Method:
def __init__(self, sdkver, abi, cursor, klass, index, override):
self._sdkver = sdkver
self._abi = abi
self._cursor = cursor
self._klass = klass
self._index = index
self._override = override
self.result_type = cursor.result_type
self.spelling = cursor.spelling
@property
def name(self):
if self._override > 1: return f'{self.spelling}_{self._override}'
return self.spelling
@property
def full_name(self):
return f'{self._klass.full_name}_{self.name}'
def get_arguments(self):
return self._cursor.get_arguments()
def needs_conversion(self, other):
if len(list(self.get_arguments())) != len(list(other.get_arguments())):
return True
return False # FIXME
def write_params(self, out):
returns_record = self.result_type.get_canonical().kind == TypeKind.RECORD
ret = "*_ret" if returns_record else "_ret"
ret = f'{declspec(self.result_type, ret, "w_")}'
names = [p.spelling if p.spelling != "" else f'_{chr(0x61 + i)}'
for i, p in enumerate(self.get_arguments())]
params = [declspec(p, names[i], "w_") for i, p in enumerate(self.get_arguments())]
if self.result_type.kind != TypeKind.VOID:
params = [ret] + params
names = ['_ret'] + names
params = ['struct u_steam_iface *linux_side'] + params
names = ['linux_side'] + names
out(f'struct {self.full_name}_params\n')
out(u'{\n')
for param in params:
out(f' {param};\n')
out(u'};\n\n')
def get_children(self):
return self._cursor.get_children()
class Destructor(Method):
def __init__(self, sdkver, abi, cursor, klass, index, override):
super().__init__(sdkver, abi, cursor, klass, index, override)
@property
def name(self):
if self._override > 1: return f'destructor_{self._override}'
return 'destructor'
def write_params(self, out):
pass
class Class:
def __init__(self, sdkver, abi, cursor):
self._cursor = cursor
self._sdkver = sdkver
self._abi = abi
self._methods = None
self._decl_order = 0
self.name = cursor.spelling
self.filename = SDK_CLASSES.get(self.name, None)
versions = all_versions[sdkver]
self.version = self.name[1:].upper()
self.version = versions.get(self.version, "")
self.type = self._cursor.type.get_canonical()
self.id = f'{abi}_{self.name}_{sdkver}'
@property
def methods(self):
if self._methods:
return self._methods
overrides = {}
is_method = lambda c: c.kind == CursorKind.CXX_METHOD and c.is_virtual_method()
in_vtable = lambda c: is_method(c) or c.kind == CursorKind.DESTRUCTOR
self._methods = []
for i, method in enumerate(filter(in_vtable, self._cursor.get_children())):
index, override = overrides.get(method.spelling, (i, 1))
overrides[method.spelling] = (index, override + 1)
if method.kind == CursorKind.DESTRUCTOR:
self._methods.append(Destructor(self._sdkver, self._abi, method, self, index, override))
else:
self._methods.append(Method(self._sdkver, self._abi, method, self, index, override))
return self._methods
@property
def full_name(self):
if len(self.version) == 0:
return self.name
return f'{self.name}_{self.version}'
@property
def order(self):
return self._decl_order
def set_used(self, order=-1):
if self._decl_order <= order:
return
self._decl_order = order
def needs_conversion(self, other):
if self._abi[0] != other._abi[0]:
return True
if len(self.methods) != len(other.methods):
return True
if any(m.needs_conversion(n) for m, n in zip(self.methods, other.methods)):
return True
return False
def write_definition(self, out, prefix):
out(f'struct {prefix}{self.full_name}\n')
out(u'{\n')
out(u'#ifdef __cplusplus\n')
for method in self.methods:
types = [declspec(p, "", prefix) for p in method.get_arguments()]
# CGameID -> CGameID &
# Windows side follows the prototype in the header while Linux
# steamclient treats gameID parameter as pointer
if self.full_name == 'ISteamUser_SteamUser008' \
and method.name == 'InitiateGameConnection':
types[3] = 'CGameID *'
if type(method) is Destructor:
out(f' virtual ~{prefix}{self.full_name}( {", ".join(types)} ) = 0;\n')
else:
method_name = f'{declspec(method.result_type, "", prefix)} {method.spelling}'
out(f' virtual {method_name}( {", ".join(types)} ) = 0;\n')
out(u'#endif /* __cplusplus */\n')
out(u'};\n\n')
def get_children(self):
return self._cursor.get_children()
def Record(sdkver, abi, cursor):
if cursor.type.get_declaration().kind == CursorKind.UNION_DECL:
return Union(sdkver, abi, cursor)
method_kinds = (CursorKind.CXX_METHOD, CursorKind.DESTRUCTOR)
is_method = lambda c: c.kind in method_kinds and c.is_virtual_method()
for _ in filter(is_method, cursor.get_children()):
return Class(sdkver, abi, cursor)
return Struct(sdkver, abi, cursor)
def Type(decl, sdkver, abi):
name = canonical_typename(decl)
if name not in all_structs:
return BasicType(decl, abi)
return all_structs[name][sdkver][abi]
def canonical_typename(cursor):
if type(cursor) in (Struct, Class, Cursor):
return canonical_typename(cursor.type)
name = cursor.get_canonical().spelling
return name.removeprefix("const ")
def underlying_typename(decl):
return canonical_typename(underlying_type(decl))
def find_struct_abis(name):
if not name in all_structs:
return None
structs = all_structs[name]
if not sdkver in structs:
return None
return structs[sdkver]
def struct_needs_conversion(struct):
name = canonical_typename(struct)
if name in EXEMPT_STRUCTS:
return False
if name in MANUAL_STRUCTS:
return True
if name in PATH_CONV_STRUCTS:
return True
abis = find_struct_abis(name)
if abis is None: return False
if abis['w32'].needs_conversion(abis['u32']):
return True
if abis['w64'].needs_conversion(abis['u64']):
return True
return False
def underlying_type(decl):
if type(decl) is Cursor:
decl = decl.type
if decl.kind == TypeKind.LVALUEREFERENCE: return underlying_type(decl.get_pointee())
if decl.kind == TypeKind.CONSTANTARRAY: return underlying_type(decl.element_type)
if decl.kind == TypeKind.POINTER: return underlying_type(decl.get_pointee())
return decl
def param_needs_conversion(decl):
decl = underlying_type(decl)
return decl.kind == TypeKind.RECORD and \
struct_needs_conversion(decl)
def callconv(cursor, prefix):
if type(cursor) is not Cursor:
return ''
canon = cursor.type.get_canonical()
if canon.kind != TypeKind.POINTER:
return ''
canon = canon.get_pointee()
if canon.kind != TypeKind.FUNCTIONPROTO:
return ''
if cursor.type.kind == TypeKind.TYPEDEF:
cursor = cursor.type.get_declaration()
tokens = cursor.get_tokens()
while next(tokens).spelling != '(': pass
token = next(tokens).spelling.strip('_')
token = token.replace('*', 'cdecl')
token = token.replace('S_CALLTYPE', 'cdecl')
return f'{prefix[0].upper()}_{token.upper()} '
def declspec_func(decl, name, prefix):
ret = declspec(decl.get_result(), "", prefix, False)
params = [declspec(a, "", prefix, False) for a in decl.argument_types()]
params = ", ".join(params) if len(params) else "void"
return f'{ret} ({name})({params})'
def declspec(decl, name, prefix, wrapped=False):
call = callconv(decl, prefix)
if type(decl) is Cursor:
decl = decl.type
decl = decl.get_canonical()
const = 'const ' if decl.is_const_qualified() else ''
if decl.kind == TypeKind.FUNCTIONPROTO:
return declspec_func(decl, name, prefix)
if decl.kind in (TypeKind.POINTER, TypeKind.LVALUEREFERENCE):
decl = decl.get_pointee()
spec = declspec(decl, f"*{call}{const}{name}", prefix, False)
if wrapped:
return f'{prefix.upper()}PTR({spec}, {name})'
return spec
if decl.kind == TypeKind.CONSTANTARRAY:
decl, count = decl.element_type, decl.element_count
if wrapped:
spec = declspec(decl, const, prefix, False)
return f'{prefix.upper()}ARRAY({spec}, {count}, {name})'
return declspec(decl, f"({const}{name})[{count}]", prefix, False)
if len(name):
name = f' {name}'
if decl.kind in (TypeKind.UNEXPOSED, TypeKind.FUNCTIONPROTO):
return f'void{name}'
if decl.kind == TypeKind.ENUM:
return f'uint{decl.get_size() * 8}_t{name}'
type_name = decl.spelling.removeprefix("const ")
if decl.kind == TypeKind.RECORD \
and type_name in all_versions[sdkver] \
and type_name not in EXEMPT_STRUCTS:
if type_name in unique_structs:
return f'{const}{all_versions[sdkver][type_name]}{name}'
return f'{const}{prefix}{all_versions[sdkver][type_name]}{name}'
if type_name.startswith('ISteam'):
return f'{const}void /*{type_name}*/{name}'
if type_name in ('void', 'char', 'float', 'double'):
return f'{const}{type_name}{name}'
if type_name.startswith(('bool', 'int', 'long', 'short', 'signed')):
return f'{const}int{decl.get_size() * 8}_t{name}'
if type_name.startswith(('uint', 'unsigned')):
return f'{const}uint{decl.get_size() * 8}_t{name}'
if 'unnamed union' in decl.spelling:
return f'{const}struct {{ uint8_t _[{decl.get_size()}]; }}{name}'
return f'{decl.spelling}{name}'
def handle_method_cpp(method, classname, out):
returns_void = method.result_type.kind == TypeKind.VOID
returns_record = method.result_type.get_canonical().kind == TypeKind.RECORD
names = [p.spelling if p.spelling != "" else f'_{chr(0x61 + i)}'
for i, p in enumerate(method.get_arguments())]
need_convert = {n: p for n, p in zip(names, method.get_arguments())
if param_needs_conversion(p)}
manual_convert = {n: p for n, p in zip(names, method.get_arguments())
if underlying_type(p).spelling in MANUAL_TYPES
or p.spelling in MANUAL_PARAMS}
names = ['linux_side'] + names
out(f'NTSTATUS {method.full_name}( void *args )\n')
out(u'{\n')
out(f' struct {method.full_name}_params *params = (struct {method.full_name}_params *)args;\n')
out(f' struct u_{klass.full_name} *iface = (struct u_{klass.full_name} *)params->linux_side;\n')
params = list(zip(names[1:], method.get_arguments()))
for i, (name, param) in enumerate(params[:-1]):
if underlying_type(param).kind != TypeKind.RECORD:
continue
next_name, next_param = params[i + 1]
if not any(w in next_name.lower() for w in ('count', 'len', 'size', 'num')):
continue
assert underlying_typename(param) in SIZED_STRUCTS | EXEMPT_STRUCTS
for i, (name, param) in enumerate(params[1:]):
if underlying_type(param).kind != TypeKind.RECORD:
continue
prev_name, prev_param = params[i - 1]
if not any(w in prev_name.lower() for w in ('count', 'len', 'size', 'num')):
continue
if underlying_typename(param) not in SIZED_STRUCTS | EXEMPT_STRUCTS:
print('Warning:', underlying_typename(param), name, 'following', prev_name)
path_conv_wtou = PATH_CONV_METHODS_WTOU.get(f'{klass.name}_{method.spelling}', {})
for name, conv in filter(lambda x: x[0] in names, path_conv_wtou.items()):
if conv['array']:
out(f' const char **u_{name} = steamclient_dos_to_unix_path_array( params->{name} );\n')
else:
out(f' char *u_{name} = steamclient_dos_to_unix_path( params->{name}, {int(conv["url"])} );\n')
need_output = {}
for name, param in sorted(need_convert.items()):
if param.type.kind != TypeKind.POINTER:
out(f' {declspec(param.type, f"u_{name}", "u_")} = params->{name};\n')
continue
pointee = param.type.get_pointee()
if pointee.kind == TypeKind.POINTER:
need_output[name] = param
out(f' {declspec(pointee, f"lin_{name}", "u_")};\n')
continue
if not pointee.is_const_qualified():
need_output[name] = param
out(f' {declspec(pointee, f"u_{name}", "u_")} = *params->{name};\n')
for name, param in sorted(manual_convert.items()):
if name in MANUAL_PARAMS:
out(f' {declspec(param, f"u_{name}", "u_")} = manual_convert_{name}( params->{name} );\n')
else:
out(f' {declspec(param, f"u_{name}", "u_")} = manual_convert_{method.name}_{name}( params->{name} );\n')
if returns_void:
out(u' ')
elif returns_record:
out(u' *params->_ret = ')
else:
out(u' params->_ret = ')
def param_call(name, param):
pfx = '&' if param.type.kind == TypeKind.POINTER else ''
if name in need_convert: return f"{pfx}u_{name}"
if name in manual_convert: return f"u_{name}"
if name in path_conv_wtou: return f"u_{name}"
return f'params->{name}'
params = [param_call(n, p) for n, p in zip(names[1:], method.get_arguments())]
# CGameID -> CGameID &
# Windows side follows the prototype in the header while Linux
# steamclient treats gameID parameter as pointer
if klass.full_name == 'ISteamUser_SteamUser008' \
and method.name == 'InitiateGameConnection':
params[3] = f'&{params[3]}'
out(f'iface->{method.spelling}( {", ".join(params)} );\n')
for name, param in sorted(need_output.items()):
out(f' *params->{name} = u_{name};\n')
path_conv_utow = PATH_CONV_METHODS_UTOW.get(f'{klass.name}_{method.spelling}', {})
for name, conv in filter(lambda x: x[0] in names, path_conv_utow.items()):
out(u' ')
if "ret_size" in path_conv_utow:
out(u'params->_ret = ')
out(f'steamclient_unix_path_to_dos_path( params->_ret, params->{name}, params->{name}, params->{conv["len"]}, {int(conv["url"])} );\n')
for name, conv in filter(lambda x: x[0] in names, path_conv_wtou.items()):
if conv["array"]:
out(f' steamclient_free_path_array( u_{name} );\n')
else:
out(f' steamclient_free_path( u_{name} );\n')
out(u' return 0;\n')
out(u'}\n\n')
def handle_thiscall_wrapper(klass, method, out):
returns_record = method.result_type.get_canonical().kind == TypeKind.RECORD
def param_stack_size(param):
if param.type.kind == TypeKind.LVALUEREFERENCE: return 4
return ((param.type.get_size() + 3) // 4) * 4
size = 4 + sum(param_stack_size(p) for p in method.get_arguments())
if returns_record: size += 4
name = f'win{klass.full_name}_{method.name}'
out(f'DEFINE_THISCALL_WRAPPER({name}, {size})\n')
def handle_method_c(klass, method, winclassname, out):
returns_void = method.result_type.kind == TypeKind.VOID
returns_record = method.result_type.get_canonical().kind == TypeKind.RECORD
ret = "*" if returns_record else ""
ret = f'{declspec(method.result_type, ret, "w_")} '
names = [p.spelling if p.spelling != "" else f'_{chr(0x61 + i)}'
for i, p in enumerate(method.get_arguments())]
params = [declspec(p, names[i], "w_") for i, p in enumerate(method.get_arguments())]
if returns_record:
params = [f'{declspec(method.result_type, "*_ret", "w_")}'] + params
names = ['_ret'] + names
params = ['struct w_steam_iface *_this'] + params
names = ['_this'] + names
out(f'{ret}__thiscall {winclassname}_{method.name}({", ".join(params)})\n')
out(u'{\n')
out(f' struct {method.full_name}_params params =\n')
out(u' {\n')
out(u' .linux_side = _this->u_iface,\n')
for name in names[1:]: out(f' .{name} = {name},\n')
out(u' };\n')
out(u' TRACE("%p\\n", _this);\n')
out(f' STEAMCLIENT_CALL( {method.full_name}, &params );\n')
if method.name.startswith('CreateFakeUDPPort'):
out(u' params._ret = create_winISteamNetworkingFakeUDPPort_SteamNetworkingFakeUDPPort001( params._ret );\n')
elif method.name.startswith("GetISteamGenericInterface"):
out(u' params._ret = create_win_interface( pchVersion, params._ret );\n')
elif method.result_type.spelling.startswith("ISteam"):
out(u' params._ret = create_win_interface( pchVersion, params._ret );\n')
if not returns_void:
out(u' return params._ret;\n')
out(u'}\n\n')
def handle_class(klass):
cppname = f"cpp{klass.full_name}"
with open(f"{cppname}.cpp", "w") as file:
out = file.write
out(u'/* This file is auto-generated, do not edit. */\n')
out(u'#include "unix_private.h"\n\n')
for method in klass.methods:
if type(method) is Destructor:
continue
if is_manual_method(klass, method, "u"):
continue
handle_method_cpp(method, klass.name, out)
winclassname = f"win{klass.full_name}"
with open(f"win{klass.name}.c", "a") as file:
out = file.write
for method in klass.methods:
handle_thiscall_wrapper(klass, method, out)
out('\n')
for method in klass.methods:
if is_manual_method(klass, method, "w"):
continue
if type(method) is Destructor:
out(f'void __thiscall {winclassname}_{method.name}(struct w_steam_iface *_this)\n{{/* never called */}}\n\n')
else:
handle_method_c(klass, method, winclassname, out)
out(f'extern vtable_ptr {winclassname}_vtable;\n')
out(u'\n')
out(f'DEFINE_RTTI_DATA0({winclassname}, 0, \".?AV{klass.name}@@\")\n')
out(u'\n')
out(f'__ASM_BLOCK_BEGIN({winclassname}_vtables)\n')
out(f' __ASM_VTABLE({winclassname},\n')
for method in sorted(klass.methods, key=lambda x: (x._index, -x._override)):
out(f' VTABLE_ADD_FUNC({winclassname}_{method.name})\n')
out(u' );\n')
out(u'__ASM_BLOCK_END\n')
out(u'\n')
out(f'struct w_steam_iface *create_{winclassname}(void *u_iface)\n')
out(u'{\n')
out(f' struct w_steam_iface *r = alloc_mem_for_iface(sizeof(struct w_steam_iface), "{klass.version}");\n')
out(u' TRACE("-> %p\\n", r);\n')
out(f' r->vtable = alloc_vtable(&{winclassname}_vtable, {len(klass.methods)}, "{klass.version}");\n')
out(u' r->u_iface = u_iface;\n')
out(u' return r;\n')
out(u'}\n\n')
def parse(sources, sdkver, abi):
args = [f'-m{abi[1:]}', '-I' + CLANG_PATH + '/include/']
if abi[0] == 'w':
args += ["-D_WIN32", "-U__linux__"]
args += ["-fms-extensions", "-mms-bitfields"]
args += ["-Wno-ignored-attributes", "-Wno-incompatible-ms-struct"]
if abi[0] == 'u':
args += ["-DGNUC"]
index = Index.create()
build = index.parse("source.cpp", args=args, unsaved_files=sources.items())
diagnostics = list(build.diagnostics)
for diag in diagnostics: print(diag)
assert len(diagnostics) == 0
return sdkver, abi, build
def load(sdkver):
sdkdir = f"steamworks_sdk_{sdkver}"
sources = {}
versions = {}
for file in os.listdir(sdkdir):
# Some files from Valve have non-UTF-8 stuff in the comments
# (typically the copyright symbol); therefore we ignore UTF-8
# encoding errors
lines = open(f"{sdkdir}/{file}", "r", errors="replace").readlines()
if """#error "This file isn't used any more"\n""" in lines:
sources[f"{sdkdir}/{file}"] = ""
results = (DEFINE_INTERFACE_VERSION.match(l) for l in lines)
for result in (r.groupdict() for r in results if r):
name, version = result['name'], result['version']
name = name.replace('_INTERFACE_VERSION', '')
name = name.replace('_VERSION', '')
versions[name] = version
source = [f'#if __has_include("{sdkdir}/{file}")\n'
f'#include "{sdkdir}/{file}"\n'
f'#endif\n'
for file in SDK_SOURCES.keys()]
sources["source.cpp"] = "\n".join(source)
return versions, sources
def classify_struct(name):
if name in EXEMPT_STRUCTS:
return None
structs = all_structs[name]
prev = []
versions = {}
unique = True
def set_class_version(abis, version):
for abi in filter(lambda x: type(x) is Class, abis):
abi.version = version
for sdkver in filter(lambda v: v in structs, reversed(SDK_VERSIONS)):
abis = [structs[sdkver][a] for a in ABIS]
if any(abis[0].needs_conversion(a) for a in abis[1:]):
unique = False
def is_always_compatible(other):
for a, b in zip(abis, other):
if a.needs_conversion(b):
return False
return True
compat = next((k for k, v in prev if is_always_compatible(v)), None)
if compat:
versions[sdkver] = versions[compat]
set_class_version(abis, compat)
else:
[abi.set_used() for abi in abis] # make sure order is computed
versions[sdkver] = f"{name}_{sdkver}"
prev += [(sdkver, abis)]
set_class_version(abis, sdkver)
if unique:
unique_structs.append(name)
if len(set(versions.values())) == 1:
versions = {sdkver: name for sdkver in versions.keys()}
for sdkver in filter(lambda v: v in structs, reversed(SDK_VERSIONS)):
abis = [structs[sdkver][a] for a in ABIS]
set_class_version(abis, "")
return versions
for i, sdkver in enumerate(SDK_VERSIONS):
print(f'loading SDKs... {i * 100 // len(SDK_VERSIONS)}%', end='\r')
all_versions[sdkver], all_sources[sdkver] = load(sdkver)
print('loading SDKs... 100%')
tmp_classes = {}
with concurrent.futures.ThreadPoolExecutor() as executor:
arg0 = [sdkver for sdkver in SDK_VERSIONS for abi in ABIS]
arg1 = [abi for sdkver in SDK_VERSIONS for abi in ABIS]
def parse_map(sdkver, abi):
return parse(all_sources[sdkver], sdkver, abi)
results = executor.map(parse_map, arg0, arg1)
for i, result in enumerate(results):
print(f'parsing SDKs... {i * 100 // len(arg0)}%', end='\r')
sdkver, abi, build = result
if sdkver not in all_records: all_records[sdkver] = {}
if sdkver not in tmp_classes: tmp_classes[sdkver] = {}
versions = all_versions[sdkver]
structs = build.cursor.get_children()
structs = filter(lambda c: c.is_definition(), structs)
structs = filter(lambda c: c.type.get_canonical().kind == TypeKind.RECORD, structs)
structs = filter(lambda c: c.kind != CursorKind.TYPEDEF_DECL, structs)
structs = filter(lambda c: c.spelling not in SDK_CLASSES, structs)
structs = [Record(sdkver, abi, c) for c in structs]
structs = {c.name: c for c in structs}
classes = build.cursor.get_children()
classes = filter(lambda c: c.is_definition(), classes)
classes = filter(lambda c: c.kind == CursorKind.CLASS_DECL, classes)
classes = filter(lambda c: c.spelling in SDK_CLASSES, classes)
classes = filter(lambda c: c.spelling[1:].upper() in versions, classes)
classes = [Class(sdkver, abi, c) for c in classes]
classes = {c.version: c for c in classes}
all_records[sdkver][abi] = structs
tmp_classes[sdkver][abi] = classes
for name, struct in structs.items():
if name not in all_structs:
all_structs[name] = {}
if sdkver not in all_structs[name]:
all_structs[name][sdkver] = {}
all_structs[name][sdkver][abi] = struct
for i, sdkver in enumerate(reversed(SDK_VERSIONS)):
all_classes.update(tmp_classes[sdkver]['u32'])
print('parsing SDKs... 100%')
tmp_structs = {}
for i, name in enumerate(all_structs.keys()):
print(f'classifying structs... {i * 100 // len(all_structs.keys())}%', end='\r')
versions = classify_struct(name)
for sdkver in SDK_VERSIONS:
if not versions or sdkver not in versions: continue
all_versions[sdkver][name] = versions[sdkver]
def struct_order(x):
name, structs = x if type(x) is tuple else (x, all_structs[x])
order = (struct.order for abis in structs.values()
for struct in abis.values())
return (min(order), name)
for name, structs in sorted(all_structs.items(), key=struct_order):
tmp_structs[name] = {}
for sdkver in filter(lambda v: v in structs, SDK_VERSIONS):
tmp_structs[name][sdkver] = {a: structs[sdkver][a] for a in ABIS}
all_structs = tmp_structs
print('classifying structs... 100%')
for klass in all_classes.values():
with open(f"win{klass.name}.c", "w") as file:
out = file.write
out(u'/* This file is auto-generated, do not edit. */\n')
out(u'#include "steamclient_private.h"\n')
out(u'\n')
out(u'WINE_DEFAULT_DEBUG_CHANNEL(steamclient);\n')
out(u'\n')
for _, klass in sorted(all_classes.items()):
sdkver = klass._sdkver
handle_class(klass)
for name in sorted(set(k.name for k in all_classes.values())):
with open(f"win{name}.c", "a") as file:
out = file.write
out(f'void init_win{name}_rtti( char *base )\n')
out(u'{\n')
out(u'#ifdef __x86_64__\n')
for _, klass in sorted(all_classes.items()):
with open(f"win{klass.name}.c", "a") as file:
out = file.write
out(f' init_win{klass.full_name}_rtti( base );\n')
for name in sorted(set(k.name for k in all_classes.values())):
with open(f"win{name}.c", "a") as file:
out = file.write
out(u'#endif /* __x86_64__ */\n')
out(u'}\n')
with open("steamclient_generated.h", "w") as file:
out = file.write
out(u'/* This file is auto-generated, do not edit. */\n\n')
for _, klass in sorted(all_classes.items()):
out(f"extern struct w_steam_iface *create_win{klass.full_name}(void *) DECLSPEC_HIDDEN;\n")
with open("steamclient_generated.c", "w") as file:
out = file.write
out(u'/* This file is auto-generated, do not edit. */\n\n')
out(u'#include "steamclient_private.h"\n\n')
out(u'static const struct { const char *iface_version; iface_constructor ctor; } constructors[] =\n')
out(u'{\n')
for _, klass in sorted(all_classes.items()):
out(f' {{"{klass.version}", &create_win{klass.full_name}}},\n')
for alias in VERSION_ALIASES.get(klass.version, []):
out(f' {{"{alias}", &create_win{klass.full_name}}}, /* alias */\n')
out(u'};\n')
out(u'\n')
out(u'iface_constructor find_iface_constructor( const char *iface_version )\n')
out(u'{\n')
out(u' int i;\n')
out(u' for (i = 0; i < ARRAYSIZE(constructors); ++i)\n')
out(u' if (!strcmp( iface_version, constructors[i].iface_version ))\n')
out(u' return constructors[i].ctor;\n')
out(u' return NULL;\n')
out(u'}\n\n')
for name in sorted(set(k.name for k in all_classes.values())):
out(f'extern void init_win{name}_rtti( char * );\n')
out(u'\n')
out(u'void init_rtti( char *base )\n')
out(u'{\n')
for name in sorted(set(k.name for k in all_classes.values())):
out(f' init_win{name}_rtti( base );\n')
out(u'}\n')
for name, klasses in all_classes.items():
if name not in SDK_CLASSES: continue
for sdkver, klass in klasses.items():
version = all_versions[sdkver][name[1:].upper()]
handle_class(sdkver, klass, version, SDK_CLASSES[name])
declared = {}
with open('steamclient_structs_generated.h', 'w') as file:
out = file.write
for name in sorted(unique_structs, key=struct_order):
if name in EXEMPT_STRUCTS: continue
for sdkver, abis in all_structs[name].items():
if name not in all_versions[sdkver]: continue
version = all_versions[sdkver][name]
if f'struct {version}' in declared: continue
declared[f'struct {version}'] = True
kind = 'union' if type(abis['w64']) is Union else 'struct'
out(f'typedef {kind} {version} {version};\n')
abis['w64'].write_definition(out, "", [])
for name, structs in all_structs.items():
if name in EXEMPT_STRUCTS: continue
if name in unique_structs: continue
for sdkver, abis in structs.items():
if name not in all_versions[sdkver]: continue
version = all_versions[sdkver][name]
if f'typedef {version}' in declared: continue
declared[f'typedef {version}'] = True
kind = 'union' if type(abis['w64']) is Union else 'struct'
if type(abis['w64']) is Class:
out(f'typedef {kind} u_{version} u_{version};\n')
out(f'typedef {kind} u_{version} u64_{version};\n')
out(f'typedef {kind} u_{version} u32_{version};\n')
out(f'typedef {kind} w_{version} w_{version};\n')
out(f'typedef {kind} w_{version} w64_{version};\n')
out(f'typedef {kind} w_{version} w32_{version};\n')
continue
if abis["w64"].needs_conversion(abis["u64"]):
out(f'typedef {kind} u64_{version} u64_{version};\n')
else:
out(f'typedef {kind} w64_{version} u64_{version};\n')
out(f'typedef {kind} w64_{version} w64_{version};\n')
if abis["w32"].needs_conversion(abis["u32"]):
out(f'typedef {kind} u32_{version} u32_{version};\n')
else:
out(f'typedef {kind} w32_{version} u32_{version};\n')
out(f'typedef {kind} w32_{version} w32_{version};\n')
for name, structs in all_structs.items():
if name in EXEMPT_STRUCTS: continue
if name in unique_structs: continue
for sdkver, abis in structs.items():
if name not in all_versions[sdkver]: continue
version = all_versions[sdkver][name]
if f'struct {version}' in declared: continue
declared[f'struct {version}'] = True
kind = 'union' if type(abis['w64']) is Union else 'struct'
if type(abis['w64']) is Class:
abis['w64'].write_definition(out, "w_")
abis['u64'].write_definition(out, "u_")
continue
if not abis["w64"].needs_conversion(abis["u64"]):
abis['w64'].write_definition(out, "w64_", [])
else:
abis['w64'].write_definition(out, "w64_", ["u64_"])
abis['u64'].write_definition(out, "u64_", ["w64_"])
if not abis["w32"].needs_conversion(abis["u32"]):
abis['w32'].write_definition(out, "w32_", [])
else:
abis['w32'].write_definition(out, "w32_", ["u32_"])
abis['u32'].write_definition(out, "u32_", ["w32_"])
out(u'#ifdef __i386__\n')
out(f'typedef w32_{version} w_{version};\n')
out(f'typedef u32_{version} u_{version};\n')
out(u'#endif\n')
out(u'#ifdef __x86_64__\n')
out(f'typedef w64_{version} w_{version};\n')
out(f'typedef u64_{version} u_{version};\n')
out(u'#endif\n')
out(u'\n')
all_methods = [(k, m) for _, k in sorted(all_classes.items())
for m in k.methods]
with open("unix_private_generated.h", "w") as file:
out = file.write
out(u'/* This file is auto-generated, do not edit. */\n\n')
out(u'#ifdef __cplusplus\n')
out(u'extern "C" {\n')
out(u'#endif /* __cplusplus */\n')
out(u'\n')
for klass in all_classes.values():
sdkver = klass._sdkver
klass.write_definition(out, "u_")
out(u'\n')
for klass, method in all_methods:
sdkver = klass._sdkver
if type(method) is Destructor:
continue
out(f'NTSTATUS {method.full_name}( void * );\n')
out(u'\n')
out(u'#ifdef __cplusplus\n')
out(u'} /* extern "C" */\n')
out(u'#endif /* __cplusplus */\n')
with open(u"unixlib_generated.h", "w") as file:
out = file.write
out(u'/* This file is auto-generated, do not edit. */\n')
out(u'#include <stdarg.h>\n')
out(u'#include <stddef.h>\n')
out(u'#include <stdint.h>\n')
out(u'\n')
out(u'#ifdef __cplusplus\n')
out(u'extern "C" {\n')
out(u'#endif /* __cplusplus */\n')
out(u'\n')
out(u'#include <pshpack1.h>\n\n')
for klass, method in all_methods:
sdkver = klass._sdkver
method.write_params(out)
out(u'#include <poppack.h>\n\n')
out(u'enum unix_funcs\n')
out(u'{\n')
for func in UNIX_FUNCS:
out(f' unix_{func},\n')
for klass, method in all_methods:
sdkver = klass._sdkver
if type(method) is Destructor:
continue
out(f' unix_{method.full_name},\n')
out(u'};\n')
out(u'\n')
out(u'#ifdef __cplusplus\n')
out(u'} /* extern "C" */\n')
out(u'#endif /* __cplusplus */\n')
with open('unixlib_generated.cpp', 'w') as file:
out = file.write
out(u'/* This file is auto-generated, do not edit. */\n\n')
out(u'#include "unix_private.h"\n\n')
out(u'extern "C" const unixlib_entry_t __wine_unix_call_funcs[] =\n')
out(u'{\n')
for func in UNIX_FUNCS:
out(f' {func},\n')
for klass, method in all_methods:
sdkver = klass._sdkver
if type(method) is Destructor:
continue
out(f' {method.full_name},\n')
out(u'};\n')
out(u'\n')
callbacks = []
for name in sorted(unique_structs, key=struct_order):
for sdkver, abis in all_structs[name].items():
if name not in all_versions[sdkver]: continue
version = all_versions[sdkver][name]
if f'checks {version}' in declared: continue
declared[f'checks {version}'] = True
abis['w64'].write_checks(out, "")
for name, structs in all_structs.items():
if name in EXEMPT_STRUCTS: continue
if name in unique_structs: continue
for sdkver, abis in structs.items():
if name not in all_versions[sdkver]: continue
version = all_versions[sdkver][name]
if f'checks {version}' in declared: continue
declared[f'checks {version}'] = True
if type(abis['w64']) is Class:
continue
cbid = abis["w64"].callback_id
if cbid is not None and (abis["w64"] != abis["u64"] or abis["w32"] != abis["u32"]):
callbacks += [(cbid, sdkver, abis)]
abis['w64'].write_checks(out, "w64_")
abis['u64'].write_checks(out, "u64_")
abis['w32'].write_checks(out, "w32_")
abis['u32'].write_checks(out, "u32_")
if name in MANUAL_STRUCTS: continue
for sdkver, abis in structs.items():
if name not in all_versions[sdkver]: continue
version = all_versions[sdkver][name]
if f'struct {version} convert' in declared: continue
declared[f'struct {version} convert'] = True
if type(abis['w64']) in (Class, Union):
continue
path_conv_fields = PATH_CONV_STRUCTS.get(name, {})
if abis["w64"].needs_conversion(abis["u64"]):
out(u'#ifdef __x86_64__\n')
abis['w64'].write_converter('u64_', {})
out(u'\n')
abis['u64'].write_converter('w64_', path_conv_fields)
out(u'#endif\n\n')
if abis["w32"].needs_conversion(abis["u32"]):
out(u'#ifdef __i386__\n')
abis['w32'].write_converter('u32_', {})
out(u'\n')
abis['u32'].write_converter('w32_', path_conv_fields)
out(u'#endif\n\n')
out(u'void callback_message_utow( const u_CallbackMsg_t *u_msg, w_CallbackMsg_t *w_msg )\n')
out(u'{\n')
out(u' int len;\n')
out(u'\n')
out(u'#define MAKE_CASE(id, wlen) ((uint64_t)(id) << 48) | ((uint64_t)(wlen) << 24)\n')
out(u' switch (MAKE_CASE(u_msg->m_iCallback, u_msg->m_cubParam))\n')
out(u' {\n')
out(u'#ifdef __i386__\n')
values = set()
for cbid, sdkver, abis in sorted(callbacks, key=lambda x: x[0]):
name, value = abis["w32"].name, (cbid, abis["u32"].size)
if name in all_versions[sdkver]: name = all_versions[sdkver][name]
if value not in values:
out(f' case MAKE_CASE({cbid}, {abis["u32"].size}): len = {abis["w32"].size}; break; /* {name} */\n')
else:
out(f' /* Conflict: case MAKE_CASE({cbid}, {abis["u32"].size}): len = {abis["w32"].size}; break; */ /* {name} */\n')
values.add(value)
out(u'#endif\n')
out(u'#ifdef __x86_64__\n')
values = set()
for cbid, sdkver, abis in sorted(callbacks, key=lambda x: x[0]):
name, value = abis["w64"].name, (cbid, abis["u64"].size)
if name in all_versions[sdkver]: name = all_versions[sdkver][name]
if value not in values:
out(f' case MAKE_CASE({cbid}, {abis["u64"].size}): len = {abis["w64"].size}; break; /* {name} */\n')
else:
out(f' /* Conflict: case MAKE_CASE({cbid}, {abis["u64"].size}): len = {abis["w64"].size}; break; */ /* {name} */\n')
values.add(value)
out(u'#endif\n')
out(u' default: len = u_msg->m_cubParam; break;\n')
out(u' }\n')
out(u'#undef MAKE_CASE\n')
out(u'\n')
out(u' w_msg->m_hSteamUser = u_msg->m_hSteamUser;\n')
out(u' w_msg->m_iCallback = u_msg->m_iCallback;\n')
out(u' w_msg->m_cubParam = len;\n')
out(u'}\n')
out(u'\n')
out(u'void *alloc_callback_wtou(int id, void *callback, int *callback_len)\n')
out(u'{\n')
out(u' int len;\n')
out(u'\n')
out(u'#define MAKE_CASE(id, wlen) ((uint64_t)(id) << 48) | ((uint64_t)(wlen) << 24)\n')
out(u' switch (MAKE_CASE(id, *callback_len))\n')
out(u' {\n')
out(u'#ifdef __i386__\n')
values = set()
for cbid, sdkver, abis in sorted(callbacks, key=lambda x: x[0]):
name, value = abis["u32"].name, (cbid, abis["w32"].size)
if name in all_versions[sdkver]: name = all_versions[sdkver][name]
if value not in values:
out(f' case MAKE_CASE({cbid}, {abis["w32"].size}): len = {abis["u32"].size}; break; /* {name} */\n')
else:
out(f' /* case MAKE_CASE({cbid}, {abis["w32"].size}): len = {abis["u32"].size}; break; {name} */\n')
values.add(value)
out(u'#endif\n')
out(u'#ifdef __x86_64__\n')
values = set()
for cbid, sdkver, abis in sorted(callbacks, key=lambda x: x[0]):
name, value = abis["u64"].name, (cbid, abis["w64"].size)
if name in all_versions[sdkver]: name = all_versions[sdkver][name]
if value not in values:
out(f' case MAKE_CASE({cbid}, {abis["w64"].size}): len = {abis["u64"].size}; break; /* {name} */\n')
else:
out(f' /* case MAKE_CASE({cbid}, {abis["w64"].size}): len = {abis["u64"].size}; break; {name} */\n')
values.add(value)
out(u'#endif\n')
out(u' default: return callback;\n')
out(u' }\n')
out(u'#undef MAKE_CASE\n')
out(u'\n')
out(u' callback = HeapAlloc( GetProcessHeap(), 0, len );\n')
out(u' *callback_len = len;\n')
out(u' return callback;\n')
out(u'}\n')
out(u'\n')
out(u'void convert_callback_utow(int id, void *u_callback, int u_callback_len, void *w_callback, int w_callback_len)\n')
out(u'{\n')
out(u'#define MAKE_CASE(id, wlen, ulen) ((uint64_t)(id) << 48) | ((uint64_t)(wlen) << 24) | (uint64_t)(ulen)\n')
out(u' switch (MAKE_CASE(id, w_callback_len, u_callback_len))\n')
out(u' {\n')
out(u'#ifdef __i386__\n')
values = set()
for cbid, sdkver, abis in sorted(callbacks, key=lambda x: x[0]):
name, value = abis["u32"].name, (cbid, abis["w32"].size, abis["u32"].size)
if name in all_versions[sdkver]: name = all_versions[sdkver][name]
if value not in values:
out(f' case MAKE_CASE({cbid}, {abis["w32"].size}, {abis["u32"].size}): *(w_{name} *)w_callback = *(u_{name} *)u_callback; break;\n')
else:
out(f' /* case MAKE_CASE({cbid}, {abis["w32"].size}, {abis["u32"].size}): *(w_{name} *)w_callback = *(u_{name} *)u_callback; break; */\n')
values.add(value)
out(u'#endif\n')
out(u'#ifdef __x86_64__\n')
values = set()
for cbid, sdkver, abis in sorted(callbacks, key=lambda x: x[0]):
name, value = abis["u64"].name, (cbid, abis["w64"].size, abis["u64"].size)
if name in all_versions[sdkver]: name = all_versions[sdkver][name]
if value not in values:
out(f' case MAKE_CASE({cbid}, {abis["w64"].size}, {abis["u64"].size}): *(w_{name} *)w_callback = *(u_{name} *)u_callback; break;\n')
else:
out(f' /* case MAKE_CASE({cbid}, {abis["w64"].size}, {abis["u64"].size}): *(w_{name} *)w_callback = *(u_{name} *)u_callback; break; */\n')
values.add(value)
out(u'#endif\n')
out(u' default: memcpy( w_callback, u_callback, u_callback_len ); break;\n')
out(u' }\n')
out(u'#undef MAKE_CASE\n')
out(u'}\n')