fix(android): resolve black screen, modernize build with vcpkg and LuaJIT (#1673)

This commit is contained in:
Kizuno18 2026-04-15 22:39:47 -03:00 committed by GitHub
parent e8f527efab
commit cea491b614
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 3916 additions and 39 deletions

View file

@ -2,11 +2,19 @@
/.github/
/.idea/
/.vs/
/android/
/build/
/cmake-build*
/build-*/
/cmake-build*/
/vc17/
Dockerfile
/vcpkg_installed/
/android/app/build/
/android/app/.cxx/
/android/.gradle/
Dockerfile*
*.dll
*.exe
*.pdb
*.obj
*.lib
*.ilk
*.exp

10
.gitignore vendored
View file

@ -317,3 +317,13 @@ google-services.json
# Android libraries
android/app/libs
android/app/src/main/assets
# Android build artifacts (generated by build_luajit_android.sh and setup_android_deps.sh)
android/app/libs/lib/
android/app/libs/include/
android/app/src/main/assets/data.zip
android/app/build/
android/app/.cxx/
android/.gradle/
android/build/
luajit-src/

50
Dockerfile.android Normal file
View file

@ -0,0 +1,50 @@
# Use a standard Android SDK image
FROM thyrlian/android-sdk:10.0 AS builder
USER root
RUN apt-get update && apt-get install -y --no-install-recommends \
git cmake curl zip unzip tar automake ca-certificates build-essential \
libncurses5-dev libssl-dev python3 python3-pip ninja-build nasm gperf \
pkg-config libtool bison flex \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
# Install vcpkg with FULL history
WORKDIR /opt
RUN git clone https://github.com/microsoft/vcpkg.git \
&& cd vcpkg && ./bootstrap-vcpkg.sh
ENV VCPKG_ROOT=/opt/vcpkg
ENV ANDROID_HOME=/opt/android-sdk
ENV ANDROID_NDK_HOME=/opt/android-sdk/ndk/29.0.13599879
ENV VCPKG_FORCE_SYSTEM_BINARIES=1
# Install NDK and stable CMake via sdkmanager
RUN yes | sdkmanager --sdk_root=$ANDROID_HOME "ndk;29.0.13599879" "cmake;3.22.1"
# Set workdir for the project
WORKDIR /otclient
COPY . /otclient
# Install gcc-multilib for 32-bit LuaJIT cross-compilation
RUN apt-get update && apt-get install -y --no-install-recommends gcc-multilib \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
# Clone LuaJIT source and cross-compile for all Android ABIs
RUN git clone --depth 1 https://github.com/LuaJIT/LuaJIT.git /otclient/luajit-src \
&& cd /otclient/luajit-src && git fetch --depth 1 origin d0e88930ddde28ff662503f9f20facf34f7265aa \
&& git checkout d0e88930ddde28ff662503f9f20facf34f7265aa
RUN cd /otclient && chmod +x build_luajit_android.sh && ./build_luajit_android.sh
# Generate data.zip asset bundle
RUN mkdir -p /otclient/android/app/src/main/assets \
&& cd /otclient && zip -r android/app/src/main/assets/data.zip data mods modules init.lua otclientrc.lua
# Build APK inside the image
RUN cd /otclient/android \
&& tr -d '\r' < gradlew > gradlew_unix \
&& chmod +x gradlew_unix \
&& ./gradlew_unix assembleRelease
# Minimal stage to extract APK
FROM scratch AS apk-artifacts
COPY --from=builder /otclient/android/app/build/outputs/apk/release/ /

127
android/BUILDING.md Normal file
View file

@ -0,0 +1,127 @@
# Building OTClient for Android
## Prerequisites
- **Linux host** (Ubuntu 22.04+ or WSL2 recommended)
- **Java 17** (`sudo apt install openjdk-17-jdk`)
- **Android SDK** with NDK 29.0.13599879 and CMake 3.22.1
- **vcpkg** (cloned and bootstrapped)
- **gcc-multilib** for 32-bit LuaJIT cross-compilation (`sudo apt install gcc-multilib g++-multilib`)
### Environment variables
```bash
export ANDROID_HOME=/path/to/android-sdk
export ANDROID_NDK_HOME=$ANDROID_HOME/ndk/29.0.13599879
export VCPKG_ROOT=/path/to/vcpkg
export JAVA_HOME=/usr # or path to JDK 17
```
## Quick build (automated)
```bash
# 1. Run the setup script (builds LuaJIT, installs vcpkg deps, generates data.zip)
./setup_android_deps.sh
# 2. Build the APK
cd android
chmod +x gradlew
./gradlew assembleRelease
```
The APK will be at `android/app/build/outputs/apk/release/app-release.apk`.
## Step-by-step build
### Step 1: Build LuaJIT for Android
LuaJIT must be cross-compiled separately because the vcpkg LuaJIT port cannot cross-compile for Android from an x64 host ([LuaJIT Issue #664](https://github.com/LuaJIT/LuaJIT/issues/664)).
```bash
# Clone LuaJIT source (pinned to vcpkg-compatible commit)
git clone https://github.com/LuaJIT/LuaJIT.git luajit-src
cd luajit-src && git checkout d0e88930ddde28ff662503f9f20facf34f7265aa && cd ..
# Cross-compile for all 4 ABIs
./build_luajit_android.sh
```
This produces static libraries in `android/app/libs/lib/{ABI}/libluajit-5.1.a` and headers in `android/app/libs/include/luajit/`.
#### How it works
LuaJIT's build is a two-stage process: host tools (`minilua`, `buildvm`) run on your machine to generate target-specific code, then the NDK cross-compiler compiles the library. Key flags:
| Flag | Why |
|------|-----|
| `HOST_CC="gcc -m32"` | Required for 32-bit targets (armeabi-v7a, x86) — ensures matching pointer sizes between host tools and target ([#664](https://github.com/LuaJIT/LuaJIT/issues/664)) |
| `-DLUAJIT_UNWIND_EXTERNAL` | Required for Android NDK — prevents conflict between LuaJIT's internal unwinder and C++ exception handling |
| `-fno-stack-protector` | Avoids linker issues with NDK's stack protector implementation |
| `-fPIC` | Required for x86 ABI — static lib linked into a shared `.so` |
| `amalg` | Single-file build for better optimization |
| `TARGET_SYS=Linux` | Never use `TARGET_SYS=Android` — causes crashes ([#440](https://github.com/LuaJIT/LuaJIT/issues/440)) |
### Step 2: Generate data.zip
```bash
# Bundle game assets into data.zip for the APK
mkdir -p android/app/src/main/assets
cd /path/to/otclient
python3 -c "
import zipfile, os
with zipfile.ZipFile('android/app/src/main/assets/data.zip', 'w', zipfile.ZIP_DEFLATED) as zf:
for item in ['data', 'mods', 'modules']:
for root, dirs, files in os.walk(item):
for f in files:
zf.write(os.path.join(root, f))
for f in ['init.lua', 'otclientrc.lua']:
if os.path.exists(f):
zf.write(f)
"
```
### Step 3: Build with Gradle
```bash
cd android
chmod +x gradlew
./gradlew assembleRelease # or assembleDebug
```
### Step 4: Install on device
```bash
adb install android/app/build/outputs/apk/release/app-release.apk
```
## Docker build
```bash
docker build -f Dockerfile.android -t otclient-android .
docker create --name otc-build otclient-android
docker cp otc-build:/app-release.apk ./app-release.apk
docker rm otc-build
```
## Supported ABIs
| ABI | Device type | LuaJIT host flag |
|-----|------------|------------------|
| arm64-v8a | Modern phones/tablets (95%+ of market) | `gcc` |
| armeabi-v7a | Older 32-bit ARM devices | `gcc -m32` |
| x86_64 | Chromebooks, emulators | `gcc` |
| x86 | Older emulators | `gcc -m32` |
## Troubleshooting
### Black screen on launch
- Ensure `data.zip` is in `android/app/src/main/assets/`. Run `setup_android_deps.sh` or generate it manually.
### `assertion "hasIndex(-n)" failed` crash
- LuaJIT was compiled without `-DLUAJIT_UNWIND_EXTERNAL` or with wrong pointer size. Re-run `build_luajit_android.sh`.
### `Unsupported target architecture` in vcpkg
- This is expected — vcpkg cannot build LuaJIT for Android. That's why we build it separately. The `vcpkg.json` excludes LuaJIT from Android.
### 32-bit build fails with `gcc -m32`
- Install `gcc-multilib`: `sudo apt install gcc-multilib g++-multilib`

View file

@ -16,16 +16,18 @@ android {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
ndk {
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
abiFilters += listOf("arm64-v8a", "armeabi-v7a", "x86_64", "x86")
}
externalNativeBuild {
cmake {
cppFlags += listOf("-std=c++20", "-flto")
cppFlags += listOf("-std=c++20")
arguments += listOf(
"-DVCPKG_TARGET_ANDROID=ON",
"-DANDROID_STL=c++_shared"
"-DANDROID_STL=c++_shared",
"-DVCPKG_MANIFEST_INSTALL=ON",
"-DVCPKG_INSTALL_OPTIONS=--allow-unsupported"
)
}
}
@ -34,7 +36,7 @@ android {
externalNativeBuild {
cmake {
path = file("../../CMakeLists.txt")
version = "4.0.2"
version = "3.22.1"
}
}
@ -63,7 +65,7 @@ android {
prefab = true
}
ndkVersion = "29.0.13599879 rc2"
ndkVersion = "29.0.13599879"
}
dependencies {
@ -71,4 +73,4 @@ dependencies {
implementation("androidx.appcompat:appcompat:1.7.1")
implementation("androidx.games:games-activity:1.2.1")
implementation("com.google.android.material:material:1.13.0")
}
}

View file

@ -2,8 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" >
<!-- OpenGL ES 2.0 -->
<uses-feature android:glEsVersion="0x00020000"/>
<!-- OpenGL ES 3.0 -->
<uses-feature android:glEsVersion="0x00030000"/>
<!-- Allow writing to external storage -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

94
build_luajit_android.sh Normal file
View file

@ -0,0 +1,94 @@
#!/bin/bash
set -euo pipefail
# Build LuaJIT 2.1 for all Android ABIs using NDK cross-compilation
# Based on official LuaJIT docs (luajit.org/install.html) and vcpkg build flags
#
# Requirements:
# - ANDROID_NDK_HOME set (NDK 25+)
# - gcc-multilib installed (for 32-bit targets: sudo apt install gcc-multilib)
# - LuaJIT source at ./luajit-src/ (commit d0e88930 recommended for vcpkg compat)
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
NDK="${ANDROID_NDK_HOME:-/home/dev/android-sdk/ndk/29.0.13599879}"
NDKBIN="$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin"
LUAJIT_SRC="$SCRIPT_DIR/luajit-src"
INSTALL_BASE="$SCRIPT_DIR/android/app/libs"
if [ ! -d "$LUAJIT_SRC/src" ]; then
echo "ERROR: LuaJIT source not found at $LUAJIT_SRC"
echo "Run: git clone https://github.com/LuaJIT/LuaJIT.git $LUAJIT_SRC"
exit 1
fi
# Verify gcc -m32 works (needed for 32-bit targets)
if ! gcc -m32 -x c -c /dev/null -o /dev/null 2>/dev/null; then
echo "Installing gcc-multilib for 32-bit cross-compilation..."
sudo apt-get install -y gcc-multilib g++-multilib
fi
build_luajit() {
local ABI=$1 CROSS_PREFIX=$2 CC_PREFIX=$3 HOST_CC=$4
echo "=== Building LuaJIT for $ABI ==="
cd "$LUAJIT_SRC"
make clean 2>/dev/null || true
# Official LuaJIT cross-compilation approach:
# - HOST_CC="gcc -m32" for 32-bit targets (correct struct offsets in buildvm)
# - CROSS= for NDK toolchain prefix
# - STATIC_CC/DYNAMIC_CC for clang compiler
# - TARGET_CFLAGS with -DLUAJIT_UNWIND_EXTERNAL (required for Android NDK C++ interop)
# - Never use TARGET_SYS=Android (causes crashes, LuaJIT Issue #440)
# - Use 'amalg' target for optimized single-file compilation
make -j$(nproc) amalg \
HOST_CC="$HOST_CC" \
CROSS="${NDKBIN}/${CROSS_PREFIX}" \
STATIC_CC="${NDKBIN}/${CC_PREFIX}clang" \
DYNAMIC_CC="${NDKBIN}/${CC_PREFIX}clang -fPIC" \
TARGET_LD="${NDKBIN}/${CC_PREFIX}clang" \
TARGET_AR="$NDKBIN/llvm-ar rcus" \
TARGET_STRIP="$NDKBIN/llvm-strip" \
TARGET_CFLAGS="-fPIC -DLUAJIT_UNWIND_EXTERNAL -fno-stack-protector" \
BUILDMODE=static
# Install lib
local LIB_DIR="$INSTALL_BASE/lib/$ABI"
mkdir -p "$LIB_DIR"
cp src/libluajit.a "$LIB_DIR/libluajit-5.1.a"
echo " -> $LIB_DIR/libluajit-5.1.a ($(du -h "$LIB_DIR/libluajit-5.1.a" | cut -f1))"
}
# arm64-v8a: 64-bit ARM (most modern Android devices)
build_luajit "arm64-v8a" "aarch64-linux-android-" "aarch64-linux-android21-" "gcc"
# armeabi-v7a: 32-bit ARM (older devices) — requires HOST_CC="gcc -m32"
build_luajit "armeabi-v7a" "arm-linux-androideabi-" "armv7a-linux-androideabi21-" "gcc -m32"
# x86_64: 64-bit x86 (emulators, Chromebooks)
build_luajit "x86_64" "x86_64-linux-android-" "x86_64-linux-android21-" "gcc"
# x86: 32-bit x86 (older emulators) — requires HOST_CC="gcc -m32"
build_luajit "x86" "i686-linux-android-" "i686-linux-android21-" "gcc -m32"
# Install headers (shared across all ABIs)
echo ""
echo "=== Installing headers ==="
mkdir -p "$INSTALL_BASE/include/luajit"
cp "$LUAJIT_SRC/src/lua.h" "$LUAJIT_SRC/src/lualib.h" "$LUAJIT_SRC/src/lauxlib.h" \
"$LUAJIT_SRC/src/luaconf.h" "$LUAJIT_SRC/src/luajit.h" \
"$INSTALL_BASE/include/luajit/"
# Create lua.hpp C++ wrapper
cat > "$INSTALL_BASE/include/luajit/lua.hpp" << 'LUAHPP'
// C++ wrapper for LuaJIT header files.
extern "C" {
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
#include "luajit.h"
}
LUAHPP
echo ""
echo "=== All ABIs built successfully ==="
ls -lh "$INSTALL_BASE/lib/"*/libluajit-5.1.a

164
setup_android_deps.sh Normal file
View file

@ -0,0 +1,164 @@
#!/bin/bash
set -euo pipefail
# Pre-build script: installs all vcpkg dependencies for all Android ABIs
# Run this ONCE before building with Gradle
# Usage: ./setup_android_deps.sh
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
NDK="${ANDROID_NDK_HOME:-/home/dev/android-sdk/ndk/29.0.13599879}"
NDKBIN="$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin"
VCPKG="${VCPKG_ROOT:-/home/dev/vcpkg}"
LUAJIT_SRC="$SCRIPT_DIR/luajit-src"
# ABI -> vcpkg triplet mapping
declare -A ABI_MAP=(
["arm64-v8a"]="arm64-android"
["armeabi-v7a"]="arm-neon-android"
["x86_64"]="x64-android"
["x86"]="x86-android"
)
# ABI -> NDK cross-compile prefix (CROSS= prefix, without API level)
declare -A CROSS_MAP=(
["arm64-v8a"]="aarch64-linux-android-"
["armeabi-v7a"]="arm-linux-androideabi-"
["x86_64"]="x86_64-linux-android-"
["x86"]="i686-linux-android-"
)
# ABI -> NDK clang prefix (with API level, for STATIC_CC/DYNAMIC_CC/TARGET_LD)
declare -A CC_MAP=(
["arm64-v8a"]="aarch64-linux-android21-"
["armeabi-v7a"]="armv7a-linux-androideabi21-"
["x86_64"]="x86_64-linux-android21-"
["x86"]="i686-linux-android21-"
)
# ABI -> HOST_CC (32-bit ABIs need -m32)
declare -A HOSTCC_MAP=(
["arm64-v8a"]="gcc"
["armeabi-v7a"]="gcc -m32"
["x86_64"]="gcc"
["x86"]="gcc -m32"
)
build_luajit_for_abi() {
local ABI=$1
local TRIPLET=${ABI_MAP[$ABI]}
local CROSS=${CROSS_MAP[$ABI]}
local CC=${CC_MAP[$ABI]}
local HOST_CC="${HOSTCC_MAP[$ABI]}"
local INSTALL_DIR="$SCRIPT_DIR/android/app/libs"
echo "=== Building LuaJIT for $ABI ($TRIPLET) ==="
if [ ! -d "$LUAJIT_SRC/src" ]; then
echo "ERROR: LuaJIT source not found at $LUAJIT_SRC"
echo "Clone it: git clone https://github.com/LuaJIT/LuaJIT.git $LUAJIT_SRC"
exit 1
fi
cd "$LUAJIT_SRC"
make clean 2>/dev/null || true
make -j"$(nproc)" amalg \
HOST_CC="$HOST_CC" \
CROSS="${NDKBIN}/${CROSS}" \
STATIC_CC="${NDKBIN}/${CC}clang" \
DYNAMIC_CC="${NDKBIN}/${CC}clang -fPIC" \
TARGET_LD="${NDKBIN}/${CC}clang" \
TARGET_AR="$NDKBIN/llvm-ar rcus" \
TARGET_STRIP="$NDKBIN/llvm-strip" \
TARGET_CFLAGS="-fPIC -DLUAJIT_UNWIND_EXTERNAL -fno-stack-protector" \
BUILDMODE=static
# Install lib per-ABI
mkdir -p "$INSTALL_DIR/lib/$ABI"
cp src/libluajit.a "$INSTALL_DIR/lib/$ABI/libluajit-5.1.a"
# Install headers (shared across ABIs)
mkdir -p "$INSTALL_DIR/include/luajit"
cp src/lua.h src/lualib.h src/lauxlib.h src/luaconf.h \
src/luajit.h src/luajit_rolling.h "$INSTALL_DIR/include/luajit/"
# Create lua.hpp C++ wrapper if missing
if [ ! -f "$INSTALL_DIR/include/luajit/lua.hpp" ]; then
cat > "$INSTALL_DIR/include/luajit/lua.hpp" << 'LUAHPP'
extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}
LUAHPP
fi
echo " -> $ABI OK ($(du -h "$INSTALL_DIR/lib/$ABI/libluajit-5.1.a" | cut -f1))"
}
install_vcpkg_deps() {
local TRIPLET=$1
echo "=== Installing vcpkg deps for $TRIPLET ==="
cd "$SCRIPT_DIR"
"$VCPKG/vcpkg" install --triplet "$TRIPLET" --x-manifest-root=. --allow-unsupported 2>&1 | tail -3
}
# Step 1: Build LuaJIT for all ABIs
echo "============================="
echo "Step 1: Building LuaJIT"
echo "============================="
# Check gcc-multilib for 32-bit builds
if ! gcc -m32 -x c -c /dev/null -o /dev/null 2>/dev/null; then
echo "Installing gcc-multilib for 32-bit cross-compilation..."
sudo apt-get install -y gcc-multilib g++-multilib
fi
for ABI in "${!ABI_MAP[@]}"; do
build_luajit_for_abi "$ABI"
done
# Step 2: Install vcpkg deps (LuaJIT excluded from vcpkg, everything else)
echo ""
echo "============================="
echo "Step 2: Installing vcpkg deps"
echo "============================="
for ABI in "${!ABI_MAP[@]}"; do
TRIPLET=${ABI_MAP[$ABI]}
install_vcpkg_deps "$TRIPLET"
done
# Step 3: Generate data.zip asset bundle
echo ""
echo "============================="
echo "Step 3: Generating data.zip"
echo "============================="
mkdir -p "$SCRIPT_DIR/android/app/src/main/assets"
cd "$SCRIPT_DIR"
if command -v zip &>/dev/null; then
zip -r android/app/src/main/assets/data.zip data mods modules init.lua otclientrc.lua
elif command -v python3 &>/dev/null; then
python3 -c "
import zipfile, os
with zipfile.ZipFile('android/app/src/main/assets/data.zip', 'w', zipfile.ZIP_DEFLATED) as zf:
for item in ['data', 'mods', 'modules']:
for root, dirs, files in os.walk(item):
for f in files:
zf.write(os.path.join(root, f))
for f in ['init.lua', 'otclientrc.lua']:
if os.path.exists(f):
zf.write(f)
"
else
echo "ERROR: neither zip nor python3 available to create data.zip"
exit 1
fi
echo "data.zip created: $(du -h android/app/src/main/assets/data.zip | cut -f1)"
echo ""
echo "============================="
echo "All dependencies installed!"
echo "============================="
echo "LuaJIT libs:"
ls -la "$SCRIPT_DIR/android/app/libs/lib/"*/libluajit-5.1.a
echo ""
echo "Now run: cd android && ./gradlew assembleRelease"

56
setup_android_wsl.sh Normal file
View file

@ -0,0 +1,56 @@
#!/bin/bash
set -euo pipefail
ANDROID_HOME=/home/dev/android-sdk
VCPKG_ROOT=/home/dev/vcpkg
PROJECT_DIR=/home/dev/kizu-otc
mkdir -p "$ANDROID_HOME"
# Install commandline-tools if missing
if [ ! -f "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" ]; then
echo "Downloading Android commandline-tools..."
cd /tmp
curl -sL "https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip" -o cmdtools.zip
unzip -qo cmdtools.zip -d "$ANDROID_HOME/cmdline-tools-tmp"
mkdir -p "$ANDROID_HOME/cmdline-tools/latest"
mv "$ANDROID_HOME/cmdline-tools-tmp/cmdline-tools/"* "$ANDROID_HOME/cmdline-tools/latest/"
rm -rf "$ANDROID_HOME/cmdline-tools-tmp" cmdtools.zip
echo "commandline-tools installed."
else
echo "cmdline-tools already present."
fi
# Install NDK, platform, build-tools
echo "Installing SDK components..."
yes | "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --sdk_root="$ANDROID_HOME" \
"ndk;29.0.13599879" \
"platforms;android-36" \
"build-tools;35.0.0" \
2>&1 | tail -5
echo "NDK:" && ls "$ANDROID_HOME/ndk/"
# Set environment
export ANDROID_HOME="$ANDROID_HOME"
export ANDROID_NDK_HOME="$ANDROID_HOME/ndk/29.0.13599879"
export VCPKG_ROOT="$VCPKG_ROOT"
# Build
echo "=== Starting Gradle build ==="
cd "$PROJECT_DIR/android"
# Ensure data.zip exists
mkdir -p "$PROJECT_DIR/android/app/src/main/assets"
if [ ! -f "$PROJECT_DIR/android/app/src/main/assets/data.zip" ]; then
echo "Generating data.zip..."
cd "$PROJECT_DIR"
zip -r android/app/src/main/assets/data.zip data mods modules init.lua otclientrc.lua
fi
cd "$PROJECT_DIR/android"
chmod +x gradlew
./gradlew assembleRelease 2>&1
echo "=== BUILD COMPLETE ==="
ls -lh "$PROJECT_DIR/android/app/build/outputs/apk/release/"

View file

@ -76,7 +76,9 @@ endif()
if (TOGGLE_FRAMEWORK_EDITOR)
set(FRAMEWORK_DEFINITIONS ${FRAMEWORK_DEFINITIONS} -DFRAMEWORK_EDITOR)
endif()
if (ANDROID OR WASM)
if (ANDROID)
set(FRAMEWORK_DEFINITIONS ${FRAMEWORK_DEFINITIONS} -DOPENGL_ES=3)
elseif(WASM)
set(FRAMEWORK_DEFINITIONS ${FRAMEWORK_DEFINITIONS} -DOPENGL_ES=2)
endif()
@ -216,12 +218,27 @@ if(TOGGLE_FRAMEWORK_SOUND)
endif()
endif()
if(ANDROID)
set(LUA_LIBRARY ${LUA_LIBRARY} ${Android_LIBRARIES}/liblua.a)
set(LUAJIT_INCLUDE_DIR ${LUAJIT_LIBRARY} ${Android_INCLUDES}/luajit)
set(LUAJIT_LIBRARY ${LUAJIT_LIBRARY} ${Android_LIBRARIES}/libluajit.a)
set(OpenAL_LIBRARY ${OpenAL_LIBRARY} ${Android_LIBRARIES}/libopenal.a)
set(MINIZIP_INCLUDE_DIR ${MINIZIP_INCLUDE_DIR} ${Android_INCLUDES}/minizip)
set(MINIZIP_LIBRARY ${MINIZIP_LIBRARY} ${Android_LIBRARIES}/libminizip.a)
# LuaJIT: pre-compiled via build_luajit_android.sh or setup_android_deps.sh
# vcpkg LuaJIT port can't cross-compile for Android from x64 host
set(LUAJIT_ANDROID_LIBS "${CMAKE_SOURCE_DIR}/android/app/libs")
set(LUAJIT_ANDROID_LIB "${LUAJIT_ANDROID_LIBS}/lib/${CMAKE_ANDROID_ARCH_ABI}/libluajit-5.1.a")
set(LUAJIT_ANDROID_INC "${LUAJIT_ANDROID_LIBS}/include/luajit")
if(EXISTS "${LUAJIT_ANDROID_LIB}")
set(LUAJIT_INCLUDE_DIR "${LUAJIT_ANDROID_INC}" CACHE PATH "" FORCE)
set(LUAJIT_LIBRARY "${LUAJIT_ANDROID_LIB}" CACHE FILEPATH "" FORCE)
set(LUA_LIBRARY "${LUAJIT_ANDROID_LIB}")
include_directories("${LUAJIT_ANDROID_INC}")
message(STATUS "LuaJIT: using pre-compiled lib for ${CMAKE_ANDROID_ARCH_ABI}")
else()
message(FATAL_ERROR "LuaJIT not found for ${CMAKE_ANDROID_ARCH_ABI}. Run ./build_luajit_android.sh first.")
endif()
find_package(OpenAL CONFIG REQUIRED)
set(OpenAL_LIBRARY OpenAL::OpenAL)
# Vendored minizip (nmoinvaz fork) for Android APK asset extraction
include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}/framework/core/minizip)
set(MINIZIP_LIBRARY "")
find_package(game-activity REQUIRED CONFIG)
find_package(EGL REQUIRED)
else()
@ -449,6 +466,16 @@ if (WASM)
)
endif()
if(ANDROID)
# Vendored minizip sources for APK asset extraction (must be after SOURCE_FILES definition)
set(SOURCE_FILES ${SOURCE_FILES}
framework/core/minizip/ioapi.c
framework/core/minizip/ioapi_mem.c
framework/core/minizip/unzip.c
framework/core/minizip/crypt.c
)
endif()
add_library(otclient_core STATIC ${SOURCE_FILES})
target_link_libraries(otclient_core PRIVATE unofficial::inih::inireader)
@ -465,6 +492,19 @@ else()
message(STATUS "Use precompiled header: OFF")
endif(TOGGLE_PRE_COMPILED_HEADER)
# Exclude vendored minizip C files from PCH and unity build (Android only, C files cannot use C++ PCH)
if(ANDROID)
set_source_files_properties(
framework/core/minizip/ioapi.c
framework/core/minizip/ioapi_mem.c
framework/core/minizip/unzip.c
framework/core/minizip/crypt.c
PROPERTIES
SKIP_PRECOMPILE_HEADERS ON
SKIP_UNITY_BUILD_INCLUSION ON
)
endif()
if(SPEED_UP_BUILD_UNITY)
set_target_properties(otclient_core PROPERTIES UNITY_BUILD ON)
log_option_enabled("Build unity for speed up compilation")
@ -641,7 +681,6 @@ elseif(ANDROID)
${EGL_INCLUDE_DIR}
${PARALLEL_HASHMAP_INCLUDE_DIRS}
${NLOHMANN_JSON_INCLUDE_DIR}
${MINIZIP_INCLUDE_DIR}
${CPPCODEC_INCLUDE_DIRS}
)
target_link_libraries(otclient_core
@ -671,6 +710,7 @@ elseif(ANDROID)
GLESv3
android
log
Freetype::Freetype
pugixml::pugixml
fmt::fmt-header-only
utf8cpp::utf8cpp

View file

@ -0,0 +1,146 @@
/* crypt.c -- base code for traditional PKWARE encryption
Version 1.2.0, September 16th, 2017
Copyright (C) 2012-2017 Nathan Moinvaziri
https://github.com/nmoinvaz/minizip
Copyright (C) 1998-2005 Gilles Vollant
Modifications for Info-ZIP crypting
http://www.winimage.com/zLibDll/minizip.html
Copyright (C) 2003 Terry Thorsen
This code is a modified version of crypting code in Info-ZIP distribution
Copyright (C) 1990-2000 Info-ZIP. All rights reserved.
This program is distributed under the terms of the same license as zlib.
See the accompanying LICENSE file for the full text of the license.
This encryption code is a direct transcription of the algorithm from
Roger Schlafly, described by Phil Katz in the file appnote.txt. This
file (appnote.txt) is distributed with the PKZIP program (even in the
version without encryption capabilities).
If you don't need crypting in your application, just define symbols
NOCRYPT and NOUNCRYPT.
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <time.h>
#ifdef _WIN32
# include <windows.h>
# include <wincrypt.h>
#else
# include <sys/stat.h>
# include <fcntl.h>
# include <unistd.h>
#endif
#include "zlib.h"
#include "crypt.h"
/***************************************************************************/
#define CRC32(c, b) ((*(pcrc_32_tab+(((uint32_t)(c) ^ (b)) & 0xff))) ^ ((c) >> 8))
/***************************************************************************/
uint8_t decrypt_byte(uint32_t *pkeys)
{
unsigned temp; /* POTENTIAL BUG: temp*(temp^1) may overflow in an
* unpredictable manner on 16-bit systems; not a problem
* with any known compiler so far, though */
temp = ((uint32_t)(*(pkeys+2)) & 0xffff) | 2;
return (uint8_t)(((temp * (temp ^ 1)) >> 8) & 0xff);
}
uint8_t update_keys(uint32_t *pkeys, const z_crc_t *pcrc_32_tab, int32_t c)
{
(*(pkeys+0)) = (uint32_t)CRC32((*(pkeys+0)), c);
(*(pkeys+1)) += (*(pkeys+0)) & 0xff;
(*(pkeys+1)) = (*(pkeys+1)) * 134775813L + 1;
{
register int32_t keyshift = (int32_t)((*(pkeys + 1)) >> 24);
(*(pkeys+2)) = (uint32_t)CRC32((*(pkeys+2)), keyshift);
}
return c;
}
void init_keys(const char *passwd, uint32_t *pkeys, const z_crc_t *pcrc_32_tab)
{
*(pkeys+0) = 305419896L;
*(pkeys+1) = 591751049L;
*(pkeys+2) = 878082192L;
while (*passwd != 0)
{
update_keys(pkeys, pcrc_32_tab, *passwd);
passwd += 1;
}
}
/***************************************************************************/
#ifndef NOCRYPT
int cryptrand(unsigned char *buf, unsigned int len)
{
#ifdef _WIN32
HCRYPTPROV provider;
unsigned __int64 pentium_tsc[1];
int rlen = 0;
int result = 0;
if (CryptAcquireContext(&provider, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_SILENT))
{
result = CryptGenRandom(provider, len, buf);
CryptReleaseContext(provider, 0);
if (result)
return len;
}
for (rlen = 0; rlen < (int)len; ++rlen)
{
if (rlen % 8 == 0)
QueryPerformanceCounter((LARGE_INTEGER *)pentium_tsc);
buf[rlen] = ((unsigned char*)pentium_tsc)[rlen % 8];
}
return rlen;
#else
arc4random_buf(buf, len);
return len;
#endif
}
int crypthead(const char *passwd, uint8_t *buf, int buf_size, uint32_t *pkeys,
const z_crc_t *pcrc_32_tab, uint8_t verify1, uint8_t verify2)
{
uint8_t n = 0; /* index in random header */
uint8_t header[RAND_HEAD_LEN-2]; /* random header */
uint16_t t = 0; /* temporary */
if (buf_size < RAND_HEAD_LEN)
return 0;
init_keys(passwd, pkeys, pcrc_32_tab);
/* First generate RAND_HEAD_LEN-2 random bytes. */
cryptrand(header, RAND_HEAD_LEN-2);
/* Encrypt random header (last two bytes is high word of crc) */
init_keys(passwd, pkeys, pcrc_32_tab);
for (n = 0; n < RAND_HEAD_LEN-2; n++)
buf[n] = (uint8_t)zencode(pkeys, pcrc_32_tab, header[n], t);
buf[n++] = (uint8_t)zencode(pkeys, pcrc_32_tab, verify1, t);
buf[n++] = (uint8_t)zencode(pkeys, pcrc_32_tab, verify2, t);
return n;
}
#endif
/***************************************************************************/

View file

@ -0,0 +1,70 @@
/* crypt.h -- base code for traditional PKWARE encryption
Version 1.2.0, September 16th, 2017
Copyright (C) 2012-2017 Nathan Moinvaziri
https://github.com/nmoinvaz/minizip
Copyright (C) 1998-2005 Gilles Vollant
Modifications for Info-ZIP crypting
http://www.winimage.com/zLibDll/minizip.html
Copyright (C) 2003 Terry Thorsen
This code is a modified version of crypting code in Info-ZIP distribution
Copyright (C) 1990-2000 Info-ZIP. All rights reserved.
This program is distributed under the terms of the same license as zlib.
See the accompanying LICENSE file for the full text of the license.
*/
#ifndef _MINICRYPT_H
#define _MINICRYPT_H
// Fix: legacy minizip v1.2 missing stdint.h for uint8_t/uint32_t/int32_t
// (upstream is archived, fix applied locally)
#include <stdint.h>
#if ZLIB_VERNUM < 0x1270
typedef unsigned long z_crc_t;
#endif
#ifdef __cplusplus
extern "C" {
#endif
#define RAND_HEAD_LEN 12
/***************************************************************************/
#define zdecode(pkeys,pcrc_32_tab,c) \
(update_keys(pkeys,pcrc_32_tab, c ^= decrypt_byte(pkeys)))
#define zencode(pkeys,pcrc_32_tab,c,t) \
(t = decrypt_byte(pkeys), update_keys(pkeys,pcrc_32_tab,c), t^(c))
/***************************************************************************/
/* Return the next byte in the pseudo-random sequence */
uint8_t decrypt_byte(uint32_t *pkeys);
/* Update the encryption keys with the next byte of plain text */
uint8_t update_keys(uint32_t *pkeys, const z_crc_t *pcrc_32_tab, int32_t c);
/* Initialize the encryption keys and the random header according to the given password. */
void init_keys(const char *passwd, uint32_t *pkeys, const z_crc_t *pcrc_32_tab);
#ifndef NOCRYPT
/* Generate cryptographically secure random numbers */
int cryptrand(unsigned char *buf, unsigned int len);
/* Create encryption header */
int crypthead(const char *passwd, uint8_t *buf, int buf_size, uint32_t *pkeys,
const z_crc_t *pcrc_32_tab, uint8_t verify1, uint8_t verify2);
#endif
/***************************************************************************/
#ifdef __cplusplus
}
#endif
#endif

View file

@ -0,0 +1,361 @@
/* ioapi.c -- IO base function header for compress/uncompress .zip
part of the MiniZip project
Copyright (C) 2012-2017 Nathan Moinvaziri
https://github.com/nmoinvaz/minizip
Modifications for Zip64 support
Copyright (C) 2009-2010 Mathias Svensson
http://result42.com
Copyright (C) 1998-2010 Gilles Vollant
http://www.winimage.com/zLibDll/minizip.html
This program is distributed under the terms of the same license as zlib.
See the accompanying LICENSE file for the full text of the license.
*/
#include <stdlib.h>
#include <string.h>
#if defined unix || defined __APPLE__
#include <sys/types.h>
#include <unistd.h>
#endif
#include "ioapi.h"
#if defined(_WIN32)
# define snprintf _snprintf
#endif
voidpf call_zopen64(const zlib_filefunc64_32_def *pfilefunc, const void *filename, int mode)
{
if (pfilefunc->zfile_func64.zopen64_file != NULL)
return (*(pfilefunc->zfile_func64.zopen64_file)) (pfilefunc->zfile_func64.opaque, filename, mode);
return (*(pfilefunc->zopen32_file))(pfilefunc->zfile_func64.opaque, (const char*)filename, mode);
}
voidpf call_zopendisk64(const zlib_filefunc64_32_def *pfilefunc, voidpf filestream, uint32_t number_disk, int mode)
{
if (pfilefunc->zfile_func64.zopendisk64_file != NULL)
return (*(pfilefunc->zfile_func64.zopendisk64_file)) (pfilefunc->zfile_func64.opaque, filestream, number_disk, mode);
return (*(pfilefunc->zopendisk32_file))(pfilefunc->zfile_func64.opaque, filestream, number_disk, mode);
}
long call_zseek64(const zlib_filefunc64_32_def *pfilefunc, voidpf filestream, uint64_t offset, int origin)
{
uint32_t offset_truncated = 0;
if (pfilefunc->zfile_func64.zseek64_file != NULL)
return (*(pfilefunc->zfile_func64.zseek64_file)) (pfilefunc->zfile_func64.opaque,filestream,offset,origin);
offset_truncated = (uint32_t)offset;
if (offset_truncated != offset)
return -1;
return (*(pfilefunc->zseek32_file))(pfilefunc->zfile_func64.opaque,filestream, offset_truncated, origin);
}
uint64_t call_ztell64(const zlib_filefunc64_32_def *pfilefunc, voidpf filestream)
{
uint64_t position;
/* Fix: legacy minizip checked zseek64 instead of ztell64 (upstream archived) */
if (pfilefunc->zfile_func64.ztell64_file != NULL)
return (*(pfilefunc->zfile_func64.ztell64_file)) (pfilefunc->zfile_func64.opaque, filestream);
position = (*(pfilefunc->ztell32_file))(pfilefunc->zfile_func64.opaque, filestream);
if ((position) == UINT32_MAX)
return (uint64_t)-1;
return position;
}
void fill_zlib_filefunc64_32_def_from_filefunc32(zlib_filefunc64_32_def *p_filefunc64_32, const zlib_filefunc_def *p_filefunc32)
{
p_filefunc64_32->zfile_func64.zopen64_file = NULL;
p_filefunc64_32->zfile_func64.zopendisk64_file = NULL;
p_filefunc64_32->zopen32_file = p_filefunc32->zopen_file;
p_filefunc64_32->zopendisk32_file = p_filefunc32->zopendisk_file;
p_filefunc64_32->zfile_func64.zerror_file = p_filefunc32->zerror_file;
p_filefunc64_32->zfile_func64.zread_file = p_filefunc32->zread_file;
p_filefunc64_32->zfile_func64.zwrite_file = p_filefunc32->zwrite_file;
p_filefunc64_32->zfile_func64.ztell64_file = NULL;
p_filefunc64_32->zfile_func64.zseek64_file = NULL;
p_filefunc64_32->zfile_func64.zclose_file = p_filefunc32->zclose_file;
p_filefunc64_32->zfile_func64.zerror_file = p_filefunc32->zerror_file;
p_filefunc64_32->zfile_func64.opaque = p_filefunc32->opaque;
p_filefunc64_32->zseek32_file = p_filefunc32->zseek_file;
p_filefunc64_32->ztell32_file = p_filefunc32->ztell_file;
}
static voidpf ZCALLBACK fopen_file_func(ZIP_UNUSED voidpf opaque, const char *filename, int mode);
static uint32_t ZCALLBACK fread_file_func(voidpf opaque, voidpf stream, void* buf, uint32_t size);
static uint32_t ZCALLBACK fwrite_file_func(voidpf opaque, voidpf stream, const void *buf, uint32_t size);
static uint64_t ZCALLBACK ftell64_file_func(voidpf opaque, voidpf stream);
static long ZCALLBACK fseek64_file_func(voidpf opaque, voidpf stream, uint64_t offset, int origin);
static int ZCALLBACK fclose_file_func(voidpf opaque, voidpf stream);
static int ZCALLBACK ferror_file_func(voidpf opaque, voidpf stream);
typedef struct
{
FILE *file;
int filenameLength;
void *filename;
} FILE_IOPOSIX;
static voidpf file_build_ioposix(FILE *file, const char *filename)
{
FILE_IOPOSIX *ioposix = NULL;
if (file == NULL)
return NULL;
/* Fix: legacy minizip missing null checks after malloc (upstream archived) */
ioposix = (FILE_IOPOSIX*)malloc(sizeof(FILE_IOPOSIX));
if (ioposix == NULL)
return NULL;
ioposix->file = file;
ioposix->filenameLength = (int)strlen(filename) + 1;
ioposix->filename = (char*)malloc(ioposix->filenameLength * sizeof(char));
if (ioposix->filename == NULL) {
free(ioposix);
return NULL;
}
strncpy((char*)ioposix->filename, filename, ioposix->filenameLength);
return (voidpf)ioposix;
}
static voidpf ZCALLBACK fopen_file_func(ZIP_UNUSED voidpf opaque, const char *filename, int mode)
{
FILE* file = NULL;
const char *mode_fopen = NULL;
if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER) == ZLIB_FILEFUNC_MODE_READ)
mode_fopen = "rb";
else if (mode & ZLIB_FILEFUNC_MODE_EXISTING)
mode_fopen = "r+b";
else if (mode & ZLIB_FILEFUNC_MODE_CREATE)
mode_fopen = "wb";
if ((filename != NULL) && (mode_fopen != NULL))
{
file = fopen(filename, mode_fopen);
return file_build_ioposix(file, filename);
}
return file;
}
static voidpf ZCALLBACK fopen64_file_func(ZIP_UNUSED voidpf opaque, const void *filename, int mode)
{
FILE* file = NULL;
const char *mode_fopen = NULL;
if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER) == ZLIB_FILEFUNC_MODE_READ)
mode_fopen = "rb";
else if (mode & ZLIB_FILEFUNC_MODE_EXISTING)
mode_fopen = "r+b";
else if (mode & ZLIB_FILEFUNC_MODE_CREATE)
mode_fopen = "wb";
if ((filename != NULL) && (mode_fopen != NULL))
{
file = fopen64((const char*)filename, mode_fopen);
return file_build_ioposix(file, (const char*)filename);
}
return file;
}
static voidpf ZCALLBACK fopendisk64_file_func(voidpf opaque, voidpf stream, uint32_t number_disk, int mode)
{
FILE_IOPOSIX *ioposix = NULL;
char *diskFilename = NULL;
voidpf ret = NULL;
int i = 0;
if (stream == NULL)
return NULL;
ioposix = (FILE_IOPOSIX*)stream;
diskFilename = (char*)malloc(ioposix->filenameLength * sizeof(char));
strncpy(diskFilename, (const char*)ioposix->filename, ioposix->filenameLength);
for (i = ioposix->filenameLength - 1; i >= 0; i -= 1)
{
if (diskFilename[i] != '.')
continue;
snprintf(&diskFilename[i], ioposix->filenameLength - i, ".z%02u", number_disk + 1);
break;
}
if (i >= 0)
ret = fopen64_file_func(opaque, diskFilename, mode);
free(diskFilename);
return ret;
}
static voidpf ZCALLBACK fopendisk_file_func(voidpf opaque, voidpf stream, uint32_t number_disk, int mode)
{
FILE_IOPOSIX *ioposix = NULL;
char *diskFilename = NULL;
voidpf ret = NULL;
int i = 0;
if (stream == NULL)
return NULL;
ioposix = (FILE_IOPOSIX*)stream;
diskFilename = (char*)malloc(ioposix->filenameLength * sizeof(char));
strncpy(diskFilename, (const char*)ioposix->filename, ioposix->filenameLength);
for (i = ioposix->filenameLength - 1; i >= 0; i -= 1)
{
if (diskFilename[i] != '.')
continue;
snprintf(&diskFilename[i], ioposix->filenameLength - i, ".z%02u", number_disk + 1);
break;
}
if (i >= 0)
ret = fopen_file_func(opaque, diskFilename, mode);
free(diskFilename);
return ret;
}
static uint32_t ZCALLBACK fread_file_func(ZIP_UNUSED voidpf opaque, voidpf stream, void* buf, uint32_t size)
{
FILE_IOPOSIX *ioposix = NULL;
uint32_t read = (uint32_t)-1;
if (stream == NULL)
return read;
ioposix = (FILE_IOPOSIX*)stream;
read = (uint32_t)fread(buf, 1, (size_t)size, ioposix->file);
return read;
}
static uint32_t ZCALLBACK fwrite_file_func(ZIP_UNUSED voidpf opaque, voidpf stream, const void *buf, uint32_t size)
{
FILE_IOPOSIX *ioposix = NULL;
uint32_t written = (uint32_t)-1;
if (stream == NULL)
return written;
ioposix = (FILE_IOPOSIX*)stream;
written = (uint32_t)fwrite(buf, 1, (size_t)size, ioposix->file);
return written;
}
static long ZCALLBACK ftell_file_func(ZIP_UNUSED voidpf opaque, voidpf stream)
{
FILE_IOPOSIX *ioposix = NULL;
long ret = -1;
if (stream == NULL)
return ret;
ioposix = (FILE_IOPOSIX*)stream;
ret = ftell(ioposix->file);
return ret;
}
static uint64_t ZCALLBACK ftell64_file_func(ZIP_UNUSED voidpf opaque, voidpf stream)
{
FILE_IOPOSIX *ioposix = NULL;
uint64_t ret = (uint64_t)-1;
if (stream == NULL)
return ret;
ioposix = (FILE_IOPOSIX*)stream;
ret = ftello64(ioposix->file);
return ret;
}
static long ZCALLBACK fseek_file_func(ZIP_UNUSED voidpf opaque, voidpf stream, uint32_t offset, int origin)
{
FILE_IOPOSIX *ioposix = NULL;
int fseek_origin = 0;
long ret = 0;
if (stream == NULL)
return -1;
ioposix = (FILE_IOPOSIX*)stream;
switch (origin)
{
case ZLIB_FILEFUNC_SEEK_CUR:
fseek_origin = SEEK_CUR;
break;
case ZLIB_FILEFUNC_SEEK_END:
fseek_origin = SEEK_END;
break;
case ZLIB_FILEFUNC_SEEK_SET:
fseek_origin = SEEK_SET;
break;
default:
return -1;
}
if (fseek(ioposix->file, offset, fseek_origin) != 0)
ret = -1;
return ret;
}
static long ZCALLBACK fseek64_file_func(ZIP_UNUSED voidpf opaque, voidpf stream, uint64_t offset, int origin)
{
FILE_IOPOSIX *ioposix = NULL;
int fseek_origin = 0;
long ret = 0;
if (stream == NULL)
return -1;
ioposix = (FILE_IOPOSIX*)stream;
switch (origin)
{
case ZLIB_FILEFUNC_SEEK_CUR:
fseek_origin = SEEK_CUR;
break;
case ZLIB_FILEFUNC_SEEK_END:
fseek_origin = SEEK_END;
break;
case ZLIB_FILEFUNC_SEEK_SET:
fseek_origin = SEEK_SET;
break;
default:
return -1;
}
if (fseeko64(ioposix->file, offset, fseek_origin) != 0)
ret = -1;
return ret;
}
static int ZCALLBACK fclose_file_func(ZIP_UNUSED voidpf opaque, voidpf stream)
{
FILE_IOPOSIX *ioposix = NULL;
int ret = -1;
if (stream == NULL)
return ret;
ioposix = (FILE_IOPOSIX*)stream;
if (ioposix->filename != NULL)
free(ioposix->filename);
ret = fclose(ioposix->file);
free(ioposix);
return ret;
}
static int ZCALLBACK ferror_file_func(ZIP_UNUSED voidpf opaque, voidpf stream)
{
FILE_IOPOSIX *ioposix = NULL;
int ret = -1;
if (stream == NULL)
return ret;
ioposix = (FILE_IOPOSIX*)stream;
ret = ferror(ioposix->file);
return ret;
}
void fill_fopen_filefunc(zlib_filefunc_def *pzlib_filefunc_def)
{
pzlib_filefunc_def->zopen_file = fopen_file_func;
pzlib_filefunc_def->zopendisk_file = fopendisk_file_func;
pzlib_filefunc_def->zread_file = fread_file_func;
pzlib_filefunc_def->zwrite_file = fwrite_file_func;
pzlib_filefunc_def->ztell_file = ftell_file_func;
pzlib_filefunc_def->zseek_file = fseek_file_func;
pzlib_filefunc_def->zclose_file = fclose_file_func;
pzlib_filefunc_def->zerror_file = ferror_file_func;
pzlib_filefunc_def->opaque = NULL;
}
void fill_fopen64_filefunc(zlib_filefunc64_def *pzlib_filefunc_def)
{
pzlib_filefunc_def->zopen64_file = fopen64_file_func;
pzlib_filefunc_def->zopendisk64_file = fopendisk64_file_func;
pzlib_filefunc_def->zread_file = fread_file_func;
pzlib_filefunc_def->zwrite_file = fwrite_file_func;
pzlib_filefunc_def->ztell64_file = ftell64_file_func;
pzlib_filefunc_def->zseek64_file = fseek64_file_func;
pzlib_filefunc_def->zclose_file = fclose_file_func;
pzlib_filefunc_def->zerror_file = ferror_file_func;
pzlib_filefunc_def->opaque = NULL;
}

