Compare commits

..

15 Commits

Author SHA1 Message Date
Alibek Omarov
215feb3ba9 engine: client: support GoldSrc signon and refactor parsing delta entities for current and legacy protocols 2024-10-07 21:44:25 +03:00
Alibek Omarov
150e2607d9 engine: client: support parsing GoldSrc event messages 2024-10-07 21:16:00 +03:00
Alibek Omarov
e8452c68d7 engine: client: add support for parsing GoldSrc messages 2024-10-07 21:09:57 +03:00
Alibek Omarov
7fe0aba71c engine: client: refactor and implement GoldSrc-specific parsing bits in common message parsing code 2024-10-07 21:09:57 +03:00
Alibek Omarov
adcc9eb0df engine: client: add support for parsing GoldSrc svc_temp_entity 2024-10-07 21:09:57 +03:00
Alibek Omarov
081d1da640 engine: client: get rid of PROTOCOL_GOLDSRC_VERSION, as connprotocol_t enum does it's job 2024-10-07 21:09:57 +03:00
Alibek Omarov
4aadea40a3 engine: common: add GoldSrc delta support 2024-10-07 21:09:57 +03:00
Alibek Omarov
8deea9dc7e engine: common: add GoldSrc bitbuf operations, add support for GoldSrc signed integers 2024-10-07 19:34:19 +03:00
Alibek Omarov
d650d8c271 engine: common: add support for GoldSrc split packet 2024-10-07 19:11:30 +03:00
Alibek Omarov
eb89c8b222 engine: client: rename CL_LegacyMode to CL_Protocol 2024-10-07 19:04:24 +03:00
Alibek Omarov
659ffc4519 engine: common: add GoldSrc netchan support 2024-10-07 18:57:52 +03:00
Alibek Omarov
63a63a52de engine: common: add buffer munge functions 2024-10-07 18:57:52 +03:00
Alibek Omarov
e082d76a45 engine: remove unused svc_deltapacketbones 2024-10-07 18:57:52 +03:00
Alibek Omarov
cb8e2a4f9f 3rdparty: bzip2: add wscript for bzip2, check for system-wide bzip2, add readme note 2024-10-07 18:57:52 +03:00
Alibek Omarov
5f55930cb7 3rdparty: add bzip2 submodule 2024-10-07 18:10:06 +03:00
400 changed files with 18477 additions and 29009 deletions

View File

@@ -5,7 +5,6 @@ packages:
- freetype-dev
- fontconfig-dev
- opus-dev
- bzip2-dev
sources:
- https://git.sr.ht/~a1batross/xash3d-fwgs
tasks:

View File

@@ -7,7 +7,6 @@ packages:
- libfreetype-dev
- libfontconfig-dev
- libopus-dev
- libbz2-dev
sources:
- https://git.sr.ht/~a1batross/xash3d-fwgs
tasks:

View File

@@ -6,7 +6,6 @@ packages:
- libfreetype-dev
- libfontconfig-dev
- libopus-dev
- libbz2-dev
sources:
- https://git.sr.ht/~a1batross/xash3d-fwgs
tasks:

View File

@@ -6,7 +6,6 @@ packages:
- print/freetype2
- x11-fonts/fontconfig
- audio/opus
- archivers/bzip2
sources:
- https://git.sr.ht/~a1batross/xash3d-fwgs
tasks:

View File

@@ -6,7 +6,6 @@ packages:
- fontconfig
- libopus
- pkgconf
- bzip2
sources:
- https://git.sr.ht/~a1batross/xash3d-fwgs
tasks:

View File

@@ -3,7 +3,6 @@ packages:
- python3
- sdl2
- opus
- bzip2
sources:
- https://git.sr.ht/~a1batross/xash3d-fwgs
tasks:

View File

