#!/usr/bin/env python3

#NOTE: If you make modifications here, consider whether they should
#be duplicated in ../vrclient/gen_wrapper.py

from __future__ import print_function

CLANG_PATH='/usr/lib/clang/13.0.1'

from clang.cindex import CursorKind, Index, Type, TypeKind
from collections import namedtuple
import pprint
import sys
import os
import re
import math

sdk_versions = [
    "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",
]

files = [
    ("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"
    ]),
]

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"],
    "STEAMAPPS_INTERFACE_VERSION001":["SteamApps001"],
    "SteamNetworkingSockets002":["SteamNetworkingSockets003"],
}

# these structs are manually confirmed to be equivalent
exempt_structs = [
        "CSteamID",
        "CGameID",
        "CCallbackBase",
        "SteamPS3Params_t",
        "ValvePackingSentinel_t"
]

# we have converters for these written by hand because they're too complicated to generate
manually_handled_structs = [
        "SteamNetworkingMessage_t"
]

Method = namedtuple('Method', ['name', 'version_func'], defaults=[lambda _: True])

manually_handled_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)
        "cppISteamNetworkingSockets_SteamNetworkingSockets": [
            Method("ReceiveMessagesOnConnection"),
            Method("ReceiveMessagesOnListenSocket"),
            Method("ReceiveMessagesOnPollGroup"),
            Method("SendMessages"),
            Method("CreateFakeUDPPort"),
        ],
        "cppISteamNetworkingUtils_SteamNetworkingUtils": [
            Method("AllocateMessage"),
            Method("SetConfigValue", lambda version: version >= 3)
        ],
        "cppISteamNetworkingMessages_SteamNetworkingMessages": [
            Method("ReceiveMessagesOnChannel"),
        ],
        "cppISteamInput_SteamInput": [
            Method("EnableActionEventCallbacks"),
            Method("GetGlyphForActionOrigin"),
            Method("GetGlyphPNGForActionOrigin"),
            Method("GetGlyphSVGForActionOrigin"),
            Method("GetGlyphForActionOrigin_Legacy"),
            Method("GetGlyphForXboxOrigin"),
        ],
        "cppISteamController_SteamController": [
            Method("GetGlyphForActionOrigin"),
            Method("GetGlyphForXboxOrigin"),
        ],
        "cppISteamNetworkingFakeUDPPort_SteamNetworkingFakeUDPPort": [
            Method("DestroyFakeUDPPort"),
            Method("ReceiveMessages"),
        ],
        "cppISteamUser_SteamUser": [
            #TODO: Do we need the the value -> pointer conversion for other versions of the interface?
            Method("InitiateGameConnection", lambda version: version == 8),
        ],
}

INTERFACE_NAME_VERSION = re.compile(r'^(?P<name>.+?)(?P<version>\d*)$')

def method_needs_manual_handling(interface_with_version, method_name):
    match_dict = INTERFACE_NAME_VERSION.match(interface_with_version).groupdict()
    interface = match_dict['name']
    version = int(match_dict['version']) if match_dict['version'] else None

    method_list = manually_handled_methods.get(interface, [])
    method = next(filter(lambda m: m.name == method_name, method_list), None)

    return method and method.version_func(version)

# manual converters for simple types (function pointers)
manual_type_converters = [
        "FSteamNetworkingSocketsDebugOutput",
        "SteamAPIWarningMessageHook_t",
        "SteamAPI_CheckCallbackRegistered_t"
]

# manual converters for specific parameters
manual_param_converters = [
        "nNativeKeyCode"
]

#struct_conversion_cache = {
#    '142': {
#                'SteamUGCDetails_t': True,
#                'SteamUGCQueryCompleted_t': False
#           }
#}
struct_conversion_cache = {}

converted_structs = []

# callback classes for which we have a linux wrapper
wrapped_classes = [
        "ISteamMatchmakingServerListResponse",
        "ISteamMatchmakingPingResponse",
        "ISteamMatchmakingPlayersResponse",
        "ISteamMatchmakingRulesResponse",
        "ISteamNetworkingFakeUDPPort",
]

print_sizes = []

class_versions = {}

