Add 'android/' from commit 'add509bf9ebaa488e2afe2cdc158a0e6725cd654'

git-subtree-dir: android
git-subtree-mainline: b9f4f1eba4
git-subtree-split: add509bf9e
This commit is contained in:
Alibek Omarov 2024-11-20 05:31:39 +03:00
commit f04a26901c
111 changed files with 4338 additions and 0 deletions

14
android/.gitignore vendored Normal file
View file

@ -0,0 +1,14 @@
.gradle/
build/
.externalNativeBuild
.cxx/
.idea/
local.properties
.project
.classpath
.gradle
.settings
release/
*.hprof
.vscode/
*.bak

11
android/.gitmodules vendored Normal file
View file

@ -0,0 +1,11 @@
[submodule "hlsdk-portable"]
path = app/src/main/cpp/hlsdk-portable
url = https://github.com/FWGS/hlsdk-portable
branch = mobile_hacks
[submodule "xash3d-fwgs"]
path = app/src/main/cpp/xash3d-fwgs
url = https://github.com/FWGS/xash3d-fwgs
[submodule "SDL"]
path = app/src/main/cpp/SDL
url = https://github.com/libsdl-org/SDL
branch = release-2.24.1

3
android/Gemfile Normal file
View file

@ -0,0 +1,3 @@
source "https://rubygems.org"
gem "fastlane"

221
android/Gemfile.lock Normal file
View file

@ -0,0 +1,221 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.5)
rexml
addressable (2.8.1)
public_suffix (>= 2.0.2, < 6.0)
artifactory (3.0.15)
atomos (0.1.3)
aws-eventstream (1.2.0)
aws-partitions (1.666.0)
aws-sdk-core (3.168.1)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.5)
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.59.0)
aws-sdk-core (~> 3, >= 3.165.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.117.1)
aws-sdk-core (~> 3, >= 3.165.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.4)
aws-sigv4 (1.5.2)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
claide (1.1.0)
colored (1.2)
colored2 (3.1.2)
commander (4.6.0)
highline (~> 2.0.0)
declarative (0.0.20)
digest-crc (0.6.4)
rake (>= 12.0.0, < 14.0.0)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
dotenv (2.8.1)
emoji_regex (3.2.3)
excon (0.94.0)
faraday (1.10.2)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
faraday-httpclient (~> 1.0)
faraday-multipart (~> 1.0)
faraday-net_http (~> 1.0)
faraday-net_http_persistent (~> 1.0)
faraday-patron (~> 1.0)
faraday-rack (~> 1.0)
faraday-retry (~> 1.0)
ruby2_keywords (>= 0.0.4)
faraday-cookie_jar (0.0.7)
faraday (>= 0.8.0)
http-cookie (~> 1.0.0)
faraday-em_http (1.0.0)
faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
faraday-multipart (1.0.4)
multipart-post (~> 2)
faraday-net_http (1.0.1)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
faraday-rack (1.0.0)
faraday-retry (1.0.3)
faraday_middleware (1.2.0)
faraday (~> 1.0)
fastimage (2.2.6)
fastlane (2.214.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
aws-sdk-s3 (~> 1.0)
babosa (>= 1.0.3, < 2.0.0)
bundler (>= 1.12.0, < 3.0.0)
colored
commander (~> 4.6)
dotenv (>= 2.1.1, < 3.0.0)
emoji_regex (>= 0.1, < 4.0)
excon (>= 0.71.0, < 1.0.0)
faraday (~> 1.0)
faraday-cookie_jar (~> 0.0.6)
faraday_middleware (~> 1.0)
fastimage (>= 2.1.0, < 3.0.0)
gh_inspector (>= 1.1.2, < 2.0.0)
google-apis-androidpublisher_v3 (~> 0.3)
google-apis-playcustomapp_v1 (~> 0.1)
google-cloud-storage (~> 1.31)
highline (~> 2.0)
json (< 3.0.0)
jwt (>= 2.1.0, < 3)
mini_magick (>= 4.9.4, < 5.0.0)
multipart-post (>= 2.0.0, < 3.0.0)
naturally (~> 2.2)
optparse (~> 0.1.1)
plist (>= 3.1.0, < 4.0.0)
rubyzip (>= 2.0.0, < 3.0.0)
security (= 0.1.3)
simctl (~> 1.6.3)
terminal-notifier (>= 2.0.0, < 3.0.0)
terminal-table (>= 1.4.5, < 2.0.0)
tty-screen (>= 0.6.3, < 1.0.0)
tty-spinner (>= 0.8.0, < 1.0.0)
word_wrap (~> 1.0.0)
xcodeproj (>= 1.13.0, < 2.0.0)
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3)
gh_inspector (1.1.3)
google-apis-androidpublisher_v3 (0.31.0)
google-apis-core (>= 0.9.1, < 2.a)
google-apis-core (0.9.1)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.16.2, < 2.a)
httpclient (>= 2.8.1, < 3.a)
mini_mime (~> 1.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.a)
rexml
webrick
google-apis-iamcredentials_v1 (0.16.0)
google-apis-core (>= 0.9.1, < 2.a)
google-apis-playcustomapp_v1 (0.12.0)
google-apis-core (>= 0.9.1, < 2.a)
google-apis-storage_v1 (0.19.0)
google-apis-core (>= 0.9.0, < 2.a)
google-cloud-core (1.6.0)
google-cloud-env (~> 1.0)
google-cloud-errors (~> 1.0)
google-cloud-env (1.6.0)
faraday (>= 0.17.3, < 3.0)
google-cloud-errors (1.3.0)
google-cloud-storage (1.44.0)
addressable (~> 2.8)
digest-crc (~> 0.4)
google-apis-iamcredentials_v1 (~> 0.1)
google-apis-storage_v1 (~> 0.19.0)
google-cloud-core (~> 1.6)
googleauth (>= 0.16.2, < 2.a)
mini_mime (~> 1.0)
googleauth (1.3.0)
faraday (>= 0.17.3, < 3.a)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (>= 0.16, < 2.a)
highline (2.0.3)
http-cookie (1.0.5)
domain_name (~> 0.5)
httpclient (2.8.3)
jmespath (1.6.2)
json (2.6.2)
jwt (2.5.0)
memoist (0.16.2)
mini_magick (4.11.0)
mini_mime (1.1.2)
multi_json (1.15.0)
multipart-post (2.0.0)
nanaimo (0.3.0)
naturally (2.2.1)
optparse (0.1.1)
os (1.1.4)
plist (3.6.0)
public_suffix (5.0.0)
rake (13.0.6)
representable (3.2.0)
declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rexml (3.3.2)
strscan
rouge (2.0.7)
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
security (0.1.3)
signet (0.17.0)
addressable (~> 2.8)
faraday (>= 0.17.5, < 3.a)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simctl (1.6.8)
CFPropertyList
naturally
strscan (3.1.0)
terminal-notifier (2.0.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
trailblazer-option (0.1.2)
tty-cursor (0.7.1)
tty-screen (0.8.1)
tty-spinner (0.9.3)
tty-cursor (~> 0.7)
uber (0.1.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.8.2)
unf_ext (0.0.8.2-x64-mingw-ucrt)
unicode-display_width (1.8.0)
webrick (1.7.0)
word_wrap (1.0.0)
xcodeproj (1.19.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.3.0)
xcpretty (0.3.0)
rouge (~> 2.0.7)
xcpretty-travis-formatter (1.0.1)
xcpretty (~> 0.2, >= 0.0.7)
PLATFORMS
x64-mingw-ucrt
x86_64-linux
DEPENDENCIES
fastlane
BUNDLED WITH
2.3.26

9
android/README.md Normal file
View file

@ -0,0 +1,9 @@
# Xash3D Android
![translation progress](https://l10n.mentality.rip/widgets/xash3d-fwgs-android/-/svg-badge.svg)
This repo is intended only to store Android launcher and wrapper code for
[Xash3D FWGS](https://github.com/FWGS/xash3d-fwgs).
Issues must be sent to Xash3D FWGS repository.
Translations to Android launcher are done through [Weblate](https://l10n.mentality.rip).

View file

@ -0,0 +1,130 @@
import java.time.LocalDateTime
import java.time.Month
import java.time.temporal.ChronoUnit
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
}
android {
namespace = "su.xash.engine"
ndkVersion = "26.1.10909125"
defaultConfig {
applicationId = "su.xash"
applicationIdSuffix = "engine"
versionName = "0.21"
versionCode = getBuildNum()
minSdk = 21
targetSdk = 34
compileSdk = 34
externalNativeBuild {
cmake {
abiFilters("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
arguments("-DANDROID_USE_LEGACY_TOOLCHAIN_FILE=OFF")
}
}
}
externalNativeBuild {
cmake {
version = "3.22.1"
path = file("${project.projectDir}/src/main/cpp/CMakeLists.txt")
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
buildTypes {
debug {
isMinifyEnabled = false
isShrinkResources = false
isDebuggable = true
applicationIdSuffix = ".test"
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
)
}
release {
isMinifyEnabled = false
isShrinkResources = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
)
}
register("asan") {
initWith(getByName("debug"))
}
register("continuous") {
initWith(getByName("release"))
applicationIdSuffix = ".test"
}
}
sourceSets {
getByName("main") {
assets.srcDir("${project.projectDir}/src/main/cpp/xash3d-fwgs/3rdparty/extras/xash-extras")
assets.srcDir("${project.projectDir}/../moddb")
java.srcDir("${project.projectDir}/src/main/cpp/SDL/android-project/app/src/main/java")
}
}
lint {
abortOnError = false
}
buildFeatures {
viewBinding = true
buildConfig = true
}
androidResources {
noCompress += ""
}
packaging {
jniLibs {
useLegacyPackaging = true
}
}
}
dependencies {
implementation("com.google.android.material:material:1.11.0")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.navigation:navigation-fragment-ktx:2.7.7")
implementation("androidx.navigation:navigation-ui-ktx:2.7.7")
implementation("androidx.cardview:cardview:1.0.0")
implementation("androidx.annotation:annotation:1.7.1")
implementation("androidx.fragment:fragment-ktx:1.6.2")
implementation("androidx.preference:preference-ktx:1.2.1")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
implementation("androidx.work:work-runtime-ktx:2.9.0")
// implementation "androidx.legacy:legacy-support-v4:1.0.0"
implementation("com.madgag.spongycastle:prov:1.58.0.0")
implementation("in.dragonbra:javasteam:1.2.0")
implementation("ch.acra:acra-http:5.11.2")
}
fun getBuildNum(): Int {
val now = LocalDateTime.now()
val releaseDate = LocalDateTime.of(2015, Month.APRIL, 1, 0, 0, 0)
val qBuildNum = releaseDate.until(now, ChronoUnit.DAYS)
val minuteOfDay = now.hour * 60 + now.minute
return (qBuildNum * 10000 + minuteOfDay).toInt()
}

21
android/app/proguard-rules.pro vendored Normal file
View file

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name" translatable="false">Xash3D FWGS (Test)</string>
<string name="authority" translatable="false">su.xash.engine.test.documents</string>
</resources>

View file

@ -0,0 +1,26 @@
#!/system/bin/sh
HERE=$(cd "$(dirname "$0")" && pwd)
cmd=$1
shift
# This must be called *before* `LD_PRELOAD` is set. Otherwise, if this is a 32-
# bit app running on a 64-bit device, the 64-bit getprop will fail to load
# because it will preload a 32-bit ASan runtime.
# https://github.com/android/ndk/issues/1744
os_version=$(getprop ro.build.version.sdk)
if [ "$os_version" -eq "27" ]; then
cmd="$cmd -Xrunjdwp:transport=dt_android_adb,suspend=n,server=y -Xcompiler-option --debuggable $@"
elif [ "$os_version" -eq "28" ]; then
cmd="$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y -Xcompiler-option --debuggable $@"
else
cmd="$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y $@"
fi
export ASAN_OPTIONS=log_to_syslog=false,allow_user_segv_handler=1
ASAN_LIB=$(ls "$HERE"/libclang_rt.asan-*-android.so)
if [ -f "$HERE/libc++_shared.so" ]; then
# Workaround for https://github.com/android-ndk/ndk/issues/988.
export LD_PRELOAD="$ASAN_LIB $HERE/libc++_shared.so"
else
export LD_PRELOAD="$ASAN_LIB"
fi
exec $cmd

View file

@ -0,0 +1,26 @@
#!/system/bin/sh
HERE=$(cd "$(dirname "$0")" && pwd)
cmd=$1
shift
# This must be called *before* `LD_PRELOAD` is set. Otherwise, if this is a 32-
# bit app running on a 64-bit device, the 64-bit getprop will fail to load
# because it will preload a 32-bit ASan runtime.
# https://github.com/android/ndk/issues/1744
os_version=$(getprop ro.build.version.sdk)
if [ "$os_version" -eq "27" ]; then
cmd="$cmd -Xrunjdwp:transport=dt_android_adb,suspend=n,server=y -Xcompiler-option --debuggable $@"
elif [ "$os_version" -eq "28" ]; then
cmd="$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y -Xcompiler-option --debuggable $@"
else
cmd="$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y $@"
fi
export ASAN_OPTIONS=log_to_syslog=false,allow_user_segv_handler=1
ASAN_LIB=$(ls "$HERE"/libclang_rt.asan-*-android.so)
if [ -f "$HERE/libc++_shared.so" ]; then
# Workaround for https://github.com/android-ndk/ndk/issues/988.
export LD_PRELOAD="$ASAN_LIB $HERE/libc++_shared.so"
else
export LD_PRELOAD="$ASAN_LIB"
fi
exec $cmd

View file

@ -0,0 +1,26 @@
#!/system/bin/sh
HERE=$(cd "$(dirname "$0")" && pwd)
cmd=$1
shift
# This must be called *before* `LD_PRELOAD` is set. Otherwise, if this is a 32-
# bit app running on a 64-bit device, the 64-bit getprop will fail to load
# because it will preload a 32-bit ASan runtime.
# https://github.com/android/ndk/issues/1744
os_version=$(getprop ro.build.version.sdk)
if [ "$os_version" -eq "27" ]; then
cmd="$cmd -Xrunjdwp:transport=dt_android_adb,suspend=n,server=y -Xcompiler-option --debuggable $@"
elif [ "$os_version" -eq "28" ]; then
cmd="$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y -Xcompiler-option --debuggable $@"
else
cmd="$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y $@"
fi
export ASAN_OPTIONS=log_to_syslog=false,allow_user_segv_handler=1
ASAN_LIB=$(ls "$HERE"/libclang_rt.asan-*-android.so)
if [ -f "$HERE/libc++_shared.so" ]; then
# Workaround for https://github.com/android-ndk/ndk/issues/988.
export LD_PRELOAD="$ASAN_LIB $HERE/libc++_shared.so"
else
export LD_PRELOAD="$ASAN_LIB"
fi
exec $cmd

View file

@ -0,0 +1,26 @@
#!/system/bin/sh
HERE=$(cd "$(dirname "$0")" && pwd)
cmd=$1
shift
# This must be called *before* `LD_PRELOAD` is set. Otherwise, if this is a 32-
# bit app running on a 64-bit device, the 64-bit getprop will fail to load
# because it will preload a 32-bit ASan runtime.
# https://github.com/android/ndk/issues/1744
os_version=$(getprop ro.build.version.sdk)
if [ "$os_version" -eq "27" ]; then
cmd="$cmd -Xrunjdwp:transport=dt_android_adb,suspend=n,server=y -Xcompiler-option --debuggable $@"
elif [ "$os_version" -eq "28" ]; then
cmd="$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y -Xcompiler-option --debuggable $@"
else
cmd="$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y $@"
fi
export ASAN_OPTIONS=log_to_syslog=false,allow_user_segv_handler=1
ASAN_LIB=$(ls "$HERE"/libclang_rt.asan-*-android.so)
if [ -f "$HERE/libc++_shared.so" ]; then
# Workaround for https://github.com/android-ndk/ndk/issues/988.
export LD_PRELOAD="$ASAN_LIB $HERE/libc++_shared.so"
else
export LD_PRELOAD="$ASAN_LIB"
fi
exec $cmd

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name" translatable="false">Xash3D FWGS (Test)</string>
<string name="authority" translatable="false">su.xash.engine.test.documents</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name" translatable="false">Xash3D FWGS (Test)</string>
<string name="authority" translatable="false">su.xash.engine.test.documents</string>
</resources>

View file

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:allowAudioPlaybackCapture="true"
android:installLocation="preferExternal"
tools:targetApi="q">
<!-- OpenGL ES 1.1 -->
<uses-feature android:glEsVersion="0x00010000" />
<!-- Touchscreen support -->
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
<!-- Game controller support -->
<uses-feature
android:name="android.hardware.bluetooth"
android:required="false" />
<uses-feature
android:name="android.hardware.gamepad"
android:required="false" />
<uses-feature
android:name="android.hardware.usb.host"
android:required="false" />
<!-- External mouse input events -->
<uses-feature
android:name="android.hardware.type.pc"
android:required="false" />
<!-- Audio recording support -->
<uses-feature
android:name="android.hardware.microphone"
android:required="false" />
<!-- Allow downloading to the external storage on Android 5.1 and older -->
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="22" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<!-- Allow access to Bluetooth devices -->
<!-- Currently this is just for Steam Controller support and requires setting SDL_HINT_JOYSTICK_HIDAPI_STEAM -->
<uses-permission
android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- Allow access to the vibrator -->
<uses-permission android:name="android.permission.VIBRATE" />
<!-- Allow recording audio -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- Allow internet access -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- Dedicated server -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<application
android:name=".MainApplication"
android:allowBackup="true"
android:hardwareAccelerated="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher"
android:theme="@style/Theme.App"
android:usesCleartextTraffic="true">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".XashActivity"
android:alwaysRetainTaskState="true"
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation|touchscreen"
android:exported="true"
android:launchMode="singleTask"
android:preferMinimalPostProcessing="true"
android:windowSoftInputMode="adjustResize"
tools:targetApi="r">
<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>
</activity>
<provider
android:name=".XashDocumentsProvider"
android:authorities="@string/authority"
android:exported="true"
android:grantUriPermissions="true"
android:permission="android.permission.MANAGE_DOCUMENTS">
<intent-filter>
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
</intent-filter>
</provider>
</application>
<queries>
<intent>
<action android:name="su.xash.engine.MOD" />
</intent>
</queries>
</manifest>

View file

@ -0,0 +1,41 @@
cmake_minimum_required(VERSION 3.6)
project(XASH_ANDROID)
# armeabi-v7a requires cpufeatures library
include(AndroidNdkModules)
android_ndk_import_module_cpufeatures()
find_package(PythonInterp 2.7 REQUIRED)
get_filename_component(C_COMPILER_ID ${CMAKE_C_COMPILER} NAME_WE)
get_filename_component(CXX_COMPILER_ID ${CMAKE_CXX_COMPILER} NAME_WE)
if(${CMAKE_BUILD_TYPE} STREQUAL "Debug")
set(BUILD_TYPE "debug")
else()
set(BUILD_TYPE "release")
endif()
set(CMAKE_VERBOSE_MAKEFILE ON)
set(WAF_CC "${CMAKE_C_COMPILER} --target=${CMAKE_C_COMPILER_TARGET}")
set(WAF_CXX "${CMAKE_CXX_COMPILER} --target=${CMAKE_CXX_COMPILER_TARGET}")
execute_process(
COMMAND ${CMAKE_COMMAND} -E env
CC=${WAF_CC} CXX=${WAF_CXX}
AR=${CMAKE_AR} STRIP=${CMAKE_STRIP}
${PYTHON_EXECUTABLE} waf configure -vvv -T ${BUILD_TYPE} cmake
--check-c-compiler=${C_COMPILER_ID} --check-cxx-compiler=${CXX_COMPILER_ID}
-s "${CMAKE_CURRENT_SOURCE_DIR}/SDL" --skip-sdl2-sanity-check
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/xash3d-fwgs"
)
if(CMAKE_SIZEOF_VOID_P MATCHES "8")
set(64BIT ON CACHE BOOL "" FORCE)
endif()
add_subdirectory("hlsdk-portable")
add_subdirectory("SDL")
add_subdirectory("xash3d-fwgs")
add_subdirectory("xash3d-fwgs/3rdparty/mainui")

@ -0,0 +1 @@
Subproject commit 2eef7ca475decd2b864214cdbfe72b143b16d459

@ -0,0 +1 @@
Subproject commit 0d8a19fd82758746cc41af5e18946a9410e4533f

@ -0,0 +1 @@
Subproject commit 1c84a5c8ade7cfac17d6b558669d4f721a0bf11f

View file

@ -0,0 +1,49 @@
package su.xash.engine;
import android.app.Activity;
import android.graphics.Rect;
import android.view.View;
import android.widget.FrameLayout;
public class AndroidBug5497Workaround {
// For more information, see https://code.google.com/p/android/issues/detail?id=5497
// To use this class, simply invoke assistActivity() on an Activity that already has its content view set.
public static void assistActivity(Activity activity) {
new AndroidBug5497Workaround(activity);
}
private View mChildOfContent;
private int usableHeightPrevious;
private FrameLayout.LayoutParams frameLayoutParams;
private AndroidBug5497Workaround(Activity activity) {
FrameLayout content = activity.findViewById(android.R.id.content);
mChildOfContent = content.getChildAt(0);
mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(this::possiblyResizeChildOfContent);
frameLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams();
}
private void possiblyResizeChildOfContent() {
int usableHeightNow = computeUsableHeight();
if (usableHeightNow != usableHeightPrevious) {
int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight();
int heightDifference = usableHeightSansKeyboard - usableHeightNow;
if (heightDifference > (usableHeightSansKeyboard / 4)) {
// keyboard probably just became visible
frameLayoutParams.height = usableHeightSansKeyboard - heightDifference;
} else {
// keyboard probably just became hidden
frameLayoutParams.height = usableHeightSansKeyboard;
}
mChildOfContent.requestLayout();
usableHeightPrevious = usableHeightNow;
}
}
private int computeUsableHeight() {
Rect r = new Rect();
mChildOfContent.getWindowVisibleDisplayFrame(r);
return (r.bottom - r.top);
}
}

View file

@ -0,0 +1,4 @@
package su.xash.engine
class DedicatedActivity {
}

View file

@ -0,0 +1,11 @@
package su.xash.engine
import android.app.Service
import android.content.Intent
import android.os.IBinder
class DedicatedService: Service() {
override fun onBind(intent: Intent?): IBinder? {
TODO("Not yet implemented")
}
}

View file

@ -0,0 +1,38 @@
package su.xash.engine
import android.os.Bundle
import android.os.PersistableBundle
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.NavController
import androidx.navigation.findNavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.navigateUp
import androidx.navigation.ui.setupActionBarWithNavController
import androidx.navigation.ui.setupWithNavController
import su.xash.engine.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var appBarConfiguration: AppBarConfiguration
private lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
setSupportActionBar(binding.toolbar)
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.fragmentContainerView) as NavHostFragment
navController = navHostFragment.navController
appBarConfiguration = AppBarConfiguration(navController.graph)
setupActionBarWithNavController(navController, appBarConfiguration)
}
override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
}

View file

@ -0,0 +1,28 @@
package su.xash.engine
import android.app.Application
import android.content.Context
import android.os.StrictMode
import org.acra.config.httpSender
import org.acra.data.StringFormat
import org.acra.ktx.initAcra
class MainApplication : Application() {
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
if (!BuildConfig.DEBUG) {
initAcra {
buildConfigClass = BuildConfig::class.java
reportFormat = StringFormat.JSON
httpSender {
uri = "http://bodis.pp.ua:5000/report"
}
}
} else {
// enable strict mode to detect memory leaks etc.
StrictMode.enableDefaults();
}
}
}