@@ -6,21 +6,8 @@ jobs:
# steps:
# - name: Cleanup
# run: rm -rf .* || true
get_prev_runid:
runs-on: ubuntu-latest
outputs:
value: ${{ steps.do.outputs.value }}
steps:
- id: do
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
value=$(gh run -R ${{ github.repository }} list -b${{ github.ref_name }} -ssuccess -L1 --json databaseId -q'.[0].databaseId')
echo "Last run ID is $value"
echo "value=$value" >> "$GITHUB_OUTPUT"
build:
runs-on: ${{ matrix.os }}
needs: get_prev_runid
continue-on-error: true
strategy:
fail-fast: false
@@ -34,22 +21,6 @@ jobs:
- os: ubuntu-20.04
targetos: linux
targetarch: i386
- os: ubuntu-20.04
targetos: linux
targetarch: arm64
cross: true
- os: ubuntu-20.04
targetos: linux
targetarch: armhf
cross: true
- os: ubuntu-20.04
targetos: linux
targetarch: riscv64
cross: true
- os: ubuntu-20.04
targetos: linux
targetarch: ppc64el
cross: true
# - os: ubuntu-aarch64-20.04
# targetos: linux
# targetarch: aarch64
@@ -72,29 +43,20 @@ jobs:
- os: windows-2019 # always use the oldest possible for 32-bit because of older compilers, and better support of certain legacy OSes
targetos: win32
targetarch: i386
- os: macos-14 # arm64 as per github documentation
- os: macos-14 # in GitHub documentation, it's an ARM64 machine
targetos: apple
targetarch: arm64
- os: macos-13 # x86 as per github documentation (will they fix it before they deprecate this version?..)
- os: macos-12 # in GitHub documentation, it's an x86 machine
targetos: apple
targetarch: amd64
env:
SDL_VERSION: 2.30.11
SDL_VERSION: 2.30.3
GH_CPU_ARCH: ${{ matrix.targetarch }}
GH_CROSSCOMPILING: ${{ matrix.cross }}
WAFCACHE: ${{ github.workspace }}/wafcache/
WAFCACHE_EVICT_MAX_BYTES: 1000000 # github only gives 10gb for the whole repository
WAFCACHE_STATS: 1 # enable statistics in build log
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
- name: Restore cache
uses: actions/cache/restore@v4
with:
key: ${{ runner.os }}-${{ matrix.targetos }}-${{ matrix.targetarch }}-${{ needs.get_prev_runid.outputs.value }}
path: ${{ github.workspace }}/wafcache/
- name: Install dependencies
run: bash scripts/gha/deps_${{ matrix.targetos }}.sh
- name: Build engine
@@ -106,12 +68,6 @@ jobs:
with:
name: artifact-${{ matrix.targetos }}-${{ matrix.targetarch }}
path: artifacts/*
- name: Save cache
uses: actions/cache/save@v4
with:
path: ${{ github.workspace }}/wafcache/
key: ${{ runner.os }}-${{ matrix.targetos }}-${{ matrix.targetarch }}-${{ github.run_id }}
flatpak:
runs-on: ubuntu-latest
strategy:
@@ -119,7 +75,7 @@ jobs:
include:
- app: su.xash.Engine.Compat.i386
container:
image: bilelmoussaoui/flatpak-github-actions:freedesktop-24.08
image: bilelmoussaoui/flatpak-github-actions:freedesktop-23.08
options: --privileged
steps:
- name: Checkout

3
.gitignore vendored
View File

@@ -342,6 +342,3 @@ enc_temp_folder/
# KDevelop4
*.kdev4
# ccls langauge server
.ccls-*

12
.gitmodules vendored
View File

@@ -22,15 +22,3 @@
[submodule "3rdparty/bzip2/bzip2"]
path = 3rdparty/bzip2/bzip2
url = https://gitlab.com/bzip2/bzip2
[submodule "3rdparty/MultiEmulator"]
path = 3rdparty/MultiEmulator
url = https://github.com/FWGS/MultiEmulator
[submodule "3rdparty/libogg/libogg"]
path = 3rdparty/libogg/libogg
url = https://gitlab.xiph.org/xiph/ogg.git
[submodule "3rdparty/vorbis/vorbis-src"]
path = 3rdparty/vorbis/vorbis-src
url = https://gitlab.xiph.org/xiph/vorbis.git
[submodule "3rdparty/opusfile/opusfile"]
path = 3rdparty/opusfile/opusfile
url = https://gitlab.xiph.org/xiph/opusfile.git

Submodule 3rdparty/MultiEmulator deleted from 7e989e670e

View File

@@ -1,12 +1,15 @@
#! /usr/bin/env python
# encoding: utf-8
BZIP_CHECK='''#include <bzlib.h>
int main(void) { return BZ2_bzlibVersion() != NULL; }
'''
def options(opt):
pass
def configure(conf):
conf.define('_GNU_SOURCE', 1)
conf.define('BZ_NO_STDIO', 1)
if conf.env.DEST_OS == 'win32':
conf.define('BZ_LCCWIN32', 1)
@@ -14,14 +17,19 @@ def configure(conf):
conf.define('BZ_UNIX', 1)
def build(bld):
bld(features = 'subst', source = 'bzip2/bz_version.h.in', target = 'bzip2/bz_version.h', BZ_VERSION='1.1.0-fwgs', name = 'bz_version')
bz_sources = ['bzip2/blocksort.c', 'bzip2/huffman.c', 'bzip2/crctable.c', 'bzip2/randtable.c', 'bzip2/compress.c', 'bzip2/decompress.c', 'bzip2/bzlib.c']
bld(
features = 'subst',
source = 'bzip2/bz_version.h.in',
target = 'bz_version.h',
BZ_VERSION='1.1.0-fwgs'
)
bld.stlib(
source = bz_sources,
source = bld.path.ant_glob(['bzip2/*.c']),
target = 'bzip2',
use = 'bz_version',
includes = 'bzip2/',
export_includes = 'bzip2/'
use = 'bz_version.h',
features = 'c',
includes = ['bzip2/', '.'],
subsystem = bld.env.MSVC_SUBSYSTEM,
export_includes = ['bzip2/']
)

View File

@@ -16,7 +16,9 @@ def build(bld):
bld.stlib(source = gl4es_srcdir.ant_glob(['gl/*.c', 'gl/*/*.c', 'glx/hardext.c']),
target = 'gl4es',
features = 'c',
includes = ['gl4es/src', 'gl4es/src/gl', 'gl4es/src/glx', 'gl4es/include'],
defines = ['NOX11', 'NO_GBM', 'NO_INIT_CONSTRUCTOR', 'DEFAULT_ES=2', 'NOEGL', 'NO_LOADER', 'STATICLIB'],
cflags = ['-w', '-fvisibility=hidden', '-std=gnu99'],
subsystem = bld.env.MSVC_SUBSYSTEM,
export_includes = '.')

Submodule 3rdparty/libogg/libogg deleted from 7cf42ea17a

View File

@@ -1,50 +0,0 @@
#! /usr/bin/env python
# encoding: utf-8
def options(opt):
pass
def configure(conf):
if not conf.path.find_dir('libogg') or not conf.path.find_dir('libogg/src'):
conf.fatal('Can\'t find libogg submodule. Run `git submodule update --init --recursive`.')
return
conf.env.INCLUDE_INTTYPES_H = 0
conf.env.INCLUDE_SYS_TYPES_H = 0
conf.env.INCLUDE_STDINT_H = 0
if conf.check_cc(header_name='inttypes.h', mandatory = False):
conf.env.INCLUDE_INTTYPES_H = 1
elif conf.check_cc(header_name='sys/types.h', mandatory = False):
conf.env.INCLUDE_SYS_TYPES_H = 1
elif conf.check_cc(header_name='stdint.h', mandatory = False):
conf.env.INCLUDE_STDINT_H = 1
def build(bld):
sources = bld.path.ant_glob([
'libogg/src/*.c'
])
bld(
features = 'subst',
name = 'libogg_config_types',
source = 'libogg/include/ogg/config_types.h.in',
target = 'libogg/include/ogg/config_types.h',
INCLUDE_INTTYPES_H = bld.env.INCLUDE_INTTYPES_H,
INCLUDE_SYS_TYPES_H = bld.env.INCLUDE_SYS_TYPES_H,
INCLUDE_STDINT_H = bld.env.INCLUDE_STDINT_H,
SIZE16 = 'int16_t',
USIZE16 = 'uint16_t',
SIZE32 = 'int32_t',
USIZE32 = 'uint32_t',
SIZE64 = 'int64_t',
USIZE64 = 'uint64_t'
)
bld.stlib(
source = sources,
target = 'ogg',
use = 'libogg_config_types',
includes = 'libogg/include/',
export_includes = 'libogg/include/'
)

32
3rdparty/opus/wscript vendored
View File

@@ -3,26 +3,6 @@
import os
FRAGMENT_VLA='''int main (int argc, char **argv) {
char a[argc];
a[sizeof( a ) - 1] = 0;
int N;
return a[0];
}'''
FRAGMENT_ALLOCA_H='''#include <alloca.h>
int main (void) {
int foo=10;
int * array = alloca(foo);
}'''
FRAGMENT_STDLIB_H='''#include <malloc.h>
#include <stdlib.h>
int main (void) {
int foo=10;
int * array = alloca(foo);
}'''
def options(opt):
pass
@@ -31,15 +11,6 @@ def configure(conf):
conf.fatal('Can\'t find opus submodule. Run `git submodule update --init --recursive`.')
return
# Check for C99 variable-size arrays, or alloca() as fallback
if conf.check_cc(fragment=FRAGMENT_VLA, msg = 'Checking for C99 VLA support', mandatory = False):
conf.define('VAR_ARRAYS', 1)
elif conf.check_cc(fragment=FRAGMENT_ALLOCA_H, msg = 'Checking for alloca in alloca.h header', mandatory = False):
conf.define('USE_ALLOCA', 1)
conf.define('HAVE_ALLOCA_H', 1)
elif conf.check_cc(fragmenmt=FRAGMENT_STDLIB_H, msg = 'Checking for alloca.h in stdlib.h', mandatory = False):
conf.define('USE_ALLOCA', 1)
# TODO: ARM/x86 intrinsics detection
# TODO: maybe call autotools/cmake/meson instead?
@@ -56,7 +27,7 @@ def build(bld):
'opus/celt/opus_custom_demo.c'
])
includes = ['opus/include/', 'opus/celt/', 'opus/silk/', 'opus/silk/float/']
defines = ['OPUS_BUILD', 'FLOAT_APPROX', 'PACKAGE_VERSION="1.4.0"', 'CUSTOM_MODES']
defines = ['USE_ALLOCA', 'OPUS_BUILD', 'FLOAT_APPROX', 'PACKAGE_VERSION="1.3.1"', 'CUSTOM_MODES']
bld.stlib(
source = sources,
@@ -64,5 +35,6 @@ def build(bld):
features = 'c',
includes = includes,
defines = defines,
subsystem = bld.env.MSVC_SUBSYSTEM,
export_includes = ['opus/include/']
)

