Skip to content

Add Android host support (arm64, GDI, Vulkan) - #12

Merged
AdvDebug merged 3 commits into
mainfrom
brovan-android
Aug 2, 2026
Merged

Add Android host support (arm64, GDI, Vulkan)#12
AdvDebug merged 3 commits into
mainfrom
brovan-android

Conversation

@AdvDebug

@AdvDebug AdvDebug commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Brovan now runs on Android as a third host alongside Windows and Linux.

New Brovan android embedding layer. The emulator becomes a NativeAOT shared library driven by a host app rather than a process with a Main:

  • BrovanAndroidApi - exported C ABI (init, surface, start, input injection, window enumeration, debugger commands).
  • AndroidWinManager - IDisplayConnection over ANativeWindow, plus IGdiRenderSupport.
  • AndroidGdiSurface - software rasteriser (lines, rects, ellipses, polygons) into a per-guest-window backbuffer posted via ANativeWindow_lock/ unlockAndPost, white background.
  • AndroidVulkanWsi and a generator branch - VK_KHR_android_surface instead of Win32/Xcb.
  • AndroidInput, AndroidLog, AndroidHost, AndroidGuestWindows, JNI shim, Java bindings.
  • Launcher app: Material 3 library, in-app program import via SAF, settings, on-screen joystick/D-pad/touchpad controls, opt-in developer console wired to the debugger.
  • build-apk.sh - Unicorn cross-build, linux-bionic-arm64 publish, bundled OpenSSL, APK assembly.

Changes to shared emulator code:

  • Case-insensitive shipped-DLL resolution in GetWindowsLibPath. Import tables say KERNEL32.dll, System32 ships kernel32.dll; broken on any case-sensitive host, and it surfaced as a guest loading zero modules.
  • GlobalPropertiesToRemove on the generator ProjectReference. Target-shaped properties leaked into the analyzer, csc silently refused to load it (CS8034 is only a warning), and every source generator emitted nothing.
  • GdiPrimitive.Hwnd. The four EnqueueGdi* helpers already had the guest HWND and dropped it, making per-window compositing impossible.
  • Program.SplitCommandLine widened to internal for embedders. Windows and Linux behaviour is unchanged; the new paths are gated on the RID or !IsWindows.

Brovan now runs on Android as a third host alongside Windows and Linux.

New Brovan android embedding layer. The emulator becomes a NativeAOT shared
library driven by a host app rather than a process with a Main:

- BrovanAndroidApi - exported C ABI (init, surface, start, input injection,
  window enumeration, debugger commands).
- AndroidWinManager - IDisplayConnection over ANativeWindow, plus
  IGdiRenderSupport.
- AndroidGdiSurface - software rasteriser (lines, rects, ellipses, polygons)
  into a per-guest-window backbuffer posted via ANativeWindow_lock/
  unlockAndPost, white background.
- AndroidVulkanWsi and a generator branch - VK_KHR_android_surface instead of
  Win32/Xcb.
- AndroidInput, AndroidLog, AndroidHost, AndroidGuestWindows, JNI shim, Java
  bindings.
- Launcher app: Material 3 library, in-app program import via SAF, settings,
  on-screen joystick/D-pad/touchpad controls, opt-in developer console wired to
  the debugger.
- build-apk.sh - Unicorn cross-build, linux-bionic-arm64 publish, bundled
  OpenSSL, APK assembly.

Changes to shared emulator code:

- Case-insensitive shipped-DLL resolution in GetWindowsLibPath. Import tables
  say KERNEL32.dll, System32 ships kernel32.dll; broken on any case-sensitive
  host, and it surfaced as a guest loading zero modules.
- GlobalPropertiesToRemove on the generator ProjectReference. Target-shaped
  properties leaked into the analyzer, csc silently refused to load it (CS8034
  is only a warning), and every source generator emitted nothing.
- GdiPrimitive.Hwnd. The four EnqueueGdi* helpers already had the guest HWND and
  dropped it, making per-window compositing impossible.
- Program.SplitCommandLine widened to internal for embedders.
Windows and Linux behaviour is unchanged; the new paths are gated on the RID or
!IsWindows.
@AdvDebug

AdvDebug commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

@claude review

@claude

claude Bot commented Aug 2, 2026

Copy link
Copy Markdown

Claude finished @AdvDebug's task in 3m 43s —— View job


