mirror of
https://github.com/ValveSoftware/Proton.git
synced 2024-12-28 23:55:52 +03:00
37853f8469
CW-Bug-Id: #22729
1580 lines
56 KiB
Python
Executable File
1580 lines
56 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
#NOTE: If you make modifications here, consider whether they should
|
|
#be duplicated in ../lsteamclient/gen_wrapper.py
|
|
|
|
CLANG_PATH='/usr/lib/clang/15'
|
|
|
|
from clang.cindex import Cursor, CursorKind, Index, Type, TypeKind
|
|
import concurrent.futures
|
|
import os
|
|
import re
|
|
|
|
SDK_VERSIONS = [
|
|
"v1.26.7",
|
|
"v1.23.7",
|
|
"v1.16.8",
|
|
"v1.14.15",
|
|
"v1.13.10",
|
|
"v1.12.5",
|
|
"v1.11.11",
|
|
"v1.10.30",
|
|
"v1.9.16",
|
|
"v1.8.19",
|
|
"v1.7.15",
|
|
"v1.6.10",
|
|
"v1.5.17",
|
|
"v1.4.18",
|
|
"v1.3.22",
|
|
"v1.2.10",
|
|
"v1.1.3b",
|
|
"v1.0.17",
|
|
"v1.0.16",
|
|
"v1.0.15",
|
|
"v1.0.14",
|
|
"v1.0.13",
|
|
"v1.0.12",
|
|
"v1.0.11",
|
|
"v1.0.10",
|
|
"v1.0.9",
|
|
"v1.0.8",
|
|
"v1.0.7",
|
|
"v1.0.6",
|
|
"v1.0.5",
|
|
"v1.0.4",
|
|
"v1.0.3a", #non-public build used by The Lab, see Proton github PR#2075
|
|
"v1.0.3",
|
|
"v1.0.2",
|
|
"v1.0.1",
|
|
"v1.0.0",
|
|
"v0.9.20",
|
|
"v0.9.19",
|
|
"v0.9.18",
|
|
"v0.9.17",
|
|
"v0.9.16",
|
|
"v0.9.15",
|
|
"v0.9.14",
|
|
"v0.9.13",
|
|
"v0.9.12",
|
|
# "v0.9.11", problematic GetComponentState, may cause crash since we don't implement it
|
|
"v0.9.10",
|
|
"v0.9.9",
|
|
"v0.9.8",
|
|
"v0.9.7",
|
|
"v0.9.6",
|
|
# "v0.9.5", doesn't compile, fixed in 0.9.6
|
|
"v0.9.4",
|
|
"0.9.3",
|
|
"0.9.2",
|
|
"0.9.1",
|
|
"0.9.0",
|
|
]
|
|
|
|
ABIS = ['u32', 'u64', 'w32', 'w64']
|
|
|
|
SDK_SOURCES = {
|
|
"ivrclientcore.h": [
|
|
[ #classes
|
|
"IVRApplications",
|
|
"IVRChaperone",
|
|
"IVRChaperoneSetup",
|
|
"IVRCompositor",
|
|
"IVRControlPanel",
|
|
"IVRDriverManager",
|
|
"IVRExtendedDisplay",
|
|
"IVRNotifications",
|
|
"IVRInput",
|
|
"IVRIOBuffer",
|
|
"IVRMailbox",
|
|
"IVROverlay",
|
|
"IVRRenderModels",
|
|
"IVRResources",
|
|
"IVRScreenshots",
|
|
"IVRSettings",
|
|
"IVRSystem",
|
|
"IVRTrackedCamera",
|
|
"IVRHeadsetView",
|
|
"IVROverlayView",
|
|
"IVRClientCore",
|
|
], [ #vrclient-allocated structs
|
|
"RenderModel_t",
|
|
"RenderModel_TextureMap_t",
|
|
],
|
|
],
|
|
}
|
|
|
|
SDK_CLASSES = {klass: source for source, value in SDK_SOURCES.items()
|
|
for klass in value[0]}
|
|
SDK_STRUCTS = {klass: source for source, value in SDK_SOURCES.items()
|
|
for klass in value[1]}
|
|
|
|
STRUCTS_NEXT_IS_SIZE = [
|
|
"VREvent_t",
|
|
"VRControllerState001_t",
|
|
"InputAnalogActionData_t",
|
|
"InputDigitalActionData_t",
|
|
"InputPoseActionData_t",
|
|
"InputSkeletalActionData_t",
|
|
"CameraVideoStreamFrameHeader_t",
|
|
"Compositor_CumulativeStats",
|
|
"VRActiveActionSet_t",
|
|
"InputOriginInfo_t",
|
|
"InputBindingInfo_t",
|
|
]
|
|
|
|
STRUCTS_NEXT_IS_SIZE_UNHANDLED = [
|
|
"VROverlayIntersectionMaskPrimitive_t" # not next, but next-next uint32 is the size
|
|
]
|
|
|
|
STRUCTS_SIZE_FIELD = {
|
|
"Compositor_OverlaySettings": ["size"],
|
|
"Compositor_FrameTiming": ["size", "m_nSize"],
|
|
"DriverDirectMode_FrameTiming": ["m_nSize"],
|
|
}
|
|
|
|
PATH_CONV = [
|
|
{
|
|
"parent_name": "SetActionManifestPath",
|
|
"l2w_names":[],
|
|
"l2w_lens":[],
|
|
"w2l_names": ["pchActionManifestPath"],
|
|
"w2l_arrays": [False],
|
|
"return_is_size": False
|
|
},
|
|
{
|
|
"parent_name": "SetOverlayFromFile",
|
|
"l2w_names":[],
|
|
"l2w_lens":[],
|
|
"w2l_names": ["pchFilePath"],
|
|
"w2l_arrays": [False],
|
|
"return_is_size": False
|
|
},
|
|
{
|
|
"parent_name": "AddApplicationManifest",
|
|
"l2w_names":[],
|
|
"l2w_lens":[],
|
|
"w2l_names": ["pchApplicationManifestFullPath"],
|
|
"w2l_arrays": [False],
|
|
"return_is_size": False
|
|
},
|
|
{
|
|
"parent_name": "RemoveApplicationManifest",
|
|
"l2w_names":[],
|
|
"l2w_lens":[],
|
|
"w2l_names": ["pchApplicationManifestFullPath"],
|
|
"w2l_arrays": [False],
|
|
"return_is_size": False
|
|
},
|
|
{
|
|
"parent_name": "RequestScreenshot",
|
|
"l2w_names":[],
|
|
"l2w_lens":[],
|
|
"w2l_names": ["pchPreviewFilename", "pchVRFilename"],
|
|
"w2l_arrays": [False, False],
|
|
"return_is_size": False
|
|
},
|
|
{
|
|
"parent_name": "TakeStereoScreenshot",
|
|
"l2w_names":[],
|
|
"l2w_lens":[],
|
|
"w2l_names": ["pchPreviewFilename", "pchVRFilename"],
|
|
"w2l_arrays": [False, False],
|
|
"return_is_size": False
|
|
},
|
|
{
|
|
"parent_name": "SubmitScreenshot",
|
|
"l2w_names":[],
|
|
"l2w_lens":[],
|
|
"w2l_names": ["pchSourcePreviewFilename", "pchSourceVRFilename"],
|
|
"w2l_arrays": [False, False],
|
|
"return_is_size": False
|
|
},
|
|
{
|
|
"parent_name": "GetScreenshotPropertyFilename",
|
|
"l2w_names":["pchFilename"],
|
|
"l2w_lens":["cchFilename"],
|
|
"w2l_names": [],
|
|
"w2l_arrays": [],
|
|
"return_is_size": True
|
|
},
|
|
{
|
|
"parent_name": "undoc23",
|
|
"l2w_names":[],
|
|
"l2w_lens":[],
|
|
"w2l_names": ["a"],
|
|
"w2l_arrays": [False],
|
|
"return_is_size": False
|
|
},
|
|
{
|
|
"parent_name": "undoc27",
|
|
"l2w_names":[],
|
|
"l2w_lens":[],
|
|
"w2l_names": ["a"],
|
|
"w2l_arrays": [False],
|
|
"return_is_size": False
|
|
},
|
|
{
|
|
"parent_name": "SetStageOverride_Async",
|
|
"l2w_names":[],
|
|
"l2w_lens":[],
|
|
"w2l_names": ["pchRenderModelPath"],
|
|
"w2l_arrays": [False],
|
|
"return_is_size": False
|
|
},
|
|
|
|
# {#maybe?
|
|
# "parent_name": "GetRenderModelOriginalPath",
|
|
# "l2w_names":[pchOriginalPath],
|
|
# "l2w_lens":[unOriginalPathLen],
|
|
# "w2l_names": [],
|
|
# "w2l_arrays": [],
|
|
# "return_is_size": False
|
|
# },
|
|
# {#maybe?
|
|
# "parent_name": "GetResourceFullPath",
|
|
# "l2w_names":[pchPathBuffer],
|
|
# "l2w_lens":[unBufferLen],
|
|
# "w2l_names": [pchResourceTypeDirectory],
|
|
# "w2l_arrays": [False],
|
|
# "return_is_size": False
|
|
# },
|
|
#IVRInput::GetInputSourceHandle
|
|
#IVRIOBuffer::Open
|
|
|
|
#TODO: LaunchInternalProcess, need steam cooperation
|
|
]
|
|
|
|
struct_conversion_cache = {}
|
|
struct_needs_size_adjustment_cache = {}
|
|
|
|
all_classes = {}
|
|
all_records = {}
|
|
all_structs = {}
|
|
all_sources = {}
|
|
all_versions = {}
|
|
|
|
|
|
def ivrclientcore_is_hmd_present(cppname, method):
|
|
return "ivrclientcore_is_hmd_present"
|
|
|
|
def ivrclientcore_init(cppname, method):
|
|
if "002" in cppname:
|
|
return "ivrclientcore_002_init"
|
|
return "ivrclientcore_init"
|
|
|
|
def ivrclientcore_get_generic_interface(cppname, method):
|
|
return "ivrclientcore_get_generic_interface"
|
|
|
|
def ivrclientcore_cleanup(cppname, method):
|
|
return "ivrclientcore_cleanup"
|
|
|
|
def ivrsystem_get_dxgi_output_info(cppname, method):
|
|
arguments = list(method.get_arguments())
|
|
param_count = len(arguments)
|
|
return {
|
|
1: "get_dxgi_output_info",
|
|
2: "get_dxgi_output_info2"
|
|
}.get(param_count, "unhandled_get_dxgi_output_info_method")
|
|
|
|
def ivrsystem_get_output_device(cppname, method):
|
|
#introduced in 016, changed in 017
|
|
if "016" in cppname:
|
|
return "ivrsystem_016_get_output_device"
|
|
return "ivrsystem_get_output_device"
|
|
|
|
def ivrcompositor_submit(cppname, method):
|
|
if "005" in cppname:
|
|
return "ivrcompositor_005_submit"
|
|
if "006" in cppname:
|
|
return "ivrcompositor_006_submit"
|
|
if "007" in cppname:
|
|
return "ivrcompositor_007_submit"
|
|
if "008" in cppname:
|
|
return "ivrcompositor_008_submit"
|
|
return "ivrcompositor_submit"
|
|
|
|
def ivrcompositor_set_skybox_override(cppname, method):
|
|
if "008" in cppname:
|
|
return "ivrcompositor_008_set_skybox_override"
|
|
return "ivrcompositor_set_skybox_override"
|
|
|
|
def ivrcompositor_post_present_handoff(cppname, method):
|
|
return "ivrcompositor_post_present_handoff"
|
|
|
|
def ivrcompositor_wait_get_poses(cppname, method):
|
|
for version in ["016", "017", "018", "019", "020", "021", "022", "024", "026"]:
|
|
if version in cppname:
|
|
return "ivrcompositor_wait_get_poses"
|
|
return None
|
|
|
|
def ivrcompositor_get_vulkan_device_extensions_required(cppname, method):
|
|
return "ivrcompositor_get_vulkan_device_extensions_required"
|
|
|
|
def ivrrendermodels_load_texture_d3d11_async(cppname, method):
|
|
assert "004" in cppname or "005" in cppname or "006" in cppname
|
|
return "ivrrendermodels_load_texture_d3d11_async"
|
|
|
|
def ivrrendermodels_free_texture_d3d11(cppname, method):
|
|
assert "004" in cppname or "005" in cppname or "006" in cppname
|
|
return "ivrrendermodels_free_texture_d3d11"
|
|
|
|
def ivrrendermodels_load_into_texture_d3d11_async(cppname, method):
|
|
assert "005" in cppname or "006" in cppname
|
|
return "ivrrendermodels_load_into_texture_d3d11_async"
|
|
|
|
def ivrmailbox_undoc3(cppname, method):
|
|
assert "001" in cppname
|
|
return "ivrmailbox_undoc3"
|
|
|
|
def ivroverlay_set_overlay_texture(cppname, method):
|
|
if "001" in cppname:
|
|
return "ivroverlay_001_set_overlay_texture"
|
|
for version in ["002", "003", "004", "005"]:
|
|
if version in cppname:
|
|
return "ivroverlay_005_set_overlay_texture"
|
|
assert \
|
|
"007" in cppname or \
|
|
"008" in cppname or \
|
|
"010" in cppname or \
|
|
"011" in cppname or \
|
|
"012" in cppname or \
|
|
"013" in cppname or \
|
|
"014" in cppname or \
|
|
"016" in cppname or \
|
|
"017" in cppname or \
|
|
"018" in cppname or \
|
|
"019" in cppname or \
|
|
"020" in cppname or \
|
|
"021" in cppname or \
|
|
"022" in cppname or \
|
|
"024" in cppname or \
|
|
"025" in cppname or \
|
|
"026" in cppname or \
|
|
"027" in cppname
|
|
return "ivroverlay_set_overlay_texture"
|
|
|
|
def ivrinput_get_digital_action_data(cppname, method):
|
|
if "003" in cppname:
|
|
return None
|
|
return "ivrinput_get_digital_action_data"
|
|
|
|
method_overrides = [
|
|
("IVRClientCore", "BIsHmdPresent", ivrclientcore_is_hmd_present),
|
|
("IVRClientCore", "Init", ivrclientcore_init),
|
|
("IVRClientCore", "GetGenericInterface", ivrclientcore_get_generic_interface),
|
|
("IVRClientCore", "Cleanup", ivrclientcore_cleanup),
|
|
("IVRSystem", "GetDXGIOutputInfo", ivrsystem_get_dxgi_output_info),
|
|
("IVRSystem", "GetOutputDevice", ivrsystem_get_output_device),
|
|
("IVRCompositor", "Submit", ivrcompositor_submit),
|
|
("IVRCompositor", "SetSkyboxOverride", ivrcompositor_set_skybox_override),
|
|
("IVRCompositor", "PostPresentHandoff", ivrcompositor_post_present_handoff),
|
|
("IVRCompositor", "WaitGetPoses", ivrcompositor_wait_get_poses),
|
|
("IVRCompositor", "GetVulkanDeviceExtensionsRequired", ivrcompositor_get_vulkan_device_extensions_required),
|
|
("IVRRenderModels", "LoadTextureD3D11_Async", ivrrendermodels_load_texture_d3d11_async),
|
|
("IVRRenderModels", "FreeTextureD3D11", ivrrendermodels_free_texture_d3d11),
|
|
("IVRRenderModels", "LoadIntoTextureD3D11_Async", ivrrendermodels_load_into_texture_d3d11_async),
|
|
("IVRMailbox", "undoc3", ivrmailbox_undoc3),
|
|
("IVROverlay", "SetOverlayTexture", ivroverlay_set_overlay_texture),
|
|
("IVRInput", "GetDigitalActionData", ivrinput_get_digital_action_data),
|
|
]
|
|
|
|
method_overrides_data = [
|
|
("IVRClientCore", "struct client_core_data", None),
|
|
]
|
|
|
|
|
|
class Method:
|
|
def __init__(self, sdkver, abi, cursor, index, override):
|
|
self._sdkver = sdkver
|
|
self._abi = abi
|
|
|
|
self._cursor = cursor
|
|
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
|
|
|
|
def get_arguments(self):
|
|
return self._cursor.get_arguments()
|
|
|
|
def get_children(self):
|
|
return self._cursor.get_children()
|
|
|
|
|
|
class Destructor(Method):
|
|
def __init__(self, sdkver, abi, cursor, index, override):
|
|
super().__init__(sdkver, abi, cursor, index, override)
|
|
|
|
@property
|
|
def name(self):
|
|
if self._override > 1: return f'destructor_{self._override}'
|
|
return 'destructor'
|
|
|
|
|
|
class Class:
|
|
def __init__(self, sdkver, abi, cursor):
|
|
self._sdkver = sdkver
|
|
self._abi = abi
|
|
|
|
self._cursor = cursor
|
|
|
|
self.spelling = cursor.spelling
|
|
self.filename = SDK_CLASSES[self.spelling]
|
|
self.version = all_versions[sdkver][self.spelling]
|
|
|
|
self._methods = None
|
|
|
|
@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, index, override))
|
|
else:
|
|
self._methods.append(Method(self._sdkver, self._abi, method, index, override))
|
|
|
|
return self._methods
|
|
|
|
def get_children(self):
|
|
return self._cursor.get_children()
|
|
|
|
|
|
def display_sdkver(s):
|
|
if s.startswith("v"):
|
|
s = s[1:]
|
|
return s.replace(".", "")
|
|
|
|
def strip_ns(name):
|
|
return name.replace("vr::","")
|
|
|
|
def get_path_converter(parent):
|
|
for conv in PATH_CONV:
|
|
if conv["parent_name"] in parent.spelling:
|
|
if None in conv["l2w_names"]:
|
|
return conv
|
|
if type(parent) == Type:
|
|
children = list(parent.get_fields())
|
|
else:
|
|
children = list(parent.get_children())
|
|
for child in children:
|
|
if child.spelling in conv["w2l_names"] or \
|
|
child.spelling in conv["l2w_names"]:
|
|
return conv
|
|
return None
|
|
|
|
|
|
def underlying_type(decl):
|
|
if type(decl) is Cursor:
|
|
decl = decl.type
|
|
decl = decl.get_canonical()
|
|
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 declspec(decl, name):
|
|
if type(decl) is Cursor:
|
|
decl = decl.type
|
|
|
|
const = 'const ' if decl.is_const_qualified() else ''
|
|
if decl.kind in (TypeKind.POINTER, TypeKind.LVALUEREFERENCE):
|
|
decl = decl.get_pointee()
|
|
return declspec(decl, f"*{const}{name}")
|
|
if decl.kind == TypeKind.CONSTANTARRAY:
|
|
decl, count = decl.element_type, decl.element_count
|
|
return declspec(decl, f"({const}{name})[{count}]")
|
|
|
|
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'{decl.spelling.split("::")[-1]}{name}'
|
|
|
|
real_name = canonical_typename(decl)
|
|
real_name = real_name.removeprefix("const ")
|
|
real_name = real_name.removeprefix("vr::")
|
|
|
|
if real_name in SDK_STRUCTS:
|
|
typename = f"win{real_name}_{display_sdkver(sdkver)}"
|
|
elif struct_needs_conversion(decl.get_canonical()) \
|
|
and not decl.is_const_qualified(): # FIXME
|
|
typename = f"win{real_name}_{display_sdkver(sdkver)}"
|
|
else:
|
|
typename = decl.spelling
|
|
typename = typename.removeprefix("const ")
|
|
typename = typename.removeprefix("vr::")
|
|
|
|
return f'{const}{typename}{name}'
|
|
|
|
|
|
def handle_method_hpp(method, cppname, out):
|
|
ret = f'{declspec(method.result_type, "")} '
|
|
|
|
params = [declspec(p, "") for p in method.get_arguments()]
|
|
params = ['void *'] + params
|
|
|
|
out(f'extern {ret}{cppname}_{method.name}({", ".join(params)});\n')
|
|
|
|
|
|
def handle_method_cpp(method, classname, cppname, out):
|
|
returns_void = method.result_type.kind == TypeKind.VOID
|
|
|
|
ret = f'{declspec(method.result_type, "")} '
|
|
|
|
names = [p.spelling if p.spelling != "" else f'_{chr(0x61 + i)}'
|
|
for i, p in enumerate(method.get_arguments())]
|
|
params = [declspec(p, names[i]) for i, p in enumerate(method.get_arguments())]
|
|
|
|
names = ['linux_side'] + names
|
|
params = ['void *linux_side'] + params
|
|
|
|
out(f'{ret}{cppname}_{method.name}({", ".join(params)})\n')
|
|
out(u'{\n')
|
|
|
|
if not returns_void:
|
|
out(f' {declspec(method.result_type, "_ret")};\n')
|
|
|
|
do_lin_to_win = None
|
|
do_win_to_lin = None
|
|
do_wrap = None
|
|
do_unwrap = None
|
|
for param in method.get_arguments():
|
|
if param.type.kind == TypeKind.POINTER and \
|
|
param.type.get_pointee().kind == TypeKind.UNEXPOSED:
|
|
#unspecified function pointer
|
|
continue
|
|
|
|
real_type = param.type;
|
|
while real_type.kind == TypeKind.POINTER:
|
|
real_type = real_type.get_pointee()
|
|
if param.type.kind == TypeKind.POINTER:
|
|
if strip_ns(param.type.get_pointee().get_canonical().spelling) in SDK_STRUCTS:
|
|
do_unwrap = (strip_ns(param.type.get_pointee().get_canonical().spelling), param.spelling)
|
|
elif param.type.get_pointee().get_canonical().kind == TypeKind.POINTER and \
|
|
strip_ns(param.type.get_pointee().get_pointee().get_canonical().spelling) in SDK_STRUCTS:
|
|
do_wrap = (strip_ns(param.type.get_pointee().get_pointee().get_canonical().spelling), param.spelling)
|
|
elif real_type.get_canonical().kind == TypeKind.RECORD and \
|
|
struct_needs_conversion(real_type.get_canonical()):
|
|
real_name = canonical_typename(real_type)
|
|
do_win_to_lin = (strip_ns(real_name), param.spelling)
|
|
if not real_type.is_const_qualified():
|
|
do_lin_to_win = (strip_ns(real_name), param.spelling)
|
|
|
|
if do_lin_to_win or do_win_to_lin:
|
|
if do_lin_to_win:
|
|
out(f' {do_lin_to_win[0]} lin;\n')
|
|
else:
|
|
out(f' {do_win_to_lin[0]} lin;\n')
|
|
|
|
if do_wrap:
|
|
out(f' {do_wrap[0]} *lin;\n')
|
|
|
|
if do_win_to_lin:
|
|
#XXX we should pass the struct size here
|
|
out(f' if ({do_win_to_lin[1]})\n')
|
|
out(f' struct_{strip_ns(do_win_to_lin[0])}_{display_sdkver(sdkver)}_win_to_lin({do_win_to_lin[1]}, &lin);\n')
|
|
|
|
|
|
size_fixup = {}
|
|
convert_size_param = ""
|
|
params = list(zip(names[1:], method.get_arguments()))
|
|
params += [(None, None)] # for next_name, next_param
|
|
for i, (name, param) in enumerate(params[:-1]):
|
|
real_type = underlying_type(param)
|
|
if strip_ns(real_type.spelling) not in STRUCTS_NEXT_IS_SIZE:
|
|
continue
|
|
|
|
next_name, next_param = params[i + 1]
|
|
if not next_param or next_param.type.spelling != "uint32_t":
|
|
convert_size_param = ', -1'
|
|
elif struct_needs_size_adjustment(real_type.get_canonical()):
|
|
real_name = real_type.spelling
|
|
out(f' uint32_t lin_{next_name} = std::min({next_name}, (uint32_t)sizeof({real_name}));\n')
|
|
convert_size_param = f', {next_name}'
|
|
size_fixup[next_name] = True
|
|
elif do_win_to_lin and do_win_to_lin[1] == name:
|
|
assert do_win_to_lin[0] not in STRUCTS_NEXT_IS_SIZE_UNHANDLED
|
|
out(f' uint32_t lin_{next_name} = {next_name} ? sizeof(lin) : 0;\n')
|
|
convert_size_param = f', {next_name}'
|
|
size_fixup[next_name] = True
|
|
|
|
if returns_void:
|
|
out(u' ')
|
|
else:
|
|
out(u' _ret = ')
|
|
|
|
params = []
|
|
for name, param in zip(names[1:], method.get_arguments()):
|
|
if name in size_fixup:
|
|
params.append(f'lin_{name}')
|
|
elif do_lin_to_win and do_lin_to_win[1] == name or \
|
|
do_win_to_lin and do_win_to_lin[1] == name or \
|
|
do_wrap and do_wrap[1] == name:
|
|
params.append("%s ? &lin : nullptr" % name)
|
|
elif do_unwrap and do_unwrap[1] == name:
|
|
params.append("struct_%s_%s_unwrap(%s)" % (strip_ns(do_unwrap[0]), display_sdkver(sdkver), do_unwrap[1]))
|
|
elif "&" in param.type.spelling:
|
|
params.append("*%s" % name)
|
|
else:
|
|
params.append("(%s)%s" % (param.type.spelling, name))
|
|
|
|
out(f'(({classname}*)linux_side)->{method.spelling}({", ".join(params)});\n')
|
|
|
|
if do_lin_to_win:
|
|
out(f' if ({do_lin_to_win[1]})\n')
|
|
out(f' struct_{strip_ns(do_lin_to_win[0])}_{display_sdkver(sdkver)}_lin_to_win(&lin, {do_lin_to_win[1]}{convert_size_param});\n')
|
|
if do_wrap and not returns_void:
|
|
out(u' if (_ret == 0)\n')
|
|
out(f' *{do_wrap[1]} = struct_{strip_ns(do_wrap[0])}_{display_sdkver(sdkver)}_wrap(lin);\n')
|
|
|
|
if not returns_void:
|
|
out(u' return _ret;\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.spelling}_{klass.version}_{method.name}'
|
|
out(f'DEFINE_THISCALL_WRAPPER({name}, {size})\n')
|
|
|
|
|
|
def handle_method_c(method, classname, winclassname, cppname, iface_version, 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)} '
|
|
|
|
names = [p.spelling if p.spelling != "" else f'_{chr(0x61 + i)}'
|
|
for i, p in enumerate(method.get_arguments())]
|
|
params = [declspec(p, names[i]) for i, p in enumerate(method.get_arguments())]
|
|
|
|
if returns_record:
|
|
params = [f'{declspec(method.result_type, "*_ret")}'] + params
|
|
names = ['_ret'] + names
|
|
|
|
params = [f'{winclassname} *_this'] + params
|
|
names = ['_this'] + names
|
|
|
|
out(f'{ret}__thiscall {winclassname}_{method.name}({", ".join(params)})\n')
|
|
out(u'{\n')
|
|
|
|
if returns_record:
|
|
del params[1]
|
|
del names[1]
|
|
|
|
if not returns_record and not returns_void:
|
|
out(f' {ret}_ret;\n')
|
|
|
|
path_conv = get_path_converter(method)
|
|
|
|
if path_conv:
|
|
for i in range(len(path_conv["w2l_names"])):
|
|
if path_conv["w2l_arrays"][i]:
|
|
out(f' const char **lin_{path_conv["w2l_names"][i]} = vrclient_dos_to_unix_stringlist({path_conv["w2l_names"][i]});\n')
|
|
# TODO
|
|
pass
|
|
else:
|
|
out(f' char lin_{path_conv["w2l_names"][i]}[PATH_MAX];\n')
|
|
out(f' vrclient_dos_path_to_unix_path({path_conv["w2l_names"][i]}, lin_{path_conv["w2l_names"][i]});\n')
|
|
|
|
out(u' TRACE("%p\\n", _this);\n')
|
|
|
|
if returns_record:
|
|
out(u' *_ret = ')
|
|
elif not returns_void:
|
|
out(u' _ret = ')
|
|
else:
|
|
out(u' ')
|
|
|
|
should_gen_wrapper = strip_ns(method.result_type.spelling).startswith("IVR")
|
|
if should_gen_wrapper:
|
|
out(u'create_win_interface(pchNameAndVersion,\n ')
|
|
|
|
is_method_overridden = False
|
|
for classname_pattern, methodname, override_generator in method_overrides:
|
|
if method.name == methodname and classname_pattern in classname:
|
|
fn_name = override_generator(cppname, method)
|
|
if fn_name:
|
|
out("%s(%s_%s, " % (fn_name, cppname, method.name))
|
|
is_method_overridden = True
|
|
break
|
|
else:
|
|
out(f'{cppname}_{method.name}(')
|
|
|
|
def param_call(param, name):
|
|
if name == '_this': return '_this->linux_side'
|
|
if path_conv and name in path_conv["w2l_names"]: return f'{name} ? lin_{name} : NULL'
|
|
return name
|
|
|
|
params = ['_this'] + list(method.get_arguments())
|
|
out(", ".join([param_call(p, n) for p, n in zip(params, names)]))
|
|
|
|
if should_gen_wrapper:
|
|
out(u')')
|
|
|
|
if should_gen_wrapper:
|
|
out(u')')
|
|
if is_method_overridden:
|
|
out(f', {iface_version[iface_version.find("_") + 1:].lstrip("0")}')
|
|
for classname_pattern, user_data_type, _ in method_overrides_data:
|
|
if classname_pattern in classname:
|
|
out(u', &_this->user_data')
|
|
break
|
|
out(u');\n')
|
|
|
|
if path_conv and len(path_conv["l2w_names"]) > 0:
|
|
for i in range(len(path_conv["l2w_names"])):
|
|
assert(path_conv["l2w_names"][i]) #otherwise, no name means string is in return value. needs special handling.
|
|
out(u' ')
|
|
if path_conv["return_is_size"]:
|
|
out(u'_ret = ')
|
|
out(f'vrclient_unix_path_to_dos_path(_ret, {path_conv["l2w_names"][i]}, {path_conv["l2w_names"][i]}, {path_conv["l2w_lens"][i]});\n')
|
|
if path_conv:
|
|
for i in range(len(path_conv["w2l_names"])):
|
|
if path_conv["w2l_arrays"][i]:
|
|
out(f' vrclient_free_stringlist(lin_{path_conv["w2l_names"][i]});\n')
|
|
|
|
if not returns_void:
|
|
out(u' return _ret;\n')
|
|
out(u'}\n\n')
|
|
|
|
|
|
max_c_api_param_count = 0
|
|
|
|
def get_capi_thunk_params(method):
|
|
def toBOOL(x):
|
|
return "TRUE" if x else "FALSE"
|
|
returns_record = method.result_type.get_canonical().kind == TypeKind.RECORD
|
|
param_types = [x.type for x in method.get_arguments()]
|
|
if returns_record:
|
|
param_types.insert(0, method.result_type)
|
|
param_count = len(param_types)
|
|
has_float_params = any(x.spelling == "float" for x in param_types)
|
|
is_4th_float = param_count >= 4 and param_types[3].spelling == "float"
|
|
return "%s, %s, %s" % (param_count, toBOOL(has_float_params), toBOOL(is_4th_float))
|
|
|
|
def handle_class(klass):
|
|
file_exists = os.path.isfile(f"vrclient_x64/win{klass.spelling}.c")
|
|
cfile = open(f"vrclient_x64/win{klass.spelling}.c", "a")
|
|
if not file_exists:
|
|
cfile.write("""/* This file is auto-generated, do not edit. */
|
|
#include <stdarg.h>
|
|
#include <stdint.h>
|
|
|
|
#include "windef.h"
|
|
#include "winbase.h"
|
|
#include "wine/debug.h"
|
|
|
|
#include "cxx.h"
|
|
|
|
#include "vrclient_defs.h"
|
|
|
|
#include "vrclient_private.h"
|
|
|
|
#include "struct_converters.h"
|
|
|
|
#include "flatapi.h"
|
|
|
|
WINE_DEFAULT_DEBUG_CHANNEL(vrclient);
|
|
|
|
""")
|
|
|
|
cppname = f"cpp{klass.spelling}_{klass.version}"
|
|
cpp = open("vrclient_x64/%s.cpp" % cppname, "w")
|
|
cpp.write("#include \"vrclient_private.h\"\n")
|
|
cpp.write("#include \"vrclient_defs.h\"\n")
|
|
if os.path.isfile("openvr_%s/ivrclientcore.h" % sdkver):
|
|
cpp.write("#include \"openvr_%s/ivrclientcore.h\"\n" % sdkver)
|
|
else:
|
|
cpp.write("#include \"openvr_%s/openvr.h\"\n" % sdkver)
|
|
cpp.write("using namespace vr;\n")
|
|
cpp.write("extern \"C\" {\n")
|
|
cpp.write("#include \"struct_converters.h\"\n")
|
|
cpp.write("}\n")
|
|
cpp.write("#include \"%s.h\"\n" % cppname)
|
|
cpp.write("#ifdef __cplusplus\nextern \"C\" {\n#endif\n")
|
|
|
|
cpp_h = open("vrclient_x64/%s.h" % cppname, "w")
|
|
cpp_h.write("#ifdef __cplusplus\nextern \"C\" {\n#endif\n")
|
|
|
|
winclassname = f"win{klass.spelling}_{klass.version}"
|
|
cfile.write("#include \"%s.h\"\n\n" % cppname)
|
|
cfile.write("typedef struct __%s {\n" % winclassname)
|
|
cfile.write(" vtable_ptr *vtable;\n") # make sure to keep this first (flat API depends on it)
|
|
cfile.write(" void *linux_side;\n")
|
|
for classname_pattern, user_data_type, _ in method_overrides_data:
|
|
if classname_pattern in klass.spelling:
|
|
cfile.write(" %s user_data;\n" % user_data_type)
|
|
break
|
|
cfile.write("} %s;\n\n" % winclassname)
|
|
|
|
for method in klass.methods:
|
|
handle_thiscall_wrapper(klass, method, cfile.write)
|
|
cfile.write('\n')
|
|
|
|
for method in klass.methods:
|
|
if type(method) is Destructor:
|
|
continue
|
|
handle_method_hpp(method, cppname, cpp_h.write)
|
|
|
|
for method in klass.methods:
|
|
if type(method) is Destructor:
|
|
continue
|
|
handle_method_cpp(method, klass.spelling, cppname, cpp.write)
|
|
|
|
for method in klass.methods:
|
|
if type(method) is Destructor:
|
|
continue
|
|
else:
|
|
handle_method_c(method, klass.spelling, winclassname, cppname, klass.version, cfile.write)
|
|
|
|
cfile.write("extern vtable_ptr %s_vtable;\n\n" % winclassname)
|
|
cfile.write("#ifndef __GNUC__\n")
|
|
cfile.write("void __asm_dummy_vtables(void) {\n")
|
|
cfile.write("#endif\n")
|
|
cfile.write(" __ASM_VTABLE(%s,\n" % winclassname)
|
|
for method in sorted(klass.methods, key=lambda x: (x._index, -x._override)):
|
|
cfile.write(f" VTABLE_ADD_FUNC({winclassname}_{method.name})\n")
|
|
cfile.write(" );\n")
|
|
cfile.write("#ifndef __GNUC__\n")
|
|
cfile.write("}\n")
|
|
cfile.write("#endif\n\n")
|
|
cfile.write("%s *create_%s(void *linux_side)\n{\n" % (winclassname, winclassname))
|
|
cfile.write(" %s *r = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(%s));\n" % (winclassname, winclassname))
|
|
cfile.write(" TRACE(\"-> %p\\n\", r);\n")
|
|
cfile.write(" r->vtable = &%s_vtable;\n" % winclassname)
|
|
cfile.write(" r->linux_side = linux_side;\n")
|
|
cfile.write(" return r;\n}\n\n")
|
|
cfile.write("void destroy_%s(void *object)\n{\n" % winclassname)
|
|
cfile.write(" TRACE(\"%p\\n\", object);\n")
|
|
for classname_pattern, user_data_type, user_data_destructor in method_overrides_data:
|
|
if user_data_destructor and classname_pattern in klass.spelling:
|
|
cfile.write(" struct __%s *win_object = object;\n" % winclassname)
|
|
cfile.write(" %s(&win_object->user_data);\n" % user_data_destructor)
|
|
break
|
|
cfile.write(" HeapFree(GetProcessHeap(), 0, object);\n}\n\n")
|
|
|
|
# flat (FnTable) API
|
|
cfile.write("%s *create_%s_FnTable(void *linux_side)\n{\n" % (winclassname, winclassname))
|
|
cfile.write(" %s *r = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(%s));\n" % (winclassname, winclassname))
|
|
cfile.write(" struct thunk *thunks = alloc_thunks(%d);\n" % len(klass.methods))
|
|
cfile.write(" struct thunk **vtable = HeapAlloc(GetProcessHeap(), 0, %d * sizeof(*vtable));\n" % len(klass.methods))
|
|
cfile.write(" int i;\n\n")
|
|
cfile.write(" TRACE(\"-> %p, vtable %p, thunks %p\\n\", r, vtable, thunks);\n")
|
|
for i, method in enumerate(klass.methods):
|
|
thunk_params = get_capi_thunk_params(method)
|
|
arguments = list(method.get_arguments())
|
|
global max_c_api_param_count
|
|
max_c_api_param_count = max(len(arguments), max_c_api_param_count)
|
|
cfile.write(" init_thunk(&thunks[%d], r, %s_%s, %s);\n" % (i, winclassname, method.name, thunk_params))
|
|
cfile.write(" for (i = 0; i < %d; i++)\n" % len(klass.methods))
|
|
cfile.write(" vtable[i] = &thunks[i];\n")
|
|
cfile.write(" r->linux_side = linux_side;\n")
|
|
cfile.write(" r->vtable = (void *)vtable;\n")
|
|
cfile.write(" return r;\n}\n\n")
|
|
cfile.write("void destroy_%s_FnTable(void *object)\n{\n" % winclassname)
|
|
cfile.write(" %s *win_object = object;\n" % winclassname)
|
|
cfile.write(" TRACE(\"%p\\n\", win_object);\n")
|
|
for classname_pattern, user_data_type, user_data_destructor in method_overrides_data:
|
|
if user_data_destructor and classname_pattern in klass.spelling:
|
|
cfile.write(" %s(&win_object->user_data);\n" % user_data_destructor)
|
|
break
|
|
cfile.write(" VirtualFree(win_object->vtable[0], 0, MEM_RELEASE);\n")
|
|
cfile.write(" HeapFree(GetProcessHeap(), 0, win_object->vtable);\n")
|
|
cfile.write(" HeapFree(GetProcessHeap(), 0, win_object);\n}\n\n")
|
|
|
|
cpp.write("#ifdef __cplusplus\n}\n#endif\n")
|
|
cpp_h.write("#ifdef __cplusplus\n}\n#endif\n")
|
|
|
|
constructors = open("vrclient_x64/win_constructors.h", "a")
|
|
constructors.write("extern void *create_%s(void *);\n" % winclassname)
|
|
constructors.write("extern void *create_%s_FnTable(void *);\n" % winclassname)
|
|
|
|
destructors = open("vrclient_x64/win_destructors.h", "a")
|
|
destructors.write("extern void destroy_%s(void *);\n" % winclassname)
|
|
destructors.write("extern void destroy_%s_FnTable(void *);\n" % winclassname)
|
|
|
|
constructors = open("vrclient_x64/win_constructors_table.dat", "a")
|
|
constructors.write(f" {{\"{klass.version}\", &create_{winclassname}, &destroy_{winclassname}}},\n")
|
|
constructors.write(f" {{\"FnTable:{klass.version}\", &create_{winclassname}_FnTable, &destroy_{winclassname}_FnTable}},\n")
|
|
|
|
generate_c_api_thunk_tests(winclassname, klass.methods)
|
|
|
|
|
|
def canonical_typename(cursor):
|
|
if type(cursor) is Cursor:
|
|
return canonical_typename(cursor.type)
|
|
|
|
name = cursor.get_canonical().spelling
|
|
return name.removeprefix("const ")
|
|
|
|
|
|
def find_struct_abis(name):
|
|
records = all_records[sdkver]
|
|
missing = [name not in records[abi] for abi in ABIS]
|
|
assert all(missing) or not any(missing)
|
|
if any(missing): return None
|
|
return {abi: records[abi][name].type for abi in ABIS}
|
|
|
|
|
|
def struct_needs_conversion_nocache(struct):
|
|
name = canonical_typename(struct)
|
|
|
|
abis = find_struct_abis(name)
|
|
if abis is None:
|
|
return False, False
|
|
|
|
names = {a: [f.spelling for f in abis[a].get_fields()]
|
|
for a in ABIS}
|
|
assert names['u32'] == names['u64']
|
|
assert names['u32'] == names['w32']
|
|
assert names['u32'] == names['w64']
|
|
|
|
offsets = {a: {f: abis[a].get_offset(f) for f in names[a]}
|
|
for a in ABIS}
|
|
if offsets['u32'] != offsets['w32']:
|
|
return True, False
|
|
if offsets['u64'] != offsets['w64']:
|
|
return True, False
|
|
|
|
types = {a: [f.type.get_canonical() for f in abis[a].get_fields()]
|
|
for a in ABIS}
|
|
if any(t.kind == TypeKind.RECORD and struct_needs_conversion(t)
|
|
for t in types['u32']):
|
|
return True, False
|
|
if any(t.kind == TypeKind.RECORD and struct_needs_conversion(t)
|
|
for t in types['u64']):
|
|
return True, False
|
|
|
|
assert abis['u32'].get_size() <= abis['w32'].get_size()
|
|
if abis['u32'].get_size() < abis['w32'].get_size():
|
|
return False, True
|
|
|
|
assert abis['u64'].get_size() <= abis['w64'].get_size()
|
|
if abis['u64'].get_size() < abis['w64'].get_size():
|
|
return False, True
|
|
|
|
return False, False
|
|
|
|
|
|
def struct_needs_conversion(struct):
|
|
name = canonical_typename(struct)
|
|
|
|
if not sdkver in struct_conversion_cache:
|
|
struct_conversion_cache[sdkver] = {}
|
|
struct_needs_size_adjustment_cache[sdkver] = {}
|
|
|
|
if not name in struct_conversion_cache[sdkver]:
|
|
struct_conversion_cache[sdkver][name], \
|
|
struct_needs_size_adjustment_cache[sdkver][name] = \
|
|
struct_needs_conversion_nocache(struct)
|
|
|
|
return struct_conversion_cache[sdkver][name]
|
|
|
|
def struct_needs_size_adjustment(struct):
|
|
name = canonical_typename(struct)
|
|
return not struct_needs_conversion(struct) and struct_needs_size_adjustment_cache[sdkver][name]
|
|
|
|
def get_field_attribute_str(field):
|
|
ftype = field.type.get_canonical()
|
|
if ftype.kind != TypeKind.RECORD:
|
|
return ""
|
|
name = canonical_typename(ftype)
|
|
abis = find_struct_abis(name)
|
|
align = abis['w32'].get_align()
|
|
return " __attribute__((aligned(" + str(align) + ")))"
|
|
|
|
generated_struct_handlers = []
|
|
cpp_files_need_close_brace = []
|
|
|
|
LIN_TO_WIN=1
|
|
WIN_TO_LIN=2
|
|
WRAPPERS=3
|
|
|
|
#because of struct packing differences between win32 and linux, we
|
|
#need to convert these structs from their linux layout to the win32
|
|
#layout.
|
|
def handle_struct(sdkver, struct):
|
|
handler_name = "%s_%s" % (struct.displayname, display_sdkver(sdkver))
|
|
|
|
if handler_name in generated_struct_handlers:
|
|
# we already have a handler for the struct struct of this size
|
|
return
|
|
|
|
which = set()
|
|
|
|
if struct_needs_conversion(struct.type.get_canonical()):
|
|
which.add(LIN_TO_WIN)
|
|
which.add(WIN_TO_LIN)
|
|
|
|
if strip_ns(struct.displayname) in SDK_STRUCTS:
|
|
which.add(WRAPPERS)
|
|
|
|
if len(which) == 0:
|
|
return
|
|
|
|
filename_base = "struct_converters_%s" % display_sdkver(sdkver)
|
|
cppname = "vrclient_x64/%s.cpp" % filename_base
|
|
file_exists = os.path.isfile(cppname)
|
|
cppfile = open(cppname, "a")
|
|
if not file_exists:
|
|
cppfile.write("#include <stdlib.h>\n");
|
|
cppfile.write("#include <string.h>\n");
|
|
cppfile.write("#include \"vrclient_private.h\"\n")
|
|
cppfile.write("#include \"vrclient_defs.h\"\n")
|
|
cppfile.write("#include \"openvr_%s/openvr.h\"\n" % sdkver)
|
|
cppfile.write("using namespace vr;\n")
|
|
cppfile.write("extern \"C\" {\n")
|
|
cppfile.write("#include \"struct_converters.h\"\n")
|
|
cpp_files_need_close_brace.append(cppname)
|
|
|
|
hfile = open("vrclient_x64/struct_converters.h", "a")
|
|
|
|
hfile.write("typedef struct win%s win%s;\n" % (handler_name, handler_name))
|
|
|
|
cppfile.write("#pragma pack(push, 8)\n")
|
|
cppfile.write("struct win%s {\n" % handler_name)
|
|
for m in struct.get_children():
|
|
if m.kind == CursorKind.FIELD_DECL:
|
|
if m.type.get_canonical().kind == TypeKind.CONSTANTARRAY:
|
|
cppfile.write(" %s %s[%u]" % (m.type.element_type.spelling, m.displayname, m.type.element_count))
|
|
elif m.type.get_canonical().kind == TypeKind.RECORD and \
|
|
struct_needs_conversion(m.type.get_canonical()):
|
|
cppfile.write(" win%s_%s %s" % (strip_ns(m.type.spelling), display_sdkver(sdkver), m.displayname))
|
|
else:
|
|
if m.type.get_canonical().kind == TypeKind.POINTER and \
|
|
m.type.get_pointee().kind == TypeKind.FUNCTIONPROTO:
|
|
cppfile.write(" void *%s /*fn pointer*/ " % m.displayname)
|
|
else:
|
|
cppfile.write(" %s %s" % (m.type.spelling, m.displayname))
|
|
cppfile.write(get_field_attribute_str(m) + ";\n")
|
|
if WRAPPERS in which:
|
|
cppfile.write("\n %s *linux_side;\n" % struct.displayname)
|
|
cppfile.write("} __attribute__ ((ms_struct));\n")
|
|
cppfile.write("#pragma pack(pop)\n\n")
|
|
|
|
def dump_converter(src, dst, size):
|
|
for m in struct.get_children():
|
|
if m.kind == CursorKind.FIELD_DECL:
|
|
if m.type.get_canonical().kind == TypeKind.CONSTANTARRAY:
|
|
#TODO: if this is a struct, or packed differently, we'll have to
|
|
# copy each element in a for-loop
|
|
cppfile.write(" memcpy(" + dst + "->" + m.displayname + ", " + src + "->" + m.displayname + ", sizeof(" + dst + "->" + m.displayname + "));\n")
|
|
elif m.type.get_canonical().kind == TypeKind.RECORD and \
|
|
struct_needs_conversion(m.type.get_canonical()):
|
|
cppfile.write(" struct_" + strip_ns(m.type.spelling) + "_" + display_sdkver(sdkver) + "_" + src + "_to_" + dst + \
|
|
"(&" + src + "->" + m.displayname + ", &" + dst + "->" + m.displayname + ");\n")
|
|
elif struct.displayname in STRUCTS_SIZE_FIELD and \
|
|
m.displayname in STRUCTS_SIZE_FIELD[struct.displayname]:
|
|
cppfile.write(" " + dst + "->" + m.displayname + " = sizeof(*" + dst + ");\n")
|
|
elif size and strip_ns(m.type.get_canonical().spelling) == "VREvent_Data_t":
|
|
#truncate variable-length data struct at the end of the parent struct
|
|
#XXX: dumb hard-coding. are the other types with lengths variable length?
|
|
cppfile.write(" memcpy(&" + dst + "->data, &" + src + "->data, " + size + " - (((char*)&" + dst + "->data) - ((char*)" + dst + ")));\n")
|
|
else:
|
|
cppfile.write(" " + dst + "->" + m.displayname + " = " + src + "->" + m.displayname + ";\n")
|
|
|
|
if strip_ns(struct.displayname) in STRUCTS_NEXT_IS_SIZE:
|
|
size_arg = "sz"
|
|
size_arg_type = ", uint32_t sz"
|
|
else:
|
|
size_arg = None
|
|
size_arg_type = ""
|
|
|
|
if LIN_TO_WIN in which:
|
|
hfile.write("extern void struct_%s_lin_to_win(void *l, void *w%s);\n" % (handler_name, size_arg_type))
|
|
cppfile.write("void struct_%s_lin_to_win(void *l, void *w%s)\n{\n" % (handler_name, size_arg_type))
|
|
cppfile.write(" struct win%s *win = (struct win%s *)w;\n" % (handler_name, handler_name))
|
|
cppfile.write(" %s *lin = (%s *)l;\n" % (struct.displayname, struct.displayname))
|
|
dump_converter("lin", "win", size_arg)
|
|
cppfile.write("}\n\n")
|
|
|
|
if WIN_TO_LIN in which:
|
|
#XXX: should pass size param here, too
|
|
hfile.write("extern void struct_%s_win_to_lin(const void *w, void *l);\n" % handler_name)
|
|
cppfile.write("void struct_%s_win_to_lin(const void *w, void *l)\n{\n" % handler_name)
|
|
cppfile.write(" struct win%s *win = (struct win%s *)w;\n" % (handler_name, handler_name))
|
|
cppfile.write(" %s *lin = (%s *)l;\n" % (struct.displayname, struct.displayname))
|
|
dump_converter("win", "lin", None)
|
|
cppfile.write("}\n\n")
|
|
|
|
if WRAPPERS in which:
|
|
hfile.write("extern struct win%s *struct_%s_wrap(void *l);\n" % (handler_name, handler_name))
|
|
|
|
cppfile.write("struct win%s *struct_%s_wrap(void *l)\n{\n" % (handler_name, handler_name))
|
|
cppfile.write(" struct win%s *win = (struct win%s *)malloc(sizeof(*win));\n" % (handler_name, handler_name))
|
|
cppfile.write(" %s *lin = (%s *)l;\n" % (struct.displayname, struct.displayname))
|
|
|
|
dump_converter("lin", "win", None)
|
|
|
|
cppfile.write(" win->linux_side = lin;\n");
|
|
cppfile.write(" return win;\n")
|
|
|
|
cppfile.write("}\n\n")
|
|
|
|
hfile.write("extern %s *struct_%s_unwrap(win%s *w);\n" % (struct.displayname, handler_name, handler_name))
|
|
|
|
cppfile.write("struct %s *struct_%s_unwrap(win%s *w)\n{\n" % (struct.displayname, handler_name, handler_name))
|
|
cppfile.write(" %s *ret = w->linux_side;\n" % struct.displayname)
|
|
cppfile.write(" free(w);\n")
|
|
cppfile.write(" return ret;\n")
|
|
cppfile.write("}\n\n")
|
|
|
|
generated_struct_handlers.append(handler_name)
|
|
|
|
|
|
def generate_x64_call_flat_method(cfile, param_count, has_floats, is_4th_float):
|
|
assert param_count >= 4
|
|
if is_4th_float:
|
|
assert has_floats
|
|
def l(line):
|
|
cfile.write(line + '\n')
|
|
|
|
stack_space = 0x20 # shadow register space
|
|
stack_space += 0x8 * (param_count - 3)
|
|
stack_space |= 0x8
|
|
src_offset = 0x20 + 0x8 + stack_space # shadow register space + ret
|
|
dst_offset = 0x20
|
|
|
|
name = "call_flat_method%s%s%s" % (param_count, "_f" if has_floats else "", "_f" if is_4th_float else "")
|
|
|
|
l(r"__ASM_GLOBAL_FUNC(%s," % name)
|
|
l(r' "subq $0x%x, %%rsp\n\t"' % stack_space);
|
|
l(r' __ASM_CFI(".cfi_adjust_cfa_offset %d\n\t")' % stack_space)
|
|
|
|
if is_4th_float:
|
|
l(r' "movq %%xmm3, 0x%x(%%rsp)\n\t"' % dst_offset)
|
|
else:
|
|
l(r' "movq %%r9, 0x%x(%%rsp)\n\t"' % dst_offset)
|
|
dst_offset += 8
|
|
|
|
for i in range(5, param_count + 1):
|
|
l(r' "movq 0x%x(%%rsp), %%rax\n\t" // copy parameter' % src_offset)
|
|
l(r' "movq %%rax, 0x%x(%%rsp)\n\t"' % dst_offset)
|
|
src_offset += 8
|
|
dst_offset += 8
|
|
|
|
l(r' "movq %r8, %r9\n\t" // shift over arguments')
|
|
l(r' "movq %rdx, %r8\n\t"')
|
|
l(r' "movq %rcx, %rdx\n\t"')
|
|
l(r' "movq %r10, %rcx\n\t" // add This pointer')
|
|
|
|
if has_floats:
|
|
l(r' "movaps %xmm2, %xmm3\n\t"')
|
|
l(r' "movaps %xmm1, %xmm2\n\t"')
|
|
l(r' "movaps %xmm0, %xmm1\n\t"')
|
|
|
|
l(r' "call *%r11\n\t"')
|
|
l(r' "addq $0x%x, %%rsp\n\t"' % stack_space);
|
|
l(r' __ASM_CFI(".cfi_adjust_cfa_offset -%d\n\t")' % stack_space)
|
|
l(r' "ret");')
|
|
l(r'extern void %s(void);' % name);
|
|
|
|
def generate_flatapi_c():
|
|
with open("vrclient_x64/flatapi.c", "w") as f:
|
|
f.write(r"""/* This file is auto-generated, do not edit. */
|
|
|
|
#include <stdarg.h>
|
|
|
|
#include "windef.h"
|
|
#include "winbase.h"
|
|
|
|
#include "cxx.h"
|
|
#include "flatapi.h"
|
|
|
|
#ifdef __i386__
|
|
__ASM_GLOBAL_FUNC(call_flat_method,
|
|
"popl %eax\n\t"
|
|
"pushl %ecx\n\t"
|
|
"pushl %eax\n\t"
|
|
"jmp *%edx");
|
|
#else
|
|
// handles "this" and up to 3 parameters
|
|
__ASM_GLOBAL_FUNC(call_flat_method,
|
|
"movq %r8, %r9\n\t" // shift over arguments
|
|
"movq %rdx, %r8\n\t"
|
|
"movq %rcx, %rdx\n\t"
|
|
"movq %r10, %rcx\n\t" // add This pointer
|
|
"jmp *%r11");
|
|
extern void call_flat_method(void);
|
|
|
|
__ASM_GLOBAL_FUNC(call_flat_method_f,
|
|
"movq %r8, %r9\n\t" // shift over arguments
|
|
"movq %rdx, %r8\n\t"
|
|
"movq %rcx, %rdx\n\t"
|
|
"movq %r10, %rcx\n\t" // add This pointer
|
|
"movaps %xmm2, %xmm3\n\t"
|
|
"movaps %xmm1, %xmm2\n\t"
|
|
"movaps %xmm0, %xmm1\n\t"
|
|
"jmp *%r11");
|
|
extern void call_flat_method_f(void);
|
|
""")
|
|
|
|
for i in range(4, max_c_api_param_count + 1):
|
|
f.write("\n")
|
|
generate_x64_call_flat_method(f, i, False, False)
|
|
for i in range(4, max_c_api_param_count + 1):
|
|
f.write("\n")
|
|
generate_x64_call_flat_method(f, i, True, False)
|
|
f.write("\n")
|
|
generate_x64_call_flat_method(f, i, True, True)
|
|
|
|
f.write('\npfn_call_flat_method\n')
|
|
f.write('get_call_flat_method_pfn( int param_count, BOOL has_floats, BOOL is_4th_float )\n{\n')
|
|
f.write(' if (!has_floats)\n')
|
|
f.write(' {\n')
|
|
f.write(' if (param_count <= 3) return call_flat_method;\n')
|
|
for i in range(4, max_c_api_param_count):
|
|
f.write(' if (param_count == %s) return call_flat_method%s;\n' % (i, i))
|
|
f.write(' return call_flat_method%s;\n' % max_c_api_param_count)
|
|
f.write(' }\n')
|
|
f.write(' if (is_4th_float)\n')
|
|
f.write(' {\n')
|
|
f.write(' if (param_count <= 3) return call_flat_method_f;\n')
|
|
for i in range(4, max_c_api_param_count):
|
|
f.write(' if (param_count == %s) return call_flat_method%s_f_f;\n' % (i, i))
|
|
f.write(' return call_flat_method%s_f_f;\n' % max_c_api_param_count)
|
|
f.write(' }\n')
|
|
f.write(' if (param_count <= 3) return call_flat_method_f;\n')
|
|
for i in range(4, max_c_api_param_count):
|
|
f.write(' if (param_count == %s) return call_flat_method%s_f;\n' % (i, i))
|
|
f.write(' return call_flat_method%s_f;\n' % max_c_api_param_count)
|
|
f.write('}\n')
|
|
|
|
f.write("#endif\n")
|
|
|
|
def generate_c_api_method_test(f, header, thunks_c, class_name, method):
|
|
thunk_params = get_capi_thunk_params(method)
|
|
f.write("\n init_thunk(t, this_ptr_value, %s_%s, %s);\n" % (class_name, method.name, thunk_params))
|
|
f.write(" ")
|
|
header.write("\n")
|
|
thunks_c.write("\n")
|
|
|
|
returns_record = method.result_type.get_canonical().kind == TypeKind.RECORD
|
|
if returns_record:
|
|
f.write("%s *" % strip_ns(method.result_type.spelling))
|
|
header.write("%s *" % strip_ns(method.result_type.spelling))
|
|
thunks_c.write("%s *" % strip_ns(method.result_type.spelling))
|
|
else:
|
|
f.write("%s " % strip_ns(method.result_type.spelling))
|
|
header.write("%s " % strip_ns(method.result_type.spelling))
|
|
thunks_c.write("%s " % strip_ns(method.result_type.spelling))
|
|
first_param = True
|
|
f.write('(__stdcall *capi_%s_%s)(' % (class_name, method.name))
|
|
header.write('__thiscall %s_%s(void *_this' % (class_name, method.name))
|
|
thunks_c.write('__thiscall %s_%s(void *_this' % (class_name, method.name))
|
|
if returns_record:
|
|
f.write("%s *_r" % strip_ns(method.result_type.spelling))
|
|
first_param = False
|
|
header.write(", %s *_r" % strip_ns(method.result_type.spelling))
|
|
thunks_c.write(", %s *_r" % strip_ns(method.result_type.spelling))
|
|
|
|
for param in method.get_arguments():
|
|
if param.type.kind == TypeKind.POINTER \
|
|
and param.type.get_pointee().kind == TypeKind.UNEXPOSED:
|
|
typename = "void *"
|
|
else:
|
|
typename = param.type.spelling.split("::")[-1].replace("&", "*");
|
|
if not first_param:
|
|
f.write(", ")
|
|
first_param = False
|
|
f.write("%s %s" % (typename, param.spelling))
|
|
header.write(", %s %s" % (typename, param.spelling))
|
|
thunks_c.write(", %s %s" % (typename, param.spelling))
|
|
f.write(") = (void *)t;\n")
|
|
header.write(");\n")
|
|
thunks_c.write(")\n{\n")
|
|
|
|
def get_param_typename(param):
|
|
param_size = param.type.get_size()
|
|
if param.type.kind == TypeKind.POINTER \
|
|
or param.type.spelling.endswith("&") \
|
|
or param.type.spelling == "vr::glSharedTextureHandle_t":
|
|
return "ptr"
|
|
elif param.type.spelling == "bool":
|
|
return "bool"
|
|
elif param.type.spelling == "float":
|
|
return "float"
|
|
elif param.type.spelling == "vr::HmdRect2_t":
|
|
return "HmdRect2"
|
|
elif param.type.spelling == "vr::HmdVector2_t":
|
|
return "HmdVector2"
|
|
elif param.type.spelling == "vr::HmdVector3_t":
|
|
return "HmdVector3"
|
|
elif param.type.spelling == "vr::HmdColor_t":
|
|
return "HmdColor"
|
|
elif param_size == 8:
|
|
return "uint64"
|
|
elif param_size == 4 or param_size == 2:
|
|
return "uint32"
|
|
else:
|
|
return "unknown"
|
|
|
|
thunks_c.write(" push_ptr_parameter(_this);\n")
|
|
if returns_record:
|
|
thunks_c.write(" push_ptr_parameter(_r);\n")
|
|
for param in method.get_arguments():
|
|
typename = get_param_typename(param)
|
|
thunks_c.write(" push_%s_parameter(%s);\n" % (typename, param.spelling))
|
|
if method.result_type.kind != TypeKind.VOID:
|
|
thunks_c.write(" return 0;\n")
|
|
thunks_c.write("}\n")
|
|
|
|
parameter_checks = []
|
|
def add_parameter_check(typename, value):
|
|
parameter_checks.append("check_%s_parameter(\"%s_%s\", %s)" % (typename, class_name, method.name, value))
|
|
add_parameter_check("ptr", "this_ptr_value")
|
|
f.write("\n")
|
|
f.write(" clear_parameters();\n")
|
|
f.write(" capi_%s_%s(" % (class_name, method.name))
|
|
first_param = True
|
|
if returns_record:
|
|
f.write("data_ptr_value")
|
|
first_param = False
|
|
add_parameter_check("ptr", "data_ptr_value")
|
|
for i, param in enumerate(method.get_arguments()):
|
|
i += 1
|
|
typename = get_param_typename(param)
|
|
if typename == "ptr":
|
|
v = "(void *)%s" % i
|
|
elif typename == "bool":
|
|
v = "1"
|
|
elif typename == "float":
|
|
v = "%s.0f" % i
|
|
elif typename == "HmdRect2":
|
|
v = "DEFAULT_RECT"
|
|
elif typename == "HmdVector2":
|
|
v = "DEFAULT_VECTOR2"
|
|
elif typename == "HmdVector3":
|
|
v = "DEFAULT_VECTOR3"
|
|
elif typename == "HmdColor":
|
|
v = "DEFAULT_COLOR"
|
|
else:
|
|
v = str(i)
|
|
if not first_param:
|
|
f.write(", ")
|
|
first_param = False
|
|
f.write(v)
|
|
add_parameter_check(typename, v)
|
|
f.write(");\n")
|
|
for c in parameter_checks:
|
|
f.write(" %s;\n" % c)
|
|
|
|
def generate_c_api_thunk_tests(winclassname, methods):
|
|
class_name = re.sub(r'^win[A-Za-z]+_', '', winclassname)
|
|
|
|
filename = "tests/capi_thunks_autogen.h"
|
|
file_exists = os.path.isfile(filename)
|
|
header = open(filename, "a")
|
|
if not file_exists:
|
|
header.write("""/* This file is auto-generated, do not edit. */
|
|
#include <stdarg.h>
|
|
#include <stdint.h>
|
|
|
|
#include "windef.h"
|
|
#include "winbase.h"
|
|
|
|
#include "cxx.h"
|
|
#include "flatapi.h"
|
|
#include "vrclient_defs.h"
|
|
|
|
#include "capi_thunks.h"
|
|
""")
|
|
header.write("\nvoid test_capi_thunks_%s(void);\n" % class_name)
|
|
|
|
filename = "tests/capi_thunks_autogen.c"
|
|
file_exists = os.path.isfile(filename)
|
|
thunks_c = open(filename, "a")
|
|
if not file_exists:
|
|
thunks_c.write("""/* This file is auto-generated, do not edit. */
|
|
#include "capi_thunks_autogen.h"
|
|
""")
|
|
|
|
filename = "tests/capi_thunks_tests_autogen.c"
|
|
file_exists = os.path.isfile(filename)
|
|
with open(filename, "a") as f:
|
|
if not file_exists:
|
|
f.write("""/* This file is auto-generated, do not edit. */
|
|
#include "capi_thunks_autogen.h"
|
|
""")
|
|
f.write("\nvoid test_capi_thunks_%s(void)\n{\n" % class_name)
|
|
f.write(" struct thunk *t = alloc_thunks(1);\n");
|
|
for method in methods:
|
|
generate_c_api_method_test(f, header, thunks_c, class_name, method)
|
|
f.write(" VirtualFree(t, 0, MEM_RELEASE);\n")
|
|
f.write("}\n")
|
|
|
|
filename = "tests/main_autogen.c"
|
|
file_exists = os.path.isfile(filename)
|
|
with open(filename, "a") as f:
|
|
if not file_exists:
|
|
f.write("""/* This file is auto-generated, do not edit. */
|
|
#include "capi_thunks_autogen.h"
|
|
|
|
#include <stdio.h>
|
|
|
|
int main(void)
|
|
{
|
|
""")
|
|
f.write(" test_capi_thunks_%s();\n" % class_name)
|
|
|
|
|
|
def enumerate_structs(cursor, vr_only=False):
|
|
for child in cursor.get_children():
|
|
if child.kind == CursorKind.NAMESPACE \
|
|
and child.displayname == "vr":
|
|
yield from child.get_children()
|
|
elif not vr_only:
|
|
yield child
|
|
|
|
|
|
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):
|
|
prog = re.compile("^.*const\s*char.* \*?(\w*)_Version.*\"(.*)\"")
|
|
sdkdir = f'openvr_{sdkver}'
|
|
|
|
sources = {}
|
|
versions = {}
|
|
has_vrclientcore = False
|
|
for file in os.listdir(sdkdir):
|
|
x = open(f"{sdkdir}/{file}", "r")
|
|
if file == "ivrclientcore.h":
|
|
has_vrclientcore = True
|
|
|
|
for l in x:
|
|
if "_Version" in l:
|
|
result = prog.match(l)
|
|
if result:
|
|
iface, version = result.group(1, 2)
|
|
versions[iface] = version
|
|
|
|
if not has_vrclientcore:
|
|
source = [f'#include "{sdkdir}/openvr.h"']
|
|
else:
|
|
source = [f'#include "{sdkdir}/{file}"'
|
|
for file in SDK_SOURCES.keys()]
|
|
sources["source.cpp"] = "\n".join(source)
|
|
|
|
return versions, sources
|
|
|
|
|
|
def generate(sdkver, structs):
|
|
print(f'generating SDK version {sdkver}...')
|
|
for child in structs['u32'].values():
|
|
handle_struct(sdkver, child)
|
|
|
|
|
|
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(u'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] = {}
|
|
if sdkver not in all_structs: all_structs[sdkver] = {}
|
|
|
|
versions = all_versions[sdkver]
|
|
|
|
records = enumerate_structs(build.cursor)
|
|
# reverse the order to favor definitions over declarations
|
|
records = dict(reversed([(c.type.spelling, c) for c in records]))
|
|
|
|
classes = enumerate_structs(build.cursor, vr_only=True)
|
|
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 in versions, classes)
|
|
classes = [Class(sdkver, abi, c) for c in classes]
|
|
classes = {c.version: c for c in classes}
|
|
|
|
structs = enumerate_structs(build.cursor, vr_only=True)
|
|
struct_kinds = (CursorKind.STRUCT_DECL, CursorKind.CLASS_DECL)
|
|
structs = filter(lambda c: c.kind in struct_kinds, structs)
|
|
structs = {c.spelling: c for c in structs}
|
|
|
|
all_records[sdkver][abi] = records
|
|
tmp_classes[sdkver][abi] = classes
|
|
all_structs[sdkver][abi] = structs
|
|
|
|
for i, sdkver in enumerate(reversed(SDK_VERSIONS)):
|
|
all_classes.update(tmp_classes[sdkver]['u32'])
|
|
|
|
print('parsing SDKs... 100%')
|
|
|
|
|
|
for _, klass in sorted(all_classes.items()):
|
|
sdkver = klass._sdkver
|
|
handle_class(klass)
|
|
|
|
|
|
for sdkver in SDK_VERSIONS:
|
|
generate(sdkver, all_structs[sdkver])
|
|
|
|
|
|
for f in cpp_files_need_close_brace:
|
|
m = open(f, "a")
|
|
m.write("\n}\n")
|
|
|
|
with open("tests/main_autogen.c", "a") as f:
|
|
f.write(" printf(\"All tests executed.\\n\");\n")
|
|
f.write("}\n")
|
|
|
|
generate_flatapi_c()
|