View file

@ -0,0 +1,138 @@
package su.xash.engine;
import android.annotation.SuppressLint;
import android.content.pm.ActivityInfo;
import android.content.res.AssetManager;
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings.Secure;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.WindowManager;
import org.libsdl.app.SDLActivity;
public class XashActivity extends SDLActivity {
private boolean mUseVolumeKeys;
private String mPackageName;
private static final String TAG = "XashActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
//getWindow().addFlags(WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES);
getWindow().getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
}
AndroidBug5497Workaround.assistActivity(this);
}
@Override
protected String[] getLibraries() {
return new String[]{"SDL2", "xash"};
}
@SuppressLint("HardwareIds")
private String getAndroidID() {
return Secure.getString(getContentResolver(), Secure.ANDROID_ID);
}
@SuppressLint("ApplySharedPref")
private void saveAndroidID(String id) {
getSharedPreferences("xash_preferences", MODE_PRIVATE).edit().putString("xash_id", id).commit();
}
private String loadAndroidID() {
return getSharedPreferences("xash_preferences", MODE_PRIVATE).getString("xash_id", "");
}
@Override
public String getCallingPackage() {
if (mPackageName != null) {
return mPackageName;
}
return super.getCallingPackage();
}
private AssetManager getAssets(boolean isEngine) {
AssetManager am = null;
if (isEngine) {
am = getAssets();
} else {
try {
am = getPackageManager().getResourcesForApplication(getCallingPackage()).getAssets();
} catch (Exception e) {
Log.e(TAG, "Unable to load mod assets!");
e.printStackTrace();
}
}
return am;
}
private String[] getAssetsList(boolean isEngine, String path) {
AssetManager am = getAssets(isEngine);
try {
return am.list(path);
} catch (Exception e) {
e.printStackTrace();
}
return new String[]{};
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (SDLActivity.mBrokenLibraries) {
return false;
}
int keyCode = event.getKeyCode();
if (!mUseVolumeKeys) {
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ||
keyCode == KeyEvent.KEYCODE_VOLUME_UP ||
keyCode == KeyEvent.KEYCODE_CAMERA ||
keyCode == KeyEvent.KEYCODE_ZOOM_IN ||
keyCode == KeyEvent.KEYCODE_ZOOM_OUT) {
return false;
}
}
return getWindow().superDispatchKeyEvent(event);
}
// TODO: REMOVE LATER, temporary launchers support?
@Override
protected String[] getArguments() {
String gamedir = getIntent().getStringExtra("gamedir");
if (gamedir == null) gamedir = "valve";
nativeSetenv("XASH3D_GAME", gamedir);
String gamelibdir = getIntent().getStringExtra("gamelibdir");
if (gamelibdir != null) nativeSetenv("XASH3D_GAMELIBDIR", gamelibdir);
String pakfile = getIntent().getStringExtra("pakfile");
if (pakfile != null) nativeSetenv("XASH3D_EXTRAS_PAK2", pakfile);
mUseVolumeKeys = getIntent().getBooleanExtra("usevolume", false);
mPackageName = getIntent().getStringExtra("package");
String[] env = getIntent().getStringArrayExtra("env");
if (env != null) {
for (int i = 0; i < env.length; i += 2)
nativeSetenv(env[i], env[i + 1]);
}
String argv = getIntent().getStringExtra("argv");
if (argv == null) argv = "-dev 2 -log";
return argv.split(" ");
}
}