View File

@@ -1,37 +0,0 @@
#! /usr/bin/env python
# encoding: utf-8
def options(opt):
pass
def configure(conf):
if not conf.path.find_dir('opusfile') or not conf.path.find_dir('opusfile/src'):
conf.fatal('Can\'t find opusfile submodule. Run `git submodule update --init --recursive`.')
return
if conf.env.COMPILER_CC == 'msvc':
conf.define('_CRT_SECURE_NO_WARNINGS', 1)
conf.define('_CRT_SECURE_NO_DEPRECATE', 1)
conf.define('_CRT_NONSTDC_NO_DEPRECATE', 1)
if conf.env.DEST_OS == 'android':
# HACKHACK: set it to 32 here because opusfile can't be built on Android SDK < 24
# with _FILE_OFFSET_BITS 64 (which it sets automatically in src/internal.h)
# we are not (????) relying on this part of the API, so it should be harmless
conf.define('_FILE_OFFSET_BITS', 32)
def build(bld):
sources = [
'opusfile/src/info.c',
'opusfile/src/internal.c',
'opusfile/src/opusfile.c',
'opusfile/src/stream.c'
]
bld.stlib(
source = sources,
target = 'opusfile',
includes = 'opusfile/include/',
use = 'ogg opus',
export_includes = 'opusfile/include/'
)

View File

@@ -1,71 +0,0 @@
#! /usr/bin/env python
# encoding: utf-8
FRAGMENT_MEMORY_H='''#include <memory.h>
int main (void) {
return 0;
}'''
FRAGMENT_ALLOCA_H='''#include <alloca.h>
int main (void) {
int foo=10;
int * array = alloca(foo);
}'''
def options(opt):
pass
def configure(conf):
if not conf.path.find_dir('vorbis-src') or not conf.path.find_dir('vorbis-src/lib'):
conf.fatal('Can\'t find Vorbis submodule. Run `git submodule update --init --recursive`.')
return
if conf.check_cc(fragment=FRAGMENT_MEMORY_H, msg = 'Checking for memory.h header', mandatory = False):
conf.define('USE_MEMORY_H', 1)
conf.check_cc(fragment=FRAGMENT_ALLOCA_H, msg = 'Checking for alloca in alloca.h header', mandatory = False)
if conf.env.COMPILER_CC == 'msvc':
conf.define('_CRT_SECURE_NO_WARNINGS', 1)
conf.define('_CRT_SECURE_NO_DEPRECATE', 1)
conf.define('_CRT_NONSTDC_NO_DEPRECATE', 1)
def build(bld):
libvorbis_sources = [
'vorbis-src/lib/mdct.c',
'vorbis-src/lib/smallft.c',
'vorbis-src/lib/block.c',
'vorbis-src/lib/envelope.c',
'vorbis-src/lib/window.c',
'vorbis-src/lib/lsp.c',
'vorbis-src/lib/lpc.c',
'vorbis-src/lib/analysis.c',
'vorbis-src/lib/synthesis.c',
'vorbis-src/lib/psy.c',
'vorbis-src/lib/info.c',
'vorbis-src/lib/floor1.c',
'vorbis-src/lib/floor0.c',
'vorbis-src/lib/res0.c',
'vorbis-src/lib/mapping0.c',
'vorbis-src/lib/registry.c',
'vorbis-src/lib/codebook.c',
'vorbis-src/lib/sharedbook.c',
'vorbis-src/lib/lookup.c',
'vorbis-src/lib/bitrate.c'
]
bld.stlib(
source = libvorbis_sources,
target = 'vorbis',
includes = 'vorbis-src/include/',
use = 'ogg',
export_includes = 'vorbis-src/include/'
)
bld.stlib(
source = 'vorbis-src/lib/vorbisfile.c',
target = 'vorbisfile',
includes = 'vorbis-src/include/',
use = 'vorbis',
export_includes = 'vorbis-src/include/'
)

View File

@@ -13,6 +13,5 @@ When `-bugcomp` is specified with argument, it interpreted as flags separated wi
| Flag | Description | Games that require this flag |
| ------- | ----------- | ---------------------------- |
| `peoei` | Reverts `pfnPEntityOfEntIndex` behavior to GoldSrc, where it returns NULL for last player due to incorrect player index comparison | * Counter-Strike: Condition Zero - Deleted Scenes |
| `gsmrf` | Rewrites message at the moment when Game DLL attempts to write an internal engine message, usually specific to GoldSrc protocol.<br>Right now only supports `svc_spawnstaticsound`, more messages added by request. | * MetaMod/AMXModX based mods |
| `gsmrf` | Rewrites message at the moment when Game DLL attempts to write an internal engine message, usually specific to GoldSrc protocol. Right now only supports `svc_spawnstaticsound`, more messages added by request. | * MetaMod/AMXModX based mods |
| `sp_attn_none` | Makes sounds with attenuation zero spatialized, i.e. have a stereo effect. | Possibly, every game that was made for GoldSrc. |
| `get_game_dir_full` | Makes server return full path in server's `pfnGetGameDir` API function | Mods targetting engine before HL 1.1.1.1, according to MetaMod [documentation](http://metamod.org/engine_notes.html#GetGameDir) |

View File

@@ -5,10 +5,5 @@ This can be useful to test engine in Wine without using virtual machines or dual
0. Clone and install https://github.com/mstorsjo/msvc-wine (you can skip CMake part)
1. Set environment variable MSVC_WINE_PATH to the path to installed MSVC toolchain
2. Pre-load wine: `wineserver -k; wineserver -p; wine64 wineboot`
3. Run `PKGCONFIG=/bin/false ./waf configure -T <build-type> --enable-wine-msvc --sdl2=../SDL2_VC`. Configuration step will take more time than usual.
3. Run `./waf configure -T <build-type> --enable-wine-msvc --sdl2=../SDL2_VC`. Configuration step will take more time than usual.
4. .. other typical steps to build from console ...
> [!NOTE]
> Notice the usage of PKGCONFIG=/bin/false here. We're disabling pkg-config so we don't accidentally pull
> system-wide dependencies and force building them from source. In future builds we might set custom
> directory to pull dependencies from, like ffmpeg...

View File