View file

@ -0,0 +1,153 @@
/* ioapi.h -- IO base function header for compress/uncompress .zip
part of the MiniZip project
Copyright (C) 2012-2017 Nathan Moinvaziri
https://github.com/nmoinvaz/minizip
Copyright (C) 2009-2010 Mathias Svensson
Modifications for Zip64 support
http://result42.com
Copyright (C) 1998-2010 Gilles Vollant
http://www.winimage.com/zLibDll/minizip.html
This program is distributed under the terms of the same license as zlib.
See the accompanying LICENSE file for the full text of the license.
*/
#ifndef _ZLIBIOAPI64_H
#define _ZLIBIOAPI64_H
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include "zlib.h"
#ifdef __GNUC__
# define ZIP_UNUSED __attribute__((__unused__))
#else
# define ZIP_UNUSED
#endif
#if defined(USE_FILE32API)
# define fopen64 fopen
# define ftello64 ftell
# define fseeko64 fseek
#else
# if defined(_MSC_VER)
# define fopen64 fopen
# if (_MSC_VER >= 1400) && (!(defined(NO_MSCVER_FILE64_FUNC)))
# define ftello64 _ftelli64
# define fseeko64 _fseeki64
# else /* old MSC */
# define ftello64 ftell
# define fseeko64 fseek
# endif
# else
# define fopen64 fopen
# define ftello64 ftello
# define fseeko64 fseeko
# endif
#endif
#ifdef __cplusplus
extern "C" {
#endif
#define ZLIB_FILEFUNC_SEEK_CUR (1)
#define ZLIB_FILEFUNC_SEEK_END (2)
#define ZLIB_FILEFUNC_SEEK_SET (0)
#define ZLIB_FILEFUNC_MODE_READ (1)
#define ZLIB_FILEFUNC_MODE_WRITE (2)
#define ZLIB_FILEFUNC_MODE_READWRITEFILTER (3)
#define ZLIB_FILEFUNC_MODE_EXISTING (4)
#define ZLIB_FILEFUNC_MODE_CREATE (8)
#ifndef ZCALLBACK
# if (defined(WIN32) || defined(_WIN32) || defined (WINDOWS) || \
defined (_WINDOWS)) && defined(CALLBACK) && defined (USEWINDOWS_CALLBACK)
# define ZCALLBACK CALLBACK
# else
# define ZCALLBACK
# endif
#endif
typedef voidpf (ZCALLBACK *open_file_func) (voidpf opaque, const char *filename, int mode);
typedef voidpf (ZCALLBACK *opendisk_file_func) (voidpf opaque, voidpf stream, uint32_t number_disk, int mode);
typedef uint32_t (ZCALLBACK *read_file_func) (voidpf opaque, voidpf stream, void* buf, uint32_t size);
typedef uint32_t (ZCALLBACK *write_file_func) (voidpf opaque, voidpf stream, const void *buf, uint32_t size);
typedef int (ZCALLBACK *close_file_func) (voidpf opaque, voidpf stream);
typedef int (ZCALLBACK *error_file_func) (voidpf opaque, voidpf stream);
typedef long (ZCALLBACK *tell_file_func) (voidpf opaque, voidpf stream);
typedef long (ZCALLBACK *seek_file_func) (voidpf opaque, voidpf stream, uint32_t offset, int origin);
/* here is the "old" 32 bits structure structure */
typedef struct zlib_filefunc_def_s
{
open_file_func zopen_file;
opendisk_file_func zopendisk_file;
read_file_func zread_file;
write_file_func zwrite_file;
tell_file_func ztell_file;
seek_file_func zseek_file;
close_file_func zclose_file;
error_file_func zerror_file;
voidpf opaque;
} zlib_filefunc_def;
typedef uint64_t (ZCALLBACK *tell64_file_func) (voidpf opaque, voidpf stream);
typedef long (ZCALLBACK *seek64_file_func) (voidpf opaque, voidpf stream, uint64_t offset, int origin);
typedef voidpf (ZCALLBACK *open64_file_func) (voidpf opaque, const void *filename, int mode);
typedef voidpf (ZCALLBACK *opendisk64_file_func)(voidpf opaque, voidpf stream, uint32_t number_disk, int mode);
typedef struct zlib_filefunc64_def_s
{
open64_file_func zopen64_file;
opendisk64_file_func zopendisk64_file;
read_file_func zread_file;
write_file_func zwrite_file;
tell64_file_func ztell64_file;
seek64_file_func zseek64_file;
close_file_func zclose_file;
error_file_func zerror_file;
voidpf opaque;
} zlib_filefunc64_def;
void fill_fopen_filefunc(zlib_filefunc_def *pzlib_filefunc_def);
void fill_fopen64_filefunc(zlib_filefunc64_def *pzlib_filefunc_def);
/* now internal definition, only for zip.c and unzip.h */
typedef struct zlib_filefunc64_32_def_s
{
zlib_filefunc64_def zfile_func64;
open_file_func zopen32_file;
opendisk_file_func zopendisk32_file;
tell_file_func ztell32_file;
seek_file_func zseek32_file;
} zlib_filefunc64_32_def;
#define ZREAD64(filefunc,filestream,buf,size) ((*((filefunc).zfile_func64.zread_file)) ((filefunc).zfile_func64.opaque,filestream,buf,size))
#define ZWRITE64(filefunc,filestream,buf,size) ((*((filefunc).zfile_func64.zwrite_file)) ((filefunc).zfile_func64.opaque,filestream,buf,size))
/*#define ZTELL64(filefunc,filestream) ((*((filefunc).ztell64_file)) ((filefunc).opaque,filestream))*/
/*#define ZSEEK64(filefunc,filestream,pos,mode) ((*((filefunc).zseek64_file)) ((filefunc).opaque,filestream,pos,mode))*/
#define ZCLOSE64(filefunc,filestream) ((*((filefunc).zfile_func64.zclose_file)) ((filefunc).zfile_func64.opaque,filestream))
#define ZERROR64(filefunc,filestream) ((*((filefunc).zfile_func64.zerror_file)) ((filefunc).zfile_func64.opaque,filestream))
voidpf call_zopen64(const zlib_filefunc64_32_def *pfilefunc,const void*filename, int mode);
voidpf call_zopendisk64(const zlib_filefunc64_32_def *pfilefunc, voidpf filestream, uint32_t number_disk, int mode);
long call_zseek64(const zlib_filefunc64_32_def *pfilefunc, voidpf filestream, uint64_t offset, int origin);
uint64_t call_ztell64(const zlib_filefunc64_32_def *pfilefunc, voidpf filestream);
void fill_zlib_filefunc64_32_def_from_filefunc32(zlib_filefunc64_32_def *p_filefunc64_32, const zlib_filefunc_def *p_filefunc32);
#define ZOPEN64(filefunc,filename,mode) (call_zopen64((&(filefunc)),(filename),(mode)))
#define ZOPENDISK64(filefunc,filestream,diskn,mode) (call_zopendisk64((&(filefunc)),(filestream),(diskn),(mode)))
#define ZTELL64(filefunc,filestream) (call_ztell64((&(filefunc)),(filestream)))
#define ZSEEK64(filefunc,filestream,pos,mode) (call_zseek64((&(filefunc)),(filestream),(pos),(mode)))
#ifdef __cplusplus
}
#endif
#endif