path_conversions = [
        {
            "parent_name": "GetAppInstallDir",
            "l2w_names": ["pchDirectory"],
            "l2w_lens": ["cchNameMax"],
            "l2w_urls": [False],
            "w2l_names": [],
            "w2l_arrays": [],
            "w2l_urls": [],
            "return_is_size": True
        },
        {
            "parent_name": "GetAppInstallDir",
            "l2w_names": ["pchFolder"],
            "l2w_lens": ["cchFolderBufferSize"],
            "l2w_urls": [False],
            "w2l_names": [],
            "w2l_arrays": [],
            "w2l_urls": [],
            "return_is_size": True
        },
        {
            "parent_name": "GetFileDetails",
            "l2w_names": [],
            "l2w_lens": [],
            "l2w_urls": [],
            "w2l_names": ["pszFileName"],
            "w2l_arrays": [False],
            "w2l_urls": [False],
            "return_is_size": True
        },
        ### ISteamGameServer::SetModDir - "Just the folder name, not the whole path. I.e. "Spacewar"."
        {
            "parent_name": "LoadURL",
            "l2w_names": [],
            "l2w_lens": [],
            "l2w_urls": [],
            "w2l_names": ["pchURL"],
            "w2l_arrays": [False],
            "w2l_urls": [True],
            "return_is_size": False
        },
        {
            "parent_name": "FileLoadDialogResponse",
            "l2w_names": [],
            "l2w_lens": [],
            "l2w_urls": [],
            "w2l_names": ["pchSelectedFiles"],
            "w2l_arrays": [True],
            "w2l_urls": [False],
            "return_is_size": False
        },
        {
            "parent_name": "HTML_StartRequest_t",
            "l2w_names": ["pchURL"],
            "l2w_lens": [None],
            "l2w_urls": [True],
            "w2l_names": [],
            "w2l_arrays": [],
            "w2l_urls": [],
            "return_is_size": False
        },
        {
            "parent_name": "HTML_URLChanged_t",
            "l2w_names": ["pchURL"],
            "l2w_lens": [None],
            "l2w_urls": [True],
            "w2l_names": [],
            "w2l_arrays": [],
            "w2l_urls": [],
            "return_is_size": False
        },
        {
            "parent_name": "HTML_FinishedRequest_t",
            "l2w_names": ["pchURL"],
            "l2w_lens": [None],
            "l2w_urls": [True],
            "w2l_names": [],
            "w2l_arrays": [],
            "w2l_urls": [],
            "return_is_size": False
        },
        {
            "parent_name": "HTML_OpenLinkInNewTab_t",
            "l2w_names": ["pchURL"],
            "l2w_lens": [None],
            "l2w_urls": [True],
            "w2l_names": [],
            "w2l_arrays": [],
            "w2l_urls": [],
            "return_is_size": False
        },
        {
            "parent_name": "HTML_LinkAtPosition_t",
            "l2w_names": ["pchURL"],
            "l2w_lens": [None],
            "l2w_urls": [True],
            "w2l_names": [],
            "w2l_arrays": [],
            "w2l_urls": [],
            "return_is_size": False
        },
        {
            "parent_name": "HTML_FileOpenDialog_t",
            "l2w_names": ["pchInitialFile"],
            "l2w_lens": [None],
            "l2w_urls": [True],
            "w2l_names": [],
            "w2l_arrays": [],
            "w2l_urls": [],
            "return_is_size": False
        },
        {
            "parent_name": "HTML_NewWindow_t",
            "l2w_names": ["pchURL"],
            "l2w_lens": [None],
            "l2w_urls": [True],
            "w2l_names": [],
            "w2l_arrays": [],
            "w2l_urls": [],
            "return_is_size": False
        },
        {
            "parent_name": "PublishWorkshopFile",
            "l2w_names": [],
            "l2w_lens": [],
            "l2w_urls": [],
            "w2l_names": ["pchFile", "pchPreviewFile"],
            "w2l_arrays": [False, False],
            "w2l_urls": [False, False],
            "return_is_size": False
        },
        {
            "parent_name": "UpdatePublishedFileFile",
            "l2w_names": [],
            "l2w_lens": [],
            "l2w_urls": [],
            "w2l_names": ["pchFile"],
            "w2l_arrays": [False],
            "w2l_urls": [False],
            "return_is_size": False
        },
        {
            "parent_name": "UpdatePublishedFilePreviewFile",
            "l2w_names": [],
            "l2w_lens": [],
            "l2w_urls": [],
            "w2l_names": ["pchPreviewFile"],
            "w2l_arrays": [False],
            "w2l_urls": [False],
            "return_is_size": False
        },
        {
            "parent_name": "PublishVideo",
            "l2w_names": [],
            "l2w_lens": [],
            "l2w_urls": [],
            "w2l_names": ["pchPreviewFile"],
            "w2l_arrays": [False],
            "w2l_urls": [False],
            "return_is_size": False
        },
        {
            "parent_name": "AddScreenshotToLibrary",
            "l2w_names": [],
            "l2w_lens": [],
            "l2w_urls": [],
            "w2l_names": ["pchFilename", "pchThumbnailFilename"],
            "w2l_arrays": [False, False],
            "w2l_urls": [False, False],
            "return_is_size": False
        },
        {
            "parent_name": "AddVRScreenshotToLibrary",
            "l2w_names": [],
            "l2w_lens": [],
            "l2w_urls": [],
            "w2l_names": ["pchFilename", "pchVRFilename"],
            "w2l_arrays": [False, False],
            "w2l_urls": [False, False],
            "return_is_size": False
        },
        {
            "parent_name": "UGCDownloadToLocation",
            "l2w_names": [],
            "l2w_lens": [],
            "l2w_urls": [],
            "w2l_names": ["pchLocation"],
            "w2l_arrays": [False],
            "w2l_urls": [False],
            "return_is_size": False
        },
        {
            "parent_name": "GetQueryUGCAdditionalPreview",
            "l2w_names": ["pchURLOrVideoID"],
            "l2w_lens": ["cchURLSize"],
            "l2w_urls": [True],
            "w2l_names": [],
            "w2l_arrays": [],
            "w2l_urls": [],
            "return_is_size": False
        },
        {
            "parent_name": "SetItemContent",
            "l2w_names": [],
            "l2w_lens": [],
            "l2w_urls": [],
            "w2l_names": ["pszContentFolder"],
            "w2l_arrays": [False],
            "w2l_urls": [False],
            "return_is_size": False
        },
        {
            "parent_name": "SetItemPreview",
            "l2w_names": [],
            "l2w_lens": [],
            "l2w_urls": [],
            "w2l_names": ["pszPreviewFile"],
            "w2l_arrays": [False],
            "w2l_urls": [False],
            "return_is_size": False
        },
        {
            "parent_name": "AddItemPreviewFile",
            "l2w_names": [],
            "l2w_lens": [],
            "l2w_urls": [],
            "w2l_names": ["pszPreviewFile"],
            "w2l_arrays": [False],
            "w2l_urls": [False],
            "return_is_size": False
        },
        {
            "parent_name": "UpdateItemPreviewFile",
            "l2w_names": [],
            "l2w_lens": [],
            "l2w_urls": [],
            "w2l_names": ["pszPreviewFile"],
            "w2l_arrays": [False],
            "w2l_urls": [False],
            "return_is_size": False
        },
        {
            "parent_name": "GetItemInstallInfo",
            "l2w_names": ["pchFolder"],
            "l2w_lens": ["cchFolderSize"],
            "l2w_urls": [False],
            "w2l_names": [],
            "w2l_arrays": [],
            "w2l_urls": [],
            "return_is_size": False
        },
        {
            "parent_name": "BInitWorkshopForGameServer",
            "l2w_names": [],
            "l2w_lens": [],
            "l2w_urls": [],
            "w2l_names": ["pszFolder"],
            "w2l_arrays": [False],
            "w2l_urls": [False],
            "return_is_size": False
        },
        {
            "parent_name": "GetUserDataFolder",
            "l2w_names": ["pchBuffer"],
            "l2w_lens": ["cubBuffer"],
            "l2w_urls": [False],
            "w2l_names": [],
            "w2l_arrays": [],
            "w2l_urls": [],
            "return_is_size": False
        },
        {
            "parent_name": "CheckFileSignature",
            "l2w_names": [],
            "l2w_lens": [],
            "l2w_urls": [],
            "w2l_names": ["szFileName"],
            "w2l_arrays": [False],
            "w2l_urls": [False],
            "return_is_size": False
        },
        {
            "parent_name": "Init",
            "l2w_names": [],
            "l2w_lens": [],
            "l2w_urls": [],
            "w2l_names": ["pchAbsolutePathToControllerConfigVDF"],
            "w2l_arrays": [False],
            "w2l_urls": [False],
            "return_is_size": False
        },
        {
            "parent_name": "SetInputActionManifestFilePath",
            "l2w_names": [],
            "l2w_lens": [],
            "l2w_urls": [],
            "w2l_names": ["pchInputActionManifestAbsolutePath"],
            "w2l_arrays": [False],
            "w2l_urls": [False],
            "return_is_size": False
        },
]