@@ -0,0 +1,150 @@
# There are few new commands availiable in xash3d fork:
## Commands:
### ent_create
Create entity with specified classname and key/values
`ent_create <classname> <key> <value> <key> <value> ...`
for example:
`ent_create monster_zombie targetname zomb1`
after creating entity, ent_last_xxx cvars are set to new entity and ent_last_cb called, look at ent_getvars description
### ent_fire
Make some actions on entity
`ent_fire <pattern> <command> <args>`
Availiavle commands:
* Set fields (Only set entity field, does not call any functions):
* health
* gravity
* movetype
* solid
* rendermode
* rendercolor (vector)
* renderfx
* renderamt
* hullmin (vector)
* hullmax (vector)
* Actions
* rename: set entity targetname
* settarget: set entity target (only targetnames)
* setmodel: set entity model (does not update)
* set: set key/value by server library
* See game FGD to get list.
* command takes two arguments
* touch: touch entity by current player.
* use: use entity by current player.
* movehere: place entity in player fov.
* drop2floor: place entity to nearest floor surface
* moveup: move entity to 25 units up
* moveup (value): move by y axis relatively to specified value
* Flags (Set/clear specified flag bit, arg is bit number):
* setflag
* clearflag
* setspawnflag
* clearspawnflag
### ent_info
Print information about entity by identificator
`ent_info <identificator>`
### ent_getvars
Set client cvars containing entity information (useful for [[Scripting]]) and call ent_last_cb
`ent_getvars <identificator>`
These cvars are set:
```
ent_last_name
ent_last_num
ent_last_inst
ent_last_origin
ent_last_class
```
### ent_list
Print short information about antities, filtered by pattern
`ent_list <pattern>`
## Syntax description
### \<identificator\>
* !cross: entity under aim
* Instance code: !\<number\>_\<seria\l>
* set by ent_getvars command
* Entity index
* targetname pattern
### \<pattern\>
Pattern is like identificator, but may filter many entities by classname
### (vector)
used by ent_fire command. vector means three float values, entered without quotes
### key/value
All entities parameters may be set by specifiing key and value strings.
Originally, this mechanizm is used in map/bsp format, but it can be used in enttools too.
Keys and values are passed to server library and processed by entity keyvalue function, setting edict and entity owns parameters.
If value contains spaces, it must be put in quotes:
`ent_fire !cross set origin "0 0 0"`
## Using with scripting
ent_create and ent_getvars commands are setting cvars on client
It can be used with ent_last_cb alias that is executed after setting cvars.
Simple example:
```
ent_create weapon_c4
alias ent_last_cb "ent_fire \$ent_last_inst use"
```
Use weapon_c4 after creating it.
Note that you cannot use many dfferent callbacks at the same time.
You can set entity name by by pattern and create special script, contatning all callbacks.
Example:
example.cfg
```
alias ent_last_cb exec entity_cb.cfg
ent create \<class\> targetname my_ent1_$name
ent_create \<class\> targetname my_ent2_$name
```
entity_cb.cfg
```
if $ent_last_name == my_ent1_$name
:(ent1 actions)
if $ent_last_name == my_ent2_$name
:(ent2 actions)
```
Note that scripting cannot be blocking. You cannot wait for server answer and continue. But you can use small scripts, connected with ent_last_cb command. The best usage is user interaction. You can add touch buttons to screen or call user command menu actions by callbacks.
## Server side
To enable entity tools on server, set sv_enttools_enable to 1
To change maximum number of entities, touched by ent_fire, change sv_enttools_maxfire to required number.
To enable actions on players, set sv_enttools_players to 1.
To enable entity tools for player by nickname, set sv_enttools_godplayer to nickname. Useful to temporary enable from rcon.
To prevent crash on some actions, set host_mapdesign_fatal to 0

View File