View file

@ -0,0 +1,174 @@
/* ioapi_mem.c -- IO base function header for compress/uncompress .zip
files using zlib + zip or unzip API
This version of ioapi is designed to access memory rather than files.
We do use a region of memory to put data in to and take it out of. We do
not have auto-extending buffers and do not inform anyone else that the
data has been written. It is really intended for accessing a zip archive
embedded in an application such that I can write an installer with no
external files. Creation of archives has not been attempted, although
parts of the framework are present.
Based on Unzip ioapi.c version 0.22, May 19th, 2003
Copyright (C) 2012-2017 Nathan Moinvaziri
https://github.com/nmoinvaz/minizip
Copyright (C) 2003 Justin Fletcher
Copyright (C) 1998-2003 Gilles Vollant
http://www.winimage.com/zLibDll/minizip.html
This file is under the same license as the Unzip tool it is distributed
with.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "zlib.h"
#include "ioapi.h"
#include "ioapi_mem.h"
#ifndef IOMEM_BUFFERSIZE
# define IOMEM_BUFFERSIZE (UINT16_MAX)
#endif
voidpf ZCALLBACK fopen_mem_func(voidpf opaque, ZIP_UNUSED const char *filename, int mode)
{
ourmemory_t *mem = (ourmemory_t *)opaque;
if (mem == NULL)
return NULL; /* Mem structure passed in was null */
if (mode & ZLIB_FILEFUNC_MODE_CREATE)
{
if (mem->grow)
{
mem->size = IOMEM_BUFFERSIZE;
mem->base = (char *)malloc(mem->size);
// Fix: legacy minizip missing null check after malloc (upstream archived)
if (mem->base == NULL)
return NULL;
}
mem->limit = 0; /* When writing we start with 0 bytes written */
}
else
mem->limit = mem->size;
mem->cur_offset = 0;
return mem;
}
voidpf ZCALLBACK fopendisk_mem_func(ZIP_UNUSED voidpf opaque, ZIP_UNUSED voidpf stream, ZIP_UNUSED uint32_t number_disk, ZIP_UNUSED int mode)
{
/* Not used */
return NULL;
}
uint32_t ZCALLBACK fread_mem_func(ZIP_UNUSED voidpf opaque, voidpf stream, void *buf, uint32_t size)
{
ourmemory_t *mem = (ourmemory_t *)stream;
if (size > mem->size - mem->cur_offset)
size = mem->size - mem->cur_offset;
memcpy(buf, mem->base + mem->cur_offset, size);
mem->cur_offset += size;
return size;
}
uint32_t ZCALLBACK fwrite_mem_func(ZIP_UNUSED voidpf opaque, voidpf stream, const void *buf, uint32_t size)
{
ourmemory_t *mem = (ourmemory_t *)stream;
uint32_t newmemsize = 0;
char *newbase = NULL;
if (size > mem->size - mem->cur_offset)
{
if (mem->grow)
{
newmemsize = mem->size;
if (size < IOMEM_BUFFERSIZE)
newmemsize += IOMEM_BUFFERSIZE;
else
newmemsize += size;
newbase = (char *)malloc(newmemsize);
// Fix: legacy minizip v1.2 missing null check after malloc
// (upstream is archived, fix applied locally)
if (newbase == NULL)
return 0;
memcpy(newbase, mem->base, mem->size);
free(mem->base);
mem->base = newbase;
mem->size = newmemsize;
}
else
size = mem->size - mem->cur_offset;
}
memcpy(mem->base + mem->cur_offset, buf, size);
mem->cur_offset += size;
if (mem->cur_offset > mem->limit)
mem->limit = mem->cur_offset;
return size;
}
long ZCALLBACK ftell_mem_func(ZIP_UNUSED voidpf opaque, voidpf stream)
{
ourmemory_t *mem = (ourmemory_t *)stream;
return mem->cur_offset;
}
long ZCALLBACK fseek_mem_func(ZIP_UNUSED voidpf opaque, voidpf stream, uint32_t offset, int origin)
{
ourmemory_t *mem = (ourmemory_t *)stream;
uint32_t new_pos = 0;
switch (origin)
{
case ZLIB_FILEFUNC_SEEK_CUR:
new_pos = mem->cur_offset + offset;
break;
case ZLIB_FILEFUNC_SEEK_END:
new_pos = mem->limit + offset;
break;
case ZLIB_FILEFUNC_SEEK_SET:
new_pos = offset;
break;
default:
return -1;
}
if (new_pos > mem->size)
return 1; /* Failed to seek that far */
mem->cur_offset = new_pos;
return 0;
}
int ZCALLBACK fclose_mem_func(ZIP_UNUSED voidpf opaque, ZIP_UNUSED voidpf stream)
{
/* Even with grow = 1, caller must always free() memory */
return 0;
}
int ZCALLBACK ferror_mem_func(ZIP_UNUSED voidpf opaque, ZIP_UNUSED voidpf stream)
{
/* We never return errors */
return 0;
}
void fill_memory_filefunc(zlib_filefunc_def *pzlib_filefunc_def, ourmemory_t *ourmem)
{
pzlib_filefunc_def->zopen_file = fopen_mem_func;
pzlib_filefunc_def->zopendisk_file = fopendisk_mem_func;
pzlib_filefunc_def->zread_file = fread_mem_func;
pzlib_filefunc_def->zwrite_file = fwrite_mem_func;
pzlib_filefunc_def->ztell_file = ftell_mem_func;
pzlib_filefunc_def->zseek_file = fseek_mem_func;
pzlib_filefunc_def->zclose_file = fclose_mem_func;
pzlib_filefunc_def->zerror_file = ferror_mem_func;
pzlib_filefunc_def->opaque = ourmem;
}