def strip_const(typename):
    return typename.replace("const ", "", 1)

windows_structs32 = {}
def find_windows_struct(struct):
    return windows_structs32.get(strip_const(struct.spelling), None)

windows_structs64 = {}
def find_windows64_struct(struct):
    return windows_structs64.get(strip_const(struct.spelling), None)

linux_structs64 = {}
def find_linux64_struct(struct):
    return linux_structs64.get(strip_const(struct.spelling), None)

def struct_needs_conversion_nocache(struct):
    if strip_const(struct.spelling) in exempt_structs:
        return False
    if strip_const(struct.spelling) in manually_handled_structs:
        return True

    #check 32-bit compat
    windows_struct = find_windows_struct(struct)
    if windows_struct is None:
        print("Couldn't find windows struct for " + struct.spelling)
    assert(not windows_struct is None) #must find windows_struct
    for field in struct.get_fields():
        if struct.get_offset(field.spelling) != windows_struct.get_offset(field.spelling):
            return True
        if field.type.kind == TypeKind.RECORD and \
                struct_needs_conversion(field.type):
            return True

    #check 64-bit compat
    windows_struct = find_windows64_struct(struct)
    assert(not windows_struct is None) #must find windows_struct
    lin64_struct = find_linux64_struct(struct)
    assert(not lin64_struct is None) #must find lin64_struct
    for field in lin64_struct.get_fields():
        if lin64_struct.get_offset(field.spelling) != windows_struct.get_offset(field.spelling):
            return True
        if field.type.kind == TypeKind.RECORD and \
                struct_needs_conversion(field.type):
            return True

    #check if any members need path conversion
    path_conv = get_path_converter(struct)
    if path_conv:
        return True
    return False

def struct_needs_conversion(struct):
    if not sdkver in struct_conversion_cache:
        struct_conversion_cache[sdkver] = {}
    if not strip_const(struct.spelling) in struct_conversion_cache[sdkver]:
        struct_conversion_cache[sdkver][strip_const(struct.spelling)] = struct_needs_conversion_nocache(struct)
    return struct_conversion_cache[sdkver][strip_const(struct.spelling)]

def handle_destructor(cfile, classname, winclassname, method):
    cfile.write(f"DEFINE_THISCALL_WRAPPER({winclassname}_destructor, 4)\n")
    cfile.write(f"void __thiscall {winclassname}_destructor({winclassname} *_this)\n{{/* never called */}}\n\n")
    return "destructor"

def get_path_converter(parent):
    for conv in path_conversions:
        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

class DummyWriter(object):
    def write(self, s):
        #noop
        pass

def to_c_bool(b):
    if b:
        return "1"
    return "0"

dummy_writer = DummyWriter()

