mirror of
https://github.com/FWGS/xash3d-fwgs.git
synced 2026-08-06 04:05:11 +08:00
Compare commits
145 Commits
continuous
...
continuous
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5d6f66f61 | ||
|
|
a106924c7f | ||
|
|
11ee883c84 | ||
|
|
881f0c5ca7 | ||
|
|
ac0c8a2210 | ||
|
|
dba090e0da | ||
|
|
262f32b002 | ||
|
|
843f6297ed | ||
|
|
d1510345f1 | ||
|
|
36307bb386 | ||
|
|
52953a7647 | ||
|
|
ebeff144f9 | ||
|
|
c8cc953d48 | ||
|
|
d6bf06e997 | ||
|
|
568b7abc5f | ||
|
|
1b7f50d3c6 | ||
|
|
c8e1c2673f | ||
|
|
c2df265de7 | ||
|
|
e9e885534a | ||
|
|
95cc6c4445 | ||
|
|
086cb6aab8 | ||
|
|
e6a44b70e0 | ||
|
|
fdb59e990e | ||
|
|
eb79bc6b40 | ||
|
|
e907e9fc95 | ||
|
|
fc356c88b3 | ||
|
|
bc672e5aca | ||
|
|
2635fd9739 | ||
|
|
653fe51fd4 | ||
|
|
980d9fcaab | ||
|
|
72ef5cffca | ||
|
|
a707f5933d | ||
|
|
b9b0ff5769 | ||
|
|
982a216df4 | ||
|
|
7d87d65a3f | ||
|
|
6e4e048ef8 | ||
|
|
5b6b372cfe | ||
|
|
2f31cf315c | ||
|
|
910cce56ca | ||
|
|
68dba8b4e0 | ||
|
|
dbfeacfecc | ||
|
|
0bdd4648bc | ||
|
|
ec8059173d | ||
|
|
f91e5a7647 | ||
|
|
7942d9b1bc | ||
|
|
7abf0c819f | ||
|
|
85eb82a370 | ||
|
|
90e25cc623 | ||
|
|
fca4152d5a | ||
|
|
2ccef63754 | ||
|
|
87615784b9 | ||
|
|
d3b01d313b | ||
|
|
a3897f402a | ||
|
|
21d84ee692 | ||
|
|
e30ce9c150 | ||
|
|
22391eba1b | ||
|
|
800725453a | ||
|
|
22ecec095e | ||
|
|
8545ed5ccb | ||
|
|
a4fdf19ad8 | ||
|
|
75e4a21d7a | ||
|
|
1099344a7d | ||
|
|
ab1eaed0a5 | ||
|
|
76a01d0855 | ||
|
|
b00679ee30 | ||
|
|
b3bb2fc16f | ||
|
|
86a2ea29e4 | ||
|
|
d4f8543556 | ||
|
|
e850728016 | ||
|
|
11877f4c96 | ||
|
|
2ba5030b62 | ||
|
|
06b14800e1 | ||
|
|
bb7c85fd22 | ||
|
|
37ce62fa6a | ||
|
|
e1b29e2da3 | ||
|
|
b60afd73e9 | ||
|
|
84447a5292 | ||
|
|
fe4ea4aabf | ||
|
|
878822290c | ||
|
|
04ca5d7cb0 | ||
|
|
d46ed40f33 | ||
|
|
82e031205c | ||
|
|
6f5f04a29d | ||
|
|
f9ed578bf1 | ||
|
|
86c84c7de7 | ||
|
|
489c1c9dd5 | ||
|
|
1df6a040ec | ||
|
|
82c2ca53fb | ||
|
|
3ddd1e2448 | ||
|
|
371d619685 | ||
|
|
480f29e96e | ||
|
|
95548053eb | ||
|
|
0daf2c2b4d | ||
|
|
e2d5bea327 | ||
|
|
306a5812a9 | ||
|
|
a3dba64513 | ||
|
|
491ba9bc7d | ||
|
|
1c462df8a2 | ||
|
|
ed975976f8 | ||
|
|
bbf8ab378d | ||
|
|
5383e36af5 | ||
|
|
897fb98b93 | ||
|
|
96e88adfcd | ||
|
|
4dde4047f1 | ||
|
|
905161529f | ||
|
|
044523434d | ||
|
|
da4c4bb76a | ||
|
|
95b3271d68 | ||
|
|
3361a5d760 | ||
|
|
b725d2ece0 | ||
|
|
66bee8b468 | ||
|
|
5e664dc3e9 | ||
|
|
bfdfeb3dc0 | ||
|
|
49248f5e14 | ||
|
|
d03ea4c10a | ||
|
|
780dbd6183 | ||
|
|
50da79f1e0 | ||
|
|
62201900cf | ||
|
|
ae233ac491 | ||
|
|
dd1cb04cb7 | ||
|
|
74869c8f5d | ||
|
|
dd712a0df9 | ||
|
|
bfdb62d31e | ||
|
|
ae7b9eb28d | ||
|
|
c63e13c8f4 | ||
|
|
d3e1407675 | ||
|
|
e2e4728c7c | ||
|
|
dd3583d11a | ||
|
|
e6924844da | ||
|
|
a49db03561 | ||
|
|
84b7b5803b | ||
|
|
c224d39ba7 | ||
|
|
9b24b08df1 | ||
|
|
a37b0e58f1 | ||
|
|
afca422e35 | ||
|
|
3440ddf443 | ||
|
|
6730ed9555 | ||
|
|
fdd3a44fa0 | ||
|
|
241c926076 | ||
|
|
148a9a5a8c | ||
|
|
513984d7fd | ||
|
|
1009795d5c | ||
|
|
e3d2da74db | ||
|
|
ff3a57ae8a | ||
|
|
271a6e2210 |
32
.cirrus.yml
32
.cirrus.yml
@@ -1,32 +0,0 @@
|
|||||||
task:
|
|
||||||
name: freebsd-14-amd64
|
|
||||||
freebsd_instance:
|
|
||||||
image_family: freebsd-14-3
|
|
||||||
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
|
|
||||||
|
|
||||||
task:
|
|
||||||
name: freebsd-15-amd64
|
|
||||||
freebsd_instance:
|
|
||||||
image_family: freebsd-15-0-amd64-zfs
|
|
||||||
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
|
|
||||||
|
|
||||||
#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
|
|
||||||
2
3rdparty/extras/xash-extras
vendored
2
3rdparty/extras/xash-extras
vendored
Submodule 3rdparty/extras/xash-extras updated: 0c83a63d00...6d2457f66f
14
3rdparty/libbacktrace/wscript
vendored
14
3rdparty/libbacktrace/wscript
vendored
@@ -20,10 +20,10 @@ int main (void) {
|
|||||||
}'''
|
}'''
|
||||||
|
|
||||||
FRAGMENT_GETPAGESIZE='''#include <unistd.h>
|
FRAGMENT_GETPAGESIZE='''#include <unistd.h>
|
||||||
int main(void) { return getpagesize() }'''
|
int main(void) { return getpagesize(); }'''
|
||||||
|
|
||||||
FRAGMENT_GETEXECNAME='''#include <stdlib.h>
|
FRAGMENT_GETEXECNAME='''#include <stdlib.h>
|
||||||
int main(void) { return getexecname() != 0 }'''
|
int main(void) { return getexecname() != 0; }'''
|
||||||
|
|
||||||
FRAGMENT_STRNLEN='''#include <string.h>
|
FRAGMENT_STRNLEN='''#include <string.h>
|
||||||
int main(int argc, char **argv) { return (int)strnlen(argv[0], 10); }'''
|
int main(int argc, char **argv) { return (int)strnlen(argv[0], 10); }'''
|
||||||
@@ -120,8 +120,8 @@ def configure(conf):
|
|||||||
check_frag(FRAGMENT_SYNC, ' __sync extensions', 'HAVE_SYNC_FUNCTIONS'),
|
check_frag(FRAGMENT_SYNC, ' __sync extensions', 'HAVE_SYNC_FUNCTIONS'),
|
||||||
check_frag(FRAGMENT_GETPAGESIZE, ' getpagesize function', 'HAVE_DECL_GETPAGESIZE'),
|
check_frag(FRAGMENT_GETPAGESIZE, ' getpagesize function', 'HAVE_DECL_GETPAGESIZE'),
|
||||||
check_frag(FRAGMENT_STRNLEN, ' strnlen function', 'HAVE_DECL_STRNLEN'),
|
check_frag(FRAGMENT_STRNLEN, ' strnlen function', 'HAVE_DECL_STRNLEN'),
|
||||||
check_frag(FRAGMENT_DL_ITERATE_PHDR % 'link.h', ' dl_iterate_phdr function in link.h', 'HAVE_DL_ITERATE_PHDR', after_tests=['link.h']),
|
check_frag(FRAGMENT_DL_ITERATE_PHDR % 'link.h', ' dl_iterate_phdr function in link.h', 'HAVE_DL_ITERATE_PHDR_LINK_H', after_tests=['link.h']),
|
||||||
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']),
|
check_frag(FRAGMENT_DL_ITERATE_PHDR % 'sys/link.h', ' dl_iterate_phdr function in sys/link.h', 'HAVE_DL_ITERATE_PHDR_SYS_LINK_H', after_tests=['sys/link.h']),
|
||||||
check_frag(FRAGMENT_FCNTL, 'fnctl function', 'HAVE_FCNTL'),
|
check_frag(FRAGMENT_FCNTL, 'fnctl function', 'HAVE_FCNTL'),
|
||||||
check_frag(FRAGMENT_GETEXECNAME, 'getexecname function','HAVE_GETEXECNAME'),
|
check_frag(FRAGMENT_GETEXECNAME, 'getexecname function','HAVE_GETEXECNAME'),
|
||||||
check_frag(FRAGMENT_GETIPINFO, '_Unwind_GetIPInfo function', 'HAVE_GETIPINFO'),
|
check_frag(FRAGMENT_GETIPINFO, '_Unwind_GetIPInfo function', 'HAVE_GETIPINFO'),
|
||||||
@@ -137,6 +137,9 @@ def configure(conf):
|
|||||||
msg='Checking for in parallel'
|
msg='Checking for in parallel'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if conf.is_defined('HAVE_DL_ITERATE_PHDR_LINK_H') or conf.is_defined('HAVE_DL_ITERATE_PHDR_SYS_LINK_H'):
|
||||||
|
conf.define('HAVE_DL_ITERATE_PHDR', 1)
|
||||||
|
|
||||||
conf.env[DEFKEYS].sort()
|
conf.env[DEFKEYS].sort()
|
||||||
|
|
||||||
conf.write_config_header()
|
conf.write_config_header()
|
||||||
@@ -181,7 +184,8 @@ def build(bld):
|
|||||||
|
|
||||||
task = bld.stlib(
|
task = bld.stlib(
|
||||||
source = ['libbacktrace/' + i for i in sources],
|
source = ['libbacktrace/' + i for i in sources],
|
||||||
target = 'backtrace',
|
# while we're using upstream libbacktrace, sometimes compiler picks up it's own version that isn't built with required options
|
||||||
|
target = 'backtrace_custom',
|
||||||
features = 'frandomseed' if bld.env.HAVE_FRANDOM_SEED else '',
|
features = 'frandomseed' if bld.env.HAVE_FRANDOM_SEED else '',
|
||||||
use = 'EXTRAFLAGS lzma z zstd',
|
use = 'EXTRAFLAGS lzma z zstd',
|
||||||
includes = '. libbacktrace/',
|
includes = '. libbacktrace/',
|
||||||
|
|||||||
2
3rdparty/library_suffix
vendored
2
3rdparty/library_suffix
vendored
Submodule 3rdparty/library_suffix updated: 6e14ae31e8...2cb2a0e120
2
3rdparty/mainui
vendored
2
3rdparty/mainui
vendored
Submodule 3rdparty/mainui updated: af2b5abdde...a4ec67f280
2
3rdparty/vgui_support
vendored
2
3rdparty/vgui_support
vendored
Submodule 3rdparty/vgui_support updated: 36219aedfc...e021fb5e7f
@@ -22,7 +22,7 @@ The other yet unsupported configuration is the big endian.
|
|||||||
|
|
||||||
2) Open `public/build.h` file. Add appropriate checks for your operating system and/or CPU architecture. It shouldn't be hard.
|
2) Open `public/build.h` file. Add appropriate checks for your operating system and/or CPU architecture. It shouldn't be hard.
|
||||||
|
|
||||||
Also, you'll need to build [Half-Life SDK](https://github.com/FWGS/hlsdk-xash3d/). It has same `public/build.h` so reflect changes into it. Note that to be compatible with HLSDK proprietary license, it's relicensed as public domain under [Unlicense](https://unlicense.org).
|
Also, you'll need to build [Half-Life SDK](https://github.com/FWGS/hlsdk-portable/). It has same `public/build.h` so reflect changes into it. Note that to be compatible with HLSDK proprietary license, it's relicensed as public domain under [Unlicense](https://unlicense.org).
|
||||||
|
|
||||||
We have a library naming scheme that allows us and game creators to distribute binaries for different platforms in one archive. Read `Documentation/extensions/library-naming.md` for more information.
|
We have a library naming scheme that allows us and game creators to distribute binaries for different platforms in one archive. Read `Documentation/extensions/library-naming.md` for more information.
|
||||||
|
|
||||||
|
|||||||
19
Documentation/extensions/litwater.md
Normal file
19
Documentation/extensions/litwater.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
## Lightmapped water
|
||||||
|
|
||||||
|
Xash3D FWGS supports lightmapped water, as an extension. It adds three new cvars and new worldspawn key values.
|
||||||
|
|
||||||
|
### For level designers:
|
||||||
|
|
||||||
|
If you're a level designer and intend to make your level to have lightmapped water, you can put these keyvalues to worldspawn entity description (always first entity in entities list):
|
||||||
|
|
||||||
|
| Key | Value | Description |
|
||||||
|
| ---------------------- | ------- | ----------- |
|
||||||
|
| `_litwater` | integer | Set to any non-zero value to enable lightmapped water. Overrides `gl_litwater_force` cvar value. |
|
||||||
|
| `_litwater_minlight` | integer | Minimal lightmap value water surface will receive. Helps to avoid too dark areas when water isn't properly lit. If not set, defaults to zero. |
|
||||||
|
| `_litwater_scale` | float | Scales up lightmap value for water surfaces. If not set, defaults to 1.0. |
|
||||||
|
|
||||||
|
### For players:
|
||||||
|
|
||||||
|
Some of the maps already have computed lightmap for water surfaces and sometimes water has been properly lit but the support hasn't been declared by the level designer.
|
||||||
|
|
||||||
|
As a player, you can enable it in `Video options` menu or through console with `gl_litwater_force` cvar. There are also `gl_litwater_minlight` and `gl_litwater_scale` cvars that function similar to keys above. The default values has been set to `192` and `1.25` respectively to slightly avoid issues with maps that wasn't intended to have lightmapped water.
|
||||||
@@ -1,8 +1,139 @@
|
|||||||
# Steam broker protocol
|
# Steam API Broker Protocol Specification
|
||||||
`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
|
## Overview
|
||||||
|
|
||||||
`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
|
Due to proprietary nature of Steamworks SDK, it cannot be run on same amount
|
||||||
|
of platforms supported by Xash3D FWGS, neither we can link library directly due to
|
||||||
|
GNU GPLv3 license.
|
||||||
|
|
||||||
`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)
|
However, here comes the broker, by running it (in trusted
|
||||||
|
network, preferrably) on a machine that has Steam client installed, the
|
||||||
|
engine can communicate with it, acquiring needed information to log-in into
|
||||||
|
Steam protected multiplayer servers.
|
||||||
|
|
||||||
|
## Key Characteristics
|
||||||
|
|
||||||
|
Broker uses a simple TCP-based binary protocol with the following properties:
|
||||||
|
|
||||||
|
- Single active session per connection: once a session is activated, the broker will reject all other TCP connections until the session is terminated.
|
||||||
|
- All messages follow a consistent structure with a header, length, and payload: this is called a "frame".
|
||||||
|
- Stateful interactions: commands affect the session state, and certain commands are only valid in specific states.
|
||||||
|
- Numeric parameters are encoded in little-endian format for compatibility with majority of platforms.
|
||||||
|
---
|
||||||
|
|
||||||
|
## Frame Format
|
||||||
|
|
||||||
|
All communication uses a fixed frame structure:
|
||||||
|
|
||||||
|
| Field | Size | Type | Description |
|
||||||
|
|-------|------|------|-------------|
|
||||||
|
| Header | 4 bytes | ASCII | Frame start signature |
|
||||||
|
| Length | 2 bytes | uint16_t (LE) | Size of payload in bytes |
|
||||||
|
| Payload | N bytes | Binary | Command string or response data |
|
||||||
|
|
||||||
|
**Notes**:
|
||||||
|
- Header must be exactly `SBRK` (`53 42 52 4B` in hexadecimal form)
|
||||||
|
- Maximum frame size: soft limit is 4096 bytes, hard limit is 65535 bytes (due to uint16_t usage)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Session Lifecycle
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────┐
|
||||||
|
│ IDLE │ ← Initial state, accepts new TCP connections
|
||||||
|
└──────────────┘
|
||||||
|
│ sb_gamedir
|
||||||
|
↓
|
||||||
|
┌──────────────┐
|
||||||
|
│ ACTIVE │ ← Session established, rejecting all of other TCP connections
|
||||||
|
└──────────────┘
|
||||||
|
│ sb_connect
|
||||||
|
↓
|
||||||
|
┌──────────────┐
|
||||||
|
│ ACTIVE │ ← Client getting auth ticket to connect to game server
|
||||||
|
└──────────────┘
|
||||||
|
│ sb_disconnect
|
||||||
|
↓
|
||||||
|
┌──────────────┐
|
||||||
|
│ ... │ ← Client announcing disconnect from game server, could connect somewhere again
|
||||||
|
└──────────────┘
|
||||||
|
│
|
||||||
|
↓
|
||||||
|
┌──────────────┐
|
||||||
|
│ ACTIVE │ ← Client shutting down game, terminating broker to reset Steam client state
|
||||||
|
└──────────────┘
|
||||||
|
│ sb_terminate
|
||||||
|
↓
|
||||||
|
┌──────────────┐
|
||||||
|
│ RESTARTING │
|
||||||
|
└──────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
All commands listed below are meant to be sent from client to broker as payload of a frame. The broker will parse the command string and respond accordingly.
|
||||||
|
|
||||||
|
### Session activation
|
||||||
|
|
||||||
|
Activates the session and signaling game startup.
|
||||||
|
|
||||||
|
**Format**: `sb_gamedir <gamedir>`
|
||||||
|
|
||||||
|
**Response**: None
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Authentication ticket request
|
||||||
|
|
||||||
|
Requests authentication ticket for a game server.
|
||||||
|
|
||||||
|
**Precondition**: Session must be active
|
||||||
|
|
||||||
|
**Format**: `sb_connect <ip:port> <server_steamid> <secure> <challenge>`
|
||||||
|
|
||||||
|
**Parameters**:
|
||||||
|
|
||||||
|
| Name | Description |
|
||||||
|
|------|------------------|
|
||||||
|
| `<ip:port>` | Game server address (e.g., `127.0.0.1:27015`) |
|
||||||
|
| `<server_steamid>` | Server SteamID |
|
||||||
|
| `<secure>` | 0 = insecure, 1 = secure |
|
||||||
|
| `<challenge>` | Random value for reply prevention |
|
||||||
|
|
||||||
|
|
||||||
|
**Response Format**:
|
||||||
|
|
||||||
|
| Field | Size | Type | Description |
|
||||||
|
|-------|------|------|-------------|
|
||||||
|
| Header | 11 bytes | string | `"sb_connect\n"` |
|
||||||
|
| Challenge | 4 bytes | int32_t (LE) | Echo of challenge from request |
|
||||||
|
| SteamID | 8 bytes | uint64_t (LE) | Client SteamID from configuration |
|
||||||
|
| Size | 4 bytes | uint32_t (LE) | Length of ticket in bytes |
|
||||||
|
| Ticket | N bytes | - | Authentication ticket data |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Server disconnection announcement
|
||||||
|
|
||||||
|
Notifies broker of disconnection from game server.
|
||||||
|
|
||||||
|
**Precondition**: Session must be active
|
||||||
|
|
||||||
|
**Format**: `sb_disconnect <ip:port> <challenge>`
|
||||||
|
|
||||||
|
**Response**: None
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Session termination
|
||||||
|
|
||||||
|
Signaling game shutdown to broker and resetting Steam client state.
|
||||||
|
|
||||||
|
**Format**: `sb_terminate`
|
||||||
|
|
||||||
|
**Response**: None
|
||||||
|
|
||||||
|
---
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|---- |------- |--------------- |----------------------
|
|---- |------- |--------------- |----------------------
|
||||||
|Area 51 |Update 1 |Uses outdated BSP31 map format and custom HLFX SDK libraries. |You can try [this tool](https://hlfx.ru/forum/showthread.php?threadid=5250) to convert maps but there no warranty if it works.
|
|Area 51 |Update 1 |Uses outdated BSP31 map format and custom HLFX SDK libraries. |You can try [this tool](https://hlfx.ru/forum/showthread.php?threadid=5250) to convert maps but there no warranty if it works.
|
||||||
|Arrange Mod: Rebirth |v150 |No idea yet. |
|
|Arrange Mod: Rebirth |v150 |No idea yet. |
|
||||||
|Blue Shift |The latest steam release |Uses vgui2 library which xash3d does not support. |Recreated source code here: https://github.com/FWGS/hlsdk-xash3d/tree/bshift.
|
|Blue Shift |The latest steam release |Uses vgui2 library which xash3d does not support. |Recreated source code here: https://github.com/FWGS/hlsdk-portable/tree/bshift.
|
||||||
|Counter Strike |Beta 6.5- |Uses an old WON HL 1.0.0.16- interface. |
|
|Counter Strike |Beta 6.5- |Uses an old WON HL 1.0.0.16- interface. |
|
||||||
| |1.4 |Has encrypted blob instead of normal client.dll. |You can try [this tool](https://aluigi.altervista.org/papers/hldlldec.zip) to decrypt client.dll but there no warranty if it works.
|
| |1.4 |Has encrypted blob instead of normal client.dll. |You can try [this tool](https://aluigi.altervista.org/papers/hldlldec.zip) to decrypt client.dll but there no warranty if it works.
|
||||||
| |1.5 |Has encrypted blob instead of normal client.dll. |Decrypted blob here: https://csm.dev/threads/cs-1-5-client-dll-decrypted-patched-for-usage.38845.
|
| |1.5 |Has encrypted blob instead of normal client.dll. |Decrypted blob here: https://csm.dev/threads/cs-1-5-client-dll-decrypted-patched-for-usage.38845.
|
||||||
@@ -15,6 +15,6 @@
|
|||||||
|Half-Life: Extended |Day One demo |Uses many hooks to GoldSource engine and version check. |Just wait new version or use more old version.
|
|Half-Life: Extended |Day One demo |Uses many hooks to GoldSource engine and version check. |Just wait new version or use more old version.
|
||||||
|Icon of Hell |Beta 0.99 |Uses outdated BSP31 map format and paranoia 2 libraries. |You can try [this tool](https://hlfx.ru/forum/showthread.php?threadid=5250) to convert maps and use Paranoia 2: The Savior 1.51 libraries but there no warranty if it works.
|
|Icon of Hell |Beta 0.99 |Uses outdated BSP31 map format and paranoia 2 libraries. |You can try [this tool](https://hlfx.ru/forum/showthread.php?threadid=5250) to convert maps and use Paranoia 2: The Savior 1.51 libraries but there no warranty if it works.
|
||||||
|Paranoia 2: The Savior |All builds older 1.51 |Uses an old renderer interface and engine features. |
|
|Paranoia 2: The Savior |All builds older 1.51 |Uses an old renderer interface and engine features. |
|
||||||
|Rebellion |1.0 |Uses an old WON HL 1.0.0.16- interface. |Recreated source code here: https://github.com/FWGS/hlsdk-xash3d/tree/rebellion.
|
|Rebellion |1.0 |Uses an old WON HL 1.0.0.16- interface. |Recreated source code here: https://github.com/FWGS/hlsdk-portable/tree/rebellion.
|
||||||
|Sven-Coop |5.0+ |Uses custom GoldSrc engine. |
|
|Sven-Coop |5.0+ |Uses custom GoldSrc engine. |
|
||||||
|Time Shadows |Beta 0.1 |Uses Direct3D renderer. |
|
|Time Shadows |Beta 0.1 |Uses Direct3D renderer. |
|
||||||
|
|||||||
@@ -57,6 +57,7 @@
|
|||||||
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:networkSecurityConfig="@xml/network_security_config"
|
||||||
android:usesCleartextTraffic="true"
|
android:usesCleartextTraffic="true"
|
||||||
android:requestLegacyExternalStorage="true">
|
android:requestLegacyExternalStorage="true">
|
||||||
<activity
|
<activity
|
||||||
@@ -68,6 +69,15 @@
|
|||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<provider
|
||||||
|
android:name="androidx.core.content.FileProvider"
|
||||||
|
android:authorities="${applicationId}.fileprovider"
|
||||||
|
android:exported="false"
|
||||||
|
android:grantUriPermissions="true">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
android:resource="@xml/provider_paths" />
|
||||||
|
</provider>
|
||||||
<activity
|
<activity
|
||||||
android:name=".XashActivity"
|
android:name=".XashActivity"
|
||||||
android:alwaysRetainTaskState="true"
|
android:alwaysRetainTaskState="true"
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package su.xash.engine
|
package su.xash.engine
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import androidx.navigation.fragment.NavHostFragment
|
import androidx.navigation.fragment.NavHostFragment
|
||||||
@@ -8,6 +9,11 @@ import androidx.navigation.ui.AppBarConfiguration
|
|||||||
import androidx.navigation.ui.navigateUp
|
import androidx.navigation.ui.navigateUp
|
||||||
import androidx.navigation.ui.setupActionBarWithNavController
|
import androidx.navigation.ui.setupActionBarWithNavController
|
||||||
import su.xash.engine.databinding.ActivityMainBinding
|
import su.xash.engine.databinding.ActivityMainBinding
|
||||||
|
import su.xash.engine.util.CrashReports
|
||||||
|
import java.io.File
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
private lateinit var binding: ActivityMainBinding
|
private lateinit var binding: ActivityMainBinding
|
||||||
@@ -27,9 +33,36 @@ class MainActivity : AppCompatActivity() {
|
|||||||
navController = navHostFragment.navController
|
navController = navHostFragment.navController
|
||||||
appBarConfiguration = AppBarConfiguration(navController.graph)
|
appBarConfiguration = AppBarConfiguration(navController.graph)
|
||||||
setupActionBarWithNavController(navController, appBarConfiguration)
|
setupActionBarWithNavController(navController, appBarConfiguration)
|
||||||
|
|
||||||
|
CrashReports.prune(this)
|
||||||
|
showPendingCrashReport()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSupportNavigateUp(): Boolean {
|
override fun onSupportNavigateUp(): Boolean {
|
||||||
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
|
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showPendingCrashReport() {
|
||||||
|
val pending = CrashReports.pendingFile(this)
|
||||||
|
if (!pending.exists() || pending.length() == 0L)
|
||||||
|
return
|
||||||
|
|
||||||
|
val historyDir = CrashReports.historyDir(this).apply { mkdirs() }
|
||||||
|
val ts = SimpleDateFormat("yyyyMMdd-HHmmss", Locale.US).format(Date())
|
||||||
|
val archived = File(historyDir, "crash-$ts.log")
|
||||||
|
if (!pending.renameTo(archived)) {
|
||||||
|
// fall back to in-place read if move failed; still consume the file
|
||||||
|
archived.writeText(pending.readText())
|
||||||
|
pending.delete()
|
||||||
|
}
|
||||||
|
|
||||||
|
val content = archived.readText()
|
||||||
|
AlertDialog.Builder(this)
|
||||||
|
.setTitle(R.string.crash_dialog_title)
|
||||||
|
.setView(CrashReports.buildContentView(this, content))
|
||||||
|
.setPositiveButton(R.string.crash_send_to_developers) { _, _ -> CrashReports.sendByEmail(this, content) }
|
||||||
|
.setNeutralButton(R.string.crash_share) { _, _ -> CrashReports.share(this, archived) }
|
||||||
|
.setNegativeButton(R.string.crash_dismiss, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import org.libsdl.app.SDLActivity;
|
|||||||
|
|
||||||
import su.xash.engine.util.AndroidBug5497Workaround;
|
import su.xash.engine.util.AndroidBug5497Workaround;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -123,6 +124,10 @@ public class XashActivity extends SDLActivity {
|
|||||||
// TODO: REMOVE LATER, temporary launchers support?
|
// TODO: REMOVE LATER, temporary launchers support?
|
||||||
@Override
|
@Override
|
||||||
protected String[] getArguments() {
|
protected String[] getArguments() {
|
||||||
|
File crashDir = new File(getFilesDir(), "crashes");
|
||||||
|
crashDir.mkdirs();
|
||||||
|
nativeSetenv("XASH3D_CRASH_DIR", crashDir.getAbsolutePath());
|
||||||
|
|
||||||
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);
|
||||||
@@ -130,6 +135,14 @@ public class XashActivity extends SDLActivity {
|
|||||||
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 rodir = System.getenv("XASH3D_RODIR");
|
||||||
|
if (rodir == null) {
|
||||||
|
// FIXME: we are using rodir as a supplier for downloaded game libraries
|
||||||
|
rodir = getFilesDir().getAbsolutePath() + "/gamelibs";
|
||||||
|
nativeSetenv("XASH3D_RODIR", rodir);
|
||||||
|
}
|
||||||
|
Log.i(TAG, "XASH3D_RODIR = " + rodir);
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
|
|||||||
@@ -6,14 +6,19 @@ 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.graphics.BitmapFactory
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.widget.TextView
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import com.google.android.material.progressindicator.LinearProgressIndicator
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import su.xash.engine.R
|
import su.xash.engine.R
|
||||||
import su.xash.engine.XashActivity
|
import su.xash.engine.XashActivity
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import java.util.Arrays
|
|
||||||
import android.util.Log
|
|
||||||
|
|
||||||
class Game(val ctx: Context, val basedir: File, val gameInfoFile: File) {
|
class Game(val ctx: Context, val basedir: File, val gameInfoFile: File) {
|
||||||
private var iconName = "game.ico"
|
private var iconName = "game.ico"
|
||||||
@@ -52,7 +57,7 @@ class Game(val ctx: Context, val basedir: File, val gameInfoFile: File) {
|
|||||||
fun startEngine(ctx: Context) {
|
fun startEngine(ctx: Context) {
|
||||||
val packageNames = getPackageNamesForGameDir(basedir.name)
|
val packageNames = getPackageNamesForGameDir(basedir.name)
|
||||||
var externalGame = false
|
var externalGame = false
|
||||||
var commandLineArgs = "";
|
var commandLineArgs = ""
|
||||||
|
|
||||||
if (basedir.name != defaultGameDir)
|
if (basedir.name != defaultGameDir)
|
||||||
commandLineArgs += "-game ${basedir.name} "
|
commandLineArgs += "-game ${basedir.name} "
|
||||||
@@ -68,17 +73,18 @@ class Game(val ctx: Context, val basedir: File, val gameInfoFile: File) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
commandLineArgs += pref.getString("arguments", "-console -log")
|
commandLineArgs += pref.getString("arguments", "-console -log") ?: ""
|
||||||
|
|
||||||
var packageName: String? = null
|
|
||||||
var gameLibDir: String? = null
|
|
||||||
if (externalGame && packageNames != null) {
|
if (externalGame && packageNames != null) {
|
||||||
|
var packageName: String? = null
|
||||||
|
var gameLibDir: String? = null
|
||||||
|
|
||||||
for (pn in packageNames) {
|
for (pn in packageNames) {
|
||||||
gameLibDir = try {
|
gameLibDir = try {
|
||||||
getGameLibDir(ctx, pn)
|
getGameLibDir(ctx, pn)
|
||||||
} catch(e: PackageManager.NameNotFoundException) {
|
} catch (e: PackageManager.NameNotFoundException) {
|
||||||
null
|
null
|
||||||
} catch(e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
@@ -99,12 +105,95 @@ class Game(val ctx: Context, val basedir: File, val gameInfoFile: File) {
|
|||||||
ctx.startActivity(intent)
|
ctx.startActivity(intent)
|
||||||
}
|
}
|
||||||
show()
|
show()
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
launchEngine(ctx, commandLineArgs, packageName = packageName, gameLibDir = gameLibDir)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packageNames == null) {
|
||||||
|
// Unknown game — try to use downloaded libraries from hlsdk-mega-build
|
||||||
|
val downloader = GameLibDownloader(ctx)
|
||||||
|
val args = commandLineArgs
|
||||||
|
|
||||||
|
if (downloader.isDownloaded(basedir.name)) {
|
||||||
|
downloader.logExistingLibs(basedir.name)
|
||||||
|
launchEngine(ctx, args)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
||||||
|
scope.launch {
|
||||||
|
when (val r = downloader.lookupBuild(basedir.name)) {
|
||||||
|
is GameLibDownloader.Lookup.Available -> showDownloadDialog(ctx, downloader, args)
|
||||||
|
is GameLibDownloader.Lookup.NotInManifest -> launchEngine(ctx, args)
|
||||||
|
is GameLibDownloader.Lookup.Error -> showManifestErrorDialog(ctx, args, r.cause)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
launchEngine(ctx, commandLineArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showDownloadDialog(ctx: Context, downloader: GameLibDownloader, commandLineArgs: String) {
|
||||||
|
val view = LayoutInflater.from(ctx).inflate(R.layout.dialog_download_progress, null)
|
||||||
|
val progressBar = view.findViewById<LinearProgressIndicator>(R.id.downloadProgress)
|
||||||
|
val statusText = view.findViewById<TextView>(R.id.downloadStatus)
|
||||||
|
|
||||||
|
val dialog = MaterialAlertDialogBuilder(ctx)
|
||||||
|
.setTitle(R.string.downloading_game_libs)
|
||||||
|
.setView(view)
|
||||||
|
.setCancelable(true)
|
||||||
|
.setNegativeButton(android.R.string.cancel) { d, _ -> d.dismiss() }
|
||||||
|
.create()
|
||||||
|
|
||||||
|
dialog.show()
|
||||||
|
|
||||||
|
val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
||||||
|
val job = scope.launch {
|
||||||
|
val result = downloader.download(basedir.name) { progress ->
|
||||||
|
progressBar.isIndeterminate = false
|
||||||
|
progressBar.progress = (progress * 100).toInt()
|
||||||
|
statusText.text = ctx.getString(R.string.download_progress, (progress * 100).toInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dialog.isShowing) return@launch
|
||||||
|
|
||||||
|
dialog.dismiss()
|
||||||
|
|
||||||
|
if (result.isSuccess) {
|
||||||
|
launchEngine(ctx, commandLineArgs)
|
||||||
|
} else {
|
||||||
|
MaterialAlertDialogBuilder(ctx)
|
||||||
|
.setTitle(R.string.download_failed)
|
||||||
|
.setMessage(result.exceptionOrNull()?.message
|
||||||
|
?: ctx.getString(R.string.download_error))
|
||||||
|
.setPositiveButton(android.R.string.ok, null)
|
||||||
|
.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dialog.setOnDismissListener { job.cancel() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showManifestErrorDialog(ctx: Context, commandLineArgs: String, cause: Throwable) {
|
||||||
|
MaterialAlertDialogBuilder(ctx)
|
||||||
|
.setTitle(R.string.manifest_error_title)
|
||||||
|
.setMessage(ctx.getString(R.string.manifest_error_message, cause.message ?: cause.javaClass.simpleName))
|
||||||
|
.setPositiveButton(R.string.launch_anyway) { _, _ -> launchEngine(ctx, commandLineArgs) }
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun launchEngine(
|
||||||
|
ctx: Context,
|
||||||
|
commandLineArgs: String,
|
||||||
|
packageName: String? = null,
|
||||||
|
gameLibDir: String? = null
|
||||||
|
) {
|
||||||
ctx.startActivity(Intent(ctx, XashActivity::class.java).apply {
|
ctx.startActivity(Intent(ctx, XashActivity::class.java).apply {
|
||||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||||
|
|
||||||
@@ -113,13 +202,8 @@ class Game(val ctx: Context, val basedir: File, val gameInfoFile: File) {
|
|||||||
putExtra("usevolume", pref.getBoolean("use_volume_buttons", false))
|
putExtra("usevolume", pref.getBoolean("use_volume_buttons", false))
|
||||||
putExtra("basedir", basedir.parent)
|
putExtra("basedir", basedir.parent)
|
||||||
|
|
||||||
if (gameLibDir != null) {
|
if (gameLibDir != null) putExtra("gamelibdir", gameLibDir)
|
||||||
putExtra("gamelibdir", gameLibDir)
|
if (packageName != null) putExtra("package", packageName)
|
||||||
}
|
|
||||||
|
|
||||||
if (packageName != null) {
|
|
||||||
putExtra("package", packageName)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,9 +236,6 @@ class Game(val ctx: Context, val basedir: File, val gameInfoFile: File) {
|
|||||||
if (mobileHacksGames.any { it.equals(gamedir, ignoreCase = true) })
|
if (mobileHacksGames.any { it.equals(gamedir, ignoreCase = true) })
|
||||||
return arrayOf("su.xash.engine")
|
return arrayOf("su.xash.engine")
|
||||||
|
|
||||||
// return if (mDbEntry != null) {
|
|
||||||
// mDbEntry.getPackageName()
|
|
||||||
// } else null
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,373 @@
|
|||||||
|
package su.xash.engine.model
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
|
import android.util.Log
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.ensureActive
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.json.JSONObject
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.io.IOException
|
||||||
|
import java.net.HttpURLConnection
|
||||||
|
import java.net.URL
|
||||||
|
import java.security.MessageDigest
|
||||||
|
import java.util.zip.ZipInputStream
|
||||||
|
import kotlin.coroutines.coroutineContext
|
||||||
|
|
||||||
|
class GameLibDownloader(private val context: Context) {
|
||||||
|
|
||||||
|
private val prefs get() = context.getSharedPreferences("gamelib_metadata", Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
// converts Android's ABI to library-suffix's arch names
|
||||||
|
fun getArch(): String? {
|
||||||
|
for (abi in Build.SUPPORTED_ABIS) {
|
||||||
|
when (abi) {
|
||||||
|
"arm64-v8a" -> return "arm64"
|
||||||
|
"armeabi-v7a" -> return "armv7l"
|
||||||
|
"x86_64" -> return "amd64"
|
||||||
|
"x86" -> return "i386"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getLibsBaseDir(): File = File(context.filesDir, "gamelibs")
|
||||||
|
|
||||||
|
fun isDownloaded(gamedir: String): Boolean {
|
||||||
|
val dir = File(getLibsBaseDir(), gamedir)
|
||||||
|
if (dir.isDirectory && dir.list()?.isNotEmpty() == true)
|
||||||
|
return true
|
||||||
|
|
||||||
|
// zips extract to the case used in the manifest (e.g. "CAd"); the user's
|
||||||
|
// folder name might differ in case, so do a case-insensitive lookup too.
|
||||||
|
val base = getLibsBaseDir()
|
||||||
|
val match = base.listFiles()?.firstOrNull {
|
||||||
|
it.isDirectory && it.name.equals(gamedir, ignoreCase = true)
|
||||||
|
}
|
||||||
|
return match != null && (match.list()?.isNotEmpty() == true)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ManifestEntry(
|
||||||
|
val modKey: String,
|
||||||
|
val platformArch: String,
|
||||||
|
val filename: String,
|
||||||
|
val sha256: String,
|
||||||
|
val sourceJson: String?
|
||||||
|
)
|
||||||
|
|
||||||
|
data class SourceInfo(
|
||||||
|
val url: String?,
|
||||||
|
val branch: String?,
|
||||||
|
val commit: String?
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun manifestCacheFile(): File = File(context.cacheDir, "hlsdk-manifest.json")
|
||||||
|
|
||||||
|
private suspend fun fetchManifest(): JSONObject? = fetchManifestOrError().first
|
||||||
|
|
||||||
|
private suspend fun fetchManifestOrError(): Pair<JSONObject?, Throwable?> {
|
||||||
|
val cacheFile = manifestCacheFile()
|
||||||
|
var connection: HttpURLConnection? = null
|
||||||
|
try {
|
||||||
|
connection = URL(MANIFEST_URL).openConnection() as HttpURLConnection
|
||||||
|
connection.connectTimeout = 10000
|
||||||
|
connection.readTimeout = 15000
|
||||||
|
connection.instanceFollowRedirects = true
|
||||||
|
connection.connect()
|
||||||
|
|
||||||
|
if (connection.responseCode != HttpURLConnection.HTTP_OK) {
|
||||||
|
val err = IOException("HTTP ${connection.responseCode} fetching manifest")
|
||||||
|
Log.w(TAG, "Manifest fetch failed: ${err.message}")
|
||||||
|
val cached = readCachedManifest(cacheFile)
|
||||||
|
return if (cached != null) cached to null else null to err
|
||||||
|
}
|
||||||
|
|
||||||
|
val text = connection.inputStream.bufferedReader().use { it.readText() }
|
||||||
|
val json = JSONObject(text)
|
||||||
|
val versionError = checkManifestVersion(json)
|
||||||
|
if (versionError != null) {
|
||||||
|
Log.w(TAG, "Manifest rejected: ${versionError.message}")
|
||||||
|
return null to versionError
|
||||||
|
}
|
||||||
|
cacheFile.writeText(text)
|
||||||
|
return json to null
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w(TAG, "Manifest fetch failed: ${e.message}")
|
||||||
|
val cached = readCachedManifest(cacheFile)
|
||||||
|
return if (cached != null) cached to null else null to e
|
||||||
|
} finally {
|
||||||
|
connection?.disconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readCachedManifest(cacheFile: File): JSONObject? {
|
||||||
|
if (!cacheFile.exists())
|
||||||
|
return null
|
||||||
|
val json = try {
|
||||||
|
JSONObject(cacheFile.readText())
|
||||||
|
} catch (_: Exception) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return if (checkManifestVersion(json) == null) json else null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkManifestVersion(json: JSONObject): IOException? {
|
||||||
|
if (!json.has("version"))
|
||||||
|
return IOException("Manifest missing 'version' field")
|
||||||
|
val v = json.opt("version")
|
||||||
|
if (v !is Int || v != MANIFEST_VERSION)
|
||||||
|
return IOException("Unsupported manifest version: $v (expected $MANIFEST_VERSION)")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun lookupEntry(manifest: JSONObject, gamedir: String): ManifestEntry? {
|
||||||
|
val arch = getArch() ?: return null
|
||||||
|
val platformArch = "android-$arch"
|
||||||
|
val mods = manifest.optJSONObject("mods") ?: return null
|
||||||
|
|
||||||
|
// Find mod key case-insensitively
|
||||||
|
var modKey: String? = null
|
||||||
|
val keys = mods.keys()
|
||||||
|
while (keys.hasNext()) {
|
||||||
|
val k = keys.next()
|
||||||
|
if (k.equals(gamedir, ignoreCase = true)) {
|
||||||
|
modKey = k
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (modKey == null)
|
||||||
|
return null
|
||||||
|
|
||||||
|
val builds = mods.optJSONObject(modKey)?.optJSONObject("builds") ?: return null
|
||||||
|
val build = builds.optJSONObject(platformArch) ?: return null
|
||||||
|
val filename = build.optString("filename", "")
|
||||||
|
val sha256 = build.optString("sha256", "")
|
||||||
|
if (filename.isEmpty() || sha256.isEmpty())
|
||||||
|
return null
|
||||||
|
|
||||||
|
val sourceJson = build.optJSONObject("source")?.toString()
|
||||||
|
return ManifestEntry(modKey, platformArch, filename, sha256.lowercase(), sourceJson)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSourceInfo(gamedir: String): SourceInfo? {
|
||||||
|
val raw = prefs.getString("${gamedir}_source", null) ?: return null
|
||||||
|
return try {
|
||||||
|
val o = JSONObject(raw)
|
||||||
|
SourceInfo(
|
||||||
|
url = o.optString("url").ifEmpty { null },
|
||||||
|
branch = o.optString("branch").ifEmpty { null },
|
||||||
|
commit = o.optString("commit").ifEmpty { null },
|
||||||
|
)
|
||||||
|
} catch (_: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getDownloadTime(gamedir: String): Long =
|
||||||
|
prefs.getLong("${gamedir}_download_time", 0L)
|
||||||
|
|
||||||
|
sealed class Lookup {
|
||||||
|
data class Available(val entry: ManifestEntry) : Lookup()
|
||||||
|
object NotInManifest : Lookup()
|
||||||
|
data class Error(val cause: Throwable) : Lookup()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun lookupBuild(gamedir: String): Lookup {
|
||||||
|
return withContext(Dispatchers.IO) {
|
||||||
|
val (manifest, error) = fetchManifestOrError()
|
||||||
|
if (manifest == null)
|
||||||
|
return@withContext Lookup.Error(error ?: IOException("Failed to fetch manifest"))
|
||||||
|
val entry = lookupEntry(manifest, gamedir)
|
||||||
|
?: return@withContext Lookup.NotInManifest
|
||||||
|
Lookup.Available(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun isUpdateAvailable(gamedir: String): Boolean {
|
||||||
|
return withContext(Dispatchers.IO) {
|
||||||
|
val manifest = fetchManifest() ?: return@withContext false
|
||||||
|
val entry = lookupEntry(manifest, gamedir) ?: return@withContext false
|
||||||
|
val storedSha = prefs.getString("${gamedir}_sha256", null)
|
||||||
|
storedSha == null || !storedSha.equals(entry.sha256, ignoreCase = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun urlForFilename(filename: String): String = "$RELEASE_BASE_URL/$filename"
|
||||||
|
|
||||||
|
private suspend fun tryDownload(
|
||||||
|
url: String,
|
||||||
|
dest: File,
|
||||||
|
onProgress: (Float) -> Unit
|
||||||
|
): Exception? {
|
||||||
|
var connection: HttpURLConnection? = null
|
||||||
|
try {
|
||||||
|
connection = URL(url).openConnection() as HttpURLConnection
|
||||||
|
connection.connectTimeout = 10000
|
||||||
|
connection.readTimeout = 30000
|
||||||
|
connection.instanceFollowRedirects = true
|
||||||
|
connection.connect()
|
||||||
|
|
||||||
|
val code = connection.responseCode
|
||||||
|
if (code !in 200..299)
|
||||||
|
return IOException("HTTP $code: $url")
|
||||||
|
|
||||||
|
val total = connection.contentLengthLong
|
||||||
|
var downloaded = 0L
|
||||||
|
|
||||||
|
connection.inputStream.use { input ->
|
||||||
|
FileOutputStream(dest).use { output ->
|
||||||
|
val buffer = ByteArray(65536)
|
||||||
|
while (true) {
|
||||||
|
coroutineContext.ensureActive()
|
||||||
|
val read = input.read(buffer)
|
||||||
|
if (read < 0)
|
||||||
|
break
|
||||||
|
output.write(buffer, 0, read)
|
||||||
|
downloaded += read
|
||||||
|
if (total > 0) {
|
||||||
|
val progress = downloaded.toFloat() / total
|
||||||
|
withContext(Dispatchers.Main) { onProgress(progress) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
} catch (e: Exception) {
|
||||||
|
return e
|
||||||
|
} finally {
|
||||||
|
connection?.disconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun download(gamedir: String, onProgress: (Float) -> Unit): Result<Unit> {
|
||||||
|
return withContext(Dispatchers.IO) {
|
||||||
|
val manifest = fetchManifest()
|
||||||
|
?: return@withContext Result.failure(IOException("Failed to fetch manifest"))
|
||||||
|
|
||||||
|
val entry = lookupEntry(manifest, gamedir)
|
||||||
|
?: return@withContext Result.failure(IOException("No build for '$gamedir' on ${getArch() ?: "this arch"}"))
|
||||||
|
|
||||||
|
val url = urlForFilename(entry.filename)
|
||||||
|
val tempFile = File(context.cacheDir, entry.filename)
|
||||||
|
|
||||||
|
try {
|
||||||
|
coroutineContext.ensureActive()
|
||||||
|
Log.i(TAG, "Downloading $url -> ${tempFile.absolutePath}")
|
||||||
|
val error = tryDownload(url, tempFile, onProgress)
|
||||||
|
if (error != null)
|
||||||
|
return@withContext Result.failure(error)
|
||||||
|
|
||||||
|
val actualSha = computeSHA256(tempFile)
|
||||||
|
Log.i(TAG, "Downloaded ${tempFile.length()} bytes, SHA-256: $actualSha (expected ${entry.sha256})")
|
||||||
|
|
||||||
|
if (!actualSha.equals(entry.sha256, ignoreCase = true))
|
||||||
|
return@withContext Result.failure(IOException("SHA-256 mismatch: expected ${entry.sha256}, got $actualSha"))
|
||||||
|
|
||||||
|
val destDir = getLibsBaseDir()
|
||||||
|
destDir.mkdirs()
|
||||||
|
val destCanon = destDir.canonicalPath
|
||||||
|
|
||||||
|
// Remove any prior install (possibly under a differently-cased dir) so we
|
||||||
|
// don't leave behind .so files from an older build.
|
||||||
|
destDir.listFiles()?.forEach {
|
||||||
|
if (it.isDirectory && it.name.equals(entry.modKey, ignoreCase = true))
|
||||||
|
it.deleteRecursively()
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.i(TAG, "Extracting to ${destDir.absolutePath}")
|
||||||
|
|
||||||
|
val extractedFiles = mutableListOf<String>()
|
||||||
|
ZipInputStream(tempFile.inputStream()).use { zip ->
|
||||||
|
var zipEntry = zip.nextEntry
|
||||||
|
while (zipEntry != null) {
|
||||||
|
coroutineContext.ensureActive()
|
||||||
|
if (!zipEntry.isDirectory) {
|
||||||
|
val outFile = File(destDir, zipEntry.name)
|
||||||
|
if (!outFile.canonicalPath.startsWith(destCanon + File.separator))
|
||||||
|
throw SecurityException("Zip path traversal: ${zipEntry.name}")
|
||||||
|
outFile.parentFile?.mkdirs()
|
||||||
|
FileOutputStream(outFile).use { zip.copyTo(it) }
|
||||||
|
// ZipInputStream doesn't restore Unix permissions; shared libraries
|
||||||
|
// need the execute bit so dlopen can mmap them with PROT_EXEC
|
||||||
|
if (outFile.name.endsWith(".so"))
|
||||||
|
outFile.setExecutable(true, false)
|
||||||
|
extractedFiles.add(" ${outFile.absolutePath} (${outFile.length()} bytes, executable: ${outFile.canExecute()})")
|
||||||
|
}
|
||||||
|
zip.closeEntry()
|
||||||
|
zipEntry = zip.nextEntry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.i(TAG, "Extracted ${extractedFiles.size} files:")
|
||||||
|
extractedFiles.forEach { Log.i(TAG, it) }
|
||||||
|
|
||||||
|
prefs.edit()
|
||||||
|
.putString("${gamedir}_sha256", entry.sha256)
|
||||||
|
.putString("${gamedir}_filename", entry.filename)
|
||||||
|
.putString("${gamedir}_source", entry.sourceJson)
|
||||||
|
.putLong("${gamedir}_download_time", System.currentTimeMillis())
|
||||||
|
.apply()
|
||||||
|
|
||||||
|
Result.success(Unit)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// Best-effort: rewinding to a clean state on failure
|
||||||
|
File(getLibsBaseDir(), entry.modKey).deleteRecursively()
|
||||||
|
Result.failure(e)
|
||||||
|
} finally {
|
||||||
|
tempFile.delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun logExistingLibs(gamedir: String) {
|
||||||
|
val gameDir = File(getLibsBaseDir(), gamedir)
|
||||||
|
val resolved = if (gameDir.exists()) {
|
||||||
|
gameDir
|
||||||
|
} else {
|
||||||
|
getLibsBaseDir().listFiles()?.firstOrNull {
|
||||||
|
it.isDirectory && it.name.equals(gamedir, ignoreCase = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resolved == null) {
|
||||||
|
Log.i(TAG, "Game libs dir does not exist for $gamedir")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.i(TAG, "Existing game libs under ${resolved.absolutePath}:")
|
||||||
|
resolved.walkTopDown().filter { it.isFile }.forEach {
|
||||||
|
Log.i(TAG, " ${it.absolutePath} (${it.length()} bytes)")
|
||||||
|
}
|
||||||
|
val storedSha = prefs.getString("${gamedir}_sha256", null)
|
||||||
|
val downloadTime = prefs.getLong("${gamedir}_download_time", 0L)
|
||||||
|
Log.i(TAG, "Archive SHA-256: $storedSha")
|
||||||
|
if (downloadTime > 0L)
|
||||||
|
Log.i(TAG, "Downloaded at: ${java.util.Date(downloadTime)}")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun computeSHA256(file: File): String {
|
||||||
|
val digest = MessageDigest.getInstance("SHA-256")
|
||||||
|
file.inputStream().use { input ->
|
||||||
|
val buffer = ByteArray(65536)
|
||||||
|
while (true) {
|
||||||
|
val read = input.read(buffer)
|
||||||
|
if (read < 0)
|
||||||
|
break
|
||||||
|
digest.update(buffer, 0, read)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return digest.digest().joinToString("") { "%02x".format(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "GameLibDownloader"
|
||||||
|
private const val RELEASE_BASE_URL =
|
||||||
|
"https://github.com/FWGS/hlsdk-mega-build/releases/download/continuous"
|
||||||
|
private const val MANIFEST_URL =
|
||||||
|
"$RELEASE_BASE_URL/manifest.json"
|
||||||
|
private const val MANIFEST_VERSION = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
package su.xash.engine.ui.settings
|
package su.xash.engine.ui.settings
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import androidx.navigation.fragment.findNavController
|
||||||
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
import su.xash.engine.R
|
import su.xash.engine.R
|
||||||
|
|
||||||
@@ -8,5 +10,10 @@ 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);
|
||||||
|
|
||||||
|
findPreference<Preference>("crash_logs")?.setOnPreferenceClickListener {
|
||||||
|
findNavController().navigate(R.id.action_appSettingsFragment_to_crashLogsFragment)
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package su.xash.engine.ui.settings
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.preference.Preference
|
||||||
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
|
import su.xash.engine.R
|
||||||
|
import su.xash.engine.util.CrashReports
|
||||||
|
import java.io.File
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
class CrashLogsFragment : PreferenceFragmentCompat() {
|
||||||
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
|
preferenceScreen = preferenceManager.createPreferenceScreen(requireContext())
|
||||||
|
populate()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
populate()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun populate() {
|
||||||
|
val ctx = requireContext()
|
||||||
|
preferenceScreen.removeAll()
|
||||||
|
|
||||||
|
val files = CrashReports.historyDir(ctx).listFiles()?.sortedByDescending { it.lastModified() } ?: emptyList()
|
||||||
|
|
||||||
|
if (files.isEmpty()) {
|
||||||
|
preferenceScreen.addPreference(Preference(ctx).apply {
|
||||||
|
setTitle(R.string.crash_logs_empty)
|
||||||
|
isSelectable = false
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val fmt = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US)
|
||||||
|
files.forEach { file ->
|
||||||
|
preferenceScreen.addPreference(Preference(ctx).apply {
|
||||||
|
title = fmt.format(Date(file.lastModified()))
|
||||||
|
summary = file.name
|
||||||
|
setOnPreferenceClickListener {
|
||||||
|
showCrashLog(file)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showCrashLog(file: File) {
|
||||||
|
val ctx = requireContext()
|
||||||
|
val content = file.readText()
|
||||||
|
AlertDialog.Builder(ctx)
|
||||||
|
.setTitle(file.name)
|
||||||
|
.setView(CrashReports.buildContentView(ctx, content))
|
||||||
|
.setPositiveButton(R.string.crash_send_to_developers) { _, _ -> CrashReports.sendByEmail(ctx, content) }
|
||||||
|
.setNeutralButton(R.string.crash_share) { _, _ -> CrashReports.share(ctx, file) }
|
||||||
|
.setNegativeButton(R.string.crash_log_delete) { _, _ ->
|
||||||
|
file.delete()
|
||||||
|
populate()
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,17 @@
|
|||||||
package su.xash.engine.ui.settings
|
package su.xash.engine.ui.settings
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import androidx.core.net.toUri
|
||||||
import androidx.preference.ListPreference
|
import androidx.preference.ListPreference
|
||||||
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
import androidx.preference.SwitchPreferenceCompat
|
import androidx.preference.SwitchPreferenceCompat
|
||||||
import su.xash.engine.R
|
import su.xash.engine.R
|
||||||
import su.xash.engine.model.Game
|
import su.xash.engine.model.Game
|
||||||
|
import su.xash.engine.model.GameLibDownloader
|
||||||
|
import java.text.DateFormat
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
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?) {
|
||||||
@@ -26,6 +32,8 @@ class GameSettingsPreferenceFragment(val game: Game) : PreferenceFragmentCompat(
|
|||||||
enableYaPBBots.isVisible = true
|
enableYaPBBots.isVisible = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
populateDownloadedBuildInfo()
|
||||||
|
|
||||||
val separatePackages = findPreference<SwitchPreferenceCompat>("separate_libraries")!!
|
val separatePackages = findPreference<SwitchPreferenceCompat>("separate_libraries")!!
|
||||||
val clientPackage = findPreference<ListPreference>("client_package")!!
|
val clientPackage = findPreference<ListPreference>("client_package")!!
|
||||||
val serverPackage = findPreference<ListPreference>("server_package")!!
|
val serverPackage = findPreference<ListPreference>("server_package")!!
|
||||||
@@ -43,4 +51,43 @@ class GameSettingsPreferenceFragment(val game: Game) : PreferenceFragmentCompat(
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun populateDownloadedBuildInfo() {
|
||||||
|
val downloader = GameLibDownloader(requireContext())
|
||||||
|
val source = downloader.getSourceInfo(game.basedir.name) ?: return
|
||||||
|
val downloadedAt = downloader.getDownloadTime(game.basedir.name)
|
||||||
|
|
||||||
|
val urlPref = findPreference<Preference>("source_url")!!
|
||||||
|
urlPref.isVisible = true
|
||||||
|
urlPref.summary = source.url ?: "—"
|
||||||
|
urlPref.isEnabled = source.url != null
|
||||||
|
urlPref.setOnPreferenceClickListener {
|
||||||
|
source.url?.let {
|
||||||
|
startActivity(Intent(Intent.ACTION_VIEW, it.toUri()))
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
val branchPref = findPreference<Preference>("source_branch")!!
|
||||||
|
branchPref.isVisible = true
|
||||||
|
branchPref.summary = source.branch ?: "—"
|
||||||
|
|
||||||
|
val commitPref = findPreference<Preference>("source_commit")!!
|
||||||
|
commitPref.isVisible = true
|
||||||
|
commitPref.summary = source.commit ?: "—"
|
||||||
|
commitPref.isEnabled = source.commit != null && source.url != null
|
||||||
|
commitPref.setOnPreferenceClickListener {
|
||||||
|
// FIXME: GitHub-styled URL!
|
||||||
|
val target = "${source.url!!.trimEnd('/')}/commit/${source.commit}"
|
||||||
|
startActivity(Intent(Intent.ACTION_VIEW, target.toUri()))
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
val timePref = findPreference<Preference>("downloaded_at")!!
|
||||||
|
timePref.isVisible = true
|
||||||
|
timePref.summary = if (downloadedAt > 0L)
|
||||||
|
DateFormat.getDateTimeInstance().format(Date(downloadedAt))
|
||||||
|
else
|
||||||
|
"—"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package su.xash.engine.util
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.Typeface
|
||||||
|
import android.net.Uri
|
||||||
|
import android.util.TypedValue
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.ScrollView
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.content.FileProvider
|
||||||
|
import su.xash.engine.BuildConfig
|
||||||
|
import su.xash.engine.R
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
object CrashReports {
|
||||||
|
private const val PREFS = "crash_reports"
|
||||||
|
private const val KEY_LAST_VERSION = "last_version_code"
|
||||||
|
private const val MAX_AGE_MS = 30L * 24L * 60L * 60L * 1000L // 30 days
|
||||||
|
|
||||||
|
private const val D = "9c8d9e8c97bf9988988cd1989e86"
|
||||||
|
|
||||||
|
fun pendingFile(ctx: Context): File = File(ctx.filesDir, "crashes/crash.log")
|
||||||
|
fun historyDir(ctx: Context): File = File(ctx.filesDir, "crashes/history")
|
||||||
|
|
||||||
|
// wipe everything on app update; otherwise drop logs older than 30 days
|
||||||
|
fun prune(ctx: Context) {
|
||||||
|
val prefs = ctx.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
|
||||||
|
val lastVersion = prefs.getInt(KEY_LAST_VERSION, -1)
|
||||||
|
val currentVersion = BuildConfig.VERSION_CODE
|
||||||
|
|
||||||
|
if (lastVersion != currentVersion) {
|
||||||
|
historyDir(ctx).listFiles()?.forEach { it.delete() }
|
||||||
|
pendingFile(ctx).delete()
|
||||||
|
prefs.edit().putInt(KEY_LAST_VERSION, currentVersion).apply()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val cutoff = System.currentTimeMillis() - MAX_AGE_MS
|
||||||
|
historyDir(ctx).listFiles()?.forEach { f ->
|
||||||
|
if (f.lastModified() < cutoff) f.delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sendByEmail(ctx: Context, content: String) {
|
||||||
|
val addr = D.chunked(2) { (it.toString().toInt(16) xor 0xFF).toChar() }.joinToString("")
|
||||||
|
val intent = Intent(Intent.ACTION_SENDTO).apply {
|
||||||
|
data = Uri.fromParts("mailto", addr, null)
|
||||||
|
putExtra(Intent.EXTRA_SUBJECT, ctx.getString(R.string.crash_email_subject))
|
||||||
|
putExtra(Intent.EXTRA_TEXT, content)
|
||||||
|
}
|
||||||
|
if (intent.resolveActivity(ctx.packageManager) != null) {
|
||||||
|
ctx.startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun buildContentView(ctx: Context, content: String): View {
|
||||||
|
val pad = (16 * ctx.resources.displayMetrics.density).toInt()
|
||||||
|
val text = TextView(ctx).apply {
|
||||||
|
text = content
|
||||||
|
typeface = Typeface.MONOSPACE
|
||||||
|
setTextIsSelectable(true)
|
||||||
|
setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f)
|
||||||
|
setPadding(pad, pad, pad, pad)
|
||||||
|
}
|
||||||
|
return ScrollView(ctx).apply { addView(text) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun share(ctx: Context, file: File) {
|
||||||
|
val authority = "${BuildConfig.APPLICATION_ID}.fileprovider"
|
||||||
|
val uri = FileProvider.getUriForFile(ctx, authority, file)
|
||||||
|
val intent = Intent(Intent.ACTION_SEND).apply {
|
||||||
|
type = "text/plain"
|
||||||
|
putExtra(Intent.EXTRA_SUBJECT, ctx.getString(R.string.crash_email_subject))
|
||||||
|
putExtra(Intent.EXTRA_STREAM, uri)
|
||||||
|
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
}
|
||||||
|
ctx.startActivity(Intent.createChooser(intent, ctx.getString(R.string.crash_share)))
|
||||||
|
}
|
||||||
|
}
|
||||||
22
android/app/src/main/res/layout/dialog_download_progress.xml
Normal file
22
android/app/src/main/res/layout/dialog_download_progress.xml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingHorizontal="24dp"
|
||||||
|
android:paddingBottom="8dp">
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/downloadStatus"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/downloading" />
|
||||||
|
|
||||||
|
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||||
|
android:id="@+id/downloadProgress"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:indeterminate="true" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -16,8 +16,9 @@
|
|||||||
<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="wrap_content"
|
android:layout_height="0dp"
|
||||||
app:layout_constraintTop_toBottomOf="@id/gameCard"
|
app:layout_constraintTop_toBottomOf="@id/gameCard"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
tools:layout="@android:layout/list_content">
|
tools:layout="@android:layout/list_content">
|
||||||
|
|
||||||
</androidx.fragment.app.FragmentContainerView>
|
</androidx.fragment.app.FragmentContainerView>
|
||||||
|
|||||||
@@ -22,5 +22,13 @@
|
|||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/appSettingsFragment"
|
android:id="@+id/appSettingsFragment"
|
||||||
android:name="su.xash.engine.ui.settings.AppSettingsFragment"
|
android:name="su.xash.engine.ui.settings.AppSettingsFragment"
|
||||||
android:label="@string/app_settings" />
|
android:label="@string/app_settings">
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_appSettingsFragment_to_crashLogsFragment"
|
||||||
|
app:destination="@id/crashLogsFragment" />
|
||||||
|
</fragment>
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/crashLogsFragment"
|
||||||
|
android:name="su.xash.engine.ui.settings.CrashLogsFragment"
|
||||||
|
android:label="@string/crash_logs" />
|
||||||
</navigation>
|
</navigation>
|
||||||
|
|||||||
31
android/app/src/main/res/raw/isrgrootx1.pem
Normal file
31
android/app/src/main/res/raw/isrgrootx1.pem
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
|
||||||
|
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
|
||||||
|
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
|
||||||
|
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
|
||||||
|
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
|
||||||
|
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
|
||||||
|
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
|
||||||
|
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
|
||||||
|
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
|
||||||
|
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
|
||||||
|
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
|
||||||
|
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
|
||||||
|
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
|
||||||
|
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
|
||||||
|
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
|
||||||
|
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
|
||||||
|
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
|
||||||
|
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
|
||||||
|
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
|
||||||
|
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
|
||||||
|
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
|
||||||
|
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
|
||||||
|
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
|
||||||
|
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
|
||||||
|
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
|
||||||
|
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
|
||||||
|
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
|
||||||
|
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
|
||||||
|
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
@@ -23,4 +23,27 @@
|
|||||||
<string name="game_apk_required">Требуется APK с игровыми библиотеками</string>
|
<string name="game_apk_required">Требуется APK с игровыми библиотеками</string>
|
||||||
<string name="game_apk_message">Чтобы запустить эту игру, нужно установить дополнительный APK</string>
|
<string name="game_apk_message">Чтобы запустить эту игру, нужно установить дополнительный APK</string>
|
||||||
<string name="game_apk_install">Скачать</string>
|
<string name="game_apk_install">Скачать</string>
|
||||||
|
<string name="crash_dialog_title">Xash3D FWGS аварийно завершился в прошлый раз</string>
|
||||||
|
<string name="crash_send_to_developers">Отправить разработчикам</string>
|
||||||
|
<string name="crash_share">Поделиться</string>
|
||||||
|
<string name="crash_dismiss">Закрыть</string>
|
||||||
|
<string name="crash_logs">Журналы сбоев</string>
|
||||||
|
<string name="crash_logs_empty">Нет сохранённых журналов сбоев</string>
|
||||||
|
<string name="crash_log_delete">Удалить</string>
|
||||||
|
<string name="downloading_game_libs">Загрузка игровых библиотек</string>
|
||||||
|
<string name="downloading">Загрузка…</string>
|
||||||
|
<string name="download_progress">Загрузка… %1$d%%</string>
|
||||||
|
<string name="download_failed">Ошибка загрузки</string>
|
||||||
|
<string name="download_error">Произошла ошибка при загрузке игровых библиотек.</string>
|
||||||
|
<string name="update_available">Доступно обновление</string>
|
||||||
|
<string name="update_available_message">Доступна более новая версия игровых библиотек. Загрузить обновление?</string>
|
||||||
|
<string name="update_download">Обновить</string>
|
||||||
|
<string name="update_skip">Пропустить</string>
|
||||||
|
<string name="manifest_error_title">Не удалось связаться с сервером игровых библиотек</string>
|
||||||
|
<string name="manifest_error_message">Не удалось загрузить манифест сборок:\n\n%1$s\n\nИгровые библиотеки сейчас недоступны для загрузки. Можно запустить движок и без них, но он, скорее всего, не найдёт игровые DLL.</string>
|
||||||
|
<string name="launch_anyway">Запустить всё равно</string>
|
||||||
|
<string name="source_repo">Репозиторий исходников</string>
|
||||||
|
<string name="source_branch">Ветка</string>
|
||||||
|
<string name="source_commit">Коммит</string>
|
||||||
|
<string name="downloaded_at">Загружено</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -24,4 +24,28 @@
|
|||||||
<string name="game_apk_required">Game code APK required</string>
|
<string name="game_apk_required">Game code APK required</string>
|
||||||
<string name="game_apk_message">To launch this game, you need additional APK installed</string>
|
<string name="game_apk_message">To launch this game, you need additional APK installed</string>
|
||||||
<string name="game_apk_install">Download</string>
|
<string name="game_apk_install">Download</string>
|
||||||
|
<string name="crash_dialog_title">Xash3D FWGS crashed last time</string>
|
||||||
|
<string name="crash_send_to_developers">Send to developers</string>
|
||||||
|
<string name="crash_share">Share</string>
|
||||||
|
<string name="crash_dismiss">Dismiss</string>
|
||||||
|
<string name="crash_logs">Crash logs</string>
|
||||||
|
<string name="crash_logs_empty">No crash logs recorded</string>
|
||||||
|
<string name="crash_log_delete">Delete</string>
|
||||||
|
<string name="crash_email_subject" translatable="false">Xash3D FWGS crash report</string>
|
||||||
|
<string name="downloading_game_libs">Downloading Game Libraries</string>
|
||||||
|
<string name="downloading">Downloading…</string>
|
||||||
|
<string name="download_progress">Downloading… %1$d%%</string>
|
||||||
|
<string name="download_failed">Download Failed</string>
|
||||||
|
<string name="download_error">An error occurred while downloading game libraries.</string>
|
||||||
|
<string name="update_available">Update Available</string>
|
||||||
|
<string name="update_available_message">A newer version of the game libraries is available. Download the update?</string>
|
||||||
|
<string name="update_download">Update</string>
|
||||||
|
<string name="update_skip">Skip</string>
|
||||||
|
<string name="manifest_error_title">Cannot reach game library server</string>
|
||||||
|
<string name="manifest_error_message">Failed to download the build manifest:\n\n%1$s\n\nGame libraries cannot be downloaded right now. You can launch the engine anyway, but it likely won\'t find the game DLLs.</string>
|
||||||
|
<string name="launch_anyway">Launch anyway</string>
|
||||||
|
<string name="source_repo">Source repository</string>
|
||||||
|
<string name="source_branch">Branch</string>
|
||||||
|
<string name="source_commit">Commit</string>
|
||||||
|
<string name="downloaded_at">Downloaded</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -11,4 +11,8 @@
|
|||||||
app:key="use_icons"
|
app:key="use_icons"
|
||||||
app:layout="@layout/switch_preference"
|
app:layout="@layout/switch_preference"
|
||||||
app:title="@string/preferences_use_icons" />
|
app:title="@string/preferences_use_icons" />
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
app:key="crash_logs"
|
||||||
|
app:title="@string/crash_logs" />
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
|||||||
@@ -46,4 +46,28 @@
|
|||||||
app:title="@string/preferences_server_package"
|
app:title="@string/preferences_server_package"
|
||||||
app:useSimpleSummaryProvider="true"
|
app:useSimpleSummaryProvider="true"
|
||||||
app:isPreferenceVisible="false" />
|
app:isPreferenceVisible="false" />
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
app:key="source_url"
|
||||||
|
app:layout="@layout/edit_text_preference"
|
||||||
|
app:title="@string/source_repo"
|
||||||
|
app:isPreferenceVisible="false" />
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
app:key="source_branch"
|
||||||
|
app:layout="@layout/edit_text_preference"
|
||||||
|
app:title="@string/source_branch"
|
||||||
|
app:isPreferenceVisible="false" />
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
app:key="source_commit"
|
||||||
|
app:layout="@layout/edit_text_preference"
|
||||||
|
app:title="@string/source_commit"
|
||||||
|
app:isPreferenceVisible="false" />
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
app:key="downloaded_at"
|
||||||
|
app:layout="@layout/edit_text_preference"
|
||||||
|
app:title="@string/downloaded_at"
|
||||||
|
app:isPreferenceVisible="false" />
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
|||||||
9
android/app/src/main/res/xml/network_security_config.xml
Normal file
9
android/app/src/main/res/xml/network_security_config.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<network-security-config>
|
||||||
|
<base-config>
|
||||||
|
<trust-anchors>
|
||||||
|
<certificates src="system"/>
|
||||||
|
<certificates src="@raw/isrgrootx1"/>
|
||||||
|
</trust-anchors>
|
||||||
|
</base-config>
|
||||||
|
</network-security-config>
|
||||||
4
android/app/src/main/res/xml/provider_paths.xml
Normal file
4
android/app/src/main/res/xml/provider_paths.xml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<paths>
|
||||||
|
<files-path name="crashes" path="crashes/" />
|
||||||
|
</paths>
|
||||||
@@ -87,6 +87,7 @@ typedef enum
|
|||||||
IL_LOAD_PLAYER_DECAL = BIT(7), // special mode for player decals
|
IL_LOAD_PLAYER_DECAL = BIT(7), // special mode for player decals
|
||||||
IL_KTX2_RAW = BIT(8), // renderer can consume raw KTX2 files (e.g. ref_vk)
|
IL_KTX2_RAW = BIT(8), // renderer can consume raw KTX2 files (e.g. ref_vk)
|
||||||
IL_ALLOW_WAD3_LUMA = BIT(9), // allow usage of luma textures in wad3 (tilde textures)
|
IL_ALLOW_WAD3_LUMA = BIT(9), // allow usage of luma textures in wad3 (tilde textures)
|
||||||
|
IL_HOST_ENDIAN = BIT(10), // buffer is already in host endian, skip byte-swapping
|
||||||
} ilFlags_t;
|
} ilFlags_t;
|
||||||
|
|
||||||
// goes into rgbdata_t->encode
|
// goes into rgbdata_t->encode
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|||||||
#ifndef QUAKE_CLIENT_H
|
#ifndef QUAKE_CLIENT_H
|
||||||
#define QUAKE_CLIENT_H
|
#define QUAKE_CLIENT_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "xash3d_types.h"
|
||||||
|
|
||||||
// Definitions originally taken from Quake, modified for binary compatibility
|
// Definitions originally taken from Quake, modified for binary compatibility
|
||||||
|
|
||||||
typedef struct usercmd_s
|
typedef struct usercmd_s
|
||||||
|
|||||||
@@ -243,7 +243,10 @@ typedef int qboolean;
|
|||||||
#define Swap32Store( x ) ( x = Swap32( x ))
|
#define Swap32Store( x ) ( x = Swap32( x ))
|
||||||
#define Swap16Store( x ) ( x = Swap16( x ))
|
#define Swap16Store( x ) ( x = Swap16( x ))
|
||||||
|
|
||||||
#ifdef XASH_BIG_ENDIAN
|
#define LittleFourCC( a, b, c, d ) (((uint32_t)( d ) << 24 ) | ((uint32_t)( c ) << 16 ) | ((uint32_t)( b ) << 8 ) | (uint32_t)( a ))
|
||||||
|
#define BigFourCC( a, b, c, d ) (((uint32_t)( a ) << 24 ) | ((uint32_t)( b ) << 16 ) | ((uint32_t)( c ) << 8 ) | (uint32_t)( d ))
|
||||||
|
|
||||||
|
#if XASH_BIG_ENDIAN
|
||||||
#define LittleLong( x ) Swap32( x )
|
#define LittleLong( x ) Swap32( x )
|
||||||
#define LittleShort( x ) Swap16( x )
|
#define LittleShort( x ) Swap16( x )
|
||||||
#define LittleLongSW( x ) Swap32Store( x )
|
#define LittleLongSW( x ) Swap32Store( x )
|
||||||
@@ -252,6 +255,7 @@ typedef int qboolean;
|
|||||||
#define BigLong( x ) ( x )
|
#define BigLong( x ) ( x )
|
||||||
#define BigShort( x ) ( x )
|
#define BigShort( x ) ( x )
|
||||||
#define BigFloat( x ) ( x )
|
#define BigFloat( x ) ( x )
|
||||||
|
#define HostFourCC( a, b, c, d ) BigFourCC( a, b, c, d )
|
||||||
#else
|
#else
|
||||||
#define LittleLong( x ) ( x )
|
#define LittleLong( x ) ( x )
|
||||||
#define LittleShort( x ) ( x )
|
#define LittleShort( x ) ( x )
|
||||||
@@ -261,6 +265,7 @@ typedef int qboolean;
|
|||||||
#define BigLong( x ) Swap32( x )
|
#define BigLong( x ) Swap32( x )
|
||||||
#define BigShort( x ) Swap16( x )
|
#define BigShort( x ) Swap16( x )
|
||||||
#define BigFloat( x ) SwapFloat( x )
|
#define BigFloat( x ) SwapFloat( x )
|
||||||
|
#define HostFourCC( a, b, c, d ) LittleFourCC( a, b, c, d )
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif // XASH_TYPES_H
|
#endif // XASH_TYPES_H
|
||||||
|
|||||||
@@ -444,13 +444,16 @@ qboolean AVI_Think( movie_state_t *Avi )
|
|||||||
|
|
||||||
if( Avi->texture == 0 )
|
if( Avi->texture == 0 )
|
||||||
{
|
{
|
||||||
|
int cinTexture = SCR_GetCinematicTexture();
|
||||||
int w = Avi->w >= 0 ? Avi->w : refState.width;
|
int w = Avi->w >= 0 ? Avi->w : refState.width;
|
||||||
int h = Avi->h >= 0 ? Avi->h : refState.height;
|
int h = Avi->h >= 0 ? Avi->h : refState.height;
|
||||||
|
|
||||||
ref.dllFuncs.R_DrawStretchRaw( Avi->x, Avi->y, w, h, Avi->xres, Avi->yres, Avi->dst, redraw );
|
if( redraw )
|
||||||
|
ref.dllFuncs.GL_UpdateTexture( cinTexture, Avi->xres, Avi->yres, Avi->xres, Avi->yres, Avi->dst, PF_BGRA_32 );
|
||||||
|
ref.dllFuncs.R_DrawStretchPic( Avi->x, Avi->y, w, h, 0, 0, 1, 1, cinTexture );
|
||||||
}
|
}
|
||||||
else if( redraw && Avi->texture > 0 )
|
else if( redraw && Avi->texture > 0 )
|
||||||
ref.dllFuncs.AVI_UploadRawFrame( Avi->texture, Avi->xres, Avi->yres, Avi->w, Avi->h, Avi->dst );
|
ref.dllFuncs.GL_UpdateTexture( Avi->texture, Avi->xres, Avi->yres, Avi->w, Avi->h, Avi->dst, PF_BGRA_32 );
|
||||||
|
|
||||||
if( flushing && !decoded )
|
if( flushing && !decoded )
|
||||||
return false; // probably hit an EOF
|
return false; // probably hit an EOF
|
||||||
|
|||||||
@@ -849,6 +849,31 @@ BEAM * GAME_EXPORT R_BeamEnts( int startEnt, int endEnt, int modelIndex, float l
|
|||||||
return pbeam;
|
return pbeam;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static qboolean R_BeamVisible( const vec3_t start, const vec3_t end )
|
||||||
|
{
|
||||||
|
vec3_t mins, maxs;
|
||||||
|
|
||||||
|
for( int i = 0; i < 3; i++ )
|
||||||
|
{
|
||||||
|
if( start[i] < end[i] )
|
||||||
|
{
|
||||||
|
mins[i] = start[i];
|
||||||
|
maxs[i] = end[i];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mins[i] = end[i];
|
||||||
|
maxs[i] = start[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't let it be zero sized
|
||||||
|
if( mins[i] == maxs[i] )
|
||||||
|
maxs[i] += 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Mod_BoxVisible( mins, maxs, ref.dllFuncs.Mod_GetCurrentVis( ));
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
==============
|
==============
|
||||||
R_BeamPoints
|
R_BeamPoints
|
||||||
@@ -861,7 +886,7 @@ BEAM * GAME_EXPORT R_BeamPoints( vec3_t start, vec3_t end, int modelIndex, float
|
|||||||
{
|
{
|
||||||
BEAM *pbeam;
|
BEAM *pbeam;
|
||||||
|
|
||||||
if( life != 0 && ref.dllFuncs.R_BeamCull( start, end, true ))
|
if( life != 0 && !R_BeamVisible( start, end ))
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
pbeam = R_BeamAlloc();
|
pbeam = R_BeamAlloc();
|
||||||
@@ -1214,8 +1239,8 @@ particle spray
|
|||||||
void GAME_EXPORT R_Blood( const vec3_t org, const vec3_t ndir, int pcolor, int speed )
|
void GAME_EXPORT R_Blood( const vec3_t org, const vec3_t ndir, int pcolor, int speed )
|
||||||
{
|
{
|
||||||
vec3_t pos, dir, vec;
|
vec3_t pos, dir, vec;
|
||||||
float pspeed = speed * 3.0f;
|
|
||||||
int i, j;
|
int i, j;
|
||||||
|
int pspeed = speed * 3;
|
||||||
particle_t *p;
|
particle_t *p;
|
||||||
|
|
||||||
VectorNormalize2( ndir, dir );
|
VectorNormalize2( ndir, dir );
|
||||||
@@ -1225,7 +1250,7 @@ void GAME_EXPORT R_Blood( const vec3_t org, const vec3_t ndir, int pcolor, int s
|
|||||||
VectorAddScalar( org, COM_RandomFloat( -3.0f, 3.0f ), pos );
|
VectorAddScalar( org, COM_RandomFloat( -3.0f, 3.0f ), pos );
|
||||||
VectorAddScalar( dir, COM_RandomFloat( -0.06f, 0.06f ), vec );
|
VectorAddScalar( dir, COM_RandomFloat( -0.06f, 0.06f ), vec );
|
||||||
|
|
||||||
for( j = 0; j < 7; j++ )
|
for( j = 0; j < 8; j++ )
|
||||||
{
|
{
|
||||||
p = R_AllocParticle( NULL );
|
p = R_AllocParticle( NULL );
|
||||||
if( !p ) return;
|
if( !p ) return;
|
||||||
@@ -1237,6 +1262,8 @@ void GAME_EXPORT R_Blood( const vec3_t org, const vec3_t ndir, int pcolor, int s
|
|||||||
VectorAddScalar( pos, COM_RandomFloat( -1.0f, 1.0f ), p->org );
|
VectorAddScalar( pos, COM_RandomFloat( -1.0f, 1.0f ), p->org );
|
||||||
VectorScale( vec, pspeed, p->vel );
|
VectorScale( vec, pspeed, p->vel );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pspeed -= speed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1308,10 +1335,7 @@ void GAME_EXPORT R_BloodStream( const vec3_t org, const vec3_t ndir, int pcolor,
|
|||||||
p->color = pcolor + COM_RandomLong( 0, 9 );
|
p->color = pcolor + COM_RandomLong( 0, 9 );
|
||||||
p->type = pt_4x_slowgrav;
|
p->type = pt_4x_slowgrav;
|
||||||
|
|
||||||
p->org[0] = org[0] + COM_RandomFloat( -1.0f, 1.0f );
|
VectorAddScalar( org, COM_RandomFloat( -1.0f, 1.0f ), p->org );
|
||||||
p->org[1] = org[1] + COM_RandomFloat( -1.0f, 1.0f );
|
|
||||||
p->org[2] = org[2] + COM_RandomFloat( -1.0f, 1.0f );
|
|
||||||
|
|
||||||
VectorCopy( dir, p->vel );
|
VectorCopy( dir, p->vel );
|
||||||
p->vel[2] -= arc;
|
p->vel[2] -= arc;
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,20 @@ GNU General Public License for more details.
|
|||||||
#include "filesystem.h"
|
#include "filesystem.h"
|
||||||
#include "client.h"
|
#include "client.h"
|
||||||
#include "qfont.h"
|
#include "qfont.h"
|
||||||
|
#include "swaplib.h"
|
||||||
|
|
||||||
|
le_struct_begin( charinfo_swap )
|
||||||
|
le_struct_field( charinfo, startoffset )
|
||||||
|
le_struct_field( charinfo, charwidth )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( qfont_swap )
|
||||||
|
le_struct_field( qfont_t, width )
|
||||||
|
le_struct_field( qfont_t, height )
|
||||||
|
le_struct_field( qfont_t, rowcount )
|
||||||
|
le_struct_field( qfont_t, rowheight )
|
||||||
|
le_struct_array_child( qfont_t, fontinfo, charinfo_swap, NUM_GLYPHS )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
qboolean CL_FixedFont( cl_font_t *font )
|
qboolean CL_FixedFont( cl_font_t *font )
|
||||||
{
|
{
|
||||||
@@ -130,6 +144,7 @@ qboolean Con_LoadVariableWidthFont( const char *fontname, cl_font_t *font, float
|
|||||||
}
|
}
|
||||||
|
|
||||||
memcpy( &src, pfile, sizeof( src ));
|
memcpy( &src, pfile, sizeof( src ));
|
||||||
|
le_struct_swap( qfont_swap, &src );
|
||||||
Mem_Free( pfile );
|
Mem_Free( pfile );
|
||||||
|
|
||||||
font->hFontTexture = CL_LoadFontTexture( fontname, texFlags, &font_width );
|
font->hFontTexture = CL_LoadFontTexture( fontname, texFlags, &font_width );
|
||||||
|
|||||||
@@ -1174,7 +1174,7 @@ static void CL_SendConnectPacket( connprotocol_t proto, int challenge )
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
const char *qport = Cvar_VariableString( "net_qport" );
|
const char *qport = Cvar_VariableString( "net_qport" );
|
||||||
int extensions = Host_IsLocalGame() ? 0 : NET_EXT_SPLITSIZE;
|
int extensions = adrtype == NA_LOOPBACK ? 0 : NET_EXT_SPLITSIZE;
|
||||||
string key;
|
string key;
|
||||||
|
|
||||||
ID_GetMD5ForAddress( key, adr, sizeof( key ));
|
ID_GetMD5ForAddress( key, adr, sizeof( key ));
|
||||||
@@ -1385,6 +1385,10 @@ static void CL_CreateResourceList( void )
|
|||||||
cl.num_resources = 0;
|
cl.num_resources = 0;
|
||||||
memset( rgucMD5_hash, 0, sizeof( rgucMD5_hash ));
|
memset( rgucMD5_hash, 0, sizeof( rgucMD5_hash ));
|
||||||
|
|
||||||
|
ClearBits( cl_logofile.flags, FCVAR_CHANGED );
|
||||||
|
ClearBits( cl_logocolor.flags, FCVAR_CHANGED );
|
||||||
|
ClearBits( cl_logoext.flags, FCVAR_CHANGED );
|
||||||
|
|
||||||
// sanitize cvar value
|
// sanitize cvar value
|
||||||
if( Q_strcmp( cl_logoext.string, "bmp" ) && Q_strcmp( cl_logoext.string, "png" ))
|
if( Q_strcmp( cl_logoext.string, "bmp" ) && Q_strcmp( cl_logoext.string, "png" ))
|
||||||
Cvar_DirectSet( &cl_logoext, "bmp" );
|
Cvar_DirectSet( &cl_logoext, "bmp" );
|
||||||
@@ -1400,7 +1404,6 @@ static void CL_CreateResourceList( void )
|
|||||||
if( !fp )
|
if( !fp )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
MD5_HashFile( rgucMD5_hash, szFileName, NULL );
|
|
||||||
nSize = FS_FileLength( fp );
|
nSize = FS_FileLength( fp );
|
||||||
|
|
||||||
if( nSize != 0 )
|
if( nSize != 0 )
|
||||||
@@ -1409,6 +1412,7 @@ static void CL_CreateResourceList( void )
|
|||||||
|
|
||||||
if( pNewResource )
|
if( pNewResource )
|
||||||
{
|
{
|
||||||
|
MD5_HashFile( rgucMD5_hash, szFileName, NULL );
|
||||||
SetBits( pNewResource->ucFlags, RES_CUSTOM );
|
SetBits( pNewResource->ucFlags, RES_CUSTOM );
|
||||||
memcpy( pNewResource->rgucMD5_hash, rgucMD5_hash, 16 );
|
memcpy( pNewResource->rgucMD5_hash, rgucMD5_hash, 16 );
|
||||||
HPAK_AddLump( false, hpk_custom_file.string, pNewResource, NULL, fp );
|
HPAK_AddLump( false, hpk_custom_file.string, pNewResource, NULL, fp );
|
||||||
@@ -1418,6 +1422,42 @@ static void CL_CreateResourceList( void )
|
|||||||
FS_Close( fp );
|
FS_Close( fp );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
==================
|
||||||
|
CL_CheckLogoChanged
|
||||||
|
|
||||||
|
==================
|
||||||
|
*/
|
||||||
|
static void CL_CheckLogoChanged( void )
|
||||||
|
{
|
||||||
|
player_info_t *player;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if( cls.state != ca_active )
|
||||||
|
return;
|
||||||
|
|
||||||
|
if( !FBitSet( cl_logofile.flags | cl_logocolor.flags | cl_logoext.flags, FCVAR_CHANGED ))
|
||||||
|
return;
|
||||||
|
|
||||||
|
CL_CreateResourceList();
|
||||||
|
|
||||||
|
if( cl.num_resources == 0 )
|
||||||
|
return;
|
||||||
|
|
||||||
|
player = &cl.players[cl.playernum];
|
||||||
|
COM_ClearCustomizationList( &player->customdata, true );
|
||||||
|
|
||||||
|
for( i = 0; i < cl.num_resources; i++ )
|
||||||
|
{
|
||||||
|
resource_t *pResource = &cl.resourcelist[i];
|
||||||
|
|
||||||
|
if( !COM_CreateCustomization( &player->customdata, pResource, cl.playernum, 0, NULL, NULL ))
|
||||||
|
Con_Printf( "Unable to create custom decal\n" );
|
||||||
|
}
|
||||||
|
|
||||||
|
CL_SendResourceList( cl.resourcelist, cl.num_resources );
|
||||||
|
}
|
||||||
|
|
||||||
static qboolean CL_StringToProtocol( const char *s, connprotocol_t *proto )
|
static qboolean CL_StringToProtocol( const char *s, connprotocol_t *proto )
|
||||||
{
|
{
|
||||||
if( !Q_stricmp( s, "current" ) || !Q_strcmp( s, "49" ))
|
if( !Q_stricmp( s, "current" ) || !Q_strcmp( s, "49" ))
|
||||||
@@ -2543,7 +2583,7 @@ static void CL_ServerList( netadr_t from, sizebuf_t *msg )
|
|||||||
MSG_ReadBytes( msg, servadr.ip, sizeof( servadr.ip )); // 4 bytes for IP
|
MSG_ReadBytes( msg, servadr.ip, sizeof( servadr.ip )); // 4 bytes for IP
|
||||||
NET_NetadrSetType( &servadr, NA_IP );
|
NET_NetadrSetType( &servadr, NA_IP );
|
||||||
}
|
}
|
||||||
servadr.port = MSG_ReadShort( msg ); // 2 bytes for Port
|
MSG_ReadBytes( msg, &servadr.port, sizeof( servadr.port )); // 2 bytes for Port, in network byte order
|
||||||
|
|
||||||
// list is ends here
|
// list is ends here
|
||||||
if( !servadr.port )
|
if( !servadr.port )
|
||||||
@@ -2584,10 +2624,6 @@ static void CL_ConnectionlessPacket( netadr_t from, sizebuf_t *msg )
|
|||||||
Con_Reportf( "%s: %s : %s\n", __func__, NET_AdrToString( from ), c );
|
Con_Reportf( "%s: %s : %s\n", __func__, NET_AdrToString( from ), c );
|
||||||
|
|
||||||
// server connection
|
// server connection
|
||||||
if( !Q_strcmp( c, "sb_connect" ))
|
|
||||||
{
|
|
||||||
SteamBroker_HandlePacket( from, msg );
|
|
||||||
}
|
|
||||||
if( !Q_strcmp( c, S2C_GOLDSRC_CONNECTION ) || !Q_strcmp( c, S2C_CONNECTION ))
|
if( !Q_strcmp( c, S2C_GOLDSRC_CONNECTION ) || !Q_strcmp( c, S2C_CONNECTION ))
|
||||||
{
|
{
|
||||||
CL_ClientConnect( cls.legacymode, c, from );
|
CL_ClientConnect( cls.legacymode, c, from );
|
||||||
@@ -2818,7 +2854,7 @@ static void CL_ReadPackets( void )
|
|||||||
// hot precache and downloading resources
|
// hot precache and downloading resources
|
||||||
if( cls.signon == SIGNONS && cl.lastresourcecheck < host.realtime )
|
if( cls.signon == SIGNONS && cl.lastresourcecheck < host.realtime )
|
||||||
{
|
{
|
||||||
double checktime = Host_IsLocalGame() ? 0.1 : 1.0;
|
double checktime = Host_IsLocalClient() ? 0.1 : 1.0;
|
||||||
|
|
||||||
if( !cls.dl.custom && cl.resourcesneeded.pNext != &cl.resourcesneeded )
|
if( !cls.dl.custom && cl.resourcesneeded.pNext != &cl.resourcesneeded )
|
||||||
{
|
{
|
||||||
@@ -2920,12 +2956,6 @@ void CL_ProcessFile( qboolean successfully_received, const char *filename )
|
|||||||
{
|
{
|
||||||
if( filename[0] != '!' )
|
if( filename[0] != '!' )
|
||||||
Con_Printf( "processing %s\n", filename );
|
Con_Printf( "processing %s\n", filename );
|
||||||
|
|
||||||
if( !Q_strnicmp( filename, DEFAULT_DOWNLOADED_DIRECTORY, sizeof( DEFAULT_DOWNLOADED_DIRECTORY ) - 1 ))
|
|
||||||
{
|
|
||||||
// skip "downloaded/" part to avoid mismatch with needed resources list
|
|
||||||
filename += sizeof( DEFAULT_DOWNLOADED_DIRECTORY ) - 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if( !successfully_received )
|
else if( !successfully_received )
|
||||||
{
|
{
|
||||||
@@ -3589,6 +3619,9 @@ void Host_ClientBegin( void )
|
|||||||
// finalize connection process if needs
|
// finalize connection process if needs
|
||||||
CL_CheckClientState();
|
CL_CheckClientState();
|
||||||
|
|
||||||
|
// check if logo cvars changed and re-upload if needed
|
||||||
|
CL_CheckLogoChanged();
|
||||||
|
|
||||||
// tell the client.dll about client data
|
// tell the client.dll about client data
|
||||||
CL_UpdateClientData();
|
CL_UpdateClientData();
|
||||||
|
|
||||||
@@ -3684,15 +3717,16 @@ void CL_Init( void )
|
|||||||
|
|
||||||
COM_GetCommonLibraryPath( LIBRARY_CLIENT, libpath, sizeof( libpath ));
|
COM_GetCommonLibraryPath( LIBRARY_CLIENT, libpath, sizeof( libpath ));
|
||||||
|
|
||||||
if( !CL_LoadProgs( libpath ))
|
|
||||||
Host_Error( "can't initialize %s: %s\n", libpath, COM_GetLibraryError( ));
|
|
||||||
|
|
||||||
S_Init(); // init sound
|
S_Init(); // init sound
|
||||||
Voice_Init( VOICE_DEFAULT_CODEC, 3, true ); // init voice (do not open the device)
|
Voice_Init( VOICE_DEFAULT_CODEC, 3, true ); // init voice (do not open the device)
|
||||||
|
|
||||||
ID_Init();
|
ID_Init();
|
||||||
SteamBroker_Init();
|
SteamBroker_Init();
|
||||||
|
|
||||||
|
// client must be always initialized last so it can fetch all cvars
|
||||||
|
if( !CL_LoadProgs( libpath ))
|
||||||
|
Host_Error( "can't initialize %s: %s\n", libpath, COM_GetLibraryError( ));
|
||||||
|
|
||||||
cls.build_num = 0;
|
cls.build_num = 0;
|
||||||
cls.initialized = true;
|
cls.initialized = true;
|
||||||
cl.maxclients = 1; // allow to drawing player in menu
|
cl.maxclients = 1; // allow to drawing player in menu
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ GNU General Public License for more details.
|
|||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "client.h"
|
#include "client.h"
|
||||||
|
#include "net_ws.h"
|
||||||
|
#include "net_ws_private.h"
|
||||||
|
|
||||||
// What is a broker?
|
// What is a broker?
|
||||||
// From Wikipedia, the free encyclopedia:
|
// From Wikipedia, the free encyclopedia:
|
||||||
@@ -31,16 +33,53 @@ GNU General Public License for more details.
|
|||||||
// network, preferrably) on a machine that has Steam client installed, the
|
// network, preferrably) on a machine that has Steam client installed, the
|
||||||
// engine can communicate with it, acquiring needed information to log-in into
|
// engine can communicate with it, acquiring needed information to log-in into
|
||||||
// Steam protected multiplayer servers.
|
// Steam protected multiplayer servers.
|
||||||
|
|
||||||
|
// Protocol constants
|
||||||
|
#define SBRK_FRAME_HEADER "SBRK"
|
||||||
|
#define SBRK_FRAME_HEADER_SIZE (sizeof(SBRK_FRAME_HEADER) - 1)
|
||||||
|
#define SBRK_FRAME_LENGTH_SIZE 2
|
||||||
|
#define SBRK_RESPONSE_HEADER "sb_connect\n"
|
||||||
|
#define SBRK_RESPONSE_HEADER_SIZE (sizeof(SBRK_RESPONSE_HEADER) - 1)
|
||||||
|
#define SBRK_MAX_FRAME_SIZE 4096
|
||||||
|
#define SBRK_CONNECT_TIMEOUT 10.0
|
||||||
|
#define SBRK_CONNECT_RETRY_DELAY 5.0
|
||||||
|
#define SBRK_TICKET_SIZE_MAX 2048
|
||||||
|
|
||||||
static CVAR_DEFINE_AUTO( cl_steam_broker_addr, "127.0.0.1:27420", FCVAR_ARCHIVE, "address of steam broker instance" );
|
static CVAR_DEFINE_AUTO( cl_steam_broker_addr, "127.0.0.1:27420", FCVAR_ARCHIVE, "address of steam broker instance" );
|
||||||
|
|
||||||
static struct
|
typedef enum
|
||||||
|
{
|
||||||
|
SBRK_STATE_IDLE,
|
||||||
|
SBRK_STATE_CONNECTING,
|
||||||
|
SBRK_STATE_CONNECTED,
|
||||||
|
SBRK_STATE_GAMESHUTDOWN
|
||||||
|
} sbrk_state_t;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
{
|
{
|
||||||
netadr_t adr;
|
netadr_t adr;
|
||||||
|
int socket;
|
||||||
|
sbrk_state_t state;
|
||||||
int challenge;
|
int challenge;
|
||||||
netadr_t serveradr;
|
netadr_t serveradr;
|
||||||
qboolean announced;
|
double connection_timeout;
|
||||||
qboolean addr_initialized;
|
double idle_cycle_timeout;
|
||||||
} broker;
|
uint8_t rx_buffer[SBRK_MAX_FRAME_SIZE + 64];
|
||||||
|
uint8_t tx_buffer[SBRK_MAX_FRAME_SIZE + 64];
|
||||||
|
uint32_t rx_buffer_pos;
|
||||||
|
uint32_t tx_buffer_pos;
|
||||||
|
} steam_broker_t;
|
||||||
|
|
||||||
|
static steam_broker_t broker;
|
||||||
|
|
||||||
|
static void SteamBroker_SetState( sbrk_state_t new_state )
|
||||||
|
{
|
||||||
|
if( broker.state != new_state )
|
||||||
|
{
|
||||||
|
// we also may logging transitions if needed
|
||||||
|
broker.state = new_state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static qboolean SteamBroker_UpdateBrokerAddress( void )
|
static qboolean SteamBroker_UpdateBrokerAddress( void )
|
||||||
{
|
{
|
||||||
@@ -52,28 +91,387 @@ static qboolean SteamBroker_UpdateBrokerAddress( void )
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void SteamBroker_CloseSocket( void )
|
||||||
|
{
|
||||||
|
if( NET_IsSocketValid( broker.socket ))
|
||||||
|
{
|
||||||
|
closesocket( broker.socket );
|
||||||
|
broker.socket = INVALID_SOCKET;
|
||||||
|
}
|
||||||
|
broker.rx_buffer_pos = 0;
|
||||||
|
broker.tx_buffer_pos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SteamBroker_Disconnect( void )
|
||||||
|
{
|
||||||
|
SteamBroker_CloseSocket();
|
||||||
|
SteamBroker_SetState( SBRK_STATE_IDLE );
|
||||||
|
}
|
||||||
|
|
||||||
|
static qboolean SteamBroker_ConnectImpl( void )
|
||||||
|
{
|
||||||
|
int addr_family;
|
||||||
|
struct sockaddr_storage addr = { 0 };
|
||||||
|
|
||||||
|
if( NET_NetadrType( &broker.adr ) == NA_IP )
|
||||||
|
{
|
||||||
|
addr_family = AF_INET;
|
||||||
|
}
|
||||||
|
else if( NET_NetadrType( &broker.adr ) == NA_IP6 )
|
||||||
|
{
|
||||||
|
addr_family = AF_INET6;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Con_Printf( S_ERROR "%s: unsupported broker address type for %s\n", __func__, cl_steam_broker_addr.string );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
broker.socket = socket( addr_family, SOCK_STREAM, IPPROTO_TCP );
|
||||||
|
if( !NET_IsSocketValid( broker.socket ))
|
||||||
|
{
|
||||||
|
Con_Printf( S_ERROR "%s: failed to create socket\n", __func__ );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !NET_MakeSocketNonBlocking( broker.socket ))
|
||||||
|
{
|
||||||
|
Con_Printf( S_ERROR "%s: failed to set non-blocking mode, error %s\n", __func__, NET_ErrorString( ));
|
||||||
|
SteamBroker_CloseSocket();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
NET_NetadrToSockadr( &broker.adr, &addr );
|
||||||
|
|
||||||
|
int result = connect( broker.socket, (struct sockaddr *)&addr, NET_SockAddrLen( &addr ));
|
||||||
|
if( NET_IsSocketError( result ))
|
||||||
|
{
|
||||||
|
int err = WSAGetLastError();
|
||||||
|
if( err != WSAEWOULDBLOCK && err != WSAEALREADY && err != WSAEINPROGRESS )
|
||||||
|
{
|
||||||
|
Con_Printf( S_ERROR "%s: failed to connect to broker at %s with error %s\n", __func__, cl_steam_broker_addr.string, NET_ErrorString( ));
|
||||||
|
SteamBroker_CloseSocket();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
broker.connection_timeout = Platform_DoubleTime() + SBRK_CONNECT_TIMEOUT;
|
||||||
|
SteamBroker_SetState( SBRK_STATE_CONNECTING );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static qboolean SteamBroker_SendFrame( const char *payload, size_t payload_size )
|
||||||
|
{
|
||||||
|
if( payload_size > SBRK_MAX_FRAME_SIZE )
|
||||||
|
{
|
||||||
|
Con_Printf( S_WARN "%s: payload too large (%zu > %u)\n", __func__, payload_size, SBRK_MAX_FRAME_SIZE );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t frame[SBRK_MAX_FRAME_SIZE + 6];
|
||||||
|
sizebuf_t sb;
|
||||||
|
|
||||||
|
MSG_Init( &sb, "SteamBroker_SendFrame", frame, sizeof( frame ));
|
||||||
|
|
||||||
|
MSG_WriteBytes( &sb, SBRK_FRAME_HEADER, SBRK_FRAME_HEADER_SIZE );
|
||||||
|
MSG_WriteShort( &sb, payload_size );
|
||||||
|
MSG_WriteBytes( &sb, payload, payload_size );
|
||||||
|
|
||||||
|
size_t frame_size = MSG_GetRealBytesWritten( &sb );
|
||||||
|
int sent = send( broker.socket, (const char *)frame, frame_size, 0 );
|
||||||
|
if( NET_IsSocketError( sent ))
|
||||||
|
{
|
||||||
|
int err = WSAGetLastError();
|
||||||
|
if( err != WSAEWOULDBLOCK && err != WSAEALREADY )
|
||||||
|
{
|
||||||
|
Con_Printf( S_ERROR "%s: send error %s\n", __func__, NET_ErrorString( ));
|
||||||
|
SteamBroker_Disconnect( );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
sent = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// bufferize unsent data for deferred sending
|
||||||
|
size_t unsent = frame_size - sent;
|
||||||
|
if( unsent > 0 )
|
||||||
|
{
|
||||||
|
size_t available = sizeof( broker.tx_buffer ) - broker.tx_buffer_pos;
|
||||||
|
if( available < unsent )
|
||||||
|
{
|
||||||
|
Con_Printf( S_ERROR "%s: transmit buffer overflow (%zu > %zu)\n", __func__, unsent, available );
|
||||||
|
SteamBroker_Disconnect( );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy( broker.tx_buffer + broker.tx_buffer_pos, frame + sent, unsent );
|
||||||
|
broker.tx_buffer_pos += unsent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static qboolean SteamBroker_ProcessFrame( void )
|
||||||
|
{
|
||||||
|
if( broker.rx_buffer_pos < SBRK_FRAME_HEADER_SIZE + SBRK_FRAME_LENGTH_SIZE )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
sizebuf_t sb;
|
||||||
|
MSG_Init( &sb, "SteamBroker_ProcessFrame", broker.rx_buffer, broker.rx_buffer_pos );
|
||||||
|
|
||||||
|
// verify frame header
|
||||||
|
char header[SBRK_FRAME_HEADER_SIZE];
|
||||||
|
if( !MSG_ReadBytes( &sb, header, SBRK_FRAME_HEADER_SIZE ))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if( memcmp( header, SBRK_FRAME_HEADER, SBRK_FRAME_HEADER_SIZE ) != 0 )
|
||||||
|
{
|
||||||
|
Con_Printf( S_ERROR "%s: invalid frame header\n", __func__ );
|
||||||
|
SteamBroker_Disconnect( );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t payload_size = MSG_ReadShort( &sb );
|
||||||
|
uint32_t frame_size = SBRK_FRAME_HEADER_SIZE + SBRK_FRAME_LENGTH_SIZE + payload_size;
|
||||||
|
|
||||||
|
if( MSG_GetNumBytesLeft( &sb ) < payload_size )
|
||||||
|
return false; // need more data
|
||||||
|
|
||||||
|
char response_header[SBRK_RESPONSE_HEADER_SIZE];
|
||||||
|
if( MSG_ReadBytes( &sb, response_header, SBRK_RESPONSE_HEADER_SIZE ))
|
||||||
|
{
|
||||||
|
if( memcmp( response_header, SBRK_RESPONSE_HEADER, SBRK_RESPONSE_HEADER_SIZE ) == 0 )
|
||||||
|
{
|
||||||
|
int32_t challenge = MSG_ReadLong( &sb );
|
||||||
|
if( broker.challenge != challenge )
|
||||||
|
{
|
||||||
|
Con_Printf( S_ERROR "%s: challenge mismatch\n", __func__ );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
uint64_t steam_id;
|
||||||
|
MSG_ReadBytes( &sb, &steam_id, sizeof( steam_id ));
|
||||||
|
uint32_t ticket_size = MSG_ReadDword( &sb );
|
||||||
|
uint8_t ticket_data[SBRK_TICKET_SIZE_MAX];
|
||||||
|
|
||||||
|
if( ticket_size > SBRK_TICKET_SIZE_MAX )
|
||||||
|
{
|
||||||
|
Con_Printf( S_ERROR "%s: ticket size exceeds limit (%u)\n", __func__, ticket_size );
|
||||||
|
}
|
||||||
|
else if( MSG_ReadBytes( &sb, ticket_data, ticket_size ))
|
||||||
|
{
|
||||||
|
Con_Printf( "%s: SteamID: %"PRIu64", ticket: [%d, %d, %d, %d...]\n", __func__, steam_id, ticket_data[0], ticket_data[1], ticket_data[2], ticket_data[3] );
|
||||||
|
|
||||||
|
memcpy( cls.steamid, &steam_id, sizeof( cls.steamid ));
|
||||||
|
CL_SendGoldSrcConnectPacket( broker.serveradr, broker.challenge, ticket_data, ticket_size );
|
||||||
|
cls.broker_wait = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Con_Printf( S_ERROR "%s: failed to read ticket data\n", __func__ );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove processed frame from buffer
|
||||||
|
memmove( broker.rx_buffer, broker.rx_buffer + frame_size, broker.rx_buffer_pos - frame_size );
|
||||||
|
broker.rx_buffer_pos -= frame_size;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SteamBroker_HandleDataTx( void )
|
||||||
|
{
|
||||||
|
if( broker.tx_buffer_pos == 0 )
|
||||||
|
return;
|
||||||
|
|
||||||
|
int sent = send( broker.socket, (const char *)broker.tx_buffer, broker.tx_buffer_pos, 0 );
|
||||||
|
if( NET_IsSocketError( sent ))
|
||||||
|
{
|
||||||
|
int err = WSAGetLastError();
|
||||||
|
if( err != WSAEWOULDBLOCK && err != WSAEALREADY )
|
||||||
|
{
|
||||||
|
Con_Printf( S_ERROR "%s: send error %s\n", __func__, NET_ErrorString( ));
|
||||||
|
SteamBroker_Disconnect( );
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( sent > 0 )
|
||||||
|
{
|
||||||
|
// remove sent data from buffer
|
||||||
|
memmove( broker.tx_buffer, broker.tx_buffer + sent, broker.tx_buffer_pos - sent );
|
||||||
|
broker.tx_buffer_pos -= sent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SteamBroker_HandleDataRx( void )
|
||||||
|
{
|
||||||
|
int available = sizeof( broker.rx_buffer ) - broker.rx_buffer_pos;
|
||||||
|
if( available <= 0 )
|
||||||
|
{
|
||||||
|
Con_Printf( S_ERROR "%s: receive buffer overflow\n", __func__ );
|
||||||
|
SteamBroker_Disconnect( );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int received = recv( broker.socket, (char *)broker.rx_buffer + broker.rx_buffer_pos, available, 0 );
|
||||||
|
if( NET_IsSocketError( received ))
|
||||||
|
{
|
||||||
|
int err = WSAGetLastError();
|
||||||
|
if( err != WSAEWOULDBLOCK && err != WSAEALREADY )
|
||||||
|
{
|
||||||
|
Con_Printf( S_ERROR "%s: recv error %s\n", __func__, NET_ErrorString( ));
|
||||||
|
SteamBroker_Disconnect( );
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( received == 0 )
|
||||||
|
{
|
||||||
|
Con_Printf( S_NOTE "%s: connection closed by broker\n", __func__ );
|
||||||
|
SteamBroker_Disconnect( );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
broker.rx_buffer_pos += received;
|
||||||
|
|
||||||
|
while( SteamBroker_ProcessFrame( ));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SteamBroker_UpdateIdle( void )
|
||||||
|
{
|
||||||
|
if( broker.idle_cycle_timeout < Platform_DoubleTime( ))
|
||||||
|
{
|
||||||
|
if( SteamBroker_UpdateBrokerAddress( ))
|
||||||
|
{
|
||||||
|
SteamBroker_ConnectImpl( );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Con_Printf( S_ERROR "%s: failed to resolve broker address \"%s\"\n", __func__, cl_steam_broker_addr.string );
|
||||||
|
}
|
||||||
|
broker.idle_cycle_timeout = Platform_DoubleTime() + SBRK_CONNECT_RETRY_DELAY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SteamBroker_AnnounceGameStart( const char *gamedir )
|
||||||
|
{
|
||||||
|
if( Q_stricmp( cl_ticket_generator.string, "steam" ) != 0 )
|
||||||
|
return;
|
||||||
|
|
||||||
|
if( broker.state != SBRK_STATE_CONNECTED )
|
||||||
|
return;
|
||||||
|
|
||||||
|
// sb_gamedir <gamedir>
|
||||||
|
char buf[512];
|
||||||
|
int len = Q_snprintf( buf, sizeof( buf ), "sb_gamedir %s", gamedir );
|
||||||
|
|
||||||
|
if( len > 0 )
|
||||||
|
SteamBroker_SendFrame( buf, len );
|
||||||
|
}
|
||||||
|
|
||||||
|
void SteamBroker_AnnounceGameShutdown( void )
|
||||||
|
{
|
||||||
|
if( Q_stricmp( cl_ticket_generator.string, "steam" ) != 0 )
|
||||||
|
return;
|
||||||
|
|
||||||
|
if( broker.state != SBRK_STATE_CONNECTED )
|
||||||
|
return;
|
||||||
|
|
||||||
|
SteamBroker_SendFrame( "sb_terminate", sizeof( "sb_terminate" ) - 1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SteamBroker_UpdateConnecting( void )
|
||||||
|
{
|
||||||
|
if( Platform_DoubleTime() > broker.connection_timeout )
|
||||||
|
{
|
||||||
|
Con_Printf( S_WARN "%s: connection to %s timed out\n", __func__, cl_steam_broker_addr.string );
|
||||||
|
SteamBroker_Disconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fd_set writefds;
|
||||||
|
FD_ZERO( &writefds );
|
||||||
|
FD_SET( broker.socket, &writefds );
|
||||||
|
|
||||||
|
struct timeval tv;
|
||||||
|
tv.tv_sec = 0;
|
||||||
|
tv.tv_usec = 0;
|
||||||
|
|
||||||
|
#if XASH_WIN32
|
||||||
|
int select_result = select( 0, NULL, &writefds, NULL, &tv );
|
||||||
|
#else
|
||||||
|
int select_result = select( broker.socket + 1, NULL, &writefds, NULL, &tv );
|
||||||
|
#endif
|
||||||
|
if( select_result == SOCKET_ERROR )
|
||||||
|
{
|
||||||
|
Con_Printf( S_ERROR "%s: select() failed\n", __func__ );
|
||||||
|
SteamBroker_Disconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( FD_ISSET( broker.socket, &writefds ))
|
||||||
|
{
|
||||||
|
// socket is writable - connection established or failed
|
||||||
|
int err = 0;
|
||||||
|
socklen_t err_len = sizeof( err );
|
||||||
|
if( NET_IsSocketError( getsockopt( broker.socket, SOL_SOCKET, SO_ERROR, (char *)&err, &err_len )))
|
||||||
|
{
|
||||||
|
Con_Printf( S_ERROR "%s: getsockopt() failed\n", __func__ );
|
||||||
|
SteamBroker_Disconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if( err != 0 )
|
||||||
|
{
|
||||||
|
Con_Printf( S_ERROR "%s: connection failed with error %d\n", __func__, err );
|
||||||
|
SteamBroker_Disconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
broker.connection_timeout = 0;
|
||||||
|
Con_Printf( S_NOTE "%s: connected to broker at %s\n", __func__, cl_steam_broker_addr.string );
|
||||||
|
SteamBroker_SetState( SBRK_STATE_CONNECTED );
|
||||||
|
SteamBroker_AnnounceGameStart( GI->gamefolder );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SteamBroker_UpdateConnected( void )
|
||||||
|
{
|
||||||
|
SteamBroker_HandleDataTx( );
|
||||||
|
SteamBroker_HandleDataRx( );
|
||||||
|
}
|
||||||
|
|
||||||
qboolean SteamBroker_InitiateGameConnection( netadr_t serveradr, int challenge )
|
qboolean SteamBroker_InitiateGameConnection( netadr_t serveradr, int challenge )
|
||||||
{
|
{
|
||||||
// only ipv4 supported
|
// only ipv4 supported
|
||||||
if( NET_NetadrType( &serveradr ) != NA_IP )
|
if( NET_NetadrType( &serveradr ) != NA_IP )
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if( broker.state != SBRK_STATE_CONNECTED )
|
||||||
|
{
|
||||||
|
Con_Printf( S_WARN "%s: broker not connected\n", __func__ );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
broker.challenge = challenge;
|
broker.challenge = challenge;
|
||||||
broker.serveradr = serveradr;
|
broker.serveradr = serveradr;
|
||||||
|
|
||||||
// sb_connect <ip:port> <server's steam id> <secure> <challenge>
|
// sb_connect <ip:port> <server_steamid> <secure> <challenge>
|
||||||
// the message calls
|
|
||||||
char buf[512];
|
char buf[512];
|
||||||
int len = Q_snprintf( buf, sizeof( buf ), "sb_connect %s %"PRIu64" %s %d", NET_AdrToString( serveradr ), cls.server_steamid, cls.vac2_secure ? "true" : "false", broker.challenge );
|
int len = Q_snprintf( buf, sizeof( buf ), "sb_connect %s %"PRIu64" %d %d", NET_AdrToString( serveradr ), cls.server_steamid, cls.vac2_secure ? 1 : 0, challenge );
|
||||||
|
|
||||||
NET_SendPacket( NS_CLIENT, len, buf, broker.adr );
|
if( !SteamBroker_SendFrame( buf, len ))
|
||||||
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SteamBroker_TerminateGameConnection( void )
|
void SteamBroker_TerminateGameConnection( void )
|
||||||
{
|
{
|
||||||
if( NET_NetadrType( &cls.serveradr ) != NA_IP )
|
if( broker.state != SBRK_STATE_CONNECTED )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if( Q_stricmp( cl_ticket_generator.string, "steam" ) != 0 )
|
if( Q_stricmp( cl_ticket_generator.string, "steam" ) != 0 )
|
||||||
@@ -83,109 +481,52 @@ void SteamBroker_TerminateGameConnection( void )
|
|||||||
char buf[512];
|
char buf[512];
|
||||||
int len = Q_snprintf( buf, sizeof( buf ), "sb_disconnect %s %d", NET_AdrToString( cls.serveradr ), broker.challenge );
|
int len = Q_snprintf( buf, sizeof( buf ), "sb_disconnect %s %d", NET_AdrToString( cls.serveradr ), broker.challenge );
|
||||||
|
|
||||||
NET_SendPacket( NS_CLIENT, len, buf, broker.adr );
|
SteamBroker_SendFrame( buf, len );
|
||||||
}
|
|
||||||
|
|
||||||
void SteamBroker_AnnounceGameStart( const char *gamedir )
|
|
||||||
{
|
|
||||||
if( Q_stricmp( cl_ticket_generator.string, "steam" ) != 0 )
|
|
||||||
return;
|
|
||||||
|
|
||||||
NET_Config( true, true ); // initialize sockets to be able to send packets to broker
|
|
||||||
|
|
||||||
char buf[512];
|
|
||||||
int len = Q_snprintf( buf, sizeof( buf ), "sb_gamedir %s", gamedir );
|
|
||||||
|
|
||||||
NET_SendPacket( NS_CLIENT, len, buf, broker.adr );
|
|
||||||
}
|
|
||||||
|
|
||||||
void SteamBroker_AnnounceGameShutdown( netadr_t broker_addr )
|
|
||||||
{
|
|
||||||
NET_SendPacket( NS_CLIENT, sizeof( "sb_terminate" ) - 1, "sb_terminate", broker_addr );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SteamBroker_Frame( void )
|
void SteamBroker_Frame( void )
|
||||||
{
|
{
|
||||||
|
if( FBitSet( cl_steam_broker_addr.flags | cl_ticket_generator.flags, FCVAR_CHANGED ))
|
||||||
|
{
|
||||||
|
ClearBits( cl_ticket_generator.flags, FCVAR_CHANGED );
|
||||||
|
ClearBits( cl_steam_broker_addr.flags, FCVAR_CHANGED );
|
||||||
|
|
||||||
|
if( broker.state != SBRK_STATE_IDLE )
|
||||||
|
{
|
||||||
|
SteamBroker_Disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
// reinitialize address
|
||||||
|
NET_NetadrSetType( &broker.adr, NA_UNDEFINED );
|
||||||
|
}
|
||||||
|
|
||||||
if( Q_stricmp( cl_ticket_generator.string, "steam" ) != 0 )
|
if( Q_stricmp( cl_ticket_generator.string, "steam" ) != 0 )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
qboolean restart = FBitSet( cl_steam_broker_addr.flags|cl_ticket_generator.flags, FCVAR_CHANGED );
|
// update state machine
|
||||||
|
switch( broker.state )
|
||||||
if( !broker.addr_initialized )
|
|
||||||
{
|
{
|
||||||
if( !SteamBroker_UpdateBrokerAddress( ))
|
case SBRK_STATE_IDLE:
|
||||||
{
|
SteamBroker_UpdateIdle( );
|
||||||
Con_Printf( "%s: failed to resolve broker address \"%s\"\n", __func__, cl_steam_broker_addr.string );
|
break;
|
||||||
return;
|
case SBRK_STATE_CONNECTING:
|
||||||
}
|
SteamBroker_UpdateConnecting( );
|
||||||
else
|
break;
|
||||||
{
|
case SBRK_STATE_CONNECTED:
|
||||||
broker.addr_initialized = true;
|
SteamBroker_UpdateConnected( );
|
||||||
}
|
break;
|
||||||
|
case SBRK_STATE_GAMESHUTDOWN:
|
||||||
|
// do nothing, just wait for game shutdown
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if( restart )
|
|
||||||
{
|
|
||||||
broker.announced = false;
|
|
||||||
ClearBits( cl_ticket_generator.flags, FCVAR_CHANGED );
|
|
||||||
ClearBits( cl_steam_broker_addr.flags, FCVAR_CHANGED );
|
|
||||||
}
|
|
||||||
|
|
||||||
if( !broker.announced )
|
|
||||||
{
|
|
||||||
netadr_t previous_addr = broker.adr;
|
|
||||||
if( !SteamBroker_UpdateBrokerAddress( ))
|
|
||||||
{
|
|
||||||
Con_Printf( "%s: failed to resolve broker address \"%s\"\n", __func__, cl_steam_broker_addr.string );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if( restart )
|
|
||||||
{
|
|
||||||
// terminate old steam broker if instance address has changed
|
|
||||||
SteamBroker_AnnounceGameShutdown( previous_addr );
|
|
||||||
}
|
|
||||||
SteamBroker_AnnounceGameStart( GI->gamefolder );
|
|
||||||
}
|
|
||||||
broker.announced = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SteamBroker_HandlePacket( netadr_t from, sizebuf_t *msg )
|
|
||||||
{
|
|
||||||
// message format
|
|
||||||
// sb_connect\n<4 byte challenge><8 byte steamid><unsigned 4 byte len><len bytes ticket>
|
|
||||||
int challenge;
|
|
||||||
uint32_t len;
|
|
||||||
uint8_t ticket[2048]; // 2048 bytes according to SDK docs
|
|
||||||
|
|
||||||
if( !NET_CompareAdr( from, broker.adr ))
|
|
||||||
return;
|
|
||||||
|
|
||||||
challenge = MSG_ReadLong( msg );
|
|
||||||
|
|
||||||
if( broker.challenge != challenge ) // TODO: print error
|
|
||||||
return;
|
|
||||||
|
|
||||||
MSG_ReadBytes( msg, cls.steamid, sizeof( cls.steamid ));
|
|
||||||
|
|
||||||
len = MSG_ReadDword( msg );
|
|
||||||
if( len > sizeof( ticket )) // TODO: print error, proceed without ticket?
|
|
||||||
return;
|
|
||||||
|
|
||||||
MSG_ReadBytes( msg, ticket, len );
|
|
||||||
|
|
||||||
Con_Printf( "%s: SteamID: %"PRIu64", ticket: [%d, %d, %d, %d...]\n", __func__, *(uint64_t *)cls.steamid, ticket[0], ticket[1], ticket[2], ticket[3] );
|
|
||||||
|
|
||||||
CL_SendGoldSrcConnectPacket( broker.serveradr, challenge, ticket, len );
|
|
||||||
|
|
||||||
cls.broker_wait = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SteamBroker_Init( void )
|
void SteamBroker_Init( void )
|
||||||
{
|
{
|
||||||
broker.announced = false;
|
broker.state = SBRK_STATE_IDLE;
|
||||||
broker.addr_initialized = false;
|
broker.socket = INVALID_SOCKET;
|
||||||
|
broker.rx_buffer_pos = 0;
|
||||||
|
broker.tx_buffer_pos = 0;
|
||||||
Cvar_RegisterVariable( &cl_steam_broker_addr );
|
Cvar_RegisterVariable( &cl_steam_broker_addr );
|
||||||
NET_NetadrSetType( &broker.adr, NA_UNDEFINED );
|
NET_NetadrSetType( &broker.adr, NA_UNDEFINED );
|
||||||
}
|
}
|
||||||
@@ -195,5 +536,6 @@ void SteamBroker_Shutdown( void )
|
|||||||
if( Q_stricmp( cl_ticket_generator.string, "steam" ) != 0 )
|
if( Q_stricmp( cl_ticket_generator.string, "steam" ) != 0 )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
SteamBroker_AnnounceGameShutdown( broker.adr );
|
SteamBroker_AnnounceGameShutdown( );
|
||||||
|
SteamBroker_SetState( SBRK_STATE_GAMESHUTDOWN );
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ AVI PLAYING
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
static movie_state_t *cin_state;
|
static movie_state_t *cin_state;
|
||||||
|
static int cin_texture;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
==================
|
==================
|
||||||
@@ -281,6 +282,12 @@ void SCR_InitCinematic( void )
|
|||||||
{
|
{
|
||||||
AVI_Initailize ();
|
AVI_Initailize ();
|
||||||
cin_state = AVI_GetState( CIN_MAIN );
|
cin_state = AVI_GetState( CIN_MAIN );
|
||||||
|
cin_texture = ref.dllFuncs.GL_CreateTexture( "*cintexture", 64, 64, NULL, TF_NOMIPMAP|TF_CLAMP );
|
||||||
|
}
|
||||||
|
|
||||||
|
int SCR_GetCinematicTexture( void )
|
||||||
|
{
|
||||||
|
return cin_texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -360,6 +360,29 @@ qboolean V_PreRender( void )
|
|||||||
|
|
||||||
//============================================================================
|
//============================================================================
|
||||||
|
|
||||||
|
/*
|
||||||
|
====================
|
||||||
|
V_ApplyRefUnderwater
|
||||||
|
|
||||||
|
apply underwater fov effect
|
||||||
|
====================
|
||||||
|
*/
|
||||||
|
static void V_ApplyRefUnderwater( ref_viewpass_t *rvp )
|
||||||
|
{
|
||||||
|
if( !FBitSet( rvp->flags, RF_DRAW_CUBEMAP|RF_DRAW_OVERVIEW ))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if( !FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE ))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if( cl.local.waterlevel < 3 )
|
||||||
|
return;
|
||||||
|
|
||||||
|
rvp->fov_x = atan( tan( DEG2RAD( rvp->fov_x ) / 2 ) * ( 0.97f + sin( cl.time * 1.5f ) * 0.03f )) * 2 / (M_PI_F / 180.0f);
|
||||||
|
rvp->fov_y = atan( tan( DEG2RAD( rvp->fov_y ) / 2 ) * ( 1.03f - sin( cl.time * 1.5f ) * 0.03f )) * 2 / (M_PI_F / 180.0f);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
==================
|
==================
|
||||||
V_RenderView
|
V_RenderView
|
||||||
@@ -389,6 +412,7 @@ void V_RenderView( void )
|
|||||||
clgame.dllFuncs.pfnCalcRefdef( &rp );
|
clgame.dllFuncs.pfnCalcRefdef( &rp );
|
||||||
V_GetRefParams( &rp, &rvp );
|
V_GetRefParams( &rp, &rvp );
|
||||||
V_RefApplyOverview( &rvp );
|
V_RefApplyOverview( &rvp );
|
||||||
|
V_ApplyRefUnderwater( &rvp );
|
||||||
|
|
||||||
if( viewnum == 0 && FBitSet( rvp.flags, RF_ONLY_CLIENTDRAW ))
|
if( viewnum == 0 && FBitSet( rvp.flags, RF_ONLY_CLIENTDRAW ))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -761,6 +761,7 @@ void CL_AddToResourceList( resource_t *pResource, resource_t *pList );
|
|||||||
void CL_RemoveFromResourceList( resource_t *pResource );
|
void CL_RemoveFromResourceList( resource_t *pResource );
|
||||||
void CL_MoveToOnHandList( resource_t *pResource );
|
void CL_MoveToOnHandList( resource_t *pResource );
|
||||||
void CL_ClearResourceLists( void );
|
void CL_ClearResourceLists( void );
|
||||||
|
void CL_SendResourceList( const resource_t *list, int count );
|
||||||
|
|
||||||
//
|
//
|
||||||
// cl_debug.c
|
// cl_debug.c
|
||||||
@@ -874,7 +875,6 @@ struct msurface_s *pfnTraceSurface( int ground, float *vstart, float *vend );
|
|||||||
void CL_EnableScissor( scissor_state_t *scissor, int x, int y, int width, int height );
|
void CL_EnableScissor( scissor_state_t *scissor, int x, int y, int width, int height );
|
||||||
void CL_DisableScissor( scissor_state_t *scissor );
|
void CL_DisableScissor( scissor_state_t *scissor );
|
||||||
qboolean CL_Scissor( const scissor_state_t *scissor, float *x, float *y, float *width, float *height, float *u0, float *v0, float *u1, float *v1 );
|
qboolean CL_Scissor( const scissor_state_t *scissor, float *x, float *y, float *width, float *height, float *u0, float *v0, float *u1, float *v1 );
|
||||||
|
|
||||||
static inline cl_entity_t *CL_EDICT_NUM( int index )
|
static inline cl_entity_t *CL_EDICT_NUM( int index )
|
||||||
{
|
{
|
||||||
if( unlikely( !clgame.entities )) // not in game yet
|
if( unlikely( !clgame.entities )) // not in game yet
|
||||||
@@ -1208,14 +1208,14 @@ void CL_GetSecuredClientAPI( CL_EXPORT_FUNCS F );
|
|||||||
void SteamBroker_Init( void );
|
void SteamBroker_Init( void );
|
||||||
void SteamBroker_Shutdown( void );
|
void SteamBroker_Shutdown( void );
|
||||||
void SteamBroker_Frame( void );
|
void SteamBroker_Frame( void );
|
||||||
void SteamBroker_HandlePacket( netadr_t from, sizebuf_t *msg );
|
qboolean SteamBroker_InitiateGameConnection( netadr_t serveradr, int challenge );
|
||||||
int SteamBroker_InitiateGameConnection( netadr_t serveradr, int challenge );
|
|
||||||
void SteamBroker_TerminateGameConnection( void );
|
void SteamBroker_TerminateGameConnection( void );
|
||||||
|
|
||||||
//
|
//
|
||||||
// cl_video.c
|
// cl_video.c
|
||||||
//
|
//
|
||||||
void SCR_InitCinematic( void );
|
void SCR_InitCinematic( void );
|
||||||
|
int SCR_GetCinematicTexture( void );
|
||||||
void SCR_FreeCinematic( void );
|
void SCR_FreeCinematic( void );
|
||||||
qboolean SCR_PlayCinematic( const char *name );
|
qboolean SCR_PlayCinematic( const char *name );
|
||||||
qboolean SCR_DrawCinematic( void );
|
qboolean SCR_DrawCinematic( void );
|
||||||
@@ -1241,7 +1241,6 @@ void Key_EnumCmds_f( void );
|
|||||||
void Key_SetKeyDest( int key_dest );
|
void Key_SetKeyDest( int key_dest );
|
||||||
void Key_EnableTextInput( qboolean enable, qboolean force );
|
void Key_EnableTextInput( qboolean enable, qboolean force );
|
||||||
int Key_ToUpper( int key );
|
int Key_ToUpper( int key );
|
||||||
void OSK_Draw( void );
|
|
||||||
qboolean Cmd_GetKeysList( const char *s, char *completedname, int length, qboolean print_suggestions );
|
qboolean Cmd_GetKeysList( const char *s, char *completedname, int length, qboolean print_suggestions );
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ GNU General Public License for more details.
|
|||||||
#include "client.h"
|
#include "client.h"
|
||||||
#include "const.h"
|
#include "const.h"
|
||||||
#include "triangleapi.h"
|
#include "triangleapi.h"
|
||||||
|
#include "r_studioint.h"
|
||||||
|
#include "mod_local.h"
|
||||||
#include "r_efx.h"
|
#include "r_efx.h"
|
||||||
#include "demo_api.h"
|
#include "demo_api.h"
|
||||||
#include "ivoicetweak.h"
|
#include "ivoicetweak.h"
|
||||||
@@ -2173,7 +2175,7 @@ pfnGetViewModel
|
|||||||
|
|
||||||
=============
|
=============
|
||||||
*/
|
*/
|
||||||
static cl_entity_t* GAME_EXPORT CL_GetViewModel( void )
|
static cl_entity_t *GAME_EXPORT CL_GetViewModel( void )
|
||||||
{
|
{
|
||||||
return &clgame.viewent;
|
return &clgame.viewent;
|
||||||
}
|
}
|
||||||
@@ -3929,6 +3931,69 @@ void CL_UnloadProgs( void )
|
|||||||
memset( &clgame, 0, sizeof( clgame ));
|
memset( &clgame, 0, sizeof( clgame ));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static model_t *pfnStudio_Mod_ForName( const char *name, int crash )
|
||||||
|
{
|
||||||
|
return Mod_ForName( name, crash, false );
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *pfnStudio_Mod_Extradata( model_t *mod )
|
||||||
|
{
|
||||||
|
return Mod_StudioExtradata( mod );
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pfnStudio_GetAliasScale( float *x, float *y )
|
||||||
|
{
|
||||||
|
if( x ) *x = 1.0f;
|
||||||
|
if( y ) *y = 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
static float ***pfnStudio_GetAliasTransform( void )
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static model_t *pfnStudio_GetChromeSprite( void )
|
||||||
|
{
|
||||||
|
return cl_sprite_shell;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pfnStudio_IsHardware( void )
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static engine_studio_api_t gStudioAPI =
|
||||||
|
{
|
||||||
|
.Mem_Calloc = Mod_Calloc,
|
||||||
|
.Cache_Check = Mod_CacheCheck,
|
||||||
|
.LoadCacheFile = Mod_LoadCacheFile,
|
||||||
|
.Mod_ForName = pfnStudio_Mod_ForName,
|
||||||
|
.Mod_Extradata = pfnStudio_Mod_Extradata,
|
||||||
|
.GetModelByIndex = CL_ModelHandle,
|
||||||
|
.GetCvar = pfnCVarGetPointer,
|
||||||
|
.GetChromeSprite = pfnStudio_GetChromeSprite,
|
||||||
|
.GetAliasScale = pfnStudio_GetAliasScale,
|
||||||
|
.StudioGetAliasTransform = pfnStudio_GetAliasTransform,
|
||||||
|
.GetViewEntity = CL_GetViewModel,
|
||||||
|
.IsHardware = pfnStudio_IsHardware,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void CL_InitStudioAPI( void )
|
||||||
|
{
|
||||||
|
static r_studio_interface_t gDefaultStudioDraw;
|
||||||
|
r_studio_interface_t *pStudioDraw;
|
||||||
|
|
||||||
|
if( !ref.dllFuncs.R_StudioFillAPI( &gStudioAPI, &gDefaultStudioDraw ))
|
||||||
|
return;
|
||||||
|
|
||||||
|
pStudioDraw = &gDefaultStudioDraw;
|
||||||
|
|
||||||
|
if( clgame.dllFuncs.pfnGetStudioModelInterface )
|
||||||
|
clgame.dllFuncs.pfnGetStudioModelInterface( STUDIO_INTERFACE_VERSION, &pStudioDraw, &gStudioAPI );
|
||||||
|
|
||||||
|
ref.dllFuncs.R_StudioSetDrawInterface( pStudioDraw );
|
||||||
|
}
|
||||||
|
|
||||||
qboolean CL_LoadProgs( const char *name )
|
qboolean CL_LoadProgs( const char *name )
|
||||||
{
|
{
|
||||||
static playermove_t gpMove;
|
static playermove_t gpMove;
|
||||||
@@ -4066,7 +4131,8 @@ qboolean CL_LoadProgs( const char *name )
|
|||||||
// initialize game
|
// initialize game
|
||||||
clgame.dllFuncs.pfnInit();
|
clgame.dllFuncs.pfnInit();
|
||||||
|
|
||||||
ref.dllFuncs.CL_InitStudioAPI();
|
CL_InitStudioAPI();
|
||||||
|
S_InitSoundAPI();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -188,7 +188,7 @@ void UI_ShowConnectionWarning( void )
|
|||||||
if( cls.state != ca_connected )
|
if( cls.state != ca_connected )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if( Host_IsLocalClient() )
|
if( Host_IsLocalClient( ))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if( ++cl.lostpackets == 8 )
|
if( ++cl.lostpackets == 8 )
|
||||||
@@ -172,8 +172,8 @@ intptr_t CL_RenderGetParm( const int parm, const int arg, const qboolean checkRe
|
|||||||
return cl.local.waterlevel;
|
return cl.local.waterlevel;
|
||||||
case PARM_LOCAL_HEALTH:
|
case PARM_LOCAL_HEALTH:
|
||||||
return cl.local.health;
|
return cl.local.health;
|
||||||
case PARM_LOCAL_GAME:
|
case PARM_SINGLEPLAYER_GAME:
|
||||||
return Host_IsLocalGame();
|
return Host_IsSinglePlayerGame();
|
||||||
case PARM_NUMENTITIES:
|
case PARM_NUMENTITIES:
|
||||||
return pfnNumberOfEntities();
|
return pfnNumberOfEntities();
|
||||||
case PARM_GET_CLIENT_PTR:
|
case PARM_GET_CLIENT_PTR:
|
||||||
@@ -214,6 +214,11 @@ static void pfnAVI_StreamSound( movie_state_t *avi, int entnum, float fvol, floa
|
|||||||
return; // stub, use AVI_SetParm and AVI_Think to stream AVI sound
|
return; // stub, use AVI_SetParm and AVI_Think to stream AVI sound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void pfnAVI_UploadRawFrame( int texture, int cols, int rows, int width, int height, const byte *data )
|
||||||
|
{
|
||||||
|
ref.dllFuncs.GL_UpdateTexture( texture, cols, rows, width, height, data, PF_BGRA_32 );
|
||||||
|
}
|
||||||
|
|
||||||
static render_api_t gRenderAPI =
|
static render_api_t gRenderAPI =
|
||||||
{
|
{
|
||||||
pfnRenderGetParm, // GL_RenderGetParm,
|
pfnRenderGetParm, // GL_RenderGetParm,
|
||||||
@@ -243,7 +248,7 @@ static render_api_t gRenderAPI =
|
|||||||
AVI_GetVideoInfo,
|
AVI_GetVideoInfo,
|
||||||
AVI_GetVideoFrameNumber,
|
AVI_GetVideoFrameNumber,
|
||||||
AVI_GetVideoFrame,
|
AVI_GetVideoFrame,
|
||||||
NULL, // R_UploadStretchRaw,
|
pfnAVI_UploadRawFrame, // AVI_UploadRawFrame
|
||||||
AVI_FreeVideo,
|
AVI_FreeVideo,
|
||||||
AVI_IsActive,
|
AVI_IsActive,
|
||||||
pfnAVI_StreamSound,
|
pfnAVI_StreamSound,
|
||||||
@@ -283,40 +288,6 @@ static render_api_t gRenderAPI =
|
|||||||
COM_SetRandomSeed,
|
COM_SetRandomSeed,
|
||||||
};
|
};
|
||||||
|
|
||||||
static void R_FillRenderAPIFromRef( render_api_t *to, const ref_interface_t *from )
|
|
||||||
{
|
|
||||||
to->GetDetailScaleForTexture = from->GetDetailScaleForTexture;
|
|
||||||
to->GetExtraParmsForTexture = from->GetExtraParmsForTexture;
|
|
||||||
to->GetFrameTime = from->GetFrameTime;
|
|
||||||
to->R_SetCurrentEntity = from->R_SetCurrentEntity;
|
|
||||||
to->R_SetCurrentModel = from->R_SetCurrentModel;
|
|
||||||
to->GL_FindTexture = from->GL_FindTexture;
|
|
||||||
to->GL_TextureName = from->GL_TextureName;
|
|
||||||
to->GL_TextureData = from->GL_TextureData;
|
|
||||||
to->GL_LoadTexture = from->GL_LoadTexture;
|
|
||||||
to->GL_CreateTexture = from->GL_CreateTexture;
|
|
||||||
to->GL_LoadTextureArray = from->GL_LoadTextureArray;
|
|
||||||
to->GL_CreateTextureArray = from->GL_CreateTextureArray;
|
|
||||||
to->GL_FreeTexture = from->GL_FreeTexture;
|
|
||||||
to->DrawSingleDecal = from->DrawSingleDecal;
|
|
||||||
to->R_DecalSetupVerts = from->R_DecalSetupVerts;
|
|
||||||
to->R_EntityRemoveDecals = from->R_EntityRemoveDecals;
|
|
||||||
to->AVI_UploadRawFrame = from->AVI_UploadRawFrame;
|
|
||||||
to->GL_Bind = from->GL_Bind;
|
|
||||||
to->GL_SelectTexture = from->GL_SelectTexture;
|
|
||||||
to->GL_LoadTextureMatrix = from->GL_LoadTextureMatrix;
|
|
||||||
to->GL_TexMatrixIdentity = from->GL_TexMatrixIdentity;
|
|
||||||
to->GL_CleanUpTextureUnits = from->GL_CleanUpTextureUnits;
|
|
||||||
to->GL_TexGen = from->GL_TexGen;
|
|
||||||
to->GL_TextureTarget = from->GL_TextureTarget;
|
|
||||||
to->GL_TexCoordArrayMode = from->GL_TexCoordArrayMode;
|
|
||||||
to->GL_UpdateTexSize = from->GL_UpdateTexSize;
|
|
||||||
to->GL_DrawParticles = from->GL_DrawParticles;
|
|
||||||
to->LightVec = from->LightVec;
|
|
||||||
to->StudioGetTexture = from->StudioGetTexture;
|
|
||||||
to->GL_GetProcAddress = from->R_GetProcAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
===============
|
===============
|
||||||
R_InitRenderAPI
|
R_InitRenderAPI
|
||||||
@@ -329,8 +300,15 @@ qboolean R_InitRenderAPI( void )
|
|||||||
// make sure what render functions is cleared
|
// make sure what render functions is cleared
|
||||||
memset( &clgame.drawFuncs, 0, sizeof( clgame.drawFuncs ));
|
memset( &clgame.drawFuncs, 0, sizeof( clgame.drawFuncs ));
|
||||||
|
|
||||||
// fill missing functions from renderer
|
gRenderAPI.GetDetailScaleForTexture = ref.dllFuncs.R_GetDetailScaleForTexture;
|
||||||
R_FillRenderAPIFromRef( &gRenderAPI, &ref.dllFuncs );
|
gRenderAPI.GL_FindTexture = ref.dllFuncs.GL_FindTexture;
|
||||||
|
gRenderAPI.GL_TextureName = ref.dllFuncs.GL_TextureName;
|
||||||
|
gRenderAPI.GL_TextureData = ref.dllFuncs.GL_TextureData;
|
||||||
|
gRenderAPI.GL_LoadTexture = ref.dllFuncs.GL_LoadTexture;
|
||||||
|
gRenderAPI.GL_FreeTexture = ref.dllFuncs.GL_FreeTexture;
|
||||||
|
gRenderAPI.GL_Bind = ref.dllFuncs.GL_Bind;
|
||||||
|
|
||||||
|
ref.dllFuncs.R_FillRenderAPI( &gRenderAPI );
|
||||||
|
|
||||||
if( clgame.dllFuncs.pfnGetRenderInterface )
|
if( clgame.dllFuncs.pfnGetRenderInterface )
|
||||||
{
|
{
|
||||||
@@ -287,13 +287,6 @@ static entity_state_t *R_StudioGetPlayerState( int index )
|
|||||||
return &cl.frames[cl.parsecountmod].playerstate[index];
|
return &cl.frames[cl.parsecountmod].playerstate[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
static int pfnGetStudioModelInterface( int version, struct r_studio_interface_s **ppinterface, struct engine_studio_api_s *pstudio )
|
|
||||||
{
|
|
||||||
return clgame.dllFuncs.pfnGetStudioModelInterface ?
|
|
||||||
clgame.dllFuncs.pfnGetStudioModelInterface( version, ppinterface, pstudio ) :
|
|
||||||
0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const bpc_desc_t *pfnImage_GetPFDesc( int idx )
|
static const bpc_desc_t *pfnImage_GetPFDesc( int idx )
|
||||||
{
|
{
|
||||||
return &PFDesc[idx];
|
return &PFDesc[idx];
|
||||||
@@ -329,11 +322,6 @@ static qboolean R_Init_Video_( ref_graphic_apis_t type )
|
|||||||
return R_Init_Video( type );
|
return R_Init_Video( type );
|
||||||
}
|
}
|
||||||
|
|
||||||
static mleaf_t *pfnMod_PointInLeaf( const vec3_t p, mnode_t *node )
|
|
||||||
{
|
|
||||||
// FIXME: get rid of this on next RefAPI update
|
|
||||||
return Mod_PointInLeaf( p, node, cl.models[1] );
|
|
||||||
}
|
|
||||||
|
|
||||||
static const ref_api_t gEngfuncs =
|
static const ref_api_t gEngfuncs =
|
||||||
{
|
{
|
||||||
@@ -375,7 +363,7 @@ static const ref_api_t gEngfuncs =
|
|||||||
|
|
||||||
Mod_SampleSizeForFace,
|
Mod_SampleSizeForFace,
|
||||||
Mod_BoxVisible,
|
Mod_BoxVisible,
|
||||||
pfnMod_PointInLeaf,
|
Mod_PointInLeaf,
|
||||||
R_DrawWorldHull,
|
R_DrawWorldHull,
|
||||||
R_DrawModelHull,
|
R_DrawModelHull,
|
||||||
|
|
||||||
@@ -411,7 +399,6 @@ static const ref_api_t gEngfuncs =
|
|||||||
Mod_CacheCheck,
|
Mod_CacheCheck,
|
||||||
Mod_LoadCacheFile,
|
Mod_LoadCacheFile,
|
||||||
Mod_Calloc,
|
Mod_Calloc,
|
||||||
pfnGetStudioModelInterface,
|
|
||||||
|
|
||||||
_Mem_AllocPool,
|
_Mem_AllocPool,
|
||||||
_Mem_FreePool,
|
_Mem_FreePool,
|
||||||
@@ -488,28 +475,78 @@ static void R_UnloadProgs( void )
|
|||||||
memset( &ref.dllFuncs, 0, sizeof( ref.dllFuncs ));
|
memset( &ref.dllFuncs, 0, sizeof( ref.dllFuncs ));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void CL_FillTriAPIFromRef( triangleapi_t *dst, const ref_interface_t *src )
|
static void CL_FillTriAPI( triangleapi_t *dst )
|
||||||
{
|
{
|
||||||
dst->version = TRI_API_VERSION;
|
dst->version = TRI_API_VERSION;
|
||||||
dst->Begin = src->Begin;
|
|
||||||
dst->RenderMode = TriRenderMode;
|
dst->RenderMode = TriRenderMode;
|
||||||
dst->End = src->End;
|
|
||||||
dst->Color4f = TriColor4f;
|
dst->Color4f = TriColor4f;
|
||||||
dst->Color4ub = TriColor4ub;
|
dst->Color4ub = TriColor4ub;
|
||||||
dst->TexCoord2f = src->TexCoord2f;
|
|
||||||
dst->Vertex3f = src->Vertex3f;
|
|
||||||
dst->Vertex3fv = src->Vertex3fv;
|
|
||||||
dst->Brightness = TriBrightness;
|
dst->Brightness = TriBrightness;
|
||||||
dst->CullFace = TriCullFace;
|
dst->CullFace = TriCullFace;
|
||||||
dst->SpriteTexture = TriSpriteTexture;
|
dst->SpriteTexture = TriSpriteTexture;
|
||||||
dst->WorldToScreen = TriWorldToScreen;
|
dst->WorldToScreen = TriWorldToScreen;
|
||||||
dst->Fog = src->Fog;
|
|
||||||
dst->ScreenToWorld = src->ScreenToWorld;
|
|
||||||
dst->GetMatrix = src->GetMatrix;
|
|
||||||
dst->BoxInPVS = TriBoxInPVS;
|
dst->BoxInPVS = TriBoxInPVS;
|
||||||
dst->LightAtPoint = TriLightAtPoint;
|
dst->LightAtPoint = TriLightAtPoint;
|
||||||
dst->Color4fRendermode = TriColor4fRendermode;
|
dst->Color4fRendermode = TriColor4fRendermode;
|
||||||
dst->FogParams = src->FogParams;
|
dst->Begin = ref.dllFuncs.Begin;
|
||||||
|
dst->End = ref.dllFuncs.End;
|
||||||
|
dst->Vertex3f = ref.dllFuncs.Vertex3f;
|
||||||
|
dst->Vertex3fv = ref.dllFuncs.Vertex3fv;
|
||||||
|
|
||||||
|
ref.dllFuncs.R_FillTriAPI( dst );
|
||||||
|
}
|
||||||
|
|
||||||
|
static void R_CreateBuiltinTextures( void )
|
||||||
|
{
|
||||||
|
// default checkerboard
|
||||||
|
uint data16x16[256];
|
||||||
|
for( int y = 0; y < 16; y++ )
|
||||||
|
{
|
||||||
|
for( int x = 0; x < 16; x++ )
|
||||||
|
{
|
||||||
|
if(( y < 8 ) ^ ( x < 8 ))
|
||||||
|
data16x16[y * 16 + x] = HostFourCC( 255, 0, 255, 255 );
|
||||||
|
else
|
||||||
|
data16x16[y * 16 + x] = HostFourCC( 0, 0, 0, 255 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ref.dllFuncs.GL_CreateTexture( REF_DEFAULT_TEXTURE, 16, 16, data16x16, 0 );
|
||||||
|
|
||||||
|
// particle texture
|
||||||
|
uint particle[64];
|
||||||
|
for( int y = 0; y < 8; y++ )
|
||||||
|
{
|
||||||
|
for( int x = 0; x < 8; x++ )
|
||||||
|
particle[y * 8 + x] = HostFourCC( 255, 255, 255, 0 ); // fill it with invisible white
|
||||||
|
}
|
||||||
|
|
||||||
|
// a1ba: moved one pixel down and to the right to make it look better with linear interpolation
|
||||||
|
// set to 0 to make it look like in goldsrc
|
||||||
|
const int corner = 1;
|
||||||
|
for( int y = corner; y < 4 + corner; y++ )
|
||||||
|
{
|
||||||
|
for( int x = corner; x < 4 + corner; x++ )
|
||||||
|
{
|
||||||
|
if(( x == corner || x == 3 + corner ) && ( y == corner || y == 3 + corner ))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
particle[y * 8 + x] = HostFourCC( 255, 255, 255, 255 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ref.dllFuncs.GL_CreateTexture( REF_PARTICLE_TEXTURE, 8, 8, particle, TF_CLAMP|TF_HAS_ALPHA );
|
||||||
|
|
||||||
|
// solid colors
|
||||||
|
uint data4x4[16];
|
||||||
|
memset( data4x4, 0xFF, sizeof( data4x4 ));
|
||||||
|
ref.dllFuncs.GL_CreateTexture( REF_WHITE_TEXTURE, 4, 4, data4x4, 0 );
|
||||||
|
|
||||||
|
for( int x = 0; x < 16; x++ )
|
||||||
|
data4x4[x] = HostFourCC( 127, 127, 127, 255 );
|
||||||
|
ref.dllFuncs.GL_CreateTexture( REF_GRAY_TEXTURE, 4, 4, data4x4, 0 );
|
||||||
|
|
||||||
|
for( int x = 0; x < 16; x++ )
|
||||||
|
data4x4[x] = HostFourCC( 0, 0, 0, 255 );
|
||||||
|
ref.dllFuncs.GL_CreateTexture( REF_BLACK_TEXTURE, 4, 4, data4x4, 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
static qboolean R_LoadProgs( const char *name )
|
static qboolean R_LoadProgs( const char *name )
|
||||||
@@ -555,8 +592,8 @@ static qboolean R_LoadProgs( const char *name )
|
|||||||
Cvar_FullSet( "host_refloaded", "1", FCVAR_READ_ONLY );
|
Cvar_FullSet( "host_refloaded", "1", FCVAR_READ_ONLY );
|
||||||
ref.initialized = true;
|
ref.initialized = true;
|
||||||
|
|
||||||
// initialize TriAPI callbacks
|
R_CreateBuiltinTextures();
|
||||||
CL_FillTriAPIFromRef( &gTriApi, &ref.dllFuncs );
|
CL_FillTriAPI( &gTriApi );
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -39,6 +39,10 @@ void IN_DeactivateMouse( void );
|
|||||||
void IN_MouseSavePos( void );
|
void IN_MouseSavePos( void );
|
||||||
void IN_MouseRestorePos( void );
|
void IN_MouseRestorePos( void );
|
||||||
void IN_ToggleClientMouse( int newstate, int oldstate );
|
void IN_ToggleClientMouse( int newstate, int oldstate );
|
||||||
|
void IN_GyroInit( void );
|
||||||
|
void IN_GyroCheckAvailability( void );
|
||||||
|
void IN_GyroEvent( vec3_t data );
|
||||||
|
void IN_GyroFinalizeMove( float *fw, float *side, float *dpitch, float *dyaw );
|
||||||
|
|
||||||
uint IN_CollectInputDevices( void );
|
uint IN_CollectInputDevices( void );
|
||||||
void IN_LockInputDevices( qboolean lock );
|
void IN_LockInputDevices( qboolean lock );
|
||||||
@@ -95,6 +99,15 @@ qboolean Touch_WantVisibleCursor( void );
|
|||||||
void Touch_NotifyResize( void );
|
void Touch_NotifyResize( void );
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
//
|
||||||
|
// in_osk.c
|
||||||
|
//
|
||||||
|
extern convar_t osk_enable;
|
||||||
|
void OSK_Init( void );
|
||||||
|
qboolean OSK_KeyEvent( int key, int down );
|
||||||
|
void OSK_EnableTextInput( qboolean enable, qboolean force );
|
||||||
|
void OSK_Draw( void );
|
||||||
|
|
||||||
//
|
//
|
||||||
// in_joy.c
|
// in_joy.c
|
||||||
//
|
//
|
||||||
|
|||||||
120
engine/client/input/in_gyro.c
Normal file
120
engine/client/input/in_gyro.c
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
/*
|
||||||
|
in_gyro.c - System gyroscope input code
|
||||||
|
Copyright (C) 2026 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include "input.h"
|
||||||
|
#include "client.h"
|
||||||
|
|
||||||
|
static CVAR_DEFINE_AUTO( gyro_enable, "0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "enables aiming with built-in device gyroscope" );
|
||||||
|
static CVAR_DEFINE_AUTO( gyro_available, "0", FCVAR_READ_ONLY, "tells whether system gyroscope hardware is available or not" );
|
||||||
|
static CVAR_DEFINE_AUTO( gyro_pitch, "1.0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "built-in gyroscope sensitivity for looking up and down" );
|
||||||
|
static CVAR_DEFINE_AUTO( gyro_yaw, "1.0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "built-in gyroscope sensitivity for turning left and right" );
|
||||||
|
static CVAR_DEFINE_AUTO( gyro_roll, "0.0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "built-in gyroscope sensitivity when tilting the device sideways" );
|
||||||
|
static CVAR_DEFINE_AUTO( gyro_pitch_deadzone, "0.5", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "built-in gyroscope pitch axis deadzone (deg/s)" );
|
||||||
|
static CVAR_DEFINE_AUTO( gyro_yaw_deadzone, "0.5", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "built-in gyroscope yaw axis deadzone (deg/s)" );
|
||||||
|
static CVAR_DEFINE_AUTO( gyro_roll_deadzone, "0.5", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "built-in gyroscope roll axis deadzone (deg/s)" );
|
||||||
|
|
||||||
|
// stores the latest instantaneous rotation rates from built-in gyroscope
|
||||||
|
static vec3_t gyro_speed;
|
||||||
|
|
||||||
|
/*
|
||||||
|
==============
|
||||||
|
IN_GyroInit
|
||||||
|
|
||||||
|
==============
|
||||||
|
*/
|
||||||
|
void IN_GyroInit( void )
|
||||||
|
{
|
||||||
|
Cvar_RegisterVariable( &gyro_enable );
|
||||||
|
Cvar_RegisterVariable( &gyro_available );
|
||||||
|
Cvar_RegisterVariable( &gyro_pitch );
|
||||||
|
Cvar_RegisterVariable( &gyro_yaw );
|
||||||
|
Cvar_RegisterVariable( &gyro_roll );
|
||||||
|
Cvar_RegisterVariable( &gyro_pitch_deadzone );
|
||||||
|
Cvar_RegisterVariable( &gyro_yaw_deadzone );
|
||||||
|
Cvar_RegisterVariable( &gyro_roll_deadzone );
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
==============
|
||||||
|
IN_GyroCheckAvailability
|
||||||
|
|
||||||
|
One-time late check called after startup and configs
|
||||||
|
==============
|
||||||
|
*/
|
||||||
|
void IN_GyroCheckAvailability( void )
|
||||||
|
{
|
||||||
|
#if XASH_SDL
|
||||||
|
if( gyro_available.value )
|
||||||
|
return;
|
||||||
|
|
||||||
|
if( SDLash_GyroIsAvailable() )
|
||||||
|
{
|
||||||
|
Cvar_FullSet( "gyro_available", "1", FCVAR_READ_ONLY );
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
=============
|
||||||
|
IN_GyroEvent
|
||||||
|
|
||||||
|
System gyroscope events from platform
|
||||||
|
=============
|
||||||
|
*/
|
||||||
|
void IN_GyroEvent( vec3_t data )
|
||||||
|
{
|
||||||
|
VectorCopy( data, gyro_speed );
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
=============
|
||||||
|
IN_GyroFinalizeMove
|
||||||
|
|
||||||
|
Apply gyro movement to view angles
|
||||||
|
=============
|
||||||
|
*/
|
||||||
|
void IN_GyroFinalizeMove( float *fw, float *side, float *dpitch, float *dyaw )
|
||||||
|
{
|
||||||
|
platform_orientation_t orient;
|
||||||
|
float orient_scale = 1.0f;
|
||||||
|
|
||||||
|
if( !gyro_enable.value || !gyro_available.value )
|
||||||
|
return;
|
||||||
|
|
||||||
|
orient = Platform_GetDisplayOrientation();
|
||||||
|
if( orient == ORIENTATION_LANDSCAPE_FLIPPED )
|
||||||
|
orient_scale = -1.0f;
|
||||||
|
|
||||||
|
// In Landscape mode axes are swapped relative to natural (Portrait) orientation
|
||||||
|
// Y axis rotation becomes Pitch (up/down)
|
||||||
|
// X axis rotation becomes Yaw (left/right)
|
||||||
|
float pitch_speed = -orient_scale * gyro_speed[1] * ( 180.0f / M_PI );
|
||||||
|
float yaw_speed = orient_scale * gyro_speed[0] * ( 180.0f / M_PI );
|
||||||
|
float roll_speed = orient_scale * gyro_speed[2] * ( 180.0f / M_PI );
|
||||||
|
|
||||||
|
if( fabs( pitch_speed ) < gyro_pitch_deadzone.value )
|
||||||
|
pitch_speed = 0.0f;
|
||||||
|
if( fabs( yaw_speed ) < gyro_yaw_deadzone.value )
|
||||||
|
yaw_speed = 0.0f;
|
||||||
|
if( fabs( roll_speed ) < gyro_roll_deadzone.value )
|
||||||
|
roll_speed = 0.0f;
|
||||||
|
|
||||||
|
*dpitch -= gyro_pitch.value * pitch_speed * host.realframetime;
|
||||||
|
*dyaw += gyro_yaw.value * yaw_speed * host.realframetime;
|
||||||
|
*dyaw += gyro_roll.value * roll_speed * host.realframetime;
|
||||||
|
|
||||||
|
VectorClear( gyro_speed );
|
||||||
|
}
|
||||||
@@ -68,6 +68,7 @@ static CVAR_DEFINE_AUTO( joy_gyro_roll, "0.0", FCVAR_ARCHIVE | FCVAR_FILTERABLE,
|
|||||||
static CVAR_DEFINE_AUTO( joy_gyro_pitch_deadzone, "0.5", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "gyroscope pitch axis deadzone (deg/s)" );
|
static CVAR_DEFINE_AUTO( joy_gyro_pitch_deadzone, "0.5", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "gyroscope pitch axis deadzone (deg/s)" );
|
||||||
static CVAR_DEFINE_AUTO( joy_gyro_yaw_deadzone, "0.5", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "gyroscope yaw axis deadzone (deg/s)" );
|
static CVAR_DEFINE_AUTO( joy_gyro_yaw_deadzone, "0.5", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "gyroscope yaw axis deadzone (deg/s)" );
|
||||||
static CVAR_DEFINE_AUTO( joy_gyro_roll_deadzone, "0.5", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "gyroscope roll axis deadzone (deg/s)" );
|
static CVAR_DEFINE_AUTO( joy_gyro_roll_deadzone, "0.5", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "gyroscope roll axis deadzone (deg/s)" );
|
||||||
|
static CVAR_DEFINE_AUTO( joy_gyro_enable, "1", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "enables aiming with gamepad gyroscope" );
|
||||||
static CVAR_DEFINE_AUTO( joy_debug, "0", 0, "visualize gamepad axes and buttons" );
|
static CVAR_DEFINE_AUTO( joy_debug, "0", 0, "visualize gamepad axes and buttons" );
|
||||||
|
|
||||||
// stores the latest instantaneous gyroscope rotation rates (in rad/s) from platform input
|
// stores the latest instantaneous gyroscope rotation rates (in rad/s) from platform input
|
||||||
@@ -197,7 +198,7 @@ static int Joy_GetHatValueForAxis( const engineAxis_t engineAxis )
|
|||||||
positive = JOY_HAT_RIGHT;
|
positive = JOY_HAT_RIGHT;
|
||||||
break;
|
break;
|
||||||
case JOY_AXIS_FWD:
|
case JOY_AXIS_FWD:
|
||||||
threshold = joy_side_key_threshold.value;
|
threshold = joy_forward_key_threshold.value;
|
||||||
negative = JOY_HAT_UP;
|
negative = JOY_HAT_UP;
|
||||||
positive = JOY_HAT_DOWN;
|
positive = JOY_HAT_DOWN;
|
||||||
break;
|
break;
|
||||||
@@ -318,7 +319,7 @@ void Joy_FinalizeMove( float *fw, float *side, float *dpitch, float *dyaw )
|
|||||||
const char *bind = joy_axis_binding.string;
|
const char *bind = joy_axis_binding.string;
|
||||||
size_t i;
|
size_t i;
|
||||||
|
|
||||||
for( i = 0; bind[i]; i++ )
|
for( i = 0; bind[i] && i < MAX_AXES; i++ )
|
||||||
{
|
{
|
||||||
switch( bind[i] )
|
switch( bind[i] )
|
||||||
{
|
{
|
||||||
@@ -340,7 +341,7 @@ void Joy_FinalizeMove( float *fw, float *side, float *dpitch, float *dyaw )
|
|||||||
*dpitch += joy_pitch.value * (float)joyaxis[JOY_AXIS_PITCH].val/(float)SHRT_MAX * host.realframetime;
|
*dpitch += joy_pitch.value * (float)joyaxis[JOY_AXIS_PITCH].val/(float)SHRT_MAX * host.realframetime;
|
||||||
*dyaw -= joy_yaw.value * (float)joyaxis[JOY_AXIS_YAW ].val/(float)SHRT_MAX * host.realframetime;
|
*dyaw -= joy_yaw.value * (float)joyaxis[JOY_AXIS_YAW ].val/(float)SHRT_MAX * host.realframetime;
|
||||||
|
|
||||||
if( joy_have_gyro.value && (int)joy_calibrated.value == JOY_CALIBRATED )
|
if( joy_gyro_enable.value && joy_have_gyro.value && (int)joy_calibrated.value == JOY_CALIBRATED )
|
||||||
{
|
{
|
||||||
float pitch_speed = joy_gyro_speed[0] * ( 180.0f / M_PI );
|
float pitch_speed = joy_gyro_speed[0] * ( 180.0f / M_PI );
|
||||||
float yaw_speed = joy_gyro_speed[1] * ( 180.0f / M_PI );
|
float yaw_speed = joy_gyro_speed[1] * ( 180.0f / M_PI );
|
||||||
@@ -596,6 +597,7 @@ void Joy_Init( void )
|
|||||||
Cvar_RegisterVariable( &joy_have_gyro );
|
Cvar_RegisterVariable( &joy_have_gyro );
|
||||||
Cvar_RegisterVariable( &joy_calibrated );
|
Cvar_RegisterVariable( &joy_calibrated );
|
||||||
Cvar_RegisterVariable( &joy_enable );
|
Cvar_RegisterVariable( &joy_enable );
|
||||||
|
Cvar_RegisterVariable( &joy_gyro_enable );
|
||||||
|
|
||||||
Cvar_RegisterVariable( &joy_gyro_pitch );
|
Cvar_RegisterVariable( &joy_gyro_pitch );
|
||||||
Cvar_RegisterVariable( &joy_gyro_yaw );
|
Cvar_RegisterVariable( &joy_gyro_yaw );
|
||||||
@@ -158,9 +158,6 @@ static const keyname_t keynames[] =
|
|||||||
{ "INTERNATIONAL9", K_INTERNATIONAL + 8, "" },
|
{ "INTERNATIONAL9", K_INTERNATIONAL + 8, "" },
|
||||||
};
|
};
|
||||||
|
|
||||||
static void OSK_EnableTextInput( qboolean enable, qboolean force );
|
|
||||||
static qboolean OSK_KeyEvent( int key, int down );
|
|
||||||
static CVAR_DEFINE_AUTO( osk_enable, "0", FCVAR_ARCHIVE|FCVAR_FILTERABLE, "enable built-in on-screen keyboard" );
|
|
||||||
static CVAR_DEFINE_AUTO( key_rotate, "0", FCVAR_ARCHIVE|FCVAR_FILTERABLE, "rotate arrow keys (0-3)" );
|
static CVAR_DEFINE_AUTO( key_rotate, "0", FCVAR_ARCHIVE|FCVAR_FILTERABLE, "rotate arrow keys (0-3)" );
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -200,7 +197,14 @@ static int Key_StringToKeynum( const char *str )
|
|||||||
|
|
||||||
// check for hex code
|
// check for hex code
|
||||||
if( str[0] == '0' && str[1] == 'x' )
|
if( str[0] == '0' && str[1] == 'x' )
|
||||||
return Q_atoi( str );
|
{
|
||||||
|
int key = Q_atoi( str );
|
||||||
|
|
||||||
|
if( key >= ARRAYSIZE( keys ))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
// scan for a text match
|
// scan for a text match
|
||||||
for( i = 0; i < ARRAYSIZE( keynames ); i++ )
|
for( i = 0; i < ARRAYSIZE( keynames ); i++ )
|
||||||
@@ -520,7 +524,7 @@ qboolean Cmd_GetKeysList( const char *s, char *completedname, int length, qboole
|
|||||||
const char *keyname = Key_KeynumToString( i );
|
const char *keyname = Key_KeynumToString( i );
|
||||||
|
|
||||||
if(( *s == '*' ) || !Q_strnicmp( keyname, s, len ))
|
if(( *s == '*' ) || !Q_strnicmp( keyname, s, len ))
|
||||||
Q_strncpy( keys_strings[numkeys++], keyname, sizeof( keys[0] ));
|
Q_strncpy( keys_strings[numkeys++], keyname, sizeof( keys_strings[0] ));
|
||||||
}
|
}
|
||||||
|
|
||||||
if( !numkeys ) return false;
|
if( !numkeys ) return false;
|
||||||
@@ -579,7 +583,6 @@ void Key_Init( void )
|
|||||||
for( i = 0; i < ARRAYSIZE( keynames ); i++ )
|
for( i = 0; i < ARRAYSIZE( keynames ); i++ )
|
||||||
Key_SetBinding( keynames[i].keynum, keynames[i].binding );
|
Key_SetBinding( keynames[i].keynum, keynames[i].binding );
|
||||||
|
|
||||||
Cvar_RegisterVariable( &osk_enable );
|
|
||||||
Cvar_RegisterVariable( &key_rotate );
|
Cvar_RegisterVariable( &key_rotate );
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -985,288 +988,3 @@ int Key_ToUpper( int keynum )
|
|||||||
|
|
||||||
return Q_toupper( keynum );
|
return Q_toupper( keynum );
|
||||||
}
|
}
|
||||||
|
|
||||||
/* On-screen keyboard:
|
|
||||||
*
|
|
||||||
* 4 lines with 13 buttons each
|
|
||||||
* Left trigger == backspace
|
|
||||||
* Right trigger == space
|
|
||||||
* Any button press is button press on keyboard
|
|
||||||
*
|
|
||||||
* Our layout:
|
|
||||||
* 0 1 2 3 4 5 6 7 8 9 10 11 12
|
|
||||||
* +--+--+--+--+--+--+--+--+--+--+--+--+--+
|
|
||||||
* |` |1 |2 |3 |4 |5 |6 |7 |8 |9 |0 |- |= | 0
|
|
||||||
* +--+--+--+--+--+--+--+--+--+--+--+--+--+
|
|
||||||
* |q |w |e |r |t |y |u |i |o |p |[ |] |\ | 1
|
|
||||||
* +--+--+--+--+--+--+--+--+--+--+--+--+--+
|
|
||||||
* |CL|a |s |d |f |g |h |j |k |l |; |' |BS| 2
|
|
||||||
* +--+--+--+--+--+--+--+--+--+--+--+--+--+
|
|
||||||
* |SH|z |x |c |v |b |n |m |, |. |/ |SP|EN| 3
|
|
||||||
* +--+--+--+--+--+--+--+--+--+--+--+--+--+
|
|
||||||
*/
|
|
||||||
|
|
||||||
#define MAX_OSK_ROWS 13
|
|
||||||
#define MAX_OSK_LINES 4
|
|
||||||
|
|
||||||
enum
|
|
||||||
{
|
|
||||||
OSK_DEFAULT = 0,
|
|
||||||
OSK_UPPER, // on caps, shift
|
|
||||||
/*
|
|
||||||
OSK_RUSSIAN,
|
|
||||||
OSK_RUSSIAN_UPPER,
|
|
||||||
*/
|
|
||||||
OSK_LAST
|
|
||||||
};
|
|
||||||
|
|
||||||
enum
|
|
||||||
{
|
|
||||||
OSK_TAB = 16,
|
|
||||||
OSK_SHIFT,
|
|
||||||
OSK_BACKSPACE,
|
|
||||||
OSK_ENTER,
|
|
||||||
OSK_SPECKEY_LAST
|
|
||||||
};
|
|
||||||
static const char *osk_keylayout[][4] =
|
|
||||||
{
|
|
||||||
{
|
|
||||||
"`1234567890-=", // 13
|
|
||||||
"qwertyuiop[]\\", // 13
|
|
||||||
"\x10" "asdfghjkl;'" "\x12", // 11 + caps on a left, enter on a right
|
|
||||||
"\x11" "zxcvbnm,./ " "\x13" // 10 + esc on left + shift on a left/right
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"~!@#$%^&*()_+",
|
|
||||||
"QWERTYUIOP{}|",
|
|
||||||
"\x10" "ASDFGHJKL:\"" "\x12",
|
|
||||||
"\x11" "ZXCVBNM<>? " "\x13"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct osk_s
|
|
||||||
{
|
|
||||||
qboolean enable;
|
|
||||||
int curlayout;
|
|
||||||
qboolean shift;
|
|
||||||
qboolean sending;
|
|
||||||
struct {
|
|
||||||
signed char x;
|
|
||||||
signed char y;
|
|
||||||
char val;
|
|
||||||
} curbutton;
|
|
||||||
} osk;
|
|
||||||
|
|
||||||
static qboolean OSK_KeyEvent( int key, int down )
|
|
||||||
{
|
|
||||||
if( !osk.enable || !osk_enable.value )
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if( osk.sending )
|
|
||||||
{
|
|
||||||
osk.sending = false;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( osk.curbutton.val == 0 )
|
|
||||||
{
|
|
||||||
if( key == K_ENTER || key == K_A_BUTTON )
|
|
||||||
{
|
|
||||||
osk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x];
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
switch ( key )
|
|
||||||
{
|
|
||||||
case K_A_BUTTON:
|
|
||||||
case K_ENTER:
|
|
||||||
switch( osk.curbutton.val )
|
|
||||||
{
|
|
||||||
case OSK_ENTER:
|
|
||||||
osk.sending = true;
|
|
||||||
Key_Event( K_ENTER, down );
|
|
||||||
//osk_enable = false; // TODO: handle multiline
|
|
||||||
break;
|
|
||||||
case OSK_SHIFT:
|
|
||||||
if( !down )
|
|
||||||
break;
|
|
||||||
|
|
||||||
if( osk.curlayout & 1 )
|
|
||||||
osk.curlayout--;
|
|
||||||
else
|
|
||||||
osk.curlayout++;
|
|
||||||
|
|
||||||
osk.shift = true;
|
|
||||||
osk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x];
|
|
||||||
break;
|
|
||||||
case OSK_BACKSPACE:
|
|
||||||
Key_Event( K_BACKSPACE, down ); break;
|
|
||||||
case OSK_TAB:
|
|
||||||
Key_Event( K_TAB, down ); break;
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
int ch;
|
|
||||||
|
|
||||||
if( !down )
|
|
||||||
{
|
|
||||||
if( osk.shift && osk.curlayout & 1 )
|
|
||||||
osk.curlayout--;
|
|
||||||
|
|
||||||
osk.shift = false;
|
|
||||||
osk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
ch = (byte)osk.curbutton.val;
|
|
||||||
|
|
||||||
// do not pass UTF-8 sequence into the engine, convert it here
|
|
||||||
if( !cls.accept_utf8 )
|
|
||||||
ch = Con_UtfProcessCharForce( ch );
|
|
||||||
|
|
||||||
if( !ch )
|
|
||||||
break;
|
|
||||||
|
|
||||||
CL_CharEvent( ch );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case K_UPARROW:
|
|
||||||
if( down && --osk.curbutton.y < 0 )
|
|
||||||
{
|
|
||||||
osk.curbutton.y = MAX_OSK_LINES - 1;
|
|
||||||
osk.curbutton.val = 0;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case K_DOWNARROW:
|
|
||||||
if( down && ++osk.curbutton.y >= MAX_OSK_LINES )
|
|
||||||
{
|
|
||||||
osk.curbutton.y = 0;
|
|
||||||
osk.curbutton.val = 0;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case K_LEFTARROW:
|
|
||||||
if( down && --osk.curbutton.x < 0 )
|
|
||||||
osk.curbutton.x = MAX_OSK_ROWS - 1;
|
|
||||||
break;
|
|
||||||
case K_RIGHTARROW:
|
|
||||||
if( down && ++osk.curbutton.x >= MAX_OSK_ROWS )
|
|
||||||
osk.curbutton.x = 0;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
osk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x];
|
|
||||||
return true;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
=============
|
|
||||||
OSK_EnableTextInput
|
|
||||||
|
|
||||||
Enables built-in IME
|
|
||||||
=============
|
|
||||||
*/
|
|
||||||
static void OSK_EnableTextInput( qboolean enable, qboolean force )
|
|
||||||
{
|
|
||||||
qboolean old = osk.enable;
|
|
||||||
|
|
||||||
osk.enable = enable;
|
|
||||||
|
|
||||||
if( osk.enable && (!old || force) )
|
|
||||||
{
|
|
||||||
osk.curlayout = 0;
|
|
||||||
osk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#define X_START 0.1347475f
|
|
||||||
#define Y_START 0.567f
|
|
||||||
#define X_STEP 0.05625
|
|
||||||
#define Y_STEP 0.0825
|
|
||||||
|
|
||||||
/*
|
|
||||||
============
|
|
||||||
Joy_DrawSymbolButton
|
|
||||||
|
|
||||||
Draw button with symbol on it
|
|
||||||
============
|
|
||||||
*/
|
|
||||||
static void OSK_DrawSymbolButton( int symb, float x, float y, float width, float height )
|
|
||||||
{
|
|
||||||
cl_font_t *font = Con_GetCurFont();
|
|
||||||
byte color[] = { 255, 255, 255, 255 };
|
|
||||||
int x1 = x * refState.width,
|
|
||||||
y1 = y * refState.height,
|
|
||||||
w = width * refState.width,
|
|
||||||
h = height * refState.height;
|
|
||||||
|
|
||||||
if( symb == osk.curbutton.val )
|
|
||||||
ref.dllFuncs.FillRGBA( kRenderTransTexture, x1, y1, w, h, 255, 160, 0, 100 );
|
|
||||||
|
|
||||||
if( !symb || symb == ' ' || (symb >= OSK_TAB && symb < OSK_SPECKEY_LAST ) )
|
|
||||||
return;
|
|
||||||
|
|
||||||
CL_DrawCharacter(
|
|
||||||
x1 + width * 0.4 * refState.width,
|
|
||||||
y1 + height * 0.4 * refState.height,
|
|
||||||
symb, color, font, 0 );
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
=============
|
|
||||||
Joy_DrawSpecialButton
|
|
||||||
|
|
||||||
Draw special button, like shift, enter or esc
|
|
||||||
=============
|
|
||||||
*/
|
|
||||||
static void OSK_DrawSpecialButton( const char *name, float x, float y, float width, float height )
|
|
||||||
{
|
|
||||||
byte color[] = { 0, 255, 0, 255 };
|
|
||||||
|
|
||||||
Con_DrawString(
|
|
||||||
x * refState.width + width * 0.4 * refState.width,
|
|
||||||
y * refState.height + height * 0.4 * refState.height,
|
|
||||||
name,
|
|
||||||
color );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
=============
|
|
||||||
Joy_DrawOnScreenKeyboard
|
|
||||||
|
|
||||||
Draw on screen keyboard, if enabled
|
|
||||||
=============
|
|
||||||
*/
|
|
||||||
void OSK_Draw( void )
|
|
||||||
{
|
|
||||||
const char **curlayout = osk_keylayout[osk.curlayout]; // shortcut :)
|
|
||||||
float x, y;
|
|
||||||
int i, j;
|
|
||||||
|
|
||||||
if( !osk.enable || !osk_enable.value || !osk.curbutton.val )
|
|
||||||
return;
|
|
||||||
|
|
||||||
// draw keyboard
|
|
||||||
ref.dllFuncs.FillRGBA( kRenderTransTexture, X_START * refState.width, Y_START * refState.height,
|
|
||||||
X_STEP * MAX_OSK_ROWS * refState.width,
|
|
||||||
Y_STEP * MAX_OSK_LINES * refState.height, 100, 100, 100, 100 );
|
|
||||||
|
|
||||||
OSK_DrawSpecialButton( "-]", X_START, Y_START + Y_STEP * 2, X_STEP, Y_STEP );
|
|
||||||
OSK_DrawSpecialButton( "<-", X_START + X_STEP * 12, Y_START + Y_STEP * 2, X_STEP, Y_STEP );
|
|
||||||
|
|
||||||
OSK_DrawSpecialButton( "sh", X_START, Y_START + Y_STEP * 3, X_STEP, Y_STEP );
|
|
||||||
OSK_DrawSpecialButton( "en", X_START + X_STEP * 12, Y_START + Y_STEP * 3, X_STEP, Y_STEP );
|
|
||||||
|
|
||||||
for( y = Y_START, j = 0; j < MAX_OSK_LINES; j++, y += Y_STEP )
|
|
||||||
for( x = X_START, i = 0; i < MAX_OSK_ROWS; i++, x += X_STEP )
|
|
||||||
OSK_DrawSymbolButton( curlayout[j][i], x, y, X_STEP, Y_STEP );
|
|
||||||
}
|
|
||||||
312
engine/client/input/in_osk.c
Normal file
312
engine/client/input/in_osk.c
Normal file
@@ -0,0 +1,312 @@
|
|||||||
|
/*
|
||||||
|
in_osk.c - on-screen keyboard input
|
||||||
|
Copyright (C) 2016-2026 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include "input.h"
|
||||||
|
#include "client.h"
|
||||||
|
|
||||||
|
CVAR_DEFINE_AUTO( osk_enable, "0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "enable built-in on-screen keyboard" );
|
||||||
|
|
||||||
|
/* On-screen keyboard:
|
||||||
|
*
|
||||||
|
* 4 lines with 13 buttons each
|
||||||
|
* Left trigger == backspace
|
||||||
|
* Right trigger == space
|
||||||
|
* Any button press is button press on keyboard
|
||||||
|
*
|
||||||
|
* Our layout:
|
||||||
|
* 0 1 2 3 4 5 6 7 8 9 10 11 12
|
||||||
|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
* |` |1 |2 |3 |4 |5 |6 |7 |8 |9 |0 |- |= | 0
|
||||||
|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
* |q |w |e |r |t |y |u |i |o |p |[ |] |\ | 1
|
||||||
|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
* |CL|a |s |d |f |g |h |j |k |l |; |' |BS| 2
|
||||||
|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
* |SH|z |x |c |v |b |n |m |, |. |/ |SP|EN| 3
|
||||||
|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define MAX_OSK_ROWS 13
|
||||||
|
#define MAX_OSK_LINES 4
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
OSK_DEFAULT = 0,
|
||||||
|
OSK_UPPER, // on caps, shift
|
||||||
|
/*
|
||||||
|
OSK_RUSSIAN,
|
||||||
|
OSK_RUSSIAN_UPPER,
|
||||||
|
*/
|
||||||
|
OSK_LAST
|
||||||
|
};
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
OSK_TAB = 16,
|
||||||
|
OSK_SHIFT,
|
||||||
|
OSK_BACKSPACE,
|
||||||
|
OSK_ENTER,
|
||||||
|
OSK_SPECKEY_LAST
|
||||||
|
};
|
||||||
|
static const char *osk_keylayout[][4] =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
"`1234567890-=", // 13
|
||||||
|
"qwertyuiop[]\\", // 13
|
||||||
|
"\x10" "asdfghjkl;'" "\x12", // 11 + caps on a left, enter on a right
|
||||||
|
"\x11" "zxcvbnm,./ " "\x13" // 10 + esc on left + shift on a left/right
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~!@#$%^&*()_+",
|
||||||
|
"QWERTYUIOP{}|",
|
||||||
|
"\x10" "ASDFGHJKL:\"" "\x12",
|
||||||
|
"\x11" "ZXCVBNM<>? " "\x13"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct osk_s
|
||||||
|
{
|
||||||
|
qboolean enable;
|
||||||
|
int curlayout;
|
||||||
|
qboolean shift;
|
||||||
|
qboolean sending;
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
signed char x;
|
||||||
|
signed char y;
|
||||||
|
char val;
|
||||||
|
} curbutton;
|
||||||
|
} osk;
|
||||||
|
|
||||||
|
qboolean OSK_KeyEvent( int key, int down )
|
||||||
|
{
|
||||||
|
if( !osk.enable || !osk_enable.value )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if( osk.sending )
|
||||||
|
{
|
||||||
|
osk.sending = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( osk.curbutton.val == 0 )
|
||||||
|
{
|
||||||
|
if( key == K_ENTER || key == K_A_BUTTON )
|
||||||
|
{
|
||||||
|
osk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
switch( key )
|
||||||
|
{
|
||||||
|
case K_A_BUTTON:
|
||||||
|
case K_ENTER:
|
||||||
|
switch( osk.curbutton.val )
|
||||||
|
{
|
||||||
|
case OSK_ENTER:
|
||||||
|
osk.sending = true;
|
||||||
|
Key_Event( K_ENTER, down );
|
||||||
|
// osk_enable = false; // TODO: handle multiline
|
||||||
|
break;
|
||||||
|
case OSK_SHIFT:
|
||||||
|
if( !down )
|
||||||
|
break;
|
||||||
|
|
||||||
|
if( osk.curlayout & 1 )
|
||||||
|
osk.curlayout--;
|
||||||
|
else
|
||||||
|
osk.curlayout++;
|
||||||
|
|
||||||
|
osk.shift = true;
|
||||||
|
osk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x];
|
||||||
|
break;
|
||||||
|
case OSK_BACKSPACE:
|
||||||
|
Key_Event( K_BACKSPACE, down );
|
||||||
|
break;
|
||||||
|
case OSK_TAB:
|
||||||
|
Key_Event( K_TAB, down );
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
int ch;
|
||||||
|
|
||||||
|
if( !down )
|
||||||
|
{
|
||||||
|
if( osk.shift && osk.curlayout & 1 )
|
||||||
|
osk.curlayout--;
|
||||||
|
|
||||||
|
osk.shift = false;
|
||||||
|
osk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ch = (byte)osk.curbutton.val;
|
||||||
|
|
||||||
|
// do not pass UTF-8 sequence into the engine, convert it here
|
||||||
|
if( !cls.accept_utf8 )
|
||||||
|
ch = Con_UtfProcessCharForce( ch );
|
||||||
|
|
||||||
|
if( !ch )
|
||||||
|
break;
|
||||||
|
|
||||||
|
CL_CharEvent( ch );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case K_UPARROW:
|
||||||
|
if( down && --osk.curbutton.y < 0 )
|
||||||
|
{
|
||||||
|
osk.curbutton.y = MAX_OSK_LINES - 1;
|
||||||
|
osk.curbutton.val = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case K_DOWNARROW:
|
||||||
|
if( down && ++osk.curbutton.y >= MAX_OSK_LINES )
|
||||||
|
{
|
||||||
|
osk.curbutton.y = 0;
|
||||||
|
osk.curbutton.val = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case K_LEFTARROW:
|
||||||
|
if( down && --osk.curbutton.x < 0 )
|
||||||
|
osk.curbutton.x = MAX_OSK_ROWS - 1;
|
||||||
|
break;
|
||||||
|
case K_RIGHTARROW:
|
||||||
|
if( down && ++osk.curbutton.x >= MAX_OSK_ROWS )
|
||||||
|
osk.curbutton.x = 0;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
osk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x];
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
=============
|
||||||
|
OSK_EnableTextInput
|
||||||
|
|
||||||
|
Enables built-in IME
|
||||||
|
=============
|
||||||
|
*/
|
||||||
|
void OSK_EnableTextInput( qboolean enable, qboolean force )
|
||||||
|
{
|
||||||
|
qboolean old = osk.enable;
|
||||||
|
|
||||||
|
osk.enable = enable;
|
||||||
|
|
||||||
|
if( osk.enable && ( !old || force ))
|
||||||
|
{
|
||||||
|
osk.curlayout = 0;
|
||||||
|
osk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define X_START 0.1347475f
|
||||||
|
#define Y_START 0.567f
|
||||||
|
#define X_STEP 0.05625
|
||||||
|
#define Y_STEP 0.0825
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
OSK_DrawSymbolButton
|
||||||
|
|
||||||
|
Draw button with symbol on it
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
static void OSK_DrawSymbolButton( int symb, float x, float y, float width, float height )
|
||||||
|
{
|
||||||
|
cl_font_t *font = Con_GetCurFont();
|
||||||
|
byte color[] = { 255, 255, 255, 255 };
|
||||||
|
int x1 = x * refState.width,
|
||||||
|
y1 = y * refState.height,
|
||||||
|
w = width * refState.width,
|
||||||
|
h = height * refState.height;
|
||||||
|
|
||||||
|
if( symb == osk.curbutton.val )
|
||||||
|
ref.dllFuncs.FillRGBA( kRenderTransTexture, x1, y1, w, h, 255, 160, 0, 100 );
|
||||||
|
|
||||||
|
if( !symb || symb == ' ' || ( symb >= OSK_TAB && symb < OSK_SPECKEY_LAST ))
|
||||||
|
return;
|
||||||
|
|
||||||
|
CL_DrawCharacter(
|
||||||
|
x1 + width * 0.4 * refState.width,
|
||||||
|
y1 + height * 0.4 * refState.height,
|
||||||
|
symb, color, font, 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
=============
|
||||||
|
OSK_DrawSpecialButton
|
||||||
|
|
||||||
|
Draw special button, like shift, enter or esc
|
||||||
|
=============
|
||||||
|
*/
|
||||||
|
static void OSK_DrawSpecialButton( const char *name, float x, float y, float width, float height )
|
||||||
|
{
|
||||||
|
byte color[] = { 0, 255, 0, 255 };
|
||||||
|
|
||||||
|
Con_DrawString(
|
||||||
|
x * refState.width + width * 0.4 * refState.width,
|
||||||
|
y * refState.height + height * 0.4 * refState.height,
|
||||||
|
name,
|
||||||
|
color );
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
=============
|
||||||
|
OSK_Draw
|
||||||
|
|
||||||
|
Draw on screen keyboard, if enabled
|
||||||
|
=============
|
||||||
|
*/
|
||||||
|
void OSK_Draw( void )
|
||||||
|
{
|
||||||
|
const char **curlayout = osk_keylayout[osk.curlayout]; // shortcut :)
|
||||||
|
float x, y;
|
||||||
|
int i, j;
|
||||||
|
|
||||||
|
if( !osk.enable || !osk_enable.value || !osk.curbutton.val )
|
||||||
|
return;
|
||||||
|
|
||||||
|
// draw keyboard
|
||||||
|
ref.dllFuncs.FillRGBA( kRenderTransTexture, X_START * refState.width, Y_START * refState.height,
|
||||||
|
X_STEP * MAX_OSK_ROWS * refState.width,
|
||||||
|
Y_STEP * MAX_OSK_LINES * refState.height, 100, 100, 100, 100 );
|
||||||
|
|
||||||
|
OSK_DrawSpecialButton( "-]", X_START, Y_START + Y_STEP * 2, X_STEP, Y_STEP );
|
||||||
|
OSK_DrawSpecialButton( "<-", X_START + X_STEP * 12, Y_START + Y_STEP * 2, X_STEP, Y_STEP );
|
||||||
|
|
||||||
|
OSK_DrawSpecialButton( "sh", X_START, Y_START + Y_STEP * 3, X_STEP, Y_STEP );
|
||||||
|
OSK_DrawSpecialButton( "en", X_START + X_STEP * 12, Y_START + Y_STEP * 3, X_STEP, Y_STEP );
|
||||||
|
|
||||||
|
for( y = Y_START, j = 0; j < MAX_OSK_LINES; j++, y += Y_STEP )
|
||||||
|
for( x = X_START, i = 0; i < MAX_OSK_ROWS; i++, x += X_STEP )
|
||||||
|
OSK_DrawSymbolButton( curlayout[j][i], x, y, X_STEP, Y_STEP );
|
||||||
|
}
|
||||||
|
|
||||||
|
void OSK_Init( void )
|
||||||
|
{
|
||||||
|
Cvar_RegisterVariable( &osk_enable );
|
||||||
|
}
|
||||||
@@ -185,10 +185,10 @@ void Touch_NotifyResize( void )
|
|||||||
|
|
||||||
static inline float Touch_AspectRatio( void )
|
static inline float Touch_AspectRatio( void )
|
||||||
{
|
{
|
||||||
if( touch.config_aspect_ratio )
|
if( touch.config_aspect_ratio >= 0.25f )
|
||||||
return touch.config_aspect_ratio;
|
return touch.config_aspect_ratio;
|
||||||
|
|
||||||
if( touch.actual_aspect_ratio )
|
if( touch.actual_aspect_ratio >= 0.25f )
|
||||||
return touch.actual_aspect_ratio;
|
return touch.actual_aspect_ratio;
|
||||||
|
|
||||||
if( refState.width && refState.height )
|
if( refState.width && refState.height )
|
||||||
@@ -369,7 +369,7 @@ static void Touch_ExportConfig_f( void )
|
|||||||
string profilebase;
|
string profilebase;
|
||||||
|
|
||||||
COM_FileBase( name, profilebase, sizeof( profilebase ));
|
COM_FileBase( name, profilebase, sizeof( profilebase ));
|
||||||
Q_snprintf( profilename, sizeof( profilebase ), "touch_profiles/%s (copy).cfg", profilebase );
|
Q_snprintf( profilename, sizeof( profilename ), "touch_profiles/%s (copy).cfg", profilebase );
|
||||||
}
|
}
|
||||||
else Q_strncpy( profilename, name, sizeof( profilename ));
|
else Q_strncpy( profilename, name, sizeof( profilename ));
|
||||||
|
|
||||||
@@ -588,7 +588,7 @@ static void Touch_RemoveButtonFromList( touchbuttonlist_t *list, const char *nam
|
|||||||
|
|
||||||
IN_TouchEditClear();
|
IN_TouchEditClear();
|
||||||
|
|
||||||
while(( button = Touch_FindFirst( &touch.list_user, name, privileged )))
|
while(( button = Touch_FindFirst( list, name, privileged )))
|
||||||
{
|
{
|
||||||
if( button->prev )
|
if( button->prev )
|
||||||
button->prev->next = button->next;
|
button->prev->next = button->next;
|
||||||
@@ -860,8 +860,7 @@ void Touch_AddClientButton( const char *name, const char *texture, const char *c
|
|||||||
if( !touch.initialized )
|
if( !touch.initialized )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if( round )
|
IN_TouchCheckCoords( &x1, &y1, &x2, &y2 );
|
||||||
IN_TouchCheckCoords( &x1, &y1, &x2, &y2 );
|
|
||||||
|
|
||||||
if( round == round_aspect )
|
if( round == round_aspect )
|
||||||
y2 = y1 + ( x2 - x1 ) / (Touch_AspectRatio()) * aspect;
|
y2 = y1 + ( x2 - x1 ) / (Touch_AspectRatio()) * aspect;
|
||||||
@@ -1068,7 +1067,7 @@ static void Touch_InitEditor( void )
|
|||||||
temp = Touch_AddButton( &touch.list_edit, "close", "touch_default/edit_close", "touch_disableedit", 0, y, x, y + 0.1f, color, true );
|
temp = Touch_AddButton( &touch.list_edit, "close", "touch_default/edit_close", "touch_disableedit", 0, y, x, y + 0.1f, color, true );
|
||||||
SetBits( temp->flags, TOUCH_FL_NOEDIT );
|
SetBits( temp->flags, TOUCH_FL_NOEDIT );
|
||||||
|
|
||||||
temp = Touch_AddButton( &touch.list_edit, "close", "#Close and save", "", x, y, x + 0.2f, y + 0.1f, color, true );
|
temp = Touch_AddButton( &touch.list_edit, "close_label", "#Close and save", "", x, y, x + 0.2f, y + 0.1f, color, true );
|
||||||
SetBits( temp->flags, TOUCH_FL_NOEDIT );
|
SetBits( temp->flags, TOUCH_FL_NOEDIT );
|
||||||
|
|
||||||
y += 0.2f;
|
y += 0.2f;
|
||||||
@@ -1076,7 +1075,7 @@ static void Touch_InitEditor( void )
|
|||||||
temp = Touch_AddButton( &touch.list_edit, "cancel", "touch_default/edit_reset", "touch_reloadconfig", 0, y, x, y + 0.1f, color, true );
|
temp = Touch_AddButton( &touch.list_edit, "cancel", "touch_default/edit_reset", "touch_reloadconfig", 0, y, x, y + 0.1f, color, true );
|
||||||
SetBits( temp->flags, TOUCH_FL_NOEDIT );
|
SetBits( temp->flags, TOUCH_FL_NOEDIT );
|
||||||
|
|
||||||
temp = Touch_AddButton( &touch.list_edit, "close", "#Cancel and reset", "", x, y, x + 0.2f, y + 0.1f, color, true );
|
temp = Touch_AddButton( &touch.list_edit, "cancel_label", "#Cancel and reset", "", x, y, x + 0.2f, y + 0.1f, color, true );
|
||||||
SetBits( temp->flags, TOUCH_FL_NOEDIT );
|
SetBits( temp->flags, TOUCH_FL_NOEDIT );
|
||||||
|
|
||||||
y += 0.2f;
|
y += 0.2f;
|
||||||
@@ -1256,12 +1255,21 @@ static void Touch_DrawTexture( float x1, float y1, float x2, float y2, int textu
|
|||||||
0, 0, 1, 1, texture );
|
0, 0, 1, 1, texture );
|
||||||
}
|
}
|
||||||
|
|
||||||
#define GRID_COUNT_X ((int)touch_grid_count.value )
|
static inline int Touch_GridCountX( void )
|
||||||
#define GRID_COUNT_Y (((int)touch_grid_count.value ) * Touch_AspectRatio( ))
|
{
|
||||||
#define GRID_X ( 1.0f / GRID_COUNT_X )
|
return Q_max((int)touch_grid_count.value, 1 );
|
||||||
#define GRID_Y ( 1.0f / Touch_AspectRatio() / GRID_COUNT_X )
|
}
|
||||||
#define GRID_ROUND_X( x ) ((float)round(( x ) * GRID_COUNT_X ) / GRID_COUNT_X )
|
|
||||||
#define GRID_ROUND_Y( x ) ((float)round(( x ) * GRID_COUNT_Y ) / GRID_COUNT_Y )
|
static inline int Touch_GridCountY( void )
|
||||||
|
{
|
||||||
|
float grid_count_y = touch_grid_count.value * Touch_AspectRatio();
|
||||||
|
return Q_max((int)grid_count_y, 1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
#define GRID_X ( 1.0f / Touch_GridCountX( ))
|
||||||
|
#define GRID_Y ( 1.0f / Touch_GridCountY( ))
|
||||||
|
#define GRID_ROUND_X( x ) ((float)round(( x ) * Touch_GridCountX() ) / Touch_GridCountX())
|
||||||
|
#define GRID_ROUND_Y( x ) ((float)round(( x ) * Touch_GridCountY() ) / Touch_GridCountY())
|
||||||
|
|
||||||
static void IN_TouchCheckCoords( float *x1, float *y1, float *x2, float *y2 )
|
static void IN_TouchCheckCoords( float *x1, float *y1, float *x2, float *y2 )
|
||||||
{
|
{
|
||||||
@@ -445,6 +445,10 @@ void IN_Init( void )
|
|||||||
{
|
{
|
||||||
IN_StartupMouse( );
|
IN_StartupMouse( );
|
||||||
|
|
||||||
|
IN_GyroInit();
|
||||||
|
|
||||||
|
OSK_Init();
|
||||||
|
|
||||||
Joy_Init(); // common joystick support init
|
Joy_Init(); // common joystick support init
|
||||||
|
|
||||||
Touch_Init();
|
Touch_Init();
|
||||||
@@ -560,6 +564,7 @@ static void IN_CollectInput( float *forward, float *side, float *pitch, float *y
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IN_GyroFinalizeMove( forward, side, pitch, yaw );
|
||||||
Joy_FinalizeMove( forward, side, pitch, yaw );
|
Joy_FinalizeMove( forward, side, pitch, yaw );
|
||||||
Touch_GetMove( forward, side, pitch, yaw );
|
Touch_GetMove( forward, side, pitch, yaw );
|
||||||
|
|
||||||
@@ -15,6 +15,7 @@ GNU General Public License for more details.
|
|||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "client.h"
|
#include "client.h"
|
||||||
|
#include "mod_local.h"
|
||||||
#include "net_encode.h"
|
#include "net_encode.h"
|
||||||
#include "cl_tent.h"
|
#include "cl_tent.h"
|
||||||
#include "shake.h"
|
#include "shake.h"
|
||||||
@@ -704,13 +705,35 @@ CL_ParseResourceRequest
|
|||||||
|
|
||||||
==================
|
==================
|
||||||
*/
|
*/
|
||||||
static void CL_ParseResourceRequest( sizebuf_t *msg )
|
void CL_SendResourceList( const resource_t *list, int count )
|
||||||
{
|
{
|
||||||
byte buffer[MAX_INIT_MSG];
|
byte buffer[MAX_INIT_MSG];
|
||||||
int i, arg, nStartIndex;
|
|
||||||
sizebuf_t sbuf;
|
sizebuf_t sbuf;
|
||||||
|
int i;
|
||||||
|
|
||||||
MSG_Init( &sbuf, "ResourceBlock", buffer, sizeof( buffer ));
|
MSG_Init( &sbuf, "ResourceBlock", buffer, sizeof( buffer ));
|
||||||
|
MSG_BeginClientCmd( &sbuf, clc_resourcelist );
|
||||||
|
MSG_WriteShort( &sbuf, count );
|
||||||
|
|
||||||
|
for( i = 0; i < count; i++ )
|
||||||
|
{
|
||||||
|
MSG_WriteString( &sbuf, list[i].szFileName );
|
||||||
|
MSG_WriteByte( &sbuf, list[i].type );
|
||||||
|
MSG_WriteShort( &sbuf, list[i].nIndex );
|
||||||
|
MSG_WriteLong( &sbuf, list[i].nDownloadSize );
|
||||||
|
MSG_WriteByte( &sbuf, list[i].ucFlags );
|
||||||
|
|
||||||
|
if( FBitSet( list[i].ucFlags, RES_CUSTOM ))
|
||||||
|
MSG_WriteBytes( &sbuf, list[i].rgucMD5_hash, 16 );
|
||||||
|
}
|
||||||
|
|
||||||
|
Netchan_CreateFragments( &cls.netchan, &sbuf );
|
||||||
|
Netchan_FragSend( &cls.netchan );
|
||||||
|
}
|
||||||
|
|
||||||
|
static void CL_ParseResourceRequest( sizebuf_t *msg )
|
||||||
|
{
|
||||||
|
int arg, nStartIndex;
|
||||||
|
|
||||||
arg = MSG_ReadLong( msg );
|
arg = MSG_ReadLong( msg );
|
||||||
nStartIndex = MSG_ReadLong( msg );
|
nStartIndex = MSG_ReadLong( msg );
|
||||||
@@ -718,31 +741,10 @@ static void CL_ParseResourceRequest( sizebuf_t *msg )
|
|||||||
if( cl.servercount != arg )
|
if( cl.servercount != arg )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if( nStartIndex < 0 && nStartIndex > cl.num_resources )
|
if( nStartIndex < 0 || nStartIndex >= cl.num_resources )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
MSG_BeginClientCmd( &sbuf, clc_resourcelist );
|
CL_SendResourceList( &cl.resourcelist[nStartIndex], cl.num_resources - nStartIndex );
|
||||||
MSG_WriteShort( &sbuf, cl.num_resources );
|
|
||||||
|
|
||||||
for( i = nStartIndex; i < cl.num_resources; i++ )
|
|
||||||
{
|
|
||||||
MSG_WriteString( &sbuf, cl.resourcelist[i].szFileName );
|
|
||||||
MSG_WriteByte( &sbuf, cl.resourcelist[i].type );
|
|
||||||
MSG_WriteShort( &sbuf, cl.resourcelist[i].nIndex );
|
|
||||||
MSG_WriteLong( &sbuf, cl.resourcelist[i].nDownloadSize );
|
|
||||||
MSG_WriteByte( &sbuf, cl.resourcelist[i].ucFlags );
|
|
||||||
|
|
||||||
if( FBitSet( cl.resourcelist[i].ucFlags, RES_CUSTOM ))
|
|
||||||
MSG_WriteBytes( &sbuf, cl.resourcelist[i].rgucMD5_hash, 16 );
|
|
||||||
}
|
|
||||||
|
|
||||||
// a1ba: useless check? MSG_BeginClientCmd and MSG_WriteShort will always
|
|
||||||
// write to the buffer
|
|
||||||
// if( MSG_GetNumBytesWritten( &sbuf ) > 0 )
|
|
||||||
{
|
|
||||||
Netchan_CreateFragments( &cls.netchan, &sbuf );
|
|
||||||
Netchan_FragSend( &cls.netchan );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -1738,6 +1740,8 @@ void CL_RegisterResources( sizebuf_t *msg, connprotocol_t proto )
|
|||||||
// tell rendering system we have a new set of models.
|
// tell rendering system we have a new set of models.
|
||||||
ref.dllFuncs.R_NewMap ();
|
ref.dllFuncs.R_NewMap ();
|
||||||
|
|
||||||
|
Mod_LoadDetailTextures( cl.worldmodel );
|
||||||
|
|
||||||
// check if this map must start from dark screen
|
// check if this map must start from dark screen
|
||||||
CL_StartDark ();
|
CL_StartDark ();
|
||||||
|
|
||||||
@@ -514,17 +514,26 @@ static void CL_ParseSoundPacketGS( sizebuf_t *msg )
|
|||||||
static void CL_ParseSpawnStaticSound( sizebuf_t *msg )
|
static void CL_ParseSpawnStaticSound( sizebuf_t *msg )
|
||||||
{
|
{
|
||||||
vec3_t pos;
|
vec3_t pos;
|
||||||
int handle, entnum, pitch, flags;
|
int sound, entnum, pitch, flags;
|
||||||
float volume, attn;
|
float volume, attn;
|
||||||
|
sound_t handle = 0;
|
||||||
|
|
||||||
MSG_ReadVec3Coord( msg, pos );
|
MSG_ReadVec3Coord( msg, pos );
|
||||||
handle = MSG_ReadShort( msg );
|
sound = MSG_ReadShort( msg );
|
||||||
volume = MSG_ReadByte( msg ) * ( 1.0f / 255.0f );
|
volume = MSG_ReadByte( msg ) * ( 1.0f / 255.0f );
|
||||||
attn = MSG_ReadByte( msg ) * ( 1.0f / 64.0f );
|
attn = MSG_ReadByte( msg ) * ( 1.0f / 64.0f );
|
||||||
entnum = MSG_ReadShort( msg );
|
entnum = MSG_ReadShort( msg );
|
||||||
pitch = MSG_ReadByte( msg );
|
pitch = MSG_ReadByte( msg );
|
||||||
flags = MSG_ReadByte( msg );
|
flags = MSG_ReadByte( msg );
|
||||||
|
|
||||||
|
if( FBitSet( flags, SND_SENTENCE ))
|
||||||
|
{
|
||||||
|
char sentenceName[32];
|
||||||
|
Q_snprintf( sentenceName, sizeof( sentenceName ), "!%i", sound );
|
||||||
|
handle = S_RegisterSound( sentenceName );
|
||||||
|
}
|
||||||
|
else handle = cl.sound_index[sound]; // see precached sound
|
||||||
|
|
||||||
S_AmbientSound( pos, entnum, handle, volume, attn, pitch, flags );
|
S_AmbientSound( pos, entnum, handle, volume, attn, pitch, flags );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,6 +99,7 @@ void SX_RoomFX( portable_samplepair_t *paint, int num_samples );
|
|||||||
void SX_ClearState( void );
|
void SX_ClearState( void );
|
||||||
|
|
||||||
qboolean S_Init( void );
|
qboolean S_Init( void );
|
||||||
|
qboolean S_InitSoundAPI( void );
|
||||||
void S_Shutdown( void );
|
void S_Shutdown( void );
|
||||||
void S_SoundList_f( void );
|
void S_SoundList_f( void );
|
||||||
void S_SoundInfo_f( void );
|
void S_SoundInfo_f( void );
|
||||||
|
|||||||
@@ -1948,7 +1948,7 @@ static const sound_api_t gSoundAPI = {
|
|||||||
S_InitSoundAPI
|
S_InitSoundAPI
|
||||||
================
|
================
|
||||||
*/
|
*/
|
||||||
static qboolean S_InitSoundAPI( void )
|
qboolean S_InitSoundAPI( void )
|
||||||
{
|
{
|
||||||
// make sure what sound functions is cleared
|
// make sure what sound functions is cleared
|
||||||
memset( &clgame.soundFuncs, 0, sizeof( clgame.soundFuncs ));
|
memset( &clgame.soundFuncs, 0, sizeof( clgame.soundFuncs ));
|
||||||
@@ -1967,6 +1967,9 @@ static qboolean S_InitSoundAPI( void )
|
|||||||
memset( &clgame.soundFuncs, 0, sizeof( clgame.soundFuncs ));
|
memset( &clgame.soundFuncs, 0, sizeof( clgame.soundFuncs ));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if( clgame.soundFuncs.pfnS_Init )
|
||||||
|
clgame.soundFuncs.pfnS_Init( &snd );
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2030,17 +2033,15 @@ qboolean S_Init( void )
|
|||||||
S_InitSounds ();
|
S_InitSounds ();
|
||||||
VOX_Init ();
|
VOX_Init ();
|
||||||
|
|
||||||
S_InitSoundAPI();
|
|
||||||
|
|
||||||
if( clgame.soundFuncs.pfnS_Init )
|
|
||||||
clgame.soundFuncs.pfnS_Init( &snd );
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// =======================================================================
|
/*
|
||||||
// Shutdown sound engine
|
============
|
||||||
// =======================================================================
|
S_Shutdown
|
||||||
|
|
||||||
|
============
|
||||||
|
*/
|
||||||
void S_Shutdown( void )
|
void S_Shutdown( void )
|
||||||
{
|
{
|
||||||
if( !snd.initialized ) return;
|
if( !snd.initialized ) return;
|
||||||
@@ -233,7 +233,7 @@ static int VOX_MixChannelToBuffer( portable_samplepair_t *pbuf, channel_t *chan,
|
|||||||
|
|
||||||
static int S_MixNormalChannels( portable_samplepair_t *dst, int end, int rate )
|
static int S_MixNormalChannels( portable_samplepair_t *dst, int end, int rate )
|
||||||
{
|
{
|
||||||
const qboolean local = Host_IsLocalGame();
|
const qboolean sp = Host_IsSinglePlayerGame();
|
||||||
const qboolean ingame = CL_IsInGame();
|
const qboolean ingame = CL_IsInGame();
|
||||||
const int num_samples = ( end - snd.paintedtime ) / ( SOUND_DMA_SPEED / rate );
|
const int num_samples = ( end - snd.paintedtime ) / ( SOUND_DMA_SPEED / rate );
|
||||||
|
|
||||||
@@ -261,7 +261,7 @@ static int S_MixNormalChannels( portable_samplepair_t *dst, int end, int rate )
|
|||||||
{
|
{
|
||||||
// play, playvol
|
// play, playvol
|
||||||
}
|
}
|
||||||
else if(( cls.key_dest == key_menu || cl.paused ) && !FBitSet( ch->flags, FL_CHAN_LOCAL_SOUND ) && local )
|
else if(( cls.key_dest == key_menu || cl.paused ) && !FBitSet( ch->flags, FL_CHAN_LOCAL_SOUND ) && sp )
|
||||||
{
|
{
|
||||||
// play only local sounds, keep pause for other
|
// play only local sounds, keep pause for other
|
||||||
continue;
|
continue;
|
||||||
@@ -403,11 +403,11 @@ static qboolean VOX_ParseWordParams( char *psz, voxword_t *pvoxword, voxword_t *
|
|||||||
|
|
||||||
command = *psz++;
|
command = *psz++;
|
||||||
|
|
||||||
if( !isdigit( *psz ))
|
if( !isdigit((byte)*psz ))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
memset( sznum, 0, sizeof( sznum ));
|
memset( sznum, 0, sizeof( sznum ));
|
||||||
for( i = 0; i < sizeof( sznum ) - 1 && isdigit( *psz ); i++, psz++ )
|
for( i = 0; i < sizeof( sznum ) - 1 && isdigit((byte)*psz ); i++, psz++ )
|
||||||
sznum[i] = *psz;
|
sznum[i] = *psz;
|
||||||
|
|
||||||
i = Q_atoi( sznum );
|
i = Q_atoi( sznum );
|
||||||
@@ -157,7 +157,7 @@ qboolean Sound_LoadOggVorbis( const char *name, const byte *buffer, fs_offset_t
|
|||||||
SetBits( sound.flags, SOUND_RESAMPLE );
|
SetBits( sound.flags, SOUND_RESAMPLE );
|
||||||
Sound_ScanVorbisComments( &vorbisFile );
|
Sound_ScanVorbisComments( &vorbisFile );
|
||||||
|
|
||||||
while(( ret = ov_read( &vorbisFile, (char *)sound.wav + written, sound.size - written, 0, sound.width, 1, §ion )) != 0 )
|
while(( ret = ov_read( &vorbisFile, (char *)sound.wav + written, sound.size - written, XASH_BIG_ENDIAN, sound.width, 1, §ion )) != 0 )
|
||||||
{
|
{
|
||||||
if( ret < 0 )
|
if( ret < 0 )
|
||||||
{
|
{
|
||||||
@@ -232,7 +232,7 @@ int Stream_ReadOggVorbis( stream_t *stream, int needBytes, void *buffer )
|
|||||||
|
|
||||||
if( !stream->buffsize )
|
if( !stream->buffsize )
|
||||||
{
|
{
|
||||||
stream->pos = ov_read( &ctx->vf, (char *)stream->temp, OUTBUF_SIZE, 0, stream->width, 1, §ion );
|
stream->pos = ov_read( &ctx->vf, (char *)stream->temp, OUTBUF_SIZE, XASH_BIG_ENDIAN, stream->width, 1, §ion );
|
||||||
if( stream->pos == 0 )
|
if( stream->pos == 0 )
|
||||||
{
|
{
|
||||||
break; // end of file
|
break; // end of file
|
||||||
|
|||||||
@@ -152,6 +152,8 @@ static qboolean StreamFindNextChunk( file_t *file, const char *name, int *last_c
|
|||||||
if( FS_Read( file, &iff_chunk_len, sizeof( iff_chunk_len )) != sizeof( iff_chunk_len ))
|
if( FS_Read( file, &iff_chunk_len, sizeof( iff_chunk_len )) != sizeof( iff_chunk_len ))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
iff_chunk_len = LittleLong( iff_chunk_len );
|
||||||
|
|
||||||
if( iff_chunk_len < 0 )
|
if( iff_chunk_len < 0 )
|
||||||
return false; // didn't find the chunk
|
return false; // didn't find the chunk
|
||||||
|
|
||||||
@@ -317,6 +319,15 @@ qboolean Sound_LoadWAV( const char *name, const byte *buffer, fs_offset_t filesi
|
|||||||
|
|
||||||
memcpy( sound.wav, buffer + (iff_dataPtr - buffer), sound.size );
|
memcpy( sound.wav, buffer + (iff_dataPtr - buffer), sound.size );
|
||||||
|
|
||||||
|
// swap 16-bit samples from little endian to native
|
||||||
|
if( sound.width == 2 )
|
||||||
|
{
|
||||||
|
short *p = (short *)sound.wav;
|
||||||
|
int count = sound.size / 2;
|
||||||
|
for( int i = 0; i < count; i++ )
|
||||||
|
p[i] = LittleShort( p[i] );
|
||||||
|
}
|
||||||
|
|
||||||
// now convert 8-bit sounds to signed
|
// now convert 8-bit sounds to signed
|
||||||
if( sound.width == 1 )
|
if( sound.width == 1 )
|
||||||
{
|
{
|
||||||
@@ -394,6 +405,7 @@ stream_t *Stream_OpenWAV( const char *filename )
|
|||||||
FS_Read( file, chunkName, 4 );
|
FS_Read( file, chunkName, 4 );
|
||||||
|
|
||||||
FS_Read( file, &t, sizeof( t ));
|
FS_Read( file, &t, sizeof( t ));
|
||||||
|
t = LittleShort( t );
|
||||||
if( t != 1 )
|
if( t != 1 )
|
||||||
{
|
{
|
||||||
Con_DPrintf( S_ERROR "%s: %s not a microsoft PCM format\n", __func__, filename );
|
Con_DPrintf( S_ERROR "%s: %s not a microsoft PCM format\n", __func__, filename );
|
||||||
@@ -402,14 +414,15 @@ stream_t *Stream_OpenWAV( const char *filename )
|
|||||||
}
|
}
|
||||||
|
|
||||||
FS_Read( file, &t, sizeof( t ));
|
FS_Read( file, &t, sizeof( t ));
|
||||||
sound.channels = t;
|
sound.channels = LittleShort( t );
|
||||||
|
|
||||||
FS_Read( file, &sound.rate, sizeof( int ));
|
FS_Read( file, &sound.rate, sizeof( int ));
|
||||||
|
sound.rate = LittleLong( sound.rate );
|
||||||
|
|
||||||
FS_Seek( file, 6, SEEK_CUR );
|
FS_Seek( file, 6, SEEK_CUR );
|
||||||
|
|
||||||
FS_Read( file, &t, sizeof( t ));
|
FS_Read( file, &t, sizeof( t ));
|
||||||
sound.width = t / 8;
|
sound.width = LittleShort( t ) / 8;
|
||||||
|
|
||||||
sound.loopstart = 0;
|
sound.loopstart = 0;
|
||||||
|
|
||||||
@@ -423,7 +436,7 @@ stream_t *Stream_OpenWAV( const char *filename )
|
|||||||
}
|
}
|
||||||
|
|
||||||
FS_Read( file, &sound.samples, sizeof( int ));
|
FS_Read( file, &sound.samples, sizeof( int ));
|
||||||
sound.samples = ( sound.samples / sound.width ) / sound.channels;
|
sound.samples = ( LittleLong( sound.samples ) / sound.width ) / sound.channels;
|
||||||
|
|
||||||
// at this point we have valid stream
|
// at this point we have valid stream
|
||||||
stream = Mem_Calloc( host.soundpool, sizeof( stream_t ));
|
stream = Mem_Calloc( host.soundpool, sizeof( stream_t ));
|
||||||
@@ -458,6 +471,14 @@ int Stream_ReadWAV( stream_t *stream, int bytes, void *buffer )
|
|||||||
stream->pos += bytes;
|
stream->pos += bytes;
|
||||||
FS_Read( stream->file, buffer, bytes );
|
FS_Read( stream->file, buffer, bytes );
|
||||||
|
|
||||||
|
if( stream->width == 2 )
|
||||||
|
{
|
||||||
|
short *p = (short *)buffer;
|
||||||
|
int count = bytes / 2;
|
||||||
|
for( int i = 0; i < count; i++ )
|
||||||
|
p[i] = LittleShort( p[i] );
|
||||||
|
}
|
||||||
|
|
||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -615,8 +615,11 @@ void Cmd_TokenizeString( const char *text )
|
|||||||
if( !*text )
|
if( !*text )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if( cmd_argc == 1 )
|
switch( cmd_argc )
|
||||||
cmd_args = text;
|
{
|
||||||
|
case 0: cmd_args = ""; break;
|
||||||
|
case 1: cmd_args = text; break;
|
||||||
|
}
|
||||||
|
|
||||||
text = COM_ParseFileSafe( (char*)text, cmd_token, sizeof( cmd_token ), PFILE_IGNOREBRACKET, NULL, NULL );
|
text = COM_ParseFileSafe( (char*)text, cmd_token, sizeof( cmd_token ), PFILE_IGNOREBRACKET, NULL, NULL );
|
||||||
|
|
||||||
|
|||||||
@@ -58,8 +58,8 @@ GNU General Public License for more details.
|
|||||||
// path to saved games
|
// path to saved games
|
||||||
#define DEFAULT_SAVE_DIRECTORY "save/"
|
#define DEFAULT_SAVE_DIRECTORY "save/"
|
||||||
|
|
||||||
// path to download games
|
// goldsrc compatible suffix for downloads directory
|
||||||
#define DEFAULT_DOWNLOADED_DIRECTORY "downloaded/"
|
#define DEFAULT_DOWNLOADED_DIRECTORY_SUFFIX "_downloads"
|
||||||
|
|
||||||
// path to user mod directory
|
// path to user mod directory
|
||||||
#define DEFAULT_CUSTOM_DIRECTORY "custom/"
|
#define DEFAULT_CUSTOM_DIRECTORY "custom/"
|
||||||
|
|||||||
@@ -227,7 +227,7 @@ qboolean LZSS_IsCompressed( const byte *source, size_t input_len )
|
|||||||
|
|
||||||
phdr = (const lzss_header_t *)source;
|
phdr = (const lzss_header_t *)source;
|
||||||
|
|
||||||
if( phdr && phdr->id == LZSS_ID )
|
if( phdr && phdr->id == LittleLong( LZSS_ID ))
|
||||||
return true;
|
return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -241,8 +241,8 @@ uint LZSS_GetActualSize( const byte *source, size_t input_len )
|
|||||||
|
|
||||||
phdr = (const lzss_header_t *)source;
|
phdr = (const lzss_header_t *)source;
|
||||||
|
|
||||||
if( phdr && phdr->id == LZSS_ID )
|
if( phdr && phdr->id == LittleLong( LZSS_ID ))
|
||||||
return phdr->size;
|
return LittleLong( phdr->size );
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -296,8 +296,8 @@ static byte *LZSS_CompressNoAlloc( lzss_state_t *state, byte *pInput, int input_
|
|||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
// set LZSS header
|
// set LZSS header
|
||||||
header->id = LZSS_ID;
|
header->id = LittleLong( LZSS_ID );
|
||||||
header->size = input_length;
|
header->size = LittleLong( input_length );
|
||||||
|
|
||||||
// create the compression work buffers, small enough (~64K) for stack
|
// create the compression work buffers, small enough (~64K) for stack
|
||||||
state->hash_table = (lzss_list_t *)alloca( 256 * sizeof( lzss_list_t ));
|
state->hash_table = (lzss_list_t *)alloca( 256 * sizeof( lzss_list_t ));
|
||||||
@@ -967,8 +967,8 @@ static void Test_LZSS( void )
|
|||||||
ASAN_POISON_MEMORY_REGION( poison3, sizeof( poison3 ));
|
ASAN_POISON_MEMORY_REGION( poison3, sizeof( poison3 ));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
hdr->size = sizeof( in ) - sizeof( *hdr );
|
hdr->size = LittleLong( sizeof( in ) - sizeof( *hdr ));
|
||||||
hdr->id = LZSS_ID;
|
hdr->id = LittleLong( LZSS_ID );
|
||||||
|
|
||||||
memset( in + sizeof( *hdr ), 0xff, sizeof( in ) - sizeof( *hdr ));
|
memset( in + sizeof( *hdr ), 0xff, sizeof( in ) - sizeof( *hdr ));
|
||||||
result = LZSS_Decompress( in, out, sizeof( in ), sizeof( out ));
|
result = LZSS_Decompress( in, out, sizeof( in ), sizeof( out ));
|
||||||
@@ -978,18 +978,18 @@ static void Test_LZSS( void )
|
|||||||
result = LZSS_Decompress( in, out, sizeof( in ), sizeof( out ));
|
result = LZSS_Decompress( in, out, sizeof( in ), sizeof( out ));
|
||||||
TASSERT_EQi( result, 0 );
|
TASSERT_EQi( result, 0 );
|
||||||
|
|
||||||
hdr->size = 1;
|
hdr->size = LittleLong( 1 );
|
||||||
hdr->id = LZSS_ID;
|
hdr->id = LittleLong( LZSS_ID );
|
||||||
result = LZSS_Decompress( in, out, sizeof( in ), sizeof( out ));
|
result = LZSS_Decompress( in, out, sizeof( in ), sizeof( out ));
|
||||||
TASSERT_EQi( result, 0 );
|
TASSERT_EQi( result, 0 );
|
||||||
|
|
||||||
hdr->size = 999;
|
hdr->size = LittleLong( 999 );
|
||||||
hdr->id = LZSS_ID;
|
hdr->id = LittleLong( LZSS_ID );
|
||||||
result = LZSS_Decompress( in, out, sizeof( in ), sizeof( out ));
|
result = LZSS_Decompress( in, out, sizeof( in ), sizeof( out ));
|
||||||
TASSERT_EQi( result, 0 );
|
TASSERT_EQi( result, 0 );
|
||||||
|
|
||||||
hdr->size = sizeof( in ) - sizeof( *hdr );
|
hdr->size = LittleLong( sizeof( in ) - sizeof( *hdr ));
|
||||||
hdr->id = 0xa1ba;
|
hdr->id = LittleLong( 0xa1ba );
|
||||||
result = LZSS_Decompress( in, out, sizeof( in ), sizeof( out ));
|
result = LZSS_Decompress( in, out, sizeof( in ), sizeof( out ));
|
||||||
TASSERT_EQi( result, 0 );
|
TASSERT_EQi( result, 0 );
|
||||||
|
|
||||||
|
|||||||
@@ -852,13 +852,15 @@ static inline connprotocol_t CL_Protocol( void )
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static inline qboolean Host_IsLocalGame( void )
|
// true in singleplayer only but not specific to local game
|
||||||
|
static inline qboolean Host_IsSinglePlayerGame( void )
|
||||||
{
|
{
|
||||||
if( SV_Active( ))
|
if( SV_Active( ))
|
||||||
return SV_GetMaxClients() == 1 ? true : false;
|
return SV_GetMaxClients() == 1 ? true : false;
|
||||||
return CL_GetMaxClients() == 1 ? true : false;
|
return CL_GetMaxClients() == 1 ? true : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// true when both server and client running on the same host
|
||||||
static inline qboolean Host_IsLocalClient( void )
|
static inline qboolean Host_IsLocalClient( void )
|
||||||
{
|
{
|
||||||
return CL_Initialized( ) && SV_Initialized( ) ? true : false;
|
return CL_Initialized( ) && SV_Initialized( ) ? true : false;
|
||||||
|
|||||||
@@ -513,7 +513,7 @@ static double Host_CalcFPS( void )
|
|||||||
{
|
{
|
||||||
fps = CL_GetDemoFramerate();
|
fps = CL_GetDemoFramerate();
|
||||||
}
|
}
|
||||||
else if( Host_IsLocalGame( ))
|
else if( Host_IsSinglePlayerGame( ))
|
||||||
{
|
{
|
||||||
if( !gl_vsync.value )
|
if( !gl_vsync.value )
|
||||||
fps = host_maxfps.value;
|
fps = host_maxfps.value;
|
||||||
@@ -642,9 +642,10 @@ static qboolean Host_FilterTime( double time )
|
|||||||
oldtime = host.realtime;
|
oldtime = host.realtime;
|
||||||
|
|
||||||
// NOTE: allow only in singleplayer while demos are not active
|
// NOTE: allow only in singleplayer while demos are not active
|
||||||
if( host_framerate.value > 0.0f && Host_IsLocalGame() && !CL_IsPlaybackDemo() && !CL_IsRecordDemo( ))
|
if( host_framerate.value > 0.0f && Host_IsSinglePlayerGame() && !CL_IsPlaybackDemo() && !CL_IsRecordDemo( ))
|
||||||
host.frametime = bound( MIN_FRAMETIME, host_framerate.value * scale, MAX_FRAMETIME );
|
host.frametime = bound( MIN_FRAMETIME, host_framerate.value * scale, MAX_FRAMETIME );
|
||||||
else host.frametime = bound( MIN_FRAMETIME, host.frametime, MAX_FRAMETIME );
|
else
|
||||||
|
host.frametime = bound( MIN_FRAMETIME, host.frametime, MAX_FRAMETIME );
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -833,7 +834,7 @@ static uint32_t Host_CheckBugcomp( void )
|
|||||||
if( !Sys_CheckParm( "-bugcomp" ))
|
if( !Sys_CheckParm( "-bugcomp" ))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if( Sys_GetParmFromCmdLine( "-bugcomp", args ) && isalpha( args[0] ))
|
if( Sys_GetParmFromCmdLine( "-bugcomp", args ) && isalpha((byte)args[0] ))
|
||||||
{
|
{
|
||||||
Q_splitstr( args, '+', &flags, Host_CheckBugcomp_splitstr_handler );
|
Q_splitstr( args, '+', &flags, Host_CheckBugcomp_splitstr_handler );
|
||||||
}
|
}
|
||||||
@@ -909,9 +910,7 @@ static qboolean Host_CollectX86Libraries( ECommonLibraryType lib_type,
|
|||||||
|
|
||||||
static void Host_CheckGameLibraries( void )
|
static void Host_CheckGameLibraries( void )
|
||||||
{
|
{
|
||||||
// on Android, game libraries are loaded from APKs and are invisible to FS_FileExists,
|
#if !defined( XASH_INTERNAL_GAMELIBS )
|
||||||
// so the check cannot work there; iOS is handled via Platform_LibraryExists -> IOS_LibraryExists
|
|
||||||
#if !defined( XASH_INTERNAL_GAMELIBS ) && !XASH_ANDROID
|
|
||||||
struct
|
struct
|
||||||
{
|
{
|
||||||
const char *name;
|
const char *name;
|
||||||
@@ -935,6 +934,11 @@ static void Host_CheckGameLibraries( void )
|
|||||||
if( !COM_StringEmpty( libs[i].override ))
|
if( !COM_StringEmpty( libs[i].override ))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
#if XASH_ANDROID
|
||||||
|
if( libs[i].type == LIBRARY_CLIENT )
|
||||||
|
continue;
|
||||||
|
#endif
|
||||||
|
|
||||||
if( libs[i].type == LIBRARY_SERVER )
|
if( libs[i].type == LIBRARY_SERVER )
|
||||||
{
|
{
|
||||||
// missing server library is only critical when singleplayer is available
|
// missing server library is only critical when singleplayer is available
|
||||||
@@ -1276,6 +1280,10 @@ int EXPORT Host_Main( int argc, char **argv, const char *progname, int bChangeGa
|
|||||||
Cbuf_ExecStuffCmds(); // execute stuffcmds (commandline)
|
Cbuf_ExecStuffCmds(); // execute stuffcmds (commandline)
|
||||||
SCR_CheckStartupVids(); // must be last
|
SCR_CheckStartupVids(); // must be last
|
||||||
|
|
||||||
|
#ifndef XASH_DEDICATED
|
||||||
|
IN_GyroCheckAvailability();
|
||||||
|
#endif
|
||||||
|
|
||||||
if( Sys_GetParmFromCmdLine( "-timedemo", demoname ))
|
if( Sys_GetParmFromCmdLine( "-timedemo", demoname ))
|
||||||
Cbuf_AddTextf( "timedemo %s\n", demoname );
|
Cbuf_AddTextf( "timedemo %s\n", demoname );
|
||||||
|
|
||||||
|
|||||||
@@ -101,6 +101,13 @@ static int HTTP_FileResolveNS( httpfile_t *file );
|
|||||||
static int HTTP_FileSendRequest( httpfile_t *file );
|
static int HTTP_FileSendRequest( httpfile_t *file );
|
||||||
static int HTTP_FileDecompress( httpfile_t *file );
|
static int HTTP_FileDecompress( httpfile_t *file );
|
||||||
|
|
||||||
|
static const char *HTTP_DownloadPath( char *buf, size_t buflen, const char *path, qboolean incomplete )
|
||||||
|
{
|
||||||
|
Q_snprintf( buf, buflen, "../%s" DEFAULT_DOWNLOADED_DIRECTORY_SUFFIX "/%s%s",
|
||||||
|
GI->gamefolder, path, incomplete ? ".incomplete" : "" );
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
==============
|
==============
|
||||||
HTTP_FreeFile
|
HTTP_FreeFile
|
||||||
@@ -110,7 +117,7 @@ Skip to next server/file
|
|||||||
*/
|
*/
|
||||||
static void HTTP_FreeFile( httpfile_t *file, qboolean error )
|
static void HTTP_FreeFile( httpfile_t *file, qboolean error )
|
||||||
{
|
{
|
||||||
char incname[MAX_SYSPATH + 64]; // plus downloaded/ plus .incomplete
|
char incname[MAX_SYSPATH + 64]; // plus ../{gamedir}_downloads/ plus .incomplete
|
||||||
qboolean was_open = false;
|
qboolean was_open = false;
|
||||||
|
|
||||||
file->blocktime = 0;
|
file->blocktime = 0;
|
||||||
@@ -132,7 +139,7 @@ static void HTTP_FreeFile( httpfile_t *file, qboolean error )
|
|||||||
|
|
||||||
file->socket = -1;
|
file->socket = -1;
|
||||||
|
|
||||||
Q_snprintf( incname, sizeof( incname ), DEFAULT_DOWNLOADED_DIRECTORY "%s.incomplete", file->path );
|
HTTP_DownloadPath( incname, sizeof( incname ), file->path, true );
|
||||||
|
|
||||||
if( error )
|
if( error )
|
||||||
{
|
{
|
||||||
@@ -149,7 +156,9 @@ static void HTTP_FreeFile( httpfile_t *file, qboolean error )
|
|||||||
if( http_autoremove.value == 1 ) // remove broken file
|
if( http_autoremove.value == 1 ) // remove broken file
|
||||||
{
|
{
|
||||||
Con_Printf( S_ERROR "no servers to download %s\n", file->path );
|
Con_Printf( S_ERROR "no servers to download %s\n", file->path );
|
||||||
|
FS_AllowDirectPaths( true );
|
||||||
FS_Delete( incname );
|
FS_Delete( incname );
|
||||||
|
FS_AllowDirectPaths( false );
|
||||||
}
|
}
|
||||||
else // autoremove disabled, keep file
|
else // autoremove disabled, keep file
|
||||||
{
|
{
|
||||||
@@ -161,14 +170,18 @@ static void HTTP_FreeFile( httpfile_t *file, qboolean error )
|
|||||||
{
|
{
|
||||||
if( file->compressed )
|
if( file->compressed )
|
||||||
{
|
{
|
||||||
|
FS_AllowDirectPaths( true );
|
||||||
FS_Delete( incname );
|
FS_Delete( incname );
|
||||||
|
FS_AllowDirectPaths( false );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Success, rename and process file
|
// Success, rename and process file
|
||||||
char name[MAX_SYSPATH];
|
char name[MAX_SYSPATH];
|
||||||
Q_snprintf( name, sizeof( name ), DEFAULT_DOWNLOADED_DIRECTORY "%s", file->path );
|
HTTP_DownloadPath( name, sizeof( name ), file->path, false );
|
||||||
|
FS_AllowDirectPaths( true );
|
||||||
FS_Rename( incname, name );
|
FS_Rename( incname, name );
|
||||||
|
FS_AllowDirectPaths( false );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,9 +208,13 @@ static int HTTP_FileQueue( httpfile_t *file )
|
|||||||
}
|
}
|
||||||
|
|
||||||
Con_Reportf( "HTTP: Starting download %s from %s:%d\n", file->path, file->server->host, file->server->port );
|
Con_Reportf( "HTTP: Starting download %s from %s:%d\n", file->path, file->server->host, file->server->port );
|
||||||
Q_snprintf( name, sizeof( name ), DEFAULT_DOWNLOADED_DIRECTORY "%s.incomplete", file->path );
|
HTTP_DownloadPath( name, sizeof( name ), file->path, true );
|
||||||
|
|
||||||
if( !( file->file = FS_Open( name, "wb+", true )))
|
FS_AllowDirectPaths( true );
|
||||||
|
file->file = FS_Open( name, "wb+", true );
|
||||||
|
FS_AllowDirectPaths( false );
|
||||||
|
|
||||||
|
if( !file->file )
|
||||||
{
|
{
|
||||||
Con_Printf( S_ERROR "HTTP: cannot open %s!\n", name );
|
Con_Printf( S_ERROR "HTTP: cannot open %s!\n", name );
|
||||||
HTTP_FreeFile( file, true );
|
HTTP_FreeFile( file, true );
|
||||||
@@ -249,9 +266,6 @@ static int HTTP_FileResolveNS( httpfile_t *file )
|
|||||||
|
|
||||||
static int HTTP_FileCreateSocket( httpfile_t *file )
|
static int HTTP_FileCreateSocket( httpfile_t *file )
|
||||||
{
|
{
|
||||||
uint mode = 1;
|
|
||||||
int res;
|
|
||||||
|
|
||||||
file->socket = socket( file->addr.ss_family, SOCK_STREAM, IPPROTO_TCP );
|
file->socket = socket( file->addr.ss_family, SOCK_STREAM, IPPROTO_TCP );
|
||||||
|
|
||||||
if( file->socket < 0 )
|
if( file->socket < 0 )
|
||||||
@@ -261,33 +275,13 @@ static int HTTP_FileCreateSocket( httpfile_t *file )
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if( ioctlsocket( file->socket, FIONBIO, (void *)&mode ) < 0 )
|
if( !NET_MakeSocketNonBlocking( file->socket ))
|
||||||
{
|
{
|
||||||
Con_Printf( S_ERROR "%s: ioctl() returned %s\n", __func__, NET_ErrorString());
|
Con_Printf( S_ERROR "%s: failed to make socket non-blocking, error %s\n", __func__, NET_ErrorString());
|
||||||
HTTP_FreeFile( file, true );
|
HTTP_FreeFile( file, true );
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if XASH_LINUX
|
|
||||||
|
|
||||||
res = fcntl( file->socket, F_GETFL, 0 );
|
|
||||||
|
|
||||||
if( res < 0 )
|
|
||||||
{
|
|
||||||
Con_Printf( S_ERROR "%s: fcntl( F_GETFL ) returned %s\n", __func__, NET_ErrorString());
|
|
||||||
HTTP_FreeFile( file, true );
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// SOCK_NONBLOCK is not portable, so use fcntl
|
|
||||||
if( fcntl( file->socket, F_SETFL, res | O_NONBLOCK ) < 0 )
|
|
||||||
{
|
|
||||||
Con_Printf( S_ERROR "%s: fcntl( F_SETFL ) returned %s\n", __func__, NET_ErrorString());
|
|
||||||
HTTP_FreeFile( file, true );
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
http.active_count++;
|
http.active_count++;
|
||||||
file->pfn_process = HTTP_FileConnect;
|
file->pfn_process = HTTP_FileConnect;
|
||||||
return 1;
|
return 1;
|
||||||
@@ -481,7 +475,7 @@ static int HTTP_FileDecompress( httpfile_t *file )
|
|||||||
data_in = Mem_Malloc( host.mempool, compressed_len + 1 );
|
data_in = Mem_Malloc( host.mempool, compressed_len + 1 );
|
||||||
data_out = Mem_Malloc( host.mempool, decompressed_len + 1 );
|
data_out = Mem_Malloc( host.mempool, decompressed_len + 1 );
|
||||||
|
|
||||||
Q_snprintf( name, sizeof( name ), DEFAULT_DOWNLOADED_DIRECTORY "%s", file->path );
|
HTTP_DownloadPath( name, sizeof( name ), file->path, false );
|
||||||
|
|
||||||
memset( &decompress_stream, 0, sizeof( decompress_stream ));
|
memset( &decompress_stream, 0, sizeof( decompress_stream ));
|
||||||
decompress_stream.total_in = decompress_stream.avail_in = compressed_len;
|
decompress_stream.total_in = decompress_stream.avail_in = compressed_len;
|
||||||
@@ -506,7 +500,9 @@ static int HTTP_FileDecompress( httpfile_t *file )
|
|||||||
|
|
||||||
if( zlib_result == Z_OK || zlib_result == Z_STREAM_END )
|
if( zlib_result == Z_OK || zlib_result == Z_STREAM_END )
|
||||||
{
|
{
|
||||||
|
FS_AllowDirectPaths( true );
|
||||||
g_fsapi.WriteFile( name, data_out, decompressed_len );
|
g_fsapi.WriteFile( name, data_out, decompressed_len );
|
||||||
|
FS_AllowDirectPaths( false );
|
||||||
HTTP_FreeFile( file, false );
|
HTTP_FreeFile( file, false );
|
||||||
}
|
}
|
||||||
else HTTP_FreeFile( file, true );
|
else HTTP_FreeFile( file, true );
|
||||||
|
|||||||
@@ -16,6 +16,24 @@ GNU General Public License for more details.
|
|||||||
#include "imagelib.h"
|
#include "imagelib.h"
|
||||||
#include "xash3d_mathlib.h"
|
#include "xash3d_mathlib.h"
|
||||||
#include "img_bmp.h"
|
#include "img_bmp.h"
|
||||||
|
#include "swaplib.h"
|
||||||
|
|
||||||
|
le_struct_begin( bmp_swap )
|
||||||
|
le_struct_field( bmp_t, fileSize )
|
||||||
|
le_struct_field( bmp_t, reserved0 )
|
||||||
|
le_struct_field( bmp_t, bitmapDataOffset )
|
||||||
|
le_struct_field( bmp_t, bitmapHeaderSize )
|
||||||
|
le_struct_field( bmp_t, width )
|
||||||
|
le_struct_field( bmp_t, height )
|
||||||
|
le_struct_field( bmp_t, planes )
|
||||||
|
le_struct_field( bmp_t, bitsPerPixel )
|
||||||
|
le_struct_field( bmp_t, compression )
|
||||||
|
le_struct_field( bmp_t, bitmapDataSize )
|
||||||
|
le_struct_field( bmp_t, hRes )
|
||||||
|
le_struct_field( bmp_t, vRes )
|
||||||
|
le_struct_field( bmp_t, colors )
|
||||||
|
le_struct_field( bmp_t, importantColors )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
=============
|
=============
|
||||||
@@ -41,6 +59,7 @@ qboolean Image_LoadBMP( const char *name, const byte *buffer, fs_offset_t filesi
|
|||||||
|
|
||||||
buf_p = (byte *)buffer;
|
buf_p = (byte *)buffer;
|
||||||
memcpy( &bhdr, buf_p, sizeof( bmp_t ));
|
memcpy( &bhdr, buf_p, sizeof( bmp_t ));
|
||||||
|
le_struct_swap( bmp_swap, &bhdr );
|
||||||
buf_p += BI_FILE_HEADER_SIZE + bhdr.bitmapHeaderSize;
|
buf_p += BI_FILE_HEADER_SIZE + bhdr.bitmapHeaderSize;
|
||||||
|
|
||||||
// bogus file header check
|
// bogus file header check
|
||||||
@@ -282,7 +301,8 @@ qboolean Image_LoadBMP( const char *name, const byte *buffer, fs_offset_t filesi
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 16:
|
case 16:
|
||||||
shortPixel = *(word *)buf_p, buf_p += 2;
|
shortPixel = buf_p[0] | (buf_p[1] << 8);
|
||||||
|
buf_p += 2;
|
||||||
*pixbuf++ = blue = (shortPixel & ( 31 << 10 )) >> 7;
|
*pixbuf++ = blue = (shortPixel & ( 31 << 10 )) >> 7;
|
||||||
*pixbuf++ = green = (shortPixel & ( 31 << 5 )) >> 2;
|
*pixbuf++ = green = (shortPixel & ( 31 << 5 )) >> 2;
|
||||||
*pixbuf++ = red = (shortPixel & ( 31 )) << 3;
|
*pixbuf++ = red = (shortPixel & ( 31 )) << 3;
|
||||||
@@ -396,7 +416,9 @@ qboolean Image_SaveBMP( const char *name, rgbdata_t *pix )
|
|||||||
hdr.colors = ( pixel_size == 1 ) ? 256 : 0;
|
hdr.colors = ( pixel_size == 1 ) ? 256 : 0;
|
||||||
hdr.importantColors = 0;
|
hdr.importantColors = 0;
|
||||||
|
|
||||||
|
le_struct_swap( bmp_swap, &hdr );
|
||||||
FS_Write( pfile, &hdr, sizeof( bmp_t ));
|
FS_Write( pfile, &hdr, sizeof( bmp_t ));
|
||||||
|
le_struct_swap( bmp_swap, &hdr );
|
||||||
|
|
||||||
pbBmpBits = Mem_Malloc( host.imagepool, cbBmpBits );
|
pbBmpBits = Mem_Malloc( host.imagepool, cbBmpBits );
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,49 @@ GNU General Public License for more details.
|
|||||||
#include "imagelib.h"
|
#include "imagelib.h"
|
||||||
#include "xash3d_mathlib.h"
|
#include "xash3d_mathlib.h"
|
||||||
#include "img_dds.h"
|
#include "img_dds.h"
|
||||||
|
#include "swaplib.h"
|
||||||
|
|
||||||
|
le_struct_begin( dds_pixf_swap )
|
||||||
|
le_struct_field( dds_pixf_t, dwSize )
|
||||||
|
le_struct_field( dds_pixf_t, dwFlags )
|
||||||
|
le_struct_field( dds_pixf_t, dwFourCC )
|
||||||
|
le_struct_field( dds_pixf_t, dwRGBBitCount )
|
||||||
|
le_struct_field( dds_pixf_t, dwRBitMask )
|
||||||
|
le_struct_field( dds_pixf_t, dwGBitMask )
|
||||||
|
le_struct_field( dds_pixf_t, dwBBitMask )
|
||||||
|
le_struct_field( dds_pixf_t, dwABitMask )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( dds_caps_swap )
|
||||||
|
le_struct_field( dds_caps_t, dwCaps1 )
|
||||||
|
le_struct_field( dds_caps_t, dwCaps2 )
|
||||||
|
le_struct_field( dds_caps_t, dwCaps3 )
|
||||||
|
le_struct_field( dds_caps_t, dwCaps4 )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( dds_swap )
|
||||||
|
le_struct_field( dds_t, dwIdent )
|
||||||
|
le_struct_field( dds_t, dwSize )
|
||||||
|
le_struct_field( dds_t, dwFlags )
|
||||||
|
le_struct_field( dds_t, dwHeight )
|
||||||
|
le_struct_field( dds_t, dwWidth )
|
||||||
|
le_struct_field( dds_t, dwLinearSize )
|
||||||
|
le_struct_field( dds_t, dwDepth )
|
||||||
|
le_struct_field( dds_t, dwMipMapCount )
|
||||||
|
le_struct_field( dds_t, dwAlphaBitDepth )
|
||||||
|
le_struct_array( dds_t, dwReserved1, 10 )
|
||||||
|
le_struct_child( dds_t, dsPixelFormat, dds_pixf_swap )
|
||||||
|
le_struct_child( dds_t, dsCaps, dds_caps_swap )
|
||||||
|
le_struct_field( dds_t, dwTextureStage )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( dds_dxt10_swap )
|
||||||
|
le_struct_field( dds_header_dxt10_t, dxgiFormat )
|
||||||
|
le_struct_field( dds_header_dxt10_t, resourceDimension )
|
||||||
|
le_struct_field( dds_header_dxt10_t, miscFlag )
|
||||||
|
le_struct_field( dds_header_dxt10_t, arraySize )
|
||||||
|
le_struct_field( dds_header_dxt10_t, miscFlags2 )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
static qboolean Image_CheckDXT3Alpha( dds_t *hdr, byte *fin )
|
static qboolean Image_CheckDXT3Alpha( dds_t *hdr, byte *fin )
|
||||||
{
|
{
|
||||||
@@ -295,6 +338,7 @@ qboolean Image_LoadDDS( const char *name, const byte *buffer, fs_offset_t filesi
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
memcpy( &header, buffer, sizeof( header ));
|
memcpy( &header, buffer, sizeof( header ));
|
||||||
|
le_struct_swap( dds_swap, &header );
|
||||||
|
|
||||||
if( header.dwIdent != DDSHEADER )
|
if( header.dwIdent != DDSHEADER )
|
||||||
return false; // it's not a dds file, just skip it
|
return false; // it's not a dds file, just skip it
|
||||||
@@ -315,6 +359,7 @@ qboolean Image_LoadDDS( const char *name, const byte *buffer, fs_offset_t filesi
|
|||||||
if( header.dsPixelFormat.dwFourCC == TYPE_DX10 )
|
if( header.dsPixelFormat.dwFourCC == TYPE_DX10 )
|
||||||
{
|
{
|
||||||
memcpy( &header2, buffer + sizeof( header ), sizeof( header2 ));
|
memcpy( &header2, buffer + sizeof( header ), sizeof( header2 ));
|
||||||
|
le_struct_swap( dds_dxt10_swap, &header2 );
|
||||||
headersOffset += sizeof( header2 );
|
headersOffset += sizeof( header2 );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,34 @@ GNU General Public License for more details.
|
|||||||
#include "imagelib.h"
|
#include "imagelib.h"
|
||||||
#include "xash3d_mathlib.h"
|
#include "xash3d_mathlib.h"
|
||||||
#include "img_ktx2.h"
|
#include "img_ktx2.h"
|
||||||
|
#include "swaplib.h"
|
||||||
|
|
||||||
|
le_struct_begin( ktx2_header_swap )
|
||||||
|
le_struct_field( ktx2_header_t, vkFormat )
|
||||||
|
le_struct_field( ktx2_header_t, typeSize )
|
||||||
|
le_struct_field( ktx2_header_t, pixelWidth )
|
||||||
|
le_struct_field( ktx2_header_t, pixelHeight )
|
||||||
|
le_struct_field( ktx2_header_t, pixelDepth )
|
||||||
|
le_struct_field( ktx2_header_t, layerCount )
|
||||||
|
le_struct_field( ktx2_header_t, faceCount )
|
||||||
|
le_struct_field( ktx2_header_t, levelCount )
|
||||||
|
le_struct_field( ktx2_header_t, supercompressionScheme )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( ktx2_index_swap )
|
||||||
|
le_struct_field( ktx2_index_t, dfdByteOffset )
|
||||||
|
le_struct_field( ktx2_index_t, dfdByteLength )
|
||||||
|
le_struct_field( ktx2_index_t, kvdByteOffset )
|
||||||
|
le_struct_field( ktx2_index_t, kvdByteLength )
|
||||||
|
le_struct_field( ktx2_index_t, sgdByteOffset )
|
||||||
|
le_struct_field( ktx2_index_t, sgdByteLength )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( ktx2_level_swap )
|
||||||
|
le_struct_field( ktx2_level_t, byteOffset )
|
||||||
|
le_struct_field( ktx2_level_t, byteLength )
|
||||||
|
le_struct_field( ktx2_level_t, uncompressedByteLength )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
static void Image_KTX2Format( uint32_t ktx2_format )
|
static void Image_KTX2Format( uint32_t ktx2_format )
|
||||||
{
|
{
|
||||||
@@ -125,6 +153,7 @@ static qboolean Image_KTX2Parse( const ktx2_header_t *header, const byte *buffer
|
|||||||
}
|
}
|
||||||
|
|
||||||
memcpy( &index, buffer + KTX2_IDENTIFIER_SIZE + sizeof( ktx2_header_t ), sizeof( index ));
|
memcpy( &index, buffer + KTX2_IDENTIFIER_SIZE + sizeof( ktx2_header_t ), sizeof( index ));
|
||||||
|
le_struct_swap( ktx2_index_swap, &index );
|
||||||
|
|
||||||
for( mip = 0; mip < header->levelCount; ++mip )
|
for( mip = 0; mip < header->levelCount; ++mip )
|
||||||
{
|
{
|
||||||
@@ -134,6 +163,7 @@ static qboolean Image_KTX2Parse( const ktx2_header_t *header, const byte *buffer
|
|||||||
|
|
||||||
ktx2_level_t level;
|
ktx2_level_t level;
|
||||||
memcpy( &level, levels_begin + mip * sizeof( level ), sizeof( level ));
|
memcpy( &level, levels_begin + mip * sizeof( level ), sizeof( level ));
|
||||||
|
le_struct_swap( ktx2_level_swap, &level );
|
||||||
|
|
||||||
if( mip_size != level.byteLength )
|
if( mip_size != level.byteLength )
|
||||||
{
|
{
|
||||||
@@ -159,6 +189,7 @@ static qboolean Image_KTX2Parse( const ktx2_header_t *header, const byte *buffer
|
|||||||
{
|
{
|
||||||
ktx2_level_t level;
|
ktx2_level_t level;
|
||||||
memcpy( &level, levels_begin + mip * sizeof( level ), sizeof( level ));
|
memcpy( &level, levels_begin + mip * sizeof( level ), sizeof( level ));
|
||||||
|
le_struct_swap( ktx2_level_swap, &level );
|
||||||
memcpy( image.rgba + cursor, buffer + level.byteOffset, level.byteLength );
|
memcpy( image.rgba + cursor, buffer + level.byteOffset, level.byteLength );
|
||||||
cursor += level.byteLength;
|
cursor += level.byteLength;
|
||||||
}
|
}
|
||||||
@@ -180,6 +211,7 @@ qboolean Image_LoadKTX2( const char *name, const byte *buffer, fs_offset_t files
|
|||||||
}
|
}
|
||||||
|
|
||||||
memcpy( &header, buffer + KTX2_IDENTIFIER_SIZE, sizeof( header ));
|
memcpy( &header, buffer + KTX2_IDENTIFIER_SIZE, sizeof( header ));
|
||||||
|
le_struct_swap( ktx2_header_swap, &header );
|
||||||
|
|
||||||
image.width = header.pixelWidth;
|
image.width = header.pixelWidth;
|
||||||
image.height = header.pixelHeight;
|
image.height = header.pixelHeight;
|
||||||
|
|||||||
@@ -16,6 +16,16 @@ GNU General Public License for more details.
|
|||||||
#include "imagelib.h"
|
#include "imagelib.h"
|
||||||
#include "xash3d_mathlib.h"
|
#include "xash3d_mathlib.h"
|
||||||
#include "img_tga.h"
|
#include "img_tga.h"
|
||||||
|
#include "swaplib.h"
|
||||||
|
|
||||||
|
le_struct_begin( tga_swap )
|
||||||
|
le_struct_field( tga_t, colormap_index )
|
||||||
|
le_struct_field( tga_t, colormap_length )
|
||||||
|
le_struct_field( tga_t, x_origin )
|
||||||
|
le_struct_field( tga_t, y_origin )
|
||||||
|
le_struct_field( tga_t, width )
|
||||||
|
le_struct_field( tga_t, height )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
=============
|
=============
|
||||||
@@ -44,10 +54,10 @@ qboolean Image_LoadTGA( const char *name, const byte *buffer, fs_offset_t filesi
|
|||||||
targa_header.colormap_index = buf_p[0] + buf_p[1] * 256; buf_p += 2;
|
targa_header.colormap_index = buf_p[0] + buf_p[1] * 256; buf_p += 2;
|
||||||
targa_header.colormap_length = buf_p[0] + buf_p[1] * 256; buf_p += 2;
|
targa_header.colormap_length = buf_p[0] + buf_p[1] * 256; buf_p += 2;
|
||||||
targa_header.colormap_size = *buf_p; buf_p += 1;
|
targa_header.colormap_size = *buf_p; buf_p += 1;
|
||||||
targa_header.x_origin = *(short *)buf_p; buf_p += 2;
|
targa_header.x_origin = buf_p[0] + buf_p[1] * 256; buf_p += 2;
|
||||||
targa_header.y_origin = *(short *)buf_p; buf_p += 2;
|
targa_header.y_origin = buf_p[0] + buf_p[1] * 256; buf_p += 2;
|
||||||
targa_header.width = image.width = *(short *)buf_p; buf_p += 2;
|
targa_header.width = image.width = buf_p[0] + buf_p[1] * 256; buf_p += 2;
|
||||||
targa_header.height = image.height = *(short *)buf_p; buf_p += 2;
|
targa_header.height = image.height = buf_p[0] + buf_p[1] * 256; buf_p += 2;
|
||||||
targa_header.pixel_size = *buf_p++;
|
targa_header.pixel_size = *buf_p++;
|
||||||
targa_header.attributes = *buf_p++;
|
targa_header.attributes = *buf_p++;
|
||||||
if( targa_header.id_length != 0 ) buf_p += targa_header.id_length; // skip TARGA image comment
|
if( targa_header.id_length != 0 ) buf_p += targa_header.id_length; // skip TARGA image comment
|
||||||
@@ -284,6 +294,7 @@ qboolean Image_SaveTGA( const char *name, rgbdata_t *pix )
|
|||||||
|
|
||||||
out = buffer;
|
out = buffer;
|
||||||
|
|
||||||
|
le_struct_swap( tga_swap, &targa_header );
|
||||||
memcpy( out, &targa_header, sizeof( tga_t ) );
|
memcpy( out, &targa_header, sizeof( tga_t ) );
|
||||||
out += sizeof( tga_t );
|
out += sizeof( tga_t );
|
||||||
|
|
||||||
|
|||||||
@@ -285,66 +285,34 @@ int Image_ComparePalette( const byte *pal )
|
|||||||
|
|
||||||
static void Image_SetPalette( const byte *pal, uint *d_table )
|
static void Image_SetPalette( const byte *pal, uint *d_table )
|
||||||
{
|
{
|
||||||
byte rgba[4];
|
|
||||||
uint uirgba; // TODO: palette looks byte-swapped on big-endian
|
|
||||||
int i;
|
|
||||||
|
|
||||||
// setup palette
|
|
||||||
switch( image.d_rendermode )
|
switch( image.d_rendermode )
|
||||||
{
|
{
|
||||||
case LUMP_NORMAL:
|
case LUMP_NORMAL:
|
||||||
for( i = 0; i < 256; i++ )
|
for( int i = 0; i < 256; i++ )
|
||||||
{
|
d_table[i] = HostFourCC( pal[i * 3 + 0], pal[i * 3 + 1], pal[i * 3 + 2], 0xFF );
|
||||||
memcpy( rgba, &pal[i * 3], 3 );
|
|
||||||
rgba[3] = 0xFF;
|
|
||||||
memcpy( &uirgba, rgba, sizeof( uirgba ));
|
|
||||||
d_table[i] = uirgba;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case LUMP_TEXGAMMA:
|
case LUMP_TEXGAMMA:
|
||||||
for( i = 0; i < 256; i++ )
|
for( int i = 0; i < 256; i++ )
|
||||||
{
|
{
|
||||||
rgba[0] = TextureToGamma( pal[i * 3 + 0] );
|
d_table[i] = HostFourCC(
|
||||||
rgba[1] = TextureToGamma( pal[i * 3 + 1] );
|
TextureToGamma( pal[i * 3 + 0] ),
|
||||||
rgba[2] = TextureToGamma( pal[i * 3 + 2] );
|
TextureToGamma( pal[i * 3 + 1] ),
|
||||||
rgba[3] = 0xFF;
|
TextureToGamma( pal[i * 3 + 2] ),
|
||||||
memcpy( &uirgba, rgba, sizeof( uirgba ));
|
0xFF );
|
||||||
d_table[i] = uirgba;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case LUMP_GRADIENT:
|
case LUMP_GRADIENT:
|
||||||
for( i = 0; i < 256; i++ )
|
for( int i = 0; i < 256; i++ )
|
||||||
{
|
d_table[i] = HostFourCC( pal[765], pal[766], pal[767], i );
|
||||||
rgba[0] = pal[765];
|
|
||||||
rgba[1] = pal[766];
|
|
||||||
rgba[2] = pal[767];
|
|
||||||
rgba[3] = i;
|
|
||||||
memcpy( &uirgba, rgba, sizeof( uirgba ));
|
|
||||||
d_table[i] = uirgba;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case LUMP_MASKED:
|
case LUMP_MASKED:
|
||||||
for( i = 0; i < 255; i++ )
|
for( int i = 0; i < 255; i++ )
|
||||||
{
|
d_table[i] = HostFourCC( pal[i * 3 + 0], pal[i * 3 + 1], pal[i * 3 + 2], 0xFF );
|
||||||
rgba[0] = pal[i*3+0];
|
|
||||||
rgba[1] = pal[i*3+1];
|
|
||||||
rgba[2] = pal[i*3+2];
|
|
||||||
rgba[3] = 0xFF;
|
|
||||||
memcpy( &uirgba, rgba, sizeof( uirgba ));
|
|
||||||
d_table[i] = uirgba;
|
|
||||||
}
|
|
||||||
d_table[255] = 0;
|
d_table[255] = 0;
|
||||||
break;
|
break;
|
||||||
case LUMP_EXTENDED:
|
case LUMP_EXTENDED:
|
||||||
for( i = 0; i < 256; i++ )
|
for( int i = 0; i < 256; i++ )
|
||||||
{
|
d_table[i] = HostFourCC( pal[i * 4 + 0], pal[i * 4 + 1], pal[i * 4 + 2], pal[i * 4 + 3] );
|
||||||
rgba[0] = pal[i*4+0];
|
|
||||||
rgba[1] = pal[i*4+1];
|
|
||||||
rgba[2] = pal[i*4+2];
|
|
||||||
rgba[3] = pal[i*4+3];
|
|
||||||
memcpy( &uirgba, rgba, sizeof( uirgba ));
|
|
||||||
d_table[i] = uirgba;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,43 @@ GNU General Public License for more details.
|
|||||||
#include "studio.h"
|
#include "studio.h"
|
||||||
#include "sprite.h"
|
#include "sprite.h"
|
||||||
#include "qfont.h"
|
#include "qfont.h"
|
||||||
|
#include "swaplib.h"
|
||||||
|
|
||||||
|
le_struct_begin( charinfo_swap )
|
||||||
|
le_struct_field( charinfo, startoffset )
|
||||||
|
le_struct_field( charinfo, charwidth )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( qfont_swap )
|
||||||
|
le_struct_field( qfont_t, width )
|
||||||
|
le_struct_field( qfont_t, height )
|
||||||
|
le_struct_field( qfont_t, rowcount )
|
||||||
|
le_struct_field( qfont_t, rowheight )
|
||||||
|
le_struct_array_child( qfont_t, fontinfo, charinfo_swap, NUM_GLYPHS )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( lmp_swap )
|
||||||
|
le_struct_field( lmp_t, width )
|
||||||
|
le_struct_field( lmp_t, height )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( mip_swap )
|
||||||
|
le_struct_field( mip_t, width )
|
||||||
|
le_struct_field( mip_t, height )
|
||||||
|
le_struct_array( mip_t, offsets, 4 )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( dwadinfo_swap )
|
||||||
|
le_struct_field( dwadinfo_t, ident )
|
||||||
|
le_struct_field( dwadinfo_t, numlumps )
|
||||||
|
le_struct_field( dwadinfo_t, infotableofs )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( dlumpinfo_swap )
|
||||||
|
le_struct_field( dlumpinfo_t, filepos )
|
||||||
|
le_struct_field( dlumpinfo_t, disksize )
|
||||||
|
le_struct_field( dlumpinfo_t, size )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
============
|
============
|
||||||
@@ -98,6 +135,7 @@ qboolean Image_LoadFNT( const char *name, const byte *buffer, fs_offset_t filesi
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
memcpy( &font, buffer, sizeof( font ));
|
memcpy( &font, buffer, sizeof( font ));
|
||||||
|
le_struct_swap( qfont_swap, &font );
|
||||||
|
|
||||||
// last sixty four bytes - what the hell ????
|
// last sixty four bytes - what the hell ????
|
||||||
size = sizeof( qfont_t ) - 4 + ( font.height * font.width * QCHAR_WIDTH ) + sizeof( short ) + 768 + 64;
|
size = sizeof( qfont_t ) - 4 + ( font.height * font.width * QCHAR_WIDTH ) + sizeof( short ) + 768 + 64;
|
||||||
@@ -120,7 +158,8 @@ qboolean Image_LoadFNT( const char *name, const byte *buffer, fs_offset_t filesi
|
|||||||
|
|
||||||
fin = buffer + sizeof( font ) - 4;
|
fin = buffer + sizeof( font ) - 4;
|
||||||
pal = fin + (image.width * image.height);
|
pal = fin + (image.width * image.height);
|
||||||
numcolors = *(short *)pal, pal += sizeof( short );
|
numcolors = pal[0] | (pal[1] << 8);
|
||||||
|
pal += sizeof( short );
|
||||||
|
|
||||||
if( numcolors == 768 || numcolors == 256 )
|
if( numcolors == 768 || numcolors == 256 )
|
||||||
{
|
{
|
||||||
@@ -305,6 +344,7 @@ qboolean Image_LoadLMP( const char *name, const byte *buffer, fs_offset_t filesi
|
|||||||
{
|
{
|
||||||
fin = (byte *)buffer;
|
fin = (byte *)buffer;
|
||||||
memcpy( &lmp, fin, sizeof( lmp ));
|
memcpy( &lmp, fin, sizeof( lmp ));
|
||||||
|
le_struct_swap( lmp_swap, &lmp );
|
||||||
image.width = lmp.width;
|
image.width = lmp.width;
|
||||||
image.height = lmp.height;
|
image.height = lmp.height;
|
||||||
rendermode = LUMP_NORMAL;
|
rendermode = LUMP_NORMAL;
|
||||||
@@ -337,7 +377,7 @@ qboolean Image_LoadLMP( const char *name, const byte *buffer, fs_offset_t filesi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pal = fin + pixels;
|
pal = fin + pixels;
|
||||||
numcolors = *(short *)pal;
|
numcolors = pal[0] | (pal[1] << 8);
|
||||||
if( numcolors != 256 ) pal = NULL; // corrupted lump ?
|
if( numcolors != 256 ) pal = NULL; // corrupted lump ?
|
||||||
else pal += sizeof( short );
|
else pal += sizeof( short );
|
||||||
}
|
}
|
||||||
@@ -397,6 +437,8 @@ qboolean Image_LoadMIP( const char *name, const byte *buffer, fs_offset_t filesi
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
memcpy( &mip, buffer, sizeof( mip ));
|
memcpy( &mip, buffer, sizeof( mip ));
|
||||||
|
if( !Image_CheckFlag( IL_HOST_ENDIAN ))
|
||||||
|
le_struct_swap( mip_swap, &mip );
|
||||||
image.width = mip.width;
|
image.width = mip.width;
|
||||||
image.height = mip.height;
|
image.height = mip.height;
|
||||||
|
|
||||||
@@ -411,7 +453,7 @@ qboolean Image_LoadMIP( const char *name, const byte *buffer, fs_offset_t filesi
|
|||||||
// half-life 1.0.0.1 mip version with palette
|
// half-life 1.0.0.1 mip version with palette
|
||||||
fin = (byte *)buffer + mip.offsets[0];
|
fin = (byte *)buffer + mip.offsets[0];
|
||||||
pal = (byte *)buffer + mip.offsets[0] + (((image.width * image.height) * 85)>>6);
|
pal = (byte *)buffer + mip.offsets[0] + (((image.width * image.height) * 85)>>6);
|
||||||
numcolors = *(short *)pal;
|
numcolors = pal[0] | (pal[1] << 8);
|
||||||
if( numcolors != 256 ) pal = NULL; // corrupted mip ?
|
if( numcolors != 256 ) pal = NULL; // corrupted mip ?
|
||||||
else pal += sizeof( short ); // skip colorsize
|
else pal += sizeof( short ); // skip colorsize
|
||||||
|
|
||||||
@@ -472,7 +514,7 @@ qboolean Image_LoadMIP( const char *name, const byte *buffer, fs_offset_t filesi
|
|||||||
// three checks here: check if we can load luma, check the texture name and, finally,
|
// three checks here: check if we can load luma, check the texture name and, finally,
|
||||||
// validate that palette isn't NULL
|
// validate that palette isn't NULL
|
||||||
if( Image_CheckFlag( IL_ALLOW_WAD3_LUMA )
|
if( Image_CheckFlag( IL_ALLOW_WAD3_LUMA )
|
||||||
&& ( mip.name[0] == '~' || ( mip.name[0] == '+' && isdigit( mip.name[1] ) && mip.name[2] == '~' ))
|
&& ( mip.name[0] == '~' || ( mip.name[0] == '+' && isdigit((byte)mip.name[1] ) && mip.name[2] == '~' ))
|
||||||
&& pal != NULL )
|
&& pal != NULL )
|
||||||
{
|
{
|
||||||
SetBits( image.flags, IMAGE_HAS_LUMA );
|
SetBits( image.flags, IMAGE_HAS_LUMA );
|
||||||
@@ -596,24 +638,23 @@ Image_LoadWAD
|
|||||||
*/
|
*/
|
||||||
qboolean Image_LoadWAD( const char *name, const byte *buffer, fs_offset_t filesize )
|
qboolean Image_LoadWAD( const char *name, const byte *buffer, fs_offset_t filesize )
|
||||||
{
|
{
|
||||||
const dwadinfo_t *header;
|
dwadinfo_t whdr;
|
||||||
const dlumpinfo_t *lumps;
|
|
||||||
const unsigned char *mipdata;
|
const unsigned char *mipdata;
|
||||||
int i, j;
|
int i, j;
|
||||||
|
|
||||||
if( !buffer || filesize < sizeof( dwadinfo_t ))
|
if( !buffer || filesize < sizeof( dwadinfo_t ))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
header = (const dwadinfo_t *)buffer;
|
memcpy( &whdr, buffer, sizeof( whdr ));
|
||||||
if( header->numlumps <= 0 || header->infotableofs <= 0 || header->infotableofs >= (int)filesize )
|
le_struct_swap( dwadinfo_swap, &whdr );
|
||||||
|
if( whdr.numlumps <= 0 || whdr.infotableofs <= 0 || whdr.infotableofs >= (int)filesize )
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
lumps = (const dlumpinfo_t *)((const unsigned char *)buffer + header->infotableofs );
|
for( i = 0; i < whdr.numlumps; ++i )
|
||||||
|
|
||||||
for( i = 0; i < header->numlumps; ++i )
|
|
||||||
{
|
{
|
||||||
const unsigned char *pixels, *palette, *use_palette;
|
const unsigned char *pixels, *palette, *use_palette;
|
||||||
unsigned char grad_palette[256 * 3];
|
unsigned char grad_palette[256 * 3];
|
||||||
|
dlumpinfo_t lump;
|
||||||
int mip_size;
|
int mip_size;
|
||||||
mip_t mip;
|
mip_t mip;
|
||||||
uint32_t width, height, offset0;
|
uint32_t width, height, offset0;
|
||||||
@@ -623,16 +664,20 @@ qboolean Image_LoadWAD( const char *name, const byte *buffer, fs_offset_t filesi
|
|||||||
float t;
|
float t;
|
||||||
byte idx;
|
byte idx;
|
||||||
|
|
||||||
if( lumps[i].type != TYP_MIPTEX && lumps[i].type != TYP_PALETTE )
|
memcpy( &lump, buffer + whdr.infotableofs + i * sizeof( dlumpinfo_t ), sizeof( lump ));
|
||||||
|
le_struct_swap( dlumpinfo_swap, &lump );
|
||||||
|
|
||||||
|
if( lump.type != TYP_MIPTEX && lump.type != TYP_PALETTE )
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// get lump data and validate
|
// get lump data and validate
|
||||||
mipdata = (const unsigned char *)buffer + lumps[i].filepos;
|
mipdata = (const unsigned char *)buffer + lump.filepos;
|
||||||
mip_size = lumps[i].disksize;
|
mip_size = lump.disksize;
|
||||||
if( lumps[i].filepos < 0 || lumps[i].filepos + mip_size > (int)filesize )
|
if( lump.filepos < 0 || lump.filepos + mip_size > (int)filesize )
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
memcpy( &mip, mipdata, sizeof( mip ));
|
memcpy( &mip, mipdata, sizeof( mip ));
|
||||||
|
le_struct_swap( mip_swap, &mip );
|
||||||
width = mip.width;
|
width = mip.width;
|
||||||
height = mip.height;
|
height = mip.height;
|
||||||
|
|
||||||
@@ -652,7 +697,7 @@ qboolean Image_LoadWAD( const char *name, const byte *buffer, fs_offset_t filesi
|
|||||||
use_palette = palette;
|
use_palette = palette;
|
||||||
|
|
||||||
// handle gradient palette
|
// handle gradient palette
|
||||||
if( lumps[i].type == TYP_PALETTE )
|
if( lump.type == TYP_PALETTE )
|
||||||
{
|
{
|
||||||
// gradient palette
|
// gradient palette
|
||||||
const unsigned char *frontColorPtr = palette + 255 * 3;
|
const unsigned char *frontColorPtr = palette + 255 * 3;
|
||||||
@@ -755,12 +800,16 @@ qboolean Image_SaveWAD( const char *name, rgbdata_t *pix )
|
|||||||
header.ident = IDWAD3HEADER;
|
header.ident = IDWAD3HEADER;
|
||||||
header.numlumps = 1;
|
header.numlumps = 1;
|
||||||
|
|
||||||
|
le_struct_swap( dwadinfo_swap, &header );
|
||||||
FS_Write( f, &header, sizeof( header ));
|
FS_Write( f, &header, sizeof( header ));
|
||||||
|
le_struct_swap( mip_swap, &miptex );
|
||||||
FS_Write( f, &miptex, sizeof( mip_t ));
|
FS_Write( f, &miptex, sizeof( mip_t ));
|
||||||
|
le_struct_swap( mip_swap, &miptex );
|
||||||
FS_Write( f, pix->buffer, m0size );
|
FS_Write( f, pix->buffer, m0size );
|
||||||
FS_Write( f, mip1_data, m1size );
|
FS_Write( f, mip1_data, m1size );
|
||||||
FS_Write( f, mip2_data, m2size );
|
FS_Write( f, mip2_data, m2size );
|
||||||
FS_Write( f, mip3_data, m3size );
|
FS_Write( f, mip3_data, m3size );
|
||||||
|
palette_size = LittleShort( palette_size );
|
||||||
FS_Write( f, &palette_size, sizeof( short ));
|
FS_Write( f, &palette_size, sizeof( short ));
|
||||||
|
|
||||||
if( lump_type == TYP_PALETTE )
|
if( lump_type == TYP_PALETTE )
|
||||||
@@ -793,10 +842,11 @@ qboolean Image_SaveWAD( const char *name, rgbdata_t *pix )
|
|||||||
lump.type = (char)lump_type;
|
lump.type = (char)lump_type;
|
||||||
lump.attribs = 0;
|
lump.attribs = 0;
|
||||||
Q_strncpy( lump.name, "tempdecal", sizeof( lump.name ));
|
Q_strncpy( lump.name, "tempdecal", sizeof( lump.name ));
|
||||||
|
le_struct_swap( dlumpinfo_swap, &lump );
|
||||||
FS_Write( f, &lump, sizeof( lump ));
|
FS_Write( f, &lump, sizeof( lump ));
|
||||||
|
|
||||||
FS_Seek( f, offsetof( dwadinfo_t, infotableofs ), SEEK_SET );
|
FS_Seek( f, offsetof( dwadinfo_t, infotableofs ), SEEK_SET );
|
||||||
infotableofs32 = (int)infotableofs;
|
infotableofs32 = LittleLong((int)infotableofs );
|
||||||
FS_Write( f, &infotableofs32, sizeof( int ));
|
FS_Write( f, &infotableofs32, sizeof( int ));
|
||||||
|
|
||||||
FS_Close( f );
|
FS_Close( f );
|
||||||
|
|||||||
@@ -327,7 +327,7 @@ static char *COM_GetItaniumName( const char * const in_name )
|
|||||||
{
|
{
|
||||||
// parse symbol length marker
|
// parse symbol length marker
|
||||||
len = 0;
|
len = 0;
|
||||||
for( ; isdigit( *f ) && remaining > 0; f++, remaining-- )
|
for( ; isdigit((byte)*f ) && remaining > 0; f++, remaining-- )
|
||||||
len = len * 10 + ( *f - '0' );
|
len = len * 10 + ( *f - '0' );
|
||||||
|
|
||||||
// sane value
|
// sane value
|
||||||
@@ -344,7 +344,7 @@ static char *COM_GetItaniumName( const char * const in_name )
|
|||||||
if( *f == 'E' )
|
if( *f == 'E' )
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if( !isdigit( *f ) || remaining <= 0 )
|
if( !isdigit((byte)*f ) || remaining <= 0 )
|
||||||
goto invalid_format;
|
goto invalid_format;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ GNU General Public License for more details.
|
|||||||
#include "enginefeatures.h"
|
#include "enginefeatures.h"
|
||||||
#include "client.h"
|
#include "client.h"
|
||||||
#include "server.h" // LUMP_ error codes
|
#include "server.h" // LUMP_ error codes
|
||||||
|
#include "swaplib.h"
|
||||||
#include "ref_common.h"
|
#include "ref_common.h"
|
||||||
#if defined( HAVE_OPENMP )
|
#if defined( HAVE_OPENMP )
|
||||||
#include <omp.h>
|
#include <omp.h>
|
||||||
@@ -165,35 +166,188 @@ typedef enum
|
|||||||
LOADLUMP_BSPX, // ... from BSPX data
|
LOADLUMP_BSPX, // ... from BSPX data
|
||||||
} loadlump_source_t;
|
} loadlump_source_t;
|
||||||
|
|
||||||
#define LUMP_SAVESTATS BIT( 0 )
|
#define LUMP_SAVESTATS BIT( 0 )
|
||||||
#define LUMP_TESTONLY BIT( 1 )
|
#define LUMP_BSHIFT_SWAP BIT( 1 )
|
||||||
#define LUMP_SILENT BIT( 2 )
|
#define LUMP_SILENT BIT( 2 )
|
||||||
#define LUMP_BSP30EXT BIT( 3 ) // extra marker for Mod_LoadLump
|
#define LUMP_BSP30EXT BIT( 3 ) // extra marker for Mod_LoadLump
|
||||||
#define LUMP_BSPX BIT( 4 )
|
#define LUMP_BSPX BIT( 4 )
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
int lumpnumber;
|
const int lumpnumber;
|
||||||
|
|
||||||
// BSPX
|
// BSPX
|
||||||
const char lumpname[24];
|
const char lumpname[24];
|
||||||
|
|
||||||
int flags;
|
const int flags;
|
||||||
const size_t mincount;
|
const size_t mincount;
|
||||||
const size_t maxcount;
|
const size_t maxcount;
|
||||||
const int entrysize;
|
const int entrysize;
|
||||||
const int entrysize32; // extended size (0 if not available)
|
const int entrysize32; // extended size (0 if not available)
|
||||||
const char *loadname;
|
const char *loadname;
|
||||||
const void **dataptr;
|
const size_t dataofs; // offsetof into dbspmodel_t for data pointer
|
||||||
size_t *count;
|
const size_t countofs; // offsetof into dbspmodel_t for count/size
|
||||||
|
#if XASH_BIG_ENDIAN // do not waste memory on little endian
|
||||||
|
const swap_struct_def_t *swap;
|
||||||
|
const size_t swaplen;
|
||||||
|
const swap_struct_def_t *swap32;
|
||||||
|
const size_t swaplen32;
|
||||||
|
#endif
|
||||||
} mlumpinfo_t;
|
} mlumpinfo_t;
|
||||||
|
|
||||||
|
// all these macros are ugly af
|
||||||
|
#if XASH_BIG_ENDIAN
|
||||||
|
#define LUMP_SWAP( x ) .swap = x, .swaplen = ARRAYSIZE( x )
|
||||||
|
#define LUMP_SWAP32( x, y ) .swap = x, .swaplen = ARRAYSIZE( x ), .swap32 = y, .swaplen32 = ARRAYSIZE( y )
|
||||||
|
#else
|
||||||
|
#define LUMP_SWAP( x )
|
||||||
|
#define LUMP_SWAP32( x, y )
|
||||||
|
#endif
|
||||||
|
|
||||||
|
le_struct_begin( dlump_swap )
|
||||||
|
le_struct_field( dlump_t, fileofs )
|
||||||
|
le_struct_field( dlump_t, filelen )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( dheader_swap )
|
||||||
|
le_struct_field( dheader_t, version )
|
||||||
|
le_struct_array_child( dheader_t, lumps, dlump_swap, HEADER_LUMPS )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( dextrahdr_swap )
|
||||||
|
le_struct_field( dextrahdr_t, id )
|
||||||
|
le_struct_field( dextrahdr_t, version )
|
||||||
|
le_struct_array_child( dextrahdr_t, lumps, dlump_swap, EXTRA_LUMPS )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( dbspx_hdr_swap )
|
||||||
|
le_struct_field( dbspx_hdr_t, id )
|
||||||
|
le_struct_field( dbspx_hdr_t, numlumps )
|
||||||
|
// flexible array member omitted
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( dbspx_lump_swap )
|
||||||
|
le_struct_field( dbspx_lump_t, fileofs )
|
||||||
|
le_struct_field( dbspx_lump_t, filelen )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( dplane_swap )
|
||||||
|
le_struct_array( dplane_t, normal, 3 )
|
||||||
|
le_struct_field( dplane_t, dist )
|
||||||
|
le_struct_field( dplane_t, type )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( dvertex_swap )
|
||||||
|
le_struct_array( dvertex_t, point, 3 )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( dnode_swap )
|
||||||
|
le_struct_field( dnode_t, planenum )
|
||||||
|
le_struct_array( dnode_t, children, 2 )
|
||||||
|
le_struct_array( dnode_t, mins, 3 )
|
||||||
|
le_struct_array( dnode_t, maxs, 3 )
|
||||||
|
le_struct_field( dnode_t, firstface )
|
||||||
|
le_struct_field( dnode_t, numfaces )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( dnode32_swap )
|
||||||
|
le_struct_field( dnode32_t, planenum )
|
||||||
|
le_struct_array( dnode32_t, children, 2 )
|
||||||
|
le_struct_array( dnode32_t, mins, 3 )
|
||||||
|
le_struct_array( dnode32_t, maxs, 3 )
|
||||||
|
le_struct_field( dnode32_t, firstface )
|
||||||
|
le_struct_field( dnode32_t, numfaces )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( dtexinfo_swap )
|
||||||
|
le_struct_array( dtexinfo_t, vecs[0], 4 )
|
||||||
|
le_struct_array( dtexinfo_t, vecs[1], 4 )
|
||||||
|
le_struct_field( dtexinfo_t, miptex )
|
||||||
|
le_struct_field( dtexinfo_t, flags )
|
||||||
|
le_struct_field( dtexinfo_t, faceinfo )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( dface_swap )
|
||||||
|
le_struct_field( dface_t, planenum )
|
||||||
|
le_struct_field( dface_t, side )
|
||||||
|
le_struct_field( dface_t, firstedge )
|
||||||
|
le_struct_field( dface_t, numedges )
|
||||||
|
le_struct_field( dface_t, texinfo )
|
||||||
|
le_struct_field( dface_t, lightofs )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( dface32_swap )
|
||||||
|
le_struct_field( dface32_t, planenum )
|
||||||
|
le_struct_field( dface32_t, side )
|
||||||
|
le_struct_field( dface32_t, firstedge )
|
||||||
|
le_struct_field( dface32_t, numedges )
|
||||||
|
le_struct_field( dface32_t, texinfo )
|
||||||
|
le_struct_field( dface32_t, lightofs )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( dclipnode_swap )
|
||||||
|
le_struct_field( dclipnode_t, planenum )
|
||||||
|
le_struct_array( dclipnode_t, children, 2 )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( dclipnode32_swap )
|
||||||
|
le_struct_field( dclipnode32_t, planenum )
|
||||||
|
le_struct_array( dclipnode32_t, children, 2 )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( dleaf_swap )
|
||||||
|
le_struct_field( dleaf_t, contents )
|
||||||
|
le_struct_field( dleaf_t, visofs )
|
||||||
|
le_struct_array( dleaf_t, mins, 3 )
|
||||||
|
le_struct_array( dleaf_t, maxs, 3 )
|
||||||
|
le_struct_field( dleaf_t, firstmarksurface )
|
||||||
|
le_struct_field( dleaf_t, nummarksurfaces )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( dleaf32_swap )
|
||||||
|
le_struct_field( dleaf32_t, contents )
|
||||||
|
le_struct_field( dleaf32_t, visofs )
|
||||||
|
le_struct_array( dleaf32_t, mins, 3 )
|
||||||
|
le_struct_array( dleaf32_t, maxs, 3 )
|
||||||
|
le_struct_field( dleaf32_t, firstmarksurface )
|
||||||
|
le_struct_field( dleaf32_t, nummarksurfaces )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( dedge_swap )
|
||||||
|
le_struct_array( dedge_t, v, 2 )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( dedge32_swap )
|
||||||
|
le_struct_array( dedge32_t, v, 2 )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( dmodel_swap )
|
||||||
|
le_struct_array( dmodel_t, mins, 3 )
|
||||||
|
le_struct_array( dmodel_t, maxs, 3 )
|
||||||
|
le_struct_array( dmodel_t, origin, 3 )
|
||||||
|
le_struct_array( dmodel_t, headnode, MAX_MAP_HULLS )
|
||||||
|
le_struct_field( dmodel_t, visleafs )
|
||||||
|
le_struct_field( dmodel_t, firstface )
|
||||||
|
le_struct_field( dmodel_t, numfaces )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( dfaceinfo_swap )
|
||||||
|
le_struct_field( dfaceinfo_t, texture_step )
|
||||||
|
le_struct_field( dfaceinfo_t, max_extent )
|
||||||
|
le_struct_field( dfaceinfo_t, groupid )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( mip_swap )
|
||||||
|
le_struct_field( mip_t, width )
|
||||||
|
le_struct_field( mip_t, height )
|
||||||
|
le_struct_array( mip_t, offsets, 4 )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
world_static_t world;
|
world_static_t world;
|
||||||
static dbspmodel_t srcmodel;
|
|
||||||
static loadstat_t loadstat;
|
static loadstat_t loadstat;
|
||||||
static model_t *worldmodel;
|
static model_t *worldmodel;
|
||||||
static byte g_visdata[(MAX_MAP_LEAFS+7)/8]; // intermediate buffer
|
static byte g_visdata[(MAX_MAP_LEAFS+7)/8]; // intermediate buffer
|
||||||
static mlumpinfo_t srclumps[HEADER_LUMPS] =
|
static const mlumpinfo_t srclumps[HEADER_LUMPS] =
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
.lumpnumber = LUMP_ENTITIES,
|
.lumpnumber = LUMP_ENTITIES,
|
||||||
@@ -201,8 +355,8 @@ static mlumpinfo_t srclumps[HEADER_LUMPS] =
|
|||||||
.maxcount = MAX_MAP_ENTSTRING,
|
.maxcount = MAX_MAP_ENTSTRING,
|
||||||
.entrysize = sizeof( byte ),
|
.entrysize = sizeof( byte ),
|
||||||
.loadname = "entities",
|
.loadname = "entities",
|
||||||
.dataptr = (const void **)&srcmodel.entdata,
|
.dataofs = offsetof( dbspmodel_t, entdata ),
|
||||||
.count = &srcmodel.entdatasize,
|
.countofs = offsetof( dbspmodel_t, entdatasize ),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.lumpnumber = LUMP_PLANES,
|
.lumpnumber = LUMP_PLANES,
|
||||||
@@ -210,8 +364,9 @@ static mlumpinfo_t srclumps[HEADER_LUMPS] =
|
|||||||
.maxcount = MAX_MAP_PLANES,
|
.maxcount = MAX_MAP_PLANES,
|
||||||
.entrysize = sizeof( dplane_t ),
|
.entrysize = sizeof( dplane_t ),
|
||||||
.loadname = "planes",
|
.loadname = "planes",
|
||||||
.dataptr = (const void **)&srcmodel.planes,
|
.dataofs = offsetof( dbspmodel_t, planes ),
|
||||||
.count = &srcmodel.numplanes,
|
.countofs = offsetof( dbspmodel_t, numplanes ),
|
||||||
|
LUMP_SWAP( dplane_swap )
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.lumpnumber = LUMP_TEXTURES,
|
.lumpnumber = LUMP_TEXTURES,
|
||||||
@@ -219,24 +374,25 @@ static mlumpinfo_t srclumps[HEADER_LUMPS] =
|
|||||||
.maxcount = MAX_MAP_MIPTEX,
|
.maxcount = MAX_MAP_MIPTEX,
|
||||||
.entrysize = sizeof( byte ),
|
.entrysize = sizeof( byte ),
|
||||||
.loadname = "textures",
|
.loadname = "textures",
|
||||||
.dataptr = (const void **)&srcmodel.textures,
|
.dataofs = offsetof( dbspmodel_t, textures ),
|
||||||
.count = &srcmodel.texdatasize,
|
.countofs = offsetof( dbspmodel_t, texdatasize ),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.lumpnumber = LUMP_VERTEXES,
|
.lumpnumber = LUMP_VERTEXES,
|
||||||
.maxcount = MAX_MAP_VERTS,
|
.maxcount = MAX_MAP_VERTS,
|
||||||
.entrysize = sizeof( dvertex_t ),
|
.entrysize = sizeof( dvertex_t ),
|
||||||
.loadname = "vertexes",
|
.loadname = "vertexes",
|
||||||
.dataptr = (const void **)&srcmodel.vertexes,
|
.dataofs = offsetof( dbspmodel_t, vertexes ),
|
||||||
.count = &srcmodel.numvertexes,
|
.countofs = offsetof( dbspmodel_t, numvertexes ),
|
||||||
|
LUMP_SWAP( dvertex_swap )
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.lumpnumber = LUMP_VISIBILITY,
|
.lumpnumber = LUMP_VISIBILITY,
|
||||||
.maxcount = MAX_MAP_VISIBILITY,
|
.maxcount = MAX_MAP_VISIBILITY,
|
||||||
.entrysize = sizeof( byte ),
|
.entrysize = sizeof( byte ),
|
||||||
.loadname = "visibility",
|
.loadname = "visibility",
|
||||||
.dataptr = (const void **)&srcmodel.visdata,
|
.dataofs = offsetof( dbspmodel_t, visdata ),
|
||||||
.count = &srcmodel.visdatasize,
|
.countofs = offsetof( dbspmodel_t, visdatasize ),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.lumpnumber = LUMP_NODES,
|
.lumpnumber = LUMP_NODES,
|
||||||
@@ -246,8 +402,9 @@ static mlumpinfo_t srclumps[HEADER_LUMPS] =
|
|||||||
.entrysize32 = sizeof( dnode32_t ),
|
.entrysize32 = sizeof( dnode32_t ),
|
||||||
.loadname = "nodes",
|
.loadname = "nodes",
|
||||||
.flags = CHECK_OVERFLOW,
|
.flags = CHECK_OVERFLOW,
|
||||||
.dataptr = (const void **)&srcmodel.nodes,
|
.dataofs = offsetof( dbspmodel_t, nodes ),
|
||||||
.count = &srcmodel.numnodes,
|
.countofs = offsetof( dbspmodel_t, numnodes ),
|
||||||
|
LUMP_SWAP32( dnode_swap, dnode32_swap )
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.lumpnumber = LUMP_TEXINFO,
|
.lumpnumber = LUMP_TEXINFO,
|
||||||
@@ -256,8 +413,9 @@ static mlumpinfo_t srclumps[HEADER_LUMPS] =
|
|||||||
.entrysize = sizeof( dtexinfo_t ),
|
.entrysize = sizeof( dtexinfo_t ),
|
||||||
.loadname = "texinfo",
|
.loadname = "texinfo",
|
||||||
.flags = CHECK_OVERFLOW,
|
.flags = CHECK_OVERFLOW,
|
||||||
.dataptr = (const void **)&srcmodel.texinfo,
|
.dataofs = offsetof( dbspmodel_t, texinfo ),
|
||||||
.count = &srcmodel.numtexinfo,
|
.countofs = offsetof( dbspmodel_t, numtexinfo ),
|
||||||
|
LUMP_SWAP( dtexinfo_swap )
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.lumpnumber = LUMP_FACES,
|
.lumpnumber = LUMP_FACES,
|
||||||
@@ -267,8 +425,9 @@ static mlumpinfo_t srclumps[HEADER_LUMPS] =
|
|||||||
.entrysize32 = sizeof( dface32_t ),
|
.entrysize32 = sizeof( dface32_t ),
|
||||||
.loadname = "faces",
|
.loadname = "faces",
|
||||||
.flags = CHECK_OVERFLOW,
|
.flags = CHECK_OVERFLOW,
|
||||||
.dataptr = (const void **)&srcmodel.surfaces,
|
.dataofs = offsetof( dbspmodel_t, surfaces ),
|
||||||
.count = &srcmodel.numsurfaces,
|
.countofs = offsetof( dbspmodel_t, numsurfaces ),
|
||||||
|
LUMP_SWAP32( dface_swap, dface32_swap )
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.lumpnumber = LUMP_LIGHTING,
|
.lumpnumber = LUMP_LIGHTING,
|
||||||
@@ -277,8 +436,8 @@ static mlumpinfo_t srclumps[HEADER_LUMPS] =
|
|||||||
.entrysize = sizeof( byte ),
|
.entrysize = sizeof( byte ),
|
||||||
.loadname = "lightmaps",
|
.loadname = "lightmaps",
|
||||||
.flags = 0,
|
.flags = 0,
|
||||||
.dataptr = (const void **)&srcmodel.lightdata,
|
.dataofs = offsetof( dbspmodel_t, lightdata ),
|
||||||
.count = &srcmodel.lightdatasize,
|
.countofs = offsetof( dbspmodel_t, lightdatasize ),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.lumpnumber = LUMP_CLIPNODES,
|
.lumpnumber = LUMP_CLIPNODES,
|
||||||
@@ -288,8 +447,9 @@ static mlumpinfo_t srclumps[HEADER_LUMPS] =
|
|||||||
.entrysize32 = sizeof( dclipnode32_t ),
|
.entrysize32 = sizeof( dclipnode32_t ),
|
||||||
.loadname = "clipnodes",
|
.loadname = "clipnodes",
|
||||||
.flags = 0,
|
.flags = 0,
|
||||||
.dataptr = (const void **)&srcmodel.clipnodes,
|
.dataofs = offsetof( dbspmodel_t, clipnodes ),
|
||||||
.count = &srcmodel.numclipnodes,
|
.countofs = offsetof( dbspmodel_t, numclipnodes ),
|
||||||
|
LUMP_SWAP32( dclipnode_swap, dclipnode32_swap )
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.lumpnumber = LUMP_LEAFS,
|
.lumpnumber = LUMP_LEAFS,
|
||||||
@@ -299,8 +459,9 @@ static mlumpinfo_t srclumps[HEADER_LUMPS] =
|
|||||||
.entrysize32 = sizeof( dleaf32_t ),
|
.entrysize32 = sizeof( dleaf32_t ),
|
||||||
.loadname = "leafs",
|
.loadname = "leafs",
|
||||||
.flags = CHECK_OVERFLOW,
|
.flags = CHECK_OVERFLOW,
|
||||||
.dataptr = (const void **)&srcmodel.leafs,
|
.dataofs = offsetof( dbspmodel_t, leafs ),
|
||||||
.count = &srcmodel.numleafs,
|
.countofs = offsetof( dbspmodel_t, numleafs ),
|
||||||
|
LUMP_SWAP32( dleaf_swap, dleaf32_swap )
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.lumpnumber = LUMP_MARKSURFACES,
|
.lumpnumber = LUMP_MARKSURFACES,
|
||||||
@@ -310,8 +471,8 @@ static mlumpinfo_t srclumps[HEADER_LUMPS] =
|
|||||||
.entrysize32 = sizeof( dmarkface32_t ),
|
.entrysize32 = sizeof( dmarkface32_t ),
|
||||||
.loadname = "markfaces",
|
.loadname = "markfaces",
|
||||||
.flags = 0,
|
.flags = 0,
|
||||||
.dataptr = (const void **)&srcmodel.markfaces,
|
.dataofs = offsetof( dbspmodel_t, markfaces ),
|
||||||
.count = &srcmodel.nummarkfaces,
|
.countofs = offsetof( dbspmodel_t, nummarkfaces ),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.lumpnumber = LUMP_EDGES,
|
.lumpnumber = LUMP_EDGES,
|
||||||
@@ -321,8 +482,9 @@ static mlumpinfo_t srclumps[HEADER_LUMPS] =
|
|||||||
.entrysize32 = sizeof( dedge32_t ),
|
.entrysize32 = sizeof( dedge32_t ),
|
||||||
.loadname = "edges",
|
.loadname = "edges",
|
||||||
.flags = 0,
|
.flags = 0,
|
||||||
.dataptr = (const void **)&srcmodel.edges,
|
.dataofs = offsetof( dbspmodel_t, edges ),
|
||||||
.count = &srcmodel.numedges,
|
.countofs = offsetof( dbspmodel_t, numedges ),
|
||||||
|
LUMP_SWAP32( dedge_swap, dedge32_swap )
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.lumpnumber = LUMP_SURFEDGES,
|
.lumpnumber = LUMP_SURFEDGES,
|
||||||
@@ -331,8 +493,8 @@ static mlumpinfo_t srclumps[HEADER_LUMPS] =
|
|||||||
.entrysize = sizeof( dsurfedge_t ),
|
.entrysize = sizeof( dsurfedge_t ),
|
||||||
.loadname = "surfedges",
|
.loadname = "surfedges",
|
||||||
.flags = 0,
|
.flags = 0,
|
||||||
.dataptr = (const void **)&srcmodel.surfedges,
|
.dataofs = offsetof( dbspmodel_t, surfedges ),
|
||||||
.count = &srcmodel.numsurfedges,
|
.countofs = offsetof( dbspmodel_t, numsurfedges ),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.lumpnumber = LUMP_MODELS,
|
.lumpnumber = LUMP_MODELS,
|
||||||
@@ -341,8 +503,9 @@ static mlumpinfo_t srclumps[HEADER_LUMPS] =
|
|||||||
.entrysize = sizeof( dmodel_t ),
|
.entrysize = sizeof( dmodel_t ),
|
||||||
.loadname = "models",
|
.loadname = "models",
|
||||||
.flags = CHECK_OVERFLOW,
|
.flags = CHECK_OVERFLOW,
|
||||||
.dataptr = (const void **)&srcmodel.submodels,
|
.dataofs = offsetof( dbspmodel_t, submodels ),
|
||||||
.count = &srcmodel.numsubmodels,
|
.countofs = offsetof( dbspmodel_t, numsubmodels ),
|
||||||
|
LUMP_SWAP( dmodel_swap )
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -354,8 +517,8 @@ static const mlumpinfo_t extlumps[EXTRA_LUMPS] =
|
|||||||
.maxcount = MAX_MAP_LIGHTING,
|
.maxcount = MAX_MAP_LIGHTING,
|
||||||
.entrysize = sizeof( byte ),
|
.entrysize = sizeof( byte ),
|
||||||
.loadname = "deluxmaps",
|
.loadname = "deluxmaps",
|
||||||
.dataptr = (const void **)&srcmodel.deluxdata,
|
.dataofs = offsetof( dbspmodel_t, deluxdata ),
|
||||||
.count = &srcmodel.deluxdatasize,
|
.countofs = offsetof( dbspmodel_t, deluxdatasize ),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.lumpnumber = LUMP_FACEINFO,
|
.lumpnumber = LUMP_FACEINFO,
|
||||||
@@ -364,8 +527,9 @@ static const mlumpinfo_t extlumps[EXTRA_LUMPS] =
|
|||||||
.entrysize = sizeof( dfaceinfo_t ),
|
.entrysize = sizeof( dfaceinfo_t ),
|
||||||
.loadname = "faceinfos",
|
.loadname = "faceinfos",
|
||||||
.flags = CHECK_OVERFLOW,
|
.flags = CHECK_OVERFLOW,
|
||||||
.dataptr = (const void **)&srcmodel.faceinfo,
|
.dataofs = offsetof( dbspmodel_t, faceinfo ),
|
||||||
.count = &srcmodel.numfaceinfo,
|
.countofs = offsetof( dbspmodel_t, numfaceinfo ),
|
||||||
|
LUMP_SWAP( dfaceinfo_swap )
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.lumpnumber = LUMP_SHADOWMAP,
|
.lumpnumber = LUMP_SHADOWMAP,
|
||||||
@@ -373,8 +537,8 @@ static const mlumpinfo_t extlumps[EXTRA_LUMPS] =
|
|||||||
.maxcount = MAX_MAP_LIGHTING / 3,
|
.maxcount = MAX_MAP_LIGHTING / 3,
|
||||||
.entrysize = sizeof( byte ),
|
.entrysize = sizeof( byte ),
|
||||||
.loadname = "shadowmap",
|
.loadname = "shadowmap",
|
||||||
.dataptr = (const void **)&srcmodel.shadowdata,
|
.dataofs = offsetof( dbspmodel_t, shadowdata ),
|
||||||
.count = &srcmodel.shadowdatasize,
|
.countofs = offsetof( dbspmodel_t, shadowdatasize ),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -385,16 +549,16 @@ static const mlumpinfo_t bspxlumps[] =
|
|||||||
.maxcount = MAX_MAP_LIGHTING,
|
.maxcount = MAX_MAP_LIGHTING,
|
||||||
.entrysize = sizeof( byte ),
|
.entrysize = sizeof( byte ),
|
||||||
.loadname = "rgblighting",
|
.loadname = "rgblighting",
|
||||||
.dataptr = (const void **)&srcmodel.rgblightdata,
|
.dataofs = offsetof( dbspmodel_t, rgblightdata ),
|
||||||
.count = &srcmodel.rgblightdatasize,
|
.countofs = offsetof( dbspmodel_t, rgblightdatasize ),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.lumpname = "LIGHTINGDIR",
|
.lumpname = "LIGHTINGDIR",
|
||||||
.maxcount = MAX_MAP_LIGHTING,
|
.maxcount = MAX_MAP_LIGHTING,
|
||||||
.entrysize = sizeof( byte ),
|
.entrysize = sizeof( byte ),
|
||||||
.loadname = "lightingdir",
|
.loadname = "lightingdir",
|
||||||
.dataptr = (const void **)&srcmodel.deluxdata,
|
.dataofs = offsetof( dbspmodel_t, deluxdata ),
|
||||||
.count = &srcmodel.deluxdatasize,
|
.countofs = offsetof( dbspmodel_t, deluxdatasize ),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -593,7 +757,7 @@ Mod_LoadLump
|
|||||||
generic loader
|
generic loader
|
||||||
=================
|
=================
|
||||||
*/
|
*/
|
||||||
static void Mod_LoadLump( const void *in, const mlumpinfo_t *info, mlumpstat_t *stat, int flags, loadlump_source_t source, const void *bspx_data )
|
static void Mod_LoadLump( const void *in, const mlumpinfo_t *info, mlumpstat_t *stat, int flags, loadlump_source_t source, const void *bspx_data, dbspmodel_t *bmod )
|
||||||
{
|
{
|
||||||
int version = ((const dheader_t *)in)->version, i;
|
int version = ((const dheader_t *)in)->version, i;
|
||||||
size_t numelems, real_entrysize;
|
size_t numelems, real_entrysize;
|
||||||
@@ -604,7 +768,16 @@ static void Mod_LoadLump( const void *in, const mlumpinfo_t *info, mlumpstat_t *
|
|||||||
case LOADLUMP_STANDARD:
|
case LOADLUMP_STANDARD:
|
||||||
{
|
{
|
||||||
const dheader_t *header = in;
|
const dheader_t *header = in;
|
||||||
l = header->lumps[info->lumpnumber];
|
int lumpnumber = info->lumpnumber;
|
||||||
|
|
||||||
|
if( FBitSet( flags, LUMP_BSHIFT_SWAP ))
|
||||||
|
{
|
||||||
|
if( lumpnumber == LUMP_ENTITIES )
|
||||||
|
lumpnumber = LUMP_PLANES;
|
||||||
|
else if( lumpnumber == LUMP_PLANES )
|
||||||
|
lumpnumber = LUMP_ENTITIES;
|
||||||
|
}
|
||||||
|
l = header->lumps[lumpnumber];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case LOADLUMP_BSP30EXT:
|
case LOADLUMP_BSP30EXT:
|
||||||
@@ -671,7 +844,7 @@ static void Mod_LoadLump( const void *in, const mlumpinfo_t *info, mlumpstat_t *
|
|||||||
}
|
}
|
||||||
|
|
||||||
// bmodels not required the visibility
|
// bmodels not required the visibility
|
||||||
if( !FBitSet( flags, LUMP_TESTONLY ) && !world.loading && info->lumpnumber == LUMP_VISIBILITY )
|
if( info->lumpnumber == LUMP_VISIBILITY && !world.loading && bmod )
|
||||||
SetBits( flags, LUMP_SILENT ); // shut up warning
|
SetBits( flags, LUMP_SILENT ); // shut up warning
|
||||||
|
|
||||||
// fill the stats for world
|
// fill the stats for world
|
||||||
@@ -749,15 +922,33 @@ static void Mod_LoadLump( const void *in, const mlumpinfo_t *info, mlumpstat_t *
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if( FBitSet( flags, LUMP_TESTONLY ))
|
// if no bmod is passed, we are only testing if BSP lumps are not corrupted
|
||||||
return; // don't fill the intermediate struct
|
if( !bmod )
|
||||||
|
return;
|
||||||
|
|
||||||
|
byte *data = (byte *)in + l.fileofs;
|
||||||
|
|
||||||
// all checks are passed, store pointers
|
// all checks are passed, store pointers
|
||||||
if( info->dataptr )
|
*(byte **)((byte *)bmod + info->dataofs) = data;
|
||||||
*info->dataptr = (void *)((byte *)in + l.fileofs);
|
*(size_t *)((byte *)bmod + info->countofs) = numelems;
|
||||||
|
|
||||||
if( info->count )
|
// finally, process the data
|
||||||
*info->count = numelems;
|
#if XASH_BIG_ENDIAN
|
||||||
|
const swap_struct_def_t *swap = real_entrysize == info->entrysize32 ? info->swap32 : info->swap;
|
||||||
|
size_t swaplen = real_entrysize == info->entrysize32 ? info->swaplen32 : info->swaplen;
|
||||||
|
|
||||||
|
if( swap )
|
||||||
|
{
|
||||||
|
for( size_t j = 0; j < numelems; j++ )
|
||||||
|
swap_struct_( swap, swaplen, data + j * real_entrysize );
|
||||||
|
}
|
||||||
|
// some lumps don't need a swapdef, as all needed data in the lump info
|
||||||
|
else if( real_entrysize > 1 )
|
||||||
|
{
|
||||||
|
for( size_t j = 0; j < numelems; j++ )
|
||||||
|
swap_field_( data + j * real_entrysize, real_entrysize );
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -2216,6 +2407,9 @@ static void Mod_LoadEntities( model_t *mod, const dbspmodel_t *bmod )
|
|||||||
world.wadlist = NULL;
|
world.wadlist = NULL;
|
||||||
world.wadcount = 0;
|
world.wadcount = 0;
|
||||||
|
|
||||||
|
world.litwater_minlight = -1;
|
||||||
|
world.litwater_scale = -1.0f;
|
||||||
|
|
||||||
// parse all the wads for loading textures in right ordering
|
// parse all the wads for loading textures in right ordering
|
||||||
while(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) != NULL )
|
while(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) != NULL )
|
||||||
{
|
{
|
||||||
@@ -2259,6 +2453,15 @@ static void Mod_LoadEntities( model_t *mod, const dbspmodel_t *bmod )
|
|||||||
Mem_Free( world.generator );
|
Mem_Free( world.generator );
|
||||||
world.generator = copystring( token );
|
world.generator = copystring( token );
|
||||||
}
|
}
|
||||||
|
else if( !Q_stricmp( keyname, "_litwater" ))
|
||||||
|
{
|
||||||
|
if( Q_atoi( token ) != 0 )
|
||||||
|
SetBits( world.flags, FWORLD_HAS_LITWATER );
|
||||||
|
}
|
||||||
|
else if( !Q_stricmp( keyname, "_litwater_minlight" ))
|
||||||
|
world.litwater_minlight = Q_atoi( token );
|
||||||
|
else if( !Q_stricmp( keyname, "_litwater_scale" ))
|
||||||
|
world.litwater_scale = Q_atof( token );
|
||||||
}
|
}
|
||||||
return; // all done
|
return; // all done
|
||||||
}
|
}
|
||||||
@@ -2535,6 +2738,7 @@ static void Mod_InitSkyClouds( model_t *mod, const mip_t *mt, texture_t *tx, qbo
|
|||||||
if( custom_palette )
|
if( custom_palette )
|
||||||
size += sizeof( short ) + 768;
|
size += sizeof( short ) + 768;
|
||||||
|
|
||||||
|
Image_SetForceFlags( IL_HOST_ENDIAN );
|
||||||
r_sky = FS_LoadImage( texname, (byte *)mt, size );
|
r_sky = FS_LoadImage( texname, (byte *)mt, size );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -2717,7 +2921,7 @@ static void Mod_LoadTextureData( model_t *mod, dbspmodel_t *bmod, int textureInd
|
|||||||
const size_t size = Mod_CalculateMipTexSize( mipTex, usesCustomPalette );
|
const size_t size = Mod_CalculateMipTexSize( mipTex, usesCustomPalette );
|
||||||
|
|
||||||
Q_snprintf( texName, sizeof( texName ), "#%s:%s.mip", loadstat.name, mipTex->name );
|
Q_snprintf( texName, sizeof( texName ), "#%s:%s.mip", loadstat.name, mipTex->name );
|
||||||
Image_SetForceFlags( texture_force_flags );
|
Image_SetForceFlags( texture_force_flags | IL_HOST_ENDIAN );
|
||||||
texture->gl_texturenum = ref.dllFuncs.GL_LoadTexture( texName, (byte *)mipTex, size, txFlags );
|
texture->gl_texturenum = ref.dllFuncs.GL_LoadTexture( texName, (byte *)mipTex, size, txFlags );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2750,7 +2954,7 @@ static void Mod_LoadTextureData( model_t *mod, dbspmodel_t *bmod, int textureInd
|
|||||||
|
|
||||||
Q_snprintf( texName, sizeof( texName ), "#%s:%s_luma.mip", loadstat.name, mipTex->name );
|
Q_snprintf( texName, sizeof( texName ), "#%s:%s_luma.mip", loadstat.name, mipTex->name );
|
||||||
|
|
||||||
Image_SetForceFlags( texture_force_flags );
|
Image_SetForceFlags( texture_force_flags | IL_HOST_ENDIAN );
|
||||||
|
|
||||||
if( mipTex->offsets[0] > 0 )
|
if( mipTex->offsets[0] > 0 )
|
||||||
{
|
{
|
||||||
@@ -2975,6 +3179,24 @@ static void Mod_LoadTextures( model_t *mod, dbspmodel_t *bmod )
|
|||||||
|
|
||||||
lump = bmod->textures;
|
lump = bmod->textures;
|
||||||
|
|
||||||
|
#if XASH_BIG_ENDIAN
|
||||||
|
if( lump )
|
||||||
|
{
|
||||||
|
lump->nummiptex = LittleLong( lump->nummiptex );
|
||||||
|
|
||||||
|
for( int i = 0; i < lump->nummiptex; i++ )
|
||||||
|
{
|
||||||
|
lump->dataofs[i] = LittleLong( lump->dataofs[i] );
|
||||||
|
|
||||||
|
if( lump->dataofs[i] >= 0 )
|
||||||
|
{
|
||||||
|
mip_t *mt = (mip_t *)((byte *)lump + lump->dataofs[i]);
|
||||||
|
le_struct_swap( mip_swap, mt );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if( bmod->texdatasize < 1 || !lump || lump->nummiptex < 1 )
|
if( bmod->texdatasize < 1 || !lump || lump->nummiptex < 1 )
|
||||||
{
|
{
|
||||||
// no textures
|
// no textures
|
||||||
@@ -2989,6 +3211,105 @@ static void Mod_LoadTextures( model_t *mod, dbspmodel_t *bmod )
|
|||||||
Mod_SequenceAllAnimatedTextures( mod );
|
Mod_SequenceAllAnimatedTextures( mod );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !XASH_DEDICATED
|
||||||
|
static void Mod_ParseDetailTextures( model_t *mod )
|
||||||
|
{
|
||||||
|
byte *afile;
|
||||||
|
char *pfile;
|
||||||
|
string token, texname;
|
||||||
|
string detail_texname;
|
||||||
|
string detail_path;
|
||||||
|
float xScale, yScale;
|
||||||
|
texture_t *tex;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
string filepath;
|
||||||
|
|
||||||
|
Q_strncpy( filepath, mod->name, sizeof( filepath ));
|
||||||
|
COM_StripExtension( filepath );
|
||||||
|
Q_strncat( filepath, "_detail.txt", sizeof( filepath ));
|
||||||
|
|
||||||
|
afile = FS_LoadFile( filepath, NULL, false );
|
||||||
|
if( !afile )
|
||||||
|
return;
|
||||||
|
|
||||||
|
pfile = (char *)afile;
|
||||||
|
|
||||||
|
// format: 'texturename' 'detailtexture' 'xScale' 'yScale'
|
||||||
|
while(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) != NULL )
|
||||||
|
{
|
||||||
|
texname[0] = '\0';
|
||||||
|
detail_texname[0] = '\0';
|
||||||
|
|
||||||
|
// read texname
|
||||||
|
if( token[0] == '{' )
|
||||||
|
{
|
||||||
|
// NOTE: COM_ParseFile handled some symbols seperately
|
||||||
|
// this code will be fix it
|
||||||
|
pfile = COM_ParseFile( pfile, token, sizeof( token ));
|
||||||
|
Q_snprintf( texname, sizeof( texname ), "{%s", token );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Q_strncpy( texname, token, sizeof( texname ));
|
||||||
|
|
||||||
|
// read detailtexture name
|
||||||
|
pfile = COM_ParseFile( pfile, token, sizeof( token ));
|
||||||
|
Q_strncpy( detail_texname, token, sizeof( detail_texname ));
|
||||||
|
|
||||||
|
// trying the scales or '{'
|
||||||
|
pfile = COM_ParseFile( pfile, token, sizeof( token ));
|
||||||
|
|
||||||
|
// read second part of detailtexture name
|
||||||
|
if( token[0] == '{' )
|
||||||
|
{
|
||||||
|
Q_strncat( detail_texname, token, sizeof( detail_texname ));
|
||||||
|
pfile = COM_ParseFile( pfile, token, sizeof( token )); // read scales
|
||||||
|
Q_strncat( detail_texname, token, sizeof( detail_texname ));
|
||||||
|
pfile = COM_ParseFile( pfile, token, sizeof( token )); // parse scales
|
||||||
|
}
|
||||||
|
|
||||||
|
Q_snprintf( detail_path, sizeof( detail_path ), "gfx/%s", detail_texname );
|
||||||
|
|
||||||
|
// read scales
|
||||||
|
xScale = Q_atof( token );
|
||||||
|
|
||||||
|
pfile = COM_ParseFile( pfile, token, sizeof( token ));
|
||||||
|
yScale = Q_atof( token );
|
||||||
|
|
||||||
|
if( xScale <= 0.0f || yScale <= 0.0f )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// search for existing texture and uploading detail texture
|
||||||
|
for( i = 0; i < mod->numtextures; i++ )
|
||||||
|
{
|
||||||
|
tex = mod->textures[i];
|
||||||
|
|
||||||
|
if( Q_stricmp( tex->name, texname ))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
tex->dt_texturenum = ref.dllFuncs.GL_LoadTexture( detail_path, NULL, 0, TF_FORCE_COLOR|TF_NOFLIP_TGA );
|
||||||
|
|
||||||
|
if( tex->dt_texturenum )
|
||||||
|
ref.dllFuncs.R_SetDetailScaleForTexture( tex->gl_texturenum, xScale, yScale );
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Mem_Free( afile );
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mod_LoadDetailTextures( model_t *mod )
|
||||||
|
{
|
||||||
|
convar_t *r_detailtextures = Cvar_FindVar( "r_detailtextures" );
|
||||||
|
|
||||||
|
if( !r_detailtextures || !r_detailtextures->value )
|
||||||
|
return;
|
||||||
|
|
||||||
|
Mod_ParseDetailTextures( mod );
|
||||||
|
}
|
||||||
|
#endif // !XASH_DEDICATED
|
||||||
|
|
||||||
/*
|
/*
|
||||||
=================
|
=================
|
||||||
Mod_LoadTexInfo
|
Mod_LoadTexInfo
|
||||||
@@ -3746,6 +4067,103 @@ static int Mod_LumpLooksLikeEntities( const char *lump, const size_t lumplen )
|
|||||||
return Q_memmem( lump, lumplen, "\"classname\"", sizeof( "\"classname\"" ) - 1 ) != NULL ? 1 : 0;
|
return Q_memmem( lump, lumplen, "\"classname\"", sizeof( "\"classname\"" ) - 1 ) != NULL ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void Mod_SwapBSPLumps( byte *mod_base, size_t bufferlen )
|
||||||
|
{
|
||||||
|
dheader_t *header = (dheader_t *)mod_base;
|
||||||
|
|
||||||
|
le_struct_swap( dheader_swap, header );
|
||||||
|
|
||||||
|
// BSP30ext pass
|
||||||
|
if( header->version == HLBSP_VERSION && bufferlen > sizeof( *header ) + sizeof( dextrahdr_t ))
|
||||||
|
{
|
||||||
|
dextrahdr_t *ext = (dextrahdr_t *)( mod_base + sizeof( *header ));
|
||||||
|
|
||||||
|
if( ext->id == LittleLong( IDEXTRAHEADER ))
|
||||||
|
le_struct_swap( dextrahdr_swap, ext );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
=================
|
||||||
|
CRC32_MapFile
|
||||||
|
|
||||||
|
compute CRC for the map lump data
|
||||||
|
=================
|
||||||
|
*/
|
||||||
|
qboolean CRC32_MapFile( dword *crcvalue, const char *filename, qboolean multiplayer )
|
||||||
|
{
|
||||||
|
byte headbuf[sizeof( dheader_t )];
|
||||||
|
char buffer[1024];
|
||||||
|
int i, num_bytes, lumplen;
|
||||||
|
dheader_t *header;
|
||||||
|
file_t *f;
|
||||||
|
|
||||||
|
if( !crcvalue )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// always calc same checksum for singleplayer
|
||||||
|
if( multiplayer == false )
|
||||||
|
{
|
||||||
|
*crcvalue = (('H'<<24)+('S'<<16)+('A'<<8)+'X');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
f = FS_Open( filename, "rb", false );
|
||||||
|
if( !f )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
num_bytes = FS_Read( f, headbuf, sizeof( headbuf ));
|
||||||
|
|
||||||
|
if( num_bytes != sizeof( headbuf ))
|
||||||
|
{
|
||||||
|
FS_Close( f );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
header = (dheader_t *)headbuf;
|
||||||
|
le_struct_swap( dheader_swap, header );
|
||||||
|
|
||||||
|
switch( header->version )
|
||||||
|
{
|
||||||
|
case Q1BSP_VERSION:
|
||||||
|
case HLBSP_VERSION:
|
||||||
|
case QBSP2_VERSION:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
FS_Close( f );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CRC32_Init( crcvalue );
|
||||||
|
|
||||||
|
for( i = LUMP_PLANES; i < HEADER_LUMPS; i++ )
|
||||||
|
{
|
||||||
|
lumplen = header->lumps[i].filelen;
|
||||||
|
FS_Seek( f, header->lumps[i].fileofs, SEEK_SET );
|
||||||
|
|
||||||
|
while( lumplen > 0 )
|
||||||
|
{
|
||||||
|
if( lumplen >= sizeof( buffer ))
|
||||||
|
num_bytes = FS_Read( f, buffer, sizeof( buffer ));
|
||||||
|
else
|
||||||
|
num_bytes = FS_Read( f, buffer, lumplen );
|
||||||
|
|
||||||
|
if( num_bytes > 0 )
|
||||||
|
{
|
||||||
|
lumplen -= num_bytes;
|
||||||
|
CRC32_ProcessBuffer( crcvalue, buffer, num_bytes );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( FS_Eof( f ))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FS_Close( f );
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
=================
|
=================
|
||||||
Mod_FindEndOfBSPFile
|
Mod_FindEndOfBSPFile
|
||||||
@@ -3790,20 +4208,26 @@ Mod_FindBSPX
|
|||||||
find BSPX header position, returns -1 on error
|
find BSPX header position, returns -1 on error
|
||||||
=================
|
=================
|
||||||
*/
|
*/
|
||||||
static fs_offset_t Mod_FindBSPX( const byte *mod_base, size_t bufferlen )
|
static fs_offset_t Mod_FindBSPX( byte *mod_base, size_t bufferlen )
|
||||||
{
|
{
|
||||||
fs_offset_t max_offset = Mod_FindEndOfBSPFile( mod_base, bufferlen );
|
fs_offset_t max_offset = Mod_FindEndOfBSPFile( mod_base, bufferlen );
|
||||||
const dbspx_hdr_t *bspx_header;
|
|
||||||
|
|
||||||
max_offset = ALIGN( max_offset, 4 ); // force 32-bit boundary
|
max_offset = ALIGN( max_offset, 4 ); // force 32-bit boundary
|
||||||
|
|
||||||
if( max_offset + sizeof( dbspx_hdr_t ) > bufferlen )
|
if( max_offset + sizeof( dbspx_hdr_t ) > bufferlen )
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
bspx_header = (const dbspx_hdr_t *)( mod_base + max_offset );
|
dbspx_hdr_t *bspx_header = (dbspx_hdr_t *)( mod_base + max_offset );
|
||||||
|
|
||||||
if( bspx_header->id != LittleLong( IDBSPXHEADER ))
|
if( bspx_header->id != LittleLong( IDBSPXHEADER ))
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
|
bspx_header->id = LittleLong( bspx_header->id );
|
||||||
|
bspx_header->numlumps = LittleLong( bspx_header->numlumps );
|
||||||
|
|
||||||
|
for( int i = 0; i < bspx_header->numlumps; i++ )
|
||||||
|
le_struct_swap( dbspx_lump_swap, &bspx_header->lumps[i] );
|
||||||
|
|
||||||
Con_DPrintf( "Found valid BSPX signature at %lld\n", (long long)max_offset );
|
Con_DPrintf( "Found valid BSPX signature at %lld\n", (long long)max_offset );
|
||||||
return max_offset;
|
return max_offset;
|
||||||
}
|
}
|
||||||
@@ -3819,22 +4243,19 @@ static qboolean Mod_LoadBmodelLumps( model_t *mod, byte *mod_base, size_t buffer
|
|||||||
{
|
{
|
||||||
dheader_t *header = (dheader_t *)mod_base;
|
dheader_t *header = (dheader_t *)mod_base;
|
||||||
int *extident = (int *)(mod_base + sizeof( dheader_t ));
|
int *extident = (int *)(mod_base + sizeof( dheader_t ));
|
||||||
dbspmodel_t *bmod = &srcmodel;
|
|
||||||
char wadvalue[2048];
|
char wadvalue[2048];
|
||||||
size_t len = 0;
|
size_t len = 0;
|
||||||
int i, stat_index = 0, ret, flags = 0;
|
int i, stat_index = 0, ret, flags = 0;
|
||||||
fs_offset_t bspx_header_offset;
|
fs_offset_t bspx_header_offset;
|
||||||
|
|
||||||
// always reset the intermediate struct
|
// always reset the intermediate struct
|
||||||
memset( bmod, 0, sizeof( *bmod ));
|
|
||||||
memset( &loadstat, 0, sizeof( loadstat ));
|
memset( &loadstat, 0, sizeof( loadstat ));
|
||||||
|
|
||||||
Q_strncpy( loadstat.name, mod->name, sizeof( loadstat.name ));
|
Q_strncpy( loadstat.name, mod->name, sizeof( loadstat.name ));
|
||||||
wadvalue[0] = '\0';
|
wadvalue[0] = '\0';
|
||||||
|
|
||||||
// restore default lump numbers
|
// byte-swap BSP header and lump directory from little-endian
|
||||||
srclumps[0].lumpnumber = LUMP_ENTITIES;
|
Mod_SwapBSPLumps( mod_base, bufferlen );
|
||||||
srclumps[1].lumpnumber = LUMP_PLANES;
|
|
||||||
|
|
||||||
switch( header->version )
|
switch( header->version )
|
||||||
{
|
{
|
||||||
@@ -3848,8 +4269,7 @@ static qboolean Mod_LoadBmodelLumps( model_t *mod, byte *mod_base, size_t buffer
|
|||||||
Mod_LumpLooksLikeEntities( mod_base + header->lumps[LUMP_PLANES].fileofs, header->lumps[LUMP_PLANES].filelen ))
|
Mod_LumpLooksLikeEntities( mod_base + header->lumps[LUMP_PLANES].fileofs, header->lumps[LUMP_PLANES].filelen ))
|
||||||
{
|
{
|
||||||
// blue-shift swapped lumps
|
// blue-shift swapped lumps
|
||||||
srclumps[0].lumpnumber = LUMP_PLANES;
|
SetBits( flags, LUMP_BSHIFT_SWAP );
|
||||||
srclumps[1].lumpnumber = LUMP_ENTITIES;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Q1BSP_VERSION:
|
case Q1BSP_VERSION:
|
||||||
@@ -3863,6 +4283,7 @@ static qboolean Mod_LoadBmodelLumps( model_t *mod, byte *mod_base, size_t buffer
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dbspmodel_t *bmod = Mem_Calloc( mod->mempool, sizeof( *bmod ));
|
||||||
bmod->version = header->version; // share up global
|
bmod->version = header->version; // share up global
|
||||||
if( isworld )
|
if( isworld )
|
||||||
{
|
{
|
||||||
@@ -3875,17 +4296,17 @@ static qboolean Mod_LoadBmodelLumps( model_t *mod, byte *mod_base, size_t buffer
|
|||||||
|
|
||||||
// loading base lumps
|
// loading base lumps
|
||||||
for( i = 0; i < ARRAYSIZE( srclumps ); i++, stat_index++ )
|
for( i = 0; i < ARRAYSIZE( srclumps ); i++, stat_index++ )
|
||||||
Mod_LoadLump( mod_base, &srclumps[i], &worldstats[stat_index], flags, LOADLUMP_STANDARD, NULL );
|
Mod_LoadLump( mod_base, &srclumps[i], &worldstats[stat_index], flags, LOADLUMP_STANDARD, NULL, bmod );
|
||||||
|
|
||||||
// loading extralumps
|
// loading extralumps
|
||||||
for( i = 0; i < ARRAYSIZE( extlumps ); i++, stat_index++ )
|
for( i = 0; i < ARRAYSIZE( extlumps ); i++, stat_index++ )
|
||||||
Mod_LoadLump( mod_base, &extlumps[i], &worldstats[stat_index], flags, LOADLUMP_BSP30EXT, NULL );
|
Mod_LoadLump( mod_base, &extlumps[i], &worldstats[stat_index], flags, LOADLUMP_BSP30EXT, NULL, bmod );
|
||||||
|
|
||||||
// loading bspx lumps
|
// loading bspx lumps
|
||||||
if( bspx_header_offset >= 0 )
|
if( bspx_header_offset >= 0 )
|
||||||
{
|
{
|
||||||
for( i = 0; i < ARRAYSIZE( bspxlumps ); i++, stat_index++ )
|
for( i = 0; i < ARRAYSIZE( bspxlumps ); i++, stat_index++ )
|
||||||
Mod_LoadLump( mod_base, &bspxlumps[i], &worldstats[stat_index], flags, LOADLUMP_BSPX, mod_base + bspx_header_offset );
|
Mod_LoadLump( mod_base, &bspxlumps[i], &worldstats[stat_index], flags, LOADLUMP_BSPX, mod_base + bspx_header_offset, bmod );
|
||||||
}
|
}
|
||||||
|
|
||||||
if( !bmod->isworld ) // a1ba: why world excluded here?
|
if( !bmod->isworld ) // a1ba: why world excluded here?
|
||||||
@@ -3893,6 +4314,7 @@ static qboolean Mod_LoadBmodelLumps( model_t *mod, byte *mod_base, size_t buffer
|
|||||||
if( loadstat.numerrors )
|
if( loadstat.numerrors )
|
||||||
{
|
{
|
||||||
Con_DPrintf( "%s: %i error(s), %i warning(s)\n", __func__, loadstat.numerrors, loadstat.numwarnings );
|
Con_DPrintf( "%s: %i error(s), %i warning(s)\n", __func__, loadstat.numerrors, loadstat.numwarnings );
|
||||||
|
Mem_Free( bmod );
|
||||||
return false; // there were errors, we can't load this map
|
return false; // there were errors, we can't load this map
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3957,6 +4379,7 @@ static qboolean Mod_LoadBmodelLumps( model_t *mod, byte *mod_base, size_t buffer
|
|||||||
Con_Reportf( "Wad files required to run the map: \"%s\"\n", wadvalue );
|
Con_Reportf( "Wad files required to run the map: \"%s\"\n", wadvalue );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Mem_Free( bmod );
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3999,7 +4422,7 @@ qboolean Mod_TestBmodelLumps( file_t *f, const char *name, byte *mod_base, size_
|
|||||||
{
|
{
|
||||||
dheader_t *header = (dheader_t *)mod_base;
|
dheader_t *header = (dheader_t *)mod_base;
|
||||||
int *extident = (int *)( mod_base + sizeof( dheader_t ));
|
int *extident = (int *)( mod_base + sizeof( dheader_t ));
|
||||||
int i, flags = LUMP_TESTONLY, stat_index = 0;
|
int i, flags = 0, stat_index = 0;
|
||||||
|
|
||||||
// always reset the intermediate struct
|
// always reset the intermediate struct
|
||||||
memset( &loadstat, 0, sizeof( loadstat_t ));
|
memset( &loadstat, 0, sizeof( loadstat_t ));
|
||||||
@@ -4012,9 +4435,8 @@ qboolean Mod_TestBmodelLumps( file_t *f, const char *name, byte *mod_base, size_
|
|||||||
if( buffersize < sizeof( *header ))
|
if( buffersize < sizeof( *header ))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// restore default lump numbers
|
// byte-swap BSP header and lump directory from little-endian
|
||||||
srclumps[0].lumpnumber = LUMP_ENTITIES;
|
Mod_SwapBSPLumps( mod_base, buffersize );
|
||||||
srclumps[1].lumpnumber = LUMP_PLANES;
|
|
||||||
|
|
||||||
switch( header->version )
|
switch( header->version )
|
||||||
{
|
{
|
||||||
@@ -4037,11 +4459,7 @@ qboolean Mod_TestBmodelLumps( file_t *f, const char *name, byte *mod_base, size_
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
if( ret )
|
if( ret )
|
||||||
{
|
SetBits( flags, LUMP_BSHIFT_SWAP );
|
||||||
// blue-shift swapped lumps
|
|
||||||
srclumps[0].lumpnumber = LUMP_PLANES;
|
|
||||||
srclumps[1].lumpnumber = LUMP_ENTITIES;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -4057,15 +4475,15 @@ qboolean Mod_TestBmodelLumps( file_t *f, const char *name, byte *mod_base, size_
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get entities lump to caller
|
// get entities lump to caller
|
||||||
*entities = header->lumps[srclumps[0].lumpnumber];
|
*entities = header->lumps[FBitSet( flags, LUMP_BSHIFT_SWAP ) ? LUMP_PLANES : LUMP_ENTITIES];
|
||||||
|
|
||||||
// loading base lumps
|
// loading base lumps
|
||||||
for( i = 0; i < ARRAYSIZE( srclumps ); i++, stat_index++ )
|
for( i = 0; i < ARRAYSIZE( srclumps ); i++, stat_index++ )
|
||||||
Mod_LoadLump( mod_base, &srclumps[i], &worldstats[stat_index], flags, LOADLUMP_STANDARD, NULL );
|
Mod_LoadLump( mod_base, &srclumps[i], &worldstats[stat_index], flags, LOADLUMP_STANDARD, NULL, NULL );
|
||||||
|
|
||||||
// loading extralumps
|
// loading extralumps
|
||||||
for( i = 0; i < ARRAYSIZE( extlumps ); i++, stat_index++ )
|
for( i = 0; i < ARRAYSIZE( extlumps ); i++, stat_index++ )
|
||||||
Mod_LoadLump( mod_base, &extlumps[i], &worldstats[stat_index], flags, LOADLUMP_BSP30EXT, NULL );
|
Mod_LoadLump( mod_base, &extlumps[i], &worldstats[stat_index], flags, LOADLUMP_BSP30EXT, NULL, NULL );
|
||||||
|
|
||||||
// FIXME: BSPX testing
|
// FIXME: BSPX testing
|
||||||
|
|
||||||
|
|||||||
@@ -124,6 +124,10 @@ typedef struct world_static_s
|
|||||||
|
|
||||||
wadentry_t *wadlist;
|
wadentry_t *wadlist;
|
||||||
int wadcount;
|
int wadcount;
|
||||||
|
|
||||||
|
// lightmapped water extra info
|
||||||
|
float litwater_scale;
|
||||||
|
int litwater_minlight;
|
||||||
} world_static_t;
|
} world_static_t;
|
||||||
|
|
||||||
#ifndef REF_DLL
|
#ifndef REF_DLL
|
||||||
@@ -156,6 +160,7 @@ model_t *Mod_ForName( const char *name, qboolean crash, qboolean trackCRC );
|
|||||||
qboolean Mod_ValidateCRC( const char *name, uint32_t crc );
|
qboolean Mod_ValidateCRC( const char *name, uint32_t crc );
|
||||||
void Mod_NeedCRC( const char *name, qboolean needCRC );
|
void Mod_NeedCRC( const char *name, qboolean needCRC );
|
||||||
void Mod_FreeUnused( void );
|
void Mod_FreeUnused( void );
|
||||||
|
void Mod_LoadDetailTextures( model_t *mod );
|
||||||
|
|
||||||
//
|
//
|
||||||
// mod_alias.c
|
// mod_alias.c
|
||||||
@@ -180,7 +185,7 @@ void Mod_PrintWorldStats_f( void );
|
|||||||
//
|
//
|
||||||
// mod_studio.c
|
// mod_studio.c
|
||||||
//
|
//
|
||||||
void Mod_LoadStudioModel( model_t *mod, const void *buffer, qboolean *loaded );
|
void Mod_LoadStudioModel( model_t *mod, void *buffer, size_t buffersize, qboolean *loaded );
|
||||||
void Mod_InitStudioAPI( void );
|
void Mod_InitStudioAPI( void );
|
||||||
void Mod_InitStudioHull( void );
|
void Mod_InitStudioHull( void );
|
||||||
void Mod_ResetStudioAPI( void );
|
void Mod_ResetStudioAPI( void );
|
||||||
@@ -190,13 +195,14 @@ void Mod_StudioGetAttachment( const edict_t *e, int iAttachment, float *org, flo
|
|||||||
void Mod_GetBonePosition( const edict_t *e, int iBone, float *org, float *ang );
|
void Mod_GetBonePosition( const edict_t *e, int iBone, float *org, float *ang );
|
||||||
hull_t *Mod_HullForStudio( model_t *m, float frame, int seq, vec3_t ang, vec3_t org, vec3_t size, byte *pcnt, byte *pbl, int *hitboxes, edict_t *ed );
|
hull_t *Mod_HullForStudio( model_t *m, float frame, int seq, vec3_t ang, vec3_t org, vec3_t size, byte *pcnt, byte *pbl, int *hitboxes, edict_t *ed );
|
||||||
void *R_StudioGetAnim( studiohdr_t *m_pStudioHeader, model_t *m_pSubModel, mstudioseqdesc_t *pseqdesc );
|
void *R_StudioGetAnim( studiohdr_t *m_pStudioHeader, model_t *m_pSubModel, mstudioseqdesc_t *pseqdesc );
|
||||||
|
void Mod_SwapStudioSeqGroupAnims( studiohdr_t *phdr, mstudioseqdesc_t *pseq, byte *buf );
|
||||||
int Mod_HitgroupForStudioHull( int index );
|
int Mod_HitgroupForStudioHull( int index );
|
||||||
void Mod_ClearStudioCache( void );
|
void Mod_ClearStudioCache( void );
|
||||||
|
|
||||||
//
|
//
|
||||||
// mod_sprite.c
|
// mod_sprite.c
|
||||||
//
|
//
|
||||||
void Mod_LoadSpriteModel( model_t *mod, const void *buffer, size_t buffersize, qboolean *loaded );
|
void Mod_LoadSpriteModel( model_t *mod, void *buffer, size_t buffersize, qboolean *loaded );
|
||||||
void Mod_SpriteLoadTextures( model_t *mod, const void *buffer );
|
void Mod_SpriteLoadTextures( model_t *mod, const void *buffer );
|
||||||
void Mod_SpriteUnloadTextures( void *data );
|
void Mod_SpriteUnloadTextures( void *data );
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,168 @@ GNU General Public License for more details.
|
|||||||
#include "sprite.h"
|
#include "sprite.h"
|
||||||
#include "studio.h"
|
#include "studio.h"
|
||||||
#include "mod_local.h"
|
#include "mod_local.h"
|
||||||
|
#include "swaplib.h"
|
||||||
|
|
||||||
|
le_struct_begin( dsprite_q1_swap )
|
||||||
|
le_struct_field( dsprite_q1_t, ident )
|
||||||
|
le_struct_field( dsprite_q1_t, version )
|
||||||
|
le_struct_field( dsprite_q1_t, type )
|
||||||
|
le_struct_field( dsprite_q1_t, boundingradius )
|
||||||
|
le_struct_array( dsprite_q1_t, bounds, 2 )
|
||||||
|
le_struct_field( dsprite_q1_t, numframes )
|
||||||
|
le_struct_field( dsprite_q1_t, beamlength )
|
||||||
|
le_struct_field( dsprite_q1_t, synctype )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( dsprite_hl_swap )
|
||||||
|
le_struct_field( dsprite_hl_t, ident )
|
||||||
|
le_struct_field( dsprite_hl_t, version )
|
||||||
|
le_struct_field( dsprite_hl_t, type )
|
||||||
|
le_struct_field( dsprite_hl_t, texFormat )
|
||||||
|
le_struct_field( dsprite_hl_t, boundingradius )
|
||||||
|
le_struct_array( dsprite_hl_t, bounds, 2 )
|
||||||
|
le_struct_field( dsprite_hl_t, numframes )
|
||||||
|
le_struct_field( dsprite_hl_t, facetype )
|
||||||
|
le_struct_field( dsprite_hl_t, synctype )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( dspriteframe_swap )
|
||||||
|
le_struct_array( dspriteframe_t, origin, 2 )
|
||||||
|
le_struct_field( dspriteframe_t, width )
|
||||||
|
le_struct_field( dspriteframe_t, height )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
static byte *Mod_SwapSpriteFrame( byte *p, byte *end, int bytes )
|
||||||
|
{
|
||||||
|
dspriteframe_t frame;
|
||||||
|
|
||||||
|
if( p + sizeof( frame ) > end )
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
memcpy( &frame, p, sizeof( frame ));
|
||||||
|
le_struct_swap( dspriteframe_swap, &frame );
|
||||||
|
#if XASH_BIG_ENDIAN
|
||||||
|
memcpy( p, &frame, sizeof( frame ));
|
||||||
|
#endif
|
||||||
|
p += sizeof( frame );
|
||||||
|
|
||||||
|
// skip pixel data
|
||||||
|
if( p + frame.width * frame.height * bytes > end )
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
p += frame.width * frame.height * bytes;
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
static byte *Mod_SwapSpriteGroup( byte *p, byte *end, int bytes )
|
||||||
|
{
|
||||||
|
dspritegroup_t *group;
|
||||||
|
|
||||||
|
if( p + sizeof( *group ) > end )
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
group = (dspritegroup_t *)p;
|
||||||
|
group->numframes = LittleLong( group->numframes );
|
||||||
|
p += sizeof( *group );
|
||||||
|
|
||||||
|
// swap intervals
|
||||||
|
int numframes = group->numframes;
|
||||||
|
|
||||||
|
if( p + numframes * sizeof( dspriteinterval_t ) > end )
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
for( int i = 0; i < numframes; i++ )
|
||||||
|
{
|
||||||
|
dspriteinterval_t *interval = (dspriteinterval_t *)p;
|
||||||
|
interval->interval = LittleFloat( interval->interval );
|
||||||
|
p += sizeof( *interval );
|
||||||
|
}
|
||||||
|
|
||||||
|
// swap each frame in the group
|
||||||
|
for( int i = 0; i < numframes; i++ )
|
||||||
|
{
|
||||||
|
p = Mod_SwapSpriteFrame( p, end, bytes );
|
||||||
|
if( !p )
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
static qboolean Mod_SwapSprite( void *buffer, size_t buffersize, int *out_version )
|
||||||
|
{
|
||||||
|
byte *end = (byte *)buffer + buffersize;
|
||||||
|
int version, numframes, bytes;
|
||||||
|
byte *p;
|
||||||
|
|
||||||
|
if( buffersize < sizeof( dsprite_t ))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// peek at ident + version before full swap
|
||||||
|
version = LittleLong(((dsprite_t *)buffer)->version );
|
||||||
|
|
||||||
|
switch( version )
|
||||||
|
{
|
||||||
|
case SPRITE_VERSION_Q1:
|
||||||
|
case SPRITE_VERSION_32:
|
||||||
|
if( buffersize < sizeof( dsprite_q1_t ))
|
||||||
|
return false;
|
||||||
|
le_struct_swap( dsprite_q1_swap, buffer );
|
||||||
|
numframes = ((dsprite_q1_t *)buffer)->numframes;
|
||||||
|
p = (byte *)buffer + sizeof( dsprite_q1_t );
|
||||||
|
break;
|
||||||
|
case SPRITE_VERSION_HL:
|
||||||
|
{
|
||||||
|
if( buffersize < sizeof( dsprite_hl_t ))
|
||||||
|
return false;
|
||||||
|
le_struct_swap( dsprite_hl_swap, buffer );
|
||||||
|
numframes = ((dsprite_hl_t *)buffer)->numframes;
|
||||||
|
p = (byte *)buffer + sizeof( dsprite_hl_t );
|
||||||
|
|
||||||
|
// HL sprites have a palette count (short) + palette data
|
||||||
|
if( p + sizeof( short ) > end )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
short numi = LittleShort( *(short *)p );
|
||||||
|
*(short *)p = numi;
|
||||||
|
p += sizeof( short ) + numi * 3;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out_version = version;
|
||||||
|
bytes = ( version == SPRITE_VERSION_32 ) ? 4 : 1;
|
||||||
|
|
||||||
|
// swap all frames
|
||||||
|
for( int i = 0; i < numframes && p && p < end; i++ )
|
||||||
|
{
|
||||||
|
dframetype_t *frametype;
|
||||||
|
|
||||||
|
if( p + sizeof( *frametype ) > end )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
frametype = (dframetype_t *)p;
|
||||||
|
frametype->type = LittleLong( frametype->type );
|
||||||
|
p += sizeof( *frametype );
|
||||||
|
|
||||||
|
switch( frametype->type )
|
||||||
|
{
|
||||||
|
case FRAME_SINGLE:
|
||||||
|
p = Mod_SwapSpriteFrame( p, end, bytes );
|
||||||
|
break;
|
||||||
|
case FRAME_GROUP:
|
||||||
|
case FRAME_ANGLED:
|
||||||
|
p = Mod_SwapSpriteGroup( p, end, bytes );
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
====================
|
====================
|
||||||
@@ -27,46 +188,18 @@ Mod_LoadSpriteModel
|
|||||||
load sprite model
|
load sprite model
|
||||||
====================
|
====================
|
||||||
*/
|
*/
|
||||||
void Mod_LoadSpriteModel( model_t *mod, const void *buffer, size_t buffersize, qboolean *loaded )
|
void Mod_LoadSpriteModel( model_t *mod, void *buffer, size_t buffersize, qboolean *loaded )
|
||||||
{
|
{
|
||||||
const dsprite_t *pin = buffer;
|
|
||||||
msprite_t *psprite;
|
msprite_t *psprite;
|
||||||
char poolname[MAX_VA_STRING];
|
char poolname[MAX_VA_STRING];
|
||||||
|
int version;
|
||||||
|
|
||||||
if( loaded )
|
if( loaded )
|
||||||
*loaded = false;
|
*loaded = false;
|
||||||
|
|
||||||
if( buffersize < sizeof( dsprite_t ))
|
if( !Mod_SwapSprite( buffer, buffersize, &version ))
|
||||||
{
|
{
|
||||||
Con_DPrintf( S_ERROR "%s: %s have incorrect file size %zu should be greater than %zu (%s)\n", __func__, mod->name, buffersize, sizeof( dsprite_t ), "basic header" );
|
Con_DPrintf( S_ERROR "%s: %s is not a valid sprite\n", __func__, mod->name );
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( pin->ident != IDSPRITEHEADER )
|
|
||||||
{
|
|
||||||
Con_DPrintf( S_ERROR "%s: %s has wrong id (0x%x should be 0x%x)\n", __func__, mod->name, pin->ident, IDSPRITEHEADER );
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch( pin->version )
|
|
||||||
{
|
|
||||||
case SPRITE_VERSION_Q1:
|
|
||||||
case SPRITE_VERSION_32:
|
|
||||||
if( buffersize < sizeof( dsprite_q1_t ))
|
|
||||||
{
|
|
||||||
Con_DPrintf( S_ERROR "%s: %s have incorrect file size %zu should be greater than %zu (%s)\n", __func__, mod->name, buffersize, sizeof( dsprite_q1_t ), "q1 header" );
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case SPRITE_VERSION_HL:
|
|
||||||
if( buffersize < sizeof( dsprite_hl_t ))
|
|
||||||
{
|
|
||||||
Con_DPrintf( S_ERROR "%s: %s have incorrect file size %zu should be greater than %zu (%s)\n", __func__, mod->name, buffersize, sizeof( dsprite_hl_t ), "hl header" );
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Con_DPrintf( S_ERROR "%s: %s has wrong version number (%i should be %i, %i or %i)\n", __func__, mod->name, pin->version, SPRITE_VERSION_Q1, SPRITE_VERSION_32, SPRITE_VERSION_HL );
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,9 +207,9 @@ void Mod_LoadSpriteModel( model_t *mod, const void *buffer, size_t buffersize, q
|
|||||||
Q_snprintf( poolname, sizeof( poolname ), "^2%s^7", mod->name );
|
Q_snprintf( poolname, sizeof( poolname ), "^2%s^7", mod->name );
|
||||||
mod->mempool = Mem_AllocPool( poolname );
|
mod->mempool = Mem_AllocPool( poolname );
|
||||||
|
|
||||||
if( pin->version == SPRITE_VERSION_Q1 || pin->version == SPRITE_VERSION_32 )
|
if( version == SPRITE_VERSION_Q1 || version == SPRITE_VERSION_32 )
|
||||||
{
|
{
|
||||||
const dsprite_q1_t *pinq1 = buffer;
|
dsprite_q1_t *pinq1 = buffer;
|
||||||
size_t size;
|
size_t size;
|
||||||
|
|
||||||
if( pinq1->numframes == 0 )
|
if( pinq1->numframes == 0 )
|
||||||
@@ -106,9 +239,9 @@ void Mod_LoadSpriteModel( model_t *mod, const void *buffer, size_t buffersize, q
|
|||||||
mod->mins[2] = -pinq1->bounds[1] * 0.5f;
|
mod->mins[2] = -pinq1->bounds[1] * 0.5f;
|
||||||
mod->maxs[2] = pinq1->bounds[1] * 0.5f;
|
mod->maxs[2] = pinq1->bounds[1] * 0.5f;
|
||||||
}
|
}
|
||||||
else // if( pin->version == SPRITE_VERSION_HL )
|
else // if( version == SPRITE_VERSION_HL )
|
||||||
{
|
{
|
||||||
const dsprite_hl_t *pinhl = buffer;
|
dsprite_hl_t *pinhl = buffer;
|
||||||
size_t size;
|
size_t size;
|
||||||
|
|
||||||
if( pinhl->numframes == 0 )
|
if( pinhl->numframes == 0 )
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ GNU General Public License for more details.
|
|||||||
#include "r_studioint.h"
|
#include "r_studioint.h"
|
||||||
#include "library.h"
|
#include "library.h"
|
||||||
#include "ref_common.h"
|
#include "ref_common.h"
|
||||||
|
#include "swaplib.h"
|
||||||
|
|
||||||
typedef int (*STUDIOAPI)( int, sv_blending_interface_t**, server_studio_api_t*, float (*transform)[3][4], float (*bones)[MAXSTUDIOBONES][3][4] );
|
typedef int (*STUDIOAPI)( int, sv_blending_interface_t**, server_studio_api_t*, float (*transform)[3][4], float (*bones)[MAXSTUDIOBONES][3][4] );
|
||||||
|
|
||||||
@@ -58,6 +59,151 @@ static int cache_current;
|
|||||||
static int cache_current_hull;
|
static int cache_current_hull;
|
||||||
static int cache_current_plane;
|
static int cache_current_plane;
|
||||||
|
|
||||||
|
le_struct_begin( studiohdr_swap )
|
||||||
|
le_struct_field( studiohdr_t, ident )
|
||||||
|
le_struct_field( studiohdr_t, version )
|
||||||
|
le_struct_field( studiohdr_t, length )
|
||||||
|
le_struct_array( studiohdr_t, eyeposition, 3 )
|
||||||
|
le_struct_array( studiohdr_t, min, 3 )
|
||||||
|
le_struct_array( studiohdr_t, max, 3 )
|
||||||
|
le_struct_array( studiohdr_t, bbmin, 3 )
|
||||||
|
le_struct_array( studiohdr_t, bbmax, 3 )
|
||||||
|
le_struct_field( studiohdr_t, flags )
|
||||||
|
le_struct_field( studiohdr_t, numbones )
|
||||||
|
le_struct_field( studiohdr_t, boneindex )
|
||||||
|
le_struct_field( studiohdr_t, numbonecontrollers )
|
||||||
|
le_struct_field( studiohdr_t, bonecontrollerindex )
|
||||||
|
le_struct_field( studiohdr_t, numhitboxes )
|
||||||
|
le_struct_field( studiohdr_t, hitboxindex )
|
||||||
|
le_struct_field( studiohdr_t, numseq )
|
||||||
|
le_struct_field( studiohdr_t, seqindex )
|
||||||
|
le_struct_field( studiohdr_t, numseqgroups )
|
||||||
|
le_struct_field( studiohdr_t, seqgroupindex )
|
||||||
|
le_struct_field( studiohdr_t, numtextures )
|
||||||
|
le_struct_field( studiohdr_t, textureindex )
|
||||||
|
le_struct_field( studiohdr_t, texturedataindex )
|
||||||
|
le_struct_field( studiohdr_t, numskinref )
|
||||||
|
le_struct_field( studiohdr_t, numskinfamilies )
|
||||||
|
le_struct_field( studiohdr_t, skinindex )
|
||||||
|
le_struct_field( studiohdr_t, numbodyparts )
|
||||||
|
le_struct_field( studiohdr_t, bodypartindex )
|
||||||
|
le_struct_field( studiohdr_t, numattachments )
|
||||||
|
le_struct_field( studiohdr_t, attachmentindex )
|
||||||
|
le_struct_field( studiohdr_t, studiohdr2index )
|
||||||
|
le_struct_field( studiohdr_t, numtransitions )
|
||||||
|
le_struct_field( studiohdr_t, transitionindex )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( mstudiobone_swap )
|
||||||
|
le_struct_field( mstudiobone_t, parent )
|
||||||
|
le_struct_field( mstudiobone_t, unused )
|
||||||
|
le_struct_array( mstudiobone_t, bonecontroller, 6 )
|
||||||
|
le_struct_array( mstudiobone_t, value, 6 )
|
||||||
|
le_struct_array( mstudiobone_t, scale, 6 )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( mstudiobonecontroller_swap )
|
||||||
|
le_struct_field( mstudiobonecontroller_t, bone )
|
||||||
|
le_struct_field( mstudiobonecontroller_t, type )
|
||||||
|
le_struct_field( mstudiobonecontroller_t, start )
|
||||||
|
le_struct_field( mstudiobonecontroller_t, end )
|
||||||
|
le_struct_field( mstudiobonecontroller_t, unused )
|
||||||
|
le_struct_field( mstudiobonecontroller_t, index )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( mstudiobbox_swap )
|
||||||
|
le_struct_field( mstudiobbox_t, bone )
|
||||||
|
le_struct_field( mstudiobbox_t, group )
|
||||||
|
le_struct_array( mstudiobbox_t, bbmin, 3 )
|
||||||
|
le_struct_array( mstudiobbox_t, bbmax, 3 )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( mstudioseqgroup_swap )
|
||||||
|
// should we even swap seqgroup?
|
||||||
|
le_struct_field( mstudioseqgroup_t, unused )
|
||||||
|
le_struct_field( mstudioseqgroup_t, unused2 )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( mstudioseqdesc_swap )
|
||||||
|
le_struct_field( mstudioseqdesc_t, fps )
|
||||||
|
le_struct_field( mstudioseqdesc_t, flags )
|
||||||
|
le_struct_field( mstudioseqdesc_t, activity )
|
||||||
|
le_struct_field( mstudioseqdesc_t, actweight )
|
||||||
|
le_struct_field( mstudioseqdesc_t, numevents )
|
||||||
|
le_struct_field( mstudioseqdesc_t, eventindex )
|
||||||
|
le_struct_field( mstudioseqdesc_t, numframes )
|
||||||
|
le_struct_field( mstudioseqdesc_t, weightlistindex )
|
||||||
|
le_struct_field( mstudioseqdesc_t, iklockindex )
|
||||||
|
le_struct_field( mstudioseqdesc_t, motiontype )
|
||||||
|
le_struct_field( mstudioseqdesc_t, motionbone )
|
||||||
|
le_struct_array( mstudioseqdesc_t, linearmovement, 3 )
|
||||||
|
le_struct_field( mstudioseqdesc_t, autolayerindex )
|
||||||
|
le_struct_field( mstudioseqdesc_t, keyvalueindex )
|
||||||
|
le_struct_array( mstudioseqdesc_t, bbmin, 3 )
|
||||||
|
le_struct_array( mstudioseqdesc_t, bbmax, 3 )
|
||||||
|
le_struct_field( mstudioseqdesc_t, numblends )
|
||||||
|
le_struct_field( mstudioseqdesc_t, animindex )
|
||||||
|
le_struct_array( mstudioseqdesc_t, blendtype, 2 )
|
||||||
|
le_struct_array( mstudioseqdesc_t, blendstart, 2 )
|
||||||
|
le_struct_array( mstudioseqdesc_t, blendend, 2 )
|
||||||
|
le_struct_field( mstudioseqdesc_t, seqgroup )
|
||||||
|
le_struct_field( mstudioseqdesc_t, entrynode )
|
||||||
|
le_struct_field( mstudioseqdesc_t, exitnode )
|
||||||
|
le_struct_field( mstudioseqdesc_t, animdescindex )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( mstudioevent_swap )
|
||||||
|
le_struct_field( mstudioevent_t, frame )
|
||||||
|
le_struct_field( mstudioevent_t, event )
|
||||||
|
le_struct_field( mstudioevent_t, unused )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( mstudioattachment_swap )
|
||||||
|
le_struct_field( mstudioattachment_t, flags )
|
||||||
|
le_struct_field( mstudioattachment_t, bone )
|
||||||
|
le_struct_array( mstudioattachment_t, org, 3 )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( mstudiobodyparts_swap )
|
||||||
|
le_struct_field( mstudiobodyparts_t, nummodels )
|
||||||
|
le_struct_field( mstudiobodyparts_t, base )
|
||||||
|
le_struct_field( mstudiobodyparts_t, modelindex )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( mstudiotexture_swap )
|
||||||
|
le_struct_field( mstudiotexture_t, flags )
|
||||||
|
le_struct_field( mstudiotexture_t, width )
|
||||||
|
le_struct_field( mstudiotexture_t, height )
|
||||||
|
le_struct_field( mstudiotexture_t, index )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( mstudiomodel_swap )
|
||||||
|
le_struct_field( mstudiomodel_t, unused )
|
||||||
|
le_struct_field( mstudiomodel_t, unused2 )
|
||||||
|
le_struct_field( mstudiomodel_t, nummesh )
|
||||||
|
le_struct_field( mstudiomodel_t, meshindex )
|
||||||
|
le_struct_field( mstudiomodel_t, numverts )
|
||||||
|
le_struct_field( mstudiomodel_t, vertinfoindex )
|
||||||
|
le_struct_field( mstudiomodel_t, vertindex )
|
||||||
|
le_struct_field( mstudiomodel_t, numnorms )
|
||||||
|
le_struct_field( mstudiomodel_t, norminfoindex )
|
||||||
|
le_struct_field( mstudiomodel_t, normindex )
|
||||||
|
le_struct_field( mstudiomodel_t, blendvertinfoindex )
|
||||||
|
le_struct_field( mstudiomodel_t, blendnorminfoindex )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( mstudiomesh_swap )
|
||||||
|
le_struct_field( mstudiomesh_t, numtris )
|
||||||
|
le_struct_field( mstudiomesh_t, triindex )
|
||||||
|
le_struct_field( mstudiomesh_t, skinref )
|
||||||
|
le_struct_field( mstudiomesh_t, numnorms )
|
||||||
|
le_struct_field( mstudiomesh_t, unused )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
|
le_struct_begin( mstudioanim_swap )
|
||||||
|
le_struct_array( mstudioanim_t, offset, 6 )
|
||||||
|
le_struct_end();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
====================
|
====================
|
||||||
Mod_InitStudioHull
|
Mod_InitStudioHull
|
||||||
@@ -404,6 +550,38 @@ static void Mod_StudioCalcRotations( int boneused[], int numbones, const byte *p
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Mod_SwapStudioSeqGroupAnims( studiohdr_t *phdr, mstudioseqdesc_t *pseq, byte *buf )
|
||||||
|
{
|
||||||
|
mstudioanim_t *panim = (mstudioanim_t *)( buf + pseq->animindex );
|
||||||
|
int numanims = pseq->numblends * phdr->numbones;
|
||||||
|
|
||||||
|
for( int j = 0; j < numanims; j++, panim++ )
|
||||||
|
{
|
||||||
|
le_struct_swap( mstudioanim_swap, panim );
|
||||||
|
|
||||||
|
for( int k = 0; k < 6; k++ )
|
||||||
|
{
|
||||||
|
if( panim->offset[k] == 0 )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
mstudioanimvalue_t *panimvalue = (mstudioanimvalue_t *)((byte *)panim + panim->offset[k] );
|
||||||
|
|
||||||
|
int frames = pseq->numframes;
|
||||||
|
while( frames > 0 )
|
||||||
|
{
|
||||||
|
int valid = panimvalue->num.valid;
|
||||||
|
int total = panimvalue->num.total;
|
||||||
|
|
||||||
|
for( int l = 1; l <= valid; l++ )
|
||||||
|
panimvalue[l].value = LittleShort( panimvalue[l].value );
|
||||||
|
|
||||||
|
panimvalue += valid + 1;
|
||||||
|
frames -= total;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
====================
|
====================
|
||||||
StudioGetAnim
|
StudioGetAnim
|
||||||
@@ -412,17 +590,10 @@ StudioGetAnim
|
|||||||
*/
|
*/
|
||||||
void *R_StudioGetAnim( studiohdr_t *m_pStudioHeader, model_t *m_pSubModel, mstudioseqdesc_t *pseqdesc )
|
void *R_StudioGetAnim( studiohdr_t *m_pStudioHeader, model_t *m_pSubModel, mstudioseqdesc_t *pseqdesc )
|
||||||
{
|
{
|
||||||
mstudioseqgroup_t *pseqgroup;
|
|
||||||
cache_user_t *paSequences;
|
|
||||||
fs_offset_t filesize;
|
|
||||||
byte *buf;
|
|
||||||
|
|
||||||
pseqgroup = (mstudioseqgroup_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqgroupindex) + pseqdesc->seqgroup;
|
|
||||||
if( pseqdesc->seqgroup == 0 )
|
if( pseqdesc->seqgroup == 0 )
|
||||||
return ((byte *)m_pStudioHeader + pseqdesc->animindex);
|
return ((byte *)m_pStudioHeader + pseqdesc->animindex);
|
||||||
|
|
||||||
paSequences = (cache_user_t *)m_pSubModel->submodels;
|
cache_user_t *paSequences = (cache_user_t *)m_pSubModel->submodels;
|
||||||
|
|
||||||
if( paSequences == NULL )
|
if( paSequences == NULL )
|
||||||
{
|
{
|
||||||
paSequences = (cache_user_t *)Mem_Calloc( com_studiocache, MAXSTUDIOGROUPS * sizeof( cache_user_t ));
|
paSequences = (cache_user_t *)Mem_Calloc( com_studiocache, MAXSTUDIOGROUPS * sizeof( cache_user_t ));
|
||||||
@@ -432,23 +603,31 @@ void *R_StudioGetAnim( studiohdr_t *m_pStudioHeader, model_t *m_pSubModel, mstud
|
|||||||
// check for already loaded
|
// check for already loaded
|
||||||
if( !Mod_CacheCheck(( cache_user_t *)&( paSequences[pseqdesc->seqgroup] )))
|
if( !Mod_CacheCheck(( cache_user_t *)&( paSequences[pseqdesc->seqgroup] )))
|
||||||
{
|
{
|
||||||
string filepath, modelname, modelpath;
|
string modelname;
|
||||||
|
|
||||||
COM_FileBase( m_pSubModel->name, modelname, sizeof( modelname ));
|
COM_FileBase( m_pSubModel->name, modelname, sizeof( modelname ));
|
||||||
|
|
||||||
|
string modelpath;
|
||||||
COM_ExtractFilePath( m_pSubModel->name, modelpath );
|
COM_ExtractFilePath( m_pSubModel->name, modelpath );
|
||||||
|
|
||||||
// NOTE: here we build real sub-animation filename because stupid user may rename model without recompile
|
// NOTE: here we build real sub-animation filename because stupid user may rename model without recompile
|
||||||
|
string filepath;
|
||||||
Q_snprintf( filepath, sizeof( filepath ), "%s/%s%i%i.mdl", modelpath, modelname, pseqdesc->seqgroup / 10, pseqdesc->seqgroup % 10 );
|
Q_snprintf( filepath, sizeof( filepath ), "%s/%s%i%i.mdl", modelpath, modelname, pseqdesc->seqgroup / 10, pseqdesc->seqgroup % 10 );
|
||||||
|
|
||||||
buf = FS_LoadFile( filepath, &filesize, false );
|
fs_offset_t filesize;
|
||||||
if( !buf || !filesize ) Host_Error( "%s: can't load %s\n", __func__, filepath );
|
byte *buf = FS_LoadFile( filepath, &filesize, false );
|
||||||
if( IDSEQGRPHEADER != *(uint *)buf ) Host_Error( "%s: %s is corrupted\n", __func__, filepath );
|
if( !buf || !filesize )
|
||||||
|
Host_Error( "%s: can't load %s\n", __func__, filepath );
|
||||||
|
if( LittleLong( IDSEQGRPHEADER ) != *(uint *)buf )
|
||||||
|
Host_Error( "%s: %s is corrupted\n", __func__, filepath );
|
||||||
|
|
||||||
Con_Printf( "loading: %s\n", filepath );
|
Con_Printf( "loading: %s\n", filepath );
|
||||||
|
|
||||||
paSequences[pseqdesc->seqgroup].data = Mem_Calloc( com_studiocache, filesize );
|
paSequences[pseqdesc->seqgroup].data = Mem_Calloc( com_studiocache, filesize );
|
||||||
memcpy( paSequences[pseqdesc->seqgroup].data, buf, filesize );
|
memcpy( paSequences[pseqdesc->seqgroup].data, buf, filesize );
|
||||||
Mem_Free( buf );
|
Mem_Free( buf );
|
||||||
|
|
||||||
|
Mod_SwapStudioSeqGroupAnims( m_pStudioHeader, pseqdesc,
|
||||||
|
(byte *)paSequences[pseqdesc->seqgroup].data );
|
||||||
}
|
}
|
||||||
|
|
||||||
return ((byte *)paSequences[pseqdesc->seqgroup].data + pseqdesc->animindex);
|
return ((byte *)paSequences[pseqdesc->seqgroup].data + pseqdesc->animindex);
|
||||||
@@ -690,7 +869,6 @@ static void Mod_StudioComputeBounds( void *buffer, vec3_t mins, vec3_t maxs, qbo
|
|||||||
vec3_t bone_mins, bone_maxs;
|
vec3_t bone_mins, bone_maxs;
|
||||||
vec3_t vert_mins, vert_maxs;
|
vec3_t vert_mins, vert_maxs;
|
||||||
int vert_count, bone_count;
|
int vert_count, bone_count;
|
||||||
int bodyCount = 0;
|
|
||||||
vec3_t pos, *pverts;
|
vec3_t pos, *pverts;
|
||||||
|
|
||||||
vert_count = bone_count = 0;
|
vert_count = bone_count = 0;
|
||||||
@@ -706,18 +884,16 @@ static void Mod_StudioComputeBounds( void *buffer, vec3_t mins, vec3_t maxs, qbo
|
|||||||
// each body part has nummodels variations so there are as many total variations as there
|
// each body part has nummodels variations so there are as many total variations as there
|
||||||
// are in a matrix of each part by each other part
|
// are in a matrix of each part by each other part
|
||||||
for( i = 0; i < pstudiohdr->numbodyparts; i++ )
|
for( i = 0; i < pstudiohdr->numbodyparts; i++ )
|
||||||
bodyCount += pbodypart[i].nummodels;
|
|
||||||
|
|
||||||
// The studio models we want are vec3_t mins, vec3_t maxsight after the bodyparts (still need to
|
|
||||||
// find a detailed breakdown of the mdl format). Move pointer there.
|
|
||||||
m_pSubModel = (mstudiomodel_t *)(&pbodypart[pstudiohdr->numbodyparts]);
|
|
||||||
|
|
||||||
for( i = 0; i < bodyCount; i++ )
|
|
||||||
{
|
{
|
||||||
pverts = (vec3_t *)((byte *)pstudiohdr + m_pSubModel[i].vertindex);
|
m_pSubModel = (mstudiomodel_t *)((byte *)pstudiohdr + pbodypart[i].modelindex);
|
||||||
|
|
||||||
for( j = 0; j < m_pSubModel[i].numverts; j++ )
|
for( j = 0; j < pbodypart[i].nummodels; j++ )
|
||||||
Mod_StudioBoundVertex( bone_mins, bone_maxs, &vert_count, pverts[j] );
|
{
|
||||||
|
pverts = (vec3_t *)((byte *)pstudiohdr + m_pSubModel[j].vertindex);
|
||||||
|
|
||||||
|
for( k = 0; k < m_pSubModel[j].numverts; k++ )
|
||||||
|
Mod_StudioBoundVertex( bone_mins, bone_maxs, &vert_count, pverts[k] );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pbones = (mstudiobone_t *)((byte *)pstudiohdr + pstudiohdr->boneindex);
|
pbones = (mstudiobone_t *)((byte *)pstudiohdr + pstudiohdr->boneindex);
|
||||||
@@ -820,26 +996,120 @@ static int Mod_StudioBodyVariations( model_t *mod )
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static qboolean Mod_SwapStudioModel( const char *name, void *buffer, size_t buffersize )
|
||||||
|
{
|
||||||
|
studiohdr_t *phdr = buffer;
|
||||||
|
byte *mod_base = buffer;
|
||||||
|
|
||||||
|
if( buffersize < sizeof( studiohdr_t ))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
le_struct_swap( studiohdr_swap, phdr );
|
||||||
|
|
||||||
|
if( phdr->ident != IDSTUDIOHEADER || phdr->version != STUDIO_VERSION )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
#if XASH_BIG_ENDIAN
|
||||||
|
if( phdr->studiohdr2index > 0 && phdr->studiohdr2index < phdr->length )
|
||||||
|
{
|
||||||
|
Con_Printf( S_ERROR "byteswapping extended studio model \"%s\" is unsupoprted\n", name );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
for( int i = 0; i < phdr->numbones; i++ )
|
||||||
|
le_struct_swap( mstudiobone_swap, (mstudiobone_t *)( mod_base + phdr->boneindex ) + i );
|
||||||
|
|
||||||
|
for( int i = 0; i < phdr->numbonecontrollers; i++ )
|
||||||
|
le_struct_swap( mstudiobonecontroller_swap, (mstudiobonecontroller_t *)( mod_base + phdr->bonecontrollerindex ) + i );
|
||||||
|
|
||||||
|
for( int i = 0; i < phdr->numhitboxes; i++ )
|
||||||
|
le_struct_swap( mstudiobbox_swap, (mstudiobbox_t *)( mod_base + phdr->hitboxindex ) + i );
|
||||||
|
|
||||||
|
for( int i = 0; i < phdr->numseqgroups; i++ )
|
||||||
|
le_struct_swap( mstudioseqgroup_swap, (mstudioseqgroup_t *)( mod_base + phdr->seqgroupindex ) + i );
|
||||||
|
|
||||||
|
for( int i = 0; i < phdr->numattachments; i++ )
|
||||||
|
{
|
||||||
|
mstudioattachment_t *pattach = (mstudioattachment_t *)( mod_base + phdr->attachmentindex ) + i;
|
||||||
|
le_struct_swap( mstudioattachment_swap, pattach );
|
||||||
|
le_array_swap((float *)pattach->vectors, 3 * 3 );
|
||||||
|
}
|
||||||
|
|
||||||
|
for( int i = 0; i < phdr->numtextures; i++ )
|
||||||
|
le_struct_swap( mstudiotexture_swap, (mstudiotexture_t *)( mod_base + phdr->textureindex ) + i );
|
||||||
|
|
||||||
|
for( int i = 0; i < phdr->numbodyparts; i++ )
|
||||||
|
le_struct_swap( mstudiobodyparts_swap, (mstudiobodyparts_t *)( mod_base + phdr->bodypartindex ) + i );
|
||||||
|
|
||||||
|
for( int i = 0; i < phdr->numseq; i++ )
|
||||||
|
{
|
||||||
|
mstudioseqdesc_t *pseq = (mstudioseqdesc_t *)( mod_base + phdr->seqindex ) + i;
|
||||||
|
le_struct_swap( mstudioseqdesc_swap, pseq );
|
||||||
|
|
||||||
|
for( int j = 0; j < pseq->numevents; j++ )
|
||||||
|
le_struct_swap( mstudioevent_swap, (mstudioevent_t *)( mod_base + pseq->eventindex ) + j );
|
||||||
|
|
||||||
|
if( pseq->seqgroup != 0 )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Mod_SwapStudioSeqGroupAnims( phdr, pseq, mod_base );
|
||||||
|
}
|
||||||
|
|
||||||
|
for( int i = 0; i < phdr->numbodyparts; i++ )
|
||||||
|
{
|
||||||
|
mstudiobodyparts_t *pbodypart = (mstudiobodyparts_t *)( mod_base + phdr->bodypartindex ) + i;
|
||||||
|
|
||||||
|
for( int j = 0; j < pbodypart->nummodels; j++ )
|
||||||
|
{
|
||||||
|
mstudiomodel_t *pmodel = (mstudiomodel_t *)( mod_base + pbodypart->modelindex ) + j;
|
||||||
|
le_struct_swap( mstudiomodel_swap, pmodel );
|
||||||
|
|
||||||
|
le_array_swap((float *)( mod_base + pmodel->vertindex ), pmodel->numverts * 3 );
|
||||||
|
le_array_swap((float *)( mod_base + pmodel->normindex ), pmodel->numnorms * 3 );
|
||||||
|
|
||||||
|
for( int k = 0; k < pmodel->nummesh; k++ )
|
||||||
|
{
|
||||||
|
mstudiomesh_t *pmesh = (mstudiomesh_t *)( mod_base + pmodel->meshindex ) + k;
|
||||||
|
le_struct_swap( mstudiomesh_swap, pmesh );
|
||||||
|
|
||||||
|
short *ptricmds = (short *)( mod_base + pmesh->triindex );
|
||||||
|
short n;
|
||||||
|
while(( n = LittleShort( *ptricmds )))
|
||||||
|
{
|
||||||
|
*ptricmds++ = n;
|
||||||
|
int count = abs( n ) * 4;
|
||||||
|
for( int l = 0; l < count; l++, ptricmds++ )
|
||||||
|
*ptricmds = LittleShort( *ptricmds );
|
||||||
|
}
|
||||||
|
*ptricmds = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
short *pskinref = (short *)( mod_base + phdr->skinindex );
|
||||||
|
for( int i = 0; i < phdr->numskinfamilies * phdr->numskinref; i++ )
|
||||||
|
pskinref[i] = LittleShort( pskinref[i] );
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
=================
|
=================
|
||||||
R_StudioLoadHeader
|
R_StudioLoadHeader
|
||||||
=================
|
=================
|
||||||
*/
|
*/
|
||||||
static studiohdr_t *R_StudioLoadHeader( model_t *mod, const void *buffer )
|
static studiohdr_t *R_StudioLoadHeader( model_t *mod, void *buffer, size_t buffersize )
|
||||||
{
|
{
|
||||||
byte *pin;
|
studiohdr_t *phdr;
|
||||||
studiohdr_t *phdr;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
if( !buffer ) return NULL;
|
if( !buffer )
|
||||||
|
return NULL;
|
||||||
|
|
||||||
pin = (byte *)buffer;
|
if( !Mod_SwapStudioModel( mod->name, buffer, buffersize ))
|
||||||
phdr = (studiohdr_t *)pin;
|
|
||||||
i = phdr->version;
|
|
||||||
|
|
||||||
if( i != STUDIO_VERSION )
|
|
||||||
{
|
{
|
||||||
Con_Printf( S_ERROR "%s has wrong version number (%i should be %i)\n", mod->name, i, STUDIO_VERSION );
|
phdr = (studiohdr_t *)buffer;
|
||||||
|
Con_Printf( S_ERROR "%s has wrong version number (%i should be %i)\n", mod->name, phdr->version, STUDIO_VERSION );
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -867,7 +1137,7 @@ static studiohdr_t *Mod_MaybeTruncateStudioTextureData( model_t *mod )
|
|||||||
Mod_LoadStudioModel
|
Mod_LoadStudioModel
|
||||||
=================
|
=================
|
||||||
*/
|
*/
|
||||||
void Mod_LoadStudioModel( model_t *mod, const void *buffer, qboolean *loaded )
|
void Mod_LoadStudioModel( model_t *mod, void *buffer, size_t buffersize, qboolean *loaded )
|
||||||
{
|
{
|
||||||
char poolname[MAX_VA_STRING];
|
char poolname[MAX_VA_STRING];
|
||||||
studiohdr_t *phdr;
|
studiohdr_t *phdr;
|
||||||
@@ -879,7 +1149,7 @@ void Mod_LoadStudioModel( model_t *mod, const void *buffer, qboolean *loaded )
|
|||||||
mod->mempool = Mem_AllocPool( poolname );
|
mod->mempool = Mem_AllocPool( poolname );
|
||||||
mod->type = mod_studio;
|
mod->type = mod_studio;
|
||||||
|
|
||||||
phdr = R_StudioLoadHeader( mod, buffer );
|
phdr = R_StudioLoadHeader( mod, buffer, buffersize );
|
||||||
if( !phdr || phdr->length < sizeof( studiohdr_t )) // garbage value in length
|
if( !phdr || phdr->length < sizeof( studiohdr_t )) // garbage value in length
|
||||||
return; // bad model
|
return; // bad model
|
||||||
|
|
||||||
@@ -888,9 +1158,10 @@ void Mod_LoadStudioModel( model_t *mod, const void *buffer, qboolean *loaded )
|
|||||||
{
|
{
|
||||||
studiohdr_t *thdr;
|
studiohdr_t *thdr;
|
||||||
void *buffer2;
|
void *buffer2;
|
||||||
|
fs_offset_t size2_len;
|
||||||
|
|
||||||
buffer2 = FS_LoadFile( Mod_StudioTexName( mod->name ), NULL, false );
|
buffer2 = FS_LoadFile( Mod_StudioTexName( mod->name ), &size2_len, false );
|
||||||
thdr = R_StudioLoadHeader( mod, buffer2 );
|
thdr = R_StudioLoadHeader( mod, buffer2, size2_len );
|
||||||
|
|
||||||
if( thdr != NULL )
|
if( thdr != NULL )
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -347,7 +347,7 @@ model_t *Mod_LoadModel( model_t *mod, qboolean crash )
|
|||||||
switch( *(uint *)buf )
|
switch( *(uint *)buf )
|
||||||
{
|
{
|
||||||
case LittleLong( IDSTUDIOHEADER ):
|
case LittleLong( IDSTUDIOHEADER ):
|
||||||
Mod_LoadStudioModel( mod, buf, &loaded );
|
Mod_LoadStudioModel( mod, buf, length, &loaded );
|
||||||
break;
|
break;
|
||||||
case LittleLong( IDSPRITEHEADER ):
|
case LittleLong( IDSPRITEHEADER ):
|
||||||
Mod_LoadSpriteModel( mod, buf, length, &loaded );
|
Mod_LoadSpriteModel( mod, buf, length, &loaded );
|
||||||
@@ -608,6 +608,31 @@ void GAME_EXPORT Mod_LoadCacheFile( const char *filename, cache_user_t *cu )
|
|||||||
cu->data = Mem_Malloc( com_studiocache, size );
|
cu->data = Mem_Malloc( com_studiocache, size );
|
||||||
memcpy( cu->data, buf, size );
|
memcpy( cu->data, buf, size );
|
||||||
Mem_Free( buf );
|
Mem_Free( buf );
|
||||||
|
|
||||||
|
// this handles when studio model renderer tries to load sequence files on it's own
|
||||||
|
// which is what they always do in HLSDK
|
||||||
|
#if XASH_BIG_ENDIAN
|
||||||
|
if( size >= sizeof( int ) && LittleLong( IDSEQGRPHEADER ) == *(uint *)cu->data )
|
||||||
|
{
|
||||||
|
studiohdr_t *phdr = (studiohdr_t *)REF_GET_PARM( PARM_GET_STUDIO_HDR, 0 );
|
||||||
|
if( !phdr )
|
||||||
|
return;
|
||||||
|
|
||||||
|
mstudioseqdesc_t *pseq = (mstudioseqdesc_t *)((byte *)phdr + phdr->seqindex );
|
||||||
|
|
||||||
|
for( int i = 0; i < phdr->numseq; i++ )
|
||||||
|
{
|
||||||
|
if( pseq[i].seqgroup == 0 )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
mstudioseqgroup_t *pgrp = (mstudioseqgroup_t *)((byte *)phdr + phdr->seqgroupindex ) + pseq[i].seqgroup;
|
||||||
|
|
||||||
|
// assuming filename passes seqgroup's name
|
||||||
|
if( !Q_stricmp( pgrp->name, filename ))
|
||||||
|
Mod_SwapStudioSeqGroupAnims( phdr, &pseq[i], (byte *)cu->data );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -321,8 +321,10 @@ void MSG_WriteUBitLong( sizebuf_t *sb, uint curData, int numbits )
|
|||||||
}
|
}
|
||||||
|
|
||||||
iCurBitMasked = iCurBit & 31;
|
iCurBitMasked = iCurBit & 31;
|
||||||
((uint32_t *)sb->pData)[iDWord] &= BitWriteMasks[iCurBitMasked][nBitsLeft-1];
|
uint32_t dword = LittleLong(((uint32_t *)sb->pData)[iDWord] );
|
||||||
((uint32_t *)sb->pData)[iDWord] |= curData << iCurBitMasked;
|
dword &= BitWriteMasks[iCurBitMasked][nBitsLeft-1];
|
||||||
|
dword |= curData << iCurBitMasked;
|
||||||
|
((uint32_t *)sb->pData)[iDWord] = LittleLong( dword );
|
||||||
|
|
||||||
// did it span a dword?
|
// did it span a dword?
|
||||||
nBitsWritten = 32 - iCurBitMasked;
|
nBitsWritten = 32 - iCurBitMasked;
|
||||||
@@ -334,8 +336,10 @@ void MSG_WriteUBitLong( sizebuf_t *sb, uint curData, int numbits )
|
|||||||
curData >>= nBitsWritten;
|
curData >>= nBitsWritten;
|
||||||
|
|
||||||
iCurBitMasked = iCurBit & 31;
|
iCurBitMasked = iCurBit & 31;
|
||||||
((uint32_t *)sb->pData)[iDWord+1] &= BitWriteMasks[iCurBitMasked][nBitsLeft-1];
|
uint32_t dword2 = LittleLong(((uint32_t *)sb->pData)[iDWord+1] );
|
||||||
((uint32_t *)sb->pData)[iDWord+1] |= curData << iCurBitMasked;
|
dword2 &= BitWriteMasks[iCurBitMasked][nBitsLeft-1];
|
||||||
|
dword2 |= curData << iCurBitMasked;
|
||||||
|
((uint32_t *)sb->pData)[iDWord+1] = LittleLong( dword2 );
|
||||||
}
|
}
|
||||||
sb->iCurBit += numbits;
|
sb->iCurBit += numbits;
|
||||||
}
|
}
|
||||||
@@ -396,7 +400,8 @@ qboolean MSG_WriteBits( sizebuf_t *sb, const void *pData, int nBits )
|
|||||||
// read dwords.
|
// read dwords.
|
||||||
while( nBitsLeft >= 32 )
|
while( nBitsLeft >= 32 )
|
||||||
{
|
{
|
||||||
MSG_WriteUBitLong( sb, *(( uint32_t *)pOut ), 32 );
|
uint32_t dword = *((uint32_t *)pOut);
|
||||||
|
MSG_WriteUBitLong( sb, LittleLong( dword ), 32 );
|
||||||
|
|
||||||
pOut += sizeof( uint32_t );
|
pOut += sizeof( uint32_t );
|
||||||
nBitsLeft -= 32;
|
nBitsLeft -= 32;
|
||||||
@@ -519,7 +524,7 @@ void MSG_WriteDword( sizebuf_t *sb, uint val )
|
|||||||
|
|
||||||
void MSG_WriteFloat( sizebuf_t *sb, float val )
|
void MSG_WriteFloat( sizebuf_t *sb, float val )
|
||||||
{
|
{
|
||||||
MSG_WriteBits( sb, &val, sizeof( val ) << 3 );
|
MSG_WriteUBitLong( sb, FloatAsUint( val ), sizeof( val ) << 3 );
|
||||||
}
|
}
|
||||||
|
|
||||||
qboolean MSG_WriteBytes( sizebuf_t *sb, const void *pBuf, int nBytes )
|
qboolean MSG_WriteBytes( sizebuf_t *sb, const void *pBuf, int nBytes )
|
||||||
@@ -593,7 +598,7 @@ uint MSG_ReadUBitLong( sizebuf_t *sb, int numbits )
|
|||||||
|
|
||||||
// Read the current dword.
|
// Read the current dword.
|
||||||
idword1 = sb->iCurBit >> 5;
|
idword1 = sb->iCurBit >> 5;
|
||||||
dword1 = ((uint *)sb->pData)[idword1];
|
dword1 = LittleLong(((uint *)sb->pData)[idword1] );
|
||||||
dword1 >>= ( sb->iCurBit & 31 ); // get the bits we're interested in.
|
dword1 >>= ( sb->iCurBit & 31 ); // get the bits we're interested in.
|
||||||
|
|
||||||
sb->iCurBit += numbits;
|
sb->iCurBit += numbits;
|
||||||
@@ -608,7 +613,7 @@ uint MSG_ReadUBitLong( sizebuf_t *sb, int numbits )
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
int nExtraBits = sb->iCurBit & 31;
|
int nExtraBits = sb->iCurBit & 31;
|
||||||
uint dword2 = ((uint *)sb->pData)[idword1+1] & ExtraMasks[nExtraBits];
|
uint dword2 = LittleLong(((uint *)sb->pData)[idword1+1] ) & ExtraMasks[nExtraBits];
|
||||||
|
|
||||||
// no need to mask since we hit the end of the dword.
|
// no need to mask since we hit the end of the dword.
|
||||||
// shift the second dword's part into the high bits.
|
// shift the second dword's part into the high bits.
|
||||||
@@ -633,7 +638,8 @@ qboolean MSG_ReadBits( sizebuf_t *sb, void *pOutData, int nBits )
|
|||||||
// read dwords.
|
// read dwords.
|
||||||
while( nBitsLeft >= 32 )
|
while( nBitsLeft >= 32 )
|
||||||
{
|
{
|
||||||
*((uint32_t *)pOut) = MSG_ReadUBitLong( sb, 32 );
|
uint32_t dword = MSG_ReadUBitLong( sb, 32 );
|
||||||
|
*((uint32_t *)pOut) = LittleLong( dword );
|
||||||
pOut += sizeof( uint32_t );
|
pOut += sizeof( uint32_t );
|
||||||
nBitsLeft -= 32;
|
nBitsLeft -= 32;
|
||||||
}
|
}
|
||||||
@@ -790,11 +796,7 @@ uint MSG_ReadDword( sizebuf_t *sb )
|
|||||||
|
|
||||||
float MSG_ReadFloat( sizebuf_t *sb )
|
float MSG_ReadFloat( sizebuf_t *sb )
|
||||||
{
|
{
|
||||||
float ret;
|
return UintAsFloat( MSG_ReadUBitLong( sb, sizeof( float ) << 3 ));
|
||||||
|
|
||||||
MSG_ReadBits( sb, &ret, sizeof( ret ) << 3 );
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
qboolean MSG_ReadBytes( sizebuf_t *sb, void *pOut, int nBytes )
|
qboolean MSG_ReadBytes( sizebuf_t *sb, void *pOut, int nBytes )
|
||||||
|
|||||||
@@ -617,7 +617,10 @@ void Netchan_AddBufferToList( fragbuf_t **pplist, fragbuf_t *pbuf )
|
|||||||
if( id1 > id2 )
|
if( id1 > id2 )
|
||||||
{
|
{
|
||||||
// insert here
|
// insert here
|
||||||
pbuf->next = n->next;
|
// pbuf->next = n->next;
|
||||||
|
|
||||||
|
// a1ba: this seems to be incorrect insertion, as `n` gets removed from list and effectively leaked
|
||||||
|
pbuf->next = n;
|
||||||
pprev->next = pbuf;
|
pprev->next = pbuf;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1237,20 +1240,24 @@ qboolean Netchan_CopyFileFragments( netchan_t *chan, sizebuf_t *msg )
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if( filename[0] != '!' )
|
|
||||||
{
|
|
||||||
string temp_filename;
|
|
||||||
Q_snprintf( temp_filename, sizeof( temp_filename ), DEFAULT_DOWNLOADED_DIRECTORY "%s", filename );
|
|
||||||
Q_strncpy( filename, temp_filename, sizeof( filename ));
|
|
||||||
}
|
|
||||||
|
|
||||||
Q_strncpy( chan->incomingfilename, filename, sizeof( chan->incomingfilename ));
|
Q_strncpy( chan->incomingfilename, filename, sizeof( chan->incomingfilename ));
|
||||||
|
|
||||||
if( filename[0] != '!' && FS_FileExists( filename, false ))
|
if( filename[0] != '!' )
|
||||||
{
|
{
|
||||||
Con_Printf( S_ERROR "can't download %s, already exists\n", filename );
|
string write_path;
|
||||||
Netchan_FlushIncoming( chan, FRAG_FILE_STREAM );
|
Q_snprintf( write_path, sizeof( write_path ), "../%s" DEFAULT_DOWNLOADED_DIRECTORY_SUFFIX "/%s", GI->gamefolder, filename );
|
||||||
return true;
|
Q_strncpy( filename, write_path, sizeof( filename ));
|
||||||
|
|
||||||
|
FS_AllowDirectPaths( true );
|
||||||
|
qboolean exists = FS_FileExists( filename, false );
|
||||||
|
FS_AllowDirectPaths( false );
|
||||||
|
|
||||||
|
if( exists )
|
||||||
|
{
|
||||||
|
Con_Printf( S_ERROR "can't download %s, already exists\n", filename );
|
||||||
|
Netchan_FlushIncoming( chan, FRAG_FILE_STREAM );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// create file from buffers
|
// create file from buffers
|
||||||
@@ -1314,14 +1321,14 @@ qboolean Netchan_CopyFileFragments( netchan_t *chan, sizebuf_t *msg )
|
|||||||
Host_Error( "%s: BZ2 compression is not supported for server", __func__ );
|
Host_Error( "%s: BZ2 compression is not supported for server", __func__ );
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
else if( chan->use_lzss && LZSS_IsCompressed( buffer, nsize + 1 ))
|
else if( chan->use_lzss && LZSS_IsCompressed( buffer, nsize ))
|
||||||
{
|
{
|
||||||
byte *uncompressedBuffer;
|
byte *uncompressedBuffer;
|
||||||
|
|
||||||
uncompressedSize = LZSS_GetActualSize( buffer, nsize + 1 ) + 1;
|
uncompressedSize = LZSS_GetActualSize( buffer, nsize );
|
||||||
uncompressedBuffer = Mem_Calloc( net_mempool, uncompressedSize );
|
uncompressedBuffer = Mem_Calloc( net_mempool, uncompressedSize );
|
||||||
|
|
||||||
nsize = LZSS_Decompress( buffer, uncompressedBuffer, nsize + 1, uncompressedSize );
|
nsize = LZSS_Decompress( buffer, uncompressedBuffer, nsize, uncompressedSize );
|
||||||
Mem_Free( buffer );
|
Mem_Free( buffer );
|
||||||
buffer = uncompressedBuffer;
|
buffer = uncompressedBuffer;
|
||||||
}
|
}
|
||||||
@@ -1336,8 +1343,9 @@ qboolean Netchan_CopyFileFragments( netchan_t *chan, sizebuf_t *msg )
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// g-cont. it's will be stored downloaded files directly into game folder
|
FS_AllowDirectPaths( true );
|
||||||
FS_WriteFile( filename, buffer, nsize );
|
FS_WriteFile( filename, buffer, nsize );
|
||||||
|
FS_AllowDirectPaths( false );
|
||||||
Mem_Free( buffer );
|
Mem_Free( buffer );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ typedef struct
|
|||||||
int sequence_number;
|
int sequence_number;
|
||||||
int ip_sockets[NS_COUNT];
|
int ip_sockets[NS_COUNT];
|
||||||
int ip6_sockets[NS_COUNT];
|
int ip6_sockets[NS_COUNT];
|
||||||
|
qboolean rr_state[NS_COUNT];
|
||||||
qboolean initialized;
|
qboolean initialized;
|
||||||
qboolean threads_initialized;
|
qboolean threads_initialized;
|
||||||
qboolean configured;
|
qboolean configured;
|
||||||
@@ -132,7 +133,7 @@ static CVAR_DEFINE_AUTO( net6_address, "0", FCVAR_PRIVILEGED|FCVAR_READ_ONLY, "c
|
|||||||
|
|
||||||
static void NET_ClearLagData( qboolean bClient, qboolean bServer );
|
static void NET_ClearLagData( qboolean bClient, qboolean bServer );
|
||||||
|
|
||||||
static inline qboolean NET_IsSocketError( int retval )
|
qboolean NET_IsSocketError( int retval )
|
||||||
{
|
{
|
||||||
#if XASH_WIN32 || XASH_DOS4GW
|
#if XASH_WIN32 || XASH_DOS4GW
|
||||||
return retval == SOCKET_ERROR ? true : false;
|
return retval == SOCKET_ERROR ? true : false;
|
||||||
@@ -141,7 +142,7 @@ static inline qboolean NET_IsSocketError( int retval )
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline qboolean NET_IsSocketValid( int socket )
|
qboolean NET_IsSocketValid( int socket )
|
||||||
{
|
{
|
||||||
#if XASH_WIN32 || XASH_DOS4GW
|
#if XASH_WIN32 || XASH_DOS4GW
|
||||||
return socket != INVALID_SOCKET;
|
return socket != INVALID_SOCKET;
|
||||||
@@ -150,6 +151,24 @@ static inline qboolean NET_IsSocketValid( int socket )
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qboolean NET_MakeSocketNonBlocking( int socket_fd )
|
||||||
|
{
|
||||||
|
#if XASH_LINUX
|
||||||
|
int res = fcntl( socket_fd, F_GETFL, 0 );
|
||||||
|
if( NET_IsSocketError( res ))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// SOCK_NONBLOCK is not portable, so use fcntl
|
||||||
|
if( NET_IsSocketError( fcntl( socket_fd, F_SETFL, res | O_NONBLOCK )))
|
||||||
|
return false;
|
||||||
|
#else
|
||||||
|
uint mode = 1;
|
||||||
|
if( NET_IsSocketError( ioctlsocket( socket_fd, FIONBIO, (void*)&mode )))
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void NET_NetadrToIP6Bytes( uint8_t *ip6, const netadr_t *adr )
|
void NET_NetadrToIP6Bytes( uint8_t *ip6, const netadr_t *adr )
|
||||||
{
|
{
|
||||||
memcpy( &ip6[0], adr->ip6_0, 2 );
|
memcpy( &ip6[0], adr->ip6_0, 2 );
|
||||||
@@ -177,7 +196,7 @@ static int NET_NetadrIP6Compare( const netadr_t *a, const netadr_t *b )
|
|||||||
NET_NetadrToSockadr
|
NET_NetadrToSockadr
|
||||||
====================
|
====================
|
||||||
*/
|
*/
|
||||||
static void NET_NetadrToSockadr( netadr_t *a, struct sockaddr_storage *s )
|
void NET_NetadrToSockadr( netadr_t *a, struct sockaddr_storage *s )
|
||||||
{
|
{
|
||||||
netadrtype_t type = NET_NetadrType( a );
|
netadrtype_t type = NET_NetadrType( a );
|
||||||
|
|
||||||
@@ -214,7 +233,7 @@ static void NET_NetadrToSockadr( netadr_t *a, struct sockaddr_storage *s )
|
|||||||
NET_SockadrToNetAdr
|
NET_SockadrToNetAdr
|
||||||
====================
|
====================
|
||||||
*/
|
*/
|
||||||
static void NET_SockadrToNetadr( const struct sockaddr_storage *s, netadr_t *a )
|
void NET_SockadrToNetadr( const struct sockaddr_storage *s, netadr_t *a )
|
||||||
{
|
{
|
||||||
if( s->ss_family == AF_INET )
|
if( s->ss_family == AF_INET )
|
||||||
{
|
{
|
||||||
@@ -1330,70 +1349,60 @@ NET_QueuePacket
|
|||||||
queue normal and lagged packets
|
queue normal and lagged packets
|
||||||
==================
|
==================
|
||||||
*/
|
*/
|
||||||
static qboolean NET_QueuePacket( netsrc_t sock, netadr_t *from, byte *data, size_t *length )
|
static qboolean NET_QueuePacket( int net_socket, netsrc_t sock, netadr_t *from, byte *data, size_t *length )
|
||||||
{
|
{
|
||||||
byte buf[NET_MAX_FRAGMENT];
|
byte buf[NET_MAX_FRAGMENT];
|
||||||
int ret, protocol;
|
int ret;
|
||||||
int net_socket;
|
|
||||||
WSAsize_t addr_len;
|
WSAsize_t addr_len;
|
||||||
struct sockaddr_storage addr = { 0 };
|
struct sockaddr_storage addr = { 0 };
|
||||||
|
|
||||||
*length = 0;
|
*length = 0;
|
||||||
|
|
||||||
for( protocol = 0; protocol < 2; protocol++ )
|
if( !NET_IsSocketValid( net_socket ))
|
||||||
|
return NET_LagPacket( false, sock, from, length, data );
|
||||||
|
|
||||||
|
addr_len = sizeof( addr );
|
||||||
|
ret = recvfrom( net_socket, buf, sizeof( buf ), 0, (struct sockaddr *)&addr, &addr_len );
|
||||||
|
|
||||||
|
NET_SockadrToNetadr( &addr, from );
|
||||||
|
|
||||||
|
if( !NET_IsSocketError( ret ))
|
||||||
{
|
{
|
||||||
switch( protocol )
|
if( ret < NET_MAX_FRAGMENT )
|
||||||
{
|
{
|
||||||
case 0: net_socket = net.ip_sockets[sock]; break;
|
// Transfer data
|
||||||
case 1: net_socket = net.ip6_sockets[sock]; break;
|
memcpy( data, buf, ret );
|
||||||
}
|
*length = ret;
|
||||||
|
|
||||||
if( !NET_IsSocketValid( net_socket ))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
addr_len = sizeof( addr );
|
|
||||||
ret = recvfrom( net_socket, buf, sizeof( buf ), 0, (struct sockaddr *)&addr, &addr_len );
|
|
||||||
|
|
||||||
NET_SockadrToNetadr( &addr, from );
|
|
||||||
|
|
||||||
if( !NET_IsSocketError( ret ))
|
|
||||||
{
|
|
||||||
if( ret < NET_MAX_FRAGMENT )
|
|
||||||
{
|
|
||||||
// Transfer data
|
|
||||||
memcpy( data, buf, ret );
|
|
||||||
*length = ret;
|
|
||||||
|
|
||||||
#if !XASH_DEDICATED
|
#if !XASH_DEDICATED
|
||||||
// check for split message
|
// check for split message
|
||||||
if( sock == NS_CLIENT && *(int *)data == NET_HEADER_SPLITPACKET )
|
if( sock == NS_CLIENT && *(int *)data == NET_HEADER_SPLITPACKET )
|
||||||
return NET_GetLong( data, ret, length, CL_GetSplitSize( ), CL_Protocol( ));
|
return NET_GetLong( data, ret, length, CL_GetSplitSize( ), CL_Protocol( ));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// lag the packet, if needed
|
// lag the packet, if needed
|
||||||
return NET_LagPacket( true, sock, from, length, data );
|
return NET_LagPacket( true, sock, from, length, data );
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Con_Reportf( "%s: oversize packet from %s\n", __func__, NET_AdrToString( *from ));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
int err = WSAGetLastError();
|
Con_Reportf( "%s: oversize packet from %s\n", __func__, NET_AdrToString( *from ));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int err = WSAGetLastError();
|
||||||
|
|
||||||
switch( err )
|
switch( err )
|
||||||
{
|
{
|
||||||
case WSAEWOULDBLOCK:
|
case WSAEWOULDBLOCK:
|
||||||
case WSAECONNRESET:
|
case WSAECONNRESET:
|
||||||
case WSAECONNREFUSED:
|
case WSAECONNREFUSED:
|
||||||
case WSAEMSGSIZE:
|
case WSAEMSGSIZE:
|
||||||
case WSAETIMEDOUT:
|
case WSAETIMEDOUT:
|
||||||
break;
|
break;
|
||||||
default: // let's continue even after errors
|
default: // let's continue even after errors
|
||||||
Con_DPrintf( S_ERROR "%s: %s from %s\n", __func__, NET_ErrorString(), NET_AdrToString( *from ));
|
Con_DPrintf( S_ERROR "%s: %s from %s\n", __func__, NET_ErrorString(), NET_AdrToString( *from ));
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1420,7 +1429,20 @@ qboolean NET_GetPacket( netsrc_t sock, netadr_t *from, byte *data, size_t *lengt
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return NET_QueuePacket( sock, from, data, length );
|
// Round-robin the socket to prevent constant activity on one starving the other.
|
||||||
|
int i = net.rr_state[sock];
|
||||||
|
int net_socket = i ? net.ip_sockets[sock] : net.ip6_sockets[sock];
|
||||||
|
|
||||||
|
if( NET_QueuePacket( net_socket, sock, from, data, length ))
|
||||||
|
{
|
||||||
|
net.rr_state[sock] = !i;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// try the other one before giving up
|
||||||
|
net_socket = i ? net.ip6_sockets[sock] : net.ip_sockets[sock];
|
||||||
|
net.rr_state[sock] = !i;
|
||||||
|
return NET_QueuePacket( net_socket, sock, from, data, length );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1591,7 +1613,7 @@ static int NET_IPSocket( const char *net_iface, int port, int family )
|
|||||||
return INVALID_SOCKET;
|
return INVALID_SOCKET;
|
||||||
}
|
}
|
||||||
|
|
||||||
if( NET_IsSocketError( ioctlsocket( net_socket, FIONBIO, (void*)&_true )))
|
if( !NET_MakeSocketNonBlocking( net_socket ))
|
||||||
{
|
{
|
||||||
struct timeval timeout;
|
struct timeval timeout;
|
||||||
|
|
||||||
@@ -1994,6 +2016,7 @@ void NET_Init( void )
|
|||||||
net.lagdata[i].next = &net.lagdata[i];
|
net.lagdata[i].next = &net.lagdata[i];
|
||||||
net.ip_sockets[i] = INVALID_SOCKET;
|
net.ip_sockets[i] = INVALID_SOCKET;
|
||||||
net.ip6_sockets[i] = INVALID_SOCKET;
|
net.ip6_sockets[i] = INVALID_SOCKET;
|
||||||
|
net.rr_state[i] = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if XASH_WIN32
|
#if XASH_WIN32
|
||||||
|
|||||||
@@ -74,6 +74,9 @@ void NET_SendPacket( netsrc_t sock, size_t length, const void *data, netadr_t to
|
|||||||
void NET_SendPacketEx( netsrc_t sock, size_t length, const void *data, netadr_t to, size_t splitsize );
|
void NET_SendPacketEx( netsrc_t sock, size_t length, const void *data, netadr_t to, size_t splitsize );
|
||||||
void NET_IP6BytesToNetadr( netadr_t *adr, const uint8_t *ip6 );
|
void NET_IP6BytesToNetadr( netadr_t *adr, const uint8_t *ip6 );
|
||||||
void NET_NetadrToIP6Bytes( uint8_t *ip6, const netadr_t *adr );
|
void NET_NetadrToIP6Bytes( uint8_t *ip6, const netadr_t *adr );
|
||||||
|
qboolean NET_IsSocketError( int retval );
|
||||||
|
qboolean NET_IsSocketValid( int socket );
|
||||||
|
qboolean NET_MakeSocketNonBlocking( int socket_fd );
|
||||||
|
|
||||||
static inline qboolean NET_IsLocalAddress( netadr_t adr )
|
static inline qboolean NET_IsLocalAddress( netadr_t adr )
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -101,3 +101,5 @@ static inline socklen_t NET_SockAddrLen( const struct sockaddr_storage *addr )
|
|||||||
}
|
}
|
||||||
|
|
||||||
net_gai_state_t NET_StringToSockaddr( const char *s, struct sockaddr_storage *sadr, qboolean nonblocking, int family );
|
net_gai_state_t NET_StringToSockaddr( const char *s, struct sockaddr_storage *sadr, qboolean nonblocking, int family );
|
||||||
|
void NET_NetadrToSockadr( netadr_t *a, struct sockaddr_storage *s );
|
||||||
|
void NET_SockadrToNetadr( const struct sockaddr_storage *s, netadr_t *a );
|
||||||
|
|||||||
@@ -84,37 +84,37 @@ void Sound_Shutdown( void )
|
|||||||
|
|
||||||
uint GAME_EXPORT Sound_GetApproxWavePlayLen( const char *filepath )
|
uint GAME_EXPORT Sound_GetApproxWavePlayLen( const char *filepath )
|
||||||
{
|
{
|
||||||
string name;
|
string name;
|
||||||
file_t *f;
|
|
||||||
wavehdr_t wav;
|
|
||||||
size_t filesize;
|
|
||||||
uint msecs;
|
|
||||||
|
|
||||||
Q_strncpy( name, filepath, sizeof( name ));
|
Q_strncpy( name, filepath, sizeof( name ));
|
||||||
COM_FixSlashes( name );
|
COM_FixSlashes( name );
|
||||||
|
|
||||||
f = FS_Open( name, "rb", false );
|
file_t *f = FS_Open( name, "rb", false );
|
||||||
if( !f )
|
if( !f )
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
wavehdr_t wav;
|
||||||
if( FS_Read( f, &wav, sizeof( wav )) != sizeof( wav ))
|
if( FS_Read( f, &wav, sizeof( wav )) != sizeof( wav ))
|
||||||
{
|
{
|
||||||
FS_Close( f );
|
FS_Close( f );
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
filesize = FS_FileLength( f );
|
// magic number from GoldSrc, seems to be header size
|
||||||
filesize -= 128; // magic number from GoldSrc, seems to be header size
|
size_t filesize = FS_FileLength( f ) - 128;
|
||||||
|
|
||||||
FS_Close( f );
|
FS_Close( f );
|
||||||
|
|
||||||
// is real wav file ?
|
// is real wav file ?
|
||||||
if( wav.riff_id != RIFFHEADER || wav.wave_id != WAVEHEADER || wav.fmt_id != FORMHEADER )
|
if( wav.riff_id != LittleLong( RIFFHEADER ) || wav.wave_id != LittleLong( WAVEHEADER ) || wav.fmt_id != LittleLong( FORMHEADER ))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if( wav.nAvgBytesPerSec >= 1000 )
|
int avgBytes = LittleLong( wav.nAvgBytesPerSec );
|
||||||
msecs = (uint)((float)filesize / ((float)wav.nAvgBytesPerSec / 1000.0f));
|
uint msecs;
|
||||||
else msecs = (uint)(((float)filesize / (float)wav.nAvgBytesPerSec) * 1000.0f);
|
|
||||||
|
if( avgBytes >= 1000 )
|
||||||
|
msecs = (uint)((float)filesize / ((float)avgBytes / 1000.0f));
|
||||||
|
else
|
||||||
|
msecs = (uint)(((float)filesize / (float)avgBytes) * 1000.0f);
|
||||||
|
|
||||||
return msecs;
|
return msecs;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ SYSTEM LOG
|
|||||||
*/
|
*/
|
||||||
int Sys_LogFileNo( void )
|
int Sys_LogFileNo( void )
|
||||||
{
|
{
|
||||||
|
if( !s_ld.logfile )
|
||||||
|
return -1;
|
||||||
return s_ld.logfileno;
|
return s_ld.logfileno;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +86,7 @@ void Sys_InitLog( void )
|
|||||||
|
|
||||||
if( Sys_CheckParm( "-log" ))
|
if( Sys_CheckParm( "-log" ))
|
||||||
{
|
{
|
||||||
if( !Sys_GetParmFromCmdLine( "-log", s_ld.log_path ) || !isalnum( s_ld.log_path[0] ))
|
if( !Sys_GetParmFromCmdLine( "-log", s_ld.log_path ) || !isalnum((byte)s_ld.log_path[0] ))
|
||||||
Q_strncpy( s_ld.log_path, "engine.log", sizeof( s_ld.log_path ));
|
Q_strncpy( s_ld.log_path, "engine.log", sizeof( s_ld.log_path ));
|
||||||
|
|
||||||
COM_DefaultExtension( s_ld.log_path, ".log", sizeof( s_ld.log_path ));
|
COM_DefaultExtension( s_ld.log_path, ".log", sizeof( s_ld.log_path ));
|
||||||
@@ -104,7 +106,17 @@ void Sys_InitLog( void )
|
|||||||
// create log if needed
|
// create log if needed
|
||||||
if( s_ld.log_active )
|
if( s_ld.log_active )
|
||||||
{
|
{
|
||||||
s_ld.logfile = fopen( s_ld.log_path, mode );
|
const char *basedir = getenv( "XASH3D_BASEDIR" );
|
||||||
|
|
||||||
|
if( !COM_StringEmptyOrNULL( basedir ) && s_ld.log_path[0] != '/' )
|
||||||
|
{
|
||||||
|
char fullpath[MAX_SYSPATH];
|
||||||
|
Q_snprintf( fullpath, sizeof( fullpath ), "%s/%s", basedir, s_ld.log_path );
|
||||||
|
s_ld.logfile = fopen( fullpath, mode );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !s_ld.logfile )
|
||||||
|
s_ld.logfile = fopen( s_ld.log_path, mode );
|
||||||
|
|
||||||
if ( !s_ld.logfile )
|
if ( !s_ld.logfile )
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -22,41 +22,69 @@ GNU General Public License for more details.
|
|||||||
|
|
||||||
void *ANDROID_LoadLibrary( const char *path )
|
void *ANDROID_LoadLibrary( const char *path )
|
||||||
{
|
{
|
||||||
const char *libdir[2], *name = COM_FileWithoutPath( path );
|
const char *name = COM_FileWithoutPath( path );
|
||||||
char fullpath[MAX_SYSPATH];
|
|
||||||
void *handle;
|
void *handle;
|
||||||
|
|
||||||
libdir[0] = getenv( "XASH3D_GAMELIBDIR" ); // TODO: remove this once distributing games from APKs will be deprecated
|
Con_Reportf( "%s: loading \"%s\" (name: \"%s\")\n", __func__, path, name );
|
||||||
libdir[1] = NULL; // TODO: put here data directory where libraries will be downloaded to
|
|
||||||
|
|
||||||
for( int i = 0; i < ARRAYSIZE( libdir ); i++ )
|
// TODO: remove this once distributing games from APKs will be deprecated
|
||||||
|
const char *gamelibdir = getenv( "XASH3D_GAMELIBDIR" );
|
||||||
|
if( !COM_StringEmptyOrNULL( gamelibdir ))
|
||||||
{
|
{
|
||||||
// this is an APK directory, get base path
|
char fullpath[MAX_SYSPATH];
|
||||||
const char *p = i == 0 ? name : path;
|
Q_snprintf( fullpath, sizeof( fullpath ), "%s/%s", gamelibdir, name );
|
||||||
|
|
||||||
if( !libdir[i] )
|
Con_Reportf( "%s: trying APK path \"%s\"\n", __func__, fullpath );
|
||||||
continue;
|
|
||||||
|
|
||||||
Q_snprintf( fullpath, sizeof( fullpath ), "%s/%s", libdir[i], p );
|
|
||||||
|
|
||||||
handle = dlopen( fullpath, RTLD_NOW );
|
handle = dlopen( fullpath, RTLD_NOW );
|
||||||
|
|
||||||
if( handle )
|
if( handle )
|
||||||
{
|
{
|
||||||
Con_Reportf( "%s: loading library %s successful\n", __func__, fullpath );
|
Con_Reportf( "%s: loaded from APK path\n", __func__ );
|
||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Con_Reportf( "%s: APK path failed: %s\n", __func__, dlerror() );
|
||||||
COM_PushLibraryError( dlerror() );
|
COM_PushLibraryError( dlerror() );
|
||||||
}
|
}
|
||||||
|
|
||||||
// find in system search path, that includes our APK
|
// try VFS
|
||||||
|
dll_user_t *hInst = FS_FindLibrary( path, false );
|
||||||
|
if( hInst )
|
||||||
|
{
|
||||||
|
Con_Reportf( "%s: VFS found \"%s\"\n", __func__, hInst->fullPath );
|
||||||
|
if( !hInst->custom_loader )
|
||||||
|
{
|
||||||
|
char libpath[MAX_SYSPATH];
|
||||||
|
Q_strncpy( libpath, hInst->fullPath, sizeof( libpath ));
|
||||||
|
Mem_Free( hInst );
|
||||||
|
|
||||||
|
handle = dlopen( libpath, RTLD_NOW );
|
||||||
|
if( handle )
|
||||||
|
{
|
||||||
|
Con_Reportf( "%s: loaded from VFS path\n", __func__ );
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
Con_Reportf( "%s: VFS dlopen failed: %s\n", __func__, dlerror() );
|
||||||
|
COM_PushLibraryError( dlerror() );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Con_Reportf( "%s: VFS entry has custom loader, skipping\n", __func__ );
|
||||||
|
COM_PushLibraryError( "custom loader not available on Android" );
|
||||||
|
Mem_Free( hInst );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// find in system search path (APK's LD_LIBRARY_PATH)
|
||||||
|
Con_Reportf( "%s: trying LD_LIBRARY_PATH for \"%s\"\n", __func__, name );
|
||||||
handle = dlopen( name, RTLD_NOW );
|
handle = dlopen( name, RTLD_NOW );
|
||||||
if( handle )
|
if( handle )
|
||||||
{
|
{
|
||||||
Con_Reportf( "%s: loading library %s from LD_LIBRARY_PATH successful\n", __func__, name );
|
Con_Reportf( "%s: loaded from LD_LIBRARY_PATH\n", __func__ );
|
||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Con_Reportf( "%s: all paths failed for \"%s\"\n", __func__, path );
|
||||||
COM_PushLibraryError( dlerror() );
|
COM_PushLibraryError( dlerror() );
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|||||||
@@ -40,6 +40,17 @@ void Platform_MessageBox( const char *title, const char *message, qboolean paren
|
|||||||
void Platform_SetStatus( const char *status );
|
void Platform_SetStatus( const char *status );
|
||||||
qboolean Platform_DebuggerPresent( void );
|
qboolean Platform_DebuggerPresent( void );
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
ORIENTATION_UNKNOWN = 0,
|
||||||
|
ORIENTATION_LANDSCAPE,
|
||||||
|
ORIENTATION_LANDSCAPE_FLIPPED,
|
||||||
|
ORIENTATION_PORTRAIT,
|
||||||
|
ORIENTATION_PORTRAIT_FLIPPED
|
||||||
|
} platform_orientation_t;
|
||||||
|
|
||||||
|
platform_orientation_t Platform_GetDisplayOrientation( void );
|
||||||
|
|
||||||
// legacy iOS port functions
|
// legacy iOS port functions
|
||||||
#if XASH_IOS
|
#if XASH_IOS
|
||||||
int IOS_GetArgs( char ***argv );
|
int IOS_GetArgs( char ***argv );
|
||||||
@@ -64,6 +75,7 @@ char *Posix_Input( void );
|
|||||||
void SDLash_Init( void );
|
void SDLash_Init( void );
|
||||||
void SDLash_Shutdown( void );
|
void SDLash_Shutdown( void );
|
||||||
void SDLash_NanoSleep( int nsec );
|
void SDLash_NanoSleep( int nsec );
|
||||||
|
qboolean SDLash_GyroIsAvailable( void );
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if XASH_ANDROID
|
#if XASH_ANDROID
|
||||||
@@ -213,11 +225,13 @@ static inline void Sys_RestoreCrashHandler( void )
|
|||||||
static inline qboolean Platform_LibraryExists( const char *name, qboolean gamedironly )
|
static inline qboolean Platform_LibraryExists( const char *name, qboolean gamedironly )
|
||||||
{
|
{
|
||||||
#if XASH_ANDROID
|
#if XASH_ANDROID
|
||||||
// sorry, unimplemented
|
// when libs come from a separate APK (cs16client, tf15client, …) we can't see them
|
||||||
return false;
|
// from the VFS; trust the launcher.
|
||||||
#else
|
if( !COM_StringEmptyOrNULL( getenv( "XASH3D_GAMELIBDIR" )))
|
||||||
return g_fsapi.FileExists( name, gamedironly );
|
return true;
|
||||||
#endif
|
#endif
|
||||||
|
// FIXME: use FS_FindLibrary
|
||||||
|
return g_fsapi.FileExists( name, gamedironly );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -27,15 +27,10 @@ static qboolean enable_libbacktrace;
|
|||||||
|
|
||||||
static void Sys_BacktraceError( void *data, const char *msg, int errnum )
|
static void Sys_BacktraceError( void *data, const char *msg, int errnum )
|
||||||
{
|
{
|
||||||
if( errnum < 0 )
|
Con_Printf( S_ERROR "libbacktrace: %s (%d)\n", msg, errnum );
|
||||||
{
|
|
||||||
Con_Printf( S_ERROR "no symbol info, no libbacktrace\n" );
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Con_Printf( S_ERROR "libbacktrace error: %s (%d)\n", msg, errnum );
|
if( errnum > 0 )
|
||||||
|
enable_libbacktrace = false;
|
||||||
enable_libbacktrace = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct print_data
|
struct print_data
|
||||||
@@ -45,8 +40,16 @@ struct print_data
|
|||||||
int len;
|
int len;
|
||||||
int idx;
|
int idx;
|
||||||
int logfd;
|
int logfd;
|
||||||
|
qboolean skip_wrappers;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static qboolean Sys_IsCrashHandlerFrame( const char *name )
|
||||||
|
{
|
||||||
|
if( !name )
|
||||||
|
return false;
|
||||||
|
return !Q_strcmp( name, "Sys_Crash" ) || !Q_strcmp( name, "Sys_CrashDetailsLibbacktrace" );
|
||||||
|
}
|
||||||
|
|
||||||
static void Sys_AppendPrint( struct print_data *pd, const char *fmt, ... )
|
static void Sys_AppendPrint( struct print_data *pd, const char *fmt, ... )
|
||||||
{
|
{
|
||||||
va_list va;
|
va_list va;
|
||||||
@@ -58,13 +61,10 @@ static void Sys_AppendPrint( struct print_data *pd, const char *fmt, ... )
|
|||||||
|
|
||||||
if( len > 0 )
|
if( len > 0 )
|
||||||
{
|
{
|
||||||
char ch = '\n';
|
if( pd->logfd >= 0 )
|
||||||
|
write( pd->logfd, pd->message, len );
|
||||||
write( pd->logfd, pd->message, len );
|
|
||||||
write( pd->logfd, &ch, 1 );
|
|
||||||
|
|
||||||
write( STDERR_FILENO, pd->message, len );
|
write( STDERR_FILENO, pd->message, len );
|
||||||
write( STDERR_FILENO, &ch, 1 );
|
|
||||||
|
|
||||||
pd->message += len;
|
pd->message += len;
|
||||||
pd->len += len;
|
pd->len += len;
|
||||||
@@ -84,6 +84,13 @@ static void Sys_BacktracePrintSyminfo( void *data, uintptr_t pc, const char *sym
|
|||||||
Dl_info dlinfo = { 0 };
|
Dl_info dlinfo = { 0 };
|
||||||
const char *module_name;
|
const char *module_name;
|
||||||
|
|
||||||
|
if( pd->skip_wrappers )
|
||||||
|
{
|
||||||
|
if( Sys_IsCrashHandlerFrame( symname ))
|
||||||
|
return;
|
||||||
|
pd->skip_wrappers = false;
|
||||||
|
}
|
||||||
|
|
||||||
if( dladdr((void *)pc, &dlinfo ))
|
if( dladdr((void *)pc, &dlinfo ))
|
||||||
module_name = dlinfo.dli_fname;
|
module_name = dlinfo.dli_fname;
|
||||||
else module_name = NULL;
|
else module_name = NULL;
|
||||||
@@ -110,6 +117,13 @@ static int Sys_BacktracePrintFull( void *data, uintptr_t pc, const char *filenam
|
|||||||
Dl_info dlinfo = { 0 };
|
Dl_info dlinfo = { 0 };
|
||||||
const char *module_name;
|
const char *module_name;
|
||||||
|
|
||||||
|
if( pd->skip_wrappers )
|
||||||
|
{
|
||||||
|
if( Sys_IsCrashHandlerFrame( function ))
|
||||||
|
return 0;
|
||||||
|
pd->skip_wrappers = false;
|
||||||
|
}
|
||||||
|
|
||||||
if( dladdr((void *)pc, &dlinfo ))
|
if( dladdr((void *)pc, &dlinfo ))
|
||||||
module_name = dlinfo.dli_fname;
|
module_name = dlinfo.dli_fname;
|
||||||
else module_name = NULL;
|
else module_name = NULL;
|
||||||
@@ -136,12 +150,13 @@ int Sys_CrashDetailsLibbacktrace( int logfd, char *message, int len, size_t max_
|
|||||||
struct print_data pd =
|
struct print_data pd =
|
||||||
{
|
{
|
||||||
.message = message + len,
|
.message = message + len,
|
||||||
.message_size = sizeof( message ) - len,
|
.message_size = max_len - len,
|
||||||
.logfd = logfd,
|
.logfd = logfd,
|
||||||
.len = len,
|
.len = len,
|
||||||
|
.skip_wrappers = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
backtrace_full( g_bt_state, 1, Sys_BacktracePrintFull, Sys_BacktracePrintError, &pd );
|
backtrace_full( g_bt_state, 0, Sys_BacktracePrintFull, Sys_BacktracePrintError, &pd );
|
||||||
|
|
||||||
return pd.len;
|
return pd.len;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,65 +15,86 @@ GNU General Public License for more details.
|
|||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
// while this is mostly POSIX-compatible functions
|
|
||||||
// the contents of ucontext_t is platform-dependent
|
|
||||||
// before adding new OS here, check platform.h
|
|
||||||
#define _XOPEN_SOURCE 1 // required for ucontext
|
|
||||||
#if XASH_FREEBSD || XASH_NETBSD || XASH_OPENBSD || XASH_ANDROID || XASH_LINUX || XASH_APPLE
|
#if XASH_FREEBSD || XASH_NETBSD || XASH_OPENBSD || XASH_ANDROID || XASH_LINUX || XASH_APPLE
|
||||||
#ifndef XASH_OPENBSD
|
|
||||||
#include <ucontext.h>
|
|
||||||
#endif
|
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
|
#if XASH_ANDROID
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <android/log.h>
|
||||||
|
#endif
|
||||||
#include "library.h"
|
#include "library.h"
|
||||||
#include "input.h"
|
#include "input.h"
|
||||||
#include "crash.h"
|
#include "crash.h"
|
||||||
|
|
||||||
|
#if XASH_ANDROID
|
||||||
|
static char crashlog_path[MAX_OSPATH];
|
||||||
|
#endif
|
||||||
|
|
||||||
static qboolean have_libbacktrace = false;
|
static qboolean have_libbacktrace = false;
|
||||||
|
static char crash_message[8192];
|
||||||
|
|
||||||
static void Sys_Crash( int signal, siginfo_t *si, void *context )
|
static void Sys_Crash( int signal, siginfo_t *si, void *context )
|
||||||
{
|
{
|
||||||
char message[8192];
|
|
||||||
int len, logfd, i = 0;
|
|
||||||
qboolean detailed_message = false;
|
|
||||||
|
|
||||||
// safe actions first, stack and memory may be corrupted
|
// safe actions first, stack and memory may be corrupted
|
||||||
len = Q_snprintf( message, sizeof( message ), "Ver: " XASH_ENGINE_NAME " " XASH_VERSION " (build %i-%s-%s, %s-%s)\n",
|
int len = Q_snprintf( crash_message, sizeof( crash_message ), "Ver: " XASH_ENGINE_NAME " " XASH_VERSION " (build %i-%s-%s, %s-%s)\n",
|
||||||
Q_buildnum(), g_buildcommit, g_buildbranch, Q_buildos(), Q_buildarch() );
|
Q_buildnum(), g_buildcommit, g_buildbranch, Q_buildos(), Q_buildarch() );
|
||||||
|
|
||||||
#if !XASH_FREEBSD && !XASH_NETBSD && !XASH_OPENBSD && !XASH_APPLE
|
#if !XASH_FREEBSD && !XASH_NETBSD && !XASH_OPENBSD && !XASH_APPLE
|
||||||
len += Q_snprintf( message + len, sizeof( message ) - len, "Crash: signal %d errno %d with code %d at %p %p\n", signal, si->si_errno, si->si_code, si->si_addr, si->si_ptr );
|
len += Q_snprintf( crash_message + len, sizeof( crash_message ) - len, "Crash: signal %d errno %d with code %d at %p %p\n", signal, si->si_errno, si->si_code, si->si_addr, si->si_ptr );
|
||||||
#else
|
#else
|
||||||
len += Q_snprintf( message + len, sizeof( message ) - len, "Crash: signal %d errno %d with code %d at %p\n", signal, si->si_errno, si->si_code, si->si_addr );
|
len += Q_snprintf( crash_message + len, sizeof( crash_message ) - len, "Crash: signal %d errno %d with code %d at %p\n", signal, si->si_errno, si->si_code, si->si_addr );
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
write( STDERR_FILENO, message, len );
|
write( STDERR_FILENO, crash_message, len );
|
||||||
|
|
||||||
|
#if XASH_ANDROID
|
||||||
|
__android_log_write( ANDROID_LOG_FATAL, "Xash", crash_message );
|
||||||
|
#endif
|
||||||
|
|
||||||
// now get log fd and write trace directly to log
|
// now get log fd and write trace directly to log
|
||||||
logfd = Sys_LogFileNo();
|
int logfd = Sys_LogFileNo();
|
||||||
write( logfd, message, len );
|
if( logfd >= 0 )
|
||||||
|
write( logfd, crash_message, len );
|
||||||
|
|
||||||
#if HAVE_LIBBACKTRACE
|
#if HAVE_LIBBACKTRACE
|
||||||
|
qboolean detailed_message = false;
|
||||||
if( have_libbacktrace && !detailed_message )
|
if( have_libbacktrace && !detailed_message )
|
||||||
{
|
{
|
||||||
len = Sys_CrashDetailsLibbacktrace( logfd, message, len, sizeof( message ));
|
len = Sys_CrashDetailsLibbacktrace( logfd, crash_message, len, sizeof( crash_message ));
|
||||||
detailed_message = true;
|
detailed_message = true;
|
||||||
}
|
}
|
||||||
#endif // HAVE_LIBBACKTRACE
|
#endif // HAVE_LIBBACKTRACE
|
||||||
|
|
||||||
|
#if XASH_ANDROID
|
||||||
|
// also write to a dedicated crash report file the Java side picks up on next launch
|
||||||
|
if( crashlog_path[0] )
|
||||||
|
{
|
||||||
|
int crashfd = open( crashlog_path, O_WRONLY|O_CREAT|O_TRUNC, 0644 );
|
||||||
|
if( crashfd >= 0 )
|
||||||
|
{
|
||||||
|
write( crashfd, crash_message, len );
|
||||||
|
close( crashfd );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// JNI/SDL calls aren't safe from a signal handler on Android
|
||||||
|
_exit( 128 + signal );
|
||||||
|
#else
|
||||||
#if !XASH_DEDICATED
|
#if !XASH_DEDICATED
|
||||||
IN_SetMouseGrab( false );
|
IN_SetMouseGrab( false );
|
||||||
#endif
|
#endif
|
||||||
host.status = HOST_CRASHED;
|
host.status = HOST_CRASHED;
|
||||||
|
|
||||||
// put MessageBox as Sys_Error
|
// put MessageBox as Sys_Error
|
||||||
Platform_MessageBox( "Xash Error", message, false );
|
Platform_MessageBox( "Xash Error", crash_message, false );
|
||||||
|
|
||||||
// log saved, now we can try to save configs and close log correctly, it may crash
|
// log saved, now we can try to save configs and close log correctly, it may crash
|
||||||
if( host.type == HOST_NORMAL )
|
if( host.type == HOST_NORMAL )
|
||||||
CL_Crashed();
|
CL_Crashed();
|
||||||
|
|
||||||
Sys_Quit( "crashed" );
|
Sys_Quit( "crashed" );
|
||||||
|
#endif // XASH_ANDROID
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct sigaction old_segv_act;
|
static struct sigaction old_segv_act;
|
||||||
@@ -89,6 +110,22 @@ void Sys_SetupCrashHandler( const char *argv0 )
|
|||||||
.sa_flags = SA_SIGINFO | SA_ONSTACK,
|
.sa_flags = SA_SIGINFO | SA_ONSTACK,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#if XASH_ANDROID
|
||||||
|
const char *crashdir = getenv( "XASH3D_CRASH_DIR" );
|
||||||
|
|
||||||
|
if( !COM_StringEmptyOrNULL( crashdir ))
|
||||||
|
Q_snprintf( crashlog_path, sizeof( crashlog_path ), "%s/crash.log", crashdir );
|
||||||
|
|
||||||
|
// unblock the engine/SDL_main thread just in case
|
||||||
|
sigset_t set;
|
||||||
|
sigemptyset( &set );
|
||||||
|
sigaddset( &set, SIGSEGV );
|
||||||
|
sigaddset( &set, SIGABRT );
|
||||||
|
sigaddset( &set, SIGBUS );
|
||||||
|
sigaddset( &set, SIGILL );
|
||||||
|
pthread_sigmask( SIG_UNBLOCK, &set, NULL );
|
||||||
|
#endif
|
||||||
|
|
||||||
#if HAVE_LIBBACKTRACE
|
#if HAVE_LIBBACKTRACE
|
||||||
have_libbacktrace = Sys_SetupLibbacktrace( argv0 );
|
have_libbacktrace = Sys_SetupLibbacktrace( argv0 );
|
||||||
#endif // HAVE_LIBBACKTRACE
|
#endif // HAVE_LIBBACKTRACE
|
||||||
@@ -97,7 +134,6 @@ void Sys_SetupCrashHandler( const char *argv0 )
|
|||||||
sigaction( SIGABRT, &act, &old_abrt_act );
|
sigaction( SIGABRT, &act, &old_abrt_act );
|
||||||
sigaction( SIGBUS, &act, &old_bus_act );
|
sigaction( SIGBUS, &act, &old_bus_act );
|
||||||
sigaction( SIGILL, &act, &old_ill_act );
|
sigaction( SIGILL, &act, &old_ill_act );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sys_RestoreCrashHandler( void )
|
void Sys_RestoreCrashHandler( void )
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ qboolean SNDDMA_Init( void )
|
|||||||
|
|
||||||
memset( &desired, 0, sizeof( desired ) );
|
memset( &desired, 0, sizeof( desired ) );
|
||||||
desired.freq = SOUND_DMA_SPEED;
|
desired.freq = SOUND_DMA_SPEED;
|
||||||
desired.format = AUDIO_S16LSB;
|
desired.format = AUDIO_S16SYS;
|
||||||
desired.samples = 1024;
|
desired.samples = 1024;
|
||||||
desired.channels = 2;
|
desired.channels = 2;
|
||||||
desired.callback = SDL_SoundCallback;
|
desired.callback = SDL_SoundCallback;
|
||||||
@@ -119,7 +119,7 @@ qboolean SNDDMA_Init( void )
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if( obtained.format != AUDIO_S16LSB )
|
if( obtained.format != AUDIO_S16SYS )
|
||||||
{
|
{
|
||||||
Con_Printf( "SDL audio format %d unsupported.\n", obtained.format );
|
Con_Printf( "SDL audio format %d unsupported.\n", obtained.format );
|
||||||
goto fail;
|
goto fail;
|
||||||
|
|||||||
@@ -368,6 +368,11 @@ static void SDLash_EventHandler( SDL_Event *event )
|
|||||||
#endif
|
#endif
|
||||||
SDLash_HandleGameControllerEvent( event );
|
SDLash_HandleGameControllerEvent( event );
|
||||||
break;
|
break;
|
||||||
|
#if SDL_VERSION_ATLEAST( 2, 0, 14 )
|
||||||
|
case SDL_SENSORUPDATE:
|
||||||
|
SDLash_SensorUpdate( event->sensor );
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
|
||||||
case SDL_WINDOWEVENT:
|
case SDL_WINDOWEVENT:
|
||||||
if( event->window.windowID != SDL_GetWindowID( host.hWnd ) )
|
if( event->window.windowID != SDL_GetWindowID( host.hWnd ) )
|
||||||
|
|||||||
@@ -35,5 +35,15 @@ void SDLash_FreeCursors( void );
|
|||||||
//
|
//
|
||||||
void SDLash_HandleGameControllerEvent( SDL_Event *ev );
|
void SDLash_HandleGameControllerEvent( SDL_Event *ev );
|
||||||
|
|
||||||
|
//
|
||||||
|
// sensor_sdl2.c
|
||||||
|
//
|
||||||
|
void SDLash_InitSensors( void );
|
||||||
|
void SDLash_ShutdownSensors( void );
|
||||||
|
qboolean SDLash_GyroIsAvailable( void );
|
||||||
|
#if SDL_VERSION_ATLEAST( 2, 0, 14 )
|
||||||
|
void SDLash_SensorUpdate( SDL_SensorEvent sensor );
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif // XASH_SDL
|
#endif // XASH_SDL
|
||||||
#endif // KEYWRAPPER_H
|
#endif // KEYWRAPPER_H
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user