@@ -1,152 +0,0 @@
# Entity tools
For some features described below, you need to enable [console scripting](https://github.com/FWGS/xash3d-fwgs/blob/master/Documentation/extensions/console-scripting.md) with command `cmd_scripting 1` in console.
To get more information about it, check another according page related to console scripting.
## Commands description
### ent_create
Create entity with specified classname and key/values.
`ent_create <classname> <key> <value> <key> <value> ...`
For example:
`ent_create monster_zombie targetname zomb1`
After creating entity, ent_last_xxx cvars are set to new entity and ent_last_cb called, look at ent_getvars description.
### ent_fire
Makes some actions on entity.
`ent_fire <pattern> <command> <args>`
#### Available commands:
Set fields (only set entity field, does not call any functions)
* health
* gravity
* movetype
* solid
* rendermode
* rendercolor (vector)
* renderfx
* renderamt
* hullmin (vector)
* hullmax (vector)
Actions
* rename: set entity targetname
* settarget: set entity target (only targetnames)
* setmodel: set entity model (does not update)
* set: set key/value by server library
* See game FGD to get list.
* command takes two arguments
* touch: touch entity by current player.
* use: use entity by current player.
* movehere: place entity in player fov.
* drop2floor: place entity to nearest floor surface
* moveup: move entity to 25 units up
* moveup (value): move by y axis relatively to specified value
Flags (set/clear specified flag bit, arg is bit number):
* setflag
* clearflag
* setspawnflag
* clearspawnflag
### ent_info
Print information about entity by identificator.
`ent_info <identificator>`
### ent_getvars
Set client cvars containing entity information (useful for [scripting](extensions/console-scripting.md)) and then calls ent_last_cb.
`ent_getvars <identificator>`
These cvars are set:
```
ent_last_name
ent_last_num
ent_last_inst
ent_last_origin
ent_last_class
```
### ent_list
Print short information about antities, filtered by pattern.
`ent_list <pattern>`
## Syntax description
#### \<identificator\>
* !cross: entity under aim
* !\<number\>_\<serial\>: instance code
* Set by ent_getvars command
* Entity index
* Targetname pattern
#### \<pattern\>
Pattern is like identificator, but may filter many entities by classname.
#### (vector)
Used by ent_fire command. vector means three float values, entered without quotes.
#### key/value
All entities parameters may be set by specifiing key and value strings.
Originally, this mechanizm is used in map/bsp format, but it can be used in enttools too.
Keys and values are passed to server library and processed by entity keyvalue function, setting edict and entity owns parameters.
If value contains spaces, it must be put in quotes:
`ent_fire !cross set origin "0 0 0"`
## Using with scripting
ent_create and ent_getvars commands are setting cvars on client
It can be used with ent_last_cb alias that is executed after setting cvars.
Simple example:
```
ent_create weapon_c4
alias ent_last_cb "ent_fire \$ent_last_inst use"
```
Use weapon_c4 after creating it.
Note that you cannot use many different callbacks at the same time.
You can set entity name by by pattern and create special script, contatning all callbacks.
Example:
> example.cfg
```
alias ent_last_cb exec entity_cb.cfg
ent create \<class\> targetname my_ent1_$name
ent_create \<class\> targetname my_ent2_$name
```
> entity_cb.cfg
```
if $ent_last_name == my_ent1_$name
:(ent1 actions)
if $ent_last_name == my_ent2_$name
:(ent2 actions)
```
Note that scripting cannot be blocking. You cannot wait for server answer and continue. But you can use small scripts, connected with ent_last_cb command. The best usage is user interaction. You can add touch buttons to screen or call user command menu actions by callbacks.
## Server side configuration
To enable entity tools on server, set sv_enttools_enable to 1
To change maximum number of entities, touched by ent_fire, change sv_enttools_maxfire to required number.

View File

@@ -85,9 +85,6 @@ These strings are specific to Xash3D FWGS.
As Xash3D accidentally supports GoldSrc games, it also supports parsing liblist.gam.\
Xash3D will use this file if gameinfo.txt is absent, or if its modification timestamp is older than liblist.gam.
> [!NOTE]
> Starting from January 2025, Xash3D FWGS doesn't automatically generate gameinfo.txt from liblist.gam. The key conversion table still remains but if you wish to use gameinfo.txt instead of liblist.gam, you can execute `fs_make_gameinfo` in console.
For game creators who plan supporting only Xash3D, using this file is not recommended.
The table below defines conversion rules from liblist.gam to gameinfo.txt. Some keys' interpretation does differ from `gameinfo.txt`, in this case a note will be left. If `liblist.gam` key isn't present in this table, it's ignored.

View File

@@ -1,15 +0,0 @@
# Support for GoldSrc network protocol
This feature is still work-in-progress, but for now it's available for all users, and we appreciate any bug-reports and contributions around it.
For connecting to GoldSrc-based servers, use this command:
```
connect ip:port gs
```
But keep in mind, there are requirement for server to be able to accept connections from Xash3D-based clients: it should use Reunion or Dproto.
Without this requirement, you will just get "Steam validation rejected" error on connecting.
That is because proper authorization with Steam API is not implemented in engine yet (but we have plans on it).
Also, we encountered that some GoldSrc-based servers are recognizing Xash3D clients as "fake clients" and banning/kicking them. Maybe this problem will be
solved along with better compatibility with GoldSrc behavior, but may be not - we don't know logic behind this fake client checks.

View File

@@ -1,32 +0,0 @@
# NAT bypass feature in Xash3D FWGS
Since IPv6 not as widespread as we would like, NAT (Network Address Translation) still being actively used by many internet service providers in an attempts to
mitigate IPv4 addresses exhaustion. In short, they uses one IPv4 address and doing some tricks with ports to represent many other users behind this address.
But this leads to a problem for users: they cannot accept direct
incoming connections anymore. This means that if you are behind provider's NAT and will try to setup Xash3D FWGS server - nobody will be able to connect to it,
and the server will not show up in servers public list.
## Is it possible to avoid this problem?
In most cases, it is possible to bypass NAT with UDP hole punching, and Xash3D FWGS uses this method too. But this method is not 100% guaranteed to work - it depends
on NAT configurations on both server and client side, and there is no way to control it.
First of all, server should not be behind symmetric NAT. You can check your NAT type on [this page](https://www.checkmynat.com/).
If you get "Symmetric NAT" result in this test, that means you cannot setup publicly available server with internet connection that you are using.
Here is more detailed scheme of different NAT types compatibility, it explains are users with different NAT types can connect to each other or not.
Client NAT type on rows, server NAT type on columns:
| *NAT Type* | Full Cone | Restricted Cone | Port Restricted Cone | Symmetric |
| -------------------- | --------- | --------------- | -------------------- | --------- |
| Full Cone | ✔️ | ✔️ | ✔️ | ✔️ |
| Restricted Cone | ✔️ | ✔️ | ✔️ | ✔️ |
| Port Restricted Cone | ✔️ | ✔️ | ✔️ | ❌ |
| Symmetric | ✔️ | ✔️ | ❌ | ❌ |
## How to use NAT bypass feature?
If you are starting server within the game, you need to enable option "*Use NAT Bypass instead of direct mode*" in bottom left corner of screen.
If you are starting dedicated server, you should add console variable `sv_nat 1` to server.cfg file, or add `+sv_nat 1` to server startup parameters.
If you need to connect to server behind NAT, there is separate tab named *NAT* for such servers in server browser. Note that it is useless to add NAT server to favorites,
or somehow trying manually to store it, because it has changing address and port.

View File

@@ -38,9 +38,6 @@ Uploaded to github by Oleg Cherkasky - https://github.com/gunrunners-paradise/Ct
## Deathmatch Classic
Available in Valve's Half-Life repository - https://github.com/ValveSoftware/halflife/tree/master/dmc
## Earth Special Forces
Alpha 2.0 - https://www.gamers-desire.de/details/2830
## ESHQ
Official github repository - https://github.com/adslbarxatov/xash3d-for-ESHQ

View File

@@ -1,15 +1,15 @@
Xash3D FWGS is intended to be easily portable for various platforms, however main issue is maintaining such ports.
Xash3D FWGS is intended to be easily ported to specific platform, however main issue is maintaining such ports.
This page is about merged ports to main source tree and responsible for it developers.
For porting guidelines, read engine-porting-guide.md.
For porting guidelines, look engine-porting-guide.md.
Status:
* **Supported**: active, confirmed to be fully functional, gets built on CI.
* **Orphaned**: some work was done but not finished or actively tested due to lack of human resources.
* **Supported**: active, was confirmed to be fully functional.
* **In progress**: active, under development.
* **Old Engine**: port was for old engine fork.
* **Deprecated**: not supported anymore.
* **Old Engine**: port was for old engine fork
* **Not maintained**: lack of human resources
* **Deprecated**: not supported anymore
Table is sorted by status and platform.
@@ -17,22 +17,17 @@ Table is sorted by status and platform.
| -------- | ------ | ---------- | ----
| Android | Supported | @Velaron |
| *BSD | Supported | @nekonomicon |
| DOS4GW | Supported | @mittorn |
| GNU/Linux | Supported | @a1batross, @mittorn |
| Haiku | Supported | not maintained | Was added by #478 and #483
| macOS | Supported | @sofakng |
| MotoMAGX | Supported | @a1batross |
| PSVita | Supported | @fgsfdsfgs |
| Switch | Supported | @fgsfdsfgs |
| Windows | Supported | @a1batross, @SNMetamorph |
| DOS4GW | Orphaned | N/A | Haven't been confirmed to work for a very long time
| Haiku | Orphaned | N/A | Was added by #478 and #483
| IRIX | Orphaned | N/A | Undone, compiles but requires big endian port
| MotoMAGX | Orphaned | N/A | Should work but the compiler used for this platform is very unstable and easy to crash (it's GCC 3.4)
| SerenityOS | Orphaned | N/A | Works but not throughly tested
| Solaris | Orphaned | N/A | Works but not throughly tested
| WebAssembly System Interface | Orphaned | N/A | Undone, WASI is missing a lot of APIs we want to use
| Dreamcast | In progress | @maximqaxd | [GitHub Repository](https://github.com/maximqaxd/xash3d-fwgs_dc/)
| PSP | In progress | @Crow_bar, @Velaron | [GitHub Repository](https://github.com/Crow-bar/xash3d-fwgs)
| Wii | In progress | Collaborative effort | [GitHub Repository](https://github.com/saucesaft/xash3d-wii)
| Emscripten | Old Engine | N/A |
| 3DS | Old Engine fork | N/A | [GitHub Repository](https://github.com/masterfeizz/Xash3DS)
| Oculus Quest | Old Engine fork | N/A | [GitHub Repository](https://github.com/DrBeef/Lambda1VR)
| iOS | Deprecated | N/A | See GitHub issue #61
| Emscripten | Old Engine | not maintained |
| 3DS | Old Engine fork | not maintained | [GitHub Repository](https://github.com/masterfeizz/Xash3DS)
| Oculus Quest | Old Engine fork | @DrBeef | [GitHub Repository](https://github.com/DrBeef/Lambda1VR)
| iOS | Deprecated | not maintained | See GitHub issue #61

View File

@@ -2,7 +2,7 @@
[![builds.sr.ht status](https://builds.sr.ht/~a1batross/xash3d-fwgs.svg)](https://builds.sr.ht/~a1batross/xash3d-fwgs?) [![GitHub Actions Status](https://github.com/FWGS/xash3d-fwgs/actions/workflows/c-cpp.yml/badge.svg)](https://github.com/FWGS/xash3d-fwgs/actions/workflows/c-cpp.yml) [![FreeBSD Build Status](https://img.shields.io/cirrus/github/FWGS/xash3d-fwgs?label=freebsd%20build)](https://cirrus-ci.com/github/FWGS/xash3d-fwgs) [![Discord Server](https://img.shields.io/discord/355697768582610945.svg)](http://fwgsdiscord.mentality.rip/) \
[![Download Stable](https://img.shields.io/badge/download-stable-yellow)](https://github.com/FWGS/xash3d-fwgs/releases/latest) [![Download Testing](https://img.shields.io/badge/downloads-testing-orange)](https://github.com/FWGS/xash3d-fwgs/releases/tag/continuous)
Xash3D ([pronounced](https://ipa-reader.com/?text=ks%C9%91%CA%82) `[ksɑʂ]`) FWGS is a game engine, aimed to provide compatibility with Half-Life Engine and extend it, as well as to give game developers well known workflow.
Xash3D (pronounced `[ksɑʂ]`) FWGS is a game engine, aimed to provide compatibility with Half-Life Engine and extend it, as well as to give game developers well known workflow.
Xash3D FWGS is a heavily modified fork of an original [Xash3D Engine](https://www.moddb.com/engines/xash3d-engine) by Unkle Mike.
@@ -11,17 +11,16 @@ Xash3D FWGS is a heavily modified fork of an original [Xash3D Engine](https://ww
If you like Xash3D FWGS, consider supporting individual engine maintainers. By supporting us, you help to continue developing this game engine further. The sponsorship links are available in [documentation](Documentation/donate.md).
## Fork features
* Steam Half-Life (HLSDK 2.5) support.
* Steam Half-Life (HLSDK 2.4) support.
* Crossplatform and modern compilers support: supports Windows, Linux, BSD & Android on x86 & ARM and [many more](Documentation/ports.md).
* Better multiplayer: multiple master servers, headless dedicated server, voice chat, [GoldSrc protocol support](Documentation/goldsrc-protocol-support.md) and IPv6 support.
* Better multiplayer support: multiple master servers, headless dedicated server, voice chat and IPv6 support.
* Multiple renderers support: OpenGL, GLESv1, GLESv2 and Software.
* Advanced virtual filesystem: `.pk3` and `.pk3dir` support, compatibility with GoldSrc FS module, fast case-insensitivity emulation for crossplatform.
* Mobility API: better game integration on mobile devices (vibration, touch controls).
* Mobility API: better game integration on mobile devices (vibration, touch controls)
* Different input methods: touch and gamepad in addition to mouse & keyboard.
* TrueType font rendering, as a part of mainui_cpp.
* External VGUI support module.
* PNG & KTX2 image format support.
* Ogg Vorbis (`.ogg`) & Ogg Opus (`.opus`) audio formats support.
* [A set of small improvements](Documentation/), without broken compatibility.
## Installation & Running
@@ -36,16 +35,17 @@ You still needed to copy `valve` directory as all game resources located there.
For additional info, run Xash3D with `-help` command line key.
## Contributing
* Before sending an issue, check if someone already reported your issue. Make sure you're following "How To Ask Questions The Smart Way" guide by Eric Steven Raymond. Read more: http://www.catb.org/~esr/faqs/smart-questions.html.
* Issues are accepted in both English and Russian.
* Before sending an issue, check if someone already reported your issue. Make sure you're following "How To Ask Questions The Smart Way" guide by Eric Steven Raymond. Read more: http://www.catb.org/~esr/faqs/smart-questions.html
* Issues are accepted in both English and Russian
* Before sending a PR, check if you followed our contribution guide in CONTRIBUTING.md file.
## Build instructions
We are using Waf build system. If you have some Waf-related questions, I recommend you to read [Waf Book](https://waf.io/book/).
We are using Waf build system. If you have some Waf-related questions, I recommend you to read https://waf.io/book/
NOTE: NEVER USE GitHub's ZIP ARCHIVES. GitHub doesn't include external dependencies we're using!
### Prerequisites
If your CPU is x86 compatible and you're on Windows or Linux, we are building 32-bit code by default. This was done to maintain compatibility with Steam releases of Half-Life and based on it's engine games.
Even if Xash3D FWGS does support targetting 64-bit, you can't load games without recompiling them from source code!
@@ -62,40 +62,31 @@ This repository contains our fork of HLSDK and restored source code for Half-Lif
#### GNU/Linux
##### Debian/Ubuntu
* Only for 32-bit engine on 64-bit x86 operating system:
* Enable i386 on your system: `$ sudo dpkg --add-architecture i386`.
* Install `aptitude` ([why?](https://github.com/FWGS/xash3d-fwgs/issues/1828#issuecomment-2415131759)): `$ sudo apt update && sudo apt upgrade && sudo apt install aptitude`
* Install development tools: `$ sudo aptitude --without-recommends install git build-essential gcc-multilib g++-multilib libsdl2-dev:i386 libfontconfig-dev:i386 libfreetype-dev:i386 libopus-dev:i386 libbz2-dev:i386`.
* Set PKG_CONFIG_PATH environment variable to point at 32-bit libraries: `$ export PKG_CONFIG_PATH=/usr/lib/i386-linux-gnu/pkgconfig`.
* Enable i386 on your system, if you're compiling 32-bit engine on amd64. If not, skip this
* For 64-bit engine on 64-bit x86 and other non-x86 systems:
* Install development tools: `$ sudo apt install git build-essential python libsdl2-dev libfontconfig-dev libfreetype6-dev libopus-dev libbz2-dev`.
* Clone this repostory: `$ git clone --recursive https://github.com/FWGS/xash3d-fwgs`.
##### RedHat/Fedora
* Only for 32-bit engine on 64-bit x86 operating system:
* Install development tools: `$ sudo dnf install git gcc gcc-c++ glibc-devel.i686 SDL2-devel.i686 opus-devel.i686 fontconfig-devel.i686 freetype-devel.i686 bzip2-devel.i686`.
* Set PKG_CONFIG_PATH environment variable to point at 32-bit libraries: `$ export PKG_CONFIG_PATH=/usr/lib/pkgconfig`.
* For 64-bit engine on 64-bit x86 and other non-x86 systems:
* Install development tools: `$ sudo dnf install git gcc gcc-c++ SDL2-devel opus-devel fontconfig-devel freetype-devel bzip2-devel`.
* Clone this repostory: `$ git clone --recursive https://github.com/FWGS/xash3d-fwgs`.
`$ sudo dpkg --add-architecture i386`
* Install development tools
* For 32-bit engine on amd64: \
`$ sudo apt install build-essential gcc-multilib g++-multilib python libsdl2-dev:i386 libfontconfig-dev:i386 libfreetype6-dev:i386 libopus-dev:i386 libbz2-dev:i386`
* For everything else: \
`$ sudo apt install build-essential python libsdl2-dev libfontconfig-dev libfreetype6-dev libopus-dev libbz2-dev`
* Clone this repostory:
`$ git clone --recursive https://github.com/FWGS/xash3d-fwgs`
### Building
#### Windows (Visual Studio)
0) Open command line.
0) Open command line
1) Navigate to `xash3d-fwgs` directory.
2) (optional) Examine which build options are available: `waf --help`.
3) Configure build: `waf configure --sdl2=c:/path/to/SDL2`.
4) Compile: `waf build`.
5) Install: `waf install --destdir=c:/path/to/any/output/directory`.
2) Carefully examine which build options are available: `waf --help`
3) Configure build: `waf configure -T release --sdl2=c:/path/to/SDL2`
4) Compile: `waf build`
5) Install: `waf install --destdir=c:/path/to/any/output/directory`
#### Linux
If compiling 32-bit on amd64, make sure `PKG_CONFIG_PATH` from the previous step is set correctly, prior to running configure.
If compiling 32-bit on amd64, you may need to supply `export PKG_CONFIG_PATH=/usr/lib/i386-linux-gnu/pkgconfig` prior to running configure.
0) (optional) Examine which build options are available: `./waf --help`.
1) Configure build: `./waf configure` (you need to pass `-8` to compile 64-bit engine on 64-bit x86 processor).
2) Compile: `./waf build`.
3) Install: `./waf install --destdir=/path/to/any/output/directory`.
0) Examine which build options are available: `./waf --help`
1) Configure build: `./waf configure -T release`
(You need to pass `-8` to compile 64-bit engine on 64-bit x86 processor)
2) Compile: `./waf build`
3) Install(optional): `./waf install --destdir=/path/to/any/output/directory`

14
android/.gitignore vendored
View File

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

View File

@@ -1,60 +0,0 @@
cmake_minimum_required(VERSION 3.6)
# Only used to build Android project
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")
list(APPEND WAF_EXTRA_ARGS --enable-poly-opt --enable-lto)
endif()
if(CMAKE_SIZEOF_VOID_P MATCHES "8")
set(64BIT ON CACHE BOOL "" FORCE)
list(APPEND WAF_EXTRA_ARGS -8) # only required for x86 when testing this cmakelist under linux
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}")
# not cleanest way to get upper directory
set(ENGINE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../")
execute_process(
COMMAND ${CMAKE_COMMAND} -E env
CC=${WAF_CC} CXX=${WAF_CXX}
AR=${CMAKE_AR} STRIP=${CMAKE_STRIP}
${PYTHON_EXECUTABLE} waf configure -T ${BUILD_TYPE} ${WAF_EXTRA_ARGS} cmake
--check-c-compiler=${C_COMPILER_ID} --check-cxx-compiler=${CXX_COMPILER_ID}
-s "${ENGINE_SOURCE_DIR}/SDL" --skip-sdl2-sanity-check --enable-bundled-deps
WORKING_DIRECTORY "${ENGINE_SOURCE_DIR}"
)
add_subdirectory("${ENGINE_SOURCE_DIR}/3rdparty/hlsdk-portable" hlsdk-portable)
# try to build minimal SDL. Enable features as we're gonna use them
set(SDL_RENDER OFF)
set(SDL_POWER OFF)
set(SDL_VULKAN OFF)
set(SDL_DISKAUDIO OFF)
set(SDL_DUMMYAUDIO OFF)
set(SDL_DUMMYVIDEO OFF)
set(SDL_VULKAN OFF)
set(SDL_OFFSCREEN OFF)
set(SDL_STATIC OFF)
add_subdirectory("${ENGINE_SOURCE_DIR}/3rdparty/SDL" SDL)
add_subdirectory("${ENGINE_SOURCE_DIR}/" xash3d-fwgs)
add_subdirectory("${ENGINE_SOURCE_DIR}/3rdparty/mainui" mainui)

View File

@@ -1,129 +0,0 @@
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 = "27.2.12479018"
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("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 = true
isShrinkResources = true
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.srcDirs("../../3rdparty/extras/xash-extras", "../moddb")
java.srcDir("../../3rdparty/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()
}

View File

@@ -1,89 +0,0 @@
-keep class su.xash.engine.XashActivity {
java.lang.String loadAndroidID();
java.lang.String getAndroidID();
void saveAndroidID(java.lang.String);
java.lang.String getCallingPackage();
java.lang.String[] getAssetsList(boolean, java.lang.String);
android.content.res.AssetManager getAssets(boolean);
}
-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLInputConnection {
void nativeCommitText(java.lang.String, int);
void nativeGenerateScancodeForUnichar(char);
}
-keep,includedescriptorclasses class org.libsdl.app.SDLActivity {
# for some reason these aren't compatible with allowoptimization modifier
boolean supportsRelativeMouse();
void setWindowStyle(boolean);
}
-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLActivity {
java.lang.String nativeGetHint(java.lang.String); # Java-side doesn't use this, so it gets minified, but C-side still tries to register it
boolean onNativeSoftReturnKey();
void onNativeKeyboardFocusLost();
boolean isScreenKeyboardShown();
android.util.DisplayMetrics getDisplayDPI();
java.lang.String clipboardGetText();
boolean clipboardHasText();
void clipboardSetText(java.lang.String);
int createCustomCursor(int[], int, int, int, int);
void destroyCustomCursor(int);
android.content.Context getContext();
boolean getManifestEnvironmentVariables();
android.view.Surface getNativeSurface();
void initTouch();
boolean isAndroidTV();
boolean isChromebook();
boolean isDeXMode();
boolean isTablet();
void manualBackButton();
int messageboxShowMessageBox(int, java.lang.String, java.lang.String, int[], int[], java.lang.String[], int[]);
void minimizeWindow();
int openURL(java.lang.String);
void requestPermission(java.lang.String, int);
int showToast(java.lang.String, int, int, int, int);
boolean sendMessage(int, int);
boolean setActivityTitle(java.lang.String);
boolean setCustomCursor(int);
void setOrientation(int, int, boolean, java.lang.String);
boolean setRelativeMouseEnabled(boolean);
boolean setSystemCursor(int);
boolean shouldMinimizeOnFocusLoss();
boolean showTextInput(int, int, int, int);
}
-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.HIDDeviceManager {
boolean initialize(boolean, boolean);
boolean openDevice(int);
int sendOutputReport(int, byte[]);
int sendFeatureReport(int, byte[]);
boolean getFeatureReport(int, byte[]);
void closeDevice(int);
}
-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLAudioManager {
int[] getAudioOutputDevices();
int[] getAudioInputDevices();
int[] audioOpen(int, int, int, int, int);
void audioWriteFloatBuffer(float[]);
void audioWriteShortBuffer(short[]);
void audioWriteByteBuffer(byte[]);
void audioClose();
int[] captureOpen(int, int, int, int, int);
int captureReadFloatBuffer(float[], boolean);
int captureReadShortBuffer(short[], boolean);
int captureReadByteBuffer(byte[], boolean);
void captureClose();
void audioSetThreadPriority(boolean, int);
native int nativeSetupJNI();
native void removeAudioDevice(boolean, int);
native void addAudioDevice(boolean, int);
}
-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLControllerManager {
void pollInputDevices();
void pollHapticDevices();
void hapticRun(int, float, int);
void hapticStop(int);
}

View File

@@ -1,5 +0,0 @@
<?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

@@ -1,26 +0,0 @@
#!/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

@@ -1,26 +0,0 @@
#!/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

@@ -1,26 +0,0 @@
#!/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

@@ -1,26 +0,0 @@
#!/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

@@ -1,5 +0,0 @@
<?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

@@ -1,5 +0,0 @@
<?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

@@ -1,101 +0,0 @@
<?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

@@ -1,49 +0,0 @@
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

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

View File

@@ -1,11 +0,0 @@
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

@@ -1,35 +0,0 @@
package su.xash.engine
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.navigateUp
import androidx.navigation.ui.setupActionBarWithNavController
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

@@ -1,28 +0,0 @@
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

@@ -1,148 +0,0 @@
package su.xash.engine;
import android.annotation.SuppressLint;
import android.content.pm.ActivityInfo;
import android.content.res.AssetManager;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings.Secure;
import android.util.Log;
import android.view.KeyEvent;
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
public void onDestroy()
{
super.onDestroy();
// Now that we don't exit from native code, we need to exit here, resetting
// application state (actually global variables that we don't cleanup on exit)
//
// When the issue with global variables will be resolved, remove that exit() call
System.exit(0);
}
@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 = "-console -log";
return argv.split(" ");
}
}

View File

@@ -1,253 +0,0 @@
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

@@ -1,77 +0,0 @@
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

@@ -1,89 +0,0 @@
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

@@ -1,133 +0,0 @@
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", "-console -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 {
// exclude unfinished downloads
if (file.name?.startsWith('.') == true)
return false
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

@@ -1,59 +0,0 @@
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

@@ -1,94 +0,0 @@
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

@@ -1,127 +0,0 @@
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

@@ -1,31 +0,0 @@
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 su.xash.engine.databinding.FragmentAppSettingsBinding
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

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

View File

@@ -1,64 +0,0 @@
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.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
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

@@ -1,40 +0,0 @@
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.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

@@ -1,48 +0,0 @@
package su.xash.engine.ui.setup
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.viewpager2.adapter.FragmentStateAdapter
import su.xash.engine.databinding.FragmentSetupBinding
import su.xash.engine.ui.setup.pages.LocationPageFragment
import su.xash.engine.ui.setup.pages.WelcomePageFragment
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

@@ -1,25 +0,0 @@
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 su.xash.engine.MainApplication
import su.xash.engine.model.Game
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

@@ -1,59 +0,0 @@
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 com.google.android.material.dialog.MaterialAlertDialogBuilder
import su.xash.engine.R
import su.xash.engine.databinding.PageLocationBinding
import su.xash.engine.ui.library.LibraryViewModel
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

@@ -1,35 +0,0 @@
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 su.xash.engine.databinding.PageWelcomeBinding
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

@@ -1,589 +0,0 @@
/**
* 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

@@ -1,97 +0,0 @@
package su.xash.engine.workers
import android.content.Context
import android.net.Uri
import androidx.documentfile.provider.DocumentFile
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
const val KEY_FILE_URI = "KEY_FILE_URI"
class FileCopyWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
companion object {
const val Input = "Input"
const val Progress = "Progress"
}
private var fileCount = 0
private var fileCopied = 0
override suspend fun doWork(): Result {
withContext(Dispatchers.IO) {
val fileUri = inputData.getString(KEY_FILE_URI)
setProgress(workDataOf(Input to fileUri))
val uri = Uri.parse(fileUri)
val source = DocumentFile.fromTreeUri(applicationContext, uri)
fileCount = source?.countDirFiles() ?: return@withContext Result.failure()
setProgress(workDataOf(Progress to 0f))
val gamedir = source.name!!
val externalFilesDir = DocumentFile.fromFile(applicationContext.getExternalFilesDir(null)!!)
// create a directory to store staged files
val target = externalFilesDir.createDirectory(".$gamedir")!!
source.copyDirTo(applicationContext, this@FileCopyWorker, target)
target.renameTo(gamedir)
setProgress(workDataOf(Progress to 1f))
}
return Result.success()
}
suspend fun fileCopied(count: Int) {
if(count == 0)
return
fileCopied += count
val percentage: Float = fileCopied.toFloat() / fileCount.toFloat();
setProgress(workDataOf(Progress to percentage))
}
}
fun DocumentFile.countDirFiles(): Int {
var count: Int = 0
listFiles().forEach {
if (it.isDirectory)
count += it.countDirFiles()
else
count++
}
return count
}
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!!)
}
}
}
suspend fun DocumentFile.copyDirTo(ctx: Context, worker: FileCopyWorker, dir: DocumentFile) {
var count: Int = 0
listFiles().forEach {
if (it.isDirectory) {
val outDir = dir.createDirectory(it.name!!)!!
it.copyDirTo(ctx, worker, outDir)
} else {
it.copyFileTo(ctx, dir)
count++
}
}
worker.fileCopied(count)
}

View File

@@ -1,10 +0,0 @@
<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

@@ -1,10 +0,0 @@
<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

@@ -1,10 +0,0 @@
<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

@@ -1,5 +0,0 @@
<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

@@ -1,5 +0,0 @@
<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

@@ -1,10 +0,0 @@
<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

@@ -1,10 +0,0 @@
<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

@@ -1,10 +0,0 @@
<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

@@ -1,15 +0,0 @@
<?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

@@ -1,9 +0,0 @@
<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

@@ -1,33 +0,0 @@
<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

@@ -1,35 +0,0 @@
<?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

@@ -1,111 +0,0 @@
<?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

@@ -1,30 +0,0 @@
<?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

@@ -1,14 +0,0 @@
<?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

@@ -1,39 +0,0 @@
<?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

@@ -1,23 +0,0 @@
<?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

@@ -1,11 +0,0 @@
<?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

@@ -1,58 +0,0 @@
<?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

@@ -1,19 +0,0 @@
<?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

@@ -1,51 +0,0 @@
<?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

@@ -1,51 +0,0 @@
<?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

@@ -1,33 +0,0 @@
<?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

@@ -1,23 +0,0 @@
<?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

@@ -1,14 +0,0 @@
<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

@@ -1,19 +0,0 @@
<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>

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