View file

@ -0,0 +1,52 @@
/* ioapi_mem.h -- IO base function header for compress/uncompress .zip
files using zlib + zip or unzip API
This version of ioapi is designed to access memory rather than files.
We do use a region of memory to put data in to and take it out of.
Copyright (C) 2012-2017 Nathan Moinvaziri (https://github.com/nmoinvaz/minizip)
(C) 2003 Justin Fletcher
(C) 1998-2003 Gilles Vollant
This program is distributed under the terms of the same license as zlib.
See the accompanying LICENSE file for the full text of the license.
*/
#ifndef _IOAPI_MEM_H
#define _IOAPI_MEM_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "zlib.h"
#include "ioapi.h"
#ifdef __cplusplus
extern "C" {
#endif
voidpf ZCALLBACK fopen_mem_func(voidpf opaque, const char* filename, int mode);
voidpf ZCALLBACK fopendisk_mem_func(voidpf opaque, voidpf stream, uint32_t number_disk, int mode);
uint32_t ZCALLBACK fread_mem_func(voidpf opaque, voidpf stream, void* buf, uint32_t size);
uint32_t ZCALLBACK fwrite_mem_func(voidpf opaque, voidpf stream, const void* buf, uint32_t size);
long ZCALLBACK ftell_mem_func(voidpf opaque, voidpf stream);
long ZCALLBACK fseek_mem_func(voidpf opaque, voidpf stream, uint32_t offset, int origin);
int ZCALLBACK fclose_mem_func(voidpf opaque, voidpf stream);
int ZCALLBACK ferror_mem_func(voidpf opaque, voidpf stream);
typedef struct ourmemory_s {
char *base; /* Base of the region of memory we're using */
uint32_t size; /* Size of the region of memory we're using */
uint32_t limit; /* Furthest we've written */
uint32_t cur_offset; /* Current offset in the area */
int grow; /* Growable memory buffer */
} ourmemory_t;
void fill_memory_filefunc(zlib_filefunc_def* pzlib_filefunc_def, ourmemory_t *ourmem);
#ifdef __cplusplus
}
#endif
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,308 @@
/* unzip.h -- IO for uncompress .zip files using zlib
Version 1.2.0, September 16th, 2017
part of the MiniZip project
Copyright (C) 2012-2017 Nathan Moinvaziri
https://github.com/nmoinvaz/minizip
Copyright (C) 2009-2010 Mathias Svensson
Modifications for Zip64 support on both zip and unzip
http://result42.com
Copyright (C) 2007-2008 Even Rouault
Modifications of Unzip for Zip64
Copyright (C) 1998-2010 Gilles Vollant
http://www.winimage.com/zLibDll/minizip.html
This program is distributed under the terms of the same license as zlib.
See the accompanying LICENSE file for the full text of the license.
*/
#ifndef _UNZ_H
#define _UNZ_H
#ifdef __cplusplus
extern "C" {
#endif
#ifndef _ZLIB_H
#include "zlib.h"
#endif
#ifndef _ZLIBIOAPI_H
#include "ioapi.h"
#endif
#ifdef HAVE_BZIP2
#include "bzlib.h"
#endif
#define Z_BZIP2ED 12
#if defined(STRICTUNZIP) || defined(STRICTZIPUNZIP)
/* like the STRICT of WIN32, we define a pointer that cannot be converted
from (void*) without cast */
typedef struct TagunzFile__ { int unused; } unz_file__;
typedef unz_file__ *unzFile;
#else
typedef voidp unzFile;
#endif
#define UNZ_OK (0)
#define UNZ_END_OF_LIST_OF_FILE (-100)
#define UNZ_ERRNO (Z_ERRNO)
#define UNZ_EOF (0)
#define UNZ_PARAMERROR (-102)
#define UNZ_BADZIPFILE (-103)
#define UNZ_INTERNALERROR (-104)
#define UNZ_CRCERROR (-105)
#define UNZ_BADPASSWORD (-106)
/* unz_global_info structure contain global data about the ZIPfile
These data comes from the end of central dir */
typedef struct unz_global_info64_s
{
uint64_t number_entry; /* total number of entries in the central dir on this disk */
uint32_t number_disk_with_CD; /* number the the disk with central dir, used for spanning ZIP*/
uint16_t size_comment; /* size of the global comment of the zipfile */
} unz_global_info64;
typedef struct unz_global_info_s
{
uint32_t number_entry; /* total number of entries in the central dir on this disk */
uint32_t number_disk_with_CD; /* number the the disk with central dir, used for spanning ZIP*/
uint16_t size_comment; /* size of the global comment of the zipfile */
} unz_global_info;
/* unz_file_info contain information about a file in the zipfile */
typedef struct unz_file_info64_s
{
uint16_t version; /* version made by 2 bytes */
uint16_t version_needed; /* version needed to extract 2 bytes */
uint16_t flag; /* general purpose bit flag 2 bytes */
uint16_t compression_method; /* compression method 2 bytes */
uint32_t dos_date; /* last mod file date in Dos fmt 4 bytes */
uint32_t crc; /* crc-32 4 bytes */
uint64_t compressed_size; /* compressed size 8 bytes */
uint64_t uncompressed_size; /* uncompressed size 8 bytes */
uint16_t size_filename; /* filename length 2 bytes */
uint16_t size_file_extra; /* extra field length 2 bytes */
uint16_t size_file_comment; /* file comment length 2 bytes */
uint32_t disk_num_start; /* disk number start 4 bytes */
uint16_t internal_fa; /* internal file attributes 2 bytes */
uint32_t external_fa; /* external file attributes 4 bytes */
uint64_t disk_offset;
uint16_t size_file_extra_internal;
} unz_file_info64;
typedef struct unz_file_info_s
{
uint16_t version; /* version made by 2 bytes */
uint16_t version_needed; /* version needed to extract 2 bytes */
uint16_t flag; /* general purpose bit flag 2 bytes */
uint16_t compression_method; /* compression method 2 bytes */
uint32_t dos_date; /* last mod file date in Dos fmt 4 bytes */
uint32_t crc; /* crc-32 4 bytes */
uint32_t compressed_size; /* compressed size 4 bytes */
uint32_t uncompressed_size; /* uncompressed size 4 bytes */
uint16_t size_filename; /* filename length 2 bytes */
uint16_t size_file_extra; /* extra field length 2 bytes */
uint16_t size_file_comment; /* file comment length 2 bytes */
uint16_t disk_num_start; /* disk number start 2 bytes */
uint16_t internal_fa; /* internal file attributes 2 bytes */
uint32_t external_fa; /* external file attributes 4 bytes */
uint64_t disk_offset;
} unz_file_info;
/***************************************************************************/
/* Opening and close a zip file */
extern unzFile ZEXPORT unzOpen(const char *path);
extern unzFile ZEXPORT unzOpen64(const void *path);
/* Open a Zip file.
path should contain the full path (by example, on a Windows XP computer
"c:\\zlib\\zlib113.zip" or on an Unix computer "zlib/zlib113.zip".
return NULL if zipfile cannot be opened or doesn't exist
return unzFile handle if no error
NOTE: The "64" function take a const void *pointer, because the path is just the value passed to the
open64_file_func callback. Under Windows, if UNICODE is defined, using fill_fopen64_filefunc, the path
is a pointer to a wide unicode string (LPCTSTR is LPCWSTR), so const char *does not describe the reality */
extern unzFile ZEXPORT unzOpen2(const char *path, zlib_filefunc_def *pzlib_filefunc_def);
/* Open a Zip file, like unzOpen, but provide a set of file low level API for read/write operations */
extern unzFile ZEXPORT unzOpen2_64(const void *path, zlib_filefunc64_def *pzlib_filefunc_def);
/* Open a Zip file, like unz64Open, but provide a set of file low level API for read/write 64-bit operations */
extern int ZEXPORT unzClose(unzFile file);
/* Close a ZipFile opened with unzOpen. If there is files inside the .Zip opened with unzOpenCurrentFile,
these files MUST be closed with unzipCloseCurrentFile before call unzipClose.
return UNZ_OK if there is no error */
extern int ZEXPORT unzGetGlobalInfo(unzFile file, unz_global_info *pglobal_info);
extern int ZEXPORT unzGetGlobalInfo64(unzFile file, unz_global_info64 *pglobal_info);
/* Write info about the ZipFile in the *pglobal_info structure.
return UNZ_OK if no error */
extern int ZEXPORT unzGetGlobalComment(unzFile file, char *comment, uint16_t comment_size);
/* Get the global comment string of the ZipFile, in the comment buffer.
uSizeBuf is the size of the szComment buffer.
return the number of byte copied or an error code <0 */
/***************************************************************************/
/* Reading the content of the current zipfile, you can open it, read data from it, and close it
(you can close it before reading all the file) */
extern int ZEXPORT unzOpenCurrentFile(unzFile file);
/* Open for reading data the current file in the zipfile.
return UNZ_OK if no error */
extern int ZEXPORT unzOpenCurrentFilePassword(unzFile file, const char *password);
/* Open for reading data the current file in the zipfile.
password is a crypting password
return UNZ_OK if no error */
extern int ZEXPORT unzOpenCurrentFile2(unzFile file, int *method, int *level, int raw);
/* Same as unzOpenCurrentFile, but open for read raw the file (not uncompress)
if raw==1 *method will receive method of compression, *level will receive level of compression
NOTE: you can set level parameter as NULL (if you did not want known level,
but you CANNOT set method parameter as NULL */
extern int ZEXPORT unzOpenCurrentFile3(unzFile file, int *method, int *level, int raw, const char *password);
/* Same as unzOpenCurrentFile, but takes extra parameter password for encrypted files */
extern int ZEXPORT unzReadCurrentFile(unzFile file, voidp buf, uint32_t len);
/* Read bytes from the current file (opened by unzOpenCurrentFile)
buf contain buffer where data must be copied
len the size of buf.
return the number of byte copied if somes bytes are copied
return 0 if the end of file was reached
return <0 with error code if there is an error (UNZ_ERRNO for IO error, or zLib error for uncompress error) */
extern int ZEXPORT unzGetCurrentFileInfo(unzFile file, unz_file_info *pfile_info, char *filename,
uint16_t filename_size, void *extrafield, uint16_t extrafield_size, char *comment, uint16_t comment_size);
extern int ZEXPORT unzGetCurrentFileInfo64(unzFile file, unz_file_info64 *pfile_info, char *filename,
uint16_t filename_size, void *extrafield, uint16_t extrafield_size, char *comment, uint16_t comment_size);
/* Get Info about the current file
pfile_info if != NULL, the *pfile_info structure will contain somes info about the current file
filename if != NULL, the file name string will be copied in filename
filename_size is the size of the filename buffer
extrafield if != NULL, the extra field information from the central header will be copied in to
extrafield_size is the size of the extraField buffer
comment if != NULL, the comment string of the file will be copied in to
comment_size is the size of the comment buffer */
extern int ZEXPORT unzGetLocalExtrafield(unzFile file, voidp buf, uint32_t len);
/* Read extra field from the current file (opened by unzOpenCurrentFile)
This is the local-header version of the extra field (sometimes, there is
more info in the local-header version than in the central-header)
if buf == NULL, it return the size of the local extra field
if buf != NULL, len is the size of the buffer, the extra header is copied in buf.
return number of bytes copied in buf, or (if <0) the error code */
extern int ZEXPORT unzCloseCurrentFile(unzFile file);
/* Close the file in zip opened with unzOpenCurrentFile
return UNZ_CRCERROR if all the file was read but the CRC is not good */
/***************************************************************************/
/* Browse the directory of the zipfile */
typedef int (*unzFileNameComparer)(unzFile file, const char *filename1, const char *filename2);
typedef int (*unzIteratorFunction)(unzFile file);
typedef int (*unzIteratorFunction2)(unzFile file, unz_file_info64 *pfile_info, char *filename,
uint16_t filename_size, void *extrafield, uint16_t extrafield_size, char *comment, uint16_t comment_size);
extern int ZEXPORT unzGoToFirstFile(unzFile file);
/* Set the current file of the zipfile to the first file.
return UNZ_OK if no error */
extern int ZEXPORT unzGoToFirstFile2(unzFile file, unz_file_info64 *pfile_info, char *filename,
uint16_t filename_size, void *extrafield, uint16_t extrafield_size, char *comment, uint16_t comment_size);
/* Set the current file of the zipfile to the first file and retrieves the current info on success.
Not as seek intensive as unzGoToFirstFile + unzGetCurrentFileInfo.
return UNZ_OK if no error */
extern int ZEXPORT unzGoToNextFile(unzFile file);
/* Set the current file of the zipfile to the next file.
return UNZ_OK if no error
return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest */
extern int ZEXPORT unzGoToNextFile2(unzFile file, unz_file_info64 *pfile_info, char *filename,
uint16_t filename_size, void *extrafield, uint16_t extrafield_size, char *comment, uint16_t comment_size);
/* Set the current file of the zipfile to the next file and retrieves the current
info on success. Does less seeking around than unzGotoNextFile + unzGetCurrentFileInfo.
return UNZ_OK if no error
return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest */
extern int ZEXPORT unzLocateFile(unzFile file, const char *filename, unzFileNameComparer filename_compare_func);
/* Try locate the file szFileName in the zipfile. For custom filename comparison pass in comparison function.
return UNZ_OK if the file is found (it becomes the current file)
return UNZ_END_OF_LIST_OF_FILE if the file is not found */
/***************************************************************************/
/* Raw access to zip file */
typedef struct unz_file_pos_s
{
uint32_t pos_in_zip_directory; /* offset in zip file directory */
uint32_t num_of_file; /* # of file */
} unz_file_pos;
extern int ZEXPORT unzGetFilePos(unzFile file, unz_file_pos *file_pos);
extern int ZEXPORT unzGoToFilePos(unzFile file, unz_file_pos *file_pos);
typedef struct unz64_file_pos_s
{
uint64_t pos_in_zip_directory; /* offset in zip file directory */
uint64_t num_of_file; /* # of file */
} unz64_file_pos;
extern int ZEXPORT unzGetFilePos64(unzFile file, unz64_file_pos *file_pos);
extern int ZEXPORT unzGoToFilePos64(unzFile file, const unz64_file_pos *file_pos);
extern int32_t ZEXPORT unzGetOffset(unzFile file);
extern int64_t ZEXPORT unzGetOffset64(unzFile file);
/* Get the current file offset */
extern int ZEXPORT unzSetOffset(unzFile file, uint32_t pos);
extern int ZEXPORT unzSetOffset64(unzFile file, uint64_t pos);
/* Set the current file offset */
extern int32_t ZEXPORT unzTell(unzFile file);
extern int64_t ZEXPORT unzTell64(unzFile file);
/* return current position in uncompressed data */
extern int ZEXPORT unzSeek(unzFile file, uint32_t offset, int origin);
extern int ZEXPORT unzSeek64(unzFile file, uint64_t offset, int origin);
/* Seek within the uncompressed data if compression method is storage */
extern int ZEXPORT unzEndOfFile(unzFile file);
/* return 1 if the end of file was reached, 0 elsewhere */
/***************************************************************************/
#ifdef __cplusplus
}
#endif
#endif /* _UNZ_H */