View file

@ -0,0 +1,254 @@
package su.xash.engine;
import android.annotation.TargetApi;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.graphics.Point;
import android.os.Build;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Root;
import android.provider.DocumentsProvider;
import android.webkit.MimeTypeMap;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
@TargetApi(Build.VERSION_CODES.KITKAT)
public class XashDocumentsProvider extends DocumentsProvider {
private static final String ALL_MIME_TYPES = "*/*";
private File mRootDir;
private static final String TAG = "XashDocumentsProvider";
@Override
public boolean onCreate() {
mRootDir = getContext().getExternalFilesDir(null);
return true;
}
private static final String[] DEFAULT_ROOT_PROJECTION = new String[]{Root.COLUMN_ROOT_ID,
Root.COLUMN_MIME_TYPES,
Root.COLUMN_FLAGS,
Root.COLUMN_ICON,
Root.COLUMN_TITLE,
Root.COLUMN_SUMMARY,
Root.COLUMN_DOCUMENT_ID,
Root.COLUMN_AVAILABLE_BYTES};
private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[]{Document.COLUMN_DOCUMENT_ID,
Document.COLUMN_MIME_TYPE,
Document.COLUMN_DISPLAY_NAME,
Document.COLUMN_LAST_MODIFIED,
Document.COLUMN_FLAGS,
Document.COLUMN_SIZE};
@Override
public Cursor queryRoots(String[] projection) {
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_ROOT_PROJECTION);
final String appName = getContext().getString(R.string.app_name);
final String docId = getDocIdForFile(mRootDir);
int flags;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_SEARCH;
} else {
flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD;
}
final MatrixCursor.RowBuilder row = result.newRow();
row.add(Root.COLUMN_ROOT_ID, docId);
row.add(Root.COLUMN_DOCUMENT_ID, docId);
row.add(Root.COLUMN_SUMMARY, null);
row.add(Root.COLUMN_FLAGS, flags);
row.add(Root.COLUMN_TITLE, appName);
row.add(Root.COLUMN_MIME_TYPES, ALL_MIME_TYPES);
row.add(Root.COLUMN_AVAILABLE_BYTES, mRootDir.getFreeSpace());
row.add(Root.COLUMN_ICON, R.mipmap.ic_launcher);
return result;
}
@Override
public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException {
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
includeFile(result, documentId, null);
return result;
}
@Override
public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder) throws FileNotFoundException {
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
final File parent = getFileForDocId(parentDocumentId);
final File[] filesList = parent.listFiles();
if (filesList != null) {
for (File file : filesList) {
includeFile(result, null, file);
}
}
return result;
}
@Override
public ParcelFileDescriptor openDocument(final String documentId, String mode, CancellationSignal signal) throws FileNotFoundException {
final File file = getFileForDocId(documentId);
final int accessMode = ParcelFileDescriptor.parseMode(mode);
return ParcelFileDescriptor.open(file, accessMode);
}
@Override
public AssetFileDescriptor openDocumentThumbnail(String documentId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException {
final File file = getFileForDocId(documentId);
final ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
return new AssetFileDescriptor(pfd, 0, file.length());
}
@Override
public String createDocument(String parentDocumentId, String mimeType, String displayName) throws FileNotFoundException {
File newFile = new File(parentDocumentId, displayName);
int noConflictId = 1;
while (newFile.exists()) {
newFile = new File(parentDocumentId, displayName + " (" + noConflictId++ + ")");
}
try {
boolean succeeded;
if (Document.MIME_TYPE_DIR.equals(mimeType)) {
succeeded = newFile.mkdir();
} else {
succeeded = newFile.createNewFile();
}
if (!succeeded) {
throw new FileNotFoundException("Failed to create document with id " + newFile.getPath());
}
} catch (IOException e) {
throw new FileNotFoundException("Failed to create document with id " + newFile.getPath());
}
return newFile.getPath();
}
@Override
public void deleteDocument(String documentId) throws FileNotFoundException {
File file = getFileForDocId(documentId);
if (file.isDirectory()) {
if (!deleteDirectory(file)) {
throw new FileNotFoundException("Failed to delete document with id " + documentId);
}
} else if (!file.delete()) {
throw new FileNotFoundException("Failed to delete document with id " + documentId);
}
}
@Override
public String getDocumentType(String documentId) throws FileNotFoundException {
File file = getFileForDocId(documentId);
return getMimeType(file);
}
@Override
public boolean isChildDocument(String parentDocumentId, String documentId) {
return documentId.startsWith(parentDocumentId);
}
private static File getFileForDocId(String docId) throws FileNotFoundException {
final File f = new File(docId);
if (!f.exists()) throw new FileNotFoundException(f.getAbsolutePath() + " not found");
return f;
}
private static String getDocIdForFile(File file) {
return file.getAbsolutePath();
}
private static String getMimeType(File file) {
if (file.isDirectory()) {
return Document.MIME_TYPE_DIR;
} else {
final String name = file.getName();
final int lastDot = name.lastIndexOf('.');
if (lastDot >= 0) {
final String extension = name.substring(lastDot + 1).toLowerCase();
final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
if (mime != null) return mime;
}
return "application/octet-stream";
}
}
private static boolean deleteDirectory(File dir) {
final File[] allContents = dir.listFiles();
if (allContents != null) {
for (File file : allContents) {
deleteDirectory(file);
}
}
return dir.delete();
}
private void includeFile(MatrixCursor result, String docId, File file) throws FileNotFoundException {
if (docId == null) {
docId = getDocIdForFile(file);
} else {
file = getFileForDocId(docId);
}
int flags = 0;
if (file.isDirectory()) {
if (file.canWrite()) {
flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
}
} else if (file.canWrite()) {
flags |= Document.FLAG_SUPPORTS_WRITE;
}
File parentFile = file.getParentFile();
if (parentFile != null && parentFile.canWrite()) {
flags |= Document.FLAG_SUPPORTS_DELETE;
}
final String displayName = file.getName();
final String mimeType = getMimeType(file);
if (mimeType.startsWith("image/")) {
flags |= Document.FLAG_SUPPORTS_THUMBNAIL;
}
final MatrixCursor.RowBuilder row = result.newRow();
row.add(Document.COLUMN_DOCUMENT_ID, docId);
row.add(Document.COLUMN_DISPLAY_NAME, displayName);
row.add(Document.COLUMN_SIZE, file.length());
row.add(Document.COLUMN_MIME_TYPE, mimeType);
row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified());
row.add(Document.COLUMN_FLAGS, flags);
row.add(Document.COLUMN_ICON, R.mipmap.ic_launcher);
}
}

View file

@ -0,0 +1,77 @@
package su.xash.engine.adapters
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.navigation.findNavController
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import su.xash.engine.R
import su.xash.engine.databinding.CardGameBinding
import su.xash.engine.model.Game
import su.xash.engine.ui.library.LibraryViewModel
class GameAdapter(private val libraryViewModel: LibraryViewModel) :
ListAdapter<Game, GameAdapter.GameViewHolder>(DiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GameAdapter.GameViewHolder {
val binding = CardGameBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return GameViewHolder(binding)
}
override fun onBindViewHolder(holder: GameAdapter.GameViewHolder, position: Int) {
return holder.bind(getItem(position))
}
private class DiffCallback : DiffUtil.ItemCallback<Game>() {
override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean {
return oldItem.basedir.name == newItem.basedir.name
}
override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean {
return oldItem.basedir.name == newItem.basedir.name && oldItem.installed == newItem.installed
}
}
inner class GameViewHolder(val binding: CardGameBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(game: Game) {
binding.apply {
gameTitle.text = game.title
if (game.icon != null) {
gameIcon.setImageBitmap(game.icon)
} else {
gameIcon.visibility = View.GONE
}
if (game.cover != null) {
gameCover.setImageBitmap(game.cover)
} else {
gameCover.visibility = View.GONE
}
if (!game.installed) {
launchButton.visibility = View.GONE
settingsButton.visibility = View.GONE
progressIndicator.visibility = View.VISIBLE
return
}
settingsButton.setOnClickListener {
libraryViewModel.setSelectedGame(game)
it.findNavController()
.navigate(R.id.action_libraryFragment_to_gameSettingsFragment)
}
root.setOnClickListener { libraryViewModel.startEngine(it.context, game) }
launchButton.setOnClickListener {
libraryViewModel.startEngine(it.context, game)
}
}
}
}
}

View file

@ -0,0 +1,89 @@
package su.xash.engine.model
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import androidx.documentfile.provider.DocumentFile
import su.xash.engine.util.TGAReader
import java.util.Scanner
object BackgroundBitmap {
private const val BACKGROUND_ROWS = 3
private const val BACKGROUND_COLUMNS = 4
private const val BACKGROUND_WIDTH = 800
private const val BACKGROUND_HEIGHT = 600
fun createBackground(ctx: Context, file: DocumentFile): Bitmap {
var bitmap =
Bitmap.createBitmap(BACKGROUND_WIDTH, BACKGROUND_HEIGHT, Bitmap.Config.ARGB_8888)
var canvas = Canvas(bitmap)
var x: Int
var y = 0
var width: Int
var height = 0
var bgLayout = file.findFile("resource")?.findFile("HD_BackgroundLayout.txt")
if (bgLayout == null) {
bgLayout = file.findFile("resource")?.findFile("BackgroundLayout.txt")
}
if (bgLayout == null) {
val dir = file.findFile("resource")?.findFile("background")
for (i in 0 until BACKGROUND_ROWS) {
x = 0
for (j in 0 until BACKGROUND_COLUMNS) {
val filename = "${BACKGROUND_WIDTH}_${i + 1}_${'a' + j}_loading.tga"
val bmpFile = dir?.findFile(filename)
val bmpImage = loadTga(ctx, bmpFile!!)
canvas.drawBitmap(bmpImage, x.toFloat(), y.toFloat(), null)
x += bmpImage.width
height = bmpImage.height
}
y += height
}
return bitmap
}
ctx.contentResolver.openInputStream(bgLayout.uri).use { inputStream ->
Scanner(inputStream).use { scanner ->
while (scanner.hasNext()) {
when (val str = scanner.next()) {
"resolution" -> {
width = scanner.nextInt()
height = scanner.nextInt()
bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
canvas = Canvas(bitmap)
}
else -> {
var bmpFile = file
str.split("/").forEach { bmpFile = bmpFile.findFile(it)!! }
//skip
scanner.next()
x = scanner.nextInt()
y = scanner.nextInt()
val bmp = loadTga(ctx, bmpFile)
canvas.drawBitmap(bmp, x.toFloat(), y.toFloat(), null)
}
}
}
}
}
return bitmap
}
private fun loadTga(ctx: Context, file: DocumentFile): Bitmap {
ctx.contentResolver.openInputStream(file.uri).use {
val buffer = it?.readBytes()
val pixels = TGAReader.read(buffer, TGAReader.ARGB)
val width = TGAReader.getWidth(buffer)
val height = TGAReader.getHeight(buffer)
return Bitmap.createBitmap(pixels, 0, width, width, height, Bitmap.Config.ARGB_8888)
}
}
}

View file

