mirror of
https://github.com/FWGS/xash3d-fwgs.git
synced 2026-08-05 11:35:05 +08:00
Compare commits
1 Commits
continuous
...
continuous
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa452160ea |
19
.cirrus.yml
19
.cirrus.yml
@@ -1,10 +1,10 @@
|
|||||||
task:
|
task:
|
||||||
name: freebsd-14-amd64
|
name: freebsd-14-amd64
|
||||||
freebsd_instance:
|
freebsd_instance:
|
||||||
image_family: freebsd-14-3
|
image_family: freebsd-14-2
|
||||||
setup_script:
|
setup_script:
|
||||||
- pkg update
|
- pkg update
|
||||||
- pkg install -y pkgconf git sdl2 python freetype2 libvorbis opusfile bzip2 libbacktrace libogg libvorbis
|
- pkg install -y pkgconf git sdl2 python fontconfig libvorbis opusfile bzip2 libbacktrace
|
||||||
- git submodule update --init --recursive
|
- git submodule update --init --recursive
|
||||||
test_script:
|
test_script:
|
||||||
- ./scripts/cirrus/build_freebsd.sh
|
- ./scripts/cirrus/build_freebsd.sh
|
||||||
@@ -12,21 +12,10 @@ task:
|
|||||||
task:
|
task:
|
||||||
name: freebsd-15-amd64
|
name: freebsd-15-amd64
|
||||||
freebsd_instance:
|
freebsd_instance:
|
||||||
image_family: freebsd-15-0-amd64-zfs
|
image_family: freebsd-15-0-snap
|
||||||
setup_script:
|
setup_script:
|
||||||
- pkg update
|
- pkg update
|
||||||
- pkg install -y pkgconf git sdl2 python freetype2 libvorbis opusfile bzip2 libbacktrace libogg libvorbis
|
- pkg install -y pkgconf git sdl2 python fontconfig libvorbis opusfile bzip2 libbacktrace
|
||||||
- git submodule update --init --recursive
|
- git submodule update --init --recursive
|
||||||
test_script:
|
test_script:
|
||||||
- ./scripts/cirrus/build_freebsd.sh
|
- ./scripts/cirrus/build_freebsd.sh
|
||||||
|
|
||||||
#task:
|
|
||||||
# name: freebsd-16-amd64
|
|
||||||
# freebsd_instance:
|
|
||||||
# image_family: freebsd-16-0-snap
|
|
||||||
# setup_script:
|
|
||||||
# - pkg update
|
|
||||||
# - pkg install -y pkgconf git sdl2 python freetype2 libvorbis opusfile bzip2 libbacktrace libogg libvorbis
|
|
||||||
# - git submodule update --init --recursive
|
|
||||||
# test_script:
|
|
||||||
# - ./scripts/cirrus/build_freebsd.sh
|
|
||||||
|
|||||||
65
.github/workflows/c-cpp.yml
vendored
65
.github/workflows/c-cpp.yml
vendored
@@ -21,12 +21,14 @@ jobs:
|
|||||||
- os: ubuntu-22.04
|
- os: ubuntu-22.04
|
||||||
targetos: linux
|
targetos: linux
|
||||||
targetarch: i386
|
targetarch: i386
|
||||||
- os: ubuntu-22.04-arm
|
- os: ubuntu-22.04
|
||||||
targetos: linux
|
targetos: linux
|
||||||
targetarch: arm64
|
targetarch: arm64
|
||||||
- os: ubuntu-22.04-arm
|
cross: true
|
||||||
|
- os: ubuntu-22.04
|
||||||
targetos: linux
|
targetos: linux
|
||||||
targetarch: armhf
|
targetarch: armhf
|
||||||
|
cross: true
|
||||||
- os: ubuntu-24.04 # riscv64 would benefit from having latest compilers
|
- os: ubuntu-24.04 # riscv64 would benefit from having latest compilers
|
||||||
targetos: linux
|
targetos: linux
|
||||||
targetarch: riscv64
|
targetarch: riscv64
|
||||||
@@ -35,10 +37,13 @@ jobs:
|
|||||||
targetos: linux
|
targetos: linux
|
||||||
targetarch: ppc64el
|
targetarch: ppc64el
|
||||||
cross: true
|
cross: true
|
||||||
# - os: ubuntu-latest
|
# - os: ubuntu-aarch64-22.04
|
||||||
# targetos: linux
|
# targetos: linux
|
||||||
# targetarch: e2k-8c
|
# targetarch: aarch64
|
||||||
# cross: true
|
- os: ubuntu-latest
|
||||||
|
targetos: linux
|
||||||
|
targetarch: e2k-8c
|
||||||
|
cross: true
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
targetos: android
|
targetos: android
|
||||||
targetarch: multiarch
|
targetarch: multiarch
|
||||||
@@ -55,27 +60,22 @@ jobs:
|
|||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
targetos: win32
|
targetos: win32
|
||||||
targetarch: amd64
|
targetarch: amd64
|
||||||
- os: windows-2022 # always use the oldest possible for 32-bit because of older compilers, and better support of certain legacy OSes
|
- os: windows-2019 # always use the oldest possible for 32-bit because of older compilers, and better support of certain legacy OSes
|
||||||
targetos: win32
|
targetos: win32
|
||||||
targetarch: i386
|
targetarch: i386
|
||||||
- os: macos-14 # arm64 as per github documentation
|
- os: macos-14 # arm64 as per github documentation
|
||||||
targetos: apple
|
targetos: apple
|
||||||
targetarch: arm64
|
targetarch: arm64
|
||||||
- os: macos-latest
|
- os: macos-13 # x86 as per github documentation (will they fix it before they deprecate this version?..)
|
||||||
targetos: ios
|
|
||||||
targetarch: arm64
|
|
||||||
- os: macos-15-intel
|
|
||||||
targetos: apple
|
targetos: apple
|
||||||
targetarch: amd64
|
targetarch: amd64
|
||||||
env:
|
env:
|
||||||
SDL_VERSION: 2.32.10
|
SDL_VERSION: 2.32.0
|
||||||
FFMPEG_VERSION: 8.1
|
|
||||||
GH_CPU_ARCH: ${{ matrix.targetarch }}
|
GH_CPU_ARCH: ${{ matrix.targetarch }}
|
||||||
GH_CPU_OS: ${{ matrix.targetos }}
|
|
||||||
GH_CROSSCOMPILING: ${{ matrix.cross }}
|
GH_CROSSCOMPILING: ${{ matrix.cross }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
@@ -85,7 +85,7 @@ jobs:
|
|||||||
FWGS_PFX_PASSWORD: ${{ secrets.FWGS_PFX_PASSWORD }}
|
FWGS_PFX_PASSWORD: ${{ secrets.FWGS_PFX_PASSWORD }}
|
||||||
run: bash scripts/gha/build_${{ matrix.targetos }}.sh
|
run: bash scripts/gha/build_${{ matrix.targetos }}.sh
|
||||||
- name: Upload engine (artifacts)
|
- name: Upload engine (artifacts)
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: artifact-${{ matrix.targetos }}-${{ matrix.targetarch }}
|
name: artifact-${{ matrix.targetos }}-${{ matrix.targetarch }}
|
||||||
path: artifacts/*
|
path: artifacts/*
|
||||||
@@ -97,15 +97,15 @@ jobs:
|
|||||||
include:
|
include:
|
||||||
- app: su.xash.Engine.Compat.i386
|
- app: su.xash.Engine.Compat.i386
|
||||||
container:
|
container:
|
||||||
image: ghcr.io/flathub-infra/flatpak-github-actions:freedesktop-25.08
|
image: bilelmoussaoui/flatpak-github-actions:freedesktop-24.08
|
||||||
options: --privileged
|
options: --privileged
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Build flatpak (Compat.i386)
|
- name: Build flatpak (Compat.i386)
|
||||||
uses: FWGS/flatpak-github-actions/flatpak-builder@v6.5
|
uses: FWGS/flatpak-github-actions/flatpak-builder@v6.3-fix
|
||||||
with:
|
with:
|
||||||
bundle: ${{ matrix.app }}.flatpak
|
bundle: ${{ matrix.app }}.flatpak
|
||||||
manifest-path: scripts/flatpak/${{ matrix.app }}.yml
|
manifest-path: scripts/flatpak/${{ matrix.app }}.yml
|
||||||
@@ -115,10 +115,31 @@ jobs:
|
|||||||
needs: [build, flatpak]
|
needs: [build, flatpak]
|
||||||
if: ${{ github.event_name == 'push' }}
|
if: ${{ github.event_name == 'push' }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
- name: Remove old release, fetch artifacts, repackage binaries and upload new release
|
- name: Remove old release, fetch artifacts, repackage binaries and upload new release
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
BASE_BUILDNUM_DATE: "2015-04-01"
|
RELEASE_TAG: ${{ github.ref_name == 'master' && 'continuous' || format('continuous-{0}', github.ref_name) }}
|
||||||
run: bash scripts/gha/make_release.sh "Xash3D FWGS" "${{ github.ref_name }}"
|
run: |
|
||||||
|
gh release delete "$RELEASE_TAG" \
|
||||||
|
--yes \
|
||||||
|
--cleanup-tag \
|
||||||
|
--repo "$GITHUB_REPOSITORY" || true
|
||||||
|
gh run download "$GITHUB_RUN_ID" \
|
||||||
|
--dir artifacts/ \
|
||||||
|
--repo "$GITHUB_REPOSITORY"
|
||||||
|
pushd artifacts/
|
||||||
|
echo "Found artifacts:"
|
||||||
|
ls
|
||||||
|
for i in $(find -mindepth 1 -maxdepth 1 -type d); do
|
||||||
|
mv "$i"/* .
|
||||||
|
rm -rf "$i"
|
||||||
|
done
|
||||||
|
echo "Repackaged artifacts:"
|
||||||
|
ls -R
|
||||||
|
popd
|
||||||
|
sleep 20s
|
||||||
|
gh release create "$RELEASE_TAG" artifacts/* \
|
||||||
|
--title "Xash3D FWGS Continuous ${{ github.ref_name }} Build" \
|
||||||
|
--target $GITHUB_SHA \
|
||||||
|
--repo "$GITHUB_REPOSITORY" \
|
||||||
|
--prerelease
|
||||||
|
|||||||
18
.gitignore
vendored
18
.gitignore
vendored
@@ -222,7 +222,7 @@ publish/
|
|||||||
# Publish Web Output
|
# Publish Web Output
|
||||||
*.[Pp]ublish.xml
|
*.[Pp]ublish.xml
|
||||||
*.azurePubxml
|
*.azurePubxml
|
||||||
# TODO: Comment the next line if you want to checkin your web deploy settings
|
# TODO: Comment the next line if you want to checkin your web deploy settings
|
||||||
# but database connection strings (with potential passwords) will be unencrypted
|
# but database connection strings (with potential passwords) will be unencrypted
|
||||||
*.pubxml
|
*.pubxml
|
||||||
*.publishproj
|
*.publishproj
|
||||||
@@ -318,12 +318,6 @@ build-*
|
|||||||
*.includes
|
*.includes
|
||||||
*.files
|
*.files
|
||||||
|
|
||||||
# ios
|
|
||||||
ios/cl_dlls/*
|
|
||||||
ios/dlls/*
|
|
||||||
ios/*.framework
|
|
||||||
scripts/ios/mod-build
|
|
||||||
|
|
||||||
# Waf
|
# Waf
|
||||||
build_current
|
build_current
|
||||||
*waf-*/
|
*waf-*/
|
||||||
@@ -345,19 +339,9 @@ __pycache__
|
|||||||
.history/*
|
.history/*
|
||||||
.cache/*
|
.cache/*
|
||||||
enc_temp_folder/
|
enc_temp_folder/
|
||||||
Taskfile.yml
|
|
||||||
|
|
||||||
# KDevelop4
|
# KDevelop4
|
||||||
*.kdev4
|
*.kdev4
|
||||||
|
|
||||||
# ccls langauge server
|
# ccls langauge server
|
||||||
.ccls-*
|
.ccls-*
|
||||||
|
|
||||||
# JetBrains
|
|
||||||
.idea/
|
|
||||||
cmake-build-*
|
|
||||||
|
|
||||||
# some Android-specific build stuff
|
|
||||||
3rdparty/SDL
|
|
||||||
3rdparty/hlsdk-portable
|
|
||||||
!scripts/build-ninja.py
|
|
||||||
|
|||||||
5
.gitmodules
vendored
5
.gitmodules
vendored
@@ -39,7 +39,4 @@
|
|||||||
url = https://github.com/ianlancetaylor/libbacktrace
|
url = https://github.com/ianlancetaylor/libbacktrace
|
||||||
[submodule "3rdparty/maintui"]
|
[submodule "3rdparty/maintui"]
|
||||||
path = 3rdparty/maintui
|
path = 3rdparty/maintui
|
||||||
url = https://github.com/FWGS/xash3d-maintui.git
|
url = https://github.com/numas13/xash3d-maintui.git
|
||||||
[submodule "3rdparty/library_suffix"]
|
|
||||||
path = 3rdparty/library_suffix
|
|
||||||
url = https://github.com/FWGS/library-suffix.git
|
|
||||||
|
|||||||
2
3rdparty/MultiEmulator
vendored
2
3rdparty/MultiEmulator
vendored
Submodule 3rdparty/MultiEmulator updated: 6a56b7b112...11ff077ca5
2
3rdparty/gl-wes-v2
vendored
2
3rdparty/gl-wes-v2
vendored
Submodule 3rdparty/gl-wes-v2 updated: 5e5a7192e6...6b392aecbb
6
3rdparty/gl4es/wscript
vendored
6
3rdparty/gl4es/wscript
vendored
@@ -13,12 +13,10 @@ def configure(conf):
|
|||||||
|
|
||||||
def build(bld):
|
def build(bld):
|
||||||
gl4es_srcdir = bld.path.find_node('gl4es/src')
|
gl4es_srcdir = bld.path.find_node('gl4es/src')
|
||||||
cflags = []
|
|
||||||
if bld.env.COMPILER_CC != 'msvc':
|
|
||||||
cflags += ['-w', '-fvisibility=hidden', '-std=gnu99']
|
|
||||||
bld.stlib(source = gl4es_srcdir.ant_glob(['gl/*.c', 'gl/*/*.c', 'glx/hardext.c']),
|
bld.stlib(source = gl4es_srcdir.ant_glob(['gl/*.c', 'gl/*/*.c', 'glx/hardext.c']),
|
||||||
target = 'gl4es',
|
target = 'gl4es',
|
||||||
includes = ['gl4es/src', 'gl4es/src/gl', 'gl4es/src/glx', 'gl4es/include'],
|
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'],
|
defines = ['NOX11', 'NO_GBM', 'NO_INIT_CONSTRUCTOR', 'DEFAULT_ES=2', 'NOEGL', 'NO_LOADER', 'STATICLIB'],
|
||||||
cflags = cflags,
|
cflags = ['-w', '-fvisibility=hidden', '-std=gnu99'],
|
||||||
export_includes = '.')
|
export_includes = '.')
|
||||||
|
|||||||
2
3rdparty/libbacktrace/libbacktrace
vendored
2
3rdparty/libbacktrace/libbacktrace
vendored
Submodule 3rdparty/libbacktrace/libbacktrace updated: b9e40069c0...78af4ffa26
44
3rdparty/libbacktrace/wscript
vendored
44
3rdparty/libbacktrace/wscript
vendored
@@ -62,7 +62,7 @@ def options(opt):
|
|||||||
|
|
||||||
def configure(conf):
|
def configure(conf):
|
||||||
# add unsupported platforms here
|
# add unsupported platforms here
|
||||||
if conf.env.DEST_OS in ['nswitch', 'psvita', 'dos', 'irix']:
|
if conf.env.DEST_OS in ['nswitch', 'psvita', 'dos']:
|
||||||
conf.env.DISABLE_LIBBACKTRACE = True
|
conf.env.DISABLE_LIBBACKTRACE = True
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -92,10 +92,7 @@ def configure(conf):
|
|||||||
conf.env.HAVE_FRANDOM_SEED = True
|
conf.env.HAVE_FRANDOM_SEED = True
|
||||||
|
|
||||||
def check_header(hdr):
|
def check_header(hdr):
|
||||||
return {'header_name':hdr, 'msg':'... %s header' % hdr, 'mandatory':False, 'id':hdr, 'compiler': 'c'}
|
return {'header_name':hdr, 'msg':'... %s header' % hdr, 'mandatory':False, 'id':hdr}
|
||||||
|
|
||||||
def check_frag(frag, msg, define, **kw):
|
|
||||||
return dict({'fragment': frag, 'msg':'... %s' % msg, 'mandatory': False, 'define_name': define, 'compiler': 'c'}, **kw)
|
|
||||||
|
|
||||||
conf.multicheck(
|
conf.multicheck(
|
||||||
check_header('dlfcn.h'),
|
check_header('dlfcn.h'),
|
||||||
@@ -116,20 +113,20 @@ def configure(conf):
|
|||||||
check_header('unistd.h'),
|
check_header('unistd.h'),
|
||||||
check_header('windows.h'),
|
check_header('windows.h'),
|
||||||
|
|
||||||
check_frag(FRAGMENT_ATOMIC, '__atomic extensions', 'HAVE_ATOMIC_FUNCTIONS'),
|
{'fragment':FRAGMENT_ATOMIC, 'msg':'... __atomic extensions', 'define_name':'HAVE_ATOMIC_FUNCTIONS', 'mandatory':False},
|
||||||
check_frag(FRAGMENT_SYNC, ' __sync extensions', 'HAVE_SYNC_FUNCTIONS'),
|
{'fragment':FRAGMENT_SYNC, 'msg':'... __sync extensions', 'define_name':'HAVE_SYNC_FUNCTIONS', 'mandatory':False},
|
||||||
check_frag(FRAGMENT_GETPAGESIZE, ' getpagesize function', 'HAVE_DECL_GETPAGESIZE'),
|
{'fragment':FRAGMENT_GETPAGESIZE, 'msg':'... getpagesize function', 'define_name':'HAVE_DECL_GETPAGESIZE', 'mandatory':False},
|
||||||
check_frag(FRAGMENT_STRNLEN, ' strnlen function', 'HAVE_DECL_STRNLEN'),
|
{'fragment':FRAGMENT_STRNLEN, 'msg':'... strnlen function', 'define_name':'HAVE_DECL_STRNLEN', 'mandatory':False},
|
||||||
check_frag(FRAGMENT_DL_ITERATE_PHDR % 'link.h', ' dl_iterate_phdr function in link.h', 'HAVE_DL_ITERATE_PHDR', after_tests=['link.h']),
|
{'fragment':FRAGMENT_DL_ITERATE_PHDR % 'link.h', 'msg':'... dl_iterate_phdr function in link.h', 'define_name':'HAVE_DL_ITERATE_PHDR', 'after_tests': ['link.h'], 'mandatory':False},
|
||||||
check_frag(FRAGMENT_DL_ITERATE_PHDR % 'sys/link.h', ' dl_iterate_phdr function in sys/link.h', 'HAVE_DL_ITERATE_PHDR', after_tests=['sys/link.h']),
|
{'fragment':FRAGMENT_DL_ITERATE_PHDR % 'sys/link.h', 'msg':'... dl_iterate_phdr function in sys/link.h', 'define_name':'HAVE_DL_ITERATE_PHDR', 'after_tests': ['sys/link.h'], 'mandatory':False},
|
||||||
check_frag(FRAGMENT_FCNTL, 'fnctl function', 'HAVE_FCNTL'),
|
{'fragment':FRAGMENT_FCNTL,'msg':'... fnctl function', 'define_name':'HAVE_FCNTL', 'mandatory':False},
|
||||||
check_frag(FRAGMENT_GETEXECNAME, 'getexecname function','HAVE_GETEXECNAME'),
|
{'fragment':FRAGMENT_GETEXECNAME,'msg':'... getexecname function','define_name':'HAVE_GETEXECNAME', 'mandatory':False},
|
||||||
check_frag(FRAGMENT_GETIPINFO, '_Unwind_GetIPInfo function', 'HAVE_GETIPINFO'),
|
{'fragment':FRAGMENT_GETIPINFO, 'msg':'... _Unwind_GetIPInfo function', 'define_name':'HAVE_GETIPINFO', 'mandatory':False},
|
||||||
check_frag(FRAGMENT_KERN_PROC % 'KERN_PROC', 'KERN_PROC and KERN_PROC_PATHNAME defines', 'HAVE_KERN_PROC'),
|
{'fragment':FRAGMENT_KERN_PROC % 'KERN_PROC', 'msg': '... KERN_PROC and KERN_PROC_PATHNAME defines', 'define_name':'HAVE_KERN_PROC', 'mandatory':False},
|
||||||
check_frag(FRAGMENT_KERN_PROC % 'KERN_PROC_ARGS', 'KERN_PROC_ARGS and KERN_PROC_PATHNAME defines', 'HAVE_KERN_PROC_ARGS'),
|
{'fragment':FRAGMENT_KERN_PROC % 'KERN_PROC_ARGS', 'msg': '... KERN_PROC_ARGS and KERN_PROC_PATHNAME defines', 'define_name':'HAVE_KERN_PROC_ARGS', 'mandatory':False},
|
||||||
check_frag(FRAGMENT_LOADQUERY, 'loadquery function', 'HAVE_LOADQUERY'),
|
{'fragment':FRAGMENT_LOADQUERY, 'msg': '... loadquery function', 'define_name':'HAVE_LOADQUERY', 'mandatory':False},
|
||||||
check_frag(FRAGMENT_LSTAT, 'lstat function', 'HAVE_LSTAT'),
|
{'fragment':FRAGMENT_LSTAT, 'msg': '... lstat function', 'define_name':'HAVE_LSTAT', 'mandatory':False},
|
||||||
check_frag(FRAGMENT_READLINK, 'readlink function', 'HAVE_READLINK'),
|
{'fragment':FRAGMENT_READLINK, 'msg': '... readlink function', 'define_name':'HAVE_READLINK', 'mandatory':False},
|
||||||
|
|
||||||
# {'lib':'lzma', 'define_name':'HAVE_LIBLZMA', 'uselib_store':'lzma', 'msg':'... lzma library', 'mandatory':False},
|
# {'lib':'lzma', 'define_name':'HAVE_LIBLZMA', 'uselib_store':'lzma', 'msg':'... lzma library', 'mandatory':False},
|
||||||
# {'lib':'z', 'define_name':'HAVE_ZLIB', 'uselib_store':'z', 'msg':'... zlib library', 'mandatory':False},
|
# {'lib':'z', 'define_name':'HAVE_ZLIB', 'uselib_store':'z', 'msg':'... zlib library', 'mandatory':False},
|
||||||
@@ -162,13 +159,8 @@ def build(bld):
|
|||||||
if bld.env.DISABLE_LIBBACKTRACE:
|
if bld.env.DISABLE_LIBBACKTRACE:
|
||||||
return
|
return
|
||||||
|
|
||||||
sources = ['atomic.c', 'dwarf.c', 'fileline.c', 'posix.c', 'print.c', 'sort.c', 'state.c', 'backtrace.c', 'simple.c']
|
# we specifically only want mmap-based allocators because calling malloc is not safe from signal handlers
|
||||||
|
sources = ['atomic.c', 'dwarf.c', 'fileline.c', 'posix.c', 'print.c', 'sort.c', 'state.c', 'backtrace.c', 'simple.c', 'mmap.c', 'mmapio.c']
|
||||||
if bld.env.DEST_OS == 'win32':
|
|
||||||
sources += ['read.c', 'alloc.c']
|
|
||||||
else:
|
|
||||||
# we specifically only want mmap-based allocators because calling malloc is not safe from signal handlers
|
|
||||||
sources += ['mmap.c', 'mmapio.c']
|
|
||||||
|
|
||||||
if bld.env.DEST_BINFMT == 'pe':
|
if bld.env.DEST_BINFMT == 'pe':
|
||||||
sources += ['pecoff.c']
|
sources += ['pecoff.c']
|
||||||
|
|||||||
1
3rdparty/library_suffix
vendored
1
3rdparty/library_suffix
vendored
Submodule 3rdparty/library_suffix deleted from 6e14ae31e8
2
3rdparty/maintui
vendored
2
3rdparty/maintui
vendored
Submodule 3rdparty/maintui updated: e5f143ee33...a136560e3f
2
3rdparty/mainui
vendored
2
3rdparty/mainui
vendored
Submodule 3rdparty/mainui updated: af2b5abdde...b1d006e521
2
3rdparty/nanogl
vendored
2
3rdparty/nanogl
vendored
Submodule 3rdparty/nanogl updated: c75f1326f9...dbfe2e75b3
78
3rdparty/opus/wscript
vendored
78
3rdparty/opus/wscript
vendored
@@ -23,17 +23,6 @@ int main (void) {
|
|||||||
int * array = alloca(foo);
|
int * array = alloca(foo);
|
||||||
}'''
|
}'''
|
||||||
|
|
||||||
FRAGMENT_LRINT='''#include <math.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
int main (int argc, char **argv) {
|
|
||||||
return lrint%s((%s)atof(argv[1]));
|
|
||||||
}'''
|
|
||||||
|
|
||||||
# assuming MSVC always enables NEON
|
|
||||||
FRAGMENT_NEON='''#if !defined __ARM_NEON__ && !defined _MSC_VER
|
|
||||||
#error
|
|
||||||
#endif'''
|
|
||||||
|
|
||||||
def options(opt):
|
def options(opt):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -42,61 +31,15 @@ def configure(conf):
|
|||||||
conf.fatal('Can\'t find opus submodule. Run `git submodule update --init --recursive`.')
|
conf.fatal('Can\'t find opus submodule. Run `git submodule update --init --recursive`.')
|
||||||
return
|
return
|
||||||
|
|
||||||
if conf.check_cc(fragment=FRAGMENT_LRINT % ('', 'double'), msg = 'Checking for C99 lrint', use = 'M', mandatory = False):
|
|
||||||
conf.define('HAVE_LRINT', 1)
|
|
||||||
|
|
||||||
if conf.check_cc(fragment=FRAGMENT_LRINT % ('f', 'float'), msg = 'Checking for C99 lrintf', use = 'M', mandatory = False):
|
|
||||||
conf.define('HAVE_LRINTF', 1)
|
|
||||||
|
|
||||||
# Check for C99 variable-size arrays, or alloca() as fallback
|
# 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):
|
if conf.check_cc(fragment=FRAGMENT_VLA, msg = 'Checking for C99 VLA support', mandatory = False):
|
||||||
conf.define('VAR_ARRAYS', 1)
|
conf.define('VAR_ARRAYS', 1)
|
||||||
elif conf.check_cc(fragment=FRAGMENT_ALLOCA_H, msg = 'Checking for alloca in alloca.h header', mandatory = False):
|
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('USE_ALLOCA', 1)
|
||||||
conf.define('HAVE_ALLOCA_H', 1)
|
conf.define('HAVE_ALLOCA_H', 1)
|
||||||
elif conf.check_cc(fragment=FRAGMENT_STDLIB_H, msg = 'Checking for alloca.h in stdlib.h', mandatory = False):
|
elif conf.check_cc(fragmenmt=FRAGMENT_STDLIB_H, msg = 'Checking for alloca.h in stdlib.h', mandatory = False):
|
||||||
conf.define('USE_ALLOCA', 1)
|
conf.define('USE_ALLOCA', 1)
|
||||||
|
|
||||||
if conf.env.DEST_CPU in ['thumb', 'arm']:
|
|
||||||
if conf.check(header_name='arm_neon.h', mandatory = False):
|
|
||||||
if conf.check(fragment=FRAGMENT_NEON, msg = 'Checking if compiler enabled NEON', mandatory = False):
|
|
||||||
conf.env.HAVE_NEON = True
|
|
||||||
conf.define('OPUS_ARM_MAY_HAVE_NEON_INTR', 1)
|
|
||||||
conf.define('OPUS_ARM_PRESUME_NEON', 1)
|
|
||||||
elif conf.env.DEST_CPU.startswith('x86'):
|
|
||||||
if conf.check(header_name='xmmintrin.h', mandatory = False):
|
|
||||||
if conf.env.COMPILER_CC != 'msvc':
|
|
||||||
conf.env.CFLAGS += ['-msse']
|
|
||||||
conf.define('OPUS_X86_MAY_HAVE_SSE', 1)
|
|
||||||
if conf.env.DEST_SIZEOF_VOID_P > 4:
|
|
||||||
conf.define('OPUS_X86_PRESUME_SSE', 1)
|
|
||||||
|
|
||||||
if conf.check(header_name='emmintrin.h', mandatory = False):
|
|
||||||
if conf.env.COMPILER_CC != 'msvc':
|
|
||||||
conf.env.CFLAGS += ['-msse2']
|
|
||||||
conf.define('OPUS_X86_MAY_HAVE_SSE2', 1)
|
|
||||||
if conf.env.DEST_SIZEOF_VOID_P > 4:
|
|
||||||
conf.define('OPUS_X86_PRESUME_SSE2', 1)
|
|
||||||
|
|
||||||
# on x86_64 we enable both SSE and SSE2, so RTCD can be disabled (it's actually doesn't even build with RTCD enabled)
|
|
||||||
if conf.env.DEST_SIZEOF_VOID_P == 4:
|
|
||||||
if conf.check(header_name='cpuid.h', mandatory=False):
|
|
||||||
conf.define('CPU_INFO_BY_C', 1)
|
|
||||||
else:
|
|
||||||
conf.define('CPU_INFO_BY_ASM', 1)
|
|
||||||
|
|
||||||
conf.define('OPUS_HAVE_RTCD', 1)
|
|
||||||
|
|
||||||
# if conf.check(header_name='smmintrin.h', mandatory = False):
|
|
||||||
# if conf.env.COMPILER_CC != 'msvc':
|
|
||||||
# conf.env.CFLAGS += ['-msse4.1']
|
|
||||||
# conf.define('OPUS_X86_MAY_HAVE_SSE4_1', 1)
|
|
||||||
|
|
||||||
# if conf.check(header_name='immintrin.h', mandatory = False):
|
|
||||||
# if conf.env.COMPILER_CC != 'msvc':
|
|
||||||
# conf.env.CFLAGS += ['-mavx']
|
|
||||||
# conf.define('OPUS_X86_MAY_HAVE_AVX', 1)
|
|
||||||
|
|
||||||
# TODO: ARM/x86 intrinsics detection
|
# TODO: ARM/x86 intrinsics detection
|
||||||
# TODO: maybe call autotools/cmake/meson instead?
|
# TODO: maybe call autotools/cmake/meson instead?
|
||||||
|
|
||||||
@@ -112,23 +55,8 @@ def build(bld):
|
|||||||
'opus/src/opus_compare.c',
|
'opus/src/opus_compare.c',
|
||||||
'opus/celt/opus_custom_demo.c'
|
'opus/celt/opus_custom_demo.c'
|
||||||
])
|
])
|
||||||
|
includes = ['opus/include/', 'opus/celt/', 'opus/silk/', 'opus/silk/float/']
|
||||||
includes = ['opus', 'opus/include/', 'opus/celt/', 'opus/silk/', 'opus/silk/float/']
|
defines = ['OPUS_BUILD', 'FLOAT_APPROX', 'PACKAGE_VERSION="1.4.0"', 'CUSTOM_MODES']
|
||||||
|
|
||||||
if bld.env.DEST_CPU in ['thumb', 'arm']:
|
|
||||||
if bld.env.HAVE_NEON:
|
|
||||||
sources += bld.path.ant_glob(['opus/silk/arm/*.c', 'opus/celt/arm/*.c'])
|
|
||||||
includes += ['opus/silk/arm', 'opus/celt/arm']
|
|
||||||
elif bld.env.DEST_CPU.startswith('x86'):
|
|
||||||
sources += ['opus/silk/x86/x86_silk_map.c',
|
|
||||||
'opus/celt/x86/pitch_sse.c',
|
|
||||||
'opus/celt/x86/pitch_sse2.c',
|
|
||||||
'opus/celt/x86/vq_sse2.c',
|
|
||||||
'opus/celt/x86/x86_celt_map.c',
|
|
||||||
'opus/celt/x86/x86cpu.c']
|
|
||||||
includes += ['opus/silk/x86', 'opus/celt/x86']
|
|
||||||
|
|
||||||
defines = ['OPUS_BUILD', 'FLOAT_APPROX', 'PACKAGE_VERSION="1.4.0"', 'CUSTOM_MODES', 'ENABLE_HARDENING']
|
|
||||||
|
|
||||||
bld.stlib(
|
bld.stlib(
|
||||||
source = sources,
|
source = sources,
|
||||||
|
|||||||
2
3rdparty/vgui_support
vendored
2
3rdparty/vgui_support
vendored
Submodule 3rdparty/vgui_support updated: 36219aedfc...9f2b9512cd
@@ -29,11 +29,10 @@
|
|||||||
|
|
||||||
* This project uses mixed Quake's and HLSDK's C/C++ code style convention.
|
* This project uses mixed Quake's and HLSDK's C/C++ code style convention.
|
||||||
* In short:
|
* In short:
|
||||||
* Use spaces in parenthesis, but not when two parentheses are consecutive.
|
* Use spaces in parenthesis.
|
||||||
* Only tabs for indentation.
|
* Only tabs for indentation.
|
||||||
* Any brace must have it's own line.
|
* Any brace must have it's own line.
|
||||||
* Short blocks, if statements and loops on single line are allowed.
|
* Short blocks, if statements and loops on single line are allowed.
|
||||||
* Prefer generic utilities from libpublic over rolling your own.
|
|
||||||
* Avoid magic numbers.
|
* Avoid magic numbers.
|
||||||
* While macros are powerful, it's better to avoid overusing them.
|
* While macros are powerful, it's better to avoid overusing them.
|
||||||
* If you unsure, try to mimic code style from anywhere else of engine source code.
|
* If you unsure, try to mimic code style from anywhere else of engine source code.
|
||||||
|
|||||||
@@ -1,103 +0,0 @@
|
|||||||
## Setting up Xash3D FWGS in Qt Creator
|
|
||||||
|
|
||||||
### Intro
|
|
||||||
|
|
||||||
Most versions of Qt Creator support using `compile_commands.json` as a project
|
|
||||||
file and Waf supports generating this file in a compliant format. In this
|
|
||||||
tutorial we will leverage that features.
|
|
||||||
|
|
||||||
The tutorial is written using Qt Creator 18.0, but I urge you to use whatever
|
|
||||||
latest Qt Creator version is available, as it's often has important bug fixes.
|
|
||||||
|
|
||||||
### Step 1. Configure the build.
|
|
||||||
|
|
||||||
`compile_commands.json` file, as it can be guessed from it's name, contains
|
|
||||||
every command used to invoke compiler during build. As the build process
|
|
||||||
depends on the build configuration, we have to configure it first. I'm not
|
|
||||||
going to get into details here, as basics of compiling Xash3D FWGS already
|
|
||||||
explained in `README.md`, located in repository root.
|
|
||||||
|
|
||||||
Save the configure step command somewhere, as it's going to be used later.
|
|
||||||
|
|
||||||
### Step 2. Generate `compile_commands.json` file.
|
|
||||||
|
|
||||||
Usually, this file is located in `build` directory after a successful build.
|
|
||||||
However, it's also possible to generate it manually by running follow command:
|
|
||||||
|
|
||||||
On *nix systems:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ ./waf clangdb
|
|
||||||
```
|
|
||||||
|
|
||||||
Or on Windows:
|
|
||||||
|
|
||||||
```
|
|
||||||
> waf.bat clangdb
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 3. Load the project in Qt Creator.
|
|
||||||
|
|
||||||
To do this, you need to be sure that `Compilation Databases` plugin is enabled.
|
|
||||||
As it's considered experimental, it probably needs to be enabled manually. It
|
|
||||||
can be done in `Help` -> `About Plugins...`. Locate it in `Build Systems`
|
|
||||||
category, put the checkbox and press `OK`. The IDE will ask for restart, so do
|
|
||||||
this.
|
|
||||||
|
|
||||||
After IDE has been restarted, you can use `File` -> `Open File or Project...`.
|
|
||||||
Navigate to Xash3D FWGS directory, then to `build` directory and select
|
|
||||||
`compile_commands.json` file. If everything is done correctly, IDE will ask to
|
|
||||||
set up the project. Press `Configure Project` button, as most options there are
|
|
||||||
usually unapplicable to us.
|
|
||||||
|
|
||||||
Next IDE will show project tree, however you might not see any files. This is
|
|
||||||
happens as Qt Creator "thinks" our project root located in `build` directory.
|
|
||||||
Thankfully it provides a way to override this. To fix it, click with right
|
|
||||||
mouse button on the root of project tree (it's usually called something like
|
|
||||||
`build [master]`) and select `Change Root Directory`. In opened window choose
|
|
||||||
Xash3D FWGS repository root and press `OK`.
|
|
||||||
|
|
||||||
### Step 4. Tell IDE how to build this project.
|
|
||||||
|
|
||||||
Open `Projects` on the left menu and choose `Build Settings` tab. You should
|
|
||||||
see an empty configuration for build and clean steps. Press on `Add Build Step`
|
|
||||||
button and choose `Custom Process Step`.
|
|
||||||
|
|
||||||
In created step you should see `Command`, `Arguments` and `Working Directory`.
|
|
||||||
|
|
||||||
For the command, choose the Python executable. For arguments, use configure
|
|
||||||
command for Waf from the first step. Working directory is usually set by IDE
|
|
||||||
to `%{buildDir}`. You need to select the upper directory, where the `waf` is
|
|
||||||
located. To save some time, it can be specified as `%{buildDir}/../`.
|
|
||||||
|
|
||||||
Add another custom step, fill it with the same command and working directory.
|
|
||||||
For arguments use `waf build`.
|
|
||||||
|
|
||||||
Below you should see `Clean steps`. Create a clean step the same way, fill the
|
|
||||||
arguments and working directory as usual, for arguments use `waf clean`.
|
|
||||||
|
|
||||||
### Step 5. Tell IDE how to copy built binaries.
|
|
||||||
|
|
||||||
Now select `Deploy Settings` tab. Press `Add Deploy Step` -> `Custom Process
|
|
||||||
Step`. Again, same command and working directory. In arguments field type:
|
|
||||||
`waf install --destdir="<here put where Xash binaries should be copied to>"`.
|
|
||||||
|
|
||||||
### Step 6. Finally, set up Qt Creator to run the engine.
|
|
||||||
|
|
||||||
Select the `Run Settings` tab. Below you might see familiar text fields:
|
|
||||||
`Executable`, `Command line arguments` and `Working directory`.
|
|
||||||
|
|
||||||
For working directory click on `Browse...` button and select the folder where
|
|
||||||
Xash3D executable will be copied to, from previous step.
|
|
||||||
|
|
||||||
For executable type `./xash3d` if you're on *nix or `xash3d.exe` if you're on
|
|
||||||
Windows. The command line arguments are the same you use to run the engine. Can
|
|
||||||
be empty as well, but I personally use `-dev 2 -log` for extra verbosity.
|
|
||||||
|
|
||||||
### Outro
|
|
||||||
|
|
||||||
At this moment, you should be able to press `Run` button, wait until build
|
|
||||||
finishes and see running Xash3D FWGS. On Windows to use the debugger you might
|
|
||||||
need the `CDB` debugger and appropriate `qtcreatorcdbext` binary for debugging
|
|
||||||
32-bit applications and a separate for 64-bit, but that's left as an exercise
|
|
||||||
for the reader and a potential improvement for this document.
|
|
||||||
@@ -18,7 +18,7 @@ Initial Xash3D SDL2/Linux port author, Xash3D FWGS engine maintainer, creator of
|
|||||||
|
|
||||||
[cs16-client](https://github.com/Velaron/cs16-client) & [tf15-client](https://github.com/Velaron/tf15-client) maintainer and Xash3D FWGS [contributor](https://github.com/FWGS/xash3d-fwgs/commits?author=Velaron) (Android port, voice chat, etc).
|
[cs16-client](https://github.com/Velaron/cs16-client) & [tf15-client](https://github.com/Velaron/tf15-client) maintainer and Xash3D FWGS [contributor](https://github.com/FWGS/xash3d-fwgs/commits?author=Velaron) (Android port, voice chat, etc).
|
||||||
|
|
||||||
* [Boosty page](https://boosty.to/velaron)
|
* [BuyMeACoffee page](https://www.buymeacoffee.com/velaron)
|
||||||
|
|
||||||
## [SNMetamorph](https://github.com/SNMetamorph)
|
## [SNMetamorph](https://github.com/SNMetamorph)
|
||||||
|
|
||||||
@@ -26,12 +26,3 @@ Initial Xash3D SDL2/Linux port author, Xash3D FWGS engine maintainer, creator of
|
|||||||
|
|
||||||
* [Boosty page](https://boosty.to/snmetamorph)
|
* [Boosty page](https://boosty.to/snmetamorph)
|
||||||
* [Other donation methods](https://snmetamorph.github.io/donate)
|
* [Other donation methods](https://snmetamorph.github.io/donate)
|
||||||
|
|
||||||
## [$_Vladislav](https://github.com/Vladislav4KZ)
|
|
||||||
|
|
||||||
[YaPB Project](https://github.com/yapb) member, [Xash3D FWGS](https://github.com/FWGS/xash3d-fwgs) tester,
|
|
||||||
editor of the [Official YaPB Documentation](https://github.com/yapb/docs) (in English and Russian), curator of the [YaPB Graph Database](https://github.com/yapb/graph) and author of [YaPB Waypoint/Graph Pack](https://gamebanana.com/mods/40087).
|
|
||||||
|
|
||||||
Also does the Russian localization of text, images (gfx/shell), as well as additional menu buttons in English used in Xash3D FWGS for various mods.
|
|
||||||
|
|
||||||
* [Boosty page](https://boosty.to/rasstaman1337)
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
# Addon folders in Xash3D FWGS
|
|
||||||
|
|
||||||
Xash3D FWGS supports both GoldSource-style addon folders and has few own. Each directory can have it's own archives that will be mounted with lower priority than directory itself.
|
|
||||||
|
|
||||||
Below is the mounts map, in order of precedence from least important to most important.
|
|
||||||
|
|
||||||
|--------------------|------|
|
|
||||||
| Directory | Note |
|
|
||||||
|--------------------|------|
|
|
||||||
| `$game/downloaded` | Always added. Used to store server downloads.|
|
|
||||||
| `$game` | This is the game directory. |
|
|
||||||
| `$game/custom` | Always added. Used for user modifications content. |
|
|
||||||
| `$game_hd` | Added with `fs_mount_hd` set to non-zero value. Used for high definition content, similar to GoldSrc.. |
|
|
||||||
| `$game_addon` | Added with `fs_mount_addon` set to non-zero value. Used for user modifications content, similar to GoldSrc. |
|
|
||||||
| `$game_lv` | Added with `fs_mount_lv` set to non-zero value. Used for low-violence content, similar to GoldSrc. |
|
|
||||||
| `$game_$language` | Added with `fs_mount_l10n` set to non-zero value. Language is controlled with `ui_language` cvar or `-language` command line switch. Used for localization content, similar to GoldSrc. |
|
|
||||||
@@ -42,6 +42,3 @@ Issue #3: Some mods (like The Specialists, Tyrian, ...) already apply suffixes _
|
|||||||
Resolution: On x86 on **Win/Lin/Mac**, don't change anything. Otherwise, strip the _i?86 part and follow the usual scheme.
|
Resolution: On x86 on **Win/Lin/Mac**, don't change anything. Otherwise, strip the _i?86 part and follow the usual scheme.
|
||||||
|
|
||||||
See discussion: https://github.com/FWGS/xash3d-fwgs/issues/39
|
See discussion: https://github.com/FWGS/xash3d-fwgs/issues/39
|
||||||
|
|
||||||
Issue #4: When distributing game libraries on Android inside an APK, they couldn't be loaded.
|
|
||||||
Resolution: Enable `useLegacyPackaging` option in build.gradle, when distributing games in APK. Always force game libraries to have `lib` prefix on Android, regardless if they are packaged in APK or not..
|
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
# Steam broker protocol
|
|
||||||
`sb_connect <ip:port> <server's steam id> <secure> <challenge>` - used for obtaining auth ticket during connection to GoldSrc server
|
|
||||||
|
|
||||||
`sb_disconnect <ip:port> <challenge>` - used for signaling broker about disconnecting from GoldSrc server
|
|
||||||
|
|
||||||
`sb_gamedir <gamedir>` - used for signaling broker about game startup and for announcing started mod, so broker could choose proper AppID for Steam API initialization
|
|
||||||
|
|
||||||
`sb_terminate` - used for signaling broker about game shutdown, ideally broker should be terminated too (and later restarted automatically and waiting for sb_gamedir message)
|
|
||||||
@@ -1,18 +1,15 @@
|
|||||||
# Support for GoldSrc network protocol
|
# 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:
|
For connecting to GoldSrc-based servers, use this command:
|
||||||
```
|
```
|
||||||
connect ip:port gs
|
connect ip:port gs
|
||||||
```
|
```
|
||||||
|
|
||||||
You need to use [Steam API broker](https://github.com/FWGS/steam-broker) and have purchased Half-Life 1 on your Steam account to be able to join GoldSrc servers.
|
But keep in mind, there are requirement for server to be able to accept connections from Xash3D-based clients: it should use Reunion.
|
||||||
|
Without this requirement, you will just get "Steam validation rejected" error on connecting.
|
||||||
|
|
||||||
Broker will run on your PC and will be responsible for communicating with Steam client and obtaining auth tickets.
|
That is because proper authorization with Steam API is not implemented in engine yet (but we have plans on it).
|
||||||
Also, make sure to set that broker as current ticket generator using console or edit `config.cfg` file manually:
|
|
||||||
```
|
|
||||||
cl_ticket_generator steam
|
|
||||||
```
|
|
||||||
|
|
||||||
If you want to join GoldSrc servers from other device (Android/iOS or console), make sure to set proper broker IP address with `cl_steam_broker_addr` console variable. Default value assumed that broker is running on the same device as game client.
|
Also, we encountered that some GoldSrc-based servers are recognizing Xash3D clients as "fake clients" and banning/kicking them. Maybe this problem will be
|
||||||
|
|
||||||
By the way, 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.
|
solved along with better compatibility with GoldSrc behavior, but may be not - we don't know logic behind this fake client checks.
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
/*
|
|
||||||
filename.ext - do not include this file, only copy the header
|
|
||||||
Copyright (C) 2015-2025 Xash3D FWGS contributors
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
@@ -16,9 +16,6 @@ Mirrored on github - https://github.com/JoelTroch/am_src_rebirth
|
|||||||
## Battle Grounds
|
## Battle Grounds
|
||||||
Mirrored on github - https://github.com/nekonomicon/BattleGrounds
|
Mirrored on github - https://github.com/nekonomicon/BattleGrounds
|
||||||
|
|
||||||
## Brain Bread
|
|
||||||
Official github repository - https://github.com/IronOak-Studios/BrainBread
|
|
||||||
|
|
||||||
## Bubblemod
|
## Bubblemod
|
||||||
Download page on official site(WM snapshot) - [http://www.bubblemod.org/dl_default.php](https://web.archive.org/web/20130717133158/http://www.bubblemod.org/dl_default.php)
|
Download page on official site(WM snapshot) - [http://www.bubblemod.org/dl_default.php](https://web.archive.org/web/20130717133158/http://www.bubblemod.org/dl_default.php)
|
||||||
|
|
||||||
@@ -53,9 +50,6 @@ Alpha 2.0 - https://www.gamers-desire.de/details/2830
|
|||||||
## ESHQ
|
## ESHQ
|
||||||
Official github repository - https://github.com/adslbarxatov/xash3d-for-ESHQ
|
Official github repository - https://github.com/adslbarxatov/xash3d-for-ESHQ
|
||||||
|
|
||||||
## Feline
|
|
||||||
Available on ModDB - https://www.moddb.com/mods/feline-10/downloads/feline-sdk
|
|
||||||
|
|
||||||
## Flat-Life
|
## Flat-Life
|
||||||
Available on GamerLab - http://gamer-lab.com/eng/code_mods_goldsrc/Half-Life_2D_(Flat-Life)
|
Available on GamerLab - http://gamer-lab.com/eng/code_mods_goldsrc/Half-Life_2D_(Flat-Life)
|
||||||
|
|
||||||
@@ -70,9 +64,6 @@ Version 3.0, mirrored on github - https://github.com/nekonomicon/Go-mod30
|
|||||||
## GT mod
|
## GT mod
|
||||||
Available on GamerLab - http://gamer-lab.com/eng/code_mods_goldsrc/GT_mod_(Polnie_ishodniki)
|
Available on GamerLab - http://gamer-lab.com/eng/code_mods_goldsrc/GT_mod_(Polnie_ishodniki)
|
||||||
|
|
||||||
## Half-Force
|
|
||||||
Available on ModDB - https://www.moddb.com/mods/half-force/downloads/half-force-v20-sdk
|
|
||||||
|
|
||||||
## Half-Life: Advanced Deathmatch
|
## Half-Life: Advanced Deathmatch
|
||||||
Mirrored on github - https://github.com/solidi/hl-mods/tree/master/hla
|
Mirrored on github - https://github.com/solidi/hl-mods/tree/master/hla
|
||||||
|
|
||||||
@@ -85,9 +76,6 @@ Available on ModDB - https://www.moddb.com/mods/half-life-echoes
|
|||||||
## Half-Life: Expanded Arsenal
|
## Half-Life: Expanded Arsenal
|
||||||
Available on ModDB - https://www.moddb.com/mods/half-life-expanded-arsenal
|
Available on ModDB - https://www.moddb.com/mods/half-life-expanded-arsenal
|
||||||
|
|
||||||
## Half-Life: Field Intensity
|
|
||||||
Branch **field_intensity_1.7** in halflife-featureful - https://github.com/FreeSlave/halflife-featureful/tree/field_intensity_1.7
|
|
||||||
|
|
||||||
## Half-Life: Gravgun mod
|
## Half-Life: Gravgun mod
|
||||||
Branch **gravgun** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/gravgun
|
Branch **gravgun** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/gravgun
|
||||||
|
|
||||||
@@ -109,9 +97,6 @@ Official github repository - https://github.com/Fograin/hl-subsmod-ex
|
|||||||
## Half-Life: Rally
|
## Half-Life: Rally
|
||||||
Official gitlab repository - https://gitlab.com/hlrally/src
|
Official gitlab repository - https://gitlab.com/hlrally/src
|
||||||
|
|
||||||
## Half-Life: RXT
|
|
||||||
Available on ModDB - https://www.moddb.com/mods/half-life-rxt/downloads/rxt-source-code-v57
|
|
||||||
|
|
||||||
## Half-Life: Weapon Edition
|
## Half-Life: Weapon Edition
|
||||||
Available on ModDB - https://www.moddb.com/mods/half-life-weapon-edition/downloads/half-life-weapon-edition-1508-alpha-open-src
|
Available on ModDB - https://www.moddb.com/mods/half-life-weapon-edition/downloads/half-life-weapon-edition-1508-alpha-open-src
|
||||||
|
|
||||||
@@ -133,25 +118,15 @@ Official github repository - https://github.com/HeathGames/half_rats_parasomnia_
|
|||||||
## Half-Screwed
|
## Half-Screwed
|
||||||
Official github repository - https://github.com/desukuran/half-screwed
|
Official github repository - https://github.com/desukuran/half-screwed
|
||||||
|
|
||||||
## Halo: GoldSource
|
|
||||||
Available on ModDB - https://www.moddb.com/mods/halo-goldsrc/downloads/halo-gold-source-summer-2025-release-v10-sdk
|
|
||||||
|
|
||||||
## Headcrab Frenzy
|
## Headcrab Frenzy
|
||||||
Version 1.3 on ModDB - https://www.moddb.com/mods/headcrab-frenzy/downloads/headcrab-frenzy-13-beta-source-code
|
Version 1.3 on ModDB - https://www.moddb.com/mods/headcrab-frenzy/downloads/headcrab-frenzy-13-beta-source-code
|
||||||
|
|
||||||
## Heart of Evil
|
## Heart of Evil
|
||||||
Mirrored on github - https://github.com/nekonomicon/HeartOfEvil
|
Mirrored on github - https://github.com/nekonomicon/HeartOfEvil
|
||||||
|
|
||||||
Mirrored on github (Includes maps and models source files) - https://github.com/malortie/heart-of-evil-source-files
|
|
||||||
|
|
||||||
## Ingram Chillin' Mod
|
## Ingram Chillin' Mod
|
||||||
Official SourceForge repository - https://sourceforge.net/projects/icm-hl/
|
Official SourceForge repository - https://sourceforge.net/projects/icm-hl/
|
||||||
|
|
||||||
## Master Sword
|
|
||||||
Official github repository of Master Sword Classic - https://github.com/BerntA/MasterSwordClassic
|
|
||||||
|
|
||||||
Official github repository of Master Sword Rebirth - https://github.com/MSRevive/MasterSwordRebirth
|
|
||||||
|
|
||||||
## MechMod
|
## MechMod
|
||||||
Official github repository - https://github.com/vermagav/mechmod
|
Official github repository - https://github.com/vermagav/mechmod
|
||||||
|
|
||||||
@@ -175,9 +150,6 @@ Prealpha, mirrored on github - https://github.com/a1batross/Paranoia2_ancient
|
|||||||
|
|
||||||
Version 1.51, mirrored on github - https://github.com/a1batross/Paranoia2_original
|
Version 1.51, mirrored on github - https://github.com/a1batross/Paranoia2_original
|
||||||
|
|
||||||
## Public Enemy
|
|
||||||
Official github repository - https://github.com/IronOak-Studios/Public-Enemy
|
|
||||||
|
|
||||||
## Raven City
|
## Raven City
|
||||||
*Unfinished mod*
|
*Unfinished mod*
|
||||||
|
|
||||||
@@ -209,9 +181,6 @@ Mirrored on code.idtech.space - https://code.idtech.space/vera/halflife-wastelan
|
|||||||
## Wizard Wars
|
## Wizard Wars
|
||||||
Download page on official site - http://www.thothie.com/ww/
|
Download page on official site - http://www.thothie.com/ww/
|
||||||
|
|
||||||
## Wrong Door
|
|
||||||
Available on ModDB - https://www.moddb.com/mods/wrong-door/downloads/wrong-door-sdk
|
|
||||||
|
|
||||||
## XashXT
|
## XashXT
|
||||||
Mirrored on github - https://github.com/a1batross/XashXT_original
|
Mirrored on github - https://github.com/a1batross/XashXT_original
|
||||||
|
|
||||||
@@ -236,7 +205,7 @@ Branch **redempt** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tr
|
|||||||
OpenAG by YaLTeR - https://github.com/YaLTeR/OpenAG
|
OpenAG by YaLTeR - https://github.com/YaLTeR/OpenAG
|
||||||
|
|
||||||
## Afraid of Monsters
|
## Afraid of Monsters
|
||||||
malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-aom
|
malortie's recreation - https://github.com/malortie/hl-aom
|
||||||
|
|
||||||
Branch **aom** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/aom
|
Branch **aom** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/aom
|
||||||
|
|
||||||
@@ -244,17 +213,17 @@ Branch **aom** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/a
|
|||||||
Reverse-engineered code: branch **aomdc** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/aomdc
|
Reverse-engineered code: branch **aomdc** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/aomdc
|
||||||
|
|
||||||
## Azure Sheep
|
## Azure Sheep
|
||||||
malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-asheep
|
malortie's recreation - https://github.com/malortie/hl-asheep
|
||||||
|
|
||||||
Reverse-engineered code: branch **asheep** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/asheep
|
Reverse-engineered code: branch **asheep** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/asheep
|
||||||
|
|
||||||
## Big Lolly
|
## Big Lolly
|
||||||
malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-biglolly
|
malortie's recreation - https://github.com/malortie/hl-biglolly
|
||||||
|
|
||||||
Branch **biglolly** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/biglolly
|
Branch **biglolly** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/biglolly
|
||||||
|
|
||||||
## Black Ops
|
## Black Ops
|
||||||
malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-blackops
|
malortie's recreation - https://github.com/malortie/hl-blackops
|
||||||
|
|
||||||
Branch **blackops** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/blackops
|
Branch **blackops** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/blackops
|
||||||
|
|
||||||
@@ -277,16 +246,13 @@ Reverse-engineered code of server part by s1lentq - https://github.com/s1lentq/R
|
|||||||
## Counter Strike Online
|
## Counter Strike Online
|
||||||
CSO-like Xash3D-based mod, CSMoE - https://github.com/MoeMod/CSMoE
|
CSO-like Xash3D-based mod, CSMoE - https://github.com/MoeMod/CSMoE
|
||||||
|
|
||||||
## Crack-Life
|
|
||||||
Reverse-engineered code: branch **cracklife** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/cracklife
|
|
||||||
|
|
||||||
## Crack-Life: Campaign Mode
|
## Crack-Life: Campaign Mode
|
||||||
Recreation by lostgamer aka nillerusr - https://github.com/LostGamerHL/crack_life
|
Recreation by lostgamer aka nillerusr - https://github.com/LostGamerHL/crack_life
|
||||||
|
|
||||||
Reverse-engineered code: branch **clcampaign** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/clcampaign
|
Reverse-engineered code: branch **clcampaign** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/clcampaign
|
||||||
|
|
||||||
## Escape from the Darkness
|
## Escape from the Darkness
|
||||||
malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-eftd
|
malortie's recreation - https://github.com/malortie/hl-eftd
|
||||||
|
|
||||||
Reverse-engineered code: branch **eftd** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/eftd
|
Reverse-engineered code: branch **eftd** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/eftd
|
||||||
|
|
||||||
@@ -317,7 +283,7 @@ Reverse-engineered code: branch **rebellion** in hlsdk-portable - https://github
|
|||||||
Branch **hl_urbicide** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/hl_urbicide
|
Branch **hl_urbicide** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/hl_urbicide
|
||||||
|
|
||||||
## Half-Life: Visitors
|
## Half-Life: Visitors
|
||||||
malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-visitors
|
malortie's recreation - https://github.com/malortie/hl-visitors
|
||||||
|
|
||||||
Reverse-engineered code: branch **visitors** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/visitors
|
Reverse-engineered code: branch **visitors** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/visitors
|
||||||
|
|
||||||
@@ -325,17 +291,17 @@ Reverse-engineered code: branch **visitors** in hlsdk-portable - https://github.
|
|||||||
Branch **half-secret** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/half-secret
|
Branch **half-secret** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/half-secret
|
||||||
|
|
||||||
## Night at the Office
|
## Night at the Office
|
||||||
malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-nato
|
malortie's recreation - https://github.com/malortie/hl-nato
|
||||||
|
|
||||||
Branch **noffice** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/noffice
|
Branch **noffice** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/noffice
|
||||||
|
|
||||||
## Poke 646
|
## Poke 646
|
||||||
malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-poke646
|
malortie's recreation - https://github.com/malortie/hl-poke646
|
||||||
|
|
||||||
Reverse-engineered code: branch **poke646** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/poke646
|
Reverse-engineered code: branch **poke646** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/poke646
|
||||||
|
|
||||||
## Poke 646: Vendetta
|
## Poke 646: Vendetta
|
||||||
malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-poke646-vendetta
|
malortie's recreation - https://github.com/malortie/hl-poke646-vendetta
|
||||||
|
|
||||||
Reverse-engineered code: branch **poke646_vendetta** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/poke646_vendetta
|
Reverse-engineered code: branch **poke646_vendetta** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/poke646_vendetta
|
||||||
|
|
||||||
@@ -352,19 +318,19 @@ Branch **sewer_beta** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable
|
|||||||
Reverse-engineered code by Velaron - https://github.com/Velaron/tf15-client
|
Reverse-engineered code by Velaron - https://github.com/Velaron/tf15-client
|
||||||
|
|
||||||
## The Gate
|
## The Gate
|
||||||
malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-thegate
|
malortie's recreation - https://github.com/malortie/hl-thegate
|
||||||
|
|
||||||
Reverse-engineered code: branch **thegate** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/thegate
|
Reverse-engineered code: branch **thegate** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/thegate
|
||||||
|
|
||||||
## They Hunger
|
## They Hunger
|
||||||
malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-theyhunger
|
malortie's recreation - https://github.com/malortie/hl-theyhunger
|
||||||
|
|
||||||
Reverse-engineered code: branch **theyhunger** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/theyhunger
|
Reverse-engineered code: branch **theyhunger** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/theyhunger
|
||||||
|
|
||||||
They Hunger: Tactical - https://www.moddb.com/mods/they-hunger-tactical/downloads/tht-source-code-documentation
|
They Hunger: Tactical - https://www.moddb.com/mods/they-hunger-tactical/downloads/tht-source-code-documentation
|
||||||
|
|
||||||
## Times of Troubles
|
## Times of Troubles
|
||||||
malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-tot
|
malortie's recreation - https://github.com/malortie/hl-tot
|
||||||
|
|
||||||
Branch **tot** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/tot
|
Branch **tot** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/tot
|
||||||
|
|
||||||
@@ -382,14 +348,11 @@ Deathmatch Quaked - https://www.moddb.com/games/deathmatch-classic/downloads/dmq
|
|||||||
|
|
||||||
Branch **dmc** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/dmc
|
Branch **dmc** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/dmc
|
||||||
|
|
||||||
## Delta Particles
|
|
||||||
Branch **delta_particles** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/delta_particles
|
|
||||||
|
|
||||||
## Half-Life: Echoes
|
## Half-Life: Echoes
|
||||||
Branch **echoes** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/echoes
|
Branch **echoes** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/echoes
|
||||||
|
|
||||||
## Half-Life: Invasion
|
## Half-Life: Invasion
|
||||||
Port to HLSDK 2.4 by malortie - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-invasion
|
Port to HLSDK 2.4 by malortie - https://github.com/malortie/hl-invasion
|
||||||
|
|
||||||
Port to Linux by fmoraw - https://github.com/fmoraw/hlinvasion
|
Port to Linux by fmoraw - https://github.com/fmoraw/hlinvasion
|
||||||
|
|
||||||
@@ -408,7 +371,7 @@ Port to Linux - https://github.com/fmoraw/NS
|
|||||||
Version 1.2: branch **sohl1.2** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/sohl1.2
|
Version 1.2: branch **sohl1.2** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/sohl1.2
|
||||||
|
|
||||||
## Swiss Cheese Halloween 2002
|
## Swiss Cheese Halloween 2002
|
||||||
Just more playable version by malortie - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-shall
|
Just more playable version by malortie - https://github.com/malortie/hl-shall
|
||||||
|
|
||||||
Branch **halloween** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/halloween
|
Branch **halloween** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/halloween
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ Table is sorted by status and platform.
|
|||||||
| PSVita | Supported | @fgsfdsfgs |
|
| PSVita | Supported | @fgsfdsfgs |
|
||||||
| Switch | Supported | @fgsfdsfgs |
|
| Switch | Supported | @fgsfdsfgs |
|
||||||
| Windows | Supported | @a1batross, @SNMetamorph |
|
| Windows | Supported | @a1batross, @SNMetamorph |
|
||||||
| iOS | Supported | @ksagameng2 |
|
|
||||||
| DOS4GW | Orphaned | N/A | Haven't been confirmed to work for a very long time
|
| 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
|
| Haiku | Orphaned | N/A | Was added by #478 and #483
|
||||||
| IRIX | Orphaned | N/A | Undone, compiles but requires big endian port
|
| IRIX | Orphaned | N/A | Undone, compiles but requires big endian port
|
||||||
@@ -36,3 +35,4 @@ Table is sorted by status and platform.
|
|||||||
| Emscripten | Old Engine | N/A |
|
| Emscripten | Old Engine | N/A |
|
||||||
| 3DS | Old Engine fork | N/A | [GitHub Repository](https://github.com/masterfeizz/Xash3DS)
|
| 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)
|
| Oculus Quest | Old Engine fork | N/A | [GitHub Repository](https://github.com/DrBeef/Lambda1VR)
|
||||||
|
| iOS | Deprecated | N/A | See GitHub issue #61
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
# Xash3D FWGS protocol support
|
|
||||||
|
|
||||||
Xash3D FWGS currently supports four network protocols:
|
|
||||||
* Version 49, introduced in Xash3D in around 2018.
|
|
||||||
* Version 48, used in Xash3D FWGS 0.19 and earlier. Deprecated and to be removed in the future.
|
|
||||||
* GoldSrc version 48, used in current GoldSource version.
|
|
||||||
* Quake version 15, only used for demo playback for Quake Wrapper mod.
|
|
||||||
|
|
||||||
On the server side, we only support Xash3D 49 protocol, but bugcomp `gsmrf` mode can convert GoldSrc 48 messages into Xash3D 49 on the fly, for some mods that directly write engine internal messages.
|
|
||||||
|
|
||||||
In the following documents we will only cover version 49, as everything else is either not Xash3D specific or deprecated.
|
|
||||||
@@ -1,305 +0,0 @@
|
|||||||
# Xash3D 49 connectionless protocol
|
|
||||||
|
|
||||||
Connectionless protocol defines four common message sources and destinations, they can be:
|
|
||||||
* `S` for Server
|
|
||||||
* `C` for Client
|
|
||||||
* `M` for Master
|
|
||||||
* `A` for Anything else
|
|
||||||
|
|
||||||
All connectionless packets have `\xff\xff\xff\xff` (32-bit integer set to all ones) in it's header. Let's go through each of possible direction in this document.
|
|
||||||
|
|
||||||
<!--TOC-->
|
|
||||||
|
|
||||||
- [Any](#any)
|
|
||||||
- [Any to Any](#any-to-any)
|
|
||||||
- [`A2A_NETINFO`](#a2a_netinfo)
|
|
||||||
- [`A2A_INFO`](#a2a_info)
|
|
||||||
- [`A2A_PING` and `A2A_GOLDSRC_PING`](#a2a_ping-and-a2a_goldsrc_ping)
|
|
||||||
- [`A2A_ACK` and `A2A_GOLDSRC_ACK`](#a2a_ack-and-a2a_goldsrc_ack)
|
|
||||||
- [Any to Client](#any-to-client)
|
|
||||||
- [`A2C_PRINT` and `A2C_GOLDSRC_PRINT`](#a2c_print-and-a2c_goldsrc_print)
|
|
||||||
- [Any to Server and Server to Any](#any-to-server-and-server-to-any)
|
|
||||||
- [`A2S_GOLDSRC_INFO`, `A2S_GOLDSRC_RULES` and `A2S_GOLDSRC_PLAYERS` and their responses](#a2s_goldsrc_info-a2s_goldsrc_rules-and-a2s_goldsrc_players-and-their-responses)
|
|
||||||
- [Any to Master](#any-to-master)
|
|
||||||
- [`S2M_SCAN_REQUEST`](#s2m_scan_request)
|
|
||||||
- [Master to Any](#master-to-any)
|
|
||||||
- [`M2A_SERVERSLIST`](#m2a_serverslist)
|
|
||||||
- [Master and game server](#master-and-game-server)
|
|
||||||
- [Server to Master](#server-to-master)
|
|
||||||
- [`S2M_HEARTBEAT`](#s2m_heartbeat)
|
|
||||||
- [`S2M_SHUTDOWN`](#s2m_shutdown)
|
|
||||||
- [`S2M_INFO`](#s2m_info)
|
|
||||||
- [Master to Server](#master-to-server)
|
|
||||||
- [`M2S_CHALLENGE`](#m2s_challenge)
|
|
||||||
- [`M2S_NAT_CONNECT`](#m2s_nat_connect)
|
|
||||||
- [Client and game server](#client-and-game-server)
|
|
||||||
- [Client to Server](#client-to-server)
|
|
||||||
- [`C2S_BANDWIDTHTEST`](#c2s_bandwidthtest)
|
|
||||||
- [`C2S_GETCHALLENGE`](#c2s_getchallenge)
|
|
||||||
- [`C2S_CONNECT`](#c2s_connect)
|
|
||||||
- [Server to Client](#server-to-client)
|
|
||||||
- [`S2C_BANDWIDTHTEST`](#s2c_bandwidthtest)
|
|
||||||
- [`S2C_CHALLENGE`](#s2c_challenge)
|
|
||||||
- [`S2C_CONNECTION`](#s2c_connection)
|
|
||||||
- [`S2C_ERRORMSG`](#s2c_errormsg)
|
|
||||||
- [`S2C_REJECT`](#s2c_reject)
|
|
||||||
|
|
||||||
<!--TOC-->
|
|
||||||
|
|
||||||
## Any
|
|
||||||
|
|
||||||
### Any to Any
|
|
||||||
|
|
||||||
#### `A2A_NETINFO`
|
|
||||||
|
|
||||||
Used to implement Half-Life's NetAPI.
|
|
||||||
* Request message, in ASCII: `netinfo <version> <context> <request_id>`
|
|
||||||
- `version` must be 49, as text.
|
|
||||||
- `context` can be any 32-bit signed integer value, encoded as text.
|
|
||||||
- `request_id` see `common/net_api.h`
|
|
||||||
|
|
||||||
* Response message, in ASCII: `netinfo <context> <request_id> <response>`
|
|
||||||
- `context` same as in request.
|
|
||||||
- `request_id` same as in request.
|
|
||||||
- `response` is server's response, depending on request type. Always encoded as Quake info string.
|
|
||||||
|
|
||||||
Possible requests and response formats:
|
|
||||||
* Request ID `1` will make server respond with empty response string.
|
|
||||||
* Request ID `2` makes server respond with all server game rule cvars where the key is cvar name and the value is cvar value. One additional key `rules` with total amount of cvars is added.
|
|
||||||
* Request ID `3` makes server respond with players list, where:
|
|
||||||
- `p<id>name` is set to player's name
|
|
||||||
- `p<id>frags` is set to player's kill count
|
|
||||||
- `p<id>time` is set to player's total play time
|
|
||||||
- `players` is total player's list.
|
|
||||||
- Alternatively, it can respond with `neterror` set to `forbidden` if server does not wish to expose it's player list.
|
|
||||||
* Request ID `4` makes server to respond with common game details:
|
|
||||||
- `hostname` is server's name
|
|
||||||
- `gamedir` is game directory name
|
|
||||||
- `current` is total player count
|
|
||||||
- `max` is max players limit
|
|
||||||
- `map` is server's current level
|
|
||||||
* On any other server will respond with `neterror` set to `undefined`
|
|
||||||
* If protocol version doesn't match, server responds with `neterror` set to `protocol`
|
|
||||||
|
|
||||||
#### `A2A_INFO`
|
|
||||||
|
|
||||||
Used to request server's details.
|
|
||||||
* Request message, in ASCII: `info <version>`
|
|
||||||
- `version` must be 49, as text
|
|
||||||
* Response message, in ASCII: `info\n<response>`
|
|
||||||
- `response` is Quake info string, otherwise it's an error message.
|
|
||||||
|
|
||||||
* Response info string format:
|
|
||||||
- `p` is set to protocol version (FWGS extension)
|
|
||||||
- `map` is server's current level
|
|
||||||
- `dm` set to 1 if current game mode is deathmatch otherwise 0
|
|
||||||
- `team` set to 1 if current game mode includes teamplay otherwise 0
|
|
||||||
- `coop` set to 1 if current game mode is cooperative otherwise 0
|
|
||||||
- `numcl` is total player count
|
|
||||||
- `maxcl` is max players limit
|
|
||||||
- `gamedir` is game directory name
|
|
||||||
- `password` set to 1 if server is protected with password otherwise 0
|
|
||||||
- `host` is server's name
|
|
||||||
|
|
||||||
#### `A2A_PING` and `A2A_GOLDSRC_PING`
|
|
||||||
|
|
||||||
Simple ping message.
|
|
||||||
* Request, in ASCII: `ping` or `i` in GoldSrc format
|
|
||||||
* Response is always `A2A_ACK` or `A2A_GOLDSRC_ACK`
|
|
||||||
|
|
||||||
#### `A2A_ACK` and `A2A_GOLDSRC_ACK`
|
|
||||||
|
|
||||||
Simple ack message.
|
|
||||||
* Request, in ASCII: `ack` or `j` in GoldSrc format
|
|
||||||
* Response: none
|
|
||||||
|
|
||||||
### Any to Client
|
|
||||||
|
|
||||||
#### `A2C_PRINT` and `A2C_GOLDSRC_PRINT`
|
|
||||||
|
|
||||||
Simple print message.
|
|
||||||
* Header: `\xff\xff\xff\xff`
|
|
||||||
* Request, in ASCII: `print <message>` or `l<message>` in GoldSrc format.
|
|
||||||
* Response: none
|
|
||||||
|
|
||||||
### Any to Server and Server to Any
|
|
||||||
|
|
||||||
#### `A2S_GOLDSRC_INFO`, `A2S_GOLDSRC_RULES` and `A2S_GOLDSRC_PLAYERS` and their responses
|
|
||||||
|
|
||||||
These match Source Engine Query messages used in GoldSrc and Source engine and implemented for compatibility with existing tools. Read documentation for them on VDC: https://developer.valvesoftware.com/wiki/Server_queries
|
|
||||||
|
|
||||||
### Any to Master
|
|
||||||
|
|
||||||
#### `S2M_SCAN_REQUEST`
|
|
||||||
|
|
||||||
* Request: `1<region><IP:Port>\0<info>`
|
|
||||||
- Format of this message is loosely based on https://developer.valvesoftware.com/wiki/Master_Server_Query_Protocol
|
|
||||||
- `info` however adds few additional fields:
|
|
||||||
- `clver` set to engine's version
|
|
||||||
- `nat` set to 1 to filter only servers behind NAT and tell master to notify servers about scan
|
|
||||||
- `commit` set to engine's build commit hash
|
|
||||||
- `branch` set to engine's build branch
|
|
||||||
- `os` set to which operating system engine has been built
|
|
||||||
- `arch` set to which CPU architecture engine has been built
|
|
||||||
- `buildnum` set to engine's build number
|
|
||||||
- `key` set to random 32-bit value formatted as hex, used to validate master responses
|
|
||||||
* Response: `M2A_SERVERSLIST`
|
|
||||||
|
|
||||||
### Master to Any
|
|
||||||
|
|
||||||
#### `M2A_SERVERSLIST`
|
|
||||||
|
|
||||||
* Request: `f\xff<key><reserved><IP:Port>...`
|
|
||||||
- `key` is random 32-bit value set in `S2M_SCAN_REQUEST`
|
|
||||||
- `reserved` is single reserved 8-bit byte
|
|
||||||
- Following is list of IP addresses in binary form: 6-bytes for IPv4 address + port and 18-bytes for IPv6 address + port
|
|
||||||
- If port is 0, it means the end of list, otherwise it's the last IP used for pagination (not implemented in Xash3D)
|
|
||||||
* Response: client doesn't make response to master server but might request `A2S_INFO` from servers
|
|
||||||
|
|
||||||
## Master and game server
|
|
||||||
|
|
||||||
### Server to Master
|
|
||||||
|
|
||||||
#### `S2M_HEARTBEAT`
|
|
||||||
|
|
||||||
Used to update game server information on the master server.
|
|
||||||
|
|
||||||
* Request: `q\xff<heartbeat challenge>`
|
|
||||||
- `heartbeat challenge` is random 32-bit value, used to prevent faked/forged source IP addresses.
|
|
||||||
* Response: `M2S_CHALLENGE`
|
|
||||||
|
|
||||||
#### `S2M_SHUTDOWN`
|
|
||||||
|
|
||||||
Used to notify master server about server shutdown, but due to security reasons must be ignored by any master implementation.
|
|
||||||
|
|
||||||
* Request: `\x62\x0a`
|
|
||||||
* Response: none
|
|
||||||
|
|
||||||
#### `S2M_INFO`
|
|
||||||
|
|
||||||
Game server info response on `M2S_CHALLENGE`.
|
|
||||||
|
|
||||||
* Request: `0\n<info>`
|
|
||||||
- `info` is Quake info string containing server information passed to master server. It contains following fields:
|
|
||||||
- `protocol` is always 49
|
|
||||||
- `challenge` is master challenge
|
|
||||||
- `players` is total player count, without bots
|
|
||||||
- `max` is max players limits
|
|
||||||
- `bots` is total bot count
|
|
||||||
- `gamedir` is set to game directory
|
|
||||||
- `map` is server's current level
|
|
||||||
- `type` set to `d` for dedicated or `l` for listen server
|
|
||||||
- `password` set to `1` if server protected with password set otherwise `0`
|
|
||||||
- `os` always `w`
|
|
||||||
- `secure` always `0`
|
|
||||||
- `lan` always `0`
|
|
||||||
- `version` engine version
|
|
||||||
- `region` always `255`
|
|
||||||
- `product` same as `gamedir`
|
|
||||||
- `nat` set to `1` if server is behind NAT
|
|
||||||
* Response: none
|
|
||||||
|
|
||||||
### Master to Server
|
|
||||||
|
|
||||||
#### `M2S_CHALLENGE`
|
|
||||||
|
|
||||||
Master's respoonse on game server's heartbeat message.
|
|
||||||
|
|
||||||
* Request: `s<master challenge><heartbeat challenge>`
|
|
||||||
- `master challenge` contains 32-bit value, used in server response to prevent faked/forged source IP addresses.
|
|
||||||
- `heartbat challenge` cotnains 32-bit value, used in heartbeat request to prevent faked/forged source IP addresses.
|
|
||||||
* Response: `S2M_INFO`.
|
|
||||||
|
|
||||||
#### `M2S_NAT_CONNECT`
|
|
||||||
|
|
||||||
Master server's message with client IP address and port, used in NAT punching.
|
|
||||||
|
|
||||||
* Request: `c <IP:Port>`
|
|
||||||
* Response: `S2C_INFO` to a specified client address.
|
|
||||||
|
|
||||||
## Client and game server
|
|
||||||
|
|
||||||
### Client to Server
|
|
||||||
|
|
||||||
#### `C2S_BANDWIDTHTEST`
|
|
||||||
|
|
||||||
Used to figure out network MTU. The message is optional and server might choose to not implement it or respond with challenge.
|
|
||||||
|
|
||||||
* Request, in ASCII: `bandwidth <version> <max_size>`
|
|
||||||
- `version` must be 49, as text.
|
|
||||||
- `max_size` is requested maximum packet size.
|
|
||||||
|
|
||||||
* Possible responses:
|
|
||||||
- `S2C_BANDWIDTHTEST`
|
|
||||||
- `S2C_CHALLENGE`
|
|
||||||
- `A2A_PRINT` followed by `S2C_REJECT`
|
|
||||||
- `S2C_ERRORMSG` (as FWGS extension) followed by `A2A_PRINT` and `S2C_REJECT`.
|
|
||||||
|
|
||||||
#### `C2S_GETCHALLENGE`
|
|
||||||
|
|
||||||
Used to validate client's address to prevent faked/forged source IP addresses.
|
|
||||||
|
|
||||||
* Header: `\xff\xff\xff\xff`
|
|
||||||
* Request, in ASCII: `getchallenge steam`
|
|
||||||
- `steam` argument is optional and can be ignored by the server, as it's only kept for compatibility with similar GoldSrc 48 message.
|
|
||||||
|
|
||||||
* Possible responses:
|
|
||||||
- `S2C_CHALLENGE`
|
|
||||||
|
|
||||||
#### `C2S_CONNECT`
|
|
||||||
|
|
||||||
Used to build a connection with the server.
|
|
||||||
|
|
||||||
* Header: `\xff\xff\xff\xff`
|
|
||||||
* Request, in ASCII: `connect <version> <challenge> "<protinfo>" "<userinfo>"`
|
|
||||||
- `version` must be 49, as text.
|
|
||||||
- `challenge` must be challenge value, as text.
|
|
||||||
- `protinfo` is a Quake info string, containing protocol extensions and other connection information.
|
|
||||||
- `uuid` is this client's unique ID (FWGS extension) hasheed with MD5.
|
|
||||||
- `qport` is random integer value from 1 to 65535, unique for this engine run.
|
|
||||||
- `ext` is an integer value OR'ed with requested protocol extensions. Currently only extension is `NET_EXT_SPLITSIZE` with the value of `1`, which tells server to split messages based on `cl_dlmax` value from `userinfo`.
|
|
||||||
- `userinfo` contains initial user info, encoded as Quake info string.
|
|
||||||
* Possible responses:
|
|
||||||
- `A2A_PRINT` followed by `S2C_REJECT`
|
|
||||||
- `S2C_ERRORMSG` (as FWGS extension) followed by `A2A_PRINT` and `S2C_REJECT`.
|
|
||||||
- `S2C_CONNECTION`
|
|
||||||
|
|
||||||
### Server to Client
|
|
||||||
|
|
||||||
#### `S2C_BANDWIDTHTEST`
|
|
||||||
|
|
||||||
* Request: `testpacket<crc><blob>`
|
|
||||||
- `crc` is 32-bit CRC of `blob`
|
|
||||||
- `blob` random data
|
|
||||||
* There is no required response to this message
|
|
||||||
|
|
||||||
Total message size doesn't exceed request max size.
|
|
||||||
|
|
||||||
#### `S2C_CHALLENGE`
|
|
||||||
|
|
||||||
* Request, in ASCII: `challenge <value>`
|
|
||||||
- `value` is challenge value that client must include in it's response
|
|
||||||
* Response: `C2S_CONNECT`
|
|
||||||
|
|
||||||
#### `S2C_CONNECTION`
|
|
||||||
|
|
||||||
* Request, in ASCII: `client_connect <protinfo>`
|
|
||||||
- `protinfo` is an optional Quake info string, contains following optional fields:
|
|
||||||
- `ext` is an integer value OR'ed with allowed protocol extension. Bit fields match requested in `ext` field in `C2S_CONNECT`
|
|
||||||
- `cheats` set to 1 if server allows cheats otherwise 0
|
|
||||||
* Response: there is no out of band from the client but client will proceed to building netchan and switch client-server interaction to it.
|
|
||||||
|
|
||||||
#### `S2C_ERRORMSG`
|
|
||||||
|
|
||||||
Show client error message. Doesn't mean connection reject, only used for UI.
|
|
||||||
|
|
||||||
* Request, in ASCII: `errormsg <message>`
|
|
||||||
- `message` contains error message
|
|
||||||
* Response: none
|
|
||||||
|
|
||||||
#### `S2C_REJECT`
|
|
||||||
|
|
||||||
Client has been rejected in connection.
|
|
||||||
|
|
||||||
* Request, in ASCII: `disconnect`
|
|
||||||
* Response: client can cope, get depressed or mentally tell server to fuck off.
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# Xash3D 49 netchan protocol
|
|
||||||
|
|
||||||
TBD...
|
|
||||||
15
Documentation/public-servers-rules-and-recommendations.md
Normal file
15
Documentation/public-servers-rules-and-recommendations.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
As online community is growing, we don't want to keep bad or invalid servers on the Internet Games list. It's a common rules for all Xash3D server owners who are using default master server.
|
||||||
|
|
||||||
|
1. Breaking master server or clients is prohibited in any way. If you found a serious bug, contact us at contact 'at' fwgs.ru or post an issue at GitHub.
|
||||||
|
|
||||||
|
2. Changing settings, exploiting vulnerabilities of clients is prohibited in any way. If you found a serious bug, contact us at contact 'at' fwgs.ru or post an issue at GitHub.
|
||||||
|
|
||||||
|
2. Agressive advertisement and spam on other servers is prohibited in any way.
|
||||||
|
|
||||||
|
3. If your server is require downloading custom content, it should be available for everyone. Recommended way to deliver custom content is FastDL.
|
||||||
|
|
||||||
|
4. It's recommended to keep your server is up-to-date. You may use our public Debian/Ubuntu/Arch Linux [repository](https://github.com/FWGS/xash3d/wiki/How-to-set-up-a-Xash3D-Dedicated-Server-on-Debian-or-Ubuntu) to simplify updating process.
|
||||||
|
|
||||||
|
Just follow these rules to not get banned on our master server. Good luck, have fun!
|
||||||
|
|
||||||
|
|
||||||
@@ -962,57 +962,53 @@ Client: https://github.com/Velaron/cs16-client/
|
|||||||
|
|
||||||
Server: https://github.com/rehlds/ReGameDLL_CS
|
Server: https://github.com/rehlds/ReGameDLL_CS
|
||||||
|
|
||||||
15. **[Crack-Life](https://www.moddb.com/mods/crack-life/downloads/crack-life)**
|
15. **[Crack-Life: Campaign Mode](https://www.moddb.com/mods/crack-life)**
|
||||||
|
|
||||||
To run this mod on specific platforms you may be need to compile libraries from **cracklife** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
|
||||||
|
|
||||||
16. **[Crack-Life: Campaign Mode](https://www.moddb.com/mods/crack-life/downloads/crack-life-campaign-mode)**
|
|
||||||
|
|
||||||
To run this mod on specific platforms you may be need to compile libraries from **clcampaign** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
To run this mod on specific platforms you may be need to compile libraries from **clcampaign** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||||
|
|
||||||
17. **[Deathmatch Classic](https://store.steampowered.com/app/40/Deathmatch_Classic/)**
|
16. **[Deathmatch Classic](https://store.steampowered.com/app/40/Deathmatch_Classic/)**
|
||||||
|
|
||||||
Steam version already supports x86 Linux.
|
Steam version already supports x86 Linux.
|
||||||
|
|
||||||
To run this mod on specific platforms you may be need to compile libraries from **dmc** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
To run this mod on specific platforms you may be need to compile libraries from **dmc** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||||
|
|
||||||
18. **[Delta Particles](https://www.moddb.com/mods/half-life-delta)**
|
17. **[Delta Particles](https://www.moddb.com/mods/half-life-delta)**
|
||||||
|
|
||||||
This mod already supports x86 Linux.
|
This mod already supports x86 Linux.
|
||||||
|
|
||||||
Support for other platforms may be available later.
|
Support for other platforms may be available later.
|
||||||
|
|
||||||
19. **[Escape from the Darkness](https://www.moddb.com/mods/escape-from-the-darkness)**
|
18. **[Escape from the Darkness](https://www.moddb.com/mods/escape-from-the-darkness)**
|
||||||
|
|
||||||
To run this mod on specific platforms you may be need to compile libraries from **eftd** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
To run this mod on specific platforms you may be need to compile libraries from **eftd** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||||
|
|
||||||
20. **[Gang Wars](https://www.moddb.com/mods/gangwars)**
|
19. **[Gang Wars](https://www.moddb.com/mods/gangwars)**
|
||||||
|
|
||||||
This mod already supports x86 Linux.
|
This mod already supports x86 Linux.
|
||||||
|
|
||||||
Support for other platforms may be available later.
|
Support for other platforms may be available later.
|
||||||
|
|
||||||
21. **[Half-Life: Blue Shift](https://store.steampowered.com/app/130/HalfLife_Blue_Shift/)**
|
20. **[Half-Life: Blue Shift](https://store.steampowered.com/app/130/HalfLife_Blue_Shift/)**
|
||||||
|
|
||||||
To run this mod on specific platforms you may be need to compile libraries from **bshift** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
To run this mod on specific platforms you may be need to compile libraries from **bshift** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||||
|
|
||||||
22. **[Half-Life: Decay](https://www.moddb.com/mods/half-life-decay)**
|
21. **[Half-Life: Decay](https://www.moddb.com/mods/half-life-decay)**
|
||||||
|
|
||||||
Currently, you can compile mod libraries for x86 Linux from **decay-pc** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
Currently, you can compile mod libraries for x86 Linux from **decay-pc** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||||
|
|
||||||
Support for other platforms may be available later.
|
Support for other platforms may be available later.
|
||||||
|
|
||||||
23. **[Half-Life: Echoes](https://www.moddb.com/mods/half-life-echoes)**
|
22. **[Half-Life: Echoes](https://www.moddb.com/mods/half-life-echoes)**
|
||||||
|
|
||||||
To run this mod on specific platforms you may be need to compile libraries from **echoes** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
To run this mod on specific platforms you may be need to compile libraries from **echoes** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||||
|
|
||||||
24. **[Half-Life: Field Intensity](https://www.moddb.com/mods/field-intensity)**
|
23. **[Half-Life: Field Intensity](https://www.moddb.com/mods/field-intensity)**
|
||||||
|
|
||||||
This mod already supports x86 Linux.
|
This mod already supports x86 Linux.
|
||||||
|
|
||||||
Support for other platforms may be available later.
|
Support for other platforms may be available later.
|
||||||
|
|
||||||
25. **Half-Life: Gravgun** (unfinished) by *mittorn*, *a1batross* and *Solexid*
|
24. **Half-Life: Gravgun** (unfinished) by *mittorn*, *a1batross* and *Solexid*
|
||||||
|
|
||||||
*Support temporary abandoned*
|
*Support temporary abandoned*
|
||||||
|
|
||||||
@@ -1022,163 +1018,163 @@ Currently, MSVC is not supported, use MinGW if you need build for Windows.
|
|||||||
|
|
||||||
Mod files: [1](http://mittorn.fwgs.ru/coop-entpatches/), [2](http://mittorn.fwgs.ru/ggm/), [3](https://github.com/nillerusr/gravgun-extras).
|
Mod files: [1](http://mittorn.fwgs.ru/coop-entpatches/), [2](http://mittorn.fwgs.ru/ggm/), [3](https://github.com/nillerusr/gravgun-extras).
|
||||||
|
|
||||||
26. **[Half-Life: Induction 1.2](https://www.moddb.com/mods/half-life-induction)**
|
25. **[Half-Life: Induction 1.2](https://www.moddb.com/mods/half-life-induction)**
|
||||||
|
|
||||||
To run this mod on specific platforms you may be need to compile libraries from **induction_1.2** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
To run this mod on specific platforms you may be need to compile libraries from **induction_1.2** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||||
|
|
||||||
27. **[Half-Life: Invasion](https://www.moddb.com/mods/half-life-invasion)**
|
26. **[Half-Life: Invasion](https://www.moddb.com/mods/half-life-invasion)**
|
||||||
|
|
||||||
This mod supports x86 Linux.
|
This mod supports x86 Linux.
|
||||||
|
|
||||||
Support for other platforms may be available later.
|
Support for other platforms may be available later.
|
||||||
|
|
||||||
28. **[Half-Life: Intense Force](https://www.moddb.com/downloads/intense-force)**
|
27. **[Half-Life: Intense Force](https://www.moddb.com/downloads/intense-force)**
|
||||||
|
|
||||||
This mod already supports x86 Linux.
|
This mod already supports x86 Linux.
|
||||||
|
|
||||||
To run this mod on specific platforms you may be need to compile libraries from **intense_force** branch of [halflife-featureful](https://github.com/FreeSlave/halflife-featureful).
|
To run this mod on specific platforms you may be need to compile libraries from **intense_force** branch of [halflife-featureful](https://github.com/FreeSlave/halflife-featureful).
|
||||||
|
|
||||||
29. **[Half-Life: Opposing Force](https://store.steampowered.com/app/50/HalfLife_Opposing_Force/)**
|
28. **[Half-Life: Opposing Force](https://store.steampowered.com/app/50/HalfLife_Opposing_Force/)**
|
||||||
|
|
||||||
Steam version already supports x86 Linux.
|
Steam version already supports x86 Linux.
|
||||||
|
|
||||||
To run this mod on specific platforms you may be need to compile libraries from **opfor** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
To run this mod on specific platforms you may be need to compile libraries from **opfor** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||||
|
|
||||||
30. **[Half-Life: Quest mod](https://csm.dev/threads/half-life-the-quest-mod-isxodniki.38030/)**
|
29. **[Half-Life: Quest mod](https://csm.dev/threads/half-life-the-quest-mod-isxodniki.38030/)**
|
||||||
|
|
||||||
To run this mod on specific platforms you may be need to compile libraries from **sci** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
To run this mod on specific platforms you may be need to compile libraries from **sci** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||||
|
|
||||||
31. **[Half-Life: TopDown](https://www.moddb.com/mods/half-life-top-downs)**
|
30. **[Half-Life: TopDown](https://www.moddb.com/mods/half-life-top-downs)**
|
||||||
|
|
||||||
To run this mod on specific platforms you may be need to compile libraries from **topdown** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
To run this mod on specific platforms you may be need to compile libraries from **topdown** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||||
|
|
||||||
32. **[Half-Life: Urbicide](https://www.moddb.com/mods/half-life-urbicide)**
|
31. **[Half-Life: Urbicide](https://www.moddb.com/mods/half-life-urbicide)**
|
||||||
|
|
||||||
To run this mod on specific platforms you may be need to compile libraries from **hl_urbicide** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
To run this mod on specific platforms you may be need to compile libraries from **hl_urbicide** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||||
|
|
||||||
33. **[Half-Life: Visitors](https://www.moddb.com/mods/half-life-visitors)**
|
32. **[Half-Life: Visitors](https://www.moddb.com/mods/half-life-visitors)**
|
||||||
|
|
||||||
To run this mod on specific platforms you may be need to compile libraries from **visitors** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
To run this mod on specific platforms you may be need to compile libraries from **visitors** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||||
|
|
||||||
34. **[Half-Rats: Parasomnia](https://www.moddb.com/mods/half-rats-parasomnia/downloads/half-rats-parasomnia-v10b-steamlinux)**
|
33. **[Half-Rats: Parasomnia](https://www.moddb.com/mods/half-rats-parasomnia/downloads/half-rats-parasomnia-v10b-steamlinux)**
|
||||||
|
|
||||||
This mod already supports x86 Linux.
|
This mod already supports x86 Linux.
|
||||||
|
|
||||||
Support for other platforms may be available later.
|
Support for other platforms may be available later.
|
||||||
|
|
||||||
35. **[Half-Screwed: Death and Rebirth](https://www.moddb.com/mods/half-screwed)**
|
34. **[Half-Screwed: Death and Rebirth](https://www.moddb.com/mods/half-screwed)**
|
||||||
|
|
||||||
To run this mod on specific platforms you may be need to compile libraries from **half-screwed** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
To run this mod on specific platforms you may be need to compile libraries from **half-screwed** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||||
|
|
||||||
36. **[Half-Secret](https://www.moddb.com/mods/half-secret)**
|
35. **[Half-Secret](https://www.moddb.com/mods/half-secret)**
|
||||||
|
|
||||||
To run this mod on specific platforms you may be need to compile libraries from **half-secret** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
To run this mod on specific platforms you may be need to compile libraries from **half-secret** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||||
|
|
||||||
37. **[Natural Selection](https://www.moddb.com/mods/natural-selection)**
|
36. **[Natural Selection](https://www.moddb.com/mods/natural-selection)**
|
||||||
|
|
||||||
This mod already supports x86 Linux.
|
This mod already supports x86 Linux.
|
||||||
|
|
||||||
Support for other platforms may be available later.
|
Support for other platforms may be available later.
|
||||||
|
|
||||||
38. **[Night at the Office](https://www.moddb.com/mods/night-at-the-office)**
|
37. **[Night at the Office](https://www.moddb.com/mods/night-at-the-office)**
|
||||||
|
|
||||||
To run this mod on specific platforms you may be need to compile libraries from **noffice** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
To run this mod on specific platforms you may be need to compile libraries from **noffice** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||||
|
|
||||||
39. **[Paranoia](https://www.moddb.com/mods/paranoia)**
|
38. **[Paranoia](https://www.moddb.com/mods/paranoia)**
|
||||||
|
|
||||||
*Support temporary abandoned*
|
*Support temporary abandoned*
|
||||||
|
|
||||||
[Source code](https://github.com/FWGS/paranoia_toolkit).
|
[Source code](https://github.com/FWGS/paranoia_toolkit).
|
||||||
|
|
||||||
40. **[Poke646](https://poke646.com/)**
|
39. **[Poke646](https://poke646.com/)**
|
||||||
|
|
||||||
To run this mod on specific platforms you may be need to compile libraries from **poke646** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
To run this mod on specific platforms you may be need to compile libraries from **poke646** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||||
|
|
||||||
41. **[Poke646: Vendetta](https://poke646.com/)**
|
40. **[Poke646: Vendetta](https://poke646.com/)**
|
||||||
|
|
||||||
To run this mod on specific platforms you may be need to compile libraries from **poke646_vendetta** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
To run this mod on specific platforms you may be need to compile libraries from **poke646_vendetta** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||||
|
|
||||||
42. [PrimeXT](https://snmetamorph.github.io/PrimeXT/)
|
41. [PrimeXT](https://snmetamorph.github.io/PrimeXT/)
|
||||||
|
|
||||||
[Source code](https://github.com/SNMetamorph/PrimeXT)
|
[Source code](https://github.com/SNMetamorph/PrimeXT)
|
||||||
|
|
||||||
43. **[Quake Remake](https://www.moddb.com/games/quake-remake)**
|
42. **[Quake Remake](https://www.moddb.com/games/quake-remake)**
|
||||||
|
|
||||||
*Support was fully abandoned due Quake Wrapper release.*
|
*Support was fully abandoned due Quake Wrapper release.*
|
||||||
|
|
||||||
[Actual source code](https://github.com/FWGS/quakeremake)
|
[Actual source code](https://github.com/FWGS/quakeremake)
|
||||||
|
|
||||||
44. **[Rebellion](https://www.moddb.com/mods/rebellion1)**
|
43. **[Rebellion](https://www.moddb.com/mods/rebellion1)**
|
||||||
|
|
||||||
To run this mod on specific platforms you may be need to compile libraries from **rebellion** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
To run this mod on specific platforms you may be need to compile libraries from **rebellion** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||||
|
|
||||||
45. **[Residual Life](https://www.moddb.com/mods/hl-residual-life)**
|
44. **[Residual Life](https://www.moddb.com/mods/hl-residual-life)**
|
||||||
|
|
||||||
To run this mod on specific platforms you may be need to compile libraries from **residual_point** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
To run this mod on specific platforms you may be need to compile libraries from **residual_point** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||||
|
|
||||||
46. **[Residual Point](https://www.moddb.com/mods/hl-residual-point)**
|
45. **[Residual Point](https://www.moddb.com/mods/hl-residual-point)**
|
||||||
|
|
||||||
To run this mod on specific platforms you may be need to compile libraries from **residual_point** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
To run this mod on specific platforms you may be need to compile libraries from **residual_point** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||||
|
|
||||||
47. **[Ricochet](https://store.steampowered.com/app/60/Ricochet/)**
|
46. **[Ricochet](https://store.steampowered.com/app/60/Ricochet/)**
|
||||||
|
|
||||||
This mod already supports x86 Linux.
|
This mod already supports x86 Linux.
|
||||||
|
|
||||||
Support for other platforms may be available later.
|
Support for other platforms may be available later.
|
||||||
|
|
||||||
48. [Sewer](https://www.moddb.com/games/half-life/addons/sewer-beta)
|
47. [Sewer](https://www.moddb.com/games/half-life/addons/sewer-beta)
|
||||||
|
|
||||||
To run this mod on specific platforms you may be need to compile libraries from **sewer_beta** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
To run this mod on specific platforms you may be need to compile libraries from **sewer_beta** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||||
|
|
||||||
49. [Spirit of Half-Life 1.2](https://www.moddb.com/mods/spirit-of-half-life)
|
48. [Spirit of Half-Life 1.2](https://www.moddb.com/mods/spirit-of-half-life)
|
||||||
|
|
||||||
To run this mod on specific platforms you may be need to compile libraries from **sohl1.2** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
To run this mod on specific platforms you may be need to compile libraries from **sohl1.2** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||||
|
|
||||||
50. **[Swiss Cheese Halloween](https://www.moddb.com/mods/half-life-halloween-mod)**
|
49. **[Swiss Cheese Halloween](https://www.moddb.com/mods/half-life-halloween-mod)**
|
||||||
|
|
||||||
To run this mod on specific platforms you may be need to compile libraries from **halloween** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
To run this mod on specific platforms you may be need to compile libraries from **halloween** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||||
|
|
||||||
51. **[Team Fortress Classic](https://store.steampowered.com/app/20/Team_Fortress_Classic/)**
|
50. **[Team Fortress Classic](https://store.steampowered.com/app/20/Team_Fortress_Classic/)**
|
||||||
|
|
||||||
This mod already supports x86 Linux.
|
This mod already supports x86 Linux.
|
||||||
|
|
||||||
Currently, only source code of [client part](https://github.com/Velaron/tf15-client) is available.
|
Currently, only source code of [client part](https://github.com/Velaron/tf15-client) is available.
|
||||||
|
|
||||||
52. **[The Gate](https://www.moddb.com/mods/the-gate)**
|
51. **[The Gate](https://www.moddb.com/mods/the-gate)**
|
||||||
|
|
||||||
To run this mod on specific platforms you may be need to compile libraries from **thegate** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
To run this mod on specific platforms you may be need to compile libraries from **thegate** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||||
|
|
||||||
53. **[They Hunger: Trilogy](https://www.moddb.com/mods/they-hunger)**
|
52. **[They Hunger: Trilogy](https://www.moddb.com/mods/they-hunger)**
|
||||||
|
|
||||||
To run this mod on specific platforms you may be need to compile libraries from **theyhunger** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
To run this mod on specific platforms you may be need to compile libraries from **theyhunger** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||||
|
|
||||||
54. **[ThreeWave CTF](https://www.moddb.com/games/deathmatch-classic/downloads/threewave-ctf)**
|
53. **[ThreeWave CTF](https://www.moddb.com/games/deathmatch-classic/downloads/threewave-ctf)**
|
||||||
|
|
||||||
To run this mod on specific platforms you may be need to compile libraries from **dmc** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
To run this mod on specific platforms you may be need to compile libraries from **dmc** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||||
|
|
||||||
55. **[Times of Troubles](https://www.moddb.com/mods/times-of-troubles)**
|
54. **[Times of Troubles](https://www.moddb.com/mods/times-of-troubles)**
|
||||||
|
|
||||||
To run this mod on specific platforms you may be need to compile libraries from **tot** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
To run this mod on specific platforms you may be need to compile libraries from **tot** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||||
|
|
||||||
56. [XashXT](https://csm.dev/threads/xashxt-0-65-rev-4-release-stable.38319/)
|
55. [XashXT](https://csm.dev/threads/xashxt-0-65-rev-4-release-stable.38319/)
|
||||||
|
|
||||||
*Support was fully abandoned due PrimeXT development*
|
*Support was fully abandoned due PrimeXT development*
|
||||||
|
|
||||||
[Source code](https://github.com/FWGS/XashXT-FWGS/)
|
[Source code](https://github.com/FWGS/XashXT-FWGS/)
|
||||||
|
|
||||||
57. **[X-Half-Life DeathMatch](https://www.moddb.com/mods/xdm)**
|
56. **[X-Half-Life DeathMatch](https://www.moddb.com/mods/xdm)**
|
||||||
|
|
||||||
This mod already supports x86 Linux.
|
This mod already supports x86 Linux.
|
||||||
|
|
||||||
Support for other platforms may be available later.
|
Support for other platforms may be available later.
|
||||||
|
|
||||||
58. **[Xen-Warrior](https://www.moddb.com/mods/xen-warrior)**
|
57. **[Xen-Warrior](https://www.moddb.com/mods/xen-warrior)**
|
||||||
|
|
||||||
To run this mod on specific platforms you may be need to compile libraries from **sohl1.2** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
To run this mod on specific platforms you may be need to compile libraries from **sohl1.2** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||||
|
|
||||||
Don't forget to enable XENWARRIOR build option!!!
|
Don't forget to enable XENWARRIOR build option!!!
|
||||||
|
|
||||||
59. [Zombie-X](https://www.moddb.com/mods/zombie-x-10-final)
|
58. [Zombie-X](https://www.moddb.com/mods/zombie-x-10-final)
|
||||||
|
|
||||||
To run this mod on specific platforms you may be need to compile libraries from **zombie-x** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
To run this mod on specific platforms you may be need to compile libraries from **zombie-x** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||||
|
|
||||||
|
|||||||
54
README.md
54
README.md
@@ -1,6 +1,6 @@
|
|||||||
# Xash3D FWGS Engine <img align="right" width="128" height="128" src="https://github.com/FWGS/xash3d-fwgs/raw/master/game_launch/icon-xash-material.png" alt="Xash3D FWGS icon" />
|
# Xash3D FWGS Engine <img align="right" width="128" height="128" src="https://github.com/FWGS/xash3d-fwgs/raw/master/game_launch/icon-xash-material.png" alt="Xash3D FWGS icon" />
|
||||||
[](https://github.com/FWGS/xash3d-fwgs/actions/workflows/c-cpp.yml) [](https://cirrus-ci.com/github/FWGS/xash3d-fwgs) \
|
[](https://github.com/FWGS/xash3d-fwgs/actions/workflows/c-cpp.yml) [](https://cirrus-ci.com/github/FWGS/xash3d-fwgs) \
|
||||||
[](http://xash.su/discord/) [](https://t.me/flyingwithgauss) \
|
[](http://fwgsdiscord.mentality.rip/) [](https://t.me/flyingwithgauss) \
|
||||||
[](https://github.com/FWGS/xash3d-fwgs/releases/tag/continuous)
|
[](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](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.
|
||||||
@@ -36,17 +36,10 @@ You still needed to copy `valve` directory as all game resources located there.
|
|||||||
|
|
||||||
For additional info, run Xash3D with `-help` command line key.
|
For additional info, run Xash3D with `-help` command line key.
|
||||||
|
|
||||||
### Android
|
## Contributing
|
||||||
0) Install the APK file.
|
* 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.
|
||||||
1) Copy `valve` directory to a folder named `xash` in the Internal storage.
|
|
||||||
2) Run games from within the app.
|
|
||||||
|
|
||||||
## Reporting issues
|
|
||||||
* Issues are accepted in both English and Russian.
|
* Issues are accepted in both English and Russian.
|
||||||
* They are only accepted if you run legally acquired product (e.g. Half-Life in Steam).
|
* Before sending a PR, check if you followed our contribution guide in CONTRIBUTING.md file.
|
||||||
|
|
||||||
## Contributing code
|
|
||||||
* Check the CONTRIBUTING.md file.
|
|
||||||
|
|
||||||
## Build instructions
|
## 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 [Waf Book](https://waf.io/book/).
|
||||||
@@ -83,33 +76,14 @@ This repository contains our fork of HLSDK and restored source code for Half-Lif
|
|||||||
|
|
||||||
##### RedHat/Fedora
|
##### RedHat/Fedora
|
||||||
* Only for 32-bit engine on 64-bit x86 operating system:
|
* Only for 32-bit engine on 64-bit x86 operating system:
|
||||||
* Install development tools: `$ sudo dnf install git gcc gcc-c++ glibc-devel.i686 SDL3-devel.i686 sdl2-compat-devel.i686 opus-devel.i686 freetype-devel.i686 bzip2-devel.i686 libvorbis-devel.i686 opusfile-devel.i686 libogg-devel.i686`.
|
* Install development tools: `$ sudo dnf install git gcc gcc-c++ glibc-devel.i686 SDL2-devel.i686 opus-devel.i686 freetype-devel.i686 bzip2-devel.i686 libvorbis-devel.i686 opusfile-devel.i686 libogg-devel.i686`.
|
||||||
* Set PKG_CONFIG_PATH environment variable to point at 32-bit libraries: `$ export PKG_CONFIG_PATH=/usr/lib/pkgconfig`.
|
* 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:
|
* For 64-bit engine on 64-bit x86 and other non-x86 systems:
|
||||||
* Install development tools: `$ sudo dnf install git gcc gcc-c++ SDL3-devel sdl2-compat-devel opus-devel freetype-devel bzip2-devel libvorbis-devel opusfile-devel libogg-devel`.
|
* Install development tools: `$ sudo dnf install git gcc gcc-c++ SDL2-devel opus-devel freetype-devel bzip2-devel libvorbis-devel opusfile-devel libogg-devel`.
|
||||||
|
|
||||||
* Clone this repostory: `$ git clone --recursive https://github.com/FWGS/xash3d-fwgs`.
|
* Clone this repostory: `$ git clone --recursive https://github.com/FWGS/xash3d-fwgs`.
|
||||||
|
|
||||||
#### Android (Windows/Linux/macOS)
|
|
||||||
* Install [Android Studio](https://developer.android.com/studio) (or the command line tools).
|
|
||||||
* Install [Python](https://python.org) (at least 2.7, latest is better).
|
|
||||||
* Install [Git](https://git-scm.com/download/win).
|
|
||||||
* Install [Ninja](https://ninja-build.org/).
|
|
||||||
* Install [CMake](https://cmake.org/) (for some dependencies).
|
|
||||||
|
|
||||||
* Clone this repostory: `$ git clone --recursive https://github.com/FWGS/xash3d-fwgs`.
|
|
||||||
|
|
||||||
#### iOS/iPadOS
|
|
||||||
* Install Xcode from the appstore.
|
|
||||||
* Install [Homebrew package manager](https://brew.sh).
|
|
||||||
|
|
||||||
* Install build dependencies by running: `brew install python`.
|
|
||||||
|
|
||||||
* Clone the SDL2 repo `$ git clone --recursive https://github.com/libsdl-org/SDL.git -b SDL2` and compile the iOS framework by navigating to SDL/Xcode/SDL and opening the Xcode project.
|
|
||||||
|
|
||||||
* Clone this repository: `$ git clone --recursive https://github.com/FWGS/xash3d-fwgs`.
|
|
||||||
|
|
||||||
### Building
|
### Building
|
||||||
#### Windows (Visual Studio)
|
#### Windows (Visual Studio)
|
||||||
0) Open command line.
|
0) Open command line.
|
||||||
@@ -126,19 +100,3 @@ If compiling 32-bit on amd64, make sure `PKG_CONFIG_PATH` from the previous step
|
|||||||
1) Configure build: `./waf configure` (you need to pass `-8` to compile 64-bit engine on 64-bit x86 processor).
|
1) Configure build: `./waf configure` (you need to pass `-8` to compile 64-bit engine on 64-bit x86 processor).
|
||||||
2) Compile: `./waf build`.
|
2) Compile: `./waf build`.
|
||||||
3) Install: `./waf install --destdir=/path/to/any/output/directory`.
|
3) Install: `./waf install --destdir=/path/to/any/output/directory`.
|
||||||
|
|
||||||
#### Android (Windows/Linux/macOS)
|
|
||||||
To build you should clone [SDL](https://github.com/libsdl-org/SDL) from `SDL2` branch and [HLSDK-portable](https://github.com/FWGS/hlsdk-portable) `mobile-hacks` branch repositories to 3rdparty folder, after that you should be able to open the project in Android Studio from `android` directory or manually call Gradle to build the APK.
|
|
||||||
|
|
||||||
#### iOS/iPadOS (MacOS only)
|
|
||||||
0) (optional) Examine which build options are available: `./waf --help`.
|
|
||||||
1) Configure build: `./waf configure --ios --enable-bundled-deps --sdl2 (path/to/SDL2.framework)`, set `--ios-simulator` instead of `--ios` if you want to build for simulator.
|
|
||||||
2) Compile `./waf build`.
|
|
||||||
3) Navigate to `build` and copy your compiled SDL2.framework there, then add your game dylibs to `build/ios/libs/(gamedir)/(dlls/cl_dlls)`(You can also run `scripts/ios/buildhlsdk.sh` instead to automatically create an ipa with hlsdk dylibs)
|
|
||||||
4) Run `scripts/ios/createipa.sh` to create an installable ipa
|
|
||||||
|
|
||||||
### Running tests
|
|
||||||
|
|
||||||
Tests are enabled with `--enable-tests` passed to `./waf configure` and can be run with `./waf --alltests`.
|
|
||||||
|
|
||||||
This builds both standalone unit tests and a separate engine test binary (`xash3d_tests`) that embeds engine-level tests. The engine test binary requires no game assets.
|
|
||||||
|
|||||||
18
SECURITY.md
18
SECURITY.md
@@ -1,18 +0,0 @@
|
|||||||
# Xash3D FWGS Security Policy
|
|
||||||
|
|
||||||
## Supported versions
|
|
||||||
|
|
||||||
Because we don't have clear release cycle yet, support is only provided to the
|
|
||||||
latest engine versions from `master` branch. You can use `host_ver` command in
|
|
||||||
console to fetch detailed info about the engine build.
|
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
|
||||||
|
|
||||||
If you discover a security vulnerability in this project, it can be reported
|
|
||||||
privately though `Security` tab on GitHub:
|
|
||||||
https://github.com/FWGS/xash3d-fwgs/security
|
|
||||||
|
|
||||||
Alternatively, you can report them publicly on the `Issues` tab:
|
|
||||||
https://github.com/FWGS/xash3d-fwgs/issues
|
|
||||||
|
|
||||||
In both cases, expect a reply within approximately a week.
|
|
||||||
95
android/app/CMakeLists.txt
Normal file
95
android/app/CMakeLists.txt
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.22)
|
||||||
|
|
||||||
|
# Only used to build Android project
|
||||||
|
|
||||||
|
project(XASH_ANDROID)
|
||||||
|
|
||||||
|
# armeabi-v7a requires cpufeatures library
|
||||||
|
if(ANDROID)
|
||||||
|
include_directories(${ANDROID_NDK}/sources/android/cpufeatures)
|
||||||
|
add_library(cpufeatures ${ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c)
|
||||||
|
target_link_libraries(cpufeatures dl)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
include(FindPython)
|
||||||
|
|
||||||
|
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 --enable-limited-debuginfo)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(ANDROID_ABI STREQUAL "x86")
|
||||||
|
# HACKHACK: I don't know why but engine gets built as 64-bit binary here
|
||||||
|
list(APPEND WAF_EXTRA_ARGS -4)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(CMAKE_VERBOSE_MAKEFILE ON)
|
||||||
|
|
||||||
|
|
||||||
|
# not cleanest way to get upper directory
|
||||||
|
set(ENGINE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../")
|
||||||
|
|
||||||
|
set(WAF_CC "${CMAKE_C_COMPILER} --target=${CMAKE_C_COMPILER_TARGET}")
|
||||||
|
set(WAF_CXX "${CMAKE_CXX_COMPILER} --target=${CMAKE_CXX_COMPILER_TARGET}")
|
||||||
|
set(WAF ${Python_EXECUTABLE} ${ENGINE_SOURCE_DIR}waf -t ${ENGINE_SOURCE_DIR} -o ${CMAKE_CURRENT_BINARY_DIR}/xash3d-fwgs)
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
include(ExternalProject)
|
||||||
|
|
||||||
|
# gradle passes backslashes to cmake, how does this even work for everybody else?
|
||||||
|
string(REPLACE "\\" "/" CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY})
|
||||||
|
|
||||||
|
ExternalProject_Add(
|
||||||
|
Xash3DFWGS
|
||||||
|
SOURCE_DIR ${ENGINE_SOURCE_DIR}
|
||||||
|
INSTALL_DIR ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}
|
||||||
|
BUILD_IN_SOURCE TRUE
|
||||||
|
DEPENDS SDL2
|
||||||
|
BUILD_ALWAYS TRUE
|
||||||
|
LOG_CONFIGURE TRUE
|
||||||
|
LOG_BUILD TRUE
|
||||||
|
LOG_INSTALL TRUE
|
||||||
|
LOG_OUTPUT_ON_FAILURE TRUE
|
||||||
|
LOG_MERGED_STDOUTERR TRUE
|
||||||
|
|
||||||
|
# USES_TERMINAL_CONFIGURE TRUE
|
||||||
|
# USES_TERMINAL_BUILD TRUE
|
||||||
|
# USES_TERMINAL_INSTALL TRUE
|
||||||
|
|
||||||
|
# NOTE: setting up WAFLOCK is important to avoid possible race conditions
|
||||||
|
CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env
|
||||||
|
ANDROID_NDK=${ANDROID_NDK}
|
||||||
|
BUILD_CMAKE_LIBRARY_OUTPUT_DIRECTORY=${CMAKE_LIBRARY_OUTPUT_DIRECTORY}
|
||||||
|
WAFLOCK=.lock-waf_android_${ANDROID_ABI}_build
|
||||||
|
${WAF} configure -T ${BUILD_TYPE} --android=${ANDROID_ABI},,${CMAKE_SYSTEM_VERSION}
|
||||||
|
-s "${ENGINE_SOURCE_DIR}/3rdparty/SDL" --enable-bundled-deps ${WAF_EXTRA_ARGS}
|
||||||
|
|
||||||
|
BUILD_COMMAND ${CMAKE_COMMAND} -E env
|
||||||
|
WAFLOCK=.lock-waf_android_${ANDROID_ABI}_build
|
||||||
|
${WAF} build -v
|
||||||
|
|
||||||
|
INSTALL_COMMAND ${CMAKE_COMMAND} -E env
|
||||||
|
WAFLOCK=.lock-waf_android_${ANDROID_ABI}_build
|
||||||
|
${WAF} install --destdir=${CMAKE_LIBRARY_OUTPUT_DIRECTORY}
|
||||||
|
)
|
||||||
|
|
||||||
|
add_subdirectory("${ENGINE_SOURCE_DIR}/3rdparty/hlsdk-portable" hlsdk-portable)
|
||||||
|
|
||||||
|
# a1ba: without this, xash3d target will be ignored as nothing depends on it
|
||||||
|
add_dependencies(client Xash3DFWGS)
|
||||||
|
add_dependencies(server Xash3DFWGS)
|
||||||
|
|
||||||
|
|
||||||
@@ -1,140 +1,130 @@
|
|||||||
import com.android.build.api.dsl.ApplicationExtension
|
|
||||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.Month
|
import java.time.Month
|
||||||
import java.time.temporal.ChronoUnit
|
import java.time.temporal.ChronoUnit
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.android.application)
|
id("com.android.application")
|
||||||
|
id("org.jetbrains.kotlin.android")
|
||||||
}
|
}
|
||||||
|
|
||||||
extensions.configure<ApplicationExtension> {
|
android {
|
||||||
namespace = "su.xash.engine"
|
namespace = "su.xash.engine"
|
||||||
ndkVersion = "29.0.14206865"
|
ndkVersion = "28.0.13004108"
|
||||||
compileSdk = 35
|
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "su.xash.engine"
|
applicationId = "su.xash"
|
||||||
versionName = "0.21-" + getGitHash()
|
applicationIdSuffix = "engine"
|
||||||
versionCode = getBuildNum()
|
versionName = "0.21"
|
||||||
minSdk = 21
|
versionCode = getBuildNum()
|
||||||
targetSdk = 35
|
minSdk = 21
|
||||||
|
targetSdk = 34
|
||||||
|
compileSdk = 34
|
||||||
|
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
val engineRoot = projectDir.parentFile.parent
|
cmake {
|
||||||
|
abiFilters("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
||||||
|
arguments("-DANDROID_USE_LEGACY_TOOLCHAIN_FILE=OFF")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
experimentalProperties["ninja.abiFilters"] = setOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
externalNativeBuild {
|
||||||
experimentalProperties["ninja.path"] = File(engineRoot, "wscript").path
|
cmake {
|
||||||
experimentalProperties["ninja.configure"] = "run-python"
|
version = "3.22.1"
|
||||||
experimentalProperties["ninja.arguments"] = setOf(
|
path = file("CMakeLists.txt")
|
||||||
File(engineRoot, "scripts/configure-ninja.py").path,
|
}
|
||||||
engineRoot,
|
}
|
||||||
"--variant=\${ndk.variantName}",
|
|
||||||
"--abi=\${ndk.abi}",
|
|
||||||
"--configuration-dir=\${ndk.buildRoot}",
|
|
||||||
"--ndk-version=\${ndk.moduleNdkVersion}",
|
|
||||||
"--min-sdk-version=\${ndk.minPlatform}",
|
|
||||||
"--ndk-root=${androidComponents.sdkComponents.ndkDirectory.get()}",
|
|
||||||
// shut up, fake options
|
|
||||||
"-p:Configuration=\${ndk.variantName}",
|
|
||||||
"-p:Platform=\${ndk.abi}"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
targetCompatibility = JavaVersion.VERSION_11
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlin {
|
kotlinOptions {
|
||||||
compilerOptions {
|
jvmTarget = "1.8"
|
||||||
jvmTarget = JvmTarget.JVM_11
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buildFeatures {
|
buildTypes {
|
||||||
viewBinding = true
|
debug {
|
||||||
buildConfig = true
|
isMinifyEnabled = false
|
||||||
}
|
isShrinkResources = false
|
||||||
|
isDebuggable = true
|
||||||
|
applicationIdSuffix = ".test"
|
||||||
|
proguardFiles(
|
||||||
|
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
lint {
|
release {
|
||||||
abortOnError = false
|
isMinifyEnabled = true
|
||||||
}
|
isShrinkResources = true
|
||||||
|
proguardFiles(
|
||||||
|
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
register("asan") {
|
||||||
androidResources {
|
initWith(getByName("debug"))
|
||||||
noCompress += ""
|
}
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
packaging {
|
register("continuous") {
|
||||||
jniLibs {
|
initWith(getByName("release"))
|
||||||
keepDebugSymbols.add("**/*.so")
|
applicationIdSuffix = ".test"
|
||||||
useLegacyPackaging = true
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
getByName("main") {
|
getByName("main") {
|
||||||
assets.directories.add("../../3rdparty/extras/xash-extras")
|
assets.srcDirs("../../3rdparty/extras/xash-extras", "../moddb")
|
||||||
java.directories.add("../../3rdparty/SDL/android-project/app/src/main/java")
|
java.srcDir("../../3rdparty/SDL/android-project/app/src/main/java")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
lint {
|
||||||
debug {
|
abortOnError = false
|
||||||
isMinifyEnabled = false
|
}
|
||||||
isShrinkResources = false
|
|
||||||
isDebuggable = true
|
|
||||||
applicationIdSuffix = ".test"
|
|
||||||
proguardFiles(
|
|
||||||
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
release {
|
buildFeatures {
|
||||||
isMinifyEnabled = true
|
viewBinding = true
|
||||||
isShrinkResources = true
|
buildConfig = true
|
||||||
proguardFiles(
|
}
|
||||||
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
register("asan") {
|
androidResources {
|
||||||
initWith(getByName("debug"))
|
noCompress += ""
|
||||||
}
|
}
|
||||||
|
|
||||||
register("continuous") {
|
packaging {
|
||||||
initWith(getByName("release"))
|
jniLibs {
|
||||||
applicationIdSuffix = ".test"
|
useLegacyPackaging = true
|
||||||
}
|
keepDebugSymbols.add("**/*.so")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(libs.material)
|
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(libs.appcompat)
|
implementation("com.madgag.spongycastle:prov:1.58.0.0")
|
||||||
implementation(libs.navigation.runtime.ktx)
|
implementation("in.dragonbra:javasteam:1.2.0")
|
||||||
implementation(libs.navigation.fragment.ktx)
|
|
||||||
implementation(libs.navigation.ui.ktx)
|
|
||||||
implementation(libs.preference.ktx)
|
|
||||||
implementation(libs.swiperefreshlayout)
|
|
||||||
|
|
||||||
implementation(libs.acra.http)
|
implementation("ch.acra:acra-http:5.11.2")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getBuildNum(): Int {
|
fun getBuildNum(): Int {
|
||||||
val now = LocalDateTime.now()
|
val now = LocalDateTime.now()
|
||||||
val releaseDate = LocalDateTime.of(2015, Month.APRIL, 1, 0, 0, 0)
|
val releaseDate = LocalDateTime.of(2015, Month.APRIL, 1, 0, 0, 0)
|
||||||
val qBuildNum = releaseDate.until(now, ChronoUnit.DAYS)
|
val qBuildNum = releaseDate.until(now, ChronoUnit.DAYS)
|
||||||
val minuteOfDay = now.hour * 60 + now.minute
|
val minuteOfDay = now.hour * 60 + now.minute
|
||||||
return (qBuildNum * 10000 + minuteOfDay).toInt()
|
return (qBuildNum * 10000 + minuteOfDay).toInt()
|
||||||
}
|
|
||||||
|
|
||||||
fun getGitHash(): String {
|
|
||||||
val process = ProcessBuilder("git", "rev-parse", "--short", "HEAD").directory(project.rootDir)
|
|
||||||
.redirectErrorStream(true).start()
|
|
||||||
return process.inputStream.bufferedReader().readText().trim()
|
|
||||||
}
|
}
|
||||||
|
|||||||
27
android/app/proguard-rules.pro
vendored
27
android/app/proguard-rules.pro
vendored
@@ -1,25 +1,3 @@
|
|||||||
# Add project specific ProGuard rules here.
|
|
||||||
# You can control the set of applied configuration files using the
|
|
||||||
# proguardFiles setting in build.gradle.
|
|
||||||
#
|
|
||||||
# For more details, see
|
|
||||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
|
||||||
|
|
||||||
# If your project uses WebView with JS, uncomment the following
|
|
||||||
# and specify the fully qualified class name to the JavaScript interface
|
|
||||||
# class:
|
|
||||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
|
||||||
# public *;
|
|
||||||
#}
|
|
||||||
|
|
||||||
# Uncomment this to preserve the line number information for
|
|
||||||
# debugging stack traces.
|
|
||||||
#-keepattributes SourceFile,LineNumberTable
|
|
||||||
|
|
||||||
# If you keep the line number information, uncomment this to
|
|
||||||
# hide the original source file name.
|
|
||||||
#-renamesourcefileattribute SourceFile
|
|
||||||
|
|
||||||
-keep class su.xash.engine.XashActivity {
|
-keep class su.xash.engine.XashActivity {
|
||||||
java.lang.String loadAndroidID();
|
java.lang.String loadAndroidID();
|
||||||
java.lang.String getAndroidID();
|
java.lang.String getAndroidID();
|
||||||
@@ -109,8 +87,3 @@
|
|||||||
void hapticRun(int, float, int);
|
void hapticRun(int, float, int);
|
||||||
void hapticStop(int);
|
void hapticStop(int);
|
||||||
}
|
}
|
||||||
|
|
||||||
# Unexpected reference to missing service class: META-INF/services/javax.annotation.processing.Processor.
|
|
||||||
-dontwarn javax.annotation.processing.Processor
|
|
||||||
-dontwarn javax.annotation.processing.AbstractProcessor
|
|
||||||
-dontwarn javax.annotation.processing.SupportedOptions
|
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
exec python $@
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
python %*
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name" translatable="false">Xash3D FWGS (Test)</string>
|
<string name="app_name" translatable="false">Xash3D FWGS (Test)</string>
|
||||||
</resources>
|
<string name="authority" translatable="false">su.xash.engine.test.documents</string>
|
||||||
|
</resources>
|
||||||
@@ -1,89 +1,101 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:allowAudioPlaybackCapture="true"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:installLocation="preferExternal">
|
android:allowAudioPlaybackCapture="true"
|
||||||
<!-- OpenGL ES 1.1 -->
|
android:installLocation="preferExternal"
|
||||||
<uses-feature android:glEsVersion="0x00010000" />
|
tools:targetApi="q">
|
||||||
<!-- Touchscreen support -->
|
<!-- OpenGL ES 1.1 -->
|
||||||
<uses-feature
|
<uses-feature android:glEsVersion="0x00010000" />
|
||||||
android:name="android.hardware.touchscreen"
|
<!-- Touchscreen support -->
|
||||||
android:required="false" />
|
<uses-feature
|
||||||
<!-- Game controller support -->
|
android:name="android.hardware.touchscreen"
|
||||||
<uses-feature
|
android:required="false" />
|
||||||
android:name="android.hardware.bluetooth"
|
<!-- Game controller support -->
|
||||||
android:required="false" />
|
<uses-feature
|
||||||
<uses-feature
|
android:name="android.hardware.bluetooth"
|
||||||
android:name="android.hardware.gamepad"
|
android:required="false" />
|
||||||
android:required="false" />
|
<uses-feature
|
||||||
<uses-feature
|
android:name="android.hardware.gamepad"
|
||||||
android:name="android.hardware.usb.host"
|
android:required="false" />
|
||||||
android:required="false" />
|
<uses-feature
|
||||||
<!-- External mouse input events -->
|
android:name="android.hardware.usb.host"
|
||||||
<uses-feature
|
android:required="false" />
|
||||||
android:name="android.hardware.type.pc"
|
<!-- External mouse input events -->
|
||||||
android:required="false" />
|
<uses-feature
|
||||||
<!-- Audio recording support -->
|
android:name="android.hardware.type.pc"
|
||||||
<uses-feature
|
android:required="false" />
|
||||||
android:name="android.hardware.microphone"
|
<!-- Audio recording support -->
|
||||||
android:required="false" />
|
<uses-feature
|
||||||
<!-- Allow downloading to the external storage on Android 5.1 and older -->
|
android:name="android.hardware.microphone"
|
||||||
<uses-permission
|
android:required="false" />
|
||||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
<!-- Allow downloading to the external storage on Android 5.1 and older -->
|
||||||
android:maxSdkVersion="29" />
|
<uses-permission
|
||||||
<uses-permission
|
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||||
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
android:maxSdkVersion="22" />
|
||||||
android:maxSdkVersion="29" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
android:maxSdkVersion="32" />
|
||||||
<!-- Allow access to Bluetooth devices -->
|
<!-- Allow access to Bluetooth devices -->
|
||||||
<!-- Currently this is just for Steam Controller support and requires setting SDL_HINT_JOYSTICK_HIDAPI_STEAM -->
|
<!-- Currently this is just for Steam Controller support and requires setting SDL_HINT_JOYSTICK_HIDAPI_STEAM -->
|
||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.BLUETOOTH"
|
android:name="android.permission.BLUETOOTH"
|
||||||
android:maxSdkVersion="30" />
|
android:maxSdkVersion="30" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||||
<!-- Allow access to the vibrator -->
|
<!-- Allow access to the vibrator -->
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
<!-- Allow recording audio -->
|
<!-- Allow recording audio -->
|
||||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||||
<!-- Allow internet access -->
|
<!-- Allow internet access -->
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<!-- Dedicated server -->
|
<!-- Dedicated server -->
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".MainApplication"
|
android:name=".MainApplication"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:roundIcon="@mipmap/ic_launcher"
|
android:roundIcon="@mipmap/ic_launcher"
|
||||||
android:theme="@style/Theme.App"
|
android:theme="@style/Theme.App"
|
||||||
android:usesCleartextTraffic="true"
|
android:usesCleartextTraffic="true">
|
||||||
android:requestLegacyExternalStorage="true">
|
<activity
|
||||||
<activity
|
android:name=".MainActivity"
|
||||||
android:name=".MainActivity"
|
android:exported="true">
|
||||||
android:exported="true">
|
<intent-filter>
|
||||||
<intent-filter>
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<action android:name="android.intent.action.MAIN" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".XashActivity"
|
android:name=".XashActivity"
|
||||||
android:alwaysRetainTaskState="true"
|
android:alwaysRetainTaskState="true"
|
||||||
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation|touchscreen"
|
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation|touchscreen"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:preferMinimalPostProcessing="true"
|
android:preferMinimalPostProcessing="true"
|
||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustResize"
|
||||||
<intent-filter>
|
tools:targetApi="r">
|
||||||
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
|
<intent-filter>
|
||||||
</intent-filter>
|
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
|
||||||
</activity>
|
</intent-filter>
|
||||||
</application>
|
</activity>
|
||||||
<queries>
|
|
||||||
<intent>
|
<provider
|
||||||
<action android:name="su.xash.engine.MOD" />
|
android:name=".XashDocumentsProvider"
|
||||||
</intent>
|
android:authorities="@string/authority"
|
||||||
</queries>
|
android:exported="true"
|
||||||
</manifest>
|
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>
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package su.xash.engine;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
|
public class AndroidBug5497Workaround {
|
||||||
|
// For more information, see https://code.google.com/p/android/issues/detail?id=5497
|
||||||
|
// To use this class, simply invoke assistActivity() on an Activity that already has its content view set.
|
||||||
|
|
||||||
|
public static void assistActivity(Activity activity) {
|
||||||
|
new AndroidBug5497Workaround(activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
private View mChildOfContent;
|
||||||
|
private int usableHeightPrevious;
|
||||||
|
private FrameLayout.LayoutParams frameLayoutParams;
|
||||||
|
|
||||||
|
private AndroidBug5497Workaround(Activity activity) {
|
||||||
|
FrameLayout content = activity.findViewById(android.R.id.content);
|
||||||
|
mChildOfContent = content.getChildAt(0);
|
||||||
|
mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(this::possiblyResizeChildOfContent);
|
||||||
|
frameLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void possiblyResizeChildOfContent() {
|
||||||
|
int usableHeightNow = computeUsableHeight();
|
||||||
|
if (usableHeightNow != usableHeightPrevious) {
|
||||||
|
int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight();
|
||||||
|
int heightDifference = usableHeightSansKeyboard - usableHeightNow;
|
||||||
|
if (heightDifference > (usableHeightSansKeyboard / 4)) {
|
||||||
|
// keyboard probably just became visible
|
||||||
|
frameLayoutParams.height = usableHeightSansKeyboard - heightDifference;
|
||||||
|
} else {
|
||||||
|
// keyboard probably just became hidden
|
||||||
|
frameLayoutParams.height = usableHeightSansKeyboard;
|
||||||
|
}
|
||||||
|
mChildOfContent.requestLayout();
|
||||||
|
usableHeightPrevious = usableHeightNow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int computeUsableHeight() {
|
||||||
|
Rect r = new Rect();
|
||||||
|
mChildOfContent.getWindowVisibleDisplayFrame(r);
|
||||||
|
return (r.bottom - r.top);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
package su.xash.engine
|
package su.xash.engine
|
||||||
|
|
||||||
class DedicatedActivity {}
|
class DedicatedActivity {
|
||||||
|
}
|
||||||
@@ -4,8 +4,8 @@ import android.app.Service
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
|
|
||||||
class DedicatedService : Service() {
|
class DedicatedService: Service() {
|
||||||
override fun onBind(intent: Intent?): IBinder? {
|
override fun onBind(intent: Intent?): IBinder? {
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,26 +10,26 @@ import androidx.navigation.ui.setupActionBarWithNavController
|
|||||||
import su.xash.engine.databinding.ActivityMainBinding
|
import su.xash.engine.databinding.ActivityMainBinding
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
private lateinit var binding: ActivityMainBinding
|
private lateinit var binding: ActivityMainBinding
|
||||||
private lateinit var appBarConfiguration: AppBarConfiguration
|
private lateinit var appBarConfiguration: AppBarConfiguration
|
||||||
private lateinit var navController: NavController
|
private lateinit var navController: NavController
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
setSupportActionBar(binding.toolbar)
|
setSupportActionBar(binding.toolbar)
|
||||||
|
|
||||||
val navHostFragment =
|
val navHostFragment =
|
||||||
supportFragmentManager.findFragmentById(R.id.fragmentContainerView) as NavHostFragment
|
supportFragmentManager.findFragmentById(R.id.fragmentContainerView) as NavHostFragment
|
||||||
navController = navHostFragment.navController
|
navController = navHostFragment.navController
|
||||||
appBarConfiguration = AppBarConfiguration(navController.graph)
|
appBarConfiguration = AppBarConfiguration(navController.graph)
|
||||||
setupActionBarWithNavController(navController, appBarConfiguration)
|
setupActionBarWithNavController(navController, appBarConfiguration)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSupportNavigateUp(): Boolean {
|
override fun onSupportNavigateUp(): Boolean {
|
||||||
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
|
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,25 +3,26 @@ package su.xash.engine
|
|||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.StrictMode
|
import android.os.StrictMode
|
||||||
|
import org.acra.config.httpSender
|
||||||
import org.acra.data.StringFormat
|
import org.acra.data.StringFormat
|
||||||
import org.acra.ktx.initAcra
|
import org.acra.ktx.initAcra
|
||||||
|
|
||||||
class MainApplication : Application() {
|
class MainApplication : Application() {
|
||||||
override fun attachBaseContext(base: Context?) {
|
override fun attachBaseContext(base: Context?) {
|
||||||
super.attachBaseContext(base)
|
super.attachBaseContext(base)
|
||||||
|
|
||||||
if (!BuildConfig.DEBUG) {
|
if (!BuildConfig.DEBUG) {
|
||||||
initAcra {
|
initAcra {
|
||||||
buildConfigClass = BuildConfig::class.java
|
buildConfigClass = BuildConfig::class.java
|
||||||
reportFormat = StringFormat.JSON
|
reportFormat = StringFormat.JSON
|
||||||
|
|
||||||
// httpSender {
|
httpSender {
|
||||||
// uri = "http://bodis.pp.ua:5000/report"
|
uri = "http://bodis.pp.ua:5000/report"
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// enable strict mode to detect memory leaks etc.
|
// enable strict mode to detect memory leaks etc.
|
||||||
StrictMode.enableDefaults();
|
StrictMode.enableDefaults();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,6 @@ import android.content.pm.ActivityInfo;
|
|||||||
import android.content.res.AssetManager;
|
import android.content.res.AssetManager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Environment;
|
|
||||||
import android.provider.Settings.Secure;
|
import android.provider.Settings.Secure;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
@@ -13,146 +12,137 @@ import android.view.WindowManager;
|
|||||||
|
|
||||||
import org.libsdl.app.SDLActivity;
|
import org.libsdl.app.SDLActivity;
|
||||||
|
|
||||||
import su.xash.engine.util.AndroidBug5497Workaround;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class XashActivity extends SDLActivity {
|
public class XashActivity extends SDLActivity {
|
||||||
private boolean mUseVolumeKeys;
|
private boolean mUseVolumeKeys;
|
||||||
private String mPackageName;
|
private String mPackageName;
|
||||||
private static final String TAG = "XashActivity";
|
private static final String TAG = "XashActivity";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
|
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
//getWindow().addFlags(WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES);
|
//getWindow().addFlags(WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES);
|
||||||
getWindow().getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
|
getWindow().getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
|
||||||
}
|
}
|
||||||
|
|
||||||
AndroidBug5497Workaround.assistActivity(this);
|
AndroidBug5497Workaround.assistActivity(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy()
|
||||||
super.onDestroy();
|
{
|
||||||
|
super.onDestroy();
|
||||||
|
|
||||||
// Now that we don't exit from native code, we need to exit here, resetting
|
// 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)
|
// 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
|
// When the issue with global variables will be resolved, remove that exit() call
|
||||||
System.exit(0);
|
System.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String[] getLibraries() {
|
protected String[] getLibraries() {
|
||||||
return new String[]{"SDL2", "xash"};
|
return new String[]{"SDL2", "xash"};
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("HardwareIds")
|
@SuppressLint("HardwareIds")
|
||||||
private String getAndroidID() {
|
private String getAndroidID() {
|
||||||
return Secure.getString(getContentResolver(), Secure.ANDROID_ID);
|
return Secure.getString(getContentResolver(), Secure.ANDROID_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("ApplySharedPref")
|
@SuppressLint("ApplySharedPref")
|
||||||
private void saveAndroidID(String id) {
|
private void saveAndroidID(String id) {
|
||||||
getSharedPreferences("xash_preferences", MODE_PRIVATE).edit().putString("xash_id", id).commit();
|
getSharedPreferences("xash_preferences", MODE_PRIVATE).edit().putString("xash_id", id).commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String loadAndroidID() {
|
private String loadAndroidID() {
|
||||||
return getSharedPreferences("xash_preferences", MODE_PRIVATE).getString("xash_id", "");
|
return getSharedPreferences("xash_preferences", MODE_PRIVATE).getString("xash_id", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getCallingPackage() {
|
public String getCallingPackage() {
|
||||||
if (mPackageName != null) {
|
if (mPackageName != null) {
|
||||||
return mPackageName;
|
return mPackageName;
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.getCallingPackage();
|
return super.getCallingPackage();
|
||||||
}
|
}
|
||||||
|
|
||||||
private AssetManager getAssets(boolean isEngine) {
|
private AssetManager getAssets(boolean isEngine) {
|
||||||
AssetManager am = null;
|
AssetManager am = null;
|
||||||
|
|
||||||
if (isEngine) {
|
if (isEngine) {
|
||||||
am = getAssets();
|
am = getAssets();
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
am = getPackageManager().getResourcesForApplication(getCallingPackage()).getAssets();
|
am = getPackageManager().getResourcesForApplication(getCallingPackage()).getAssets();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(TAG, "Unable to load mod assets!");
|
Log.e(TAG, "Unable to load mod assets!");
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return am;
|
return am;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String[] getAssetsList(boolean isEngine, String path) {
|
private String[] getAssetsList(boolean isEngine, String path) {
|
||||||
AssetManager am = getAssets(isEngine);
|
AssetManager am = getAssets(isEngine);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return am.list(path);
|
return am.list(path);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new String[]{};
|
return new String[]{};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean dispatchKeyEvent(KeyEvent event) {
|
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||||
if (SDLActivity.mBrokenLibraries) {
|
if (SDLActivity.mBrokenLibraries) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int keyCode = event.getKeyCode();
|
int keyCode = event.getKeyCode();
|
||||||
if (!mUseVolumeKeys) {
|
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) {
|
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ||
|
||||||
return false;
|
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);
|
return getWindow().superDispatchKeyEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: REMOVE LATER, temporary launchers support?
|
// TODO: REMOVE LATER, temporary launchers support?
|
||||||
@Override
|
@Override
|
||||||
protected String[] getArguments() {
|
protected String[] getArguments() {
|
||||||
String gamedir = getIntent().getStringExtra("gamedir");
|
String gamedir = getIntent().getStringExtra("gamedir");
|
||||||
if (gamedir == null) gamedir = "valve";
|
if (gamedir == null) gamedir = "valve";
|
||||||
nativeSetenv("XASH3D_GAME", gamedir);
|
nativeSetenv("XASH3D_GAME", gamedir);
|
||||||
|
|
||||||
String gamelibdir = getIntent().getStringExtra("gamelibdir");
|
String gamelibdir = getIntent().getStringExtra("gamelibdir");
|
||||||
if (gamelibdir != null) nativeSetenv("XASH3D_GAMELIBDIR", gamelibdir);
|
if (gamelibdir != null) nativeSetenv("XASH3D_GAMELIBDIR", gamelibdir);
|
||||||
|
|
||||||
String pakfile = getIntent().getStringExtra("pakfile");
|
String pakfile = getIntent().getStringExtra("pakfile");
|
||||||
if (pakfile != null) nativeSetenv("XASH3D_EXTRAS_PAK2", pakfile);
|
if (pakfile != null) nativeSetenv("XASH3D_EXTRAS_PAK2", pakfile);
|
||||||
|
|
||||||
String basedir = getIntent().getStringExtra("basedir");
|
mUseVolumeKeys = getIntent().getBooleanExtra("usevolume", false);
|
||||||
if (basedir != null) {
|
mPackageName = getIntent().getStringExtra("package");
|
||||||
nativeSetenv("XASH3D_BASEDIR", basedir);
|
|
||||||
} else {
|
|
||||||
String rootPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/xash";
|
|
||||||
nativeSetenv("XASH3D_BASEDIR", rootPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
mUseVolumeKeys = getIntent().getBooleanExtra("usevolume", false);
|
String[] env = getIntent().getStringArrayExtra("env");
|
||||||
mPackageName = getIntent().getStringExtra("package");
|
if (env != null) {
|
||||||
|
for (int i = 0; i < env.length; i += 2)
|
||||||
|
nativeSetenv(env[i], env[i + 1]);
|
||||||
|
}
|
||||||
|
|
||||||
String[] env = getIntent().getStringArrayExtra("env");
|
String argv = getIntent().getStringExtra("argv");
|
||||||
if (env != null) {
|
if (argv == null) argv = "-console -log";
|
||||||
for (int i = 0; i < env.length; i += 2)
|
return argv.split(" ");
|
||||||
nativeSetenv(env[i], env[i + 1]);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
String argv = getIntent().getStringExtra("argv");
|
|
||||||
if (argv == null) argv = "-console -log";
|
|
||||||
|
|
||||||
return argv.split(" ");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,253 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,57 +14,64 @@ import su.xash.engine.ui.library.LibraryViewModel
|
|||||||
|
|
||||||
|
|
||||||
class GameAdapter(private val libraryViewModel: LibraryViewModel) :
|
class GameAdapter(private val libraryViewModel: LibraryViewModel) :
|
||||||
ListAdapter<Game, GameAdapter.GameViewHolder>(DiffCallback()) {
|
ListAdapter<Game, GameAdapter.GameViewHolder>(DiffCallback()) {
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GameAdapter.GameViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GameAdapter.GameViewHolder {
|
||||||
val binding = CardGameBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
val binding = CardGameBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
return GameViewHolder(binding)
|
return GameViewHolder(binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: GameAdapter.GameViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: GameAdapter.GameViewHolder, position: Int) {
|
||||||
return holder.bind(getItem(position))
|
return holder.bind(getItem(position))
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DiffCallback : DiffUtil.ItemCallback<Game>() {
|
private class DiffCallback : DiffUtil.ItemCallback<Game>() {
|
||||||
override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean {
|
override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean {
|
||||||
return oldItem.basedir.name == newItem.basedir.name
|
return oldItem.basedir.name == newItem.basedir.name
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean {
|
override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean {
|
||||||
return oldItem.basedir.name == newItem.basedir.name
|
return oldItem.basedir.name == newItem.basedir.name && oldItem.installed == newItem.installed
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class GameViewHolder(val binding: CardGameBinding) :
|
inner class GameViewHolder(val binding: CardGameBinding) :
|
||||||
RecyclerView.ViewHolder(binding.root) {
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
fun bind(game: Game) {
|
fun bind(game: Game) {
|
||||||
binding.apply {
|
binding.apply {
|
||||||
gameTitle.text = game.title
|
gameTitle.text = game.title
|
||||||
|
|
||||||
if (game.icon != null) {
|
if (game.icon != null) {
|
||||||
gameIcon.setImageBitmap(game.icon)
|
gameIcon.setImageBitmap(game.icon)
|
||||||
} else {
|
} else {
|
||||||
gameIcon.visibility = View.GONE
|
gameIcon.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
if (game.cover != null) {
|
if (game.cover != null) {
|
||||||
gameCover.setImageBitmap(game.cover)
|
gameCover.setImageBitmap(game.cover)
|
||||||
} else {
|
} else {
|
||||||
gameCover.visibility = View.GONE
|
gameCover.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
settingsButton.setOnClickListener {
|
if (!game.installed) {
|
||||||
libraryViewModel.setSelectedGame(game)
|
launchButton.visibility = View.GONE
|
||||||
it.findNavController()
|
settingsButton.visibility = View.GONE
|
||||||
.navigate(R.id.action_libraryFragment_to_gameSettingsFragment)
|
progressIndicator.visibility = View.VISIBLE
|
||||||
}
|
return
|
||||||
|
}
|
||||||
|
|
||||||
root.setOnClickListener { libraryViewModel.startEngine(it.context, game) }
|
settingsButton.setOnClickListener {
|
||||||
launchButton.setOnClickListener {
|
libraryViewModel.setSelectedGame(game)
|
||||||
libraryViewModel.startEngine(it.context, game)
|
it.findNavController()
|
||||||
}
|
.navigate(R.id.action_libraryFragment_to_gameSettingsFragment)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
root.setOnClickListener { libraryViewModel.startEngine(it.context, game) }
|
||||||
}
|
launchButton.setOnClickListener {
|
||||||
|
libraryViewModel.startEngine(it.context, game)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,90 +1,89 @@
|
|||||||
package su.xash.engine.model
|
package su.xash.engine.model
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import su.xash.engine.util.TGAReader
|
import su.xash.engine.util.TGAReader
|
||||||
import java.io.File
|
|
||||||
import java.io.FileInputStream
|
|
||||||
import java.util.Scanner
|
import java.util.Scanner
|
||||||
|
|
||||||
|
|
||||||
object BackgroundBitmap {
|
object BackgroundBitmap {
|
||||||
private const val BACKGROUND_ROWS = 3
|
private const val BACKGROUND_ROWS = 3
|
||||||
private const val BACKGROUND_COLUMNS = 4
|
private const val BACKGROUND_COLUMNS = 4
|
||||||
private const val BACKGROUND_WIDTH = 800
|
private const val BACKGROUND_WIDTH = 800
|
||||||
private const val BACKGROUND_HEIGHT = 600
|
private const val BACKGROUND_HEIGHT = 600
|
||||||
|
|
||||||
fun createBackground(file: File): Bitmap {
|
fun createBackground(ctx: Context, file: DocumentFile): Bitmap {
|
||||||
var bitmap =
|
var bitmap =
|
||||||
Bitmap.createBitmap(BACKGROUND_WIDTH, BACKGROUND_HEIGHT, Bitmap.Config.ARGB_8888)
|
Bitmap.createBitmap(BACKGROUND_WIDTH, BACKGROUND_HEIGHT, Bitmap.Config.ARGB_8888)
|
||||||
var canvas = Canvas(bitmap)
|
var canvas = Canvas(bitmap)
|
||||||
var x: Int
|
var x: Int
|
||||||
var y = 0
|
var y = 0
|
||||||
var width: Int
|
var width: Int
|
||||||
var height = 0
|
var height = 0
|
||||||
|
|
||||||
val resourceFolder = File(file, "resource")
|
var bgLayout = file.findFile("resource")?.findFile("HD_BackgroundLayout.txt")
|
||||||
var bgLayout = File(resourceFolder, "HD_BackgroundLayout.txt")
|
if (bgLayout == null) {
|
||||||
if (!bgLayout.exists()) {
|
bgLayout = file.findFile("resource")?.findFile("BackgroundLayout.txt")
|
||||||
bgLayout = File(resourceFolder, "BackgroundLayout.txt")
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (!bgLayout.exists()) {
|
if (bgLayout == null) {
|
||||||
val dir = File(resourceFolder, "background")
|
val dir = file.findFile("resource")?.findFile("background")
|
||||||
for (i in 0 until BACKGROUND_ROWS) {
|
for (i in 0 until BACKGROUND_ROWS) {
|
||||||
x = 0
|
x = 0
|
||||||
for (j in 0 until BACKGROUND_COLUMNS) {
|
for (j in 0 until BACKGROUND_COLUMNS) {
|
||||||
val filename = "${BACKGROUND_WIDTH}_${i + 1}_${'a' + j}_loading.tga"
|
val filename = "${BACKGROUND_WIDTH}_${i + 1}_${'a' + j}_loading.tga"
|
||||||
val bmpFile = File(dir, filename)
|
val bmpFile = dir?.findFile(filename)
|
||||||
val bmpImage = loadTga(bmpFile)
|
val bmpImage = loadTga(ctx, bmpFile!!)
|
||||||
|
|
||||||
canvas.drawBitmap(bmpImage, x.toFloat(), y.toFloat(), null)
|
canvas.drawBitmap(bmpImage, x.toFloat(), y.toFloat(), null)
|
||||||
x += bmpImage.width
|
x += bmpImage.width
|
||||||
height = bmpImage.height
|
height = bmpImage.height
|
||||||
|
|
||||||
}
|
}
|
||||||
y += height
|
y += height
|
||||||
}
|
}
|
||||||
return bitmap
|
return bitmap
|
||||||
}
|
}
|
||||||
|
|
||||||
FileInputStream(bgLayout).use { inputStream ->
|
ctx.contentResolver.openInputStream(bgLayout.uri).use { inputStream ->
|
||||||
Scanner(inputStream).use { scanner ->
|
Scanner(inputStream).use { scanner ->
|
||||||
while (scanner.hasNext()) {
|
while (scanner.hasNext()) {
|
||||||
when (val str = scanner.next()) {
|
when (val str = scanner.next()) {
|
||||||
"resolution" -> {
|
"resolution" -> {
|
||||||
width = scanner.nextInt()
|
width = scanner.nextInt()
|
||||||
height = scanner.nextInt()
|
height = scanner.nextInt()
|
||||||
bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||||
canvas = Canvas(bitmap)
|
canvas = Canvas(bitmap)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
var bmpFile = file
|
var bmpFile = file
|
||||||
str.split("/").forEach { bmpFile = File(bmpFile, it) }
|
str.split("/").forEach { bmpFile = bmpFile.findFile(it)!! }
|
||||||
//skip
|
//skip
|
||||||
scanner.next()
|
scanner.next()
|
||||||
x = scanner.nextInt()
|
x = scanner.nextInt()
|
||||||
y = scanner.nextInt()
|
y = scanner.nextInt()
|
||||||
val bmp = loadTga(bmpFile)
|
val bmp = loadTga(ctx, bmpFile)
|
||||||
canvas.drawBitmap(bmp, x.toFloat(), y.toFloat(), null)
|
canvas.drawBitmap(bmp, x.toFloat(), y.toFloat(), null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return bitmap
|
return bitmap
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadTga(file: File): Bitmap {
|
private fun loadTga(ctx: Context, file: DocumentFile): Bitmap {
|
||||||
FileInputStream(file).use {
|
ctx.contentResolver.openInputStream(file.uri).use {
|
||||||
val buffer = it.readBytes()
|
val buffer = it?.readBytes()
|
||||||
val pixels = TGAReader.read(buffer, TGAReader.ARGB)
|
val pixels = TGAReader.read(buffer, TGAReader.ARGB)
|
||||||
|
|
||||||
val width = TGAReader.getWidth(buffer)
|
val width = TGAReader.getWidth(buffer)
|
||||||
val height = TGAReader.getHeight(buffer)
|
val height = TGAReader.getHeight(buffer)
|
||||||
|
|
||||||
return Bitmap.createBitmap(pixels, 0, width, width, height, Bitmap.Config.ARGB_8888)
|
return Bitmap.createBitmap(pixels, 0, width, width, height, Bitmap.Config.ARGB_8888)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,204 +5,129 @@ import android.content.Intent
|
|||||||
import android.content.pm.PackageInfo
|
import android.content.pm.PackageInfo
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.BitmapFactory
|
import android.net.Uri
|
||||||
import androidx.core.net.toUri
|
import android.provider.MediaStore
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import su.xash.engine.R
|
|
||||||
import su.xash.engine.XashActivity
|
import su.xash.engine.XashActivity
|
||||||
import java.io.File
|
|
||||||
import java.io.FileInputStream
|
|
||||||
import java.util.Arrays
|
|
||||||
import android.util.Log
|
|
||||||
|
|
||||||
class Game(val ctx: Context, val basedir: File, val gameInfoFile: File) {
|
|
||||||
private var iconName = "game.ico"
|
|
||||||
var title = "Unknown Game"
|
|
||||||
var icon: Bitmap? = null
|
|
||||||
var cover: Bitmap? = null
|
|
||||||
|
|
||||||
val mobileHacksGames = arrayOf("aom", "bdlands", "biglolly", "bshift", "caseclosed",
|
class Game(val ctx: Context, val basedir: DocumentFile, var installed: Boolean = true) {
|
||||||
"hl_urbicide", "induction", "redempt", "secret",
|
private var iconName = "game.ico"
|
||||||
"sewer_beta", "tot", "valve", "vendetta")
|
var title = "Unknown Game"
|
||||||
|
var icon: Bitmap? = null
|
||||||
|
var cover: Bitmap? = null
|
||||||
|
|
||||||
// a1ba: follow the behavior of Xash3D's game_launch.
|
private val pref = ctx.getSharedPreferences(basedir.name, Context.MODE_PRIVATE)
|
||||||
// for hl mods we put `valve` as game directory
|
|
||||||
// for any other game that's not hl this string must be replaced with your
|
|
||||||
// main game directory
|
|
||||||
// mods always use -game command line parameter
|
|
||||||
var defaultGameDir = "valve"
|
|
||||||
|
|
||||||
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) }
|
||||||
|
|
||||||
init {
|
basedir.findFile(iconName)
|
||||||
parseGameInfo(gameInfoFile)
|
?.let { icon = MediaStore.Images.Media.getBitmap(ctx.contentResolver, it.uri) }
|
||||||
|
|
||||||
val iconFile = File(basedir, iconName)
|
try {
|
||||||
if (iconFile.exists()) {
|
cover = BackgroundBitmap.createBackground(ctx, basedir)
|
||||||
icon = BitmapFactory.decodeFile(iconFile.path)
|
} catch (e: Exception) {
|
||||||
}
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
fun startEngine(ctx: Context) {
|
||||||
cover = BackgroundBitmap.createBackground(basedir)
|
ctx.startActivity(Intent(ctx, XashActivity::class.java).apply {
|
||||||
} catch (e: Exception) {
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||||
e.printStackTrace()
|
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()) }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fun startEngine(ctx: Context) {
|
private fun parseGameInfo(file: DocumentFile) {
|
||||||
val packageNames = getPackageNamesForGameDir(basedir.name)
|
ctx.contentResolver.openInputStream(file.uri).use { inputStream ->
|
||||||
var externalGame = false
|
inputStream?.bufferedReader().use { reader ->
|
||||||
var commandLineArgs = "";
|
reader?.forEachLine {
|
||||||
|
val tokens = it.split("\\s+".toRegex(), limit = 2)
|
||||||
|
if (tokens.size >= 2) {
|
||||||
|
val k = tokens[0]
|
||||||
|
val v = tokens[1].trim('"')
|
||||||
|
|
||||||
if (basedir.name != defaultGameDir)
|
if (k == "title" || k == "game") title = v
|
||||||
commandLineArgs += "-game ${basedir.name} "
|
if (k == "icon") iconName = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (packageNames != null) {
|
private fun getPackageName(): String? {
|
||||||
if (packageNames.contains("su.xash.engine")) {
|
// return if (mDbEntry != null) {
|
||||||
commandLineArgs += "-dll @hl "
|
// mDbEntry.getPackageName()
|
||||||
} else if (packageNames.contains("su.xash.cs16client")) {
|
// } else null
|
||||||
if (pref.getBoolean("enable_yapb_bots", true)) {
|
return null
|
||||||
commandLineArgs += "-dll @yapb "
|
}
|
||||||
}
|
|
||||||
externalGame = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
commandLineArgs += pref.getString("arguments", "-console -log")
|
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
|
||||||
|
}
|
||||||
|
|
||||||
var packageName: String? = null
|
companion object {
|
||||||
var gameLibDir: String? = null
|
fun getGames(ctx: Context, file: DocumentFile): List<Game> {
|
||||||
if (externalGame && packageNames != null) {
|
val games = mutableListOf<Game>()
|
||||||
for (pn in packageNames) {
|
|
||||||
gameLibDir = try {
|
|
||||||
getGameLibDir(ctx, pn)
|
|
||||||
} catch(e: PackageManager.NameNotFoundException) {
|
|
||||||
null
|
|
||||||
} catch(e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gameLibDir != null) {
|
if (checkIfGamedir(file)) {
|
||||||
packageName = pn
|
games.add(Game(ctx, file))
|
||||||
break
|
} else {
|
||||||
}
|
file.listFiles().forEach {
|
||||||
}
|
if (it.isDirectory) {
|
||||||
|
if (checkIfGamedir(it)) {
|
||||||
|
games.add(Game(ctx, it))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (gameLibDir == null) {
|
return games
|
||||||
MaterialAlertDialogBuilder(ctx).apply {
|
}
|
||||||
setTitle(R.string.game_apk_required)
|
|
||||||
setMessage(R.string.game_apk_message)
|
|
||||||
setPositiveButton(R.string.game_apk_install) { _, _ ->
|
|
||||||
val intent = Intent(Intent.ACTION_VIEW,
|
|
||||||
getDownloadPageForGameDir(basedir.name).toUri())
|
|
||||||
ctx.startActivity(intent)
|
|
||||||
}
|
|
||||||
show()
|
|
||||||
|
|
||||||
return
|
fun checkIfGamedir(file: DocumentFile): Boolean {
|
||||||
}
|
// exclude unfinished downloads
|
||||||
}
|
if (file.name?.startsWith('.') == true)
|
||||||
}
|
return false
|
||||||
|
|
||||||
ctx.startActivity(Intent(ctx, XashActivity::class.java).apply {
|
file.findFile("liblist.gam")?.let { return true }
|
||||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
file.findFile("gameinfo.txt")?.let { return true }
|
||||||
|
return false
|
||||||
putExtra("gamedir", defaultGameDir)
|
}
|
||||||
putExtra("argv", commandLineArgs)
|
}
|
||||||
putExtra("usevolume", pref.getBoolean("use_volume_buttons", false))
|
|
||||||
putExtra("basedir", basedir.parent)
|
|
||||||
|
|
||||||
if (gameLibDir != null) {
|
|
||||||
putExtra("gamelibdir", gameLibDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (packageName != null) {
|
|
||||||
putExtra("package", packageName)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun parseGameInfo(file: File) {
|
|
||||||
FileInputStream(file).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 getPackageNamesForGameDir(gamedir: String): Array<String>? {
|
|
||||||
if (gamedir.equals("cstrike", ignoreCase = true)
|
|
||||||
|| gamedir.equals("czero", ignoreCase = true))
|
|
||||||
return arrayOf("su.xash.cs16client.test", "su.xash.cs16client")
|
|
||||||
|
|
||||||
if (gamedir.equals("tfc", ignoreCase = true))
|
|
||||||
return arrayOf("su.xash.tf15client.test", "su.xash.tf15client")
|
|
||||||
|
|
||||||
// mobile_hacks hlsdk-portable branch allows us to have few more mods out of the box
|
|
||||||
if (mobileHacksGames.any { it.equals(gamedir, ignoreCase = true) })
|
|
||||||
return arrayOf("su.xash.engine")
|
|
||||||
|
|
||||||
// return if (mDbEntry != null) {
|
|
||||||
// mDbEntry.getPackageName()
|
|
||||||
// } else null
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getDownloadPageForGameDir(gamedir: String): String {
|
|
||||||
if (gamedir.equals("cstrike", ignoreCase = true)
|
|
||||||
|| gamedir.equals("czero", ignoreCase = true))
|
|
||||||
return "https://github.com/Velaron/cs16-client/releases/download/continuous/CS16Client-Android.apk"
|
|
||||||
|
|
||||||
if (gamedir.equals("tfc", ignoreCase = true))
|
|
||||||
return "https://github.com/Velaron/tf15-client/releases/download/continuous/TF15Client-Android.apk"
|
|
||||||
|
|
||||||
// just so we don't return null
|
|
||||||
return "https://github.com/FWGS/xash3d-fwgs/releases/download/continuous/xash3d-fwgs-android.apk"
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getGameLibDir(ctx: Context, packageName: String): String? {
|
|
||||||
val packageInfo: PackageInfo = ctx.packageManager.getPackageInfo(packageName, 0)
|
|
||||||
return packageInfo.applicationInfo?.nativeLibraryDir
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun getGames(ctx: Context, root: File): List<Game> {
|
|
||||||
val games = mutableListOf<Game>()
|
|
||||||
|
|
||||||
root.listFiles()?.forEach {
|
|
||||||
if (it.isDirectory) {
|
|
||||||
val subDirGameInfoFile = checkIfGamedir(it)
|
|
||||||
if (subDirGameInfoFile != null) {
|
|
||||||
games.add(Game(ctx, it, subDirGameInfoFile))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return games
|
|
||||||
}
|
|
||||||
|
|
||||||
fun checkIfGamedir(gamedir: File): File? {
|
|
||||||
gamedir.listFiles()?.forEach {
|
|
||||||
if (it.isFile) {
|
|
||||||
if (it.name.equals("liblist.gam", ignoreCase = true))
|
|
||||||
return it
|
|
||||||
|
|
||||||
if (it.name.equals("gameinfo.txt", ignoreCase = true))
|
|
||||||
return it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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()));
|
||||||
|
//}
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package su.xash.engine.model
|
||||||
|
|
||||||
|
import org.json.JSONArray
|
||||||
|
import org.json.JSONObject
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
|
class ModDatabase(inputStream: InputStream) {
|
||||||
|
val entries = mutableListOf<Entry>()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val VERSION = 1
|
||||||
|
|
||||||
|
fun getFilename(): String {
|
||||||
|
return "v${VERSION}.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
inputStream.bufferedReader().use {
|
||||||
|
val jsonArray = JSONArray(it.readText())
|
||||||
|
|
||||||
|
for (i in 0..<jsonArray.length()) {
|
||||||
|
entries.add(Entry(jsonArray.getJSONObject(i)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getByGameDir(gamedir: String): Entry? {
|
||||||
|
return entries.filter { it.gamedir.equals(gamedir) }.firstOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
inner class Entry(jsonObject: JSONObject) {
|
||||||
|
var name: String? = null
|
||||||
|
var appid: Int? = null
|
||||||
|
var gamedir: String? = null
|
||||||
|
|
||||||
|
// TODO Depots
|
||||||
|
var pkgname: String? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (jsonObject.has("name")) {
|
||||||
|
name = jsonObject.getString("name");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jsonObject.has("app_id")) {
|
||||||
|
appid = jsonObject.getInt("app_id");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jsonObject.has("gamedir")) {
|
||||||
|
gamedir = jsonObject.getString("gamedir");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jsonObject.has("package_name")) {
|
||||||
|
pkgname = jsonObject.getString("package_name");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,184 +1,101 @@
|
|||||||
package su.xash.engine.ui.library
|
package su.xash.engine.ui.library
|
||||||
|
|
||||||
import android.Manifest
|
import android.annotation.SuppressLint
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.ActivityNotFoundException
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Environment
|
import android.util.Log
|
||||||
import android.provider.Settings
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import android.widget.Toast
|
||||||
import androidx.core.app.ActivityCompat
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.core.view.MenuProvider
|
import androidx.core.view.MenuProvider
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import kotlinx.coroutines.launch
|
||||||
import su.xash.engine.BuildConfig
|
|
||||||
import su.xash.engine.R
|
import su.xash.engine.R
|
||||||
import su.xash.engine.adapters.GameAdapter
|
import su.xash.engine.adapters.GameAdapter
|
||||||
import su.xash.engine.databinding.FragmentLibraryBinding
|
import su.xash.engine.databinding.FragmentLibraryBinding
|
||||||
|
|
||||||
|
|
||||||
class LibraryFragment : Fragment(), MenuProvider {
|
class LibraryFragment : Fragment(), MenuProvider {
|
||||||
private var _binding: FragmentLibraryBinding? = null
|
private var _binding: FragmentLibraryBinding? = null
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
private val libraryViewModel: LibraryViewModel by activityViewModels()
|
private val libraryViewModel: LibraryViewModel by activityViewModels()
|
||||||
|
|
||||||
private val startActivityForResult =
|
override fun onCreateView(
|
||||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
||||||
if (checkStoragePermissions()) {
|
): View? {
|
||||||
libraryViewModel.reloadGames(requireContext())
|
_binding = FragmentLibraryBinding.inflate(inflater, container, false)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val requiredPermissions = arrayOf(
|
val adapter = GameAdapter(libraryViewModel)
|
||||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
binding.gamesList.adapter = adapter
|
||||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
|
||||||
)
|
|
||||||
|
|
||||||
private val requestPermissionLauncher = registerForActivityResult(
|
requireActivity().addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)
|
||||||
ActivityResultContracts.RequestMultiplePermissions()
|
|
||||||
) { permissions ->
|
|
||||||
val granted = permissions.entries.all { it.value }
|
|
||||||
if (granted) {
|
|
||||||
libraryViewModel.reloadGames(requireContext())
|
|
||||||
} else {
|
|
||||||
checkStoragePermissions()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun checkStoragePermissions(): Boolean {
|
return binding.root
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
}
|
||||||
if (!Environment.isExternalStorageManager()) {
|
|
||||||
MaterialAlertDialogBuilder(requireContext()).apply {
|
|
||||||
setTitle(R.string.file_access_required)
|
|
||||||
setMessage(R.string.file_access_message)
|
|
||||||
setPositiveButton(R.string.open_settings) { _, _ ->
|
|
||||||
startActivityForResult.launch(
|
|
||||||
Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION).setData(
|
|
||||||
Uri.fromParts("package", BuildConfig.APPLICATION_ID, null)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
setNeutralButton(R.string.done_check_permissions) { dialog, _ ->
|
|
||||||
checkStoragePermissions()
|
|
||||||
}
|
|
||||||
setCancelable(false)
|
|
||||||
show()
|
|
||||||
|
|
||||||
return false
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
}
|
binding.swipeRefresh.setOnRefreshListener { libraryViewModel.reloadGames(requireContext()) }
|
||||||
} else {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
||||||
val permissionsNeeded = requiredPermissions.filter {
|
|
||||||
ContextCompat.checkSelfPermission(
|
|
||||||
requireContext(),
|
|
||||||
it
|
|
||||||
) != PackageManager.PERMISSION_GRANTED
|
|
||||||
}.toTypedArray()
|
|
||||||
|
|
||||||
if (!permissionsNeeded.isEmpty()) {
|
libraryViewModel.isReloading.observe(viewLifecycleOwner) {
|
||||||
val showRationale = permissionsNeeded.any {
|
binding.swipeRefresh.isRefreshing = it
|
||||||
ActivityCompat.shouldShowRequestPermissionRationale(requireActivity(), it)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
MaterialAlertDialogBuilder(requireContext()).apply {
|
libraryViewModel.installedGames.observe(viewLifecycleOwner) {
|
||||||
setTitle(R.string.external_storage_required)
|
(binding.gamesList.adapter as GameAdapter).submitList(it)
|
||||||
setMessage(R.string.external_storage_message)
|
}
|
||||||
setPositiveButton(R.string.open_settings) { _, _ ->
|
|
||||||
if (showRationale) {
|
|
||||||
requestPermissionLauncher.launch(permissionsNeeded)
|
|
||||||
} else {
|
|
||||||
val intent =
|
|
||||||
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
|
|
||||||
data =
|
|
||||||
Uri.fromParts("package", requireContext().packageName, null)
|
|
||||||
}
|
|
||||||
startActivity(intent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setNeutralButton(R.string.done_check_permissions) { _, _ ->
|
|
||||||
checkStoragePermissions()
|
|
||||||
}
|
|
||||||
setCancelable(false)
|
|
||||||
show()
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
libraryViewModel.workInfos.observe(viewLifecycleOwner) {
|
||||||
} else {
|
libraryViewModel.refreshDownloads(requireContext())
|
||||||
return true
|
}
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateView(
|
libraryViewModel.downloads.observe(viewLifecycleOwner) {
|
||||||
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
libraryViewModel.reloadGames(requireContext())
|
||||||
): View {
|
}
|
||||||
_binding = FragmentLibraryBinding.inflate(inflater, container, false)
|
}
|
||||||
|
|
||||||
val adapter = GameAdapter(libraryViewModel)
|
override fun onDestroyView() {
|
||||||
binding.gamesList.adapter = adapter
|
super.onDestroyView()
|
||||||
|
_binding = null
|
||||||
|
}
|
||||||
|
|
||||||
requireActivity().addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)
|
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||||
|
menuInflater.inflate(R.menu.menu_library, menu)
|
||||||
|
}
|
||||||
|
|
||||||
return binding.root
|
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
|
||||||
}
|
when (menuItem.itemId) {
|
||||||
|
R.id.action_browse -> {
|
||||||
|
try {
|
||||||
|
startActivity(
|
||||||
|
Intent(Intent.ACTION_VIEW).setDataAndType(
|
||||||
|
null, "vnd.android.document/directory"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
catch(e: ActivityNotFoundException) {
|
||||||
|
Toast.makeText(getActivity(), R.string.library_fragment_no_file_manager, Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
R.id.action_install -> {
|
||||||
binding.swipeRefresh.setOnRefreshListener { libraryViewModel.reloadGames(requireContext()) }
|
findNavController().navigate(R.id.action_libraryFragment_to_setupFragment)
|
||||||
|
}
|
||||||
|
|
||||||
libraryViewModel.isReloading.observe(viewLifecycleOwner) {
|
R.id.action_settings -> {
|
||||||
binding.swipeRefresh.isRefreshing = it
|
findNavController().navigate(R.id.action_libraryFragment_to_appSettingsFragment)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
libraryViewModel.installedGames.observe(viewLifecycleOwner) {
|
return false
|
||||||
(binding.gamesList.adapter as GameAdapter).submitList(it)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (checkStoragePermissions()) {
|
|
||||||
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_settings -> {
|
|
||||||
findNavController().navigate(R.id.action_libraryFragment_to_appSettingsFragment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
super.onResume()
|
|
||||||
|
|
||||||
if (checkStoragePermissions()) {
|
|
||||||
libraryViewModel.reloadGames(requireContext())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,56 +3,125 @@ package su.xash.engine.ui.library
|
|||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.os.Environment
|
import android.net.Uri
|
||||||
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.lifecycle.viewModelScope
|
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.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import su.xash.engine.model.Game
|
import su.xash.engine.model.Game
|
||||||
import su.xash.engine.util.Nomedia
|
import su.xash.engine.model.ModDatabase
|
||||||
import java.io.File
|
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) {
|
class LibraryViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
val installedGames: LiveData<List<Game>> get() = _installedGames
|
val installedGames: LiveData<List<Game>> get() = _installedGames
|
||||||
private val _installedGames = MutableLiveData(emptyList<Game>())
|
private val _installedGames = MutableLiveData(emptyList<Game>())
|
||||||
|
|
||||||
val isReloading: LiveData<Boolean> get() = _isReloading
|
val downloads: LiveData<List<Game>> get() = _downloads
|
||||||
private val _isReloading = MutableLiveData(false)
|
private val _downloads = MutableLiveData(emptyList<Game>())
|
||||||
|
|
||||||
val selectedItem: LiveData<Game> get() = _selectedItem
|
val isReloading: LiveData<Boolean> get() = _isReloading
|
||||||
private val _selectedItem = MutableLiveData<Game>()
|
private val _isReloading = MutableLiveData(false)
|
||||||
|
|
||||||
private val appPreferences: SharedPreferences =
|
private val workManager = WorkManager.getInstance(application.applicationContext)
|
||||||
application.getSharedPreferences("app_preferences", Context.MODE_PRIVATE)
|
val workInfos: LiveData<List<WorkInfo>> = workManager.getWorkInfosByTagLiveData(TAG_INSTALL)
|
||||||
|
|
||||||
fun reloadGames(ctx: Context) {
|
val selectedItem: LiveData<Game> get() = _selectedItem
|
||||||
if (isReloading.value == true) {
|
private val _selectedItem = MutableLiveData<Game>()
|
||||||
return
|
|
||||||
}
|
|
||||||
_isReloading.value = true
|
|
||||||
|
|
||||||
viewModelScope.launch {
|
val appPreferences = application.getSharedPreferences("app_preferences", Context.MODE_PRIVATE)
|
||||||
withContext(Dispatchers.IO) {
|
val modDb: ModDatabase
|
||||||
val rootPath = appPreferences.getString("game_path", null)
|
|
||||||
?: (Environment.getExternalStorageDirectory().absolutePath + "/xash")
|
|
||||||
val root = File(rootPath)
|
|
||||||
|
|
||||||
Nomedia.ensureNomedia(root)
|
init {
|
||||||
|
modDb = application.assets.open(ModDatabase.getFilename()).use { ModDatabase(it) }
|
||||||
|
reloadGames(application.applicationContext)
|
||||||
|
}
|
||||||
|
|
||||||
_installedGames.postValue(Game.getGames(ctx, root))
|
fun reloadGames(ctx: Context) {
|
||||||
_isReloading.postValue(false)
|
if (isReloading.value == true) {
|
||||||
}
|
return
|
||||||
}
|
}
|
||||||
}
|
_isReloading.value = true
|
||||||
|
|
||||||
fun setSelectedGame(game: Game) {
|
viewModelScope.launch {
|
||||||
_selectedItem.value = game
|
withContext(Dispatchers.IO) {
|
||||||
}
|
val games = mutableListOf<Game>()
|
||||||
|
val root = DocumentFile.fromFile(ctx.getExternalFilesDir(null)!!)
|
||||||
|
|
||||||
fun startEngine(ctx: Context, game: Game) {
|
val installedGames = Game.getGames(ctx, root)
|
||||||
game.startEngine(ctx)
|
.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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,24 +8,24 @@ import androidx.fragment.app.Fragment
|
|||||||
import su.xash.engine.databinding.FragmentAppSettingsBinding
|
import su.xash.engine.databinding.FragmentAppSettingsBinding
|
||||||
|
|
||||||
class AppSettingsFragment : Fragment() {
|
class AppSettingsFragment : Fragment() {
|
||||||
private var _binding: FragmentAppSettingsBinding? = null
|
private var _binding: FragmentAppSettingsBinding? = null
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
||||||
): View {
|
): View? {
|
||||||
_binding = FragmentAppSettingsBinding.inflate(inflater, container, false)
|
_binding = FragmentAppSettingsBinding.inflate(inflater, container, false)
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
childFragmentManager.beginTransaction()
|
childFragmentManager.beginTransaction()
|
||||||
.add(binding.settingsFragment.id, AppSettingsPreferenceFragment()).commit();
|
.add(binding.settingsFragment.id, AppSettingsPreferenceFragment()).commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
_binding = null
|
_binding = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import androidx.preference.PreferenceFragmentCompat
|
|||||||
import su.xash.engine.R
|
import su.xash.engine.R
|
||||||
|
|
||||||
class AppSettingsPreferenceFragment() : PreferenceFragmentCompat() {
|
class AppSettingsPreferenceFragment() : PreferenceFragmentCompat() {
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
preferenceManager.sharedPreferencesName = "app_preferences";
|
preferenceManager.sharedPreferencesName = "app_preferences";
|
||||||
setPreferencesFromResource(R.xml.app_preferences, rootKey);
|
setPreferencesFromResource(R.xml.app_preferences, rootKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,52 +6,59 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
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.databinding.FragmentGameSettingsBinding
|
||||||
import su.xash.engine.ui.library.LibraryViewModel
|
import su.xash.engine.ui.library.LibraryViewModel
|
||||||
|
|
||||||
class GameSettingsFragment : Fragment() {
|
class GameSettingsFragment : Fragment() {
|
||||||
private var _binding: FragmentGameSettingsBinding? = null
|
private var _binding: FragmentGameSettingsBinding? = null
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
private val libraryViewModel: LibraryViewModel by activityViewModels()
|
private val libraryViewModel: LibraryViewModel by activityViewModels()
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
||||||
): View {
|
): View? {
|
||||||
_binding = FragmentGameSettingsBinding.inflate(inflater, container, false)
|
_binding = FragmentGameSettingsBinding.inflate(inflater, container, false)
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
val game = libraryViewModel.selectedItem.value!!
|
val game = libraryViewModel.selectedItem.value!!
|
||||||
|
|
||||||
binding.gameCard.apply {
|
binding.gameCard.apply {
|
||||||
gameTitle.text = game.title
|
gameTitle.text = game.title
|
||||||
|
|
||||||
if (game.icon != null) {
|
if (game.icon != null) {
|
||||||
gameIcon.setImageBitmap(game.icon)
|
gameIcon.setImageBitmap(game.icon)
|
||||||
} else {
|
} else {
|
||||||
gameIcon.visibility = View.GONE
|
gameIcon.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
if (game.cover != null) {
|
if (game.cover != null) {
|
||||||
gameCover.setImageBitmap(game.cover)
|
gameCover.setImageBitmap(game.cover)
|
||||||
} else {
|
} else {
|
||||||
gameCover.visibility = View.GONE
|
gameCover.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
buttonsContainer.visibility = View.GONE
|
buttonsContainer.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
childFragmentManager.beginTransaction()
|
childFragmentManager.beginTransaction()
|
||||||
.add(binding.settingsFragment.id, GameSettingsPreferenceFragment(game))
|
.add(binding.settingsFragment.id, GameSettingsPreferenceFragment(game))
|
||||||
.commit();
|
.commit();
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroyView() {
|
binding.bottomNavigation.menu.findItem(R.id.action_uninstall).setOnMenuItemClickListener {
|
||||||
super.onDestroyView()
|
libraryViewModel.uninstallGame(game)
|
||||||
_binding = null
|
findNavController().popBackStack()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
_binding = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,39 +8,33 @@ import su.xash.engine.R
|
|||||||
import su.xash.engine.model.Game
|
import su.xash.engine.model.Game
|
||||||
|
|
||||||
class GameSettingsPreferenceFragment(val game: Game) : PreferenceFragmentCompat() {
|
class GameSettingsPreferenceFragment(val game: Game) : PreferenceFragmentCompat() {
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
preferenceManager.sharedPreferencesName = game.basedir.name;
|
preferenceManager.sharedPreferencesName = game.basedir.name;
|
||||||
setPreferencesFromResource(R.xml.game_preferences, rootKey);
|
setPreferencesFromResource(R.xml.game_preferences, rootKey);
|
||||||
|
|
||||||
val packageList = findPreference<ListPreference>("package_name")!!
|
val packageList = findPreference<ListPreference>("package_name")!!
|
||||||
packageList.entries = arrayOf(getString(R.string.app_name))
|
packageList.entries = arrayOf(getString(R.string.app_name))
|
||||||
packageList.entryValues = arrayOf(requireContext().packageName)
|
packageList.entryValues = arrayOf(requireContext().packageName)
|
||||||
|
|
||||||
if (packageList.value == null) {
|
if (packageList.value == null) {
|
||||||
packageList.setValueIndex(0);
|
packageList.setValueIndex(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (game.basedir.name.equals("cstrike", ignoreCase = true)
|
val separatePackages = findPreference<SwitchPreferenceCompat>("separate_libraries")!!
|
||||||
|| game.basedir.name.equals("czero", ignoreCase = true)) {
|
val clientPackage = findPreference<ListPreference>("client_package")!!
|
||||||
val enableYaPBBots = findPreference<SwitchPreferenceCompat>("enable_yapb_bots")!!
|
val serverPackage = findPreference<ListPreference>("server_package")!!
|
||||||
enableYaPBBots.isVisible = true
|
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
|
||||||
|
}
|
||||||
|
|
||||||
val separatePackages = findPreference<SwitchPreferenceCompat>("separate_libraries")!!
|
true
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
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]
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
package su.xash.engine.util;
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
/*
|
|
||||||
Nomedia.kt - ensure that .nomedia file exists in directory
|
|
||||||
Copyright (C) 2025 Alibek Omarov
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
*/
|
|
||||||
package su.xash.engine.util
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
object Nomedia {
|
|
||||||
fun ensureNomedia(directory: File): Boolean {
|
|
||||||
try {
|
|
||||||
// ensure paths exists
|
|
||||||
if (!directory.exists() && !directory.mkdirs()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if it's a directory
|
|
||||||
if (!directory.isDirectory) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
val nomediaFile = File(directory, ".nomedia")
|
|
||||||
|
|
||||||
// if the .nomedia is directory, remove it
|
|
||||||
if (nomediaFile.isDirectory) {
|
|
||||||
// it should be empty to be deleted, but that's good
|
|
||||||
// because this way we're not deleting something important
|
|
||||||
// (who would stash important files in .nomedia directory though...)
|
|
||||||
if (!nomediaFile.delete()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// it returns false if file already exists, which is fine for us
|
|
||||||
nomediaFile.createNewFile()
|
|
||||||
|
|
||||||
return true
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,97 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
5
android/app/src/main/res/drawable/ic_baseline_key_24.xml
Normal file
5
android/app/src/main/res/drawable/ic_baseline_key_24.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<vector android:height="24dp" android:tint="#000000"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M21,10h-8.35C11.83,7.67 9.61,6 7,6c-3.31,0 -6,2.69 -6,6s2.69,6 6,6c2.61,0 4.83,-1.67 5.65,-4H13l2,2l2,-2l2,2l4,-4.04L21,10zM7,15c-1.65,0 -3,-1.35 -3,-3c0,-1.65 1.35,-3 3,-3s3,1.35 3,3C10,13.65 8.65,15 7,15z"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<vector android:height="24dp" android:tint="#000000"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M12,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"/>
|
||||||
|
</vector>
|
||||||
15
android/app/src/main/res/drawable/steam_button_gradient.xml
Normal file
15
android/app/src/main/res/drawable/steam_button_gradient.xml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:state_enabled="true">
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<gradient android:endColor="#2D73FF" android:startColor="#06BFFF" />
|
||||||
|
<corners android:radius="4dp" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<item android:state_enabled="false">
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<gradient android:endColor="#6D6D6D" android:startColor="#8F8F8F" />
|
||||||
|
<corners android:radius="4dp" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</selector>
|
||||||
9
android/app/src/main/res/drawable/steam_icon.xml
Normal file
9
android/app/src/main/res/drawable/steam_icon.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M12.004,2c-5.25,0 -9.556,4.05 -9.964,9.197l5.36,2.216c0.454,-0.31 1.002,-0.492 1.593,-0.492 0.053,0 0.104,0.003 0.157,0.005l2.384,-3.452v-0.049c0,-2.08 1.69,-3.77 3.77,-3.77 2.079,0 3.77,1.692 3.77,3.772s-1.692,3.771 -3.77,3.771h-0.087l-3.397,2.426c0,0.043 0.003,0.088 0.003,0.133 0,1.562 -1.262,2.83 -2.825,2.83 -1.362,0 -2.513,-0.978 -2.775,-2.273l-3.838,-1.589C3.573,18.922 7.427,22 12.005,22c5.522,0 9.998,-4.477 9.998,-10 0,-5.522 -4.477,-10 -9.999,-10zM7.078,16.667c0.218,0.452 0.595,0.832 1.094,1.041 1.081,0.45 2.328,-0.063 2.777,-1.145 0.22,-0.525 0.22,-1.1 0.004,-1.625 -0.215,-0.525 -0.625,-0.934 -1.147,-1.152 -0.52,-0.217 -1.075,-0.208 -1.565,-0.025l1.269,0.525c0.797,0.333 1.174,1.25 0.84,2.046 -0.33,0.797 -1.247,1.175 -2.044,0.843l-1.228,-0.508zM17.818,9.422c0,-1.385 -1.128,-2.512 -2.513,-2.512 -1.387,0 -2.512,1.127 -2.512,2.512 0,1.388 1.125,2.513 2.512,2.513 1.386,0 2.512,-1.125 2.512,-2.513zM15.31,7.53c1.04,0 1.888,0.845 1.888,1.888s-0.847,1.888 -1.888,1.888c-1.044,0 -1.888,-0.845 -1.888,-1.888s0.845,-1.888 1.888,-1.888z"/>
|
||||||
|
</vector>
|
||||||
33
android/app/src/main/res/drawable/steam_logo.xml
Normal file
33
android/app/src/main/res/drawable/steam_logo.xml
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="295.46dp"
|
||||||
|
android:height="90.47dp"
|
||||||
|
android:viewportWidth="295.46"
|
||||||
|
android:viewportHeight="90.47">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="m45.08,1c-23.24,0 -42.28,17.92 -44.08,40.69l23.71,9.8c2.01,-1.37 4.44,-2.18 7.05,-2.18 0.23,0 0.47,0.01 0.7,0.02l10.54,-15.28c0,-0.07 0,-0.14 0,-0.22 0,-9.2 7.48,-16.68 16.68,-16.68 9.2,0 16.68,7.48 16.68,16.68s-7.48,16.68 -16.68,16.68c-0.13,0 -0.25,0 -0.38,-0.01l-15.04,10.73c0.01,0.19 0.01,0.39 0.01,0.59 0,6.91 -5.62,12.52 -12.52,12.52 -6.06,0 -11.13,-4.33 -12.28,-10.06l-16.96,-7.01c5.25,18.57 22.31,32.18 42.56,32.18 24.43,0 44.24,-19.81 44.24,-44.24 0,-24.43 -19.81,-44.24 -44.24,-44.24"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="m28.72,68.12 l-5.43,-2.24c0.96,2.01 2.63,3.68 4.84,4.61 4.78,1.99 10.3,-0.28 12.29,-5.06 0.96,-2.31 0.97,-4.87 0.01,-7.19 -0.95,-2.32 -2.76,-4.13 -5.07,-5.1 -2.3,-0.96 -4.76,-0.92 -6.93,-0.1l5.61,2.32c3.53,1.47 5.2,5.52 3.72,9.05 -1.47,3.53 -5.52,5.2 -9.05,3.72"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="m70.8,33.83c0,-6.13 -4.99,-11.12 -11.12,-11.12 -6.13,0 -11.12,4.99 -11.12,11.12 0,6.13 4.99,11.11 11.12,11.11 6.13,0 11.12,-4.99 11.12,-11.11m-19.45,-0.02c0,-4.61 3.74,-8.35 8.35,-8.35s8.35,3.74 8.35,8.35 -3.74,8.35 -8.35,8.35 -8.35,-3.74 -8.35,-8.35"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="m136.56,31.27 l-2.96,5.21c-2.28,-1.6 -5.38,-2.56 -8.08,-2.56 -3.09,0 -5,1.28 -5,3.57 0,2.78 3.39,3.43 8.44,5.24 5.42,1.92 8.54,4.17 8.54,9.14 0,6.79 -5.34,10.61 -13.02,10.61 -3.74,0 -8.26,-0.97 -11.73,-3.08l2.16,-5.78c2.82,1.49 6.19,2.37 9.2,2.37 4.05,0 5.98,-1.5 5.98,-3.7 0,-2.53 -2.94,-3.29 -7.68,-4.86 -5.4,-1.8 -9.15,-4.17 -9.15,-9.67 0,-6.2 4.96,-9.76 12.1,-9.76 4.98,0 8.98,1.58 11.2,3.26"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="m152.76,61.9v-27.34h-10.13v-5.99h27.21v5.99h-10.1v27.34z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="m197.9,42.05v5.99h-13.36v7.82h15.5v6.04h-22.47v-33.33h22.47v5.97h-15.5v7.51z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="m215.62,55.43 l-2.21,6.47h-7.32l12.49,-33.33h7.03l12.85,33.32h-7.56l-2.25,-6.47h-13.03zM222.07,36.52 L217.51,49.87h9.2z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="m261.22,60.93 l-8.97,-19.3v20.27h-6.68v-33.33h6.67l11.2,24.06 10.8,-24.06h6.73v33.33h-6.68v-20.44l-9.12,19.47z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="m294.46,32.78c0,2.86 -2.15,4.65 -4.61,4.65 -2.47,0 -4.62,-1.78 -4.62,-4.65 0,-2.86 2.15,-4.64 4.62,-4.64 2.46,0 4.61,1.77 4.61,4.64m-8.46,0c0,2.4 1.73,3.9 3.85,3.9 2.11,0 3.83,-1.5 3.83,-3.9 0,-2.4 -1.72,-3.88 -3.83,-3.88 -2.12,0 -3.85,1.5 -3.85,3.88m3.91,-2.37c1.2,0 1.6,0.63 1.6,1.32 0,0.63 -0.37,1.05 -0.82,1.26l1.07,2.01h-0.88l-0.9,-1.78h-0.93v1.78h-0.73v-4.58zM289.05,32.54h0.81c0.53,0 0.84,-0.33 0.84,-0.75 0,-0.42 -0.22,-0.69 -0.84,-0.69h-0.81v1.44z"/>
|
||||||
|
</vector>
|
||||||
@@ -1,36 +1,35 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_height="match_parent"
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
android:fitsSystemWindows="true">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:id="@+id/appBarLayout"
|
android:id="@+id/appBarLayout"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
<androidx.appcompat.widget.Toolbar
|
<androidx.appcompat.widget.Toolbar
|
||||||
android:id="@+id/toolbar"
|
android:id="@+id/toolbar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent" />
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<androidx.fragment.app.FragmentContainerView
|
<androidx.fragment.app.FragmentContainerView
|
||||||
android:id="@+id/fragmentContainerView"
|
android:id="@+id/fragmentContainerView"
|
||||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
app:defaultNavHost="true"
|
app:defaultNavHost="true"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/appBarLayout"
|
app:layout_constraintTop_toBottomOf="@id/appBarLayout"
|
||||||
app:navGraph="@navigation/nav_graph"
|
app:navGraph="@navigation/nav_graph"
|
||||||
tools:layout="@layout/fragment_library" />
|
tools:layout="@layout/fragment_library"/>
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -1,98 +1,111 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
<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:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
style="@style/Widget.Material3.CardView.Filled"
|
style="@style/Widget.Material3.CardView.Filled"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp">
|
android:layout_marginTop="8dp">
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="128dp">
|
android:layout_height="128dp">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/gameCover"
|
android:id="@+id/gameCover"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:adjustViewBounds="true"
|
android:adjustViewBounds="true"
|
||||||
android:scaleType="centerCrop"
|
android:scaleType="centerCrop"
|
||||||
tools:src="@android:mipmap/sym_def_app_icon" />
|
tools:src="@android:mipmap/sym_def_app_icon" />
|
||||||
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
<com.google.android.material.card.MaterialCardView
|
||||||
style="@style/Widget.Material3.CardView.Filled"
|
style="@style/Widget.Material3.CardView.Filled"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="48dp"
|
android:layout_height="48dp"
|
||||||
android:layout_margin="8dp"
|
android:layout_margin="8dp"
|
||||||
app:contentPadding="10dp"
|
app:contentPadding="10dp"
|
||||||
app:layout_constrainedWidth="true"
|
app:layout_constrainedWidth="true"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toStartOf="@id/buttonsContainer"
|
app:layout_constraintEnd_toStartOf="@id/buttonsContainer"
|
||||||
app:layout_constraintHorizontal_bias="0"
|
app:layout_constraintHorizontal_bias="0"
|
||||||
app:layout_constraintStart_toStartOf="parent">
|
app:layout_constraintStart_toStartOf="parent">
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/gameIcon"
|
android:id="@+id/gameIcon"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginRight="8dp"
|
android:layout_marginRight="8dp"
|
||||||
android:adjustViewBounds="true"
|
android:adjustViewBounds="true"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toStartOf="@id/gameTitle"
|
app:layout_constraintEnd_toStartOf="@id/gameTitle"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
tools:src="@android:mipmap/sym_def_app_icon" />
|
tools:src="@android:mipmap/sym_def_app_icon" />
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
<com.google.android.material.textview.MaterialTextView
|
||||||
android:id="@+id/gameTitle"
|
android:id="@+id/gameTitle"
|
||||||
style="@style/TextAppearance.Material3.TitleMedium"
|
style="@style/TextAppearance.Material3.TitleMedium"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
app:layout_constrainedWidth="true"
|
app:layout_constrainedWidth="true"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toEndOf="@id/gameIcon"
|
app:layout_constraintStart_toEndOf="@id/gameIcon"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
tools:text="Game Title" />
|
tools:text="Game Title" />
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</com.google.android.material.card.MaterialCardView>
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
<com.google.android.material.card.MaterialCardView
|
||||||
android:id="@+id/buttonsContainer"
|
android:id="@+id/buttonsContainer"
|
||||||
style="@style/Widget.Material3.CardView.Filled"
|
style="@style/Widget.Material3.CardView.Filled"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginEnd="8dp"
|
android:layout_marginEnd="8dp"
|
||||||
android:layout_marginBottom="8dp"
|
android:layout_marginBottom="8dp"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent">
|
app:layout_constraintEnd_toEndOf="parent">
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||||
android:id="@+id/launchButton"
|
android:id="@+id/progressIndicator"
|
||||||
style="@style/Widget.Material3.Button.IconButton"
|
android:layout_width="wrap_content"
|
||||||
android:layout_width="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:indeterminate="true"
|
||||||
app:icon="@drawable/ic_baseline_play_arrow_24"
|
android:padding="10dp"
|
||||||
app:layout_constraintEnd_toStartOf="@+id/settingsButton" />
|
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
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/settingsButton"
|
android:id="@+id/launchButton"
|
||||||
style="@style/Widget.Material3.Button.IconButton"
|
style="@style/Widget.Material3.Button.IconButton"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
app:icon="@drawable/ic_baseline_settings_24"
|
app:icon="@drawable/ic_baseline_play_arrow_24"
|
||||||
app:layout_constraintEnd_toEndOf="parent" />
|
app:layout_constraintEnd_toStartOf="@+id/settingsButton" />
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
<com.google.android.material.button.MaterialButton
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
android:id="@+id/settingsButton"
|
||||||
</com.google.android.material.card.MaterialCardView>
|
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>
|
||||||
@@ -1,45 +1,30 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
android:layout_width="match_parent"
|
||||||
style="@style/Widget.Material3.CardView.Filled"
|
android:layout_height="wrap_content">
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginHorizontal="8dp"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<!-- <com.google.android.material.textfield.TextInputLayout-->
|
||||||
android:layout_width="match_parent"
|
<!-- android:layout_width="match_parent"-->
|
||||||
android:layout_height="wrap_content"
|
<!-- android:layout_height="wrap_content"-->
|
||||||
android:padding="10dp">
|
<!-- app:layout_constraintTop_toTopOf="parent"-->
|
||||||
|
<!-- app:layout_constraintBottom_toBottomOf="parent">-->
|
||||||
|
|
||||||
<TextView
|
<!-- <com.google.android.material.textfield.TextInputEditText-->
|
||||||
android:id="@android:id/title"
|
<!-- android:layout_width="match_parent"-->
|
||||||
android:layout_width="match_parent"
|
<!-- android:layout_height="wrap_content" />-->
|
||||||
android:layout_height="wrap_content"
|
<!-- </com.google.android.material.textfield.TextInputLayout>-->
|
||||||
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"
|
<TextView
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
android:id="@android:id/title"
|
||||||
tools:text="EditText Preference" />
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
<TextView
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
android:id="@android:id/summary"
|
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"/>
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@android:id/title"
|
|
||||||
tools:text="Summary" />
|
|
||||||
|
|
||||||
<!-- <com.google.android.material.button.MaterialButton-->
|
|
||||||
<!-- style="@style/Widget.Material3.Button.IconButton"-->
|
|
||||||
<!-- android:layout_width="wrap_content"-->
|
|
||||||
<!-- android:layout_height="wrap_content"-->
|
|
||||||
<!-- app:icon="@drawable/baseline_edit_24"-->
|
|
||||||
<!-- app:layout_constraintBottom_toBottomOf="parent"-->
|
|
||||||
<!-- app:layout_constraintEnd_toEndOf="parent"-->
|
|
||||||
<!-- app:layout_constraintTop_toTopOf="parent" />-->
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
|
||||||
|
|
||||||
|
<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>
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_height="match_parent">
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:paddingHorizontal="8dp">
|
||||||
|
|
||||||
<androidx.fragment.app.FragmentContainerView
|
<androidx.fragment.app.FragmentContainerView
|
||||||
android:id="@+id/settingsFragment"
|
android:id="@+id/settingsFragment"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:layout="@android:layout/list_content" />
|
tools:layout="@android:layout/list_content" />
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -1,32 +1,39 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<include
|
<include
|
||||||
android:id="@+id/gameCard"
|
android:id="@+id/gameCard"
|
||||||
layout="@layout/card_game"
|
layout="@layout/card_game"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="8dp"
|
android:layout_margin="8dp"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<androidx.fragment.app.FragmentContainerView
|
<com.google.android.material.card.MaterialCardView
|
||||||
android:id="@+id/settingsFragment"
|
style="@style/Widget.Material3.CardView.Filled"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:layout_constraintTop_toBottomOf="@id/gameCard"
|
android:layout_margin="8dp"
|
||||||
tools:layout="@android:layout/list_content">
|
app:contentPadding="16dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/gameCard">
|
||||||
|
|
||||||
</androidx.fragment.app.FragmentContainerView>
|
<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.bottomnavigation.BottomNavigationView-->
|
</com.google.android.material.card.MaterialCardView>
|
||||||
<!-- android:id="@+id/bottomNavigation"-->
|
|
||||||
<!-- style="@style/Widget.MaterialComponents.BottomNavigationView.Colored"-->
|
<com.google.android.material.bottomnavigation.BottomNavigationView
|
||||||
<!-- android:layout_width="match_parent"-->
|
android:id="@+id/bottomNavigation"
|
||||||
<!-- android:layout_height="wrap_content"-->
|
style="@style/Widget.MaterialComponents.BottomNavigationView.Colored"
|
||||||
<!-- app:layout_constraintBottom_toBottomOf="parent"-->
|
android:layout_width="match_parent"
|
||||||
<!-- app:menu="@menu/menu_game_settings" />-->
|
android:layout_height="wrap_content"
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:menu="@menu/menu_game_settings" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -1,23 +1,23 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/swipeRefresh"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:id="@+id/swipeRefresh">
|
||||||
|
|
||||||
<RelativeLayout
|
<RelativeLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/gamesList"
|
android:id="@+id/gamesList"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:clipToPadding="false"
|
app:layoutManager="LinearLayoutManager"
|
||||||
android:paddingHorizontal="8dp"
|
android:clipToPadding="false"
|
||||||
app:layoutManager="LinearLayoutManager"
|
android:paddingHorizontal="8dp"
|
||||||
tools:listitem="@layout/card_game" />
|
tools:listitem="@layout/card_game" />
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||||
11
android/app/src/main/res/layout/fragment_setup.xml
Normal file
11
android/app/src/main/res/layout/fragment_setup.xml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.viewpager2.widget.ViewPager2
|
||||||
|
android:id="@+id/viewPager"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
58
android/app/src/main/res/layout/fragment_steam_login.xml
Normal file
58
android/app/src/main/res/layout/fragment_steam_login.xml
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/steam_background"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="vertical"
|
||||||
|
tools:context=".fragment.SteamLoginFragment">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/imageView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:src="@drawable/steam_logo"
|
||||||
|
app:tint="#C5C3C0" />
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="320dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
app:startIconDrawable="@drawable/ic_baseline_person_24">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/steamLogin"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/username"
|
||||||
|
android:inputType="textEmailAddress" />
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="320dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
|
||||||
|
app:startIconDrawable="@drawable/ic_baseline_key_24">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/steamPassword"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/password"
|
||||||
|
android:inputType="textPassword" />
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/loginButton"
|
||||||
|
style="@style/App.Theme.SteamButton"
|
||||||
|
android:layout_width="240dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="@string/login" />
|
||||||
|
</LinearLayout>
|
||||||
@@ -1,20 +1,19 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
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
|
<TextView
|
||||||
android:id="@android:id/title"
|
android:id="@android:id/summary"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"
|
app:layout_constraintTop_toBottomOf="@android:id/title"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"/>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
<TextView
|
|
||||||
android:id="@android:id/summary"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
|
|
||||||
app:layout_constraintTop_toBottomOf="@android:id/title" />
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
51
android/app/src/main/res/layout/page_location.xml
Normal file
51
android/app/src/main/res/layout/page_location.xml
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/pagePic"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginTop="64dp"
|
||||||
|
android:layout_marginBottom="32dp"
|
||||||
|
android:src="@drawable/ic_baseline_folder_open_24"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/pageTitle"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/pageTitle"
|
||||||
|
style="@style/TextAppearance.Material3.TitleLarge"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:paddingHorizontal="16dp"
|
||||||
|
android:text="@string/setup_location_title"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/pageMessage"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/pagePic" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/pageMessage"
|
||||||
|
style="@style/TextAppearance.Material3.TitleMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:paddingHorizontal="16dp"
|
||||||
|
android:text="@string/setup_location_message"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/pageButton"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/pageTitle" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/pageButton"
|
||||||
|
android:layout_width="240dp"
|
||||||
|
android:layout_marginBottom="128dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/ok"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/pageMessage" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
51
android/app/src/main/res/layout/page_welcome.xml
Normal file
51
android/app/src/main/res/layout/page_welcome.xml
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/pagePic"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginTop="64dp"
|
||||||
|
android:layout_marginBottom="32dp"
|
||||||
|
android:src="@mipmap/ic_launcher"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/pageTitle"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/pageTitle"
|
||||||
|
style="@style/TextAppearance.Material3.TitleLarge"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:paddingHorizontal="16dp"
|
||||||
|
android:text="@string/setup_welcome_title"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/pageMessage"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/pagePic" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/pageMessage"
|
||||||
|
style="@style/TextAppearance.Material3.TitleMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:paddingHorizontal="16dp"
|
||||||
|
android:text="@string/setup_welcome_message"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/pageButton"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/pageTitle" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/pageButton"
|
||||||
|
android:layout_width="240dp"
|
||||||
|
android:layout_marginBottom="128dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/next"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/pageMessage" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
33
android/app/src/main/res/layout/steam_guard_dialog.xml
Normal file
33
android/app/src/main/res/layout/steam_guard_dialog.xml
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="32dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/steam_guard_code"
|
||||||
|
android:textAppearance="@style/TextAppearance.Material3.TitleLarge"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/textInputLayout"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/textView">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/steamCode"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:hint="@string/steam_guard_code" />
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -1,37 +1,23 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
android:layout_width="match_parent"
|
||||||
style="@style/Widget.Material3.CardView.Filled"
|
android:layout_height="wrap_content">
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginHorizontal="8dp"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:id="@android:id/title"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:padding="10dp">
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
<TextView
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
android:id="@android:id/title"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
android:layout_width="wrap_content"
|
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"/>
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
tools:text="Switch preference" />
|
|
||||||
|
|
||||||
<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>
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
|
||||||
|
|
||||||
|
<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>
|
||||||
@@ -1,9 +1,14 @@
|
|||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_dedicated"
|
android:id="@+id/action_dedicated"
|
||||||
android:icon="@drawable/ic_baseline_terminal_24"
|
android:icon="@drawable/ic_baseline_terminal_24"
|
||||||
android:title="@string/dedicated_server"
|
android:title="@string/dedicated_server"
|
||||||
android:visible="false"
|
app:showAsAction="always|withText"
|
||||||
app:showAsAction="always|withText" />
|
android:visible="false"/>
|
||||||
</menu>
|
<item
|
||||||
|
android:id="@+id/action_uninstall"
|
||||||
|
android:icon="@drawable/ic_baseline_delete_24"
|
||||||
|
android:title="@string/uninstall"
|
||||||
|
app:showAsAction="always|withText" />
|
||||||
|
</menu>
|
||||||
@@ -1,8 +1,19 @@
|
|||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_settings"
|
android:id="@+id/action_browse"
|
||||||
android:icon="@drawable/ic_baseline_settings_24"
|
android:icon="@drawable/ic_baseline_folder_open_24"
|
||||||
android:title="@string/app_settings"
|
android:title="@string/browse"
|
||||||
app:showAsAction="always|withText" />
|
app:showAsAction="always|withText" />
|
||||||
</menu>
|
<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>
|
||||||
@@ -1,26 +1,38 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
|
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:id="@+id/nav_graph"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:label="@string/library"
|
android:id="@+id/nav_graph"
|
||||||
app:startDestination="@id/libraryFragment">
|
android:label="@string/library"
|
||||||
<fragment
|
app:startDestination="@id/libraryFragment">
|
||||||
android:id="@+id/libraryFragment"
|
<fragment
|
||||||
android:name="su.xash.engine.ui.library.LibraryFragment"
|
android:id="@+id/libraryFragment"
|
||||||
android:label="@string/library">
|
android:name="su.xash.engine.ui.library.LibraryFragment"
|
||||||
<action
|
android:label="@string/library">
|
||||||
android:id="@+id/action_libraryFragment_to_gameSettingsFragment"
|
<action
|
||||||
app:destination="@id/gameSettingsFragment" />
|
android:id="@+id/action_libraryFragment_to_setupFragment"
|
||||||
<action
|
app:destination="@id/setupFragment" />
|
||||||
android:id="@+id/action_libraryFragment_to_appSettingsFragment"
|
<action
|
||||||
app:destination="@id/appSettingsFragment" />
|
android:id="@+id/action_libraryFragment_to_gameSettingsFragment"
|
||||||
</fragment>
|
app:destination="@id/gameSettingsFragment" />
|
||||||
<fragment
|
<action
|
||||||
android:id="@+id/gameSettingsFragment"
|
android:id="@+id/action_libraryFragment_to_appSettingsFragment"
|
||||||
android:name="su.xash.engine.ui.settings.GameSettingsFragment"
|
app:destination="@id/appSettingsFragment" />
|
||||||
android:label="@string/game_settings" />
|
</fragment>
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/appSettingsFragment"
|
android:id="@+id/setupFragment"
|
||||||
android:name="su.xash.engine.ui.settings.AppSettingsFragment"
|
android:name="su.xash.engine.ui.setup.SetupFragment"
|
||||||
android:label="@string/app_settings" />
|
android:label="@string/setup" >
|
||||||
</navigation>
|
<action
|
||||||
|
android:id="@+id/action_setupFragment_to_libraryFragment"
|
||||||
|
app:destination="@id/libraryFragment" />
|
||||||
|
</fragment>
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/gameSettingsFragment"
|
||||||
|
android:name="su.xash.engine.ui.settings.GameSettingsFragment"
|
||||||
|
android:label="@string/game_settings" />
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/appSettingsFragment"
|
||||||
|
android:name="su.xash.engine.ui.settings.AppSettingsFragment"
|
||||||
|
android:label="@string/app_settings" />
|
||||||
|
</navigation>
|
||||||
@@ -1,21 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name" translatable="false">Xash3D FWGS</string>
|
<string name="ok">Ok</string>
|
||||||
<string name="library">Librería</string>
|
<string name="cancel">Cancelar</string>
|
||||||
<string name="dedicated_server">Dedicado</string>
|
|
||||||
<string name="app_settings">Ajustes</string>
|
|
||||||
<string name="game_settings">Ajustes del juego</string>
|
|
||||||
<string name="game_settings_command_line">Argumentos de la línea de comandos</string>
|
|
||||||
<string name="game_settings_volume_buttons">Usar botones de volumen en el juego</string>
|
|
||||||
<string name="preferences_package_name">Paquete de librerías</string>
|
|
||||||
<string name="preferences_separate_libraries">Librerías de paquetes separados</string>
|
|
||||||
<string name="preferences_client_package">Paquete de cliente</string>
|
|
||||||
<string name="preferences_server_package">Paquete de servidor</string>
|
|
||||||
<string name="preferences_use_icons">Usar iconos en lugar de fondos</string>
|
|
||||||
<string name="game_data_location">Ubicación de los datos del juego</string>
|
|
||||||
<string name="file_access_required">Se requiere acceso a todos los archivos</string>
|
|
||||||
<string name="file_access_message">Se requiere acceso a todos los archivos para que la aplicación funcione correctamente.</string>
|
|
||||||
<string name="select_current_directory">Seleccionar directorio actual</string>
|
|
||||||
<string name="external_storage_required">Se requiere acceso al almacenamiento externo</string>
|
|
||||||
<string name="external_storage_message">Se requiere acceso al almacenamiento externo para que la aplicación funcione correctamente.</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<string name="app_name" translatable="false">Xash3D FWGS</string>
|
|
||||||
<string name="library">Bibliothèque</string>
|
|
||||||
<string name="dedicated_server">Dédié</string>
|
|
||||||
<string name="app_settings">Paramètres</string>
|
|
||||||
<string name="game_settings">Paramètres du jeu</string>
|
|
||||||
<string name="game_settings_command_line">Arguments de ligne de commande</string>
|
|
||||||
<string name="game_settings_volume_buttons">Utiliser les boutons de volume dans le jeu</string>
|
|
||||||
<string name="game_settings_yapb_bots">Activer les bots YaPB (Uniquement pour CS)</string>
|
|
||||||
<string name="preferences_package_name">Paquet de bibliothèques</string>
|
|
||||||
<string name="preferences_separate_libraries">Bibliothèques de paquets séparés</string>
|
|
||||||
<string name="preferences_client_package">Paquet client</string>
|
|
||||||
<string name="preferences_server_package">Paquet serveur</string>
|
|
||||||
<string name="preferences_use_icons">Utiliser des icônes au lieu d\'arrière-plans</string>
|
|
||||||
<string name="game_data_location">Emplacement des données du jeu</string>
|
|
||||||
<string name="file_access_required">L\'accès à tous les fichiers est requis</string>
|
|
||||||
<string name="file_access_message">L\'accès à tous les fichiers est requis pour que l\'application fonctionne correctement.</string>
|
|
||||||
<string name="select_current_directory">Sélectionner le répertoire actuel</string>
|
|
||||||
<string name="external_storage_required">L\'accès au stockage externe est requis</string>
|
|
||||||
<string name="external_storage_message">L\'accès au stockage externe est requis pour que l\'application fonctionne correctement.</string>
|
|
||||||
<string name="open_settings">Ouvrir les paramètres</string>
|
|
||||||
<string name="done_check_permissions">Terminé ! Vérifier les permissions</string>
|
|
||||||
<string name="game_apk_required">Nécessite un APK de bibliothèques de jeu</string>
|
|
||||||
<string name="game_apk_message">Pour lancer ce jeu, vous devez installer un APK additionnel</string>
|
|
||||||
<string name="game_apk_install">Télécharger</string>
|
|
||||||
</resources>
|
|
||||||
5
android/app/src/main/res/values-it/strings.xml
Normal file
5
android/app/src/main/res/values-it/strings.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="cancel">Annulla</string>
|
||||||
|
<string name="ok">Ok</string>
|
||||||
|
</resources>
|
||||||
@@ -1,20 +1,33 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="library">Biblioteca</string>
|
<string name="ok">Ok</string>
|
||||||
<string name="dedicated_server">Dedicado</string>
|
<string name="cancel">Cancelar</string>
|
||||||
<string name="app_settings">Configurações</string>
|
<string name="next">Próximo</string>
|
||||||
<string name="game_settings">Configurações do jogo</string>
|
<string name="browse">Navegar</string>
|
||||||
<string name="game_settings_command_line">Argumentos de linha de comando</string>
|
<string name="install">Instalar</string>
|
||||||
<string name="game_settings_volume_buttons">Usar botões de volume no jogo</string>
|
<string name="library">Biblioteca</string>
|
||||||
<string name="preferences_package_name">Pacote de bibliotecas</string>
|
<string name="steam_guard_code">Código do Steam Guard</string>
|
||||||
<string name="preferences_separate_libraries">Bibliotecas de pacotes separados</string>
|
<string name="steam_login">Logar com Steam</string>
|
||||||
<string name="preferences_client_package">Pacote do cliente</string>
|
<string name="login">Logar</string>
|
||||||
<string name="preferences_server_package">Pacote do servidor</string>
|
<string name="username">Usuário</string>
|
||||||
<string name="preferences_use_icons">Usar ícones em vez de fundos</string>
|
<string name="password">Senha</string>
|
||||||
<string name="game_data_location">Local dos dados do jogo</string>
|
<string name="uninstall">Desinstalar</string>
|
||||||
<string name="file_access_required">Acesso a todos os arquivos necessário</string>
|
<string name="dedicated_server">Dedicado</string>
|
||||||
<string name="file_access_message">É necessário acesso a todos os arquivos para que o aplicativo funcione corretamente.</string>
|
<string name="error">Erro!</string>
|
||||||
<string name="select_current_directory">Selecionar diretório atual</string>
|
<string name="setup">Configurar</string>
|
||||||
<string name="external_storage_required">Acesso ao armazenamento externo necessário</string>
|
<string name="app_settings">Configurações</string>
|
||||||
<string name="external_storage_message">É necessário acesso ao armazenamento externo para que o aplicativo funcione corretamente.</string>
|
<string name="game_settings">Configurações do jogo</string>
|
||||||
|
<string name="game_settings_command_line">Argumentos de linha de comando</string>
|
||||||
|
<string name="game_settings_volume_buttons">Usar botões de volume no jogo</string>
|
||||||
|
<string name="setup_welcome_title">Bem vindo ao Xash3D FWGS!</string>
|
||||||
|
<string name="setup_welcome_message">Agora, nós vamos guiá-lo durante o processo de instalação.</string>
|
||||||
|
<string name="setup_location_title">Jogos</string>
|
||||||
|
<string name="setup_location_message">Selecione a pasta onde seus jogos estão localizados.</string>
|
||||||
|
<string name="setup_location_empty">A pasta selecionada não contem nenhum jogo.</string>
|
||||||
|
<string name="setup_location_invalid">A pasta selecionada é invalida.</string>
|
||||||
|
<string name="preferences_package_name">Bibliotecas de pacotes</string>
|
||||||
|
<string name="preferences_separate_libraries">Bibliotecas de pacotes separados</string>
|
||||||
|
<string name="preferences_client_package">Pacotes de Cliente</string>
|
||||||
|
<string name="preferences_server_package">Pacotes de Servidor</string>
|
||||||
|
<string name="preferences_use_icons">Usar ícones ao inves de imagens de fundo</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,26 +1,33 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="library">Библиотека</string>
|
<string name="cancel">Отмена</string>
|
||||||
<string name="dedicated_server">Выделенный</string>
|
<string name="ok">ОК</string>
|
||||||
<string name="app_settings">Настройки</string>
|
<string name="next">Далее</string>
|
||||||
<string name="game_settings">Настройки игры</string>
|
<string name="browse">Обзор</string>
|
||||||
<string name="game_settings_command_line">Аргументы командной строки</string>
|
<string name="install">Установить</string>
|
||||||
<string name="game_settings_volume_buttons">Использовать кнопки громкости</string>
|
<string name="library">Библиотека</string>
|
||||||
<string name="game_settings_yapb_bots">Включить YaPB ботов (только CS)</string>
|
<string name="steam_guard_code">Код Steam Guard</string>
|
||||||
<string name="preferences_package_name">Пакет библиотек</string>
|
<string name="steam_login">Логин Steam</string>
|
||||||
<string name="preferences_separate_libraries">Библиотеки из отдельных пакетов</string>
|
<string name="login">Логин</string>
|
||||||
<string name="preferences_client_package">Пакет клиента</string>
|
<string name="username">Имя пользователя</string>
|
||||||
<string name="preferences_server_package">Пакет сервера</string>
|
<string name="password">Пароль</string>
|
||||||
<string name="preferences_use_icons">Использовать иконки вместо фона</string>
|
<string name="uninstall">Удалить</string>
|
||||||
<string name="game_data_location">Расположение игровых данных</string>
|
<string name="dedicated_server">Выделенный</string>
|
||||||
<string name="file_access_required">Требуется доступ ко всем файлам</string>
|
<string name="error">Ошибка!</string>
|
||||||
<string name="file_access_message">Для правильной работы приложения требуется доступ ко всем файлам.</string>
|
<string name="setup">Установка</string>
|
||||||
<string name="select_current_directory">Выбрать текущую директорию</string>
|
<string name="app_settings">Настройки</string>
|
||||||
<string name="external_storage_required">Требуется доступ к внешнему хранилищу</string>
|
<string name="game_settings">Настройки игры</string>
|
||||||
<string name="external_storage_message">Для правильной работы приложения требуется доступ к внешнему хранилищу.</string>
|
<string name="game_settings_command_line">Аргументы командной строки</string>
|
||||||
<string name="open_settings">Открыть настройки</string>
|
<string name="game_settings_volume_buttons">Использовать кнопки громкости</string>
|
||||||
<string name="done_check_permissions">Готово! Проверить разрешения</string>
|
<string name="setup_welcome_title">Добро пожаловать в Xash3D FWGS!</string>
|
||||||
<string name="game_apk_required">Требуется APK с игровыми библиотеками</string>
|
<string name="setup_welcome_message">Теперь мы ознакомим вас с процессом установки.</string>
|
||||||
<string name="game_apk_message">Чтобы запустить эту игру, нужно установить дополнительный APK</string>
|
<string name="setup_location_title">Игры</string>
|
||||||
<string name="game_apk_install">Скачать</string>
|
<string name="setup_location_message">Выберите папку, в которой находятся ваши игры.</string>
|
||||||
</resources>
|
<string name="setup_location_empty">Выбранный каталог не содержит ни одной игры.</string>
|
||||||
|
<string name="setup_location_invalid">Выбранный каталог недопустим.</string>
|
||||||
|
<string name="preferences_package_name">Пакет библиотек</string>
|
||||||
|
<string name="preferences_separate_libraries">Библиотеки из отдельных пакетов</string>
|
||||||
|
<string name="preferences_client_package">Пакет клиента</string>
|
||||||
|
<string name="preferences_server_package">Пакет сервера</string>
|
||||||
|
<string name="preferences_use_icons">Использовать иконки вместо фона</string>
|
||||||
|
</resources>
|
||||||
5
android/app/src/main/res/values-tr/strings.xml
Normal file
5
android/app/src/main/res/values-tr/strings.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="ok">Tamam</string>
|
||||||
|
<string name="cancel">İptal</string>
|
||||||
|
</resources>
|
||||||
5
android/app/src/main/res/values-uk/strings.xml
Normal file
5
android/app/src/main/res/values-uk/strings.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="ok">Ок</string>
|
||||||
|
<string name="cancel">Скасувати</string>
|
||||||
|
</resources>
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<string name="library">库</string>
|
|
||||||
<string name="dedicated_server">专用服务器</string>
|
|
||||||
<string name="app_settings">设置</string>
|
|
||||||
<string name="game_settings">游戏设置</string>
|
|
||||||
<string name="game_settings_command_line">命令行参数</string>
|
|
||||||
<string name="game_settings_volume_buttons">在游戏中使用音量按钮</string>
|
|
||||||
<string name="game_settings_yapb_bots">启用yapb机器人(仅限CS)</string>
|
|
||||||
<string name="preferences_package_name">数据包</string>
|
|
||||||
<string name="preferences_separate_libraries">来自独立包的库</string>
|
|
||||||
<string name="preferences_client_package">客户端包</string>
|
|
||||||
<string name="preferences_server_package">服务器包</string>
|
|
||||||
<string name="preferences_use_icons">使用图标代替背景</string>
|
|
||||||
<string name="game_data_location">游戏数据包路径位置</string>
|
|
||||||
<string name="file_access_required">需要所有文件访问权限</string>
|
|
||||||
<string name="file_access_message">应用程序需要所有文件访问权限才能正常运行</string>
|
|
||||||
<string name="select_current_directory">选择当前目录</string>
|
|
||||||
<string name="external_storage_required">需要外部存储访问权限</string>
|
|
||||||
<string name="external_storage_message">应用程序需要外部存储访问权限才能正常运行</string>
|
|
||||||
<string name="open_settings">打开设置</string>
|
|
||||||
<string name="done_check_permissions">准备好了!检查权限</string>
|
|
||||||
<string name="game_apk_required">需要带有游戏库的APK</string>
|
|
||||||
<string name="game_apk_message">要运行此游戏,您需要安装额外的APK</string>
|
|
||||||
<string name="game_apk_install">下载</string>
|
|
||||||
</resources>
|
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<color name="valve_red">#F74843</color>
|
<color name="valve_red">#F74843</color>
|
||||||
<color name="hl_orange">#FB7E14</color>
|
<color name="steam_grey">#C5C3C0</color>
|
||||||
<color name="black">#000000</color>
|
<color name="steam_background">#181A21</color>
|
||||||
<color name="hl_dark_grey">#151515</color>
|
<color name="steam_dark_grey">#32353C</color>
|
||||||
<color name="hl_grey">#292929</color>
|
<color name="hl_orange">#FB7E14</color>
|
||||||
|
<color name="black">#000000</color>
|
||||||
|
<color name="hl_dark_grey">#151515</color>
|
||||||
|
<color name="hl_grey">#292929</color>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<color name="ic_launcher_background">#F74843</color>
|
<color name="ic_launcher_background">#F74843</color>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,27 +1,37 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name" translatable="false">Xash3D FWGS</string>
|
<string name="app_name" translatable="false">Xash3D FWGS</string>
|
||||||
<string name="library">Library</string>
|
<string name="authority" translatable="false">su.xash.engine.documents</string>
|
||||||
<string name="dedicated_server">Dedicated</string>
|
<string name="cancel">Cancel</string>
|
||||||
<string name="app_settings">Settings</string>
|
<string name="ok">OK</string>
|
||||||
<string name="game_settings">Game Settings</string>
|
<string name="next">Next</string>
|
||||||
<string name="game_settings_command_line">Command-line arguments</string>
|
<string name="browse">Browse</string>
|
||||||
<string name="game_settings_volume_buttons">Use volume buttons in-game</string>
|
<string name="install">Install</string>
|
||||||
<string name="game_settings_yapb_bots">Enable YaPB bots (CS only)</string>
|
<string name="library">Library</string>
|
||||||
<string name="preferences_package_name">Libraries package</string>
|
<string name="steam_guard_code">Steam Guard Code</string>
|
||||||
<string name="preferences_separate_libraries">Libraries from separate packages</string>
|
<string name="steam_login">Steam Login</string>
|
||||||
<string name="preferences_client_package">Client package</string>
|
<string name="login">Login</string>
|
||||||
<string name="preferences_server_package">Server package</string>
|
<string name="username">Username</string>
|
||||||
<string name="preferences_use_icons">Use icons instead of backgrounds</string>
|
<string name="password">Password</string>
|
||||||
<string name="game_data_location">Game data location</string>
|
<string name="steam" translatable="false">Steam</string>
|
||||||
<string name="file_access_required">All-files access required</string>
|
<string name="uninstall">Uninstall</string>
|
||||||
<string name="file_access_message">All-files access is required for the app to function properly.</string>
|
<string name="dedicated_server">Dedicated</string>
|
||||||
<string name="select_current_directory">Select current directory</string>
|
<string name="error">Error!</string>
|
||||||
<string name="external_storage_required">External storage access required</string>
|
<string name="setup">Setup</string>
|
||||||
<string name="external_storage_message">External storage access is required for the app to function properly.</string>
|
<string name="app_settings">Settings</string>
|
||||||
<string name="open_settings">Open settings</string>
|
<string name="game_settings">Game Settings</string>
|
||||||
<string name="done_check_permissions">Done! Check permissions</string>
|
<string name="game_settings_command_line">Command-line arguments</string>
|
||||||
<string name="game_apk_required">Game code APK required</string>
|
<string name="game_settings_volume_buttons">Use volume buttons in-game</string>
|
||||||
<string name="game_apk_message">To launch this game, you need additional APK installed</string>
|
<string name="setup_welcome_title">Welcome to Xash3D FWGS!</string>
|
||||||
<string name="game_apk_install">Download</string>
|
<string name="setup_welcome_message">Now, we will guide you through the installation process.</string>
|
||||||
|
<string name="setup_location_title">Games</string>
|
||||||
|
<string name="setup_location_message">Select the folder, where your games are located.</string>
|
||||||
|
<string name="setup_location_empty">Selected directory doesn\'t contain any games.</string>
|
||||||
|
<string name="setup_location_invalid">Selected directory is invalid.</string>
|
||||||
|
<string name="preferences_package_name">Libraries package</string>
|
||||||
|
<string name="preferences_separate_libraries">Libraries from separate packages</string>
|
||||||
|
<string name="preferences_client_package">Client package</string>
|
||||||
|
<string name="preferences_server_package">Server package</string>
|
||||||
|
<string name="preferences_use_icons">Use icons instead of backgrounds</string>
|
||||||
|
<string name="library_fragment_no_file_manager">File manager is missing</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user