View file

@ -26,9 +26,9 @@
#include "logger.h"
#include "resourcemanager.h"
#include <filesystem>
#include <ioapi.h>
#include <ioapi_mem.h>
#include <unzip.h>
#include "minizip/ioapi.h"
#include "minizip/ioapi_mem.h"
#include "minizip/unzip.h"
const int MAX_FILENAME = 512;
const int READ_SIZE = 8192;
@ -37,7 +37,7 @@ const char dir_delimiter = '/';
void unzipper::extract(const char* fileBuffer, uint fileLength, std::string& destinationPath) {
const std::filesystem::path destinationFolder { destinationPath };
if (!std::filesystem::exists(destinationFolder)) {
std::filesystem::create_directory(destinationFolder);
std::filesystem::create_directories(destinationFolder);
}
zlib_filefunc_def filefunc32 = { nullptr };
@ -81,9 +81,20 @@ void unzipper::extract(const char* fileBuffer, uint fileLength, std::string& des
// Check if this entry is a directory or file.
const size_t filename_length = strlen( filename );
// Guard against zip-slip: reject entries that escape the destination folder
const auto entryPath = std::filesystem::path(filename).lexically_normal();
if (entryPath.is_absolute() || entryPath.string().starts_with("..")) {
g_logger.warning("skipping zip entry with unsafe path: {}", filename);
if ( ( i+1 ) < global_info.number_entry )
unzGoToNextFile( zipfile );
continue;
}
const auto outputPath = destinationFolder / entryPath;
if (filename[ filename_length-1 ] == dir_delimiter )
{
std::filesystem::create_directory({ destinationPath + filename });
std::filesystem::create_directories(outputPath);
}
else
{
@ -95,7 +106,12 @@ void unzipper::extract(const char* fileBuffer, uint fileLength, std::string& des
}
// Open a file to write out the data.
std::string destFilePath = destinationPath + filename;
const std::string destFilePath = outputPath.string();
// Ensure parent directory exists (zip may not have explicit directory entries)
const std::filesystem::path parentDir = outputPath.parent_path();
if (!std::filesystem::exists(parentDir)) {
std::filesystem::create_directories(parentDir);
}
FILE *out = fopen(destFilePath.c_str(), "wb");
if ( out == nullptr )
{

View file

@ -104,6 +104,11 @@ void AndroidManager::unZipAssetData() {
"data.zip",
AASSET_MODE_BUFFER);
if (!dataAsset) {
g_logger.fatal("Failed to open data.zip from APK assets. Run setup_android_deps.sh to generate it.");
return;
}
auto dataFileLength = AAsset_getLength(dataAsset);
char* dataContent = (char *) malloc(dataFileLength + 1);
AAsset_read(dataAsset, dataContent, dataFileLength);

View file

@ -27,6 +27,8 @@
#include <game-activity/native_app_glue/android_native_app_glue.h>
#include "framework/core/clock.h"
#include <framework/core/eventdispatcher.h>
#include <android/log.h>
#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "OTClientMobile", __VA_ARGS__)
AndroidWindow& g_androidWindow = (AndroidWindow&) g_window;
@ -90,7 +92,9 @@ void AndroidWindow::internalCheckGL() {
void AndroidWindow::internalChooseGL() {
static int attrList[] = {
#if OPENGL_ES==2
#if OPENGL_ES>=3
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT,
#elif OPENGL_ES==2
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
#else
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES_BIT,
@ -121,7 +125,9 @@ void AndroidWindow::internalCreateGLContext() {
}
EGLint attrList[] = {
#if OPENGL_ES==2
#if OPENGL_ES>=3
EGL_CONTEXT_CLIENT_VERSION, 3,
#elif OPENGL_ES==2
EGL_CONTEXT_CLIENT_VERSION, 2,
#else
EGL_CONTEXT_CLIENT_VERSION, 1,
@ -175,6 +181,11 @@ void AndroidWindow::internalCreateGLSurface() {
}
void AndroidWindow::queryGlSize() {
// Surface may not be created yet during early CONFIG_CHANGED events
if (m_eglDisplay == EGL_NO_DISPLAY || m_eglSurface == EGL_NO_SURFACE) {
return;
}
int width, height;
if (EGL_FALSE == eglQuerySurface(m_eglDisplay, m_eglSurface, EGL_WIDTH, &width) ||
EGL_FALSE == eglQuerySurface(m_eglDisplay, m_eglSurface, EGL_HEIGHT, &height)) {
@ -250,8 +261,7 @@ void AndroidWindow::processTextInput() {
void AndroidWindow::processFingerDownAndUp() {
bool isTouchdown = m_currentEvent.type == TOUCH_DOWN;
Fw::MouseButton mouseButton = (m_currentEvent.type == TOUCH_UP && !m_isDragging && stdext::millis() > m_lastPress + 500) ?
Fw::MouseRightButton : Fw::MouseLeftButton;
Fw::MouseButton mouseButton = Fw::MouseLeftButton;
m_inputEvent.reset();
m_inputEvent.type = (isTouchdown) ? Fw::MousePressInputEvent : Fw::MouseReleaseInputEvent;
@ -261,11 +271,9 @@ void AndroidWindow::processFingerDownAndUp() {
m_lastPress = g_clock.millis();
m_mouseButtonStates |= 1 << mouseButton;
} else if (m_currentEvent.type == TOUCH_UP) {
if (!m_isDragging) {
if (stdext::millis() > m_lastPress + 500) {
mouseButton = Fw::MouseRightButton;
m_inputEvent.mouseButton = mouseButton;
}
if (!m_isDragging && stdext::millis() > m_lastPress + 500) {
mouseButton = Fw::MouseRightButton;
m_inputEvent.mouseButton = mouseButton;
}
m_isDragging = false;
g_dispatcher.addEvent([this, mouseButton] { m_mouseButtonStates &= ~(1 << mouseButton); });
@ -276,7 +284,7 @@ void AndroidWindow::processFingerDownAndUp() {
void AndroidWindow::processFingerMotion() {
static Point lastMousePos(-1, -1);
static const int dragThreshold = 5;
static const int dragThreshold = 15; // Increased for touch (was 5, too sensitive)
m_inputEvent.reset();
m_inputEvent.type = Fw::MouseMoveInputEvent;
@ -306,7 +314,21 @@ void AndroidWindow::handleInputEvent() {
}
void AndroidWindow::swapBuffers() {
eglSwapBuffers(m_eglDisplay, m_eglSurface);
if (m_eglDisplay == EGL_NO_DISPLAY || m_eglSurface == EGL_NO_SURFACE)
return;
if (!eglSwapBuffers(m_eglDisplay, m_eglSurface)) {
EGLint error = eglGetError();
if (error == EGL_BAD_SURFACE || error == EGL_BAD_NATIVE_WINDOW) {
// Surface was lost (e.g. during config change), try to recreate
internalDestroySurface();
if (m_app->window != nullptr) {
internalCreateGLSurface();
internalConnectSurface();
queryGlSize();
}
}
}
}
void AndroidWindow::setVerticalSync(bool enable) {
@ -314,12 +336,12 @@ void AndroidWindow::setVerticalSync(bool enable) {
}
std::string AndroidWindow::getClipboardText() {
// TODO
// TODO: implement via JNI ClipboardManager (see PR #1674)
return "";
}
void AndroidWindow::setClipboardText(const std::string_view text) {
// TODO
// TODO: implement via JNI ClipboardManager (see PR #1674)
}
Size AndroidWindow::getDisplaySize() {
@ -427,9 +449,11 @@ void AndroidWindow::handleNativeEvents() {
}
void AndroidWindow::handleCmd(int32_t cmd) {
ALOGD("handleCmd: cmd=%d", cmd);
switch (cmd) {
case APP_CMD_INIT_WINDOW:
if (m_app->window != nullptr) {
ALOGD("INIT_WINDOW: window=%p, hasContext=%d", m_app->window, m_eglContext != EGL_NO_CONTEXT);
if (m_eglContext) {
internalCreateGLSurface();
internalConnectSurface();
@ -437,13 +461,21 @@ void AndroidWindow::handleCmd(int32_t cmd) {
internalInitGL();
}
updateDisplayDensityFromSystem(g_androidManager.getScreenDensity());
queryGlSize();
ALOGD("INIT_WINDOW: size=%dx%d, density=%.2f, visible=true", m_size.width(), m_size.height(), m_displayDensity);
m_visible = true;
} else {
ALOGD("INIT_WINDOW: window is null, visible=false");
m_visible = false;
}
break;
case APP_CMD_LOW_MEMORY:
ALOGD("LOW_MEMORY: ignoring surface destruction to keep rendering alive");
// Do NOT destroy the surface on low memory - it prevents recovery
// since APP_CMD_INIT_WINDOW won't be sent again.
break;
case APP_CMD_TERM_WINDOW:
ALOGD("TERM_WINDOW: setting visible=false");
m_visible = false;
internalDestroySurface();
break;
@ -451,6 +483,7 @@ void AndroidWindow::handleCmd(int32_t cmd) {
case APP_CMD_CONFIG_CHANGED:
updateDisplayDensityFromSystem(g_androidManager.getScreenDensity());
queryGlSize();
ALOGD("RESIZED/CONFIG_CHANGED: size=%dx%d, density=%.2f", m_size.width(), m_size.height(), m_displayDensity);
break;
default:
break;

View file

@ -1357,7 +1357,8 @@ void UITextEdit::onFocusChange(const bool focused, const Fw::FocusReason reason)
blinkCursor();
update(true);
#ifdef ANDROID
if (getProp(PropEditable)) {
// Only show keyboard on user interaction (mouse/touch), not programmatic focus
if (getProp(PropEditable) && reason == Fw::MouseFocusReason) {
g_androidManager.showKeyboardSoft();
g_androidManager.showInputPreview(getText());
}

View file

@ -26,6 +26,12 @@
#include "framework/core/resourcemanager.h"
#include "framework/luaengine/luainterface.h"
#include "framework/platform/platform.h"
#ifdef ANDROID
#include <android/log.h>
#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "OTClientMobile", __VA_ARGS__)
#else
#define ALOGD(...)
#endif
#ifdef FRAMEWORK_EDITOR
#include "tools/datdump.h"
#endif
@ -77,12 +83,16 @@ void printHelp(const std::string& executableName)
// process args encoding
g_platform.init(args);
ALOGD("main: platform init done");
// initialize resources
#ifdef ANDROID
// Unzip Android assets/data.zip
ALOGD("main: starting unZipAssetData...");
g_androidManager.unZipAssetData();
ALOGD("main: unZipAssetData complete");
g_resources.init(nullptr);
ALOGD("main: resources init done");
#else
g_resources.init(args[0].data());
#endif
@ -104,8 +114,10 @@ void printHelp(const std::string& executableName)
}
// find script init.lua and run it
ALOGD("main: discovering work dir...");
if (!g_resources.discoverWorkDir("init.lua"))
g_logger.fatal("Unable to find work directory, the application cannot be initialized.");
ALOGD("main: work dir found: %s", g_resources.getWorkDir().c_str());
if (shouldShowHelp(args)) {
printHelp(args[0]);
@ -119,8 +131,10 @@ void printHelp(const std::string& executableName)
#endif
// initialize application framework and otclient
ALOGD("main: initializing app framework...");
const auto drawEvents = ApplicationDrawEventsPtr(&g_client, [](ApplicationDrawEvents*) {});
g_app.init(args, new GraphicalApplicationContext(g_gameConfig.getSpriteSize(), drawEvents));
ALOGD("main: app framework initialized");
#ifndef ANDROID
#if ENABLE_DISCORD_RPC == 1
@ -142,13 +156,16 @@ void printHelp(const std::string& executableName)
#endif
#endif
ALOGD("main: initializing client...");
g_client.init(args);
#ifdef FRAMEWORK_NET
g_http.init();
#endif
ALOGD("main: running init.lua...");
if (!g_lua.safeRunScript("init.lua"))
g_logger.fatal("Unable to run script init.lua!");
ALOGD("main: init.lua executed successfully");
// the run application main loop
g_app.run();