def handle_method(cfile, classname, winclassname, cppname, method, cpp, cpp_h, existing_methods):
    used_name = method.spelling
    if used_name in existing_methods:
        number = '2'
        while used_name in existing_methods:
            idx = existing_methods.index(used_name)
            used_name = f"{method.spelling}_{number}"
            number = chr(ord(number) + 1)
        existing_methods.insert(idx, used_name)
    else:
        existing_methods.append(used_name)
    returns_record = method.result_type.get_canonical().kind == TypeKind.RECORD
    if returns_record:
        parambytes = 8 #_this + return pointer
    else:
        parambytes = 4 #_this
    for param in list(method.get_children()):
        if param.kind == CursorKind.PARM_DECL:
            if param.type.kind == TypeKind.LVALUEREFERENCE:
                parambytes += 4
            else:
                parambytes += int(math.ceil(param.type.get_size()/4.0) * 4)
    if method_needs_manual_handling(cppname, used_name):
        cpp = dummy_writer #just don't write the cpp function
    cfile.write(f"DEFINE_THISCALL_WRAPPER({winclassname}_{used_name}, {parambytes})\n")
    cpp_h.write("extern ")
    if method.result_type.spelling.startswith("ISteam"):
        cfile.write(f"win{method.result_type.spelling} ")
        cpp.write("void *")
        cpp_h.write("void *")
    elif returns_record:
        cfile.write(f"{method.result_type.spelling} *")
        cpp.write(f"{method.result_type.spelling} ")
        cpp_h.write(f"{method.result_type.spelling} ")
    else:
        cfile.write(f"{method.result_type.spelling} ")
        cpp.write(f"{method.result_type.spelling} ")
        cpp_h.write(f"{method.result_type.spelling} ")
    cfile.write(f'__thiscall {winclassname}_{used_name}({winclassname} *_this')
    cpp.write(f"{cppname}_{used_name}(void *linux_side")
    cpp_h.write(f"{cppname}_{used_name}(void *")
    if returns_record:
        cfile.write(f", {method.result_type.spelling} *_r")
    unnamed = 'a'
    need_convert = []
    manual_convert = []
    for param in list(method.get_children()):
        if param.kind == CursorKind.PARM_DECL:
            if param.type.kind == TypeKind.POINTER and \
                (param.type.get_pointee().kind == TypeKind.UNEXPOSED or param.type.get_pointee().kind == TypeKind.FUNCTIONPROTO):
                #unspecified function pointer
                typename = "void *"
            else:
                typename = param.type.spelling.split("::")[-1]

            real_type = param.type
            while real_type.kind == TypeKind.POINTER:
                real_type = real_type.get_pointee()
            win_name = typename
            if real_type.kind == TypeKind.RECORD and \
                    not real_type.spelling in wrapped_classes and \
                    struct_needs_conversion(real_type):
                need_convert.append(param)
                #preserve pointers
                win_name = typename.replace(real_type.spelling, f"win{real_type.spelling}_{sdkver}")
            elif real_type.spelling in manual_type_converters:
                manual_convert.append(param)
            elif param.spelling in manual_param_converters:
                manual_convert.append(param)

            win_name = win_name.replace('&', '*')

            if param.spelling == "":
                cfile.write(f", {win_name} _{unnamed}")
                cpp.write(f", {win_name} _{unnamed}")
                cpp_h.write(f", {win_name}")
                unnamed = chr(ord(unnamed) + 1)
            else:
                cfile.write(f", {win_name} {param.spelling}")
                cpp.write(f", {win_name} {param.spelling}")
                cpp_h.write(f", {win_name}")
    cfile.write(")\n{\n")
    cpp.write(")\n{\n")
    cpp_h.write(");\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]:
                cfile.write(f"    const char **lin_{path_conv['w2l_names'][i]} = steamclient_dos_to_unix_stringlist({path_conv['w2l_names'][i]});\n")
                # TODO
                pass
            else:
                cfile.write(f"    char lin_{path_conv['w2l_names'][i]}[PATH_MAX];\n")
                cfile.write(f"    steamclient_dos_path_to_unix_path({path_conv['w2l_names'][i]}, lin_{path_conv['w2l_names'][i]}, {to_c_bool(path_conv['w2l_urls'][i])});\n")
        if None in path_conv["l2w_names"]:
            cfile.write("    const char *path_result;\n")
        elif path_conv["return_is_size"]:
            cfile.write("    uint32 path_result;\n")
        elif len(path_conv["l2w_names"]) > 0:
            cfile.write(f"    {method.result_type.spelling} path_result;\n")

    for param in need_convert:
        if param.type.kind == TypeKind.POINTER:
            #handle single pointers, but not double pointers
            real_type = param.type
            while real_type.kind == TypeKind.POINTER:
                real_type = real_type.get_pointee()
            assert(param.type.get_pointee().kind == TypeKind.RECORD or \
                    strip_const(real_type.spelling) in manually_handled_structs)
            cpp.write(f"    {strip_const(param.type.get_pointee().spelling)} lin_{param.spelling};\n")
            cpp.write(f"    win_to_lin_struct_{strip_const(real_type.spelling)}_{sdkver}({param.spelling}, &lin_{param.spelling});\n")
        else:
            #raw structs
            cpp.write(f"    {param.type.spelling} lin_{param.spelling};\n")
            cpp.write(f"    win_to_lin_struct_{param.type.spelling}_{sdkver}(&{param.spelling}, &lin_{param.spelling});\n")
    for param in manual_convert:
        if param.spelling in manual_param_converters:
            cpp.write(f"    {param.spelling} = manual_convert_{param.spelling}({param.spelling});\n")
        else:
            cpp.write(f"    {param.spelling} = ({param.type.spelling})manual_convert_{param.type.spelling}((void*){param.spelling});\n")

    cfile.write("    TRACE(\"%p\\n\", _this);\n")

    if method.result_type.kind == TypeKind.VOID:
        cfile.write("    ")
    elif path_conv and (len(path_conv["l2w_names"]) > 0 or path_conv["return_is_size"]):
        cfile.write("    path_result = ")
    elif returns_record:
        cfile.write("    *_r = ")
    else:
        cfile.write("    return ")

    if method.result_type.kind == TypeKind.VOID:
        cpp.write("    ")
    elif len(need_convert) > 0:
        cpp.write(f"    {method.result_type.spelling} retval = ")
    else:
        cpp.write("    return ")

    should_do_cb_wrap = "GetAPICallResult" in used_name
    should_gen_wrapper = cpp != dummy_writer and \
            (method.result_type.spelling.startswith("ISteam") or \
             used_name.startswith("GetISteamGenericInterface"))

    if should_do_cb_wrap:
        cfile.write(f"do_cb_wrap(0, _this->linux_side, &{cppname}_{used_name}")
    else:
        if should_gen_wrapper:
            cfile.write("create_win_interface(pchVersion,\n        ")
        cfile.write(f"{cppname}_{used_name}(_this->linux_side")
    cpp.write(f"(({classname}*)linux_side)->{method.spelling}(")
    unnamed = 'a'
    first = True
    for param in list(method.get_children()):
        if param.kind == CursorKind.PARM_DECL:
            if not first:
                cpp.write(", ")
            else:
                first = False
            if param.spelling == "":
                cfile.write(f", _{unnamed}")
                cpp.write(f"({param.type.spelling})_{unnamed}")
                unnamed = chr(ord(unnamed) + 1)
            elif param.type.kind == TypeKind.POINTER and \
                    param.type.get_pointee().spelling in wrapped_classes:
                cfile.write(f", create_Linux{param.type.get_pointee().spelling}({param.spelling}, \"{winclassname}\")")
                cpp.write(f"({param.type.spelling}){param.spelling}")
            elif path_conv and param.spelling in path_conv["w2l_names"]:
                cfile.write(f", {param.spelling} ? lin_{param.spelling} : NULL")
                cpp.write(f"({param.type.spelling}){param.spelling}")
            elif param in need_convert:
                cfile.write(f", {param.spelling}")
                if param.type.kind != TypeKind.POINTER:
                    cpp.write(f"lin_{param.spelling}")
                else:
                    cpp.write(f"&lin_{param.spelling}")
            elif param.type.kind == TypeKind.LVALUEREFERENCE:
                cfile.write(f", {param.spelling}")
                cpp.write(f"*{param.spelling}")
            else:
                cfile.write(f", {param.spelling}")
                cpp.write(f"({param.type.spelling}){param.spelling}")
    if should_gen_wrapper:
        cfile.write(")")
    cfile.write(");\n")
    cpp.write(");\n")
    if returns_record:
        cfile.write("    return _r;\n")
    if path_conv and len(path_conv["l2w_names"]) > 0:
        for i in range(len(path_conv["l2w_names"])):
            cfile.write("    ")
            if path_conv["return_is_size"]:
                cfile.write("path_result = ")
            cfile.write(f"steamclient_unix_path_to_dos_path(path_result, {path_conv['l2w_names'][i]}, {path_conv['l2w_names'][i]}, {path_conv['l2w_lens'][i]}, {to_c_bool(path_conv['l2w_urls'][i])});\n")
        cfile.write("    return path_result;\n")
    if path_conv:
        for i in range(len(path_conv["w2l_names"])):
            if path_conv["w2l_arrays"][i]:
                cfile.write(f"    steamclient_free_stringlist(lin_{path_conv['w2l_names'][i]});\n")
    cfile.write("}\n\n")
    for param in need_convert:
        if param.type.kind == TypeKind.POINTER:
            if not "const " in param.type.spelling: #don't modify const arguments
                real_type = param.type
                while real_type.kind == TypeKind.POINTER:
                    real_type = real_type.get_pointee()
                cpp.write(f"    lin_to_win_struct_{real_type.spelling}_{sdkver}(&lin_{param.spelling}, {param.spelling});\n")
        else:
            cpp.write(f"    lin_to_win_struct_{param.type.spelling}_{sdkver}(&lin_{param.spelling}, &{param.spelling});\n")
    if method.result_type.kind != TypeKind.VOID and \
            len(need_convert) > 0:
        cpp.write("    return retval;\n")
    cpp.write("}\n\n")

def get_iface_version(classname):
    # ISteamClient -> STEAMCLIENT_INTERFACE_VERSION
    defname = f"{classname[1:].upper()}_INTERFACE_VERSION"
    if defname in iface_versions.keys():
        ver = iface_versions[defname]
    else:
        defname = f"{classname[1:].upper()}_VERSION"
        if defname in iface_versions.keys():
            ver = iface_versions[defname]
        else:
            ver = "UNVERSIONED"
    if classname in class_versions.keys() and ver in class_versions[classname]:
        return (ver, True)
    if not classname in class_versions.keys():
        class_versions[classname] = []
    class_versions[classname].append(ver)
    return (ver, False)

def handle_class(sdkver, classnode, file):
    children = list(classnode.get_children())
    if len(children) == 0:
        return
    (iface_version, already_generated) = get_iface_version(classnode.spelling)
    if already_generated:
        return
    winname = f"win{classnode.spelling}"
    cppname = f"cpp{classnode.spelling}_{iface_version}"

    file_exists = os.path.isfile(f"{winname}.c")
    cfile = open(f"{winname}.c", "a")
    if not file_exists:
        cfile.write("""/* This file is auto-generated, do not edit. */
#include <stdarg.h>

#include "windef.h"
#include "winbase.h"
#include "wine/debug.h"

#include "cxx.h"

#include "steam_defs.h"

#include "steamclient_private.h"

#include "struct_converters.h"

WINE_DEFAULT_DEBUG_CHANNEL(steamclient);

""")

    cpp = open(f"{cppname}.cpp", "w")
    cpp.write("#include \"steam_defs.h\"\n")
    cpp.write("#pragma push_macro(\"__cdecl\")\n")
    cpp.write("#undef __cdecl\n")
    cpp.write(f"#include \"steamworks_sdk_{sdkver}/steam_api.h\"\n")
    if os.path.isfile(f"steamworks_sdk_{sdkver}/steamnetworkingtypes.h"):
        cpp.write(f"#include \"steamworks_sdk_{sdkver}/steamnetworkingtypes.h\"\n")
    if not file == "steam_api.h":
        cpp.write(f"#include \"steamworks_sdk_{sdkver}/{file}\"\n")
    cpp.write("#pragma pop_macro(\"__cdecl\")\n")
    cpp.write("#include \"steamclient_private.h\"\n")
    cpp.write("#ifdef __cplusplus\nextern \"C\" {\n#endif\n")
    cpp.write(f"#define SDKVER_{sdkver}\n")
    cpp.write("#include \"struct_converters.h\"\n")
    cpp.write(f"#include \"{cppname}.h\"\n")

    cpp_h = open(f"{cppname}.h", "w")

    winclassname = f"win{classnode.spelling}_{iface_version}"
    cfile.write(f"#include \"{cppname}.h\"\n\n")
    cfile.write(f"typedef struct __{winclassname} {{\n")
    cfile.write("    vtable_ptr *vtable;\n")
    cfile.write("    void *linux_side;\n")
    cfile.write(f"}} {winclassname};\n\n")
    methods = []
    for child in children:
        if child.kind == CursorKind.CXX_METHOD and \
                child.is_virtual_method():
            handle_method(cfile, classnode.spelling, winclassname, cppname, child, cpp, cpp_h, methods)
        elif child.kind == CursorKind.DESTRUCTOR:
            methods.append(handle_destructor(cfile, classnode.spelling, winclassname, child))

    cfile.write(f"extern vtable_ptr {winclassname}_vtable;\n\n")
    cfile.write("#ifndef __GNUC__\n")
    cfile.write("void __asm_dummy_vtables(void) {\n")
    cfile.write("#endif\n")
    cfile.write(f"    __ASM_VTABLE({winclassname},\n")
    for method in methods:
        cfile.write(f"        VTABLE_ADD_FUNC({winclassname}_{method})\n")
    cfile.write("    );\n")
    cfile.write("#ifndef __GNUC__\n")
    cfile.write("}\n")
    cfile.write("#endif\n\n")
    cfile.write(f"{winclassname} *create_{winclassname}(void *linux_side)\n{{\n")
    if classnode.spelling in wrapped_classes:
        cfile.write(f"    {winclassname} *r = HeapAlloc(GetProcessHeap(), 0, sizeof({winclassname}));\n")
    else:
        cfile.write(f"    {winclassname} *r = alloc_mem_for_iface(sizeof({winclassname}), \"{iface_version}\");\n")
    cfile.write("    TRACE(\"-> %p\\n\", r);\n")
    cfile.write(f"    r->vtable = alloc_vtable(&{winclassname}_vtable, {len(methods)}, \"{iface_version}\");\n")
    cfile.write("    r->linux_side = linux_side;\n")
    cfile.write("    return r;\n}\n\n")

    cpp.write("#ifdef __cplusplus\n}\n#endif\n")

    constructors = open("win_constructors.h", "a")
    constructors.write(f"extern void *create_{winclassname}(void *);\n")

    constructors = open("win_constructors_table.dat", "a")
    constructors.write(f"    {{\"{iface_version}\", &create_{winclassname}}},\n")
    if iface_version in aliases.keys():
        for alias in aliases[iface_version]:
            constructors.write(f"    {{\"{alias}\", &create_{winclassname}}}, /* alias */\n")


generated_cb_handlers = []
generated_cb_ids = []
cpp_files_need_close_brace = []
cb_table = {}
cb_table64 = {}

def get_field_attribute_str(field):
    if field.type.kind != TypeKind.RECORD:
        return ""
    win_struct = find_windows_struct(field.type)
    if win_struct is None:
        align = field.type.get_align()
    else:
        align = win_struct.get_align()
    return " __attribute__((aligned(" + str(align) + ")))"

#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):
    members = struct.get_children()
    cb_num = None
    has_fields = False
    for c in members:
        if c.kind == CursorKind.ENUM_DECL:
            enums = c.get_children()
            for e in enums:
                if e.displayname == "k_iCallback":
                    cb_num = e.enum_value
        if c.kind == CursorKind.FIELD_DECL:
            has_fields = True

    w2l_handler_name = None
    l2w_handler_name = None

    def dump_win_struct(to_file, name):
        to_file.write("#pragma pack( push, 8 )\n")
        to_file.write(f"struct win{name} {{\n")
        for m in struct.get_children():
            if m.kind == CursorKind.FIELD_DECL:
                if m.type.kind == TypeKind.CONSTANTARRAY:
                    to_file.write(f"    {m.type.element_type.spelling} {m.displayname}[{m.type.element_count}];\n")
                elif m.type.kind == TypeKind.RECORD and \
                        struct_needs_conversion(m.type):
                    to_file.write(f"    win{m.type.spelling}_{sdkver} {m.displayname};\n")
                else:
                    if m.type.kind == TypeKind.POINTER and \
                            (m.type.get_pointee().kind == TypeKind.UNEXPOSED or m.type.get_pointee().kind == TypeKind.FUNCTIONPROTO):
                        to_file.write(f"    void *{m.displayname}; /*fn pointer*/\n")
                    else:
                        to_file.write(f"    {m.type.spelling} {m.displayname}{get_field_attribute_str(m)};\n")
        to_file.write("}  __attribute__ ((ms_struct));\n")
        to_file.write("#pragma pack( pop )\n")

    if cb_num is None:
        hfile = open("struct_converters.h", "a")

        if not has_fields:
            return
        if struct.spelling == "":
            return
        if not struct_needs_conversion(struct.type):
            return

        struct_name = f"{struct.displayname}_{sdkver}"

        if struct_name in converted_structs:
            return
        converted_structs.append(struct_name)

        w2l_handler_name = f"win_to_lin_struct_{struct_name}"
        l2w_handler_name = f"lin_to_win_struct_{struct_name}"
        l2w_handler_name64 = None

        hfile.write(f"#if defined(SDKVER_{sdkver}) || !defined(__cplusplus)\n")
        dump_win_struct(hfile, struct_name)
        hfile.write(f"typedef struct win{struct_name} win{struct_name};\n")
        hfile.write(f"struct {struct.displayname};\n")

        if strip_const(struct.spelling) in manually_handled_structs:
            hfile.write("#endif\n\n")
            return

        hfile.write(f"extern void {w2l_handler_name}(const struct win{struct_name} *w, struct {struct.displayname} *l);\n")
        hfile.write(f"extern void {l2w_handler_name}(const struct {struct.displayname} *l, struct win{struct_name} *w);\n")
        hfile.write("#endif\n\n")
    else:
        #for callbacks, we use the windows struct size in the cb dispatch switch
        windows_struct = find_windows_struct(struct.type)
        windows_struct64 = find_windows64_struct(struct.type)
        struct64 = find_linux64_struct(struct.type)
        struct_name = f"{struct.displayname}_{windows_struct.get_size()}"
        l2w_handler_name = f"cb_{struct_name}"
        if windows_struct64.get_size() != windows_struct.get_size():
            struct_name64 = f"{struct.displayname}_{windows_struct64.get_size()}"
            l2w_handler_name64 = f"cb_{struct_name64}"
        else:
            l2w_handler_name64 = None
        if l2w_handler_name in generated_cb_handlers:
            # we already have a handler for the callback struct of this size
            return
        if not struct_needs_conversion(struct.type):
            return

        cb_id = cb_num | (struct.type.get_size() << 16)
        cb_id64 = cb_num | (struct64.get_size() << 16)
        if cb_id in generated_cb_ids:
            # either this cb changed name, or steam used the same ID for different structs
            return

        generated_cb_ids.append(cb_id)

        datfile = open("cb_converters.dat", "a")
        if l2w_handler_name64:
            datfile.write("#ifdef __i386__\n")
            datfile.write(f"case 0x{cb_id:08x}: win_msg->m_cubParam = {windows_struct.get_size()}; win_msg->m_pubParam = HeapAlloc(GetProcessHeap(), 0, win_msg->m_cubParam); {l2w_handler_name}((void*)lin_msg.m_pubParam, (void*)win_msg->m_pubParam); break;\n")
            datfile.write("#endif\n")

            datfile.write("#ifdef __x86_64__\n")
            datfile.write(f"case 0x{cb_id64:08x}: win_msg->m_cubParam = {windows_struct64.get_size()}; win_msg->m_pubParam = HeapAlloc(GetProcessHeap(), 0, win_msg->m_cubParam); {l2w_handler_name64}((void*)lin_msg.m_pubParam, (void*)win_msg->m_pubParam); break;\n")
            datfile.write("#endif\n")
        else:
            datfile.write(f"case 0x{cb_id:08x}: win_msg->m_cubParam = {windows_struct.get_size()}; win_msg->m_pubParam = HeapAlloc(GetProcessHeap(), 0, win_msg->m_cubParam); {l2w_handler_name}((void*)lin_msg.m_pubParam, (void*)win_msg->m_pubParam); break;\n")

        generated_cb_handlers.append(l2w_handler_name)

        if not cb_num in cb_table.keys():
            # latest SDK linux size, list of windows struct sizes and names
            cb_table[cb_num] = (struct.type.get_size(), [])
            if l2w_handler_name64:
                cb_table64[cb_num] = (struct64.get_size(), [])
            else:
                cb_table64[cb_num] = (struct.type.get_size(), [])
        cb_table[cb_num][1].append((windows_struct.get_size(), struct_name))
        if l2w_handler_name64:
            cb_table64[cb_num][1].append((windows_struct64.get_size(), struct_name64))
        else:
            cb_table64[cb_num][1].append((windows_struct.get_size(), struct_name))

        hfile = open("cb_converters.h", "a")
        hfile.write(f"struct {struct.displayname};\n")
        if l2w_handler_name64:
            hfile.write("#ifdef __i386__\n")
            hfile.write(f"struct win{struct_name};\n")
            hfile.write(f"extern void {l2w_handler_name}(const struct {struct.displayname} *l, struct win{struct_name} *w);\n")
            hfile.write("#endif\n")
            hfile.write("#ifdef __x86_64__\n")
            hfile.write(f"struct win{struct_name64};\n")
            hfile.write(f"extern void {l2w_handler_name64}(const struct {struct.displayname} *l, struct win{struct_name64} *w);\n")
            hfile.write("#endif\n\n")
        else:
            hfile.write(f"struct win{struct_name};\n")
            hfile.write(f"extern void {l2w_handler_name}(const struct {struct.displayname} *l, struct win{struct_name} *w);\n\n")

    cppname = f"struct_converters_{sdkver}.cpp"
    file_exists = os.path.isfile(cppname)
    cppfile = open(cppname, "a")
    if not file_exists:
        cppfile.write("#include \"steam_defs.h\"\n")
        cppfile.write("#pragma push_macro(\"__cdecl\")\n")
        cppfile.write("#undef __cdecl\n")
        cppfile.write(f"#include \"steamworks_sdk_{sdkver}/steam_api.h\"\n")
        cppfile.write(f"#include \"steamworks_sdk_{sdkver}/isteamgameserver.h\"\n")
        if os.path.isfile(f"steamworks_sdk_{sdkver}/isteamnetworkingsockets.h"):
            cppfile.write(f"#include \"steamworks_sdk_{sdkver}/isteamnetworkingsockets.h\"\n")
        if os.path.isfile(f"steamworks_sdk_{sdkver}/isteamgameserverstats.h"):
            cppfile.write(f"#include \"steamworks_sdk_{sdkver}/isteamgameserverstats.h\"\n")
        if os.path.isfile(f"steamworks_sdk_{sdkver}/isteamgamecoordinator.h"):
            cppfile.write(f"#include \"steamworks_sdk_{sdkver}/isteamgamecoordinator.h\"\n")
        if os.path.isfile(f"steamworks_sdk_{sdkver}/steamnetworkingtypes.h"):
            cppfile.write(f"#include \"steamworks_sdk_{sdkver}/steamnetworkingtypes.h\"\n")
        cppfile.write("#pragma pop_macro(\"__cdecl\")\n")
        cppfile.write("#include \"steamclient_private.h\"\n")
        cppfile.write("extern \"C\" {\n")
        cppfile.write(f"#define SDKVER_{sdkver}\n")
        cppfile.write("#include \"struct_converters.h\"\n")
        cpp_files_need_close_brace.append(cppname)

    path_conv = get_path_converter(struct.type)

    def handle_field(m, src, dst):
        if m.kind == CursorKind.FIELD_DECL:
            if m.type.kind == TypeKind.CONSTANTARRAY:
                assert(m.type.element_type.kind != TypeKind.RECORD or \
                        not struct_needs_conversion(m.type.element_type))
                cppfile.write(f"    memcpy({dst}->{m.displayname}, {src}->{m.displayname}, sizeof({dst}->{m.displayname}));\n")
            elif m.type.kind == TypeKind.RECORD and \
                    struct_needs_conversion(m.type):
                cppfile.write(f"    {src}_to_{dst}_struct_{m.type.spelling}_{sdkver}(&{src}->{m.displayname}, &{dst}->{m.displayname});\n")
            elif path_conv and m.displayname in path_conv["l2w_names"]:
                for i in range(len(path_conv["l2w_names"])):
                    if path_conv["l2w_names"][i] == m.displayname:
                        url = path_conv["l2w_urls"][i]
                        break
                cppfile.write(f"    steamclient_unix_path_to_dos_path(1, {src}->{m.displayname}, g_tmppath, sizeof(g_tmppath), {to_c_bool(url)});\n")
                cppfile.write(f"    {dst}->{m.displayname} = g_tmppath;\n")
            else:
                cppfile.write(f"    {dst}->{m.displayname} = {src}->{m.displayname};\n")

    if not cb_num is None:
        if l2w_handler_name64:
            cppfile.write("#ifdef __i386__\n")
            dump_win_struct(cppfile, struct_name)
            cppfile.write("#endif\n")
            cppfile.write("#ifdef __x86_64__\n")
            dump_win_struct(cppfile, struct_name64)
            cppfile.write("#endif\n")
        else:
            dump_win_struct(cppfile, struct_name)

    if w2l_handler_name:
        cppfile.write(f"void {w2l_handler_name}(const struct win{struct_name} *win, struct {struct.displayname} *lin)\n{{\n")
        for m in struct.get_children():
            handle_field(m, "win", "lin")
        cppfile.write("}\n\n")

    if l2w_handler_name64:
        cppfile.write("#ifdef __x86_64__\n")
        cppfile.write(f"void {l2w_handler_name64}(const struct {struct.displayname} *lin, struct win{struct_name64} *win)\n{{\n")
        for m in struct.get_children():
            handle_field(m, "lin", "win")
        cppfile.write("}\n")
        cppfile.write("#endif\n\n")

    if l2w_handler_name:
        if l2w_handler_name64:
            cppfile.write("#ifdef __i386__\n")
        cppfile.write(f"void {l2w_handler_name}(const struct {struct.displayname} *lin, struct win{struct_name} *win)\n{{\n")
        for m in struct.get_children():
            handle_field(m, "lin", "win")
        cppfile.write("}\n")
        if l2w_handler_name64:
            cppfile.write("#endif\n\n")
        else:
            cppfile.write("\n")

prog = re.compile("^#define\s*(\w*)\s*\"(.*)\"")
for sdkver in sdk_versions:
    print(f"parsing SDK version {sdkver}...")
    sdkdir = f"steamworks_sdk_{sdkver}"

    sources = {}
    iface_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 file == "isteammasterserverupdater.h":
            if """#error "This file isn't used any more"\n""" in lines:
                sources[f"{sdkdir}/isteammasterserverupdater.h"] = ""

        for line in lines:
            if "define STEAM" in line and "_VERSION" in line:
                result = prog.match(line)
                if result:
                    iface, version = result.group(1, 2)
                    iface_versions[iface] = version

    source = [f"""#if __has_include("{sdkdir}/{file}")
                  #include "{sdkdir}/{file}"
                  #endif""" for file, _ in files]
    sources["source.cpp"] = "\n".join(source)
    windows_args = ["-D_WIN32", "-fms-extensions", "-Wno-ignored-attributes",
                    "-mms-bitfields", "-U__linux__", "-Wno-incompatible-ms-struct"]
    windows_args += ['-I' + CLANG_PATH + '/include/']
    linux_args = ["-DGNUC"]
    linux_args += ['-I' + CLANG_PATH + '/include/']

    index = Index.create()

    linux_build32 = index.parse("source.cpp", args=linux_args + ["-m32"], unsaved_files=sources.items())
    diagnostics = list(linux_build32.diagnostics)
    for diag in diagnostics: print(diag)
    assert len(diagnostics) == 0

    linux_build64 = index.parse("source.cpp", args=linux_args + ["-m64"], unsaved_files=sources.items())
    diagnostics = list(linux_build64.diagnostics)
    for diag in diagnostics: print(diag)
    assert len(diagnostics) == 0

    windows_build32 = index.parse("source.cpp", args=windows_args + ["-m32"], unsaved_files=sources.items())
    diagnostics = list(windows_build32.diagnostics)
    for diag in diagnostics: print(diag)
    assert len(diagnostics) == 0

    windows_build64 = index.parse("source.cpp", args=windows_args + ["-m64"], unsaved_files=sources.items())
    diagnostics = list(windows_build64.diagnostics)
    for diag in diagnostics: print(diag)
    assert len(diagnostics) == 0

    linux_structs64 = dict(reversed([(child.spelling, child.type) for child
                                     in linux_build64.cursor.get_children()]))
    windows_structs32 = dict(reversed([(child.spelling, child.type) for child
                                       in windows_build32.cursor.get_children()]))
    windows_structs64 = dict(reversed([(child.spelling, child.type) for child
                                       in windows_build64.cursor.get_children()]))

    classes = dict([(klass, file) for file, classes in files for klass in classes])
    for child in linux_build32.cursor.get_children():
        if child.kind == CursorKind.CLASS_DECL and child.displayname in classes:
            handle_class(sdkver, child, classes[child.displayname])
        if child.kind in [CursorKind.STRUCT_DECL, CursorKind.CLASS_DECL]:
            handle_struct(sdkver, child)
        if child.displayname in print_sizes:
            print("size of %s is %u" % (child.displayname, child.type.get_size()))

for f in cpp_files_need_close_brace:
    m = open(f, "a")
    m.write("\n}\n")

getapifile = open("cb_getapi_table.dat", "w")
cbsizefile = open("cb_getapi_sizes.dat", "w")

cbsizefile.write("#ifdef __i386__\n")
getapifile.write("#ifdef __i386__\n")
for cb in sorted(cb_table.keys()):
    cbsizefile.write(f"case {cb}: /* {cb_table[cb][1][0][1]} */\n")
    cbsizefile.write(f"    return {cb_table[cb][0]};\n")
    getapifile.write(f"case {cb}:\n")
    getapifile.write("    switch(callback_len){\n")
    getapifile.write("    default:\n") # the first one should be the latest, should best support future SDK versions
    for (size, name) in cb_table[cb][1]:
        getapifile.write(f"    case {size}: cb_{name}(lin_callback, callback); break;\n")
    getapifile.write("    }\n    break;\n")
cbsizefile.write("#endif\n")
getapifile.write("#endif\n")

cbsizefile.write("#ifdef __x86_64__\n")
getapifile.write("#ifdef __x86_64__\n")
for cb in sorted(cb_table64.keys()):
    cbsizefile.write(f"case {cb}: /* {cb_table64[cb][1][0][1]} */\n")
    cbsizefile.write(f"    return {cb_table64[cb][0]};\n")
    getapifile.write(f"case {cb}:\n")
    getapifile.write("    switch(callback_len){\n")
    getapifile.write("    default:\n") # the first one should be the latest, should best support future SDK versions
    for (size, name) in cb_table64[cb][1]:
        getapifile.write(f"    case {size}: cb_{name}(lin_callback, callback); break;\n")
    getapifile.write("    }\n    break;\n")
cbsizefile.write("#endif\n")
getapifile.write("#endif\n")