@ -0,0 +1,129 @@
package su.xash.engine.model
import android.content.Context
import android.content.Intent
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.net.Uri
import android.provider.MediaStore
import androidx.documentfile.provider.DocumentFile
import su.xash.engine.XashActivity
class Game(val ctx: Context, val basedir: DocumentFile, var installed: Boolean = true) {
private var iconName = "game.ico"
var title = "Unknown Game"
var icon: Bitmap? = null
var cover: Bitmap? = null
private val pref = ctx.getSharedPreferences(basedir.name, Context.MODE_PRIVATE)
init {
basedir.findFile("gameinfo.txt")?.let {
parseGameInfo(it)
} ?: basedir.findFile("liblist.gam")?.let { parseGameInfo(it) }
basedir.findFile(iconName)
?.let { icon = MediaStore.Images.Media.getBitmap(ctx.contentResolver, it.uri) }
try {
cover = BackgroundBitmap.createBackground(ctx, basedir)
} catch (e: Exception) {
e.printStackTrace()
}
}
fun startEngine(ctx: Context) {
ctx.startActivity(Intent(ctx, XashActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
putExtra("gamedir", basedir.name)
putExtra("argv", pref.getString("arguments", "-dev 2 -log"))
putExtra("usevolume", pref.getBoolean("use_volume_buttons", false))
//.putExtra("gamelibdir", getGameLibDir(context))
//.putExtra("package", getPackageName()) }
})
}
private fun parseGameInfo(file: DocumentFile) {
ctx.contentResolver.openInputStream(file.uri).use { inputStream ->
inputStream?.bufferedReader().use { reader ->
reader?.forEachLine {
val tokens = it.split("\\s+".toRegex(), limit = 2)
if (tokens.size >= 2) {
val k = tokens[0]
val v = tokens[1].trim('"')
if (k == "title" || k == "game") title = v
if (k == "icon") iconName = v
}
}
}
}
}
private fun getPackageName(): String? {
// return if (mDbEntry != null) {
// mDbEntry.getPackageName()
// } else null
return null
}
private fun getGameLibDir(ctx: Context): String? {
val pkgName = getPackageName()
if (pkgName != null) {
val pkgInfo: PackageInfo = try {
ctx.packageManager.getPackageInfo(pkgName, 0)
} catch (e: PackageManager.NameNotFoundException) {
e.printStackTrace()
ctx.startActivity(
Intent(
Intent.ACTION_VIEW, Uri.parse("market://details?id=$pkgName")
).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
)
return null
}
return pkgInfo.applicationInfo.nativeLibraryDir
}
return ctx.applicationInfo.nativeLibraryDir
}
companion object {
fun getGames(ctx: Context, file: DocumentFile): List<Game> {
val games = mutableListOf<Game>()
if (checkIfGamedir(file)) {
games.add(Game(ctx, file))
} else {
file.listFiles().forEach {
if (it.isDirectory) {
if (checkIfGamedir(it)) {
games.add(Game(ctx, it))
}
}
}
}
return games
}
fun checkIfGamedir(file: DocumentFile): Boolean {
file.findFile("liblist.gam")?.let { return true }
file.findFile("gameinfo.txt")?.let { return true }
return false
}
}
}
// Intent intent = new Intent("su.xash.engine.MOD");
// for (ResolveInfo info : context.getPackageManager()
// .queryIntentActivities(intent, PackageManager.GET_META_DATA)) {
// String packageName = info.activityInfo.applicationInfo.packageName;
// String gameDir = info.activityInfo.applicationInfo.metaData.getString(
// "su.xash.engine.gamedir");
// Log.d(TAG, "package = " + packageName + " gamedir = " + gameDir);
// }
//public void startEngine(Context context) {
// context.startActivity(new Intent(context, XashActivity.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP).putExtra("gamedir", getGameDir()).putExtra("argv", getArguments()).putExtra("usevolume", getVolumeState()).putExtra("gamelibdir", getGameLibDir(context)).putExtra("package", getPackageName()));
//}

View file

@ -0,0 +1,59 @@
package su.xash.engine.model
import org.json.JSONArray
import org.json.JSONObject
import java.io.InputStream
class ModDatabase(inputStream: InputStream) {
val entries = mutableListOf<Entry>()
companion object {
const val VERSION = 1
fun getFilename(): String {
return "v${VERSION}.json"
}
}
init {
inputStream.bufferedReader().use {
val jsonArray = JSONArray(it.readText())
for (i in 0..<jsonArray.length()) {
entries.add(Entry(jsonArray.getJSONObject(i)))
}
}
}
fun getByGameDir(gamedir: String): Entry? {
return entries.filter { it.gamedir.equals(gamedir) }.firstOrNull()
}
inner class Entry(jsonObject: JSONObject) {
var name: String? = null
var appid: Int? = null
var gamedir: String? = null
// TODO Depots
var pkgname: String? = null
init {
if (jsonObject.has("name")) {
name = jsonObject.getString("name");
}
if (jsonObject.has("app_id")) {
appid = jsonObject.getInt("app_id");
}
if (jsonObject.has("gamedir")) {
gamedir = jsonObject.getString("gamedir");
}
if (jsonObject.has("package_name")) {
pkgname = jsonObject.getString("package_name");
}
}
}
}

View file

@ -0,0 +1,94 @@
package su.xash.engine.ui.library
import android.annotation.SuppressLint
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.core.view.MenuProvider
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController
import kotlinx.coroutines.launch
import su.xash.engine.R
import su.xash.engine.adapters.GameAdapter
import su.xash.engine.databinding.FragmentLibraryBinding
class LibraryFragment : Fragment(), MenuProvider {
private var _binding: FragmentLibraryBinding? = null
private val binding get() = _binding!!
private val libraryViewModel: LibraryViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
_binding = FragmentLibraryBinding.inflate(inflater, container, false)
val adapter = GameAdapter(libraryViewModel)
binding.gamesList.adapter = adapter
requireActivity().addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.swipeRefresh.setOnRefreshListener { libraryViewModel.reloadGames(requireContext()) }
libraryViewModel.isReloading.observe(viewLifecycleOwner) {
binding.swipeRefresh.isRefreshing = it
}
libraryViewModel.installedGames.observe(viewLifecycleOwner) {
(binding.gamesList.adapter as GameAdapter).submitList(it)
}
libraryViewModel.workInfos.observe(viewLifecycleOwner) {
libraryViewModel.refreshDownloads(requireContext())
}
libraryViewModel.downloads.observe(viewLifecycleOwner) {
libraryViewModel.reloadGames(requireContext())
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.menu_library, menu)
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
when (menuItem.itemId) {
R.id.action_browse -> {
startActivity(
Intent(Intent.ACTION_VIEW).setDataAndType(
null, "vnd.android.document/directory"
)
)
}
R.id.action_install -> {
findNavController().navigate(R.id.action_libraryFragment_to_setupFragment)
}
R.id.action_settings -> {
findNavController().navigate(R.id.action_libraryFragment_to_appSettingsFragment)
}
}
return false
}
}

View file

@ -0,0 +1,127 @@
package su.xash.engine.ui.library
import android.app.Application
import android.content.Context
import android.content.SharedPreferences
import android.net.Uri
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.viewModelScope
import androidx.preference.PreferenceManager
import androidx.work.Data
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.OutOfQuotaPolicy
import androidx.work.WorkInfo
import androidx.work.WorkManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import su.xash.engine.model.Game
import su.xash.engine.model.ModDatabase
import su.xash.engine.workers.FileCopyWorker
import su.xash.engine.workers.KEY_FILE_URI
import java.util.Locale
const val TAG_INSTALL = "TAG_INSTALL"
class LibraryViewModel(application: Application) : AndroidViewModel(application) {
val installedGames: LiveData<List<Game>> get() = _installedGames
private val _installedGames = MutableLiveData(emptyList<Game>())
val downloads: LiveData<List<Game>> get() = _downloads
private val _downloads = MutableLiveData(emptyList<Game>())
val isReloading: LiveData<Boolean> get() = _isReloading
private val _isReloading = MutableLiveData(false)
private val workManager = WorkManager.getInstance(application.applicationContext)
val workInfos: LiveData<List<WorkInfo>> = workManager.getWorkInfosByTagLiveData(TAG_INSTALL)
val selectedItem: LiveData<Game> get() = _selectedItem
private val _selectedItem = MutableLiveData<Game>()
val appPreferences = application.getSharedPreferences("app_preferences", Context.MODE_PRIVATE)
val modDb: ModDatabase
init {
modDb = application.assets.open(ModDatabase.getFilename()).use { ModDatabase(it) }
reloadGames(application.applicationContext)
}
fun reloadGames(ctx: Context) {
if (isReloading.value == true) {
return
}
_isReloading.value = true
viewModelScope.launch {
withContext(Dispatchers.IO) {
val games = mutableListOf<Game>()
val root = DocumentFile.fromFile(ctx.getExternalFilesDir(null)!!)
val installedGames = Game.getGames(ctx, root)
.filter { p -> _downloads.value?.any { p.basedir.name == it.basedir.name } == false }
games.addAll(installedGames)
downloads.value?.let { games.addAll(it) }
_installedGames.postValue(games)
_isReloading.postValue(false)
}
}
}
fun refreshDownloads(ctx: Context) {
viewModelScope.launch {
withContext(Dispatchers.IO) {
val games = mutableListOf<Game>()
workInfos.value?.filter {
it.state == WorkInfo.State.RUNNING && !it.progress.getString(FileCopyWorker.Input)
.isNullOrEmpty()
}?.forEach {
val uri = Uri.parse(it.progress.getString(FileCopyWorker.Input))
val file = DocumentFile.fromTreeUri(ctx, uri)
games.addAll(Game.getGames(ctx, file!!))
games.forEach { g -> g.installed = false }
}
_downloads.postValue(games)
}
}
}
fun installGame(uri: Uri) {
val data = Data.Builder().putString(KEY_FILE_URI, uri.toString()).build()
val request = OneTimeWorkRequestBuilder<FileCopyWorker>().run {
setInputData(data)
addTag(TAG_INSTALL)
build()
}
workManager.enqueue(request)
}
fun setSelectedGame(game: Game) {
_selectedItem.value = game
}
fun uninstallGame(game: Game) {
viewModelScope.launch {
withContext(Dispatchers.IO) {
game.installed = false
game.basedir.delete()
_installedGames.postValue(_installedGames.value)
}
}
}
fun startEngine(ctx: Context, game: Game) {
game.startEngine(ctx)
}
}

View file

@ -0,0 +1,37 @@
package su.xash.engine.ui.settings
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle
import androidx.navigation.fragment.findNavController
import su.xash.engine.R
import su.xash.engine.adapters.GameAdapter
import su.xash.engine.databinding.FragmentAppSettingsBinding
import su.xash.engine.databinding.FragmentGameSettingsBinding
import su.xash.engine.databinding.FragmentLibraryBinding
class AppSettingsFragment : Fragment() {
private var _binding: FragmentAppSettingsBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
_binding = FragmentAppSettingsBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
childFragmentManager.beginTransaction()
.add(binding.settingsFragment.id, AppSettingsPreferenceFragment()).commit();
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

View file

@ -0,0 +1,16 @@
package su.xash.engine.ui.settings
import android.os.Bundle
import androidx.preference.ListPreference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreferenceCompat
import su.xash.engine.BuildConfig
import su.xash.engine.R
import su.xash.engine.model.Game
class AppSettingsPreferenceFragment() : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
preferenceManager.sharedPreferencesName = "app_preferences";
setPreferencesFromResource(R.xml.app_preferences, rootKey);
}
}

View file

@ -0,0 +1,75 @@
package su.xash.engine.ui.settings
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.core.view.MenuProvider
import androidx.core.view.get
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.viewModelScope
import androidx.navigation.fragment.findNavController
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import su.xash.engine.R
import su.xash.engine.databinding.FragmentGameSettingsBinding
import su.xash.engine.ui.library.LibraryViewModel
class GameSettingsFragment : Fragment() {
private var _binding: FragmentGameSettingsBinding? = null
private val binding get() = _binding!!
private val libraryViewModel: LibraryViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
_binding = FragmentGameSettingsBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val game = libraryViewModel.selectedItem.value!!
binding.gameCard.apply {
gameTitle.text = game.title
if (game.icon != null) {
gameIcon.setImageBitmap(game.icon)
} else {
gameIcon.visibility = View.GONE
}
if (game.cover != null) {
gameCover.setImageBitmap(game.cover)
} else {
gameCover.visibility = View.GONE
}
buttonsContainer.visibility = View.GONE
}
childFragmentManager.beginTransaction()
.add(binding.settingsFragment.id, GameSettingsPreferenceFragment(game))
.commit();
binding.bottomNavigation.menu.findItem(R.id.action_uninstall).setOnMenuItemClickListener {
libraryViewModel.uninstallGame(game)
findNavController().popBackStack()
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

View file

@ -0,0 +1,41 @@
package su.xash.engine.ui.settings
import android.os.Bundle
import androidx.preference.ListPreference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreferenceCompat
import su.xash.engine.BuildConfig
import su.xash.engine.R
import su.xash.engine.model.Game
class GameSettingsPreferenceFragment(val game: Game) : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
preferenceManager.sharedPreferencesName = game.basedir.name;
setPreferencesFromResource(R.xml.game_preferences, rootKey);
val packageList = findPreference<ListPreference>("package_name")!!
packageList.entries = arrayOf(getString(R.string.app_name))
packageList.entryValues = arrayOf(requireContext().packageName)
if (packageList.value == null) {
packageList.setValueIndex(0);
}
val separatePackages = findPreference<SwitchPreferenceCompat>("separate_libraries")!!
val clientPackage = findPreference<ListPreference>("client_package")!!
val serverPackage = findPreference<ListPreference>("server_package")!!
separatePackages.setOnPreferenceChangeListener { _, newValue ->
if (newValue == true) {
packageList.isVisible = false
clientPackage.isVisible = true
serverPackage.isVisible = true
} else {
packageList.isVisible = true
clientPackage.isVisible = false
serverPackage.isVisible = false
}
true
}
}
}

