diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index fe62a28042..04e4d8bcb3 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -6,6 +6,9 @@ permissions: jobs: build: runs-on: ubuntu-24.04 + strategy: + matrix: + abi: [arm64-v8a, armeabi] steps: - uses: actions/checkout@v6 - name: Install required packages @@ -19,18 +22,18 @@ jobs: uses: android-actions/setup-android@v3 with: cmdline-tools-version: 9123335 - packages: 'platforms;android-30 ndk;21.4.7075529' + packages: 'platforms;android-30 ndk;27.3.13750724' - name: Configure path to NDK - run: sed -i -e 's,NDK = .*,NDK = /usr/local/lib/android/sdk/ndk/21.4.7075529,g' sys/android/Makefile.src + run: sed -i -e 's,NDK = .*,NDK = /usr/local/lib/android/sdk/ndk/27.3.13750724,g' sys/android/Makefile.src - name: Build native Android application - run: cd sys/android && sh ./setup.sh && cd ../.. && make install + run: cd sys/android && sh ./setup.sh && cd ../.. && make install ABI=${{ matrix.abi }} - name: Build NetHack run: cd sys/android && sh gradlew build - uses: actions/upload-artifact@v5 with: - name: nethack-release-unsigned + name: nethack-release-unsigned-${{ matrix.abi }} path: sys/android/app/build/outputs/apk/release/app-release-unsigned.apk - uses: actions/upload-artifact@v5 with: - name: nethack-debug + name: nethack-debug-${{ matrix.abi }} path: sys/android/app/build/outputs/apk/debug/app-debug.apk diff --git a/sys/android/.gitignore b/sys/android/.gitignore index 6959d1aef3..8b7aecc5f5 100644 --- a/sys/android/.gitignore +++ b/sys/android/.gitignore @@ -1,6 +1,6 @@ bin -libs/* -assets/nethackdir +app/libs/ +app/assets/nethackdir/ gen .gradle build diff --git a/sys/android/Makefile.src b/sys/android/Makefile.src index e98bbbce2f..6abd99d153 100644 --- a/sys/android/Makefile.src +++ b/sys/android/Makefile.src @@ -87,30 +87,20 @@ SYSOBJ = ioctl.o androidmain.o unixtty.o androidunix.o # CC = arm-linux-gcc # # For cross-compiling with Android NDK -NDK = /path/to/android-ndk-r21e +NDK = /path/to/android-ndk-r27d NDK_TOOLCHAIN = $(NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin -# Choose an ABI -ABI = armeabi -#ABI = armeabi-v7a -#ABI = arm64-v8a -#ABI = x86 -#ABI = x86_64 +# Choose an ABI (normally set in the top-level Makefile) +ABI ?= arm64-v8a CC_armeabi = $(NDK_TOOLCHAIN)/armv7a-linux-androideabi30-clang -LD_armeabi = $(NDK_TOOLCHAIN)/arm-linux-androideabi-ld CC_armeabi-v7a = $(NDK_TOOLCHAIN)/armv7a-linux-androideabi30-clang -LD_armeabi-v7a = $(NDK_TOOLCHAIN)/arm-linux-androideabi-ld CC_arm64-v8a = $(NDK_TOOLCHAIN)/aarch64-linux-android30-clang -LD_arm64-v8a = $(NDK_TOOLCHAIN)/aarch64-linux-android-ld CC_x86 = $(NDK_TOOLCHAIN)/i686-linux-android30-clang -LD_x86 = $(NDK_TOOLCHAIN)/i686-linux-android-ld CC_x86_64 = $(NDK_TOOLCHAIN)/x86_64-linux-android30-clang -LD_x86_64 = $(NDK_TOOLCHAIN)/x86_64-linux-android-ld CC_VAR = CC_$(ABI) -LD_VAR = LD_$(ABI) CC := $($(CC_VAR)) LINK := $(CC) @@ -118,7 +108,13 @@ LINK := $(CC) # Compile normally CFLAGS = -DANDROID -O2 -g -Wno-format -fsigned-char -fPIC -I../include #CFLAGS = -DANDROID -DDEBUG -g -Wno-format -fsigned-char -fPIC -I../include -LFLAGS = -shared -Wl,--fix-cortex-a8 +LFLAGS_armeabi = -Wl,--fix-cortex-a8 +LFLAGS_armeabi-v7a = -Wl,--fix-cortex-a8 +# 16 KB page alignment, required by newer 64-bit Android devices +LFLAGS_arm64-v8a = -Wl,-z,max-page-size=16384 +LFLAGS_x86_64 = -Wl,-z,max-page-size=16384 +LFLAGS_VAR = LFLAGS_$(ABI) +LFLAGS = -shared $($(LFLAGS_VAR)) # LFLAGS = -L/usr/X11R6/lib # OR compile backwards compatible a.out format @@ -408,6 +404,7 @@ Sysunix: $(HOBJ) Makefile Sysandroid: $(HOBJ) Makefile @echo "Loading ..." + rm -rf ../sys/android/app/libs mkdir -p ../sys/android/app/libs/$(ABI) $(LINK) $(LFLAGS) -o ../sys/android/app/libs/$(ABI)/lib$(GAME).so $(HOBJ) $(LIBS) @touch Sysandroid diff --git a/sys/android/Makefile.top b/sys/android/Makefile.top index 20df4a4461..9ee70663cf 100644 --- a/sys/android/Makefile.top +++ b/sys/android/Makefile.top @@ -19,6 +19,13 @@ NHSROOT=. # make NetHack #PREFIX = /usr GAME = nethack + +# Target ABI for the native library: arm64-v8a, armeabi, armeabi-v7a, x86 +# or x86_64. Override with 'make install ABI=...'. The compiled data files +# depend on the target's word size, so the host utilities are built to match +# and one build of the data files only works with one ABI. +ABI = arm64-v8a +export ABI # GAME = nethack.prg #GAMEUID = games #GAMEGRP = bin diff --git a/sys/android/Makefile.utl b/sys/android/Makefile.utl index f11e3de35a..9d969799f9 100644 --- a/sys/android/Makefile.utl +++ b/sys/android/Makefile.utl @@ -92,8 +92,18 @@ NHSROOT=.. # flags for debugging: # CFLAGS = -g -I../include -CFLAGS = -DANDROID -m32 -Wno-format -fsigned-char -I../include -LFLAGS = -m32 +# The compiled data files must match the target's word size, so build the +# host utilities as 32-bit for 32-bit ABIs (needs gcc-multilib) and 64-bit +# otherwise. +ABI ?= arm64-v8a +HOSTBITS_armeabi = -m32 +HOSTBITS_armeabi-v7a = -m32 +HOSTBITS_x86 = -m32 +HOSTBITS_VAR = HOSTBITS_$(ABI) +HOSTBITS = $($(HOSTBITS_VAR)) + +CFLAGS = -DANDROID $(HOSTBITS) -Wno-format -fsigned-char -I../include +LFLAGS = $(HOSTBITS) LIBS = diff --git a/sys/android/README.md b/sys/android/README.md index ed554f8b5d..5dcf3a8f5c 100644 --- a/sys/android/README.md +++ b/sys/android/README.md @@ -19,7 +19,7 @@ work. If you're running Windows you're on your own. 1. `cd /path/to/android-sdk/tools/bin` 2. Update the sdk manager: `./sdkmanager --update`. If you get "NoClassDefFoundError" it's because you're not running JDK 8. Make sure the env variable `JAVA_HOME` points to JDK 8. 3. Install the platform tools: `./sdkmanager --install "platforms;android-30"` - 4. Install the NDK: `./sdkmanager --install "ndk;21.4.7075529"` + 4. Install the NDK: `./sdkmanager --install "ndk;27.3.13750724"` ## Build @@ -32,6 +32,16 @@ work. If you're running Windows you're on your own. 4. `cd ../..` 5. `make install` +This builds for `arm64-v8a`, which works on all current devices. Some newer +devices, e.g. the Galaxy Z Fold6 or Pixel 7 and later, can only run 64-bit code. +To build for another ABI, e.g. older 32-bit devices, pass it to make: +`make install ABI=armeabi-v7a`. The 32-bit ABIs need `gcc-multilib`. + +The compiled data files depend on the target's word size, so an APK only +supports the one ABI it was built for. Run `make spotless` before switching +ABI. Saved games from a 32-bit build can't be loaded by a 64-bit build, and +the reverse is also true. + ### Build the Android application 1. `cd /path/to/NetHack-Android/sys/android` diff --git a/sys/android/app/assets/ver b/sys/android/app/assets/ver index fe4afb0df8..e34885bbc6 100644 --- a/sys/android/app/assets/ver +++ b/sys/android/app/assets/ver @@ -1 +1 @@ -106 +107 diff --git a/sys/android/app/build.gradle b/sys/android/app/build.gradle index 815c6058c7..739df55dba 100644 --- a/sys/android/app/build.gradle +++ b/sys/android/app/build.gradle @@ -10,8 +10,8 @@ android { defaultConfig { minSdkVersion 7 targetSdkVersion 30 - versionName '3.6.7-1' - versionCode 3671 + versionName '3.6.7-2' + versionCode 3672 } compileOptions { diff --git a/sys/android/winandroid.c b/sys/android/winandroid.c index 5c6caa769f..7a6512bce0 100644 --- a/sys/android/winandroid.c +++ b/sys/android/winandroid.c @@ -173,6 +173,16 @@ static char* msghistory[32]; static int msghistory_idx; static int msghistory_idx0; +// Menu identifiers are often pointers, which don't fit in the int passed to +// Java on 64-bit devices. Keep them here and pass Java a 1-based index. +struct menu_idents { + winid wid; + anything *ids; + int n, size; + struct menu_idents *next; +}; +static struct menu_idents *menu_idents; + extern const char *status_fieldfmt[MAXBLSTATS]; // Need to separate conditions in order to color them properly enum bl_conditions { @@ -632,6 +642,17 @@ void and_display_nhwindow(winid wid, BOOLEAN_P blocking) // already been dismissed. void and_destroy_nhwindow(winid wid) { + struct menu_idents **pm, *m; + for(pm = &menu_idents; (m = *pm) != 0; pm = &m->next) + { + if(m->wid == wid) + { + *pm = m->next; + free(m->ids); + free(m); + break; + } + } JNICallV(jDestroyWindow, wid); } @@ -1145,8 +1166,24 @@ void and_display_file(const char *name, BOOLEAN_P complain) // before add_menu(). After calling start_menu() you may not // putstr() to the window. Only windows of type NHW_MENU may // be used for menus. +static struct menu_idents *get_menu_idents(winid wid) +{ + struct menu_idents *m; + for(m = menu_idents; m; m = m->next) + if(m->wid == wid) + return m; + m = (struct menu_idents*)alloc(sizeof(struct menu_idents)); + m->wid = wid; + m->ids = 0; + m->n = m->size = 0; + m->next = menu_idents; + menu_idents = m; + return m; +} + void and_start_menu(winid wid) { + get_menu_idents(wid)->n = 0; JNICallV(jStartMenu, wid); } @@ -1185,6 +1222,24 @@ void and_start_menu(winid wid) void and_add_menu(winid wid, int glyph, const ANY_P *ident, CHAR_P accelerator, CHAR_P groupacc, int attr, const char *str, BOOLEAN_P preselected) { int tile, color; + int id = 0; // 0 means not selectable + if(ident->a_void) + { + struct menu_idents *m = get_menu_idents(wid); + if(m->n == m->size) + { + anything *ids; + m->size = m->size ? m->size * 2 : 32; + ids = (anything*)alloc(sizeof(anything) * m->size); + if(m->n) + memcpy(ids, m->ids, sizeof(anything) * m->n); + free(m->ids); + m->ids = ids; + } + m->ids[m->n++] = *ident; + id = m->n; + } + if(glyph == NO_GLYPH) tile = -1; else @@ -1202,7 +1257,7 @@ void and_add_menu(winid wid, int glyph, const ANY_P *ident, CHAR_P accelerator, attr = 1<a_int, (int)accelerator, (int)groupacc, attr, jstr, (int)preselected, color); + JNICallV(jAddMenu, wid, tile, id, (int)accelerator, (int)groupacc, attr, jstr, (int)preselected, color); destroy_jobject(jstr); } @@ -1272,11 +1327,16 @@ int and_select_menu_r(winid wid, int how, MENU_ITEM_P **selected, int reentry) { n >>= 1; + struct menu_idents *m = get_menu_idents(wid); q = p = (*jEnv)->GetIntArrayElements(jEnv, a, 0); *selected = (MENU_ITEM_P*)malloc(sizeof(MENU_ITEM_P) * n); for(i = 0; i < n; i++) { - (*selected)[i].item.a_int = *p++; + int id = *p++; + if(id > 0 && id <= m->n) + (*selected)[i].item = m->ids[id - 1]; + else + (*selected)[i].item = zeroany; (*selected)[i].count = *p++; } (*jEnv)->ReleaseIntArrayElements(jEnv, a, q, 0); @@ -1575,9 +1635,9 @@ char and_yn_function(const char *question, const char *choices, CHAR_P def) if(choices) { nChoices = strlen(choices); - esc = (int)index(choices, '\033'); - if(esc) - esc -= (int)choices; + const char *pesc = index(choices, '\033'); + if(pesc) + esc = (int)(pesc - choices); else esc = -1; }