xref: /reactos/sdk/lib/apisets/update.py (revision d6eebaa4)
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