View file

@ -0,0 +1,64 @@
package su.xash.engine.ui.setup
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import androidx.viewbinding.ViewBinding
import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2
import androidx.work.Data
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import su.xash.engine.R
import su.xash.engine.databinding.FragmentLibraryBinding
import su.xash.engine.databinding.FragmentSetupBinding
import su.xash.engine.databinding.PageLocationBinding
import su.xash.engine.databinding.PageWelcomeBinding
import su.xash.engine.ui.library.LibraryViewModel
import su.xash.engine.ui.setup.pages.LocationPageFragment
import su.xash.engine.ui.setup.pages.WelcomePageFragment
import su.xash.engine.workers.FileCopyWorker
class SetupFragment : Fragment() {
private var _binding: FragmentSetupBinding? = null
private val binding get() = _binding!!
private val setupViewModel: SetupViewModel by activityViewModels()
private lateinit var setupPageAdapter: SetupPageAdapter
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
_binding = FragmentSetupBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
setupPageAdapter = SetupPageAdapter(this)
binding.viewPager.isUserInputEnabled = false
binding.viewPager.adapter = setupPageAdapter
setupViewModel.pageNumber.observe(viewLifecycleOwner) {
binding.viewPager.setCurrentItem(it, true)
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
class SetupPageAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {
val pages = listOf(WelcomePageFragment(), LocationPageFragment())
override fun getItemCount(): Int = 2
override fun createFragment(position: Int): Fragment = pages[position]
}

View file

@ -0,0 +1,29 @@
package su.xash.engine.ui.setup
import android.app.Application
import android.net.Uri
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.work.Data
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import su.xash.engine.MainApplication
import su.xash.engine.model.Game
import su.xash.engine.workers.FileCopyWorker
class SetupViewModel(application: Application) : AndroidViewModel(application) {
val pageNumber: LiveData<Int> get() = _pageNumber
private val _pageNumber = MutableLiveData(0)
fun checkIfGameDir(uri: Uri): Boolean {
val ctx = getApplication<MainApplication>().applicationContext
val file = DocumentFile.fromTreeUri(ctx, uri)!!
return Game.checkIfGamedir(file)
}
fun setPageNumber(pos: Int) {
_pageNumber.value = pos
}
}

View file

@ -0,0 +1,62 @@
package su.xash.engine.ui.setup.pages
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import androidx.viewbinding.ViewBinding
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import su.xash.engine.R
import su.xash.engine.databinding.PageLocationBinding
import su.xash.engine.databinding.PageWelcomeBinding
import su.xash.engine.ui.library.LibraryViewModel
import su.xash.engine.ui.setup.SetupFragment
import su.xash.engine.ui.setup.SetupViewModel
class LocationPageFragment : Fragment() {
private var _binding: PageLocationBinding? = null
private val binding get() = _binding!!
private val setupViewModel: SetupViewModel by activityViewModels()
private val libraryViewModel: LibraryViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
_binding = PageLocationBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.pageButton.setOnClickListener {
getGamesDirectory.launch(null)
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
private val getGamesDirectory =
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) {
it?.let {
if (!setupViewModel.checkIfGameDir(it)) {
MaterialAlertDialogBuilder(requireContext()).apply {
setTitle(R.string.error)
setMessage(R.string.setup_location_empty)
setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
show()
}
} else {
requireContext().contentResolver.takePersistableUriPermission(it, Intent.FLAG_GRANT_READ_URI_PERMISSION)
libraryViewModel.installGame(it)
findNavController().navigate(R.id.action_setupFragment_to_libraryFragment)
}
}
}
}

View file

@ -0,0 +1,38 @@
package su.xash.engine.ui.setup.pages
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.viewbinding.ViewBinding
import su.xash.engine.databinding.PageLocationBinding
import su.xash.engine.databinding.PageWelcomeBinding
import su.xash.engine.ui.setup.SetupFragment
import su.xash.engine.ui.setup.SetupViewModel
class WelcomePageFragment : Fragment() {
private var _binding: PageWelcomeBinding? = null
private val binding get() = _binding!!
private val setupViewModel: SetupViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
_binding = PageWelcomeBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.pageButton.setOnClickListener {
setupViewModel.setPageNumber(1)
}
setupViewModel.setPageNumber(0)
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

View file

@ -0,0 +1,589 @@
/**
* TGAReader.java
* <p>
* Copyright (c) 2014 Kenji Sasaki
* Released under the MIT license.
* https://github.com/npedotnet/TGAReader/blob/master/LICENSE
* <p>
* English document
* https://github.com/npedotnet/TGAReader/blob/master/README.md
* <p>
* Japanese document
* http://3dtech.jp/wiki/index.php?TGAReader
*/
package su.xash.engine.util;
import java.io.IOException;
public final class TGAReader {
public static final Order ARGB = new Order(16, 8, 0, 24);
public static final Order ABGR = new Order(0, 8, 16, 24);
public static int getWidth(byte[] buffer) {
return (buffer[12] & 0xFF) | (buffer[13] & 0xFF) << 8;
}
public static int getHeight(byte[] buffer) {
return (buffer[14] & 0xFF) | (buffer[15] & 0xFF) << 8;
}
public static int[] read(byte[] buffer, Order order) throws IOException {
// header
// int idFieldLength = buffer[0] & 0xFF;
// int colormapType = buffer[1] & 0xFF;
int type = buffer[2] & 0xFF;
int colormapOrigin = (buffer[3] & 0xFF) | (buffer[4] & 0xFF) << 8;
int colormapLength = (buffer[5] & 0xFF) | (buffer[6] & 0xFF) << 8;
int colormapDepth = buffer[7] & 0xFF;
// int originX = (buffer[8] & 0xFF) | (buffer[9] & 0xFF) << 8; // unsupported
// int originY = (buffer[10] & 0xFF) | (buffer[11] & 0xFF) << 8; // unsupported
int width = getWidth(buffer);
int height = getHeight(buffer);
int depth = buffer[16] & 0xFF;
int descriptor = buffer[17] & 0xFF;
int[] pixels;
// data
switch (type) {
case COLORMAP: {
int imageDataOffset = 18 + (colormapDepth / 8) * colormapLength;
pixels = createPixelsFromColormap(width, height, colormapDepth, buffer, imageDataOffset, buffer, colormapOrigin, descriptor, order);
}
break;
case RGB:
pixels = createPixelsFromRGB(width, height, depth, buffer, 18, descriptor, order);
break;
case GRAYSCALE:
pixels = createPixelsFromGrayscale(width, height, depth, buffer, 18, descriptor, order);
break;
case COLORMAP_RLE: {
int imageDataOffset = 18 + (colormapDepth / 8) * colormapLength;
byte[] decodeBuffer = decodeRLE(width, height, depth, buffer, imageDataOffset);
pixels = createPixelsFromColormap(width, height, colormapDepth, decodeBuffer, 0, buffer, colormapOrigin, descriptor, order);
}
break;
case RGB_RLE: {
byte[] decodeBuffer = decodeRLE(width, height, depth, buffer, 18);
pixels = createPixelsFromRGB(width, height, depth, decodeBuffer, 0, descriptor, order);
}
break;
case GRAYSCALE_RLE: {
byte[] decodeBuffer = decodeRLE(width, height, depth, buffer, 18);
pixels = createPixelsFromGrayscale(width, height, depth, decodeBuffer, 0, descriptor, order);
}
break;
default:
throw new IOException("Unsupported image type: " + type);
}
return pixels;
}
private static final int COLORMAP = 1;
private static final int RGB = 2;
private static final int GRAYSCALE = 3;
private static final int COLORMAP_RLE = 9;
private static final int RGB_RLE = 10;
private static final int GRAYSCALE_RLE = 11;
private static final int RIGHT_ORIGIN = 0x10;
private static final int UPPER_ORIGIN = 0x20;
private static byte[] decodeRLE(int width, int height, int depth, byte[] buffer, int offset) {
int elementCount = depth / 8;
byte[] elements = new byte[elementCount];
int decodeBufferLength = elementCount * width * height;
byte[] decodeBuffer = new byte[decodeBufferLength];
int decoded = 0;
while (decoded < decodeBufferLength) {
int packet = buffer[offset++] & 0xFF;
if ((packet & 0x80) != 0) { // RLE
for (int i = 0; i < elementCount; i++) {
elements[i] = buffer[offset++];
}
int count = (packet & 0x7F) + 1;
for (int i = 0; i < count; i++) {
for (int j = 0; j < elementCount; j++) {
decodeBuffer[decoded++] = elements[j];
}
}
} else { // RAW
int count = (packet + 1) * elementCount;
for (int i = 0; i < count; i++) {
decodeBuffer[decoded++] = buffer[offset++];
}
}
}
return decodeBuffer;
}
private static int[] createPixelsFromColormap(int width, int height, int depth, byte[] bytes, int offset, byte[] palette, int colormapOrigin, int descriptor, Order order) throws IOException {
int[] pixels;
int rs = order.redShift;
int gs = order.greenShift;
int bs = order.blueShift;
int as = order.alphaShift;
switch (depth) {
case 24:
pixels = new int[width * height];
if ((descriptor & RIGHT_ORIGIN) != 0) {
if ((descriptor & UPPER_ORIGIN) != 0) {
// UpperRight
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int colormapIndex = bytes[offset + width * i + j] &
0xFF - colormapOrigin;
int color = 0xFFFFFFFF;
if (colormapIndex >= 0) {
int index = 3 * colormapIndex + 18;
int b = palette[index] & 0xFF;
int g = palette[index + 1] & 0xFF;
int r = palette[index + 2] & 0xFF;
int a = 0xFF;
color = (r << rs) | (g << gs) | (b << bs) | (a << as);
}
pixels[width * i + (width - j - 1)] = color;
}
}
} else {
// LowerRight
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int colormapIndex = bytes[offset + width * i + j] &
0xFF - colormapOrigin;
int color = 0xFFFFFFFF;
if (colormapIndex >= 0) {
int index = 3 * colormapIndex + 18;
int b = palette[index] & 0xFF;
int g = palette[index + 1] & 0xFF;
int r = palette[index + 2] & 0xFF;
int a = 0xFF;
color = (r << rs) | (g << gs) | (b << bs) | (a << as);
}
pixels[width * (height - i - 1) + (width - j - 1)] = color;
}
}
}
} else {
if ((descriptor & UPPER_ORIGIN) != 0) {
// UpperLeft
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int colormapIndex = bytes[offset + width * i + j] &
0xFF - colormapOrigin;
int color = 0xFFFFFFFF;
if (colormapIndex >= 0) {
int index = 3 * colormapIndex + 18;
int b = palette[index] & 0xFF;
int g = palette[index + 1] & 0xFF;
int r = palette[index + 2] & 0xFF;
int a = 0xFF;
color = (r << rs) | (g << gs) | (b << bs) | (a << as);
}
pixels[width * i + j] = color;
}
}
} else {
// LowerLeft
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int colormapIndex = bytes[offset + width * i + j] &
0xFF - colormapOrigin;
int color = 0xFFFFFFFF;
if (colormapIndex >= 0) {
int index = 3 * colormapIndex + 18;
int b = palette[index] & 0xFF;
int g = palette[index + 1] & 0xFF;
int r = palette[index + 2] & 0xFF;
int a = 0xFF;
color = (r << rs) | (g << gs) | (b << bs) | (a << as);
}
pixels[width * (height - i - 1) + j] = color;
}
}
}
}
break;
case 32:
pixels = new int[width * height];
if ((descriptor & RIGHT_ORIGIN) != 0) {
if ((descriptor & UPPER_ORIGIN) != 0) {
// UpperRight
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int colormapIndex = bytes[offset + width * i + j] &
0xFF - colormapOrigin;
int color = 0xFFFFFFFF;
if (colormapIndex >= 0) {
int index = 4 * colormapIndex + 18;
int b = palette[index] & 0xFF;
int g = palette[index + 1] & 0xFF;
int r = palette[index + 2] & 0xFF;
int a = palette[index + 3] & 0xFF;
color = (r << rs) | (g << gs) | (b << bs) | (a << as);
}
pixels[width * i + (width - j - 1)] = color;
}
}
} else {
// LowerRight
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int colormapIndex = bytes[offset + width * i + j] &
0xFF - colormapOrigin;
int color = 0xFFFFFFFF;
if (colormapIndex >= 0) {
int index = 4 * colormapIndex + 18;
int b = palette[index] & 0xFF;
int g = palette[index + 1] & 0xFF;
int r = palette[index + 2] & 0xFF;
int a = palette[index + 3] & 0xFF;
color = (r << rs) | (g << gs) | (b << bs) | (a << as);
}
pixels[width * (height - i - 1) + (width - j - 1)] = color;
}
}
}
} else {
if ((descriptor & UPPER_ORIGIN) != 0) {
// UpperLeft
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int colormapIndex = bytes[offset + width * i + j] &
0xFF - colormapOrigin;
int color = 0xFFFFFFFF;
if (colormapIndex >= 0) {
int index = 4 * colormapIndex + 18;
int b = palette[index] & 0xFF;
int g = palette[index + 1] & 0xFF;
int r = palette[index + 2] & 0xFF;
int a = palette[index + 3] & 0xFF;
color = (r << rs) | (g << gs) | (b << bs) | (a << as);
}
pixels[width * i + j] = color;
}
}
} else {
// LowerLeft
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int colormapIndex = bytes[offset + width * i + j] &
0xFF - colormapOrigin;
int color = 0xFFFFFFFF;
if (colormapIndex >= 0) {
int index = 4 * colormapIndex + 18;
int b = palette[index] & 0xFF;
int g = palette[index + 1] & 0xFF;
int r = palette[index + 2] & 0xFF;
int a = palette[index + 3] & 0xFF;
color = (r << rs) | (g << gs) | (b << bs) | (a << as);
}
pixels[width * (height - i - 1) + j] = color;
}
}
}
}
break;
default:
throw new IOException("Unsupported depth:" + depth);
}
return pixels;
}
private static int[] createPixelsFromRGB(int width, int height, int depth, byte[] bytes, int offset, int descriptor, Order order) throws IOException {
int[] pixels;
int rs = order.redShift;
int gs = order.greenShift;
int bs = order.blueShift;
int as = order.alphaShift;
switch (depth) {
case 24:
pixels = new int[width * height];
if ((descriptor & RIGHT_ORIGIN) != 0) {
if ((descriptor & UPPER_ORIGIN) != 0) {
// UpperRight
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int index = offset + 3 * width * i + 3 * j;
int b = bytes[index] & 0xFF;
int g = bytes[index + 1] & 0xFF;
int r = bytes[index + 2] & 0xFF;
int a = 0xFF;
pixels[width * i + (width - j - 1)] = (r << rs) |
(g << gs) |
(b << bs) |
(a << as);
}
}
} else {
// LowerRight
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int index = offset + 3 * width * i + 3 * j;
int b = bytes[index] & 0xFF;
int g = bytes[index + 1] & 0xFF;
int r = bytes[index + 2] & 0xFF;
int a = 0xFF;
pixels[width * (height - i - 1) + (width - j - 1)] = (r << rs) |
(g << gs) |
(b << bs) |
(a << as);
}
}
}
} else {
if ((descriptor & UPPER_ORIGIN) != 0) {
// UpperLeft
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int index = offset + 3 * width * i + 3 * j;
int b = bytes[index] & 0xFF;
int g = bytes[index + 1] & 0xFF;
int r = bytes[index + 2] & 0xFF;
int a = 0xFF;
pixels[width * i + j] = (r << rs) |
(g << gs) |
(b << bs) |
(a << as);
}
}
} else {
// LowerLeft
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int index = offset + 3 * width * i + 3 * j;
int b = bytes[index] & 0xFF;
int g = bytes[index + 1] & 0xFF;
int r = bytes[index + 2] & 0xFF;
int a = 0xFF;
pixels[width * (height - i - 1) + j] = (r << rs) |
(g << gs) |
(b << bs) |
(a << as);
}
}
}
}
break;
case 32:
pixels = new int[width * height];
if ((descriptor & RIGHT_ORIGIN) != 0) {
if ((descriptor & UPPER_ORIGIN) != 0) {
// UpperRight
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int index = offset + 4 * width * i + 4 * j;
int b = bytes[index] & 0xFF;
int g = bytes[index + 1] & 0xFF;
int r = bytes[index + 2] & 0xFF;
int a = bytes[index + 3] & 0xFF;
pixels[width * i + (width - j - 1)] = (r << rs) |
(g << gs) |
(b << bs) |
(a << as);
}
}
} else {
// LowerRight
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int index = offset + 4 * width * i + 4 * j;
int b = bytes[index] & 0xFF;
int g = bytes[index + 1] & 0xFF;
int r = bytes[index + 2] & 0xFF;
int a = bytes[index + 3] & 0xFF;
pixels[width * (height - i - 1) + (width - j - 1)] = (r << rs) |
(g << gs) |
(b << bs) |
(a << as);
}
}
}
} else {
if ((descriptor & UPPER_ORIGIN) != 0) {
// UpperLeft
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int index = offset + 4 * width * i + 4 * j;
int b = bytes[index] & 0xFF;
int g = bytes[index + 1] & 0xFF;
int r = bytes[index + 2] & 0xFF;
int a = bytes[index + 3] & 0xFF;
pixels[width * i + j] = (r << rs) |
(g << gs) |
(b << bs) |
(a << as);
}
}
} else {
// LowerLeft
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int index = offset + 4 * width * i + 4 * j;
int b = bytes[index] & 0xFF;
int g = bytes[index + 1] & 0xFF;
int r = bytes[index + 2] & 0xFF;
int a = bytes[index + 3] & 0xFF;
pixels[width * (height - i - 1) + j] = (r << rs) |
(g << gs) |
(b << bs) |
(a << as);
}
}
}
}
break;
default:
throw new IOException("Unsupported depth:" + depth);
}
return pixels;
}
private static int[] createPixelsFromGrayscale(int width, int height, int depth, byte[] bytes, int offset, int descriptor, Order order) throws IOException {
int[] pixels;
int rs = order.redShift;
int gs = order.greenShift;
int bs = order.blueShift;
int as = order.alphaShift;
switch (depth) {
case 8:
pixels = new int[width * height];
if ((descriptor & RIGHT_ORIGIN) != 0) {
if ((descriptor & UPPER_ORIGIN) != 0) {
// UpperRight
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int e = bytes[offset + width * i + j] & 0xFF;
int a = 0xFF;
pixels[width * i + (width - j - 1)] = (e << rs) |
(e << gs) |
(e << bs) |
(a << as);
}
}
} else {
// LowerRight
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int e = bytes[offset + width * i + j] & 0xFF;
int a = 0xFF;
pixels[width * (height - i - 1) + (width - j - 1)] = (e << rs) |
(e << gs) |
(e << bs) |
(a << as);
}
}
}
} else {
if ((descriptor & UPPER_ORIGIN) != 0) {
// UpperLeft
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int e = bytes[offset + width * i + j] & 0xFF;
int a = 0xFF;
pixels[width * i + j] = (e << rs) |
(e << gs) |
(e << bs) |
(a << as);
}
}
} else {
// LowerLeft
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int e = bytes[offset + width * i + j] & 0xFF;
int a = 0xFF;
pixels[width * (height - i - 1) + j] = (e << rs) |
(e << gs) |
(e << bs) |
(a << as);
}
}
}
}
break;
case 16:
pixels = new int[width * height];
if ((descriptor & RIGHT_ORIGIN) != 0) {
if ((descriptor & UPPER_ORIGIN) != 0) {
// UpperRight
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int e = bytes[offset + 2 * width * i + 2 * j] & 0xFF;
int a = bytes[offset + 2 * width * i + 2 * j + 1] & 0xFF;
pixels[width * i + (width - j - 1)] = (e << rs) |
(e << gs) |
(e << bs) |
(a << as);
}
}
} else {
// LowerRight
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int e = bytes[offset + 2 * width * i + 2 * j] & 0xFF;
int a = bytes[offset + 2 * width * i + 2 * j + 1] & 0xFF;
pixels[width * (height - i - 1) + (width - j - 1)] = (e << rs) |
(e << gs) |
(e << bs) |
(a << as);
}
}
}
} else {
if ((descriptor & UPPER_ORIGIN) != 0) {
// UpperLeft
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int e = bytes[offset + 2 * width * i + 2 * j] & 0xFF;
int a = bytes[offset + 2 * width * i + 2 * j + 1] & 0xFF;
pixels[width * i + j] = (e << rs) |
(e << gs) |
(e << bs) |
(a << as);
}
}
} else {
// LowerLeft
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int e = bytes[offset + 2 * width * i + 2 * j] & 0xFF;
int a = bytes[offset + 2 * width * i + 2 * j + 1] & 0xFF;
pixels[width * (height - i - 1) + j] = (e << rs) |
(e << gs) |
(e << bs) |
(a << as);
}
}
}
}
break;
default:
throw new IOException("Unsupported depth:" + depth);
}
return pixels;
}
private TGAReader() {
}
public static final class Order {
Order(int redShift, int greenShift, int blueShift, int alphaShift) {
this.redShift = redShift;
this.greenShift = greenShift;
this.blueShift = blueShift;
this.alphaShift = alphaShift;
}
public int redShift;
public int greenShift;
public int blueShift;
public int alphaShift;
}
}

