1''' 2PROJECT: ReactOS apisets 3LICENSE: MIT (https://spdx.org/licenses/MIT) 4PURPOSE: Create apiset lookup table based on the data files of https://apisets.info 5COPYRIGHT: Copyright 2024 Mark Jansen <mark.jansen@reactos.org> 6''' 7 8from pathlib import Path 9from dataclasses import dataclass, field 10import sys 11import json 12 13# These are modules we do not have, so redirect them to ones we do have. 14REDIRECT_HOSTS = { 15 'kernelbase.dll': 'kernel32.dll', 16 'kernel.appcore.dll': 'kernel32.dll', 17 'combase.dll': 'ole32.dll', 18 'ucrtbase.dll': 'msvcrt.dll', 19 'shcore.dll': 'shell32.dll', 20 'winmmbase.dll': 'winmm.dll', 21 'gdi32full.dll': 'gdi32.dll' 22} 23 24OUTPUT_HEADER = """/* 25 * PROJECT: ReactOS apisets 26 * LICENSE: LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later) 27 * PURPOSE: Autogenerated table of all apisets 28 * COPYRIGHT: Copyright 2024 Mark Jansen <mark.jansen@reactos.org> 29 */ 30 31#include <ndk/umtypes.h> 32#include <ndk/rtlfuncs.h> 33#include "apisetsp.h" 34 35const ROS_APISET g_Apisets[] = { 36""" 37 38OUTPUT_FOOTER = """}; 39 40const LONG g_ApisetsCount = RTL_NUMBER_OF(g_Apisets); 41""" 42 43def winver_to_name(version): 44 major, minor, build, _ = map(int, version.split('.')) 45 if (major, minor) == (6, 1): 46 return 'APISET_WIN7' 47 if (major, minor) == (6, 2): 48 return 'APISET_WIN8' 49 if (major, minor) == (6, 3): 50 return 'APISET_WIN81' 51 if (major, minor) == (10, 0): 52 if build < 22000: 53 return 'APISET_WIN10' 54 return 'APISET_WIN11' 55 assert False, (major, minor, build) 56 57@dataclass 58class Apiset: 59 name: str 60 host: str 61 versions: list[str] = field(default_factory=list) 62 63 def add_version(self, version): 64 if version not in self.versions: 65 self.versions.append(version) 66 67 def __str__(self): 68 version_str = ' | '.join(self.versions) 69 name = self.name 70 assert name[-4:].lower() == '.dll' 71 name = name[:-4] 72 prefix, postfix = '', '' 73 host = self.host 74 if host == '': 75 # Disable forwarders that have an empty host 76 prefix = '// ' 77 else: 78 # Check to see if there is any dll we want to swap (kernelbase -> kernel32) 79 replace = REDIRECT_HOSTS.get(host.lower(), None) 80 if replace: 81 postfix = ' // ' + host 82 host = replace 83 return f' {prefix}{{ RTL_CONSTANT_STRING(L"{name}"), RTL_CONSTANT_STRING(L"{host}"), {version_str} }},{postfix}' 84 85 86class ApisetSchema: 87 def __init__(self, file): 88 self._data = json.load(file.open()) 89 self.version = winver_to_name(self._data['PE']['ProductVersion']) 90 self._arch = self._data['PE']['Machine'] 91 92 def apisets(self): 93 for elem in self._data['namespaces']: 94 name = elem['name'] 95 host = elem['host'] 96 yield Apiset(name, host) 97 98 99class CombinedSchemas: 100 def __init__(self): 101 self._apisets = {} 102 103 def add(self, schema: ApisetSchema): 104 for apiset in schema.apisets(): 105 lowername = apiset.name.lower() 106 if lowername not in self._apisets: 107 self._apisets[lowername] = apiset 108 else: 109 apiset = self._apisets[lowername] 110 apiset.add_version(schema.version) 111 112 def generate(self, output): 113 for key in sorted(self._apisets): 114 apiset = self._apisets[key] 115 output.write(f'{apiset}\n'.encode('utf-8')) 116 117 118def process_apisetschemas(input_dir: Path, output_file): 119 schemas = CombinedSchemas() 120 121 for schemafile in input_dir.glob('*.json'): 122 schema = ApisetSchema(schemafile) 123 # Skip Win11 for now 124 if schema.version != 'APISET_WIN11': 125 schemas.add(schema) 126 127 output_file.write(OUTPUT_HEADER.encode('utf-8')) 128 schemas.generate(output_file) 129 output_file.write(OUTPUT_FOOTER.encode('utf-8')) 130 131 132def usage(): 133 print('Usage: update.py <apisetschema folder>') 134 print(' where <apisetschema folder> is the folder with all apisetschema json files') 135 136def main(args): 137 if len(args) < 1: 138 return usage() 139 140 apisetschemas = Path(args[0]) 141 if not apisetschemas.is_dir(): 142 return usage() 143 144 output = Path(__file__).parent / 'apisets.table.c' 145 146 process_apisetschemas(apisetschemas, output.open('wb')) 147 148 149if __name__ == '__main__': 150 main(sys.argv[1:]) 151