reactos/sdk/lib/apisets/update.py
Mark Jansen 24a56f89ab
Rework apisets to use a table
This removes all fake apiset forwarders,
and handles apisets inside ntdll.
This is not 100% compatible with how windows does it, but it should be good enough for us.
2024-04-27 22:51:34 +02:00

150 lines
4.3 KiB
Python

'''
PROJECT: ReactOS apisets
LICENSE: MIT (https://spdx.org/licenses/MIT)
PURPOSE: Create apiset lookup table based on the data files of https://apisets.info
COPYRIGHT: Copyright 2024 Mark Jansen <mark.jansen@reactos.org>
'''
from pathlib import Path
from dataclasses import dataclass, field
import sys
import json
# These are modules we do not have, so redirect them to ones we do have.
REDIRECT_HOSTS = {
'kernelbase.dll': 'kernel32.dll',
'kernel.appcore.dll': 'kernel32.dll',
'combase.dll': 'ole32.dll',
'ucrtbase.dll': 'msvcrt.dll',
'shcore.dll': 'shell32.dll',
'winmmbase.dll': 'winmm.dll',
'gdi32full.dll': 'gdi32.dll'
}
OUTPUT_HEADER = """/*
* PROJECT: ReactOS apisets
* LICENSE: LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later)
* PURPOSE: Autogenerated table of all apisets
* COPYRIGHT: Copyright 2024 Mark Jansen <mark.jansen@reactos.org>
*/
#include <ndk/umtypes.h>
#include <ndk/rtlfuncs.h>
#include "apisetsp.h"
const ROS_APISET g_Apisets[] = {
"""
OUTPUT_FOOTER = """};
const LONG g_ApisetsCount = RTL_NUMBER_OF(g_Apisets);
"""
def winver_to_name(version):
major, minor, build, _ = map(int, version.split('.'))
if (major, minor) == (6, 1):
return 'APISET_WIN7'
if (major, minor) == (6, 2):
return 'APISET_WIN8'
if (major, minor) == (6, 3):
return 'APISET_WIN81'
if (major, minor) == (10, 0):
if build < 22000:
return 'APISET_WIN10'
return 'APISET_WIN11'
assert False, (major, minor, build)
@dataclass
class Apiset:
name: str
host: str
versions: list[str] = field(default_factory=list)
def add_version(self, version):
if version not in self.versions:
self.versions.append(version)
def __str__(self):
version_str = ' | '.join(self.versions)
name = self.name
assert name[-4:].lower() == '.dll'
name = name[:-4]
prefix, postfix = '', ''
host = self.host
if host == '':
# Disable forwarders that have an empty host
prefix = '// '
else:
# Check to see if there is any dll we want to swap (kernelbase -> kernel32)
replace = REDIRECT_HOSTS.get(host.lower(), None)
if replace:
postfix = ' // ' + host
host = replace
return f' {prefix}{{ RTL_CONSTANT_STRING(L"{name}"), RTL_CONSTANT_STRING(L"{host}"), {version_str} }},{postfix}'
class ApisetSchema:
def __init__(self, file):
self._data = json.load(file.open())
self.version = winver_to_name(self._data['PE']['ProductVersion'])
self._arch = self._data['PE']['Machine']
def apisets(self):
for elem in self._data['namespaces']:
name = elem['name']
host = elem['host']
yield Apiset(name, host)
class CombinedSchemas:
def __init__(self):
self._apisets = {}
def add(self, schema: ApisetSchema):
for apiset in schema.apisets():
lowername = apiset.name.lower()
if lowername not in self._apisets:
self._apisets[lowername] = apiset
else:
apiset = self._apisets[lowername]
apiset.add_version(schema.version)
def generate(self, output):
for key in sorted(self._apisets):
apiset = self._apisets[key]
output.write(f'{apiset}\n'.encode('utf-8'))
def process_apisetschemas(input_dir: Path, output_file):
schemas = CombinedSchemas()
for schemafile in input_dir.glob('*.json'):
schema = ApisetSchema(schemafile)
# Skip Win11 for now
if schema.version != 'APISET_WIN11':
schemas.add(schema)
output_file.write(OUTPUT_HEADER.encode('utf-8'))
schemas.generate(output_file)
output_file.write(OUTPUT_FOOTER.encode('utf-8'))
def usage():
print('Usage: update.py <apisetschema folder>')
print(' where <apisetschema folder> is the folder with all apisetschema json files')
def main(args):
if len(args) < 1:
return usage()
apisetschemas = Path(args[0])
if not apisetschemas.is_dir():
return usage()
output = Path(__file__).parent / 'apisets.table.c'
process_apisetschemas(apisetschemas, output.open('wb'))
if __name__ == '__main__':
main(sys.argv[1:])