View file

@ -0,0 +1,58 @@
package su.xash.engine.workers
import android.content.Context
import android.net.Uri
import android.provider.DocumentsContract
import android.util.Log
import androidx.documentfile.provider.DocumentFile
import androidx.work.CoroutineWorker
import androidx.work.Worker
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import su.xash.engine.model.Game
import java.io.FileInputStream
const val KEY_FILE_URI = "KEY_FILE_URI"
class FileCopyWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
companion object {
const val Input = "Input"
}
override suspend fun doWork(): Result {
withContext(Dispatchers.IO) {
val fileUri = inputData.getString(KEY_FILE_URI)
setProgress(workDataOf(Input to fileUri))
val target = DocumentFile.fromFile(applicationContext.getExternalFilesDir(null)!!)
val source = DocumentFile.fromTreeUri(applicationContext, Uri.parse(fileUri))
source?.copyDirTo(applicationContext, target) ?: return@withContext Result.failure()
}
return Result.success()
}
}
fun DocumentFile.copyFileTo(ctx: Context, file: DocumentFile) {
val outFile = file.createFile("application", name!!)!!
ctx.contentResolver.openOutputStream(outFile.uri).use { os ->
ctx.contentResolver.openInputStream(uri).use {
it?.copyTo(os!!)
}
}
}
fun DocumentFile.copyDirTo(ctx: Context, dir: DocumentFile) {
val outDir = dir.createDirectory(name!!)!!
listFiles().forEach {
if (it.isDirectory) {
it.copyDirTo(ctx, outDir)
} else {
it.copyFileTo(ctx, outDir)
}
}
}

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z" />
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M20,6h-8l-2,-2L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,8c0,-1.1 -0.9,-2 -2,-2zM20,18L4,18L4,8h16v10z" />
</vector>

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M21,10h-8.35C11.83,7.67 9.61,6 7,6c-3.31,0 -6,2.69 -6,6s2.69,6 6,6c2.61,0 4.83,-1.67 5.65,-4H13l2,2l2,-2l2,2l4,-4.04L21,10zM7,15c-1.65,0 -3,-1.35 -3,-3c0,-1.65 1.35,-3 3,-3s3,1.35 3,3C10,13.65 8.65,15 7,15z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M12,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"/>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#000000"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M8,5v14l11,-7z" />
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M19.14,12.94c0.04,-0.3 0.06,-0.61 0.06,-0.94c0,-0.32 -0.02,-0.64 -0.07,-0.94l2.03,-1.58c0.18,-0.14 0.23,-0.41 0.12,-0.61l-1.92,-3.32c-0.12,-0.22 -0.37,-0.29 -0.59,-0.22l-2.39,0.96c-0.5,-0.38 -1.03,-0.7 -1.62,-0.94L14.4,2.81c-0.04,-0.24 -0.24,-0.41 -0.48,-0.41h-3.84c-0.24,0 -0.43,0.17 -0.47,0.41L9.25,5.35C8.66,5.59 8.12,5.92 7.63,6.29L5.24,5.33c-0.22,-0.08 -0.47,0 -0.59,0.22L2.74,8.87C2.62,9.08 2.66,9.34 2.86,9.48l2.03,1.58C4.84,11.36 4.8,11.69 4.8,12s0.02,0.64 0.07,0.94l-2.03,1.58c-0.18,0.14 -0.23,0.41 -0.12,0.61l1.92,3.32c0.12,0.22 0.37,0.29 0.59,0.22l2.39,-0.96c0.5,0.38 1.03,0.7 1.62,0.94l0.36,2.54c0.05,0.24 0.24,0.41 0.48,0.41h3.84c0.24,0 0.44,-0.17 0.47,-0.41l0.36,-2.54c0.59,-0.24 1.13,-0.56 1.62,-0.94l2.39,0.96c0.22,0.08 0.47,0 0.59,-0.22l1.92,-3.32c0.12,-0.22 0.07,-0.47 -0.12,-0.61L19.14,12.94zM12,15.6c-1.98,0 -3.6,-1.62 -3.6,-3.6s1.62,-3.6 3.6,-3.6s3.6,1.62 3.6,3.6S13.98,15.6 12,15.6z" />
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M20,4H4C2.89,4 2,4.9 2,6v12c0,1.1 0.89,2 2,2h16c1.1,0 2,-0.9 2,-2V6C22,4.9 21.11,4 20,4zM20,18H4V8h16V18zM18,17h-6v-2h6V17zM7.5,17l-1.41,-1.41L8.67,13l-2.59,-2.59L7.5,9l4,4L7.5,17z" />
</vector>

View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="true">
<shape android:shape="rectangle">
<gradient android:endColor="#2D73FF" android:startColor="#06BFFF" />
<corners android:radius="4dp" />
</shape>
</item>
<item android:state_enabled="false">
<shape android:shape="rectangle">
<gradient android:endColor="#6D6D6D" android:startColor="#8F8F8F" />
<corners android:radius="4dp" />
</shape>
</item>
</selector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M12.004,2c-5.25,0 -9.556,4.05 -9.964,9.197l5.36,2.216c0.454,-0.31 1.002,-0.492 1.593,-0.492 0.053,0 0.104,0.003 0.157,0.005l2.384,-3.452v-0.049c0,-2.08 1.69,-3.77 3.77,-3.77 2.079,0 3.77,1.692 3.77,3.772s-1.692,3.771 -3.77,3.771h-0.087l-3.397,2.426c0,0.043 0.003,0.088 0.003,0.133 0,1.562 -1.262,2.83 -2.825,2.83 -1.362,0 -2.513,-0.978 -2.775,-2.273l-3.838,-1.589C3.573,18.922 7.427,22 12.005,22c5.522,0 9.998,-4.477 9.998,-10 0,-5.522 -4.477,-10 -9.999,-10zM7.078,16.667c0.218,0.452 0.595,0.832 1.094,1.041 1.081,0.45 2.328,-0.063 2.777,-1.145 0.22,-0.525 0.22,-1.1 0.004,-1.625 -0.215,-0.525 -0.625,-0.934 -1.147,-1.152 -0.52,-0.217 -1.075,-0.208 -1.565,-0.025l1.269,0.525c0.797,0.333 1.174,1.25 0.84,2.046 -0.33,0.797 -1.247,1.175 -2.044,0.843l-1.228,-0.508zM17.818,9.422c0,-1.385 -1.128,-2.512 -2.513,-2.512 -1.387,0 -2.512,1.127 -2.512,2.512 0,1.388 1.125,2.513 2.512,2.513 1.386,0 2.512,-1.125 2.512,-2.513zM15.31,7.53c1.04,0 1.888,0.845 1.888,1.888s-0.847,1.888 -1.888,1.888c-1.044,0 -1.888,-0.845 -1.888,-1.888s0.845,-1.888 1.888,-1.888z"/>
</vector>

View file

@ -0,0 +1,33 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="295.46dp"
android:height="90.47dp"
android:viewportWidth="295.46"
android:viewportHeight="90.47">
<path
android:fillColor="#FF000000"
android:pathData="m45.08,1c-23.24,0 -42.28,17.92 -44.08,40.69l23.71,9.8c2.01,-1.37 4.44,-2.18 7.05,-2.18 0.23,0 0.47,0.01 0.7,0.02l10.54,-15.28c0,-0.07 0,-0.14 0,-0.22 0,-9.2 7.48,-16.68 16.68,-16.68 9.2,0 16.68,7.48 16.68,16.68s-7.48,16.68 -16.68,16.68c-0.13,0 -0.25,0 -0.38,-0.01l-15.04,10.73c0.01,0.19 0.01,0.39 0.01,0.59 0,6.91 -5.62,12.52 -12.52,12.52 -6.06,0 -11.13,-4.33 -12.28,-10.06l-16.96,-7.01c5.25,18.57 22.31,32.18 42.56,32.18 24.43,0 44.24,-19.81 44.24,-44.24 0,-24.43 -19.81,-44.24 -44.24,-44.24"/>
<path
android:fillColor="#FF000000"
android:pathData="m28.72,68.12 l-5.43,-2.24c0.96,2.01 2.63,3.68 4.84,4.61 4.78,1.99 10.3,-0.28 12.29,-5.06 0.96,-2.31 0.97,-4.87 0.01,-7.19 -0.95,-2.32 -2.76,-4.13 -5.07,-5.1 -2.3,-0.96 -4.76,-0.92 -6.93,-0.1l5.61,2.32c3.53,1.47 5.2,5.52 3.72,9.05 -1.47,3.53 -5.52,5.2 -9.05,3.72"/>
<path
android:fillColor="#FF000000"
android:pathData="m70.8,33.83c0,-6.13 -4.99,-11.12 -11.12,-11.12 -6.13,0 -11.12,4.99 -11.12,11.12 0,6.13 4.99,11.11 11.12,11.11 6.13,0 11.12,-4.99 11.12,-11.11m-19.45,-0.02c0,-4.61 3.74,-8.35 8.35,-8.35s8.35,3.74 8.35,8.35 -3.74,8.35 -8.35,8.35 -8.35,-3.74 -8.35,-8.35"/>
<path
android:fillColor="#FF000000"
android:pathData="m136.56,31.27 l-2.96,5.21c-2.28,-1.6 -5.38,-2.56 -8.08,-2.56 -3.09,0 -5,1.28 -5,3.57 0,2.78 3.39,3.43 8.44,5.24 5.42,1.92 8.54,4.17 8.54,9.14 0,6.79 -5.34,10.61 -13.02,10.61 -3.74,0 -8.26,-0.97 -11.73,-3.08l2.16,-5.78c2.82,1.49 6.19,2.37 9.2,2.37 4.05,0 5.98,-1.5 5.98,-3.7 0,-2.53 -2.94,-3.29 -7.68,-4.86 -5.4,-1.8 -9.15,-4.17 -9.15,-9.67 0,-6.2 4.96,-9.76 12.1,-9.76 4.98,0 8.98,1.58 11.2,3.26"/>
<path
android:fillColor="#FF000000"
android:pathData="m152.76,61.9v-27.34h-10.13v-5.99h27.21v5.99h-10.1v27.34z"/>
<path
android:fillColor="#FF000000"
android:pathData="m197.9,42.05v5.99h-13.36v7.82h15.5v6.04h-22.47v-33.33h22.47v5.97h-15.5v7.51z"/>
<path
android:fillColor="#FF000000"
android:pathData="m215.62,55.43 l-2.21,6.47h-7.32l12.49,-33.33h7.03l12.85,33.32h-7.56l-2.25,-6.47h-13.03zM222.07,36.52 L217.51,49.87h9.2z"/>
<path
android:fillColor="#FF000000"
android:pathData="m261.22,60.93 l-8.97,-19.3v20.27h-6.68v-33.33h6.67l11.2,24.06 10.8,-24.06h6.73v33.33h-6.68v-20.44l-9.12,19.47z"/>
<path
android:fillColor="#FF000000"
android:pathData="m294.46,32.78c0,2.86 -2.15,4.65 -4.61,4.65 -2.47,0 -4.62,-1.78 -4.62,-4.65 0,-2.86 2.15,-4.64 4.62,-4.64 2.46,0 4.61,1.77 4.61,4.64m-8.46,0c0,2.4 1.73,3.9 3.85,3.9 2.11,0 3.83,-1.5 3.83,-3.9 0,-2.4 -1.72,-3.88 -3.83,-3.88 -2.12,0 -3.85,1.5 -3.85,3.88m3.91,-2.37c1.2,0 1.6,0.63 1.6,1.32 0,0.63 -0.37,1.05 -0.82,1.26l1.07,2.01h-0.88l-0.9,-1.78h-0.93v1.78h-0.73v-4.58zM289.05,32.54h0.81c0.53,0 0.84,-0.33 0.84,-0.75 0,-0.42 -0.22,-0.69 -0.84,-0.69h-0.81v1.44z"/>
</vector>

View file

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragmentContainerView"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/appBarLayout"
app:navGraph="@navigation/nav_graph"
tools:layout="@layout/fragment_library"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
style="@style/Widget.Material3.CardView.Filled"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="128dp">
<ImageView
android:id="@+id/gameCover"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
tools:src="@android:mipmap/sym_def_app_icon" />
<com.google.android.material.card.MaterialCardView
style="@style/Widget.Material3.CardView.Filled"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_margin="8dp"
app:contentPadding="10dp"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/buttonsContainer"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="match_parent">
<ImageView
android:id="@+id/gameIcon"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginRight="8dp"
android:adjustViewBounds="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/gameTitle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@android:mipmap/sym_def_app_icon" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/gameTitle"
style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:singleLine="true"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/gameIcon"
app:layout_constraintTop_toTopOf="parent"
tools:text="Game Title" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/buttonsContainer"
style="@style/Widget.Material3.CardView.Filled"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/progressIndicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true"
android:padding="10dp"
android:visibility="gone"
app:indicatorSize="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/launchButton"
style="@style/Widget.Material3.Button.IconButton"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:icon="@drawable/ic_baseline_play_arrow_24"
app:layout_constraintEnd_toStartOf="@+id/settingsButton" />
<com.google.android.material.button.MaterialButton
android:id="@+id/settingsButton"
style="@style/Widget.Material3.Button.IconButton"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:icon="@drawable/ic_baseline_settings_24"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>

View file

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- <com.google.android.material.textfield.TextInputLayout-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- app:layout_constraintTop_toTopOf="parent"-->
<!-- app:layout_constraintBottom_toBottomOf="parent">-->
<!-- <com.google.android.material.textfield.TextInputEditText-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content" />-->
<!-- </com.google.android.material.textfield.TextInputLayout>-->
<TextView
android:id="@android:id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"/>
<TextView
android:id="@android:id/summary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@android:id/title"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingHorizontal="8dp">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/settingsFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:layout="@android:layout/list_content" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
android:id="@+id/gameCard"
layout="@layout/card_game"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.card.MaterialCardView
style="@style/Widget.Material3.CardView.Filled"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:contentPadding="16dp"
app:layout_constraintTop_toBottomOf="@id/gameCard">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/settingsFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:layout="@android:layout/list_content" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomNavigation"
style="@style/Widget.MaterialComponents.BottomNavigationView.Colored"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:menu="@menu/menu_game_settings" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/swipeRefresh">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/gamesList"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="LinearLayoutManager"
android:clipToPadding="false"
android:paddingHorizontal="8dp"
tools:listitem="@layout/card_game" />
</RelativeLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/steam_background"
android:gravity="center_vertical"
android:orientation="vertical"
tools:context=".fragment.SteamLoginFragment">
<ImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:src="@drawable/steam_logo"
app:tint="#C5C3C0" />
<com.google.android.material.textfield.TextInputLayout
android:layout_width="320dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="16dp"
app:startIconDrawable="@drawable/ic_baseline_person_24">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/steamLogin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/username"
android:inputType="textEmailAddress" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="320dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="16dp"
app:startIconDrawable="@drawable/ic_baseline_key_24">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/steamPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/password"
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/loginButton"
style="@style/App.Theme.SteamButton"
android:layout_width="240dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="16dp"
android:text="@string/login" />
</LinearLayout>

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@android:id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"/>
<TextView
android:id="@android:id/summary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@android:id/title"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools">
<ImageView
android:id="@+id/pagePic"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="64dp"
android:layout_marginBottom="32dp"
android:src="@drawable/ic_baseline_folder_open_24"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/pageTitle"/>
<TextView
android:id="@+id/pageTitle"
style="@style/TextAppearance.Material3.TitleLarge"
android:layout_width="match_parent"
android:layout_height="0dp"
android:gravity="center"
android:paddingHorizontal="16dp"
android:text="@string/setup_location_title"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@id/pageMessage"
app:layout_constraintTop_toBottomOf="@id/pagePic" />
<TextView
android:id="@+id/pageMessage"
style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="match_parent"
android:layout_height="0dp"
android:gravity="center"
android:paddingHorizontal="16dp"
android:text="@string/setup_location_message"
app:layout_constraintBottom_toTopOf="@id/pageButton"
app:layout_constraintTop_toBottomOf="@id/pageTitle" />
<Button
android:id="@+id/pageButton"
android:layout_width="240dp"
android:layout_marginBottom="128dp"
android:layout_height="wrap_content"
android:text="@string/ok"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/pageMessage" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools">
<ImageView
android:id="@+id/pagePic"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="64dp"
android:layout_marginBottom="32dp"
android:src="@mipmap/ic_launcher"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/pageTitle"/>
<TextView
android:id="@+id/pageTitle"
style="@style/TextAppearance.Material3.TitleLarge"
android:layout_width="match_parent"
android:layout_height="0dp"
android:gravity="center"
android:textStyle="bold"
android:paddingHorizontal="16dp"
android:text="@string/setup_welcome_title"
app:layout_constraintBottom_toTopOf="@id/pageMessage"
app:layout_constraintTop_toBottomOf="@id/pagePic" />
<TextView
android:id="@+id/pageMessage"
style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="match_parent"
android:layout_height="0dp"
android:gravity="center"
android:paddingHorizontal="16dp"
android:text="@string/setup_welcome_message"
app:layout_constraintBottom_toTopOf="@id/pageButton"
app:layout_constraintTop_toBottomOf="@id/pageTitle" />
<Button
android:id="@+id/pageButton"
android:layout_width="240dp"
android:layout_marginBottom="128dp"
android:layout_height="wrap_content"
android:text="@string/next"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/pageMessage" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="32dp">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/steam_guard_code"
android:textAppearance="@style/TextAppearance.Material3.TitleLarge"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/textInputLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/steamCode"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="@string/steam_guard_code" />
</com.google.android.material.textfield.TextInputLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"/>
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/switchWidget"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,14 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_dedicated"
android:icon="@drawable/ic_baseline_terminal_24"
android:title="@string/dedicated_server"
app:showAsAction="always|withText"
android:visible="false"/>
<item
android:id="@+id/action_uninstall"
android:icon="@drawable/ic_baseline_delete_24"
android:title="@string/uninstall"
app:showAsAction="always|withText" />
</menu>