Review: Android host support (arm64, GDI, Vulkan)

  • Gather context (PR body, changed files list)
  • Read repository CLAUDE.md for conventions (none found)
  • Review shared emulator code changes (WindowManager, GeneralHelper, WinSyscallsHelper, Program.cs, csproj, generator)
  • Review Android host layer (C#: BrovanAndroidApi, AndroidWinManager, AndroidGdiSurface, AndroidVulkanWsi, AndroidInput, AndroidHost, AndroidGuestWindows, AndroidLog, AndroidNative)
  • Review JNI shim, launcher app, build scripts / CI
  • Post final review

This is a big, well-organized piece of work — the case-sensitivity fix, the GlobalPropertiesToRemove generator fix, and the GdiPrimitive.Hwnd plumbing are all sharp, well-motivated fixes with good explanatory comments. One issue below looks like it would block the Android host from actually running a guest, so flagging it as the main thing to look at before merge.

Likely blocker: native library resolver never handles Android

Brovan/Core/Emulation/UnicornBinding/Native.cs:156-190 (NativeLibraryResolver.Resolve, not touched by this PR) only special-cases GeneralHelper.IsWindows and GeneralHelper.IsLinux when resolving the "unicorn" and "vulkan-1.dll" P/Invoke library names:

if (string.Equals(LibName, "unicorn", StringComparison.OrdinalIgnoreCase))
{
    if (GeneralHelper.IsWindows) return NativeLibrary.Load("unicorn.dll", Asm, SearchPath);
    if (GeneralHelper.IsLinux) return NativeLibrary.Load("libunicorn.so", Asm, SearchPath);
    throw new PlatformNotSupportedException("Brovan currently supports resolving unicorn for Windows and Linux only.");
}

if (string.Equals(LibName, "vulkan-1.dll", StringComparison.OrdinalIgnoreCase) && GeneralHelper.IsLinux)
{
    if (NativeLibrary.TryLoad("libvulkan.so.1", out IntPtr handle)) return handle;
    if (NativeLibrary.TryLoad("libvulkan.so", out handle)) return handle;
}

RuntimeInformation.IsOSPlatform(OSPlatform.Linux) (what GeneralHelper.IsLinux wraps) is false on Android — that's exactly why this PR introduces a separate Brovan.Android.AndroidHost.IsActive flag and consistently OR's it alongside GeneralHelper.IsLinux everywhere else that used to be Linux-only (WindowManager.cs's EnsureFromX11/CreateFactory branches, and the generator's VkInstanceCreateInfo/vkCreateAndroidSurfaceKHR branches). This one call site wasn't updated to match.

The practical effect: on Android, the very first P/Invoke into uc_* (Unicorn — the CPU backend, used unconditionally) goes through this resolver, hits neither IsWindows nor IsLinux, and throws PlatformNotSupportedException, which would abort guest execution immediately. The same gap applies to vkCreateAndroidSurfaceKHR in Brovan/Android/AndroidVulkanWsi.cs:10 (DllImport("vulkan-1.dll", ...)) — the resolver returns IntPtr.Zero silently for Android since the block is gated on IsLinux.

Since build-apk.sh already bundles libunicorn.so into jniLibs/arm64-v8a (Brovan/Android/build-apk.sh:103), and Android's dynamic linker exposes bundled native libs by soname, the fix is likely just extending the resolver:

if (string.Equals(LibName, "unicorn", StringComparison.OrdinalIgnoreCase))
{
    if (GeneralHelper.IsWindows) return NativeLibrary.Load("unicorn.dll", Asm, SearchPath);
    if (GeneralHelper.IsLinux || Brovan.Android.AndroidHost.IsActive) return NativeLibrary.Load("libunicorn.so", Asm, SearchPath);
    throw new PlatformNotSupportedException(...);
}

if (string.Equals(LibName, "vulkan-1.dll", StringComparison.OrdinalIgnoreCase) && (GeneralHelper.IsLinux || Brovan.Android.AndroidHost.IsActive))
{
    if (NativeLibrary.TryLoad("libvulkan.so.1", out IntPtr handle)) return handle;
    if (NativeLibrary.TryLoad("libvulkan.so", out handle)) return handle;
}

Worth double-checking on-device (or in CI) that a guest actually runs to confirm this either isn't hit for some reason I'm missing, or gets fixed.

Fix this →

Minor / worth a second look

  • Brovan/Brovan.csproj:23 — the UsingTask AssemblyFile path separator was changed from $(MSBuildToolsPath)/Microsoft.Build.Tasks.Core.dll to $(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll. This is unrelated to the Android work and looks like incidental IDE reformatting (the surrounding CDATA block was also re-wrapped with no logic change). It's probably harmless in practice since DisableGuardCF only actually runs when $(TargetDir)$(TargetName).exe exists (i.e., Windows-targeted builds), but a backslash-only path is non-portable if this project is ever cross-compiled to a Windows RID from a Linux/macOS host — worth reverting to / (or using $([MSBuild]::NormalizePath(...))) since MSBuild normalizes forward slashes on all platforms but not backslashes.
  • Brovan/GeneralHelper.cs's new ResolveShippedLibraryCase/ShippedLibraryIndex cache is keyed per-directory and never invalidated — fine for this use case (WindowsLibs/System32 contents are static after install) but worth a one-line comment if that assumption ever needs revisiting.
  • Everything else — AndroidHost, AndroidWinManager/AndroidGdiSurface (software rasterizer + ANativeWindow_lock/unlockAndPost), AndroidInput's Win32 message synthesis, AndroidLog's stdout/stderr pipe redirection, the JNI shim's thread attach/detach handling, and the Java launcher/player activities — all read as careful, correct, well-commented ports of the existing Windows/Linux abstractions to Android's constraints (bionic dlopen, sandboxed /data/data paths, no libssl, Surface lifecycle). No other correctness issues found.

@AdvDebug

AdvDebug commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

it worked fine before that and i didn't notice that this was missing in the first place, so it seemed like RuntimeInformation.IsOSPlatform(OSPlatform.Linux) already returned true in android, confirmed it too. so i think it is better to revert that vulkan android check change and just write a summary comment on IsLinux.

@AdvDebug
AdvDebug merged commit 2a181d1 into main Aug 2, 2026
1 check passed
@AdvDebug
AdvDebug deleted the brovan-android branch August 2, 2026 17:03
@AdvDebug

AdvDebug commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

It isn't really usable right now because you will have to transfer files using ADB for Registry and libraries, but i will add them soon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant