Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions .github/workflows/build-proton.yml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,27 @@ jobs:
bash build-scripts/build-step-arm64ec.sh --build-sysvshm
fi

- name: Validate all patches
run: |
echo "Checking all patch files for validity..."
FAILED=0
for patch in android/patches/*.patch; do
if ! git apply --check "$patch" 2>/dev/null; then
echo "FAIL: $patch"
git apply --check "$patch" 2>&1 | head -5
FAILED=1
else
echo " OK: $patch"
fi
done
if [ "$FAILED" -eq 1 ]; then
echo ""
echo "Some patches are invalid or do not apply cleanly."
echo "Check for missing trailing newlines or context mismatches."
exit 1
fi
echo "All patches valid."

- name: Configure build for ${{ matrix.arch }}
run: |
if [ "${{ matrix.arch }}" == "x86_64" ]; then
Expand All @@ -144,6 +165,61 @@ jobs:
bash build-scripts/build-step-arm64ec.sh --install
fi

- name: Verify build output contains critical PE binaries
run: |
if [ "${{ matrix.arch }}" == "x86_64" ]; then
OUTPUT_DIR="$HOME/compiled-files-x86_64"
PE_DIR="$OUTPUT_DIR/lib/wine/x86_64-windows"
else
OUTPUT_DIR="$HOME/compiled-files-aarch64"
PE_DIR="$OUTPUT_DIR/lib/wine/aarch64-windows"
fi

MISSING=0
for f in \
"$PE_DIR/wineboot.exe" \
"$PE_DIR/start.exe" \
"$PE_DIR/explorer.exe" \
"$PE_DIR/winedevice.exe" \
"$PE_DIR/kernel32.dll" \
"$PE_DIR/ntdll.dll" \
"$PE_DIR/user32.dll" \
"$PE_DIR/gdi32.dll" \
"$PE_DIR/advapi32.dll" \
"$PE_DIR/ws2_32.dll" \
"$PE_DIR/shell32.dll" \
"$PE_DIR/msvcrt.dll" \
"$OUTPUT_DIR/bin/wine" \
"$OUTPUT_DIR/bin/wineserver"
do
if [ ! -f "$f" ]; then
echo "MISSING: $f"
MISSING=1
fi
done

if [ "$MISSING" -eq 1 ]; then
echo ""
echo "Build output is incomplete — critical PE binaries are missing."
echo ""
echo "=== All .exe files in $PE_DIR ==="
ls "$PE_DIR"/*.exe 2>/dev/null || echo "(none)"
echo ""
echo "=== All .dll files in $PE_DIR ==="
ls "$PE_DIR"/*.dll 2>/dev/null | head -30 || echo "(none)"
echo ""
echo "=== Contents of install dir ==="
ls "$OUTPUT_DIR/" 2>/dev/null
echo ""
echo "=== Contents of bin/ ==="
ls "$OUTPUT_DIR/bin/" 2>/dev/null || echo "(empty or missing)"
exit 1
fi

EXE_COUNT=$(ls "$PE_DIR"/*.exe 2>/dev/null | wc -l)
DLL_COUNT=$(ls "$PE_DIR"/*.dll 2>/dev/null | wc -l)
echo "Build verified: $EXE_COUNT .exe files, $DLL_COUNT .dll files in $PE_DIR"

- name: Download prefixPack.txz for ${{ matrix.arch }}
run: |
if [ "${{ matrix.arch }}" == "x86_64" ]; then
Expand Down
4 changes: 2 additions & 2 deletions android/patches/android_network.patch
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ index 158a39e..35cab14 100644
static BOOL have_ethernet_iface;

static struct if_entry *find_entry_from_index( UINT index )
@@ -309,6 +311,16 @@ static struct if_entry *add_entry( UINT index, const char *name )
@@ -309,6 +311,15 @@ static struct if_entry *add_entry( UINT index, const char *name )
free( entry );
return NULL;
}
Expand All @@ -296,7 +296,7 @@ index 158a39e..35cab14 100644
+ entry->if_type = MIB_IF_TYPE_PPP;
+#endif

if_get_physical( name, &entry->if_type, &entry->if_phys_addr );
- if_get_physical( name, &entry->if_type, &entry->if_phys_addr );

@@ -330,9 +342,35 @@ static struct if_entry *add_entry( UINT index, const char *name )

Expand Down
19 changes: 19 additions & 0 deletions android/patches/dlls_ntdll_unix_loader_c_steamclient.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
diff --git a/dlls/ntdll/unix/loader.c b/dlls/ntdll/unix/loader.c
index 2f978520af6..b07b6a980b2 100644
--- a/dlls/ntdll/unix/loader.c
+++ b/dlls/ntdll/unix/loader.c
@@ -1184,7 +1184,14 @@ static NTSTATUS steamclient_setup_trampolines( void *args )
int i;

if (noexec_cached == -1)
+ {
noexec_cached = (wsne = getenv("WINESTEAMNOEXEC")) && atoi(wsne);
+#ifdef __ANDROID__
+ /* W+X mprotect silently fails on Android 10+ (W^X enforcement).
+ * Default to noexec mode unless the caller has explicitly opted out. */
+ if (!noexec_cached) noexec_cached = 1;
+#endif
+ }