View file

@ -0,0 +1,19 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_browse"
android:icon="@drawable/ic_baseline_folder_open_24"
android:title="@string/browse"
app:showAsAction="always|withText" />
<item
android:id="@+id/action_install"
android:icon="@drawable/ic_baseline_add_24"
android:title="@string/install"
app:showAsAction="always|withText" />
<item
android:id="@+id/action_settings"
android:icon="@drawable/ic_baseline_settings_24"
android:title="@string/app_settings"
app:showAsAction="always|withText"
android:visible="false"/>
</menu>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome android:drawable="@mipmap/ic_launcher_monochrome"/>
</adaptive-icon>

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="108dp" android:viewportHeight="1024" android:viewportWidth="1024" android:width="108dp">
<path android:fillColor="#FFFFFF" android:pathData="M352.3,335.9l0,64.8l45.7,0l69.9,113l-109.9,171.8l87.3,0l68.4,-103.5l65.5,103.5l90,0l0,-62l-42,-0.7l-70.4,-112.1l109.3,-174.8l-84.1,0l-68.3,106.4l-69,-106.4z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="108dp" android:viewportHeight="1024" android:viewportWidth="1024" android:width="108dp">
<path android:fillColor="#FF000000" android:pathData="M352.3,335.9l0,64.8l45.7,0l69.9,113l-109.9,171.8l87.3,0l68.4,-103.5l65.5,103.5l90,0l0,-62l-42,-0.7l-70.4,-112.1l109.3,-174.8l-84.1,0l-68.3,106.4l-69,-106.4z"/>
</vector>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_graph"
android:label="@string/library"
app:startDestination="@id/libraryFragment">
<fragment
android:id="@+id/libraryFragment"
android:name="su.xash.engine.ui.library.LibraryFragment"
android:label="@string/library">
<action
android:id="@+id/action_libraryFragment_to_setupFragment"
app:destination="@id/setupFragment" />
<action
android:id="@+id/action_libraryFragment_to_gameSettingsFragment"
app:destination="@id/gameSettingsFragment" />
<action
android:id="@+id/action_libraryFragment_to_appSettingsFragment"
app:destination="@id/appSettingsFragment" />
</fragment>
<fragment
android:id="@+id/setupFragment"
android:name="su.xash.engine.ui.setup.SetupFragment"
android:label="@string/setup" >
<action
android:id="@+id/action_setupFragment_to_libraryFragment"
app:destination="@id/libraryFragment" />
</fragment>
<fragment
android:id="@+id/gameSettingsFragment"
android:name="su.xash.engine.ui.settings.GameSettingsFragment"
android:label="@string/game_settings" />
<fragment
android:id="@+id/appSettingsFragment"
android:name="su.xash.engine.ui.settings.AppSettingsFragment"
android:label="@string/app_settings" />
</navigation>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="ok">Ok</string>
<string name="cancel">Cancelar</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="cancel">Annulla</string>
<string name="ok">Ok</string>
</resources>

View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="ok">Ok</string>
<string name="cancel">Cancelar</string>
<string name="next">Próximo</string>
<string name="browse">Navegar</string>
<string name="install">Instalar</string>
<string name="library">Biblioteca</string>
<string name="steam_guard_code">Código do Steam Guard</string>
<string name="steam_login">Logar com Steam</string>
<string name="login">Logar</string>
<string name="username">Usuário</string>
<string name="password">Senha</string>
<string name="uninstall">Desinstalar</string>
<string name="dedicated_server">Dedicado</string>
<string name="error">Erro!</string>
<string name="setup">Configurar</string>
<string name="app_settings">Configurações</string>
<string name="game_settings">Configurações do jogo</string>
<string name="game_settings_command_line">Argumentos de linha de comando</string>
<string name="game_settings_volume_buttons">Usar botões de volume no jogo</string>
<string name="setup_welcome_title">Bem vindo ao Xash3D FWGS!</string>
<string name="setup_welcome_message">Agora, nós vamos guiá-lo durante o processo de instalação.</string>
<string name="setup_location_title">Jogos</string>
<string name="setup_location_message">Selecione a pasta onde seus jogos estão localizados.</string>
<string name="setup_location_empty">A pasta selecionada não contem nenhum jogo.</string>
<string name="setup_location_invalid">A pasta selecionada é invalida.</string>
<string name="preferences_package_name">Bibliotecas de pacotes</string>
<string name="preferences_separate_libraries">Bibliotecas de pacotes separados</string>
<string name="preferences_client_package">Pacotes de Cliente</string>
<string name="preferences_server_package">Pacotes de Servidor</string>
<string name="preferences_use_icons">Usar ícones ao inves de imagens de fundo</string>
</resources>

View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="cancel">Отмена</string>
<string name="ok">ОК</string>
<string name="next">Далее</string>
<string name="browse">Обзор</string>
<string name="install">Установить</string>
<string name="library">Библиотека</string>
<string name="steam_guard_code">Код Steam Guard</string>
<string name="steam_login">Логин Steam</string>
<string name="login">Логин</string>
<string name="username">Имя пользователя</string>
<string name="password">Пароль</string>
<string name="uninstall">Удалить</string>
<string name="dedicated_server">Выделенный</string>
<string name="error">Ошибка!</string>
<string name="setup">Установка</string>
<string name="app_settings">Настройки</string>
<string name="game_settings">Настройки игры</string>
<string name="game_settings_command_line">Аргументы командной строки</string>
<string name="game_settings_volume_buttons">Использовать кнопки громкости</string>
<string name="setup_welcome_title">Добро пожаловать в Xash3D FWGS!</string>
<string name="setup_welcome_message">Теперь мы ознакомим вас с процессом установки.</string>
<string name="setup_location_title">Игры</string>
<string name="setup_location_message">Выберите папку, в которой находятся ваши игры.</string>
<string name="setup_location_empty">Выбранный каталог не содержит ни одной игры.</string>
<string name="setup_location_invalid">Выбранный каталог недопустим.</string>
<string name="preferences_package_name">Пакет библиотек</string>
<string name="preferences_separate_libraries">Библиотеки из отдельных пакетов</string>
<string name="preferences_client_package">Пакет клиента</string>
<string name="preferences_server_package">Пакет сервера</string>
<string name="preferences_use_icons">Использовать иконки вместо фона</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="ok">Tamam</string>
<string name="cancel">İptal</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="ok">Ок</string>
<string name="cancel">Скасувати</string>
</resources>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="valve_red">#F74843</color>
<color name="steam_grey">#C5C3C0</color>
<color name="steam_background">#181A21</color>
<color name="steam_dark_grey">#32353C</color>
<color name="hl_orange">#FB7E14</color>
<color name="black">#000000</color>
<color name="hl_dark_grey">#151515</color>
<color name="hl_grey">#292929</color>
</resources>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#F74843</color>
</resources>

View file

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name" translatable="false">Xash3D FWGS</string>
<string name="authority" translatable="false">su.xash.engine.documents</string>
<string name="cancel">Cancel</string>
<string name="ok">OK</string>
<string name="next">Next</string>
<string name="browse">Browse</string>
<string name="install">Install</string>
<string name="library">Library</string>
<string name="steam_guard_code">Steam Guard Code</string>
<string name="steam_login">Steam Login</string>
<string name="login">Login</string>
<string name="username">Username</string>
<string name="password">Password</string>
<string name="steam" translatable="false">Steam</string>
<string name="uninstall">Uninstall</string>
<string name="dedicated_server">Dedicated</string>
<string name="error">Error!</string>
<string name="setup">Setup</string>
<string name="app_settings">Settings</string>
<string name="game_settings">Game Settings</string>
<string name="game_settings_command_line">Command-line arguments</string>
<string name="game_settings_volume_buttons">Use volume buttons in-game</string>
<string name="setup_welcome_title">Welcome to Xash3D FWGS!</string>
<string name="setup_welcome_message">Now, we will guide you through the installation process.</string>
<string name="setup_location_title">Games</string>
<string name="setup_location_message">Select the folder, where your games are located.</string>
<string name="setup_location_empty">Selected directory doesn\'t contain any games.</string>
<string name="setup_location_invalid">Selected directory is invalid.</string>
<string name="preferences_package_name">Libraries package</string>
<string name="preferences_separate_libraries">Libraries from separate packages</string>
<string name="preferences_client_package">Client package</string>
<string name="preferences_server_package">Server package</string>
<string name="preferences_use_icons">Use icons instead of backgrounds</string>
</resources>

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="App.Theme.SteamButton" parent="@style/Widget.Material3.Button">
<item name="android:background">@drawable/steam_button_gradient</item>
<item name="backgroundTint">@null</item>
<item name="android:textColor">#FFFFFF</item>
</style>
<style name="ShapeAppearance.App.MediumComponent" parent="ShapeAppearance.Material3.MediumComponent">
<item name="cornerSize">4dp</item>
</style>
</resources>

View file

@ -0,0 +1,17 @@
<resources>
<style name="Theme.App" parent="Theme.Material3.Dark.NoActionBar">
<item name="colorPrimary">@color/hl_orange</item>
<item name="colorPrimaryDark">@color/hl_dark_grey</item>
<!-- <item name="colorPrimaryContainer">@color/valve_red</item>-->
<item name="colorSurface">@color/hl_grey</item>
<item name="colorSurfaceVariant">@color/hl_grey</item>
<item name="android:colorBackground">@color/hl_dark_grey</item>
<item name="colorControlNormal">@color/hl_orange</item>
<item name="colorOnPrimary">@color/hl_dark_grey</item>
<!-- <item name="colorOnPrimaryContainer">@android:color/white</item>-->
<!-- <item name="colorOnSurface">@android:color/white</item>-->
<!-- <item name="colorOnSurfaceVariant">@android:color/white</item>-->
<!-- <item name="shapeAppearanceMediumComponent">@style/ShapeAppearance.App.MediumComponent</item>-->
</style>
</resources>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<SwitchPreferenceCompat
app:key="use_icons"
app:layout="@layout/switch_preference"
app:title="@string/preferences_use_icons" />
</PreferenceScreen>

View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<EditTextPreference
app:defaultValue="-dev 2 -log"
app:key="arguments"
app:layout="@layout/edit_text_preference"
app:title="@string/game_settings_command_line"
app:useSimpleSummaryProvider="true" />
<SwitchPreferenceCompat
app:key="use_volume_buttons"
app:layout="@layout/switch_preference"
app:title="@string/game_settings_volume_buttons" />
<SwitchPreferenceCompat
app:key="separate_libraries"
app:layout="@layout/switch_preference"
app:title="@string/preferences_separate_libraries"
app:isPreferenceVisible="false"/>
<ListPreference
app:key="package_name"
app:layout="@layout/list_preference"
app:title="@string/preferences_package_name"
app:useSimpleSummaryProvider="true"
app:isPreferenceVisible="false"/>
<ListPreference
app:key="client_package"
app:layout="@layout/list_preference"
app:title="@string/preferences_client_package"
app:useSimpleSummaryProvider="true"
app:isPreferenceVisible="false"/>
<ListPreference
app:key="server_package"
app:layout="@layout/list_preference"
app:title="@string/preferences_server_package"
app:useSimpleSummaryProvider="true"
app:isPreferenceVisible="false"/>
</PreferenceScreen>

Some files were not shown because too many files have changed in this diff Show more