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.
This commit is contained in:
Mark Jansen 2024-01-04 23:02:22 +01:00
parent 116c0cd9a5
commit 24a56f89ab
No known key found for this signature in database
GPG key ID: B39240EE84BEAE8B
246 changed files with 1910 additions and 9091 deletions

View file

@ -6,6 +6,7 @@ add_subdirectory(inflib)
if(CMAKE_CROSSCOMPILING)
add_subdirectory(3rdparty)
add_subdirectory(apisets)
add_subdirectory(cicero)
add_subdirectory(comsupp)
add_subdirectory(conutils)

View file

@ -0,0 +1,13 @@
add_library(apisets
apisets.c
apisets.h
apisetsp.h
# Generated file:
apisets.table.c
)
target_include_directories(apisets INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_definitions(apisets PRIVATE _NTSYSTEM_)
add_dependencies(apisets psdk)

92
sdk/lib/apisets/apisets.c Normal file
View file

@ -0,0 +1,92 @@
/*
* PROJECT: ReactOS apisets
* LICENSE: LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later)
* PURPOSE: Resolving the apiset to a ReactOS system dll
* COPYRIGHT: Copyright 2024 Mark Jansen <mark.jansen@reactos.org>
*/
#include <ndk/umtypes.h>
#include <ndk/rtlfuncs.h>
#include "apisetsp.h"
const ULONGLONG API_ = (ULONGLONG)0x2D004900500041; /// L"API-"
const ULONGLONG EXT_ = (ULONGLONG)0x2D005400580045; /// L"EXT-";
WORD PrefixSize = sizeof(L"api-") - sizeof(WCHAR);
WORD ExtensionSize = sizeof(L".dll") - sizeof(WCHAR);
// The official prototype according to the Windows kit 8.1 is:
//NTSTATUS
//ApiSetResolveToHost (
// _In_ PCAPI_SET_NAMESPACE_ARRAY Schema,
// _In_ PCUNICODE_STRING FileNameIn,
// _In_opt_ PCUNICODE_STRING ParentName,
// _Out_ PBOOLEAN Resolved,
// _Out_ PUNICODE_STRING HostBinary
// );
NTSTATUS
ApiSetResolveToHost(
_In_ DWORD ApisetVersion,
_In_ PCUNICODE_STRING ApiToResolve,
_Out_ PBOOLEAN Resolved,
_Out_ PUNICODE_STRING Output
)
{
if (ApiToResolve->Length < PrefixSize)
{
*Resolved = FALSE;
return STATUS_SUCCESS;
}
// Grab the first four chars from the string, converting the first 3 to uppercase
PWSTR ApiSetNameBuffer = ApiToResolve->Buffer;
ULONGLONG ApiSetNameBufferPrefix = ((ULONGLONG *)ApiSetNameBuffer)[0] & 0xFFFFFFDFFFDFFFDF;
// Check if it matches either 'api-' or 'ext-'
if (!(ApiSetNameBufferPrefix == API_ || ApiSetNameBufferPrefix == EXT_))
{
*Resolved = FALSE;
return STATUS_SUCCESS;
}
// If there is an extension, cut it off (we store apisets without extension)
UNICODE_STRING Tmp = *ApiToResolve;
const WCHAR *Extension = Tmp.Buffer + (Tmp.Length - ExtensionSize) / sizeof(WCHAR);
if (!_wcsnicmp(Extension, L".dll", ExtensionSize / sizeof(WCHAR)))
Tmp.Length -= ExtensionSize;
// Binary search the apisets
// Ideally we should use bsearch here, but that drags in another dependency and we do not want that here.
LONG UBnd = g_ApisetsCount - 1;
LONG LBnd = 0;
while (LBnd <= UBnd)
{
LONG Index = (UBnd - LBnd) / 2 + LBnd;
LONG result = RtlCompareUnicodeString(&Tmp, &g_Apisets[Index].Name, TRUE);
if (result == 0)
{
// Check if this version is included
if (g_Apisets[Index].dwOsVersions & ApisetVersion)
{
// Return a static string (does not have to be freed)
*Resolved = TRUE;
*Output = g_Apisets[Index].Target;
}
return STATUS_SUCCESS;
}
else if (result < 0)
{
UBnd = Index - 1;
}
else
{
LBnd = Index + 1;
}
}
*Resolved = FALSE;
return STATUS_SUCCESS;
}

31
sdk/lib/apisets/apisets.h Normal file
View file

@ -0,0 +1,31 @@
/*
* PROJECT: ReactOS apisets
* LICENSE: LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later)
* PURPOSE: Interface for resolving the apisets
* COPYRIGHT: Copyright 2024 Mark Jansen <mark.jansen@reactos.org>
*/
#ifndef APISETS_H
#define APISETS_H
#ifdef __cplusplus
extern "C"
{
#endif
#define APISET_WIN7 (1 << 0)
#define APISET_WIN8 (1 << 1)
#define APISET_WIN81 (1 << 2)
#define APISET_WIN10 (1 << 3)
//#define APISET_WIN11 (1 << 4)
NTSTATUS
ApiSetResolveToHost(_In_ DWORD ApisetVersion, _In_ PCUNICODE_STRING ApiToResolve, _Out_ PBOOLEAN Resolved, _Out_ PUNICODE_STRING Output);
#ifdef __cplusplus
}
#endif
#endif // APISETS_H

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,25 @@
#ifndef APISETSP_H
#define APISETSP_H
#ifdef __cplusplus
extern "C"
{
#endif
#include "apisets.h"
typedef struct _ROS_APISET
{
const UNICODE_STRING Name;
const UNICODE_STRING Target;
DWORD dwOsVersions;
} ROS_APISET;
extern const ROS_APISET g_Apisets[];
extern const LONG g_ApisetsCount;
#ifdef __cplusplus
}
#endif
#endif // APISETSP_H

150
sdk/lib/apisets/update.py Normal file
View file

@ -0,0 +1,150 @@
'''
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:])

View file

@ -5100,15 +5100,7 @@ void actctx_init(PVOID* pOldShimData)
ctx.lpResourceName = NULL;
ctx.lpSource = buffer;
RtlStringCchCopyW(buffer, RTL_NUMBER_OF(buffer), SharedUserData->NtSystemRoot);
if (RosGetProcessCompatVersion())
{
RtlStringCchCatW(buffer, RTL_NUMBER_OF(buffer), L"\\winsxs\\manifests\\forwardcompatible.manifest");
}
else
{
RtlStringCchCatW(buffer, RTL_NUMBER_OF(buffer), L"\\winsxs\\manifests\\systemcompatible.manifest");
}
RtlStringCchCatW(buffer, RTL_NUMBER_OF(buffer), L"\\winsxs\\manifests\\systemcompatible.manifest");
Status = RtlCreateActivationContext(0, (PVOID)&ctx, 0, NULL, NULL, &handle);
if (NT_SUCCESS(Status))