virtual_get_system_info( &info, !!NtCurrentTeb()->WowTebOffset );
page_mask = info.PageSize - 1;
49 changes: 49 additions & 0 deletions android/patches/dlls_ntdll_unix_uffd_tmp_defs_h.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
diff --git a/dlls/ntdll/unix/uffd_tmp_defs.h b/dlls/ntdll/unix/uffd_tmp_defs.h
index d9f7a286c58..25114732d2a 100644
--- a/dlls/ntdll/unix/uffd_tmp_defs.h
+++ b/dlls/ntdll/unix/uffd_tmp_defs.h
@@ -14,6 +14,44 @@
#define UFFD_FEATURE_WP_ASYNC (1<<15)
#endif

+/* Fallback definitions for the userfaultfd write-protect API (Linux 5.7).
+ * Android NDK kernel headers older than NDK r26 (kernel 5.10) lack these. */
+#ifndef UFFDIO_REGISTER_MODE_WP
+
+# ifndef UFFDIO
+# define UFFDIO 0xaa
+# endif
+
+/* uffdio_range / uffdio_register exist since kernel 4.3 but may be absent
+ * from very old or stripped NDK sysroots. */
+# ifndef UFFDIO_REGISTER
+struct uffdio_range {
+ __u64 start;
+ __u64 len;
+};
+struct uffdio_register {
+ struct uffdio_range range;
+ __u64 mode;
+ __u64 ioctls;
+};
+# define _UFFDIO_REGISTER 0x00
+# define UFFDIO_REGISTER _IOWR(UFFDIO, _UFFDIO_REGISTER, struct uffdio_register)
+# define UFFDIO_REGISTER_MODE_MISSING ((__u64)1<<0)
+# endif /* UFFDIO_REGISTER */
+
+/* write-protect structs and ioctls — only in kernel 5.7+ */
+struct uffdio_writeprotect {
+ struct uffdio_range range;
+ __u64 mode;
+};
+# define _UFFDIO_WRITEPROTECT 0x06
+# define UFFDIO_WRITEPROTECT _IOWR(UFFDIO, _UFFDIO_WRITEPROTECT, struct uffdio_writeprotect)
+# define UFFDIO_REGISTER_MODE_WP ((__u64)1<<1)
+# define UFFDIO_WRITEPROTECT_MODE_WP ((__u64)1<<0)
+# define UFFDIO_WRITEPROTECT_MODE_DONTWAKE ((__u64)1<<1)
+
+#endif /* UFFDIO_REGISTER_MODE_WP */
+
#ifndef PAGEMAP_SCAN
/* Pagemap ioctl */
#define PAGEMAP_SCAN _IOWR('f', 16, struct pm_scan_arg)
27 changes: 27 additions & 0 deletions android/patches/dlls_ntdll_unix_virtual_c_android.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
diff --git a/dlls/ntdll/unix/virtual.c b/dlls/ntdll/unix/virtual.c
index 7a70299940059..24ca15347573990 100644
--- a/dlls/ntdll/unix/virtual.c
+++ b/dlls/ntdll/unix/virtual.c
@@ -6997,6 +6997,22 @@ NTSTATUS WINAPI NtReadVirtualMemory( HANDLE process, const void *addr, void *buf

if (ret == -1)
{
+#ifdef __ANDROID__
+ if (errno == EACCES || errno == EPERM)
+ {
+ /* Android SELinux blocks process_vm_readv between processes.
+ * Fall back to the wineserver-mediated path. */
+ SERVER_START_REQ( read_process_memory )
+ {
+ req->handle = wine_server_obj_handle( process );
+ req->addr = wine_server_client_ptr( addr );
+ wine_server_set_reply( req, buffer, size );
+ if ((status = wine_server_call( req ))) size = 0;
+ }
+ SERVER_END_REQ;
+ goto done;
+ }
+#endif
status = errno == ESRCH ? STATUS_PARTIAL_COPY : errno_to_status( errno );
size = 0;
}
6 changes: 3 additions & 3 deletions android/patches/dlls_user32_makefile_in.patch
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ index 69e1fa2..d629905 100644
EXTRADEFS = -D_USER32_
MODULE = user32.dll
IMPORTLIB = user32
-IMPORTS = $(PNG_PE_LIBS) gdi32 sechost advapi32 kernelbase win32u uuid
+IMPORTS = $(PNG_PE_LIBS) gdi32 sechost advapi32 kernelbase win32u uuid ws2_32
IMPORTS = $(PNG_PE_LIBS) gdi32 sechost advapi32 kernelbase win32u uuid
EXTRAINCL = $(PNG_PE_CFLAGS)
DELAYIMPORTS = imm32 combase
-DELAYIMPORTS = imm32 combase
+DELAYIMPORTS = imm32 combase ws2_32

139 changes: 139 additions & 0 deletions android/patches/dlls_winex11_drv_keyboard_c.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
diff --git a/dlls/winex11.drv/keyboard.c b/dlls/winex11.drv/keyboard.c
index 0456d819bc8..c8a3b11fc0b 100644
--- a/dlls/winex11.drv/keyboard.c
+++ b/dlls/winex11.drv/keyboard.c
@@ -1490,10 +1490,17 @@ X11DRV_KEYBOARD_DetectLayout( Display *display )
}

