Add DPI awareness and guest process launch - #10
Conversation
# DPI awareness The emulator gave every program a screen of 96 DPI. It also made the host window with no DPI awareness. A program that asks for a different DPI got the wrong screen size and a window that is not sharp. The emulator now reads the DPI awareness of the program. It reads it from the application manifest in the image, from an external manifest file, or from the NtUserSetProcessDpiAwarenessContext system call. user32 does not ask the kernel for most DPI values. It calculates them from a packed value in the CLIENTINFO block of the thread and in the window structure. The emulator writes this value into the two locations. If it does not, the program reads one DPI from user32 and a different DPI from the system calls. The emulator gives the same awareness to the host window. An aware program gets a window in true pixels. An unaware program keeps the window that the desktop manager makes larger for it. The emulator also sets the composited flag in the desktop data. Without this flag, user32 uses a different code path. That path gives 96 DPI to a program that is aware. # Guest process launch A guest process could not start a different program. The emulator did not have the NtCreateUserProcess system call. DELTARUNE stopped at its menu for this reason. The game starts a new process when you select a chapter. The emulator now starts a second Brovan for each new guest process. One guest process is one host process. The window manager, the Vulkan device, the scheduler and the caches are global to a process. Two guests in one emulator need a process identity in all of them. The host operating system gives this isolation at no cost. The parent sends the image path, the command line and the directory to the child. It encodes these values with base64. Without base64, the host command line divides the values and joins them again. A value with a quotation mark or a space does not stay correct. # Session registry The Brovan instances of one session share a table in a memory-mapped file. Each instance writes one row. An instance that must stop a different guest process writes a request into the row of that process. The owner of the row reads the request and stops itself. This is the only correct method. The memory of that process is in a different host process. The emulator counts the rows before it starts a child. It permits a maximum of six guest processes in one session. # New options --cwd <dir> sets the directory in which the program starts. --guest-cmdline <s> sets the command line of the program. The two options also accept a value in the form "base64:<value>".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2dd672f4bd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| Process = new WinProcess | ||
| { | ||
| PID = unchecked((uint)HostProcess.Id), |
There was a problem hiding this comment.
Use a guest PID for spawned processes
The child emulator registers its session slot with Instance.WinHelper.PID, but this returns HostProcess.Id as the WinProcess/CLIENT_ID PID. When those values differ, which is the normal case because WinSysHelper generates guest PIDs independently, all follow-up cross-process operations route Process.PID into GuestSessionRegistry.SendRequest and fail to find the child slot with STATUS_INVALID_CID, so the launched process cannot be queried, read/written, or injected via the new registry RPC path.
Useful? React with 👍 / 👎.
| ThreadId = Process.PID, | ||
| }; | ||
|
|
||
| ulong ProcessHandle = Instance.WinHelper.HandleManager.AddHandle(Process, AccessMask.GenericAll).Handle; |
There was a problem hiding this comment.
Register spawned processes before returning handles
This returns handles to a WinProcess that is only in the handle manager, not in Instance.WinHelper.WinProcesses. Several existing and newly changed syscalls validate process handles through ValidProcessHandle, which also requires the PID to be present in WinProcesses, so a handle returned by NtCreateUserProcess is immediately rejected by paths like NtCreateThreadEx and NtQueryInformationProcess instead of behaving like other created/opened process handles.
Useful? React with 👍 / 👎.
| WriteSlot(Offset, GuestProcessId, Architecture, ImageName); | ||
| _ownSlot = i; |
There was a problem hiding this comment.
Clear the mailbox when reusing a session slot
When this overwrites a dead slot, only the process slot is cleared; the corresponding mailbox page is left with the previous owner's request/response sequence and payload. A new process starts with _lastHandledSequence == 0, so if the reused mailbox has a nonzero request sequence, TryTakeRequest treats that stale request as new and can replay an old read/write/create-thread operation against the new process before any parent sends a fresh request.
Useful? React with 👍 / 👎.
| if (BytesReadPtr != 0 && Instance.IsRegionMapped(BytesReadPtr, sizeof(ulong))) | ||
| Instance._emulator.WriteMemory(BytesReadPtr, (ulong)RemoteLength, 8); |
There was a problem hiding this comment.
Write the byte count using the guest pointer size
For a 32-bit guest reading memory from a spawned process, NumberOfBytesRead is a 4-byte SIZE_T*, but this branch requires 8 bytes to be mapped and writes 8 bytes. That means a valid 4-byte output slot is silently left unset if the following bytes are unmapped, or adjacent guest memory is overwritten when they are mapped; use the current PointerSize as in the write-memory path.
Useful? React with 👍 / 👎.
|
Will fix it later |
DPI awareness
The emulator gave every program a screen of 96 DPI. It also made the host window with no DPI awareness. A program that asks for a different DPI got the wrong screen size and a window that is not sharp.
The emulator now reads the DPI awareness of the program. It reads it from the application manifest in the image, from an external manifest file, or from the NtUserSetProcessDpiAwarenessContext system call.
user32 does not ask the kernel for most DPI values. It calculates them from a packed value in the CLIENTINFO block of the thread and in the window structure. The emulator writes this value into the two locations. If it does not, the program reads one DPI from user32 and a different DPI from the system calls.
The emulator gives the same awareness to the host window. An aware program gets a window in true pixels. An unaware program keeps the window that the desktop manager makes larger for it.
The emulator also sets the composited flag in the desktop data. Without this flag, user32 uses a different code path. That path gives 96 DPI to a program that is aware.
Guest process launch
A guest process could not start a different program. The emulator did not have the NtCreateUserProcess system call.
The emulator now starts a second Brovan for each new guest process. One guest process is one host process. The window manager, the Vulkan device, the scheduler and the caches are global to a process. Two guests in one emulator need a process identity in all of them.
The parent sends the image path, the command line and the directory to the child. It encodes these values with base64. Without base64, the host command line divides the values and joins them again. A value with a quotation mark or a space does not stay correct.
Session registry
The Brovan instances of one session share a table in a memory-mapped file. Each instance writes one row. An instance that must stop a different guest process writes a request into the row of that process. The owner of the row reads the request and stops itself. This is the only correct method. The memory of that process is in a different host process.
The emulator counts the rows before it starts a child. It permits a maximum of six guest processes in one session.
New options
--cwd <dir> sets the directory in which the program starts.--guest-cmdline <s> sets the command line of the program. The two options also accept a value in the form "base64:<value>".