memset( ckey, 0, sizeof(ckey) );
+ XKeyEvent det;
+ memset(&det, 0, sizeof(det));
+ det.display = display;
+ det.type = KeyPress;
for (keyc = min_keycode; keyc <= max_keycode; keyc++) {
/* get data for keycode from X server */
+ det.keycode = (KeyCode)keyc;
for (i = 0; i < syms; i++) {
- if (!(keysym = XkbKeycodeToKeysym( display, keyc, 0, i ))) continue;
+ keysym = XkbKeycodeToKeysym( display, keyc, 0, i );
+ if (!keysym) { det.state = (i == 1) ? ShiftMask : 0; keysym = XLookupKeysym(&det, i); }
+ if (!keysym) continue;
ckey[keyc][i] = keysym_to_char(keysym);
if (TRACE_ON(keyboard))
{
@@ -1624,16 +1631,20 @@ void X11DRV_InitKeyboard( Display *display )
int k;

for (k = 0; k < keysyms_per_keycode; k += 1)
- if (XkbKeycodeToKeysym( display, *kcp, 0, k ) == XK_Num_Lock)
+ {
+ KeySym ksym = XkbKeycodeToKeysym( display, *kcp, 0, k );
+ if (!ksym) { XKeyEvent tmpev = {0}; tmpev.display = display; tmpev.keycode = *kcp; tmpev.type = KeyPress; ksym = XLookupKeysym(&tmpev, k); }
+ if (ksym == XK_Num_Lock)
{
NumLockMask = 1 << i;
TRACE_(key)("NumLockMask is %x\n", NumLockMask);
}
- else if (XkbKeycodeToKeysym( display, *kcp, 0, k ) == XK_Scroll_Lock)
+ else if (ksym == XK_Scroll_Lock)
{
ScrollLockMask = 1 << i;
TRACE_(key)("ScrollLockMask is %x\n", ScrollLockMask);
}
+ }
}
}
XFreeModifiermap(mmp);
@@ -1682,6 +1693,7 @@ void X11DRV_InitKeyboard( Display *display )
int maxlen=0,maxval=-1,ok;
for (i=0; i<syms; i++) {
keysym = XkbKeycodeToKeysym( display, keyc, 0, i );
+ if (!keysym) keysym = XLookupKeysym(&e2, i);
ckey[i] = keysym_to_char(keysym);
}
/* find key with longest match streak */
@@ -1815,17 +1827,54 @@ void X11DRV_InitKeyboard( Display *display )
} /* for */
#undef VKEY_IF_NOT_USED

- /* If some keys still lack scancodes, assign some arbitrary ones to them now */
+ /* Assign QWERTY scancodes as fallback for any keycode that has a vkey
+ * but was not matched in the layout table (e.g. embedded X servers
+ * where XkbKeycodeToKeysym returns 0 for letter keys). */
+ for (keyc = min_keycode; keyc <= max_keycode; keyc++)
+ {
+ if (keyc2scan[keyc])
+ continue;
+ if (!keyc2vkey[keyc])
+ continue;
+ e2.keycode = (KeyCode)keyc;
+ keysym = XLookupKeysym(&e2, 0);
+ if (!keysym)
+ keysym = XkbKeycodeToKeysym( display, keyc, 0, 0 );
+ if (!keysym)
+ continue;
+ if (keysym >= 'a' && keysym <= 'z')
+ {
+ static const WORD letter_scan[] = {
+ 0x1E,0x30,0x2E,0x20,0x12,0x21,0x22,0x23,0x17,0x24,0x25,0x26,0x32,
+ 0x31,0x18,0x19,0x10,0x13,0x1F,0x14,0x16,0x2F,0x11,0x2D,0x15,0x2C
+ };
+ keyc2scan[keyc] = letter_scan[keysym - 'a'];
+ TRACE_(key)("assigning QWERTY scancode %02x to keycode %u (letter '%c')\n",
+ keyc2scan[keyc], keyc, (char)keysym);
+ }
+ else if (keysym >= '1' && keysym <= '9')
+ {
+ keyc2scan[keyc] = keysym - '1' + 0x02;
+ TRACE_(key)("assigning QWERTY scancode %02x to keycode %u (digit '%c')\n",
+ keyc2scan[keyc], keyc, (char)keysym);
+ }
+ else if (keysym == '0')
+ {
+ keyc2scan[keyc] = 0x0B;
+ TRACE_(key)("assigning QWERTY scancode %02x to keycode %u (digit '0')\n",
+ keyc2scan[keyc], keyc);
+ }
+ else
+ {
+ /* Fall back to sequential scancodes for remaining unmapped keys */
+ const char *ksname = XKeysymToString(keysym);
+ if (!ksname) ksname = "NoSymbol";
+ TRACE_(key)("no QWERTY scancode for keycode %u (%s)\n", keyc, ksname);
+ }
+ }
+ /* Assign sequential scancodes to anything still unmapped */
for (scan = 0x60, keyc = min_keycode; keyc <= max_keycode; keyc++)
if (keyc2vkey[keyc]&&!keyc2scan[keyc]) {
- const char *ksname;
- keysym = XkbKeycodeToKeysym( display, keyc, 0, 0 );
- ksname = XKeysymToString(keysym);
- if (!ksname) ksname = "NoSymbol";
-
- /* should make sure the scancode is unassigned here, but >=0x60 currently always is */
-
- TRACE_(key)("assigning scancode %02x to unidentified keycode %u (%s)\n",scan,keyc,ksname);
keyc2scan[keyc]=scan++;
}

@@ -1941,7 +1990,11 @@ SHORT X11DRV_VkKeyScanEx( WCHAR wChar, HKL hkl )
}

for (index = 0; index < 4; index++) /* find shift state */
- if (XkbKeycodeToKeysym( display, keycode, 0, index ) == keysym) break;
+ {
+ KeySym ks = XkbKeycodeToKeysym( display, keycode, 0, index );
+ if (!ks) { XKeyEvent tmpev = {0}; tmpev.display = display; tmpev.keycode = keycode; tmpev.type = KeyPress; ks = XLookupKeysym(&tmpev, index); }
+ if (ks == keysym) break;
+ }

pthread_mutex_unlock( &kbd_mutex );

@@ -2192,6 +2245,7 @@ INT X11DRV_GetKeyNameText( LONG lParam, LPWSTR lpBuffer, INT nSize )

keyc = (KeyCode) keyi;
keys = XkbKeycodeToKeysym( display, keyc, 0, 0 );
+ if (!keys) { XKeyEvent tmpev = {0}; tmpev.display = display; tmpev.keycode = keyc; tmpev.type = KeyPress; keys = XLookupKeysym(&tmpev, 0); }
name = XKeysymToString(keys);

if (name && (vkey == VK_SHIFT || vkey == VK_CONTROL || vkey == VK_MENU))
Loading